Reports: the filter filters, the frame is wide, and the ✕ closes one message - #665
Merged
Conversation
Three findings from the v0.12.0 build, in one change because they share
app.css and the alert renderer.
1. THE HEALTH FILTER DID NOTHING.
Clicking 要対応 / 注視 / 指摘なし left every card on screen. The JS was right
and had tests proving it: `applyReportsHealthFilter` set `hidden` on exactly
the cards it should. `.reports-client-card-item` declares `display: flex`,
an AUTHOR rule, which beats the user agent's `[hidden] { display: none }`.
No pin over dashboard.js could have caught it, because dashboard.js was not
wrong — the two files were, together.
So the fix comes with the test that was missing. `tests/js/dom_harness.js`
parses the real app.html, evaluates the real modules and dashboard.js
against it, and answers "would an operator SEE this?" from the real app.css,
modelling the one piece of cascade that decides it and nothing else.
`reports_filter_interaction.test.js` clicks the real chips and counts the
cards on screen; removing the one-line fix fails four of its assertions. The
general form is pinned too: every element the view hides by setting `.hidden`
needs a `[hidden]` rule of its own if its class declares a display.
2. THE FRAME WAS TOO NARROW, AND THE CONTENT DID NOT USE IT.
At 1180px the Reports card column fitted two cards abreast, so a
twenty-seven-client roster was fourteen rows of scrolling before the
operator had read anything. The dashboard shell is `min(1680px, 96vw)` now
— one width for every left-nav item, because a frame that resized as the
operator moved between them would read as a broken layout — which is three
cards abreast at 1440px and four at 1920px, derived from the stylesheet's
own numbers rather than from a screenshot.
The content follows it: lists, rows and tables take the width they are
given. Two things do not survive being stretched, and both are handled
rather than capped-and-abandoned on the left of an empty frame:
• a single-line field. The fields flow side by side instead, as many
across as fit, stacking again on a narrow screen;
• a table whose two columns exist to ASSOCIATE a name with a value.
About's package/version table keeps the width its content needs: at
1600px "mureo" and "0.12.0" sit half a metre apart and the table stops
doing the only thing it is for. The test is whether stretching breaks
the association, not whether the table is small — BYOD's platform /
mode / detail table keeps the full width.
`dashboard_sections.test.js` opens every left-nav item and asserts each one
shows, renders, and holds something that follows the width.
3. THE ✕ CLOSED A CATEGORY, NOT A MESSAGE.
The alert rows group by kind and one can cover six clients, so "hide" took
five findings the operator had never read. A dismissal is now keyed to one
MESSAGE — `triageItemKey` over the same content fingerprint as before — and
every message in an expanded row carries its own ✕. The row's ✕ is defined
as that control applied to each message on it, which is the only reading
that keeps the two consistent.
Closing one shrinks the row it was on: its count and the clients it names
are recomputed from what is left, and the row goes with its last message.
The row stays open across the re-render, because closing six findings should
not be six trips through the disclosure.
The receipt is unchanged in spirit and corrected in arithmetic: "N hidden"
counts MESSAGES (counting rows would report "1" for six findings nobody can
see), still says in words that the conditions are still true and still
counted, and still offers the way back. The layer's own count, the KPI cell
and the marked cards are untouched by any of it — asserted by clicking, not
by reading the source.
Structure pins updated, intent preserved:
- reports_triage.py::test_closing_an_alert_is_never_silent — the receipt
counts messages, and a message-level control has to exist.
- reports_triage.py::test_a_dismissal_is_keyed_to_what_the_row_said →
…_the_message_said. Same property, one level finer, plus: the row-level
control must BE the message-level one applied to each.
- reports_index_height.test.js — the width model reads the shared shell rule
instead of a reports-only one, and pins the column count at 1440/1920.
Refs #651
Merged
hyoshi
added a commit
that referenced
this pull request
Aug 20, 2026
Cuts a `0.12.1` section and bumps every version string. - CHANGELOG.md: a new `[0.12.1] - 2026-08-21` section. `[Unreleased]` was empty — #665 merged without a CHANGELOG entry, so the section is written from that PR here rather than being moved down from `[Unreleased]` - pyproject.toml, mureo/__init__.py, .claude-plugin/plugin.json, gemini-extension.json: 0.12.0 -> 0.12.1 - 53 SKILL.md frontmatter `version` fields synced Patch rather than minor: #665 only fixes and adjusts what 0.12.0 shipped — the health filter that did not filter, a frame widened to fit the roster, and a ✕ that closed a category instead of a message. No new capability. No functional change.
This was referenced Aug 21, 2026
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.
Three findings from the v0.12.0 build. One branch, because they share
app.cssand the alert renderer.1. The health filter did nothing
Clicking 要対応 / 注視 / 指摘なし left every card on screen.
The JS was right, and had tests proving it:
applyReportsHealthFiltersethiddenon exactly the cards it should..reports-client-card-itemdeclares
display: flex— an author rule, which beats the user agent's[hidden] { display: none }. Nothing moved.No pin over
dashboard.jscould have caught this, becausedashboard.jswas not wrong. The two files were, together.
So the fix ships with the test that was missing
tests/js/dom_harness.js— a helper, not a suite — parses the realapp.html, evaluates the real modules anddashboard.jsagainst it, andanswers "would an operator SEE this element?" from the real
app.css.It is not a CSS engine: it models the one piece of cascade that decides this
question (
[hidden]versus an authordisplay) and nothing else.tests/js/reports_filter_interaction.test.jsthen does what nobody haddone: it clicks the chips and counts the cards on screen.
shows only the clients at the health that was clickedbrings every card back when All is clickedstarts unfiltered again when the index re-rendersgives every element the view hides an escape from its own displayThe general form is pinned too: every element the Reports view hides by
setting
.hiddenneeds a[hidden]rule of its own if its class declares adisplay. That list is asserted rather than remembered.
The design is unchanged: the filter hides cards, it does not remove them, so
the operator's own card order (#556) survives it — asserted by comparing the
grid before and after.
2. The frame was too narrow, and the content did not use it
At 1180px the Reports card column fitted two cards abreast, so a
27-client roster was fourteen rows of scrolling before the operator had read
anything.
The dashboard shell is now
min(1680px, 96vw)with a 20px gutter — onewidth for every left-nav item, because a frame that resized as the
operator moved between them would read as a broken layout.
Derived from the stylesheet's own numbers — shell width, gutter, nav, rail,
panel padding, card track — in
reports_index_height.test.js, so narrowingany of them fails a test rather than a screenshot. The card track is the
mockup's own
minmax(230px, 1fr).The content follows the frame. Lists, rows and tables take the width they
are given. Two things do not survive being stretched, and neither is capped
and abandoned on the left of an empty frame:
the fields flow — each a column of its own, as many across as fit, the
row filling, stacking again on a narrow screen. A block of text (a
textarea and its hint) keeps a row to itself.
About's package/version table keeps the width its content needs: stretched
to 1600px, "mureo" and "0.12.0" sit half a metre apart and the table stops
doing the only thing it is for.
Every table was judged individually, not narrowed by category:
.dashboard-about-table(Package / Version)width: auto; max-width: 560px.dashboard-byod-table(Platform / Mode / Detail / action)<ul>)<ul><li>)tests/js/dashboard_sections.test.jsopens every left-nav item andasserts each one shows, renders content, and holds something that follows the
width.
3. The ✕ closed a category, not a message
The rows group by kind and one can cover six clients, so "hide" took five
findings the operator had never read.
A dismissal is now keyed to one message —
triageItemKey, over the samecontent fingerprint as before — and every message in an expanded row carries
its own ✕. The row's ✕ is defined as that control applied to each message
on it, which is the only reading that keeps the two consistent.
recomputed from what is left ("unknown key ×6" → close four → "×2");
not be six trips through the disclosure;
findings nobody can see. It still says in words that the conditions are
still true and still counted, and still offers Show them again;
its own — the fingerprint rule is unchanged, one level finer;
Asserted by clicking three ✕s and re-reading the heading and the marks, not
by reading the source.
Structure pins updated (intent preserved)
reports_triage.py::test_closing_an_alert_is_never_silenthiddenCount), and a message-level control must existreports_triage.py::test_a_dismissal_is_keyed_to_what_the_row_said→…_the_message_saidreports_index_height.test.jsNew tests
tests/js/dom_harness.js— the harness (helper, not a suite).tests/js/reports_filter_interaction.test.js(8) — the filter, clicked.tests/js/reports_dismiss_interaction.test.js(8) — closing one message,a whole row, and the receipt.
tests/js/dashboard_sections.test.js(6) — every left-nav item opens anduses the width.
tests/js/reports_triage.test.js(+3 net) — per-message dismissal keys,the shrinking row, message counting.
Verification
node --test tests/js/*.test.js— 330 pass, 0 fail (was 297).pytest tests/— 9695 pass, 14 fail, all 14 pre-existing on thismachine before the branch (an installed agency / bridge distribution
changes what the OSS-only handler tests see).
black --check mureo testsandruff check mureo testsclean.Not verified by me: no screenshot. The interactions are now driven and
asserted, and the widths are derived from the stylesheet — but how the
flowed form fields and the widened sections actually LOOK still needs eyes.