slidemind 0.3.4 → 0.3.12
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/.agents/plugins/marketplace.json +2 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +5 -5
- package/bin/slidemind.mjs +535 -134
- package/package.json +1 -1
- package/skills/create-slides/SKILL.md +718 -127
- package/skills/search/SKILL.md +217 -57
- package/src/bridge/build-and-push.mjs +3 -3
- package/src/bridge/bundle.mjs +91 -36
- package/src/bridge/capture.mjs +34 -0
- package/src/bridge/client.mjs +81 -23
- package/src/bridge/config.mjs +21 -2
- package/src/bridge/engine-download.mjs +26 -7
- package/src/bridge/recolor.mjs +46 -0
- package/src/bridge/setup-claim.mjs +7 -1
- package/src/builders/build.mjs +179 -5
- package/src/builders/gate-server.mjs +25 -36
- package/src/builders/gate-session.js +28 -48
- package/src/builders/lint-fix.mjs +88 -0
- package/src/builders/plan-overlay.js +507 -136
- package/src/builders/plan.mjs +337 -60
- package/src/builders/review-overlay.js +126 -109
- package/src/builders/review.mjs +118 -64
- package/src/builders/storyboard-markdown.mjs +15 -0
- package/src/builders/storyboard-schema.mjs +23 -6
- package/src/builders/visual-families.mjs +20 -4
- package/src/lib/adopt.mjs +10 -6
- package/src/lib/conversions.mjs +26 -6
- package/src/lib/overwrite-guard.mjs +14 -1
- package/src/lib/reconcile.mjs +15 -4
- package/src/pptx/tags.mjs +49 -0
|
@@ -9,34 +9,32 @@
|
|
|
9
9
|
// - Click anywhere on the stage -> resolve the innermost element under the
|
|
10
10
|
// click via closest('[data-sm-loc]'), drop a pin, capture a note.
|
|
11
11
|
// Slide-level notes (empty-area clicks) carry no element.
|
|
12
|
-
// - Saving a pin
|
|
13
|
-
//
|
|
12
|
+
// - Saving a pin keeps it as an unsent DRAFT in this tab (editable, deletable).
|
|
13
|
+
// "Send N comments" in the header submits the whole batch: the gate resolves
|
|
14
|
+
// and the `slidemind review` command returns with it (the add-in's
|
|
15
|
+
// "Submit N comments").
|
|
14
16
|
// - Approve -> POST to the local server, which unblocks the command with the
|
|
15
|
-
// sign-off.
|
|
17
|
+
// sign-off. Refused while drafts exist — send or delete them first, exactly
|
|
18
|
+
// the add-in's rule.
|
|
16
19
|
//
|
|
17
20
|
// TWO MODES, not "reviewing vs. veiled over":
|
|
18
|
-
// - PARKED (the command is blocking on this tab):
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// Queued pins are never lost: the tab polls the heartbeat, and when the agent's
|
|
27
|
-
// next `slidemind review` run claims it the reload's boot POSTs the whole
|
|
28
|
-
// backlog to /review/backlog — which resolves that run immediately as a drain
|
|
29
|
-
// round, so the tab simply stays in working mode with the ledger flipped to
|
|
30
|
-
// "sent". A tab superseded by a fresh window flushes before retiring, likewise.
|
|
21
|
+
// - PARKED (the command is blocking on this tab): pinning, sending and
|
|
22
|
+
// approving are live.
|
|
23
|
+
// - WORKING (the agent is fixing the slides): the preview stays browsable and
|
|
24
|
+
// the ledger stays visible, but no new pins are taken and Approve is off —
|
|
25
|
+
// the displayed render is already superseded. A status strip says so. The
|
|
26
|
+
// tab polls the heartbeat and reloads itself when the agent's next
|
|
27
|
+
// `slidemind review` run presents the new round. Nothing is queued behind
|
|
28
|
+
// the agent's back (add-in parity: rounds are synchronous).
|
|
31
29
|
//
|
|
32
30
|
// ONE LEDGER, three statuses. Every pin is {id, round, jsxPath, resolvedTarget,
|
|
33
31
|
// x, y, text, status} (x/y are full-precision 0..1 fractions of the slide):
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
32
|
+
// draft (saved here, not yet sent) -> sent (the agent has it) -> addressed (a
|
|
33
|
+
// newer gate run served this page — the server's round nonce changed).
|
|
34
|
+
// Addressed pins are the read-only GHOST pins of previous rounds — there is no
|
|
35
|
+
// separate previous-round channel. A tab with no session of its own seeds the
|
|
36
|
+
// ledger from CFG.ledgerSeed (the deck's persisted review.json) as SENT rows:
|
|
37
|
+
// the agent has just been handed them, nothing has answered them.
|
|
40
38
|
//
|
|
41
39
|
// Branding (lifted from the add-in's product identity):
|
|
42
40
|
// - The embossed "raised slide" logo (public/logo.svg).
|
|
@@ -54,15 +52,15 @@
|
|
|
54
52
|
var SIDEBAR_W = 316; // panel + gaps reserved on the right when comments exist
|
|
55
53
|
// The ledger IS the state: one flat list of this session's pins. Pins, sidebar
|
|
56
54
|
// rows and the approve guard are all derived from it, so a comment keeps its
|
|
57
|
-
// identity across
|
|
55
|
+
// identity across draft -> sent -> addressed (and across the round reload).
|
|
58
56
|
var ledger = [];
|
|
59
57
|
var roundNo = 1; // this tab's round counter (sessionStorage, not server state)
|
|
60
58
|
var currentStem = null;
|
|
61
59
|
var pendingStem = null; // the slide to re-anchor on after a round reload
|
|
62
60
|
var draftPopover = null;
|
|
63
61
|
var approved = false; // approve latch — the only thing that ends the session
|
|
64
|
-
var working = false; // the agent is fixing →
|
|
65
|
-
var workingNote = null; // extra line for the working strip (
|
|
62
|
+
var working = false; // the agent is fixing → no new pins, no approve
|
|
63
|
+
var workingNote = null; // extra line for the working strip (superseded)
|
|
66
64
|
var sidebarEl = null;
|
|
67
65
|
var sidebarOpen = false;
|
|
68
66
|
var hoverBox = null;
|
|
@@ -70,8 +68,8 @@
|
|
|
70
68
|
|
|
71
69
|
// The shared gate session (src/builders/gate-session.js, inlined into the page
|
|
72
70
|
// as window.__SLIDEMIND_GATE__): the deck-keyed sessionStorage ledger, the
|
|
73
|
-
//
|
|
74
|
-
//
|
|
71
|
+
// server-nonce round transition, the round-ready announce and the heartbeat
|
|
72
|
+
// poll — one implementation, shared with the plan gate.
|
|
75
73
|
var SES = window.__SLIDEMIND_GATE__.createGateSession({
|
|
76
74
|
name: "review",
|
|
77
75
|
deck: CFG.deck || "",
|
|
@@ -82,7 +80,6 @@
|
|
|
82
80
|
round: CFG.round || "",
|
|
83
81
|
service: "slidemind-review",
|
|
84
82
|
heartbeat: CFG.heartbeat || "/__review/heartbeat",
|
|
85
|
-
backlogEndpoint: CFG.backlogEndpoint || "/review/backlog",
|
|
86
83
|
pollMs: CFG.pollMs,
|
|
87
84
|
pollTtlMs: CFG.pollTtlMs,
|
|
88
85
|
roundInSentLabel: true, // "sent · round N" — a pin names the render it sits on
|
|
@@ -195,6 +192,8 @@
|
|
|
195
192
|
".sm-note{max-width:min(560px,38vw);font-size:10.5px;line-height:1.35;color:#c7c7cf;" +
|
|
196
193
|
"border-left:2px solid var(--sm-accent);padding-left:10px;}" +
|
|
197
194
|
".sm-note b{font-weight:600;color:#f4f4f5;}" +
|
|
195
|
+
".sm-chart-note{display:none;border-left-color:#fbbf24;}" +
|
|
196
|
+
".sm-chart-note.on{display:block;}" +
|
|
198
197
|
".sm-bar~.deck__rail,.deck__rail{top:var(--deck-chrome-h,62px);}" +
|
|
199
198
|
".sm-grow{flex:1;}" +
|
|
200
199
|
".sm-count{font-size:12px;color:var(--sm-dim);font-variant-numeric:tabular-nums;}" +
|
|
@@ -226,7 +225,7 @@
|
|
|
226
225
|
"font:600 12px var(--sm-font);cursor:pointer;display:flex;align-items:center;justify-content:center;" +
|
|
227
226
|
"pointer-events:auto;animation:sm-pin-in .26s cubic-bezier(.2,1.5,.4,1);transition:filter .12s;}" +
|
|
228
227
|
".sm-pin:hover{filter:brightness(1.12);}" +
|
|
229
|
-
/*
|
|
228
|
+
/* draft (saved here, not sent yet) / sent (the agent has it) /
|
|
230
229
|
addressed (answered by a newer render — the read-only ghost) */
|
|
231
230
|
".sm-pin-active{background:#161618;color:#fff;border:2px solid rgba(255,255,255,.92);box-shadow:0 2px 9px rgba(0,0,0,.55);}" +
|
|
232
231
|
".sm-pin-sent{background:#161618;color:var(--sm-accent);border:2px dashed var(--sm-accent);box-shadow:0 2px 9px rgba(0,0,0,.45);}" +
|
|
@@ -287,7 +286,7 @@
|
|
|
287
286
|
/* ledger status chip — where each pin stands, per row */
|
|
288
287
|
".sm-side-state{display:inline-block;margin-top:4px;padding:1px 7px;border-radius:999px;font-size:9.5px;font-weight:600;" +
|
|
289
288
|
"letter-spacing:.05em;text-transform:uppercase;border:1px solid var(--sm-border);color:var(--sm-dim);}" +
|
|
290
|
-
'.sm-side-state[data-state="
|
|
289
|
+
'.sm-side-state[data-state="draft"]{border-color:color-mix(in srgb,var(--sm-accent) 45%,transparent);color:var(--sm-accent);}' +
|
|
291
290
|
'.sm-side-state[data-state="sent"]{border-color:color-mix(in srgb,var(--sm-accent) 30%,transparent);color:#c6d4f5;}';
|
|
292
291
|
|
|
293
292
|
// ---------- helpers ----------
|
|
@@ -322,12 +321,17 @@
|
|
|
322
321
|
function activeComments() {
|
|
323
322
|
return commentsForStem(currentStem);
|
|
324
323
|
}
|
|
325
|
-
// Outstanding = written but not yet answered by a render
|
|
326
|
-
// guard: signing off while something is outstanding would approve slides the
|
|
327
|
-
// agent has been told to change.
|
|
324
|
+
// Outstanding = written but not yet answered by a render (the header count).
|
|
328
325
|
function openComments() {
|
|
329
326
|
return ledger.filter(function (c) {
|
|
330
|
-
return c.status === "
|
|
327
|
+
return c.status === "draft" || c.status === "sent";
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
// Saved here, not sent: the batch "Send comments" submits, and the reason
|
|
331
|
+
// Approve is off while any exist.
|
|
332
|
+
function draftComments() {
|
|
333
|
+
return ledger.filter(function (c) {
|
|
334
|
+
return c.status === "draft";
|
|
331
335
|
});
|
|
332
336
|
}
|
|
333
337
|
function el(tag, cls, html) {
|
|
@@ -393,15 +397,10 @@
|
|
|
393
397
|
SES.saveLedger(ledger);
|
|
394
398
|
}
|
|
395
399
|
// The context the NEXT round restores from: the server nonce (the round
|
|
396
|
-
// marker, added by the shared session)
|
|
400
|
+
// marker, added by the shared session), which slide the reviewer was on, and
|
|
401
|
+
// whether this round was already sent (so a plain refresh stays in WORKING).
|
|
397
402
|
function saveCtx() {
|
|
398
|
-
SES.saveCtx({ round: roundNo, stem: currentStem });
|
|
399
|
-
}
|
|
400
|
-
function queuedComments() {
|
|
401
|
-
return SES.queued(ledger);
|
|
402
|
-
}
|
|
403
|
-
function flushBacklog() {
|
|
404
|
-
return SES.flushBacklog(ledger);
|
|
403
|
+
SES.saveCtx({ round: roundNo, stem: currentStem, working: working });
|
|
405
404
|
}
|
|
406
405
|
|
|
407
406
|
// Restore this tab's session and work out whether THIS load is a new round.
|
|
@@ -418,7 +417,7 @@
|
|
|
418
417
|
}
|
|
419
418
|
|
|
420
419
|
// ---------- chrome (top bar) ----------
|
|
421
|
-
var bar, approveBtn, countLabel, statusEl;
|
|
420
|
+
var bar, approveBtn, sendBtn, countLabel, statusEl, chartNoteEl;
|
|
422
421
|
function buildChrome() {
|
|
423
422
|
bar = el("div", "sm-o sm-bar");
|
|
424
423
|
bar.setAttribute("data-sm-overlay", "1");
|
|
@@ -446,11 +445,27 @@
|
|
|
446
445
|
);
|
|
447
446
|
bar.appendChild(note);
|
|
448
447
|
|
|
448
|
+
// Add-in parity: a slide holding a <Chart> shows a stand-in, never
|
|
449
|
+
// PowerPoint's render — warn on exactly those slides.
|
|
450
|
+
chartNoteEl = el(
|
|
451
|
+
"div",
|
|
452
|
+
"sm-note sm-chart-note",
|
|
453
|
+
"<b>Preview only</b> — the chart may look different in PowerPoint.",
|
|
454
|
+
);
|
|
455
|
+
bar.appendChild(chartNoteEl);
|
|
456
|
+
|
|
449
457
|
bar.appendChild(el("div", "sm-grow"));
|
|
450
458
|
|
|
451
459
|
countLabel = el("div", "sm-count");
|
|
452
460
|
bar.appendChild(countLabel);
|
|
453
461
|
|
|
462
|
+
// The add-in's footer: "Submit N comments" appears once drafts exist and is
|
|
463
|
+
// the ONLY way forward while they do; Approve is the exclusive alternative.
|
|
464
|
+
sendBtn = mkButton("Send comments", "ghost", "arrow");
|
|
465
|
+
sendBtn.onclick = sendComments;
|
|
466
|
+
sendBtn.style.display = "none";
|
|
467
|
+
bar.appendChild(sendBtn);
|
|
468
|
+
|
|
454
469
|
approveBtn = mkButton("Approve & continue", "primary", "check");
|
|
455
470
|
approveBtn.onclick = approve;
|
|
456
471
|
bar.appendChild(approveBtn);
|
|
@@ -459,45 +474,38 @@
|
|
|
459
474
|
refreshChrome();
|
|
460
475
|
}
|
|
461
476
|
// Approve is refused while the agent is fixing (the render on screen is
|
|
462
|
-
// superseded) or while
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
// so a round that touched no file left Approve off for the rest of the session.
|
|
477
|
+
// superseded) or while DRAFTS exist — the add-in's rule: submit or delete them
|
|
478
|
+
// first. It is NOT refused for already-delivered ("sent") pins: a sent pin
|
|
479
|
+
// only clears on a round transition, so guarding on it deadlocked a round
|
|
480
|
+
// that touched no file.
|
|
467
481
|
function refreshChrome() {
|
|
468
482
|
if (!countLabel) return;
|
|
469
483
|
var open = openComments().length;
|
|
470
|
-
var
|
|
484
|
+
var d = draftComments().length;
|
|
471
485
|
countLabel.textContent =
|
|
472
486
|
ledger.length === 0
|
|
473
487
|
? "no comments yet"
|
|
474
488
|
: open + " open · " + ledger.length + " total";
|
|
475
|
-
|
|
489
|
+
sendBtn.style.display = !working && d > 0 ? "" : "none";
|
|
490
|
+
sendBtn.querySelector("span").textContent =
|
|
491
|
+
"Send " + d + (d === 1 ? " comment" : " comments");
|
|
492
|
+
var off = working || d > 0;
|
|
476
493
|
approveBtn.disabled = off;
|
|
477
494
|
approveBtn.setAttribute("aria-disabled", off ? "true" : "false");
|
|
478
495
|
approveBtn.classList.toggle("is-off", off);
|
|
479
496
|
approveBtn.title = working
|
|
480
497
|
? "The agent is fixing the slides — approve once the next round arrives."
|
|
481
|
-
:
|
|
482
|
-
? "Send your
|
|
498
|
+
: d > 0
|
|
499
|
+
? "Send or delete your comments before approving."
|
|
483
500
|
: "Approve and continue.";
|
|
484
501
|
statusEl.classList.toggle("on", working);
|
|
485
502
|
if (working) {
|
|
486
503
|
statusEl.innerHTML = '<div class="sm-spin"></div>';
|
|
487
504
|
var txt = el("div");
|
|
488
|
-
txt.appendChild(el("div", null, "<b>The agent is
|
|
489
|
-
txt.appendChild(
|
|
490
|
-
el(
|
|
491
|
-
"div",
|
|
492
|
-
null,
|
|
493
|
-
"",
|
|
494
|
-
),
|
|
495
|
-
).textContent =
|
|
505
|
+
txt.appendChild(el("div", null, "<b>The agent is working through your comments</b>"));
|
|
506
|
+
txt.appendChild(el("div", null, "")).textContent =
|
|
496
507
|
(workingNote ? workingNote + " " : "") +
|
|
497
|
-
|
|
498
|
-
? q + (q === 1 ? " comment is" : " comments are") +
|
|
499
|
-
" queued — they're delivered with the next round."
|
|
500
|
-
: "Keep browsing and keep commenting — new pins are picked up automatically.");
|
|
508
|
+
"This tab refreshes with the next round — comment again then.";
|
|
501
509
|
statusEl.appendChild(txt);
|
|
502
510
|
}
|
|
503
511
|
}
|
|
@@ -570,11 +578,11 @@
|
|
|
570
578
|
y: sr.top + fy(c) * sr.height,
|
|
571
579
|
};
|
|
572
580
|
}
|
|
573
|
-
// Only a
|
|
574
|
-
//
|
|
581
|
+
// Only a DRAFT is still the reviewer's to change; once it is with the agent
|
|
582
|
+
// it is read-only history.
|
|
575
583
|
function openCommentAt(c) {
|
|
576
584
|
var p = commentScreenPoint(c);
|
|
577
|
-
if (c.status === "
|
|
585
|
+
if (c.status === "draft") openEditor(c, p.x, p.y);
|
|
578
586
|
else openGhost(c, p.x, p.y);
|
|
579
587
|
}
|
|
580
588
|
function statusLabel(c) {
|
|
@@ -691,7 +699,7 @@
|
|
|
691
699
|
});
|
|
692
700
|
}
|
|
693
701
|
function pinVariant(c) {
|
|
694
|
-
if (c.status === "
|
|
702
|
+
if (c.status === "draft") return "sm-pin-active";
|
|
695
703
|
if (c.status === "sent") return "sm-pin-sent";
|
|
696
704
|
return "sm-pin-ghost";
|
|
697
705
|
}
|
|
@@ -757,9 +765,8 @@
|
|
|
757
765
|
});
|
|
758
766
|
}
|
|
759
767
|
|
|
760
|
-
// Saving a pin
|
|
761
|
-
//
|
|
762
|
-
// working queues for the agent's next run.
|
|
768
|
+
// Saving a pin keeps it as a DRAFT; the header's "Send comments" submits the
|
|
769
|
+
// batch (the add-in's "Save comment" → "Submit N comments").
|
|
763
770
|
function openDraft(point, region, px, py) {
|
|
764
771
|
var pop = makePopover(px, py);
|
|
765
772
|
pop.appendChild(head(targetLabel(region), !!region));
|
|
@@ -768,12 +775,12 @@
|
|
|
768
775
|
var row = el("div", "sm-row end");
|
|
769
776
|
var cancel = mkButton("Cancel", "ghost", "close");
|
|
770
777
|
cancel.onclick = closePopover;
|
|
771
|
-
var save = mkButton(
|
|
778
|
+
var save = mkButton("Save comment", "primary", "check");
|
|
772
779
|
function doSave() {
|
|
773
780
|
var text = ta.value.trim();
|
|
774
781
|
if (!text) return;
|
|
775
782
|
closePopover();
|
|
776
|
-
|
|
783
|
+
addDraft({
|
|
777
784
|
id: genId(),
|
|
778
785
|
round: roundNo,
|
|
779
786
|
jsxPath: "slides/" + currentStem + ".jsx",
|
|
@@ -781,7 +788,7 @@
|
|
|
781
788
|
x: clamp01(point.x),
|
|
782
789
|
y: clamp01(point.y),
|
|
783
790
|
text: text,
|
|
784
|
-
status: "
|
|
791
|
+
status: "draft",
|
|
785
792
|
at: new Date().toISOString(),
|
|
786
793
|
});
|
|
787
794
|
}
|
|
@@ -795,8 +802,8 @@
|
|
|
795
802
|
bindTextareaKeys(ta, doSave);
|
|
796
803
|
}
|
|
797
804
|
|
|
798
|
-
// Only reachable for a
|
|
799
|
-
//
|
|
805
|
+
// Only reachable for a DRAFT (openCommentAt routes the rest to the read-only
|
|
806
|
+
// view): until it is sent, the reviewer can still fix or drop it.
|
|
800
807
|
function openEditor(comment, px, py) {
|
|
801
808
|
var pop = makePopover(px, py);
|
|
802
809
|
pop.appendChild(head(targetLabel(comment.resolvedTarget), !!comment.resolvedTarget));
|
|
@@ -849,18 +856,19 @@
|
|
|
849
856
|
var WAITING_TITLE = "⏳ Agent revising slides — slidemind";
|
|
850
857
|
|
|
851
858
|
// ---------- submit ----------
|
|
852
|
-
|
|
853
|
-
// along on the next run). PARKED → it resolves the gate right now, carrying
|
|
854
|
-
// any queued stragglers from a flush that couldn't reach the server, and the
|
|
855
|
-
// tab enters working mode instead of veiling over.
|
|
856
|
-
function sendComment(c) {
|
|
859
|
+
function addDraft(c) {
|
|
857
860
|
ledger.push(c);
|
|
858
861
|
saveLedger();
|
|
859
862
|
renderAll();
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
863
|
+
}
|
|
864
|
+
// "Send N comments": the whole draft batch resolves the gate as one comments
|
|
865
|
+
// round, the drafts flip to "sent" and the tab enters working mode.
|
|
866
|
+
function sendComments() {
|
|
867
|
+
if (approved || working) return;
|
|
868
|
+
var batch = draftComments();
|
|
869
|
+
if (!batch.length) return;
|
|
870
|
+
// Ask for notification permission HERE — the click is a user gesture, the
|
|
871
|
+
// only context where the browser will show the prompt.
|
|
864
872
|
SES.requestNotifyPermission();
|
|
865
873
|
fetch(CFG.endpoint, {
|
|
866
874
|
method: "POST",
|
|
@@ -869,7 +877,8 @@
|
|
|
869
877
|
})
|
|
870
878
|
.then(function (res) {
|
|
871
879
|
if (res.status === 409) {
|
|
872
|
-
//
|
|
880
|
+
// Another tab already decided this run. The drafts stay drafts — the
|
|
881
|
+
// next round reloads this tab and the reviewer can send them then.
|
|
873
882
|
enterWorking("Round superseded.");
|
|
874
883
|
return;
|
|
875
884
|
}
|
|
@@ -881,13 +890,12 @@
|
|
|
881
890
|
enterWorking();
|
|
882
891
|
})
|
|
883
892
|
.catch(function () {
|
|
884
|
-
// The command is gone (killed / timed out). The
|
|
885
|
-
|
|
886
|
-
enterWorking("Couldn't reach the terminal command.");
|
|
893
|
+
// The command is gone (killed / timed out). The drafts stay in the tab.
|
|
894
|
+
alert("Could not reach the slidemind review server — is the terminal command still running?");
|
|
887
895
|
});
|
|
888
896
|
}
|
|
889
897
|
function approve() {
|
|
890
|
-
if (approved || working ||
|
|
898
|
+
if (approved || working || draftComments().length) return;
|
|
891
899
|
approved = true;
|
|
892
900
|
fetch(CFG.endpoint, {
|
|
893
901
|
method: "POST",
|
|
@@ -896,7 +904,7 @@
|
|
|
896
904
|
})
|
|
897
905
|
.then(function (res) {
|
|
898
906
|
if (res.status === 409) {
|
|
899
|
-
//
|
|
907
|
+
// Another tab already decided this run: the render is superseded.
|
|
900
908
|
approved = false;
|
|
901
909
|
enterWorking("Round superseded.");
|
|
902
910
|
return;
|
|
@@ -910,15 +918,17 @@
|
|
|
910
918
|
});
|
|
911
919
|
}
|
|
912
920
|
// ---------- working mode (the agent is fixing) ----------
|
|
913
|
-
// The tab neither veils over nor freezes: the stage stays browsable and
|
|
914
|
-
//
|
|
915
|
-
// polling waits for the agent's next `slidemind review` run, which
|
|
916
|
-
// this same tab with the new render.
|
|
921
|
+
// The tab neither veils over nor freezes: the stage stays browsable and the
|
|
922
|
+
// ledger stays visible; pinning and Approve go away until the next round.
|
|
923
|
+
// Heartbeat polling waits for the agent's next `slidemind review` run, which
|
|
924
|
+
// reloads this same tab with the new render.
|
|
917
925
|
function enterWorking(note) {
|
|
918
926
|
working = true;
|
|
919
927
|
workingNote = note || null;
|
|
920
928
|
document.title = WAITING_TITLE; // the tab label signals the state from the tab bar
|
|
921
929
|
closePopover();
|
|
930
|
+
hideHoverBox();
|
|
931
|
+
saveCtx(); // a plain refresh of this round stays in working mode
|
|
922
932
|
renderAll();
|
|
923
933
|
startNextRoundPolling();
|
|
924
934
|
}
|
|
@@ -932,15 +942,16 @@
|
|
|
932
942
|
}
|
|
933
943
|
}, 120);
|
|
934
944
|
}
|
|
935
|
-
// The shared heartbeat poll (gate-session.js) with this gate's two endings
|
|
936
|
-
// a retired tab hands its backlog over FIRST — the fresh window that replaces
|
|
937
|
-
// it starts with an empty sessionStorage, so anything still queued here would
|
|
938
|
-
// otherwise be lost (the handover also drains the new run immediately).
|
|
945
|
+
// The shared heartbeat poll (gate-session.js) with this gate's two endings.
|
|
939
946
|
function startNextRoundPolling() {
|
|
940
947
|
SES.startNextRoundPolling({
|
|
941
948
|
onEnded: showSessionEnded,
|
|
949
|
+
// A retired tab still holding unsent drafts must not discard them: the
|
|
950
|
+
// fresh window that replaced it has its own (empty) sessionStorage, so
|
|
951
|
+
// this tab reloads into the new round instead and flags itself.
|
|
942
952
|
onSuperseded: function () {
|
|
943
|
-
|
|
953
|
+
if (draftComments().length) SES.markRoundReadyAndReload();
|
|
954
|
+
else showSuperseded();
|
|
944
955
|
},
|
|
945
956
|
});
|
|
946
957
|
}
|
|
@@ -989,6 +1000,14 @@
|
|
|
989
1000
|
renderPins();
|
|
990
1001
|
saveCtx(); // the slide the reviewer is on survives the round reload
|
|
991
1002
|
}
|
|
1003
|
+
refreshChartNote();
|
|
1004
|
+
}
|
|
1005
|
+
// The chart stand-in stamps its spec on its root as data-sm-chart.
|
|
1006
|
+
function refreshChartNote() {
|
|
1007
|
+
if (!chartNoteEl) return;
|
|
1008
|
+
var st = stage();
|
|
1009
|
+
var hasChart = !!(st && st.querySelector("[data-sm-chart]"));
|
|
1010
|
+
chartNoteEl.classList.toggle("on", hasChart);
|
|
992
1011
|
}
|
|
993
1012
|
|
|
994
1013
|
// ---------- wire up ----------
|
|
@@ -1020,8 +1039,8 @@
|
|
|
1020
1039
|
setTimeout(init, 50);
|
|
1021
1040
|
return;
|
|
1022
1041
|
}
|
|
1023
|
-
// Session first: it decides the MODE this page renders in (a
|
|
1024
|
-
//
|
|
1042
|
+
// Session first: it decides the MODE this page renders in (a refresh of an
|
|
1043
|
+
// already-sent round stays in working mode) and the round-transition state.
|
|
1025
1044
|
var flagged = SES.consumeRoundFlag();
|
|
1026
1045
|
restoreSession();
|
|
1027
1046
|
|
|
@@ -1035,10 +1054,10 @@
|
|
|
1035
1054
|
buildPinLayer();
|
|
1036
1055
|
buildHoverBox();
|
|
1037
1056
|
|
|
1038
|
-
// Pinnable
|
|
1039
|
-
//
|
|
1057
|
+
// Pinnable only while PARKED: while the agent is fixing, the render on
|
|
1058
|
+
// screen is superseded and the strip says to comment on the next round.
|
|
1040
1059
|
st.addEventListener("click", function (e) {
|
|
1041
|
-
if (approved) return;
|
|
1060
|
+
if (approved || working) return;
|
|
1042
1061
|
if (e.target.closest("[data-sm-overlay]")) return;
|
|
1043
1062
|
var sr = st.getBoundingClientRect();
|
|
1044
1063
|
var x = (e.clientX - sr.left) / sr.width;
|
|
@@ -1050,7 +1069,7 @@
|
|
|
1050
1069
|
|
|
1051
1070
|
// Hover-box: outline the element under the cursor (the add-in's affordance).
|
|
1052
1071
|
st.addEventListener("pointermove", function (e) {
|
|
1053
|
-
if (approved || locked) return;
|
|
1072
|
+
if (approved || working || locked) return;
|
|
1054
1073
|
if (e.target.closest("[data-sm-overlay]")) {
|
|
1055
1074
|
hideHoverBox();
|
|
1056
1075
|
return;
|
|
@@ -1083,11 +1102,9 @@
|
|
|
1083
1102
|
renderAll();
|
|
1084
1103
|
|
|
1085
1104
|
if (working) {
|
|
1086
|
-
//
|
|
1087
|
-
// the reviewer already knows the agent is mid-fix.
|
|
1105
|
+
// A refresh of a round the reviewer already sent: keep waiting quietly.
|
|
1088
1106
|
document.title = WAITING_TITLE;
|
|
1089
1107
|
startNextRoundPolling();
|
|
1090
|
-
flushBacklog().then(renderAll); // rows flip queued → sent
|
|
1091
1108
|
} else {
|
|
1092
1109
|
// Parked round: the agent is waiting on the reviewer — announce it.
|
|
1093
1110
|
SES.announceNewRound(flagged);
|