041: one condition, one notice (#278 #327 #328 #314) - #337
Merged
Bidthedog merged 8 commits intoAug 27, 2026
Conversation
Bidthedog
added a commit
that referenced
this pull request
Aug 27, 2026
T047a (the FR-019 banner guard, 4/4 passing), T064 (no mutation harness or gate stage added — verified against the diff and package.json), T065 (docs/testing.md needed no edit; it references e2e-budget.json rather than restating its numbers) and T066 (affected.ts's header now describes casualties and records why the panel-named symbols keep their names). 74 of 81. The status block is the point of this commit rather than the four checkboxes. An unticked task looks the same whether it was forgotten, deferred, or deliberately not done — and these seven are open for three different reasons, only one of which is ordinary unfinished work. Three cannot be reproduced. #278 declares a second defect, a raw ENOENT rendered as the notice's second line, and the route by which it reaches the screen was not found: ENOENT classifies at cause.ts:102, so files-service returns the spoken sentence and the raw text rides in copyDetail, which nothing renders. Nothing was changed on a theory, because a fix behind no failing test cannot be verified by anything — including the green suite that would follow it. One is the developer's: the manual regression watch is not automatable and not the implementer's to tick. Three are genuinely outstanding, and T063 is the one with teeth — FR-030a's sensitivity proof, where each guard's fix is reverted once and that guard is observed to fail. A guard nobody has watched go red is an assertion that it would, which is exactly the assumption Group 5 exists to stop making. NOT regenerated. /speckit-tasks builds a task list from the template, which would have rewritten this file wholesale and taken 74 ticks, the per-task deviation notes, and the task IDs that five commit messages and PR #337 reference with it. The need was four checkboxes.
Bidthedog
force-pushed
the
feature/S041-I278-I314-I327-I328-notice-consolidation
branch
from
August 27, 2026 07:01
d149f25 to
b554891
Compare
Bidthedog
added a commit
that referenced
this pull request
Aug 27, 2026
T047a (the FR-019 banner guard, 4/4 passing), T064 (no mutation harness or gate stage added — verified against the diff and package.json), T065 (docs/testing.md needed no edit; it references e2e-budget.json rather than restating its numbers) and T066 (affected.ts's header now describes casualties and records why the panel-named symbols keep their names). 74 of 81. The status block is the point of this commit rather than the four checkboxes. An unticked task looks the same whether it was forgotten, deferred, or deliberately not done — and these seven are open for three different reasons, only one of which is ordinary unfinished work. Three cannot be reproduced. #278 declares a second defect, a raw ENOENT rendered as the notice's second line, and the route by which it reaches the screen was not found: ENOENT classifies at cause.ts:102, so files-service returns the spoken sentence and the raw text rides in copyDetail, which nothing renders. Nothing was changed on a theory, because a fix behind no failing test cannot be verified by anything — including the green suite that would follow it. One is the developer's: the manual regression watch is not automatable and not the implementer's to tick. Three are genuinely outstanding, and T063 is the one with teeth — FR-030a's sensitivity proof, where each guard's fix is reverted once and that guard is observed to fail. A guard nobody has watched go red is an assertion that it would, which is exactly the assumption Group 5 exists to stop making. NOT regenerated. /speckit-tasks builds a task list from the template, which would have rewritten this file wholesale and taken 74 ticks, the per-task deviation notes, and the task IDs that five commit messages and PR #337 reference with it. The need was four checkboxes.
…nd real errors Spec 041 covers #278, #328, #327 and #314 as one feature, on the grounds that three of the four are conformance rather than invention: 029 FR-016/018/019 and 030 FR-034/037a were established, shipped, and have since stopped holding. The spec restores them without renumbering them, and adds guards so the next silent retirement fails something. Two pieces are genuinely new ground — what happens when a file is refused and no editor panel exists (#327), and what the keyboard route to a notice is (#314). THIRTEEN CLARIFICATIONS, THEN THREE ANALYSIS PASSES The passes are worth recording because each caught a different class of error, and the severity fell 3 → 4 → 0 without any finding repeating. The first pass caught a contradiction the clarifications had themselves introduced: FR-007a said "a refused open does" carry an affected-panel list while FR-013 said a refused open creates no panel. affected.ts requires a panelId on every row and de-duplicates on it, so after FR-013 there was nothing to key on and #328 would have stayed unfixable for exactly the case #327 creates. That is why the two are one piece of work rather than two. The second pass read the SOURCE rather than the artifacts, and found three more. FR-007's identity is a fallback and not a triple — the panel supersedes (subject, reason), because within one notice a given panel fails once. The widening as specified would have made subject and reason required on every row, breaking six construction sites at compile time, four of them the tests whose job is to prove the old behaviour survived. And FR-013a missed an entry point in the very surface #327 was reported from. The third pass caught the sharpest one: FR-013d, as I had just written it, REVERSED a decision 033 recorded in quick-open.tsx — that the gate belongs in the caller because making openFileInNewEditor silently not force a new panel "would change a shipped contract under a caller that has already done the check". The governing decision was in a source comment, not in any spec.md. TWO REQUIREMENTS WERE WRONG AND ARE REWRITTEN RATHER THAN QUIETLY FIXED FR-018b instructed correcting a comment that is accurate: the row render emits row.label and nothing else, so detail reaches Copy and the log and reaches the DOM by no path at all. FR-019 turned out to be already honoured in both panel types with zero tasks tracking it, so it gains FR-019a — a guard under FR-028, not an implementation. A requirement that is true with nothing asserting it is the exact shape all three restored requirements had immediately before they stopped being true. Both are recorded in a "Corrections from analysis" section. A wrong requirement that is silently fixed teaches nobody why it was wrong.
…278 #327 #328 #314) Four issues, one model. They are together because the first thing the plan discovered was that they are not independent: #327's fix removes the only de-duplication key #328 has. THE WIDENING, FIRST, BECAUSE EVERYTHING ELSE NEEDS IT 030 keyed a notice's list on panelId. FR-013 stops creating a panel for a refused open, so such a casualty has no panelId and "at most one row per casualty" becomes unstateable rather than merely unimplemented. A row is now a union: AffectedPanel keeps every field it had, AffectedSubject requires subject and reason and has panelId?: undefined. The union is the enforcement — a panel-less row cannot omit its identity and a panelled row is unchanged, which is what lets every existing caller and test stand untouched. casualtyKey is a FALLBACK, not a composite: the panel supersedes (subject, reason). Within one notice the cause is already fixed and a given panel fails once, so reason can never separate two rows sharing a panel — folding it in would undo 030 FR-037a. The same panel defeated by a different cause is a different NOTICE, decided by groupKey. groupAffected keeps its signature and returns the panelled rows; a sibling ungroupedAffected returns the rest. Changing the return would have broken four consumers and five destructuring sites in its own suite. #278 — SUPPRESSION DECIDED PER EVENT, SO ARRIVAL ORDER CANNOT MATTER Five vanished folders minted five different path-missing:<folder> keys, so cause-key equality matched none and 029 FR-019 never engaged. The keys were never wrong; four of those five removals were not causes. isSuppressedByAncestor asks, from the removal alone, whether an ancestor inside the project root is also gone — answerable without reference to any other event, which is why it needs no buffering. 030 FR-036 forbids grouping "by time or by window", and a wait would have been exactly that. #328 — A REPEAT IS LOUDER, NOT LONGER Two sites returned silently: mergeAffected returning the original array, and the duplicate check matching. The model DETECTED both repeats and threw the fact away, so a user re-triggering a condition got nothing back. Both now flash — pulse the card, restart the dismissal timer. The timer restart is what stops a notice expiring while the user is still producing the condition it reports. Repeats during a running pulse are absorbed, and the announcement is bound to the pulse (one per pulse), so ten rapid retries give one utterance rather than ten queued in a polite live region. #327 — A REFUSAL IS NOT A DOCUMENT createDedicatedEditor built the panel BEFORE anything read the file, so the first moment a refusal was knowable was already too late. openInto now answers first, as a third OpenDecision variant beside focus and open — no new IPC, because every open path already awaits that decision, and a caller that ignores refuse fails to COMPILE. A missing file returns open, never refuse: its panel is what holds the recovered buffer 018 shipped. The riskiest line in the feature is useReportPanelFailure's "if (!place) return". It stays for a panel destroyed mid-flight, and refusals report through a sibling hook instead — otherwise "no panel is created" becomes "no panel and no notification", which every panel-counting test would call success. #314 — A KEYBOARD ROUTE, AND A WAY BACK focus.notice, Ctrl+Alt+M, scoped EVERYWHERE because a notice can be raised while a terminal has focus and that is where it is most likely to appear. Idempotent and never walking the stack — Tab already traverses it. The Escape origin is captured at the press and not re-captured by Tab, so a user who tabs on to a second notice still lands where they started; when that element has been destroyed, focus falls back to a real surface, because document.body resolves to no keybinding scope and the next chord would silently do nothing. NOT_A_MISSING_FILE moved to @throng/core: main cannot import a renderer module, and the enumeration of what counts as a refusal is a pure decision with consumers in two processes. TWO PROJECTIONS THAT WOULD HAVE LOST DATA SILENTLY affectedDetails and affectedNames both flat-mapped groupAffected alone — so a panel-less casualty's absolute path would never have reached the diagnostics log, and a screen reader would have said "That happened again" with no subject. Both invisible by construction, since detail is never rendered. Both fixed, both now asserted. AND ONE THE E2E STAGE CAUGHT THAT NOTHING CHEAPER COULD resolveSaveConfinement falls back to "must be outside every project" when a project-owned document has no ownerRoot. openInto had no owner to derive one from, so ordinary in-project files came back out-of-tree and NORMAL OPENS CREATED NO PANEL. The renderer now sends ownerProjectId — main still derives the root itself from the daemon's list, so 018 US9's trust boundary holds — and an out-of-tree refusal is never invented when the owner is unknown. Confinement is still enforced at load time with the real per-panel ownership.
Constitution V's rule is the layer is chosen by what the assertion needs. Nearly all of this is unit and component; there is exactly ONE new E2E, for the single fact no cheaper layer can observe. WHAT jsdom CANNOT SEE, AND WHAT THAT CHANGED jsdom applies no stylesheet and performs no layout, so "the card visibly pulses", "the list did not move" and "a visible affordance" are not observable at the component layer — a test claiming them would pass vacuously. So the pulse is a STATE (data-pulsing) and the focus affordance is MARKUP (data-focusable), with the stylesheet deciding only appearance. The flash's "moves nothing" test asserts scrollTop, activeElement and row order, which jsdom does track. The announcement count is an attribute rather than a nonce in the sentence, because a live region only speaks when its content changes and two consecutive repeats of one subject produce the same string — appending a counter would have made a screen reader read the number out loud. THE ASSERTIONS THAT EARN THEIR PLACE - All 120 arrival orders of five removal events yield the same single cause, and the same subject every time. Permuted rather than slept through: a result needing a delay to be right IS the time-grouping the requirement forbids, so a sleeping test would pass for the wrong reason and keep passing through a regression. - SC-001's stated 1/3/5 measurement points, rather than a convenient number. - Utterances counted against pulses in BOTH directions — ten rapid repeats give one, three spaced repeats give three. A bound that only ever suppressed would pass against an implementation that had gone silent, which is the defect being fixed. - Zero panels at 0, 1 and 3 existing editors, each paired with the notification: "zero panels" alone is satisfied by doing nothing at all. - A MISSING file still creates its panel. The single highest-value assertion here — if it ever goes green by creating zero panels, 018's recovery path has been deleted with nothing near it failing. - The banner guard counts path OCCURRENCES rather than asserting presence, because presence passes just as happily against a banner that prints it three times. ONE E2E, IN THE FILE IT BELONGS TO That a real shell does not swallow Ctrl+Alt+M. A terminal forwards nearly everything to its process, so a binding in the wrong tier is eaten and the notice is unreachable exactly where FR-020a says it matters. Added to window-chord-resolution.e2e.ts beside the family it belongs to — tagged @reserve:input from that precedent, and @Extended because chord routing does not change per-commit. A new spec file would have needed a parallel-plan entry with a mechanism from a closed set whose UNATTRIBUTED count is asserted by equality at 14. e2e-budget.json re-seeded here: total 558→559, @window 191→192, core unchanged. window-chords.ts claims focus.notice, because its manifest guard reported it uncovered — "a regression in this listener is silent, so an uncovered action is a command that can die without a single test going red". Seven pre-existing test lines changed, all mechanical: six awaits and one async, because openInto became asynchronous. No assertion was altered — the widening is held to the rule that the tests proving 030's behaviour are not edited, and affected.test.ts is 113 insertions with zero deletions.
The one user-visible surface this feature adds is a default chord, and docs/quick-start.md tabulates that family by name — Ctrl+Alt+B/N, Ctrl+Alt+T, Ctrl+`, Ctrl+Alt+Arrow. A shortcut nobody can discover is #314 one layer out, so it joins them, with Escape's behaviour stated because returning focus is half of what makes the binding usable. Nothing else needed. The other three issues change how existing behaviour works rather than adding a surface the manual describes, and docs/testing.md references e2e-budget.json rather than restating its numbers — so the 558→559 bump needs no edit there. The two E2E measurements it does carry are stamped to spec 035 and remain accurate as provenance.
Generated by the agent-context extension, which rewrites the block between the SPECKIT
markers to name the current feature's plan. It moves from 040 to 041.
Its own commit rather than folded into docs(041), because an agent instruction file
changes how every future agent behaves in this repository and must be revertible on its
own — a rule reverted by accident, alongside a shortcut-table edit, is a rule nobody
notices going.
Worth recording for whoever runs the generator next: it is dead on a Windows checkout for
two reasons that look like one. `python3` resolves to the WindowsApps Store stub, and the
bash flavour takes `command -v python3` as proof of an interpreter with no version check
on that branch — so the probe succeeds and the invocation prints "Python was not found".
Past that, Python's text mode on Windows writes \r\n to stdout while the script reads the
config parser's three lines with `IFS= read -r`, which strips \n and keeps \r: the updater
then tries to open a file literally named "CLAUDE.md\r". Both were worked around outside
the repo. It also emits an ABSOLUTE path when it auto-detects the plan, because its
`${_plan_abs#$PROJECT_ROOT/}` strip fails against Python's backslashes — passing the plan
path explicitly avoids that branch entirely, which is how this block came out
repo-relative.
T047a (the FR-019 banner guard, 4/4 passing), T064 (no mutation harness or gate stage added — verified against the diff and package.json), T065 (docs/testing.md needed no edit; it references e2e-budget.json rather than restating its numbers) and T066 (affected.ts's header now describes casualties and records why the panel-named symbols keep their names). 74 of 81. The status block is the point of this commit rather than the four checkboxes. An unticked task looks the same whether it was forgotten, deferred, or deliberately not done — and these seven are open for three different reasons, only one of which is ordinary unfinished work. Three cannot be reproduced. #278 declares a second defect, a raw ENOENT rendered as the notice's second line, and the route by which it reaches the screen was not found: ENOENT classifies at cause.ts:102, so files-service returns the spoken sentence and the raw text rides in copyDetail, which nothing renders. Nothing was changed on a theory, because a fix behind no failing test cannot be verified by anything — including the green suite that would follow it. One is the developer's: the manual regression watch is not automatable and not the implementer's to tick. Three are genuinely outstanding, and T063 is the one with teeth — FR-030a's sensitivity proof, where each guard's fix is reverted once and that guard is observed to fail. A guard nobody has watched go red is an assertion that it would, which is exactly the assumption Group 5 exists to stop making. NOT regenerated. /speckit-tasks builds a task list from the template, which would have rewritten this file wholesale and taken 74 ticks, the per-task deviation notes, and the task IDs that five commit messages and PR #337 reference with it. The need was four checkboxes.
FR-029 and FR-030 exist to be applied to this feature's own tests at the end (T062). Applying them found three defects, none of which any green suite would have shown, and each needed a fix rather than a note. THE STORM GUARD TESTED A FUNCTION PRODUCTION NEVER CALLED. use-explorer-data.ts shipped with its own ancestor loop, so isSuppressedByAncestor -- the subject of SC-001's counts and SC-006f's 120-permutation sweep -- had no production caller at all. The rule had two statements and the tests exercised the one that never ran. The renderer now resolves absence over the shared walk and delegates the decision to core; explorer-storm-suppression.test.ts covers the half core cannot see, which is that the renderer asks. Measured: with the renderer's suppression deleted, ancestor-suppression.test.ts passes 18/18 while the storm rages. THE ONE E2E ASSERTED NOTHING. It focused an editor, pressed the chord against an empty notice stack, and asserted the editor still had focus -- true whether the binding resolves, is inert, or is deleted outright, and its own comment described a focused terminal it never created. Replaced by notice-focus-chord.e2e.ts: a real cmd, a real notice, and an assertion that focus MOVED. focus.notice moves to COVERED_ELSEWHERE, which the manifest guard verifies by reading the keystroke out of the code. The budget is unchanged -- one declaration, one @window tag, a different file. FOCUS.NOTICE COULD NOT REACH MOST NOTICES. FR-020 says "the most recent notice on screen", unqualified, but focusMostRecentNotice targeted the most recent notice CARRYING A CASUALTY LIST. A rename collision, a refused delete, a failed watcher -- each is a sentence and a Dismiss button, each is commoner than the consolidated kind, and on each the binding did nothing. Worse, with a plain notice newest it skipped backwards to an older one that had a list, which FR-020d forbids in as many words. Now targets the newest notice card and prefers its list within. FR-025's affordance rule is untouched: being reachable and advertising a tab stop are different claims. T063 -- each guard reverted once, run, and observed red; the pairings are in tasks.md and the PR. Nothing ships from that exercise (FR-030b). T062a labels the four already-true requirements (FR-008ca, FR-012a, FR-017a, FR-021a) so an unlabelled one stops reading as a missing implementation. Two traps recorded where they bite: e2e-budget.test.ts counts categories with a per-line regex, so a wrapped test( declaration is counted in the total but belongs to no category (measured: @window 191 against a budget of 192 while the total was right); and window-chord-manifest.test.ts wants the literal keystroke, not a named constant. npm run gate: GREEN, all 8 stages, 24m 25s. lint, typecheck, build, unit 3189, component 1020, integration 529, contract 138, e2e 205 parallel + 354 serial, zero retries.
Two things this session paid for, written where the next person hits them. REPRODUCING A FLAKE: separate invocations, never --repeat-each. The rule already says stress the one test until it fails on demand; it did not say that HOW you stress it decides whether the answer means anything. --repeat-each replays a test inside one worker against the state the previous repeat left, which the suite never does -- so for a spec about state that persists it manufactures failures. Measured: with --repeat-each=5, preferences-reset.e2e.ts:265 failed waiting for binding-reset-zoom.in to become enabled, and the button was disabled because the PREVIOUS repeat's reset-all was still in effect. A perfect-looking reproduction of a defect that was not there, and it aimed the investigation at the diff rather than at the harness. Re-run as separate invocations, that test passed 4 of 4 and the real flake surfaced somewhere else: theme-flash.e2e.ts:199. And run the same rounds on origin/master before attributing anything. Branch 1 failure in 4 rounds against master 2 in 6, failing DIFFERENT tests, is what turned "my branch broke the preferences specs" into issue #341. THE BUDGET COUNTS CATEGORIES PER LINE. e2e-budget.json gains a note: a test( declaration wrapped across several lines is counted in the total but its tags are invisible, so it belongs to no category. Measured: @window read 191 against a budget of 192 while the total was right, which is a confusing failure to reach from the other direction. Docs and one JSON comment only; no test reads either. e2e-budget.test.ts re-run: 5 passed.
Bidthedog
force-pushed
the
feature/S041-I278-I314-I327-I328-notice-consolidation
branch
from
August 27, 2026 16:01
b554891 to
eb8cbda
Compare
Bidthedog
marked this pull request as ready for review
August 27, 2026 16:02
Bidthedog
deleted the
feature/S041-I278-I314-I327-I328-notice-consolidation
branch
August 27, 2026 16:11
Bidthedog
added a commit
that referenced
this pull request
Aug 27, 2026
T047a (the FR-019 banner guard, 4/4 passing), T064 (no mutation harness or gate stage added — verified against the diff and package.json), T065 (docs/testing.md needed no edit; it references e2e-budget.json rather than restating its numbers) and T066 (affected.ts's header now describes casualties and records why the panel-named symbols keep their names). 74 of 81. The status block is the point of this commit rather than the four checkboxes. An unticked task looks the same whether it was forgotten, deferred, or deliberately not done — and these seven are open for three different reasons, only one of which is ordinary unfinished work. Three cannot be reproduced. #278 declares a second defect, a raw ENOENT rendered as the notice's second line, and the route by which it reaches the screen was not found: ENOENT classifies at cause.ts:102, so files-service returns the spoken sentence and the raw text rides in copyDetail, which nothing renders. Nothing was changed on a theory, because a fix behind no failing test cannot be verified by anything — including the green suite that would follow it. One is the developer's: the manual regression watch is not automatable and not the implementer's to tick. Three are genuinely outstanding, and T063 is the one with teeth — FR-030a's sensitivity proof, where each guard's fix is reverted once and that guard is observed to fail. A guard nobody has watched go red is an assertion that it would, which is exactly the assumption Group 5 exists to stop making. NOT regenerated. /speckit-tasks builds a task list from the template, which would have rewritten this file wholesale and taken 74 ticks, the per-task deviation notes, and the task IDs that five commit messages and PR #337 reference with it. The need was four checkboxes.
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.
One condition raises one notice, carrying one row per distinct casualty, and a repeat makes that notice louder rather than adding another.
Three of the four issues are conformance, not invention: 029 FR-016/018/019 and 030 FR-034/037a were established, shipped, and had since stopped holding. This restores them without renumbering them, and adds guards so the next silent retirement fails something. The genuinely new ground is #327 (what happens when a file is refused and no editor panel exists) and #314 (what the keyboard route to a notice is).
What a user can now do
Why the four are one branch
They are not independent.
030keyed a notice's list onpanelId; #327's fix stops creating a panel for a refused open, which removes the only de-duplication key #328 has. Fixing either alone leaves the other unstateable — so the branch widens the row into a casualty (panel optional, keyed on the panel where there is one, else(subject, reason)) and builds the rest on that.Worth a reviewer's attention
useReportPanelFailureopens withif (!place) return. That guard is correct and stays — a panel destroyed mid-flight must not get an invented row (030 FR-027). But a refusal now has no panel by construction, so it reports through a sibling hook. Left alone, "no panel is created" would have become "no panel and no notification", which every panel-counting test would call success.affectedDetailsandaffectedNamesboth flat-mappedgroupAffectedalone, so a panel-less casualty's absolute path would never have reached the diagnostics log and a screen reader would have said "That happened again" with no subject. Both invisible by construction, sincedetailis never rendered.resolveSaveConfinementfalls back to "must be outside every project" when a project-owned document has noownerRoot;openIntohad no owner to derive one from, so ordinary in-project files came backout-of-treeand normal opens created no panel. Fixed twice over: the renderer now sendsownerProjectId(main still derives the root itself, so 018 US9's trust boundary holds), and anout-of-treerefusal is never invented when the owner is unknown.openFileInNewEditor, andeditor-ipc.ts's rule that the renderer must never supply its own confinement roots. Both drafts of mine did exactly what those comments forbid.The guard review, and what it found (T062, T063)
FR-029 and FR-030 exist to be applied to this feature's own tests at the end. Doing it found three
defects, none of which any green suite would have shown.
1. The storm guard tested a function production never called.
use-explorer-data.tsshipped withits own ancestor loop, so
isSuppressedByAncestor— the subject of SC-001's counts and SC-006f's120-permutation sweep — had no production caller at all. The rule had two statements and the tests
exercised the one that never ran. The renderer now resolves absence over the shared walk and delegates
the decision to core, and
explorer-storm-suppression.test.tscovers the half core cannot see: thatthe renderer asks.
2. The one E2E asserted nothing. It focused an editor, pressed the chord against an empty
notice stack, and asserted the editor still had focus — true whether the binding resolves, is inert,
or is deleted outright, and its own comment described a focused terminal it never created. Replaced by
notice-focus-chord.e2e.ts: a realcmd, a real notice, and an assertion that focus moved. Thebudget is unchanged — one declaration, one
@windowtag, a different file — andfocus.noticemovedto
COVERED_ELSEWHERE, which the manifest guard verifies by reading the keystroke out of the code.3.
focus.noticecould not reach most notices. FR-020 says "the most recent notice on screen",unqualified, but the implementation targeted the most recent notice carrying a casualty list. A
rename collision, a refused delete, a failed watcher — each is a sentence and a Dismiss button, each is
commoner than the consolidated kind, and on each the binding did nothing. Worse, with a plain notice
newest it skipped backwards to an older one that had a list, which FR-020d forbids in as many words.
Now targets the newest notice card and prefers its list within.
T063 — each guard, reverted once, and what went red
Measured 2026-08-27. Nothing here ships (FR-030b): every revert was applied, run, and undone.
isSuppressedByAncestorexplorer-storm-suppression.test.ts: 2 notices where 1 is requireddetail.systemErrorpanel-failure-banner-path.test.ts: 2 failures —not to contain 'ENOENT', and the path-once guard too (the errno carries the path)joinedPanelskeys onpanelIdalone againaffected.test.ts: 4 failures, allexpected … length of 2 but got 1Control+Alt+Shift+F9The row that matters most is the one that stayed green. Under the first revert,
ancestor-suppression.test.tspassed 18/18 — SC-001 and the 120-permutation sweep included — whilethe storm raged. A guard can be immaculate and still be aimed at code that does not run, and that is
the argument for finding 1 in a single measurement rather than in a paragraph.
Verification
On the pushed tree (
eb8cbdad, rebased ontoe6460d10), the seven cheap gate stages:lint✓ ·typecheck✓ ·build✓ ·unit3203 ✓ ·component1023 ✓ ·integration533 ✓ ·
contract138 ✓A full 8-stage
npm run gatewent green on this branch's content earlier — 24m 25s, e2e 205parallel + 354 serial, zero retries — but that was before master gained the #290 terminal work, so
it does not describe the tree being merged. E2E has not been re-run locally since that rebase: a
sync run does not start one, and CI's
@corelane gates the push. Said plainly rather than rounded up.The rebase had two real conflicts, and one was arithmetic
e2e-budget.jsonis a ratchet that fails in both directions, and both sides of the rebase hadwritten
total: 559— master because #290 added a@terminaltest, this branch because 041 added a@windowone. Each was right alone; together the answer is 560, with@terminal103 and@window192. Resolved to that and proven bye2e-budget.test.tspassing in the run above. Aconflict where both sides wrote the same number and both were wrong is exactly what this ratchet is
for.
docs/testing.mdwas additive on both sides — master's third race-class sibling and this branch'sflake-reproduction section — and both are kept.
An earlier run's flakes, and who they belong to
An intermediate gate run came back 2 flaky in the serial tier:
preferences-reset.e2e.ts:217andtheme-flash.e2e.ts:199, both@prefs @reserve:window. Neither is in code this branch touches, but"it isn't mine" needs evidence, so both trees were sampled the way the suite actually runs —
separate invocations, fresh app and config root each, one worker,
--retries=0, idle machine:origin/masterpreferences-reset.e2e.ts:486, twicetheme-flash.e2e.ts:199Different tests fail on each side, which rules out a defect in either tree and points at the
family being timing-sensitive. Filed as #341.
One method note, because it nearly produced the wrong conclusion:
--repeat-eachis the wronginstrument. It replays a test in one worker against the previous repeat's state, and
preferences-reset.e2e.tsis a file about config writes landing — so it produced a convincingfailure (
binding-reset-zoom.indisabled, "already at its default value") that was the harnessrather than the code, and aimed the investigation at this diff. Recorded in
docs/testing.md.Known gap
#278 declares two defects. The storm (one notice per expanded child) is fixed, tested, and its
guard now points at the code that actually runs — see the guard review above. The raw
ENOENTrendered on the notice could not be reproduced:
ENOENTclassifies atcause.ts:102, sofiles-service.tsreturns the spoken sentence and the raw text rides incopyDetail, which no codepath renders. Nothing was changed on a theory. Closing #278 on the storm; reopen if the raw errno is
seen again.
80 of 84 tasks. The four open are three that cannot be reproduced (the above) and one that is the
developer's to perform — the manual regression watch in
quickstart.md§7.tasks.mdrecords whichis which and why.
Closes
Closes #278
Closes #327
Closes #328
Closes #314
Related
Related to #321 — serial-tier E2E flakes; measured evidence posted there, not fixed here