Repository navigation
Every page shares one frame: the Trace Console's top bar, palette and theme - #669
Merged
Merged
Conversation
… theme The gallery, sessions list, replays, agent page and error pages used their own older header and palette. They now use the frame in src/ui/chrome, with the Trace Console's top bar, the palette's short names and quiet controls. The theme is one shared setting, so the top bar's button and the content agree. The top bar no longer links to the gallery or the sessions list. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
danbarua
added a commit
that referenced
this pull request
Oct 12, 2026
…kspace's folder (#670) ## Problem The agent's chat was a page of its own (`/agent`), reached from a link in the top bar, and its sessions opened in one folder the dev stack names (`.labkit-dev/labkit-effect/workspace`), whatever workspace the Trace Console showed. Its `/new` command reloaded the page. A session the address named that the agent could not reopen disabled the composer, so `/new` could not be sent. The gallery's failed-turn tiles logged "Maximum update depth exceeded". ## Change **The chat in the Trace Console** - **Right sidebar.** In development, inside a workspace, the chat sits at the right of the page. A rail at the right edge stays when the chat is minimised; its chat button expands or minimises it. The top bar's `agent` link is removed. - **Session lifetime.** - The session connects the first time the chat is expanded. - It stays connected while the chat is minimised; only its view is not drawn. - The address names it (`?session=`), so a reload or a link reopens it. - **Resizing.** The border at the chat's left edge moves by drag, Left and Right (16 px), Home (320 px) and End (60% of the window). A double-click returns it to 400 px. The browser keeps the width. - **Motion.** The chat slides open and shut over 220 ms, as the left sidebar does, and follows the pointer without the transition while its border is dragged. **Sessions open in the workspace's folder** - **Resolver.** `readWorkspaceDirectories` (`src/server/workspace-directories.ts`) reads `workspaces.local.json` and checks each entry. `openRepositories` opens the repository in each folder it returns. The API and the dev server both use it. - **Dev-server endpoint.** The dev server answers `GET /agent-workspace/<slug>`: - with `{ "cwd": "<folder>" }` for a workspace the file maps; - with `{ "cwd": null, "reason": "…" }` for one it does not map. It reads the file on each request, so an edit needs no restart. - **The session's `cwd`.** The chat sends that folder as the session's `cwd`. labkit-effect's ACP host roots each session's tools in its `cwd`, and filters `session/list` by it, so a workspace's sessions are its own. - **Unmapped workspace.** A workspace with no folder has no chat. The rail's button is disabled, and its tooltip gives the reason. - **Leaving a workspace.** On leaving a workspace, the session the address names is not reopened in the next one, and it leaves the address. Going home drops it too. **The agent's session, shared** - **Components.** `useAgentSession` and `AgentChat` (`src/ui/agent/`) hold what the `/agent` page did. A page passes the session to reopen and is told the id of each session started. The `/agent` page uses them too, keeps its own address scheme, and still opens its sessions in `VITE_LABKIT_ACP_CWD`. - **`/new`.** Sending `/new` now connects a new session in place, without reloading the page. - **A failed open or reopen.** If opening or reopening a session fails, the composer takes input again, and the error ends with "Send /new to start a new session." **A render loop in `@labkit/ui`.** `currentActivity` makes a new object on each call. `useLingering` stored it from an effect that depends on it, so each render set state and drew again, until React stopped it with "Maximum update depth exceeded". An activity equal to the one held now keeps it (`sameActivity`). The loop affected a failed turn and a running one: the gallery's failed-turn tiles, and a chat whose session failed to reopen. ## Controls - **Narrow windows.** In a window 860 px wide or narrower, the chat is not a drawer: it takes its width from the page beside it. The left sidebar's drawer is unchanged. - **A new session on each visit.** Once the chat has been expanded on a page, each workspace visited opens its own session, even while the chat is minimised. Going home and back opens another session in the same workspace. With the real agent, each one is a saved session. A session could wait to connect until the chat is expanded in each workspace; that is a small change if wanted. - **Where the folder comes from.** The browser learns the folder from the dev server, and sends it to the agent as ACP's `session/new` requires. The server does not yet check that a session's `cwd` is the workspace's folder. That check is where the design is heading, and it is left for later. - **The `/agent` page, the sessions list and session replays** still use `VITE_LABKIT_ACP_CWD`. Nothing links to them. - **A closed connection's DELETE can be answered 404.** Closing a connection ends its event stream and then sends `DELETE /acp`. The ACP server (`@agentclientprotocol/sdk` 1.5.0) forgets a connection whose stream has ended (`trackConnectionClose`), so the DELETE can find nothing, and the browser logs the 404. The connection is closed either way. The browser tests excuse one such log line for each DELETE answered 404; any other 404 still fails a test. - **Seen in a browser.** On :8850, a session opened in bonsai-2026 listed `~/Code/PyCharm/bonsai-2026`'s own root. I did not look at the layout myself. ## Verification - `bun run check`: exit 0, 1548 pass, 0 fail. - `bun run e2e` with `E2E_PORT_API=9019 E2E_PORT_DEV=8970 E2E_PORT_BUILT=8971`: 161 passed, 7 skipped (the dev-only tests, in the built bundle). - **Unit tests:** - `tests/workspace-directories.test.ts`: an absolute folder and a `~/` folder are mapped. A missing file, a file that is not a JSON object, and an entry that is not a string or not absolute are skipped, each with its log line. - `tests/repositories.test.ts`: a folder that holds a repository is opened, and one that does not is skipped with a warning. - `packages/ui/tests/activity.test.tsx`: two equal activities, including a failed turn's two new objects, are the same activity; another kind or tool label is not. - **e2e, dev server** (the built bundle asserts the rail is absent): - The rail is at the page's right edge in a workspace, and absent at `/app/`. - Expanding the chat sends `session/new` with `alpha`'s mapped folder as `cwd`, and names the session in the address. - Minimising and expanding again keeps the session. Opening another record keeps it in the address, and a reload reopens it. - In `beta`, which the e2e mapping file does not map, the button is disabled, and its tooltip says the file maps no folder to `beta`. Going home dropped the session from the address. - With `?session=no-such-session`, the chat says the reopen failed and suggests `/new`. The composer is enabled, and `/new` starts a session that the address then names. - `/agent` starts a session and takes its address, and `/new` starts another without a reload. - Dragging the chat's border 100 px left widens it from 400 to 500 px, and a reload keeps 500. Right gives 484, Home 320, End 768 (60% of 1280), and a double-click 400. - The gallery draws every tile without an error. - **Before and after the render-loop fix,** with 4 workers: - The stale-session test failed 8 of 8 runs before the fix. - A test that only opens the gallery failed 4 of 12 runs on `main`. - After the fix, the stale-session and gallery tests passed 16 of 16 runs. - The loop's stack, captured from React's warning, pointed at `useLingering`'s effect. ## Linked issues None. This follows #669. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
The gallery, the sessions list, session replays, the agent page and the error pages drew a header and a palette from before the Trace Console's. Their header (
Bar.tsx) had its own old dark palette, bordered buttons and links to the gallery and the sessions list. The Trace Console's top bar was separate CSS under.trace, so no other page could use it, and the right-hand sidebar planned next would have copied it again.Change
src/ui/chrome/Frame.tsxandchrome.csshold what every page shares:--panel,--line,--accentand the rest, as aliases of the--lk-*tokens);.mono,.dimand.faint;.page, the sessions table,.load-error, and quiet buttons in the top bar.These rules moved out of
trace.cssandstyles.css, with their selectors changed from.traceto.lk-frame. The Trace Console's root now has.lk-root lk-frame trace.TopBarandFrame.TopBardraws the LabKit mark and name, which are one link (or a button, whenonHomeis given), then the page's own controls, thenendcontrols at the right, then the theme button.Frameis the page root with aTopBar. The Trace Console, the gallery, the sessions list, replays, the agent page, the load-error page and the not-found page all use them.Bar.tsxis deleted.useSavedThemeis now one shared setting (useSyncExternalStore), not state per component. Before, a page that drew content in the theme and a toggle in the bar each kept their own copy.agentlink stays until the chat moves into the sidebar.styles.csskeeps only the page reset and the graph canvas rules. The old:rootpalette, the#barrules and the global button and select styles are gone.docs/architecture.mddescribes the frame.Controls
live · hosttext and sidebar toggle already went in earlier PRs.maintoo: 4 of 12 runs of a test that only opens/app/galleryfailed there, and I did not fix it. The new top-bar test uses the sessions page, which does not do this..topbar buttonrule.Verification
bun run check: exit 0, 1544 pass, 0 fail.bun run e2ewithE2E_PORT_API=9019 E2E_PORT_DEV=8970 E2E_PORT_BUILT=8971: 153 passed, 1 skipped (the dev-only test, in the built bundle).Linked issues
None. This prepares the right-hand chat sidebar.
🤖 Generated with Claude Code