@wenathlan/saddle 2.0.151 → 2.0.152

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 (293) hide show
  1. package/README.md +3 -3
  2. package/biome.jsonc +12 -5
  3. package/dist/App.d.ts +3 -0
  4. package/dist/App.js +50 -0
  5. package/dist/Sol/Sol.d.ts +3 -0
  6. package/dist/Sol/Sol.js +46 -0
  7. package/dist/Sol/agentbrowser/agentbrowser.d.ts +9 -0
  8. package/dist/Sol/agentbrowser/agentbrowser.js +121 -0
  9. package/dist/Sol/architecture/architecture.d.ts +9 -0
  10. package/dist/Sol/architecture/architecture.js +93 -0
  11. package/dist/Sol/compute/compute.d.ts +9 -0
  12. package/dist/Sol/compute/compute.js +22 -0
  13. package/dist/Sol/console/console.d.ts +15 -0
  14. package/dist/Sol/console/console.js +292 -0
  15. package/dist/Sol/dashboard/dashboard.d.ts +10 -0
  16. package/dist/Sol/dashboard/dashboard.js +181 -0
  17. package/dist/Sol/docs/docs.d.ts +9 -0
  18. package/dist/Sol/docs/docs.js +28 -0
  19. package/dist/Sol/home/home.d.ts +35 -0
  20. package/dist/Sol/home/home.js +239 -0
  21. package/dist/Sol/integrations/integrations.d.ts +9 -0
  22. package/dist/Sol/integrations/integrations.js +83 -0
  23. package/dist/Sol/intro/intro.d.ts +7 -0
  24. package/dist/Sol/intro/intro.js +102 -0
  25. package/dist/Sol/login/login.d.ts +16 -0
  26. package/dist/Sol/login/login.js +49 -0
  27. package/dist/Sol/notfound/notfound.d.ts +9 -0
  28. package/dist/Sol/notfound/notfound.js +15 -0
  29. package/dist/Sol/onboarding/onboarding.d.ts +25 -0
  30. package/dist/Sol/onboarding/onboarding.js +60 -0
  31. package/dist/Sol/playground/playground.d.ts +9 -0
  32. package/dist/Sol/playground/playground.js +127 -0
  33. package/dist/Sol/register/register.d.ts +16 -0
  34. package/dist/Sol/register/register.js +193 -0
  35. package/dist/Sol/shell/Shell.d.ts +124 -0
  36. package/dist/Sol/shell/Shell.js +335 -0
  37. package/dist/Sol/wingets/ComputeBootladder.d.ts +6 -0
  38. package/dist/Sol/wingets/ComputeBootladder.js +71 -0
  39. package/dist/Sol/wingets/ComputeModalitycatalog.d.ts +7 -0
  40. package/dist/Sol/wingets/ComputeModalitycatalog.js +71 -0
  41. package/dist/Sol/wingets/ConsoleApibadge.d.ts +7 -0
  42. package/dist/Sol/wingets/ConsoleApibadge.js +5 -0
  43. package/dist/Sol/wingets/ConsoleEdgelaw.d.ts +6 -0
  44. package/dist/Sol/wingets/ConsoleEdgelaw.js +36 -0
  45. package/dist/Sol/wingets/ConsoleEventstimeline.d.ts +19 -0
  46. package/dist/Sol/wingets/ConsoleEventstimeline.js +12 -0
  47. package/dist/Sol/wingets/ConsoleSnapshotcontrols.d.ts +8 -0
  48. package/dist/Sol/wingets/ConsoleSnapshotcontrols.js +8 -0
  49. package/dist/Sol/wingets/ConsoleSpecpanel.d.ts +21 -0
  50. package/dist/Sol/wingets/ConsoleSpecpanel.js +75 -0
  51. package/dist/Sol/wingets/ConsoleTerminal.d.ts +22 -0
  52. package/dist/Sol/wingets/ConsoleTerminal.js +77 -0
  53. package/dist/Sol/wingets/DashboardAccountpanel.d.ts +18 -0
  54. package/dist/Sol/wingets/DashboardAccountpanel.js +65 -0
  55. package/dist/Sol/wingets/DashboardAdminpanel.d.ts +6 -0
  56. package/dist/Sol/wingets/DashboardAdminpanel.js +90 -0
  57. package/dist/Sol/wingets/DashboardAuditlog.d.ts +8 -0
  58. package/dist/Sol/wingets/DashboardAuditlog.js +50 -0
  59. package/dist/Sol/wingets/DashboardCreatesandboxform.d.ts +10 -0
  60. package/dist/Sol/wingets/DashboardCreatesandboxform.js +71 -0
  61. package/dist/Sol/wingets/DashboardEventstimeline.d.ts +8 -0
  62. package/dist/Sol/wingets/DashboardEventstimeline.js +83 -0
  63. package/dist/Sol/wingets/DashboardGlobalsandboxestable.d.ts +8 -0
  64. package/dist/Sol/wingets/DashboardGlobalsandboxestable.js +57 -0
  65. package/dist/Sol/wingets/DashboardMeshnodestable.d.ts +8 -0
  66. package/dist/Sol/wingets/DashboardMeshnodestable.js +103 -0
  67. package/dist/Sol/wingets/DashboardSandboxlist.d.ts +10 -0
  68. package/dist/Sol/wingets/DashboardSandboxlist.js +163 -0
  69. package/dist/Sol/wingets/DashboardUserstable.d.ts +8 -0
  70. package/dist/Sol/wingets/DashboardUserstable.js +78 -0
  71. package/dist/Sol/wingets/HomeEntrygate.d.ts +23 -0
  72. package/dist/Sol/wingets/HomeEntrygate.js +73 -0
  73. package/dist/Sol/wingets/HomeMetricstrip.d.ts +9 -0
  74. package/dist/Sol/wingets/HomeMetricstrip.js +4 -0
  75. package/dist/Sol/wingets/HomeRuntimediagram.d.ts +1 -0
  76. package/dist/Sol/wingets/HomeRuntimediagram.js +22 -0
  77. package/dist/Sol/wingets/LoginForm.d.ts +19 -0
  78. package/dist/Sol/wingets/LoginForm.js +141 -0
  79. package/dist/Sol/wingets/RegisterPasswordconfirm.d.ts +13 -0
  80. package/dist/Sol/wingets/RegisterPasswordconfirm.js +16 -0
  81. package/dist/Sol/wingets/RegisterPasswordmeter.d.ts +19 -0
  82. package/dist/Sol/wingets/RegisterPasswordmeter.js +65 -0
  83. package/dist/Sol/wingets/RegisterUsernamerules.d.ts +11 -0
  84. package/dist/Sol/wingets/RegisterUsernamerules.js +16 -0
  85. package/dist/Sol/wingets/index.d.ts +39 -0
  86. package/dist/Sol/wingets/index.js +39 -0
  87. package/dist/alternatives.d.ts +508 -0
  88. package/dist/alternatives.js +950 -0
  89. package/dist/api.d.ts +154 -0
  90. package/dist/api.js +174 -0
  91. package/dist/auth.d.ts +268 -0
  92. package/dist/auth.js +476 -0
  93. package/dist/automation.d.ts +425 -0
  94. package/dist/automation.js +566 -0
  95. package/dist/browser.d.ts +561 -0
  96. package/dist/browser.js +1058 -0
  97. package/dist/capacitor.config.d.ts +45 -0
  98. package/dist/capacitor.config.js +48 -0
  99. package/dist/catalog.d.ts +43 -0
  100. package/dist/catalog.js +44 -0
  101. package/dist/cli.d.ts +16 -0
  102. package/dist/cli.js +168 -0
  103. package/dist/communication.d.ts +181 -0
  104. package/dist/communication.js +376 -0
  105. package/dist/compute.d.ts +1160 -0
  106. package/dist/compute.js +2272 -0
  107. package/dist/dashboardlib.d.ts +58 -0
  108. package/dist/dashboardlib.js +161 -0
  109. package/dist/db.d.ts +569 -0
  110. package/dist/db.js +1080 -0
  111. package/dist/distribution.d.ts +528 -0
  112. package/dist/distribution.js +810 -0
  113. package/dist/drizzle.config.d.ts +20 -0
  114. package/dist/drizzle.config.js +18 -0
  115. package/dist/edgelandes.d.ts +70 -0
  116. package/dist/edgelandes.host.d.ts +26 -0
  117. package/dist/edgelandes.host.js +1089 -0
  118. package/dist/edgelandes.inproc.d.ts +27 -0
  119. package/dist/edgelandes.inproc.js +952 -0
  120. package/dist/edgelandes.js +1133 -0
  121. package/dist/edgelandes.ninety.d.ts +31 -0
  122. package/dist/edgelandes.ninety.js +174 -0
  123. package/dist/edgelandes.virtual.d.ts +25 -0
  124. package/dist/edgelandes.virtual.js +629 -0
  125. package/dist/edgeplane.d.ts +114 -0
  126. package/dist/edgeplane.js +305 -0
  127. package/dist/examplelocaljob.d.ts +1 -0
  128. package/{docs/example-localjob.ts → dist/examplelocaljob.js} +0 -1
  129. package/dist/execution.d.ts +529 -0
  130. package/dist/execution.js +760 -0
  131. package/dist/family.d.ts +16 -0
  132. package/dist/family.js +42 -0
  133. package/dist/familyurl.d.ts +21 -0
  134. package/dist/familyurl.js +24 -0
  135. package/dist/format.d.ts +18 -0
  136. package/dist/format.js +59 -0
  137. package/dist/foundation.d.ts +172 -0
  138. package/dist/foundation.js +312 -0
  139. package/dist/index.d.ts +735 -0
  140. package/dist/index.js +1020 -0
  141. package/dist/integration.d.ts +235 -0
  142. package/dist/integration.js +228 -0
  143. package/dist/intelligence.d.ts +93 -0
  144. package/dist/intelligence.js +144 -0
  145. package/dist/isolation.d.ts +128 -0
  146. package/dist/isolation.js +95 -0
  147. package/dist/labels.d.ts +665 -0
  148. package/dist/labels.js +751 -0
  149. package/dist/localauth.d.ts +127 -0
  150. package/dist/localauth.js +413 -0
  151. package/dist/media.d.ts +343 -0
  152. package/dist/media.js +985 -0
  153. package/dist/mesh.d.ts +177 -0
  154. package/dist/mesh.js +468 -0
  155. package/dist/modalityregistry.d.ts +109 -0
  156. package/dist/modalityregistry.js +1386 -0
  157. package/dist/modes.d.ts +358 -0
  158. package/dist/modes.js +133 -0
  159. package/dist/onboardingstate.d.ts +79 -0
  160. package/dist/onboardingstate.js +282 -0
  161. package/dist/orchestrator.d.ts +1338 -0
  162. package/dist/orchestrator.js +4310 -0
  163. package/dist/packager/targetcli.js +4 -0
  164. package/dist/paths.d.ts +2 -0
  165. package/dist/paths.js +14 -0
  166. package/dist/performance.d.ts +415 -0
  167. package/dist/performance.js +826 -0
  168. package/dist/quantum.d.ts +809 -0
  169. package/dist/quantum.js +2072 -0
  170. package/dist/render.d.ts +380 -0
  171. package/dist/render.js +1072 -0
  172. package/dist/runall.d.ts +2 -0
  173. package/dist/runall.js +39 -0
  174. package/dist/runtimeprobe.d.ts +4 -0
  175. package/dist/runtimeprobe.js +8 -0
  176. package/dist/sandbox.d.ts +355 -0
  177. package/dist/sandbox.js +1721 -0
  178. package/dist/sandboxenvprofile.d.ts +29 -0
  179. package/dist/sandboxenvprofile.js +89 -0
  180. package/dist/sandboxprofile.d.ts +137 -0
  181. package/dist/sandboxprofile.js +241 -0
  182. package/dist/sandboxqueue.d.ts +108 -0
  183. package/dist/sandboxqueue.js +201 -0
  184. package/dist/sandboxquota.d.ts +121 -0
  185. package/dist/sandboxquota.js +236 -0
  186. package/dist/scheduler.d.ts +595 -0
  187. package/dist/scheduler.js +1258 -0
  188. package/dist/scriptlanes.d.ts +182 -0
  189. package/dist/scriptlanes.js +660 -0
  190. package/dist/scripts.buildengine.d.ts +1 -0
  191. package/dist/scripts.buildengine.js +39 -0
  192. package/dist/scripts.securitygate.d.ts +1 -0
  193. package/dist/scripts.securitygate.js +68 -0
  194. package/dist/scripts.validateflatnative.d.ts +1 -0
  195. package/dist/scripts.validateflatnative.js +72 -0
  196. package/dist/security.d.ts +500 -0
  197. package/dist/security.js +1169 -0
  198. package/dist/seedcatalog.d.ts +13 -0
  199. package/dist/seedcatalog.js +46 -0
  200. package/dist/server.d.ts +20 -0
  201. package/dist/server.js +53 -0
  202. package/dist/speccatalog.d.ts +31 -0
  203. package/dist/speccatalog.js +163 -0
  204. package/dist/tiers.d.ts +1001 -0
  205. package/dist/tiers.js +2169 -0
  206. package/dist/utils.d.ts +2 -0
  207. package/dist/utils.js +5 -0
  208. package/dist/vaulthandoff.d.ts +141 -0
  209. package/dist/vaulthandoff.js +444 -0
  210. package/dist/virtual.d.ts +640 -0
  211. package/dist/virtual.js +1284 -0
  212. package/dist/virtualcpu.d.ts +315 -0
  213. package/dist/virtualcpu.js +1180 -0
  214. package/dist/virtualgpu.d.ts +344 -0
  215. package/dist/virtualgpu.js +1000 -0
  216. package/dist/virtualization.d.ts +606 -0
  217. package/dist/virtualization.js +1186 -0
  218. package/dist/virtualmemory.d.ts +817 -0
  219. package/dist/virtualmemory.js +1795 -0
  220. package/dist/vite.config.d.ts +3 -0
  221. package/dist/vite.config.js +43 -0
  222. package/dist/vitest.config.d.ts +2 -0
  223. package/dist/vitest.config.js +25 -0
  224. package/dist/webserver.d.ts +166 -0
  225. package/dist/webserver.js +2226 -0
  226. package/dist/workloadlaw.d.ts +133 -0
  227. package/dist/workloadlaw.js +193 -0
  228. package/docs/artifactavailability.md +2 -2
  229. package/docs/{containerplatforms-2.1.0.md → containerplatforms.2.1.0.md} +1 -1
  230. package/docs/{documentmap-1.8.12.md → documentmap.1.8.12.md} +1 -1
  231. package/docs/ninety.modalities.md +108 -0
  232. package/docs/planning.ninety.md +391 -0
  233. package/docs/{releasenotes-1.8.13.md → releasenotes.1.8.13.md} +2 -2
  234. package/docs/{releasenotes-1.8.14.md → releasenotes.1.8.14.md} +1 -1
  235. package/docs/{repo-synthesis-1.8.16.md → repo.synthesis.1.8.16.md} +1 -1
  236. package/docs/{todo-1.8.15.md → todo.1.8.15.md} +1 -1
  237. package/docs/{todo-1.8.16.md → todo.1.8.16.md} +5 -5
  238. package/docs/todo.md +2 -2
  239. package/docs/workloadlaw.md +104 -0
  240. package/index.ts +1 -1
  241. package/manifest.json +1 -1
  242. package/modalityregistry.ts +1371 -34
  243. package/orchestrator.ts +1 -1
  244. package/package.json +73 -33
  245. package/docs/CONVERSA.txt +0 -1523
  246. package/docs/architecture-1.8.19.png +0 -0
  247. package/docs/assets/architecture.svg +0 -45
  248. package/docs/assets/saddle-browser-trace.webp +0 -0
  249. package/docs/assets/saddle-hero-bridge.webp +0 -0
  250. package/docs/assets/saddle-mark.webp +0 -0
  251. package/docs/assets/saddle-runtime-map.webp +0 -0
  252. package/docs/assets/saddlemark.svg +0 -13
  253. package/docs/saddle.archive.1.8.17.tar.gz.gpg +0 -0
  254. /package/docs/{architecture-1.8.19.md → architecture.1.8.19.md} +0 -0
  255. /package/docs/{architecture-1.8.19.mmd → architecture.1.8.19.mmd} +0 -0
  256. /package/docs/{artifactresearch-1.8.9.md → artifactresearch.1.8.9.md} +0 -0
  257. /package/docs/{batches-1816-1820.md → batches.1816.to.1820.md} +0 -0
  258. /package/docs/{brancharchive-2026-08-18.md → brancharchive.2026.08.18.md} +0 -0
  259. /package/docs/{containerplatforms-1.8.17.md → containerplatforms.1.8.17.md} +0 -0
  260. /package/docs/{e2ugh-engine.md → e2ugh.engine.md} +0 -0
  261. /package/docs/{flatbuildresearch-1.8.11.md → flatbuildresearch.1.8.11.md} +0 -0
  262. /package/docs/{licenseaudit-1.8.12.md → licenseaudit.1.8.12.md} +0 -0
  263. /package/docs/{mobileconversionresearch-1.8.10.md → mobileconversionresearch.1.8.10.md} +0 -0
  264. /package/docs/{native-wrappers.md → native.wrappers.md} +0 -0
  265. /package/docs/{nativeidentityresearch-1.8.11.md → nativeidentityresearch.1.8.11.md} +0 -0
  266. /package/docs/{releasenotes-1.8.12.md → releasenotes.1.8.12.md} +0 -0
  267. /package/docs/{releasenotes-1.8.15.md → releasenotes.1.8.15.md} +0 -0
  268. /package/docs/{releasenotes-1.8.16.md → releasenotes.1.8.16.md} +0 -0
  269. /package/docs/{releasenotes-1.8.17.md → releasenotes.1.8.17.md} +0 -0
  270. /package/docs/{releasenotes-1.8.18.md → releasenotes.1.8.18.md} +0 -0
  271. /package/docs/{releasenotes-1.8.19.md → releasenotes.1.8.19.md} +0 -0
  272. /package/docs/{reorganization-1.8.8.md → reorganization.1.8.8.md} +0 -0
  273. /package/docs/{reorganization-1.8.9.md → reorganization.1.8.9.md} +0 -0
  274. /package/docs/{repo-analysis-1.8.21.json → repo.analysis.1.8.21.json} +0 -0
  275. /package/docs/{repo-candidates-1.8.21.jsonl → repo.candidates.1.8.21.jsonl} +0 -0
  276. /package/docs/{repo-research-1.8.16.md → repo.research.1.8.16.md} +0 -0
  277. /package/docs/{repo-research-1.8.21.md → repo.research.1.8.21.md} +0 -0
  278. /package/docs/{repo-selected-1.8.21.json → repo.selected.1.8.21.json} +0 -0
  279. /package/docs/{repo-synthesis-1.8.21.md → repo.synthesis.1.8.21.md} +0 -0
  280. /package/docs/{research-1.8.18-isolation.md → research.1.8.18.isolation.md} +0 -0
  281. /package/docs/{research-1.8.19-virtual-browser.md → research.1.8.19.virtual.browser.md} +0 -0
  282. /package/docs/{scanningresearch-1.8.11.md → scanningresearch.1.8.11.md} +0 -0
  283. /package/docs/{securityaudit-1.8.7.md → securityaudit.1.8.7.md} +0 -0
  284. /package/docs/{securityreferences-1.8.11.md → securityreferences.1.8.11.md} +0 -0
  285. /package/docs/{securityresearch-1.8.11.md → securityresearch.1.8.11.md} +0 -0
  286. /package/docs/{signingresearch-1.8.12.md → signingresearch.1.8.12.md} +0 -0
  287. /package/docs/{tauri-glib-risk-1.8.11.md → tauri.glib.risk.1.8.11.md} +0 -0
  288. /package/docs/{todo-1.8.18.md → todo.1.8.18.md} +0 -0
  289. /package/docs/{todo-1.8.19.md → todo.1.8.19.md} +0 -0
  290. /package/docs/{toolchainresearch-1.8.9.md → toolchainresearch.1.8.9.md} +0 -0
  291. /package/docs/{web-duplicatecleanup.md → web.duplicatecleanup.md} +0 -0
  292. /package/docs/{web-ideas.md → web.ideas.md} +0 -0
  293. /package/docs/{workflowimprovements-2026-08-19.md → workflowimprovements.2026.08.19.md} +0 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * onboardingstate.ts — the ONE writer of the visitor's first-run flags, the
3
+ * state of the entry-flow contract (intro → onboarding → the platform). The
4
+ * state is { introseen, onboardingseen } and it is catalogued, not just
5
+ * browser: the accessor consults the platform database over GET
6
+ * /api/v1/onboarding and writes it over POST /api/v1/onboarding, both HTTPS
7
+ * only — the base url arrives through VITE_ONBOARDING_URL and every
8
+ * candidate host is validated first (localhost, loopback, private,
9
+ * link-local, CGNAT and the reserved documentation ranges are refused, so
10
+ * the flags never ride a dev or internal endpoint; mapped IPv6 literals are
11
+ * normalized back to their embedded IPv4 before the public check). The
12
+ * localStorage key saddle.onboarding.seen is purely the offline cache of
13
+ * the database (the DB is the source of truth; the cache avoids replaying
14
+ * the flow offline) and module memory covers client-side re-navigation.
15
+ * Reads reconcile with a seen-wins merge — a stage seen anywhere stays
16
+ * seen, the flow is never forced twice. The page anchors never touch the
17
+ * flags directly: the intro marks introseen, the onboarding marks
18
+ * onboardingseen, the home entry gate only reads.
19
+ */
20
+ /** the localStorage cache key (the DB mirror, never the source of truth). */
21
+ const CACHE_KEY = 'saddle.onboarding.seen';
22
+ /** the localStorage key of the stable per-browser visitor id. */
23
+ const VISITOR_KEY = 'saddle.visitor.id';
24
+ /** the configured platform base (HTTPS self-hosted gateway); empty answers cache-only. */
25
+ const BASE = (typeof import.meta !== 'undefined'
26
+ ? import.meta.env?.VITE_ONBOARDING_URL
27
+ : undefined)?.trim().replace(/\/+$/, '') ?? '';
28
+ /** the platform route the accessor rides (the gateway serves /api/v1/*). */
29
+ const API_PATH = '/api/v1/onboarding';
30
+ /** the abort window of the platform read (ms) — the flow never blocks on a slow gateway. */
31
+ const READ_TIMEOUT_MS = 2500;
32
+ /** module memory: the flags of the session, consulted before any i/o. */
33
+ let memory = null;
34
+ /**
35
+ * Validates a platform base url: HTTPS only, and the host must be public —
36
+ * localhost, loopback, private, link-local, CGNAT, benchmarking,
37
+ * unique-local and the reserved documentation ranges are refused; a mapped
38
+ * IPv6 literal is normalized to its embedded IPv4 first; any other IPv6
39
+ * literal is refused (the gateway rides a name).
40
+ *
41
+ * @param base the configured base url candidate.
42
+ * @returns true when the base may be consulted.
43
+ */
44
+ export function hostallowed(base) {
45
+ try {
46
+ const url = new URL(base);
47
+ if (url.protocol !== 'https:')
48
+ return false;
49
+ const host = url.hostname
50
+ .toLowerCase()
51
+ .replace(/\.+$/, '')
52
+ .replace(/^\[|\]$/g, '');
53
+ if (host.length === 0)
54
+ return false;
55
+ if (host === 'localhost' || host === '0.0.0.0' || host === '::1' || host === '::')
56
+ return false;
57
+ if (host.endsWith('.localhost') || host.endsWith('.local') || host.endsWith('.internal')) {
58
+ return false;
59
+ }
60
+ if (host.endsWith('.home.arpa'))
61
+ return false;
62
+ if (host.includes(':'))
63
+ return mappedipv6(host);
64
+ const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
65
+ if (v4)
66
+ return publicipv4(Number(v4[1]), Number(v4[2]));
67
+ return true;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ /**
74
+ * Answers whether an IPv6 literal is the IPv4-mapped form of a PUBLIC
75
+ * address (the url parser normalizes `::ffff:127.0.0.1` to hex hextets, so
76
+ * both spellings are normalized here and the embedded IPv4 decides).
77
+ *
78
+ * @param host the bracket-free ipv6 literal.
79
+ * @returns true when the literal maps to a public ipv4 address.
80
+ */
81
+ function mappedipv6(host) {
82
+ if (!host.startsWith('::ffff:'))
83
+ return false;
84
+ const tail = host.slice('::ffff:'.length);
85
+ const decimals = tail.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
86
+ if (decimals)
87
+ return publicipv4(Number(decimals[1]), Number(decimals[2]));
88
+ // the hex spelling the url parser produces: the first hextet of the pair
89
+ // carries the first two ipv4 octets, and they alone decide the ranges
90
+ const hextets = tail.match(/^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
91
+ if (hextets) {
92
+ const high = Number.parseInt(hextets[1], 16);
93
+ return publicipv4((high >> 8) & 0xff, high & 0xff);
94
+ }
95
+ return false;
96
+ }
97
+ /** the public side of an ipv4 address (the first two octets decide). */
98
+ function publicipv4(a, b) {
99
+ if (a === 0 || a === 10 || a === 127)
100
+ return false;
101
+ if (a === 172 && b >= 16 && b <= 31)
102
+ return false;
103
+ if (a === 192 && b === 168)
104
+ return false;
105
+ if (a === 169 && b === 254)
106
+ return false;
107
+ if (a === 100 && b >= 64 && b <= 127)
108
+ return false;
109
+ if (a === 192 && b === 0)
110
+ return false;
111
+ if (a === 198 && (b === 18 || b === 19))
112
+ return false;
113
+ if (a === 198 && (b === 51 || b === 52))
114
+ return false;
115
+ if (a === 203 && b === 0)
116
+ return false;
117
+ if (a >= 224)
118
+ return false;
119
+ return true;
120
+ }
121
+ /**
122
+ * Answers the flags after one stage was marked seen (pure: the writer and
123
+ * the tests share this merge — seen-wins, a stage never unsees).
124
+ *
125
+ * @param state the flags before the write.
126
+ * @param stage the stage being marked seen.
127
+ * @returns the flags after the write.
128
+ */
129
+ export function mergeStage(state, stage) {
130
+ return stage === 'intro'
131
+ ? { ...state, introseen: true }
132
+ : { ...state, introseen: true, onboardingseen: true };
133
+ }
134
+ /** Reads the localStorage cache; any corruption answers null (the cache is best effort). */
135
+ function readCache() {
136
+ if (typeof window === 'undefined')
137
+ return null;
138
+ try {
139
+ const raw = window.localStorage.getItem(CACHE_KEY);
140
+ if (!raw)
141
+ return null;
142
+ const parsed = JSON.parse(raw);
143
+ return { introseen: parsed.introseen === true, onboardingseen: parsed.onboardingseen === true };
144
+ }
145
+ catch {
146
+ return null;
147
+ }
148
+ }
149
+ /** Mirrors the flags into the localStorage cache (silently skipped when storage is sealed). */
150
+ function writeCache(state) {
151
+ if (typeof window === 'undefined')
152
+ return;
153
+ try {
154
+ window.localStorage.setItem(CACHE_KEY, JSON.stringify(state));
155
+ }
156
+ catch {
157
+ // the cache is best effort: the DB and the memory stay authoritative
158
+ }
159
+ }
160
+ /**
161
+ * The stable per-browser visitor id (generated once, cached beside the
162
+ * flags); storage unavailable answers the shared web key.
163
+ *
164
+ * @returns the visitor string the gateway rows are keyed by.
165
+ */
166
+ function visitor() {
167
+ if (typeof window === 'undefined')
168
+ return 'web';
169
+ try {
170
+ const existing = window.localStorage.getItem(VISITOR_KEY);
171
+ if (existing)
172
+ return existing;
173
+ const fresh = crypto.randomUUID();
174
+ window.localStorage.setItem(VISITOR_KEY, fresh);
175
+ return fresh;
176
+ }
177
+ catch {
178
+ return 'web';
179
+ }
180
+ }
181
+ /** Reads the flags off the platform database; unreachable or unconfigured answers null. */
182
+ async function readRemote() {
183
+ if (!BASE || !hostallowed(BASE) || typeof fetch === 'undefined')
184
+ return null;
185
+ const controller = new AbortController();
186
+ const timer = setTimeout(() => controller.abort(), READ_TIMEOUT_MS);
187
+ try {
188
+ const response = await fetch(`${BASE}${API_PATH}?visitor=${encodeURIComponent(visitor())}`, {
189
+ headers: { accept: 'application/json' },
190
+ signal: controller.signal,
191
+ });
192
+ if (!response.ok)
193
+ return null;
194
+ const answer = (await response.json());
195
+ return { introseen: answer.introseen === true, onboardingseen: answer.onboardingseen === true };
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ finally {
201
+ clearTimeout(timer);
202
+ }
203
+ }
204
+ /** Writes the flags to the platform database over POST; failures leave the cache holding them. */
205
+ async function writeRemote(state) {
206
+ if (!BASE || !hostallowed(BASE) || typeof fetch === 'undefined')
207
+ return;
208
+ try {
209
+ await fetch(`${BASE}${API_PATH}`, {
210
+ method: 'POST',
211
+ headers: { 'content-type': 'application/json' },
212
+ body: JSON.stringify({ visitor: visitor(), ...state }),
213
+ });
214
+ }
215
+ catch {
216
+ // the cache mirrors the flags until the platform answers again
217
+ }
218
+ }
219
+ /**
220
+ * Synchronous read of the known state: module memory first, then the
221
+ * localStorage cache. The entry gate uses this for the flash-free fast
222
+ * path; null means the state is still unknown and the caller must await
223
+ * loadOnboardingState.
224
+ *
225
+ * @returns the known flags, or null when nothing is known yet.
226
+ */
227
+ export function readOnboardingState() {
228
+ if (memory)
229
+ return memory;
230
+ const cached = readCache();
231
+ if (cached) {
232
+ memory = cached;
233
+ return cached;
234
+ }
235
+ return null;
236
+ }
237
+ /**
238
+ * Full resolve of the flags: memory/cache first, then the platform database
239
+ * over GET /api/v1/onboarding — reconciled with the seen-wins merge so a
240
+ * stage seen anywhere stays seen. The session answer is memoized;
241
+ * re-navigation never refetches.
242
+ *
243
+ * @returns the resolved visitor onboarding flags.
244
+ */
245
+ export async function loadOnboardingState() {
246
+ const local = readOnboardingState() ?? { introseen: false, onboardingseen: false };
247
+ if (!BASE || !hostallowed(BASE) || (local.introseen && local.onboardingseen))
248
+ return local;
249
+ const remote = await readRemote();
250
+ if (!remote)
251
+ return local;
252
+ const merged = {
253
+ introseen: local.introseen || remote.introseen,
254
+ onboardingseen: local.onboardingseen || remote.onboardingseen,
255
+ };
256
+ memory = merged;
257
+ writeCache(merged);
258
+ return merged;
259
+ }
260
+ /**
261
+ * Marks the intro stage seen and persists it everywhere: module memory, the
262
+ * localStorage cache and — when a platform base is configured — the
263
+ * database over POST /api/v1/onboarding. The ONLY writer of the intro flag.
264
+ */
265
+ export function markIntroSeen() {
266
+ memory = mergeStage(memory ?? readCache() ?? { introseen: false, onboardingseen: false }, 'intro');
267
+ writeCache(memory);
268
+ void writeRemote(memory);
269
+ }
270
+ /**
271
+ * Marks the onboarding stage seen (finish or skip — both imply the intro)
272
+ * and persists it everywhere. The ONLY writer of the onboarding flag.
273
+ */
274
+ export function markOnboardingSeen() {
275
+ memory = mergeStage(memory ?? readCache() ?? { introseen: false, onboardingseen: false }, 'onboarding');
276
+ writeCache(memory);
277
+ void writeRemote(memory);
278
+ }
279
+ /** Test seam: clears the module memory (the cache and the platform are untouched). */
280
+ export function resetOnboardingMemory() {
281
+ memory = null;
282
+ }