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,282 @@
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
2
+ <style>
3
+ :host{display:block}
4
+ .task-progress{overflow:hidden}
5
+ .heading-copy{display:grid;gap:var(--arcane-space-1)}
6
+ .heading-copy p{color:var(--arcane-muted);margin:0}
7
+ .overall{display:grid;gap:var(--arcane-space-2);min-width:min(16rem,100%)}
8
+ .overall-label{font-variant-numeric:tabular-nums;text-align:end}
9
+ progress{accent-color:var(--arcane-action);width:100%}
10
+ .tasks{list-style:none;margin:0;padding:0}
11
+ .task{
12
+ align-items:center;
13
+ border-bottom:1px solid var(--arcane-border);
14
+ display:grid;
15
+ gap:var(--arcane-space-3);
16
+ grid-template-columns:auto minmax(0,1fr) auto;
17
+ padding:var(--arcane-space-3) var(--arcane-space-4);
18
+ }
19
+ .task:last-child{border-bottom:0}
20
+ .task-copy{display:grid;gap:var(--arcane-space-1);min-width:0}
21
+ .task-name{font-weight:650;overflow-wrap:anywhere}
22
+ .task-message{color:var(--arcane-muted);font-size:.88rem;overflow-wrap:anywhere}
23
+ .task-status{text-transform:capitalize}
24
+ .empty{margin:var(--arcane-space-4)}
25
+ @media(max-width:34rem){
26
+ .task{grid-template-columns:auto minmax(0,1fr)}
27
+ .task-status{grid-column:2}
28
+ .overall-label{text-align:start}
29
+ }
30
+ </style>
31
+
32
+ <section id="panel" class="task-progress arcane-card" aria-labelledby="taskProgressTitle">
33
+ <header class="arcane-card__header">
34
+ <div class="heading-copy">
35
+ <h2 id="taskProgressTitle" class="arcane-section-heading">Progress</h2>
36
+ <p id="description"></p>
37
+ </div>
38
+ <div id="overall" class="overall" hidden>
39
+ <progress id="overallProgress" max="100" value="0"></progress>
40
+ <span id="overallLabel" class="overall-label"></span>
41
+ </div>
42
+ </header>
43
+ <p id="empty" class="empty arcane-state arcane-state--empty">No tasks yet.</p>
44
+ <ol id="tasks" class="tasks" aria-live="polite"></ol>
45
+ </section>
46
+
47
+ <script type="module">
48
+ const host=this;
49
+ const root=host.shadowRoot;
50
+ const panel=root.querySelector('#panel');
51
+ const heading=root.querySelector('#taskProgressTitle');
52
+ const description=root.querySelector('#description');
53
+ const overall=root.querySelector('#overall');
54
+ const overallProgress=root.querySelector('#overallProgress');
55
+ const overallLabel=root.querySelector('#overallLabel');
56
+ const empty=root.querySelector('#empty');
57
+ const list=root.querySelector('#tasks');
58
+
59
+ const allowedStatuses=new Set(['pending','running','complete','failed','canceled']);
60
+ let activeRun=Promise.resolve([]);
61
+ let options={};
62
+ let running=false;
63
+ let tasks=[];
64
+
65
+ host.clear=clear;
66
+ host.configure=configure;
67
+ host.runTasks=runTasks;
68
+ host.setTasks=setTasks;
69
+ host.updateTask=updateTask;
70
+
71
+ Object.defineProperties(host,{
72
+ running:{get:()=>running},
73
+ snapshot:{get:getSnapshot},
74
+ tasks:{get:getTasks,set:setTasks}
75
+ });
76
+
77
+ function datasetOptions(){
78
+ return {
79
+ description:host.dataset.description||'',
80
+ emptyLabel:host.dataset.emptyLabel||'No tasks yet.',
81
+ title:host.dataset.title||'Progress'
82
+ };
83
+ }
84
+
85
+ function configure(input={}){
86
+ options={...datasetOptions(),...options,...input};
87
+ heading.innerText=`${options.title||'Progress'}`;
88
+ description.innerText=`${options.description||''}`;
89
+ description.hidden=!description.innerText;
90
+ empty.innerText=`${options.emptyLabel||'No tasks yet.'}`;
91
+ return {...options};
92
+ }
93
+
94
+ function setTasks(values=[]){
95
+ const ids=new Set();
96
+ tasks=Array.from(values||[]).map((value,index)=>{
97
+ const task=normalizeTask(value,index);
98
+ if(ids.has(task.id)){
99
+ task.id=`${task.id}-${index+1}`;
100
+ }
101
+ ids.add(task.id);
102
+ return task;
103
+ });
104
+ render();
105
+ emitChange();
106
+ return getTasks();
107
+ }
108
+
109
+ function normalizeTask(value={},index=0){
110
+ const status=allowedStatuses.has(value.status)?value.status:'pending';
111
+ return {
112
+ id:`${value.id??`task-${index+1}`}`,
113
+ message:`${value.message??''}`,
114
+ name:`${value.name??value.label??`Task ${index+1}`}`,
115
+ status
116
+ };
117
+ }
118
+
119
+ function updateTask(id,update={}){
120
+ const index=typeof id==='number'?id:tasks.findIndex(task=>task.id===`${id}`);
121
+ if(index<0||index>=tasks.length){
122
+ return null;
123
+ }
124
+ const current=tasks[index];
125
+ const status=allowedStatuses.has(update.status)?update.status:current.status;
126
+ tasks[index]={
127
+ ...current,
128
+ ...update,
129
+ id:current.id,
130
+ message:`${update.message??current.message}`,
131
+ name:`${update.name??current.name}`,
132
+ status
133
+ };
134
+ render();
135
+ emitChange(tasks[index]);
136
+ return {...tasks[index]};
137
+ }
138
+
139
+ function render(){
140
+ const fragment=document.createDocumentFragment();
141
+ for(const task of tasks){
142
+ const item=document.createElement('li');
143
+ const light=document.createElement('span');
144
+ const copy=document.createElement('span');
145
+ const name=document.createElement('span');
146
+ const message=document.createElement('span');
147
+ const status=document.createElement('span');
148
+
149
+ item.className='task';
150
+ item.dataset.taskId=task.id;
151
+ item.dataset.status=task.status;
152
+ light.className='arcane-status-light';
153
+ light.dataset.status=task.status;
154
+ light.setAttribute('aria-hidden','true');
155
+ copy.className='task-copy';
156
+ name.className='task-name';
157
+ name.innerText=task.name;
158
+ message.className='task-message';
159
+ message.innerText=task.message;
160
+ message.hidden=!task.message;
161
+ status.className='task-status arcane-badge';
162
+ status.dataset.status=statusTone(task.status);
163
+ status.innerText=statusLabel(task.status);
164
+ copy.append(name,message);
165
+ item.append(light,copy,status);
166
+ fragment.append(item);
167
+ }
168
+ list.replaceChildren(fragment);
169
+ empty.hidden=tasks.length>0;
170
+ list.hidden=!tasks.length;
171
+ syncOverall();
172
+ }
173
+
174
+ function syncOverall(){
175
+ const completed=tasks.filter(task=>['complete','failed','canceled'].includes(task.status)).length;
176
+ const percent=tasks.length?Math.round(completed/tasks.length*100):0;
177
+ overall.hidden=!tasks.length;
178
+ overallProgress.value=percent;
179
+ overallLabel.innerText=tasks.length?`${completed} of ${tasks.length} finished`:'';
180
+ panel.setAttribute('aria-busy',`${running}`);
181
+ }
182
+
183
+ async function runTasks(title='Progress',jobs=[]){
184
+ if(running){
185
+ return activeRun;
186
+ }
187
+ const runnable=Array.from(jobs||[]).filter(job=>job&&typeof job.task==='function');
188
+ if(!runnable.length){
189
+ setTasks([]);
190
+ return [];
191
+ }
192
+ configure({title});
193
+ setTasks(runnable.map((job,index)=>({
194
+ id:job.id??`task-${index+1}`,
195
+ message:job.message||'',
196
+ name:job.name||job.label||`Task ${index+1}`,
197
+ status:'pending'
198
+ })));
199
+ running=true;
200
+ syncOverall();
201
+ host.dispatchEvent(new CustomEvent('task-progress-started',{
202
+ bubbles:true,
203
+ composed:true,
204
+ detail:getSnapshot()
205
+ }));
206
+ activeRun=Promise.allSettled(runnable.map(async(job,index)=>{
207
+ updateTask(index,{status:'running'});
208
+ try{
209
+ const value=await job.task();
210
+ updateTask(index,{status:'complete',message:job.completeMessage||''});
211
+ return value;
212
+ }catch(error){
213
+ updateTask(index,{status:'failed',message:job.errorMessage||error?.message||'Unable to complete.'});
214
+ host.dispatchEvent(new CustomEvent('task-progress-error',{
215
+ bubbles:true,
216
+ composed:true,
217
+ detail:{error,id:tasks[index]?.id,index,task:{...tasks[index]}}
218
+ }));
219
+ throw error;
220
+ }
221
+ }));
222
+ const results=await activeRun;
223
+ running=false;
224
+ syncOverall();
225
+ host.dispatchEvent(new CustomEvent('task-progress-complete',{
226
+ bubbles:true,
227
+ composed:true,
228
+ detail:{...getSnapshot(),results:results.slice()}
229
+ }));
230
+ return results;
231
+ }
232
+
233
+ function statusLabel(status){
234
+ return {
235
+ canceled:'Canceled',
236
+ complete:'Complete',
237
+ failed:'Failed',
238
+ pending:'Pending',
239
+ running:'In progress'
240
+ }[status]||status;
241
+ }
242
+
243
+ function statusTone(status){
244
+ return {
245
+ complete:'success',
246
+ failed:'error',
247
+ pending:'warning',
248
+ running:'warning'
249
+ }[status]||status;
250
+ }
251
+
252
+ function getTasks(){
253
+ return tasks.map(task=>({...task}));
254
+ }
255
+
256
+ function getSnapshot(){
257
+ return {running,tasks:getTasks()};
258
+ }
259
+
260
+ function emitChange(task=null){
261
+ host.dispatchEvent(new CustomEvent('task-progress-change',{
262
+ bubbles:true,
263
+ composed:true,
264
+ detail:{...getSnapshot(),task:task?{...task}:null}
265
+ }));
266
+ }
267
+
268
+ function clear(){
269
+ if(running){
270
+ return false;
271
+ }
272
+ tasks=[];
273
+ render();
274
+ emitChange();
275
+ return true;
276
+ }
277
+
278
+ configure(datasetOptions());
279
+ render();
280
+ host.ready=true;
281
+ host.dispatchEvent(new CustomEvent('task-progress-ready',{bubbles:true,composed:true}));
282
+ </script>
@@ -0,0 +1,65 @@
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
2
+ <style>
3
+ :host{display:block;height:100%;min-height:18rem;--terminal-bg:var(--background,rgb(244, 246, 251));--terminal-fg:var(--text-color,rgb(23, 34, 56));--terminal-muted:var(--muted-text-color,rgb(95, 107, 130));--terminal-accent:var(--focus-color,rgb(118, 87, 213));--terminal-danger:var(--danger-color,rgb(165, 56, 56));--terminal-font:ui-monospace,"Cascadia Code","SFMono-Regular",Consolas,monospace;--terminal-font-size:14px;--terminal-line-height:1.55;--terminal-opacity:1}
4
+ *{box-sizing:border-box}.workspace{background:color-mix(in srgb,var(--terminal-bg) calc(var(--terminal-opacity)*100%),transparent);border:1px solid color-mix(in srgb,var(--terminal-accent) 20%,transparent);border-radius:.85rem;color:var(--terminal-fg);display:grid;grid-template-rows:auto auto minmax(0,1fr) auto;height:100%;overflow:hidden;box-shadow:0 1.5rem 5rem rgba(0,0,0,.28)}
5
+ .tabs,.toolbar{align-items:center;background:color-mix(in srgb,var(--terminal-bg) 88%,white 12%);display:flex;gap:.35rem;min-width:0;padding:.45rem .65rem}.tabs{border-bottom:1px solid color-mix(in srgb,var(--terminal-fg) 10%,transparent)}
6
+ .tab{align-items:center;background:transparent;border:1px solid transparent;border-radius:.45rem;color:var(--terminal-muted);display:flex;font:600 .78rem/1 var(--terminal-font);gap:.35rem;max-width:13rem;padding:.55rem .65rem}.tab[aria-selected="true"]{background:color-mix(in srgb,var(--terminal-accent) 12%,transparent);border-color:color-mix(in srgb,var(--terminal-accent) 30%,transparent);color:var(--terminal-fg)}.tab-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tab-close{border:0;background:transparent;color:inherit;cursor:pointer;padding:0 .15rem}.new-session,.tool{background:transparent;border:1px solid transparent;border-radius:.4rem;color:var(--terminal-muted);cursor:pointer;font:600 .78rem/1 var(--terminal-font);padding:.55rem}.new-session:hover,.tool:hover{border-color:color-mix(in srgb,var(--terminal-accent) 25%,transparent);color:var(--terminal-fg)}
7
+ .toolbar{border-bottom:1px solid color-mix(in srgb,var(--terminal-fg) 8%,transparent);justify-content:flex-end}.connection{align-items:center;display:flex;gap:.4rem;margin-right:auto;color:var(--terminal-muted);font:600 .72rem/1 var(--terminal-font);text-transform:uppercase;letter-spacing:.08em}.connection::before{background:var(--terminal-muted);border-radius:50%;content:"";height:.48rem;width:.48rem}.connection[data-state="running"]::before{background:var(--terminal-accent);box-shadow:0 0 .7rem var(--terminal-accent)}.connection[data-state="error"]::before,.connection[data-state="exited"]::before{background:var(--terminal-danger)}
8
+ .search{background:var(--terminal-bg);border:1px solid color-mix(in srgb,var(--terminal-fg) 16%,transparent);border-radius:.4rem;color:var(--terminal-fg);font:inherit;max-width:15rem;padding:.4rem .55rem}.search[hidden]{display:none}
9
+ .output{font:var(--terminal-font-size)/var(--terminal-line-height) var(--terminal-font);overflow:auto;overscroll-behavior:contain;padding:1rem 1.1rem;scrollbar-color:color-mix(in srgb,var(--terminal-accent) 30%,transparent) transparent;white-space:pre-wrap;word-break:break-word}.entry{min-height:1em}.entry.command{color:var(--terminal-accent);font-weight:650}.entry.system{color:var(--terminal-muted)}.entry.error{color:var(--terminal-danger)}mark{background:color-mix(in srgb,var(--terminal-accent) 38%,transparent);color:inherit}
10
+ .prompt{align-items:flex-start;border-top:1px solid color-mix(in srgb,var(--terminal-fg) 10%,transparent);display:grid;font:var(--terminal-font-size)/var(--terminal-line-height) var(--terminal-font);gap:.6rem;grid-template-columns:auto minmax(0,1fr);padding:.75rem 1.1rem}.prompt-label{color:var(--terminal-accent);font-weight:800;padding-top:.34rem}.input{background:transparent;border:0;color:var(--terminal-fg);font:inherit;min-height:2rem;outline:0;padding:.3rem 0;resize:none;width:100%}.input::placeholder{color:var(--terminal-muted)}
11
+ .ansi-bold{font-weight:800}.ansi-dim{opacity:.65}.ansi-italic{font-style:italic}.ansi-underline{text-decoration:underline}.ansi-red,.ansi-bright-red{color:rgb(255, 107, 122)}.ansi-green,.ansi-bright-green{color:rgb(99, 242, 162)}.ansi-yellow,.ansi-bright-yellow{color:rgb(255, 209, 102)}.ansi-blue,.ansi-bright-blue{color:rgb(121, 184, 255)}.ansi-magenta,.ansi-bright-magenta{color:rgb(210, 168, 255)}.ansi-cyan,.ansi-bright-cyan{color:rgb(114, 233, 238)}.ansi-black{color:rgb(135, 147, 141)}.ansi-white,.ansi-bright-white{color:rgb(255, 255, 255)}
12
+ @media(max-width:44rem){.toolbar .tool[data-secondary]{display:none}.workspace{border-radius:0}.output{padding:.8rem}.prompt{padding:.65rem .8rem}}
13
+ </style>
14
+ <section class="workspace" aria-label="Terminal workspace">
15
+ <nav class="tabs" id="tabs" aria-label="Terminal sessions"></nav>
16
+ <div class="toolbar">
17
+ <span class="connection" id="connection" data-state="starting">Starting</span>
18
+ <input class="search" id="search" type="search" placeholder="Find in output" aria-label="Find in terminal output" hidden>
19
+ <button class="tool" id="find" type="button" title="Find (Ctrl+Shift+F)">Find</button>
20
+ <button class="tool" id="copy" data-secondary type="button" title="Copy selected text">Copy</button>
21
+ <button class="tool" id="clear" type="button" title="Clear terminal">Clear</button>
22
+ <button class="tool" id="interrupt" type="button" title="Interrupt process (Ctrl+C)">Ctrl+C</button>
23
+ <button class="tool" id="settings" type="button" title="Terminal settings">Settings</button>
24
+ </div>
25
+ <div class="output" id="output" role="log" aria-live="polite" aria-label="Terminal output" tabindex="0"></div>
26
+ <form class="prompt" id="prompt"><label class="prompt-label" id="promptLabel" for="input">arcane ❯</label><textarea class="input" id="input" rows="1" autocomplete="off" autocapitalize="off" spellcheck="false" placeholder="Enter a command"></textarea></form>
27
+ </section>
28
+ <script type="module">
29
+ const {parseAnsi,stripAnsi}=await import('../modules/AnsiText.js');
30
+ const host=this,root=host.shadowRoot;
31
+ const tabs=root.querySelector('#tabs'),output=root.querySelector('#output'),input=root.querySelector('#input'),prompt=root.querySelector('#prompt'),promptLabel=root.querySelector('#promptLabel'),connection=root.querySelector('#connection'),search=root.querySelector('#search');
32
+ const sessions=new Map();let activeId='',history=[],historyIndex=0,completions=[];
33
+ host.configure=configure;host.addSession=addSession;host.removeSession=removeSession;host.activateSession=activateSession;host.append=append;host.clear=clear;host.setState=setState;host.setTheme=setTheme;host.focus=()=>input.focus();host.ready=true;
34
+ prompt.addEventListener('submit',event=>{event.preventDefault();submit();});
35
+ input.addEventListener('keydown',event=>{
36
+ if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();submit();}
37
+ else if(event.key==='ArrowUp'){event.preventDefault();recall(-1);}
38
+ else if(event.key==='ArrowDown'){event.preventDefault();recall(1);}
39
+ else if(event.key==='Tab'){event.preventDefault();complete();}
40
+ else if(event.key.toLowerCase()==='c'&&event.ctrlKey){const selected=globalThis.getSelection?.()?.toString();if(!selected){event.preventDefault();emit('terminal-interrupt',{sessionId:activeId});}}
41
+ else if(event.key.toLowerCase()==='f'&&event.ctrlKey&&event.shiftKey){event.preventDefault();toggleSearch(true);}
42
+ requestAnimationFrame(resizeInput);
43
+ });
44
+ root.querySelector('#find').onclick=()=>toggleSearch();root.querySelector('#clear').onclick=()=>clear();root.querySelector('#interrupt').onclick=()=>emit('terminal-interrupt',{sessionId:activeId});root.querySelector('#settings').onclick=()=>emit('terminal-settings');
45
+ root.querySelector('#copy').onclick=()=>navigator.clipboard?.writeText(globalThis.getSelection?.()?.toString()||'');
46
+ search.addEventListener('input',render);search.addEventListener('keydown',event=>{if(event.key==='Escape')toggleSearch(false);});
47
+ function configure(options={}){history=Array.from(options.history||[]);historyIndex=history.length;completions=Array.from(options.completions||[]);promptLabel.textContent=String(options.prompt||'arcane ❯');if(options.theme)setTheme(options.theme);return host;}
48
+ function addSession(session){sessions.set(session.id,{...session,entries:session.entries||[]});if(!activeId)activeId=session.id;renderTabs();render();setState(session.state||'starting');}
49
+ function removeSession(id){sessions.delete(id);if(activeId===id)activeId=sessions.keys().next().value||'';renderTabs();render();}
50
+ function activateSession(id){if(!sessions.has(id))return;activeId=id;renderTabs();render();setState(sessions.get(id).state||'running');input.focus();emit('terminal-session-select',{sessionId:id});}
51
+ function append(text,kind='output',sessionId=activeId){const session=sessions.get(sessionId);if(!session)return;session.entries.push({text:String(text??''),kind});if(session.entries.length>4000)session.entries.splice(0,session.entries.length-4000);if(sessionId===activeId)render(true);}
52
+ function clear(sessionId=activeId){const session=sessions.get(sessionId);if(session)session.entries=[];render();emit('terminal-clear',{sessionId});}
53
+ function setState(state,id=activeId){const session=sessions.get(id);if(session)session.state=state;if(id===activeId){connection.dataset.state=state;connection.textContent=state;}}
54
+ function setTheme(theme={}){for(const [key,value] of Object.entries(theme)){const name=`--terminal-${key.replace(/[A-Z]/g,match=>`-${match.toLowerCase()}`)}`;host.style.setProperty(name,String(value));}}
55
+ function submit(){const line=input.value.trimEnd();if(!line.trim())return;if(history.at(-1)!==line)history.push(line);historyIndex=history.length;append(`${promptLabel.textContent} ${line}\n`,'command');input.value='';resizeInput();emit('terminal-submit',{sessionId:activeId,line,history:[...history]});}
56
+ function recall(direction){if(!history.length)return;historyIndex=Math.max(0,Math.min(history.length,historyIndex+direction));input.value=historyIndex===history.length?'':history[historyIndex];resizeInput();input.setSelectionRange(input.value.length,input.value.length);}
57
+ function complete(){const prefix=input.value.split(/\s/).at(-1).toLowerCase();const matches=completions.filter(value=>value.toLowerCase().startsWith(prefix));if(matches.length===1)input.value=input.value.slice(0,-prefix.length)+matches[0];else if(matches.length>1)append(`${matches.join(' ')}\n`,'system');resizeInput();}
58
+ function renderTabs(){const fragment=document.createDocumentFragment();for(const session of sessions.values()){const button=document.createElement('button');button.className='tab';button.type='button';button.setAttribute('role','tab');button.setAttribute('aria-selected',String(session.id===activeId));button.innerHTML=`<span class="tab-label"></span><span class="tab-close" aria-label="Close session">×</span>`;button.querySelector('.tab-label').textContent=session.title||session.shell||'Terminal';button.onclick=event=>{if(event.target.classList.contains('tab-close'))emit('terminal-session-close',{sessionId:session.id});else activateSession(session.id);};fragment.append(button);}const add=document.createElement('button');add.className='new-session';add.type='button';add.textContent='+';add.title='New terminal session';add.onclick=()=>emit('terminal-session-new');fragment.append(add);tabs.replaceChildren(fragment);}
59
+ function render(stick=false){const session=sessions.get(activeId);const atEnd=output.scrollHeight-output.scrollTop-output.clientHeight<48;const term=search.value.toLowerCase();const fragment=document.createDocumentFragment();for(const entry of session?.entries||[]){const row=document.createElement('div');row.className=`entry ${entry.kind||'output'}`;renderText(row,entry.text,term);fragment.append(row);}output.replaceChildren(fragment);if(stick&&(atEnd||!output.scrollTop))output.scrollTop=output.scrollHeight;}
60
+ function renderText(row,text,term){const clean=stripAnsi(text);if(!term){for(const token of parseAnsi(text)){const span=document.createElement('span');span.textContent=token.text;const classes=[];if(token.style.bold)classes.push('ansi-bold');if(token.style.dim)classes.push('ansi-dim');if(token.style.italic)classes.push('ansi-italic');if(token.style.underline)classes.push('ansi-underline');if(token.style.foreground&&!token.style.foreground.startsWith('index-'))classes.push(`ansi-${token.style.foreground}`);span.className=classes.join(' ');row.append(span);}return;}let cursor=0,index=clean.toLowerCase().indexOf(term);while(index>=0){row.append(document.createTextNode(clean.slice(cursor,index)));const mark=document.createElement('mark');mark.textContent=clean.slice(index,index+term.length);row.append(mark);cursor=index+term.length;index=clean.toLowerCase().indexOf(term,cursor);}row.append(document.createTextNode(clean.slice(cursor)));}
61
+ function toggleSearch(force){search.hidden=force===undefined?!search.hidden:!force;if(!search.hidden)search.focus();else{search.value='';render();input.focus();}}
62
+ function resizeInput(){input.style.height='auto';input.style.height=`${Math.min(input.scrollHeight,160)}px`;}
63
+ function emit(type,detail={}){host.dispatchEvent(new CustomEvent(type,{bubbles:true,composed:true,detail}));}
64
+ host.dispatchEvent(new CustomEvent('terminal-workspace-ready',{bubbles:true,composed:true}));
65
+ </script>
@@ -0,0 +1,41 @@
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
2
+ <style>
3
+ :host{display:block;min-width:0}.intro{display:grid;gap:var(--arcane-space-1)}.intro p{margin:0}.editor{display:grid;gap:var(--arcane-space-4)}.identity{display:grid;gap:var(--arcane-space-4);grid-template-columns:2fr 1fr}.colors{display:grid;gap:var(--arcane-space-3);grid-template-columns:repeat(auto-fit,minmax(11rem,1fr))}.color-field{align-items:center;display:grid;gap:.65rem;grid-template-columns:2.5rem minmax(0,1fr)}.color-field input{height:2.5rem;padding:.15rem;width:2.5rem}.color-field label{font-weight:650}.status{margin-right:auto;min-height:1.4em}.preview{border:1px solid var(--border-color);border-radius:var(--arcane-radius-medium);display:flex;gap:.6rem;padding:.8rem;background:var(--background);color:var(--text-color)}.preview span{border-radius:.4rem;padding:.5rem .7rem;background:var(--secondary-color)}.preview strong{border-radius:.4rem;padding:.5rem .7rem;background:var(--primary-color);color:var(--button-text-color)}@media(max-width:36rem){.identity{grid-template-columns:1fr}}
4
+ </style>
5
+ <form class="arcane-card editor">
6
+ <header class="arcane-card__header intro"><h2 class="arcane-section-heading">Create your own skin</h2><p class="arcane-help">Choose a small set of safe design tokens. Compatible Arcane apps use the same saved skin.</p></header>
7
+ <div class="arcane-card__body editor">
8
+ <div class="identity">
9
+ <div class="arcane-field"><label for="themeName">Skin name</label><input id="themeName" name="name" maxlength="40" required></div>
10
+ <div class="arcane-field"><label for="themeScheme">Starting contrast</label><select id="themeScheme" name="scheme"><option value="light">Light</option><option value="dark">Dark</option></select></div>
11
+ </div>
12
+ <div id="colors" class="colors"></div>
13
+ <div class="preview" aria-label="Skin preview"><span>Surface</span><strong>Primary action</strong></div>
14
+ </div>
15
+ <footer class="arcane-card__footer"><p id="status" class="status arcane-help" role="status" aria-live="polite"></p><button id="reset" class="arcane-button arcane-button--tertiary" type="button">Remove custom skin</button><button class="arcane-button" type="submit">Save and use skin</button></footer>
16
+ </form>
17
+ <script type="module">
18
+ const host=this;
19
+ const root=host.shadowRoot;
20
+ const form=root.querySelector('form');
21
+ const colors=root.querySelector('#colors');
22
+ const status=root.querySelector('#status');
23
+ const {default:Theme,themeColorToHex,themeTokens}=await import('../entities/Theme.js');
24
+
25
+ host.configure=configure;host.getTheme=getTheme;host.setTheme=setTheme;host.setBusy=setBusy;host.setStatus=setStatus;
26
+ colors.replaceChildren(...themeTokens.map(token=>{
27
+ const field=document.createElement('div'),input=document.createElement('input'),label=document.createElement('label');
28
+ field.className='color-field';input.type='color';input.id=`theme-${token.key}`;input.name=token.key;input.value=themeColorToHex(token.defaultValue);label.htmlFor=input.id;label.textContent=token.label;field.append(input,label);return field;
29
+ }));
30
+ form.addEventListener('input',()=>emit('theme-preview',{theme:getTheme()}));
31
+ form.addEventListener('submit',event=>{event.preventDefault();emit('theme-save',{theme:getTheme()});});
32
+ root.querySelector('#reset').addEventListener('click',()=>emit('theme-reset'));
33
+
34
+ function emit(name,detail={}){host.dispatchEvent(new CustomEvent(name,{bubbles:true,composed:true,detail}));}
35
+ function getTheme(){return new Theme({name:form.elements.name.value,scheme:form.elements.scheme.value,tokens:Object.fromEntries(themeTokens.map(token=>[token.key,form.elements[token.key].value]))});}
36
+ function setTheme(input={}){const theme=Theme.fromJSON(input);form.elements.name.value=theme.name;form.elements.scheme.value=theme.scheme;for(const token of themeTokens) form.elements[token.key].value=themeColorToHex(theme.tokens[token.key]);return theme;}
37
+ function configure(options={}){return setTheme(options.theme||{name:'My Arcane skin'});}
38
+ function setBusy(value=true){for(const control of form.elements) control.disabled=Boolean(value);form.setAttribute('aria-busy',String(Boolean(value)));}
39
+ function setStatus(value=''){status.textContent=String(value||'');return status.textContent;}
40
+ configure();host.ready=true;emit('theme-editor-ready');
41
+ </script>
@@ -0,0 +1,46 @@
1
+ <style>
2
+ :host{display:inline-block;color:inherit}
3
+ .switcher{align-items:center;display:flex;gap:.15rem;padding:.2rem;border:1px solid var(--app-bar-border,var(--border-color,rgba(255,255,255,.25)));border-radius:999px;background:color-mix(in srgb,currentColor 8%,transparent)}
4
+ button{appearance:none;border:0;border-radius:999px;background:transparent;color:inherit;cursor:pointer;font:700 .68rem/1 system-ui,sans-serif;min-height:1.85rem;padding:.42rem .58rem}
5
+ button:hover{background:color-mix(in srgb,currentColor 14%,transparent)}
6
+ button[aria-pressed="true"]{background:var(--app-bar-accent,var(--secondary-color,#c49a52));color:var(--app-bar-active-text,var(--primary-color,#172238))}
7
+ button:focus-visible{outline:.18rem solid var(--focus-color,currentColor);outline-offset:.12rem}
8
+ @media(max-width:32rem){button{font-size:0;padding-inline:.5rem}button::first-letter{font-size:.72rem}}
9
+ </style>
10
+ <div class="switcher" role="group" aria-label="Color scheme">
11
+ <button type="button" data-scheme="system" aria-pressed="false" title="Use device theme">Auto</button>
12
+ <button type="button" data-scheme="light" aria-pressed="false" title="Use light theme">Light</button>
13
+ <button type="button" data-scheme="dark" aria-pressed="false" title="Use dark theme">Dark</button>
14
+ <button type="button" data-scheme="custom" aria-pressed="false" title="Use my custom skin" disabled>Skin</button>
15
+ </div>
16
+ <script type="module">
17
+ const host=this;
18
+ const root=host.shadowRoot;
19
+ const buttons=Array.from(root.querySelectorAll('[data-scheme]'));
20
+ const {default:ThemeManager}=await import('../modules/ThemeManager.js');
21
+ const manager=new ThemeManager();
22
+
23
+ host.setMode=setMode;
24
+ host.refresh=refresh;
25
+ root.querySelector('.switcher').addEventListener('click',async event=>{
26
+ const button=event.target.closest('[data-scheme]');
27
+ if(!button) return;
28
+ setBusy(true);
29
+ try{
30
+ const state=button.dataset.scheme==='custom'?await manager.activateCustom():await manager.setScheme(button.dataset.scheme);
31
+ setMode(state.mode);setCustomAvailable(Boolean(state.theme));
32
+ }finally{setBusy(false);setCustomAvailable(Boolean(manager.customTheme));}
33
+ });
34
+ globalThis.addEventListener('arcane-theme-change',()=>refresh().catch(()=>{}));
35
+
36
+ function setMode(mode='system'){
37
+ for(const button of buttons) button.setAttribute('aria-pressed',String(button.dataset.scheme===mode));
38
+ host.dataset.mode=mode;
39
+ return mode;
40
+ }
41
+ function setBusy(value){for(const button of buttons) button.disabled=Boolean(value);}
42
+ function setCustomAvailable(value){root.querySelector('[data-scheme="custom"]').disabled=!value;}
43
+ async function refresh(){const state=await manager.load();setMode(state.mode);setCustomAvailable(Boolean(state.theme));return state;}
44
+
45
+ refresh().catch(()=>setMode('system'));
46
+ </script>
@@ -0,0 +1,20 @@
1
+ <link rel="stylesheet" href="./arcane/css/primitives.css?v=1">
2
+ <style>
3
+ :host{display:block;min-width:0}.toolbar{display:grid;gap:.75rem;padding:1rem;border-bottom:1px solid var(--border-color,#ccd4df)}.toolbar-row{display:flex;gap:.5rem}.toolbar input{min-width:0;width:100%}.filters{display:flex;gap:.4rem;overflow:auto}.filters button{white-space:nowrap}.list{display:grid;max-height:calc(100vh - 17rem);overflow:auto}.thread{display:grid;gap:.35rem;width:100%;padding:1rem;border:0;border-bottom:1px solid var(--border-color,#ccd4df);color:inherit;background:transparent;text-align:left}.thread:hover,.thread[aria-current="true"]{background:var(--secondary-color,#e8edf4)}.thread:focus-visible{outline:.18rem solid var(--focus-color,#7657d5);outline-offset:-.2rem}.line{align-items:center;display:flex;gap:.5rem;justify-content:space-between}.title{overflow:hidden;font-weight:750;text-overflow:ellipsis;white-space:nowrap}.preview{color:var(--muted-text-color,#607080);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.meta{align-items:center;display:flex;gap:.4rem;font-size:.75rem}.badge{border:1px solid var(--border-color,#ccd4df);border-radius:999px;padding:.15rem .45rem;text-transform:uppercase}.unread{display:grid;place-items:center;min-width:1.4rem;height:1.4rem;border-radius:999px;color:var(--button-text-color,#fff);background:var(--focus-color,#7657d5);font-weight:800}.empty{padding:2rem;text-align:center}.empty p{color:var(--muted-text-color,#607080)}
4
+ </style>
5
+ <section aria-label="Unified inbox">
6
+ <div class="toolbar"><div class="toolbar-row"><input id="search" type="search" placeholder="Search conversations" aria-label="Search conversations"><button id="refresh" class="arcane-button arcane-button--tertiary" type="button">Refresh</button></div><div id="filters" class="filters" aria-label="Channel filters"></div></div>
7
+ <div id="list" class="list"></div>
8
+ <div id="empty" class="empty"><strong>No conversations yet</strong><p>Connect a service in settings to bring conversations into this inbox.</p></div>
9
+ </section>
10
+ <script type="module">
11
+ const host=this,root=host.shadowRoot,list=root.querySelector('#list'),empty=root.querySelector('#empty'),search=root.querySelector('#search'),filters=root.querySelector('#filters');let threads=[],activeId='',channel='all',providers={};
12
+ host.configure=options=>{providers=Object.fromEntries(Array.from(options?.providers||[],item=>[item.id,item]));renderFilters(options?.channels);return host.setThreads(options?.threads||[])};
13
+ host.setThreads=values=>{threads=Array.from(values||[]);render();return threads};host.setActive=id=>{activeId=String(id||'');render();};host.setLoading=value=>{root.querySelector('#refresh').disabled=Boolean(value)};
14
+ root.querySelector('#refresh').addEventListener('click',()=>host.dispatchEvent(new CustomEvent('inbox-refresh',{bubbles:true,composed:true})));
15
+ search.addEventListener('input',render);
16
+ function renderFilters(values){const names=['all',...new Set(values||[])];filters.replaceChildren(...names.map(name=>{const button=document.createElement('button');button.type='button';button.className='arcane-button arcane-button--tertiary';button.textContent=name==='all'?'All':name.toUpperCase();button.setAttribute('aria-pressed',String(channel===name));button.addEventListener('click',()=>{channel=name;renderFilters(values);render()});return button}))}
17
+ function render(){const term=search.value.trim().toLowerCase();const visible=threads.filter(item=>(channel==='all'||item.channel===channel)&&(!term||`${item.title} ${item.preview} ${(item.participants||[]).join(' ')}`.toLowerCase().includes(term)));empty.hidden=visible.length>0;list.replaceChildren(...visible.map(makeThread));}
18
+ function makeThread(item){const button=document.createElement('button');button.type='button';button.className='thread';button.setAttribute('aria-current',String(item.id===activeId));const top=document.createElement('span'),title=document.createElement('span'),time=document.createElement('time'),preview=document.createElement('span'),meta=document.createElement('span'),badge=document.createElement('span'),provider=document.createElement('span');top.className='line';title.className='title';title.textContent=item.title;time.dateTime=item.updatedAt;time.textContent=new Intl.DateTimeFormat(undefined,{month:'short',day:'numeric'}).format(new Date(item.updatedAt));preview.className='preview';preview.textContent=item.preview||'No preview';meta.className='meta';badge.className='badge';badge.textContent=item.channel;provider.textContent=providers[item.providerId]?.label||item.providerId;top.append(title,time);meta.append(badge,provider);if(item.unreadCount){const unread=document.createElement('span');unread.className='unread';unread.textContent=String(item.unreadCount);meta.append(unread)}button.append(top,preview,meta);button.addEventListener('click',()=>host.dispatchEvent(new CustomEvent('thread-select',{bubbles:true,composed:true,detail:{thread:item}})));return button;}
19
+ host.ready=true;host.dispatchEvent(new CustomEvent('unified-inbox-ready',{bubbles:true,composed:true}));
20
+ </script>