arcane-os 0.42.1 → 0.43.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +1 -1
  3. package/docs/reference/runtime-components.md +63 -10
  4. package/docs/reference/runtime-modules.md +68 -3
  5. package/package.json +1 -1
  6. package/runtime/arcane/components/browser-ai-setup.html +1 -1
  7. package/runtime/arcane/components/calculator.html +1 -1
  8. package/runtime/arcane/components/chart.html +1 -1
  9. package/runtime/arcane/components/chat.html +2 -2
  10. package/runtime/arcane/components/conversation-view.html +1 -1
  11. package/runtime/arcane/components/dashboard-config.html +4 -4
  12. package/runtime/arcane/components/data-maintenance.html +3 -3
  13. package/runtime/arcane/components/data-view.html +138 -53
  14. package/runtime/arcane/components/directory-picker.html +2 -2
  15. package/runtime/arcane/components/file-drop.html +1 -1
  16. package/runtime/arcane/components/file-inspector.html +1 -1
  17. package/runtime/arcane/components/file-manager.html +217 -35
  18. package/runtime/arcane/components/integration-settings.html +1 -1
  19. package/runtime/arcane/components/local-ai-status.html +1 -1
  20. package/runtime/arcane/components/markdown-document.html +4 -4
  21. package/runtime/arcane/components/markdown-editor.html +72 -2
  22. package/runtime/arcane/components/media-embed.html +1 -1
  23. package/runtime/arcane/components/modal.html +9 -5
  24. package/runtime/arcane/components/output-panel.html +1 -1
  25. package/runtime/arcane/components/preferences-form.html +1 -1
  26. package/runtime/arcane/components/pwa-install.html +2 -2
  27. package/runtime/arcane/components/screen-capture.html +1 -1
  28. package/runtime/arcane/components/source-code-viewer.html +3 -3
  29. package/runtime/arcane/components/speech.html +1 -1
  30. package/runtime/arcane/components/summary-strip.html +1 -1
  31. package/runtime/arcane/components/table.html +1 -1
  32. package/runtime/arcane/components/task-progress.html +1 -1
  33. package/runtime/arcane/components/terminal-workspace.html +1 -1
  34. package/runtime/arcane/components/theme-editor.html +1 -1
  35. package/runtime/arcane/components/unified-inbox.html +1 -1
  36. package/runtime/arcane/components/voice-transcription.html +2 -2
  37. package/runtime/arcane/components/weather-widget.html +1 -1
  38. package/runtime/arcane/components/web-navigator.html +2 -2
  39. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +1 -1
  40. package/runtime/arcane/modules/CommunicationAppController.js +2 -2
  41. package/runtime/arcane/modules/CommunicationHub.js +1 -1
  42. package/runtime/arcane/modules/ConversationClosingReport.js +1 -1
  43. package/runtime/arcane/modules/Errors.js +1 -1
  44. package/runtime/arcane/modules/HTMLImport.js +19 -38
  45. package/runtime/arcane/modules/LocalAIReadinessController.js +1 -1
  46. package/runtime/arcane/modules/PrintView.js +625 -0
  47. package/runtime/arcane/modules/ScamRiskPolicy.js +1 -1
  48. package/src/dev-server.mjs +2 -2
  49. package/src/import-map.mjs +66 -145
  50. package/src/packager/core.mjs +3 -4
  51. package/src/templates/workspace-template.mjs +5 -5
  52. package/src/workspace-runtime.mjs +6 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.43.0
4
+
5
+ - Add reusable rendered printing through `createPrintView`, preserving the
6
+ complete rendered content, title, styles and prepared image/font resources.
7
+ Explicit print requests await preparation; native printing uses the active
8
+ view. Resources remain owned through `afterprint`.
9
+ - Add Markdown editor `print()` and `insertMarkdown(text)` and file-manager
10
+ `printPreview()`. Insertion replaces the current selection through the
11
+ ordinary full-content change lifecycle; printing does not save the document.
12
+ - Add reusable history/file-manager `close()` with child-first closure and
13
+ pending-open cancellation. Refused or failed closure preserves the settled
14
+ visible preview, media and printing until its dialog actually closes.
15
+ Preserve modal focus restoration and existing saved records without migration.
16
+
17
+ ## 0.42.2
18
+
19
+ - Stop generating SDK cache suffixes in import maps, materialized runtime,
20
+ source serving, packaged resources, scaffold references and shared components.
21
+ Remove retired local `arcaneVersion` fields while preserving complete
22
+ functional queries, including caller-owned `v`, URL spelling and fragments.
23
+ - Preserve external URLs and resources under an external document base,
24
+ public method signatures, semantic version metadata, conditional HTTP caching
25
+ and stable application caches. Regenerate managed references through the
26
+ public package workflow after updating the SDK.
27
+
3
28
  ## 0.42.1
4
29
 
5
30
  - Keep Enter as native newline input on phones and tablets using the shared
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.42.1` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.43.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
 
@@ -67,17 +67,17 @@ appropriate.
67
67
  | [`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 |
68
68
  | [`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 |
69
69
  | [`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 |
70
- | [`data-view.html`](#data-viewhtml) | Opens a modal file view and forwards application-owned preview and directory-filter callbacks. | `beforeOpen()`<br>`open()`<br>`destroy()` | `data-view-ready` | DOM-native result; preview/filter configuration reaches the child before loading |
70
+ | [`data-view.html`](#data-viewhtml) | Opens a modal file view and forwards application-owned preview and directory-filter callbacks. | `beforeOpen()`<br>`open()`<br>`close()`<br>`printPreview()`<br>`destroy()` | `data-view-ready` | DOM-native result; preview/filter configuration reaches the child before loading |
71
71
  | [`directory-picker.html`](#directory-pickerhtml) | Presents the provider-owned OS directory chooser with change/cancel/error states. | `configure()`<br>`focus()`<br>`select()`<br>`destroy()` | `directory-picker-ready`<br>`directory-picker-change`<br>`directory-picker-cancel`<br>`directory-picker-error` | Complete plain-text paths and native selection/error |
72
72
  | [`document-inspector.html`](#document-inspectorhtml) | Inspects PDF, text, or source documents and records review state. | `loadDocument()`<br>`selectView()`<br>`markSaved()`<br>`destroy()` | `document-inspector-ready`<br>`document-review-change` | Document state normalized; browser document APIs mixed |
73
73
  | [`file-drop.html`](#file-drophtml) | Acquires complete file selections by drag/drop or picker and presents busy, progress, error, and cleared state. | `configure()`<br>`openPicker()`<br>`clear()`<br>`setBusy()`<br>`setError()`<br>`setProgress()`<br>`destroy()` | `file-drop-ready`<br>`file-drop-selected`<br>`file-drop-progress`<br>`file-drop-state`<br>`file-drop-error` | Complete selections preserved; browser File/drop errors mixed |
74
74
  | [`file-inspector.html`](#file-inspectorhtml) | Displays file metadata, preview, busy/error state, and caller-defined actions. | `configure()`<br>`show()`<br>`clear()`<br>`setActions()`<br>`setBusy()`<br>`setError()`<br>`setPreview()`<br>`destroy()` | `file-inspector-ready`<br>`file-inspector-action`<br>`file-inspector-change`<br>`file-inspector-cleared`<br>`file-inspector-error` | State normalized; preview/provider behavior mixed |
75
- | [`file-manager.html`](#file-managerhtml) | Browses app-scoped files, filters directories before reads, and renders application-described previews. | `setProvider()`<br>`loadAll()`<br>`setFilter()`<br>`setDirectoryFilter()`<br>`select()`<br>`clearSelection()`<br>`destroy()` | `file-manager-ready`<br>`file-manager-select`<br>`file-manager-open`<br>`file-manager-action` | Selection/filter state, neutral preview descriptors, cancellation, and media cleanup normalized; storage/provider behavior mixed |
75
+ | [`file-manager.html`](#file-managerhtml) | Browses app-scoped files, filters directories before reads, and renders application-described previews. | `setProvider()`<br>`loadAll()`<br>`setFilter()`<br>`setDirectoryFilter()`<br>`select()`<br>`clearSelection()`<br>`close()`<br>`printPreview()`<br>`destroy()` | `file-manager-ready`<br>`file-manager-select`<br>`file-manager-open`<br>`file-manager-action` | Selection/filter state, neutral preview descriptors, cancellation, and media cleanup normalized; storage/provider behavior mixed |
76
76
  | [`header.html`](#headerhtml) | Shared title bar with history, reload, online marker, presentation labels, and 988 link. | None | No component-specific event | Browser/platform-native behavior; no component-ready contract |
77
77
  | [`integration-settings.html`](#integration-settingshtml) | Edits non-secret communication service configuration and service actions. | `configure()`<br>`getValues()`<br>`setStatus()`<br>`destroy()` | `integration-settings-ready`<br>`integration-settings-save`<br>`integration-settings-close`<br>`integration-action` | Normalized non-secret values |
78
78
  | [`local-ai-status.html`](#local-ai-statushtml) | Presents local-AI standby, failure, recovery, guidance, retry, and dismissal states. | `configure()`<br>`begin()`<br>`present()`<br>`destroy()`<br>`hidden` | `local-ai-status-ready`<br>`local-ai-status-dismissed`<br>`local-ai-retry` | Fully normalized LocalAIReadiness report |
79
79
  | [`markdown-document.html`](#markdown-documenthtml) | Renders and navigates a complete Markdown document with focusable fragments. | `configure()`<br>`load()`<br>`render()`<br>`clear()`<br>`fail()`<br>`focus()`<br>`focusFragment()`<br>`destroy()` | `markdown-document-ready`<br>`markdown-document-state`<br>`markdown-document-loading`<br>`markdown-document-rendered`<br>`markdown-document-empty`<br>`markdown-document-error`<br>`markdown-document-navigate` | Complete Markdown/state normalized; malformed input and Marked/DOM failures remain visible |
80
- | [`markdown-editor.html`](#markdown-editorhtml) | Configurable Markdown authoring with optional bounded input/preview panes and edit-position following. | `configure()`<br>`focus()`<br>`clear()`<br>`saveEntry()`<br>`destroy()` | `markdown-editor-ready`<br>`markdown-editor-change`<br>`markdown-editor-saved` | Editor values, opt-in fit layout, and preview following normalized; injected save result mixed |
80
+ | [`markdown-editor.html`](#markdown-editorhtml) | Configurable Markdown authoring with optional bounded input/preview panes, edit-position following, and rendered printing. | `configure()`<br>`focus()`<br>`clear()`<br>`insertMarkdown()`<br>`saveEntry()`<br>`print()`<br>`destroy()` | `markdown-editor-ready`<br>`markdown-editor-change`<br>`markdown-editor-saved` | Editor values, opt-in fit layout, and preview following normalized; injected save result mixed |
81
81
  | [`media-embed.html`](#media-embedhtml) | Loads a parsed YouTube video or playlist embed with ordinary hosting by default, optional privacy enhancement, and an external-platform action. | `configure()`<br>`load()`<br>`destroy()` | `media-embed-ready`<br>`media-load`<br>`media-error`<br>`media-open-platform` | URL/error normalized; iframe/platform behavior native |
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 |
@@ -663,7 +663,7 @@ own record mappings through the same preview callbacks as the standalone
663
663
 
664
664
  ### Public surface
665
665
 
666
- Methods/properties: `beforeOpen()`, `open()`, `destroy()`.
666
+ Methods/properties: `beforeOpen()`, `open()`, `close()`, `printPreview()`, `destroy()`.
667
667
 
668
668
  Assign `previewDescriptor`, `previewTransform`, and `directoryFilter` on the
669
669
  host before opening. The component forwards those callbacks, comma-separated
@@ -673,6 +673,20 @@ before the child's first load. Reopening refreshes those values before
673
673
  `beforeOpen()` is awaited before this configuration is consumed. `data-dirs`
674
674
  selects the initial directories; `data-layout` defaults to `files`.
675
675
 
676
+ `await close()` invalidates pending opens, closes the child's dialogs before the
677
+ parent, and returns `true` only after closure. It returns `false` when destroyed
678
+ or a child/parent cannot close; complete errors reach shared diagnostics. It is
679
+ reusable: a later `open()` reloads the existing child. Repeated closes share the
680
+ pending closure. `destroy()` remains terminal.
681
+
682
+ An application that loads a history selection into an editor must synchronously
683
+ call `preventDefault()` on the existing cancelable `file-manager-open` event
684
+ before awaiting its own save/load work. That application owns record selection,
685
+ autosave, title, and editor focus; call `close()` after loading and restore focus
686
+ after it resolves `true`. No record-loading policy is added to the SDK.
687
+ `printPreview()` forwards to the currently open child preview and returns
688
+ `false` when no such preview is available.
689
+
676
690
  Events: `data-view-ready`.
677
691
 
678
692
  Shared dependencies: [`WaitForComponent.js`](runtime-modules.md#waitforcomponentjs).
@@ -821,7 +835,7 @@ and `destroy()` cleanup, without delaying unrelated component startup.
821
835
  ### Public surface
822
836
 
823
837
  Methods/properties: `setProvider()`, `loadAll()`, `setFilter()`,
824
- `setDirectoryFilter()`, `select()`, `clearSelection()`, `destroy()`,
838
+ `setDirectoryFilter()`, `select()`, `clearSelection()`, `close()`, `printPreview()`, `destroy()`,
825
839
  `previewDescriptor`, `previewTransform`, `directoryFilter`.
826
840
 
827
841
  Assign `directoryFilter(entry, context)` before the first load, or call
@@ -879,8 +893,9 @@ component does not automatically read every related media variant.
879
893
 
880
894
  Closing the preview, opening another file, or destroying the manager aborts
881
895
  the preview signal, ignores late descriptor/loader results, stops owned audio,
882
- removes iframe `srcdoc`, and revokes every owned object URL, including those
883
- in nested collections.
896
+ removes iframe `srcdoc`, and releases owned object URLs, including those in
897
+ nested collections. An active print snapshot retains its URLs until the
898
+ browser's `afterprint` event, even when the preview closes meanwhile.
884
899
  Applications should observe the supplied signal in their asynchronous reads.
885
900
  Descriptor and file-open failures show `Unable to open this file.` in the
886
901
  current modal and send the original error to the shared logger.
@@ -900,6 +915,26 @@ of the original `File`.
900
915
  The larger file-preview dialog reuses `--modal-width`, `--modal-max-height`,
901
916
  and `--modal-padding`; applications can override those existing modal values.
902
917
 
918
+ The preview's **Print** button and `await printPreview()` print its current
919
+ rendered content with the file title. Native browser Print selects the open
920
+ preview ahead of the editor. Printing uses the shared
921
+ [`PrintView.js`](runtime-modules.md#printviewjs) owner; accessible HTML frames
922
+ are copied from their current rendered document, never substituted with source
923
+ text. Explicit printing waits for images/fonts and HTML frame readiness.
924
+ Native PDF and inaccessible embedded viewers require their own print command.
925
+ `true` means the browser print dialog was requested, not that paper or a PDF
926
+ was produced. Closed, destroyed, or unavailable previews return `false`;
927
+ preparation failures reject and the visible control reports a concise failure.
928
+
929
+ `await close()` cancels pending presentation work and closes delete, file, and
930
+ directory dialogs in that order. A settled preview keeps its rendered content,
931
+ media, and printing available until the file dialog actually closes; refusing
932
+ or failing to close leaves the still-visible preview usable. Actual file-dialog
933
+ closure releases its preview resources. The method returns `false` on
934
+ refused/failed closure or after destruction. The manager stays
935
+ reusable; a later opening can use it again. Already accepted upload/deletion
936
+ writes complete through the storage owner without reopening closed dialogs.
937
+
903
938
  Events: `file-manager-ready`, `file-manager-select`, `file-manager-open`, `file-manager-action`.
904
939
 
905
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).
@@ -1048,8 +1083,26 @@ inside an application-supplied bounded area.
1048
1083
 
1049
1084
  ### Public surface
1050
1085
 
1051
- Methods/properties: `configure()`, `focus()`, `clear()`, `saveEntry()`,
1052
- `destroy()`, `value`, `entryTitle`, `options`.
1086
+ Methods/properties: `configure()`, `focus()`, `clear()`, `insertMarkdown(text)`,
1087
+ `saveEntry()`, `print()`, `destroy()`, `value`, `entryTitle`, `options`.
1088
+
1089
+ `insertMarkdown(text)` replaces the current source selection with the exact
1090
+ supplied string, places the caret after it, and emits the ordinary change
1091
+ event. It preserves the source viewport and normal preview/save lifecycle.
1092
+ It returns `false` when read-only or destroyed and throws `TypeError` for a
1093
+ non-string. The application owns the inserted Markdown and any related data.
1094
+
1095
+ The visible **Print** control and `await print()` print the complete rendered
1096
+ Markdown with `entryTitle`, independently of saving. Native browser Print uses
1097
+ that same registered view unless an open file preview takes precedence.
1098
+ 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
1101
+ and uses the currently rendered content. `print()` returns `false` when the
1102
+ view is unavailable, rejects preparation failures, and returns `true` only
1103
+ when the browser dialog was requested. Completion of physical printing or PDF
1104
+ saving is not observable through this method. See
1105
+ [`PrintView.js`](runtime-modules.md#printviewjs).
1053
1106
 
1054
1107
  `configure({fit:true,followPreview:true})` opts into the two independent
1055
1108
  behaviors. Both options default to `false`; omitted options retain their
@@ -1087,7 +1140,7 @@ lists, quotes, and tables follow their block, while invisible source positions
1087
1140
  use a nearby available rendered block rather than exact glyph geometry.
1088
1141
 
1089
1142
  Public CSS parts are `editor`, `title`, `toolbar`, `input`, `preview`,
1090
- `actions`, `status`, and `save-action`. Applications can make the title
1143
+ `actions`, `status`, `print-action`, and `save-action`. Applications can make the title
1091
1144
  borderless and compact through `::part(title)` while retaining its native
1092
1145
  editing behavior and visible keyboard focus. These parts are styling hooks,
1093
1146
  not slots or replacement controls.
@@ -109,6 +109,7 @@ own asynchronous work, cancellation, and backpressure.
109
109
  | [`PersistentAIChatSession.js`](#persistentaichatsessionjs) | esm | Adds explicit retained-history/memory policy to complete configured chat without changing DBOPFS or ChatEntity semantics. | Browser / native WebView with DBOPFS and configured chat | Retained context commits atomically; `persist:false` turns are one-operation-only. |
110
110
  | [`PreferenceStore.js`](#preferencestorejs) | esm | Loads and updates schema-defined app preferences through native storage with a narrow browser fallback. | Browser/native hybrid | Complete ordinary values remain mutable; setAll uses one optional atomic adapter batch for every selected value when advertised, otherwise performs complete ordered serial writes, and only exact unsupported native capability changes future operations to the browser fallback. |
111
111
  | [`PreparedSpeech.js`](#preparedspeechjs) | esm | Owns detached ordered preparation, semantic audio reuse, and per-caller cancellation behind AI.prepareTTS. | Browser / native WebView with injected synthesis and optional DBOPFS | Complete original inputs, ordered audio metadata, durable reuse, and observable preparation results. |
112
+ | [`PrintView.js`](#printviewjs) | esm | Prints current rendered content and title through one document-owned print lifecycle. | Browser / supported native WebView print implementation | Complete rendered snapshot, preparation cancellation, resource lifetime, and honest request result. |
112
113
  | [`QRCode.min.js`](#qrcodeminjs) | classic-script | Vendored QRCode generator for DOM, canvas, SVG, and image output. | Browser vendor script | Vendor-native. |
113
114
  | [`Questionnaire.js`](#questionnairejs) | esm | Evaluates whether a one-time questionnaire prompt is due without performing the prompt. | Cross-host | Normalized conservative boolean. |
114
115
  | [`RecordLinkIndex.js`](#recordlinkindexjs) | esm | Parses record links and builds their normalized index. | Cross-host | Fully normalized. |
@@ -2444,9 +2445,10 @@ module URLs reuse and export the registered constructor, including overlapping
2444
2445
  application startup and developer error-dialog imports. Existing component
2445
2446
  instances, loading, readiness events, and teardown keep that same constructor.
2446
2447
 
2447
- Local component resources use the loader's `arcaneVersion` as their sole version
2448
- field. Existing `v` fields are removed; unrelated query fields and fragments
2449
- remain intact.
2448
+ Local component resources use stable URLs. The loader removes retired SDK-owned
2449
+ `arcaneVersion` fields and adds no cache suffix. Functional query fields,
2450
+ including caller-owned `v` fields, and fragments remain intact. External resource
2451
+ URLs retain their complete query. Ordinary HTTP conditional caching is unchanged.
2450
2452
 
2451
2453
  Exact exports: `default`.
2452
2454
 
@@ -3250,6 +3252,69 @@ import {prepareSpeech} from '/arcane/modules/PreparedSpeech.js';
3250
3252
  console.log(typeof prepareSpeech); // function
3251
3253
  ```
3252
3254
 
3255
+ ## PrintView.js
3256
+
3257
+ ### Overview
3258
+
3259
+ Shared rendered-print owner used by Markdown Editor and File Manager. It
3260
+ captures the current DOM and computed styles, including accessible rendered
3261
+ HTML frames, complete text, selected images, and the current title. A temporary
3262
+ print-only surface expands scrollable content without editing the live screen.
3263
+
3264
+ ### Public surface
3265
+
3266
+ Named `createPrintView({host,content,title,active,priority=0,signal,onError,retain})`
3267
+ returns `{print,destroy}`. `host` is a connected element; synchronous callbacks
3268
+ return the rendered content node, string title, and whether the view is active.
3269
+ Optional `retain()` returns a resource-release callback. It keeps owned media
3270
+ available until preparation is cancelled or the browser emits `afterprint`.
3271
+
3272
+ `await print()` selects that view, waits for accessible HTML frame, image, and
3273
+ font readiness, and requests the browser print dialog. It returns `false` when
3274
+ the view is unavailable or preparation is cancelled, returns `true` for a dialog
3275
+ request, and rejects preparation errors. It cannot confirm physical printing
3276
+ or PDF saving. Overlapping print sessions on the same document reject.
3277
+
3278
+ One native `beforeprint`/`afterprint` registration serves each document. Native
3279
+ Print chooses the highest-priority visible active view, most recently registered
3280
+ on ties. File preview uses priority `1`; editor uses `0`. Native `beforeprint`
3281
+ is synchronous: it snapshots currently rendered content, cannot await a newly
3282
+ loading resource, and cannot cancel the browser's dialog. `onError(error)` or
3283
+ the developer console receives complete preparation failures.
3284
+
3285
+ `destroy()` unregisters the view and cancels pending preparation. Once the
3286
+ dialog has been requested, its snapshot and retained media remain until
3287
+ `afterprint`. Import alone creates no listener, provider, storage, or print job.
3288
+
3289
+ Exact exports: `createPrintView`.
3290
+
3291
+ ### Availability and normalization
3292
+
3293
+ **Browser and native WebViews with a print implementation.** Requires DOM,
3294
+ CSSOM, FontFace/Image readiness, and browser print events. No Core capability.
3295
+ Cross-origin/inaccessible frames, native PDF frames, and object/embed viewers
3296
+ must use their owning viewer's print command; the helper does not replace their
3297
+ content with raw source or claim an unrendered document was printed. Font
3298
+ sources in embedded documents must be accessible for snapshot preparation.
3299
+
3300
+ ### Example
3301
+
3302
+ ```javascript
3303
+ import {createPrintView} from '/arcane/modules/PrintView.js';
3304
+
3305
+ const ledger = document.querySelector('#moon-library-ledger');
3306
+ const printing = createPrintView({
3307
+ host:ledger,
3308
+ content:() => ledger,
3309
+ title:() => 'Books overdue on the Moon',
3310
+ active:() => true
3311
+ });
3312
+ document.querySelector('#print-ledger').onclick = async function printLedger(){
3313
+ try { await printing.print(); }
3314
+ catch(error) { console.error('Unable to print the ledger:', error); }
3315
+ };
3316
+ ```
3317
+
3253
3318
  ## QRCode.min.js
3254
3319
 
3255
3320
  ### Overview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.42.1",
3
+ "version": "0.43.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",
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css">
2
2
  <style>
3
3
  :host{display:block;min-width:0}
4
4
  .flag-choice {
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/theme.css?v=1"><link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
1
+ <link rel="stylesheet" href="./arcane/css/theme.css"><link rel="stylesheet" href="./arcane/css/primitives.css">
2
2
  <style>:host{display:block;max-width:34rem}.calculator{display:grid;gap:var(--arcane-space-3);padding:var(--arcane-space-4)}.display{display:grid;gap:var(--arcane-space-2)}.display input{font:600 1.3rem/1.4 ui-monospace,monospace;text-align:right}.result{min-height:2.5rem;margin:0;text-align:right;font-size:1.8rem;font-weight:750;overflow-wrap:anywhere}.keys{display:grid;gap:var(--arcane-space-2);grid-template-columns:repeat(5,1fr)}.keys button{min-width:0;padding:.7rem .35rem}.status{min-height:1.4rem;margin:0;color:var(--arcane-muted)}@media(max-width:28rem){.keys{grid-template-columns:repeat(4,1fr)}}</style>
3
3
  <section class="calculator arcane-card"><div class="display arcane-field"><label for="expression">Expression</label><input id="expression" inputmode="decimal" autocomplete="off" spellcheck="false" value=""><p id="result" class="result" aria-live="polite">0</p></div><div id="keys" class="keys" aria-label="Calculator keypad"></div><p id="status" class="status" role="status"></p></section>
4
4
  <script type="module">const host=this,root=host.shadowRoot,input=root.querySelector('#expression'),result=root.querySelector('#result'),status=root.querySelector('#status'),keys=root.querySelector('#keys');const {default:CalculatorEngine}=await import('../modules/CalculatorEngine.js');const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');const events=createArcaneEventSource(host,{source:'arcane.component.calculator',eventTypes:['calculation-complete','calculation-error','calculator-ready']}),lifecycleController=new AbortController();const engine=new CalculatorEngine(),definitions=['7','8','9','/','sqrt(','4','5','6','*','^','1','2','3','-','%','0','.','(',')','+','pi','e','C','⌫','='];let destroyed=false,operationSequence=0;
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/layout.css?v=3">
1
+ <link rel="stylesheet" href="./arcane/css/layout.css">
2
2
  <link rel="stylesheet" href="./arcane/modules/uPlot.min.css">
3
3
  <style>
4
4
  :host{
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/layout.css?v=3" />
1
+ <link rel="stylesheet" href="./arcane/css/layout.css" />
2
2
  <style>
3
3
  :host {
4
4
  display: block;
@@ -842,7 +842,7 @@
842
842
  speechImport.id='speech';
843
843
  speechImport.setAttribute(
844
844
  'href',
845
- new URL('./speech.html?v=3',componentURL).href
845
+ new URL('./speech.html',componentURL).href
846
846
  );
847
847
  speechMount.replaceWith(speechImport);
848
848
  const events=createArcaneEventSource(
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css">
2
2
  <style>
3
3
  :host{display:block;min-width:0;height:100%}.shell{display:grid;grid-template-rows:auto 1fr auto;height:100%;min-height:32rem}.heading{align-items:center;display:flex;gap:.75rem;justify-content:space-between;padding:1rem;border-bottom:1px solid var(--border-color,#ccd4df)}.heading h2,.heading p{margin:0}.heading p{color:var(--muted-text-color,#607080)}.badge{border:1px solid var(--border-color,#ccd4df);border-radius:999px;padding:.25rem .55rem;font-size:.72rem;text-transform:uppercase}.messages{display:flex;flex-direction:column;gap:.8rem;overflow:auto;padding:1rem}.message{align-self:flex-start;max-width:min(78%,42rem);padding:.75rem .9rem;border:1px solid var(--border-color,#ccd4df);border-radius:.9rem;background:var(--modal-background,#fff)}.message.outbound{align-self:flex-end;color:var(--button-text-color,#fff);background:var(--primary-color,#172238)}.message p{margin:0;white-space:pre-wrap;overflow-wrap:anywhere}.message footer{display:flex;gap:.5rem;justify-content:flex-end;margin-top:.4rem;font-size:.7rem;opacity:.72}.advisory{display:grid;gap:.45rem;margin-top:.7rem;padding:.7rem;border:2px solid var(--border-color,#ccd4df);border-radius:.65rem;background:var(--background-color,#f5f7fa);color:var(--text-color,#172238)}.advisory[data-level="critical"],.advisory[data-level="high"]{border-color:rgb(174,48,52)}.advisory[data-level="caution"]{border-color:rgb(190,120,34)}.advisory strong,.advisory p{margin:0}.advisory ul{margin:.1rem 0;padding-left:1.2rem}.advisory button{justify-self:start}.composer{display:grid;gap:.6rem;padding:1rem;border-top:1px solid var(--border-color,#ccd4df)}textarea{min-height:5rem;resize:vertical}.composer-row{align-items:center;display:flex;gap:.75rem;justify-content:space-between}.status{color:var(--muted-text-color,#607080);margin:0}.blank{display:grid;place-items:center;height:100%;padding:2rem;text-align:center}.blank p{color:var(--muted-text-color,#607080)}
4
4
  </style>
@@ -1,5 +1,5 @@
1
- <link rel="stylesheet" href="./arcane/css/layout.css?v=3">
2
- <link rel="stylesheet" href="./arcane/css/dashboard-config.css?v=2">
1
+ <link rel="stylesheet" href="./arcane/css/layout.css">
2
+ <link rel="stylesheet" href="./arcane/css/dashboard-config.css">
3
3
  <style>
4
4
  :host{
5
5
  display:block;
@@ -7,7 +7,7 @@
7
7
  </style>
8
8
 
9
9
  <button id="openConfig" type="button" aria-haspopup="dialog" disabled></button>
10
- <html-import id="configModal" class="modal" href="./arcane/components/modal.html?v=13"></html-import>
10
+ <html-import id="configModal" class="modal" href="./arcane/components/modal.html"></html-import>
11
11
 
12
12
  <script type="module">
13
13
  const {default:Is}=await import('strong-type');
@@ -233,7 +233,7 @@
233
233
  const items=document.createElement('section');
234
234
 
235
235
  styles.rel='stylesheet';
236
- styles.href=new URL('../css/dashboard-config.css?v=2',componentHref).href;
236
+ styles.href=new URL('../css/dashboard-config.css',componentHref).href;
237
237
  heading.dataset.dashboardConfigHeading='';
238
238
  message.dataset.dashboardConfigDescription='';
239
239
  empty.className='dashboard-config-empty';
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/layout.css?v=3">
1
+ <link rel="stylesheet" href="./arcane/css/layout.css">
2
2
  <style>
3
3
  :host{
4
4
  display:block;
@@ -10,7 +10,7 @@
10
10
  </style>
11
11
 
12
12
  <button id="openMaintenance" type="button" aria-haspopup="dialog">Maintenance</button>
13
- <html-import id="maintenanceModal" class="modal" href="./arcane/components/modal.html?v=13"></html-import>
13
+ <html-import id="maintenanceModal" class="modal" href="./arcane/components/modal.html"></html-import>
14
14
 
15
15
  <script type="module">
16
16
  const {arcaneLogging}=await import('arcane-os/logging');
@@ -19,7 +19,7 @@
19
19
  const openButton=host.shadowRoot.querySelector('#openMaintenance');
20
20
  const maintenanceModal=host.shadowRoot.querySelector('#maintenanceModal');
21
21
  const {default:waitForComponent}=await import('../modules/WaitForComponent.js');
22
- const {clearEmptyChatsAndMemories}=await import('../modules/DataMaintenance.js?v=3');
22
+ const {clearEmptyChatsAndMemories}=await import('../modules/DataMaintenance.js');
23
23
  const {createArcaneEventSource,projectArcaneDOMEvent}=await import('arcane-os/event-manager');
24
24
  const events=createArcaneEventSource(host,{source:'arcane.component.data-maintenance',eventTypes:['data-maintenance-complete','data-maintenance-ready']});
25
25
  const lifecycleController=new AbortController();
@@ -1,4 +1,4 @@
1
- <link rel="stylesheet" href="./arcane/css/layout.css?v=3">
1
+ <link rel="stylesheet" href="./arcane/css/layout.css">
2
2
  <style>
3
3
  :host{
4
4
  display:flex;
@@ -11,7 +11,7 @@
11
11
  </style>
12
12
 
13
13
  <button id="openData" type="button" aria-haspopup="dialog"></button>
14
- <html-import id="dataModal" class="modal" href="./arcane/components/modal.html?v=13"></html-import>
14
+ <html-import id="dataModal" class="modal" href="./arcane/components/modal.html"></html-import>
15
15
 
16
16
  <script type="module">
17
17
  const {default:Is}=await import('strong-type');
@@ -31,12 +31,16 @@
31
31
  let content=null;
32
32
  let fileManager=null;
33
33
  let operationGeneration=0;
34
+ let openController = null;
35
+ let closingTask = null;
34
36
  let destroyed=false;
35
37
 
36
38
  host.beforeOpen=is.function(host.beforeOpen)
37
39
  ?host.beforeOpen
38
40
  :async function beforeOpen(){};
39
41
  host.open=openData;
42
+ host.close = closeData;
43
+ host.printPreview=printPreview;
40
44
  host.destroy=destroy;
41
45
 
42
46
  openButton.innerText=host.dataset.button||'View Data';
@@ -52,80 +56,159 @@
52
56
  {signal:lifecycleController.signal}
53
57
  );
54
58
 
55
- async function openData(){
56
- if(destroyed){
59
+ dataModal.addEventListener(
60
+ 'modal-closed',
61
+ function closeDataFromModal(event) {
62
+ if (event.target === dataModal && !closingTask) {
63
+ closeData();
64
+ }
65
+ },
66
+ {signal: lifecycleController.signal}
67
+ );
68
+
69
+ async function openData() {
70
+ if (destroyed) {
57
71
  return false;
58
72
  }
59
- const generation=++operationGeneration;
60
- await host.beforeOpen();
61
- if(destroyed||generation!==operationGeneration){
73
+ const requestedGeneration=operationGeneration;
74
+ if (closingTask && !await closingTask) {
62
75
  return false;
63
76
  }
64
-
65
- const reload=Boolean(content);
66
-
67
- const modal=await waitForComponent(
68
- dataModal,
69
- {
70
- methods:['populate','open'],
71
- property:'ready',
72
- event:'modal-ready',
73
- signal:lifecycleController.signal
74
- }
75
- );
76
- if(destroyed||generation!==operationGeneration){
77
+ if (destroyed||requestedGeneration!==operationGeneration) {
77
78
  return false;
78
79
  }
80
+ const generation = ++operationGeneration;
81
+ openController?.abort('data-view-open-replaced');
82
+ const controller = new AbortController();
83
+ openController = controller;
84
+
85
+ try {
86
+ await host.beforeOpen();
87
+ if (destroyed || generation !== operationGeneration) {
88
+ return false;
89
+ }
79
90
 
80
- if(!content){
81
- content=document.createElement('section');
82
- fileManager=document.createElement('html-import');
91
+ const reload = Boolean(content);
92
+ const modal = await waitForComponent(
93
+ dataModal,
94
+ {
95
+ methods: ['populate', 'open'],
96
+ property: 'ready',
97
+ event: 'modal-ready',
98
+ signal: controller.signal
99
+ }
100
+ );
101
+ if (destroyed || generation !== operationGeneration) {
102
+ return false;
103
+ }
83
104
 
84
- const heading=document.createElement('h1');
105
+ if (!content) {
106
+ content = document.createElement('section');
107
+ fileManager = document.createElement('html-import');
108
+
109
+ const heading = document.createElement('h1');
110
+
111
+ heading.innerText = host.dataset.title || 'Data';
112
+ fileManager.className = 'file-manager';
113
+ forwardPreviewOptions();
114
+ fileManager.dataset.dirs = host.dataset.dirs || '';
115
+ fileManager.dataset.layout = host.dataset.layout || 'files';
116
+ fileManager.setAttribute(
117
+ 'href',
118
+ new URL('./file-manager.html', componentHref).href
119
+ );
120
+
121
+ content.append(heading, fileManager);
122
+ await modal.populate(content, false);
123
+ if (destroyed || generation !== operationGeneration) {
124
+ return false;
125
+ }
126
+ }
85
127
 
86
- heading.innerText=host.dataset.title||'Data';
87
- fileManager.className='file-manager';
88
- forwardPreviewOptions();
89
- fileManager.dataset.dirs=host.dataset.dirs||'';
90
- fileManager.dataset.layout=host.dataset.layout||'files';
91
- fileManager.setAttribute(
92
- 'href',
93
- new URL('./file-manager.html?v=19',componentHref).href
128
+ if (reload) {
129
+ forwardPreviewOptions();
130
+ }
131
+
132
+ const manager = await waitForComponent(
133
+ fileManager,
134
+ {
135
+ methods: ['loadAll'],
136
+ property: 'ready',
137
+ event: 'file-manager-ready',
138
+ signal: controller.signal
139
+ }
94
140
  );
141
+ if (destroyed || generation !== operationGeneration) {
142
+ return false;
143
+ }
95
144
 
96
- content.append(heading,fileManager);
97
- await modal.populate(content,false);
98
- if(destroyed||generation!==operationGeneration){
145
+ if (reload) {
146
+ await manager.loadAll();
147
+ if (destroyed || generation !== operationGeneration) {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ return await modal.open();
153
+ } catch (error) {
154
+ if (controller.signal.aborted) {
99
155
  return false;
100
156
  }
157
+ throw error;
158
+ } finally {
159
+ if (openController === controller) {
160
+ openController = null;
161
+ }
101
162
  }
163
+ }
102
164
 
103
- if(reload){
104
- forwardPreviewOptions();
165
+ async function closeData() {
166
+ if (destroyed) {
167
+ return false;
168
+ }
169
+ operationGeneration += 1;
170
+ openController?.abort('data-view-closed');
171
+ openController = null;
172
+ if (closingTask) {
173
+ return closingTask;
105
174
  }
106
175
 
107
- const manager=await waitForComponent(
108
- fileManager,
109
- {
110
- methods:['loadAll'],
111
- property:'ready',
112
- event:'file-manager-ready',
113
- signal:lifecycleController.signal
114
- }
115
- );
116
- if(destroyed||generation!==operationGeneration){
117
- return false;
176
+ const childClosing = is.function(fileManager?.close)
177
+ ? fileManager.close()
178
+ : Promise.resolve(true);
179
+ closingTask = Promise.resolve(childClosing)
180
+ .then(closeDataAfterChildren)
181
+ .catch(
182
+ function reportDataCloseFailure(error) {
183
+ arcaneLogging.error('Unable to close data view:', error);
184
+ return false;
185
+ }
186
+ );
187
+ try {
188
+ return await closingTask;
189
+ } finally {
190
+ closingTask = null;
118
191
  }
192
+ }
119
193
 
120
- if(reload){
121
- await manager.loadAll();
122
- if(destroyed||generation!==operationGeneration){
194
+ async function closeDataAfterChildren(childrenClosed) {
195
+ if (childrenClosed === false || destroyed) {
196
+ return false;
197
+ }
198
+ if (is.function(dataModal.close)) {
199
+ const closed = await dataModal.close();
200
+ if (closed === false) {
123
201
  return false;
124
202
  }
125
203
  }
204
+ return !destroyed && dataModal.opened !== true;
205
+ }
126
206
 
127
- modal.open();
128
- return true;
207
+ async function printPreview(){
208
+ if(destroyed||dataModal.opened!==true||!is.function(fileManager?.printPreview)){
209
+ return false;
210
+ }
211
+ return fileManager.printPreview();
129
212
  }
130
213
 
131
214
  function forwardPreviewOptions(){
@@ -146,6 +229,8 @@
146
229
  }
147
230
  destroyed=true;
148
231
  operationGeneration+=1;
232
+ openController?.abort('data-view-destroyed');
233
+ openController = null;
149
234
  lifecycleController.abort('data-view-destroyed');
150
235
  fileManager?.destroy?.();
151
236
  dataModal.destroy?.();