Skip to content

feat(sessions): pin folders to the top of the session drawer#21

Open
goyamegh wants to merge 1 commit into
ashwin-pc:mainfrom
goyamegh:fix/pin-folders
Open

feat(sessions): pin folders to the top of the session drawer#21
goyamegh wants to merge 1 commit into
ashwin-pc:mainfrom
goyamegh:fix/pin-folders

Conversation

@goyamegh
Copy link
Copy Markdown

@goyamegh goyamegh commented Jun 8, 2026

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

  • Each folder header gets a small pin/unpin icon button that fades in on hover (and stays solid when the folder is pinned).
  • Pinned folders sort to the top of the drawer in pin order. Pinning a folder appends to the end of the pinned list.
  • Unpinned folders below keep the existing recency-based ordering.
  • Pin state survives reloads and is round-tripped through the existing pi-web-session-ui-state.json mechanism (new pinnedFolders: string[] field on SessionUiState).
  • Pinning is independent of the active marker-color / search filter: a pinned folder header stays visible even when its sessions are all filtered out (consistent with Drawer hides whole folders when filter is applied or sessions are empty #13).
  • A pinned folder whose sessions have all been deleted still renders a small placeholder row so the user can unpin it.
  • A subtle accent on the pinned folder name and a divider above the first unpinned folder make the two regions distinguishable at a glance.
  • No regressions to per-session pinning, marker colors, or the color filter.

Implementation

  • server/sessionUiState.ts & src/app/types.ts — extend SessionUiState with pinnedFolders: string[], add normalizer / patch handler / legacy localStorage reader. The existing PATCH /api/session-ui-state endpoint accepts the new field via the existing patch shape, no new route needed.
  • src/sessions/sessionDrawer.tsapplySessionUiState and refreshSessionUiState now carry pinnedFolders through. Added pinFolder / unpinFolder / toggleFolderPin helpers. The grouping pass in renderSessionList now 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 .sessionFolderPinButton and a divider between the pinned and unpinned regions.

Tests

  • Unit: extended tests/api.test.ts to cover pinnedFolders round-tripping through PATCH/GET /api/session-ui-state, including dedupe and trim.
  • E2E: new tests/e2e/session-bar.spec.ts test session drawer pins folders to the top regardless of session activity order covers:
    • Default order is recency-based.
    • Pinning a folder moves it to the top.
    • Pinning a second folder appends to the pinned region in pin order.
    • Pin state persists in /api/session-ui-state.
    • Pinned folders keep their position under an active marker-color filter.
    • Unpinning reverts the folder to its recency slot.
  • Pre-flight: npm run typecheck, npm run build, vitest run (50/50), and the session-bar.spec.ts suite (63/63) all pass.
    • The two existing mobile visual-regression failures (hero showcase, conversation tree) reproduce on a clean checkout of origin/main and are unrelated to this PR.

Acceptance criteria from #18

  • Each folder header has a pin/unpin affordance.
  • Pinned folders render above unpinned folders in a stable order across session switches.
  • Pin state survives reloads.
  • Pinning is independent of the active marker-color / search filter.
  • No regressions to the existing per-session pin behaviour.

Closes #18.

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>
Copilot AI review requested due to automatic review settings June 8, 2026 03:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 pinnedFolders to 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 thread src/styles/sessions.css
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"]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pin folders in the session drawer so they don't reshuffle on every session switch

2 participants