arcane-os 0.11.3 → 0.13.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.
@@ -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
@@ -6,7 +6,7 @@
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
7
7
  "moduleSystem": "ESM"
8
8
  },
9
- "memberCount": 205,
9
+ "memberCount": 208,
10
10
  "members": [
11
11
  {
12
12
  "id": "root:APP_BUNDLE_DESCRIPTOR_NAME",
@@ -2991,11 +2991,53 @@
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."
2998
2998
  },
2999
+ {
3000
+ "id": "pwa:PWA_INSTALL_STATE_EVENT",
3001
+ "name": "PWA_INSTALL_STATE_EVENT",
3002
+ "displayName": "PWA_INSTALL_STATE_EVENT",
3003
+ "kind": "constant",
3004
+ "signature": "const PWA_INSTALL_STATE_EVENT",
3005
+ "entrypoints": ["arcane-os/pwa"],
3006
+ "primaryImport": "arcane-os/pwa",
3007
+ "group": "Progressive web applications",
3008
+ "summary": "Names the shared native installation availability and choice state event.",
3009
+ "availability": "Browser installation lifecycle; importable without starting observation",
3010
+ "protocol": "Existing Arcane event owner and native browser installation events",
3011
+ "normalization": "The exact event name is arcane.pwa.install.state."
3012
+ },
3013
+ {
3014
+ "id": "pwa:getPwaInstall",
3015
+ "name": "getPwaInstall",
3016
+ "displayName": "getPwaInstall()",
3017
+ "kind": "function",
3018
+ "signature": "getPwaInstall()",
3019
+ "entrypoints": ["arcane-os/pwa"],
3020
+ "primaryImport": "arcane-os/pwa",
3021
+ "group": "Progressive web applications",
3022
+ "summary": "Returns one shared page owner for native installation availability, prompt choices and session dismissal.",
3023
+ "availability": "Browser native installation events; waiting state while no prompt is available",
3024
+ "protocol": "Native beforeinstallprompt, appinstalled and display-mode change events",
3025
+ "normalization": "Synchronous owner exposes state, subscribe, prompt, dismiss and dispose; subscriptions replay current state, prompt invokes the native event directly in the user click and consumes it once, and session dismissal preserves a retained event for explicit inline installation."
3026
+ },
3027
+ {
3028
+ "id": "pwa:mountPwaInstallPrompt",
3029
+ "name": "mountPwaInstallPrompt",
3030
+ "displayName": "mountPwaInstallPrompt()",
3031
+ "kind": "function",
3032
+ "signature": "mountPwaInstallPrompt({appName = ''} = {})",
3033
+ "entrypoints": ["arcane-os/pwa"],
3034
+ "primaryImport": "arcane-os/pwa",
3035
+ "group": "Progressive web applications",
3036
+ "summary": "Starts shared installation observation and mounts one initially hidden, themed installation suggestion without delaying application rendering.",
3037
+ "availability": "Browser document and managed HTML import; resolves to null without a document",
3038
+ "protocol": "Shared native install owner and pwa-install.html component lifecycle",
3039
+ "normalization": "Repeated calls return the same mounting promise; the first appName initializes the component, success resolves to its ready html-import host, absence of a document or disposal before mounting resolves to null, and component loading failures reject. Removal or disposal during loading rejects with AbortError. A rejected mount permits another explicit attempt. Generated PWA bootstraps invoke it automatically."
3040
+ },
2999
3041
  {
3000
3042
  "id": "browser-speech:BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL",
3001
3043
  "name": "BROWSER_SPEECH_ARTIFACT_GRAPH_PROTOCOL",
@@ -7,7 +7,7 @@
7
7
  "path": "runtime/arcane/components",
8
8
  "sdkVersion": "0.7.2"
9
9
  },
10
- "componentCount": 40,
10
+ "componentCount": 41,
11
11
  "loader": "/arcane/modules/HTMLImport.js",
12
12
  "artifacts": [
13
13
  {
@@ -645,6 +645,34 @@
645
645
  "transport": "HTMLImport + DOM; injected Arcane/provider modules where listed",
646
646
  "normalization": "Normalized form values"
647
647
  },
648
+ {
649
+ "file": "runtime/arcane/components/pwa-install.html",
650
+ "name": "pwa-install.html",
651
+ "purpose": "Presents a dismissible browser installation action with floating or inline placement.",
652
+ "methods": [
653
+ "configure()",
654
+ "install()",
655
+ "dismiss()",
656
+ "destroy()",
657
+ "state",
658
+ "ready"
659
+ ],
660
+ "events": [
661
+ "pwa-install-ready",
662
+ "pwa-install-change",
663
+ "pwa-install-dismissed"
664
+ ],
665
+ "slots": [],
666
+ "dependencies": [
667
+ "strong-type",
668
+ "arcane-os/pwa",
669
+ "arcane-os/event-manager",
670
+ "arcane-os/logging"
671
+ ],
672
+ "availability": "Browser installation requires a browser-provided beforeinstallprompt event; otherwise hidden",
673
+ "transport": "HTMLImport + DOM; shared PWA installation owner and browser-native prompt",
674
+ "normalization": "Browser install availability and outcome supplied by the shared PWA owner"
675
+ },
648
676
  {
649
677
  "file": "runtime/arcane/components/record-timeline.html",
650
678
  "name": "record-timeline.html",
@@ -2,7 +2,9 @@
2
2
 
3
3
  An application supplies its installation identity and offline resource selection.
4
4
  The SDK generates the Web App Manifest, offline inventory, service worker and
5
- nonblocking registration module. Native packages keep their existing lifecycle.
5
+ nonblocking registration module. Its shared installation owner and dismissible
6
+ component expose the browser's available install action. Native packages keep
7
+ their existing lifecycle.
6
8
 
7
9
  ## Application configuration
8
10
 
@@ -63,7 +65,7 @@ Browser packaging emits these files at the selected deployment root:
63
65
  | `arcane.webmanifest` | Browser installation metadata. |
64
66
  | `arcane-offline.json` | App ID/version, SDK version, deployment revision, resource URLs and explicit navigation aliases. |
65
67
  | `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. |
68
+ | `arcane-pwa.mjs` | Independent registration and installation-component bootstrap importing the SDK client. |
67
69
 
68
70
  Each packaged output gets one deployment revision shared by its offline
69
71
  manifest and worker. It distinguishes separately generated outputs even when
@@ -74,6 +76,9 @@ It follows actual resource references to include meaningful query variants.
74
76
  Generated application pages receive a manifest link and an `async` module
75
77
  marked `data-arcane-pwa`. Existing application scripts retain their order.
76
78
  PWA registration does not wait for models, storage, preferences or page rendering.
79
+ The same bootstrap starts one initially hidden `pwa-install.html` component with
80
+ the generated manifest's app name. Component loading and worker registration
81
+ proceed independently.
77
82
 
78
83
  The selected PWA browser delivery removes `v` and `arcaneVersion` from actual
79
84
  local resource references, including the managed import map. Other query fields,
@@ -83,31 +88,103 @@ the [existing asset version contract](asset-versioning.md).
83
88
 
84
89
  ## Development and hosting
85
90
 
91
+ Use the ordinary `arcane dev --app <id>` command after editing the selected
92
+ app's `arcane-app.json`. Startup refreshes that app's generated
93
+ `arcane-package.json` from the authored descriptor before refreshing its import
94
+ maps and starting the server. No packaging or `dist` output is required.
95
+ Package-only applications retain their existing descriptor workflow.
96
+
97
+ Add a file or directory to `package.include` to make it part of the app's
98
+ resources. A new file inside an already included directory needs no separate
99
+ entry. `package.include` is an application resource selection, not a file list
100
+ inside the Web App Manifest. `arcane.webmanifest` contains browser installation
101
+ metadata; `arcane-offline.json` contains the selected offline resource inventory.
102
+ If `package.pwa.offline.include` is nonempty, the resource must also
103
+ match that offline selection and must not match `offline.exclude`. Adding a
104
+ path only to the offline selection does not add it to the app's resources.
105
+ Restart after changing descriptor settings. Edits to selected source files are
106
+ picked up by the next due page-load check while the server remains running.
107
+
86
108
  For an enabled application, `arcane dev` serves the generated PWA files at the
87
109
  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;
110
+ selected app per development origin. The SDK uses `node-http-server` for source
111
+ and packaged-preview serving, including conditional resource responses.
112
+ Every Arcane development server and packaged browser preview serves HTTPS,
113
+ including localhost. Configure the workspace certificate pair before starting
114
+ the ordinary command; see [development HTTPS setup](cli.md#development-https-setup).
115
+ `--public` selects the IPv4 wildcard bind address; it does not enable PWA
116
+ configuration, change manifest metadata, or determine browser installability.
117
+
118
+ Source inventory work begins when the browser requests the worker or current
119
+ offline manifest, after the page can start. It traverses the selected route
120
+ inventory once for that request, follows page and runtime resource references
121
+ to retain selected query variants, and shares an in-flight traversal with
122
+ concurrent requests. Each referenced source file is read once per traversal;
97
123
  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.
124
+ the application. Installation metadata and bootstrap requests reuse the current
125
+ generated bundle.
126
+
127
+ The SDK owns version information in `arcane-offline.json`:
128
+
129
+ | Field | Owner and update rule |
130
+ | --- | --- |
131
+ | `schemaVersion` | SDK offline-manifest format; currently `1`. |
132
+ | `appVersion` | The app descriptor's top-level `version`. |
133
+ | `sdkVersion` | The selected installed SDK or explicit live SDK source version. |
134
+ | `revision` | `development` for the source server; a fresh generated deployment ID for each packaged output. |
135
+
136
+ Do not hand-edit generated manifests or bump a version for every source edit.
137
+ `arcane.webmanifest` holds installation metadata and has no separate SDK-managed
138
+ release counter. Resource freshness uses each response's `Last-Modified` header.
139
+
140
+ On each page load, the SDK reads one `lastChecked` value for the app and worker
141
+ scope from DBOPFS in the background. When that value is missing or older than
142
+ the delivery mode's interval, it checks the current offline manifest and every
143
+ selected resource:
100
144
 
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.
145
+ | Mode | Page-load check interval |
146
+ | --- | --- |
147
+ | Development | 120 seconds |
148
+ | Packaged browser delivery | 15 minutes |
149
+
150
+ These intervals schedule revalidation; they never expire a cached file. The
151
+ worker sends `GET` with `If-Modified-Since` using the cached response's
152
+ `Last-Modified`. A `304 Not Modified` retains the complete cached response. A
153
+ successful `200` replaces it after the new response is stored. Missing cached
154
+ resources are downloaded. Network and server failures retain an existing
155
+ offline copy and remain observable through SDK diagnostics. A host without
156
+ modification headers must send the current response because freshness cannot
157
+ be established from a missing header.
158
+
159
+ Complete resource responses remain in browser CacheStorage. DBOPFS stores one
160
+ successful whole-cycle timestamp, updated only after the manifest and every
161
+ selected resource have been checked successfully. A partial failure preserves
162
+ the previous timestamp so the next page load can retry. Each cached response
163
+ retains its own `Last-Modified` header, but there are no per-file check times.
164
+ The SDK imposes no age-based cache deletion and
165
+ retains resource bodies across app and SDK version changes. Requests for a page
166
+ do not wait for the complete resource inventory to finish checking. A file
167
+ already in the current resource cache also returns immediately when its own
168
+ conditional check is pending or in flight. That background check keeps its
169
+ existing owner and updates the stored response for subsequent requests.
170
+ The page receives the cached response's original status, commonly `200`, even
171
+ when the separate conditional network response is `304`. Status alone does
172
+ not identify a network transfer; use the browser's response source and timing
173
+ details to distinguish cache access from worker startup, queueing and network.
174
+ The SDK
175
+ uses at most four concurrent background resource requests and starts no timer
176
+ or polling loop between page loads.
177
+
178
+ During initial installation, selected resources are cached before the browser
179
+ activates the worker. Failures remain observable through native worker state
180
+ and SDK diagnostics. HTTP modification dates have second-level precision;
181
+ hosts must report changes to the served representation, including generated
182
+ output, rather than only the date of an unrelated source file.
107
183
 
108
184
  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.
185
+ An independent static host should support `Last-Modified` and conditional GET
186
+ for stable HTML, module, style and import-map URLs, revalidate worker URLs, and
187
+ serve JavaScript with a JavaScript content type.
111
188
  Keep the worker beside the deployment root it controls. The browser requires a
112
189
  supported secure context, such as trusted HTTPS or localhost, to register it.
113
190
  The SDK does not change certificates or browser permissions.
@@ -132,14 +209,165 @@ worker while they are open. Closing those pages allows activation; a refresh
132
209
  can leave overlapping document clients and keep the update waiting.
133
210
 
134
211
  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.
212
+ restart a model or poll for updates. Worker activation preserves cached
213
+ resources and the DBOPFS check history for the same app and registration scope.
214
+ Saved application data and caches owned by other capabilities are untouched.
215
+
216
+ When importing caches from an older SDK worker, the new worker fetches the
217
+ SDK-owned registration bootstrap and PWA client once so the page can use the
218
+ current cache-check protocol. Other cached resource bodies retain the normal
219
+ check cadence. This transition follows native worker installation and
220
+ activation without forcing a page reload.
138
221
 
139
222
  Switching a server from a packaged release to live development does not replace
140
223
  an already active release worker inside an open document. The same native
141
224
  worker lifecycle applies.
142
225
 
226
+ ## Installation component
227
+
228
+ Starting with SDK `0.13.0`, enabled PWA pages automatically mount the shared
229
+ [`pwa-install.html` component](runtime-components.md#pwa-installhtml). It appears
230
+ when the browser supplies an installation prompt, offers **Install** and a
231
+ clearly labeled close control, and does not move focus when it appears.
232
+ The floating suggestion has no automatic dismissal timer. Closing it remembers
233
+ the choice for the current tab session and manifest URL, so another page
234
+ load does not immediately show it again. A storage failure leaves the current
235
+ page's dismissal functional and reports the error through console diagnostics.
236
+
237
+ An application can also place the same component inline through `html-import`
238
+ with `data-presentation="inline"`. Both presentations share one page-owned
239
+ native installation event. Dismissing the floating suggestion does not consume
240
+ that event or disable an explicitly placed inline component. Closing an inline
241
+ instance hides only that instance. See the component reference for its
242
+ configuration, methods and events.
243
+
244
+ The browser controls the URL-bar installation indicator and native prompt.
245
+ The SDK cannot force either to appear. Without a captured
246
+ `beforeinstallprompt`, the component remains hidden; that waiting state does
247
+ not establish that installation is unsupported. The browser may still be
248
+ evaluating the app, may already have it installed, or may only support a
249
+ manual browser-menu installation path.
250
+
251
+ ### Browser installation requirements
252
+
253
+ Inspect the loaded page's manifest link and the browser's manifest diagnostics
254
+ when an install action is missing. Confirm that the generated manifest has the
255
+ intended name, `start_url`, scope and app display mode, and that its icon URLs
256
+ resolve to actual images with the declared dimensions. For Chromium's manifest
257
+ install promotion, provide a `purpose: "any"` icon, or omit `purpose` to use
258
+ that default, in PNG, SVG or WebP format. Its strict installation icon selector
259
+ excludes JPEG even when the same image renders successfully on the page.
260
+ Do not change a file's extension or MIME declaration without converting the
261
+ actual image at the application's asset owner. See Chromium's
262
+ [icon selection implementation](https://raw.githubusercontent.com/chromium/chromium/main/third_party/blink/common/manifest/manifest_icon_selector.cc).
263
+
264
+ Providing 192-by-192 and 512-by-512 raster icons follows the
265
+ [browser guidance](https://web.dev/articles/add-manifest). Their absence alone
266
+ does not prove the failure: Chromium can select one larger supported icon.
267
+ Keep actual icon dimensions in `sizes`. Browser diagnostics about missing
268
+ `screenshots` concern the richer installation dialog; screenshots are optional
269
+ and are separate from a usable installation icon.
270
+
271
+ Browser installation requires HTTPS or the browser's localhost/loopback
272
+ exception. A device-facing LAN address is not loopback. Arcane's development
273
+ server still follows its own HTTPS serving contract above. Browser engagement,
274
+ installation state and platform support also affect whether native promotion
275
+ appears; worker cache readiness is not an installation UI prerequisite. See
276
+ [browser installation requirements](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable).
277
+
278
+ Browsers without `beforeinstallprompt` can offer manual installation. For
279
+ example, current iPhone Safari uses Share, **Add to Home Screen**, **Open as
280
+ Web App**, then **Add**. A product may explain that browser-owned path in its
281
+ help, but should not present it as a programmatic SDK install action. The SDK
282
+ does not infer installation support from the user-agent string. See
283
+ [Apple's installation instructions](https://support.apple.com/guide/iphone/open-as-web-app-iphea86e5236/ios).
284
+
285
+ ## getPwaInstall()
286
+
287
+ Import `getPwaInstall` and `PWA_INSTALL_STATE_EVENT` from `arcane-os/pwa`.
288
+ `getPwaInstall()` synchronously returns the shared page owner with `state`,
289
+ `subscribe`, `prompt`, `dismiss` and `dispose`. Call it early when owning a
290
+ separate install entry point so it can capture `beforeinstallprompt` before
291
+ loading the UI. The generated bootstrap already does this through
292
+ `mountPwaInstallPrompt()`.
293
+
294
+ `state` contains `status`, `available`, `dismissed`, `outcome` and `error`.
295
+ Status is `waiting`, `available`, `prompting`, `accepted`, `dismissed`,
296
+ `installed`, `running`, `error` or `disposed`. `available` means a native event
297
+ is retained; a dismissed floating suggestion can still have `available: true`.
298
+ `outcome` is the browser's `accepted` or `dismissed` choice, or `null` before a
299
+ choice. `error` carries the complete prompt error, or `null`.
300
+
301
+ `subscribe(listener, {emitCurrent: true, signal} = {})` immediately replays
302
+ state by default and returns an unsubscribe function. Later state travels
303
+ through the existing Arcane event owner using `PWA_INSTALL_STATE_EVENT`
304
+ (`arcane.pwa.install.state`). A subscription does not wait for worker
305
+ registration, storage initialization or model readiness.
306
+
307
+ Call `prompt()` directly from the user's install click, before any asynchronous
308
+ wait. It invokes the browser prompt in the same call stack, consumes the event
309
+ once, and returns a promise for the browser's choice. It resolves to `null`
310
+ when there is no retained event or the owner is disposed. Failure publishes
311
+ `error` state and rejects. A new native event is required for another prompt.
312
+
313
+ ```javascript
314
+ import {getPwaInstall} from 'arcane-os/pwa';
315
+
316
+ const install = getPwaInstall();
317
+ const installButton = document.querySelector('#install');
318
+
319
+ install.subscribe(function showInstallAvailability(state) {
320
+ installButton.hidden = !state.available;
321
+ });
322
+ installButton.addEventListener('click', function requestInstallation() {
323
+ install.prompt().catch(function reportInstallFailure(error) {
324
+ console.error(error);
325
+ });
326
+ });
327
+ ```
328
+
329
+ `dismiss()` remembers the session choice without consuming the retained event
330
+ and returns the current state. A browser-native dismissed choice is remembered
331
+ too. `appinstalled` clears the event and publishes `installed`; running in an
332
+ app display mode publishes `running` and suppresses the prompt. These states
333
+ do not establish offline readiness. On Android, `appinstalled` can arrive
334
+ before WebAPK creation finishes. See the
335
+ [browser lifecycle distinction](https://web.dev/learn/pwa/detection/).
336
+
337
+ `dispose()` removes the shared owner's native listeners and subscriptions.
338
+ Leaving the page disposes it automatically, except when the browser retains
339
+ the page in its back/forward cache.
340
+ Because the owner is shared, an individual component should dispose its own
341
+ subscription instead. A later `getPwaInstall()` creates a new owner after
342
+ disposal; it cannot recover a native event that was already consumed.
343
+
344
+ ## mountPwaInstallPrompt()
345
+
346
+ `mountPwaInstallPrompt({appName = ''} = {})` starts native install observation
347
+ synchronously, then loads the shared HTML import and theme modules concurrently
348
+ and appends one initially hidden component when the document body is available.
349
+ It returns the same mounting promise on repeated calls; the first call supplies
350
+ the initial app name. The promise resolves to the ready `html-import` host, or
351
+ `null` without a document or when the owner is disposed before mounting. It
352
+ rejects if component loading fails, or with `AbortError` when the loading host
353
+ is removed or its owner disposed. A rejected mount releases its slot so an
354
+ explicit later call can try again. Observe the rejection without making page
355
+ rendering wait for it.
356
+
357
+ The generated PWA bootstrap calls this automatically using the manifest name.
358
+ Applications need not add another floating suggestion. A separate entry point
359
+ can call it explicitly:
360
+
361
+ ```javascript
362
+ import {mountPwaInstallPrompt} from 'arcane-os/pwa';
363
+
364
+ mountPwaInstallPrompt({appName: 'Example Library'}).catch(
365
+ function reportInstallComponentFailure(error) {
366
+ console.error(error);
367
+ }
368
+ );
369
+ ```
370
+
143
371
  ## registerPwa()
144
372
 
145
373
  Import `registerPwa` and `PWA_STATE_EVENT` from `arcane-os/pwa` through the
@@ -82,6 +82,7 @@ appropriate.
82
82
  | [`modal.html`](#modalhtml) | Generic modal with population, open/close, actions, and sequential task execution. | `populate()`<br>`open()`<br>`close()`<br>`runTasks()`<br>`destroy()` | `modal-ready`<br>`modal-opened`<br>`modal-closed`<br>`modal-action` | Modal state normalized; injected task results mixed |
83
83
  | [`output-panel.html`](#output-panelhtml) | Presents status, output, body, coverage, actions, pending, error, and cleared states. | `configure()`<br>`setOutput()`<br>`setBody()`<br>`setCoverage()`<br>`setActions()`<br>`setPending()`<br>`setStatus()`<br>`setError()`<br>`clear()`<br>`destroy()` | `output-panel-ready`<br>`output-panel-state`<br>`output-panel-change`<br>`output-panel-action`<br>`output-panel-error`<br>`output-panel-cleared` | DOM-normalized |
84
84
  | [`preferences-form.html`](#preferences-formhtml) | Builds a schema-driven preferences form with submit, reset, busy, and status behavior. | `configure()`<br>`getValues()`<br>`setValues()`<br>`setBusy()`<br>`setStatus()`<br>`destroy()` | `preferences-form-ready`<br>`preferences-change`<br>`preferences-submit`<br>`preferences-reset` | Normalized form values |
85
+ | [`pwa-install.html`](#pwa-installhtml) | Presents a dismissible browser installation action with floating or inline placement. | `configure()`<br>`install()`<br>`dismiss()`<br>`destroy()`<br>`state`<br>`ready` | `pwa-install-ready`<br>`pwa-install-change`<br>`pwa-install-dismissed` | Browser install availability and outcome supplied by the shared PWA owner |
85
86
  | [`record-timeline.html`](#record-timelinehtml) | Displays complete chronological records/evidence and emits open actions. | `setItems()`<br>`populate()`<br>`destroy()` | `record-timeline-ready`<br>`record-timeline-open` | Complete item fields and inventories preserved |
86
87
  | [`relationship-board.html`](#relationship-boardhtml) | Displays complete normalized relationship nodes/edges in graph and list forms. | `setGraph()`<br>`populate()`<br>`destroy()` | `relationship-board-ready`<br>`relationship-node-open`<br>`relationship-edge-open` | Complete graph inventories and fields preserved |
87
88
  | [`screen-capture.html`](#screen-capturehtml) | Presents image, video, or GIF display-capture workflow. | `capture` (`ScreenCapture` instance)<br>`destroy()` | `screen-capture-ready`<br>`screen-capture-result` | State/result normalized; media permission/codec failures mixed |
@@ -1018,6 +1019,69 @@ Events: `preferences-form-ready`, `preferences-change`, `preferences-submit`, `p
1018
1019
  </html-import>
1019
1020
  ```
1020
1021
 
1022
+ ## pwa-install.html
1023
+
1024
+ ### Overview
1025
+
1026
+ A compact installation suggestion using the shared [PWA installation owner](pwa.md).
1027
+ The default floating panel appears near the top right only when the browser offers
1028
+ installation. It has an Install action and an explicit close button, takes no
1029
+ focus automatically, and has no dismissal timer. It uses the Arcane theme and
1030
+ primitives, wraps complete labels and errors, and scrolls its own content when
1031
+ the available height is limited. The parent page loads `ThemeBootstrap.js` to
1032
+ apply the user's appearance preferences.
1033
+
1034
+ ### Public surface
1035
+
1036
+ `configure({appName, installLabel, closeLabel, promptingLabel, description,
1037
+ presentation})` updates display configuration and returns its current record.
1038
+ Labels remain complete strings. `appName` initially uses `data-app-name` or
1039
+ `this app`; the default button label is `Install`. `presentation` is `floating`
1040
+ by default or `inline`, initially read from `data-presentation`. Inline placement
1041
+ uses the parent's layout. Set `description` to an empty string when a compact
1042
+ placement needs no supporting text; supplied descriptions remain visible.
1043
+
1044
+ `install()` calls the shared owner's native prompt synchronously and returns its
1045
+ promise of the browser outcome or `null` when no prompt is available. Call it
1046
+ directly from a user action. The component's Install button already does this.
1047
+ An explicit request's complete failure message remains visible until dismissed;
1048
+ the method rejects with the same error. The browser controls actual installation.
1049
+ Acceptance hides the suggestion without claiming installation has completed.
1050
+
1051
+ `dismiss()` closes the component. Floating dismissal also uses the owner's
1052
+ session dismissal; an explicit inline component ignores that shared dismissal
1053
+ and closes only its own instance. Closing retains any unused native prompt at
1054
+ the shared owner. `destroy()` removes the component's listeners and subscription,
1055
+ disposes its event source, hides its host, and marks `ready` false; it does not
1056
+ dispose the shared owner or change browser installation state. Both methods
1057
+ return true while active and false after destruction; `destroy()` is idempotent.
1058
+
1059
+ The readonly `state` property returns the shared owner's current install-state
1060
+ record; `ready` becomes true after methods and the state subscription are attached.
1061
+ `pwa-install-ready` carries `{ready, state}`; `pwa-install-change` carries
1062
+ `{state, visible}` for each observed owner update; `pwa-install-dismissed` carries
1063
+ `{presentation, state}`. These follow the canonical event projection contract.
1064
+
1065
+ ### Availability and normalization
1066
+
1067
+ The component requires HTMLImport and a DOM renderer. Native installation
1068
+ availability comes from the browser's `beforeinstallprompt` event through
1069
+ `getPwaInstall()`. Without an available event, the suggestion stays hidden;
1070
+ absence does not identify why installation is unavailable. Installed-app events,
1071
+ accepted prompts, and an already running installed display mode hide it.
1072
+
1073
+ ### Example
1074
+
1075
+ ```html
1076
+ <html-import
1077
+ id="install-app"
1078
+ href="/arcane/components/pwa-install.html"
1079
+ data-app-name="Example Library"
1080
+ data-presentation="inline"
1081
+ hidden>
1082
+ </html-import>
1083
+ ```
1084
+
1021
1085
  ## record-timeline.html
1022
1086
 
1023
1087
  ### Overview