Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "downrange",
"version": "1.1.6",
"version": "1.1.7",
"description": "A band, pane, and /downrange table: progress, ETA, and last output for Claude Code background shells and Monitor watches, tool activity for subagents, state for workflows, and the next /loop wakeup.",
"author": { "name": "dot-agi" },
"license": "MIT",
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ Run all of them after each change. All must pass.
- Write event names in `on(...)` as string literals. Write each matcher value as a literal or as a constant of `hooks/register.tsx`: `claude plugin validate` shows an imported constant as `?`. Use static imports only. Hooks run with no DOM and no Node.
- Export every type in `types/index.d.ts`. `claude plugin validate` refuses a contract type without `export`.
- Keep the other modules pure: no `$`, no I/O, and no clock reads. Pass `now` in.
- Keep the jobs and the wakeup in module memory, `memory`: it is the source of truth, and it survives `/clear`, `/resume`, and `/branch`. `$.state` holds a mirror of the current session's view for drawing, and **Hide** and the last answered status prompt as atoms. The only module variables are `ticker`, `isTicking`, `lastTickAt`, `memory`, `since`, `sessions`, `switchedAt`, `mirror`, `historyWrites`, `reported`, and `isAnyRatio`, the `genericRatios` option that `register` sets.
- Keep the jobs and the wakeup in module memory, `memory`: it is the source of truth, and it survives `/clear`, `/resume`, and `/branch`. `$.state` holds a mirror of the current session's view for drawing, and **Hide** and the last answered status prompt as atoms. The only module variables are `ticker`, `isTicking`, `lastTickAt`, `memory`, `since`, `sessions`, `switchedAt`, `pending`, `mirror`, `historyWrites`, `reported`, and `isAnyRatio`, the `genericRatios` option that `register` sets.
- Change `memory.jobs` only through `writeJobs` and `memory.wakeup` only through `writeWakeup`: they change memory at once, then write the mirror through `mirrored`, wait until a write that holds the change has landed, and keep the ticker current. A failed write is no failure of the hook (see `persisted`): memory took the change, so the rest of the hook runs. Each hook passes one `deadlineOf()` to all its writes: it waits `MIRROR_WAIT_MS` at most in all, and a wait that passes its deadline leaves the write's waiters. A reload of the mod empties module variables and keeps `$.state`: only `loaded`, at the first `session.start`, change of memory, or mirror write, reads the jobs and the wakeup from `$.state` into memory. Each change of memory waits for that read and applies on top of it, and no mirror write goes out before it. The mirror keeps a removal for each ended job that the session does not show (see `viewedJobs`). Read the mirror only to draw: in a render hook, `/downrange`, and the status answer.
- Settle each `/clear`, `/resume`, and `/branch` in two ways. `classic.SessionStart` is the main way. Under managed settings or a Team or Enterprise login, Claude Code's built-in guard keeps every `classic.*` event from the mod, so `switched` is the second way. `session.end` sets `pending`. Every hook other than `session.start`, `session.end`, `classic.SessionStart`, and the render hooks calls `switched` first, and so does each tick. A `command.run` hook for every command calls `switched`, then passes the command on unchanged, so that a command that reloads the mod, such as `/reload-plugins`, finds the view in `$.state`. A `config.set` hook for every key calls `switched` before a `/config` row changes, because a change of an option reloads the mod. It returns `next(e)` unchanged: it never refuses a change and never changes a value. A settle clears `pending` before its write lands, so `mirror.settle` holds the write of each settle, by `switched` or by `classic.SessionStart`, until it lands or fails. Both hooks call `settleLanded` after `switched`: it waits for that write within the hook's deadline, and with no settle in progress it waits for nothing. Then, when the last mirror write failed (`mirror.isBehind`), both write the view once more through `persisted`, within the same deadline. A failed write there is no failure of the hook, and with no write behind they write nothing. `session.end` also runs `switched` once at once, with `$.clock.after(0)` (see `settledAtOnce`), because the first tick can be a second away. Both ways must give a session the same start, and `classic.SessionStart` must keep every change that came after `switched`. Never settle a `/branch` before `classic.SessionStart`: during that event, `$.session.id()` still answers the parent's id.
- Render hooks never write state. Buttons write from their press handlers.
- Give every `on(...)` registration a `.catch` that logs the failure under the hook's label, such as `tool.call{tool=Bash}`, and passes the event on with `next(e)`: write it as a function literal that calls `passOn`. `claude plugin validate` refuses a `.catch` that is not a function literal or the name of one. The `/downrange` command and the pane answer with the cause instead.
- Start every diagnostic with `downrange: <event>: `, then the cause. Never log output text, prompt text, or a command line.
Expand Down
Loading