arcane-os 0.44.0 → 0.46.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,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.46.0
4
+
5
+ - Add public `decodeMarkdownMediaRecord` for complete stored image records or
6
+ JSON text without storage or network access. Preserve singleton record layers,
7
+ original content and explicit decoding errors; never select from multiple
8
+ records. File previews recognize declared image records in JSON-family files
9
+ while ordinary untyped/non-image JSON and application preview hooks keep their
10
+ existing presentation and precedence.
11
+ - Preserve complete standalone IMG tags with multiline quoted descriptions
12
+ through the shared Markdown renderer and its original-source mapping. Native
13
+ HTML handling and literal Markdown examples keep their existing parsing;
14
+ stored Markdown is unchanged and requires no migration.
15
+ - Use the existing light paper palette and 15mm page margins for rendered print
16
+ snapshots and direct shared-theme printing. Preserve full content, rendered
17
+ fonts, image pixels, SVG artwork, live screen preferences, cancellation and
18
+ resource ownership through afterprint. A dialog request is not proof of
19
+ physical printing or PDF completion.
20
+ - Keep image destinations, export selection, model and credentials application
21
+ owned. No saved-data migration, model/provider/default change or app layout
22
+ change is introduced.
23
+
24
+ ## 0.45.0
25
+
26
+ - Add public MarkdownMedia save, read, reference parsing and hydration helpers.
27
+ Store complete image records in application-owned DBOPFS tables outside raw
28
+ Markdown, with stable references and application-selected filenames. Preserve
29
+ JSONL/NDJSON singleton records across cold reads and backup restoration.
30
+ - Hydrate local images independently in shared Markdown views while preserving
31
+ raw Markdown, external URLs, source maps and saving. Observe cancellation and
32
+ individual failures without removing successful sibling images. Retain print
33
+ resources through afterprint; explicit PrintView preparation awaits local
34
+ media while native beforeprint remains synchronous.
35
+ - Apply no-retention semantics to entity-wide persistence choices as well as
36
+ request persist:false: complete input and response remain available to the
37
+ active operation, then stay outside retained transcript, recurring model
38
+ context, DBOPFS and memory extraction. Capture the accepted choice before
39
+ rendering waits and preserve original pending calls after temporary tool
40
+ continuations. Existing retained turns and saved history remain untouched;
41
+ structural sanitization applies to new durable writes only, without migration.
42
+ - Keep image model selection, credentials, storage selection and app orchestration
43
+ application-owned. No model/provider/default changes are introduced.
44
+
3
45
  ## 0.44.0
4
46
 
5
47
  - Add stateless `generateImages` through `arcane-os/ai/twin-cloud` with
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.44.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.46.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
 
@@ -54,6 +54,16 @@ imported component's `destroy()` through `HTMLImport`; callers that invoke
54
54
  `destroy()` directly retain responsibility for removing the element when
55
55
  appropriate.
56
56
 
57
+ ## Shared print presentation
58
+
59
+ Applications loading the shared `theme.css` receive a print-only base light
60
+ palette with a white paper surface and `15mm` page margins. Root, body, and
61
+ scoped `data-arcane-palette` tokens override screen/custom colors during print;
62
+ root/body margins are reset. Saved preferences, screen presentation, scrolling,
63
+ media, and other layout rules remain unchanged. Registered rendered-print
64
+ views use the separate [`PrintView.js`](runtime-modules.md#printviewjs) snapshot
65
+ owner described with the editor and file preview below.
66
+
57
67
  ## Canonical inventory
58
68
 
59
69
  | Component | Capability | Principal methods | Events | Normalization |
@@ -426,7 +436,8 @@ history stores each tool's required user-facing message as an ordinary
426
436
  call envelope or ID and therefore does not recreate a pending executable call
427
437
  after reload. `submitToolResult()` and `submitToolResults()` accept `status` as
428
438
  the public result term; `disposition` remains an accepted input spelling.
429
- When a submitted turn uses `persist:false`, Chat may show its cards while that
439
+ When a submitted turn uses `persist:false`, or its session's Chat entity has
440
+ `persist=false`, Chat may show its cards while that
430
441
  one operation is active, then removes them when the operation settles; neither
431
442
  the input nor response remains in the retained transcript or model context.
432
443
  This cleanup also applies to failed, cancelled, detached, or replaced-session
@@ -437,6 +448,15 @@ operation's nodes so a completion listener can start another turn safely.
437
448
  Submission code passes those card references directly when it creates them;
438
449
  reusing an operation identifier never selects earlier conversation cards for
439
450
  cleanup or timestamp updates.
451
+ The session's terminal `retained` boolean reports its actual commit decision;
452
+ Chat uses that decision instead of retroactively interpreting a flag changed
453
+ for a later request. A completion listener cannot erase an earlier settled
454
+ turn by changing the next operation's retention setting.
455
+ Chat carries a submission's no-retention choice into its copied session request
456
+ before any rendering wait, so re-enabling the entity before dispatch cannot
457
+ retain that temporary input. Existing falsy entity settings retain their
458
+ no-retention meaning; a compatible custom session without a Chat entity keeps
459
+ its existing default behavior.
440
460
  Persistent-turn recovery and existing saved history remain unchanged.
441
461
 
442
462
  For streaming sessions, every provisional structural card and the terminal
@@ -909,7 +929,16 @@ extension. Routing compares the lowercase MIME essence without its parameters;
909
929
  the callback's `mimeType` and the file content remain unchanged.
910
930
  HTML uses its complete original string in `srcdoc` without an
911
931
  added sandbox policy; Markdown reads the complete original text before
912
- rendering. Untransformed JSONL/NDJSON shows its complete original text,
932
+ rendering. With no descriptor or `previewTransform`, JSON, JSONL, and NDJSON
933
+ files are read in full and recognized through
934
+ [`decodeMarkdownMediaRecord()`](runtime-modules.md#markdownmediajs). A complete
935
+ stored image record, including nested single-record arrays, opens as an image
936
+ when its decoded Blob has an `image/` media type. Untyped/non-image records,
937
+ multiple records, and unrecognized content keep their ordinary JSON-family
938
+ presentation; malformed recognized image encoding follows the existing
939
+ file-open failure path.
940
+ This recognition leaves saved content unchanged and performs no migration.
941
+ Untransformed JSONL/NDJSON shows its complete original text,
913
942
  including lines that are not JSON records. Other binary files offer a download
914
943
  of the original `File`.
915
944
  The larger file-preview dialog reuses `--modal-width`, `--modal-max-height`,
@@ -921,11 +950,26 @@ preview ahead of the editor. Printing uses the shared
921
950
  [`PrintView.js`](runtime-modules.md#printviewjs) owner; accessible HTML frames
922
951
  are copied from their current rendered document, never substituted with source
923
952
  text. Explicit printing waits for images/fonts and HTML frame readiness.
953
+ The shared print surface uses a light paper palette and `15mm` page margins
954
+ while preserving image/SVG colors and leaving the screen theme unchanged.
924
955
  Native PDF and inaccessible embedded viewers require their own print command.
925
956
  `true` means the browser print dialog was requested, not that paper or a PDF
926
957
  was produced. Closed, destroyed, or unavailable previews return `false`;
927
958
  preparation failures reject and the visible control reports a concise failure.
928
959
 
960
+ Markdown previews resolve saved `arcane-media:` images through
961
+ [`MarkdownMedia.js`](runtime-modules.md#markdownmediajs) before connecting IMG
962
+ nodes. Text appears while independent image reads finish; failures leave the
963
+ complete text and successful images visible with a concise recovery status.
964
+ Explicit printing waits for local reads and decoding. Native Print reports
965
+ pending media because it cannot await them. Closing or replacing a preview
966
+ cancels display readiness without waiting for storage; URLs already retained
967
+ by a requested print remain until `afterprint`. Original Markdown and ordinary
968
+ external image URLs remain unchanged.
969
+ Standalone IMG blocks with multiline quoted descriptions use the shared
970
+ [`MD.js`](runtime-modules.md#mdjs) tokenizer in both ordinary and source-mapped
971
+ rendering; the complete description and stored Markdown remain intact.
972
+
929
973
  `await close()` cancels pending presentation work and closes delete, file, and
930
974
  directory dialogs in that order. A settled preview keeps its rendered content,
931
975
  media, and printing available until the file dialog actually closes; refusing
@@ -937,7 +981,7 @@ writes complete through the storage owner without reopening closed dialogs.
937
981
 
938
982
  Events: `file-manager-ready`, `file-manager-select`, `file-manager-open`, `file-manager-action`.
939
983
 
940
- Shared dependencies: [`DBOPFS.js`](runtime-modules.md#dbopfsjs), [`File.js`](runtime-entities.md#filejs), [`MD.js`](runtime-modules.md#mdjs), [`WaitForComponent.js`](runtime-modules.md#waitforcomponentjs).
984
+ Shared dependencies: [`DBOPFS.js`](runtime-modules.md#dbopfsjs), [`File.js`](runtime-entities.md#filejs), [`MD.js`](runtime-modules.md#mdjs), [`MarkdownMedia.js`](runtime-modules.md#markdownmediajs), [`PrintView.js`](runtime-modules.md#printviewjs), [`WaitForComponent.js`](runtime-modules.md#waitforcomponentjs).
941
985
 
942
986
  ### Availability and normalization
943
987
 
@@ -1054,7 +1098,19 @@ Events: `markdown-document-ready`, `markdown-document-state`,
1054
1098
  `markdown-document-empty`, `markdown-document-error`, and
1055
1099
  `markdown-document-navigate`.
1056
1100
 
1057
- Shared dependencies: [`MD.js`](runtime-modules.md#mdjs).
1101
+ Saved local image references use
1102
+ [`MarkdownMedia.js`](runtime-modules.md#markdownmediajs). Text rendering and
1103
+ the existing rendered event do not wait for image reads. The optional
1104
+ `labels.mediaError` supplies concise visible failure text while complete
1105
+ diagnostics remain in the developer console; successful images and document
1106
+ text remain visible. Replacing, clearing, failing, or destroying the document
1107
+ releases its display resources without deleting stored media. Markdown source
1108
+ and ordinary external image URLs remain unchanged.
1109
+ Complete standalone IMG blocks with quoted multiline descriptions render
1110
+ through the shared [`MD.js`](runtime-modules.md#mdjs) tokenizer without
1111
+ rewriting saved Markdown.
1112
+
1113
+ Shared dependencies: [`MD.js`](runtime-modules.md#mdjs), [`MarkdownMedia.js`](runtime-modules.md#markdownmediajs).
1058
1114
 
1059
1115
  ### Availability and normalization
1060
1116
 
@@ -1096,14 +1152,29 @@ The visible **Print** control and `await print()` print the complete rendered
1096
1152
  Markdown with `entryTitle`, independently of saving. Native browser Print uses
1097
1153
  that same registered view unless an open file preview takes precedence.
1098
1154
  The SDK copies current rendered DOM/styles into a temporary print-only view,
1099
- leaving the editor's source, controls, and screen layout in place. Explicit
1100
- printing waits for resource readiness; native `beforeprint` is synchronous
1155
+ leaving the editor's source, controls, and screen layout in place. Both explicit
1156
+ and native print use the shared light paper palette and `15mm` page margins;
1157
+ image pixels and SVG artwork keep their colors. Explicit printing waits for
1158
+ resource readiness; native `beforeprint` is synchronous
1101
1159
  and uses the currently rendered content. `print()` returns `false` when the
1102
1160
  view is unavailable, rejects preparation failures, and returns `true` only
1103
1161
  when the browser dialog was requested. Completion of physical printing or PDF
1104
1162
  saving is not observable through this method. See
1105
1163
  [`PrintView.js`](runtime-modules.md#printviewjs).
1106
1164
 
1165
+ Saved `arcane-media:` image references are hydrated through
1166
+ [`MarkdownMedia.js`](runtime-modules.md#markdownmediajs) in detached rendered
1167
+ nodes. Source text, edit selection, source-map comments, ordinary image URLs,
1168
+ and the existing save flow remain unchanged. Image status has its own
1169
+ idle-hidden `media-status` part, separate from save status. Explicit printing
1170
+ waits for the current image reads/decoding, follows a replacement preview, and
1171
+ rechecks readiness before capture. Native Print reports still-pending media
1172
+ synchronously; it cannot wait or cancel the native dialog. Print-owned URLs
1173
+ outlive a changed or destroyed editor until `afterprint`.
1174
+ The shared [`MD.js`](runtime-modules.md#mdjs) tokenizer preserves complete
1175
+ standalone IMG blocks with quoted multiline descriptions in ordinary and
1176
+ source-mapped rendering. Existing saved markup needs no rewriting.
1177
+
1107
1178
  `configure({fit:true,followPreview:true})` opts into the two independent
1108
1179
  behaviors. Both options default to `false`; omitted options retain their
1109
1180
  previous values during reconfiguration, and explicit `false` disables either.
@@ -1140,7 +1211,7 @@ lists, quotes, and tables follow their block, while invisible source positions
1140
1211
  use a nearby available rendered block rather than exact glyph geometry.
1141
1212
 
1142
1213
  Public CSS parts are `editor`, `title`, `toolbar`, `input`, `preview`,
1143
- `actions`, `status`, `print-action`, and `save-action`. Applications can make the title
1214
+ `actions`, `status`, `media-status`, `print-action`, and `save-action`. Applications can make the title
1144
1215
  borderless and compact through `::part(title)` while retaining its native
1145
1216
  editing behavior and visible keyboard focus. These parts are styling hooks,
1146
1217
  not slots or replacement controls.
@@ -96,6 +96,7 @@ own asynchronous work, cancellation, and backpressure.
96
96
  | [`Mail.js`](#mailjs) | esm | Builds complete reports and prefers the native mail capability with an explicit HTTP transport fallback. | Browser/native hybrid + cloud | Mail inputs/results normalized; transport failures mixed. |
97
97
  | [`MailOutbox.mjs`](#mailoutboxmjs) | esm | Persists complete mail reports before delivery and normalizes idempotent enqueue, retry, reconciliation, and invalid-record maintenance. | Browser/native WebView or compatible injected host | Complete records, full work, cancellation, and lifecycle states normalized; storage, lock, and delivery failures coded. |
98
98
  | [`MailTransport.mjs`](#mailtransportmjs) | esm | Sends one complete mail report to a normalized HTTP(S) endpoint. | Browser/server with fetch + cloud | Normalized endpoint and transport errors; remote detail preserved. |
99
+ | [`MarkdownMedia.js`](#markdownmediajs) | esm | Saves local Markdown images separately, decodes complete stored records, and resolves stable references in rendered views. | Browser / supported native WebView | Complete image records, concurrent display reads, cancellation, and print-owned URL lifetime. |
99
100
  | [`MarkdownSpeech.js`](#markdownspeechjs) | esm | Removes repeated Markdown formatting marks from streamed narration. | Cross-host | Speech-only filtering; single marks and ordinary punctuation remain literal. |
100
101
  | [`Marked.min.js`](#markedminjs) | esm | Vendored Marked 18.0.5 Markdown lexer, parser, renderer, extension, and walk-token API. | Cross-host vendor module | Vendor-native Marked contract. |
101
102
  | [`MD.js`](#mdjs) | esm | Renders complete Markdown with Marked and optionally maps source blocks to rendered comment anchors. | Browser / native WebView | Complete raw and rendered Marked values; opt-in original-source offsets; parse errors vendor-native. |
@@ -2794,6 +2795,85 @@ import * as module from '/arcane/modules/Marked.min.js';
2794
2795
  console.log(Object.keys(module));
2795
2796
  ```
2796
2797
 
2798
+ ## MarkdownMedia.js
2799
+
2800
+ ### Overview
2801
+
2802
+ Stores complete images in the application's existing DBOPFS database while
2803
+ Markdown contains only a short, stable `arcane-media:` reference. Rendering
2804
+ resolves those local references into temporary display URLs. Importing the
2805
+ module starts no storage, network, provider, or rendering operation.
2806
+
2807
+ ### Public surface
2808
+
2809
+ Public package import: `arcane-os/modules/MarkdownMedia.js`.
2810
+
2811
+ - `await saveMarkdownMedia({blob,tableName='markdown-media',fileName})` writes
2812
+ `{mediaType,dataUrl}` as a JSON-compatible record and returns
2813
+ `{reference,tableName,fileName,mediaType}` only after the write completes.
2814
+ The default filename is a new UUID followed by `.json`. There is no abort
2815
+ option: accepted storage writes settle through the storage owner.
2816
+ - `parseMarkdownMediaReference(reference)` returns `{tableName,fileName}` for
2817
+ a local reference, or `null` for an ordinary URL. Malformed local addresses
2818
+ report their actual parsing error.
2819
+ - `decodeMarkdownMediaRecord(value)` synchronously decodes a complete
2820
+ `{dataUrl,mediaType?}` record or its JSON text into a `Blob`, without storage
2821
+ or network access. Nested single-record arrays are unwrapped; arrays with
2822
+ multiple records are never reduced to one image. Unrecognized content,
2823
+ including text that is not complete JSON, returns `null`. A recognized
2824
+ record with unreadable base64 data-URL content throws its decoding error.
2825
+ Omitted `mediaType` preserves the Blob's empty type. Input remains unchanged.
2826
+ - `await readMarkdownMedia(reference)` returns the complete `Blob`, using
2827
+ only local storage and data-URL decoding; stored strings are never fetched
2828
+ as network addresses. It uses the same record decoder after loading, while
2829
+ preserving JSON parse errors for unreadable stored JSON strings and reporting
2830
+ an unreadable record as an error rather than `null`.
2831
+ - `hydrateMarkdownMedia(root,{signal}={})` returns `{ready,destroy,retain}`
2832
+ immediately. It includes an IMG root and descendant IMG elements, removes
2833
+ local-reference `src` values synchronously, starts independent reads
2834
+ together, and assigns owned object URLs. `ready` includes image decoding.
2835
+ Use a detached fragment before connecting those images to the document.
2836
+
2837
+ Hydration preserves Markdown source, image descriptions, ordinary URLs, and
2838
+ surrounding content. Failed reads or decodes reject `ready` with an
2839
+ `AggregateError` whose complete `failures` array contains
2840
+ `{image,reference,reason}`; successful sibling images remain visible.
2841
+
2842
+ `destroy()` or abort settles display readiness promptly, prevents late display,
2843
+ and releases owned URLs. An already-started storage read remains observed and
2844
+ settles independently. `retain()` returns an idempotent release callback for
2845
+ printing; retained URLs survive destruction of the original view until every
2846
+ print owner releases them. Destruction never deletes stored images.
2847
+
2848
+ Applications own record names, entry association, cleanup policy, and which
2849
+ records their existing JSON backup/export includes. Reusing the same filename
2850
+ supports an application-selected save-only retry without another generation
2851
+ request. The helper performs no existing-data migration.
2852
+
2853
+ Exact exports: `saveMarkdownMedia`, `parseMarkdownMediaReference`,
2854
+ `decodeMarkdownMediaRecord`, `readMarkdownMedia`, `hydrateMarkdownMedia`.
2855
+
2856
+ ### Availability and normalization
2857
+
2858
+ Browser or native WebView with the existing app-scoped DBOPFS owner, Blob,
2859
+ FileReader, object URLs, and IMG decoding. Encoding, storage, missing-record,
2860
+ parsing, and decoding errors remain observable. No Core capability is selected.
2861
+
2862
+ ### Example
2863
+
2864
+ ```javascript
2865
+ import {saveMarkdownMedia} from 'arcane-os/modules/MarkdownMedia.js';
2866
+
2867
+ const mural = new Blob([
2868
+ '<svg xmlns="http://www.w3.org/2000/svg" width="240" height="80"><text x="10" y="45">Octopus parking only</text></svg>'
2869
+ ], {type:'image/svg+xml'});
2870
+ const {reference} = await saveMarkdownMedia({blob:mural});
2871
+ editor.insertMarkdown(`![Octopus parking only](${reference})`);
2872
+ ```
2873
+
2874
+ Here `editor` is the ready shared Markdown Editor. Its normal change/save flow
2875
+ still owns saving the Markdown; this operation saves the image record.
2876
+
2797
2877
  ## MD.js
2798
2878
 
2799
2879
  ### Overview
@@ -2828,16 +2908,29 @@ mapping, not exact rendered glyph or caret geometry.
2828
2908
 
2829
2909
  Assigning `raw` or calling `append(string)` rebuilds both the complete rendered
2830
2910
  result and its current mapping. Without `sourceMap:true`, `sourceMap` is empty
2831
- and the ordinary rendered output is unchanged. Existing raw content, link
2911
+ and the same parsing runs without comment anchors. Existing raw content, link
2832
2912
  behavior, no-op `rendered` setter, and complete `append()` result remain.
2833
2913
 
2914
+ Both rendering paths first use Marked's native HTML block tokenizer. When it
2915
+ does not recognize a complete standalone `<img>` tag with a newline inside a
2916
+ quoted attribute value, MD keeps that complete tag together as one HTML block.
2917
+ This includes blank lines in an image's full `alt` description. The tag must
2918
+ start at an existing Markdown block boundary with at most three leading spaces
2919
+ and end on its own line or at the end of the source. Separate inserted image
2920
+ blocks from surrounding Markdown with blank lines. Native HTML handling,
2921
+ inline prose, fenced/indented/inline code, escaped examples, and incomplete
2922
+ tags retain their existing parsing behavior. No source is rewritten or saved
2923
+ data migrated. Marked's existing rendered line-ending normalization remains;
2924
+ mapped offsets still address the exact original source, including CRLF or CR.
2925
+
2834
2926
  Exact exports: `default`.
2835
2927
 
2836
2928
  ### Availability and normalization
2837
2929
 
2838
- **Browser / native WebView.** Raw Marked behavior is preserved; parse errors are
2839
- vendor-native. Rendering and the getters are DOM-independent. DOM lookup and
2840
- scrolling belong to the consuming component;
2930
+ **Browser / native WebView.** Marked parsing, including the shared standalone
2931
+ image-token correction above; parse errors remain vendor-native. Rendering and
2932
+ the getters are DOM-independent. DOM lookup and scrolling belong to the
2933
+ consuming component;
2841
2934
  mapping performs no persistence or viewport movement. Transport: Marked.
2842
2935
  [Deep protocol details](protocols.md).
2843
2936
 
@@ -3071,6 +3164,9 @@ assistant content in configured model context and ChatEntity/DBOPFS history.
3071
3164
  That assistant-only opening survives reload and empty-chat maintenance without
3072
3165
  a fabricated user turn. A second opening rejects as `AI_CHAT_OPENING_EXISTS`;
3073
3166
  an unavailable durable ChatEntity rejects as `AI_CHAT_PERSISTENCE_UNAVAILABLE`.
3167
+ If retention is disabled while the opening is being prepared, that same error
3168
+ rolls back the opening before its append; it does not leave hidden model context.
3169
+ An opening write already accepted by DBOPFS still completes normally.
3074
3170
 
3075
3171
  `send()` accepts either
3076
3172
  `{message:{content,role:'user'|'tool',tool_call_id?,message?,name?,status?,persist},...}`
@@ -3086,6 +3182,20 @@ the retained transcript, memory extraction, or DBOPFS. A nonpersistent response
3086
3182
  therefore does not open a retained structural-tool continuation. A retained
3087
3183
  structural tool result must use the persistence choice captured by its matching
3088
3184
  assistant tool call.
3185
+ An explicitly nonretained tool-result continuation uses its matching pending
3186
+ call only for that operation, then rolls back without settling or changing the
3187
+ retained call. A later retained continuation can still settle it normally.
3188
+
3189
+ The entity-wide `session.chatEntity.persist=false` applies the same no-retention
3190
+ rule to new turns and direct entity additions; it is not a disk-only switch.
3191
+ The session captures that setting when it accepts a request and observes it
3192
+ again before accepting a durable append. Changing it later cannot bring back
3193
+ discarded inputs or responses. A write already accepted by DBOPFS finishes
3194
+ normally; toggling the setting does not undo that write or rewrite saved data.
3195
+ Completed `send()` and `stream()` responses include a session-owned `retained`
3196
+ boolean outside `message` and the provider payload, identifying whether the
3197
+ turn entered recurring history. Existing retained and stored records remain
3198
+ untouched: this boundary applies to new additions and performs no migration.
3089
3199
 
3090
3200
  `history()` returns provider-safe configured model context, including the
3091
3201
  system prompt and every complete ordinary visible committed turn. Only a
@@ -3260,14 +3370,26 @@ Shared rendered-print owner used by Markdown Editor and File Manager. It
3260
3370
  captures the current DOM and computed styles, including accessible rendered
3261
3371
  HTML frames, complete text, selected images, and the current title. A temporary
3262
3372
  print-only surface expands scrollable content without editing the live screen.
3373
+ That surface uses the shared Arcane light-theme text, paper, and border colors,
3374
+ with `15mm` page margins. It retains rendered fonts, emphasis, and complete
3375
+ content while removing screen-theme text/shadow paint from ordinary HTML and
3376
+ pseudo-elements. Links are underlined and table cells receive visible borders.
3377
+ Image pixels, canvas snapshots, and SVG artwork keep their own colors. These
3378
+ print rules leave the live screen theme unchanged; browser print settings still
3379
+ control the eventual page output.
3263
3380
 
3264
3381
  ### Public surface
3265
3382
 
3266
- Named `createPrintView({host,content,title,active,priority=0,signal,onError,retain})`
3383
+ Named `createPrintView({host,content,title,active,priority=0,signal,onError,retain,prepare})`
3267
3384
  returns `{print,destroy}`. `host` is a connected element; synchronous callbacks
3268
3385
  return the rendered content node, string title, and whether the view is active.
3269
3386
  Optional `retain()` returns a resource-release callback. It keeps owned media
3270
3387
  available until preparation is cancelled or the browser emits `afterprint`.
3388
+ Optional `prepare(signal)` may await view-owned readiness during an explicit
3389
+ print request. After it settles, current content and title are read again
3390
+ synchronously immediately before capture. A view whose content changes during
3391
+ preparation must keep its owned media retained and reject a still-pending
3392
+ snapshot through its synchronous `content()` callback.
3271
3393
 
3272
3394
  `await print()` selects that view, waits for accessible HTML frame, image, and
3273
3395
  font readiness, and requests the browser print dialog. It returns `false` when
@@ -3281,6 +3403,8 @@ on ties. File preview uses priority `1`; editor uses `0`. Native `beforeprint`
3281
3403
  is synchronous: it snapshots currently rendered content, cannot await a newly
3282
3404
  loading resource, and cannot cancel the browser's dialog. `onError(error)` or
3283
3405
  the developer console receives complete preparation failures.
3406
+ Native printing never invokes the asynchronous `prepare` callback; prepared
3407
+ views still receive the synchronous content/title read at capture.
3284
3408
 
3285
3409
  `destroy()` unregisters the view and cancels pending preparation. Once the
3286
3410
  dialog has been requested, its snapshot and retained media remain until
@@ -3930,12 +4054,20 @@ is applied once the body is available.
3930
4054
 
3931
4055
  The shared `theme.css` owns the default, warm, curious, hopeful, harmony, and
3932
4056
  warrior palettes. Each works with explicit light/dark or system appearance.
3933
- `layout.css` imports that same owner. Explicit custom themes retain precedence
3934
- through `data-arcane-skin="custom"` and their inline tokens.
4057
+ `layout.css` imports that same owner. On screen, explicit custom themes retain
4058
+ precedence through `data-arcane-skin="custom"` and their inline tokens.
3935
4059
  The bare root retains the generic neutral light/dark baseline; the prior
3936
4060
  layout default palette is selected explicitly through `data-user-skin="default"`,
3937
4061
  `body.default` compatibility, or the default swatch.
3938
4062
 
4063
+ During printing, `theme.css` applies the existing base light palette, with a
4064
+ white paper surface and `15mm` page margins, to the root, body, and scoped
4065
+ `data-arcane-palette` elements. These print-only tokens override screen/custom
4066
+ palette tokens. It resets the root/body margin without changing saved
4067
+ preferences, screen presentation, scrolling, media, or other layout rules.
4068
+ The separate [`PrintView.js`](#printviewjs) owner uses the same light palette
4069
+ and margins for its expanded rendered-content snapshot.
4070
+
3939
4071
  A scoped `data-arcane-palette="warm"` element exposes the same palette variables
3940
4072
  for a chooser preview without changing the page's selection. Use any of the
3941
4073
  six named palettes and paint the preview with ordinary shared variables such
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.44.0",
3
+ "version": "0.46.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",
@@ -86,7 +86,7 @@
86
86
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
87
87
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
88
88
  "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/conversation-closing-report.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
89
- "test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
89
+ "test:functional": "node ./bin/arcane-test.mjs test/ai-request-model.test.mjs test/ai-request-retry.test.mjs test/ai-stream-cancellation.test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/chat-input.test.mjs test/chat-stream-speech.test.mjs test/chat-transcript-scroll.test.mjs test/chat-transient-retention.test.mjs test/cli.test.mjs test/continuous-voice-capture.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs-png.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/file-preview.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/markdown-editor.test.mjs test/markdown-media.test.mjs test/markdown-source-map.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/rendered-print.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/theme-manager.test.mjs test/twin-cloud-images.test.mjs test/twin-cloud.test.mjs test/update-check.test.mjs test/user-explicit-updates.test.mjs",
90
90
  "test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
91
91
  "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
92
92
  "check": "node tools/check-source.mjs && npm test",
@@ -3255,13 +3255,22 @@
3255
3255
  );
3256
3256
  }
3257
3257
 
3258
- const sessionRequestMessages=is.array(sessionRequest)
3258
+ const submittedMessages=is.array(sessionRequest)
3259
3259
  ?sessionRequest
3260
3260
  :[sessionRequest];
3261
- if(!sessionRequestMessages.length){
3261
+ if(!submittedMessages.length){
3262
3262
  throw new TypeError('A chat session request must contain at least one message.');
3263
3263
  }
3264
- const retainTurn=sessionRequestMessages.every(message=>message.persist!==false);
3264
+ function entityRetainsMessages(){
3265
+ return !session.chatEntity||Boolean(session.chatEntity.persist);
3266
+ }
3267
+ let retainTurn=submittedMessages.every(message=>message.persist!==false)
3268
+ &&entityRetainsMessages();
3269
+ // Preserve this submission's choice across the rendering await before
3270
+ // dispatch; a later entity setting belongs to a later submission.
3271
+ const sessionRequestMessages=retainTurn
3272
+ ?submittedMessages
3273
+ :submittedMessages.map(message=>({...message,persist:false}));
3265
3274
  const request={
3266
3275
  ...(sessionRequestMessages.length===1
3267
3276
  ?{message:sessionRequestMessages[0]}
@@ -3276,9 +3285,18 @@
3276
3285
  ?session.aiName
3277
3286
  :host.aiName;
3278
3287
  const sessionMessageToken=Symbol(messageId);
3279
- const transientCards=retainTurn?[]:[...requestMessages];
3288
+ const transientCards=[...requestMessages];
3289
+ let retentionSettled=false;
3290
+ function settleTurnRetention(result){
3291
+ if(retentionSettled) return;
3292
+ // The session reports the decision made at its actual commit boundary.
3293
+ retainTurn=retainTurn&&(is.boolean(result?.retained)
3294
+ ?result.retained
3295
+ :entityRetainsMessages());
3296
+ retentionSettled=true;
3297
+ }
3280
3298
  function removeTransientSessionCards(){
3281
- if(!transientCards.length) return;
3299
+ if(retainTurn||!transientCards.length) return;
3282
3300
  // The submitter supplies only nodes created for this submission.
3283
3301
  for(const card of transientCards) card.remove();
3284
3302
  transientCards.length=0;
@@ -3290,7 +3308,7 @@
3290
3308
  setSessionStatus('sending','AI is responding…');
3291
3309
  try{
3292
3310
  const responseCard=receivedMessage(responseName, '', messageId);
3293
- if(!retainTurn) transientCards.push(responseCard);
3311
+ transientCards.push(responseCard);
3294
3312
  await streamMessage('',messageId,true);
3295
3313
  setMessageProgress(
3296
3314
  messageId,
@@ -3344,6 +3362,7 @@
3344
3362
  }
3345
3363
  )
3346
3364
  :await session.send(request);
3365
+ settleTurnRetention(result);
3347
3366
  if(
3348
3367
  destroyed
3349
3368
  ||bindingGeneration!==sessionBindingGeneration
@@ -3441,6 +3460,7 @@
3441
3460
  );
3442
3461
  return result;
3443
3462
  }catch(error){
3463
+ settleTurnRetention();
3444
3464
  if(!destroyed&&bindingGeneration===sessionBindingGeneration){
3445
3465
  setPendingStructuralToolCalls(previousPendingToolCalls);
3446
3466
  }
@@ -3501,6 +3521,7 @@
3501
3521
  }
3502
3522
  throw error;
3503
3523
  }finally{
3524
+ settleTurnRetention();
3504
3525
  removeTransientSessionCards();
3505
3526
  if(activeSessionMessageToken===sessionMessageToken){
3506
3527
  activeSessionMessageToken=null;