Skip to content

v0.21.17.0 fix(ui): remote-host projects no longer split into two headers - #70

Merged
avinashjoshi merged 2 commits into
mainfrom
smart-dolphin
Jun 26, 2026
Merged

v0.21.17.0 fix(ui): remote-host projects no longer split into two headers#70
avinashjoshi merged 2 commits into
mainfrom
smart-dolphin

Conversation

@avinashjoshi

Copy link
Copy Markdown
Owner

Summary

Fixes a Workspaces-tab bug where a remote host rendered each project twice: a lone (main) row under one project header, then the project's real workspaces under a second header for the same project. Every project on the host did it, so the list looked like it had twice as many projects as it does.

Root cause: a key mismatch in the row pipeline. The projectlist renderer emits project headers keyed on Project (basename), but sortRowsMineFirst grouped its contiguous runs on ProjectRoot (canonical path). Remote rows carry an empty ProjectRoot (omitempty, and older hosts' canopy ls --json never sent it), so the sort collapsed an entire host into one run and pinned every project's (main) row to the top — the renderer then split each project into a (main)-only header plus a separate workspaces header.

Fix: group the sort on (Host, Project) — the same key the renderer uses. The two can no longer disagree, and because Project is always populated on local and remote rows, every mixed-version fleet groups correctly (not just hosts running a new-enough canopy).

This was reached after an adversarial review of an earlier, narrower attempt (propagating ProjectRoot onto remote rows). That approach only fixed new-enough remotes and its test didn't actually exercise the changed line. This fix addresses the root-cause mismatch instead.

Test Coverage

All new code paths have test coverage.

  • TestSortRowsMineFirst_RemoteProjectsStayGrouped uses empty-ProjectRoot remote rows (matching production): it fails under the old ProjectRoot key (reproduces [(main) (main) free-robin kind-maple]) and passes under the new Project key. Verified by temporarily reverting the key during this ship.
  • TestSortRowsMineFirst and TestSortRowsMineFirst_KeepsSectionsSeparate updated to set Project (reflecting real rows) and still pass.

go build ./... clean. go test ./... — all packages pass.

Pre-Landing Review

Diff is 61 lines — specialists skipped (<50-line gate is for code; this is a single logical fix + tests). Always-on adversarial review (Claude subagent) ran and surfaced four findings on the original approach; all are resolved by the final fix:

  1. Test didn't exercise the changed line → final test fails under the old key, passes under the new (genuinely guards the fix).
  2. Sort key ≠ render key, and the narrow fix left older remotes broken → fixed at the root by grouping on the renderer's key.
  3. Stale "remote rows have empty ProjectRoot" comments → avoided; the final fix leaves ProjectRoot empty on remote rows, so those comments stay correct.
  4. New identity-check ambiguity from propagating ProjectRoot → avoided; no propagation in the final fix.

Scope Drift

Scope Check: CLEAN. Intent: stop remote-host projects splitting into two headers. Delivered: exactly that.

Plan Completion

No plan file detected.

TODOS

No TODO items completed in this PR.

Test plan

  • go build ./... clean
  • go test ./... — all packages pass
  • Regression test proven to fail under the old grouping key and pass under the new one

🤖 Generated with Claude Code

avinashjoshi and others added 2 commits June 25, 2026 22:44
sortRowsMineFirst grouped its contiguous runs by ProjectRoot, but the
projectlist renderer emits project headers keyed on Project (basename).
Remote rows carry an empty ProjectRoot (omitempty; older hosts'
`canopy ls --json` never sent it), so the sort collapsed an entire host
into one run and pinned every project's (main) row to the top — the
renderer then split each project into a "(main)"-only header plus a
separate workspaces header.

Group the sort on (Host, Project), the same key the renderer uses, so
the two can never disagree. Project is always populated on local and
remote rows, so every mixed-version fleet groups correctly.

Regression test uses empty-ProjectRoot remote rows: it fails under the
old ProjectRoot key and passes under the Project key.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@avinashjoshi
avinashjoshi merged commit 0fea6e1 into main Jun 26, 2026
3 of 4 checks passed
@avinashjoshi
avinashjoshi deleted the smart-dolphin branch June 26, 2026 05:55
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