lavish-axi 0.1.36 → 0.1.38

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
@@ -157,12 +157,13 @@ pnpm link
157
157
  - **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) are interactive automatically, so they do not need `data-lavish-action`.
158
158
  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()`.
159
159
  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.
160
- The browser chrome keeps editing actions in the overflow menu (copy path, reload artifact, copy DOM snapshot, export standalone HTML, publish link, end session) and can submit queued prompts with **Send & end session**, which sends the prompts and user-ended attribution together.
160
+ Queued annotation preview pills and chat history share a scrollable Conversation panel above a sticky composer, so long feedback queues do not push the text box or send controls off screen.
161
+ The browser chrome keeps editing actions in the overflow menu (copy path, reload artifact, copy DOM snapshot, export standalone HTML, publish link, end session), while the composer exposes **Send & End** beside **Send to Agent** to submit queued prompts and user-ended attribution together.
161
162
  - **Keyboard shortcuts** - In the chrome composer, Enter sends queued prompts and Shift+Enter inserts a newline.
162
163
  In the annotation card, Enter queues the annotation, Shift+Enter inserts a newline, and Ctrl+Enter (Cmd+Enter on macOS) queues it and sends all queued prompts immediately.
163
164
  Cmd+I or Ctrl+I toggles between annotate and explore mode from either the browser chrome or the artifact iframe, including while focus is in a textarea or control.
164
165
  - **Agent presence** - The browser shows when no agent is listening, keeps queued feedback and fresh layout warnings for the next successful `lavish-axi poll` send even across reloads, and only blocks human sends while the agent is working on delivered feedback. The no-timeout poll writes an immediate stderr banner and periodic stderr heartbeats while stdout stays reserved for the final response; if the poll is interrupted or times out, re-run it because queued feedback is never lost.
165
- - **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.
166
+ - **Session end etiquette** - Lavish tracks who ended a session: a human clicking **End session** (or **Send & End**) in the browser is a user-initiated end, while `lavish-axi end <html-file>` is agent-initiated.
166
167
  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.
167
168
  Agent-initiated ends keep reopening normally, same as before.
168
169
  `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.
@@ -15,14 +15,11 @@ function isModeToggleHotkeyEvent(event) {
15
15
  }
16
16
 
17
17
  const frame = /** @type {HTMLIFrameElement} */ (document.getElementById("artifact"));
18
+ const panelScroll = /** @type {HTMLDivElement} */ (document.getElementById("panelScroll"));
18
19
  const annotationPills = /** @type {HTMLDivElement} */ (document.getElementById("annotationPills"));
19
20
  const chatLog = /** @type {HTMLDivElement} */ (document.getElementById("chatLog"));
20
21
  const chatInput = /** @type {HTMLTextAreaElement} */ (document.getElementById("chatInput"));
21
22
  const sendButton = /** @type {HTMLButtonElement} */ (document.getElementById("send"));
22
- const sendCaret = /** @type {HTMLButtonElement} */ (document.getElementById("sendCaret"));
23
- const sendActions = /** @type {HTMLDivElement} */ (document.getElementById("sendActions"));
24
- const sendMenu = /** @type {HTMLDivElement} */ (document.getElementById("sendMenu"));
25
- const sendFromMenuButton = /** @type {HTMLButtonElement} */ (document.getElementById("sendFromMenu"));
26
23
  const sendAndEndButton = /** @type {HTMLButtonElement} */ (document.getElementById("sendAndEnd"));
27
24
  const annotationSwitch = /** @type {HTMLButtonElement} */ (document.getElementById("annotation"));
28
25
  const moreWrap = /** @type {HTMLDivElement} */ (document.getElementById("moreWrap"));
@@ -55,7 +52,7 @@ const layoutGateTitle = /** @type {HTMLDivElement} */ (document.getElementById("
55
52
  const layoutGateCopy = /** @type {HTMLParagraphElement} */ (document.getElementById("layoutGateCopy"));
56
53
  const layoutGateAction = /** @type {HTMLButtonElement} */ (document.getElementById("layoutGateAction"));
57
54
  const layoutIssueBanner = /** @type {HTMLDivElement} */ (document.getElementById("layoutIssueBanner"));
58
- const sendHint = /** @type {HTMLSpanElement} */ (document.getElementById("sendHint"));
55
+ const sendHint = /** @type {HTMLDivElement} */ (document.getElementById("sendHint"));
59
56
  const artifactSrc = frame.dataset.artifactSrc || frame.getAttribute?.("data-artifact-src") || frame.src || "";
60
57
 
61
58
  const queued = loadQueuedPrompts();
@@ -146,12 +143,12 @@ function render() {
146
143
  closeButton.addEventListener("click", (event) => removeQueuedPrompt(Number(closeButton.dataset.index), event));
147
144
  }
148
145
  updateSendState();
146
+ scrollPanelToBottom();
149
147
  }
150
148
 
151
149
  function updateSendState() {
152
150
  sendButton.disabled = ended || agentPresence === "working";
153
- sendCaret.disabled = ended || agentPresence === "working";
154
- sendFromMenuButton.disabled = sendButton.disabled;
151
+ sendAndEndButton.disabled = sendButton.disabled;
155
152
  }
156
153
 
157
154
  function showSendHint() {
@@ -175,7 +172,6 @@ function setMenuOpen(button, menu, open) {
175
172
 
176
173
  function closeMenus() {
177
174
  setMenuOpen(moreButton, moreMenu, false);
178
- setMenuOpen(sendCaret, sendMenu, false);
179
175
  }
180
176
 
181
177
  function toggleMenu(button, menu) {
@@ -204,14 +200,15 @@ async function copyText(text) {
204
200
  return true;
205
201
  }
206
202
 
207
- function addChat(role, text) {
203
+ function addChat(role, text, shouldScroll = true) {
208
204
  if (!text) return;
209
205
 
210
206
  const el = document.createElement("div");
211
207
  el.className = "bubble " + role;
212
208
  el.innerHTML = "<small>" + (role === "agent" ? "Agent" : "You") + "</small><div>" + escapeHtml(text) + "</div>";
213
209
  chatLog.appendChild(el);
214
- chatLog.scrollTop = chatLog.scrollHeight;
210
+ if (shouldScroll) scrollElementIntoView(el);
211
+ return el;
215
212
  }
216
213
 
217
214
  function syncChat(chat) {
@@ -219,9 +216,14 @@ function syncChat(chat) {
219
216
  el.remove();
220
217
  }
221
218
 
222
- for (const item of chat) addChat(item.role, item.text);
223
- if (workingBubble) chatLog.appendChild(workingBubble);
224
- chatLog.scrollTop = chatLog.scrollHeight;
219
+ let lastChatBubble = null;
220
+ for (const item of chat) lastChatBubble = addChat(item.role, item.text, false) || lastChatBubble;
221
+ if (workingBubble) {
222
+ chatLog.appendChild(workingBubble);
223
+ scrollElementIntoView(workingBubble);
224
+ } else if (lastChatBubble) {
225
+ scrollElementIntoView(lastChatBubble);
226
+ }
225
227
  }
226
228
 
227
229
  function setAgentPresence(state) {
@@ -241,7 +243,15 @@ function setAgentPresence(state) {
241
243
  workingBubble.innerHTML = '<span class="spinner"></span><span>Working...</span>';
242
244
  chatLog.appendChild(workingBubble);
243
245
  }
244
- chatLog.scrollTop = chatLog.scrollHeight;
246
+ scrollElementIntoView(workingBubble);
247
+ }
248
+
249
+ function scrollPanelToBottom() {
250
+ panelScroll.scrollTop = panelScroll.scrollHeight;
251
+ }
252
+
253
+ function scrollElementIntoView(el) {
254
+ el.scrollIntoView({ block: "nearest", inline: "nearest" });
245
255
  }
246
256
 
247
257
  function removeQueuedPrompt(index, event) {
@@ -303,11 +313,7 @@ function sendQueued(endAfter) {
303
313
  render();
304
314
  }
305
315
  if (!queued.length) {
306
- if (endAfter) {
307
- endSession();
308
- } else {
309
- showSendHint();
310
- }
316
+ showSendHint();
311
317
  return;
312
318
  }
313
319
  hideSendHint();
@@ -722,9 +728,7 @@ function toggleAnnotationMode() {
722
728
  annotationSwitch.onclick = toggleAnnotationMode;
723
729
 
724
730
  sendButton.onclick = () => sendQueued(false);
725
- sendFromMenuButton.onclick = () => sendQueued(false);
726
731
  sendAndEndButton.onclick = () => sendQueued(true);
727
- sendCaret.onclick = () => toggleMenu(sendCaret, sendMenu);
728
732
  moreButton.onclick = () => toggleMenu(moreButton, moreMenu);
729
733
  chatInput.addEventListener("keydown", (event) => {
730
734
  if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
@@ -753,7 +757,6 @@ endButton.onclick = () => {
753
757
  document.addEventListener("mousedown", (event) => {
754
758
  const target = /** @type {Node} */ (event.target);
755
759
  if (!moreMenu.hidden && !moreWrap.contains(target)) setMenuOpen(moreButton, moreMenu, false);
756
- if (!sendMenu.hidden && !sendActions.contains(target)) setMenuOpen(sendCaret, sendMenu, false);
757
760
  });
758
761
  document.addEventListener("keydown", (event) => {
759
762
  if (event.key === "Escape") {
package/dist/chrome.css CHANGED
@@ -404,25 +404,18 @@ body.lavish {
404
404
  opacity: 0.55;
405
405
  cursor: not-allowed;
406
406
  }
407
- .split {
407
+ .button-danger {
408
408
  display: inline-flex;
409
- align-items: stretch;
410
- }
411
- .send-main {
412
- border-radius: var(--radius-md) 0 0 var(--radius-md);
409
+ align-items: center;
410
+ gap: 8px;
411
+ background: transparent;
412
+ color: var(--danger);
413
413
  }
414
- .send-caret {
415
- border-radius: 0 var(--radius-md) var(--radius-md) 0;
416
- padding: 9px;
417
- border-left: 1px solid rgba(23, 19, 10, 0.22);
418
- display: grid;
419
- place-items: center;
414
+ .button-danger:hover:not(:disabled) {
415
+ background: rgba(240, 100, 100, 0.1);
420
416
  }
421
- .send-menu {
422
- right: 0;
423
- bottom: 44px;
424
- width: 208px;
425
- border-radius: 11px;
417
+ .button-danger > svg {
418
+ flex-shrink: 0;
426
419
  }
427
420
  .share-overlay {
428
421
  position: fixed;
@@ -639,11 +632,21 @@ body.lavish {
639
632
  margin: 16px 16px 8px;
640
633
  font-weight: var(--w-semi);
641
634
  letter-spacing: 0;
635
+ flex: 0 0 auto;
636
+ }
637
+ .panel-scroll {
638
+ flex: 1 1 auto;
639
+ min-width: 0;
640
+ min-height: 0;
641
+ overflow-y: auto;
642
+ overflow-x: hidden;
643
+ display: flex;
644
+ flex-direction: column;
642
645
  }
643
646
  .chat {
644
- flex: 1;
647
+ flex: 0 0 auto;
645
648
  min-height: 0;
646
- overflow: auto;
649
+ overflow: visible;
647
650
  padding: 0 16px 12px;
648
651
  display: flex;
649
652
  flex-direction: column;
@@ -704,9 +707,13 @@ body.lavish {
704
707
  gap: 8px;
705
708
  padding: 12px 16px;
706
709
  border-top: var(--hairline-subtle);
710
+ background: var(--bg-panel);
707
711
  min-width: 0;
708
712
  flex-shrink: 0;
709
713
  box-sizing: border-box;
714
+ position: sticky;
715
+ bottom: 0;
716
+ z-index: 4;
710
717
  }
711
718
  .presence-banner {
712
719
  border: 1px solid var(--border);
@@ -726,9 +733,17 @@ body.lavish {
726
733
  flex-wrap: wrap;
727
734
  gap: 6px;
728
735
  min-width: 0;
736
+ padding: 0 16px 12px;
737
+ flex: 0 0 auto;
738
+ }
739
+ .annotation-pills:empty {
740
+ display: none;
729
741
  }
730
742
  .pill-wrap {
731
- position: relative;
743
+ display: flex;
744
+ flex-direction: column;
745
+ align-items: flex-start;
746
+ width: min(320px, 100%);
732
747
  max-width: 100%;
733
748
  }
734
749
  .pill {
@@ -769,11 +784,10 @@ body.lavish {
769
784
  }
770
785
  .pill-tooltip {
771
786
  display: none;
772
- position: absolute;
787
+ position: static;
773
788
  z-index: 5;
774
- left: 0;
775
- bottom: calc(100% + 8px);
776
- width: min(320px, 80vw);
789
+ width: 100%;
790
+ margin-top: 8px;
777
791
  border: 1px solid var(--border-strong);
778
792
  border-radius: var(--radius-lg);
779
793
  background: var(--bg-bar);
@@ -831,10 +845,10 @@ body.lavish {
831
845
  align-items: center;
832
846
  gap: 8px;
833
847
  justify-content: flex-end;
848
+ min-width: 0;
834
849
  position: relative;
835
850
  }
836
851
  .send-hint {
837
- margin-right: auto;
838
852
  color: var(--fg-faint);
839
853
  font-size: 11px;
840
854
  line-height: 1.35;
@@ -842,6 +856,12 @@ body.lavish {
842
856
  .send-hint[hidden] {
843
857
  display: none;
844
858
  }
859
+ @media (max-width: 320px) {
860
+ .composer {
861
+ padding-left: 12px;
862
+ padding-right: 12px;
863
+ }
864
+ }
845
865
  .ended-overlay {
846
866
  position: fixed;
847
867
  inset: var(--bar-h) 0 0 0;
package/dist/cli.mjs CHANGED
@@ -5717,7 +5717,15 @@ async function serve({
5717
5717
  return;
5718
5718
  }
5719
5719
  await watchSession(session, watchers, events, logEvent);
5720
- res.type("html").send(createChromeHtml(session, { layoutGateEnabled: shouldEnableLayoutGate(req.query || {}) }));
5720
+ const artifactHtml = await readFile3(session.file, "utf8").catch(() => "");
5721
+ const { faviconTag, title } = extractArtifactHead(artifactHtml);
5722
+ res.type("html").send(
5723
+ createChromeHtml(session, {
5724
+ layoutGateEnabled: shouldEnableLayoutGate(req.query || {}),
5725
+ faviconTag,
5726
+ title: title ? `${title} \xB7 Lavish` : "Lavish Editor"
5727
+ })
5728
+ );
5721
5729
  } catch (error) {
5722
5730
  next(error);
5723
5731
  }
@@ -6100,9 +6108,7 @@ var chromeIcons = {
6100
6108
  exit: chromeIcon(
6101
6109
  '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>',
6102
6110
  15
6103
- ),
6104
- send: chromeIcon('<path d="M22 2 11 13"/><path d="M22 2 15 22l-4-9-9-4Z"/>', 14),
6105
- caret: chromeIcon('<path d="m6 9 6 6 6-6"/>', 13, 2)
6111
+ )
6106
6112
  };
6107
6113
  function displayPathParts(file, home = homedir()) {
6108
6114
  const normalizedFile = file.replaceAll("\\", "/");
@@ -6141,7 +6147,34 @@ function normalizeFlagValue(value) {
6141
6147
  if (Array.isArray(value)) return normalizeFlagValue(value[0]);
6142
6148
  return value === void 0 || value === null ? "" : String(value).trim().toLowerCase();
6143
6149
  }
6144
- function createChromeHtml(session, { layoutGateEnabled = true } = {}) {
6150
+ var LAVISH_DEFAULT_FAVICON = `<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>\u{1F48E}</text></svg>">`;
6151
+ function readTagAttr(tag, name) {
6152
+ const attrRe = /([a-z][\w:-]*)\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/gi;
6153
+ const target = name.toLowerCase();
6154
+ let match;
6155
+ while ((match = attrRe.exec(tag)) !== null) {
6156
+ if (match[1].toLowerCase() === target) {
6157
+ return (match[3] ?? match[4] ?? match[5] ?? "").trim();
6158
+ }
6159
+ }
6160
+ return "";
6161
+ }
6162
+ function extractArtifactHead(html) {
6163
+ const head = String(html || "").slice(0, 1e4);
6164
+ let faviconTag = LAVISH_DEFAULT_FAVICON;
6165
+ const linkTags = head.match(/<link\b(?:"[^"]*"|'[^']*'|[^"'>])*>/gi) || [];
6166
+ const iconTag = linkTags.find((tag) => /(^|\s)icon(\s|$)/i.test(readTagAttr(tag, "rel")));
6167
+ const iconHref = iconTag ? readTagAttr(iconTag, "href") : "";
6168
+ if (iconHref && /^(data:|https?:|\/\/)/i.test(iconHref)) {
6169
+ const safeHref = iconHref.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
6170
+ faviconTag = `<link rel="icon" href="${safeHref}">`;
6171
+ }
6172
+ let title = "";
6173
+ const titleMatch = head.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
6174
+ if (titleMatch) title = titleMatch[1].replace(/\s+/g, " ").trim();
6175
+ return { faviconTag, title };
6176
+ }
6177
+ function createChromeHtml(session, { layoutGateEnabled = true, faviconTag = LAVISH_DEFAULT_FAVICON, title = "Lavish Editor" } = {}) {
6145
6178
  const sessionJson = jsonScript({
6146
6179
  key: session.key,
6147
6180
  file: session.file,
@@ -6159,12 +6192,13 @@ function createChromeHtml(session, { layoutGateEnabled = true } = {}) {
6159
6192
  <head>
6160
6193
  <meta charset="utf-8">
6161
6194
  <meta name="viewport" content="width=device-width, initial-scale=1">
6162
- <title>Lavish Editor</title>
6195
+ <title>${escapeHtml(title)}</title>
6196
+ ${faviconTag}
6163
6197
  <link rel="stylesheet" href="/chrome.css">
6164
6198
  </head>
6165
6199
  <body class="${bodyClass}">
6166
6200
  <div class="bar"><div class="brand"><span class="brand-mark">Lavish</span><span class="brand-support">Editor</span></div><div class="spacer" aria-hidden="true"></div><button class="annotate-switch" id="annotation" type="button" aria-pressed="true" title="${escapeHtml(modeToggleHint)}"><span class="switch-track" aria-hidden="true"><span class="switch-knob"></span></span><span>Annotate</span></button><div class="more-wrap" id="moreWrap"><button class="more-button" id="moreButton" type="button" title="More" aria-haspopup="menu" aria-expanded="false">${chromeIcons.more}</button><div class="menu more-menu" id="moreMenu" hidden><div class="menu-head"><div class="menu-label">Editing</div><button class="menu-file" id="copyPath" type="button" title="Copy path \xB7 ${escapeHtml(session.file)}">${chromeIcons.file}<span class="menu-file-text"><span class="path-head">${escapeHtml(pathHead)}</span><span class="path-tail">${escapeHtml(pathTail)}</span></span><span class="copy-hint" id="copyHint"><span class="icon-copy">${chromeIcons.copy}</span><span class="icon-check">${chromeIcons.check}</span><span id="copyHintText">Copy</span></span></button></div><div class="menu-rule"></div><button class="menu-item" id="reloadArtifact" type="button">${chromeIcons.refresh}<span>Reload artifact</span></button><button class="menu-item" id="copySnapshot" type="button">${chromeIcons.camera}<span>Copy DOM snapshot</span></button><button class="menu-item" id="exportArtifact" type="button">${chromeIcons.download}<span>Export standalone HTML</span></button><button class="menu-item" id="shareArtifact" type="button">${chromeIcons.globe}<span>Publish link</span></button><div class="menu-rule"></div><button class="menu-item danger" id="end" type="button">${chromeIcons.exit}<span>End session</span></button></div></div></div>
6167
- <div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-downloads" data-artifact-src="/artifact/${session.key}/index.html"></iframe><div class="layout-issue-banner" id="layoutIssueBanner" hidden>This surface may have layout issues. Your agent has been notified.</div></div><aside class="panel"><h2>Conversation</h2><div class="chat" id="chatLog"></div><div class="composer"><div class="presence-banner" id="presenceBanner" hidden>Your agent is not listening. If this persists, ask your agent to poll for updates from Lavish.</div><div class="annotation-pills" id="annotationPills"></div><textarea id="chatInput" placeholder="Write a message for the agent..."></textarea><div class="actions" id="sendActions"><span class="send-hint" id="sendHint" hidden>Write a message or annotate an element first.</span><div class="split"><button class="button send-main" id="send">Send to Agent</button><button class="button send-caret" id="sendCaret" type="button" title="Send options" aria-haspopup="menu" aria-expanded="false">${chromeIcons.caret}</button></div><div class="menu send-menu" id="sendMenu" hidden><button class="menu-item" id="sendFromMenu" type="button">${chromeIcons.send}<span>Send to Agent</span></button><button class="menu-item danger" id="sendAndEnd" type="button">${chromeIcons.exit}<span>Send &amp; end session</span></button></div></div></div></aside></div>
6201
+ <div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-downloads" data-artifact-src="/artifact/${session.key}/index.html"></iframe><div class="layout-issue-banner" id="layoutIssueBanner" hidden>This surface may have layout issues. Your agent has been notified.</div></div><aside class="panel"><h2>Conversation</h2><div class="panel-scroll" id="panelScroll"><div class="chat" id="chatLog"></div><div class="annotation-pills" id="annotationPills"></div></div><div class="composer"><div class="presence-banner" id="presenceBanner" hidden>Your agent is not listening. If this persists, ask your agent to poll for updates from Lavish.</div><textarea id="chatInput" placeholder="Write a message for the agent..."></textarea><div class="send-hint" id="sendHint" hidden>Write a message or annotate an element first.</div><div class="actions" id="sendActions"><button class="button button-danger" id="sendAndEnd" type="button">${chromeIcons.exit}<span>Send &amp; End</span></button><button class="button" id="send">Send to Agent</button></div></div></aside></div>
6168
6202
  <div class="share-overlay" id="shareDialog" role="dialog" aria-modal="true" aria-labelledby="shareTitleText" hidden><form class="share-card" id="shareForm"><div class="share-head"><div><div class="share-kicker">Publish to <a class="share-link" href="https://ht-ml.app" target="_blank" rel="noopener noreferrer">ht-ml.app</a></div><h2 id="shareTitleText">Publish artifact</h2></div><button class="share-close" id="shareClose" type="button" aria-label="Close publish dialog"><svg width="14" height="14" viewBox="0 0 10 10" fill="none" aria-hidden="true" focusable="false"><path d="M1 1L9 9M9 1L1 9" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></button></div><p class="share-note">ht-ml.app is a separate, third-party hosting service, not part of Lavish. Publishing sends this artifact to its servers.</p><p class="share-copy">This uploads this artifact to ht-ml.app with local assets inlined. Without a password, the page is PUBLIC and anyone with the link can open it. With a password, the page is PRIVATE and viewers must supply the password to view.</p><p class="share-note">Do not publish secrets. The Lavish annotation SDK is not included.</p><div class="share-grid"><label>Password (optional)<input id="sharePassword" name="password" type="password" autocomplete="new-password" placeholder="Leave blank for a public page"></label></div><div class="share-status" id="shareStatus" role="status"></div><div class="share-result" id="shareResult" hidden><label>Share URL<div class="share-copy-row"><input id="shareUrl" readonly><button class="share-copy-btn" id="copyShareUrl" type="button">Copy URL</button></div></label><label>Update key (secret)<div class="share-copy-row"><input id="shareUpdateKey" readonly><button class="share-copy-btn" id="copyUpdateKey" type="button">Copy key</button></div></label><p class="share-note">Keep the update key private. ht-ml.app returns it once and it is the only way to update or delete this page later.</p></div><div class="share-actions"><button class="share-cancel" id="shareCancel" type="button">Cancel</button><button class="button" id="sharePublish" type="submit">Publish</button></div></form></div>
6169
6203
  <div class="ended-overlay layout-gate-overlay" id="layoutGateOverlay"${layoutGateHidden}><div class="ended-card"><div class="ended-title" id="layoutGateTitle">Checking layout.<br>One moment.</div><p class="ended-copy" id="layoutGateCopy">Lavish is waiting for fonts and final geometry before revealing this artifact.</p><button class="button ended-action" id="layoutGateAction" type="button">Show anyway</button></div></div>
6170
6204
  <div class="ended-overlay" id="endedOverlay" hidden><div class="ended-card"><div class="ended-title">Session ended.<br>Return to your agent to continue.</div><p class="ended-copy">${escapeHtml(session.file)}</p></div></div>
@@ -6376,7 +6410,7 @@ function normalizePagePath(path6) {
6376
6410
  var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "stop", "server", "playbook", "design", "setup", "export", "share"]);
6377
6411
  var RESERVED = new Set(RESERVED_COMMANDS);
6378
6412
  var DESCRIPTION = "Lavish Editor helps agents turn rich HTML artifacts into collaborative human review surfaces. Whenever you are about to give user a complex response that will be easier to understand via a rich / interactive page, consider using Lavish Editor. First generate an interactive HTML artifact according to user request, then run `lavish-axi <html-file>` so the user can visually review it, annotate elements or selected text, queue prompts, and send feedback back through `lavish-axi poll`.";
6379
- var VERSION = "0.1.36";
6413
+ var VERSION = "0.1.38";
6380
6414
  async function run(argv) {
6381
6415
  await ensureStateDir();
6382
6416
  const normalizedArgv = normalizeArgv(argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
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",
@@ -23,6 +23,7 @@
23
23
  "format": "prettier --write .",
24
24
  "format:check": "prettier --check .",
25
25
  "prepack": "npm run build",
26
+ "prepare": "npm run build",
26
27
  "test": "node --test",
27
28
  "typecheck": "tsc --noEmit"
28
29
  },