@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.
- package/lib/edit/prompt.ts +2 -1
- package/lib/edit/server.ts +53 -14
- package/lib/qa/index.ts +4 -4
- package/package.json +1 -1
- package/plugin.ts +11 -5
- package/skill/SKILL.md +11 -8
- package/tools/qa.ts +2 -2
package/lib/edit/prompt.ts
CHANGED
|
@@ -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
|
-
-
|
|
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
|
}
|
package/lib/edit/server.ts
CHANGED
|
@@ -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
|
|
97
|
+
const version = readDeckVersion(session)
|
|
98
98
|
session.lastActiveAt = Date.now()
|
|
99
99
|
scheduleIdleStop()
|
|
100
|
-
return jsonResponse({ ok: true,
|
|
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.
|
|
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.
|
|
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
|
|
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 = '
|
|
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 === '
|
|
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
|
|
6
|
-
*
|
|
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
|
-
*
|
|
26
|
-
*
|
|
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
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
|
|
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]**
|
|
162
|
+
"---\n\n**[revela design compliance]** Static check completed:\n\n" +
|
|
158
163
|
formatReport(report)
|
|
159
164
|
)
|
|
160
165
|
} catch (e) {
|
|
161
|
-
childLog("
|
|
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
|
|
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
|
-
// ──
|
|
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
|
|
236
|
-
|
|
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
|
|
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
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
closest component from the Component Index.
|
|
389
|
-
|
|
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
|
|
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
|
-
"
|
|
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()
|