Repository navigation
feat(app): pin sessions to a Pinned section above Recents - #189
Merged
Merged
Conversation
quickbeard
force-pushed
the
pinned-sessions
branch
3 times, most recently
from
August 11, 2026 07:01
f015828 to
66e1efe
Compare
Pin leads every session's ⋮ / ⋯ menu — sidebar rows, the View all list and the session header — and reads Unpin once set. Pinned sessions move out of Recents into a Pinned section above it, and the View all list grows a matching Pinned group above Today. Pinned is a second instance of the SidebarSection Recents already uses, so it folds, persists and reads identically; its collapse state slots into the same `sidebar.sections` record, which was keyed by section name for exactly this. The one addition is a `grow` prop: only one open section may claim the column's free height, or the two halve it. Pinned sizes to its rows and scrolls past five, so Recents cannot be squeezed out. The ids live in a fork-only context rather than in createHomeModel: the sidebar and the home screen each hold their own model, so two persisted stores over one key would write the same storage without sharing memory and a pin would not cross between them until a reload. Also ships fitsRecentsRow. Drawing "View all" into a list too short to hold a whole row has always clipped it, but only under a ~380px window; Pinned takes a fixed 60px off the column, which lifts that into heights people actually use, so the guard belongs with it. Every new assertion was run against the pre-change source and fails there — 8 of them, with the 3 pre-existing collapse tests still passing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
quickbeard
force-pushed
the
pinned-sessions
branch
from
August 11, 2026 09:48
66e1efe to
878675e
Compare
quickbeard
added a commit
that referenced
this pull request
Aug 11, 2026
#189 shipped with the pinned section covered only through the sidebar row menu. Three behaviours it introduced had nothing behind them. - The five-row cap: pinning a sixth session now has to leave the list capped and scrolling, with all six rows still rendered, and Recents still holding its height. PINNED_VISIBLE_ROWS appeared in no test before this. - The in-session header menu: pinning from the ⋯ beside a session title has to show up in the sidebar. That is the cross-surface agreement DIVERGENCES singles out as the silent breakage — the header resolves the pin's server independently, and a mismatch files the pin under a key the sidebar never reads. - Deleting a pinned session has to drop the pin from storage, and pins have to be stored under the server they were made against rather than in one flat list. Each was run against a deliberate break of the behaviour it covers and fails there. The per-server test needed tightening to earn that: the first version asserted only that some single key existed, which a hardcoded "all" satisfied, so it now asserts the exact server key. 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.
Pin leads every session's ⋮ / ⋯ menu and reads Unpin once set. Pinned sessions move out of Recents into a Pinned section above it, and the View all list grows a matching Pinned group above Today.
Pinned is a second SidebarSection, not a new one
It is the same component Recents renders, passed
name="pinned"— the header toggle, chevron,aria-expanded, keyboard operability and 28px folded height all come along unduplicated. Its collapse state slots into the samePersist.global("sidebar.sections")record, which was already keyed by section name for exactly this: no new stored key, no migration, and the two fold independently.The one addition to the shared component is a
grow?: boolean. It hard-codedflex-1when open, which was right while Recents was the only section but would have had two open sections halve the column. Pinned passesgrow={false}and sizes to its rows, scrolling past five, so Recents can never be squeezed out.Where the pins live
A fork-only
context/pinned-sessions.tsx, not state insidecreateHomeModel(). The sidebar and the home screen each hold their own model, so twopersistedstores over one key would write the same storage without sharing memory — a pin in the sidebar would not reach View all until a reload. Same reason the project selection already lives inuseLayout().Ids are keyed by server (session ids are only unique within one), ordered newest-pin-first so a pinned row does not reshuffle when its session is touched, and resolved through the records list — which drops pins whose session was deleted elsewhere and keeps the stored list self-healing. Deleting a session drops the pins of its whole subtree.
The Pinned list is keyed by session id, matching what #185 did for the other two lists: it renders the same rebuilt record wrappers, so a reference-keyed
<For>would have given its rows the first-click defect that PR removed.Tests
Unit tests for
orderPinnedRecords/togglePinnedIDs(pin order over recency, unresolvable ids dropped, no duplicates, no mutation), and e2e for: pin moves the row and persists across reload, the label swaps to Unpin and unpinning restores it, Pin leads the menu, Pinned hugs its rows instead of halving the column, the View all group, and both sections folding independently.All 8 new or changed assertions were run against the pre-change source and fail there, with the pre-existing collapse tests still passing.
Divergences
Four upstream files change —
home.tsx(thepinnedgroup),message-timeline.tsx(header menu),layout-new.tsx(provider) and theen/esdicts — under oneDIVERGENCES.mdrow, filed beside the two existinghome.tsxrows. Its on-conflict note flags the one thing that would silently break: the header must resolve the pin's server key the same way the sidebar's selection does, or one user's pins split in two.Rebased onto main
#185, #186 and #188 have since landed. Rebased with four conflicts resolved:
app-sidebar-rows.ts— both sides had added an identicalfitsRecentsRow. Kept main's, with a note that Pinned lifts the thresholds ~60px while expanded.app-sidebar.tsx— took main's id-keyed Recents<For>, and applied the same keying to the new Pinned list, which the rebase would otherwise have left reference-keyed and therefore first-click broken.sidebar-section-collapse.spec.ts— merged the two height sweeps, keeping 440 since Pinned raises the clipping threshold.CHANGELOG.md— docs: fold the seven sidebar entries into one #188 had folded the older entries into the 1.18.4-8 sidebar entry, so my duplicate "half-cut View all" entry is gone (that fix shipped in fix(app): open session row menus on the first click, and stop clipping View all #185) and the Pin copy is now folded into that same entry rather than standing alone: the Pinned section joins the sections narrative, Pin joins the row-menu list, and feat(app): pin sessions to a Pinned section above Recents #189 joins its PR list.## Unreleasedis left empty, as docs: fold the seven sidebar entries into one #188 left it.Both earlier caveats are now moot — the clipping guard is on main, and pin/unpin opens on the first click.
Verification (rebased onto #190)
bun typecheck(packages/app) — cleanbun test(packages/app) — 638 passbun test:e2e:local— 130 passed, 0 failed. The tworemote-session-settingsfailures every earlier run reported are gone: fix(app): press the settings shortcut the modifier the page expects #190 fixed them, and this branch now sits on top of it.bun lint— 4746 warnings + 1 error, identical tomain's baseline🤖 Generated with Claude Code