feat(THI-243): grouping mode toggle — discovery view (MVP, ListView) - #111
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a global grouping-mode toggle to the dashboard:
git rev-parse --show-toplevelon the backend. Each window is placed in the bucket of its ownrepoKey; 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
Windowschema gainsrepo_key+repo_label._git_repo_root(cwd)helper inclaude_parser.py, TTL-cached 60 s (mirrors the existing branch / repo-url cache patterns).tmux.collect_statepopulates the fields per pane;repo_labelisbasename(repo_key.rstrip("/")).Frontend
types.WindowgainsrepoKey/repoLabel. Local test factories all updated.groupingMode: "sessions" | "repos"(default"sessions"— zero behavior change for existing users).Subhead: new segmented chip pair ("Sessions" / "Repos") next to the layout switcher.lib/groupByRepo.ts: per-window bucketing byrepoKey; non-git windows in "Other" pinned to the bottom. 8 unit tests for the rules.ListViewaccepts agroupingModeprop. 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/fdyputs 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)
repo_order,sessions_by_repo) — YAGNI for v1; "Other" pinned + first-seen for repos is enough.branchfield.Test plan (automated)
uv run pytest);ruff check+ruff format --check: clean.npm test); typecheck + build clean.groupByRepo+ 2Subheadswitcher + 3ListViewdiscovery = 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:
"repos"mode, rows are grouped under repo header rows; windows with no git-repo cwd appear under an "Other" header at the bottom.🤖 Generated with Claude Code