Skip to content

chore(deps-dev): bump eslint from 9.39.4 to 10.6.0 - #95

Closed
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/eslint-10.6.0
Closed

chore(deps-dev): bump eslint from 9.39.4 to 10.6.0#95
dependabot[bot] wants to merge 1 commit into
mainfrom
dependabot/npm_and_yarn/eslint-10.6.0

Conversation

@dependabot

@dependabot dependabot Bot commented on behalf of github Jun 29, 2026

Copy link
Copy Markdown
Contributor

Bumps eslint from 9.39.4 to 10.6.0.

Release notes

Sourced from eslint's releases.

v10.6.0

Features

  • b1f9106 feat: detect Symbol() and BigInt() in no-constant-binary-expression (#20981) (Taejin Kim)
  • f291007 feat: add checkRelationalComparisons to no-constant-binary-expression (#20948) (sethamus)

Bug Fixes

  • 6b05784 fix: prefer-exponentiation-operator invalid autofix at statement start (#20997) (Milos Djermanovic)
  • bb9eb2a fix: account for shadowed Boolean in no-extra-boolean-cast (#21013) (den$)
  • 8fd8741 fix: don't report shadowed undefined in radix rule (#21011) (Pixel)
  • 5784980 fix: don't report shadowed undefined in no-throw-literal (#21010) (Pixel)
  • 9cd1e6d fix: suppress invalid class suggestion in no-promise-executor-return (#21008) (Pixel)
  • d4eb2dc fix: don't report shadowed undefined in prefer-promise-reject-errors (#21006) (Pixel)
  • 2360464 fix: prefer-promise-reject-errors false positives for shadowed Promise (#21003) (den$)
  • 63d52d2 fix: restore max-classes-per-file report range (#21002) (Pixel)
  • 7feaff0 fix: callback detection logic for IIFEs in max-nested-callbacks (#20979) (fnx)
  • 399a2ec fix: don't report inner non-callbacks in max-nested-callbacks (#20995) (Milos Djermanovic)

Documentation

  • a83683d docs: Update README (GitHub Actions Bot)
  • f5449f9 docs: document userland patterns for global assertionOptions in RuleT… (#20986) (playgirl)
  • bea49f7 docs: Update README (GitHub Actions Bot)
  • e5f70f9 docs: update code-path diagrams (#20984) (Tanuj Kanti)
  • 8890c2d docs: add TypeScript config guidance for MCP server (#20796) (Pierluigi Lenoci)
  • 3eb3d9b docs: Update README (GitHub Actions Bot)
  • c5bb59c docs: Update README (GitHub Actions Bot)
  • eb3c97c docs: fix grammar in prefer-const rule description (#20983) (lumir)

Chores

  • 6a42034 ci: run ecosystem tests on main branch (#20891) (sethamus)
  • 3dbacdb ci: bump actions/checkout from 6 to 7 (#21014) (dependabot[bot])
  • c3abfca chore: correct JSDoc param types in html formatter (#21018) (Minseon Kim)
  • a832320 ci: split ecosystem tests into separate jobs (#21001) (xbinaryx)
  • 27166e7 chore: update ecosystem plugins (#21005) (ESLint Bot)
  • 865d76e ci: bump pnpm/action-setup from 6.0.8 to 6.0.9 (#20989) (dependabot[bot])
  • 27a88c9 chore: update dependency markdown-it to v14 in root (#20994) (Milos Djermanovic)
  • 970cea6 chore: update dependency markdown-it to v14 (#20993) (Milos Djermanovic)
  • b482120 chore: update dependency prettier to v3.8.4 (#20990) (renovate[bot])
  • 6993fb3 chore: update ecosystem plugins (#20985) (ESLint Bot)

v10.5.0

Features

  • 5ca8c52 feat: correct stack tracking in max-nested-callbacks (#20973) (Pixel998)
  • b565783 feat: report no-with violations at the with keyword (#20971) (Pixel998)
  • 2ce032f feat: report max-lines-per-function violations at function head (#20966) (Pixel998)
  • 732cb3e feat: report max-nested-callbacks violations at function head (#20967) (Pixel998)
  • f9c138a feat: report max-depth violations on keywords (#20943) (Pixel998)
  • bdb496c feat: correct max-depth handling for else-if chains (#20944) (Pixel998)
  • c296873 feat: update error loc in max-statements to function header (#20907) (Taejin Kim)

Documentation

... (truncated)

Commits
  • 5d12a04 10.6.0
  • f7ca54b Build: changelog update for 10.6.0
  • 6a42034 ci: run ecosystem tests on main branch (#20891)
  • b1f9106 feat: detect Symbol() and BigInt() in no-constant-binary-expression (#20981)
  • 3dbacdb ci: bump actions/checkout from 6 to 7 (#21014)
  • c3abfca chore: correct JSDoc param types in html formatter (#21018)
  • a83683d docs: Update README
  • a832320 ci: split ecosystem tests into separate jobs (#21001)
  • 6b05784 fix: prefer-exponentiation-operator invalid autofix at statement start (#20997)
  • bb9eb2a fix: account for shadowed Boolean in no-extra-boolean-cast (#21013)
  • Additional commits viewable in compare view

@dependabot dependabot Bot added dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code labels Jun 29, 2026
@BigSimmo

Copy link
Copy Markdown
Owner

Deferred: ESLint 10 is a major tooling upgrade and current CI is failing on this PR. This should be handled as a dedicated lint/tooling migration with config review and focused fixes rather than merged with routine maintenance.

Bumps [eslint](https://github.com/eslint/eslint) from 9.39.4 to 10.6.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v9.39.4...v10.6.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.6.0
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
@dependabot
dependabot Bot force-pushed the dependabot/npm_and_yarn/eslint-10.6.0 branch from 63659f0 to ac58ae2 Compare June 29, 2026 02:47
@BigSimmo BigSimmo closed this Jun 29, 2026
@dependabot @github

dependabot Bot commented on behalf of github Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

OK, I won't notify you again about this release, but will get in touch when a new version is available. If you'd rather skip all updates until the next major or minor version, let me know by commenting @dependabot ignore this major version or @dependabot ignore this minor version. You can also ignore all major, minor, or patch releases for a dependency by adding an ignore condition with the desired update_types to your config file.

If you change your mind, just re-open this PR and I'll resolve any conflicts on it.

@dependabot
dependabot Bot deleted the dependabot/npm_and_yarn/eslint-10.6.0 branch June 29, 2026 04:01
BigSimmo pushed a commit that referenced this pull request Jul 28, 2026
Records the five follow-ups that surfaced during PR #1316 and would
otherwise be lost with this session's context: the band's inability to
express a partial-source failure (and the favourites mask that papers
over it), the refetch pulse deferred on auth-backed registries by the
identity-clearing invariant, the Next streaming-clone strict-mode flake,
the design-system gates that assert structure rather than rendered
effect, and `PR required` reporting concurrency cancellations as
failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Akwz3Sdms8uJ5AkDt3CduY
BigSimmo added a commit that referenced this pull request Jul 30, 2026
The visual-baseline job reports failure on every UI-touching PR and will
keep doing so until someone adopts baselines from a CI artifact. That is
working as designed, and the design is the problem: "no baseline" and "a
real visual regression" are the same red, so the day a regression appears
it looks exactly like the runs before it. A check that is always red is a
check nobody reads. Ledger #95 is the same failure mode reached from the
other direction.

The two states are now distinct:

- no baseline AND declared in AWAITING_BASELINE -> SKIPPED, with a
  candidate PNG written and attached so a reviewer can adopt it
- no baseline and NOT declared -> still FAILS; that is a deleted,
  renamed or mis-pathed golden and must stay loud
- baseline present -> compared exactly as before

Candidates go to test-results/visual-candidates/<platform>/, deliberately
not into __screenshots__. Writing into the snapshot directory would let a
later attempt compare against a golden this same run produced and report
green — the self-adoption hole `retries: 0` exists to close. The baseline
directory stays something only a human writes to.

Two tests keep the declaration honest, so the list cannot rot into a
permanent exemption: one fails if a declared target already has a
committed baseline (stale entry silently skipping a real comparison), one
fails on a name that matches no target (a typo exempting nothing while
appearing to exempt something).

Adopting a baseline is now one commit: add the PNG, delete the name.

Decision table proven by execution against the real snapshotPathTemplate
rather than by inspection — `testInfo.snapshotPath()` resolves to
tests/__screenshots__/linux/<name>.png, matching the CI failure paths:

  declared + missing      -> 1 skipped
  undeclared + missing    -> 1 failed
  declared + present      -> comparison runs, stale-declaration test failed

typecheck, eslint and prettier clean.


Claude-Session: https://claude.ai/code/session_01FvU8z73P6TXUXoYBqN5K1P

Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo added a commit that referenced this pull request Jul 30, 2026
* fix(ci): distinguish a cancelled PR-required run from a real failure

`cancel-in-progress` supersedes an in-flight run on every push, and the
aggregate's require_* helpers lumped the resulting `cancelled` in with `failure`.
The red was therefore indistinguishable from a genuine break at a glance. Measured
cost: one 2026-07-30 session spent four separate investigations on
`::error::changes result was cancelled` / `static-pr result was cancelled`, and
PR #1401 merged straight through an unrelated red the repo had learned to ignore
— which is the worse half, because a signal that is red for reasons unrelated to
the diff trains everyone to click through the one time it is real.

The aggregate now wires RUN_CANCELLED: ${{ cancelled() }}, reports a
workflow-level cancellation once through a shared cancelled_error helper, and
labels a cancelled job result as cancelled rather than as a plain failure. The
message is actionable rather than merely accurate: it names the supersession,
points at the newest run for the current head, and tells a reader who finds no
newer run that the run was hand-cancelled, verified nothing, and must be re-run
rather than merged past.

The obvious fix was rejected as unsafe. Treating cancelled as neutral, or
skipping the aggregate with `if: !cancelled()`, makes the red disappear — but
GitHub counts a SKIPPED required check as PASSING, so a hand-cancelled run on the
current head would become mergeable with nothing verified. That is also what
#95's own stop rule forbids. So the result stays RED; only the diagnosis cost is
removed, and `if: always()` is now pinned by a test with the reason recorded.

Seven cases in tests/ci-cache-safety.test.ts EXECUTE the extracted aggregate
script under synthetic job results rather than grepping the YAML, because the
defect was behavioural and a structural assertion passed against it (#94). They
cover the green baseline, workflow-level cancellation, a single cancelled job, a
genuine failure keeping its plain message with no cancellation excuse, and the
invariant that no cancelled required job can ever exit 0. Mutation-proven: three
fail against the pre-fix aggregate, and one asserts the extraction is non-empty
so a YAML restructure cannot make the group vacuously green.

Verified: verify:cheap — Test Files 432 passed (432), Tests 4477 passed | 4
skipped (4481); check:github-actions, check:ci-scope, check:ci-triage, prettier
all pass. Ledger #95 records the fix and the rejected approach.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b

* fix(ci): drop the invalid cancelled() env expression that broke ci.yml

My previous commit wired the workflow-level cancelled status function through an
`env:` value. That is invalid: GitHub allows the status-check functions
(success/failure/cancelled/always) only in `if:` conditions. The whole workflow
file therefore failed to parse — the run was named `.github/workflows/ci.yml`
instead of `CI`, created ZERO jobs, and reported a bare failure with no logs to
read.

Nothing local caught it, and that is the part worth fixing. It is valid YAML and
an invalid Actions schema, so prettier, lint, typecheck, check:github-actions and
all 432 unit files passed the broken version. It was visible only on hosted CI —
exactly the gap I flagged as unverifiable when the first commit went up.

The env expression was never needed. Reading each job's own `cancelled` result
does the same work, because a supersession cancels the upstream jobs, so the
first require_* call reports it. So the fix is a deletion, not a workaround.

A new case in tests/ci-cache-safety.test.ts scans every file under
.github/workflows/** and fails on a status-check function outside an `if:`. It
caught a second instance immediately: the comment I had written to warn about the
first mistake contained the offending expression, and `${{ }}` is interpolated
inside `run:` blocks too, so that comment alone would have kept the file
unparseable. The comment now describes the trap without spelling it.

Verified: verify:cheap — Test Files 432 passed (432), Tests 4478 passed | 4
skipped (4482); check:github-actions passes; the guard is mutation-proven by
reintroducing the env expression and watching it fail.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012YRCXgX4AWZ579bKN6sk6b

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo added a commit that referenced this pull request Jul 30, 2026
…ine (#1413)

* ci: hygiene gates for matrix, scope, cancel, gitleaks, RAG offline

Unblock the weekly release-browser-matrix from pr-required so a
blocking scheduled dependency audit cannot skip Firefox/WebKit.
Narrow ui_changed away from src/app/api and db_changed away from
API routes; fail-fast @critical UI on PRs before the full suite;
treat aggregate cancelled as neutral; pin Gitleaks to event SHAs;
wire eval:rag:offline when rag_eval_changed.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs(ledger): record ci-hygiene-gates implementation review

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* style: format outstanding-issues after CI hygiene ledger edits

Static PR failed format:check on docs/outstanding-issues.md after the
#95/#97 resolution rows were rewritten.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* fix(ci): avoid cancel-to-green on PR required (#95)

Use always() && !cancelled() so superseded concurrency cancels leave the
aggregate cancelled/skipped instead of false-red, without treating cancelled
needs as success when the tip never produced proof. Format the merged
outstanding-issues archive for Prettier.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs: record PR #1413 merge-readiness ledger row

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* docs: supersede PR #1413 merge-readiness ledger at tip

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* ci: checksum-pin Gitleaks tarball and scope the pin script

Verify the linux_x64 release archive against the published SHA-256 before
install, keep the pin in the gitleaks self-test, and treat
scripts/run-gitleaks-pinned.mjs as a workflow-scope change.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

* ci: retrigger checks after gitleaks checksum pin

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
main's #1418 (ledger merge dedupe + L4 quarterly archive rotation) and #1413
both edited docs/outstanding-issues.md, so this was a real content conflict
rather than staleness: git merge-tree --write-tree confirmed CONFLICT before
any resolution was attempted.

Resolved by taking main's version of the ledger wholesale and re-applying this
branch's five-row archive move on top, so neither side's work is lost:

- from main: #88 and #97 archived, new open row #126 (quarterly ledger
  rotation) with queue order 35, the #23 "When" update (release-browser-matrix
  no longer blocked by pr-required), the #86 detail update, and the
  issues:next-id bump to 127.
- from this branch: #95, #96, #104, #109 and #115 moved from Open items to
  Resolved / archive.

No row from either side was dropped, and no id appears in both tables.

Verified: 121 rows (52 open, 69 archived), marker next-id=127 above the highest;
each of #88, #97, #95, #96, #104, #109, #115 resolves to exactly one archive
row and #126 to one open row; zero conflict markers remain.

npm run verify:cheap -> EXIT=0; "Gate-manifest OK: all 29 verify:cheap gates are
enforced in CI"; "Test Files 431 passed (431)"; "Tests 4496 passed | 4 skipped
(4500)". npx prettier --check . -> "All matched files use Prettier code style!"

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YdPS2KhKqz2buzsUgmX3c
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Two things in one commit because the merge and the fix are on the same file
and the PR's CI cancels in-progress runs on every push.

Merge: main advanced 28 commits to b1f5718 and had again edited
docs/outstanding-issues.md, so git merge-tree confirmed a real conflict rather
than staleness. Resolved as before by taking main's ledger wholesale and
re-applying this branch's five-row archive move; all five were verified still
open on main first, so the move is still the correct change. 125 rows
(56 open, 69 archived), marker next-id=128.

Correction (Codex P2 on PR #1428, docs/outstanding-issues.md:224): the #95
archive record described the fix inaccurately, and the claim was verified
against .github/workflows/ci.yml before being accepted:

- It named "a shared cancelled_error helper". No such helper exists at any
  commit on this branch - grep finds nothing at be27fbb or at HEAD. The real
  implementation is a single record() collector (ci.yml:739) that reads each
  job's own result into failures/cancellations arrays, with require_success and
  require_skipped_or_success as thin wrappers passing skipped_ok false/true.
- It claimed the error "names the newest run for the head". It does not; it
  points the reader at a newer PR required run on the current head SHA and says
  that if there is none the run was hand-cancelled and must be re-run.

Both were carried over from an obsolete sentence in the open row, which
described an earlier revision; the row's later text described the shipped
two-pass behaviour, and condensing it for the archive kept the stale half and
dropped the accurate one. An archive record that misdescribes its own fix
defeats the purpose of archiving it.

The rewritten record also fixes an inherited count: the row said "seven cases"
in tests/ci-cache-safety.test.ts, but ten of that file's thirteen tests execute
the extracted aggregate (lines 95-200); the first three cover caching and
Playwright deps. It additionally records the failures-win refinement from
PR #1409, which the original condensation omitted entirely.

Verified: npm run verify:cheap -> EXIT=0; "Test Files 434 passed (434)";
"Tests 4562 passed | 4 skipped (4566)"; "Outstanding-issues guard passed: 125
rows (56 open, 69 archived), unique ids, next-id=128 above the highest".
npx prettier --check . -> "All matched files use Prettier code style!"

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YdPS2KhKqz2buzsUgmX3c
BigSimmo pushed a commit that referenced this pull request Jul 30, 2026
Third main sync on this branch. The new `PR mergeability` check on main caught
the drift, which is #116's fix working as designed.

The replay script now carries the review-corrected #95 text. Re-running it
against a fresh main would otherwise have reintroduced the exact `cancelled_error`
description Codex flagged, since the script held the pre-review wording.

main's .gitattributes now sets `merge=union` for this file and SKILL.md adds:
"Never resolve a conflict by taking one side wholesale — that drops the other
agent's rows." Proven not to happen here, by ID-set comparison against
origin/main rather than by assertion:

  origin/main : 60 open, 65 archived, 125 total
  resolved    : 55 open, 70 archived, 125 total
  IDs lost from main: NONE
  IDs added: NONE
  main's open rows preserved except the 5 moved: True
  main's archived rows all still archived: True

So the result is a true union: main's 60 open minus the five archived equals 55,
and main's 65 archived plus those five equals 70.

Verified: npm run verify:cheap -> EXIT=0; "Test Files 434 passed (434)";
"Tests 4563 passed | 4 skipped (4567)"; "Outstanding-issues guard passed: 125
rows (55 open, 70 archived), unique ids, next-id=128 above the highest, union
merge active". npx prettier --check . -> "All matched files use Prettier code
style!". Zero conflict markers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YdPS2KhKqz2buzsUgmX3c
BigSimmo added a commit that referenced this pull request Jul 30, 2026
* issues: archive five completed rows, delete none

Triage pass over all 58 open items found five rows whose work is finished and
whose next action is empty. Each moves from Open items to Resolved / archive
with its fix evidence and the 2026-07-30 date:

- #95 the pr-required aggregate now routes a cancelled result through a shared
  cancelled_error helper; guarded by seven cases that execute the extracted
  script, three mutation-proven. The red is deliberately retained, since GitHub
  counts a skipped required check as passing.
- #96 every PR #1316 sub-item is dispositioned: the adoption-gate root-path gap
  closed on PR #1394, four findings were fixed independently, and the Therapy
  Compass retry-waiter finding was corrected to not-a-live-defect.
- #104 a correction row with no next action - the worker's triple image read is
  an accepted peak-memory trade-off documented at worker/main.ts:866-869, not
  debt. Archived so a fourth audit does not re-file it.
- #109 the branch sweep refuses on a shallow clone, an indeterminate result is
  its own failure, and the guard moved into the exported collector so the
  evidence-pack path fails closed too.
- #115 the band adoption gate was redesigned to walk a real reachability graph
  rather than asking whether a file mentions the band.

Nothing is deleted. The ledger contract is archive-only (SKILL.md:44 "Archive,
never delete"; this file's own conventions: "rows are archived, not deleted, so
the history stays auditable"), so no row qualifies for deletion. Row total is
unchanged at 120: 58 -> 53 open, 62 -> 67 archived.

Prettier widened the archive Outcome column to fit the new evidence, which
repads the other archive rows; git diff --ignore-all-space is 7 insertions /
7 deletions, i.e. the five moved rows plus both separators.

Verified: node scripts/check-outstanding-issues.mjs --self-test && node
scripts/check-outstanding-issues.mjs -> "Outstanding-issues guard passed: 120
rows (53 open, 67 archived), unique ids, next-id=126 above the highest".
npx prettier --check . -> "All matched files use Prettier code style!"

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011YdPS2KhKqz2buzsUgmX3c

* docs: record PR 1428 review

* docs: align issue 109 resolution date

---------

Co-authored-by: Claude <noreply@anthropic.com>
BigSimmo added a commit that referenced this pull request Jul 30, 2026
…t did (#1427)

* test(phone-scroll): prove the drag delivered before asserting the chrome hid

CI run 30518866604 failed `ui-phone-scroll.spec.ts:423` on

  expect(getByTestId('universal-header-collapse'))
    .toHaveAttribute('data-scroll-hidden', 'true')  // received ""

after the full 10s auto-retry, and the classifier recorded it as "needs
investigation". The assertion was right; the scroll never happened.

`dragScrollBy` moved the scroller with `scrollTop +=`, which clamps silently
at the end of the range, and returned nothing. When a page lays out shorter
than the test assumed — content still settling under full-suite CI load — a
720px request delivers a fraction of that, the chrome correctly stays visible
because document-detail chrome only hides past `scrollTop > 120`, and the
failure surfaces ten seconds later looking like a product regression. The
helper also resolved the scroll owner once up front, so a mid-drag layout
change left it pushing an element that had stopped scrolling.

- `dragScrollBy` now re-resolves the owner each step and returns the distance
  actually travelled.
- `dragScrollUntilHidden` waits for the remaining downward runway (a condition
  wait, not a settle sleep), drags, and fails naming the shortfall if the drag
  could not cross the threshold. Used at the four sites that assert a hide
  immediately after a fixed-distance drag.
- `addPhoneScrollRunway` waits for its 1600px filler to reach layout instead of
  sleeping 50ms. All 14 call sites already depend on that runway existing.

Every assertion is byte-identical: a genuinely stuck header still fails exactly
as before, once the drag is proven to have happened. No `.first()` was added
(#93's stop rule) and no tolerance was relaxed.

* ci: shard Production UI across three runners

Measured on 2026-07-30 from the Actions API, two full UI-scope PR runs
(30520443076, 30519912667): `Production UI` took 15m26-16m31 of a 16.8-18.6
minute run — 83-89% of wall clock — while every other job finished by minute 4
and then waited. Playwright itself reported `339 passed (13.5m)`; the balance is
the isolated production build.

That single job is also where the churn cost lands: 42% of PR runs in the
sampled window were cancelled (25 of 60 completed), almost all superseded
mid-Production-UI.

Sharding is across runners, not workers. `workers: 1`, `fullyParallel: false`
and `retries: 0` are unchanged inside each shard, so determinism is identical
and per-runner load falls — which matters because #93's duplicate page root is
load-dependent. `run-playwright.mjs` already forwards argv to `playwright test`,
so `--shard` needed no runner change.

The shard count is measured, not chosen. `fullyParallel: false` makes a spec
file indivisible, so shard sizes are lumpy and more shards is not monotonically
faster. Over the 340 required chromium tests:

  N=3 -> 121/106/113       largest 121
  N=4 -> 121/106/96/17     largest 121  (same critical path, one more runner)
  N=6 -> 65/56/106/5/91/17 largest 106
  N=5 -> 121/106/0/96/17   and N=8 -> two empty shards

N=4 buys nothing over N=3, and any N with an empty shard would go red because
`test:e2e:pr` deliberately omits `--pass-with-no-tests`. Expected critical path
~15.5 -> ~7 min, assuming per-test cost is roughly uniform.

`fail-fast: false` so a failing shard cannot cancel its siblings and re-create
the cancelled-vs-failed ambiguity #95 removed. Artifact names are shard-scoped
because upload-artifact runs with `overwrite: false`. Branch protection requires
only the `pr-required` aggregate, and `needs` on a matrix job yields the roll-up
of all shards, so the aggregate is unchanged.

Also adds `restore-keys` to both Playwright browser caches: without a prefix
fallback a lockfile bump forced a cold browser download in every UI job at once,
now three times over.

* ci: bound the codex auto-resolve jobs and serialise the visual config

Two inconsistencies found while mapping the pipeline, neither load-bearing but
both silent:

- `codex-autofix-review-comments.yml` was the only workflow in the repo with no
  `timeout-minutes` on either job, so both inherited GitHub's 360-minute default
  for work that reads PR metadata and posts one comment.
- `playwright.visual.config.ts` set neither `workers` nor `fullyParallel`, so it
  inherited Playwright's default `workers = 50% of CPUs`. The production config
  pins both to serial deliberately; the visual lane was quietly opting out of
  the anti-flake posture the rest of the suite is configured for.

* chore(gates): pin the documented gate count to the real chain

Both numbers were wrong. `CLAUDE.md` said 24 static/consistency gates against an
actual 25 — `check:assets` landed before that line was written, so it was wrong
at authoring — and the `gates` skill said "check 2 of 26" against an actual 28.

A stale count is not cosmetic here. The skill's whole point at that line is that
`verify:cheap` stops at the first failure and everything after it never ran; an
agent that believes the chain is 26 long cannot say how much a mid-chain failure
skipped.

`check:gate-manifest` already derives the real count from
`verify:cheap:internal`, so it now asserts the documented numbers against it.
The assertions fail closed: if the anchor phrasing disappears, the guard reports
a lost anchor rather than passing on a document it no longer checks.

Mutation-proven: reverting the skill to "26" fails with
".claude/skills/gates/SKILL.md says 26 where the chain has 28".

* docs(issues): capture the CI review's deferred findings

Five items from the CI/testing review that should not be changed blind:

- #125 `ui_changed` matches all of `src/app`, so an API-only diff pays the
  15-minute UI gate. Narrowing it can hide a real regression, so it needs a
  decision plus a compensating check rather than a quieter filter.
- #126 the Playwright build writes to a per-run distDir, so Next's build cache
  is cold every run (~2 min, now ~29% of the sharded critical path). Fixing it
  means suppressing the runner's documented always-cleanup, which must not ship
  without executing the runner.
- #127 the advisory UI lane spends ~3 min per UI PR on 5 mockup tests; there are
  currently zero `@quarantine` tests for it to cover.
- #128 CI Triage is complete and self-tested but inert pending a repo variable.
- #129 four `changes` outputs are computed and consumed by nothing, and
  `coverage_changed` fires on any non-doc file.

* docs(ledger): record the ci-testing-review pass at this HEAD

* ci: re-measure the shard split on the merged tree and refresh stale gate counts

The merge changed both numbers this branch had recorded.

Shard balance, re-measured against 342 required chromium tests (was 340):
  N=3 -> 121/111/110    largest 121
  N=4 -> 121/106/98/17  largest 121
N=3 remains correct — one 121-test spec group bounds both, so N=4 spends an
extra runner for the same critical path. The re-measure command is now in the
workflow comment so the next person does not have to rediscover it.

Gate counts: merging main added `check:gitleaks-pinned` and
`check:pr-mergeability` to `verify:cheap:internal`, so the documented counts
went stale the moment the merge landed — 25 -> 27 static, 28 -> 30 total. The
guard added earlier in this branch caught it immediately rather than letting the
docs drift again, which is the whole reason it exists.

Also records the `ui-critical-fast` interaction: the UI critical path is now that
15-test fail-fast job plus the slowest shard, not the full 13.5-minute suite, so
neither of this branch's pre-merge timings can be read on its own.

* docs(issues): rebuild the ledger after a union-merge duplication

The `merge=union` driver on `docs/outstanding-issues.md` preserves concurrent
appends, but when both sides restructure the same region it concatenates them
wholesale. Merging the latest main did exactly that: every open row appeared
twice and both `issues:next-id` markers survived — 66 duplicate-id errors from
`check:outstanding-issues`, which is precisely the failure that gate exists to
catch (#112).

Resolved by rebuilding on main's canonical file rather than by hand-editing the
duplicated table: reset to `origin/main`, then re-apply this branch's five
captured rows at #131-#135 (main had advanced its allocation to #130 while this
branch was open, so the earlier #128-#132 numbering collided again) and
re-apply the #127 narrowing note. Marker bumped to 136.

Union merge cannot allocate unique ids; only the structural gate can catch when
it has produced an invalid file. It did.

* ci: record the measured shard result, correcting the predicted one

First real run of the sharded shape (CI 30530618838, all green, whole run
13m39 against a 16.8-18.6 min unsharded baseline):

  ui-critical-fast  15 tests   3m14
  Production UI (1) 121 tests  9m36
  Production UI (2) 111 tests  6m54
  Production UI (3) 110 tests  6m20

The prediction was wrong by ~40%. ~6.8 min was expected for the largest shard
from 121/342 tests x 13.5 min; 9m36 happened. Per-test cost is not uniform —
111 tests took 6m54 while 121 took 9m36 — so a count-balanced split understates
the slowest shard whenever the slow specs land in one group. `--shard` can only
balance by count; balancing by duration would mean splitting the slow spec files
themselves.

The win is real but smaller than claimed, and the workflow comment and
process-hardening now carry the measured numbers plus the reason the arithmetic
misleads, so the next person re-measures instead of re-deriving.

Also merges origin/main. The ledger conflict was GitHub-visible only: that file
carries merge=union locally, which GitHub does not honour (#129). Resolved by
keeping the one genuinely new record and dropping three that main already had
elsewhere in the file — append-only forbids dropping a record that exists once,
not keeping a second copy. Superseding record appended for this HEAD, since the
prior one asserted a root cause that #127's trace evidence refutes.

* docs(issues): renumber this branch's rows above main's concurrent allocation

CI caught what I did not: `static-pr` failed on `check:outstanding-issues` with
#131-#134 duplicated and two `issues:next-id` markers.

Cause: main's PR #1424 allocated #131-#134 for its own findings at the same time
this branch held #131-#135, and `merge=union` did what union does — kept both
sides under the same ids. That is #112's documented limit: union preserves
concurrent appends but cannot allocate unique ids, so the structural gate is the
only thing that catches it.

My error was pushing without re-running that gate. The previous push resolved a
`docs/branch-review-ledger.md` conflict, and I validated only that file before
pushing to win the race against main — but the same merge also touched
`docs/outstanding-issues.md`. `verify:cheap` would have caught it locally.

Main's rows keep #131-#134 (already merged and referenced elsewhere); this
branch's five renumber to #136-#140, one marker at 141, and the cold-cache
cross-reference in process-hardening follows its row.

Two of main's new rows also make a planned addition here redundant: #134 is the
absent ledger merge driver and #133 is the outstanding-issues merge churn — both
hit during this branch's work, both already captured upstream, so nothing new is
filed for them.

* docs(issues): rebuild against main's current id allocation

The union merge duplicated the whole open and archive tables again (two header
rows, every id twice) because main restructured the file while this branch held
rows in it. Same resolution as before and for the same reason: rebuild on main's
canonical file rather than hand-editing a doubled table, then re-apply this
branch's five rows.

Main is now at next-id=135, so they land as #135-#139 with the marker at 140.
None of the five is duplicated upstream — checked by summary before re-applying.

This is the third renumber of the same five rows in one PR. That is not a
mistake being repeated, it is #133 ("outstanding-issues conflicts on nearly
every main advance") happening: any branch that holds rows in this file
re-collides every time main lands one. Worth weighing whether captures should
land in their own PR ahead of the work rather than riding along with it.

* docs: record PR 1427 review

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file javascript Pull requests that update javascript code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant