lavish-axi 0.1.85 → 0.1.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -199,6 +199,7 @@ pnpm link
199
199
  - **Live reload** - Lavish watches the HTML artifact file by default and preserves review context across reloads: the artifact iframe scroll position, an open annotation card's unsent text, and answers to `data-lavish-question` controls (application-owned form state is left alone). Unsent annotation text also survives a full reload of the review page itself. While a queued layout-issue batch is outstanding, closely spaced saves coalesce so one batch of fixes costs one refresh. To also reload on sibling asset changes, add `data-lavish-live-reload-root` to the root element or `<meta name="lavish-live-reload" content="root">`.
200
200
  If the element an unsent annotation was attached to is gone from the artifact for good, Lavish cannot reopen that card, and if the artifact replaces a queued note while you are editing it, the replacement is kept as the artifact sent it. Either way, Lavish writes your edited text into the conversation panel under **Unsent annotation** - selectable, never written over anything you have typed, and kept there across reloads; no note is ever dropped to make room for a newer one, and a note the browser refuses to store says so where it is shown.
201
201
  - **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) and custom widgets with an interactive ARIA role (`button`, `checkbox`, `combobox`, `menuitem`, `menuitemcheckbox`, `menuitemradio`, `option`, `radio`, `switch`, `tab`, `treeitem`) are interactive automatically, so they do not need `data-lavish-action`. A link (`<a href>`) stays annotatable when it carries one of these roles or sits inside such a widget.
202
+ Artifacts can start in explore mode with `data-lavish-annotate="off"` on the root `<html>` element so stray clicks do not open annotation cards. Annotation stays on by default (or with `"on"`); unknown values default to on. The toolbar reflects the page default and can override it; the reviewer's choice persists across artifact reloads within that review page. See `lavish-axi playbook input` for author guidance.
202
203
  For reversible choices, let option clicks update local state, then queue exactly one final answer from a per-question submit or Queue answer button with `window.lavish.queuePrompt()`.
203
204
  If someone may open a decision artifact as a standalone HTML file, `lavish-axi playbook input` provides an optional Copy all answers control they can use to paste their form answers back into chat.
204
205
  Mark only custom (non-native) clickable elements with `data-lavish-action` so Lavish does not annotate them, and use `data-lavish-question` or `queueKey` when pre-send updates for the same question should replace each other.
@@ -249,7 +250,7 @@ pnpm link
249
250
  Each library carries its author's terms (AWS Architecture Icons, for example, are covered by AWS's own usage terms), so check them before using one.
250
251
  - **Server cleanup** - The detached server stops after the last session ends when nothing is connected, or after `LAVISH_AXI_IDLE_TIMEOUT_MS` (default 30 minutes) with no browser or poll connections.
251
252
  Set `LAVISH_AXI_IDLE_TIMEOUT_MS=0` or `off` to disable idle self-shutdown.
252
- - **Server upgrades** - One background server serves every session, so upgrading `lavish-axi` while reviews are open makes the next `lavish-axi <html-file>` replace that server. Only the review page for the artifact being opened reloads itself once the replacement answers - and not even that one while you have unsent annotation text open, which gets the same banner instead so the reload is yours to make. Every other open review page keeps working and shows a banner reading "Lavish was updated. This page is running the previous version.", with a Check and reload button and a Dismiss button, so no page you are reading reloads on its own. After `lavish-axi stop` those pages say Lavish was stopped and to reload after you start it again, and a restart that only picks up a local build says that rather than claiming an update. An open review page whose server simply goes away - idle self-shutdown, a crash, a machine that slept - shows that same banner once reconnecting has failed for a few seconds, rather than retrying a dead server silently while looking fine; it clears itself if the server comes back, and dismissing it keeps it dismissed until then.
253
+ - **Server upgrades** - One background server serves every session, so upgrading `lavish-axi` while reviews are open makes the next `lavish-axi <html-file>` replace that server. Only a newer CLI replaces an older server: a CLI older than the running server uses it as is, so two installed versions on one machine do not restart each other's server. Releases up to and including 0.1.86 still replace a server of a different version, so this turn-by-turn restart stops only when both installed versions contain this fix. Only the review page for the artifact being opened reloads itself once the replacement answers - and not even that one while you have unsent annotation text open, which gets the same banner instead so the reload is yours to make. Every other open review page keeps working and shows a banner reading "Lavish was updated. This page is running the previous version.", with a Check and reload button and a Dismiss button, so no page you are reading reloads on its own. After `lavish-axi stop` those pages say Lavish was stopped and to reload after you start it again, and a restart that only picks up a local build says that rather than claiming an update. An open review page whose server simply goes away - idle self-shutdown, a crash, a machine that slept - shows that same banner once reconnecting has failed for a few seconds, rather than retrying a dead server silently while looking fine; it clears itself if the server comes back, and dismissing it keeps it dismissed until then.
253
254
  A page still using the legacy event stream reloads once when it reconnects to the replacement server, moving itself onto the current transport without retaining an HTTP connection. If unsent annotation text is open, it shows the neutral server-restarted banner instead and waits for you to reload.
254
255
  Every **Check and reload** control asks the server whether it is running before it navigates; while nothing answers, the page stays where it is and says so, and a check that gets no answer at all says that instead of guessing.
255
256
  In-flight `lavish-axi poll` commands end with an interrupted-poll error and are safe to re-run; queued feedback is never lost, and annotation text you have typed but not queued yet survives the reload as described under **Live reload**.
@@ -193,6 +193,9 @@ const artifactSrc = frame.dataset.artifactSrc || frame.getAttribute?.("data-arti
193
193
 
194
194
  const queued = loadQueuedPrompts();
195
195
  let annotation = true;
196
+ // Page defaults follow artifact loads until the reviewer explicitly chooses a mode.
197
+ let annotationOverridden = false;
198
+ let annotationDefaultReceived = false;
196
199
  let ended = false;
197
200
  let agentPresence = "waiting";
198
201
  const layoutGateEnabled = sessionData.layoutGateEnabled !== false;
@@ -3501,6 +3504,7 @@ async function replaceArtifactFrame({ recoveryRetry = false } = {}) {
3501
3504
  artifactLoadRevision = revision;
3502
3505
  artifactLoadToken = token;
3503
3506
  artifactSpokeToken = "";
3507
+ annotationDefaultReceived = false;
3504
3508
  inlineWhiteboardChannels.clear();
3505
3509
  setHandoffSuperseded(false);
3506
3510
  startLayoutGateCycle();
@@ -4242,6 +4246,13 @@ window.addEventListener("message", (event) => {
4242
4246
  return;
4243
4247
  }
4244
4248
  // The artifact spoke, so it rendered and ran its SDK - there is nothing fatal to probe for.
4249
+ if (msg.type === "lavish:annotationMode" && typeof msg.enabled === "boolean" && !annotationDefaultReceived) {
4250
+ annotationDefaultReceived = true;
4251
+ if (!annotationOverridden && !ended && !terminalSubmission) annotation = msg.enabled;
4252
+ annotationSwitch.setAttribute("aria-pressed", String(annotation && !ended));
4253
+ // Reconcile even if frame load replayed the old mode before this report arrived.
4254
+ postToFrame({ type: "lavish:setAnnotationMode", enabled: annotation && !ended });
4255
+ }
4245
4256
  if (msg.type === "lavish:queuePrompt") {
4246
4257
  enqueuePrompt(msg.prompt);
4247
4258
  // Queued from inside the artifact, where the closed dock is the only sign it landed.
@@ -4405,6 +4416,7 @@ loadFrame();
4405
4416
 
4406
4417
  function toggleAnnotationMode() {
4407
4418
  if (ended || terminalSubmission) return;
4419
+ annotationOverridden = true;
4408
4420
  annotation = !annotation;
4409
4421
  annotationSwitch.setAttribute("aria-pressed", String(annotation));
4410
4422
  postToFrame({ type: "lavish:setAnnotationMode", enabled: annotation });
package/dist/cli.mjs CHANGED
@@ -224,6 +224,7 @@ var PLAYBOOKS = [
224
224
  "Show selected state separately from queued state so the user trusts what will be sent back."
225
225
  ],
226
226
  design_rules: [
227
+ `Set data-lavish-annotate="off" on <html> for decision, choice, and form-shaped pages; leave annotation on (the default, or "on") when reviewing a frontend or layout. Unknown values default to on. The toolbar can override the page default and keeps the reviewer's choice across artifact reloads.`,
227
228
  "Native controls - radios, checkboxes, text inputs, selects, textareas, buttons, options, labels, disclosure summaries, and contenteditable regions - and custom widgets with an interactive ARIA role (button, checkbox, combobox, menuitem, option, radio, switch, tab, treeitem, and the menuitem variants) are interactive automatically: clicks toggle, focus, and type instead of annotating, so they do not need data-lavish-action. An <a href> stays annotatable when it carries one of these roles or sits inside such a widget. Build choice and option UIs from these whenever you can.",
228
229
  "For reversible choices, do not call window.lavish.queuePrompt() from radio change handlers or option click handlers. Those handlers should only update local selected state.",
229
230
  "Use a per-question form submit or explicit Queue answer button to read the current values and call window.lavish.queuePrompt() exactly once for the final answer.",
@@ -5498,7 +5499,8 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
5498
5499
  function postArtifactMessage(type, payload = {}) {
5499
5500
  parent.postMessage({ type, ...payload, artifact_load_token: String(artifactLoadToken || "") }, "*");
5500
5501
  }
5501
- let annotationMode = true;
5502
+ const initialAnnotationMode = document.documentElement.getAttribute("data-lavish-annotate") !== "off";
5503
+ let annotationMode = initialAnnotationMode;
5502
5504
  let hovered = null;
5503
5505
  let selected = null;
5504
5506
  let ignoreNextClick = false;
@@ -6872,6 +6874,7 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
6872
6874
  return shadow;
6873
6875
  }
6874
6876
  function closeCard() {
6877
+ const hadCard = activeCardContext !== null;
6875
6878
  activeCardContext = null;
6876
6879
  if (activeAttachments) {
6877
6880
  activeAttachments.destroy();
@@ -6885,7 +6888,7 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
6885
6888
  hovered = null;
6886
6889
  clearTextHighlight();
6887
6890
  selected = null;
6888
- scheduleReviewStateReport();
6891
+ if (hadCard) scheduleReviewStateReport();
6889
6892
  }
6890
6893
  function showAnnotationCard(target, options2 = {}) {
6891
6894
  cancelPendingDraftRestore();
@@ -7176,6 +7179,7 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
7176
7179
  true
7177
7180
  );
7178
7181
  setAnnotationMode(annotationMode);
7182
+ postArtifactMessage("lavish:annotationMode", { enabled: initialAnnotationMode });
7179
7183
  if (document.readyState === "loading") {
7180
7184
  document.addEventListener("DOMContentLoaded", startLayoutAudit, { once: true });
7181
7185
  } else {
@@ -12770,7 +12774,7 @@ var AGENT_REPLY_INPUT_LIMIT_BYTES = AGENT_REPLY_JSON_LIMIT_BYTES - AGENT_REPLY_J
12770
12774
  var AGENT_REPLY_LIMIT_LABEL = "2 MB JSON request limit";
12771
12775
  var POLL_STATE_HEADER = "lavish-poll-state";
12772
12776
  var CODEX_POLL_WAKE_PATH_GUIDANCE = "Codex detected: completed background tasks may not resume Codex automatically, so keep the poll attached to the active turn.";
12773
- var VERSION = "0.1.85";
12777
+ var VERSION = "0.1.87";
12774
12778
  function detectInvokingAgent(env = process.env) {
12775
12779
  return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
12776
12780
  }
@@ -14185,7 +14189,7 @@ async function ensureServer({ forceRestart = false, reloadKey = "" } = {}) {
14185
14189
  }
14186
14190
  }
14187
14191
  await startServer(port, { alsoListen });
14188
- const replacedForNetwork = Boolean(existing) && existing.app === "lavish-axi" && existing.network_stale === true && !forceRestart && typeof existing.version === "string" && existing.version === VERSION;
14192
+ const replacedForNetwork = Boolean(existing) && existing.app === "lavish-axi" && existing.network_stale === true && !forceRestart && typeof existing.version === "string" && !isServerOlderThanCli(VERSION, existing.version);
14189
14193
  let networkRestarted = replacedForNetwork;
14190
14194
  let raceRestarted = false;
14191
14195
  let deadline = Date.now() + 5e3;
@@ -14248,12 +14252,26 @@ function shouldRestartServer(currentVersion, healthBody, forceRestart = false) {
14248
14252
  if (forceRestart && healthBody.app === "lavish-axi") return true;
14249
14253
  if (healthBody.network_stale === true && healthBody.app === "lavish-axi") return true;
14250
14254
  if (typeof healthBody.version !== "string" || healthBody.version === "") return true;
14251
- return healthBody.version !== currentVersion;
14255
+ return isServerOlderThanCli(currentVersion, healthBody.version);
14256
+ }
14257
+ function isServerOlderThanCli(currentVersion, runningVersion) {
14258
+ if (runningVersion === currentVersion) return false;
14259
+ const current = parseReleaseVersion(currentVersion);
14260
+ const running = parseReleaseVersion(runningVersion);
14261
+ if (!current || !running) return true;
14262
+ for (let index = 0; index < 3; index += 1) {
14263
+ if (running[index] !== current[index]) return running[index] < current[index];
14264
+ }
14265
+ return false;
14266
+ }
14267
+ function parseReleaseVersion(version) {
14268
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(version));
14269
+ return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : null;
14252
14270
  }
14253
14271
  function serverReplacementReason(currentVersion, healthBody, forceRestart = false) {
14254
14272
  if (!shouldRestartServer(currentVersion, healthBody, forceRestart)) return "";
14255
14273
  const runningVersion = healthBody.version;
14256
- if (typeof runningVersion !== "string" || runningVersion === "" || runningVersion !== currentVersion) {
14274
+ if (typeof runningVersion !== "string" || runningVersion === "" || isServerOlderThanCli(currentVersion, runningVersion)) {
14257
14275
  return "upgrade";
14258
14276
  }
14259
14277
  return forceRestart ? "local-build" : "";
@@ -14269,7 +14287,7 @@ function shouldKillProcessOnPort(currentVersion, healthBody) {
14269
14287
  if (!healthBody || typeof healthBody !== "object") return false;
14270
14288
  if (typeof healthBody.version !== "string" || healthBody.version === "") return true;
14271
14289
  if (healthBody.app !== "lavish-axi") return false;
14272
- return healthBody.version !== currentVersion;
14290
+ return isServerOlderThanCli(currentVersion, healthBody.version);
14273
14291
  }
14274
14292
  async function canControlServerOnPort(baseUrl, healthBody, processMatchesLavish) {
14275
14293
  if (!healthBody || typeof healthBody !== "object") return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.85",
3
+ "version": "0.1.87",
4
4
  "packageManager": "pnpm@11.1.1",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "type": "module",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "lavish-axi",
4
- "version": "0.1.85",
4
+ "version": "0.1.87",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "author": {
7
7
  "name": "Kun Chen",