From 71b984f18208aa0a54f720a47df5174372297964 Mon Sep 17 00:00:00 2001 From: ulises-jeremias Date: Sun, 5 Jul 2026 21:31:04 -0300 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20landing=20consolidation=20phase?= =?UTF-8?q?=202=20=E2=80=94=20consistency,=20assets,=20CI,=20and=20gracefu?= =?UTF-8?q?l=20degradation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1 — Consistency Hardening: - Standardize 6 feature cards per template in shared/landings/features.ts - Fix version drift (Vite 8→Vite, React 19→React, Astro 7→Astro, etc.) - Switch all docs links to root-absolute /docs/ prefix - Create docs/ dirs for remix and astro starters (fix 404s) - Fix 'Start editing' CTA to non-navigable in all templates - Replace all hardcoded CSS colors with --cna-* tokens - Add --cna-amber-soft light-mode override where missing - Add .cna-landing__card to stagger animation selectors - Add prefers-reduced-motion + light mode to webext popup - Reconcile DESIGN vs GUIDE docs (keyframe names, shadows, fill mode) - Remove dead files (next.svg, vercel.svg, Welcome.astro, stray page.tsx) Phase 2 — Asset Robustness: - Unique gradient IDs across all SVGs (no ID collisions) - Add role=img to favicon.svg, aria-hidden to wordmark text - Create logo-wordmark-dark.svg variant - Create scripts/sync-assets.js for idempotent asset distribution Phase 3 — Automated Verification: - Create scripts/landing-audit.js (static landing page validator) - Wire validate-templates.js into CI (validate-registry job) - Expand smoke-test to all 7 UI templates - Add npm cache, concurrency cancel, timeout-minutes to all CI jobs - Run test-combinations on PRs (not just push/main + cron) - Create Playwright visual regression harness + CI workflow - Update docs to reference CI automation Phase 4 — Graceful Degradation: - Fix react-tailwindcss PostCSS plugin (tailwindcss → @tailwindcss/postcss) - Add extension-isolation CI job testing each UI extension individually --- .github/workflows/landing-visual.yml | 81 +++++++ .github/workflows/smoke-test.yml | 104 ++++++++- .github/workflows/test-combinations.yml | 24 +- docs/DEFAULT_LANDING_DESIGN.md | 53 ++--- docs/DEFAULT_LANDING_GUIDE.md | 38 +++- docs/MAINTENANCE_RUNBOOK.md | 2 + extensions/react-tailwindcss/package.json | 1 + .../postcss.config.js.template | 2 +- scripts/landing-audit.js | 211 ++++++++++++++++++ scripts/landing-visual/landing.spec.ts | 36 +++ scripts/landing-visual/package.json | 12 + scripts/landing-visual/playwright.config.ts | 27 +++ scripts/sync-assets.js | 71 ++++++ shared/assets/favicon.svg | 2 +- shared/assets/logo-mark-dark.svg | 8 +- shared/assets/logo-mark.svg | 4 +- shared/assets/logo-wordmark-dark.svg | 40 ++++ shared/assets/logo-wordmark.svg | 10 +- shared/landings/features.ts | 112 ++++++++++ .../docs/COMPONENTS_AND_STYLING.md | 89 ++++++++ templates/astro-starter/docs/PERFORMANCE.md | 43 ++++ .../docs/PROJECT_CONFIGURATION.md.template | 55 +++++ .../docs/PROJECT_STRUCTURE.md.template | 197 ++++++++++++++++ templates/astro-starter/docs/README.md | 24 ++ .../astro-starter/docs/STATE_MANAGEMENT.md | 40 ++++ templates/astro-starter/public/favicon.svg | 2 +- templates/astro-starter/public/logo.svg | 4 +- .../src/components/Welcome.astro | 8 - templates/astro-starter/src/pages/index.astro | 46 ++-- templates/hono-starter/public/favicon.svg | 21 ++ templates/hono-starter/public/logo.svg | 23 ++ templates/nestjs-starter/public/favicon.svg | 21 ++ templates/nestjs-starter/public/logo.svg | 23 ++ .../nextjs-saas-ai-starter/public/favicon.svg | 21 ++ .../nextjs-saas-ai-starter/public/logo.svg | 4 +- .../nextjs-saas-ai-starter/public/next.svg | 1 - .../nextjs-saas-ai-starter/public/vercel.svg | 1 - .../nextjs-starter/[src]/app/favicon.svg | 2 +- .../nextjs-starter/[src]/app/page.module.css | 33 ++- templates/nextjs-starter/[src]/app/page.tsx | 126 ----------- .../[src]/app/page.tsx.template | 37 +-- templates/nextjs-starter/public/favicon.svg | 21 ++ templates/nextjs-starter/public/logo.svg | 4 +- templates/nextjs-starter/public/next.svg | 1 - templates/nextjs-starter/public/vercel.svg | 1 - .../[src]/pages/Landing.css | 5 +- .../[src]/pages/Landing.tsx.template | 36 +-- .../react-vite-starter/public/favicon.svg | 2 +- templates/react-vite-starter/public/logo.svg | 4 +- .../app/routes/_index.tsx.template | 28 +-- .../remix-starter/app/styles/landing.css | 20 +- .../docs/COMPONENTS_AND_STYLING.md | 89 ++++++++ templates/remix-starter/docs/PERFORMANCE.md | 43 ++++ .../docs/PROJECT_CONFIGURATION.md.template | 55 +++++ .../docs/PROJECT_STRUCTURE.md.template | 197 ++++++++++++++++ templates/remix-starter/docs/README.md | 24 ++ .../remix-starter/docs/STATE_MANAGEMENT.md | 40 ++++ templates/remix-starter/public/favicon.svg | 2 +- templates/remix-starter/public/logo.svg | 4 +- .../turborepo-starter/public/favicon.svg | 21 ++ templates/turborepo-starter/public/logo.svg | 23 ++ templates/wdio-starter/public/favicon.svg | 21 ++ templates/wdio-starter/public/logo.svg | 23 ++ .../[src]/assets/img/logo.svg | 4 +- .../[src]/newtab/Newtab.css | 20 +- .../[src]/newtab/Newtab.tsx.template | 28 +-- .../[src]/popup/Popup.css | 2 +- .../[src]/popup/index.css | 29 ++- .../public/favicon.svg | 2 +- .../public/logo.svg | 23 ++ 70 files changed, 2093 insertions(+), 338 deletions(-) create mode 100644 .github/workflows/landing-visual.yml create mode 100644 scripts/landing-audit.js create mode 100644 scripts/landing-visual/landing.spec.ts create mode 100644 scripts/landing-visual/package.json create mode 100644 scripts/landing-visual/playwright.config.ts create mode 100644 scripts/sync-assets.js create mode 100644 shared/assets/logo-wordmark-dark.svg create mode 100644 shared/landings/features.ts create mode 100644 templates/astro-starter/docs/COMPONENTS_AND_STYLING.md create mode 100644 templates/astro-starter/docs/PERFORMANCE.md create mode 100644 templates/astro-starter/docs/PROJECT_CONFIGURATION.md.template create mode 100644 templates/astro-starter/docs/PROJECT_STRUCTURE.md.template create mode 100644 templates/astro-starter/docs/README.md create mode 100644 templates/astro-starter/docs/STATE_MANAGEMENT.md delete mode 100644 templates/astro-starter/src/components/Welcome.astro create mode 100644 templates/hono-starter/public/favicon.svg create mode 100644 templates/hono-starter/public/logo.svg create mode 100644 templates/nestjs-starter/public/favicon.svg create mode 100644 templates/nestjs-starter/public/logo.svg create mode 100644 templates/nextjs-saas-ai-starter/public/favicon.svg delete mode 100644 templates/nextjs-saas-ai-starter/public/next.svg delete mode 100644 templates/nextjs-saas-ai-starter/public/vercel.svg delete mode 100644 templates/nextjs-starter/[src]/app/page.tsx create mode 100644 templates/nextjs-starter/public/favicon.svg delete mode 100644 templates/nextjs-starter/public/next.svg delete mode 100644 templates/nextjs-starter/public/vercel.svg create mode 100644 templates/remix-starter/docs/COMPONENTS_AND_STYLING.md create mode 100644 templates/remix-starter/docs/PERFORMANCE.md create mode 100644 templates/remix-starter/docs/PROJECT_CONFIGURATION.md.template create mode 100644 templates/remix-starter/docs/PROJECT_STRUCTURE.md.template create mode 100644 templates/remix-starter/docs/README.md create mode 100644 templates/remix-starter/docs/STATE_MANAGEMENT.md create mode 100644 templates/turborepo-starter/public/favicon.svg create mode 100644 templates/turborepo-starter/public/logo.svg create mode 100644 templates/wdio-starter/public/favicon.svg create mode 100644 templates/wdio-starter/public/logo.svg create mode 100644 templates/webextension-react-vite-starter/public/logo.svg diff --git a/.github/workflows/landing-visual.yml b/.github/workflows/landing-visual.yml new file mode 100644 index 00000000..5c7fef14 --- /dev/null +++ b/.github/workflows/landing-visual.yml @@ -0,0 +1,81 @@ +name: Landing Visual Regression + +on: + pull_request: + branches: + - main + paths: + - 'shared/assets/**' + - 'templates/**/Landing*' + - 'templates/**/landing*' + - 'templates/**/page.module.css' + - 'templates/**/index.astro' + - 'templates/**/Newtab.*' + - 'templates/**/Popup.*' + - 'scripts/landing-visual/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + visual: + name: Visual regression on ${{ matrix.template }} + strategy: + fail-fast: false + matrix: + template: + - react-vite-starter + - nextjs-starter + - remix-starter + - astro-starter + - webextension-react-vite-starter + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: 'npm' + + - name: Install Playwright + working-directory: scripts/landing-visual + run: | + npm ci + npx playwright install chromium --with-deps + + - name: Scaffold test project + run: | + CI=true npx create-awesome-node-app visual-test-project \ + -t "file://${PWD}?subdir=templates/${{ matrix.template }}" + echo "Project scaffolded" + + - name: Start dev server + working-directory: visual-test-project + run: | + npm install + npm run dev -- --host 0.0.0.0 & + npx wait-on http://localhost:5173 http://localhost:3000 http://localhost:4321 --timeout 30000 2>/dev/null || true + echo "Dev server started" + + - name: Run visual tests + working-directory: scripts/landing-visual + run: | + npx playwright test --project=chromium + env: + BASE_URL: http://localhost:${{ matrix.template == 'nextjs-starter' && '3000' || matrix.template == 'astro-starter' && '4321' || '5173' }} + + - name: Upload screenshots on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: screenshots-${{ matrix.template }} + path: scripts/landing-visual/test-results/ diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index ef1f5916..b04560b5 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -10,23 +10,69 @@ on: - reopened workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read jobs: + validate-registry: + name: Validate template registry + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + - name: Validate templates.json + run: node scripts/validate-templates.js + + landing-audit: + name: Landing page audit + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + - name: Audit landing pages + run: node scripts/landing-audit.js + smoke-test: - name: Smoke test scaffolded projects + name: Smoke test ${{ matrix.template }} + needs: [validate-registry, landing-audit] strategy: fail-fast: false matrix: include: + - template: react-vite-starter + addons: "" + build-env: "" - template: nextjs-starter addons: "" - - template: nestjs-boilerplate + build-env: "" + - template: astro-starter + addons: "" + build-env: "" + - template: remix-starter addons: "" - - template: react-vite-boilerplate - addons: '--addons "file://${PWD}?subdir=extensions/react-material-ui" "file://${PWD}?subdir=extensions/react-zustand"' + build-env: "" + - template: webextension-react-vite-starter + addons: "" + build-env: "" + - template: turborepo-starter + addons: "" + build-env: "" runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -37,6 +83,7 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 + cache: 'npm' - name: Scaffold project run: | @@ -47,6 +94,8 @@ jobs: - name: Run basic checks working-directory: smoke-test-project + env: + SKIP_ENV_VALIDATION: ${{ matrix.build-env == 'skip-env' && 'true' || 'true' }} run: | npm install npm run format --if-present @@ -55,3 +104,50 @@ jobs: npm run type-check --if-present || echo "Type-check warnings found (non-blocking)" SKIP_ENV_VALIDATION=true npm run build --if-present || echo "Build warnings found (non-blocking)" echo "Smoke test passed for ${{ matrix.template }}" + + extension-isolation: + name: Isolate ${{ matrix.combination.template }} + ${{ matrix.combination.extension }} + needs: [validate-registry, landing-audit] + strategy: + fail-fast: false + matrix: + combination: + - template: react-vite-starter + extension: react-tailwindcss + - template: react-vite-starter + extension: react-shadcn + - template: react-vite-starter + extension: react-material-ui + - template: nextjs-starter + extension: nextjs-tailwindcss + - template: nextjs-starter + extension: nextjs-shadcn + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: 22 + cache: 'npm' + + - name: Scaffold with extension + run: | + CI=true npx create-awesome-node-app isolate-test-project \ + -t "file://${PWD}?subdir=templates/${{ matrix.combination.template }}" \ + --addons "file://${PWD}?subdir=extensions/${{ matrix.combination.extension }}" + echo "Project scaffolded with ${{ matrix.combination.extension }}" + + - name: Run basic checks + working-directory: isolate-test-project + run: | + npm install + npm run format --if-present + npm run lint:fix --if-present + npm run lint --if-present || echo "Lint warnings found (non-blocking)" + npm run type-check --if-present || echo "Type-check warnings found (non-blocking)" + SKIP_ENV_VALIDATION=true npm run build --if-present || echo "Build warnings found (non-blocking)" + echo "Isolated test passed for ${{ matrix.combination.template }} + ${{ matrix.combination.extension }}" diff --git a/.github/workflows/test-combinations.yml b/.github/workflows/test-combinations.yml index ce1fd615..6dbb6ff1 100644 --- a/.github/workflows/test-combinations.yml +++ b/.github/workflows/test-combinations.yml @@ -4,10 +4,21 @@ on: push: branches: - main + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened schedule: - - cron: "0 0 * * 0" # Weekly full matrix on Sunday at midnight UTC + - cron: "0 0 * * 0" workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -43,7 +54,6 @@ jobs: })); const extensions = data.extensions; - // Build local URL for each extension using its url path const extLocalUrl = (ext) => { const match = ext.url.match(/\/extensions\/([^/]+)(?:\/|$)/); const dir = match ? match[1] : null; @@ -78,7 +88,6 @@ jobs: if (ext) selectedExtensions.push(ext); } - // Derive local template URL from the url field in templates.json const tplMatch = template.url.match(/\/templates\/([^/]+)(?:\/|$)/); const tplDir = tplMatch ? tplMatch[1] : null; const templateUrl = tplDir ? \`file://${repoDir}?subdir=templates/${tplDir}\` : template.slug; @@ -98,6 +107,7 @@ jobs: matrix: combination: ${{ fromJson(needs.generate-combinations.outputs.matrix) }} runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -108,6 +118,7 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 + cache: 'npm' - name: Run tests for combination env: @@ -138,7 +149,6 @@ jobs: tree -I 'node_modules|dist|.git' - # Full matrix - tests ALL combinations (runs weekly, not on PRs) generate-full-matrix: name: Generate full test matrix if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' @@ -166,7 +176,6 @@ jobs: const extensions = data.extensions; - // Build local URL for each extension using its url path const extLocalUrl = (ext) => { const match = ext.url.match(/\/extensions\/([^/]+)(?:\/|$)/); const dir = match ? match[1] : null; @@ -185,7 +194,6 @@ jobs: return false; }; - // Generate ALL non-conflicting combinations (template x all compatible extensions) const combinations = []; for (const template of data.templates) { @@ -194,7 +202,6 @@ jobs: return extTypes.includes(template.type); }); - // Test with all mutually-compatible extensions for this template const selectedExtensions = []; for (const ext of compatibleExtensions) { if (!hasIncompatibility(selectedExtensions, ext)) { @@ -205,7 +212,6 @@ jobs: if (selectedExtensions.length > 0) { const extUrls = selectedExtensions.map(ext => extLocalUrl(ext)).filter(Boolean); - // Derive local template URL from the url field in templates.json const tplMatch = template.url.match(/\/templates\/([^/]+)(?:\/|$)/); const tplDir = tplMatch ? tplMatch[1] : null; const templateUrl = tplDir ? \`file://${repoDir}?subdir=templates/${tplDir}\` : template.slug; @@ -232,6 +238,7 @@ jobs: matrix: combination: ${{ fromJson(needs.generate-full-matrix.outputs.matrix) }} runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -242,6 +249,7 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 22 + cache: 'npm' - name: Run full test env: diff --git a/docs/DEFAULT_LANDING_DESIGN.md b/docs/DEFAULT_LANDING_DESIGN.md index de6cf756..8636b245 100644 --- a/docs/DEFAULT_LANDING_DESIGN.md +++ b/docs/DEFAULT_LANDING_DESIGN.md @@ -22,10 +22,10 @@ | Token | Hex | Usage | |---|---|---| -| `brand-amber` | `#f59e0b` | Primary accent, highlights, CTAs | -| `brand-amber-dark` | `#d97706` | Hover states, secondary accents | -| `brand-teal` | `#0d9488` | Success, links, secondary accent | -| `brand-teal-soft` | `#14b8a6` | Soft teal surfaces | +| `--cna-amber` | `#f59e0b` | Primary accent, highlights, CTAs | +| `--cna-amber-dark` | `#d97706` | Hover states, secondary accents | +| `--cna-teal` | `#0d9488` | Success, links, secondary accent | +| `--cna-teal-soft` | `#14b8a6` | Soft teal surfaces | ### Neutral colors @@ -104,13 +104,13 @@ font-family: 'JetBrains Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Me ### Shadows ```css ---shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); ---shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); ---shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); ---shadow-glow: 0 0 40px -10px rgba(245, 158, 11, 0.25); +--cna-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); +--cna-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); +--cna-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); +--cna-shadow-glow: 0 0 40px -10px rgba(245, 158, 11, 0.25); ``` -Use `shadow-glow` sparingly on hero containers. +Use `--cna-shadow-glow` sparingly on hero containers. --- @@ -119,7 +119,7 @@ Use `shadow-glow` sparingly on hero containers. ### Entrance animation ```css -@keyframes fade-in-up { +@keyframes cna-fade-in-up { from { opacity: 0; transform: translateY(12px); @@ -134,8 +134,8 @@ Use `shadow-glow` sparingly on hero containers. Apply with staggered delays: ```css -animation: fade-in-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) backwards; -animation-delay: calc(var(--index) * 80ms); +animation: cna-fade-in-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; +animation-delay: calc(var(--index, 0) * 80ms); ``` ### Hover transitions @@ -147,9 +147,9 @@ transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s eas Card hover: ```css -.feature-card:hover { +.cna-landing__card:hover { transform: translateY(-3px); - box-shadow: var(--shadow-lg), var(--shadow-glow); + box-shadow: var(--cna-shadow-lg), var(--cna-shadow-glow); } ``` @@ -208,7 +208,7 @@ Every template landing should follow this layout: | [Primary CTA] [Secondary] | | | +----------------------------------+ -| Feature cards (3-6 items) | +| Feature cards (6 items) | +----------------------------------+ | Docs + next steps | +----------------------------------+ @@ -225,21 +225,22 @@ Each feature card contains: 3. One-sentence description. 4. Optional doc link rendered as a plain link with a label and arrow. -Example cards per template: +Each template has exactly **6 feature cards** with consistent icons and tone. Canonical data is in [`shared/landings/features.ts`](../shared/landings/features.ts). -- **React Vite**: React 19, Vite 8, React Router 7, TypeScript, ESLint + Prettier, feature-based structure. -- **Next.js**: Next.js 16 App Router, TypeScript, feature-based structure, login example, ESLint + Prettier. -- **Remix**: React Router v7, TypeScript, ESLint + Prettier, fast builds. -- **Astro**: Astro 7, static output, partial hydration, TypeScript. +- **React Vite**: Vite, React, React Router v7, TypeScript + ESLint + Prettier, feature-based structure, addon ready. +- **Next.js**: Next.js App Router, React, TypeScript + ESLint + Prettier, feature-based structure, login example, addon ready. +- **Remix**: React Router v7, React, TypeScript, Vite, feature-based structure, addon ready. +- **Astro**: Astro, React-ready, TypeScript, feature-based structure, static output, addon ready. +- **WebExtension**: Browser extension shell, React + Vite, TypeScript, feature-based structure, CNA identity, addon ready. ### Documentation links -Link to the generated docs folder using relative paths: +Link to the generated docs folder using root-absolute paths: -- `docs/README.md` -- `docs/PROJECT_STRUCTURE.md` -- `docs/COMPONENTS_AND_STYLING.md` -- `docs/STATE_MANAGEMENT.md` +- `/docs/README.md` +- `/docs/PROJECT_STRUCTURE.md` +- `/docs/COMPONENTS_AND_STYLING.md` +- `/docs/STATE_MANAGEMENT.md` Use a simple link with a label and an arrow. A shared `DocsLink` helper is optional when the template already has a component library installed. @@ -266,7 +267,7 @@ Use a simple link with a label and an arrow. A shared `DocsLink` helper is optio ### Astro -- Use scoped `