arcane-os 0.11.3 → 0.12.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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.0
4
+
5
+ - Refresh the selected authored app descriptor's package projection before
6
+ development startup, so file and PWA configuration changes take effect through
7
+ the ordinary dev command without packaging.
8
+ - Use published `node-http-server` 9.1.1 for development and packaged-preview HTTPS
9
+ serving, preserving source mounts and generated resource transformations.
10
+ Supply modification dates for conditional GET and HEAD requests.
11
+ Enforce HTTPS for every Arcane app, including localhost and packaged browser
12
+ previews, using the configured workspace certificate pair or public TLS options.
13
+ Redirect the paired HTTP listener with status 308 while preserving the request
14
+ path and query; select its port with `httpPort` or CLI `--http-port`.
15
+ - Keep complete PWA resource responses across app and SDK version changes.
16
+ Check on page load after 120 seconds in development or 15 minutes otherwise,
17
+ with one DBOPFS timestamp updated only after the whole check succeeds. Reuse
18
+ cached responses on `304`, replace them after a successful current response, and retain offline
19
+ copies on network failures. No SDK cache expiration or polling is added.
20
+
3
21
  ## 0.11.3
4
22
 
5
23
  - Require Wllama's model-context load result to report success before publishing
package/NOTICE CHANGED
@@ -8,6 +8,7 @@ informational notice and is not itself a grant of commercial rights.
8
8
  Third-party material retains its own terms:
9
9
 
10
10
  - event-pubsub 6.1.0: MIT License; see its installed `licence` file.
11
+ - node-http-server 9.1.1: MIT License; see its installed `licence` file.
11
12
  - strong-type 2.0.1: MIT License; see its installed `licence` file.
12
13
  - vanilla-test 2.1.3: MIT License; see its installed `licence` file.
13
14
  - ansi-colors-es6 5.0.0: MIT License; see its installed `LICENSE` file.
package/README.md CHANGED
@@ -19,15 +19,17 @@ 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.11.3` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.12.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
26
  Applications can enable [PWA installation and offline resources](docs/reference/pwa.md)
27
27
  through their app descriptor. The SDK generates manifests, an independent
28
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.
29
+ page-load revalidation and persistent resource caches. The SDK records one
30
+ completed-check timestamp per app/cache in DBOPFS and checks after 120 seconds
31
+ in development or 15 minutes in packaged browser delivery. Apps retain ownership
32
+ of branding, offline page selection and network-dependent product behavior.
31
33
 
32
34
  That registry query is a maintainer action, not an application behavior. Apps
33
35
  never poll npm for SDK updates or replace their own SDK or synchronized runtime.
@@ -41,9 +43,15 @@ Create one browser application, install its pinned SDK, and start its source
41
43
  server:
42
44
 
43
45
  ```bash
44
- npx arcane-os@0.11.3 new hello-speech --path ./hello-speech --target browser
46
+ npx arcane-os@0.12.0 new hello-speech --path ./hello-speech --target browser
45
47
  cd hello-speech
46
48
  npm install
49
+ ```
50
+
51
+ Configure the workspace's HTTPS certificate pair as described in
52
+ [development HTTPS setup](docs/reference/cli.md#development-https-setup), then start:
53
+
54
+ ```bash
47
55
  npm run dev
48
56
  ```
49
57
 
@@ -62,9 +70,12 @@ each device. Keep these local files ignored by Git. The same certificate pair
62
70
  works for any selected app in that workspace. The command reports missing TLS
63
71
  files instead of starting an HTTP listener.
64
72
 
65
- Plain `npm run dev` keeps its HTTP localhost default. An explicit `--host`
66
- overrides the public bind address, and `--port` selects the port. Use `--https`
67
- for HTTPS on localhost, or `--cert <file> --key <file>` for an explicit PEM pair;
73
+ Every Arcane app uses HTTPS for development and packaged browser previews.
74
+ Plain `npm run dev` binds to localhost with the same workspace certificate pair.
75
+ An explicit `--host` overrides the bind address, and `--port` selects the HTTPS port.
76
+ The paired HTTP listener returns 308 redirects and uses an OS-assigned port
77
+ unless selected with `--http-port`. The command prints its redirect URL.
78
+ Use `--cert <file> --key <file>` for an explicit PEM pair;
68
79
  relative paths resolve from the workspace. The server prints HTTPS network URLs
69
80
  for public mode. Network reachability depends on the machine's firewall and
70
81
  network. See the [development HTTPS setup](docs/reference/cli.md#development-https-setup)
@@ -392,7 +403,7 @@ uses the same controller for automatic memory extraction.
392
403
  Create a new repository-shaped Arcane application with the exact stable SDK:
393
404
 
394
405
  ```bash
395
- npx arcane-os@0.11.3 new my-app --path ./my-app --target portable --git
406
+ npx arcane-os@0.12.0 new my-app --path ./my-app --target portable --git
396
407
  cd my-app
397
408
  npm install
398
409
  npm run dev
@@ -402,7 +413,7 @@ To enroll an existing repository, install the exact SDK and initialize only
402
413
  missing Arcane files:
403
414
 
404
415
  ```bash
405
- npm install --save-dev --save-exact arcane-os@0.11.3
416
+ npm install --save-dev --save-exact arcane-os@0.12.0
406
417
  npm exec -- arcane init my-app --target portable
407
418
  ```
408
419
 
@@ -418,7 +429,7 @@ npm exec -- arcane-os targets
418
429
  No global SDK install or standalone Arcane CLI is required. The application
419
430
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
420
431
 
421
- Use `npx arcane-os@0.11.3` for the initial bootstrap because it names this npm
432
+ Use `npx arcane-os@0.12.0` for the initial bootstrap because it names this npm
422
433
  package explicitly; bare `npx arcane` outside an installed project could resolve
423
434
  a different package. Both installed commands invoke the same headless toolchain.
424
435
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -438,7 +449,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
438
449
 
439
450
  # From the generated app repository
440
451
  cd ../local-app
441
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.11.3.tgz
452
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.12.0.tgz
442
453
  npm ci
443
454
  ```
444
455
 
@@ -447,7 +458,7 @@ same location. The lockfile retains the selected package dependency while
447
458
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
448
459
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
449
460
  runner also needs that tarball at the locked path. After publication, replace
450
- the local declaration with the exact `arcane-os@0.11.3` registry package and
461
+ the local declaration with the exact `arcane-os@0.12.0` registry package and
451
462
  commit the regenerated lock.
452
463
 
453
464
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -586,7 +597,7 @@ package installation, or assertions.
586
597
 
587
598
  ## Current target support
588
599
 
589
- Version `0.11.3` exposes one browser target and five explicitly paired
600
+ Version `0.12.0` exposes one browser target and five explicitly paired
590
601
  native development targets: a non-runnable portable directory, a
591
602
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
592
603
  unsigned-local-test DEBs, and an Android development-signed APK. The
@@ -24,6 +24,11 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
24
24
  const container = globalThis.navigator?.serviceWorker;
25
25
  const listeners = [];
26
26
  const workers = new Map();
27
+ const queriedWorkers = new WeakSet();
28
+ const refreshedWorkers = new WeakSet();
29
+ const pendingRefreshes = new Set();
30
+ const refreshTasks = new Set();
31
+ let storageTask = null;
27
32
  let registration = null;
28
33
  let disposed = false;
29
34
  let current = {
@@ -83,6 +88,13 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
83
88
  function onWorkerStateChange(event) {
84
89
  const previousState = workers.get(event.target);
85
90
  workers.set(event.target, event.target.state);
91
+ if (event.target.state === 'redundant') {
92
+ for (const operation of pendingRefreshes) {
93
+ if (operation.worker === event.target) {
94
+ operation.cancel();
95
+ }
96
+ }
97
+ }
86
98
  // A newer installation normally supersedes an already waiting worker.
87
99
  if (event.target.state === 'redundant'
88
100
  && previousState !== 'activated' && previousState !== 'installed') {
@@ -107,7 +119,118 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
107
119
  watchWorker(registration.installing);
108
120
  watchWorker(registration.waiting);
109
121
  watchWorker(registration.active);
122
+ watchWorker(container.controller);
110
123
  publish(currentStatus());
124
+ const worker = registration.active ?? container.controller;
125
+ if (worker?.state === 'activated' && !queriedWorkers.has(worker)) {
126
+ queriedWorkers.add(worker);
127
+ try {
128
+ worker.postMessage({type: 'arcane.pwa.capabilities'});
129
+ } catch (error) {
130
+ publish('error', error);
131
+ }
132
+ }
133
+ }
134
+
135
+ function startResourceRefresh(worker, cacheName) {
136
+ if (disposed || worker.state !== 'activated' || refreshedWorkers.has(worker)) {
137
+ return;
138
+ }
139
+ refreshedWorkers.add(worker);
140
+ const task = refreshCachedResources(worker, cacheName);
141
+ refreshTasks.add(task);
142
+ task.then(
143
+ function resourceRefreshComplete() {
144
+ refreshTasks.delete(task);
145
+ },
146
+ function resourceRefreshFailed(error) {
147
+ refreshTasks.delete(task);
148
+ if (error?.code !== 'ARCANE_PWA_REFRESH_CANCELLED') {
149
+ publish('error', error);
150
+ }
151
+ }
152
+ );
153
+ }
154
+
155
+ async function loadCheckStorage() {
156
+ if (!globalThis.dbopfs) {
157
+ await import('arcane/DBOPFS');
158
+ }
159
+ const storage = globalThis.dbopfs;
160
+ if (!storage) {
161
+ throw new Error('PWA check history could not open DBOPFS.');
162
+ }
163
+ await storage.readyPromise;
164
+ return storage;
165
+ }
166
+
167
+ function requestResourceRefresh(worker, lastChecked) {
168
+ const channel = new MessageChannel();
169
+ return new Promise(
170
+ function resourceRefreshReply(resolve, reject) {
171
+ function cleanup() {
172
+ pendingRefreshes.delete(operation);
173
+ channel.port1.onmessage = null;
174
+ channel.port1.onmessageerror = null;
175
+ channel.port1.close();
176
+ channel.port2.close();
177
+ }
178
+ function cancel() {
179
+ cleanup();
180
+ const error = new Error('The PWA resource refresh owner is no longer active.');
181
+ error.code = 'ARCANE_PWA_REFRESH_CANCELLED';
182
+ reject(error);
183
+ }
184
+ const operation = {worker, cancel};
185
+ pendingRefreshes.add(operation);
186
+ channel.port1.onmessage = function receiveResourceRefresh(event) {
187
+ if (event.data?.type === 'arcane.pwa.refreshed') {
188
+ cleanup();
189
+ resolve(event.data);
190
+ }
191
+ };
192
+ channel.port1.onmessageerror = function unreadableResourceRefresh() {
193
+ cleanup();
194
+ reject(new Error('The PWA resource refresh reply could not be read.'));
195
+ };
196
+ channel.port1.start();
197
+ try {
198
+ worker.postMessage({type: 'arcane.pwa.refresh', lastChecked}, [channel.port2]);
199
+ } catch (error) {
200
+ cleanup();
201
+ reject(error);
202
+ }
203
+ }
204
+ );
205
+ }
206
+
207
+ async function refreshCachedResources(worker, cacheName) {
208
+ storageTask ??= loadCheckStorage();
209
+ const storage = await storageTask;
210
+ const key = `${encodeURIComponent(cacheName)}.json`;
211
+ async function refreshStoredChecks() {
212
+ if (disposed || worker.state !== 'activated') {
213
+ return;
214
+ }
215
+ // A fresh read under the shared lock preserves checks made by another tab.
216
+ const record = await storage.get('pwa', key, true);
217
+ if (disposed || worker.state !== 'activated') {
218
+ return;
219
+ }
220
+ const result = await requestResourceRefresh(worker, record?.lastChecked ?? null);
221
+ if (!result.error && Number.isFinite(result.lastChecked) && result.lastChecked !== record?.lastChecked) {
222
+ await storage.set('pwa', key, {lastChecked: result.lastChecked});
223
+ }
224
+ if (result.error) {
225
+ publish('error', result.error);
226
+ }
227
+ }
228
+ const locks = globalThis.navigator?.locks;
229
+ if (locks?.request) {
230
+ await locks.request(`arcane-pwa-checks|${cacheName}`, refreshStoredChecks);
231
+ } else {
232
+ await refreshStoredChecks();
233
+ }
111
234
  }
112
235
 
113
236
  function onControllerChange() {
@@ -115,6 +238,9 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
115
238
  }
116
239
 
117
240
  function onWorkerMessage(event) {
241
+ if (event.data?.type === 'arcane.pwa.capabilities' && event.data.refresh === true && workers.has(event.source)) {
242
+ startResourceRefresh(event.source, event.data.cacheName);
243
+ }
118
244
  if (event.data?.type === 'arcane.pwa.error' && workers.has(event.source)) {
119
245
  publish('error', event.data.error);
120
246
  }
@@ -205,6 +331,9 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
205
331
  for (const removeListener of listeners) {
206
332
  removeListener();
207
333
  }
334
+ for (const operation of pendingRefreshes) {
335
+ operation.cancel();
336
+ }
208
337
  listeners.length = 0;
209
338
  workers.clear();
210
339
  source.dispose();
@@ -110,21 +110,37 @@ behavior or claiming that the capability exists.
110
110
 
111
111
  Rapid development uses `arcane dev`. The development server maps the selected
112
112
  application's canonical source tree and the live installed SDK/runtime routes.
113
- Each request reads and returns the complete current saved source, so a browser
114
- refresh shows source changes without packaging, copying
115
- files into `dist`, or restarting the server. Restarting is not a content
116
- synchronization step; when a refresh is stale, first verify the command, URL,
117
- workspace, selected app, and resolved source route.
118
-
119
- The shared dev server owns HTTP and HTTPS transport for the same selected
120
- routes. `arcane dev --public` selects HTTPS on the IPv4 wildcard address;
121
- explicit `--host` controls the bind address, while `--https` selects HTTPS
122
- without changing it. Public/HTTPS CLI startup reads one workspace-local PEM
123
- pair before binding. The certificate covers the device-facing address, and
113
+ At startup, the SDK refreshes only the selected authored app descriptor's
114
+ schema-1 package projection and managed import maps under the existing
115
+ development-refresh lock, then releases the lock before binding the listener.
116
+ The authored descriptor remains unchanged, and package-only apps retain their
117
+ existing path. An enabled PWA receives generated manifests directly from this
118
+ source server without creating `dist` output.
119
+ Changed resource requests return the complete current saved source without
120
+ packaging, copying files into `dist`, or restarting the server. Conditional
121
+ requests for unchanged resources return `304`. Enabled PWAs check on page load
122
+ when their single DBOPFS `lastChecked` value is older than 120 seconds in
123
+ development or 15 minutes in packaged browser delivery. Cached resource bodies have no SDK
124
+ expiration. The timestamp advances only after the whole resource check succeeds.
125
+ Restarting is not a content synchronization step; inspect the
126
+ selected source route and the last successful check when evaluating freshness.
127
+
128
+ The shared dev server uses RIAEvangelist's `node-http-server` public interface
129
+ for HTTPS and conditional responses on those selected routes. The SDK
130
+ owns source selection and generated representations. Every Arcane development
131
+ server and packaged browser preview serves content on HTTPS and redirects its
132
+ paired HTTP listener with status 308 through the public request hook.
133
+ `arcane dev --public` selects the IPv4 wildcard address;
134
+ explicit `--host` controls the bind address. CLI startup reads one workspace-local
135
+ PEM pair before binding, including ordinary localhost startup.
136
+ The certificate covers the device-facing address, and
124
137
  each client trusts its issuing CA through that platform's certificate setup.
125
138
  This supplies the secure origin required by OPFS/DBOPFS on LAN devices. The
126
139
  server does not install trust, generate certificates, or expose private TLS
127
- material through CLI events. Ordinary localhost development remains HTTP.
140
+ material through CLI events. `port` selects HTTPS; `httpPort` selects the HTTP
141
+ redirect listener, defaulting to an OS-assigned port. Existing raw `tls` inputs
142
+ retain their native HTTPS transport under the SDK, as described by the module's
143
+ advanced TLS extension guidance; all content uses its public serving methods.
128
144
 
129
145
  Development is an intentionally fast feedback loop. Keep each increment small
130
146
  and independently understandable so its effect has one clear cause and a
@@ -48,10 +48,11 @@ meaning and cardinality rules:
48
48
  | `--workspace` | directory | Commands that select an external or integrated workspace; defaults to `.`. |
49
49
  | `--app` | app id | Workspace/app operations except shared scope and `verify-bundle`; also the exact `mail serve` caller id. |
50
50
  | `--arcane-root` | directory | `doctor`, native `build`/`run`, `native-doctor`, `native-prepare` |
51
- | `--host` / `--port` | host / integer 0–65535 | Browser `dev`/`run` default to `127.0.0.1:8000`; `mail serve` defaults to `127.0.0.1:8025` and admits numeric loopback only. |
51
+ | `--host` / `--port` | host / integer 0–65535 | Browser `dev`/`run` default to HTTPS at `127.0.0.1:8000`; `mail serve` defaults to HTTP at `127.0.0.1:8025` and admits numeric loopback only. |
52
+ | `--http-port` | integer 0–65535 | Browser `dev`/`run` HTTP redirect listener; defaults to `0`, which selects an available port. |
52
53
  | `--public` | flag | `dev`; serves HTTPS and binds to `0.0.0.0` unless `--host` explicitly selects another address. |
53
- | `--https` | flag | `dev`; serves HTTPS with the configured or default workspace PEM pair. |
54
- | `--cert` / `--key` | PEM file paths | `dev`; supply both to select HTTPS with an explicit certificate chain and private key. Relative paths resolve from the workspace. |
54
+ | `--https` | flag | Browser `dev`/`run`; retained explicitly, while HTTPS is always enabled. |
55
+ | `--cert` / `--key` | PEM file paths | Browser `dev`/`run`; supply both for an explicit certificate chain and private key. Relative paths resolve from the workspace. |
55
56
  | `--target` | target id | `new`, `init`, native diagnostics, `build`, `run` |
56
57
  | `--format` / `--signing` | target-supported values | Native diagnostics, `build`, `run` |
57
58
  | `--output-root` | directory | Native `build` and `run` |
@@ -311,7 +312,7 @@ npm exec -- arcane upgrade --workspace . --app hello-world
311
312
  ### Overview
312
313
 
313
314
  Starts one development server for one selected app and maps the exact
314
- workspace/runtime routes. It defaults to localhost; `--public` enables access
315
+ workspace/runtime routes. It defaults to HTTPS on localhost; `--public` enables access
315
316
  from other devices on the network over HTTPS.
316
317
 
317
318
  For an external workspace, the server exposes the selected projected
@@ -321,11 +322,19 @@ The explicit live-source SDK mapping remains unchanged and does not replace the
321
322
  installed projection.
322
323
 
323
324
  ```text
324
- arcane dev [--app <id>] [--public] [--https] [--cert <file> --key <file>] [--host <address>] [--port 8000]
325
+ arcane dev [--app <id>] [--public] [--https] [--cert <file> --key <file>] [--host <address>] [--port 8000] [--http-port 0]
325
326
  ```
326
327
 
327
328
  ### Lifecycle
328
329
 
330
+ Startup refreshes the selected authored `arcane-app.json` projection into
331
+ `arcane-package.json`, then refreshes its managed import maps under one
332
+ development-refresh operation lock. The lock is released before the server
333
+ starts. Package-only apps retain their existing manifest. This operation does
334
+ not package the app or produce `dist` output. With PWA enabled, the server
335
+ generates the installation and offline manifests directly; see
336
+ [PWA development and versioning](pwa.md#development-and-hosting).
337
+
329
338
  The command reports acceptance before bind/start work, emits the final URL,
330
339
  owns the server until cancellation, and restores failure to the process exit.
331
340
  The default host is `127.0.0.1`. `--public` selects `0.0.0.0` (all IPv4
@@ -335,25 +344,33 @@ device, since `localhost` refers to that device and `0.0.0.0` is a bind address.
335
344
  Network URLs come from one interface snapshot at startup and do not establish
336
345
  remote reachability through the machine's firewall or network.
337
346
 
338
- Public mode also selects HTTPS. Plain localhost development stays HTTP.
339
- `--https` selects HTTPS without changing the bind address; supplying both
340
- `--cert` and `--key` also selects HTTPS. The command does not configure a
341
- firewall, router forwarding, or an internet tunnel.
347
+ Every Arcane app uses HTTPS for development and packaged browser previews.
348
+ `--https` remains accepted but is no longer needed to select the transport.
349
+ `--port` selects the HTTPS application port. A second HTTP listener returns
350
+ `308` redirects to that HTTPS port, preserving the requested path and query.
351
+ `--http-port` selects its port; the default `0` lets the operating system choose
352
+ an available port. Both listeners use the selected host and must be ready
353
+ before startup completes. Cancellation or a listener failure closes both.
354
+ Human output prints `HTTP redirect: <url>` alongside the HTTPS application URL;
355
+ JSON/NDJSON server results include `httpPort`, `httpOrigin`, and `httpUrl` for
356
+ the redirect endpoint.
357
+ Supplying both `--cert` and `--key` selects an explicit PEM pair. The command
358
+ does not configure a firewall, router forwarding, or an internet tunnel.
342
359
 
343
360
  ### Development HTTPS setup
344
361
 
345
- Before starting public mode, place the development server's PEM certificate
346
- chain at `.arcane/dev/server-cert.pem` and its PEM private key at
362
+ Before starting `arcane dev` or a packaged browser preview, place the server's
363
+ PEM certificate chain at `.arcane/dev/server-cert.pem` and its PEM private key at
347
364
  `.arcane/dev/server-key.pem`, relative to the workspace. Alternatively, pass
348
- `--cert <file> --key <file>` together. The certificate must cover the LAN IP
365
+ `--cert <file> --key <file>` together. The certificate must cover localhost or the LAN IP
349
366
  address or hostname opened by each device. Certificate creation and renewal
350
367
  belong to the developer's certificate tooling; the server does not generate a
351
368
  CA or alter device trust stores. Keep `.arcane/dev/` ignored by Git and keep the
352
369
  private key on the development computer.
353
370
 
354
- The server reads the selected pair once asynchronously per startup, before
355
- binding. Missing files or certificate/key parse errors produce a startup error; public
356
- mode never silently falls back to HTTP. Certificate/key contents are not
371
+ The server reads the selected pair during startup, before binding.
372
+ Missing files or certificate/key parse errors produce a startup error;
373
+ Arcane never silently falls back to HTTP. Certificate/key contents are not
357
374
  included in operation events or JSON/NDJSON output. Restart the server after
358
375
  replacing its certificate pair; ordinary app source edits still appear on
359
376
  refresh without restarting.
@@ -383,6 +400,9 @@ npm run dev -- --app hello-world --public
383
400
 
384
401
  # Equivalent direct CLI invocation, with an optional port.
385
402
  npm exec -- arcane dev --app hello-world --public --port 8000
403
+
404
+ # Select a stable HTTP entry that redirects to HTTPS on port 8000.
405
+ npm exec -- arcane dev --app hello-world --port 8000 --http-port 8080
386
406
  ```
387
407
 
388
408
  ## `arcane test`
@@ -617,11 +637,17 @@ npm exec -- arcane build \
617
637
 
618
638
  For `--target browser`, starts the existing current `dist/<app>` release; it
619
639
  does not package, rebuild, test, check, or verify that release automatically.
640
+ The preview always uses HTTPS with the workspace certificate pair. Supply
641
+ `--cert <file> --key <file>` together to use another pair; see
642
+ [development HTTPS setup](#development-https-setup).
643
+ `--port` selects the HTTPS application port, and `--http-port` selects the
644
+ paired HTTP `308` redirect port. The HTTP port defaults to an available port;
645
+ the CLI prints its actual redirect URL and reports both listener endpoints.
620
646
  For a paired native target, it performs package, prepare, plan, build, launch,
621
647
  readiness, and owned cancellation in one process.
622
648
 
623
649
  ```text
624
- arcane run [--target <target>] [--app <id>] [--arcane-root <directory>] [--output-root <directory>] [--format <format>] [--signing <mode>]
650
+ arcane run [--target <target>] [--app <id>] [--cert <file> --key <file>] [--port 8000] [--http-port 0] [--arcane-root <directory>] [--output-root <directory>] [--format <format>] [--signing <mode>]
625
651
  ```
626
652
 
627
653
  ### Availability
@@ -2991,7 +2991,7 @@
2991
2991
  "entrypoints": ["arcane-os/pwa"],
2992
2992
  "primaryImport": "arcane-os/pwa",
2993
2993
  "group": "Progressive web applications",
2994
- "summary": "Starts PWA registration without blocking page rendering and returns an observable lifecycle owner.",
2994
+ "summary": "Starts PWA registration and DBOPFS-backed page-load resource checks without blocking rendering, returning an observable lifecycle owner.",
2995
2995
  "availability": "Browser service workers; explicit unsupported state elsewhere",
2996
2996
  "protocol": "Native registration, updatefound, statechange and controllerchange events",
2997
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."
@@ -83,31 +83,90 @@ the [existing asset version contract](asset-versioning.md).
83
83
 
84
84
  ## Development and hosting
85
85
 
86
+ Use the ordinary `arcane dev --app <id>` command after editing the selected
87
+ app's `arcane-app.json`. Startup refreshes that app's generated
88
+ `arcane-package.json` from the authored descriptor before refreshing its import
89
+ maps and starting the server. No packaging or `dist` output is required.
90
+ Package-only applications retain their existing descriptor workflow.
91
+
92
+ Add a file or directory to `package.include` to make it part of the app's
93
+ resources. A new file inside an already included directory needs no separate
94
+ entry. If `package.pwa.offline.include` is nonempty, the resource must also
95
+ match that offline selection and must not match `offline.exclude`. Adding a
96
+ path only to the offline selection does not add it to the app's resources.
97
+ Restart after changing descriptor settings. Edits to selected source files are
98
+ picked up by the next due page-load check while the server remains running.
99
+
86
100
  For an enabled application, `arcane dev` serves the generated PWA files at the
87
101
  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;
102
+ selected app per development origin. The SDK uses `node-http-server` for source
103
+ and packaged-preview serving, including conditional resource responses.
104
+ Every Arcane development server and packaged browser preview serves HTTPS,
105
+ including localhost. Configure the workspace certificate pair before starting
106
+ the ordinary command; see [development HTTPS setup](cli.md#development-https-setup).
107
+
108
+ Source inventory work begins when the browser requests the worker or current
109
+ offline manifest, after the page can start. It traverses the selected route
110
+ inventory once for that request, follows page and runtime resource references
111
+ to retain selected query variants, and shares an in-flight traversal with
112
+ concurrent requests. Each referenced source file is read once per traversal;
97
113
  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.
114
+ the application. Installation metadata and bootstrap requests reuse the current
115
+ generated bundle.
116
+
117
+ The SDK owns version information in `arcane-offline.json`:
118
+
119
+ | Field | Owner and update rule |
120
+ | --- | --- |
121
+ | `schemaVersion` | SDK offline-manifest format; currently `1`. |
122
+ | `appVersion` | The app descriptor's top-level `version`. |
123
+ | `sdkVersion` | The selected installed SDK or explicit live SDK source version. |
124
+ | `revision` | `development` for the source server; a fresh generated deployment ID for each packaged output. |
100
125
 
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.
126
+ Do not hand-edit generated manifests or bump a version for every source edit.
127
+ `arcane.webmanifest` holds installation metadata and has no separate SDK-managed
128
+ release counter. Resource freshness uses each response's `Last-Modified` header.
129
+
130
+ On each page load, the SDK reads one `lastChecked` value for the app and worker
131
+ scope from DBOPFS in the background. When that value is missing or older than
132
+ the delivery mode's interval, it checks the current offline manifest and every
133
+ selected resource:
134
+
135
+ | Mode | Page-load check interval |
136
+ | --- | --- |
137
+ | Development | 120 seconds |
138
+ | Packaged browser delivery | 15 minutes |
139
+
140
+ These intervals schedule revalidation; they never expire a cached file. The
141
+ worker sends `GET` with `If-Modified-Since` using the cached response's
142
+ `Last-Modified`. A `304 Not Modified` retains the complete cached response. A
143
+ successful `200` replaces it after the new response is stored. Missing cached
144
+ resources are downloaded. Network and server failures retain an existing
145
+ offline copy and remain observable through SDK diagnostics. A host without
146
+ modification headers must send the current response because freshness cannot
147
+ be established from a missing header.
148
+
149
+ Complete resource responses remain in browser CacheStorage. DBOPFS stores one
150
+ successful whole-cycle timestamp, updated only after the manifest and every
151
+ selected resource have been checked successfully. A partial failure preserves
152
+ the previous timestamp so the next page load can retry. Each cached response
153
+ retains its own `Last-Modified` header, but there are no per-file check times.
154
+ The SDK imposes no age-based cache deletion and
155
+ retains resource bodies across app and SDK version changes. Requests for a page
156
+ do not wait for the complete resource inventory to finish checking. The SDK
157
+ uses at most four concurrent background resource requests and starts no timer
158
+ or polling loop between page loads.
159
+
160
+ During initial installation, selected resources are cached before the browser
161
+ activates the worker. Failures remain observable through native worker state
162
+ and SDK diagnostics. HTTP modification dates have second-level precision;
163
+ hosts must report changes to the served representation, including generated
164
+ output, rather than only the date of an unrelated source file.
107
165
 
108
166
  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.
167
+ An independent static host should support `Last-Modified` and conditional GET
168
+ for stable HTML, module, style and import-map URLs, revalidate worker URLs, and
169
+ serve JavaScript with a JavaScript content type.
111
170
  Keep the worker beside the deployment root it controls. The browser requires a
112
171
  supported secure context, such as trusted HTTPS or localhost, to register it.
113
172
  The SDK does not change certificates or browser permissions.
@@ -132,9 +191,15 @@ worker while they are open. Closing those pages allows activation; a refresh
132
191
  can leave overlapping document clients and keep the update waiting.
133
192
 
134
193
  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.
194
+ restart a model or poll for updates. Worker activation preserves cached
195
+ resources and the DBOPFS check history for the same app and registration scope.
196
+ Saved application data and caches owned by other capabilities are untouched.
197
+
198
+ When importing caches from an older SDK worker, the new worker fetches the
199
+ SDK-owned registration bootstrap and PWA client once so the page can use the
200
+ current cache-check protocol. Other cached resource bodies retain the normal
201
+ check cadence. This transition follows native worker installation and
202
+ activation without forcing a page reload.
138
203
 
139
204
  Switching a server from a packaged release to live development does not replace
140
205
  an already active release worker inside an open document. The same native