@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/fonts.css ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * The brand typeface, served from the same origin every app already preconnects to.
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.
8
+ *
9
+ * A rule never names a family here — it names a scale role from `tokens.css`, which names one of the five face
10
+ * roles. Replacing the typeface is an edit to `--font-family-*` in `styleframe.config.ts` plus this file.
11
+ */
12
+
13
+ @font-face {
14
+ font-family: "Iosevka Nexus Web";
15
+ font-display: swap;
16
+ font-weight: 400;
17
+ font-style: normal;
18
+ src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Regular.woff2") format("woff2");
19
+ }
20
+
21
+ @font-face {
22
+ font-family: "Iosevka Nexus Web";
23
+ font-display: swap;
24
+ font-weight: 450;
25
+ font-style: normal;
26
+ src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Book.woff2") format("woff2");
27
+ }
28
+
29
+ @font-face {
30
+ font-family: "Iosevka Nexus Web";
31
+ 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");
35
+ }
36
+
37
+ @font-face {
38
+ font-family: "Iosevka Nexus Web";
39
+ font-display: swap;
40
+ font-weight: 400;
41
+ font-style: italic;
42
+ src: url("https://public.mailwoman.ai/fonts/IoveskaNexus/WOFF2/IosevkaNexus-Italic.woff2") format("woff2");
43
+ }
44
+
45
+ @font-face {
46
+ font-family: "Iosevka Nexus Mono Web";
47
+ font-display: swap;
48
+ font-weight: 400;
49
+ font-style: normal;
50
+ src: url("https://public.mailwoman.ai/fonts/IosevkaNexusMono/WOFF2/IosevkaNexusMono-Regular.woff2") format("woff2");
51
+ }
52
+
53
+ @font-face {
54
+ font-family: "Iosevka Nexus Mono Web";
55
+ font-display: swap;
56
+ font-weight: 450;
57
+ font-style: normal;
58
+ src: url("https://public.mailwoman.ai/fonts/IosevkaNexusMono/WOFF2/IosevkaNexusMono-Book.woff2") format("woff2");
59
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
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.
9
+ */
10
+
11
+ import type { ReactNode } from "react"
12
+
13
+ export function About(): ReactNode {
14
+ return (
15
+ <details className="mw-about">
16
+ <summary className="mw-about__summary">
17
+ <span className="mw-about__icon">ℹ️</span>
18
+ About this geocoder
19
+ </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>
87
+ </details>
88
+ )
89
+ }
@@ -9,16 +9,11 @@
9
9
  * pipeline's projection satisfy it without a hard type dependency.
10
10
  */
11
11
 
12
+ import type { KindView } from "@mailwoman/core/pipeline/client-result"
12
13
  import type { ReactNode } from "react"
13
14
 
14
- export interface KindBadgeResult {
15
- kind: string
16
- confidence: number
17
- alternatives: ReadonlyArray<{ kind: string; confidence: number }>
18
- }
19
-
20
15
  export interface KindBadgeProps {
21
- kindResult: KindBadgeResult
16
+ kindResult: KindView
22
17
  }
23
18
 
24
19
  const formatPct = (n: number): string => `${Math.round(n * 100)}%`
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Confidence banding shared by every component that colours a span by confidence.
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
9
+ * pipeline can see them.
10
+ */
11
+
12
+ /**
13
+ * At or above this the span is drawn as high-confidence.
14
+ */
15
+ export const HIGH_CONFIDENCE_MIN = 0.8
16
+
17
+ /**
18
+ * At or above this the span is drawn as medium-confidence; below it, low.
19
+ */
20
+ export const MID_CONFIDENCE_MIN = 0.5
21
+
22
+ /**
23
+ * The banding a component applies to a span's confidence.
24
+ */
25
+ export type ConfidenceTier = "high" | "mid" | "low"
26
+
27
+ /**
28
+ * Bands a continuous confidence into the three tiers the demo styles against.
29
+ *
30
+ * @param confidence A parser confidence in `[0, 1]`.
31
+ *
32
+ * @returns The tier to style with.
33
+ */
34
+ export function confidenceTier(confidence: number): ConfidenceTier {
35
+ if (confidence >= HIGH_CONFIDENCE_MIN) return "high"
36
+
37
+ if (confidence >= MID_CONFIDENCE_MIN) return "mid"
38
+
39
+ return "low"
40
+ }
41
+
42
+ /**
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.
45
+ */
46
+ export function confidenceTierOrMid(confidence?: number): ConfidenceTier {
47
+ if (confidence == null) return "mid"
48
+
49
+ return confidenceTier(confidence)
50
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * @copyright Sister Software
3
+ * @license AGPL-3.0
4
+ * @author Teffen Ellis, et al.
5
+ * @file Word tokenization + span ownership shared by the word-level visualizers.
6
+ *
7
+ * A word-level panel splits the raw input on whitespace and assigns each word to its most specific
8
+ * covering span (the shortest-span owner, the same rule a character-level highlight applies per
9
+ * character). Two panels must agree for the BIO labels to line up between them, so the split and
10
+ * the owner walk live here.
11
+ */
12
+
13
+ export interface WordToken {
14
+ /**
15
+ * The word text as it appears in the input.
16
+ */
17
+ text: string
18
+ /**
19
+ * Leading whitespace before this word.
20
+ */
21
+ whitespace: string
22
+ /**
23
+ * Start offset in the input string.
24
+ */
25
+ start: number
26
+ /**
27
+ * End offset in the input string (exclusive).
28
+ */
29
+ end: number
30
+ }
31
+
32
+ /**
33
+ * Tokenize the raw input into words, preserving leading whitespace for each token.
34
+ */
35
+ export function tokenizeWords(input: string): WordToken[] {
36
+ const words: WordToken[] = []
37
+ let i = 0
38
+
39
+ // `charAt` answers "" past the end, which no whitespace test matches, so both walks stop at the input's length.
40
+ while (i < input.length) {
41
+ let ws = ""
42
+
43
+ while (/\s/.test(input.charAt(i))) {
44
+ ws += input.charAt(i)
45
+
46
+ i++
47
+ }
48
+
49
+ if (i >= input.length) break
50
+ const start = i
51
+
52
+ while (i < input.length && !/\s/.test(input.charAt(i))) {
53
+ i++
54
+ }
55
+
56
+ words.push({ text: input.slice(start, i), start, end: i, whitespace: ws })
57
+ }
58
+
59
+ return words
60
+ }
61
+
62
+ /**
63
+ * A half-open `[start, end)` character range into the input.
64
+ */
65
+ export interface CharSpan {
66
+ start: number
67
+ end: number
68
+ }
69
+
70
+ /**
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
+ */
74
+ export function shortestSpanOwners(words: readonly CharSpan[], spans: readonly CharSpan[]): number[] {
75
+ return words.map((word) => {
76
+ let best = -1
77
+ let bestLen = Infinity
78
+
79
+ for (const [s, sp] of spans.entries()) {
80
+ if (word.start < sp.end && word.end > sp.start && sp.end - sp.start < bestLen) {
81
+ bestLen = sp.end - sp.start
82
+ best = s
83
+ }
84
+ }
85
+
86
+ return best
87
+ })
88
+ }
package/lib/index.ts CHANGED
@@ -24,7 +24,7 @@ export type { CopyButtonProps } from "./common/CopyButton.tsx"
24
24
  export { cx } from "#common/cx"
25
25
  export type { ClassValue } from "#common/cx"
26
26
  export { KindBadge } from "./common/KindBadge.tsx"
27
- export type { KindBadgeProps, KindBadgeResult } from "./common/KindBadge.tsx"
27
+ export type { KindBadgeProps } from "./common/KindBadge.tsx"
28
28
  export { LoadingIndicator } from "./common/LoadingIndicator.tsx"
29
29
  export type { LoadingIndicatorProps, LoadingMode } from "./common/LoadingIndicator.tsx"
30
30
  export { PresetChips } from "./common/PresetChips.tsx"
@@ -67,6 +67,18 @@ export type {
67
67
  export { usePOISearch } from "#poi/usePOISearch"
68
68
  export type { UsePOISearch, UsePOISearchOptions } from "#poi/usePOISearch"
69
69
 
70
+ // ── Result visualizers ─────────────────────────────────────────────────────
71
+
72
+ export { About } from "./common/About.tsx"
73
+ export { FailureDiagnostic } from "./pipeline/FailureDiagnostic.tsx"
74
+ export type { FailureDiagnosticProps } from "./pipeline/FailureDiagnostic.tsx"
75
+ export { SpanHighlight } from "./pipeline/SpanHighlight.tsx"
76
+ export type { SpanHighlightProps } from "./pipeline/SpanHighlight.tsx"
77
+ export { TimingPanel } from "./pipeline/TimingPanel.tsx"
78
+ export type { TimingPanelProps } from "./pipeline/TimingPanel.tsx"
79
+ export { TreeView } from "./pipeline/TreeView.tsx"
80
+ export type { TreeViewProps } from "./pipeline/TreeView.tsx"
81
+
70
82
  // ── Pipeline explorer ──────────────────────────────────────────────────────
71
83
  export { CandidatePicker } from "./pipeline/CandidatePicker.tsx"
72
84
  export type { CandidatePickerProps } from "./pipeline/CandidatePicker.tsx"
@@ -83,46 +95,36 @@ export type { QueryFormProps } from "./pipeline/QueryForm.tsx"
83
95
  export { ResolvedPlace } from "./pipeline/ResolvedPlace.tsx"
84
96
  export type { ResolvedPlaceProps } from "./pipeline/ResolvedPlace.tsx"
85
97
 
86
- export type {
87
- DualRoleView,
88
- FSTProvenance,
89
- ParsedComponent,
90
- ParseResult,
91
- PipelineLoadingState,
92
- PipelinePanels,
93
- PipelineRuntime,
94
- ResolvedPlaceView,
95
- StageTiming,
96
- } from "#pipeline/types"
98
+ export type { PipelineLoadingState, PipelinePanels, PipelineRuntime } from "#pipeline/types"
97
99
 
98
100
  export { useParsePipeline } from "#pipeline/useParsePipeline"
99
101
  export type { UseParsePipeline, UseParsePipelineOptions } from "#pipeline/useParsePipeline"
100
102
 
101
- // ── Demo runtime (shared load orchestration; node-safe — no onnx/httpvfs/maplibre) ──
102
- export { useDemoRuntime } from "#runtime/useDemoRuntime"
103
+ // ── Release runtime (shared load orchestration; node-safe — no onnx/httpvfs/maplibre) ──
104
+ export { useReleaseRuntime } from "#runtime/useReleaseRuntime"
103
105
 
104
106
  export type {
105
- DemoAssetsLoadContext,
106
- DemoLoaderState,
107
- DemoManifest,
108
- DemoReleaseBase,
109
- DemoRuntimeConfig,
110
- } from "#runtime/useDemoRuntime"
107
+ AssetsLoadContext,
108
+ ReleaseBase,
109
+ ReleaseLoaderState,
110
+ ReleaseManifest,
111
+ ReleaseRuntimeConfig,
112
+ } from "#runtime/useReleaseRuntime"
111
113
 
112
114
  // ── Map (types only from root) ──────────────────────────────────────────────
113
- // The demo-map SURFACE lives behind the `@mailwoman/react/map` subpath so `maplibre-gl` / `react-map-gl`
115
+ // The map SURFACE lives behind the `@mailwoman/react/map` subpath so `maplibre-gl` / `react-map-gl`
114
116
  // (WebGL + DOM at import) never enter the package-root graph — a bare `import("@mailwoman/react")` in node
115
117
  // must not pull them. Only the CONTRACT types are re-exported here (fully erased at compile time, so no
116
- // runtime linkage): the `DemoMap` component + hooks are import-able solely via `@mailwoman/react/map`.
118
+ // runtime linkage): the `MapCanvas` component + hooks are import-able solely via `@mailwoman/react/map`.
117
119
  export type {
118
- DemoBackend,
119
- DemoMapExtraProps,
120
- DemoMapProps,
121
- DemoMapStyle,
122
- DemoRuntime,
123
- DemoVersionOption,
120
+ GeocoderRuntime,
121
+ InferenceBackend,
124
122
  LngLatTuple,
125
123
  MapBias,
124
+ MapCanvasExtraProps,
125
+ MapCanvasProps,
126
+ MapCanvasStyle,
126
127
  OverlaySpec,
127
128
  Suggestion,
129
+ VersionOption,
128
130
  } from "#map/index"
@@ -13,13 +13,13 @@
13
13
 
14
14
  import type { ReactNode } from "react"
15
15
 
16
- import type { DemoVersionOption } from "#map/types"
16
+ import type { VersionOption } from "#map/types"
17
17
 
18
18
  export interface CompareToggleProps {
19
19
  /**
20
20
  * The selectable model bundles (the primary version is filtered out of the compare list).
21
21
  */
22
- versions: ReadonlyArray<DemoVersionOption>
22
+ versions: ReadonlyArray<VersionOption>
23
23
  /**
24
24
  * The primary version, excluded from the compare options.
25
25
  */
@@ -3,18 +3,18 @@
3
3
  * @license AGPL-3.0
4
4
  * @author Teffen Ellis, et al.
5
5
  *
6
- * `<GeocoderDemo>` — the WHOLE geocoder demo, composed. It is the map analogue of `PipelineExplorer`
7
- * and takes the SAME DI shape: an injected {@link DemoRuntime} (the host owns ONNX / httpvfs / R2 /
8
- * the composed map style) plus a {@link DemoPanels} bag (the host's ModelVisualizer / VersionCompare /
6
+ * `<Geocoder>` — the WHOLE geocoder, composed. It is the map analogue of `PipelineExplorer`
7
+ * and takes the SAME DI shape: an injected {@link GeocoderRuntime} (the host owns ONNX / httpvfs / R2 /
8
+ * the composed map style) plus a {@link GeocoderPanels} bag (the host's ModelVisualizer / VersionCompare /
9
9
  * About / Permalink). Everything here is composition + a `ClientOnly` boundary:
10
10
  *
11
- * - the floating {@link DemoControls} panel (version / compare / backend / query+autocomplete / result),
12
- * - the declarative {@link DemoMap} with the phase-2 overlays ({@link OverlayLayers}) and the
11
+ * - the floating {@link GeocoderControls} panel (version / compare / backend / query+autocomplete / result),
12
+ * - the declarative {@link MapCanvas} with the phase-2 overlays ({@link OverlayLayers}) and the
13
13
  * resolved-place marker/outline/camera ({@link ResolvedPlaceLayers}) driven by the parse state,
14
- * - the hooks that wire them: {@link useDemoGeocode} (parse + viewport bias + map place),
14
+ * - the hooks that wire them: {@link useGeocode} (parse + viewport bias + map place),
15
15
  * {@link usePlaceAutocomplete}, {@link useCompareState}.
16
16
  *
17
- * Because it pulls {@link DemoMap} (→ `react-map-gl` → `maplibre-gl`, WebGL + DOM at import), it lives on
17
+ * Because it pulls {@link MapCanvas} (→ `react-map-gl` → `maplibre-gl`, WebGL + DOM at import), it lives on
18
18
  * the `@mailwoman/react/map` subpath ONLY — never the package root. The whole thing renders in Storybook
19
19
  * over a fake runtime (offline stub style + canned geocode) with no network, no ONNX, no gazetteer.
20
20
  */
@@ -22,28 +22,28 @@
22
22
  import { type ReactNode, useCallback, useEffect, useRef } from "react"
23
23
  import type { MapRef } from "react-map-gl/maplibre"
24
24
 
25
- import type { DemoPanels, DemoRuntime, MapBias } from "#map/types"
25
+ import type { GeocoderPanels, GeocoderRuntime, MapBias } from "#map/types"
26
26
  import { useCompareState } from "#map/useCompareState"
27
- import { useDemoGeocode } from "#map/useDemoGeocode"
27
+ import { useGeocode } from "#map/useGeocode"
28
28
  import { useMapPlaceRender } from "#map/useMapPlaceRender"
29
29
  import { usePlaceAutocomplete } from "#map/usePlaceAutocomplete"
30
30
 
31
31
  import { ClientOnly } from "../common/ClientOnly.tsx"
32
32
  import type { Preset } from "../common/PresetChips.tsx"
33
- import { DemoControls } from "./DemoControls.tsx"
34
- import { DemoMap } from "./DemoMap.tsx"
33
+ import { GeocoderControls } from "./GeocoderControls.tsx"
34
+ import { MapCanvas } from "./MapCanvas.tsx"
35
35
  import { OverlayLayers } from "./OverlayLayers.tsx"
36
36
  import { ResolvedPlaceLayers } from "./ResolvedPlaceLayers.tsx"
37
37
 
38
- export interface GeocoderDemoProps {
38
+ export interface GeocoderProps {
39
39
  /**
40
- * The injected demo runtime (map style + overlays + parse + version/backend).
40
+ * The injected geocoder runtime (map style + overlays + parse + version/backend).
41
41
  */
42
- runtime: DemoRuntime
42
+ runtime: GeocoderRuntime
43
43
  /**
44
44
  * Host-injected panels (about, release blurb, compare, permalink, debug drawer, map controls, …).
45
45
  */
46
- panels?: DemoPanels
46
+ panels?: GeocoderPanels
47
47
  /**
48
48
  * Address to pre-fill.
49
49
  */
@@ -54,54 +54,54 @@ export interface GeocoderDemoProps {
54
54
  presets?: ReadonlyArray<Preset>
55
55
  /**
56
56
  * Only hint the viewport bias once the visitor has zoomed past the global view — a whole-globe center is noise.
57
- * Matches the demo's `map.getZoom() >= 4` threshold. @default 4
57
+ * Matches the `map.getZoom() >= 4` threshold. @default 4
58
58
  */
59
59
  minBiasZoom?: number
60
60
  /**
61
61
  * Fly/fit the map to the resolved place on each result (via {@link ResolvedPlaceLayers}). @default true. Set false
62
- * for a host that drives the camera itself (a controlled `<DemoMap viewState>`), or to keep a headless test
62
+ * for a host that drives the camera itself (a controlled `<MapCanvas viewState>`), or to keep a headless test
63
63
  * deterministic — the marker + outline still render, only the animated camera move is skipped.
64
64
  */
65
65
  applyResultCamera?: boolean
66
66
  }
67
67
 
68
- interface GeocoderDemoInnerProps extends Required<
69
- Pick<GeocoderDemoProps, "runtime" | "defaultAddress" | "minBiasZoom" | "applyResultCamera">
68
+ interface GeocoderInnerProps extends Required<
69
+ Pick<GeocoderProps, "runtime" | "defaultAddress" | "minBiasZoom" | "applyResultCamera">
70
70
  > {
71
- panels: DemoPanels
71
+ panels: GeocoderPanels
72
72
  presets: ReadonlyArray<Preset>
73
73
  }
74
74
 
75
- function GeocoderDemoInner({
75
+ function GeocoderInner({
76
76
  runtime,
77
77
  panels,
78
78
  defaultAddress,
79
79
  presets,
80
80
  minBiasZoom,
81
81
  applyResultCamera,
82
- }: GeocoderDemoInnerProps): ReactNode {
82
+ }: GeocoderInnerProps): ReactNode {
83
83
  const mapRef = useRef<MapRef>(null)
84
84
 
85
85
  // TEST INJECTION POINT: the e2e viewport-bias suite drives the REAL map (pan + zoom past the bias threshold)
86
86
  // before submitting, and a browser test cannot reach a React ref — so the live map handle is
87
- // republished on `globalThis.__mailwomanDemoMap`, the same global the pre-port demo carried. The
88
- // ref fills only after react-map-gl instantiates the map, hence the short poll; cleared on
89
- // unmount so a torn-down demo never leaves a stale handle behind.
87
+ // republished on `globalThis.__mailwomanMapCanvas`. The ref fills only after react-map-gl instantiates
88
+ // the map, hence the short poll; cleared on unmount so a torn-down geocoder never leaves a stale
89
+ // handle behind.
90
90
  useEffect(() => {
91
- const host = globalThis as { __mailwomanDemoMap?: ReturnType<MapRef["getMap"]> }
91
+ const host = globalThis as { __mailwomanMapCanvas?: ReturnType<MapRef["getMap"]> }
92
92
 
93
93
  const timer = setInterval(() => {
94
94
  const map = mapRef.current?.getMap()
95
95
 
96
96
  if (map) {
97
- host.__mailwomanDemoMap = map
97
+ host.__mailwomanMapCanvas = map
98
98
  clearInterval(timer)
99
99
  }
100
100
  }, 250)
101
101
 
102
102
  return () => {
103
103
  clearInterval(timer)
104
- delete host.__mailwomanDemoMap
104
+ delete host.__mailwomanMapCanvas
105
105
  }
106
106
  }, [])
107
107
 
@@ -119,7 +119,7 @@ function GeocoderDemoInner({
119
119
  return { center: [center.lng, center.lat], zoom }
120
120
  }, [minBiasZoom])
121
121
 
122
- const geocode = useDemoGeocode({ runtime, defaultText: defaultAddress, getBias })
122
+ const geocode = useGeocode({ runtime, defaultText: defaultAddress, getBias })
123
123
  const compare = useCompareState()
124
124
 
125
125
  const autocomplete = usePlaceAutocomplete({
@@ -143,7 +143,7 @@ function GeocoderDemoInner({
143
143
  return (
144
144
  <div className="mw-geocoder-demo">
145
145
  <div className="mw-geocoder-demo__map">
146
- <DemoMap
146
+ <MapCanvas
147
147
  mapStyle={runtime.mapStyle}
148
148
  mapRef={mapRef}
149
149
  initialViewState={{
@@ -152,17 +152,17 @@ function GeocoderDemoInner({
152
152
  zoom: runtime.initialZoom ?? 3,
153
153
  }}
154
154
  style={{ width: "100%", height: "100%" }}
155
- // Match the live demo's chrome: one compact attribution pill (the map's own default is a wide, always-open
156
- // "MapLibre | © …" bar), no maplibre wordmark logo.
155
+ // One compact attribution pill (the map's own default is a wide, always-open "MapLibre | © …" bar), no
156
+ // maplibre wordmark logo.
157
157
  mapProps={{ attributionControl: { compact: true }, maplibreLogo: false }}
158
158
  >
159
159
  <OverlayLayers overlays={runtime.overlays} />
160
160
  <ResolvedPlaceLayers spec={spec} applyCamera={applyResultCamera} />
161
161
  {panels.mapControls}
162
- </DemoMap>
162
+ </MapCanvas>
163
163
  </div>
164
164
 
165
- <DemoControls
165
+ <GeocoderControls
166
166
  runtime={runtime}
167
167
  geocode={geocode}
168
168
  autocomplete={autocomplete}
@@ -182,30 +182,30 @@ function GeocoderDemoInner({
182
182
  /**
183
183
  * Stable empty defaults — a fresh `{}`/`[]` per render would churn every downstream memo dep.
184
184
  */
185
- const NO_PANELS: DemoPanels = {}
185
+ const NO_PANELS: GeocoderPanels = {}
186
186
  const NO_PRESETS: ReadonlyArray<Preset> = []
187
187
 
188
188
  /**
189
- * The composed geocoder demo, behind a `ClientOnly` SSR boundary (the map is intrinsically a client component).
189
+ * The composed geocoder, behind a `ClientOnly` SSR boundary (the map is intrinsically a client component).
190
190
  */
191
- export function GeocoderDemo({
191
+ export function Geocoder({
192
192
  runtime,
193
193
  panels = NO_PANELS,
194
194
  defaultAddress = "",
195
195
  presets = NO_PRESETS,
196
196
  minBiasZoom = 4,
197
197
  applyResultCamera = true,
198
- }: GeocoderDemoProps): ReactNode {
198
+ }: GeocoderProps): ReactNode {
199
199
  return (
200
200
  <ClientOnly
201
201
  fallback={
202
202
  <div className="mw-geocoder-demo">
203
- <p>Loading demo…</p>
203
+ <p>Loading the geocoder…</p>
204
204
  </div>
205
205
  }
206
206
  >
207
207
  {() => (
208
- <GeocoderDemoInner
208
+ <GeocoderInner
209
209
  runtime={runtime}
210
210
  panels={panels}
211
211
  defaultAddress={defaultAddress}