arcane-os 0.1.0-dev.5

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 (248) hide show
  1. package/CHANGELOG.md +154 -0
  2. package/COMMERCIAL-LICENSE.md +13 -0
  3. package/LICENSE +661 -0
  4. package/NOTICE +70 -0
  5. package/README.md +448 -0
  6. package/bin/arcane-test.mjs +741 -0
  7. package/bin/arcane.mjs +5 -0
  8. package/docs/architecture.md +234 -0
  9. package/docs/compatibility.md +36 -0
  10. package/docs/event-manager.md +166 -0
  11. package/docs/platform-targets.md +108 -0
  12. package/docs/publishing.md +203 -0
  13. package/docs/reference/README.md +117 -0
  14. package/docs/reference/arcane-ollama.md +288 -0
  15. package/docs/reference/availability-and-normalization.md +123 -0
  16. package/docs/reference/behavioral-testing.md +86 -0
  17. package/docs/reference/cli.md +569 -0
  18. package/docs/reference/core/README.md +62 -0
  19. package/docs/reference/core/arcane-ai-contracts.md +873 -0
  20. package/docs/reference/core/arcane-api.md +601 -0
  21. package/docs/reference/core/arcane-entities.md +65 -0
  22. package/docs/reference/core/arcane-events.md +134 -0
  23. package/docs/reference/core/ollama-module.md +181 -0
  24. package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
  25. package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
  26. package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
  27. package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
  28. package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
  29. package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
  30. package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
  31. package/docs/reference/event-manager.md +957 -0
  32. package/docs/reference/inventory/package-api.json +2632 -0
  33. package/docs/reference/inventory/runtime-components.json +934 -0
  34. package/docs/reference/inventory/runtime-entities.json +26 -0
  35. package/docs/reference/inventory/runtime-modules.json +1249 -0
  36. package/docs/reference/protocols.md +242 -0
  37. package/docs/reference/runtime-components.md +1098 -0
  38. package/docs/reference/runtime-entities.md +303 -0
  39. package/docs/reference/runtime-modules.md +2010 -0
  40. package/docs/reference/sdk-api.md +4901 -0
  41. package/docs/roadmap.md +79 -0
  42. package/docs/work-amplification.md +124 -0
  43. package/node_modules/event-pubsub/CHANGELOG.md +55 -0
  44. package/node_modules/event-pubsub/MIGRATION.md +70 -0
  45. package/node_modules/event-pubsub/README.md +363 -0
  46. package/node_modules/event-pubsub/SECURITY.md +37 -0
  47. package/node_modules/event-pubsub/index.js +141 -0
  48. package/node_modules/event-pubsub/licence +21 -0
  49. package/node_modules/event-pubsub/package.json +59 -0
  50. package/node_modules/strong-type/README.md +408 -0
  51. package/node_modules/strong-type/assets/strong-type-header.png +0 -0
  52. package/node_modules/strong-type/index.js +1151 -0
  53. package/node_modules/strong-type/licence +21 -0
  54. package/node_modules/strong-type/node.js +125 -0
  55. package/node_modules/strong-type/package.json +61 -0
  56. package/package.json +95 -0
  57. package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
  58. package/runtime/arcane/components/app-bar.html +468 -0
  59. package/runtime/arcane/components/assistant-panel.html +715 -0
  60. package/runtime/arcane/components/calculator.html +8 -0
  61. package/runtime/arcane/components/chart.html +655 -0
  62. package/runtime/arcane/components/chat.html +1225 -0
  63. package/runtime/arcane/components/conversation-view.html +13 -0
  64. package/runtime/arcane/components/dashboard-config.html +341 -0
  65. package/runtime/arcane/components/data-maintenance.html +112 -0
  66. package/runtime/arcane/components/data-view.html +92 -0
  67. package/runtime/arcane/components/directory-picker.html +197 -0
  68. package/runtime/arcane/components/document-inspector.html +252 -0
  69. package/runtime/arcane/components/file-drop.html +264 -0
  70. package/runtime/arcane/components/file-inspector.html +293 -0
  71. package/runtime/arcane/components/file-manager.html +1715 -0
  72. package/runtime/arcane/components/header.html +142 -0
  73. package/runtime/arcane/components/integration-settings.html +14 -0
  74. package/runtime/arcane/components/local-ai-status.html +360 -0
  75. package/runtime/arcane/components/markdown-document.html +1048 -0
  76. package/runtime/arcane/components/markdown-editor.html +360 -0
  77. package/runtime/arcane/components/media-embed.html +8 -0
  78. package/runtime/arcane/components/modal.html +402 -0
  79. package/runtime/arcane/components/output-panel.html +259 -0
  80. package/runtime/arcane/components/preferences-form.html +135 -0
  81. package/runtime/arcane/components/record-timeline.html +105 -0
  82. package/runtime/arcane/components/relationship-board.html +116 -0
  83. package/runtime/arcane/components/screen-capture.html +8 -0
  84. package/runtime/arcane/components/source-code-viewer.html +441 -0
  85. package/runtime/arcane/components/source-explanation.html +124 -0
  86. package/runtime/arcane/components/speech.html +365 -0
  87. package/runtime/arcane/components/summary-strip.html +177 -0
  88. package/runtime/arcane/components/table.html +77 -0
  89. package/runtime/arcane/components/task-progress.html +282 -0
  90. package/runtime/arcane/components/terminal-workspace.html +65 -0
  91. package/runtime/arcane/components/theme-editor.html +41 -0
  92. package/runtime/arcane/components/theme-switcher.html +46 -0
  93. package/runtime/arcane/components/unified-inbox.html +20 -0
  94. package/runtime/arcane/components/voice-transcription.html +476 -0
  95. package/runtime/arcane/components/weather-widget.html +8 -0
  96. package/runtime/arcane/components/web-navigator.html +239 -0
  97. package/runtime/arcane/css/communications.css +1 -0
  98. package/runtime/arcane/css/dashboard-config.css +45 -0
  99. package/runtime/arcane/css/document-site.css +981 -0
  100. package/runtime/arcane/css/layout.css +438 -0
  101. package/runtime/arcane/css/primitives.css +321 -0
  102. package/runtime/arcane/css/theme.css +112 -0
  103. package/runtime/arcane/css/utility-workspace.css +1 -0
  104. package/runtime/arcane/entities/ApiModelRecord.js +20 -0
  105. package/runtime/arcane/entities/Calculation.js +13 -0
  106. package/runtime/arcane/entities/Chat.js +581 -0
  107. package/runtime/arcane/entities/CommunicationMessage.js +29 -0
  108. package/runtime/arcane/entities/CommunicationThread.js +21 -0
  109. package/runtime/arcane/entities/Document.js +10 -0
  110. package/runtime/arcane/entities/File.js +143 -0
  111. package/runtime/arcane/entities/Image.js +135 -0
  112. package/runtime/arcane/entities/IntentEnvelope.js +834 -0
  113. package/runtime/arcane/entities/Preference.js +83 -0
  114. package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
  115. package/runtime/arcane/entities/TerminalSession.js +46 -0
  116. package/runtime/arcane/entities/Theme.js +107 -0
  117. package/runtime/arcane/entities/User.js +1046 -0
  118. package/runtime/arcane/entities/Weather.js +23 -0
  119. package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
  120. package/runtime/arcane/img/arrow-left.png +0 -0
  121. package/runtime/arcane/img/arrow-right.png +0 -0
  122. package/runtime/arcane/img/doc.svg +5 -0
  123. package/runtime/arcane/img/folder.svg +4 -0
  124. package/runtime/arcane/img/image.svg +5 -0
  125. package/runtime/arcane/img/refresh.png +0 -0
  126. package/runtime/arcane/img/send.svg +9 -0
  127. package/runtime/arcane/img/trash.svg +5 -0
  128. package/runtime/arcane/img/upload.svg +5 -0
  129. package/runtime/arcane/modules/AI.js +2048 -0
  130. package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
  131. package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
  132. package/runtime/arcane/modules/AIResponseLength.js +42 -0
  133. package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
  134. package/runtime/arcane/modules/AnsiText.js +53 -0
  135. package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
  136. package/runtime/arcane/modules/AppDataScope.js +245 -0
  137. package/runtime/arcane/modules/AppearancePreferences.js +28 -0
  138. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
  139. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
  140. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
  141. package/runtime/arcane/modules/AsyncBoundary.js +161 -0
  142. package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
  143. package/runtime/arcane/modules/CalculatorEngine.js +20 -0
  144. package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
  145. package/runtime/arcane/modules/ChartLibrary.js +35 -0
  146. package/runtime/arcane/modules/ChatRecords.js +13 -0
  147. package/runtime/arcane/modules/CommunicationAppController.js +43 -0
  148. package/runtime/arcane/modules/CommunicationHub.js +16 -0
  149. package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
  150. package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
  151. package/runtime/arcane/modules/ComponentContracts.js +586 -0
  152. package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
  153. package/runtime/arcane/modules/ConversationActionItems.js +488 -0
  154. package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
  155. package/runtime/arcane/modules/ConversationTimebox.js +527 -0
  156. package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
  157. package/runtime/arcane/modules/DBLS.js +171 -0
  158. package/runtime/arcane/modules/DBOPFS.js +1154 -0
  159. package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
  160. package/runtime/arcane/modules/DataMaintenance.js +91 -0
  161. package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
  162. package/runtime/arcane/modules/DirectoryPicker.js +109 -0
  163. package/runtime/arcane/modules/DocumentNavigation.js +223 -0
  164. package/runtime/arcane/modules/Errors.js +1025 -0
  165. package/runtime/arcane/modules/GifEncoder.js +29 -0
  166. package/runtime/arcane/modules/HTMLImport.js +114 -0
  167. package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
  168. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
  169. package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
  170. package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
  171. package/runtime/arcane/modules/MD.js +111 -0
  172. package/runtime/arcane/modules/Mail.js +352 -0
  173. package/runtime/arcane/modules/MailTransport.mjs +180 -0
  174. package/runtime/arcane/modules/Marked.min.js +71 -0
  175. package/runtime/arcane/modules/MemoryRecords.js +44 -0
  176. package/runtime/arcane/modules/MessageAdvisory.js +38 -0
  177. package/runtime/arcane/modules/ModelDefinition.js +189 -0
  178. package/runtime/arcane/modules/Ollama.js +74 -0
  179. package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
  180. package/runtime/arcane/modules/OllamaSettings.js +24 -0
  181. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
  182. package/runtime/arcane/modules/PreferenceStore.js +109 -0
  183. package/runtime/arcane/modules/QRCode.min.js +1 -0
  184. package/runtime/arcane/modules/Questionnaire.js +61 -0
  185. package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
  186. package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
  187. package/runtime/arcane/modules/RecordReviewStore.js +94 -0
  188. package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
  189. package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
  190. package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
  191. package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
  192. package/runtime/arcane/modules/ScreenCapture.js +20 -0
  193. package/runtime/arcane/modules/SpeechPlayback.js +581 -0
  194. package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
  195. package/runtime/arcane/modules/SystemAppearance.js +20 -0
  196. package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
  197. package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
  198. package/runtime/arcane/modules/TerminalClient.js +52 -0
  199. package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
  200. package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
  201. package/runtime/arcane/modules/ThemeManager.js +131 -0
  202. package/runtime/arcane/modules/TimeGuard.js +149 -0
  203. package/runtime/arcane/modules/ToolCallRouter.js +83 -0
  204. package/runtime/arcane/modules/WaitForComponent.js +102 -0
  205. package/runtime/arcane/modules/YouTubeMedia.js +16 -0
  206. package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
  207. package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
  208. package/runtime/arcane/modules/uPlot.min.css +1 -0
  209. package/runtime/arcane/security/arcane-network-policy.json +6 -0
  210. package/runtime/strong-type/index.js +352 -0
  211. package/runtime/strong-type/licence +21 -0
  212. package/runtime/strong-type/package.json +45 -0
  213. package/schemas/arcane-app-bundle.schema.json +125 -0
  214. package/schemas/arcane-app.schema.json +329 -0
  215. package/schemas/arcane-lock.schema.json +86 -0
  216. package/schemas/arcane-package.schema.json +224 -0
  217. package/schemas/cli-event.schema.json +122 -0
  218. package/schemas/event-stack.schema.json +152 -0
  219. package/schemas/native-build-plan.schema.json +176 -0
  220. package/schemas/target-adapter.schema.json +117 -0
  221. package/src/app-descriptor.mjs +500 -0
  222. package/src/cli/main.mjs +561 -0
  223. package/src/constants.mjs +31 -0
  224. package/src/dev-server.mjs +718 -0
  225. package/src/doctor.mjs +315 -0
  226. package/src/dom-event-instrumentation.mjs +594 -0
  227. package/src/errors.mjs +75 -0
  228. package/src/event-manager.mjs +1342 -0
  229. package/src/event-queue.mjs +138 -0
  230. package/src/events.mjs +219 -0
  231. package/src/index.mjs +177 -0
  232. package/src/integrated-provider-loader.mjs +432 -0
  233. package/src/native-plan.mjs +698 -0
  234. package/src/native-provider-loader.mjs +1126 -0
  235. package/src/packager/core.mjs +2691 -0
  236. package/src/process.mjs +353 -0
  237. package/src/release-bundle.mjs +2523 -0
  238. package/src/repository.mjs +90 -0
  239. package/src/runtime.mjs +452 -0
  240. package/src/scaffold.mjs +380 -0
  241. package/src/targets/index.mjs +436 -0
  242. package/src/templates/assets/app-icon.png +0 -0
  243. package/src/templates/workspace-template.mjs +388 -0
  244. package/src/testing-loader.mjs +9 -0
  245. package/src/testing.mjs +427 -0
  246. package/src/toolchain.mjs +1335 -0
  247. package/src/update-check.mjs +307 -0
  248. package/src/workspace.mjs +449 -0
@@ -0,0 +1,197 @@
1
+ <link rel="stylesheet" href="./arcane/css/theme.css?v=1">
2
+ <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
3
+ <style>
4
+ :host{display:block;min-width:0}
5
+ .directory-picker{min-width:0}
6
+ .picker-row{
7
+ align-items:stretch;
8
+ display:grid;
9
+ gap:var(--arcane-space-2);
10
+ grid-template-columns:minmax(0,1fr) auto;
11
+ }
12
+ .path{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace;min-width:0}
13
+ .status{min-height:1.35em}
14
+ .status[data-tone="error"]{color:var(--arcane-danger)}
15
+ :host([data-pending="true"]){cursor:wait}
16
+ @media(max-width:32rem){
17
+ .picker-row{grid-template-columns:1fr}
18
+ .choose{width:100%}
19
+ }
20
+ </style>
21
+
22
+ <div id="field" class="directory-picker arcane-field">
23
+ <label id="label" class="arcane-field__label" for="path">Folder</label>
24
+ <div class="picker-row">
25
+ <input id="path" class="path" type="text" readonly placeholder="No folder selected" aria-describedby="help status">
26
+ <button id="choose" class="choose arcane-button arcane-button--secondary" type="button" aria-describedby="help status">Choose folder</button>
27
+ </div>
28
+ <p id="help" class="arcane-help">Choose one folder from this device.</p>
29
+ <p id="status" class="status arcane-help" role="status" aria-live="polite"></p>
30
+ </div>
31
+
32
+ <script type="module">
33
+ const host=this;
34
+ const root=host.shadowRoot;
35
+ const {default:DirectoryPicker}=await import('../modules/DirectoryPicker.js');
36
+ const field=root.querySelector('#field');
37
+ const label=root.querySelector('#label');
38
+ const pathDisplay=root.querySelector('#path');
39
+ const choose=root.querySelector('#choose');
40
+ const help=root.querySelector('#help');
41
+ const status=root.querySelector('#status');
42
+
43
+ let options={};
44
+ let currentValue='';
45
+ let disabled=false;
46
+ let pending=false;
47
+ let picker=new DirectoryPicker();
48
+
49
+ host.configure=configure;
50
+ host.focus=focus;
51
+ host.select=select;
52
+
53
+ Object.defineProperties(host,{
54
+ disabled:{get:()=>disabled,set:setDisabled},
55
+ options:{get:getOptions,set:configure},
56
+ value:{get:()=>currentValue,set:setValue}
57
+ });
58
+
59
+ choose.addEventListener('click',()=>select().catch(()=>{}));
60
+
61
+ function datasetOptions(){
62
+ return {
63
+ buttonLabel:host.dataset.buttonLabel||'Choose folder',
64
+ disabled:host.hasAttribute('disabled')||host.dataset.disabled==='true',
65
+ help:host.dataset.help||'Choose one folder from this device.',
66
+ label:host.dataset.label||'Folder',
67
+ placeholder:host.dataset.placeholder||'No folder selected',
68
+ title:host.dataset.title||host.dataset.label||'Choose folder',
69
+ value:host.dataset.value||''
70
+ };
71
+ }
72
+
73
+ function configure(input={}){
74
+ if(!input||typeof input!=='object'||Array.isArray(input)){
75
+ throw new TypeError('Directory picker configuration must be an object.');
76
+ }
77
+ const next={...datasetOptions(),...options,...input};
78
+ const nextDisabled=Object.prototype.hasOwnProperty.call(input,'disabled')||!host.ready
79
+ ?Boolean(next.disabled)
80
+ :disabled;
81
+ if(input.picker!==undefined){
82
+ if(!input.picker||typeof input.picker.select!=='function'){
83
+ throw new TypeError('picker must expose a select(options) function.');
84
+ }
85
+ picker=input.picker;
86
+ }
87
+ options={
88
+ buttonLabel:text(next.buttonLabel,'Choose folder'),
89
+ help:text(next.help,'Choose one folder from this device.'),
90
+ label:text(next.label,'Folder'),
91
+ placeholder:text(next.placeholder,'No folder selected'),
92
+ title:text(next.title,next.label||'Choose folder')
93
+ };
94
+ label.innerText=options.label;
95
+ help.innerText=options.help;
96
+ choose.innerText=options.buttonLabel;
97
+ pathDisplay.placeholder=options.placeholder;
98
+ if(Object.prototype.hasOwnProperty.call(input,'value')||!host.ready){
99
+ setValue(next.value??'');
100
+ }
101
+ setDisabled(nextDisabled);
102
+ return getOptions();
103
+ }
104
+
105
+ function getOptions(){
106
+ return {...options,disabled,value:currentValue};
107
+ }
108
+
109
+ function text(value,fallback=''){
110
+ const normalized=`${value??''}`.trim();
111
+ return normalized||fallback;
112
+ }
113
+
114
+ function setValue(value=''){
115
+ const next=`${value??''}`.trim();
116
+ if(next.length>4096||/[\u0000-\u001f\u007f]/.test(next)){
117
+ throw new TypeError('The directory path must be bounded plain text.');
118
+ }
119
+ currentValue=next;
120
+ pathDisplay.value=currentValue;
121
+ host.dataset.hasValue=`${Boolean(currentValue)}`;
122
+ return currentValue;
123
+ }
124
+
125
+ function setDisabled(value=false){
126
+ disabled=Boolean(value);
127
+ pathDisplay.disabled=disabled;
128
+ choose.disabled=disabled||pending;
129
+ field.setAttribute('aria-disabled',`${disabled}`);
130
+ host.toggleAttribute('data-disabled',disabled);
131
+ return disabled;
132
+ }
133
+
134
+ function focus(options){
135
+ choose.focus(options);
136
+ return document.activeElement===host||root.activeElement===choose;
137
+ }
138
+
139
+ async function select(){
140
+ if(disabled||pending) return null;
141
+ const previousPath=currentValue;
142
+ setPending(true);
143
+ setStatus('Opening the folder selector.');
144
+ try{
145
+ const result=await picker.select({
146
+ ...(currentValue?{initialPath:currentValue}:{}),
147
+ title:options.title||options.label||'Choose folder'
148
+ });
149
+ if(result.cancelled){
150
+ setStatus('Folder selection canceled.');
151
+ host.dispatchEvent(new CustomEvent('directory-picker-cancel',{
152
+ bubbles:true,
153
+ composed:true,
154
+ detail:{path:currentValue}
155
+ }));
156
+ return result;
157
+ }
158
+ setValue(result.path);
159
+ setStatus('Folder selected.');
160
+ host.dispatchEvent(new CustomEvent('directory-picker-change',{
161
+ bubbles:true,
162
+ composed:true,
163
+ detail:{path:currentValue,previousPath,source:'picker'}
164
+ }));
165
+ return result;
166
+ }catch(error){
167
+ const message=error?.message||'The folder selector could not open.';
168
+ setStatus(message,'error');
169
+ host.dispatchEvent(new CustomEvent('directory-picker-error',{
170
+ bubbles:true,
171
+ composed:true,
172
+ detail:{error,message}
173
+ }));
174
+ throw error;
175
+ }finally{
176
+ setPending(false);
177
+ }
178
+ }
179
+
180
+ function setPending(value=false){
181
+ pending=Boolean(value);
182
+ choose.disabled=disabled||pending;
183
+ pathDisplay.setAttribute('aria-busy',`${pending}`);
184
+ host.toggleAttribute('data-pending',pending);
185
+ return pending;
186
+ }
187
+
188
+ function setStatus(message='',tone=''){
189
+ status.innerText=`${message||''}`;
190
+ status.dataset.tone=tone;
191
+ return status.innerText;
192
+ }
193
+
194
+ configure(datasetOptions());
195
+ host.ready=true;
196
+ host.dispatchEvent(new CustomEvent('directory-picker-ready',{bubbles:true,composed:true}));
197
+ </script>
@@ -0,0 +1,252 @@
1
+ <style>
2
+ :host{display:block;color:var(--text-color);font-family:Inter,Segoe UI,sans-serif}
3
+ *{box-sizing:border-box}
4
+ .inspector{display:grid;grid-template-rows:auto minmax(28rem,1fr);gap:.8rem;min-height:32rem}
5
+ .tabs{display:flex;gap:.35rem;flex-wrap:wrap;border-bottom:1px solid var(--border-color)}
6
+ .tab{margin:0;padding:.65rem .9rem;border:0;border-bottom:.2rem solid transparent;border-radius:.35rem .35rem 0 0;background:transparent;color:var(--text-color);box-shadow:none}
7
+ .tab[aria-selected="true"]{border-bottom-color:var(--accent-color);background:var(--secondary-color);color:var(--text-color)}
8
+ .tab:focus-visible,.search input:focus-visible,.review select:focus-visible,.review textarea:focus-visible,.save:focus-visible{outline:.18rem solid var(--focus-color);outline-offset:.12rem}
9
+ .panel{min-height:0}
10
+ .panel[hidden]{display:none}
11
+ .pdf-frame{width:100%;height:min(68vh,52rem);min-height:34rem;border:1px solid var(--border-color);border-radius:.5rem;background:rgb(245,245,245)}
12
+ .text-panel{display:grid;grid-template-rows:auto minmax(0,1fr);gap:.7rem}
13
+ .search{display:flex;align-items:end;gap:.6rem}
14
+ .search label{display:grid;gap:.25rem;flex:1;font-size:.72rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
15
+ .search input{width:100%;margin:0;padding:.65rem .75rem}
16
+ .matches{min-width:7rem;padding-bottom:.65rem;color:var(--platinum);font-size:.78rem;text-align:right}
17
+ .markdown{height:min(62vh,46rem);overflow:auto;padding:1.2rem;border:1px solid var(--border-color);border-radius:.5rem;background:var(--modal-background);line-height:1.6}
18
+ .markdown h1,.markdown h2,.markdown h3{margin:1.2em 0 .45em}
19
+ .markdown h1:first-child,.markdown h2:first-child{margin-top:0}
20
+ .markdown table{width:100%;border-collapse:collapse}.markdown th,.markdown td{padding:.45rem;border:1px solid var(--border-color);vertical-align:top}
21
+ .markdown pre{overflow:auto}.markdown mark{padding:0 .08em;background:rgb(255,224,116);color:rgb(25,29,34)}
22
+ .loading,.error{padding:1.2rem;border:1px solid var(--border-color);border-radius:.5rem}.error{color:var(--danger-color)}
23
+ .details{display:grid;grid-template-columns:minmax(14rem,.8fr) minmax(18rem,1.2fr);gap:1rem}
24
+ .metadata,.review{padding:1rem;border:1px solid var(--border-color);border-radius:.5rem;background:var(--modal-background)}
25
+ .metadata h3,.review h3{margin:0 0 .8rem}
26
+ dl{display:grid;grid-template-columns:max-content 1fr;gap:.55rem .8rem;margin:0}dt{font-weight:700}dd{margin:0;overflow-wrap:anywhere}
27
+ .review{display:grid;align-content:start;gap:.8rem}.review label,[data-review-fields]{display:grid;gap:.3rem;font-weight:700}[data-review-fields]{gap:.8rem}.review select,.review textarea,.review input{width:100%;margin:0}.review textarea{min-height:7rem;resize:vertical}.save{justify-self:start;margin:0;padding:.65rem 1rem}.save-state{min-height:1.2em;color:var(--success-color);font-size:.8rem}
28
+ @media(max-width:48rem){.inspector{grid-template-rows:auto auto}.pdf-frame{min-height:28rem}.details{grid-template-columns:1fr}.markdown{height:55vh}}
29
+ @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
30
+ </style>
31
+
32
+ <section class="inspector" aria-label="Document inspector">
33
+ <div class="tabs" role="tablist" aria-label="Document views">
34
+ <button class="tab" type="button" role="tab" id="pdf-tab" aria-controls="pdf-panel" aria-selected="true" data-view="pdf">Original PDF</button>
35
+ <button class="tab" type="button" role="tab" id="text-tab" aria-controls="text-panel" aria-selected="false" data-view="text">Filing text</button>
36
+ <button class="tab" type="button" role="tab" id="details-tab" aria-controls="details-panel" aria-selected="false" data-view="details">Details &amp; review</button>
37
+ </div>
38
+ <div id="pdf-panel" class="panel" role="tabpanel" aria-labelledby="pdf-tab" data-panel="pdf"><p class="loading">Loading original PDF…</p></div>
39
+ <div id="text-panel" class="panel text-panel" role="tabpanel" aria-labelledby="text-tab" data-panel="text" hidden>
40
+ <div class="search"><label><span data-text-search-label>Search filing text</span><input type="search" data-text-search placeholder="Search this filing…"></label><span class="matches" aria-live="polite"></span></div>
41
+ <article class="markdown" data-markdown><p class="loading" role="status">Loading filing text…</p></article>
42
+ </div>
43
+ <div id="details-panel" class="panel details" role="tabpanel" aria-labelledby="details-tab" data-panel="details" hidden>
44
+ <section class="metadata"><h3>Source details</h3><dl data-metadata></dl></section>
45
+ <form class="review"><h3 data-review-heading>Review</h3><label>Status<select data-review-status></select></label><label data-classification-field hidden><span data-classification-label>Classification</span><select data-review-classification></select></label><div data-review-fields></div><label><span data-notes-label>Notes</span><textarea data-review-notes maxlength="10000"></textarea></label><button class="save" type="submit">Save review</button><span class="save-state" aria-live="polite"></span></form>
46
+ </div>
47
+ </section>
48
+
49
+ <script type="module">
50
+ const host=this;
51
+ const shadow=host.shadowRoot;
52
+ const tabs=[...shadow.querySelectorAll('[role="tab"]')];
53
+ const panels=[...shadow.querySelectorAll('[role="tabpanel"]')];
54
+ const pdfTab=shadow.querySelector('[data-view="pdf"]');
55
+ const textTab=shadow.querySelector('[data-view="text"]');
56
+ const pdfPanel=shadow.querySelector('[data-panel="pdf"]');
57
+ const markdownSurface=shadow.querySelector('[data-markdown]');
58
+ const searchInput=shadow.querySelector('[data-text-search]');
59
+ const textSearchLabel=shadow.querySelector('[data-text-search-label]');
60
+ const matchStatus=shadow.querySelector('.matches');
61
+ const metadataList=shadow.querySelector('[data-metadata]');
62
+ const reviewForm=shadow.querySelector('.review');
63
+ const reviewStatus=shadow.querySelector('[data-review-status]');
64
+ const classificationField=shadow.querySelector('[data-classification-field]');
65
+ const classificationLabel=shadow.querySelector('[data-classification-label]');
66
+ const reviewClassification=shadow.querySelector('[data-review-classification]');
67
+ const reviewNotes=shadow.querySelector('[data-review-notes]');
68
+ const reviewFields=shadow.querySelector('[data-review-fields]');
69
+ const reviewHeading=shadow.querySelector('[data-review-heading]');
70
+ const notesLabel=shadow.querySelector('[data-notes-label]');
71
+ const saveState=shadow.querySelector('.save-state');
72
+ const {default:MD}=await import('../modules/MD.js');
73
+ let rawMarkdown='';
74
+ let renderedMarkdown='';
75
+ let markdownMessage=null;
76
+ let activeRecordId='';
77
+ let loadSequence=0;
78
+
79
+ host.loadDocument=loadDocument;
80
+ host.selectView=selectView;
81
+ host.ready=true;
82
+
83
+ tabs.forEach(tab=>tab.addEventListener('click',()=>selectView(tab.dataset.view)));
84
+ tabs.forEach(tab=>tab.addEventListener('keydown',event=>{
85
+ if(!['ArrowLeft','ArrowRight','Home','End'].includes(event.key)) return;
86
+ event.preventDefault();
87
+ const available=tabs.filter(candidate=>!candidate.hidden);
88
+ const index=available.indexOf(tab);
89
+ const next=event.key==='Home'?0:event.key==='End'?available.length-1:(index+(event.key==='ArrowRight'?1:-1)+available.length)%available.length;
90
+ selectView(available[next].dataset.view); available[next].focus();
91
+ }));
92
+ searchInput.addEventListener('input',()=>highlightSearch(searchInput.value));
93
+ reviewForm.addEventListener('submit',event=>{
94
+ event.preventDefault();
95
+ const attributes={};
96
+ reviewFields.querySelectorAll('[data-review-field]').forEach(field=>{attributes[field.dataset.reviewField]=field.value;});
97
+ const detail={recordId:activeRecordId,status:reviewStatus.value,classification:reviewClassification.value||'unassigned',attributes,notes:reviewNotes.value};
98
+ saveState.textContent='Saving…';
99
+ host.dispatchEvent(new CustomEvent('document-review-change',{detail,bubbles:true,composed:true}));
100
+ });
101
+
102
+ function sameOriginUrl(value){
103
+ if(!String(value||'').trim()) throw new TypeError('A document source is required.');
104
+ const url=new URL(String(value||''),document.baseURI);
105
+ if(url.origin!==location.origin) throw new TypeError('Document sources must use the current application origin.');
106
+ return url.href;
107
+ }
108
+
109
+ function pdfSourceUrl(value,page=null){
110
+ const url=new URL(sameOriginUrl(value));
111
+ if(page===null||page===undefined||page==='') return url.href;
112
+ const resolvedPage=Number(page);
113
+ if(!Number.isSafeInteger(resolvedPage)||resolvedPage<1) throw new TypeError('PDF page must be a positive integer.');
114
+ url.searchParams.set('arcane-pdf-page',String(resolvedPage));
115
+ url.hash=`page=${resolvedPage}`;
116
+ return url.href;
117
+ }
118
+
119
+ function selectView(view='pdf'){
120
+ const available=tabs.filter(tab=>!tab.hidden);
121
+ const selected=available.some(tab=>tab.dataset.view===view)?view:available[0]?.dataset.view;
122
+ tabs.forEach(tab=>{const active=tab.dataset.view===selected;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;});
123
+ panels.forEach(panel=>panel.hidden=panel.dataset.panel!==selected);
124
+ }
125
+
126
+ function showSurfaceMessage(surface,message,className='loading'){
127
+ const paragraph=document.createElement('p');
128
+ paragraph.className=className;
129
+ paragraph.textContent=String(message||'');
130
+ paragraph.setAttribute('role',className==='error'?'alert':'status');
131
+ surface.replaceChildren(paragraph);
132
+ }
133
+
134
+ function safeMarkdown(markdown=''){
135
+ const template=document.createElement('template');
136
+ template.innerHTML=new MD(markdown).safeRendered;
137
+ template.content.querySelectorAll('img,video,audio,source,picture').forEach(element=>element.remove());
138
+ template.content.querySelectorAll('a').forEach(link=>{link.removeAttribute('href');link.removeAttribute('target');link.removeAttribute('rel');});
139
+ return template.innerHTML;
140
+ }
141
+
142
+ function showMarkdown(){
143
+ if(markdownMessage){showSurfaceMessage(markdownSurface,markdownMessage.message,markdownMessage.className);return;}
144
+ markdownSurface.innerHTML=renderedMarkdown||'<p>No document text is available.</p>';
145
+ }
146
+
147
+ function highlightSearch(term=''){
148
+ showMarkdown();
149
+ const query=String(term).trim();
150
+ if(!query){matchStatus.textContent='';return;}
151
+ const nodes=[]; const walker=document.createTreeWalker(markdownSurface,NodeFilter.SHOW_TEXT);
152
+ while(walker.nextNode()) nodes.push(walker.currentNode);
153
+ let count=0;
154
+ for(const node of nodes){
155
+ const text=node.nodeValue||''; const lower=text.toLocaleLowerCase(); const needle=query.toLocaleLowerCase();
156
+ if(!needle||!lower.includes(needle)) continue;
157
+ const fragment=document.createDocumentFragment(); let offset=0; let index=lower.indexOf(needle,offset);
158
+ while(index>-1){fragment.append(text.slice(offset,index));const mark=document.createElement('mark');mark.textContent=text.slice(index,index+query.length);fragment.append(mark);count++;offset=index+query.length;index=lower.indexOf(needle,offset);}
159
+ fragment.append(text.slice(offset)); node.replaceWith(fragment);
160
+ }
161
+ matchStatus.textContent=`${count} match${count===1?'':'es'}`;
162
+ markdownSurface.querySelector('mark')?.scrollIntoView({block:'center'});
163
+ }
164
+
165
+ function metadataRows(metadata={}){
166
+ metadataList.replaceChildren();
167
+ for(const [label,value] of Object.entries(metadata)){
168
+ if(value===null||value===undefined||value==='') continue;
169
+ const term=document.createElement('dt'); const detail=document.createElement('dd');
170
+ term.textContent=label; detail.textContent=String(value); metadataList.append(term,detail);
171
+ }
172
+ }
173
+
174
+ async function loadDocument(config={}){
175
+ const sequence=++loadSequence;
176
+ activeRecordId=String(config.recordId||'');
177
+ saveState.textContent=''; searchInput.value=''; matchStatus.textContent='';
178
+ const hasPdf=Boolean(String(config.pdfUrl||'').trim());
179
+ const hasText=Boolean(String(config.markdownUrl||'').trim());
180
+ pdfTab.hidden=!hasPdf;
181
+ textTab.hidden=!hasText;
182
+ const textViewLabel=String(config.textViewLabel||'Filing text');
183
+ textTab.textContent=textViewLabel;
184
+ textSearchLabel.textContent=`Search ${textViewLabel.toLocaleLowerCase()}`;
185
+ searchInput.placeholder=`Search this ${textViewLabel.toLocaleLowerCase()}…`;
186
+ reviewHeading.textContent=String(config.reviewHeading||'Review');
187
+ notesLabel.textContent=String(config.notesLabel||'Notes');
188
+ reviewStatus.replaceChildren(...(config.reviewOptions||[{value:'not-reviewed',label:'Not reviewed'},{value:'reviewed',label:'Reviewed'}]).map(option=>{
189
+ const element=document.createElement('option');element.value=String(option.value);element.textContent=String(option.label);return element;
190
+ }));
191
+ reviewStatus.value=String(config.review?.status||'not-reviewed');
192
+ if(!reviewStatus.value&&reviewStatus.options.length) reviewStatus.selectedIndex=0;
193
+ const classificationOptions=Array.isArray(config.classificationOptions)?config.classificationOptions:[];
194
+ classificationField.hidden=!classificationOptions.length;
195
+ classificationLabel.textContent=String(config.classificationLabel||'Classification');
196
+ reviewClassification.replaceChildren(...classificationOptions.map(option=>{
197
+ const element=document.createElement('option');element.value=String(option.value);element.textContent=String(option.label);return element;
198
+ }));
199
+ reviewClassification.value=String(config.review?.classification||'unassigned');
200
+ if(!reviewClassification.value&&reviewClassification.options.length) reviewClassification.selectedIndex=0;
201
+ reviewFields.replaceChildren();
202
+ for(const definition of Array.isArray(config.reviewFields)?config.reviewFields:[]){
203
+ const key=String(definition.key||'').replace(/[^a-z0-9._-]/gi,'-').slice(0,80);
204
+ if(!key) continue;
205
+ const label=document.createElement('label'); const caption=document.createElement('span'); caption.textContent=String(definition.label||key);
206
+ let field;
207
+ if(definition.type==='select'){
208
+ field=document.createElement('select');
209
+ field.append(...(definition.options||[]).map(option=>{const element=document.createElement('option');element.value=String(option.value);element.textContent=String(option.label);return element;}));
210
+ }else if(definition.type==='textarea'){
211
+ field=document.createElement('textarea'); field.rows=Number(definition.rows)||3;
212
+ }else{
213
+ field=document.createElement('input'); field.type='text';
214
+ }
215
+ field.dataset.reviewField=key; field.maxLength=Math.min(Number(definition.maxLength)||10000,10000); field.placeholder=String(definition.placeholder||'');
216
+ field.value=String(config.review?.attributes?.[key]||'');
217
+ if(definition.type==='select'&&!field.value&&field.options.length&&![...field.options].some(option=>option.value==='')) field.selectedIndex=0;
218
+ label.append(caption,field); reviewFields.append(label);
219
+ }
220
+ reviewNotes.value=String(config.review?.notes||'');
221
+ metadataRows(config.metadata||{});
222
+ pdfPanel.replaceChildren();
223
+ if(hasPdf){
224
+ try{
225
+ const resolvedPage=config.pdfPage===null||config.pdfPage===undefined||config.pdfPage===''?null:Number(config.pdfPage);
226
+ const frame=document.createElement('iframe'); frame.className='pdf-frame'; frame.title=`Original PDF: ${config.title||'document'}${resolvedPage?`, page ${resolvedPage}`:''}`; frame.referrerPolicy='no-referrer'; frame.src=pdfSourceUrl(config.pdfUrl,resolvedPage); pdfPanel.append(frame);
227
+ }catch(error){showSurfaceMessage(pdfPanel,error.message,'error');}
228
+ }
229
+ rawMarkdown=''; renderedMarkdown=''; markdownMessage=null;
230
+ selectView(config.initialView||(hasPdf?'pdf':hasText?'text':'details'));
231
+ if(hasText){
232
+ showSurfaceMessage(markdownSurface,'Loading document text…');
233
+ try{
234
+ const response=await fetch(sameOriginUrl(config.markdownUrl),{cache:'no-store',credentials:'same-origin'});
235
+ if(!response.ok) throw new Error(`Document text unavailable (${response.status}).`);
236
+ const markdown=await response.text();
237
+ if(sequence!==loadSequence) return false;
238
+ rawMarkdown=markdown; renderedMarkdown=safeMarkdown(rawMarkdown); showMarkdown();
239
+ }catch(error){
240
+ if(sequence!==loadSequence) return false;
241
+ markdownMessage={message:error.message,className:'error'}; showMarkdown();
242
+ }
243
+ }else{
244
+ markdownMessage={message:'No document text is available.',className:'loading'};
245
+ showMarkdown();
246
+ }
247
+ return true;
248
+ }
249
+
250
+ host.markSaved=message=>{saveState.textContent=String(message||'Saved');};
251
+ host.dispatchEvent(new CustomEvent('document-inspector-ready',{bubbles:true,composed:true}));
252
+ </script>