feat(ui): add undo, json import and export, and the raw editor #34

Merged
TJC merged 8 commits from feat/m7-authoring into main 2026-08-18 16:55:22 +01:00
Collaborator

Summary

  • Undo and redo over a snapshot stack of the immutable template, in memory, reset on reload and on template switch (#22)
  • Consecutive edits to one field of one node coalesce within 500ms, so a stepper drag is one undo; insert, delete, reorder and unpack never coalesce
  • Per-template JSON export and import, self-contained by decision 18 - an import lands beside what is here rather than on top of it (#23)
  • Raw JSON editor beside the sheet, validated live and committed through the same gate, so one bad paste is listed rather than saved

Test plan

  • pnpm test - 556 pass, including the milestone exit gate: export a template, reimport it into a clean profile, get the same sheet
  • pnpm lint, npx tsc --noEmit, pnpm build clean

closes #22
closes #23

## Summary - Undo and redo over a snapshot stack of the immutable template, in memory, reset on reload and on template switch (#22) - Consecutive edits to one field of one node coalesce within 500ms, so a stepper drag is one undo; insert, delete, reorder and unpack never coalesce - Per-template JSON export and import, self-contained by decision 18 - an import lands beside what is here rather than on top of it (#23) - Raw JSON editor beside the sheet, validated live and committed through the same gate, so one bad paste is listed rather than saved ## Test plan - `pnpm test` - 556 pass, including the milestone exit gate: export a template, reimport it into a clean profile, get the same sheet - `pnpm lint`, `npx tsc --noEmit`, `pnpm build` clean closes #22 closes #23
- coalesce consecutive edits to one field within 500ms
- never coalesce a structural edit, which carries no tag
- reset the stack when another template is opened
- record every accepted edit in one place, so none lands untracked
- tag an inspector edit with the node and field it came from
- offer undo and redo in the bar and on the usual shortcut

closes #22
- write the open template to a file named after it
- share the download path with the pdf button

part of #23
- parse the file before the library holds it
- re-id and rename an arrival whose id is already here, rather than replacing it
- report a file that will not parse against its own name

part of #23
- commit through the same gate, so only text that parsed reaches storage
- list what a bad paste got wrong and leave the sheet on the last good one
- refuse to move the id the library files the template under
- reopen the name field on a rename typed into the panel

closes #23
TJC requested review from TJC 2026-08-18 16:41:42 +01:00
- a library change is computed against the library as it stands rather
  than the one the render that started it captured
- the read is a real await, so an edit landing in it was reverted in
  memory and overwritten in storage when the import landed
- the name is part of the template a snapshot holds, so a rename that
  skipped the stack was taken back by an undo aimed at the edit before
  it, and was not undoable on its own
- the field reseeds on the template's name rather than remounting on it,
  so committing a rename no longer unmounts the input under the caret
Author
Collaborator

Review findings from /code-review medium, all three fixed in 6874937, 5712ec4 and 57dc5ad.

Import raced a concurrent edit. importFile read the file across an await and then built the import on the library the render that started it had captured, so an edit committed while the file was being read was reverted in memory and overwritten in storage. useLibrary's commit now takes the change as a function of the library as it stands.

Rename bypassed the undo stack. rename went straight to update, so record never saw it: an undo aimed at the edit before a rename took the rename back with it, and a rename on its own was not undoable at all. It moved to useEditor and commits through the same gate as every other edit, with a no-op when the name is unchanged so the blur after an Enter is not a second entry.

The name field lost focus on Enter. Keying it on the name remounted the input as the rename it had just committed landed. It reseeds on the name instead, the way the JSON editor does, and is still keyed on the template id so a switch drops a half-typed draft.

Four tests added, each of which fails without its fix. pnpm test 560 pass, pnpm lint, npx tsc --noEmit and pnpm build clean.

Review findings from `/code-review medium`, all three fixed in 6874937, 5712ec4 and 57dc5ad. **Import raced a concurrent edit.** `importFile` read the file across an await and then built the import on the library the render that started it had captured, so an edit committed while the file was being read was reverted in memory and overwritten in storage. `useLibrary`'s `commit` now takes the change as a function of the library as it stands. **Rename bypassed the undo stack.** `rename` went straight to `update`, so `record` never saw it: an undo aimed at the edit before a rename took the rename back with it, and a rename on its own was not undoable at all. It moved to `useEditor` and commits through the same gate as every other edit, with a no-op when the name is unchanged so the blur after an Enter is not a second entry. **The name field lost focus on Enter.** Keying it on the name remounted the input as the rename it had just committed landed. It reseeds on the name instead, the way the JSON editor does, and is still keyed on the template id so a switch drops a half-typed draft. Four tests added, each of which fails without its fix. `pnpm test` 560 pass, `pnpm lint`, `npx tsc --noEmit` and `pnpm build` clean.
TJC approved these changes 2026-08-18 16:55:19 +01:00
TJC merged commit d88f1bf059 into main 2026-08-18 16:55:22 +01:00
Sign in to join this conversation.
No reviewers
TJC
No labels
chore
core
render
ui
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TJC/daily-planner-template!34
No description provided.