feat(core-status): let the Problems tab act on what it is showing - #448
Merged
Conversation
The protocol has no request for a diagnostics list. A core pushes one on connection and again whenever its own detectors change it; moonproto's docs/problems.md says outright that "there is no periodic diagnostic-list refresh", and the only client command that produces a fresh list is the destructive clear. That would make a refresh button pure theatre, except for one thing the library does silently. moonproto mutates its retained problems through three paths and only two raise an event this feed can follow. The third is the hard-session reset: on a ServerToken or peer-app-token CHANGE - a hard reconnect, or the MoonBot process restarting behind the same endpoint - events/active.rs assigns `settings.problems = Default::default()` and publishes nothing. The terminal never respawns its feed thread for that, so its own store is not rebuilt either and keeps serving the findings of a core instance that no longer exists, with `supported` still true. The restarted core normally pushes a list within seconds and repairs it; a core too old for the extension never does, and the stale rows then stand forever. `CoreCmd::RefreshProblems` re-reads what moonproto holds now and publishes it. Usually that equals what the store already has and the store drops it. After a silent reset it does not, and the honest answer - this core has told this connection nothing - replaces the previous instance's findings, which is the same rule `CoreData::begin_connection_attempt` applies when the terminal itself reconnects a core. The request joins the existing event gate rather than branching beside it: both reasons read the same retained state through the same projection, so one gate is all the difference between them. `convert::snapshot_when` carries the snapshot/flatten/project chain that `settings_event_snapshot` already used, so the tenth publish block in that loop is not a hand-rolled copy of the other nine. `clear_core_problems_many` and `refresh_core_problems_many` follow `set_auto_detect_many`: one command per core, because that is what the protocol offers, and the cores whose channel accepted are returned so a caller can report what it actually reached.
MoonBot's own Problems window carries "reset all" and "refresh". This tab had neither: it had a channel test and a clear, both gated on the operator having hand-picked exactly one core in the panel's core selector. That gate was permanently shut on every workspace-owned panel - Auto pins its scope and renders the selector read-only, so the retained Classic pick can never be set or cleared there - which is why both buttons read as always greyed. Three buttons now, and ONE rule behind all of them: a clicked finding narrows them to its core, and no click means the panel's whole scope. That is this panel's own convention for a bulk command, the same "the selection when the operator has made one, the displayed scope otherwise" the footer's fleet update already obeys. The scope itself is set where it always was - the core selector in Classic, the workspace in Auto - so no button carries a private notion of which cores it means any more. The pick is stored as a CORE, never as the clicked row index. The finding list is rebuilt from live core data on every repaint, so an index outlives the row it named: one finding appearing or clearing above it re-points it at a different core, and the reset it narrows cannot be undone. The click resolves its index against the list it was drawn from, while that list is still the one on screen. Clicking any row of the picked core clears the pick, because a narrowing that cannot be undone strands the operator on one core. The table keeps its default selection mode on purpose. Taking the click through `controlled_row_selection` also hands the caller the row highlight and returns early from the table's entire keyboard block, which would cost up/down/home/end navigation in a table that is read far more than it is clicked. Reset is labelled "Reset", not MoonBot's "Reset all": their window belongs to one core, so "all" there means all of that core's findings, while this tab spans a fleet and the same word would read as "all cores" - a lie the moment the operator has clicked one. The tooltip and the confirm both state how many cores the press will reach, the confirm names them through the same non-truncating list the footer's fleet confirm uses, and the outcome reports the shortfall: "reached 9 of 12" rather than a success toast over a partial result. Refresh says in its tooltip that it cannot ask the core for anything, because the label promises a round trip the wire cannot make. Known gaps, deliberate: the synthetic core swallows both new commands, so the tab is inert under --fixture; and a reset still reaches only the connected cores of a scope, which the confirm's count states but its text does not dwell on.
guyverino
force-pushed
the
feat/core-status-problem-actions
branch
from
September 7, 2026 10:34
72225be to
9a0c486
Compare
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 & why
MoonBot's own Problems window carries "reset all" and "refresh". This tab had
neither. It had a channel test and a clear, both gated on the operator having
hand-picked exactly one core in the panel's core selector — a gate that is
permanently shut on every workspace-owned panel, because Auto pins its scope and
renders that selector read-only, so the retained Classic pick can neither be set
nor cleared there. Both buttons therefore read as always greyed, which is how
this started.
Three buttons now, and ONE rule behind all of them. A clicked finding narrows
them to its core; no click means the panel's whole scope. That is this panel's own
convention for a bulk command — the same "the selection when the operator has made
one, the displayed scope otherwise" the footer's fleet update already obeys. The
scope itself is still set where it always was, the core selector in Classic and the
workspace in Auto, so no button carries a private notion of which cores it means.
Refresh is not decoration, and the reason is not obvious. The protocol has no
request for a diagnostics list: a core pushes one on connection and again when its
detectors change it,
docs/problems.mdsays outright that "there is no periodicdiagnostic-list refresh", and the only client command that yields a fresh list is
the destructive clear. But moonproto mutates its retained problems through three
paths and only two raise an event this feed can follow. The third is the
hard-session reset — on a ServerToken or peer-app-token CHANGE, i.e. a hard
reconnect or the MoonBot process restarting behind the same endpoint,
events/active.rsassignssettings.problems = Default::default()and publishesnothing. The terminal never respawns its feed thread for that, so its store keeps
serving the findings of a core instance that no longer exists, with
supportedstill true. A restarted core normally pushes a list within seconds and repairs it;
a core too old for the extension never does.
CoreCmd::RefreshProblemsre-readswhat the library holds now and publishes it — usually identical and dropped by the
store, and after a silent reset the honest "this core has told this connection
nothing".
Notable decisions
from live core data on every repaint, so an index outlives the row it named: one
finding appearing or clearing above it re-points it at a different core, and the
reset it narrows cannot be undone. The click resolves its index against the list
it was drawn from, while that list is still the one on screen. Clicking any row of
the picked core clears the pick — a narrowing that cannot be undone would strand
the operator on one core.
controlled_row_selectionalso hands the caller the row highlight and returnsearly from the table's entire keyboard block (
data_table.rs:1100), which costsup/down/home/end navigation in a table that is read far more than it is clicked.
The highlight is set per row from the picked core instead.
"all" there means all of that core's findings. This tab spans a fleet, where the
same word reads as "all cores" — a lie the moment the operator has clicked one.
the count, the confirm names the cores through the same non-truncating list the
footer's fleet confirm uses, and the outcome reports the shortfall — "reached 9 of
12" rather than a success toast over a partial result.
of its core is the only gesture that clears one, so a pick whose findings have all
gone — reset on the core, or
supportedflipped back to unknown — would sit therenarrowing an irreversible action with nothing on screen saying so. It is checked
against the DRAWN rows, so the 500-row cap cannot strand one either.
cleared on every pick change: left behind they paint a second highlight from a raw
row index (
data_table.rs:1231, and the cell background at:1262paints whetheror not cells are selectable), and an index is exactly what the pick exists to
avoid.
select_row(None)alone is not enough — it leavesselected_cellbehind.scope is connected" have different remedies, and a scope full of live cores must
never be reported as offline because one picked core is not.
read the same retained state through the same projection, so
convert::snapshot_whencarries the chainsettings_event_snapshotalready usedand the tenth publish block in that loop is not a copy of the other nine.
Known limitations
feed/synth.rshas a catch-all),so the tab is inert under
--fixture. Nothing there reports diagnostics anyway.how many that is, but its text does not spell out that offline members were
dropped.
(it closes an open chart), and a second one on the same key risks swallowing that.
The pick is cleared by clicking its row again.
command has a result path back to the window today.
How to verify
Green on the rebased tree: moon-core 1654, moon-ui-gpui 1500 + 320
(theme_contract), fmt clean. Clippy reports 78 findings on this tree and none in
any file this change touches —
too_many_argumentsondrain_commandsalreadyfired at 12 arguments on
origin/main, verified against the base file. FireTest notrun: this change touches neither chart, render, windows nor input.
By hand, on a fleet whose cores report diagnostics: click a finding and confirm all
three buttons narrow to its core and its rows highlight; click it again to widen
back to the scope; check the arrow keys still walk the table; reset one core and
watch its rows go when the core's next list arrives.