Composer: port the calternal.js composer (NLP, modes, draft deck, attachments incl. voice) #111

Closed
opened 2026-09-25 18:30:48 +00:00 by kayg · 1 comment
Owner

Branch job/composer. DESIGN §38: port ALL of calternal.js Composer.svelte and helpers (NLP date/time chips, tag chips, mode pill Log/Task/Note/Event, draft deck with swipe and Send all, commit-freeze) plus improvements; the UI reuses the search palette surface with the shared scrim; modes map to real backends; attachments: photos, files, voice (MediaRecorder); openComposer() API, N/Cmd+N, palette command; parses times written in the text ('2130 - 2200') into the entry's range (§39). Adversarial UI cases required.

Branch job/composer. DESIGN §38: port ALL of calternal.js Composer.svelte and helpers (NLP date/time chips, tag chips, mode pill Log/Task/Note/Event, draft deck with swipe and Send all, commit-freeze) plus improvements; the UI reuses the search palette surface with the shared scrim; modes map to real backends; attachments: photos, files, voice (MediaRecorder); openComposer() API, N/Cmd+N, palette command; parses times written in the text ('2130 - 2200') into the entry's range (§39). Adversarial UI cases required.
Author
Owner

job/composer report (not pushed, not merged)

Commits (branch job/composer, from dev 0e6c428)

  • 34c188f Notes: composer parse endpoint and Log attachments on create
  • afde586 UI: composer pieces and a keyboard-safe palette capsule
  • 6e145b2 Composer: port the calternal.js composer into the palette capsule
  • 057c7b4 Composer: unit and component tests
  • 613f0a6 Composer: fixes from the real-server runs
  • 0270d44 e2e: shared harness; the calendar flow uses the app's composer
  • 083e360 Composer: e2e flow, §39 written times, late-parse and focus fixes
  • 776d86c test(adversarial): composer parse and Log attachment probe

Gates

  • bun run check: COMPLETED 1415 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS
  • bun run test: Test Files 44 passed (44), Tests 329 passed (329)
  • bun run build: ✓ built in 30.49s
  • layout sweep: checked 132 screens / LAYOUT SWEEP PASSED
  • calendar e2e: calendar e2e: all flows passed
  • composer e2e (new, bun run test:e2e:composer): composer e2e: all flows passed
  • cargo fmt --check: clean; cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings: Finished, no warnings; cargo test -p calternal-plugin-notes: test result: ok. 66 passed; 0 failed
  • adversarial round 2, composer section: ==== ROUND 2 FINDINGS 0. Round 1 reported 25 SLOW findings (appearance concurrent PUT, Task create/attach, calendar linked Log) in code this job did not touch, on a loaded machine.

Ported vs changed

Ported: NLP chips (date, time range, span over midnight with warning, since/until anchors, duration, Task date role, priority, recurrence, tags), the highlight mirror, ModePill with the 300/600 ms suggest/classify hysteresis, long-press/chip correction menu, tag picker, draft deck (stack, discard + Undo, pull-back swap, touch swipe, double-Enter, "Send all · N" with the mid-batch skip/reassign rules), commit-freeze, park-as-card on failure, CrossMidnightPartialFailure, the sr-only Stack/Discard twins, long-press Send = Send all.
Changed: NLP runs on the server (POST /api/v1/notes/composer/parse, one core for chips and saves) instead of WASM; drafts are per-user localStorage (sync writes, v1 #93 drafts imported and removed); the composer is the palette capsule (OverlaySurface pill, shared scrim) with the ModePill inside it; four modes (Log/Task/Note/Event); "Edit date/time" uses FloatingSurface; menus use the shared Menu.
Not ported (no backend or not applicable): Jev tag suggestions, the event-bound "Add note" binding and edit mode (calternal edits in LogEntryEditor), "Tag color…", the FAB↔sheet island morph.

Improvements and bugs fixed

  • A Log entry is never in the future (§30 C14): a future moment moves a Log draft to Event, Send refuses a future Log with a reason, the commit refuses it as a backstop.
  • Send validates against a fresh parse when pressed inside the debounce; a late parse still runs the owed suggest/classify ticks.
  • Re-picking the shown mode pins it (calternal.js ignored it); a mode picked on an empty composer sticks.
  • ⌘/Ctrl+Enter works from anywhere in the composer, and when a removed control dropped focus to body.
  • Inline Undo and reasons (toasts tuck away under an overlay); a pending Undo becomes a toast on close.
  • A closing composer is inert at once; N reopens mid-close and focuses the text.
  • Keyword-inferred tags (area/commute from "flight") are shown as dashed chips and removable; calternal.js saved them unseen.
  • One live region for hints (was announced twice); no box-shadow ring on the text field; empty chip row takes no space; tag picker loads only tags.
  • Rich paste → plain text; paste/drop of files attaches them; 10k-char Note titles are bounded with the full line kept in the body.
  • No backdrop-filter on anything that moves; every animation off under reduced motion.
  • §39: "2130 - 2200", "21:30–22:00", "9:30pm for 30m" → range + stripped title (the core already parses them; regression test + e2e added).

Modes and backends (all real, no stubs)

  • Log → POST /api/v1/notes/journal/log (canonical HH:MM[ - HH:MM] title #tags, now pinned to the day; lines 2+ → linked note via /journal/entries/{id}/note; ranges over midnight → two entries). New optional attachments field on the Log input, validated (existing regular file in the Home, no traversal/URLs, ≤32), written in the same Daily note write.
  • Task → POST /api/v1/notes/tasks (body + attachment links).
  • Note → POST /api/v1/notes.
  • Event → POST /api/v1/calendar/events (iCal with DESCRIPTION, ATTACH to /f/<item-id>, all-day when no time). Disabled with a reason and a link when no calendar is connected.
  • Attachments: tus queue (uploads.enqueue/settled, fail policy with unique names) into Attachments/. Voice: MediaRecorder webm/opus → mp4 → ogg, live level, Keep/Discard, announced states.

Shared files touched (small, additive)

OverlaySurface (fitVisualViewport prop), SegmentedControl (onreselect), search providers (4 "New …" actions), search sources (ensureTags), shortcut registry (composer.open = N / ⌘N; Jev entry removed), +layout (mounts the composer, N handler, FAB → openComposer), calendar page (creation → openComposer; CreateForm and lib/calendar/drafts.ts deleted), notes plugin lib.rs (route + LogInput.attachments — overlaps job/journal-cas's file), files uploads queue, calendar data.ts (createEvent takes an object).

Note: ⌘N in a browser tab usually opens a browser window before the page sees it; N works everywhere, ⌘N in the installed app. No axe in the repo; the e2e does an accessible-name and live-region pass instead.

Screenshots (/home/kayg/Developer/calternal/target/composer-screens/)

{1440,390}-{paper,tokyo-night}-{empty,log-chips,attachments,task-chips,note,event-chips,recording,deck}.png, plus 1440-paper-{recording,deck,rtl-emoji,upload-failed,mic-denied}.png, 320-paper-log.png, zoom200-paper-log.png.

## job/composer report (not pushed, not merged) ### Commits (branch job/composer, from dev 0e6c428) - 34c188f Notes: composer parse endpoint and Log attachments on create - afde586 UI: composer pieces and a keyboard-safe palette capsule - 6e145b2 Composer: port the calternal.js composer into the palette capsule - 057c7b4 Composer: unit and component tests - 613f0a6 Composer: fixes from the real-server runs - 0270d44 e2e: shared harness; the calendar flow uses the app's composer - 083e360 Composer: e2e flow, §39 written times, late-parse and focus fixes - 776d86c test(adversarial): composer parse and Log attachment probe ### Gates - `bun run check`: `COMPLETED 1415 FILES 0 ERRORS 0 WARNINGS 0 FILES_WITH_PROBLEMS` - `bun run test`: `Test Files 44 passed (44)`, `Tests 329 passed (329)` - `bun run build`: `✓ built in 30.49s` - layout sweep: `checked 132 screens` / `LAYOUT SWEEP PASSED` - calendar e2e: `calendar e2e: all flows passed` - composer e2e (new, `bun run test:e2e:composer`): `composer e2e: all flows passed` - `cargo fmt --check`: clean; `cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings`: Finished, no warnings; `cargo test -p calternal-plugin-notes`: `test result: ok. 66 passed; 0 failed` - adversarial round 2, composer section: `==== ROUND 2 FINDINGS 0`. Round 1 reported 25 SLOW findings (appearance concurrent PUT, Task create/attach, calendar linked Log) in code this job did not touch, on a loaded machine. ### Ported vs changed Ported: NLP chips (date, time range, span over midnight with warning, since/until anchors, duration, Task date role, priority, recurrence, tags), the highlight mirror, ModePill with the 300/600 ms suggest/classify hysteresis, long-press/chip correction menu, tag picker, draft deck (stack, discard + Undo, pull-back swap, touch swipe, double-Enter, "Send all · N" with the mid-batch skip/reassign rules), commit-freeze, park-as-card on failure, CrossMidnightPartialFailure, the sr-only Stack/Discard twins, long-press Send = Send all. Changed: NLP runs on the server (`POST /api/v1/notes/composer/parse`, one core for chips and saves) instead of WASM; drafts are per-user localStorage (sync writes, v1 #93 drafts imported and removed); the composer is the palette capsule (OverlaySurface pill, shared scrim) with the ModePill inside it; four modes (Log/Task/Note/Event); "Edit date/time" uses FloatingSurface; menus use the shared Menu. Not ported (no backend or not applicable): Jev tag suggestions, the event-bound "Add note" binding and edit mode (calternal edits in LogEntryEditor), "Tag color…", the FAB↔sheet island morph. ### Improvements and bugs fixed - A Log entry is never in the future (§30 C14): a future moment moves a Log draft to Event, Send refuses a future Log with a reason, the commit refuses it as a backstop. - Send validates against a fresh parse when pressed inside the debounce; a late parse still runs the owed suggest/classify ticks. - Re-picking the shown mode pins it (calternal.js ignored it); a mode picked on an empty composer sticks. - ⌘/Ctrl+Enter works from anywhere in the composer, and when a removed control dropped focus to body. - Inline Undo and reasons (toasts tuck away under an overlay); a pending Undo becomes a toast on close. - A closing composer is inert at once; N reopens mid-close and focuses the text. - Keyword-inferred tags (area/commute from "flight") are shown as dashed chips and removable; calternal.js saved them unseen. - One live region for hints (was announced twice); no box-shadow ring on the text field; empty chip row takes no space; tag picker loads only tags. - Rich paste → plain text; paste/drop of files attaches them; 10k-char Note titles are bounded with the full line kept in the body. - No backdrop-filter on anything that moves; every animation off under reduced motion. - §39: "2130 - 2200", "21:30–22:00", "9:30pm for 30m" → range + stripped title (the core already parses them; regression test + e2e added). ### Modes and backends (all real, no stubs) - Log → `POST /api/v1/notes/journal/log` (canonical `HH:MM[ - HH:MM] title #tags`, now pinned to the day; lines 2+ → linked note via `/journal/entries/{id}/note`; ranges over midnight → two entries). New optional `attachments` field on the Log input, validated (existing regular file in the Home, no traversal/URLs, ≤32), written in the same Daily note write. - Task → `POST /api/v1/notes/tasks` (body + attachment links). - Note → `POST /api/v1/notes`. - Event → `POST /api/v1/calendar/events` (iCal with DESCRIPTION, ATTACH to `/f/<item-id>`, all-day when no time). Disabled with a reason and a link when no calendar is connected. - Attachments: tus queue (`uploads.enqueue`/`settled`, `fail` policy with unique names) into `Attachments/`. Voice: MediaRecorder webm/opus → mp4 → ogg, live level, Keep/Discard, announced states. ### Shared files touched (small, additive) OverlaySurface (`fitVisualViewport` prop), SegmentedControl (`onreselect`), search providers (4 "New …" actions), search sources (`ensureTags`), shortcut registry (composer.open = N / ⌘N; Jev entry removed), +layout (mounts the composer, N handler, FAB → openComposer), calendar page (creation → openComposer; CreateForm and lib/calendar/drafts.ts deleted), notes plugin lib.rs (route + LogInput.attachments — overlaps job/journal-cas's file), files uploads queue, calendar data.ts (createEvent takes an object). Note: ⌘N in a browser tab usually opens a browser window before the page sees it; N works everywhere, ⌘N in the installed app. No axe in the repo; the e2e does an accessible-name and live-region pass instead. ### Screenshots (/home/kayg/Developer/calternal/target/composer-screens/) {1440,390}-{paper,tokyo-night}-{empty,log-chips,attachments,task-chips,note,event-chips,recording,deck}.png, plus 1440-paper-{recording,deck,rtl-emoji,upload-failed,mic-denied}.png, 320-paper-log.png, zoom200-paper-log.png.
kayg closed this issue 2026-09-25 21:43:41 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
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
kayg/calternal#111
No description provided.