lavish-axi 0.1.84 → 0.1.86

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.
@@ -243,6 +244,10 @@ pnpm link
243
244
  The agent updates the artifact's Mermaid source, which remains authoritative.
244
245
  Flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other diagram types are images that reviewers can draw and annotate.
245
246
  Lavish changes only the browser view, so saved, standalone, and exported artifacts still render plain Mermaid.
247
+ - **Whiteboard libraries** - Copy Excalidraw library files (`.excalidrawlib`, for example from [libraries.excalidraw.com](https://libraries.excalidraw.com)) into `~/.lavish-axi/excalidraw-libraries/`, or `LAVISH_AXI_STATE_DIR/excalidraw-libraries/` when set. Every whiteboard's library panel lists their items, and an item you drop on the canvas reaches the agent by name in the edit summary.
248
+ Agents run `lavish-axi libraries` to find item refs and can show a flowchart node as one of those icons by adding a `%% lavish-icon <node-id> <library-id>/<item name>` comment to the Mermaid source. Mermaid ignores the comment, so saved, exported, and shared copies, and whiteboards missing that library, show the plain node.
249
+ Lavish only reads that directory and never downloads libraries. Files over 10 MB, more than 25 MB or 5,000 items in total, and unreadable files are skipped; `lavish-axi libraries` lists each skip with its reason.
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.
246
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.
247
252
  Set `LAVISH_AXI_IDLE_TIMEOUT_MS=0` or `off` to disable idle self-shutdown.
248
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 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.
@@ -272,6 +277,7 @@ pnpm link
272
277
  | `lavish-axi share <html-file>` | Publish the artifact (local assets inlined) to [ht-ml.app](https://ht-ml.app), a third-party host not part of Lavish, and print a visitable URL plus a secret update key; shares are public by default, `--private` locks one behind a generated password, and the same command republishes or unpublishes an existing page with `--site`/`--update-key`. |
273
278
  | `lavish-axi stop` | Shut down the background server. |
274
279
  | `lavish-axi playbook [id]` | List focused artifact guidance or show one playbook; agents must open each matching playbook before writing HTML. |
280
+ | `lavish-axi libraries` | List the Excalidraw libraries in the whiteboard library directory; `--search <text>` or `--library <id>` lists item refs for `%% lavish-icon` nodes. |
275
281
  | `lavish-axi design` | Show agent-facing design guidance, including optional CDN snippets and the whiteboard (Mermaid) opt-in snippet. |
276
282
  | `lavish-axi setup hooks` | Install or repair optional SessionStart hooks for Claude Code, Codex, OpenCode, and GitHub Copilot CLI; restart the agent session afterward. |
277
283
  | `lavish-axi setup plugin` | Register the installed package as an [Agent Plugin](https://agent-plugins.org) in VS Code, Cursor, and GitHub Copilot CLI; opt-in, idempotent, no marketplace involved. Reload each client afterward. |
@@ -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();
@@ -3574,11 +3578,32 @@ function postToWhiteboard(index, placement, message) {
3574
3578
  else postToInlineWhiteboard(index, message);
3575
3579
  }
3576
3580
 
3577
- async function fetchMermaidSources() {
3581
+ async function fetchMermaidSourcesData() {
3578
3582
  const response = await fetch("/api/" + key + "/mermaid-sources");
3579
3583
  if (!response.ok) throw new Error("could not read the artifact's Mermaid sources");
3580
3584
  const data = await response.json();
3581
- return Array.isArray(data.sources) ? data.sources : [];
3585
+ return {
3586
+ sources: Array.isArray(data.sources) ? data.sources : [],
3587
+ librariesVersion: typeof data.libraries_version === "string" ? data.libraries_version : "",
3588
+ };
3589
+ }
3590
+
3591
+ async function fetchMermaidSources() {
3592
+ return (await fetchMermaidSourcesData()).sources;
3593
+ }
3594
+
3595
+ // Fetched per init so a frame converts with the libraries its source hash was
3596
+ // computed from; a failed fetch resolves null and the frame converts without
3597
+ // libraries.
3598
+ function fetchWhiteboardLibraries() {
3599
+ return fetch("/api/whiteboard-libraries")
3600
+ .then((response) => (response.ok ? response.json() : null))
3601
+ .then((data) =>
3602
+ Array.isArray(data?.libraries)
3603
+ ? { libraries: data.libraries, version: typeof data.version === "string" ? data.version : "" }
3604
+ : null,
3605
+ )
3606
+ .catch(() => null);
3582
3607
  }
3583
3608
 
3584
3609
  async function authenticateWhiteboardChannel(token) {
@@ -3607,11 +3632,17 @@ function whiteboardRecord(index) {
3607
3632
 
3608
3633
  async function handleWhiteboardReady(index, mode, isCurrent) {
3609
3634
  try {
3610
- const sources = await fetchMermaidSources();
3635
+ const librariesRequest = fetchWhiteboardLibraries();
3636
+ const { sources, librariesVersion } = await fetchMermaidSourcesData();
3611
3637
  const source = sources.find((item) => item.index === index);
3612
3638
  if (!source) throw new Error("this diagram's Mermaid source was not found in the artifact file");
3613
3639
  const savedResponse = await fetch("/api/" + key + "/whiteboard/" + index);
3614
3640
  const saved = savedResponse.ok ? (await savedResponse.json()).whiteboard : null;
3641
+ const librariesData = await librariesRequest;
3642
+ // A library file that changed between the two requests leaves the frame
3643
+ // with libraries that do not match this source hash: treat that like a
3644
+ // failed fetch, so the conversion's autosave is held.
3645
+ const librariesMatch = Boolean(librariesData) && (!librariesVersion || librariesData.version === librariesVersion);
3615
3646
  const record = whiteboardRecord(index);
3616
3647
  record.source = String(source.source || "");
3617
3648
  record.sourceHash = String(source.hash || "");
@@ -3624,6 +3655,8 @@ async function handleWhiteboardReady(index, mode, isCurrent) {
3624
3655
  source: record.source,
3625
3656
  sourceHash: record.sourceHash,
3626
3657
  saved,
3658
+ libraries: librariesData ? librariesData.libraries : [],
3659
+ librariesUnavailable: !librariesMatch,
3627
3660
  theme: whiteboardTheme(),
3628
3661
  });
3629
3662
  return true;
@@ -4213,6 +4246,13 @@ window.addEventListener("message", (event) => {
4213
4246
  return;
4214
4247
  }
4215
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
+ }
4216
4256
  if (msg.type === "lavish:queuePrompt") {
4217
4257
  enqueuePrompt(msg.prompt);
4218
4258
  // Queued from inside the artifact, where the closed dock is the only sign it landed.
@@ -4376,6 +4416,7 @@ loadFrame();
4376
4416
 
4377
4417
  function toggleAnnotationMode() {
4378
4418
  if (ended || terminalSubmission) return;
4419
+ annotationOverridden = true;
4379
4420
  annotation = !annotation;
4380
4421
  annotationSwitch.setAttribute("aria-pressed", String(annotation));
4381
4422
  postToFrame({ type: "lavish:setAnnotationMode", enabled: annotation });