lavish-axi 0.1.33 → 0.1.34
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 +5 -3
- package/dist/cli.mjs +208 -69
- package/package.json +1 -1
- package/skills/lavish/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -125,6 +125,7 @@ pnpm link
|
|
|
125
125
|
┌────────────────────────┐
|
|
126
126
|
│ lavish-axi poll waits │
|
|
127
127
|
│ and returns prompts │
|
|
128
|
+
│ or layout warnings │
|
|
128
129
|
└────────────────────────┘
|
|
129
130
|
```
|
|
130
131
|
|
|
@@ -138,9 +139,10 @@ pnpm link
|
|
|
138
139
|
- **Open-time layout gate** - The browser chrome masks each artifact until the real in-iframe layout audit reports no error-severity findings.
|
|
139
140
|
Warning-only artifacts reveal normally; error findings notify the agent through the same `layout_warnings` poll path and keep the curtain up until a clean reload.
|
|
140
141
|
The user can click **Show anyway**, and a bounded safety timeout reveals with a persistent layout-issues banner so review is never blocked indefinitely.
|
|
141
|
-
- **Layout warnings** - After fonts load and layout settles, the injected SDK audits the real browser render for page horizontal overflow, element overflow, clipped text, and overlapping text.
|
|
142
|
-
Intentional horizontal scrollers using `overflow-x: auto` or `scroll` are excluded.
|
|
143
|
-
|
|
142
|
+
- **Layout warnings** - After fonts load and layout settles, the injected SDK audits the real browser render for page horizontal overflow, element overflow, clipped or visibly spilling text, and overlapping text.
|
|
143
|
+
Intentional horizontal scrollers using `overflow-x: auto` or `scroll` are excluded from horizontal checks, and `overflow-y: auto` or `scroll` is treated as intentional for vertical overflow.
|
|
144
|
+
Current findings are returned from `lavish-axi poll` as `layout_warnings` with `selector`, `kind`, `overflowPx`, `viewportWidth`, `severity`, and `persistent`.
|
|
145
|
+
Fresh error-severity findings should be fixed and rechecked before asking the human to review; repeated or warning-only findings can be surfaced to the human with a note when the cause is not obvious.
|
|
144
146
|
- **Local assets** - Copy local images, CSS, fonts, and scripts next to the HTML artifact and reference them with relative paths from that directory; root-prefixed paths such as `/assets/logo.png` will not resolve through Lavish's artifact route.
|
|
145
147
|
- **Export and sharing** - `lavish-axi export` writes `<name>.export.html` by inlining local assets only, stripping the annotation SDK, and leaving remote CDN/font references as links that still need network access.
|
|
146
148
|
`lavish-axi share` publishes the same local-inlined HTML to [ht-ml.app](https://ht-ml.app), a third-party hosting service not part of Lavish.
|
package/dist/cli.mjs
CHANGED
|
@@ -4277,6 +4277,57 @@ function isNativeInteractiveControl(el) {
|
|
|
4277
4277
|
"button,input,select,textarea,option,optgroup,label,summary,[contenteditable]:not([contenteditable='false'])"
|
|
4278
4278
|
));
|
|
4279
4279
|
}
|
|
4280
|
+
function fragmentsSignificantlyOverlap(fragmentsA, fragmentsB, { minAreaRatio = 0.25, minAreaPx = 24 } = {}) {
|
|
4281
|
+
function rectAreaOf(rect) {
|
|
4282
|
+
return Math.max(0, rect.width) * Math.max(0, rect.height);
|
|
4283
|
+
}
|
|
4284
|
+
function intersectionAreaOf(a, b) {
|
|
4285
|
+
const width = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left));
|
|
4286
|
+
const height = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
|
|
4287
|
+
return width * height;
|
|
4288
|
+
}
|
|
4289
|
+
for (const a of fragmentsA) {
|
|
4290
|
+
const threshold = Math.min(rectAreaOf(a) * minAreaRatio, minAreaPx);
|
|
4291
|
+
for (const b of fragmentsB) {
|
|
4292
|
+
if (intersectionAreaOf(a, b) >= threshold) return true;
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
return false;
|
|
4296
|
+
}
|
|
4297
|
+
function classifyHorizontalOverflow({ scrollWidth, clientWidth, overflowX, hasText, isTruncated, epsilon = 1 }) {
|
|
4298
|
+
const overflowPx = clientWidth > 0 ? scrollWidth - clientWidth : 0;
|
|
4299
|
+
if (overflowPx <= epsilon) return null;
|
|
4300
|
+
const clipsText = hasText && (overflowX === "hidden" || overflowX === "clip") && !isTruncated;
|
|
4301
|
+
return { overflowPx, kind: clipsText ? "clipped-text" : "element-scroll-overflow" };
|
|
4302
|
+
}
|
|
4303
|
+
function classifyVerticalOverflow({ scrollHeight, clientHeight, overflowY, hasText, isTruncated, epsilon = 1 }) {
|
|
4304
|
+
const overflowPx = clientHeight > 0 ? scrollHeight - clientHeight : 0;
|
|
4305
|
+
if (overflowPx <= epsilon) return null;
|
|
4306
|
+
const scrollable = overflowY === "auto" || overflowY === "scroll";
|
|
4307
|
+
if (scrollable || !hasText || isTruncated) return null;
|
|
4308
|
+
const clips = overflowY === "hidden" || overflowY === "clip";
|
|
4309
|
+
return { overflowPx, kind: "clipped-text", clips };
|
|
4310
|
+
}
|
|
4311
|
+
function resolveVisibleSpillCandidates(spillCandidates, { epsilon = 1 } = {}) {
|
|
4312
|
+
function spillBottomEdge(candidate) {
|
|
4313
|
+
const explicit = Number(candidate.spillBottom);
|
|
4314
|
+
if (Number.isFinite(explicit)) return explicit;
|
|
4315
|
+
const rectBottom = Number(candidate.rect?.bottom);
|
|
4316
|
+
const overflowPx = Number(candidate.overflowPx);
|
|
4317
|
+
if (!Number.isFinite(rectBottom) || !Number.isFinite(overflowPx)) return null;
|
|
4318
|
+
return rectBottom + overflowPx;
|
|
4319
|
+
}
|
|
4320
|
+
function sameSpillEdge(candidate, other) {
|
|
4321
|
+
const candidateBottom = spillBottomEdge(candidate);
|
|
4322
|
+
const otherBottom = spillBottomEdge(other);
|
|
4323
|
+
return candidateBottom !== null && otherBottom !== null && Math.abs(candidateBottom - otherBottom) <= epsilon;
|
|
4324
|
+
}
|
|
4325
|
+
return spillCandidates.filter(
|
|
4326
|
+
(candidate) => !spillCandidates.some(
|
|
4327
|
+
(other) => other.el !== candidate.el && candidate.el.contains(other.el) && sameSpillEdge(candidate, other)
|
|
4328
|
+
)
|
|
4329
|
+
);
|
|
4330
|
+
}
|
|
4280
4331
|
function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInteractiveControl) {
|
|
4281
4332
|
let annotationMode = true;
|
|
4282
4333
|
let hovered = null;
|
|
@@ -4475,11 +4526,6 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
4475
4526
|
function rectArea(rect) {
|
|
4476
4527
|
return Math.max(0, rect.width) * Math.max(0, rect.height);
|
|
4477
4528
|
}
|
|
4478
|
-
function intersectionArea(a, b) {
|
|
4479
|
-
const width = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left));
|
|
4480
|
-
const height = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top));
|
|
4481
|
-
return width * height;
|
|
4482
|
-
}
|
|
4483
4529
|
function isVisibleForLayoutAudit(el, rect = el.getBoundingClientRect()) {
|
|
4484
4530
|
if (!el || isLavishUi(el) || rect.width <= 0 || rect.height <= 0) return false;
|
|
4485
4531
|
const style = getComputedStyle(el);
|
|
@@ -4543,31 +4589,56 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
4543
4589
|
function isIntentionalTextTruncation(style) {
|
|
4544
4590
|
return style.textOverflow === "ellipsis" || Number.parseInt(style.webkitLineClamp || "0", 10) > 0;
|
|
4545
4591
|
}
|
|
4546
|
-
function auditElementOverflow(el, viewportWidth, findings, seen) {
|
|
4592
|
+
function auditElementOverflow(el, viewportWidth, findings, seen, spillCandidates) {
|
|
4547
4593
|
if (el === document.body || el === document.documentElement || hasIntentionalHorizontalScrollerAncestor(el)) return;
|
|
4548
4594
|
const rect = el.getBoundingClientRect();
|
|
4549
4595
|
if (!isVisibleForLayoutAudit(el, rect)) return;
|
|
4550
4596
|
const style = getComputedStyle(el);
|
|
4551
|
-
const
|
|
4552
|
-
|
|
4553
|
-
|
|
4597
|
+
const hasText = hasReadableText(el);
|
|
4598
|
+
const isTruncated = isIntentionalTextTruncation(style);
|
|
4599
|
+
const horizontal = classifyHorizontalOverflow({
|
|
4600
|
+
scrollWidth: el.scrollWidth,
|
|
4601
|
+
clientWidth: el.clientWidth,
|
|
4602
|
+
overflowX: style.overflowX,
|
|
4603
|
+
hasText,
|
|
4604
|
+
isTruncated,
|
|
4605
|
+
epsilon: layoutAuditOverflowEpsilon
|
|
4606
|
+
});
|
|
4607
|
+
if (horizontal) {
|
|
4554
4608
|
pushLayoutFinding(findings, seen, {
|
|
4555
4609
|
selector: selector(el),
|
|
4556
|
-
kind:
|
|
4557
|
-
overflowPx:
|
|
4610
|
+
kind: horizontal.kind,
|
|
4611
|
+
overflowPx: horizontal.overflowPx,
|
|
4558
4612
|
viewportWidth,
|
|
4559
|
-
severity:
|
|
4613
|
+
severity: horizontal.kind === "clipped-text" ? "error" : overflowSeverity(horizontal.overflowPx)
|
|
4560
4614
|
});
|
|
4561
4615
|
}
|
|
4562
|
-
const
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4616
|
+
const vertical = classifyVerticalOverflow({
|
|
4617
|
+
scrollHeight: el.scrollHeight,
|
|
4618
|
+
clientHeight: el.clientHeight,
|
|
4619
|
+
overflowY: style.overflowY,
|
|
4620
|
+
hasText,
|
|
4621
|
+
isTruncated,
|
|
4622
|
+
epsilon: layoutAuditOverflowEpsilon
|
|
4623
|
+
});
|
|
4624
|
+
if (vertical) {
|
|
4625
|
+
if (vertical.clips) {
|
|
4626
|
+
pushLayoutFinding(findings, seen, {
|
|
4627
|
+
selector: selector(el),
|
|
4628
|
+
kind: vertical.kind,
|
|
4629
|
+
overflowPx: vertical.overflowPx,
|
|
4630
|
+
viewportWidth,
|
|
4631
|
+
severity: "error"
|
|
4632
|
+
});
|
|
4633
|
+
} else {
|
|
4634
|
+
spillCandidates.push({
|
|
4635
|
+
el,
|
|
4636
|
+
selector: selector(el),
|
|
4637
|
+
overflowPx: vertical.overflowPx,
|
|
4638
|
+
viewportWidth,
|
|
4639
|
+
spillBottom: rect.bottom + vertical.overflowPx
|
|
4640
|
+
});
|
|
4641
|
+
}
|
|
4571
4642
|
}
|
|
4572
4643
|
const parent2 = el.parentElement;
|
|
4573
4644
|
if (!parent2 || parent2 === document.body || parent2 === document.documentElement) return;
|
|
@@ -4585,36 +4656,56 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
4585
4656
|
});
|
|
4586
4657
|
}
|
|
4587
4658
|
}
|
|
4659
|
+
function resolveSpillCandidates(spillCandidates, findings, seen) {
|
|
4660
|
+
for (const candidate of resolveVisibleSpillCandidates(spillCandidates, { epsilon: layoutAuditOverflowEpsilon })) {
|
|
4661
|
+
pushLayoutFinding(findings, seen, {
|
|
4662
|
+
selector: candidate.selector,
|
|
4663
|
+
kind: "clipped-text",
|
|
4664
|
+
overflowPx: candidate.overflowPx,
|
|
4665
|
+
viewportWidth: candidate.viewportWidth,
|
|
4666
|
+
severity: "error"
|
|
4667
|
+
});
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
function elementLineFragments(el) {
|
|
4671
|
+
const rects = [...el.getClientRects()].filter((r) => r.width > 0 && r.height > 0);
|
|
4672
|
+
if (rects.length) return rects;
|
|
4673
|
+
const rect = el.getBoundingClientRect();
|
|
4674
|
+
return rect.width > 0 && rect.height > 0 ? [rect] : [];
|
|
4675
|
+
}
|
|
4588
4676
|
function auditOverlappingText(elements, viewportWidth, findings, seen) {
|
|
4589
|
-
const candidates = elements.filter((el) => el.children.length === 0 && hasReadableText(el)).filter((el) => isVisibleForLayoutAudit(el)).slice(0, 200);
|
|
4677
|
+
const candidates = elements.filter((el) => el.children.length === 0 && hasReadableText(el)).filter((el) => isVisibleForLayoutAudit(el)).filter((el) => getComputedStyle(el).position === "static").slice(0, 200);
|
|
4590
4678
|
for (const el of candidates) {
|
|
4591
|
-
const
|
|
4592
|
-
|
|
4593
|
-
const
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
continue;
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4679
|
+
const fragments = elementLineFragments(el);
|
|
4680
|
+
let flagged = false;
|
|
4681
|
+
for (const rect of fragments) {
|
|
4682
|
+
if (flagged) break;
|
|
4683
|
+
if (rectArea(rect) < 16) continue;
|
|
4684
|
+
const points = [
|
|
4685
|
+
{ x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 },
|
|
4686
|
+
{ x: rect.left + Math.min(4, rect.width / 2), y: rect.top + Math.min(4, rect.height / 2) },
|
|
4687
|
+
{ x: rect.right - Math.min(4, rect.width / 2), y: rect.bottom - Math.min(4, rect.height / 2) }
|
|
4688
|
+
];
|
|
4689
|
+
for (const point of points) {
|
|
4690
|
+
if (point.x < 0 || point.y < 0 || point.x > viewportWidth || point.y > window.innerHeight) continue;
|
|
4691
|
+
const top = document.elementFromPoint(point.x, point.y);
|
|
4692
|
+
if (!(top instanceof Element) || top === el || el.contains(top) || top.contains(el) || isLavishUi(top))
|
|
4693
|
+
continue;
|
|
4694
|
+
if (hasIntentionalHorizontalScrollerAncestor(top)) continue;
|
|
4695
|
+
if (getComputedStyle(top).position !== "static") continue;
|
|
4696
|
+
if (!fragmentsSignificantlyOverlap([rect], elementLineFragments(top))) continue;
|
|
4697
|
+
pushLayoutFinding(findings, seen, {
|
|
4698
|
+
selector: selector(el),
|
|
4699
|
+
kind: "overlapping-text",
|
|
4700
|
+
overflowPx: 0,
|
|
4701
|
+
viewportWidth,
|
|
4702
|
+
// Heuristic and sampling-based even after fragment-aware matching, so it stays a
|
|
4703
|
+
// warning rather than holding the open-time gate the way a real clip/overflow does.
|
|
4704
|
+
severity: "warning"
|
|
4705
|
+
});
|
|
4706
|
+
flagged = true;
|
|
4707
|
+
break;
|
|
4708
|
+
}
|
|
4618
4709
|
}
|
|
4619
4710
|
}
|
|
4620
4711
|
}
|
|
@@ -4633,7 +4724,9 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
4633
4724
|
});
|
|
4634
4725
|
}
|
|
4635
4726
|
const elements = collectLayoutAuditElements();
|
|
4636
|
-
|
|
4727
|
+
const spillCandidates = [];
|
|
4728
|
+
for (const el of elements) auditElementOverflow(el, viewportWidth, findings, seen, spillCandidates);
|
|
4729
|
+
resolveSpillCandidates(spillCandidates, findings, seen);
|
|
4637
4730
|
auditOverlappingText(elements, viewportWidth, findings, seen);
|
|
4638
4731
|
return findings;
|
|
4639
4732
|
}
|
|
@@ -4918,6 +5011,7 @@ var SessionStore = class {
|
|
|
4918
5011
|
pending_prompts: existing.pending_prompts || 0,
|
|
4919
5012
|
prompts: existingPrompts,
|
|
4920
5013
|
layout_warnings: [],
|
|
5014
|
+
delivered_layout_warning_keys: existing.delivered_layout_warning_keys || [],
|
|
4921
5015
|
dom_snapshot: existing.dom_snapshot || "",
|
|
4922
5016
|
chat: existing.chat || [],
|
|
4923
5017
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4950,13 +5044,23 @@ var SessionStore = class {
|
|
|
4950
5044
|
if (!session) {
|
|
4951
5045
|
return null;
|
|
4952
5046
|
}
|
|
4953
|
-
const
|
|
5047
|
+
const deliveredWarningKeys = session.delivered_layout_warning_keys || [];
|
|
5048
|
+
const deliveredKeys = new Set(deliveredWarningKeys);
|
|
5049
|
+
const layoutWarnings = normalizeLayoutWarnings(
|
|
5050
|
+
payload.layout_warnings || payload.layoutWarnings || [],
|
|
5051
|
+
deliveredKeys
|
|
5052
|
+
);
|
|
5053
|
+
const activeWarningKeys = new Set(layoutWarnings.map(layoutWarningKey));
|
|
5054
|
+
const nextDeliveredWarningKeys = deliveredWarningKeys.filter((key2) => activeWarningKeys.has(key2)).slice(-200);
|
|
5055
|
+
const deliveredKeysChanged = nextDeliveredWarningKeys.length !== deliveredWarningKeys.length || nextDeliveredWarningKeys.some((key2, index) => key2 !== deliveredWarningKeys[index]);
|
|
4954
5056
|
const previousSignature = JSON.stringify(session.layout_warnings || []);
|
|
4955
5057
|
const nextSignature = JSON.stringify(layoutWarnings);
|
|
4956
|
-
|
|
5058
|
+
const warningsChanged = previousSignature !== nextSignature;
|
|
5059
|
+
if (!warningsChanged && !deliveredKeysChanged) {
|
|
4957
5060
|
return { session, changed: false, hasWarnings: layoutWarnings.length > 0 };
|
|
4958
5061
|
}
|
|
4959
5062
|
session.layout_warnings = layoutWarnings;
|
|
5063
|
+
session.delivered_layout_warning_keys = nextDeliveredWarningKeys;
|
|
4960
5064
|
if (layoutWarnings.length > 0 && session.status !== "ended") {
|
|
4961
5065
|
session.status = "feedback";
|
|
4962
5066
|
} else if ((session.prompts || []).length === 0 && session.status !== "ended") {
|
|
@@ -4964,7 +5068,7 @@ var SessionStore = class {
|
|
|
4964
5068
|
}
|
|
4965
5069
|
session.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4966
5070
|
await this.writeState(state);
|
|
4967
|
-
return { session, changed:
|
|
5071
|
+
return { session, changed: warningsChanged, hasWarnings: layoutWarnings.length > 0 };
|
|
4968
5072
|
}
|
|
4969
5073
|
async takeFeedback(key) {
|
|
4970
5074
|
const state = await this.readState();
|
|
@@ -4987,6 +5091,11 @@ var SessionStore = class {
|
|
|
4987
5091
|
session.layout_warnings = [];
|
|
4988
5092
|
session.pending_prompts = 0;
|
|
4989
5093
|
session.dom_snapshot = "";
|
|
5094
|
+
if (layoutWarnings.length > 0) {
|
|
5095
|
+
const deliveredKeys = new Set(session.delivered_layout_warning_keys || []);
|
|
5096
|
+
for (const warning of layoutWarnings) deliveredKeys.add(layoutWarningKey(warning));
|
|
5097
|
+
session.delivered_layout_warning_keys = [...deliveredKeys].slice(-200);
|
|
5098
|
+
}
|
|
4990
5099
|
if (session.status !== "ended") {
|
|
4991
5100
|
session.status = "open";
|
|
4992
5101
|
}
|
|
@@ -5052,15 +5161,23 @@ function normalizePrompt(prompt) {
|
|
|
5052
5161
|
if (target) normalized.target = target;
|
|
5053
5162
|
return normalized;
|
|
5054
5163
|
}
|
|
5055
|
-
function
|
|
5164
|
+
function layoutWarningKey(warning) {
|
|
5165
|
+
return `${warning.kind}:${warning.selector}`;
|
|
5166
|
+
}
|
|
5167
|
+
function normalizeLayoutWarnings(layoutWarnings, deliveredKeys = /* @__PURE__ */ new Set()) {
|
|
5056
5168
|
if (!Array.isArray(layoutWarnings)) return [];
|
|
5057
|
-
return layoutWarnings.filter((warning) => warning && typeof warning === "object" && !Array.isArray(warning)).map((warning) =>
|
|
5058
|
-
selector
|
|
5059
|
-
kind
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5169
|
+
return layoutWarnings.filter((warning) => warning && typeof warning === "object" && !Array.isArray(warning)).map((warning) => {
|
|
5170
|
+
const selector = String(warning.selector || "");
|
|
5171
|
+
const kind = String(warning.kind || "layout-warning");
|
|
5172
|
+
return {
|
|
5173
|
+
selector,
|
|
5174
|
+
kind,
|
|
5175
|
+
overflowPx: normalizeFiniteNumber(warning.overflowPx),
|
|
5176
|
+
viewportWidth: normalizeFiniteNumber(warning.viewportWidth),
|
|
5177
|
+
severity: warning.severity === "warning" ? "warning" : "error",
|
|
5178
|
+
persistent: deliveredKeys.has(layoutWarningKey({ kind, selector }))
|
|
5179
|
+
};
|
|
5180
|
+
});
|
|
5064
5181
|
}
|
|
5065
5182
|
function normalizeFiniteNumber(value) {
|
|
5066
5183
|
const number = Number(value);
|
|
@@ -5818,6 +5935,10 @@ const key=${JSON.stringify(key)};
|
|
|
5818
5935
|
void key;
|
|
5819
5936
|
const deriveQueueKey=${deriveLavishQueueKey.toString()};
|
|
5820
5937
|
const isNativeInteractiveControl=${isNativeInteractiveControl.toString()};
|
|
5938
|
+
const fragmentsSignificantlyOverlap=${fragmentsSignificantlyOverlap.toString()};
|
|
5939
|
+
const resolveVisibleSpillCandidates=${resolveVisibleSpillCandidates.toString()};
|
|
5940
|
+
const classifyHorizontalOverflow=${classifyHorizontalOverflow.toString()};
|
|
5941
|
+
const classifyVerticalOverflow=${classifyVerticalOverflow.toString()};
|
|
5821
5942
|
(${createArtifactSdk.toString()})(deriveQueueKey, isNativeInteractiveControl);
|
|
5822
5943
|
})();`;
|
|
5823
5944
|
}
|
|
@@ -6004,7 +6125,7 @@ function normalizePagePath(path6) {
|
|
|
6004
6125
|
var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "stop", "server", "playbook", "design", "setup", "export", "share"]);
|
|
6005
6126
|
var RESERVED = new Set(RESERVED_COMMANDS);
|
|
6006
6127
|
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`.";
|
|
6007
|
-
var VERSION = "0.1.
|
|
6128
|
+
var VERSION = "0.1.34";
|
|
6008
6129
|
async function run(argv) {
|
|
6009
6130
|
await ensureStateDir();
|
|
6010
6131
|
const normalizedArgv = normalizeArgv(argv);
|
|
@@ -6099,7 +6220,7 @@ function createHomeOutput({ bin, sessions, includeSessions = true }) {
|
|
|
6099
6220
|
"Run `lavish-axi <html-file>` to open or resume a Lavish Editor session",
|
|
6100
6221
|
"Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
|
|
6101
6222
|
"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",
|
|
6102
|
-
"Run `lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix layout_warnings before involving the human. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost",
|
|
6223
|
+
"Run `lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; if the poll says every current warning is persistent or low-severity, proceed with a note instead of looping. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost",
|
|
6103
6224
|
"Run `lavish-axi end <html-file>` to end a session",
|
|
6104
6225
|
"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",
|
|
6105
6226
|
"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",
|
|
@@ -6129,7 +6250,7 @@ function createPlaybookOutput(args) {
|
|
|
6129
6250
|
function createOpenOutput({ file, url, status }) {
|
|
6130
6251
|
return {
|
|
6131
6252
|
session: { file, url, status },
|
|
6132
|
-
next_step: `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, ends the session, or the real browser reports layout_warnings from the in-iframe layout audit, and it stays silent the whole time - that is normal, never kill it. If layout_warnings arrive, fix overflow
|
|
6253
|
+
next_step: `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, ends the session, or the real browser reports layout_warnings from the in-iframe layout audit, and it stays silent the whole time - that is normal, never kill it. If layout_warnings arrive, follow the poll response's next_step: fix and re-check fresh error-severity overflow or clipped-text findings before involving the human, but persistent or low-severity warnings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if the poll still gets killed or times out, just re-run it - queued feedback is never lost. 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.`
|
|
6133
6254
|
};
|
|
6134
6255
|
}
|
|
6135
6256
|
async function openCommand(args) {
|
|
@@ -6234,7 +6355,7 @@ function createPollOutput({ file, response }) {
|
|
|
6234
6355
|
dom_snapshot: response.dom_snapshot || "",
|
|
6235
6356
|
prompts: response.prompts || [],
|
|
6236
6357
|
...layoutWarnings.length > 0 ? { layout_warnings: layoutWarnings } : {},
|
|
6237
|
-
next_step: createFeedbackNextStep(file, layoutWarnings
|
|
6358
|
+
next_step: createFeedbackNextStep(file, layoutWarnings)
|
|
6238
6359
|
};
|
|
6239
6360
|
}
|
|
6240
6361
|
if (response.status === "ended") {
|
|
@@ -6245,10 +6366,28 @@ function createPollOutput({ file, response }) {
|
|
|
6245
6366
|
next_step: `No user feedback arrived before the optional timeout. Run \`lavish-axi poll ${file}\` without --timeout-ms to wait indefinitely - queued feedback is never lost, so re-running the poll is always safe.`
|
|
6246
6367
|
};
|
|
6247
6368
|
}
|
|
6248
|
-
function createFeedbackNextStep(file,
|
|
6249
|
-
const
|
|
6369
|
+
function createFeedbackNextStep(file, layoutWarnings) {
|
|
6370
|
+
const count = layoutWarnings.length;
|
|
6371
|
+
const layoutPrefix = count > 0 ? layoutWarningsPrefix(file, layoutWarnings) : `Apply the requested changes to ${file}. `;
|
|
6250
6372
|
return `${layoutPrefix}Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms unless the user ended the session. The poll waits silently until the user sends more feedback, ends the session, or reports fresh layout_warnings - never kill it. If your harness limits how long a foreground command may run, run the poll as a background task; if it still gets killed or times out, just re-run it - queued feedback is never lost.`;
|
|
6251
6373
|
}
|
|
6374
|
+
function layoutWarningsPrefix(file, layoutWarnings) {
|
|
6375
|
+
const count = layoutWarnings.length;
|
|
6376
|
+
const plural = count === 1 ? "" : "s";
|
|
6377
|
+
const allPersistent = layoutWarnings.every((warning) => warning.persistent);
|
|
6378
|
+
const allLowSeverity = layoutWarnings.every((warning) => warning.severity !== "error");
|
|
6379
|
+
const allRepeatOrLowSeverity = layoutWarnings.every((warning) => warning.persistent || warning.severity !== "error");
|
|
6380
|
+
if (allPersistent) {
|
|
6381
|
+
return `${count} layout warning${plural} detected, and every one was already reported in a prior poll and is still unresolved - if you already attempted a fix, it is fine to proceed to the human with a short note about what remains instead of looping further edits and reloads. `;
|
|
6382
|
+
}
|
|
6383
|
+
if (allLowSeverity) {
|
|
6384
|
+
return `${count} low-severity layout warning${plural} detected (no error-severity findings) - fix them if the cause is obvious in ${file}, otherwise it is fine to proceed to the human with a note instead of iterating further. `;
|
|
6385
|
+
}
|
|
6386
|
+
if (allRepeatOrLowSeverity) {
|
|
6387
|
+
return `${count} layout warning${plural} detected, with no fresh error-severity findings - fix any obvious low-severity issue in ${file}, otherwise it is fine to proceed to the human with a note instead of iterating further. `;
|
|
6388
|
+
}
|
|
6389
|
+
return `${count} layout warning${plural} detected - fix horizontal overflow or clipped text in ${file}, then reload or re-open the artifact and re-check before involving the human. `;
|
|
6390
|
+
}
|
|
6252
6391
|
async function endCommand(args) {
|
|
6253
6392
|
const file = firstPositionalArg(args);
|
|
6254
6393
|
if (!file) {
|
|
@@ -6783,7 +6922,7 @@ Usage:
|
|
|
6783
6922
|
|
|
6784
6923
|
${DESIGN_SYSTEM_HINT}
|
|
6785
6924
|
|
|
6786
|
-
Note: poll long-polls indefinitely by default until the user sends feedback, ends the session, or the browser reports fresh layout_warnings, staying silent while it waits - never kill it. Fix layout_warnings before involving the human. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost.
|
|
6925
|
+
Note: poll long-polls indefinitely by default until the user sends feedback, ends the session, or the browser reports fresh layout_warnings, staying silent while it waits - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; persistent or low-severity findings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost.
|
|
6787
6926
|
|
|
6788
6927
|
`;
|
|
6789
6928
|
var COMMAND_HELP = {
|
|
@@ -6793,7 +6932,7 @@ Open or resume a Lavish Editor review session for an HTML artifact. Use --no-ope
|
|
|
6793
6932
|
`,
|
|
6794
6933
|
poll: `Usage: lavish-axi poll <html-file> [--agent-reply "..."]
|
|
6795
6934
|
|
|
6796
|
-
This command long-polls indefinitely for queued user prompts and browser-reported layout_warnings, then returns them to the agent. It stays silent while it waits - that is normal, never kill it. Fix layout_warnings before involving the human. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if it still gets killed or times out, just re-run it - queued feedback is never lost. Use --agent-reply after applying prior feedback to display your response in Lavish Editor before waiting again.
|
|
6935
|
+
This command long-polls indefinitely for queued user prompts and browser-reported layout_warnings, then returns them to the agent. It stays silent while it waits - that is normal, never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; persistent or low-severity findings may be surfaced with a note when the cause is not obvious. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. If your harness limits how long a foreground command may run, run the poll as a background task and wait for it to finish; if it still gets killed or times out, just re-run it - queued feedback is never lost. Use --agent-reply after applying prior feedback to display your response in Lavish Editor before waiting again.
|
|
6797
6936
|
`,
|
|
6798
6937
|
end: `Usage: lavish-axi end <html-file>
|
|
6799
6938
|
|
package/package.json
CHANGED
package/skills/lavish/SKILL.md
CHANGED
|
@@ -34,7 +34,7 @@ Use lavish-axi when the user asks for a visual artifact, HTML explainer, interac
|
|
|
34
34
|
3. Run `npx -y lavish-axi poll <html-file>` to long-poll for the user's annotations, queued prompts, and browser-reported `layout_warnings`.
|
|
35
35
|
The poll stays silent until the user acts or the real browser reports fresh layout warnings - leave it running, never kill it.
|
|
36
36
|
If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost.
|
|
37
|
-
4. If poll returns `layout_warnings`, fix
|
|
37
|
+
4. If poll returns `layout_warnings`, follow the returned `next_step`: fix and re-check fresh error-severity findings, but proceed with a note instead of looping when every current warning is persistent or low-severity.
|
|
38
38
|
5. Apply human feedback, then poll again with `--agent-reply "<message>"` to reply in the browser and keep the loop going.
|
|
39
39
|
6. Run `npx -y lavish-axi end <html-file>` when the review is finished.
|
|
40
40
|
|
|
@@ -65,7 +65,7 @@ For flows, architecture, state, or sequence diagrams, do not hand-build boxes-an
|
|
|
65
65
|
- Run `npx -y lavish-axi <html-file>` to open or resume a Lavish Editor session
|
|
66
66
|
- Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`
|
|
67
67
|
- 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
|
|
68
|
-
- Run `npx -y lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix layout_warnings before involving the human. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost
|
|
68
|
+
- Run `npx -y lavish-axi poll <html-file>` to wait for user feedback or browser-reported layout_warnings. It long-polls and stays silent until the user sends feedback, ends the session, or the real browser reports fresh layout_warnings, so leave it running - never kill it. Fix and re-check fresh error-severity layout_warnings before involving the human; if the poll says every current warning is persistent or low-severity, proceed with a note instead of looping. If your harness limits how long a foreground command may run, run the poll as a background task; if it gets killed or times out anyway, just re-run it - queued feedback is never lost
|
|
69
69
|
- Run `npx -y lavish-axi end <html-file>` to end a session
|
|
70
70
|
- 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
|
|
71
71
|
- 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
|