@mainarchfn/point-overlay 0.1.0
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/dist/esm/index.js +703 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/inspection.js +151 -0
- package/dist/esm/inspection.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +710 -0
- package/dist/index.js.map +1 -0
- package/dist/inspection.d.ts +8 -0
- package/dist/inspection.d.ts.map +1 -0
- package/dist/inspection.js +158 -0
- package/dist/inspection.js.map +1 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.routeForDocument = exports.relativeSourceFile = exports.inspectElement = void 0;
|
|
4
|
+
exports.mountPaseoOverlay = mountPaseoOverlay;
|
|
5
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
6
|
+
const point_client_1 = require("@mainarchfn/point-client");
|
|
7
|
+
const react_1 = require("react");
|
|
8
|
+
const client_1 = require("react-dom/client");
|
|
9
|
+
const inspection_1 = require("./inspection");
|
|
10
|
+
var inspection_2 = require("./inspection");
|
|
11
|
+
Object.defineProperty(exports, "inspectElement", { enumerable: true, get: function () { return inspection_2.inspectElement; } });
|
|
12
|
+
Object.defineProperty(exports, "relativeSourceFile", { enumerable: true, get: function () { return inspection_2.relativeSourceFile; } });
|
|
13
|
+
Object.defineProperty(exports, "routeForDocument", { enumerable: true, get: function () { return inspection_2.routeForDocument; } });
|
|
14
|
+
const HOST_ID = "paseo-overlay-host";
|
|
15
|
+
const ROOT_ID = "paseo-overlay-root";
|
|
16
|
+
const OVERLAY_Z_INDEX = "2147483646";
|
|
17
|
+
const NEW_SPACE_VALUE = "__new__";
|
|
18
|
+
function makeId(prefix) {
|
|
19
|
+
const cryptoObject = globalThis.crypto;
|
|
20
|
+
if (cryptoObject?.randomUUID)
|
|
21
|
+
return `${prefix}_${cryptoObject.randomUUID()}`;
|
|
22
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
23
|
+
}
|
|
24
|
+
function currentNodeEnvironment() {
|
|
25
|
+
const processLike = globalThis.process;
|
|
26
|
+
return processLike?.env?.NODE_ENV;
|
|
27
|
+
}
|
|
28
|
+
function createSelectionController(options) {
|
|
29
|
+
const { document, host, shadowRoot, onSelected, onCancel } = options;
|
|
30
|
+
const highlight = document.createElement("div");
|
|
31
|
+
const label = document.createElement("div");
|
|
32
|
+
highlight.setAttribute("aria-hidden", "true");
|
|
33
|
+
highlight.style.cssText = [
|
|
34
|
+
"position:fixed",
|
|
35
|
+
"display:none",
|
|
36
|
+
"box-sizing:border-box",
|
|
37
|
+
"border:2px solid #7c3aed",
|
|
38
|
+
"background:rgba(124,58,237,.10)",
|
|
39
|
+
"border-radius:4px",
|
|
40
|
+
"pointer-events:none",
|
|
41
|
+
`z-index:${OVERLAY_Z_INDEX}`,
|
|
42
|
+
"transition:top 80ms ease,left 80ms ease,width 80ms ease,height 80ms ease"
|
|
43
|
+
].join(";");
|
|
44
|
+
label.style.cssText = [
|
|
45
|
+
"position:absolute",
|
|
46
|
+
"left:-2px",
|
|
47
|
+
"top:-28px",
|
|
48
|
+
"max-width:360px",
|
|
49
|
+
"padding:5px 8px",
|
|
50
|
+
"border-radius:5px",
|
|
51
|
+
"background:#241447",
|
|
52
|
+
"color:#fff",
|
|
53
|
+
"font:12px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace",
|
|
54
|
+
"white-space:nowrap",
|
|
55
|
+
"overflow:hidden",
|
|
56
|
+
"text-overflow:ellipsis"
|
|
57
|
+
].join(";");
|
|
58
|
+
highlight.append(label);
|
|
59
|
+
shadowRoot.append(highlight);
|
|
60
|
+
let current = null;
|
|
61
|
+
const isOverlayElement = (element) => element === host || host.contains(element);
|
|
62
|
+
const updateHighlight = () => {
|
|
63
|
+
if (!current || !current.isConnected || isOverlayElement(current)) {
|
|
64
|
+
highlight.style.display = "none";
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const rectangle = current.getBoundingClientRect();
|
|
68
|
+
if (rectangle.width === 0 && rectangle.height === 0) {
|
|
69
|
+
highlight.style.display = "none";
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const target = (0, inspection_1.inspectElement)(current, document);
|
|
73
|
+
const source = (0, inspection_1.targetSourceLabel)(target);
|
|
74
|
+
label.textContent = source
|
|
75
|
+
? `${(0, inspection_1.targetLabel)(target)} ${source}`
|
|
76
|
+
: (0, inspection_1.targetLabel)(target);
|
|
77
|
+
highlight.style.display = "block";
|
|
78
|
+
highlight.style.top = `${rectangle.top}px`;
|
|
79
|
+
highlight.style.left = `${rectangle.left}px`;
|
|
80
|
+
highlight.style.width = `${rectangle.width}px`;
|
|
81
|
+
highlight.style.height = `${rectangle.height}px`;
|
|
82
|
+
};
|
|
83
|
+
const elementFromEvent = (event) => {
|
|
84
|
+
const target = event.target;
|
|
85
|
+
if (target && target instanceof Element)
|
|
86
|
+
return target;
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
const onPointerMove = (event) => {
|
|
90
|
+
const element = elementFromEvent(event);
|
|
91
|
+
if (!element || isOverlayElement(element)) {
|
|
92
|
+
current = null;
|
|
93
|
+
updateHighlight();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
current = element;
|
|
97
|
+
updateHighlight();
|
|
98
|
+
};
|
|
99
|
+
const onClick = (event) => {
|
|
100
|
+
const element = elementFromEvent(event);
|
|
101
|
+
if (!element || isOverlayElement(element))
|
|
102
|
+
return;
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
event.stopPropagation();
|
|
105
|
+
event.stopImmediatePropagation();
|
|
106
|
+
onSelected((0, inspection_1.inspectElement)(element, document));
|
|
107
|
+
};
|
|
108
|
+
const onKeyDown = (event) => {
|
|
109
|
+
if (event.key !== "Escape")
|
|
110
|
+
return;
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
event.stopPropagation();
|
|
113
|
+
onCancel();
|
|
114
|
+
};
|
|
115
|
+
document.addEventListener("mousemove", onPointerMove, true);
|
|
116
|
+
document.addEventListener("click", onClick, true);
|
|
117
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
118
|
+
document.addEventListener("scroll", updateHighlight, true);
|
|
119
|
+
return () => {
|
|
120
|
+
document.removeEventListener("mousemove", onPointerMove, true);
|
|
121
|
+
document.removeEventListener("click", onClick, true);
|
|
122
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
123
|
+
document.removeEventListener("scroll", updateHighlight, true);
|
|
124
|
+
highlight.remove();
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function phaseForSession(session) {
|
|
128
|
+
const runtimeReady = session.runtime?.status === "ready";
|
|
129
|
+
const changeSetActive = session.activeChangeSetId !== null;
|
|
130
|
+
if (runtimeReady && !changeSetActive && ["waiting_for_user", "completed", "ready", "failed"].includes(session.status)) {
|
|
131
|
+
return "ready";
|
|
132
|
+
}
|
|
133
|
+
return "working";
|
|
134
|
+
}
|
|
135
|
+
function requestStatusForSession(session) {
|
|
136
|
+
const latestChangeSet = session.changeSets[session.changeSets.length - 1];
|
|
137
|
+
if (session.runtime?.status === "failed" || session.runtime?.status === "stopped") {
|
|
138
|
+
return "error";
|
|
139
|
+
}
|
|
140
|
+
if (session.runtime?.status === "starting")
|
|
141
|
+
return "setup";
|
|
142
|
+
// A runtime that is up is the source of truth for the application status.
|
|
143
|
+
// Session/change-set failures describe the previous agent request and must
|
|
144
|
+
// not make a healthy, reloadable workspace look like a dead app.
|
|
145
|
+
if (session.runtime?.status === "ready" && !session.activeChangeSetId) {
|
|
146
|
+
return "ready";
|
|
147
|
+
}
|
|
148
|
+
if (session.status === "failed" || latestChangeSet?.status === "failed")
|
|
149
|
+
return "error";
|
|
150
|
+
if (latestChangeSet?.status === "queued" || session.status === "creating") {
|
|
151
|
+
return "setup";
|
|
152
|
+
}
|
|
153
|
+
return "working";
|
|
154
|
+
}
|
|
155
|
+
function requestStatusLabel(status, session) {
|
|
156
|
+
if (status === "setup" && session?.runtime?.status === "starting")
|
|
157
|
+
return "Starting";
|
|
158
|
+
return { setup: "Setup", working: "Working", ready: "Ready", error: "Error" }[status];
|
|
159
|
+
}
|
|
160
|
+
function spaceStatus(space) {
|
|
161
|
+
if (space.runtimeStatus === "failed")
|
|
162
|
+
return { kind: "error", label: "Failed" };
|
|
163
|
+
if (space.runtimeStatus === "stopped")
|
|
164
|
+
return { kind: "error", label: "Stopped" };
|
|
165
|
+
if (space.runtimeStatus === "starting" || space.status === "creating" || space.status === "working") {
|
|
166
|
+
return { kind: "working", label: "Working" };
|
|
167
|
+
}
|
|
168
|
+
if (space.runtimeStatus === "ready")
|
|
169
|
+
return { kind: "ready", label: "Ready" };
|
|
170
|
+
if (space.status === "failed")
|
|
171
|
+
return { kind: "error", label: "Failed" };
|
|
172
|
+
return { kind: "idle", label: "No active workspace" };
|
|
173
|
+
}
|
|
174
|
+
function directivePreview(instruction) {
|
|
175
|
+
return instruction?.trim() || "No directive submitted yet";
|
|
176
|
+
}
|
|
177
|
+
function sessionErrorDetail(session) {
|
|
178
|
+
const latestChangeSet = session.changeSets[session.changeSets.length - 1];
|
|
179
|
+
if (session.runtime?.status === "failed") {
|
|
180
|
+
return "The workspace service is not reachable.";
|
|
181
|
+
}
|
|
182
|
+
if (session.runtime?.status === "stopped") {
|
|
183
|
+
return "The workspace service is stopped.";
|
|
184
|
+
}
|
|
185
|
+
if (latestChangeSet?.status === "failed") {
|
|
186
|
+
return latestChangeSet.error ?? "The previous change set failed.";
|
|
187
|
+
}
|
|
188
|
+
if (session.status === "failed")
|
|
189
|
+
return "The development session is in a failed state.";
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
function canRestartRuntime(session) {
|
|
193
|
+
return session.runtime?.status === "failed" || session.runtime?.status === "stopped";
|
|
194
|
+
}
|
|
195
|
+
function SpaceStatusIcon({ space }) {
|
|
196
|
+
const status = spaceStatus(space);
|
|
197
|
+
return ((0, jsx_runtime_1.jsx)("span", { className: `paseo-space-status is-${status.kind}`, role: "img", "aria-label": status.label, title: status.label, children: status.kind === "ready" ? "✓" : status.kind === "error" ? "!" : status.kind === "working" ? "·" : "–" }));
|
|
198
|
+
}
|
|
199
|
+
function readableError(error) {
|
|
200
|
+
if (error instanceof point_client_1.RuntimeIdentityError) {
|
|
201
|
+
return { summary: "Invalid runtime identity", detail: error.message };
|
|
202
|
+
}
|
|
203
|
+
if (error instanceof point_client_1.PaseoNetworkError) {
|
|
204
|
+
return { summary: "Unable to contact Paseo", detail: error.message };
|
|
205
|
+
}
|
|
206
|
+
if (error instanceof point_client_1.PaseoApiError) {
|
|
207
|
+
const summaries = {
|
|
208
|
+
RUNTIME_NOT_FOUND: "Invalid runtime identity",
|
|
209
|
+
RUNTIME_INACTIVE: "The application runtime is not active",
|
|
210
|
+
WORKTREE_CREATE_FAILED: "Failed to create worktree",
|
|
211
|
+
RUNTIME_START_FAILED: "Failed to start runtime",
|
|
212
|
+
AGENT_FAILED: "Agent failed",
|
|
213
|
+
CHANGE_SET_FAILED: "Change set failed",
|
|
214
|
+
VALIDATION_ERROR: "The submitted change set is invalid"
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
summary: summaries[error.code] ?? error.message,
|
|
218
|
+
detail: error.message
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
if (error instanceof Error)
|
|
222
|
+
return { summary: error.message };
|
|
223
|
+
return { summary: "Paseo request failed" };
|
|
224
|
+
}
|
|
225
|
+
function TargetBadge({ target }) {
|
|
226
|
+
if (target.type === "page") {
|
|
227
|
+
return (0, jsx_runtime_1.jsx)("span", { className: "paseo-target-badge", children: "Current page" });
|
|
228
|
+
}
|
|
229
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "paseo-target-badge", children: [(0, inspection_1.targetLabel)(target), (0, inspection_1.targetSourceLabel)(target) ? ` · ${(0, inspection_1.targetSourceLabel)(target)}` : ""] }));
|
|
230
|
+
}
|
|
231
|
+
function OverlayApp({ client, runtime, document, initiallyOpen = false }) {
|
|
232
|
+
const [open, setOpen] = (0, react_1.useState)(initiallyOpen);
|
|
233
|
+
const [selecting, setSelecting] = (0, react_1.useState)(false);
|
|
234
|
+
const [selectedTarget, setSelectedTarget] = (0, react_1.useState)(null);
|
|
235
|
+
const [selectedInstruction, setSelectedInstruction] = (0, react_1.useState)("");
|
|
236
|
+
const [pageInstruction, setPageInstruction] = (0, react_1.useState)("");
|
|
237
|
+
const [pending, setPending] = (0, react_1.useState)([]);
|
|
238
|
+
const [phase, setPhase] = (0, react_1.useState)("compose");
|
|
239
|
+
const [sessionId, setSessionId] = (0, react_1.useState)(runtime.sessionId);
|
|
240
|
+
const [session, setSession] = (0, react_1.useState)(null);
|
|
241
|
+
const [spaces, setSpaces] = (0, react_1.useState)([]);
|
|
242
|
+
const [selectedSpaceId, setSelectedSpaceId] = (0, react_1.useState)(runtime.sessionId ?? NEW_SPACE_VALUE);
|
|
243
|
+
const [expandedSpaceId, setExpandedSpaceId] = (0, react_1.useState)(null);
|
|
244
|
+
const [spaceDetails, setSpaceDetails] = (0, react_1.useState)({});
|
|
245
|
+
const [spaceDetailsError, setSpaceDetailsError] = (0, react_1.useState)(null);
|
|
246
|
+
const [error, setError] = (0, react_1.useState)(null);
|
|
247
|
+
const [showChanges, setShowChanges] = (0, react_1.useState)(false);
|
|
248
|
+
const [restartingSessionId, setRestartingSessionId] = (0, react_1.useState)(null);
|
|
249
|
+
const [shelfClosing, setShelfClosing] = (0, react_1.useState)(false);
|
|
250
|
+
const drawerRef = (0, react_1.useRef)(null);
|
|
251
|
+
const shelfCloseTimerRef = (0, react_1.useRef)(null);
|
|
252
|
+
(0, react_1.useEffect)(() => {
|
|
253
|
+
if (!open)
|
|
254
|
+
return;
|
|
255
|
+
drawerRef.current?.querySelector(".paseo-input")?.focus();
|
|
256
|
+
}, [open]);
|
|
257
|
+
const refreshSession = (0, react_1.useCallback)(async (id) => {
|
|
258
|
+
try {
|
|
259
|
+
const next = await client.getDevelopmentSession(id);
|
|
260
|
+
setSession(next);
|
|
261
|
+
setPhase(phaseForSession(next));
|
|
262
|
+
const latestChangeSet = next.changeSets[next.changeSets.length - 1];
|
|
263
|
+
if (next.runtime?.status === "failed" || next.runtime?.status === "stopped") {
|
|
264
|
+
setError({ summary: "Working application failed to start" });
|
|
265
|
+
}
|
|
266
|
+
else if (next.runtime?.status === "ready" && !next.activeChangeSetId) {
|
|
267
|
+
setError((current) => current?.summary === "Change set failed" || current?.summary === "Working application failed to start"
|
|
268
|
+
? null
|
|
269
|
+
: current);
|
|
270
|
+
}
|
|
271
|
+
else if (latestChangeSet?.status === "failed") {
|
|
272
|
+
setError({
|
|
273
|
+
summary: "Change set failed",
|
|
274
|
+
detail: latestChangeSet.error
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
else if (next.status !== "failed") {
|
|
278
|
+
setError((current) => current?.summary === "Change set failed" || current?.summary === "Working application failed to start"
|
|
279
|
+
? null
|
|
280
|
+
: current);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch (requestError) {
|
|
284
|
+
setError(readableError(requestError));
|
|
285
|
+
}
|
|
286
|
+
}, [client]);
|
|
287
|
+
const restartSessionRuntime = async (id) => {
|
|
288
|
+
setRestartingSessionId(id);
|
|
289
|
+
setSelectedSpaceId(id);
|
|
290
|
+
setSessionId(id);
|
|
291
|
+
setError(null);
|
|
292
|
+
setPhase("working");
|
|
293
|
+
try {
|
|
294
|
+
await client.restartRuntime(id);
|
|
295
|
+
await refreshSession(id);
|
|
296
|
+
await refreshSpaces();
|
|
297
|
+
}
|
|
298
|
+
catch (requestError) {
|
|
299
|
+
setPhase("compose");
|
|
300
|
+
setError(readableError(requestError));
|
|
301
|
+
}
|
|
302
|
+
finally {
|
|
303
|
+
setRestartingSessionId(null);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
const refreshSpaces = (0, react_1.useCallback)(async () => {
|
|
307
|
+
try {
|
|
308
|
+
const next = await client.listDevelopmentSessions();
|
|
309
|
+
setSpaces(next);
|
|
310
|
+
if (selectedSpaceId !== NEW_SPACE_VALUE &&
|
|
311
|
+
!next.some((space) => space.id === selectedSpaceId && space.status !== "closed")) {
|
|
312
|
+
setSelectedSpaceId(NEW_SPACE_VALUE);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
catch (requestError) {
|
|
316
|
+
setError(readableError(requestError));
|
|
317
|
+
}
|
|
318
|
+
}, [client, selectedSpaceId]);
|
|
319
|
+
(0, react_1.useEffect)(() => {
|
|
320
|
+
if (!open)
|
|
321
|
+
return;
|
|
322
|
+
void refreshSpaces();
|
|
323
|
+
const timer = document.defaultView?.setInterval(() => void refreshSpaces(), 1_500);
|
|
324
|
+
return () => {
|
|
325
|
+
if (timer !== undefined)
|
|
326
|
+
document.defaultView?.clearInterval(timer);
|
|
327
|
+
};
|
|
328
|
+
}, [document, open, refreshSpaces]);
|
|
329
|
+
(0, react_1.useEffect)(() => {
|
|
330
|
+
if (!expandedSpaceId)
|
|
331
|
+
return;
|
|
332
|
+
let disposed = false;
|
|
333
|
+
const refresh = () => {
|
|
334
|
+
void client
|
|
335
|
+
.getDevelopmentSession(expandedSpaceId)
|
|
336
|
+
.then((next) => {
|
|
337
|
+
if (disposed)
|
|
338
|
+
return;
|
|
339
|
+
setSpaceDetails((current) => ({ ...current, [expandedSpaceId]: next }));
|
|
340
|
+
setSpaceDetailsError(null);
|
|
341
|
+
})
|
|
342
|
+
.catch((requestError) => {
|
|
343
|
+
if (!disposed)
|
|
344
|
+
setSpaceDetailsError(readableError(requestError).summary);
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
refresh();
|
|
348
|
+
const timer = document.defaultView?.setInterval(refresh, 1_000);
|
|
349
|
+
return () => {
|
|
350
|
+
disposed = true;
|
|
351
|
+
if (timer !== undefined)
|
|
352
|
+
document.defaultView?.clearInterval(timer);
|
|
353
|
+
};
|
|
354
|
+
}, [client, document, expandedSpaceId]);
|
|
355
|
+
(0, react_1.useEffect)(() => {
|
|
356
|
+
if (!sessionId)
|
|
357
|
+
return;
|
|
358
|
+
let disposed = false;
|
|
359
|
+
const refresh = () => {
|
|
360
|
+
if (!disposed)
|
|
361
|
+
void refreshSession(sessionId);
|
|
362
|
+
};
|
|
363
|
+
refresh();
|
|
364
|
+
// The overlay only needs the current projection. Polling avoids presenting
|
|
365
|
+
// an implementation event log as the request's user-facing state.
|
|
366
|
+
const timer = document.defaultView?.setInterval(refresh, 750);
|
|
367
|
+
return () => {
|
|
368
|
+
disposed = true;
|
|
369
|
+
if (timer !== undefined)
|
|
370
|
+
document.defaultView?.clearInterval(timer);
|
|
371
|
+
};
|
|
372
|
+
}, [document, refreshSession, sessionId]);
|
|
373
|
+
(0, react_1.useEffect)(() => {
|
|
374
|
+
if (!selecting)
|
|
375
|
+
return;
|
|
376
|
+
const host = document.getElementById(HOST_ID);
|
|
377
|
+
const shadowRoot = host?.shadowRoot;
|
|
378
|
+
if (!host || !shadowRoot)
|
|
379
|
+
return;
|
|
380
|
+
return createSelectionController({
|
|
381
|
+
document,
|
|
382
|
+
host,
|
|
383
|
+
shadowRoot,
|
|
384
|
+
onSelected: (target) => {
|
|
385
|
+
setSelectedTarget(target);
|
|
386
|
+
setSelectedInstruction("");
|
|
387
|
+
setSelecting(false);
|
|
388
|
+
if (shelfCloseTimerRef.current !== null) {
|
|
389
|
+
window.clearTimeout(shelfCloseTimerRef.current);
|
|
390
|
+
shelfCloseTimerRef.current = null;
|
|
391
|
+
}
|
|
392
|
+
setShelfClosing(false);
|
|
393
|
+
setOpen(true);
|
|
394
|
+
},
|
|
395
|
+
onCancel: () => {
|
|
396
|
+
setSelecting(false);
|
|
397
|
+
setShelfClosing(false);
|
|
398
|
+
setOpen(false);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}, [document, selecting]);
|
|
402
|
+
const addSelectedDirective = () => {
|
|
403
|
+
const instruction = selectedInstruction.trim();
|
|
404
|
+
if (!selectedTarget || !instruction)
|
|
405
|
+
return;
|
|
406
|
+
setPending((current) => [
|
|
407
|
+
...current,
|
|
408
|
+
{ instruction, target: selectedTarget }
|
|
409
|
+
]);
|
|
410
|
+
setSelectedTarget(null);
|
|
411
|
+
setSelectedInstruction("");
|
|
412
|
+
setError(null);
|
|
413
|
+
};
|
|
414
|
+
const startSelection = () => {
|
|
415
|
+
setError(null);
|
|
416
|
+
setSelecting(true);
|
|
417
|
+
setShelfClosing(true);
|
|
418
|
+
if (shelfCloseTimerRef.current !== null)
|
|
419
|
+
window.clearTimeout(shelfCloseTimerRef.current);
|
|
420
|
+
shelfCloseTimerRef.current = window.setTimeout(() => {
|
|
421
|
+
setOpen(false);
|
|
422
|
+
setShelfClosing(false);
|
|
423
|
+
shelfCloseTimerRef.current = null;
|
|
424
|
+
}, 160);
|
|
425
|
+
};
|
|
426
|
+
const submit = async () => {
|
|
427
|
+
const directives = [...pending];
|
|
428
|
+
const instruction = pageInstruction.trim();
|
|
429
|
+
if (instruction) {
|
|
430
|
+
directives.push({
|
|
431
|
+
instruction,
|
|
432
|
+
target: {
|
|
433
|
+
type: "page",
|
|
434
|
+
route: (0, inspection_1.routeForDocument)(document),
|
|
435
|
+
documentTitle: document.title || undefined
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (!directives.length) {
|
|
440
|
+
setError({ summary: "Add an instruction before submitting" });
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
setPhase("submitting");
|
|
444
|
+
setError(null);
|
|
445
|
+
try {
|
|
446
|
+
const result = await client.createChangeSet({
|
|
447
|
+
runtimeId: runtime.runtimeId,
|
|
448
|
+
developmentSessionId: selectedSpaceId === NEW_SPACE_VALUE ? null : selectedSpaceId,
|
|
449
|
+
route: (0, inspection_1.routeForDocument)(document),
|
|
450
|
+
directives
|
|
451
|
+
});
|
|
452
|
+
setPending([]);
|
|
453
|
+
setPageInstruction("");
|
|
454
|
+
setSessionId(result.developmentSessionId);
|
|
455
|
+
setSelectedSpaceId(result.developmentSessionId);
|
|
456
|
+
void refreshSpaces();
|
|
457
|
+
setPhase("working");
|
|
458
|
+
}
|
|
459
|
+
catch (requestError) {
|
|
460
|
+
setPhase("compose");
|
|
461
|
+
setError(readableError(requestError));
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
const openSessionRuntime = (nextSession) => {
|
|
465
|
+
if (!nextSession.runtime || nextSession.runtime.status !== "ready")
|
|
466
|
+
return;
|
|
467
|
+
const currentUrl = document.defaultView?.location.href;
|
|
468
|
+
const url = (0, point_client_1.appendRuntimeContext)((0, point_client_1.constructWorkingRuntimeUrl)(nextSession.runtime, currentUrl), {
|
|
469
|
+
controlPlaneUrl: runtime.controlPlaneUrl,
|
|
470
|
+
...(runtime.defaultRuntimeUrl ? { defaultRuntimeUrl: runtime.defaultRuntimeUrl } : {}),
|
|
471
|
+
projectId: nextSession.runtime.projectId ?? runtime.projectId,
|
|
472
|
+
runtimeId: nextSession.runtime.id,
|
|
473
|
+
sessionId: nextSession.runtime.developmentSessionId ?? nextSession.id,
|
|
474
|
+
checkoutKind: nextSession.runtime.checkoutKind ?? "worktree",
|
|
475
|
+
...(nextSession.runtime.branch ? { branch: nextSession.runtime.branch } : {})
|
|
476
|
+
});
|
|
477
|
+
document.defaultView?.location.assign(url);
|
|
478
|
+
};
|
|
479
|
+
const openWorkingVersion = () => {
|
|
480
|
+
if (!session?.runtime || session.runtime.status !== "ready")
|
|
481
|
+
return;
|
|
482
|
+
openSessionRuntime(session);
|
|
483
|
+
};
|
|
484
|
+
const activeRuntime = session?.runtime;
|
|
485
|
+
const canOpenWorkingVersion = activeRuntime?.status === "ready" &&
|
|
486
|
+
activeRuntime.id !== runtime.runtimeId;
|
|
487
|
+
const requestStatus = session
|
|
488
|
+
? requestStatusForSession(session)
|
|
489
|
+
: phase === "compose"
|
|
490
|
+
? "working"
|
|
491
|
+
: "setup";
|
|
492
|
+
const activeSpaces = spaces.filter((space) => space.status !== "closed");
|
|
493
|
+
const selectSpace = (nextSpaceId) => {
|
|
494
|
+
setSelectedSpaceId(nextSpaceId);
|
|
495
|
+
setError(null);
|
|
496
|
+
};
|
|
497
|
+
const toggleSpaceDetails = (spaceId) => {
|
|
498
|
+
selectSpace(spaceId);
|
|
499
|
+
setSpaceDetailsError(null);
|
|
500
|
+
setExpandedSpaceId((current) => current === spaceId ? null : spaceId);
|
|
501
|
+
};
|
|
502
|
+
const openSpace = (space) => {
|
|
503
|
+
if (space.runtimeStatus !== "ready")
|
|
504
|
+
return;
|
|
505
|
+
selectSpace(space.id);
|
|
506
|
+
void client
|
|
507
|
+
.getDevelopmentSession(space.id)
|
|
508
|
+
.then((nextSession) => {
|
|
509
|
+
setSpaceDetails((current) => ({ ...current, [space.id]: nextSession }));
|
|
510
|
+
openSessionRuntime(nextSession);
|
|
511
|
+
})
|
|
512
|
+
.catch((requestError) => setError(readableError(requestError)));
|
|
513
|
+
};
|
|
514
|
+
const startNewSpace = () => {
|
|
515
|
+
selectSpace(NEW_SPACE_VALUE);
|
|
516
|
+
setExpandedSpaceId(null);
|
|
517
|
+
setSpaceDetailsError(null);
|
|
518
|
+
setSessionId(null);
|
|
519
|
+
setSession(null);
|
|
520
|
+
setPending([]);
|
|
521
|
+
setPageInstruction("");
|
|
522
|
+
setSelectedTarget(null);
|
|
523
|
+
setSelectedInstruction("");
|
|
524
|
+
setPhase("compose");
|
|
525
|
+
setError(null);
|
|
526
|
+
setShowChanges(false);
|
|
527
|
+
if (runtime.checkoutKind === "worktree") {
|
|
528
|
+
if (runtime.defaultRuntimeUrl) {
|
|
529
|
+
document.defaultView?.location.assign(runtime.defaultRuntimeUrl);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
setError({
|
|
533
|
+
summary: "Open the baseline app to create a new space",
|
|
534
|
+
detail: "This working space does not have a default runtime URL configured."
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("style", { children: `
|
|
540
|
+
:host { all: initial; }
|
|
541
|
+
* { box-sizing: border-box; }
|
|
542
|
+
.paseo-shell { position: fixed; inset: 0; z-index: ${OVERLAY_Z_INDEX}; pointer-events: none; color: #f7f5ff; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-size: 14px; }
|
|
543
|
+
.paseo-fab { pointer-events: auto; position: fixed; right: 20px; bottom: 20px; width: 46px; height: 46px; border: 0; border-radius: 999px; background: #6d28d9; color: white; box-shadow: 0 8px 24px rgba(34, 17, 66, .32); cursor: pointer; font: 700 15px/1 system-ui, sans-serif; transition: transform 160ms ease, background 160ms ease; }
|
|
544
|
+
.paseo-fab:hover { background: #5b21b6; transform: scale(1.04); }
|
|
545
|
+
.paseo-fab:focus-visible, .paseo-shelf button:focus-visible { outline: 3px solid #c4b5fd; outline-offset: 2px; }
|
|
546
|
+
.paseo-shelf input:focus-visible { outline: 2px solid #594477; outline-offset: 1px; }
|
|
547
|
+
.paseo-shelf { pointer-events: auto; position: fixed; right: 20px; bottom: 20px; display: grid; gap: 7px; width: min(420px, calc(100vw - 40px)); max-height: calc(100vh - 40px); overflow: visible; padding: 7px; border: 1px solid #3d2d59; border-radius: 14px; background: #171122; color: #f7f5ff; box-shadow: 0 12px 38px rgba(15, 7, 30, .42); transition: opacity 160ms ease, transform 160ms ease; }
|
|
548
|
+
.paseo-shelf.is-closing { pointer-events: none; opacity: 0; transform: translateY(7px) scale(.98); }
|
|
549
|
+
.paseo-shelf-top { display: flex; align-items: center; justify-content: space-between; gap: 6px; width: 100%; animation: paseo-shelf-in 180ms ease-out both; }
|
|
550
|
+
.paseo-shelf-top:empty { display: none; }
|
|
551
|
+
.paseo-spaces-heading { color: #f0eaff; font-size: 12px; font-weight: 750; }
|
|
552
|
+
.paseo-new-space { border: 0; padding: 2px 4px; background: transparent; color: #c4b5fd; cursor: pointer; font: 650 11px/1.2 inherit; }
|
|
553
|
+
.paseo-new-space:hover { color: #fff; }
|
|
554
|
+
.paseo-changes-trigger { overflow: hidden; max-width: 185px; height: 28px; border: 1px solid #594477; border-radius: 7px; padding: 0 9px; background: #281a3c; color: #d9cfea; cursor: pointer; font: 650 11px/1 inherit; text-overflow: ellipsis; white-space: nowrap; box-shadow: 0 7px 22px rgba(15, 7, 30, .2); }
|
|
555
|
+
.paseo-changes-trigger:hover, .paseo-icon-button:hover { background: #35214f; }
|
|
556
|
+
.paseo-space-list { display: grid; gap: 4px; max-height: min(42vh, 320px); overflow: auto; padding-right: 1px; }
|
|
557
|
+
.paseo-space-row { display: grid; grid-template-columns: 18px minmax(0, 1fr) 30px; align-items: center; gap: 7px; min-width: 0; padding: 7px 6px; border: 1px solid #302244; border-radius: 8px; background: #1e152c; color: #eee7fa; cursor: pointer; text-align: left; }
|
|
558
|
+
.paseo-space-row:hover, .paseo-space-row.is-selected { border-color: #594477; background: #251936; }
|
|
559
|
+
.paseo-space-row:focus-visible { outline: 3px solid #c4b5fd; outline-offset: 1px; }
|
|
560
|
+
.paseo-space-status { display: inline-grid; place-items: center; width: 17px; height: 17px; border-radius: 999px; font: 800 12px/1 system-ui, sans-serif; }
|
|
561
|
+
.paseo-space-status.is-ready { background: #245b42; color: #baf1ce; }
|
|
562
|
+
.paseo-space-status.is-working { background: #49356c; color: #decfff; }
|
|
563
|
+
.paseo-space-status.is-working::first-letter { animation: paseo-status-pulse 1s ease-in-out infinite; }
|
|
564
|
+
.paseo-space-status.is-error { background: #71324b; color: #ffd4df; }
|
|
565
|
+
.paseo-space-status.is-idle { background: #3a3044; color: #b9abc9; }
|
|
566
|
+
.paseo-space-copy { min-width: 0; }
|
|
567
|
+
.paseo-space-directive { display: block; overflow: hidden; margin-top: 2px; color: #a99bb9; font-size: 11px; line-height: 1.25; text-overflow: ellipsis; white-space: nowrap; }
|
|
568
|
+
.paseo-space-open { display: inline-grid; place-items: center; width: 28px; height: 28px; border: 1px solid #594477; border-radius: 7px; background: #281a3c; color: #f7f5ff; cursor: pointer; }
|
|
569
|
+
.paseo-space-open:hover { background: #35214f; }
|
|
570
|
+
.paseo-space-open:disabled { cursor: not-allowed; opacity: .35; }
|
|
571
|
+
.paseo-space-details { grid-column: 1 / -1; margin: 1px 0 0 24px; padding: 7px 8px 2px; border-top: 1px solid #302244; color: #c7bdd6; font-size: 11px; line-height: 1.35; }
|
|
572
|
+
.paseo-space-detail-line { display: flex; justify-content: space-between; gap: 10px; margin-bottom: 5px; }
|
|
573
|
+
.paseo-space-detail-line strong { color: #f0eaff; font-weight: 650; }
|
|
574
|
+
.paseo-space-detail-directive { margin: 0 0 5px; color: #d9cfea; overflow-wrap: anywhere; }
|
|
575
|
+
.paseo-space-empty { margin: 0; padding: 4px 2px; }
|
|
576
|
+
.paseo-shelf-bar { display: flex; align-items: center; gap: 5px; width: 100%; min-height: 46px; padding: 5px; border: 1px solid #3d2d59; border-radius: 12px; background: #100b18; animation: paseo-shelf-in 180ms ease-out both; }
|
|
577
|
+
.paseo-input { flex: 1 1 auto; width: 1px; height: 34px; min-width: 0; border: 1px solid #302244; border-radius: 8px; padding: 0 10px; background: #100b18; color: #fff; font: 13px/34px inherit; }
|
|
578
|
+
.paseo-input::placeholder { color: #897b9c; }
|
|
579
|
+
.paseo-icon-button { display: inline-grid; place-items: center; flex: 0 0 34px; width: 34px; height: 34px; border: 1px solid #594477; border-radius: 8px; background: #281a3c; color: #f7f5ff; cursor: pointer; font: 800 16px/1 inherit; }
|
|
580
|
+
.paseo-icon-button.primary { border-color: #7c3aed; background: #6d28d9; }
|
|
581
|
+
.paseo-icon-button.primary:hover { background: #7c3aed; }
|
|
582
|
+
.paseo-icon-button:disabled { cursor: not-allowed; opacity: .45; }
|
|
583
|
+
.paseo-icon-button.paseo-close-button { font-size: 20px; font-weight: 900; line-height: .8; }
|
|
584
|
+
.paseo-close { border: 0; background: transparent; color: #c7bdd6; cursor: pointer; font-size: 20px; line-height: 1; }
|
|
585
|
+
.paseo-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
|
|
586
|
+
.paseo-popover { width: 100%; max-height: min(60vh, 430px); overflow: auto; padding: 4px; border: 1px solid #4b3965; border-radius: 11px; background: #171122; box-shadow: 0 8px 24px rgba(15, 7, 30, .3); animation: paseo-popover-in 140ms ease-out both; }
|
|
587
|
+
.paseo-popover-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 9px; color: #f0eaff; font-size: 12px; font-weight: 750; }
|
|
588
|
+
.paseo-popover-header span { color: #a99bb9; font-size: 11px; font-weight: 500; }
|
|
589
|
+
.paseo-target-list { display: grid; gap: 6px; }
|
|
590
|
+
.paseo-target-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; min-width: 0; padding: 8px 9px; border: 1px solid #302244; border-radius: 8px; background: #1e152c; }
|
|
591
|
+
.paseo-target-copy { min-width: 0; color: #d9cfea; font-size: 12px; line-height: 1.35; }
|
|
592
|
+
.paseo-target-badge { display: block; overflow: hidden; margin-bottom: 2px; color: #c4b5fd; font-size: 10px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; }
|
|
593
|
+
.paseo-remove { flex: 0 0 auto; border: 0; background: transparent; color: #a99bb9; cursor: pointer; padding: 0 2px; }
|
|
594
|
+
.paseo-popover-actions { display: flex; align-items: center; gap: 7px; margin-top: 9px; }
|
|
595
|
+
.paseo-button { border: 1px solid #594477; border-radius: 8px; padding: 7px 10px; background: #281a3c; color: #f7f5ff; cursor: pointer; font: 650 12px/1.2 inherit; }
|
|
596
|
+
.paseo-button:hover { background: #35214f; }
|
|
597
|
+
.paseo-button.primary { border-color: #7c3aed; background: #6d28d9; }
|
|
598
|
+
.paseo-button.primary:hover { background: #7c3aed; }
|
|
599
|
+
.paseo-button:disabled { cursor: not-allowed; opacity: .55; }
|
|
600
|
+
.paseo-status-line { display: flex; justify-content: space-between; gap: 12px; padding-top: 8px; margin-top: 9px; border-top: 1px solid #302244; color: #b9abc9; font-size: 11px; }
|
|
601
|
+
.paseo-status-line strong { min-width: 0; overflow: hidden; color: #f0eaff; text-align: right; font-weight: 650; text-overflow: ellipsis; white-space: nowrap; }
|
|
602
|
+
.paseo-error { margin-top: 8px; padding: 8px 9px; border: 1px solid #9f3f5b; border-radius: 8px; background: #351522; color: #ffd9e2; font-size: 12px; }
|
|
603
|
+
.paseo-error strong { display: block; }
|
|
604
|
+
.paseo-error button { border: 0; margin-top: 4px; padding: 0; background: transparent; color: #f5a9bd; cursor: pointer; text-decoration: underline; }
|
|
605
|
+
.paseo-error-details { margin-top: 5px; }
|
|
606
|
+
.paseo-error-details summary { color: #f5a9bd; cursor: pointer; }
|
|
607
|
+
.paseo-error-details p { margin: 5px 0 0; }
|
|
608
|
+
.paseo-error-action { border: 1px solid #c45a76 !important; border-radius: 6px; padding: 5px 8px !important; color: #ffe8ee !important; text-decoration: none !important; }
|
|
609
|
+
.paseo-error pre { max-height: 130px; overflow: auto; margin: 7px 0 0; white-space: pre-wrap; font: 11px/1.35 ui-monospace, monospace; }
|
|
610
|
+
.paseo-ready, .paseo-working { margin-top: 8px; padding: 8px 9px; border-radius: 8px; font-size: 12px; }
|
|
611
|
+
.paseo-ready { border: 1px solid #367a59; background: #10271c; color: #c8f5d7; }
|
|
612
|
+
.paseo-working { border: 1px solid #594477; background: #20142f; color: #e5d8f5; }
|
|
613
|
+
.paseo-hint { margin: 5px 0 0; color: #b9abc9; font-size: 11px; line-height: 1.4; }
|
|
614
|
+
@keyframes paseo-shelf-in { from { opacity: 0; transform: translateY(7px) scale(.98); } to { opacity: 1; transform: translateY(0) scale(1); } }
|
|
615
|
+
@keyframes paseo-status-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }
|
|
616
|
+
@keyframes paseo-popover-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
|
617
|
+
@media (prefers-reduced-motion: reduce) { .paseo-shelf-top, .paseo-shelf-bar, .paseo-popover { animation: none; } }
|
|
618
|
+
@media (max-width: 480px) { .paseo-shelf { right: 12px; bottom: 12px; width: calc(100vw - 24px); } .paseo-fab { right: 12px; bottom: 12px; } }
|
|
619
|
+
` }), (0, jsx_runtime_1.jsxs)("div", { className: "paseo-shell", children: [(0, jsx_runtime_1.jsx)("button", { className: "paseo-fab", type: "button", "aria-label": open ? "Close Paseo shelf" : "Open Paseo shelf", "aria-expanded": open, onClick: () => {
|
|
620
|
+
setOpen((current) => !current);
|
|
621
|
+
setShowChanges(false);
|
|
622
|
+
}, children: "P" }), open ? ((0, jsx_runtime_1.jsxs)("aside", { className: `paseo-shelf${shelfClosing ? " is-closing" : ""}`, ref: (element) => {
|
|
623
|
+
drawerRef.current = element;
|
|
624
|
+
}, "aria-label": "Paseo development overlay", role: "dialog", "aria-modal": "false", children: [showChanges ? ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-popover", role: "dialog", "aria-label": "Pending changes", children: [(0, jsx_runtime_1.jsxs)("div", { className: "paseo-popover-header", children: [(0, jsx_runtime_1.jsx)("div", { children: "Pending changes" }), (0, jsx_runtime_1.jsxs)("span", { children: [pending.length, " ", pending.length === 1 ? "item" : "items"] })] }), pending.length ? ((0, jsx_runtime_1.jsx)("div", { className: "paseo-target-list", children: pending.map((directive, index) => ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-target-row", children: [(0, jsx_runtime_1.jsxs)("div", { className: "paseo-target-copy", children: [(0, jsx_runtime_1.jsx)(TargetBadge, { target: directive.target }), (0, jsx_runtime_1.jsx)("div", { children: directive.instruction })] }), (0, jsx_runtime_1.jsx)("button", { className: "paseo-remove", type: "button", "aria-label": `Remove directive ${index + 1}`, onClick: () => setPending((current) => current.filter((_, item) => item !== index)), children: "\u00D7" })] }, `${directive.instruction}-${index}`))) })) : (0, jsx_runtime_1.jsx)("p", { className: "paseo-hint", children: "No pending changes." }), (0, jsx_runtime_1.jsxs)("div", { className: "paseo-popover-actions", children: [(0, jsx_runtime_1.jsx)("button", { className: "paseo-button primary", type: "button", onClick: () => void submit(), disabled: !pending.length || phase === "submitting", children: phase === "submitting" ? "Submitting…" : "Submit change set" }), (0, jsx_runtime_1.jsx)("button", { className: "paseo-button", type: "button", onClick: () => setShowChanges(false), children: "Done" })] }), sessionId && (phase === "working" || phase === "ready") ? ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-status-line", "aria-live": "polite", children: [(0, jsx_runtime_1.jsx)("span", { children: requestStatus === "error" ? "View details" : requestStatusLabel(requestStatus, session) }), (0, jsx_runtime_1.jsx)("strong", { children: session?.branch ?? sessionId })] })) : null, requestStatus === "ready" && canOpenWorkingVersion ? ((0, jsx_runtime_1.jsx)("button", { className: "paseo-button primary", style: { width: "100%", marginTop: 8 }, type: "button", onClick: openWorkingVersion, children: "Open working version" })) : null, requestStatus === "error" ? ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-error", role: "alert", children: [(0, jsx_runtime_1.jsx)("strong", { children: session?.runtime?.status === "failed" || session?.runtime?.status === "stopped"
|
|
625
|
+
? "Workspace service unavailable"
|
|
626
|
+
: "Change set failed" }), (0, jsx_runtime_1.jsxs)("details", { className: "paseo-error-details", children: [(0, jsx_runtime_1.jsx)("summary", { children: "View error details" }), (0, jsx_runtime_1.jsx)("p", { children: error?.detail ?? (session ? sessionErrorDetail(session) : "The working application failed.") })] }), session && canRestartRuntime(session) ? ((0, jsx_runtime_1.jsx)("button", { className: "paseo-error-action", type: "button", onClick: () => void restartSessionRuntime(session.id), disabled: restartingSessionId === session.id, children: restartingSessionId === session.id ? "Starting service…" : "Start service" })) : null] })) : null] })) : null, (0, jsx_runtime_1.jsxs)("div", { className: "paseo-shelf-top", children: [(0, jsx_runtime_1.jsx)("span", { className: "paseo-spaces-heading", children: "Spaces" }), (0, jsx_runtime_1.jsx)("button", { className: "paseo-new-space", type: "button", onClick: startNewSpace, disabled: phase === "submitting", children: runtime.checkoutKind === "worktree" ? "Back to main app" : "+ Add space" }), pending.length ? ((0, jsx_runtime_1.jsxs)("button", { className: "paseo-changes-trigger", type: "button", "aria-expanded": showChanges, onClick: () => setShowChanges((current) => !current), children: [pending.length, " pending ", pending.length === 1 ? "change" : "changes"] })) : null] }), (0, jsx_runtime_1.jsx)("div", { className: "paseo-space-list", "aria-label": "Spaces", children: activeSpaces.length ? activeSpaces.map((space) => {
|
|
627
|
+
const details = spaceDetails[space.id];
|
|
628
|
+
const latest = details?.changeSets[details.changeSets.length - 1];
|
|
629
|
+
const expanded = expandedSpaceId === space.id;
|
|
630
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: `paseo-space-row${selectedSpaceId === space.id ? " is-selected" : ""}`, role: "button", tabIndex: 0, "aria-expanded": expanded, onClick: () => toggleSpaceDetails(space.id), onKeyDown: (event) => {
|
|
631
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
632
|
+
event.preventDefault();
|
|
633
|
+
toggleSpaceDetails(space.id);
|
|
634
|
+
}
|
|
635
|
+
}, children: [(0, jsx_runtime_1.jsx)(SpaceStatusIcon, { space: space }), (0, jsx_runtime_1.jsx)("div", { className: "paseo-space-copy", children: (0, jsx_runtime_1.jsx)("span", { className: "paseo-space-directive", title: directivePreview(space.latestDirective), children: directivePreview(space.latestDirective) }) }), (0, jsx_runtime_1.jsx)("button", { className: "paseo-space-open", type: "button", "aria-label": "Open space", title: space.runtimeStatus === "ready" ? "Open workspace" : "Workspace is not active", disabled: space.runtimeStatus !== "ready" || phase === "submitting", onClick: (event) => {
|
|
636
|
+
event.stopPropagation();
|
|
637
|
+
openSpace(space);
|
|
638
|
+
}, children: (0, jsx_runtime_1.jsxs)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("path", { d: "M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" }), (0, jsx_runtime_1.jsx)("circle", { cx: "12", cy: "12", r: "2.5" })] }) }), expanded ? ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-space-details", children: [spaceDetailsError && !details ? (0, jsx_runtime_1.jsx)("div", { children: spaceDetailsError }) : null, !details && !spaceDetailsError ? (0, jsx_runtime_1.jsx)("div", { children: "Loading details\u2026" }) : null, details ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "paseo-space-detail-line", children: [(0, jsx_runtime_1.jsx)("span", { children: "Step" }), (0, jsx_runtime_1.jsx)("strong", { children: requestStatusForSession(details) === "error" ? "View details" : requestStatusLabel(requestStatusForSession(details), details) })] }), (0, jsx_runtime_1.jsx)("p", { className: "paseo-space-detail-directive", children: latest?.directives[0]?.instruction ?? "No directive submitted yet" }), requestStatusForSession(details) === "error" ? ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-error", role: "alert", children: [(0, jsx_runtime_1.jsx)("strong", { children: "Workspace service unavailable" }), (0, jsx_runtime_1.jsxs)("details", { className: "paseo-error-details", children: [(0, jsx_runtime_1.jsx)("summary", { children: "View error details" }), (0, jsx_runtime_1.jsx)("p", { children: sessionErrorDetail(details) ?? "The working application failed." })] }), canRestartRuntime(details) ? ((0, jsx_runtime_1.jsx)("button", { className: "paseo-error-action", type: "button", onClick: (event) => {
|
|
639
|
+
event.stopPropagation();
|
|
640
|
+
void restartSessionRuntime(details.id);
|
|
641
|
+
}, disabled: restartingSessionId === details.id, children: restartingSessionId === details.id ? "Starting service…" : "Start service" })) : null] })) : null] })) : null] })) : null] }, space.id));
|
|
642
|
+
}) : ((0, jsx_runtime_1.jsx)("p", { className: "paseo-hint paseo-space-empty", children: "No spaces yet. Submit a change to create one." })) }), (0, jsx_runtime_1.jsxs)("div", { className: "paseo-shelf-bar", children: [selectedTarget ? ((0, jsx_runtime_1.jsx)("input", { id: "paseo-element-instruction", className: "paseo-input", value: selectedInstruction, onChange: (event) => setSelectedInstruction(event.target.value), onKeyDown: (event) => {
|
|
643
|
+
if (event.key === "Enter")
|
|
644
|
+
addSelectedDirective();
|
|
645
|
+
}, "aria-label": `Instruction for ${(0, inspection_1.targetLabel)(selectedTarget)}`, placeholder: `Change ${(0, inspection_1.targetLabel)(selectedTarget)}`, disabled: phase === "submitting" })) : ((0, jsx_runtime_1.jsx)("input", { id: "paseo-page-instruction", className: "paseo-input", value: pageInstruction, onChange: (event) => setPageInstruction(event.target.value), onKeyDown: (event) => {
|
|
646
|
+
if (event.key === "Enter")
|
|
647
|
+
void submit();
|
|
648
|
+
}, "aria-label": "Instruction for this page", placeholder: "Describe a change\u2026", disabled: phase === "submitting" })), selectedTarget ? (0, jsx_runtime_1.jsxs)("span", { className: "paseo-visually-hidden", children: ["Selected element: ", (0, inspection_1.targetLabel)(selectedTarget)] }) : null, selectedTarget ? ((0, jsx_runtime_1.jsx)("button", { className: "paseo-icon-button primary", type: "button", "aria-label": "Add selected element to pending changes", onClick: addSelectedDirective, disabled: !selectedInstruction.trim() || phase === "submitting", children: "+" })) : ((0, jsx_runtime_1.jsxs)("button", { className: "paseo-icon-button", type: "button", "aria-label": "Select element", title: "Select element", onClick: startSelection, disabled: phase === "submitting", children: [(0, jsx_runtime_1.jsx)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("path", { d: "M5 3.5 19.5 12l-6.7 1.1 3.5 6.1-2.3 1.3-3.5-6.1L5 19.5V3.5Z", fill: "currentColor" }) }), (0, jsx_runtime_1.jsx)("span", { className: "paseo-visually-hidden", children: "Select Element" })] })), (0, jsx_runtime_1.jsx)("button", { className: "paseo-icon-button paseo-close-button", type: "button", "aria-label": selectedTarget ? "Cancel selected element" : "Close Paseo shelf", title: selectedTarget ? "Cancel" : "Close", onClick: () => {
|
|
649
|
+
if (selectedTarget) {
|
|
650
|
+
setSelectedTarget(null);
|
|
651
|
+
setSelectedInstruction("");
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
setOpen(false);
|
|
655
|
+
setShowChanges(false);
|
|
656
|
+
}
|
|
657
|
+
}, children: "\u00D7" })] }), error && (!session || requestStatus !== "error") ? ((0, jsx_runtime_1.jsxs)("div", { className: "paseo-error", role: "alert", children: [(0, jsx_runtime_1.jsx)("strong", { children: error.summary }), error.detail ? ((0, jsx_runtime_1.jsxs)("details", { className: "paseo-error-details", children: [(0, jsx_runtime_1.jsx)("summary", { children: "View error details" }), (0, jsx_runtime_1.jsx)("p", { children: error.detail })] })) : null] })) : null] })) : null] })] }));
|
|
658
|
+
}
|
|
659
|
+
function ensureStyleRoot(shadowRoot) {
|
|
660
|
+
const root = shadowRoot.querySelector(`#${ROOT_ID}`);
|
|
661
|
+
if (root)
|
|
662
|
+
return;
|
|
663
|
+
const container = shadowRoot.ownerDocument.createElement("div");
|
|
664
|
+
container.id = ROOT_ID;
|
|
665
|
+
container.style.pointerEvents = "none";
|
|
666
|
+
shadowRoot.append(container);
|
|
667
|
+
}
|
|
668
|
+
function mountPaseoOverlay(options = {}) {
|
|
669
|
+
if (options.enabled === false || currentNodeEnvironment() === "production") {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
const document = options.document ?? (typeof globalThis.document !== "undefined" ? globalThis.document : undefined);
|
|
673
|
+
if (!document?.body)
|
|
674
|
+
return null;
|
|
675
|
+
let client;
|
|
676
|
+
try {
|
|
677
|
+
client = options.client ?? (0, point_client_1.createPaseoClient)({ runtime: options.runtime });
|
|
678
|
+
}
|
|
679
|
+
catch {
|
|
680
|
+
// The overlay fails closed until the application's explicit bootstrap is present.
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
const runtime = client.getRuntime();
|
|
684
|
+
const existing = document.getElementById(HOST_ID);
|
|
685
|
+
if (existing)
|
|
686
|
+
return null;
|
|
687
|
+
const host = document.createElement("div");
|
|
688
|
+
host.id = HOST_ID;
|
|
689
|
+
host.setAttribute("data-paseo-overlay", "true");
|
|
690
|
+
host.style.cssText = `position:fixed;inset:0;z-index:${OVERLAY_Z_INDEX};pointer-events:none;`;
|
|
691
|
+
const shadowRoot = host.attachShadow({ mode: "open" });
|
|
692
|
+
ensureStyleRoot(shadowRoot);
|
|
693
|
+
document.body.append(host);
|
|
694
|
+
const rootContainer = shadowRoot.querySelector(`#${ROOT_ID}`);
|
|
695
|
+
if (!rootContainer) {
|
|
696
|
+
host.remove();
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
const root = (0, client_1.createRoot)(rootContainer);
|
|
700
|
+
root.render((0, jsx_runtime_1.jsx)(OverlayApp, { client: client, runtime: runtime, document: document, initiallyOpen: options.initiallyOpen }));
|
|
701
|
+
return {
|
|
702
|
+
host,
|
|
703
|
+
unmount() {
|
|
704
|
+
root.unmount();
|
|
705
|
+
host.remove();
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
exports.default = mountPaseoOverlay;
|
|
710
|
+
//# sourceMappingURL=index.js.map
|