Skip to content

Align the mobile thread list with the web sidebar and add section reorder - #2058

Merged
SawyerHood merged 1 commit into
mainfrom
bb/create-html-visual-mockups-thr_tc2irwdrwx
Aug 20, 2026
Merged

Align the mobile thread list with the web sidebar and add section reorder#2058
SawyerHood merged 1 commit into
mainfrom
bb/create-html-visual-mockups-thr_tc2irwdrwx

Conversation

@SawyerHood

Copy link
Copy Markdown
Collaborator

What was wrong

The mobile home thread list did not line up. It had four left text edges (section labels, parent threads pushed by a leading chevron, child threads, leaf threads), 20px section labels that outweighed the 17px titles, a count chip and "+" action in different columns from the status glyph, no tree line under nested rows, and a project line under every thread. Sections also had no order control: the web sidebar lets the user drag sections, the mobile app did not.

What changed

  • apps/mobile/src/screens/sidebar/SidebarRows.tsx: rows follow the web sidebar rules. One text edge per depth (16 + 24 × depth, web getSidebarThreadRowPaddingLeft), the disclosure chevron after the label (header 12px, thread 14px), a hairline group line under nested rows (web SIDEBAR_PROJECT_GROUP_LINE_CLASS), one 36px trailing slot for the status glyph and the header "+", 12px uppercase sectionLabel headers.
  • SidebarThreadList.tsx: the home list passes no subtitle (one line per row). Search and Archive keep theirs. The dead groupProjectId row field is removed.
  • Section reorder:
    • data/sidebar/sidebar-preferences.ts: sectionOrder per organize mode under the web keys (bb.sidebar.sectionOrder, bb.sidebar.manualSectionOrder, bb.sidebar.machineSectionOrder) plus setSectionOrder.
    • data/sidebar/sidebar-section-order.ts: resolveSidebarSectionOrder (client-core normalizeSidebarSectionOrder), useSidebarSectionOrder (writes the normalized order back like the web), listSidebarSectionOrderEntries, mergeHiddenSectionOrder.
    • sidebar-list-rows.ts: buildSidebarListRows takes sectionOrder and emits Pinned and groups in that order.
    • SectionReorderList.tsx: drag-to-reorder list (reanimated + gesture-handler), commits on drop, haptic ticks.
    • SidebarActionsProvider.tsx: "Reorder sections…" in the display options, "Reorder sections" in project and section menus, a long-press on a built-in or machine header opens the sheet. ui/Sheet.tsx exposes enableContentPanningGesture so the list owns vertical drags.
  • apps/mobile/README.md: one line for the feature.

No wire change. Client-local preference only, so no CLI surface (same as the web).

How you verified

  • pnpm exec turbo run typecheck lint test --filter=@bb/mobile: pass (829 tests). New tests: section-order persistence and keys (sidebar-preferences.test.ts), section ordering in buildSidebarListRows (sidebar-list-rows.test.ts), mergeHiddenSectionOrder (sidebar-section-order.test.ts).
  • iOS 26.3 simulator (iPhone 17 Pro) against the fake-provider mobile e2e backend: project and manual modes render with one left edge, chevrons after labels, the group line, and one trailing column; the reorder sheet opens from the display options and from a long-press on a built-in header; drags in both directions reorder the sheet and the list behind it; Pinned stays hidden while nothing is pinned.

Fixes #

AGENT GENERATED: by Claude Opus 5

…rder

The home thread list had four left text edges (headers, parent threads
pushed by a leading chevron, children, leaf threads), a 20px section label
that outweighed 17px titles, a count chip and "+" in different columns from
the status glyph, and a project line under every row.

Rows now follow the web sidebar rules: one text edge per depth
(16 + 24 × depth), the disclosure chevron after the label, a hairline group
line under nested rows, one 36px trailing slot, 12px uppercase section
labels, and one line per row on the home list (search and archive keep
their subtitle).

Sections can be reordered: the order is stored per organize mode under the
web localStorage keys, normalized with the shared client-core helper, and
applied when the rows are built. A drag-to-reorder sheet opens from the
display options, the project and section menus, and a long-press on a
built-in or machine header.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood
SawyerHood merged commit de8a740 into main Aug 20, 2026
13 checks passed
@SawyerHood
SawyerHood deleted the bb/create-html-visual-mockups-thr_tc2irwdrwx branch August 20, 2026 18:33

@bb-slop-cop bb-slop-cop Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 SLOP COP 🚨 · review

I am the Slop Cop. I am reviewing this pull request for security, quality, performance, architecture, and mobile behavior.

const isReady = model.isReady;
useEffect(() => {
if (!isReady || haveSameOrder(storedOrder, order)) return;
setSectionOrder(organize, order);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P1] Keep the order separate for each server profile.

This effect writes the normalized order to one app-wide key. Profile B removes Profile A identifiers during normalization.

This destroys the custom order for Profile A. Store entity-specific orders by profile ID, and migrate each old global key once.

layout="scroll"
// The reorder list owns vertical drags, so it gets a plain view body
// and the sheet stops following the finger.
layout={state?.kind === "section-reorder" ? "view" : "scroll"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P1] Keep every reorder row and the Done action reachable.

This sheet uses a plain view and disables body panning. Each section adds 48 pixels.

With enough projects, the sheet hides lower rows and the Done action. Use a bounded scroll list and add edge scrolling during a drag.

return (
<GestureDetector gesture={rowPan}>
<Animated.View
accessibilityRole="button"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P1] Add an accessible reorder action.

This row has a button role but no button action. VoiceOver uses drag gestures itself, so users cannot reorder these rows.

Use the adjustable role with increment and decrement actions. Move the row one slot for each action, and announce its position.

const organize = model.organize;
const isReady = model.isReady;
useEffect(() => {
if (!isReady || haveSameOrder(storedOrder, order)) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P2] Wait for the host query before machine-order persistence.

The bootstrap query can finish before the host query. Machine mode then builds and saves a temporary unknown-host order.

Wait for host-query readiness before this effect saves a machine order. Add a test for bootstrap data that arrives first.

"worklet";
const current = slots.value[id] ?? 0;
if (nextIndex === current) return;
const next = { ...slots.value };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P2] Do not copy every slot at each drag boundary.

Each crossed boundary copies and scans all slots on the UI thread. A long list causes quadratic work during one drag.

Keep an order array or direct slot references. Update only the slots in the crossed range.

emptyProjectLabel: string,
rows: SidebarListRow[],
): void {
const group = model.groups.find((candidate) => candidate.id === sectionId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P2] Avoid a full group scan for every ordered section.

This lookup scans all groups once for each section. Large project sets give each list projection quadratic work.

Build one ID-to-group map before the loop, then use constant-time lookups.

{
height: SECTION_REORDER_ROW_HEIGHT,
borderRadius: radii.md,
shadowColor: "#000",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 slopcop/review[P3] Use the theme shadow color.

The fixed black shadow ignores custom palettes. The mobile theme already supplies tokens.shadowColor.

Use that token here, and set the opacity for the token.

@bb-slop-cop bb-slop-cop Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 SLOP COP 🚨 · review

This change makes the mobile thread list match the web sidebar. It also adds saved drag order for project, machine, and manual sections.

I found seven issues:

  • P1: A profile switch can delete the prior profile's saved section order.
  • P1: A long section list can hide lower rows and the Done action.
  • P1: VoiceOver users cannot use the new reorder control.
  • P2: Machine order can save before the host query finishes.
  • P2: The drag operation copies and scans all slots at each boundary.
  • P2: The list projection scans all groups once for each section.
  • P3: The drag shadow ignores the theme shadow token.

I found no security or authorization issue.

I compared this code with the web sidebar. Client-core already owns section identifiers and normalization. I found no other useful shared refactor.

The mobile Turbo test passed 122 files and 829 tests. The Turbo type check, lint check, and diff check also passed.

GitHub CI passed, but the iOS simulator job did not run. I started Metro and used doobie against its web route.

The web bundle failed because this native app does not include react-native-web. Maestro is not installed, so I could not run the native flow.

I left inline comments with fixes for each issue. I used a final GPT-5.6 review to check the findings.

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