feat(cli): interactive watcher manager TUI (cix watch manage)#139
Open
dvcdsys wants to merge 2 commits into
Open
feat(cli): interactive watcher manager TUI (cix watch manage)#139dvcdsys wants to merge 2 commits into
cix watch manage)#139dvcdsys wants to merge 2 commits into
Conversation
Managing watchers was fragmented: `cix watch list` printed a static snapshot and stopping one meant re-typing its path in a separate command. There was no restart at all, and no single place to see every watcher and act on individual ones. Add `cix watch manage` (alias `ui`) — a bubbletea TUI mirroring internal/config/tui. Rows union the local known-project list (~/.cix/config.yaml `projects:`) with the running daemons, so listing, stopping and toggling need no server; the server is only a preflight on start plus an optional last-indexed enrichment. The list is windowed so long project lists stay navigable on short terminals. Keys: s stop · r restart · S start · a/space toggle auto_watch · A start all auto_watch · x stop all · d delete project (confirmed) · l/enter tail log · g refresh · ? help. Daemon actions run synchronously inside Update (they are local and microsecond); only the network last-indexed load is a tea.Cmd, and it only returns a message, so a tick can never race an in-flight action. Also: - client: add DeleteProject, mapping 404 to ErrProjectGone so deleting an already-deleted project is an idempotent success. Delete stops the watcher first, so a reindex cannot re-create the project between the server delete and the local cleanup; a non-404 server error aborts before local state is touched. - watchtui: GuardedStart (server reachable + project registered) is now shared by `cix watch` and the TUI — runWatchDaemon reuses it instead of duplicating the preflight. - config: fix AddProject silently dropping an auto_watch update. It assigned to a range-loop copy and then saved the unchanged config, so the flag never changed for an already-registered project. This also makes `cix init -w=...` persist on re-run. auto_watch was previously inert — nothing read it — and `A` now acts on it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review follow-ups on the watcher manager: - start / restart / start-all-auto / delete now run as background tea.Cmds behind a single-flight busy gate. They preflight (or delete on) the server, and the HTTP client timeout is 600s — running them inside Update froze rendering and input for the whole wait when the server was slow or unreachable. Local actions (stop, stop-all, auto_watch toggle) stay synchronous. - StartAllAuto probes /health once per batch instead of once per pending project; the server-down worst case was one connect timeout multiplied by every auto_watch project. - The last-indexed enrichment is single-flight: the 2s tick no longer stacks another hung ListProjects call behind a stalled server. - readTail seeks to the last 64 KiB of the watcher log instead of reading the whole file (logs are append-only and never rotated), and drops the partial line at the cut. Verified with go test -race, plus a pty run of the real binary: first frame, help overlay, delete-confirm cancel path, clean quit. Co-Authored-By: Claude Fable 5 <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.
What
Adds
cix watch manage(aliasui) — a full-screen TUI to centrally manage file-watcher daemons — plus aclient.DeleteProjectmethod and a bugfix inconfig.AddProject.Keys:
sstop ·rrestart ·Sstart ·a/spacetoggleauto_watch·Astart allauto_watch·xstop all ·ddelete project (confirmed) ·l/entertail log ·grefresh ·?help.Why
Watcher management was fragmented and non-interactive:
cix watch listprinted a static snapshot, and stopping one meant re-typing its path in a separatecix watch stop <path>. There was no restart command at all, and no single place to see every watcher and act on individual ones.Separately,
ProjectEntry.AutoWatchwas written bycix initbut no code ever read it — dead metadata. A project flaggedauto_watch: truenever came back after a reboot or awatch stop --all.How
Rows are local-first. The list unions the local known-project set (
~/.cix/config.yamlprojects:) with the running daemons (daemon.ListAll()). Listing, stopping, and toggling need no server; the server is only a preflight on start and an optional best-effortLastIndexedAtenrichment. The list is windowed (listWindow) so long project lists stay navigable on short terminals.Concurrency. Local actions (stop, stop-all,
auto_watchtoggle) are filesystem-only and microsecond-fast, so they run synchronously insideUpdate. Anything that talks to the server — the start/restart preflight,A(start-all-auto), delete, and the last-indexed enrichment — runs off the event loop as atea.Cmdthat only returns a message, never touches the Model: a slow or unreachable server (the client timeout is 600 s) can't freeze rendering or input. A single-flightbusygate rejects other mutating keys while a background action is running, the enrichment is single-flight too (a stalled server can't stack one hung request per 2 s tick), andStartAllAutoprobes/healthonce per batch instead of once per pending project. The log-tail overlay reads only the last 64 KiB of the (unrotated, append-only) watcher log.Preflight is load-bearing, not cosmetic.
daemon.Startreturns success on fork, not on child-survival — the child self-exits if the project isn't registered, leaving a phantom "running" row until the next prune.GuardedStart(server reachable + project registered) now gates every start and is shared:runWatchDaemonreuses it instead of duplicating the check.Delete ordering. Stop the watcher first (otherwise a reindex can re-create the project between the server delete and the local cleanup) → delete server-side → drop the local config entry and log. A 404 maps to
client.ErrProjectGoneand is treated as an idempotent success (already deleted server-side → still clean up locally). Any other server error (403/5xx/network) aborts before local state is touched, so a live project is never orphaned.drequires an explicityconfirmation.Authorization note:
DELETE /api/v1/projects/{hash}is guarded byrequireProjectOwnership(owner or admin), so the CLI's normal API key deletes the user's own local projects — no admin key needed and no new endpoint was added.Restart is
guard → stop → start(guard first, so a server-down restart never leaves the user with nothing running). It lives inwatchtui, notdaemon, because it needs the client —daemondeliberately doesn't import it.Bugfix:
config.AddProjectdroppedauto_watchupdatesUpdating the flag on an already-registered project silently did nothing. Fixed by indexing the slice, with a regression test (
TestAddProject_UpdatesAutoWatchOnExisting) that fails against the old code. Side benefit:cix init -w=...now persists on re-run.a/spacetoggles the flag;Afinally acts on it.Type of change
Checklist
go vet ./...passes (CLI changes)pytest tests/passes (API changes) — n/a, no API changesVerification
go build ./...,go vet ./..., fullgo test ./...green. 30 new tests: puremergeItems/listWindow, andUpdatedriven with synthetictea.KeyMsgfor every action (stop/start/restart/stop-all/start-auto/toggle/delete-confirm), incl. the confirm-cancel and error paths.Watchers — 3 running / 35 knownwith real PIDs andautotags,?opens help, arrow keys scroll the window (↑ 1 more … ↑ 14 more), clean quit, and a clean refusal (exit 1, no panic) when stdout is not a TTY.d(prompt rendered, then cancelled) — nothing was deleted.auto_watchtoggle verified end-to-end in an isolatedHOME: the flag flippedfalse → trueand persisted to disk, neighbouring project untouched.🤖 Generated with Claude Code