On this page
An application supplies its installation identity and offline resource selection. The SDK generates the Web App Manifest, offline inventory, service worker and nonblocking registration module. Its shared installation owner and dismissible component expose the browser's available install action. Native packages keep their existing lifecycle.
Application configuration
Set package.pwa in the application's arcane-app.json. Its ordinary package
projection carries the same pwa record in arcane-package.json:
{
"enabled": true,
"manifest": {
"name": "Example Library",
"short_name": "Library",
"display": "standalone",
"icons": [
{
"src": "img/library.png",
"sizes": "512x512",
"type": "image/png"
}
]
},
"offline": {
"exclude": ["documents"]
}
}
enabled defaults to false. manifest contains ordinary Web App Manifest
metadata. Its default name comes from displayName; its default start page
comes from the selected application entry. The app owns names, icons, colors,
display preference, routes and descriptions. Use real app icons; the SDK does
not invent branding or claim that a browser has installed the app.
Manifest URL fields are relative to the application directory. Each standalone
app's root is its repository root. Explicit multi-app source and packaged
delivery retain the selected app tree at apps/<id>/. For multi-app packaged output,
the default start_url is ./apps/<id>/<entry>, resolved from the generated
root manifest. The default id and scope remain ./, preserving the existing
deployment-root installation identity. Authored relative URL fields, including
explicit id and scope values, resolve against ./apps/<id>/; an icon such
as img/library.png therefore remains beneath the app directory. Absolute URL
fields retain their authored destination.
For appsRoot: ".", application URLs stay at the deployment root. Direct npm
routes keep runtime URLs under /node_modules/<dependency>/.... The public
arcane import-map operation generates static PWA files beside the root entry
and adds their links to the selected navigable documents. This works with an
ordinary static host; it does not copy the installed runtime into arcane/.
The source root defaults its installation ID to /apps/<id>/ to preserve the
previous source identity while its start URL and scope move to the root. An
authored manifest.id remains authoritative. Packaged default identity remains
./. App identity and saved application data remain unchanged.
Application pages and preserved HTML resources
Optional package.documents in arcane-app.json selects the app-root-relative
.html/.htm pages that receive managed import maps and PWA links/bootstrap.
Its schema-1 projection is arcane-package.json's top-level documents.
For example, "documents": ["index.html"] selects the entry while other
included HTML documents retain their exact content. The entry is always selected;
[] also selects only the entry. Omitting the property preserves automatic
discovery of the entry and included secondary pages with an active <base>.
Selected pages retain the existing path-correct base requirement.
This declaration does not add or remove resources from package.include,
package.exclude, serving, packaging or the offline inventory. Included
originals remain available and can be cached under the normal offline selection,
including when a selected page displays them in an iframe. With an explicit
list, those unselected documents are not scanned as application pages or given
managed import maps, base handling or PWA injection. HTML-import components and
shared runtime-resource traversal keep their existing processing. See
application document selection
for ordering and the complete declaration contract.
Root generated output
For a standalone application, appsRoot: "." places the app at its repository
root. Import-map refresh, arcane dev, package inspection/dry run and browser
packaging use that layout directly:
- The four root PWA files are generated normally when PWA is enabled.
- The SDK generates no
apps/<id>/navigation pages, nested worker or duplicate offline inventory, and adds no redirects for that path family. - Runtime resources use the selected npm package paths; no repository-root
arcane/projection is generated. - The default source installation ID remains
/apps/<id>/, the default packaged installation ID remains./, and an explicitmanifest.idremains authoritative. An ID identifies the installation; it does not generate a directory or a redirect. - Authored resources follow their normal include/exclude selection. Ordinary generation preserves existing files, stored application data, and worker caches. Explicit retirement of confirmed generated source output is described below.
Generated and offline app files are committed. GitHub Actions consume those
committed files rather than generating them. Actual multi-app workspaces keep
their explicit appsRoot: "apps" layout. This source/package contract does not
establish any particular installed application's browser lifecycle.
Disable source PWA generation and retire generated output
Set package.pwa.enabled:false in arcane-app.json (or pwa.enabled:false in
an authored schema-1 arcane-package.json), then run the public import-map
operation. For standalone appsRoot:"." applications, disabled refresh removes
active data-arcane-pwa registration scripts and matching manifest relationships
marked data-arcane-manifest from the selected application documents. Root source
generation marks only manifest links it newly inserts; it does not claim an
existing unmarked link as its own. A wholly omitted pwa record does not select
source retirement.
By default, unmarked manifest references and all existing PWA files remain in place. This preserves authored resources that happen to use the standard names. Older SDK source generation emitted an unmarked manifest link. When the application owner has established that this link and the four standard root outputs are SDK-generated, use the same public toolchain with an explicit ownership declaration:
import {createToolchain} from 'arcane-os';
const toolchain = createToolchain({workspaceRoot: process.cwd(), appId: 'my-app'});
await toolchain.importMap({retireGeneratedPwa: true});
Before retirement, remove any explicit entries for these generated outputs from
the application's package.include selection. They are generated output, not
required source input; leaving a deleted file as a required input would make the
next inspection fail. Keep authored selection changes with the application
owner. Confirm that unselected documents or separately hosted pages do not still
need the retiring files; this operation does not rewrite those pages.
With PWA disabled, retireGeneratedPwa:true also removes selected-document
manifest relationships resolving to the root arcane.webmanifest, including
older unmarked generated links, and deletes exactly these confirmed generated
files at the application root:
arcane.webmanifestarcane-offline.jsonarcane-sw.jsarcane-pwa.mjs
The option is an application-owner declaration of generated ownership, not a
filename-based inference. Omit it when any listed file or matching manifest
reference is authored or its ownership is unknown. It is available through
createToolchain().importMap() and executeOperation('import-map', options);
there is no new CLI flag. The ordinary CLI refresh performs marked-reference
retirement only. An enabled descriptor ignores this retirement option and
continues normal generation.
With the source selection updated, repeated retirement accepts already-absent
generated files. The
import-map.root-files.completed event reports the paths actually removed in
retiredPwa. Re-enabling PWA regenerates its normal root output and references.
The operation leaves other files (including a separate manifest.json), other
link relationships, inactive HTML, unselected documents, SDK mappings, and
application content unchanged. It never unregisters an already registered
service worker, clears caches, or changes browser saved data. Existing browser
registrations therefore keep their own lifecycle; source retirement is not a
claim that an installed browser PWA was removed. Package-output behavior below
is unchanged.
Reference updates finish before file removal begins. Cancellation or a file error is reported without undoing earlier completed changes; a later call can finish retirement. The operation does not claim an all-or-nothing transaction.
Disabled worker retirement on the source server
startDevServer() and developApplication() serve a retirement worker at
/arcane-sw.js when source mode selects a standalone appsRoot:"." application
with explicit pwa.enabled:false and that root worker file is absent. This
browser lifecycle is separate from the import-map filesystem operation above.
The existing server returns JavaScript with Cache-Control: no-cache, including
when the request carries a former enabled worker's If-Modified-Since date.
It writes no replacement worker file and performs no offline-inventory scan.
When the browser updates the existing registration from that URL, the
retirement worker calls skipWaiting() during installation and unregisters its
own registration during activation. It has no fetch handler, so subsequent
requests reaching it use the ordinary network path. It never clears caches,
DBOPFS, profiles or other saved data; it does not enumerate or claim clients,
reload pages, or start a polling loop. Other service-worker registrations remain
untouched.
Native service-worker update and activation timing remains browser-owned. An
already open page may retain the neutral retirement worker as its controller
for the rest of its lifetime, and its already rendered content stays unchanged.
Unregistration removes the registration from future navigation matching;
retirement does not promise immediate controller === null or automatic page
refresh. These boundaries follow the platform's
activation and
unregistration
contracts.
An existing root arcane-sw.js retains its ordinary authored-file routing,
including its normal application selection; only an ENOENT filesystem result
selects the retirement response. Omitted PWA configuration, enabled PWA,
multi-app source workspaces, other URLs and packaged serving keep their existing
behavior. A separately operated static host must own its worker lifecycle;
filesystem retirement alone does not establish browser retirement on that host.
The server path uses the existing portable Node serving owner, and browser
retirement requires the browser's native service-worker support and an existing
registration eligible for that update.
Retirement through an existing custom host
An application that already serves its source through node-http-server can
keep that server and delegate only its root worker route to the public SDK
representation owner:
import {Server} from 'node-http-server';
import {resolvePwaRetirementResponse} from 'arcane-os';
// This is the application's existing subclass and workspace root.
class DragonMailServer extends Server {
async onRequest(request, response) {
const pathname = new URL(request.originalUrl || request.url, 'http://localhost').pathname;
if ((request.method === 'GET' || request.method === 'HEAD') && pathname === '/arcane-sw.js') {
const retirement = await resolvePwaRetirementResponse({
workspaceRoot: this.workspaceRoot,
appId: 'dragon-mail'
});
if (retirement) {
response.statusCode = retirement.statusCode;
for (const [name, value] of Object.entries(retirement.headers)) {
response.setHeader(name, value);
}
await this.serve(request, response, retirement.body);
return true;
}
}
// Keep the application's other routes here before ordinary static serving.
return false;
}
}
Use the application's actual root and ID. The SDK reads the current
arcane-packager.json and, for standalone appsRoot:".", the authored root
arcane-app.json; only an absent authored descriptor selects
arcane-package.json. Reads use the existing descriptor projection and package
contract without rewriting either file or requiring a stale generated projection
to equal a newly edited authored descriptor. appId is optional; when supplied,
it must match that descriptor's ID.
The result is null for multi-app workspaces, omitted or enabled PWA, an omitted
pwa.enabled field (including {pwa:{}}), and any existing root arcane-sw.js
entry. The author's explicit pwa.enabled:false choice and an ENOENT
worker result produce {statusCode, headers, body}: status 200, the shared
JavaScript content type, Cache-Control: no-cache, and the complete existing
retirement script. Malformed configuration and other filesystem failures reject
instead of selecting another descriptor or treating a file as absent.
The host applies those headers directly; it must not replace no-cache with
its unrelated response defaults or treat a former enabled worker's validator as
validation of this response. The existing serve() method owns response
completion and HEAD body omission. Keep this exact worker route at the serving
origin that owns the registration; application HTTPS redirects and other routes
remain application-owned. This API starts no server, writes no file, performs
no offline-inventory scan, and touches no browser data. It shares the same
response policy and worker generator as the SDK source server. The native
activation and existing-page lifetime limits above also apply here.
Authored host root and app entry
An authored index.html at the source host root keeps / separate from the
selected application entry. Root import-map generation and development PWA
generation omit the /-to-entry navigation alias whenever that file exists,
including when it is outside package.include or the offline selection.
Development serving answers / with that host index. An index outside the app's
selected source mapping is served unchanged through this exact-root route;
/index.html and other resource URLs retain their existing selection rules.
A selected index follows its existing managed-document classification. Root
presence alone adds no page or assets to the application or offline inventory
and adds no managed maps or PWA bootstrap. The host owns a root response outside
the worker's selected offline resources. With no host index, development retains
its app-entry redirect.
Packaging considers the selected output instead of unrelated source-host files.
A selected authored output-root index.html is preserved and suppresses the
deployment-root navigation alias. When selected content supplies no output-root
index, the package retains its generated launcher and app-entry alias. An
authored source root excluded from the package does not replace that launcher.
Only when selected records supply an authored output-root index does packaging
write app.rootDocument: "./index.html" in ARCANE_APP_RELEASE.json.
Packaged preview uses that generated field to serve the authored document at
/; it does not inspect HTML to infer root ownership. Without the field,
including older releases and generated-launcher packages, preview retains its
302 redirect to app.start (or the older app.entry fallback), preserving the
request query. The startup URL remains the selected app launch URL.
This behavior requires no new setting. The application entry, default install start URL, manifest identity and scope, worker registration scope, cache identity, and saved application data keep their existing contracts.
Offline resource selection
offline.include and offline.exclude select literal paths or directory
prefixes from the selected emitted inventory. An omitted or empty include list
selects that inventory; exclusions subtract from it. App files use app-relative
paths. Shared runtime selections follow the configured destinations: for example,
node_modules/arcane-os/browser-runtime/pwa.mjs for a direct installed route,
or arcane/sdk/pwa.mjs for a physical or virtual route.
For example, selecting modules matches a root app's modules/... or a nested
app's apps/<id>/modules/...; emitted offline URLs retain that selected layout.
The application entry and generated PWA shell records are retained. Select the
resources needed by every offline page, including its shared modules and styles.
The worker script itself is never an application cache entry.
For direct installed-package root applications, public arcane import-map
generation follows the same source resource graph as arcane dev before writing
the static offline inventory. Imports from unvisited pages, shared runtime
modules, component styles, and statically declared Worker URLs retain their
authored query values. SDK component ./arcane/ resource attributes resolve from
that component's actual runtime root, including installed package aliases and
nested component directories; ordinary application document bases keep their
normal meaning. The graph selects resources already present in the configured
routes. It does not invent resource paths or enumerate user-specific page URLs.
These settings describe published application resources. They do not select user uploads, provider responses, live API requests, saved conversations, preferences, IndexedDB, OPFS, or a model owner's download/cache lifecycle. Offline page availability does not imply that cloud inference or other network services work offline. Applications own the corresponding visible behavior.
Generated output
Browser packaging emits these files at the selected deployment root:
| File | Purpose |
|---|---|
arcane.webmanifest |
Browser installation metadata. |
arcane-offline.json |
App ID/version, SDK version, deployment revision, resource URLs and explicit navigation aliases. |
arcane-sw.js |
Stable worker URL with the selected offline manifest embedded in its source. |
arcane-pwa.mjs |
Independent registration and installation-component bootstrap importing the SDK client. |
Standalone app files stay at the output root. Explicit multi-app workspaces
retain their selected app under apps/<id>/, with shared runtime routes alongside
it. The SDK supplies a root index.html launcher when selected content has no
root page. The generated PWA files stay at the deployment
root so their worker can serve the selected app and shared resources. The
worker is registered for the deployment root independently of any app-authored
manifest scope.
Each packaged output gets one deployment revision shared by its offline manifest and worker. It distinguishes separately generated outputs even when their app and SDK versions match. It is not a content measurement.
The package owns its selected inventory once, after any app adapter finishes.
It follows actual resource references to include meaningful query variants.
Selected application pages receive a manifest link and an async module
marked data-arcane-pwa. Existing application scripts retain their order.
PWA registration does not wait for models, storage, preferences or page rendering.
The same bootstrap starts one initially hidden pwa-install.html component with
the generated manifest's app name. Component loading and worker registration
proceed independently.
SDK processing of selected application pages, components and runtime resources
removes only SDK-owned arcaneVersion fields from actual local resource references,
including the managed import map. Explicitly unselected HTML documents keep their
exact content. Authored fields such as v, encoded and repeated fields, empty
query segments, fragments,
source spelling and unrelated payloads are preserved. The offline
manifest carries release information. Non-PWA and native packaging use the same
clean local resource URLs. Native packaging does not
generate the browser PWA worker, installation manifest or registration bootstrap.
Package without browser PWA output
Select browserPwa:false for an artifact, such as an application directory
inside an extension-owned root. The package omits arcane.webmanifest,
arcane-offline.json, arcane-sw.js, and arcane-pwa.mjs. Before native-module
processing, it removes active SDK-marked data-arcane-pwa scripts and manifest
relationships that resolve to that package's arcane.webmanifest from the
selected application document set.
This is a package-output choice, not a source or saved-setting change. It
preserves unrelated manifest links and link relationships, comments, templates,
other inactive HTML, unselected documents, and authored PWA configuration. The
application remains responsible for any extension manifest, permissions,
background process, toolbar launcher, or other outer host. See the
native-module output contract and
packageApp() options for the complete boundary.
Development and hosting
Use the ordinary arcane dev --app <id> command after editing the selected
app's arcane-app.json. Startup refreshes that app's generated
arcane-package.json from the authored descriptor before refreshing its import
maps and starting the server. No packaging or dist output is required.
Package-only applications retain their existing descriptor workflow.
With the external installed-v1 routes,
the server inventories the selected SDK directly in node_modules. Runtime
resources follow the configured destinations. Direct routes use real
node_modules/<dependency>/... URLs, including an npm alias when selected.
Earlier virtual routes retain their arcane/, arcane/sdk/, and
arcane/dependencies/strong-type/ destinations. Offline selections use those
same emitted paths. No workspace arcane/ tree or
arcane.lock.json is needed. Packaging uses the same route selection and copies
the selected resources into the portable output; existing physical projections
remain supported.
Add a file or directory to package.include to make it part of the app's
resources. A new file inside an already included directory needs no separate
entry. package.include is an application resource selection, not a file list
inside the Web App Manifest. arcane.webmanifest contains browser installation
metadata; arcane-offline.json contains the selected offline resource inventory.
If package.pwa.offline.include is nonempty, the resource must also
match that offline selection and must not match offline.exclude. Adding a
path only to the offline selection does not add it to the app's resources.
The running source server reads changed app descriptors before the next app,
root-navigation or generated-PWA request. Include/exclude rules, the entry,
document selection and PWA settings share the current selection; no restart or
package projection write is needed. Edits to selected source files are picked up by the next due
page-load check while the server remains running.
For an enabled application, arcane dev serves the generated PWA files at the
origin root and starts at the selected app entry: /index.html for a standalone
root app with that entry, or /apps/<id>/<entry> for a nested app. Use one
selected app per development origin. The SDK uses node-http-server for source
and packaged-preview serving, including conditional resource responses.
Arcane development servers and packaged browser previews default to HTTPS,
including localhost. Configure the workspace certificate pair before
starting the ordinary command; see development HTTPS setup.
Explicit source-only arcane dev --http serves the same generated PWA routes
without loading certificates; see explicit HTTP development.
API callers may select http:true in packaged mode to serve the existing
packaged web root, without generating or changing its PWA files. Browser
secure-context and installation requirements remain browser-owned.
--public selects the IPv4 wildcard bind address; it does not enable PWA
configuration, change manifest metadata, or determine browser installability.
Live source inventory work begins when the browser requests the worker or current offline manifest, after the page can start. It traverses the selected route inventory once for that request, follows page and runtime resource references to retain selected query variants, and shares an in-flight traversal with concurrent requests. Each referenced source file is read once per traversal; document corpus bodies remain under their existing owner. It does not rebuild the application. Installation metadata and bootstrap requests reuse the current generated bundle.
An explicit root import-map refresh also performs that traversal to produce the committed static PWA files. It opens no server or listener. Its generated inventory is available to ordinary static hosting without first visiting each page through the development server.
The SDK owns version information in arcane-offline.json:
| Field | Owner and update rule |
|---|---|
schemaVersion |
SDK offline-manifest format; currently 1. |
appVersion |
The app descriptor's top-level version. |
sdkVersion |
The selected installed SDK or explicit live SDK source version. |
revision |
development for the source server; a fresh generated deployment ID for each packaged output. |
Do not hand-edit generated manifests or bump a version for every source edit.
arcane.webmanifest holds installation metadata and has no separate SDK-managed
release counter. Resource freshness uses each response's Last-Modified header.
On each page load, the SDK reads one lastChecked value for the app and worker
scope from DBOPFS in the background. When that value is missing or older than
the delivery mode's interval, it checks the current offline manifest and every
selected resource:
| Mode | Page-load check interval |
|---|---|
| Development | 120 seconds |
| Packaged browser delivery | 15 minutes |
These intervals schedule revalidation; they never expire a cached file. The
worker sends GET with If-Modified-Since using the cached response's
Last-Modified. A 304 Not Modified retains the complete cached response. A
successful 200 replaces it after the new response is stored. Missing cached
resources are downloaded. Network and server failures retain an existing
offline copy and remain observable through SDK diagnostics. A host without
modification headers must send the current response because freshness cannot
be established from a missing header.
Complete resource responses remain in browser CacheStorage. DBOPFS stores one
successful whole-cycle timestamp, updated only after the manifest and every
selected resource have been checked successfully. A partial failure preserves
the previous timestamp so the next page load can retry. Each cached response
retains its own Last-Modified header, but there are no per-file check times.
The SDK imposes no age-based cache deletion and
retains resource bodies across app and SDK version changes. Requests for a page
do not wait for the complete resource inventory to finish checking. A file
already in the current resource cache also returns immediately when its own
conditional check is pending or in flight. That background check keeps its
existing owner and updates the stored response for subsequent requests.
The page receives the cached response's original status, commonly 200, even
when the separate conditional network response is 304. Status alone does
not identify a network transfer; use the browser's response source and timing
details to distinguish cache access from worker startup, queueing and network.
The SDK
uses at most four concurrent background resource requests and starts no timer
or polling loop between page loads.
During initial installation, selected resources are cached before the browser activates the worker. Failures remain observable through native worker state and SDK diagnostics. HTTP modification dates have second-level precision; hosts must report changes to the served representation, including generated output, rather than only the date of an unrelated source file.
The SDK development server sends Cache-Control: no-cache for PWA resources.
An independent static host should support Last-Modified and conditional GET
for stable HTML, module, style and import-map URLs, revalidate worker URLs, and
serve JavaScript with a JavaScript content type.
Keep the worker beside the deployment root it controls. The browser requires a
supported secure context, such as trusted HTTPS or localhost, to register it.
The SDK does not change certificates or browser permissions.
An initial visit can load before a worker controls the document. Revalidation therefore matters even when an offline cache exists. Updating files in place while installation fetches them is not an atomic release snapshot; the host owns consistent deployment of the selected output.
Where an app-entry navigation alias remains, it redirects to the selected entry page, preserving the document URL used to resolve relative modules and styles. An authored host root suppresses that alias as described above; merely retaining an old cached root resource does not add it to the current offline selection.
For a same-origin navigation to a selected static .html or .htm document,
the worker can reuse that document's cached body while leaving the complete
query and fragment in the browser's navigation URL. Pages such as
dashboard.html?id=person therefore retain their application-owned parameters
without enumerating people or saving a cache entry for every query. An exact
query-specific resource already selected in the inventory takes precedence.
When the plain document has no retained cached body, the original navigation
request goes to the network unchanged; its response is not saved over the plain
document. This does not change query matching for assets, API calls, ordinary
fetch requests, unselected documents or other origins. Cache refresh and the
Update prompt keep their existing lifecycle.
For a same-origin directory navigation without an app-entry alias, the worker
can likewise reuse that directory's index.html body only when the index is
already in the current offline selection and has a retained cached response.
The directory URL and its query remain unchanged, including beneath a deployment
subpath. An exact query-specific selected resource takes precedence. Otherwise,
the original request passes to the host unchanged. This adds no page or asset to
the inventory and does not save a host response over the selected index.
Browser storage eviction can remove an offline cache. A missing release cache entry falls back to the network, so offline availability still depends on the browser retaining the selected resources.
After a complete successful refresh, updateAvailable becomes true for an
already-open page when at least one cached replacement has different parseable
old and new Last-Modified dates, or a successfully checked resource was absent
from the prior durable offline inventory. Different modification dates are the
server's declaration of modification, not a comparison of response content.
Initial cache population, eviction repair of a previously selected URL, 304,
and 200 responses with equal, missing or unreadable modification dates do not
establish an update. Such successful responses still follow the existing cache
replacement behavior. A host without meaningful modification dates cannot
announce same-URL changes through this contract.
The resource cache privately retains the pre-refresh inventory membership and a pending-change flag while a refresh is incomplete. Before replacing a resource with a different modification date, it also saves that resource's prior date. Writes to this one metadata entry are ordered; independent resource fetches remain concurrent. This lifecycle record is outside the resource inventory and is never fetched from the server. A change becomes evidence only after the resource's cache write succeeds. After interruption, the cached response's date is compared with the retained prior date, so a failed later metadata write cannot lose that evidence. Failed replacements alone establish no update. The record survives worker termination and partial refresh failure and is cleared after complete success. Partial failure never announces a newly available update. The completed worker reply and a scoped message to existing window clients carry the result; each page owner keeps its own sticky availability state. A new page does not replay an old completed notification.
Updates and stored data
An updated worker installs alongside the current worker, then waits for the browser's normal activation boundary. Existing controlled pages retain their worker while they are open. Closing those pages allows activation; a refresh can leave overlapping document clients and keep the update waiting.
The SDK does not call skipWaiting, claim the initial page, automatically reload a page,
restart a model or poll for updates. Worker activation preserves cached
resources and the DBOPFS check history for the same app and registration scope.
Saved application data and caches owned by other capabilities are untouched.
When importing caches from an older SDK worker, the new worker fetches the SDK-owned registration bootstrap and PWA client once so the page can use the current cache-check protocol. Other cached resource bodies retain the normal check cadence. This transition follows native worker installation and activation without forcing a page reload.
When a complete resource refresh establishes an update, the shared prompt shows
Update available and Update, including in an installed or running app
and after an earlier installation suggestion was dismissed. Update dismissal
is separate and lasts for this page owner's lifetime. Clicking Update calls
ordinary location.reload() for the current page, with no extra SDK
confirmation or cache reset. Native beforeunload handling and application-owned
draft/session persistence retain their normal ownership. This is a page reload,
not a promise to preserve transient application state or an active chat.
Switching a server from a packaged release to live development does not replace an already active release worker inside an open document. The same native worker lifecycle applies.
Installation component
Starting with SDK 0.13.0, enabled PWA pages automatically mount the shared
pwa-install.html component. It appears
when the browser supplies an installation prompt, offers Install and a
clearly labeled close control, and does not move focus when it appears.
The floating suggestion has no automatic dismissal timer. Closing it remembers
the choice for the current tab session and manifest URL, so another page
load does not immediately show it again. A storage failure leaves the current
page's dismissal functional and reports the error through console diagnostics.
An application can also place the same component inline through html-import
with data-presentation="inline". Both presentations share one page-owned
native installation event. Dismissing the floating suggestion does not consume
that event or disable an explicitly placed inline component. Closing an inline
instance hides only that instance. See the component reference for its
configuration, methods and events.
The browser controls the URL-bar installation indicator and native prompt.
The SDK cannot force either to appear. Without a captured
beforeinstallprompt, installation remains hidden; an available update can
still show. That waiting installation state does
not establish that installation is unsupported. The browser may still be
evaluating the app, may already have it installed, or may only support a
manual browser-menu installation path.
Browser installation requirements
Inspect the loaded page's manifest link and the browser's manifest diagnostics
when an install action is missing. Confirm that the generated manifest has the
intended name, start_url, scope and app display mode, and that its icon URLs
resolve to actual images with the declared dimensions. For Chromium's manifest
install promotion, provide a purpose: "any" icon, or omit purpose to use
that default, in PNG, SVG or WebP format. Its strict installation icon selector
excludes JPEG even when the same image renders successfully on the page.
Do not change a file's extension or MIME declaration without converting the
actual image at the application's asset owner. See Chromium's
icon selection implementation.
Providing 192-by-192 and 512-by-512 raster icons follows the
browser guidance. Their absence alone
does not prove the failure: Chromium can select one larger supported icon.
Keep actual icon dimensions in sizes. Browser diagnostics about missing
screenshots concern the richer installation dialog; screenshots are optional
and are separate from a usable installation icon.
Browser installation requires HTTPS or the browser's localhost/loopback exception. A device-facing LAN address is not loopback. Selecting HTTP serving does not make that LAN origin a secure context. Chromium documents a separate explicit developer origin setting; the SDK does not configure that setting or claim that starting the server proves installation eligibility. Browser engagement, installation state and platform support also affect whether native promotion appears; worker cache readiness is not an installation UI prerequisite. See browser installation requirements.
Browsers without beforeinstallprompt can offer manual installation. For
example, current iPhone Safari uses Share, Add to Home Screen, Open as
Web App, then Add. A product may explain that browser-owned path in its
help, but should not present it as a programmatic SDK install action. The SDK
does not infer installation support from the user-agent string. See
Apple's installation instructions.
getPwaInstall()
Import getPwaInstall and PWA_INSTALL_STATE_EVENT from arcane-os/pwa.
getPwaInstall() synchronously returns the shared page owner with state, ready,
subscribe, prompt, dismiss, update, dismissUpdate and dispose. Call it early when owning a
separate install entry point so it can capture beforeinstallprompt before
loading the UI. The generated bootstrap already does this through
mountPwaInstallPrompt().
state contains status, available, installed, dismissed, updateAvailable,
updateDismissed, outcome,
error and storageError.
Status is waiting, available, prompting, accepted, dismissed,
installed, running, error or disposed. available means a native event
is retained, the initial installation-record read has settled, and installation
has not been recorded or detected. A dismissed floating suggestion can still
have available: true. installed is true after browser-reported installation
or restoration of that app's saved installation record.
outcome is the browser's accepted or dismissed choice, or null before a
choice. error carries the complete prompt error, or null; storageError
carries the complete DBOPFS read or write error, or null.
updateAvailable starts false and remains true after this owner observes a
completed changed refresh for the current page's worker scope through
arcane.pwa.state. updateDismissed starts false and is independent of
installation dismissal and saved installation status. Neither field changes
native installation status or available. A late-created prompt reads the
current state of live registration owners before returning; earlier creation
is still needed to capture a browser installation event that has not yet fired.
The generated bootstrap establishes observation before registering the worker.
ready resolves to the state after the initial DBOPFS read settles. Native
events are captured synchronously while this read runs. Only installation
availability waits for it; page rendering, component loading and worker
registration continue independently. A storage failure is logged and published
as storageError; ready still resolves and native installation remains usable.
subscribe(listener, {emitCurrent: true, signal} = {}) immediately replays
state by default and returns an unsubscribe function. Later state travels
through the existing Arcane event owner using PWA_INSTALL_STATE_EVENT
(arcane.pwa.install.state). A subscription does not wait for worker
registration, storage initialization or model readiness.
Call prompt() directly from the user's install click, before any asynchronous
wait. It invokes the browser prompt in the same call stack, consumes the event
once, and returns a promise for the browser's choice. It resolves to null
when installation is unavailable, including while the saved state is loading
or after installation is remembered. Failure publishes
error state and rejects. A new native event is required for another prompt.
import {getPwaInstall} from 'arcane-os/pwa';
const install = getPwaInstall();
const installButton = document.querySelector('#install');
install.subscribe(function showInstallAvailability(state) {
installButton.hidden = !state.available;
});
installButton.addEventListener('click', function requestInstallation() {
install.prompt().catch(function reportInstallFailure(error) {
console.error(error);
});
});
dismiss() remembers the session choice without consuming the retained event
and returns the current state. A browser-native dismissed choice is remembered
too. appinstalled clears the event, publishes installed, and saves
{installed: true} as pwa/installed.json through the current app-scoped
DBOPFS singleton. An installed-app launch in standalone, minimal-ui or
window-controls-overlay mode, or with navigator.standalone === true, also
records installation. Ordinary fullscreen suppresses the current prompt but
does not record installation because browsers can enter fullscreen without
installing an app; see the display-mode specification.
Every new owner reads the saved flag, so both floating and explicit inline controls stay hidden on subsequent visits in that browser origin's app scope. The SDK never clears this flag or resets it when display mode changes. Prompt acceptance and dismissal alone do not write it. The record is independent of resource-check history and other application data. A pending read cannot undo newly observed installation, and a confirmed installation's pending write is retained through owner disposal. A write failure remains observable without making the current installed session eligible again.
update() synchronously calls the current page's ordinary location.reload()
and returns true when an update is available, including after dismissal.
It returns false when unavailable or disposed. A native reload error propagates
to the caller. It leaves availability unchanged so a cancelled native unload
does not discard the update. dismissUpdate() hides the update suggestion for
this page owner and returns the current state, without changing installation
dismissal or writing a durable record. A disposed owner returns its current
state unchanged. Call update() only from the user's explicit Update action.
Running in an app display mode publishes running. These states
do not establish offline readiness. On Android, appinstalled can arrive
before WebAPK creation finishes. See the
browser lifecycle distinction.
dispose() removes the shared owner's native listeners and subscriptions.
Leaving the page disposes it automatically, except when the browser retains
the page in its back/forward cache.
Because the owner is shared, an individual component should dispose its own
subscription instead. A later getPwaInstall() creates a new owner after
disposal; it cannot recover a native event that was already consumed.
mountPwaInstallPrompt()
mountPwaInstallPrompt({appName = ''} = {}) starts native install observation
synchronously, then loads the shared HTML import and theme modules concurrently
and appends one initially hidden component when the document body is available.
It returns the same mounting promise on repeated calls; the first call supplies
the initial app name. The promise resolves to the ready html-import host, or
null without a document or when the owner is disposed before mounting. It
rejects if component loading fails, or with AbortError when the loading host
is removed or its owner disposed. A rejected mount releases its slot so an
explicit later call can try again. Observe the rejection without making page
rendering wait for it.
The generated PWA bootstrap calls this automatically using the manifest name. Applications need not add another floating suggestion. A separate entry point can call it explicitly:
import {mountPwaInstallPrompt} from 'arcane-os/pwa';
mountPwaInstallPrompt({appName: 'Example Library'}).catch(
function reportInstallComponentFailure(error) {
console.error(error);
}
);
registerPwa()
Import registerPwa and PWA_STATE_EVENT from arcane-os/pwa through the
managed browser import map. The generated bootstrap already calls this API;
manual callers use it when they own a separate registration entry point.
import {registerPwa} from 'arcane-os/pwa';
const pwa = registerPwa(
{
workerUrl: new URL('./arcane-sw.js', import.meta.url).href,
scope: new URL('./', import.meta.url).href
}
);
pwa.subscribe(function showPwaState(state) {
console.log(state);
});
pwa.ready.catch(function reportRegistrationFailure(error) {
console.error(error);
});
The function returns synchronously with {ready, state, subscribe, update, dispose}. ready settles with the native registration, or null when service
workers are unavailable. A registration failure rejects it and publishes error
state. Do not await it before rendering or confuse it with a controlled page.
state reports status, workerUrl, scope, controller, installing,
waiting, active, updateAvailable and the complete error when present. Status is one of
registering, unsupported, registered, installing, waiting, active,
error or disposed. Native worker state fields are strings or null.
updateAvailable starts false and latches true after a successful full refresh
with known change evidence; later native lifecycle/error events preserve it,
and subscriptions replay it. Duplicate worker replies and broadcasts do not
produce another availability transition. It is page-local, not persisted in
DBOPFS, and remains separate from native worker activation.
subscribe(listener, {emitCurrent: true, signal} = {}) immediately replays the
current state by default and returns an unsubscribe function. Subsequent state
uses the existing Arcane event owner and PWA_STATE_EVENT (arcane.pwa.state).
update() requests the browser's normal worker update check on demand, not a
page reload or a forced resource refresh; failure is
observable in state and through its returned promise. dispose() removes
page-owned listeners and subscriptions without unregistering the persistent
worker or deleting stored data.