Repository navigation
Conversation
…26-09-22) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- motion-dom patch: install the view-transition rules once as a static stylesheet keyed on view-transition-class tokens and a root class. Rewriting a stylesheet around each capture forced a full-document restyle before the old snapshot, after the update, and at cleanup. - Keep bitmap shadow layers in a persistent display: contents host; removing direct <body> children restyles the whole document. - Release focus from the closing card before the transition. Removing the focused Close button mid-update made focus fixup recalc style synchronously. - Settle a crossing when the browser skips its view transition on viewport resize (or the clock has run out). motion's finished never settled, leaving the destination body unmounted and cleanup pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sings - glimmer-motion patch: Choreo takes an @armed argument. When nothing is armed and no run, leaver or claim is in flight, a render skips both measurement passes instead of reading every participant's rect. Typing in a large card went from 608 layout reads (104 ms) to 5. - The stack region counts a workspace portal or a non-bitmap dock opening as armed; both score their own steps, and skipping their passes left the index card invisible after opening a workspace. - Opening a card through a bitmap crossing fetches the card's indexed isolated HTML and shows it, inert, in place of the deferred live body as soon as it arrives. The incoming view is live during the transition, so light cards show content from the first frame of the flight. Large renderings (over ~100 KB) are skipped: parsing and inserting them stuttered the flight. - The crossing releases focus inside a leaving card before capture; removing the focused Close button mid-update forced a synchronous style recalculation. - Finished view-transition animations are cancelled when a crossing ends; about 34 were retained per crossing for the rest of the session. - The search input focuses with preventScroll, avoiding a forced layout of the half-mounted search panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # packages/host/app/services/loader-service.ts # packages/host/app/services/operator-mode-state-service.ts # packages/runtime-common/loader.ts
A parent that renders the same card more than once (a fitted tile and an embedded row) made the origin lookup ambiguous, so the card opened without its tile flight and closed without a return. The preview containing the activating click is now the origin, and it is kept as the return address for the buried parent. Also: - close() records why a return was skipped (motionTrace only) - crossings take no time in tests and hold a test waiter until the crossing and any deferred body are done Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closing a card never armed the stack scene, a scene without a primary card reflowed nothing, and the blur after the Close click re-closed the already-closed search sheet, whose new scene finished the reflow at once. Closing now arms the stack scene (with no primary when the whole stack goes, so every kept card reflows), and closing a closed sheet starts no scene. host-motion records begun and refused scenes under motionTrace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A card opened into a new right-hand stack crossed as a bitmap while the stack region was held instant, so the existing stacks snapped to their new widths under the flight. That crossing now leaves the stacks live: it passes no underlay (its source card stays on top of its own stack), and the stack scene may arm during it with no primary, so every existing stack reflows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expanding or restoring a card changed its frame in one frame: the reflow
only translates a card from its old centre, which does not move here. The
card now crosses its own two faces as a bitmap while its frame morphs.
Expanding keeps the narrow face for most of the move and hands over near
the landing ('late' handoff), so the wide layout is never shown squeezed.
The crossing also accepts whole-scene layers that fade out or in around
it, for the workspace tile crossing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a workspace from its dashboard tile now uses the same bitmap crossing as a fitted card opening to isolated: the tile's wallpaper crosses into the full realm background while the dashboard fades out beneath it and the index card fades in over the landing. Opening the dashboard again crosses the background back into its tile. The clip-path portal remains the path in tests and wherever the crossing is unavailable. The tile element rides on the open origin only for the crossing and is never kept on the stack item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- motion-dom: replace the browser-generated view-transition animations with WAAPI copies instead of retiming them. Chrome sampled a retimed CSSAnimation inconsistently (some frames with the effect easing, others with the CSS linear timing), so layers lurched forward and back. - The playback deadline treated the crossing duration (seconds) as milliseconds, so any crossing longer than ~500 ms was jumped to its end. - The bitmap shadow now hands back at its resting value: transitions stay off through the landing paint, so the card's shadow no longer fades in from none after the flight. - ?motionInspect records every frame of every view-transition layer and summarizes reversals, spikes, face jumps and long frames per crossing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a card opens over its parent, the parent's header used to morph from its old place up into the buried strip, so the new card's rising edge uncovered it from below. It now enters the strip as its own layer, sliding down one header-height from under the top bar, with the title and realm icon inside it; its old place fades with the parent's face. Closing still matches the header and scales its title back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A card opened into its own stack from another stack's card remembers that card (in memory only). Closing it, when that card is still on top and the tile is visible, crosses the card back into the tile while every remaining stack morphs into the freed width. A click beside a tile (an open-in-new- stack strip) now resolves to that tile rather than the button. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every crossing (open, return, expand, search pick, workspace tile) used to repeat the same host policy by hand: gate on tests, reduced motion and drag, take and release the bitmap budget, hold a test waiter, tag the landing element with a one-off data attribute after the update, and wait for afterRender before the new capture. That now lives in hostMotion.cross(), whose `to` is a function returning the landing element once the update has rendered. hostMotion.canCross() exposes the gate for callers with their own fallback (a search pick docks with Choreo when no bitmap can play). crossfadeCardBitmap() takes one options object instead of nine positional arguments; its fields are documented on BitmapCrossing. A dashboard tile's rounding belongs to its card container, so the tile image adopts the container's corners during a workspace crossing and they tween to and from the square realm background. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/host-motion.md describes the current system in one place: live Choreo geometry versus bitmap crossings, HostMotionService's policy and its cross() entry, the anatomy of a crossing, origins and return addresses, timing, the measured performance rules, and the trace and inspection tools. The vendor notes now describe both dependency patches as they are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening the dashboard only crossed back into a tile when the workspace had exactly one stack whose index still carried the tile origin it was opened with, so it silently didn't animate after a reload, a pasted URL, or with a right-hand stack open. The return needs only the realm whose background is showing: it now finds that realm's tile on screen after the update, preferring the favourite or catalogue copy it was opened from, and fades the background when no tile is visible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shboard tile The stacks now follow the realm background out of the dashboard tile: they start at 30% of the crossing, are opaque by 70% and settle from 0.94 on the boundary spring, zooming about the tile. Returning, they shrink toward the tile and fade by 35%, ahead of the background. Closing the realm's index card, the last card on screen, now takes the same crossing back to the dashboard tile instead of cutting to it. A dismissed right stack moves each remaining stack as one layer, so a top card and the parents buried under it move together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dashboard to workspace, the realm icon leaves the tile bitmap as a
companion layer and flies to the first card's header, while the platter
of cards grows out of it on the boundary spring (a 'rise' scene seeded
by the icon). Scaling about the icon keeps the header's icon slot close
to the flying icon throughout. The return mirrors it with a 'fall'
scene, and the dashboard fades in from a quarter of the way so the
middle of the crossing is never empty.
Crossings between the dashboard and a workspace crossfade the top bar
('chrome: crossfade') rather than cutting to the new one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every lookup in the updated document is now a function called after the update, given the landing face: to(), a companion's to(landing), a fall scene's seed(landing). The mechanism tags the landing itself, so cross() no longer keeps its own landing bookkeeping. The dashboard <-> workspace crossing is defined once (workspaceEntry / workspaceExit) and the dashboard return has one gate (crossToDashboard) for the dashboard button and for closing the last card. The platter now scales and fades up centred on the crossing card's frame, from the middle of the tile at about the realm icon's size. A cold realm's entry waits up to 300 ms for the header's realm icon so the flying icon has somewhere to land; an unlanded companion is traced. A reflowing neighbour stack keeps its faces at natural size while its frame widens, instead of scaling up under a covering crop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cold realm's index card is still loading when the workspace crossing captures, and a realm without an icon URL renders an empty header slot, so the flying realm icon had nowhere to land and stayed on the tile. It now lands on a stand-in: a copy of the tile's icon where the header's will sit while the card loads, or an empty stand-in in the empty slot. The return starts from the same empty stand-in, which also keeps the icon out of the landing tile's bitmap. The header wait drops to 150 ms. The workspace chooser focused its default tile with scrollIntoView each time a tile was re-created by arriving realm info, scrolling the dashboard under a landing crossing so the return ended a row off. It now focuses without scrolling unless arrow keys moved the selection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Choreo's live geometry used an ease-out bezier while bitmap crossings used a critically damped spring, so a card opening into a new stack and the stacks reflowing beside it arrived on different curves and clocks. The spring also started from rest, adding slow frames after a click that had already waited for capture. Every geometric motion now uses motionEase (cubic-bezier 0.2, 0.8, 0.2, 1): it leaves at once and lands without overshoot. Sampled keyframes use motionEaseAt. Card reflow and card open share 0.32 s. The spring and boundaryEase/boundaryReturnEase are gone; crossings take the shared curve by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/host-motion.md is the reference for host motion; these planning notes and the sample recording describe designs since replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preview deploymentsHost Test Results 1 files ±0 1 suites ±0 1h 27m 39s ⏱️ - 4m 3s Results for commit bab0af9. ± Comparison against earlier commit 21cae06. Realm Server Test Results 1 files ± 0 281 suites +8 2h 9m 1s ⏱️ + 12m 39s Results for commit bab0af9. ± Comparison against earlier commit 21cae06. |
Opening a card over its parent slid the parent's header into the buried strip from under the top bar as a new layer, so for a moment the strip and the card's own header both showed and nothing travelled between them. The header is now one matched object in both directions, with its title and realm icon matched separately: opening, it rises from its place on the card into the strip; returning, it comes back down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closing the only card on screen, when it is not the realm's index card, put the index card in its place without motion. A new crossing handoff, 'replace', covers a different card taking the departing one's place: the faces never share a frame; the closing card recedes toward the top of its slot and is gone by 40%, and the index card surfaces there from 94%, opaque between 20% and 60%. Platter and replacement keyframes share one sampler. The Boxel button, account, search and AI now sit on their own topmost plane, each its own natural-size layer whose faces cross additively, so they never fade with the top bar between the dashboard and a workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Between the dashboard and a workspace the top bar crossfaded in place. With chrome: 'summon' its controls travel instead: the leaving set (View All, or Interact and New) rises out of the top edge and fades by 40%, and the arriving set drops in from above from 45% on the motion curve. The AI panel is captured separately and stays still; the app's own controls stay pinned on the persistent plane. Scenes and reflowing neighbour stacks had no z-index and depended on document order; they now sit on plane -1, completing an explicit ladder documented in app.css and host-motion.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
header-motion-parts marked the header with classList, but the header's class attribute is bound in its template, so any re-render dropped the class. Whether a header was transparent over its motion surface, or painted itself as well, depended on render timing, which made 'stack item with custom header color does not lose the color' fail in CI when it read the header's own background. The modifier now marks the header with a data attribute the template never rewrites. The test checks the header's text colour on the header and its background on the surface layer that paints it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stack header's colours and icon/action widths moved from a bound style attribute to Choreo's motion modifier style argument, which is applied only when the modifier installs. A header colour that arrived with its card definition, or a header becoming buried (95px -> 50px slots), never reached the header, which failed 'stack item with custom header color does not lose the color'. A cssVariables modifier now sets each variable with setProperty and re-applies on change; it never rewrites the inline style, so transforms Choreo writes during motion survive re-renders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bugs
Architecture
Dependencies
Tests
Performance (questions)
Accessibility
UI
Description and doc
Reduced-motion support, the opt-in diagnostics and the single 🤖 Drafted by Claude |
…ooks Bugs: - A crossing's update always runs its navigation. It used to return early when a newer open or close had started, so a quick close-then-open left the closed card on the stack (and an edit never finished), and two quick opens dropped the first card. - The workspace portal is set and cleared only through one setter, which ends workspace motion on every path, and WorkspaceScene ends its portal when torn down mid-flight (switching to code mode). Stack, header and sheet motion no longer stay off after a dropped portal. - Reopening the mounted chooser clears the stale selection ring, focuses the default tile and scrolls it into view unless a crossing is landing. - A search pick without a crossing passes only the tile's geometry (never the result element) and releases the origin once the dock plays, so the stack region stops measuring every render. Gates and errors: - canCross() is the one gate: it includes the modal check and whether view-transition classes are supported, and viewCard defers the body and fetches the placeholder only when it passes. - Crossing durations resolve in HostMotionService.duration(); tests can set crossingsInTests to play them. - A motion failure after the navigation ran is logged, not rethrown. Lookups and cost: - Chrome layers are found by data-motion-chrome hooks, and the workspace card by its stack item's instance (data-stack-item), never the first stack in the document. - The hidden chooser uses content-visibility: hidden. - Closing a card returns focus to the preview it lands on. - motion-dom and motion-utils are pinned to 13.3.0 through the catalog. - Motion tests wait with named deadlines instead of unbounded loops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/return-latency # Conflicts: # packages/host/app/components/operator-mode/stack-item.gts # packages/host/app/components/operator-mode/stack.gts
My recommended next actions are:
|
Background and Goal
Operator mode changed scenes by swapping DOM: a card opened by appearing, a stack closed by vanishing, and the dashboard jumped to a workspace. This PR gives every one of those navigations continuous motion without scaling live text or making layout part of playback.
Two mechanisms split the work:
glimmer-motion, vendored) tweens live geometry within a scene: stack reflow, the header handoff to the top bar, and the search sheet.A single service,
HostMotionService, decides what may move at any moment. Everything moves on one ease-out curve.docs/host-motion.mdis the architecture reference; this description summarizes it.Where to start
services/host-motion.ts: the policy.begin(scene)arms one Choreo scene at a time.cross()is the only entry for a bitmap crossing. It gates on tests, reduced motion and drag, owns the bitmap budget and the test waiter, and waits for the update to render before the new capture.canCross()exposes the gate for callers that have their own fallback.lib/bitmap-crossing.ts: the mechanism,crossfadeCardBitmap(crossing). The fields ofBitmapCrossing(from,to,update,handoff,parent,scenes,companions,chrome) describe every layer a crossing captures. Anything in the updated document is a lookup function, called after the update and given the landing face.components/operator-mode/interact-submode.gts(viewCard,close): the main callers. Read them for opening over a parent, returning into a tile, opening into a new stack withreflowStacks, and settling a right-stack card back into its tile while the other stacksmorph.lib/card-open-origin.ts: which preview a crossing starts from and returns to, including a card shown twice and a click beside a tile.lib/workspace-open-origin.ts(workspaceEntry,workspaceExit): the dashboard ↔ workspace crossing, defined once for both directions.services/operator-mode-state-service.ts: where the Choreo stack scene is armed (open, close, stack removal), andcrossToDashboard, the single gate for the dashboard button and for closing the last card.The remaining files are supporting pieces:
stack-motion.gts,search-sheet/motion.gtsandworkspace-scene.gts: the Choreo regions.bitmap-shadow.ts: shadow layers.prerendered-placeholder.ts: the stand-in body while a card flies.inspect-host-motion.ts: instrumentation.Key decisions and non-obvious mechanics
updatealways runs its navigation, even when a newer crossing has taken over, and a motion failure after it is logged rather than thrown. OnecanCross()decides everything a crossing implies, including whether to defer the card body and fetch its placeholder.reflowStacks) leaves the existing stacks live, and they reflow with Choreo beneath it.morphlayers at their natural size, so a widening stack is revealed rather than scaled.StackItem.returnTo, in memory only.motionEase(cubic-bezier 0.2, 0.8, 0.2, 1): it leaves at once, since the click has already waited for capture, and lands without overshoot. Motions that play together share a duration.glimmer-motionhas no npm release yet, so it is vendored as a tarball. It andmotion-domcarry pnpm patches, listed invendor/glimmer-motion.md; both should move upstream.?motionSpeed,?motionTraceand?motionInspectare opt-in review tools.motionInspectrecords every frame of every view-transition layer and flags reversals, spikes and long frames.🤖 Generated with Claude Code