@conduction/docusaurus-preset 3.20.0 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conduction/docusaurus-preset",
3
- "version": "3.20.0",
3
+ "version": "3.21.0",
4
4
  "scripts": {
5
5
  "prepack": "node scripts/prepack-bundle-css.js"
6
6
  },
@@ -0,0 +1,57 @@
1
+ /**
2
+ * <FeaturesPage />
3
+ *
4
+ * Route component used by the `conduction-features-page` plugin. Renders
5
+ * the list of implemented/reviewed capabilities extracted from
6
+ * `openspec/specs/` into `docs/features.json` (regenerated by the
7
+ * org-wide Features Extract workflow stage).
8
+ *
9
+ * Reuses the brand `<FeatureGrid>` for the actual list — every entry maps
10
+ * to a single `<FeatureItem>` with status `stable` (the extractor only
11
+ * emits specs whose frontmatter declares `implemented` or `reviewed`,
12
+ * both of which are stable by definition).
13
+ *
14
+ * Receives one prop module from the plugin via `addRoute({modules})`:
15
+ *
16
+ * data: {
17
+ * features: Array<{slug, title, summary, docsUrl}>,
18
+ * title: string,
19
+ * intro: string | null,
20
+ * }
21
+ *
22
+ * Sites that want a different page chrome can swizzle the plugin route
23
+ * to register their own component; this default is intentionally minimal
24
+ * (one heading, one optional intro paragraph, the grid).
25
+ */
26
+
27
+ import React from 'react';
28
+ import Layout from '@theme/Layout';
29
+ import FeatureGrid from '../FeatureGrid/FeatureGrid.jsx';
30
+
31
+ export default function FeaturesPage({data}) {
32
+ const {features = [], title = 'Features', intro = null} = data || {};
33
+
34
+ const items = features.map((f) => ({
35
+ label: f.title || f.slug,
36
+ tip: f.summary || '',
37
+ status: 'stable',
38
+ href: f.docsUrl || undefined,
39
+ }));
40
+
41
+ return (
42
+ <Layout
43
+ title={title}
44
+ description={intro || `Capabilities shipped in this app, sourced from openspec/specs/.`}
45
+ >
46
+ <main className="container margin-vert--lg">
47
+ <h1>{title}</h1>
48
+ {intro && <p>{intro}</p>}
49
+ {items.length === 0 ? (
50
+ <p>No features documented yet.</p>
51
+ ) : (
52
+ <FeatureGrid items={items} legend />
53
+ )}
54
+ </main>
55
+ </Layout>
56
+ );
57
+ }
@@ -89,6 +89,7 @@ export {default as McpToolShelf} from './McpToolShelf/McpToolShelf.jsx';
89
89
  export {default as ExternalAppShelf} from './ExternalAppShelf/ExternalAppShelf.jsx';
90
90
  export {default as WidgetShelf} from './WidgetShelf/WidgetShelf.jsx';
91
91
  export {default as FeatureGrid, FeatureGridGroup, FeatureItem as FeatureGridItem} from './FeatureGrid/FeatureGrid.jsx';
92
+ export {default as FeaturesPage} from './FeaturesPage/FeaturesPage.jsx';
92
93
 
93
94
  /* Academy components (Batch 4). Card-and-chrome patterns for
94
95
  academy.conduction.nl: a single feed of blogs, guides, case studies,
package/src/index.js CHANGED
@@ -683,6 +683,17 @@ function createConfig(opts) {
683
683
  require.resolve('./plugins/indexnow.js'),
684
684
  opts.indexnow || {},
685
685
  ],
686
+ /* The Features Page plugin registers a `/features` route backed by
687
+ the consuming app's `docs/features.json` (regenerated from
688
+ openspec/specs/ by the org-wide Features Extract workflow stage).
689
+ No-ops when features.json is absent, so it's safe to enable
690
+ across the fleet ahead of full adoption. Sites opt out via
691
+ opts.featuresPage = { disable: true } or override the page
692
+ title / intro / route path. */
693
+ [
694
+ require.resolve('./plugins/features-page.js'),
695
+ opts.featuresPage || {},
696
+ ],
686
697
  ...(opts.plugins || []),
687
698
  ],
688
699
  };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @conduction/docusaurus-preset/plugins/features-page
3
+ *
4
+ * Adds a `/features` route to every Conduction docs site, backed by the
5
+ * `docs/features.json` artefact regenerated from `openspec/specs/` by the
6
+ * org-wide Features Extract workflow stage
7
+ * (https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml).
8
+ *
9
+ * The route renders the brand `<FeaturesPage />` component, which in turn
10
+ * maps each entry to a `<FeatureItem>` inside `<FeatureGrid>`. Every entry
11
+ * is rendered with status `stable` (the extractor only emits specs whose
12
+ * frontmatter declares `status: implemented` or `status: reviewed`, both
13
+ * of which map to the mint hex bullet).
14
+ *
15
+ * Options:
16
+ * path string route path (default '/features')
17
+ * featuresFile string path to features.json (default 'features.json',
18
+ * resolved against `siteDir` — i.e. the directory
19
+ * holding `docusaurus.config.js`)
20
+ * title string page title (default 'Features')
21
+ * intro string optional intro paragraph rendered above the grid
22
+ * disable boolean skip the plugin entirely (the consuming site can
23
+ * keep the entry in `createConfig({featuresPage:
24
+ * {disable: true}})` instead of yanking the plugin)
25
+ *
26
+ * No-op when the resolved `featuresFile` doesn't exist on disk. This
27
+ * keeps the plugin safe to enable across the fleet before every app has
28
+ * adopted the workflow stage that produces the file.
29
+ */
30
+
31
+ const fs = require('fs');
32
+ const path = require('path');
33
+
34
+ function loadFeatures(absPath) {
35
+ try {
36
+ if (!fs.existsSync(absPath)) return null;
37
+ const raw = fs.readFileSync(absPath, 'utf8');
38
+ const parsed = JSON.parse(raw);
39
+ if (!Array.isArray(parsed)) return null;
40
+ return parsed;
41
+ } catch (e) {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function featuresPagePlugin(context, options = {}) {
47
+ if (options.disable === true) return {name: 'conduction-features-page'};
48
+
49
+ const featuresFile = options.featuresFile || 'features.json';
50
+ const absPath = path.isAbsolute(featuresFile)
51
+ ? featuresFile
52
+ : path.resolve(context.siteDir, featuresFile);
53
+
54
+ const features = loadFeatures(absPath);
55
+ if (features === null) {
56
+ /* Plugin is enabled but the consuming site hasn't produced
57
+ features.json yet — most likely the Features Extract workflow
58
+ stage hasn't run, or the app has no openspec/specs/ entries with
59
+ a publishable status. Stay silent rather than build-fail. */
60
+ return {name: 'conduction-features-page'};
61
+ }
62
+
63
+ const routePath = options.path || '/features';
64
+ const title = options.title || 'Features';
65
+ const intro = options.intro || null;
66
+
67
+ return {
68
+ name: 'conduction-features-page',
69
+
70
+ async loadContent() {
71
+ return {features, title, intro};
72
+ },
73
+
74
+ async contentLoaded({content, actions}) {
75
+ const dataPath = await actions.createData(
76
+ 'conduction-features-page.json',
77
+ JSON.stringify(content)
78
+ );
79
+ actions.addRoute({
80
+ path: routePath,
81
+ component: require.resolve('../components/FeaturesPage/FeaturesPage.jsx'),
82
+ exact: true,
83
+ modules: {data: dataPath},
84
+ });
85
+ },
86
+ };
87
+ }
88
+
89
+ module.exports = featuresPagePlugin;