Skip to content

fix(seo-inject): add JSON-LD structured data injection#3670

Merged
PierreBrisorgueil merged 2 commits intomasterfrom
fix/seo-inject-json-ld
Mar 10, 2026
Merged

fix(seo-inject): add JSON-LD structured data injection#3670
PierreBrisorgueil merged 2 commits intomasterfrom
fix/seo-inject-json-ld

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Collaborator

@PierreBrisorgueil PierreBrisorgueil commented Mar 10, 2026

Summary

  • What changed: Added JSON-LD structured data injection to the seo-inject Vite plugin
  • Why: The config system supports seo.schema with enabled, type, name, jobTitle, sameAs, etc., but the plugin had no code to read these values or emit a <script type="application/ld+json"> block
  • Related issues: Closes fix(seo-inject): add JSON-LD structured data injection #3664

Scope

  • Modules impacted: src/lib/plugins/seo-inject.js
  • Cross-module impact: none
  • Risk level: low

Validation

  • npm run lint
  • npm run test:unit
  • npm run build
  • Manual checks done (if applicable)

Guardrails check

  • No secrets or credentials introduced (.env*, secrets/**, keys, tokens)
  • No risky rename/move of core stack paths
  • Changes remain merge-friendly for downstream projects
  • Tests added or updated when behavior changed

Notes for reviewers

  • Security considerations: JSON-LD values are intentionally NOT HTML-escaped since they live inside a JSON string within a <script> tag, not in HTML attributes. The values come from build-time config (not user input).
  • Mergeability considerations: Minimal change — adds a new block after the Twitter Card section with no modifications to existing code.
  • Follow-up tasks: None

Summary by CodeRabbit

Release Notes

  • New Features

    • Added JSON-LD structured data injection to enhance SEO. When schema is enabled, relevant metadata including type, name, URL, and description are automatically injected into the page head.
  • Tests

    • Added comprehensive test suite covering JSON-LD functionality, including validation of required fields and conditional inclusion of optional metadata.

Generate a JSON-LD script tag from seo.schema config when enabled.
Supports Person/Organization types with optional jobTitle, sameAs,
and image fields. Values are not HTML-escaped since they live inside
a JSON string in a script tag.

Closes #3664
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 10, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 43 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35f5b5e2-afd0-435c-b92c-3154ff004e66

📥 Commits

Reviewing files that changed from the base of the PR and between 575f31f and 93a5639.

📒 Files selected for processing (2)
  • src/lib/plugins/seo-inject.js
  • src/lib/plugins/tests/seo-inject.spec.js
📝 Walkthrough

Walkthrough

The seo-inject plugin now generates and injects a <script type="application/ld+json"> tag into the HTML head when seo.schema configuration has enabled set to true and a type is specified. The injected script contains structured data with fields like @context, @type, name, url, and description, plus optional fields like jobTitle, sameAs, and image when available.

Changes

Cohort / File(s) Summary
JSON-LD Injection Implementation
src/lib/plugins/seo-inject.js
Adds JSON-LD structured data generation and injection logic. Builds a jsonLd object when schema.enabled and schema.type are present, conditionally including optional fields (jobTitle, sameAs, image) and pushing a <script type="application/ld+json"> tag to the HTML head.
JSON-LD Test Coverage
src/lib/plugins/tests/seo-inject.spec.js
Adds comprehensive test suite validating JSON-LD injection behavior, including tests for field presence/absence, optional field inclusion when configured, and prevention of injection when schema is disabled or type is missing.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

A rabbit hops through structured light, ✨
JSON-LD now shines oh-so-bright,
Schema data flows with graceful ease,
SEO crawlers surely please! 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding JSON-LD structured data injection to the seo-inject plugin, matching the primary objective of the pull request.
Description check ✅ Passed The description comprehensively covers all template sections with clear explanations of what changed, why, affected modules, risk level, validation steps, and security considerations.
Linked Issues check ✅ Passed The pull request successfully implements all requirements from issue #3664: reads seo.schema config, injects JSON-LD when enabled with proper structure including required fields (context, type, name, url, description) and optional fields (jobTitle, sameAs, image) as specified.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue requirements: the plugin modification adds JSON-LD support and tests verify the implementation without introducing unrelated changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/seo-inject-json-ld

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@PierreBrisorgueil PierreBrisorgueil marked this pull request as ready for review March 10, 2026 07:50
Copilot AI review requested due to automatic review settings March 10, 2026 07:50
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 10, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.16%. Comparing base (0a50388) to head (93a5639).
⚠️ Report is 3 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3670      +/-   ##
==========================================
+ Coverage   91.03%   91.16%   +0.12%     
==========================================
  Files          21       21              
  Lines         636      645       +9     
  Branches      170      176       +6     
==========================================
+ Hits          579      588       +9     
  Misses         57       57              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds build-time JSON-LD structured data injection to the seo-inject Vite plugin so configured app.seo.schema values can be emitted into the built index.html head (addressing missing functionality described in #3664).

Changes:

  • Inject <script type="application/ld+json"> JSON-LD block when seo.schema.enabled and seo.schema.type are set.
  • Add unit tests covering JSON-LD injection, omission rules, and optional fields.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/lib/plugins/seo-inject.js Adds JSON-LD structured data generation/injection into the transformed HTML head.
src/lib/plugins/tests/seo-inject.spec.js Adds unit tests validating JSON-LD injection behavior and optional fields.

Comment thread src/lib/plugins/seo-inject.js
Comment thread src/lib/plugins/tests/seo-inject.spec.js
Address Copilot review: replace < with \u003c in the serialized JSON-LD
string to prevent config values containing </script> from breaking out
of the script tag. Add regression test for this XSS vector.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/plugins/seo-inject.js (1)

119-133: Add JSDoc for the modified transformIndexHtml hook.

This change updates the hook, but it still has no JSDoc for html and the returned HTML string.

📝 Suggested fix
   return {
     name: 'seo-inject',
+    /**
+     * Inject SEO tags and structured data into the HTML document.
+     *
+     * `@param` {string} html - Source `index.html` contents.
+     * `@returns` {string} HTML with injected SEO markup.
+     */
     transformIndexHtml(html) {
       const tags = [];

As per coding guidelines, "Every new or modified function must have a JSDoc header with one-line description, @param for each argument, and @returns for any non-void return value (always include @returns for async functions)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/plugins/seo-inject.js` around lines 119 - 133, Add a JSDoc header to
the modified transformIndexHtml hook in seo-inject.js: include a one-line
description, an `@param` for the html argument (string) and any other parameters
used (e.g., ctx or opts) with types and brief descriptions, and an `@returns`
describing that the function returns a (possibly Promise-wrapped) HTML string;
ensure the JSDoc notes whether the function is async and documents the returned
string.
src/lib/plugins/tests/seo-inject.spec.js (1)

304-326: Add a regression test for the schema.name fallback.

One of the core requirements from #3664 is that name falls back to app.title when schema.name is absent, but this block only exercises the explicit schema.name path.

✅ Suggested test
   describe('JSON-LD structured data', () => {
     it('injects JSON-LD when schema.enabled is true', () => {
       const config = {
         app: {
           title: 'Test App',
           description: 'Test description',
           url: 'https://example.com',
           seo: {
             schema: { enabled: true, type: 'Person', name: 'Test User' },
           },
         },
       };
       const result = transform(config);
       expect(result).toContain('<script type="application/ld+json">');
       const match = result.match(/<script type="application\/ld\+json">(.*?)<\/script>/);
       expect(match).not.toBeNull();
       const jsonLd = JSON.parse(match[1]);
       expect(jsonLd['@context']).toBe('https://schema.org');
       expect(jsonLd['@type']).toBe('Person');
       expect(jsonLd.name).toBe('Test User');
       expect(jsonLd.url).toBe('https://example.com');
       expect(jsonLd.description).toBe('Test description');
     });
+
+    it('falls back to app.title when schema.name is missing', () => {
+      const config = {
+        app: {
+          title: 'Test App',
+          description: 'Test description',
+          url: 'https://example.com',
+          seo: {
+            schema: { enabled: true, type: 'Person' },
+          },
+        },
+      };
+      const result = transform(config);
+      const match = result.match(/<script type="application\/ld\+json">(.*?)<\/script>/);
+      const jsonLd = JSON.parse(match[1]);
+      expect(jsonLd.name).toBe('Test App');
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/plugins/tests/seo-inject.spec.js` around lines 304 - 326, Add a
regression test that verifies schema.name falls back to app.title when
schema.name is missing: create a new it-block (next to the existing JSON-LD
test) that builds a config with app.title/app.description/app.url and
seo.schema: { enabled: true, type: 'Person' } but without schema.name, call
transform(config), extract the JSON-LD script the same way as the existing test,
JSON.parse it and assert jsonLd.name === app.title (and keep assertions for
`@context`, `@type`, url and description to ensure all other fields remain correct);
use the same helper/regex and the transform function reference so the test
mirrors the original but verifies the fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/plugins/seo-inject.js`:
- Around line 122-132: The JSON-LD payload built in jsonLd and injected via
tags.push(`  <script
type="application/ld+json">${JSON.stringify(jsonLd)}</script>`) can be
terminated by unescaped values (e.g., schema.name or app.description); change to
a script-safe serialization: produce the JSON string from jsonLd (using
JSON.stringify) then escape any occurrences that would break a script tag (for
example replace the sequence "</script" with "<\/script>" and optionally escape
"<!--" / "-->"), and use that escaped string in the tags.push call (or factor
into a helper like safeJsonStringify/jsonLdToScript to centralize the escaping).

---

Nitpick comments:
In `@src/lib/plugins/seo-inject.js`:
- Around line 119-133: Add a JSDoc header to the modified transformIndexHtml
hook in seo-inject.js: include a one-line description, an `@param` for the html
argument (string) and any other parameters used (e.g., ctx or opts) with types
and brief descriptions, and an `@returns` describing that the function returns a
(possibly Promise-wrapped) HTML string; ensure the JSDoc notes whether the
function is async and documents the returned string.

In `@src/lib/plugins/tests/seo-inject.spec.js`:
- Around line 304-326: Add a regression test that verifies schema.name falls
back to app.title when schema.name is missing: create a new it-block (next to
the existing JSON-LD test) that builds a config with
app.title/app.description/app.url and seo.schema: { enabled: true, type:
'Person' } but without schema.name, call transform(config), extract the JSON-LD
script the same way as the existing test, JSON.parse it and assert jsonLd.name
=== app.title (and keep assertions for `@context`, `@type`, url and description to
ensure all other fields remain correct); use the same helper/regex and the
transform function reference so the test mirrors the original but verifies the
fallback behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3586985d-9e79-4d6e-869d-5cc28af12735

📥 Commits

Reviewing files that changed from the base of the PR and between 0a50388 and 575f31f.

📒 Files selected for processing (2)
  • src/lib/plugins/seo-inject.js
  • src/lib/plugins/tests/seo-inject.spec.js

Comment thread src/lib/plugins/seo-inject.js Outdated
@PierreBrisorgueil PierreBrisorgueil merged commit 5b36e5c into master Mar 10, 2026
5 checks passed
PierreBrisorgueil pushed a commit that referenced this pull request Apr 1, 2026
# [1.5.0](v1.4.0...v1.5.0) (2026-04-01)

### Bug Fixes

* **analytics:** PostHog api_host bug + identify/group on login ([#3772](#3772)) ([534fdf3](534fdf3)), closes [#3753](#3753) [#3766](#3766) [#3769](#3769) [#3771](#3771)
* **auth,admin:** move mailer warning to admin page, fix verify-email routing, align invite button ([0d195c4](0d195c4))
* **auth:** add missing $route.query mock in verifyEmail tests ([b2732f6](b2732f6))
* **auth:** address CodeRabbit review feedback ([1d14b06](1d14b06))
* **auth:** address review feedback from pass 1 ([bd27d2f](bd27d2f))
* **auth:** address review feedback from pass 1 ([a87fdb1](a87fdb1))
* **auth:** address review feedback from pass 2 — prevent form flash on load ([a6afeca](a6afeca))
* **auth:** fix runtime deprecation warnings and add auth view unit tests ([1901dad](1901dad)), closes [#3592](#3592)
* **auth:** persist snackbar dismissal on implicit close ([5e464c3](5e464c3))
* **auth:** resolve lint errors and update test mocks for fetchServerConfig ([a642001](a642001))
* **billing:** add store unit tests and fix view import paths ([e370272](e370272))
* **billing:** address CodeRabbit review — guards, tests, JSDoc, price fallback ([f0f6657](f0f6657))
* **billing:** address CodeRabbit review feedback ([b195d2d](b195d2d))
* **billing:** address CodeRabbit review feedback ([8435d1c](8435d1c))
* **billing:** address CodeRabbit review feedback ([4187771](4187771))
* **billing:** address review — tier comparison, graceful failure, tests, naming ([7b00b91](7b00b91))
* **billing:** address review feedback — pricing card, fetch guard, router tests ([34cbda9](34cbda9))
* **billing:** address UI review findings ([b281a8a](b281a8a))
* **billing:** address UI review findings ([ead3eb0](ead3eb0))
* **billing:** address UI review findings ([b2d3897](b2d3897))
* **billing:** address UI review findings ([a756cd9](a756cd9))
* **billing:** align CASL guard subject with backend policy ([7e8287b](7e8287b))
* **billing:** align CASL guard subject with backend policy ([8e9b768](8e9b768))
* **billing:** correct plan field name, add enterprise badge, store cleanup ([#3733](#3733)) ([9b6e30d](9b6e30d)), closes [#3729](#3729) [#3730](#3730) [#3731](#3731) [#3732](#3732) [#3729](#3729) [#3730](#3730) [#3731](#3731) [#3732](#3732)
* **billing:** correct test URL assertions to match store checkout URLs ([cd64628](cd64628))
* **billing:** correct view import paths to match lowercase filenames ([e2646dd](e2646dd))
* **billing:** default free plan display + E2E security tests ([#3751](#3751)) ([025534a](025534a))
* **billing:** fix branch coverage and E2E auth guard test ([2864cf7](2864cf7))
* **billing:** fix branch coverage and harden E2E auth test ([7b91edb](7b91edb))
* **billing:** fix error-handling tests and restore CASL guard on /billing route ([bcb15a6](bcb15a6))
* **billing:** fix plan merge — match by planId/name and build price objects ([1c1c765](1c1c765))
* **billing:** gate fetchSubscription behind org check and add missing [@returns](https://github.com/returns) JSDoc ([9dd15a7](9dd15a7))
* **billing:** harden URL validation, test assertions, and error handling ([44d9cc2](44d9cc2))
* **billing:** include canceled query param in checkout cancel URL ([9e28636](9e28636))
* **billing:** propagate errors, add CASL route metadata, rename views ([39343ba](39343ba))
* **billing:** remove duplicate import in E2E test ([02576f5](02576f5))
* **billing:** rename views, fix test assertions, address review feedback ([cb72130](cb72130))
* **billing:** separate checkout error from fetch error in pricing view ([78db8b1](78db8b1))
* **billing:** update store cancel URL to include canceled query param ([ca5f1a3](ca5f1a3))
* **billing:** validate portal URL and catch fetchSubscription rejection ([7534b6c](7534b6c))
* **ci:** ensure coverage thresholds pass with all modules ([8e0c970](8e0c970)), closes [#3710](#3710)
* **ci:** remove ARC-specific conditionals from CI.yml ([#3806](#3806)) ([48747f3](48747f3)), closes [#3805](#3805)
* **config:** add missing api section to config.test.js ([#3679](#3679)) ([fff9ffc](fff9ffc)), closes [#3676](#3676)
* **config:** address review — use lodash-es, drop glob dep, add env warning ([1ec84fa](1ec84fa))
* **config:** address review feedback from pass 1 ([3336f09](3336f09))
* **config:** align app_title default between Dockerfile and hooks/build ([b08dbf3](b08dbf3))
* **config:** backward-compat fallback for sucessColor and improve warning ([1965557](1965557))
* **config:** fail fast when config is missing in production builds ([#3671](#3671)) ([c660ba4](c660ba4)), closes [#3669](#3669)
* **config:** generateConfig ignores module env-specific configs ([#3827](#3827)) ([1ffd80e](1ffd80e)), closes [#3826](#3826)
* **config:** remove [@desc](https://github.com/desc) tag from deepMerge JSDoc ([7c4b613](7c4b613))
* **config:** remove duplicate vuetify.theme block from app config merge ([5a583a7](5a583a7))
* **config:** remove unused imports and fix typo in generateConfig ([7b0c006](7b0c006))
* **config:** replace _.merge with deepMerge to fix array handling ([4e91025](4e91025)), closes [#3628](#3628)
* **config:** skip undefined values and guard against prototype pollution in deepMerge ([ffc574e](ffc574e))
* **config:** use pathToFileURL for cross-platform dynamic imports ([acad44e](acad44e))
* **core:** address review feedback from pass 1 ([0f6e3af](0f6e3af))
* **core:** make datatable generic via fetchAction prop ([e3a4926](e3a4926)), closes [#3596](#3596)
* **coverage:** exclude bootstrapper files with 0% coverage from collection ([4dfde5a](4dfde5a))
* **datatable:** increase per page select width to 100px ([#3779](#3779)) ([ff65d6f](ff65d6f)), closes [#3778](#3778)
* **docker:** add DEVKIT_NODE_api_port to docker-compose.test.yml ([#3815](#3815)) ([0f06402](0f06402)), closes [#3809](#3809)
* **docker:** make docker-compose.test.yml work for downstream projects ([#3790](#3790)) ([838aec1](838aec1)), closes [#3789](#3789)
* **docs:** add src/ prefix and use <env> placeholder in merge order table ([5af5d5b](5af5d5b))
* **docs:** mark global env override config files as optional ([7459d0f](7459d0f))
* **e2e:** read ports from project config instead of hardcoding ([#3777](#3777)) ([ec56d20](ec56d20)), closes [#3775](#3775)
* **header:** address review feedback from pass 1 ([b05d210](b05d210))
* **home,config:** address review feedback from pass 1 ([709ccbe](709ccbe))
* **home:** add JSDoc for tabs colorMode validator and tabStyle method ([a7fb5e3](a7fb5e3))
* **home:** add JSDoc to created() hook in home.team.view ([7548c15](7548c15))
* **home:** address CodeRabbit review feedback ([9990286](9990286))
* **home:** hero vertical centering, overlap prop, and Vuetify 4 typography migration ([4c26b25](4c26b25)), closes [#3587](#3587)
* **home:** pass colorMode to tabs component so forced text color applies to tab items ([50256d8](50256d8))
* **lodash:** use lodash-es imports for proper tree-shaking ([50570cf](50570cf))
* **nav:** fix organizations ghost item icon in sidenav ([bca4237](bca4237)), closes [#3706](#3706)
* **pull-request skill:** add consecutive_zero guard and re-check pending review checks ([18c70f8](18c70f8))
* **pull-request skill:** address review feedback — clarity and consistency ([2dd1ce2](2dd1ce2))
* **pull-request skill:** reply and resolve all threads including non-actionable ([ff776c8](ff776c8))
* **security:** harden auth views against XSS and weak validation ([#3735](#3735)) ([c3b844d](c3b844d))
* **seo-inject:** add JSON-LD structured data injection ([#3670](#3670)) ([5b36e5c](5b36e5c)), closes [#3664](#3664)
* **seo:** improve heading hierarchy and alt text for accessibility ([#3661](#3661)) ([918f9bd](918f9bd))
* **seo:** puppeteer prerender Docker support + minor plugin fixes ([#3673](#3673)) ([8ac241b](8ac241b))
* **seo:** skip runtime JSON-LD when seo-inject handles schema ([#3677](#3677)) ([#3680](#3680)) ([b0d90ac](b0d90ac))
* **skill:** add merge conflict check in pull-request convergence loop ([ef136ed](ef136ed)), closes [#3707](#3707)
* **skill:** address review feedback from pass 1 ([4020b82](4020b82))
* **skill:** handle CHANGES_REQUESTED, UNKNOWN mergeable, and standardize $PR usage ([13cb3cf](13cb3cf))
* **skill:** pull-request skill should ignore stack-level CodeRabbit comments for downstream projects ([97d2cb2](97d2cb2)), closes [#3604](#3604)
* **skills:** clarify feature Phase 0 completeness check ([#3773](#3773)) ([8ac6b1b](8ac6b1b))
* **skills:** match coderabbitai[bot] login in monitoring jq filter ([aae850f](aae850f))
* **update-stack:** add concrete gh issue create command and resolution guidance ([6092368](6092368))
* **update-stack:** add downstream-only new files rule to conflict table ([4a15e39](4a15e39))
* **update-stack:** address review feedback from pass 1 ([f7ed820](f7ed820))
* **update-stack:** address review feedback from pass 2 ([2bef8a3](2bef8a3))
* **update-stack:** clarify failure origin criteria in step 3bis ([a71515e](a71515e))

### Features

* add /frontend design skill + migrate Vuetify 3 patterns to V4 ([#3644](#3644)) ([2f2a8ff](2f2a8ff))
* **analytics:** add useFeatureFlag and usePostHog composables ([#3774](#3774)) ([2506a4f](2506a4f)), closes [#3771](#3771)
* **auth:** add email verification gate UI for org setup ([999aa60](999aa60))
* **auth:** add password visibility toggle ([#3752](#3752)) ([ea3b0a0](ea3b0a0)), closes [#336](#336)
* **auth:** display server-side auth status on signin/signup pages ([0f4276d](0f4276d))
* **billing:** add billing page with plan badge and subscription management ([e9a1fc5](e9a1fc5)), closes [#3715](#3715)
* **billing:** add BillingUsageBar component and enhance UpgradePrompt ([#3744](#3744)) ([0e9ca7f](0e9ca7f))
* **billing:** add checkout flow with auth/org guards and Stripe redirect ([ce17d67](ce17d67)), closes [#3714](#3714)
* **billing:** add checkout flow with auth/org guards and Stripe redirect ([f762d91](f762d91)), closes [#3714](#3714)
* **billing:** add feature gates — composable, upgrade prompt, router guard ([207c022](207c022)), closes [#3716](#3716)
* **billing:** add homepage pricing section ([a8ced6d](a8ced6d)), closes [#3717](#3717)
* **billing:** add pricing page with plan cards and billing toggle ([abb1d4a](abb1d4a)), closes [#3713](#3713)
* **billing:** add pricing page with plan cards and billing toggle ([0881a9f](0881a9f)), closes [#3713](#3713)
* **billing:** add useQuota composable for plan-based feature gating ([#3743](#3743)) ([85e2984](85e2984))
* **billing:** move pricing to dedicated page, update topnav ([eaab6f3](eaab6f3))
* **billing:** scaffold billing module with store, router, and views ([7b08b12](7b08b12)), closes [#3712](#3712)
* **ci:** skip setup-node and playwright install on self-hosted runner ([#3803](#3803)) ([104d4f8](104d4f8)), closes [#3802](#3802)
* **ci:** support configurable runner via RUNNER variable ([#3794](#3794)) ([1151d02](1151d02)), closes [#3792](#3792)
* **ci:** use APP_ENV variable for generateConfig in CI ([#3801](#3801)) ([e384c7c](e384c7c)), closes [#3795](#3795)
* **config:** migrate WAOS_VUE_* env prefix to DEVKIT_VUE_* ([7ad1a7f](7ad1a7f)), closes [#3614](#3614)
* **header:** add float scroll behavior and document all config options ([d1d4891](d1d4891))
* **home:** add dedicated 404 Not Found page ([#3655](#3655)) ([ac73739](ac73739))
* **home:** add FAQ accordion component with JSON-LD schema ([#3821](#3821)) ([444e62c](444e62c)), closes [#3819](#3819)
* **nav:** liquid glass sidenav with Apple-style inset mode ([#3737](#3737)) ([8203f2c](8203f2c))
* **pageHeader:** add tabs slot as alternative to icon + title ([#3781](#3781)) ([763d814](763d814)), closes [#3780](#3780)
* phase 2 organizations ([#3702](#3702)) ([71c6a5c](71c6a5c)), closes [#3674](#3674) [#3675](#3675) [#3684](#3684) [#3686](#3686) [#3681](#3681) [#3682](#3682) [#3683](#3683) [#3684](#3684) [#3685](#3685) [#3686](#3686) [#3675](#3675) [#3674](#3674)
* rename /frontend to /ui, add workflow rules ([#3704](#3704)) ([3d8259e](3d8259e))
* **sentry:** add Sentry error tracking and ErrorBoundary component ([#3788](#3788)) ([b5463d7](b5463d7)), closes [#3787](#3787)
* **seo-inject:** add noscript fallback, preconnect hints, and theme-color ([#3663](#3663)) ([261a3a8](261a3a8))
* **seo:** add pre-rendering of home page at build time ([#3660](#3660)) ([39d9414](39d9414))
* **seo:** add seo-static Vite plugin for robots.txt, sitemap.xml, manifest.json ([#3658](#3658)) ([0a50388](0a50388))
* **update-stack:** report upstream issues when verify fails on stack code ([71dbecc](71dbecc))
* **verify:** add coverage enforcement to verify skill ([#3776](#3776)) ([63301f9](63301f9))

### Performance Improvements

* **ci:** optimize GitHub Actions ([#3793](#3793)) ([e31287e](e31287e)), closes [#3791](#3791)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(seo-inject): add JSON-LD structured data injection

2 participants