lavish-axi 0.1.55 → 0.1.56
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 -1
- package/dist/chrome-client.js +214 -1
- package/dist/chrome.css +234 -8
- package/dist/cli.mjs +6 -4
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -194,8 +194,9 @@ pnpm link
|
|
|
194
194
|
- **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) are interactive automatically, so they do not need `data-lavish-action`.
|
|
195
195
|
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()`.
|
|
196
196
|
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.
|
|
197
|
-
|
|
197
|
+
On wider screens, 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.
|
|
198
198
|
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.
|
|
199
|
+
- **Reviewing on a phone** - Below 860px wide, the artifact takes the whole screen above a **Conversation** dock, and the conversation opens as a bottom sheet over it: tap the dock, swipe it up, or press the chevron to raise it; tap the dimmed artifact, swipe the sheet down, press the chevron, or press Escape to lower it. The dock reports what matters while the sheet is down - how many prompts are queued, a reply that arrived while you were reading, or whether the agent is listening - and the sheet stays open across a reload of the review page. The sheet sizes itself to the visible viewport and respects safe-area insets; if the keyboard or attachments leave little room, conversation content yields or scrolls while the send actions remain pinned above the bottom edge. In landscape the sheet covers the top bar as well. Wider screens keep the side-by-side layout.
|
|
199
200
|
- **Keyboard shortcuts** - In the chrome composer, Enter sends queued prompts and Shift+Enter inserts a newline.
|
|
200
201
|
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.
|
|
201
202
|
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.
|
package/dist/chrome-client.js
CHANGED
|
@@ -108,6 +108,11 @@ const chatAttachments = /** @type {HTMLDivElement} */ (document.getElementById("
|
|
|
108
108
|
const chatAttachButton = /** @type {HTMLButtonElement} */ (document.getElementById("chatAttach"));
|
|
109
109
|
const chatAttachInput = /** @type {HTMLInputElement} */ (document.getElementById("chatAttachInput"));
|
|
110
110
|
const chatAttachmentNotice = /** @type {HTMLSpanElement} */ (document.getElementById("chatAttachmentNotice"));
|
|
111
|
+
const panel = /** @type {HTMLElement} */ (document.getElementById("panel"));
|
|
112
|
+
const panelHead = /** @type {HTMLDivElement} */ (document.getElementById("panelHead"));
|
|
113
|
+
const panelSummary = /** @type {HTMLSpanElement} */ (document.getElementById("panelSummary"));
|
|
114
|
+
const panelToggle = /** @type {HTMLButtonElement} */ (document.getElementById("panelToggle"));
|
|
115
|
+
const panelScrim = /** @type {HTMLDivElement} */ (document.getElementById("panelScrim"));
|
|
111
116
|
const sendButton = /** @type {HTMLButtonElement} */ (document.getElementById("send"));
|
|
112
117
|
const sendAndEndButton = /** @type {HTMLButtonElement} */ (document.getElementById("sendAndEnd"));
|
|
113
118
|
const annotationSwitch = /** @type {HTMLButtonElement} */ (document.getElementById("annotation"));
|
|
@@ -400,6 +405,7 @@ function render() {
|
|
|
400
405
|
}
|
|
401
406
|
updateSendState();
|
|
402
407
|
scrollPanelToBottom();
|
|
408
|
+
renderSheetSummary();
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
function updateSendState() {
|
|
@@ -540,6 +546,7 @@ function syncChat(chat) {
|
|
|
540
546
|
function setAgentPresence(state) {
|
|
541
547
|
agentPresence = state === "listening" || state === "working" ? state : "waiting";
|
|
542
548
|
updateSendState();
|
|
549
|
+
renderSheetSummary();
|
|
543
550
|
if (presenceBanner) presenceBanner.hidden = ended || agentPresence !== "waiting";
|
|
544
551
|
|
|
545
552
|
if (agentPresence !== "working") {
|
|
@@ -691,6 +698,198 @@ function scrollPanelToBottom() {
|
|
|
691
698
|
panelScroll.scrollTop = panelScroll.scrollHeight;
|
|
692
699
|
}
|
|
693
700
|
|
|
701
|
+
// ---- Phone-width conversation sheet ----
|
|
702
|
+
// Below this width chrome.css turns the conversation panel into a dock the user raises as a
|
|
703
|
+
// bottom sheet over the artifact. This controller owns intent, accessibility state, gestures,
|
|
704
|
+
// visual-viewport measurements, and the dock summary; CSS owns the geometry. The query must match
|
|
705
|
+
// the one chrome.css lays the sheet out under.
|
|
706
|
+
const MOBILE_SHEET_MEDIA = "(max-width: 860px)";
|
|
707
|
+
// How far a drag on the dock must travel before it counts as a gesture rather than a tap.
|
|
708
|
+
const SHEET_DRAG_THRESHOLD_PX = 48;
|
|
709
|
+
const sheetStorageKey = "lavish-axi:sheet-open:" + key;
|
|
710
|
+
const sheetMedia = typeof window.matchMedia === "function" ? window.matchMedia(MOBILE_SHEET_MEDIA) : null;
|
|
711
|
+
// The user's intent, kept across a chrome reload so a live-reload or server upgrade does not drop
|
|
712
|
+
// them back onto a closed dock mid-conversation.
|
|
713
|
+
let sheetOpen = readSheetOpen();
|
|
714
|
+
// The latest agent reply that landed while the sheet was closed: the dock previews it until the
|
|
715
|
+
// user opens the sheet, so a reply never arrives silently behind the artifact.
|
|
716
|
+
let unreadAgentReply = "";
|
|
717
|
+
/** @type {{ pointerId: any, startY: number, moved: boolean } | null} */
|
|
718
|
+
let sheetDrag = null;
|
|
719
|
+
let suppressSheetClick = false;
|
|
720
|
+
|
|
721
|
+
function readSheetOpen() {
|
|
722
|
+
try {
|
|
723
|
+
return sessionStorage.getItem(sheetStorageKey) === "1";
|
|
724
|
+
} catch {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function isMobileSheet() {
|
|
730
|
+
return Boolean(sheetMedia && sheetMedia.matches);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function setSheetOpen(open) {
|
|
734
|
+
const next = Boolean(open);
|
|
735
|
+
const changed = next !== sheetOpen;
|
|
736
|
+
sheetOpen = next;
|
|
737
|
+
try {
|
|
738
|
+
if (sheetOpen) sessionStorage.setItem(sheetStorageKey, "1");
|
|
739
|
+
else sessionStorage.removeItem(sheetStorageKey);
|
|
740
|
+
} catch {
|
|
741
|
+
// Storage refused is not worth a broken sheet: the state just stops surviving a reload.
|
|
742
|
+
}
|
|
743
|
+
if (sheetOpen) unreadAgentReply = "";
|
|
744
|
+
applySheetState();
|
|
745
|
+
if (!changed || !isMobileSheet()) return;
|
|
746
|
+
if (sheetOpen) scrollPanelToBottom();
|
|
747
|
+
}
|
|
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.
|
|
752
|
+
function applySheetState() {
|
|
753
|
+
const mobile = isMobileSheet();
|
|
754
|
+
const open = mobile && sheetOpen;
|
|
755
|
+
document.body.classList.toggle("sheet-open", open);
|
|
756
|
+
const docked = mobile && !open;
|
|
757
|
+
panelScroll.inert = docked;
|
|
758
|
+
chatComposer.inert = docked;
|
|
759
|
+
const activeElement = document.activeElement;
|
|
760
|
+
if (docked && activeElement && (panelScroll.contains(activeElement) || chatComposer.contains(activeElement))) {
|
|
761
|
+
panelToggle.focus();
|
|
762
|
+
}
|
|
763
|
+
panelToggle.setAttribute("aria-expanded", open ? "true" : "false");
|
|
764
|
+
panelToggle.setAttribute("aria-label", open ? "Hide conversation" : "Show conversation");
|
|
765
|
+
renderSheetSummary();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// What the closed dock says. One line, most actionable state first: work the user has queued,
|
|
769
|
+
// then a reply they have not seen, then whether the agent is there to receive a send.
|
|
770
|
+
function sheetSummary() {
|
|
771
|
+
if (ended) return { text: "Session ended", accent: false, unread: false };
|
|
772
|
+
if (queued.length > 0) {
|
|
773
|
+
return { text: queued.length === 1 ? "1 queued" : queued.length + " queued", accent: true, unread: false };
|
|
774
|
+
}
|
|
775
|
+
if (unreadAgentReply) return { text: unreadAgentReply, accent: false, unread: true };
|
|
776
|
+
if (agentPresence === "working") return { text: "Agent is working…", accent: false, unread: false };
|
|
777
|
+
if (agentPresence === "listening") return { text: "Agent listening", accent: false, unread: false };
|
|
778
|
+
return { text: "Agent not listening", accent: false, unread: false };
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function renderSheetSummary() {
|
|
782
|
+
const summary = sheetSummary();
|
|
783
|
+
panelSummary.textContent = summary.text;
|
|
784
|
+
panelSummary.classList.toggle("is-accent", summary.accent);
|
|
785
|
+
panelSummary.classList.toggle("is-unread", summary.unread);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// A brief pulse on the dock when something the user should notice lands while the sheet is
|
|
789
|
+
// closed: a prompt they queued from the artifact, or an agent reply.
|
|
790
|
+
function pulseSheetDock() {
|
|
791
|
+
if (!isMobileSheet() || sheetOpen) return;
|
|
792
|
+
panelHead.classList.remove("is-fresh");
|
|
793
|
+
// Restart the animation even when the previous pulse is still running.
|
|
794
|
+
void panelHead.offsetWidth;
|
|
795
|
+
panelHead.classList.add("is-fresh");
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function noteAgentReply(text) {
|
|
799
|
+
if (!isMobileSheet() || sheetOpen) return;
|
|
800
|
+
unreadAgentReply = String(text || "");
|
|
801
|
+
renderSheetSummary();
|
|
802
|
+
pulseSheetDock();
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// The phone keyboard shrinks the visual viewport without touching the layout viewport on iOS, so
|
|
806
|
+
// the sheet reads its height and offset from here; chrome.css consumes these only under the phone
|
|
807
|
+
// breakpoint. Android reports the same numbers through the layout viewport thanks to the
|
|
808
|
+
// `interactive-widget=resizes-content` viewport meta, which makes this a no-op there.
|
|
809
|
+
function syncVisualViewport() {
|
|
810
|
+
const root = document.documentElement;
|
|
811
|
+
if (!root || !root.style || typeof root.style.setProperty !== "function") return;
|
|
812
|
+
const viewport = window.visualViewport;
|
|
813
|
+
const height = viewport ? viewport.height : window.innerHeight;
|
|
814
|
+
const top = viewport ? viewport.offsetTop : 0;
|
|
815
|
+
if (!(height > 0)) return;
|
|
816
|
+
root.style.setProperty("--vv-height", Math.round(height) + "px");
|
|
817
|
+
root.style.setProperty("--vv-top", Math.round(Math.max(0, top || 0)) + "px");
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function sheetDragOffset(event) {
|
|
821
|
+
return sheetDrag ? Number(event.clientY) - sheetDrag.startY : 0;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function clearSheetDrag() {
|
|
825
|
+
sheetDrag = null;
|
|
826
|
+
panel.classList.remove("is-dragging");
|
|
827
|
+
panel.style.transform = "";
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function finishSheetDrag(event) {
|
|
831
|
+
if (!sheetDrag || event.pointerId !== sheetDrag.pointerId) return;
|
|
832
|
+
const offset = sheetDragOffset(event);
|
|
833
|
+
const moved = sheetDrag.moved;
|
|
834
|
+
clearSheetDrag();
|
|
835
|
+
if (!moved) return;
|
|
836
|
+
// The click that follows a completed drag must not undo what the drag decided.
|
|
837
|
+
suppressSheetClick = true;
|
|
838
|
+
if (sheetOpen && offset > SHEET_DRAG_THRESHOLD_PX) setSheetOpen(false);
|
|
839
|
+
else if (!sheetOpen && offset < -SHEET_DRAG_THRESHOLD_PX) setSheetOpen(true);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
panelHead.addEventListener("click", () => {
|
|
843
|
+
if (!isMobileSheet()) return;
|
|
844
|
+
if (suppressSheetClick) {
|
|
845
|
+
suppressSheetClick = false;
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
setSheetOpen(!sheetOpen);
|
|
849
|
+
});
|
|
850
|
+
panelScrim.addEventListener("click", () => setSheetOpen(false));
|
|
851
|
+
panelHead.addEventListener("pointerdown", (event) => {
|
|
852
|
+
if (!isMobileSheet() || event.button) return;
|
|
853
|
+
sheetDrag = { pointerId: event.pointerId, startY: Number(event.clientY), moved: false };
|
|
854
|
+
if (typeof panelHead.setPointerCapture === "function") panelHead.setPointerCapture(event.pointerId);
|
|
855
|
+
});
|
|
856
|
+
panelHead.addEventListener("pointermove", (event) => {
|
|
857
|
+
if (!sheetDrag || event.pointerId !== sheetDrag.pointerId) return;
|
|
858
|
+
const offset = sheetDragOffset(event);
|
|
859
|
+
if (Math.abs(offset) > 6) sheetDrag.moved = true;
|
|
860
|
+
if (!sheetDrag.moved) return;
|
|
861
|
+
panel.classList.add("is-dragging");
|
|
862
|
+
// Follow the finger: an open sheet only moves down, a closed dock only up.
|
|
863
|
+
panel.style.transform = sheetOpen
|
|
864
|
+
? "translateY(" + Math.max(0, offset) + "px)"
|
|
865
|
+
: "translateY(calc(100% - var(--dock-h) - env(safe-area-inset-bottom, 0px) + " + Math.min(0, offset) + "px))";
|
|
866
|
+
});
|
|
867
|
+
panelHead.addEventListener("pointerup", finishSheetDrag);
|
|
868
|
+
panelHead.addEventListener("pointercancel", (event) => {
|
|
869
|
+
if (!sheetDrag || event.pointerId !== sheetDrag.pointerId) return;
|
|
870
|
+
clearSheetDrag();
|
|
871
|
+
suppressSheetClick = false;
|
|
872
|
+
});
|
|
873
|
+
if (sheetMedia && typeof sheetMedia.addEventListener === "function") {
|
|
874
|
+
sheetMedia.addEventListener("change", (event) => {
|
|
875
|
+
if (!event.matches) {
|
|
876
|
+
sheetOpen = false;
|
|
877
|
+
try {
|
|
878
|
+
sessionStorage.removeItem(sheetStorageKey);
|
|
879
|
+
} catch {
|
|
880
|
+
// Storage refusal only prevents persistence; the in-memory state is already reset.
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
applySheetState();
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
if (window.visualViewport && typeof window.visualViewport.addEventListener === "function") {
|
|
887
|
+
window.visualViewport.addEventListener("resize", syncVisualViewport);
|
|
888
|
+
window.visualViewport.addEventListener("scroll", syncVisualViewport);
|
|
889
|
+
}
|
|
890
|
+
window.addEventListener("resize", syncVisualViewport);
|
|
891
|
+
syncVisualViewport();
|
|
892
|
+
|
|
694
893
|
function scrollElementIntoView(el) {
|
|
695
894
|
el.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
696
895
|
}
|
|
@@ -1586,6 +1785,7 @@ function markSessionEnded() {
|
|
|
1586
1785
|
moreButton.disabled = true;
|
|
1587
1786
|
chatInput.disabled = true;
|
|
1588
1787
|
updateSendState();
|
|
1788
|
+
renderSheetSummary();
|
|
1589
1789
|
if (presenceBanner) presenceBanner.hidden = true;
|
|
1590
1790
|
if (handoffBanner) handoffBanner.hidden = true;
|
|
1591
1791
|
if (outdatedBanner) outdatedBanner.hidden = true;
|
|
@@ -2253,6 +2453,8 @@ async function queueWhiteboardFeedback(index, message, mode) {
|
|
|
2253
2453
|
// earlier unsent prompt instead of stacking duplicates.
|
|
2254
2454
|
[internalQueueKeyField]: "whiteboard:" + index,
|
|
2255
2455
|
});
|
|
2456
|
+
// Queued from the whiteboard inside the artifact, like any other in-artifact prompt.
|
|
2457
|
+
pulseSheetDock();
|
|
2256
2458
|
postToWhiteboard(index, mode, { type: "lavish-whiteboard:queueResult", ok: true });
|
|
2257
2459
|
if (mode === "overlay") closeWhiteboard();
|
|
2258
2460
|
} catch (error) {
|
|
@@ -2556,6 +2758,8 @@ window.addEventListener("message", (event) => {
|
|
|
2556
2758
|
// The artifact spoke, so it rendered and ran its SDK - there is nothing fatal to probe for.
|
|
2557
2759
|
if (msg.type === "lavish:queuePrompt") {
|
|
2558
2760
|
enqueuePrompt(msg.prompt);
|
|
2761
|
+
// Queued from inside the artifact, where the closed dock is the only sign it landed.
|
|
2762
|
+
pulseSheetDock();
|
|
2559
2763
|
}
|
|
2560
2764
|
if (msg.type === "lavish:snapshot") {
|
|
2561
2765
|
const snapshotAction = snapshotRequests.shift() || "submit";
|
|
@@ -2867,6 +3071,10 @@ document.addEventListener("keydown", (event) => {
|
|
|
2867
3071
|
closeShareDialog();
|
|
2868
3072
|
} else if (warningsDrawerOpen) {
|
|
2869
3073
|
closeWarningsDrawer({ restoreFocus: true });
|
|
3074
|
+
} else if (!moreMenu.hidden) {
|
|
3075
|
+
closeMenus();
|
|
3076
|
+
} else if (sheetOpen && isMobileSheet()) {
|
|
3077
|
+
setSheetOpen(false);
|
|
2870
3078
|
} else {
|
|
2871
3079
|
closeMenus();
|
|
2872
3080
|
}
|
|
@@ -2907,13 +3115,18 @@ events.addEventListener("chrome-reload", (event) => reloadAfterServerRestart(shu
|
|
|
2907
3115
|
// The replacement server serves a different artifact's review. This page keeps working against
|
|
2908
3116
|
// it; it is only running the previous version of the chrome, which is the user's to act on.
|
|
2909
3117
|
events.addEventListener("chrome-outdated", (event) => setChromeOutdated(true, shutdownEventReason(event)));
|
|
2910
|
-
events.addEventListener("agent-reply", (event) =>
|
|
3118
|
+
events.addEventListener("agent-reply", (event) => {
|
|
3119
|
+
const text = JSON.parse(event.data).text;
|
|
3120
|
+
addChat("agent", text);
|
|
3121
|
+
noteAgentReply(text);
|
|
3122
|
+
});
|
|
2911
3123
|
events.addEventListener("chat-sync", (event) => syncChat(JSON.parse(event.data).chat || []));
|
|
2912
3124
|
events.addEventListener("agent-presence", (event) => setAgentPresence(JSON.parse(event.data).state));
|
|
2913
3125
|
events.addEventListener("layout-warnings", (event) => setLayoutWarnings(JSON.parse(event.data).warnings || []));
|
|
2914
3126
|
// A reconnecting stream means this chrome may have missed updates while it was away.
|
|
2915
3127
|
events.addEventListener("open", () => refreshLayoutWarnings());
|
|
2916
3128
|
|
|
3129
|
+
applySheetState();
|
|
2917
3130
|
render();
|
|
2918
3131
|
setChromeOutdated(false);
|
|
2919
3132
|
setWarningsDrawerOpen(false);
|
package/dist/chrome.css
CHANGED
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
--shadow-tooltip: 0 16px 44px rgba(0, 0, 0, 0.35);
|
|
97
97
|
--shadow-floating: 0 20px 70px rgba(0, 0, 0, 0.35);
|
|
98
98
|
--bar-h: 56px;
|
|
99
|
+
--bar-pad: var(--space-8);
|
|
99
100
|
--panel-w: 360px;
|
|
100
101
|
--ease: cubic-bezier(0.2, 0.6, 0.2, 1);
|
|
101
102
|
--dur-fast: 120ms;
|
|
@@ -135,7 +136,7 @@ body.lavish {
|
|
|
135
136
|
display: flex;
|
|
136
137
|
align-items: center;
|
|
137
138
|
gap: 14px;
|
|
138
|
-
padding: 0 var(--
|
|
139
|
+
padding: 0 var(--bar-pad);
|
|
139
140
|
background: var(--bg-bar);
|
|
140
141
|
border-bottom: var(--hairline-subtle);
|
|
141
142
|
box-sizing: border-box;
|
|
@@ -891,6 +892,18 @@ body.lavish {
|
|
|
891
892
|
min-width: 0;
|
|
892
893
|
min-height: 0;
|
|
893
894
|
}
|
|
895
|
+
/* Desktop keeps the panel's original flex children: the head wrappers exist only so the phone
|
|
896
|
+
layout can turn the heading into a dock, and they are transparent to layout everywhere else. */
|
|
897
|
+
.panel-head,
|
|
898
|
+
.panel-head-row {
|
|
899
|
+
display: contents;
|
|
900
|
+
}
|
|
901
|
+
.panel-handle,
|
|
902
|
+
.panel-summary,
|
|
903
|
+
.panel-toggle,
|
|
904
|
+
.panel-scrim {
|
|
905
|
+
display: none;
|
|
906
|
+
}
|
|
894
907
|
.panel h2 {
|
|
895
908
|
font-size: 15px;
|
|
896
909
|
line-height: 1.3;
|
|
@@ -1331,7 +1344,7 @@ body.layout-gate-active iframe#artifact {
|
|
|
1331
1344
|
@media (max-width: 640px) {
|
|
1332
1345
|
.bar {
|
|
1333
1346
|
gap: 10px;
|
|
1334
|
-
|
|
1347
|
+
--bar-pad: var(--space-6);
|
|
1335
1348
|
}
|
|
1336
1349
|
.brand-support {
|
|
1337
1350
|
display: none;
|
|
@@ -1345,23 +1358,202 @@ body.layout-gate-active iframe#artifact {
|
|
|
1345
1358
|
max-height: min(70vh, 460px);
|
|
1346
1359
|
}
|
|
1347
1360
|
}
|
|
1361
|
+
/* Phone-width chrome: the artifact takes the whole viewport above a persistent conversation dock,
|
|
1362
|
+
and the conversation itself is a bottom sheet the dock raises over a scrim. A side-by-side or
|
|
1363
|
+
stacked split leaves neither half usable on a phone - the composer alone is taller than the
|
|
1364
|
+
strip a split gives the panel - so the two surfaces take turns owning the height instead.
|
|
1365
|
+
The sheet is sized from the visual viewport (`--vv-top`/`--vv-height`, kept current by
|
|
1366
|
+
chrome-client.js) rather than the layout viewport, so an on-screen keyboard shrinks it instead
|
|
1367
|
+
of covering the composer, and every edge that can meet a notch or home indicator pads by the
|
|
1368
|
+
matching safe-area inset. */
|
|
1348
1369
|
@media (max-width: 860px) {
|
|
1349
|
-
|
|
1350
|
-
|
|
1370
|
+
:root {
|
|
1371
|
+
--bar-h: calc(56px + env(safe-area-inset-top, 0px));
|
|
1372
|
+
--dock-h: 60px;
|
|
1373
|
+
--sheet-peek: 16px;
|
|
1374
|
+
--composer-reserve: calc(1px + var(--space-4) + 34px + var(--space-6) + env(safe-area-inset-bottom, 0px));
|
|
1375
|
+
--sheet-top: max(var(--bar-h) + var(--sheet-peek) - var(--vv-top, 0px), var(--sheet-peek));
|
|
1376
|
+
}
|
|
1377
|
+
.bar {
|
|
1378
|
+
padding-top: env(safe-area-inset-top, 0px);
|
|
1379
|
+
padding-right: calc(var(--bar-pad) + env(safe-area-inset-right, 0px));
|
|
1380
|
+
padding-left: calc(var(--bar-pad) + env(safe-area-inset-left, 0px));
|
|
1351
1381
|
}
|
|
1352
1382
|
.layout {
|
|
1353
1383
|
height: calc(100vh - var(--bar-h));
|
|
1354
|
-
|
|
1355
|
-
grid-template-
|
|
1384
|
+
height: calc(100dvh - var(--bar-h));
|
|
1385
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1386
|
+
/* The second row only reserves the dock's footprint so the artifact is never underneath it;
|
|
1387
|
+
the panel itself is fixed. */
|
|
1388
|
+
grid-template-rows: minmax(0, 1fr) calc(var(--dock-h) + env(safe-area-inset-bottom, 0px));
|
|
1389
|
+
}
|
|
1390
|
+
.frame {
|
|
1391
|
+
padding: 0 env(safe-area-inset-right, 0px) 0 env(safe-area-inset-left, 0px);
|
|
1392
|
+
background: var(--bg-bar);
|
|
1356
1393
|
}
|
|
1357
1394
|
.panel {
|
|
1358
|
-
|
|
1395
|
+
position: fixed;
|
|
1396
|
+
z-index: 30;
|
|
1397
|
+
top: calc(var(--vv-top, 0px) + var(--sheet-top));
|
|
1398
|
+
right: 0;
|
|
1399
|
+
left: 0;
|
|
1400
|
+
width: auto;
|
|
1401
|
+
height: calc(var(--vv-height, 100dvh) - var(--sheet-top));
|
|
1402
|
+
padding: 0 env(safe-area-inset-right, 0px) 0 env(safe-area-inset-left, 0px);
|
|
1359
1403
|
border-left: 0;
|
|
1360
|
-
border-
|
|
1404
|
+
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
|
1405
|
+
/* The hairline is a shadow, not a border: a border would add to the sheet's box and push
|
|
1406
|
+
the docked head one pixel past the viewport edge. */
|
|
1407
|
+
box-shadow:
|
|
1408
|
+
0 -1px 0 var(--border-subtle),
|
|
1409
|
+
var(--shadow-floating);
|
|
1410
|
+
/* Collapsed: slide everything but the dock below the viewport. The sheet keeps its full
|
|
1411
|
+
height, so opening it is one transform and the chat never reflows mid-gesture. */
|
|
1412
|
+
transform: translateY(calc(100% - var(--dock-h) - env(safe-area-inset-bottom, 0px)));
|
|
1413
|
+
transition: transform var(--dur-slow) var(--ease);
|
|
1414
|
+
will-change: transform;
|
|
1415
|
+
}
|
|
1416
|
+
body.sheet-open .panel {
|
|
1417
|
+
transform: none;
|
|
1418
|
+
}
|
|
1419
|
+
.panel.is-dragging {
|
|
1420
|
+
transition: none;
|
|
1421
|
+
}
|
|
1422
|
+
.panel-head {
|
|
1423
|
+
display: flex;
|
|
1424
|
+
flex: 0 0 auto;
|
|
1425
|
+
flex-direction: column;
|
|
1426
|
+
height: var(--dock-h);
|
|
1427
|
+
padding: 6px 16px 0;
|
|
1428
|
+
cursor: pointer;
|
|
1429
|
+
user-select: none;
|
|
1430
|
+
-webkit-user-select: none;
|
|
1431
|
+
touch-action: none;
|
|
1432
|
+
}
|
|
1433
|
+
body:not(.sheet-open) .panel-head {
|
|
1434
|
+
height: calc(var(--dock-h) + env(safe-area-inset-bottom, 0px));
|
|
1435
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
1436
|
+
}
|
|
1437
|
+
.panel-handle {
|
|
1438
|
+
display: block;
|
|
1439
|
+
width: 36px;
|
|
1440
|
+
height: 4px;
|
|
1441
|
+
margin: 0 auto 6px;
|
|
1442
|
+
border-radius: var(--radius-pill);
|
|
1443
|
+
background: var(--border-strong);
|
|
1444
|
+
}
|
|
1445
|
+
.panel-head-row {
|
|
1446
|
+
display: flex;
|
|
1447
|
+
flex: 1 1 auto;
|
|
1448
|
+
align-items: center;
|
|
1449
|
+
gap: 10px;
|
|
1450
|
+
min-width: 0;
|
|
1451
|
+
}
|
|
1452
|
+
.panel h2 {
|
|
1453
|
+
flex: 0 0 auto;
|
|
1454
|
+
margin: 0;
|
|
1455
|
+
}
|
|
1456
|
+
.panel-summary {
|
|
1457
|
+
display: block;
|
|
1458
|
+
flex: 1 1 auto;
|
|
1459
|
+
min-width: 0;
|
|
1460
|
+
color: var(--fg-faint);
|
|
1461
|
+
font-size: 12px;
|
|
1462
|
+
line-height: 1.35;
|
|
1463
|
+
text-align: right;
|
|
1464
|
+
white-space: nowrap;
|
|
1465
|
+
overflow: hidden;
|
|
1466
|
+
text-overflow: ellipsis;
|
|
1467
|
+
}
|
|
1468
|
+
.panel-summary:empty {
|
|
1469
|
+
display: none;
|
|
1470
|
+
}
|
|
1471
|
+
.panel-summary.is-accent {
|
|
1472
|
+
color: var(--accent);
|
|
1473
|
+
font-weight: var(--w-semi);
|
|
1474
|
+
}
|
|
1475
|
+
.panel-summary.is-unread::before {
|
|
1476
|
+
content: "";
|
|
1477
|
+
display: inline-block;
|
|
1478
|
+
width: 6px;
|
|
1479
|
+
height: 6px;
|
|
1480
|
+
margin-right: 6px;
|
|
1481
|
+
border-radius: var(--radius-pill);
|
|
1482
|
+
background: var(--accent);
|
|
1483
|
+
vertical-align: 1px;
|
|
1484
|
+
}
|
|
1485
|
+
.panel-toggle {
|
|
1486
|
+
display: flex;
|
|
1487
|
+
flex: 0 0 auto;
|
|
1488
|
+
align-items: center;
|
|
1489
|
+
justify-content: center;
|
|
1490
|
+
width: 32px;
|
|
1491
|
+
height: 32px;
|
|
1492
|
+
padding: 0;
|
|
1493
|
+
border: 1px solid var(--border);
|
|
1494
|
+
border-radius: var(--radius-pill);
|
|
1495
|
+
background: var(--bg-elevated);
|
|
1496
|
+
color: var(--fg-muted);
|
|
1497
|
+
cursor: pointer;
|
|
1498
|
+
}
|
|
1499
|
+
.panel-toggle svg {
|
|
1500
|
+
display: block;
|
|
1501
|
+
transition: transform var(--dur) var(--ease);
|
|
1502
|
+
}
|
|
1503
|
+
body.sheet-open .panel-toggle svg {
|
|
1504
|
+
transform: rotate(180deg);
|
|
1505
|
+
}
|
|
1506
|
+
.panel-head.is-fresh .panel-toggle {
|
|
1507
|
+
animation: dock-pulse 700ms var(--ease);
|
|
1508
|
+
}
|
|
1509
|
+
.panel-scroll {
|
|
1510
|
+
flex: 1 1 56px;
|
|
1511
|
+
min-height: clamp(
|
|
1512
|
+
0px,
|
|
1513
|
+
calc(var(--vv-height, 100dvh) - var(--sheet-top) - var(--dock-h) - var(--composer-reserve)),
|
|
1514
|
+
56px
|
|
1515
|
+
);
|
|
1516
|
+
overscroll-behavior: contain;
|
|
1361
1517
|
}
|
|
1362
1518
|
.chat {
|
|
1363
1519
|
padding-bottom: 10px;
|
|
1364
1520
|
}
|
|
1521
|
+
.composer {
|
|
1522
|
+
position: static;
|
|
1523
|
+
min-height: min(
|
|
1524
|
+
var(--composer-reserve),
|
|
1525
|
+
max(0px, calc(var(--vv-height, 100dvh) - var(--sheet-top) - var(--dock-h)))
|
|
1526
|
+
);
|
|
1527
|
+
flex: 0 1 auto;
|
|
1528
|
+
overflow-y: auto;
|
|
1529
|
+
overscroll-behavior: contain;
|
|
1530
|
+
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
|
|
1531
|
+
}
|
|
1532
|
+
.chat-attachments {
|
|
1533
|
+
max-height: min(176px, 25dvh);
|
|
1534
|
+
}
|
|
1535
|
+
.composer .actions {
|
|
1536
|
+
position: sticky;
|
|
1537
|
+
bottom: 0;
|
|
1538
|
+
z-index: 1;
|
|
1539
|
+
padding-top: 8px;
|
|
1540
|
+
background: var(--bg-panel);
|
|
1541
|
+
box-shadow: 0 -1px 0 var(--border-subtle);
|
|
1542
|
+
}
|
|
1543
|
+
.panel-scrim {
|
|
1544
|
+
display: block;
|
|
1545
|
+
position: fixed;
|
|
1546
|
+
inset: var(--bar-h) 0 0 0;
|
|
1547
|
+
z-index: 29;
|
|
1548
|
+
background: rgba(15, 17, 21, 0.72);
|
|
1549
|
+
opacity: 0;
|
|
1550
|
+
pointer-events: none;
|
|
1551
|
+
transition: opacity var(--dur-slow) var(--ease);
|
|
1552
|
+
}
|
|
1553
|
+
body.sheet-open .panel-scrim {
|
|
1554
|
+
opacity: 1;
|
|
1555
|
+
pointer-events: auto;
|
|
1556
|
+
}
|
|
1365
1557
|
.pill-preview {
|
|
1366
1558
|
max-width: min(220px, 70vw);
|
|
1367
1559
|
}
|
|
@@ -1373,6 +1565,40 @@ body.layout-gate-active iframe#artifact {
|
|
|
1373
1565
|
flex-direction: column;
|
|
1374
1566
|
}
|
|
1375
1567
|
}
|
|
1568
|
+
/* A phone on its side has no height to share: the raised sheet covers the bar as well (the sheet
|
|
1569
|
+
carries its own close control) and the composer gives up its tall default. */
|
|
1570
|
+
@media (max-width: 860px) and (max-height: 520px) {
|
|
1571
|
+
:root {
|
|
1572
|
+
--sheet-top: var(--sheet-peek);
|
|
1573
|
+
}
|
|
1574
|
+
.panel-scrim {
|
|
1575
|
+
inset: 0;
|
|
1576
|
+
}
|
|
1577
|
+
.composer textarea {
|
|
1578
|
+
min-height: 48px;
|
|
1579
|
+
}
|
|
1580
|
+
.chat-attachments {
|
|
1581
|
+
max-height: 44px;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
|
|
1585
|
+
.panel,
|
|
1586
|
+
.panel-scrim,
|
|
1587
|
+
.panel-toggle svg {
|
|
1588
|
+
transition: none;
|
|
1589
|
+
}
|
|
1590
|
+
.panel-head.is-fresh .panel-toggle {
|
|
1591
|
+
animation: none;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
@keyframes dock-pulse {
|
|
1595
|
+
0% {
|
|
1596
|
+
box-shadow: 0 0 0 0 rgba(244, 201, 93, 0.55);
|
|
1597
|
+
}
|
|
1598
|
+
100% {
|
|
1599
|
+
box-shadow: 0 0 0 10px rgba(244, 201, 93, 0);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1376
1602
|
|
|
1377
1603
|
/* Whiteboard overlay: a full-viewport layer above the chrome hosting the
|
|
1378
1604
|
sandboxed Excalidraw frame. The frame owns all whiteboard UI; this layer
|
package/dist/cli.mjs
CHANGED
|
@@ -9920,7 +9920,9 @@ var chromeIcons = {
|
|
|
9920
9920
|
16
|
|
9921
9921
|
),
|
|
9922
9922
|
reveal: chromeIcon('<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"/><circle cx="12" cy="12" r="3"/>', 13),
|
|
9923
|
-
dismiss: chromeIcon('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>', 13)
|
|
9923
|
+
dismiss: chromeIcon('<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>', 13),
|
|
9924
|
+
// The mobile conversation sheet's only chevron: CSS rotates it when the sheet is open.
|
|
9925
|
+
chevronUp: chromeIcon('<polyline points="6 15 12 9 18 15"/>', 18, 2)
|
|
9924
9926
|
};
|
|
9925
9927
|
function displayPathParts(file, home = homedir()) {
|
|
9926
9928
|
const normalizedFile = file.replaceAll("\\", "/");
|
|
@@ -10056,14 +10058,14 @@ function createChromeHtml(session, {
|
|
|
10056
10058
|
<html>
|
|
10057
10059
|
<head>
|
|
10058
10060
|
<meta charset="utf-8">
|
|
10059
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
10061
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
10060
10062
|
<title>${escapeHtml(title)}</title>
|
|
10061
10063
|
${faviconTag}
|
|
10062
10064
|
<link rel="stylesheet" href="/chrome.css">
|
|
10063
10065
|
</head>
|
|
10064
10066
|
<body class="${bodyClass}">
|
|
10065
10067
|
<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>
|
|
10066
|
-
<div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox 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" id="chatComposer"><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 handoff-banner" id="outdatedBanner" hidden><span id="outdatedText">The Lavish server this page was connected to is no longer running. Reloading will work once it is running again.</span><span class="outdated-actions"><button class="handoff-takeover" id="outdatedReload" type="button">Check and reload</button><button class="handoff-takeover" id="outdatedDismiss" type="button">Dismiss</button></span></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="chat-attachments" id="chatAttachments"></div><div class="chat-attachment-toolbar"><button class="chat-attach" id="chatAttach" type="button">Attach images</button><input id="chatAttachInput" type="file" accept="${escapeHtml(acceptedMime.join(","))}" multiple hidden><span class="chat-attachment-notice" id="chatAttachmentNotice" role="status"></span></div><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>
|
|
10068
|
+
<div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-downloads" data-artifact-src="/artifact/${session.key}/index.html"></iframe></div><div class="panel-scrim" id="panelScrim"></div><aside class="panel" id="panel"><div class="panel-head" id="panelHead"><span class="panel-handle" aria-hidden="true"></span><div class="panel-head-row"><h2>Conversation</h2><span class="panel-summary" id="panelSummary" role="status" aria-live="polite"></span><button class="panel-toggle" id="panelToggle" type="button" aria-expanded="false" aria-controls="panel" aria-label="Show conversation">${chromeIcons.chevronUp}</button></div></div><div class="panel-scroll" id="panelScroll"><div class="chat" id="chatLog"></div><div class="annotation-pills" id="annotationPills"></div></div><div class="composer" id="chatComposer"><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 handoff-banner" id="outdatedBanner" hidden><span id="outdatedText">The Lavish server this page was connected to is no longer running. Reloading will work once it is running again.</span><span class="outdated-actions"><button class="handoff-takeover" id="outdatedReload" type="button">Check and reload</button><button class="handoff-takeover" id="outdatedDismiss" type="button">Dismiss</button></span></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="chat-attachments" id="chatAttachments"></div><div class="chat-attachment-toolbar"><button class="chat-attach" id="chatAttach" type="button">Attach images</button><input id="chatAttachInput" type="file" accept="${escapeHtml(acceptedMime.join(","))}" multiple hidden><span class="chat-attachment-notice" id="chatAttachmentNotice" role="status"></span></div><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>
|
|
10067
10069
|
<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>
|
|
10068
10070
|
<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>
|
|
10069
10071
|
<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>
|
|
@@ -10332,7 +10334,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
|
|
|
10332
10334
|
]);
|
|
10333
10335
|
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.";
|
|
10334
10336
|
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.";
|
|
10335
|
-
var VERSION = "0.1.
|
|
10337
|
+
var VERSION = "0.1.56";
|
|
10336
10338
|
function detectInvokingAgent(env = process.env) {
|
|
10337
10339
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
10338
10340
|
}
|
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.56",
|
|
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",
|