Skip to content

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

Merged
brsbl merged 23 commits into
mainfrom
bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv
Jun 24, 2026
Merged

Add sidebar thread folders (folders view, scoped archived views, shared components)#258
brsbl merged 23 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 brsbl changed the title Add nested sidebar folders Add sidebar thread folders (folders view, scoped archived views, shared components) Jun 20, 2026
@brsbl brsbl force-pushed the bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv branch 4 times, most recently from 06d9fec to 04bfdda Compare June 23, 2026 22:44
brsbl added a commit that referenced this pull request Jun 23, 2026
These are the non-folder changes split out of the nested-folders PR (#258),
placed here so #258 is folder-only and nothing is dropped. Adapted to main's
reworked sidebar (chevron/worktree-menu ProjectRow, ProjectActionsMenu).

Search deep-link / scroll-to-message:
- Expose ThreadSearchMatch.sourceSeq (db) + threadSearchMatchSchema.sourceSeq
  (contract); server already passes matches through.
- useScrollToSearchedMessage + ThreadTimelineRows scroll hook, AppSidebar nav
  state (searchMessageSeq), SidebarThreadSearchPanel/sidebarThreadSearch
  messageSeq, app.css bb-search-flash, thread-search tests.
- ThreadSearchResultRow redesign (+ story).

Sidebar/menu interaction polish:
- Row cursor/state-class affordances (sidebarRowClasses cursor-pointer +
  selected /70, ThreadRow grab cursor + dropped title tooltips, ProjectList +
  ProjectRow row classes), adapted to main's chevron (SidebarChildToggleChevron
  drops title props; callers updated).
- Menu icons + label tweaks (ThreadActionsMenu Mail/Pin icons, "Mark read",
  separators) + rename payload refactor (ThreadActionsProvider/ThreadRenameDialog),
  PinnedThreadTree/ProjectListProjects/SidebarHistoryNavigationControls/
  sortableMotion/ui sidebar tooltip + story-harness TooltipProvider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
- Swap the worktree-style FolderGit icon for a plain Folder icon, matching the
  sidebar's project/folder iconography (avoids the worktree confusion).
- Add an optional folderLabel prop: when the sidebar is organized by folder the
  caller passes the thread's folder path, shown in place of the project. Story
  adds an 'organized by folder' variant demonstrating title + folder.

The live wiring (caller deriving folderLabel from Organize-by + thread.folderPath)
lands when #258's folderPath merges in — that data is not on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brsbl brsbl force-pushed the bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv branch from 1c0a029 to 290dedb Compare June 24, 2026 11:12
brsbl and others added 21 commits June 24, 2026 04:29
Add thread folders end to end, rebuilt cleanly on top of current main so the
change set is folder-only (no cross-cutting design/interaction polish).

Backend
- thread_folders table + migration 0044_thread_folders (snapshot + journal),
  folderPath column on threads, fld_ id prefix, thread-folders data CRUD
  (create/rename/delete/ensure/list + path normalization), and folderPath
  handling in createThread/updateThread/listThreads (folderPath + unfiled).
- domain threadSchema.folderPath; server-contract folder routes + request/
  response schemas, folderPath in thread create/list/update/filter schemas,
  folders in the sidebar bootstrap response.
- server thread-folders routes + registration, folderPath in thread create/
  update/list and the public thread mapper.

Frontend (built on main's reworked ProjectRow)
- SidebarFolderRow, folder bucketing/grouping in projectThreadGroups, ProjectList
  folder sections + the Group-by-folder organize menu, folder collapse/organize
  state atoms, folder drag-and-drop, ThreadFolderCreateDialog, folder API client
  methods, folder query keys, folder archived views + route-paths, and
  create-thread-in-folder from the root composer.
- ProjectRow keeps main's chevron/worktree-menu header (ProjectRow.interactions
  tests pass unchanged); folder rendering lives in the cross-project Folders view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The folder row's action span was missing the bb-sidebar-hover-actions class,
so its ···/+ buttons stayed visible on every folder row. Add the class so they
reveal on row hover / focus / menu-open, matching the project row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Aligns the visible label (tooltip / mobile title / menu header + aria-label)
with the existing internal naming (sidebarOrganizationModeAtom / organizationMode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Row actions reveal on hover by default; but when exactly one project/folder
container is open (visible + expanded) the sidebar is sparse, so render them
inline. Counted once in ProjectList (expanded projects in the Projects section
+ visible-expanded folders in the Threads section) and applied via a
data-sidebar-actions-inline attribute + one CSS rule — no prop threading through
the row components. Adds countVisibleExpandedFolders helper + unit tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… rows

Replace the "show row actions inline when only one container is expanded" rule
with a simpler split that matches the intended design:

- The primary section header (Projects in project mode, Folders in folder mode)
  always shows its actions.
- Project rows, folder rows, the Threads header, and thread rows reveal their
  actions on hover.

The old inline rule used a shared `.bb-sidebar-hover-actions` CSS override that
leaked onto thread rows, forcing their actions visible whenever the sidebar was
sparse. Removing it (plus the `data-sidebar-actions-inline` attr,
`countVisibleExpandedFolders`, and its tests) fixes that and drops the
complexity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`folderPathSubtreeFilter` sized its SQLite `substr()` prefix with JS
`String.length` (UTF-16 code units), but `substr()` counts code points. A
folder named with a non-BMP character (e.g. an emoji) over-read the prefix, so
the `"${path}/"` comparison never matched its descendants — silently skipping
descendant folders/threads on rename and delete and orphaning their
`folder_path` values.

Size the prefix by the path's code-point length (`[...path].length`). Adds a
regression test covering rename + delete of emoji-named folders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@brsbl brsbl force-pushed the bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv branch from fd08688 to 9790a23 Compare June 24, 2026 11:30
@brsbl brsbl merged commit 5ff13a1 into main Jun 24, 2026
6 checks passed
@brsbl brsbl deleted the bb/otto-s1-sidebar-nested-folders-thr_js7dkc3iwv branch June 24, 2026 12:43
brsbl added a commit that referenced this pull request Jun 24, 2026
These are the non-folder changes split out of the nested-folders PR (#258),
placed here so #258 is folder-only and nothing is dropped. Adapted to main's
reworked sidebar (chevron/worktree-menu ProjectRow, ProjectActionsMenu).

Search deep-link / scroll-to-message:
- Expose ThreadSearchMatch.sourceSeq (db) + threadSearchMatchSchema.sourceSeq
  (contract); server already passes matches through.
- useScrollToSearchedMessage + ThreadTimelineRows scroll hook, AppSidebar nav
  state (searchMessageSeq), SidebarThreadSearchPanel/sidebarThreadSearch
  messageSeq, app.css bb-search-flash, thread-search tests.
- ThreadSearchResultRow redesign (+ story).

Sidebar/menu interaction polish:
- Row cursor/state-class affordances (sidebarRowClasses cursor-pointer +
  selected /70, ThreadRow grab cursor + dropped title tooltips, ProjectList +
  ProjectRow row classes), adapted to main's chevron (SidebarChildToggleChevron
  drops title props; callers updated).
- Menu icons + label tweaks (ThreadActionsMenu Mail/Pin icons, "Mark read",
  separators) + rename payload refactor (ThreadActionsProvider/ThreadRenameDialog),
  PinnedThreadTree/ProjectListProjects/SidebarHistoryNavigationControls/
  sortableMotion/ui sidebar tooltip + story-harness TooltipProvider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
- Swap the worktree-style FolderGit icon for a plain Folder icon, matching the
  sidebar's project/folder iconography (avoids the worktree confusion).
- Add an optional folderLabel prop: when the sidebar is organized by folder the
  caller passes the thread's folder path, shown in place of the project. Story
  adds an 'organized by folder' variant demonstrating title + folder.

The live wiring (caller deriving folderLabel from Organize-by + thread.folderPath)
lands when #258's folderPath merges in — that data is not on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
These are the non-folder changes split out of the nested-folders PR (#258),
placed here so #258 is folder-only and nothing is dropped. Adapted to main's
reworked sidebar (chevron/worktree-menu ProjectRow, ProjectActionsMenu).

Search deep-link / scroll-to-message:
- Expose ThreadSearchMatch.sourceSeq (db) + threadSearchMatchSchema.sourceSeq
  (contract); server already passes matches through.
- useScrollToSearchedMessage + ThreadTimelineRows scroll hook, AppSidebar nav
  state (searchMessageSeq), SidebarThreadSearchPanel/sidebarThreadSearch
  messageSeq, app.css bb-search-flash, thread-search tests.
- ThreadSearchResultRow redesign (+ story).

Sidebar/menu interaction polish:
- Row cursor/state-class affordances (sidebarRowClasses cursor-pointer +
  selected /70, ThreadRow grab cursor + dropped title tooltips, ProjectList +
  ProjectRow row classes), adapted to main's chevron (SidebarChildToggleChevron
  drops title props; callers updated).
- Menu icons + label tweaks (ThreadActionsMenu Mail/Pin icons, "Mark read",
  separators) + rename payload refactor (ThreadActionsProvider/ThreadRenameDialog),
  PinnedThreadTree/ProjectListProjects/SidebarHistoryNavigationControls/
  sortableMotion/ui sidebar tooltip + story-harness TooltipProvider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
- Swap the worktree-style FolderGit icon for a plain Folder icon, matching the
  sidebar's project/folder iconography (avoids the worktree confusion).
- Add an optional folderLabel prop: when the sidebar is organized by folder the
  caller passes the thread's folder path, shown in place of the project. Story
  adds an 'organized by folder' variant demonstrating title + folder.

The live wiring (caller deriving folderLabel from Organize-by + thread.folderPath)
lands when #258's folderPath merges in — that data is not on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
These are the non-folder changes split out of the nested-folders PR (#258),
placed here so #258 is folder-only and nothing is dropped. Adapted to main's
reworked sidebar (chevron/worktree-menu ProjectRow, ProjectActionsMenu).

Search deep-link / scroll-to-message:
- Expose ThreadSearchMatch.sourceSeq (db) + threadSearchMatchSchema.sourceSeq
  (contract); server already passes matches through.
- useScrollToSearchedMessage + ThreadTimelineRows scroll hook, AppSidebar nav
  state (searchMessageSeq), SidebarThreadSearchPanel/sidebarThreadSearch
  messageSeq, app.css bb-search-flash, thread-search tests.
- ThreadSearchResultRow redesign (+ story).

Sidebar/menu interaction polish:
- Row cursor/state-class affordances (sidebarRowClasses cursor-pointer +
  selected /70, ThreadRow grab cursor + dropped title tooltips, ProjectList +
  ProjectRow row classes), adapted to main's chevron (SidebarChildToggleChevron
  drops title props; callers updated).
- Menu icons + label tweaks (ThreadActionsMenu Mail/Pin icons, "Mark read",
  separators) + rename payload refactor (ThreadActionsProvider/ThreadRenameDialog),
  PinnedThreadTree/ProjectListProjects/SidebarHistoryNavigationControls/
  sortableMotion/ui sidebar tooltip + story-harness TooltipProvider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
- Swap the worktree-style FolderGit icon for a plain Folder icon, matching the
  sidebar's project/folder iconography (avoids the worktree confusion).
- Add an optional folderLabel prop: when the sidebar is organized by folder the
  caller passes the thread's folder path, shown in place of the project. Story
  adds an 'organized by folder' variant demonstrating title + folder.

The live wiring (caller deriving folderLabel from Organize-by + thread.folderPath)
lands when #258's folderPath merges in — that data is not on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
These are the non-folder changes split out of the nested-folders PR (#258),
placed here so #258 is folder-only and nothing is dropped. Adapted to main's
reworked sidebar (chevron/worktree-menu ProjectRow, ProjectActionsMenu).

Search deep-link / scroll-to-message:
- Expose ThreadSearchMatch.sourceSeq (db) + threadSearchMatchSchema.sourceSeq
  (contract); server already passes matches through.
- useScrollToSearchedMessage + ThreadTimelineRows scroll hook, AppSidebar nav
  state (searchMessageSeq), SidebarThreadSearchPanel/sidebarThreadSearch
  messageSeq, app.css bb-search-flash, thread-search tests.
- ThreadSearchResultRow redesign (+ story).

Sidebar/menu interaction polish:
- Row cursor/state-class affordances (sidebarRowClasses cursor-pointer +
  selected /70, ThreadRow grab cursor + dropped title tooltips, ProjectList +
  ProjectRow row classes), adapted to main's chevron (SidebarChildToggleChevron
  drops title props; callers updated).
- Menu icons + label tweaks (ThreadActionsMenu Mail/Pin icons, "Mark read",
  separators) + rename payload refactor (ThreadActionsProvider/ThreadRenameDialog),
  PinnedThreadTree/ProjectListProjects/SidebarHistoryNavigationControls/
  sortableMotion/ui sidebar tooltip + story-harness TooltipProvider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
- Swap the worktree-style FolderGit icon for a plain Folder icon, matching the
  sidebar's project/folder iconography (avoids the worktree confusion).
- Add an optional folderLabel prop: when the sidebar is organized by folder the
  caller passes the thread's folder path, shown in place of the project. Story
  adds an 'organized by folder' variant demonstrating title + folder.

The live wiring (caller deriving folderLabel from Organize-by + thread.folderPath)
lands when #258's folderPath merges in — that data is not on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
These are the non-folder changes split out of the nested-folders PR (#258),
placed here so #258 is folder-only and nothing is dropped. Adapted to main's
reworked sidebar (chevron/worktree-menu ProjectRow, ProjectActionsMenu).

Search deep-link / scroll-to-message:
- Expose ThreadSearchMatch.sourceSeq (db) + threadSearchMatchSchema.sourceSeq
  (contract); server already passes matches through.
- useScrollToSearchedMessage + ThreadTimelineRows scroll hook, AppSidebar nav
  state (searchMessageSeq), SidebarThreadSearchPanel/sidebarThreadSearch
  messageSeq, app.css bb-search-flash, thread-search tests.
- ThreadSearchResultRow redesign (+ story).

Sidebar/menu interaction polish:
- Row cursor/state-class affordances (sidebarRowClasses cursor-pointer +
  selected /70, ThreadRow grab cursor + dropped title tooltips, ProjectList +
  ProjectRow row classes), adapted to main's chevron (SidebarChildToggleChevron
  drops title props; callers updated).
- Menu icons + label tweaks (ThreadActionsMenu Mail/Pin icons, "Mark read",
  separators) + rename payload refactor (ThreadActionsProvider/ThreadRenameDialog),
  PinnedThreadTree/ProjectListProjects/SidebarHistoryNavigationControls/
  sortableMotion/ui sidebar tooltip + story-harness TooltipProvider.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl added a commit that referenced this pull request Jun 24, 2026
- Swap the worktree-style FolderGit icon for a plain Folder icon, matching the
  sidebar's project/folder iconography (avoids the worktree confusion).
- Add an optional folderLabel prop: when the sidebar is organized by folder the
  caller passes the thread's folder path, shown in place of the project. Story
  adds an 'organized by folder' variant demonstrating title + folder.

The live wiring (caller deriving folderLabel from Organize-by + thread.folderPath)
lands when #258's folderPath merges in — that data is not on this branch.

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