Fit-and-finish batch: first-load perf, Format tab + color picker, layout & selection fixes, Android Save As#34
Merged
Conversation
… + 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>
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two rounds of user-reported issues across startup, chrome, layout fidelity, selection, and Android.
Startup & window
parley::FontContext::new(), a DirectWrite enumeration) ran synchronously insideEditorInner's first mount hook, blocking the loading view's first frame. NewSharedFontResources(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-blockingtry_lock.loki-app-shell::window_geometry+AtWindowSizeSensor, debounced writes). Suite naming applied to Loki Calc / Loki Slides incl. Android labels and cargo-bundle metadata.AtViewportWidthSensoron the Home route; previously only the editor fed the responsive context).Chrome
save_messageis 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.HighlightColorpalette,TODO(highlight-custom-color)). New reusableappthere-uicomponents (AtColorPickerTrigger/AtColorPickerPanel, docked above the ribbon - popovers inside the ribbon row are clipped by its scroll container, COMPAT-documented).Layout fidelity
continuoussection 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).Selection
SelectionSpans with per-fragment transforms, clipped per page, correct across multi-paragraph and page-spanning selections.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
pick_saveaborted on atakePersistableUriPermissionSecurityExceptionafterACTION_CREATE_DOCUMENThad created the file. Vendoredloki-file-accessintopatches/([patch] +docs/patches.mdentry 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_namesfailures (loki-ooxml, loki-odf) that blocked the workspace clippy gate.Verification
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,SharedFontResourceswarm-up.🤖 Generated with Claude Code