Skip to content

Notifications and the models pit, integrated (supersedes #33, #34) - #35

Merged
jack-work merged 6 commits into
mainfrom
feat/pits
Sep 30, 2026
Merged

jack-work merged 6 commits into
mainfrom
feat/pits

Conversation

@jack-work

Copy link
Copy Markdown
Owner

Both branches merged into one, so there is one thing to validate. Worktree: ~/dev/figaro-qua/pits.

The merge

  • The one conflict: both branches added a case to the pager's own verbs in the same place. Both are kept.
  • Checked beyond the conflict:
    • The models view claims no keys of its own, so notifications' change that lets a list view take keys first leaves it alone.
    • Its results (in.note) and failures (in.noteErr) are now recorded in notifications at the right level.

Found by integrating, and fixed (654d9994): the models pit shows "…models" while it loads, and every routed verb shows "…verb" while it runs. With every note kept, each :ls became two history rows, one of them a progress indicator. Progress now goes to the bar only (setProgressNote / in.progress), and the result that follows is what's recorded. #34 had this flaw on its own too.

Verified on the flake build of the merged commit

  • Full suite green, 50 packages.
  • notifications-pty.sh: 14/14.
  • tabpit-pty.sh: 12/12.
  • Real session (isolated .#sandbox devshell, real credentials): :models → /claude-sonnet-5-5 → Enter. The bar confirmed it, space n showed exactly one row for the switch, and the next turn answered (200) with the board on claude-sonnet-5-5.

gluck added 5 commits September 30, 2026 16:41
`:models` in the command box printed the table `figaro models` prints, with
os.Stdout: round the pager's writer, onto the terminal. Nothing on it could be
acted on. It is now an itemised live view (itemView, like the form and the
queue), so the pit's one picker drives it: ^N/^P and j/k move, the wash and
the marker show the choice, `y` yanks the model id, `/` searches, G and gg
reach the ends of three hundred models. The subject's current model carries a
●, and each row carries its provider.

ENTER SETS THE MODEL, and its provider with it, in one patch: system.model to
the chosen id and system.provider to the provider that listed it. A model is
only meaningful to its own provider, and choosing copilot's model while the
board still said anthropic would send a name Anthropic has never heard of on
the next turn. When the provider already matches, the store drops the
unchanged key and the write is exactly `figaro set system.model <id>`. The
bar says what took; the ● moves. Activate hands off to a goroutine: it runs
under the render lock and the set dials.

`/` could not reach a provider: the heading is chrome and search walks
selectable rows. The picker now searches a row's note as well as its text --
what a reader sees is what `/` finds -- which also makes the tab pit's
descriptions searchable.

The standalone `figaro models` shares the fetch and now writes to the
package's stdout, so it can never paint over a pager again.

Verified with the flake build in an isolated .#sandbox shell, real
credentials: :models lists every configured provider's models, Enter on
claude-opus-5-5 set it (figaro state agrees) and the next turn was answered by
it; Enter on a copilot model set model and provider together and the next
turn was answered through copilot; Enter back on anthropic restored both.
…g it

Everything the pager said went through one slot, the first cell of the
status bar, which retires after notice_ttl. After that it existed nowhere
but one lastError string and the log file. A `𝄞` glyph for a notifications
pit had been reserved since August, and nothing opened it.

Now every alert is POSTED to a history first, and the bar and a pit are two
views of it:

  the ALERT  the newest one, in the bar's first slot, retiring as before
  the MARK   𝄞 N while warnings or errors are unread, red or yellow by the
             worst of them; it is what stays when the alert retires
  the PIT    `space n` or `:notifications`: the history, newest first, as
             the same picker every pit is

A row is time, level (✗ ! ·), source (cli or the aria it concerns) and text,
with ×N when it repeated: the same source, level and text, digits folded,
coalesce into one row that moves to the top and is unread again, as Gluck's
Neovim msg_router coalesces. Enter expands a row with what it could not hold;
`a` attends the row's aria; `y` yanks it; `f` cycles all, warnings and up,
errors; `/` searches. Opening it reads it. `:notifications clear` empties the
pager's copy, as Vim's `:messages clear` does.

Space is a leader: `space n` is `<leader>n`, which is where Gluck's Neovim
opens its notification history. n stays search-repeat. A leader arms and
waits, so space no longer falls through to a panel's any-key dismissal: the
keymap oracles record that, dated.

What is posted: every verb result and refusal (setCommandNoteAt, about 60
sites, levels as they were), every turn report for the aria on screen
(reportAt, with the aria as the source), and the pager process's own slog
WARN and ERROR (a freeze dump written, templates that failed to load,
messages dropped on exit), minus reportAt's echo of every alert. It lives on
the TRANSCRIPT, in the pager's memory: a hop mints a new session status, and
setStatus binds the same history to each.

Found on the way: `[cli] notice_ttl` was documented and never applied (nothing
called setNoticeTTL). It is now, through setStatus, so a hop keeps it.

Phase 2, the daemon's own trouble and turns finishing on arias this session
has shown, is designed in plans/notifications.md and not built here.

scripts/notifications-pty.sh drives the real binary in tmux, hermetically:
14/14 on this build, 5/14 on 0.37.11 (the five are absences).
# Conflicts:
#	internal/cli/command_router.go
…ry's

Merging the two pits showed it: the models pit says "…models" while the
list loads, and every routed verb says "…verb" while it runs. With every
note now kept, each :ls became two history rows, one a progress indicator.
Progress goes to the bar alone (setProgressNote / in.progress); the result
that follows is the news.
Two polish passes on the notifications pit, both asked for after living
with it (Gluck, 2026-09-30).

ENTER. It used to print the full date and the source under a row, which
is furniture: the row already carries the time and the aria. It now
spells the notification OUT, the way the state pit's Enter opens a value
(formView.Activate -> openBranch): the whole text, wrapped to the pane,
every line selectable and yanking the whole, so a long single line can be
read rather than clipped at the pane edge. Bounded at 200 lines, as an
opened form value is at 500, with the same "y yanks all of it".

THE LEVEL IS A COLOUR, not only a glyph. It cannot ride in the row's
text, which goes through pitText and loses every escape, so a row carries
a tone (pitRow.tone) and the picker paints it: the palette's own red for
an error, its yellow for a warning, nothing for info. Over the selection
wash each is the same colour lifted (term.NoticeOnWash), because
autumnRed on mid-grey reads at 1.9:1. A pit that sets no tone renders
byte for byte as it did before, which is asserted rather than assumed.

Tests, red first: two view cases (the whole text wrapped at width 60, and
no date or source anywhere in it) and one picker case (wash plus lifted
red on the selected row, the palette's red on an unselected one, an
untoned row unchanged byte for byte, no escape at all with colour off).
scripts/notifications-pty.sh grows four checks, each of which fails on
654d999 and passes here, plus the premise they rest on: the row really
cannot hold the text.
@jack-work

Copy link
Copy Markdown
Owner Author

Two polish passes on the notifications pit, pushed as 027fc9c.

Enter spells the notification out. It used to print the full date and the source under the row, which is furniture (the row already carries the time and the aria). It now opens the whole text beneath the row, wrapped to the pane, following the state pit's Enter rather than a parallel mechanism: every opened line is selectable and yanks the whole, so a long single line is readable instead of clipped at the pane edge. Bounded at 200 lines with the same "y yanks all of it" the form uses at 500.

The level is a colour. A row now carries a tone (pitRow.tone) that the picker paints, since pitText strips every escape out of a row's text: the palette's own red for an error, its yellow for a warning, nothing for info. Over the selection wash each is the same colour lifted (term.NoticeOnWash), because autumnRed on mid-grey reads at 1.9:1. A pit that sets no tone renders byte for byte as before, asserted in the picker test.

go build ./... && go vet ./... && go test ./... -count=1 green. scripts/notifications-pty.sh grows four checks, each canaried red against 654d999, plus the premise they rest on (the row really cannot hold the text): 20 ok, 0 failed here, 16 ok / 4 failed there. Verified by hand in a real tmux pane on the flake build (nix develop .#sandbox) with real credentials: a 157-character :attend failure opens to reveal its tail and the actual reason, and errors read red at rest and under the cursor while info rows stay plain.

One thing seen while looking and NOT touched: a verb error like :ls --bogus is posted at info level, so it shows a · and no colour. The level is the caller's, not the pit's.

@jack-work
jack-work merged commit 1e1d429 into main Sep 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant