slidemind 0.3.20 → 0.4.1
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/AGENTS.md +592 -16
- package/LICENSE +4 -0
- package/README.md +203 -8
- package/THIRD-PARTY-NOTICES.md +201 -0
- package/bin/slidemind.mjs +50 -2
- package/package.json +5 -3
- package/skills/create-slides/SKILL.md +484 -886
- package/skills/search/SKILL.md +46 -23
- package/src/bridge/build-and-push.mjs +548 -73
- package/src/bridge/bundle.mjs +100 -31
- package/src/bridge/capture.mjs +14 -17
- package/src/bridge/client/brands.mjs +157 -18
- package/src/bridge/client/engine-routing.mjs +48 -13
- package/src/bridge/client/engine-search.mjs +24 -1
- package/src/bridge/client/render.mjs +27 -1
- package/src/bridge/client/transport.mjs +499 -23
- package/src/bridge/client.mjs +10 -0
- package/src/bridge/config.mjs +67 -6
- package/src/bridge/engine-download.mjs +0 -10
- package/src/bridge/graph-auth.mjs +55 -3
- package/src/bridge/image-intent-envelope.mjs +239 -0
- package/src/bridge/package-preview.mjs +67 -0
- package/src/bridge/sharepoint.mjs +914 -0
- package/src/builders/asset-fetch.mjs +203 -0
- package/src/builders/block-dividers.mjs +83 -0
- package/src/builders/build.mjs +171 -87
- package/src/builders/card-height.mjs +294 -0
- package/src/builders/chart-hole.mjs +183 -0
- package/src/builders/chart-jsx.mjs +410 -0
- package/src/builders/chart-labels.mjs +326 -0
- package/src/builders/concept-geometry.mjs +370 -0
- package/src/builders/concept-intent.mjs +200 -0
- package/src/builders/concept-layout.mjs +79 -0
- package/src/builders/concept-lines.mjs +509 -0
- package/src/builders/concept-lists.mjs +270 -0
- package/src/builders/concept-marks.mjs +84 -0
- package/src/builders/concept-relationships.mjs +12 -0
- package/src/builders/concept-spec.mjs +645 -0
- package/src/builders/concept-text.mjs +140 -0
- package/src/builders/concept-waves.mjs +57 -0
- package/src/builders/copy-fit.mjs +139 -0
- package/src/builders/design-tokens.mjs +347 -0
- package/src/builders/draft-check.mjs +552 -340
- package/src/builders/draft-lines.mjs +369 -0
- package/src/builders/edit-route.mjs +29 -0
- package/src/builders/figure-sketch.mjs +375 -0
- package/src/builders/fold-sets.mjs +639 -0
- package/src/builders/gate-server.mjs +9 -4
- package/src/builders/gate-session.js +273 -12
- package/src/builders/gate-shell.js +1494 -0
- package/src/builders/image-intent-check.mjs +308 -0
- package/src/builders/image-use-reconcile.mjs +107 -0
- package/src/builders/kit-helpers.mjs +121 -0
- package/src/builders/layout-names.mjs +32 -0
- package/src/builders/legacy-reference-uses.mjs +179 -0
- package/src/builders/line-fit.mjs +224 -0
- package/src/builders/line-native.mjs +895 -0
- package/src/builders/line-relations.mjs +230 -0
- package/src/builders/lint-fix.mjs +117 -9
- package/src/builders/lint.mjs +25 -20
- package/src/builders/list-jsx.mjs +88 -0
- package/src/builders/placement.mjs +133 -0
- package/src/builders/plan-checks.mjs +135 -0
- package/src/builders/plan-comments.mjs +19 -0
- package/src/builders/plan-handlers.mjs +95 -0
- package/src/builders/plan-markdown-edit.mjs +3 -3
- package/src/builders/plan-overlay.js +1530 -1608
- package/src/builders/plan.mjs +689 -186
- package/src/builders/preset-definitions.json +1 -0
- package/src/builders/preset-eval.mjs +244 -0
- package/src/builders/preview.html +51 -120
- package/src/builders/red-thread-backbone.mjs +135 -36
- package/src/builders/repair.mjs +205 -0
- package/src/builders/repeat-groups.mjs +540 -0
- package/src/builders/resolve-skill.mjs +5 -4
- package/src/builders/review-overlay.js +296 -293
- package/src/builders/review.mjs +305 -84
- package/src/builders/rule-shift.mjs +96 -0
- package/src/builders/seat.mjs +354 -0
- package/src/builders/settle-driver.mjs +236 -0
- package/src/builders/settle.mjs +1448 -0
- package/src/builders/slide-prep.mjs +67 -0
- package/src/builders/spec-assets.mjs +236 -0
- package/src/builders/spec-match.mjs +56 -0
- package/src/builders/stack.mjs +413 -0
- package/src/builders/storyboard-markdown.mjs +70 -27
- package/src/builders/storyboard-schema.mjs +54 -114
- package/src/builders/text-blocks.mjs +212 -0
- package/src/builders/text-card-fit.mjs +552 -0
- package/src/builders/text-stack.mjs +276 -0
- package/src/builders/transcribe.mjs +1069 -0
- package/src/cli/assets.mjs +164 -0
- package/src/cli/common.mjs +173 -12
- package/src/cli/concept-spec-support.mjs +76 -0
- package/src/cli/concepts.mjs +1002 -103
- package/src/cli/deck.mjs +401 -65
- package/src/cli/diagnose.mjs +4 -2
- package/src/cli/engine-helpers.mjs +17 -10
- package/src/cli/engine-results.mjs +76 -3
- package/src/cli/help.mjs +232 -28
- package/src/cli/install.mjs +9 -0
- package/src/cli/maintenance.mjs +104 -0
- package/src/cli/read.mjs +2 -1
- package/src/cli/ready.mjs +6 -3
- package/src/cli/record.mjs +522 -0
- package/src/cli/remote.mjs +818 -0
- package/src/cli/revision.mjs +686 -0
- package/src/cli/search.mjs +30 -11
- package/src/cli/session.mjs +56 -13
- package/src/cli/setup.mjs +46 -2
- package/src/cli/status.mjs +142 -33
- package/src/cli/tasks.mjs +246 -0
- package/src/cli/transcribe.mjs +854 -0
- package/src/cli/workspace-ui.mjs +260 -0
- package/src/cli/workspace.mjs +212 -34
- package/src/install-channel.mjs +64 -7
- package/src/lib/adopt.mjs +346 -25
- package/src/lib/concept-spec-binding.mjs +96 -0
- package/src/lib/conversions.mjs +70 -6
- package/src/lib/convert-folded.mjs +107 -0
- package/src/lib/deck-paths.mjs +418 -0
- package/src/lib/durable-ids.mjs +47 -0
- package/src/lib/edit-operations.mjs +316 -0
- package/src/lib/edit-state.mjs +1244 -0
- package/src/lib/edit-transitions.mjs +120 -0
- package/src/lib/extensions.mjs +501 -0
- package/src/lib/image-assets.mjs +465 -0
- package/src/lib/image-derivatives.mjs +234 -0
- package/src/lib/image-intent-fingerprint.mjs +70 -0
- package/src/lib/image-intent-tags.mjs +176 -0
- package/src/lib/image-intent.mjs +802 -0
- package/src/lib/image-use-overrides.mjs +279 -0
- package/src/lib/inline-components.mjs +613 -0
- package/src/lib/lint-converted.mjs +68 -4
- package/src/lib/merge-json.mjs +163 -0
- package/src/lib/overwrite-guard.mjs +3 -3
- package/src/lib/presentation-context.mjs +531 -0
- package/src/lib/preservation-tags.mjs +157 -0
- package/src/lib/preservation-verdict.mjs +1342 -0
- package/src/lib/publish.mjs +1557 -0
- package/src/lib/rebase.mjs +945 -0
- package/src/lib/reconcile-ack.mjs +4 -6
- package/src/lib/reconcile.mjs +62 -24
- package/src/lib/record-client.mjs +175 -0
- package/src/lib/record-state.mjs +151 -0
- package/src/lib/remote.mjs +661 -0
- package/src/lib/revision.mjs +1210 -0
- package/src/lib/slide-guids.mjs +4 -6
- package/src/lib/slide-preservation.mjs +423 -0
- package/src/lib/slide-selection.mjs +9 -0
- package/src/lib/workspace-transaction.mjs +117 -0
- package/src/pptx/content-digest.mjs +334 -0
- package/src/pptx/deck-assemble.mjs +1278 -0
- package/src/pptx/fill/engine.mjs +2 -3
- package/src/pptx/fill/freeform.mjs +6 -3
- package/src/pptx/ooxml.mjs +20 -0
- package/src/pptx/package-evidence.mjs +383 -0
- package/src/pptx/tags.mjs +163 -11
- package/src/update-check.mjs +22 -4
- package/src/workspace/adapters.mjs +222 -0
- package/src/workspace/auth.mjs +256 -0
- package/src/workspace/autostart.mjs +204 -0
- package/src/workspace/daemon.mjs +360 -0
- package/src/workspace/discovery.mjs +180 -0
- package/src/workspace/edit.mjs +167 -0
- package/src/workspace/events.mjs +52 -0
- package/src/workspace/gate-client.mjs +290 -0
- package/src/workspace/handback.mjs +73 -0
- package/src/workspace/lifecycle.mjs +635 -0
- package/src/workspace/operations.mjs +200 -0
- package/src/workspace/pick-folder.mjs +152 -0
- package/src/workspace/registry.mjs +359 -0
- package/src/workspace/remote-links.mjs +397 -0
- package/src/workspace/remote-status.mjs +359 -0
- package/src/workspace/routes.mjs +1331 -0
- package/src/workspace/server.mjs +159 -0
- package/src/workspace/sync.mjs +1119 -0
- package/src/workspace/task-actions.mjs +215 -0
- package/src/workspace/tasks.mjs +507 -0
- package/src/workspace/ui/app.js +1886 -0
- package/src/workspace/ui/index.html +141 -0
- package/src/workspace/ui/plan-bridge.js +120 -0
- package/src/workspace/ui/sharepoint.svg +59 -0
- package/src/workspace/ui/styles.css +423 -0
- package/src/builders/visual-families.mjs +0 -46
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
// - Approve -> POST to the local server, which unblocks the command with the
|
|
17
17
|
// sign-off. Refused while drafts exist — send or delete them first, exactly
|
|
18
18
|
// the add-in's rule.
|
|
19
|
+
// - Every decision goes through the shell's decision wire (SHELL.decide): one
|
|
20
|
+
// attempt at a time, an operationId reused on every retry, and each refusal
|
|
21
|
+
// (a busy deck, a newer version, a sign-out, a stopped workspace) handled on
|
|
22
|
+
// its own — what the reviewer wrote survives every one of them.
|
|
19
23
|
//
|
|
20
24
|
// TWO MODES, not "reviewing vs. veiled over":
|
|
21
25
|
// - PARKED (the command is blocking on this tab): pinning, sending and
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
|
|
50
54
|
var STAGE_SEL = "#stage";
|
|
51
55
|
var HEADER_H = 62;
|
|
56
|
+
var chromeH = HEADER_H; // HEADER_H + any banner stacked under the bar
|
|
52
57
|
var SIDEBAR_W = 316; // panel + gaps reserved on the right when comments exist
|
|
53
58
|
// The ledger IS the state: one flat list of this session's pins. Pins, sidebar
|
|
54
59
|
// rows and the approve guard are all derived from it, so a comment keeps its
|
|
@@ -70,13 +75,20 @@
|
|
|
70
75
|
// as window.__SLIDEMIND_GATE__): the deck-keyed sessionStorage ledger, the
|
|
71
76
|
// server-nonce round transition, the round-ready announce and the heartbeat
|
|
72
77
|
// poll — one implementation, shared with the plan gate.
|
|
78
|
+
// The storage namespace: inside the workspace the registry id (two decks may
|
|
79
|
+
// share a name, and one tab's sessionStorage serves every deck it frames);
|
|
80
|
+
// standalone the deck name, as before.
|
|
81
|
+
var DECK_KEY = CFG.embedded && CFG.deckId ? "id:" + CFG.deckId : CFG.deck || "";
|
|
73
82
|
var SES = window.__SLIDEMIND_GATE__.createGateSession({
|
|
74
83
|
name: "review",
|
|
75
|
-
deck:
|
|
84
|
+
deck: DECK_KEY,
|
|
76
85
|
storage: sessionStorage,
|
|
77
86
|
fetch: function (u, o) {
|
|
78
87
|
return fetch(u, o);
|
|
79
88
|
},
|
|
89
|
+
endpoint: CFG.endpoint || "/review",
|
|
90
|
+
embedded: !!CFG.embedded,
|
|
91
|
+
subject: CFG.subjectHash || null, // the content version this page shows (rides in ctx)
|
|
80
92
|
round: CFG.round || "",
|
|
81
93
|
service: "slidemind-review",
|
|
82
94
|
heartbeat: CFG.heartbeat || "/__review/heartbeat",
|
|
@@ -97,128 +109,94 @@
|
|
|
97
109
|
// the sidebar jump to any slide by synthesising the deck's arrow-key nav.
|
|
98
110
|
var slideOrder = [];
|
|
99
111
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
112
|
+
// The shared chrome: bar, strips, hand-off dialog, veil, buttons, icons.
|
|
113
|
+
var SHELL = window.__SLIDEMIND_SHELL__.createGateShell({
|
|
114
|
+
CFG: CFG,
|
|
115
|
+
session: SES,
|
|
116
|
+
service: "slidemind-review",
|
|
117
|
+
what: { revising: "the slides", approved: "Slides" },
|
|
118
|
+
// The agent changes slides only while it works on an answer, and then asks
|
|
119
|
+
// again with a new preview. Approved once and not asked again: a later change
|
|
120
|
+
// never comes back here, so there is nothing to say — the "Approved" badge's
|
|
121
|
+
// tooltip already says later changes go straight into the PowerPoint file.
|
|
122
|
+
staleLine: CFG.embedded && CFG.approved && !CFG.asked
|
|
123
|
+
? null
|
|
124
|
+
: "The slides changed after this preview was made — your agent will show you the new version.",
|
|
125
|
+
isWorking: function () {
|
|
126
|
+
return working;
|
|
127
|
+
},
|
|
128
|
+
workingNote: function () {
|
|
129
|
+
return workingNote;
|
|
130
|
+
},
|
|
131
|
+
onOffset: function (total) {
|
|
132
|
+
// A strip under the bar makes the chrome taller: the rail follows the
|
|
133
|
+
// CSS var (set by the shell), the stage is refit under it.
|
|
134
|
+
chromeH = total;
|
|
135
|
+
applyStageFit();
|
|
136
|
+
},
|
|
137
|
+
onSaveCtx: function () {
|
|
138
|
+
saveCtx();
|
|
139
|
+
},
|
|
140
|
+
onChange: function () {
|
|
141
|
+
renderAll();
|
|
142
|
+
},
|
|
143
|
+
onDecided: function (decision, ack, body) {
|
|
144
|
+
decided(decision, body);
|
|
145
|
+
},
|
|
146
|
+
onDecisionFailed: function () {
|
|
147
|
+
renderAll();
|
|
148
|
+
},
|
|
149
|
+
onAlreadyAnswered: function (a) {
|
|
150
|
+
enterWorking(a && a.action === "approve" ? "Already approved." : a && a.action ? "Comments already sent." : "Already answered in another window.");
|
|
151
|
+
},
|
|
152
|
+
onApprovalPickedUp: function () {
|
|
153
|
+
showDone();
|
|
154
|
+
},
|
|
155
|
+
onLost: function () {
|
|
156
|
+
startNextRoundPolling(); // a re-run of the review reloads this tab, drafts and all
|
|
157
|
+
},
|
|
158
|
+
// A pin being written is not saved anywhere yet: a newer version waits for it.
|
|
159
|
+
reloadBlocker: function () {
|
|
160
|
+
var ta = draftPopover && draftPopover.querySelector("textarea");
|
|
161
|
+
return ta && ta.value.trim() ? "typing" : null;
|
|
162
|
+
},
|
|
163
|
+
reloadIntoNewRound: function () {
|
|
164
|
+
saveLedger();
|
|
165
|
+
saveCtx();
|
|
166
|
+
SES.markRoundReadyAndReload();
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
var el = SHELL.el;
|
|
170
|
+
var ICONS = SHELL.ICONS;
|
|
171
|
+
var mkButton = SHELL.mkButton;
|
|
142
172
|
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
'<svg
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 10c0 4.4-8 12-8 12s-8-7.6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></svg>',
|
|
155
|
-
arrow:
|
|
156
|
-
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>',
|
|
157
|
-
};
|
|
173
|
+
// The stage cursor while pinning: a comment bubble (the add-in's affordance);
|
|
174
|
+
// `crosshair` when a browser refuses the data-URL cursor.
|
|
175
|
+
var COMMENT_CURSOR =
|
|
176
|
+
"url(\"data:image/svg+xml," +
|
|
177
|
+
encodeURIComponent(
|
|
178
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28">' +
|
|
179
|
+
'<path d="M5 23.5V9.5a5 5 0 0 1 5-5h9a5 5 0 0 1 5 5v5a5 5 0 0 1-5 5h-8.6z" fill="#161618" fill-opacity=".96" stroke="#fff" stroke-width="1.5" stroke-linejoin="round"/>' +
|
|
180
|
+
'<path d="M14.5 9.2v5.6M11.7 12h5.6" stroke="#a9c2ff" stroke-width="1.7" stroke-linecap="round"/>' +
|
|
181
|
+
"</svg>",
|
|
182
|
+
) +
|
|
183
|
+
'") 5 24, crosshair';
|
|
158
184
|
|
|
159
|
-
// ---------- styles ----------
|
|
185
|
+
// ---------- styles (review-specific; the chrome comes from the shell) ----------
|
|
160
186
|
var CSS =
|
|
161
|
-
"@keyframes sm-drift-a{0%,100%{transform:translateX(-5px)}50%{transform:translateX(5px)}}" +
|
|
162
|
-
"@keyframes sm-drift-b{0%,100%{transform:translateX(5px)}50%{transform:translateX(-5px)}}" +
|
|
163
|
-
"@keyframes sm-bar-in{from{transform:translateY(-100%);opacity:0}to{transform:none;opacity:1}}" +
|
|
164
|
-
"@keyframes sm-pop-in{from{opacity:0;transform:translateY(5px) scale(.98)}to{opacity:1;transform:none}}" +
|
|
165
187
|
"@keyframes sm-pin-in{from{transform:translate(-50%,-50%) scale(0)}to{transform:translate(-50%,-50%) scale(1)}}" +
|
|
166
|
-
"@keyframes sm-veil-in{from{opacity:0}to{opacity:1}}" +
|
|
167
|
-
"@keyframes sm-rise{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}" +
|
|
168
|
-
"@keyframes sm-pulse{0%,100%{opacity:.45}50%{opacity:.85}}" +
|
|
169
|
-
":root{--sm-bg:var(--deck-chrome-bg,#111113);--sm-surface:var(--deck-chrome-bg-2,#161618);" +
|
|
170
|
-
"--sm-border:var(--deck-chrome-border,#2b2b30);--sm-fg:var(--deck-chrome-fg,#ededed);" +
|
|
171
|
-
"--sm-dim:var(--deck-chrome-dim,#9a9aa0);--sm-accent:#a9c2ff;--sm-success:#2ecc71;--sm-danger:#f87171;" +
|
|
172
|
-
"--sm-font:'Poppins',ui-sans-serif,system-ui,sans-serif;--sm-word:'Urbanist',var(--sm-font);}" +
|
|
173
|
-
".sm-o,.sm-o *{box-sizing:border-box;}" +
|
|
174
|
-
/* header */
|
|
175
|
-
".sm-bar{position:fixed;top:0;left:0;right:0;z-index:2147483600;display:flex;align-items:center;" +
|
|
176
|
-
"gap:16px;height:var(--deck-chrome-h,62px);padding:0 18px;color:var(--sm-fg);font-family:var(--sm-font);font-size:13px;" +
|
|
177
|
-
"background:var(--sm-bg);border-bottom:1px solid var(--sm-border);" +
|
|
178
|
-
"user-select:none;animation:sm-bar-in .5s cubic-bezier(.2,.7,.2,1);}" +
|
|
179
|
-
".sm-brand{display:flex;align-items:center;gap:11px;}" +
|
|
180
|
-
".sm-logo{position:relative;width:48px;height:27px;border-radius:5px;overflow:hidden;flex-shrink:0;background:#0a0c11;box-shadow:0 1px 3px rgba(0,0,0,.5);}" +
|
|
181
|
-
".sm-logo-svg{position:absolute;inset:0;z-index:1;}.sm-logo-svg svg{width:100%;height:100%;display:block;}" +
|
|
182
|
-
".sm-logo-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;}" +
|
|
183
|
-
".sm-word{display:flex;flex-direction:column;line-height:.98;font-family:var(--sm-word);" +
|
|
184
|
-
"font-weight:300;font-size:13px;letter-spacing:.46em;color:var(--sm-fg);}" +
|
|
185
|
-
".sm-word b{font-weight:300;display:block;will-change:transform;}" +
|
|
186
|
-
".sm-word .top{animation:sm-drift-a 20s ease-in-out infinite;}" +
|
|
187
|
-
".sm-word .bot{margin-left:.18em;animation:sm-drift-b 20s ease-in-out infinite;}" +
|
|
188
|
-
".sm-sep{width:1px;height:28px;background:var(--sm-border);}" +
|
|
189
|
-
".sm-meta{display:flex;flex-direction:column;gap:2px;}" +
|
|
190
|
-
".sm-meta .t{font-weight:600;font-size:13px;}" +
|
|
191
|
-
".sm-meta .h{font-size:11.5px;color:var(--sm-dim);}" +
|
|
192
|
-
".sm-note{max-width:min(560px,38vw);font-size:10.5px;line-height:1.35;color:#c7c7cf;" +
|
|
193
|
-
"border-left:2px solid var(--sm-accent);padding-left:10px;}" +
|
|
194
|
-
".sm-note b{font-weight:600;color:#f4f4f5;}" +
|
|
195
188
|
".sm-chart-note{display:none;border-left-color:#fbbf24;}" +
|
|
196
|
-
".sm-chart-note.on{display
|
|
189
|
+
".sm-chart-note.on{display:-webkit-box;}" +
|
|
197
190
|
".sm-bar~.deck__rail,.deck__rail{top:var(--deck-chrome-h,62px);}" +
|
|
198
|
-
".sm-
|
|
199
|
-
".sm-count{font-size:12px;color:var(--sm-dim);font-variant-numeric:tabular-nums;}" +
|
|
191
|
+
"@media (max-width:760px){.sm-bar~.deck__rail,.deck__rail{top:auto;}}" +
|
|
200
192
|
/* working strip: the agent is fixing — the preview stays live behind it */
|
|
201
193
|
".sm-status{display:none;align-items:center;gap:9px;max-width:min(420px,32vw);padding:7px 12px;border-radius:10px;" +
|
|
202
194
|
"border:1px solid color-mix(in srgb,var(--sm-accent) 32%,transparent);" +
|
|
203
195
|
"background:color-mix(in srgb,var(--sm-accent) 10%,transparent);font-size:11.5px;line-height:1.4;color:#d7d7de;}" +
|
|
204
|
-
".sm-status.on{display:flex;}" +
|
|
196
|
+
".sm-status.on{display:flex;flex-shrink:0;max-height:52px;overflow:hidden;}" +
|
|
197
|
+
/* the working chip outranks the notes beside it: one of them fits a 62px bar */
|
|
198
|
+
".sm-status.on~.sm-note{display:none;}" +
|
|
205
199
|
".sm-status b{display:block;font-weight:600;font-size:12px;color:#f4f4f5;}" +
|
|
206
|
-
".sm-spin{width:11px;height:11px;flex-shrink:0;border-radius:50%;border:2px solid var(--sm-accent);" +
|
|
207
|
-
"border-top-color:transparent;animation:sm-spin 0.9s linear infinite;}" +
|
|
208
|
-
"@keyframes sm-spin{to{transform:rotate(360deg)}}" +
|
|
209
|
-
/* buttons */
|
|
210
|
-
".sm-btn{display:inline-flex;align-items:center;gap:7px;border:1px solid transparent;border-radius:9px;" +
|
|
211
|
-
"padding:8px 14px;font-family:var(--sm-font);font-weight:600;font-size:12.5px;cursor:pointer;" +
|
|
212
|
-
"transition:transform .14s ease,background .14s ease,box-shadow .14s ease,border-color .14s ease;white-space:nowrap;}" +
|
|
213
|
-
".sm-btn svg{width:15px;height:15px;flex-shrink:0;}" +
|
|
214
|
-
".sm-btn-primary{background:#f4f4f5;color:#0f0f0f;}" +
|
|
215
|
-
".sm-btn-primary:hover{background:#fff;transform:translateY(-1px);box-shadow:0 6px 18px rgba(0,0,0,.45);}" +
|
|
216
|
-
".sm-btn-primary svg{color:var(--sm-success);}" +
|
|
217
|
-
".sm-btn-ghost{background:transparent;border-color:var(--sm-border);color:var(--sm-fg);}" +
|
|
218
|
-
".sm-btn-ghost:hover{background:rgba(255,255,255,.06);border-color:#3a3a40;}" +
|
|
219
|
-
".sm-btn-danger{background:transparent;border-color:rgba(248,113,113,.4);color:var(--sm-danger);}" +
|
|
220
|
-
".sm-btn-danger:hover{background:rgba(248,113,113,.12);}" +
|
|
221
|
-
".sm-btn.is-off{opacity:.4;cursor:not-allowed;pointer-events:none;}" +
|
|
222
200
|
/* pins */
|
|
223
201
|
".sm-pinlayer{position:fixed;inset:0;z-index:2147483500;pointer-events:none;}" +
|
|
224
202
|
".sm-pin{position:fixed;transform:translate(-50%,-50%);width:26px;height:26px;border-radius:50%;" +
|
|
@@ -237,35 +215,20 @@
|
|
|
237
215
|
".sm-pop-head{display:flex;align-items:center;gap:6px;font-weight:600;font-size:11.5px;" +
|
|
238
216
|
"color:var(--sm-accent);margin-bottom:9px;word-break:break-all;}" +
|
|
239
217
|
".sm-pop-head svg{width:13px;height:13px;flex-shrink:0;}" +
|
|
240
|
-
".sm-pop-head .mono{font-family:
|
|
218
|
+
".sm-pop-head .mono{font-family:var(--sm-mono);}" +
|
|
241
219
|
".sm-ta{width:100%;min-height:74px;resize:vertical;background:#0c0c0e;color:var(--sm-fg);" +
|
|
242
220
|
"border:1px solid var(--sm-border);border-radius:8px;padding:9px;font:inherit;outline:none;transition:border-color .15s;}" +
|
|
243
221
|
".sm-ta:focus{border-color:var(--sm-accent);}.sm-ta::placeholder{color:#6b6b72;}" +
|
|
244
222
|
".sm-row{display:flex;gap:8px;margin-top:10px;}.sm-row.end{justify-content:flex-end;}.sm-row.split{justify-content:space-between;}" +
|
|
245
223
|
".sm-pop .sm-btn{padding:6px 12px;font-size:12px;}" +
|
|
246
224
|
".sm-ghost-body{white-space:pre-wrap;color:#d4d4d8;font-size:12.5px;line-height:1.5;}" +
|
|
247
|
-
/* done veil */
|
|
248
|
-
".sm-veil{position:fixed;inset:0;z-index:2147483647;display:flex;flex-direction:column;align-items:center;" +
|
|
249
|
-
"justify-content:center;gap:30px;font-family:var(--sm-font);color:var(--sm-fg);" +
|
|
250
|
-
"background:radial-gradient(ellipse at center,rgba(16,16,18,.93),rgba(5,5,7,.98));" +
|
|
251
|
-
"-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);animation:sm-veil-in .4s ease;}" +
|
|
252
|
-
".sm-veil .sm-logo{width:188px;height:106px;border-radius:14px;animation:sm-rise .6s cubic-bezier(.2,.7,.2,1) both;}" +
|
|
253
|
-
".sm-veil .word{display:flex;flex-direction:column;gap:6px;font-family:var(--sm-word);font-weight:300;" +
|
|
254
|
-
"font-size:36px;letter-spacing:.6em;text-align:center;animation:sm-rise .6s .06s cubic-bezier(.2,.7,.2,1) both;}" +
|
|
255
|
-
".sm-veil .word b{font-weight:300;}" +
|
|
256
|
-
".sm-veil .word .top{animation:sm-drift-a 18s ease-in-out infinite;}" +
|
|
257
|
-
".sm-veil .word .bot{margin-left:.3em;animation:sm-drift-b 18s ease-in-out infinite;}" +
|
|
258
|
-
".sm-veil .status{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:600;animation:sm-rise .6s .12s cubic-bezier(.2,.7,.2,1) both;}" +
|
|
259
|
-
".sm-veil .status svg{width:24px;height:24px;color:var(--sm-success);}" +
|
|
260
|
-
".sm-veil .sub{color:var(--sm-dim);font-size:13.5px;animation:sm-rise .6s .18s cubic-bezier(.2,.7,.2,1) both;}" +
|
|
261
|
-
".sm-veil .sub.sm-pulse{animation:sm-rise .6s .18s cubic-bezier(.2,.7,.2,1) both,sm-pulse 2s .9s ease-in-out infinite;}" +
|
|
262
225
|
/* hover box */
|
|
263
226
|
".sm-hover{position:fixed;z-index:2147483450;pointer-events:none;border-radius:5px;border:2px solid var(--sm-accent);" +
|
|
264
227
|
"box-shadow:0 0 0 1px rgba(0,0,0,.3),0 6px 22px rgba(0,0,0,.28);opacity:0;" +
|
|
265
228
|
"transition:left .07s ease,top .07s ease,width .07s ease,height .07s ease,opacity .12s ease;}" +
|
|
266
229
|
/* sidebar */
|
|
267
230
|
"@keyframes sm-side-in{from{opacity:0;transform:translateX(16px)}to{opacity:1;transform:none}}" +
|
|
268
|
-
".sm-side{position:fixed;top:
|
|
231
|
+
".sm-side{position:fixed;top:calc(var(--deck-chrome-h,62px) + 12px);right:14px;bottom:14px;width:288px;z-index:2147483520;display:flex;flex-direction:column;" +
|
|
269
232
|
"background:rgba(18,18,20,.93);-webkit-backdrop-filter:blur(13px);backdrop-filter:blur(13px);border:1px solid var(--sm-border);" +
|
|
270
233
|
"border-radius:14px;box-shadow:0 18px 54px rgba(0,0,0,.5);font-family:var(--sm-font);color:var(--sm-fg);overflow:hidden;" +
|
|
271
234
|
"animation:sm-side-in .28s cubic-bezier(.2,.7,.2,1);}" +
|
|
@@ -274,7 +237,7 @@
|
|
|
274
237
|
".sm-side-head .n{margin-left:auto;color:var(--sm-dim);font-weight:500;font-variant-numeric:tabular-nums;}" +
|
|
275
238
|
".sm-side-body{flex:1;overflow-y:auto;padding:8px 8px 12px;}" +
|
|
276
239
|
".sm-side-grp{margin-top:9px;}.sm-side-grp:first-child{margin-top:2px;}" +
|
|
277
|
-
".sm-side-grp-h{font-size:11px;font-weight:600;color:var(--sm-dim);padding:6px 8px 4px;font-family:
|
|
240
|
+
".sm-side-grp-h{font-size:11px;font-weight:600;color:var(--sm-dim);padding:6px 8px 4px;font-family:var(--sm-mono);}" +
|
|
278
241
|
".sm-side-sub{font-size:10px;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:var(--sm-dim);padding:7px 8px 2px;}" +
|
|
279
242
|
".sm-side-row{display:flex;align-items:flex-start;gap:9px;padding:7px 8px;border-radius:9px;cursor:pointer;transition:background .12s;}" +
|
|
280
243
|
".sm-side-row:hover{background:rgba(255,255,255,.05);}" +
|
|
@@ -282,12 +245,14 @@
|
|
|
282
245
|
"font-size:10px;font-weight:600;margin-top:1px;background:#161618;color:#fff;border:1.5px solid rgba(255,255,255,.85);}" +
|
|
283
246
|
".sm-side-chip.ghost{background:rgba(20,20,22,.6);color:#a1a1aa;border:1.5px dashed #71717a;}" +
|
|
284
247
|
".sm-side-txt{font-size:12.5px;line-height:1.4;color:#e4e4e7;word-break:break-word;}" +
|
|
285
|
-
".sm-side-loc{font-size:10.5px;color:var(--sm-dim);font-family:
|
|
248
|
+
".sm-side-loc{font-size:10.5px;color:var(--sm-dim);font-family:var(--sm-mono);margin-top:2px;word-break:break-all;}" +
|
|
286
249
|
/* ledger status chip — where each pin stands, per row */
|
|
287
250
|
".sm-side-state{display:inline-block;margin-top:4px;padding:1px 7px;border-radius:999px;font-size:9.5px;font-weight:600;" +
|
|
288
251
|
"letter-spacing:.05em;text-transform:uppercase;border:1px solid var(--sm-border);color:var(--sm-dim);}" +
|
|
289
252
|
'.sm-side-state[data-state="draft"]{border-color:color-mix(in srgb,var(--sm-accent) 45%,transparent);color:var(--sm-accent);}' +
|
|
290
|
-
'.sm-side-state[data-state="sent"]{border-color:color-mix(in srgb,var(--sm-accent) 30%,transparent);color:#c6d4f5;}'
|
|
253
|
+
'.sm-side-state[data-state="sent"]{border-color:color-mix(in srgb,var(--sm-accent) 30%,transparent);color:#c6d4f5;}' +
|
|
254
|
+
/* small screens: the comment rail docks under the stage instead of over it */
|
|
255
|
+
"@media (max-width:760px){.sm-side{top:auto;right:8px;left:8px;bottom:8px;width:auto;max-height:38vh;}.sm-note{display:none;}}";
|
|
291
256
|
|
|
292
257
|
// ---------- helpers ----------
|
|
293
258
|
function stemOf(jsxPath) {
|
|
@@ -334,36 +299,6 @@
|
|
|
334
299
|
return c.status === "draft";
|
|
335
300
|
});
|
|
336
301
|
}
|
|
337
|
-
function el(tag, cls, html) {
|
|
338
|
-
var e = document.createElement(tag);
|
|
339
|
-
if (cls) e.className = cls;
|
|
340
|
-
if (html != null) e.innerHTML = html;
|
|
341
|
-
return e;
|
|
342
|
-
}
|
|
343
|
-
function wordmarkHtml() {
|
|
344
|
-
return '<b class="top">slide</b><b class="bot">mind</b>';
|
|
345
|
-
}
|
|
346
|
-
// A logo mark. Genuine path: the add-in's per-brand orb PNG (CFG.logoMark),
|
|
347
|
-
// shipped on the bundle and shown verbatim. Fallback (no PNG materialized
|
|
348
|
-
// yet): a plain embossed slide glyph — no CLI-side orb re-creation.
|
|
349
|
-
function logoNode(cls, idSuffix) {
|
|
350
|
-
var box = el("div", cls);
|
|
351
|
-
if (CFG.logoMark) {
|
|
352
|
-
var img = el("img", "sm-logo-img");
|
|
353
|
-
img.src = CFG.logoMark;
|
|
354
|
-
img.alt = "slidemind";
|
|
355
|
-
box.appendChild(img);
|
|
356
|
-
return box;
|
|
357
|
-
}
|
|
358
|
-
box.appendChild(el("div", "sm-logo-svg", idSuffix ? logoWithIds(idSuffix) : LOGO_SVG));
|
|
359
|
-
return box;
|
|
360
|
-
}
|
|
361
|
-
function mkButton(label, variant, iconName) {
|
|
362
|
-
var b = el("button", "sm-btn sm-btn-" + variant);
|
|
363
|
-
b.innerHTML = (iconName ? ICONS[iconName] : "") + "<span>" + label + "</span>";
|
|
364
|
-
return b;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
302
|
// Build the resolvedTarget HitRegion for an element, in the same shape the
|
|
368
303
|
// add-in's preview-review uses, so the harness-side formatter ports verbatim.
|
|
369
304
|
function describeTarget(elx) {
|
|
@@ -399,19 +334,33 @@
|
|
|
399
334
|
// The context the NEXT round restores from: the server nonce (the round
|
|
400
335
|
// marker, added by the shared session), which slide the reviewer was on, and
|
|
401
336
|
// whether this round was already sent (so a plain refresh stays in WORKING).
|
|
337
|
+
function notAskedYet() {
|
|
338
|
+
return SHELL.notAskedYet();
|
|
339
|
+
}
|
|
340
|
+
var arrivedAfterRevising = false; // this load is the round the reviewer was waiting for
|
|
341
|
+
var askedAgainUnchanged = false; // …and the agent asked again without changing anything
|
|
342
|
+
|
|
402
343
|
function saveCtx() {
|
|
403
|
-
|
|
344
|
+
var f = { round: roundNo, stem: currentStem, working: working };
|
|
345
|
+
var shell = SHELL.ctxFields(); // the hand-off dialog and the revising strip
|
|
346
|
+
for (var k in shell) f[k] = shell[k];
|
|
347
|
+
SES.saveCtx(f);
|
|
404
348
|
}
|
|
405
349
|
|
|
406
350
|
// Restore this tab's session and work out whether THIS load is a new round.
|
|
407
351
|
// Runs before the first render so the chrome is built in the right mode.
|
|
408
352
|
function restoreSession() {
|
|
409
|
-
|
|
353
|
+
// A changed nonce while the agent still revises this page's comments is not
|
|
354
|
+
// the next round (SHELL.holdsRound) — nothing it was handed is answered yet.
|
|
355
|
+
var st = SES.restore(CFG.ledgerSeed, { hold: SHELL.holdsRound });
|
|
410
356
|
ledger = st.ledger;
|
|
411
357
|
roundNo = st.roundNo;
|
|
412
358
|
working = st.working;
|
|
413
359
|
// Reviewer context survives the round: come back to the same slide.
|
|
414
360
|
if (st.ctx && typeof st.ctx.stem === "string" && st.ctx.stem) pendingStem = st.ctx.stem;
|
|
361
|
+
arrivedAfterRevising = SHELL.restoreCtx(st.ctx, working, st.newRound);
|
|
362
|
+
// Same bytes, asked again: the earlier pins stay "sent", never "worked in".
|
|
363
|
+
askedAgainUnchanged = !!(st.unchanged && st.hadSent);
|
|
415
364
|
saveLedger();
|
|
416
365
|
saveCtx();
|
|
417
366
|
}
|
|
@@ -419,31 +368,20 @@
|
|
|
419
368
|
// ---------- chrome (top bar) ----------
|
|
420
369
|
var bar, approveBtn, sendBtn, countLabel, statusEl, chartNoteEl;
|
|
421
370
|
function buildChrome() {
|
|
422
|
-
|
|
423
|
-
bar.
|
|
424
|
-
|
|
425
|
-
var brand = el("div", "sm-brand");
|
|
426
|
-
brand.appendChild(logoNode("sm-logo"));
|
|
427
|
-
brand.appendChild(el("div", "sm-word", wordmarkHtml()));
|
|
428
|
-
bar.appendChild(brand);
|
|
429
|
-
bar.appendChild(el("div", "sm-sep"));
|
|
430
|
-
|
|
431
|
-
var meta = el("div", "sm-meta");
|
|
432
|
-
meta.appendChild(el("div", "t", "Review"));
|
|
433
|
-
meta.appendChild(el("div", "h", "Click any element to comment · ← → to switch slide"));
|
|
434
|
-
bar.appendChild(meta);
|
|
435
|
-
|
|
371
|
+
var built = SHELL.buildBar("Review", "Click any element to comment · ← → to switch slide");
|
|
372
|
+
bar = built.bar;
|
|
373
|
+
var grow = built.grow;
|
|
436
374
|
// The working strip: shown instead of the old full-screen waiting veil, so
|
|
437
375
|
// the preview behind it stays readable and pinnable while the agent fixes.
|
|
438
376
|
statusEl = el("div", "sm-o sm-status");
|
|
439
|
-
|
|
377
|
+
grow.appendChild(statusEl);
|
|
440
378
|
|
|
441
379
|
var note = el(
|
|
442
380
|
"div",
|
|
443
381
|
"sm-note",
|
|
444
|
-
"<b>Ask for slide changes here.</b>
|
|
382
|
+
"<b>Ask for slide changes here.</b> You approve once: your agent then builds the deck and makes later changes directly in PowerPoint. Preview may differ slightly from final PowerPoint.",
|
|
445
383
|
);
|
|
446
|
-
|
|
384
|
+
grow.appendChild(note);
|
|
447
385
|
|
|
448
386
|
// Add-in parity: a slide holding a <Chart> shows a stand-in, never
|
|
449
387
|
// PowerPoint's render — warn on exactly those slides.
|
|
@@ -452,9 +390,11 @@
|
|
|
452
390
|
"sm-note sm-chart-note",
|
|
453
391
|
"<b>Preview only</b> — the chart may look different in PowerPoint.",
|
|
454
392
|
);
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
393
|
+
grow.appendChild(chartNoteEl);
|
|
394
|
+
grow.style.display = "flex";
|
|
395
|
+
grow.style.alignItems = "center";
|
|
396
|
+
grow.style.gap = "16px";
|
|
397
|
+
SHELL.fitBar(); // with no room in the middle, the notes fold behind the (i)
|
|
458
398
|
|
|
459
399
|
countLabel = el("div", "sm-count");
|
|
460
400
|
bar.appendChild(countLabel);
|
|
@@ -470,7 +410,6 @@
|
|
|
470
410
|
approveBtn.onclick = approve;
|
|
471
411
|
bar.appendChild(approveBtn);
|
|
472
412
|
|
|
473
|
-
document.body.appendChild(bar);
|
|
474
413
|
refreshChrome();
|
|
475
414
|
}
|
|
476
415
|
// Approve is refused while the agent is fixing (the render on screen is
|
|
@@ -482,30 +421,63 @@
|
|
|
482
421
|
if (!countLabel) return;
|
|
483
422
|
var open = openComments().length;
|
|
484
423
|
var d = draftComments().length;
|
|
424
|
+
// the in-flight latch: one answer at a time, a double click cannot send twice
|
|
425
|
+
var pending = SHELL.decisionPending();
|
|
426
|
+
var sending = pending && SHELL.state.pending;
|
|
485
427
|
countLabel.textContent =
|
|
486
428
|
ledger.length === 0
|
|
487
429
|
? "no comments yet"
|
|
488
430
|
: open + " open · " + ledger.length + " total";
|
|
489
431
|
sendBtn.style.display = !working && d > 0 ? "" : "none";
|
|
490
|
-
sendBtn.
|
|
491
|
-
|
|
492
|
-
|
|
432
|
+
sendBtn.disabled = pending;
|
|
433
|
+
sendBtn.classList.toggle("is-off", pending);
|
|
434
|
+
// in the workspace the answer also copies the message the user pastes to
|
|
435
|
+
// their agent; the dialog after the click says so, the button stays short
|
|
436
|
+
sendBtn.querySelector("span").textContent = sending && sending.decision === "comments" ? "Sending…" : "Send " + d + (d === 1 ? " comment" : " comments");
|
|
437
|
+
var already = SHELL.alreadyApproved();
|
|
438
|
+
var notAsked = notAskedYet();
|
|
439
|
+
// an approval would cover a preview older than the slides (comments stay open)
|
|
440
|
+
var stale = SHELL.isPreviewStale() && !working;
|
|
441
|
+
var off = working || d > 0 || already || notAsked || pending || stale;
|
|
493
442
|
approveBtn.disabled = off;
|
|
494
443
|
approveBtn.setAttribute("aria-disabled", off ? "true" : "false");
|
|
495
444
|
approveBtn.classList.toggle("is-off", off);
|
|
496
|
-
approveBtn.
|
|
497
|
-
?
|
|
445
|
+
approveBtn.querySelector("span").textContent = already
|
|
446
|
+
? SHELL.reviewNotNeeded() ? "No review needed" : "Approved"
|
|
447
|
+
: notAsked
|
|
448
|
+
? "Approve when your agent asks"
|
|
449
|
+
: sending && sending.decision === "approve"
|
|
450
|
+
? "Approving…"
|
|
451
|
+
: CFG.embedded
|
|
452
|
+
? "Approve slides"
|
|
453
|
+
: "Approve & continue";
|
|
454
|
+
approveBtn.title = already
|
|
455
|
+
? SHELL.reviewNotNeeded()
|
|
456
|
+
? "This deck came from your PowerPoint file, so there is nothing to approve. Pin a comment if something must change."
|
|
457
|
+
: "These slides are approved. Pin a comment if something must still change."
|
|
458
|
+
: notAsked
|
|
459
|
+
? "Your agent is still working on the slides. It asks for your review when they are ready."
|
|
460
|
+
: pending
|
|
461
|
+
? "Sending your answer…"
|
|
462
|
+
: stale
|
|
463
|
+
? "Your agent changed the slides after this preview was made — approve the new version when it shows it to you."
|
|
464
|
+
: working
|
|
465
|
+
? (CFG.embedded ? "Your answer is saved — approve the next version when your agent asks." : "The agent is fixing the slides — approve once the next round arrives.")
|
|
498
466
|
: d > 0
|
|
499
467
|
? "Send or delete your comments before approving."
|
|
500
|
-
:
|
|
501
|
-
|
|
502
|
-
|
|
468
|
+
: CFG.embedded
|
|
469
|
+
? "Approve these slides. You then paste a message into your agent chat."
|
|
470
|
+
: "Approve and continue.";
|
|
471
|
+
// Embedded, the working state is the full-width strip under the bar
|
|
472
|
+
// (renderRevising); in the terminal gate the bar chip carries it.
|
|
473
|
+
var chip = working && !CFG.embedded;
|
|
474
|
+
statusEl.classList.toggle("on", chip);
|
|
475
|
+
if (chip) {
|
|
503
476
|
statusEl.innerHTML = '<div class="sm-spin"></div>';
|
|
504
477
|
var txt = el("div");
|
|
505
478
|
txt.appendChild(el("div", null, "<b>The agent is working through your comments</b>"));
|
|
506
479
|
txt.appendChild(el("div", null, "")).textContent =
|
|
507
|
-
(workingNote ? workingNote + " " : "") +
|
|
508
|
-
"This tab refreshes with the next round — comment again then.";
|
|
480
|
+
(workingNote ? workingNote + " " : "") + "This tab refreshes with the next round.";
|
|
509
481
|
statusEl.appendChild(txt);
|
|
510
482
|
}
|
|
511
483
|
}
|
|
@@ -559,13 +531,19 @@
|
|
|
559
531
|
var st = stage();
|
|
560
532
|
if (!st) return;
|
|
561
533
|
var rail = document.getElementById("slide-rail");
|
|
562
|
-
|
|
563
|
-
|
|
534
|
+
// The rail sits left (its width) or, on small screens, along the bottom
|
|
535
|
+
// (its height) — see preview.html's media query.
|
|
536
|
+
var railShown = rail && getComputedStyle(rail).display !== "none";
|
|
537
|
+
var railBottom = railShown && rail.offsetWidth >= window.innerWidth - 1;
|
|
538
|
+
var railW = railShown && !railBottom ? rail.offsetWidth : 0;
|
|
539
|
+
var railH = railBottom ? rail.offsetHeight : 0;
|
|
540
|
+
var narrow = window.innerWidth <= 760;
|
|
541
|
+
var reserve = sidebarOpen && !narrow ? SIDEBAR_W : 0;
|
|
564
542
|
var availW = window.innerWidth - railW - reserve;
|
|
565
|
-
var availH = window.innerHeight -
|
|
543
|
+
var availH = window.innerHeight - chromeH - railH - 8;
|
|
566
544
|
var s = Math.min(availW / 1280, availH / 720) * 0.94;
|
|
567
545
|
var offX = -reserve / 2;
|
|
568
|
-
var offY =
|
|
546
|
+
var offY = (chromeH - railH) / 2;
|
|
569
547
|
st.style.transform = "translate(" + offX + "px," + offY + "px) scale(" + s + ")";
|
|
570
548
|
renderPins();
|
|
571
549
|
}
|
|
@@ -579,10 +557,10 @@
|
|
|
579
557
|
};
|
|
580
558
|
}
|
|
581
559
|
// Only a DRAFT is still the reviewer's to change; once it is with the agent
|
|
582
|
-
// it is read-only history.
|
|
560
|
+
// — or on its way there (SHELL.inFlight) — it is read-only history.
|
|
583
561
|
function openCommentAt(c) {
|
|
584
562
|
var p = commentScreenPoint(c);
|
|
585
|
-
if (c.status === "draft") openEditor(c, p.x, p.y);
|
|
563
|
+
if (c.status === "draft" && !SHELL.inFlight(c.id)) openEditor(c, p.x, p.y);
|
|
586
564
|
else openGhost(c, p.x, p.y);
|
|
587
565
|
}
|
|
588
566
|
function statusLabel(c) {
|
|
@@ -712,6 +690,49 @@
|
|
|
712
690
|
var py = sr.top + fy(c) * sr.height;
|
|
713
691
|
pin.style.left = px + "px";
|
|
714
692
|
pin.style.top = py + "px";
|
|
693
|
+
if (c.status === "draft" && !SHELL.inFlight(c.id)) {
|
|
694
|
+
// A draft pin can be dragged onto the right spot: the comment keeps its
|
|
695
|
+
// text, its slide fraction (and the element under it) are re-read on drop.
|
|
696
|
+
pin.style.cursor = "grab";
|
|
697
|
+
pin.addEventListener("pointerdown", function (e) {
|
|
698
|
+
if (e.button !== 0) return;
|
|
699
|
+
var startX = e.clientX, startY = e.clientY, moved = false;
|
|
700
|
+
pin.setPointerCapture(e.pointerId);
|
|
701
|
+
function onMove(ev) {
|
|
702
|
+
if (!moved && Math.hypot(ev.clientX - startX, ev.clientY - startY) < 4) return;
|
|
703
|
+
moved = true;
|
|
704
|
+
pin.style.cursor = "grabbing";
|
|
705
|
+
pin.style.left = ev.clientX + "px";
|
|
706
|
+
pin.style.top = ev.clientY + "px";
|
|
707
|
+
}
|
|
708
|
+
function onUp(ev) {
|
|
709
|
+
pin.removeEventListener("pointermove", onMove);
|
|
710
|
+
pin.removeEventListener("pointerup", onUp);
|
|
711
|
+
pin.removeEventListener("pointercancel", onUp);
|
|
712
|
+
pin.style.cursor = "grab";
|
|
713
|
+
if (!moved) return; // a click: the onclick below opens the editor
|
|
714
|
+
var r = stage() && stage().getBoundingClientRect();
|
|
715
|
+
if (!r) return;
|
|
716
|
+
c.x = clamp01((ev.clientX - r.left) / r.width);
|
|
717
|
+
c.y = clamp01((ev.clientY - r.top) / r.height);
|
|
718
|
+
pin.style.pointerEvents = "none";
|
|
719
|
+
var under = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
720
|
+
pin.style.pointerEvents = "";
|
|
721
|
+
c.resolvedTarget = under && stage().contains(under) && under !== stage() ? describeTarget(under) : null;
|
|
722
|
+
saveLedger();
|
|
723
|
+
renderAll();
|
|
724
|
+
}
|
|
725
|
+
pin.addEventListener("pointermove", onMove);
|
|
726
|
+
pin.addEventListener("pointerup", onUp);
|
|
727
|
+
pin.addEventListener("pointercancel", onUp);
|
|
728
|
+
});
|
|
729
|
+
pin.addEventListener("click", function (e) {
|
|
730
|
+
e.stopPropagation();
|
|
731
|
+
if (pin.style.cursor === "grabbing") return;
|
|
732
|
+
openCommentAt(c);
|
|
733
|
+
});
|
|
734
|
+
return pin;
|
|
735
|
+
}
|
|
715
736
|
pin.onclick = function (e) {
|
|
716
737
|
e.stopPropagation();
|
|
717
738
|
openCommentAt(c);
|
|
@@ -862,61 +883,47 @@
|
|
|
862
883
|
renderAll();
|
|
863
884
|
}
|
|
864
885
|
// "Send N comments": the whole draft batch resolves the gate as one comments
|
|
865
|
-
// round
|
|
886
|
+
// round. The shell carries it to the server (retries a busy deck, waits out a
|
|
887
|
+
// stopped workspace) and calls decided() once it is saved — only then do the
|
|
888
|
+
// drafts flip to "sent" and the tab enter working mode.
|
|
866
889
|
function sendComments() {
|
|
867
|
-
if (approved || working) return;
|
|
890
|
+
if (approved || working || SHELL.decisionPending()) return;
|
|
868
891
|
var batch = draftComments();
|
|
869
892
|
if (!batch.length) return;
|
|
870
893
|
// Ask for notification permission HERE — the click is a user gesture, the
|
|
871
|
-
// only context where the browser will show the prompt.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
})
|
|
878
|
-
.then(function (res) {
|
|
879
|
-
if (res.status === 409) {
|
|
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.
|
|
882
|
-
enterWorking("Round superseded.");
|
|
883
|
-
return;
|
|
884
|
-
}
|
|
885
|
-
if (!res.ok) throw new Error("gate refused the decision");
|
|
886
|
-
batch.forEach(function (x) {
|
|
887
|
-
x.status = "sent";
|
|
888
|
-
});
|
|
889
|
-
saveLedger();
|
|
890
|
-
enterWorking();
|
|
891
|
-
})
|
|
892
|
-
.catch(function () {
|
|
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?");
|
|
895
|
-
});
|
|
894
|
+
// only context where the browser will show the prompt. Standalone only: in
|
|
895
|
+
// the workspace nothing waits in the background, and a permission prompt
|
|
896
|
+
// there is friction on the click that copies the message.
|
|
897
|
+
if (!CFG.embedded) SES.requestNotifyPermission();
|
|
898
|
+
SHELL.decide({ decision: "comments", comments: batch.map(SES.wire) });
|
|
899
|
+
renderAll();
|
|
896
900
|
}
|
|
897
901
|
function approve() {
|
|
898
902
|
if (approved || working || draftComments().length) return;
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
headers: { "Content-Type": "application/json" },
|
|
903
|
-
body: JSON.stringify({ decision: "approve", comments: [] }),
|
|
904
|
-
})
|
|
905
|
-
.then(function (res) {
|
|
906
|
-
if (res.status === 409) {
|
|
907
|
-
// Another tab already decided this run: the render is superseded.
|
|
908
|
-
approved = false;
|
|
909
|
-
enterWorking("Round superseded.");
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
showDone();
|
|
913
|
-
requestTabClose();
|
|
914
|
-
})
|
|
915
|
-
.catch(function () {
|
|
916
|
-
approved = false;
|
|
917
|
-
alert("Could not reach the slidemind review server — is the terminal command still running?");
|
|
918
|
-
});
|
|
903
|
+
if (SHELL.decisionPending() || SHELL.alreadyApproved() || notAskedYet() || SHELL.isPreviewStale()) return;
|
|
904
|
+
SHELL.decide({ decision: "approve", comments: [] });
|
|
905
|
+
renderAll();
|
|
919
906
|
}
|
|
907
|
+
// The server saved the answer (never before: a refused POST leaves every
|
|
908
|
+
// draft a draft). The rows the request carried are the ones that are sent.
|
|
909
|
+
function decided(decision, body) {
|
|
910
|
+
if (decision === "approve") {
|
|
911
|
+
approved = true;
|
|
912
|
+
showDone();
|
|
913
|
+
requestTabClose();
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
var sentIds = {};
|
|
917
|
+
((body && body.comments) || []).forEach(function (c) {
|
|
918
|
+
sentIds[c.id] = true;
|
|
919
|
+
});
|
|
920
|
+
ledger.forEach(function (x) {
|
|
921
|
+
if (x.status === "draft" && sentIds[x.id]) x.status = "sent";
|
|
922
|
+
});
|
|
923
|
+
saveLedger();
|
|
924
|
+
enterWorking(CFG.embedded ? "Comments saved." : null);
|
|
925
|
+
}
|
|
926
|
+
|
|
920
927
|
// ---------- working mode (the agent is fixing) ----------
|
|
921
928
|
// The tab neither veils over nor freezes: the stage stays browsable and the
|
|
922
929
|
// ledger stays visible; pinning and Approve go away until the next round.
|
|
@@ -930,20 +937,18 @@
|
|
|
930
937
|
hideHoverBox();
|
|
931
938
|
saveCtx(); // a plain refresh of this round stays in working mode
|
|
932
939
|
renderAll();
|
|
940
|
+
SHELL.renderRevising();
|
|
933
941
|
startNextRoundPolling();
|
|
934
942
|
}
|
|
935
943
|
function requestTabClose() {
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
window.open("", "_self");
|
|
939
|
-
window.close();
|
|
940
|
-
} catch {
|
|
941
|
-
// Some browsers only allow script-opened tabs to close themselves.
|
|
942
|
-
}
|
|
943
|
-
}, 120);
|
|
944
|
+
if (CFG.embedded) return; // the workspace page stays; nothing to close
|
|
945
|
+
SHELL.requestTabClose();
|
|
944
946
|
}
|
|
945
|
-
// The shared heartbeat poll (gate-session.js) with this gate's two endings
|
|
947
|
+
// The shared heartbeat poll (gate-session.js) with this gate's two endings —
|
|
948
|
+
// standalone only: in the workspace page the shell's monitor watches the round
|
|
949
|
+
// for the page's whole life, and the page never "ends".
|
|
946
950
|
function startNextRoundPolling() {
|
|
951
|
+
if (CFG.embedded) return;
|
|
947
952
|
SES.startNextRoundPolling({
|
|
948
953
|
onEnded: showSessionEnded,
|
|
949
954
|
// A retired tab still holding unsent drafts must not discard them: the
|
|
@@ -955,27 +960,33 @@
|
|
|
955
960
|
},
|
|
956
961
|
});
|
|
957
962
|
}
|
|
958
|
-
//
|
|
959
|
-
// already shown rather than stacking a second one, so a comments round's
|
|
960
|
-
// "waiting" veil can be swapped for "session ended" in place.
|
|
963
|
+
// The shared veil, with this gate's own nodes taken off screen behind it.
|
|
961
964
|
function showVeil(statusHtml, subText, subPulse) {
|
|
962
965
|
closePopover();
|
|
963
|
-
|
|
964
|
-
if (bar) bar.style.display = "none";
|
|
965
|
-
var existing = document.querySelector(".sm-veil[data-sm-overlay]");
|
|
966
|
-
if (existing && existing.parentNode) existing.parentNode.removeChild(existing);
|
|
967
|
-
var veil = el("div", "sm-o sm-veil");
|
|
968
|
-
veil.setAttribute("data-sm-overlay", "1");
|
|
969
|
-
veil.appendChild(logoNode("sm-logo", "-v"));
|
|
970
|
-
veil.appendChild(el("div", "word", wordmarkHtml()));
|
|
971
|
-
veil.appendChild(el("div", "status", statusHtml));
|
|
972
|
-
veil.appendChild(el("div", "sub" + (subPulse ? " sm-pulse" : ""), subText));
|
|
973
|
-
document.body.appendChild(veil);
|
|
966
|
+
return SHELL.showVeil(statusHtml, subText, subPulse, [pinLayer, sidebarEl, hoverBox]);
|
|
974
967
|
}
|
|
975
968
|
// The veil is now ONLY for states where the review is over (approved, session
|
|
976
969
|
// expired, superseded). A comments round no longer veils: see enterWorking.
|
|
970
|
+
// Embedded, the veil says the agent HAS the approval — only once the wire
|
|
971
|
+
// proved it (delivered, or picked up later). Until then the approval is saved
|
|
972
|
+
// and the strip + hand-off say what is still missing.
|
|
977
973
|
function showDone() {
|
|
978
|
-
|
|
974
|
+
if (CFG.embedded && SHELL.state.phase !== "approved") {
|
|
975
|
+
enterWorking("Approved.");
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
// working, so a reload (the approval itself may move the round) restores
|
|
979
|
+
// this state instead of a fresh "approved — nothing to do" page
|
|
980
|
+
if (CFG.embedded && !working) enterWorking("Approved.");
|
|
981
|
+
var picked = SHELL.state.pickedUpAt;
|
|
982
|
+
showVeil(
|
|
983
|
+
ICONS.check + "<span>Approved</span>",
|
|
984
|
+
!CFG.embedded
|
|
985
|
+
? "Returned to your terminal."
|
|
986
|
+
: picked
|
|
987
|
+
? "Your agent picked it up at " + SHELL.hhmm(picked) + " and is building the PowerPoint now."
|
|
988
|
+
: "Your agent has it and is building the PowerPoint now.",
|
|
989
|
+
);
|
|
979
990
|
}
|
|
980
991
|
function showSessionEnded() {
|
|
981
992
|
showVeil("<span>Session ended</span>", "You can close this tab.");
|
|
@@ -1008,29 +1019,12 @@
|
|
|
1008
1019
|
var st = stage();
|
|
1009
1020
|
var hasChart = !!(st && st.querySelector("[data-sm-chart]"));
|
|
1010
1021
|
chartNoteEl.classList.toggle("on", hasChart);
|
|
1022
|
+
SHELL.fitBar();
|
|
1011
1023
|
}
|
|
1012
1024
|
|
|
1013
1025
|
// ---------- wire up ----------
|
|
1014
1026
|
function injectAssets() {
|
|
1015
|
-
|
|
1016
|
-
style.id = "sm-review-style";
|
|
1017
|
-
style.textContent = CSS;
|
|
1018
|
-
document.head.appendChild(style);
|
|
1019
|
-
|
|
1020
|
-
var pre1 = el("link");
|
|
1021
|
-
pre1.rel = "preconnect";
|
|
1022
|
-
pre1.href = "https://fonts.googleapis.com";
|
|
1023
|
-
var pre2 = el("link");
|
|
1024
|
-
pre2.rel = "preconnect";
|
|
1025
|
-
pre2.href = "https://fonts.gstatic.com";
|
|
1026
|
-
pre2.crossOrigin = "anonymous";
|
|
1027
|
-
var fonts = el("link");
|
|
1028
|
-
fonts.rel = "stylesheet";
|
|
1029
|
-
fonts.href =
|
|
1030
|
-
"https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&family=Urbanist:wght@300;400&display=swap";
|
|
1031
|
-
document.head.appendChild(pre1);
|
|
1032
|
-
document.head.appendChild(pre2);
|
|
1033
|
-
document.head.appendChild(fonts);
|
|
1027
|
+
SHELL.injectAssets(CSS);
|
|
1034
1028
|
}
|
|
1035
1029
|
|
|
1036
1030
|
function init() {
|
|
@@ -1045,12 +1039,19 @@
|
|
|
1045
1039
|
restoreSession();
|
|
1046
1040
|
|
|
1047
1041
|
injectAssets();
|
|
1048
|
-
st.style.cursor =
|
|
1042
|
+
st.style.cursor = COMMENT_CURSOR;
|
|
1049
1043
|
// Slide play order, for the sidebar's jump-to-slide.
|
|
1050
1044
|
document.querySelectorAll('script[data-kind="slide"]').forEach(function (s) {
|
|
1051
1045
|
slideOrder[parseInt(s.dataset.index, 10)] = s.dataset.name;
|
|
1052
1046
|
});
|
|
1053
1047
|
buildChrome();
|
|
1048
|
+
if (CFG.embedded && working) setTimeout(SHELL.renderRevising, 0); // a refresh mid-round keeps the strip
|
|
1049
|
+
if (CFG.embedded && working && SHELL.state.phase === "approved") setTimeout(showDone, 0); // the agent has the approval
|
|
1050
|
+
if (CFG.embedded && !working && askedAgainUnchanged && CFG.asked) setTimeout(SHELL.showAskedAgainUnchanged, 0);
|
|
1051
|
+
else if (CFG.embedded && !working && arrivedAfterRevising && CFG.asked) setTimeout(function () { SHELL.showArrived("It worked through your comments. Have another look."); }, 0);
|
|
1052
|
+
if (SHELL.alreadyApproved()) setTimeout(function () { SHELL.showApprovedBanner(SHELL.reviewNotNeeded() ? "This deck came from your PowerPoint file, so your agent makes changes directly in it." : "Later changes go straight into the PowerPoint file — no new review needed."); }, 0);
|
|
1053
|
+
if (SHELL.askedAgain()) setTimeout(function () { SHELL.showApprovedBanner("Approve again to let it continue, or pin a comment if something must still change.", "Your agent asks you to confirm the slides again"); }, 0);
|
|
1054
|
+
if (notAskedYet()) setTimeout(function () { SHELL.showNotAskedBanner("Your agent is still working on the slides. Look around if you like — this page updates by itself when it asks you."); }, 0);
|
|
1054
1055
|
buildPinLayer();
|
|
1055
1056
|
buildHoverBox();
|
|
1056
1057
|
|
|
@@ -1084,7 +1085,7 @@
|
|
|
1084
1085
|
|
|
1085
1086
|
window.addEventListener("resize", function () {
|
|
1086
1087
|
closePopover();
|
|
1087
|
-
|
|
1088
|
+
SHELL.offsetSurface(); // a banner rewraps at a new width; it refits the stage
|
|
1088
1089
|
});
|
|
1089
1090
|
|
|
1090
1091
|
var nav = document.getElementById("nav");
|
|
@@ -1109,6 +1110,8 @@
|
|
|
1109
1110
|
// Parked round: the agent is waiting on the reviewer — announce it.
|
|
1110
1111
|
SES.announceNewRound(flagged);
|
|
1111
1112
|
}
|
|
1113
|
+
// The hand-over in progress, the next-load note, an answer on its way.
|
|
1114
|
+
setTimeout(SHELL.resume, 0);
|
|
1112
1115
|
}
|
|
1113
1116
|
|
|
1114
1117
|
if (document.readyState === "loading") {
|