diff --git a/docs/workflows.md b/docs/workflows.md
index b63f7f7491..78d27a32c0 100644
--- a/docs/workflows.md
+++ b/docs/workflows.md
@@ -28,6 +28,75 @@ OPSX (fluid actions):
> **Customization:** OPSX workflows are driven by schemas that define artifact sequences. See [Customization](customization.md) for details on creating custom schemas.
+## Workflow at a Glance
+
+The default workflow stays fluid: exploration and verification are optional, and
+you can update planning artifacts whenever implementation reveals something new.
+
+```mermaid
+flowchart TD
+ Idea["Idea or problem"] --> Explore["/opsx:explore
(optional)"]
+ Idea --> Propose["/opsx:propose"]
+ Explore --> Propose
+ Propose --> Review{"Planning artifacts
ready?"}
+ Review -->|"Refine"| Update["/opsx:update"]
+ Update --> Review
+ Review -->|"Implement"| Apply["/opsx:apply"]
+ Apply -->|"Plan changed"| Update
+ Apply --> Archive["/opsx:archive"]
+ Apply --> Verify["/opsx:verify
(optional, custom selection)"]
+ Apply --> Sync["/opsx:sync
(optional before archive)"]
+ Verify --> Verified{"Ready to archive?"}
+ Verified -->|"Fix implementation"| Apply
+ Verified -->|"Revise plan"| Update
+ Verified -->|"Ready"| Sync
+ Verified -->|"Ready"| Archive
+ Sync --> Archive
+```
+
+The AI assistant drives the workflow, while the CLI provides deterministic
+scaffolding, status, and artifact instructions:
+
+```mermaid
+sequenceDiagram
+ actor Human
+ participant Assistant as AI assistant
+ participant CLI as OpenSpec CLI
+ participant Files as Planning and implementation files
+
+ Human->>Assistant: /opsx:propose "change"
+ Assistant->>CLI: openspec new change
+ CLI->>Files: Scaffold change metadata
+ Assistant->>CLI: Request status and artifact instructions
+ CLI-->>Assistant: Build order, paths, and templates
+ Assistant->>Files: Write schema-defined planning artifacts
+ Assistant-->>Human: Present artifacts for review
+
+ Human->>Assistant: /opsx:apply
+ Assistant->>CLI: Request apply instructions
+ CLI-->>Assistant: Context files and task state
+ Assistant->>Files: Implement tasks and update checkboxes
+ Assistant-->>Human: Report implementation status
+
+ Human->>Assistant: /opsx:archive
+ Assistant->>CLI: Request archive inputs and artifact status
+ CLI-->>Assistant: Planning paths and artifact completion
+ Assistant->>Files: Read task state and compare delta specs
+ opt Delta specs exist
+ Assistant-->>Human: Offer to sync before archiving
+ alt Sync accepted
+ Human->>Assistant: Confirm sync
+ Assistant->>Files: Merge delta specs into main specs
+ else Sync skipped
+ Human->>Assistant: Archive without syncing
+ end
+ end
+ Assistant->>Files: Move the change into the archive
+ Assistant-->>Human: Report archive location and sync result
+
+ Note over Human,CLI: CLI alternative: openspec archive change-name --yes skips confirmation prompts; it still validates, then applies any delta specs and archives
+```
+
## Two Modes
### Default Quick Path (`core` profile)
diff --git a/website/components/mdx.tsx b/website/components/mdx.tsx
index a407f51f42..d638e730f4 100644
--- a/website/components/mdx.tsx
+++ b/website/components/mdx.tsx
@@ -2,6 +2,7 @@ import defaultMdxComponents from 'fumadocs-ui/mdx';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
+import { Mermaid } from '@/components/mermaid';
import type { MDXComponents } from 'mdx/types';
export function getMDXComponents(components?: MDXComponents) {
@@ -13,6 +14,7 @@ export function getMDXComponents(components?: MDXComponents) {
Steps,
Accordion,
Accordions,
+ Mermaid,
...components,
} satisfies MDXComponents;
}
diff --git a/website/components/mermaid.tsx b/website/components/mermaid.tsx
new file mode 100644
index 0000000000..62cc61ebb4
--- /dev/null
+++ b/website/components/mermaid.tsx
@@ -0,0 +1,37 @@
+import { renderMermaidSVG } from 'beautiful-mermaid';
+import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock';
+
+export function Mermaid({ chart }: { chart: string }) {
+ try {
+ // beautiful-mermaid injects remote font imports; the site already provides Inter.
+ const svg = renderMermaidSVG(chart, {
+ bg: 'var(--color-fd-background)',
+ fg: 'var(--color-fd-foreground)',
+ transparent: true,
+ }).replace(/^\s*@import url\(['"]https:\/\/fonts\.googleapis\.com\/[^)]*\);\s*$/m, '');
+
+ return (
+
+
+ Mermaid diagram source: {chart}
+
+ );
+ } catch {
+ return (
+
+ {chart}
+
+ );
+ }
+}
diff --git a/website/lib/source.ts b/website/lib/source.ts
index a480d10a99..e40147f089 100644
--- a/website/lib/source.ts
+++ b/website/lib/source.ts
@@ -1,4 +1,5 @@
import { docs } from 'collections/server';
+import { renderPlaceholder } from 'fumadocs-core/mdx-plugins/remark-llms.runtime';
import { loader } from 'fumadocs-core/source';
import { icons } from 'lucide-react';
import { createElement } from 'react';
@@ -37,8 +38,17 @@ export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) {
export async function getLLMText(page: (typeof source)['$inferPage']) {
const processed = await page.data.getText('processed');
+ const markdown = await renderPlaceholder(processed, {
+ Mermaid({ attributes }) {
+ if (typeof attributes.chart !== 'string') return '';
+
+ return `\`\`\`mermaid
+${attributes.chart}
+\`\`\``;
+ },
+ });
return `# ${page.data.title} (${page.url})
-${processed}`;
+${markdown}`;
}
diff --git a/website/package.json b/website/package.json
index 15b473faf3..6bc26c4cf6 100644
--- a/website/package.json
+++ b/website/package.json
@@ -12,6 +12,7 @@
},
"dependencies": {
"@orama/orama": "^3.1.18",
+ "beautiful-mermaid": "^1.1.3",
"fumadocs-core": "^16.12.1",
"fumadocs-mdx": "^15.2.1",
"fumadocs-ui": "^16.12.1",
diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml
index f9766b7e0d..53a752ce50 100644
--- a/website/pnpm-lock.yaml
+++ b/website/pnpm-lock.yaml
@@ -16,6 +16,9 @@ importers:
'@orama/orama':
specifier: ^3.1.18
version: 3.1.18
+ beautiful-mermaid:
+ specifier: ^1.1.3
+ version: 1.1.3
fumadocs-core:
specifier: ^16.12.1
version: 16.12.1(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.5)(@types/mdast@4.0.4)(@types/react@19.2.18)(lucide-react@1.27.0(react@19.2.8))(next@16.2.12(@types/node@26.1.2)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(zod@4.4.3)
@@ -1133,6 +1136,9 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ beautiful-mermaid@1.1.3:
+ resolution: {integrity: sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg==}
+
boxen@7.0.0:
resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==}
engines: {node: '>=14.16'}
@@ -1285,6 +1291,9 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ elkjs@0.11.1:
+ resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1299,6 +1308,10 @@ packages:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ entities@7.0.1:
+ resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
+ engines: {node: '>=0.12'}
+
esast-util-from-estree@2.0.0:
resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==}
@@ -3211,6 +3224,11 @@ snapshots:
baseline-browser-mapping@2.11.11: {}
+ beautiful-mermaid@1.1.3:
+ dependencies:
+ elkjs: 0.11.1
+ entities: 7.0.1
+
boxen@7.0.0:
dependencies:
ansi-align: 3.0.1
@@ -3341,6 +3359,8 @@ snapshots:
eastasianwidth@0.2.0: {}
+ elkjs@0.11.1: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -3352,6 +3372,8 @@ snapshots:
entities@6.0.1: {}
+ entities@7.0.1: {}
+
esast-util-from-estree@2.0.0:
dependencies:
'@types/estree-jsx': 1.0.5
diff --git a/website/source.config.ts b/website/source.config.ts
index 628513c667..b3c9a76773 100644
--- a/website/source.config.ts
+++ b/website/source.config.ts
@@ -1,5 +1,6 @@
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
+import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
import { z } from 'zod';
// You can customize Zod schemas for frontmatter and `meta.json` here
@@ -12,7 +13,9 @@ export const docs = defineDocs({
// page" link opens the real source rather than the generated mirror.
schema: pageSchema.extend({ githubSource: z.string().optional() }),
postprocess: {
- includeProcessedMarkdown: true,
+ includeProcessedMarkdown: {
+ mdxAsPlaceholder: ['Mermaid'],
+ },
},
},
meta: {
@@ -22,6 +25,6 @@ export const docs = defineDocs({
export default defineConfig({
mdxOptions: {
- // MDX options
+ remarkPlugins: [remarkMdxMermaid],
},
});