arcane-os 0.9.0 → 0.10.1

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.
@@ -4,7 +4,7 @@ The npm package exposes a Node.js ESM control plane, the portable
4
4
  `arcane-os/event-manager`, `arcane-os/logging`, `arcane-os/mail`,
5
5
  `arcane-os/preference-store`, `arcane-os/speech-playback`,
6
6
  `arcane-os/speech-text`, and `arcane-os/browser-device` entrypoints, and the browser-only
7
- `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints.
7
+ `arcane-os/pwa`, `arcane-os/ai/browser-wasm` and `arcane-os/ai/browser-speech` entrypoints.
8
8
  Those package subpaths are distinct from application-facing projection modules
9
9
  in the managed browser map, such as `arcane/AIProviderRuntime`,
10
10
  `arcane/AIRuntimeState`, and `arcane/ThemeBootstrap`. Applications use those
@@ -39,6 +39,7 @@ for the installed-inventory-derived physical-runtime contract in SDK `0.5.18`.
39
39
  | `arcane-os/speech-playback` | Portable speech preparation, playback state, and injected media adapters. |
40
40
  | `arcane-os/speech-text` | Speech-input formatting cleanup for complete text and streamed chunks. |
41
41
  | `arcane-os/browser-device` | Synchronous mobile or desktop identity hints for application-owned settings. |
42
+ | `arcane-os/pwa` | Nonblocking PWA registration, native worker updates and observable lifecycle state. |
42
43
  | `arcane-os/ai/browser-wasm` | Caller-selected browser-local Wllama inference, complete DBOPFS model storage, streaming, cancellation, and structural tool-call results. |
43
44
  | `arcane-os/ai/browser-speech` | Caller-selected browser-local Whisper STT and Kokoro TTS provider mechanisms, ordinary upstream assets, materialized/native routing, Workers, and cancellation. |
44
45
  | `arcane-os/mail` | Portable Mail runtime, durable outbox, complete transport responses, and provider-neutral acceptance contracts. |
@@ -126,6 +127,8 @@ browser map are cataloged separately in [Runtime modules](runtime-modules.md).
126
127
  | `createCanonicalUstarHeader()` | function | `arcane-os` | Packaging and release bundles | Node |
127
128
  | `createDbopfsModelStore()` | function | `arcane-os/ai/browser-wasm` | Browser-WASM local AI | Browser with a ready DBOPFS instance and OPFS |
128
129
  | `getBrowserDeviceClass()` | function | `arcane-os/browser-device` | Browser device settings | Node and Browser; synchronous identity hint with no model, storage, or GPU operation |
130
+ | `PWA_STATE_EVENT` | constant | `arcane-os/pwa` | Progressive web applications | Browser lifecycle event name; importable without registration |
131
+ | `registerPwa()` | function | `arcane-os/pwa` | Progressive web applications | Browser service workers; synchronous unsupported state when unavailable |
129
132
  | `createDbopfsSpeechArtifactStore()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser with ready DBOPFS, Web Locks, Fetch, File/Blob, and object URLs |
130
133
  | `removeBrowserSpeechModelCache()` | function | `arcane-os/ai/browser-speech` | Browser speech providers | Browser CacheStorage; explicit removal of one selected upstream model |
131
134
  | `createNativeBuildPlan()` | function | `arcane-os` | Targets, native plans, and providers | Node; selected browser/native target or provider as documented |
@@ -6046,6 +6049,47 @@ const deviceClass = getBrowserDeviceClass();
6046
6049
  console.log(deviceClass); // mobile or desktop
6047
6050
  ```
6048
6051
 
6052
+ ## PWA_STATE_EVENT
6053
+
6054
+ ### Overview
6055
+
6056
+ The event name `arcane.pwa.state` identifies PWA registration and native worker
6057
+ lifecycle state published through the existing Arcane event owner.
6058
+
6059
+ ### Example
6060
+
6061
+ ```javascript
6062
+ import {PWA_STATE_EVENT} from 'arcane-os/pwa';
6063
+ console.log(PWA_STATE_EVENT);
6064
+ ```
6065
+
6066
+ See the [PWA state contract](pwa.md#registerpwa) for complete payload fields and
6067
+ current-state subscriptions. Importing the constant does not register a worker.
6068
+
6069
+ ## registerPwa()
6070
+
6071
+ ### Overview
6072
+
6073
+ `registerPwa({workerUrl = './arcane-sw.js', scope} = {})` starts native browser
6074
+ registration and returns a synchronous owner with `ready`, `state`, `subscribe`,
6075
+ `update` and `dispose`. It does not block page rendering or load models.
6076
+ Unsupported environments receive an explicit unsupported state.
6077
+
6078
+ ### Example
6079
+
6080
+ ```javascript
6081
+ import {registerPwa} from 'arcane-os/pwa';
6082
+ const pwa = registerPwa();
6083
+ pwa.ready.catch(function reportRegistrationError(error) {
6084
+ console.error(error);
6085
+ });
6086
+ ```
6087
+
6088
+ The [PWA guide](pwa.md) defines inputs, native registration results, error state,
6089
+ current-state replay, disposal, offline resource ownership and update behavior.
6090
+ The generated bootstrap registers automatically; a manual caller owns a separate
6091
+ entry point and must observe its `ready` rejection.
6092
+
6049
6093
  ## createBrowserWasmLlmProvider()
6050
6094
 
6051
6095
  ### Overview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -15,6 +15,7 @@
15
15
  "./event-manager": "./src/event-manager.mjs",
16
16
  "./logging": "./browser-runtime/logging.mjs",
17
17
  "./browser-device": "./browser-runtime/browser-device.mjs",
18
+ "./pwa": "./browser-runtime/pwa.mjs",
18
19
  "./preference-store": "./runtime/arcane/modules/PreferenceStore.js",
19
20
  "./speech-playback": "./runtime/arcane/modules/SpeechPlayback.js",
20
21
  "./speech-text": "./browser-runtime/speech-text.mjs",
@@ -66,7 +67,7 @@
66
67
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
67
68
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
68
69
  "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
69
- "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/packaging.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
70
+ "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
70
71
  "test:integration": "node ./bin/arcane-test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/wllama-webgpu-runtime.test.mjs",
71
72
  "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
72
73
  "check": "node tools/check-source.mjs && npm test",
@@ -477,6 +477,7 @@ class Errors {
477
477
  return;
478
478
  }
479
479
 
480
+ // Restored records resume delivery; only live capture offers developer modals.
480
481
  const timestamp=this.now();
481
482
  for(const storedRecord of records){
482
483
  if(
@@ -519,7 +520,6 @@ class Errors {
519
520
  if(!record.retryRequired){
520
521
  this.scheduleRecord(record,dueAt-timestamp);
521
522
  }
522
- this.offerDeveloperIncident(record.incident,record.occurrenceId);
523
523
  }
524
524
 
525
525
  this.persistLedger();
@@ -19,8 +19,13 @@ let htmlImportScriptId=0;
19
19
  const htmlImportModuleURL=new URL(import.meta.url);
20
20
  const htmlImportAssetVersion=htmlImportModuleURL.searchParams.get('arcaneVersion');
21
21
 
22
+ function pwaResourceUrlsEnabled(){
23
+ return Boolean(document.querySelector?.('script[data-arcane-pwa]'));
24
+ }
25
+
22
26
  function versionComponentResource(value,baseHref){
23
- if(!htmlImportAssetVersion||!is.string(value)||!value||value.startsWith('#')){
27
+ const clean=pwaResourceUrlsEnabled();
28
+ if((!htmlImportAssetVersion&&!clean)||!is.string(value)||!value||value.startsWith('#')){
24
29
  return value;
25
30
  }
26
31
  let resolvedURL;
@@ -46,7 +51,7 @@ function versionComponentResource(value,baseHref){
46
51
  const parameter=new URLSearchParams(field);
47
52
  if(parameter.has('v'))return null;
48
53
  if(!parameter.has('arcaneVersion'))return field;
49
- if(replaced)return null;
54
+ if(clean||replaced)return null;
50
55
  replaced=true;
51
56
  const equals=field.indexOf('=');
52
57
  const key=equals<0?field:field.slice(0,equals);
@@ -54,6 +59,11 @@ function versionComponentResource(value,baseHref){
54
59
  }).filter(function retainQueryField(field){
55
60
  return field!==null;
56
61
  }):[];
62
+ if(clean){
63
+ return fields.some(function hasRemainingField(field){return field!=='';})
64
+ ?`${pathname}?${fields.join('&')}${fragment}`
65
+ :`${pathname}${fragment}`;
66
+ }
57
67
  if(!replaced){
58
68
  if(fields.at(-1)==='')fields[fields.length-1]=versionField;
59
69
  else fields.push(versionField);
@@ -81,7 +91,7 @@ function resolveComponentResource(value,runtimeRoot){
81
91
  }
82
92
 
83
93
  function resolveComponentStyleResources(styleText,runtimeRoot){
84
- if(!runtimeRoot&&!htmlImportAssetVersion)return styleText;
94
+ if(!runtimeRoot&&!htmlImportAssetVersion&&!pwaResourceUrlsEnabled())return styleText;
85
95
  const parts=[];
86
96
  let copiedThrough=0;
87
97
  let index=0;
@@ -40,6 +40,7 @@
40
40
  "entry": { "$ref": "#/$defs/relativePath" },
41
41
  "strategy": { "enum": ["static", "adapter"] },
42
42
  "localAIModelPolicy": { "$ref": "#/$defs/localAIModelPolicy" },
43
+ "pwa": { "$ref": "#/$defs/pwa" },
43
44
  "include": { "$ref": "#/$defs/nonemptyPathList" },
44
45
  "exclude": { "$ref": "#/$defs/pathList" },
45
46
  "shared": {
@@ -224,6 +225,33 @@
224
225
  }
225
226
  ],
226
227
  "$defs": {
228
+ "pwa": {
229
+ "type": "object",
230
+ "additionalProperties": false,
231
+ "properties": {
232
+ "enabled": { "type": "boolean", "default": false },
233
+ "manifest": {
234
+ "type": "object",
235
+ "description": "Application-owned Web App Manifest members, including branding, icons, and presentation."
236
+ },
237
+ "offline": {
238
+ "type": "object",
239
+ "additionalProperties": false,
240
+ "properties": {
241
+ "include": {
242
+ "type": "array",
243
+ "description": "Literal package paths or directories to cache. Omitted or empty selects all emitted files.",
244
+ "items": { "type": "string", "minLength": 1 }
245
+ },
246
+ "exclude": {
247
+ "type": "array",
248
+ "description": "Literal package paths or directories excluded from offline assets. Generated shell records and the entry remain available.",
249
+ "items": { "type": "string", "minLength": 1 }
250
+ }
251
+ }
252
+ }
253
+ }
254
+ },
227
255
  "appId": { "type": "string", "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$" },
228
256
  "presentationText": {
229
257
  "type": "string",
@@ -43,6 +43,9 @@
43
43
  "localAIModelPolicy": {
44
44
  "$ref": "#/$defs/localAIModelPolicy"
45
45
  },
46
+ "pwa": {
47
+ "$ref": "#/$defs/pwa"
48
+ },
46
49
  "security": {
47
50
  "type": "object",
48
51
  "additionalProperties": false,
@@ -117,6 +120,33 @@
117
120
  }
118
121
  ],
119
122
  "$defs": {
123
+ "pwa": {
124
+ "type": "object",
125
+ "additionalProperties": false,
126
+ "properties": {
127
+ "enabled": { "type": "boolean", "default": false },
128
+ "manifest": {
129
+ "type": "object",
130
+ "description": "Application-owned Web App Manifest members, including branding, icons, and presentation."
131
+ },
132
+ "offline": {
133
+ "type": "object",
134
+ "additionalProperties": false,
135
+ "properties": {
136
+ "include": {
137
+ "type": "array",
138
+ "description": "Literal package paths or directories to cache. Omitted or empty selects all emitted files.",
139
+ "items": { "type": "string", "minLength": 1 }
140
+ },
141
+ "exclude": {
142
+ "type": "array",
143
+ "description": "Literal package paths or directories excluded from offline assets. Generated shell records and the entry remain available.",
144
+ "items": { "type": "string", "minLength": 1 }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ },
120
150
  "appId": {
121
151
  "type": "string",
122
152
  "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$"
@@ -2,6 +2,7 @@ import Is from 'strong-type';
2
2
  import {isDeepStrictEqual} from 'node:util';
3
3
  import {readFile} from 'node:fs/promises';
4
4
  import path from 'node:path';
5
+ import {normalizePwaConfig} from './pwa.mjs';
5
6
  import {
6
7
  ARCANE_MACHINE_BUNDLE_VERSION,
7
8
  ARCANE_PROTOCOL,
@@ -107,7 +108,7 @@ function validateLocalAIModelPolicy(value,label){
107
108
 
108
109
  function validatePackage(value,appId){
109
110
  assertOnlyKeys(value,new Set([
110
- 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy'
111
+ 'entry','strategy','include','exclude','shared','adapter','localAIModelPolicy','pwa'
111
112
  ]),'descriptor.package');
112
113
  const entry=normalizeRelativePath(value.entry,'descriptor.package.entry');
113
114
  if(!['static','adapter'].includes(value.strategy))fail('descriptor.package.strategy must be static or adapter.');
@@ -135,6 +136,7 @@ function validatePackage(value,appId){
135
136
  include,
136
137
  exclude,
137
138
  shared,
139
+ ...(value.pwa===undefined?{}:{pwa:normalizePwaConfig(value.pwa)}),
138
140
  ...(adapter?{adapter}:{}),
139
141
  ...(value.localAIModelPolicy===undefined?{}:{
140
142
  localAIModelPolicy:validateLocalAIModelPolicy(
@@ -320,6 +322,7 @@ export function projectPackageManifest(descriptor){
320
322
  mediaOrigins:[...value.security.mediaOrigins]
321
323
  }}:{}),
322
324
  ...(value.package.localAIModelPolicy?{localAIModelPolicy:value.package.localAIModelPolicy}:{}),
325
+ ...(value.package.pwa===undefined?{}:{pwa:value.package.pwa}),
323
326
  include:[...value.package.include],
324
327
  exclude:[...value.package.exclude],
325
328
  shared:[...value.package.shared],
@@ -378,6 +381,7 @@ function synthesizedDescriptor(packageManifest,nativeDescriptor){
378
381
  entry:packageManifest.entry,
379
382
  strategy:packageManifest.strategy,
380
383
  ...(packageManifest.localAIModelPolicy?{localAIModelPolicy:packageManifest.localAIModelPolicy}:{}),
384
+ ...(packageManifest.pwa===undefined?{}:{pwa:packageManifest.pwa}),
381
385
  include:[...packageManifest.include],
382
386
  exclude:[...(packageManifest.exclude??[])],
383
387
  shared:[...packageManifest.shared],
@@ -426,7 +430,11 @@ export async function loadAppDescriptor({workspaceRoot,appRoot,appId,packageMani
426
430
  if(authored){
427
431
  const descriptor=validateAppDescriptor(authored,{appId});
428
432
  const projection=projectPackageManifest(authored);
429
- if(!isDeepStrictEqual(projection,packageManifest)){
433
+ const packageProjection=packageManifest.pwa===undefined?packageManifest:{
434
+ ...packageManifest,
435
+ pwa:normalizePwaConfig(packageManifest.pwa)
436
+ };
437
+ if(!isDeepStrictEqual(projection,packageProjection)){
430
438
  fail(`${APP_DESCRIPTOR_NAME} does not project exactly to arcane-package.json.`);
431
439
  }
432
440
  return completeValue({descriptor,source:'authored',descriptorPath});