v0.72: auto-spawn tickets, in-place updates, rewind fix#14
Merged
Conversation
… reconciliation
Extended the ticketStore EventType union with 'Idle' and slotted it into EVENT_PRIORITY at -1 so any real event (Notification=0, Stop=1, PermissionRequest=2) supersedes it through the existing upsert priority guard with no branch changes. Added findByPane(tmux_pane) — linear scan over the store — that hook handlers will call to locate and remove a pending:<pane_id> placeholder before upserting the authoritative ticket keyed by session_id.
On the page side, mirrored the union into the local EventType alias, added Idle branches to typeClass ('type-idle') and typeLabel ('IDLE'), short-circuited staleClass for Idle (the type-idle CSS already applies filter: saturate(0), so stacking stale-N tiers would be redundant), and added the .ticket.type-idle CSS rule alongside the other .type-* rules.
Phase 1 of the boot-time-session-enumeration plan; no behavior change yet because nothing upserts Idle tickets until Phase 2 wires SessionStart and Phase 4 wires the boot scan.
…e scan for complete dock coverage Phases 2-6 of the boot-time-session-enumeration plan land together because Phase 2's hook handler imports from the module Phase 3 creates; per-phase commits would leave broken builds. install.sh: restructured EVENTS into (event_name, matcher) tuples and registered SessionStart three times (matcher = startup / resume / clear) since the matcher field accepts only single exact strings. Reworked the Python settings.json merge so the dedupe key is (matcher, hook_script-in-command) instead of bare command — without it, the three SessionStart blocks would collapse to one. src/lib/server/sessionsStore.ts (new): loadSessions/recordSession/forgetSession/pruneStaleSessions backing ~/.expediter/sessions.json. Atomic temp+rename writes; the temp filename includes pid + monotonic counter + random suffix so concurrent writers don't share a temp path. Defensive per-entry shape validation drops malformed rows without losing the rest of the file. EXPEDITER_SESSIONS_FILE env var overrides the path for tests. src/lib/server/bootScan.ts (new): runBootScan walks `tmux list-panes -a`, filters to claude/claude.exe processes, prunes orphans from sessions.json, then for each live claude pane either upserts from a persisted entry, resolves a --name argv flag against ~/.claude/projects/<slug>/*.jsonl via latestCustomTitle (named-session path), or seeds a `pending:<pane_id>` placeholder ticket. parseName handles both --name=foo and --name foo, single and double quoted. All shell calls go through execFile (no shell). Wired into hooks.server.ts as `void runBootScan().catch(...)`, gated on NODE_ENV !== 'test' so test imports don't shell out. src/routes/api/hooks/event/+server.ts: new SessionStart branch (await recordSession, upsert Idle, fire-and-forget title pre-fill via latestCustomTitle). Extracted reconcilePlaceholder helper that removes any `pending:<pane>` ticket before upserting an authoritative one; called from SessionStart and from the existing Stop/PR/Notification path so pre-existing unnamed sessions reconcile on their first interaction regardless of event type. SessionEnd now awaits forgetSession. recordSession/forgetSession are awaited rather than fire-and-forget so the on-disk state is observable to the next request and can't race a subsequent write. ticketStore.ts (Phase 1, already committed): exposes findByPane used by reconcilePlaceholder; Idle event_type slots into EVENT_PRIORITY at -1. Tests: 197 pass, 0 fail across 13 files; svelte-check 0 errors. New tests cover Idle priority cases, findByPane, SessionStart upserts + recordSession side effect, placeholder reconciliation across all event branches, SessionEnd forgetSession, concurrent recordSession (parallel writers must not corrupt the file — fixed a real bug here where shared .tmp filenames stomped each other), pruneStaleSessions, slugify/parseName/parsePaneRows/isClaudePane/findSessionIdByName/upsertPlaceholder.
Detached panes were producing dead tickets — select-window / select-pane succeed but there's no Terminal.app window to bring forward. Added #{session_attached} to the tmux -F format, threaded session_attached through PaneRow, and gated the claude-pane filter on it. parsePaneRows now requires 5 columns; tests updated.
This reverts commit 3c90105.
upsertPlaceholder gained an optional title parameter; runBootScan now passes the parseName result through the fall-through call so an explicitly-named session no longer renders a whimsical stub when findSessionIdByName misses. Synthetic pending:<pane> key preserved. Added two regression tests.
The pgrep+argv+jsonl-scan path missed claude --resume (no --name on CLI) and was racy at boot. runBootScan now reads claude's own per-pid metadata files via readSessionMetas, walks each up with ps -o ppid= to find the owning tmux shell, and uses the metadata's sessionId and name directly. Removed claudeArgvFor, parseName, findSessionIdByName and reverted the upsertPlaceholder title param; added parseSessionMeta with unit tests.
runBootScan now consults the live ~/.claude/sessions/<pid>.json metadata before the persisted ~/.expediter/sessions.json entry. The old order let a stale persisted entry (left behind when a claude exited without firing SessionEnd in a still-live pane) mask the live session, so the dock keyed the ticket by the dead session_id and markWorking calls from the live hook payloads silently no-op'd. markWorking now lifts event_type from Idle to Stop on the way in. The .ticket.type-idle saturate(0) parent filter was desaturating the green working palette and the typeLabel kept reading "IDLE" while claude was actively processing.
Made runBootScan take injectable deps (listPanes/readSessionMetas/parentPid) so the metadata-vs-persisted ordering is testable. The detached-skip work rides along: listPanes now reads #{session_attached} and the seed loop skips detached panes, while keeping them in livePaneIds so persisted records aren't pruned.
…tickets update.sh replaces the uninstall+install cycle: rebuilds the app, rewrites the shims/config, re-copies the cc-clock/cc-dates helpers, and re-merges settings.json hooks (idempotent, backed up), skipping first-install checks and warning rather than aborting when the daemon is live. The ticket stub now shows COOKING instead of the event-type label while a ticket is working.
…r rewinds Replaced placeholder-only reconcilePlaceholder with dropPaneTicketsExcept (drops any ticket on a pane not keyed by the live session_id) and rebindPaneTicket (re-keys a pane's ticket to the live session_id before markWorking, so a rewind that diverged the session_id no longer leaves the ticket stuck idle). Cancels decline watchers for the displaced ids.
…mand update.sh now fast-forward pulls the current branch before rebuilding (new Sync phase), skipped by --dev/--no-pull, a dirty tree, missing .git, or non-ff history — it never forces or merges, falling back to building the checkout as-is. expediter.mjs gains an `update` subcommand that runs $EXPEDITER_HOME/update.sh with extra args passed through, dispatched before the daemon starts.
Updated the brand-version span in +page.svelte (v0.7 -> v0.72) and package.json (0.0.1 -> 0.72.0) for the v0.72 release.
Added an Update section (mirroring Install/Uninstall) covering `expediter update` and `./update.sh`, the fast-forward-only pull with the --dev/--no-pull escape hatch, and the post-update daemon restart.
Swapped em dashes for hyphens, commas, and parentheses across update.sh (all new this release) and the three lines added for the `expediter update` subcommand in expediter.mjs. Pre-existing author comments elsewhere in expediter.mjs were left untouched.
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.
v0.72
New
SessionStarthook plus~/.claude/sessions/<pid>.jsonpersistence keep them identified, and placeholders reconcile to the real ticket on the first hook event.expediter update/update.sh. Update in place instead of uninstall + reinstall. Pulls the latest (fast-forward only), rebuilds, and re-syncs the shims, helpers, and hook entries. Pass--dev/--no-pullto skip the pull (for contributors on a branch). New README "Update" section documents it.Fixed
Known issues
expediter update.