feat(gates): add gate-105, duplicate-page-refresh - #657
Merged
Conversation
added 6 commits
August 31, 2026 23:11
Every surface built on CnDashboardPage or CnDetailPage already ships a Refresh. CnActionsMenu renders it as the first item of the page-level Actions menu, and showRefresh defaults to true. A surface that declares a Refresh of its own therefore ships two, doing the same thing. Measured across the fleet on 2026-08-31, after a user reported the dossiq dashboard showing Refresh as a toolbar button and again as the first Actions-menu item. Five surfaces had it: dossiq, hermiq and larpinq through a manifest "type": "refresh" headerAction, opencatalogi and shillinq through a hand-written button in the #actions or #header-actions slot. All five are fixed; this is what keeps them fixed. Nothing downstream could catch it. check:manifest validates against a JSON Schema, and a schema cannot know that the component rendering the page contributes an item of its own. The only detector was a person counting buttons on screen. The remedy names the @refresh listener, not just the deletion. On both Vue apps the hand-written button called a host method that was not subscribed to cn:page:refresh, so the menu item did nothing: removing the button alone would have left only the dead Refresh. Full-tree rather than diff-scoped, for the reason gates 84 and 93 through 96 give. openregister passes because it opts out with showRefresh: false, which stays a supported shape. The acceptance suite carries two load-bearing arms. Arm 5 plants Refresh markup inside an HTML comment, the shape this gate's own remedy leaves behind, and fails without comment stripping. Arm 6 puts an NcButton's nested <template #icon> between the slot opening and the Refresh label, so a scan that matched to the nearest </template> would read an empty slot and report clean over a real duplicate.
src/manifest.d/*.json is merged into the manifest at runtime by require.context, so a checker that opens only src/manifest.json is blind to whatever the fragments add. Eight fleet apps use them and shillinq ships 87, which is the same reason gate-96 reads them. The checker already did. Nothing asserted it, so nothing would have caught a refactor that stopped.
ShellCheck failed the build with four SC2016 notes. The fixtures wrote Vue
markup as single-quoted arguments and used backticks inside them to dodge
nested quoting, and a backtick in single quotes reads as command
substitution. The workflow's wrapper exits 1 on any finding, note severity
included.
`_vue` now takes the body on stdin from a quoted heredoc, so the fixtures can
use the real `t('app', 'Dashboard')` form the components actually carry.
All ten arms stay green, and arm 5 still holds the `<Refresh>` tag and the
quoted label that make it load-bearing.
The coverage ratchet in test_gate_acceptance_matrix.sh caught this, in its own words: "gate-104 is DECLARED by the runner but has neither a planted/clean fixture nor a reasoned row in UNCOVERED.md. A gate can be added to the runner and never tested; this is that moment." The dedicated checker suite tests the checker; this drives the whole runner over a real tree. Two rows, because the defect has two shapes in two file kinds and each is independently fatal: a "type": "refresh" headerAction in src/manifest.json, and a Refresh button in a .vue #actions slot. Three fleet apps had the first and two had the second, so a manifest-only fixture would pass a Vue-blind checker. The clean arm is not merely Refresh-free. It keeps three near-misses a widened gate would turn red, each a shape the fleet actually ships: two surfaces that keep their own Refresh and stand the menu item down with showRefresh: false (openregister's dashboard, the exception the rule genuinely has); an api-call action carrying "icon": "Refresh" (shillinq's renew-consent); and an HTML comment quoting the removed markup, which is what this gate's own remedy writes on every surface it touches. Matrix: 211 passed, 0 failed. Fixtured gates 76 -> 77.
reports-one-page took 104 first, from a branch cut in parallel with this one. check_gate_numbers_unique.sh caught it on the merge: "gate-104 is claimed by: duplicate-page-refresh reports-one-page". 104 was genuinely free when this branch was cut, which is the point. A gate number has to be read off the CALL SITES on the CURRENT main, not off the `# GATE <n>` comments and not off the highest number anyone remembers, and not once at the start: another branch can take it while yours is open. The renumber is anchored on the gate NAME, not on the bare string 104. The runner mentions 104 sixteen times, six of them reports-one-page's own call sites and two unrelated (a count of @SPEC targets, and a list of gate ids), so a blanket replace would have corrupted the gate that just landed. gate numbers unique: 85 gates, every number claimed once checker suite: 10 of 10 arms green acceptance matrix: 220 passed, 0 failed, 78 of 85 gates fixtured both gates coexist: 104 reports-one-page, 105 duplicate-page-refresh
Contributor
Author
|
Renumbered 104 → 105: The renumber is anchored on the gate name, not the bare string Also added since the first review pass:
Verified locally: |
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.
What
Every surface built on
CnDashboardPageorCnDetailPagealready ships a Refresh:CnActionsMenurenders it as the first item of the page-level Actions menu, andshowRefreshdefaults to true. A surface that declares a Refresh of its own therefore ships two, doing the same thing.Measured across the fleet on 2026-08-31, after a user reported the dossiq dashboard showing Refresh as a toolbar button and as the first Actions-menu item. Five surfaces had it:
"type": "refresh"headerAction"type": "refresh"headerAction"type": "refresh"headerAction#actionsslot button#header-actionsslot buttonAll five are fixed in sibling PRs. This is what keeps them fixed.
Why a gate
Nothing downstream could catch it.
check:manifestvalidates against a JSON Schema, and a schema cannot know that the component rendering the page contributes an item of its own. The only detector was a person counting buttons on screen.The remedy names the listener, not just the deletion
On both Vue apps the hand-written button called a host method that was not subscribed to
cn:page:refresh, so the menu item did nothing. Removing the button alone would have left only the dead Refresh. The gate says so.openregister passes because it opts out with
showRefresh: falsewhile keeping its own button. That stays a supported shape; shipping both does not.Full-tree rather than diff-scoped, for the reason gates 84 and 93 through 96 give.
Two load-bearing test arms
NcButton's nested<template #icon>between the slot opening and the Refresh label, so a scan matching to the nearest</template>reads an empty slot and reports clean over a real duplicate.Verified
run-helper-suites.shbash tests/run-helper-suites.sh: exit 0, whole package still greencheck_gate_numbers_unique.sh: 84 gates, every number claimed once[gate-104] duplicate-page-refresh: FAIL[gate-104] duplicate-page-refresh: PASS, COVERAGE 72 of 84