arcane-os 0.37.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,38 @@
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
+
24
+ ## 0.38.0
25
+
26
+ - Add `User.AI_name` as an application-owned complete string with an empty-string
27
+ default, ordinary getter/setter, profile loading and `updateExplicit` support.
28
+ Preserve older saved profiles and restore the hydrated baseline after a failed
29
+ save; no existing history or provider/model selection is rewritten.
30
+ - Distinguish saved conversation speakers in file previews using the shared
31
+ action/action-text colors for user cards and surface/text colors for assistant
32
+ cards. Assistant headings use the complete saved nonblank string name or `AI`,
33
+ without a role suffix. Preserve other role labels, timestamps, status, complete
34
+ content and persistence. Capturing names on new Chat turns is separate work.
35
+
3
36
  ## 0.37.0
4
37
 
5
38
  - Add opt-in `markdown-editor` fit layout and preview following through
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.37.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",
@@ -421,7 +421,7 @@
421
421
  ],
422
422
  "availability": "Browser and supported native WebViews",
423
423
  "transport": "HTMLImport + DOM; injected Arcane/provider modules where listed",
424
- "normalization": "Selection/filter state and neutral previews normalized; storage/provider behavior mixed. Initial directoryFilter and setDirectoryFilter(fn|null) use false-only exclusion before built-in DBOPFS key/metadata reads; data-hidden-prefixes also applies to directories unless data-show-hidden is present. Existing file predicates preserve their first argument and receive optional directory/fileName/path/layout context. previewDescriptor(original File,{directory,fileName,extension,path,mimeType,signal}) may asynchronously return text/markdown/html/json content, conversation messages with role/content/real timestamp and optional name/status, collection items, or image/audio/pdf/download content as Blob/File or a lazy ({signal}) loader. Downloads require fileName; descriptors accept title and images accept alt. Lazy items load only on explicit request and retain item-local retry. Close, replacement, and destroy abort the preview signal, suppress late output, remove iframe srcdoc, stop audio, and revoke all owned object URLs. HTML preserves the exact string in iframe srcdoc; HTML/Markdown MIME fallback supports extensionless files. Nullish descriptors preserve previewTransform for JSON-family files and native previews; untransformed JSONL/NDJSON retains complete raw text, and other binary files download the original File."
424
+ "normalization": "Selection/filter state and neutral previews normalized; storage/provider behavior mixed. Initial directoryFilter and setDirectoryFilter(fn|null) use false-only exclusion before built-in DBOPFS key/metadata reads; data-hidden-prefixes also applies to directories unless data-show-hidden is present. Existing file predicates preserve their first argument and receive optional directory/fileName/path/layout context. previewDescriptor(original File,{directory,fileName,extension,path,mimeType,signal}) may asynchronously return text/markdown/html/json content, conversation messages with role/content/real timestamp and optional name/status, collection items, or image/audio/pdf/download content as Blob/File or a lazy ({signal}) loader. Assistant conversation labels use the exact nonblank string name or AI without a role suffix; other role labels retain existing name/role presentation. User and assistant cards have distinct theme backgrounds and preserve data-role. Downloads require fileName; descriptors accept title and images accept alt. Lazy items load only on explicit request and retain item-local retry. Close, replacement, and destroy abort the preview signal, suppress late output, remove iframe srcdoc, stop audio, and revoke all owned object URLs. HTML preserves the exact string in iframe srcdoc; HTML/Markdown MIME fallback supports extensionless files. Nullish descriptors preserve previewTransform for JSON-family files and native previews; untransformed JSONL/NDJSON retains complete raw text, and other binary files download the original File."
425
425
  },
426
426
  {
427
427
  "file": "runtime/arcane/components/header.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"},
@@ -19,7 +19,7 @@
19
19
  {"file":"runtime/arcane/entities/Preference.js","exports":["default","preferenceSchema"],"availability":"cross-host","normalization":"normalized"},
20
20
  {"file":"runtime/arcane/entities/TerminalSession.js","exports":["default","terminalShells"],"availability":"cross-host","normalization":"normalized"},
21
21
  {"file":"runtime/arcane/entities/Theme.js","exports":["arcaneDarkThemeTokens","arcaneLightThemeTokens","default","themeColorToHex","themeTokens"],"availability":"cross-host/dom-when-applied","normalization":"normalized/mixed"},
22
- {"file":"runtime/arcane/entities/User.js","exports":["default","profileUpdateFromSearchParams"],"availability":"browser/native-webview","normalization":"updateExplicit accepts existing partial/JSON updates or an awaited fresh-profile callback under the existing serialized write owner; callback null skips saving and returns the fresh profile, while failed writes restore the fresh baseline. Timestamp setters persist partial fields through that owner. profileUpdateFromSearchParams purely maps nonempty supplied URLSearchParams values without further decoding, trimming, or persistence."},
22
+ {"file":"runtime/arcane/entities/User.js","exports":["default","profileUpdateFromSearchParams"],"availability":"browser/native-webview","normalization":"AI_name is a persisted string preference with an empty-string default that preserves complete supplied text alongside AI_personality; loading older records does not rewrite them. updateExplicit accepts existing partial/JSON updates or an awaited fresh-profile callback under the existing serialized write owner; callback null skips saving and returns the fresh profile, while failed writes restore the fresh baseline. Timestamp setters persist partial fields through that owner. profileUpdateFromSearchParams purely maps nonempty supplied URLSearchParams values without further decoding, trimming, or persistence."},
23
23
  {"file":"runtime/arcane/entities/Weather.js","exports":["WeatherDay","WeatherLocation","WeatherObservation","WeatherSnapshot"],"availability":"cross-host","normalization":"normalized"}
24
24
  ]
25
25
  }
@@ -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.