arcane-os 0.38.0 → 0.39.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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.39.0
4
+
5
+ - Add application-owned assistant display names to new Chat turns through
6
+ `aiName` and the `addAIMessage`/`addTurn` name option. Capture the selected
7
+ display name before asynchronous work and preserve complete nonblank names
8
+ in new ordinary saved records. Restored turns use their saved name or `AI`;
9
+ existing history is not rewritten, and display labels stay outside model
10
+ context and memory. Request-only `persist:false` behavior is unchanged.
11
+ - Add shared `applyUserSkin(skin,{root,body,cache})`, named light/dark palettes,
12
+ and classic `ThemePresentation.js` for cached appearance before styles load.
13
+ Cache presentation under the declared application ID without delaying page
14
+ rendering or profile readiness. Preserve unrelated body classes, custom theme
15
+ values and saved profile data. Scoped `data-arcane-palette` previews may select
16
+ their own `data-color-scheme` without changing the current page theme.
17
+ - Extend the shared Install presentation with an explicit Update action after
18
+ a complete successful cache refresh detects a server-declared modification
19
+ or a new resource in an established inventory. Preserve change evidence
20
+ through interrupted refreshes and worker restarts. Initial fills, unchanged
21
+ resources and failed refreshes do not announce an update. Reload occurs only
22
+ on the user's action and preserves ordinary unsaved-work cancellation.
23
+
3
24
  ## 0.38.0
4
25
 
5
26
  - Add `User.AI_name` as an application-owned complete string with an empty-string
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles share the theme, packaging, event, cancellation,
20
20
  and browser run contracts while retaining their selected application layout.
21
21
 
22
- This checkout defines the `0.38.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.39.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
 
@@ -1,10 +1,19 @@
1
1
  import Is from './dependencies/strong-type/index.js';
2
- import {createArcaneEventSource} from './event-manager.mjs';
2
+ import {arcaneEvents, createArcaneEventSource} from './event-manager.mjs';
3
3
 
4
4
  const is = new Is(false);
5
5
  export const PWA_INSTALL_STATE_EVENT = 'arcane.pwa.install.state';
6
6
  let sharedOwner = null;
7
7
  let mountedPrompt = null;
8
+ const registrationOwners = new Set();
9
+
10
+ // Internal connection to the registration owner, not a second copy of its state.
11
+ export function observePwaRegistration(owner) {
12
+ registrationOwners.add(owner);
13
+ return function releasePwaRegistration() {
14
+ registrationOwners.delete(owner);
15
+ };
16
+ }
8
17
 
9
18
  /** Capture native installation availability once per page, before loading UI. */
10
19
  export function getPwaInstall() {
@@ -14,7 +23,7 @@ export function getPwaInstall() {
14
23
  const owner = {
15
24
  get state() { return snapshot(); },
16
25
  get ready() { return ready; },
17
- subscribe, prompt, dismiss, dispose
26
+ subscribe, prompt, dismiss, update, dismissUpdate, dispose
18
27
  };
19
28
  const source = createArcaneEventSource(owner, {
20
29
  source: 'arcane.pwa.install', eventTypes: [PWA_INSTALL_STATE_EVENT]
@@ -32,6 +41,8 @@ export function getPwaInstall() {
32
41
  let deferredPrompt = null;
33
42
  let disposed = false;
34
43
  let dismissed = false;
44
+ let updateAvailable = false;
45
+ let updateDismissed = false;
35
46
  let status = isRunningAsApp() ? 'running' : 'waiting';
36
47
  let outcome = null;
37
48
  let error = null;
@@ -59,7 +70,7 @@ export function getPwaInstall() {
59
70
  return {
60
71
  status,
61
72
  available: storageReady && deferredPrompt !== null && !installed && !isRunningAsApp() && !disposed,
62
- installed, dismissed, outcome, error, storageError
73
+ installed, dismissed, updateAvailable, updateDismissed, outcome, error, storageError
63
74
  };
64
75
  }
65
76
 
@@ -237,6 +248,32 @@ export function getPwaInstall() {
237
248
  return snapshot();
238
249
  }
239
250
 
251
+ function onPwaState(event) {
252
+ if (event.source === 'arcane.pwa') receivePwaState(event.detail);
253
+ }
254
+
255
+ function receivePwaState(state) {
256
+ const pageUrl = globalThis.location?.href ?? globalThis.document?.baseURI ?? manifestUrl;
257
+ if (state.status === 'disposed' || state.updateAvailable !== true
258
+ || !state.scope || !pageUrl?.startsWith(state.scope) || updateAvailable) return;
259
+ updateAvailable = true;
260
+ publish(status, error);
261
+ }
262
+
263
+ function update() {
264
+ if (disposed || !updateAvailable) return false;
265
+ // Ordinary reload leaves native unload handlers and application draft ownership intact.
266
+ globalThis.location.reload();
267
+ return true;
268
+ }
269
+
270
+ function dismissUpdate() {
271
+ if (disposed) return snapshot();
272
+ updateDismissed = true;
273
+ publish(status, error);
274
+ return snapshot();
275
+ }
276
+
240
277
  function dispose() {
241
278
  if (disposed) return;
242
279
  deferredPrompt = null;
@@ -252,9 +289,11 @@ export function getPwaInstall() {
252
289
  observe(displayMode, 'change', onDisplayModeChange);
253
290
  observe(installedDisplayMode, 'change', onDisplayModeChange);
254
291
  observe(globalThis, 'pagehide', onPageHide);
292
+ listeners.push(arcaneEvents.subscribe('arcane.pwa.state', onPwaState));
255
293
  sharedOwner = owner;
256
294
  const storageTask = loadInstallStorage();
257
295
  const ready = restoreInstallation();
296
+ for (const registration of registrationOwners) receivePwaState(registration.state);
258
297
  if (installed) rememberInstallation();
259
298
  return owner;
260
299
  }
@@ -1,4 +1,5 @@
1
1
  import {createArcaneEventSource} from './event-manager.mjs';
2
+ import {observePwaRegistration} from './pwa-install.mjs';
2
3
 
3
4
  export {PWA_INSTALL_STATE_EVENT, getPwaInstall, mountPwaInstallPrompt} from './pwa-install.mjs';
4
5
 
@@ -41,6 +42,7 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
41
42
  installing: null,
42
43
  waiting: null,
43
44
  active: null,
45
+ updateAvailable: false,
44
46
  error: null
45
47
  };
46
48
 
@@ -60,6 +62,7 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
60
62
  installing: registration?.installing?.state ?? null,
61
63
  waiting: registration?.waiting?.state ?? null,
62
64
  active: registration?.active?.state ?? null,
65
+ updateAvailable: current.updateAvailable,
63
66
  error
64
67
  };
65
68
  source.dispatch(
@@ -220,6 +223,7 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
220
223
  return;
221
224
  }
222
225
  const result = await requestResourceRefresh(worker, record?.lastChecked ?? null);
226
+ receiveRefresh(result);
223
227
  if (!result.error && Number.isFinite(result.lastChecked) && result.lastChecked !== record?.lastChecked) {
224
228
  await storage.set('pwa', key, {lastChecked: result.lastChecked});
225
229
  }
@@ -239,7 +243,18 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
239
243
  refreshRegistration();
240
244
  }
241
245
 
246
+ function receiveRefresh(result) {
247
+ if (disposed || result.error || result.updateAvailable !== true || current.updateAvailable) {
248
+ return;
249
+ }
250
+ current.updateAvailable = true;
251
+ publish(current.status, current.error);
252
+ }
253
+
242
254
  function onWorkerMessage(event) {
255
+ if (event.data?.type === 'arcane.pwa.refreshed' && workers.has(event.source)) {
256
+ receiveRefresh(event.data);
257
+ }
243
258
  if (event.data?.type === 'arcane.pwa.capabilities' && event.data.refresh === true && workers.has(event.source)) {
244
259
  startResourceRefresh(event.source, event.data.cacheName);
245
260
  }
@@ -341,6 +356,7 @@ export function registerPwa({workerUrl = './arcane-sw.js', scope} = {}) {
341
356
  source.dispose();
342
357
  }
343
358
 
359
+ listeners.push(observePwaRegistration(owner));
344
360
  const ready = startRegistration();
345
361
  return owner;
346
362
  }
@@ -3013,7 +3013,7 @@
3013
3013
  "entrypoints": ["arcane-os/pwa"],
3014
3014
  "primaryImport": "arcane-os/pwa",
3015
3015
  "group": "Progressive web applications",
3016
- "summary": "Names the shared PWA lifecycle state event.",
3016
+ "summary": "Names the shared PWA lifecycle and page-local update availability state event.",
3017
3017
  "availability": "Browser lifecycle; importable without registration",
3018
3018
  "protocol": "Existing Arcane event owner and native service worker lifecycle",
3019
3019
  "normalization": "The exact event name is arcane.pwa.state."
@@ -3030,7 +3030,7 @@
3030
3030
  "summary": "Starts PWA registration and DBOPFS-backed page-load resource checks without blocking rendering, returning an observable lifecycle owner.",
3031
3031
  "availability": "Browser service workers; explicit unsupported state elsewhere",
3032
3032
  "protocol": "Native registration, updatefound, statechange and controllerchange events",
3033
- "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."
3033
+ "normalization": "Synchronous owner exposes ready, state, subscribe, update and dispose; current state replays by default. updateAvailable latches after a complete successful refresh with different parseable modification dates or a successfully checked addition to the prior durable inventory; initial fill, eviction repair, unchanged or unknown responses and partial failure do not announce updates. Pending evidence survives worker termination. update only checks the native worker, without reloading the page. Failures remain observable; disposal does not unregister the worker or delete saved data."
3034
3034
  },
3035
3035
  {
3036
3036
  "id": "pwa:PWA_INSTALL_STATE_EVENT",
@@ -3041,7 +3041,7 @@
3041
3041
  "entrypoints": ["arcane-os/pwa"],
3042
3042
  "primaryImport": "arcane-os/pwa",
3043
3043
  "group": "Progressive web applications",
3044
- "summary": "Names the shared native installation availability and choice state event.",
3044
+ "summary": "Names the shared native installation availability, choice and update-prompt state event.",
3045
3045
  "availability": "Browser installation lifecycle; importable without starting observation",
3046
3046
  "protocol": "Existing Arcane event owner and native browser installation events",
3047
3047
  "normalization": "The exact event name is arcane.pwa.install.state."
@@ -3055,10 +3055,10 @@
3055
3055
  "entrypoints": ["arcane-os/pwa"],
3056
3056
  "primaryImport": "arcane-os/pwa",
3057
3057
  "group": "Progressive web applications",
3058
- "summary": "Returns one shared page owner for native installation availability, prompt choices and session dismissal.",
3058
+ "summary": "Returns one shared page owner for native installation, prompt choices, independent update availability and explicit page reload.",
3059
3059
  "availability": "Browser native installation events; waiting state while no prompt is available",
3060
3060
  "protocol": "Native beforeinstallprompt, appinstalled and display-mode change events",
3061
- "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."
3061
+ "normalization": "Synchronous owner exposes ready, state, subscribe, prompt, dismiss, update, dismissUpdate and dispose; subscriptions replay current state. prompt invokes the native event directly in the user click and consumes it once; installation dismissal preserves that event. updateAvailable and updateDismissed are independent page-local fields. A late prompt reads current state from live registration owners; the generated bootstrap observes before registration. Explicit update synchronously calls ordinary location.reload and returns true, returns false when unavailable/disposed, and propagates native errors. Native unload cancellation leaves Update available. dismissUpdate returns state without a durable write. Installed/running status and earlier installation dismissal do not suppress updates. Native unload handling and application draft ownership remain unchanged."
3062
3062
  },
3063
3063
  {
3064
3064
  "id": "pwa:mountPwaInstallPrompt",
@@ -3069,7 +3069,7 @@
3069
3069
  "entrypoints": ["arcane-os/pwa"],
3070
3070
  "primaryImport": "arcane-os/pwa",
3071
3071
  "group": "Progressive web applications",
3072
- "summary": "Starts shared installation observation and mounts one initially hidden, themed installation suggestion without delaying application rendering.",
3072
+ "summary": "Starts shared installation/update observation and mounts one initially hidden, themed suggestion without delaying application rendering.",
3073
3073
  "availability": "Browser document and managed HTML import; resolves to null without a document",
3074
3074
  "protocol": "Shared native install owner and pwa-install.html component lifecycle",
3075
3075
  "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."
@@ -195,7 +195,7 @@
195
195
  ],
196
196
  "availability": "Browser and supported native WebViews",
197
197
  "transport": "HTMLImport + DOM; injected Arcane/provider modules where listed",
198
- "normalization": "UI/runtime state, honest model progress, complete timestamped transcript including ui_hidden stored records, user-facing structural arguments.message, nonblank all-ID tool-result restoration, collapsed complete raw inspection, ordered parallel-call replay validation, per-choice streamed/terminal complete-envelope correlation, complete nonstructural stream rendering, automatic repeated-formatting-mark removal from only the outbound speech-input copy while displayed and stored Markdown stays exact, generic visible failure outcomes with complete console diagnostics, atomic executed/declined/cancelled/not-executed result batches with one continuation, plural pending-call reload recovery, reentrant terminal-event ownership, and BFCache-preserving page lifecycle are normalized; destroy aborts observation and returns true once/false thereafter; AI/storage/media behavior remains mixed"
198
+ "normalization": "UI/runtime state, honest model progress, complete timestamped transcript including ui_hidden stored records, exact saved nonblank assistant display names with AI fallback rather than current-name inference, live session name capture with host.aiName display fallback, user-facing structural arguments.message, nonblank all-ID tool-result restoration, collapsed complete raw inspection, ordered parallel-call replay validation, per-choice streamed/terminal complete-envelope correlation, complete nonstructural stream rendering, automatic repeated-formatting-mark removal from only the outbound speech-input copy while displayed and stored Markdown stays exact, generic visible failure outcomes with complete console diagnostics, atomic executed/declined/cancelled/not-executed result batches with one continuation, plural pending-call reload recovery, reentrant terminal-event ownership, and BFCache-preserving page lifecycle are normalized; destroy aborts observation and returns true once/false thereafter; AI/storage/media behavior remains mixed"
199
199
  },
200
200
  {
201
201
  "file": "runtime/arcane/components/conversation-view.html",
@@ -650,11 +650,13 @@
650
650
  {
651
651
  "file": "runtime/arcane/components/pwa-install.html",
652
652
  "name": "pwa-install.html",
653
- "purpose": "Presents a dismissible browser installation action with floating or inline placement.",
653
+ "purpose": "Presents independent installation and explicit update/reload actions with floating or inline placement.",
654
654
  "methods": [
655
655
  "configure()",
656
656
  "install()",
657
657
  "dismiss()",
658
+ "update()",
659
+ "dismissUpdate()",
658
660
  "destroy()",
659
661
  "state",
660
662
  "ready"
@@ -671,9 +673,9 @@
671
673
  "arcane-os/event-manager",
672
674
  "arcane-os/logging"
673
675
  ],
674
- "availability": "Browser installation requires a browser-provided beforeinstallprompt event; otherwise hidden",
675
- "transport": "HTMLImport + DOM; shared PWA installation owner and browser-native prompt",
676
- "normalization": "Browser install availability and outcome supplied by the shared PWA owner"
676
+ "availability": "Installation requires beforeinstallprompt; a completed changed refresh independently enables the Update action",
677
+ "transport": "HTMLImport + DOM; shared PWA owner, browser-native install prompt and explicit ordinary page reload",
678
+ "normalization": "Complete labels and independent installation/update state; update dismissal is page-local, and native unload cancellation leaves Update available"
677
679
  },
678
680
  {
679
681
  "file": "runtime/arcane/components/record-timeline.html",
@@ -9,7 +9,7 @@
9
9
  "modules": [
10
10
  {"file":"runtime/arcane/entities/ApiModelRecord.js","exports":["default"],"availability":"cross-host","normalization":"normalized"},
11
11
  {"file":"runtime/arcane/entities/Calculation.js","exports":["default"],"availability":"cross-host","normalization":"normalized"},
12
- {"file":"runtime/arcane/entities/Chat.js","exports":["default"],"availability":"browser/native-webview","normalization":"Provider-facing recurring messages preserve raw structural protocol only for the one active matching continuation, then replace settled exchanges with complete ordinary visible content; transcript records remain sanitized and readable, including ui_hidden rows; ordered parallel structural calls require one exact nonblank result for every pending ID in one atomic turn, with rollback-safe persistence."},
12
+ {"file":"runtime/arcane/entities/Chat.js","exports":["default"],"availability":"browser/native-webview","normalization":"Provider-facing recurring messages preserve raw structural protocol only for the one active matching continuation, then replace settled exchanges with complete ordinary visible content; transcript records remain sanitized and readable, including ui_hidden rows. aiName defaults to empty; addAIMessage and addTurn capture an optional exact nonblank application-owned name on new assistant records only. Provider names do not establish display attribution; assistant display names stay out of messages and memory extraction, and existing records are never renamed or rewritten. ordered parallel structural calls require one exact nonblank result for every pending ID in one atomic turn, with rollback-safe persistence."},
13
13
  {"file":"runtime/arcane/entities/CommunicationMessage.js","exports":["communicationChannels","default"],"availability":"cross-host","normalization":"normalized"},
14
14
  {"file":"runtime/arcane/entities/CommunicationThread.js","exports":["default"],"availability":"cross-host","normalization":"normalized"},
15
15
  {"file":"runtime/arcane/entities/Document.js","exports":["default"],"availability":"browser/native-webview","normalization":"mixed"},
@@ -8,9 +8,9 @@
8
8
  "sdkVersion": "0.6.0",
9
9
  "protocol": "arcane/1"
10
10
  },
11
- "artifactCount": 88,
12
- "javascriptArtifactCount": 86,
13
- "esmExportCount": 356,
11
+ "artifactCount": 89,
12
+ "javascriptArtifactCount": 87,
13
+ "esmExportCount": 357,
14
14
  "artifacts": [
15
15
  {
16
16
  "file": "runtime/arcane/modules/AI.js",
@@ -1010,7 +1010,7 @@
1010
1010
  "availability": "Browser or native WebView with the projected ChatEntity, DBOPFS, and a configured chat function or normalized Arcane.ai surface",
1011
1011
  "protocol": "Existing ChatEntity/DBOPFS methods plus provider-neutral chat request/result records",
1012
1012
  "normalization": "Preserves complete ordinary visible recurring provider history, one active raw structural continuation, every sanitized timestamped transcript record, and arbitrary nonempty caller-owned chat file names; commits live context atomically; falls back from optional streaming to configured non-stream transport; carries per-turn generation options while owning lifecycle callbacks; exposes complete data callbacks; accepts terminal-only structural calls; correlates every observed per-choice call against the exact validated terminal envelope before callback/persistence; atomically settles every pending ID once; and keeps malformed stored records readable without inventing or rewriting messages.",
1013
- "surface": "Default/named `PersistentAIChatSession`, `createPersistentAIChatSession()`; `create()`, `ai`, `chatEntity`, `fileName`, `ready()`, `history()`, `transcript()`, `settleMemory()`, `send()`, and `stream()`."
1013
+ "surface": "Default/named `PersistentAIChatSession`, `createPersistentAIChatSession()`; `create()`, `ai`, `chatEntity`, `fileName`, `ready()`, `history()`, `transcript()`, `settleMemory()`, `open()`, `send()`, and `stream()`. Optional constructor aiName and getter/setter accept a string; open/send/stream capture it before their first await. A nonblank application name is preserved exactly on the copied terminal message and retained assistant transcript, with the actual assistant record timestamp when available. Complete raw providerResponse stays unchanged; configured model context and memory omit the application display name. Existing history is never renamed or rewritten, and persist:false remains operation-only."
1014
1014
  },
1015
1015
  {
1016
1016
  "file": "runtime/arcane/modules/PreferenceStore.js",
@@ -1292,25 +1292,37 @@
1292
1292
  "default",
1293
1293
  "disposeArcaneThemeBootstrap"
1294
1294
  ],
1295
- "summary": "Performs import-time Arcane theme loading and subscribes to native appearance changes.",
1295
+ "summary": "Reuses the shared presentation owner, loads authoritative preferences, and observes native appearance and existing canonical User readiness without blocking rendering or starting User storage.",
1296
1296
  "availability": "Browser/native hybrid",
1297
1297
  "protocol": "ThemeManager + host Arcane.events projected once through the per-realm globalThis.arcaneEvents authority",
1298
1298
  "normalization": "Theme state normalized; storage/native errors mixed.",
1299
- "surface": "`bootstrapArcaneTheme()`, `disposeArcaneThemeBootstrap()`, `arcaneThemeReady`, and default ready promise."
1299
+ "surface": "`bootstrapArcaneTheme()`, `disposeArcaneThemeBootstrap()`, `arcaneThemeReady`, and default ready promise. Shared bootstrap replays an already-ready User and disposes both appearance and user-ready subscriptions; presentation cache is nonauthoritative. Its module graph does not guarantee restoration before first paint; use the classic ThemePresentation entry after app-id and before CSS."
1300
1300
  },
1301
1301
  {
1302
1302
  "file": "runtime/arcane/modules/ThemeManager.js",
1303
1303
  "name": "ThemeManager.js",
1304
1304
  "kind": "esm",
1305
1305
  "exports": [
1306
+ "applyUserSkin",
1306
1307
  "default",
1307
1308
  "loadAndApplyTheme"
1308
1309
  ],
1309
1310
  "summary": "Loads, applies, previews, saves, resets, and synchronizes semantic Arcane themes.",
1310
1311
  "availability": "Browser/native hybrid",
1311
1312
  "protocol": "PreferenceStore, DOM, Arcane.appearance",
1312
- "normalization": "Theme values/events normalized; storage/native failures mixed.",
1313
- "surface": "default `ThemeManager`, `loadAndApplyTheme()`; scheme/custom/system APIs and `arcane-theme-change`."
1313
+ "normalization": "Theme values/events normalized; storage/native failures mixed. Scoped data-arcane-palette previews follow the page unless their own data-color-scheme is light or dark; this preview override leaves page mode unchanged, with chooser order/layout/toggle owned by the application.",
1314
+ "surface": "default `ThemeManager`, `loadAndApplyTheme()`, synchronous `applyUserSkin(skin,{root?,body?,cache=true}={})` delegated to ThemePresentation; complete skin classes preserve unrelated body classes, names default/warm/curious/hopeful/harmony/warrior follow light/dark/system, custom themes retain precedence, and data-arcane-palette scopes shared-color previews. Bare root retains generic neutral colors. Complete strings and numeric class text are cached without a User write. apply() records only applied appearance attributes and Theme-owned custom properties; preview() stays transient. Cache is reconciled by saved preferences and existing User readiness; no profile migration or first-paint guarantee from module-only use. Existing scheme/custom/system APIs and arcane-theme-change remain."
1315
+ },
1316
+ {
1317
+ "file": "runtime/arcane/modules/ThemePresentation.js",
1318
+ "name": "ThemePresentation.js",
1319
+ "kind": "classic-script",
1320
+ "exports": [],
1321
+ "summary": "Restores app-scoped presentation before CSS and owns complete skin-class application without starting profile storage.",
1322
+ "availability": "Browser / native WebView classic script",
1323
+ "protocol": "DOM and optional application-scoped localStorage",
1324
+ "normalization": "Synchronous presentation only; complete strings and numeric class text remain usable, cache failures preserve rendering, and authoritative profile and appearance loads reconcile the cache.",
1325
+ "surface": "No ESM exports; globalThis.arcaneThemePresentation with applyUserSkin(skin,{root?,body?,cache?}) returning palette|null, remember(fields,root?) returning boolean, restore(root?) returning cached record ({} on miss) or null when unavailable/failed, and reportError(message,error). Classic script runs after the existing app-id declaration and before CSS without type=module, async, or defer; first evaluation restores automatically. Cache fields: skin, palette, appearance:{colorScheme,density,reduceMotion,fontSize}, customProperties (Theme-owned applied CSS map or null). Complete Error goes to console.warn initially and shared logging after ThemeManager import. No User import, OPFS, model wait, profile snapshot, unrelated inline style capture, or migration; missing cache cannot supply an unloaded profile skin."
1314
1326
  },
1315
1327
  {
1316
1328
  "file": "runtime/arcane/modules/TimeGuard.js",
@@ -266,6 +266,33 @@ Browser storage eviction can remove an offline cache. A missing release cache
266
266
  entry falls back to the network, so offline availability still depends on the
267
267
  browser retaining the selected resources.
268
268
 
269
+ After a complete successful refresh, `updateAvailable` becomes true for an
270
+ already-open page when at least one cached replacement has different parseable
271
+ old and new `Last-Modified` dates, or a successfully checked resource was absent
272
+ from the prior durable offline inventory. Different modification dates are the
273
+ server's declaration of modification, not a comparison of response content.
274
+ Initial cache population, eviction repair of a previously selected URL, `304`,
275
+ and `200` responses with equal, missing or unreadable modification dates do not
276
+ establish an update. Such successful responses still follow the existing cache
277
+ replacement behavior. A host without meaningful modification dates cannot
278
+ announce same-URL changes through this contract.
279
+
280
+ The resource cache privately retains the pre-refresh inventory membership and
281
+ a pending-change flag while a refresh is incomplete. Before replacing a resource
282
+ with a different modification date, it also saves that resource's prior date.
283
+ Writes to this one metadata entry are ordered; independent resource fetches
284
+ remain concurrent. This lifecycle record is outside the resource inventory and
285
+ is never fetched from the server. A change becomes evidence only after the
286
+ resource's cache write succeeds. After interruption, the cached response's date
287
+ is compared with the retained prior date, so a failed later metadata write cannot
288
+ lose that evidence. Failed replacements alone establish no update. The record
289
+ survives worker termination and partial refresh failure and is cleared after
290
+ complete success.
291
+ Partial failure never announces a newly available update. The completed worker
292
+ reply and a scoped message to existing window clients carry the result; each
293
+ page owner keeps its own sticky availability state. A new page does not replay
294
+ an old completed notification.
295
+
269
296
  ## Updates and stored data
270
297
 
271
298
  An updated worker installs alongside the current worker, then waits for the
@@ -273,7 +300,7 @@ browser's normal activation boundary. Existing controlled pages retain their
273
300
  worker while they are open. Closing those pages allows activation; a refresh
274
301
  can leave overlapping document clients and keep the update waiting.
275
302
 
276
- The SDK does not call `skipWaiting`, claim the initial page, reload a page,
303
+ The SDK does not call `skipWaiting`, claim the initial page, automatically reload a page,
277
304
  restart a model or poll for updates. Worker activation preserves cached
278
305
  resources and the DBOPFS check history for the same app and registration scope.
279
306
  Saved application data and caches owned by other capabilities are untouched.
@@ -284,6 +311,15 @@ current cache-check protocol. Other cached resource bodies retain the normal
284
311
  check cadence. This transition follows native worker installation and
285
312
  activation without forcing a page reload.
286
313
 
314
+ When a complete resource refresh establishes an update, the shared prompt shows
315
+ **Update available** and **Update**, including in an installed or running app
316
+ and after an earlier installation suggestion was dismissed. Update dismissal
317
+ is separate and lasts for this page owner's lifetime. Clicking Update calls
318
+ ordinary `location.reload()` for the current page, with no extra SDK
319
+ confirmation or cache reset. Native `beforeunload` handling and application-owned
320
+ draft/session persistence retain their normal ownership. This is a page reload,
321
+ not a promise to preserve transient application state or an active chat.
322
+
287
323
  Switching a server from a packaged release to live development does not replace
288
324
  an already active release worker inside an open document. The same native
289
325
  worker lifecycle applies.
@@ -308,7 +344,8 @@ configuration, methods and events.
308
344
 
309
345
  The browser controls the URL-bar installation indicator and native prompt.
310
346
  The SDK cannot force either to appear. Without a captured
311
- `beforeinstallprompt`, the component remains hidden; that waiting state does
347
+ `beforeinstallprompt`, installation remains hidden; an available update can
348
+ still show. That waiting installation state does
312
349
  not establish that installation is unsupported. The browser may still be
313
350
  evaluating the app, may already have it installed, or may only support a
314
351
  manual browser-menu installation path.
@@ -354,12 +391,13 @@ does not infer installation support from the user-agent string. See
354
391
 
355
392
  Import `getPwaInstall` and `PWA_INSTALL_STATE_EVENT` from `arcane-os/pwa`.
356
393
  `getPwaInstall()` synchronously returns the shared page owner with `state`, `ready`,
357
- `subscribe`, `prompt`, `dismiss` and `dispose`. Call it early when owning a
394
+ `subscribe`, `prompt`, `dismiss`, `update`, `dismissUpdate` and `dispose`. Call it early when owning a
358
395
  separate install entry point so it can capture `beforeinstallprompt` before
359
396
  loading the UI. The generated bootstrap already does this through
360
397
  `mountPwaInstallPrompt()`.
361
398
 
362
- `state` contains `status`, `available`, `installed`, `dismissed`, `outcome`,
399
+ `state` contains `status`, `available`, `installed`, `dismissed`, `updateAvailable`,
400
+ `updateDismissed`, `outcome`,
363
401
  `error` and `storageError`.
364
402
  Status is `waiting`, `available`, `prompting`, `accepted`, `dismissed`,
365
403
  `installed`, `running`, `error` or `disposed`. `available` means a native event
@@ -371,6 +409,15 @@ or restoration of that app's saved installation record.
371
409
  choice. `error` carries the complete prompt error, or `null`; `storageError`
372
410
  carries the complete DBOPFS read or write error, or `null`.
373
411
 
412
+ `updateAvailable` starts false and remains true after this owner observes a
413
+ completed changed refresh for the current page's worker scope through
414
+ `arcane.pwa.state`. `updateDismissed` starts false and is independent of
415
+ installation dismissal and saved installation status. Neither field changes
416
+ native installation `status` or `available`. A late-created prompt reads the
417
+ current state of live registration owners before returning; earlier creation
418
+ is still needed to capture a browser installation event that has not yet fired.
419
+ The generated bootstrap establishes observation before registering the worker.
420
+
374
421
  `ready` resolves to the state after the initial DBOPFS read settles. Native
375
422
  events are captured synchronously while this read runs. Only installation
376
423
  availability waits for it; page rendering, component loading and worker
@@ -425,6 +472,15 @@ newly observed installation, and a confirmed installation's pending write is
425
472
  retained through owner disposal. A write failure remains observable without
426
473
  making the current installed session eligible again.
427
474
 
475
+ `update()` synchronously calls the current page's ordinary `location.reload()`
476
+ and returns `true` when an update is available, including after dismissal.
477
+ It returns `false` when unavailable or disposed. A native reload error propagates
478
+ to the caller. It leaves availability unchanged so a cancelled native unload
479
+ does not discard the update. `dismissUpdate()` hides the update suggestion for
480
+ this page owner and returns the current state, without changing installation
481
+ dismissal or writing a durable record. A disposed owner returns its current
482
+ state unchanged. Call `update()` only from the user's explicit Update action.
483
+
428
484
  Running in an app display mode publishes `running`. These states
429
485
  do not establish offline readiness. On Android, `appinstalled` can arrive
430
486
  before WebAPK creation finishes. See the
@@ -494,14 +550,20 @@ workers are unavailable. A registration failure rejects it and publishes error
494
550
  state. Do not await it before rendering or confuse it with a controlled page.
495
551
 
496
552
  `state` reports `status`, `workerUrl`, `scope`, `controller`, `installing`,
497
- `waiting`, `active` and the complete `error` when present. Status is one of
553
+ `waiting`, `active`, `updateAvailable` and the complete `error` when present. Status is one of
498
554
  `registering`, `unsupported`, `registered`, `installing`, `waiting`, `active`,
499
555
  `error` or `disposed`. Native worker state fields are strings or `null`.
556
+ `updateAvailable` starts false and latches true after a successful full refresh
557
+ with known change evidence; later native lifecycle/error events preserve it,
558
+ and subscriptions replay it. Duplicate worker replies and broadcasts do not
559
+ produce another availability transition. It is page-local, not persisted in
560
+ DBOPFS, and remains separate from native worker activation.
500
561
 
501
562
  `subscribe(listener, {emitCurrent: true, signal} = {})` immediately replays the
502
563
  current state by default and returns an unsubscribe function. Subsequent state
503
564
  uses the existing Arcane event owner and `PWA_STATE_EVENT` (`arcane.pwa.state`).
504
- `update()` requests the browser's normal update check on demand; failure is
565
+ `update()` requests the browser's normal worker update check on demand, not a
566
+ page reload or a forced resource refresh; failure is
505
567
  observable in state and through its returned promise. `dispose()` removes
506
568
  page-owned listeners and subscriptions without unregistering the persistent
507
569
  worker or deleting stored data.
@@ -82,7 +82,7 @@ appropriate.
82
82
  | [`modal.html`](#modalhtml) | Generic modal with population, configurable user dismissal, actions, and concurrent task execution. | `configure()`<br>`populate()`<br>`open()`<br>`close()`<br>`runTasks()`<br>`destroy()`<br>`running`<br>`opened` | `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
+ | [`pwa-install.html`](#pwa-installhtml) | Presents independent installation and explicit update/reload actions with floating or inline placement. | `configure()`<br>`install()`<br>`dismiss()`<br>`update()`<br>`dismissUpdate()`<br>`destroy()`<br>`state`<br>`ready` | `pwa-install-ready`<br>`pwa-install-change`<br>`pwa-install-dismissed` | Complete labels and independent installation/update state; native unload cancellation leaves Update available |
86
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 |
87
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 |
88
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 |
@@ -351,6 +351,15 @@ restored or new message uses a separate semantic `<time>` element at the card's
351
351
  lower-right with an ISO `datetime`, full local title, and local 24-hour `HH:MM`
352
352
  text.
353
353
 
354
+ Restored assistant cards use the record's exact nonblank string `name`, or
355
+ `AI` when absent. The current profile/model/voice name never renames older
356
+ messages. For live session replies, the card starts with `session.aiName`
357
+ when nonblank, otherwise the existing `host.aiName` display value; the terminal
358
+ result aligns it with the name captured by the session for that operation.
359
+ Set the session/entity name as well as the live component label when the
360
+ application intends new saved turns to retain that attribution. The shared
361
+ component does not persist the profile name or choose a naming policy.
362
+
354
363
  Assistant structural calls render their nonempty `function.arguments.message`
355
364
  as ordinary visible progress. The exact call name and argument string are
356
365
  retained inside a collapsed `Tool call details` inspection surface. Displaying
@@ -1257,9 +1266,10 @@ Events: `preferences-form-ready`, `preferences-change`, `preferences-submit`, `p
1257
1266
 
1258
1267
  ### Overview
1259
1268
 
1260
- A compact installation suggestion using the shared [PWA installation owner](pwa.md).
1261
- The default floating panel appears near the top right only when the browser offers
1262
- installation. It has an Install action and an explicit close button, takes no
1269
+ A compact installation and update suggestion using the shared [PWA owner](pwa.md).
1270
+ The default floating panel appears near the top right when the browser offers
1271
+ installation or a completed resource refresh establishes an update. It has an
1272
+ Install or Update action and an explicit close button, takes no
1263
1273
  focus automatically, and has no dismissal timer. It uses the Arcane theme and
1264
1274
  primitives, wraps complete labels and errors, and scrolls its own content when
1265
1275
  the available height is limited. The parent page loads `ThemeBootstrap.js` to
@@ -1268,7 +1278,8 @@ apply the user's appearance preferences.
1268
1278
  ### Public surface
1269
1279
 
1270
1280
  `configure({appName, installLabel, closeLabel, promptingLabel, description,
1271
- presentation})` updates display configuration and returns its current record.
1281
+ updateTitle, updateLabel, updateDescription, updateCloseLabel, presentation})`
1282
+ updates display configuration and returns its current record.
1272
1283
  Labels remain complete strings. `appName` initially uses `data-app-name` or
1273
1284
  `this app`; the default button label is `Install`. `presentation` is `floating`
1274
1285
  by default or `inline`, initially read from `data-presentation`. Inline placement
@@ -1282,6 +1293,20 @@ An explicit request's complete failure message remains visible until dismissed;
1282
1293
  the method rejects with the same error. The browser controls actual installation.
1283
1294
  Acceptance hides the suggestion without claiming installation has completed.
1284
1295
 
1296
+ When an update is available, the same panel shows `Update available`, `Update`,
1297
+ `Reload this page to use the updated app.`, and the close label `Close update
1298
+ suggestion`. Each string has its matching configuration key. This presentation
1299
+ takes precedence over installation, including for installed/running apps and
1300
+ previously dismissed installation suggestions.
1301
+
1302
+ `update()` calls the shared owner's ordinary `location.reload()` synchronously,
1303
+ returning true when available and false when unavailable or destroyed. It leaves
1304
+ availability intact if a native unload handler cancels navigation. Native reload
1305
+ errors propagate; the button observes them through developer diagnostics. There
1306
+ is no automatic reload, extra SDK confirmation, cache reset, or guarantee of
1307
+ preserving transient application state. `dismissUpdate()` hides only the update
1308
+ suggestion for this page owner and returns true, or false after destruction.
1309
+
1285
1310
  `dismiss()` closes the component. Floating dismissal also uses the owner's
1286
1311
  session dismissal; an explicit inline component ignores that shared dismissal
1287
1312
  and closes only its own instance. Closing retains any unused native prompt at
@@ -1290,22 +1315,28 @@ disposes its event source, hides its host, and marks `ready` false; it does not
1290
1315
  dispose the shared owner or change browser installation state. Both methods
1291
1316
  return true while active and false after destruction; `destroy()` is idempotent.
1292
1317
 
1293
- The readonly `state` property returns the shared owner's current install-state
1318
+ The readonly `state` property returns the shared owner's current install/update-state
1294
1319
  record; `ready` becomes true after methods and the state subscription are attached.
1295
1320
  `pwa-install-ready` carries `{ready, state}`; `pwa-install-change` carries
1296
1321
  `{state, visible}` for each observed owner update; `pwa-install-dismissed` carries
1297
- `{presentation, state}`. These follow the canonical event projection contract.
1322
+ `{presentation, state}`, with `kind: 'update'` for update dismissal. These follow
1323
+ the canonical event projection contract.
1298
1324
 
1299
1325
  ### Availability and normalization
1300
1326
 
1301
1327
  The component requires HTMLImport and a DOM renderer. Native installation
1302
1328
  availability comes from the browser's `beforeinstallprompt` event through
1303
- `getPwaInstall()`. Without an available event, the suggestion stays hidden;
1329
+ `getPwaInstall()`. Without an available event, the installation suggestion stays hidden;
1304
1330
  absence does not identify why installation is unavailable. Installed-app events,
1305
1331
  accepted prompts, and an already running installed display mode hide it.
1306
1332
  The shared SDK owner persists browser-reported installation in app-scoped
1307
1333
  DBOPFS. Both floating and inline controls stay hidden while that record is
1308
1334
  loading and remain hidden on later visits when installation is remembered.
1335
+ Those installation conditions do not hide an available update. Update state
1336
+ comes from the existing page-scoped registration owner, including a current-state
1337
+ read when the prompt is created after the refresh. A new page does not replay a
1338
+ completed update from an earlier page. Update dismissal is independent of saved
1339
+ installation state and performs no durable write.
1309
1340
  Acceptance alone does not save installation. The component's `ready` means
1310
1341
  its methods and subscription are attached; storage completion belongs to the
1311
1342
  shared owner's `ready` promise and does not delay page rendering.
@@ -78,8 +78,9 @@ call and its matching results remain raw only through their one active provider
78
78
  continuation. Once that continuation settles, `messages` replaces the protocol
79
79
  with complete ordinary visible call, public result, and assistant content.
80
80
  `transcript` returns the narrow human-readable projection owned by the durable
81
- storage boundary. User and assistant records contain only role, complete
82
- visible content, and their real timestamp. A visible tool record may
81
+ storage boundary. User and assistant records contain role, complete
82
+ visible content, and their real timestamp. An assistant record may also carry
83
+ its application-owned display `name`. A visible tool record may
83
84
  additionally carry its public name and plain result status, while its `content`
84
85
  comes only from the tool call's required user-facing `message`. System prompts,
85
86
  reasoning, provider-extension fields, memory flags, raw tool calls, call IDs,
@@ -90,6 +91,15 @@ One complete nonblank assistant record is also a durable conversation entry, so
90
91
  a model-authored opening can be stored and survive maintenance before the first
91
92
  ordinary user turn. No synthetic user record is required or written.
92
93
 
94
+ `aiName` defaults to an empty string. `addAIMessage(text,{name=chat.aiName,...})`
95
+ and `addTurn({name=chat.aiName,...})` capture that application-owned name on the
96
+ new assistant record. A nonblank string is preserved exactly; blank or
97
+ nonstring names are omitted. Provider `message.name` never establishes that
98
+ display attribution. The name is transcript-only metadata: `messages` and
99
+ memory extraction omit it, including during a pending tool continuation.
100
+ Changing `aiName` affects later messages only; loading existing records does
101
+ not rename or rewrite them.
102
+
93
103
  An assistant record may open an ordered array of structural calls with unique
94
104
  IDs in transient provider state. Until every pending ID receives exactly one
95
105
  matching nonblank `role:'tool'` result in the same active session, another