Skip to content

feat: fast document open, cloud-sync UX, and a debug HUD - #18

Merged
nprudhomme merged 1 commit into
ekino:mainfrom
padupuy:feat/fast-document-open-and-debug-hud
Jul 2, 2026
Merged

nprudhomme merged 1 commit into
ekino:mainfrom
padupuy:feat/fast-document-open-and-debug-hud

Conversation

@padupuy

@padupuy padupuy commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Why

Opening a document could take ~3 seconds. A new debug HUD showed the render pipeline was only ~110 ms — the entire cost was the file read.

Root cause: OneDrive "files on-demand". Online-only (dataless) files are downloaded on first read. A plain cat of the same file:

Location time cat
~/Documents (OneDrive-synced) 2.3s (0% CPU — blocked on I/O)
local folder 0.031s

Not the app, not Tauri, not the IPC (a no-op IPC round-trip measured ~1 ms). Nothing in code makes the hydration faster, so this focuses on not freezing, not re-paying the cost, and telling the user.

Changes

Read path

  • New async Rust command read_document (replaces the fs plugin for document reads). async keeps the possibly-slow read off the UI thread — a synchronous command ran on the main thread and froze the window (FPS dropped to ~2 during the read).
  • Confined to the home / resource dirs with canonicalize + prefix check; generic errors (addresses an automated path-traversal review finding).

Perceived performance

  • In-memory cache keyed by path + mtime → re-opening a document is instant. A cheap document_mtime command (metadata only) validates freshness so an external edit is never served stale.
  • Loading spinner shown after a 150 ms delay (no flash on fast/cached reads).
  • One-time dismissible notice when an uncached read exceeds 600 ms, explaining the likely cloud-sync cause. Translated (fr + en).

Debug tooling (kept in-repo for future perf work)

  • Debug HUD — Cmd/Ctrl+Shift+D: live FPS + per-phase open timings (ipc ping / read / parse / images / mermaid / dom / outline / total), with click-to-copy. The IPC ping probe only runs while the HUD is open, so normal use pays nothing.
  • read_document logs the raw std::fs read duration to stderr.

Tests

npm test (294 passing), npx tsc --noEmit, and cargo check all pass. HUD formatting logic is unit-tested (debug-overlay.test.ts).

Note

Independent of #17 (search highlight fix).

🤖 Generated with Claude Code

Opening a document could take ~3s. Instrumentation (new debug HUD) showed
the render pipeline was only ~110ms — the time was entirely in the file
read. Root cause: OneDrive "files on-demand" hydrating online-only files
on first read (a plain `cat` of the same file took 2.3s at 0% CPU;
0.03s once moved to a local folder). Not the app, not the IPC.

What this changes:

- Read via a dedicated async Rust command `read_document` instead of the
  fs plugin. `async` keeps the (possibly slow) read off the UI thread —
  a sync command ran on the main thread and froze the window (FPS → 2).
  Confined to the home/resource dirs with canonicalization; errors stay
  generic (addresses the path-traversal review).
- In-memory document cache keyed by path + mtime (new cheap
  `document_mtime` command) → re-opening a document is instant, without
  serving stale content after an external edit.
- Loading spinner shown after a 150ms delay (no flash on fast/cached
  reads) and a one-time dismissible notice when an uncached read exceeds
  600ms, explaining the likely cloud-sync cause (fr + en).

Debug tooling (kept for future perf work):

- Debug HUD (Cmd/Ctrl+Shift+D): live FPS + per-phase open timings
  (ipc ping, read, parse, images, mermaid, dom, outline, total), with
  click-to-copy. The IPC "ping" probe only runs while the HUD is open.
- `read_document` logs the raw std::fs read duration to stderr.
@nprudhomme
nprudhomme merged commit fb33bb0 into ekino:main Jul 2, 2026
4 checks passed
@nprudhomme nprudhomme mentioned this pull request Jul 3, 2026
7 tasks
nprudhomme added a commit that referenced this pull request Aug 13, 2026
Bump version to 0.11.0 and document the user-facing changes since 0.10.0:
native File/View menus with Open Recent (#19), fast document open with
in-memory cache, cloud-sync UX and a debug HUD (#18), and a search
highlight fix on document switch / 1->0 clear (#17).
nprudhomme added a commit that referenced this pull request Aug 13, 2026
* chore(release): v0.11.0

Bump version to 0.11.0 and document the user-facing changes since 0.10.0:
native File/View menus with Open Recent (#19), fast document open with
in-memory cache, cloud-sync UX and a debug HUD (#18), and a search
highlight fix on document switch / 1->0 clear (#17).

* fix(ui): stop transparent modal backdrops from locking the window

A modal backdrop could be left in `display: flex` while fully transparent,
covering the whole window at z-index 2000. It swallowed every click and
scroll while staying invisible, and because the close path had already
detached its listeners, the user had no way to dismiss it — the app was
unusable until restart. Reported on 0.10.0: no clicks on Open Folder, no
file selection in the sidebar, no scrolling, while the native menu bar
still worked.

The backdrops were shown and hidden through deferred callbacks. WebKit
suspends both animation frames and timers while a window is occluded, so
neither the reveal nor the hide is guaranteed to run — leaving the layer
displayed but never marked `visible`.

Make interactivity follow opacity in CSS: an overlay without `.visible`
is now click-through and hidden from the tab order and the accessibility
tree. That is the load-bearing guarantee — it holds whatever the reason
the layer got stuck, including a keyboard user reaching a destructive
button inside an invisible dialog.

Defence in depth on top of it:
- reveal synchronously (forced reflow, then class) instead of from a
  frame callback, so a dialog opened on an occluded window is still
  visible and therefore dismissable;
- track an open generation so a pending hide never acts on a backdrop a
  newer open has claimed, in both confirm-dialog and Preferences;
- focus the trap immediately, since callers now reveal before trapping.

Tests cover the two suspension modes that produce the field state:
frames never delivered, and timers never delivered.

Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4

* docs(changelog): record multi-window support in 0.11.0

PR #22 landed without a changelog entry. Covers the new windows, the
native Window menu, the arrangement commands, and the switch from
broadcast to frontmost-window menu delivery.

Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4

* docs(changelog): set the 0.11.0 release date

The heading still carried the date the release branch was prepared.

Claude-Session: https://claude.ai/code/session_01NcJuB2iR4WEfuQVLpYqWi4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants