Repository navigation
Canvas: stronger selection, recolor the selection, reliable color taps - #1962
Merged
Merged
Conversation
Member
Author
|
Main on bb87568: e2e failed on 2 of 4 shards, and both failures are outside the canvas.
The canvas specs, the unit stage and the downstream saas build passed. I'm re-dispatching Main once to confirm. Generated by Claude Code |
joepio
marked this pull request as ready for review
October 1, 2026 17:01
Member
Author
|
Trekmeester: included in batch #1966 (run 36898843808). It lands when that run is green; if this PR breaks something there, I take it out and say why here. |
joepio
added a commit
that referenced
this pull request
Oct 2, 2026
joepio
added a commit
that referenced
this pull request
Oct 2, 2026
- CanvasPage: the develop merge declared FAN_DRAG_THRESHOLD twice (#1962 landed on develop and in this batch); keep it once, each constant with its comment. - #1960: usageMetadata/compactionTokens as a pure module with tests, including that the last tool step's input wins once the SDK merges every part. - #1523: rowIsCommentable, tested with a real deferred-genesis draft from the store (final subject, still new) and the legacy _new: key. - #1941: DriveUnavailable test: the 10 s retry and Retry now must not leak a rejection (fails with an unhandled rejection without the fix).
joepio
added a commit
that referenced
this pull request
Oct 2, 2026
…ts, AI fixes, presence order, vault quiet, canvas selection (12 PRs) (#1966) * Comment on a table row from its gutter A table row is a resource of its own, so its comment thread is already the ordinary one — Messages whose `about` points at the row. What was missing was a way to reach it: the Comments panel could only ever be about the page's own resource, and a row is never that. Two pieces: - The grid takes a `RowHeaderAddonComponent`, rendered in each row's gutter left of the row number. It knows rows only by index, so the table page supplies the index -> subject mapping (`getRowSubject`) and the component. The gutter is widened to hold a bubble beside the row number, and row-scoped affordances marked `data-row-affordance` are revealed by hovering anywhere in the row rather than only over the gutter. - The panel gains a target. `commentSubject` says which resource the thread is about, `undefined` meaning the page's own — what the NavBar button opens. Aiming it at another resource switches threads instead of toggling, so clicking row 3's bubble while row 1's thread is up does what it looks like. A close request carries no target and so cannot be expressed as a toggle: `closePanelState` closes the panel whatever it is aimed at, which is what a deleted target, a navigation and the drawer's own dismiss all need. The bubble stays put, with its count, for a row that has comments (highlighted while some are unseen) and appears on hover for one that has none. Not covered: reaching the bubble by keyboard. It shares the gutter's mouse-only reachability with the existing expand button, because the grid owns Tab for cell navigation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SooZ4RQshFY8YncRNq6Qpe * Resolve a row's subject the way the grid renders it The gutter bubble never appeared for the rows it most needed to: the ones you had just typed. It asked the collection for every row's subject and treated `index >= memberCount` as "no resource yet", which is not what that boundary means. A row added this session keeps its local `_new:` key for its whole life on the page — materializing persists it but deliberately does NOT turn it into a collection member, so react-window never remounts it mid-edit. Past the member count sat real, saved rows, reported as having nothing to point at. So the context now reports which of the two kinds a row is (`rowSource`), mirroring what `TablePage/TableRow.tsx` itself renders, and the bubble uses the matching hook for each: `useMemberFromCollection` for a member, `useResource` on the `_new:` key for a session row, which the store aliases to the real subject once it materializes. The two cases genuinely need different hooks — a session row has no subject at all until then — which is why this reports the source rather than resolving to a subject. Caught by `table-row-comments.spec.ts`, which now passes end to end against a real server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SooZ4RQshFY8YncRNq6Qpe * Add a view when choosing a type from a tab, never replace the last one Choosing Calendar from the menu of a table's only view tab turned that view into a calendar, and the table layout was gone (#1806). The user should always be able to switch between all the applicable views. - A tab's menu now has an "Add a view" section (built-in kinds and apps), the same as the + tab: it adds a view named after the type and switches to it, leaving the tab's view intact. - "Change this view to" is offered only while another view of the current type remains, so the last table view (or the only view of an app) can never be converted away. setViewKind enforces the same rule and adds a view instead. - Delete is disabled on the last table view while other views exist. - A converted view whose name was a default ("Default View", a type label, a template view name, or an "All <rows>" table name) takes the new type's name; a name a person chose is kept. Closes #1806 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Ask the drive for its apps again when a view menu opens (#1846) useDriveApps published nothing until its whole mount chain had finished: findSchema, the local app query, then an awaited getResource per app, one after the other. Live events that landed meanwhile were held back too. After a reload, an app whose sync delta could not apply to the page's copy ("incomplete Loro import ... missing base state") sits as a placeholder under repair, and getResource waits up to 10 s on it, which is as long as the money e2e waits for the "New app" menu item. - Attach the listeners before anything else and fold every answer in as it arrives, reading the live resource each time, so no answer overrules a newer one and no slow read holds back the others. - Read the query's members in parallel. - Expose refresh(), and call it when "+ Add view" and a view tab's menu open, so opening a menu re-runs the query and re-reads what it knows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Add a SessionStart hook for Claude Code cloud sessions Sets up a fresh Claude Code on the web container so agents can lint, test and run the stack straight away: the pinned Rust toolchain and WASM targets, wasm-pack, wasm-opt, pnpm install, the @Tomic package builds, Playwright's browsers for @tomic/e2e and a warm debug build of atomic-server (which also builds data-browser and the WASM). Two sandbox workarounds: - The proxy blocks cdn.playwright.dev, so the Chrome for Testing builds come from the storage.googleapis.com mirror. - Containers have no IPv6, so ATOMIC_IP=0.0.0.0 is exported for the session; atomic-server binds to :: by default and fails otherwise. Only runs when CLAUDE_CODE_REMOTE=true. .gitignore now tracks the shared hook and settings while keeping local .claude state ignored, and AGENTS.md says to keep the hook in sync with the environment requirements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168N2XS6KXcewxhP3XTwDa6 * Say where the time went when the demo setup stalls The 45 second stall notice is a fixed deadline, so a machine that is slow and one that is stuck report the same thing. Sentry ATOMIC-BROWSER-1J holds twelve of them across four releases, spread over every step (identity 7, workspace 3, storage 2, and one that never left the sign-in check), from Chrome on Windows and Mac alike, and nothing in them says which kind it was. Setup measures 17 to 28 seconds on a desktop CPU, so 45 seconds is under twice the ordinary time, and a slow laptop could reach it while still making progress. The report now carries how long each step took and whether the tab was in the background, which browsers throttle. And a run that does finish after the notice sends a second, informational report with its total time and its slowest step, so a stall that ends is a slow machine, and one with no such follow-up is a hang. That is all this changes: what to fix depends on which of the two the reports turn out to be, and twelve events do not say. * Name the failing request in the account passkey error The message was the same for the status check, both start calls and both finish calls, and dropped the HTTP status. It now ends with the request and status, for example (/use/finish: 401). * Build in the background and use the environment cache in cloud sessions Only a cloud environment's setup script is cached between sessions; hooks run every session on a fresh clone. The previous hook redid the whole cold setup, server build included (10-20 min), while blocking the session. Split it in three: - cloud-setup.sh installs toolchains, node_modules, Playwright browsers and crates, skipping what is present. It fits the setup script's five-minute cache limit (about a minute from cold), so AGENTS.md suggests calling it from there; the hook also runs it, so sessions work without that. - cloud-build.sh builds the @Tomic packages and the debug atomic-server, writing packages.done / server.done markers under /tmp/atomic-setup. - session-start.sh exports ATOMIC_IP, runs cloud-setup.sh, starts cloud-build.sh in the background and tells the agent, through additionalContext, how to wait for it. With a cached environment the hook now blocks for about a minute (mostly linking node_modules) instead of the full build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168N2XS6KXcewxhP3XTwDa6 * Don't report a vault backup as failed when another tab switched account A second tab signing in as a different account takes over the shared portal cookie. A backup pass the first tab already had in flight then reaches the control plane as that account and is told the enrollment does not exist, which was logged as a failed backup. When the signed-in account no longer matches the one the pass started for, it is now skipped quietly, like the 401 case. * Explain a drive this device can't reach instead of showing a raw error A drive with no copy here and nothing to fetch it from (a local-only drive while no other device is open, or no server at all) now gets its own page: what is going on, the Cloud Vault restore first when a backup exists, a device diagram, automatic retries every 10 seconds, and the raw error under Technical details. The open drive counts as a drive even before this device registered it as local-only. * AI chat: compact on last-step context size, not summed multi-step usage * Canvas: make the selection stand out with a glow, tinted box and bigger handles * Canvas: picking a color recolors the selected strokes and text * Canvas: measure a color or size tap from where the press landed * OpenRouter return: open the AI sidebar, and don't error on a code-less return link * Send PRESENCE_SUBSCRIBE before the presence update that follows it subscribePresence() is deferred behind authenticate().then(), while the presence manager sends its first update (and reSubscribeAll() its rebroadcast) synchronously. The update reached the server ahead of the subscribe frame and was dropped as 'presence update from non-subscriber'. Hold presence updates for a drive while its subscribe is pending and send them right behind it. Nothing is held before authentication, and a withdrawn subscription drops what was held, so the server's subscribe-time read check stays the only gate. * Batch fixes: DriveUnavailable catches its expected fetch failures (#1941); HANDLE_HIT_RADIUS gets its doc comment back (#1962) * Settle translation catalogs for the batch * kanban/timer specs: convert a copy, since #1902 keeps the only view of a type from being converted * CI traces: name the Dagger function failedTestResults, which the CLI can call (e2eTestResults was 'unknown command') * @tomic/lib unit tests: 20 s timeout, since a loaded CI host timed out ten millisecond tests at 5 s * Batch fixes and coverage - CanvasPage: the develop merge declared FAN_DRAG_THRESHOLD twice (#1962 landed on develop and in this batch); keep it once, each constant with its comment. - #1960: usageMetadata/compactionTokens as a pure module with tests, including that the last tool step's input wins once the SDK merges every part. - #1523: rowIsCommentable, tested with a real deferred-genesis draft from the store (final subject, still new) and the legacy _new: key. - #1941: DriveUnavailable test: the 10 s retry and Retry now must not leak a rejection (fails with an unhandled rejection without the fix). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Michiel de Jong <michielbdejong@ontola.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Joep · project thread
Related Issues
No issue; requested directly.
Before: a lasso selection was only a thin dashed box with small white corner handles, so it was hard to see what was selected. Picking a color while something was selected only changed the pen, and a tap on the color button sometimes did nothing.
After: every selected stroke, text and image gets a blue glow behind it, the box is solid with a light blue tint, and the corner handles are larger solid blue discs with a white ring. Picking a color from the color button (drag-pick or tap-swap) recolors the selected strokes and text as one undoable step; images keep their look. A tap on the color or size button counts as a tap unless the finger moves more than 10px from where it landed.
How:
drawSelectionHaloincanvas-draw.tspaints the glow underneath the existing canvas content (so colors stay true), anddrawSelectionOverlaydraws the tinted box and bigger handles.recolorSelectioninCanvasPage.tsxrewrites the selected elements throughcommitReplace. The fan gesture now measures drag distance from the press point instead of the button centre.Checklist
canvas-touch.spec.tslasso test still passes locally)