Match chat sessions to sites by site id instead of path - #4131
Conversation
|
In the other PRs, we're deleting the AI sessions when we delete the site, which means the issue being fixed here won't matter anymore. That said, I agree with the change here conceptually, it's better to match the id but my understanding is that this requires a change in the JSONL of the sessions right? In which case, what happens to old sessions? |
📊 Performance Test ResultsComparing 2e911bd vs trunk app-size
site-editor
site-startup
Results are median values from multiple test runs. Legend: 🟢 Improvement (faster) | 🔴 Regression (slower) | ⚪ No change (<50ms diff) |
This change will still prevent existing unassigned sessions to be assigned to wrong sites. But you are correct, the main root cause of the issue will be gone with that PR
In the app we have the information in Thanks for the review! |
…by-site-id # Conflicts: # apps/cli/commands/ai/index.ts
I don't understand this, the source of truth of all sessions is the JSONL no? so how do you find the "id" of a session if it's not stored in the JSONL (in the app I mean)? |
|
The JSONL is the source of truth for the conversation (the transcript + which site the agent acts on), but the session↔site lives in app.json, keyed by the session's own id. Concrete example — one chat on a site called "Restaurant" produces two entries: {"type":"session","version":3,"id":"03239858-e5b1-4060-8123-80fc64316146","timestamp":"2026-07-10T11:10:35.481Z","cwd":"~/Studio"}
{"type":"custom","customType":"studio.site_selected","data":{"siteName":"Restaurant","sitePath":"/Users/bcotrim/Studio/restaurant"}}~/.studio/app.json: "aiSessionPlacements": {
"03239858-e5b1-4060-8123-80fc64316146": {
"kind": "site",
"siteId": "2ebcac65-b983-4e35-86e9-665041ec4a21",
"sitePath": "/Users/bcotrim/Studio/restaurant",
"siteName": "Restaurant"
}
} |
AFAIK, this is only for the legacy app no, not the agentic one? I see this app.json config as throwable personally. |
It does use it, unless I'm missing something: If you edit What would you suggest as an alternative? Now is the best time to revisit the solution and change it, I'm happy to do that in the context of this PR or as a follow-up |
When I initially implemented the agentic UI, it was not like that for sure. I think that app.json config was added later. The idea was to guess the assignment from the sessions JSONL and organize them in the sidebar. I don't mind the app.json addition, but I'm interested in two things:
|
|
I did a bit of digging, it seems the placements were introduced by #3476 |
No, I think we should remove, shared, app and cli.json files in favor of a single config.json file. Today, the CLI make use of all the app.json and shared.json config files in the |
|
Interesting that I made that change without realizing haha. And yeah we can explore moving away separately. |
I added STU-2046 so we can move the discussion there. |
## Related issues - Related to STU-1985 - Final extraction from #3998 (built on the already-landed #4131, #4133, and #4134) ## How AI was used in this PR Claude (Fable 5) extracted and adapted this change from the #3998 reference branch under close direction, preserving Shaun Andrews' original work via co-authored commits, and re-verified the result (typecheck, unit tests, light/dark visual pass in the browser UI). Code was human-reviewed throughout. ## Proposed Changes This is the capstone of the site-centric sidebar work: the sidebar becomes a flat list of sites, with no per-site chat sublists. - Clicking a site opens its most recent (non-archived) chat, or starts a new chat when the site has none. Chat history is no longer browsed from the sidebar — that moved below the composer in #4133. - Each site row carries a single activity indicator that aggregates all of the site's sessions, with priority: live-site sync > needs an answer > working > new message. This keeps long site lists calm while still surfacing what needs attention. - Row quick actions (site actions menu + start/stop status button) appear on hover/focus; drag-to-reorder from #4134 keeps working on the flat rows. - Status colors move to shared `--studio-color-status-*` tokens: the wpds palette has no vivid status colors (`--wpds-color-fg-content-success` resolves to near-black at dot size), which also fixes the session header's running dot rendering almost black. - Chrome polish: full-bleed 44px site icon with overlaid status badge in the site dropdown, flattened site icons, a visible hover tint on the sidebar create button, the floating sidebar toggle moved to the bottom-left, and aligned settings-header padding. Two product decisions to be aware of (intentional for now): - **Unread "new message" badges are in-memory only** — they reset (seed as all-seen) on every launch. - **The current chat is indicated via `aria-current` + active-row styling**, not the literal "Current chat" label from the mockups. ## Testing Instructions - Start Studio with `npm start` and enable the `Agentic UI` feature flag. - Confirm the sidebar shows a flat list of sites (no chat sublists): clicking a site with chats opens its latest chat; a site without chats opens the new-chat view. - Hover a row: the site actions menu and start/stop button appear; right side shows a green dot for running sites, a play affordance for stopped ones. - Send a prompt on one site, switch to another: the first row shows a working spinner, then a "New message" badge when the turn completes; answering a pending question clears the "Needs an answer" indicator. - Drag a site row to reorder; the order persists across restarts. - Delete a site you're currently viewing and confirm you land back on the home view. - Check the sidebar and site dropdown in **both light and dark mode**. ## Pre-merge Checklist - [x] Have you checked for TypeScript, React or other console errors? 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Shaun Andrews <191598+shaunandrews@users.noreply.github.com>
…#4130) ## Related issues - Related to STU-1985 — extracted from #3998 as part of splitting it into smaller PRs. ## How AI was used in this PR The core change was cherry-picked from #3998 (authored there with AI assistance). Claude Code ported it onto trunk, resolved merge conflicts (including the moves to id-based session↔site matching from #4131 and the signed-out gates from #4183), addressed review feedback, and wrote the unit tests for the redirect logic. All code was reviewed and verified locally (lint, typecheck, apps/ui test suite). ## Proposed Changes Studio's agentic UI always lands on the first site (in raw fetch order) at launch, regardless of where the user was working. This PR makes the app reopen on the site the user last worked with: - The dashboard remembers the last visited site in `localStorage`. - On launch, the `/` index route opens that site's newest active chat (or a new chat if it has none) — matching exactly what clicking the site in the site-centric sidebar (#4191) does. - If the remembered site no longer exists (deleted) or nothing is stored yet, it falls back to the **top site in sidebar order**, respecting the manual ordering from #4134 instead of raw fetch order. - The sidebar auto-scrolls to keep the restored site's row visible when it sits below the fold (thanks @sejas for catching this!). An earlier revision restored the exact last-visited *session*. We simplified to site-level restore to stay consistent with the site-centric sidebar model: sites are the primary navigation unit, and a site always opens its newest active chat. No launch-time cost: the index route already fetched sites and sessions before redirecting; this adds only a synchronous `localStorage` read. ## Testing Instructions 1. Launch Studio with the agentic UI and open a chat on a site other than the top one in the sidebar. 2. Quit and relaunch — you should land on that same site (its newest active chat). 3. With enough sites to make the sidebar scroll, work on a site near the bottom, relaunch — the sidebar should scroll so the selected site's row is visible. 4. Archive the chat you were on, relaunch — you should land on that site's newest remaining active chat, or a new chat if none are left. 5. Delete the site you were on, relaunch — you should land on your sidebar's top site, with no errors. 6. Drag a different site to the top of the sidebar, clear the app's localStorage, relaunch — the fallback should follow your sidebar order. ## Pre-merge Checklist - [x] Have you checked for TypeScript, React or other console errors? 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Shaun Andrews <shaun@automattic.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Related issues
How AI was used in this PR
Claude implemented the change end-to-end from a scoped brief: traced the placement/matching flow, made the code changes, and extended the unit tests. I reviewed the diff via a guided code tour and steered the follow-ups (shared matching helper; recording the site id in session events).
Proposed Changes
Chat sessions are bound to local sites via placements in
~/.studio/app.json, which have always stored a validatedsiteId— but the actual matching (sidebar grouping, empty-draft reuse, session header/preview owner lookup, and the main process' owner-server resolution) compared filesystem paths. Since site paths are name-derived and get reused, deleting a site and creating a new one at the same folder made the new site silently inherit the dead site's chat history.This PR switches matching to the site id, consistent with everything else per-site in
app.json(e.g.siteMetadata). All matching now routes through one shared, dependency-free helper (packages/common/ai/sessions/owner-site.ts) used by the renderer, the router, and the main process:ownerSitePath/ownerSiteNameare kept for display, and path matching remains as a defensive fallback for placements written beforesiteIdexisted (placements are read from user-editable JSON without schema validation), so no existing chats are dropped.Execution target (session JSONL): the same path-reuse hazard existed inside the session event log —
studio.site_selectedevents identified the agent's working site by path only, so prompting an old session whose folder now belongs to a recreated site would act on the new site's directory. Now every writer records an optionalsiteIdonsite_selectedevents, and turn dispatch resolves the working site by id from the CLI registry (refreshing a moved site's current name/path) with path fallback for events written before the field existed. Additive schema change — no migration: old events simply lack the field and keep resolving by path, exactly as today.Testing Instructions
Unit tests cover the matching semantics and event recording:
npm test -- packages/common/ai/sessions/tests apps/ui/src/components/site-list apps/cli/commands/ai/tests apps/cli/ai/tests/tools.test.tsManual (agentic UI,
apps/ui):siteIdfrom one entry inaiSessionPlacementsin~/.studio/app.json— that chat should still group under its site via the path.~/.studio/sessions/— thestudio.site_selectedentry should includesiteId. Rename the site's folder (site settings), prompt again — the turn should resolve the site's new path via the id.Pre-merge Checklist
🤖 Generated with Claude Code