arcane-os 0.47.0 → 0.48.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,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.48.0
4
+
5
+ - Add opt-in Chat `transcriptionMode='draft'` and public `appendDraft(text)`.
6
+ Native recognition appears immediately in the editable composer; interim
7
+ replacement preserves typed text and user edits, and completion leaves manual
8
+ Send to the user. Existing consumers retain default completed-transcription
9
+ submission. Conversation language remains separate from recognition language.
10
+ - Add native BCP47 `recognitionLanguage` in the visible shared speech status
11
+ area and cumulative `speech-transcription-progress` events. Preserve model
12
+ transcription wording, TTS controls, gesture-bound capture, final draining,
13
+ cancellation and provider ownership. Continuous voice shows canonical text,
14
+ queued finals and transient interim text together in its existing output;
15
+ durable text and ordered save/retry payloads remain unchanged.
16
+ - Centralize direct-page and rendered-snapshot print styles in `print.css`,
17
+ with a light paper palette and one-inch page margins. Shared screen shells
18
+ expand into document flow only for print. Preserve rendered fonts, complete
19
+ images and SVG artwork, resource readiness and snapshot ownership through
20
+ `afterprint`.
21
+ - Add public Chat `print()` and `printTitle`, using shared PrintView for the
22
+ complete rendered conversation without composer controls, transient thinking
23
+ or tool-inspection details. Preserve human-facing tool messages. A successful
24
+ method result means the browser print request began, not that paper or PDF
25
+ output completed. No saved-data migration or model/provider/default change.
26
+
3
27
  ## 0.47.0
4
28
 
5
29
  - Add explicitly selected native browser speech recognition 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.47.0` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.48.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
 
@@ -56,13 +56,14 @@ appropriate.
56
56
 
57
57
  ## Shared print presentation
58
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.
59
+ The shared `theme.css` and `document-site.css` import `print.css`, the single
60
+ print-style owner. It provides a light paper palette and `1in` (`25.4mm`) page
61
+ margins. During print, root/body/scoped palette tokens override screen colors;
62
+ the shared header/sidebar shell is hidden and its main scroller expands into
63
+ document flow. Screen layout, preferences, fonts, and media remain unchanged.
64
+ [`PrintView.js`](runtime-modules.md#printviewjs) loads the same stylesheet for
65
+ rendered snapshots of Chat, the editor, and file previews. Its temporary
66
+ snapshot remains owned until the browser's `afterprint` event.
66
67
 
67
68
  ## Canonical inventory
68
69
 
@@ -73,7 +74,7 @@ owner described with the editor and file preview below.
73
74
  | [`browser-ai-setup.html`](#browser-ai-setuphtml) | Browser API availability, reported GPU class, and copyable NPU/GPU settings addresses. | `refresh()`<br>`checkGpu()`<br>`open()`<br>`destroy()`<br>`ready` | `browser-ai-setup-ready` | Browser-reported adapter class; browser flag state and model execution remain browser-owned |
74
75
  | [`calculator.html`](#calculatorhtml) | Calculator keypad and result/error event surface backed by CalculatorEngine. | `calculate()`<br>`destroy()` | `calculator-ready`<br>`calculation-complete`<br>`calculation-error` | Normalized Calculation/error events |
75
76
  | [`chart.html`](#charthtml) | Accessible uPlot line, area, or point chart with normalized options and rows. | `configure()`<br>`populate()`<br>`setData()`<br>`addData()`<br>`update()`<br>`destroy()` | `chart-ready`<br>`chart-remove` | Options/rows normalized; uPlot rendering is vendor-native |
76
- | [`chat.html`](#chathtml) | Shared chat, visible selected-model activation request, file upload, streaming, structural tool settlement, speech, language, availability, and conversation-timebox surface. | `streamMessage()`<br>`setMessageProgress()`<br>`setAIAvailability()`<br>`setInitialSpeechMuted()`<br>`setConversationComplete()`<br>`bindConversationTimebox()`<br>`bindSession()`<br>`submitMessage()`<br>`submitToolResult()`<br>`submitToolResults()`<br>`sendMessage()`<br>`languageChanged()`<br>`requestAIActivation()`<br>`destroy()` | `chat-ready`<br>`chat-session-bound`<br>`chat-session-message`<br>`chat-session-error`<br>`chat-send-message`<br>`chat-send-error`<br>`chat-file-uploaded`<br>`chat-file-upload-error`<br>`chat-language-changed`<br>`chat-language-change-error`<br>`chat-ai-activation-request`<br>`chat-ai-activation-error`<br>`chat-speech-synthesis-error`<br>`conversation-timebox-error` | UI/runtime state, explicit user activation intent, and honest structural-call settlement normalized; AI/storage/media behavior mixed |
77
+ | [`chat.html`](#chathtml) | Shared chat, visible selected-model activation request, file upload, streaming, structural tool settlement, speech, language, availability, and conversation-timebox surface. | `streamMessage()`<br>`setMessageProgress()`<br>`setAIAvailability()`<br>`setInitialSpeechMuted()`<br>`setConversationComplete()`<br>`bindConversationTimebox()`<br>`bindSession()`<br>`submitMessage()`<br>`submitToolResult()`<br>`submitToolResults()`<br>`sendMessage()`<br>`appendDraft()`<br>`print()`<br>`languageChanged()`<br>`requestAIActivation()`<br>`destroy()` | `chat-ready`<br>`chat-session-bound`<br>`chat-session-message`<br>`chat-session-error`<br>`chat-send-message`<br>`chat-send-error`<br>`chat-file-uploaded`<br>`chat-file-upload-error`<br>`chat-language-changed`<br>`chat-language-change-error`<br>`chat-ai-activation-request`<br>`chat-ai-activation-error`<br>`chat-speech-synthesis-error`<br>`conversation-timebox-error` | UI/runtime state, explicit user activation intent, and honest structural-call settlement normalized; AI/storage/media behavior mixed |
77
78
  | [`conversation-view.html`](#conversation-viewhtml) | Provider-neutral conversation display, advisory actions, composer, busy state, and status. | `setConversation()`<br>`setBusy()`<br>`setStatus()`<br>`clearComposer()`<br>`destroy()` | `conversation-view-ready`<br>`communication-send`<br>`communication-advisory-action` | DOM-normalized |
78
79
  | [`dashboard-config.html`](#dashboard-confightml) | Selects which normalized chart definitions are visible on a dashboard. | `configure()`<br>`setDefinitions()`<br>`setVisibility()`<br>`getChartOptions()`<br>`getEffectiveVisibility()`<br>`open()`<br>`close()`<br>`destroy()` | `dashboard-config-ready`<br>`dashboard-config-opened`<br>`dashboard-config-closed`<br>`dashboard-config-change` | Fully normalized definitions and visibility |
79
80
  | [`data-maintenance.html`](#data-maintenancehtml) | Runs destructive cleanup of empty chats and memories inside the current app data scope. | `open()`<br>`destroy()` | `data-maintenance-ready`<br>`data-maintenance-complete` | Normalized counts; DBOPFS failures mixed |
@@ -98,7 +99,7 @@ owner described with the editor and file preview below.
98
99
  | [`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 |
99
100
  | [`source-code-viewer.html`](#source-code-viewerhtml) | Renders complete line-addressable source code with load, error, focus, and state behavior. | `configure()`<br>`load()`<br>`render()`<br>`clear()`<br>`fail()`<br>`focus()`<br>`focusLine()`<br>`destroy()` | `source-code-viewer-ready`<br>`source-code-viewer-state`<br>`source-code-viewer-state-loading`<br>`source-code-viewer-state-ready`<br>`source-code-viewer-state-empty`<br>`source-code-viewer-state-error` | Complete mutable source/state |
100
101
  | [`source-explanation.html`](#source-explanationhtml) | Presents an evidence finding, source selection, explanation, and save state. | `showFinding()`<br>`populate()`<br>`selectSource()`<br>`markSaved()`<br>`destroy()` | `source-explanation-ready`<br>`source-explanation-save`<br>`source-explanation-source-selected` | DOM-normalized |
101
- | [`speech.html`](#speechhtml) | Coordinates explicit STT activation, speech controls, transcription completion, mute state, and microphone availability. | `configure()`<br>`setMuted()`<br>`reportTTSError()`<br>`requestSTTActivation()`<br>`destroy()`<br>`availability`<br>`muted`<br>`initialMuted`<br>`componentReady` | `speech-ready`<br>`speech-transcription-complete`<br>`speech-transcription-error`<br>`speech-transcription-cancelled`<br>`speech-microphone-unavailable`<br>`speech-stt-activation-request`<br>`speech-stt-activation-error`<br>`speech-tts-lifecycle-error`<br>`speech-synthesis-error` | Sticky runtime speech readiness, explicit STT activation, request cancellation, TTS mute lifecycle intent, and exact TTS operation failures normalized; provider/model authority remains external |
102
+ | [`speech.html`](#speechhtml) | Coordinates explicit STT activation, speech controls, transcription completion, mute state, and microphone availability. | `configure()`<br>`setMuted()`<br>`reportTTSError()`<br>`requestSTTActivation()`<br>`destroy()`<br>`availability`<br>`muted`<br>`initialMuted`<br>`recognitionLanguage`<br>`displayTranscription`<br>`componentReady` | `speech-ready`<br>`speech-transcription-progress`<br>`speech-transcription-complete`<br>`speech-transcription-error`<br>`speech-transcription-cancelled`<br>`speech-microphone-unavailable`<br>`speech-stt-activation-request`<br>`speech-stt-activation-error`<br>`speech-tts-lifecycle-error`<br>`speech-synthesis-error` | Sticky runtime speech readiness, explicit STT activation, request cancellation, TTS mute lifecycle intent, and exact TTS operation failures normalized; provider/model authority remains external |
102
103
  | [`summary-strip.html`](#summary-striphtml) | Displays compact selectable KPI or summary items. | `configure()`<br>`setItems()`<br>`updateItem()`<br>`clear()`<br>`destroy()` | `summary-strip-ready`<br>`summary-strip-change`<br>`summary-strip-select` | DOM-normalized |
103
104
  | [`table.html`](#tablehtml) | Builds and updates a simple header/body table. | `buildHeader()`<br>`buildTable()`<br>`destroy()` | `table-ready`<br>`header-update`<br>`body-update` | DOM-normalized |
104
105
  | [`task-progress.html`](#task-progresshtml) | Runs and displays a task list with started/change/complete/error state. | `configure()`<br>`setTasks()`<br>`updateTask()`<br>`runTasks()`<br>`clear()`<br>`destroy()` | `task-progress-ready`<br>`task-progress-started`<br>`task-progress-change`<br>`task-progress-complete`<br>`task-progress-error` | Task state normalized; injected task results mixed |
@@ -317,7 +318,8 @@ Methods/properties: `streamMessage()`, `setMessageProgress()`,
317
318
  `submitMessage()`, `submitToolResult()`, `submitToolResults()`, `sendMessage()`,
318
319
  `languageChanged()`, `requestAIActivation()`, `session`, `sessionStatus`,
319
320
  `pendingTool`, `pendingTools`, `pendingToolCall`, `pendingToolCalls`, `modelName`,
320
- and `destroy()`.
321
+ `transcriptionMode`, `recognitionLanguage`, `appendDraft()`, `print()`,
322
+ `printTitle`, and `destroy()`.
321
323
 
322
324
  `sendMessage(text)` and `languageChanged(text)` are host-overridable async
323
325
  extension callbacks. The component installs warning-only defaults when the host
@@ -331,6 +333,38 @@ editing. The visible Send button remains available on every device. Input
331
333
  changes resize the composer without submitting; held desktop Enter does not
332
334
  submit repeatedly. All input listeners follow the component's existing cleanup.
333
335
 
336
+ `transcriptionMode` defaults to `'submit'`, preserving completed-transcription
337
+ submission. Set it to `'draft'` for editable voice input with manual Send.
338
+ Native final words and interim hypotheses appear immediately in the existing
339
+ composer; later hypotheses replace only the recognition-owned interim range.
340
+ Typing elsewhere is preserved, and editing that range makes its text user-owned.
341
+ Completion does not append the same final text twice. Cancellation removes only
342
+ an unedited interim hypothesis; final words and user edits remain in the draft.
343
+ The shared Speech status stops repeating transcript text in draft mode.
344
+ `appendDraft(text)` appends complete text without submitting, preserves existing
345
+ draft text and selection, and emits the normal input event. It adds a newline
346
+ between entries only when neither boundary already has whitespace. It returns
347
+ `false` for empty input or a destroyed component.
348
+
349
+ `recognitionLanguage` forwards the Speech component's native BCP47 language
350
+ selection. If omitted, Speech uses the browser language. It is independent of
351
+ the existing conversation-language label and is latched when capture starts.
352
+
353
+ `await print()` delegates the current rendered transcript to shared PrintView;
354
+ `printTitle` optionally supplies the title (default `Conversation`). Controls,
355
+ status, transient thinking, and tool-inspection details are outside the printed
356
+ conversation; complete human-facing tool messages remain. Print uses
357
+ a document-flow transcript layout instead of left/right screen bubbles;
358
+ timestamps keep their own alignment and table headings can repeat across pages.
359
+ Native Print uses
360
+ the same registered view while it is visible and has transcript content. The
361
+ method resolves `true` when the browser print dialog is requested, not when a
362
+ PDF or paper output is produced; it resolves `false` when unavailable or
363
+ cancelled during preparation and rejects preparation failures. Complete errors
364
+ reach developer diagnostics and a concise recovery status stays outside the
365
+ transcript. Destruction cancels pending preparation, while an already requested
366
+ print retains its snapshot through `afterprint`.
367
+
334
368
  Before any session binding begins, callback-mode `sessionStatus` follows the
335
369
  selected sticky LLM role and the existing application-owned `modelName` label
336
370
  (or the selected model ID). Assigning `modelName` refreshes that status even
@@ -950,7 +984,7 @@ preview ahead of the editor. Printing uses the shared
950
984
  [`PrintView.js`](runtime-modules.md#printviewjs) owner; accessible HTML frames
951
985
  are copied from their current rendered document, never substituted with source
952
986
  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
987
+ The shared print surface uses a light paper palette and `1in` (`25.4mm`) page margins
954
988
  while preserving image/SVG colors and leaving the screen theme unchanged.
955
989
  Native PDF and inaccessible embedded viewers require their own print command.
956
990
  `true` means the browser print dialog was requested, not that paper or a PDF
@@ -1153,7 +1187,7 @@ Markdown with `entryTitle`, independently of saving. Native browser Print uses
1153
1187
  that same registered view unless an open file preview takes precedence.
1154
1188
  The SDK copies current rendered DOM/styles into a temporary print-only view,
1155
1189
  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;
1190
+ and native print use the shared light paper palette and `1in` (`25.4mm`) page margins;
1157
1191
  image pixels and SVG artwork keep their colors. Explicit printing waits for
1158
1192
  resource readiness; native `beforeprint` is synchronous
1159
1193
  and uses the currently rendered content. `print()` returns `false` when the
@@ -1632,13 +1666,27 @@ Events: `source-explanation-ready`, `source-explanation-save`, `source-explanati
1632
1666
  An explicitly selected native Web Speech provider uses gesture-started live
1633
1667
  capture instead of MediaRecorder. The shared control retains every final
1634
1668
  result and emits its existing completion event once with the exact combined
1635
- text when native capture ends. The live display retains final words alongside
1636
- interim hypotheses while speaking; interim hypotheses stay transient. Release/Stop
1669
+ text when native capture ends. The `speech-transcription-progress` event carries
1670
+ cumulative complete final `text` and the current `interim` hypothesis, with the
1671
+ capture's `operationId`. Final segments replace their interim hypothesis before
1672
+ publishing; capture completion clears remaining interim text. The live display
1673
+ retains both while `displayTranscription` is true (the default). Set it false
1674
+ when a containing Chat owns the editable draft; status still reports listening,
1675
+ loading, and errors. Interim hypotheses stay transient. Release/Stop
1637
1676
  finishes native recognition; cancellation, replacement and destruction abort
1638
1677
  it and suppress late submission. A persisted page hide stops live native capture
1639
1678
  without restarting it when the page returns. Existing Whisper and TTS paths
1640
1679
  remain unchanged.
1641
1680
 
1681
+ On desktop native capture, an accessible editable BCP47 language control occupies
1682
+ the feedback area instead of idle transcription/voice readiness boilerplate.
1683
+ `recognitionLanguage` defaults to `navigator.language`, accepts an explicit
1684
+ language tag without restricting it to the suggested choices, and is passed to
1685
+ the capture owner at start. The control is disabled during active capture so
1686
+ changes affect the next capture. Native voice loading, errors, and speaking
1687
+ remain visible. Model-based transcription retains its existing status, and
1688
+ mobile speech retains its existing voice-output-only presentation.
1689
+
1642
1690
  ### Overview
1643
1691
 
1644
1692
  Coordinates explicit speech-to-text activation, speech controls, transcription
@@ -1648,9 +1696,11 @@ completion, mute state, and microphone availability.
1648
1696
 
1649
1697
  Methods/properties: `configure()`, `setMuted()`,
1650
1698
  `reportTTSError(error,boundary='synthesis')`, `requestSTTActivation()`,
1651
- `destroy()`, `availability`, `muted`, `initialMuted`, and `componentReady`.
1699
+ `destroy()`, `availability`, `muted`, `initialMuted`, `recognitionLanguage`,
1700
+ `displayTranscription`, and `componentReady`. The two new properties may also
1701
+ be supplied through `configure()`.
1652
1702
 
1653
- Events: `speech-ready`, `speech-transcription-complete`,
1703
+ Events: `speech-ready`, `speech-transcription-progress`, `speech-transcription-complete`,
1654
1704
  `speech-transcription-error`, `speech-transcription-cancelled`,
1655
1705
  `speech-microphone-unavailable`, `speech-stt-activation-request`,
1656
1706
  `speech-stt-activation-error`, `speech-tts-lifecycle-error`, and
@@ -2095,10 +2145,12 @@ Native methods remain subject to the bound app's capabilities. [Deep protocol de
2095
2145
 
2096
2146
  An explicitly selected native Web Speech provider uses the same ordered
2097
2147
  transcript/save queue in either capture mode. Each complete final text segment
2098
- skips file transcription and enters the existing append/save stages; interim
2099
- text stays in the transient `interim` part and never reaches a save or success
2100
- event. Final segments waiting behind a save stay visible there until appended
2101
- to the main transcript. Native `stopRecording()` returns the drain promise, including final
2148
+ skips file transcription and enters the existing append/save stages. The existing
2149
+ transcript output immediately displays canonical text, queued final segments,
2150
+ and the current interim hypothesis in order. There is no separate interim pane.
2151
+ Interim text never reaches `value`, a save, or a success event. Moving an accepted
2152
+ final segment into canonical text does not display it twice; saved text and
2153
+ retry payloads remain owned by the ordered queue. Native `stopRecording()` returns the drain promise, including final
2102
2154
  results emitted during native stop. Browser `end` stops capture without an
2103
2155
  automatic restart. Provider replacement stops native capture while already
2104
2156
  accepted final segments retain their ordered saves. Explicit Cancel, transcript
@@ -3394,12 +3394,14 @@ console.log(typeof prepareSpeech); // function
3394
3394
 
3395
3395
  ### Overview
3396
3396
 
3397
- Shared rendered-print owner used by Markdown Editor and File Manager. It
3397
+ Shared rendered-print owner used by Chat, Markdown Editor, and File Manager. It
3398
3398
  captures the current DOM and computed styles, including accessible rendered
3399
3399
  HTML frames, complete text, selected images, and the current title. A temporary
3400
3400
  print-only surface expands scrollable content without editing the live screen.
3401
- That surface uses the shared Arcane light-theme text, paper, and border colors,
3402
- with `15mm` page margins. It retains rendered fonts, emphasis, and complete
3401
+ The shared `/arcane/css/print.css` owns print presentation for this snapshot,
3402
+ direct themed pages, and documentation sites. It supplies the existing Arcane
3403
+ light palette and `1in` (`25.4mm`) page margins. The snapshot retains rendered
3404
+ fonts, font sizing, line height, emphasis, and complete
3403
3405
  content while removing screen-theme text/shadow paint from ordinary HTML and
3404
3406
  pseudo-elements. Links are underlined and table cells receive visible borders.
3405
3407
  Image pixels, canvas snapshots, and SVG artwork keep their own colors. These
@@ -3434,6 +3436,13 @@ the developer console receives complete preparation failures.
3434
3436
  Native printing never invokes the asynchronous `prepare` callback; prepared
3435
3437
  views still receive the synchronous content/title read at capture.
3436
3438
 
3439
+ Import fetches the complete shared stylesheet through a module-relative URL
3440
+ and resolves after that text is available. The editor, file preview, and chat
3441
+ load this module independently of rendering; stylesheet readiness delays only
3442
+ print availability. Every registered view can therefore capture synchronously
3443
+ during native `beforeprint`, with no new stylesheet request at print time.
3444
+ Loading failures reject the module import and remain observable to its caller.
3445
+
3437
3446
  `destroy()` unregisters the view and cancels pending preparation. Once the
3438
3447
  dialog has been requested, its snapshot and retained media remain until
3439
3448
  `afterprint`. Import alone creates no listener, provider, storage, or print job.
@@ -3443,7 +3452,8 @@ Exact exports: `createPrintView`.
3443
3452
  ### Availability and normalization
3444
3453
 
3445
3454
  **Browser and native WebViews with a print implementation.** Requires DOM,
3446
- CSSOM, FontFace/Image readiness, and browser print events. No Core capability.
3455
+ CSSOM, Fetch for initial stylesheet loading, FontFace/Image readiness, and
3456
+ browser print events. No Core capability.
3447
3457
  Cross-origin/inaccessible frames, native PDF frames, and object/embed viewers
3448
3458
  must use their owning viewer's print command; the helper does not replace their
3449
3459
  content with raw source or claim an unrendered document was printed. Font
@@ -4088,13 +4098,18 @@ The bare root retains the generic neutral light/dark baseline; the prior
4088
4098
  layout default palette is selected explicitly through `data-user-skin="default"`,
4089
4099
  `body.default` compatibility, or the default swatch.
4090
4100
 
4091
- During printing, `theme.css` applies the existing base light palette, with a
4092
- white paper surface and `15mm` page margins, to the root, body, and scoped
4101
+ `theme.css` and `document-site.css` import the shared `/arcane/css/print.css`.
4102
+ During printing, that owner applies the existing base light palette, with a
4103
+ white paper surface and `1in` (`25.4mm`) page margins, to the root, body, and scoped
4093
4104
  `data-arcane-palette` elements. These print-only tokens override screen/custom
4094
- palette tokens. It resets the root/body margin without changing saved
4095
- preferences, screen presentation, scrolling, media, or other layout rules.
4096
- The separate [`PrintView.js`](#printviewjs) owner uses the same light palette
4097
- and margins for its expanded rendered-content snapshot.
4105
+ palette tokens. During print, it resets root/body margins, hides the shared
4106
+ header/sidebar shell, and expands main-content scrolling into document flow.
4107
+ Saved preferences, screen presentation, fonts, and media remain unchanged.
4108
+ [`PrintView.js`](#printviewjs) loads that same stylesheet before registering
4109
+ views and applies it to its expanded rendered-content snapshot. Snapshot rules
4110
+ are scoped to owned print nodes; documentation-site print rules remain scoped
4111
+ to that site's surface. Screen styles and saved theme preferences remain with
4112
+ their existing owners.
4098
4113
 
4099
4114
  A scoped `data-arcane-palette="warm"` element exposes the same palette variables
4100
4115
  for a chooser preview without changing the page's selection. Use any of the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.47.0",
3
+ "version": "0.48.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",
@@ -902,6 +902,30 @@
902
902
  const browserDeviceClass = getBrowserDeviceClass();
903
903
 
904
904
  host.language=host.language||'English';
905
+ let transcriptionMode=host.transcriptionMode??'submit';
906
+ speech.displayTranscription=transcriptionMode!=='draft';
907
+ Object.defineProperty(host,'transcriptionMode',{
908
+ configurable:true,
909
+ enumerable:true,
910
+ get:function getTranscriptionMode(){return transcriptionMode;},
911
+ set:function setTranscriptionMode(mode){
912
+ transcriptionMode=mode;
913
+ speech.displayTranscription=mode!=='draft';
914
+ }
915
+ });
916
+ if(host.recognitionLanguage!==undefined){
917
+ speech.recognitionLanguage=host.recognitionLanguage;
918
+ }
919
+ Object.defineProperty(host,'recognitionLanguage',{
920
+ configurable:true,
921
+ enumerable:true,
922
+ get:function getRecognitionLanguage(){
923
+ return speech.recognitionLanguage;
924
+ },
925
+ set:function setRecognitionLanguage(language){
926
+ speech.recognitionLanguage=language;
927
+ }
928
+ });
905
929
  host.name = host.name||'User';
906
930
  host.aiName='AI';
907
931
  let requestedInitialSpeechMuted=is.boolean(speech.initialMuted)
@@ -920,6 +944,22 @@
920
944
  let hostSubmissionGeneration=0;
921
945
  let eventOperationSequence=0;
922
946
  let destroyed=false;
947
+ let recognitionDraft=null;
948
+ let writingRecognitionDraft=false;
949
+ const printViewPromise=import('../modules/PrintView.js').then(
950
+ function registerChatPrint({createPrintView}){
951
+ if(destroyed) return null;
952
+ return createPrintView({
953
+ host,
954
+ content:function chatPrintContent(){return chatOutput;},
955
+ title:function chatPrintTitle(){return host.printTitle||'Conversation';},
956
+ active:function chatPrintActive(){return !destroyed&&chatOutput.children.length>0;},
957
+ signal:aiRuntimeStateAbortController.signal,
958
+ onError:reportChatPrintFailure
959
+ });
960
+ }
961
+ );
962
+ void printViewPromise.catch(reportChatPrintFailure);
923
963
  let boundChatSession=null;
924
964
  let boundChatAI=null;
925
965
  let sessionBindingPending=false;
@@ -1852,6 +1892,8 @@
1852
1892
  host.bindConversationTimebox=bindConversationTimebox;
1853
1893
  host.bindSession=bindSession;
1854
1894
  host.submitMessage=submitMessage;
1895
+ host.appendDraft=appendDraft;
1896
+ host.print=print;
1855
1897
  host.submitToolResult=submitToolResult;
1856
1898
  host.submitToolResults=submitToolResults;
1857
1899
  Object.defineProperty(host,'pendingTool',{
@@ -3005,6 +3047,12 @@
3005
3047
  {signal:aiRuntimeStateAbortController.signal}
3006
3048
  );
3007
3049
 
3050
+ textArea.addEventListener(
3051
+ 'input',
3052
+ trackRecognitionDraftEdit,
3053
+ {signal:aiRuntimeStateAbortController.signal}
3054
+ );
3055
+
3008
3056
  textArea.addEventListener(
3009
3057
  'input',
3010
3058
  resizeTextArea,
@@ -3084,9 +3132,29 @@
3084
3132
  {signal:aiRuntimeStateAbortController.signal}
3085
3133
  );
3086
3134
 
3135
+ speech.addEventListener(
3136
+ 'speech-transcription-progress',
3137
+ receiveRecognitionDraft,
3138
+ {signal:aiRuntimeStateAbortController.signal}
3139
+ );
3140
+ speech.addEventListener(
3141
+ 'speech-transcription-cancelled',
3142
+ finishRecognitionDraft,
3143
+ {signal:aiRuntimeStateAbortController.signal}
3144
+ );
3087
3145
  speech.addEventListener(
3088
3146
  'speech-transcription-complete',
3089
- function submitTranscribedChatMessage(event){
3147
+ function receiveTranscribedChatMessage(event){
3148
+ if(recognitionDraft&&recognitionDraft.operationId===event.detail?.operationId){
3149
+ const completedDraft=recognitionDraft;
3150
+ receiveRecognitionDraft(event);
3151
+ if(recognitionDraft===completedDraft) recognitionDraft=null;
3152
+ return;
3153
+ }
3154
+ if(host.transcriptionMode==='draft'){
3155
+ appendDraft(event.detail?.text??'');
3156
+ return;
3157
+ }
3090
3158
  void submitMessage(
3091
3159
  event.detail?.text||'',
3092
3160
  {source:'speech'}
@@ -3170,6 +3238,140 @@
3170
3238
  resizeTextArea();
3171
3239
  }
3172
3240
 
3241
+ function trackRecognitionDraftEdit(){
3242
+ if(!recognitionDraft||writingRecognitionDraft){
3243
+ return;
3244
+ }
3245
+ const state=recognitionDraft;
3246
+ const before=state.value;
3247
+ const after=textArea.value;
3248
+ let start=0;
3249
+ while(start<before.length&&start<after.length&&before[start]===after[start]) start++;
3250
+ let oldEnd=before.length;
3251
+ let newEnd=after.length;
3252
+ while(oldEnd>start&&newEnd>start&&before[oldEnd-1]===after[newEnd-1]){
3253
+ oldEnd--;
3254
+ newEnd--;
3255
+ }
3256
+ if(state.range&&before!==after){
3257
+ const range=state.range;
3258
+ if(oldEnd<=range.start){
3259
+ const shift=newEnd-oldEnd;
3260
+ range.start+=shift;
3261
+ range.end+=shift;
3262
+ }else if(start<range.end){
3263
+ // An intentional edit promotes the hypothesis to user text.
3264
+ // Later recognition must not overwrite that edit.
3265
+ state.range=null;
3266
+ state.editedInterim=true;
3267
+ }
3268
+ }
3269
+ state.value=after;
3270
+ }
3271
+
3272
+ function replaceRecognitionDraft(text,finalLength,state=recognitionDraft){
3273
+ const existing=textArea.value;
3274
+ const range=state.range??{start:existing.length,end:existing.length};
3275
+ const selectionStart=textArea.selectionStart;
3276
+ const selectionEnd=textArea.selectionEnd;
3277
+ const direction=textArea.selectionDirection;
3278
+ const editing=shadowRoot.activeElement===textArea;
3279
+ const wasWriting=writingRecognitionDraft;
3280
+ writingRecognitionDraft=true;
3281
+ try{
3282
+ textArea.value=existing.slice(0,range.start)+text+existing.slice(range.end);
3283
+ if(editing){
3284
+ function moveSelection(position){
3285
+ if(position<=range.start) return position;
3286
+ if(position>=range.end) return position+text.length-(range.end-range.start);
3287
+ return range.start+text.length;
3288
+ }
3289
+ textArea.setSelectionRange(moveSelection(selectionStart),moveSelection(selectionEnd),direction);
3290
+ }
3291
+ state.range={start:range.start+finalLength,end:range.start+text.length};
3292
+ state.value=textArea.value;
3293
+ textArea.dispatchEvent(new Event('input',{bubbles:true,composed:true}));
3294
+ }finally{
3295
+ writingRecognitionDraft=wasWriting;
3296
+ }
3297
+ }
3298
+
3299
+ function receiveRecognitionDraft(event){
3300
+ if(destroyed||(!recognitionDraft&&host.transcriptionMode!=='draft')) return;
3301
+ const {text='',interim='',operationId=null}=event.detail??{};
3302
+ if(recognitionDraft&&recognitionDraft.operationId!==operationId){
3303
+ finishRecognitionDraft();
3304
+ // A synchronous input observer may already own a newer capture.
3305
+ if(recognitionDraft) return;
3306
+ }
3307
+ if(!recognitionDraft){
3308
+ recognitionDraft={
3309
+ operationId,
3310
+ finalText:'',
3311
+ value:textArea.value,
3312
+ range:null,
3313
+ separator:'',
3314
+ started:false,
3315
+ editedInterim:false
3316
+ };
3317
+ }
3318
+ trackRecognitionDraftEdit();
3319
+ const state=recognitionDraft;
3320
+ const finalText=text.startsWith(state.finalText)?text.slice(state.finalText.length):text;
3321
+ state.finalText=text;
3322
+ if(state.editedInterim){
3323
+ if(!finalText) return;
3324
+ // The edited words stand in place of this finalized hypothesis.
3325
+ state.editedInterim=false;
3326
+ state.started=false;
3327
+ state.separator='';
3328
+ if(!interim) return;
3329
+ receiveRecognitionDraft({detail:{text,interim,operationId}});
3330
+ return;
3331
+ }
3332
+ if(!state.started&&(finalText||interim)){
3333
+ const incoming=finalText||interim;
3334
+ state.separator=textArea.value&&!/\s$/.test(textArea.value)&&!/^\s/.test(incoming)?'\n':'';
3335
+ state.started=true;
3336
+ }
3337
+ const separator=state.separator;
3338
+ const replacement=finalText||interim?separator+finalText+interim:'';
3339
+ if(finalText) state.separator='';
3340
+ replaceRecognitionDraft(replacement,finalText?separator.length+finalText.length:0,state);
3341
+ }
3342
+
3343
+ function finishRecognitionDraft(event){
3344
+ if(!recognitionDraft||(event&&recognitionDraft.operationId!==event.detail?.operationId)) return;
3345
+ trackRecognitionDraftEdit();
3346
+ const completedDraft=recognitionDraft;
3347
+ recognitionDraft=null;
3348
+ if(completedDraft.range){
3349
+ replaceRecognitionDraft('',0,completedDraft);
3350
+ }
3351
+ }
3352
+
3353
+ function appendDraft(text){
3354
+ if(!is.string(text)){
3355
+ throw new TypeError('Chat draft text must be a string.');
3356
+ }
3357
+ if(destroyed||text===''){
3358
+ return false;
3359
+ }
3360
+ const existing=textArea.value;
3361
+ const selectionStart=textArea.selectionStart;
3362
+ const selectionEnd=textArea.selectionEnd;
3363
+ const selectionDirection=textArea.selectionDirection;
3364
+ const editing=shadowRoot.activeElement===textArea;
3365
+ // Join distinct entries without changing either entry's authored text.
3366
+ const separator=existing&&!/\s$/.test(existing)&&!/^\s/.test(text)?'\n':'';
3367
+ textArea.value=existing+separator+text;
3368
+ if(editing){
3369
+ textArea.setSelectionRange(selectionStart,selectionEnd,selectionDirection);
3370
+ }
3371
+ textArea.dispatchEvent(new Event('input',{bubbles:true,composed:true}));
3372
+ return true;
3373
+ }
3374
+
3173
3375
  async function resizeTextArea() {
3174
3376
  // Reset height to shrink to content size
3175
3377
  trackTranscriptScrollPosition();
@@ -3934,6 +4136,24 @@
3934
4136
  return message;
3935
4137
  }
3936
4138
 
4139
+ async function print(){
4140
+ if(destroyed) return false;
4141
+ try{
4142
+ const view=await printViewPromise;
4143
+ return destroyed||!view?false:await view.print();
4144
+ }catch(error){
4145
+ reportChatPrintFailure(error);
4146
+ throw error;
4147
+ }
4148
+ }
4149
+
4150
+ function reportChatPrintFailure(error){
4151
+ arcaneLogging.error('Unable to print the conversation.',error);
4152
+ if(!destroyed){
4153
+ setSessionStatus('error','Unable to open print preview. Please try again.');
4154
+ }
4155
+ }
4156
+
3937
4157
  function reportTTSError(error,boundary='synthesis'){
3938
4158
  if(destroyed){
3939
4159
  return false;
@@ -4001,9 +4221,10 @@
4001
4221
  if(destroyed){
4002
4222
  return false;
4003
4223
  }
4224
+ destroyed=true;
4225
+ finishRecognitionDraft();
4004
4226
  sessionBindingGeneration+=1;
4005
4227
  setSessionStatus('destroyed','Chat closed.');
4006
- destroyed=true;
4007
4228
  transcriptResizeObserver?.disconnect();
4008
4229
  transcriptMutationObserver?.disconnect();
4009
4230
  const destroyReason=createChatSubmissionAbort(