@cyber-dash-tech/revela 0.17.4 → 0.17.6

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.
@@ -481,6 +481,8 @@ These rules are mandatory for Monet.
481
481
  - **No glass cards, neon KPI styling, or startup-product chrome.** Monet is editorial and print-adjacent.
482
482
  - **Visual hierarchy is strict:** eyebrow -> heading -> body -> caption.
483
483
  - **Icon system is Lucide.** For ordinary UI, semantic, status, category, process, and navigation icons, use Lucide (`data-lucide`). Do not hand-write inline SVG for icons. SVG is allowed only for intentional decorative motifs, illustrations, or design-specific artwork. If any `data-lucide` icon is present, load Lucide via CDN and call `lucide.createIcons()` after `SlidePresentation`.
484
+ - **Chart system is ECharts.** Data charts default to ECharts inside `echart-panel`. Do not use hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts as data-chart substitutes. SVG remains acceptable for decorative motifs, diagrams, or illustrations, not data charts. Before creating or changing a chart, fetch the `echart-panel` component and `section: "chart-rules"`; if chart rules or runtime are unavailable, report the gap instead of inventing a fake chart fallback.
485
+ - **Start from foundation.** New deck HTML starts from `@design:foundation`. Do not recreate foundation CSS, JavaScript, or the HTML skeleton from memory. Prefer a foundation helper when available; otherwise fetch `section: "foundation"` before writing a new deck shell. Existing deck edits preserve the current foundation unless the user asks for foundation repair or QA reports a foundation contract problem.
484
486
 
485
487
  ### Common Mistakes
486
488
 
@@ -2805,3 +2807,30 @@ Rules:
2805
2807
  <!-- @component:roadmap-vertical:end -->
2806
2808
 
2807
2809
  <!-- @design:components:end -->
2810
+
2811
+ <!-- @design:chart-rules:start -->
2812
+
2813
+ ### Data Visualization (ECharts)
2814
+
2815
+ - Chart system is ECharts. Data charts should use `echarts.init()` with an `echart-panel` container, not hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts.
2816
+ - Monet charts should feel calm and art-book-like: soft contrast, restrained labels, limited series count, and no dashboard chrome, glow, or excessive gridlines.
2817
+ - Use `--accent-earth`, `--accent-olive`, `--accent-stone`, and `--accent-sage` for primary/supporting series. Use `--accent-danger` only for negative indicators. Derive colors from CSS variables with `getComputedStyle(document.documentElement)` instead of hard-coding unrelated palettes.
2818
+ - Keep chart containers inside `echart-panel` so QA can measure stable geometry. `.echart-container` must have stable sizing through explicit width/height or flex sizing with `min-height: 0`.
2819
+ - Give every chart a unique id. Initialise with `echarts.init()` after `SlidePresentation` is instantiated, and call `chart.resize()` on window resize.
2820
+ - Set `backgroundColor: "transparent"` in chart options. Set text, axis, legend, grid, and tooltip colors explicitly; ECharts canvas text does not inherit CSS reliably.
2821
+ - Always include a short chart caption or source note when data is shown.
2822
+ - Do not use fake chart fallback when ECharts runtime or chart rules are missing. Report the missing runtime/rules or use an approved local/runtime dependency.
2823
+
2824
+ Recommended ECharts defaults:
2825
+
2826
+ ```javascript
2827
+ const styles = getComputedStyle(document.documentElement);
2828
+ const chartText = styles.getPropertyValue('--text-secondary').trim();
2829
+ const chartMuted = styles.getPropertyValue('--text-muted').trim();
2830
+ const chartLine = styles.getPropertyValue('--line').trim();
2831
+ const primary = styles.getPropertyValue('--accent-earth').trim();
2832
+ const secondary = styles.getPropertyValue('--accent-olive').trim();
2833
+ const soft = styles.getPropertyValue('--accent-sage').trim();
2834
+ ```
2835
+
2836
+ <!-- @design:chart-rules:end -->
@@ -234,6 +234,8 @@ new SlidePresentation();
234
234
  - **Reusable class vocabulary.** New classes must be documented in this DESIGN.md. Avoid many one-off selectors in generated decks.
235
235
  - **SVG is exceptional.** Use decorative SVG only when the user explicitly asks for an illustration/icon-like visual or when design authoring requires a motif.
236
236
  - **Icon system is Lucide.** For ordinary UI, semantic, status, category, process, and navigation icons, use Lucide (`data-lucide`). Do not hand-write inline SVG for icons. SVG is allowed only for intentional decorative motifs, illustrations, or design-specific artwork. If any `data-lucide` icon is present, load Lucide via CDN and call `lucide.createIcons()` after `SlidePresentation`.
237
+ - **Chart system is ECharts.** Data charts default to ECharts inside `echart-panel`. Do not use hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts as data-chart substitutes. SVG remains acceptable for decorative motifs, diagrams, or illustrations, not data charts. Before creating or changing a chart, fetch the `echart-panel` component and `section: "chart-rules"`; if chart rules or runtime are unavailable, report the gap instead of inventing a fake chart fallback.
238
+ - **Start from foundation.** New deck HTML starts from `@design:foundation`. Do not recreate foundation CSS, JavaScript, or the HTML skeleton from memory. Prefer a foundation helper when available; otherwise fetch `section: "foundation"` before writing a new deck shell. Existing deck edits preserve the current foundation unless the user asks for foundation repair or QA reports a foundation contract problem.
237
239
  - **Images for photographic references.** Use image treatment rules rather than fake SVG when the reference is photographic, UI, webpage, or product imagery.
238
240
  - **Content pages need a stable title block.** Except cover, TOC, closing, section divider, and full-bleed hero slides, every normal content slide should include a visible title block from the upper-left safe area. It should contain a compact chapter/section label plus a slide title written as the page's claim or takeaway.
239
241
  - **Do not hide the page title inside a card.** Body components may have their own headings, but the slide-level title block should remain separate and easy to scan unless the chosen layout explicitly defines a compact side-title variant.
@@ -881,11 +883,14 @@ Rules:
881
883
 
882
884
  ### Data Visualization (ECharts)
883
885
 
884
- - Use neutral chart styling by default: clean axes, limited series count, and restrained labels.
885
- - Use `--accent-primary` for the main series and `--accent-secondary` for supporting series.
886
- - Avoid dashboard chrome, glowing charts, and excessive gridlines.
886
+ - Chart system is ECharts. Data charts should use `echarts.init()` with an `echart-panel` container, not hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts.
887
+ - Use neutral chart styling by default: clean axes, limited series count, restrained labels, and transparent backgrounds.
888
+ - Use `--accent-primary` for the main series and `--accent-secondary` for supporting series. Derive colors from CSS variables with `getComputedStyle(document.documentElement)` instead of hard-coding unrelated palettes.
889
+ - Keep chart containers inside `echart-panel` so QA can measure stable geometry. `.echart-container` must have stable sizing through explicit width/height or flex sizing with `min-height: 0`.
890
+ - Give every chart a unique id. Initialise with `echarts.init()` after `SlidePresentation` is instantiated, and call `chart.resize()` on window resize.
891
+ - Set `backgroundColor: "transparent"` in chart options. Set text, axis, legend, grid, and tooltip colors explicitly; ECharts canvas text does not inherit CSS reliably.
887
892
  - Always include a short chart caption or source note when data is shown.
888
- - Keep chart containers inside `echart-panel` so QA can measure stable geometry.
893
+ - Do not use fake chart fallback when ECharts runtime or chart rules are missing. Report the missing runtime/rules or use an approved local/runtime dependency.
889
894
 
890
895
  Recommended ECharts defaults:
891
896
 
@@ -444,6 +444,8 @@ These rules are mandatory for Summit.
444
444
  - **Titles are Title Case.** Do not set `text-transform:uppercase` on `h1`, `h2`, `h3`, or `h4` titles. Uppercase is reserved for eyebrows, captions, metadata labels, short codes, and date/code-like markers.
445
445
  - **Components are transparent by default.** Component primitives should not bring their own paper/background fill. Let `.page`, layout containers, or explicit modifier variants provide background color when needed.
446
446
  - **Icon system is Lucide.** For ordinary UI, semantic, status, category, process, and navigation icons, use Lucide (`data-lucide`). Do not hand-write inline SVG for icons. SVG is allowed only for intentional decorative motifs, illustrations, or design-specific artwork. If any `data-lucide` icon is present, load Lucide via CDN and call `lucide.createIcons()` after `SlidePresentation`.
447
+ - **Chart system is ECharts.** Data charts default to ECharts inside `echart-panel`. Do not use hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts as data-chart substitutes. SVG remains acceptable for decorative motifs, diagrams, or illustrations, not data charts. Before creating or changing a chart, fetch the `echart-panel` component and `section: "chart-rules"`; if chart rules or runtime are unavailable, report the gap instead of inventing a fake chart fallback.
448
+ - **Start from foundation.** New deck HTML starts from `@design:foundation`. Do not recreate foundation CSS, JavaScript, or the HTML skeleton from memory. Prefer a foundation helper when available; otherwise fetch `section: "foundation"` before writing a new deck shell. Existing deck edits preserve the current foundation unless the user asks for foundation repair or QA reports a foundation contract problem.
447
449
 
448
450
  ### Common Mistakes
449
451
 
@@ -2608,3 +2610,30 @@ Rules:
2608
2610
  <!-- @component:roadmap-vertical:end -->
2609
2611
 
2610
2612
  <!-- @design:components:end -->
2613
+
2614
+ <!-- @design:chart-rules:start -->
2615
+
2616
+ ### Data Visualization (ECharts)
2617
+
2618
+ - Chart system is ECharts. Data charts should use `echarts.init()` with an `echart-panel` container, not hand-written SVG, div/CSS shapes, canvas mocks, or static faux charts.
2619
+ - Summit charts should feel editorial and report-like: calm typography, limited series count, restrained labels, and no dashboard chrome, glow, or excessive gridlines.
2620
+ - Use `--accent-earth`, `--accent-olive`, and `--accent-gold` for primary/supporting series. Use `--accent-danger` only for negative indicators. Derive colors from CSS variables with `getComputedStyle(document.documentElement)` instead of hard-coding unrelated palettes.
2621
+ - Keep chart containers inside `echart-panel` so QA can measure stable geometry. `.echart-container` must have stable sizing through explicit width/height or flex sizing with `min-height: 0`.
2622
+ - Give every chart a unique id. Initialise with `echarts.init()` after `SlidePresentation` is instantiated, and call `chart.resize()` on window resize.
2623
+ - Set `backgroundColor: "transparent"` in chart options. Set text, axis, legend, grid, and tooltip colors explicitly; ECharts canvas text does not inherit CSS reliably.
2624
+ - Always include a short chart caption or source note when data is shown.
2625
+ - Do not use fake chart fallback when ECharts runtime or chart rules are missing. Report the missing runtime/rules or use an approved local/runtime dependency.
2626
+
2627
+ Recommended ECharts defaults:
2628
+
2629
+ ```javascript
2630
+ const styles = getComputedStyle(document.documentElement);
2631
+ const chartText = styles.getPropertyValue('--text-secondary').trim();
2632
+ const chartMuted = styles.getPropertyValue('--text-muted').trim();
2633
+ const chartLine = styles.getPropertyValue('--line').trim();
2634
+ const primary = styles.getPropertyValue('--accent-earth').trim();
2635
+ const secondary = styles.getPropertyValue('--accent-olive').trim();
2636
+ const highlight = styles.getPropertyValue('--accent-gold').trim();
2637
+ ```
2638
+
2639
+ <!-- @design:chart-rules:end -->
@@ -93,11 +93,12 @@ Workflow:
93
93
  6. If target slide count, audience, language, output purpose, or visual style is unclear, ask the user for the smallest needed confirmation before writing the plan.
94
94
  7. Write \`deck-plan/index.md\` and one file per planned slide under \`deck-plan/slides/*.md\` from the planning packet and requirements. The index must identify the chapter structure first: 3-5 chapter headings, each chapter's slide range, and which non-structural slides belong to each chapter. Each slide file must include frontmatter with positive 1-based \`slideIndex\` and \`## Narrative Links\` using plain wikilinks to canonical claim/evidence/risk/objection/gap ids. Include a low-fidelity ASCII/text layout sketch for every slide; do not generate visual images or HTML mockups.
95
95
  8. Stop after presenting the plan unless the user already asked to proceed. Ask whether to continue, revise the plan, or run more research. Do not require an Approval block or \`confirmDeckPlan\` gate; \`confirmDeckPlan\` is compatibility/provenance only.
96
- 9. Ask for or confirm visual design only after the narrative deck plan exists. Fetch required design layouts/components with \`revela-designs read\` as needed.
97
- 10. Do not update cached \`DECKS.json\` slide specs for plan authoring. Use \`deck-plan/\` files and artifact files as the execution surface.
98
- 11. Call \`revela-decks\` action \`readDeckPlan\` before artifact review or HTML writing; use it to inspect the current deck-plan projection without regenerating it. Treat stale hashes, missing links, or incomplete coverage as advisory diagnostics unless the user asks to stop.
99
- 12. Run artifact diagnostics when useful, but do not treat \`writeReadiness\`, cached slide specs, unconfirmed plans, missing research, or stale coverage as workflow blockers.
100
- 13. Write \`decks/*.html\` when the user chooses to proceed and all deck HTML contract requirements can be satisfied. Generate the artifact chapter by chapter instead of drafting all content slides in one broad pass. Partial decks are allowed during chapter-by-chapter authoring when written slide sections have unique, increasing 1-based \`data-slide-index\` values and valid canvases; do not pad missing planned chapters with filler to match cached \`DECKS.json.slides[]\` length. Keep the HTML file valid after every write, preserve already-written slides, and update one chapter's slide sections at a time.
96
+ 9. Ask for or confirm visual design only after the narrative deck plan exists. For a new deck HTML file, call \`revela-deck-foundation\` first to create the active-design foundation shell; it must not create narrative slide content, choose layouts/components, or read/write \`${DECKS_STATE_FILE}\`.
97
+ 10. Fetch active design rules plus required layouts/components with \`revela-designs read\` as needed before patching slides into the foundation shell. Fetch chart rules before ECharts.
98
+ 11. Do not update cached \`DECKS.json\` slide specs for plan authoring. Use \`deck-plan/\` files and artifact files as the execution surface.
99
+ 12. Call \`revela-decks\` action \`readDeckPlan\` before artifact review or HTML writing; use it to inspect the current deck-plan projection without regenerating it. Treat stale hashes, missing links, or incomplete coverage as advisory diagnostics unless the user asks to stop.
100
+ 13. Run artifact diagnostics when useful, but do not treat \`writeReadiness\`, cached slide specs, unconfirmed plans, missing research, or stale coverage as workflow blockers.
101
+ 14. Write \`decks/*.html\` when the user chooses to proceed and all deck HTML contract requirements can be satisfied. For new files, patch slide sections between the \`revela-slides\` markers created by \`revela-deck-foundation\`. Generate the artifact chapter by chapter instead of drafting all content slides in one broad pass. Partial decks are allowed during chapter-by-chapter authoring when written slide sections have unique, increasing 1-based \`data-slide-index\` values and valid canvases; do not pad missing planned chapters with filler to match cached \`DECKS.json.slides[]\` length. Keep the HTML file valid after every write, preserve already-written slides, and update one chapter's slide sections at a time.
101
102
  15. For each chapter, make every content slide carry a distinct claim, evidence item, comparison, risk, or action. If a chapter lacks enough substance for its allocated slides, merge weak slides or reduce the slide count instead of creating sparse filler.
102
103
  16. After each HTML write, the system automatically runs artifact QA before opening Review. If post-write artifact QA reports hard errors, fix them and let QA run again. Review opens only after hard errors pass. Density warnings about thin claim/evidence substance should be reported and improved when useful, but they do not block Review.
103
104
 
@@ -142,6 +143,7 @@ Report format before any HTML write:
142
143
  - Start with \`Deck handoff: <status>\`.
143
144
  - Include which deck-plan projection and narrative hash are guiding artifact work.
144
145
  - State that \`revela-decks readDeckPlan\` was called and the current \`deck-plan/\` Chapter Writing Batches are being followed.
146
+ - For new HTML files, state that \`revela-deck-foundation\` created the foundation shell and identify the output path/design before slide content is patched.
145
147
  - Include the chapter currently being generated and confirm already-written slides are being preserved.
146
148
  - If technical artifact checks cannot be satisfied, list those blockers separately from narrative/deck-plan diagnostics.
147
149
  - After writing HTML, read the appended \`Artifact QA\` report from the tool output. If it failed, fix hard errors before considering the deck ready for Review.
@@ -149,6 +151,7 @@ Report format before any HTML write:
149
151
  Rules:
150
152
  - \`compileDeckPlan\` prepares the canonical narrative claim/evidence packet and deck-plan requirements. The LLM authors \`deck-plan/index.md\` and \`deck-plan/slides/*.md\` from that packet and asks the user for page count, audience, language, output purpose, or visual style when unclear.
151
153
  - \`deck-plan/\` is the execution blueprint for HTML generation when present. It must be read before writing HTML and followed chapter by chapter; \`DECKS.json.slides[]\` is compatibility/cache data, not the HTML slide-count authority.
154
+ - \`revela-deck-foundation\` is the file-native foundation helper for new deck shells. Use it before adding slides to a new \`decks/*.html\` file; do not use \`DECKS.json\` or \`revela-decks\` to create the shell.
152
155
  - Visual intent is part of the deck-plan projection. During HTML generation, satisfy the planned component/visual brief using fetched design components; do not collapse planned visuals into prose-only bullets.
153
156
  - Cached deck slide specs in \`DECKS.json\` are legacy projections only. Canonical narrative remains the authority for audience, decision, claims, evidence boundaries, objections, and risks.
154
157
  - Cover, Table of Contents, and Closing are mandatory deck structure. TOC chapter headings must match the chapter grouping used for generation.
@@ -0,0 +1,190 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs"
2
+ import { dirname, isAbsolute, normalize, resolve } from "path"
3
+ import { activeDesign, getDesignSection } from "../design/designs"
4
+
5
+ export type DeckFoundationMode = "create" | "repair"
6
+ export type DeckFoundationStatus = "created" | "updated"
7
+
8
+ export interface CreateDeckFoundationInput {
9
+ workspaceRoot: string
10
+ outputPath: string
11
+ title: string
12
+ language: string
13
+ designName?: string
14
+ mode?: DeckFoundationMode
15
+ overwrite?: boolean
16
+ }
17
+
18
+ export interface CreateDeckFoundationResult {
19
+ ok: true
20
+ outputPath: string
21
+ design: string
22
+ includedSections: string[]
23
+ status: DeckFoundationStatus
24
+ next: string[]
25
+ }
26
+
27
+ interface FoundationParts {
28
+ fontLinks: string[]
29
+ cssBlocks: string[]
30
+ scriptBlocks: string[]
31
+ }
32
+
33
+ const SLIDES_START = "<!-- revela-slides:start -->"
34
+ const SLIDES_END = "<!-- revela-slides:end -->"
35
+
36
+ export function createDeckFoundation(input: CreateDeckFoundationInput): CreateDeckFoundationResult {
37
+ const outputPath = normalizeOutputPath(input.outputPath)
38
+ const targetPath = safeWorkspaceFilePath(input.workspaceRoot, outputPath)
39
+ const mode = input.mode ?? "create"
40
+ const canOverwrite = input.overwrite === true || mode === "repair"
41
+ const existed = existsSync(targetPath)
42
+
43
+ if (existed && !canOverwrite) {
44
+ throw new Error(`Deck HTML already exists at ${outputPath}. Pass overwrite=true or mode=repair to replace the foundation shell.`)
45
+ }
46
+
47
+ const design = input.designName || activeDesign()
48
+ const foundation = getDesignSection("foundation", design)
49
+ const parts = parseFoundationParts(foundation)
50
+ if (parts.cssBlocks.length === 0) throw new Error(`Design '${design}' foundation does not include a CSS code block.`)
51
+ if (parts.scriptBlocks.length === 0) throw new Error(`Design '${design}' foundation does not include a SlidePresentation JavaScript code block.`)
52
+
53
+ const html = renderFoundationHtml({
54
+ language: input.language || "en",
55
+ title: input.title || "Revela Deck",
56
+ fontLinks: parts.fontLinks,
57
+ css: parts.cssBlocks.join("\n\n"),
58
+ script: parts.scriptBlocks.map(guardEmptyDeckNavigation).join("\n\n"),
59
+ })
60
+
61
+ mkdirSync(dirname(targetPath), { recursive: true })
62
+ writeFileSync(targetPath, html, "utf-8")
63
+
64
+ return {
65
+ ok: true,
66
+ outputPath,
67
+ design,
68
+ includedSections: [
69
+ "design:foundation",
70
+ parts.fontLinks.length > 0 ? "foundation:font-links" : "foundation:font-links:none",
71
+ "foundation:css",
72
+ "foundation:SlidePresentation",
73
+ ],
74
+ status: existed ? "updated" : "created",
75
+ next: [
76
+ "Fetch active design rules before patching slides.",
77
+ "Fetch required layouts and components from the design before adding slide content.",
78
+ "Patch slides between the revela-slides markers chapter by chapter, then run artifact QA.",
79
+ ],
80
+ }
81
+ }
82
+
83
+ export function normalizeOutputPath(outputPath: string): string {
84
+ const trimmed = outputPath.trim()
85
+ if (!trimmed) throw new Error("outputPath is required")
86
+ if (!trimmed.endsWith(".html")) throw new Error("Deck foundation outputPath must end in .html")
87
+ if (isAbsolute(trimmed)) throw new Error("Deck foundation outputPath must be workspace-relative")
88
+ const segments = trimmed.split(/[\\/]+/)
89
+ if (segments.includes("..")) throw new Error("Deck foundation outputPath must not contain parent-directory traversal")
90
+ return normalize(trimmed).replace(/\\/g, "/")
91
+ }
92
+
93
+ function safeWorkspaceFilePath(workspaceRoot: string, outputPath: string): string {
94
+ const root = resolve(workspaceRoot)
95
+ const target = resolve(root, outputPath)
96
+ if (target !== root && !target.startsWith(`${root}/`)) {
97
+ throw new Error("Deck foundation outputPath must stay inside the workspace")
98
+ }
99
+ return target
100
+ }
101
+
102
+ function parseFoundationParts(foundation: string): FoundationParts {
103
+ const fontLinks = extractFontLinks(foundation)
104
+ const cssBlocks: string[] = []
105
+ const scriptBlocks: string[] = []
106
+ const fenceRe = /```([\w-]*)\n([\s\S]*?)```/g
107
+ let match: RegExpExecArray | null
108
+
109
+ while ((match = fenceRe.exec(foundation)) !== null) {
110
+ const lang = (match[1] || "").toLowerCase()
111
+ const body = match[2].trim()
112
+ if (!body) continue
113
+ if (lang === "css") cssBlocks.push(body)
114
+ if ((lang === "javascript" || lang === "js") && body.includes("class SlidePresentation")) {
115
+ scriptBlocks.push(body)
116
+ }
117
+ }
118
+
119
+ return { fontLinks, cssBlocks, scriptBlocks }
120
+ }
121
+
122
+ function extractFontLinks(foundation: string): string[] {
123
+ const links: string[] = []
124
+ const seen = new Set<string>()
125
+ const linkRe = /<link\b[^>]*(?:fonts\.googleapis|fonts\.gstatic|rel=["']preconnect["'])[^>]*>/gi
126
+ let match: RegExpExecArray | null
127
+ while ((match = linkRe.exec(foundation)) !== null) {
128
+ const link = match[0].trim()
129
+ if (seen.has(link)) continue
130
+ seen.add(link)
131
+ links.push(link)
132
+ }
133
+ return links
134
+ }
135
+
136
+ function guardEmptyDeckNavigation(script: string): string {
137
+ return script.replace(
138
+ /new\s+SlidePresentation\s*\(\s*\)\s*;?/g,
139
+ 'if (document.querySelector(".slide")) { new SlidePresentation(); }',
140
+ )
141
+ }
142
+
143
+ function renderFoundationHtml(input: {
144
+ language: string
145
+ title: string
146
+ fontLinks: string[]
147
+ css: string
148
+ script: string
149
+ }): string {
150
+ const fontLinks = input.fontLinks.map((link) => ` ${link}`).join("\n")
151
+ return [
152
+ "<!DOCTYPE html>",
153
+ `<html lang="${escapeAttribute(input.language)}">`,
154
+ "<head>",
155
+ " <meta charset=\"UTF-8\">",
156
+ " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
157
+ ` <title>${escapeHtml(input.title)}</title>`,
158
+ fontLinks,
159
+ " <style>",
160
+ input.css,
161
+ " </style>",
162
+ "</head>",
163
+ "<body>",
164
+ ` ${SLIDES_START}`,
165
+ ` ${SLIDES_END}`,
166
+ " <script>",
167
+ input.script,
168
+ " </script>",
169
+ "</body>",
170
+ "</html>",
171
+ "",
172
+ ].filter((line) => line !== "").join("\n")
173
+ }
174
+
175
+ function escapeHtml(value: string): string {
176
+ return value
177
+ .replace(/&/g, "&amp;")
178
+ .replace(/</g, "&lt;")
179
+ .replace(/>/g, "&gt;")
180
+ .replace(/"/g, "&quot;")
181
+ .replace(/'/g, "&#39;")
182
+ }
183
+
184
+ function escapeAttribute(value: string): string {
185
+ return escapeHtml(value.trim() || "en")
186
+ }
187
+
188
+ export function deckFoundationMarkers(): { start: string; end: string } {
189
+ return { start: SLIDES_START, end: SLIDES_END }
190
+ }
@@ -79,6 +79,9 @@ Instructions:
79
79
  - Pure artifact polish such as layout, spacing, typography, alignment, color, image crop, animation, export fidelity, runtime JavaScript fixes, or deck HTML contract fixes may remain an artifact-level edit.
80
80
  - If the request mixes content meaning and visual polish, treat it as narrative-impacting unless the user clarifies otherwise.
81
81
  - Preserve the existing deck structure, active design language, typography, spacing system, animations, and slide count unless the comment explicitly asks otherwise.
82
+ - Before patching ${"`decks/*.html`"}, call ${"`revela-designs`"} with ${"`action: \"read\"`"} and ${"`section: \"rules\"`"} to fetch the active design rules for this edit pass.
83
+ - If the edit changes layout, component structure, typography scale, visual hierarchy, chart usage, icon usage, media treatment, or design-system classes, fetch the relevant ${"`revela-designs`"} layout/component details before editing. Fetch ${"`section: \"chart-rules\"`"} before changing or adding ECharts.
84
+ - Follow the fetched design rules and vocabulary exactly. Do not invent layout classes, component names, CSS variables, icon systems, or visual effects from model memory or the existing deck alone.
82
85
  - If an asset/drop payload is present, this is an asset placement request. Use only the saved local asset path from the asset payload in deck HTML. Prefer asset.deckPath when present because it is relative to the target HTML file; otherwise use asset.path.
83
86
  - Do not write remote imageUrl, thumbnailUrl, source page URLs, or ${"`/__revela_asset`"} proxy URLs into deck HTML.
84
87
  - Logo assets should remain small, clear, and brand-like; do not use logos as decorative backgrounds.
@@ -2,6 +2,8 @@ import { randomBytes } from "crypto"
2
2
  import { existsSync, readFileSync, statSync } from "fs"
3
3
  import { fileURLToPath } from "url"
4
4
  import { dirname, extname, isAbsolute, resolve, sep } from "path"
5
+ import { ctx } from "../ctx"
6
+ import { buildPrompt } from "../prompt-builder"
5
7
  import type { EditableDeck } from "./resolve-deck"
6
8
  import { buildEditPrompt, type EditCommentPayload } from "./prompt"
7
9
 
@@ -405,6 +407,9 @@ async function handleComment(req: Request, session: EditSession): Promise<Respon
405
407
  const elements = Array.isArray(body.elements) ? body.elements : []
406
408
  if (!comment && comments.length === 0) return jsonResponse({ ok: false, error: "Comment is required" }, 400)
407
409
 
410
+ ctx.enabled = true
411
+ buildPrompt({ mode: "deck-render" })
412
+
408
413
  const prompt = buildEditPrompt({
409
414
  ...body,
410
415
  deck: session.deck,
package/lib/qa/index.ts CHANGED
@@ -79,6 +79,7 @@ function scanAssetRefs(htmlFilePath: string): LayoutIssue[] {
79
79
  const ref = raw.trim()
80
80
  if (!ref || ref.startsWith("data:") || ref.startsWith("#") || ref.startsWith("mailto:") || ref.startsWith("tel:")) continue
81
81
  if (/^https?:\/\//i.test(ref) || ref.startsWith("//")) {
82
+ if (isAllowedRemoteRuntimeRef(ref)) continue
82
83
  issues.push({ type: "asset", sub: "remote_url", severity: "error", detail: `Deck HTML references remote asset URL \`${ref}\`. Save network images to workspace assets and reference the local file instead.` })
83
84
  continue
84
85
  }
@@ -96,6 +97,17 @@ function scanAssetRefs(htmlFilePath: string): LayoutIssue[] {
96
97
  return issues
97
98
  }
98
99
 
100
+ function isAllowedRemoteRuntimeRef(ref: string): boolean {
101
+ try {
102
+ const url = new URL(ref.startsWith("//") ? `https:${ref}` : ref)
103
+ if (url.hostname === "fonts.googleapis.com" || url.hostname === "fonts.gstatic.com") return true
104
+ if (url.hostname === "cdn.jsdelivr.net" && url.pathname.startsWith("/npm/echarts@")) return true
105
+ } catch {
106
+ return false
107
+ }
108
+ return false
109
+ }
110
+
99
111
  function looksLikeImageRef(ref: string): boolean {
100
112
  return /\.(?:png|jpe?g|webp|gif|svg)(?:[?#].*)?$/i.test(ref)
101
113
  }
@@ -2,8 +2,10 @@ import { randomBytes } from "crypto"
2
2
  import { existsSync, readFileSync, statSync } from "fs"
3
3
  import { fileURLToPath } from "url"
4
4
  import { dirname, extname, isAbsolute, relative, resolve, sep } from "path"
5
+ import { ctx } from "../ctx"
5
6
  import type { EditableDeck } from "../edit/resolve-deck"
6
7
  import { buildEditPrompt, type EditCommentPayload } from "../edit/prompt"
8
+ import { buildPrompt } from "../prompt-builder"
7
9
  import type { InspectionElementSnapshot } from "../inspection-context/match"
8
10
  import { buildInspectionPrompt } from "../inspect/prompt"
9
11
  import { projectWorkspaceElement } from "../inspect/request"
@@ -715,6 +717,9 @@ async function handleComment(req: Request, session: EditSession): Promise<Respon
715
717
  const elements = Array.isArray(body.elements) ? body.elements : []
716
718
  if (!comment && comments.length === 0) return jsonResponse({ ok: false, error: "Comment is required" }, 400)
717
719
 
720
+ ctx.enabled = true
721
+ buildPrompt({ mode: "deck-render" })
722
+
718
723
  const prompt = buildEditPrompt({
719
724
  ...body,
720
725
  deck: session.deck,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.17.4",
3
+ "version": "0.17.6",
4
4
  "description": "OpenCode plugin for trusted narrative artifacts from local sources, research, and evidence",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/plugin.ts CHANGED
@@ -81,6 +81,7 @@ import decksTool from "./tools/decks"
81
81
  import designsAuthorTool from "./tools/designs-author"
82
82
  import designsTool from "./tools/designs"
83
83
  import domainsTool from "./tools/domains"
84
+ import deckFoundationTool from "./tools/deck-foundation"
84
85
  import mediaBatchSaveTool from "./tools/media-batch-save"
85
86
  import mediaSaveTool from "./tools/media-save"
86
87
  import researchImagesListTool from "./tools/research-images-list"
@@ -586,6 +587,7 @@ const server: Plugin = (async (pluginCtx) => {
586
587
  tool: {
587
588
  "revela-decks": decksTool,
588
589
  "revela-designs": designsTool,
590
+ "revela-deck-foundation": deckFoundationTool,
589
591
  "revela-designs-author": designsAuthorTool,
590
592
  "revela-domains": domainsTool,
591
593
  "revela-media-batch-save": mediaBatchSaveTool,
package/skill/SKILL.md CHANGED
@@ -64,8 +64,13 @@ handoff exactly:
64
64
  5. Use `readDeckPlan` to inspect the current `deck-plan/` projection before
65
65
  artifact review or HTML generation. Diagnostics are advisory unless they are
66
66
  artifact validity errors handled by QA.
67
- 6. Fetch the required design layouts/components with `revela-designs read`.
68
- 7. Write HTML when the user proceeds and the deck contract can be satisfied.
67
+ 6. For a new deck HTML file, call `revela-deck-foundation` to create the
68
+ active-design foundation shell. The helper is file-native and must not create
69
+ narrative slide content, choose layouts/components, or read/write `DECKS.json`.
70
+ 7. Fetch the required design rules, layouts, and components with
71
+ `revela-designs read`.
72
+ 8. Patch slides between the foundation shell's `revela-slides` markers when the
73
+ user proceeds and the deck contract can be satisfied.
69
74
 
70
75
  Before any HTML generation, call `revela-decks` action `readDeckPlan` and follow
71
76
  the current `deck-plan/`: Source Authority, deck parameters, Chapter Writing
@@ -164,7 +169,8 @@ one broad `write`, `edit`, or `apply_patch` call.
164
169
 
165
170
  For decks with 5 or more slides:
166
171
 
167
- - First create a stable HTML shell plus structural slides and the first chapter.
172
+ - First call `revela-deck-foundation` for new files, then patch structural
173
+ slides and the first chapter between the `revela-slides` markers.
168
174
  - Then fill or revise exactly one chapter range at a time.
169
175
  - Do not mix multiple central-claim chapters in the same write.
170
176
  - Chapter divider or chapter TOC slides are allowed as structural wayfinding and
@@ -193,14 +199,17 @@ If a write produces QA hard errors, fix them before continuing.
193
199
  Before writing or materially changing HTML:
194
200
 
195
201
  1. Read the deck-plan projection's layout and component names.
196
- 2. Call `revela-designs` with `action: "read"` and `section: "rules"` to fetch
202
+ 2. For a new deck HTML file, call `revela-deck-foundation` before adding slide
203
+ content. Use `mode: "repair"` only for explicit foundation repair or QA
204
+ foundation contract fixes, not normal Review Comment edits.
205
+ 3. Call `revela-designs` with `action: "read"` and `section: "rules"` to fetch
197
206
  the active design's current composition and usage rules.
198
- 3. Call `revela-designs` with `action: "read"` and `layout` set to all required
207
+ 4. Call `revela-designs` with `action: "read"` and `layout` set to all required
199
208
  layout names, comma-separated.
200
- 4. Call `revela-designs` with `action: "read"` and `component` set to all
209
+ 5. Call `revela-designs` with `action: "read"` and `component` set to all
201
210
  required component names, comma-separated.
202
- 5. Fetch `section: "chart-rules"` before using ECharts.
203
- 6. Do not update legacy `requiredInputs`; design fetching is an execution step,
211
+ 6. Fetch `section: "chart-rules"` before using ECharts.
212
+ 7. Do not update legacy `requiredInputs`; design fetching is an execution step,
204
213
  not a workflow permission gate.
205
214
 
206
215
  Never generate HTML from memory or prior knowledge of a design. Copy the fetched
@@ -0,0 +1,48 @@
1
+ import { tool } from "@opencode-ai/plugin"
2
+ import { createDeckFoundation } from "../lib/deck-html/foundation"
3
+
4
+ export default tool({
5
+ description:
6
+ "Create or repair a file-native Revela deck HTML foundation shell from the active design. " +
7
+ "Writes a deterministic empty deck shell with doctype, html/head/body, active design foundation CSS, complete SlidePresentation JavaScript, and stable slide insertion markers. " +
8
+ "It does not create narrative slide content, choose layouts/components, or read/write DECKS.json.",
9
+ args: {
10
+ outputPath: tool.schema
11
+ .string()
12
+ .describe("Workspace-relative HTML output path, usually decks/{name}.html."),
13
+ title: tool.schema
14
+ .string()
15
+ .describe("Presentation title for the HTML <title> tag only; this does not create a cover slide."),
16
+ language: tool.schema
17
+ .string()
18
+ .describe("HTML language tag, e.g. en or zh-CN."),
19
+ designName: tool.schema
20
+ .string()
21
+ .optional()
22
+ .describe("Optional design name. Defaults to the active design."),
23
+ mode: tool.schema
24
+ .enum(["create", "repair"])
25
+ .optional()
26
+ .describe("create protects existing files unless overwrite=true; repair may replace an existing foundation shell."),
27
+ overwrite: tool.schema
28
+ .boolean()
29
+ .optional()
30
+ .describe("Whether create mode may overwrite an existing HTML file. Defaults to false."),
31
+ },
32
+ async execute(args, { directory }) {
33
+ try {
34
+ const result = createDeckFoundation({
35
+ workspaceRoot: directory || process.cwd(),
36
+ outputPath: args.outputPath,
37
+ title: args.title,
38
+ language: args.language,
39
+ designName: args.designName,
40
+ mode: args.mode,
41
+ overwrite: args.overwrite ?? false,
42
+ })
43
+ return JSON.stringify(result, null, 2)
44
+ } catch (e: any) {
45
+ return JSON.stringify({ error: e?.message || String(e) })
46
+ }
47
+ },
48
+ })