Merge train 2026-10-14: #259 #243 #251 #252 #254 #240 #264 #261 #265

Closed
kayg wants to merge 0 commits from train/2026-10-14 into dev
Owner

Merge train approved by kayg (2026-10-14). It lands these PRs, each reviewed on its own thread:

PR What
#259 CI: web e2e trustworthy (#11)
#243 Instant arrowing through notes and the mouse fade (#150)
#251 Link chips, focused link in the accent (#162)
#252 Width Narrow/Wide/Full, opacity fix, menu motion, monospace source, --- cards (#155)
#254 Safari bars on the phone list screens (#226)
#240 Phone bar above the keyboard, capsule blur, toasts in the bar (#145)
#264 Fix: an outside rewrite could be overwritten by a save (#222); storm test
#261 Fix: @ and [[ picker wired to search; ⌘K visit key (#165)

Conflicts I resolved:

  • .gitignore;
  • editor.ts: #150's live/read-only split plus #225's Return keymap;
  • hidden.ts, cm.css and Editor.svelte: #155 breaks plus #162 chips plus #150 show/live; #155's width state is renamed mounted;
  • BottomBar: #101's ask plus #145's toasts, with the question outranking a toast;
  • tests.rs mod list;
  • cm.css plus editor index exports for #261.

Checks on the train:

  • fmt passes and svelte-check reports 0 errors; the web build passes;
  • the calternal, notes (168), fsio, server and auth tests pass;
  • editor vitest: 105 of 106 pass. The failure is tree-cost "1001 vs 1000", already failing on dev; I'm filing it.

🤖 Generated with Claude Code

https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T

Merge train approved by kayg (2026-10-14). It lands these PRs, each reviewed on its own thread: | PR | What | |---|---| | #259 | CI: web e2e trustworthy (#11) | | #243 | Instant arrowing through notes and the mouse fade (#150) | | #251 | Link chips, focused link in the accent (#162) | | #252 | Width Narrow/Wide/Full, opacity fix, menu motion, monospace source, `---` cards (#155) | | #254 | Safari bars on the phone list screens (#226) | | #240 | Phone bar above the keyboard, capsule blur, toasts in the bar (#145) | | #264 | Fix: an outside rewrite could be overwritten by a save (#222); storm test | | #261 | Fix: `@` and `[[` picker wired to search; ⌘K visit key (#165) | **Conflicts I resolved:** - `.gitignore`; - editor.ts: #150's live/read-only split plus #225's Return keymap; - hidden.ts, cm.css and Editor.svelte: #155 breaks plus #162 chips plus #150 show/live; #155's width state is renamed `mounted`; - BottomBar: #101's ask plus #145's toasts, with the question outranking a toast; - tests.rs mod list; - cm.css plus editor index exports for #261. **Checks on the train:** - fmt passes and svelte-check reports 0 errors; the web build passes; - the calternal, notes (168), fsio, server and auth tests pass; - editor vitest: 105 of 106 pass. The failure is tree-cost "1001 vs 1000", already failing on dev; I'm filing it. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
kayg added 31 commits 2026-10-14 12:13:35 +00:00
The browser tests failed on every PR, the same tests on a commit that
was green before. Measured causes, and what changed:

- CI had no priority. Job containers sat in system.slice at the same
  weight as all the agents (user-1001's CPUWeight=20 is the only slice
  under user.slice, so it changed nothing). Now every job container is
  in ci.slice (weight 1000, ci/host/ci.slice; the runner passes
  --cgroup-parent=ci.slice).
- WebKit composites the glass with Mesa's software GL in the container.
  Each shader is compiled on first use on the compositor's one thread:
  a page's first frame took ~9 s at 1440, whatever the cores. The shader
  cache now lives in a volume kept between runs (~2.5 s warm).
- Four workers on CI instead of six, WebKit 1440 at two at a time, and
  the CPU-throttled layout checks (@throttled) alone after the rest.
- The settings tests get their own server and person: flipping the
  theme of the shared person repainted every other test's pages.
- Test bugs a busy machine exposed: the shortcut helper read a
  modifier's or an earlier key's answer; ⌘⇧; was checked in today's
  note, which other tests log into too; fixed sleeps before measuring
  are now still(), which waits for animations to end.
- Every server (web, primitives, mockup) listens on a port the system
  picks, so runs side by side never collide or reuse another run's
  server; the app is built once per run instead of four times at once.

ci/README.md has the host setup and the numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Two web jobs at once split CI's share between eight browsers, and
WebKit's software drawing slowed until toasts timed out before they
could be clicked (run 107 of #147). A second runner with the label web
and capacity 1 takes only the web job; rust stays on the main runner, so
a web job never waits for a rust job that can't start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
The contrast check photographs the lists, and other tests adding notes
moved the rows between finding the words and taking the picture (#159,
run 110: ratios of 1.4 for ordinary titles). It now runs on its own
server, like the settings tests; the server comes from the project's
metadata. WebKit 1440 frames with the glass open took 1-3 s each on a
busy machine, and two at once starved each other: one at a time, a test
averages 10 s instead of 16 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
pwa-update.spec.ts fakes a deploy by rewriting sw.js in the served
build; with one build for every server that would have reached all of
them. Also still() for two new fixed sleeps in safe-areas.spec.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
WebKit 1440 alone still met three WebKit 390 workers: four software-drawn
browsers at once, and the first WebKit 1440 test ran out of time twice
(#177). Now one per width, and one in the primitives. hidden-cost.test.ts
timed buildHidden by the wall clock, which on a busy machine also counts
waiting for a core; it now measures CPU time. The main runner takes one
job at a time, and both runners finish running jobs on a restart
(ci/host/graceful.conf).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Two proof runs failed waiting 15 minutes for a rust job still queued
behind other runs' jobs. The web job now waits up to 40 minutes, and the
rust job leaves a build-failed mark so a broken build ends the wait at
once. The web job's container outranks the rust build inside ci.slice
(--cpu-shares on the web runner, weight 532 to 100); the main runner
stays at two jobs so a run's rust job can always start.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Web e2e: still() for the fixed sleeps in the new tooltip, selector and tag specs (#11)
Some checks failed
CI / web (pull_request) Failing after 43m7s
CI / rust (pull_request) Failing after 1m53s
475d854fff
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Bottom bar: rides on the phone keyboard, hugs its blur, and shows toasts (#145)
Some checks failed
CI / web (pull_request) Failing after 20s
CI / rust (pull_request) Successful in 2m1s
5964754b58
- The progressive blur behind the bar is a soft capsule around it (Morph's
  halo), the same size and spring as the bar, instead of a full-width band.
- On a phone the bar sits just above the keyboard (visualViewport), moved
  with a transform; it hides in place while the keyboard closes, and the
  caret line is kept clear of it.
- Toasts are the bar: the capsule morphs into the toast and Undo, then back.
  More than one wait their turn. notify() is unchanged; with no bar on
  screen they show on their own as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Links as calm chips; the link you are on glows (#162)
Some checks failed
CI / web (pull_request) Failing after 27s
CI / rust (pull_request) Successful in 1m46s
8ecb506ed6
Every link's words are drawn as a soft pill: a small note icon (or a
diagonal arrow for a web link), a faint fill, a hairline edge and a
whisper of shadow. No underline and no dashes anywhere: a link to a note
in the Trash is the same pill, faded, and an unlinked mention in Info is
a calm tint.

The link the caret is on gets a cyan, yellow and orange ring and a
little lift; the pointer fades the same light in. The caret lights it at
once. The pill is a mark around ordinary text, never a widget, so the
caret walks through it letter by letter, IME works inside, a long link
wraps into one pill per line and the line never changes height.

Link marks are drawn outermost, so bold words or a search match inside
a link never cut it into pieces. The chips are only redrawn when the
caret lands on or leaves a link. The look lives in links.css, reusable
outside the editor as .link-chip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
A rust job's linker deadlocked on a futex and held the shared target
lock for 45 minutes; every other run's rust job, and so its web job,
waited behind it. Rust jobs now stop after 30 minutes, web jobs after 60
(40 of which may be waiting for the binary).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
CI: a server job the web job needs, instead of a wait inside the web job (#11)
Some checks failed
CI / server (pull_request) Successful in 22s
CI / rust (pull_request) Successful in 1m47s
CI / web (pull_request) Has been cancelled
ff7ea090ac
The web job started at once on its own runner and waited inside for the
binary; meanwhile the forge gave the free docker slots to other runs'
jobs, and the wait ran out with the binary never built (#218, #219). A
small server job now builds the binary; the web job needs it, so the web
runner only takes web jobs that can run, and the browser tests no longer
depend on format, lint and the Rust tests passing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Notes show in full the moment they're picked; arrowing joins no rooms (#150)
Some checks failed
CI / web (pull_request) Failing after 29s
CI / rust (pull_request) Successful in 1m55s
757b7bbe94
Holding ↓ in the list used to mount a new editor per note, open two
IndexedDB databases, join and leave a room, and only then draw the text:
title-only or blank frames all the way down. Now:

- One editor stays on the page. A picked note is drawn at once from its
  remembered text, read-only (preview to preview swaps the text in place,
  no new editor state). Its live document is attached once the selection
  rests (150 ms from the keyboard, straight away with the mouse), after
  the room has caught up, so the text never steps back. Only the live
  document is ever edited: a preview refuses every change, and clicking
  or typing into it attaches the live note first.
- Remembered text (lib/peek.ts): memory plus this device, bounded, kept
  fresh by the journal. The list asks for the notes ahead of the
  selection (60 in the direction of travel, 10 behind) and on screen;
  missing ones go out in one `notes.peek` request.
- `notes.peek` (server): up to 50 notes' text at once, from the open
  room or the file. Never opens a room.
- An arrow walk is one history entry, written once the steps stop.
- Smaller costs on every switch: the notes metadata database is opened
  once per page; links resolve through a file index instead of scanning
  every note; the sky's clouds repaint only when the light changes, not
  every minute.
- Opening a note with the mouse or a tap is a quick 140 ms opacity fade
  on the text, no movement (kayg). The keyboard never animates.

Tests: editor unit tests for preview/attach; server tests for peek;
e2e: every frame of an arrow walk shows the picked note in full, typing
after the walk lands once, a note changed elsewhere shows its new text,
the mouse fade is opacity only; an @perf test holds ↓ for 3 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
A save checked the file against our last write, then (after the pending
history write and the temp file's fsync) renamed its file over the note.
Another app renaming its version in during that window was overwritten
without a trace: its echo later read our bytes and matched by hash. This
is the line the perf storm lost.

Saves now write through calternal_fsio::write_unless_changed: the file is
checked again just before the write, and our file is swapped in with an
atomic exchange (renameat2 RENAME_EXCHANGE). If what it replaced is not
the file that was checked, it is swapped straight back, ingested like any
outside edit, and the save tries again.

The storm test (tests/storm.rs) reproduces it: 3 typists, saves on their
own and on request, another app rewriting the file every 0-8 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Phone bars match the frosted list screens (#226)
Some checks failed
CI / web (pull_request) Failing after 25s
CI / rust (pull_request) Failing after 1m51s
f4d459428b
The list screens are a glass sheet over the backdrop, so the page base colour
(what Safari paints its bars from, and theme-color) is now the glass's colour at
the screen edge, not the raw sky. The sheet melts into that colour at its top and
bottom, and the backdrop's floor carries it under Safari's toolbar. A Playwright
check reads the real edge pixels on every phone screen.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
The link you are on lights in your accent, not a gradient (#162)
Some checks failed
CI / web (pull_request) Failing after 26s
CI / rust (pull_request) Successful in 2m9s
33b562e4a8
kayg: the focused link should be accent highlighted. The lit pill now
takes the accent as its edge and a soft accent tint as its fill (plus
the accent on its icon), with the same lift; the caret lights it at
once and the pointer fades it in over 140 ms. The cyan, yellow and
orange gradient is gone everywhere, and with it the registered --ring
property: the edge is a plain border again.

TASTE.md: highlights use the accent, never decorative gradients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
- Width (Narrow / Wide / Full) replaces the Page/Card look and "Wide page". Full is the old
  page in its frame; Narrow and Wide float the paper on the backdrop, drawn as cards on a
  CodeMirror layer, so the margin controls sit inside the card. Older calternal-layout /
  calternal-wide keys open at their new width and are tidied when a width is chosen.
- A width change from the mouse springs the card in place; the text rewraps once, after.
  The keyboard changes it at once.
- --- (a thematic break) splits floating paper into stacked cards and is a calm divider at
  Full. The --- shows, muted, only with the caret on its line; same height either way.
  Typing --- under a line of text adds a blank line first, so it stays a break, not a heading.
- Opacity and blur act on the paper itself (page or cards), floor 40%. Fixes Card at 99%:
  the frame glass vanished and the card's paper ignored the opacity.
- The context menu grows from the pointer with a short spring, fade and blur; the submenu
  from its row; keyboard-opened menus just appear.
- Source mode is set in Maple Mono; switching keeps the caret's line in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Width spring always starts; context menu reads over any backdrop (#155)
Some checks failed
CI / web (pull_request) Failing after 26s
CI / rust (pull_request) Successful in 2m0s
c47d642a0f
- The page's data-width is set by applyWidth after the spring is armed, so a width change is
  never styled before the card knows to spring.
- The context menu's surface is mostly paper over its glass (88%), so its words read over dark
  photos too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
CI: back to waiting for the binary inside the web job; needs never schedules here (#11)
Some checks failed
CI / server (pull_request) Successful in 17s
CI / rust (pull_request) Successful in 1m52s
CI / web (pull_request) Failing after 15m11s
76334449fb
On #236 and #237 the server job succeeded and the web job that needs it
stayed pending (the same forge behaviour that removed needs before). The
web job waits for the server job's binary again, up to 40 minutes, and a
failed build ends the wait at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
Web: @ and [[ offer notes again, from search (#165)
Some checks failed
CI / web (pull_request) Failing after 43s
CI / rust (pull_request) Successful in 2m45s
33c702bae5
Why they showed nothing: the editor's link shortcuts (@ and [[) only
offer notes when the app hands them a search, and Editor.svelte never
did. The completion source returned nothing on every keystroke.

- One search client (lib/search.ts) for ⌘K and the link picker, on
  search.query, so ranking, typo fixes and "used lately" match ⌘K.
- lib/link-targets.ts turns hits into link targets; offline or on an
  error it falls back to notes whose title matches. Never offers the
  note itself. A typed [[Title]] links the note with that title.
- The picker sits on <body> with the palette's glass and the app font,
  a set width, 12px from the screen's sides, so it never clips or
  overflows at 320.
- e2e/picker.spec.ts: [[ and @ find a note by search and Enter writes a
  standard [Title](<Title.md>) link, Chromium and WebKit, 390 and 1440.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
# Conflicts:
#	.gitignore
# Conflicts:
#	packages/editor/src/editor.ts
fsio: keep the checked file open across the swap (#222)
Some checks failed
CI / web (pull_request) Failing after 30s
CI / rust (pull_request) Successful in 2m12s
67dbfcbb10
Its inode number can't then be reused by a newer file and mistaken for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
# Conflicts:
#	apps/web/src/components/Editor.svelte
#	apps/web/src/styles/cm.css
#	packages/editor/src/syntax/hidden.ts
# Conflicts:
#	apps/web/src/components/BottomBar.svelte
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Yyy8fQGxPLkLa8v8NXz7T
# Conflicts:
#	crates/notes/src/tests.rs
Merge remote-tracking branch 'origin/issue/165-link-picker-fix-2' into train/2026-10-14
Some checks failed
CI / server (pull_request) Successful in 18s
CI / web (pull_request) Failing after 25s
CI / rust (pull_request) Successful in 1m56s
a760dbfa1a
# Conflicts:
#	apps/web/src/styles/cm.css
#	packages/editor/src/index.ts
Author
Owner

Closing: this branch is already fully contained in dev (it landed through a merge train). Branch cleanup, 2026-10-15.

Closing: this branch is already fully contained in `dev` (it landed through a merge train). Branch cleanup, 2026-10-15.
kayg closed this pull request 2026-10-15 08:34:32 +00:00
Some checks failed
CI / server (pull_request) Successful in 18s
CI / web (pull_request) Failing after 25s
CI / rust (pull_request) Successful in 1m56s

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!265
No description provided.