mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
* fix(plugins): keep a workspace-scoped panel edit made just before a workspace switch The guest's persist queue holds atoms, not values, and the renderer stored each ui.persist under whichever workspace was active when it arrived. An edit made less than 0.4 s before a switch was flushed after the new workspace's hydrate, so workspace B's value was written under B and A's edit was lost. The host now names each ui.hydrate; the guest flushes pending writes before applying the next hydrate and tags ui.persist with the hydration its values came from, and the renderer stores the write under that hydration's workspace. (STA-13316) * test(plugins): cover workspace-persist hydration routing edge cases and add switch e2e Adds unit coverage for A->B->A routing, legacy (no hydration) persists, the eight-hydration window, plugin-scope entries, and guest flush/tagging; adds an Electron e2e that types in workspace A, switches inside the persist debounce, and checks both workspaces after switching back and after the panel's guest is recreated. Regenerates orca-testing.mjs after rebasing on main.
1600 lines
47 KiB
JavaScript
1600 lines
47 KiB
JavaScript
// Generated by config/scripts/plugin-build/declarative-runtime/build-plugin-declarative-runtime.mjs from
|
|
// src/shared/plugins/declarative/runtime/. Do not edit.
|
|
|
|
// src/shared/plugins/frames/plugin-frame-source-map-prelude.ts
|
|
var PLUGIN_SOURCE_MAP_HOOK = "orca.pluginSourceMap";
|
|
function pluginSourceMapPrelude(mappings) {
|
|
return `globalThis[Symbol.for(${JSON.stringify(PLUGIN_SOURCE_MAP_HOOK)})]?.(import.meta.url,${JSON.stringify(mappings)});`;
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-source-maps.ts
|
|
var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
var MAX_MODULES = 256;
|
|
var maps = /* @__PURE__ */ new Map();
|
|
function decodeSourceMapMappings(mappings) {
|
|
const lines = [];
|
|
const state2 = [0, 0, 0, 0, 0];
|
|
for (const line of mappings.split(";")) {
|
|
const segments = [];
|
|
state2[0] = 0;
|
|
for (const text of line.split(",")) {
|
|
const fields = [];
|
|
let value = 0;
|
|
let shift = 0;
|
|
for (const char of text) {
|
|
const digit = BASE64.indexOf(char);
|
|
value += (digit & 31) << shift;
|
|
if (digit & 32) {
|
|
shift += 5;
|
|
continue;
|
|
}
|
|
fields.push(value & 1 ? -(value >>> 1) : value >>> 1);
|
|
value = 0;
|
|
shift = 0;
|
|
}
|
|
for (const [index, delta] of fields.entries()) {
|
|
state2[index] += delta;
|
|
}
|
|
if (fields.length >= 4) {
|
|
segments.push([state2[0], state2[2], state2[3]]);
|
|
}
|
|
}
|
|
lines.push(segments);
|
|
}
|
|
return lines;
|
|
}
|
|
function registerPluginSourceMap(url, mappings) {
|
|
maps.delete(url);
|
|
maps.set(url, { mappings, lineOneShift: pluginSourceMapPrelude(mappings).length });
|
|
for (const oldest of maps.keys()) {
|
|
if (maps.size <= MAX_MODULES) {
|
|
break;
|
|
}
|
|
maps.delete(oldest);
|
|
}
|
|
}
|
|
function installPluginSourceMapHook() {
|
|
Object.assign(globalThis, { [Symbol.for(PLUGIN_SOURCE_MAP_HOOK)]: registerPluginSourceMap });
|
|
}
|
|
function originalPluginPosition(url, line, column) {
|
|
const map = maps.get(url);
|
|
if (!map) {
|
|
return null;
|
|
}
|
|
map.lines ??= decodeSourceMapMappings(map.mappings);
|
|
const generated = column - 1 - (line === 1 ? map.lineOneShift : 0);
|
|
let found;
|
|
for (const segment of map.lines[line - 1] ?? []) {
|
|
if (segment[0] > generated) {
|
|
break;
|
|
}
|
|
found = segment;
|
|
}
|
|
return found ? { line: found[1] + 1, column: found[2] + 1 } : null;
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-atoms.ts
|
|
var ATOM = /* @__PURE__ */ Symbol.for("orca.declarative.atom");
|
|
var activeTracker = null;
|
|
var ATOM_KEY = /^[A-Za-z0-9_.:-]{1,128}$/;
|
|
var keyed = /* @__PURE__ */ new Map();
|
|
var snapshot = {
|
|
memory: /* @__PURE__ */ new Map(),
|
|
plugin: /* @__PURE__ */ new Map(),
|
|
workspace: /* @__PURE__ */ new Map()
|
|
};
|
|
var onPersistedChange = null;
|
|
var onMemoryChange = null;
|
|
var onStorageAtom = null;
|
|
var writableAtoms = /* @__PURE__ */ new Set();
|
|
function create(initial, fields = {}) {
|
|
const created = Object.freeze({
|
|
[ATOM]: {
|
|
value: initial,
|
|
initial,
|
|
listeners: /* @__PURE__ */ new Set(),
|
|
readonly: false,
|
|
persist: "memory",
|
|
version: 1,
|
|
...fields
|
|
}
|
|
});
|
|
if (!created[ATOM].readonly) {
|
|
writableAtoms.add(created[ATOM]);
|
|
}
|
|
return created;
|
|
}
|
|
function resetAtoms() {
|
|
snapshot.plugin = /* @__PURE__ */ new Map();
|
|
snapshot.workspace = /* @__PURE__ */ new Map();
|
|
for (const inner of writableAtoms) {
|
|
inner.value = inner.initial;
|
|
}
|
|
}
|
|
function stored(inner) {
|
|
if (!inner.key || inner.persist === "storage") {
|
|
return { found: false };
|
|
}
|
|
const entry = snapshot[inner.persist].get(inner.key);
|
|
if (!entry) {
|
|
return { found: false };
|
|
}
|
|
if (entry.version === inner.version) {
|
|
return { found: true, value: entry.value };
|
|
}
|
|
return inner.migrate && inner.persist !== "memory" ? { found: true, value: inner.migrate(entry.value, entry.version) } : { found: false };
|
|
}
|
|
function atom(...args) {
|
|
if (args.length === 1) {
|
|
return create(args[0]);
|
|
}
|
|
const [key, initial, options = {}] = args;
|
|
if (typeof key !== "string" || !ATOM_KEY.test(key)) {
|
|
throw new TypeError("an atom key is 1-128 letters, digits, or . _ : -");
|
|
}
|
|
if (keyed.has(key)) {
|
|
throw new Error(`atom key "${key}" is already used`);
|
|
}
|
|
const persist = options.persist ?? "memory";
|
|
if (!["memory", "plugin", "workspace", "storage"].includes(persist)) {
|
|
throw new TypeError('persist is "memory", "plugin", "workspace" or "storage"');
|
|
}
|
|
const result = create(initial, {
|
|
key,
|
|
persist,
|
|
version: options.version ?? 1,
|
|
migrate: options.migrate
|
|
});
|
|
const inner = result[ATOM];
|
|
const hydrated = stored(inner);
|
|
if (hydrated.found) {
|
|
inner.value = hydrated.value;
|
|
}
|
|
keyed.set(key, result);
|
|
if (persist === "storage") {
|
|
onStorageAtom?.(key);
|
|
}
|
|
return result;
|
|
}
|
|
function derived(compute) {
|
|
if (typeof compute !== "function") {
|
|
throw new TypeError("derived() takes a function");
|
|
}
|
|
return create(compute(), { readonly: true, compute });
|
|
}
|
|
function hostAtom(initial) {
|
|
const result = create(initial, { readonly: true });
|
|
return { atom: result, set: (value) => assign(result[ATOM], value, false) };
|
|
}
|
|
function state(target) {
|
|
const inner = typeof target === "object" && target !== null ? target[ATOM] : void 0;
|
|
if (!inner) {
|
|
throw new TypeError("expected an atom created by atom()");
|
|
}
|
|
return inner;
|
|
}
|
|
function read(target) {
|
|
const inner = state(target);
|
|
if (inner.compute) {
|
|
return inner.compute();
|
|
}
|
|
activeTracker?.(target);
|
|
return inner.value;
|
|
}
|
|
function assign(inner, value, persist) {
|
|
if (Object.is(value, inner.value)) {
|
|
return;
|
|
}
|
|
inner.value = value;
|
|
for (const listener of inner.listeners) {
|
|
listener();
|
|
}
|
|
const target = persist && inner.key ? keyed.get(inner.key) : void 0;
|
|
const notify = inner.persist === "memory" ? onMemoryChange : onPersistedChange;
|
|
if (target) {
|
|
notify?.(target);
|
|
}
|
|
}
|
|
function writable(target, verb) {
|
|
if (activeTracker) {
|
|
throw new Error(`${verb}() was called while rendering; call it from an event handler instead`);
|
|
}
|
|
const inner = state(target);
|
|
if (inner.readonly) {
|
|
throw new TypeError(`${verb}() cannot change a read-only atom`);
|
|
}
|
|
return inner;
|
|
}
|
|
function set(target, value) {
|
|
assign(writable(target, "set"), value, true);
|
|
}
|
|
function update(target, updater) {
|
|
const inner = writable(target, "update");
|
|
if (typeof updater !== "function") {
|
|
throw new TypeError("update() takes an updater function; use set() to store a value");
|
|
}
|
|
assign(inner, updater(inner.value), true);
|
|
}
|
|
function subscribeAtom(target, listener) {
|
|
const inner = state(target);
|
|
inner.listeners.add(listener);
|
|
return () => inner.listeners.delete(listener);
|
|
}
|
|
function trackAtomReads(reads, render) {
|
|
const previous = activeTracker;
|
|
activeTracker = (target) => reads.add(target);
|
|
try {
|
|
return render();
|
|
} finally {
|
|
activeTracker = previous;
|
|
}
|
|
}
|
|
function persistedEntry(target) {
|
|
const inner = state(target);
|
|
return inner.key && inner.persist !== "memory" ? { scope: inner.persist, key: inner.key, version: inner.version, value: inner.value } : null;
|
|
}
|
|
function watchPersistedAtoms(listener) {
|
|
onPersistedChange = listener;
|
|
}
|
|
function memoryEntry(target) {
|
|
const inner = state(target);
|
|
return inner.key && inner.persist === "memory" ? { key: inner.key, version: inner.version, value: inner.value } : null;
|
|
}
|
|
function watchMemoryAtoms(listener) {
|
|
onMemoryChange = listener;
|
|
}
|
|
function watchStorageAtoms(listener) {
|
|
onStorageAtom = listener;
|
|
for (const [key, target] of keyed) {
|
|
if (listener && target[ATOM].persist === "storage") {
|
|
listener(key);
|
|
}
|
|
}
|
|
}
|
|
function applyStorageValue(key, value) {
|
|
const inner = keyed.get(key)?.[ATOM];
|
|
if (inner?.persist === "storage") {
|
|
assign(inner, value === void 0 ? inner.initial : value, false);
|
|
}
|
|
}
|
|
function captureAtomValues() {
|
|
const values = /* @__PURE__ */ new Map();
|
|
for (const inner of writableAtoms) {
|
|
values.set(inner, inner.value);
|
|
}
|
|
return () => {
|
|
for (const [inner, value] of values) {
|
|
assign(inner, value, true);
|
|
}
|
|
};
|
|
}
|
|
function hydrateAtoms(scope, values) {
|
|
snapshot[scope] = new Map(values);
|
|
for (const target of keyed.values()) {
|
|
const inner = target[ATOM];
|
|
if (inner.persist !== scope) {
|
|
continue;
|
|
}
|
|
const hydrated = stored(inner);
|
|
if (hydrated.found || scope !== "memory") {
|
|
assign(inner, hydrated.found ? hydrated.value : inner.initial, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-error-location.ts
|
|
var ORCA_RESERVED_PATH_PREFIX = "/__orca/";
|
|
var STACK_FRAME_URL = /([a-z][a-z0-9+.-]*:\/\/[^\s()]+):(\d+):(\d+)\)?\s*$/i;
|
|
var LOCATED_MESSAGE = /^[^\n:]+\.[a-z]+:\d+:\d+: /i;
|
|
function pageBase() {
|
|
return typeof location === "undefined" ? null : new URL("/", location.href).href;
|
|
}
|
|
function pluginFrame(stackLine, base) {
|
|
const match = STACK_FRAME_URL.exec(stackLine);
|
|
const url = match?.[1];
|
|
if (!match || !url?.startsWith(base)) {
|
|
return null;
|
|
}
|
|
const path = `/${url.slice(base.length).split(/[?#]/)[0] ?? ""}`;
|
|
if (path.startsWith(ORCA_RESERVED_PATH_PREFIX)) {
|
|
return "orca";
|
|
}
|
|
const generated = { line: Number(match[2]), column: Number(match[3]) };
|
|
const at = originalPluginPosition(url, generated.line, generated.column) ?? generated;
|
|
try {
|
|
return { path: decodeURIComponent(path.slice(1)), ...at };
|
|
} catch {
|
|
return { path: path.slice(1), ...at };
|
|
}
|
|
}
|
|
var formatFrame = (frame) => `${frame.path}:${frame.line}:${frame.column}`;
|
|
function pluginErrorLocation(stack, base) {
|
|
for (const line of stack.split("\n")) {
|
|
const frame = pluginFrame(line, base);
|
|
if (frame && frame !== "orca") {
|
|
return formatFrame(frame);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
function describePluginError(error, base = pageBase()) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
const at = base && !LOCATED_MESSAGE.test(message) ? pluginErrorLocation(stack, base) : null;
|
|
if (!at) {
|
|
return message;
|
|
}
|
|
const file = at.replace(/:\d+:\d+$/, "");
|
|
return message.startsWith(`${file}:`) ? message : `${at}: ${message}`;
|
|
}
|
|
function describePluginErrorForLog(error, base = pageBase()) {
|
|
const headline = describePluginError(error, base);
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error && typeof error.stack === "string" ? error.stack : "";
|
|
if (!base || LOCATED_MESSAGE.test(message)) {
|
|
return base ? headline : stack || headline;
|
|
}
|
|
const frames = stack.split("\n").flatMap((line) => {
|
|
if (!/^\s+at /.test(line)) {
|
|
return [];
|
|
}
|
|
const frame = pluginFrame(line, base);
|
|
if (frame === "orca") {
|
|
return [];
|
|
}
|
|
const closing = line.trimEnd().endsWith(")") ? ")" : "";
|
|
return [frame ? line.replace(STACK_FRAME_URL, () => `${formatFrame(frame)}${closing}`) : line];
|
|
});
|
|
return [headline, ...frames].join("\n");
|
|
}
|
|
|
|
// src/shared/plugins/declarative/plugin-declarative-message-names.ts
|
|
var PLUGIN_DECLARATIVE_MESSAGES = {
|
|
/** guest → host, `{ document, revision, tree }` */
|
|
render: "ui.render",
|
|
/** host → guest, `{ document, revision, callback, args, edit? }`: `revision` is the tree shown. */
|
|
event: "ui.event",
|
|
/** guest → host, `{ document, revision, message }`: the plugin threw; the last good tree stays. */
|
|
error: "ui.error",
|
|
/** host → guest, `{ document, revision, accepted, retryAfterMs? }`: whether a render is shown. */
|
|
ack: "ui.ack",
|
|
/** guest → host, `{ document }`: the runtime is up and waits for `ui.hydrate` to render. */
|
|
ready: "ui.ready",
|
|
/**
|
|
* host → guest, `{ panel?, visible?, plugin?, workspace?, memory?, retain? }`: persisted state and
|
|
* context; `memory` hands keyed memory atoms to a dev hot reload, `retain` asks for `ui.retain`.
|
|
*/
|
|
hydrate: "ui.hydrate",
|
|
/** host → guest, `{ visible }` */
|
|
visibility: "ui.visibility",
|
|
/** guest → host, `{ entries: [{ scope, key, version, value }] }`: persisted atoms changed. */
|
|
persist: "ui.persist",
|
|
/** guest → host, `{ document, entries: [{ key, version, value? }] }`: keyed memory atoms changed. */
|
|
retain: "ui.retain",
|
|
/** host → guest, `{ document }`: remember every atom's value before `orca plugin shot --act`. */
|
|
snapshot: "ui.snapshot",
|
|
/** host → guest, `{ document }`: put the atoms back to the last `ui.snapshot`. */
|
|
restore: "ui.restore"
|
|
};
|
|
var PLUGIN_DECLARATIVE_CATALOG_VERSION = 4;
|
|
var PLUGIN_DECLARATIVE_COMPONENT_PROPS = {
|
|
Stack: ["gap", "align"],
|
|
Row: ["gap", "align", "justify", "wrap"],
|
|
Text: ["tone", "muted", "size", "weight", "truncate"],
|
|
Heading: ["level"],
|
|
Button: ["variant", "tone", "size", "icon", "tooltip", "disabled", "onPress"],
|
|
IconButton: ["icon", "label", "tooltip", "variant", "tone", "size", "disabled", "onPress"],
|
|
Badge: ["tone"],
|
|
Card: ["title", "description"],
|
|
List: ["label"],
|
|
ListItem: ["title", "description", "icon", "current", "disabled", "onPress"],
|
|
Input: [
|
|
"label",
|
|
"labelHidden",
|
|
"value",
|
|
"defaultValue",
|
|
"placeholder",
|
|
"type",
|
|
"maxLength",
|
|
"invalid",
|
|
"disabled",
|
|
"onChange",
|
|
"onSubmit"
|
|
],
|
|
Switch: ["label", "checked", "defaultChecked", "disabled", "onChange"],
|
|
Select: [
|
|
"label",
|
|
"labelHidden",
|
|
"value",
|
|
"defaultValue",
|
|
"placeholder",
|
|
"options",
|
|
"disabled",
|
|
"onChange"
|
|
],
|
|
Separator: ["orientation"],
|
|
Progress: ["label", "labelHidden", "value"],
|
|
Spinner: ["label", "labelHidden"],
|
|
EmptyState: ["title", "description", "icon"],
|
|
Link: ["href", "onPress"],
|
|
Code: ["block"],
|
|
// Catalog 2 (Orca 1.4.247).
|
|
Textarea: [
|
|
"label",
|
|
"labelHidden",
|
|
"value",
|
|
"defaultValue",
|
|
"placeholder",
|
|
"rows",
|
|
"maxLength",
|
|
"invalid",
|
|
"disabled",
|
|
"onChange",
|
|
"onSubmit"
|
|
],
|
|
Checkbox: ["label", "description", "checked", "defaultChecked", "disabled", "onChange"],
|
|
RadioGroup: [
|
|
"label",
|
|
"labelHidden",
|
|
"value",
|
|
"defaultValue",
|
|
"options",
|
|
"orientation",
|
|
"disabled",
|
|
"onChange"
|
|
],
|
|
Slider: [
|
|
"label",
|
|
"labelHidden",
|
|
"value",
|
|
"defaultValue",
|
|
"min",
|
|
"max",
|
|
"step",
|
|
"showValue",
|
|
"disabled",
|
|
"onChange"
|
|
],
|
|
Field: ["label", "description", "error", "required"],
|
|
Tabs: ["label", "value", "defaultValue", "onChange"],
|
|
Tab: ["value", "label", "icon", "disabled"],
|
|
Collapsible: ["title", "description", "open", "defaultOpen", "disabled", "onChange"],
|
|
Toolbar: ["label", "gap"],
|
|
Grid: ["columns", "minItemWidth", "gap"],
|
|
Table: [
|
|
"label",
|
|
"columns",
|
|
"rows",
|
|
"sort",
|
|
"selectionMode",
|
|
"selected",
|
|
"defaultSelected",
|
|
"hasMore",
|
|
"loading",
|
|
"emptyText",
|
|
"maxHeight",
|
|
"onSortChange",
|
|
"onSelectionChange",
|
|
"onRowAction",
|
|
"onLoadMore"
|
|
],
|
|
Tree: [
|
|
"label",
|
|
"items",
|
|
"selected",
|
|
"defaultSelected",
|
|
"expanded",
|
|
"defaultExpanded",
|
|
"maxHeight",
|
|
"onSelect",
|
|
"onToggle",
|
|
"onAction"
|
|
],
|
|
Markdown: ["content"],
|
|
CodeView: ["code", "language", "label", "lineNumbers", "wrap", "maxHeight"],
|
|
Image: ["src", "alt", "size", "fit", "rounded"],
|
|
KeyValue: ["label", "items"],
|
|
Callout: ["tone", "title", "icon"],
|
|
Skeleton: ["shape", "lines", "label"],
|
|
// Catalog 3 (also Orca 1.4.247).
|
|
RelativeTime: ["at", "format", "tone", "muted"],
|
|
// Catalog 4 (Orca 1.4.248).
|
|
Box: [
|
|
"direction",
|
|
"gap",
|
|
"padding",
|
|
"paddingX",
|
|
"paddingY",
|
|
"margin",
|
|
"align",
|
|
"justify",
|
|
"wrap",
|
|
"grow",
|
|
"shrink",
|
|
"width",
|
|
"height",
|
|
"minWidth",
|
|
"minHeight",
|
|
"maxWidth",
|
|
"maxHeight",
|
|
"columns",
|
|
"scroll",
|
|
"border",
|
|
"radius",
|
|
"background"
|
|
]
|
|
};
|
|
var PLUGIN_DECLARATIVE_VALUE_CONTROLS = {
|
|
Input: { value: "value", change: "onChange" },
|
|
Textarea: { value: "value", change: "onChange" },
|
|
Switch: { value: "checked", change: "onChange" },
|
|
Checkbox: { value: "checked", change: "onChange" },
|
|
Select: { value: "value", change: "onChange" },
|
|
RadioGroup: { value: "value", change: "onChange" },
|
|
Slider: { value: "value", change: "onChange" },
|
|
Tabs: { value: "value", change: "onChange" },
|
|
Collapsible: { value: "open", change: "onChange" },
|
|
Table: { value: "selected", change: "onSelectionChange" },
|
|
Tree: { value: "selected", change: "onSelect" }
|
|
};
|
|
var VALUE_CONTROL_BY_TYPE = new Map(
|
|
Object.entries(PLUGIN_DECLARATIVE_VALUE_CONTROLS)
|
|
);
|
|
function pluginDeclarativeValueControl(type) {
|
|
return VALUE_CONTROL_BY_TYPE.get(type) ?? null;
|
|
}
|
|
var PLUGIN_DECLARATIVE_EDIT_PROP = "$edit";
|
|
function isReservedPluginDeclarativeEvent(name) {
|
|
return name.startsWith("ui.") || name.startsWith("editor.") || name === "ready" || name === "hello" || name === "ping" || name === "pong";
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-elements.ts
|
|
var ORCA_ELEMENT = /* @__PURE__ */ Symbol.for("orca.declarative.element");
|
|
var Fragment = /* @__PURE__ */ Symbol.for("orca.declarative.fragment");
|
|
function isOrcaElement(value) {
|
|
return typeof value === "object" && value !== null && "$$typeof" in value && value.$$typeof === ORCA_ELEMENT;
|
|
}
|
|
function createElement(type, props, key) {
|
|
return Object.freeze({
|
|
$$typeof: ORCA_ELEMENT,
|
|
type,
|
|
props,
|
|
key: key === void 0 || key === null ? void 0 : String(key)
|
|
});
|
|
}
|
|
function jsx(type, props, key) {
|
|
const { key: propKey, ...rest } = props ?? {};
|
|
return createElement(type, rest, key ?? propKey);
|
|
}
|
|
function h(type, props, ...children) {
|
|
const { key, ...rest } = props ?? {};
|
|
if (children.length > 0) {
|
|
rest.children = children.length === 1 ? children[0] : children;
|
|
}
|
|
return createElement(type, rest, key);
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-evaluate.ts
|
|
var MAX_EVALUATION_DEPTH = 512;
|
|
var MAX_PROP_DEPTH = 40;
|
|
var IGNORED_PROPS = /* @__PURE__ */ new Set([
|
|
"children",
|
|
"ref",
|
|
"key",
|
|
PLUGIN_DECLARATIVE_EDIT_PROP
|
|
]);
|
|
function isCallback(value) {
|
|
return typeof value === "function";
|
|
}
|
|
function describe(value) {
|
|
if (value === null || typeof value !== "object") {
|
|
return typeof value === "number" ? String(value) : typeof value;
|
|
}
|
|
return Object.getPrototypeOf(value)?.constructor?.name ?? "object";
|
|
}
|
|
function assertPlainJson(value, where, depth = 0) {
|
|
if (depth > MAX_PROP_DEPTH) {
|
|
throw new TypeError(`${where} is nested too deeply`);
|
|
}
|
|
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
return;
|
|
}
|
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
return;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
value.forEach((item, index) => assertPlainJson(item, `${where}[${index}]`, depth + 1));
|
|
return;
|
|
}
|
|
if (typeof value === "object") {
|
|
const prototype = Object.getPrototypeOf(value);
|
|
if (prototype === Object.prototype || prototype === null) {
|
|
for (const [name, item] of Object.entries(value)) {
|
|
if (item !== void 0) {
|
|
assertPlainJson(item, `${where}.${name}`, depth + 1);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
throw new TypeError(`${where} must be plain JSON data, not ${describe(value)}`);
|
|
}
|
|
function plainJsonCopy(value, where) {
|
|
assertPlainJson(value, where);
|
|
return withoutUndefined(value);
|
|
}
|
|
function withoutUndefined(value) {
|
|
if (Array.isArray(value)) {
|
|
return value.map(withoutUndefined);
|
|
}
|
|
if (value === null || typeof value !== "object") {
|
|
return value;
|
|
}
|
|
const copy = {};
|
|
for (const [name, item] of Object.entries(value)) {
|
|
if (item !== void 0) {
|
|
copy[name] = withoutUndefined(item);
|
|
}
|
|
}
|
|
return copy;
|
|
}
|
|
function assertUniqueKeys(children, parent) {
|
|
const seen = /* @__PURE__ */ new Set();
|
|
for (const child of children) {
|
|
if (typeof child !== "string") {
|
|
if (seen.has(child.key)) {
|
|
throw new Error(`duplicate key "${child.key.slice(0, 64)}" among the children of ${parent}`);
|
|
}
|
|
seen.add(child.key);
|
|
}
|
|
}
|
|
}
|
|
function hashId(text) {
|
|
let a = 2166136261;
|
|
let b = 16777619;
|
|
for (let index = 0; index < text.length; index++) {
|
|
const code = text.charCodeAt(index);
|
|
a = Math.imul(a ^ code, 16777619);
|
|
b = Math.imul(b ^ code, 1540483477);
|
|
}
|
|
return `c${(a >>> 0).toString(36)}${(b >>> 0).toString(36)}`;
|
|
}
|
|
function evaluateOrcaTree(component, editsSeen = /* @__PURE__ */ new Map()) {
|
|
const callbacks = /* @__PURE__ */ new Map();
|
|
function callbackId(path, prop) {
|
|
const base = hashId(`${path}|${prop}`);
|
|
let id = base;
|
|
for (let attempt = 1; callbacks.has(id); attempt++) {
|
|
id = `${base}.${attempt}`;
|
|
}
|
|
return id;
|
|
}
|
|
function hostNode(type, props, key, parentPath, depth) {
|
|
if (!/^[A-Z]/.test(type)) {
|
|
throw new TypeError(
|
|
`<${type.slice(0, 64)}> is not an Orca component; panels never render DOM`
|
|
);
|
|
}
|
|
const path = `${parentPath}/${key}`;
|
|
const wireProps = {};
|
|
for (const [name, value] of Object.entries(props)) {
|
|
if (IGNORED_PROPS.has(name) || value === void 0) {
|
|
continue;
|
|
}
|
|
if (isCallback(value)) {
|
|
const id = callbackId(path, name);
|
|
callbacks.set(id, value);
|
|
wireProps[name] = { $callback: id };
|
|
} else {
|
|
wireProps[name] = plainJsonCopy(value, `<${type}> prop "${name}"`);
|
|
}
|
|
}
|
|
const control = pluginDeclarativeValueControl(type);
|
|
const onChange = control ? wireProps[control.change] : void 0;
|
|
if (typeof onChange === "object" && onChange !== null) {
|
|
const id = "$callback" in onChange ? onChange.$callback : void 0;
|
|
wireProps[PLUGIN_DECLARATIVE_EDIT_PROP] = typeof id === "string" ? editsSeen.get(id) ?? 0 : 0;
|
|
}
|
|
const children = [];
|
|
const list = Array.isArray(props.children) ? props.children : [props.children];
|
|
list.forEach((child, index) => emit(child, `#${index}`, "", children, path, depth + 1));
|
|
assertUniqueKeys(children, `<${type}>`);
|
|
return { type, key, props: wireProps, children };
|
|
}
|
|
function emit(value, slot, scope, out, parentPath, depth) {
|
|
if (depth > MAX_EVALUATION_DEPTH) {
|
|
throw new Error(`component tree is deeper than ${MAX_EVALUATION_DEPTH}`);
|
|
}
|
|
if (value === null || value === void 0 || typeof value === "boolean") {
|
|
return;
|
|
}
|
|
if (typeof value === "string" || typeof value === "number") {
|
|
out.push(String(value));
|
|
return;
|
|
}
|
|
if (Array.isArray(value)) {
|
|
value.forEach(
|
|
(item, index) => emit(item, `${slot}.${index}`, `${slot}.`, out, parentPath, depth + 1)
|
|
);
|
|
return;
|
|
}
|
|
if (!isOrcaElement(value)) {
|
|
throw new TypeError(`a ${describe(value)} is not a valid child; render a component or text`);
|
|
}
|
|
const { type, props } = value;
|
|
const key = value.key === void 0 ? slot : `${scope}${value.key}`;
|
|
if (type === Fragment) {
|
|
emit(props.children, key, `${key}.`, out, parentPath, depth + 1);
|
|
} else if (typeof type === "function") {
|
|
emit(type(props), key, `${key}:`, out, parentPath, depth + 1);
|
|
} else if (typeof type === "string") {
|
|
out.push(hostNode(type, props, key, parentPath, depth));
|
|
} else {
|
|
throw new TypeError('invalid element type; import components from "orca"');
|
|
}
|
|
}
|
|
const root = [];
|
|
emit(component({}), "#0", "", root, "", 0);
|
|
const nodes = root.map(
|
|
(child, index) => typeof child === "string" ? { type: "Text", key: `#text${index}`, props: {}, children: [child] } : child
|
|
);
|
|
assertUniqueKeys(nodes, "the panel");
|
|
const tree = nodes.length <= 1 ? nodes[0] ?? null : { type: "Stack", key: "#root", props: {}, children: nodes };
|
|
return { tree, callbacks };
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-memory-retention.ts
|
|
var RETAIN_DEBOUNCE_MS = 50;
|
|
var RETAIN_BATCH = 64;
|
|
function createMemoryRetention(post) {
|
|
const pending = /* @__PURE__ */ new Set();
|
|
let timer = null;
|
|
let started = false;
|
|
function flush() {
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
const entries = [];
|
|
for (const target of pending) {
|
|
const entry = memoryEntry(target);
|
|
if (!entry) {
|
|
continue;
|
|
}
|
|
try {
|
|
assertPlainJson(entry.value, `atom "${entry.key}"`);
|
|
entries.push(entry);
|
|
} catch {
|
|
entries.push({ key: entry.key, version: entry.version });
|
|
}
|
|
}
|
|
pending.clear();
|
|
for (let start = 0; start < entries.length; start += RETAIN_BATCH) {
|
|
post(entries.slice(start, start + RETAIN_BATCH));
|
|
}
|
|
}
|
|
function add(target) {
|
|
pending.add(target);
|
|
timer ??= setTimeout(flush, RETAIN_DEBOUNCE_MS);
|
|
}
|
|
return {
|
|
start() {
|
|
if (!started) {
|
|
started = true;
|
|
watchMemoryAtoms(add);
|
|
}
|
|
},
|
|
flush,
|
|
stop() {
|
|
if (started) {
|
|
flush();
|
|
watchMemoryAtoms(null);
|
|
started = false;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-host-messages.ts
|
|
function readHostMessage(payload) {
|
|
if (typeof payload !== "object" || payload === null) {
|
|
return {};
|
|
}
|
|
const panel = "panel" in payload ? payload.panel : void 0;
|
|
return {
|
|
document: "document" in payload ? payload.document : void 0,
|
|
revision: "revision" in payload ? payload.revision : void 0,
|
|
callback: "callback" in payload ? payload.callback : void 0,
|
|
args: "args" in payload ? payload.args : void 0,
|
|
edit: "edit" in payload ? payload.edit : void 0,
|
|
accepted: "accepted" in payload ? payload.accepted : void 0,
|
|
retryAfterMs: "retryAfterMs" in payload ? payload.retryAfterMs : void 0,
|
|
colorScheme: "colorScheme" in payload ? payload.colorScheme : void 0,
|
|
visible: "visible" in payload ? payload.visible : void 0,
|
|
panelId: typeof panel === "object" && panel !== null && "id" in panel ? panel.id : void 0,
|
|
plugin: "plugin" in payload ? payload.plugin : void 0,
|
|
workspace: "workspace" in payload ? payload.workspace : void 0,
|
|
memory: "memory" in payload ? payload.memory : void 0,
|
|
retain: "retain" in payload ? payload.retain : void 0,
|
|
hydration: "hydration" in payload ? payload.hydration : void 0
|
|
};
|
|
}
|
|
function readStoredValues(raw) {
|
|
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
return null;
|
|
}
|
|
const values = /* @__PURE__ */ new Map();
|
|
for (const [key, entry] of Object.entries(raw)) {
|
|
if (typeof entry !== "object" || entry === null) {
|
|
continue;
|
|
}
|
|
const version = "version" in entry ? entry.version : void 0;
|
|
if (typeof version === "number" && "value" in entry) {
|
|
values.set(key, { version, value: entry.value });
|
|
}
|
|
}
|
|
return values;
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-act-snapshot.ts
|
|
function actSnapshotSubscriptions(on2, document2) {
|
|
let restore = null;
|
|
const ours = (payload) => readHostMessage(payload).document === document2;
|
|
return [
|
|
on2(PLUGIN_DECLARATIVE_MESSAGES.snapshot, (payload) => {
|
|
if (ours(payload)) {
|
|
restore = captureAtomValues();
|
|
}
|
|
}),
|
|
on2(PLUGIN_DECLARATIVE_MESSAGES.restore, (payload) => {
|
|
if (ours(payload) && restore) {
|
|
const pending = restore;
|
|
restore = null;
|
|
pending();
|
|
}
|
|
})
|
|
];
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-renderer-support.ts
|
|
var MIN_RENDER_INTERVAL_MS = 16;
|
|
var MAX_RETAINED_RENDERS = 32;
|
|
var HYDRATION_TIMEOUT_MS = 3e3;
|
|
function errorMessage(error) {
|
|
return describePluginError(error).slice(0, 4096);
|
|
}
|
|
function defaultSchedule(flush, delayMs) {
|
|
if (delayMs > 0) {
|
|
setTimeout(flush, delayMs);
|
|
} else {
|
|
queueMicrotask(flush);
|
|
}
|
|
}
|
|
function newDocumentId(now) {
|
|
return `${now.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
}
|
|
function isCallable(value) {
|
|
return typeof value === "function";
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-panel-context.ts
|
|
function createPanelContext(onRetain) {
|
|
const visibility = hostAtom(true);
|
|
const lifetime = new AbortController();
|
|
const context = {
|
|
subscriptions: [],
|
|
signal: lifetime.signal,
|
|
visible: visibility.atom,
|
|
panel: { id: "" }
|
|
};
|
|
let hydratedNow = false;
|
|
let resolve = () => void 0;
|
|
const hydrated = new Promise((done) => {
|
|
resolve = () => {
|
|
hydratedNow = true;
|
|
done();
|
|
};
|
|
});
|
|
function setVisibility(payload) {
|
|
const { visible } = readHostMessage(payload);
|
|
if (typeof visible === "boolean") {
|
|
visibility.set(visible);
|
|
}
|
|
}
|
|
return {
|
|
context,
|
|
hydrated,
|
|
isHydrated: () => hydratedNow,
|
|
markHydrated: () => resolve(),
|
|
setVisibility,
|
|
/** `ui.hydrate`: on boot, and again with the new workspace's values after a switch. */
|
|
hydrate(payload) {
|
|
const message = readHostMessage(payload);
|
|
if (typeof message.panelId === "string") {
|
|
context.panel = { id: message.panelId };
|
|
}
|
|
setVisibility(payload);
|
|
for (const scope of ["plugin", "workspace", "memory"]) {
|
|
const values = readStoredValues(message[scope]);
|
|
if (values) {
|
|
hydrateAtoms(scope, values);
|
|
}
|
|
}
|
|
if (message.retain === true) {
|
|
onRetain?.();
|
|
}
|
|
resolve();
|
|
},
|
|
abort: () => lifetime.abort()
|
|
};
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-persist-queue.ts
|
|
var PERSIST_DEBOUNCE_MS = 400;
|
|
function createPersistQueue(options) {
|
|
const pending = /* @__PURE__ */ new Set();
|
|
let timer = null;
|
|
function flush() {
|
|
if (timer !== null) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
const entries = [];
|
|
for (const target of pending) {
|
|
const entry = persistedEntry(target);
|
|
if (!entry) {
|
|
continue;
|
|
}
|
|
try {
|
|
assertPlainJson(entry.value, `persisted atom "${entry.key}"`);
|
|
entries.push(entry);
|
|
} catch (error) {
|
|
options.report(error);
|
|
}
|
|
}
|
|
pending.clear();
|
|
if (entries.length > 0) {
|
|
options.post(entries);
|
|
}
|
|
}
|
|
return {
|
|
add(target) {
|
|
pending.add(target);
|
|
timer ??= setTimeout(flush, PERSIST_DEBOUNCE_MS);
|
|
},
|
|
flush
|
|
};
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-storage-sync.ts
|
|
function createDeclarativeStorageSync(options) {
|
|
const storage2 = options.bridge?.storage;
|
|
const loads = /* @__PURE__ */ new Set();
|
|
const writtenSince = /* @__PURE__ */ new Map();
|
|
let generation = 0;
|
|
function load(key) {
|
|
if (!storage2) {
|
|
return;
|
|
}
|
|
const startedAt = generation;
|
|
const task = storage2.get(key).then((value) => {
|
|
if ((writtenSince.get(key) ?? -1) < startedAt) {
|
|
applyStorageValue(key, value ?? void 0);
|
|
}
|
|
}).catch(options.report).finally(() => loads.delete(task));
|
|
loads.add(task);
|
|
}
|
|
watchStorageAtoms((key) => {
|
|
generation += 1;
|
|
load(key);
|
|
});
|
|
const subscription = storage2?.onDidChange(({ key }) => {
|
|
generation += 1;
|
|
load(key);
|
|
});
|
|
return {
|
|
write(entries) {
|
|
const rest = [];
|
|
for (const entry of entries) {
|
|
if (entry.scope !== "storage") {
|
|
rest.push(entry);
|
|
continue;
|
|
}
|
|
generation += 1;
|
|
writtenSince.set(entry.key, generation);
|
|
void storage2?.set(entry.key, entry.value).catch(options.report);
|
|
}
|
|
return rest;
|
|
},
|
|
loaded: async () => {
|
|
await Promise.all(loads);
|
|
},
|
|
dispose() {
|
|
watchStorageAtoms(null);
|
|
subscription?.dispose();
|
|
}
|
|
};
|
|
}
|
|
function createDeclarativePersistence(options) {
|
|
const storageSync = createDeclarativeStorageSync(options);
|
|
const queue = createPersistQueue({
|
|
report: options.report,
|
|
post: (all) => {
|
|
const entries = storageSync.write(all);
|
|
if (entries.length > 0) {
|
|
options.post(entries);
|
|
}
|
|
}
|
|
});
|
|
watchPersistedAtoms(queue.add);
|
|
return {
|
|
flush: queue.flush,
|
|
loaded: storageSync.loaded,
|
|
dispose() {
|
|
watchPersistedAtoms(null);
|
|
storageSync.dispose();
|
|
}
|
|
};
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-hydration-persistence.ts
|
|
function createHydratedPersistence(options) {
|
|
let hydration;
|
|
const persistence = createDeclarativePersistence({
|
|
bridge: options.bridge,
|
|
report: options.report,
|
|
post: (entries) => options.post({ entries, ...hydration === void 0 ? {} : { hydration } })
|
|
});
|
|
function hydrate(payload) {
|
|
persistence.flush();
|
|
const next = readHostMessage(payload).hydration;
|
|
if (typeof next === "number") {
|
|
hydration = next;
|
|
}
|
|
options.hydratePanel(payload);
|
|
}
|
|
return { persistence, hydrate };
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-renderer.ts
|
|
function createOrcaDeclarativeRuntime(options) {
|
|
const { post } = options;
|
|
const bridge = options.bridge ?? null;
|
|
const schedule = options.schedule ?? defaultSchedule;
|
|
const now = options.now ?? (() => Date.now());
|
|
const document2 = newDocumentId(now());
|
|
const theme = hostAtom({ colorScheme: options.colorScheme ?? "dark" });
|
|
const retention = createMemoryRetention(
|
|
(entries) => post(PLUGIN_DECLARATIVE_MESSAGES.retain, { document: document2, entries })
|
|
);
|
|
const panel = createPanelContext(retention.start);
|
|
const { context } = panel;
|
|
let component = null;
|
|
let revision = 0;
|
|
let lastRenderAt = Number.NEGATIVE_INFINITY;
|
|
let renders = /* @__PURE__ */ new Map();
|
|
let accepted = null;
|
|
const editsSeen = /* @__PURE__ */ new Map();
|
|
let subscriptions = /* @__PURE__ */ new Map();
|
|
let scheduled = false;
|
|
let setupCleanup = null;
|
|
let disposed = false;
|
|
function invalidate(delayMs = 0) {
|
|
if (scheduled || !component) {
|
|
return;
|
|
}
|
|
scheduled = true;
|
|
const wait = Math.max(delayMs, lastRenderAt + MIN_RENDER_INTERVAL_MS - now());
|
|
schedule(() => {
|
|
if (scheduled) {
|
|
render();
|
|
}
|
|
}, wait);
|
|
}
|
|
function resubscribe(reads) {
|
|
const next = /* @__PURE__ */ new Map();
|
|
for (const target of reads) {
|
|
next.set(target, subscriptions.get(target) ?? subscribeAtom(target, () => invalidate()));
|
|
}
|
|
for (const [target, unsubscribe] of subscriptions) {
|
|
if (!next.has(target)) {
|
|
unsubscribe();
|
|
}
|
|
}
|
|
subscriptions = next;
|
|
}
|
|
function report(error) {
|
|
console.error(describePluginErrorForLog(error));
|
|
const stack = error instanceof Error && error.stack ? error.stack.slice(0, 8192) : void 0;
|
|
post(PLUGIN_DECLARATIVE_MESSAGES.error, {
|
|
document: document2,
|
|
revision,
|
|
message: errorMessage(error),
|
|
...stack ? { stack } : {}
|
|
});
|
|
}
|
|
function retain(at, callbacks) {
|
|
renders.set(at, callbacks);
|
|
for (const kept of renders.keys()) {
|
|
if (renders.size <= MAX_RETAINED_RENDERS) {
|
|
break;
|
|
}
|
|
if (kept !== accepted) {
|
|
renders.delete(kept);
|
|
}
|
|
}
|
|
}
|
|
function render() {
|
|
scheduled = false;
|
|
if (!component || disposed) {
|
|
return;
|
|
}
|
|
lastRenderAt = now();
|
|
const reads = /* @__PURE__ */ new Set();
|
|
const mounted = component;
|
|
let evaluation;
|
|
try {
|
|
evaluation = trackAtomReads(reads, () => evaluateOrcaTree(mounted, editsSeen));
|
|
} catch (error) {
|
|
resubscribe(/* @__PURE__ */ new Set([...subscriptions.keys(), ...reads]));
|
|
report(error);
|
|
return;
|
|
}
|
|
revision += 1;
|
|
retain(revision, evaluation.callbacks);
|
|
resubscribe(reads);
|
|
post(PLUGIN_DECLARATIVE_MESSAGES.render, { document: document2, revision, tree: evaluation.tree });
|
|
}
|
|
function dispatchEvent(payload) {
|
|
const message = readHostMessage(payload);
|
|
const { callback, args, edit } = message;
|
|
if (message.document !== document2 || typeof callback !== "string") {
|
|
return false;
|
|
}
|
|
const handler = typeof message.revision === "number" ? renders.get(message.revision)?.get(callback) : void 0;
|
|
if (!handler) {
|
|
return false;
|
|
}
|
|
if (typeof edit === "number" && Number.isSafeInteger(edit)) {
|
|
editsSeen.set(callback, Math.max(edit, editsSeen.get(callback) ?? 0));
|
|
invalidate();
|
|
}
|
|
try {
|
|
const result = handler(...Array.isArray(args) ? args : []);
|
|
if (result instanceof Promise) {
|
|
result.catch(report);
|
|
}
|
|
} catch (error) {
|
|
report(error);
|
|
}
|
|
return true;
|
|
}
|
|
function acknowledge(payload) {
|
|
const message = readHostMessage(payload);
|
|
const acked = message.revision;
|
|
if (message.document !== document2 || typeof acked !== "number" || !renders.has(acked)) {
|
|
return;
|
|
}
|
|
if (message.accepted === true) {
|
|
accepted = acked;
|
|
for (const kept of renders.keys()) {
|
|
if (kept < acked) {
|
|
renders.delete(kept);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
renders.delete(acked);
|
|
const retryAfterMs = message.retryAfterMs;
|
|
if (typeof retryAfterMs === "number" && acked === revision) {
|
|
invalidate(retryAfterMs);
|
|
}
|
|
}
|
|
const { persistence, hydrate } = createHydratedPersistence({
|
|
bridge,
|
|
report,
|
|
post: (fields) => post(PLUGIN_DECLARATIVE_MESSAGES.persist, { document: document2, ...fields }),
|
|
hydratePanel: panel.hydrate
|
|
});
|
|
const flushPersist = persistence.flush;
|
|
const bridgeSubscriptions = bridge?.on ? [
|
|
bridge.on(PLUGIN_DECLARATIVE_MESSAGES.event, dispatchEvent),
|
|
bridge.on(PLUGIN_DECLARATIVE_MESSAGES.ack, acknowledge),
|
|
bridge.on(PLUGIN_DECLARATIVE_MESSAGES.hydrate, hydrate),
|
|
bridge.on(PLUGIN_DECLARATIVE_MESSAGES.visibility, panel.setVisibility),
|
|
...actSnapshotSubscriptions(bridge.on.bind(bridge), document2),
|
|
bridge.on("theme", (payload) => {
|
|
const { colorScheme } = readHostMessage(payload);
|
|
if ((colorScheme === "light" || colorScheme === "dark") && read(theme.atom).colorScheme !== colorScheme) {
|
|
theme.set({ colorScheme });
|
|
}
|
|
})
|
|
] : [];
|
|
if (options.awaitHydration) {
|
|
post(PLUGIN_DECLARATIVE_MESSAGES.ready, { document: document2 });
|
|
const fallback = setTimeout(panel.markHydrated, HYDRATION_TIMEOUT_MS);
|
|
void panel.hydrated.then(() => clearTimeout(fallback));
|
|
} else {
|
|
panel.markHydrated();
|
|
}
|
|
function unsubscribeAll() {
|
|
for (const unsubscribe of subscriptions.values()) {
|
|
unsubscribe();
|
|
}
|
|
subscriptions = /* @__PURE__ */ new Map();
|
|
}
|
|
const runtime2 = {
|
|
document: document2,
|
|
async boot(module) {
|
|
await panel.hydrated;
|
|
await persistence.loaded();
|
|
if (isCallable(module.setup)) {
|
|
const cleanup = module.setup(context);
|
|
setupCleanup = isCallable(cleanup) ? () => void cleanup() : null;
|
|
}
|
|
const panelComponent = module.default;
|
|
if (isCallable(panelComponent)) {
|
|
runtime2.mount((props) => panelComponent(props));
|
|
} else if (!component) {
|
|
const kind = module.default === null ? "null" : typeof module.default;
|
|
throw new Error(
|
|
`The panel module must export a component function as its default export (got ${kind})`
|
|
);
|
|
}
|
|
},
|
|
mount(next) {
|
|
component = next;
|
|
if (panel.isHydrated()) {
|
|
render();
|
|
return;
|
|
}
|
|
void panel.hydrated.then(() => {
|
|
if (component === next) {
|
|
render();
|
|
}
|
|
});
|
|
},
|
|
unmount() {
|
|
component = null;
|
|
scheduled = false;
|
|
unsubscribeAll();
|
|
renders = /* @__PURE__ */ new Map();
|
|
accepted = null;
|
|
revision += 1;
|
|
post(PLUGIN_DECLARATIVE_MESSAGES.render, { document: document2, revision, tree: null });
|
|
},
|
|
mounted: () => component !== null,
|
|
dispatchEvent,
|
|
acknowledge,
|
|
hydrate,
|
|
rerender: () => render(),
|
|
flush() {
|
|
if (scheduled) {
|
|
render();
|
|
}
|
|
flushPersist();
|
|
retention.flush();
|
|
},
|
|
report,
|
|
themeAtom: theme.atom,
|
|
bridge,
|
|
dispose() {
|
|
if (disposed) {
|
|
return;
|
|
}
|
|
flushPersist();
|
|
retention.stop();
|
|
disposed = true;
|
|
component = null;
|
|
scheduled = false;
|
|
unsubscribeAll();
|
|
persistence.dispose();
|
|
for (const subscription of [...bridgeSubscriptions, ...context.subscriptions]) {
|
|
try {
|
|
subscription.dispose();
|
|
} catch (error) {
|
|
report(error);
|
|
}
|
|
}
|
|
try {
|
|
setupCleanup?.();
|
|
} catch (error) {
|
|
report(error);
|
|
}
|
|
panel.abort();
|
|
}
|
|
};
|
|
return runtime2;
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-sdk.ts
|
|
function hostError(code, message) {
|
|
return Object.assign(new Error(message), { code, retryable: code === "timeout" });
|
|
}
|
|
function unavailable(what) {
|
|
return Promise.reject(hostError("unavailable", `${what} is not available in this Orca`));
|
|
}
|
|
function isComponentTag(name) {
|
|
return Object.hasOwn(PLUGIN_DECLARATIVE_COMPONENT_PROPS, name);
|
|
}
|
|
function createOrcaSdk(source) {
|
|
const host2 = Object.freeze({
|
|
call(method, params, options = {}) {
|
|
const bridgeHost = source.bridge()?.host;
|
|
if (!bridgeHost) {
|
|
return unavailable("host.call");
|
|
}
|
|
const { signal, timeoutMs } = options;
|
|
if (timeoutMs === void 0) {
|
|
return bridgeHost.call(method, params, signal ? { signal } : void 0);
|
|
}
|
|
const controller = new AbortController();
|
|
const forward = () => controller.abort();
|
|
if (signal?.aborted) {
|
|
controller.abort();
|
|
}
|
|
signal?.addEventListener("abort", forward, { once: true });
|
|
let timedOut = false;
|
|
const timer = setTimeout(() => {
|
|
timedOut = true;
|
|
controller.abort();
|
|
}, timeoutMs);
|
|
return bridgeHost.call(method, params, { signal: controller.signal }).catch((error) => {
|
|
throw timedOut ? hostError("timeout", `${method} took longer than ${timeoutMs} ms`) : error;
|
|
}).finally(() => {
|
|
clearTimeout(timer);
|
|
signal?.removeEventListener("abort", forward);
|
|
});
|
|
},
|
|
has(method) {
|
|
return source.bridge()?.host?.has?.(method) === true;
|
|
}
|
|
});
|
|
const api2 = Object.freeze({
|
|
get major() {
|
|
return source.bridge()?.api?.major ?? 1;
|
|
},
|
|
get minor() {
|
|
return source.bridge()?.api?.minor ?? 0;
|
|
}
|
|
});
|
|
const env2 = Object.freeze({
|
|
/** Orca's UI language (BCP-47); the guest's `navigator.language` follows the OS instead. */
|
|
get locale() {
|
|
return source.bridge()?.locale ?? "en";
|
|
},
|
|
/** Reading it while rendering re-renders on light/dark switches. */
|
|
get theme() {
|
|
return source.theme();
|
|
},
|
|
client: "desktop"
|
|
});
|
|
const shell2 = Object.freeze({
|
|
openExternal(url) {
|
|
return host2.call("shell.openExternal", { url: String(url) });
|
|
}
|
|
});
|
|
const files2 = Object.freeze({
|
|
saveAs(options) {
|
|
const { data } = options;
|
|
const bytes = ArrayBuffer.isView(data) ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data;
|
|
return host2.call("files.saveAs", { ...options, data: bytes });
|
|
}
|
|
});
|
|
function on2(event, handler) {
|
|
if (typeof event !== "string" || isReservedPluginDeclarativeEvent(event)) {
|
|
throw new TypeError(`"${String(event)}" is not an event plugins can subscribe to`);
|
|
}
|
|
return source.bridge()?.on?.(event, handler) ?? { dispose: () => void 0 };
|
|
}
|
|
const ui2 = Object.freeze({
|
|
/** Grows with the component catalog. */
|
|
catalog: PLUGIN_DECLARATIVE_CATALOG_VERSION,
|
|
/** Whether this Orca draws `component`, and accepts `prop` on it when given. */
|
|
has(component, prop) {
|
|
if (!isComponentTag(component)) {
|
|
return false;
|
|
}
|
|
const props = PLUGIN_DECLARATIVE_COMPONENT_PROPS[component];
|
|
return prop === void 0 || props.includes(prop);
|
|
}
|
|
});
|
|
return { host: host2, api: api2, env: env2, shell: shell2, files: files2, on: on2, ui: ui2 };
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/declarative-data-sdk.ts
|
|
function unavailable2(what) {
|
|
return Promise.reject(
|
|
Object.assign(new Error(`${what} is not available in this Orca`), {
|
|
code: "unavailable",
|
|
retryable: false
|
|
})
|
|
);
|
|
}
|
|
var NOTHING = { dispose: () => void 0 };
|
|
function createOrcaDataSdk(bridge) {
|
|
const plugin2 = Object.freeze({
|
|
request(name, input, options) {
|
|
return bridge()?.plugin?.request(name, input, options) ?? unavailable2("plugin.request");
|
|
},
|
|
on(event, handler) {
|
|
return bridge()?.plugin?.on(event, handler) ?? NOTHING;
|
|
}
|
|
});
|
|
const storage2 = Object.freeze({
|
|
get: (key) => bridge()?.storage?.get(key) ?? unavailable2("storage"),
|
|
set: (key, value) => bridge()?.storage?.set(key, value) ?? unavailable2("storage"),
|
|
delete: (key) => bridge()?.storage?.delete(key) ?? unavailable2("storage"),
|
|
keys: () => bridge()?.storage?.keys() ?? unavailable2("storage"),
|
|
onDidChange: (handler) => bridge()?.storage?.onDidChange(handler) ?? NOTHING
|
|
});
|
|
const settings2 = Object.freeze({
|
|
get: (key) => bridge()?.settings?.get(key) ?? unavailable2("settings"),
|
|
set: (key, value) => bridge()?.settings?.set(key, value) ?? unavailable2("settings"),
|
|
onDidChange: (handler) => bridge()?.settings?.onDidChange(handler) ?? NOTHING
|
|
});
|
|
const secrets2 = Object.freeze({
|
|
get: (key) => bridge()?.secrets?.get(key) ?? unavailable2("secrets"),
|
|
set: (key, value) => bridge()?.secrets?.set(key, value) ?? unavailable2("secrets"),
|
|
delete: (key) => bridge()?.secrets?.delete(key) ?? unavailable2("secrets")
|
|
});
|
|
return { plugin: plugin2, storage: storage2, settings: settings2, secrets: secrets2 };
|
|
}
|
|
|
|
// src/shared/plugins/declarative/runtime/orca-module.ts
|
|
var runtime = null;
|
|
var pendingMount = null;
|
|
function isOrcaBridge(value) {
|
|
return typeof value === "object" && value !== null && "on" in value && typeof value.on === "function";
|
|
}
|
|
function globalBridge() {
|
|
const scope = globalThis;
|
|
const candidate = "orca" in scope ? scope.orca : void 0;
|
|
return isOrcaBridge(candidate) ? candidate : null;
|
|
}
|
|
function documentColorScheme() {
|
|
const root = typeof document === "undefined" ? null : document.documentElement;
|
|
if (!root) {
|
|
return void 0;
|
|
}
|
|
return root.classList.contains("light") ? "light" : "dark";
|
|
}
|
|
function installOrcaDeclarativeRuntime(options) {
|
|
runtime?.dispose();
|
|
resetAtoms();
|
|
installPluginSourceMapHook();
|
|
const installed = createOrcaDeclarativeRuntime({
|
|
bridge: globalBridge(),
|
|
colorScheme: documentColorScheme(),
|
|
...options
|
|
});
|
|
runtime = installed;
|
|
if (typeof addEventListener === "function") {
|
|
addEventListener("pagehide", () => installed.dispose(), { once: true });
|
|
}
|
|
const queued = pendingMount;
|
|
pendingMount = null;
|
|
if (queued) {
|
|
installed.mount(queued);
|
|
}
|
|
return installed;
|
|
}
|
|
function mount(component) {
|
|
if (runtime) {
|
|
runtime.mount(component);
|
|
} else {
|
|
pendingMount = component;
|
|
}
|
|
}
|
|
var sdk = createOrcaSdk({
|
|
bridge: () => runtime?.bridge ?? globalBridge(),
|
|
theme: () => runtime ? read(runtime.themeAtom) : { colorScheme: documentColorScheme() ?? "dark" }
|
|
});
|
|
var { host, api, env, shell, files, on, ui } = sdk;
|
|
var { plugin, storage, settings, secrets } = createOrcaDataSdk(
|
|
() => runtime?.bridge ?? globalBridge()
|
|
);
|
|
var Stack = "Stack";
|
|
var Row = "Row";
|
|
var Text = "Text";
|
|
var Heading = "Heading";
|
|
var Button = "Button";
|
|
var IconButton = "IconButton";
|
|
var Badge = "Badge";
|
|
var Card = "Card";
|
|
var List = "List";
|
|
var ListItem = "ListItem";
|
|
var Input = "Input";
|
|
var Switch = "Switch";
|
|
var Select = "Select";
|
|
var Separator = "Separator";
|
|
var Progress = "Progress";
|
|
var Spinner = "Spinner";
|
|
var EmptyState = "EmptyState";
|
|
var Link = "Link";
|
|
var Code = "Code";
|
|
var Textarea = "Textarea";
|
|
var Checkbox = "Checkbox";
|
|
var RadioGroup = "RadioGroup";
|
|
var Slider = "Slider";
|
|
var Field = "Field";
|
|
var Tabs = "Tabs";
|
|
var Tab = "Tab";
|
|
var Collapsible = "Collapsible";
|
|
var Toolbar = "Toolbar";
|
|
var Grid = "Grid";
|
|
var Table = "Table";
|
|
var Tree = "Tree";
|
|
var Markdown = "Markdown";
|
|
var CodeView = "CodeView";
|
|
var Image = "Image";
|
|
var KeyValue = "KeyValue";
|
|
var Callout = "Callout";
|
|
var Skeleton = "Skeleton";
|
|
var RelativeTime = "RelativeTime";
|
|
var Box = "Box";
|
|
export {
|
|
Badge,
|
|
Box,
|
|
Button,
|
|
Callout,
|
|
Card,
|
|
Checkbox,
|
|
Code,
|
|
CodeView,
|
|
Collapsible,
|
|
EmptyState,
|
|
Field,
|
|
Fragment,
|
|
Grid,
|
|
Heading,
|
|
IconButton,
|
|
Image,
|
|
Input,
|
|
KeyValue,
|
|
Link,
|
|
List,
|
|
ListItem,
|
|
Markdown,
|
|
Progress,
|
|
RadioGroup,
|
|
RelativeTime,
|
|
Row,
|
|
Select,
|
|
Separator,
|
|
Skeleton,
|
|
Slider,
|
|
Spinner,
|
|
Stack,
|
|
Switch,
|
|
Tab,
|
|
Table,
|
|
Tabs,
|
|
Text,
|
|
Textarea,
|
|
Toolbar,
|
|
Tree,
|
|
api,
|
|
atom,
|
|
derived,
|
|
env,
|
|
files,
|
|
h,
|
|
host,
|
|
installOrcaDeclarativeRuntime,
|
|
jsx,
|
|
jsx as jsxDEV,
|
|
jsx as jsxs,
|
|
mount,
|
|
on,
|
|
plugin,
|
|
read,
|
|
secrets,
|
|
set,
|
|
settings,
|
|
shell,
|
|
storage,
|
|
ui,
|
|
update
|
|
};
|