Skip to content

Fit-and-finish batch: first-load perf, Format tab + color picker, layout & selection fixes, Android Save As#34

Merged
kevincarlson merged 3 commits into
mainfrom
claude/fit-and-finish-batch
Jul 13, 2026
Merged

Fit-and-finish batch: first-load perf, Format tab + color picker, layout & selection fixes, Android Save As#34
kevincarlson merged 3 commits into
mainfrom
claude/fit-and-finish-batch

Conversation

@kevincarlson

Copy link
Copy Markdown
Member

Summary

Two rounds of user-reported issues across startup, chrome, layout fidelity, selection, and Android.

Startup & window

  • Windows first-open freeze fixed at the root: the system-font scan (parley::FontContext::new(), a DirectWrite enumeration) ran synchronously inside EditorInner's first mount hook, blocking the loading view's first frame. New SharedFontResources (loki-layout) warms it on a background thread at app launch; the editor mount is now cheap, workers block on it, render paths use a non-blocking try_lock.
  • Window title "Loki Text" (was "Dioxus App"), 1280x800 default size, and the last session's window size restored on launch (loki-app-shell::window_geometry + AtWindowSizeSensor, debounced writes). Suite naming applied to Loki Calc / Loki Slides incl. Android labels and cargo-bundle metadata.
  • Home reflows before any document opens (AtViewportWidthSensor on the Home route; previously only the editor fed the responsive context).

Chrome

  • Font-substitution warning inverted: the status-bar chip is the always-on indicator; clicking it opens the detail panel above the ribbon (banner removed).
  • Save notification: save_message is now typed (SaveStatus). Successes are an auto-clearing status-bar chip (4 s, also cleared the instant the document goes dirty); errors keep the persistent banner.
  • Write tab slimmed (Document / History / Styles / Paragraph / Inline only): font size +/- removed, alignment lives solely in the paragraph style editor, and font colour + highlight moved to a new Format tab as full pickers - swatches, per-session recent colours, custom Hex/RGB/HSL/HSV/CMYK entry (font colour; highlight is constrained to the named HighlightColor palette, TODO(highlight-custom-color)). New reusable appthere-ui components (AtColorPickerTrigger/AtColorPickerPanel, docked above the ribbon - popovers inside the ribbon row are clipped by its scroll container, COMPAT-documented).

Layout fidelity

  • ACID document: 18 -> 19 pages, matching Word. A continuous section break that changes the page size is promoted to a page break, so the A4 portrait section gets its own correctly-sized page (regression tests both ways).
  • Caret no longer paints on the previous page when editing the first line after a page break: page selection now requires the caret's line to fully fit the page's content band (the old line-centre heuristic misattributed boundary lines whenever the previous page ended with more than half a line of slack).
  • The reported bullet hanging-indent misalignment did not reproduce against the real document (paginated, reflow, and post-Loro paths all correct); the regression tests were strengthened to pin first-line-text vs continuation alignment, including the wide bullet glyph case.

Selection

  • Paginated selection highlights now paint (drag + Shift+arrow selection state already worked but was invisible): per-paragraph SelectionSpans with per-fragment transforms, clipped per page, correct across multi-paragraph and page-spanning selections.
  • Mobile selection handles: teardrop handles paint at the selection edges (Android), and a touch on a handle drags that selection edge (TouchPhase::HandleDrag, opposite end normalised into the anchor so handles may cross). Grab geometry is the exact inverse of the hit-test transform, unit-tested by round-tripping through it.

Android

  • Save As no longer strands a blank file: pick_save aborted on a takePersistableUriPermission SecurityException after ACTION_CREATE_DOCUMENT had created the file. Vendored loki-file-access into patches/ ([patch] + docs/patches.md entry with removal condition): the grant is best-effort, the pending JNI exception is cleared, and the real display name is queried. Should be upstreamed to appthere/loki-file-access.

Also fixes two pre-existing clippy::similar_names failures (loki-ooxml, loki-odf) that blocked the workspace clippy gate.

Verification

  • Live-driven on Windows: title/default size/restore-after-resize, Home reflow at phone width, Format tab + colour picker end-to-end (pick -> mark applied -> recents populated), substitution chip -> detail panel, drag-selection highlight, and the ACID doc opening at 19 pages.
  • cargo clippy --workspace --all-targets -- -D warnings = 0, cargo fmt, file-ceiling and license-header gates, all touched-crate tests green. New/strengthened regression tests: continuous-break page-size split, caret page-band fit, hanging-indent first-line-text alignment, selection-handle grab geometry, SharedFontResources warm-up.
  • Needs on-device (Android) verification: Save As write, handle rendering + drag feel (32 px grab radius), and whether the bullet-indent report reproduces anywhere.

🤖 Generated with Claude Code

kevincarlson and others added 2 commits July 13, 2026 06:03
… + selection fixes

Windows first open / loading screen:
- SharedFontResources (loki-layout): the system-font scan (DirectWrite on
  Windows) runs on a background thread at app launch instead of synchronously
  inside EditorInner's first mount hook, so the loading view paints and the
  first document open no longer freezes. Editor consumers block only on
  worker threads; render paths use a non-blocking try_lock.

Font-substitution warning inverted:
- The status-bar chip is now the always-on indicator; clicking it opens the
  detail panel above the ribbon (banner removed).

Ribbon restructure + color picker:
- New "Format" tab (span-level formatting) hosting Font colour + Highlight
  as picker triggers; Write keeps Document/History/Styles/Paragraph/Inline.
- Font size +/- buttons and the Write-tab alignment group removed (alignment
  lives in the paragraph style editor); editor_font_size/editor_alignment
  modules deleted.
- New appthere-ui color picker (AtColorPickerTrigger + docked
  AtColorPickerPanel): preset swatches, per-session recent colours, custom
  Hex/RGB/HSL/HSV/CMYK entry (font colour only; highlight is constrained to
  the named HighlightColor palette - TODO(highlight-custom-color)). Docked
  above the ribbon: popovers inside the ribbon row are clipped by its
  scroll container (COMPAT note in the module docs).

Window + app naming + Home:
- Window titles "Loki Text"/"Loki Calc"/"Loki Slides", 1280x800 default,
  last session's inner size persisted (loki-app-shell window_geometry +
  AtWindowSizeSensor); Android labels + cargo-bundle metadata updated.
- Home mounts AtViewportWidthSensor so it reflows before any document opens.

Save notification:
- save_message typed as SaveStatus: successes are an auto-clearing (4s,
  also cleared on dirty) status-bar chip; errors keep the persistent banner.

Layout/editing fixes:
- A continuous section break that changes the page size is promoted to a
  page break (Word fidelity) - the ACID doc now lays out to 19 pages and
  the A4 portrait section gets its own correctly-sized page.
- Caret page selection at page breaks now requires the line to fully fit
  the page's content band (line-centre heuristic misattributed the first
  line after a break to the previous page).
- Hanging-indent regression tests strengthened to compare first-line text
  vs continuation lines (wide-bullet case included); the reported Iris
  misalignment does not reproduce against the current engine.

Selection:
- Paginated selection highlights now paint: per-paragraph SelectionSpans
  with per-fragment transforms, clipped per page (drag + Shift+arrow
  selection state already worked but was invisible). Teardrop handles
  paint at the selection edges on Android.

Android Save As:
- Vendored loki-file-access into patches/ ([patch] in root Cargo.toml,
  docs/patches.md): pick_save no longer aborts when
  takePersistableUriPermission throws after ACTION_CREATE_DOCUMENT already
  created the file (stranding a blank file); grant is best-effort, pending
  JNI exception cleared, real display name queried.

Also fixes two pre-existing clippy::similar_names failures (loki-ooxml,
loki-odf) that blocked the workspace clippy gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A touch that starts within 32 CSS px of a selection handle's teardrop enters
the new TouchPhase::HandleDrag: the opposite endpoint is normalised into the
anchor at grab time, every touchmove hit-tests the finger position into the
selection focus (handles may cross), and touchend keeps the adjusted
selection instead of collapsing to a tap-caret.

- editing/selection_handles.rs: maps a selection endpoint back to its
  window-relative grab point (exact inverse of hit_test_document's
  origin/zoom/page-slot transform, teardrop offset mirroring the painter's
  geometry) and decides which handle a touch grabs. Unit-tested by
  round-tripping through the real hit test, opposite-endpoint selection,
  and zoom scaling.
- Touch handlers moved from editor_pointer.rs to editor_pointer_touch.rs
  (300-line ceiling), gaining make_touchstart_handler with the grab check
  and a shared paginated_hit helper; the inline mousedown/mouseup closures
  in editor_canvas.rs became editor_pointer.rs factories to keep the canvas
  file at its ratchet baseline.

Works on any touch input in paginated mode (handles paint on Android; the
grab math is platform-neutral). Needs on-device verification alongside the
handle painting from the previous commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kevincarlson kevincarlson self-assigned this Jul 13, 2026
The `lint` job failed on `check-suppressions.py`: the fit-and-finish /
mobile-selection batch added six new production files carrying
narrowly-scoped, deliberate suppressions, and resolved the debt in a
seventh (a stale baseline entry).

Each flagged item is intended semantics, not a swallowed bug:

- `#[allow(clippy::too_many_arguments)]` on geometry/handler factories
  (status_bar_chips, selection_handles, editor_pointer_touch) — the
  narrowly-scoped allow CLAUDE.md's tech-debt notes accept (flow_cell_blocks
  precedent).
- `let _ = tx.send(..)` on oneshot channels (editor_fonts, editor_save_banner)
  — the receiver may have dropped; discarding is idiomatic.
- `let _ = fs::write / create_dir_all / spawn(..)` in window_geometry —
  documented silent-fail persistence (losing a window size is harmless).
- `let _ = slot.set(..) / available_font_families()` in font_handle —
  OnceLock::set cannot fail here; the other is a lazy-index warm-up touch.
- `let _ = last_y;` in editor_pointer_touch — an explicit discard carrying a
  TODO(partial-render).

Per the ratchet's own contract, a genuinely-needed suppression is a
deliberate `--update` + justification. Regenerated with
`scripts/check-suppressions.py --update`; also drops the now-0/0
editor_ribbon_color.rs entry and tracks the editor_pointer.rs →
editor_pointer_touch.rs split. Verified the remaining lint gates
(fmt, clippy --all-features -D warnings, and the other Python gates)
that never ran because bash -e stopped at the suppression gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UB6u6gsqSCBceTb9Y9U4Ba
@kevincarlson kevincarlson merged commit aeac908 into main Jul 13, 2026
2 checks passed
@AppThere AppThere locked as resolved and limited conversation to collaborators Jul 13, 2026
@kevincarlson kevincarlson deleted the claude/fit-and-finish-batch branch July 13, 2026 12:31
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants