port: rows, shared press component (approved mockup) 1/3 #875

Open
kayg wants to merge 11 commits from port/rows into dev
Owner

Slice 1 of 3 (rows + the shared press component). Stacked: slice 2 (row and bulk actions) and slice 3 (drop on tags, place move/hide, Empty Trash) follow on this branch.

What ships

  • apps/web/src/press/* (from the mockup): the one press engine. Hold 0.25 s lifts (draggable lists only), 0.5 s opens the preview card + menu; moving before 0.25 s scrolls; right-click / Shift+F10 / menu key open the same menu with no preview or blur. Previews are a registry (registerPreview); the notes provider shows the title and the snippet the list already holds (nothing is fetched).
  • packages/ui menu surface with preview support (ContextMenu.svelte, menu.ts, lift.ts, Float, TapCatcher, focus-keeping portal, keyChips, dimPage, menu size tokens), 3-way merged from review/calendar-2 onto dev.
  • Notes list rows use it (RowActions gets menu/open/drag; swipe gets busy so a hold is never also a swipe).
  • Fix over the mockup: a press on the preview card closed the menu before its tap could open the item.

Perf (no extra work on scroll)

  • Delegated like swipe: 4 listeners on the scroller (use:pressRows); a row gets its real press (7 listeners) only on its first touch/right-click/key. Test: a fresh list has 0 [data-pressable] rows.
  • The engine is loaded on idle, not in the first paint. First-paint gz 313,543 → 314,891 B (+1.3 KB); total JS gz 420,373 → 430,754 (+10.4 KB), CSS gz 41,035 → 41,981.
  • The mockup's per-row document touchstart listener now exists only during a hold.

Not in this slice / decisions

  • Task, contact and event preview providers are not ported: the product has no such lists yet; they register with registerPreview when those lists land.
  • Notes preview shows the list snippet, not the first six rendered blocks (the product list holds no blocks; fetching on hold would add a request). Open for kayg if the full text matters.
  • Notes rows are menu-only here; dragging notes onto tags is slice 3.
  • A touch within ~1 s of the first paint (before the idle load) is not a hold; a right-click then is replayed, not lost.

Gates (head d295257a): svelte-check 0 errors; web vitest 81 + press registry tests, editor vitest passed; Playwright press.spec.ts (new: hold opens card+menu, tap card opens note, scroll/short hold open nothing, right-click, menu key, lazy arming) + swipe/select/touch-select/menu/list-chrome/glass-roots/phone-targets: chromium 390+1440 146 passed / 58 skipped / 0 failed; WebKit 390+1440 (press, swipe, select, touch-select, menu-first-press) 54 passed / 12 skipped / 0 failed (at fbd05227; only token CSS changed since).
Shots: product vs mockup pairs at 1440x900 and 390x844 in the comment below.

Budget (kayg approved, 2026-11-08 08:20 IST): kayg approved raising the lazy budget total, up to 543.9 KiB. perf/budgets.json in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.

Slice 1 of 3 (rows + the shared press component). Stacked: slice 2 (row and bulk actions) and slice 3 (drop on tags, place move/hide, Empty Trash) follow on this branch. **What ships** - `apps/web/src/press/*` (from the mockup): the one press engine. Hold 0.25 s lifts (draggable lists only), 0.5 s opens the preview card + menu; moving before 0.25 s scrolls; right-click / Shift+F10 / menu key open the same menu with no preview or blur. Previews are a registry (`registerPreview`); the notes provider shows the title and the snippet the list already holds (nothing is fetched). - `packages/ui` menu surface with preview support (`ContextMenu.svelte`, `menu.ts`, `lift.ts`, `Float`, `TapCatcher`, focus-keeping `portal`, `keyChips`, `dimPage`, menu size tokens), 3-way merged from `review/calendar-2` onto dev. - Notes list rows use it (`RowActions` gets `menu`/`open`/`drag`; swipe gets `busy` so a hold is never also a swipe). - Fix over the mockup: a press on the preview card closed the menu before its tap could open the item. **Perf (no extra work on scroll)** - Delegated like swipe: 4 listeners on the scroller (`use:pressRows`); a row gets its real press (7 listeners) only on its first touch/right-click/key. Test: a fresh list has 0 `[data-pressable]` rows. - The engine is loaded on idle, not in the first paint. First-paint gz 313,543 → 314,891 B (+1.3 KB); total JS gz 420,373 → 430,754 (+10.4 KB), CSS gz 41,035 → 41,981. - The mockup's per-row document `touchstart` listener now exists only during a hold. **Not in this slice / decisions** - Task, contact and event preview providers are not ported: the product has no such lists yet; they register with `registerPreview` when those lists land. - Notes preview shows the list snippet, not the first six rendered blocks (the product list holds no blocks; fetching on hold would add a request). Open for kayg if the full text matters. - Notes rows are menu-only here; dragging notes onto tags is slice 3. - A touch within ~1 s of the first paint (before the idle load) is not a hold; a right-click then is replayed, not lost. **Gates** (head d295257a): svelte-check 0 errors; web vitest 81 + press registry tests, editor vitest passed; Playwright `press.spec.ts` (new: hold opens card+menu, tap card opens note, scroll/short hold open nothing, right-click, menu key, lazy arming) + swipe/select/touch-select/menu/list-chrome/glass-roots/phone-targets: chromium 390+1440 146 passed / 58 skipped / 0 failed; WebKit 390+1440 (press, swipe, select, touch-select, menu-first-press) 54 passed / 12 skipped / 0 failed (at fbd05227; only token CSS changed since). Shots: product vs mockup pairs at 1440x900 and 390x844 in the comment below. **Budget (kayg approved, 2026-11-08 08:20 IST):** kayg approved raising the lazy budget total, up to 543.9 KiB. `perf/budgets.json` in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.
kayg added 11 commits 2026-11-02 17:35:08 +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>
Author
Owner

Screenshots, product (left name) vs mockup, 1440x900 and 390x844: rest, right-click menu (wide), hold = preview + menu (phone). Product menu lacks the icon row / Select / Archive / Hide of the mockup: that is slice 2. Product backdrop is dark, mockup light (theme only).

Screenshots, product (left name) vs mockup, 1440x900 and 390x844: rest, right-click menu (wide), hold = preview + menu (phone). Product menu lacks the icon row / Select / Archive / Hide of the mockup: that is slice 2. Product backdrop is dark, mockup light (theme only).
Author
Owner

Round 2 shots (light theme both sides, 1440x900 and 390x844, product vs mockup): rest, hold = preview + menu, right-click menu. The hold menu now has the icon row (Copy link, Pin, Delete), a divider, then Open, Select, Archive, Hide (Archive/Hide arrive with #877; here the menu shows what the registry holds). Open in focus appears on desktop only, as in the mockup.

Round 2 shots (light theme both sides, 1440x900 and 390x844, product vs mockup): rest, hold = preview + menu, right-click menu. The hold menu now has the icon row (Copy link, Pin, Delete), a divider, then Open, Select, Archive, Hide (Archive/Hide arrive with #877; here the menu shows what the registry holds). Open in focus appears on desktop only, as in the mockup.
Author
Owner

Heads-up from the BM15 first-paint diet lane (#947): I restacked the rows PRs on top of the #887 diet and pushed port/rows (#875), port/rows-actions (#877) and port/rows-drop (#878) with --force-with-lease. Your last commits on #877 (82b257f6, the Move menu through openMenuWith) and #878 (11e8ae1d, b68a61e5, the Undo and live-pick fixes) are kept: I cherry-picked them onto the new stack, so their SHAs changed. Please git fetch and reset your local branches to the pushed heads before pushing again; a plain push from an older checkout would drop the diet. Anything you change in first-paint code (index chunk) should stay behind the lazy chunks: the press engine, swipe engine, menu helpers, Archive/Hide actions and the tooltip engine now load with press/press.ts or on first use. Numbers are in the #947 comment.

Heads-up from the BM15 first-paint diet lane (#947): I restacked the rows PRs on top of the #887 diet and pushed `port/rows` (#875), `port/rows-actions` (#877) and `port/rows-drop` (#878) with `--force-with-lease`. Your last commits on #877 (`82b257f6`, the Move menu through `openMenuWith`) and #878 (`11e8ae1d`, `b68a61e5`, the Undo and live-pick fixes) are kept: I cherry-picked them onto the new stack, so their SHAs changed. **Please `git fetch` and reset your local branches to the pushed heads before pushing again**; a plain push from an older checkout would drop the diet. Anything you change in first-paint code (`index` chunk) should stay behind the lazy chunks: the press engine, swipe engine, menu helpers, Archive/Hide actions and the tooltip engine now load with `press/press.ts` or on first use. Numbers are in the #947 comment.
Author
Owner

First-paint diet (BM15, #947). Head 302d92ac (restacked on the #887 diet; please reset local checkouts before pushing).

First-paint bytes are brotli q11 from apps/web/scripts/bundle-size.mjs (JS of the first-paint closure = the index chunk; CSS of the first stylesheet). Dev baseline 41f2efa3: 238,797 B JS, 19,250 B CSS. A second figure, "hash-normalised", is the same chunk with the 8-character chunk-file hashes replaced by a fixed string: the hashes are random, and they move the real brotli number by ±300 B between two builds of identical code, so a few-hundred-byte claim is only meaningful in the normalised figure (dev: 238,804). Chunk budgets: every new lazy chunk's line is taken from the first-script line or the idle-screens line so the total stays 404.2; perf/web.sh --bundle-only --gate passes on this head.

first JS first CSS lazy JS
dev 41f2efa3 238,797 19,250 122,936
before (as in #947, with #887) 240,787 (+1,990) 19,381 (+131) 130,815
now (#887 + this PR) 236,746 (−2,051; normalised 236,281 = −2,523) 18,979 (−271) 136,870 (+13.9 KB)

Own effect over #887: −2,384 B JS, −271 B CSS. Per change (real brotli, first script):

step (real brotli, first script, measured one after the other) JS CSS
start (the PR as in #947, with #887) +1,990 +131
a row hands over plain data; the menu builder, preview providers and menu-only Open / Open in focus actions load with the press chunk; dimPage, the menu size tokens (CSS) and nextMenu's staged/preview/above options move to files only the lazy chunks import +985 (−1,005) 0 (−131)
press-rows.ts keeps the registry and four listeners (arming and the early right-click replay are in the chunk); no prop-spread in the lazy menu (it had dragged Svelte's spread_props into index) +769 (−216) 0
the row swipe engine (swipe-row, swipe-math, spring-to: 6.6 KB raw) loads with the press chunk; a touch before it is in waits for it and goes on −1,405 (−2,174) 0
tooltips: the engine, the warm-window logic and the tooltip stylesheet load on the first hover or when idle; the first screen keeps the accessible name and one listener −2,251 (−846) −271
final head, after the preview-card fix and budget commits (hash noise ±200) −2,051 −271

Lazy loading never shows an unstyled flash: the press chunk imports press.css, the tooltip engine imports tooltip.css, so each arrives with its code. Warming: the press+swipe chunk starts at once where a finger is expected (coarse pointer) and when idle (2 s cap) elsewhere; the tooltip engine is fetched at idle. A bug I introduced and fixed on the way: mounting the preview card by hand ran after the menu measured it, so tapping the preview did not open the note; the card now renders <content.component item=…/> (provider contract is { component } and the component takes item).

First-use latency (Chromium, localhost, host load ~22; ABAB interleaved dev vs this stack, medians of 8, cold = the first action right after the list shows, before the idle warm; warm = after 3.5 s):

dev this PR
right-click menu, warm 319 ms 434 ms (range 86–700; dev 112–991)
right-click menu, cold 561 ms 805 ms
swipe first transform, warm (390) 26 ms 28 ms
swipe first transform, cold (390) 34 ms 113 ms
tooltip shown (400 ms delay + hover), warm 1,275 ms 1,325 ms
tooltip, cold 1,692 ms 1,579 ms

Warm is equal within noise. Cold costs one chunk fetch (about +80 ms for the first swipe, +240 ms for a right-click made in the first moments after the list appears) on this loaded VM with no service-worker cache; a user touches the list long after the idle warm, and the service worker serves the chunk from its cache.

Specs on this head, chromium 1440/390 + webkit 1440/390, CI=1 --retries=0 --workers=1: press, swipe-rows, menu-first-press, select-rows, touch-select, tooltips: 167 passed, 81 skipped, 0 failed. Lint ok, svelte-check = baseline, web units 55 passed.

Budgets: new lines press 7.0, tooltip-engine 1.5, notes-visibility-apply 0.6, taken from index (240 → 231.5, it measures 231.2) and idle-screens (50.5 → 49.9); total stays 404.2 (the earlier total bumps are reverted).

**First-paint diet (BM15, #947).** Head `302d92ac` (restacked on the #887 diet; **please reset local checkouts before pushing**). First-paint bytes are brotli q11 from `apps/web/scripts/bundle-size.mjs` (JS of the first-paint closure = the `index` chunk; CSS of the first stylesheet). Dev baseline `41f2efa3`: **238,797 B JS, 19,250 B CSS**. A second figure, "hash-normalised", is the same chunk with the 8-character chunk-file hashes replaced by a fixed string: the hashes are random, and they move the real brotli number by ±300 B between two builds of identical code, so a few-hundred-byte claim is only meaningful in the normalised figure (dev: 238,804). Chunk budgets: every new lazy chunk's line is taken from the first-script line or the idle-screens line so the total stays 404.2; `perf/web.sh --bundle-only --gate` passes on this head. | | first JS | first CSS | lazy JS | |---|---|---|---| | dev `41f2efa3` | 238,797 | 19,250 | 122,936 | | before (as in #947, with #887) | 240,787 (**+1,990**) | 19,381 (+131) | 130,815 | | **now (#887 + this PR)** | **236,746 (−2,051**; normalised 236,281 = **−2,523)** | **18,979 (−271)** | 136,870 (+13.9 KB) | Own effect over #887: **−2,384 B JS, −271 B CSS**. Per change (real brotli, first script): | step (real brotli, first script, measured one after the other) | JS | CSS | |---|---|---| | start (the PR as in #947, with #887) | +1,990 | +131 | | a row hands over plain data; the menu builder, preview providers and menu-only Open / Open in focus actions load with the press chunk; `dimPage`, the menu size tokens (CSS) and `nextMenu`'s staged/preview/above options move to files only the lazy chunks import | +985 (−1,005) | 0 (−131) | | `press-rows.ts` keeps the registry and four listeners (arming and the early right-click replay are in the chunk); no prop-spread in the lazy menu (it had dragged Svelte's `spread_props` into `index`) | +769 (−216) | 0 | | the row **swipe engine** (`swipe-row`, `swipe-math`, `spring-to`: 6.6 KB raw) loads with the press chunk; a touch before it is in waits for it and goes on | −1,405 (**−2,174**) | 0 | | **tooltips**: the engine, the warm-window logic and the tooltip stylesheet load on the first hover or when idle; the first screen keeps the accessible name and one listener | −2,251 (**−846**) | −271 | | final head, after the preview-card fix and budget commits (hash noise ±200) | −2,051 | −271 | Lazy loading never shows an unstyled flash: the press chunk imports `press.css`, the tooltip engine imports `tooltip.css`, so each arrives with its code. Warming: the press+swipe chunk starts at once where a finger is expected (coarse pointer) and when idle (2 s cap) elsewhere; the tooltip engine is fetched at idle. A bug I introduced and fixed on the way: mounting the preview card by hand ran after the menu measured it, so tapping the preview did not open the note; the card now renders `<content.component item=…/>` (provider contract is `{ component }` and the component takes `item`). First-use latency (Chromium, localhost, host load ~22; ABAB interleaved dev vs this stack, medians of 8, **cold** = the first action right after the list shows, before the idle warm; warm = after 3.5 s): | | dev | this PR | |---|---|---| | right-click menu, warm | 319 ms | 434 ms (range 86–700; dev 112–991) | | right-click menu, cold | 561 ms | 805 ms | | swipe first transform, warm (390) | 26 ms | 28 ms | | swipe first transform, cold (390) | 34 ms | 113 ms | | tooltip shown (400 ms delay + hover), warm | 1,275 ms | 1,325 ms | | tooltip, cold | 1,692 ms | 1,579 ms | Warm is equal within noise. Cold costs one chunk fetch (about +80 ms for the first swipe, +240 ms for a right-click made in the first moments after the list appears) on this loaded VM with no service-worker cache; a user touches the list long after the idle warm, and the service worker serves the chunk from its cache. Specs on this head, chromium 1440/390 + webkit 1440/390, `CI=1 --retries=0 --workers=1`: `press`, `swipe-rows`, `menu-first-press`, `select-rows`, `touch-select`, `tooltips`: **167 passed, 81 skipped, 0 failed**. Lint ok, svelte-check = baseline, web units 55 passed. Budgets: new lines `press` 7.0, `tooltip-engine` 1.5, `notes-visibility-apply` 0.6, taken from `index` (240 → 231.5, it measures 231.2) and `idle-screens` (50.5 → 49.9); total stays 404.2 (the earlier total bumps are reverted).
Author
Owner

Warm-up commit on this stack (BM15 follow-up, #947): the press+swipe chunk is now fetched right after the list's first paint (at once on touch, first idle moment with a 150 ms cap elsewhere) and by the first pointer over the list; the tooltip engine at idle (200 ms cap) and by the first pointer movement. Heads: #875 259b5ab7, #877 53104962, #878 bef3b4f7 (restacked with --force-with-lease; reset local checkouts before pushing). Cold first-use numbers and the specs for these heads are in #947 (comment).

Warm-up commit on this stack (BM15 follow-up, #947): the press+swipe chunk is now fetched right after the list's first paint (at once on touch, first idle moment with a 150 ms cap elsewhere) and by the first pointer over the list; the tooltip engine at idle (200 ms cap) and by the first pointer movement. Heads: #875 `259b5ab7`, #877 `53104962`, #878 `bef3b4f7` (restacked with `--force-with-lease`; reset local checkouts before pushing). Cold first-use numbers and the specs for these heads are in https://git.kayg.org/kayg/calternal/issues/947#issuecomment-79453.
Author
Owner

Orchestrator: fast-forwarded port/rows to fix/sw-lazy-precache (e2397446). The service worker now precaches only the 8 files index.html points at, and caches the rest after the list arrives.

This is the fix for the WebKit first-row regression, measured on #947 comment 79662: train + fix 1,647 ms vs dev 2,096 ms. First paint is −85 B vs dev. #877 and #878 need restacking onto this head.

Orchestrator: fast-forwarded `port/rows` to `fix/sw-lazy-precache` (`e2397446`). The service worker now precaches only the 8 files `index.html` points at, and caches the rest after the list arrives. This is the fix for the WebKit first-row regression, measured on #947 comment 79662: train + fix 1,647 ms vs dev 2,096 ms. First paint is −85 B vs dev. #877 and #878 need restacking onto this head.
Author
Owner

Flaky lane (U4): heads-up from the orchestrator's instruction. I am about to push one small fix and its regression test onto port/rows, then merge it forward through #877 and #878. I will not touch anything else, and will wait for the per-PR gate lane to finish on the current head before pushing; tell me if that is wrong.

The bug (found as tasks-board.spec.ts:56, 9 of 20 failing on #893): press-rows.ts loads the press engine on idle. A mouse pressed before the engine is in has its pointerdown dropped, and a mouse drag starts after 5 px, not after a hold, so the drag never starts. Every row in every app has this, not only Tasks.

The fix: press() gets a begin(e) handle, and arm() replays a still-held left-button mouse press once the engine has loaded. Two files, 37 lines, plus one regression test (idle never comes in the test, so the press itself loads the engine). Test fails 10 of 10 without the fix and passes 10 of 10 with it (dev-01 chromium). Source commits on #893: f05a95c5 (test), d03df77e (fix).

Flaky lane (U4): heads-up from the orchestrator's instruction. I am about to push one small fix and its regression test onto `port/rows`, then merge it forward through #877 and #878. I will not touch anything else, and will wait for the per-PR gate lane to finish on the current head before pushing; tell me if that is wrong. **The bug (found as `tasks-board.spec.ts:56`, 9 of 20 failing on #893):** `press-rows.ts` loads the press engine on idle. A mouse pressed before the engine is in has its `pointerdown` dropped, and a mouse drag starts after 5 px, not after a hold, so the drag never starts. Every row in every app has this, not only Tasks. **The fix:** `press()` gets a `begin(e)` handle, and `arm()` replays a still-held left-button mouse press once the engine has loaded. Two files, 37 lines, plus one regression test (idle never comes in the test, so the press itself loads the engine). Test fails 10 of 10 without the fix and passes 10 of 10 with it (dev-01 chromium). Source commits on #893: `f05a95c5` (test), `d03df77e` (fix).
Author
Owner

Flaky lane (U4): while verifying the press replay fix I found that port/rows (6853bbfb4), and so #877 and #878 too, do not compile in Rust. crates/notes/src/write_executor/plan.rs has no Requirement::ExpectedOwners variant (dev has it, from #898) but home.rs:436 and plan.rs:153 still use it: error[E0599]: no variant named ExpectedOwners. The branch has ScopedMetadata / ExactUndo instead, so a merge of dev (the notes-archive-hide one, 847e26bef) kept one side of a conflict. Merging current dev into port/rows should restore it; I have not touched it, that is your call.

My press fix is web-only. I verify it with the variant restored locally on the VMs (uncommitted). I am holding the push to port/rows until you say the gate lane is done with the current head; the fix is ready on flaky/rows (7c901c6d2, one commit on 6853bbfb4), with its test on flaky/rows-drop-test (018b36e43).

Flaky lane (U4): while verifying the press replay fix I found that **`port/rows` (`6853bbfb4`), and so #877 and #878 too, do not compile in Rust.** `crates/notes/src/write_executor/plan.rs` has no `Requirement::ExpectedOwners` variant (dev has it, from #898) but `home.rs:436` and `plan.rs:153` still use it: `error[E0599]: no variant named ExpectedOwners`. The branch has `ScopedMetadata` / `ExactUndo` instead, so a merge of dev (the `notes-archive-hide` one, 847e26bef) kept one side of a conflict. Merging current dev into `port/rows` should restore it; I have not touched it, that is your call. My press fix is web-only. I verify it with the variant restored locally on the VMs (uncommitted). I am holding the push to `port/rows` until you say the gate lane is done with the current head; the fix is ready on `flaky/rows` (`7c901c6d2`, one commit on `6853bbfb4`), with its test on `flaky/rows-drop-test` (`018b36e43`).
Author
Owner

Not gated yet: carries #887

This branch contains a merge of #887 (notes-archive-hide: the hide/archive/restore Rust and the contracts/actions.json changes). #887's write-path regression is being fixed by a separate lane, so gating this branch now would measure that regression, and merging it would land #887 through the back door. Once #887 is in dev, I merge dev here and run the full per-PR gates (I already merged origin/dev b5e8dcb2 into #875 and #904; #877 and #878 follow #875 as a stack). Also needs then: backend perf ABAB on the perf VMs (Rust), and for #904 the unchanged phone-bar fix f30e0bb7.

## Not gated yet: carries #887 This branch contains a merge of #887 (`notes-archive-hide`: the hide/archive/restore Rust and the `contracts/actions.json` changes). #887's write-path regression is being fixed by a separate lane, so gating this branch now would measure that regression, and merging it would land #887 through the back door. Once #887 is in dev, I merge dev here and run the full per-PR gates (I already merged origin/dev `b5e8dcb2` into #875 and #904; #877 and #878 follow #875 as a stack). Also needs then: backend perf ABAB on the perf VMs (Rust), and for #904 the unchanged phone-bar fix `f30e0bb7`.
Author
Owner

Flaky lane (U4): pushed. The gate lane's note says #875 is not gated yet, so no gate was running.

PR new head what
#875 7c901c6d2 the press replay fix only (press.ts, press-rows.ts, 35 lines)
#877 adb9f96df merge of port/rows
#878 9405c7bd1 merge of port/rows-actions, plus the regression test and a test-race fix in sidebar-drop.spec.ts

Evidence (sidebar-drop.spec.ts, --repeat-each through ~/bin/slot, Rust built with ExpectedOwners restored locally on the VMs because the stack does not compile, see above):

chromium-1440 (dev-01) webkit-1440 (dev-02)
original "note dragged onto a tag", 40 repeats, no fix 8 failed / 32 passed 7 failed / 33 passed
new "pressed before the engine" test, 10 repeats, test head without the fix 10 failed 10 failed
whole file, 40 repeats, fixed head 160 / 160 passed 160 / 160 passed

Two causes, both fixed: (1) the product bug in the press commit (a mouse pressed before the engine arrived lost its drag); (2) a test race: each run adds a tag to the one sidebar, so the target row sits below the fold (y=1063 in a 900 px window) and boundingBox() does not scroll. The tests now scroll it into view and wait for the row to be armed.

Still open for the orchestrator: the stack does not compile in Rust (previous comment). First-paint bytes: I did not run bundle-size.mjs; the fix is in the press chunk, and press-rows.ts grew by 3 lines.

Flaky lane (U4): pushed. The gate lane's note says #875 is not gated yet, so no gate was running. | PR | new head | what | |---|---|---| | #875 | `7c901c6d2` | the press replay fix only (`press.ts`, `press-rows.ts`, 35 lines) | | #877 | `adb9f96df` | merge of `port/rows` | | #878 | `9405c7bd1` | merge of `port/rows-actions`, plus the regression test and a test-race fix in `sidebar-drop.spec.ts` | **Evidence** (`sidebar-drop.spec.ts`, `--repeat-each` through `~/bin/slot`, Rust built with `ExpectedOwners` restored locally on the VMs because the stack does not compile, see above): | | chromium-1440 (dev-01) | webkit-1440 (dev-02) | |---|---|---| | original "note dragged onto a tag", 40 repeats, no fix | 8 failed / 32 passed | 7 failed / 33 passed | | new "pressed before the engine" test, 10 repeats, test head without the fix | 10 failed | 10 failed | | whole file, 40 repeats, fixed head | 160 / 160 passed | 160 / 160 passed | Two causes, both fixed: (1) the product bug in the press commit (a mouse pressed before the engine arrived lost its drag); (2) a test race: each run adds a tag to the one sidebar, so the target row sits below the fold (y=1063 in a 900 px window) and `boundingBox()` does not scroll. The tests now scroll it into view and wait for the row to be armed. **Still open for the orchestrator:** the stack does not compile in Rust (previous comment). First-paint bytes: I did not run `bundle-size.mjs`; the fix is in the press chunk, and `press-rows.ts` grew by 3 lines.
Author
Owner

Compiles again: dev 258f3eac-era merge, Requirement::ExpectedOwners restored

The branch had lost the ExpectedOwners { digest } variant (reader half of #898, dev 72434e45) in an earlier dev merge, so home.rs, plan.rs and tests/tolerant.rs did not compile. Merged origin/dev 02cdf878 into port/rows (#875) 367c7d37, put the variant back in plan.rs next to #887's ScopedMetadata / ExactUndo, and merged forward: #877 a54f90f2, #878 d96ee32f. perf/budgets.json merged by the 3-way rule on each side's line moves (index 220.7 → 221.7, idle-screens 51.0 → 49.8 → 49.65, picker-list 9.7 from #975; total stays 404.2).
Checked on dev-01: cargo fmt --all --check rc 0 and cargo clippy -j4 --workspace --all-targets --locked -- -D warnings rc 0 (compiles every test target) on #875 alone and on the #878 tip (the whole stack). Full gates still wait for #887.

## Compiles again: dev `258f3eac`-era merge, `Requirement::ExpectedOwners` restored The branch had lost the `ExpectedOwners { digest }` variant (reader half of #898, dev `72434e45`) in an earlier dev merge, so `home.rs`, `plan.rs` and `tests/tolerant.rs` did not compile. Merged origin/dev `02cdf878` into `port/rows` (#875) `367c7d37`, put the variant back in `plan.rs` next to #887's `ScopedMetadata` / `ExactUndo`, and merged forward: #877 `a54f90f2`, #878 `d96ee32f`. `perf/budgets.json` merged by the 3-way rule on each side's line moves (index 220.7 → 221.7, idle-screens 51.0 → 49.8 → 49.65, `picker-list` 9.7 from #975; total stays 404.2). Checked on dev-01: `cargo fmt --all --check` rc 0 and `cargo clippy -j4 --workspace --all-targets --locked -- -D warnings` rc 0 (compiles every test target) on #875 alone and on the #878 tip (the whole stack). Full gates still wait for #887.
Author
Owner

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

rebased rows with the missing #887 backend commits. kept ExpectedOwners and current swipe selection guards; reconciled lazy press/tooltip/visibility allocations without increasing total budget.

head: 568322d99976818e9afcf312956c9406c21be6d6; 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 228418 B (-2359 B), CSS 18174 B (-212 B).

Rust gate results (Mac VM):

exit command
0 cargo fmt -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks -- --check
0 cargo check -j4 --locked -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks --all-targets
0 cargo clippy -j4 --locked -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks --all-targets -- -D warnings
101 cargo test -j4 --locked -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks

Engine idle-user fixture fails with missing file; the same failure reproduces on dev. 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-875.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. rebased rows with the missing #887 backend commits. kept ExpectedOwners and current swipe selection guards; reconciled lazy press/tooltip/visibility allocations without increasing total budget. head: `568322d99976818e9afcf312956c9406c21be6d6`; 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 228418 B (-2359 B), CSS 18174 B (-212 B). Rust gate results (Mac VM): | exit | command | |---|---| | 0 | `cargo fmt -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks -- --check` | | 0 | `cargo check -j4 --locked -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks --all-targets` | | 0 | `cargo clippy -j4 --locked -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks --all-targets -- -D warnings` | | 101 | `cargo test -j4 --locked -p calternal-core -p calternal-engine -p calternal-notes -p calternal-perf -p calternal-search -p calternal-tasks` | Engine idle-user fixture fails with missing file; the same failure reproduces on dev. 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-875.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: 1 real failure in both browsers; bundle gate fails.

head 568322d99976818e9afcf312956c9406c21be6d6

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
lazy-offline, lazy-ui, preload, press, shortcuts 31 pass, 1 fail 24 pass, 1 fail 30 pass, 1 fail 23 pass, 1 fail

Failing: lazy-ui.spec.ts:70 failed idle-screens idle warming recovers in place, never reloading (the Palette and ListSearch variants pass; all three pass on dev).

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.

  • After the failed idle-screens CSS is asked for again, the page has no loaded idle-screens-*.css sheet with rules (lazy-ui.spec.ts:125). Something in the rows change breaks the in-place retry for that one chunk. Not fixed: needs a look at how the rows move CSS between first paint and idle-screens.
  • Gate: idle-screens 55.89 vs 51.2, index 223.06 vs 217.75, press 7.03 vs 7. First paint is smaller than dev (JS -2359 B, CSS -212 B), good.

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: 1 real failure in both browsers; bundle gate fails.** head `568322d99976818e9afcf312956c9406c21be6d6` 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 | |---|---|---|---|---| | lazy-offline, lazy-ui, preload, press, shortcuts | 31 pass, 1 fail | 24 pass, 1 fail | 30 pass, 1 fail | 23 pass, 1 fail | Failing: `lazy-ui.spec.ts:70 failed idle-screens idle warming recovers in place, never reloading` (the `Palette` and `ListSearch` variants pass; all three pass on dev). **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. - After the failed `idle-screens` CSS is asked for again, the page has no loaded `idle-screens-*.css` sheet with rules (`lazy-ui.spec.ts:125`). Something in the rows change breaks the in-place retry for that one chunk. Not fixed: needs a look at how the rows move CSS between first paint and `idle-screens`. - Gate: `idle-screens` 55.89 vs 51.2, `index` 223.06 vs 217.75, `press` 7.03 vs 7. First paint is smaller than dev (JS -2359 B, CSS -212 B), good. **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

READY FOR MERGE (issue #1009), 5382bcf132

gate result
first paint JS 228656 B: -2499 B (plain) / 228433 B: -2189 B (normalised)
first paint CSS 18126 B: -245 B
specs, Chromium + WebKit, 1440 + 390 green (rows specs: press, lazy-ui, preload, shortcuts, bulk-tag, visibility; the dev failures aside)
perf/web.sh --bundle-only --gate passes (budget: this stack's lines at measured sizes, kayg's approval; total in merge order stays under 543.9 KiB)
perf ABAB x5 + control below
screenshots 2x, light + dark, 1440 + 390; real Safari below / in this thread

First paint. Plain build vs dev (231155 B JS / 18371 B CSS): 228656 B: -2499 B. Hash-normalised vs dev (230622 B): 228433 B: -2189 B. CSS: 18126 B: -245 B. Dev itself fails five phone specs after #1014 on Chromium and WebKit at 390 (glass:76, lazy-ui:70, preload:89 x3); they fail the same way on this PR's head and are not caused by it.

Perf (perf-02, same method, dev 2fa9e18fb; devA/devB are the same dev build). Median of the 5 per-run medians, ms:

metric devA devB (control) #878 tip
typing, long note p50 23.5 22.6 22.2
typing, long note CPU/key 27.3 27.4 27.3
typing, nested list p50 42.9 42.6 42.3
typing, nested list CPU/key 50.5 49.3 50.0
open note, shown p50 35.4 34.8 35.4
open note, back p50 31.1 31.0 31.9
list flick, CPU 76.6 76.6 76.3
longest task in the flick 166 157 150

Inside the control on every metric. (#875 and #877 are inside the tip; the press engine they share is the one measured in the Tasks stack.)

Screenshots, Chromium 2x and real Safari 1x, light and dark next to the mockup: in the comment on #878 (the tip carries all three rows PRs).

**READY FOR MERGE (issue #1009), 5382bcf132594f9c451a5a7f8c1bf6bc7a9ecf18** | gate | result | |---|---| | first paint JS | 228656 B: -2499 B (plain) / 228433 B: -2189 B (normalised) | | first paint CSS | 18126 B: -245 B | | specs, Chromium + WebKit, 1440 + 390 | green (rows specs: press, lazy-ui, preload, shortcuts, bulk-tag, visibility; the dev failures aside) | | `perf/web.sh --bundle-only --gate` | passes (budget: this stack's lines at measured sizes, kayg's approval; total in merge order stays under 543.9 KiB) | | perf ABAB x5 + control | below | | screenshots 2x, light + dark, 1440 + 390; real Safari | below / in this thread | **First paint.** Plain build vs dev (231155 B JS / 18371 B CSS): 228656 B: -2499 B. Hash-normalised vs dev (230622 B): 228433 B: -2189 B. CSS: 18126 B: -245 B. Dev itself fails five phone specs after #1014 on Chromium and WebKit at 390 (`glass:76`, `lazy-ui:70`, `preload:89` x3); they fail the same way on this PR's head and are not caused by it. **Perf (perf-02, same method, dev `2fa9e18fb`; `devA`/`devB` are the same dev build).** Median of the 5 per-run medians, ms: | metric | devA | devB (control) | #878 tip | |---|---|---|---| | typing, long note p50 | 23.5 | 22.6 | 22.2 | | typing, long note CPU/key | 27.3 | 27.4 | 27.3 | | typing, nested list p50 | 42.9 | 42.6 | 42.3 | | typing, nested list CPU/key | 50.5 | 49.3 | 50.0 | | open note, shown p50 | 35.4 | 34.8 | 35.4 | | open note, back p50 | 31.1 | 31.0 | 31.9 | | list flick, CPU | 76.6 | 76.6 | 76.3 | | longest task in the flick | 166 | 157 | 150 | Inside the control on every metric. (#875 and #877 are inside the tip; the press engine they share is the one measured in the Tasks stack.) Screenshots, Chromium 2x and real Safari 1x, light and dark next to the mockup: in the comment on #878 (the tip carries all three rows PRs).
Author
Owner

READY FOR MERGE: rows base (head dd0a8dddf)

Design merge with Sharing (#842/#876) and dev 4274dafb4 (rusqlite 0.41, restore detection, #1044 frame fixes, #1048 lifecycle ids). Done once in the rows base (#875), carried up by merges:

  • write_executor/home.rs: run_note_write(plan, undo_of, caller) takes both the rows undo key and Sharing's caller; plain writes pass (None, None), execute_note_write_for passes the caller, undo passes its key.
  • crates/notes/src/actions.rs: shared::actions and visibility::actions are both chained. contracts/actions.json is the union (notes.hide/archive/unarchive/unhide and notes.hide-shared side by side; actions.gen.ts regenerated with action-types.mjs --check).
  • crates/search/src/index.rs: dev's hand-opened read snapshot (rusqlite 0.41) plus the foreground gauge.
  • Web: NoteList, lists.ts, sidebar.svelte.ts, actions.ts keep both the Archived and the Shared with me lists; Show hidden does not add your own hidden notes to Shared with me; lists.ts reads store.list(key) (#1030) for Archived/Pinned/tags.
  • #904: its duplicated copy of the backend is replaced by the rows branch's, so there is one backend; its own web commits are untouched.

Checks on dev-02 (aarch64), PR-built backend: cargo test -p calternal-notes 623 passed (one run had typing_during_a_tag_rename_is_kept fail while WebKit specs loaded the host; it passes 3/3 alone on this head and on dev), calternal-auth isolation 3, calternal isolation 40, calternal-server access 1, admin-sharing 11 passed in both chromium projects on #878 and #904 (including the new trash/restore test). Specs, 4 projects: preload, lazy-ui, lazy-offline, glass, shortcuts, keys-no-motion, press, bulk-tag, visibility, sidebar-drop, hidden-blocks, slash-menu: 0 failures except keys-no-motion webkit-1440 settings (Control+,), a flake (fails on 845/877/904 runs and passes on rerun, also on #854/#861).

First paint vs dev fe2efbf11 (228023 B plain / 227378 B normalised): −28 / +149 B, CSS 18236 (0). Budget total 532.55 KiB. Gate clean.

## READY FOR MERGE: rows base (head `dd0a8dddf`) **Design merge with Sharing (#842/#876) and dev `4274dafb4` (rusqlite 0.41, restore detection, #1044 frame fixes, #1048 lifecycle ids).** Done once in the rows base (#875), carried up by merges: - `write_executor/home.rs`: `run_note_write(plan, undo_of, caller)` takes both the rows undo key and Sharing's caller; plain writes pass `(None, None)`, `execute_note_write_for` passes the caller, undo passes its key. - `crates/notes/src/actions.rs`: `shared::actions` and `visibility::actions` are both chained. `contracts/actions.json` is the union (`notes.hide`/`archive`/`unarchive`/`unhide` and `notes.hide-shared` side by side; `actions.gen.ts` regenerated with `action-types.mjs --check`). - `crates/search/src/index.rs`: dev's hand-opened read snapshot (rusqlite 0.41) plus the foreground gauge. - Web: `NoteList`, `lists.ts`, `sidebar.svelte.ts`, `actions.ts` keep both the Archived and the Shared with me lists; Show hidden does not add your own hidden notes to Shared with me; `lists.ts` reads `store.list(key)` (#1030) for Archived/Pinned/tags. - #904: its duplicated copy of the backend is replaced by the rows branch's, so there is one backend; its own web commits are untouched. **Checks on dev-02 (aarch64), PR-built backend:** `cargo test -p calternal-notes` 623 passed (one run had `typing_during_a_tag_rename_is_kept` fail while WebKit specs loaded the host; it passes 3/3 alone on this head and on dev), `calternal-auth` isolation 3, `calternal` isolation 40, `calternal-server` access 1, **admin-sharing 11 passed in both chromium projects on #878 and #904** (including the new trash/restore test). Specs, 4 projects: preload, lazy-ui, lazy-offline, glass, shortcuts, keys-no-motion, press, bulk-tag, visibility, sidebar-drop, hidden-blocks, slash-menu: 0 failures except `keys-no-motion` webkit-1440 `settings (Control+,)`, a flake (fails on 845/877/904 runs and passes on rerun, also on #854/#861). First paint vs dev `fe2efbf11` (228023 B plain / 227378 B normalised): **−28 / +149 B**, CSS 18236 (0). Budget total 532.55 KiB. Gate clean.
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
Merge conflict checking is in progress. Try again in few moments.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin port/rows:port/rows
git switch port/rows

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch dev
git merge --no-ff port/rows
git switch port/rows
git rebase dev
git switch dev
git merge --ff-only port/rows
git switch port/rows
git rebase dev
git switch dev
git merge --no-ff port/rows
git switch dev
git merge --squash port/rows
git switch dev
git merge --ff-only port/rows
git switch dev
git merge port/rows
git push origin dev
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!875
No description provided.