arcane-os 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +14 -8
- package/browser-runtime/browser-device.mjs +13 -0
- package/browser-runtime/pwa.mjs +215 -0
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +5 -3
- package/docs/reference/ai/browser-wasm.md +19 -0
- package/docs/reference/asset-versioning.md +13 -0
- package/docs/reference/inventory/package-api.json +45 -1
- package/docs/reference/pwa.md +183 -0
- package/docs/reference/sdk-api.md +86 -3
- package/package.json +4 -2
- package/runtime/arcane/modules/HTMLImport.js +13 -3
- package/schemas/arcane-app.schema.json +28 -0
- package/schemas/arcane-package.schema.json +30 -0
- package/src/app-descriptor.mjs +10 -2
- package/src/dev-server.mjs +252 -13
- package/src/import-map.mjs +187 -11
- package/src/packager/core.mjs +140 -17
- package/src/pwa-worker.mjs +227 -0
- package/src/pwa.mjs +311 -0
- package/src/targets/index.mjs +1 -0
- package/src/toolchain.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
- Add opt-in application PWA configuration, generated installation and offline
|
|
6
|
+
manifests, a stable service worker and asynchronous registration bootstrap.
|
|
7
|
+
Keep branding and offline resource selection application-owned.
|
|
8
|
+
- Expose `registerPwa()` and `PWA_STATE_EVENT` through `arcane-os/pwa`, with
|
|
9
|
+
current-state replay, native update lifecycle, complete errors and disposal.
|
|
10
|
+
Keep registration independent of rendering, preferences and model startup.
|
|
11
|
+
- Use clean local resource URLs for enabled PWA browser delivery. Remove `v`
|
|
12
|
+
and `arcaneVersion` from actual references and managed import-map aliases,
|
|
13
|
+
preserving meaningful queries, fragments and unrelated payloads. Dynamic
|
|
14
|
+
HTML imports follow the same document-owned selection.
|
|
15
|
+
- Revalidate PWA source resources for live development and retain selected
|
|
16
|
+
successful responses offline. Packaged output uses a separate cache generation
|
|
17
|
+
for each deployment revision, bounded concurrent installation and normal
|
|
18
|
+
browser activation. Preserve user data, model caches and native packaging.
|
|
19
|
+
|
|
20
|
+
## 0.9.0
|
|
21
|
+
|
|
22
|
+
- Add `getBrowserDeviceClass()` through the dependency-free
|
|
23
|
+
`arcane-os/browser-device` entrypoint and managed browser import map. Return
|
|
24
|
+
`mobile` or `desktop` from browser identity hints, including Android tablets
|
|
25
|
+
and iPadOS Mac-platform touch identity, for application-owned settings.
|
|
26
|
+
- Keep classification independent of model imports, GPU requests, network,
|
|
27
|
+
storage, listeners, and viewport changes. Missing or unrecognized identity
|
|
28
|
+
defaults to `desktop`; the result does not claim hardware capability or
|
|
29
|
+
model readiness, and existing provider defaults remain unchanged.
|
|
30
|
+
|
|
3
31
|
## 0.8.1
|
|
4
32
|
|
|
5
33
|
- Use `arcaneVersion` as the sole local resource version field across import maps,
|
package/README.md
CHANGED
|
@@ -19,10 +19,16 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.10.0` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
26
|
+
Applications can enable [PWA installation and offline resources](docs/reference/pwa.md)
|
|
27
|
+
through their app descriptor. The SDK generates manifests, an independent
|
|
28
|
+
registration module and a service worker, with clean browser resource URLs,
|
|
29
|
+
live-source revalidation and selected release caches. Apps retain ownership of
|
|
30
|
+
branding, offline page selection and network-dependent product behavior.
|
|
31
|
+
|
|
26
32
|
That registry query is a maintainer action, not an application behavior. Apps
|
|
27
33
|
never poll npm for SDK updates or replace their own SDK or synchronized runtime.
|
|
28
34
|
The app repository's exact dependency and lockfile select the SDK; changing that
|
|
@@ -35,7 +41,7 @@ Create one browser application, install its pinned SDK, and start its source
|
|
|
35
41
|
server:
|
|
36
42
|
|
|
37
43
|
```bash
|
|
38
|
-
npx arcane-os@0.
|
|
44
|
+
npx arcane-os@0.10.0 new hello-speech --path ./hello-speech --target browser
|
|
39
45
|
cd hello-speech
|
|
40
46
|
npm install
|
|
41
47
|
npm run dev
|
|
@@ -386,7 +392,7 @@ uses the same controller for automatic memory extraction.
|
|
|
386
392
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
387
393
|
|
|
388
394
|
```bash
|
|
389
|
-
npx arcane-os@0.
|
|
395
|
+
npx arcane-os@0.10.0 new my-app --path ./my-app --target portable --git
|
|
390
396
|
cd my-app
|
|
391
397
|
npm install
|
|
392
398
|
npm run dev
|
|
@@ -396,7 +402,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
396
402
|
missing Arcane files:
|
|
397
403
|
|
|
398
404
|
```bash
|
|
399
|
-
npm install --save-dev --save-exact arcane-os@0.
|
|
405
|
+
npm install --save-dev --save-exact arcane-os@0.10.0
|
|
400
406
|
npm exec -- arcane init my-app --target portable
|
|
401
407
|
```
|
|
402
408
|
|
|
@@ -412,7 +418,7 @@ npm exec -- arcane-os targets
|
|
|
412
418
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
413
419
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
414
420
|
|
|
415
|
-
Use `npx arcane-os@0.
|
|
421
|
+
Use `npx arcane-os@0.10.0` for the initial bootstrap because it names this npm
|
|
416
422
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
417
423
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
418
424
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -432,7 +438,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
432
438
|
|
|
433
439
|
# From the generated app repository
|
|
434
440
|
cd ../local-app
|
|
435
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.
|
|
441
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.10.0.tgz
|
|
436
442
|
npm ci
|
|
437
443
|
```
|
|
438
444
|
|
|
@@ -441,7 +447,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
441
447
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
442
448
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
443
449
|
runner also needs that tarball at the locked path. After publication, replace
|
|
444
|
-
the local declaration with the exact `arcane-os@0.
|
|
450
|
+
the local declaration with the exact `arcane-os@0.10.0` registry package and
|
|
445
451
|
commit the regenerated lock.
|
|
446
452
|
|
|
447
453
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -580,7 +586,7 @@ package installation, or assertions.
|
|
|
580
586
|
|
|
581
587
|
## Current target support
|
|
582
588
|
|
|
583
|
-
Version `0.
|
|
589
|
+
Version `0.10.0` exposes one browser target and five explicitly paired
|
|
584
590
|
native development targets: a non-runnable portable directory, a
|
|
585
591
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
586
592
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Selects application settings from browser identity hints, including tablets. */
|
|
2
|
+
export function getBrowserDeviceClass(navigatorObject = globalThis.navigator) {
|
|
3
|
+
const userAgent = String(navigatorObject?.userAgent ?? '');
|
|
4
|
+
const clientHints = navigatorObject?.userAgentData;
|
|
5
|
+
const platform = String(clientHints?.platform || navigatorObject?.platform || '');
|
|
6
|
+
const mobile = clientHints?.mobile === true
|
|
7
|
+
|| /\b(?:Android|iOS|iPhone|iPad|iPod)\b/iu.test(platform)
|
|
8
|
+
|| /\b(?:Android|iPhone|iPad|iPod|Mobile)\b/iu.test(userAgent)
|
|
9
|
+
// iPadOS can identify as a Mac while retaining its touch capabilities.
|
|
10
|
+
|| (/\b(?:MacIntel|Macintosh|macOS)\b/iu.test(platform)
|
|
11
|
+
&& navigatorObject?.maxTouchPoints > 1);
|
|
12
|
+
return mobile ? 'mobile' : 'desktop';
|
|
13
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {createArcaneEventSource} from './event-manager.mjs';
|
|
2
|
+
|
|
3
|
+
export const PWA_STATE_EVENT = 'arcane.pwa.state';
|
|
4
|
+
|
|
5
|
+
export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
|
|
6
|
+
const owner = {
|
|
7
|
+
get ready() {
|
|
8
|
+
return ready;
|
|
9
|
+
},
|
|
10
|
+
get state() {
|
|
11
|
+
return snapshot();
|
|
12
|
+
},
|
|
13
|
+
subscribe,
|
|
14
|
+
dispose,
|
|
15
|
+
update
|
|
16
|
+
};
|
|
17
|
+
const source = createArcaneEventSource(
|
|
18
|
+
owner,
|
|
19
|
+
{
|
|
20
|
+
source: 'arcane.pwa',
|
|
21
|
+
eventTypes: [PWA_STATE_EVENT]
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
const container = globalThis.navigator?.serviceWorker;
|
|
25
|
+
const listeners = [];
|
|
26
|
+
const workers = new Map();
|
|
27
|
+
let registration = null;
|
|
28
|
+
let disposed = false;
|
|
29
|
+
let current = {
|
|
30
|
+
status: 'registering',
|
|
31
|
+
workerUrl: String(workerUrl),
|
|
32
|
+
scope: scope === undefined ? null : String(scope),
|
|
33
|
+
controller: null,
|
|
34
|
+
installing: null,
|
|
35
|
+
waiting: null,
|
|
36
|
+
active: null,
|
|
37
|
+
error: null
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function snapshot() {
|
|
41
|
+
return {...current};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function publish(status, error = null) {
|
|
45
|
+
if (disposed) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
current = {
|
|
49
|
+
status,
|
|
50
|
+
workerUrl: current.workerUrl,
|
|
51
|
+
scope: registration?.scope ?? current.scope,
|
|
52
|
+
controller: container?.controller?.state ?? null,
|
|
53
|
+
installing: registration?.installing?.state ?? null,
|
|
54
|
+
waiting: registration?.waiting?.state ?? null,
|
|
55
|
+
active: registration?.active?.state ?? null,
|
|
56
|
+
error
|
|
57
|
+
};
|
|
58
|
+
source.dispatch(
|
|
59
|
+
PWA_STATE_EVENT,
|
|
60
|
+
snapshot()
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function observe(target, eventName, listener) {
|
|
65
|
+
target.addEventListener(eventName, listener);
|
|
66
|
+
listeners.push(
|
|
67
|
+
function removeNativeListener() {
|
|
68
|
+
target.removeEventListener(eventName, listener);
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function currentStatus() {
|
|
74
|
+
if (registration.waiting) {
|
|
75
|
+
return 'waiting';
|
|
76
|
+
}
|
|
77
|
+
if (registration.installing) {
|
|
78
|
+
return 'installing';
|
|
79
|
+
}
|
|
80
|
+
return registration.active ? 'active' : 'registered';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onWorkerStateChange(event) {
|
|
84
|
+
const previousState = workers.get(event.target);
|
|
85
|
+
workers.set(event.target, event.target.state);
|
|
86
|
+
// A newer installation normally supersedes an already waiting worker.
|
|
87
|
+
if (event.target.state === 'redundant'
|
|
88
|
+
&& previousState !== 'activated' && previousState !== 'installed') {
|
|
89
|
+
publish('error', current.error ?? new Error('The PWA worker became redundant before activation.'));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
refreshRegistration();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function watchWorker(worker) {
|
|
96
|
+
if (!worker || workers.has(worker)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
workers.set(worker, worker.state);
|
|
100
|
+
observe(worker, 'statechange', onWorkerStateChange);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function refreshRegistration() {
|
|
104
|
+
if (disposed) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
watchWorker(registration.installing);
|
|
108
|
+
watchWorker(registration.waiting);
|
|
109
|
+
watchWorker(registration.active);
|
|
110
|
+
publish(currentStatus());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function onControllerChange() {
|
|
114
|
+
refreshRegistration();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function onWorkerMessage(event) {
|
|
118
|
+
if (event.data?.type === 'arcane.pwa.error' && workers.has(event.source)) {
|
|
119
|
+
publish('error', event.data.error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function onRegistered(value) {
|
|
124
|
+
registration = value;
|
|
125
|
+
if (!disposed) {
|
|
126
|
+
observe(registration, 'updatefound', refreshRegistration);
|
|
127
|
+
observe(container, 'controllerchange', onControllerChange);
|
|
128
|
+
observe(container, 'message', onWorkerMessage);
|
|
129
|
+
refreshRegistration();
|
|
130
|
+
}
|
|
131
|
+
return registration;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function onRegistrationError(error) {
|
|
135
|
+
publish('error', error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function startRegistration() {
|
|
140
|
+
if (!container) {
|
|
141
|
+
publish('unsupported');
|
|
142
|
+
return Promise.resolve(null);
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const url = new URL(workerUrl, globalThis.document?.baseURI ?? globalThis.location?.href);
|
|
146
|
+
const registrationScope = new URL(scope ?? './', url);
|
|
147
|
+
current.workerUrl = url.href;
|
|
148
|
+
current.scope = registrationScope.href;
|
|
149
|
+
const registered = container.register(
|
|
150
|
+
url.href,
|
|
151
|
+
{
|
|
152
|
+
scope: registrationScope.href,
|
|
153
|
+
updateViaCache: 'none'
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
return Promise.resolve(registered).then(onRegistered).catch(onRegistrationError);
|
|
157
|
+
} catch (error) {
|
|
158
|
+
publish('error', error);
|
|
159
|
+
return Promise.reject(error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function subscribe(listener, {emitCurrent = true, signal} = {}) {
|
|
164
|
+
function forwardPwaState(event) {
|
|
165
|
+
listener(event.detail);
|
|
166
|
+
}
|
|
167
|
+
const unsubscribe = source.on(PWA_STATE_EVENT, forwardPwaState, signal ? {signal} : undefined);
|
|
168
|
+
try {
|
|
169
|
+
if (emitCurrent && !signal?.aborted) {
|
|
170
|
+
listener(snapshot());
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
unsubscribe();
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
return unsubscribe;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function update() {
|
|
180
|
+
const value = await ready;
|
|
181
|
+
if (disposed) {
|
|
182
|
+
throw new Error('The PWA registration owner has been disposed.');
|
|
183
|
+
}
|
|
184
|
+
if (!value) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
return await value.update();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
publish('error', error);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function dispose() {
|
|
196
|
+
if (disposed) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
current = {...current, status: 'disposed'};
|
|
200
|
+
disposed = true;
|
|
201
|
+
source.dispatch(
|
|
202
|
+
PWA_STATE_EVENT,
|
|
203
|
+
snapshot()
|
|
204
|
+
);
|
|
205
|
+
for (const removeListener of listeners) {
|
|
206
|
+
removeListener();
|
|
207
|
+
}
|
|
208
|
+
listeners.length = 0;
|
|
209
|
+
workers.clear();
|
|
210
|
+
source.dispose();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const ready = startRegistration();
|
|
214
|
+
return owner;
|
|
215
|
+
}
|
package/docs/architecture.md
CHANGED
|
@@ -289,7 +289,7 @@ paths are withheld from the native provider. The provider copies the complete
|
|
|
289
289
|
selected release rather than accepting an unrelated source path. Verification
|
|
290
290
|
is a separate explicit operation for a selected release artifact.
|
|
291
291
|
|
|
292
|
-
The SDK `0.
|
|
292
|
+
The SDK `0.9.0` runtime requires Arcane `0.8.12` or newer. Compatibility
|
|
293
293
|
is contractual rather than exact-version pinning: the prepared Core must meet
|
|
294
294
|
the highest minimum declared by the runtime, selected app, and bundled app
|
|
295
295
|
dependencies; keep each app's Arcane protocol generation; and provide every
|
package/docs/reference/README.md
CHANGED
|
@@ -36,6 +36,7 @@ alone does not make bare module names resolve in a browser.
|
|
|
36
36
|
| Publish central events, capture complete time-travel history, or observe the DOM | [EventManager and event-stack reference](event-manager.md) |
|
|
37
37
|
| Inspect complete AI and speech calls using the shared developer-mode preference | [Shared logger](sdk-api.md#arcanelogging) and [speech developer diagnostics](ai/browser-speech.md#developer-diagnostics) |
|
|
38
38
|
| Use the `arcane` command | [CLI reference](cli.md) |
|
|
39
|
+
| Install an app and cache selected resources offline | [Progressive web applications](pwa.md) |
|
|
39
40
|
| Generate named browser imports or inspect the selected physical runtime | [`arcane import-map`](cli.md#arcane-import-map) and [browser runtime delivery](protocols.md#browser-runtime-delivery) |
|
|
40
41
|
| Choose browser, native, cloud, or cross-host behavior | [Availability and normalization](availability-and-normalization.md) |
|
|
41
42
|
| Import a shipped renderer module | [Runtime module catalog](runtime-modules.md) |
|
|
@@ -124,12 +125,13 @@ Public reference entries follow the established Arcane documentation model:
|
|
|
124
125
|
|
|
125
126
|
## Public runtime inventory
|
|
126
127
|
|
|
127
|
-
The package exposes
|
|
128
|
+
The package exposes 205 semantic JavaScript records across 20 JavaScript
|
|
128
129
|
entrypoints, plus eight JSON Schemas and package metadata. Ten entrypoints are
|
|
129
130
|
Node.js control-plane surfaces,
|
|
130
131
|
`arcane-os/event-manager`, `arcane-os/logging`, `arcane-os/mail`, `arcane-os/preference-store`, and
|
|
131
|
-
`arcane-os/speech-playback
|
|
132
|
-
|
|
132
|
+
`arcane-os/speech-playback`, `arcane-os/speech-text`, and `arcane-os/browser-device`
|
|
133
|
+
run in Node and browsers, and
|
|
134
|
+
`arcane-os/pwa`, `arcane-os/ai/browser-wasm` plus `arcane-os/ai/browser-speech` are browser-only.
|
|
133
135
|
The [machine-readable package
|
|
134
136
|
inventory](inventory/package-api.json) and [SDK member reference](sdk-api.md)
|
|
135
137
|
are checked bidirectionally against every declared JavaScript export.
|
|
@@ -61,6 +61,25 @@ model source. Each file needs only a name and HTTPS URL. A positive optional
|
|
|
61
61
|
`bytes` value supplies observational progress and transport-planning metadata;
|
|
62
62
|
it never validates, admits, identifies, or decides cache reuse for content.
|
|
63
63
|
|
|
64
|
+
## Mobile and desktop settings
|
|
65
|
+
|
|
66
|
+
`getBrowserDeviceClass(navigatorObject = globalThis.navigator)`, exported from
|
|
67
|
+
`arcane-os/browser-device`, returns `mobile` or `desktop` for choosing
|
|
68
|
+
application-owned settings. Mobile includes a positive mobile client hint,
|
|
69
|
+
Android or iOS platform information, mobile user-agent identifiers, and iPadOS
|
|
70
|
+
reporting a Mac platform with multiple touch points. Missing or unrecognized
|
|
71
|
+
identity defaults to `desktop`; a browser hiding all mobile hints may therefore
|
|
72
|
+
receive that profile. This classification is a settings hint, not evidence of
|
|
73
|
+
GPU capability, available memory, or model readiness.
|
|
74
|
+
|
|
75
|
+
Keep complete mobile and desktop load profiles at the application owner and
|
|
76
|
+
pass the selected profile as `createBrowserWasmLlmProvider({loadDefaults})`.
|
|
77
|
+
Choose it once before provider creation so catalog inspection and model loading
|
|
78
|
+
use the same settings. The helper reads identity synchronously and starts no
|
|
79
|
+
GPU request, model operation, listener, or viewport-dependent reload. Its
|
|
80
|
+
dependency-free entrypoint does not import the inference runtime. Profile
|
|
81
|
+
selection does not alter prompts, documents, history, or output limits.
|
|
82
|
+
|
|
64
83
|
## Lifecycle at a glance
|
|
65
84
|
|
|
66
85
|
`createArcaneAI()` owns one LLM controller. Its default `loadPolicy` is
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Release-derived browser asset URLs
|
|
2
2
|
|
|
3
|
+
Applications that enable [PWA delivery](pwa.md) use clean local resource URLs.
|
|
4
|
+
Their generated offline manifest and service worker own the selected application
|
|
5
|
+
and SDK release information. In that mode, the delivery transformer removes
|
|
6
|
+
both `v` and `arcaneVersion`, preserving other query fields and fragments.
|
|
7
|
+
The behavior below continues to apply when PWA delivery is disabled and to native
|
|
8
|
+
packages. Workspace runtime materialization remains usable by either target;
|
|
9
|
+
the selected browser delivery applies its PWA URL policy.
|
|
10
|
+
|
|
3
11
|
The SDK's public import-map generator, runtime materializer, source server and
|
|
4
12
|
application packager use the selected SDK package version for local browser
|
|
5
13
|
resource references. The query field is `arcaneVersion`. Its value comes from
|
|
@@ -49,6 +57,11 @@ HTML and managed import-map JSON. This allows storage but requests revalidation,
|
|
|
49
57
|
or refresh obtains the current entry document and release URLs. Other assets
|
|
50
58
|
retain ordinary caching; no cache or user storage is cleared.
|
|
51
59
|
|
|
60
|
+
For PWA delivery the SDK server revalidates all served resources, including
|
|
61
|
+
the stable worker script. A static host serving a PWA package must likewise
|
|
62
|
+
revalidate stable resource URLs. The service worker maintains its own selected
|
|
63
|
+
offline resource cache independently of the HTTP cache.
|
|
64
|
+
|
|
52
65
|
An independently configured static host must likewise revalidate entry HTML
|
|
53
66
|
and managed import-map JSON. The generated package supplies versioned resource
|
|
54
67
|
references; it cannot configure another server's HTTP headers.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"minimumVersion": "22.23.2 for Node entrypoints",
|
|
7
7
|
"moduleSystem": "ESM"
|
|
8
8
|
},
|
|
9
|
-
"memberCount":
|
|
9
|
+
"memberCount": 205,
|
|
10
10
|
"members": [
|
|
11
11
|
{
|
|
12
12
|
"id": "root:APP_BUNDLE_DESCRIPTOR_NAME",
|
|
@@ -2952,6 +2952,50 @@
|
|
|
2952
2952
|
"protocol": "Resumable ordered model members and deterministic per-member Range parts over existing DBOPFS method semantics",
|
|
2953
2953
|
"normalization": "Uses one bounded transfer axis, preserves ordered completed shards and deterministic current Range parts within any member across interruption, emits coalesced additive loaded/total/remaining/rate/ETA/active-worker telemetry, rolls discarded partial writes back out of progress, cleans superseded current Range parts after a complete replacement exists, and settles failed or cancelled peers while retaining completed work for retry"
|
|
2954
2954
|
},
|
|
2955
|
+
{
|
|
2956
|
+
"id": "browser-device:getBrowserDeviceClass",
|
|
2957
|
+
"name": "getBrowserDeviceClass",
|
|
2958
|
+
"displayName": "getBrowserDeviceClass()",
|
|
2959
|
+
"kind": "function",
|
|
2960
|
+
"signature": "getBrowserDeviceClass(navigatorObject = globalThis.navigator)",
|
|
2961
|
+
"entrypoints": [
|
|
2962
|
+
"arcane-os/browser-device"
|
|
2963
|
+
],
|
|
2964
|
+
"primaryImport": "arcane-os/browser-device",
|
|
2965
|
+
"group": "Browser device settings",
|
|
2966
|
+
"summary": "Returns a mobile or desktop browser identity hint for application-owned settings.",
|
|
2967
|
+
"availability": "Node and Browser; no model, storage, or GPU capability required",
|
|
2968
|
+
"protocol": "Synchronous optional navigator-like identity input and mobile or desktop string result",
|
|
2969
|
+
"normalization": "Positive mobile hints, Android or iOS identity, and iPadOS Mac-platform touch identity select mobile; missing or unrecognized identity selects desktop. Importing the dependency-free entrypoint loads no AI runtime, and classification performs no model, GPU, storage, network, or listener operation."
|
|
2970
|
+
},
|
|
2971
|
+
{
|
|
2972
|
+
"id": "pwa:PWA_STATE_EVENT",
|
|
2973
|
+
"name": "PWA_STATE_EVENT",
|
|
2974
|
+
"displayName": "PWA_STATE_EVENT",
|
|
2975
|
+
"kind": "constant",
|
|
2976
|
+
"signature": "const PWA_STATE_EVENT",
|
|
2977
|
+
"entrypoints": ["arcane-os/pwa"],
|
|
2978
|
+
"primaryImport": "arcane-os/pwa",
|
|
2979
|
+
"group": "Progressive web applications",
|
|
2980
|
+
"summary": "Names the shared PWA lifecycle state event.",
|
|
2981
|
+
"availability": "Browser lifecycle; importable without registration",
|
|
2982
|
+
"protocol": "Existing Arcane event owner and native service worker lifecycle",
|
|
2983
|
+
"normalization": "The exact event name is arcane.pwa.state."
|
|
2984
|
+
},
|
|
2985
|
+
{
|
|
2986
|
+
"id": "pwa:registerPwa",
|
|
2987
|
+
"name": "registerPwa",
|
|
2988
|
+
"displayName": "registerPwa()",
|
|
2989
|
+
"kind": "function",
|
|
2990
|
+
"signature": "registerPwa({workerUrl = './arcane-sw.js', scope} = {})",
|
|
2991
|
+
"entrypoints": ["arcane-os/pwa"],
|
|
2992
|
+
"primaryImport": "arcane-os/pwa",
|
|
2993
|
+
"group": "Progressive web applications",
|
|
2994
|
+
"summary": "Starts PWA registration without blocking page rendering and returns an observable lifecycle owner.",
|
|
2995
|
+
"availability": "Browser service workers; explicit unsupported state elsewhere",
|
|
2996
|
+
"protocol": "Native registration, updatefound, statechange and controllerchange events",
|
|
2997
|
+
"normalization": "Synchronous owner exposes ready, state, subscribe, update and dispose; current state replays by default, failures remain observable, disposal does not unregister the worker or delete saved data."
|
|
2998
|
+
},
|
|
2955
2999
|
{
|
|
2956
3000
|
"id": "browser-speech:BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL",
|
|
2957
3001
|
"name": "BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL",
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Progressive web applications
|
|
2
|
+
|
|
3
|
+
An application supplies its installation identity and offline resource selection.
|
|
4
|
+
The SDK generates the Web App Manifest, offline inventory, service worker and
|
|
5
|
+
nonblocking registration module. Native packages keep their existing lifecycle.
|
|
6
|
+
|
|
7
|
+
## Application configuration
|
|
8
|
+
|
|
9
|
+
Set `package.pwa` in the application's `arcane-app.json`. Its ordinary package
|
|
10
|
+
projection carries the same `pwa` record in `arcane-package.json`:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"enabled": true,
|
|
15
|
+
"manifest": {
|
|
16
|
+
"name": "Example Library",
|
|
17
|
+
"short_name": "Library",
|
|
18
|
+
"display": "standalone",
|
|
19
|
+
"icons": [
|
|
20
|
+
{
|
|
21
|
+
"src": "img/library.png",
|
|
22
|
+
"sizes": "512x512",
|
|
23
|
+
"type": "image/png"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"offline": {
|
|
28
|
+
"exclude": ["documents"]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`enabled` defaults to `false`. `manifest` contains ordinary Web App Manifest
|
|
34
|
+
metadata. Its default name comes from `displayName`; its default start page
|
|
35
|
+
comes from the selected application entry. The app owns names, icons, colors,
|
|
36
|
+
display preference, routes and descriptions. Use real app icons; the SDK does
|
|
37
|
+
not invent branding or claim that a browser has installed the app.
|
|
38
|
+
|
|
39
|
+
Manifest URL fields are relative to the application directory. Source delivery
|
|
40
|
+
and packaged delivery resolve those fields into their respective layouts.
|
|
41
|
+
Absolute URL fields retain their authored destination.
|
|
42
|
+
|
|
43
|
+
`offline.include` and `offline.exclude` select literal paths or directory
|
|
44
|
+
prefixes from the selected emitted inventory. An omitted or empty include list
|
|
45
|
+
selects that inventory; exclusions subtract from it. App files use app-relative
|
|
46
|
+
paths and shared runtime files use paths such as `arcane/sdk/pwa.mjs`.
|
|
47
|
+
The application entry and generated PWA shell records are retained. Select the
|
|
48
|
+
resources needed by every offline page, including its shared modules and styles.
|
|
49
|
+
The worker script itself is never an application cache entry.
|
|
50
|
+
|
|
51
|
+
These settings describe published application resources. They do not select
|
|
52
|
+
user uploads, provider responses, live API requests, saved conversations,
|
|
53
|
+
preferences, IndexedDB, OPFS, or a model owner's download/cache lifecycle.
|
|
54
|
+
Offline page availability does not imply that cloud inference or other network
|
|
55
|
+
services work offline. Applications own the corresponding visible behavior.
|
|
56
|
+
|
|
57
|
+
## Generated output
|
|
58
|
+
|
|
59
|
+
Browser packaging emits these files at the selected deployment root:
|
|
60
|
+
|
|
61
|
+
| File | Purpose |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `arcane.webmanifest` | Browser installation metadata. |
|
|
64
|
+
| `arcane-offline.json` | App ID/version, SDK version, deployment revision, resource URLs and explicit navigation aliases. |
|
|
65
|
+
| `arcane-sw.js` | Stable worker URL with the selected offline manifest embedded in its source. |
|
|
66
|
+
| `arcane-pwa.mjs` | Independent registration module importing the SDK client. |
|
|
67
|
+
|
|
68
|
+
Each packaged output gets one deployment revision shared by its offline
|
|
69
|
+
manifest and worker. It distinguishes separately generated outputs even when
|
|
70
|
+
their app and SDK versions match. It is not a content measurement.
|
|
71
|
+
|
|
72
|
+
The package owns its selected inventory once, after any app adapter finishes.
|
|
73
|
+
It follows actual resource references to include meaningful query variants.
|
|
74
|
+
Generated application pages receive a manifest link and an `async` module
|
|
75
|
+
marked `data-arcane-pwa`. Existing application scripts retain their order.
|
|
76
|
+
PWA registration does not wait for models, storage, preferences or page rendering.
|
|
77
|
+
|
|
78
|
+
The selected PWA browser delivery removes `v` and `arcaneVersion` from actual
|
|
79
|
+
local resource references, including the managed import map. Other query fields,
|
|
80
|
+
fragments, source spelling and unrelated payloads are preserved. The offline
|
|
81
|
+
manifest now carries release information. Non-PWA and native delivery retain
|
|
82
|
+
the [existing asset version contract](asset-versioning.md).
|
|
83
|
+
|
|
84
|
+
## Development and hosting
|
|
85
|
+
|
|
86
|
+
For an enabled application, `arcane dev` serves the generated PWA files at the
|
|
87
|
+
origin root and starts at the selected app page under `/apps/<id>/`. Use one
|
|
88
|
+
selected app per development origin. Its worker uses network revalidation first
|
|
89
|
+
and retains successful selected resource responses for offline use, so saved
|
|
90
|
+
source edits remain visible on an ordinary online refresh.
|
|
91
|
+
|
|
92
|
+
Source inventory work begins when the browser requests the worker update, after
|
|
93
|
+
the page can start. It traverses the selected route inventory once for that
|
|
94
|
+
request, follows application page and runtime resource references to retain
|
|
95
|
+
selected query variants, and shares an in-flight traversal with concurrent
|
|
96
|
+
manifest requests. Each referenced source file is read once per traversal;
|
|
97
|
+
document corpus bodies remain under their existing owner. It does not rebuild
|
|
98
|
+
the application. Generated metadata and bootstrap requests reuse the current
|
|
99
|
+
bundle.
|
|
100
|
+
|
|
101
|
+
Packaged workers use their selected cache generation first. During installation,
|
|
102
|
+
at most four resource requests run together; each response is fetched from the
|
|
103
|
+
network with the browser's reload cache mode before it enters the candidate
|
|
104
|
+
cache. The candidate must finish
|
|
105
|
+
installation before the browser activates it. Failures retain the prior active
|
|
106
|
+
worker and remain observable through native worker state and SDK diagnostics.
|
|
107
|
+
|
|
108
|
+
The SDK development server sends `Cache-Control: no-cache` for PWA resources.
|
|
109
|
+
An independent static host must also revalidate stable HTML, module, style,
|
|
110
|
+
import-map and worker URLs, and serve JavaScript with a JavaScript content type.
|
|
111
|
+
Keep the worker beside the deployment root it controls. The browser requires a
|
|
112
|
+
supported secure context, such as trusted HTTPS or localhost, to register it.
|
|
113
|
+
The SDK does not change certificates or browser permissions.
|
|
114
|
+
|
|
115
|
+
An initial visit can load before a worker controls the document. Revalidation
|
|
116
|
+
therefore matters even when an offline cache exists. Updating files in place
|
|
117
|
+
while installation fetches them is not an atomic release snapshot; the host owns
|
|
118
|
+
consistent deployment of the selected output.
|
|
119
|
+
|
|
120
|
+
An offline navigation alias redirects to its selected entry page, preserving the
|
|
121
|
+
document URL used to resolve relative modules and styles.
|
|
122
|
+
|
|
123
|
+
Browser storage eviction can remove an offline cache. A missing release cache
|
|
124
|
+
entry falls back to the network, so offline availability still depends on the
|
|
125
|
+
browser retaining the selected resources.
|
|
126
|
+
|
|
127
|
+
## Updates and stored data
|
|
128
|
+
|
|
129
|
+
An updated worker installs alongside the current worker, then waits for the
|
|
130
|
+
browser's normal activation boundary. Existing controlled pages retain their
|
|
131
|
+
worker while they are open. Closing those pages allows activation; a refresh
|
|
132
|
+
can leave overlapping document clients and keep the update waiting.
|
|
133
|
+
|
|
134
|
+
The SDK does not call `skipWaiting`, claim the initial page, reload a page,
|
|
135
|
+
restart a model or poll for updates. Activation retires only obsolete resource
|
|
136
|
+
caches belonging to that exact app and registration scope. Saved application
|
|
137
|
+
data and caches owned by other capabilities are untouched.
|
|
138
|
+
|
|
139
|
+
Switching a server from a packaged release to live development does not replace
|
|
140
|
+
an already active release worker inside an open document. The same native
|
|
141
|
+
worker lifecycle applies.
|
|
142
|
+
|
|
143
|
+
## registerPwa()
|
|
144
|
+
|
|
145
|
+
Import `registerPwa` and `PWA_STATE_EVENT` from `arcane-os/pwa` through the
|
|
146
|
+
managed browser import map. The generated bootstrap already calls this API;
|
|
147
|
+
manual callers use it when they own a separate registration entry point.
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
import {registerPwa} from 'arcane-os/pwa';
|
|
151
|
+
|
|
152
|
+
const pwa = registerPwa(
|
|
153
|
+
{
|
|
154
|
+
workerUrl: new URL('./arcane-sw.js', import.meta.url).href,
|
|
155
|
+
scope: new URL('./', import.meta.url).href
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
pwa.subscribe(function showPwaState(state) {
|
|
160
|
+
console.log(state);
|
|
161
|
+
});
|
|
162
|
+
pwa.ready.catch(function reportRegistrationFailure(error) {
|
|
163
|
+
console.error(error);
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The function returns synchronously with `{ready, state, subscribe, update,
|
|
168
|
+
dispose}`. `ready` settles with the native registration, or `null` when service
|
|
169
|
+
workers are unavailable. A registration failure rejects it and publishes error
|
|
170
|
+
state. Do not await it before rendering or confuse it with a controlled page.
|
|
171
|
+
|
|
172
|
+
`state` reports `status`, `workerUrl`, `scope`, `controller`, `installing`,
|
|
173
|
+
`waiting`, `active` and the complete `error` when present. Status is one of
|
|
174
|
+
`registering`, `unsupported`, `registered`, `installing`, `waiting`, `active`,
|
|
175
|
+
`error` or `disposed`. Native worker state fields are strings or `null`.
|
|
176
|
+
|
|
177
|
+
`subscribe(listener, {emitCurrent: true, signal} = {})` immediately replays the
|
|
178
|
+
current state by default and returns an unsubscribe function. Subsequent state
|
|
179
|
+
uses the existing Arcane event owner and `PWA_STATE_EVENT` (`arcane.pwa.state`).
|
|
180
|
+
`update()` requests the browser's normal update check on demand; failure is
|
|
181
|
+
observable in state and through its returned promise. `dispose()` removes
|
|
182
|
+
page-owned listeners and subscriptions without unregistering the persistent
|
|
183
|
+
worker or deleting stored data.
|