feat(sessions): pin folders to the top of the session drawer#21
Open
goyamegh wants to merge 1 commit into
Open
feat(sessions): pin folders to the top of the session drawer#21goyamegh wants to merge 1 commit into
goyamegh wants to merge 1 commit into
Conversation
Folders (cwd groups) in the session drawer can now be pinned via a small pin button in the folder header. Pinned folders sort to the top of the drawer in pin order and stay there across session switches, filter changes, and reloads — so the drawer behaves like a stable spatial map instead of a recency-ordered log. Behaviour: - Each folder header gets a pin/unpin icon button. State is persisted per-device via the existing pi-web-session-ui-state.json round-trip (new `pinnedFolders: string[]` field). - Pinned folders render above unpinned folders in pin order. Unpinned folders keep the existing recency-based order. - A pinned folder whose sessions are hidden by an active marker-color or search filter still keeps its header (and unpin affordance) visible. - A pinned folder with no sessions left renders a small placeholder row so the user can still unpin it (related to ashwin-pc#13). Closes ashwin-pc#18. Co-authored-by: pi <pi@example.com> Signed-off-by: goyamegh <goyamegh@amazon.com>
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds support for pinning session folders in the session drawer, persisting pinned folder order in session UI state across reloads and filters.
Changes:
- Introduces
pinnedFoldersto session UI state (client + server) with normalization/deduping. - Updates session drawer rendering to keep pinned folders at the top (in pin order) and adds a folder pin/unpin button.
- Adds API and E2E coverage for persisting and rendering pinned folders (including filter interactions).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/e2e/session-bar.spec.ts | Adds E2E coverage for pinning folders and verifying ordering + persistence. |
| tests/api.test.ts | Extends API tests to assert pinnedFolders normalization, persistence, and resets state. |
| src/styles/sessions.css | Styles pin button + pinned folder visual treatment and separation. |
| src/sessions/sessionDrawer.ts | Implements pinned folder state, UI affordance, ordering rules, and ghost rendering for empty pinned folders. |
| src/app/types.ts | Adds pinnedFolders to app/session UI state types, defaults, localStorage read, and normalization. |
| server/sessionUiState.ts | Adds pinnedFolders to server-side session UI state + patch normalization. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+350
to
+371
| .sessionFolderPinButton { | ||
| flex: 0 0 auto; | ||
| width: 24px; | ||
| min-width: 24px; | ||
| height: 24px; | ||
| min-height: 24px; | ||
| border: none; | ||
| border-radius: 6px; | ||
| background: transparent; | ||
| color: color-mix(in srgb, var(--muted) 70%, transparent); | ||
| opacity: 0.45; | ||
| transition: opacity 0.12s ease, color 0.12s ease, background 0.12s ease; | ||
| } | ||
| .sessionFolderHeader:hover .sessionFolderPinButton { | ||
| opacity: 1; | ||
| } | ||
| .sessionFolderPinButton:hover { | ||
| border: none; | ||
| background: var(--panel-2); | ||
| color: var(--accent); | ||
| opacity: 1; | ||
| } |
Comment on lines
357
to
363
| function applySessionUiState(value: unknown) { | ||
| const next = normalizeSessionUiState(value); | ||
| state.pinnedSessions = next.pinnedSessions; | ||
| state.pinnedFolders = next.pinnedFolders; | ||
| state.sessionMarkers = next.sessionMarkers; | ||
| state.selectedMarkerColor = next.selectedMarkerColor; | ||
| if (selectedSessionRowTool !== "pin") selectedSessionRowTool = next.selectedMarkerColor; |
Comment on lines
367
to
371
| renderSessionBar(); | ||
| updateCurrentSessionPinButton(); | ||
| renderCurrentSessionBucketButton(); | ||
| if (state.pinnedSessions.length > 0 && cachedSessions.length === 0) refreshSessions().catch(() => undefined); | ||
| if ((state.pinnedSessions.length > 0 || state.pinnedFolders.length > 0) && cachedSessions.length === 0) refreshSessions().catch(() => undefined); | ||
| } |
Comment on lines
+293
to
+297
| // Pin folder-c. It should jump to the top and stay there. | ||
| const folderCGroup = page.locator(".sessionFolderGroup", { has: page.locator(".sessionFolderName", { hasText: "folder-c" }) }); | ||
| await folderCGroup.locator(".sessionFolderPinButton").click(); | ||
| await expect(folderCGroup).toHaveClass(/\bpinned\b/); | ||
| await expect(page.locator(".sessionFolderName")).toContainText(["folder-c", "folder-a", "folder-b"]); |
Comment on lines
+304
to
+306
| // Persisted server-side. | ||
| const stored = await (await page.request.get("/api/session-ui-state")).json(); | ||
| expect(stored.sessionUiState.pinnedFolders).toEqual(["/workspace/folder-c", "/workspace/folder-b"]); |
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.
Summary
Lets users pin folders (cwd groups) in the session drawer so they stay at the top of the drawer in a stable order, instead of reshuffling on every session switch. Fixes #18.
The drawer currently orders folders implicitly by the most-recent activity inside them — switching to a session in folder X bubbles folder X up; switching to folder Y reshuffles again. For users who actively rotate between many folders, that means the drawer is in motion almost constantly and muscle memory for finding a folder never has a chance to form.
This PR adds an explicit pin region at the top, mirroring the pinned-sessions behaviour one level up the hierarchy.
Behaviour
pi-web-session-ui-state.jsonmechanism (newpinnedFolders: string[]field onSessionUiState).Implementation
server/sessionUiState.ts&src/app/types.ts— extendSessionUiStatewithpinnedFolders: string[], add normalizer / patch handler / legacylocalStoragereader. The existingPATCH /api/session-ui-stateendpoint accepts the new field via the existing patch shape, no new route needed.src/sessions/sessionDrawer.ts—applySessionUiStateandrefreshSessionUiStatenow carrypinnedFoldersthrough. AddedpinFolder/unpinFolder/toggleFolderPinhelpers. The grouping pass inrenderSessionListnow partitions cwds into a pinned region (in pin order, ghost-rendered if no sessions remain) and an unpinned region (existing recency-based order), then concatenates.src/styles/sessions.css— small styles for the new.sessionFolderPinButtonand a divider between the pinned and unpinned regions.Tests
tests/api.test.tsto coverpinnedFoldersround-tripping throughPATCH/GET /api/session-ui-state, including dedupe and trim.tests/e2e/session-bar.spec.tstestsession drawer pins folders to the top regardless of session activity ordercovers:/api/session-ui-state.npm run typecheck,npm run build,vitest run(50/50), and thesession-bar.spec.tssuite (63/63) all pass.hero showcase,conversation tree) reproduce on a clean checkout oforigin/mainand are unrelated to this PR.Acceptance criteria from #18
Closes #18.