@pyric/cli 0.1.0-alpha.12 → 0.1.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/serve.js +4 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/serve/bundler.d.ts +13 -8
- package/dist/serve/bundler.d.ts.map +1 -1
- package/dist/serve/bundler.js +31 -36
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
- package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
- package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/api-reference/index.html +1 -1
- package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
- package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
- package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
- package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
- package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
- package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
- package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
- package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
- package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
- package/dist/serve/docs-ui/docs/index.json +149 -4
- package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
- package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
- package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
- package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
- package/dist/serve/docs-ui/docs/overview/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +38 -11
- package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +26 -8
- package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
- package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
- package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
- package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
- package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
- package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
- package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
- package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
- package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
- package/dist/serve/docs-ui/docs/tutorial.md +701 -0
- package/dist/serve/docs-ui/llms.txt +2 -0
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +3 -0
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +20 -5
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/worker-runtime.d.ts +2 -2
- package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
- package/dist/serve/entries/worker-runtime.js +45 -10
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts +19 -0
- package/dist/serve/runtime/chip-config.d.ts.map +1 -0
- package/dist/serve/runtime/chip-config.js +19 -0
- package/dist/serve/runtime/chip-config.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +10 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -0
- package/dist/serve/runtime/chip-install.js +15 -0
- package/dist/serve/runtime/chip-install.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +17 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -0
- package/dist/serve/runtime/chip.js +257 -0
- package/dist/serve/runtime/chip.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +22 -0
- package/dist/serve/runtime/manifest.d.ts.map +1 -0
- package/dist/serve/runtime/manifest.js +20 -0
- package/dist/serve/runtime/manifest.js.map +1 -0
- package/dist/serve/runtime/status.d.ts +49 -0
- package/dist/serve/runtime/status.d.ts.map +1 -0
- package/dist/serve/runtime/status.js +164 -0
- package/dist/serve/runtime/status.js.map +1 -0
- package/dist/serve/runtime/worker-connection.d.ts +4 -0
- package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
- package/dist/serve/runtime/worker-connection.js +11 -0
- package/dist/serve/runtime/worker-connection.js.map +1 -0
- package/dist/serve/runtime/worker-generation.d.ts +14 -0
- package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
- package/dist/serve/runtime/worker-generation.js +56 -0
- package/dist/serve/runtime/worker-generation.js.map +1 -0
- package/dist/serve/runtime/worker-replacement.d.ts +18 -0
- package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
- package/dist/serve/runtime/worker-replacement.js +29 -0
- package/dist/serve/runtime/worker-replacement.js.map +1 -0
- package/dist/serve/standalone-assets.d.ts +2 -2
- package/dist/serve/standalone-assets.d.ts.map +1 -1
- package/dist/serve/standalone-assets.js +1 -1
- package/dist/serve/standalone-assets.js.map +1 -1
- package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
- package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
- package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
- package/dist/serve/studio-ui/index.html +1 -1
- package/dist/serve/vite-ai-config.d.ts +25 -0
- package/dist/serve/vite-ai-config.d.ts.map +1 -0
- package/dist/serve/vite-ai-config.js +45 -0
- package/dist/serve/vite-ai-config.js.map +1 -0
- package/dist/serve/vite-plugin.d.ts +17 -36
- package/dist/serve/vite-plugin.d.ts.map +1 -1
- package/dist/serve/vite-plugin.js +38 -57
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-rules-source.d.ts +8 -0
- package/dist/serve/vite-rules-source.d.ts.map +1 -0
- package/dist/serve/vite-rules-source.js +23 -0
- package/dist/serve/vite-rules-source.js.map +1 -0
- package/dist/serve/vite-worker-runtime.d.ts +20 -0
- package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
- package/dist/serve/vite-worker-runtime.js +25 -0
- package/dist/serve/vite-worker-runtime.js.map +1 -0
- package/dist/serve/worker/client/connection.d.ts +7 -2
- package/dist/serve/worker/client/connection.d.ts.map +1 -1
- package/dist/serve/worker/client/connection.js +13 -16
- package/dist/serve/worker/client/connection.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +7 -0
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +28 -0
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/runtime-control.d.ts +8 -0
- package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
- package/dist/serve/worker/client/runtime-control.js +11 -0
- package/dist/serve/worker/client/runtime-control.js.map +1 -0
- package/dist/serve/worker/entry.js +44 -2
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/studio.js +1 -1
- package/dist/serve/worker/host/studio.js.map +1 -1
- package/dist/serve/worker/host-context.d.ts +1 -1
- package/dist/serve/worker/host-context.d.ts.map +1 -1
- package/dist/serve/worker/protocol.d.ts +15 -1
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/retirement.d.ts +21 -0
- package/dist/serve/worker/retirement.d.ts.map +1 -0
- package/dist/serve/worker/retirement.js +97 -0
- package/dist/serve/worker/retirement.js.map +1 -0
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +5 -6
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/vite.d.ts +1 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js.map +1 -1
- package/package.json +5 -4
- package/src/cli/serve.ts +4 -4
- package/src/serve/bundler.ts +41 -15
- package/src/serve/entries/init.ts +3 -0
- package/src/serve/entries/runtime.ts +23 -7
- package/src/serve/entries/worker-runtime.ts +69 -9
- package/src/serve/runtime/chip-config.ts +30 -0
- package/src/serve/runtime/chip-install.ts +27 -0
- package/src/serve/runtime/chip.ts +282 -0
- package/src/serve/runtime/manifest.ts +37 -0
- package/src/serve/runtime/status.ts +216 -0
- package/src/serve/runtime/worker-connection.ts +14 -0
- package/src/serve/runtime/worker-generation.ts +75 -0
- package/src/serve/runtime/worker-replacement.ts +42 -0
- package/src/serve/standalone-assets.ts +2 -2
- package/src/serve/vite-ai-config.ts +77 -0
- package/src/serve/vite-plugin.ts +60 -87
- package/src/serve/vite-rules-source.ts +28 -0
- package/src/serve/vite-worker-runtime.ts +51 -0
- package/src/serve/worker/client/connection.ts +29 -4
- package/src/serve/worker/client/core.ts +34 -0
- package/src/serve/worker/client/runtime-control.ts +22 -0
- package/src/serve/worker/entry.ts +46 -2
- package/src/serve/worker/host/studio.ts +1 -1
- package/src/serve/worker/host-context.ts +1 -1
- package/src/serve/worker/protocol.ts +13 -1
- package/src/serve/worker/retirement.ts +125 -0
- package/src/serve/worker/serve-init.ts +5 -6
- package/src/vite.ts +1 -0
|
@@ -3,28 +3,62 @@ import {
|
|
|
3
3
|
getFirestore,
|
|
4
4
|
getWorkerVersion,
|
|
5
5
|
startPresence,
|
|
6
|
+
subscribeEvents,
|
|
6
7
|
subscribePresence,
|
|
7
8
|
type ClientDb,
|
|
8
9
|
} from '../worker/client.js';
|
|
9
10
|
import { getServiceWorkerFirestore } from '../worker/client/service-worker-connection.js';
|
|
10
11
|
import { isServiceWorkerRealm } from '../worker/service-worker-channel.js';
|
|
12
|
+
import {
|
|
13
|
+
PYRIC_WORKER_NAME,
|
|
14
|
+
PYRIC_WORKER_URL,
|
|
15
|
+
} from '../runtime/manifest.js';
|
|
16
|
+
import { getPyricRuntimeStatus } from '../runtime/status.js';
|
|
17
|
+
import { connectRuntimeWorker } from '../runtime/worker-connection.js';
|
|
18
|
+
import { createWorkerReplacement } from '../runtime/worker-replacement.js';
|
|
19
|
+
import {
|
|
20
|
+
preflightWorkerEpochStorage,
|
|
21
|
+
rememberWorkerEpoch,
|
|
22
|
+
workerNameForEpoch,
|
|
23
|
+
} from '../runtime/worker-generation.js';
|
|
24
|
+
import {
|
|
25
|
+
onWorkerRuntimeReload,
|
|
26
|
+
retireWorkerRuntime,
|
|
27
|
+
} from '../worker/client/runtime-control.js';
|
|
11
28
|
|
|
12
29
|
const hasSharedWorker = typeof SharedWorker !== 'undefined';
|
|
30
|
+
const runtimeStatus = getPyricRuntimeStatus();
|
|
13
31
|
|
|
14
|
-
|
|
32
|
+
const workerRequested =
|
|
15
33
|
(hasSharedWorker || (isServiceWorkerRealm() && typeof BroadcastChannel !== 'undefined'))
|
|
16
34
|
&& !(globalThis as { __PYRIC_FORCE_INPAGE__?: boolean }).__PYRIC_FORCE_INPAGE__;
|
|
17
35
|
|
|
18
|
-
export const WORKER_URL =
|
|
19
|
-
|
|
36
|
+
export const WORKER_URL = PYRIC_WORKER_URL;
|
|
37
|
+
let epochStorage: Storage | undefined;
|
|
38
|
+
try {
|
|
39
|
+
epochStorage = typeof localStorage === 'undefined' ? undefined : localStorage;
|
|
40
|
+
} catch {
|
|
41
|
+
epochStorage = undefined;
|
|
42
|
+
}
|
|
43
|
+
export const WORKER_NAME = workerNameForEpoch(
|
|
44
|
+
runtimeStatus.getSnapshot().servedEpoch,
|
|
45
|
+
epochStorage,
|
|
46
|
+
);
|
|
20
47
|
|
|
21
48
|
/** Control traffic only; Firebase apps receive independent app-owned ports. */
|
|
22
|
-
export const workerDb: ClientDb | null =
|
|
49
|
+
export const workerDb: ClientDb | null = workerRequested
|
|
23
50
|
? hasSharedWorker
|
|
24
|
-
?
|
|
51
|
+
? connectRuntimeWorker(
|
|
52
|
+
() => getFirestore(WORKER_URL, WORKER_NAME, {
|
|
53
|
+
onError: (error) => runtimeStatus.reportError(error, 'worker'),
|
|
54
|
+
}),
|
|
55
|
+
(error) => runtimeStatus.reportError(error, 'worker'),
|
|
56
|
+
)
|
|
25
57
|
: null
|
|
26
58
|
: null;
|
|
27
59
|
|
|
60
|
+
export const useWorker = workerRequested && (!hasSharedWorker || workerDb !== null);
|
|
61
|
+
|
|
28
62
|
export function openWorkerDb(appName: string): ClientDb {
|
|
29
63
|
if (hasSharedWorker) return getFirestore(WORKER_URL, WORKER_NAME);
|
|
30
64
|
if (isServiceWorkerRealm()) return getServiceWorkerFirestore(appName);
|
|
@@ -35,12 +69,38 @@ export const presenceSession = useWorker && workerDb
|
|
|
35
69
|
? startPresence({ db: workerDb, kind: 'app' })
|
|
36
70
|
: null;
|
|
37
71
|
|
|
72
|
+
runtimeStatus.setWorker({
|
|
73
|
+
mode: useWorker ? 'shared-worker' : 'in-page',
|
|
74
|
+
runningEpoch: null,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const workerReplacement = useWorker && workerDb && typeof window !== 'undefined'
|
|
78
|
+
? createWorkerReplacement({
|
|
79
|
+
targetEpoch: runtimeStatus.getSnapshot().servedEpoch!,
|
|
80
|
+
retire: () => retireWorkerRuntime(
|
|
81
|
+
workerDb,
|
|
82
|
+
runtimeStatus.getSnapshot().servedEpoch!,
|
|
83
|
+
),
|
|
84
|
+
subscribeReload: onWorkerRuntimeReload,
|
|
85
|
+
preflight: () => preflightWorkerEpochStorage(epochStorage),
|
|
86
|
+
commitGeneration: (epoch) => rememberWorkerEpoch(epoch, epochStorage),
|
|
87
|
+
onPreparationError: (error) => runtimeStatus.reportError(error, 'worker'),
|
|
88
|
+
reload: () => window.location.reload(),
|
|
89
|
+
})
|
|
90
|
+
: null;
|
|
91
|
+
runtimeStatus.setWorkerUpdater(
|
|
92
|
+
workerReplacement ? () => workerReplacement.request() : null,
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (useWorker && workerDb) {
|
|
96
|
+
subscribeEvents(workerDb, (events) => runtimeStatus.recordSandboxEvents(events));
|
|
97
|
+
}
|
|
98
|
+
|
|
38
99
|
if (useWorker && typeof document !== 'undefined') {
|
|
39
|
-
const servedVersion =
|
|
40
|
-
.querySelector('meta[name="pyric-worker-v"]')
|
|
41
|
-
?.getAttribute('content');
|
|
100
|
+
const servedVersion = runtimeStatus.getSnapshot().servedEpoch;
|
|
42
101
|
void getWorkerVersion(workerDb!)
|
|
43
102
|
.then(async (runningVersion) => {
|
|
103
|
+
runtimeStatus.setWorker({ mode: 'shared-worker', runningEpoch: runningVersion });
|
|
44
104
|
if (
|
|
45
105
|
!servedVersion
|
|
46
106
|
|| !runningVersion
|
|
@@ -71,5 +131,5 @@ if (useWorker && typeof document !== 'undefined') {
|
|
|
71
131
|
+ `the old code running for everyone.)${othersHint}`,
|
|
72
132
|
);
|
|
73
133
|
})
|
|
74
|
-
.catch(() =>
|
|
134
|
+
.catch((error) => runtimeStatus.reportError(error, 'worker'));
|
|
75
135
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type PyricRuntimeChipOption = boolean | { initiallyOpen?: boolean };
|
|
2
|
+
|
|
3
|
+
export interface PyricRuntimeChipConfig {
|
|
4
|
+
initiallyOpen: boolean;
|
|
5
|
+
studioEnabled: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const PYRIC_RUNTIME_CHIP_META = 'pyric-runtime-chip';
|
|
9
|
+
|
|
10
|
+
/** Encode the Vite option as declarative HTML consumed by the init entry. */
|
|
11
|
+
export function runtimeChipMetaValue(option: PyricRuntimeChipOption | undefined): string {
|
|
12
|
+
if (option === false) return 'off';
|
|
13
|
+
return typeof option === 'object' && option.initiallyOpen ? 'expanded' : 'collapsed';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RuntimeChipDocument {
|
|
17
|
+
querySelector(selector: string): { getAttribute(name: string): string | null } | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Read only plugin-authored values; absent/unknown metadata keeps the UI off. */
|
|
21
|
+
export function readPyricRuntimeChipConfig(
|
|
22
|
+
documentLike: RuntimeChipDocument,
|
|
23
|
+
): PyricRuntimeChipConfig | null {
|
|
24
|
+
const meta = documentLike.querySelector(`meta[name="${PYRIC_RUNTIME_CHIP_META}"]`);
|
|
25
|
+
const value = meta?.getAttribute('content');
|
|
26
|
+
const studioEnabled = meta?.getAttribute('data-studio') !== 'off';
|
|
27
|
+
if (value === 'collapsed') return { initiallyOpen: false, studioEnabled };
|
|
28
|
+
if (value === 'expanded') return { initiallyOpen: true, studioEnabled };
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mountPyricRuntimeChip,
|
|
3
|
+
type PyricRuntimeChip,
|
|
4
|
+
type PyricRuntimeChipOptions,
|
|
5
|
+
} from './chip.js';
|
|
6
|
+
import { readPyricRuntimeChipConfig } from './chip-config.js';
|
|
7
|
+
import type { PyricRuntimeStatus } from './status.js';
|
|
8
|
+
|
|
9
|
+
export interface InstallPyricRuntimeChipOptions {
|
|
10
|
+
runtime: PyricRuntimeStatus;
|
|
11
|
+
document: Document;
|
|
12
|
+
mount?: (options: PyricRuntimeChipOptions) => PyricRuntimeChip;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Mount once when the Vite plugin opted this page into runtime UI. */
|
|
16
|
+
export function installPyricRuntimeChip(
|
|
17
|
+
options: InstallPyricRuntimeChipOptions,
|
|
18
|
+
): PyricRuntimeChip | null {
|
|
19
|
+
const config = readPyricRuntimeChipConfig(options.document);
|
|
20
|
+
if (!config || options.document.querySelector('[data-pyric-runtime-chip-host]')) return null;
|
|
21
|
+
return (options.mount ?? mountPyricRuntimeChip)({
|
|
22
|
+
runtime: options.runtime,
|
|
23
|
+
document: options.document,
|
|
24
|
+
initiallyOpen: config.initiallyOpen,
|
|
25
|
+
...(config.studioEnabled ? {} : { studioUrl: null }),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PyricRuntimeError,
|
|
3
|
+
PyricRuntimeSnapshot,
|
|
4
|
+
PyricRuntimeStatus,
|
|
5
|
+
} from './status.js';
|
|
6
|
+
|
|
7
|
+
export interface PyricRuntimeChipOptions {
|
|
8
|
+
runtime: PyricRuntimeStatus;
|
|
9
|
+
document?: Document;
|
|
10
|
+
clipboard?: Pick<Clipboard, 'writeText'>;
|
|
11
|
+
initiallyOpen?: boolean;
|
|
12
|
+
/** Override Studio availability. Omitted uses the runtime manifest URL. */
|
|
13
|
+
studioUrl?: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PyricRuntimeChip {
|
|
17
|
+
element: HTMLElement;
|
|
18
|
+
dispose(): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const styles = `
|
|
22
|
+
:host {
|
|
23
|
+
--pyric-bg: #1e1e24;
|
|
24
|
+
--pyric-content: #16161a;
|
|
25
|
+
--pyric-border: #33333f;
|
|
26
|
+
--pyric-border-soft: #2a2a35;
|
|
27
|
+
--pyric-text: #fbfbfe;
|
|
28
|
+
--pyric-muted: #89899f;
|
|
29
|
+
--pyric-accent: #19cc61;
|
|
30
|
+
--pyric-warning: #e6c79c;
|
|
31
|
+
--pyric-error: #f0a0a0;
|
|
32
|
+
all: initial;
|
|
33
|
+
position: fixed;
|
|
34
|
+
right: max(20px, env(safe-area-inset-right));
|
|
35
|
+
bottom: max(20px, env(safe-area-inset-bottom));
|
|
36
|
+
z-index: 2147483000;
|
|
37
|
+
color: var(--pyric-text);
|
|
38
|
+
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
39
|
+
font-synthesis: none;
|
|
40
|
+
}
|
|
41
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
42
|
+
.announcer { height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
43
|
+
button, a { font: inherit; }
|
|
44
|
+
button { margin: 0; }
|
|
45
|
+
.chip {
|
|
46
|
+
align-items: center;
|
|
47
|
+
background: var(--pyric-bg);
|
|
48
|
+
border: 1px solid var(--pyric-border);
|
|
49
|
+
border-radius: 999px;
|
|
50
|
+
box-shadow: 0 12px 34px rgba(0, 0, 0, .38);
|
|
51
|
+
color: var(--pyric-text);
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
display: flex;
|
|
54
|
+
gap: 10px;
|
|
55
|
+
height: 36px;
|
|
56
|
+
padding: 0 12px;
|
|
57
|
+
}
|
|
58
|
+
.chip:hover { border-color: #4a4a58; }
|
|
59
|
+
.brand, .signals, .signal, .panel-title, .worker-state { align-items: center; display: flex; }
|
|
60
|
+
.brand { gap: 8px; }
|
|
61
|
+
.brand-mark { color: rgba(251, 251, 254, .78); font: 600 11px/1 ui-monospace, monospace; }
|
|
62
|
+
.brand-label, .signals, .worker-state, code, .button { font-family: "JetBrains Mono", ui-monospace, monospace; }
|
|
63
|
+
.brand-label { font-size: 11px; }
|
|
64
|
+
.signals { color: var(--pyric-muted); font-size: 10px; gap: 8px; }
|
|
65
|
+
.signal { gap: 4px; white-space: nowrap; }
|
|
66
|
+
.dot { background: var(--pyric-accent); border-radius: 50%; height: 8px; width: 8px; }
|
|
67
|
+
.dot.error { background: var(--pyric-error); box-shadow: 0 0 0 3px rgba(240,160,160,.12); }
|
|
68
|
+
.signal.update { color: var(--pyric-warning); }
|
|
69
|
+
.chevron { color: var(--pyric-muted); height: 14px; width: 14px; }
|
|
70
|
+
.panel {
|
|
71
|
+
background: var(--pyric-bg);
|
|
72
|
+
border: 1px solid var(--pyric-border);
|
|
73
|
+
border-radius: 8px;
|
|
74
|
+
box-shadow: 0 18px 60px rgba(0, 0, 0, .48);
|
|
75
|
+
max-width: calc(100vw - 40px);
|
|
76
|
+
overflow: hidden;
|
|
77
|
+
width: 380px;
|
|
78
|
+
}
|
|
79
|
+
.panel-header { align-items: center; display: flex; height: 44px; justify-content: space-between; padding: 0 12px; }
|
|
80
|
+
.panel-title { gap: 8px; min-width: 0; }
|
|
81
|
+
.panel-title strong { font: 500 12px/1 ui-monospace, monospace; }
|
|
82
|
+
.count { background: rgba(58,42,42,.3); border: 1px solid #3a2a2a; border-radius: 999px; color: var(--pyric-error); font: 9px/1 ui-monospace, monospace; padding: 4px 6px; }
|
|
83
|
+
.icon-button { align-items: center; background: transparent; border: 0; border-radius: 4px; color: var(--pyric-muted); cursor: pointer; display: inline-flex; height: 28px; justify-content: center; padding: 0; width: 28px; }
|
|
84
|
+
.icon-button:hover { background: rgba(255,255,255,.05); color: var(--pyric-text); }
|
|
85
|
+
.icon-button:disabled { cursor: not-allowed; opacity: .42; }
|
|
86
|
+
.icon-button[data-copy-failed] { color: var(--pyric-error); }
|
|
87
|
+
.icon { height: 15px; width: 15px; }
|
|
88
|
+
.worker-state { border-top: 1px solid var(--pyric-border-soft); color: var(--pyric-muted); font-size: 10px; justify-content: space-between; min-height: 34px; padding: 7px 12px; }
|
|
89
|
+
.worker-state .available { color: var(--pyric-warning); }
|
|
90
|
+
.worker-state .state-label { align-items: center; display: flex; gap: 7px; }
|
|
91
|
+
.mini-dot { background: var(--pyric-accent); border-radius: 50%; height: 6px; width: 6px; }
|
|
92
|
+
.available .mini-dot { background: var(--pyric-warning); }
|
|
93
|
+
.epochs { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
94
|
+
.errors { background: var(--pyric-content); border-bottom: 1px solid var(--pyric-border-soft); border-top: 1px solid var(--pyric-border-soft); max-height: 184px; min-height: 58px; overflow-y: auto; }
|
|
95
|
+
.errors::-webkit-scrollbar { width: 8px; }
|
|
96
|
+
.errors::-webkit-scrollbar-thumb { background: #33333f; border-radius: 4px; }
|
|
97
|
+
.error-row { align-items: flex-start; border-bottom: 1px solid rgba(42,42,53,.7); display: grid; gap: 8px; grid-template-columns: 18px minmax(0,1fr) 28px; padding: 10px 8px 10px 12px; }
|
|
98
|
+
.error-row:last-child { border-bottom: 0; }
|
|
99
|
+
.error-number { color: var(--pyric-error); font: 10px/20px ui-monospace, monospace; }
|
|
100
|
+
.error-body { min-width: 0; }
|
|
101
|
+
.error-body code { color: #d7d7df; display: block; font-size: 11px; line-height: 1.55; overflow-wrap: anywhere; white-space: pre-wrap; }
|
|
102
|
+
.error-meta { color: var(--pyric-muted); font: 9px/1.4 ui-monospace, monospace; margin-top: 4px; overflow-wrap: anywhere; }
|
|
103
|
+
.empty { align-items: center; color: var(--pyric-muted); display: flex; font: 11px/1.5 ui-monospace, monospace; min-height: 57px; padding: 12px; }
|
|
104
|
+
.actions { display: grid; gap: 8px; grid-template-columns: 1fr 1fr; min-height: 56px; padding: 10px 12px; }
|
|
105
|
+
.button { align-items: center; background: transparent; border: 1px solid var(--pyric-border-soft); border-radius: 4px; color: var(--pyric-muted); display: inline-flex; font-size: 10px; justify-content: center; letter-spacing: .06em; min-height: 34px; padding: 6px 8px; text-decoration: none; text-transform: uppercase; }
|
|
106
|
+
button.button { cursor: pointer; }
|
|
107
|
+
.button:hover:not(:disabled):not([aria-disabled="true"]), a.button:hover { border-color: #3a3a48; color: var(--pyric-text); }
|
|
108
|
+
.button.update:not(:disabled):not([aria-disabled="true"]) { background: rgba(230,199,156,.1); border-color: rgba(230,199,156,.4); color: var(--pyric-warning); }
|
|
109
|
+
.button.update:not(:disabled):not([aria-disabled="true"]):hover { background: rgba(230,199,156,.15); }
|
|
110
|
+
.button:disabled, .button[aria-disabled="true"] { cursor: not-allowed; opacity: .42; }
|
|
111
|
+
.button svg { height: 14px; margin-left: 6px; width: 14px; }
|
|
112
|
+
@media (max-width: 460px) {
|
|
113
|
+
:host { bottom: max(12px, env(safe-area-inset-bottom)); right: 12px; }
|
|
114
|
+
.panel { max-width: calc(100vw - 24px); }
|
|
115
|
+
.worker-state { align-items: flex-start; flex-direction: column; gap: 4px; }
|
|
116
|
+
}
|
|
117
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
118
|
+
.chip, .panel { animation: pyric-enter 120ms ease-out; transform-origin: bottom right; }
|
|
119
|
+
@keyframes pyric-enter { from { opacity: 0; transform: translateY(4px) scale(.98); } }
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
const icons = {
|
|
124
|
+
close: '<svg class="icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="m6 6 12 12M18 6 6 18"/></svg>',
|
|
125
|
+
copy: '<svg class="icon" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><rect x="8" y="8" width="11" height="11" rx="1"/><path d="M16 8V5H5v11h3"/></svg>',
|
|
126
|
+
chevron: '<svg class="chevron" aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="m6 15 6-6 6 6"/></svg>',
|
|
127
|
+
external: '<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"><path d="M14 5h5v5M19 5l-8 8"/><path d="M19 13v6H5V5h6"/></svg>',
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function escapeAttribute(value: string): string {
|
|
131
|
+
return value.replaceAll('&', '&').replaceAll('"', '"').replaceAll('<', '<');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function formatPyricRuntimeError(error: PyricRuntimeError): string {
|
|
135
|
+
const context = [
|
|
136
|
+
error.source,
|
|
137
|
+
error.service && error.method ? `${error.service}.${error.method}` : error.service ?? error.method,
|
|
138
|
+
error.path,
|
|
139
|
+
error.code,
|
|
140
|
+
].filter(Boolean).join(' · ');
|
|
141
|
+
return `${error.message}${context ? `\n${context}` : ''}${error.stack ? `\n${error.stack}` : ''}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function renderErrors(snapshot: PyricRuntimeSnapshot, canCopy: boolean): string {
|
|
145
|
+
if (snapshot.errors.length === 0) return '<div class="empty">No sandbox errors.</div>';
|
|
146
|
+
return snapshot.errors.map((error, index) => `
|
|
147
|
+
<div class="error-row" data-error-id="${escapeAttribute(error.id)}">
|
|
148
|
+
<span class="error-number">${String(index + 1).padStart(2, '0')}</span>
|
|
149
|
+
<div class="error-body"><code></code><div class="error-meta"></div></div>
|
|
150
|
+
<button class="icon-button" type="button" data-copy-error="${escapeAttribute(error.id)}" aria-label="${canCopy ? `Copy error ${index + 1}` : 'Copy unavailable'}" title="${canCopy ? 'Copy error' : 'Clipboard unavailable'}" ${canCopy ? '' : 'disabled'}>${icons.copy}</button>
|
|
151
|
+
</div>
|
|
152
|
+
`).join('');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Mount the framework-independent runtime chip in an isolated shadow root. */
|
|
156
|
+
export function mountPyricRuntimeChip(options: PyricRuntimeChipOptions): PyricRuntimeChip {
|
|
157
|
+
const documentLike = options.document ?? document;
|
|
158
|
+
const host = documentLike.createElement('div');
|
|
159
|
+
host.setAttribute('data-pyric-runtime-chip-host', '');
|
|
160
|
+
const root = host.attachShadow({ mode: 'open' });
|
|
161
|
+
root.innerHTML = `<style>${styles}</style><div class="announcer" role="status" aria-live="polite" aria-atomic="true"></div><div data-view></div>`;
|
|
162
|
+
const view = root.querySelector<HTMLElement>('[data-view]')!;
|
|
163
|
+
const announcer = root.querySelector<HTMLElement>('.announcer')!;
|
|
164
|
+
const clipboard = options.clipboard
|
|
165
|
+
?? documentLike.defaultView?.navigator.clipboard;
|
|
166
|
+
const studioUrl = 'studioUrl' in options
|
|
167
|
+
? options.studioUrl
|
|
168
|
+
: options.runtime.getSnapshot().manifest.studioUrl;
|
|
169
|
+
let open = options.initiallyOpen ?? false;
|
|
170
|
+
let snapshot = options.runtime.getSnapshot();
|
|
171
|
+
|
|
172
|
+
const render = (next = snapshot): void => {
|
|
173
|
+
const active = root.activeElement;
|
|
174
|
+
const oldViewport = view.querySelector<HTMLElement>('[data-error-viewport]');
|
|
175
|
+
const oldScroll = oldViewport
|
|
176
|
+
? {
|
|
177
|
+
top: oldViewport.scrollTop,
|
|
178
|
+
atBottom: oldViewport.scrollHeight - oldViewport.scrollTop - oldViewport.clientHeight <= 8,
|
|
179
|
+
}
|
|
180
|
+
: null;
|
|
181
|
+
const activeCopyId = active?.getAttribute('data-copy-error');
|
|
182
|
+
const activeControl = ['data-expand', 'data-collapse', 'data-update-worker', 'data-open-studio']
|
|
183
|
+
.find((attribute) => active?.hasAttribute(attribute));
|
|
184
|
+
const focusToken = activeCopyId !== null && activeCopyId !== undefined
|
|
185
|
+
? { attribute: 'data-copy-error', value: activeCopyId }
|
|
186
|
+
: activeControl
|
|
187
|
+
? { attribute: activeControl, value: null }
|
|
188
|
+
: null;
|
|
189
|
+
snapshot = next;
|
|
190
|
+
const errorCount = snapshot.errors.length;
|
|
191
|
+
const workerLabel = snapshot.updateAvailable
|
|
192
|
+
? 'New worker available'
|
|
193
|
+
: snapshot.mode === 'starting'
|
|
194
|
+
? 'Sandbox starting'
|
|
195
|
+
: snapshot.mode === 'in-page'
|
|
196
|
+
? 'In-page sandbox'
|
|
197
|
+
: 'Worker current';
|
|
198
|
+
const epochs = snapshot.updateAvailable
|
|
199
|
+
? `${snapshot.runningEpoch?.slice(0, 8) ?? 'unknown'} → ${snapshot.servedEpoch?.slice(0, 8) ?? 'unknown'}`
|
|
200
|
+
: snapshot.runningEpoch?.slice(0, 8) ?? '';
|
|
201
|
+
|
|
202
|
+
view.innerHTML = `${open ? `
|
|
203
|
+
<section class="panel" role="dialog" aria-label="Pyric runtime">
|
|
204
|
+
<header class="panel-header">
|
|
205
|
+
<div class="panel-title"><span class="brand-mark">>_</span><strong>Pyric runtime</strong>${errorCount > 0 ? `<span class="count">${errorCount} ${errorCount === 1 ? 'error' : 'errors'}</span>` : ''}</div>
|
|
206
|
+
<button class="icon-button" type="button" data-collapse aria-label="Collapse Pyric runtime">${icons.close}</button>
|
|
207
|
+
</header>
|
|
208
|
+
<div class="worker-state"><span class="state-label${snapshot.updateAvailable ? ' available' : ''}"><span class="mini-dot"></span>${workerLabel}</span><span class="epochs">${epochs}</span></div>
|
|
209
|
+
<div class="errors" data-error-viewport>${renderErrors(snapshot, Boolean(clipboard))}</div>
|
|
210
|
+
<div class="actions">
|
|
211
|
+
<button class="button update" type="button" data-update-worker ${snapshot.updateAvailable ? '' : 'disabled'} aria-disabled="${snapshot.updateAvailable && !snapshot.updatingWorker ? 'false' : 'true'}">${snapshot.updatingWorker ? 'Updating…' : 'Update worker'}</button>
|
|
212
|
+
${studioUrl
|
|
213
|
+
? `<a class="button" data-open-studio href="${escapeAttribute(studioUrl)}" target="_blank" rel="noopener noreferrer">Studio${icons.external}</a>`
|
|
214
|
+
: `<span class="button" data-open-studio aria-disabled="true" title="Pyric Studio is disabled">Studio${icons.external}</span>`}
|
|
215
|
+
</div>
|
|
216
|
+
</section>
|
|
217
|
+
` : `
|
|
218
|
+
<button class="chip" type="button" data-expand aria-label="Open Pyric runtime" aria-expanded="false">
|
|
219
|
+
<span class="brand"><span class="dot${errorCount > 0 ? ' error' : ''}"></span><span class="brand-label">Pyric</span></span>
|
|
220
|
+
<span class="signals">${snapshot.updateAvailable ? '<span class="signal update">update</span>' : ''}${errorCount > 0 ? `<span class="signal">${errorCount} ${errorCount === 1 ? 'error' : 'errors'}</span>` : '<span class="signal">ready</span>'}${icons.chevron}</span>
|
|
221
|
+
</button>
|
|
222
|
+
`}`;
|
|
223
|
+
|
|
224
|
+
const announcement = `${workerLabel}. ${errorCount === 0 ? 'No runtime errors' : `${errorCount} runtime ${errorCount === 1 ? 'error' : 'errors'}`}.`;
|
|
225
|
+
if (announcer.textContent !== announcement) announcer.textContent = announcement;
|
|
226
|
+
const newViewport = view.querySelector<HTMLElement>('[data-error-viewport]');
|
|
227
|
+
if (oldScroll && newViewport) {
|
|
228
|
+
newViewport.scrollTop = oldScroll.atBottom ? newViewport.scrollHeight : oldScroll.top;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (const error of snapshot.errors) {
|
|
232
|
+
const row = [...root.querySelectorAll('[data-error-id]')]
|
|
233
|
+
.find((candidate) => candidate.getAttribute('data-error-id') === error.id);
|
|
234
|
+
const code = row?.querySelector('code');
|
|
235
|
+
const meta = row?.querySelector('.error-meta');
|
|
236
|
+
if (code) code.textContent = error.message;
|
|
237
|
+
if (meta) meta.textContent = [error.source, error.service && error.method ? `${error.service}.${error.method}` : error.service ?? error.method, error.path, error.code].filter(Boolean).join(' · ');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
root.querySelector('[data-expand]')?.addEventListener('click', () => {
|
|
241
|
+
open = true;
|
|
242
|
+
render();
|
|
243
|
+
root.querySelector<HTMLButtonElement>('[data-collapse]')?.focus();
|
|
244
|
+
});
|
|
245
|
+
root.querySelector('[data-collapse]')?.addEventListener('click', () => {
|
|
246
|
+
open = false;
|
|
247
|
+
render();
|
|
248
|
+
root.querySelector<HTMLButtonElement>('[data-expand]')?.focus();
|
|
249
|
+
});
|
|
250
|
+
root.querySelector('[data-update-worker]')?.addEventListener('click', () => {
|
|
251
|
+
if (!snapshot.updateAvailable || snapshot.updatingWorker) return;
|
|
252
|
+
void options.runtime.updateWorker().catch(() => { /* status records and renders the failure */ });
|
|
253
|
+
});
|
|
254
|
+
for (const button of root.querySelectorAll<HTMLButtonElement>('[data-copy-error]')) {
|
|
255
|
+
button.addEventListener('click', () => {
|
|
256
|
+
const error = snapshot.errors.find((item) => item.id === button.dataset.copyError);
|
|
257
|
+
if (!error || !clipboard) return;
|
|
258
|
+
void clipboard.writeText(formatPyricRuntimeError(error)).catch(() => {
|
|
259
|
+
button.setAttribute('data-copy-failed', '');
|
|
260
|
+
button.setAttribute('aria-label', 'Copy failed');
|
|
261
|
+
button.title = 'Copy failed';
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
if (focusToken) {
|
|
266
|
+
const candidates = root.querySelectorAll<HTMLElement>(`[${focusToken.attribute}]`);
|
|
267
|
+
const replacement = [...candidates].find((candidate) =>
|
|
268
|
+
focusToken.value === null || candidate.getAttribute(focusToken.attribute) === focusToken.value);
|
|
269
|
+
replacement?.focus();
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
documentLike.body.append(host);
|
|
274
|
+
const unsubscribe = options.runtime.subscribe(render);
|
|
275
|
+
return {
|
|
276
|
+
element: host,
|
|
277
|
+
dispose() {
|
|
278
|
+
unsubscribe();
|
|
279
|
+
host.remove();
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Stable routes and identity used by the served runtime and its UI. */
|
|
2
|
+
export const PYRIC_WORKER_URL = '/__pyric/sdk/worker.js';
|
|
3
|
+
export const PYRIC_WORKER_NAME = 'pyric-shared-worker';
|
|
4
|
+
export const PYRIC_STUDIO_URL = '/__pyric/ui/';
|
|
5
|
+
|
|
6
|
+
export interface PyricRuntimeManifest {
|
|
7
|
+
studioUrl: string;
|
|
8
|
+
worker: {
|
|
9
|
+
url: string;
|
|
10
|
+
name: string;
|
|
11
|
+
/** Content epoch served by the current dev server; null in in-page mode. */
|
|
12
|
+
servedEpoch: string | null;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface RuntimeDocument {
|
|
17
|
+
querySelector(selector: string): { getAttribute(name: string): string | null } | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Read the server-stamped worker epoch before application code starts. */
|
|
21
|
+
export function readPyricRuntimeManifest(
|
|
22
|
+
documentLike: RuntimeDocument | undefined = typeof document === 'undefined' ? undefined : document,
|
|
23
|
+
): PyricRuntimeManifest {
|
|
24
|
+
const servedEpoch = documentLike
|
|
25
|
+
?.querySelector('meta[name="pyric-worker-v"]')
|
|
26
|
+
?.getAttribute('content')
|
|
27
|
+
?.trim() || null;
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
studioUrl: PYRIC_STUDIO_URL,
|
|
31
|
+
worker: {
|
|
32
|
+
url: PYRIC_WORKER_URL,
|
|
33
|
+
name: PYRIC_WORKER_NAME,
|
|
34
|
+
servedEpoch,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|