Skip to content

A Row 2 key shows one refused face for every refusal, so a working guard is indistinguishable from a broken key #7

Description

@louisalexander

Problem

A Row 2 key has one refused face, and it is used for every refusal. The
operator cannot tell a guard working correctly from a key that is broken.

Observed live. The operator pressed ISSUE repeatedly and reported: "i press
issue, i see a line underneath the word issue but nothing else."
That line is
the refused marker. Nothing on the key said why, and the two candidate
causes were indistinguishable without reading a log file:

19:33:37  send: armed issue for 2feedd52 -- press again to confirm
19:33:49  send: refusing issue -- target is working, and a confirm verb must fire while the operator is watching
19:34:09  send: no such verb: stop
19:34:25  send: refusing issue -- target is working, and a confirm verb must fire while the operator is watching

Three materially different situations, one identical face:

  • the guard is working — the target is busy, so an outward-facing verb
    refuses (bin/fleet-send, the WAKEABLE_STATES check);
  • nothing is configured — the key has no verb set in its property
    inspector (plugin/src/plugin.ts, the !verb branch);
  • the verb does not exist — no file for that id, e.g. stop, which is on
    the panel but not yet written.

This matters more than a normal polish issue because the whole premise of the
panel is that state is legible at a glance. A key that says only "no" on a
deck whose job is answering "what is going on" is the one place that premise
breaks. The operator's next move differs completely between the three: wait,
configure the key, or write a verb file.

Ruled out

Not a rendering bug and not a stale plugin. During diagnosis the deck was
running a stale bundle (Stream Deck had started at 13:17, the build was 15:14)
which separately suppressed the ARMED face — that has been fixed by restarting
the app, and the refusal ambiguity remains afterwards. The information all
exists in ~/.fleet/fleet.log; it simply never reaches the key.

What a fix has to decide

bin/fleet-send currently reports three outcomes by exit code: 0 delivered,
1 refused, 2 armed. Distinguishing refusals means widening that channel,
and there is a real choice about how far:

  1. More exit codes (3 busy, 4 unconfigured, 5 no-such-verb). Cheapest,
    keeps the plugin dumb, but the exit code becomes a growing enum that both
    sides must agree on, and it does not carry any detail.
  2. Structured output on stdoutfleet-send prints a small JSON verdict
    the plugin parses. Extensible and carries a reason string, but fleet-send
    currently prints nothing, and giving it stdout means being careful it never
    becomes a hook-style contract violation elsewhere.
  3. Only distinguish "not now" from "no" — one extra face for a
    transient/busy refusal, everything else stays as today. Smallest change that
    fixes the operator's actual confusion, at the cost of still merging
    unconfigured with unknown-verb.

Whichever is chosen, the second half is visual: refused currently renders as
a bright bar. A "not now" state should read as transient rather than wrong —
and it must not borrow the attention amber, which already means ARMED on this
row.

Not in scope

The never-queue rule that produced the busy refusal is being changed
separately (confirm verbs will be allowed to queue briefly against a working
agent). That reduces how often the busy refusal appears; it does not make the
refusal legible, and every other refusal path is untouched.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions