From de8c4ef5c7a679c9d4aecf046c7cb899a8a453b2 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Mon, 10 Aug 2026 15:38:10 +0700 Subject: [PATCH 1/2] feat(app): add a session actions menu to both session lists 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) --- CHANGELOG.md | 13 + DIVERGENCES.md | 2 + packages/app/src/components/app-sidebar.tsx | 133 ++++++++-- .../src/components/session-actions-menu.tsx | 196 +++++++++++++++ packages/app/src/pages/home-model.tsx | 130 +++++++++- packages/app/src/pages/home.tsx | 152 +++++++----- .../session/timeline/message-timeline.tsx | 231 +++--------------- .../app/src/utils/session-share-request.ts | 23 ++ 8 files changed, 595 insertions(+), 285 deletions(-) create mode 100644 packages/app/src/components/session-actions-menu.tsx create mode 100644 packages/app/src/utils/session-share-request.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 64fe678102..c73e49c09b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,19 @@ file is the human-readable summary of what changed and why. ### Desktop / Web UI +- **Session rows have a ⋮ menu** — every session in the sidebar's Recents and on + the **View all** list now carries a vertical three-dot button at its right + edge, appearing on hover. It opens the same menu the ⋯ button beside a session + title opens: **Rename**, **Share…** (only where sharing is enabled), + **Archive**, **Delete…**. Renaming edits the row's label in place — Enter + saves, Esc cancels — instead of taking you into the session first; deleting + still asks for confirmation and takes the session's children with it. Sharing + from a row opens the session and its share popover, because the share state + is not carried by the list. Previously the only way to rename, archive or + delete was to open the session and use the menu next to its title. + +### Desktop / Web UI + - **The sidebar collapses, and Search opens the recents list** — two buttons join the titlebar. The leftmost one hides and restores the sidebar, so a session can have the full window width; it remembers the choice for next launch, and it diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 1f499a1efa..725ee85a54 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -100,6 +100,8 @@ from the squash message first, or use a plain merge. | `packages/app/src/components/titlebar.tsx` | Two changes, both to the **v2 titlebar branch only** (`useV2Titlebar()`); the legacy branch below it is untouched. (1) The Home button (and its `home.toggle` command, `mod+b`) opens a new session instead of toggling between `/` and the last active tab: `tabs.toggleHome(...)` becomes a `goHome` that selects an already-open draft if there is one and otherwise falls through to the existing `openNewTab()` project-resolution chain. Its pressed / `aria-pressed` state keys on `layout.route().type === "draft"` rather than `"home"`. The icon, label (`home.title`) and keybind are unchanged, as is `tabs.toggleHome` itself, which is simply no longer called. (2) `` is dropped from that row and the component gains an `export`, so the sidebar can render the DEV/BETA badge at its bottom instead; `Titlebar` keeps its `debugTools` prop because the legacy branch still renders the badge inline. (3) Two buttons added to the row: a sidebar collapse toggle (`sidebar-right` icon, `data-action="titlebar-sidebar-toggle"`, `pressed` while open) as its **first** child, ahead of the `windows() || linux()` `WindowsAppMenu`; and a Search button (`magnifying-glass`, `data-action="titlebar-search"`) immediately after Home, navigating to `/?focus=search`. The toggle reads the fork-only `useAppSidebar()` and is wrapped in ``, which is what keeps it out of the legacy shell. Labels reuse the existing `command.sidebar.toggle` and `home.sessions.search.placeholder` keys — no new i18n. | The home screen is a new session in this fork, so Home has to go there; toggling into the recent-sessions list contradicted that. Reuse-before-create keeps repeated presses from stacking up draft tabs — unlike the New session button, which is expected to always make a new one. Moving the badge lets Home lead the titlebar (measured: x 137 → 92 on macOS, the traffic-light inset). | Keep our `goHome` and the removed indicator. If upstream reworks the titlebar, re-apply both rather than restoring `toggleHome` or the inline badge. If upstream deletes `tabs.toggleHome`, nothing here breaks. Do not remove `Titlebar`'s `debugTools` prop — the legacy titlebar still needs it. Keep `useAppSidebar()` optional-by-design: `useV2Titlebar()` is just `newLayoutDesigns()`, so this titlebar also renders inside the legacy shell, which has no provider — swapping it for `createSimpleContext` would throw there (verified: with the new layout off, the legacy shell renders its own rail, no toggle, and no exception). | | `packages/app/src/pages/layout-new.tsx` (again) | Mounts the fork-only ``, which on desktop redirects one time per boot to a new-session draft (reusing an existing empty draft rather than stacking one up per launch). It pins its decision to the pathname the window came up on and disarms as soon as anything navigates, so it can never hijack a later visit to `/` — the sidebar's "View all" links there. Desktop-only: in the browser there is no "launch" distinct from "navigate to /", so redirecting would make the recents screen unreachable by URL and would break two `packages/app/e2e` specs that `goto("/")`. | Launching should put you in front of a prompt, the way Claude Desktop does, rather than on a list. This supersedes the desktop's restore-last-screen behaviour (`getLastActiveUrl`, `packages/desktop/src/renderer/index.tsx`); the previous session stays one click away in Recents and its tab is still restored. | Keep the mount. If upstream restructures this layout, re-add it. Do not "simplify" the boot-window guard into a plain `location.pathname === "/"` check — that is the bug it exists to prevent. | | `packages/app/src/pages/layout-new.tsx`, `packages/app/src/pages/home.tsx`, `packages/app/src/i18n/{en,es}.ts` | **Persistent app sidebar.** Upstream's project column is an `