Uh oh!
There was an error while loading. Please reload this page.
chore(desktop): drop the source-scanning contract test suite - #1724
Merged
Conversation
These 149 tests read the project's own source and CSS off disk and asserted on its text. They imported nothing under test and ran no code, so they could only ever catch what a linter catches — "don't write a bare 3px", "don't hand-write role=dialog" — while charging every refactor a rewrite of its own guards. #1711 deleted two of them to land an Astryx migration; #1716 had to rewrite six to move three buttons. Deleted: 149 test files (~675 tests) plus one orphaned source helper. Kept: the 219 behavioral tests in the same directory, every e2e spec, and the fast scripts/check-*.mjs commands (dead-css, a11y, copy, console) that cover the same ground in seconds without a build. Also kept, deliberately, seven static guards that protect non-cosmetic invariants rather than styling: the preload/IPC surface pins, the renderer token-leak gate, external-link noopener, and secret redaction. Those are cheap and guard things review reliably misses. The CSS governance doc kept pointing at deleted guards, so its enforcement section now says what actually holds the line: conventions plus the check-* scripts, verified on the rendered surface.
The deleted CSS-parse contract was postcss's only consumer, and the tailwind-compile contract was the only TS importer of tailwindcss — which styles.css still needs through `@import "tailwindcss"`, so it stays declared and is now ignored by knip instead. Nine helper exports lost their last readers with the suite; knip must report zero for apps/desktop.
jackwenerforce-pushed
the
chore/prune-static-contract-tests
branch
from
July 31, 2026 18:12
7de62ea to
aaaad73CompareAstro-Han added a commit
that referenced
this pull request
Aug 1, 2026
The previous commit had FIDELITY.md admit that nothing checks the `// Real path:` annotations. That was honest but it was the wrong repair: presence of the sentence is mechanically decidable, and this branch had just proved review does not hold that line — chat-surface.stories.tsx reached thirteen stories with twelve annotations and nobody noticed until a script counted them. So the convention now splits along what a machine can decide. Existence is checked by scripts/check-story-annotations.mjs in CI; truth stays with the reviewer, where it has to stay — a schema is satisfied by a plausible lie just as easily. This is not a revival of story-annotation-contract.test.ts. #1724 was right to delete 149 tests that read source text and charged every refactor a rewrite of its own guards, and it explicitly kept the fast scripts/check-*.mjs commands for non-cosmetic invariants. This is one of those: ~110 lines, no build, runs beside check-dead-css in the typecheck job. It keeps the one property of the retired test that mattered: it fails on any top-level export it cannot classify rather than skipping it, because a guard that ignores what it cannot parse passes *because* it did not understand. It also fails if .storybook/main.ts stops loading a directory it scans, so a story tree cannot drift out of coverage. Eight tests cover both, including the case that caught a real bug while writing this: both story roots end in `stories`, so the config check originally matched only the last path segment and stayed green when either root was removed.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…onvention FIDELITY.md described `story-annotation-contract.test.ts` as the thing that checks every product story carries a `// Real path:` sentence, and spent a paragraph on how it derives its file list and refuses to skip exports it cannot classify. #1724 deleted that test with the rest of the source-scanning contract suite. A document that promises enforcement nobody runs is worse than one that admits there is none — it tells reviewers the check already happened. The convention itself is worth keeping, so it now says plainly that it is a review convention and that it decays like one. It had already decayed: AstryxNativeConversation was the one product story with no annotation. It is reachable — a long session accumulates reasoning, tool calls and prose, and an image can sit staged in the composer — but it stacks those states deliberately, which FIDELITY.md requires a story to say out loud. Its comment now says both. Every `Product/*` story carries the sentence again; the remaining gaps are all `Primitives/*` and `Design System/*`, which the document exempts.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
The previous commit had FIDELITY.md admit that nothing checks the `// Real path:` annotations. That was honest but it was the wrong repair: presence of the sentence is mechanically decidable, and this branch had just proved review does not hold that line — chat-surface.stories.tsx reached thirteen stories with twelve annotations and nobody noticed until a script counted them. So the convention now splits along what a machine can decide. Existence is checked by scripts/check-story-annotations.mjs in CI; truth stays with the reviewer, where it has to stay — a schema is satisfied by a plausible lie just as easily. This is not a revival of story-annotation-contract.test.ts. #1724 was right to delete 149 tests that read source text and charged every refactor a rewrite of its own guards, and it explicitly kept the fast scripts/check-*.mjs commands for non-cosmetic invariants. This is one of those: ~110 lines, no build, runs beside check-dead-css in the typecheck job. It keeps the one property of the retired test that mattered: it fails on any top-level export it cannot classify rather than skipping it, because a guard that ignores what it cannot parse passes *because* it did not understand. It also fails if .storybook/main.ts stops loading a directory it scans, so a story tree cannot drift out of coverage. Eight tests cover both, including the case that caught a real bug while writing this: both story roots end in `stories`, so the config check originally matched only the last path segment and stayed green when either root was removed.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…onvention FIDELITY.md described `story-annotation-contract.test.ts` as the thing that checks every product story carries a `// Real path:` sentence, and spent a paragraph on how it derives its file list and refuses to skip exports it cannot classify. #1724 deleted that test with the rest of the source-scanning contract suite. A document that promises enforcement nobody runs is worse than one that admits there is none — it tells reviewers the check already happened. The convention itself is worth keeping, so it now says plainly that it is a review convention and that it decays like one. It had already decayed: AstryxNativeConversation was the one product story with no annotation. It is reachable — a long session accumulates reasoning, tool calls and prose, and an image can sit staged in the composer — but it stacks those states deliberately, which FIDELITY.md requires a story to say out loud. Its comment now says both. Every `Product/*` story carries the sentence again; the remaining gaps are all `Primitives/*` and `Design System/*`, which the document exempts.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
The previous commit had FIDELITY.md admit that nothing checks the `// Real path:` annotations. That was honest but it was the wrong repair: presence of the sentence is mechanically decidable, and this branch had just proved review does not hold that line — chat-surface.stories.tsx reached thirteen stories with twelve annotations and nobody noticed until a script counted them. So the convention now splits along what a machine can decide. Existence is checked by scripts/check-story-annotations.mjs in CI; truth stays with the reviewer, where it has to stay — a schema is satisfied by a plausible lie just as easily. This is not a revival of story-annotation-contract.test.ts. #1724 was right to delete 149 tests that read source text and charged every refactor a rewrite of its own guards, and it explicitly kept the fast scripts/check-*.mjs commands for non-cosmetic invariants. This is one of those: ~110 lines, no build, runs beside check-dead-css in the typecheck job. It keeps the one property of the retired test that mattered: it fails on any top-level export it cannot classify rather than skipping it, because a guard that ignores what it cannot parse passes *because* it did not understand. It also fails if .storybook/main.ts stops loading a directory it scans, so a story tree cannot drift out of coverage. Eight tests cover both, including the case that caught a real bug while writing this: both story roots end in `stories`, so the config check originally matched only the last path segment and stayed green when either root was removed.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…onvention FIDELITY.md described `story-annotation-contract.test.ts` as the thing that checks every product story carries a `// Real path:` sentence, and spent a paragraph on how it derives its file list and refuses to skip exports it cannot classify. #1724 deleted that test with the rest of the source-scanning contract suite. A document that promises enforcement nobody runs is worse than one that admits there is none — it tells reviewers the check already happened. The convention itself is worth keeping, so it now says plainly that it is a review convention and that it decays like one. It had already decayed: AstryxNativeConversation was the one product story with no annotation. It is reachable — a long session accumulates reasoning, tool calls and prose, and an image can sit staged in the composer — but it stacks those states deliberately, which FIDELITY.md requires a story to say out loud. Its comment now says both. Every `Product/*` story carries the sentence again; the remaining gaps are all `Primitives/*` and `Design System/*`, which the document exempts.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
The previous commit had FIDELITY.md admit that nothing checks the `// Real path:` annotations. That was honest but it was the wrong repair: presence of the sentence is mechanically decidable, and this branch had just proved review does not hold that line — chat-surface.stories.tsx reached thirteen stories with twelve annotations and nobody noticed until a script counted them. So the convention now splits along what a machine can decide. Existence is checked by scripts/check-story-annotations.mjs in CI; truth stays with the reviewer, where it has to stay — a schema is satisfied by a plausible lie just as easily. This is not a revival of story-annotation-contract.test.ts. #1724 was right to delete 149 tests that read source text and charged every refactor a rewrite of its own guards, and it explicitly kept the fast scripts/check-*.mjs commands for non-cosmetic invariants. This is one of those: ~110 lines, no build, runs beside check-dead-css in the typecheck job. It keeps the one property of the retired test that mattered: it fails on any top-level export it cannot classify rather than skipping it, because a guard that ignores what it cannot parse passes *because* it did not understand. It also fails if .storybook/main.ts stops loading a directory it scans, so a story tree cannot drift out of coverage. Eight tests cover both, including the case that caught a real bug while writing this: both story roots end in `stories`, so the config check originally matched only the last path segment and stayed green when either root was removed.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…ory per surface (#1783) * ci: guard dead CSS and Storybook rendering check-dead-css was red on main: `.astryx-collapsible-trigger` is rendered by Astryx's own Collapsible and targeted by chat-message.css to give reasoning and tool disclosures one trigger dialect (#1768), but it never appears as a className literal, so the scanner counted it dead. It joins the astryx-button / astryx-badge entries already in DYNAMIC_STYLE_HOOKS. The regression stayed invisible because the guard only ran under `check:release`. It now runs in the typecheck job alongside knip, which is the same class of source-scanning governance. Storybook had no CI coverage of rendering at all. Story typechecking already rides along in `typecheck` via tsconfig.storybook.json, but the static build and the 51-check render/play smoke ran only when someone remembered them locally. Both now run at the end of the e2e job, next to the alignment audit. That job drives Electron through `_electron.launch`, which uses Electron's own binary and never downloads a browser; the smoke calls `chromium.launch()`, so Chromium is installed explicitly. Locally the build takes 7s and the smoke 17s cold, which is noise next to the Electron suite. The e2e route also had to widen. It keys on direct workspace changes so backend work does not drag the renderer suites along, and that stays. But .storybook/preview.tsx reads THEME_PALETTES straight out of packages/core, so a core change could break the Storybook build while routing away from the only job that builds it. * docs(storybook): drop the Astryx showcase and legacy-token stories Eleven stories documented something other than this product. Five were straight Astryx pass-throughs. Button, Badge and Astryx Atoms render re-exports from packages/ui/src/index.ts; Menu and Dialog import @astryxdesign/core/DropdownMenu, /Dialog and /Layout directly. Astryx publishes its own Storybook for all of them, so ours could only ever be a staler copy. Six were catalogues of the token layer #1565 is deleting. Typography, Elevation, Layering, Palette Matrix and Spacing were last touched before the first Astryx commit; Design System/Tokens is newer but documents the same set. All of them hand-render --shadow-minimal, --border-strong and --font-sans through inline styles, under a namespace that presents them as current truth. Rewriting them against Astryx tokens now would mean writing them twice, and once the token contract is settled the useful artifact is one Maka/Astryx integration story that can act as an oracle for the migration — not six generic catalogues that duplicate upstream. Kept: Primitives/Toast wraps Maka's own toast.tsx (the toast.confirm() queue) and product-smoke-manifest.json pins primitives-toast--confirm-queued; Primitives/StatTile covers a Maka primitive. Design System/Icons, Animation Catalog and Interaction States also reference legacy variables, but what they document — the Maka icon set, Maka motion tokens, interaction states — outlives the token rename, so they move with it rather than out. Storybook still builds and the smoke still passes its 51 render/play checks. * docs(storybook): stop claiming a retired test enforces the fidelity convention FIDELITY.md described `story-annotation-contract.test.ts` as the thing that checks every product story carries a `// Real path:` sentence, and spent a paragraph on how it derives its file list and refuses to skip exports it cannot classify. #1724 deleted that test with the rest of the source-scanning contract suite. A document that promises enforcement nobody runs is worse than one that admits there is none — it tells reviewers the check already happened. The convention itself is worth keeping, so it now says plainly that it is a review convention and that it decays like one. It had already decayed: AstryxNativeConversation was the one product story with no annotation. It is reachable — a long session accumulates reasoning, tool calls and prose, and an image can sit staged in the composer — but it stacks those states deliberately, which FIDELITY.md requires a story to say out loud. Its comment now says both. Every `Product/*` story carries the sentence again; the remaining gaps are all `Primitives/*` and `Design System/*`, which the document exempts. * docs(renderer): correct the Astryx tailwind-theme.css note The header said tailwind-theme.css stays out "until Tailwind leaves", which has it backwards. That file's own header calls it a Tailwind Bridge: it maps Astryx tokens onto Tailwind v4 @theme variables so utilities resolve against the theme. It is only useful while Tailwind is present, and the slice in question deletes Tailwind — so it should never be imported at all. Anyone reading the old note while planning that slice would go looking for an import that must not happen. Also corrects the slice number: Tailwind removal is Slice 13, not 12, and #1565 asks that "PR N" never be used as a synonym for a slice. * docs(storybook): apply the deletion rule to the retained motion and state stories The earlier cut used the wrong test. It asked "is this Maka's own?" and kept whatever answered yes. The question that matters is whether Astryx already owns the domain — if it does, a Maka catalogue of the same thing is a parallel vocabulary being documented as the contract, which is exactly what the six deleted token stories were. Animation Catalog failed that test. Its DurationScale and EasingScale presented --duration-quick/base/emphasized/large and the four --ease-* tokens as the motion scale, while the Astryx theme already ships --duration-fast/medium/slow. Both are gone. What remains is the spinner and the streaming shimmer — load-bearing, not decorative, and they outlive the token rename — so the file is now Design System/Functional Motion, which is what it actually shows. Interaction States failed it too, in the part nobody flagged: ButtonStates, NeutralButtonStates and SolidButtonStates are state matrices for Astryx's Button, the same component whose Primitives/Button story this branch already deleted. ListRowStates stays, and it is the reason the file stays: it compares hover and focus across two DIFFERENT row components — Astryx's side-nav item and its list item — as the sidebar composes them. session-list-panel.stories.tsx owns SessionListPanel's own states; nothing else covers the seam between the two. The file is now Design System/Composite Row States. Design System/* is left holding Icons and Functional Motion — the Maka icon set, the brand mark, and two functional animations — plus that one cross-component seam. Nothing in it catalogues a token scale any more. * ci: enforce the mechanical half of the story fidelity convention The previous commit had FIDELITY.md admit that nothing checks the `// Real path:` annotations. That was honest but it was the wrong repair: presence of the sentence is mechanically decidable, and this branch had just proved review does not hold that line — chat-surface.stories.tsx reached thirteen stories with twelve annotations and nobody noticed until a script counted them. So the convention now splits along what a machine can decide. Existence is checked by scripts/check-story-annotations.mjs in CI; truth stays with the reviewer, where it has to stay — a schema is satisfied by a plausible lie just as easily. This is not a revival of story-annotation-contract.test.ts. #1724 was right to delete 149 tests that read source text and charged every refactor a rewrite of its own guards, and it explicitly kept the fast scripts/check-*.mjs commands for non-cosmetic invariants. This is one of those: ~110 lines, no build, runs beside check-dead-css in the typecheck job. It keeps the one property of the retired test that mattered: it fails on any top-level export it cannot classify rather than skipping it, because a guard that ignores what it cannot parse passes *because* it did not understand. It also fails if .storybook/main.ts stops loading a directory it scans, so a story tree cannot drift out of coverage. Eight tests cover both, including the case that caught a real bug while writing this: both story roots end in `stories`, so the config check originally matched only the last path segment and stayed green when either root was removed. * ci(storybook): render the whole catalog, not just the manifest The smoke this branch wired into CI verified 12 stories out of 141. The manifest is curated on purpose — its 51 checks assert layout geometry across three viewports and both colour schemes, which is expensive and only worth paying for where layout actually varies. But that left ~130 stories verified by nothing: build-storybook bundles a story without ever mounting it, so a render that throws, a play function that rejects, or a console error all ship green. "Storybook can no longer rot unobserved" was not true as written. The catalog pass renders every unmanifested story once at wide/light and asks one question: does it mount and finish its play function without errors. It reuses smokeStory, so it already catches storyThrewException, playFunctionThrewException, unhandledErrorsWhilePlaying, console errors and empty roots. Four pages run concurrently: 124 renders in ~35s, against 217s serial. The manifest jobs stay serial and unbaselined — they measure geometry, which is why they pin a viewport. The first run found seven already-broken stories, which is the argument for the pass. Each is a play function whose selector went stale during the Astryx migration with nothing executing it — model-picker looks for the exact placeholder '搜索模型' while shared-ui-copy.ts now reads '搜索模型…'; ask-user-question wants an accessible name user-question-prompt no longer exposes; tool-activity predates the #1768 disclosure dialect. They span four components and each needs that component's current DOM to fix, which is a different change from wiring up the guard. So they go in storybook-catalog-baseline.json, keyed by story id with the reason — the same shape as check-dead-css-baseline.json. Unlisted failures fail the build. A listed story that starts passing ALSO fails, demanding its removal; without that a baseline quietly turns into a permanent exemption, and the list stops being a to-do. * test(storybook): delete the broken stories instead of baselining them The catalog pass found seven stories whose play functions had gone stale during the Astryx migration. The previous commit recorded them in a baseline so the guard could land without also fixing four components. Deleting them is the better answer: a story that has not verified anything since the migration is not coverage waiting to be restored, and the states they claimed to cover are either already covered elsewhere or were asserting behaviour rather than appearance. model-picker Search / NoResults filtering the catalog; the empty result is EmptyCatalog's job tool-activity DisclosureInteraction aria-expanded, computed display, hit-area width and chevron centring — geometry that audit-alignment.mjs and e2e own ask-user-question OtherAnswerSelected typing into the free-text answer settings-pages WebSearch/Voice ×3 provider round-trips reaching a status text Three more went with them under the same rule — a story earns its place only if someone opens it to make a visual judgement that no other story supports: model-picker Pending Default with the control disabled tool-activity CopyFeedback same fixture as ErrorsAndPermissionDenied plus a transient label tool-activity FileDiffAndWebSearch denseMixedResultItems already contains all three of its items, expanded So storybook-catalog-baseline.json and the reconcile step are gone with them. Without known-broken entries the catalog pass is just "every story renders", and a future breakage has two honest options — fix it or delete it — rather than a third that defers both. 136 stories → 126; the smoke now runs 51 manifest checks and 114 catalog renders in ~64s with nothing exempted. * test(storybook): keep one story per surface, drop the state enumerations 126 stories down to 58. The rule: a story earns its place only if someone opens it to make a visual judgement no other story supports. In practice that leaves, per surface, the densest reachable state — plus an empty state where the layout genuinely differs, plus whatever product-smoke-manifest.json pins. What went: state enumerations SearchModal's Loading / NoResults / Error / Blocked next to Results; onboarding's four gate variants of one hero; provider-settings' Loading / LoadError / Empty behaviour, not paint CommandPaletteKeyboardFocusedSelection, RowActions, RowMenuOpen — interaction the E2E suite owns states contained by Usage and UsageEmpty under UsageRequestsPopulated; a denser sibling Memory under MemoryPopulated; app-shell's PlanModeActive and SwarmModeActive; tool-activity's StatusOverview, TerminalAndLiveOutput and SubagentAndExplore, all inside denseMixedResultItems duplicated elsewhere app-shell's MarkdownCore against markdown.stories.tsx Every story the manifest pins survives, including the eight module-hubs entries and primitives-toast--confirm-queued. Deleting stories orphans their fixtures, and tsconfig.storybook.json does not set noUnusedLocals, so nothing would have reported them. Compiling once with the flag on found fourteen — bridges, settings fixtures, voice capture helpers — and they go too. Two guards caught mistakes made while doing this, which is the argument for having wired them up: validateCoverageManifest rejected a build after a regex with a shared name prefix took ExtensionsSkillsInstalled out along with ExtensionsSkills, and check-story-annotations rejected the next one after a cleanup pass ate the `// Real path:` line above Models. The catalog pass is now 47 renders instead of 114, and the whole smoke runs in 34s instead of 64s. * fix(storybook): restore the three states the prune should not have cut Three of the cuts failed the retention rule they were made under — "keep the state no other story supports" — and an independent review of the branch caught all three. Palette Matrix was dropped as a token catalog Astryx now owns. It is not one: THEME_PALETTES is a product feature, the palettes a user picks in 设置 → 外观, and the catalog pass renders wide/light/default only. Ten of the eleven had no oracle at all. The file's header now records why the other five token stories still went, since "Astryx already owns this domain" was the wrong reason for all of them — maka-tokens.css is still the single authority and dies with Slice 13; a table of var names is simply low review value with a short life. Permission Center kept the collapsed state and cut the expanded one, which is backwards: the capability layers grid and guidance block only exist once diagnostics are expanded, and the deleted story's own comment says that is where the last overflow bug was hiding. Everything the collapsed story showed is still on screen in the expanded one, so this stays one story. VoicePermissionDenied comes back because the surviving Voice story is idle and the page's only error surface had no coverage anywhere — the E2E suite checks fields and persistence, not capture outcomes. It comes back without the 120-line MediaRecorder decorator the old story carried: permissionSnapshot already reports microphone `denied` on darwin and runCaptureSmoke returns on that snapshot before it ever reaches getUserMedia, so the real path needs no browser mocking. The catalog pass then failed the restored story immediately, for the same reason the originals rotted: the result lives in one of six `[role="status"]` regions and `querySelector` had picked an empty one. Also drops two doc comments the prune orphaned, which now described stories that no longer exist. * fix(ci): close the holes in the story-annotation guard The guard claimed to fail on anything it could not classify. Review found five inputs where it did not, each now covered by a test: - `.storybook/main.ts` loads `*.stories.@(ts|tsx)`; the scanner only opened `.stories.tsx`, so a `.ts` story was never read at all. - `export { Story }` and `export async function Story()` reach the browser as stories and matched nothing, so they passed in silence — the exact failure the "fail on what you cannot parse" rule exists to prevent. - `export const X: Story =` wrapping onto the next line was reported as a malformed export. A guard that reddens on formatting teaches people to ignore it. - The title regex took the first `title:` in the file, not meta's. A fixture literal above meta decided the file's namespace, and a `Design System/…` one exempted every story in it. - An empty `// Real path:` counted as an annotation. checkStorybookRoots claimed to catch a root added to main.ts but not scanned here; it only checked that the two known roots were still present. It now compares both directions, and the test covers the addition case and the accept-the-real-config case — the latter previously asserted `[] deepEqual []` against a function that returns undefined, so it verified nothing. FIDELITY.md now also says what the check is: a convention guard that must fail in seconds inside typecheck, approximating a story set only Storybook's index truly knows. Every gap above was closed by widening what it fails on, never by widening what it skips. * ci: run the Storybook smoke when its own runner changes `scripts/**` only sets scriptMode, so a change to the smoke runner was verified by nothing but the run it never triggered — the one file most able to make the guard silently stop guarding.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…ing the checker Two edits to the fidelity convention: - Add the rule the commit before this one discovered: `play` is inert here, so behavioural and computed-style contracts belong in a packages/ui test or the smoke script's checks, not in a story. - Cut three paragraphs that re-derive check-story-annotations.mjs's design — its fail-closed rationale, #1724's history, and why a source scanner only approximates the story set. All of it already lives in that script's header, which is where someone editing the guard will read it. The doc keeps the rule authors need: annotate every story, and write it as `export const Name: Story = …`. - Drop the trailing `# Sandbox boundary prompt` section. It described one story's two variants, in an H1 that broke the document's structure, and those variants carry the same description as their own `// Real path:`.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…ing the checker Two edits to the fidelity convention: - Add the rule the commit before this one discovered: `play` is inert here, so behavioural and computed-style contracts belong in a packages/ui test or the smoke script's checks, not in a story. - Cut three paragraphs that re-derive check-story-annotations.mjs's design — its fail-closed rationale, #1724's history, and why a source scanner only approximates the story set. All of it already lives in that script's header, which is where someone editing the guard will read it. The doc keeps the rule authors need: annotate every story, and write it as `export const Name: Story = …`. - Drop the trailing `# Sandbox boundary prompt` section. It described one story's two variants, in an H1 that broke the document's structure, and those variants carry the same description as their own `// Real path:`.
Astro-Han added a commit
that referenced
this pull request
Aug 1, 2026
…scaffolding (#1849) * refactor(stories): derive bot brand icons from BOT_BRAND The catalog kept its own list of the eight IM channels, so the story was satisfied by any subset of the registry BotBrandLogo actually reads — a newly supported channel would simply never render. Read the keys instead, matching how LucideIcons already tracks the icon barrel, and drop the empty omit-list and the always-true component guard it filtered through. * refactor(stories): drop the ask-user-question review scaffold The story built its own two-column board with a hand-written fake transcript and 60 lines of story-only CSS, then hardcoded 760px and 390px chat columns. The prompt already caps itself at --maka-chat-measure and its production slot is display:contents, so those widths were the story's invention rather than the product's — the retyped chain FIDELITY.md warns about, and the transcript above it was a screen no user reaches. Mount the prompt the way its sibling sandbox-boundary-prompt story does, and give the manifest the narrow column back as compact and floor viewports, where it is the real thing instead of a fixed div. * refactor(stories): fold the plan-reminder variants into one list Plan reminders held five of the manifest's surfaces while every neighbouring page held one, and two of the five paid for it without showing anything new. The attention state is a single blocked row, which is exactly the kind of thing a scanning page only proves it surfaces when the row has healthy neighbours — so it joins the configured list, the way ExtensionsMcpConfigured already pairs one healthy server with one failed one. Keep-awake renders nothing at all: plan-reminder-panel.test.tsx asserts the enabled page stays visually identical and keeps the state inside the settings menu, so the story smoked pixels identical to the configured one and its play function re-tested a prop the unit test already covers. Also drop the "~130 stories" figure from the catalog-pass comment; the count has been wrong since #1783 cut it, and a live number there earns nothing. * refactor(stories): host the chat transcript in the real shell Product/Chat Surface mounted ChatView and Composer inside a 960x760 centred card with a border and a radius — a frame the product does not have. In the app the transcript lives in the full-height maka-panel-detail pane that app-shell.stories.tsx already composes from the real AstryxAppShell, so every geometry comparison against the old story was measuring its own scaffolding. Two of its six stories were also the shell stories' states in that wrong frame: StreamingResponse against StreamingTurn, and the two empty chats against EmptyHome, which is the only empty home the app renders. Move the states the shell did not have — the reasoning-and-tool-call transcript and the session context layer — onto ComposedShell, and delete the parallel file with its second copy of the session, message and composer fixtures. Widening the session overlay to Partial<SessionSummary> keeps the one-active-session rule while letting a story set the name and labels the context layer reads. SessionContextNarrow is not carried over: it faked a narrow column with a 360px div, and the manifest now renders the same story at compact and floor. * test: assert the keep-awake state on its settings menu item The panel renders identically whether keep-awake is on or off, so the settings checkbox is the only thing carrying the state — and the existing test only checked that the trigger exists. Deleting the story that opened the menu therefore left aria-checked unverified. Astryx renders the menu in static markup, so the contract lands in the same suite rather than needing a browser. * refactor(stories): derive the shell's chat state from production helpers Review found the migrated stories still short of what this PR claimed. The detail pane retyped AppShellDetailPanel's classes and dropped the two wrappers below it, so .mainColumn never applied and the composer measured its own box; the composer omitted onModelChange, which renders ChatModelSwitcher disabled; and NativeConversation lost the footer actions the old file hardcoded. Mount the real AppShellDetailPanel with the production wrappers, and derive what the renderer derives: deriveAppShellTurnViewModel for footer actions, deriveBranchBanner and deriveSessionRevisionNavigation for lineage. Deriving is the point — SessionContextLayer previously hand-wrote a banner and a revision counter for a session catalog containing neither a parent nor a revision family, and asserted fromAbortedTurn, which app-shell.tsx documents it deliberately never passes. The story now supplies the relatives and gets the banner only if the real rules still produce one. A story that renders no lineage while claiming to show it is the failure this should catch, so sessionContext gets a surface check for the breadcrumbs, the revision counter and horizontal overflow; removing the fixture makes it fail at all three viewports. Excluding id from the session overlay closes the one field that would have desynchronized the sidebar from the chat rather than projecting onto it. * fix(stories): make the shell fixtures states the app can actually produce Second review round found the lineage fixture from the first one was itself impossible. The store's isValidRevisionLineage accepts the five revision fields together or not at all and rejects any index below 2; the fixture set revisionRootSessionId and revisionIndex alone, and made the root its own revision. reviseBeforeTurn shows the real shape: the root carries no revision fields and each revision carries all five, keeping the branch lineage it was revised from. The family is now built that way, and the sidebar runs through projectRevisionLinkedSessionTree, because production collapses a family to one row and the story was about to show three. WaitingForPermission forced disabled: true, but app-shell.tsx never passes disabled to ChatComposerRegion — the textarea stays usable while a tool waits, so the story was inventing a locked input. Restore the no-active-session composer as NewChatComposer. Deleting EmptyChat took the only coverage of Composer's other branch with it: without an active session it renders NewChatModelPicker instead of ChatModelSwitcher and drops the thinking selector. EmptyHome's comment claimed that state differed only in the sidebar, which was wrong. The ask-user-question decorator now reproduces the two wrappers around the composer slot. The prompt's root carries the composer class and .mainColumn zeroes its top padding, so the bare canvas rendered it 8px low; measured at 0px in the built story afterwards. Guards, all three verified by making them fail: - sessionContext joins REQUIRED_PRODUCT_SURFACES, so deleting its manifest entry now fails the validator instead of silently retiring its check. - The check reads every OverflowList copy rather than the first, which is the hidden measurement clone, and asserts goal and revision are visible or in the overflow menu — not merely present in the DOM. - The countdown spacing loop ran zero times if countdowns disappeared; it now requires one when a scheduled row exists. - check-story-annotations skipped `export` followed by a newline, so a story in that shape passed unread while the check reported success. * test(ui): cover every Host origin marker where the assertion runs #1853 asserted the automation-origin marker's line height inside a story `play` function. Play functions never execute in this Storybook — no test addon is configured, and a deliberately throwing `play` still ships green through scripts/storybook-visual-smoke.mjs — so that contract never ran. Keep the automation-triggered turn as visual coverage by folding it into the transcript scaffold, and move the contract into the seam that runs: the existing origin-presentation test, widened from Goal alone to all three Host origins, each of which is a separate branch with its own copy. * docs(stories): record that play functions do not run, and stop restating the checker Two edits to the fidelity convention: - Add the rule the commit before this one discovered: `play` is inert here, so behavioural and computed-style contracts belong in a packages/ui test or the smoke script's checks, not in a story. - Cut three paragraphs that re-derive check-story-annotations.mjs's design — its fail-closed rationale, #1724's history, and why a source scanner only approximates the story set. All of it already lives in that script's header, which is where someone editing the guard will read it. The doc keeps the rule authors need: annotate every story, and write it as `export const Name: Story = …`. - Drop the trailing `# Sandbox boundary prompt` section. It described one story's two variants, in an H1 that broke the document's structure, and those variants carry the same description as their own `// Real path:`.
Astro-Han added a commit
that referenced
this pull request
Aug 2, 2026
maka-tokens.css claimed five static guards enforced its scales: motion-token-converge, icon-system-contract, the spacing adjacency pin, and governance contract #406. All of them went out with the 149 source-scanning tests in #1724. A comment that promises a guard nobody runs is worse than no comment — it reads as "the build will catch this" while nothing does. The rules themselves stay; only the enforcement claims change to what actually holds them: convention and review. Also record why chat-message.css reaches for an Astryx duration instead of the product scale. It is not a leak: Astryx ChatToolCalls opens its own group with `grid-template-rows --duration-medium --ease-standard`, so a detail row that expands beside one has to share that clock. Without the note the next reader "fixes" it onto --duration-large and desyncs the two disclosures. check-dead-css: allowlist astryx-icon and astryx-app-shell-sidenav, both themeProps classes that exist only at runtime.
Astro-Han added a commit
that referenced
this pull request
Aug 2, 2026
maka-tokens.css claimed five static guards enforced its scales: motion-token-converge, icon-system-contract, the spacing adjacency pin, and governance contract #406. All of them went out with the 149 source-scanning tests in #1724. A comment that promises a guard nobody runs is worse than no comment — it reads as "the build will catch this" while nothing does. The rules themselves stay; only the enforcement claims change to what actually holds them: convention and review. Also record why chat-message.css reaches for an Astryx duration instead of the product scale. It is not a leak: Astryx ChatToolCalls opens its own group with `grid-template-rows --duration-medium --ease-standard`, so a detail row that expands beside one has to share that clock. Without the note the next reader "fixes" it onto --duration-large and desyncs the two disclosures. check-dead-css: allowlist astryx-icon and astryx-app-shell-sidenav, both themeProps classes that exist only at runtime.
Astro-Han added a commit
that referenced
this pull request
Aug 2, 2026
…rail (#1876) * fix(desktop): paint column surfaces through a transparent titlebar overlay Stop using AppShell topNav as a full-width chrome row so sidebar canvas and session --background extend to the window top (Codex/Claude/Cursor). Keep a single absolute drag overlay with no-drag action clusters, Windows WCO gutters, and 16px secondary ghost icons aligned with SideNav chrome. * feat(desktop): collapse the session rail into a 48px icon rail Continuous Collapse: the 48px rail stays in flow and paints ink only, with `.appFrame` flipping to the plate background when collapsed, so the transparent titlebar reads as one surface with the column instead of a chrome bar. Session rows share the nav items' left edge: `.maka-list-stackContent` no longer adds a second inline inset on top of SideNav's `scrollable` padding, and its grid track is `minmax(0, 1fr)` so a long title cannot push the row past the rail and get its end actions clipped. `.maka-session-item-label` is block-level flex, not inline-flex. An inline box is shrink-to-fit with a min-content floor, which for a nowrap title is the whole string — the label then outgrew its slot and painted over the row meta instead of ellipsizing. Width easing is scoped to collapse/expand via `data-easing`. A standing width transition also caught the per-pointer-move updates from the resize handle and made the drag lag the cursor. * fix(desktop): restore Astryx's icon scale under the 13px product root Astryx sizes icons in rem so they track the root font-size, and documents the scale as its px-equivalents at a 16px root: 12 / 16 / 20 / 24. Maka's root is 13px, so every icon Astryx renders lands at 81% of that — 13px sitting next to the 16px lucide icons the product hands to Astryx slots by absolute size. The nav rail showed it worst: 新任务 / 扩展 / 定时任务 read visibly smaller than the same-family icons one settings page away. The 13px type scale is deliberate; the icon scale was never meant to follow it, so restate Astryx's own intent in px at the renderer mounts rather than move the rem base. Class-on-class beats StyleX's single-class rules without !important. * fix(desktop): drop the footer hairline on the collapsed rail The rule that was supposed to do this targeted `.maka-session-panel-footer`, whose border is already `0 !important` — it never had a border to clear. The hairline is drawn one level up, on the sticky-bottom host that separates the scrolling history from the footer. At 48px there is no history column left to separate, so the line reads as an orphan rule floating above the Settings icon on an otherwise ink-only rail. Scope the collapsed override to the rule that owns the border in sidebar.css rather than reaching for it from the shell. * docs(desktop): stop citing contracts that no longer exist maka-tokens.css claimed five static guards enforced its scales: motion-token-converge, icon-system-contract, the spacing adjacency pin, and governance contract #406. All of them went out with the 149 source-scanning tests in #1724. A comment that promises a guard nobody runs is worse than no comment — it reads as "the build will catch this" while nothing does. The rules themselves stay; only the enforcement claims change to what actually holds them: convention and review. Also record why chat-message.css reaches for an Astryx duration instead of the product scale. It is not a leak: Astryx ChatToolCalls opens its own group with `grid-template-rows --duration-medium --ease-standard`, so a detail row that expands beside one has to share that clock. Without the note the next reader "fixes" it onto --duration-large and desyncs the two disclosures. check-dead-css: allowlist astryx-icon and astryx-app-shell-sidenav, both themeProps classes that exist only at runtime. * chore(desktop): delete orphan tokens and let check-dead-css see them Sixteen tokens in maka-tokens.css had no reader left anywhere: the --action CTA pair, --toast-accent, --bot-brand-default, --brand-deep-hover, --system-alert-accent, two --color-* alias tiers, --shadow-modal, --focus-glow-width, --scale-hover, --ease-drawer, --font-serif, --h-toolbar, --h-list-header, and --w-sessionlist (session width has been React state for a while). Their surrounding notes claimed live consumers, so the comments went with them. Ten unused tokens stay. A rung of an ordered scale outliving its last consumer is not rot — deleting the middle of the z-index or control-height ladder is exactly what invites the next bare number. RESERVED_SCALE_TOKENS carries them with the series each belongs to. check-dead-css now sweeps tokens alongside classes, so this converges instead of drifting back. Two consumer surfaces the class sweep never needed are in scope for tokens: packages/ui stylesheets, and stories — a story composing a surface out of the design vocabulary is a real reader, though a product class only a story mentions is still dead. Verified both ways: an injected orphan trips --check in either declaration style, and the live renderer resolves every var() after the deletions. * fix(desktop): give the session column a plate you can actually see Nothing draws a line between the session rail and the content column, by design: Astryx ships a divider only on `AppShell variant="section"` and Maka runs "surface" (#1732). The separation is meant to come from the material — `--surface-canvas` behind the rail, `--background` on the content — which is exactly what the token block has claimed since the gray-plate/white-card model landed. The step was too small to deliver it. `l - 0.024` resolves to #f7f7f7 against white: six sRGB levels, below what reads as a second surface on most displays. Until now nobody saw the miss, because Astryx painted its own opaque surface over the frame plate; this branch makes the shell transparent so the plate finally shows, and what showed was almost nothing. 0.045 is not a taste pick. It is the surface→body step Astryx itself ships (#ffffff L1.000 → #f1f1f1 L0.958), and it puts light mode within a hair of the dark step (0.065) this file has carried all along. Dark stays where it is — it was tuned against a dark plate, reads fine, and is not the boundary anyone reported losing. Not variant="section": a hairline plus a wash is two mechanisms for one job, and section's divider is unconditional, so it would draw a rule down the 48px icon rail — the same orphan line this branch just removed above Settings. * refactor(desktop): let AppShell own the column materials The column boundary was a product invention held together by two `!important` rules: paint `--surface-canvas` on `.appFrame`, then force AppShell and its sideNav slot transparent so the plate reads through them. Every layer in between had to stay transparent forever, and Astryx's own material tokens sat unused underneath. Astryx ships this exact model. `variant="elevated"` — its default — paints the nav column `--color-background-body` and the content column `--color-background-surface`; with no topNav there is no radius or backdrop, just the two materials. Maka ran `"surface"`, the one variant that paints both columns the same and draws no divider, which is why there was nothing to see. Taking Astryx's paint means fixing whose colors it paints with. Its neutral pair is static light-dark(), so it cannot follow Maka's six switchable palettes — and in dark mode the two systems disagree outright: Astryx's surface is #262626 against the product's #171719, so the nav column rendered LIGHTER than the content it navigates. makaTheme.ts now points both material tokens back at the palette. Direction is deliberate and opposite to the type scale above it: Astryx's scale covers everything Maka needs from type, while its neutral pair is a fraction of a palette that also carries status, chat, and per-theme colors. Dark's plate step joins light's 0.045. The old 0.065 was never seen on the shell — Astryx painted over the frame plate until now — and against #171719 content it renders a near-black #09090b rail. Continuous Collapse survives as the one rule that overrides a column material: collapsed, the 48px rail takes the content surface, because the traffic lights are wider than the rail and a wash boundary would run a seam through them. Measured in Storybook (Chromium, sampled pixels), both schemes, both states: light expanded nav #f0f0f0 content #ffffff light collapsed nav #ffffff content #ffffff dark expanded nav #0d0d0f content #171819 dark collapsed nav #171719 content #171819 * fix(desktop): stop tooltips from swallowing clicks under them Astryx tooltips are text popovers with no interactive content, but they leave pointer-events at auto, so an open tooltip is a hit target. Anchored to a control in the titlebar there is no room above it, so the layer flips below the window's top edge and lands on the SideNav's first row — 86×28 of it, measured live, directly over 新任务. The E2E caught it deterministically: click 展开侧边栏, and the pointer stays parked on its trigger, so the tooltip that opens never closes and the next click has nowhere to land. A person hits the same edge more briefly on the way down to the first nav row. It surfaced now because 新任务 moved into the rail in this branch; before, the row under that tooltip was not a target. A tooltip is an affordance, never a target. One unscoped rule — the popover renders in the top layer, where a mount-scoped selector is not reliably in the chain — and no !important, since StyleX declares no pointer-events of its own. While here: keep the type-scale comment in makaTheme.ts adjacent to `typography`. The materials block landed between them last commit. * feat(desktop): split the columns with a hairline and a quieter wash Sampled the two apps this shell is measured against, by scanning a pixel row across their column seam: Cursor nav #edeeef line 220 content #f6f6f6 ΔL 0.025 Codex nav ~#eeefef line 210 content #ffffff ΔL ~0.010 Maka nav #f0f0f0 (none) content #ffffff ΔL 0.045 Both draw a 1px rule, and both keep the wash far below what Maka was carrying. Maka's sidebar was never the darker one — it was the lightest of the three — but with no line it had to state the whole boundary through lightness, and a wash that wide reads as a gray area rather than a second panel. So: wash down to 0.025, plus the rule. The line says where the boundary is; the wash only says the two columns are different material. The rule is authored in makaTheme.ts, not as a product override, so AppShell keeps one paint authority — the theme emits it into its own @scope, and the color is the product's --border, the same product → Astryx direction as the materials. Astryx's own divider is not usable here: it is hardcoded to `variant === 'section'`, which also drops the wash entirely, and it is unconditional — it would draw down the 48px icon rail. Collapsed still drops both halves of the edge. The traffic lights measure ~62px against a 48px rail, so any column edge there cuts through the cluster. Live measurements (2x capture, pixel row across the seam): light nav 247 line 224 content 255 dark nav 17 line 38 content 23 * revert(desktop): drop the icon-scale pin, the 13px root is gone This branch pinned Astryx's four icon sizes to px because the product root was 13px and every rem-based icon landed at 81% — 13px next to the 16px lucide icons handed to the same slots. That was true when it was written and is not true now: #1857 landed in the rebase and removed the root pin outright, saying so in maka-tokens.css ("the root font-size stays at the browser default (16px) ON PURPOSE") for exactly the same reason this commit existed. So the pin restates what rem already computes. Measured in Storybook across three stories, with the rule and with it reverted: xsm 12, sm 16, md 20, root 16px, identical either way. Keeping it would leave 34 lines of CSS whose comment asserts a 13px root that main's own token sheet denies — the precise shape of rot the rest of this branch is removing. * refactor(desktop): key the column edge on the elevated variant `base` drew the rule under any variant, including ones that paint the columns differently or draw their own divider. Astryx keys component overrides by prop (`variant:elevated`), which emits `.astryx-app-shell-sidenav.elevated` — verified on the live node, which carries both classes. * test(e2e): lock the column edge in both sidebar states The bug this branch was asked to fix — no visible boundary between the session column and the content column — had nothing holding it. Neither half of the edge is greppable: the material comes from a generated theme file, the rule from a component override inside it, and a CSS grep passes on declarations that never reach the element. So assert the rendered result. Collapsed is asserted too, and asserts the opposite: both halves go away, because the traffic lights are wider than the 48px rail and any column edge there cuts through the cluster. Without that half a reviewer cannot tell the collapsed rail from a regression. Both halves ease between states, so the comparisons poll for the resting value — sampled mid-transition the color comes back as oklab(). Mutation-checked, rebuilding between runs: divider width 1px→0px fails on the border assertion, dropping the collapsed background override fails on the material assertion. * docs(desktop): stop describing tokens this branch deleted The dead-token sweep removed the declarations and left their prose, so the token sheet documented six things that no longer exist: the serif family with its whole section header, a "these three geometry tokens" focus-ring recipe that has two, a transform-amplitude note listing a hover scale nothing uses, and the brand/system-banner tint aliases whose comments dangled above the next section. Also drops the double blank line where --shadow-modal stood. The lucide stroke note is corrected rather than deleted: it justified keeping size per call site by pointing at icon rules in astryx-mount.css, and those were reverted on this same branch when the 13px root that motivated them went away. The reason still holds — Astryx sizes its slot icons through its own props — so the sentence now names that instead of a file that no longer says it. * fix(desktop): give the shell one paint authority and tests that can fail Five independent reviews of this branch converged on two defects it had introduced, both of the same kind: a comment asserting a guarantee the code did not provide. window-titlebar.spec.ts had stopped seeing the class of defect it exists for. The strip became an absolute overlay with default pointer-events, so it is the topmost hit at every point of the band and `elementFromPoint` returns it and nothing else; every control beneath silently failed the "is this reachable" precondition and was skipped. The assertion that used to catch this was inverted in the same change (`band.bottom <= contentTop` became its converse), leaving a form satisfied by exactly the geometry that creates the hazard. Mutation-proved: deleting either column's `padding-top` put 新任务, the workbar tab and 查看上一版本 under the overlay — real 30s click timeouts in three other specs — while this spec passed. The sweep now probes with `elementsFromPoint` minus the strip's own subtree, and splits the judgment: the strip's children are reachable and keep the app-region rules, anything beneath it is a defect outright, since no-drag shapes the OS rect while the click is already lost to the strip's hit-testing. That immediately caught the resize handle, which is positioned against the column box rather than flowing inside it and so kept its top 36px under the strip; it now starts below it. The Astryx neutral ramp was half-migrated. Remapping only the two tokens the shell reads left card, popover, muted and the hairline on the static light-dark() pair, and a ramp with one half palette-driven and the other frozen does not merely drift — it inverts: stock muted is L 0.222 against a --background of 0.18–0.24, so the recessive fill sat at or above the surface it is recessed into, in 8 of 11 dark palettes. Card, Code, ChatToolCalls, Slider and TableRow are the transcript, not chrome. The whole ramp now moves, onto the product's own stated hierarchy; --muted lands on foreground-at-5%, defined relative to whatever it sits on, so the inversion is unrepresentable rather than merely corrected. Also from the reviews, each verified before acting on it: - The tooltip hit-test rule was global, disabling the hover bridge Astryx documents as its WCAG 1.4.13 implementation for all 33 call sites. Scoped to the titlebar, the one place the layer cannot flip into empty space, with the concession stated. - Both new `!important`s rested on "StyleX is unlayered", which this app contradicts: astryx.css is one @layer astryx-base block imported into astryx-components, which product `components` outranks outright. Dropped. The `.astryx-button` colour rule went with them — Icon colours the glyph itself, so a button-level rule never reached it. - The content column had two paint authorities, and the new column-material test sampled the product's copy, so it would have passed with AppShell painting nothing. It samples AppShell's own element now, and the product's background is gone. - `.maka-sidenav-motion` mirrored a width SideNav already owns and then needed `width: 100% !important` to beat it. Deleted; the transition lives on the nav. Its React easing flag went too: cleared on transitionend, it never cleared when no transition ran, leaving every later drag animated — the exact fault it existed to prevent. The gate is now derived from the handle's own `data-resizing`/focus, so it cannot desynchronize. - `data-sidebar-state` had three writers and one reader; `data-modal-background- hidden` had no readers at all. One writer, on the frame. - The Storybook shell omitted `data-sidebar-state`, so the pixel-review surface rendered a collapsed state the app does not have. New locks, each mutation-verified to fail: the 48px collapsed rail (the number the dropped column edge argues from), the transition suppression during resize, and the column materials read off AppShell. Not done, and deliberately: the generated edge selector is a bare `.elevated` class, which Astryx's own themeProps notes advise against — the CLI emits it, so it is an upstream report, not a local fix.
6 tasks
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 freeto 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.
Summary
apps/desktop/src/main/__tests__held 375 test files. 149 of them read the project's own source and CSS off disk and asserted on its text — they imported nothing under test and executed no product code. That shape can only catch what a linter catches ("don't write a bare3px", "don't hand-writerole=dialog"), and it charges every refactor a rewrite of its own guards:This deletes that suite.
Kept:
scripts/check-dead-css / check-a11y / check-copy / check-console— same ground, seconds, no build requiredpreload-bridge-contract,ipc-surface-contract,permission-response-ipc-boundary,claude-subscription-ipc-boundary(renderer token-leak gate),external-link-noopener-contract,tool-args-redaction-contract,shared-oauth-token-persistence. These are cheap and guard things review reliably misses (Electron sandbox boundary, secrets in logs).One orphaned helper (
session-history-owner-source-helpers.ts) went with its only consumers.docs/frontend-css-governance.md(+ zh-CN) pointed at three deleted guards; its enforcement section now states what actually holds the line — conventions plus thecheck-*scripts, verified on the rendered surface rather than by regex over CSS.Verification
tsc -p tsconfig.main.jsonclean after deletion — no remaining test imports a deleted helper.mcp-runtime-e2e(assert.ok(echo)), which passes on its own twice and is load-sensitive under the parallel run — it also failed intermittently on this tree before this change, so it is pre-existing flake, not fallout.biome lintclean;knipreports no newly unused files.testjob duration on this PR versus the 3m43s/6m41s runs on main is the real number.Review focus
Whether the seven kept guards are the right cut line — the argument for keeping them is that they encode security/privacy boundaries rather than styling taste. If you want them gone too, they are seven files and one follow-up commit.