Skip to content

fix(admin): the panel's trigger row shows every badge, and the post-mortem wraps (#482) - #486

Merged
edgehero merged 1 commit into
mainfrom
fix/panel-badges-postmortem
Sep 29, 2026
Merged

edgehero merged 1 commit into
mainfrom
fix/panel-badges-postmortem

Conversation

@edgehero

@edgehero edgehero commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Closes #482.

The panel showed the same trigger differently from /dispatch triggers, and clipped lines lost their colour.

  • Trigger rows now carry [skills <dir>] and [instructions] (accent, operator overrides of a default) and [secrets N via <profile>] (warning, it changes what a job can reach), in render.mjs's order, with the skills basename from one shared skillsBasename. A row too wide for the pane never gives away its identity (cursor, kind, the target with its flow, and the selector's first 4 columns, or a cron row's id) nor its risk badges ([packages], [resume], [xN], [secrets], health): the text inside every badge is cut to its ellipsis first, non-risk badges drop only if that is still too wide, and the room left goes back to the selector, then the risk badges' text, then the rest. Below that floor plus the risk badges' minimum (a formula in the spec, not a fixed width) the plain right-hand clip applies. Rows that fit are byte identical to before.
  • The trigger drill-in gains skills (the full path), instructions (the text, scrubbed and wrapped, six lines at most) and secrets rows. The instruction text reaches the panel through a separate map, never through the display records that dispatch_triggers returns to the model, and it is captured with the record when the pane opens, so an edit to the file under an open pane cannot show another trigger's text.
  • The run detail wraps its post-mortem inside the pane, every line dimmed, instead of clipping it. A word wider than the pane breaks between graphemes.
  • Clipping kept no colour. fitLine and the frame's fallback stripped styling before clipping, so every overflowing line in the panel lost its colour. One styler.clipStyled now clips between codes, strips a joiner left at the cut, and resets once at the end.
  • A colour code in stored trigger text escaped the frame. The frame's filter passes colour codes because the panel's own styling uses them, so one inside a label, phrase, image, flow or secrets profile coloured the pane and pushed its line past the border, and the unframed degrade printed it raw. Every trigger renderer (the LIST row, the drill-in and render.mjs's renderTriggers) now scrubs the whole trigger record on entry (scrubTrigger), one rule rather than a fix per field.

Review round 1 corrected the first overflow rule (a fixed threshold that still dropped [secrets] at common widths), the drill-in's live read of the instructions, the unscrubbed renderTriggers, and a joiner before the ellipsis. Round 2 replaced the give-way order that followed with the floor rule above, after two rules differing only in their label rendered as one row; twin tests now differ only in selector, and only in cron id, across widths 60 to 200.

DES-ADMIN-VIA-PI-EXTENSION amended with one revision row. New tests in admin/test/dashboard-badges.test.mjs, every new branch mutation checked. Full suite: 5198 tests, 0 fail.

@edgehero
edgehero force-pushed the fix/panel-badges-postmortem branch from 45f740d to 4229033 Compare September 29, 2026 08:37
edgehero added a commit that referenced this pull request Sep 29, 2026
…ortem wraps (#482)

The LIST trigger row showed [packages], [image], [resume], [xN], [once]/[spent]
and the health badge, but not the [skills <basename>], [instructions] and
[secrets N via <profile>] that render.mjs's triggerLine shows for the same
trigger. Not a width decision: the read model has carried all four fields
since #60 and #225, and those commits added the badges to render.mjs and the
trust model without touching the row. [secrets] is the badge that says
credentials reach the job, so its absence was the 0.1.4 defect again.

Colours follow the file's own doctrine. [skills] and [instructions] are
accent: both are operator-authored overrides of a deployment default, not
third-party code. [secrets] is warning: it is what the job can REACH, and the
drill-in's own secrets lines are already amber. Both surfaces take the skills
basename from one skillsBasename in render.mjs.

A row too wide for the frame gives way in a fixed order (fitTriggerRow). The
floor is the cursor, the kind and the target with its flow; the risk badges
([packages], [resume], [xN], [secrets], health) must survive. What yields:
the selector down to 8 columns, the free text inside a badge (image ref,
secrets profile, skills basename) down to 6, the non-risk badges right to
left, the selector entirely, the free text down to its ellipsis; room a later
step freed is then given back. Only a row that still cannot fit gets the
plain right-hand clip. The first version split at one fixed threshold and
still lost [secrets] at 80 to 136 columns with a long image ref (review round
1 of PR #486). A row that fits renders byte-identically, pinned against
goldens rendered from origin/main.

RUN_DETAIL's post-mortem sentence is 86 columns in a 66-column pane and was
cut at "+ optiona". It is now word-wrapped by columns through the styler's
visibleLen, every line dimmed on its own. A word wider than the pane breaks
at a grapheme boundary inside the column budget, so a joined emoji is never
split onto a line starting with a bare U+200D.

TRIGGER_DETAIL now shows the skills dir in full (render.mjs's comment said it
lived there, and it did not), the instructions text, and a secrets row, so
the three read alike on both surfaces. Long values wrap under a blank label;
the instructions stop at six lines with the rest counted. The trust-model
bullets wrap too ("the job holds no vau..." before).

The instructions text is deliberately NOT on the display record: those
records are what the model-callable dispatch_triggers tool returns, which is
why they carry a boolean. read-model.mjs gains readTriggersWithInstructions,
called only by the panel's deps factory: the same view plus the text by raw
file index, from one read through readTriggers' own fs seam. The text is
captured with the record when the pane opens, so a trigger deleted or moved
under an open pane cannot show another trigger's words (review round 1).
Nothing routes drill-in text elsewhere: the overlay resolves with an action
and an index only.

Every trigger renderer now scrubs the record at its door (scrubTrigger in
render.mjs): the LIST row, TRIGGER_DETAIL, and renderTriggers, whose line the
unframed degrade prints. The panes' gate keeps SGR runs because the styler
emits them, so an SGR run in a label, phrase, image, flow or secrets profile
painted the pane and pushed its line past the frame, and the degrade printed
it raw at render(undefined) and render(NaN).

The colour loss was general, so it is fixed at the rule. fitLine, and the
frame's padVisible belt, clipped an over-wide line through styler.cell on its
stripped text, so every overflowing line printed in the default colour. Both
now use styler.clipStyled, which cuts each text run between SGR codes with
the same columnsOf/sliceColumns the cell uses, strips a joiner left at the
cut, closes the cut run with one reset, and falls back to the colourless cell
on the one shape where the whole-line count exceeds the run-by-run count
(issue #417). With no escape in the input it is byte-identical to the old
clip.

Specs: DES-ADMIN-VIA-PI-EXTENSION amended (LIST row badges and overflow
order, TRIGGER_DETAIL rows and wrapping, RUN_DETAIL post-mortem wrap), with
one revision row.

Checked and unchanged: REQ-PER-TRIGGER-INSTRUCTION, REQ-PER-TRIGGER-SKILLS,
INT-TRIGGERS-FILE-CONTRACT, the layout of render.mjs's triggerLine (only
scrubbed now, and a segmentless skills dir reads "-" where it read
"undefined"), styler.cell itself, and normalizeTriggerForDisplay, whose
records still carry the instructions as a boolean only.

Signed-off-by: Rob Boerman <robboerman@live.nl>
…ortem wraps (#482)

The LIST trigger row showed [packages], [image], [resume], [xN], [once]/[spent]
and the health badge, but not the [skills <basename>], [instructions] and
[secrets N via <profile>] that render.mjs's triggerLine shows for the same
trigger. Not a width decision: the read model has carried all four fields
since #60 and #225, and those commits added the badges to render.mjs and the
trust model without touching the row. [secrets] is the badge that says
credentials reach the job, so its absence was the 0.1.4 defect again.

Colours follow the file's own doctrine. [skills] and [instructions] are
accent: both are operator-authored overrides of a deployment default, not
third-party code. [secrets] is warning: it is what the job can REACH, and the
drill-in's own secrets lines are already amber. Both surfaces take the skills
basename from one skillsBasename in render.mjs.

A row too wide for the frame follows one rule (fitTriggerRow). Its floor
is its identity and is never given away: cursor, kind, the target with its
flow, and the selector's first 4 columns plus an ellipsis (a cron row's id).
With it, the risk badges ([packages], [resume], [xN], [secrets], health)
always stay. The row is taken to its minimum (selector at its floor, the text
inside every badge at its ellipsis), the non-risk badges drop right to left
only if that still does not fit, and the room left is given back to the
selector, then the risk badges' text, then the other badges' text. Below the
floor plus the risk badges' minimum, which the spec states as a formula, the
plain right-hand clip applies. Review of PR #486 refuted two earlier rules: a
fixed threshold that still lost [secrets] at 80 to 136 columns, then a
give-way order under which two rules differing only in label rendered as one
row. A row that fits renders byte-identically, pinned against goldens
rendered from origin/main.

RUN_DETAIL's post-mortem sentence is 86 columns in a 66-column pane and was
cut at "+ optiona". It is now word-wrapped by columns through the styler's
visibleLen, every line dimmed on its own. A word wider than the pane breaks
at a grapheme boundary inside the column budget, so a joined emoji is never
split onto a line starting with a bare U+200D.

TRIGGER_DETAIL now shows the skills dir in full (render.mjs's comment said it
lived there, and it did not), the instructions text, and a secrets row, so
the three read alike on both surfaces. Long values wrap under a blank label;
the instructions stop at six lines with the rest counted. The trust-model
bullets wrap too ("the job holds no vau..." before).

The instructions text is deliberately NOT on the display record: those
records are what the model-callable dispatch_triggers tool returns, which is
why they carry a boolean. read-model.mjs gains readTriggersWithInstructions,
called only by the panel's deps factory: the same view plus the text by raw
file index, from one read through readTriggers' own fs seam. The text is
captured with the record when the pane opens, so a trigger deleted or moved
under an open pane cannot show another trigger's words (review round 1).
Nothing routes drill-in text elsewhere: the overlay resolves with an action
and an index only.

Every trigger renderer now scrubs the record at its door (scrubTrigger in
render.mjs): the LIST row, TRIGGER_DETAIL, and renderTriggers, whose line the
unframed degrade prints. The panes' gate keeps SGR runs because the styler
emits them, so an SGR run in a label, phrase, image, flow or secrets profile
painted the pane and pushed its line past the frame, and the degrade printed
it raw at render(undefined) and render(NaN).

The colour loss was general, so it is fixed at the rule. fitLine, and the
frame's padVisible belt, clipped an over-wide line through styler.cell on its
stripped text, so every overflowing line printed in the default colour. Both
now use styler.clipStyled, which cuts each text run between SGR codes with
the same columnsOf/sliceColumns the cell uses, strips a joiner left at the
cut, closes the cut run with one reset, and falls back to the colourless cell
on the one shape where the whole-line count exceeds the run-by-run count
(issue #417). With no escape in the input it is byte-identical to the old
clip.

Specs: DES-ADMIN-VIA-PI-EXTENSION amended (LIST row badges and overflow
order, TRIGGER_DETAIL rows and wrapping, RUN_DETAIL post-mortem wrap), with
one revision row.

Checked and unchanged: REQ-PER-TRIGGER-INSTRUCTION, REQ-PER-TRIGGER-SKILLS,
INT-TRIGGERS-FILE-CONTRACT, the layout of render.mjs's triggerLine (only
scrubbed now, and a segmentless skills dir reads "-" where it read
"undefined"), styler.cell itself, and normalizeTriggerForDisplay, whose
records still carry the instructions as a boolean only.

Signed-off-by: Rob Boerman <robboerman@live.nl>
@edgehero
edgehero force-pushed the fix/panel-badges-postmortem branch from 4229033 to dbea180 Compare September 29, 2026 09:10
@edgehero
edgehero merged commit ecd65e4 into main Sep 29, 2026
7 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.

Panel: trigger rows drop the skills, instructions and secrets badges; run detail clips its post-mortem

1 participant