feat(docs): in-app docs module (/docs) + docs-aware SEO (flag-gated, default off) - #4319
Conversation
|
Warning Review limit reached
More reviews will be available in 9 minutes and 29 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (18)
WalkthroughAdds a flag-gated in-app docs module ( ChangesIn-app Docs Module + Docs-aware SEO
Sequence Diagram(s)sequenceDiagram
participant Browser
participant VueRouter
participant DocsArticle
participant useDocsStore
participant docsService
participant useDocsPage
participant DOMPurify
Browser->>VueRouter: navigate /docs/:category/:slug
VueRouter->>DocsArticle: mount with route params
DocsArticle->>useDocsStore: fetchTree()
useDocsStore->>docsService: getDocsTree()
docsService-->>useDocsStore: { categories }
useDocsStore->>useDocsStore: normalizeTree(raw)
useDocsStore-->>DocsArticle: tree cached
DocsArticle->>useDocsStore: fetchArticle(slug)
useDocsStore->>docsService: getDocArticle(slug)
docsService-->>useDocsStore: markdown string
useDocsStore-->>DocsArticle: markdown cached
DocsArticle->>useDocsPage: useDocsPage(slug, { fetcher: store.fetchArticle })
useDocsPage->>useDocsPage: extractExamples → examples[], stitchedMd
useDocsPage->>useDocsPage: marked.render(stitchedMd) + hljs highlight
useDocsPage->>DOMPurify: sanitize(rawHtml)
DOMPurify-->>useDocsPage: safeHtml
useDocsPage-->>DocsArticle: { title, html, toc, examples }
DocsArticle->>DocsArticle: bodySegments splits html on data-docs-example markers
DocsArticle-->>Browser: prose fragments + DocsCodeblock per example
sequenceDiagram
participant ViteBuild
participant vite_config
participant augmentSeoConfigWithDocs
participant fetchDocsTree
participant ContentAPI
participant SeoPlugins
ViteBuild->>vite_config: load appConfig
vite_config->>augmentSeoConfigWithDocs: augmentSeoConfigWithDocs(appConfig)
augmentSeoConfigWithDocs->>augmentSeoConfigWithDocs: check app.seo.docs.enabled
augmentSeoConfigWithDocs->>fetchDocsTree: fetchDocsTree(contentUrl, { timeout })
fetchDocsTree->>ContentAPI: fetch(contentUrl)
ContentAPI-->>fetchDocsTree: { categories }
fetchDocsTree-->>augmentSeoConfigWithDocs: normalizedTree
augmentSeoConfigWithDocs->>augmentSeoConfigWithDocs: deriveDocsRoutes → prerender paths
augmentSeoConfigWithDocs->>augmentSeoConfigWithDocs: deriveDocsLlmsSections → llms.txt sections
augmentSeoConfigWithDocs-->>vite_config: augmented appConfig (routes + sitemap + llms)
vite_config->>SeoPlugins: defineConfig with augmented appConfig
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels
✨ Finishing Touches🧪 Generate unit tests (beta)
|
dd532ce to
04a40e2
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new, flag-gated in-app Docs module at /docs (home/article/OpenAPI reference/search/codeblocks) and a docs-aware SEO augmentation layer that can (optionally) derive prerender/sitemap/llms.txt entries from a remote docs tree at build time, without failing the build when offline.
Changes:
- Introduces
src/modules/docs/**(routes, views, components, store/service, composables) plus extensive unit tests/fixtures for the API wire shape and markdown/example rendering. - Adds
src/lib/plugins/docs-seo.jsand wires it intovite.config.jsto optionally augmentappConfig.app.seoat build-time (fail-soft, default off). - Extends default configs to keep docs module + docs SEO disabled by default, and adds
highlight.jsas a dependency for consistent code rendering.
Reviewed changes
Copilot reviewed 35 out of 36 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.js | Awaits docs-aware SEO augmentation before instantiating existing SEO plugins. |
| src/modules/docs/views/docs.reference.view.vue | New in-theme OpenAPI reference surface driven by parsed spec + optional guide cross-links. |
| src/modules/docs/views/docs.home.view.vue | New docs landing page (personas, quickstart, job-first category grid, search). |
| src/modules/docs/views/docs.article.view.vue | New markdown article view with ToC and hydrated runnable code blocks. |
| src/modules/docs/tests/useDocsReference.unit.tests.js | Unit tests for OpenAPI spec parsing and fetch wrapper behavior. |
| src/modules/docs/tests/useDocsPage.unit.tests.js | Unit tests for markdown rendering, ToC building, example extraction, and sanitization. |
| src/modules/docs/tests/useDocsNav.unit.tests.js | Unit tests for target resolution + tree flattening helpers. |
| src/modules/docs/tests/fixtures/docsTree.api.js | Real-wire-shape fixture for /api/public/docs tree contract. |
| src/modules/docs/tests/docs.store.unit.tests.js | Unit tests for docs store caching, normalization, and error behavior. |
| src/modules/docs/tests/docs.service.unit.tests.js | Unit tests for docs HTTP service URL composition and slug encoding. |
| src/modules/docs/tests/docs.search.component.unit.tests.js | Unit tests for modal open shortcuts and client-side fuzzy fallback search. |
| src/modules/docs/tests/docs.reference.view.unit.tests.js | Unit tests for reference view rendering, cross-links, and empty states. |
| src/modules/docs/tests/docs.nav.component.unit.tests.js | Unit tests for sidebar nav category/article ordering and empty state. |
| src/modules/docs/tests/docs.home.view.unit.tests.js | Unit tests for home rendering, persona resolution, quickstart rendering, and empty-tree state. |
| src/modules/docs/tests/docs.config.contract.unit.tests.js | Contract test ensuring shipped config targets exist in the normalized real API tree. |
| src/modules/docs/tests/docs.codeblock.component.unit.tests.js | Unit tests ensuring placeholder-only copy behavior and login-gated UI. |
| src/modules/docs/stores/docs.store.js | Docs Pinia store + normalizeTree() I/O-boundary mapper from wire to canonical shape. |
| src/modules/docs/services/docs.service.js | Pure axios-based HTTP service for docs tree, articles, and OpenAPI spec. |
| src/modules/docs/router/docs.router.js | Docs routes (/docs, /docs/api, /docs/:category/:slug) exported as optional module routes. |
| src/modules/docs/index.js | Docs module barrel exports (store/components/composables/services/config/routes). |
| src/modules/docs/config/docs.development.config.js | Development config fragment re-exporting canonical docs config. |
| src/modules/docs/config/docs.config.js | Default docs module config (personas/quickstart/reference/search) with neutral placeholders. |
| src/modules/docs/composables/useDocsReference.js | Pure OpenAPI parsing helpers to produce render-ready tag groups/endpoints. |
| src/modules/docs/composables/useDocsPage.js | Pure markdown render pipeline (marked+DOMPurify) + example extraction markers. |
| src/modules/docs/composables/useDocsNav.js | Pure route-resolution helpers for persona/CTA targets and article flattening. |
| src/modules/docs/components/docs.toc.component.vue | ToC component for h2/h3 anchors collected during markdown render. |
| src/modules/docs/components/docs.search.component.vue | Search component with Algolia DocSearch lazy mount + local fuzzy fallback. |
| src/modules/docs/components/docs.nav.component.vue | Sidebar navigation listing categories/articles from the normalized tree + search trigger. |
| src/modules/docs/components/docs.codeblock.component.vue | Runnable code-block component with language tabs and placeholder-only copy behavior. |
| src/modules/app/app.router.js | Registers docs routes as an optional module (activated=false by default). |
| src/lib/plugins/tests/docs-seo.unit.tests.js | Unit tests covering derivation helpers + fail-soft behavior + OFF-by-default invariants. |
| src/lib/plugins/docs-seo.js | Build-time docs-aware SEO augmentation (fetch tree → derive routes/llms/sitemap; fail-soft). |
| src/config/defaults/test.config.js | Adds app.seo.docs settings (disabled) and disables docs module in test defaults. |
| src/config/defaults/development.config.js | Adds app.seo.docs settings (disabled) and disables docs module in dev defaults. |
| package.json | Adds highlight.js dependency used by docs surfaces. |
| package-lock.json | Locks highlight.js@11.11.1 and updates dependency tree accordingly. |
…— generic stack default Lift the docs module into the stack as a flag-gated optional module (config.modules.docs.activated, off by default). Public guide surface rendered from GET /api/public/docs (tree + per-slug markdown) and an in-theme OpenAPI reference from GET /api/spec.json. - 3 generic persona doors (Developer / Integrator / Operator), generic quickstart snippet (placeholder endpoint + <YOUR_API_KEY>), empty reference tagGuides — all overridable via config.docs.* downstream. - Reuses the marked + DOMPurify pipeline (no second renderer) plus highlight.js for fenced code; preserves the fence-lang attribute encoding XSS hardening. - normalizeTree at the store I/O boundary; a real-shape API fixture feeds the config-contract test so a category/persona-target drift fails in CI, not production. - Copy button copies the static <YOUR_API_KEY> placeholder verbatim (no key fetch, never injected into the DOM). Registered in app.router.js optionalModules (gated by isModuleActive); docs.development.config.js auto-merged by generateConfig. Adds highlight.js as an explicit dependency.
…ee (config-gated, fail-soft)
…nt-disable - docs-seo.js normalizeCategories: flip field-preference to slug ?? id (was id ?? slug) to match docs.store.js normalizeTree; add comment noting the order MUST stay in sync to keep SEO-derived /docs/:cat/:slug paths aligned with runtime router paths (latent contract-drift fix; real-wire behavior unchanged since the wire shape carries id-only) - docs.reference.view.vue: remove dead eslint-disable-next-line vue/no-v-html above <VMarkdown :source=...> — the rule targets v-html directives, not component props; disable was a no-op and misleading - docs.home.view.vue: add inline comment on QUICKSTART_COMMAND warning editors to keep api.example.com as a generic placeholder; downstream overrides supply the real endpoint
…dering F1: resolveDocsTarget returned /docs/:category (no matching route → dead link) for a category with no articles; now returns /docs as the safe fallback. F2: normalizeCategories in docs-seo.js now sorts categories by order then label and guides by order then slug, making prerender/sitemap/llms output deterministic regardless of backend return order. New ordering tests added for both helpers. F3: JSDoc for resolveDocsTarget corrected to internal-router-only (the return value is always passed to Vue Router's `to` prop; no external URL is ever valid).
04a40e2 to
bddd581
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4319 +/- ##
==========================================
- Coverage 99.59% 99.56% -0.03%
==========================================
Files 32 34 +2
Lines 1232 1388 +156
Branches 366 433 +67
==========================================
+ Hits 1227 1382 +155
- Misses 5 6 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Add 9 targeted tests for the non-string/nullish data coercion branch in fetchArticle, the null-data path in fetchSpec, and the three-step error message fallback chain (response.data.message → err.message → 'An error occurred') in both actions. Branch coverage: 71.87% → 81.25%.
There was a problem hiding this comment.
Actionable comments posted: 15
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/plugins/docs-seo.js`:
- Around line 31-35: The normalizeBasePath function assumes the input is a
string when calling .trim(), but if a user provides a non-string value in the
config, this will crash and violate the fail-soft contract. Ensure the basePath
parameter is coerced to a string before applying string methods by wrapping it
with String() conversion (e.g., String(basePath ?? '').trim()). Apply the same
hardening at the second affected location around line 205-207 where similar
string method calls (.replace) are made on a user-configurable value without
type safety.
In `@src/lib/plugins/tests/docs-seo.unit.tests.js`:
- Around line 244-254: Add a JSDoc header above the baseConfig factory function
that documents its purpose, parameters, and return type. The JSDoc should
include a description explaining that the function returns a fresh config object
for testing, an `@param` tag (if any parameters exist; if none, this can be
omitted), and an `@returns` tag describing the returned configuration object
structure. The baseConfig function should be fully documented according to the
coding guidelines requiring all functions to have JSDoc headers.
In `@src/modules/app/app.router.js`:
- Line 20: The app router module directly imports optional modules like docs,
organizations, admin, tasks, billing, and legal on lines 14-20, then
conditionally mounts them in the optionalModules array (lines 121-129) using
isModuleActive(), which violates the core module guideline. Either remove all
direct imports of optional modules from the app router and migrate them to
self-registration via registerDownstreamRoutes mechanism, or update the
project's coding guideline documentation to explicitly permit direct imports for
built-in devkit optional modules (distinguishing them from external downstream
modules that must use registerDownstreamRoutes). Choose one approach and
implement it consistently across all affected modules.
In `@src/modules/docs/components/docs.search.component.vue`:
- Around line 29-33: The DocSearch host element (the div with
ref="docsearchHost") is conditionally rendered only when docsearchReady is
already true, but the mountDocSearch() function requires that host element to
exist in the DOM before it can initialize and set docsearchReady to true. This
creates a circular dependency preventing initialization. Remove the
v-if="docsearchReady" condition from the host element so it always renders in
the DOM, allowing mountDocSearch() to access and initialize it properly. The
fallback template with v-else should then only show when DocSearch is not
available or initialization fails.
In `@src/modules/docs/composables/useDocsPage.js`:
- Around line 13-15: Add a JSDoc comment block above the EXAMPLE_MARKER function
that includes a one-line description explaining what the function does, a `@param`
tag documenting the n parameter (describing what it represents), and a `@returns`
tag describing that it returns a string containing the HTML div element with the
data-docs-example attribute.
In `@src/modules/docs/composables/useDocsReference.js`:
- Around line 95-98: The secured property calculation in the flattenPathItem
function at line 95 only checks operation-level security (op.security) and
ignores the global security requirements defined at the root spec level
(spec.security). Per OpenAPI 3.0 specification, operations inherit security from
spec.security when they don't define their own security property. Update the
secured logic to fall back to spec.security when op.security is undefined by
checking if op.security is explicitly set, and if not, check whether
spec.security exists and has length greater than zero. This same fallback
pattern must be applied to all other secured property calculations in the
parseSpec function (around lines 118–160) where flattenPathItem is called,
ensuring the function receives or has access to the root spec object to properly
resolve inherited security requirements.
- Around line 63-70: The hasRequestBody function in useDocsReference.js
currently only validates request bodies that contain inline content definitions,
but misses OpenAPI 3.x-compliant $ref references. Modify the hasRequestBody
function to return true if either the inline .content property exists with keys
OR if the operation.requestBody contains a $ref property, ensuring both inline
request body definitions and direct $ref references to request body components
are recognized as valid request bodies.
- Around line 81-97: The current code in the useDocsReference composable is
concatenating path-level and operation-level parameters without implementing
OpenAPI override semantics. Instead of simply spreading both sharedParams and
op.parameters into the normalizeParameters call, you need to implement proper
merge logic where operation-level parameters override path-level ones when they
share the same (name, in) combination. Create a merge function that takes
sharedParams and op.parameters, identifies matching parameters by their name and
in properties, and returns a combined array where operation-level parameters
replace any matching path-level ones. Apply this merge function before passing
the result to normalizeParameters.
In `@src/modules/docs/stores/docs.store.js`:
- Around line 113-115: The catch block accesses err.message without optional
chaining, which can cause a secondary crash if a non-Error value is thrown. Add
optional chaining to the err.message access by changing it from err.message to
err?.message in the error assignment statement. This ensures that if err is null
or not an Error object, the fallback to the default error message will be used
instead of attempting to read the message property and throwing an error. Apply
this same fix to all catch blocks that have this pattern.
- Around line 105-119: The loading flag is not concurrency-safe because it is a
single boolean shared across all async operations in the store. When multiple
async requests overlap, one completing request sets loading to false while
another request is still in flight, causing the UI to show incorrect state.
Replace the single boolean loading flag with a counter mechanism: increment the
counter when any async operation (like fetchTree) starts and decrement it when
the operation completes; set loading to true when the counter transitions from 0
to 1, and set loading to false only when the counter reaches 0. This
counter-based approach must be applied consistently across all async methods in
the store (fetchTree, and any other async operations) to ensure accurate loading
state across concurrent requests.
In `@src/modules/docs/tests/docs.home.view.unit.tests.js`:
- Line 32: Add a JSDoc header comment block above the vuetify factory function
to document it according to coding guidelines. The comment should include a
brief description explaining that this is a factory function that creates and
returns a Vuetify instance, and a `@returns` tag indicating it returns a Vuetify
instance. Since the function takes no parameters, no `@param` tags are needed.
In `@src/modules/docs/tests/docs.reference.view.unit.tests.js`:
- Line 27: Add a JSDoc header above the vuetify factory function to comply with
coding guidelines. The JSDoc must include a description explaining that the
function creates and returns a Vuetify instance configured with the provided
components and directives, include a `@returns` tag documenting that it returns a
Vuetify instance, and include `@param` tags as required by the guidelines (noting
that this function takes no parameters).
In `@src/modules/docs/tests/docs.search.component.unit.tests.js`:
- Line 28: Add a JSDoc comment block above the `vuetify` factory function. The
JSDoc header must include a description explaining that this is a factory
function that creates and returns a Vuetify instance configured with components
and directives, a `@param` tag section (which can note that no parameters are
required), and a `@returns` tag specifying that it returns a Vuetify instance.
This ensures the function complies with the coding guidelines requiring JSDoc
headers on all functions.
In `@src/modules/docs/views/docs.article.view.vue`:
- Around line 177-189: The `load` function has a race condition where multiple
concurrent async calls can occur when the slug changes rapidly, and if an older
request resolves after a newer one, it overwrites `page.value` with stale data
for the wrong article. Implement a mechanism to track the current request (such
as an AbortController or a request counter/ID) in the `load` function and the
watch callback to ensure only the most recent request updates `page.value`. This
prevents stale async results from overwriting the current route state.
In `@src/modules/docs/views/docs.reference.view.vue`:
- Around line 19-22: The link element with the redocUrl href attribute has
rel="noopener" but is missing noreferrer to prevent referrer leakage on external
navigation. Update the rel attribute on the link element that uses
target="_blank" to include both noopener and noreferrer by changing
rel="noopener" to rel="noopener noreferrer". Apply the same fix to the other
target="_blank" link in the component that has the same security concern.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2d782a3c-a125-475d-b8dc-39f1127d8f18
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (35)
package.jsonsrc/config/defaults/development.config.jssrc/config/defaults/test.config.jssrc/lib/plugins/docs-seo.jssrc/lib/plugins/tests/docs-seo.unit.tests.jssrc/modules/app/app.router.jssrc/modules/docs/components/docs.codeblock.component.vuesrc/modules/docs/components/docs.nav.component.vuesrc/modules/docs/components/docs.search.component.vuesrc/modules/docs/components/docs.toc.component.vuesrc/modules/docs/composables/useDocsNav.jssrc/modules/docs/composables/useDocsPage.jssrc/modules/docs/composables/useDocsReference.jssrc/modules/docs/config/docs.config.jssrc/modules/docs/config/docs.development.config.jssrc/modules/docs/index.jssrc/modules/docs/router/docs.router.jssrc/modules/docs/services/docs.service.jssrc/modules/docs/stores/docs.store.jssrc/modules/docs/tests/docs.codeblock.component.unit.tests.jssrc/modules/docs/tests/docs.config.contract.unit.tests.jssrc/modules/docs/tests/docs.home.view.unit.tests.jssrc/modules/docs/tests/docs.nav.component.unit.tests.jssrc/modules/docs/tests/docs.reference.view.unit.tests.jssrc/modules/docs/tests/docs.search.component.unit.tests.jssrc/modules/docs/tests/docs.service.unit.tests.jssrc/modules/docs/tests/docs.store.unit.tests.jssrc/modules/docs/tests/fixtures/docsTree.api.jssrc/modules/docs/tests/useDocsNav.unit.tests.jssrc/modules/docs/tests/useDocsPage.unit.tests.jssrc/modules/docs/tests/useDocsReference.unit.tests.jssrc/modules/docs/views/docs.article.view.vuesrc/modules/docs/views/docs.home.view.vuesrc/modules/docs/views/docs.reference.view.vuevite.config.js
…rrer + JSDoc - docs.article.view: token-based race guard discards stale in-flight fetchArticle responses when the slug changes before the earlier load resolves (#1) - docs.store: `err?.message` optional-chain in all three catch blocks prevents a throw when the rejected value is null/non-object (#2); document loading-flag concurrency trade-off with a comment (#3) - useDocsReference: hasRequestBody detects $ref request bodies (#4); mergeParameters implements override semantics (op overrides path on same name+in, no naive concat duplication) (#5); isSecured inherits spec.security when op omits its own security field, explicit [] still disables auth (#6) - docs-seo: normalizeBasePath coerces non-string basePath via String(); guard contentUrl to be a non-empty string before proceeding (#7) - docs.search: v-if → v-show on DocSearch host so the element exists in the DOM before @docsearch/js mounts; paired template updated to v-if="!docsearchReady" (#8) - docs.reference.view: rel="noopener noreferrer" on all target="_blank" links (#9) - useDocsPage: JSDoc @param/@returns on EXAMPLE_MARKER (#11) - test files: JSDoc on vuetify() factories, baseConfig() factory; new article view test suite (race guard + spinner + not-found); store null-error tests; reference $ref body / param-override / global-security tests; seo non-string config tests; search docsearch host v-show DOM presence test (+15 tests, 137→152)
|
Addressed CodeRabbit's review ( Fixed (with tests): stale async article-load race (request-token guard); Left (intentional):
CI green (Lint/Unit/Build + E2E), coverage held. @coderabbitai review |
|
Kicking off a fresh review against 🐇✨ ✅ Action performedReview finished.
|
…ues (closes #4310) Drop the 5-regex key-unquoting pipeline in generateConfig that corrupted any string value containing `:`, `{`, `}`, `,`, or quotes (e.g. a curl snippet or a JSON result block). JSON is a strict subset of JS object-literal syntax — quoted keys are valid in every ESM runtime. Switch to `JSON.stringify(config, null, 2)` so multi-line strings with quote/brace markup round-trip byte-for-byte. Add a vitest round-trip test (generateConfig.unit.tests.js) proving the idiom survives a curl+Authorization+JSON-body command and a nested JSON result string.
….config.js) Move the hardcoded QUICKSTART_COMMAND / QUICKSTART_RESULT consts out of the home view and into docs.config.js (docs.quickstart.command + .result). Now that generateConfig serializes via JSON.stringify (#4310 fixed in the same branch), multi-line strings with quote/brace markup round-trip safely. - docs.config.js: add command/result as NEUTRAL generic placeholders (api.example.com / <YOUR_API_KEY>); downstream overrides supply real snippets - docs.home.view.vue: read command/result from config.docs.quickstart with a small inline fallback (view never renders blank); remove hardcoded consts and the now-moot "serializer can't round-trip" comment; join array-of-lines defensively in resolveSnippet() - docs.home.view.unit.tests.js: add command/result to the config mock; update existing quickstart test to assert config-driven content; add new test proving the rendered terminal shows config-provided strings (not old hardcoded ones)
# [2.2.0](v2.1.0...v2.2.0) (2026-07-01) ### Bug Fixes * **auth:** failed login no longer shows a 'success: Signed out' toast ([#4309](#4309)) ([464e022](464e022)), closes [#4305](#4305) [#4305](#4305) * **billing:** nav compute gauge — admin ∞ display + login refresh ([#4260](#4260), [#4261](#4261)) ([#4268](#4268)) ([2e614db](2e614db)) * **billing:** update VProgressLinear snapshots for Vuetify 4.1.0 ([#4257](#4257)) ([549bb39](549bb39)) * **configGuard:** warn-only by default, throw only on opt-in strict flag ([#4263](#4263)) ([f37050f](f37050f)), closes [#4258](#4258) * **core:** center footer version badge + show backend API version ([#4275](#4275)) ([a17795b](a17795b)) * **docs:** left-align /docs home search hero + uncap block trigger width ([#4323](#4323)) ([b701c3d](b701c3d)) * **e2e:** resolve flaky Playwright webServer boot-race ([#4378](#4378)) ([0fc1886](0fc1886)), closes [#4372](#4372) * **invitations:** inert referrals state when public signup is open ([#4306](#4306)) ([b1cb7d5](b1cb7d5)), closes [pierreb-devkit/Node#3833](pierreb-devkit/Node#3833) * **invitations:** pre-P9 UI hardening — revoked status, admin error banner, canonical verifyInvite ([#4293](#4293)) ([d977558](d977558)), closes [#4291](#4291) * **legal:** patch happy-dom Node.prototype.nodeName for DOMPurify 3.4.8 ([#4240](#4240)) ([#4256](#4256)) ([f0b0e41](f0b0e41)) * **skills/update-stack:** switch drift gate scan to git diff (catches missing-locally case) ([#4244](#4244)) ([32bd2d3](32bd2d3)), closes [#4233](#4233) * **theme:** re-apply OS theme post-hydration when dark='auto' (prerender light-lock) ([#4262](#4262)) ([9aa74cf](9aa74cf)), closes [#4230](#4230) * **ui:** invitations/org surfaces polish — app-level nudge, toolbar slot, card rhythm ([#4301](#4301)) ([d203d84](d203d84)), closes [#toolbar](https://github.com/pierreb-devkit/Vue/issues/toolbar) [#toolbar](https://github.com/pierreb-devkit/Vue/issues/toolbar) * Vue low-severity security hardening bundle (tabnabbing, href scheme, console creds, headers, npm ci, stale key) ([#4316](#4316)) ([9b21450](9b21450)) ### Features * **admin:** activation-aware tabs, readiness badge, activity search, invite copy-link ([#4308](#4308)) ([da419ac](da419ac)), closes [#4295](#4295) [#4297](#4297) [pierreb-devkit/Node#3836](pierreb-devkit/Node#3836) [#4296](#4296) [pierreb-devkit/Node#3834](pierreb-devkit/Node#3834) * **app/router:** add registerDownstreamRoutes extension hook for downstream route injection ([#4242](#4242)) ([54358f0](54358f0)) * **auth:** add beta seat getters to auth store ([#4229](#4229)) ([0560aab](0560aab)) * **auth:** reframe post-signup org-setup step as friendly workspace setup ([#4373](#4373)) ([27ec893](27ec893)) * **billing:** grouped feature sections + plan inheritance on pricing cards ([#4382](#4382)) ([ded622b](ded622b)) * **billing:** surface capacity equivalences on the nav compute gauge ([#4350](#4350)) ([dfeef01](dfeef01)), closes [#4349](#4349) * **configGuard:** block dev-host/port leak into production config ([#4235](#4235)) ([10c3f17](10c3f17)), closes [#949](#949) [pierreb-projects/infra#38](https://github.com/pierreb-projects/infra/issues/38) [#949](#949) [trawl_vue#949](https://github.com/trawl_vue/issues/949) * **core+home+tasks:** promote 4 generic improvements from trawl downstream ([#4239](#4239)) ([a00caa5](a00caa5)) * **docs:** cross-guide #anchor link rewrite in the article renderer ([#4343](#4343)) ([a89331c](a89331c)), closes [#anchor](https://github.com/pierreb-devkit/Vue/issues/anchor) [#4334](#4334) * **docs:** in-app docs module (/docs) + docs-aware SEO (flag-gated, default off) ([#4319](#4319)) ([32e3948](32e3948)), closes [#1](#1) [#2](#2) [#3](#3) [#4](#4) [#5](#5) [#6](#6) [#7](#7) [#8](#8) [#9](#9) [#11](#11) [#4310](#4310) * **docs:** link the OpenAPI reference from the docs nav ([#4330](#4330)) ([1b5c8dd](1b5c8dd)) * **footer:** display app version from DEVKIT_VUE_app_version build-arg ([#4259](#4259)) ([#4267](#4267)) ([14fe314](14fe314)) * **home+auth:** promote external-link safety + org-setup error UX from trawl ([#4237](#4237)) ([8e0be0f](8e0be0f)), closes [pierreb-projects/infra#38](https://github.com/pierreb-projects/infra/issues/38) * **invitations:** referrals summary + rewards placeholder on account view (P8b) ([#4292](#4292)) ([1c0db9d](1c0db9d)), closes [#5](#5) [#4282](#4282) * **invitations:** standalone Vue module + account Referrals tab + router gap-fixes (P6) ([#4289](#4289)) ([4366fa2](4366fa2)) * **organizations:** add-member UI + pending-invitations list + accept (P5b) ([#4288](#4288)) ([96b5b14](96b5b14)), closes [#4281](#4281) * **organizations:** owner_add lifecycle surfaces + signup error detail ([#4307](#4307)) ([e70dbf7](e70dbf7)), closes [pierreb-devkit/Node#3831](pierreb-devkit/Node#3831) [pierreb-devkit/Node#3832](pierreb-devkit/Node#3832) * **seo:** config-driven llms.txt generator in seo-static plugin ([#4274](#4274)) ([e9cedad](e9cedad)), closes [#4269](#4269) * **seo:** per-route self-referential canonical + og:url ([#4338](#4338)) ([94234ff](94234ff)) * **skills/update-stack:** drop ledger condition + auto-derive scan list ([#4232](#4232)) ([7be1220](7be1220)), closes [#4231](#4231) [infra#37](https://github.com/infra/issues/37)
What
src/modules/docs/**— flag-gated (config.modules.docs.activated, default OFF) in-app docs: 3-persona home, job-first nav/ToC, in-theme OpenAPI reference (rendered from/api/spec.json), article view, search (Algolia + client fallback), code blocks (copy = static<YOUR_API_KEY>placeholder). Tree/articles from theGET /api/public/docscontract;normalizeTreeat the store I/O boundary; reuses<VMarkdown>(marked+DOMPurify) — no 2nd renderer. Neutral defaults (generic personas,api.example.com).src/lib/plugins/docs-seo.js— build-time, config-gated, fail-soft: whenapp.seo.docs.enabled+contentUrlset, fetch the content tree → merge docs-derived prerender + sitemap + llms.txt sections onto the existing staticapp.seoconfig (reuses the existing prerender/seo-static machinery). Offline/unreachable → static fallback (never breaks build). OFF by default.Why
Promotes a working docs experience into the stack as a generic default — every consumer can enable
/docs+ docs SEO from its own content + config, rendering the OpenAPI spec in-theme (the bundled Redoc UI is being decommissioned Node-side).Tests
28-file module ported with its tests incl. a real-wire-shape contract test (
fixtures/docsTree.api.js→normalizeTree, guards the exact shape-mismatch class that's bitten before).docs-seo.jshas 23 unit tests incl. explicit fail-soft (offline/timeout/non-2xx/empty → static fallback, no throw) + OFF-by-default. Flag-OFF build verified inert. Coverage held.Notes
Summary by CodeRabbit
Release Notes
New Features
Dependencies
highlight.jsfor syntax highlighting