Commit Graph

3 Commits

Author SHA1 Message Date
2eb18b5c70 Persist edits immediately so a fast app-kill loses no state
Reproduced on device: open a file, scroll, long-press-highlight a
word, then recents-wipe the app ~1s after the highlight. The session
file held the file but lost the selection and cursor: the 1s
rate-limit had not elapsed since the previous save, so the final
changes never flushed before the process died (a recents-wipe is a
kill, not a clean shutdown, so Shutdown's final save never runs).

Two changes in saveSessionIfChanged:

- Urgent path: save immediately when the file changes or a selection
  appears/vanishes. These are low-frequency, high-value changes
  (the user just opened a file or highlighted/cleared text), and they
  are exactly what 'where I left off' means.
- sessionSaveInterval 1s -> 250ms: bounds how stale scroll/cursor can
  be at an unlucky kill. The file is a few hundred bytes, so a few
  small writes a second during active scrolling is negligible.

TestRestore_UrgentSaveOnSelection drives the kill race: a selection
made right after the file-open save must land in the saver without
waiting out the interval (fails with the urgent path disabled).
2026-08-20 18:06:29 -04:00
03595af27d Fix relaunch-restore bugs found on device (spec §7 verification)
On-device runs exposed three bugs the e2e suite could not (it never
feeds layout feedback, and runs headless without size events):

1. WrapIndex poisoning from zero-width shapes. The first frames are
   built before the window size is known (px=0x0); the renderer shapes
   the editor window at zero width, where every line wraps into many
   visual lines. When that feedback arrives, applyWrapCounts writes the
   inflated counts to the window's lines. Normally the app stays on
   those lines and re-shapes them at a real width, which corrects the
   counts before anyone notices. A restored scroll moves the viewport
   away instead, so the poisoned counts persist and map the restored
   scroll offset to the wrong line (2000 landed on line 11 of 200).
   The frame now carries ViewportDegenerate (set at frame-build time,
   since feedback delivery lags shaping by a frame), and the main loop
   drops layout feedback for such frames.

2. Session saving suppressed forever after a successful restore.
   saveSessionIfChanged suppresses saves while l.session is set, and
   only abortRestore cleared it — the success path never did, so an
   app restored from a session never persisted new state. The
   snapshot has fully landed once the cursor/selection/find have
   landed with the content and the armed scroll has landed (or there
   was none); clear l.session at both points.

3. gofmt on restore_test.go (comment alignment).
2026-08-20 17:31:55 -04:00
e3690d6b98 Restore last file, cursor, scroll, selection and find state on relaunch (spec §7)
Persist a tiny JSON snapshot (SessionState) written by the cmd layer
($HOME/.pad/session.json off-Android, /storage/emulated/0/Pad/ on
Android) and call the logic-owned snapshot rate-limited (<=1/s, on
change) from emitFrame plus unconditionally at Shutdown. On launch the
cmd layer hands the snapshot to Logic.BeginRestore before Run; the
file re-opens through the normal openFile path and lands straight on
the editor page.

- Cursor/selection land with the content, clamped to a shrunk file
  (path-guarded so a late result for a replaced file cannot apply the
  snapshot to the wrong buffer); a missing file falls back to the
  browser.
- Scroll is applied only after the first ScaleEvent has been laid out:
  the size ConfigEvent precedes it and the one-way MaxScroll clamp in a
  wrong-unit layout would corrupt the offset (found by e2e).
- Find: query + open/closed + current match are persisted; results are
  regenerated by re-scanning and the saved current match is re-selected
  by byte offset (Find.Restoring/RestoreMatch) without re-scrolling the
  restored viewport. A closed-bar query re-scans on the next bar open
  instead (an eager scan would be dropped and leave Scanning stuck).
- The main-owned find_bar widget is seeded with the restored query so
  its first frame matches the logic-side query.

Docs: spec.md gains §2.4 and drops the §7 row; invariant 5 updated;
architecture.md gains §6.7. Tests: 7 e2e tests covering cursor/scroll/
selection restore, clamping, missing-file fallback, find-bar restore
(open/closed), and the saver/shutdown persist paths.
2026-08-20 16:01:02 -04:00