Block actions: hover controls, make-note (→), block reminders with quiet hours, phone capsule bar (DESIGN §42) #191

Closed
opened 2026-09-26 15:25:15 +00:00 by kayg · 89 comments
Owner

Implement docs/DESIGN.md §42 exactly (owner grill 2026-09-26; read it first). Depends on #182 (Craft block vs text modes): merge dev once it lands. Slices, one commit or more each:

  1. Shared capsule bar component (packages/ui): horizontally scrollable, HIG sizes (44pt targets, ~20px icons, research Apple HIG toolbar/pill metrics and document the numbers), glass material from #157; used as desktop floating text-selection toolbar, phone formatting bar above the keyboard (text mode) and phone block bar (block mode, replaces the mode tray).
  2. Desktop hover controls (left handle; right ⋯, →, clock) only on the hovered/selected block; ⋯ menu uses the shared menu with icons (#176); 'L' copies the selected block's link (register in #159's shortcut registry).
  3. Make note (→): one atomic, undoable transaction through the collab room and one server route (DESIGN §41); child-block prompt with a Settings → Editor default; title rule; open-existing when the block already links one note; undo/redo exact.
  4. Block reminders: frontmatter via crates/calternal-notes-core/src/reminders.rs (reuse), scheduler + web push + in-app inbox (DESIGN §30 C11), audience Everyone/Only me, recipients computed at fire time, quiet hours per user (Settings → Notifications, default 23:00-06:00 local, held and delivered at the end; setter's own explicit time never held), picker shows each recipient's local time and flags quiet hours; per-person Done/Snooze; all lifecycle cases in §42 with tests. Presets generic now; record chosen times so a later change can learn the user's most used times.
  5. Phone block mode bar with all block actions; multi-select disables make note, copy link, remind.
  6. Adversarial probes for every new route (reminder spam, audience escalation, guest access, hostile times/zones, DST gaps, quiet hours across midnight, concurrent edits); the #186 editor break-it job will also attack these.
    Screenshots desktop hover/menus/picker/floating toolbar and phone text/block bars, light and dark, for Claude's review.
Implement docs/DESIGN.md §42 exactly (owner grill 2026-09-26; read it first). Depends on #182 (Craft block vs text modes): merge dev once it lands. Slices, one commit or more each: 1. Shared capsule bar component (packages/ui): horizontally scrollable, HIG sizes (44pt targets, ~20px icons, research Apple HIG toolbar/pill metrics and document the numbers), glass material from #157; used as desktop floating text-selection toolbar, phone formatting bar above the keyboard (text mode) and phone block bar (block mode, replaces the mode tray). 2. Desktop hover controls (left handle; right ⋯, →, clock) only on the hovered/selected block; ⋯ menu uses the shared menu with icons (#176); 'L' copies the selected block's link (register in #159's shortcut registry). 3. Make note (→): one atomic, undoable transaction through the collab room and one server route (DESIGN §41); child-block prompt with a Settings → Editor default; title rule; open-existing when the block already links one note; undo/redo exact. 4. Block reminders: frontmatter via crates/calternal-notes-core/src/reminders.rs (reuse), scheduler + web push + in-app inbox (DESIGN §30 C11), audience Everyone/Only me, recipients computed at fire time, quiet hours per user (Settings → Notifications, default 23:00-06:00 local, held and delivered at the end; setter's own explicit time never held), picker shows each recipient's local time and flags quiet hours; per-person Done/Snooze; all lifecycle cases in §42 with tests. Presets generic now; record chosen times so a later change can learn the user's most used times. 5. Phone block mode bar with all block actions; multi-select disables make note, copy link, remind. 6. Adversarial probes for every new route (reminder spam, audience escalation, guest access, hostile times/zones, DST gaps, quiet hours across midnight, concurrent edits); the #186 editor break-it job will also attack these. Screenshots desktop hover/menus/picker/floating toolbar and phone text/block bars, light and dark, for Claude's review.
Author
Owner

Starting #191 on branch job/block-actions, based on e976bff6c74b3131266b3baeb33ab318edccd46e (dev). I have read §42 and confirmed #182 has not landed on dev yet. I am beginning with slices 1 and 4 as instructed; I will merge dev before final gates and handle dependent slices if #182 lands during this job.

Starting #191 on branch `job/block-actions`, based on `e976bff6c74b3131266b3baeb33ab318edccd46e` (`dev`). I have read §42 and confirmed #182 has not landed on `dev` yet. I am beginning with slices 1 and 4 as instructed; I will merge `dev` before final gates and handle dependent slices if #182 lands during this job.
Author
Owner

Finding for slice 1: packages/ui/src/components/PillGroup.svelte already provides the shared glass capsule, horizontal scrolling, keyboard toolbar semantics, and body portal. The live app editor had formatting commands in packages/editor, but NoteEditorSurface.svelte did not mount a formatting bar. I am reusing PillGroup and mounting desktop selection and phone keyboard bars in the app.

Finding for slice 1: `packages/ui/src/components/PillGroup.svelte` already provides the shared glass capsule, horizontal scrolling, keyboard toolbar semantics, and body portal. The live app editor had formatting commands in `packages/editor`, but `NoteEditorSurface.svelte` did not mount a formatting bar. I am reusing `PillGroup` and mounting desktop selection and phone keyboard bars in the app.
Author
Owner

Slice 4 decision: I kept the existing calternal-reminders record envelope in crates/calternal-notes-core/src/reminders.rs (schema, blockId, trigger, state, revision, updatedAt) and added setter plus audience as additive fields. This keeps the lossless frontmatter projection compatible with existing records while making the new audience explicit. Reminder create/list/delete use stable Note and block IDs, strict RFC 3339 times, ETag guarded writes, and a 128-record per-Note bound. A picker with generic presets and the existing composer parser is being added before #182 integration.

Slice 4 decision: I kept the existing `calternal-reminders` record envelope in `crates/calternal-notes-core/src/reminders.rs` (`schema`, `blockId`, `trigger`, `state`, `revision`, `updatedAt`) and added `setter` plus `audience` as additive fields. This keeps the lossless frontmatter projection compatible with existing records while making the new audience explicit. Reminder create/list/delete use stable Note and block IDs, strict RFC 3339 times, ETag guarded writes, and a 128-record per-Note bound. A picker with generic presets and the existing composer parser is being added before #182 integration.
Author
Owner

Build finding: the Notifications test build hit an existing compile error from 70c847ea in crates/plugins/files/src/index.rs:620: Ok(()) was returned from a Result<bool, Failure> function. The unchanged-row fast path means the index row is current, so it now returns Ok(true). Verification: cargo test -p calternal-plugin-files --lib passed (88 passed, 0 failed).

Build finding: the Notifications test build hit an existing compile error from `70c847ea` in `crates/plugins/files/src/index.rs:620`: `Ok(())` was returned from a `Result<bool, Failure>` function. The unchanged-row fast path means the index row is current, so it now returns `Ok(true)`. Verification: `cargo test -p calternal-plugin-files --lib` passed (88 passed, 0 failed).
Author
Owner

Slice 4 backend is committed as 3ddd98e959975d5835910735fbe90f33efa3d6b5. It projects Note reminder history and pause/restore state, resolves Everyone from current internal Shares and stable Files item fingerprints, previews each recipient's local delivery time and quiet-hours hold, and persists per-User Done/Snooze actions. Quiet hours are stored in the existing top-level notifications User settings section and default to 23:00–06:00 in the User's configured zone (UTC when none is configured); the setter's explicit time is never held. Snooze defaults to ten minutes and accepts 1 minute to 7 days. Verification: cargo test -p calternal-plugin-notifications --lib passed (17 passed, 0 failed); cargo test -p calternal-plugin-notes block_reminder_projection --lib passed (2 passed, 0 failed). OpenAPI and web checks are still in progress.

Slice 4 backend is committed as `3ddd98e959975d5835910735fbe90f33efa3d6b5`. It projects Note reminder history and pause/restore state, resolves Everyone from current internal Shares and stable Files item fingerprints, previews each recipient's local delivery time and quiet-hours hold, and persists per-User Done/Snooze actions. Quiet hours are stored in the existing top-level `notifications` User settings section and default to 23:00–06:00 in the User's configured zone (UTC when none is configured); the setter's explicit time is never held. Snooze defaults to ten minutes and accepts 1 minute to 7 days. Verification: `cargo test -p calternal-plugin-notifications --lib` passed (17 passed, 0 failed); `cargo test -p calternal-plugin-notes block_reminder_projection --lib` passed (2 passed, 0 failed). OpenAPI and web checks are still in progress.
Author
Owner

Finding: the Notes E2E bound its server to 127.0.0.1 but navigated to localhost. On this host, getent resolves localhost to ::1 first, and Playwright returned ERR_CONNECTION_REFUSED. I changed this test harness base URL to 127.0.0.1 to match the IPv4 bind. The full production-build E2E run is in progress.

Finding: the Notes E2E bound its server to 127.0.0.1 but navigated to localhost. On this host, getent resolves localhost to ::1 first, and Playwright returned ERR_CONNECTION_REFUSED. I changed this test harness base URL to 127.0.0.1 to match the IPv4 bind. The full production-build E2E run is in progress.
Author
Owner

Correction to the Notes E2E finding: setting its public URL to 127.0.0.1 caused WebAuthn to reject the RP ID (rp_id is not an effective_domain of rp_origin). I restored the localhost origin and added a Chromium host resolver rule to connect localhost to the server's IPv4 listener. This keeps the origin valid for passkeys while fixing the connection refusal. I am rerunning the E2E flow.

Correction to the Notes E2E finding: setting its public URL to 127.0.0.1 caused WebAuthn to reject the RP ID (`rp_id is not an effective_domain of rp_origin`). I restored the localhost origin and added a Chromium host resolver rule to connect localhost to the server's IPv4 listener. This keeps the origin valid for passkeys while fixing the connection refusal. I am rerunning the E2E flow.
Author
Owner

Further trace of the connection refusal: Auth bootstrap logs the setup URL during build_live_app, before main binds the TCP listener. The Notes E2E returned as soon as it saw that log line, so it could navigate before /readyz was available. I added a readiness poll before launching the browser, and kept the localhost origin plus the IPv4 Chromium resolver rule. Retrying now.

Further trace of the connection refusal: Auth bootstrap logs the setup URL during build_live_app, before main binds the TCP listener. The Notes E2E returned as soon as it saw that log line, so it could navigate before /readyz was available. I added a readiness poll before launching the browser, and kept the localhost origin plus the IPv4 Chromium resolver rule. Retrying now.
Author
Owner

Resuming after the planned VM restart. Branch: job/block-actions; checkpoint HEAD: 0bf913aa4d; working tree is clean. I have read the prior report comments and confirmed #182 is still open and its selection-mode commit is on job/editor-modes, not dev. I am continuing independent work now and will merge dev before final gates once the dependency lands.

Resuming after the planned VM restart. Branch: job/block-actions; checkpoint HEAD: 0bf913aa4d57f491c17db32803ddbf4f1fba3de5; working tree is clean. I have read the prior report comments and confirmed #182 is still open and its selection-mode commit is on job/editor-modes, not dev. I am continuing independent work now and will merge dev before final gates once the dependency lands.
Author
Owner

Slice 1 verification: the shipped FormatButton uses 44×44 px targets with 20 px glyphs for coarse pointers and 28×28 px targets with 18 px glyphs for fine pointers. Apple HIG buttons guidance confirms the general 44×44 pt hit region; its accessibility sizing table lists 28×28 pt as the macOS default button size. I added both source links beside the §42 measurements in commit 6dd63aa9.

Slice 1 verification: the shipped FormatButton uses 44×44 px targets with 20 px glyphs for coarse pointers and 28×28 px targets with 18 px glyphs for fine pointers. Apple HIG buttons guidance confirms the general 44×44 pt hit region; its accessibility sizing table lists 28×28 pt as the macOS default button size. I added both source links beside the §42 measurements in commit 6dd63aa9.
Author
Owner

Test-runner finding: a component test named FormatButton.test.ts ran in Vitest's unit project and resolved Svelte's server entry, so mount() was unavailable. The app config assigns browser resolution only to *.svelte.test.ts; renaming the test to that form selected the component project. The new button tests now pass, and bun run check reports svelte-check found 0 errors and 0 warnings.

Test-runner finding: a component test named `FormatButton.test.ts` ran in Vitest's unit project and resolved Svelte's server entry, so `mount()` was unavailable. The app config assigns browser resolution only to `*.svelte.test.ts`; renaming the test to that form selected the component project. The new button tests now pass, and `bun run check` reports `svelte-check found 0 errors and 0 warnings`.
Author
Owner

The phone action row is now a reusable row over the existing PillGroup. It keeps Make a note, Remind and Copy link visible but disabled for multi-selection, and keeps Move, Duplicate and Delete available. Focused verification: 5 test files, 8 tests passed; bun run check reported svelte-check found 0 errors and 0 warnings. Parent surface wiring is pending the #182 selection-mode change.

The phone action row is now a reusable row over the existing PillGroup. It keeps Make a note, Remind and Copy link visible but disabled for multi-selection, and keeps Move, Duplicate and Delete available. Focused verification: 5 test files, 8 tests passed; bun run check reported svelte-check found 0 errors and 0 warnings. Parent surface wiring is pending the #182 selection-mode change.
Author
Owner

Progress (HEAD 469bc5b4): added the fine-pointer block hover action capsule and its component tests. It reuses the shared PillGroup, FormatButton and block action descriptors; targets use the existing 28 px fine-pointer and 44 px coarse-pointer metrics. Test output: Test Files 1 passed (1) / Tests 2 passed (2). Check output: svelte-check found 0 errors and 0 warnings.

The component test caught a Svelte effect_update_depth_exceeded loop when root registration measured the anchor. The root callback now runs under untrack; both tests pass. The editor host wiring still depends on #182 landing in dev.

Progress (HEAD 469bc5b4): added the fine-pointer block hover action capsule and its component tests. It reuses the shared `PillGroup`, `FormatButton` and block action descriptors; targets use the existing 28 px fine-pointer and 44 px coarse-pointer metrics. Test output: `Test Files 1 passed (1)` / `Tests 2 passed (2)`. Check output: `svelte-check found 0 errors and 0 warnings`. The component test caught a Svelte `effect_update_depth_exceeded` loop when root registration measured the anchor. The root callback now runs under `untrack`; both tests pass. The editor host wiring still depends on #182 landing in `dev`.
Author
Owner

Finding (HEAD 69e60600): typed local reminder time 2026-03-29 02:30 in Europe/Berlin falls inside the spring DST gap. JavaScript normalizes that wall time to a later clock time, which would schedule an instant the user did not enter. The picker now checks calendar and local clock components after conversion and asks the user to choose another time when the wall time is missing. TZ=Europe/Berlin bun run test -- src/lib/editor/format/reminderDateTime.test.ts: Test Files 1 passed (1), Tests 3 passed (3). bun run check: svelte-check found 0 errors and 0 warnings.

Finding (HEAD 69e60600): typed local reminder time `2026-03-29 02:30` in `Europe/Berlin` falls inside the spring DST gap. JavaScript normalizes that wall time to a later clock time, which would schedule an instant the user did not enter. The picker now checks calendar and local clock components after conversion and asks the user to choose another time when the wall time is missing. `TZ=Europe/Berlin bun run test -- src/lib/editor/format/reminderDateTime.test.ts`: `Test Files 1 passed (1)`, `Tests 3 passed (3)`. `bun run check`: `svelte-check found 0 errors and 0 warnings`.
Author
Owner

Add to this job (Claude review of #182 screenshots): the block selection ring sits flush against the text (no inner space). Draw it outset from the text box (about 6px on each side, radius matching the block surface) so text never touches the ring, and keep the drag handle aligned with the first line.

Add to this job (Claude review of #182 screenshots): the block selection ring sits flush against the text (no inner space). Draw it outset from the text box (about 6px on each side, radius matching the block surface) so text never touches the ring, and keep the drag handle aligned with the first line.
Author
Owner

Finding after merging #182: the production Notes E2E exposed two integration gaps. The merged editor still mounts neither the desktop block capsule nor the phone block-action row. The selection cue also computed outlineOffset: 0px, matching the screenshot review note that the ring touches the text. I added production E2E assertions for both toolbars, a 6px ring gap, and disabled single-target actions for Shift-click multi-selection; wiring is in progress.

Finding after merging #182: the production Notes E2E exposed two integration gaps. The merged editor still mounts neither the desktop block capsule nor the phone block-action row. The selection cue also computed `outlineOffset: 0px`, matching the screenshot review note that the ring touches the text. I added production E2E assertions for both toolbars, a 6px ring gap, and disabled single-target actions for Shift-click multi-selection; wiring is in progress.
Author
Owner

Full workspace test after merging the latest dev into #152 on 2026-09-26:

  • cargo test failed in calternal-plugin-notifications::reminders::tests::reminder_paths_and_block_ids_are_constrained: date_from_daily_note_path("Notes/Journal/20260924-dailynote.md") returned None, but the test expected Some("2026-09-24").
  • The helper strips only Notes/Journal/ and validates the result with calternal_notes_core::daily_note_path, which now returns the canonical flat Notes/YYYYMMDD-dailynote.md path per DESIGN §40.
  • The runtime scanner also queries only Notes/Journal/%-dailynote.md and rejects paths outside Notes/Journal/. A Log reminder stored in a flat Daily note therefore cannot be found or delivered.

This crosses into notifications behavior. I am holding that change pending scope direction; #191 is the existing owner issue for block reminders.

Full workspace test after merging the latest `dev` into #152 on 2026-09-26: - `cargo test` failed in `calternal-plugin-notifications::reminders::tests::reminder_paths_and_block_ids_are_constrained`: `date_from_daily_note_path("Notes/Journal/20260924-dailynote.md")` returned `None`, but the test expected `Some("2026-09-24")`. - The helper strips only `Notes/Journal/` and validates the result with `calternal_notes_core::daily_note_path`, which now returns the canonical flat `Notes/YYYYMMDD-dailynote.md` path per DESIGN §40. - The runtime scanner also queries only `Notes/Journal/%-dailynote.md` and rejects paths outside `Notes/Journal/`. A Log reminder stored in a flat Daily note therefore cannot be found or delivered. This crosses into notifications behavior. I am holding that change pending scope direction; #191 is the existing owner issue for block reminders.
Author
Owner

Production Notes E2E evidence: after selection, menu, collaboration and screenshots passed, the final uncaught-error check found from the block menu's Escape path and repeated Svelte effect-depth errors when the floating text toolbar mounted. The toolbar root callback now updates outside the PillGroup effect, and the menu keeps its data object through close. I am rebuilding and rerunning the production E2E against these fixes.

Production Notes E2E evidence: after selection, menu, collaboration and screenshots passed, the final uncaught-error check found from the block menu's Escape path and repeated Svelte effect-depth errors when the floating text toolbar mounted. The toolbar root callback now updates outside the PillGroup effect, and the menu keeps its data object through close. I am rebuilding and rerunning the production E2E against these fixes.
Author
Owner

Production Notes E2E evidence: after selection, menu, collaboration and screenshots passed, the final uncaught-error check found a null items read from the block menu Escape path and repeated Svelte effect-depth errors when the floating text toolbar mounted. The toolbar root callback now updates outside the PillGroup effect, and the menu keeps its data object through close. I am rebuilding and rerunning the production E2E against these fixes.

Production Notes E2E evidence: after selection, menu, collaboration and screenshots passed, the final uncaught-error check found a null items read from the block menu Escape path and repeated Svelte effect-depth errors when the floating text toolbar mounted. The toolbar root callback now updates outside the PillGroup effect, and the menu keeps its data object through close. I am rebuilding and rerunning the production E2E against these fixes.
Author
Owner

The production Notes E2E now passes: notes e2e: ok. It covers the desktop block toolbar, multi-select action disabling and menu cleanup, the 6 px selection ring, phone action and text bars in both themes, and the existing Notes collaboration flow. Targeted block menu tests also pass (3/3).

The production Notes E2E now passes: notes e2e: ok. It covers the desktop block toolbar, multi-select action disabling and menu cleanup, the 6 px selection ring, phone action and text bars in both themes, and the existing Notes collaboration flow. Targeted block menu tests also pass (3/3).
Author
Owner

A real editor test found that Copy link could not resolve a list-item container position (ensureAnchorAt returned null). The anchor resolver now finds the item's first text block, and refuses to insert an anchor into a code block where it would become visible code. Both reminder/anchor tests pass.

A real editor test found that Copy link could not resolve a list-item container position (ensureAnchorAt returned null). The anchor resolver now finds the item's first text block, and refuses to insert an anchor into a code block where it would become visible code. Both reminder/anchor tests pass.
Author
Owner

Finding during the production Notes E2E: Back returned to /notes/<source-id> with the source title Project Atlas and browser history intact. /n/<id> is the public stable alias and redirects with replaceState to /notes/<id>, so the new E2E helper's /n expectation timed out despite the app route being correct. The helper now waits for /notes/<id>; the production flow is running again.

Finding during the production Notes E2E: Back returned to `/notes/<source-id>` with the source title `Project Atlas` and browser history intact. `/n/<id>` is the public stable alias and redirects with replaceState to `/notes/<id>`, so the new E2E helper's `/n` expectation timed out despite the app route being correct. The helper now waits for `/notes/<id>`; the production flow is running again.
Author
Owner

Finding in the production Notes E2E: Make a note, Back, and opening the existing linked Note all passed after the route assertion matched /notes/<id>. Returning from that linked Note remounted the source editor and cleared its prior selection anchor; the later Shift-click selected only Goals (one .cal-block-selected node). I am updating the E2E to select its range start again after the Note navigation, then rerunning the full production flow.

Finding in the production Notes E2E: Make a note, Back, and opening the existing linked Note all passed after the route assertion matched `/notes/<id>`. Returning from that linked Note remounted the source editor and cleared its prior selection anchor; the later Shift-click selected only `Goals` (one `.cal-block-selected` node). I am updating the E2E to select its range start again after the Note navigation, then rerunning the full production flow.
Author
Owner

Production Notes E2E passed on the real local server after the route and range-start assertions were corrected. Exact output: notes e2e: ok. It covers Make a note creation, Back to the source Note, opening the existing linked Note, reminder creation and chip rendering, L stable block links, multi-select action states, keyboard selection, and Notes collaboration. I committed the E2E changes and this issue's desktop/phone screenshots in e24233b1 (test(web): cover block actions and capture issue screenshots).

Production Notes E2E passed on the real local server after the route and range-start assertions were corrected. Exact output: `notes e2e: ok`. It covers Make a note creation, Back to the source Note, opening the existing linked Note, reminder creation and chip rendering, `L` stable block links, multi-select action states, keyboard selection, and Notes collaboration. I committed the E2E changes and this issue's desktop/phone screenshots in `e24233b1` (`test(web): cover block actions and capture issue screenshots`).
Author
Owner

Finding from the first local-server adversarial attempt: attack.py stopped before the new Note reminder probes at its existing quiet-hours assertion, with TypeError: check() got an unexpected keyword argument 'expect'. Multiple expect= call sites were already present, but check() did not accept expected statuses. I added the same expected-status handling used by attack2.py; the current run is finishing its remaining probes, then I will rerun with the corrected helper. The attempt also reported a Photos shared-timeline mismatch under concurrent host load; I will verify that separately before classifying it.

Finding from the first local-server adversarial attempt: `attack.py` stopped before the new Note reminder probes at its existing quiet-hours assertion, with `TypeError: check() got an unexpected keyword argument 'expect'`. Multiple `expect=` call sites were already present, but `check()` did not accept expected statuses. I added the same expected-status handling used by `attack2.py`; the current run is finishing its remaining probes, then I will rerun with the corrected helper. The attempt also reported a Photos shared-timeline mismatch under concurrent host load; I will verify that separately before classifying it.
Author
Owner

The Photos timeline and Files 404 findings were cascading setup failures from the same probe exception: attack.py creates Photos/2024/2024-06-02/adversarial-photo.jpg later in its Photos section, but the TypeError at the earlier quiet-hours check stopped before that upload. attack2.py then checked the missing fixture. I will rerun its isolation and plugins sections after the helper fix, with the fixture present, to verify the cross-Plugin behavior.

The Photos timeline and Files 404 findings were cascading setup failures from the same probe exception: `attack.py` creates `Photos/2024/2024-06-02/adversarial-photo.jpg` later in its Photos section, but the TypeError at the earlier quiet-hours check stopped before that upload. `attack2.py` then checked the missing fixture. I will rerun its `isolation` and `plugins` sections after the helper fix, with the fixture present, to verify the cross-Plugin behavior.
Author
Owner

Merged the latest dev into job/block-actions as 6cf75175 before final verification. The OpenAPI schema conflict was additive: I kept this branch's RecipientPreview and reminder response field, retained dev's RangeMetrics/RangeReport and search 401/403 responses, then regenerated packages/api-client/src/generated.ts from the combined contract. The contract parses and has no unresolved merge values. The full adversarial runner is now building and will wait for another worktree's active Chromium process before browser probes begin.

Merged the latest `dev` into `job/block-actions` as `6cf75175` before final verification. The OpenAPI schema conflict was additive: I kept this branch's `RecipientPreview` and reminder response field, retained `dev`'s `RangeMetrics`/`RangeReport` and search 401/403 responses, then regenerated `packages/api-client/src/generated.ts` from the combined contract. The contract parses and has no unresolved merge values. The full adversarial runner is now building and will wait for another worktree's active Chromium process before browser probes begin.
Author
Owner

Finding after the dev merge: the FloatingSurface export moved below Menu on dev, while this branch still exported it at its old position. Git kept both lines; Vite 8.3.0 then failed the production build with Duplicated export 'FloatingSurface' in packages/ui/src/index.ts:6 and :21. I removed the old duplicate and kept the relocated export. Verification: bun run --cwd apps/web build exited 0 and wrote the production site to build. Fix committed as 5037ad32.

Finding after the `dev` merge: the `FloatingSurface` export moved below `Menu` on `dev`, while this branch still exported it at its old position. Git kept both lines; Vite 8.3.0 then failed the production build with `Duplicated export 'FloatingSurface'` in `packages/ui/src/index.ts:6` and `:21`. I removed the old duplicate and kept the relocated export. Verification: `bun run --cwd apps/web build` exited 0 and wrote the production site to `build`. Fix committed as `5037ad32`.
Author
Owner

Finding after the dev merge: authz_matrix.py stopped before sending its authorization requests because the new /api/v1/analytics operation had no identity classification. The handler calls data_scope_user and reads the authenticated User's data, so I added it to the matrix's data prefixes. python3 -m py_compile tests/adversarial/authz_matrix.py passed, and a focused classification check returned standard and admin only. Fix committed as 596f30ea. The in-flight run had already loaded the old mapping, so I will verify the matrix with a clean local server after its remaining probes finish.

Finding after the `dev` merge: `authz_matrix.py` stopped before sending its authorization requests because the new `/api/v1/analytics` operation had no identity classification. The handler calls `data_scope_user` and reads the authenticated User's data, so I added it to the matrix's data prefixes. `python3 -m py_compile tests/adversarial/authz_matrix.py` passed, and a focused classification check returned `standard and admin only`. Fix committed as `596f30ea`. The in-flight run had already loaded the old mapping, so I will verify the matrix with a clean local server after its remaining probes finish.
Author
Owner

Resuming #191 after the VM restart. Branch job/block-actions is clean at 596f30ea87bed6ca8abeab94cc8da1313ce725fe; current dev is f6418fc72d750d941f23554ad16381f6dd400fd6 and is merged in 6cf75175. I read §42 and the prior report comments. The pending task is to finish the local-server adversarial verification, then run the final workspace gates, clean generated build output, and post the final report.

Resuming #191 after the VM restart. Branch `job/block-actions` is clean at `596f30ea87bed6ca8abeab94cc8da1313ce725fe`; current `dev` is `f6418fc72d750d941f23554ad16381f6dd400fd6` and is merged in `6cf75175`. I read §42 and the prior report comments. The pending task is to finish the local-server adversarial verification, then run the final workspace gates, clean generated build output, and post the final report.
Author
Owner

Adversarial finding: POST /api/v1/notes/{id}/reminders with audience: "public" returned 422 from Axum JSON deserialization, while the probe expected 400. The route documents 400 for semantic validation but does not document the extractor's invalid-enum 422. I will update the probe and OpenAPI response list to record the actual malformed-field response. The same run did not return the semantic-search fixture within its 120-second deadline; I am classifying that timeout as load/model latency and will confirm from the final runner output.

Adversarial finding: `POST /api/v1/notes/{id}/reminders` with `audience: "public"` returned 422 from Axum JSON deserialization, while the probe expected 400. The route documents 400 for semantic validation but does not document the extractor's invalid-enum 422. I will update the probe and OpenAPI response list to record the actual malformed-field response. The same run did not return the semantic-search fixture within its 120-second deadline; I am classifying that timeout as load/model latency and will confirm from the final runner output.
Author
Owner

Final Clippy finding in the touched Notes API file: clippy::redundant_closure at crates/plugins/notes/src/reminders_api.rs:243 for .ok_or_else(|| ApiError::internal()). The lint gate is -D warnings; I will use the associated function directly and rerun Clippy.

Final Clippy finding in the touched Notes API file: `clippy::redundant_closure` at `crates/plugins/notes/src/reminders_api.rs:243` for `.ok_or_else(|| ApiError::internal())`. The lint gate is `-D warnings`; I will use the associated function directly and rerun Clippy.
Author
Owner

Resolved the Clippy finding in commit 98a8c008 by passing ApiError::internal directly to ok_or_else. Focused verification passed: cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings finished successfully.

Resolved the Clippy finding in commit `98a8c008` by passing `ApiError::internal` directly to `ok_or_else`. Focused verification passed: `cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings` finished successfully.
Author
Owner

Finding after the latest dev merge: bash packages/api-client/check-generated.sh found that the four Public Edit operations in the merged OpenAPI/client were missing their standard 429 ErrorEnvelope responses. The server's generated document includes these rate-limit responses, so I regenerated and am staging the contract and TypeScript client before rerunning the consistency check.

Finding after the latest `dev` merge: `bash packages/api-client/check-generated.sh` found that the four Public Edit operations in the merged OpenAPI/client were missing their standard 429 `ErrorEnvelope` responses. The server's generated document includes these rate-limit responses, so I regenerated and am staging the contract and TypeScript client before rerunning the consistency check.
Author
Owner

Merge decision for the public-guest editor: preserve the incoming Home-relative link, note-card and attachment restrictions. Hide the #191 block-action controls and L shortcut in that editor because the public-guest session has no authenticated adapters for Make a note, Copy link or Remind. Text editing and formatting remain available. This keeps the public share boundary while retaining block actions for account-backed Note editors.

Merge decision for the public-guest editor: preserve the incoming Home-relative link, note-card and attachment restrictions. Hide the #191 block-action controls and `L` shortcut in that editor because the public-guest session has no authenticated adapters for Make a note, Copy link or Remind. Text editing and formatting remain available. This keeps the public share boundary while retaining block actions for account-backed Note editors.
Author
Owner

Completed #191 on job/block-actions.

Built

Implemented shared formatting capsules and block actions for desktop and phone: hover controls, the block menu, Make a note, block-link copying, and block reminders. Added reminder persistence and APIs, fire-time recipient resolution, per-user Done/Snooze, quiet-hour delivery, settings, inbox handling, and OpenAPI/TypeScript client updates. Added the Apple HIG capsule metrics to docs/DESIGN.md §42, adversarial probes, and 37 real-build screenshots in apps/web/artifacts/block-actions-191/.

Files include apps/web/src/lib/editor/format/*, apps/web/src/lib/notes/{NoteEditorSurface.svelte,NoteView.svelte,editorHost.ts,api.ts}, the editor and notification settings, crates/plugins/notes/{src/reminders_api.rs,src/store.rs,migrations/0012_block_reminders.sql}, crates/plugins/notifications/{src/block_reminders.rs,src/routes.rs,src/store.rs,migrations/0003_block_reminders.sql}, crates/calternal-notes-core/src/reminders.rs, contracts/openapi.json, packages/api-client/src/generated.ts, apps/web/e2e/notes.mjs, and tests/adversarial/attack.py.

Head SHA: 5993a437d941be37c433f9198dfc5e684e4e77f1.

Gates

  • cargo fmt --check: exit 0, no output.
  • cargo clippy --all-targets -- -D warnings: Finished dev profile [unoptimized + debuginfo] target(s) in 21.40s
  • cargo test: exit 0. Aggregating its 69 suite summaries: 1,246 passed, 0 failed, 12 ignored. The Notes suite output was test result: ok. 95 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 63.63s; Notifications was test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s.
  • bun run --cwd apps/web check: svelte-check found 0 errors and 0 warnings
  • bun run --cwd apps/web test: Test Files 79 passed (79); Tests 564 passed (564).
  • bash packages/api-client/check-generated.sh: 🚀 ../../contracts/openapi.json → src/generated.ts [359.8ms]; no generated diff.
  • Cleanup: Removed 7551 files, 9.2GiB total; apps/web/build was deleted.

Adversarial findings and gaps

After merging dev through 21ee397d, the live-server suite covered 237 OpenAPI operations across four identities (948 requests). The server stayed alive; hostile-byte checks had 0 findings; the restart probe had 0 findings. The runner exited 1 on two non-SLOW findings, both recorded on existing issues: a Journal log-rewrite storm returned [-1, 200, 412] (#205), and public thumbnail requests returned {256: 404, 1024: 404} after thumbnail generation had already timed out at 30 seconds (SLOW) (#213). The earlier bookmark-capture timeout finding is tracked on #210. Remaining SLOW timings occurred on the shared host and are treated as load.

The semantic recall timeout reported earlier on #208 returned the expected Apartment hunting Note in both later full adversarial runs.

Decisions where DESIGN.md was silent

  • Public Edit guests keep text editing, but do not see or invoke block actions. Make a note, Reminder, and stable private block links rely on the private Note identity and have no guest-safe adapter.
  • Invalid reminder request bodies use HTTP 422 with a text/plain schema-error response. OpenAPI and the TypeScript client now declare this response.
Completed #191 on `job/block-actions`. ## Built Implemented shared formatting capsules and block actions for desktop and phone: hover controls, the block menu, Make a note, block-link copying, and block reminders. Added reminder persistence and APIs, fire-time recipient resolution, per-user Done/Snooze, quiet-hour delivery, settings, inbox handling, and OpenAPI/TypeScript client updates. Added the Apple HIG capsule metrics to `docs/DESIGN.md` §42, adversarial probes, and 37 real-build screenshots in `apps/web/artifacts/block-actions-191/`. Files include `apps/web/src/lib/editor/format/*`, `apps/web/src/lib/notes/{NoteEditorSurface.svelte,NoteView.svelte,editorHost.ts,api.ts}`, the editor and notification settings, `crates/plugins/notes/{src/reminders_api.rs,src/store.rs,migrations/0012_block_reminders.sql}`, `crates/plugins/notifications/{src/block_reminders.rs,src/routes.rs,src/store.rs,migrations/0003_block_reminders.sql}`, `crates/calternal-notes-core/src/reminders.rs`, `contracts/openapi.json`, `packages/api-client/src/generated.ts`, `apps/web/e2e/notes.mjs`, and `tests/adversarial/attack.py`. Head SHA: `5993a437d941be37c433f9198dfc5e684e4e77f1`. ## Gates - `cargo fmt --check`: exit 0, no output. - `cargo clippy --all-targets -- -D warnings`: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 21.40s` - `cargo test`: exit 0. Aggregating its 69 suite summaries: 1,246 passed, 0 failed, 12 ignored. The Notes suite output was `test result: ok. 95 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 63.63s`; Notifications was `test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.40s`. - `bun run --cwd apps/web check`: `svelte-check found 0 errors and 0 warnings` - `bun run --cwd apps/web test`: `Test Files 79 passed (79)`; `Tests 564 passed (564)`. - `bash packages/api-client/check-generated.sh`: `🚀 ../../contracts/openapi.json → src/generated.ts [359.8ms]`; no generated diff. - Cleanup: `Removed 7551 files, 9.2GiB total`; `apps/web/build` was deleted. ## Adversarial findings and gaps After merging `dev` through `21ee397d`, the live-server suite covered 237 OpenAPI operations across four identities (948 requests). The server stayed alive; hostile-byte checks had 0 findings; the restart probe had 0 findings. The runner exited 1 on two non-`SLOW` findings, both recorded on existing issues: a Journal log-rewrite storm returned `[-1, 200, 412]` (#205), and public thumbnail requests returned `{256: 404, 1024: 404}` after thumbnail generation had already timed out at 30 seconds (`SLOW`) (#213). The earlier bookmark-capture timeout finding is tracked on #210. Remaining `SLOW` timings occurred on the shared host and are treated as load. The semantic recall timeout reported earlier on #208 returned the expected `Apartment hunting` Note in both later full adversarial runs. ## Decisions where DESIGN.md was silent - Public Edit guests keep text editing, but do not see or invoke block actions. Make a note, Reminder, and stable private block links rely on the private Note identity and have no guest-safe adapter. - Invalid reminder request bodies use HTTP 422 with a `text/plain` schema-error response. OpenAPI and the TypeScript client now declare this response.
Author
Owner

Claude review (block-actions-191 screenshots). Not merged yet:

  1. BUG (data display): block-actions-phone-390-tokyo-night shows a raw block id in the text: '^iefhey Scope confirmed with the leads.' A ^block-id must never render as text and must stay at the end of its block's line in the Markdown. Find how it got into the middle/second line (reminder or make-note assigning an id to a paragraph with a hard break or a following sentence?), fix the writer and the renderer, and add a round-trip test plus a break-editor style case.
  2. Reminder picker: 'Delivery time for 2026-09-27T00:44' prints a raw ISO string. Use the shared date formatter (#179 when it lands; until then the existing locale formatter) and the recipient's zone label (not 'UTC' for an IST user; the test user should carry a real zone).
  3. Phone block mode: the mode tray and primary pill still show behind the block action capsule. DESIGN §42: the block bar replaces the tray while a block is selected (one bottom bar).
  4. 'Editing with Kay, Kay': the same person in two tabs appears twice; de-duplicate presence by user.
  5. Selection ring on desktop is fine now; keep the outset.
    Re-take the phone and picker screenshots after the fixes.
Claude review (block-actions-191 screenshots). Not merged yet: 1. BUG (data display): block-actions-phone-390-tokyo-night shows a raw block id in the text: '^iefhey Scope confirmed with the leads.' A ^block-id must never render as text and must stay at the end of its block's line in the Markdown. Find how it got into the middle/second line (reminder or make-note assigning an id to a paragraph with a hard break or a following sentence?), fix the writer and the renderer, and add a round-trip test plus a break-editor style case. 2. Reminder picker: 'Delivery time for 2026-09-27T00:44' prints a raw ISO string. Use the shared date formatter (#179 when it lands; until then the existing locale formatter) and the recipient's zone label (not 'UTC' for an IST user; the test user should carry a real zone). 3. Phone block mode: the mode tray and primary pill still show behind the block action capsule. DESIGN §42: the block bar replaces the tray while a block is selected (one bottom bar). 4. 'Editing with Kay, Kay': the same person in two tabs appears twice; de-duplicate presence by user. 5. Selection ring on desktop is fine now; keep the outset. Re-take the phone and picker screenshots after the fixes.
Author
Owner

Continuation start: branch job/block-actions, HEAD 5993a437d941be37c433f9198dfc5e684e4e77f1; dev at 21ee397d5c3d24f6355d90b17c2e36b2cf76615a is already merged in this branch. I am fixing the five points in Claude's latest review and will commit each fix separately, then re-take the phone and reminder-picker screenshots.

Continuation start: branch `job/block-actions`, HEAD `5993a437d941be37c433f9198dfc5e684e4e77f1`; `dev` at `21ee397d5c3d24f6355d90b17c2e36b2cf76615a` is already merged in this branch. I am fixing the five points in Claude's latest review and will commit each fix separately, then re-take the phone and reminder-picker screenshots.
Author
Owner

Owner 2026-09-27: the reminder picker 'needs to look way better, it doesn't fit our app aesthetic'. Redesign it to this spec (references: Things 3 'When' popover, Todoist web date picker, Linear due-date popover):

  • Surface: the shared overlay glass popover (#157), about 320px wide, anchored to the clock button with collision handling (flip/shift): it must never be clipped by the viewport (today it runs off the right edge).
  • Top: ONE natural-language field with a clock icon, placeholder 'Remind me… e.g. tomorrow 9am'. As you type, a live chip under it shows the parsed time in the user's format ('Tomorrow · Sun 28 Sep, 09:00'). No 'Preview' button; Enter sets it.
  • Presets: a vertical list styled exactly like the shared menu rows (#176): leading icon, label, resolved time right-aligned in muted text (e.g. 'Later today · 15:00', 'This evening · 18:00', 'Tomorrow morning · Sun 09:00', 'Next week · Mon 09:00'). Hover/arrow-key highlight like menus. Clicking a preset sets the reminder at once (Things-style one click) and shows a toast with Undo. No boxed buttons, no big 'Set reminder' button.
  • Audience: the shared borderless segmented pill (#183), small: 'Everyone · Only me', with recipient avatars beside it.
  • Recipient times: hidden when they match yours; shown only when they differ or fall in quiet hours, as one compact row per person: avatar, name, their local time, and '(quiet hours, arrives 06:00)' when held.
  • No ISO strings anywhere; all times through the shared formatter; a zone label only when it differs from the device zone.
  • An existing reminder shows at the top as a row with its time and a Remove action.
  • Phone: the same content in a bottom sheet opened from the block bar's Remind, 44pt rows.
    Screenshots: popover near the right edge (proves no clipping), with typed input, with a quiet-hours recipient, existing reminder; light and dark; 1440 and 390.
Owner 2026-09-27: the reminder picker 'needs to look way better, it doesn't fit our app aesthetic'. Redesign it to this spec (references: Things 3 'When' popover, Todoist web date picker, Linear due-date popover): - Surface: the shared overlay glass popover (#157), about 320px wide, anchored to the clock button with collision handling (flip/shift): it must never be clipped by the viewport (today it runs off the right edge). - Top: ONE natural-language field with a clock icon, placeholder 'Remind me… e.g. tomorrow 9am'. As you type, a live chip under it shows the parsed time in the user's format ('Tomorrow · Sun 28 Sep, 09:00'). No 'Preview' button; Enter sets it. - Presets: a vertical list styled exactly like the shared menu rows (#176): leading icon, label, resolved time right-aligned in muted text (e.g. 'Later today · 15:00', 'This evening · 18:00', 'Tomorrow morning · Sun 09:00', 'Next week · Mon 09:00'). Hover/arrow-key highlight like menus. Clicking a preset sets the reminder at once (Things-style one click) and shows a toast with Undo. No boxed buttons, no big 'Set reminder' button. - Audience: the shared borderless segmented pill (#183), small: 'Everyone · Only me', with recipient avatars beside it. - Recipient times: hidden when they match yours; shown only when they differ or fall in quiet hours, as one compact row per person: avatar, name, their local time, and '(quiet hours, arrives 06:00)' when held. - No ISO strings anywhere; all times through the shared formatter; a zone label only when it differs from the device zone. - An existing reminder shows at the top as a row with its time and a Remove action. - Phone: the same content in a bottom sheet opened from the block bar's Remind, 44pt rows. Screenshots: popover near the right edge (proves no clipping), with typed input, with a quiet-hours recipient, existing reminder; light and dark; 1440 and 390.
Author
Owner

Owner 2026-09-27 (screenshot of the phone block bar showing 'Make a note / Remind me / Copy li…' with text): the bottom capsule shows ICONS ONLY, no text. Labels on demand, implemented once in the shared tooltip/capsule so every icon capsule on touch behaves the same (tray, pills, block bar):

  • tap = run the action;
  • touch-and-hold ~450ms = show the warm tooltip (same component as desktop hover) above the icon; releasing after the tooltip appeared does NOT run the action (a peek);
  • while holding, sliding along the capsule moves the tooltip to the icon under the finger;
  • no text selection or iOS callout on these buttons (user-select: none, -webkit-touch-callout: none), subtle press scale, reduced motion respected;
  • every button keeps its aria-label; a unit/e2e test covers tap vs hold vs slide.
    Also record the rule in DESIGN §34 (touch tooltips) in ASD-STE100.
Owner 2026-09-27 (screenshot of the phone block bar showing 'Make a note / Remind me / Copy li…' with text): the bottom capsule shows ICONS ONLY, no text. Labels on demand, implemented once in the shared tooltip/capsule so every icon capsule on touch behaves the same (tray, pills, block bar): - tap = run the action; - touch-and-hold ~450ms = show the warm tooltip (same component as desktop hover) above the icon; releasing after the tooltip appeared does NOT run the action (a peek); - while holding, sliding along the capsule moves the tooltip to the icon under the finger; - no text selection or iOS callout on these buttons (user-select: none, -webkit-touch-callout: none), subtle press scale, reduced motion respected; - every button keeps its aria-label; a unit/e2e test covers tap vs hold vs slide. Also record the rule in DESIGN §34 (touch tooltips) in ASD-STE100.
Author
Owner

Continuation on job/block-actions. Starting HEAD 5993a437d9; current dev is cf142cb41e. The worktree is clean. I am implementing the latest review items: anchor rendering/writing, phone tray replacement, presence de-duplication, localized time formatting, reminder picker redesign, and shared touch-and-hold tooltips; I will commit each fix separately and re-take the requested production screenshots.

Continuation on job/block-actions. Starting HEAD 5993a437d941be37c433f9198dfc5e684e4e77f1; current dev is cf142cb41e69334705b32f72292daa0ac4c45d5a. The worktree is clean. I am implementing the latest review items: anchor rendering/writing, phone tray replacement, presence de-duplication, localized time formatting, reminder picker redesign, and shared touch-and-hold tooltips; I will commit each fix separately and re-take the requested production screenshots.
Author
Owner

Diagnosis for the leaking ^iefhey (Claude): packages/editor/src/anchor.ts applyBlockAnchor with placement 'after' writes the id on its own line ('\n^id') after the block. For a paragraph that line is a soft break INSIDE the same paragraph; when the paragraph is later continued (here 'Scope confirmed with the leads.'), the id is no longer at the end, so the parser keeps it as text. Fix at the source: for paragraphs, list items and headings, write the id inline at the end of the block's last line (' ^id'); keep the own-line form only for blocks that need it (tables, code fences, quotes). The serializer must always move a block's anchor to its end on write, and the parser should also recover a mid-paragraph '^id' line followed by more text (repair on load, preserving bytes elsewhere). Tests: continue a paragraph after its id was assigned, from the editor and via collab; round-trip keeps exactly one trailing id.

Diagnosis for the leaking ^iefhey (Claude): packages/editor/src/anchor.ts applyBlockAnchor with placement 'after' writes the id on its own line ('\n^id') after the block. For a paragraph that line is a soft break INSIDE the same paragraph; when the paragraph is later continued (here 'Scope confirmed with the leads.'), the id is no longer at the end, so the parser keeps it as text. Fix at the source: for paragraphs, list items and headings, write the id inline at the end of the block's last line (' ^id'); keep the own-line form only for blocks that need it (tables, code fences, quotes). The serializer must always move a block's anchor to its end on write, and the parser should also recover a mid-paragraph '^id' line followed by more text (repair on load, preserving bytes elsewhere). Tests: continue a paragraph after its id was assigned, from the editor and via collab; round-trip keeps exactly one trailing id.
Author
Owner

Owner 2026-09-27 (screenshot of the desktop block controls):

  1. Order of the hover controls: → , clock, ⋯ (the ⋯ 'more' menu is LAST, not first).
  2. The controls pill must not touch the selected block: keep a consistent gap (8px) between the selection and the pill.
  3. Selection style: replace the bordered rectangle with a Craft-style TINT: the selected block gets a soft accent-tinted rounded fill (no border, no ring), on desktop and phones (Craft screenshots: a teal/accent wash behind the block). Multi-select tints each selected block the same way.
  4. General rule (also DESIGN §34): no border highlights or outlines for selected/active/current states anywhere; use fills/tints. Keyboard focus keeps a focus ring only via :focus-visible (accessibility); pointer and touch never show it.
Owner 2026-09-27 (screenshot of the desktop block controls): 1. Order of the hover controls: → , clock, ⋯ (the ⋯ 'more' menu is LAST, not first). 2. The controls pill must not touch the selected block: keep a consistent gap (8px) between the selection and the pill. 3. Selection style: replace the bordered rectangle with a Craft-style TINT: the selected block gets a soft accent-tinted rounded fill (no border, no ring), on desktop and phones (Craft screenshots: a teal/accent wash behind the block). Multi-select tints each selected block the same way. 4. General rule (also DESIGN §34): no border highlights or outlines for selected/active/current states anywhere; use fills/tints. Keyboard focus keeps a focus ring only via :focus-visible (accessibility); pointer and touch never show it.
Author
Owner

Starting continuation of #191 on branch job/block-actions.
Base: 21ee397d5c
Current head: 5993a437d9

Starting continuation of #191 on branch job/block-actions. Base: 21ee397d5c3d24f6355d90b17c2e36b2cf76615a Current head: 5993a437d941be37c433f9198dfc5e684e4e77f1
Author
Owner

Finding: packages/editor/src/anchor.ts::applyRawBlockAnchorTarget handles every placement: 'after' target by inserting \n^<id> after the block. packages/editor/src/markdown.ts only recognizes a following anchor line when the full line contains anchor tokens, so adding text after that line makes ^<id> ordinary paragraph text. This matches the leak in the reviewed screenshot. I am adding a regression for paragraph continuation and fixing the source writer/parser plus editor collaboration path.

Finding: `packages/editor/src/anchor.ts::applyRawBlockAnchorTarget` handles every `placement: 'after'` target by inserting `\n^<id>` after the block. `packages/editor/src/markdown.ts` only recognizes a following anchor line when the full line contains anchor tokens, so adding text after that line makes `^<id>` ordinary paragraph text. This matches the leak in the reviewed screenshot. I am adding a regression for paragraph continuation and fixing the source writer/parser plus editor collaboration path.
Author
Owner

Finding: the focused Markdown and real editor anchor tests pass (198 editor tests; 3 Svelte anchor tests). The full editor suite then exposed that inline anchor ownership must stay in the shared Rust/TypeScript Markdown contract: six vector outputs still expect the old own-line spelling, and schema-equality cases see owner attrs on newly parsed headings and trailing ids. I am updating both readers/writers and the shared vectors together; the typecheck also found four Editor.svelte attributes set to undefined, which I will fix as a separate small gate repair.

Finding: the focused Markdown and real editor anchor tests pass (198 editor tests; 3 Svelte anchor tests). The full editor suite then exposed that inline anchor ownership must stay in the shared Rust/TypeScript Markdown contract: six vector outputs still expect the old own-line spelling, and schema-equality cases see owner attrs on newly parsed headings and trailing ids. I am updating both readers/writers and the shared vectors together; the typecheck also found four `Editor.svelte` attributes set to `undefined`, which I will fix as a separate small gate repair.
Author
Owner

The cross-language property round found that the Rust reader treated ^idtext as a stored block anchor while the editor kept it as text. Rust now requires a separator before continuation prose, matching the editor. I added the rule to the Markdown contract, refreshed both vector sets, and verified cargo test -p calternal-collab --test cross_language passes, including 5,000 generated cases per family.

The cross-language property round found that the Rust reader treated `^idtext` as a stored block anchor while the editor kept it as text. Rust now requires a separator before continuation prose, matching the editor. I added the rule to the Markdown contract, refreshed both vector sets, and verified `cargo test -p calternal-collab --test cross_language` passes, including 5,000 generated cases per family.
Author
Owner

Note presence used each Yjs client id as a peer key, so the same person appeared more than once when they opened the note in multiple tabs. Presence now keys by immutable User ID, omits other tabs for the local User, and keeps client-id fallback only for older states without an ID. collab.test.ts covers both cases; the focused suite passes (7 tests).

Note presence used each Yjs client id as a peer key, so the same person appeared more than once when they opened the note in multiple tabs. Presence now keys by immutable User ID, omits other tabs for the local User, and keeps client-id fallback only for older states without an ID. `collab.test.ts` covers both cases; the focused suite passes (7 tests).
Author
Owner

The reminder chip used Date.toLocaleString() in its accessible label, which exposed a machine-formatted timestamp, and the picker built a local ISO-like label by hand. Both now use shared @calternal/ui formatters. The chip uses the device IANA zone; recipient rows retain their server-computed local date and zone, with the clock localized by the shared formatter. Tests cover Europe/Berlin, America/Los_Angeles and localized wall clocks; the focused suite passes (11 tests).

The reminder chip used `Date.toLocaleString()` in its accessible label, which exposed a machine-formatted timestamp, and the picker built a local ISO-like label by hand. Both now use shared `@calternal/ui` formatters. The chip uses the device IANA zone; recipient rows retain their server-computed local date and zone, with the clock localized by the shared formatter. Tests cover Europe/Berlin, America/Los_Angeles and localized wall clocks; the focused suite passes (11 tests).
Author
Owner

Finding: the presence de-duplication change made the account id required, but public-link guest identities do not have one. bun run --cwd apps/web check reported the missing id at the guest provider boundary. Made the account id optional and passed an empty local-id fallback into the de-duplicator; the web check then passed with 0 errors and 0 warnings. Committed as 8dc73106.

Finding: the presence de-duplication change made the account id required, but public-link guest identities do not have one. `bun run --cwd apps/web check` reported the missing `id` at the guest provider boundary. Made the account id optional and passed an empty local-id fallback into the de-duplicator; the web check then passed with 0 errors and 0 warnings. Committed as `8dc73106`.
Author
Owner

Progress: the phone toolbar now uses icon-only controls and replaces the bottom mode tray while a block is selected. Desktop action order is →, clock, ⋯; the menu stays 8 px from the selected block and flips to the left when the right side has no room. Block selection now uses a tinted fill without a border or outline, with the keyboard focus ring rule kept separate. Focused block action tests passed (3/3); bun run check reported 0 errors and 0 warnings. Commit: a85ccc3c.

Progress: the phone toolbar now uses icon-only controls and replaces the bottom mode tray while a block is selected. Desktop action order is `→`, clock, `⋯`; the menu stays 8 px from the selected block and flips to the left when the right side has no room. Block selection now uses a tinted fill without a border or outline, with the keyboard focus ring rule kept separate. Focused block action tests passed (3/3); `bun run check` reported 0 errors and 0 warnings. Commit: `a85ccc3c`.
Author
Owner

Progress: the shared warm tooltip now supports a 450 ms touch hold. A held finger can slide across actions; release leaves a 900 ms peek and suppresses that gesture's click. A normal tap still runs the action. The shortcut label stays in aria-label, and the icon buttons disable text selection/callout and use a reduced-motion-aware press scale. The focused touch gesture test passed (1/1); bun run check reported 0 errors and 0 warnings. Commit: cce597fb.

Progress: the shared warm tooltip now supports a 450 ms touch hold. A held finger can slide across actions; release leaves a 900 ms peek and suppresses that gesture's click. A normal tap still runs the action. The shortcut label stays in `aria-label`, and the icon buttons disable text selection/callout and use a reduced-motion-aware press scale. The focused touch gesture test passed (1/1); `bun run check` reported 0 errors and 0 warnings. Commit: `cce597fb`.
Author
Owner

Finding: the reminder preview endpoint labels the setter as You. The audience avatar stack used the first character of that short label, so it showed Y instead of the account's real initials. It now maps the setter id back to the current account display name; a focused test checks Kay and Grace Hopper, and the E2E flow asserts K/G are shown with no Y. Focused helper tests passed (4/4), and bun run check reported 0 errors and 0 warnings. Commit: c4b209fd.

Finding: the reminder preview endpoint labels the setter as `You`. The audience avatar stack used the first character of that short label, so it showed Y instead of the account's real initials. It now maps the setter id back to the current account display name; a focused test checks Kay and Grace Hopper, and the E2E flow asserts K/G are shown with no Y. Focused helper tests passed (4/4), and `bun run check` reported 0 errors and 0 warnings. Commit: `c4b209fd`.
Author
Owner

Production-build local E2E evidence after merging dev: creating a block reminder added an active reminder with a generated blockId in the Note frontmatter, but GET /api/v1/notes/{id} did not show the matching raw ^block-id in the Markdown body within the 15 s save poll. This leaves the reminder target without its durable file anchor. I am tracing the editor transaction and source-aware save path before rerunning the screenshots.

Production-build local E2E evidence after merging dev: creating a block reminder added an active reminder with a generated blockId in the Note frontmatter, but GET /api/v1/notes/{id} did not show the matching raw `^block-id` in the Markdown body within the 15 s save poll. This leaves the reminder target without its durable file anchor. I am tracing the editor transaction and source-aware save path before rerunning the screenshots.
Author
Owner

Correction to the previous finding: code inspection showed that the E2E poll used note.body.split("\n\n")[0], which is the YAML frontmatter added by the reminder. It therefore never checked the Markdown body. The reported persistence gap is not confirmed. I am updating the probe to strip frontmatter before checking the raw ^block-id and will rerun it.

Correction to the previous finding: code inspection showed that the E2E poll used `note.body.split("\n\n")[0]`, which is the YAML frontmatter added by the reminder. It therefore never checked the Markdown body. The reported persistence gap is not confirmed. I am updating the probe to strip frontmatter before checking the raw `^block-id` and will rerun it.
Author
Owner

Clarification: the reminder anchor poll searched the full Note body and passed; the raw ^block-id was present. The later collaboration save poll failed because it split note.body at the first blank line and inspected the YAML frontmatter instead of the Markdown body. My earlier persistence-gap report was incorrect. I am fixing that E2E predicate and will rerun the local production flow.

Clarification: the reminder anchor poll searched the full Note body and passed; the raw `^block-id` was present. The later collaboration save poll failed because it split `note.body` at the first blank line and inspected the YAML frontmatter instead of the Markdown body. My earlier persistence-gap report was incorrect. I am fixing that E2E predicate and will rerun the local production flow.
Author
Owner

Adversarial finding (real local server, production browser build): a fresh Note with one body paragraph accepts an HTML clipboard paste containing one paragraph and one image. Typing one short suffix and pressing Ctrl+Z then Ctrl+Shift+Z duplicates the pasted text/image in the live Note. One reproduction changed saved Markdown from one pasted paragraph/image to three pasted paragraphs/images, and duplicated the typed suffix. This also reproduces without the block-anchor repair actions, so it is independent of the #191 anchor fix. I am tracing the undo/update path before changing code.

Adversarial finding (real local server, production browser build): a fresh Note with one body paragraph accepts an HTML clipboard paste containing one paragraph and one image. Typing one short suffix and pressing Ctrl+Z then Ctrl+Shift+Z duplicates the pasted text/image in the live Note. One reproduction changed saved Markdown from one pasted paragraph/image to three pasted paragraphs/images, and duplicated the typed suffix. This also reproduces without the block-anchor repair actions, so it is independent of the #191 anchor fix. I am tracing the undo/update path before changing code.
Author
Owner

Follow-up to the paste-history finding: I corrected the browser reproduction so it enters text mode with the documented block-to-text gesture before placing the caret and pasting. In a fresh Note, a single HTML clipboard paragraph containing one image, followed by one typed suffix and Ctrl+Z / Ctrl+Shift+Z, still reproduces persisted duplication: a paste that starts with one image is saved with three image embeds (the minimal run also showed duplicated pasted text/suffix). The text-only clipboard variant does not reproduce.

The behavior points to the collaboration room’s delete/conflict-shadow merge path in crates/calternal-collab/src/session.rs (update_deletes_block and merge_conflict_shadow); source inspection and the persisted result are consistent with the undo deletion being shadowed, then the redo update being merged into the live room twice. This is a likely root path, not a proven line-level diagnosis. I did not change that crate because fixing it would alter another crate’s behavior beyond this web-editor job. The finding is filed for a collaboration-owner fix and should get a server-level regression case there.

Follow-up to the paste-history finding: I corrected the browser reproduction so it enters text mode with the documented block-to-text gesture before placing the caret and pasting. In a fresh Note, a single HTML clipboard paragraph containing one image, followed by one typed suffix and Ctrl+Z / Ctrl+Shift+Z, still reproduces persisted duplication: a paste that starts with one image is saved with three image embeds (the minimal run also showed duplicated pasted text/suffix). The text-only clipboard variant does not reproduce. The behavior points to the collaboration room’s delete/conflict-shadow merge path in `crates/calternal-collab/src/session.rs` (`update_deletes_block` and `merge_conflict_shadow`); source inspection and the persisted result are consistent with the undo deletion being shadowed, then the redo update being merged into the live room twice. This is a likely root path, not a proven line-level diagnosis. I did not change that crate because fixing it would alter another crate’s behavior beyond this web-editor job. The finding is filed for a collaboration-owner fix and should get a server-level regression case there.
Author
Owner

Adversarial finding (fresh Note on a real local server and production browser build): one HTML text paragraph pasted into a Note, followed by a short typed suffix, survives one Ctrl+Z / Ctrl+Shift+Z pair. With 500 undo keystrokes followed by 500 redo keystrokes, the live editor returns to the original paragraph. After a 2-second flush window, GET /api/v1/notes/{id} also returns only the original body (# 186 isolated editor history / history start); the pasted paragraph and suffix are absent. The fixture contains no image and does not perform block-anchor actions, so this is separate from the image-paste duplication reported above. This is persisted Note data loss under a repeated collaboration-history storm.

The behavior points to collaboration room update processing. I have not proven the exact server line. I am not changing calternal-collab because that would change another crate's behavior beyond this web-editor job; this needs a collaboration-owner fix and a regression test there. I will keep the minimal reproduction available as an opt-in adversarial probe and keep the default browser pass focused on the normal one-pair history round trip.

Adversarial finding (fresh Note on a real local server and production browser build): one HTML text paragraph pasted into a Note, followed by a short typed suffix, survives one Ctrl+Z / Ctrl+Shift+Z pair. With 500 undo keystrokes followed by 500 redo keystrokes, the live editor returns to the original paragraph. After a 2-second flush window, GET /api/v1/notes/{id} also returns only the original body (`# 186 isolated editor history` / `history start`); the pasted paragraph and suffix are absent. The fixture contains no image and does not perform block-anchor actions, so this is separate from the image-paste duplication reported above. This is persisted Note data loss under a repeated collaboration-history storm. The behavior points to collaboration room update processing. I have not proven the exact server line. I am not changing `calternal-collab` because that would change another crate's behavior beyond this web-editor job; this needs a collaboration-owner fix and a regression test there. I will keep the minimal reproduction available as an opt-in adversarial probe and keep the default browser pass focused on the normal one-pair history round trip.
Author
Owner

Post-merge production E2E found that a double-click on a linked heading could enter text mode, then Escape briefly restore the block selection before a late Chromium editor focus cleared it. The event trace showed Goals selected during the Escape handler, followed by focus returning to .cal-prose and removing the selection. I added a one-event focus guard for that exit path and changed the test to double-click .linked-heading-text because the full-width heading locator had been hitting its inline Copy link control. bun run --cwd apps/web check passed (svelte-check found 0 errors and 0 warnings), and the production E2E reached and passed “double click edits and Escape returns to selection”.

Post-merge production E2E found that a double-click on a linked heading could enter text mode, then Escape briefly restore the block selection before a late Chromium editor focus cleared it. The event trace showed `Goals` selected during the Escape handler, followed by focus returning to `.cal-prose` and removing the selection. I added a one-event focus guard for that exit path and changed the test to double-click `.linked-heading-text` because the full-width heading locator had been hitting its inline Copy link control. `bun run --cwd apps/web check` passed (`svelte-check found 0 errors and 0 warnings`), and the production E2E reached and passed “double click edits and Escape returns to selection”.
Author
Owner

The post-merge real-local-server adversarial run found one non-SLOW analytics consistency issue. The probe ran 90 analytics reads and 30 log writes in 32 threads; four reads timed out, then its two post-storm day-metrics reads disagreed (analytics storm consistency: two reads after the storm disagree). The local server stayed alive. The same run had concurrent perf and build jobs on the shared host, and the server was using 50–66% of one core during this section, so load may contribute, but this is not classified as SLOW-only. It touches Analytics plugin behavior outside the block-actions UI work; I filed the evidence without changing that crate.

The post-merge real-local-server adversarial run found one non-SLOW analytics consistency issue. The probe ran 90 analytics reads and 30 log writes in 32 threads; four reads timed out, then its two post-storm day-metrics reads disagreed (`analytics storm consistency: two reads after the storm disagree`). The local server stayed alive. The same run had concurrent perf and build jobs on the shared host, and the server was using 50–66% of one core during this section, so load may contribute, but this is not classified as SLOW-only. It touches Analytics plugin behavior outside the block-actions UI work; I filed the evidence without changing that crate.
Author
Owner

The post-merge real-local-server adversarial run found a public collaboration protocol-rate-limit gap. The probe opened a valid public Edit WebSocket, sent 301 empty binary frames (the documented probe comment says malformed frames share the per-link/IP budget), and observed that the socket did not close within 10 seconds: public collab frame rate limit: the 301st frame did not close the socket. This is a non-SLOW protocol-abuse result. It touches public collaboration behavior outside the block-actions UI work; I filed the evidence without changing that behavior.

The post-merge real-local-server adversarial run found a public collaboration protocol-rate-limit gap. The probe opened a valid public Edit WebSocket, sent 301 empty binary frames (the documented probe comment says malformed frames share the per-link/IP budget), and observed that the socket did not close within 10 seconds: `public collab frame rate limit: the 301st frame did not close the socket`. This is a non-SLOW protocol-abuse result. It touches public collaboration behavior outside the block-actions UI work; I filed the evidence without changing that behavior.
Author
Owner

Continuation #191 on job/block-actions. Starting HEAD: d9b6a1d9ac6d340e15be246723d6b77bbc407902. Current dev: 0558cc4ecaeb7f51af1e06acd31870b5f87b9f00; common base: 57d1752c990e1509dfdb7faed55815158dbb2c57. I have read the repository contract, DESIGN §42, CONTEXT.md, and the issue history. I am merging dev once now, then will run the requested final verification and report any remaining work instead of extending scope.

Continuation #191 on `job/block-actions`. Starting HEAD: `d9b6a1d9ac6d340e15be246723d6b77bbc407902`. Current `dev`: `0558cc4ecaeb7f51af1e06acd31870b5f87b9f00`; common base: `57d1752c990e1509dfdb7faed55815158dbb2c57`. I have read the repository contract, DESIGN §42, CONTEXT.md, and the issue history. I am merging `dev` once now, then will run the requested final verification and report any remaining work instead of extending scope.
Author
Owner

Finding during final production screenshot capture (head 72d03077): bun apps/web/e2e/notes.mjs --screenshots artifacts/block-actions-191-final captured the desktop action menu, reminder picker (typed, quiet-hours, existing), hover tooltip, selection and formatting states in both themes. The run then exited 1 before fresh phone captures: the mobile context timed out after 10 seconds waiting for .cal-prose text Continued from another tab.. Per the finish instruction, I did not tune or modify the E2E probe. I copied the existing production phone captures from the prior reviewed run into the ignored artifact directory for the issue attachment set.

Finding during final production screenshot capture (head `72d03077`): `bun apps/web/e2e/notes.mjs --screenshots artifacts/block-actions-191-final` captured the desktop action menu, reminder picker (typed, quiet-hours, existing), hover tooltip, selection and formatting states in both themes. The run then exited 1 before fresh phone captures: the mobile context timed out after 10 seconds waiting for `.cal-prose` text `Continued from another tab.`. Per the finish instruction, I did not tune or modify the E2E probe. I copied the existing production phone captures from the prior reviewed run into the ignored artifact directory for the issue attachment set.
Author
Owner

One time-boxed adversarial round after merge 72d03077 (ADVERSARIAL_SKIP_WEB_BUILD=1 timeout -k 30s 15m tests/adversarial/run.sh) reached the 15 minute cap and exited 124.

Completed sections: the authorization matrix passed all 237 OpenAPI operations across four identities (948 requests); the editor round passed its Markdown size, browser paste/history, collaboration, concurrent edit and restart cases; the search storm had 0 failures; semantic recall returned Apartment hunting for the transport-nearby housing query.

The API round was still running at the cap. Every finding emitted before termination had the runner's SLOW marker: log write 5.1 s, Files Note rename 7.4 s, Task create 8.3 s, task tick 7.2 s, task attach 5.0 s, DAV Basic app password 9.6 s, and DAV discovery 10.5 s. I treated these as load per the owner rule. The later API sections and the final restart probes did not run; I did not restart the round.

One time-boxed adversarial round after merge `72d03077` (`ADVERSARIAL_SKIP_WEB_BUILD=1 timeout -k 30s 15m tests/adversarial/run.sh`) reached the 15 minute cap and exited 124. Completed sections: the authorization matrix passed all 237 OpenAPI operations across four identities (948 requests); the editor round passed its Markdown size, browser paste/history, collaboration, concurrent edit and restart cases; the search storm had 0 failures; semantic recall returned `Apartment hunting` for the transport-nearby housing query. The API round was still running at the cap. Every finding emitted before termination had the runner's `SLOW` marker: log write 5.1 s, Files Note rename 7.4 s, Task create 8.3 s, task tick 7.2 s, task attach 5.0 s, DAV Basic app password 9.6 s, and DAV discovery 10.5 s. I treated these as load per the owner rule. The later API sections and the final restart probes did not run; I did not restart the round.
Author
Owner

Final report — #191

Built

Implemented the #191 slices already committed on job/block-actions: shared text/action capsules, desktop block controls and menu, L block links, Make a note, phone block mode, reminder picker/API/frontmatter, fire-time audience resolution, per-User Done/Snooze, quiet-hours handling, settings, and OpenAPI/client updates.

Key files: apps/web/src/lib/editor/format/*; apps/web/src/lib/notes/{NoteEditorSurface.svelte,NoteView.svelte,editorHost.ts,api.ts}; editor and Notifications settings; packages/editor/src/{anchor.ts,markdown.ts}; crates/calternal-notes-core/src/reminders.rs; crates/plugins/notes/{src/reminders_api.rs,src/store.rs,migrations/0012_block_reminders.sql}; crates/plugins/notifications/{src/block_reminders.rs,src/routes.rs,src/store.rs,migrations/0003_block_reminders.sql}; crates/calternal-collab/src/markdown.rs; contracts/openapi.json; packages/api-client/src/generated.ts; docs/DESIGN.md; apps/web/e2e/notes.mjs; tests/adversarial/attack.py.

Head and push

Head: 72d03077c7d37f2eb5c177a1bfe621d99daf5b6a (dev merge commit). git push origin job/block-actions output: Everything up-to-date.

Gates

  • cargo fmt --check — exit 0; no output.
  • cargo clippy --all-targets -- -D warnings — exit 0. Verbatim final line: Finished dev profile [unoptimized + debuginfo] target(s) in 8m 45s
  • cargo test — stopped at the 60-minute cutoff while the Files suite was still running. Last observed test output: test tests::slow_upload_keeps_its_slot_while_bytes_arrive ... ok. No final exit status was produced.
  • bun run --cwd apps/web check, bun run --cwd apps/web test, and bash packages/api-client/check-generated.sh were not reached.
  • cargo clean output: Removed 17085 files, 14.8GiB total. Removed apps/web/build and apps/web/.svelte-kit/output.

Adversarial round

One 15-minute run exited 124 at its timebox. It completed the 948-request authorization matrix, editor round, search storm (0 failures), and semantic recall. Every API finding emitted before the cap had the SLOW marker. The later API and restart sections did not run; I did not restart the round.

Screenshots

Uploaded 28 PNG issue assets for desktop and phone states in Paper White and Tokyo Night. They cover hover actions, menus, formatting capsules, reminder typed/quiet-hours/existing states, and phone block/text bars. The fresh production E2E run captured the desktop set, then exited 1 before fresh phone capture: mobile timed out after 10 seconds waiting for .cal-prose text Continued from another tab.. The phone screenshots attached here are from the prior production capture after the reviewed UI fixes. No new PNGs were committed.

Remaining work

Finish cargo test, run the web check and test gates plus generated-client consistency, complete the remaining adversarial sections, and refresh the phone screenshots after fixing or diagnosing the E2E timeout.

Decisions where DESIGN.md was silent

  • Public Edit guests can edit text but do not get Make a note, Remind, Copy link, or other block actions because those adapters use private Note identities.
  • Invalid reminder enum values use HTTP 422 with a plain-text extractor error; semantic validation errors keep the documented 400 response.
  • Reminder records add setter and audience to the existing lossless frontmatter envelope. Quiet-hour settings live in the existing top-level notifications User settings section; a missing zone defaults to UTC.
# Final report — #191 ## Built Implemented the #191 slices already committed on `job/block-actions`: shared text/action capsules, desktop block controls and menu, `L` block links, Make a note, phone block mode, reminder picker/API/frontmatter, fire-time audience resolution, per-User Done/Snooze, quiet-hours handling, settings, and OpenAPI/client updates. Key files: `apps/web/src/lib/editor/format/*`; `apps/web/src/lib/notes/{NoteEditorSurface.svelte,NoteView.svelte,editorHost.ts,api.ts}`; editor and Notifications settings; `packages/editor/src/{anchor.ts,markdown.ts}`; `crates/calternal-notes-core/src/reminders.rs`; `crates/plugins/notes/{src/reminders_api.rs,src/store.rs,migrations/0012_block_reminders.sql}`; `crates/plugins/notifications/{src/block_reminders.rs,src/routes.rs,src/store.rs,migrations/0003_block_reminders.sql}`; `crates/calternal-collab/src/markdown.rs`; `contracts/openapi.json`; `packages/api-client/src/generated.ts`; `docs/DESIGN.md`; `apps/web/e2e/notes.mjs`; `tests/adversarial/attack.py`. ## Head and push Head: `72d03077c7d37f2eb5c177a1bfe621d99daf5b6a` (dev merge commit). `git push origin job/block-actions` output: `Everything up-to-date`. ## Gates - `cargo fmt --check` — exit 0; no output. - `cargo clippy --all-targets -- -D warnings` — exit 0. Verbatim final line: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 8m 45s` - `cargo test` — stopped at the 60-minute cutoff while the Files suite was still running. Last observed test output: `test tests::slow_upload_keeps_its_slot_while_bytes_arrive ... ok`. No final exit status was produced. - `bun run --cwd apps/web check`, `bun run --cwd apps/web test`, and `bash packages/api-client/check-generated.sh` were not reached. - `cargo clean` output: `Removed 17085 files, 14.8GiB total`. Removed `apps/web/build` and `apps/web/.svelte-kit/output`. ## Adversarial round One 15-minute run exited 124 at its timebox. It completed the 948-request authorization matrix, editor round, search storm (0 failures), and semantic recall. Every API finding emitted before the cap had the `SLOW` marker. The later API and restart sections did not run; I did not restart the round. ## Screenshots Uploaded 28 PNG issue assets for desktop and phone states in Paper White and Tokyo Night. They cover hover actions, menus, formatting capsules, reminder typed/quiet-hours/existing states, and phone block/text bars. The fresh production E2E run captured the desktop set, then exited 1 before fresh phone capture: mobile timed out after 10 seconds waiting for `.cal-prose` text `Continued from another tab.`. The phone screenshots attached here are from the prior production capture after the reviewed UI fixes. No new PNGs were committed. ## Remaining work Finish `cargo test`, run the web check and test gates plus generated-client consistency, complete the remaining adversarial sections, and refresh the phone screenshots after fixing or diagnosing the E2E timeout. ## Decisions where DESIGN.md was silent - Public Edit guests can edit text but do not get Make a note, Remind, Copy link, or other block actions because those adapters use private Note identities. - Invalid reminder enum values use HTTP 422 with a plain-text extractor error; semantic validation errors keep the documented 400 response. - Reminder records add `setter` and `audience` to the existing lossless frontmatter envelope. Quiet-hour settings live in the existing top-level `notifications` User settings section; a missing zone defaults to UTC.
Author
Owner

Starting work on branch job/block-actions at 72d03077c7; base dev is 21d985bf7a. I read CLAUDE.md, CONTEXT.md, docs/DESIGN.md §42 and the issue. I will fix selected text styling, reminder picker details, verify the missing-space report, then merge dev once and run the requested gates/screenshots.

Starting work on branch job/block-actions at 72d03077c7d37f2eb5c177a1bfe621d99daf5b6a; base dev is 21d985bf7ac8d4e13f2eb93bdc600198412df038. I read CLAUDE.md, CONTEXT.md, docs/DESIGN.md §42 and the issue. I will fix selected text styling, reminder picker details, verify the missing-space report, then merge dev once and run the requested gates/screenshots.
Author
Owner

Finding: the screenshot's blue underline is the real internal link created by the earlier Make a note step in apps/web/e2e/notes.mjs. The selected-state CSS in NoteEditorSurface.svelte only sets the tint/background; it does not set text color or decoration. I will capture block selection on plain text and assert computed text styles are unchanged before/after selection, while keeping linked-block behavior covered separately.

Finding: the screenshot's blue underline is the real internal link created by the earlier Make a note step in apps/web/e2e/notes.mjs. The selected-state CSS in NoteEditorSurface.svelte only sets the tint/background; it does not set text color or decoration. I will capture block selection on plain text and assert computed text styles are unchanged before/after selection, while keeping linked-block behavior covered separately.
Author
Owner

Progress: the picker now resolves 18:00/20:00 defaults and shows each preset's resolved clock via the shared User time formatter. It reads up to two distinct, per-User times from the existing local reminder history. The input uses a filled surface with a focus ring and no border. E2E assertions now cover preset times, learned history, a viewport-edge flip, selected text style, and where the collaboration edit lands relative to the callout.

Progress: the picker now resolves 18:00/20:00 defaults and shows each preset's resolved clock via the shared User time formatter. It reads up to two distinct, per-User times from the existing local reminder history. The input uses a filled surface with a focus ring and no border. E2E assertions now cover preset times, learned history, a viewport-edge flip, selected text style, and where the collaboration edit lands relative to the callout.
Author
Owner

E2E finding: block selection resolves a list paragraph to its parent list item, so the first style assertion targeted a node that does not receive the selection decoration. I updated the test fixture with a plain top-level paragraph; the assertion and fresh selection captures now target the actual selected paragraph. The first run stopped at this test assertion before reaching the collaboration check.

E2E finding: block selection resolves a list paragraph to its parent list item, so the first style assertion targeted a node that does not receive the selection decoration. I updated the test fixture with a plain top-level paragraph; the assertion and fresh selection captures now target the actual selected paragraph. The first run stopped at this test assertion before reaching the collaboration check.
Author
Owner

Finding: the two-context production-build E2E edit did not reproduce the missing separator. “Update from the field team.” remained in the “Retire the legacy export tool” list item; the callout body retained “Decision” and did not contain the inserted text. The test also checked the saved Markdown after both edits. This points to the reported screenshot being a fixture/capture artifact rather than a block merge or collaboration defect. I am keeping the corrected placement assertions as a regression check.

Finding: the two-context production-build E2E edit did not reproduce the missing separator. “Update from the field team.” remained in the “Retire the legacy export tool” list item; the callout body retained “Decision” and did not contain the inserted text. The test also checked the saved Markdown after both edits. This points to the reported screenshot being a fixture/capture artifact rather than a block merge or collaboration defect. I am keeping the corrected placement assertions as a regression check.
Author
Owner

Fresh screenshots from the production-build Notes E2E on this merged branch. These are issue attachments and are not committed.

View Paper White Tokyo Night
Phone block actions PNG PNG
Phone reminder picker PNG PNG
Desktop selection PNG PNG
Fresh screenshots from the production-build Notes E2E on this merged branch. These are issue attachments and are not committed. | View | Paper White | Tokyo Night | | --- | --- | --- | | Phone block actions | [PNG](https://git.kayg.org/attachments/c8f4c738-1c4f-43e3-9a83-be345ca8b5df) | [PNG](https://git.kayg.org/attachments/8f789862-3130-42c9-b7d2-521461f0eb96) | | Phone reminder picker | [PNG](https://git.kayg.org/attachments/7cc7de50-011e-4da2-bc1c-abeabd758290) | [PNG](https://git.kayg.org/attachments/4341d545-a23a-4db2-9bc0-5371f200fd74) | | Desktop selection | [PNG](https://git.kayg.org/attachments/3fbee31a-3684-40ff-8053-0d97d40b1e2a) | [PNG](https://git.kayg.org/attachments/660445bb-0184-48b4-9dee-160275d69cfd) |
Author
Owner

Gate finding: the single full cargo test pass reported one failure in the server's wire::tests::live_apps_run_in_separate_processes wrapper. Its child wire::tests::full_app_setup_session_config_and_backup timed out waiting for /api/v1/admin/config to observe the instance_name = "Updated" file change within the test's 5-second watcher window (crates/calternal-server/src/wire.rs:2904-2922). The child ran for 14.87 seconds and returned Elapsed(()); preceding Rust tests passed. This is outside the block-actions changes. I am not rerunning the full gate under the one-pass instruction.

Gate finding: the single full `cargo test` pass reported one failure in the server's `wire::tests::live_apps_run_in_separate_processes` wrapper. Its child `wire::tests::full_app_setup_session_config_and_backup` timed out waiting for `/api/v1/admin/config` to observe the `instance_name = "Updated"` file change within the test's 5-second watcher window (`crates/calternal-server/src/wire.rs:2904-2922`). The child ran for 14.87 seconds and returned `Elapsed(())`; preceding Rust tests passed. This is outside the block-actions changes. I am not rerunning the full gate under the one-pass instruction.
Author
Owner

Final report

Head: 04506cb9a12faffe65d52ec0e322f42aea5a9eef (working tree clean). Commits: caccf225, merge 6ed1a463, 40a6725d, 04506cb9.

Built

  • Reminder presets show resolved times in the user’s time format; defaults are 18:00, 20:00, tomorrow 09:00, and next Monday 09:00. Up to two frequently chosen clock times are learned from per-user local reminder history. The input uses the filled field and focus ring, and the popover flips to stay in the viewport.
  • Added a plain-paragraph selection assertion that compares selected and unselected computed text color and decoration. A true Note link keeps its blue underline; selection adds only the tint.
  • Added two-context placement assertions over the rendered editor and saved Markdown. “Update from the field team.” stays in its target list item; the callout retains a separate “Decision” paragraph. The reported concatenation did not reproduce and was a fixture/capture artifact.
  • Merged dev once and kept the shared MENU_ICONS map. Regenerated the OpenAPI client. Fresh phone and desktop screenshots are attached in the preceding issue comment; none are committed.

Issue-specific files: apps/web/src/lib/editor/format/BlockReminderPicker.svelte, apps/web/src/lib/editor/format/reminderTimeHistory.ts, and apps/web/e2e/notes.mjs. The requested dev merge also carries the current contract/client changes.

Gates

cargo fmt --check: passed, exit code 0, no stdout.

cargo clippy --all-targets -- -D warnings: first attempt hit a missing shared sccache temp directory under another worktree. Retried with RUSTC_WRAPPER= and a worktree-local TMPDIR; passed:

Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 30s

cargo test: failed only in the server’s nested live-app test. The child timed out waiting for the config watcher’s 5-second reload window at wire.rs:2904-2922:

test wire::tests::live_apps_run_in_separate_processes ... FAILED
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 45 filtered out; finished in 14.87s
test result: FAILED. 43 passed; 1 failed; 2 ignored; 0 measured; 0 filtered out; finished in 20.65s
error: test failed, to rerun pass -p calternal-server --bin calternal-server

bun run check:

svelte-check found 0 errors and 0 warnings

bun run test:

Test Files 94 passed (94)
Tests 627 passed (627)
Start at 21:40:12
Duration 132.62s (transform 66%, environment 14%, import 10%, tests 7%, setup 3%)

packages/api-client/check-generated.sh passed:

✨ openapi-typescript 7.13.0
🚀 ../../contracts/openapi.json → src/generated.ts [802.9ms]

Production-build Notes E2E: notes e2e: ok.

Known gaps and decisions

The one adversarial round reached its 45-minute cap (exit 124) with 335 reported entries, mostly SLOW latency markers. It also recorded non-SLOW timeouts: 16 of 24 concurrent Journal PATCHes got no response within 30 seconds; the other eight returned 412 after 25.7 seconds. Calendar and DAV requests also timed out, and a recurring-log check found the same Event identity for two occurrences after a timed-out write. Later, the feed proxy returned 502 local adversarial server is unavailable; the harness could not finish isolation/collaboration probes before the cap. These findings are filed for triage in #267. The single round was not repeated.

The design does not define where reminder-use history lives. I kept the two learned times in localStorage scoped by User ID; the choices do not sync between devices. The new plain paragraph is test fixture data only and is placed after “Goals” so it does not change the original range-navigation checks.

Cleanup completed: cargo clean printed Removed 17895 files, 13.4GiB total; apps/web/build/ was deleted. Screenshots remain attached to #191, not in Git.

# Final report Head: `04506cb9a12faffe65d52ec0e322f42aea5a9eef` (working tree clean). Commits: `caccf225`, merge `6ed1a463`, `40a6725d`, `04506cb9`. ## Built - Reminder presets show resolved times in the user’s time format; defaults are 18:00, 20:00, tomorrow 09:00, and next Monday 09:00. Up to two frequently chosen clock times are learned from per-user local reminder history. The input uses the filled field and focus ring, and the popover flips to stay in the viewport. - Added a plain-paragraph selection assertion that compares selected and unselected computed text color and decoration. A true Note link keeps its blue underline; selection adds only the tint. - Added two-context placement assertions over the rendered editor and saved Markdown. “Update from the field team.” stays in its target list item; the callout retains a separate “Decision” paragraph. The reported concatenation did not reproduce and was a fixture/capture artifact. - Merged `dev` once and kept the shared `MENU_ICONS` map. Regenerated the OpenAPI client. Fresh phone and desktop screenshots are attached in the preceding issue comment; none are committed. Issue-specific files: `apps/web/src/lib/editor/format/BlockReminderPicker.svelte`, `apps/web/src/lib/editor/format/reminderTimeHistory.ts`, and `apps/web/e2e/notes.mjs`. The requested `dev` merge also carries the current contract/client changes. ## Gates `cargo fmt --check`: passed, exit code 0, no stdout. `cargo clippy --all-targets -- -D warnings`: first attempt hit a missing shared sccache temp directory under another worktree. Retried with `RUSTC_WRAPPER=` and a worktree-local `TMPDIR`; passed: > Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 30s `cargo test`: failed only in the server’s nested live-app test. The child timed out waiting for the config watcher’s 5-second reload window at `wire.rs:2904-2922`: > test wire::tests::live_apps_run_in_separate_processes ... FAILED > test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 45 filtered out; finished in 14.87s > test result: FAILED. 43 passed; 1 failed; 2 ignored; 0 measured; 0 filtered out; finished in 20.65s > error: test failed, to rerun pass `-p calternal-server --bin calternal-server` `bun run check`: > svelte-check found 0 errors and 0 warnings `bun run test`: > Test Files 94 passed (94) > Tests 627 passed (627) > Start at 21:40:12 > Duration 132.62s (transform 66%, environment 14%, import 10%, tests 7%, setup 3%) `packages/api-client/check-generated.sh` passed: > ✨ openapi-typescript 7.13.0 > 🚀 ../../contracts/openapi.json → src/generated.ts [802.9ms] Production-build Notes E2E: `notes e2e: ok`. ## Known gaps and decisions The one adversarial round reached its 45-minute cap (exit 124) with 335 reported entries, mostly SLOW latency markers. It also recorded non-SLOW timeouts: 16 of 24 concurrent Journal PATCHes got no response within 30 seconds; the other eight returned 412 after 25.7 seconds. Calendar and DAV requests also timed out, and a recurring-log check found the same Event identity for two occurrences after a timed-out write. Later, the feed proxy returned `502 local adversarial server is unavailable`; the harness could not finish isolation/collaboration probes before the cap. These findings are filed for triage in #267. The single round was not repeated. The design does not define where reminder-use history lives. I kept the two learned times in localStorage scoped by User ID; the choices do not sync between devices. The new plain paragraph is test fixture data only and is placed after “Goals” so it does not change the original range-navigation checks. Cleanup completed: `cargo clean` printed `Removed 17895 files, 13.4GiB total`; `apps/web/build/` was deleted. Screenshots remain attached to #191, not in Git.
Author
Owner

Starting final fixes on job/block-actions, based on dev merge-base 7b169436e5. Review head: 04506cb9a1. I will fix reminder preset chronology, desktop action-pill placement, merge dev once, and rerun the isolated watcher test as requested.

Starting final fixes on job/block-actions, based on dev merge-base 7b169436e57ddda0e6a361a53b69aa90405470f5. Review head: 04506cb9a12faffe65d52ec0e322f42aea5a9eef. I will fix reminder preset chronology, desktop action-pill placement, merge dev once, and rerun the isolated watcher test as requested.
Author
Owner

Finding: BlockReminderPicker.svelte resolved fixed preset times on open but kept the static label 'This evening' and static row order. At a 20:45 local clock, the 20:00 row resolves to tomorrow, while tomorrow morning sorts before it; near 23:30, the later-today fallback can move into the next day. I added a fixed-clock regression test for 08:00, 18:30, 20:45 and 23:30; the test failed on the missing resolver before implementation and now passes.

Finding: BlockReminderPicker.svelte resolved fixed preset times on open but kept the static label 'This evening' and static row order. At a 20:45 local clock, the 20:00 row resolves to tomorrow, while tomorrow morning sorts before it; near 23:30, the later-today fallback can move into the next day. I added a fixed-clock regression test for 08:00, 18:30, 20:45 and 23:30; the test failed on the missing resolver before implementation and now passes.
Author
Owner

Finding: BlockHoverActions.svelte clamps against the viewport only (). When the block's right-side pill does not fit, the left-side fallback can start around x=188 and cross the sidebar boundary, matching the reported x≈200 placement. The toolbar is portalled to body, so its placement needs the content bounds explicitly.

Finding: BlockHoverActions.svelte clamps against the viewport only (). When the block's right-side pill does not fit, the left-side fallback can start around x=188 and cross the sidebar boundary, matching the reported x≈200 placement. The toolbar is portalled to body, so its placement needs the content bounds explicitly.
Author
Owner

Finding: BlockHoverActions.svelte clamps against the viewport only (minX = 8). When the block right-side pill does not fit, the left-side fallback can start around x=188 and cross the sidebar boundary, matching the reported x≈200 placement. The toolbar is portalled to body, so its placement needs the .editor-surface content bounds explicitly.

Finding: BlockHoverActions.svelte clamps against the viewport only (minX = 8). When the block right-side pill does not fit, the left-side fallback can start around x=188 and cross the sidebar boundary, matching the reported x≈200 placement. The toolbar is portalled to body, so its placement needs the .editor-surface content bounds explicitly.
Author
Owner

The ignored config-reload watcher test passed alone 3 times with its existing 5-second timeout: run 1 passed in 6.47 s, run 2 in 3.95 s, run 3 in 13.11 s (test-process elapsed values). No timeout occurred, so I left the deadline unchanged.

The ignored config-reload watcher test passed alone 3 times with its existing 5-second timeout: run 1 passed in 6.47 s, run 2 in 3.95 s, run 3 in 13.11 s (test-process elapsed values). No timeout occurred, so I left the deadline unchanged.
Author
Owner

Finding: the production notes E2E measured the 850 px case with a full-width block at x=302.8–816 and its action pill at x=714–808, so the pill covered the block when neither horizontal side fit inside the editor column. The fallback now aligns the pill with the block right edge and moves it above or below; the focused component test passes 3/3.

Finding: the production notes E2E measured the 850 px case with a full-width block at x=302.8–816 and its action pill at x=714–808, so the pill covered the block when neither horizontal side fit inside the editor column. The fallback now aligns the pill with the block right edge and moves it above or below; the focused component test passes 3/3.
Author
Owner

The production notes E2E had two time-dependent assumptions that no longer hold after hiding past/duplicate presets: it required 4 rows and clicked Later today. At Sunday 23:17 local, only Tomorrow morning and Tomorrow evening are distinct future standard presets; Later today is past and Next week duplicates tomorrow morning. The E2E now accepts 2–4 unique rows and clicks the first available standard preset.

The production notes E2E had two time-dependent assumptions that no longer hold after hiding past/duplicate presets: it required 4 rows and clicked Later today. At Sunday 23:17 local, only Tomorrow morning and Tomorrow evening are distinct future standard presets; Later today is past and Next week duplicates tomorrow morning. The E2E now accepts 2–4 unique rows and clicks the first available standard preset.
Author
Owner

E2E finding: the collaboration probe's generic dblclick() targeted the center of a full-width paragraph. For short list text, that point is whitespace; Chromium selected the paragraph break into the next block, and End sometimes placed the caret in that next block. The probe now double-clicks actual text and asserts that the caret stays in the intended block before typing. A production notes E2E run completed successfully; the corrected gesture and layout assertions are being rerun before final gates.

E2E finding: the collaboration probe's generic `dblclick()` targeted the center of a full-width paragraph. For short list text, that point is whitespace; Chromium selected the paragraph break into the next block, and `End` sometimes placed the caret in that next block. The probe now double-clicks actual text and asserts that the caret stays in the intended block before typing. A production notes E2E run completed successfully; the corrected gesture and layout assertions are being rerun before final gates.
Author
Owner

E2E finding: the second collaborative edit target was below the 900 px viewport (y=974). Coordinate-based Playwright clicks do not scroll, so the interaction hit the page and left focus on the live-region status node. The E2E now scrolls each target into view before deriving pointer coordinates. This is a probe issue, not a production editing failure.

E2E finding: the second collaborative edit target was below the 900 px viewport (`y=974`). Coordinate-based Playwright clicks do not scroll, so the interaction hit the page and left focus on the live-region status node. The E2E now scrolls each target into view before deriving pointer coordinates. This is a probe issue, not a production editing failure.
Author
Owner

Verification finding: bun e2e/layout-sweep.mjs --only note checked 28 screens. The new block-action bounds assertion passed for desktop with the sidebar open and collapsed. The existing note route still reports CLS 0.054 at 1440/open (limit 0.05), and the mobile sweep reports the Linked mentions label/icon vertical alignment at about 1.0 px on the 390 px note and menu-note screens. These are outside the block-action files. One later notes E2E also received a 200 reminder-preview response containing only the owner after sharing the quiet-hours recipient; a prior full notes E2E run passed that recipient flow. I am reporting both as verification gaps and leaving notification behavior outside this issue.

Verification finding: `bun e2e/layout-sweep.mjs --only note` checked 28 screens. The new block-action bounds assertion passed for desktop with the sidebar open and collapsed. The existing note route still reports CLS 0.054 at 1440/open (limit 0.05), and the mobile sweep reports the Linked mentions label/icon vertical alignment at about 1.0 px on the 390 px note and menu-note screens. These are outside the block-action files. One later notes E2E also received a 200 reminder-preview response containing only the owner after sharing the quiet-hours recipient; a prior full notes E2E run passed that recipient flow. I am reporting both as verification gaps and leaving notification behavior outside this issue.
Author
Owner

#191 finished on job/block-actions.

Head SHA: 821bac40518ca92f43ae6e8a96d05290d380e497. dev was merged once before the final gates. Conflict resolution kept both sides in apps/web/src/routes/+layout.svelte and tests/adversarial/attack2.py.

Built

  • Reminder suggestions resolve to future local instants, sort by time, and do not offer elapsed times. Fixed-clock cases cover 08:00, 18:30, 20:45, and 23:30.
  • The desktop block action pill stays within the Note column and viewport, including full-width blocks and either sidebar state. The layout sweep checks this geometry.
  • Removed 61 tracked review PNGs from the branch tree. Fresh evidence images are attached below.

Issue-specific source and test files: apps/web/src/lib/editor/format/reminderDateTime.ts, BlockReminderPicker.svelte, reminderDateTime.test.ts, BlockHoverActions.svelte, BlockHoverActions.svelte.test.ts, apps/web/e2e/layout-sweep.mjs, and apps/web/e2e/notes.mjs. The dev merge also resolved apps/web/src/routes/+layout.svelte and tests/adversarial/attack2.py.

Final gates

cargo fmt --check — exit 0; output was empty.

cargo clippy --all-targets -- -D warnings — exit 0:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 55s

cargo test — exit 0. The following per-suite summary lines are verbatim from its output (72 suites, 1,318 passed, 0 failed, 12 ignored):

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 30.35s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.43s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.83s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 59.68s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.77s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.55s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.65s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.65s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.72s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.46s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.62s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.58s
test result: ok. 9 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.33s
test result: ok. 17 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.12s
test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.85s
test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.75s
test result: ok. 486 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.21s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.53s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.40s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.50s
test result: ok. 46 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.93s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s
test result: ok. 110 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 34.54s
test result: ok. 98 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 56.91s
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.25s
test result: ok. 42 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 8.23s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
test result: ok. 25 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.88s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.05s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.08s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 6.35s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 51 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 14.11s
test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.61s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.54s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.60s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

bun run check — first attempt found the readonly-property delete typing error in the new test cleanup. Replaced it with Reflect.deleteProperty; the final rerun exited 0:

$ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/block-actions/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test — exit 0:

 Test Files  98 passed (98)
      Tests  643 passed (643)
   Start at  01:01:26
   Duration  180.04s (transform 65%, environment 15%, import 10%, tests 6%, setup 3%)

  Transform  |component| transforming modules took 427.79s · 58% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

bash packages/api-client/check-generated.sh — exit 0:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 45.17s
     Running `target/debug/calternal-server openapi`
$ bunx --package openapi-typescript@7.13.0 openapi-typescript ../../contracts/openapi.json -o src/generated.ts
Resolving dependencies
Resolved, downloaded and extracted [23]
Saved lockfile
✨ openapi-typescript 7.13.0
🚀 ../../contracts/openapi.json → src/generated.ts [1.2s]

Adversarial and visual checks

The one time-boxed broad round found one authz response-order mismatch (PUT /api/v1/admin/config for a standard user returned 422, expected 403), recorded on #268. Calendar Event from Log timed out at 30 seconds while multiple jobs were loading the shared host; the server stayed alive and later Calendar/Journal calls completed, recorded on #250. The media probe did not see a thumbnail within its 10-second wait under the same load; that observation is on #264. These runs do not establish quiet-host failures. Most other broad-round reports were SLOW load markers; hostile-byte and restart probes reported zero findings.

The broad runner used the prebuilt server override, so target/debug/calternal was absent when the CLI subsection started. I built calternal-cli and ran the missing CLI subsection against a real local server:

---------- cli ----------
cli ls names: 2
server alive at end: True

==== ROUND 2 FINDINGS 0

==== ROUND 2 SLOW 0

The notes E2E completed successfully once. Later full reruns were unstable in recipient preview and editor focus setup. The note-only layout sweep checked 28 screens and passed the new block-action bounds assertion; it also flagged existing CLS 0.054 and about 1 px Linked mentions icon/label alignment at 390 px. These are not block-action placement failures.

Decisions

  • Later today uses 18:00 if it is still ahead; after that, it uses the next whole hour if that remains the same local day, and is hidden if rounding crosses midnight.
  • This evening uses 20:00 today when future, otherwise it becomes Tomorrow evening. All visible presets are future, chronological, and duplicate instants are collapsed.
  • If a full-width block leaves no horizontal slot, the pill aligns to that block's right edge and moves above or below it while staying inside the Note column.

Screenshots:

#191 finished on `job/block-actions`. Head SHA: `821bac40518ca92f43ae6e8a96d05290d380e497`. `dev` was merged once before the final gates. Conflict resolution kept both sides in `apps/web/src/routes/+layout.svelte` and `tests/adversarial/attack2.py`. ## Built - Reminder suggestions resolve to future local instants, sort by time, and do not offer elapsed times. Fixed-clock cases cover 08:00, 18:30, 20:45, and 23:30. - The desktop block action pill stays within the Note column and viewport, including full-width blocks and either sidebar state. The layout sweep checks this geometry. - Removed 61 tracked review PNGs from the branch tree. Fresh evidence images are attached below. Issue-specific source and test files: `apps/web/src/lib/editor/format/reminderDateTime.ts`, `BlockReminderPicker.svelte`, `reminderDateTime.test.ts`, `BlockHoverActions.svelte`, `BlockHoverActions.svelte.test.ts`, `apps/web/e2e/layout-sweep.mjs`, and `apps/web/e2e/notes.mjs`. The dev merge also resolved `apps/web/src/routes/+layout.svelte` and `tests/adversarial/attack2.py`. ## Final gates `cargo fmt --check` — exit 0; output was empty. `cargo clippy --all-targets -- -D warnings` — exit 0: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 55s ``` `cargo test` — exit 0. The following per-suite summary lines are verbatim from its output (72 suites, 1,318 passed, 0 failed, 12 ignored): ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 50 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 30.35s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.43s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.83s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 59.68s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 9.77s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.55s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.65s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.65s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.72s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.46s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.16s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.62s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.58s test result: ok. 9 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.33s test result: ok. 17 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.12s test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.85s test result: ok. 40 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.75s test result: ok. 486 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.19s test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.21s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.53s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.40s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.33s test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.50s test result: ok. 46 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.93s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.15s test result: ok. 110 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 34.54s test result: ok. 98 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 56.91s test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.25s test result: ok. 42 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 8.23s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s test result: ok. 25 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.88s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.05s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.08s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 6.35s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 51 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 14.11s test result: ok. 51 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.61s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.54s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.60s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `bun run check` — first attempt found the readonly-property `delete` typing error in the new test cleanup. Replaced it with `Reflect.deleteProperty`; the final rerun exited 0: ```text $ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/block-actions/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` — exit 0: ```text Test Files 98 passed (98) Tests 643 passed (643) Start at 01:01:26 Duration 180.04s (transform 65%, environment 15%, import 10%, tests 6%, setup 3%) Transform |component| transforming modules took 427.79s · 58% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` `bash packages/api-client/check-generated.sh` — exit 0: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 45.17s Running `target/debug/calternal-server openapi` $ bunx --package openapi-typescript@7.13.0 openapi-typescript ../../contracts/openapi.json -o src/generated.ts Resolving dependencies Resolved, downloaded and extracted [23] Saved lockfile ✨ openapi-typescript 7.13.0 🚀 ../../contracts/openapi.json → src/generated.ts [1.2s] ``` ## Adversarial and visual checks The one time-boxed broad round found one authz response-order mismatch (`PUT /api/v1/admin/config` for a standard user returned 422, expected 403), recorded on #268. `Calendar Event from Log` timed out at 30 seconds while multiple jobs were loading the shared host; the server stayed alive and later Calendar/Journal calls completed, recorded on #250. The media probe did not see a thumbnail within its 10-second wait under the same load; that observation is on #264. These runs do not establish quiet-host failures. Most other broad-round reports were SLOW load markers; hostile-byte and restart probes reported zero findings. The broad runner used the prebuilt server override, so `target/debug/calternal` was absent when the CLI subsection started. I built `calternal-cli` and ran the missing CLI subsection against a real local server: ```text ---------- cli ---------- cli ls names: 2 server alive at end: True ==== ROUND 2 FINDINGS 0 ==== ROUND 2 SLOW 0 ``` The notes E2E completed successfully once. Later full reruns were unstable in recipient preview and editor focus setup. The note-only layout sweep checked 28 screens and passed the new block-action bounds assertion; it also flagged existing CLS 0.054 and about 1 px Linked mentions icon/label alignment at 390 px. These are not block-action placement failures. ## Decisions - `Later today` uses 18:00 if it is still ahead; after that, it uses the next whole hour if that remains the same local day, and is hidden if rounding crosses midnight. - `This evening` uses 20:00 today when future, otherwise it becomes `Tomorrow evening`. All visible presets are future, chronological, and duplicate instants are collapsed. - If a full-width block leaves no horizontal slot, the pill aligns to that block's right edge and moves above or below it while staying inside the Note column. Screenshots: - [Block actions at the right edge, 850 px](https://git.kayg.org/attachments/d469c6dd-df63-436c-97b8-637fde1f8233) - [Typed reminder preview, Tokyo Night](https://git.kayg.org/attachments/e72d4650-3462-4215-9980-8c06f4bf3ad1)
Author
Owner

Continuing the accepted #191 work on job/block-actions. Starting branch HEAD: 821bac40518ca92f43ae6e8a96d05290d380e497; local origin/dev base at start: 976b804f99188feb300e391c1942836599e2d429. I am fetching and integrating origin/dev once, then will run the requested gates, push this branch, and report the exact results.

Continuing the accepted #191 work on `job/block-actions`. Starting branch HEAD: `821bac40518ca92f43ae6e8a96d05290d380e497`; local `origin/dev` base at start: `976b804f99188feb300e391c1942836599e2d429`. I am fetching and integrating `origin/dev` once, then will run the requested gates, push this branch, and report the exact results.
Author
Owner

Gate finding after integrating origin/dev: bun run --cwd apps/web test reported 1 failed, 101 passed; the failed assertion expected formatLocalClock('09:30', 'de-DE') to return 9:30, while merged UI commit 60de3ef7 now intentionally pads system 24-hour labels and returns 09:30. I kept the formatter behavior and aligned that stale expectation with the merged behavior. Focused verification passed: Test Files 1 passed (1) and Tests 17 passed (17). I did not rerun the full web suite because this job requires one full gate run.

Gate finding after integrating `origin/dev`: `bun run --cwd apps/web test` reported 1 failed, 101 passed; the failed assertion expected `formatLocalClock('09:30', 'de-DE')` to return `9:30`, while merged UI commit `60de3ef7` now intentionally pads system 24-hour labels and returns `09:30`. I kept the formatter behavior and aligned that stale expectation with the merged behavior. Focused verification passed: `Test Files 1 passed (1)` and `Tests 17 passed (17)`. I did not rerun the full web suite because this job requires one full gate run.
Author
Owner

#191 continuation complete

Branch: job/block-actions
Merged origin/dev once at 976b804f99188feb300e391c1942836599e2d429. Final HEAD: 16f5364f4da19be7b8cf505066dc6176cff70a5a. The branch was pushed; origin already had this exact HEAD.

Integration

Resolved the Notes E2E, bottom chrome, and adversarial probe conflicts while preserving both sides: reminder and block-action checks remain; CSP checks and mode preloading remain; the phone block bar still suppresses the mode tray; direct adversarial probes reach the Rust server and keep the public Origin. Regenerated OpenAPI and the typed client with packages/api-client/check-generated.sh after building the web SPA; the script exited 0.

The full web test gate found the existing de-DE clock expectation stale against merged upstream commit 60de3ef7 (system 24-hour clocks are now padded). I kept the formatter behavior and changed the expectation to 09:30. The focused file passed: Test Files 1 passed (1) and Tests 17 passed (17).

Gates

  • cargo fmt --check: exit 0, no output.
  • cargo clippy --all-targets -- -D warnings: exit 0. Final output: Finished dev profile [unoptimized + debuginfo] target(s) in 8m 30s.
  • cargo test: exit 0; 72 suite summaries, 1,339 passed, 0 failed, 12 ignored. Final output excerpt: test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s.
  • bun run --cwd apps/web check: exit 0. svelte-check found 0 errors and 0 warnings.
  • Full bun run --cwd apps/web test gate output: Test Files 1 failed | 101 passed (102); Tests 1 failed | 667 passed (668). The one failure was the stale clock expectation above. The focused time.test.ts run passed after the update; the full suite was not rerun because this job calls for one full gate run.

Adversarial round

Ran the real-server probe with ADVERSARIAL_ATTACK2_ONLY=1 ADVERSARIAL_SKIP_WEB_BUILD=1. The round-one reminder, auth, and hostile-input probes completed; the authorization matrix covered 244 OpenAPI operations across four identities. HOSTILE BYTES FINDINGS 0. Its only finding was appearance concurrent PUT latency :: SLOW p95=0.810s exceeds the 0.500s target. Round two reached journal-race probes and was stopped after about 20 minutes because another adversarial suite was running on the shared host and the job has a ~60-minute limit. All findings observed before the stop were SLOW; no non-SLOW finding was observed. This adversarial round is incomplete.

Decisions and gaps

  • Kept the upstream system-locale padding behavior and aligned the stale de-DE assertion with it.
  • Kept the incoming mode-preload handlers on the bottom TabBar while retaining the block-actions-bar visibility condition.
  • The full web test suite was not rerun after the one-line assertion update; the focused time suite passed. The adversarial round-two pass was time-boxed and incomplete.
  • Cleanup completed: cargo clean reported Removed 18318 files, 14.7GiB total; apps/web/build and apps/web/.svelte-kit were removed. Worktree is clean.
#191 continuation complete Branch: `job/block-actions` Merged `origin/dev` once at `976b804f99188feb300e391c1942836599e2d429`. Final HEAD: `16f5364f4da19be7b8cf505066dc6176cff70a5a`. The branch was pushed; origin already had this exact HEAD. ## Integration Resolved the Notes E2E, bottom chrome, and adversarial probe conflicts while preserving both sides: reminder and block-action checks remain; CSP checks and mode preloading remain; the phone block bar still suppresses the mode tray; direct adversarial probes reach the Rust server and keep the public Origin. Regenerated OpenAPI and the typed client with `packages/api-client/check-generated.sh` after building the web SPA; the script exited 0. The full web test gate found the existing `de-DE` clock expectation stale against merged upstream commit `60de3ef7` (system 24-hour clocks are now padded). I kept the formatter behavior and changed the expectation to `09:30`. The focused file passed: `Test Files 1 passed (1)` and `Tests 17 passed (17)`. ## Gates - `cargo fmt --check`: exit 0, no output. - `cargo clippy --all-targets -- -D warnings`: exit 0. Final output: `Finished `dev` profile [unoptimized + debuginfo] target(s) in 8m 30s`. - `cargo test`: exit 0; 72 suite summaries, 1,339 passed, 0 failed, 12 ignored. Final output excerpt: `test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s`. - `bun run --cwd apps/web check`: exit 0. `svelte-check found 0 errors and 0 warnings`. - Full `bun run --cwd apps/web test` gate output: `Test Files 1 failed | 101 passed (102)`; `Tests 1 failed | 667 passed (668)`. The one failure was the stale clock expectation above. The focused `time.test.ts` run passed after the update; the full suite was not rerun because this job calls for one full gate run. ## Adversarial round Ran the real-server probe with `ADVERSARIAL_ATTACK2_ONLY=1 ADVERSARIAL_SKIP_WEB_BUILD=1`. The round-one reminder, auth, and hostile-input probes completed; the authorization matrix covered 244 OpenAPI operations across four identities. `HOSTILE BYTES FINDINGS 0`. Its only finding was `appearance concurrent PUT latency :: SLOW p95=0.810s exceeds the 0.500s target`. Round two reached journal-race probes and was stopped after about 20 minutes because another adversarial suite was running on the shared host and the job has a ~60-minute limit. All findings observed before the stop were `SLOW`; no non-`SLOW` finding was observed. This adversarial round is incomplete. ## Decisions and gaps - Kept the upstream system-locale padding behavior and aligned the stale `de-DE` assertion with it. - Kept the incoming mode-preload handlers on the bottom `TabBar` while retaining the block-actions-bar visibility condition. - The full web test suite was not rerun after the one-line assertion update; the focused time suite passed. The adversarial round-two pass was time-boxed and incomplete. - Cleanup completed: `cargo clean` reported `Removed 18318 files, 14.7GiB total`; `apps/web/build` and `apps/web/.svelte-kit` were removed. Worktree is clean.
kayg referenced this issue from a commit 2026-09-28 01:36:53 +00:00
Author
Owner

Merged in bdcecc42: block actions (→ ⏰ ⋯ inside the note column), reminder picker with future-only resolved presets + learned times, quiet hours, icon-only phone capsule, inline ^id anchors (Obsidian style; fixes the raw ^id leak), tint selection. Orchestrator verified: web 670/670, editor 397/397, clippy clean, notes/notifications/server tests 177/177, generated client OK.

Merged in bdcecc42: block actions (→ ⏰ ⋯ inside the note column), reminder picker with future-only resolved presets + learned times, quiet hours, icon-only phone capsule, inline ^id anchors (Obsidian style; fixes the raw ^id leak), tint selection. Orchestrator verified: web 670/670, editor 397/397, clippy clean, notes/notifications/server tests 177/177, generated client OK.
kayg closed this issue 2026-09-28 01:43:39 +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#191
No description provided.