Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
d37d3e3
docs(adr): add Spec 01 audit report and target-architecture ADR
claude Jun 28, 2026
9b97dbb
chore: record session tool permissions in local settings
claude Jun 28, 2026
5ca5bb1
docs(adr): add Spec 02 and conformance inventory (audit-first)
claude Jun 28, 2026
7179a95
docs(adr): revise Spec 02 — resolve B-1 (vello_cpu), add Gelasio
claude Jun 28, 2026
6850e7e
docs(adr): sync Spec 02 inventory with resolved B-1 and B-10
claude Jun 28, 2026
47ae789
build: document and enforce loki-opc MIT vs suite Apache-2.0
claude Jun 28, 2026
cd4869e
chore: resolve Spec 01 audit findings A-4, A-6, A-9, A-10
claude Jun 28, 2026
449ccfb
refactor: resolve Spec 01 audit A-7 (unsafe policy) and A-14 (android…
claude Jun 28, 2026
084bf0a
build: enforce 300-line file ceiling (A-2) + split 3 test-bloated files
claude Jun 28, 2026
5d29fbc
fix+enforce: resolve audit A-5 (unwrap/expect), A-11 (TODO), A-12 (na…
claude Jun 28, 2026
fb62103
fix: replace the 1280px window_width with a measured Viewport (A-1, A…
claude Jun 28, 2026
4c61904
fix+enforce: close A-8 (render→ui edge) and A-13 (enforcement gates)
claude Jun 29, 2026
737bec8
docs(adr): sync Spec 02 inventory — B-11 unblocked (Spec 01 pipeline …
claude Jun 29, 2026
03a66fe
feat(conformance): appthere-conformance crate + schema axis (Spec 02 …
claude Jun 29, 2026
dac13db
feat(conformance): NormalizedModel for Document — round-trip axis on …
claude Jun 29, 2026
a5226b8
test(ooxml): wire import→export→import round-trip shape via conforman…
claude Jun 29, 2026
94f2e73
fix(ooxml): emit all read-back run properties on DOCX export (close r…
claude Jun 29, 2026
2d7c2bd
fix(ooxml): footnote markers carry superscript via char style, not ex…
claude Jun 29, 2026
7b408a7
feat(conformance): deepen round-trip canonicalization — table interio…
claude Jun 29, 2026
9bcdd52
test(odf): wire ODT import→export→import round-trip shape via conform…
claude Jun 29, 2026
76a1378
feat(conformance): NormalizedModel for Workbook + XLSX round-trip sha…
claude Jun 29, 2026
69e2f98
test(ooxml): fold genuine round-trip tests through the normalized dif…
claude Jun 29, 2026
896b2f2
docs(spec-02): mark B-7 round-trip axis largely done — deepened canon…
claude Jun 29, 2026
f8aadc0
chore: update Cargo.lock for conformance sheet-model + loki-odf dev-d…
claude Jun 29, 2026
a2adf42
docs(adr): add Spec 03 — Responsive UI Foundation
claude Jun 29, 2026
bf61f75
docs(adr): Spec 03 responsive audit — Blitz surface, viewport status,…
claude Jun 29, 2026
d9ce510
feat(responsive): Spec 03 M1 — Breakpoint foundation on the unified V…
claude Jun 29, 2026
3a671bc
feat(responsive): Spec 03 M2 — page-fit renderer switch with hysteresis
claude Jun 29, 2026
211c523
feat(responsive): Spec 03 M3 — font-substitution warning redesign
claude Jun 29, 2026
3dff32f
feat(responsive): Spec 03 M4 — bounded, centred reflow reading measure
claude Jun 29, 2026
1264209
feat(responsive): Spec 03 M5 — cross-UI Compact sweep
claude Jun 29, 2026
f8d085f
docs(adr): add Spec 04 — Ribbon UI Refinement
claude Jun 29, 2026
acb00a6
docs(adr): Spec 04 ribbon audit — capability table, framework invento…
claude Jun 29, 2026
7032828
feat(ribbon): Spec 04 M1+M2 — Home→Write rename + labeled-group stand…
claude Jun 29, 2026
9ce74ea
feat(loro-bridge): native inline image mapping (live-editable)
claude Jun 29, 2026
eebf466
feat(loro-bridge): native inline footnote/endnote mapping
claude Jun 29, 2026
f3c590b
feat(loro-bridge): native table mapping (live per-cell content)
claude Jun 29, 2026
18f6b20
feat(loki-text): Spec 04 M4 Insert tab — hyperlink (increment 1)
claude Jun 29, 2026
ec586eb
feat(loki-text): Spec 04 M4 Insert tab — image (increment 2)
claude Jun 29, 2026
7e13d25
feat(loro-bridge): nested-addressing mutation extension (table cells)
claude Jun 29, 2026
dc629be
feat(loro-bridge): footnote/endnote bodies as live CRDT containers
claude Jun 29, 2026
1890532
feat(loro-bridge): BlockPath note-descent — nested addressing complete
claude Jun 29, 2026
fb46b31
feat(loki-layout): nested-editing producer seam (PageParagraphData.path)
claude Jun 29, 2026
53897b5
feat(loki-text): nested-editing position seam (DocumentPosition.path)
claude Jun 29, 2026
2106194
feat(loki-layout): emit editing data for footnote bodies (+ fix block…
claude Jun 29, 2026
6c503a7
feat(loki-layout): emit editing data for table cells
claude Jun 29, 2026
15b543e
feat(loki-text): route editor text mutations through the cursor Block…
claude Jun 29, 2026
ea1a834
Add path-aware block split/merge for nested editing
claude Jun 30, 2026
e6dbaf6
Align caret origin with glyphs in vertically-aligned table cells
claude Jun 30, 2026
4083d25
Add Insert → Table and Insert → Footnote controls
claude Jun 30, 2026
10fecdd
Add Spec 05 — Style Management Panel design spec
claude Jun 30, 2026
d22a2ad
Add Spec 05 style-management audit report
claude Jun 30, 2026
62ca4d8
Spec 05 M1: provenance-aware style resolution layer
claude Jun 30, 2026
57af03a
Spec 05 M3: sanctioned conditional-panel pattern + AtPanelHost
claude Jun 30, 2026
8da4ed0
Spec 05 M2: resolved-vs-overridden inspector row model
claude Jun 30, 2026
28c8c80
Spec 05 M2: read-only provenance inspector view
claude Jul 1, 2026
0f525c5
Spec 05 M2: reset-to-inherited affordance
claude Jul 1, 2026
d86a657
Spec 05 M2: jump-to-ancestor navigation
claude Jul 1, 2026
9f38cc2
Spec 05 M2: built-in-style identification (SM-11 decision)
claude Jul 1, 2026
b9c9249
Spec 05 M2: staged display of pending overrides
claude Jul 1, 2026
55a6b33
Spec 05 M2: mark milestone substantively complete
claude Jul 1, 2026
93d2a40
Spec 05 M4: hierarchy queries + impact-preview model
claude Jul 2, 2026
beac8d7
Spec 05 M4: impact preview before Apply
claude Jul 2, 2026
a8742ee
Spec 05 M4: cycle-guarded re-parenting
claude Jul 2, 2026
fc58061
Spec 05 M4: inheritance-tree visualization
claude Jul 2, 2026
1ffdfc5
Spec 05 M5: delete-with-orphan-handling primitive
claude Jul 4, 2026
319214d
Spec 05 M5: delete UI with built-in protection
claude Jul 4, 2026
7677956
Spec 05 M5: create-from-parent
claude Jul 4, 2026
c2bcba1
Spec 05 M6: character-family inspector model
claude Jul 4, 2026
dbe9696
Spec 05 M6: linked family surface (consumes character model)
claude Jul 4, 2026
ff49798
feat(loki-text): dedicated character-style panel (Spec 05 M6)
claude Jul 4, 2026
3b406e3
feat(loki-text): list-style family panel (Spec 05 M6)
claude Jul 4, 2026
3654dff
feat(loki-text): Compact posture for the style panel (Spec 05 M7)
claude Jul 4, 2026
23ae0ea
docs(adr): add Spec 06 (benchmarking & memory tracking) + audit
claude Jul 4, 2026
423b390
feat(loki-bench): benchmark harness + two-axis split (Spec 06 M1)
claude Jul 4, 2026
e3f26b3
feat(loki-bench): portable benches for each §6 target (Spec 06 M2)
claude Jul 4, 2026
87f1ebf
chore: update Cargo.lock for loki-bench M2 dev-dependencies
claude Jul 4, 2026
ae606db
feat(loki-bench): continuous memory tracking + Arc guard (Spec 06 M3)
claude Jul 4, 2026
937f372
feat(loki-bench): leak detection for the three culprits (Spec 06 M4)
claude Jul 4, 2026
278626e
feat(loki-bench): peak-RSS + budgets, GPU frame-time device-gated (Sp…
claude Jul 4, 2026
c731c9d
feat(loki-bench): tracking discipline + parity cadence (Spec 06 M6)
claude Jul 4, 2026
5adc60f
docs: deferred-features audit with verification (2026-07-04)
claude Jul 4, 2026
345b9b9
docs: correct stale deferral documentation (S-1..S-9 + Spec 02)
claude Jul 4, 2026
cce9772
Merge branch 'main' into claude/adr-docs-setup-ogwz5a
kevincarlson Jul 4, 2026
14f99a4
fix: repair botched main-merge (Cargo.toml/lock + unsafe policy)
claude Jul 4, 2026
c16d198
fix: register merged server crates + loki-bench in the dependency-dir…
claude Jul 4, 2026
5e22af8
fix(loki-renderer): set PageParagraphData.path in test fixture (merge…
claude Jul 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@
"Bash(git push *)",
"Bash(rustup show *)",
"Bash(echo \"exit: $?\")",
"Bash(claude plugin *)"
"Bash(claude plugin *)",
"Bash(cp \"/root/.claude/uploads/995af0a3-e865-5b96-b002-e90985880970/cc51b77a-spec01codebaseauditandarchitecture.md\" \"/home/user/loki/docs/adr/spec-01-codebase-audit-and-architecture.md\" && echo \"placed\" && ls -la docs/adr/spec-01-codebase-audit-and-architecture.md)",
"mcp__code-review-graph__list_graph_stats_tool"
]
},
"enableAllProjectMcpServers": true,
Expand Down
32 changes: 31 additions & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,35 @@ jobs:
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- name: License-header gate (per-crate SPDX on line 1)
# loki-opc is MIT (released standalone); the rest of the suite is
# Apache-2.0. See docs/adr/0010-per-crate-licensing.md.
run: python3 scripts/check-license-headers.py
- name: Panic-guard gate (no panic!/todo!/unimplemented! in lib src)
# Spec 01 audit A-6. unreachable! with a message is permitted.
run: python3 scripts/check-no-panics.py
- name: Unsafe-policy gate (forbid, or deny + allow-list)
# Spec 01 audit A-7. Only the 3 Android cdylib crates may use deny+allow.
run: python3 scripts/check-unsafe-policy.py
- name: File-ceiling gate (300-line ratchet)
# Spec 01 audit A-2. New files <= 300 lines; baselined debt may not grow.
run: python3 scripts/check-file-ceiling.py
- name: TODO-format gate (TODO(<topic>) tags)
# Spec 01 audit A-11. No bare TODO / FIXME / HACK / XXX in production code.
run: python3 scripts/check-todo-format.py
- name: Dependency-direction gate (acyclic, downhill-only)
# Spec 01 audit A-13 / ADR-0009. Fails on any uphill internal edge.
run: python3 scripts/check-dependency-direction.py
- name: Viewport-dimension guard (no assumed screen sizes)
# Spec 01 audit A-1. No bare 1280-class literals in editor input/viewport paths.
run: python3 scripts/check-no-hardcoded-viewport-dims.py
- name: Format check
run: cargo fmt --all --check
- name: Clippy (workspace, all features, warnings denied)
run: cargo clippy --workspace --all-features -- -D warnings
# `unwrap_used`/`expect_used` keep panicking calls out of library runtime
# code (audit A-5); clippy.toml exempts test code. Tooling (build.rs, the
# gen_templates bin) carries a scoped file-level allow.
run: cargo clippy --workspace --all-features -- -D warnings -D clippy::unwrap_used -D clippy::expect_used

build-and-test:
runs-on: ubuntu-latest
Expand All @@ -33,6 +58,11 @@ jobs:
- name: Install toolchain
uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Install xmllint (libxml2)
# appthere-conformance's schema axis (Spec 02 M2) shells to xmllint; install
# it so those tests actually run instead of skipping. The harness fails
# loudly if it is absent, so this keeps the schema axis genuinely exercised.
run: sudo apt-get update && sudo apt-get install -y libxml2-utils
# `--all-features` so the feature-gated format crates (pptx, xlsx) and their
# integration tests actually compile and run — under the default feature set
# `cargo test --workspace` silently skips `#![cfg(feature = ...)]` suites.
Expand Down
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,19 @@ loki-opc/.DS_Store

# cargo vendor output — dependencies are patched via patches/ not vendored
vendor/

# Stray debug/render dumps at the repo root (Spec 01 audit A-4). These are
# generated by examples/benches (e.g. `render_to_png` writes ./output.png) and
# must not be committed — keep them ignored so they can't drift back in.
/scratch.rs
/diff_flow.txt
/output.png
/iris*.png
/iris*.log
/test_output*.png
/test_output*.txt
/test_log*.txt

# Python bytecode cache (from scripts/*.py gate helpers)
__pycache__/
*.pyc
68 changes: 51 additions & 17 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,21 @@ These conventions apply to all crates in the workspace.
- **Error handling:** Use typed error enums with `thiserror`. Do not use `anyhow`
in library crates. Do not use `.unwrap()` or `.expect()` in library code
outside of `#[cfg(test)]` blocks.
- **Unsafe:** `#![forbid(unsafe_code)]` must be present in `lib.rs` for all
`appthere-ui` and future library crates.
- **License header:** Every `.rs` file must begin with:
`// SPDX-License-Identifier: Apache-2.0`
- **Unsafe:** every crate root must carry `#![forbid(unsafe_code)]`. The sole
exception is the three Android `cdylib` binaries (`loki-text`,
`loki-presentation`, `loki-spreadsheet`): their `#[unsafe(no_mangle)]`
`android_main` FFI entry point makes `forbid` impossible, so they use
`#![deny(unsafe_code)]` + a scoped `#[allow(unsafe_code)]` (emitted by
`loki_app_shell::android_main!`) and are enumerated in
`scripts/unsafe-policy-allowlist.txt`. Enforced in CI by
`scripts/check-unsafe-policy.py` (Spec 01 audit A-7). New crates must `forbid`.
- **License header:** Line 1 of every `.rs` file must be the SPDX identifier
matching that crate's `Cargo.toml` `license` field, line 2 the copyright.
The suite is Apache-2.0 (`// SPDX-License-Identifier: Apache-2.0`), **except
`loki-opc`, which is MIT** (`// SPDX-License-Identifier: MIT`) because it is
released as a standalone crate — see
[docs/adr/0010-per-crate-licensing.md](docs/adr/0010-per-crate-licensing.md).
Enforced in CI by `scripts/check-license-headers.py`.
- **Annotations:**
- `// COMPAT(dioxus-native): <explanation>` — marks any workaround for a
Dioxus Native / Blitz CSS or API limitation, including unconfirmed CSS
Expand Down Expand Up @@ -132,13 +143,21 @@ lines). The full list and a proposed split strategy live in
offenders are below. This is a dedicated split-pass backlog, not a per-change
blocker — but do not *grow* these files or add new ones over the ceiling.

The split pass is **in progress** — **11 of 43 files done** (≈32 remain). Two
techniques:
**The ceiling is now mechanically enforced** (Spec 01 audit A-2):
`scripts/check-file-ceiling.py` (CI) ratchets against
`scripts/file-ceiling-baseline.txt` — new files must be ≤300, baselined files
may not grow, and a file split to ≤300 must be removed from the baseline. So the
backlog can only shrink. When you split a file below the ceiling, drop its line
with `scripts/check-file-ceiling.py --update` (review the diff).

The split pass is **in progress** — current backlog is the **35** entries in the
baseline file. Two techniques:
1. *Inline-test extraction* (safest, no production-code change): move a file's
`#[cfg(test)] mod tests { … }` into a sibling `<name>_tests.rs` referenced via
`#[cfg(test)] #[path = "<name>_tests.rs"] mod tests;`. Done 2026-06-21 for
`block.rs`, `docx/mapper/{paragraph,numbering,mod,table}.rs`, `odt/import.rs`,
`odt/mapper/lists.rs`, `layout/result.rs`, `renderer/render_layout.rs` — each
`odt/mapper/lists.rs`, `layout/result.rs`, `renderer/render_layout.rs`, and
2026-06-28 for `editing/hit_test.rs`, `xml_util.rs`, `pdf/src/page.rs` — each
was over the ceiling only because of a large inline test module.
2. *Directory split*: convert `foo.rs` → a `foo/` directory with section-cohesive
submodules, re-export the public entry points from `foo/mod.rs`, and move the
Expand All @@ -153,15 +172,18 @@ techniques:

| File | Current lines | Priority |
|---|---|---|
| `loki-layout/src/flow.rs` | 1612 | High |
| `loki-odf/src/odt/reader/styles.rs` | 1441 | High |
| `loki-odf/src/odt/reader/document.rs` | 1428 | High |
| `loki-layout/src/para.rs` | 1278 | High |
| `loki-spreadsheet/src/routes/editor/editor_inner.rs` | 1241 | High |
| `loki-ooxml/src/docx/write/document.rs` | 1169 | High |
| `loki-ooxml/src/docx/reader/document.rs` | 1126 | High |
| `loki-text/src/routes/editor/editor_inner.rs` | 1040 | High |
| … 24 more (300–600 lines) — see the audit (10 files split 2026-06-21) | | |
| `loki-layout/src/para.rs` | 1979 | High |
| `loki-layout/src/flow.rs` | 1953 | High |
| `loki-odf/src/odt/reader/styles.rs` | 1554 | High |
| `loki-odf/src/odt/reader/document.rs` | 1494 | High |
| `loki-spreadsheet/src/routes/editor/editor_inner.rs` | 1244 | High |
| `loki-ooxml/src/docx/reader/document.rs` | 1209 | High |
| `loki-ooxml/src/docx/write/document.rs` | 1073 | High |
| `loki-layout/src/resolve.rs` | 984 | High |
| … 27 more (6 over 600, 21 in 300–600) — see `scripts/file-ceiling-baseline.txt` | | |

*(Sizes above are from `scripts/file-ceiling-baseline.txt`, refreshed 2026-07-04;
the earlier numbers were stale — several files grew since first baselined.)*

(`odt/mapper/document.rs` (1094 lines) was split into the `odt/mapper/document/`
directory on 2026-06-26 — each module is now under the ceiling.)
Expand All @@ -179,7 +201,7 @@ but are **not perfectly round-tripped through the Loro CRDT**.
|---|---|---|
| `tab_stops` | Written as unreadable Debug string; not read back. | Medium |
| `background_color` (paragraph) | Written as Debug string; not decoded on read. | Low |
| `DocumentMeta` / `DublinCoreMeta` | Round-trips **through the Loro CRDT** as a JSON snapshot (`loro_bridge::meta`), so Publish-tab edits are durable and undoable. Still **not** written back to DOCX/ODT on export (export drops the extended Dublin Core fields). | Low |
| `DocumentMeta` / `DublinCoreMeta` | Round-trips **through the Loro CRDT** (`loro_bridge::meta`) **and is written back on export** — core properties + extended Dublin Core reach DOCX (`docProps/core.xml` + `custom.xml`) and ODT (`meta.xml`), tested by `metadata_round_trip.rs` / `extended_dublin_core_round_trips`. Remaining tail (not the Loro bridge): custom user properties, `meta:editing-duration`, and OOXML `docProps/app.xml` are still not written. | Low |

---

Expand Down Expand Up @@ -318,6 +340,18 @@ is documented in the Loki Text UI specification (v0.4).
5. All interactive elements: 44×44 px minimum, documented in a doc comment.
6. Mark Dioxus Native CSS limitations with `// COMPAT(dioxus-native): ...`

### Conditionally-mounted panels are components (ADR-0013)

A panel shown behind a condition must be a `#[component]` mounted **at the
boundary** — `{open().then(|| rsx! { Panel { .. } })}` — never a plain function
called inside `if cond { panel(..) }` with an early `return rsx!{}`. Only a
component owns a hook scope, so only a component can call `use_breakpoint()` (or
any hook) and adapt to the size class **without threading a `compact` flag**
through the parent (which grows it). Prefer hosting the panel in
`appthere_ui::AtPanelHost`, which reads the breakpoint and picks Compact-sheet vs
Expanded-side-panel posture for you. See
[docs/adr/0013-conditional-panels-are-components.md](docs/adr/0013-conditional-panels-are-components.md).

### Confirmed CSS properties (Dioxus Native 0.7.x / Blitz)

These work in production code:
Expand Down
Loading
Loading