Skip to content

Add sidebar thread folders (folders view, scoped archived views, shared components)#258

Open
brsbl wants to merge 47 commits into
mainfrom
bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv
Open

Add sidebar thread folders (folders view, scoped archived views, shared components)#258
brsbl wants to merge 47 commits into
mainfrom
bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv

Conversation

@brsbl

@brsbl brsbl commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds thread folders to the sidebar — group personal threads into folders via a dedicated Folders organization mode — and reworks the sidebar's view‑options and archived surfaces to use shared components with variants, so the project view and folders view can't drift. Built on the otto‑s1 spec, then refined across QA.

Folders

  • Group personal threads into folders; toggle via Organize → Project / Folders.
  • Folder rows mirror project rows: a New‑thread (+) button plus a menu with View archived threads / Rename / Remove.
  • Explicit folder creation (New folder); rename/remove update member threads.
  • Drag a thread onto a folder to file it. Spring‑loaded drop: after a short hover the folder auto‑expands and shows an optimistic preview row — deferred so dragging through/out of a folder no longer sticks.

Organize & Sort menus

  • Split into two triggers: Organize (Layers icon) and Sort (up/down‑arrows icon).
  • Sort shows a single state arrow per field — none / ↓ descending / ↑ ascending; re‑clicking the active field flips direction. Alphabetical defaults to A→Z.
  • Fixed an alphabetical‑sort bug where leaf threads and folders sorted in opposite directions.
  • Menus size to their content; section labels match the standard dropdown hierarchy.

Archived views

  • One ArchivedThreadsView for every scope (project / personal‑loose / folder); the scope shows in the AppLayout breadcrumb (Threads › <folder> › Archived), not a bespoke in‑body heading.
  • Per‑folder View archived threads (/archived?folder=…). The personal/loose list now excludes foldered threads via a new unfiled filter. Menu items renamed to "View archived threads".

Shared components (the no‑duplication goal)

  • ManualThreadTreeItems — the single place that maps tree items to rows for every view (project, chronological, folders), plus ThreadTreeLoadingSkeleton.
  • SidebarDisplayOptionsActions + SidebarThreadsSectionActions — shared header action clusters used by both modes.
  • ArchivedThreadsView + breadcrumb variants for the archived pages.

Other polish

  • Path‑missing project rows show only the red warning icon, flush right (no /+).
  • Sidebar tooltips match the agent message action bar (300ms open delay).
  • Restored the sidebar New‑thread label; dropped "folder" from the path‑missing tooltip + aria‑label.

Server / data

  • Optional folderPath and unfiled filters on the thread‑list query, threaded contract → route → db, with db regression tests.

Testing

  • typecheck (30/30), lint, and prettier --check clean.
  • Sidebar unit tests including new sortComparator (sort direction + leaf/folder consistency) and db folderPath/unfiled filter tests.
  • Archived filters verified end‑to‑end against the dev server.
  • ⚠️ Drag‑and‑drop and tooltip timing verified manually (no headless browser available).

Notes

  • Nested folder creation is descoped in the UI (folders are flat); the data model still stores full folder paths.

🤖 Generated with Claude Code

brsbl and others added 30 commits June 18, 2026 18:27
First session of the Sidebar Nested Folders feature: the client-only
foundation that later sessions build on.

- folderPath.ts: pure parseThreadFolderPath / normalizeThreadTitle /
  titleCreatesFolder, plus buildFolderKey. "/" in a thread title is read
  as a folder separator; titles are split → trimmed → emptied → re-joined.
- folderPath.test.ts: covers every normalization rule and the
  titleCreatesFolder / buildFolderKey boundaries (18 cases).
- sidebarCollapsedAtoms.ts: three persisted prefs mirroring the existing
  atom pattern — sidebarGroupByAtom ("none"|"folder", default "none"),
  sidebarCollapsedFoldersAtom (string[]), folderOnboardingSeenAtom (bool).

No rendering, tree-building, or rename changes yet (later sessions).
Tests, typecheck, and lint pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the opt-in "Group by: Folder" sidebar mode: top-level threads fold
into nested, collapsible folders derived from "/" in their titles. Pure
derived rendering — no DB/API/daemon changes.

Assembly (S2):
- SidebarFolderGroup item variant + bucketIntoFolders helper, folding the
  top-level item list into a nested folder tree. Folders render as a block
  above loose threads; folders, contents, and nested subfolders are each
  ordered by the active comparator (folders by their representative
  descendant). One parentKey-aware orderSiblingItems seam so S5 manual sort
  can swap ordering per list without re-cutting the tree walk.
- buildProjectThreadGroups / buildChronologicalThreadList take folderOptions
  and early-return today's output untouched under Group by: None.
- buildPinnedSidebarState folds pinned roots into folders ordered by
  comparePinnedRoots (pinned keeps its pinSortKey ordering), exposing
  rootItems while keeping rootNodes.

Rendering (S3):
- SidebarFolderRow collapsible header (icon, leaf name, descendant count,
  rolled-up activity) mirroring the parent/worktree row chrome.
- ProjectRow renders folder items + recurses; ThreadRow gains
  displayTitle/accessibleTitle so a folder member shows its leaf while
  keeping the full "Work › Q3 › Planning" path for a11y + tooltip.
- Collapse state in sidebarCollapsedFoldersAtom (read where rendered);
  selected thread's folder ancestors auto-expand.
- SidebarViewOptionsMenu gains a Group by (None / Folder) section; the
  existing organization "Group by" is relabeled "Organize by" to avoid the
  label collision.
- PinnedThreadTree renders folders statically (drag-reorder and derived
  folders don't compose), keeping the sortable flat list when no folders.

Tests: bucketIntoFolders nesting, folders-first + representative-descendant
ordering under both comparators, child-stays-under-parent, env-group-in-
folder, pinned folder ordering, and the Group by: None regression (deep-
equal to the pre-change builder + folder branch never entered).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Sort options use a single state-driven arrow (none / down=desc / up=asc)
  instead of a check plus dual direction buttons; re-selecting the active
  field flips its direction.
- Organize trigger uses the Layers icon; Sort trigger uses up/down arrows.
- Align the menu section labels to the standard DropdownMenuLabel hierarchy
  used by the rest of the app's dropdowns.
- Make the View options menu story interactive and add TooltipProvider to the
  Ladle harness so tooltip-using stories render instead of crashing blank.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl and others added 15 commits June 19, 2026 14:10
Resolve thread-create-helpers conflict: keep main's request-carried
titleFallback and the branch's folderPath. Add folderPath: null to the
ThreadActionsMenu test fixture for the now-required field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reads "Open project settings to fix".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Folder rows now expose a dedicated New thread (+) button alongside a
  "..." menu (Rename, Remove), mirroring the project row's action cluster
  instead of stuffing New thread into the menu.
- Size the sidebar/project/thread action and view-options dropdowns to
  their content (drop fixed w-44/w-52/w-56; the shared min-w-[8rem] floor
  remains) so menus wrap their contents.
- Clean up the project path-missing affordance: tooltip reads "Open project
  settings" and the icon's accessible name is "Project path not found".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The icon-only New thread action read inconsistently next to the labeled
Automations button below it. Bring back the "New thread" label (Search
stays an icon button alongside).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folder rows now have a "View archived threads" action, opening a
folder-scoped archived list — the analog of a project's archived view.

- Add an optional folderPath filter to the thread list query end to end:
  server-contract schema -> list route -> db listThreads filter.
- useArchivedThreads accepts folderPath (keyed separately in the cache);
  the archived view reads it from a `?folder=` param and labels the folder.
- getFolderArchivedRoutePath builds the projectless archived route scoped
  by folder (folders live in the personal section).
- Thread the onViewArchivedThreadsInFolder callback through the folder row
  chain; the folder "..." menu shows View archived threads / Rename / Remove.
- Cover the db folderPath filter with a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The sidebar's TooltipProvider opened tooltips instantly (delayDuration 0),
which felt flickery on hover. Use 300ms like MessageActionBar so sidebar
icon tooltips open/close with the same cadence. Mirror it in the Ladle
harness for story fidelity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The personal "Threads" archived list (no folder selected) was showing
threads that live in a folder. Add an `unfiled` thread-list filter
(folderPath IS NULL) end to end and use it for the personal archived view,
so foldered archived threads appear only in their folder's archived list.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The folder archived page diverged from the project/personal pages with a
bespoke in-body heading. Make all archived scopes render the one shared
view and carry their scope in the AppLayout breadcrumb instead:

- Rename ProjectArchivedThreadsView -> ArchivedThreadsView (it already
  serves project, personal/loose, and folder scopes) and drop the bespoke
  folder heading + folder-specific empty state so the body is identical.
- Add a folder segment to the projectless archived breadcrumb
  ("Threads > <folder> > Archived") and document title, mirroring how the
  project name scopes the project archived page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Smoke testing surfaced that the title sort inverted the wrong way: its base
comparator ascends (A→Z), but getSidebarThreadComparator applied the same
"asc inverts" rule used for the descending time sorts. So leaf threads sorted
opposite to the folder/item comparator (folders Z→A while threads A→Z) and the
↑/↓ arrows read backwards for alphabetical.

- Special-case the ascending title base: asc keeps it (A→Z), desc inverts it
  (Z→A), for both the leaf and mixed folder/thread comparators.
- Default a newly selected Alphabetical sort to asc (A→Z); time sorts still
  default to desc (newest first).
- Export getSidebarThreadComparator and add direction/consistency tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dragging a thread onto a folder now reads as a real drop:

- onDragOver resolves the target folder (shared with onDragEnd so they agree),
  expands it if collapsed, and records a drag preview.
- The hovered folder renders an optimistic, non-interactive row for the dragged
  thread inside it (forcing the children area open) so there's a visible target
  to drop onto before the move commits.
- Preview/auto-expand apply only to real folders (not the loose root) and clear
  on drop/cancel; state updates are guarded so drag-over doesn't thrash renders.

useSidebarReorderDnd now forwards onDragStart/onDragOver/onDragCancel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a project's local path is missing, the row now renders just the red
warning icon (flush right) instead of also showing the actions menu and new
thread button — the warning is the single relevant action (open settings to
fix). Normal rows keep the "..." + "+" actions. Right-click context menu is
unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Threads header in the folders view was missing the Organize/Sort actions,
and each view defined its own near-duplicate action clusters. Extract shared
components so the headers can't drift:

- SidebarDisplayOptionsActions: the Organize + Sort menu pair, used by the
  Projects, Folders, and Threads headers.
- SidebarThreadsSectionActions: the full Threads-header cluster (archived menu
  + display options + new thread), used by the Threads header in BOTH project
  mode and the folders view — so it now has Organize/Sort there too.

Rebalanced the two display-menu open states so each mode's two sections own one
("primary": Projects/Folders, plus Threads) and stay independent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ProjectThreadTree, ChronologicalThreadTree, and the Folders view each had their
own near-identical copy of "group + sortable list + map item rows" plus the
loading skeleton, so every row-prop change had to be repeated per view.

- Add ManualThreadTreeItems: the single place that maps thread-tree items to
  rows. Variants via props — fixed vs per-item projectId, and an optional
  sortableParentKey (wrap in a SortableContext, or let an outer one provide it
  for the split Folders/Threads view).
- Add ThreadTreeLoadingSkeleton for the repeated loading state.
- Route all three renderers through them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expanding the hovered folder and inserting the drop-preview row mid-drag shifted
layout under the in-flow dragged item, so dragging a thread up out of its folder
got shoved back down ("stuck"). Defer both behind a short hover dwell so passing
through a folder doesn't mutate layout; the expand + preview only fire once the
pointer settles over a target. Drop still works immediately regardless.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brsbl brsbl changed the title Add nested sidebar folders Add sidebar thread folders (folders view, scoped archived views, shared components) Jun 20, 2026
brsbl and others added 2 commits June 19, 2026 17:22
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant