Launch in PowerShell, terminal picker, per-session hide - #73
Open
Mr-Godot wants to merge 12 commits into
Open
Conversation
…butions, fix search ordering and silent failures P1 lib/scanner/live-sessions.ts: read ~/.claude/sessions/*.json (pid + status), filter by process.kill(pid,0) liveness, memoized 1s. Read-only. P2 active-detector: drop the mtime heuristic that marked months-dead sessions 'working'. isActive stays true only for 'working' so waiting sessions do not bypass the hidden-project filter, readmit stubs, or pin the 5s poll. The 3s active poll no longer runs a full 454-file scan. P3 stats-parser: cache per-session contributions keyed on a fingerprint that includes the subagent directory, killing the 11.1s re-parse per stats request. persistSummaryCache now skips the write when nothing changed. P4 search: stop swallowing provider failures into an empty result, log the real driver error, coalesce concurrent refreshes, yield during indexing, and stop the retry storm on a rejected search query. P5 search: order hits newest-first (ORDER BY ts DESC, sid tiebreak), make the representative row the most recent matching block so date and snippet agree, and apply the limit as a session count server-side.
…lation G liveness gaps: the 3s poll now applies the same busy-mtime rule as the scan, so a killed-while-busy session can no longer be flipped back to 'working'. Forced search refresh no longer no-ops behind an in-flight non-forced pass. P6 names: drop the ~/.claude/sessions/*.json name tier entirely (it contributed 163 duplicates, 1 wrong value, 15 derived junk names like 'agents-0a', and 0 unique correct values). The durable name is the JSONL custom-title. Fixes firstUserMessage for string-typed content and skips injected turns. SUMMARY_CACHE_VERSION 4 -> 5 to evict poisoned entries. P7 identity: resolve each project from its recorded cwd into a new realPath field (projectPath keeps its lookup meaning), disambiguate same-basename projects, flag dead paths instead of hiding them, and return 409 from the launch handler rather than spawning into a directory that no longer exists. P8 UI: shared EmptyState, honest loading affordance that keeps previous rows, suppressed stale-filter flash, surfaced search index staleness, and an unset theme preference now resolves to dark in both the pre-paint script and the provider. Also fixes a pre-existing data-loss bug: getCacheDir() ignored CLAUDE_HOME, so the e2e suite wrote to the real ~/.claude-dashboard and pruned the live search index to its 3 fixture sessions. All dashboard paths now derive from getDashboardDir(), which honours CLAUDE_HOME.
The Launch button wrote a .bat and hosted `claude --resume` under cmd.exe. Now writes a .ps1 and hosts it under PowerShell, preferring pwsh.exe when installed and falling back to powershell.exe. -ExecutionPolicy Bypass is required because the generated .ps1 is unsigned and lives in %TEMP%. -NoExit keeps the window open after claude exits. Shell selection and script generation are factored into powershell-launch.ts so the terminal picker in #72 can make them configurable. Closes #70
hidden existed only on ProjectMetadataEntrySchema, so hiding hid an entire project and there was no way to hide a single session. Adds hidden to SessionMetadataEntrySchema and filters on it independently of the project flag in both directions. Tracks two separate counts so the banner cannot double-count: sessions inside hidden projects, and sessions hidden one by one. Also adds the missing hide control to the session-detail view. No metadata version bump: the field is optional and additive. Refs #71
Adds a terminal profile picker. On the first Launch click with no saved preference the app asks which terminal to use, listing only what was detected on that OS, instead of picking silently. A profile is one allowlisted ID resolving to a fixed launcher, a fixed argv template and a script flavor. The stored value is never a path and never an argument, and the registry is a Map whose lookup rejects any miss, so a hand-edited settings.json degrades to asking the user rather than spawning something unexpected. Preference is keyed per platform so a synced settings.json does not make two machines overwrite each other's answer. Also drops resolvePowerShellExe, orphaned by the registry, along with its shell-interpolated where.exe probe. Closes #72
/api/launch-session spawned `claude --resume --dangerously-skip-permissions` for any POST that reached it, so any page in any open browser could drive it against a guessed session UUID. Validates Origin against the request's own Host rather than a hardcoded port, since the port varies (3030 in use, 3000 default, 3001 under e2e). Requiring Host itself to be loopback also blocks DNS rebinding. Missing and opaque Origins are rejected: browsers send Origin on every POST, so only non-browser callers omit it and none exist here. The check runs before a single byte of body is read.
PR Size Analysis
This label helps reviewers prioritize PRs by size. |
| ) | ||
| } | ||
|
|
||
| function DetailHideButton({ sessionId, hidden }: { sessionId: string; hidden: boolean }) { |
There was a problem hiding this comment.
Fast refresh only works when a file only exports components. Move your component(s) to a separate file. If all exports are HOCs, add them to the extraHOCs option.
Four independent defects made a renamed session unfindable. Fixed together
because any subset still leaves the user's literal queries returning nothing.
Hidden filtering ran before the search filter, so every session in a hidden
project was unreachable by any query. Hiding now applies only when the query
is empty; revealed rows carry a hidden-project or hidden-session chip and a
count line. SessionFilters dropped showHidden and starFirst on every
keystroke, which destroyed the manual workaround, so navigations now preserve
prior state.
The matcher lowercased the whole query and ran String.includes, so
'vector-crm-v2'.includes('vector crm') was false. Replaced with normalized
term-AND prefix matching: NFKD, combining marks stripped, every non
alphanumeric run folded to one space. Session id keeps a raw substring test
so an 8-char paste still works.
Session names never entered the FTS index and never rendered on result cards,
so the right session could be on screen and unrecognizable. Titles are now
indexed as an additive block_type row and shown on the cards.
Cross-session ordering was pure recency, so a name match could fall past the
result limit. Name matches now outrank body matches.
SCHEMA_VERSION stays at 1: the title row is additive, which avoids a rebuild
of the 214 MB index.
Verified against real data: 'vector crm' returns vector-crm, vector-crm-v2
and vector-crm-v3, and 'brain' surfaces its hidden matches.
The name-match rank was compared before the date, so sessions named Brain, brain-fix and hermes-brain came back ordered by how closely each name matched rather than by when they were last used. The grouping is now binary: a session either matched on its name (tiers 0 to 2) or it did not, and recency decides inside each group. Name matches still sort above body matches, which was the point of the earlier ranking change. Verified against real data: query 'brain' now returns brain-fix (Aug 3), hermes-brain (Jun 17), Brain-3 (Jun 4), Brain (Jun 3), BRAIN (May 19), BRAIN (May 15), then the body-only matches, each group newest first.
Adds a v1.7.0 changelog section, README sections for choosing a terminal, hiding one session versus a whole project, and how search now matches session names and surfaces matches inside hidden projects. Also removes every em-dash from README, CHANGELOG and the product spec.
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.
Closes #70, closes #71, closes #72.
What changed
Launch opens PowerShell, not cmd.exe (#70). Writes a
.ps1and hostsclaude --resumeunder PowerShell, preferringpwsh.exeand falling back topowershell.exe.-ExecutionPolicy Bypassis required because the generated script is unsigned and lives in%TEMP%.Terminal picker with a first-run prompt (#72). On the first Launch click with no saved preference, a modal asks which terminal to use, listing only what was detected on that OS. It never picks silently. A gear icon next to Launch and a Settings card change it later.
Design decisions worth knowing:
"auto"(explicitly chose automatic, never prompt again), or a profile ID. Absent is not a synonym for auto.settings.jsondoes not make two machines overwrite each other's answer and re-prompt forever.Hide works per session, not per project (#71).
hiddenexisted only onProjectMetadataEntrySchema, so hiding hid an entire folder with no way to hide one session. Session hide and project hide are now independent in both directions, with separate non-overlapping counts so the banner cannot double-count, and a hide control was added to the session-detail view where none existed.Cross-origin POSTs to the launch endpoint are rejected. Pre-existing, surfaced during the #72 design review.
/api/launch-sessionspawnsclaude --resume --dangerously-skip-permissionsand accepted any POST that reached it, so any page in an open browser could drive it against a guessed UUID. Origin is now validated against the request's own Host rather than a hardcoded port, before any body is read.Security shape of the picker
The stored preference is an allowlisted ID, never a path and never an argument. There is no free-text field anywhere in the feature. The registry is a
Mapwhose lookup rejects any miss, with no regex, trimming, case folding or whitespace splitting. A hand-editedsettings.jsoncontaining"C:\Users\Public\evil.exe"fails enum parsing and degrades to asking the user rather than spawning.spawn(exe, argsArray)runs with noshelloption anywhere in the launch path.launch-session.ts(UUID regex, cwd sanitization, dead-cwd guard) is unchanged.Verification
typecheck clean, lint 0 errors, 883 tests passing across 51 files (baseline was 790).
The Origin gate was verified live against the running dev server, not only by unit test: no Origin gives 403,
https://evil.comgives 403, the lookalikehttp://localhost.evil.com:3030gives 403, andhttp://localhost:3030passes through.Terminal detection was verified live on Windows: all six Windows profiles detected,
autoresolving to Windows Terminal with PowerShell 7, Git Bash resolved to its mintty path.Known gaps
npm run buildnot run, per the known Node v24 issue noted in CLAUDE.md.Note for the repo
CLAUDE.mdclaims architecture boundary tests live insrc/__tests__/architecture/and run in CI. That directory does not exist. Unrelated to this work, but the file is asserting a safety net that is not there.Design doc:
.plans/26-0806-terminal-picker-design.md(gitignored, local only).