@mailwoman/react 9.3.0 → 9.4.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 (159) hide show
  1. package/fonts.css +59 -0
  2. package/lib/common/About.tsx +89 -0
  3. package/lib/common/KindBadge.tsx +2 -7
  4. package/lib/common/confidence-tiers.ts +50 -0
  5. package/lib/common/text-tokens.ts +88 -0
  6. package/lib/index.ts +30 -28
  7. package/lib/map/CompareToggle.tsx +2 -2
  8. package/lib/map/{GeocoderDemo.tsx → Geocoder.tsx} +40 -40
  9. package/lib/map/{DemoControls.tsx → GeocoderControls.tsx} +16 -17
  10. package/lib/map/{DemoMap.tsx → MapCanvas.tsx} +15 -15
  11. package/lib/map/PlaceMarker.tsx +1 -1
  12. package/lib/map/ResolvedPlaceLayers.tsx +3 -3
  13. package/lib/map/ResultCamera.tsx +1 -1
  14. package/lib/map/ResultPanel.tsx +2 -2
  15. package/lib/map/VersionPicker.tsx +3 -3
  16. package/lib/map/fake-runtime.ts +122 -0
  17. package/lib/map/index.ts +16 -16
  18. package/lib/map/map-debug.ts +51 -0
  19. package/lib/map/place-render.ts +3 -2
  20. package/lib/map/types.ts +41 -39
  21. package/lib/map/{useDemoGeocode.ts → useGeocode.ts} +8 -8
  22. package/lib/pipeline/CandidatePicker.tsx +1 -1
  23. package/lib/pipeline/ComponentTable.tsx +1 -2
  24. package/lib/pipeline/ConfidenceCell.tsx +7 -19
  25. package/lib/pipeline/FailureDiagnostic.tsx +56 -0
  26. package/lib/pipeline/ResolvedPlace.tsx +17 -4
  27. package/lib/pipeline/SpanHighlight.tsx +97 -0
  28. package/lib/pipeline/TimingPanel.tsx +65 -0
  29. package/lib/pipeline/TreeView.tsx +55 -0
  30. package/lib/pipeline/copy.ts +1 -1
  31. package/lib/pipeline/types.ts +2 -75
  32. package/lib/pipeline/useParsePipeline.ts +2 -1
  33. package/lib/poi/types.ts +2 -1
  34. package/lib/runtime/{useDemoRuntime.ts → useReleaseRuntime.ts} +22 -24
  35. package/out/common/About.d.ts +12 -0
  36. package/out/common/About.d.ts.map +1 -0
  37. package/out/common/About.js +5 -0
  38. package/out/common/About.js.map +1 -0
  39. package/out/common/KindBadge.d.ts +2 -9
  40. package/out/common/KindBadge.d.ts.map +1 -1
  41. package/out/common/KindBadge.js.map +1 -1
  42. package/out/common/confidence-tiers.d.ts +36 -0
  43. package/out/common/confidence-tiers.d.ts.map +1 -0
  44. package/out/common/confidence-tiers.js +42 -0
  45. package/out/common/confidence-tiers.js.map +1 -0
  46. package/out/common/text-tokens.d.ts +46 -0
  47. package/out/common/text-tokens.d.ts.map +1 -0
  48. package/out/common/text-tokens.js +52 -0
  49. package/out/common/text-tokens.js.map +1 -0
  50. package/out/index.d.ts +14 -5
  51. package/out/index.d.ts.map +1 -1
  52. package/out/index.js +8 -2
  53. package/out/index.js.map +1 -1
  54. package/out/map/CompareToggle.d.ts +2 -2
  55. package/out/map/CompareToggle.d.ts.map +1 -1
  56. package/out/map/{GeocoderDemo.d.ts → Geocoder.d.ts} +17 -17
  57. package/out/map/Geocoder.d.ts.map +1 -0
  58. package/out/map/{GeocoderDemo.js → Geocoder.js} +25 -25
  59. package/out/map/Geocoder.js.map +1 -0
  60. package/out/map/{DemoControls.d.ts → GeocoderControls.d.ts} +16 -17
  61. package/out/map/GeocoderControls.d.ts.map +1 -0
  62. package/out/map/{DemoControls.js → GeocoderControls.js} +2 -2
  63. package/out/map/GeocoderControls.js.map +1 -0
  64. package/out/map/{DemoMap.d.ts → MapCanvas.d.ts} +15 -15
  65. package/out/map/MapCanvas.d.ts.map +1 -0
  66. package/out/map/{DemoMap.js → MapCanvas.js} +2 -2
  67. package/out/map/MapCanvas.js.map +1 -0
  68. package/out/map/PlaceMarker.d.ts +1 -1
  69. package/out/map/ResolvedPlaceLayers.d.ts +3 -3
  70. package/out/map/ResultCamera.d.ts +1 -1
  71. package/out/map/ResultCamera.js +1 -1
  72. package/out/map/ResultPanel.d.ts +2 -2
  73. package/out/map/ResultPanel.d.ts.map +1 -1
  74. package/out/map/ResultPanel.js.map +1 -1
  75. package/out/map/VersionPicker.d.ts +3 -3
  76. package/out/map/VersionPicker.d.ts.map +1 -1
  77. package/out/map/fake-runtime.d.ts +38 -0
  78. package/out/map/fake-runtime.d.ts.map +1 -0
  79. package/out/map/fake-runtime.js +107 -0
  80. package/out/map/fake-runtime.js.map +1 -0
  81. package/out/map/index.d.ts +10 -10
  82. package/out/map/index.d.ts.map +1 -1
  83. package/out/map/index.js +6 -6
  84. package/out/map/index.js.map +1 -1
  85. package/out/map/map-debug.d.ts +31 -0
  86. package/out/map/map-debug.d.ts.map +1 -0
  87. package/out/map/map-debug.js +31 -0
  88. package/out/map/map-debug.js.map +1 -0
  89. package/out/map/place-render.d.ts +2 -2
  90. package/out/map/place-render.d.ts.map +1 -1
  91. package/out/map/place-render.js +1 -1
  92. package/out/map/place-render.js.map +1 -1
  93. package/out/map/types.d.ts +41 -39
  94. package/out/map/types.d.ts.map +1 -1
  95. package/out/map/types.js +5 -5
  96. package/out/map/{useDemoGeocode.d.ts → useGeocode.d.ts} +9 -9
  97. package/out/map/useGeocode.d.ts.map +1 -0
  98. package/out/map/{useDemoGeocode.js → useGeocode.js} +4 -4
  99. package/out/map/useGeocode.js.map +1 -0
  100. package/out/pipeline/CandidatePicker.d.ts +1 -1
  101. package/out/pipeline/CandidatePicker.d.ts.map +1 -1
  102. package/out/pipeline/CandidatePicker.js.map +1 -1
  103. package/out/pipeline/ComponentTable.d.ts +1 -1
  104. package/out/pipeline/ComponentTable.d.ts.map +1 -1
  105. package/out/pipeline/ComponentTable.js.map +1 -1
  106. package/out/pipeline/ConfidenceCell.d.ts +2 -2
  107. package/out/pipeline/ConfidenceCell.d.ts.map +1 -1
  108. package/out/pipeline/ConfidenceCell.js +4 -16
  109. package/out/pipeline/ConfidenceCell.js.map +1 -1
  110. package/out/pipeline/FailureDiagnostic.d.ts +15 -0
  111. package/out/pipeline/FailureDiagnostic.d.ts.map +1 -0
  112. package/out/pipeline/FailureDiagnostic.js +21 -0
  113. package/out/pipeline/FailureDiagnostic.js.map +1 -0
  114. package/out/pipeline/ResolvedPlace.d.ts +1 -1
  115. package/out/pipeline/ResolvedPlace.d.ts.map +1 -1
  116. package/out/pipeline/ResolvedPlace.js +4 -10
  117. package/out/pipeline/ResolvedPlace.js.map +1 -1
  118. package/out/pipeline/SpanHighlight.d.ts +23 -0
  119. package/out/pipeline/SpanHighlight.d.ts.map +1 -0
  120. package/out/pipeline/SpanHighlight.js +31 -0
  121. package/out/pipeline/SpanHighlight.js.map +1 -0
  122. package/out/pipeline/TimingPanel.d.ts +16 -0
  123. package/out/pipeline/TimingPanel.d.ts.map +1 -0
  124. package/out/pipeline/TimingPanel.js +20 -0
  125. package/out/pipeline/TimingPanel.js.map +1 -0
  126. package/out/pipeline/TreeView.d.ts +17 -0
  127. package/out/pipeline/TreeView.d.ts.map +1 -0
  128. package/out/pipeline/TreeView.js +15 -0
  129. package/out/pipeline/TreeView.js.map +1 -0
  130. package/out/pipeline/copy.d.ts +1 -1
  131. package/out/pipeline/copy.d.ts.map +1 -1
  132. package/out/pipeline/types.d.ts +2 -68
  133. package/out/pipeline/types.d.ts.map +1 -1
  134. package/out/pipeline/types.js +1 -1
  135. package/out/pipeline/useParsePipeline.d.ts +2 -1
  136. package/out/pipeline/useParsePipeline.d.ts.map +1 -1
  137. package/out/pipeline/useParsePipeline.js.map +1 -1
  138. package/out/poi/types.d.ts +2 -1
  139. package/out/poi/types.d.ts.map +1 -1
  140. package/out/runtime/{useDemoRuntime.d.ts → useReleaseRuntime.d.ts} +18 -20
  141. package/out/runtime/useReleaseRuntime.d.ts.map +1 -0
  142. package/out/runtime/{useDemoRuntime.js → useReleaseRuntime.js} +4 -4
  143. package/out/runtime/useReleaseRuntime.js.map +1 -0
  144. package/package.json +139 -29
  145. package/styleframe.config.ts +321 -0
  146. package/styles.css +1074 -664
  147. package/tokens/index.css +115 -0
  148. package/tokens/tokens.json +521 -0
  149. package/tokens/tokens.resolver.json +185 -0
  150. package/out/map/DemoControls.d.ts.map +0 -1
  151. package/out/map/DemoControls.js.map +0 -1
  152. package/out/map/DemoMap.d.ts.map +0 -1
  153. package/out/map/DemoMap.js.map +0 -1
  154. package/out/map/GeocoderDemo.d.ts.map +0 -1
  155. package/out/map/GeocoderDemo.js.map +0 -1
  156. package/out/map/useDemoGeocode.d.ts.map +0 -1
  157. package/out/map/useDemoGeocode.js.map +0 -1
  158. package/out/runtime/useDemoRuntime.d.ts.map +0 -1
  159. package/out/runtime/useDemoRuntime.js.map +0 -1
package/lib/map/types.ts CHANGED
@@ -3,25 +3,26 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * Types for the geocoder-demo map surface. Mirrors the pipeline boundary: the package owns the UI state
7
- * machine + the declarative map, while the host injects a {@link DemoRuntime} that owns ONNX / httpvfs
8
- * / R2 and the composed map style. {@link DemoRuntime} EXTENDS {@link PipelineRuntime} so the shared
6
+ * Types for the geocoder map surface. Mirrors the pipeline boundary: the package owns the UI state
7
+ * machine + the declarative map, while the host injects a {@link GeocoderRuntime} that owns ONNX / httpvfs
8
+ * / R2 and the composed map style. {@link GeocoderRuntime} EXTENDS {@link PipelineRuntime} so the shared
9
9
  * `runParse` / `parseStageLabels` / `loading` contract is reused, and adds the map-specific surface
10
10
  * (style, overlays, initial center, viewport bias, backend/version selection). Phase 4 adds the
11
- * `resolveMapPlace` enricher, the {@link DemoPanels} injection bag, and the {@link DemoCompareContext}.
11
+ * `resolveMapPlace` enricher, the {@link GeocoderPanels} injection bag, and the {@link CompareContext}.
12
12
  *
13
13
  * The map-spec types are imported type-only from `react-map-gl/maplibre`; nothing here loads maplibre at
14
- * runtime, so this module stays node-safe (its concrete-value CONSUMERS — `DemoMap`, `GeocoderDemo` —
14
+ * runtime, so this module stays node-safe (its concrete-value CONSUMERS — `MapCanvas`, `Geocoder` —
15
15
  * are the ones behind the `@mailwoman/react/map` subpath).
16
16
  */
17
17
 
18
+ import type { ParseResult, ResolvedPlaceView } from "@mailwoman/core/pipeline/client-result"
18
19
  import type { ReactNode } from "react"
19
20
  import type { LayerSpecification, SourceSpecification } from "react-map-gl/maplibre"
20
21
 
21
22
  import type { LngLat, ResolvedMapPlace } from "#map/place-render"
22
- import type { ParseResult, PipelineRuntime, ResolvedPlaceView } from "#pipeline/types"
23
+ import type { PipelineRuntime } from "#pipeline/types"
23
24
 
24
- import type { DemoMapStyle } from "./DemoMap.tsx"
25
+ import type { MapCanvasStyle } from "./MapCanvas.tsx"
25
26
 
26
27
  /**
27
28
  * `[longitude, latitude]`.
@@ -92,7 +93,7 @@ export interface Suggestion {
92
93
  /**
93
94
  * A selectable model bundle (version tag + a display label the picker shows).
94
95
  */
95
- export interface DemoVersionOption {
96
+ export interface VersionOption {
96
97
  /**
97
98
  * The version tag (e.g. a git tag or model-card version).
98
99
  */
@@ -104,23 +105,23 @@ export interface DemoVersionOption {
104
105
  }
105
106
 
106
107
  /**
107
- * Which neural backend the demo is currently running on.
108
+ * Which neural backend the geocoder is currently running on.
108
109
  */
109
- export type DemoBackend = "webgpu" | "wasm"
110
+ export type InferenceBackend = "webgpu" | "wasm"
110
111
 
111
112
  /**
112
- * The injected demo runtime. Extends {@link PipelineRuntime} (shared `runParse` / `parseStageLabels` / `loading` /
113
- * `ready`) with the map + version/backend surface the demo needs. The host composes `mapStyle` (via cartographer's
113
+ * The injected geocoder runtime. Extends {@link PipelineRuntime} (shared `runParse` / `parseStageLabels` / `loading` /
114
+ * `ready`) with the map + version/backend surface the geocoder needs. The host composes `mapStyle` (via cartographer's
114
115
  * `StyleSpecificationComposer` + the tile-worker TileJSON), supplies the overlay specs, the initial center (from
115
116
  * geolocation), the FST autocomplete, and the calibrator — nothing in the package imports `@mailwoman/cartographer`,
116
- * `@mailwoman/neural-web`, httpvfs, or Docusaurus.
117
+ * `@mailwoman/neural`'s web loader, httpvfs, or Docusaurus.
117
118
  */
118
- export interface DemoRuntime extends PipelineRuntime {
119
+ export interface GeocoderRuntime extends PipelineRuntime {
119
120
  // ── Map ────────────────────────────────────────────────────────────────
120
121
  /**
121
122
  * The composed basemap style (URL or `StyleSpecification`).
122
123
  */
123
- mapStyle: DemoMapStyle
124
+ mapStyle: MapCanvasStyle
124
125
  /**
125
126
  * Host-supplied overlays (coverage, race-dots, …).
126
127
  */
@@ -136,7 +137,7 @@ export interface DemoRuntime extends PipelineRuntime {
136
137
 
137
138
  // ── Parse extras layered over PipelineRuntime.runParse ──────────────────
138
139
  /**
139
- * A bias-aware parse. The map demo feeds the current viewport center as a soft prior; when absent the host falls back
140
+ * A bias-aware parse. The geocoder feeds the current viewport center as a soft prior; when absent the host falls back
140
141
  * to the base {@link PipelineRuntime.runParse}. Kept separate so the shared `runParse` contract is unchanged.
141
142
  */
142
143
  runParseWithBias?: (
@@ -154,9 +155,9 @@ export interface DemoRuntime extends PipelineRuntime {
154
155
  calibrator?: (raw: number) => number | null
155
156
  /**
156
157
  * Enrich the selected candidate into the richer {@link ResolvedMapPlace} the declarative map render consumes (bbox,
157
- * street tier + uncertainty, a pre-fetched crisp polygon) — the fields that live on the demo's `ResolvedHit` but not
158
+ * street tier + uncertainty, a pre-fetched crisp polygon) — the fields that live on the host's `ResolvedHit` but not
158
159
  * on the shared {@link ResolvedPlaceView}. The host owns this because those extras (and the async polygon fetch in the
159
- * real demo) are host/gazetteer concerns; the package keeps {@link ParseResult} unpolluted. Absent → the candidate
160
+ * real runtime) are host/gazetteer concerns; the package keeps {@link ParseResult} unpolluted. Absent → the candidate
160
161
  * renders as a bare point (marker + a mid-zoom fly-to). Returning `null` also renders nothing.
161
162
  */
162
163
  resolveMapPlace?: (candidate: ResolvedPlaceView, result: ParseResult) => ResolvedMapPlace | null
@@ -165,7 +166,7 @@ export interface DemoRuntime extends PipelineRuntime {
165
166
  /**
166
167
  * The selectable model bundles the version picker offers.
167
168
  */
168
- availableVersions?: DemoVersionOption[]
169
+ availableVersions?: VersionOption[]
169
170
  /**
170
171
  * The currently-selected model version.
171
172
  */
@@ -189,9 +190,10 @@ export interface DemoRuntime extends PipelineRuntime {
189
190
  }
190
191
 
191
192
  /**
192
- * The compare-mode state a {@link DemoPanels.compare} render-prop receives (the second parse itself stays host-side).
193
+ * The compare-mode state a {@link GeocoderPanels.compare} render-prop receives (the second parse itself stays
194
+ * host-side).
193
195
  */
194
- export interface DemoCompareContext {
196
+ export interface CompareContext {
195
197
  /**
196
198
  * The current primary parse result, or `null` before the first submit.
197
199
  */
@@ -207,12 +209,12 @@ export interface DemoCompareContext {
207
209
  }
208
210
 
209
211
  /**
210
- * The state a {@link DemoPanels.result} render-prop receives, so a host can render its OWN result block (the docs
212
+ * The state a {@link GeocoderPanels.result} render-prop receives, so a host can render its OWN result block (a
211
213
  * `<ResultPanel>` with its span-highlight / timing / hierarchy / precision detail) in place of the package's default
212
214
  * {@link ResultPanel}. Everything the default panel needs is passed through; the candidate-selection state stays owned
213
- * by the package (`useDemoGeocode`).
215
+ * by the package (`useGeocode`).
214
216
  */
215
- export interface DemoResultContext {
217
+ export interface ResultContext {
216
218
  /**
217
219
  * The current parse+resolve result.
218
220
  */
@@ -232,14 +234,14 @@ export interface DemoResultContext {
232
234
  }
233
235
 
234
236
  /**
235
- * Host-injected panels for {@link GeocoderDemo}, the map analogue of `PipelinePanels`. Each is an already-rendered
236
- * `ReactNode` (or a thunk of the parse result / compare state) so the package needs neither the heavy docs visualizers
237
- * (ModelVisualizer, VersionCompare, AboutDemo, PermalinkButton) nor their data types. Every field is optional — the
238
- * fake-runtime Storybook stories pass none and still render the whole demo.
237
+ * Host-injected panels for {@link Geocoder}, the map analogue of `PipelinePanels`. Each is an already-rendered
238
+ * `ReactNode` (or a thunk of the parse result / compare state) so the package needs neither the heavy host visualizers
239
+ * (ModelVisualizer, VersionCompare, About, PermalinkButton) nor their data types. Every field is optional — the
240
+ * fake-runtime Storybook stories pass none and still render the whole geocoder.
239
241
  */
240
- export interface DemoPanels {
242
+ export interface GeocoderPanels {
241
243
  /**
242
- * Rendered at the top of the control panel (e.g. the docs "About this demo").
244
+ * Rendered at the top of the control panel (e.g. the host's "About this geocoder").
243
245
  */
244
246
  header?: ReactNode
245
247
  /**
@@ -251,9 +253,9 @@ export interface DemoPanels {
251
253
  */
252
254
  footer?: ReactNode
253
255
  /**
254
- * A device-location / proximity-bias control, rendered between the query form and the autocomplete list (the demo's
255
- * "📍 Use my location" row). Host-owned so the geolocation permission + the bias it feeds into the host's
256
- * {@link DemoRuntime.runParseWithBias} stay a host concern.
256
+ * A device-location / proximity-bias control, rendered between the query form and the autocomplete list (the "📍 Use
257
+ * my location" row). Host-owned so the geolocation permission + the bias it feeds into the host's
258
+ * {@link GeocoderRuntime.runParseWithBias} stay a host concern.
257
259
  */
258
260
  bias?: ReactNode
259
261
  /**
@@ -261,17 +263,17 @@ export interface DemoPanels {
261
263
  */
262
264
  extras?: (result: ParseResult) => ReactNode
263
265
  /**
264
- * Rendered just above the result block (present or empty). The demo's opt-in display toggles live here — calibrated
266
+ * Rendered just above the result block (present or empty). The opt-in display toggles live here — calibrated
265
267
  * confidence + dev-mode — because the host owns both the toggle state AND the {@link result} / {@link debugDrawer}
266
268
  * renderers those toggles drive.
267
269
  */
268
270
  aboveResult?: (context: { result: ParseResult | null }) => ReactNode
269
271
  /**
270
272
  * Replace the package's default {@link ResultPanel} entirely. When provided, the host renders its own result block
271
- * (the docs `<ResultPanel>` — span highlight, timing, hierarchy, precision detail, calibrated confidences) from the
272
- * {@link DemoResultContext}. Absent → the built-in panel renders.
273
+ * (span highlight, timing, hierarchy, precision detail, calibrated confidences) from the {@link ResultContext}. Absent
274
+ * → the built-in panel renders.
273
275
  */
274
- result?: (context: DemoResultContext) => ReactNode
276
+ result?: (context: ResultContext) => ReactNode
275
277
  /**
276
278
  * Rendered in place of the resolved-place panel when nothing resolved (host's FailureDiagnostic). Ignored when
277
279
  * {@link result} is set.
@@ -280,7 +282,7 @@ export interface DemoPanels {
280
282
  /**
281
283
  * The version-compare view — the host renders its own diff from the compare state it owns.
282
284
  */
283
- compare?: (context: DemoCompareContext) => ReactNode
285
+ compare?: (context: CompareContext) => ReactNode
284
286
  /**
285
287
  * The model-visualizer / debug drawer, mounted beside the map (host's ModelVisualizer). A render-prop so the host can
286
288
  * trace the CURRENT result (its input) — the package passes the live parse result; the host decides on its own
@@ -288,7 +290,7 @@ export interface DemoPanels {
288
290
  */
289
291
  debugDrawer?: (context: { result: ParseResult | null }) => ReactNode
290
292
  /**
291
- * Extra map controls mounted as `<DemoMap>` children (host's DebugControl / LayerToggle via `useControl`).
293
+ * Extra map controls mounted as `<MapCanvas>` children (host's DebugControl / LayerToggle via `useControl`).
292
294
  */
293
295
  mapControls?: ReactNode
294
296
  /**
@@ -3,9 +3,9 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `useDemoGeocode` — the parse+resolve state machine for the geocoder map demo. It REUSES the pipeline's
6
+ * `useGeocode` — the parse+resolve state machine for the geocoder map. It REUSES the pipeline's
7
7
  * `useParsePipeline` (text / busy / stage / result / candidate selection) and layers on the two map-only
8
- * concerns the demo adds over the base explorer:
8
+ * concerns the geocoder adds over the base explorer:
9
9
  *
10
10
  * 1. Viewport bias — when the runtime exposes `runParseWithBias`, the current map center (read through
11
11
  * an injected `getBias`, itself reading the `MapRef`) rides along as a soft proximity prior. The
@@ -21,15 +21,15 @@
21
21
  import { useMemo } from "react"
22
22
 
23
23
  import type { ResolvedMapPlace } from "#map/place-render"
24
- import type { DemoRuntime, MapBias } from "#map/types"
24
+ import type { GeocoderRuntime, MapBias } from "#map/types"
25
25
  import type { PipelineRuntime } from "#pipeline/types"
26
26
  import { useParsePipeline, type UseParsePipeline } from "#pipeline/useParsePipeline"
27
27
 
28
- export interface UseDemoGeocodeOptions {
28
+ export interface UseGeocodeOptions {
29
29
  /**
30
- * The injected demo runtime (extends `PipelineRuntime` with the map + bias surface).
30
+ * The injected geocoder runtime (extends `PipelineRuntime` with the map + bias surface).
31
31
  */
32
- runtime: DemoRuntime
32
+ runtime: GeocoderRuntime
33
33
  /**
34
34
  * Address to pre-fill.
35
35
  */
@@ -40,14 +40,14 @@ export interface UseDemoGeocodeOptions {
40
40
  getBias?: () => MapBias | null
41
41
  }
42
42
 
43
- export interface UseDemoGeocode extends UseParsePipeline {
43
+ export interface UseGeocode extends UseParsePipeline {
44
44
  /**
45
45
  * The selected candidate enriched into the map-render shape (bbox / tier / polygon), or `null`.
46
46
  */
47
47
  mapPlace: ResolvedMapPlace | null
48
48
  }
49
49
 
50
- export function useDemoGeocode({ runtime, defaultText, getBias }: UseDemoGeocodeOptions): UseDemoGeocode {
50
+ export function useGeocode({ runtime, defaultText, getBias }: UseGeocodeOptions): UseGeocode {
51
51
  // Bind the viewport bias into a derived `runParse` so `useParsePipeline` is reused unchanged. When the runtime has no
52
52
  // bias-aware parse, pass it straight through.
53
53
  const geoRuntime = useMemo<PipelineRuntime>(() => {
@@ -7,10 +7,10 @@
7
7
  * Presentational; the selected index is owned by the caller (the pipeline hook).
8
8
  */
9
9
 
10
+ import type { ResolvedPlaceView } from "@mailwoman/core/pipeline/client-result"
10
11
  import type { ReactNode } from "react"
11
12
 
12
13
  import { cx } from "#common/cx"
13
- import type { ResolvedPlaceView } from "#pipeline/types"
14
14
 
15
15
  export interface CandidatePickerProps {
16
16
  candidates: ResolvedPlaceView[]
@@ -6,10 +6,9 @@
6
6
  * `ComponentTable` — the tag / value / confidence table of decoded components. Presentational.
7
7
  */
8
8
 
9
+ import type { ParsedComponent } from "@mailwoman/core/pipeline/client-result"
9
10
  import type { ReactNode } from "react"
10
11
 
11
- import type { ParsedComponent } from "#pipeline/types"
12
-
13
12
  import { ConfidenceCell } from "./ConfidenceCell.tsx"
14
13
 
15
14
  export interface ComponentTableProps {
@@ -3,28 +3,13 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `ConfidenceCell` — a compact confidence bar + value for the component table. Tiers the fill colour
7
- * at 0.8 / 0.5. Presentational.
6
+ * `ConfidenceCell` — a compact confidence bar + value for the component table. The fill colour follows the shared
7
+ * confidence tiers. Presentational.
8
8
  */
9
9
 
10
+ import { confidenceTierOrMid } from "#common/confidence-tiers"
10
11
  import { cx } from "#common/cx"
11
12
 
12
- /**
13
- * At or above this the cell is drawn as high-confidence. Presentation only — see docs/src/shared/confidence-tiers.ts.
14
- */
15
- const HIGH_CONFIDENCE_MIN = 0.8
16
-
17
- /**
18
- * At or above this the cell is drawn as medium-confidence; below it, low.
19
- */
20
- const MID_CONFIDENCE_MIN = 0.5
21
-
22
- function tier(confidence?: number): "high" | "mid" | "low" {
23
- if (confidence == null) return "mid"
24
-
25
- return confidence >= HIGH_CONFIDENCE_MIN ? "high" : confidence >= MID_CONFIDENCE_MIN ? "mid" : "low"
26
- }
27
-
28
13
  export interface ConfidenceCellProps {
29
14
  confidence?: number
30
15
  }
@@ -36,7 +21,10 @@ export function ConfidenceCell({ confidence }: ConfidenceCellProps) {
36
21
 
37
22
  return (
38
23
  <div className="mw-conf">
39
- <div className={cx("mw-conf__bar", `mw-conf__bar--${tier(confidence)}`)} style={{ width: `${pct}%` }} />
24
+ <div
25
+ className={cx("mw-conf__bar", `mw-conf__bar--${confidenceTierOrMid(confidence)}`)}
26
+ style={{ width: `${pct}%` }}
27
+ />
40
28
  <span className="mw-conf__value">{confidence.toFixed(2)}</span>
41
29
  </div>
42
30
  )
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `FailureDiagnostic` — what to say when nothing resolved. It reads the parsed components and names the missing
7
+ * piece a person can add (a city, a state), so the answer is a next step rather than "no hit".
8
+ */
9
+
10
+ import type { ParsedComponent } from "@mailwoman/core/pipeline/client-result"
11
+ import type { ReactNode } from "react"
12
+
13
+ export interface FailureDiagnosticProps {
14
+ nodes: ParsedComponent[]
15
+ }
16
+
17
+ export function FailureDiagnostic({ nodes }: FailureDiagnosticProps): ReactNode {
18
+ const hasLocality = nodes.some((n) => n.tag === "locality")
19
+ const hasPostcode = nodes.some((n) => n.tag === "postcode")
20
+ const hasRegion = nodes.some((n) => n.tag === "region")
21
+
22
+ const hints: string[] = []
23
+
24
+ if (!hasLocality && !hasPostcode) {
25
+ hints.push(
26
+ "The parser found no city or postcode in this input. Try adding one — e.g. append ', Chicago, IL 60613'."
27
+ )
28
+ }
29
+
30
+ if (hasPostcode && !hasLocality) {
31
+ hints.push(
32
+ "Only a postcode was extracted. WOF ships placeholder coordinates (0, 0) for about 22% of US postcodes, and the cascade drops those."
33
+ )
34
+ }
35
+
36
+ if (hasLocality && !hasRegion) {
37
+ hints.push(
38
+ "No state in the parse. US localities share names across states (Springfield, Portland, …); add a state to disambiguate."
39
+ )
40
+ }
41
+
42
+ if (!hints.length) {
43
+ hints.push("The parsed components look reasonable, but the gazetteer does not index this entry.")
44
+ }
45
+
46
+ return (
47
+ <div className="mw-failure">
48
+ <h2>No gazetteer hit</h2>
49
+ <ul>
50
+ {hints.map((hint, i) => (
51
+ <li key={i}>{hint}</li>
52
+ ))}
53
+ </ul>
54
+ </div>
55
+ )
56
+ }
@@ -7,10 +7,9 @@
7
7
  * optional dual-role note. Presentational.
8
8
  */
9
9
 
10
+ import type { DualRoleView, ResolvedPlaceView } from "@mailwoman/core/pipeline/client-result"
10
11
  import { Fragment, type ReactNode } from "react"
11
12
 
12
- import type { DualRoleView, ResolvedPlaceView } from "#pipeline/types"
13
-
14
13
  export interface ResolvedPlaceProps {
15
14
  place: ResolvedPlaceView
16
15
  dualRoles?: DualRoleView[]
@@ -25,8 +24,22 @@ export function ResolvedPlace({ place, dualRoles }: ResolvedPlaceProps): ReactNo
25
24
  <dd>{place.name}</dd>
26
25
  <dt>placetype</dt>
27
26
  <dd>{place.placetype}</dd>
28
- <dt>WOF id</dt>
29
- <dd>{place.id}</dd>
27
+ {place.tier ? (
28
+ // A street-tier answer is not a gazetteer place: it carries a precision instead of an id.
29
+ <>
30
+ <dt>precision</dt>
31
+ <dd>
32
+ {place.tier === "address_point"
33
+ ? "📍 exact address point (≤10 m)"
34
+ : `≈ interpolated · ±${place.uncertaintyM ?? "?"} m`}
35
+ </dd>
36
+ </>
37
+ ) : (
38
+ <>
39
+ <dt>WOF id</dt>
40
+ <dd>{place.id}</dd>
41
+ </>
42
+ )}
30
43
  <dt>coords</dt>
31
44
  <dd>
32
45
  {place.lat.toFixed(4)}, {place.lon.toFixed(4)}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `SpanHighlight` — a displaCy-style span ribbon over the raw input: each tagged span is a column with the text on
7
+ * top, tinted by its confidence tier, and the tag beneath. Gaps (delimiters, unparsed characters) fall through as
8
+ * plain text, so a dropped span is visible as a literal break in the colour.
9
+ */
10
+
11
+ import type { ParsedComponent } from "@mailwoman/core/pipeline/client-result"
12
+ import type { ReactNode } from "react"
13
+
14
+ import { confidenceTierOrMid } from "#common/confidence-tiers"
15
+ import { shortestSpanOwners } from "#common/text-tokens"
16
+
17
+ export interface SpanHighlightProps {
18
+ /**
19
+ * The raw text handed to the parser — `nodes[].start/end` index into this.
20
+ */
21
+ input: string
22
+ /**
23
+ * Flattened parse nodes; only those with numeric `start`/`end` are rendered.
24
+ */
25
+ nodes: ParsedComponent[]
26
+ }
27
+
28
+ interface Segment {
29
+ text: string
30
+ node: ParsedComponent | null
31
+ }
32
+
33
+ type Span = ParsedComponent & { start: number; end: number }
34
+
35
+ export function SpanHighlight({ input, nodes }: SpanHighlightProps): ReactNode {
36
+ if (!input) return null
37
+
38
+ // Keep only well-formed spans that index into the input.
39
+ const spans = nodes.filter(
40
+ (n): n is Span =>
41
+ typeof n.start === "number" &&
42
+ typeof n.end === "number" &&
43
+ n.start >= 0 &&
44
+ n.end > n.start &&
45
+ n.end <= input.length
46
+ )
47
+
48
+ if (!spans.length) return null
49
+
50
+ // Per-character owner: the most specific (shortest) span covering it, the same rule the word-level panels apply
51
+ // per word. The leaf always wins whatever nesting the tree hands over, so every character renders once.
52
+ const characters = Array.from({ length: input.length }, (_, i) => ({ start: i, end: i + 1 }))
53
+ const owner = shortestSpanOwners(characters, spans)
54
+
55
+ // Coalesce runs of the same owner into segments.
56
+ const segments: Segment[] = []
57
+ let from = 0
58
+
59
+ for (let i = 1; i <= input.length; i++) {
60
+ if (i === input.length || owner[i] !== owner[from]) {
61
+ const ownerIndex = owner[from] ?? -1
62
+
63
+ segments.push({ text: input.slice(from, i), node: ownerIndex === -1 ? null : (spans[ownerIndex] ?? null) })
64
+
65
+ from = i
66
+ }
67
+ }
68
+
69
+ return (
70
+ <div className="mw-spans">
71
+ <div className="mw-spans__legend">
72
+ <span>confidence</span>
73
+ <span className="mw-spans__swatch mw-spans__swatch--low" /> low
74
+ <span className="mw-spans__swatch mw-spans__swatch--mid" /> mid
75
+ <span className="mw-spans__swatch mw-spans__swatch--high" /> high
76
+ </div>
77
+ <div className="mw-spans__track">
78
+ {segments.map((seg, i) =>
79
+ seg.node ? (
80
+ <span
81
+ key={i}
82
+ className={`mw-spans__seg mw-spans__seg--${confidenceTierOrMid(seg.node.confidence)}`}
83
+ title={`${seg.node.tag}${seg.node.confidence != null ? ` · ${seg.node.confidence.toFixed(2)}` : ""}`}
84
+ >
85
+ <span className="mw-spans__text">{seg.text}</span>
86
+ <span className="mw-spans__tag">{seg.node.tag}</span>
87
+ </span>
88
+ ) : (
89
+ <span key={i} className="mw-spans__gap">
90
+ {seg.text}
91
+ </span>
92
+ )
93
+ )}
94
+ </div>
95
+ </div>
96
+ )
97
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `TimingPanel` — the per-stage timing breakdown: a stacked bar sized by each stage's wall-clock, plus a legend.
7
+ * Stage colours are distinct hues, never the confidence red/amber/green, because they encode the pipeline stage and
8
+ * not quality.
9
+ */
10
+
11
+ import type { StageTiming } from "@mailwoman/core/pipeline/client-result"
12
+ import type { ReactNode } from "react"
13
+
14
+ export interface TimingPanelProps {
15
+ timing: StageTiming
16
+ }
17
+
18
+ const STAGES: ReadonlyArray<{ key: keyof StageTiming; label: string }> = [
19
+ { key: "shape", label: "shape + kind" },
20
+ { key: "classify", label: "classify" },
21
+ { key: "resolve", label: "resolve" },
22
+ ]
23
+
24
+ function formatMilliseconds(ms: number): string {
25
+ return ms >= 100 ? `${Math.round(ms)} ms` : `${ms.toFixed(1)} ms`
26
+ }
27
+
28
+ export function TimingPanel({ timing }: TimingPanelProps): ReactNode {
29
+ const present = STAGES.flatMap((stage) => {
30
+ const ms = timing[stage.key]
31
+
32
+ return typeof ms === "number" ? [{ ...stage, ms }] : []
33
+ })
34
+
35
+ const total = present.reduce((sum, stage) => sum + stage.ms, 0)
36
+
37
+ if (total <= 0) return null
38
+
39
+ return (
40
+ <div className="mw-timing">
41
+ <div className="mw-timing__heading">
42
+ Timing <span className="mw-timing__total">{formatMilliseconds(total)} total</span>
43
+ </div>
44
+ <div className="mw-timing__bar">
45
+ {present.map((stage) => (
46
+ // The rendered width is floored so a sub-millisecond stage stays visible as a sliver.
47
+ <div
48
+ key={stage.key}
49
+ className={`mw-timing__seg mw-timing__seg--${stage.key}`}
50
+ style={{ width: `${Math.max((stage.ms / total) * 100, 1.5)}%` }}
51
+ title={`${stage.label}: ${formatMilliseconds(stage.ms)}`}
52
+ />
53
+ ))}
54
+ </div>
55
+ <div className="mw-timing__legend">
56
+ {present.map((stage) => (
57
+ <span key={stage.key} className="mw-timing__item">
58
+ <span className={`mw-timing__swatch mw-timing__seg--${stage.key}`} />
59
+ {stage.label} <span className="mw-timing__ms">{formatMilliseconds(stage.ms)}</span>
60
+ </span>
61
+ ))}
62
+ </div>
63
+ </div>
64
+ )
65
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ *
6
+ * `TreeView` — the parse's containment tree as nested lists with a guide rail per level. The tag tint follows the
7
+ * confidence tier; the value and score render in monospace.
8
+ */
9
+
10
+ import type { ReactNode } from "react"
11
+
12
+ import { confidenceTierOrMid } from "#common/confidence-tiers"
13
+
14
+ interface TreeNode {
15
+ tag?: string
16
+ value?: unknown
17
+ confidence?: number
18
+ children?: TreeNode[]
19
+ }
20
+
21
+ export interface TreeViewProps {
22
+ /**
23
+ * The parser's `AddressTree` (`result.tree`): the view reads `.roots` and recurses `.children`.
24
+ */
25
+ tree: unknown
26
+ }
27
+
28
+ function renderNode(node: TreeNode, path: string): ReactNode {
29
+ if (typeof node.tag !== "string") return null
30
+
31
+ const kids = Array.isArray(node.children) ? node.children : []
32
+
33
+ return (
34
+ <li key={path} className="mw-tree__node">
35
+ <span className="mw-tree__row">
36
+ <span className={`mw-tree__tag mw-tree__tag--${confidenceTierOrMid(node.confidence)}`}>{node.tag}</span>
37
+ {node.value != null && String(node.value) !== "" ? (
38
+ <span className="mw-tree__value">{String(node.value)}</span>
39
+ ) : null}
40
+ {typeof node.confidence === "number" ? (
41
+ <span className="mw-tree__conf">{node.confidence.toFixed(2)}</span>
42
+ ) : null}
43
+ </span>
44
+ {kids.length ? <ul className="mw-tree__children">{kids.map((c, i) => renderNode(c, `${path}.${i}`))}</ul> : null}
45
+ </li>
46
+ )
47
+ }
48
+
49
+ export function TreeView({ tree }: TreeViewProps): ReactNode {
50
+ const roots = (tree as { roots?: unknown[] } | null | undefined)?.roots
51
+
52
+ if (!Array.isArray(roots) || !roots.length) return null
53
+
54
+ return <ul className="mw-tree mw-tree__children">{(roots as TreeNode[]).map((n, i) => renderNode(n, String(i)))}</ul>
55
+ }
@@ -7,7 +7,7 @@
7
7
  * pretty-printed. Pure; shared by the explorer's copy button.
8
8
  */
9
9
 
10
- import type { ParseResult, ResolvedPlaceView } from "#pipeline/types"
10
+ import type { ParseResult, ResolvedPlaceView } from "@mailwoman/core/pipeline/client-result"
11
11
 
12
12
  export function buildParsePayload(result: ParseResult, selected: ResolvedPlaceView | null): string {
13
13
  return JSON.stringify(