···12121313- incorrect repository drag insertion indicators when dragging downward across expanded repositories
1414- unstable bulk expand/collapse animations
1515-- potential sidebar flicker during drag when live terminal / notification / ordering updates arrive
1515+- potential sidebar flicker during drag when live terminal, notification, or ordering updates arrive
16161717## Current Findings
1818···56565757The current `List` carries several behaviors at once:
58585959-- special rows: Canvas, Shelf, archived worktrees, repository list header
5959+- archived worktree selection and repository list header
6060- repository row selection for plain folders
6161- worktree multi-selection
6262- repository expand/collapse
···6565- reveal-in-sidebar via `ScrollViewReader.scrollTo`
6666- native sidebar styling and accessibility
67676868-This makes local fixes brittle because changing row structure affects selection, drag, animation, and scroll identity at the same time.
6868+Canvas, Shelf, and the footer are not `List` rows today; they are safe-area inset chrome around the list. The refactor should preserve that boundary unless a later design intentionally moves them into the scroll content.
69697070### 3. Live state still reaches rows during drag
7171···80808181These are not necessarily the root cause of the drop-indicator bug, but they are credible contributors to #222-style flicker.
82828383-## Recommended Direction
8383+## Revised Direction
84848585Use a custom sidebar scroll container rather than trying to keep the current flat `List` structure.
86868787+Execution order matters: first stabilize the old `List` path with a reducer-level drag gate, then replace the visual structure. The drag gate is a hard prerequisite because it reduces #222 risk before the broader #249 rewrite starts.
8888+8789Recommended shape:
88908991```text
9090-ScrollViewReader
9191-└── ScrollView
9292- └── LazyVStack or VStack
9393- ├── Special rows
9494- ├── RepositoryContainerRow
9595- │ ├── RepositoryHeaderRow
9696- │ └── WorktreeRows
9797- └── FailedRepositoryRow
9292+SidebarView chrome
9393+├── top safeAreaInset buttons
9494+│ ├── Canvas
9595+│ └── Shelf
9696+├── ScrollViewReader
9797+│ └── ScrollView
9898+│ └── LazyVStack or VStack
9999+│ ├── repository list header
100100+│ ├── RepositoryContainerRow
101101+│ │ ├── RepositoryHeaderRow
102102+│ │ └── WorktreeRows
103103+│ ├── FailedRepositoryRow
104104+│ └── ArchivedWorktreesRow
105105+└── bottom safeAreaInset footer
98106```
99107100108Key property: repository containers are the only repository-level siblings in the outer stack. Expanded worktrees are children inside the container, not siblings beside it.
···140148- must rebuild keyboard navigation, multi-selection, reorder, and accessibility affordances
141149- more implementation work
142150143143-This is the recommended route for #249 if the goal is "fix the sidebar design once" rather than patch one symptom.
151151+This remains the recommended route for #249 if the goal is "fix the sidebar design once" rather than patch one symptom.
144152145153### Option C: Short-term drag-time freeze only
146154···154162- does not fix repository insertion indicator because row boundaries remain wrong
155163- leaves the main structural mismatch in place
156164157157-This can be kept as a subset of Option B, but it is not enough alone.
165165+This is now the mandatory M1 prerequisite for Option B, not a replacement for Option B.
166166+167167+## Hard Requirements
168168+169169+The refactor must preserve these behavior and state contracts.
170170+171171+### Reducer actions and persistence
172172+173173+- Keep the existing reducer actions and persistence paths for repository and worktree ordering unless a later implementation note explicitly proves a rename is worth it.
174174+- Preserve calls behind repository reorder, pinned worktree reorder, unpinned worktree reorder, and notification-driven reorder.
175175+- Treat failed repository reorder semantics as an explicit product decision:
176176+ - either failed repository rows are reorderable and their order persists through the same root ordering path
177177+ - or they are not reorderable and the UI gives consistent feedback with no insertion target around them
178178+179179+### Expanded and collapsed state
180180+181181+- Preserve `@Shared` write-back semantics for collapsed repository IDs.
182182+- Preserve cleanup of invalid collapsed IDs when repository IDs change.
183183+- Ensure bulk expand/collapse and single expand/collapse share the same model path.
184184+185185+### Focused actions and selection synchronization
186186+187187+- Preserve `SidebarView` focused values for `confirmWorktreeAction`, `archiveWorktreeAction`, `deleteWorktreeAction`, and `visibleHotkeyWorktreeRows`.
188188+- Preserve the `sidebarSelections -> setSidebarSelectedWorktreeIDs` synchronization currently owned by `SidebarView`.
189189+- Do not regress menu commands or numbered worktree hotkeys when replacing `List(selection:)`.
190190+191191+### Existing row affordances
192192+193193+- Preserve repository and worktree context menus.
194194+- Preserve drag previews.
195195+- Preserve current worktree row type-select behavior. Worktree rows currently use `.typeSelectEquivalent("")`; V1 should keep type-select effectively disabled for those rows.
196196+- Preserve root-level `dropDestination(for: URL.self)` on the sidebar container, including drops into blank sidebar space.
197197+198198+### Ordered roots
199199+200200+- Converge the current `orderedRoots.isEmpty` fallback and non-empty custom-order path into one presentation path.
201201+- The empty ordered-roots case is a valid user state and must have tests.
158202159203## Proposed Architecture
160204161161-### SidebarPresentationModel
205205+### SidebarPresentation
162206163207Introduce a pure presentation model that flattens current repository state into explicit sidebar units.
164208···171215172216enum SidebarItem: Equatable, Identifiable {
173217 case listHeader(SidebarListHeaderModel)
174174- case special(SidebarSpecialRowModel)
175218 case repository(SidebarRepositoryContainerModel)
176219 case failedRepository(FailedRepositoryModel)
220220+ case archivedWorktrees(ArchivedWorktreesRowModel)
177221}
178222179223struct SidebarRepositoryContainerModel: Equatable, Identifiable {
···189233190234Rules:
191235236236+- build `SidebarPresentation` from reducer/state-side pure functions or equivalent helpers
192237- outer `items` contains one item per repository, not one item per row
193238- worktree sections remain inside the repository container
194194-- presentation construction should be pure and unit-tested
195195-- live terminal state should not be part of the broad presentation model unless required for layout identity
239239+- presentation construction is pure and unit-tested
240240+- high-frequency terminal notification/task/run-script state stays in leaf views, not in broad presentation state
241241+- Canvas, Shelf, and footer chrome remain outside `SidebarPresentation` in V1
196242197243### Selection
198244199245Replace `List(selection:)` with explicit selection handling.
200246201201-Keep `RepositoriesFeature.State.selection` and `sidebarSelectedWorktreeIDs` as the source of truth, but route clicks through helper functions:
247247+Keep `RepositoriesFeature.State.selection` and `sidebarSelectedWorktreeIDs` as the source of truth, but route clicks through helper functions.
202248203203-- repository header click:
204204- - plain folder: select repository
205205- - git repository: toggle expand by default, or select repository if a future repository-detail mode needs it
206206-- worktree row click:
207207- - normal click: select worktree and focus terminal
208208- - Cmd-click: toggle multi-selection
209209- - Shift-click: optional follow-up, only if current behavior supports it through `List`
210210-- Canvas / Shelf / Archived rows: dispatch existing actions
249249+Compatibility matrix:
250250+251251+| Interaction | State behavior | Focus behavior |
252252+| --- | --- | --- |
253253+| Canvas button | Selects Canvas and clears incompatible sidebar worktree selection. | Does not focus a terminal. |
254254+| Shelf button | Selects Shelf and clears incompatible sidebar worktree selection. | Does not focus a terminal. |
255255+| Archived worktrees row | Selects archived worktrees and clears incompatible worktree selection. | Does not focus a terminal. |
256256+| Git repository header click | Toggles expanded state by default. | Does not focus a terminal. |
257257+| Plain folder repository click | Selects the repository. | Does not focus a terminal unless current behavior already does. |
258258+| Worktree row normal click | Selects one worktree and updates sidebar selected worktree IDs to that one ID. | Focuses the terminal for the selected worktree. |
259259+| Worktree row Cmd-click | Toggles membership in sidebar selected worktree IDs, preserving multi-select priority. | Does not steal focus unless the resulting primary selection changes by existing rules. |
260260+| Empty sidebar selection | Clears sidebar selected worktree IDs. | Does not focus a terminal. |
211261212262Selection visuals should be explicit in `RepositoryHeaderRow` and `WorktreeRow`, not inherited from `List`.
213263···227277- command shortcuts still select worktrees
228278- selected row is scrolled into view on reveal
229279- focus returns to terminal after single worktree selection
230230-- sidebar focus does not accidentally forward text while Canvas / Shelf rules say it should not
280280+- sidebar focus does not accidentally forward text while Canvas, Shelf, or Archived rules say it should not
231281232282### Repository Reorder
233283···238288- make `RepositoryContainerRow` draggable with repository ID payload
239289- render a custom repository insertion indicator between repository containers
240290- compute drop destination as a repository index
241241-- dispatch existing `.worktreeOrdering(.repositoriesMoved(offsets, destination))` or a new clearer action such as `.repositoriesReordered([Repository.ID])`
291291+- dispatch existing repository-ordering actions or a new reducer action that delegates to the same persistence path
242292243293The custom indicator should always render at repository container boundaries:
244294···267317268318### Drag-Time Freeze
269319270270-Add a small sidebar drag state to suppress non-essential row churn.
320320+Add sidebar drag state at reducer level and use it in both the old and new sidebar paths.
271321272322During any sidebar drag:
273323···276326- suppress row-ID animations caused by notification-driven reorder
277327- defer "move notified worktree to top" until drag ends, or apply it without animation after drop
278328329329+Reducer behavior:
330330+331331+- drag begin records that sidebar drag is active
332332+- `worktreeNotificationReceived` while drag is active records pending reorder IDs instead of mutating row order immediately
333333+- drag end flushes pending notification reorders in deterministic order, dropping stale worktree IDs
334334+- `moveNotifiedWorktreeToTop == false` remains a no-op
335335+279336This addresses #222 without requiring every live data read to stop.
280337281338### Expand / Collapse
···295352296353- repository container: `SidebarScrollID.repository(repositoryID)`
297354- worktree row: `SidebarScrollID.worktree(worktreeID)`
298298-- special rows: `SidebarScrollID.canvas`, etc.
355355+- archived worktrees row: `SidebarScrollID.archivedWorktrees`
299356300357When revealing a collapsed worktree:
3013583023591. expand its repository
303303-2. yield for layout materialization
360360+2. wait for an event-driven row availability signal
3043613. scroll to `SidebarScrollID.worktree(worktreeID)`
3053624. consume pending reveal
306363307307-This matches the current two-yield approach but removes dependency on `List` row materialization.
364364+Do not rely on a fixed number of `Task.yield()` calls in the new architecture. The implementation can use a scroll target registry, preference key, or equivalent view materialization signal.
308365309366### Accessibility
310367···328385 - sidebar multi-selection
329386 - reveal-in-sidebar
330387- Add signposts around sidebar presentation build and drag state transitions if trace work is needed.
331331-- Keep current `List` code untouched until presentation tests exist.
388388+- Establish `LazyVStack` vs `VStack` decision metrics before replacing the list:
389389+ - expand/collapse latency for 10+ repositories
390390+ - frame stability during repository drag
391391+ - CPU peak during drag and bulk expand/collapse
392392+ - body recomputation count for repository container and worktree row views
393393+- Keep current `List` code untouched until M1 and presentation tests exist.
394394+395395+### M1: Stabilize Old `List` Drag Behavior
396396+397397+Files likely involved:
398398+399399+- `supacode/Features/Repositories/Reducer/RepositoriesFeature.swift`
400400+- `supacode/Features/Repositories/Reducer/RepositoriesFeature+WorktreeOrdering.swift`
401401+- `supacode/Features/Repositories/Views/SidebarListView.swift`
402402+- `supacodeTests/RepositoriesFeatureTests.swift`
403403+404404+Deliver:
405405+406406+- reducer-level sidebar drag state
407407+- view action for drag begin/end from the old `List` path
408408+- delayed or no-animation handling for notification-driven reorder during drag
409409+- deterministic pending reorder flush on drag end
410410+411411+Tests:
412412+413413+- notification during sidebar drag does not mutate visible worktree order immediately
414414+- drag end applies the pending notification reorder in deterministic order
415415+- multiple notifications during one drag produce stable ordering
416416+- stale pending worktree IDs are ignored
417417+- `moveNotifiedWorktreeToTop == false` remains a no-op
418418+- persistence is called only when the reorder is actually applied
332419333420### Phase 1: Pure Presentation and Reorder Mapping
334421···343430- pure sidebar presentation builder
344431- stable scroll IDs
345432- pure drop-destination mapping for repository and worktree reorder
346346-- tests for:
347347- - expanded repository keeps one outer item with child rows
348348- - failed repositories preserve order
349349- - plain folders produce repository containers with no worktree children
350350- - pinned/main/pending/unpinned sections are preserved
351351- - repository drop destinations map to expected order
352352- - worktree drop destinations map within pinned/unpinned sections
433433+- one unified presentation path for empty and non-empty ordered roots
434434+- explicit failed repository row reorder semantics
435435+436436+Tests:
437437+438438+- expanded repository keeps one outer item with child rows
439439+- failed repositories preserve the chosen reorder semantics
440440+- plain folders produce repository containers with no worktree children
441441+- pinned/main/pending/unpinned sections are preserved
442442+- empty ordered roots and custom ordered roots produce equivalent presentation rules
443443+- repository drop destinations map to expected order
444444+- worktree drop destinations map within pinned/unpinned sections
353445354446### Phase 2: New Container Views Behind a Switch
355447···366458- render the new container sidebar behind a local compile-time or private runtime switch
367459- no reducer changes except new presentation helpers if needed
368460- preserve row styling visually before enabling custom drag/drop
461461+- preserve root-level URL drop for files dragged into blank sidebar space
462462+- preserve context menus and drag previews
369463370464This phase should be screenshot/manual verified before deleting the old `List` path.
371465372372-### Phase 3: Explicit Selection and Reveal
466466+### Phase 3: Explicit Selection, Focus, and Reveal
373467374468Deliver:
375469376470- click handling for repository and worktree rows
377471- explicit selection visuals
378378-- multi-selection behavior matching current sidebar expectations
379379-- reveal-in-sidebar via new scroll IDs
472472+- multi-selection behavior matching the compatibility matrix
473473+- `sidebarSelections -> setSidebarSelectedWorktreeIDs` synchronization
474474+- focused actions and hotkey row values
475475+- reveal-in-sidebar via new scroll IDs and row availability events
380476- focused terminal handoff after single worktree selection
381477382478Tests:
383479384384-- pure selection helper tests if logic is factored out
385385-- existing reducer selection tests should keep passing
480480+- pure selection helper tests
481481+- reducer tests for sidebar selected worktree synchronization
482482+- focused action manual checklist for confirm/archive/delete and numbered hotkeys
386483387484### Phase 4: Custom Repository Reorder
388485···390487391488- repository drag payload
392489- custom repo-level insertion indicator
393393-- drop handling that dispatches repository reorder
490490+- drop handling that dispatches repository reorder through the existing persistence path
394491- drag-time UI freeze for non-essential row affordances
395492396493Manual verification:
397494398495- dragging a repository upward shows indicator below the target repository container when appropriate
399496- dragging a repository downward never shows the indicator between target header and target worktree rows
400400-- failed repository rows either reorder correctly or are explicitly non-reorderable
497497+- failed repository rows follow the documented reorder semantics
401498402499### Phase 5: Custom Worktree Reorder
403500···429526Automated:
430527431528- `SidebarPresentationTests`
529529+- reducer tests for sidebar drag gate and notification reorder concurrency
530530+- reducer tests for expanded/collapsed state write-back and invalid collapsed ID cleanup
531531+- reducer tests for sidebar selected worktree synchronization
432532- existing `RepositoriesFeatureTests` ordering tests
433533- existing `RepositorySectionViewTests` migrated or renamed
434534- `make check`
···437537Manual:
4385384395391. Select a plain folder repository row.
440440-2. Select a git repository worktree row and confirm terminal focus.
441441-3. Cmd-click multiple worktree rows and confirm bulk archive/delete commands still target selected rows.
442442-4. Expand/collapse one repository.
443443-5. Bulk expand/collapse at least 10 repositories.
444444-6. Drag repository upward and downward across expanded repositories.
445445-7. Drag pinned worktrees within a repository.
446446-8. Drag unpinned worktrees within a repository.
447447-9. Trigger reveal-in-sidebar from Canvas or command.
448448-10. Verify Canvas / Shelf / Archived rows remain selectable.
449449-11. Verify notification/task/run-script indicators update without moving rows during a drag.
540540+2. Click a git repository header and confirm it expands/collapses without selecting a worktree.
541541+3. Select a git repository worktree row and confirm terminal focus.
542542+4. Cmd-click multiple worktree rows and confirm bulk archive/delete commands still target selected rows.
543543+5. Verify confirm/archive/delete menu commands target the same worktrees as before.
544544+6. Verify numbered worktree hotkeys use visible sidebar rows.
545545+7. Expand/collapse one repository.
546546+8. Bulk expand/collapse at least 10 repositories.
547547+9. Drag repository upward and downward across expanded repositories.
548548+10. Drag pinned worktrees within a repository.
549549+11. Drag unpinned worktrees within a repository.
550550+12. Trigger reveal-in-sidebar from Canvas or command.
551551+13. Verify Canvas / Shelf / Archived interactions remain correct.
552552+14. Verify notification/task/run-script indicators update without moving rows during a drag.
553553+15. Drop a repository URL onto a visible row and onto blank sidebar space.
554554+16. Verify repository and worktree context menus.
555555+17. Verify drag previews.
556556+18. Verify worktree rows do not gain type-select behavior in V1.
450557451558## Risks
452559···477584Mitigation:
478585479586- stage behind a private switch until visual behavior is verified
480480-- land presentation model tests first
587587+- land M1 and presentation model tests first
481588- keep reducer actions and persistence shape stable
482589483590### Performance regressions
···488595489596- start with `LazyVStack`
490597- switch only repository containers to non-lazy child stacks if expand/collapse animation needs it
491491-- measure with the existing signpost / Instruments workflow if the sidebar feels worse
598598+- decide using the Phase 0 metrics rather than visual impression alone
492599493600## Recommendation
494601···498605499606The safest execution path is:
500607501501-1. pure presentation model and tests
502502-2. render-only new sidebar path
503503-3. explicit selection/reveal
504504-4. custom repository reorder
505505-5. custom worktree reorder
506506-6. remove old `List` path
608608+1. baseline metrics and manual guardrails
609609+2. M1 old `List` drag gate and reducer concurrency tests
610610+3. pure presentation model and tests
611611+4. render-only new sidebar path
612612+5. explicit selection, focus, and reveal
613613+6. custom repository reorder
614614+7. custom worktree reorder
615615+8. remove old `List` path
507616508617This is larger than a tactical #222 fix, but it addresses the underlying sidebar design mismatch and gives future sidebar features a cleaner foundation.