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,74 @@
1
+ /**
2
+ * First-class browser module for Arcane's capability-gated Ollama service.
3
+ * Apps should import this module instead of connecting to localhost:11434.
4
+ */
5
+ function api(){
6
+ const client=globalThis.Arcane?.ollama
7
+ if(!client){
8
+ const error=new Error('The Arcane Ollama API is unavailable. Open this app through Arcane OS.')
9
+ error.code='ARCANE_OLLAMA_UNAVAILABLE'
10
+ throw error
11
+ }
12
+ return client
13
+ }
14
+
15
+ export class Ollama{
16
+ version(){ return api().version() }
17
+ models(){ return api().models() }
18
+ list(){ return api().models() }
19
+ running(){ return api().running() }
20
+ show(model,options){ return api().show(model,options) }
21
+ generate(request,options){ return api().generate(request,options) }
22
+ chat(request,options){ return api().chat(request,options) }
23
+ embed(request){ return api().embed(request) }
24
+ pull(model,options,streamOptions){ return api().pull(model,options,streamOptions) }
25
+ push(model,options,streamOptions){ return api().push(model,options,streamOptions) }
26
+ create(request,options){ return api().create(request,options) }
27
+ copy(source,destination){ return api().copy(source,destination) }
28
+ delete(model){ return api().delete(model) }
29
+ selection(){ return api().selection() }
30
+ select(preference){ return api().select(preference) }
31
+ settings(){ return api().settings() }
32
+ saveSettings(settings){ return api().saveSettings(settings) }
33
+ createBrain(definition){ return api().createBrain(definition) }
34
+ serviceSettings(){ return api().serviceSettings() }
35
+ saveServiceSettings(settings){ return api().saveServiceSettings(settings) }
36
+
37
+ async readiness(){
38
+ try{
39
+ const response=await this.version()
40
+ const version=String(
41
+ typeof response==='string' ? response : response?.version||''
42
+ ).trim()||null
43
+ return Object.freeze({ready:true,version,errorCode:null})
44
+ }catch(error){
45
+ return Object.freeze({
46
+ ready:false,
47
+ version:null,
48
+ errorCode:String(error?.code||'OLLAMA_UNAVAILABLE')
49
+ })
50
+ }
51
+ }
52
+
53
+ async generateText(request,options){
54
+ const response=await this.generate(request,options)
55
+ return String(response?.response||'')
56
+ }
57
+
58
+ async chatText(request,options){
59
+ const response=await this.chat(request,options)
60
+ return String(response?.message?.content||'')
61
+ }
62
+
63
+ unload(model){
64
+ return this.generate({ model,prompt:'',keep_alive:0 })
65
+ }
66
+ }
67
+
68
+ export const ollama=Object.freeze(new Ollama())
69
+ export default ollama
70
+
71
+ if(!Object.prototype.hasOwnProperty.call(globalThis,'arcaneOllama')){
72
+ Object.defineProperty(globalThis,'arcaneOllama',{ value:ollama,enumerable:true,configurable:false,writable:false })
73
+ }
74
+ globalThis.dispatchEvent?.(new CustomEvent('arcane-ollama-ready',{ detail:{ ollama } }))
@@ -0,0 +1,21 @@
1
+ const OLLAMA_MODEL_IDENTIFIER=
2
+ /^[A-Za-z0-9][A-Za-z0-9._/-]{0,191}(?::[A-Za-z0-9][A-Za-z0-9._-]{0,63})?$/;
3
+
4
+ /**
5
+ * Returns an exact bounded Ollama model identifier or null. This validates
6
+ * transport shape only; it does not infer ownership, capability, or hardware
7
+ * compatibility from the name.
8
+ */
9
+ export function normalizeOllamaModelIdentifier(value){
10
+ if(typeof value!=='string'||value!==value.trim()){
11
+ return null;
12
+ }
13
+ if(!OLLAMA_MODEL_IDENTIFIER.test(value)||value.toUpperCase()==='OPENAI'){
14
+ return null;
15
+ }
16
+ return value;
17
+ }
18
+
19
+ export function isOllamaModelIdentifier(value){
20
+ return normalizeOllamaModelIdentifier(value)!==null;
21
+ }
@@ -0,0 +1,24 @@
1
+ import {preferenceSchema} from '../entities/Preference.js';
2
+
3
+ export const ollamaRuntimeSchema=preferenceSchema([
4
+ {key:'bootLoad',type:'boolean',label:'Load default model at boot',description:'Preload the default model after ArcaneOllama and Arcane Core are ready.',defaultValue:true},
5
+ {key:'bootKeepAlive',type:'select',label:'Boot model memory',description:'How long the boot-loaded model remains in memory.',defaultValue:'-1',options:[{label:'Keep loaded',value:'-1'},{label:'5 minutes',value:'5m'},{label:'30 minutes',value:'30m'},{label:'1 hour',value:'1h'},{label:'24 hours',value:'24h'}]},
6
+ {key:'contextLength',type:'number',label:'Model context length',description:'Use 0 for automatic, or choose 1,024–262,144 tokens. Larger contexts require more memory.',defaultValue:0,minimum:0,maximum:262144,step:1024},
7
+ ]);
8
+
9
+ export const ollamaServiceSchema=preferenceSchema([
10
+ {key:'contextLength',type:'number',label:'Server context length',description:'Global OLLAMA_CONTEXT_LENGTH. Use 0 for Ollama automatic sizing.',defaultValue:0,minimum:0,maximum:262144,step:1024},
11
+ {key:'keepAlive',type:'text',label:'Server keep alive',description:'Global duration such as 5m or 1h; -1 keeps models loaded.',defaultValue:'5m'},
12
+ {key:'maxLoadedModels',type:'number',label:'Maximum loaded models',description:'Use 0 for hardware automatic. Arcane bounds the effective value and defaults constrained and balanced machines to one.',defaultValue:1,minimum:0,maximum:16,step:1},
13
+ {key:'numParallel',type:'number',label:'Parallel requests per model',description:'Arcane bounds this value by reliable hardware capacity; higher values multiply context memory requirements.',defaultValue:1,minimum:1,maximum:16,step:1},
14
+ {key:'maxQueue',type:'number',label:'Maximum request queue',description:'Requests beyond this limit receive an overload response.',defaultValue:512,minimum:1,maximum:4096,step:1},
15
+ {key:'flashAttention',type:'boolean',label:'Flash Attention',description:'Reduce context memory use on supported hardware.',defaultValue:false},
16
+ {key:'kvCacheType',type:'select',label:'K/V cache precision',description:'Quantized caches save memory and may slightly affect quality.',defaultValue:'f16',options:[{label:'f16 — highest precision',value:'f16'},{label:'q8_0 — balanced',value:'q8_0'},{label:'q4_0 — lowest memory',value:'q4_0'}]},
17
+ {key:'noCloud',type:'boolean',label:'Local-only mode',description:'Disable Ollama cloud models and cloud features.',defaultValue:true},
18
+ ]);
19
+
20
+ export function arcaneBrainModelName(name=''){
21
+ const slug=String(name).trim().toLowerCase().replace(/[^a-z0-9._-]+/g,'-').replace(/^-+|-+$/g,'').slice(0,64);
22
+ if(!slug) throw new TypeError('Enter a name for this Arcane brain.');
23
+ return `arcane-${slug}:latest`;
24
+ }
@@ -0,0 +1,14 @@
1
+ import ApiModelDatabase from './ApiModelDatabase.js';
2
+ import {WeatherDay,WeatherLocation,WeatherObservation,WeatherSnapshot} from '../entities/Weather.js';
3
+
4
+ export const OPEN_METEO_ENDPOINTS=Object.freeze({geocoding:'https://geocoding-api.open-meteo.com/v1/search',forecast:'https://api.open-meteo.com/v1/forecast'});
5
+
6
+ export default class OpenMeteoWeatherProvider extends EventTarget{
7
+ constructor({geocodingEndpoint=OPEN_METEO_ENDPOINTS.geocoding,forecastEndpoint=OPEN_METEO_ENDPOINTS.forecast,fetchImpl=globalThis.fetch}={}){super();this.geocoder=new ApiModelDatabase({endpoint:geocodingEndpoint,fetchImpl,parser:raw=>Array.from(raw.results||[],item=>new WeatherLocation({id:item.id,name:item.name,region:item.admin1||'',country:item.country||'',latitude:item.latitude,longitude:item.longitude,timezone:item.timezone||'auto'}))});this.forecast=new ApiModelDatabase({endpoint:forecastEndpoint,fetchImpl,parser:(raw,{context})=>mapForecast(raw,context.location)});for(const model of [this.geocoder,this.forecast]){model.addEventListener('api-model-request',event=>this.emit('request',event.detail));model.addEventListener('api-model-error',event=>this.emit('error',event.detail));}}
8
+ setEndpoints({geocoding,forecast}={}){if(geocoding)this.geocoder.setEndpoint(geocoding);if(forecast)this.forecast.setEndpoint(forecast);return {geocoding:this.geocoder.endpoint,forecast:this.forecast.endpoint};}
9
+ async search(query){const name=String(query||'').trim();if(name.length<2)throw new TypeError('Enter at least two characters for a location search.');const record=await this.geocoder.fetch({name,count:8,language:'en',format:'json'});this.emit('locations',{locations:record.value});return record.value;}
10
+ async load(location,{temperatureUnit='fahrenheit',windSpeedUnit='mph',precipitationUnit='inch'}={}){const place=location instanceof WeatherLocation?location:new WeatherLocation(location);const record=await this.forecast.fetch({latitude:place.latitude,longitude:place.longitude,timezone:'auto',forecast_days:7,temperature_unit:temperatureUnit,wind_speed_unit:windSpeedUnit,precipitation_unit:precipitationUnit,current:['temperature_2m','relative_humidity_2m','apparent_temperature','precipitation','weather_code','wind_speed_10m','is_day'],daily:['weather_code','temperature_2m_max','temperature_2m_min','precipitation_probability_max','sunrise','sunset']},{location:place});this.emit('weather',{weather:record.value});return record.value;}
11
+ emit(type,detail){this.dispatchEvent(new CustomEvent(`weather-${type}`,{detail}));}
12
+ }
13
+
14
+ export function mapForecast(raw,location){const current=raw.current||{},units=raw.current_units||{},daily=raw.daily||{},dailyUnits=raw.daily_units||{};const observation=new WeatherObservation({time:current.time,temperature:current.temperature_2m,apparentTemperature:current.apparent_temperature,humidity:current.relative_humidity_2m,precipitation:current.precipitation,weatherCode:current.weather_code,windSpeed:current.wind_speed_10m,isDay:current.is_day===1,temperatureUnit:units.temperature_2m||'°',windUnit:units.wind_speed_10m||''});const days=Array.from(daily.time||[],(date,index)=>new WeatherDay({date,weatherCode:daily.weather_code?.[index],temperatureMax:daily.temperature_2m_max?.[index],temperatureMin:daily.temperature_2m_min?.[index],precipitationProbability:daily.precipitation_probability_max?.[index]??0,sunrise:daily.sunrise?.[index]||'',sunset:daily.sunset?.[index]||'',temperatureUnit:dailyUnits.temperature_2m_max||units.temperature_2m||'°'}));return new WeatherSnapshot({location,current:observation,daily:days,source:'Open-Meteo',fetchedAt:new Date()});}
@@ -0,0 +1,109 @@
1
+ import Preference,{preferenceSchema} from '../entities/Preference.js';
2
+ import {resolveApplicationLocalStorageKey} from './AppDataScope.js';
3
+
4
+ function localAdapter(prefix){
5
+ const storage=globalThis.localStorage;
6
+ const scopedPrefix=resolveApplicationLocalStorageKey(prefix);
7
+ return {
8
+ async get(key){
9
+ const raw=storage?.getItem(`${scopedPrefix}:${key}`);
10
+ return {found:raw!==null&&raw!==undefined,value:raw==null?null:JSON.parse(raw)};
11
+ },
12
+ async set(key,value){ storage?.setItem(`${scopedPrefix}:${key}`,JSON.stringify(value)); return {key,value}; },
13
+ async delete(key){ storage?.removeItem(`${scopedPrefix}:${key}`); return {key,deleted:true}; }
14
+ };
15
+ }
16
+
17
+ function nativeAdapter(){
18
+ const preferences=globalThis.Arcane?.preferences;
19
+ if(!preferences?.get||!preferences?.set||!preferences?.delete) return null;
20
+ return preferences;
21
+ }
22
+
23
+ function isUnsupportedNativeAdapter(error){
24
+ return error?.code==='ANDROID_CAPABILITY_UNSUPPORTED';
25
+ }
26
+
27
+ function preferenceAdapter(){
28
+ const local=localAdapter('arcane.preferences');
29
+ if(typeof globalThis.arcaneAndroid?.postMessage==='function') return local;
30
+ const native=nativeAdapter();
31
+ if(!native) return local;
32
+ let active=native;
33
+ async function call(method,args){
34
+ try{
35
+ return await active[method](...args);
36
+ }catch(error){
37
+ if(active!==native||!isUnsupportedNativeAdapter(error)) throw error;
38
+ active=local;
39
+ return active[method](...args);
40
+ }
41
+ }
42
+ return {
43
+ get:key=>call('get',[key]),
44
+ set:(key,value)=>call('set',[key,value]),
45
+ delete:key=>call('delete',[key])
46
+ };
47
+ }
48
+
49
+ export default class PreferenceStore extends EventTarget{
50
+ constructor({namespace='arcane',schema=[],adapter=null}={}){
51
+ super();
52
+ this.namespace=String(namespace||'arcane');
53
+ this.schema=preferenceSchema(schema);
54
+ this.adapter=adapter||preferenceAdapter();
55
+ this.values=this.defaults();
56
+ }
57
+
58
+ defaults(){
59
+ return Object.fromEntries(this.schema.map(item=>[item.key,item.defaultValue]));
60
+ }
61
+
62
+ storageKey(key){ return `${this.namespace}.${key}`; }
63
+
64
+ definition(key){
65
+ const definition=this.schema.find(item=>item.key===key);
66
+ if(!definition) throw new RangeError(`Unknown preference: ${key}`);
67
+ return definition;
68
+ }
69
+
70
+ async load(){
71
+ const values=this.defaults();
72
+ await Promise.all(this.schema.map(async definition=>{
73
+ const result=await this.adapter.get(this.storageKey(definition.key));
74
+ if(result?.found) values[definition.key]=definition.value(result.value);
75
+ }));
76
+ this.values=values;
77
+ this.emit('load');
78
+ return {...values};
79
+ }
80
+
81
+ async set(key,value){
82
+ const definition=this.definition(key);
83
+ const normalized=definition.value(value);
84
+ await this.adapter.set(this.storageKey(key),normalized);
85
+ this.values={...this.values,[key]:normalized};
86
+ this.emit('change',{key,value:normalized});
87
+ return normalized;
88
+ }
89
+
90
+ async setAll(values={}){
91
+ for(const definition of this.schema){
92
+ if(Object.prototype.hasOwnProperty.call(values,definition.key)) await this.set(definition.key,values[definition.key]);
93
+ }
94
+ return {...this.values};
95
+ }
96
+
97
+ async reset(){
98
+ await Promise.all(this.schema.map(definition=>this.adapter.delete(this.storageKey(definition.key))));
99
+ this.values=this.defaults();
100
+ this.emit('reset');
101
+ return {...this.values};
102
+ }
103
+
104
+ emit(type,detail={}){
105
+ this.dispatchEvent(new CustomEvent(`preference-${type}`,{detail:{values:{...this.values},...detail}}));
106
+ }
107
+ }
108
+
109
+ export {Preference,preferenceSchema};
@@ -0,0 +1 @@
1
+ var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c<a.length&&0==a[c];)c++;this.num=new Array(a.length-c+b);for(var d=0;d<a.length-c;d++)this.num[d]=a[d+c]}function j(a,b){this.totalCount=a,this.dataCount=b}function k(){this.buffer=[],this.length=0}function m(){return"undefined"!=typeof CanvasRenderingContext2D}function n(){var a=!1,b=navigator.userAgent;return/android/i.test(b)&&(a=!0,aMat=b.toString().match(/android ([0-9]\.[0-9])/i),aMat&&aMat[1]&&(a=parseFloat(aMat[1]))),a}function r(a,b){for(var c=1,e=s(a),f=0,g=l.length;g>=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=new Array(this.moduleCount);for(var e=0;e<this.moduleCount;e++)this.modules[d][e]=null}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(a,c),this.typeNumber>=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f<this.modules.length;f++)for(var g=f*e,h=0;h<this.modules[f].length;h++){var i=h*e,j=this.modules[f][h];j&&(d.beginFill(0,100),d.moveTo(i,g),d.lineTo(i+e,g),d.lineTo(i+e,g+e),d.lineTo(i,g+e),d.endFill())}return d},setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(var b=8;b<this.moduleCount-8;b++)null==this.modules[6][b]&&(this.modules[6][b]=0==b%2)},setupPositionAdjustPattern:function(){for(var a=f.getPatternPosition(this.typeNumber),b=0;b<a.length;b++)for(var c=0;c<a.length;c++){var d=a[b],e=a[c];if(null==this.modules[d][e])for(var g=-2;2>=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g<a.length&&(j=1==(1&a[g]>>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h<d.length;h++){var i=d[h];g.put(i.mode,4),g.put(i.getLength(),f.getLengthInBits(i.mode,a)),i.write(g)}for(var l=0,h=0;h<e.length;h++)l+=e[h].dataCount;if(g.getLengthInBits()>8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j<b.length;j++){var k=b[j].dataCount,l=b[j].totalCount-k;d=Math.max(d,k),e=Math.max(e,l),g[j]=new Array(k);for(var m=0;m<g[j].length;m++)g[j][m]=255&a.buffer[m+c];c+=k;var n=f.getErrorCorrectPolynomial(l),o=new i(g[j],n.getLength()-1),p=o.mod(n);h[j]=new Array(n.getLength()-1);for(var m=0;m<h[j].length;m++){var q=m+p.getLength()-h[j].length;h[j][m]=q>=0?p.get(q):0}}for(var r=0,m=0;m<b.length;m++)r+=b[m].totalCount;for(var s=new Array(r),t=0,m=0;d>m;m++)for(var j=0;j<b.length;j++)m<g[j].length&&(s[t++]=g[j][m]);for(var m=0;e>m;m++)for(var j=0;j<b.length;j++)m<h[j].length&&(s[t++]=h[j][m]);return s};for(var c={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},d={L:1,M:0,Q:3,H:2},e={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7},f={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var b=a<<10;f.getBCHDigit(b)-f.getBCHDigit(f.G15)>=0;)b^=f.G15<<f.getBCHDigit(b)-f.getBCHDigit(f.G15);return(a<<10|b)^f.G15_MASK},getBCHTypeNumber:function(a){for(var b=a<<12;f.getBCHDigit(b)-f.getBCHDigit(f.G18)>=0;)b^=f.G18<<f.getBCHDigit(b)-f.getBCHDigit(f.G18);return a<<12|b},getBCHDigit:function(a){for(var b=0;0!=a;)b++,a>>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<<h;for(var h=8;256>h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;c<this.getLength();c++)for(var d=0;d<a.getLength();d++)b[c+d]^=g.gexp(g.glog(this.get(c))+g.glog(a.get(d)));return new i(b,0)},mod:function(a){if(this.getLength()-a.getLength()<0)return this;for(var b=g.glog(this.get(0))-g.glog(a.get(0)),c=new Array(this.getLength()),d=0;d<this.getLength();d++)c[d]=this.get(d);for(var d=0;d<a.getLength();d++)c[d]^=g.gexp(g.glog(a.get(d))+b);return new i(c,0).mod(a)}},j.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],j.getRSBlocks=function(a,b){var c=j.getRsBlockTable(a,b);if(void 0==c)throw new Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+b);for(var d=c.length/3,e=[],f=0;d>f;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=['<table style="border:0;border-collapse:collapse;">'],h=0;d>h;h++){g.push("<tr>");for(var i=0;d>i;i++)g.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:'+e+"px;height:"+f+"px;background-color:"+(a.isDark(h,i)?b.colorDark:b.colorLight)+';"></td>');g.push("</tr>")}g.push("</table>"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}();
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Default delay of exactly seven 24-hour periods in milliseconds.
3
+ *
4
+ * @type {number}
5
+ */
6
+ export const DEFAULT_QUESTIONNAIRE_NOTIFICATION_TIME_MS=7*24*60*60*1000;
7
+
8
+ /**
9
+ * Evaluates whether a one-time questionnaire prompt is due.
10
+ *
11
+ * The class owns only in-memory timing policy. Applications remain responsible
12
+ * for persistence, modal presentation, questionnaire content, and form URLs.
13
+ * Construction has no side effects and starts no timer or background work.
14
+ */
15
+ export class Questionnaire{
16
+ #notificationTime=DEFAULT_QUESTIONNAIRE_NOTIFICATION_TIME_MS;
17
+
18
+ /**
19
+ * Override the delay for this instance without persisting it.
20
+ *
21
+ * @param {number} notificationTime Delay in elapsed milliseconds.
22
+ * @returns {void}
23
+ * @throws {RangeError} When the delay is not a positive finite number.
24
+ */
25
+ setNotificationTime(notificationTime){
26
+ if(!Number.isFinite(notificationTime)||notificationTime<=0){
27
+ throw new RangeError('notificationTime must be a positive finite number');
28
+ }
29
+
30
+ this.#notificationTime=notificationTime;
31
+ }
32
+
33
+ /**
34
+ * Return true only when an unshown prompt has reached its configured delay.
35
+ *
36
+ * Eligibility requires an exact false shown flag, a positive safe-integer
37
+ * anchor timestamp, a finite current time at or after that anchor, and
38
+ * elapsed time greater than or equal to the configured delay. Invalid
39
+ * eligibility state fails closed and returns false.
40
+ *
41
+ * @param {number} firstBootUp Positive millisecond anchor timestamp.
42
+ * @param {boolean} questionnaireShown Persisted prompt-shown flag.
43
+ * @param {number} now Current millisecond timestamp; injectable for tests.
44
+ * @returns {boolean} Whether the unshown prompt is due.
45
+ */
46
+ checkQuestionnaireShown(firstBootUp,questionnaireShown,now=Date.now()){
47
+ if(questionnaireShown!==false){
48
+ return false;
49
+ }
50
+
51
+ if(!Number.isSafeInteger(firstBootUp)||firstBootUp<=0){
52
+ return false;
53
+ }
54
+
55
+ if(!Number.isFinite(now)||now<firstBootUp){
56
+ return false;
57
+ }
58
+
59
+ return now-firstBootUp>=this.#notificationTime;
60
+ }
61
+ }
@@ -0,0 +1,24 @@
1
+ function parseRecordLinks(value='',{pattern=/\b[A-Z]\d{4}\b/gi}={}){
2
+ const text=Array.isArray(value)?value.join(' '):String(value||'');
3
+ return [...new Set([...text.matchAll(pattern)].map(match=>match[0].toUpperCase()))];
4
+ }
5
+
6
+ function buildRecordLinkIndex(records=[],{
7
+ id=item=>item.id,
8
+ links=item=>item.links||[],
9
+ validIds=null
10
+ }={}){
11
+ const known=new Set(validIds||records.map(id).filter(Boolean));
12
+ const outbound={}; const inbound={}; const invalid={};
13
+ for(const record of records){
14
+ const source=String(id(record)||'');
15
+ if(!source) continue;
16
+ const targets=[...new Set(links(record).map(value=>String(value).toUpperCase()))].filter(target=>target&&target!==source);
17
+ outbound[source]=targets.filter(target=>known.has(target));
18
+ invalid[source]=targets.filter(target=>!known.has(target));
19
+ for(const target of outbound[source]) inbound[target]=[...new Set([...(inbound[target]||[]),source])];
20
+ }
21
+ return {outbound,inbound,invalid,linkCount:Object.values(outbound).reduce((sum,targets)=>sum+targets.length,0)};
22
+ }
23
+
24
+ export {buildRecordLinkIndex,parseRecordLinks};
@@ -0,0 +1,222 @@
1
+ const MONTH_NUMBER={
2
+ january:1,february:2,march:3,april:4,may:5,june:6,
3
+ july:7,august:8,september:9,october:10,november:11,december:12
4
+ };
5
+
6
+ const MONTH_TOKEN='January|February|March|April|May|June|July|August|September|October|November|December';
7
+
8
+ function boundedInteger(value,{minimum=0,maximum=10000,fallback=0}={}){
9
+ const number=Number(value);
10
+ return Number.isInteger(number)&&number>=minimum&&number<=maximum?number:fallback;
11
+ }
12
+
13
+ function textLines(value=''){
14
+ return String(value??'').replace(/\r\n?/g,'\n').split('\n');
15
+ }
16
+
17
+ function lineOffsets(lines=[]){
18
+ const offsets=[];
19
+ let offset=0;
20
+ for(const line of lines){
21
+ offsets.push(offset);
22
+ offset+=line.length+1;
23
+ }
24
+ return offsets;
25
+ }
26
+
27
+ function lineAtOffset(offsets=[],index=0){
28
+ let low=0;
29
+ let high=Math.max(0,offsets.length-1);
30
+ while(low<=high){
31
+ const middle=(low+high)>>1;
32
+ if(offsets[middle]<=index) low=middle+1;
33
+ else high=middle-1;
34
+ }
35
+ return Math.max(0,high);
36
+ }
37
+
38
+ function cleanExcerpt(lines=[],start=0,end=start,{maximumLength=1200}={}){
39
+ const maximum=boundedInteger(maximumLength,{minimum:80,maximum:10000,fallback:1200});
40
+ const value=lines.slice(start,end+1)
41
+ .filter(line=>!/^\s*(?:```|---)\s*$/.test(line))
42
+ .map(line=>line.replace(/^\s{0,4}(?:[-*+]\s+|>\s*)?/,'').trim())
43
+ .filter(Boolean)
44
+ .join(' ')
45
+ .replace(/\s+/g,' ')
46
+ .trim();
47
+ return value.length>maximum?`${value.slice(0,maximum-1).trimEnd()}…`:value;
48
+ }
49
+
50
+ function pageMarkers(lines=[]){
51
+ const markers=[];
52
+ for(let index=0;index<lines.length;index++){
53
+ const match=lines[index].match(/^#{1,6}\s+(?:(?:PDF|Text)\s+)?Page\s+(\d+)\b/i);
54
+ if(match) markers.push({line:index,page:Number(match[1])});
55
+ }
56
+ return markers;
57
+ }
58
+
59
+ function pageAtLine(markers=[],line=0){
60
+ let page=null;
61
+ for(const marker of markers){
62
+ if(marker.line>line) break;
63
+ page=marker.page;
64
+ }
65
+ return page;
66
+ }
67
+
68
+ function globalPattern(pattern){
69
+ if(pattern instanceof RegExp){
70
+ return new RegExp(pattern.source,pattern.flags.includes('g')?pattern.flags:`${pattern.flags}g`);
71
+ }
72
+ return new RegExp(String(pattern),'gi');
73
+ }
74
+
75
+ function passageKey(item={}){
76
+ return `${item.ruleId}|${item.page??''}|${item.lineStart}|${item.excerpt.toLocaleLowerCase().replace(/\W+/g,' ').slice(0,180)}`;
77
+ }
78
+
79
+ function findRulePassages(text='',rules=[],{
80
+ recordId='',
81
+ contextLines=2,
82
+ maximumExcerptLength=1200,
83
+ maximumPerRule=40,
84
+ maximumResults=1200
85
+ }={}){
86
+ const source=String(text??'').replace(/\r\n?/g,'\n');
87
+ const lines=textLines(source);
88
+ const offsets=lineOffsets(lines);
89
+ const markers=pageMarkers(lines);
90
+ const defaultContext=boundedInteger(contextLines,{minimum:0,maximum:20,fallback:2});
91
+ const perRule=boundedInteger(maximumPerRule,{minimum:1,maximum:500,fallback:40});
92
+ const totalLimit=boundedInteger(maximumResults,{minimum:1,maximum:10000,fallback:1200});
93
+ const findings=[];
94
+ const seen=new Set();
95
+
96
+ for(const definition of Array.isArray(rules)?rules:[]){
97
+ if(!definition?.id) continue;
98
+ const patterns=Array.isArray(definition.patterns)?definition.patterns:[definition.pattern];
99
+ let ruleCount=0;
100
+ for(const supplied of patterns.filter(Boolean)){
101
+ const pattern=globalPattern(supplied);
102
+ for(const match of source.matchAll(pattern)){
103
+ if(ruleCount>=perRule||findings.length>=totalLimit) break;
104
+ const matchIndex=match.index??0;
105
+ const line=lineAtOffset(offsets,matchIndex);
106
+ const localContext=boundedInteger(definition.contextLines,{minimum:0,maximum:20,fallback:defaultContext});
107
+ const lineStart=Math.max(0,line-localContext);
108
+ const lineEnd=Math.min(lines.length-1,line+localContext);
109
+ const excerpt=cleanExcerpt(lines,lineStart,lineEnd,{maximumLength:definition.maximumExcerptLength||maximumExcerptLength});
110
+ if(!excerpt) continue;
111
+ const candidate={
112
+ id:`${String(recordId||'record')}:${String(definition.id)}:${line+1}:${ruleCount+1}`,
113
+ recordId:String(recordId||''),
114
+ ruleId:String(definition.id),
115
+ label:String(definition.label||definition.id),
116
+ kind:String(definition.kind||'lead'),
117
+ match:String(match[0]||''),
118
+ lineStart:lineStart+1,
119
+ lineEnd:lineEnd+1,
120
+ page:pageAtLine(markers,line),
121
+ excerpt,
122
+ metadata:definition.metadata&&typeof definition.metadata==='object'?{...definition.metadata}:{}
123
+ };
124
+ if(typeof definition.accept==='function'&&!definition.accept(candidate,{match,source,lines,line})) continue;
125
+ const key=passageKey(candidate);
126
+ if(seen.has(key)) continue;
127
+ seen.add(key);
128
+ findings.push(candidate);
129
+ ruleCount++;
130
+ }
131
+ }
132
+ if(findings.length>=totalLimit) break;
133
+ }
134
+ return findings;
135
+ }
136
+
137
+ function validIsoDate(year,month=1,day=1){
138
+ const y=Number(year); const m=Number(month); const d=Number(day);
139
+ if(!Number.isInteger(y)||y<1900||y>2100||!Number.isInteger(m)||m<1||m>12||!Number.isInteger(d)||d<1||d>31) return null;
140
+ const date=new Date(Date.UTC(y,m-1,d));
141
+ if(date.getUTCFullYear()!==y||date.getUTCMonth()!==m-1||date.getUTCDate()!==d) return null;
142
+ return `${String(y).padStart(4,'0')}-${String(m).padStart(2,'0')}-${String(d).padStart(2,'0')}`;
143
+ }
144
+
145
+ function parseDateMention(value=''){
146
+ const raw=String(value||'').trim().replace(/\s+/g,' ');
147
+ let match=raw.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
148
+ if(match) return {isoDate:validIsoDate(match[1],match[2],match[3]),precision:'day'};
149
+ match=raw.match(/^(\d{1,2})[\/-](\d{1,2})[\/-](\d{2}|\d{4})$/);
150
+ if(match){
151
+ const year=match[3].length===2?2000+Number(match[3]):Number(match[3]);
152
+ return {isoDate:validIsoDate(year,match[1],match[2]),precision:'day'};
153
+ }
154
+ match=raw.match(new RegExp(`^(${MONTH_TOKEN})\\s+(\\d{1,2})(?:st|nd|rd|th)?,?\\s+(\\d{4})$`,'i'));
155
+ if(match) return {isoDate:validIsoDate(match[3],MONTH_NUMBER[match[1].toLocaleLowerCase()],match[2]),precision:'day'};
156
+ match=raw.match(new RegExp(`^(${MONTH_TOKEN})\\s+(\\d{4})$`,'i'));
157
+ if(match) return {isoDate:validIsoDate(match[2],MONTH_NUMBER[match[1].toLocaleLowerCase()],1),precision:'month'};
158
+ match=raw.match(/^(19\d{2}|20\d{2})\s+(?:through|to|[-–—])\s+(19\d{2}|20\d{2})$/i);
159
+ if(match) return {isoDate:validIsoDate(match[1],1,1),endDate:validIsoDate(match[2],12,31),precision:'range'};
160
+ return {isoDate:null,precision:'unknown'};
161
+ }
162
+
163
+ function extractDateMentions(text='',{
164
+ recordId='',
165
+ contextLines=1,
166
+ maximumExcerptLength=900,
167
+ maximumResults=2000
168
+ }={}){
169
+ const source=String(text??'').replace(/\r\n?/g,'\n');
170
+ const lines=textLines(source);
171
+ const offsets=lineOffsets(lines);
172
+ const markers=pageMarkers(lines);
173
+ const context=boundedInteger(contextLines,{minimum:0,maximum:20,fallback:1});
174
+ const limit=boundedInteger(maximumResults,{minimum:1,maximum:20000,fallback:2000});
175
+ const patterns=[
176
+ new RegExp(`\\b(?:${MONTH_TOKEN})\\s+\\d{1,2}(?:st|nd|rd|th)?,?\\s+\\d{4}\\b`,'gi'),
177
+ /\b\d{4}-\d{1,2}-\d{1,2}\b/g,
178
+ /\b\d{1,2}[\/-]\d{1,2}[\/-](?:\d{4}|\d{2})\b/g,
179
+ new RegExp(`\\b(?:${MONTH_TOKEN})\\s+\\d{4}\\b`,'gi'),
180
+ /\b(?:19\d{2}|20\d{2})\s+(?:through|to|[-–—])\s+(?:19\d{2}|20\d{2})\b/gi
181
+ ];
182
+ const occupied=[];
183
+ const results=[];
184
+ for(const pattern of patterns){
185
+ for(const match of source.matchAll(pattern)){
186
+ if(results.length>=limit) break;
187
+ const start=match.index??0; const end=start+match[0].length;
188
+ if(occupied.some(range=>start<range.end&&end>range.start)) continue;
189
+ const parsed=parseDateMention(match[0]);
190
+ if(!parsed.isoDate) continue;
191
+ occupied.push({start,end});
192
+ const line=lineAtOffset(offsets,start);
193
+ const lineStart=Math.max(0,line-context);
194
+ const lineEnd=Math.min(lines.length-1,line+context);
195
+ results.push({
196
+ id:`${String(recordId||'record')}:date:${line+1}:${results.length+1}`,
197
+ recordId:String(recordId||''),
198
+ rawDate:String(match[0]),
199
+ isoDate:parsed.isoDate,
200
+ endDate:parsed.endDate||null,
201
+ precision:parsed.precision,
202
+ lineStart:lineStart+1,
203
+ lineEnd:lineEnd+1,
204
+ page:pageAtLine(markers,line),
205
+ excerpt:cleanExcerpt(lines,lineStart,lineEnd,{maximumLength:maximumExcerptLength})
206
+ });
207
+ }
208
+ if(results.length>=limit) break;
209
+ }
210
+ return results.sort((left,right)=>left.isoDate.localeCompare(right.isoDate)||left.lineStart-right.lineStart);
211
+ }
212
+
213
+ export {
214
+ cleanExcerpt,
215
+ extractDateMentions,
216
+ findRulePassages,
217
+ pageAtLine,
218
+ pageMarkers,
219
+ parseDateMention,
220
+ textLines,
221
+ validIsoDate
222
+ };