@mailwoman/react 9.4.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (370) hide show
  1. package/README.md +17 -16
  2. package/fonts.css +97 -22
  3. package/lib/common/About.tsx +87 -71
  4. package/lib/common/ClientOnly.tsx +2 -14
  5. package/lib/common/CopyButton.tsx +3 -1
  6. package/lib/common/LoadingIndicator.tsx +4 -2
  7. package/lib/common/PresetChips.tsx +9 -8
  8. package/lib/common/confidence-tiers.ts +7 -6
  9. package/lib/common/cx.ts +3 -1
  10. package/lib/common/text-tokens.ts +7 -4
  11. package/lib/common/useBuildInfo.ts +54 -0
  12. package/lib/common/useClipboard.ts +2 -11
  13. package/lib/common.ts +22 -0
  14. package/lib/index.ts +27 -112
  15. package/lib/map/AppIdentity.tsx +85 -0
  16. package/lib/map/BackendControl.tsx +16 -13
  17. package/lib/map/CompareToggle.tsx +11 -33
  18. package/lib/map/DebugInfo.tsx +88 -0
  19. package/lib/map/Geocoder.tsx +92 -58
  20. package/lib/map/GeocoderControls.tsx +582 -127
  21. package/lib/map/GraticuleLayer.tsx +54 -0
  22. package/lib/map/MapCanvas.tsx +23 -12
  23. package/lib/map/MapChipRow.tsx +73 -0
  24. package/lib/map/MapCompass.tsx +68 -0
  25. package/lib/map/MapControlStack.tsx +97 -0
  26. package/lib/map/MapFooter.tsx +101 -0
  27. package/lib/map/MapProgressBar.tsx +55 -0
  28. package/lib/map/MapSearchBar.tsx +82 -0
  29. package/lib/map/MapSheet.tsx +71 -0
  30. package/lib/map/OverlayLayers.tsx +1 -1
  31. package/lib/map/PlaceAutocomplete.tsx +10 -21
  32. package/lib/map/PlaceMarker.tsx +4 -2
  33. package/lib/map/ResolvedPlaceLayers.tsx +5 -14
  34. package/lib/map/ResultCamera.tsx +8 -31
  35. package/lib/map/ResultOverlay.tsx +2 -12
  36. package/lib/map/ResultPanel.tsx +1 -20
  37. package/lib/map/VersionPicker.tsx +10 -19
  38. package/lib/map/fake-runtime.ts +10 -12
  39. package/lib/map/geometry.ts +14 -47
  40. package/lib/map/graticule.ts +80 -0
  41. package/lib/map/map-debug.ts +1 -1
  42. package/lib/map/place-render.ts +28 -60
  43. package/lib/map/types.ts +65 -97
  44. package/lib/map/useCompareState.ts +5 -4
  45. package/lib/map/useGeocode.ts +7 -5
  46. package/lib/map/useMapBearing.ts +56 -0
  47. package/lib/map/useMapLabelPick.ts +114 -0
  48. package/lib/map/useMapPlaceRender.ts +2 -6
  49. package/lib/map/usePlaceAutocomplete.ts +44 -50
  50. package/lib/map.ts +95 -0
  51. package/lib/pipeline/CandidatePicker.tsx +13 -5
  52. package/lib/pipeline/ConfidenceCell.tsx +1 -1
  53. package/lib/pipeline/FailureDiagnostic.tsx +8 -2
  54. package/lib/pipeline/QueryForm.tsx +5 -2
  55. package/lib/pipeline/ResolvedPlace.tsx +1 -1
  56. package/lib/pipeline/SpanHighlight.tsx +7 -5
  57. package/lib/pipeline/TimingPanel.tsx +1 -1
  58. package/lib/pipeline/TreeView.tsx +1 -1
  59. package/lib/pipeline/copy.ts +22 -25
  60. package/lib/pipeline/presets.ts +15 -13
  61. package/lib/pipeline/types.ts +21 -14
  62. package/lib/pipeline/useParsePipeline.ts +38 -24
  63. package/lib/pipeline.ts +36 -0
  64. package/lib/poi/AbstainPanel.tsx +1 -1
  65. package/lib/poi/LiveResultsBlock.tsx +2 -1
  66. package/lib/poi/POIExplorer.tsx +24 -15
  67. package/lib/poi/QueryInput.tsx +1 -1
  68. package/lib/poi/SubjectPanel.tsx +4 -3
  69. package/lib/poi/runtime.ts +5 -16
  70. package/lib/poi/types.ts +39 -27
  71. package/lib/poi/usePOISearch.ts +39 -44
  72. package/lib/poi.ts +39 -0
  73. package/lib/runtime/useReleaseRuntime.ts +125 -74
  74. package/lib/runtime.ts +16 -0
  75. package/lib/search/SearchModal.tsx +317 -0
  76. package/lib/search/search.css +144 -0
  77. package/lib/search/types.ts +67 -0
  78. package/lib/search.ts +11 -0
  79. package/out/common/About.d.ts +13 -4
  80. package/out/common/About.d.ts.map +1 -1
  81. package/out/common/About.js +5 -2
  82. package/out/common/About.js.map +1 -1
  83. package/out/common/ClientOnly.d.ts +2 -11
  84. package/out/common/ClientOnly.d.ts.map +1 -1
  85. package/out/common/ClientOnly.js +1 -13
  86. package/out/common/ClientOnly.js.map +1 -1
  87. package/out/common/CopyButton.d.ts +3 -1
  88. package/out/common/CopyButton.d.ts.map +1 -1
  89. package/out/common/CopyButton.js.map +1 -1
  90. package/out/common/LoadingIndicator.d.ts +4 -2
  91. package/out/common/LoadingIndicator.d.ts.map +1 -1
  92. package/out/common/LoadingIndicator.js.map +1 -1
  93. package/out/common/PresetChips.d.ts +9 -8
  94. package/out/common/PresetChips.d.ts.map +1 -1
  95. package/out/common/PresetChips.js.map +1 -1
  96. package/out/common/confidence-tiers.d.ts +7 -6
  97. package/out/common/confidence-tiers.d.ts.map +1 -1
  98. package/out/common/confidence-tiers.js +7 -6
  99. package/out/common/confidence-tiers.js.map +1 -1
  100. package/out/common/cx.d.ts +3 -1
  101. package/out/common/cx.d.ts.map +1 -1
  102. package/out/common/cx.js +3 -1
  103. package/out/common/cx.js.map +1 -1
  104. package/out/common/text-tokens.d.ts +4 -3
  105. package/out/common/text-tokens.d.ts.map +1 -1
  106. package/out/common/text-tokens.js +6 -3
  107. package/out/common/text-tokens.js.map +1 -1
  108. package/out/common/useBuildInfo.d.ts +27 -0
  109. package/out/common/useBuildInfo.d.ts.map +1 -0
  110. package/out/common/useBuildInfo.js +32 -0
  111. package/out/common/useBuildInfo.js.map +1 -0
  112. package/out/common/useClipboard.d.ts +1 -3
  113. package/out/common/useClipboard.d.ts.map +1 -1
  114. package/out/common/useClipboard.js +2 -11
  115. package/out/common/useClipboard.js.map +1 -1
  116. package/out/common.d.ts +22 -0
  117. package/out/common.d.ts.map +1 -0
  118. package/out/common.js +15 -0
  119. package/out/common.js.map +1 -0
  120. package/out/index.d.ts +26 -71
  121. package/out/index.d.ts.map +1 -1
  122. package/out/index.js +21 -47
  123. package/out/index.js.map +1 -1
  124. package/out/map/AppIdentity.d.ts +42 -0
  125. package/out/map/AppIdentity.d.ts.map +1 -0
  126. package/out/map/AppIdentity.js +16 -0
  127. package/out/map/AppIdentity.js.map +1 -0
  128. package/out/map/BackendControl.d.ts +9 -10
  129. package/out/map/BackendControl.d.ts.map +1 -1
  130. package/out/map/BackendControl.js +2 -2
  131. package/out/map/BackendControl.js.map +1 -1
  132. package/out/map/CompareToggle.d.ts +8 -31
  133. package/out/map/CompareToggle.d.ts.map +1 -1
  134. package/out/map/CompareToggle.js +5 -2
  135. package/out/map/CompareToggle.js.map +1 -1
  136. package/out/map/DebugInfo.d.ts +34 -0
  137. package/out/map/DebugInfo.d.ts.map +1 -0
  138. package/out/map/DebugInfo.js +39 -0
  139. package/out/map/DebugInfo.js.map +1 -0
  140. package/out/map/Geocoder.d.ts +47 -26
  141. package/out/map/Geocoder.d.ts.map +1 -1
  142. package/out/map/Geocoder.js +34 -50
  143. package/out/map/Geocoder.js.map +1 -1
  144. package/out/map/GeocoderControls.d.ts +23 -33
  145. package/out/map/GeocoderControls.d.ts.map +1 -1
  146. package/out/map/GeocoderControls.js +277 -16
  147. package/out/map/GeocoderControls.js.map +1 -1
  148. package/out/map/GraticuleLayer.d.ts +28 -0
  149. package/out/map/GraticuleLayer.d.ts.map +1 -0
  150. package/out/map/GraticuleLayer.js +25 -0
  151. package/out/map/GraticuleLayer.js.map +1 -0
  152. package/out/map/MapCanvas.d.ts +23 -12
  153. package/out/map/MapCanvas.d.ts.map +1 -1
  154. package/out/map/MapCanvas.js +5 -2
  155. package/out/map/MapCanvas.js.map +1 -1
  156. package/out/map/MapChipRow.d.ts +43 -0
  157. package/out/map/MapChipRow.d.ts.map +1 -0
  158. package/out/map/MapChipRow.js +12 -0
  159. package/out/map/MapChipRow.js.map +1 -0
  160. package/out/map/MapCompass.d.ts +33 -0
  161. package/out/map/MapCompass.d.ts.map +1 -0
  162. package/out/map/MapCompass.js +15 -0
  163. package/out/map/MapCompass.js.map +1 -0
  164. package/out/map/MapControlStack.d.ts +56 -0
  165. package/out/map/MapControlStack.d.ts.map +1 -0
  166. package/out/map/MapControlStack.js +18 -0
  167. package/out/map/MapControlStack.js.map +1 -0
  168. package/out/map/MapFooter.d.ts +27 -0
  169. package/out/map/MapFooter.d.ts.map +1 -0
  170. package/out/map/MapFooter.js +37 -0
  171. package/out/map/MapFooter.js.map +1 -0
  172. package/out/map/MapProgressBar.d.ts +31 -0
  173. package/out/map/MapProgressBar.d.ts.map +1 -0
  174. package/out/map/MapProgressBar.js +8 -0
  175. package/out/map/MapProgressBar.js.map +1 -0
  176. package/out/map/MapSearchBar.d.ts +48 -0
  177. package/out/map/MapSearchBar.d.ts.map +1 -0
  178. package/out/map/MapSearchBar.js +12 -0
  179. package/out/map/MapSearchBar.js.map +1 -0
  180. package/out/map/MapSheet.d.ts +35 -0
  181. package/out/map/MapSheet.d.ts.map +1 -0
  182. package/out/map/MapSheet.js +29 -0
  183. package/out/map/MapSheet.js.map +1 -0
  184. package/out/map/OverlayLayers.d.ts +1 -1
  185. package/out/map/OverlayLayers.js +2 -2
  186. package/out/map/PlaceAutocomplete.d.ts +10 -21
  187. package/out/map/PlaceAutocomplete.d.ts.map +1 -1
  188. package/out/map/PlaceAutocomplete.js +2 -1
  189. package/out/map/PlaceAutocomplete.js.map +1 -1
  190. package/out/map/PlaceMarker.d.ts +4 -2
  191. package/out/map/PlaceMarker.d.ts.map +1 -1
  192. package/out/map/PlaceMarker.js +3 -1
  193. package/out/map/PlaceMarker.js.map +1 -1
  194. package/out/map/ResolvedPlaceLayers.d.ts +5 -14
  195. package/out/map/ResolvedPlaceLayers.d.ts.map +1 -1
  196. package/out/map/ResolvedPlaceLayers.js +2 -2
  197. package/out/map/ResolvedPlaceLayers.js.map +1 -1
  198. package/out/map/ResultCamera.d.ts +8 -31
  199. package/out/map/ResultCamera.d.ts.map +1 -1
  200. package/out/map/ResultCamera.js +3 -28
  201. package/out/map/ResultCamera.js.map +1 -1
  202. package/out/map/ResultOverlay.d.ts +2 -12
  203. package/out/map/ResultOverlay.d.ts.map +1 -1
  204. package/out/map/ResultOverlay.js +1 -2
  205. package/out/map/ResultOverlay.js.map +1 -1
  206. package/out/map/ResultPanel.d.ts +1 -20
  207. package/out/map/ResultPanel.d.ts.map +1 -1
  208. package/out/map/ResultPanel.js +1 -1
  209. package/out/map/ResultPanel.js.map +1 -1
  210. package/out/map/VersionPicker.d.ts +7 -19
  211. package/out/map/VersionPicker.d.ts.map +1 -1
  212. package/out/map/VersionPicker.js +5 -2
  213. package/out/map/VersionPicker.js.map +1 -1
  214. package/out/map/fake-runtime.d.ts +9 -11
  215. package/out/map/fake-runtime.d.ts.map +1 -1
  216. package/out/map/fake-runtime.js +9 -12
  217. package/out/map/fake-runtime.js.map +1 -1
  218. package/out/map/geometry.d.ts +13 -34
  219. package/out/map/geometry.d.ts.map +1 -1
  220. package/out/map/geometry.js +9 -34
  221. package/out/map/geometry.js.map +1 -1
  222. package/out/map/graticule.d.ts +17 -0
  223. package/out/map/graticule.d.ts.map +1 -0
  224. package/out/map/graticule.js +67 -0
  225. package/out/map/graticule.js.map +1 -0
  226. package/out/map/map-debug.d.ts +1 -1
  227. package/out/map/map-debug.d.ts.map +1 -1
  228. package/out/map/map-debug.js +1 -1
  229. package/out/map/map-debug.js.map +1 -1
  230. package/out/map/place-render.d.ts +23 -43
  231. package/out/map/place-render.d.ts.map +1 -1
  232. package/out/map/place-render.js +4 -39
  233. package/out/map/place-render.js.map +1 -1
  234. package/out/map/types.d.ts +67 -95
  235. package/out/map/types.d.ts.map +1 -1
  236. package/out/map/types.js +1 -10
  237. package/out/map/types.js.map +1 -1
  238. package/out/map/useCompareState.d.ts +5 -4
  239. package/out/map/useCompareState.d.ts.map +1 -1
  240. package/out/map/useCompareState.js +3 -3
  241. package/out/map/useCompareState.js.map +1 -1
  242. package/out/map/useGeocode.d.ts +5 -3
  243. package/out/map/useGeocode.d.ts.map +1 -1
  244. package/out/map/useGeocode.js +4 -4
  245. package/out/map/useGeocode.js.map +1 -1
  246. package/out/map/useMapBearing.d.ts +28 -0
  247. package/out/map/useMapBearing.d.ts.map +1 -0
  248. package/out/map/useMapBearing.js +30 -0
  249. package/out/map/useMapBearing.js.map +1 -0
  250. package/out/map/useMapLabelPick.d.ts +13 -0
  251. package/out/map/useMapLabelPick.d.ts.map +1 -0
  252. package/out/map/useMapLabelPick.js +95 -0
  253. package/out/map/useMapLabelPick.js.map +1 -0
  254. package/out/map/useMapPlaceRender.d.ts +2 -6
  255. package/out/map/useMapPlaceRender.d.ts.map +1 -1
  256. package/out/map/useMapPlaceRender.js +2 -6
  257. package/out/map/useMapPlaceRender.js.map +1 -1
  258. package/out/map/usePlaceAutocomplete.d.ts +30 -28
  259. package/out/map/usePlaceAutocomplete.d.ts.map +1 -1
  260. package/out/map/usePlaceAutocomplete.js +6 -33
  261. package/out/map/usePlaceAutocomplete.js.map +1 -1
  262. package/out/map.d.ts +69 -0
  263. package/out/map.d.ts.map +1 -0
  264. package/out/map.js +46 -0
  265. package/out/map.js.map +1 -0
  266. package/out/pipeline/CandidatePicker.d.ts +12 -2
  267. package/out/pipeline/CandidatePicker.d.ts.map +1 -1
  268. package/out/pipeline/CandidatePicker.js +2 -2
  269. package/out/pipeline/CandidatePicker.js.map +1 -1
  270. package/out/pipeline/ConfidenceCell.d.ts +1 -1
  271. package/out/pipeline/ConfidenceCell.js +1 -1
  272. package/out/pipeline/FailureDiagnostic.d.ts +8 -2
  273. package/out/pipeline/FailureDiagnostic.d.ts.map +1 -1
  274. package/out/pipeline/FailureDiagnostic.js +3 -0
  275. package/out/pipeline/FailureDiagnostic.js.map +1 -1
  276. package/out/pipeline/PipelineExplorer.js +1 -1
  277. package/out/pipeline/QueryForm.d.ts +5 -2
  278. package/out/pipeline/QueryForm.d.ts.map +1 -1
  279. package/out/pipeline/QueryForm.js +1 -1
  280. package/out/pipeline/QueryForm.js.map +1 -1
  281. package/out/pipeline/ResolvedPlace.js +1 -1
  282. package/out/pipeline/SpanHighlight.d.ts +4 -3
  283. package/out/pipeline/SpanHighlight.d.ts.map +1 -1
  284. package/out/pipeline/SpanHighlight.js +3 -2
  285. package/out/pipeline/SpanHighlight.js.map +1 -1
  286. package/out/pipeline/TimingPanel.d.ts +1 -1
  287. package/out/pipeline/TimingPanel.js +1 -1
  288. package/out/pipeline/TreeView.d.ts +1 -1
  289. package/out/pipeline/copy.d.ts +1 -1
  290. package/out/pipeline/copy.d.ts.map +1 -1
  291. package/out/pipeline/copy.js +4 -3
  292. package/out/pipeline/copy.js.map +1 -1
  293. package/out/pipeline/presets.d.ts +5 -4
  294. package/out/pipeline/presets.d.ts.map +1 -1
  295. package/out/pipeline/presets.js +15 -13
  296. package/out/pipeline/presets.js.map +1 -1
  297. package/out/pipeline/types.d.ts +21 -14
  298. package/out/pipeline/types.d.ts.map +1 -1
  299. package/out/pipeline/types.js +2 -4
  300. package/out/pipeline/types.js.map +1 -1
  301. package/out/pipeline/useParsePipeline.d.ts +15 -7
  302. package/out/pipeline/useParsePipeline.d.ts.map +1 -1
  303. package/out/pipeline/useParsePipeline.js +10 -7
  304. package/out/pipeline/useParsePipeline.js.map +1 -1
  305. package/out/pipeline.d.ts +33 -0
  306. package/out/pipeline.d.ts.map +1 -0
  307. package/out/pipeline.js +21 -0
  308. package/out/pipeline.js.map +1 -0
  309. package/out/poi/AbstainPanel.d.ts +1 -1
  310. package/out/poi/LiveResultsBlock.d.ts +2 -1
  311. package/out/poi/LiveResultsBlock.d.ts.map +1 -1
  312. package/out/poi/LiveResultsBlock.js +1 -1
  313. package/out/poi/LiveResultsBlock.js.map +1 -1
  314. package/out/poi/POIExplorer.d.ts +22 -12
  315. package/out/poi/POIExplorer.d.ts.map +1 -1
  316. package/out/poi/POIExplorer.js +10 -9
  317. package/out/poi/POIExplorer.js.map +1 -1
  318. package/out/poi/QueryInput.d.ts +1 -1
  319. package/out/poi/SubjectPanel.d.ts +2 -2
  320. package/out/poi/SubjectPanel.d.ts.map +1 -1
  321. package/out/poi/SubjectPanel.js +3 -2
  322. package/out/poi/SubjectPanel.js.map +1 -1
  323. package/out/poi/runtime.d.ts +5 -14
  324. package/out/poi/runtime.d.ts.map +1 -1
  325. package/out/poi/runtime.js +5 -16
  326. package/out/poi/runtime.js.map +1 -1
  327. package/out/poi/types.d.ts +39 -27
  328. package/out/poi/types.d.ts.map +1 -1
  329. package/out/poi/types.js +2 -4
  330. package/out/poi/types.js.map +1 -1
  331. package/out/poi/usePOISearch.d.ts +28 -17
  332. package/out/poi/usePOISearch.d.ts.map +1 -1
  333. package/out/poi/usePOISearch.js +6 -32
  334. package/out/poi/usePOISearch.js.map +1 -1
  335. package/out/poi.d.ts +23 -0
  336. package/out/poi.d.ts.map +1 -0
  337. package/out/poi.js +15 -0
  338. package/out/poi.js.map +1 -0
  339. package/out/runtime/useReleaseRuntime.d.ts +64 -65
  340. package/out/runtime/useReleaseRuntime.d.ts.map +1 -1
  341. package/out/runtime/useReleaseRuntime.js +31 -26
  342. package/out/runtime/useReleaseRuntime.js.map +1 -1
  343. package/out/runtime.d.ts +9 -0
  344. package/out/runtime.d.ts.map +1 -0
  345. package/out/runtime.js +8 -0
  346. package/out/runtime.js.map +1 -0
  347. package/out/search/SearchModal.d.ts +33 -0
  348. package/out/search/SearchModal.d.ts.map +1 -0
  349. package/out/search/SearchModal.js +187 -0
  350. package/out/search/SearchModal.js.map +1 -0
  351. package/out/search/types.d.ts +64 -0
  352. package/out/search/types.d.ts.map +1 -0
  353. package/out/search/types.js +13 -0
  354. package/out/search/types.js.map +1 -0
  355. package/out/search.d.ts +11 -0
  356. package/out/search.d.ts.map +1 -0
  357. package/out/search.js +9 -0
  358. package/out/search.js.map +1 -0
  359. package/package.json +132 -133
  360. package/styles.css +1340 -202
  361. package/tokens/index.css +26 -7
  362. package/tokens/tokens.json +121 -6
  363. package/tokens/tokens.resolver.json +1 -1
  364. package/lib/map/index.ts +0 -76
  365. package/out/map/index.d.ts +0 -52
  366. package/out/map/index.d.ts.map +0 -1
  367. package/out/map/index.js +0 -36
  368. package/out/map/index.js.map +0 -1
  369. package/styleframe.config.ts +0 -321
  370. package/vitest.node.config.ts +0 -20
package/README.md CHANGED
@@ -1,21 +1,22 @@
1
1
  # @mailwoman/react
2
2
 
3
- React components + headless hooks for [mailwoman](https://mailwoman.ai) — the
4
- parse/geocode/POI explorers from the docs site, decomposed and packaged for use in any React app.
3
+ This package provides React components and headless hooks for [mailwoman](https://mailwoman.ai). It
4
+ packages the parse/geocode/POI explorers from the docs site as smaller parts for use in any React app.
5
5
 
6
6
  ## What's in the box
7
7
 
8
- Two composed explorers, plus the small presentational units and headless hooks they decompose into:
8
+ The package contains two composed explorers, plus the small presentational units and headless hooks
9
+ they are built from:
9
10
 
10
- - **`POIExplorer`** — a self-contained POI-intent tester (classify → subject → OverpassQL). The intent
11
+ - **`POIExplorer`** is a self-contained POI-intent tester (classify → subject → OverpassQL). The intent
11
12
  path runs entirely offline over `@mailwoman/kind-classifier`, `@mailwoman/poi-taxonomy`, and
12
- `@mailwoman/query-shape` — no weights, no network. A live poi.db search is opt-in via an injected
13
- `runLiveSearch` probe, so the httpvfs/worker implementation never enters this package's graph.
14
- - **`PipelineExplorer`** — a parse + resolve tester driven by an **injected `PipelineRuntime`**. The
13
+ `@mailwoman/query-shape`, without weights or network access. A live poi.db search is opt-in through an
14
+ injected `runLiveSearch` probe, so the httpvfs/worker implementation never enters this package's graph.
15
+ - **`PipelineExplorer`** is a parse and resolve tester driven by an **injected `PipelineRuntime`**. The
15
16
  host supplies `runParse` (compute shape → classify → resolve) and the heavy visualizers as `panels`,
16
- keeping onnxruntime-web, sql.js-httpvfs, and node builtins out of this package entirely.
17
+ which keeps onnxruntime-web, sqlite-wasm, and node builtins out of this package entirely.
17
18
 
18
- The headless hooks — `usePOISearch`, `useParsePipeline` — own the state machines; the presentational
19
+ The headless hooks, `usePOISearch` and `useParsePipeline`, own the state machines. The presentational
19
20
  units (`QueryInput`, `SubjectPanel`, `OverpassBlock`, `LiveResultsBlock`, `ComponentTable`,
20
21
  `ResolvedPlace`, `CandidatePicker`, `KindBadge`, `LoadingIndicator`, …) are pure and prop-driven.
21
22
 
@@ -26,17 +27,17 @@ import { POIExplorer, PipelineExplorer } from "@mailwoman/react"
26
27
  import "@mailwoman/react/styles.css"
27
28
  ```
28
29
 
29
- Styling ships as a standalone stylesheet (`@mailwoman/react/styles.css`) — plain, `mw-`-prefixed, and
30
- Infima-token-aware, so it looks right both inside Docusaurus and standalone. No component imports CSS,
31
- so the bare package import stays node-safe.
30
+ The stylesheet ships as a standalone file (`@mailwoman/react/styles.css`). It is plain CSS with a `mw-`
31
+ prefix and reads Infima tokens, so it renders correctly both inside Docusaurus and standalone. No
32
+ component imports CSS, so the bare package import stays safe to load in Node.
32
33
 
33
34
  ## Development
34
35
 
35
- - `yarn workspace @mailwoman/react storybook` — Storybook (Vite) for every unit + the composed
36
- explorers, with mocked runtimes (no network/db).
37
- - `yarn workspace @mailwoman/react test:browser` — Vitest browser-mode component + hook tests
36
+ - `yarn workspace @mailwoman/react storybook` runs Storybook (Vite) for every unit and the composed
37
+ explorers, with mocked runtimes that need no network or database.
38
+ - `yarn workspace @mailwoman/react test:browser` runs the Vitest browser-mode component and hook tests
38
39
  (Playwright / headless Chromium).
39
40
 
40
41
  ## License
41
42
 
42
- AGPL-3.0-only OR LicenseRef-Commercial — see the [mailwoman repository](https://github.com/sister-software/mailwoman).
43
+ AGPL-3.0-only OR LicenseRef-Commercial. See the [mailwoman repository](https://github.com/sister-software/mailwoman).
package/fonts.css CHANGED
@@ -1,59 +1,134 @@
1
1
  /**
2
- * The brand typeface, served from the same origin every app already preconnects to.
2
+ * The brand typefaces, served from the same origin every app already preconnects to.
3
3
  *
4
- * Six faces, not the twenty the family ships. Each `@font-face` is a network fetch on first paint, and the token
5
- * layer binds only these: regular and medium for running text, bold where a heading or a chip needs weight, italic
6
- * for a caption, and two mono weights for the `code`, `number` and `glyph` roles. Iosevka provides no 600, so
7
- * `--font-weight-semibold: 600` matches the 700 face by the CSS weight-matching rules rather than being synthesized.
4
+ * The token layer binds five faces: Bliss Pro regular, medium and bold for running text and headings, Bliss Pro
5
+ * italic for a caption, and the variable Fira Code face (weights 300 to 700) for the `code`, `number` and `glyph`
6
+ * roles. Bliss Pro's figures are proportional and it has no `tnum` feature, so `number` sets in Fira Code. Bliss Pro
7
+ * provides no 600, so `--font-weight-semibold: 600` matches the 700 face by the CSS weight-matching rules rather than
8
+ * being synthesized.
9
+ *
10
+ * Each Bliss Pro face is split by `unicode-range` into a Latin file and an extended file (Latin Extended-A, Greek,
11
+ * Cyrillic, math), and the browser downloads the extended file only when a page uses one of its codepoints.
12
+ * "Bliss Pro Fallback" scales Arial to Bliss Pro's average advance and vertical metrics, so text keeps its line
13
+ * breaks when the web font swaps in.
8
14
  *
9
15
  * A rule never names a family here — it names a scale role from `tokens.css`, which names one of the five face
10
16
  * roles. Replacing the typeface is an edit to `--font-family-*` in `styleframe.config.ts` plus this file.
11
17
  */
12
18
 
13
19
  @font-face {
14
- font-family: "Iosevka Nexus Web";
20
+ font-family: "Bliss Pro Fallback";
21
+ src: local("Arial");
22
+ size-adjust: 92%;
23
+ ascent-override: 103.6%;
24
+ descent-override: 26.2%;
25
+ line-gap-override: 0%;
26
+ }
27
+
28
+ @font-face {
29
+ font-family: "Bliss Pro Web";
15
30
  font-display: swap;
16
31
  font-weight: 400;
17
32
  font-style: normal;
18
- src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Regular.woff2") format("woff2");
33
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Latin.woff2") format("woff2");
34
+ unicode-range:
35
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
36
+ U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD, U+FB01-FB02;
19
37
  }
20
38
 
21
39
  @font-face {
22
- font-family: "Iosevka Nexus Web";
40
+ font-family: "Bliss Pro Web";
23
41
  font-display: swap;
24
- font-weight: 450;
42
+ font-weight: 400;
25
43
  font-style: normal;
26
- src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Book.woff2") format("woff2");
44
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Extended.woff2") format("woff2");
45
+ unicode-range:
46
+ U+0100-0129, U+012B-012C, U+012E-0130, U+0132-0137, U+0139-0148, U+014A-0151, U+0154-017E, U+0192, U+01FA-01FF,
47
+ U+0218-0219, U+02C7, U+02D8-02D9, U+02DB, U+02DD, U+0315, U+0326, U+0386, U+0388-038A, U+038C, U+038E-0393,
48
+ U+0395-03A1, U+03A3-03CE, U+0400-045F, U+0462-0463, U+0472-0475, U+0490-0491, U+1E80-1E85, U+1EF2-1EF3, U+2070,
49
+ U+2074-2089, U+208D-208E, U+2113, U+2116-2117, U+212E, U+2153-2154, U+215B-215E, U+2202, U+2206, U+220F, U+2211,
50
+ U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA;
27
51
  }
28
52
 
29
53
  @font-face {
30
- font-family: "Iosevka Nexus Web";
54
+ font-family: "Bliss Pro Web";
31
55
  font-display: swap;
32
- font-weight: 700;
33
- font-style: normal;
34
- src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Bold.woff2") format("woff2");
56
+ font-weight: 400;
57
+ font-style: italic;
58
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Italic-Latin.woff2") format("woff2");
59
+ unicode-range:
60
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
61
+ U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD, U+FB01-FB02;
35
62
  }
36
63
 
37
64
  @font-face {
38
- font-family: "Iosevka Nexus Web";
65
+ font-family: "Bliss Pro Web";
39
66
  font-display: swap;
40
67
  font-weight: 400;
41
68
  font-style: italic;
42
- src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Italic.woff2") format("woff2");
69
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Italic-Extended.woff2") format("woff2");
70
+ unicode-range:
71
+ U+0100-0129, U+012B-012C, U+012E-0130, U+0132-0137, U+0139-0148, U+014A-0151, U+0154-017E, U+0192, U+01FA-01FF,
72
+ U+0218-0219, U+02C7, U+02D8-02D9, U+02DB, U+02DD, U+0315, U+0326, U+0386, U+0388-038A, U+038C, U+038E-0393,
73
+ U+0395-03A1, U+03A3-03CE, U+0400-045F, U+0462-0463, U+0472-0475, U+0490-0491, U+1E80-1E85, U+1EF2-1EF3, U+2070,
74
+ U+2074-2089, U+208D-208E, U+2113, U+2116-2117, U+212E, U+2153-2154, U+215B-215E, U+2202, U+2206, U+220F, U+2211,
75
+ U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA;
43
76
  }
44
77
 
45
78
  @font-face {
46
- font-family: "Iosevka Nexus Mono Web";
79
+ font-family: "Bliss Pro Web";
47
80
  font-display: swap;
48
- font-weight: 400;
81
+ font-weight: 500;
82
+ font-style: normal;
83
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Medium-Latin.woff2") format("woff2");
84
+ unicode-range:
85
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
86
+ U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD, U+FB01-FB02;
87
+ }
88
+
89
+ @font-face {
90
+ font-family: "Bliss Pro Web";
91
+ font-display: swap;
92
+ font-weight: 500;
93
+ font-style: normal;
94
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Medium-Extended.woff2") format("woff2");
95
+ unicode-range:
96
+ U+0100-0129, U+012B-012C, U+012E-0130, U+0132-0137, U+0139-0148, U+014A-0151, U+0154-017E, U+0192, U+01FA-01FF,
97
+ U+0218-0219, U+02C7, U+02D8-02D9, U+02DB, U+02DD, U+0315, U+0326, U+0386, U+0388-038A, U+038C, U+038E-0393,
98
+ U+0395-03A1, U+03A3-03CE, U+0400-045F, U+0462-0463, U+0472-0475, U+0490-0491, U+1E80-1E85, U+1EF2-1EF3, U+2070,
99
+ U+2074-2089, U+208D-208E, U+2113, U+2116-2117, U+212E, U+2153-2154, U+215B-215E, U+2202, U+2206, U+220F, U+2211,
100
+ U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA;
101
+ }
102
+
103
+ @font-face {
104
+ font-family: "Bliss Pro Web";
105
+ font-display: swap;
106
+ font-weight: 700;
107
+ font-style: normal;
108
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Bold-Latin.woff2") format("woff2");
109
+ unicode-range:
110
+ U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC,
111
+ U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD, U+FB01-FB02;
112
+ }
113
+
114
+ @font-face {
115
+ font-family: "Bliss Pro Web";
116
+ font-display: swap;
117
+ font-weight: 700;
49
118
  font-style: normal;
50
- src: url("https://public.mailwoman.ai/fonts/IosevkaNexusMono/WOFF2/IosevkaNexusMono-Regular.woff2") format("woff2");
119
+ src: url("https://public.mailwoman.ai/fonts/BlissPro/WOFF2/BlissPro-Bold-Extended.woff2") format("woff2");
120
+ unicode-range:
121
+ U+0100-0129, U+012B-012C, U+012E-0130, U+0132-0137, U+0139-0148, U+014A-0151, U+0154-017E, U+0192, U+01FA-01FF,
122
+ U+0218-0219, U+02C7, U+02D8-02D9, U+02DB, U+02DD, U+0315, U+0326, U+0386, U+0388-038A, U+038C, U+038E-0393,
123
+ U+0395-03A1, U+03A3-03CE, U+0400-045F, U+0462-0463, U+0472-0475, U+0490-0491, U+1E80-1E85, U+1EF2-1EF3, U+2070,
124
+ U+2074-2089, U+208D-208E, U+2113, U+2116-2117, U+212E, U+2153-2154, U+215B-215E, U+2202, U+2206, U+220F, U+2211,
125
+ U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA;
51
126
  }
52
127
 
53
128
  @font-face {
54
- font-family: "Iosevka Nexus Mono Web";
129
+ font-family: "Fira Code Web";
55
130
  font-display: swap;
56
- font-weight: 450;
131
+ font-weight: 300 700;
57
132
  font-style: normal;
58
- src: url("https://public.mailwoman.ai/fonts/IosevkaNexusMono/WOFF2/IosevkaNexusMono-Book.woff2") format("woff2");
133
+ src: url("https://public.mailwoman.ai/fonts/FiraCode/WOFF2/FiraCode-VF.woff2") format("woff2");
59
134
  }
@@ -3,87 +3,103 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `About` — the collapsible explainer for the browser geocoder: the neural model, the confidence colours, the
7
- * gazetteer resolver, the byte-range database and the FST prior. Prose about the product a host places above or
8
- * beside the geocoder; it renders nothing live.
6
+ * `About` — the explainer for the browser geocoder: the neural model, the confidence colors, the gazetteer resolver, the byte-range database and the FST prior. It renders no live content.
9
7
  */
10
8
 
11
9
  import type { ReactNode } from "react"
12
10
 
13
- export function About(): ReactNode {
11
+ export interface AboutProps {
12
+ /**
13
+ * Wraps the prose in its own `<details>` disclosure.
14
+ *
15
+ * Set false where the host is already the disclosure, because a summary repeating
16
+ * an About button's words is one control too many.
17
+ *
18
+ * @default true
19
+ */
20
+ collapsible?: boolean
21
+ }
22
+
23
+ export function About({ collapsible = true }: AboutProps = {}): ReactNode {
24
+ const body = (
25
+ <div className="mw-about__body">
26
+ <section className="mw-about__section">
27
+ <h3 className="mw-about__heading">Neural model</h3>
28
+ <p>
29
+ A ~29M-parameter BERT-style encoder classifies each token into one of 33 BIO labels covering 16 address
30
+ components (street, house number, unit, locality, region, postcode, venue, country, intersection, etc.). It
31
+ runs entirely in your browser via <strong>onnxruntime-web</strong> (WebGPU with WASM SIMD fallback). The model
32
+ ships as a 37.6 MB int8-quantized ONNX bundle; fp32 weights are also available for higher accuracy.
33
+ </p>
34
+ </section>
35
+
36
+ <section className="mw-about__section">
37
+ <h3 className="mw-about__heading">Confidence colors</h3>
38
+ <p>
39
+ Every classified span carries a confidence score between 0 and 1. The colors in the results table give you a
40
+ quick visual read:
41
+ </p>
42
+ <ul className="mw-about__legend">
43
+ <li className="mw-about__item">
44
+ <span className="mw-about__swatch mw-about__swatch--high" />
45
+ <strong>Green</strong> (≥ 0.8) — high confidence. The model is nearly certain.
46
+ </li>
47
+ <li className="mw-about__item">
48
+ <span className="mw-about__swatch mw-about__swatch--mid" />
49
+ <strong>Amber</strong> (0.5–0.8) — moderate confidence. Worth verifying.
50
+ </li>
51
+ <li className="mw-about__item">
52
+ <span className="mw-about__swatch mw-about__swatch--low" />
53
+ <strong>Red</strong> (&lt; 0.5) — low confidence. The model is unsure — likely wrong.
54
+ </li>
55
+ </ul>
56
+ <p>
57
+ These are raw model scores. For calibrated probabilities, the library ships a per-locale isotonic calibrator (
58
+ <code>calibration.json</code>) that maps the raw scores to well-calibrated probability estimates.
59
+ </p>
60
+ </section>
61
+
62
+ <section className="mw-about__section">
63
+ <h3 className="mw-about__heading">WOF resolver</h3>
64
+ <p>
65
+ After classification, the resolver queries a <strong>Who&apos;s On First</strong> (WOF) SQLite gazetteer to
66
+ match the parsed components — locality, region, and postcode — against real-world places with coordinates. The
67
+ cascade tries postcode first (most precise), then locality, then the raw input text. The result is a sorted
68
+ list of candidate places with WOF IDs, placetypes, lat/lon, and bounding boxes.
69
+ </p>
70
+ </section>
71
+
72
+ <section className="mw-about__section">
73
+ <h3 className="mw-about__heading">Byte-range gazetteer</h3>
74
+ <p>
75
+ The gazetteer is a multi-gigabyte SQLite file served from a public bucket. It loads via{" "}
76
+ <strong>sqlite-wasm</strong> over a read-only HTTP file system. It range-requests only the pages a lookup
77
+ touches. Therefore, your browser fetches a few hundred kilobytes per session instead of the whole file.
78
+ </p>
79
+ </section>
80
+
81
+ <section className="mw-about__section">
82
+ <h3 className="mw-about__heading">FST prior</h3>
83
+ <p>
84
+ A <strong>finite-state transducer</strong> (FST) built from ~94K US place names runs before the neural
85
+ classifier. It pre-annotates known locality, region, and postcode spans in the input — places like
86
+ &quot;Springfield&quot; or &quot;Cook County&quot; that the FST can match exactly. These &quot;FST prior&quot;
87
+ hits feed the classifier as strong hints, improving locality and region recall on addresses where the neural
88
+ model alone might miss them. The FST is active whenever the selected release includes it.
89
+ </p>
90
+ </section>
91
+ </div>
92
+ )
93
+
94
+ if (!collapsible) return body
95
+
14
96
  return (
15
97
  <details className="mw-about">
16
98
  <summary className="mw-about__summary">
17
99
  <span className="mw-about__icon">ℹ️</span>
18
100
  About this geocoder
19
101
  </summary>
20
- <div className="mw-about__body">
21
- <section className="mw-about__section">
22
- <h3 className="mw-about__heading">Neural model</h3>
23
- <p>
24
- A ~29M-parameter BERT-style encoder classifies each token into one of 33 BIO labels covering 16 address
25
- components (street, house number, unit, locality, region, postcode, venue, country, intersection, etc.). It
26
- runs entirely in your browser via <strong>onnxruntime-web</strong> (WebGPU with WASM SIMD fallback). The
27
- model ships as a 37.6 MB int8-quantized ONNX bundle; fp32 weights are also available for higher accuracy.
28
- </p>
29
- </section>
30
-
31
- <section className="mw-about__section">
32
- <h3 className="mw-about__heading">Confidence colors</h3>
33
- <p>
34
- Every classified span carries a confidence score between 0 and 1. The colors in the results table give you a
35
- quick visual read:
36
- </p>
37
- <ul className="mw-about__legend">
38
- <li className="mw-about__item">
39
- <span className="mw-about__swatch mw-about__swatch--high" />
40
- <strong>Green</strong> (≥ 0.8) — high confidence. The model is nearly certain.
41
- </li>
42
- <li className="mw-about__item">
43
- <span className="mw-about__swatch mw-about__swatch--mid" />
44
- <strong>Amber</strong> (0.5–0.8) — moderate confidence. Worth verifying.
45
- </li>
46
- <li className="mw-about__item">
47
- <span className="mw-about__swatch mw-about__swatch--low" />
48
- <strong>Red</strong> (&lt; 0.5) — low confidence. The model is unsure — likely wrong.
49
- </li>
50
- </ul>
51
- <p>
52
- These are raw model scores. For calibrated probabilities, the library ships a per-locale isotonic calibrator
53
- (<code>calibration.json</code>) that maps the raw scores to well-calibrated probability estimates.
54
- </p>
55
- </section>
56
-
57
- <section className="mw-about__section">
58
- <h3 className="mw-about__heading">WOF resolver</h3>
59
- <p>
60
- After classification, the resolver queries a <strong>Who&apos;s On First</strong> (WOF) SQLite gazetteer to
61
- match the parsed components — locality, region, and postcode — against real-world places with coordinates.
62
- The cascade tries postcode first (most precise), then locality, then the raw input text. The result is a
63
- sorted list of candidate places with WOF IDs, placetypes, lat/lon, and bounding boxes.
64
- </p>
65
- </section>
66
-
67
- <section className="mw-about__section">
68
- <h3 className="mw-about__heading">Byte-range gazetteer</h3>
69
- <p>
70
- The gazetteer is a multi-gigabyte SQLite file served from a public bucket. It loads via{" "}
71
- <strong>sql.js-httpvfs</strong>, which range-requests only the pages a lookup touches, so your browser
72
- fetches a few hundred kilobytes per session instead of the whole file.
73
- </p>
74
- </section>
75
-
76
- <section className="mw-about__section">
77
- <h3 className="mw-about__heading">FST prior</h3>
78
- <p>
79
- A <strong>finite-state transducer</strong> (FST) built from ~94K US place names runs before the neural
80
- classifier. It pre-annotates known locality, region, and postcode spans in the input — places like
81
- &quot;Springfield&quot; or &quot;Cook County&quot; that the FST can match exactly. These &quot;FST
82
- prior&quot; hits feed the classifier as strong hints, improving locality and region recall on addresses
83
- where the neural model alone might miss them. The FST is active whenever the selected release includes it.
84
- </p>
85
- </section>
86
- </div>
102
+ {body}
87
103
  </details>
88
104
  )
89
105
  }
@@ -3,30 +3,18 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `ClientOnly` — a portable SSR boundary. The docs explorers wrapped Docusaurus's `<BrowserOnly>`;
7
- * this is the framework-agnostic equivalent so the components stay usable in any React app (Next,
8
- * Remix, Docusaurus). It renders `fallback` on the server and the first client paint, then swaps to
9
- * `children()` once mounted — keeping timers, clipboard, and dynamic imports off the server render.
10
- *
11
- * The mount flag comes from `useSyncExternalStore` with a constant snapshot pair — `false` on the
12
- * server, `true` in the browser — which is the store-shaped statement of exactly this boundary. The
13
- * earlier `useState(false)` + `useEffect(() => setMounted(true))` said the same thing with a
14
- * render-cascade the react(set-state-in-effect) rule rightly flags: the second render was the
15
- * mechanism, not an accident, and the store form gets the same second paint without a set-state.
6
+ * `ClientOnly` — a portable SSR boundary that renders `fallback` on the server and first client paint. It swaps to `children()` after mounting, keeping timers, clipboard and dynamic imports off the server render.
16
7
  */
17
8
 
18
9
  import { type ReactNode, useSyncExternalStore } from "react"
19
10
 
20
- /**
21
- * The boundary never changes after mount, so nothing ever notifies.
22
- */
23
11
  function subscribeNever(): () => void {
24
12
  return () => {}
25
13
  }
26
14
 
27
15
  export interface ClientOnlyProps {
28
16
  /**
29
- * Rendered once mounted in the browser. A thunk so its (browser-only) imports never run on the server.
17
+ * Rendered once mounted in the browser, as a thunk so its browser-only imports never run on the server.
30
18
  */
31
19
  children: () => ReactNode
32
20
  /**
@@ -13,7 +13,9 @@ import { useClipboard } from "#common/useClipboard"
13
13
 
14
14
  export interface CopyButtonProps {
15
15
  /**
16
- * The text to copy. If a thunk, it's evaluated at click time (so callers can copy the latest value).
16
+ * The text to copy.
17
+ *
18
+ * If a thunk, it's evaluated at click time (so callers can copy the latest value).
17
19
  */
18
20
  value: string | (() => string)
19
21
  /**
@@ -21,7 +21,8 @@ export interface LoadingIndicatorProps extends React.HTMLAttributes<HTMLDivEleme
21
21
  */
22
22
  mode?: LoadingMode
23
23
  /**
24
- * Optional label; in staged mode it renders above the step list.
24
+ * Optional label.
25
+ * In staged mode it renders above the step list.
25
26
  */
26
27
  label?: string
27
28
  /**
@@ -37,7 +38,8 @@ export interface LoadingIndicatorProps extends React.HTMLAttributes<HTMLDivEleme
37
38
  */
38
39
  steps?: string[]
39
40
  /**
40
- * 0-based index of the active step; earlier steps are complete, later ones pending. @default -1
41
+ * 0-based index of the active step.
42
+ * Earlier steps are complete, later ones pending. @default -1
41
43
  */
42
44
  activeStep?: number
43
45
  }
@@ -3,8 +3,8 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `PresetChips` — the "Try:" row of example buttons shared by both explorers. Presentational: it
7
- * renders a labelled list and calls `onPick` with the chosen preset's value.
6
+ * `PresetChips` is the "Try:" row of example buttons shared by both explorers. Presentational: it
7
+ * renders a labeled list and calls `onPick` with the chosen preset's value.
8
8
  */
9
9
 
10
10
  import type { ReactNode } from "react"
@@ -13,10 +13,10 @@ export interface Preset {
13
13
  label: string
14
14
  value: string
15
15
  /**
16
- * ISO country code for the placetype-pair country pin (#1278 phase 2's `{country}` override). Optional: a host that
17
- * wires the pair prior hands this to `selectPairIndexForText` when the input still equals this preset's text, so a
18
- * locale structural routing can't detect (NZ's 4-digit postcode isn't distinctive) still fires the dependent_locality
19
- * prior. Purely descriptive to `PresetChips` itself (which only emits `value`).
16
+ * ISO country code for the placetype-pair country pin.
17
+ *
18
+ * A host that wires the pair prior hands this to `selectPairIndexForText`
19
+ * when the input still equals this preset's text.
20
20
  */
21
21
  country?: string
22
22
  }
@@ -26,11 +26,12 @@ export interface PresetChipsProps {
26
26
  onPick: (value: string) => void
27
27
  disabled?: boolean
28
28
  /**
29
- * Leading label. @default "Try:"
29
+ * The label shown before the choices. @default "Try:"
30
30
  */
31
31
  caption?: string
32
32
  /**
33
- * Optional trailing content rendered inside the chip row after the presets (e.g. an inline permalink button).
33
+ * Optional trailing content rendered inside the chip row after the presets
34
+ * (e.g. an inline permalink button).
34
35
  */
35
36
  trailing?: ReactNode
36
37
  }
@@ -2,10 +2,10 @@
2
2
  * @copyright Sister Software
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
- * @file Confidence banding shared by every component that colours a span by confidence.
5
+ * @file Confidence banding shared by every component that colors a span by confidence.
6
6
  *
7
- * It is a presentation choice, not a model one: the parser emits a continuous confidence and these
8
- * bounds only decide which colour a span is drawn in, so they live here rather than anywhere the
7
+ * It is a presentation choice rather than a model one: the parser emits a continuous confidence and these
8
+ * bounds only decide which color a span is drawn in, so they live here rather than anywhere the
9
9
  * pipeline can see them.
10
10
  */
11
11
 
@@ -15,7 +15,8 @@
15
15
  export const HIGH_CONFIDENCE_MIN = 0.8
16
16
 
17
17
  /**
18
- * At or above this the span is drawn as medium-confidence; below it, low.
18
+ * At or above this the span is drawn as medium-confidence.
19
+ * Below it, low.
19
20
  */
20
21
  export const MID_CONFIDENCE_MIN = 0.5
21
22
 
@@ -40,8 +41,8 @@ export function confidenceTier(confidence: number): ConfidenceTier {
40
41
  }
41
42
 
42
43
  /**
43
- * {@link confidenceTier} for a possibly-absent confidence: an unmeasured span is drawn as `mid` rather than `low`, so an
44
- * older model that emits no confidence never reads as uniformly wrong.
44
+ * {@link confidenceTier} for a possibly-absent confidence: an unmeasured span is drawn as `mid`
45
+ * rather than `low`, so an older model that emits no confidence never reads as uniformly wrong.
45
46
  */
46
47
  export function confidenceTierOrMid(confidence?: number): ConfidenceTier {
47
48
  if (confidence == null) return "mid"
package/lib/common/cx.ts CHANGED
@@ -11,7 +11,9 @@
11
11
  export type ClassValue = string | number | false | null | undefined | Record<string, boolean>
12
12
 
13
13
  /**
14
- * Join truthy class tokens; object entries contribute their key when the value is truthy.
14
+ * Join truthy class tokens.
15
+ *
16
+ * Object entries contribute their key when the value is truthy.
15
17
  */
16
18
  export function cx(...values: ClassValue[]): string {
17
19
  const out: string[] = []
@@ -16,7 +16,7 @@ export interface WordToken {
16
16
  */
17
17
  text: string
18
18
  /**
19
- * Leading whitespace before this word.
19
+ * Whitespace before this word.
20
20
  */
21
21
  whitespace: string
22
22
  /**
@@ -36,7 +36,9 @@ export function tokenizeWords(input: string): WordToken[] {
36
36
  const words: WordToken[] = []
37
37
  let i = 0
38
38
 
39
- // `charAt` answers "" past the end, which no whitespace test matches, so both walks stop at the input's length.
39
+ // `charAt` answers "" past the end.
40
+ // It no whitespace test matches.
41
+ // Therefore, both walks stop at the input's length.
40
42
  while (i < input.length) {
41
43
  let ws = ""
42
44
 
@@ -68,8 +70,9 @@ export interface CharSpan {
68
70
  }
69
71
 
70
72
  /**
71
- * Per-word index of the most specific (shortest) span covering it, or `-1` when no span overlaps the word. A word is
72
- * covered when any part of it falls within the span.
73
+ * Per-word index of the most specific (shortest) span covering it, or `-1` when no span overlaps the word.
74
+ *
75
+ * A word is covered when any part of it falls within the span.
73
76
  */
74
77
  export function shortestSpanOwners(words: readonly CharSpan[], spans: readonly CharSpan[]): number[] {
75
78
  return words.map((word) => {
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `useBuildInfo()` — reads the deployment's own `build.json`, resolving a failed fetch to `null` because a dev server has no such file and the page must not show an error strip.
7
+ */
8
+
9
+ import { useEffect, useState } from "react"
10
+
11
+ export interface BuildInfoRecord {
12
+ /**
13
+ * The deployment's name: `mailwoman-earth`, `mailwoman-moon`, `mailwoman-mars`.
14
+ */
15
+ app: string
16
+ revision: string
17
+ /**
18
+ * The same revision, full length — what a commit URL wants.
19
+ */
20
+ commit?: string
21
+ /**
22
+ * ISO-8601 seconds, `Z` suffix.
23
+ */
24
+ buildTime: string
25
+ }
26
+
27
+ /**
28
+ * The deployment record, or `null` while it loads and whenever it cannot be read.
29
+ */
30
+ export function useBuildInfo(url = "/build.json"): BuildInfoRecord | null {
31
+ const [info, setInfo] = useState<BuildInfoRecord | null>(null)
32
+
33
+ useEffect(() => {
34
+ const controller = new AbortController()
35
+
36
+ void (async () => {
37
+ try {
38
+ const response = await fetch(url, { signal: controller.signal })
39
+
40
+ if (!response.ok) return
41
+
42
+ const record = (await response.json()) as BuildInfoRecord
43
+
44
+ if (!controller.signal.aborted && record?.revision) {
45
+ setInfo(record)
46
+ }
47
+ } catch {}
48
+ })()
49
+
50
+ return () => controller.abort()
51
+ }, [url])
52
+
53
+ return info
54
+ }