@erdemtuna/doc-review 0.11.1 → 0.12.0

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.
@@ -260,6 +260,8 @@ const transport = createReviewApi({ token: state.token });
260
260
  const api = transport.request;
261
261
  const frameController = createFrameController({
262
262
  sessionId: state.sessionId, host: frameHost, request: api,
263
+ activated: completeFrameReady,
264
+ diagnostic: (code) => diagnostic(code, {}),
263
265
  suspended() {
264
266
  captures.cancel();
265
267
  captureCoordinator.reset();
@@ -353,6 +355,7 @@ export const recoveryRuntime = createRecoveryController({
353
355
  comparing: state.comparing, ended: state.ended, loading: frameController.loading,
354
356
  pendingReload: frameController.state.pendingReload || !!frameHost.previous,
355
357
  frameError: frameController.state.phase.kind === "failed" ? frameController.state.phase.message : null,
358
+ themeSync: frameController.themeSync,
356
359
  reload: {
357
360
  visible: state.reloadVisible, message: state.reloadMessage,
358
361
  error: state.reloadFailed || saveController.state.conflict,
@@ -378,6 +381,7 @@ export const recoveryRuntime = createRecoveryController({
378
381
  toFrame({ type: "eh:modeMenuState", open });
379
382
  },
380
383
  reload: () => reloadLatest({ explicit: true }),
384
+ retryTheme: () => frameController.retryTheme(),
381
385
  keepCurrent() {
382
386
  state.reloadVisible = false;
383
387
  announce("Keeping the current page. Return to Review to reload when ready.");
@@ -1280,47 +1284,48 @@ function targetPayload(msg) {
1280
1284
  horizontal: Number.isFinite(msg.horizontal) ? Number(msg.horizontal) : null,
1281
1285
  };
1282
1286
  }
1287
+ function completeFrameReady(readyState) {
1288
+ state.page = { ...state.page, ...readyState };
1289
+ toFrame({ type: "eh:anchors", comments: state.page ? state.page.comments : [] });
1290
+ if (frameController.state.reloading) {
1291
+ toFrame({ type: "eh:restoreScroll", x: state.scroll.x, y: state.scroll.y });
1292
+ frameController.restoredScroll();
1293
+ }
1294
+ const configuration = reviewConfiguration(state.page, state.reviewMode);
1295
+ state.savePolicy = configuration.savePolicy;
1296
+ void frameController.configure(configuration.mode, configuration.savePolicy, false);
1297
+ render();
1298
+ if (state.page?.kind !== "url") {
1299
+ const rawIdentity = currentRender();
1300
+ void api(`/api/page/${frameController.state.key}/raw`).then((raw) => {
1301
+ if (!sameRender(rawIdentity, currentRender()) || frameController.state.pendingReload)
1302
+ return;
1303
+ if (frameController.state.sourceHash && frameController.state.sourceHash !== raw.hash) {
1304
+ saveController.markConflict();
1305
+ holdReload();
1306
+ return;
1307
+ }
1308
+ saveController.baseline(raw.hash || null);
1309
+ if (configuration.savePolicy === "writable")
1310
+ toFrame({ type: "eh:raw", html: raw.html });
1311
+ scheduleResultCapture();
1312
+ }).catch((error) => {
1313
+ if (sameRender(rawIdentity, currentRender()) && !state.ended) {
1314
+ toast(`Source could not be loaded: ${error.message}. Reload before saving.`);
1315
+ }
1316
+ });
1317
+ }
1318
+ void refreshHistory();
1319
+ }
1283
1320
  listen(window, "message", async (event) => {
1321
+ if (frameController.handleThemeMessage(event))
1322
+ return;
1284
1323
  if (!frameController.accepts(event))
1285
1324
  return;
1286
1325
  const msg = event.data;
1287
1326
  switch (msg.type) {
1288
1327
  case "eh:ready": {
1289
- const readyIdentity = currentRender();
1290
- const readyState = await frameController.ready();
1291
- if (!readyState || !sameRender(readyIdentity, currentRender()))
1292
- return;
1293
- state.page = { ...state.page, ...readyState };
1294
- toFrame({ type: "eh:anchors", comments: state.page ? state.page.comments : [] });
1295
- if (frameController.state.reloading) {
1296
- toFrame({ type: "eh:restoreScroll", x: state.scroll.x, y: state.scroll.y });
1297
- frameController.restoredScroll();
1298
- }
1299
- const configuration = reviewConfiguration(state.page, state.reviewMode);
1300
- state.savePolicy = configuration.savePolicy;
1301
- void frameController.configure(configuration.mode, configuration.savePolicy, false);
1302
- render();
1303
- if (state.page?.kind !== "url") {
1304
- const rawIdentity = currentRender();
1305
- void api(`/api/page/${frameController.state.key}/raw`).then((raw) => {
1306
- if (!sameRender(rawIdentity, currentRender()) || frameController.state.pendingReload)
1307
- return;
1308
- if (frameController.state.sourceHash && frameController.state.sourceHash !== raw.hash) {
1309
- saveController.markConflict();
1310
- holdReload();
1311
- return;
1312
- }
1313
- saveController.baseline(raw.hash || null);
1314
- if (configuration.savePolicy === "writable")
1315
- toFrame({ type: "eh:raw", html: raw.html });
1316
- scheduleResultCapture();
1317
- }).catch((error) => {
1318
- if (sameRender(rawIdentity, currentRender()) && !state.ended) {
1319
- toast(`Source could not be loaded: ${error.message}. Reload before saving.`);
1320
- }
1321
- });
1322
- }
1323
- void refreshHistory();
1328
+ await frameController.ready();
1324
1329
  break;
1325
1330
  }
1326
1331
  case "eh:configurationApplied":
@@ -1555,6 +1560,7 @@ function toggleTheme() {
1555
1560
  function applyTheme(dark) {
1556
1561
  document.documentElement.dataset.theme = dark ? "dark" : "light";
1557
1562
  state.theme = dark ? "dark" : "light";
1563
+ frameController.setTheme(state.theme);
1558
1564
  toolbarRuntime.publish();
1559
1565
  }
1560
1566
  listen(document, "keydown", (event) => {
package/lib/chrome.html CHANGED
@@ -4,6 +4,9 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>doc-review</title>
7
+ <!-- brand:start -->
8
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2264%22%20height%3D%2264%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%2317685F%22%2F%3E%3Cpath%20d%3D%22M16%2012H48Q54%2012%2054%2018V40Q54%2046%2048%2046H30L16%2054V46Q10%2046%2010%2040V18Q10%2012%2016%2012Z%20M20%2022H36V18L43%2024L36%2030V26H20Z%20M44%2034H28V30L21%2036L28%2042V38H44Z%22%20fill%3D%22%23FFFDF7%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E">
9
+ <!-- brand:end -->
7
10
  <link rel="stylesheet" href="/chrome.css">
8
11
  </head>
9
12
  <body data-session="__SESSION_ID__" data-token="__TOKEN__">
@@ -1,3 +1,9 @@
1
+ export function isThemePayload(value) {
2
+ return typeof value === "object" && value !== null &&
3
+ "theme" in value && (value.theme === "light" || value.theme === "dark") &&
4
+ "themeRevision" in value && typeof value.themeRevision === "number" &&
5
+ Number.isSafeInteger(value.themeRevision) && value.themeRevision > 0;
6
+ }
1
7
  /** Correlation alone does not validate a message type or its payload. */
2
8
  export function isFrameIdentity(value) {
3
9
  return typeof value === "object" && value !== null &&
@@ -1,2 +1,2 @@
1
- export { isFrameIdentity } from "./frame.js";
1
+ export { isFrameIdentity, isThemePayload } from "./frame.js";
2
2
  export { isExecutionPolicy, isPageResponse, isRenderExecution } from "./page.js";
@@ -1,5 +1,6 @@
1
1
  import { record } from "./chrome-api.js";
2
- export function createFrameController({ sessionId, host, request, suspended, transitioning = () => { }, failed, now = Date.now, setTimer = globalThis.setTimeout, clearTimer = globalThis.clearTimeout, }) {
2
+ import { isThemePayload } from "./contracts/frame.js";
3
+ export function createFrameController({ sessionId, host, request, suspended, transitioning = () => { }, failed, now = Date.now, activated = () => { }, diagnostic = () => { }, setTimer = globalThis.setTimeout, clearTimer = globalThis.clearTimeout, }) {
3
4
  const state = {
4
5
  key: null, generation: 0, renderId: null, capability: null, sourceHash: null,
5
6
  execution: null, policy: null, phase: { kind: "loading" }, readyAt: 0,
@@ -17,6 +18,10 @@ export function createFrameController({ sessionId, host, request, suspended, tra
17
18
  let flushSequence = 0;
18
19
  let configuration = null;
19
20
  let expectedConfiguration = null;
21
+ let desired = { theme: "light", themeRevision: 1 };
22
+ let currentTheme = null;
23
+ let previousTheme = null;
24
+ let retainedCandidate = null;
20
25
  const alive = () => state.phase.kind !== "disposed";
21
26
  const loading = () => state.phase.kind !== "ready";
22
27
  const publish = () => { if (alive())
@@ -49,7 +54,114 @@ export function createFrameController({ sessionId, host, request, suspended, tra
49
54
  ...message, capability: state.capability, generation: state.generation, pageKey: state.key,
50
55
  }, state.policy?.targetOrigin || "*");
51
56
  }
52
- function suspend() {
57
+ function cancelTheme(channel) {
58
+ if (!channel)
59
+ return;
60
+ cancelTimer(channel.timer);
61
+ channel.timer = null;
62
+ channel.pending = null;
63
+ channel.failed = false;
64
+ channel.settle?.(false);
65
+ channel.settle = null;
66
+ }
67
+ function clearPreviousTheme() {
68
+ cancelTheme(previousTheme);
69
+ previousTheme = null;
70
+ publish();
71
+ }
72
+ const stopRemoval = host.onPreviousRemoved(clearPreviousTheme);
73
+ const themeApplied = (channel) => channel?.applied?.themeRevision === desired.themeRevision && channel.applied.theme === desired.theme &&
74
+ !channel.pending && !channel.failed;
75
+ function handoff() {
76
+ if (!host.previous || loading() || !expectedConfiguration ||
77
+ state.configurationGeneration !== state.generation || !themeApplied(currentTheme))
78
+ return;
79
+ const started = identity();
80
+ const appliedConfiguration = expectedConfiguration;
81
+ const revision = desired.themeRevision;
82
+ host.afterPaint(() => {
83
+ if (matches(started) && !loading() && expectedConfiguration === appliedConfiguration &&
84
+ state.configurationGeneration === state.generation && desired.themeRevision === revision &&
85
+ themeApplied(currentTheme))
86
+ finishReplacement();
87
+ });
88
+ }
89
+ function exposeReady() {
90
+ if (state.phase.kind !== "confirming" || !state.execution || !themeApplied(currentTheme))
91
+ return;
92
+ state.phase = { kind: "ready" };
93
+ host.ready(state.execution);
94
+ publish();
95
+ activated(state.execution);
96
+ }
97
+ function requestTheme(channel) {
98
+ cancelTheme(channel);
99
+ channel.pending = { ...desired };
100
+ const pending = channel.pending;
101
+ const promise = new Promise((resolve) => { channel.settle = resolve; });
102
+ channel.timer = later(() => {
103
+ if (channel.pending !== pending)
104
+ return;
105
+ channel.timer = null;
106
+ channel.pending = null;
107
+ channel.failed = true;
108
+ channel.settle?.(false);
109
+ channel.settle = null;
110
+ publish();
111
+ }, 3000);
112
+ const message = {
113
+ type: "eh:setTheme", ...pending, capability: channel.capability,
114
+ generation: channel.generation, pageKey: channel.pageKey,
115
+ };
116
+ if (channel === currentTheme)
117
+ host.send(message, channel.policy.targetOrigin);
118
+ else if (channel.source && channel.source === host.previousWindow) {
119
+ channel.source.postMessage(message, channel.policy.targetOrigin);
120
+ }
121
+ publish();
122
+ return promise;
123
+ }
124
+ function handleThemeMessage(event) {
125
+ const value = event.data;
126
+ if (!alive() || !value || typeof value !== "object" || !("type" in value) ||
127
+ (value.type !== "eh:themeApplied" && value.type !== "eh:setTheme"))
128
+ return false;
129
+ const channel = [currentTheme, previousTheme].find((candidate) => candidate && candidate.source &&
130
+ candidate.source === event.source && candidate.policy.incomingOrigin === event.origin &&
131
+ "capability" in value && value.capability === candidate.capability &&
132
+ "generation" in value && value.generation === candidate.generation &&
133
+ "pageKey" in value && value.pageKey === candidate.pageKey);
134
+ if (!channel)
135
+ return true;
136
+ if (channel === previousTheme && channel.source !== host.previousWindow) {
137
+ clearPreviousTheme();
138
+ return true;
139
+ }
140
+ if (value.type !== "eh:themeApplied" || !isThemePayload(value)) {
141
+ if (!channel.diagnosed) {
142
+ channel.diagnosed = true;
143
+ diagnostic("invalid-theme-acknowledgment");
144
+ }
145
+ return true;
146
+ }
147
+ if (!channel.pending || channel.pending.themeRevision !== value.themeRevision ||
148
+ channel.pending.theme !== value.theme)
149
+ return true;
150
+ channel.applied = { theme: value.theme, themeRevision: value.themeRevision };
151
+ cancelTimer(channel.timer);
152
+ channel.timer = null;
153
+ channel.pending = null;
154
+ channel.failed = false;
155
+ channel.settle?.(true);
156
+ channel.settle = null;
157
+ if (channel === currentTheme) {
158
+ exposeReady();
159
+ handoff();
160
+ }
161
+ publish();
162
+ return true;
163
+ }
164
+ function suspend(preserveTheme = false) {
53
165
  if (!alive())
54
166
  return;
55
167
  suspended();
@@ -57,6 +169,13 @@ export function createFrameController({ sessionId, host, request, suspended, tra
57
169
  flush.settle(false);
58
170
  configuration?.settle(false);
59
171
  expectedConfiguration = null;
172
+ cancelTheme(currentTheme);
173
+ cancelTheme(previousTheme);
174
+ if (!preserveTheme) {
175
+ previousTheme = null;
176
+ retainedCandidate = null;
177
+ }
178
+ currentTheme = null;
60
179
  for (const timer of timers)
61
180
  clearTimer(timer);
62
181
  timers.clear();
@@ -73,7 +192,8 @@ export function createFrameController({ sessionId, host, request, suspended, tra
73
192
  if (!alive())
74
193
  throw new Error("Review ended");
75
194
  transitioning();
76
- suspend();
195
+ retainedCandidate = currentTheme || retainedCandidate;
196
+ suspend(true);
77
197
  state.key = key;
78
198
  state.generation++;
79
199
  state.sourceHash = null;
@@ -117,6 +237,13 @@ export function createFrameController({ sessionId, host, request, suspended, tra
117
237
  state.phase = { kind: "loading" };
118
238
  initialLoad = true;
119
239
  host.navigate(value.path, state.reloading);
240
+ if (previousTheme?.source !== host.previousWindow)
241
+ clearPreviousTheme();
242
+ if (!previousTheme && retainedCandidate?.source === host.previousWindow)
243
+ previousTheme = retainedCandidate;
244
+ retainedCandidate = null;
245
+ if (previousTheme && !themeApplied(previousTheme))
246
+ void requestTheme(previousTheme);
120
247
  publish();
121
248
  readinessTimer = later(() => {
122
249
  if (state.key !== key || state.generation !== generation || !loading())
@@ -181,10 +308,15 @@ export function createFrameController({ sessionId, host, request, suspended, tra
181
308
  feedbackOnly: value.savePolicy === "feedback-only",
182
309
  executionNotice: typeof value.executionNotice === "string" ? value.executionNotice : null,
183
310
  };
184
- state.phase = { kind: "ready" };
185
- host.ready(state.execution);
186
- publish();
187
- return state.execution;
311
+ if (!state.policy || !state.key)
312
+ throw new Error("Missing frame policy.");
313
+ currentTheme = {
314
+ source: host.currentWindow, capability: state.capability, generation: state.generation,
315
+ pageKey: state.key, policy: { ...state.policy }, applied: null, pending: null,
316
+ failed: false, timer: null, settle: null, diagnosed: false,
317
+ };
318
+ await requestTheme(currentTheme);
319
+ return matches(started) && !loading() ? state.execution : null;
188
320
  }
189
321
  catch (error) {
190
322
  if (matches(started)) {
@@ -211,6 +343,7 @@ export function createFrameController({ sessionId, host, request, suspended, tra
211
343
  return Promise.resolve(false);
212
344
  configuration?.settle(false);
213
345
  expectedConfiguration = { mode, savePolicy };
346
+ state.configurationGeneration = null;
214
347
  configurationTimeout();
215
348
  if (!wait) {
216
349
  send({ type: "eh:configureReview", mode, savePolicy });
@@ -252,6 +385,40 @@ export function createFrameController({ sessionId, host, request, suspended, tra
252
385
  return {
253
386
  get state() { return state; },
254
387
  get loading() { return loading(); },
388
+ get themeSync() {
389
+ const channels = [currentTheme, previousTheme].filter((channel) => channel !== null);
390
+ const failure = channels.some((channel) => channel.failed);
391
+ return {
392
+ desired: { ...desired },
393
+ status: failure ? "failed" : channels.some((channel) => channel.pending) ? "pending" :
394
+ themeApplied(currentTheme) ? "applied" : "idle",
395
+ appliedRevision: currentTheme?.applied?.themeRevision ?? null,
396
+ message: failure ? "Annotation theme synchronization was not confirmed. Your page and drafts are unchanged. Retry theme without reloading." : null,
397
+ };
398
+ },
399
+ setTheme(theme) {
400
+ if (!alive() || (theme !== "light" && theme !== "dark") || desired.theme === theme)
401
+ return;
402
+ if (desired.themeRevision === Number.MAX_SAFE_INTEGER)
403
+ throw new Error("Theme revision limit reached.");
404
+ desired = { theme, themeRevision: desired.themeRevision + 1 };
405
+ if (currentTheme)
406
+ void requestTheme(currentTheme);
407
+ if (previousTheme && previousTheme.source === host.previousWindow)
408
+ void requestTheme(previousTheme);
409
+ publish();
410
+ },
411
+ async retryTheme() {
412
+ if (!alive())
413
+ return false;
414
+ const pending = [];
415
+ if (currentTheme)
416
+ pending.push(requestTheme(currentTheme));
417
+ if (previousTheme && previousTheme.source === host.previousWindow)
418
+ pending.push(requestTheme(previousTheme));
419
+ return pending.length > 0 && (await Promise.all(pending)).every(Boolean);
420
+ },
421
+ handleThemeMessage,
255
422
  identity, matches, begin, register, suspend, ready, configure, flush, send, fail,
256
423
  subscribe(listener) {
257
424
  listeners.add(listener);
@@ -277,7 +444,10 @@ export function createFrameController({ sessionId, host, request, suspended, tra
277
444
  return false;
278
445
  return value.capability === state.capability && value.generation === state.generation &&
279
446
  value.pageKey === state.key && typeof value.type === "string" &&
280
- (!loading() || value.type === "eh:ready");
447
+ (!loading() || value.type === "eh:ready" ||
448
+ (state.phase.kind === "confirming" && value.type === "eh:themeApplied" &&
449
+ isThemePayload(value) && value.theme === currentTheme?.pending?.theme &&
450
+ value.themeRevision === currentTheme.pending.themeRevision));
281
451
  },
282
452
  configured(mode, savePolicy) {
283
453
  if (!expectedConfiguration || expectedConfiguration.mode !== mode ||
@@ -287,13 +457,7 @@ export function createFrameController({ sessionId, host, request, suspended, tra
287
457
  cancelTimer(configurationTimer);
288
458
  configurationTimer = null;
289
459
  state.configurationGeneration = state.generation;
290
- const started = identity();
291
- const appliedConfiguration = expectedConfiguration;
292
- if (host.previous)
293
- host.afterPaint(() => {
294
- if (matches(started) && expectedConfiguration === appliedConfiguration && !loading())
295
- finishReplacement();
296
- });
460
+ handoff();
297
461
  if (configuration && configuration.mode === mode && configuration.savePolicy === savePolicy) {
298
462
  configuration.settle(true);
299
463
  }
@@ -313,6 +477,7 @@ export function createFrameController({ sessionId, host, request, suspended, tra
313
477
  suspend();
314
478
  state.phase = { kind: "disposed" };
315
479
  stopLoad();
480
+ stopRemoval();
316
481
  host.dispose();
317
482
  listeners.clear();
318
483
  },
package/lib/frame-host.js CHANGED
@@ -4,6 +4,7 @@ export function createFrameHost(initial, artifactOrigin) {
4
4
  let disposed = false;
5
5
  const loadListeners = new Set();
6
6
  const focusListeners = new Set();
7
+ const removalListeners = new Set();
7
8
  const detach = new Map();
8
9
  const paintFrames = new Set();
9
10
  function paint(callback) {
@@ -41,6 +42,12 @@ export function createFrameHost(initial, artifactOrigin) {
41
42
  return {
42
43
  get current() { return current; },
43
44
  get previous() { return previous; },
45
+ get currentWindow() { return current.contentWindow; },
46
+ get previousWindow() { return previous?.contentWindow || null; },
47
+ onPreviousRemoved(listener) {
48
+ removalListeners.add(listener);
49
+ return () => { removalListeners.delete(listener); };
50
+ },
44
51
  get visibleExecution() {
45
52
  const visible = previous || current;
46
53
  const savePolicy = visible.dataset.savePolicy;
@@ -98,6 +105,8 @@ export function createFrameHost(initial, artifactOrigin) {
98
105
  if (previous)
99
106
  remove(previous);
100
107
  previous = null;
108
+ for (const listener of removalListeners)
109
+ listener();
101
110
  delete current.dataset.replacing;
102
111
  },
103
112
  suspend() { current.removeAttribute("data-sdk-ready"); },
@@ -131,6 +140,7 @@ export function createFrameHost(initial, artifactOrigin) {
131
140
  paintFrames.clear();
132
141
  loadListeners.clear();
133
142
  focusListeners.clear();
143
+ removalListeners.clear();
134
144
  },
135
145
  };
136
146
  }
package/lib/paths.js CHANGED
@@ -2,9 +2,9 @@ import { createHash } from "node:crypto";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
4
  import fs from "node:fs";
5
- // Bump this when the CLI and detached server no longer share the same request
6
- // contract. A new CLI must not silently reuse an older background server.
7
- export const SERVER_PROTOCOL = 17;
5
+ // Bump for incompatible CLI/server or shell/SDK contracts, including readiness.
6
+ // A new CLI must not silently reuse an older background server.
7
+ export const SERVER_PROTOCOL = 18;
8
8
  export function serverProtocolMatches(protocol) {
9
9
  return Number(protocol) === SERVER_PROTOCOL;
10
10
  }
@@ -26,6 +26,9 @@ export function createRecoveryController(options) {
26
26
  reloadMessage: context.reload.message, reloadError: context.reload.error,
27
27
  canReload: !disposed && visible && context.reload.visible && !!context.identity.key && (!context.loading || !!context.frameError) && !reloadBusy,
28
28
  reloadBusy,
29
+ themeVisible: visible && context.themeSync?.status === "failed",
30
+ themeMessage: context.themeSync?.message || "",
31
+ canRetryTheme: !disposed && visible && context.themeSync?.status === "failed" && !!options.retryTheme,
29
32
  status: visible && !context.reload.visible ? (context.frameError || error || view.status || (busy ? "Updating page recovery settings…" : "")) : "",
30
33
  statusError: !!context.frameError || !!error,
31
34
  };
@@ -105,6 +108,12 @@ export function createRecoveryController(options) {
105
108
  getSnapshot: store.getSnapshot, subscribe: store.subscribe, publish,
106
109
  commands: {
107
110
  setMenuOpen, recover, reload,
111
+ async retryTheme() {
112
+ if (!project().canRetryTheme)
113
+ return;
114
+ await options.retryTheme?.();
115
+ publish();
116
+ },
108
117
  keepCurrent() {
109
118
  if (disposed || !project().reloadVisible || reloadBusy)
110
119
  return;
@@ -0,0 +1,97 @@
1
+ // Build-time only. Browser styles are generated, never imported from this module.
2
+ export const REVIEW_PALETTE = {
3
+ light: {
4
+ background: "#F5F2EA",
5
+ foreground: "#292E2B",
6
+ card: "#FFFDF7",
7
+ "card-foreground": "#292E2B",
8
+ popover: "#FFFDF7",
9
+ "popover-foreground": "#292E2B",
10
+ primary: "#17685F",
11
+ "primary-foreground": "#FFFDF7",
12
+ secondary: "#ECEDE5",
13
+ "secondary-foreground": "#626B65",
14
+ muted: "#ECEDE5",
15
+ "muted-foreground": "#626B65",
16
+ accent: "#E1EEEA",
17
+ "accent-foreground": "#17685F",
18
+ destructive: "#B43C48",
19
+ "destructive-foreground": "#FFFDF7",
20
+ border: "#D8D8CC",
21
+ input: "#7C877D",
22
+ ring: "#17685F",
23
+ "review-added": "#EFF3E2",
24
+ "review-added-foreground": "#4D6B24",
25
+ "review-added-border": "#82945E",
26
+ "review-removed": "#FCECEF",
27
+ "review-removed-foreground": "#B43C48",
28
+ "review-removed-border": "#C88791",
29
+ "review-modified": "#FFF1D9",
30
+ "review-modified-foreground": "#915B13",
31
+ "review-modified-border": "#B99157",
32
+ "review-count-added": "#4D6B24",
33
+ "review-count-removed": "#B43C48",
34
+ "review-count-modified": "#915B13",
35
+ "review-insert": "#DCE7C5",
36
+ "review-delete": "#FAE2E7",
37
+ "review-overlay": "#191F1D80",
38
+ "review-shadow-color": "#191F1D26",
39
+ "annotation-foreground": "#75470E",
40
+ "annotation-background": "#FFF1D9",
41
+ "annotation-active": "#F5D6A3",
42
+ "annotation-border": "#915B13",
43
+ "annotation-tint": "#915B131A",
44
+ "annotation-active-tint": "#915B132E",
45
+ "halo-light": "#FFFDF7",
46
+ "halo-dark": "#191F1D",
47
+ },
48
+ dark: {
49
+ background: "#191F1D",
50
+ foreground: "#EEEFE6",
51
+ card: "#222A26",
52
+ "card-foreground": "#EEEFE6",
53
+ popover: "#222A26",
54
+ "popover-foreground": "#EEEFE6",
55
+ primary: "#85C7B8",
56
+ "primary-foreground": "#102F29",
57
+ secondary: "#303A33",
58
+ "secondary-foreground": "#AFB9AF",
59
+ muted: "#303A33",
60
+ "muted-foreground": "#AFB9AF",
61
+ accent: "#293F37",
62
+ "accent-foreground": "#85C7B8",
63
+ destructive: "#F2A2AB",
64
+ "destructive-foreground": "#40272C",
65
+ border: "#3C4841",
66
+ input: "#77877B",
67
+ ring: "#85C7B8",
68
+ "review-added": "#2B3522",
69
+ "review-added-foreground": "#BBCD87",
70
+ "review-added-border": "#6F8347",
71
+ "review-removed": "#40272C",
72
+ "review-removed-foreground": "#F2A2AB",
73
+ "review-removed-border": "#A96572",
74
+ "review-modified": "#3D3020",
75
+ "review-modified-foreground": "#E7BB72",
76
+ "review-modified-border": "#9C7A46",
77
+ "review-count-added": "#BBCD87",
78
+ "review-count-removed": "#F2A2AB",
79
+ "review-count-modified": "#E7BB72",
80
+ "review-insert": "#3E4D2D",
81
+ "review-delete": "#633944",
82
+ "review-overlay": "#191F1DB3",
83
+ "review-shadow-color": "#00000066",
84
+ "annotation-foreground": "#F1CC8E",
85
+ "annotation-background": "#3D3020",
86
+ "annotation-active": "#594325",
87
+ "annotation-border": "#E7BB72",
88
+ "annotation-tint": "#E7BB721A",
89
+ "annotation-active-tint": "#E7BB722E",
90
+ "halo-light": "#FFFDF7",
91
+ "halo-dark": "#191F1D",
92
+ },
93
+ };
94
+ export const BRAND_COLORS = {
95
+ tile: REVIEW_PALETTE.light.primary,
96
+ bubble: REVIEW_PALETTE.light.card,
97
+ };