Repository navigation
feat(app): add a session actions menu to both session lists - #179
Merged
Merged
Conversation
Every session row in the sidebar's Recents and on the home "View all" list gains a hover-revealed vertical three-dot button opening the same menu the session header has: Rename, Share, Archive, Delete. Renaming edits the row label in place rather than making you open the session first. The menu is reused, not reimplemented. Both renderings -- the legacy DropdownMenu and the v2 MenuV2 -- plus DialogDeleteSession move verbatim out of message-timeline.tsx into components/session-actions-menu.tsx, which the header now renders too. Deferring Rename and Share to the menu's onCloseAutoFocus becomes the component's job, since every caller needs it: the closing menu pulls focus back to its trigger and would otherwise blur the inline editor the moment it opened. archiveSession/renameSession/deleteSession move into createHomeModel so the sidebar and the home screen share one implementation. Two fixes this surfaced: - The local sidecar's /event stream publishes no session lifecycle events (verified against a running app: neither create nor archive emits anything), so the recents index only ever corrected itself on a refetch and an archived row stayed on screen. Actions now invalidate the index query. - `share` exists only on the directory-scoped sync store; the server-level store has no such key, so gating on it offered Share on a server with sharing disabled, which the session page then refused to honour. The rows now read ensureDirSyncContext(session.directory), the same store the session page uses. Sharing from a row opens the session and asks it to open its popover -- the share URL is not carried by the list, because SessionV2Info has no share field. That bridge is unverified at runtime: sharing is disabled in the environment this was tested against, so the item is correctly hidden in both menus there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
force-pushed
the
session-row-menu
branch
from
August 10, 2026 08:47
c87a64a to
de8c4ef
Compare
This was referenced Aug 11, 2026
quickbeard
pushed a commit
that referenced
this pull request
Aug 11, 2026
#173, #175 and #179 shipped with no tests; #181 and #183 already carried their own, so this fills the gap left by the other three. Unit (test-browser, since home-model reaches kobalte through the directory picker and will not import under the server condition): - buildHomeSessionRecords: directory filtering, pathKey normalisation so a Windows path and a trailing slash are one directory, sort by last update with a created-time fallback, dedupe keeping the fresher copy, sandbox and projectID resolution, and dropping a session no project claims. - descendantIDs: the subtree a delete evicts, including cycle safety. Exported for this, alongside buildHomeSessionRecords which was already exported. - session-share-request: the row-to-timeline share handoff consumes once, and a request meant for another session is not swallowed. E2E: - sidebar-persistence: the column survives opening a session, orders recents newest first, opens the session a row names, and View all and New session both work from inside a session. - sidebar-toggle-search: the titlebar toggle hides and restores the column, survives a reload and a route change, and Search lands on the recents list with its field focused and the ?focus=search param cleared. - session-actions-menu: the four actions on both lists, Share hidden when the server disables sharing, rename in place without opening the session, cancel sending nothing, and delete asking first. Two defects turned up while writing these, both left unfixed and documented where they bite: - Clicking a Recents row opens the session but never marks the row as open; the marker only appears once the tab list is read back on a fresh mount. Arriving by URL marks it immediately, so the state is right and the click path fails to re-run the memo. - The first activation of a row's actions menu is swallowed. Four clicks give closed, open, closed, open, and focus + Enter is swallowed the same way, so a user's first click on the dots lands on nothing too. 623 unit, 45 browser-condition unit and all 110 e2e regression tests pass; tsgo -b and the e2e project both typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
added a commit
that referenced
this pull request
Aug 11, 2026
* test(app): cover the sidebar, titlebar and session-row menu PRs #173, #175 and #179 shipped with no tests; #181 and #183 already carried their own, so this fills the gap left by the other three. Unit (test-browser, since home-model reaches kobalte through the directory picker and will not import under the server condition): - buildHomeSessionRecords: directory filtering, pathKey normalisation so a Windows path and a trailing slash are one directory, sort by last update with a created-time fallback, dedupe keeping the fresher copy, sandbox and projectID resolution, and dropping a session no project claims. - descendantIDs: the subtree a delete evicts, including cycle safety. Exported for this, alongside buildHomeSessionRecords which was already exported. - session-share-request: the row-to-timeline share handoff consumes once, and a request meant for another session is not swallowed. E2E: - sidebar-persistence: the column survives opening a session, orders recents newest first, opens the session a row names, and View all and New session both work from inside a session. - sidebar-toggle-search: the titlebar toggle hides and restores the column, survives a reload and a route change, and Search lands on the recents list with its field focused and the ?focus=search param cleared. - session-actions-menu: the four actions on both lists, Share hidden when the server disables sharing, rename in place without opening the session, cancel sending nothing, and delete asking first. Two defects turned up while writing these, both left unfixed and documented where they bite: - Clicking a Recents row opens the session but never marks the row as open; the marker only appears once the tab list is read back on a fresh mount. Arriving by URL marks it immediately, so the state is right and the click path fails to re-run the memo. - The first activation of a row's actions menu is swallowed. Four clicks give closed, open, closed, open, and focus + Enter is swallowed the same way, so a user's first click on the dots lands on nothing too. 623 unit, 45 browser-condition unit and all 110 e2e regression tests pass; tsgo -b and the e2e project both typecheck. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(app): drop the workarounds #185 makes unnecessary Both specs carried a detour around a defect, each with a comment explaining why. #185 fixes both, so the detours are now dead weight and the comments are wrong. - session-actions-menu: openRowMenu clicked a second time when the first was swallowed. One click now, and the helper no longer needs the page. - sidebar-persistence: "marks the row whose session is already open" reloaded before asserting data-selected. It asserts on the click that opened the session. The hover in openRowMenu stays. That one is not a workaround: the trigger is pointer-events-none until its row is hovered, and Playwright hit-tests before it moves the mouse. Checked both ways rather than assumed. On #185's branch the three specs pass 20/20; on main without it, both edited tests fail — the menu never reports aria-expanded=true, and no row is ever marked selected. So this must merge after #185. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Quickbeard <quickbeard@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <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.
Every session row — in the sidebar's Recents and on the home View all list — gains a hover-revealed vertical three-dot button at its right edge. It opens the same menu the ⋯ button beside a session title opens: Rename, Share…, Archive, Delete….
Previously the only way to rename, archive or delete a session was to open it first and use the menu next to its title, which is exactly backwards when you are clearing out sessions you do not want to open.
The menu is reused, not reimplemented
Both renderings — the legacy
DropdownMenuand the v2MenuV2— plus theDialogDeleteSessionclosure move verbatim out ofmessage-timeline.tsxinto the fork-onlycomponents/session-actions-menu.tsx. The session header renders that component too, so there is one definition of the four items instead of three copies free to drift.One subtlety worth not undoing: the component owns the deferred Rename and Share. Neither can run on
onSelect— the menu is still closing and pulls focus back to its trigger, which blurs the inline editor the instant it opens. That deferral used to be the header'spendingRename/pendingSharestore fields; it now lives in the component, so every caller inherits it.Rename edits the row's label in place using the existing
createInlineEditorController(the same primitive the legacy sidebar's workspace rename uses) — Enter saves, Esc cancels.archiveSession/renameSession/deleteSessionmove intocreateHomeModel()so the sidebar and the home screen run one implementation;home.tsx's duplicatearchiveSessionis gone.Two bugs this surfaced
The local sidecar publishes no session lifecycle events. Watching
/eventwhile creating and archiving sessions against a running app produces nothing butserver.connected. The recents index is only ever corrected by a refetch, so an archived row stayed on screen looking broken. Actions now invalidate the index query. This is pre-existing — the session header's Archive has the same staleness — but it is what made the new menu look non-functional.shareis scope-sensitive. It exists only on the directory-scoped sync store; the server-level store has no such key at all. Gating on the server-level store offered Share on a server with sharing disabled, and the session page then refused to open the popover. The rows now readensureDirSyncContext(session.directory)— the same store the session page reads throughuseSync— and the row menu matches the header exactly.Share from a row
Share opens the session and asks it to open its popover, via a small pending-signal bridge (
utils/session-share-request.ts). The popover cannot live on a row: it readsshare.urloff the fully synced session, and the recents index drops it becauseSessionV2Infohas nosharefield — making it work in place would be a Schema/Protocol/Server change.This bridge is unverified at runtime. The environment it was tested against has
share: "disabled"both globally and per-directory, so the item is correctly hidden in both menus there and the path could not be exercised.Verification
Driven against a running desktop app over CDP:
archivedserver-sidebun turbo typecheckclean across 32 packages.bun testinpackages/app: 624 pass / 0 fail. Lint clean on touched files.Ten throwaway sessions were created for testing and all deleted; a final audit confirmed no real session was archived or deleted.
Fork bookkeeping
DIVERGENCES.mdgains rows for the two upstream files touched (message-timeline.tsx,home.tsx). The four fork-only files are not tracked there.CHANGELOG.mdhas an Unreleased entry.🤖 Generated with Claude Code