Repository navigation
feat(session-ui): quiet divider for automatic compaction with background compaction on - #58996
Conversation
There was a problem hiding this comment.
Looks good to me. I took screenshots of the CompactionInProgress story on the base and on this branch (attached), and ran session-compaction.spec.ts and session-timeline.spec.ts with --repeat-each=3: 45/45 passed. tsc -b in packages/session-ui is clean. Manual and failed compactions keep the full record, and cancelled or interrupted ones count as failed, so they keep it too.
Public API: unchanged. The only addition is the UI string ui.messagePart.compaction.done.
One small accessibility point is in an inline comment: the running state no longer reaches screen readers.
| data-state={props.running ? "running" : "done"} | ||
| class="flex h-8 w-full items-center gap-3 text-v2-text-text-faint" | ||
| disabled={!expandable()} | ||
| aria-busy={props.running} |
There was a problem hiding this comment.
The old running row was wrapped in role="status", so screen readers announced "Compacting". This divider is a disabled button with only aria-busy and no live region, so assistive tech no longer hears that a compaction started or finished. Wrapping the divider (or the label) in role="status" would bring that back without changing the layout.
There was a problem hiding this comment.
The new commit updates the e2e compaction expectations to match the quiet divider, and the change looks right. The component code is unchanged, so my earlier check (story screenshots on base vs PR, component specs 45/45 over 3 repeats) still covers it. I did not run the app e2e suite.
The earlier point about the missing role="status" for the running divider is still open. This commit drops the e2e assertion on that status role rather than restoring it.
Public API: unchanged.
There was a problem hiding this comment.
The new commits change the entrance timing and aria-controls, and both look right. The inside-grace path restarts the fade and shows only "Compacted". I ran session-compaction.spec.ts with --repeat-each=5 (40/40, including the new inside-grace case), and tsc -b in packages/session-ui is clean.
The earlier point about the missing live region for the running divider is still open.
Public API: unchanged.
…t the ending to Compacted
There was a problem hiding this comment.
The new commits work as described. Only the ending changes, and the stem and the divider's box hold still (the box-stability test now checks the stem too). I ran session-compaction.spec.ts with --repeat-each=3 (27/27), and tsc -b in packages/session-ui is clean.
The earlier point about the running divider having no live region is still open. The new aria-label gives it a name but doesn't announce changes.
The new glow-timing test pins animation tuning; see the inline comment.
Public API: unchanged.
| expect(ended).toEqual({ enter: "now", animation: "session-compaction-enter", running: "0", done: "1" }) | ||
| }) | ||
|
|
||
| story("warms the label only once the gathering light reaches it", async ({ mount }) => { |
There was a problem hiding this comment.
This test checks exact keyframe opacities at fixed points of the 3.2 s cycle (30/48/57/90%, glint ≈ 0.75). Any easing or timing tweak to the light will break it, even though nothing a user would notice has changed. It also runs four at() calls through Promise.all that all pause and seek the same shared animations, which only works because page evaluations run one at a time. I'd drop it, or cut it down to a single check that the glint is off at the start of the cycle and on at some point after it.
There was a problem hiding this comment.
The new commit only uses the quiet divider when experimental.background_compaction is on, and the gate works. Manual and failed compactions, and any compaction with the flag off, keep the full record. I ran session-compaction.spec.ts and session-timeline.spec.ts (19/19, including the new flag-off cases). tsc -b in packages/session-ui and tsc --noEmit in packages/app are clean. The client's reading of the flag matches the server's Config.latest(..., "experimental").
The two earlier points (no live region for the running divider, and the keyframe-timing test) are still open.
New: the divider flashes on load while the config is still loading; see the inline comment.
Public API: unchanged. backgroundCompaction is a new optional, internal prop on session-ui's DataProvider, and the config key already existed.
| ) | ||
|
|
||
| // Matches the server: the highest-priority document that sets `experimental` decides. | ||
| const backgroundCompaction = () => |
There was a problem hiding this comment.
While the config query is still loading, config.data is undefined, so this is false. For a user with the flag on, a timeline that already has compactions first renders the full record ("Session compaction started", the whole summary, the separator) and then collapses to the quiet divider once the config arrives. That's a visible flash and a layout shift on every load. Treating "still loading" separately from "off" would avoid it. For example, keep the timeline's compaction rows from rendering until the query has resolved, or reuse config the app has already loaded.


What it does
Automatic compaction used to take over the timeline: a "Session compaction started" divider, the summary streaming in as a full reply, a Compacting tool row, then a second divider. Most of that is housekeeping the reader never asked for. With
experimental.background_compactionon (the flag that makes compaction run in the background), an automatic compaction is now one faint divider that reads Compacting while it runs and Compacted when it's done, and clicking it opens the summary. With the flag off, automatic compaction looks exactly as it does today. Manual/compactand failed compactions always look the same as before.How it works
api.config.get, refetched onconfig.updated) and passesbackgroundCompactionto session-ui'sDataProvider. Like the server, the highest-priority document that setsexperimentaldecides.SessionCompactionMessagesends automatic, non-failed compactions to a newQuietCompactiononly when that flag is on. Everything else keeps the existing markup.startedandendedcan still land in separate flushes), the fade restarts and only "Compacted" fades in, with no crossfade. One that was already running (over 400 ms old) or already finished (over 1 s old) when the timeline loads shows up without any animation.ui.messagePart.compaction.done("Compacted"), English only.Risk
UI only (session-ui, plus one config query in the app's session provider). With the flag off, nothing changes. The summary no longer streams while an automatic compaction runs. It's still there to open once the compaction finishes. If the routing were wrong, a manual or failed compaction, or any compaction with the flag off, would lose its full record; component tests cover all three, and an e2e test covers the flag on end to end.
Proof
Before → after, a running automatic compaction:
Finished and opened:
A 20 s compaction at real speed. Just before it ends the page drops to 0.2× speed (CDP
Animation.setPlaybackRate, marked on screen) so the ending's roll is visible:compaction-20s-then-slow.mp4
One that ends inside the 400 ms grace, and an instant one: both just fade in as Compacted.
compaction-inside-grace.mp4
compaction-instant.mp4
bun run test:components -- component-tests/session-compaction.spec.ts component-tests/session-timeline.spec.ts --repeat-each=5(packages/session-ui): 75 passed. The new spec covers running, click-to-open, instant fade, manual, failed, a stable box from running to done, and reduced motion.--repeat-each=3: 57 passed.bun run test:e2e -- e2e/regression/timeline-stream.spec.ts -g compaction --repeat-each=2(packages/app): 8 passed. The existing flag-off tests are back to their original assertions, and a new one turns the flag on through the mock config and checks the quiet divider runs, finishes as Compacted and opens the summary.timeline-stream.spec.tscompaction group: the failed and interrupted cases now expect the quiet divider (no partial summary) while an automatic compaction runs; 3/3 repeats pass.bun typecheck(packages/session-ui and packages/app),bun script/lint-changed.ts origin/dev: clean.