Repository navigation
Reorder sidebar sections by dragging their header - #909
Conversation
Sections now keep a placed order. Each section row carries its own orderKey, minted between its neighbours' keys as pins are, so the order syncs across devices and concurrent moves converge. Sections no move has keyed yet, including those older apps create, follow by creation. On desktop, dragging a section header lifts the section and slides its siblings aside; releasing outside the sidebar sends it home. Synced workspaces need an engine advertising sidebar-section-order-v1.
On the iOS front page, long-pressing a session and dragging it moves it into Pinned or a user section at the slot it is dropped on, or back to Recent. Placements carry their neighbours as after/before anchors, so the order syncs with desktop. A folded destination opens to show the row. Sections now list their members in their placed order, as desktop does, instead of by recency. The client and FFI gain pin_session_at and place_session; assign_section places without anchors.
Placing a section (or member) re-keyed every row once any lacked a key, so a move on one device after a section was created could overwrite a concurrent move from another device with a later clock. Key only the keyless tail, past the last key, and fall back to re-keying everything only when no key fits there. Also de-flake the same-section concurrent move test and dedupe section members in the client view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wingleeio
left a comment
There was a problem hiding this comment.
Security audit: clean — hostile/oversized orderKey values fall back to creation order, anchors are validated, no unwraps on synced data, FFI is plain String passthrough, regenerated Swift matches uniffi output, capability gate is correct.
Pushed one fix (e7ad957): placing a section re-keyed every section once any lacked a key, so after a section was created, the next move could overwrite a concurrent move from another device with a later clock. Now only the keyless tail is keyed (past the last key), with a full re-key only when no key fits. Added a regression case to sidebar_sections_reorder_and_converge_across_devices, de-flaked its same-section concurrent step, and deduped section members in the client view. Thanks!
Upstream squash-merged zeronsh#895, zeronsh#903 and zeronsh#909, already merged here from their branches. Conflict resolution: - Take upstream's final versions of those PRs (session hover card reconcile, side chat rename leaving takeover, section reorder tests). - Keep this branch's cross-PR changes: right_layout/right_panes modules (zeronsh#939), chat-unread capability (zeronsh#515), native_fork_point (zeronsh#622), and the observe_sidebar_row -> observe_row rename (zeronsh#515), also applied to upstream's new sidebar section code.
…07 becomes the session-card superset; 12 superseded into 07
Wave-7 ticket 06 — port of upstream 05f5f2b + ae19fe3 + 672cc14, with the 2026-10-22 riders 26c8e84 (zeronsh#909, drag-reorder) and 81e04e1 (zeronsh#901, Archived toggle) folded in; the 05f5f2b/ae19fe31 head start came from the saved ticket-06 fold-ins patch. Upstream 672cc14 (searchable popovers, ordered sidebar sections, titlebar sidebar options): - popover: every searchable popover (project, device, branch, spaces, changes ref, font) shares the model picker's search row (40px, magnifier, full-bleed hairline) and 32px picker rows in one tone with scroll edge fades and rails (search_input_frame/search_row rework + picker_row/picker_icon_slot/picker_divider). - Project rows in both project dropdowns wear the project artwork (ProjectIcons extracted from Shell's inline cache into the shared pipeline shape, now owned by Shell and Pickers); the device tag is gone from the spaces dropdown and the sidebar filter trigger. - All sidebar section headings share one header; collapsed sections show a muted count badge (monospace, cut like the PR badge) instead of a "(N)" label suffix; header glyphs share the label's tone. - Custom sections keep a placed order, as Pinned does: assign carries optional after/before anchors (section_insert_index, the pin rule), drags reorder members in place (sidebar_section_drop_anchors reads the preview gap), and the sidebar lists section members in placed order rather than by activity. - The view options moved to the titlebar at the sidebar's trailing edge (render_sidebar_options_titlebar, sliding/fading with the sidebar, occluded over the drag strip); View > Show gains a Project filter toggle (hiding it clears the filter) and SIDEBAR_MIN widened 224 -> 242 so the options button fits beside the titlebar controls. - Polish: agent-update pill summary fits the card border (no left strip), thinking/tool-call group headers roll as their summary changes. Rider 26c8e84 (zeronsh#909): dragging a section header lifts the section (frosted, in the list's movement layer) and slides its siblings aside; releasing outside the sidebar sends it home. Roboco's engine-local equivalent of SidebarSectionChange::Move: the reorder splices the sections list and pushes the SetSidebarSections snapshot. Rider 81e04e1 (zeronsh#901): sidebar toggle for the Archived section (sidebar_show_archived) — hidden removes the whole shelf without touching its chats or disclosure state; a reveal into it re-enables it. Wire change: the SidebarSection proto doc now states the placed-order contract — membership and the section list carry their placed order in the array order through the engine-local LWW replaces. Upstream's per-member fractional orderKey registry rows + HLC clocks exist only for cross-device convergence, which roboco excludes (ADR 0004); the engine store already preserves list order, so no per-key minting and no sidebar-section-order-v1 capability gate were carried. Wiregen regenerated (SidebarSection.ts doc); --check gate green. The web peer (sectionRows) lists members in placed order, with tests. Excluded by intent: crates/client hunks (cloud), pull_requests.rs popover hunks (rides parked zeronsh#484/ticket 21), crates/mobile FFI pin_session_at/place_session (Android parity — held for a mobile follow-up per ticket step 6), iOS hunks, and the registry cross-device convergence tests (replaced by the engine-side placed-order restart test plus UI placed-order/drag/anchor tests). Verification: cargo check -p roboco-doc -p roboco-proto -p roboco-ui; cargo test -p roboco-ui (1618 passed); sidebar-sections/spaces/ pickers/appearance/changes/chat-rename/transcript/harness-updates tests; cargo test -p roboco-engine sidebar_state (10 passed); wiregen --check green; pnpm -r build green; web vitest sidebar suites green (6 unrelated failures reproduce on clean main).
Summary
Custom sidebar sections can now be reordered by dragging their header on desktop. The order syncs across devices like pins and section members do. Until now, sections always showed in creation order.
Data and sync
SidebarSectionChange::Move { id, after, before }places a section between its neighbours, using the same anchor rule asAssign(section_insert_index). Itsproject()reorders the localVec, which covers the optimistic queue and Local workspaces.sidebarSectionsrow gets its ownorderKey, minted withpin_order_key_betweenbetween the neighbours' keys. Concurrent moves of one section converge by clock, and moves of different sections both survive.(orderKey, id), then unkeyed ones by(createdAt, id). Until the first move, nothing changes. Sections created later, including by older apps or iOS, append.place_amongand shared by members and sections.sidebarSectionshas no field allow-list.Compatibility
sidebar-section-order-v1. An older engine would reject the unknown action, so the UI only offers the drag in Local scope, or in synced scope when the engine advertises it.orderKeyand keep showing creation order. They lose nothing: their renames and collapses don't touch the key.zeron-doc.Desktop UI
TAB_SLIDE). The section lands past a sibling once its leading edge crosses that sibling's middle, so sections of different heights reorder naturally.iOS: drag sessions between sections
after/beforeanchors, so the order syncs with desktop. A folded destination opens to show the row.pin_session_atandplace_session;assign_sectionplaces without anchors.zeron_core.swiftregenerated.sidebar_writes_reorder_the_front_page(client demo) covers anchored placement, unpinning on placement and anchored pins.Tests
section_move_reorders_sections_by_anchor(proto)sidebar_sections_reorder_and_converge_across_devices(doc): unmoved order, appended unkeyed sections, concurrent moves of different sections and of the same section, deleted sections and anchorssections_reorder_by_dragging_their_header(ui): real mouse drag, plus a release outside the sidebarsection_drop_index_follows_the_leading_edge(ui)docs/registry-sync.mddescribes section ordering.Need help on this PR? Tag
@codesmith-botwith what you need.