@cyber-dash-tech/revela 0.19.8 → 0.20.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.
@@ -8,6 +8,7 @@ export * from "./claim-supporting-visual"
8
8
  export * from "./closing"
9
9
  export * from "./cover"
10
10
  export * from "./executive-summary"
11
+ export * from "./free"
11
12
  export * from "./key-message-evidence"
12
13
  export * from "./metric-highlight"
13
14
  export * from "./milestone"
@@ -18,5 +19,6 @@ export * from "./risks-tradeoffs"
18
19
  export * from "./section-divider"
19
20
  export * from "./table"
20
21
  export * from "./table-comparison"
22
+ export * from "./team"
21
23
  export * from "./timeline"
22
24
  export * from "./timeline-roadmap"
@@ -0,0 +1,3 @@
1
+ import { templateModule } from "./shared"
2
+
3
+ export const teamTemplate = templateModule("team")
@@ -51,6 +51,7 @@ export const PAGE_TEMPLATE_VOCABULARY: PageTemplateVocabulary[] = [
51
51
  vocab("closing", ["template-hero"], ["hero"], ["hero"], ["Closing uses the same hero-safe structure as cover."]),
52
52
  vocab("agenda", ["template-agenda-panel"], ["agenda", "agenda-list"], ["agenda", "agenda-list"], ["Agenda numbers must remain in DOM order."]),
53
53
  vocab("executive-summary", ["template-card"], ["summary-cards"], ["summary-cards"], ["Cards are editable; visual placeholders are optional and may become image/chart slots."]),
54
+ vocab("team", ["template-team-grid", "template-team-card", "template-team-photo", "template-team-highlights", "template-team-education"], ["members"], ["members"], ["Team cards keep portrait, name/role, highlights, and education as distinct regions.", "Use 3-4 members for a readable 16:9 page; 5-6 members require shorter copy."]),
54
55
  vocab("problem-context", ["template-card"], ["context", "supporting-points"], ["context", "supporting-points"], ["Context should stay separate from supporting bullets."]),
55
56
  vocab("key-message-evidence", ["template-key-message-panel", "template-evidence-grid"], ["key-message", "evidence"], ["key-message", "evidence"], ["Key message and evidence regions must remain distinct."]),
56
57
  vocab("claim-supporting-visual", ["template-claim-text-panel", "template-visual-slot-panel"], ["claim", "visual"], ["claim", "visual"], ["Visual slot may be replaced by image, chart, table, or diagram container."]),
@@ -63,6 +64,7 @@ export const PAGE_TEMPLATE_VOCABULARY: PageTemplateVocabulary[] = [
63
64
  vocab("process-steps", ["template-steps", "template-step-number"], ["steps"], ["steps"], ["Steps should remain ordered in DOM order."]),
64
65
  vocab("recommendation-decision", ["template-card"], ["recommendation", "rationale", "next-steps"], ["recommendation", "rationale", "next-steps"], ["Keep recommendation, rationale, and next steps separate."]),
65
66
  vocab("risks-tradeoffs", ["template-card"], ["risks"], ["risks"], ["Risk/tradeoff cards should name uncertainty explicitly."]),
67
+ vocab("free", ["template-free-stage", "template-free-placeholder"], ["placeholder"], ["placeholder"], ["Free pages keep a title plus one semantic placeholder region for agent-decided image, chart, text, table, or mixed content.", "Do not split the placeholder into multiple top-level slots when bounded-editing the page."]),
66
68
  ]
67
69
 
68
70
  const additionalClasses = [
@@ -128,6 +130,19 @@ const additionalClasses = [
128
130
  "template-timeline-date",
129
131
  "template-steps",
130
132
  "template-step-number",
133
+ "template-team-grid",
134
+ "template-team-card",
135
+ "template-team-photo",
136
+ "template-team-copy",
137
+ "template-team-name",
138
+ "template-team-role",
139
+ "template-team-highlights",
140
+ "template-team-education",
141
+ "template-free-stage",
142
+ "template-free-placeholder",
143
+ "template-free-placeholder-label",
144
+ "template-free-placeholder-hints",
145
+ "template-free-placeholder-hint",
131
146
  "template-catalog-panel",
132
147
  "template-catalog-kicker",
133
148
  "template-catalog-title",
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "crypto"
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
3
- import { dirname, resolve } from "path"
2
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs"
3
+ import { dirname, isAbsolute, resolve, sep } from "path"
4
4
  import {
5
5
  activeDesign,
6
6
  activateDesign,
@@ -16,13 +16,14 @@ import {
16
16
  listDesignAssets,
17
17
  listDesigns,
18
18
  materializeDesignPreview,
19
+ materializeDesignCssSnapshot,
19
20
  packDesignPackage,
20
21
  seedBuiltinDesigns,
21
22
  validateDesignDraftPackage,
22
23
  validateDesignPackage,
23
24
  type DesignPackageAssetInput,
24
25
  } from "../design/designs"
25
- import { createDeckFoundation as createDeckFoundationShell } from "../deck-html/foundation"
26
+ import { activeDesignSnapshotName, createDeckFoundation as createDeckFoundationShell } from "../deck-html/foundation"
26
27
  import { activeDomain, activateDomain, createDomainDraftPackage, createDomainPackage, getDomainSkillMd, installDomainDraftPackage, listDomains, seedBuiltinDomains, validateDomainDraftPackage, validateDomainPackage } from "../domain/domains"
27
28
  import { compileNarrativeVault } from "../narrative-vault/compile"
28
29
  import { autoCompileNarrativeVault } from "../narrative-vault/auto-compile"
@@ -36,6 +37,8 @@ import { recordRenderedArtifact, workspaceRelative } from "../workspace-state/re
36
37
  import { existingWorkspaceMetaPath, workspaceMetaPath } from "../workspace-meta"
37
38
  import { checkMaterialIntake, extractMaterial, materialIntakeNoticeForCommand, prepareLocalMaterials, recordMaterialReview } from "../material-intake"
38
39
  import type { ReviewDeckOpenInput, ReviewDeckReadInput } from "./review"
40
+ export type { OpenDeckInput } from "./open-deck"
41
+ import { openDeck as openDeckDirect } from "./open-deck"
39
42
  import pkg from "../../package.json"
40
43
  export { bindResearchFindings, evaluateResearchFindings, researchSave, researchTargets } from "./research"
41
44
  export { storyRead } from "./story"
@@ -72,6 +75,12 @@ export interface RuntimeDesignInventoryInput {
72
75
  name?: string
73
76
  }
74
77
 
78
+ export interface RuntimeDeckDesignSwitchInput extends RuntimeWorkspaceInput {
79
+ file: string
80
+ name: string
81
+ openBrowser?: boolean
82
+ }
83
+
75
84
  export interface RuntimeDesignLayoutReadInput {
76
85
  name?: string
77
86
  layout: string | string[]
@@ -380,6 +389,16 @@ export async function reviewDeckOpen(input: ReviewDeckOpenInput) {
380
389
  return review.reviewDeckOpen(input)
381
390
  }
382
391
 
392
+ export async function openDeck(input: RuntimeFileInput & { openBrowser?: boolean; openUrl?: (url: string) => void }) {
393
+ const direct = await import("./open-deck")
394
+ return direct.openDeck(input)
395
+ }
396
+
397
+ export async function stopOpenDeckServers() {
398
+ const direct = await import("./open-deck")
399
+ return direct.stopOpenDeckServers()
400
+ }
401
+
383
402
  export function designList() {
384
403
  return {
385
404
  ok: true,
@@ -586,6 +605,50 @@ export function designActivate(input: RuntimeNameInput) {
586
605
  }
587
606
  }
588
607
 
608
+ export function switchDeckDesign(input: RuntimeDeckDesignSwitchInput) {
609
+ seedBuiltinDesigns()
610
+ const workspaceRoot = root(input.workspaceRoot)
611
+ const file = normalizeDeckFile(workspaceRoot, requiredString(input.file, "file"))
612
+ const design = requiredName({ name: input.name }, "design")
613
+ activateDesign(design)
614
+
615
+ const snapshot = materializeDesignCssSnapshot({
616
+ workspaceRoot,
617
+ outputPath: file,
618
+ designName: design,
619
+ snapshotName: activeDesignSnapshotName(file),
620
+ })
621
+
622
+ const htmlPath = resolve(workspaceRoot, file)
623
+ const html = readFileSync(htmlPath, "utf-8")
624
+ const migratedHtml = migrateDeckDesignLink(html, snapshot.href)
625
+ const migratedLink = migratedHtml !== html
626
+ if (migratedLink) writeFileSync(htmlPath, migratedHtml, "utf-8")
627
+
628
+ const opened = input.openBrowser === false
629
+ ? undefined
630
+ : openDeckDirect({ workspaceRoot, file, openBrowser: input.openBrowser })
631
+
632
+ return {
633
+ ok: true,
634
+ file,
635
+ activeDesign: activeDesign(),
636
+ design,
637
+ snapshotHref: snapshot.href,
638
+ snapshotDir: workspaceRelative(workspaceRoot, snapshot.snapshotDir),
639
+ assetCount: snapshot.assetCount,
640
+ migratedLink,
641
+ opened: opened ? {
642
+ ok: opened.ok,
643
+ url: opened.url,
644
+ openedBrowser: opened.openedBrowser,
645
+ mode: opened.mode,
646
+ readOnly: opened.readOnly,
647
+ } : undefined,
648
+ warnings: snapshot.warnings,
649
+ }
650
+ }
651
+
589
652
  export function domainList() {
590
653
  seedBuiltinDomains()
591
654
  return {
@@ -659,6 +722,35 @@ function root(workspaceRoot: string | undefined): string {
659
722
  return resolve(workspaceRoot || process.cwd())
660
723
  }
661
724
 
725
+ function normalizeDeckFile(workspaceRoot: string, fileInput: string): string {
726
+ const requested = fileInput.trim()
727
+ const absolute = isAbsolute(requested) ? resolve(requested) : resolve(workspaceRoot, requested)
728
+ if (!isInside(workspaceRoot, absolute)) throw new Error(`Deck HTML file is outside the workspace: ${requested}`)
729
+ if (!existsSync(absolute) || !statSync(absolute).isFile()) throw new Error(`Deck HTML file not found: ${requested}`)
730
+ const file = workspaceRelative(workspaceRoot, absolute)
731
+ if (!file.startsWith("decks/") || !file.endsWith(".html")) throw new Error(`Deck HTML file must be under decks/*.html: ${file}`)
732
+ return file
733
+ }
734
+
735
+ function migrateDeckDesignLink(html: string, activeHref: string): string {
736
+ const escapedHref = activeHref.replace(/"/g, """)
737
+ if (html.includes(`href="${escapedHref}"`) || html.includes(`href='${escapedHref}'`)) return html
738
+ const linkRe = /<link\b(?=[^>]*\brel=(["'])stylesheet\1)(?=[^>]*\bhref=(["'])\.\/_revela-design\/[^"']+\/design\.css\2)[^>]*>/i
739
+ if (linkRe.test(html)) {
740
+ return html.replace(linkRe, `<link rel="stylesheet" href="${escapedHref}" data-revela-design-link="active">`)
741
+ }
742
+ if (/<\/head>/i.test(html)) {
743
+ return html.replace(/<\/head>/i, ` <link rel="stylesheet" href="${escapedHref}" data-revela-design-link="active">\n</head>`)
744
+ }
745
+ return html
746
+ }
747
+
748
+ function isInside(rootPath: string, candidate: string): boolean {
749
+ const normalizedRoot = resolve(rootPath)
750
+ const normalizedCandidate = resolve(candidate)
751
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(normalizedRoot.endsWith(sep) ? normalizedRoot : normalizedRoot + sep)
752
+ }
753
+
662
754
  function safe<T>(fn: () => T): T | undefined {
663
755
  try {
664
756
  return fn()
@@ -0,0 +1,190 @@
1
+ import { existsSync, statSync } from "fs"
2
+ import { extname, resolve, sep } from "path"
3
+ import { openUrl as systemOpenUrl } from "../edit/open"
4
+ import { workspaceRelative } from "../workspace-state/rendered-artifacts"
5
+
6
+ export interface OpenDeckInput {
7
+ workspaceRoot?: string
8
+ file: string
9
+ openBrowser?: boolean
10
+ openUrl?: (url: string) => void
11
+ }
12
+
13
+ interface DirectDeckServer {
14
+ server: ReturnType<typeof Bun.serve>
15
+ baseUrl: string
16
+ workspaceRoot: string
17
+ idleTimer?: Timer
18
+ }
19
+
20
+ const servers = new Map<string, DirectDeckServer>()
21
+ const IDLE_STOP_MS = 30 * 60 * 1000
22
+ const FALLBACK_PORT_START = 8765
23
+ const FALLBACK_PORT_END = 8899
24
+
25
+ export function openDeck(input: OpenDeckInput): any {
26
+ const workspaceRoot = resolve(input.workspaceRoot || process.cwd())
27
+ const requestedFile = input.file?.trim()
28
+ if (!requestedFile) {
29
+ return {
30
+ ok: false,
31
+ file: "",
32
+ error: "Missing required file.",
33
+ diagnostics: [{ severity: "error", code: "missing_file", message: "Provide a workspace-relative or absolute deck HTML file." }],
34
+ }
35
+ }
36
+
37
+ const absoluteFile = resolve(workspaceRoot, requestedFile)
38
+ const file = workspaceRelative(workspaceRoot, absoluteFile)
39
+ if (!isInside(workspaceRoot, absoluteFile)) {
40
+ return {
41
+ ok: false,
42
+ file,
43
+ error: `Deck HTML file is outside the workspace: ${file}`,
44
+ diagnostics: [{ severity: "error", code: "file_outside_workspace", message: `Deck HTML file is outside the workspace: ${file}` }],
45
+ }
46
+ }
47
+ if (!existsSync(absoluteFile) || !statSync(absoluteFile).isFile()) {
48
+ return {
49
+ ok: false,
50
+ file,
51
+ error: `Deck HTML file not found: ${file}`,
52
+ diagnostics: [{ severity: "error", code: "file_not_found", message: `Deck HTML file not found: ${file}` }],
53
+ }
54
+ }
55
+ if (!file.startsWith("decks/") || !file.endsWith(".html")) {
56
+ return {
57
+ ok: false,
58
+ file,
59
+ error: `Deck HTML file must be under decks/*.html: ${file}`,
60
+ diagnostics: [{ severity: "error", code: "invalid_deck_path", message: `Deck HTML file must be under decks/*.html: ${file}` }],
61
+ }
62
+ }
63
+
64
+ try {
65
+ const deckServer = startDeckStaticServer(workspaceRoot)
66
+ const url = `${deckServer.baseUrl}/${file.split("/").map(encodeURIComponent).join("/")}`
67
+ const openedBrowser = input.openBrowser !== false
68
+ if (openedBrowser) (input.openUrl ?? systemOpenUrl)(url)
69
+ return {
70
+ ok: true,
71
+ file,
72
+ url,
73
+ serveRoot: workspaceRoot,
74
+ openedBrowser,
75
+ mode: "direct",
76
+ readOnly: true,
77
+ }
78
+ } catch (e) {
79
+ const message = e instanceof Error ? e.message : String(e)
80
+ return {
81
+ ok: false,
82
+ file,
83
+ error: message,
84
+ diagnostics: [{ severity: "error", code: "open_deck_failed", message }],
85
+ }
86
+ }
87
+ }
88
+
89
+ export function stopOpenDeckServers(): void {
90
+ for (const item of servers.values()) {
91
+ if (item.idleTimer) clearTimeout(item.idleTimer)
92
+ item.server.stop()
93
+ }
94
+ servers.clear()
95
+ }
96
+
97
+ function startDeckStaticServer(workspaceRoot: string): DirectDeckServer {
98
+ const existing = servers.get(workspaceRoot)
99
+ if (existing) {
100
+ scheduleIdleStop(existing)
101
+ return existing
102
+ }
103
+
104
+ const server = serveWithFallback(workspaceRoot)
105
+ const item: DirectDeckServer = {
106
+ server,
107
+ baseUrl: `http://127.0.0.1:${server.port}`,
108
+ workspaceRoot,
109
+ }
110
+ ;(server as any).unref?.()
111
+ servers.set(workspaceRoot, item)
112
+ scheduleIdleStop(item)
113
+ return item
114
+ }
115
+
116
+ function serveWithFallback(workspaceRoot: string): ReturnType<typeof Bun.serve> {
117
+ const ports = [0, ...Array.from({ length: FALLBACK_PORT_END - FALLBACK_PORT_START + 1 }, (_, index) => FALLBACK_PORT_START + index)]
118
+ const failures: string[] = []
119
+ for (const port of ports) {
120
+ try {
121
+ return Bun.serve({
122
+ hostname: "127.0.0.1",
123
+ port,
124
+ fetch: (req) => handleStaticRequest(workspaceRoot, req),
125
+ })
126
+ } catch (e) {
127
+ failures.push(`port ${port}: ${e instanceof Error ? e.message : String(e)}`)
128
+ }
129
+ }
130
+ throw new Error(`Failed to start direct deck server. ${failures.slice(0, 3).join(" ")}`)
131
+ }
132
+
133
+ async function handleStaticRequest(workspaceRoot: string, req: Request): Promise<Response> {
134
+ const url = new URL(req.url)
135
+ if (req.method !== "GET" && req.method !== "HEAD") return new Response("Method not allowed", { status: 405 })
136
+ if (url.pathname === "/health") return new Response("ok", { status: 200 })
137
+ const pathPart = decodeURIComponent(url.pathname.replace(/^\/+/, ""))
138
+ if (!pathPart) return new Response("Not found", { status: 404 })
139
+ const absolutePath = resolve(workspaceRoot, pathPart)
140
+ if (!isInside(workspaceRoot, absolutePath)) return new Response("Forbidden", { status: 403 })
141
+ if (!existsSync(absolutePath) || !statSync(absolutePath).isFile()) return new Response("Not found", { status: 404 })
142
+ const file = Bun.file(absolutePath)
143
+ const headers = new Headers({ "content-type": contentType(absolutePath) })
144
+ if (req.method === "HEAD") return new Response(null, { status: 200, headers })
145
+ return new Response(file, { headers })
146
+ }
147
+
148
+ function scheduleIdleStop(item: DirectDeckServer): void {
149
+ if (item.idleTimer) clearTimeout(item.idleTimer)
150
+ item.idleTimer = setTimeout(() => {
151
+ item.server.stop()
152
+ servers.delete(item.workspaceRoot)
153
+ }, IDLE_STOP_MS)
154
+ }
155
+
156
+ function isInside(root: string, candidate: string): boolean {
157
+ const normalizedRoot = resolve(root)
158
+ const normalizedCandidate = resolve(candidate)
159
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(normalizedRoot.endsWith(sep) ? normalizedRoot : normalizedRoot + sep)
160
+ }
161
+
162
+ function contentType(filePath: string): string {
163
+ switch (extname(filePath).toLowerCase()) {
164
+ case ".html":
165
+ return "text/html; charset=utf-8"
166
+ case ".css":
167
+ return "text/css; charset=utf-8"
168
+ case ".js":
169
+ return "application/javascript; charset=utf-8"
170
+ case ".json":
171
+ return "application/json; charset=utf-8"
172
+ case ".svg":
173
+ return "image/svg+xml"
174
+ case ".png":
175
+ return "image/png"
176
+ case ".jpg":
177
+ case ".jpeg":
178
+ return "image/jpeg"
179
+ case ".webp":
180
+ return "image/webp"
181
+ case ".gif":
182
+ return "image/gif"
183
+ case ".woff":
184
+ return "font/woff"
185
+ case ".woff2":
186
+ return "font/woff2"
187
+ default:
188
+ return "application/octet-stream"
189
+ }
190
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.19.8",
3
+ "version": "0.20.0",
4
4
  "description": "Codex-first CLI/MCP workspace for trusted narrative artifacts from local sources, research, and evidence",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "revela",
3
- "version": "0.19.7",
4
- "description": "Use Revela in Codex to specify, research, plan, make, and export trusted narrative decision artifacts.",
3
+ "version": "0.20.0",
4
+ "description": "Use Revela in Codex to specify, research, plan, make, review, and export trusted narrative decision artifacts.",
5
5
  "author": {
6
6
  "name": "cyber-dash-tech",
7
7
  "url": "https://github.com/cyber-dash-tech"
@@ -20,7 +20,7 @@
20
20
  "interface": {
21
21
  "displayName": "Revela",
22
22
  "shortDescription": "Trusted narrative artifacts from local sources and research.",
23
- "longDescription": "Revela helps Codex route workspace workflows, capture requirements in spec.md, ingest local materials, save research findings, plan decks, generate HTML deck artifacts, open them in Codex Browser for annotation, and export PDF/PPTX/PNG outputs while preserving source traceability.",
23
+ "longDescription": "Revela helps Codex route workspace workflows, capture requirements in spec.md, ingest local materials, save research findings, plan decks, generate HTML deck artifacts, open them directly in Codex Browser, and export PDF/PPTX/PNG outputs while preserving source traceability.",
24
24
  "developerName": "cyber-dash-tech",
25
25
  "category": "Productivity",
26
26
  "capabilities": [
@@ -31,6 +31,7 @@
31
31
  "Use Revela to route the next workflow step for this workspace.",
32
32
  "Use Revela to write a spec.md for this deck objective.",
33
33
  "Use Revela to make a deck from the deck plan.",
34
+ "Use Revela to review this deck in Codex Browser.",
34
35
  "Use Revela to export this deck artifact."
35
36
  ],
36
37
  "brandColor": "#2563EB"
@@ -30,6 +30,7 @@ type RuntimeModule = {
30
30
  designReadLayout(input: any): any
31
31
  designReadComponent(input: any): any
32
32
  designActivate(input: any): any
33
+ switchDeckDesign(input: any): any
33
34
  designCreate(input: any): any
34
35
  designValidate(input: any): any
35
36
  designPreview(input: any): any
@@ -47,7 +48,7 @@ type RuntimeModule = {
47
48
  domainDraftValidate(input: any): any
48
49
  domainDraftInstall(input: any): any
49
50
  reviewDeckRead(input: any): Promise<any>
50
- reviewDeckOpen(input: any): Promise<any>
51
+ openDeck(input: any): Promise<any>
51
52
  researchSave(input: any): any
52
53
  prepareLocalMaterials(input: any): Promise<any>
53
54
  extractMaterial(input: any): Promise<any>
@@ -242,6 +243,16 @@ const tools = [
242
243
  description: "Activate a Revela design for future deck planning and artifact generation.",
243
244
  inputSchema: objectSchema({ name: requiredStringProp("Design name to activate.") }, ["name"]),
244
245
  },
246
+ {
247
+ name: "revela_switch_deck_design",
248
+ description: "Switch one existing decks/*.html artifact to a design by refreshing its deck-local active design snapshot without rewriting slide content.",
249
+ inputSchema: objectSchema({
250
+ workspaceRoot: stringProp("Optional workspace root."),
251
+ file: requiredStringProp("Workspace-relative or absolute decks/*.html file."),
252
+ name: requiredStringProp("Design name to use for this deck."),
253
+ openBrowser: booleanProp("Whether to open the switched deck in Codex Browser. Defaults to true."),
254
+ }, ["file", "name"]),
255
+ },
245
256
  {
246
257
  name: "revela_design_create",
247
258
  description: "Create and validate a local Revela design package from complete DESIGN.md, design.css, and optional legacy preview.html content.",
@@ -380,7 +391,7 @@ const tools = [
380
391
  },
381
392
  {
382
393
  name: "revela_review_deck_read",
383
- description: "Compatibility-only read of aggregate Review diagnostics for a Revela HTML deck: artifact QA, deck-plan diagnostics, and export/readiness signals.",
394
+ description: "Read optional Review diagnostics for a Revela HTML deck: artifact QA, deck-plan diagnostics, and export/readiness signals.",
384
395
  inputSchema: objectSchema({
385
396
  workspaceRoot: stringProp("Optional workspace root."),
386
397
  file: requiredStringProp("Workspace-relative or absolute HTML deck path."),
@@ -388,12 +399,11 @@ const tools = [
388
399
  }, ["file"]),
389
400
  },
390
401
  {
391
- name: "revela_review_deck_open",
392
- description: "Compatibility-only opener for the legacy local Codex-backed Review UI for a Revela HTML deck.",
402
+ name: "revela_open_deck",
403
+ description: "Open a Revela HTML deck directly in Codex Browser through a read-only localhost URL for user review.",
393
404
  inputSchema: objectSchema({
394
405
  workspaceRoot: stringProp("Optional workspace root."),
395
406
  file: requiredStringProp("Workspace-relative or absolute HTML deck path."),
396
- bridge: enumProp(["codex-exec"], "Prompt bridge for browser saved-comment Apply interactions."),
397
407
  openBrowser: booleanProp("Whether the tool should open the browser itself. Defaults to true when omitted."),
398
408
  }, ["file"]),
399
409
  },
@@ -524,6 +534,7 @@ async function callTool(name: string, args: any): Promise<any> {
524
534
  if (name === "revela_design_read_layout") return r.designReadLayout(args)
525
535
  if (name === "revela_design_read_component") return r.designReadComponent(args)
526
536
  if (name === "revela_design_activate") return r.designActivate(args)
537
+ if (name === "revela_switch_deck_design") return r.switchDeckDesign(args)
527
538
  if (name === "revela_design_create") return r.designCreate(args)
528
539
  if (name === "revela_design_validate") return r.designValidate(args)
529
540
  if (name === "revela_design_draft_create") return r.designDraftCreate(args)
@@ -541,7 +552,7 @@ async function callTool(name: string, args: any): Promise<any> {
541
552
  if (name === "revela_domain_draft_validate") return r.domainDraftValidate(args)
542
553
  if (name === "revela_domain_draft_install") return r.domainDraftInstall(args)
543
554
  if (name === "revela_review_deck_read") return r.reviewDeckRead(args)
544
- if (name === "revela_review_deck_open") return r.reviewDeckOpen(args)
555
+ if (name === "revela_open_deck") return r.openDeck(args)
545
556
  if (name === "revela_research_save") return r.researchSave(args)
546
557
  if (name === "revela_prepare_local_materials") return r.prepareLocalMaterials(args)
547
558
  if (name === "revela_extract_document_materials") return r.extractMaterial(args)
@@ -29,7 +29,7 @@ Use this skill as the main Revela entrypoint in Codex. It should inspect intent
29
29
  - `spec.md` exists but source support, material review, or findings are missing: use `revela-research`.
30
30
  - `spec.md` and sufficient findings exist but `deck-plan.md` is missing or needs normal authoring: use `revela-research` Planning Handoff.
31
31
  - Valid `deck-plan.md` exists and the user asks to make, generate, render, or update a deck: use `revela-make-deck`.
32
- - Existing deck artifact and the user asks to review, annotate, diagnose, QA, or refine: use Codex Browser's native browsing/annotation flow. If the deck was not just generated, start a read-only local static server from the workspace root, reply with the existing deck as a localhost website card/link, and use native annotations after the user opens it; route export requests to `revela-export`.
32
+ - Existing deck artifact and the user asks to review, annotate, diagnose, QA, inspect, comment, or refine: use `revela-review`.
33
33
  - Existing deck artifact and the user asks for PDF, PPTX, or PNG output: use `revela-export`.
34
34
  - If the next step is still ambiguous after inspection, ask the smallest missing question and recommend the safest next specialist skill.
35
35
 
@@ -27,7 +27,8 @@ For status, inspection, activation, or selection:
27
27
 
28
28
  1. Call `revela_design_list`.
29
29
  2. Call `revela_design_read`, `revela_design_inventory`, `revela_design_read_layout`, `revela_design_read_component`, or `revela_page_template_foundation` as needed.
30
- 3. Call `revela_design_activate` only when the user asks to use a design.
30
+ 3. Call `revela_design_activate` only when the user asks to use a design for future planning/rendering.
31
+ 4. For an existing `decks/*.html` artifact, call `revela_switch_deck_design` with the deck file and design name. This refreshes the deck-local active design snapshot and can reopen the deck without rewriting slide content.
31
32
 
32
33
  For new or edited designs:
33
34
 
@@ -41,7 +42,7 @@ For new or edited designs:
41
42
  8. Call `revela_design_preview` for the draft, start a read-only local static server from the returned `browserHandoff.serveRoot`, and reply with the resulting localhost preview link for the user to open in Codex Browser.
42
43
  9. If validation or preview review fails, revise the draft content and repeat draft create/validate/preview.
43
44
  10. Call `revela_design_draft_install` only after the draft validates and the user intent is to install it.
44
- 11. Call `revela_design_activate` only when the user asks to make it active.
45
+ 11. Call `revela_design_activate` only when the user asks to make it active for future work; use `revela_switch_deck_design` for an already-rendered deck.
45
46
 
46
47
  For sharing or installing design archives:
47
48
 
@@ -78,13 +79,13 @@ Use `revela_design_create` only when the user explicitly requests direct local c
78
79
  - Asset metadata surfaced by read/inventory tools when `assets/**` exists.
79
80
  - Saved asset paths and intended uses, for example `assets/cover-background.png -> cover hero background`.
80
81
  - Validation result and any remaining diagnostics.
81
- - Whether the design was activated.
82
+ - Whether the design was activated or an existing deck was switched to that design.
82
83
  - Next step, usually `revela-research` for planning with the design or `revela-make-deck` when a valid `deck-plan.md` already exists.
83
84
 
84
85
  ## Must Not
85
86
 
86
87
  - Do not write `deck-plan.md`.
87
- - Do not write `decks/*.html`.
88
+ - Do not rewrite slide content in `decks/*.html` while switching design; use `revela_switch_deck_design` to refresh the deck-local active CSS/assets snapshot.
88
89
  - Do not patch `decks/_revela-design/**/design.css`; those files are regenerated deck-local snapshots.
89
90
  - Do not install or activate a design unless the user requested that outcome.
90
91
  - Do not invent licenses, asset provenance, or brand permissions.
@@ -46,7 +46,7 @@ Report:
46
46
  - `spec.md` exists but no `researches/`: run `revela-research`.
47
47
  - Research exists but no `deck-plan.md`: continue `revela-research` to the Planning Handoff.
48
48
  - Valid `deck-plan.md` but no deck artifact: run `revela-make-deck`.
49
- - Existing deck artifact: start a read-only local static server from the workspace root and surface the HTML deck as a localhost website card/link for Codex Browser native annotation, or run `revela-export` for PDF/PPTX/PNG.
49
+ - Existing deck artifact: run `revela-review` to open the HTML deck directly in Codex Browser, or run `revela-export` for PDF/PPTX/PNG.
50
50
 
51
51
  ## Must Not
52
52
 
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: revela-review
3
+ description: Open an existing Revela HTML deck directly in Codex Browser for user inspection.
4
+ ---
5
+
6
+ # Revela Review
7
+
8
+ Use this skill when the user asks to review, open, inspect, or look at an existing Revela HTML deck artifact in Codex Browser.
9
+
10
+ ## Contract
11
+
12
+ - Review is the user-facing browser step after a deck exists.
13
+ - The default path opens the HTML deck itself in Codex Browser without running QA first.
14
+ - Diagnostics are optional: call `revela_review_deck_read` only when the user explicitly asks for QA, readiness, diagnostics, or a written review summary.
15
+ - This skill does not open the legacy comment/apply browser surface, render new decks, or export PDF/PPTX/PNG.
16
+
17
+ ## Preconditions
18
+
19
+ - Required: a readable `decks/*.html` file or a user-provided deck HTML file path.
20
+ - If the user gives `@decks/<file>.html`, use that file.
21
+ - If the user asks to review "the deck" and exactly one `decks/*.html` file is present, use it.
22
+ - If multiple deck HTML files are present and the target is unclear, ask the user which deck to open.
23
+
24
+ ## Required Tool
25
+
26
+ Call `revela_open_deck` with:
27
+
28
+ - `workspaceRoot` when known.
29
+ - `file` set to the workspace-relative or absolute HTML deck path.
30
+ - `openBrowser` omitted or `true` for the normal user-facing flow.
31
+
32
+ ## Optional Diagnostics
33
+
34
+ When the user explicitly asks for QA, readiness, diagnostics, or a written review summary:
35
+
36
+ 1. Call `revela_review_deck_read` with the target deck file.
37
+ 2. Report artifact QA and deck-plan diagnostics concisely.
38
+ 3. Still open Review unless the file is missing or the user asked only for diagnostics.
39
+
40
+ ## Output
41
+
42
+ Report:
43
+
44
+ - The deck file opened.
45
+ - The direct deck URL returned by `revela_open_deck`.
46
+ - Whether Codex opened the browser.
47
+ - A short user prompt for what to inspect: copy, argument flow, hierarchy, spacing, charts/tables, visuals, and export readiness.
48
+ - If diagnostics were requested, include the concise diagnostic summary.
49
+
50
+ ## Must Not
51
+
52
+ - Do not run QA before opening Review unless the user explicitly asks for diagnostics.
53
+ - Do not open any legacy comment/apply browser surface or token-based review UI.
54
+ - Do not call export tools; route PDF/PPTX/PNG requests to `revela-export`.
55
+ - Do not generate or rewrite `deck-plan.md`.
56
+ - Do not generate a new HTML deck; route rendering requests to `revela-make-deck`.
57
+ - Do not open local deck files directly with `file://`; use the direct deck opener.