Skip to content

fix(ops): stop the channels panel reporting success it cannot observe - #59

Merged
sulthannauval merged 1 commit into
mainfrom
fix/console-channels-panel-truth
Aug 14, 2026
Merged

fix(ops): stop the channels panel reporting success it cannot observe#59
sulthannauval merged 1 commit into
mainfrom
fix/console-channels-panel-truth

Conversation

@sulthannauval

Copy link
Copy Markdown
Member

Executes plans/137-console-channels-panel-truth.md (the plan file lives in the
RantAIClaw repo; the work is all here).

Problem

Saving a Telegram allowlist showed a green "Allowlist updated" toast and then
broke. Three defects turn a backend restart into a silent failure, and a fourth
silently revokes people.

  1. Success asserted before it was observable, with a refetch fired
    immediately — racing a daemon restart the save itself triggered 750 ms later.
  2. The gateway's "the runtime is reloading" note was suppressed by an
    else if, in exactly the two states an operator is most likely to be in
    while editing: the gateway sets warning when the list is empty or contains
    *, and puts the restart notice in note.
  3. A refresh failure blanked the whole panel, so the most likely outcome of a
    successful save was an error screen — indistinguishable, to the operator,
    from the save having failed.
  4. The panel sent the entire allowlist as a wholesale replacement built from a
    possibly-stale snapshot
    , silently revoking anyone who self-onboarded via
    /claim since the panel was opened. The backend goes out of its way to avoid
    clobbering that — it re-reads the freshest config under a lock, with a comment
    saying so — and the console defeated it.

Change, step by step

  1. notify surfaces a warning and a note; neither implies the other.
  2. allowlistDrift(seeded, server, next) re-fetches before POSTing and, when the
    server has moved, lists who would be removed and asks via the ConfirmModal
    already in this file. The wholesale-replacement API contract is preserved —
    making the revocation visible is the actual defect, and building a delta API
    is out of scope. A failed pre-check falls through to the save rather than
    blocking it: it is a courtesy, not a gate.
  3. The toast reports r.allowed_users — what the server stored.
  4. PanelFrame takes loaded and distinguishes an initial-load failure (error
    state is right) from a refresh failure (keep the content, show a non-blocking
    strip). use-async already tracked loaded internally; it now returns it.
    Callers that do not pass it keep the old behaviour.
  5. use-async gains the request-id guard from use-async-guarded, with the
    refreshing/loaded semantics unchanged — a slow pre-restart response can no
    longer land last and show pre-save data. The 3-second settle timer moves into
    a ref and is cleared on unmount.
  6. The panel consumes use-gateway-status and renders a "Reloading the
    runtime…" state that keeps its content, recovers on its own when the gateway
    answers, and gives up after 60 s with the manual recovery named.

Step 7 (drive it in a browser): NOT done

The plan asks for four browser observations against a running gateway with a
live Telegram bot. I did not have a gateway or a bot token, so none of the four
were made
— I am not claiming them. What replaced them:

  • allowlistDrift is extracted as a pure function and unit-tested, including the
    /claim-after-open case that motivated step 2, and mutation-checked (making it
    ignore server additions fails two of its four tests).
  • bunx tsc --noEmit (via ./node_modules/.bin/tsc) → exit 0.
  • next build → succeeded; the route table rendered, so this is a real build.
  • vitest run163 passed, 21 files (was 159 / 20).

The race in step 5 and the reconnect in step 6 are the two that genuinely need a
browser. They should be driven before this ships.

Note on tooling

bun/bunx are not installed on this machine; the repo's binaries under
node_modules/.bin were used directly. There is no eslint config here, so
bun run lint contributes nothing — as the plan says.

Scope

Only the four files the plan lists: channels-panel.tsx, use-async.ts,
shared.tsx, plus a new test file. use-gateway-status.ts was read and reused,
not modified. Nothing in src/app/api/rc/**, src/proxy.ts, other ops panels, or
the RantAIClaw repo was touched.

Changing use-async.ts affects every panel that uses it — that is intended, and
is why plan 138 depends on this one. No other panel's call sites were changed.

Saving a Telegram allowlist showed a green toast and then broke. Three defects
turned a backend restart into a silent failure, and a fourth silently revoked
people.

- `notify` used `else if`, so the gateway's "the runtime is reloading" note was
  suppressed whenever a `warning` was also set — and the gateway sets `warning`
  when the list is empty or contains `*`, which are exactly the two states an
  operator is most likely to be in while editing. Both surface now.
- The toast asserted "Allowlist updated" before anything was observable. It
  reports the count the SERVER stored, so a mismatch with what was typed is
  visible.
- `PanelFrame` swapped children for the error state on any error, so a refresh
  failure blanked the panel — making the most likely outcome of a *successful*
  save an error screen, indistinguishable from the save having failed. It now
  distinguishes an initial-load failure (error state is right) from a refresh
  failure (keep the content, show a non-blocking strip), using a `loaded` flag
  `use-async` already tracked internally and did not expose.
- The POST replaces the allowlist wholesale from a snapshot taken when the panel
  loaded, so anyone who self-onboarded via `/claim` since then was silently
  revoked — the backend re-reads the freshest config under a lock specifically
  to avoid that, and the console defeated it. `allowlistDrift` re-fetches before
  saving and, when the server has moved, names who would be removed and asks.

Also: `use-async` gains the request-id guard from `use-async-guarded`, so a slow
pre-restart response can no longer land last and show pre-save data; the panel's
3-second settle timer lives in a ref and is cleared on unmount instead of firing
into an unmounted tree; and the panel consumes `use-gateway-status` to render a
bounded "reloading the runtime" state that keeps its content and recovers on its
own, naming the manual recovery if the gateway does not return within a minute.
@sulthannauval
sulthannauval merged commit 4514c32 into main Aug 14, 2026
1 check 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