Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 30 additions & 19 deletions e2e/semantic-search.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import { test, expect } from '@playwright/test';
import { navClick } from './helpers/navigation';

const SEMANTIC_FALLBACK_ASSERTION_TIMEOUT_MS = 10_000;
const SEMANTIC_FALLBACK_TEST_TIMEOUT_MS = 15_000;

/**
* Semantic search (N1, Issue #751) — UI-level coverage only.
*
* No model download happens here: the spec aborts requests to the Hugging
* Face Hub and the transformers.js WASM CDN, which forces the graceful
* No model download happens here: the spec returns a deterministic 404 for
* Hugging Face Hub and transformers.js WASM CDN requests to force the graceful
* lexical fallback path with its surfaced hint. Ranking behavior itself is
* covered by the mocked unit tests (embeddings/vector-store/worker).
*/
test.describe('Semantic search toggle', () => {
// Prevent the app service worker from forwarding model requests outside the
// Playwright route handler.
test.use({ serviceWorkers: 'block' });

test.beforeEach(async ({ page }) => {
// Block the embedding model and WASM binaries so the embedder cannot
// load — semantic queries then degrade to the lexical fallback hint.
// Subdomains matter: the model files live on cdn-lfs.huggingface.co and
// the tokenizer/assets may come from jsdelivr subdomains.
// Use an immediate missing-asset response instead of relying on browser
// network abort/retry scheduling. Subdomains matter: model files live on
// cdn-lfs.huggingface.co and tokenizer/assets may come from jsdelivr.
await page.route(
/^https:\/\/(?:[a-z0-9-]+\.)*(?:huggingface\.co|cdn\.jsdelivr\.net)\//,
(route) => route.abort(),
/^https:\/\/(?:[a-z0-9-]+\.)*(?:huggingface\.co|cdn\.hf\.co|cdn\.jsdelivr\.net)\//,
(route) =>
route.fulfill({
status: 404,
contentType: 'text/plain',
body: 'Model unavailable in E2E',
}),
);
await page.goto('/');
await navClick(page, /library/i);
Expand All @@ -43,19 +54,19 @@ test.describe('Semantic search toggle', () => {
test('surfaces a lexical fallback hint when the semantic model is unavailable', async ({
page,
}) => {
test.setTimeout(SEMANTIC_FALLBACK_TEST_TIMEOUT_MS);

await page.getByRole('switch', { name: /semantic search/i }).click();
await page.getByRole('searchbox', { name: /search library/i }).fill('triz');
const searchInput = page.getByRole('searchbox', { name: /search library/i });
await searchInput.fill('triz');

const status = page.getByRole('status').filter({ hasText: /keyword results/i });
// transformers.js spends several seconds failing its CDN fetches (model
// + WASM) before surfacing the embedder error that drives the fallback, and a
// full parallel sweep stretches that well past 20s — it failed on both the
// attempt and the retry of one four-project run while passing 9/9 in
// isolation (plans/149). The budget is deliberately generous because this
// assertion waits on a failing network stack, not on app logic.
await expect(status).toBeVisible({ timeout: 60_000 });

// The search box keeps working — results still render (graceful fallback).
await expect(page.getByRole('searchbox', { name: /search library/i })).toHaveValue('triz');
await expect(status).toBeVisible({
timeout: SEMANTIC_FALLBACK_ASSERTION_TIMEOUT_MS,
});
await expect(searchInput).toHaveValue('triz');
await expect(
page.getByRole('heading', { name: 'TRIZ Contradiction Matrix' }),
).toBeVisible();
});
});
23 changes: 13 additions & 10 deletions plans/149-nightly-full-viewport-e2e-2026-09-24.md
Original file line number Diff line number Diff line change
@@ -1,219 +1,222 @@
# Plan 149 — Nightly E2E Sweep Covers Every Viewport (2026-09-24)

**Type**: CI coverage extension + test robustness
**Scope**: `.github/workflows/ci-and-labels.yml`, `e2e/semantic-search.spec.ts`
**Follows**: plans/148 §6.1–6.2, plans/122 W2 (which introduced the nightly E2E job)

## 1. Problem

CI's E2E job installs **Chromium only** and runs `pnpm run test:e2e
--project=chromium`. The `mobile` (iPhone 13, 390×664) and `tablet` (iPad Pro 11,
834×1194) projects are **WebKit** device descriptors, so they have never run in
CI — not on PRs, not in the nightly sweep that plans/122 added.

That gap is not theoretical: the graph label-click defect in plans/148 reached
`main` and was only caught by a manual four-project sweep. Playwright's own log
showed the failure at `chromium`, but the sweep is also the only way
viewport-specific layout regressions surface at all.

### The nightly never ran at all

Checking the most recent scheduled run (`2026-09-24T07:59Z`, head `064702a`)
before changing anything showed:

```
Detect Changes: completed/success
Quality Gate: completed/skipped
Unit Tests: completed/skipped
E2E Tests: completed/skipped
Build: completed/skipped
```

`e2e-tests` declares `needs: [changes, unit-tests]`, and `unit-tests` excludes
scheduled events (`github.event_name != 'schedule'`). GitHub skips a job whose
needed job was skipped unless the dependent job uses a status function, and the
E2E condition has none — so the "nightly E2E backfill" added by plans/122 W2 has
been a no-op since it landed. Nothing in the workflow said so: the job that would
have reported it was the one being skipped.

Two things therefore stood in the way of a working nightly:

1. **WebKit is not installed** in that job (`playwright install --with-deps
chromium`), and installing it adds apt dependencies — fine nightly, wasteful
per PR.
2. **`e2e/semantic-search.spec.ts` is load-sensitive.** It waits up to 20 s for
transformers.js to finish failing its blocked CDN fetches before the lexical
fallback hint appears. Under a four-project sweep it failed on both the
attempt *and* the retry, while passing 9/9 in isolation. A nightly that goes
red for that reason is worse than no nightly.

## 2. Change

**Workflow** (`e2e-tests` job):

| Event | Browsers installed | Projects run |
|---|---|---|
| `pull_request` | chromium | `--project=chromium` |
| push to `main` | chromium | `--project=chromium` |
| `schedule` (nightly, main) | chromium + webkit | all four |
| `workflow_dispatch` | chromium + webkit | all four |

`timeout-minutes` goes 20 → 40: PR runs still finish in ~3 minutes, and the
nightly now runs 596 tests across four projects on WebKit as well as Chromium.
The existing `actions/cache` step for `~/.cache/ms-playwright` keeps the WebKit
download off the nightly's critical path after the first run.

The condition is written as `event_name == 'schedule' || event_name ==
'workflow_dispatch'` rather than `event_name != 'pull_request'`. The first
version used the negation, which silently included **pushes to `main`** — every
frontend merge then paid the four-project cost (~10 minutes, observed on the
merge that landed this work). A push to `main` is a merge the PR already
validated; the sweep belongs to the nightly that exists for that gap. The
contract test now pins the sweep to those two events.

**Workflow** (`unit-tests` job): the `github.event_name != 'schedule'` exclusion
is dropped, so the job runs nightly. This is the fix for the silently skipped
sweep — `e2e-tests` needs it, and a skipped dependency skips the dependent. The
alternative (keeping the exclusion and giving `e2e-tests` an `always()`/`!cancelled()`
escape) relies on status-function semantics that cannot be exercised by a manual
dispatch, whereas "the dependency runs in every event that reaches it" is
provable from the run history and testable in the workflow contract test. The
nightly therefore also runs the unit suite on `main`, which is a bonus signal
rather than a deviation.

**Spec** (`semantic-search.spec.ts`): the fallback-hint budget goes 20 s → 60 s,
with the reason recorded in the comment — the assertion waits on a *failing
network stack*, not on app logic, so its timing is a property of the environment.

## 3. Verification

| Check | Result |
|---|---|
| Workflow YAML parses (`yaml.safe_load`) | OK — `timeout-minutes: 40` |
| `yamllint -c .yamllint.yml` | clean |
| `shellcheck` on both `run:` blocks | clean |
| Both branches of each `run:` block, executed for `pull_request` and `schedule` | select the intended command |
| Workflow contract test (`src/lib/__tests__/workflows.test.ts`) | 63/63 pass |
| `pnpm run test:e2e` (all four projects, 596 tests) | 592 passed, 4 skipped, 0 failed |
| `e2e/semantic-search.spec.ts --repeat-each=3` in isolation | 9/9 pass |
| `./scripts/quality_gate.sh`, `pnpm run build` | ✓ green |
| **Live nightly path**: `workflow_dispatch` on `main` after merge | see §4 |

The workflow suite asserted `timeout-minutes: 20` for the E2E job, so this change
updates it to 40 and adds two cases:

- PR runs install and run Chromium only, while the scheduled and manual paths
install WebKit and run every project. Both branches are split on `else` and
asserted by exact command — `--project=chromium` is a prefix of the nightly
command and `chromium` is a prefix of `chromium webkit`, so substring checks
would pass on a Chromium-only nightly (mutation-checked: rewriting the nightly
branch to `--project=chromium`, or the PR branch to install `chromium webkit`,
each fails the suite).
- No job in `e2e-tests.needs` may exclude scheduled events, since a skipped
dependency skips the dependent (mutation-checked: restoring the exclusion on
`unit-tests` fails the suite).

## 4. Post-merge check

The change is only proven when the dispatched run actually installs WebKit and
executes the mobile/tablet projects on `main`. `workflow_dispatch` exists for
exactly this (plans/122 W2), so the run is triggered immediately after merge
rather than waiting for the 03:00 UTC cron.

**Result** — dispatch run [`35986156948`](https://github.com/d-oit/do-knowledge-studio/actions/runs/35986156948)
on `main` (`9d693ba`):

| Job | Result |
|---|---|
| Detect Changes | success |
| Quality Gate | success |
| Unit Tests | success |
| Coverage Report | success |
| Build | success |
| **E2E Tests** | **success — `Running 596 tests using 2 workers`, 591 passed, 4 skipped, 1 flaky (8.5 m)** |

So the full-viewport sweep now executes in CI for the first time: 596 tests, not
the 149 that a Chromium-only run covers. The dispatch also confirmed the
dependency fix — `unit-tests` ran and `e2e-tests` followed it, which is the exact
chain the schedule needs.

Note: the dispatch and the post-merge push run share the `ci-main` concurrency
group, so the push-triggered run was cancelled by the dispatch. Same commit, and
the dispatch ran the superset.

### The flaky test it surfaced

`1 flaky`: `[desktop-xl] keyboard-navigation.spec.ts:27 › Escape closes command palette`
failed on the first attempt and passed on retry — `getByRole('dialog', { name:
/command/i })` was not found within 5 s.

Cause: `keyboard-navigation.spec.ts` was the only one of the three specs that
presses Ctrl+K *without* a readiness wait — its `beforeEach` was just
`page.goto('/')`, and the in-test `expectNavigationReachable` only proves the
sidebar is *visible*, which it is from server-rendered HTML. The shortcut is
bound by an effect, so a press issued before hydration is simply lost, and no
timeout increase would recover it.

Fix: the shell now renders a real readiness signal, and the specs wait on it.

- `AppShell` sets `data-app-ready="true"` from a mount effect. React flushes child
effects before parent effects, so when the attribute appears every descendant
listener — including `CommandPalette`'s window-level Ctrl+K handler — is bound.
It is set from an effect rather than rendered during hydration, so server and
client markup still match on the first pass (the mistake that forced the removal
of a `data-hydrated` attribute in plans/145).
- `e2e/helpers/navigation.ts` gains `waitForAppReady(page)`, which waits for that
attribute, and all three specs call it from `beforeEach`.

Verified in a live browser: the attribute is absent immediately after `goto`
(count 0), appears after mount (count 1), Ctrl+K opens the palette immediately
after it appears, and there are **no hydration-related console messages**.
`src/components/studio/app-shell.test.tsx` pins the hook so it cannot be dropped
silently.

### What the first fix got wrong

The first attempt waited on `networkidle` plus a visible `<main>` landmark. Review
correctly rejected it: `AppShell` renders `<main>` unconditionally, so a
server-rendered DOM satisfies both conditions before hydration — the helper
correlated with readiness without observing it. It happened to remove the flake,
but for the wrong reason, and the plan already argued for the marker it should
have used.

## 5. Follow-ups

1. **The next real nightly should be confirmed.** The dispatch run proves the
mechanism; the 03:00 UTC schedule run is the last piece. If it reports
`E2E Tests: skipped` again, the cause is a dependency this plan did not see.
→ **Explained and hardened** (plans/151 §2). The `2026-09-24T07:59Z` scheduled
run skipped every job because its head (`064702a`) predates this plan's fix;
the schedule runs the workflow as it exists on the default branch at trigger
time. The same investigation found the nightly's scope depended on the path
filter's incidental fallback, which plans/151 replaced with an explicit
`schedule`/`workflow_dispatch` guard. The next nightly is the first scheduled
execution of the fixed workflow.
1. **Nightly execution — confirmed 2026-09-25.** Scheduled run
[`36112610311`](https://github.com/d-oit/do-knowledge-studio/actions/runs/36112610311)
on `main` (`094b7e0`) ran both `Unit Tests` and `E2E Tests`. E2E completed
`604` tests (`600 passed`, `4 skipped`) in `13.6m` across chromium, mobile,
tablet, and desktop-xl. This confirms the scheduled job runs; the run used
`main` before this local semantic-search test change and does not validate
that edit.
→ **Explained and hardened** (plans/151 §2). The `2026-09-24T07:59Z`
scheduled run skipped every job because its head (`064702a`) predates this
plan's fix; the schedule runs the workflow as it exists on the default
branch at trigger time. The same investigation found the nightly's scope
depended on the path filter's incidental fallback, which plans/151 replaced
with an explicit `schedule`/`workflow_dispatch` guard.
2. **Pre-hydration interaction audit — closed at the helpers (2026-09-24).** The
exposure was measured across all 24 specs rather than patched per spec:
- `openNavIfHidden` (and therefore `navClick`) now waits for `data-app-ready`
first. That covers the 22 specs that navigate through the helpers —
`crud-workflow`, `home`, `timeline`, `progressive-disclosure` and
`accessibility` all have `navClick(...)` as their first action, so no per-spec
edit was needed.
- `responsive.spec.ts` waits after each of its seven `goto` calls. It sets the
viewport *before* navigating, so a `beforeEach` wait would run against
`about:blank` and time out — worth remembering for viewport-specific specs.
- `right-panel.spec.ts` waits in its `beforeEach`: its first action is a click
on a server-rendered close control.
- `contrast.spec.ts` performs no interactions.
- `claim-extraction` and `editor-mentions` build on `createNewEntity`, which
navigates via `navClick`.
What remains unguarded is a spec that clicks a statically imported view's
element before hydration *without* going through the helpers; none does today.
3. **PR runs still cover one viewport.** The nightly closes the gap daily, not
per PR. If a viewport-specific regression lands, the next nightly catches it —
acceptable for now; a matrix job per viewport would cost ~3× the runner time
on every frontend PR.
4. **`lint_cache.sh` is skipped silently when absent** (plans/147 §4.1) — every
lint reports as failed with a misleading message.

Check notice on line 220 in plans/149-nightly-full-viewport-e2e-2026-09-24.md

View check run for this annotation

nexus-check / GitNexus

Changed symbol: Plan 149 — Nightly E2E Sweep Covers Every Viewport (2026-09-24)

`Plan 149 — Nightly E2E Sweep Covers Every Viewport (2026-09-24)` (Section) is directly changed by this PR. PR-wide downstream impact: 0 direct dependent(s), 0 indirect. See the check summary for the impacted-file breakdown.

Check notice on line 220 in plans/149-nightly-full-viewport-e2e-2026-09-24.md

View check run for this annotation

nexus-check / GitNexus

Changed symbol: 5. Follow-ups

`5. Follow-ups` (Section) is directly changed by this PR. PR-wide downstream impact: 0 direct dependent(s), 0 indirect. See the check summary for the impacted-file breakdown.
5. **DeepSource quota** (plans/141 §1) — account-level, needs the maintainer.
6. **ESLint 10 workaround** (plans/140 §2) — blocked upstream.
34 changes: 22 additions & 12 deletions plans/151-quality-gate-lint-cache-and-nightly-guard-2026-09-24.md
Original file line number Diff line number Diff line change
@@ -1,133 +1,143 @@
# Plan 151 — Quality-Gate Lint-Cache Fallback + Nightly Scope Guard (2026-09-24)

**Type**: tooling correctness + CI hardening
**Scope**: `scripts/quality_gate.sh`, `.github/workflows/ci-and-labels.yml`,
`tests/quality-gate-lint-cache.bats`, `src/lib/__tests__/workflows.test.ts`
**Follows**: plans/147 §4.1 (lint cache), plans/149 §5.1 (nightly confirmation)

## 1. `lint_cache.sh` was required but optional (plans/147 §4.1)

`quality_gate.sh` sources `scripts/lib/lint_cache.sh` only when the file exists,
but calls `lint_if_changed` unconditionally in two places (shellcheck, ~line 581;
markdownlint, ~line 661). With the library absent the helper is undefined, so
each call fails as `command not found` and the gate reports

```
✗ shellcheck failed: <file> # for every shell file
✗ markdownlint failed: <file> # for every markdown file
```

That is a **false failure**, and the cause is invisible: the shellcheck call
redirects stderr to `/dev/null`, so the `command not found` never reaches the
log. Fail-closed was the right instinct, but the message accused the wrong thing.

**Fix.** An `else` branch defines an uncached fallback and says so once:

```bash
echo "Warning: scripts/lib/lint_cache.sh is missing - lints run uncached" >&2
lint_if_changed() {
shift 3
"$@"
}
```

The contract is unchanged — first three arguments are consumed, the rest is the
command — so both call sites keep working, and a real finding still fails the
gate. Fail-soft, not fail-open.

## 2. A scheduled run had no guaranteed scope (plans/149 §5.1)

The nightly's job graph is `e2e-tests needs [changes, unit-tests]`, and both gate
on `needs.changes.outputs.any_code == 'true'`. On a scheduled run there is no
diff, so that value came from `dorny/paths-filter`'s fallback (list every file as
added) — verified in the `2026-09-24T07:59Z` run, which reported
`Detected 1404 changed files` with `any_code = true`. It worked, but **by
accident**: had the filter resolved to `false`, `unit-tests` would be skipped and
`e2e-tests` — which needs it — skipped with it. That is the same class of silent
no-op plans/149 fixed one layer up, so it is now explicit instead of incidental.

A `forced` step in the `changes` job writes all three outputs as `true` when the
event is `schedule` or `workflow_dispatch`, and every job output prefers it:

```yaml
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
```

Pull requests and pushes keep the real diff, so PR scope and cost are untouched
(the full four-project sweep stays nightly-only, plans/149 §5.3).

### Why the nightly looked broken today

The `07:59Z` scheduled run skipped every job — including Unit Tests and E2E. It
was **not** a regression: the run's head was `064702a` (2026-09-23 21:47), which
predates the plans/149 fix (`9d693ba`, 2026-09-24 10:14 UTC). The schedule fires
against the workflow on the default branch at trigger time, so today's run used
the pre-fix file. The next nightly is the first scheduled execution of the fixed
workflow.

Observed cadence, for the record: the cron is `0 3 * * *`, but GitHub has fired
this workflow at `07:40`–`08:24Z` every day for the last week (7/7 runs). The
delay is GitHub's scheduler, not this repository; the daily sweep is what
matters, and `workflow_dispatch` remains the on-demand lever.

## 3. Verification

| Check | Result |
|---|---|
| `bats tests/quality-gate-lint-cache.bats` | 3/3 pass |
| Mutation: revert §1, rerun the suite | test 1 fails (`status` non-zero — the false failure is reproduced), tests 2–3 pass |
| `pnpm exec vitest run src/lib/__tests__/workflows.test.ts` | 64/64 pass |
| Mutation: drop the `forced` step, rerun | the new contract test fails |
| `./scripts/quality_gate.sh` | see §4 |

`tests/quality-gate-lint-cache.bats` builds a throwaway repository with **no**
`scripts/lib/lint_cache.sh` and stub linters on `PATH`, so it is independent of
whether shellcheck/markdownlint are installed and of their versions. Test 2 pins
the other direction: a stub that exits 1 must still fail the gate.

### Review finding on the workflow contract test

The first version of the contract test asserted the forced term with `toContain`
and checked only `any_code=true`. Both were too weak, and the second was wrong in
a way that matters:

- **Ordering.** In a GitHub Actions `||` chain the first non-empty term wins, so
a forced term placed *after* the filter would never apply — and `toContain`
passes anyway. The test now splits each output expression on `||` and asserts the
exact order: forced, filter, diff-API default.
- **Emissions.** Only one of the three emissions was asserted, so deleting
`frontend=true` or `tooling=true` would have passed while restoring the
fallback-dependent value for that output. All three are now asserted.

Both gaps were confirmed by mutation before pushing: reordering the `frontend`
output and deleting the `frontend=true` emission each fail the test.

## 4. Gate and CI results

| Check | Result |
|---|---|
| `./scripts/quality_gate.sh` (full, all scopes) | **✓ All Quality Gates PASSED** — lint, typecheck, test, shellcheck, `bats tests/`, link validation |
| `yamllint` (CI-parity config) on `.github/` | exit 0 — and the two warnings this file carried (`truthy` on `on:`, `comments-indentation` on the `workflow_dispatch` comment) are now cleared |
| `actionlint .github/workflows/ci-and-labels.yml` | exit 0 — the SC2129 findings in both output steps are gone |

## 5. Follow-ups

1. **`SC2002` in the coverage job** — `COVERAGE=$(cat coverage/coverage-summary.json | jq …)`
(`ci-and-labels.yml`, "Generate coverage badge"). Pre-existing and style-level
(`actionlint` runs with `fail_level: error`), so it does not fail CI; it is the
last finding in that file and unrelated to this change.
2. **Confirm the next nightly** — the first scheduled execution of the guarded
workflow. `gh api "repos/d-oit/do-knowledge-studio/actions/runs?event=schedule"`
and check `E2E Tests` is not `skipped`.
→ **Mechanism proven on `main`** (dispatch run
[`36048842590`](https://github.com/d-oit/do-knowledge-studio/actions/runs/36048842590),
head `55033bf`): `Treat every path as changed on scheduled and manual runs`
ran, **Unit Tests ran** instead of being skipped, and `E2E Tests` swept
`604 tests` with `600 passed` in 9.9 min across all four projects. The guarded
path and the scheduled path differ only in `github.event_name`, which the
contract test pins — the remaining confirmation is the next 03:00 UTC
(observed ~08:00 UTC) scheduled run.
3. **`semantic-search.spec.ts` load sensitivity** (plans/148 §6.1) — unchanged.
2. **Confirm the next nightly** — closed 2026-09-25. Scheduled run
[`36112610311`](https://github.com/d-oit/do-knowledge-studio/actions/runs/36112610311)
on `main` (`094b7e0`) ran `Unit Tests` and `E2E Tests` successfully; E2E
completed `604` tests (`600 passed`, `4 skipped`) in `13.6m` across all four
projects. `Quality Gate`, `Build`, `Coverage Report`, and `Dependency Verify`
were skipped as configured; the workflow limits scheduled runs to unit and
E2E jobs; this confirmation came from the scheduled event, not a manual dispatch.
3. **`semantic-search.spec.ts` load sensitivity** (plans/148 §6.1) — resolved
in test code only. The spec blocks service workers (the app SW bypassed
Playwright routes) and returns deterministic `404`s for Hugging Face
Hub/CDN and jsDelivr model requests. The fallback uses a `10s` assertion
timeout inside a `15s` test timeout. Chromium repeat-each=3 passed all nine
test instances in `29.8s`; the latest-main four-project, zero-retry sweep
passed `600/604` (`4` skipped) in `8.8m`.
The nightly at `094b7e0` confirms the scheduled job runs but predates this
local edit; it does not validate the updated semantic-search spec. No

Check notice on line 134 in plans/151-quality-gate-lint-cache-and-nightly-guard-2026-09-24.md

View check run for this annotation

nexus-check / GitNexus

Changed symbol: Plan 151 — Quality-Gate Lint-Cache Fallback + Nightly Scope Guard (2026-09-24)

`Plan 151 — Quality-Gate Lint-Cache Fallback + Nightly Scope Guard (2026-09-24)` (Section) is directly changed by this PR. PR-wide downstream impact: 0 direct dependent(s), 0 indirect. See the check summary for the impacted-file breakdown.

Check notice on line 134 in plans/151-quality-gate-lint-cache-and-nightly-guard-2026-09-24.md

View check run for this annotation

nexus-check / GitNexus

Changed symbol: 5. Follow-ups

`5. Follow-ups` (Section) is directly changed by this PR. PR-wide downstream impact: 0 direct dependent(s), 0 indirect. See the check summary for the impacted-file breakdown.
production semantic-search code changed.
4. **ESLint 10 workaround** (plans/140 §2) — still blocked upstream.
5. **Graph density** (plans/148 §6.3) — the remaining item from the same follow-up
sweep; tracked in plans/152.
6. **HomeView hydration mismatch (E2E warning)** — React logs mismatched motion
styles during the local and scheduled sweeps (`opacity: "0"` vs `1`; progress
widths `0px` vs computed percentages). The same warning appears in the
pre-change manual run `36048842590`; track it separately rather than
suppressing it in the semantic-search test.
Loading