@cyber-dash-tech/revela 0.7.4 → 0.7.5

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.
@@ -76,6 +76,7 @@ Instructions:
76
76
  - Locate each target primarily with slideIndex, slideTitle, selected text, nearbyText, and outerHTMLExcerpt. Use selector/domPath as hints; they may be approximate.
77
77
  - Before patching or writing ${"`decks/*.html`"}, ensure ${"`DECKS.json`"} contains this deck and call ${"`revela-decks`"} with action ${"`review`"}. If ${"`DECKS.json`"} or the deck entry is missing, initialize/upsert the deck state with ${"`revela-decks`"} first. If readiness remains blocked, explain the blockers instead of forcing the edit.
78
78
  - Apply the edit to ${payload.file} only after readiness allows deck HTML changes.
79
- - Design compliance is checked automatically after deck writes. Run ${"`revela-qa`"} on ${payload.file} only when the edit may affect layout geometry, such as size, spacing, font scale, content amount, or container structure; fix any overflow it reports.
79
+ - Static design compliance is checked automatically after deck writes. If the tool result reports unknown classes, replace them with classes from the active design.
80
+ - Do not run QA after the edit unless the user explicitly asks for diagnostics. PDF/PPTX export commands run hard-error pre-export QA automatically.
80
81
  - If the comment is ambiguous, ask one concise clarification question instead of guessing.`
81
82
  }
@@ -94,16 +94,22 @@ async function handleRequest(req: Request): Promise<Response> {
94
94
 
95
95
  function handleDeckVersion(session: EditSession): Response {
96
96
  try {
97
- const stat = statSync(session.absoluteFile)
97
+ const version = readDeckVersion(session)
98
98
  session.lastActiveAt = Date.now()
99
99
  scheduleIdleStop()
100
- return jsonResponse({ ok: true, mtimeMs: stat.mtimeMs, size: stat.size })
100
+ return jsonResponse({ ok: true, ...version })
101
101
  } catch (error) {
102
102
  const message = error instanceof Error ? error.message : String(error)
103
103
  return jsonResponse({ ok: false, error: message }, 404)
104
104
  }
105
105
  }
106
106
 
107
+ function readDeckVersion(session: EditSession): { mtimeMs: number; size: number; version: string } {
108
+ const stat = statSync(session.absoluteFile)
109
+ const version = `${stat.mtimeMs}:${stat.size}`
110
+ return { mtimeMs: stat.mtimeMs, size: stat.size, version }
111
+ }
112
+
107
113
  async function handleComment(req: Request, session: EditSession): Promise<Response> {
108
114
  let body: Partial<EditCommentPayload>
109
115
  try {
@@ -132,6 +138,7 @@ async function handleComment(req: Request, session: EditSession): Promise<Respon
132
138
  elements,
133
139
  comments,
134
140
  })
141
+ const deckVersion = readDeckVersion(session).version
135
142
 
136
143
  await session.client.session.prompt({
137
144
  path: { id: session.sessionID },
@@ -142,7 +149,7 @@ async function handleComment(req: Request, session: EditSession): Promise<Respon
142
149
 
143
150
  session.lastActiveAt = Date.now()
144
151
  scheduleIdleStop()
145
- return jsonResponse({ ok: true })
152
+ return jsonResponse({ ok: true, deckVersion })
146
153
  }
147
154
 
148
155
  function validateSession(token: string | null): { ok: true; value: EditSession } | { ok: false; response: Response } {
@@ -233,11 +240,13 @@ function renderEditorShell(token: string): string {
233
240
  .comment-thread { display: flex; flex-direction: column; gap: 10px; max-height: 30vh; overflow: auto; }
234
241
  .comment-bubble { border: 1px solid #374151; border-radius: 14px; padding: 10px 12px; background: #0f172a; color: #e5e7eb; font-size: 13px; line-height: 1.45; }
235
242
  .comment-bubble.sending { border-color: rgba(56,189,248,.5); background: rgba(14,116,144,.14); }
236
- .comment-bubble.done { border-color: rgba(34,197,94,.55); background: rgba(22,101,52,.18); }
243
+ .comment-bubble.applied { border-color: rgba(34,197,94,.55); background: rgba(22,101,52,.18); }
244
+ .comment-bubble.stale { border-color: rgba(251,191,36,.6); background: rgba(120,53,15,.2); }
237
245
  .comment-bubble.failed { border-color: rgba(248,113,113,.65); background: rgba(127,29,29,.2); }
238
246
  .comment-bubble-text { white-space: pre-wrap; overflow-wrap: anywhere; }
239
247
  .comment-bubble-state { margin-top: 8px; color: #93c5fd; font-size: 12px; font-weight: 700; }
240
- .comment-bubble.done .comment-bubble-state { color: #86efac; }
248
+ .comment-bubble.applied .comment-bubble-state { color: #86efac; }
249
+ .comment-bubble.stale .comment-bubble-state { color: #fcd34d; }
241
250
  .comment-bubble.failed .comment-bubble-state { color: #fca5a5; }
242
251
  button { width: 100%; padding: 12px 14px; border: 0; border-radius: 12px; background: #38bdf8; color: #04111d; font-weight: 700; cursor: pointer; }
243
252
  button:disabled { cursor: not-allowed; opacity: .5; }
@@ -265,6 +274,7 @@ function renderEditorShell(token: string): string {
265
274
  <script>
266
275
  (() => {
267
276
  const token = ${encodedToken};
277
+ const COMMENT_STALE_MS = 60000;
268
278
  const state = {
269
279
  references: [],
270
280
  pendingComments: [],
@@ -374,7 +384,6 @@ function renderEditorShell(token: string): string {
374
384
  updateSendState();
375
385
  if (state.pendingRefreshMessage) {
376
386
  state.pendingRefreshMessage = false;
377
- markPendingCommentsDone();
378
387
  setStatus('Deck updated. Preview refreshed. Element references were cleared.');
379
388
  } else {
380
389
  setStatus(slides.length > 0 ? 'Editor ready. Found ' + slides.length + ' slides. Ctrl/Cmd + click to reference elements.' : 'Editor ready, but no .slide elements were found. Ctrl/Cmd + click to reference elements.');
@@ -394,13 +403,18 @@ function renderEditorShell(token: string): string {
394
403
  const res = await fetch('/api/deck-version?token=' + encodeURIComponent(token), { cache: 'no-store' });
395
404
  const body = await res.json().catch(() => ({}));
396
405
  if (!res.ok || !body.ok) throw new Error(body.error || 'Failed to check deck version');
397
- const nextVersion = String(body.mtimeMs) + ':' + String(body.size);
406
+ const nextVersion = body.version || (String(body.mtimeMs) + ':' + String(body.size));
398
407
  if (!state.deckVersion) {
399
408
  state.deckVersion = nextVersion;
409
+ markStaleComments();
410
+ return;
411
+ }
412
+ if (state.deckVersion === nextVersion) {
413
+ markStaleComments();
400
414
  return;
401
415
  }
402
- if (state.deckVersion === nextVersion) return;
403
416
  state.deckVersion = nextVersion;
417
+ markCommentsAppliedForVersion(nextVersion);
404
418
  refreshDeckPreview(body.mtimeMs);
405
419
  } catch (error) {
406
420
  reportError(error);
@@ -482,7 +496,7 @@ function renderEditorShell(token: string): string {
482
496
  });
483
497
  const body = await res.json().catch(() => ({}));
484
498
  if (!res.ok || !body.ok) throw new Error(body.error || 'Failed to send comment');
485
- updatePendingCommentStatus(commentId, 'sent');
499
+ updatePendingCommentStatus(commentId, 'sent', { baseDeckVersion: body.deckVersion || state.deckVersion });
486
500
  setStatus('Comment sent. Waiting for deck update...');
487
501
  updateSendState();
488
502
  } catch (error) {
@@ -555,29 +569,53 @@ function renderEditorShell(token: string): string {
555
569
  text,
556
570
  elements,
557
571
  status,
572
+ createdAt: Date.now(),
573
+ baseDeckVersion: state.deckVersion,
574
+ appliedVersion: null,
558
575
  });
559
576
  renderCommentThread();
560
577
  return id;
561
578
  }
562
579
 
563
- function updatePendingCommentStatus(id, status) {
580
+ function updatePendingCommentStatus(id, status, updates) {
564
581
  const comment = state.pendingComments.find((item) => item.id === id);
565
582
  if (!comment) return;
583
+ if (comment.status === 'applied' && status !== 'failed') return;
566
584
  comment.status = status;
585
+ if (updates) Object.assign(comment, updates);
567
586
  renderCommentThread();
568
587
  }
569
588
 
570
- function markPendingCommentsDone() {
589
+ function markCommentsAppliedForVersion(version) {
571
590
  let changed = false;
572
591
  state.pendingComments.forEach((comment) => {
573
- if (comment.status === 'sent' || comment.status === 'sending') {
574
- comment.status = 'done';
592
+ if ((comment.status === 'sent' || comment.status === 'sending' || comment.status === 'stale') && comment.baseDeckVersion !== version) {
593
+ comment.status = 'applied';
594
+ comment.appliedVersion = version;
575
595
  changed = true;
576
596
  }
577
597
  });
578
598
  if (changed) renderCommentThread();
579
599
  }
580
600
 
601
+ function markStaleComments() {
602
+ const now = Date.now();
603
+ let changed = false;
604
+ const hasWaiting = state.pendingComments.some((comment) => {
605
+ if (comment.status !== 'sent' && comment.status !== 'sending') return false;
606
+ if (now - comment.createdAt < COMMENT_STALE_MS) return true;
607
+ comment.status = 'stale';
608
+ changed = true;
609
+ return true;
610
+ });
611
+ if (changed) {
612
+ renderCommentThread();
613
+ setStatus('Still waiting for deck file update. If OpenCode already finished, refresh the editor.');
614
+ } else if (hasWaiting) {
615
+ setStatus('Comment sent. Waiting for deck update...');
616
+ }
617
+ }
618
+
581
619
  function renderCommentThread() {
582
620
  els.commentThread.textContent = '';
583
621
  state.pendingComments.forEach((comment) => {
@@ -599,7 +637,8 @@ function renderEditorShell(token: string): string {
599
637
  }
600
638
 
601
639
  function commentStatusLabel(status) {
602
- if (status === 'done') return '✅ Applied';
640
+ if (status === 'applied') return '✅ Applied';
641
+ if (status === 'stale') return 'Still waiting for deck file update';
603
642
  if (status === 'failed') return 'Failed to send';
604
643
  if (status === 'sending') return 'Sending to OpenCode...';
605
644
  return '⏳ Sent to OpenCode';
package/lib/qa/index.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * lib/qa/index.ts
3
3
  *
4
4
  * Public entry point for hard-error slide QA.
5
- * Runs overflow measurement only; design compliance is an automatic post-write
6
- * hook concern, not part of manual/export QA.
5
+ * Runs overflow measurement only. Static design compliance is handled by a
6
+ * separate post-write/post-patch/post-edit hook.
7
7
  */
8
8
 
9
9
  import { measureSlides } from "./measure"
@@ -22,8 +22,8 @@ export type { RunChecksOptions } from "./checks"
22
22
  * 3. Runs hard-error overflow checks only
23
23
  * 4. Returns a structured QAReport
24
24
  *
25
- * Pass `vocabulary` (from `extractDesignClasses()`) to enable compliance checks.
26
- * Omit it to run geometry-only checks (backward compatible).
25
+ * The optional `vocabulary` argument is retained for backward compatibility;
26
+ * compliance is intentionally not part of hard-error QA.
27
27
  *
28
28
  * Throws if the file cannot be opened or Chrome is not found.
29
29
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyber-dash-tech/revela",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
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
@@ -11,7 +11,7 @@
11
11
  * 5. experimental.chat.system.transform: inject three-layer prompt when enabled
12
12
  * 6. chat.message: intercept @-referenced / pasted binary files → extract text → replace FilePart with TextPart
13
13
  * 7. tool.execute.before: intercept read on DOCX/PPTX/XLSX → preRead()
14
- * 8. tool.execute.after: intercept read on PDF/images → postRead(); run fast design compliance after deck writes/patches/edits
14
+ * 8. tool.execute.after: intercept read on PDF/images → postRead(); run static compliance after deck writes/patches/edits
15
15
  */
16
16
 
17
17
  import type { Plugin } from "@opencode-ai/plugin"
@@ -98,6 +98,11 @@ const INTERNAL_AGENT_SIGNATURES = [
98
98
  ]
99
99
 
100
100
  function appendToolResult(output: any, text: string): void {
101
+ if (typeof output.output === "string") {
102
+ output.output = (output.output ? output.output + "\n\n" : "") + text
103
+ return
104
+ }
105
+
101
106
  const existing = output.result ?? ""
102
107
  output.result = (existing ? existing + "\n\n" : "") + text
103
108
  }
@@ -154,11 +159,11 @@ const server: Plugin = (async (pluginCtx) => {
154
159
 
155
160
  appendToolResult(
156
161
  output,
157
- "---\n\n**[revela design compliance]** Auto-check completed:\n\n" +
162
+ "---\n\n**[revela design compliance]** Static check completed:\n\n" +
158
163
  formatReport(report)
159
164
  )
160
165
  } catch (e) {
161
- childLog("qa").warn("auto compliance failed", {
166
+ childLog("compliance").warn("static compliance failed", {
162
167
  filePath,
163
168
  error: e instanceof Error ? e.message : String(e),
164
169
  })
@@ -613,7 +618,8 @@ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FI
613
618
  // Handles PDF and images — read tool succeeds with base64 attachment.
614
619
  // PDF: extract text, remove base64. Images: jimp compress.
615
620
  //
616
- // Also handles: fast design compliance after writing/patching/editing decks/*.html
621
+ // Also reports writes/patches blocked by the DECKS.json prewrite gate and
622
+ // runs lightweight static design compliance after successful deck changes.
617
623
  "tool.execute.after": async (input, output) => {
618
624
  if (!ctx.enabled) return
619
625
 
@@ -630,7 +636,7 @@ Next step: use \`revela-decks\` or \`/revela review\` to update ${DECKS_STATE_FI
630
636
  return
631
637
  }
632
638
 
633
- // ── Fast design compliance after deck HTML writes/patches/edits ───
639
+ // ── Report blocked deck writes and run static compliance ──────────
634
640
  if (input.tool === "write") {
635
641
  const filePath: string = input.args?.filePath ?? ""
636
642
  const blockedReason = blockedDeckWrites.get(filePath)
package/skill/SKILL.md CHANGED
@@ -232,8 +232,8 @@ layouts (cover, TOC, closing, quote, summary, etc.). When unsure, use `"false"`.
232
232
 
233
233
  Example: `<section class="slide" slide-qa="true" data-index="0">`
234
234
 
235
- The current QA path treats this as deck metadata. Automated checks focus on
236
- design compliance and hard overflow errors, not subjective fill or spacing.
235
+ The export QA path treats this as deck metadata. It is consumed when PDF/PPTX
236
+ export runs preflight checks.
237
237
 
238
238
  ### Domain Context
239
239
 
@@ -323,7 +323,7 @@ After generating, briefly tell the user:
323
323
  - How to navigate (arrow keys / swipe)
324
324
  - One line invitation to request changes
325
325
 
326
- Then use `revela-decks` to record written/QA status when available. Preserve
326
+ Then use `revela-decks` to record written status when available. Preserve
327
327
  stable decisions in deck memory when useful.
328
328
 
329
329
  For change requests: re-generate the **entire** file (don't patch). Apply the
@@ -382,11 +382,14 @@ exclusively for fine-tuning spacing and sizing (`margin`, `padding`, `gap`,
382
382
  component — **NEVER adapt the component structure to fit content. NEVER create
383
383
  a new component because the existing ones "don't quite fit".**
384
384
 
385
- The automatic compliance check will flag any unrecognised CSS class. If the
386
- QA report contains compliance issues after you write the file, you MUST
387
- fix them immediately remove the offending classes and replace them with the
388
- closest component from the Component Index. Do not move on until all compliance
389
- issues are resolved.
385
+ The automatic static compliance check will flag any unrecognised CSS class after
386
+ deck HTML writes or patches. If the tool result reports compliance issues, fix
387
+ them immediately by removing the offending classes and replacing them with the
388
+ closest component from the Component Index.
389
+
390
+ Do not run `revela-qa` after writing or editing HTML unless the user explicitly
391
+ asks for diagnostics. PDF/PPTX export commands run hard-error pre-export QA
392
+ automatically and will report overflow issues that must be fixed before exporting.
390
393
 
391
394
  ### Inline Editing
392
395
 
package/tools/qa.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * revela-qa — Hard-error quality assurance for generated slide HTML files.
5
5
  *
6
- * Exposed to the LLM so it can check overflow and design compliance.
6
+ * Exposed as a manual diagnostic tool. Export commands run pre-export QA automatically.
7
7
  */
8
8
 
9
9
  import { tool } from "@opencode-ai/plugin"
@@ -17,7 +17,7 @@ export default tool({
17
17
  "Opens the file in a headless browser and measures actual rendered geometry. " +
18
18
  "Checks for element overflow. " +
19
19
  "Returns a structured report with specific issues and fix instructions. " +
20
- "Call this when an edit may affect layout, or before delivery if export commands are not used.",
20
+ "Normally PDF/PPTX export commands run this automatically; call it directly only for explicit diagnostics.",
21
21
  args: {
22
22
  file: tool.schema
23
23
  .string()