@go-go-golems/os-scripting 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/README.md +147 -0
- package/app/createAppStore.d.ts +89 -0
- package/app/createAppStore.js +90 -0
- package/app/index.d.ts +2 -0
- package/app/index.js +2 -0
- package/app/runtimeSessionLifecycleMiddleware.d.ts +6 -0
- package/app/runtimeSessionLifecycleMiddleware.js +42 -0
- package/features/runtimeSessions/capabilityPolicy.d.ts +12 -0
- package/features/runtimeSessions/capabilityPolicy.js +36 -0
- package/features/runtimeSessions/index.d.ts +3 -0
- package/features/runtimeSessions/index.js +3 -0
- package/features/runtimeSessions/runtimeSessionsSlice.d.ts +90 -0
- package/features/runtimeSessions/runtimeSessionsSlice.js +210 -0
- package/features/runtimeSessions/selectors.d.ts +16 -0
- package/features/runtimeSessions/selectors.js +40 -0
- package/hypercard/artifacts/artifactProjectionMiddleware.d.ts +1 -0
- package/hypercard/artifacts/artifactProjectionMiddleware.js +66 -0
- package/hypercard/artifacts/artifactRuntime.d.ts +22 -0
- package/hypercard/artifacts/artifactRuntime.js +137 -0
- package/hypercard/artifacts/artifactsSelectors.d.ts +10 -0
- package/hypercard/artifacts/artifactsSelectors.js +2 -0
- package/hypercard/artifacts/artifactsSlice.d.ts +38 -0
- package/hypercard/artifacts/artifactsSlice.js +94 -0
- package/hypercard/debug/RuntimeSurfaceDebugWindow.d.ts +7 -0
- package/hypercard/debug/RuntimeSurfaceDebugWindow.js +181 -0
- package/hypercard/debug/jsSessionDebugRegistry.d.ts +11 -0
- package/hypercard/debug/jsSessionDebugRegistry.js +43 -0
- package/hypercard/debug/runtimeDebugApp.d.ts +19 -0
- package/hypercard/debug/runtimeDebugApp.js +25 -0
- package/hypercard/debug/runtimeDebugRegistry.d.ts +6 -0
- package/hypercard/debug/runtimeDebugRegistry.js +49 -0
- package/hypercard/editor/CodeEditorWindow.d.ts +7 -0
- package/hypercard/editor/CodeEditorWindow.js +128 -0
- package/hypercard/editor/editorLaunch.d.ts +16 -0
- package/hypercard/editor/editorLaunch.js +58 -0
- package/hypercard/editor/runtimeSurfaceRef.d.ts +8 -0
- package/hypercard/editor/runtimeSurfaceRef.js +60 -0
- package/hypercard/index.d.ts +12 -0
- package/hypercard/index.js +12 -0
- package/hypercard/task-manager/TaskManagerWindow.d.ts +1 -0
- package/hypercard/task-manager/TaskManagerWindow.js +102 -0
- package/hypercard/task-manager/index.d.ts +6 -0
- package/hypercard/task-manager/index.js +6 -0
- package/hypercard/task-manager/jsSessionSource.d.ts +10 -0
- package/hypercard/task-manager/jsSessionSource.js +49 -0
- package/hypercard/task-manager/runtimeSessionSource.d.ts +26 -0
- package/hypercard/task-manager/runtimeSessionSource.js +123 -0
- package/hypercard/task-manager/taskManagerApp.d.ts +16 -0
- package/hypercard/task-manager/taskManagerApp.js +25 -0
- package/hypercard/task-manager/taskManagerRegistry.d.ts +10 -0
- package/hypercard/task-manager/taskManagerRegistry.js +75 -0
- package/hypercard/task-manager/types.d.ts +25 -0
- package/hypercard/task-manager/types.js +1 -0
- package/hypercard/timeline/hypercardCard.d.ts +5 -0
- package/hypercard/timeline/hypercardCard.js +104 -0
- package/index.d.ts +18 -0
- package/index.js +18 -0
- package/package.json +72 -0
- package/plugin-runtime/contracts.d.ts +116 -0
- package/plugin-runtime/contracts.js +32 -0
- package/plugin-runtime/fixtures/column-stack.vm.js +19 -0
- package/plugin-runtime/fixtures/dynamic-card.vm.js +13 -0
- package/plugin-runtime/fixtures/inventory-stack.vm.js +29 -0
- package/plugin-runtime/fixtures/kanban-card.vm.js +55 -0
- package/plugin-runtime/fixtures/loop-stack.vm.js +16 -0
- package/plugin-runtime/fixtures/patched-low-stock-handler.vm.js +3 -0
- package/plugin-runtime/fixtures/patched-low-stock-render.vm.js +5 -0
- package/plugin-runtime/index.d.ts +6 -0
- package/plugin-runtime/index.js +6 -0
- package/plugin-runtime/intentSchema.d.ts +3 -0
- package/plugin-runtime/intentSchema.js +25 -0
- package/plugin-runtime/jsEvalSupport.d.ts +6 -0
- package/plugin-runtime/jsEvalSupport.js +93 -0
- package/plugin-runtime/jsSessionService.d.ts +55 -0
- package/plugin-runtime/jsSessionService.js +136 -0
- package/plugin-runtime/quickJsSessionCore.d.ts +24 -0
- package/plugin-runtime/quickJsSessionCore.js +92 -0
- package/plugin-runtime/runtimeService.d.ts +34 -0
- package/plugin-runtime/runtimeService.js +248 -0
- package/plugin-runtime/runtimeSurfaceRegistry.d.ts +45 -0
- package/plugin-runtime/runtimeSurfaceRegistry.js +73 -0
- package/plugin-runtime/stack-bootstrap.vm.js +236 -0
- package/repl/attachedJsSessionRegistry.d.ts +25 -0
- package/repl/attachedJsSessionRegistry.js +81 -0
- package/repl/attachedRuntimeSessionRegistry.d.ts +11 -0
- package/repl/attachedRuntimeSessionRegistry.js +107 -0
- package/repl/hypercardReplDriver.d.ts +54 -0
- package/repl/hypercardReplDriver.js +600 -0
- package/repl/jsReplDriver.d.ts +8 -0
- package/repl/jsReplDriver.js +348 -0
- package/repl/jsSessionBroker.d.ts +21 -0
- package/repl/jsSessionBroker.js +75 -0
- package/repl/runtimeBroker.d.ts +43 -0
- package/repl/runtimeBroker.js +117 -0
- package/runtime-host/RuntimeSurfaceSessionHost.d.ts +8 -0
- package/runtime-host/RuntimeSurfaceSessionHost.js +384 -0
- package/runtime-host/fixtures/CardSessionHost.chat.vm.js +61 -0
- package/runtime-host/fixtures/CardSessionHost.list.vm.js +29 -0
- package/runtime-host/fixtures/CardSessionHost.nav.vm.js +101 -0
- package/runtime-host/fixtures/CardSessionHost.report.vm.js +34 -0
- package/runtime-host/pluginIntentRouting.d.ts +13 -0
- package/runtime-host/pluginIntentRouting.js +83 -0
- package/runtime-packages/index.d.ts +1 -0
- package/runtime-packages/index.js +1 -0
- package/runtime-packages/runtimePackageRegistry.d.ts +14 -0
- package/runtime-packages/runtimePackageRegistry.js +42 -0
- package/runtime-packages/ui.package.vm.js +84 -0
- package/runtime-packs/index.d.ts +1 -0
- package/runtime-packs/index.js +1 -0
- package/runtime-packs/runtimeSurfaceTypeRegistry.d.ts +20 -0
- package/runtime-packs/runtimeSurfaceTypeRegistry.js +37 -0
- package/runtime-session-manager/index.d.ts +2 -0
- package/runtime-session-manager/index.js +2 -0
- package/runtime-session-manager/runtimeOwnership.d.ts +10 -0
- package/runtime-session-manager/runtimeOwnership.js +19 -0
- package/runtime-session-manager/runtimeSessionManager.d.ts +47 -0
- package/runtime-session-manager/runtimeSessionManager.js +214 -0
- package/testRuntimeUi.d.ts +4 -0
- package/testRuntimeUi.js +39 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
import { useDispatch } from 'react-redux';
|
|
5
|
+
import { openWindow } from '@go-go-golems/os-core/desktop-core';
|
|
6
|
+
import { getPendingRuntimeSurfaces, onRegistryChange, } from '../../plugin-runtime';
|
|
7
|
+
import { SyntaxHighlight } from '@go-go-golems/os-chat';
|
|
8
|
+
import { openCodeEditor } from '../editor/editorLaunch';
|
|
9
|
+
import { useRegisteredRuntimeDebugStacks } from './runtimeDebugRegistry';
|
|
10
|
+
import { useRegisteredJsSessionDebugSources } from './jsSessionDebugRegistry';
|
|
11
|
+
import { buildTaskManagerWindowPayload } from '../task-manager/taskManagerApp';
|
|
12
|
+
function nextDebugSessionId(prefix) {
|
|
13
|
+
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
|
14
|
+
return `${prefix}${globalThis.crypto.randomUUID()}`;
|
|
15
|
+
}
|
|
16
|
+
return `${prefix}${Date.now()}`;
|
|
17
|
+
}
|
|
18
|
+
function buildBundleSurfaceWindowPayload(bundle, surfaceId) {
|
|
19
|
+
const surface = bundle.surfaces[surfaceId];
|
|
20
|
+
if (!surface) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const sessionId = nextDebugSessionId(`runtime-debug:${bundle.id}:${surfaceId}:`);
|
|
24
|
+
return {
|
|
25
|
+
id: `window:runtime-debug:${bundle.id}:${surfaceId}:${sessionId}`,
|
|
26
|
+
title: surface.title ?? surfaceId,
|
|
27
|
+
icon: surface.icon ?? '📄',
|
|
28
|
+
bounds: { x: 180, y: 56, w: 960, h: 700 },
|
|
29
|
+
content: {
|
|
30
|
+
kind: 'surface',
|
|
31
|
+
surface: {
|
|
32
|
+
bundleId: bundle.id,
|
|
33
|
+
surfaceId,
|
|
34
|
+
surfaceSessionId: sessionId,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function surfaceSource(surface) {
|
|
40
|
+
const runtime = surface.meta?.runtime;
|
|
41
|
+
if (!runtime || typeof runtime !== 'object') {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const source = runtime.source;
|
|
45
|
+
return typeof source === 'string' && source.trim().length > 0 ? source : null;
|
|
46
|
+
}
|
|
47
|
+
function Section({ title, children }) {
|
|
48
|
+
return (_jsxs("div", { style: { marginBottom: 16 }, children: [_jsx("div", { style: { fontWeight: 700, fontSize: 13, borderBottom: '1px solid #999', paddingBottom: 4, marginBottom: 8, color: '#111' }, children: title }), children] }));
|
|
49
|
+
}
|
|
50
|
+
function CodePreview({ code, maxLines = 8 }) {
|
|
51
|
+
return _jsx(SyntaxHighlight, { code: code, language: "javascript", maxLines: maxLines });
|
|
52
|
+
}
|
|
53
|
+
function Badge({ text, color }) {
|
|
54
|
+
return (_jsx("span", { style: {
|
|
55
|
+
display: 'inline-block',
|
|
56
|
+
fontSize: 10,
|
|
57
|
+
fontWeight: 600,
|
|
58
|
+
padding: '1px 6px',
|
|
59
|
+
borderRadius: 3,
|
|
60
|
+
background: color,
|
|
61
|
+
color: '#fff',
|
|
62
|
+
marginLeft: 6,
|
|
63
|
+
}, children: text }));
|
|
64
|
+
}
|
|
65
|
+
export function RuntimeSurfaceDebugWindow({ ownerAppId, bundles, initialStackId, }) {
|
|
66
|
+
const dispatch = useDispatch();
|
|
67
|
+
const [registryCards, setRegistryCards] = useState(getPendingRuntimeSurfaces());
|
|
68
|
+
const registeredStacks = useRegisteredRuntimeDebugStacks();
|
|
69
|
+
const jsSessionSources = useRegisteredJsSessionDebugSources();
|
|
70
|
+
const availableBundles = useMemo(() => (bundles && bundles.length > 0 ? [...bundles] : registeredStacks), [bundles, registeredStacks]);
|
|
71
|
+
const [jsSessions, setJsSessions] = useState([]);
|
|
72
|
+
const [selectedStackId, setSelectedStackId] = useState(initialStackId ?? availableBundles[0]?.id ?? null);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const update = () => setRegistryCards(getPendingRuntimeSurfaces());
|
|
75
|
+
return onRegistryChange(update);
|
|
76
|
+
}, []);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const candidateId = (selectedStackId && availableBundles.some((bundle) => bundle.id === selectedStackId))
|
|
79
|
+
? selectedStackId
|
|
80
|
+
: initialStackId ?? availableBundles[0]?.id ?? null;
|
|
81
|
+
if (candidateId !== selectedStackId) {
|
|
82
|
+
setSelectedStackId(candidateId);
|
|
83
|
+
}
|
|
84
|
+
}, [availableBundles, initialStackId, selectedStackId]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const update = () => {
|
|
87
|
+
setJsSessions(jsSessionSources.flatMap((source) => source.broker.listSessions().map((session) => ({
|
|
88
|
+
...session,
|
|
89
|
+
sourceId: source.id,
|
|
90
|
+
sourceTitle: source.title,
|
|
91
|
+
}))));
|
|
92
|
+
};
|
|
93
|
+
update();
|
|
94
|
+
const unsubscribes = jsSessionSources.map((source) => source.broker.subscribe(update));
|
|
95
|
+
return () => {
|
|
96
|
+
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
97
|
+
};
|
|
98
|
+
}, [jsSessionSources]);
|
|
99
|
+
const artifacts = useSelector((s) => s.hypercardArtifacts?.byId ?? {});
|
|
100
|
+
const sessions = useSelector((s) => s.runtimeSessions?.sessions ?? {});
|
|
101
|
+
const windowingSessions = useSelector((s) => s.windowing?.sessions ?? {});
|
|
102
|
+
const bundlesById = useMemo(() => new Map(availableBundles.map((bundle) => [bundle.id, bundle])), [availableBundles]);
|
|
103
|
+
const activeBundle = availableBundles.find((bundle) => bundle.id === selectedStackId) ?? availableBundles[0];
|
|
104
|
+
const bundleSurfaces = activeBundle ? Object.values(activeBundle.surfaces) : [];
|
|
105
|
+
const runtimeArtifacts = Object.values(artifacts).filter((artifact) => artifact.runtimeSurfaceId);
|
|
106
|
+
const td = { padding: '3px 8px', fontSize: 11, borderBottom: '1px solid #ccc', verticalAlign: 'top', color: '#111' };
|
|
107
|
+
const th = { ...td, fontWeight: 700, background: '#e8e8f0', position: 'sticky', top: 0, color: '#111' };
|
|
108
|
+
const launchBundleSurface = (bundleId, surfaceId) => {
|
|
109
|
+
const bundle = bundlesById.get(bundleId);
|
|
110
|
+
if (!bundle) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const payload = buildBundleSurfaceWindowPayload(bundle, surfaceId);
|
|
114
|
+
if (!payload) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
dispatch(openWindow(payload));
|
|
118
|
+
};
|
|
119
|
+
const sessionCurrentSurfaceIds = useMemo(() => {
|
|
120
|
+
const entries = Object.entries(sessions).map(([sessionId, session]) => {
|
|
121
|
+
const nav = windowingSessions[sessionId]?.nav;
|
|
122
|
+
const navSurface = Array.isArray(nav) && nav.length > 0 && typeof nav[nav.length - 1]?.surface === 'string'
|
|
123
|
+
? nav[nav.length - 1]?.surface ?? null
|
|
124
|
+
: null;
|
|
125
|
+
const fallbackSurface = Object.keys(session.surfaceState ?? {})[0] ?? null;
|
|
126
|
+
return [sessionId, navSurface ?? fallbackSurface];
|
|
127
|
+
});
|
|
128
|
+
return new Map(entries);
|
|
129
|
+
}, [sessions, windowingSessions]);
|
|
130
|
+
return (_jsxs("div", { style: { padding: 12, fontFamily: 'monospace', fontSize: 12, color: '#111', overflow: 'auto', height: '100%' }, children: [_jsxs(Section, { title: activeBundle ? `📦 Bundle: ${activeBundle.name} (${activeBundle.id})` : '📦 Bundle: (none provided)', children: [availableBundles.length > 1 && (_jsxs("label", { style: { display: 'grid', gap: 4, marginBottom: 8, fontSize: 11 }, children: [_jsx("span", { style: { color: '#555' }, children: "Selected bundle" }), _jsx("select", { value: activeBundle?.id ?? '', onChange: (event) => setSelectedStackId(event.target.value), style: { width: 260, padding: '4px 6px', fontFamily: 'inherit', fontSize: 12 }, children: availableBundles.map((bundle) => (_jsxs("option", { value: bundle.id, children: [bundle.name, " (", bundle.id, ")"] }, bundle.id))) })] })), _jsxs("div", { style: { fontSize: 11, color: '#555', marginBottom: 6 }, children: ["homeSurface: ", _jsx("code", { children: activeBundle?.homeSurface ?? '—' }), " \u00B7 ", bundleSurfaces.length, " predefined surfaces"] }), _jsxs("table", { style: { width: '100%', borderCollapse: 'collapse' }, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: th, children: "Icon" }), _jsx("th", { style: th, children: "ID" }), _jsx("th", { style: th, children: "Title" }), _jsx("th", { style: th, children: "Type" }), _jsx("th", { style: th, children: "Actions" })] }) }), _jsx("tbody", { children: bundleSurfaces.map(c => (_jsxs("tr", { children: [_jsx("td", { style: td, children: c.icon }), _jsx("td", { style: td, children: _jsx("code", { children: c.id }) }), _jsx("td", { style: td, children: c.title }), _jsx("td", { style: td, children: c.type }), _jsx("td", { style: td, children: _jsxs("div", { style: { display: 'flex', gap: 6, flexWrap: 'wrap' }, children: [_jsx("button", { onClick: () => activeBundle && launchBundleSurface(activeBundle.id, c.id), style: {
|
|
131
|
+
fontSize: 10,
|
|
132
|
+
padding: '1px 6px',
|
|
133
|
+
borderRadius: 3,
|
|
134
|
+
border: '1px solid #999',
|
|
135
|
+
background: '#f0f0f0',
|
|
136
|
+
cursor: 'pointer',
|
|
137
|
+
}, children: "\u25B6 Open" }), surfaceSource(c) ? (_jsx("button", { onClick: () => openCodeEditor(dispatch, { ownerAppId, surfaceId: c.id }, surfaceSource(c) ?? ''), style: {
|
|
138
|
+
fontSize: 10,
|
|
139
|
+
padding: '1px 6px',
|
|
140
|
+
borderRadius: 3,
|
|
141
|
+
border: '1px solid #999',
|
|
142
|
+
background: '#f0f0f0',
|
|
143
|
+
cursor: 'pointer',
|
|
144
|
+
}, children: "\u270F\uFE0F Edit" })) : null] }) })] }, c.id))) })] })] }), _jsxs(Section, { title: `🃏 Runtime Surface Registry (${registryCards.length})`, children: [_jsx("div", { style: { fontSize: 11, color: '#555', marginBottom: 8 }, children: "Injected runtime surfaces from artifacts and ad-hoc registration appear here. Built-in bundle surfaces are listed above." }), registryCards.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: '#555' }, children: "No runtime surfaces registered yet." })) : (registryCards.map((surface) => (_jsxs("div", { style: { marginBottom: 12, border: '1px solid #ccc', borderRadius: 4, padding: 8 }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 6 }, children: [_jsx("code", { style: { fontWeight: 700 }, children: surface.surfaceId }), _jsx(Badge, { text: "registered", color: "#2d6a4f" }), _jsx("span", { style: { fontSize: 10, color: '#555' }, children: new Date(surface.registeredAt).toLocaleTimeString() }), _jsx("button", { onClick: () => openCodeEditor(dispatch, { ownerAppId, surfaceId: surface.surfaceId }, surface.code, surface.packId), style: {
|
|
145
|
+
fontSize: 10, padding: '1px 6px', borderRadius: 3,
|
|
146
|
+
border: '1px solid #999', background: '#f0f0f0', cursor: 'pointer',
|
|
147
|
+
marginLeft: 'auto',
|
|
148
|
+
}, children: "\u270F\uFE0F Edit" })] }), _jsxs("div", { style: { fontSize: 10, color: '#555', marginTop: 2 }, children: ["code: ", surface.code.length, " chars, ", surface.code.split('\n').length, " lines"] }), _jsx(CodePreview, { code: surface.code })] }, surface.surfaceId))))] }), _jsx(Section, { title: `🗃 Artifacts with Runtime Surfaces (${runtimeArtifacts.length})`, children: runtimeArtifacts.length === 0 ? (_jsx("div", { style: { fontSize: 11, color: '#555' }, children: "No artifacts with runtime surface code." })) : (_jsxs("table", { style: { width: '100%', borderCollapse: 'collapse' }, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: th, children: "Artifact ID" }), _jsx("th", { style: th, children: "Surface ID" }), _jsx("th", { style: th, children: "Title" }), _jsx("th", { style: th, children: "Injection" }), _jsx("th", { style: th, children: "Source" })] }) }), _jsx("tbody", { children: runtimeArtifacts.map(a => (_jsxs("tr", { children: [_jsx("td", { style: td, children: _jsx("code", { children: a.id }) }), _jsx("td", { style: td, children: _jsx("code", { children: a.runtimeSurfaceId }) }), _jsx("td", { style: td, children: a.title }), _jsxs("td", { style: td, children: [a.injectionStatus === 'injected' && _jsx(Badge, { text: "injected", color: "#2d6a4f" }), a.injectionStatus === 'pending' && _jsx(Badge, { text: "pending", color: "#e67e22" }), a.injectionStatus === 'failed' && _jsx(Badge, { text: "failed", color: "#c0392b" }), !a.injectionStatus && _jsx(Badge, { text: "unknown", color: "#666" }), a.injectionError && _jsx("div", { style: { fontSize: 10, color: '#c0392b', marginTop: 2 }, children: a.injectionError })] }), _jsx("td", { style: td, children: a.runtimeSurfaceCode ? `${a.runtimeSurfaceCode.length} chars` : '—' })] }, a.id))) })] })) }), _jsx(Section, { title: `⚙️ Plugin Sessions (${Object.keys(sessions).length})`, children: Object.keys(sessions).length === 0 ? (_jsx("div", { style: { fontSize: 11, color: '#555' }, children: "No active plugin sessions." })) : (_jsxs("table", { style: { width: '100%', borderCollapse: 'collapse' }, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { style: th, children: "Session ID" }), _jsx("th", { style: th, children: "Bundle" }), _jsx("th", { style: th, children: "Status" }), _jsx("th", { style: th, children: "Current Surface" }), _jsx("th", { style: th, children: "Actions" })] }) }), _jsx("tbody", { children: Object.entries(sessions).map(([sid, s]) => (_jsxs("tr", { children: [_jsx("td", { style: td, children: _jsx("code", { children: sid }) }), _jsx("td", { style: td, children: s.bundleId }), _jsxs("td", { style: td, children: [s.status === 'ready' && _jsx(Badge, { text: "ready", color: "#2d6a4f" }), s.status === 'loading' && _jsx(Badge, { text: "loading", color: "#e67e22" }), s.status === 'error' && _jsx(Badge, { text: "error", color: "#c0392b" }), s.error && _jsx("div", { style: { fontSize: 10, color: '#c0392b', marginTop: 2 }, children: s.error })] }), _jsx("td", { style: td, children: sessionCurrentSurfaceIds.get(sid) ? (_jsx("code", { style: { fontSize: 10 }, children: sessionCurrentSurfaceIds.get(sid) })) : (_jsx("span", { style: { color: '#555' }, children: "\u2014" })) }), _jsx("td", { style: td, children: (() => {
|
|
149
|
+
const currentSurfaceId = sessionCurrentSurfaceIds.get(sid);
|
|
150
|
+
if (!currentSurfaceId) {
|
|
151
|
+
return _jsx("span", { style: { color: '#555' }, children: "\u2014" });
|
|
152
|
+
}
|
|
153
|
+
const bundle = bundlesById.get(s.bundleId);
|
|
154
|
+
const surface = bundle?.surfaces[currentSurfaceId];
|
|
155
|
+
const source = surface ? surfaceSource(surface) : null;
|
|
156
|
+
return (_jsxs("div", { style: { display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("button", { onClick: () => launchBundleSurface(s.bundleId, currentSurfaceId), style: {
|
|
157
|
+
fontSize: 10,
|
|
158
|
+
padding: '1px 6px',
|
|
159
|
+
borderRadius: 3,
|
|
160
|
+
border: '1px solid #999',
|
|
161
|
+
background: '#f0f0f0',
|
|
162
|
+
cursor: 'pointer',
|
|
163
|
+
}, children: "\u25B6 Open" }), source ? (_jsx("button", { onClick: () => openCodeEditor(dispatch, { ownerAppId, surfaceId: currentSurfaceId }, source, s.surfaceTypes?.[currentSurfaceId]), style: {
|
|
164
|
+
fontSize: 10,
|
|
165
|
+
padding: '1px 6px',
|
|
166
|
+
borderRadius: 3,
|
|
167
|
+
border: '1px solid #999',
|
|
168
|
+
background: '#f0f0f0',
|
|
169
|
+
cursor: 'pointer',
|
|
170
|
+
}, children: "\u270F\uFE0F Edit" })) : null] }));
|
|
171
|
+
})() })] }, sid))) })] })) }), _jsxs(Section, { title: `🧪 JS Sessions (${jsSessions.length})`, children: [_jsxs("div", { style: { fontSize: 11, color: '#555', marginBottom: 8 }, children: ["Plain JavaScript sessions no longer get a full operator table here. Use ", _jsx("strong", { children: "Task Manager" }), " for reset/dispose/focus actions and cross-source session management."] }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }, children: [_jsx("div", { style: { fontSize: 11, color: '#555' }, children: jsSessions.length === 0
|
|
172
|
+
? 'No active JS sessions.'
|
|
173
|
+
: `${jsSessions.length} JS session${jsSessions.length === 1 ? '' : 's'} across ${jsSessionSources.length} source${jsSessionSources.length === 1 ? '' : 's'}.` }), _jsx("button", { onClick: () => dispatch(openWindow(buildTaskManagerWindowPayload())), style: {
|
|
174
|
+
fontSize: 10,
|
|
175
|
+
padding: '1px 6px',
|
|
176
|
+
borderRadius: 3,
|
|
177
|
+
border: '1px solid #999',
|
|
178
|
+
background: '#f0f0f0',
|
|
179
|
+
cursor: 'pointer',
|
|
180
|
+
}, children: "\uD83D\uDDC2\uFE0F Open Task Manager" })] })] })] }));
|
|
181
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsSessionBroker } from '../../repl/jsSessionBroker';
|
|
2
|
+
export interface JsSessionDebugSource {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
broker: JsSessionBroker;
|
|
6
|
+
}
|
|
7
|
+
export declare function registerJsSessionDebugSource(source: JsSessionDebugSource): void;
|
|
8
|
+
export declare function clearRegisteredJsSessionDebugSources(): void;
|
|
9
|
+
export declare function getRegisteredJsSessionDebugSources(): JsSessionDebugSource[];
|
|
10
|
+
export declare function subscribeJsSessionDebugSources(listener: () => void): () => void;
|
|
11
|
+
export declare function useRegisteredJsSessionDebugSources(): JsSessionDebugSource[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
const registeredSources = new Map();
|
|
3
|
+
const listeners = new Set();
|
|
4
|
+
let registeredSourcesSnapshot = [];
|
|
5
|
+
function refreshSnapshot() {
|
|
6
|
+
registeredSourcesSnapshot = Array.from(registeredSources.values());
|
|
7
|
+
}
|
|
8
|
+
function emitChange() {
|
|
9
|
+
for (const listener of listeners) {
|
|
10
|
+
listener();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function registerJsSessionDebugSource(source) {
|
|
14
|
+
if (!source?.id || !source.broker) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (registeredSources.get(source.id) === source) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
registeredSources.set(source.id, source);
|
|
21
|
+
refreshSnapshot();
|
|
22
|
+
emitChange();
|
|
23
|
+
}
|
|
24
|
+
export function clearRegisteredJsSessionDebugSources() {
|
|
25
|
+
if (registeredSources.size === 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
registeredSources.clear();
|
|
29
|
+
refreshSnapshot();
|
|
30
|
+
emitChange();
|
|
31
|
+
}
|
|
32
|
+
export function getRegisteredJsSessionDebugSources() {
|
|
33
|
+
return registeredSourcesSnapshot;
|
|
34
|
+
}
|
|
35
|
+
export function subscribeJsSessionDebugSources(listener) {
|
|
36
|
+
listeners.add(listener);
|
|
37
|
+
return () => {
|
|
38
|
+
listeners.delete(listener);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function useRegisteredJsSessionDebugSources() {
|
|
42
|
+
return useSyncExternalStore(subscribeJsSessionDebugSources, getRegisteredJsSessionDebugSources, getRegisteredJsSessionDebugSources);
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RuntimeBundleDefinition } from '@go-go-golems/os-core';
|
|
2
|
+
import type { OpenWindowPayload } from '@go-go-golems/os-core/desktop-core';
|
|
3
|
+
export declare const HYPERCARD_RUNTIME_DEBUG_APP_ID = "hypercard-runtime-debug";
|
|
4
|
+
export declare const HYPERCARD_RUNTIME_DEBUG_INSTANCE_ID = "stacks";
|
|
5
|
+
export interface BuildRuntimeDebugWindowPayloadOptions {
|
|
6
|
+
appId?: string;
|
|
7
|
+
instanceId?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
icon?: string;
|
|
10
|
+
bounds?: OpenWindowPayload['bounds'];
|
|
11
|
+
dedupeKey?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RuntimeDebugAppWindowProps {
|
|
14
|
+
ownerAppId: string;
|
|
15
|
+
instanceId: string;
|
|
16
|
+
bundles?: RuntimeBundleDefinition[];
|
|
17
|
+
}
|
|
18
|
+
export declare function buildRuntimeDebugWindowPayload(options?: BuildRuntimeDebugWindowPayloadOptions): OpenWindowPayload;
|
|
19
|
+
export declare function RuntimeDebugAppWindow({ ownerAppId, instanceId, bundles }: RuntimeDebugAppWindowProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { RuntimeSurfaceDebugWindow } from './RuntimeSurfaceDebugWindow';
|
|
3
|
+
export const HYPERCARD_RUNTIME_DEBUG_APP_ID = 'hypercard-runtime-debug';
|
|
4
|
+
export const HYPERCARD_RUNTIME_DEBUG_INSTANCE_ID = 'stacks';
|
|
5
|
+
export function buildRuntimeDebugWindowPayload(options = {}) {
|
|
6
|
+
const appId = options.appId ?? HYPERCARD_RUNTIME_DEBUG_APP_ID;
|
|
7
|
+
const instanceId = options.instanceId ?? HYPERCARD_RUNTIME_DEBUG_INSTANCE_ID;
|
|
8
|
+
return {
|
|
9
|
+
id: `window:${appId}:${instanceId}`,
|
|
10
|
+
title: options.title ?? 'Stacks & Cards',
|
|
11
|
+
icon: options.icon ?? '🔧',
|
|
12
|
+
bounds: options.bounds ?? { x: 80, y: 30, w: 560, h: 480 },
|
|
13
|
+
content: {
|
|
14
|
+
kind: 'app',
|
|
15
|
+
appKey: `${appId}:${instanceId}`,
|
|
16
|
+
},
|
|
17
|
+
dedupeKey: options.dedupeKey ?? `${appId}:${instanceId}`,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function RuntimeDebugAppWindow({ ownerAppId, instanceId, bundles }) {
|
|
21
|
+
if (instanceId !== HYPERCARD_RUNTIME_DEBUG_INSTANCE_ID) {
|
|
22
|
+
return (_jsxs("section", { style: { padding: 12, display: 'grid', gap: 8 }, children: [_jsx("strong", { children: "Stacks & Cards" }), _jsxs("span", { children: ["Unknown runtime debug window instance: ", instanceId] })] }));
|
|
23
|
+
}
|
|
24
|
+
return _jsx(RuntimeSurfaceDebugWindow, { ownerAppId: ownerAppId, bundles: bundles });
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RuntimeBundleDefinition } from '@go-go-golems/os-core';
|
|
2
|
+
export declare function registerRuntimeDebugStacks(stacks: readonly RuntimeBundleDefinition[]): void;
|
|
3
|
+
export declare function getRegisteredRuntimeDebugStacks(): RuntimeBundleDefinition[];
|
|
4
|
+
export declare function clearRegisteredRuntimeDebugStacks(): void;
|
|
5
|
+
export declare function subscribeRuntimeDebugStacks(listener: () => void): () => void;
|
|
6
|
+
export declare function useRegisteredRuntimeDebugStacks(): RuntimeBundleDefinition[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
const registeredStacks = new Map();
|
|
3
|
+
const listeners = new Set();
|
|
4
|
+
let registeredStacksSnapshot = [];
|
|
5
|
+
function refreshSnapshot() {
|
|
6
|
+
registeredStacksSnapshot = Array.from(registeredStacks.values());
|
|
7
|
+
}
|
|
8
|
+
function emitChange() {
|
|
9
|
+
for (const listener of listeners) {
|
|
10
|
+
listener();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function registerRuntimeDebugStacks(stacks) {
|
|
14
|
+
let changed = false;
|
|
15
|
+
for (const bundle of stacks) {
|
|
16
|
+
if (!bundle?.id) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (registeredStacks.get(bundle.id) === bundle) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
registeredStacks.set(bundle.id, bundle);
|
|
23
|
+
changed = true;
|
|
24
|
+
}
|
|
25
|
+
if (changed) {
|
|
26
|
+
refreshSnapshot();
|
|
27
|
+
emitChange();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function getRegisteredRuntimeDebugStacks() {
|
|
31
|
+
return registeredStacksSnapshot;
|
|
32
|
+
}
|
|
33
|
+
export function clearRegisteredRuntimeDebugStacks() {
|
|
34
|
+
if (registeredStacks.size === 0) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
registeredStacks.clear();
|
|
38
|
+
refreshSnapshot();
|
|
39
|
+
emitChange();
|
|
40
|
+
}
|
|
41
|
+
export function subscribeRuntimeDebugStacks(listener) {
|
|
42
|
+
listeners.add(listener);
|
|
43
|
+
return () => {
|
|
44
|
+
listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function useRegisteredRuntimeDebugStacks() {
|
|
48
|
+
return useSyncExternalStore(subscribeRuntimeDebugStacks, getRegisteredRuntimeDebugStacks, getRegisteredRuntimeDebugStacks);
|
|
49
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface CodeEditorWindowProps {
|
|
2
|
+
surfaceId: string;
|
|
3
|
+
initialCode: string;
|
|
4
|
+
packId?: string;
|
|
5
|
+
onSave?: (surfaceId: string, code: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function CodeEditorWindow({ surfaceId, initialCode, packId, onSave }: CodeEditorWindowProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { EditorView, keymap, lineNumbers, drawSelection, highlightActiveLine } from '@codemirror/view';
|
|
4
|
+
import { EditorState } from '@codemirror/state';
|
|
5
|
+
import { javascript } from '@codemirror/lang-javascript';
|
|
6
|
+
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
7
|
+
import { bracketMatching, foldGutter, indentOnInput, syntaxHighlighting } from '@codemirror/language';
|
|
8
|
+
import { oneDark } from '@codemirror/theme-one-dark';
|
|
9
|
+
import { classHighlighter } from '@lezer/highlight';
|
|
10
|
+
import { hasRuntimeSurface, registerRuntimeSurface } from '../../plugin-runtime';
|
|
11
|
+
export function CodeEditorWindow({ surfaceId, initialCode, packId, onSave }) {
|
|
12
|
+
const editorRef = useRef(null);
|
|
13
|
+
const viewRef = useRef(null);
|
|
14
|
+
const [status, setStatus] = useState({ type: 'idle' });
|
|
15
|
+
const [dirty, setDirty] = useState(false);
|
|
16
|
+
// Create editor on mount
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!editorRef.current)
|
|
19
|
+
return;
|
|
20
|
+
const state = EditorState.create({
|
|
21
|
+
doc: initialCode,
|
|
22
|
+
extensions: [
|
|
23
|
+
lineNumbers(),
|
|
24
|
+
history(),
|
|
25
|
+
drawSelection(),
|
|
26
|
+
indentOnInput(),
|
|
27
|
+
bracketMatching(),
|
|
28
|
+
foldGutter(),
|
|
29
|
+
highlightActiveLine(),
|
|
30
|
+
javascript(),
|
|
31
|
+
syntaxHighlighting(classHighlighter),
|
|
32
|
+
oneDark,
|
|
33
|
+
keymap.of([
|
|
34
|
+
...defaultKeymap,
|
|
35
|
+
...historyKeymap,
|
|
36
|
+
{
|
|
37
|
+
key: 'Mod-s',
|
|
38
|
+
run: () => {
|
|
39
|
+
// Trigger save via DOM event — picked up by handleSave
|
|
40
|
+
editorRef.current?.dispatchEvent(new CustomEvent('editor-save'));
|
|
41
|
+
return true;
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
]),
|
|
45
|
+
EditorView.updateListener.of((update) => {
|
|
46
|
+
if (update.docChanged) {
|
|
47
|
+
setDirty(true);
|
|
48
|
+
setStatus({ type: 'idle' });
|
|
49
|
+
}
|
|
50
|
+
}),
|
|
51
|
+
EditorView.theme({
|
|
52
|
+
'&': { height: '100%', fontSize: '13px' },
|
|
53
|
+
'.cm-scroller': { overflow: 'auto' },
|
|
54
|
+
'.cm-content': { fontFamily: 'monospace' },
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
const view = new EditorView({ state, parent: editorRef.current });
|
|
59
|
+
viewRef.current = view;
|
|
60
|
+
return () => {
|
|
61
|
+
view.destroy();
|
|
62
|
+
viewRef.current = null;
|
|
63
|
+
};
|
|
64
|
+
// Only create once on mount
|
|
65
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
|
+
}, []);
|
|
67
|
+
// Listen for Ctrl+S custom event from editor keymap
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const el = editorRef.current;
|
|
70
|
+
if (!el)
|
|
71
|
+
return;
|
|
72
|
+
const listener = () => handleSaveRef.current();
|
|
73
|
+
el.addEventListener('editor-save', listener);
|
|
74
|
+
return () => el.removeEventListener('editor-save', listener);
|
|
75
|
+
}, []);
|
|
76
|
+
const handleSaveRef = useRef(() => { });
|
|
77
|
+
const handleSave = useCallback(() => {
|
|
78
|
+
const view = viewRef.current;
|
|
79
|
+
if (!view)
|
|
80
|
+
return;
|
|
81
|
+
const code = view.state.doc.toString();
|
|
82
|
+
try {
|
|
83
|
+
const normalizedPackId = typeof packId === 'string' ? packId.trim() : '';
|
|
84
|
+
if (!normalizedPackId) {
|
|
85
|
+
throw new Error(`Runtime surface packId is required for ${surfaceId}`);
|
|
86
|
+
}
|
|
87
|
+
registerRuntimeSurface(surfaceId, code, normalizedPackId);
|
|
88
|
+
setStatus({ type: 'saved', message: `Injected ${surfaceId} — ${code.length} chars` });
|
|
89
|
+
setDirty(false);
|
|
90
|
+
onSave?.(surfaceId, code);
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
setStatus({ type: 'error', message: err instanceof Error ? err.message : String(err) });
|
|
94
|
+
}
|
|
95
|
+
}, [packId, surfaceId, onSave]);
|
|
96
|
+
handleSaveRef.current = handleSave;
|
|
97
|
+
const handleRevert = useCallback(() => {
|
|
98
|
+
const view = viewRef.current;
|
|
99
|
+
if (!view)
|
|
100
|
+
return;
|
|
101
|
+
view.dispatch({
|
|
102
|
+
changes: { from: 0, to: view.state.doc.length, insert: initialCode },
|
|
103
|
+
});
|
|
104
|
+
setDirty(false);
|
|
105
|
+
setStatus({ type: 'idle' });
|
|
106
|
+
}, [initialCode]);
|
|
107
|
+
const isRegistered = hasRuntimeSurface(surfaceId);
|
|
108
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', background: '#282c34' }, children: [_jsxs("div", { style: {
|
|
109
|
+
display: 'flex', alignItems: 'center', gap: 8,
|
|
110
|
+
padding: '6px 10px', borderBottom: '1px solid #444',
|
|
111
|
+
fontSize: 12, color: '#abb2bf', fontFamily: 'monospace',
|
|
112
|
+
}, children: [_jsx("span", { style: { fontWeight: 700, color: '#e5c07b' }, children: surfaceId }), isRegistered && (_jsx("span", { style: {
|
|
113
|
+
fontSize: 10, padding: '1px 6px', borderRadius: 3,
|
|
114
|
+
background: '#2d6a4f', color: '#fff', fontWeight: 600,
|
|
115
|
+
}, children: "registered" })), dirty && (_jsx("span", { style: { fontSize: 10, color: '#e5c07b' }, children: "\u25CF modified" }))] }), _jsx("div", { ref: editorRef, style: { flex: 1, overflow: 'hidden' } }), _jsxs("div", { style: {
|
|
116
|
+
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
|
117
|
+
padding: '6px 10px', borderTop: '1px solid #444',
|
|
118
|
+
fontSize: 11, color: '#abb2bf', fontFamily: 'monospace',
|
|
119
|
+
}, children: [_jsxs("div", { children: [status.type === 'saved' && (_jsxs("span", { style: { color: '#98c379' }, children: ["\u2713 ", status.message] })), status.type === 'error' && (_jsxs("span", { style: { color: '#e06c75' }, children: ["\u2717 ", status.message] })), status.type === 'idle' && (_jsx("span", { style: { color: '#666' }, children: packId ? 'Ctrl+S to save & inject' : 'Runtime surface packId required before injection' }))] }), _jsxs("div", { style: { display: 'flex', gap: 6 }, children: [_jsx("button", { onClick: handleRevert, disabled: !dirty, style: {
|
|
120
|
+
padding: '3px 10px', fontSize: 11, borderRadius: 3,
|
|
121
|
+
border: '1px solid #555', background: '#3e4451', color: '#abb2bf',
|
|
122
|
+
cursor: dirty ? 'pointer' : 'default', opacity: dirty ? 1 : 0.4,
|
|
123
|
+
}, children: "\u21BB Revert" }), _jsx("button", { onClick: handleSave, disabled: !packId, style: {
|
|
124
|
+
padding: '3px 10px', fontSize: 11, borderRadius: 3,
|
|
125
|
+
border: '1px solid #2d6a4f', background: '#2d6a4f', color: '#fff',
|
|
126
|
+
cursor: packId ? 'pointer' : 'default', fontWeight: 600, opacity: packId ? 1 : 0.5,
|
|
127
|
+
}, children: "\uD83D\uDCBE Save & Inject" })] })] })] }));
|
|
128
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple module to pass initial code to the CodeEditorWindow.
|
|
3
|
+
* Since the editor opens as a windowing-system app (by appKey string),
|
|
4
|
+
* we can't pass rich props directly. Instead, the caller stashes the
|
|
5
|
+
* code here before dispatching openWindow, and the editor reads it on mount.
|
|
6
|
+
*/
|
|
7
|
+
import { type OpenWindowPayload, openWindow } from '@go-go-golems/os-core/desktop-core';
|
|
8
|
+
import { type RuntimeSurfaceRef } from './runtimeSurfaceRef';
|
|
9
|
+
type WindowDispatch = (action: ReturnType<typeof openWindow>) => unknown;
|
|
10
|
+
export declare function buildCodeEditorWindowPayload(ref: RuntimeSurfaceRef): OpenWindowPayload;
|
|
11
|
+
/** Stash code for a runtime surface editor and open the window. */
|
|
12
|
+
export declare function openCodeEditor(dispatch: WindowDispatch, ref: RuntimeSurfaceRef, code: string, packId?: string): void;
|
|
13
|
+
/** Get the initial code for a runtime surface editor. Falls back to registry. */
|
|
14
|
+
export declare function getEditorInitialCode(ref: RuntimeSurfaceRef): string;
|
|
15
|
+
export declare function getEditorInitialPackId(ref: RuntimeSurfaceRef): string | undefined;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple module to pass initial code to the CodeEditorWindow.
|
|
3
|
+
* Since the editor opens as a windowing-system app (by appKey string),
|
|
4
|
+
* we can't pass rich props directly. Instead, the caller stashes the
|
|
5
|
+
* code here before dispatching openWindow, and the editor reads it on mount.
|
|
6
|
+
*/
|
|
7
|
+
import { openWindow } from '@go-go-golems/os-core/desktop-core';
|
|
8
|
+
import { getPendingRuntimeSurfaces } from '../../plugin-runtime';
|
|
9
|
+
import { buildRuntimeSurfaceEditorAppKey, HYPERCARD_TOOLS_APP_ID, } from './runtimeSurfaceRef';
|
|
10
|
+
const pendingCode = new Map();
|
|
11
|
+
const pendingPackIds = new Map();
|
|
12
|
+
function codeKey(ref) {
|
|
13
|
+
return `${ref.ownerAppId}::${ref.surfaceId}`;
|
|
14
|
+
}
|
|
15
|
+
export function buildCodeEditorWindowPayload(ref) {
|
|
16
|
+
const key = codeKey(ref);
|
|
17
|
+
const appKey = buildRuntimeSurfaceEditorAppKey(ref);
|
|
18
|
+
return {
|
|
19
|
+
id: `window:${HYPERCARD_TOOLS_APP_ID}:editor:${key}`,
|
|
20
|
+
title: `✏️ ${ref.surfaceId}`,
|
|
21
|
+
icon: '✏️',
|
|
22
|
+
bounds: { x: 100, y: 40, w: 600, h: 500 },
|
|
23
|
+
content: { kind: 'app', appKey },
|
|
24
|
+
dedupeKey: `runtime-surface-editor:${key}`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** Stash code for a runtime surface editor and open the window. */
|
|
28
|
+
export function openCodeEditor(dispatch, ref, code, packId) {
|
|
29
|
+
pendingCode.set(codeKey(ref), code);
|
|
30
|
+
if (typeof packId === 'string' && packId.trim().length > 0) {
|
|
31
|
+
pendingPackIds.set(codeKey(ref), packId.trim());
|
|
32
|
+
}
|
|
33
|
+
dispatch(openWindow(buildCodeEditorWindowPayload(ref)));
|
|
34
|
+
}
|
|
35
|
+
/** Get the initial code for a runtime surface editor. Falls back to registry. */
|
|
36
|
+
export function getEditorInitialCode(ref) {
|
|
37
|
+
const key = codeKey(ref);
|
|
38
|
+
const stashed = pendingCode.get(key);
|
|
39
|
+
if (stashed !== undefined) {
|
|
40
|
+
pendingCode.delete(key);
|
|
41
|
+
return stashed;
|
|
42
|
+
}
|
|
43
|
+
// Fallback: look up from the runtime surface registry.
|
|
44
|
+
const surfaces = getPendingRuntimeSurfaces();
|
|
45
|
+
const found = surfaces.find((surface) => surface.surfaceId === ref.surfaceId);
|
|
46
|
+
return found?.code ?? `// No code found for surface: ${ref.surfaceId}\n`;
|
|
47
|
+
}
|
|
48
|
+
export function getEditorInitialPackId(ref) {
|
|
49
|
+
const key = codeKey(ref);
|
|
50
|
+
const stashed = pendingPackIds.get(key);
|
|
51
|
+
if (stashed !== undefined) {
|
|
52
|
+
pendingPackIds.delete(key);
|
|
53
|
+
return stashed;
|
|
54
|
+
}
|
|
55
|
+
const surfaces = getPendingRuntimeSurfaces();
|
|
56
|
+
const found = surfaces.find((surface) => surface.surfaceId === ref.surfaceId);
|
|
57
|
+
return found?.packId;
|
|
58
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const HYPERCARD_TOOLS_APP_ID = "hypercard-tools";
|
|
2
|
+
export interface RuntimeSurfaceRef {
|
|
3
|
+
ownerAppId: string;
|
|
4
|
+
surfaceId: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function encodeRuntimeSurfaceEditorInstanceId(ref: RuntimeSurfaceRef): string;
|
|
7
|
+
export declare function decodeRuntimeSurfaceEditorInstanceId(instanceId: string): RuntimeSurfaceRef | null;
|
|
8
|
+
export declare function buildRuntimeSurfaceEditorAppKey(ref: RuntimeSurfaceRef): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export const HYPERCARD_TOOLS_APP_ID = 'hypercard-tools';
|
|
2
|
+
const INSTANCE_KIND_EDITOR = 'editor';
|
|
3
|
+
const INSTANCE_DELIMITER = '~';
|
|
4
|
+
const APP_ID_RE = /^[a-z][a-z0-9-]*$/;
|
|
5
|
+
function clean(value) {
|
|
6
|
+
if (typeof value !== 'string') {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
11
|
+
}
|
|
12
|
+
function assertValidOwnerAppId(ownerAppId) {
|
|
13
|
+
if (!APP_ID_RE.test(ownerAppId)) {
|
|
14
|
+
throw new Error(`Invalid owner app id "${ownerAppId}". Expected /^[a-z][a-z0-9-]*$/.`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function assertValidSurfaceId(surfaceId) {
|
|
18
|
+
if (surfaceId.trim().length === 0) {
|
|
19
|
+
throw new Error('Invalid runtime surface id: expected a non-empty surface id.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function encodeRuntimeSurfaceEditorInstanceId(ref) {
|
|
23
|
+
const ownerAppId = clean(ref.ownerAppId);
|
|
24
|
+
const surfaceId = clean(ref.surfaceId);
|
|
25
|
+
if (!ownerAppId) {
|
|
26
|
+
throw new Error('Invalid owner app id: expected a non-empty owner app id.');
|
|
27
|
+
}
|
|
28
|
+
if (!surfaceId) {
|
|
29
|
+
throw new Error('Invalid runtime surface id: expected a non-empty surface id.');
|
|
30
|
+
}
|
|
31
|
+
assertValidOwnerAppId(ownerAppId);
|
|
32
|
+
assertValidSurfaceId(surfaceId);
|
|
33
|
+
return `${INSTANCE_KIND_EDITOR}${INSTANCE_DELIMITER}${encodeURIComponent(ownerAppId)}${INSTANCE_DELIMITER}${encodeURIComponent(surfaceId)}`;
|
|
34
|
+
}
|
|
35
|
+
export function decodeRuntimeSurfaceEditorInstanceId(instanceId) {
|
|
36
|
+
const raw = clean(instanceId);
|
|
37
|
+
if (!raw) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
const parts = raw.split(INSTANCE_DELIMITER);
|
|
41
|
+
if (parts.length !== 3 || parts[0] !== INSTANCE_KIND_EDITOR) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const ownerAppId = clean(decodeURIComponent(parts[1] ?? ''));
|
|
46
|
+
const surfaceId = clean(decodeURIComponent(parts[2] ?? ''));
|
|
47
|
+
if (!ownerAppId || !surfaceId) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
assertValidOwnerAppId(ownerAppId);
|
|
51
|
+
assertValidSurfaceId(surfaceId);
|
|
52
|
+
return { ownerAppId, surfaceId };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function buildRuntimeSurfaceEditorAppKey(ref) {
|
|
59
|
+
return `${HYPERCARD_TOOLS_APP_ID}:${encodeRuntimeSurfaceEditorInstanceId(ref)}`;
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './artifacts/artifactRuntime';
|
|
2
|
+
export * from './artifacts/artifactsSelectors';
|
|
3
|
+
export * from './artifacts/artifactsSlice';
|
|
4
|
+
export * from './debug/RuntimeSurfaceDebugWindow';
|
|
5
|
+
export * from './debug/runtimeDebugApp';
|
|
6
|
+
export * from './debug/runtimeDebugRegistry';
|
|
7
|
+
export * from './debug/jsSessionDebugRegistry';
|
|
8
|
+
export * from './editor/CodeEditorWindow';
|
|
9
|
+
export * from './editor/editorLaunch';
|
|
10
|
+
export * from './editor/runtimeSurfaceRef';
|
|
11
|
+
export * from './task-manager';
|
|
12
|
+
export * from './timeline/hypercardCard';
|