lavish-axi 0.1.56 → 0.1.58

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
@@ -31,10 +31,10 @@ HTML is the new markdown. Lavish is the new editor for your HTML artifacts.
31
31
  Agents are good at producing rich HTML artifacts, but the human-agent collaboration loop on such artifacts is lacking and falls back into screenshots and long responses for “tell me what to change.”
32
32
  That loses the thing HTML is best at: interactivity.
33
33
 
34
- Lavish Editor opens agent-generated HTML files in a local browser, lets you pinpoint elements and selected text, edit rendered Mermaid diagrams as whiteboards, and send feedback to the agent to address.
34
+ Lavish Editor opens agent-generated HTML files in a local browser, lets you pinpoint elements and selected text, edit diagrams your agent authored as Mermaid whiteboards, and send feedback to the agent to address.
35
35
 
36
36
  - **Local-first** - Review local HTML artifacts with a local CLI and no cloud dependency in the core feedback loop; hosted sharing through third-party ht-ml.app is explicit and opt-in.
37
- - **Human-AI collaboration** - Annotate elements and selected text ranges, edit Mermaid diagrams as whiteboards, and send messages to the agent without leaving Lavish Editor.
37
+ - **Human-AI collaboration** - Annotate elements and selected text ranges, edit Mermaid whiteboard diagrams, and send messages to the agent without leaving Lavish Editor.
38
38
  - **Battery included** - Lavish Editor teaches your agent good visualization for common use cases such as product or technical plans, design explorations and more out of the box.
39
39
 
40
40
  Lavish Editor is an [AXI](https://axi.md), which means -
@@ -162,7 +162,7 @@ pnpm link
162
162
  - **Portable artifacts** - The artifact runs in a sandboxed iframe while Lavish injects a small SDK for annotations, snapshots, feedback controls, and render-time layout checks.
163
163
  Author-defined links and popups can open in top-level tabs, while artifact documents remain sandboxed without same-origin access.
164
164
  Lavish does not inject any design system, so the saved HTML file renders identically whether you open it through `lavish-axi` or directly in a browser.
165
- Run `lavish-axi design` for the single source of agent-facing design guidance and optional CDN or Mermaid snippets.
165
+ Run `lavish-axi design` for the single source of agent-facing design guidance, including optional CDN snippets and the whiteboard (Mermaid) opt-in snippet.
166
166
  - **Self-paint warning** - `lavish-axi <html-file>`, `export`, and `share` run a render-free check for artifacts missing an explicit page background and return a one-line `self_paint_warning`.
167
167
  The check fails open - any stylesheet link, `@import`, Tailwind runtime script, `color-scheme`, or `html`/`body`/`:root` background signal suppresses it - and it never blocks the open.
168
168
  - **Open-time layout gate** - The browser chrome masks an artifact only while the real in-iframe audit waits for fonts and final geometry.
@@ -204,6 +204,7 @@ pnpm link
204
204
  The no-timeout poll always writes an immediate stderr banner so it is visibly not hung; it adds the periodic stderr wait ticks only in an interactive terminal, so when stderr is piped (as under agent harnesses) the captured output carries no tick noise. Stdout always stays reserved for the final response; if the poll is interrupted or times out before feedback arrives, re-run it because feedback remains queued until delivery. Poll delivery consumes the response, so read the complete response before truncating or filtering it.
205
205
  Codex-specific guidance keeps that poll attached to the active turn instead of hiding it in a background task, because completed background tasks may not resume the agent.
206
206
  - **Session end etiquette** - Lavish tracks who ended a session: a human clicking **End session** (or **Send & end session**) in the browser is a user-initiated end, while `lavish-axi end <html-file>` is agent-initiated.
207
+ When either side ends the session, every open review tab becomes visibly read-only and disables its feedback controls; feedback submitted after the end is refused instead of being accepted without an agent to receive it.
207
208
  A plain `lavish-axi <html-file>` after a user-initiated end refuses to reopen the browser and returns guidance instead; pass `--reopen` only when the user asks for further review or something important needs their visual attention.
208
209
  Agent-initiated ends keep reopening normally, same as before.
209
210
  `lavish-axi poll`'s `ended` response and the `feedback` response for the final batch before an end both carry `next_step` guidance telling the agent to stop polling and deliver remaining updates in chat instead of reopening.
@@ -218,7 +219,8 @@ pnpm link
218
219
  As a browser-side abuse guard, each chrome page allows 30 upload attempts per rolling minute, 4 uploads in flight at once, and 256 MiB of attempted image bytes over its lifetime; rejected uploads stay visible for retry or removal.
219
220
  Attachments are cleaned up by `LAVISH_AXI_ATTACHMENT_TTL_MS` (default 7 days; `0`/`off` disables) but only once no pending prompt still references them; `LAVISH_AXI_MAX_ATTACHMENT_DISK_MB` (default 512 MiB; `0`/`off` disables) caps total attachment disk.
220
221
  The cap is enforced when an image is uploaded, not just periodically: the upload first reclaims unreferenced files (oldest first, and never one added within the last hour), and if it still would not fit, that upload is refused with a storage-full error on its chip instead of discarding an image the user is about to send.
221
- - **Mermaid diagrams** - In the Lavish browser, every rendered Mermaid diagram in a `.mermaid` container becomes an embedded editable Excalidraw whiteboard.
222
+ - **Mermaid diagrams** - Whiteboards are an opt-in: agents author a diagram as Mermaid only when you ask for an editable whiteboard, and hand-authored inline SVG illustrations are the default figure medium otherwise.
223
+ In the Lavish browser, every rendered Mermaid diagram in a `.mermaid` container becomes an embedded editable Excalidraw whiteboard.
222
224
  Click a diagram to unlock editing, and use its Fullscreen action to edit it over the whole viewport.
223
225
  Whiteboard scenes autosave locally.
224
226
  If a live reload changes the Mermaid source, an unmodified whiteboard silently re-converts to the new diagram. If the reviewer had edited the scene, reopening it lets them re-convert and discard the saved edits or keep editing the saved scene.
@@ -252,7 +254,7 @@ pnpm link
252
254
  | `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, and `--password` makes viewers enter the password before viewing. |
253
255
  | `lavish-axi stop` | Shut down the background server. |
254
256
  | `lavish-axi playbook [id]` | List focused artifact guidance or show one playbook; agents must open each matching playbook before writing HTML. |
255
- | `lavish-axi design` | Show agent-facing design guidance, including optional CDN and Mermaid snippets. |
257
+ | `lavish-axi design` | Show agent-facing design guidance, including optional CDN snippets and the whiteboard (Mermaid) opt-in snippet. |
256
258
  | `lavish-axi setup hooks` | Install or repair optional SessionStart hooks for Claude Code, Codex, OpenCode, and GitHub Copilot CLI; restart the agent session afterward. |
257
259
  | `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. |
258
260
  | `lavish-axi server` | Run the local Lavish Editor server. |
@@ -746,16 +746,16 @@ function setSheetOpen(open) {
746
746
  if (sheetOpen) scrollPanelToBottom();
747
747
  }
748
748
 
749
- // Re-derives every sheet attribute from the two facts that matter - whether the phone layout is
750
- // active and whether the sheet is open - so a viewport crossing the breakpoint in either direction
751
- // leaves nothing stale: a desktop panel is never inert, and a closed dock never traps focus.
749
+ // Re-derives every sheet attribute from the phone layout, sheet-open, and session-ended state so a
750
+ // viewport crossing the breakpoint in either direction cannot make an ended panel interactive or
751
+ // leave a closed dock trapping focus.
752
752
  function applySheetState() {
753
753
  const mobile = isMobileSheet();
754
754
  const open = mobile && sheetOpen;
755
755
  document.body.classList.toggle("sheet-open", open);
756
756
  const docked = mobile && !open;
757
- panelScroll.inert = docked;
758
- chatComposer.inert = docked;
757
+ panelScroll.inert = ended || docked;
758
+ chatComposer.inert = ended || docked;
759
759
  const activeElement = document.activeElement;
760
760
  if (docked && activeElement && (panelScroll.contains(activeElement) || chatComposer.contains(activeElement))) {
761
761
  panelToggle.focus();
@@ -1217,6 +1217,14 @@ async function submitQueuedOnce() {
1217
1217
  if (!response.ok) {
1218
1218
  if (response.status === 409) {
1219
1219
  const data = await response.json().catch(() => null);
1220
+ // The session already ended before this batch arrived - most likely this chrome missed the
1221
+ // SSE `ended` event (a dropped connection). Go read-only now instead of leaving Send enabled
1222
+ // for another attempt that will be refused the same way.
1223
+ if (data?.status === "ended") {
1224
+ endAfterSubmit = false;
1225
+ markSessionEnded();
1226
+ return false;
1227
+ }
1220
1228
  if (Array.isArray(data?.warnings)) setLayoutWarnings(data.warnings);
1221
1229
  endAfterSubmit = false;
1222
1230
  return false;
@@ -1778,6 +1786,7 @@ function markSessionEnded() {
1778
1786
  ended = true;
1779
1787
  cancelArtifactLoadRecovery();
1780
1788
  closeMenus();
1789
+ closeShareDialog();
1781
1790
  closeWarningsDrawer();
1782
1791
  renderWarnings();
1783
1792
  closeWhiteboard();
@@ -1785,7 +1794,7 @@ function markSessionEnded() {
1785
1794
  moreButton.disabled = true;
1786
1795
  chatInput.disabled = true;
1787
1796
  updateSendState();
1788
- renderSheetSummary();
1797
+ applySheetState();
1789
1798
  if (presenceBanner) presenceBanner.hidden = true;
1790
1799
  if (handoffBanner) handoffBanner.hidden = true;
1791
1800
  if (outdatedBanner) outdatedBanner.hidden = true;
@@ -3123,6 +3132,7 @@ events.addEventListener("agent-reply", (event) => {
3123
3132
  events.addEventListener("chat-sync", (event) => syncChat(JSON.parse(event.data).chat || []));
3124
3133
  events.addEventListener("agent-presence", (event) => setAgentPresence(JSON.parse(event.data).state));
3125
3134
  events.addEventListener("layout-warnings", (event) => setLayoutWarnings(JSON.parse(event.data).warnings || []));
3135
+ events.addEventListener("ended", () => markSessionEnded());
3126
3136
  // A reconnecting stream means this chrome may have missed updates while it was away.
3127
3137
  events.addEventListener("open", () => refreshLayoutWarnings());
3128
3138
 
@@ -3134,6 +3144,9 @@ renderWarnings();
3134
3144
  initialChat.forEach((item) => addChat(item.role, item.text));
3135
3145
  retiredDrafts.forEach((text) => renderRetiredDraft(text));
3136
3146
  setAgentPresence("waiting");
3147
+ // The session already ended before this page (re)loaded, so there is no future SSE `ended` event
3148
+ // to wait for - start read-only instead of looking live until a Send gets silently refused.
3149
+ if (sessionData.initialEnded) markSessionEnded();
3137
3150
 
3138
3151
  // Reaching this line is the only proof that this file parsed and ran to completion. The page it
3139
3152
  // bootstraps ships with the layout-gate overlay already covering the artifact, and only this
package/dist/cli.mjs CHANGED
@@ -20,11 +20,11 @@ var PLAYBOOK_ROUTER_HELP = "One artifact often combines several playbooks (for e
20
20
  var PLAYBOOKS = [
21
21
  {
22
22
  id: "diagram",
23
- use_when: "Map relationships, flows, state, and architecture",
23
+ use_when: "Explain relationships, flows, state, architecture, and concepts with illustrations",
24
24
  choose: [
25
- "Use Mermaid when automatic node placement and edge routing matter more than rich card content.",
26
- "Use CSS grid, SVG, or positioned HTML when each item needs prose, code, controls, or detailed annotations.",
27
- "Use a hybrid shape for large systems: a small overview diagram followed by detailed module cards."
25
+ "Default to hand-authored inline SVG: it gives proportion, emphasis, spatial metaphor, and annotation-ready structure that generated layouts cannot.",
26
+ "Use Mermaid only when the user asks for an editable whiteboard: rendered Mermaid in a `.mermaid` container becomes an Excalidraw whiteboard in the Lavish browser.",
27
+ "For large systems, draw a small overview illustration and put detail in module cards below it, instead of one dense auto-laid graph."
28
28
  ],
29
29
  structure: [
30
30
  "Lead with the question the diagram answers, not with the implementation detail that produced it.",
@@ -32,15 +32,18 @@ var PLAYBOOKS = [
32
32
  "For complex systems, separate topology from detail so the overview stays readable."
33
33
  ],
34
34
  design_rules: [
35
- "Use page-scoped class names and avoid generic names like .node that can collide with diagram libraries.",
36
- "Prefer top-down flow for multi-step diagrams unless the flow is genuinely linear and short.",
37
- "Quote labels that contain punctuation or code-like names, and use explicit line breaks where the renderer supports them.",
38
- "Initialize Mermaid to match the page theme and re-render when the theme changes: pick the Mermaid theme from the effective page appearance (light or dark) at render time, and use the theme-aware `lavish-axi design` Mermaid snippet rather than hardcoding a single theme, since Mermaid does not restyle an already-rendered SVG when the viewer toggles the page theme."
35
+ "Size with viewBox plus width:100%; never fixed pixel dimensions, and keep every element inside the viewBox.",
36
+ "Color through currentColor and the page's CSS custom properties so figures follow the artifact's light and dark themes.",
37
+ "Give every meaningful node, edge, and region a stable id and a <title> so reviewers can annotate precisely.",
38
+ "Keep labels to a few words and put prose beside the figure in HTML - SVG text does not wrap, so short labels are also the overflow discipline.",
39
+ "Keep figures self-contained: no external images, fonts, or scripts, so exports render offline.",
40
+ "Render-verify before serving: screenshot the artifact in light, dark, and a narrow viewport - the layout audit deliberately skips SVG interiors.",
41
+ "When the user asked for a whiteboard, initialize Mermaid theme-aware with the `lavish-axi design` snippet rather than hardcoding one theme."
39
42
  ],
40
43
  pitfalls: [
41
- "Do not cram every file or function into one diagram when a layered explanation would be clearer.",
42
- "Do not hand-build boxes-and-arrows from div/flexbox for a flow: it does not auto-route edges and reads worse than Mermaid; reach for Mermaid or SVG for richly annotated nodes.",
43
- "Do not let default diagram colors clash with the page palette or dark mode.",
44
+ "Do not cram every file or function into one figure when a layered explanation would be clearer.",
45
+ "Do not hand-build boxes-and-arrows from div/flexbox: inline SVG owns figures, HTML owns the prose around them.",
46
+ "Do not reach for Mermaid to save authoring effort - it surrenders position, size, and emphasis to the engine.",
44
47
  "Do not present unverified architecture claims as facts. Cite the files or commands that support them."
45
48
  ],
46
49
  lavish_notes: [
@@ -402,7 +405,7 @@ var LAYOUT_SAFETY_CSS_SNIPPET = `<style>
402
405
  }
403
406
  </style>`;
404
407
  var DESIGN_PRIORITY_RULE = "Decide the design direction in this strict priority order, and only move to the next step when the current one truly yields nothing: (1) if the user asked for a specific look or named design system, use that; (2) otherwise you must first inspect the project the artifact is about - the subject or product whose content or UI it represents, which may differ from your current working directory - and match that project's design system: Tailwind or theme config, shared CSS variables or design tokens, component library, brand assets, or existing styled pages. If the artifact previews, proposes, or mocks a specific app's UI, render it in that app's own design system so it faithfully shows the product, even when you are running in a different repo; (3) only when both steps come up empty, use the Lavish-recommended Tailwind CSS browser runtime v4 + DaisyUI v5, available via CDN, and prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user.";
405
- var DESIGN_SYSTEM_HINT = "Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: " + DESIGN_PRIORITY_RULE + " Run `lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet, a Mermaid CDN snippet/init for diagrams, and the DaisyUI component reference. When you deliver the artifact, state which of the three design sources you used and why.";
408
+ var DESIGN_SYSTEM_HINT = "Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: " + DESIGN_PRIORITY_RULE + " Run `lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet, the whiteboard (Mermaid) opt-in snippet, and the DaisyUI component reference. When you deliver the artifact, state which of the three design sources you used and why.";
406
409
  var DAISYUI_THEMES = [
407
410
  "light",
408
411
  "dark",
@@ -457,8 +460,8 @@ function createDesignOutput() {
457
460
  layout_safety_note: "Optional copy-paste CSS for artifacts with dense nested grid/flex layouts, badges, wide monospace or pixel fonts, or local media. Paste it into the artifact yourself when useful. Lavish never auto-injects it, so direct-open portability stays intact.",
458
461
  other_design_systems: "If the user asks for a different design system (Bootstrap, custom CSS, plain HTML, etc.), use that instead - Lavish does not require DaisyUI."
459
462
  },
460
- diagram_tooling: {
461
- use_when: "Use this for flows / architecture / state / sequence diagrams after opening the diagram playbook; Mermaid handles layout and edge routing better than hand-built div/flexbox boxes.",
463
+ whiteboard_tooling: {
464
+ use_when: "Opt-in only: author a diagram as Mermaid in a `.mermaid` container solely when the user asks for an editable whiteboard - Lavish turns it into an Excalidraw whiteboard whose edits come back as feedback. Every other figure is hand-authored inline SVG per the diagram playbook.",
462
465
  mermaid_cdn_snippet: MERMAID_CDN_SNIPPET,
463
466
  cdn_urls: { mermaid: MERMAID_CDN_URL },
464
467
  versions: { mermaid: MERMAID_VERSION }
@@ -7379,6 +7382,9 @@ var SessionStore = class {
7379
7382
  const shouldEndSession = Boolean(payload.endSession || payload.end_session);
7380
7383
  const restoring = options.restore === true;
7381
7384
  const alreadyEnded = session.status === "ended";
7385
+ if (alreadyEnded && !restoring) {
7386
+ return { ended: true, ended_by: session.ended_by };
7387
+ }
7382
7388
  const normalized = prompts.map(normalizePrompt);
7383
7389
  const normalizedPrompts = normalized.map((entry) => entry.prompt);
7384
7390
  const rejected = boundAttachmentRefs(normalized, options);
@@ -8881,6 +8887,10 @@ async function serve({
8881
8887
  res.status(404).json({ error: "session not found" });
8882
8888
  return;
8883
8889
  }
8890
+ if (result.ended) {
8891
+ res.status(409).json({ status: "ended", error: "session already ended", ended_by: result.ended_by });
8892
+ return;
8893
+ }
8884
8894
  if (result.rejected) {
8885
8895
  res.status(400).json({
8886
8896
  error: "some attachments could not be delivered",
@@ -8904,7 +8914,7 @@ async function serve({
8904
8914
  await syncOutstandingRepairs(req.params.key);
8905
8915
  events.emit("layout-warnings", req.params.key, serializeLayoutWarnings(session.layout_warnings));
8906
8916
  }
8907
- events.emit(shouldEndSession ? "ended" : "feedback", req.params.key);
8917
+ events.emit(shouldEndSession ? "ended" : "feedback", req.params.key, session.ended_by);
8908
8918
  res.json({ status: "queued", pending_prompts: session.pending_prompts });
8909
8919
  if (shouldEndSession) await shutdownIfNoLiveSessions();
8910
8920
  } catch (error) {
@@ -8991,9 +9001,9 @@ async function serve({
8991
9001
  });
8992
9002
  app.post("/api/:key/end", async (req, res, next) => {
8993
9003
  try {
8994
- await store.endSession(req.params.key, "user");
9004
+ const session = await store.endSession(req.params.key, "user");
8995
9005
  clearFeedbackDelivery(req.params.key, activePolls, deliveredFeedback, events);
8996
- events.emit("ended", req.params.key);
9006
+ events.emit("ended", req.params.key, session?.ended_by);
8997
9007
  res.json({ status: "ended" });
8998
9008
  await shutdownIfNoLiveSessions();
8999
9009
  } catch (error) {
@@ -9080,9 +9090,9 @@ async function serve({
9080
9090
  try {
9081
9091
  const file = await canonicalFile(req.body.file);
9082
9092
  const key = sessionKey(file);
9083
- await store.endSession(key, "agent");
9093
+ const session = await store.endSession(key, "agent");
9084
9094
  clearFeedbackDelivery(key, activePolls, deliveredFeedback, events);
9085
- events.emit("ended", key);
9095
+ events.emit("ended", key, session?.ended_by);
9086
9096
  res.json({ status: "ended" });
9087
9097
  await shutdownIfNoLiveSessions();
9088
9098
  } catch (error) {
@@ -9215,6 +9225,8 @@ async function serve({
9215
9225
  }
9216
9226
  });
9217
9227
  app.get("/events/:key", async (req, res, next) => {
9228
+ let cleanup = () => {
9229
+ };
9218
9230
  try {
9219
9231
  res.writeHead(200, {
9220
9232
  "content-type": "text/event-stream",
@@ -9223,7 +9235,6 @@ async function serve({
9223
9235
  });
9224
9236
  sseClients.set(res, String(req.params.key || ""));
9225
9237
  refreshIdleTimer();
9226
- const session = await store.findByKey(req.params.key);
9227
9238
  const sendReload = (key) => {
9228
9239
  if (key === req.params.key) {
9229
9240
  res.write("event: reload\ndata: {}\n\n");
@@ -9253,29 +9264,51 @@ data: ${JSON.stringify({ warnings })}
9253
9264
  `);
9254
9265
  }
9255
9266
  };
9256
- res.write(`event: chat-sync
9257
- data: ${JSON.stringify({ chat: session?.chat || [] })}
9267
+ const sendEnded = (key, endedBy) => {
9268
+ if (key === req.params.key) {
9269
+ res.write(`event: ended
9270
+ data: ${JSON.stringify({ ended_by: endedBy || null })}
9258
9271
 
9259
9272
  `);
9260
- res.write(
9261
- `event: agent-presence
9262
- data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, deliveredFeedback) })}
9263
-
9264
- `
9265
- );
9273
+ }
9274
+ };
9266
9275
  events.on("reload", sendReload);
9267
9276
  events.on("agent-reply", sendAgentReply);
9268
9277
  events.on("agent-presence", sendPresence);
9269
9278
  events.on("layout-warnings", sendLayoutWarnings);
9270
- req.on("close", () => {
9279
+ events.on("ended", sendEnded);
9280
+ let cleanedUp = false;
9281
+ cleanup = () => {
9282
+ if (cleanedUp) return;
9283
+ cleanedUp = true;
9284
+ req.off("close", cleanup);
9271
9285
  sseClients.delete(res);
9272
9286
  events.off("reload", sendReload);
9273
9287
  events.off("agent-reply", sendAgentReply);
9274
9288
  events.off("agent-presence", sendPresence);
9275
9289
  events.off("layout-warnings", sendLayoutWarnings);
9290
+ events.off("ended", sendEnded);
9276
9291
  refreshIdleTimer();
9277
- });
9292
+ };
9293
+ req.once("close", cleanup);
9294
+ const session = await store.findByKey(req.params.key);
9295
+ if (req.destroyed || res.writableEnded) {
9296
+ cleanup();
9297
+ return;
9298
+ }
9299
+ res.write(`event: chat-sync
9300
+ data: ${JSON.stringify({ chat: session?.chat || [] })}
9301
+
9302
+ `);
9303
+ res.write(
9304
+ `event: agent-presence
9305
+ data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, deliveredFeedback) })}
9306
+
9307
+ `
9308
+ );
9309
+ if (session?.status === "ended") sendEnded(req.params.key, session.ended_by);
9278
9310
  } catch (error) {
9311
+ cleanup();
9279
9312
  next(error);
9280
9313
  }
9281
9314
  });
@@ -10035,6 +10068,11 @@ function createChromeHtml(session, {
10035
10068
  const sessionJson = jsonScript({
10036
10069
  key: session.key,
10037
10070
  file: session.file,
10071
+ // A page loaded (or reloaded) after the session already ended has no future SSE `ended`
10072
+ // event to wait for - it must start read-only instead of looking live until the user tries
10073
+ // to send and gets refused (#171).
10074
+ initialEnded: session.status === "ended",
10075
+ initialEndedBy: session.ended_by || null,
10038
10076
  initialChat: session.chat || [],
10039
10077
  // Bootstrapping the inbox from the server is what makes it survive a browser refresh or a
10040
10078
  // reconnect: the chrome never owns warning state, it only renders it.
@@ -10334,7 +10372,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
10334
10372
  ]);
10335
10373
  var POLL_SEND_AND_END_RULE = "`Send & End` ends the session. Its final feedback is still delivered once. After that response, polling stops, and the agent must not reopen the session uninvited.";
10336
10374
  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.";
10337
- var VERSION = "0.1.56";
10375
+ var VERSION = "0.1.58";
10338
10376
  function detectInvokingAgent(env = process.env) {
10339
10377
  return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
10340
10378
  }
@@ -10440,10 +10478,10 @@ function createHomeOutput({ bin, sessions, includeSessions = true, agent = "gene
10440
10478
  } : {},
10441
10479
  visual_guidance: [
10442
10480
  "Use visual hierarchy to make the most important decisions, risks, tradeoffs, and next actions obvious at a glance",
10443
- "Use visual structure such as sections, cards, tables, diagrams, annotated snippets, and side-by-side comparisons instead of long prose",
10481
+ "Show, don't tell: explain concepts, flows, relationships, and comparisons with labeled illustrations - hand-authored inline SVG, per the diagram playbook - and show existing UI or state with screenshots of the real pages (run the app read-only if needed); reserve prose for what cannot be shown, such as rationale, trade-offs, and open questions",
10482
+ "Structure the prose that remains with sections, cards, tables, annotated snippets, and side-by-side comparisons instead of long paragraphs",
10444
10483
  "Choose typography, spacing, color, and layout deliberately so the artifact has a clear point of view",
10445
- "Prevent horizontal overflow at every nesting level: nested grid/flex children also need minmax(0, 1fr) tracks and min-width: 0, especially when badges, labels, or status text use wide pixel or monospace fonts; wrap, truncate, or contain long unbreakable text deliberately",
10446
- "When the artifact would describe existing or current UI or state, show it instead: capture screenshots of the real pages (run the app read-only if needed) and embed them, rather than explaining the current look in prose; reserve prose for what cannot be shown such as rationale, trade-offs, and open questions"
10484
+ "Prevent horizontal overflow at every nesting level: nested grid/flex children also need minmax(0, 1fr) tracks and min-width: 0, especially when badges, labels, or status text use wide pixel or monospace fonts; wrap, truncate, or contain long unbreakable text deliberately"
10447
10485
  ],
10448
10486
  playbooks: listPlaybooks(),
10449
10487
  help: [
@@ -10451,7 +10489,7 @@ function createHomeOutput({ bin, sessions, includeSessions = true, agent = "gene
10451
10489
  "Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
10452
10490
  "Lavish serves the html file through a local express.js server. If your html needs to reference other filesystem assets such as images, CSS, fonts, and local scripts, copy them into the same directory as the HTML file, then reference them with relative paths from that directory. Never prepend `/` to those asset paths - root paths won't work",
10453
10491
  `Run \`lavish-axi poll <html-file>\` to wait for user feedback. It long-polls and stays silent until the user sends feedback or ends the session, so leave it running - never kill it. Detected layout issues never return this poll: the browser files them in the user's Layout issues inbox in the Lavish top bar, and they arrive as an ordinary tag "layout-warnings" prompt only when the user selects them and queues the fixes. Never edit the artifact to chase a layout issue the user has not queued. The only exception is a fatal artifact_failures response, which means the review surface itself could not be used. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE}`,
10454
- 'Rendered Mermaid diagrams in `.mermaid` containers become embedded, editable Excalidraw whiteboards in the browser (click a diagram to unlock editing; a Fullscreen action opens it over the whole viewport) - flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other types embed as an image to draw on. Scenes autosave locally; an unmodified autosave silently re-converts when a reload changes the Mermaid source. If the reviewer edited the scene, they choose to re-convert and discard saved edits or keep editing the saved scene. Standalone and exported copies still render plain Mermaid. Queue feedback adds a prompt to the Conversation panel; when the user sends it, poll returns a tag "whiteboard" prompt carrying a bounded edit summary plus local scenePath (.excalidraw JSON) and previewPath (PNG) files - read the summary first, open the files only when needed, then apply the edits by updating the Mermaid source in the artifact (never try to write the scene back)',
10492
+ 'Mermaid is the whiteboard opt-in, not the diagram default: only when the user asks for an editable whiteboard, author that diagram as Mermaid in a `.mermaid` container. Rendered Mermaid diagrams there become embedded, editable Excalidraw whiteboards in the browser (click a diagram to unlock editing; a Fullscreen action opens it over the whole viewport) - flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other types embed as an image to draw on. Scenes autosave locally; an unmodified autosave silently re-converts when a reload changes the Mermaid source. If the reviewer edited the scene, they choose to re-convert and discard saved edits or keep editing the saved scene. Standalone and exported copies still render plain Mermaid. Queue feedback adds a prompt to the Conversation panel; when the user sends it, poll returns a tag "whiteboard" prompt carrying a bounded edit summary plus local scenePath (.excalidraw JSON) and previewPath (PNG) files - read the summary first, open the files only when needed, then apply the edits by updating the Mermaid source in the artifact (never try to write the scene back)',
10455
10493
  "Run `lavish-axi end <html-file>` to end a session as the agent - ending it this way still allows a plain reopen later. When the user ends it from the browser instead, a later `lavish-axi <html-file>` refuses to reopen it without `--reopen`",
10456
10494
  "Run `lavish-axi export <html-file> [--out <path>]` to write a portable copy of the artifact - one HTML file with its LOCAL assets inlined - so it opens with no Lavish server and no sibling files. Remote CDN/font references are left as links, so it needs network to render those. Users can also export from the browser chrome's overflow menu",
10457
10495
  "Run `lavish-axi share <html-file> [--password <pw>] [--token <t>]` to publish the artifact on ht-ml.app (https://ht-ml.app), a third-party hosting service not part of Lavish, and get back a visitable URL. Shares are PUBLIC by default, so anyone with the link can open them. Pass --password to publish a PRIVATE password-protected page; viewers must supply the password to view. Local assets are inlined; remote refs load over the network. It returns the url plus a secret update_key for managing the page later. Use --token or LAVISH_AXI_HTML_APP_TOKEN only when you have an optional bearer token; it is never required. Users can also publish from the browser chrome's overflow menu",
@@ -11459,7 +11497,7 @@ Examples:
11459
11497
  `,
11460
11498
  design: `Usage: lavish-axi design
11461
11499
 
11462
- Show a copy-pasteable CDN snippet for Tailwind CSS browser runtime v4 + DaisyUI v5 + themes, Mermaid diagram tooling, a content-to-playbook router, an optional layout safety CSS snippet, plus technical reference for DaisyUI components. ${PLAYBOOK_ROUTER_HELP} Lavish artifacts stay portable HTML. This CDN snippet is the design fallback, not the default: inspect the subject project before falling back, and paste the layout safety CSS only when useful for dense nested grid/flex layouts, badges, wide fonts, or local media. ${DESIGN_PRIORITY_RULE}
11500
+ Show a copy-pasteable CDN snippet for Tailwind CSS browser runtime v4 + DaisyUI v5 + themes, the whiteboard (Mermaid) opt-in snippet, a content-to-playbook router, an optional layout safety CSS snippet, plus technical reference for DaisyUI components. ${PLAYBOOK_ROUTER_HELP} Lavish artifacts stay portable HTML. This CDN snippet is the design fallback, not the default: inspect the subject project before falling back, and paste the layout safety CSS only when useful for dense nested grid/flex layouts, badges, wide fonts, or local media. ${DESIGN_PRIORITY_RULE}
11463
11501
  `,
11464
11502
  setup: `Usage: lavish-axi setup hooks
11465
11503
  lavish-axi setup plugin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
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.56",
4
+ "version": "0.1.58",
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",
@@ -52,18 +52,18 @@ Use lavish-axi when the user asks for a visual artifact, HTML explainer, interac
52
52
  ## Visual guidance
53
53
 
54
54
  - Use visual hierarchy to make the most important decisions, risks, tradeoffs, and next actions obvious at a glance
55
- - Use visual structure such as sections, cards, tables, diagrams, annotated snippets, and side-by-side comparisons instead of long prose
55
+ - Show, don't tell: explain concepts, flows, relationships, and comparisons with labeled illustrations - hand-authored inline SVG, per the diagram playbook - and show existing UI or state with screenshots of the real pages (run the app read-only if needed); reserve prose for what cannot be shown, such as rationale, trade-offs, and open questions
56
+ - Structure the prose that remains with sections, cards, tables, annotated snippets, and side-by-side comparisons instead of long paragraphs
56
57
  - Choose typography, spacing, color, and layout deliberately so the artifact has a clear point of view
57
58
  - Prevent horizontal overflow at every nesting level: nested grid/flex children also need minmax(0, 1fr) tracks and min-width: 0, especially when badges, labels, or status text use wide pixel or monospace fonts; wrap, truncate, or contain long unbreakable text deliberately
58
- - When the artifact would describe existing or current UI or state, show it instead: capture screenshots of the real pages (run the app read-only if needed) and embed them, rather than explaining the current look in prose; reserve prose for what cannot be shown such as rationale, trade-offs, and open questions
59
59
 
60
60
  ## Playbooks
61
61
 
62
62
  Run `npx -y lavish-axi playbook <id>` for focused, detailed guidance on any of these.
63
63
  One artifact often combines several playbooks (for example a plan that includes a comparison and a diagram), so MUST open each matching playbook before writing HTML.
64
- For flows, architecture, state, or sequence diagrams, do not hand-build boxes-and-arrows from div/flexbox; open the diagram playbook and use the theme-aware Mermaid snippet from `npx -y lavish-axi design` unless SVG is needed for richly annotated nodes.
64
+ Figures are hand-authored inline SVG by default - open the diagram playbook before drawing, and never build boxes-and-arrows from div/flexbox. Use the Mermaid whiteboard snippet from `npx -y lavish-axi design` only when the user asks for an editable whiteboard.
65
65
 
66
- - `diagram` - Map relationships, flows, state, and architecture
66
+ - `diagram` - Explain relationships, flows, state, architecture, and concepts with illustrations
67
67
  - `table` - Turn dense records into scan-friendly review surfaces
68
68
  - `comparison` - Show options, tradeoffs, and current vs target behavior
69
69
  - `plan` - Explain a product or technical plan before implementation
@@ -77,11 +77,11 @@ For flows, architecture, state, or sequence diagrams, do not hand-build boxes-an
77
77
  - Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`
78
78
  - Lavish serves the html file through a local express.js server. If your html needs to reference other filesystem assets such as images, CSS, fonts, and local scripts, copy them into the same directory as the HTML file, then reference them with relative paths from that directory. Never prepend `/` to those asset paths - root paths won't work
79
79
  - Run `npx -y lavish-axi poll <html-file>` to wait for user feedback. It long-polls and stays silent until the user sends feedback or ends the session, so leave it running - never kill it. Detected layout issues never return this poll: the browser files them in the user's Layout issues inbox in the Lavish top bar, and they arrive as an ordinary tag "layout-warnings" prompt only when the user selects them and queues the fixes. Never edit the artifact to chase a layout issue the user has not queued. The only exception is a fatal artifact_failures response, which means the review surface itself could not be used. Keep the poll in the foreground by default and let it return the feedback directly to the agent. A background poll is allowed only through a harness-native tracked background-job facility whose completion result is guaranteed to resume or notify the same agent. Never use `nohup`, shell `&`, `disown`, redirected fire-and-forget processes, or a detached terminal without an explicit verified callback merely to keep polling alive. If the harness has no completion-aware background facility, use the foreground poll or first wire a verified wake callback into the surrounding supervisor. Do not tell the user the artifact is being monitored until that wake path is live. If the poll gets killed or times out before feedback arrives, re-run it - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely. `Send & End` ends the session. Its final feedback is still delivered once. After that response, polling stops, and the agent must not reopen the session uninvited.
80
- - Rendered Mermaid diagrams in `.mermaid` containers become embedded, editable Excalidraw whiteboards in the browser (click a diagram to unlock editing; a Fullscreen action opens it over the whole viewport) - flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other types embed as an image to draw on. Scenes autosave locally; an unmodified autosave silently re-converts when a reload changes the Mermaid source. If the reviewer edited the scene, they choose to re-convert and discard saved edits or keep editing the saved scene. Standalone and exported copies still render plain Mermaid. Queue feedback adds a prompt to the Conversation panel; when the user sends it, poll returns a tag "whiteboard" prompt carrying a bounded edit summary plus local scenePath (.excalidraw JSON) and previewPath (PNG) files - read the summary first, open the files only when needed, then apply the edits by updating the Mermaid source in the artifact (never try to write the scene back)
80
+ - Mermaid is the whiteboard opt-in, not the diagram default: only when the user asks for an editable whiteboard, author that diagram as Mermaid in a `.mermaid` container. Rendered Mermaid diagrams there become embedded, editable Excalidraw whiteboards in the browser (click a diagram to unlock editing; a Fullscreen action opens it over the whole viewport) - flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other types embed as an image to draw on. Scenes autosave locally; an unmodified autosave silently re-converts when a reload changes the Mermaid source. If the reviewer edited the scene, they choose to re-convert and discard saved edits or keep editing the saved scene. Standalone and exported copies still render plain Mermaid. Queue feedback adds a prompt to the Conversation panel; when the user sends it, poll returns a tag "whiteboard" prompt carrying a bounded edit summary plus local scenePath (.excalidraw JSON) and previewPath (PNG) files - read the summary first, open the files only when needed, then apply the edits by updating the Mermaid source in the artifact (never try to write the scene back)
81
81
  - Run `npx -y lavish-axi end <html-file>` to end a session as the agent - ending it this way still allows a plain reopen later. When the user ends it from the browser instead, a later `npx -y lavish-axi <html-file>` refuses to reopen it without `--reopen`
82
82
  - Run `npx -y lavish-axi export <html-file> [--out <path>]` to write a portable copy of the artifact - one HTML file with its LOCAL assets inlined - so it opens with no Lavish server and no sibling files. Remote CDN/font references are left as links, so it needs network to render those. Users can also export from the browser chrome's overflow menu
83
83
  - Run `npx -y lavish-axi share <html-file> [--password <pw>] [--token <t>]` to publish the artifact on ht-ml.app (https://ht-ml.app), a third-party hosting service not part of Lavish, and get back a visitable URL. Shares are PUBLIC by default, so anyone with the link can open them. Pass --password to publish a PRIVATE password-protected page; viewers must supply the password to view. Local assets are inlined; remote refs load over the network. It returns the url plus a secret update_key for managing the page later. Use --token or LAVISH_AXI_HTML_APP_TOKEN only when you have an optional bearer token; it is never required. Users can also publish from the browser chrome's overflow menu
84
84
  - Run `npx -y lavish-axi stop` to shut down the background server (it also self-stops when idle or after the last session ends with nothing connected)
85
85
  - Run `npx -y lavish-axi playbook <playbook_id>` for focused artifact guidance. One artifact often combines several playbooks (for example a plan that includes a comparison and a diagram), so MUST open each matching playbook before writing HTML.
86
- - Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: Decide the design direction in this strict priority order, and only move to the next step when the current one truly yields nothing: (1) if the user asked for a specific look or named design system, use that; (2) otherwise you must first inspect the project the artifact is about - the subject or product whose content or UI it represents, which may differ from your current working directory - and match that project's design system: Tailwind or theme config, shared CSS variables or design tokens, component library, brand assets, or existing styled pages. If the artifact previews, proposes, or mocks a specific app's UI, render it in that app's own design system so it faithfully shows the product, even when you are running in a different repo; (3) only when both steps come up empty, use the Lavish-recommended Tailwind CSS browser runtime v4 + DaisyUI v5, available via CDN, and prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user. Run `npx -y lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet, a Mermaid CDN snippet/init for diagrams, and the DaisyUI component reference. When you deliver the artifact, state which of the three design sources you used and why.
86
+ - Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: Decide the design direction in this strict priority order, and only move to the next step when the current one truly yields nothing: (1) if the user asked for a specific look or named design system, use that; (2) otherwise you must first inspect the project the artifact is about - the subject or product whose content or UI it represents, which may differ from your current working directory - and match that project's design system: Tailwind or theme config, shared CSS variables or design tokens, component library, brand assets, or existing styled pages. If the artifact previews, proposes, or mocks a specific app's UI, render it in that app's own design system so it faithfully shows the product, even when you are running in a different repo; (3) only when both steps come up empty, use the Lavish-recommended Tailwind CSS browser runtime v4 + DaisyUI v5, available via CDN, and prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user. Run `npx -y lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet, the whiteboard (Mermaid) opt-in snippet, and the DaisyUI component reference. When you deliver the artifact, state which of the three design sources you used and why.
87
87
  - Use lavish-axi when the user asks for a visual artifact, HTML explainer, interactive prototype, review surface, product or technical plan, comparison, report, or browser-based feedback loop