lavish-axi 0.1.46 → 0.1.48
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 +2 -0
- package/dist/chrome-client.js +2 -29
- package/dist/chrome.css +0 -19
- package/dist/cli.mjs +83 -10
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/lavish/SKILL.md +1 -0
package/README.md
CHANGED
|
@@ -162,6 +162,8 @@ pnpm link
|
|
|
162
162
|
- **Portable artifacts** - The artifact runs in an iframe while Lavish injects a small SDK for annotations, snapshots, feedback controls, and render-time layout checks.
|
|
163
163
|
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.
|
|
164
164
|
Run `lavish-axi design` for the single source of agent-facing design guidance and optional CDN or Mermaid snippets.
|
|
165
|
+
- **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`.
|
|
166
|
+
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.
|
|
165
167
|
- **Open-time layout gate** - The browser chrome masks an artifact only while the real in-iframe audit waits for fonts and final geometry.
|
|
166
168
|
The first completed check always reveals the artifact, whatever it found; the gate never holds the review hostage waiting for a repair.
|
|
167
169
|
The user can click **Show anyway**, and a bounded safety timeout fails open when no check has completed.
|
package/dist/chrome-client.js
CHANGED
|
@@ -8,7 +8,6 @@ const queueStorageKey = "lavish-axi:queued:" + key;
|
|
|
8
8
|
// Review-chrome state that must survive a browser refresh. Keyed per session so one review's
|
|
9
9
|
// triage can never leak into another artifact's.
|
|
10
10
|
const warningSelectionStorageKey = "lavish-axi:warning-selection:" + key;
|
|
11
|
-
const warningAckStorageKey = "lavish-axi:warning-ack:" + key;
|
|
12
11
|
const internalQueueKeyField = "_lavishQueueKey";
|
|
13
12
|
const initialChat = Array.isArray(sessionData.initialChat) ? sessionData.initialChat : [];
|
|
14
13
|
const MODE_TOGGLE_HOTKEY_KEY = String(sessionData.modeToggleHotkeyKey || "").toLowerCase();
|
|
@@ -57,7 +56,6 @@ const layoutGateOverlay = /** @type {HTMLDivElement} */ (document.getElementById
|
|
|
57
56
|
const layoutGateTitle = /** @type {HTMLDivElement} */ (document.getElementById("layoutGateTitle"));
|
|
58
57
|
const layoutGateCopy = /** @type {HTMLParagraphElement} */ (document.getElementById("layoutGateCopy"));
|
|
59
58
|
const layoutGateAction = /** @type {HTMLButtonElement} */ (document.getElementById("layoutGateAction"));
|
|
60
|
-
const layoutIssueBanner = /** @type {HTMLDivElement} */ (document.getElementById("layoutIssueBanner"));
|
|
61
59
|
const warningsWrap = /** @type {HTMLDivElement} */ (document.getElementById("warningsWrap"));
|
|
62
60
|
const warningsButton = /** @type {HTMLButtonElement} */ (document.getElementById("warningsButton"));
|
|
63
61
|
const warningsCount = /** @type {HTMLSpanElement} */ (document.getElementById("warningsCount"));
|
|
@@ -96,7 +94,6 @@ let layoutGateTimer;
|
|
|
96
94
|
let layoutWarnings = Array.isArray(sessionData.initialLayoutWarnings) ? sessionData.initialLayoutWarnings : [];
|
|
97
95
|
const selectedWarningIds = new Set(loadJsonState(warningSelectionStorageKey, []));
|
|
98
96
|
let warningsDrawerOpen = false;
|
|
99
|
-
let warningsAcknowledged = loadJsonState(warningAckStorageKey, false) === true;
|
|
100
97
|
const snapshotRequests = [];
|
|
101
98
|
let endAfterSubmit = false;
|
|
102
99
|
let workingBubble = null;
|
|
@@ -482,17 +479,6 @@ function normalizeLayoutFindings(value) {
|
|
|
482
479
|
: [];
|
|
483
480
|
}
|
|
484
481
|
|
|
485
|
-
function setLayoutIssueBanner(visible) {
|
|
486
|
-
if (!layoutIssueBanner) return;
|
|
487
|
-
layoutIssueBanner.hidden = !visible;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// The banner is a one-time "there is unresolved work" cue for the top-bar inbox. Once the user
|
|
491
|
-
// has opened the drawer it stays out of the way; the badge remains the standing signal.
|
|
492
|
-
function refreshLayoutIssueBanner() {
|
|
493
|
-
setLayoutIssueBanner(!ended && !warningsAcknowledged && !layoutGateVisible && activeWarnings().length > 0);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
482
|
function clearLayoutGateTimer() {
|
|
497
483
|
if (layoutGateTimer) clearTimeout(layoutGateTimer);
|
|
498
484
|
layoutGateTimer = undefined;
|
|
@@ -522,7 +508,6 @@ function revealLayoutGate() {
|
|
|
522
508
|
clearLayoutGateTimer();
|
|
523
509
|
layoutGateArmed = false;
|
|
524
510
|
setLayoutGateActive(false);
|
|
525
|
-
refreshLayoutIssueBanner();
|
|
526
511
|
}
|
|
527
512
|
|
|
528
513
|
function forceRevealLayoutGate(reason) {
|
|
@@ -536,7 +521,6 @@ function startLayoutGateCycle() {
|
|
|
536
521
|
|
|
537
522
|
layoutGateCycle += 1;
|
|
538
523
|
layoutGateArmed = true;
|
|
539
|
-
setLayoutIssueBanner(false);
|
|
540
524
|
setLayoutGateCard("checking");
|
|
541
525
|
setLayoutGateActive(true);
|
|
542
526
|
clearLayoutGateTimer();
|
|
@@ -553,21 +537,14 @@ function startLayoutGateCycle() {
|
|
|
553
537
|
// pending an agent repair: findings are the user's to triage, so a completed pass always reveals
|
|
554
538
|
// and hands the result to the passive inbox.
|
|
555
539
|
function handleLayoutGatePass() {
|
|
556
|
-
if (!layoutGateEnabled || layoutGateManuallyBypassed)
|
|
557
|
-
|
|
558
|
-
return;
|
|
559
|
-
}
|
|
560
|
-
if (!layoutGateArmed && !layoutGateVisible) {
|
|
561
|
-
refreshLayoutIssueBanner();
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
540
|
+
if (!layoutGateEnabled || layoutGateManuallyBypassed) return;
|
|
541
|
+
if (!layoutGateArmed && !layoutGateVisible) return;
|
|
564
542
|
revealLayoutGate();
|
|
565
543
|
}
|
|
566
544
|
|
|
567
545
|
function initializeLayoutGate() {
|
|
568
546
|
if (!layoutGateEnabled) {
|
|
569
547
|
setLayoutGateActive(false);
|
|
570
|
-
setLayoutIssueBanner(false);
|
|
571
548
|
return;
|
|
572
549
|
}
|
|
573
550
|
|
|
@@ -821,7 +798,6 @@ function renderWarnings() {
|
|
|
821
798
|
for (const warning of active) warningsList.appendChild(createWarningRow(warning));
|
|
822
799
|
}
|
|
823
800
|
updateWarningSelectionState();
|
|
824
|
-
refreshLayoutIssueBanner();
|
|
825
801
|
}
|
|
826
802
|
|
|
827
803
|
function updateWarningSelectionState() {
|
|
@@ -854,9 +830,6 @@ function setWarningsDrawerOpen(open) {
|
|
|
854
830
|
warningsButton.setAttribute("aria-expanded", String(warningsDrawerOpen));
|
|
855
831
|
if (warningsDrawerOpen) {
|
|
856
832
|
closeMenus();
|
|
857
|
-
warningsAcknowledged = true;
|
|
858
|
-
saveJsonState(warningAckStorageKey, true);
|
|
859
|
-
refreshLayoutIssueBanner();
|
|
860
833
|
warningsSelectAll.focus();
|
|
861
834
|
}
|
|
862
835
|
}
|
package/dist/chrome.css
CHANGED
|
@@ -880,7 +880,6 @@ body.lavish {
|
|
|
880
880
|
min-width: 0;
|
|
881
881
|
min-height: 0;
|
|
882
882
|
background: #fff;
|
|
883
|
-
position: relative;
|
|
884
883
|
overflow: hidden;
|
|
885
884
|
}
|
|
886
885
|
.panel {
|
|
@@ -1194,24 +1193,6 @@ body.lavish {
|
|
|
1194
1193
|
.ended-action {
|
|
1195
1194
|
margin-top: var(--space-8);
|
|
1196
1195
|
}
|
|
1197
|
-
.layout-issue-banner {
|
|
1198
|
-
position: absolute;
|
|
1199
|
-
top: var(--space-8);
|
|
1200
|
-
left: var(--space-8);
|
|
1201
|
-
right: var(--space-8);
|
|
1202
|
-
z-index: 20;
|
|
1203
|
-
border: 1px solid rgba(244, 201, 93, 0.35);
|
|
1204
|
-
border-radius: var(--radius-lg);
|
|
1205
|
-
background: rgba(37, 35, 15, 0.92);
|
|
1206
|
-
color: var(--brass-400);
|
|
1207
|
-
padding: var(--space-6) var(--space-8);
|
|
1208
|
-
font-size: var(--text-sm);
|
|
1209
|
-
line-height: var(--lh-sm);
|
|
1210
|
-
box-shadow: var(--shadow-tooltip);
|
|
1211
|
-
}
|
|
1212
|
-
.layout-issue-banner[hidden] {
|
|
1213
|
-
display: none;
|
|
1214
|
-
}
|
|
1215
1196
|
iframe {
|
|
1216
1197
|
width: 100%;
|
|
1217
1198
|
height: 100%;
|
package/dist/cli.mjs
CHANGED
|
@@ -447,7 +447,7 @@ function createDesignOutput() {
|
|
|
447
447
|
playbooks: listPlaybooks()
|
|
448
448
|
},
|
|
449
449
|
design: {
|
|
450
|
-
summary: "Use this Lavish CDN fallback only if (1) the user gave no design direction and (2) you already inspected the project the artifact is about and found no design system or style conventions to match. If you have not checked the subject project yet, check first. Lavish does not auto-inject any design system; artifacts stay portable HTML. " + DESIGN_PRIORITY_RULE + " Paste the CDN snippet below into your `<head>`.",
|
|
450
|
+
summary: "Use this Lavish CDN fallback only if (1) the user gave no design direction and (2) you already inspected the project the artifact is about and found no design system or style conventions to match. If you have not checked the subject project yet, check first. Lavish does not auto-inject any design system; artifacts stay portable HTML. Paint an explicit page background and readable text. " + DESIGN_PRIORITY_RULE + " Paste the CDN snippet below into your `<head>`.",
|
|
451
451
|
cdn_snippet: DESIGN_CDN_SNIPPET,
|
|
452
452
|
cdn_urls: DESIGN_CDN_URLS,
|
|
453
453
|
versions: { tailwind: TAILWIND_BROWSER_VERSION, daisyui: DAISYUI_VERSION },
|
|
@@ -4450,6 +4450,48 @@ function linkFailureReason(error) {
|
|
|
4450
4450
|
return message || "link creation failed";
|
|
4451
4451
|
}
|
|
4452
4452
|
|
|
4453
|
+
// src/self-paint.js
|
|
4454
|
+
var ROOT_TAG_RE = /<(?:html|body)\b([^>]*)>/gi;
|
|
4455
|
+
var STYLE_BLOCK_RE = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
4456
|
+
var CSS_RULE_RE = /([^{}]+)\{([^{}]*)\}/g;
|
|
4457
|
+
var ROOT_SELECTOR_TOKEN_RE = /(^|[\s,>~+])(html|body|:root|\*)(?![\w-])/i;
|
|
4458
|
+
var SELF_PAINT_WARNING = "This artifact never paints its own page surface: no background on html/body/:root, no bg-* class or data-theme on html/body, and no stylesheet that could set one. Lavish injects no design system, so text that assumes a dark or light host surface can render invisible. Set an explicit background and readable text.";
|
|
4459
|
+
function analyzeSelfPaint(html) {
|
|
4460
|
+
const source = typeof html === "string" ? html : "";
|
|
4461
|
+
if (/<link\b[^>]*\brel\s*=\s*["']?[^"'>]*stylesheet/i.test(source)) {
|
|
4462
|
+
return { painted: true, signal: "stylesheet-link" };
|
|
4463
|
+
}
|
|
4464
|
+
if (/<script\b[^>]*\bsrc\s*=\s*["']?[^"'>]*tailwind/i.test(source)) {
|
|
4465
|
+
return { painted: true, signal: "tailwind-runtime" };
|
|
4466
|
+
}
|
|
4467
|
+
if (/<meta\b[^>]*\bname\s*=\s*["']?color-scheme/i.test(source)) {
|
|
4468
|
+
return { painted: true, signal: "color-scheme" };
|
|
4469
|
+
}
|
|
4470
|
+
for (const [, attrs] of source.matchAll(ROOT_TAG_RE)) {
|
|
4471
|
+
if (/\bdata-theme\s*=/i.test(attrs)) return { painted: true, signal: "data-theme" };
|
|
4472
|
+
const className = attrValue(attrs, "class");
|
|
4473
|
+
if (className && /(^|[^\w-])bg-/i.test(className)) return { painted: true, signal: "background-class" };
|
|
4474
|
+
const style = attrValue(attrs, "style");
|
|
4475
|
+
if (style && /background/i.test(style)) return { painted: true, signal: "inline-background" };
|
|
4476
|
+
if (style && /color-scheme\s*:/i.test(style)) return { painted: true, signal: "color-scheme" };
|
|
4477
|
+
}
|
|
4478
|
+
for (const [, css] of source.matchAll(STYLE_BLOCK_RE)) {
|
|
4479
|
+
const stripped = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
4480
|
+
if (/@import\b/i.test(stripped)) return { painted: true, signal: "css-import" };
|
|
4481
|
+
if (/color-scheme\s*:/i.test(stripped)) return { painted: true, signal: "color-scheme" };
|
|
4482
|
+
for (const [, selector, declarations] of stripped.matchAll(CSS_RULE_RE)) {
|
|
4483
|
+
if (!/background/i.test(declarations)) continue;
|
|
4484
|
+
if (ROOT_SELECTOR_TOKEN_RE.test(selector)) return { painted: true, signal: "root-background-rule" };
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
return { painted: false, signal: null };
|
|
4488
|
+
}
|
|
4489
|
+
function attrValue(attrs, name) {
|
|
4490
|
+
const match = attrs.match(new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"));
|
|
4491
|
+
if (!match) return null;
|
|
4492
|
+
return match[1] ?? match[2] ?? match[3] ?? null;
|
|
4493
|
+
}
|
|
4494
|
+
|
|
4453
4495
|
// src/server.js
|
|
4454
4496
|
import crypto4 from "node:crypto";
|
|
4455
4497
|
import { EventEmitter } from "node:events";
|
|
@@ -8430,7 +8472,7 @@ ${faviconTag}
|
|
|
8430
8472
|
</head>
|
|
8431
8473
|
<body class="${bodyClass}">
|
|
8432
8474
|
<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><div class="warnings-wrap" id="warningsWrap" hidden><button class="warnings-button" id="warningsButton" type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="warningsDrawer">${chromeIcons.warning}<span class="warnings-count" id="warningsCount">0</span></button><div class="menu warnings-drawer" id="warningsDrawer" role="dialog" aria-labelledby="warningsTitle" aria-describedby="warningsSummary" hidden><div class="warnings-head"><h2 class="warnings-title" id="warningsTitle">Layout issues</h2><p class="warnings-summary" id="warningsSummary"></p></div><div class="warnings-toolbar"><label class="warnings-selectall"><input type="checkbox" id="warningsSelectAll"><span>Select all</span></label><span class="warnings-selected" id="warningsSelected" role="status" aria-live="polite"></span></div><div class="warnings-list" id="warningsList"></div><div class="warnings-foot"><p class="warnings-note">Queueing sends a repair request with your next feedback. An issue is marked resolved only after a newer artifact load and a complete check at the same viewport no longer finds it.</p><button class="button" id="warningsQueueButton" type="button" disabled>Queue selected fixes</button></div></div></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>
|
|
8433
|
-
<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
|
|
8475
|
+
<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><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 handoff-banner" id="handoffBanner" hidden><span>This review is open in another Lavish tab.</span><button class="handoff-takeover" id="handoffTakeover" type="button">Take over here</button></div><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 & End</span></button><button class="button" id="send">Send to Agent</button></div></div></aside></div>
|
|
8434
8476
|
<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>
|
|
8435
8477
|
<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>
|
|
8436
8478
|
<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>
|
|
@@ -8674,7 +8716,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
|
|
|
8674
8716
|
]);
|
|
8675
8717
|
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.";
|
|
8676
8718
|
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.";
|
|
8677
|
-
var VERSION = "0.1.
|
|
8719
|
+
var VERSION = "0.1.48";
|
|
8678
8720
|
function detectInvokingAgent(env = process.env) {
|
|
8679
8721
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
8680
8722
|
}
|
|
@@ -8818,10 +8860,12 @@ function createPlaybookOutput(args) {
|
|
|
8818
8860
|
}
|
|
8819
8861
|
return { playbook };
|
|
8820
8862
|
}
|
|
8821
|
-
function createOpenOutput({ file, url, status, agent = "generic" }) {
|
|
8863
|
+
function createOpenOutput({ file, url, status, agent = "generic", selfPaintWarning = void 0 }) {
|
|
8864
|
+
const selfPaintPrefix = selfPaintWarning ? `First fix the unpainted page surface flagged in self_paint_warning and save - Lavish live-reloads the artifact automatically, so you do not need to re-run \`lavish-axi ${file}\`. ` : "";
|
|
8822
8865
|
return {
|
|
8823
8866
|
session: { file, url, status },
|
|
8824
|
-
|
|
8867
|
+
...selfPaintWarning ? { self_paint_warning: selfPaintWarning } : {},
|
|
8868
|
+
next_step: `${selfPaintPrefix}Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file}\`. This command long-polls until the user sends feedback or ends the session, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use. ${pollExecutionGuidance({ agent })} After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show your response in Lavish Editor and wait for more feedback. If the user ends the session, stop polling and do not reopen it by re-running \`lavish-axi ${file}\` unless the user asks for further review or something genuinely important needs their visual attention - deliver routine updates directly in this conversation instead. When reopening is warranted, run \`lavish-axi ${file} --reopen\`.`
|
|
8825
8869
|
};
|
|
8826
8870
|
}
|
|
8827
8871
|
function createUserEndedOpenOutput({ file, url }) {
|
|
@@ -8837,6 +8881,7 @@ async function openCommand(args) {
|
|
|
8837
8881
|
}
|
|
8838
8882
|
await assertHtmlFile(file);
|
|
8839
8883
|
const absolute = await canonicalFile(file);
|
|
8884
|
+
const selfPaintWarning = await selfPaintWarningForFile(absolute);
|
|
8840
8885
|
const noGate = args.includes("--no-gate");
|
|
8841
8886
|
const reopen = args.includes("--reopen");
|
|
8842
8887
|
const baseUrl = await ensureServer({ forceRestart: shouldForceRestartForLocalBuild(process.argv[1] || "") });
|
|
@@ -8856,9 +8901,17 @@ async function openCommand(args) {
|
|
|
8856
8901
|
file: absolute,
|
|
8857
8902
|
url: response.url,
|
|
8858
8903
|
status: response.status || "opened",
|
|
8859
|
-
agent: detectInvokingAgent(process.env)
|
|
8904
|
+
agent: detectInvokingAgent(process.env),
|
|
8905
|
+
selfPaintWarning
|
|
8860
8906
|
});
|
|
8861
8907
|
}
|
|
8908
|
+
async function selfPaintWarningForFile(absolute) {
|
|
8909
|
+
try {
|
|
8910
|
+
return analyzeSelfPaint(await readFile5(absolute, "utf8")).painted ? void 0 : SELF_PAINT_WARNING;
|
|
8911
|
+
} catch {
|
|
8912
|
+
return void 0;
|
|
8913
|
+
}
|
|
8914
|
+
}
|
|
8862
8915
|
function shouldOpenBrowser(args, env) {
|
|
8863
8916
|
return !args.includes("--no-open") && env.LAVISH_AXI_NO_OPEN !== "1";
|
|
8864
8917
|
}
|
|
@@ -9020,9 +9073,15 @@ async function exportCommand(args) {
|
|
|
9020
9073
|
resolveAbsolute: resolveDesignAssetPath
|
|
9021
9074
|
});
|
|
9022
9075
|
await writeFile3(output, html);
|
|
9023
|
-
return createExportOutput({
|
|
9076
|
+
return createExportOutput({
|
|
9077
|
+
source: absolute,
|
|
9078
|
+
output,
|
|
9079
|
+
html,
|
|
9080
|
+
warnings,
|
|
9081
|
+
selfPaintWarning: analyzeSelfPaint(source).painted ? void 0 : SELF_PAINT_WARNING
|
|
9082
|
+
});
|
|
9024
9083
|
}
|
|
9025
|
-
function createExportOutput({ source, output, html, warnings }) {
|
|
9084
|
+
function createExportOutput({ source, output, html, warnings, selfPaintWarning = void 0 }) {
|
|
9026
9085
|
const allWarnings = Array.isArray(warnings) ? warnings : [];
|
|
9027
9086
|
const { unresolved, notices } = splitExportWarnings(allWarnings);
|
|
9028
9087
|
const result = {
|
|
@@ -9044,6 +9103,10 @@ function createExportOutput({ source, output, html, warnings }) {
|
|
|
9044
9103
|
} else {
|
|
9045
9104
|
result.next_step = `Wrote ${output}. Open it directly or host it anywhere - it needs no Lavish server. Local assets are inlined; remote CDN/font references are left as links, so it needs network to render those.`;
|
|
9046
9105
|
}
|
|
9106
|
+
if (selfPaintWarning) {
|
|
9107
|
+
result.self_paint_warning = selfPaintWarning;
|
|
9108
|
+
result.next_step = `Fix the unpainted page surface flagged in self_paint_warning and re-run the export before sharing the file - an exported page renders over whatever surface hosts it. ${result.next_step}`;
|
|
9109
|
+
}
|
|
9047
9110
|
return result;
|
|
9048
9111
|
}
|
|
9049
9112
|
function assetWarningSummaries(warnings) {
|
|
@@ -9066,9 +9129,15 @@ async function shareCommand(args) {
|
|
|
9066
9129
|
resolveAbsolute: resolveDesignAssetPath
|
|
9067
9130
|
});
|
|
9068
9131
|
const site = await publishToHtmlApp(html, { password, token });
|
|
9069
|
-
return createShareOutput({
|
|
9132
|
+
return createShareOutput({
|
|
9133
|
+
source: absolute,
|
|
9134
|
+
site,
|
|
9135
|
+
warnings,
|
|
9136
|
+
passwordProtected: Boolean(password),
|
|
9137
|
+
selfPaintWarning: analyzeSelfPaint(source).painted ? void 0 : SELF_PAINT_WARNING
|
|
9138
|
+
});
|
|
9070
9139
|
}
|
|
9071
|
-
function createShareOutput({ source, site, warnings, passwordProtected = false }) {
|
|
9140
|
+
function createShareOutput({ source, site, warnings, passwordProtected = false, selfPaintWarning = void 0 }) {
|
|
9072
9141
|
const allWarnings = Array.isArray(warnings) ? warnings : [];
|
|
9073
9142
|
const { unresolved, notices } = splitExportWarnings(allWarnings);
|
|
9074
9143
|
const isPasswordProtected = Boolean(passwordProtected);
|
|
@@ -9099,6 +9168,10 @@ function createShareOutput({ source, site, warnings, passwordProtected = false }
|
|
|
9099
9168
|
} else {
|
|
9100
9169
|
result.next_step = `Published a PUBLIC page that anyone with the link can view: ${site.url} - share this URL with the user. ${noticeNote ? `${noticeNote} ` : ""}The update_key is a secret shown only once; keep it to update or delete the page later (there is no recovery). ` + hostNote;
|
|
9101
9170
|
}
|
|
9171
|
+
if (selfPaintWarning) {
|
|
9172
|
+
result.self_paint_warning = selfPaintWarning;
|
|
9173
|
+
result.next_step = `Fix the unpainted page surface flagged in self_paint_warning, then re-run the share command and share only its replacement URL - the hosted page renders over ht-ml.app's own surface. ${result.next_step}`;
|
|
9174
|
+
}
|
|
9102
9175
|
return result;
|
|
9103
9176
|
}
|
|
9104
9177
|
async function stopCommand(args) {
|
package/package.json
CHANGED
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.
|
|
4
|
+
"version": "0.1.48",
|
|
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",
|
package/skills/lavish/SKILL.md
CHANGED
|
@@ -32,6 +32,7 @@ Use lavish-axi when the user asks for a visual artifact, HTML explainer, interac
|
|
|
32
32
|
|
|
33
33
|
1. Create the HTML artifact (default location `.lavish/<name>.html` in the working directory).
|
|
34
34
|
2. Run `npx -y lavish-axi <html-file>` to open or resume a review session in the browser.
|
|
35
|
+
If the output carries a `self_paint_warning`, fix the unpainted page surface and save before polling - Lavish live-reloads the artifact.
|
|
35
36
|
3. Run `npx -y lavish-axi poll <html-file>` to long-poll for the user's annotations and queued prompts.
|
|
36
37
|
On the first poll, prefer `--agent-reply "<one-line summary of what you built and what to review first>"` so the conversation panel opens with context.
|
|
37
38
|
Browser-detected layout issues are filed passively in the user's Layout issues inbox and arrive as an ordinary `layout-warnings` prompt only when the user selects and queues them. Never edit an issue the user has not queued. The only response that arrives without user action is `artifact_failures`, when the review surface itself is unusable.
|