Skip to content

Reports: the filter filters, the frame is wide, and the ✕ closes one message - #665

Merged
hyoshi merged 1 commit into
mainfrom
fix/reports-filter-width-dismiss
Aug 20, 2026
Merged

Reports: the filter filters, the frame is wide, and the ✕ closes one message#665
hyoshi merged 1 commit into
mainfrom
fix/reports-filter-width-dismiss

Conversation

@hyoshi

@hyoshi hyoshi commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Three findings from the v0.12.0 build. One branch, 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 }. Nothing moved.

No pin over dashboard.js could have caught this, because dashboard.js
was 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 real
app.html, evaluates the real modules and dashboard.js against it, and
answers "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 author display) and nothing else.

tests/js/reports_filter_interaction.test.js then does what nobody had
done: it clicks the chips and counts the cards on screen.

Removing the one-line fix Result
shows only the clients at the health that was clicked
brings every card back when All is clicked
starts unfiltered again when the index re-renders
gives every element the view hides an escape from its own display

The general form is pinned too: every element the Reports view hides by
setting .hidden needs a [hidden] rule of its own if its class declares a
display.
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 — one
width for every left-nav item
, because a frame that resized as the
operator moved between them would read as a broken layout.

Viewport Card columns
1440px 3
1920px 4
3840px 4 (the ceiling stops the cards stretching)

Derived from the stylesheet's own numbers — shell width, gutter, nav, rail,
panel padding, card track — in reports_index_height.test.js, so narrowing
any 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:

  • a single-line field. 1600px of text input is not an improvement, so
    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.
  • a table whose two columns exist to ASSOCIATE a name with a value.
    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:

Table Verdict Why
.dashboard-about-table (Package / Version) capped, width: auto; max-width: 560px a name→value pair; stretching destroys the association
.dashboard-byod-table (Platform / Mode / Detail / action) full width reading ACROSS the columns is its job, and the free-text detail wants the room
About "updates available" list (<ul>) full width prose lines that reflow; nothing to separate
Setup / Advanced row lists (<ul><li>) full width label + control rows, which is what the width was widened for

tests/js/dashboard_sections.test.js opens every left-nav item and
asserts each one shows, renders content, and holds something that follows the
width.

クライアント and セキュリティ are not nav items in this repo — they come
from the agency extension surface, which builds its tabs as
.dashboard-group inside the same pane (extensions.js). They inherit the
shell width automatically, and inherit the content rules wherever they use
.dashboard-section. There is nothing further to change here for them.

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 messagetriageItemKey, 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: its count and the clients it names are
    recomputed from what is left ("unknown key ×6" → close four → "×2");
  • the row goes with its last message;
  • the row stays open across the re-render — closing six findings should
    not be six trips through the disclosure;
  • "N hidden" counts MESSAGES. Counting rows would report "1" for six
    findings nobody can see. It still says in words that the conditions are
    still true and still counted, and still offers Show them again;
  • a message whose content changed is a different message and comes back on
    its own
    — the fingerprint rule is unchanged, one level finer;
  • the layer's count, the KPI cell and the marked cards are untouched.
    Asserted by clicking three ✕s and re-reading the heading and the marks, not
    by reading the source.

Structure pins updated (intent preserved)

Pin Guarded Now
reports_triage.py::test_closing_an_alert_is_never_silent closing an alert leaves a trace and a way back the receipt counts messages (hiddenCount), and a message-level control must exist
reports_triage.py::test_a_dismissal_is_keyed_to_what_the_row_said…_the_message_said a dismissal cannot outlive what it was about same property one level finer, plus: the row control must BE the message control applied to each
reports_index_height.test.js the index fits a screen the width model reads the shared shell rule instead of a reports-only one, and pins the column count at 1440/1920

New 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 and
    uses 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.js330 pass, 0 fail (was 297).
  • pytest tests/9695 pass, 14 fail, all 14 pre-existing on this
    machine before the branch (an installed agency / bridge distribution
    changes what the OSS-only handler tests see).
  • black --check mureo tests and ruff check mureo tests clean.

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.

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
@hyoshi
hyoshi merged commit 6ffee2c into main Aug 20, 2026
13 checks passed
@hyoshi
hyoshi deleted the fix/reports-filter-width-dismiss branch August 20, 2026 22:55
@hyoshi hyoshi mentioned this pull request Aug 20, 2026
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.
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