From 9e40ebf30de90e899ee395154f46393d6cafcbe9 Mon Sep 17 00:00:00 2001 From: Stackwright Bot Date: Sun, 31 May 2026 10:08:51 -0400 Subject: [PATCH] fix(e2e): use auto-retrying expect(locator) in rapid nav test (stackwright-jh6) --- .beads/issues.jsonl | 6 +++--- .changeset/fix-flaky-rapid-nav-test.md | 5 +++++ packages/e2e/tests/edge-cases/error-scenarios.spec.ts | 5 +++-- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 .changeset/fix-flaky-rapid-nav-test.md diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 56553477..8c436814 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,5 +1,5 @@ {"_type":"issue","id":"stackwright-1ec","title":"bug: scaffold missing @stackwright/collections and lucide-react deps","description":"## What breaks\n\nA freshly-scaffolded project (via `npx launch-stackwright`) fails `next build` immediately with two module-not-found errors:\n\n```\n./app/_components/providers.tsx:6:1\nModule not found: Can't resolve '@stackwright/collections'\n\n./stackwright-generated/icons.ts:6:1\nModule not found: Can't resolve 'lucide-react'\n```\n\n## Where it breaks\n\n1. `providers.tsx` imports `FileCollectionProvider` from `@stackwright/collections`, but that package is not injected into the scaffolded project's `package.json` by the scaffold generator.\n2. The generated `stackwright-generated/icons.ts` (produced by `stackwright-prebuild`) imports from `lucide-react`, but `lucide-react` is also absent from the scaffolded project's `package.json`.\n\nBoth are runtime/build-time hard dependencies that every scaffolded project needs; neither is optional.\n\n## Fix location\n\nThe scaffold template generator — most likely `packages/launch-stackwright/src/index.ts` or the scaffold template files it uses — needs to inject both of the following as dependencies in the generated `package.json`:\n- `@stackwright/collections`\n- `lucide-react`\n\n## Evidence\n\n`scaffold-smoke-test` CI check failing on `dev` branch across 3+ consecutive runs. See PR #467 for CI logs.","status":"closed","priority":0,"issue_type":"bug","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-30T12:56:09Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T13:00:42Z","started_at":"2026-05-30T12:59:57Z","closed_at":"2026-05-30T13:00:42Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-8c4","title":"A11y: Add skip-to-content landmark navigation","description":"## Problem\nPageLayout.tsx and TopAppBar.tsx have no skip-to-content link. Keyboard users must tab through the entire navigation bar (logo, menu items, dark mode toggle) before reaching page content on every page load. This is WCAG 2.4.1 Level A — the lowest accessibility bar.\n\n## Proposed Solution\n- Add a visually-hidden-until-focused Skip to main content link as the first focusable element in PageLayout\n- Wrap the content area in main id=main-content tabIndex={-1}\n- Style: invisible until focused, then appears as high-contrast banner at top of viewport\n- ~20 lines of code, massive accessibility win","acceptance_criteria":"- [ ] Skip-to-content link is the first focusable element on every page\n- [ ] Link is visually hidden until focused via Tab key\n- [ ] Clicking/activating moves focus to main content area\n- [ ] Works correctly with NavSidebar present\n- [ ] Visual style is high-contrast and clearly visible when focused\n- [ ] E2E keyboard navigation test updated to verify skip link\n- [ ] Works in both light and dark modes","status":"in_progress","priority":1,"issue_type":"feature","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-31T13:31:23Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:57:02Z","started_at":"2026-05-31T13:57:02Z","labels":["a11y","quality","wcag"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-8c4","title":"A11y: Add skip-to-content landmark navigation","description":"## Problem\nPageLayout.tsx and TopAppBar.tsx have no skip-to-content link. Keyboard users must tab through the entire navigation bar (logo, menu items, dark mode toggle) before reaching page content on every page load. This is WCAG 2.4.1 Level A — the lowest accessibility bar.\n\n## Proposed Solution\n- Add a visually-hidden-until-focused Skip to main content link as the first focusable element in PageLayout\n- Wrap the content area in main id=main-content tabIndex={-1}\n- Style: invisible until focused, then appears as high-contrast banner at top of viewport\n- ~20 lines of code, massive accessibility win","acceptance_criteria":"- [ ] Skip-to-content link is the first focusable element on every page\n- [ ] Link is visually hidden until focused via Tab key\n- [ ] Clicking/activating moves focus to main content area\n- [ ] Works correctly with NavSidebar present\n- [ ] Visual style is high-contrast and clearly visible when focused\n- [ ] E2E keyboard navigation test updated to verify skip link\n- [ ] Works in both light and dark modes","status":"closed","priority":1,"issue_type":"feature","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-31T13:31:23Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:59:12Z","started_at":"2026-05-31T13:57:02Z","closed_at":"2026-05-31T13:59:12Z","close_reason":"Closed","labels":["a11y","quality","wcag"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-rqj","title":"chore(examples): validate stackwright-docs App Router migration build","description":"## Context\nThe examples/stackwright-docs app was migrated from Pages Router to App Router (June 2026) to fix broken React.lazy() code splitting and eliminate the 110KB polyfills chunk. The migration created app/layout.tsx, app/_components/providers.tsx, app/_components/page-client.tsx, app/page.tsx, app/[...slug]/page.tsx, and app/not-found.tsx. Pages Router routing files were removed.\n\n## Tasks\n- [ ] Run `pnpm build` in examples/stackwright-docs and fix any build errors\n- [ ] Verify `out/_next/static/chunks/` structure: no polyfills chunk, framework+main+app chunks present\n- [ ] Verify Carousel React.lazy() produces a separate chunk file (code splitting works)\n- [ ] Run E2E smoke tests (`pnpm test:e2e tests/smoke.spec.ts`) — all pages render\n- [ ] Run bundle-size benchmark and confirm it passes the interim 350KB budget\n- [ ] Run visual regression tests and update screenshots if layout is identical\n\n## Acceptance\n- Build succeeds with zero errors\n- All E2E smoke tests pass\n- Bundle size benchmark passes (≤350KB gzip first-load)\n- Carousel chunk is separate from main bundle","status":"closed","priority":1,"issue_type":"task","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-30T17:35:47Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T17:58:08Z","started_at":"2026-05-30T17:39:38Z","closed_at":"2026-05-30T17:58:08Z","close_reason":"Closed","labels":["app-router","performance","validation"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-awj","title":"feat(otters): implement Collection Otter agent (Phase 2)","description":"A complete design spec exists at packages/otters/src/stackwright-collection-otter-spec.md but the otter is not yet implemented. Dependency stackwright-bls (Collections system) is now closed and complete, so this is fully unblocked. Implementation requirements (from spec): (1) Create stackwright-collection-otter.json agent config in packages/otters/src/. (2) Wire CollectionProvider interface from @stackwright/collections. (3) Update Foreman Otter (stackwright-foreman-otter.json) to detect collection needs and invoke Collection Otter. (4) Add Collection Otter to install-agents.js postinstall script. (5) Test with a real blog collection in an example site. The otter should scaffold content/\u003cname\u003e/ dirs, create sample YAML entries in brand voice, wire collection_list content types in pages, and validate all output.","status":"closed","priority":1,"issue_type":"feature","assignee":"planning-agent-931480","owner":"bot@per-aspera.dev","created_at":"2026-05-28T20:18:00Z","created_by":"Stackwright Bot","updated_at":"2026-05-28T20:24:01Z","started_at":"2026-05-28T20:19:37Z","closed_at":"2026-05-28T20:24:01Z","close_reason":"Implemented Collection Otter (stackwright-collection-otter): created agent JSON with full 5-step workflow (create collection, write sample entries, wire listing page, validate+render, nav check + prebuild reminder). Updated Foreman Otter with Phase 4 dispatch, phase skip logic, collection phase section. Updated AGENTS.md with new otter in overview table + full reference section. Updated README.md with raft table row, pipeline diagram Phase 4, package structure listing, and example sites note. Created changeset collection-otter-phase2.md (@stackwright/otters minor).","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-wao","title":"checkForPlaintextSecret: add high-entropy detection for real tokens (bi-directional check)","description":"The checkForPlaintextSecret function in packages/types/src/types/secret-detection.ts currently warns when entropy is low (\u003c 3.8) — catching human-readable plaintext passwords stored in integration auth YAML fields.\n\nHowever, there are two distinct threat classes that one threshold can't catch:\n- Low entropy (\u003c 3.8): human-readable passwords like \"password123\"\n- High entropy (\u003e 4.5): real cryptographic tokens like JWTs, bearer tokens, API keys stored directly in YAML\n\nThe current check only catches the first class. A JWT stored as `token: \"eyJhbGciOiJSUzI1NiJ9...\"` passes through silently.\n\nSuggested fix: change the condition to `entropy \u003c 3.8 || entropy \u003e 4.5` to catch both classes. Update the warning message to distinguish which case triggered.\n\nAdditional context: checkForPlaintextSecret is currently exported but never called (dead code). This issue should also cover wiring it into the prebuild pipeline at the integration auth processing point in packages/build-scripts/src/prebuild.ts. Consider adding a minimum length guard (e.g., skip values \u003c 32 chars for the high-entropy check) to reduce false positives from short but random-looking strings.","notes":"Tags: security, types, build-scripts","status":"closed","priority":1,"issue_type":"task","assignee":"planning-agent-2840e9","owner":"bot@per-aspera.dev","created_at":"2026-05-19T01:40:56Z","created_by":"Stackwright Bot","updated_at":"2026-05-20T16:41:06Z","started_at":"2026-05-20T16:32:10Z","closed_at":"2026-05-20T16:41:06Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0} @@ -10,10 +10,10 @@ {"_type":"issue","id":"stackwright-qb9","title":"A11y: Add aria-live regions for dynamic state changes (Form, Search, Carousel)","description":"## Problem\nZero uses of aria-live, role=alert, or role=status anywhere in packages/core/src (confirmed via grep). Components with dynamic state changes:\n- Form.tsx: loading → success/error with no announcements\n- SearchModal.tsx: result count changes not announced\n- Carousel.tsx: slide changes not communicated\n- ContentItemErrorBoundary: error UI with no live region\n\nThis violates WCAG 4.1.3 (Status Messages).\n\n## Proposed Solution\n- Add AriaLiveRegion utility component to core\n- Form.tsx: announce Submitting.../success/error messages\n- SearchModal.tsx: announce result count (3 results found / No results)\n- Carousel: announce slide position (Slide 2 of 5)\n- ContentItemErrorBoundary: wrap error message in role=alert","acceptance_criteria":"- [ ] Form announces submission state changes to screen readers\n- [ ] SearchModal announces result count changes\n- [ ] Carousel announces current slide position on change\n- [ ] ContentItemErrorBoundary uses role=alert\n- [ ] E2E tests verify aria-live announcements\n- [ ] No false positives in axe-core a11y audit","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-31T13:30:00Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:30:00Z","labels":["a11y","quality","wcag"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-nw6","title":"A11y: Add prefers-reduced-motion support across all animated components","description":"## Problem\nZero instances of prefers-reduced-motion in the entire codebase (confirmed via grep). Yet multiple components have animations:\n- DynamicPage.tsx injects CSS keyframes (sf-shimmer, sf-drift, sf-float) for background animations\n- Carousel.tsx has autoplay with timed transitions\n- TopAppBar uses transition: background-color 0.2s\n\nUsers with vestibular disorders get no relief. This is a WCAG 2.3.3 failure.\n\n## Proposed Solution\n- Add a useReducedMotion() hook (matchMedia wrapper)\n- Wrap all @keyframes injections in @media (prefers-reduced-motion: no-preference) guard\n- Carousel: disable autoplay + instant slide changes when reduced motion preferred\n- Expose theme-level config: motionPreference: respect | always | never","acceptance_criteria":"- [ ] useReducedMotion() hook added to packages/core/src/hooks/\n- [ ] All CSS keyframe animations wrapped in prefers-reduced-motion media query\n- [ ] Carousel autoplay disabled when reduced motion is preferred\n- [ ] All transitions reduced to 0ms or removed when reduced motion preferred\n- [ ] E2E test verifying animations are suppressed with emulateMedia\n- [ ] Unit test for the hook","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-31T13:29:49Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:29:49Z","labels":["a11y","quality","wcag"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-11p","title":"SEO Autopilot: Auto-generate sitemap.xml, robots.txt, and JSON-LD in prebuild","description":"## Problem\nDespite being a content-first static site framework, Stackwright has zero SEO automation. No sitemap.xml, no robots.txt, no structured data. The prebuild already walks all pages and knows their slugs, titles, and content types.\n\n## Proposed Solution\n- Auto-generate sitemap.xml in public/ during prebuild (all page slugs + locale variants are already known)\n- Auto-generate robots.txt with sensible defaults\n- Auto-generate JSON-LD for content types that map naturally to schema.org:\n - faq → FAQPage schema\n - pricing_table → Product/Offer schemas\n - Collection entries with dates → Article/BlogPosting\n- Add optional meta field to page YAML for og:image, description, canonicalUrl\n\n## Why This Fits Stackwright\nThe constrained schema is uniquely suited — content types are enumerable and validated, so JSON-LD generation can be deterministic and correct by construction.","acceptance_criteria":"- [ ] prebuild generates public/sitemap.xml with all page slugs including locale variants\n- [ ] prebuild generates public/robots.txt referencing the sitemap\n- [ ] FAQ content items auto-generate FAQPage JSON-LD in page head\n- [ ] pricing_table content items auto-generate Product/Offer JSON-LD\n- [ ] Collection entries with date fields generate Article JSON-LD\n- [ ] All generated structured data passes Google Rich Results Test\n- [ ] Unit tests for sitemap and JSON-LD generation","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-31T13:29:30Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:29:30Z","labels":["feature","prebuild","seo"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-pzr","title":"fix(e2e): update SSR test for App Router static export behavior","description":"The 'JavaScript disabled still shows content (SSR)' test in tests/edge-cases/error-scenarios.spec.ts:466 fails because App Router static export (output: 'export') produces client-rendered pages — the body is empty when JS is disabled. This is expected behavior for static export but the test was written for Pages Router SSR. Options: (1) update test to expect empty body for static export mode, (2) skip test when output=export, (3) remove test. Discovered during stackwright-rqj CI validation.","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-30T22:20:01Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T22:20:01Z","labels":["app-router","e2e"],"dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-pzr","title":"fix(e2e): update SSR test for App Router static export behavior","description":"The 'JavaScript disabled still shows content (SSR)' test in tests/edge-cases/error-scenarios.spec.ts:466 fails because App Router static export (output: 'export') produces client-rendered pages — the body is empty when JS is disabled. This is expected behavior for static export but the test was written for Pages Router SSR. Options: (1) update test to expect empty body for static export mode, (2) skip test when output=export, (3) remove test. Discovered during stackwright-rqj CI validation.","status":"closed","priority":2,"issue_type":"bug","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-30T22:20:01Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:59:12Z","started_at":"2026-05-31T13:57:03Z","closed_at":"2026-05-31T13:59:12Z","close_reason":"Closed","labels":["app-router","e2e"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-b2w","title":"perf: tree-shake MapLibre GL from stackwright-docs bundle","description":"MapLibre GL JS (36KB gzip / 118KB raw) is included in every page of stackwright-docs despite zero pages using the map content type. Discovered during App Router migration validation (stackwright-rqj). The entire @stackwright/maplibre package is pulled into the shared chunk by Turbopack because @stackwright/core imports or re-exports it unconditionally. Fix: ensure MapLibre is only loaded via React.lazy() or dynamic import when a page actually contains a map content_item. Removing this dead weight would reduce first-load JS from ~448KB to ~412KB gzip.","status":"closed","priority":2,"issue_type":"task","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-30T17:57:44Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T01:52:41Z","started_at":"2026-05-31T01:49:08Z","closed_at":"2026-05-31T01:52:41Z","close_reason":"Closed","labels":["bundle-size","performance"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-70q","title":"perf(core): bundle optimization phase 3 — lazy content types + dep cleanup","description":"## Context\nAfter the App Router migration (June 2026), React.lazy() code splitting now works correctly. The ADR (docs/adr/bundle-architecture.md) targets ≤250KB gzip first-load. Current interim budget is 350KB. This bead covers the remaining optimizations to reach the target.\n\n## Tasks\n\n### Code Splitting (biggest wins)\n- [ ] Lazy-load CodeBlock + PrismJS (~12-15KB gzip savings)\n - Add React.lazy() wrapper in componentRegistry.ts for code_block\n - Add src/components/base/CodeBlock.tsx as separate tsup entry in packages/core/tsup.config.ts\n - prismHighlighter.ts moves into the lazy chunk automatically\n- [ ] Lazy-load FAQ + @radix-ui/react-accordion (~5KB gzip savings)\n - Add React.lazy() wrapper in componentRegistry.ts for faq\n - Add Faq.tsx as separate tsup entry\n\n### Dependency Cleanup\n- [ ] Remove fuse.js from @stackwright/core dependencies (it's dynamically imported in SearchModal — move to peerDependencies or optionalDependencies)\n- [ ] Remove fuse.js from examples/stackwright-docs direct dependencies if unnecessary\n- [ ] Test removing transpilePackages from examples/stackwright-docs/next.config.js (App Router may not need it for workspace packages)\n\n### Budget Tightening\n- [ ] After above changes land, measure actual first-load size\n- [ ] Update performance-budgets.json: target 250KB max / 200KB warn\n- [ ] Update ADR Decision 5 to reflect final achieved targets\n\n## Acceptance\n- First-load JS ≤ 250KB gzip\n- CodeBlock and FAQ load as separate async chunks (visible in out/_next/static/chunks/)\n- All E2E tests pass\n- Performance benchmarks pass with tightened budgets\n\n## References\n- docs/adr/bundle-architecture.md (Decisions 2, 3, 5, 6)\n- packages/e2e/tests/performance/performance-budgets.json\n- packages/core/tsup.config.ts (splitting: true already enabled)","status":"closed","priority":2,"issue_type":"task","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-30T17:35:47Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T13:04:03Z","started_at":"2026-05-31T13:01:23Z","closed_at":"2026-05-31T13:04:03Z","close_reason":"Closed","labels":["bundle-size","code-splitting","performance"],"dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-jh6","title":"flaky: Rapid page navigation e2e test gets empty body intermittently","description":"## What breaks\n\nThe Playwright test `Rapid State Changes › Rapid page navigation doesn't crash` in `packages/e2e/tests/edge-cases/error-scenarios.spec.ts:410` fails intermittently in CI.\n\n## Error\n\n```\nexpect(received).not.toBe(expected) // Object.is equality\nExpected: not \"\"\n\n \u003e 417 | expect(await page.locator('body').innerText()).not.toBe('');\n```\n\n## Context\n\nThe test navigates rapidly between pages and then asserts the body is non-empty. Under CI load, the page sometimes hasn't rendered content by the time the assertion fires. This is a race condition in the test itself, not a product bug.\n\n## Suggested fix\n\nAdd a `waitFor` or `toBeVisible` assertion on a known content element instead of checking `body.innerText()` immediately after navigation. Alternatively, add a retry or increase the navigation settling timeout.\n\n## Evidence\n\nObserved failing across multiple unrelated PRs (e.g., PR #468 CI runs 26684567601 and 26684947798). Passes locally and on re-runs.","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-30T14:31:47Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T14:31:47Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-jh6","title":"flaky: Rapid page navigation e2e test gets empty body intermittently","description":"## What breaks\n\nThe Playwright test `Rapid State Changes › Rapid page navigation doesn't crash` in `packages/e2e/tests/edge-cases/error-scenarios.spec.ts:410` fails intermittently in CI.\n\n## Error\n\n```\nexpect(received).not.toBe(expected) // Object.is equality\nExpected: not \"\"\n\n \u003e 417 | expect(await page.locator('body').innerText()).not.toBe('');\n```\n\n## Context\n\nThe test navigates rapidly between pages and then asserts the body is non-empty. Under CI load, the page sometimes hasn't rendered content by the time the assertion fires. This is a race condition in the test itself, not a product bug.\n\n## Suggested fix\n\nAdd a `waitFor` or `toBeVisible` assertion on a known content element instead of checking `body.innerText()` immediately after navigation. Alternatively, add a retry or increase the navigation settling timeout.\n\n## Evidence\n\nObserved failing across multiple unrelated PRs (e.g., PR #468 CI runs 26684567601 and 26684947798). Passes locally and on re-runs.","status":"in_progress","priority":2,"issue_type":"bug","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-30T14:31:47Z","created_by":"Stackwright Bot","updated_at":"2026-05-31T14:07:52Z","started_at":"2026-05-31T14:07:52Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-pez","title":"perf/benchmarks: fix performance budget files and reset to meaningful targets post-optimization","description":"## Goal\nFix the current state where performance budgets are meaningless ('current + 10% headroom') and the comments in `bundle-size.bench.ts` are badly out of sync with `performance-budgets.json`. Set real forward-looking targets once optimization work lands.\n\n## Current problems\n1. `performance-budgets.json` first-load JS budget: 450KB max. Benchmark comments in `bundle-size.bench.ts` say baseline was ~85KB and budget was \u003c100KB. Nobody updated the comments when the budget was bumped.\n2. Benchmarks have been failing for ~1 month — the budget ceiling has likely been breached even at 450KB as new features shipped.\n3. The `performance.yml` CI workflow is the actual gate; any mismatch between it and `performance-budgets.json` creates confusion about what the real enforcement is.\n\n## Tasks\n- [ ] Sync the comments in `bundle-size.bench.ts` with `performance-budgets.json` (do this now, independent of optimization)\n- [ ] After optimization work lands (icons + code-splitting beads), set new meaningful targets in `performance-budgets.json`\n- [ ] Proposed targets post-optimization: first-load JS warn 150KB / max 200KB, all-pages JS warn 600KB / max 800KB\n- [ ] Consider adding `@next/bundle-analyzer` output as a CI artifact on every PR for visibility (not a gate, just a report)\n- [ ] Update the benchmark README comments and PERFORMANCE.md to reflect the new architecture\n\n## Can be done in two phases\n- **Phase 1 (now)**: Fix the comment/budget mismatch — pure maintenance, no optimization needed\n- **Phase 2 (after optimization)**: Set the new meaningful budget numbers\n\n## Dependencies\n- Phase 2 depends on icons bead (stackwright-zkq) and code-split bead (stackwright-mp6) landing first","status":"closed","priority":2,"issue_type":"task","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-29T15:09:49Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T01:52:21Z","started_at":"2026-05-30T01:49:42Z","closed_at":"2026-05-30T01:52:21Z","close_reason":"Phase 1 (comment sync) and Phase 2 (new meaningful targets) both done. ADR written at docs/adr/bundle-architecture.md. first-load budget: 150KB warn / 200KB max.","labels":["maintenance","performance"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-mp6","title":"perf/code-split: implement dynamic import pattern for optional heavy content types","description":"## Goal\nEstablish and implement a code-splitting pattern for content types that are heavy but not universally used (carousel, video, potentially others identified by the audit).\n\n## Background\nAll content types registered via `DefaultStackwrightComponents` and `@stackwright/core` exports are currently loaded synchronously. For sites that don't use a carousel or video, they still pay the JS cost for those components.\n\nThe map content type has already solved this problem with a registry/provider pattern — it has zero weight in the base bundle unless the provider is explicitly registered. That pattern is a good model.\n\n## Tasks\n- [ ] Define the dynamic import pattern to use (Next.js `dynamic()` wrapper, React.lazy + Suspense, or registry async-loader) — decision from RFC bead\n- [ ] Identify which content types are candidates for code-splitting based on bundle audit\n- [ ] Implement the pattern for the top 2–3 candidates first (likely: carousel, video)\n- [ ] Add to AGENTS.md: 'When adding a new content type with \u003e20KB gzipped weight, use [pattern]'\n- [ ] Measure before/after first-load JS impact\n\n## Dependencies\n- RFC bead (stackwright-y5u) must resolve the code-splitting pattern choice\n- Audit bead (stackwright-9yh) confirms which content types are worth splitting","status":"closed","priority":2,"issue_type":"task","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-29T15:09:34Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T01:48:00Z","started_at":"2026-05-30T01:35:27Z","closed_at":"2026-05-30T01:48:00Z","close_reason":"Dynamic imports implemented for Carousel and fuse.js/SearchModal. React.lazy() in componentRegistry + tsup splitting:true creates real ESM chunks. Carousel removed from main barrel, available at @stackwright/core/carousel. fuse.js dynamic import in SearchModal useEffect. Suspense boundary added in DynamicPage. No API changes. All 503 tests pass.","labels":["architecture","performance"],"dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-zkq","title":"perf/icons: implement prebuild icon manifest — generate per-site static lucide imports","description":"## Goal\nReplace lucideAllIconsPreset with a prebuild-generated per-site icon manifest that contains ONLY the icons actually used in the site's YAML content. Eliminates ~120KB gzip from first-load JS.\n\n## Approach\nThe stackwright-prebuild script (in @stackwright/build-scripts) will:\n1. After processing all YAML content to JSON, recursively walk all page data\n2. Collect all { type: 'icon', src: string } objects across all pages\n3. Resolve legacy MUI aliases (hard-coded map, candidates for future deprecation)\n4. Write public/stackwright-content/_icon-manifest.json (debug artifact)\n5. Write stackwright-generated/icons.ts with static lucide imports + registerSiteIcons() function\n\n## Generated file shape\n```typescript\n// GENERATED by stackwright-prebuild — do not edit. Run pnpm predev to regenerate.\nimport { Star, Moon, Sun } from 'lucide-react';\nimport { registerStackwrightIcons } from '@stackwright/icons';\nimport { BlueSkyIcon } from '@stackwright/icons/icons/social/BlueSkyIcon';\nimport { StackwrightIcon } from '@stackwright/icons/icons/brand/StackwrightIcon';\n\nexport function registerSiteIcons(): void {\n registerStackwrightIcons({ Star, Moon, Sun, bluesky: BlueSkyIcon, stackwright: StackwrightIcon });\n}\n```\n\n## Wiring\n- No webpack alias needed — generated file is in user app directory, Next.js webpack processes it natively and tree-shakes lucide-react\n- defaultIcons.ts fallback: swap lucideAllIconsPreset → lucideIconPreset (curated subset, ~43 icons)\n- launch-stackwright templates: Providers.tsx calls registerSiteIcons() from generated file\n- stackwright-generated/ added to .gitignore\n\n## Tasks\n- [ ] Add collectIconSrcs() recursive walker to prebuild.ts\n- [ ] Add LEGACY_MUI_ICON_ALIASES hard-coded map and SYSTEM_ICON_NAMES set to prebuild.ts\n- [ ] Add generateIconManifest() function to prebuild.ts\n- [ ] Call generateIconManifest() in main prebuild flow after all pages processed\n- [ ] Update defaultIcons.ts: swap lucideAllIconsPreset → lucideIconPreset\n- [ ] Update launch-stackwright templates (Providers.tsx pattern)\n- [ ] Add stackwright-generated/ to .gitignore\n- [ ] Document in AGENTS.md\n- [ ] Build + measure before/after on docs example; document savings\n\n## Notes\n- lucideAllIconsPreset and registerAllLucideIcons() are KEPT as opt-in exports for power users\n- System icons (Sun, Moon, Info, AlertTriangle, CircleAlert) always included regardless of YAML content\n- Legacy MUI aliases are hard-coded in prebuild scanner (option B) and are candidates for deprecation","status":"closed","priority":2,"issue_type":"task","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-29T15:09:23Z","created_by":"Stackwright Bot","updated_at":"2026-05-30T01:13:34Z","started_at":"2026-05-30T01:09:26Z","closed_at":"2026-05-30T01:13:34Z","close_reason":"Prebuild icon manifest implemented. collectIconSrcs + generateIconManifest added to prebuild.ts. defaultIcons.ts now uses curated preset as fallback. Scaffold template updated to registerSiteIcons(). AGENTS.md and CLAUDE.md updated.","labels":["performance","quick-win"],"dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/.changeset/fix-flaky-rapid-nav-test.md b/.changeset/fix-flaky-rapid-nav-test.md new file mode 100644 index 00000000..d30c0287 --- /dev/null +++ b/.changeset/fix-flaky-rapid-nav-test.md @@ -0,0 +1,5 @@ +--- +"@stackwright/e2e": patch +--- + +fix(e2e): replace non-retrying innerText() with auto-retrying expect(locator) in rapid navigation test diff --git a/packages/e2e/tests/edge-cases/error-scenarios.spec.ts b/packages/e2e/tests/edge-cases/error-scenarios.spec.ts index 007581b9..9f631361 100644 --- a/packages/e2e/tests/edge-cases/error-scenarios.spec.ts +++ b/packages/e2e/tests/edge-cases/error-scenarios.spec.ts @@ -409,8 +409,9 @@ test.describe('Rapid State Changes', () => { for (let i = 0; i < 3; i++) { for (const pagePath of pages) { await page.goto(pagePath, { waitUntil: 'domcontentloaded' }); - // Quick check that it loaded - expect(await page.locator('body').innerText()).not.toBe(''); + // Use auto-retrying expect(locator) instead of bare innerText() — the old form + // had zero retry logic and raced against React hydration under CI load (stackwright-jh6) + await expect(page.locator('main')).toBeVisible(); } }