Skip to content

feat(docs): in-app docs module (/docs) + docs-aware SEO (flag-gated, default off) - #4319

Merged
PierreBrisorgueil merged 8 commits into
masterfrom
feat/docs-module-lift
Jun 15, 2026
Merged

PierreBrisorgueil merged 8 commits into
masterfrom
feat/docs-module-lift

Conversation

@PierreBrisorgueil

@PierreBrisorgueil PierreBrisorgueil commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

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 the GET /api/public/docs contract; normalizeTree at 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: when app.seo.docs.enabled+contentUrl set, fetch the content tree → merge docs-derived prerender + sitemap + llms.txt sections onto the existing static app.seo config (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.jsnormalizeTree, guards the exact shape-mismatch class that's bitten before). docs-seo.js has 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

    • Added comprehensive documentation module with searchable guides, API reference viewer, and syntax-highlighted code examples
    • Integrated Algolia DocSearch with client-side fuzzy search fallback
    • Added code example rendering with language tabs and copy-to-clipboard functionality
  • Dependencies

    • Added highlight.js for syntax highlighting

Copilot AI review requested due to automatic review settings June 15, 2026 14:00
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PierreBrisorgueil, we couldn't start this review because you've reached your PR review rate limit.

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 @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 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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0deefd63-2c60-4821-9b36-c65d86276475

📥 Commits

Reviewing files that changed from the base of the PR and between 1c0d5d5 and 5729c7f.

📒 Files selected for processing (18)
  • scripts/generateConfig.js
  • src/lib/helpers/tests/generateConfig.unit.tests.js
  • src/lib/plugins/docs-seo.js
  • src/lib/plugins/tests/docs-seo.unit.tests.js
  • src/modules/docs/components/docs.search.component.vue
  • src/modules/docs/composables/useDocsPage.js
  • src/modules/docs/composables/useDocsReference.js
  • src/modules/docs/config/docs.config.js
  • src/modules/docs/stores/docs.store.js
  • src/modules/docs/tests/docs.article.view.unit.tests.js
  • src/modules/docs/tests/docs.home.view.unit.tests.js
  • src/modules/docs/tests/docs.reference.view.unit.tests.js
  • src/modules/docs/tests/docs.search.component.unit.tests.js
  • src/modules/docs/tests/docs.store.unit.tests.js
  • src/modules/docs/tests/useDocsReference.unit.tests.js
  • src/modules/docs/views/docs.article.view.vue
  • src/modules/docs/views/docs.home.view.vue
  • src/modules/docs/views/docs.reference.view.vue

Walkthrough

Adds a flag-gated in-app docs module (/docs) with a home view, article view, in-theme OpenAPI reference view, sidebar nav, search (Algolia + client-side fuzzy fallback), and syntax-highlighted code blocks. Backed by a Pinia store, axios service, and composables for markdown rendering and spec parsing. Also adds a build-time docs-aware SEO plugin that derives prerender routes, sitemap entries, and llms.txt sections from the docs content tree. Everything defaults to off.

Changes

In-app Docs Module + Docs-aware SEO

Layer / File(s) Summary
Module config, default flags, and dependency
package.json, src/modules/docs/config/docs.config.js, src/modules/docs/config/docs.development.config.js, src/config/defaults/development.config.js, src/config/defaults/test.config.js
Adds highlight.js dependency, a module config object (endpoint key, home/quickstart/persona/search/reference settings), a dev environment re-export, and app.seo.docs + modules.docs default flags set to disabled in both development and test configs.
HTTP service, Pinia store, and data normalization
src/modules/docs/services/docs.service.js, src/modules/docs/stores/docs.store.js
Adds axios wrappers for three docs API endpoints, implements normalizeTree for wire→canonical shape conversion, and a Pinia store with cached tree/articles/spec state, an orderedArticles getter, and fail-soft async fetch actions.
Composables: page rendering, nav routing, and OpenAPI parsing
src/modules/docs/composables/useDocsPage.js, src/modules/docs/composables/useDocsNav.js, src/modules/docs/composables/useDocsReference.js
Implements useDocsPage (marked rendering with heading TOC, fenced-code example extraction, DOMPurify sanitization), resolveDocsTarget/flattenArticles nav helpers, and parseSpec/useDocsReference (OpenAPI 3 spec flattened into tag-grouped endpoint descriptors).
Vue UI components: codeblock, nav sidebar, search, and TOC
src/modules/docs/components/docs.codeblock.component.vue, src/modules/docs/components/docs.nav.component.vue, src/modules/docs/components/docs.search.component.vue, src/modules/docs/components/docs.toc.component.vue
Adds DocsCodeblock (highlight.js + DOMPurify, tabbed examples, clipboard copy with API_KEY_PLACEHOLDER), DocsNav (sticky sidebar with order-sorted categories/articles), DocsSearch (Algolia DocSearch + subsequence fuzzy fallback, Cmd/Ctrl+K shortcut), and DocsToc (sticky right-rail anchor links).
Views, router, barrel, and app router wiring
src/modules/docs/views/docs.home.view.vue, src/modules/docs/views/docs.article.view.vue, src/modules/docs/views/docs.reference.view.vue, src/modules/docs/router/docs.router.js, src/modules/app/app.router.js, src/modules/docs/index.js
Implements DocsHome (persona doors, quickstart hero, category grid), DocsArticle (bodySegments HTML/example hydration, prev/next nav), DocsReference (OpenAPI accordion with guide cross-links), defines three route records, registers the module as activation-gated in the app router, and exports the public barrel.
Build-time docs-aware SEO plugin
src/lib/plugins/docs-seo.js, vite.config.js
Adds augmentSeoConfigWithDocs which, when app.seo.docs.enabled is set, fetches the docs tree at build time and merges derived prerender routes, sitemap entries, and llms.txt sections into config.app.seo; wired into vite.config.js as a fail-soft step before existing SEO plugins.
Unit test suites and fixtures
src/lib/plugins/tests/docs-seo.unit.tests.js, src/modules/docs/tests/*, src/modules/docs/tests/fixtures/docsTree.api.js
Adds a docs tree API fixture and comprehensive unit test suites covering the SEO plugin, Pinia store, HTTP service, all three composables, all four components, all three views, and a config contract test that verifies persona/quickstart targets resolve to real article paths in the normalized tree.

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
Loading
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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related issues

  • #4318: This PR directly implements the feature described in this issue — the flag-gated /docs module with 3-persona home, nav, ToC, in-theme OpenAPI reference, article view, search, and code blocks, plus the docs-seo.js build-time SEO augmenter with fail-soft behavior and the app.seo.docs config flag.

Possibly related PRs

  • pierreb-devkit/Vue#3943: Directly conflicts with this PR — the referenced PR removes the docs module from app.router.js and deletes docs-related config, while this PR re-introduces and fully implements those same integration points.
  • pierreb-devkit/Vue#4274: The docs-seo.js plugin derives llms.txt sections and tests merging them via buildLlmsTxt, which was introduced in this referenced PR.
  • pierreb-devkit/Vue#3874: Both PRs modify src/modules/app/app.router.js to conditionally register docs routes via optionalModules/isModuleActive('docs').

Suggested labels

Feat

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/docs-module-lift

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.js and wires it into vite.config.js to optionally augment appConfig.app.seo at build-time (fail-soft, default off).
  • Extends default configs to keep docs module + docs SEO disabled by default, and adds highlight.js as 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.

Comment thread src/modules/docs/composables/useDocsNav.js Outdated
Comment thread src/lib/plugins/docs-seo.js
Comment thread src/modules/docs/composables/useDocsNav.js
…— 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.
…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).
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.35897% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.56%. Comparing base (9b21450) to head (5729c7f).

Files with missing lines Patch % Lines
src/modules/docs/stores/docs.store.js 98.48% 1 Missing ⚠️
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.
📢 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.

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%.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9b21450 and 1c0d5d5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (35)
  • package.json
  • src/config/defaults/development.config.js
  • src/config/defaults/test.config.js
  • src/lib/plugins/docs-seo.js
  • src/lib/plugins/tests/docs-seo.unit.tests.js
  • src/modules/app/app.router.js
  • src/modules/docs/components/docs.codeblock.component.vue
  • src/modules/docs/components/docs.nav.component.vue
  • src/modules/docs/components/docs.search.component.vue
  • src/modules/docs/components/docs.toc.component.vue
  • src/modules/docs/composables/useDocsNav.js
  • src/modules/docs/composables/useDocsPage.js
  • src/modules/docs/composables/useDocsReference.js
  • src/modules/docs/config/docs.config.js
  • src/modules/docs/config/docs.development.config.js
  • src/modules/docs/index.js
  • src/modules/docs/router/docs.router.js
  • src/modules/docs/services/docs.service.js
  • src/modules/docs/stores/docs.store.js
  • src/modules/docs/tests/docs.codeblock.component.unit.tests.js
  • src/modules/docs/tests/docs.config.contract.unit.tests.js
  • src/modules/docs/tests/docs.home.view.unit.tests.js
  • src/modules/docs/tests/docs.nav.component.unit.tests.js
  • src/modules/docs/tests/docs.reference.view.unit.tests.js
  • src/modules/docs/tests/docs.search.component.unit.tests.js
  • src/modules/docs/tests/docs.service.unit.tests.js
  • src/modules/docs/tests/docs.store.unit.tests.js
  • src/modules/docs/tests/fixtures/docsTree.api.js
  • src/modules/docs/tests/useDocsNav.unit.tests.js
  • src/modules/docs/tests/useDocsPage.unit.tests.js
  • src/modules/docs/tests/useDocsReference.unit.tests.js
  • src/modules/docs/views/docs.article.view.vue
  • src/modules/docs/views/docs.home.view.vue
  • src/modules/docs/views/docs.reference.view.vue
  • vite.config.js

Comment thread src/lib/plugins/docs-seo.js
Comment thread src/lib/plugins/tests/docs-seo.unit.tests.js
Comment thread src/modules/app/app.router.js
Comment thread src/modules/docs/components/docs.search.component.vue Outdated
Comment thread src/modules/docs/composables/useDocsPage.js
Comment thread src/modules/docs/tests/docs.home.view.unit.tests.js
Comment thread src/modules/docs/tests/docs.reference.view.unit.tests.js
Comment thread src/modules/docs/tests/docs.search.component.unit.tests.js
Comment thread src/modules/docs/views/docs.article.view.vue
Comment thread src/modules/docs/views/docs.reference.view.vue
…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)
@PierreBrisorgueil

Copy link
Copy Markdown
Collaborator Author

Addressed CodeRabbit's review (4f428e10): 9 fixed, 2 intentionally left.

Fixed (with tests): stale async article-load race (request-token guard); err?.message guards in all store catch blocks; hasRequestBody now detects $ref request bodies; path/operation parameter merge uses override semantics (not concat); secured now inherits root spec.security when op.security is absent; docs-SEO fail-soft hardened against non-string config; DocSearch host v-ifv-show so the container exists when DocSearch mounts; rel="noopener noreferrer" on target="_blank"; JSDoc headers on the test factories + EXAMPLE_MARKER.

Left (intentional):

  • loading concurrency: the store actions are only ever called sequentially at every call site (await fetchTree(); await fetchSpec()); a counter would add overhead for a scenario that doesn't occur. Documented in a state comment.
  • Direct module import in app.router.js: docs mirrors the established legal optional-module registration pattern exactly ({ name, routes } in optionalModules, gated by isModuleActive). This is the stack's accepted convention.

CI green (Lint/Unit/Build + E2E), coverage held. @coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

@PierreBrisorgueil — thanks for the detailed walkthrough. The 9 fixes look comprehensive, and the rationale for both intentional omissions is solid:

  • loading concurrency: sequential call sites + a state comment is the right trade-off; a counter would be premature complexity.
  • app.router.js import: mirroring the established legal pattern is the correct convention to follow here.

Kicking off a fresh review against 4f428e10.

🐇✨

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…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)
@PierreBrisorgueil
PierreBrisorgueil merged commit 32e3948 into master Jun 15, 2026
6 of 7 checks passed
@PierreBrisorgueil
PierreBrisorgueil deleted the feat/docs-module-lift branch June 15, 2026 17:56
PierreBrisorgueil pushed a commit that referenced this pull request Jul 1, 2026
# [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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Generic in-app docs module (/docs) + docs-aware SEO

2 participants