Skip to content

feat(THI-243): grouping mode toggle — discovery view (MVP, ListView) - #111

Merged
tdody merged 7 commits into
mainfrom
thibaultdody/thi-243-grouping-toggle
Jun 7, 2026
Merged

feat(THI-243): grouping mode toggle — discovery view (MVP, ListView)#111
tdody merged 7 commits into
mainfrom
thibaultdody/thi-243-grouping-toggle

Conversation

@tdody

@tdody tdody commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a global grouping-mode toggle to the dashboard:

  • Sessions (historical) — group panes by tmux session.
  • Repos (discovery) — group panes under their git repo, derived per pane via git rev-parse --show-toplevel on the backend. Each window is placed in the bucket of its own repoKey; a session that spans multiple repos appears under each repo it touches. Non-git windows land in an "Other" bucket pinned to the bottom.

This PR lands all the infrastructure (backend repo field, setting, pure helper, toggle UI) plus ListView re-rendering. Kanban and Grid follow in separate PRs once the simpler List-mode discovery feel is validated.

What ships

Backend

  • Window schema gains repo_key + repo_label.
  • New _git_repo_root(cwd) helper in claude_parser.py, TTL-cached 60 s (mirrors the existing branch / repo-url cache patterns).
  • tmux.collect_state populates the fields per pane; repo_label is basename(repo_key.rstrip("/")).
  • 5 new pytest tests cover the helper (git / non-git / empty / TTL boundary / schema carry).

Frontend

  • types.Window gains repoKey / repoLabel. Local test factories all updated.
  • New setting groupingMode: "sessions" | "repos" (default "sessions" — zero behavior change for existing users).
  • Subhead: new segmented chip pair ("Sessions" / "Repos") next to the layout switcher.
  • New pure helper lib/groupByRepo.ts: per-window bucketing by repoKey; non-git windows in "Other" pinned to the bottom. 8 unit tests for the rules.
  • ListView accepts a groupingMode prop. In "repos" mode it renders a repo header row before each group's windows. 3 new tests for the discovery rendering.

Design change vs. the original THI-243 spec

The original spec made tmux sessions atomic — one session, one repo, first-seen wins. In practice that breaks daily-driver sessions that mix projects: every window in the session inherits the first window's repo, so e.g. a session whose first pane is in ~/faraday/fdy puts every later switchboard / periscope pane under "fdy" too.

This PR drops the atomic rule. Each window goes into its own repo's bucket; sessions fragment across groups as needed. The cost — the user can no longer think purely in sessions inside the discovery view — is exactly the trade-off the discovery view is meant to make.

Explicitly deferred (separate tickets)

  • Kanban swim-lane axis (status × repo grid). 2D rendering is a substantial UI rework; better as its own PR.
  • Grid section headers per repo. Same: ship List first, mirror to Grid in a follow-up.
  • Drag-reorder persistence (repo_order, sessions_by_repo) — YAGNI for v1; "Other" pinned + first-seen for repos is enough.
  • Sidebar bullet from the spec — Switchboard has no sidebar.
  • Per-session git meta in discovery mode — already shown per pane via the existing branch field.

Test plan (automated)

  • Backend: 379 tests pass (uv run pytest); ruff check + ruff format --check: clean.
  • Frontend: 562 tests pass (npm test); typecheck + build clean.
  • New tests: 5 backend + 8 groupByRepo + 2 Subhead switcher + 3 ListView discovery = 18 new tests.

Manual smoke (needs a live session — only the user can run this)

Open the dashboard with at least two tmux sessions, where some panes' cwds are inside git repos and some aren't:

  • Subhead: the new Sessions / Repos segmented chip is visible next to the layout switcher.
  • Sessions is highlighted by default; clicking Repos flips the active state.
  • Switch to List layout. In "repos" mode, rows are grouped under repo header rows; windows with no git-repo cwd appear under an "Other" header at the bottom.
  • A repo's basename shows as the header label; hovering the header reveals the full path tooltip.
  • A session whose windows span multiple repos shows windows under each respective repo (sessions are no longer atomic).
  • Flip back to Sessions mode: list returns to the legacy flat rendering, no headers.
  • Reload the page: grouping-mode choice persists.
  • Switch to Kanban / Grid / Sessions-mode List — none of those views' behavior should have changed.

🤖 Generated with Claude Code

tdody and others added 7 commits June 7, 2026 09:50
Adds a global grouping-mode toggle that flips between two ways of
organizing the dashboard:

1. "sessions" — the historical behavior, group by tmux session.
2. "repos" — discovery view, group panes under their git repo,
   sessions atomic to one repo via first-seen-wins.

Lands all the infrastructure (backend field, setting, pure helper,
toggle UI) plus ListView re-rendering in discovery mode. Kanban /
Grid follow in separate PRs once the simpler List discovery feel is
validated.

Backend:
- Window schema gains `repo_key` + `repo_label`. Derived per pane via
  `git rev-parse --show-toplevel`, TTL-cached 60 s. None for non-git
  cwds; basename of toplevel for the label.

Frontend:
- types.Window: new repoKey/repoLabel fields, also added to every
  local test factory.
- Setting `groupingMode: "sessions" | "repos"`, default "sessions".
- Segmented chip pair in Subhead (next to the layout switcher).
- New pure helper lib/groupByRepo.ts: session→repo first-seen-wins
  assignment, sessions atomic, "Other" pinned to bottom for
  non-git-resolvable sessions. 8 unit tests.
- ListView accepts groupingMode prop; in "repos" mode renders a repo
  header row before each group's windows. 3 new tests for the
  discovery rendering.

Explicitly deferred (separate tickets):
- Kanban swim-lane axis (status × repo grid).
- Grid section headers per repo.
- Drag-reorder persistence (`repo_order`, `sessions_by_repo`).

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

The original spec pinned every tmux session to a single repo (first-seen
wins), reasoning that "the user thinks in sessions; splitting fragments
the UX." In practice that breaks down for catch-all daily-driver sessions
that mix projects — every window in the session gets misattributed to
whatever repo its first window happened to open in.

Drop the atomic rule. Each window goes to the bucket of its own
`repoKey`. A session that spans multiple repos now appears under each
repo it has a window in. Non-git windows still go to "Other"; "Other"
still pins to the bottom.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sessions mode previously rendered a flat list with the session as a small
prefix on each row. Mirror the repos-mode treatment: one header per
session with the count of windows below it, sharing the .list-group DOM
structure so both modes use the same CSS.

The repo-grouped classes are renamed `.list-repo-*` → `.list-group-*` so
the shared structure is no longer named for one of its two consumers.

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

Both views currently ignore groupingMode (the switcher's only consumers
are ListView and the Split rail). Hiding the toggle when the active
layout is kanban or grid avoids the silent-no-op UX. The persisted
groupingMode value is preserved, so flipping back to List restores the
user's prior choice.

Tracked as v0.4 follow-ups: THI-247 (Kanban swim-lane) and THI-248
(Grid section headers). When those land, drop the gate in
GroupingSwitcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three sibling tests in test_ws.py — non_resize_json_falls_through,
signal_control_message_routes, plain_text_is_pasted — assert on a list
populated by the recv loop, then exit the `with` block. Since THI-184
offloaded the recv-loop tmux calls onto asyncio.to_thread, the WS close
at the end of the block can race the thread hop and the assertion sees
an empty list. Observed in CI run 27107086903 on the THI-243 branch
(test_ws_plain_text_is_pasted_as_keys, [] vs ['abc']).

Apply the same poll-until-condition pattern already used in the resize
tests (08865e2) and the recv-loop offload regression test (64559df).
Local runs unaffected; CI runners stop flaking.

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

Five small improvements after a CI failure slipped through on the
THI-243 branch (the hook hadn't run because core.hooksPath had been
clobbered to .git/hooks; nothing in the hook itself caught that).

- **Path banner.** Echo the resolved hook path on every invocation so
  it's obvious from terminal output that the version-controlled hook
  ran (and which copy of it). If the banner is missing, the hook
  isn't installed.
- **Prerequisite check.** Bail early with a clear message if `uv` or
  `npx` is missing, instead of failing mid-run on the first command.
- **Read git's stdin for the actual push range.** git feeds the hook
  `<local_ref> <local_sha> <remote_ref> <remote_sha>` per ref. Use
  that range instead of `@{push}` so first pushes (remote_sha=zeros)
  still narrow against `origin/HEAD`'s merge-base instead of falling
  back to run-everything. Manual invocations (stdin empty) keep the
  old `@{push}` fallback.
- **Keep logs on failure.** A failing step's full log path is now
  printed and the temp dir is preserved so you can `cat $log_dir/…log`
  for more than the last 40 lines.
- **CI-flake reminder on success.** Add one dim-text line noting that
  a green pre-push does NOT prove CI will pass — timing-sensitive
  tests can still flake on slower CI runners, and the right response
  is to fix the flake, not retry the job.

Install (idempotent — either suffices):
  git config core.hooksPath scripts/hooks
  ln -sf ../../scripts/hooks/pre-push .git/hooks/pre-push

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tdody
tdody merged commit 60ad67f into main Jun 7, 2026
3 checks passed
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