@cyber-dash-tech/revela 0.18.8 → 0.18.9
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 +4 -4
- package/README.zh-CN.md +4 -4
- package/lib/refine/server.ts +141 -62
- package/package.json +1 -1
- package/plugins/revela/.mcp.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**English** | [中文](README.zh-CN.md)
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="560" />
|
|
@@ -34,7 +34,7 @@ To install globally, add the same entry to `~/.config/opencode/opencode.json`.
|
|
|
34
34
|
Requirements:
|
|
35
35
|
|
|
36
36
|
- The Codex CLI must be installed and the `codex` command must be available in your shell.
|
|
37
|
-
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.18.
|
|
37
|
+
- Your environment must be able to run `npx`; Revela uses `npx -y @cyber-dash-tech/revela@0.18.9 mcp` to start the MCP server.
|
|
38
38
|
- For interactive Review Apply actions, `codex exec` must also work because the Review UI uses it after saved comments are applied.
|
|
39
39
|
|
|
40
40
|
Optional preflight:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
Install Revela through the Codex Git marketplace:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.9
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.18.
|
|
62
|
+
The Git marketplace install provides the Codex plugin shell, skills, hooks, and MCP configuration. When Codex starts the Revela MCP server for the first time, it runs `npx -y @cyber-dash-tech/revela@0.18.9 mcp` so npm can fetch the published package and its dependencies.
|
|
63
63
|
|
|
64
64
|
You do not need to run `bun install` inside the Codex marketplace clone.
|
|
65
65
|
|
package/README.zh-CN.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.md) | **中文**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](https://www.npmjs.com/package/@cyber-dash-tech/revela) [](LICENSE) [](tests/) [](https://opencode.ai) [](https://bun.sh)
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<img src="assets/img/logo.png" alt="Revela" width="560" />
|
|
@@ -34,7 +34,7 @@ Revela 可在 [OpenCode](https://opencode.ai) 和 Codex 中使用,把来源材
|
|
|
34
34
|
环境要求:
|
|
35
35
|
|
|
36
36
|
- 需要已安装 Codex CLI,并且 shell 中可以执行 `codex`。
|
|
37
|
-
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.18.
|
|
37
|
+
- 环境中需要可以执行 `npx`;Revela 会用 `npx -y @cyber-dash-tech/revela@0.18.9 mcp` 启动 MCP server。
|
|
38
38
|
- 如果使用 Review UI 的 Apply,需要 `codex exec` 可用;评论会先保存,点击 Apply 后才执行修复。
|
|
39
39
|
|
|
40
40
|
可选的安装前检查:
|
|
@@ -55,11 +55,11 @@ npm_config_cache=/tmp/revela-npm-cache bun run smoke:mcp-pack
|
|
|
55
55
|
通过 Codex Git marketplace 安装 Revela:
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.
|
|
58
|
+
codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref v0.18.9
|
|
59
59
|
codex plugin add revela@revela
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.18.
|
|
62
|
+
Git marketplace 安装的是 Codex plugin 壳、skills、hooks 和 MCP 配置。Codex 第一次启动 Revela MCP server 时,会运行 `npx -y @cyber-dash-tech/revela@0.18.9 mcp`,由 npm 获取已发布 package 及其 dependencies。
|
|
63
63
|
|
|
64
64
|
不需要在 Codex marketplace clone 里运行 `bun install`。
|
|
65
65
|
|
package/lib/refine/server.ts
CHANGED
|
@@ -224,6 +224,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
224
224
|
if (url.pathname === "/api/comment" && req.method === "POST") {
|
|
225
225
|
const session = validateSession(url.searchParams.get("token"))
|
|
226
226
|
if (!session.ok) return session.response
|
|
227
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
227
228
|
return handleComment(req, session.value)
|
|
228
229
|
}
|
|
229
230
|
|
|
@@ -236,6 +237,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
236
237
|
if (url.pathname === "/api/comments" && req.method === "POST") {
|
|
237
238
|
const session = validateSession(url.searchParams.get("token"))
|
|
238
239
|
if (!session.ok) return session.response
|
|
240
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
239
241
|
return handleReviewCommentCreate(req, session.value)
|
|
240
242
|
}
|
|
241
243
|
|
|
@@ -243,6 +245,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
243
245
|
if (applyMatch && req.method === "POST") {
|
|
244
246
|
const session = validateSession(url.searchParams.get("token"))
|
|
245
247
|
if (!session.ok) return session.response
|
|
248
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
246
249
|
return handleReviewCommentApply(decodeURIComponent(applyMatch[1]), req, session.value)
|
|
247
250
|
}
|
|
248
251
|
|
|
@@ -250,6 +253,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
250
253
|
if (stopMatch && req.method === "POST") {
|
|
251
254
|
const session = validateSession(url.searchParams.get("token"))
|
|
252
255
|
if (!session.ok) return session.response
|
|
256
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
253
257
|
return handleReviewCommentStop(decodeURIComponent(stopMatch[1]), session.value)
|
|
254
258
|
}
|
|
255
259
|
|
|
@@ -257,6 +261,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
257
261
|
if (deleteMatch && req.method === "DELETE") {
|
|
258
262
|
const session = validateSession(url.searchParams.get("token"))
|
|
259
263
|
if (!session.ok) return session.response
|
|
264
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
260
265
|
return handleReviewCommentDelete(decodeURIComponent(deleteMatch[1]), session.value)
|
|
261
266
|
}
|
|
262
267
|
|
|
@@ -299,6 +304,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
299
304
|
if (url.pathname === "/api/visual-changes" && req.method === "POST") {
|
|
300
305
|
const session = validateSession(url.searchParams.get("token"))
|
|
301
306
|
if (!session.ok) return session.response
|
|
307
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
302
308
|
return handleVisualChanges(req, session.value)
|
|
303
309
|
}
|
|
304
310
|
|
|
@@ -311,6 +317,7 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
311
317
|
if (url.pathname === "/api/assets/save" && req.method === "POST") {
|
|
312
318
|
const session = validateSession(url.searchParams.get("token"))
|
|
313
319
|
if (!session.ok) return session.response
|
|
320
|
+
if (session.value.activeApplyCommentId) return applyLockedResponse()
|
|
314
321
|
return handleAssetSave(req, session.value)
|
|
315
322
|
}
|
|
316
323
|
|
|
@@ -323,6 +330,14 @@ async function handleRequest(req: Request): Promise<Response> {
|
|
|
323
330
|
return textResponse("Not found", 404)
|
|
324
331
|
}
|
|
325
332
|
|
|
333
|
+
function applyLockedResponse(): Response {
|
|
334
|
+
return jsonResponse({
|
|
335
|
+
ok: false,
|
|
336
|
+
code: "apply_locked",
|
|
337
|
+
error: "Wait for the current apply to finish before making another change.",
|
|
338
|
+
}, 409)
|
|
339
|
+
}
|
|
340
|
+
|
|
326
341
|
async function handleAssetSearch(url: URL, session: EditSession): Promise<Response> {
|
|
327
342
|
const query = (url.searchParams.get("query") || "").trim()
|
|
328
343
|
if (!query) return jsonResponse({ ok: false, error: "query is required" }, 400)
|
|
@@ -888,17 +903,7 @@ function handleReviewCommentDelete(commentId: string, session: EditSession): Res
|
|
|
888
903
|
|
|
889
904
|
async function enqueueOrStartPersistedReviewCommentApply(session: EditSession, comment: ReviewCommentRecord, body: any = {}): Promise<Response> {
|
|
890
905
|
session.applyQueue = session.applyQueue ?? []
|
|
891
|
-
if (session.activeApplyCommentId
|
|
892
|
-
const current = readReviewComment(session.workspaceRoot, comment.id) ?? comment
|
|
893
|
-
return jsonResponse({
|
|
894
|
-
ok: true,
|
|
895
|
-
requestId: current.lastApplyRequestId,
|
|
896
|
-
commentRequestId: current.lastApplyRequestId,
|
|
897
|
-
deckVersion: readDeckVersion(session).version,
|
|
898
|
-
status: "pending",
|
|
899
|
-
comment: current,
|
|
900
|
-
})
|
|
901
|
-
}
|
|
906
|
+
if (session.activeApplyCommentId) return applyLockedResponse()
|
|
902
907
|
|
|
903
908
|
const queuedIndex = session.applyQueue.indexOf(comment.id)
|
|
904
909
|
if (queuedIndex >= 0) {
|
|
@@ -912,20 +917,6 @@ async function enqueueOrStartPersistedReviewCommentApply(session: EditSession, c
|
|
|
912
917
|
})
|
|
913
918
|
}
|
|
914
919
|
|
|
915
|
-
if (session.activeApplyCommentId) {
|
|
916
|
-
session.applyQueue.push(comment.id)
|
|
917
|
-
const queued = markReviewCommentQueued(session.workspaceRoot, comment.id) ?? comment
|
|
918
|
-
session.lastActiveAt = Date.now()
|
|
919
|
-
scheduleIdleStop()
|
|
920
|
-
return jsonResponse({
|
|
921
|
-
ok: true,
|
|
922
|
-
deckVersion: readDeckVersion(session).version,
|
|
923
|
-
status: "queued",
|
|
924
|
-
queuePosition: session.applyQueue.length,
|
|
925
|
-
comment: queued,
|
|
926
|
-
})
|
|
927
|
-
}
|
|
928
|
-
|
|
929
920
|
return startPersistedReviewCommentApply(session, comment, body)
|
|
930
921
|
}
|
|
931
922
|
|
|
@@ -1387,31 +1378,31 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1387
1378
|
.comment-editor:empty::before { content: attr(data-placeholder); color: #94a3b8; pointer-events: none; }
|
|
1388
1379
|
.ref-chip { display: inline-flex; align-items: center; max-width: 32ch; overflow: hidden; text-overflow: ellipsis; margin: 0 2px; padding: 1px 7px; border-radius: 999px; background: var(--ref-bg, #eff6ff); color: var(--ref-text, #1d4ed8); border: 1px solid var(--ref-border, #bfdbfe); font-weight: 800; white-space: nowrap; }
|
|
1389
1380
|
.activity-panel { display: flex; flex: 1 1 auto; flex-direction: column; gap: 8px; min-height: 0; padding-top: 2px; }
|
|
1390
|
-
.comment-thread { display: flex; flex: 1 1 auto; flex-direction: column; gap:
|
|
1381
|
+
.comment-thread { display: flex; flex: 1 1 auto; flex-direction: column; gap: 16px; min-height: 160px; overflow-y: auto; overflow-x: hidden; padding: 12px 14px; }
|
|
1391
1382
|
.comment-thread:empty::before { content: "No activity yet. Leave a comment to start."; display: block; padding: 14px; border: 1px dashed #cbd5e1; border-radius: 16px; color: #64748b; font-size: 12px; line-height: 1.45; background: #f8fafc; box-shadow: none; }
|
|
1392
|
-
.comment-bubble { position: relative; display: flex; flex: 0 0
|
|
1393
|
-
.comment-bubble:hover { transform: translateY(-1px); border-color: #cbd5e1; box-shadow: 0 8px 20px rgba(15,23,42,.07)
|
|
1394
|
-
.comment-bubble.active { border-color: #93c5fd; box-shadow: 0 0 0 3px rgba(59,130,246,.12), 0 8px 20px rgba(15,23,42,.08)
|
|
1395
|
-
.comment-bubble.sending {
|
|
1396
|
-
.comment-bubble.open {
|
|
1397
|
-
.comment-bubble.queued {
|
|
1398
|
-
.comment-bubble.applying {
|
|
1399
|
-
.comment-bubble.
|
|
1400
|
-
.comment-bubble.
|
|
1401
|
-
.comment-bubble.
|
|
1402
|
-
.comment-bubble.
|
|
1383
|
+
.comment-bubble { position: relative; display: flex; flex: 0 0 150px; flex-direction: column; min-height: 150px; max-height: 150px; overflow: hidden; border: 1px solid #e2e8f0; border-radius: 15px; padding: 14px 15px 13px 16px; background: #ffffff; color: #334155; font-size: 13px; line-height: 1.45; box-shadow: 0 1px 2px rgba(15,23,42,.04); transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; cursor: pointer; }
|
|
1384
|
+
.comment-bubble:hover { transform: translateY(-1px); border-color: #cbd5e1; box-shadow: 0 8px 20px rgba(15,23,42,.07); }
|
|
1385
|
+
.comment-bubble.active { border-color: #93c5fd; box-shadow: 0 0 0 3px rgba(59,130,246,.12), 0 8px 20px rgba(15,23,42,.08); }
|
|
1386
|
+
.comment-bubble.sending { border-color: #bfdbfe; background: #f8fbff; }
|
|
1387
|
+
.comment-bubble.open { border-color: #e2e8f0; background: #ffffff; }
|
|
1388
|
+
.comment-bubble.queued { border-color: #fde68a; background: #fffdf4; }
|
|
1389
|
+
.comment-bubble.applying { border: 2px solid transparent; padding: 13px 14px 12px 15px; background: linear-gradient(#f8fbff, #f8fbff) padding-box, conic-gradient(from var(--comment-aurora-angle), #10b981, #14b8a6, #22d3ee, #2563eb, #7c3aed, #d946ef, #10b981) border-box; box-shadow: 0 0 0 1px rgba(20,184,166,.14), 0 0 18px rgba(6,182,212,.26), 0 0 28px rgba(124,58,237,.18), 0 8px 22px rgba(15,23,42,.08); animation: comment-aurora-flow 2.8s linear infinite; }
|
|
1390
|
+
.comment-bubble.applying.active { border-color: transparent; box-shadow: 0 0 0 3px rgba(6,182,212,.16), 0 0 22px rgba(20,184,166,.26), 0 0 32px rgba(124,58,237,.2), 0 8px 22px rgba(15,23,42,.09); }
|
|
1391
|
+
.comment-bubble.applied { border-color: #bbf7d0; background: #f8fefb; }
|
|
1392
|
+
.comment-bubble.updated { border-color: #bbf7d0; background: #f7fef9; }
|
|
1393
|
+
.comment-bubble.stale { border-color: #fed7aa; background: #fffaf0; }
|
|
1394
|
+
.comment-bubble.failed { border-color: #fecaca; background: #fffafa; }
|
|
1403
1395
|
.comment-bubble-text { flex: 1 1 auto; min-height: 0; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
1404
|
-
.comment-bubble-state { margin-top: 8px; align-self: flex-start; padding:
|
|
1396
|
+
.comment-bubble-state { margin-top: 8px; align-self: flex-start; padding: 0; background: transparent; color: #475569; font-size: 11px; font-weight: 800; }
|
|
1405
1397
|
.comment-bubble-meta { margin-bottom: 6px; color: #64748b; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
|
|
1406
|
-
.comment-actions { position: absolute; right:
|
|
1398
|
+
.comment-actions { position: absolute; right: 11px; bottom: 11px; display: flex; gap: 6px; }
|
|
1407
1399
|
.comment-action-button { display: inline-flex; align-items: center; justify-content: center; width: 30px; min-width: 30px; height: 30px; min-height: 30px; padding: 0; border-radius: 999px; border-color: #e2e8f0; background: rgba(255,255,255,.92); color: #475569; box-shadow: 0 1px 2px rgba(15,23,42,.06); }
|
|
1408
1400
|
.comment-action-button:hover:not(:disabled) { background: #f1f5f9; color: #111827; transform: translateY(-1px); }
|
|
1409
1401
|
.comment-action-button.danger { color: #dc2626; }
|
|
1410
1402
|
.comment-action-button.stop { color: #b45309; }
|
|
1411
1403
|
.comment-action-icon { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
|
|
1412
1404
|
.comment-progress { margin-top: 7px; display: flex; flex-direction: column; gap: 4px; color: #475569; font-size: 12px; }
|
|
1413
|
-
.comment-progress-line { display: flex; gap: 8px; align-items: flex-start; padding:
|
|
1414
|
-
.comment-progress-line::before { content: ""; width: 7px; height: 7px; margin-top: 5px; border-radius: 999px; background: #2563eb; box-shadow: 0 0 0 4px rgba(37,99,235,.12); flex: 0 0 auto; animation: progress-pulse 1.2s ease-in-out infinite; }
|
|
1405
|
+
.comment-progress-line { display: flex; gap: 8px; align-items: flex-start; padding: 0; border: 0; background: transparent; }
|
|
1415
1406
|
.comment-raw { margin-top: 8px; color: #b91c1c; font-size: 12px; }
|
|
1416
1407
|
.comment-raw summary { cursor: pointer; font-weight: 800; }
|
|
1417
1408
|
.comment-raw pre { margin: 6px 0 0; max-height: 160px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; background: #f8fafc; border: 1px solid #fecaca; border-radius: 8px; padding: 8px; }
|
|
@@ -1433,12 +1424,12 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1433
1424
|
.codex-log-modal .codex-log-list { margin: 0; padding: 14px; max-height: none; overflow: auto; }
|
|
1434
1425
|
.codex-log-modal .codex-log-entry { background: transparent; }
|
|
1435
1426
|
.codex-log-modal .codex-log-detail { max-height: 260px; }
|
|
1436
|
-
.comment-bubble.updated .comment-bubble-state {
|
|
1437
|
-
.comment-bubble.applied .comment-bubble-state {
|
|
1438
|
-
.comment-bubble.applying .comment-bubble-state {
|
|
1439
|
-
.comment-bubble.queued .comment-bubble-state {
|
|
1440
|
-
.comment-bubble.stale .comment-bubble-state {
|
|
1441
|
-
.comment-bubble.failed .comment-bubble-state {
|
|
1427
|
+
.comment-bubble.updated .comment-bubble-state { color: #166534; }
|
|
1428
|
+
.comment-bubble.applied .comment-bubble-state { color: #166534; }
|
|
1429
|
+
.comment-bubble.applying .comment-bubble-state { color: #0f766e; }
|
|
1430
|
+
.comment-bubble.queued .comment-bubble-state { color: #92400e; }
|
|
1431
|
+
.comment-bubble.stale .comment-bubble-state { color: #9a3412; }
|
|
1432
|
+
.comment-bubble.failed .comment-bubble-state { color: #991b1b; }
|
|
1442
1433
|
.inspect-actions { display: flex; flex-direction: column; gap: 8px; }
|
|
1443
1434
|
.inspect-options { display: flex; flex-direction: column; gap: 5px; }
|
|
1444
1435
|
.inspect-options label { color: #64748b; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
|
|
@@ -1465,10 +1456,11 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1465
1456
|
.asset-card.is-saving::after { content: ""; position: absolute; inset: 0; background: rgba(15,23,42,.32); }
|
|
1466
1457
|
.asset-card.is-saving .asset-save { z-index: 1; }
|
|
1467
1458
|
.asset-card.is-saved-candidate .asset-thumb { opacity: .72; }
|
|
1459
|
+
@property --comment-aurora-angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
|
|
1468
1460
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
1469
|
-
@keyframes
|
|
1461
|
+
@keyframes comment-aurora-flow { to { --comment-aurora-angle: 360deg; } }
|
|
1470
1462
|
@keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
|
|
1471
|
-
@media (prefers-reduced-motion: reduce) { .comment-bubble, .
|
|
1463
|
+
@media (prefers-reduced-motion: reduce) { .comment-bubble, .skeleton-line, .spinner { animation: none !important; transition: none !important; } .comment-bubble:hover { transform: none; } }
|
|
1472
1464
|
.asset-search { display: grid; grid-template-columns: minmax(0, 1fr) 118px; gap: 8px; }
|
|
1473
1465
|
.asset-search input, .asset-search select { min-width: 0; padding: 10px 11px; border: 1px solid #dbe3ef; border-radius: 12px; background: #ffffff; color: #111827; font: inherit; font-size: 12px; font-weight: 700; outline: none; }
|
|
1474
1466
|
.asset-search input:focus, .asset-search select:focus { border-color: #93c5fd; box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
|
|
@@ -1511,12 +1503,17 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1511
1503
|
.composer-send .spinner + span { display: none; }
|
|
1512
1504
|
.composer-icon { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
|
1513
1505
|
.status { min-height: 20px; color: #64748b; font-size: 13px; line-height: 1.45; }
|
|
1506
|
+
.apply-lock-overlay { position: absolute; inset: 0; z-index: 20; display: none; align-items: center; justify-content: center; padding: 24px; background: rgba(248,250,252,.52); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); pointer-events: auto; }
|
|
1507
|
+
.apply-lock-overlay.open { display: flex; }
|
|
1508
|
+
.apply-lock-card { width: min(320px, calc(100% - 48px)); padding: 16px 18px; border: 1px solid rgba(20,184,166,.26); border-radius: 16px; background: rgba(255,255,255,.94); color: #0f172a; box-shadow: 0 24px 70px rgba(15,23,42,.2), 0 0 36px rgba(6,182,212,.16); text-align: center; }
|
|
1509
|
+
.apply-lock-card strong { display: block; margin-bottom: 4px; font-size: 13px; font-weight: 900; }
|
|
1510
|
+
.apply-lock-card span { color: #64748b; font-size: 12px; line-height: 1.4; }
|
|
1514
1511
|
@media (max-width: 900px) { .app { grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) auto; } .resize-handle { display: none; } aside { max-height: 48vh; min-width: 0; border-left: 0; border-top: 1px solid #e2e8f0; } .deck-nav { bottom: 10px; } .asset-search { grid-template-columns: 1fr; } }
|
|
1515
1512
|
</style>
|
|
1516
1513
|
</head>
|
|
1517
1514
|
<body class="${bodyClass}">
|
|
1518
1515
|
<main class="app">
|
|
1519
|
-
<section class="preview"><iframe id="deck" src="/deck?token=${encodeURIComponent(token)}"></iframe><div id="commentHighlightLayer" class="comment-highlight-layer" aria-hidden="true"></div><div id="hitbox" class="hitbox" aria-label="Deck element selection layer"></div><div id="visualMoveHandle" class="visual-move-handle" aria-hidden="true"></div><div id="visualResizeHandle" class="visual-resize-handle" aria-hidden="true"></div><div id="visualEditToolbar" class="visual-edit-toolbar" aria-live="polite"><span id="visualEditCount">No unsaved visual changes</span><button id="visualUndo" type="button">Undo</button><button id="visualReset" type="button">Reset</button><button id="visualSave" class="save-visual" type="button">Save Changes</button></div><nav class="deck-nav" aria-label="Deck navigation"><button id="deckPrev" type="button" title="Previous slide (ArrowLeft / ArrowUp / PageUp)">Previous</button><div id="deckCounter" class="deck-nav-status" aria-live="polite">-- / --</div><button id="deckNext" type="button" title="Next slide (ArrowRight / ArrowDown / Space / PageDown)">Next</button></nav></section>
|
|
1516
|
+
<section class="preview"><iframe id="deck" src="/deck?token=${encodeURIComponent(token)}"></iframe><div id="commentHighlightLayer" class="comment-highlight-layer" aria-hidden="true"></div><div id="hitbox" class="hitbox" aria-label="Deck element selection layer"></div><div id="visualMoveHandle" class="visual-move-handle" aria-hidden="true"></div><div id="visualResizeHandle" class="visual-resize-handle" aria-hidden="true"></div><div id="visualEditToolbar" class="visual-edit-toolbar" aria-live="polite"><span id="visualEditCount">No unsaved visual changes</span><button id="visualUndo" type="button">Undo</button><button id="visualReset" type="button">Reset</button><button id="visualSave" class="save-visual" type="button">Save Changes</button></div><nav class="deck-nav" aria-label="Deck navigation"><button id="deckPrev" type="button" title="Previous slide (ArrowLeft / ArrowUp / PageUp)">Previous</button><div id="deckCounter" class="deck-nav-status" aria-live="polite">-- / --</div><button id="deckNext" type="button" title="Next slide (ArrowRight / ArrowDown / Space / PageDown)">Next</button></nav><div id="applyLockOverlay" class="apply-lock-overlay" aria-hidden="true"><div class="apply-lock-card"><strong>Applying deck edit...</strong><span>Preview is locked until Codex finishes.</span></div></div></section>
|
|
1520
1517
|
<div id="resizeHandle" class="resize-handle" role="separator" aria-label="Resize editor panel" aria-orientation="vertical" title="Drag to resize editor. Double-click to reset."></div>
|
|
1521
1518
|
<aside>
|
|
1522
1519
|
<div>
|
|
@@ -1692,6 +1689,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1692
1689
|
assetShuffleButton: null,
|
|
1693
1690
|
assetResults: null,
|
|
1694
1691
|
editSavedAssets: null,
|
|
1692
|
+
applyLockOverlay: null,
|
|
1695
1693
|
codexLogModal: null,
|
|
1696
1694
|
codexLogBackdrop: null,
|
|
1697
1695
|
codexLogClose: null,
|
|
@@ -1749,6 +1747,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1749
1747
|
els.assetShuffleButton = document.getElementById('assetShuffleButton');
|
|
1750
1748
|
els.assetResults = document.getElementById('assetResults');
|
|
1751
1749
|
els.editSavedAssets = document.getElementById('editSavedAssets');
|
|
1750
|
+
els.applyLockOverlay = document.getElementById('applyLockOverlay');
|
|
1752
1751
|
els.codexLogModal = document.getElementById('codexLogModal');
|
|
1753
1752
|
els.codexLogBackdrop = document.getElementById('codexLogBackdrop');
|
|
1754
1753
|
els.codexLogClose = document.getElementById('codexLogClose');
|
|
@@ -1758,7 +1757,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1758
1757
|
|
|
1759
1758
|
els.inspectLanguage = document.getElementById('inspectLanguage');
|
|
1760
1759
|
|
|
1761
|
-
if (!els.frame || !els.commentHighlightLayer || !els.hitbox || !els.resizeHandle || !els.visualMoveHandle || !els.visualResizeHandle || !els.visualEditToolbar || !els.visualEditCount || !els.visualUndo || !els.visualReset || !els.visualSave || !els.deckPrev || !els.deckNext || !els.deckCounter || !els.selectionSummary || !els.selectionChips || !els.editTab || !els.inspectTab || !els.editPanel || !els.inspectPanel || !els.comment || !els.commentThread || !els.send || !els.inspectComment || !els.inspectButton || !els.inspectLanguage || !els.inspectCards || !els.inspectStale || !els.localAssetToggle || !els.localAssetMenu || !els.assetSearchToggle || !els.assetSearchBack || !els.assetSearchView || !els.assetQuery || !els.assetPurpose || !els.assetSearchButton || !els.assetShuffleButton || !els.assetResults || !els.editSavedAssets || !els.codexLogModal || !els.codexLogBackdrop || !els.codexLogClose || !els.codexLogTitle || !els.codexLogBody || !els.status) {
|
|
1760
|
+
if (!els.frame || !els.commentHighlightLayer || !els.hitbox || !els.resizeHandle || !els.visualMoveHandle || !els.visualResizeHandle || !els.visualEditToolbar || !els.visualEditCount || !els.visualUndo || !els.visualReset || !els.visualSave || !els.deckPrev || !els.deckNext || !els.deckCounter || !els.selectionSummary || !els.selectionChips || !els.editTab || !els.inspectTab || !els.editPanel || !els.inspectPanel || !els.comment || !els.commentThread || !els.send || !els.inspectComment || !els.inspectButton || !els.inspectLanguage || !els.inspectCards || !els.inspectStale || !els.localAssetToggle || !els.localAssetMenu || !els.assetSearchToggle || !els.assetSearchBack || !els.assetSearchView || !els.assetQuery || !els.assetPurpose || !els.assetSearchButton || !els.assetShuffleButton || !els.assetResults || !els.editSavedAssets || !els.applyLockOverlay || !els.codexLogModal || !els.codexLogBackdrop || !els.codexLogClose || !els.codexLogTitle || !els.codexLogBody || !els.status) {
|
|
1762
1761
|
throw new Error('Editor boot failed: required DOM nodes are missing.');
|
|
1763
1762
|
}
|
|
1764
1763
|
|
|
@@ -1787,10 +1786,19 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1787
1786
|
closeCodexLogModal();
|
|
1788
1787
|
return;
|
|
1789
1788
|
}
|
|
1789
|
+
if (isApplyLocked()) {
|
|
1790
|
+
event.preventDefault();
|
|
1791
|
+
setStatus(applyLockStatus());
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1790
1794
|
closeLocalAssetMenu();
|
|
1791
1795
|
clearHover();
|
|
1792
1796
|
return;
|
|
1793
1797
|
}
|
|
1798
|
+
if (isApplyLocked() && !isTextInputTarget(event.target)) {
|
|
1799
|
+
event.preventDefault();
|
|
1800
|
+
return;
|
|
1801
|
+
}
|
|
1794
1802
|
if (isTextInputTarget(event.target) || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
1795
1803
|
if (['ArrowDown', 'ArrowRight', ' ', 'PageDown'].includes(event.key)) {
|
|
1796
1804
|
event.preventDefault();
|
|
@@ -1830,6 +1838,10 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1830
1838
|
if (event.ctrlKey || event.metaKey) event.preventDefault();
|
|
1831
1839
|
});
|
|
1832
1840
|
els.hitbox.addEventListener('wheel', (event) => {
|
|
1841
|
+
if (isApplyLocked()) {
|
|
1842
|
+
event.preventDefault();
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1833
1845
|
const win = els.frame.contentWindow;
|
|
1834
1846
|
if (!win) return;
|
|
1835
1847
|
event.preventDefault();
|
|
@@ -1885,7 +1897,38 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1885
1897
|
return state.mode === 'inspect' ? els.inspectComment : els.comment;
|
|
1886
1898
|
}
|
|
1887
1899
|
|
|
1900
|
+
function isApplyLocked() {
|
|
1901
|
+
return state.pendingComments.some((comment) => comment && comment.status === 'applying');
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function applyLockStatus() {
|
|
1905
|
+
return 'Wait for the current apply to finish before making another change.';
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function updateApplyLockUi() {
|
|
1909
|
+
const locked = isApplyLocked();
|
|
1910
|
+
els.applyLockOverlay.classList.toggle('open', locked);
|
|
1911
|
+
els.applyLockOverlay.setAttribute('aria-hidden', locked ? 'false' : 'true');
|
|
1912
|
+
els.comment.setAttribute('contenteditable', 'true');
|
|
1913
|
+
els.inspectComment.setAttribute('contenteditable', 'true');
|
|
1914
|
+
els.localAssetToggle.disabled = locked;
|
|
1915
|
+
els.assetSearchToggle.disabled = locked;
|
|
1916
|
+
els.assetSearchBack.disabled = locked;
|
|
1917
|
+
els.assetQuery.disabled = locked;
|
|
1918
|
+
els.assetPurpose.disabled = locked;
|
|
1919
|
+
if (locked) {
|
|
1920
|
+
closeLocalAssetMenu();
|
|
1921
|
+
closeAssetSearchPanel();
|
|
1922
|
+
clearHoverSilently();
|
|
1923
|
+
}
|
|
1924
|
+
updateDeckNavControls();
|
|
1925
|
+
updateVisualToolbar();
|
|
1926
|
+
updateAssetShuffleState();
|
|
1927
|
+
updateSendState();
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1888
1930
|
function toggleLocalAssetMenu() {
|
|
1931
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
1889
1932
|
setLocalAssetMenuOpen(!els.localAssetMenu.classList.contains('open'));
|
|
1890
1933
|
}
|
|
1891
1934
|
|
|
@@ -1900,6 +1943,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1900
1943
|
}
|
|
1901
1944
|
|
|
1902
1945
|
function toggleAssetSearchPanel() {
|
|
1946
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
1903
1947
|
const open = !els.assetSearchView.classList.contains('open');
|
|
1904
1948
|
setAssetSearchOpen(open);
|
|
1905
1949
|
}
|
|
@@ -1927,6 +1971,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
1927
1971
|
}
|
|
1928
1972
|
|
|
1929
1973
|
function startEditorResize(event) {
|
|
1974
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
1930
1975
|
event.preventDefault();
|
|
1931
1976
|
const currentWidth = Number.parseFloat(getComputedStyle(document.querySelector('.app')).getPropertyValue('--editor-width')) || DEFAULT_EDITOR_WIDTH;
|
|
1932
1977
|
state.resizeDrag = { startX: event.clientX, startWidth: currentWidth };
|
|
@@ -2072,6 +2117,12 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2072
2117
|
}
|
|
2073
2118
|
|
|
2074
2119
|
function renderVisualHandles(target) {
|
|
2120
|
+
if (isApplyLocked()) {
|
|
2121
|
+
state.hoverVisualTarget = null;
|
|
2122
|
+
renderVisualMoveHandle(null);
|
|
2123
|
+
renderVisualResizeHandle(null);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2075
2126
|
state.hoverVisualTarget = target && (isDirectResizable(target) || isDirectMovable(target)) ? target : null;
|
|
2076
2127
|
renderVisualMoveHandle(state.hoverVisualTarget);
|
|
2077
2128
|
renderVisualResizeHandle(state.hoverVisualTarget);
|
|
@@ -2090,6 +2141,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2090
2141
|
}
|
|
2091
2142
|
|
|
2092
2143
|
function startVisualMove(event) {
|
|
2144
|
+
if (isApplyLocked()) return false;
|
|
2093
2145
|
const target = state.hoverVisualTarget;
|
|
2094
2146
|
if (!target || !isDirectMovable(target)) return false;
|
|
2095
2147
|
event.preventDefault();
|
|
@@ -2148,6 +2200,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2148
2200
|
}
|
|
2149
2201
|
|
|
2150
2202
|
function startVisualResize(event) {
|
|
2203
|
+
if (isApplyLocked()) return false;
|
|
2151
2204
|
const target = state.hoverVisualTarget;
|
|
2152
2205
|
if (!target || !isDirectResizable(target)) return false;
|
|
2153
2206
|
event.preventDefault();
|
|
@@ -2218,6 +2271,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2218
2271
|
}
|
|
2219
2272
|
|
|
2220
2273
|
function undoVisualChange() {
|
|
2274
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2221
2275
|
const change = state.visualChanges.pop();
|
|
2222
2276
|
if (!change) return;
|
|
2223
2277
|
const target = elementFromVisualChange(change);
|
|
@@ -2229,6 +2283,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2229
2283
|
}
|
|
2230
2284
|
|
|
2231
2285
|
function resetVisualChanges() {
|
|
2286
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2232
2287
|
while (state.visualChanges.length) {
|
|
2233
2288
|
const change = state.visualChanges.pop();
|
|
2234
2289
|
const target = change ? elementFromVisualChange(change) : null;
|
|
@@ -2242,14 +2297,16 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2242
2297
|
|
|
2243
2298
|
function updateVisualToolbar() {
|
|
2244
2299
|
const count = state.visualChanges.length;
|
|
2300
|
+
const locked = isApplyLocked();
|
|
2245
2301
|
els.visualEditToolbar.classList.toggle('active', count > 0);
|
|
2246
2302
|
els.visualEditCount.textContent = count === 0 ? 'No unsaved visual changes' : count + ' unsaved visual change' + (count === 1 ? '' : 's');
|
|
2247
|
-
els.visualUndo.disabled = count === 0 || state.savingVisualChanges;
|
|
2248
|
-
els.visualReset.disabled = count === 0 || state.savingVisualChanges;
|
|
2249
|
-
els.visualSave.disabled = count === 0 || state.savingVisualChanges;
|
|
2303
|
+
els.visualUndo.disabled = locked || count === 0 || state.savingVisualChanges;
|
|
2304
|
+
els.visualReset.disabled = locked || count === 0 || state.savingVisualChanges;
|
|
2305
|
+
els.visualSave.disabled = locked || count === 0 || state.savingVisualChanges;
|
|
2250
2306
|
}
|
|
2251
2307
|
|
|
2252
2308
|
async function saveVisualChanges() {
|
|
2309
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2253
2310
|
if (!state.visualChanges.length || state.savingVisualChanges) return;
|
|
2254
2311
|
state.savingVisualChanges = true;
|
|
2255
2312
|
updateVisualToolbar();
|
|
@@ -2352,16 +2409,19 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2352
2409
|
function updateDeckNavControls() {
|
|
2353
2410
|
const total = state.deckSlideCount;
|
|
2354
2411
|
const current = total > 0 ? state.deckSlideIndex + 1 : 0;
|
|
2412
|
+
const locked = isApplyLocked();
|
|
2355
2413
|
els.deckCounter.textContent = total > 0 ? current + ' / ' + total : '-- / --';
|
|
2356
|
-
els.deckPrev.disabled = total <= 1 || state.deckSlideIndex <= 0;
|
|
2357
|
-
els.deckNext.disabled = total <= 1 || state.deckSlideIndex >= total - 1;
|
|
2414
|
+
els.deckPrev.disabled = locked || total <= 1 || state.deckSlideIndex <= 0;
|
|
2415
|
+
els.deckNext.disabled = locked || total <= 1 || state.deckSlideIndex >= total - 1;
|
|
2358
2416
|
}
|
|
2359
2417
|
|
|
2360
2418
|
function prevDeckSlide() {
|
|
2419
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2361
2420
|
goToDeckSlide(state.deckSlideIndex - 1);
|
|
2362
2421
|
}
|
|
2363
2422
|
|
|
2364
2423
|
function nextDeckSlide() {
|
|
2424
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2365
2425
|
goToDeckSlide(state.deckSlideIndex + 1);
|
|
2366
2426
|
}
|
|
2367
2427
|
|
|
@@ -2519,6 +2579,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2519
2579
|
}
|
|
2520
2580
|
|
|
2521
2581
|
function onHover(event) {
|
|
2582
|
+
if (isApplyLocked()) return clearHoverSilently();
|
|
2522
2583
|
try {
|
|
2523
2584
|
initFrame();
|
|
2524
2585
|
const directTarget = visualTargetFromPointer(event);
|
|
@@ -2538,6 +2599,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2538
2599
|
}
|
|
2539
2600
|
|
|
2540
2601
|
function onClick(event) {
|
|
2602
|
+
if (isApplyLocked()) return;
|
|
2541
2603
|
try {
|
|
2542
2604
|
initFrame();
|
|
2543
2605
|
const target = selectable(targetFromPointer(event));
|
|
@@ -2554,6 +2616,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2554
2616
|
}
|
|
2555
2617
|
|
|
2556
2618
|
function onPointerDown(event) {
|
|
2619
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2557
2620
|
if (!event.ctrlKey && !event.metaKey && pointerIsOnVisualMoveHandle(event)) {
|
|
2558
2621
|
if (startVisualMove(event)) return;
|
|
2559
2622
|
}
|
|
@@ -2572,6 +2635,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2572
2635
|
}
|
|
2573
2636
|
|
|
2574
2637
|
async function sendComment() {
|
|
2638
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2575
2639
|
syncReferencesFromComment(false, els.comment);
|
|
2576
2640
|
syncSelectedAssetFromComment();
|
|
2577
2641
|
const text = getCommentText().trim();
|
|
@@ -2652,6 +2716,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2652
2716
|
}
|
|
2653
2717
|
|
|
2654
2718
|
async function applyPersistedComment(commentId) {
|
|
2719
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2655
2720
|
const comment = state.pendingComments.find((item) => item.id === commentId);
|
|
2656
2721
|
if (!comment || comment.status === 'applying' || comment.status === 'queued') return;
|
|
2657
2722
|
updatePendingCommentStatus(commentId, 'applying', { baseDeckVersion: state.deckVersion || comment.baseDeckVersion, progressEvent: null, eventLog: [], failureRaw: '', failureMessage: '' });
|
|
@@ -2680,6 +2745,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2680
2745
|
}
|
|
2681
2746
|
|
|
2682
2747
|
async function stopPersistedComment(commentId) {
|
|
2748
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2683
2749
|
const comment = state.pendingComments.find((item) => item.id === commentId);
|
|
2684
2750
|
if (!comment || !canStopPersistedComment(comment.status)) return;
|
|
2685
2751
|
try {
|
|
@@ -2695,6 +2761,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2695
2761
|
}
|
|
2696
2762
|
|
|
2697
2763
|
async function deletePersistedComment(commentId) {
|
|
2764
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2698
2765
|
const comment = state.pendingComments.find((item) => item.id === commentId);
|
|
2699
2766
|
if (!comment || !canDeletePersistedComment(comment.status)) return;
|
|
2700
2767
|
try {
|
|
@@ -2748,6 +2815,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2748
2815
|
}
|
|
2749
2816
|
|
|
2750
2817
|
async function searchAssets(nextBatch) {
|
|
2818
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2751
2819
|
const query = (els.assetQuery.value || '').trim();
|
|
2752
2820
|
if (!query || state.assetSearchBusy) return;
|
|
2753
2821
|
const key = query + '\u0000' + (els.assetPurpose.value || 'illustration');
|
|
@@ -2794,7 +2862,8 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2794
2862
|
}
|
|
2795
2863
|
|
|
2796
2864
|
function updateAssetShuffleState() {
|
|
2797
|
-
els.
|
|
2865
|
+
els.assetSearchButton.disabled = isApplyLocked() || state.assetSearchBusy;
|
|
2866
|
+
els.assetShuffleButton.disabled = isApplyLocked() || state.assetSearchBusy || !state.assetCandidates.length;
|
|
2798
2867
|
}
|
|
2799
2868
|
|
|
2800
2869
|
function renderAssetCandidates() {
|
|
@@ -2823,6 +2892,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2823
2892
|
}
|
|
2824
2893
|
|
|
2825
2894
|
async function saveCandidate(index) {
|
|
2895
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2826
2896
|
const candidate = state.assetCandidates[index];
|
|
2827
2897
|
if (!candidate) return;
|
|
2828
2898
|
state.assetSavingIndex = index;
|
|
@@ -2963,6 +3033,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2963
3033
|
}
|
|
2964
3034
|
|
|
2965
3035
|
function addAssetToComment(asset) {
|
|
3036
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2966
3037
|
if (!asset) return;
|
|
2967
3038
|
state.selectedAsset = asset;
|
|
2968
3039
|
removeAssetChip();
|
|
@@ -2977,6 +3048,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2977
3048
|
}
|
|
2978
3049
|
|
|
2979
3050
|
function onAssetDragOver(event) {
|
|
3051
|
+
if (isApplyLocked()) return;
|
|
2980
3052
|
if (!state.draggingAsset) return;
|
|
2981
3053
|
event.preventDefault();
|
|
2982
3054
|
if (event.dataTransfer) event.dataTransfer.dropEffect = 'copy';
|
|
@@ -2991,6 +3063,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
2991
3063
|
}
|
|
2992
3064
|
|
|
2993
3065
|
async function onAssetDrop(event) {
|
|
3066
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
2994
3067
|
const asset = state.draggingAsset || findSavedAsset(event.dataTransfer?.getData('application/revela-asset-id'));
|
|
2995
3068
|
if (!asset) return;
|
|
2996
3069
|
event.preventDefault();
|
|
@@ -3171,6 +3244,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3171
3244
|
}
|
|
3172
3245
|
|
|
3173
3246
|
async function sendAssetPlacement(asset, placement) {
|
|
3247
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
3174
3248
|
const modeText = placement.targetMode === 'replace'
|
|
3175
3249
|
? 'replace the image at the drop target'
|
|
3176
3250
|
: placement.targetMode === 'insert-into'
|
|
@@ -3201,6 +3275,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3201
3275
|
}
|
|
3202
3276
|
|
|
3203
3277
|
function toggleReference(target) {
|
|
3278
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
3204
3279
|
if (!target) {
|
|
3205
3280
|
setStatus('No selectable deck element found under pointer.');
|
|
3206
3281
|
return;
|
|
@@ -3478,6 +3553,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3478
3553
|
|
|
3479
3554
|
function renderCommentThread(scrollToBottom = true) {
|
|
3480
3555
|
els.commentThread.textContent = '';
|
|
3556
|
+
const locked = isApplyLocked();
|
|
3481
3557
|
state.pendingComments.forEach((comment) => {
|
|
3482
3558
|
const bubble = document.createElement('div');
|
|
3483
3559
|
bubble.className = 'comment-bubble ' + comment.status + (comment.id === state.activeCommentId ? ' active' : '');
|
|
@@ -3510,7 +3586,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3510
3586
|
if (refs) bubble.appendChild(refs);
|
|
3511
3587
|
bubble.appendChild(text);
|
|
3512
3588
|
bubble.appendChild(status);
|
|
3513
|
-
if (comment.persisted && canApplyPersistedComment(comment.status)) {
|
|
3589
|
+
if (!locked && comment.persisted && canApplyPersistedComment(comment.status)) {
|
|
3514
3590
|
const actions = document.createElement('div');
|
|
3515
3591
|
actions.className = 'comment-actions';
|
|
3516
3592
|
const apply = commentActionButton(isReapplyStatus(comment.status) ? 'Re-apply' : 'Apply', isReapplyStatus(comment.status) ? '${lucideIcon("refresh-cw", "comment-action-icon")}' : '${lucideIcon("play", "comment-action-icon")}');
|
|
@@ -3518,7 +3594,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3518
3594
|
actions.appendChild(apply);
|
|
3519
3595
|
bubble.appendChild(actions);
|
|
3520
3596
|
}
|
|
3521
|
-
if (comment.persisted && canStopPersistedComment(comment.status)) {
|
|
3597
|
+
if (!locked && comment.persisted && canStopPersistedComment(comment.status)) {
|
|
3522
3598
|
const actions = bubble.querySelector('.comment-actions') || document.createElement('div');
|
|
3523
3599
|
actions.className = 'comment-actions';
|
|
3524
3600
|
const stop = commentActionButton('Stop', '${lucideIcon("square", "comment-action-icon")}', 'stop');
|
|
@@ -3526,7 +3602,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3526
3602
|
actions.appendChild(stop);
|
|
3527
3603
|
if (!actions.parentElement) bubble.appendChild(actions);
|
|
3528
3604
|
}
|
|
3529
|
-
if (comment.persisted && canDeletePersistedComment(comment.status)) {
|
|
3605
|
+
if (!locked && comment.persisted && canDeletePersistedComment(comment.status)) {
|
|
3530
3606
|
const actions = bubble.querySelector('.comment-actions') || document.createElement('div');
|
|
3531
3607
|
actions.className = 'comment-actions';
|
|
3532
3608
|
const remove = commentActionButton('Delete', '${lucideIcon("trash-2", "comment-action-icon")}', 'danger');
|
|
@@ -3564,6 +3640,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3564
3640
|
}
|
|
3565
3641
|
els.commentThread.appendChild(bubble);
|
|
3566
3642
|
});
|
|
3643
|
+
updateApplyLockUi();
|
|
3567
3644
|
if (scrollToBottom) scrollCommentThreadToBottom();
|
|
3568
3645
|
}
|
|
3569
3646
|
|
|
@@ -3788,12 +3865,13 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3788
3865
|
}
|
|
3789
3866
|
|
|
3790
3867
|
function updateSendState() {
|
|
3868
|
+
const locked = isApplyLocked();
|
|
3791
3869
|
if (state.sendingEdit) setButtonLoading(els.send, true, 'Sending...');
|
|
3792
3870
|
else setButtonLoading(els.send, false, sendButtonHtml, true);
|
|
3793
|
-
els.send.disabled = state.sendingEdit || !getCommentText().trim();
|
|
3871
|
+
els.send.disabled = locked || state.sendingEdit || !getCommentText().trim();
|
|
3794
3872
|
if (state.inspecting) setButtonLoading(els.inspectButton, true, 'Getting insight...');
|
|
3795
3873
|
else setButtonLoading(els.inspectButton, false, 'Get Insight');
|
|
3796
|
-
els.inspectButton.disabled = state.inspecting || state.references.length === 0;
|
|
3874
|
+
els.inspectButton.disabled = locked || state.inspecting || state.references.length === 0;
|
|
3797
3875
|
}
|
|
3798
3876
|
|
|
3799
3877
|
function setButtonLoading(button, loading, label, html) {
|
|
@@ -3840,6 +3918,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
|
|
|
3840
3918
|
}
|
|
3841
3919
|
|
|
3842
3920
|
async function inspectCurrentSelection() {
|
|
3921
|
+
if (isApplyLocked()) return setStatus(applyLockStatus());
|
|
3843
3922
|
if (!state.references.length || state.inspecting) return;
|
|
3844
3923
|
const snapshot = collectReferenceSnapshot();
|
|
3845
3924
|
const comment = getInspectComment();
|
package/package.json
CHANGED