@cyber-dash-tech/revela 0.8.1 → 0.8.3

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/README.md CHANGED
@@ -205,7 +205,7 @@ Review the current deck state with:
205
205
 
206
206
  Minimum readiness conditions:
207
207
 
208
- - topic, audience, slide count, language, and visual style/design are decided
208
+ - topic, audience, language, visual style/design, and slide plan are decided
209
209
  - source materials are identified or explicitly deemed unnecessary
210
210
  - research need is assessed
211
211
  - needed research findings have been read and reflected in the slide specs
@@ -222,7 +222,6 @@ The gate checks:
222
222
  - `writeReadiness.blockers` is empty
223
223
  - the deck `outputPath` exactly matches the target `decks/*.html` path
224
224
  - all `requiredInputs` booleans are true
225
- - `slides.length` matches `slideCount` when a slide count is set
226
225
  - every slide has title, layout, components, and structured content
227
226
  - every needed research axis is `done`, `read`, or `skipped`
228
227
 
package/README.zh-CN.md CHANGED
@@ -204,7 +204,7 @@ Revela 使用工作区根目录的 `DECKS.json` 做跨会话记忆和 deck 生
204
204
 
205
205
  最小 readiness 条件:
206
206
 
207
- - topic、audience、slide count、language、visual style/design 已确定
207
+ - topic、audience、language、visual style/design 和 slide plan 已确定
208
208
  - source materials 已识别,或明确不需要源材料
209
209
  - research need 已评估
210
210
  - 需要调研时,相关 findings 已读取并反映到逐页规格中
@@ -221,7 +221,6 @@ Revela 使用工作区根目录的 `DECKS.json` 做跨会话记忆和 deck 生
221
221
  - `writeReadiness.blockers` 为空
222
222
  - deck `outputPath` 精确匹配目标 `decks/*.html` 路径
223
223
  - 所有 `requiredInputs` 布尔值都是 true
224
- - 如果设置了 `slideCount`,`slides.length` 必须匹配
225
224
  - 每页都有 title、layout、components 和结构化 content
226
225
  - 每个 needed research axis 都是 `done`、`read` 或 `skipped`
227
226
 
@@ -16,9 +16,10 @@ export function buildInitPrompt({
16
16
  Goal:
17
17
  - Build or update ${DECKS_STATE_FILE}, the workspace-level machine-readable state file for slide deck work.
18
18
  - Use the \`revela-decks\` tool for state updates. Do not write or patch ${DECKS_STATE_FILE} directly.
19
- - Capture stable project context, available source materials, the current deck spec, slide plan, and open questions for future sessions.
19
+ - Capture stable project context, professional/domain context, topic, audience, available source materials, the current deck artifact, slide specs, and open questions for future sessions.
20
20
  - Do not treat initialization as permission to write a slide deck; the current deck must pass a later readiness review.
21
21
  - ${DECKS_STATE_FILE} is the source of truth for the single current workspace deck.
22
+ - \`slides\` is the only slide-plan source of truth. Do not track or infer a separate slide count field.
22
23
 
23
24
  Current state:
24
25
  - ${mode}
@@ -45,8 +46,9 @@ Workflow:
45
46
  4. For selected PDF/PPTX/DOCX/XLSX files, call \`revela-extract-document-materials\` before deciding what to summarize.
46
47
  5. Read only the materials needed to form a conservative workspace memory. Do not exhaustively read every file if the workspace is large.
47
48
  6. Call \`revela-decks\` with action \`init\` to create ${DECKS_STATE_FILE} if needed.
48
- 7. If this conversation already contains a concrete deck task, call \`revela-decks\` with action \`upsertDeck\` and later \`upsertSlides\` only for explicit deck spec information. Do not pass or ask for a deck key; the tool uses the workspace folder name internally. Do not mark readiness ready during init.
49
- 8. Report what was initialized or updated and list any open questions.
49
+ 7. If this conversation or the workspace contains a concrete deck task or an existing deck artifact, call \`revela-decks\` with action \`upsertDeck\` and later \`upsertSlides\` for explicit deck information. Do not pass or ask for a deck key; the tool uses the workspace folder name internally. Do not mark readiness ready during init.
50
+ 8. When adopting an existing HTML deck, analyze the artifact and create one conservative \`SlideSpec\` per identifiable slide/page. The \`SlideSpec[]\` itself is the worklist; do not create a separate target slide count.
51
+ 9. Report what was initialized or updated and list any open questions.
50
52
 
51
53
  Memory rules:
52
54
  - Only write facts supported by workspace files into ${DECKS_STATE_FILE} workspace state, source materials, deck memory, and open questions.
@@ -45,7 +45,6 @@ export interface DeckSpec {
45
45
  goal: string
46
46
  audience?: string
47
47
  language?: string
48
- slideCount?: number
49
48
  outputPath: string
50
49
  theme: {
51
50
  design?: string
@@ -65,7 +64,6 @@ export interface DeckSpec {
65
64
  export interface RequiredInputs {
66
65
  topicClarified: boolean
67
66
  audienceClarified: boolean
68
- slideCountDecided: boolean
69
67
  languageDecided: boolean
70
68
  visualStyleSelected: boolean
71
69
  sourceMaterialsIdentified: boolean
@@ -179,17 +177,15 @@ export function normalizeWorkspaceDeckState(state: DecksState, workspaceRoot: st
179
177
 
180
178
  export function defaultRequiredInputs(overrides?: Partial<RequiredInputs>): RequiredInputs {
181
179
  return {
182
- topicClarified: false,
183
- audienceClarified: false,
184
- slideCountDecided: false,
185
- languageDecided: false,
186
- visualStyleSelected: false,
187
- sourceMaterialsIdentified: false,
188
- researchNeedAssessed: false,
189
- researchFindingsRead: false,
190
- slidePlanConfirmed: false,
191
- designLayoutsFetched: false,
192
- ...overrides,
180
+ topicClarified: overrides?.topicClarified ?? false,
181
+ audienceClarified: overrides?.audienceClarified ?? false,
182
+ languageDecided: overrides?.languageDecided ?? false,
183
+ visualStyleSelected: overrides?.visualStyleSelected ?? false,
184
+ sourceMaterialsIdentified: overrides?.sourceMaterialsIdentified ?? false,
185
+ researchNeedAssessed: overrides?.researchNeedAssessed ?? false,
186
+ researchFindingsRead: overrides?.researchFindingsRead ?? false,
187
+ slidePlanConfirmed: overrides?.slidePlanConfirmed ?? false,
188
+ designLayoutsFetched: overrides?.designLayoutsFetched ?? false,
193
189
  }
194
190
  }
195
191
 
@@ -201,7 +197,6 @@ export function createDeckSpec(input: Partial<DeckSpec> & { slug: string }): Dec
201
197
  goal: input.goal ?? "",
202
198
  audience: input.audience,
203
199
  language: input.language,
204
- slideCount: input.slideCount,
205
200
  outputPath: normalizeDeckPath(input.outputPath || `decks/${slug}.html`),
206
201
  theme: input.theme ?? {},
207
202
  requiredInputs: defaultRequiredInputs(input.requiredInputs),
@@ -419,9 +414,6 @@ function computeDeckBlockers(deck: DeckSpec): string[] {
419
414
  if (value !== true) blockers.push(`requiredInputs.${key} is not true`)
420
415
  }
421
416
 
422
- if (typeof deck.slideCount === "number" && deck.slideCount > 0 && deck.slides.length !== deck.slideCount) {
423
- blockers.push(`slides length ${deck.slides.length} does not match slideCount ${deck.slideCount}`)
424
- }
425
417
  if (deck.slides.length === 0) blockers.push("slides are missing")
426
418
  for (const slide of deck.slides) {
427
419
  if (!slide.title.trim()) blockers.push(`Slide ${slide.index} title is missing`)
@@ -6,18 +6,15 @@ import {
6
6
  DECKS_STATE_FILE,
7
7
  normalizeWorkspaceDeckState,
8
8
  readOrCreateDecksState,
9
- reviewDeckState,
10
9
  upsertDeck,
11
10
  upsertSlides,
12
11
  writeDecksState,
13
- type DeckStateReadinessResult,
14
12
  type SlideSpec,
15
13
  } from "../decks-state"
16
14
  import type { EditableDeck } from "./resolve-deck"
17
15
 
18
16
  export interface EditDeckStatePreflightResult {
19
17
  changed: boolean
20
- readiness: DeckStateReadinessResult
21
18
  }
22
19
 
23
20
  export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDeck): EditDeckStatePreflightResult {
@@ -27,7 +24,6 @@ export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDec
27
24
  throw new Error(`${DECKS_STATE_FILE} already points to ${active.outputPath}. Revela 0.8 expects one deck per workspace; move extra decks to a separate workspace.`)
28
25
  }
29
26
  const existing = state.decks[deck.slug]
30
- const existingReady = existing?.writeReadiness?.status === "ready" && existing.writeReadiness.blockers.length === 0
31
27
  let changed = !existing || existing.outputPath !== deck.file
32
28
 
33
29
  state = upsertDeck(state, {
@@ -36,7 +32,6 @@ export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDec
36
32
  goal: existing?.goal || `Edit existing Revela deck ${deck.slug}.`,
37
33
  audience: existing?.audience || "Existing deck viewers",
38
34
  language: existing?.language || "en",
39
- slideCount: existing?.slideCount || inferSlideCount(deck.absoluteFile),
40
35
  outputPath: deck.file,
41
36
  theme: {
42
37
  design: existing?.theme?.design || safeActiveDesign(),
@@ -46,7 +41,6 @@ export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDec
46
41
  ...existing?.requiredInputs,
47
42
  topicClarified: true,
48
43
  audienceClarified: true,
49
- slideCountDecided: true,
50
44
  languageDecided: true,
51
45
  visualStyleSelected: true,
52
46
  sourceMaterialsIdentified: true,
@@ -64,19 +58,13 @@ export function ensureEditableDeckState(workspaceRoot: string, deck: EditableDec
64
58
  changed = true
65
59
  }
66
60
 
67
- const reviewed = reviewDeckState(state, deck.slug)
68
- writeDecksState(workspaceRoot, reviewed.state)
61
+ writeDecksState(workspaceRoot, state)
69
62
 
70
63
  return {
71
- changed: changed || !existingReady,
72
- readiness: reviewed.result,
64
+ changed,
73
65
  }
74
66
  }
75
67
 
76
- function inferSlideCount(filePath: string): number {
77
- return inferSlides(filePath).length
78
- }
79
-
80
68
  function inferSlides(filePath: string): SlideSpec[] {
81
69
  const html = readFileSync(filePath, "utf-8")
82
70
  const chunks = html.match(/<section\b[\s\S]*?<\/section>/gi) || [html]
package/lib/edit/open.ts CHANGED
@@ -66,9 +66,6 @@ function openEditableDeckInternal(
66
66
  ): EnsureEditableDeckOpenResult {
67
67
  const deck = resolveEditableDeck(options.workspaceRoot, target)
68
68
  const preflight = ensureEditableDeckState(options.workspaceRoot, deck)
69
- if (!preflight.readiness.ready) {
70
- throw new Error(preflight.readiness.blocker || "Deck is not ready for HTML edits.")
71
- }
72
69
 
73
70
  ctx.enabled = true
74
71
  if (!existsSync(ACTIVE_PROMPT_FILE)) {
@@ -88,7 +85,7 @@ function openEditableDeckInternal(
88
85
  if (shouldOpen) (options.openUrl ?? openUrl)(url)
89
86
 
90
87
  const source = deck.source === "decks-state" ? "DECKS.json" : deck.source === "file-path" ? "file path" : "fallback path"
91
- const stateNote = preflight.changed ? "Deck state was prepared in DECKS.json before opening the editor." : "Deck state is ready in DECKS.json."
88
+ const stateNote = preflight.changed ? "Deck state was prepared in DECKS.json for visual editing." : "Deck state already points to this visual edit target."
92
89
 
93
90
  return {
94
91
  deck,
@@ -1,5 +1,6 @@
1
1
  import { randomBytes } from "crypto"
2
2
  import { readFileSync, statSync } from "fs"
3
+ import { resolve, sep } from "path"
3
4
  import type { EditableDeck } from "./resolve-deck"
4
5
  import { buildEditPrompt, type EditCommentPayload } from "./prompt"
5
6
 
@@ -85,6 +86,16 @@ export function hasLiveEditorSession(deck: EditableDeck, maxIdleMs = LIVE_EDITOR
85
86
  return existing ? isSessionLive(existing.session, maxIdleMs) : false
86
87
  }
87
88
 
89
+ export function hasLiveEditorSessionForFile(workspaceRoot: string, filePath: string, maxIdleMs = LIVE_EDITOR_IDLE_MS): boolean {
90
+ if (!filePath) return false
91
+ const root = resolve(workspaceRoot)
92
+ const absoluteFile = resolve(root, filePath)
93
+ if (absoluteFile !== root && !absoluteFile.startsWith(root.endsWith(sep) ? root : root + sep)) return false
94
+ cleanupExpiredSessions()
95
+ const existing = findSessionForDeck(absoluteFile)
96
+ return existing ? isSessionLive(existing.session, maxIdleMs) : false
97
+ }
98
+
88
99
  export function stopEditServer(): void {
89
100
  if (idleTimer) clearTimeout(idleTimer)
90
101
  idleTimer = undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "OpenCode plugin that turns AI into an HTML slide deck generator",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/plugin.ts CHANGED
@@ -47,6 +47,7 @@ import { handlePdf } from "./lib/commands/pdf"
47
47
  import { handlePptx } from "./lib/commands/pptx"
48
48
  import { handleEdit } from "./lib/commands/edit"
49
49
  import { ensureEditableDeckOpenForChange } from "./lib/edit/open"
50
+ import { hasLiveEditorSessionForFile } from "./lib/edit/server"
50
51
  import { handleDesignsPreview } from "./lib/commands/designs-preview"
51
52
  import {
52
53
  parseDesignsNewArgs,
@@ -540,6 +541,7 @@ Next step: use \`revela-decks\` with action \`init\`, \`upsertDeck\`, \`upsertSl
540
541
  return
541
542
  }
542
543
  if (!isDeckHtmlPath(filePath)) return
544
+ if (hasLiveEditorSessionForFile(workspaceRoot, filePath)) return
543
545
 
544
546
  const readiness = checkDeckStateWriteReadiness(workspaceRoot, filePath) ?? {
545
547
  ready: false,
@@ -595,6 +597,7 @@ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FI
595
597
 
596
598
  const targets = extractDeckHtmlTargetsFromPatch(patchText)
597
599
  if (targets.length === 0) return
600
+ if (targets.every((target) => hasLiveEditorSessionForFile(workspaceRoot, target))) return
598
601
 
599
602
  const blocked = targets
600
603
  .map((target) => ({
package/skill/SKILL.md CHANGED
@@ -23,7 +23,7 @@ Before writing any HTML, ask the user these questions **in a single message**
23
23
 
24
24
  1. **Topic** — What is the presentation about?
25
25
  2. **Audience** — Who will see it? (e.g. investors, team, conference, class)
26
- 3. **Slide count** — How many slides? (suggest 6–10 if unsure)
26
+ 3. **Scope** — How broad should the deck be? If the user has a preferred length, treat it as guidance only.
27
27
  4. **Language** — What language should the slides be in?
28
28
  5. **Reference materials** — Do you have any reference files to draw content from?
29
29
  (PDF research reports, Excel data, Word documents, PowerPoint decks, images)
@@ -43,7 +43,7 @@ research or writing HTML. The brief should capture:
43
43
  - working thesis or angle, if one has emerged
44
44
  - key questions the deck must answer
45
45
  - known workspace sources from `DECKS.json`, user attachments, or visible files
46
- - desired output shape, slide count, and visual direction
46
+ - desired output shape, approximate scope, and visual direction
47
47
 
48
48
  If the brief is unclear, ask 1–3 targeted clarification questions. Do not force
49
49
  the user to provide a research topic command; the working topic emerges from the
@@ -82,7 +82,7 @@ All subsequent file paths in this session use the current workspace deck:
82
82
  Create or update the active deck in `DECKS.json` through `revela-decks` actions
83
83
  `upsertDeck` and `upsertSlides`. Keep the deck spec current as work progresses:
84
84
  - `goal` — purpose and decision/context
85
- - `audience`, `language`, `slideCount`, `outputPath`, and `theme`
85
+ - `audience`, `language`, `outputPath`, and `theme`
86
86
  - `requiredInputs` — checklist state for prewrite readiness
87
87
  - `researchPlan` — axes, status, and findings files
88
88
  - `slides` — confirmed per-slide title, purpose, layout, components, content, evidence, visuals, and status
@@ -227,7 +227,7 @@ The exact visual style for each section comes from the active design.
227
227
  When the user asks for N slides, distribute them across these sections.
228
228
  A 6-slide deck might be: Cover → Background → Content × 3 → Closing.
229
229
  An 8-slide deck might be: Cover → TOC → Background → Content × 3 → Summary → Closing.
230
- Never skip Cover, Background, or Closing regardless of slide count.
230
+ Never skip Cover, Background, or Closing regardless of deck length.
231
231
 
232
232
  **Every `<section class="slide">` must include a `slide-qa` attribute.** Set
233
233
  `slide-qa="true"` for content-heavy layouts (those marked ✓ in the Layout Index
@@ -247,7 +247,7 @@ core rules and the visual design below.
247
247
 
248
248
  **When a domain definition is present:**
249
249
  - Follow its report structure instead of the default "Required Slide Structure" above.
250
- The domain defines its own sections, ordering, and slide count distribution.
250
+ The domain defines its own sections, ordering, and deck length guidance.
251
251
  - Follow its AI logic rules (e.g. terminology, evidence standards, risk frameworks).
252
252
  - The domain's visual preferences are **suggestions only** — the active Design's
253
253
  visual rules always take precedence for colors, fonts, animations, and layout.
package/tools/decks.ts CHANGED
@@ -28,7 +28,6 @@ export default tool({
28
28
  goal: tool.schema.string().optional().describe("For upsertDeck: deck goal."),
29
29
  audience: tool.schema.string().optional().describe("For upsertDeck: deck audience."),
30
30
  language: tool.schema.string().optional().describe("For upsertDeck: deck language."),
31
- slideCount: tool.schema.number().optional().describe("For upsertDeck: expected slide count."),
32
31
  outputPath: tool.schema.string().optional().describe("For upsertDeck: target output path, normally decks/{workspace-name}.html."),
33
32
  design: tool.schema.string().optional().describe("For upsertDeck: active design name."),
34
33
  domain: tool.schema.string().optional().describe("For upsertDeck: active domain name."),
@@ -37,7 +36,6 @@ export default tool({
37
36
  requiredInputs: tool.schema.object({
38
37
  topicClarified: tool.schema.boolean().optional(),
39
38
  audienceClarified: tool.schema.boolean().optional(),
40
- slideCountDecided: tool.schema.boolean().optional(),
41
39
  languageDecided: tool.schema.boolean().optional(),
42
40
  visualStyleSelected: tool.schema.boolean().optional(),
43
41
  sourceMaterialsIdentified: tool.schema.boolean().optional(),
@@ -111,7 +109,6 @@ export default tool({
111
109
  goal: args.goal ?? existing?.goal ?? "",
112
110
  audience: args.audience ?? existing?.audience,
113
111
  language: args.language ?? existing?.language,
114
- slideCount: args.slideCount ?? existing?.slideCount,
115
112
  outputPath: args.outputPath ?? existing?.outputPath,
116
113
  theme: {
117
114
  design: args.design ?? existing?.theme?.design,