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.
@@ -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
@@ -844,7 +853,11 @@ Each descriptor accepts an optional `title`. Supported shapes are:
844
853
  | `image`, `audio`, `pdf`, `download` | `content`: a `Blob` or `File`, or a function receiving `{signal}` and resolving to one |
845
854
 
846
855
  Conversation `role` and complete Markdown `content` are strings. Optional
847
- `name` and `status` are displayed alongside the role and time. Valid supplied
856
+ assistant `name` is displayed exactly when it is a nonblank string; otherwise
857
+ the assistant label is `AI`, without a role suffix. User and other role labels
858
+ retain their supplied name alongside the role. User and assistant cards use
859
+ distinct shared-theme backgrounds, and each card retains its original
860
+ `data-role`. Optional `status` is displayed beside the time. Valid supplied
848
861
  timestamps are displayed in local time with their real ISO datetime; absent
849
862
  timestamps display `Time unavailable`, and an unrecognized supplied timestamp
850
863
  is displayed as supplied. No timestamp is invented.
@@ -1253,9 +1266,10 @@ Events: `preferences-form-ready`, `preferences-change`, `preferences-submit`, `p
1253
1266
 
1254
1267
  ### Overview
1255
1268
 
1256
- A compact installation suggestion using the shared [PWA installation owner](pwa.md).
1257
- The default floating panel appears near the top right only when the browser offers
1258
- 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
1259
1273
  focus automatically, and has no dismissal timer. It uses the Arcane theme and
1260
1274
  primitives, wraps complete labels and errors, and scrolls its own content when
1261
1275
  the available height is limited. The parent page loads `ThemeBootstrap.js` to
@@ -1264,7 +1278,8 @@ apply the user's appearance preferences.
1264
1278
  ### Public surface
1265
1279
 
1266
1280
  `configure({appName, installLabel, closeLabel, promptingLabel, description,
1267
- presentation})` updates display configuration and returns its current record.
1281
+ updateTitle, updateLabel, updateDescription, updateCloseLabel, presentation})`
1282
+ updates display configuration and returns its current record.
1268
1283
  Labels remain complete strings. `appName` initially uses `data-app-name` or
1269
1284
  `this app`; the default button label is `Install`. `presentation` is `floating`
1270
1285
  by default or `inline`, initially read from `data-presentation`. Inline placement
@@ -1278,6 +1293,20 @@ An explicit request's complete failure message remains visible until dismissed;
1278
1293
  the method rejects with the same error. The browser controls actual installation.
1279
1294
  Acceptance hides the suggestion without claiming installation has completed.
1280
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
+
1281
1310
  `dismiss()` closes the component. Floating dismissal also uses the owner's
1282
1311
  session dismissal; an explicit inline component ignores that shared dismissal
1283
1312
  and closes only its own instance. Closing retains any unused native prompt at
@@ -1286,22 +1315,28 @@ disposes its event source, hides its host, and marks `ready` false; it does not
1286
1315
  dispose the shared owner or change browser installation state. Both methods
1287
1316
  return true while active and false after destruction; `destroy()` is idempotent.
1288
1317
 
1289
- 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
1290
1319
  record; `ready` becomes true after methods and the state subscription are attached.
1291
1320
  `pwa-install-ready` carries `{ready, state}`; `pwa-install-change` carries
1292
1321
  `{state, visible}` for each observed owner update; `pwa-install-dismissed` carries
1293
- `{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.
1294
1324
 
1295
1325
  ### Availability and normalization
1296
1326
 
1297
1327
  The component requires HTMLImport and a DOM renderer. Native installation
1298
1328
  availability comes from the browser's `beforeinstallprompt` event through
1299
- `getPwaInstall()`. Without an available event, the suggestion stays hidden;
1329
+ `getPwaInstall()`. Without an available event, the installation suggestion stays hidden;
1300
1330
  absence does not identify why installation is unavailable. Installed-app events,
1301
1331
  accepted prompts, and an already running installed display mode hide it.
1302
1332
  The shared SDK owner persists browser-reported installation in app-scoped
1303
1333
  DBOPFS. Both floating and inline controls stay hidden while that record is
1304
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.
1305
1340
  Acceptance alone does not save installation. The component's `ready` means
1306
1341
  its methods and subscription are attached; storage completion belongs to the
1307
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
@@ -274,6 +284,29 @@ Owns persisted user preference/profile fields, explicit-preference updates,
274
284
  fresh reads, saves, and JSON projection. It installs `window.user` after its
275
285
  DBLS/DBOPFS lifecycle and emits `user-entity-loaded`.
276
286
 
287
+ ### AI_name
288
+
289
+ Saved AI-name preference alongside `AI_personality`. It is a string with an
290
+ empty-string default and preserves the complete supplied text, including
291
+ Unicode, whitespace, and an explicitly empty value. The property setter follows
292
+ the same persistence setting as `AI_personality`; non-string assignments throw
293
+ `Error('AI_name must be string')`.
294
+
295
+ `explicit`, `toJSON()`, `load()`, `refresh()`, and `updateExplicit()` include
296
+ this field through the existing profile schema. Loading an older record that
297
+ omits it leaves a new entity's empty default in memory and does not rewrite the
298
+ stored record. Partial updates that omit it retain the current value.
299
+
300
+ ```javascript
301
+ await user.updateExplicit(
302
+ {AI_name: 'Captain Moonboots'}
303
+ );
304
+ ```
305
+
306
+ The application owns naming choices and any mapping to its AI display name;
307
+ this preference changes neither the human `username` nor chat history. No
308
+ automatic migration or naming policy runs when it is loaded or saved.
309
+
277
310
  ### updateExplicit(update)
278
311
 
279
312
  Accepts an ordinary partial profile object, a JSON string, or a function
@@ -125,8 +125,9 @@ own asynchronous work, cancellation, and backpressure.
125
125
  | [`SystemToolRegistry.js`](#systemtoolregistryjs) | esm | Registers validated command builders and constructs command strings without executing them. | Cross-host | Fully normalized definitions/quoting. |
126
126
  | [`TerminalClient.js`](#terminalclientjs) | esm | Maps native terminal sessions and Arcane events into an EventTarget client. | Native bridge | Client events/state normalized; native result/error mixed. |
127
127
  | [`TerminalCommandRegistry.js`](#terminalcommandregistryjs) | esm | Routes parsed command lines to injected handlers and provides definitions/completions. | Cross-host | Parsing/routing normalized; handler result/error preserved. |
128
- | [`ThemeBootstrap.js`](#themebootstrapjs) | esm | Performs import-time Arcane theme loading and subscribes to native appearance changes. | Browser/native hybrid | Theme state normalized; storage/native errors mixed. |
128
+ | [`ThemeBootstrap.js`](#themebootstrapjs) | esm | Loads authoritative appearance preferences and observes existing User readiness without blocking rendering. | Browser/native hybrid | Theme state normalized; storage/native errors mixed. |
129
129
  | [`ThemeManager.js`](#thememanagerjs) | esm | Loads, applies, previews, saves, resets, and synchronizes semantic Arcane themes. | Browser/native hybrid | Theme values/events normalized; storage/native failures mixed. |
130
+ | [`ThemePresentation.js`](#themepresentationjs) | classic-script | Restores app-scoped presentation before CSS and owns complete skin-class application. | Browser / native WebView classic script | Synchronous presentation only; saved profile and preferences remain authoritative. |
130
131
  | [`TimeGuard.js`](#timeguardjs) | esm | Persists and evaluates clock rollback and grace-period state. | Browser / native WebView | Time decisions normalized; storage lifecycle mixed. |
131
132
  | [`ToolCallRouter.js`](#toolcallrouterjs) | esm | Parses OpenAI-style tool calls and dispatches complete or streamed calls to injected handlers. | Cross-host | Argument records validated; handler results returned or all-settled. |
132
133
  | [`uPlot.iife.min.js`](#uplotiifeminjs) | classic-script | Vendored uPlot chart constructor and rendering runtime. | Browser vendor script | Vendor-native. |
@@ -3013,13 +3014,24 @@ semantics; it does not define a new storage protocol.
3013
3014
  Exact exports: `PersistentAIChatSession`, `createPersistentAIChatSession`, and
3014
3015
  `default`.
3015
3016
 
3016
- Constructor and factory options are `{ai,chat,chatEntity,chatFileName,
3017
+ Constructor and factory options are `{ai,aiName,chat,chatEntity,chatFileName,
3017
3018
  contextBuilder,loadExisting,memory,request,responseLength,systemPrompt}`. Public members are
3018
3019
  static `create()`, getters `ai`, `chatEntity`, and `fileName`, and `ready()`,
3019
3020
  `history()`, `transcript()`, `settleMemory()`, `open(input)`, `send(input)`, and
3020
3021
  `stream(input,handlers)`.
3021
3022
  `ready()` waits for initialization and resolves the same session instance.
3022
3023
 
3024
+ Optional `aiName` is an application-owned string, also exposed through the
3025
+ session's getter/setter. `open()`, `send()`, and `stream()` capture it at operation
3026
+ entry before awaiting readiness, memory, or the provider, so later name changes
3027
+ apply only to subsequent operations. A nonblank name is preserved exactly on
3028
+ the copied terminal `message.name` and the retained assistant transcript record;
3029
+ the terminal copy carries the assistant record's real timestamp when one exists.
3030
+ Empty or whitespace-only names omit this display field. The complete raw
3031
+ `providerResponse` stays unchanged. Configured model context and memory inputs
3032
+ omit the application display name. Existing stored history is never renamed or
3033
+ rewritten when `aiName` changes; `persist:false` remains operation-only.
3034
+
3023
3035
  `open({message:{content,persist:false?},request?,signal?})` performs one
3024
3036
  application-authored bootstrap request only when the retained conversation is
3025
3037
  otherwise empty. The bootstrap is never committed. After a complete nonblank
@@ -3049,8 +3061,9 @@ system prompt and every complete ordinary visible committed turn. Only a
3049
3061
  currently unresolved structural-call tail remains raw for its matching active
3050
3062
  continuation. `transcript()` returns the sanitized human-readable ChatEntity
3051
3063
  projection.
3052
- User and assistant records retain only role, complete visible content, and the
3053
- real timestamp. Tool records retain only role, the required user-facing
3064
+ User and assistant records retain role, complete visible content, and the
3065
+ real timestamp, with an optional exact nonblank application display name on
3066
+ assistant records. Tool records retain only role, the required user-facing
3054
3067
  `message` as content, and optional public `name` and result `status`.
3055
3068
 
3056
3069
  `stream()` accepts the same input as `send()` and optional
@@ -3755,7 +3768,12 @@ console.log(Object.keys(module));
3755
3768
 
3756
3769
  ### Overview
3757
3770
 
3758
- Performs import-time Arcane theme loading and subscribes to native appearance changes.
3771
+ Performs import-time Arcane theme loading, reuses the shared presentation
3772
+ owner, and subscribes to native appearance changes and
3773
+ the canonical user's readiness event. Rendering does not wait for profile
3774
+ loading. A ready user is replayed immediately; its saved skin supersedes the
3775
+ presentation cache. Bootstrap observes the existing User lifecycle rather than
3776
+ creating a profile or migrating saved data.
3759
3777
 
3760
3778
  ### Public surface
3761
3779
 
@@ -3764,6 +3782,16 @@ Performs import-time Arcane theme loading and subscribes to native appearance ch
3764
3782
  Exact exports: `arcaneThemeReady`, `bootstrapArcaneTheme`, `default`, and
3765
3783
  `disposeArcaneThemeBootstrap`.
3766
3784
 
3785
+ `disposeArcaneThemeBootstrap()` removes both bootstrap-owned subscriptions.
3786
+ The cache is a presentation hint, not the authority for preferences or user
3787
+ data. Preference loading and User readiness independently reconcile it; cache
3788
+ failures are logged without blocking the page.
3789
+
3790
+ Module evaluation follows its import graph, so importing ThemeBootstrap alone
3791
+ does not establish restoration before the first paint. Use the classic
3792
+ [ThemePresentation head entry](#themepresentationjs) before CSS for that early
3793
+ restoration. Neither entry imports User or starts its storage lifecycle.
3794
+
3767
3795
  ### Availability and normalization
3768
3796
 
3769
3797
  **Browser/native hybrid.** Theme state normalized; storage/native errors mixed. Transport: ThemeManager + Arcane.events. [Deep protocol details](protocols.md).
@@ -3784,9 +3812,56 @@ Loads, applies, previews, saves, resets, and synchronizes semantic Arcane themes
3784
3812
 
3785
3813
  ### Public surface
3786
3814
 
3787
- default `ThemeManager`, `loadAndApplyTheme()`; scheme/custom/system APIs and `arcane-theme-change`.
3788
-
3789
- Exact exports: `default`, `loadAndApplyTheme`.
3815
+ default `ThemeManager`, `loadAndApplyTheme()`, `applyUserSkin()`;
3816
+ scheme/custom/system APIs and `arcane-theme-change`.
3817
+
3818
+ Exact exports: `default`, `loadAndApplyTheme`, `applyUserSkin`.
3819
+
3820
+ `applyUserSkin(skin,{root?,body?,cache=true}={})` synchronously applies the
3821
+ application's saved skin classes without replacing unrelated body classes.
3822
+ The default root is the current document element; the default body belongs to
3823
+ that root's document. It accepts the
3824
+ existing string/number skin values and whitespace-separated class lists;
3825
+ unknown application classes remain usable. It selects the shared named
3826
+ palette on the root through `data-user-skin`, returning its name or `null`
3827
+ when there is no applicable skin/root. The palette selection does not rewrite
3828
+ the supplied skin or save a User record. Set `cache:false` for a presentation
3829
+ that should not be remembered across navigation.
3830
+ This export delegates to the same owner installed by `ThemePresentation.js`.
3831
+ Falsy skin values retain the current presentation. Complete supplied strings
3832
+ remain exact in the presentation cache; numbers use their complete class-text
3833
+ representation, leaving the profile value unchanged. If the body does not yet
3834
+ exist, root selection happens immediately and the latest complete class list
3835
+ is applied once the body is available.
3836
+
3837
+ The shared `theme.css` owns the default, warm, curious, hopeful, harmony, and
3838
+ warrior palettes. Each works with explicit light/dark or system appearance.
3839
+ `layout.css` imports that same owner. Explicit custom themes retain precedence
3840
+ through `data-arcane-skin="custom"` and their inline tokens.
3841
+ The bare root retains the generic neutral light/dark baseline; the prior
3842
+ layout default palette is selected explicitly through `data-user-skin="default"`,
3843
+ `body.default` compatibility, or the default swatch.
3844
+
3845
+ A scoped `data-arcane-palette="warm"` element exposes the same palette variables
3846
+ for a chooser preview without changing the page's selection. Use any of the
3847
+ six named palettes and paint the preview with ordinary shared variables such
3848
+ as `--background`, `--text-color`, and `--primary-color`; applications need no
3849
+ copied palette values. The scoped preview follows the page's appearance scheme.
3850
+ Set `data-color-scheme="light"` or `data-color-scheme="dark"` on that same
3851
+ `data-arcane-palette` element to preview either variant independently of the
3852
+ page scheme. Leaving it unset follows the page. Applications own preview order,
3853
+ layout, and any toggle; the scoped attribute leaves the page mode unchanged.
3854
+
3855
+ `loadAndApplyTheme()` restores the last app-scoped presentation before its
3856
+ asynchronous preference loads. The complete saved skin and applied
3857
+ appearance/custom presentation are cached by `ThemePresentation` only as
3858
+ nonauthoritative UI state;
3859
+ User and preference stores retain ownership of saved settings. No page hiding,
3860
+ profile-read barrier, polling, or history migration is involved.
3861
+ `apply()` remembers only its applied appearance attributes and the custom CSS
3862
+ properties owned by Theme's existing token list. `preview()` remains transient.
3863
+ The module's side-effect import also installs the presentation owner when no
3864
+ classic head script was selected; it does not provide a first-paint guarantee.
3790
3865
 
3791
3866
  ### Availability and normalization
3792
3867
 
@@ -3795,9 +3870,75 @@ Exact exports: `default`, `loadAndApplyTheme`.
3795
3870
  ### Example
3796
3871
 
3797
3872
  ```javascript
3798
- import * as module from '/arcane/modules/ThemeManager.js';
3873
+ import {applyUserSkin} from '/arcane/modules/ThemeManager.js';
3799
3874
 
3800
- console.log(Object.keys(module));
3875
+ applyUserSkin('warm dragon-observatory');
3876
+ ```
3877
+
3878
+ ## ThemePresentation.js
3879
+
3880
+ ### Overview
3881
+
3882
+ A dependency-free classic head entry restores the last application-scoped
3883
+ presentation synchronously, before following stylesheets are loaded. It
3884
+ does not import modules, User, OPFS, or a model, hide the page, or wait for
3885
+ profile or preference readiness. ThemeManager reuses the same per-realm owner.
3886
+
3887
+ ### Public surface
3888
+
3889
+ No ESM exports. The first evaluation installs
3890
+ `globalThis.arcaneThemePresentation` and calls `restore()`; later evaluations
3891
+ reuse the installed object.
3892
+
3893
+ - `applyUserSkin(skin,{root?,body?,cache=true}={})` is the same synchronous skin
3894
+ operation exported through ThemeManager. It preserves independently owned
3895
+ body classes, selects `data-user-skin`, and returns the selected palette name
3896
+ or `null` for a falsy skin or missing root. Numeric presentation is cached as
3897
+ its complete class text; supplied strings remain exact.
3898
+ - `remember(fields,root=document.documentElement)` merges the supplied
3899
+ presentation fields with the current cache and returns whether the write
3900
+ succeeded. The SDK supplies `skin`, `palette`,
3901
+ `appearance:{colorScheme,density,reduceMotion,fontSize}`, and
3902
+ `customProperties` (the applied Theme-owned CSS property map, or `null`).
3903
+ - `restore(root=document.documentElement)` applies cached root appearance and
3904
+ owned custom properties plus the complete skin classes. It returns the
3905
+ cached record, an empty record on a cache miss, or `null` when restoration
3906
+ is unavailable or fails. If the body is still absent, one DOMContentLoaded
3907
+ callback applies the latest class list; root colors are already selected.
3908
+ - `reportError(message,error)` initially forwards the complete Error to
3909
+ `console.warn`; ThemeManager connects it to `arcaneLogging.warn` when its
3910
+ module evaluates.
3911
+
3912
+ The cache key is `arcane.apps.<application-id>:arcane.theme.presentation`, using
3913
+ the existing `arcane-app-id` meta declaration or `data-arcane-app-id` on the
3914
+ document root. Missing identity or storage skips caching. Conflicting identity,
3915
+ storage failures, and malformed JSON are logged without stopping rendering.
3916
+ Malformed JSON does not prevent a later authoritative presentation write.
3917
+ Only presentation is remembered; no User record, unrelated inline style,
3918
+ profile snapshot, model state, or history is captured or migrated.
3919
+
3920
+ Canonical already-ready User state and later `user-entity-loaded` events
3921
+ reconcile the cached skin through ThemeBootstrap; loaded appearance/custom
3922
+ preferences remain authoritative through ThemeManager. A cache miss cannot
3923
+ know an unloaded saved profile skin, and a module-only import may evaluate
3924
+ after first paint.
3925
+
3926
+ ### Availability and normalization
3927
+
3928
+ **Browser / native WebView classic script.** DOM and optional application-scoped
3929
+ localStorage only. No native capability is required.
3930
+
3931
+ ### Example
3932
+
3933
+ Place the ordinary classic script after the existing app-id declaration and
3934
+ before theme/layout styles. Do not use `type="module"`, `async`, or `defer` on
3935
+ this early entry; retain the application's normal ThemeBootstrap module use.
3936
+
3937
+ ```html
3938
+ <meta name="arcane-app-id" content="dragon-observatory">
3939
+ <script src="/arcane/modules/ThemePresentation.js"></script>
3940
+ <link rel="stylesheet" href="/arcane/css/theme.css">
3941
+ <script type="module" src="/arcane/modules/ThemeBootstrap.js"></script>
3801
3942
  ```
3802
3943
 
3803
3944
  ## TimeGuard.js
@@ -6407,8 +6407,8 @@ console.log(deviceClass); // mobile or desktop
6407
6407
 
6408
6408
  ### Overview
6409
6409
 
6410
- The event name `arcane.pwa.state` identifies PWA registration and native worker
6411
- lifecycle state published through the existing Arcane event owner.
6410
+ The event name `arcane.pwa.state` identifies PWA registration, native worker
6411
+ lifecycle and page-local update availability published through the existing Arcane event owner.
6412
6412
 
6413
6413
  ### Example
6414
6414
 
@@ -6434,7 +6434,15 @@ It stores one `lastChecked` timestamp per app/cache in DBOPFS, advancing it only
6434
6434
  after the full resource check succeeds, and revalidates on page load after
6435
6435
  120 seconds in development or 15 minutes in packaged delivery. Complete
6436
6436
  responses remain cached without SDK expiration; `304` retains the response
6437
- and a successful replacement updates it. See the [PWA guide](pwa.md).
6437
+ and a successful replacement updates it. A complete successful refresh latches
6438
+ `state.updateAvailable` when parseable old/new `Last-Modified` dates differ or
6439
+ a successfully checked resource was absent from the prior durable inventory.
6440
+ Initial population, eviction repair, `304`, and unknown/equal modification
6441
+ dates do not announce updates. Partial-change evidence survives worker restart
6442
+ until complete success; existing scoped window clients receive the completed
6443
+ signal. The state replays to subscribers and remains true for this page owner.
6444
+ The registration owner's `update()` only requests a native worker update check;
6445
+ it does not reload the page. See the [PWA guide](pwa.md).
6438
6446
 
6439
6447
  ### Example
6440
6448
 
@@ -6456,7 +6464,7 @@ entry point and must observe its `ready` rejection.
6456
6464
  ### Overview
6457
6465
 
6458
6466
  The exact event name `arcane.pwa.install.state` identifies native installation
6459
- availability and choice state through the existing Arcane event owner. It is
6467
+ availability, choice and shared update-prompt state through the existing Arcane event owner. It is
6460
6468
  separate from service-worker registration state. Importing it starts no
6461
6469
  observation or installation.
6462
6470
 
@@ -6475,16 +6483,17 @@ payload and current-state subscription behavior.
6475
6483
  ### Overview
6476
6484
 
6477
6485
  `getPwaInstall()` returns one synchronous page owner exposing `state`, `ready`,
6478
- `subscribe`, `prompt`, `dismiss` and `dispose`. It captures the browser's
6486
+ `subscribe`, `prompt`, `dismiss`, `update`, `dismissUpdate` and `dispose`. It captures the browser's
6479
6487
  `beforeinstallprompt` event and observes `appinstalled` and app display-mode
6480
6488
  changes. A missing event leaves installation availability unknown and the owner
6481
6489
  waiting; it does not prove browser incompatibility.
6482
6490
 
6483
- State contains `status`, `available`, `installed`, `dismissed`, `outcome`,
6491
+ State contains `status`, `available`, `installed`, `dismissed`, `updateAvailable`,
6492
+ `updateDismissed`, `outcome`,
6484
6493
  `error` and `storageError`. The owner restores `pwa/installed.json` through the
6485
6494
  app-scoped DBOPFS singleton and saves `{installed: true}` after `appinstalled`
6486
6495
  or an installed-app launch. Remembered installation suppresses both floating
6487
- and inline controls on later visits. Acceptance alone and ordinary fullscreen
6496
+ and inline installation controls on later visits. Acceptance alone and ordinary fullscreen
6488
6497
  do not record installation.
6489
6498
 
6490
6499
  `ready` resolves to the state once the initial read settles. Native events are
@@ -6498,6 +6507,18 @@ consumes the event once and returns the browser choice, resolves to `null` when
6498
6507
  unavailable, or rejects with the actual error. `dismiss()` remembers the
6499
6508
  session's floating-suggestion dismissal without consuming a retained event.
6500
6509
 
6510
+ The generated bootstrap starts this owner before worker registration, so it
6511
+ observes the current page scope's `arcane.pwa.state` update signal while the
6512
+ component loads. A late-created prompt also reads current state from live
6513
+ registration owners. `updateAvailable` remains true for the page owner;
6514
+ `updateDismissed` is a separate page-local choice. An explicit `update()` calls
6515
+ ordinary `location.reload()` synchronously and returns true, or false when
6516
+ unavailable/disposed; native errors propagate. It preserves native unload
6517
+ handling and application draft ownership without promising transient-state
6518
+ survival. `dismissUpdate()` returns the current state and writes no durable
6519
+ record. Installed/running state or installation dismissal does not suppress
6520
+ the update suggestion. A fresh page does not replay an old completed update.
6521
+
6501
6522
  ### Example
6502
6523
 
6503
6524
  ```javascript
@@ -6537,7 +6558,9 @@ supplies the initial app name.
6537
6558
  The generated PWA bootstrap calls this automatically with the manifest name.
6538
6559
  Component mounting and worker registration run independently, and application
6539
6560
  rendering must not await them. The floating component offers Install and a
6540
- manual close control; an application can also place the same component inline.
6561
+ manual close control, or Update available with an explicit Update action after
6562
+ a completed changed refresh. Update dismissal is separate from installation
6563
+ dismissal. An application can also place the same component inline.
6541
6564
 
6542
6565
  ### Example
6543
6566
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.37.0",
3
+ "version": "0.39.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -1576,7 +1576,9 @@
1576
1576
  :'Tool'
1577
1577
  :message.role==='system'
1578
1578
  ?'System'
1579
- :host.aiName;
1579
+ :is.string(message.name)&&message.name.trim()
1580
+ ?message.name
1581
+ :'AI';
1580
1582
  item=createTranscriptMessage(message.role,message.content,name,{
1581
1583
  timestamp:message.timestamp,
1582
1584
  toolCalls:calls,
@@ -3245,6 +3247,9 @@
3245
3247
  const previousPendingToolCalls=[...pendingStructuralToolCalls];
3246
3248
  const streamedStructuralToolCalls=[];
3247
3249
  const messageId=`session-${++sessionMessageSequence}`;
3250
+ const responseName=is.string(session.aiName)&&session.aiName.trim()
3251
+ ?session.aiName
3252
+ :host.aiName;
3248
3253
  const sessionMessageToken=Symbol(messageId);
3249
3254
  const transientCards=retainTurn?[]:[...requestMessages];
3250
3255
  function removeTransientSessionCards(){
@@ -3259,7 +3264,7 @@
3259
3264
  applyAIAvailability(host.aiAvailability);
3260
3265
  setSessionStatus('sending','AI is responding…');
3261
3266
  try{
3262
- const responseCard=receivedMessage(host.aiName, '', messageId);
3267
+ const responseCard=receivedMessage(responseName, '', messageId);
3263
3268
  if(!retainTurn) transientCards.push(responseCard);
3264
3269
  await streamMessage('',messageId,true);
3265
3270
  setMessageProgress(
@@ -3344,6 +3349,10 @@
3344
3349
  result.message.content,
3345
3350
  committedTurn?.response?.timestamp??result.message.timestamp
3346
3351
  );
3352
+ const assistantName=committedTurn?.response?.name??result.message.name;
3353
+ if(is.string(assistantName)&&assistantName.trim()){
3354
+ message.querySelector('.message_header strong').textContent=assistantName;
3355
+ }
3347
3356
  for(const [index,requestMessage] of requestMessages.entries()){
3348
3357
  setTranscriptMessageTimestamp(
3349
3358
  requestMessage,
@@ -387,6 +387,21 @@
387
387
  background:var(--background);
388
388
  }
389
389
 
390
+ .file-preview-message[data-role="user"]{
391
+ background:var(--arcane-action,var(--primary-color));
392
+ color:var(--arcane-action-text,var(--button-text-color,var(--text-color)));
393
+ border-color:var(--arcane-action,var(--primary-color));
394
+ }
395
+
396
+ .file-preview-message[data-role="user"] a{
397
+ color:inherit;
398
+ }
399
+
400
+ .file-preview-message[data-role="assistant"]{
401
+ background:var(--arcane-surface,var(--background));
402
+ color:var(--text-color);
403
+ }
404
+
390
405
  .file-preview-message header,
391
406
  .file-preview-message footer{
392
407
  display:flex;
@@ -1740,9 +1755,15 @@
1740
1755
  const time=document.createElement('time');
1741
1756
  article.className='file-preview-message';
1742
1757
  article.dataset.role=message.role;
1743
- speaker.textContent=message.name===undefined
1744
- ?message.role
1745
- :`${message.name} · ${message.role}`;
1758
+ if(message.role==='assistant'){
1759
+ speaker.textContent=is.string(message.name)&&message.name.trim()
1760
+ ?message.name
1761
+ :'AI';
1762
+ }else{
1763
+ speaker.textContent=message.name===undefined
1764
+ ?message.role
1765
+ :`${message.name} · ${message.role}`;
1766
+ }
1746
1767
  header.append(speaker);
1747
1768
  if(message.timestamp===undefined||message.timestamp===null){
1748
1769
  time.textContent='Time unavailable';
@@ -78,12 +78,14 @@
78
78
  </header>
79
79
 
80
80
  <script type="module">
81
- const {arcaneLogging}=await import('arcane-os/logging');
81
+ const [{arcaneLogging},{applyUserSkin}]=await Promise.all([
82
+ import('arcane-os/logging'),
83
+ import('../modules/ThemeManager.js')
84
+ ]);
82
85
 
83
86
  import('../entities/User.js');
84
87
 
85
88
  const host=this;
86
- const appliedSkinClasses=new Set();
87
89
 
88
90
  window.addEventListener('user-entity-loaded', setSkin);
89
91
  window.addEventListener('online', checkInternet);
@@ -124,22 +126,7 @@
124
126
  try {
125
127
  const skin = window.user?.skin;
126
128
  if (skin) {
127
- const nextClasses=new Set(String(skin).match(/[^\t\n\f\r ]+/gu)||[]);
128
- const classes=document.body.classList;
129
- // Only replace classes added by this header; application layout
130
- // and independently owned theme classes stay with their owners.
131
- for(const name of appliedSkinClasses){
132
- if(!nextClasses.has(name)){
133
- classes.remove(name);
134
- appliedSkinClasses.delete(name);
135
- }
136
- }
137
- for(const name of nextClasses){
138
- if(!classes.contains(name)){
139
- classes.add(name);
140
- appliedSkinClasses.add(name);
141
- }
142
- }
129
+ applyUserSkin(skin);
143
130
  } else {
144
131
  arcaneLogging.log('No saved skin found. Using default.');
145
132
  }