Tasks UI slice 5: swipes, drag and the Board (includes #875) #893

Closed
kayg wants to merge 24 commits from port/tasks-3-s5b into port/tasks-3
Owner

Stacked on #889 (slice 4) → #888 (slice 3) → #832. Slice 5 of the Tasks UI port plan: swipes, drag, and the Board.

Read first: this branch contains #875 (port/rows, the shared press component and the new context menu). The drag and hold engine (apps/web/src/press/*, ui/lib/lift.ts, the new ContextMenu) exists only in the open stack #875 → #877 → #878, not on dev, and I will not copy it. The branch merges origin/port/rows (3a7e1629, a clean merge); until #875 lands the diff shows its ~2,500 lines. The Tasks work is only apps/web/src/tasks/**, apps/web/e2e/tasks-{swipe,board}.spec.ts and Middle.svelte's scroller. Merge #875 first, then this PR's diff is only the Tasks files.

What it does

  • Swipes (the shared row registry, tasks/rows.ts): left → right Done (a long swipe runs it) and Date (opens the task with its When picker out); right → left Someday. All real: tasks.set-status.
  • Drag (the press component): a row picked up (a finger holds, a mouse takes it after 5 px) and dropped on a section sets what the section stands for: a day or a month of Upcoming sets When (a month takes its first usable day); a tag's four sections set When / clear it / Someday; on a desktop a row dropped on a list in the sidebar does the same (Today, Upcoming = tomorrow, Anytime = no date, Someday = parked). tasks.update and tasks.set-status, one after the other (never together: the second needs the note's new version). The dropped row stays in this list for the visit.
  • Board: To do / Doing / Done as columns; a card dropped on a column is tasks.set-status (Won't do sits in Done, Someday in To do). The whole column takes the drop. Layout List/Board is remembered per device (cal.tasks.layout) and ?layout=board wins. The switcher is the shared selector capsule.
  • The list's scroller wears swipeRows and pressRows, like the Notes list: a handful of listeners for 1,000 rows.

Not ported / missing server actions (listed, not faked)

  • Reorder inside a section: needs tasks.reorder (and tasks.move), not on dev (#799 is WIP). A drop on the section it came from does nothing.
  • Delete (the right-to-left swipe's second button and the menu's Delete): needs tasks.delete. Duplicate: tasks.duplicate. Neither is on dev.
  • Matrix: waits for its redesign grill (kayg 10-19/10-21), so the switcher has two choices.
  • The context menu is the registry's default menu (Done, Date, Someday), not TickTick's When/Priority icon rows; those are slice 6's When field plus the pickers' own rows.
  • Phone layout switcher is the same capsule in the header, not the mockup's single-icon pill menu.

Gates: first comment.

🤖 Generated with Claude Code

Stacked on #889 (slice 4) → #888 (slice 3) → #832. Slice 5 of the Tasks UI port plan: swipes, drag, and the Board. **Read first: this branch contains #875 (port/rows, the shared press component and the new context menu).** The drag and hold engine (`apps/web/src/press/*`, `ui/lib/lift.ts`, the new `ContextMenu`) exists only in the open stack #875 → #877 → #878, not on dev, and I will not copy it. The branch merges `origin/port/rows` (3a7e1629, a clean merge); until #875 lands the diff shows its ~2,500 lines. The Tasks work is only `apps/web/src/tasks/**`, `apps/web/e2e/tasks-{swipe,board}.spec.ts` and `Middle.svelte`'s scroller. Merge #875 first, then this PR's diff is only the Tasks files. **What it does** - Swipes (the shared row registry, `tasks/rows.ts`): left → right Done (a long swipe runs it) and Date (opens the task with its When picker out); right → left Someday. All real: `tasks.set-status`. - Drag (the press component): a row picked up (a finger holds, a mouse takes it after 5 px) and dropped on a section sets what the section stands for: a day or a month of Upcoming sets When (a month takes its first usable day); a tag's four sections set When / clear it / Someday; on a desktop a row dropped on a list in the sidebar does the same (Today, Upcoming = tomorrow, Anytime = no date, Someday = parked). `tasks.update` and `tasks.set-status`, one after the other (never together: the second needs the note's new version). The dropped row stays in this list for the visit. - Board: To do / Doing / Done as columns; a card dropped on a column is `tasks.set-status` (Won't do sits in Done, Someday in To do). The whole column takes the drop. Layout List/Board is remembered per device (`cal.tasks.layout`) and `?layout=board` wins. The switcher is the shared selector capsule. - The list's scroller wears `swipeRows` and `pressRows`, like the Notes list: a handful of listeners for 1,000 rows. **Not ported / missing server actions (listed, not faked)** - **Reorder inside a section**: needs `tasks.reorder` (and `tasks.move`), not on dev (#799 is WIP). A drop on the section it came from does nothing. - **Delete** (the right-to-left swipe's second button and the menu's Delete): needs `tasks.delete`. **Duplicate**: `tasks.duplicate`. Neither is on dev. - Matrix: waits for its redesign grill (kayg 10-19/10-21), so the switcher has two choices. - The context menu is the registry's default menu (Done, Date, Someday), not TickTick's When/Priority icon rows; those are slice 6's When field plus the pickers' own rows. - Phone layout switcher is the same capsule in the header, not the mockup's single-icon pill menu. Gates: first comment. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kayg added 24 commits 2026-11-03 09:50:15 +00:00
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
port(rows): the menu size tokens the shared menu surface reads
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
d295257ab3
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merge branch 'port/tasks-3' into port/tasks-3-s5b
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
b0bf61ce1d
Author
Owner

Gates for slice 5 (head 851a1826, includes #889 at 080b8a7e and #875's 3a7e1629)

dev-02 (aarch64), CI=1 --retries=0 --workers=1, through ~/bin/slot. Specs: all of slices 3 and 4 plus tasks-swipe and tasks-board.

Project Result
chromium-1440 35 passed
chromium-390 28 passed (the swipe and mouse-drag specs run at 1440 only)
webkit-1440 35 passed
webkit-390 28 passed
tasks-swipe ×5 on webkit-1440 15 passed
tasks-contrast on 275b7deb (one commit earlier, no styling change since) passed
vitest (web) 99 passed
svelte-check 0 errors in src/tasks and src/press (same 2 pre-existing e2e errors)

Flakes seen once each during the work and what I did: a WebKit wheel swipe that did not open the row (tasks-swipe now swipes until the row is open; 15/15 afterwards); a keyboard tick and an Undo that took longer than 5 s on a loaded machine (the server waits in tasks-tick are now 20 s; both passed 8/8 on repeat).

tasks-board: the Board switcher is remembered across a reload; a card dragged to Doing then Done is set-status doing then done, and stays in Done; a row dragged to Upcoming in the sidebar takes tomorrow, and to Someday is parked. tasks-swipe: right swipe shows Done and Date and Done writes done; left swipe shows Someday which parks it; Date opens the card with the When picker out.

Real bugs found by the specs: the list scroller needs use:swipeRows and use:pressRows (without them nothing swipes and nothing lifts); a drop's two writes (date, then status) must be sequential or the second is refused with the old version.

Bundle (brotli, KB)

head first-paint JS all JS Tasks chunk JS / CSS
#889 233.84 326.75 10.23 / 3.24
this PR 234.91 337.44 12.03 / 3.76

First paint 234.91 ≤ 240 (the +1.07 comes with #875's press and menu code merged into this branch). all JS 337.44 is under web.bundle.total_js_brotli 404.2. Chunks for budgets.json: TasksScreen 12.03 JS + 3.76 CSS (existing cap 20). The press engine loads when idle after a list shows (#875), not in first paint.

Screenshots

~/Developer/Worktrees/shots/port-tasks-3/: s5-board-* (product) and m-s5-board-* (mockup). Attached. The mockup also has the Matrix button, which waits for its redesign.

Not measured

Mac VM frame numbers for drag (lift, move, drop), swipe, and the 1,000-row scroll: not done. No virtualised list yet.

s5-board-1440x900.png

s5-board-1440x900.png

m-s5-board-1440x900.png

m-s5-board-1440x900.png

s5-board-390x844.png

s5-board-390x844.png

m-s5-board-390x844.png

m-s5-board-390x844.png

## Gates for slice 5 (head `851a1826`, includes #889 at `080b8a7e` and #875's `3a7e1629`) dev-02 (aarch64), `CI=1 --retries=0 --workers=1`, through `~/bin/slot`. Specs: all of slices 3 and 4 plus `tasks-swipe` and `tasks-board`. | Project | Result | |---|---| | chromium-1440 | 35 passed | | chromium-390 | 28 passed (the swipe and mouse-drag specs run at 1440 only) | | webkit-1440 | 35 passed | | webkit-390 | 28 passed | | `tasks-swipe` ×5 on webkit-1440 | 15 passed | | `tasks-contrast` on `275b7deb` (one commit earlier, no styling change since) | passed | | vitest (web) | 99 passed | | svelte-check | 0 errors in `src/tasks` and `src/press` (same 2 pre-existing e2e errors) | Flakes seen once each during the work and what I did: a WebKit wheel swipe that did not open the row (`tasks-swipe` now swipes until the row is open; 15/15 afterwards); a keyboard tick and an Undo that took longer than 5 s on a loaded machine (the server waits in `tasks-tick` are now 20 s; both passed 8/8 on repeat). `tasks-board`: the Board switcher is remembered across a reload; a card dragged to Doing then Done is `set-status doing` then `done`, and stays in Done; a row dragged to Upcoming in the sidebar takes tomorrow, and to Someday is parked. `tasks-swipe`: right swipe shows Done and Date and Done writes `done`; left swipe shows Someday which parks it; Date opens the card with the When picker out. Real bugs found by the specs: the list scroller needs `use:swipeRows` and `use:pressRows` (without them nothing swipes and nothing lifts); a drop's two writes (date, then status) must be sequential or the second is refused with the old version. ## Bundle (brotli, KB) | head | first-paint JS | all JS | Tasks chunk JS / CSS | |---|---|---|---| | #889 | 233.84 | 326.75 | 10.23 / 3.24 | | this PR | 234.91 | 337.44 | 12.03 / 3.76 | First paint 234.91 ≤ 240 (the +1.07 comes with #875's press and menu code merged into this branch). `all JS` 337.44 is under `web.bundle.total_js_brotli` 404.2. Chunks for budgets.json: `TasksScreen` 12.03 JS + 3.76 CSS (existing cap 20). The press engine loads when idle after a list shows (#875), not in first paint. ## Screenshots `~/Developer/Worktrees/shots/port-tasks-3/`: `s5-board-*` (product) and `m-s5-board-*` (mockup). Attached. The mockup also has the Matrix button, which waits for its redesign. ## Not measured Mac VM frame numbers for drag (lift, move, drop), swipe, and the 1,000-row scroll: not done. No virtualised list yet. **s5-board-1440x900.png** ![s5-board-1440x900.png](https://git.kayg.org/attachments/4a972ae2-b6e1-4495-8131-73d51c8fd196) **m-s5-board-1440x900.png** ![m-s5-board-1440x900.png](https://git.kayg.org/attachments/37f072a2-506f-445c-896a-609f8c5492fb) **s5-board-390x844.png** ![s5-board-390x844.png](https://git.kayg.org/attachments/23313501-6708-4bd8-83e5-6bd82de9ff64) **m-s5-board-390x844.png** ![m-s5-board-390x844.png](https://git.kayg.org/attachments/3d4560eb-afe8-4013-9441-bd04b34da5a9)
kayg changed title from WIP: Tasks UI slice 5: swipes, drag and the Board to Tasks UI slice 5: swipes, drag and the Board (includes #875) 2026-11-03 10:54:20 +00:00
Author
Owner

Round 2 (head d7df9bbb)

  1. Drag grip: hidden at rest on a desktop, shown on hover only, as the mockup. The earlier shot caught the row under the mouse after a click. Spec: opacity 0 with the mouse away, 1 on hover.
  2. Header icons: the List/Board switcher's icons and the + use --glyph-bar (20 px, 1.9 line on a desktop; 22 px, 2.1 on a phone), in the shared segmented selector capsule. Spec: all header icons are 20 px.
    Plus everything from #888 / #889 above (this PR includes both).

Gates, dev-02, CI=1 --retries=0 --workers=1, all Tasks specs: chromium-1440 40 passed, chromium-390 31, webkit-1440 40, webkit-390 31. tasks-contrast passed. vitest 99 passed; svelte-check 0 errors in src/tasks and src/press. Bundle (brotli KB): first-paint JS 234.98, all JS 337.53, TasksScreen 12.0 JS + 3.72 CSS.
Shots: ~/Developer/Worktrees/shots/port-tasks-3/s5-board-* and m-s5-board-* (Doing holds "Check the perf VM storage", Done the ticked grill), attached.

s5-board-1440x900.png

s5-board-1440x900.png

m-s5-board-1440x900.png

m-s5-board-1440x900.png

s5-board-390x844.png

s5-board-390x844.png

m-s5-board-390x844.png

m-s5-board-390x844.png

## Round 2 (head `d7df9bbb`) 6. **Drag grip**: hidden at rest on a desktop, shown on hover only, as the mockup. The earlier shot caught the row under the mouse after a click. Spec: opacity 0 with the mouse away, 1 on hover. 7. **Header icons**: the List/Board switcher's icons and the + use `--glyph-bar` (20 px, 1.9 line on a desktop; 22 px, 2.1 on a phone), in the shared segmented selector capsule. Spec: all header icons are 20 px. Plus everything from #888 / #889 above (this PR includes both). Gates, dev-02, `CI=1 --retries=0 --workers=1`, all Tasks specs: chromium-1440 40 passed, chromium-390 31, webkit-1440 40, webkit-390 31. `tasks-contrast` passed. vitest 99 passed; svelte-check 0 errors in `src/tasks` and `src/press`. Bundle (brotli KB): first-paint JS 234.98, all JS 337.53, `TasksScreen` 12.0 JS + 3.72 CSS. Shots: `~/Developer/Worktrees/shots/port-tasks-3/s5-board-*` and `m-s5-board-*` (Doing holds "Check the perf VM storage", Done the ticked grill), attached. **s5-board-1440x900.png** ![s5-board-1440x900.png](https://git.kayg.org/attachments/379939eb-5ef5-4531-ba4e-322c3b25ebbf) **m-s5-board-1440x900.png** ![m-s5-board-1440x900.png](https://git.kayg.org/attachments/1c9199f6-3684-41b6-9d10-ae102defd5db) **s5-board-390x844.png** ![s5-board-390x844.png](https://git.kayg.org/attachments/70a20ddb-1358-4798-945a-3f95b76a4078) **m-s5-board-390x844.png** ![m-s5-board-390x844.png](https://git.kayg.org/attachments/cd68d02a-4525-4574-8a8f-ed8c7b803c72)
Author
Owner

housekeeping #1009: rebased+failing, left open for review.

restacked on #889. kept current choose(when) API, swipe query restoration, busy/selection guards, and newer rim/glass CSS. inherits current close Pill and single swipe/When route effect.

head: c90ac82da3ee10fdef6e855cd732d529ad3f96cd; based on dev cd7f84432febfc63b788fbb9b9f9950452982d74, pushed with the original exact force-with-lease.

frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 232757 B (+1980 B), CSS 18561 B (+175 B).

targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun.

all executed gates used the VM Nix environment, shared Mac lock and slot. exact commands, source heads, logs and dev baseline. frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head.

original-head cherry/patch inventory, pr-893.md; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.

final queue, cleanup and source-verification evidence.

housekeeping #1009: rebased+failing, left open for review. restacked on #889. kept current choose(when) API, swipe query restoration, busy/selection guards, and newer rim/glass CSS. inherits current close Pill and single swipe/When route effect. head: `c90ac82da3ee10fdef6e855cd732d529ad3f96cd`; based on dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, pushed with the original exact force-with-lease. frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 232757 B (+1980 B), CSS 18561 B (+175 B). targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun. all executed gates used the VM Nix environment, shared Mac lock and slot. [exact commands, source heads, logs and dev baseline](https://git.kayg.org/attachments/870a0fef-2e31-425c-aea6-eb87a66d365a). frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head. [original-head cherry/patch inventory](https://git.kayg.org/attachments/e05a38dc-c028-4d77-8655-efa9d5c8ea50), `pr-893.md`; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above. [final queue, cleanup and source-verification evidence](https://git.kayg.org/attachments/7ab315a5-4b6c-4af1-971b-cdae2637adae).
Author
Owner

Final-port test pass (issue #1009), READY: no: specs pass; bundle gate fails; first paint is bigger than dev.

head c90ac82da3ee10fdef6e855cd732d529ad3f96cd

Targeted specs, CI=1 --retries=0 --workers=1, dev-01 Chromium and dev-02 WebKit (through ~/bin/slot), against a dev-head backend (cd7f84432).

spec Chromium 1440 Chromium 390 WebKit 1440 WebKit 390
press, tasks-board, tasks-swipe 13/13 5/5 13/13 5/5

Bundle gate: perf/web.sh --bundle-only --gate (the 17 failures from the housekeeping audit). Per the gate's own rule (total_js_brotli budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing.

  • Over: TasksScreen 12.07 vs 5.1, index 227.30 vs 226.25, idle-screens 55.75 vs 51.8, press (3.45 KB, no budget), recognise (no budget), total_js_brotli 490.16 vs 488.3.
  • First paint is BIGGER than dev: JS +1980 B, CSS +175 B. That breaks the 'first paint <= dev' rule. It needs real cuts or lazy-loading the swipe code.

Not done: Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment).

Not marked READY FOR MERGE.

**Final-port test pass (issue #1009), READY: no: specs pass; bundle gate fails; first paint is bigger than dev.** head `c90ac82da3ee10fdef6e855cd732d529ad3f96cd` Targeted specs, `CI=1 --retries=0 --workers=1`, dev-01 Chromium and dev-02 WebKit (through `~/bin/slot`), against a dev-head backend (`cd7f84432`). | spec | Chromium 1440 | Chromium 390 | WebKit 1440 | WebKit 390 | |---|---|---|---|---| | press, tasks-board, tasks-swipe | 13/13 | 5/5 | 13/13 | 5/5 | **Bundle gate:** `perf/web.sh --bundle-only --gate` (the 17 failures from the housekeeping audit). Per the gate's own rule (`total_js_brotli` budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing. - Over: `TasksScreen` 12.07 vs 5.1, `index` 227.30 vs 226.25, `idle-screens` 55.75 vs 51.8, `press` (3.45 KB, no budget), `recognise` (no budget), `total_js_brotli` 490.16 vs 488.3. - First paint is BIGGER than dev: JS +1980 B, CSS +175 B. That breaks the 'first paint <= dev' rule. It needs real cuts or lazy-loading the swipe code. **Not done:** Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment). Not marked READY FOR MERGE.
Author
Owner

Final gates (issue #1009), 69f2c751d0

head 69f2c751d043cc2edfc5e71516d45f8030be75dd, on dev 2fa9e18fb (#888 merged).

READY FOR MERGE (after #889).

gate result
first paint JS, plain build, median of 3 builds (the three are byte-identical) 228352 B vs dev 231155 B: -2803 B
first paint JS, hash-normalised 228234 B vs 230622 B: -2388 B
first paint CSS 18126 B vs 18371 B: -245 B
specs, Chromium 1440 + 390 (dev-01) press, tasks-board, tasks-swipe: 18 passed (16 skipped by design)
specs, WebKit 1440 + 390 (dev-02) 18 passed (16 skipped)
perf/web.sh --bundle-only --gate passes
perf ABAB x5 + control below (taken on the previous dev; this PR's hot paths are unchanged since)
screenshots, 2x light and dark, 1440 and 390; real Safari on #957 below

First paint, two ways. Plain: the build as shipped, brotli of the first-screen files (dev 231155 B JS, 18371 B CSS). Hash-normalised: the same files with the random chunk hashes replaced by a constant, which removes the 100 to 250 B that any change adds through the hashes (dev 230622 B). The press engine and the rows' swipe are out of the first screen.

Typing and open-note, perf-02, Chromium with 4x CPU slowdown, 10k Home, release server (dev head, 20eab8d3a) for every arm, rounds rotated, 5 rounds. devA and devB are the same dev build (the control). Median of the 5 per-run medians [min-max], ms:

metric devA devB (control) #957 (stack tip)
typing, long note p50 22.3 [22.1-22.6] 22.4 [21.8-23.4] 22.5 [21.8-26.2]
typing, long note CPU/key 26.9 [26.5-27.3] 27.4 [26.7-27.7] 27.2 [26.7-27.9]
typing, nested list p50 42.2 [42.1-42.9] 42.4 [42.1-43.1] 42.5 [41.9-43.5]
typing, nested list CPU/key 49.1 [48.8-50.9] 49.8 [49.0-50.4] 50.2 [49.1-51.0]
open note (blaze), shown p50 35.0 [34.3-35.8] 35.3 [34.0-36.4] 35.2 [33.5-35.3]
open note, back p50 30.9 [30.1-31.5] 31.2 [30.8-31.8] 31.7 [31.1-32.0]
flick through 50 notes, CPU 70.8 [70.5-77.1] 75.4 [72.2-82.7] 73.1 [69.4-77.3]
longest task in the flick 143 [133-151] 151 [150-184] 142 [129-173]

Every metric of the tip sits inside the dev-vs-dev control (the control itself differs by 4.6 ms in the flick's CPU). Two variants of the press registration (one effect per row; the press engine asked for on pointer intent) were built and measured in the same session (v1 76.4, v2 74.8 in the flick's CPU): neither beats the control, so neither is in the PRs. The earlier session (which showed +1.4 to +3.3 % on the tip) was one that the control spread covers as well.

Screenshots (2x; light and dark; 1440x900 and 390x844). Left: the approved mockup (review/calendar-2). Right: this PR.

list

mockup this PR
1440 light
1440 dark
390 light
390 dark

list-ticked

mockup this PR
1440 light a tick is interactive; not in the static mockup
1440 dark a tick is interactive; not in the static mockup
390 light a tick is interactive; not in the static mockup
390 dark a tick is interactive; not in the static mockup

card

mockup this PR
1440 light
1440 dark
390 light
390 dark

board

mockup this PR
1440 light
1440 dark
390 light
390 dark

Differences from the mockup that are later slices or not ported: the view switcher beyond List and Board, the Views section, the big title, the share and more pills on the card, the mic in the When field; the product also shows its own sky backdrop and the seeded tasks above.

**Final gates (issue #1009), 69f2c751d043cc2edfc5e71516d45f8030be75dd** head `69f2c751d043cc2edfc5e71516d45f8030be75dd`, on dev `2fa9e18fb` (#888 merged). **READY FOR MERGE** (after #889). | gate | result | |---|---| | first paint JS, plain build, median of 3 builds (the three are byte-identical) | **228352 B vs dev 231155 B: -2803 B** | | first paint JS, hash-normalised | 228234 B vs 230622 B: **-2388 B** | | first paint CSS | 18126 B vs 18371 B: -245 B | | specs, Chromium 1440 + 390 (dev-01) | press, tasks-board, tasks-swipe: 18 passed (16 skipped by design) | | specs, WebKit 1440 + 390 (dev-02) | 18 passed (16 skipped) | | `perf/web.sh --bundle-only --gate` | passes | | perf ABAB x5 + control | below (taken on the previous dev; this PR's hot paths are unchanged since) | | screenshots, 2x light and dark, 1440 and 390; real Safari on #957 | below | **First paint, two ways.** Plain: the build as shipped, brotli of the first-screen files (dev 231155 B JS, 18371 B CSS). Hash-normalised: the same files with the random chunk hashes replaced by a constant, which removes the 100 to 250 B that any change adds through the hashes (dev 230622 B). The press engine and the rows' swipe are out of the first screen. Typing and open-note, perf-02, Chromium with 4x CPU slowdown, 10k Home, release server (dev head, 20eab8d3a) for every arm, rounds rotated, 5 rounds. `devA` and `devB` are the same dev build (the control). Median of the 5 per-run medians [min-max], ms: | metric | devA | devB (control) | #957 (stack tip) | |---|---|---|---| | typing, long note p50 | 22.3 [22.1-22.6] | 22.4 [21.8-23.4] | 22.5 [21.8-26.2] | | typing, long note CPU/key | 26.9 [26.5-27.3] | 27.4 [26.7-27.7] | 27.2 [26.7-27.9] | | typing, nested list p50 | 42.2 [42.1-42.9] | 42.4 [42.1-43.1] | 42.5 [41.9-43.5] | | typing, nested list CPU/key | 49.1 [48.8-50.9] | 49.8 [49.0-50.4] | 50.2 [49.1-51.0] | | open note (blaze), shown p50 | 35.0 [34.3-35.8] | 35.3 [34.0-36.4] | 35.2 [33.5-35.3] | | open note, back p50 | 30.9 [30.1-31.5] | 31.2 [30.8-31.8] | 31.7 [31.1-32.0] | | flick through 50 notes, CPU | 70.8 [70.5-77.1] | 75.4 [72.2-82.7] | 73.1 [69.4-77.3] | | longest task in the flick | 143 [133-151] | 151 [150-184] | 142 [129-173] | Every metric of the tip sits inside the dev-vs-dev control (the control itself differs by 4.6 ms in the flick's CPU). Two variants of the press registration (one effect per row; the press engine asked for on pointer intent) were built and measured in the same session (v1 76.4, v2 74.8 in the flick's CPU): neither beats the control, so neither is in the PRs. The earlier session (which showed +1.4 to +3.3 % on the tip) was one that the control spread covers as well. ### Screenshots (2x; light and dark; 1440x900 and 390x844). Left: the approved mockup (review/calendar-2). Right: this PR. **list** | | mockup | this PR | |---|---|---| | 1440 light | ![](https://git.kayg.org/attachments/4cdd7802-b2ec-4c0e-8a0e-783a77ea8e0c) | ![](https://git.kayg.org/attachments/becad76b-401b-4370-857e-b146867549fc) | | 1440 dark | ![](https://git.kayg.org/attachments/43024294-8257-4989-a8ea-94aa5e45b9ff) | ![](https://git.kayg.org/attachments/84d31071-14e7-47a8-bb23-e1b663a97514) | | 390 light | ![](https://git.kayg.org/attachments/73a0efe9-10ca-44d7-a75c-50279862fe64) | ![](https://git.kayg.org/attachments/6398de99-12cc-4c98-b824-1e3ba49eb4f8) | | 390 dark | ![](https://git.kayg.org/attachments/8b9ee0b6-9991-4247-bd51-b98da1f85836) | ![](https://git.kayg.org/attachments/e2d4a519-d8e7-414c-8743-25e76fd8744d) | **list-ticked** | | mockup | this PR | |---|---|---| | 1440 light | a tick is interactive; not in the static mockup | ![](https://git.kayg.org/attachments/2bb38263-4ee9-4f9b-ad51-3016d29ecca1) | | 1440 dark | a tick is interactive; not in the static mockup | ![](https://git.kayg.org/attachments/e018cd08-96b0-4452-8611-8fede39db81a) | | 390 light | a tick is interactive; not in the static mockup | ![](https://git.kayg.org/attachments/ed8cf5d5-e9c0-4845-b49c-490078aaf468) | | 390 dark | a tick is interactive; not in the static mockup | ![](https://git.kayg.org/attachments/1ff68796-34d6-49ba-b9e5-02c3b52ae110) | **card** | | mockup | this PR | |---|---|---| | 1440 light | ![](https://git.kayg.org/attachments/b727302c-94e4-4941-a044-6555070f606d) | ![](https://git.kayg.org/attachments/a5190438-efb7-4af1-960b-4975c2587059) | | 1440 dark | ![](https://git.kayg.org/attachments/bb9c7578-cd06-4ae8-b8f1-4023e0f2162e) | ![](https://git.kayg.org/attachments/22e4d632-8757-40da-919d-32683cb8c3f7) | | 390 light | ![](https://git.kayg.org/attachments/422045a9-e4f2-43ef-8cc4-b6ff913ef503) | ![](https://git.kayg.org/attachments/19f45704-a613-4140-8e26-fe419c62d498) | | 390 dark | ![](https://git.kayg.org/attachments/230aa9a8-ef0f-4c42-ac6c-02ea30e48310) | ![](https://git.kayg.org/attachments/02c0e949-a1b0-49e8-b41f-d4d9ead84395) | **board** | | mockup | this PR | |---|---|---| | 1440 light | ![](https://git.kayg.org/attachments/55bb4c79-9f0e-4956-843c-c91353fdb613) | ![](https://git.kayg.org/attachments/83cb9099-f26c-4389-a5a1-b14144b37dda) | | 1440 dark | ![](https://git.kayg.org/attachments/5e338ec2-d088-4c16-938a-14ebc6eca5d9) | ![](https://git.kayg.org/attachments/b89e9d6c-ed42-40bc-a1d5-56869a5075ce) | | 390 light | ![](https://git.kayg.org/attachments/bbc63a05-657e-4f7e-b68d-b755bf9ca676) | ![](https://git.kayg.org/attachments/80b3c10e-2a8b-4c50-8293-617ac72bb4f6) | | 390 dark | ![](https://git.kayg.org/attachments/3ad04996-370b-42be-8eb4-2b7ecf20bbb8) | ![](https://git.kayg.org/attachments/c9b3fcf5-0a93-40c0-8d33-2312ccc61603) | Differences from the mockup that are later slices or not ported: the view switcher beyond List and Board, the Views section, the big title, the share and more pills on the card, the mic in the When field; the product also shows its own sky backdrop and the seeded tasks above.
Author
Owner

Board and swipe crops (issue #1009), 69f2c751d0

Product (Chromium, 2x, dev's Today list with five seeded tasks) next to the approved mockup (review/calendar-2, 2x). The layout switcher icons in these shots are the fixed ones (List and Board; see the comment on #957 for the bug and the Matrix note).

Board

mockup this PR
1440 light
1440 dark
390 light
390 dark

Swipe (a trackpad two-finger sideways scroll on a row opens its Done / Date tray; 1440 only: the phone finger is the same registry)

mockup this PR
1440 light the static mockup has no swipe state
1440 dark the static mockup has no swipe state
**Board and swipe crops (issue #1009), 69f2c751d043cc2edfc5e71516d45f8030be75dd** Product (Chromium, 2x, dev's Today list with five seeded tasks) next to the approved mockup (review/calendar-2, 2x). The layout switcher icons in these shots are the fixed ones (List and Board; see the comment on #957 for the bug and the Matrix note). **Board** | | mockup | this PR | |---|---|---| | 1440 light | ![](https://git.kayg.org/attachments/d5269ab7-f05d-4620-a038-df8a20a60319) | ![](https://git.kayg.org/attachments/e7efc33e-04e9-4ad8-aa63-6e4ccaf27ae2) | | 1440 dark | ![](https://git.kayg.org/attachments/79e7710a-2ac2-47c4-aad2-4d1bf6c588ff) | ![](https://git.kayg.org/attachments/f0062992-9316-482f-84bd-1cd1acd52497) | | 390 light | ![](https://git.kayg.org/attachments/1d1e6d2d-60fb-4320-8ae0-e607d1bc28e3) | ![](https://git.kayg.org/attachments/36756465-b9d0-4538-8962-2d658cdb09e6) | | 390 dark | ![](https://git.kayg.org/attachments/c8ad94e9-476a-4ac9-8405-88c7559a50ce) | ![](https://git.kayg.org/attachments/a2bda865-c941-43a6-a102-a346981def11) | **Swipe (a trackpad two-finger sideways scroll on a row opens its Done / Date tray; 1440 only: the phone finger is the same registry)** | | mockup | this PR | |---|---|---| | 1440 light | the static mockup has no swipe state | ![](https://git.kayg.org/attachments/818133df-27b5-4058-8e28-e22897372500) | | 1440 dark | the static mockup has no swipe state | ![](https://git.kayg.org/attachments/705e6400-3473-4a3e-bee7-2b49155ff148) |
Author
Owner

Merged to dev (head 69f2c751d; merge chain 24b3d75db → 0635d3de7 → d4b32af94). Checked by the orchestrator: first paint gate, specs in Chromium and WebKit at 1440/390, typing/open-note ABAB inside the control, 2× crops vs mockup (switcher icons fixed). Matrix view not ported yet (separate follow-up). Ships with the next daily train.

Merged to dev (head 69f2c751d; merge chain 24b3d75db → 0635d3de7 → d4b32af94). Checked by the orchestrator: first paint gate, specs in Chromium and WebKit at 1440/390, typing/open-note ABAB inside the control, 2× crops vs mockup (switcher icons fixed). Matrix view not ported yet (separate follow-up). Ships with the next daily train.
kayg closed this pull request 2026-11-09 04:54:48 +00:00
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
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!893
No description provided.