@mailwoman/react 9.1.0 → 9.3.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 (211) hide show
  1. package/README.md +2 -2
  2. package/{common → lib/common}/ClientOnly.tsx +18 -6
  3. package/{common → lib/common}/CopyButton.tsx +1 -1
  4. package/{common → lib/common}/LoadingIndicator.tsx +1 -1
  5. package/{index.ts → lib/index.ts} +17 -17
  6. package/{map → lib/map}/CompareToggle.tsx +1 -1
  7. package/{map → lib/map}/DemoControls.tsx +5 -4
  8. package/{map → lib/map}/DemoMap.tsx +2 -2
  9. package/{map → lib/map}/GeocoderDemo.tsx +32 -8
  10. package/{map → lib/map}/OverlayLayers.tsx +3 -3
  11. package/{map → lib/map}/PlaceAutocomplete.tsx +2 -2
  12. package/{map → lib/map}/ResolvedPlaceLayers.tsx +2 -1
  13. package/{map → lib/map}/ResultCamera.tsx +2 -2
  14. package/{map → lib/map}/ResultOverlay.tsx +2 -2
  15. package/{map → lib/map}/ResultPanel.tsx +3 -2
  16. package/{map → lib/map}/VersionPicker.tsx +1 -1
  17. package/{map → lib/map}/geometry.ts +7 -7
  18. package/{map → lib/map}/index.ts +12 -12
  19. package/{map → lib/map}/place-render.ts +3 -3
  20. package/{map → lib/map}/types.ts +12 -9
  21. package/{map → lib/map}/useDemoGeocode.ts +4 -4
  22. package/{map → lib/map}/useMapPlaceRender.ts +2 -2
  23. package/{map → lib/map}/usePlaceAutocomplete.ts +39 -27
  24. package/{pipeline → lib/pipeline}/CandidatePicker.tsx +2 -2
  25. package/{pipeline → lib/pipeline}/ComponentTable.tsx +2 -1
  26. package/{pipeline → lib/pipeline}/ConfidenceCell.tsx +1 -1
  27. package/{pipeline → lib/pipeline}/PipelineExplorer.tsx +5 -4
  28. package/{pipeline → lib/pipeline}/QueryForm.tsx +2 -2
  29. package/{pipeline → lib/pipeline}/ResolvedPlace.tsx +1 -1
  30. package/{pipeline → lib/pipeline}/copy.ts +1 -1
  31. package/{pipeline → lib/pipeline}/presets.ts +2 -1
  32. package/{pipeline → lib/pipeline}/useParsePipeline.ts +1 -1
  33. package/{poi → lib/poi}/LiveResultsBlock.tsx +2 -2
  34. package/{poi → lib/poi}/POIExplorer.tsx +4 -3
  35. package/{poi → lib/poi}/SubjectPanel.tsx +1 -1
  36. package/{poi → lib/poi}/runtime.ts +3 -3
  37. package/{poi → lib/poi}/types.ts +1 -1
  38. package/{poi → lib/poi}/usePOISearch.ts +77 -51
  39. package/{runtime → lib/runtime}/useDemoRuntime.ts +8 -5
  40. package/out/common/ClientOnly.d.ts +5 -2
  41. package/out/common/ClientOnly.d.ts.map +1 -1
  42. package/out/common/ClientOnly.js +13 -5
  43. package/out/common/ClientOnly.js.map +1 -1
  44. package/out/common/CopyButton.d.ts.map +1 -1
  45. package/out/common/CopyButton.js +1 -1
  46. package/out/common/CopyButton.js.map +1 -1
  47. package/out/common/KindBadge.d.ts.map +1 -1
  48. package/out/common/KindBadge.js.map +1 -1
  49. package/out/common/LoadingIndicator.d.ts.map +1 -1
  50. package/out/common/LoadingIndicator.js +1 -1
  51. package/out/common/LoadingIndicator.js.map +1 -1
  52. package/out/common/PresetChips.d.ts.map +1 -1
  53. package/out/common/PresetChips.js.map +1 -1
  54. package/out/common/cx.d.ts.map +1 -1
  55. package/out/common/cx.js.map +1 -1
  56. package/out/common/useClipboard.d.ts.map +1 -1
  57. package/out/common/useClipboard.js.map +1 -1
  58. package/out/common/useDebouncedValue.d.ts.map +1 -1
  59. package/out/common/useDebouncedValue.js.map +1 -1
  60. package/out/index.d.ts +17 -17
  61. package/out/index.d.ts.map +1 -1
  62. package/out/index.js +9 -9
  63. package/out/index.js.map +1 -1
  64. package/out/map/BackendControl.d.ts.map +1 -1
  65. package/out/map/BackendControl.js.map +1 -1
  66. package/out/map/CompareToggle.d.ts +1 -1
  67. package/out/map/CompareToggle.d.ts.map +1 -1
  68. package/out/map/CompareToggle.js.map +1 -1
  69. package/out/map/DemoControls.d.ts +4 -4
  70. package/out/map/DemoControls.d.ts.map +1 -1
  71. package/out/map/DemoControls.js.map +1 -1
  72. package/out/map/DemoMap.d.ts +2 -2
  73. package/out/map/DemoMap.d.ts.map +1 -1
  74. package/out/map/DemoMap.js.map +1 -1
  75. package/out/map/GeocoderDemo.d.ts +3 -3
  76. package/out/map/GeocoderDemo.d.ts.map +1 -1
  77. package/out/map/GeocoderDemo.js +25 -6
  78. package/out/map/GeocoderDemo.js.map +1 -1
  79. package/out/map/OverlayLayers.d.ts +3 -3
  80. package/out/map/OverlayLayers.d.ts.map +1 -1
  81. package/out/map/OverlayLayers.js +2 -2
  82. package/out/map/OverlayLayers.js.map +1 -1
  83. package/out/map/PlaceAutocomplete.d.ts +1 -1
  84. package/out/map/PlaceAutocomplete.d.ts.map +1 -1
  85. package/out/map/PlaceAutocomplete.js +1 -1
  86. package/out/map/PlaceAutocomplete.js.map +1 -1
  87. package/out/map/PlaceMarker.d.ts.map +1 -1
  88. package/out/map/PlaceMarker.js.map +1 -1
  89. package/out/map/ResolvedPlaceLayers.d.ts +1 -1
  90. package/out/map/ResolvedPlaceLayers.d.ts.map +1 -1
  91. package/out/map/ResolvedPlaceLayers.js.map +1 -1
  92. package/out/map/ResultCamera.d.ts +2 -2
  93. package/out/map/ResultCamera.d.ts.map +1 -1
  94. package/out/map/ResultCamera.js +1 -1
  95. package/out/map/ResultCamera.js.map +1 -1
  96. package/out/map/ResultOverlay.d.ts +2 -2
  97. package/out/map/ResultOverlay.d.ts.map +1 -1
  98. package/out/map/ResultOverlay.js.map +1 -1
  99. package/out/map/ResultPanel.d.ts +1 -1
  100. package/out/map/ResultPanel.d.ts.map +1 -1
  101. package/out/map/ResultPanel.js +1 -1
  102. package/out/map/ResultPanel.js.map +1 -1
  103. package/out/map/VersionPicker.d.ts +1 -1
  104. package/out/map/VersionPicker.d.ts.map +1 -1
  105. package/out/map/VersionPicker.js.map +1 -1
  106. package/out/map/geometry.d.ts.map +1 -1
  107. package/out/map/geometry.js +7 -7
  108. package/out/map/geometry.js.map +1 -1
  109. package/out/map/index.d.ts +12 -12
  110. package/out/map/index.d.ts.map +1 -1
  111. package/out/map/index.js +6 -6
  112. package/out/map/index.js.map +1 -1
  113. package/out/map/place-render.d.ts +2 -2
  114. package/out/map/place-render.d.ts.map +1 -1
  115. package/out/map/place-render.js +1 -1
  116. package/out/map/place-render.js.map +1 -1
  117. package/out/map/types.d.ts +11 -9
  118. package/out/map/types.d.ts.map +1 -1
  119. package/out/map/types.js +2 -2
  120. package/out/map/types.js.map +1 -1
  121. package/out/map/useCompareState.d.ts.map +1 -1
  122. package/out/map/useCompareState.js.map +1 -1
  123. package/out/map/useDemoGeocode.d.ts +3 -3
  124. package/out/map/useDemoGeocode.d.ts.map +1 -1
  125. package/out/map/useDemoGeocode.js +1 -1
  126. package/out/map/useDemoGeocode.js.map +1 -1
  127. package/out/map/useMapPlaceRender.d.ts +1 -1
  128. package/out/map/useMapPlaceRender.d.ts.map +1 -1
  129. package/out/map/useMapPlaceRender.js +1 -1
  130. package/out/map/useMapPlaceRender.js.map +1 -1
  131. package/out/map/usePlaceAutocomplete.d.ts +1 -1
  132. package/out/map/usePlaceAutocomplete.d.ts.map +1 -1
  133. package/out/map/usePlaceAutocomplete.js +34 -23
  134. package/out/map/usePlaceAutocomplete.js.map +1 -1
  135. package/out/pipeline/CandidatePicker.d.ts +1 -1
  136. package/out/pipeline/CandidatePicker.d.ts.map +1 -1
  137. package/out/pipeline/CandidatePicker.js +1 -1
  138. package/out/pipeline/CandidatePicker.js.map +1 -1
  139. package/out/pipeline/ComponentTable.d.ts +1 -1
  140. package/out/pipeline/ComponentTable.d.ts.map +1 -1
  141. package/out/pipeline/ComponentTable.js.map +1 -1
  142. package/out/pipeline/ConfidenceCell.d.ts.map +1 -1
  143. package/out/pipeline/ConfidenceCell.js +1 -1
  144. package/out/pipeline/ConfidenceCell.js.map +1 -1
  145. package/out/pipeline/PipelineExplorer.d.ts +1 -1
  146. package/out/pipeline/PipelineExplorer.d.ts.map +1 -1
  147. package/out/pipeline/PipelineExplorer.js +3 -3
  148. package/out/pipeline/PipelineExplorer.js.map +1 -1
  149. package/out/pipeline/QueryForm.d.ts +2 -2
  150. package/out/pipeline/QueryForm.d.ts.map +1 -1
  151. package/out/pipeline/QueryForm.js.map +1 -1
  152. package/out/pipeline/ResolvedPlace.d.ts +1 -1
  153. package/out/pipeline/ResolvedPlace.d.ts.map +1 -1
  154. package/out/pipeline/ResolvedPlace.js.map +1 -1
  155. package/out/pipeline/copy.d.ts +1 -1
  156. package/out/pipeline/copy.d.ts.map +1 -1
  157. package/out/pipeline/copy.js.map +1 -1
  158. package/out/pipeline/presets.d.ts.map +1 -1
  159. package/out/pipeline/presets.js +2 -1
  160. package/out/pipeline/presets.js.map +1 -1
  161. package/out/pipeline/types.d.ts.map +1 -1
  162. package/out/pipeline/types.js.map +1 -1
  163. package/out/pipeline/useParsePipeline.d.ts +1 -1
  164. package/out/pipeline/useParsePipeline.d.ts.map +1 -1
  165. package/out/pipeline/useParsePipeline.js.map +1 -1
  166. package/out/poi/AbstainPanel.d.ts.map +1 -1
  167. package/out/poi/AbstainPanel.js.map +1 -1
  168. package/out/poi/LiveResultsBlock.d.ts +1 -1
  169. package/out/poi/LiveResultsBlock.d.ts.map +1 -1
  170. package/out/poi/LiveResultsBlock.js +1 -1
  171. package/out/poi/LiveResultsBlock.js.map +1 -1
  172. package/out/poi/OverpassBlock.d.ts.map +1 -1
  173. package/out/poi/OverpassBlock.js.map +1 -1
  174. package/out/poi/POIExplorer.d.ts +1 -1
  175. package/out/poi/POIExplorer.d.ts.map +1 -1
  176. package/out/poi/POIExplorer.js +2 -2
  177. package/out/poi/POIExplorer.js.map +1 -1
  178. package/out/poi/QueryInput.d.ts.map +1 -1
  179. package/out/poi/QueryInput.js.map +1 -1
  180. package/out/poi/SubjectPanel.d.ts +1 -1
  181. package/out/poi/SubjectPanel.d.ts.map +1 -1
  182. package/out/poi/SubjectPanel.js.map +1 -1
  183. package/out/poi/runtime.d.ts +1 -1
  184. package/out/poi/runtime.d.ts.map +1 -1
  185. package/out/poi/runtime.js.map +1 -1
  186. package/out/poi/types.d.ts +1 -1
  187. package/out/poi/types.d.ts.map +1 -1
  188. package/out/poi/types.js +1 -1
  189. package/out/poi/types.js.map +1 -1
  190. package/out/poi/usePOISearch.d.ts +1 -1
  191. package/out/poi/usePOISearch.d.ts.map +1 -1
  192. package/out/poi/usePOISearch.js +75 -48
  193. package/out/poi/usePOISearch.js.map +1 -1
  194. package/out/runtime/useDemoRuntime.d.ts +2 -2
  195. package/out/runtime/useDemoRuntime.d.ts.map +1 -1
  196. package/out/runtime/useDemoRuntime.js +7 -5
  197. package/out/runtime/useDemoRuntime.js.map +1 -1
  198. package/package.json +338 -18
  199. package/styles.css +49 -19
  200. /package/{common → lib/common}/KindBadge.tsx +0 -0
  201. /package/{common → lib/common}/PresetChips.tsx +0 -0
  202. /package/{common → lib/common}/cx.ts +0 -0
  203. /package/{common → lib/common}/useClipboard.ts +0 -0
  204. /package/{common → lib/common}/useDebouncedValue.ts +0 -0
  205. /package/{map → lib/map}/BackendControl.tsx +0 -0
  206. /package/{map → lib/map}/PlaceMarker.tsx +0 -0
  207. /package/{map → lib/map}/useCompareState.ts +0 -0
  208. /package/{pipeline → lib/pipeline}/types.ts +0 -0
  209. /package/{poi → lib/poi}/AbstainPanel.tsx +0 -0
  210. /package/{poi → lib/poi}/OverpassBlock.tsx +0 -0
  211. /package/{poi → lib/poi}/QueryInput.tsx +0 -0
@@ -15,10 +15,10 @@
15
15
  * the presentational {@link PlaceAutocomplete} listbox renders `suggestions` / `activeIndex`.
16
16
  */
17
17
 
18
- import { type KeyboardEvent, useCallback, useEffect, useRef, useState } from "react"
18
+ import { type KeyboardEvent, useCallback, useEffect, useState } from "react"
19
19
 
20
- import { useDebouncedValue } from "../common/useDebouncedValue.ts"
21
- import type { Suggestion } from "./types.ts"
20
+ import { useDebouncedValue } from "#common/useDebouncedValue"
21
+ import type { Suggestion } from "#map/types"
22
22
 
23
23
  export interface UsePlaceAutocompleteOptions {
24
24
  /**
@@ -95,6 +95,12 @@ export interface UsePlaceAutocomplete {
95
95
  }
96
96
 
97
97
  const LISTBOX_ID = "mw-demo-suggest-list"
98
+
99
+ /**
100
+ * Stable empty list for the derived no-suggestions state — a fresh `[]` per render would churn the identity every
101
+ * consumer and every useCallback dependency list sees.
102
+ */
103
+ const NO_SUGGESTIONS: Suggestion[] = []
98
104
  const optionID = (index: number) => `mw-demo-suggest-${index}`
99
105
 
100
106
  /**
@@ -118,30 +124,33 @@ export function usePlaceAutocomplete({
118
124
  minChars = 2,
119
125
  debounceMs = 150,
120
126
  }: UsePlaceAutocompleteOptions): UsePlaceAutocomplete {
121
- const [suggestions, setSuggestions] = useState<Suggestion[]>([])
127
+ /**
128
+ * The last COMPLETED fetch, keyed by the query that produced it. Visibility is DERIVED from this during render rather
129
+ * than pushed through sync setStates in the effect — the effect's only job is the async fetch, so every state write
130
+ * in it happens after an await and the react(set-state-in-effect) rule is satisfied by structure, not by exception.
131
+ */
132
+ const [fetched, setFetched] = useState<{ query: string; suggestions: Suggestion[] } | null>(null)
133
+ /**
134
+ * The query whose list was dismissed or picked. Suppression as DATA, replacing the one-shot ref flag: a pick stores
135
+ * the query the rewritten text will produce, so the post-pick fetch is skipped and the place just chosen is never
136
+ * re-suggested. One divergence from the ref version, accepted and small: after Esc, retyping the exact same string
137
+ * keeps the list hidden until the query changes.
138
+ */
139
+ const [dismissed, setDismissed] = useState<string | null>(null)
122
140
  const [activeIndex, setActiveIndex] = useState(-1)
123
- // One-shot guard: a pick rewrites `text` to the chosen name, which would otherwise re-trigger the fetch and
124
- // immediately re-suggest the place just chosen. Set on pick, consumed by the next effect run.
125
- const suppressRef = useRef(false)
126
141
 
127
142
  const query = localitySegment(text)
128
143
  const debouncedQuery = useDebouncedValue(query, debounceMs)
129
144
 
130
- useEffect(() => {
131
- if (suppressRef.current) {
132
- suppressRef.current = false
133
- setSuggestions([])
134
- setActiveIndex(-1)
145
+ const eligible = Boolean(autocomplete) && debouncedQuery.length >= minChars && !/^\d/.test(debouncedQuery)
135
146
 
136
- return
137
- }
147
+ const suggestions =
148
+ eligible && fetched?.query === debouncedQuery && dismissed !== debouncedQuery ? fetched.suggestions : NO_SUGGESTIONS
138
149
 
139
- if (!autocomplete || debouncedQuery.length < minChars || /^\d/.test(debouncedQuery)) {
140
- setSuggestions([])
141
- setActiveIndex(-1)
150
+ useEffect(() => {
151
+ if (!autocomplete || debouncedQuery.length < minChars || /^\d/.test(debouncedQuery)) return
142
152
 
143
- return
144
- }
153
+ if (dismissed === debouncedQuery) return
145
154
 
146
155
  let cancelled = false
147
156
 
@@ -150,11 +159,11 @@ export function usePlaceAutocomplete({
150
159
  const next = await autocomplete(debouncedQuery)
151
160
 
152
161
  if (cancelled) return
153
- setSuggestions(next)
162
+ setFetched({ query: debouncedQuery, suggestions: next })
154
163
  setActiveIndex(-1)
155
164
  } catch {
156
165
  if (cancelled) return
157
- setSuggestions([])
166
+ setFetched({ query: debouncedQuery, suggestions: [] })
158
167
  setActiveIndex(-1)
159
168
  }
160
169
  })()
@@ -162,22 +171,25 @@ export function usePlaceAutocomplete({
162
171
  return () => {
163
172
  cancelled = true
164
173
  }
165
- }, [debouncedQuery, autocomplete, minChars])
174
+ }, [debouncedQuery, autocomplete, minChars, dismissed])
166
175
 
167
176
  const pick = useCallback(
168
177
  (value: string) => {
169
- suppressRef.current = true
170
- setText(replaceSegment(text, value))
171
- setSuggestions([])
178
+ const next = replaceSegment(text, value)
179
+
180
+ setText(next)
181
+ // Suppress the fetch the rewritten text would trigger — the segment being typed IS the picked name.
182
+ setDismissed(localitySegment(next))
183
+ setFetched(null)
172
184
  setActiveIndex(-1)
173
185
  },
174
186
  [text, setText]
175
187
  )
176
188
 
177
189
  const dismiss = useCallback(() => {
178
- setSuggestions([])
190
+ setDismissed(debouncedQuery)
179
191
  setActiveIndex(-1)
180
- }, [])
192
+ }, [debouncedQuery])
181
193
 
182
194
  const onInputKeyDown = useCallback(
183
195
  (event: KeyboardEvent<HTMLInputElement>) => {
@@ -9,8 +9,8 @@
9
9
 
10
10
  import type { ReactNode } from "react"
11
11
 
12
- import { cx } from "../common/cx.ts"
13
- import type { ResolvedPlaceView } from "./types.ts"
12
+ import { cx } from "#common/cx"
13
+ import type { ResolvedPlaceView } from "#pipeline/types"
14
14
 
15
15
  export interface CandidatePickerProps {
16
16
  candidates: ResolvedPlaceView[]
@@ -8,8 +8,9 @@
8
8
 
9
9
  import type { ReactNode } from "react"
10
10
 
11
+ import type { ParsedComponent } from "#pipeline/types"
12
+
11
13
  import { ConfidenceCell } from "./ConfidenceCell.tsx"
12
- import type { ParsedComponent } from "./types.ts"
13
14
 
14
15
  export interface ComponentTableProps {
15
16
  nodes: ParsedComponent[]
@@ -7,7 +7,7 @@
7
7
  * at 0.8 / 0.5. Presentational.
8
8
  */
9
9
 
10
- import { cx } from "../common/cx.ts"
10
+ import { cx } from "#common/cx"
11
11
 
12
12
  /**
13
13
  * At or above this the cell is drawn as high-confidence. Presentation only — see docs/src/shared/confidence-tiers.ts.
@@ -11,6 +11,11 @@
11
11
 
12
12
  import type { ReactNode } from "react"
13
13
 
14
+ import { buildParsePayload } from "#pipeline/copy"
15
+ import { PIPELINE_DEFAULT_ADDRESS, PIPELINE_PRESETS } from "#pipeline/presets"
16
+ import type { PipelinePanels, PipelineRuntime } from "#pipeline/types"
17
+ import { useParsePipeline } from "#pipeline/useParsePipeline"
18
+
14
19
  import { ClientOnly } from "../common/ClientOnly.tsx"
15
20
  import { CopyButton } from "../common/CopyButton.tsx"
16
21
  import { KindBadge } from "../common/KindBadge.tsx"
@@ -18,12 +23,8 @@ import { LoadingIndicator } from "../common/LoadingIndicator.tsx"
18
23
  import { PresetChips, type Preset } from "../common/PresetChips.tsx"
19
24
  import { CandidatePicker } from "./CandidatePicker.tsx"
20
25
  import { ComponentTable } from "./ComponentTable.tsx"
21
- import { buildParsePayload } from "./copy.ts"
22
- import { PIPELINE_DEFAULT_ADDRESS, PIPELINE_PRESETS } from "./presets.ts"
23
26
  import { QueryForm } from "./QueryForm.tsx"
24
27
  import { ResolvedPlace } from "./ResolvedPlace.tsx"
25
- import type { PipelinePanels, PipelineRuntime } from "./types.ts"
26
- import { useParsePipeline } from "./useParsePipeline.ts"
27
28
 
28
29
  export interface PipelineExplorerProps {
29
30
  /**
@@ -19,11 +19,11 @@ export interface QueryFormProps {
19
19
  busy?: boolean
20
20
  placeholder?: string
21
21
  /**
22
- * Keydown on the input — the seam a combobox (the demo's place-autocomplete) needs for arrow/Enter/Esc nav.
22
+ * Keydown on the input — the hook a combobox (the demo's place-autocomplete) needs for arrow/Enter/Esc nav.
23
23
  */
24
24
  onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void
25
25
  /**
26
- * Escape hatch for extra `<input>` attributes (aria-combobox props, `autoComplete`, …). Merged last.
26
+ * Override for extra `<input>` attributes (aria-combobox props, `autoComplete`, …). Merged last.
27
27
  */
28
28
  inputProps?: InputHTMLAttributes<HTMLInputElement>
29
29
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { Fragment, type ReactNode } from "react"
11
11
 
12
- import type { DualRoleView, ResolvedPlaceView } from "./types.ts"
12
+ import type { DualRoleView, ResolvedPlaceView } from "#pipeline/types"
13
13
 
14
14
  export interface ResolvedPlaceProps {
15
15
  place: ResolvedPlaceView
@@ -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 "./types.ts"
10
+ import type { ParseResult, ResolvedPlaceView } from "#pipeline/types"
11
11
 
12
12
  export function buildParsePayload(result: ParseResult, selected: ResolvedPlaceView | null): string {
13
13
  return JSON.stringify(
@@ -30,6 +30,7 @@ export const PIPELINE_PRESETS: ReadonlyArray<Preset> = [
30
30
  { label: "Space Needle", value: "400 Broad St, Seattle, WA 98109", country: "us" },
31
31
  { label: "ZIP only", value: "90210", country: "us" },
32
32
  { label: "Berlin (native order)", value: "Straußstraße 27, 12623 Berlin", country: "de" },
33
+ { label: "Berlin city-state (int'l order)", value: "5 Hauptstraße, Berlin, Berlin 10115", country: "de" },
33
34
  { label: "Paris (street fall-through)", value: "181 Rue du Chevaleret, Paris", country: "fr" },
34
35
  // GB dependent_locality (placetype-pair-prior arc) — "Henbury" flips to dependent_locality via the en-gb pair-index
35
36
  // prior; the UK postcode is also structurally detectable (parity with docs EXAMPLE_ADDRESSES).
@@ -40,7 +41,7 @@ export const PIPELINE_PRESETS: ReadonlyArray<Preset> = [
40
41
  },
41
42
  // NZ dependent_locality (en-nz pair-prior arc) — Plimmerton is a suburb (dependent_locality) of Porirua. Postcode
42
43
  // DELIBERATELY OMITTED: a trailing "Porirua 5026" folds "porirua 5026" in segment mode and misses the index's bare
43
- // "porirua" key (tracked as #1308). The `country: "nz"` pin is load-bearing — locale-gate can't structurally detect
44
+ // "porirua" key (tracked as #1308). The `country: "nz"` pin is required — locale-hint can't structurally detect
44
45
  // NZ (4-digit postcode isn't distinctive), so only the pin selects the nz index; free-typed NZ stays unfired.
45
46
  { label: "Plimmerton (NZ dependent_locality)", value: "35 Steyne Avenue, Plimmerton, Porirua", country: "nz" },
46
47
  ]
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { useCallback, useState } from "react"
13
13
 
14
- import type { ParseResult, PipelineRuntime, ResolvedPlaceView } from "./types.ts"
14
+ import type { ParseResult, PipelineRuntime, ResolvedPlaceView } from "#pipeline/types"
15
15
 
16
16
  export interface UseParsePipelineOptions {
17
17
  runtime: PipelineRuntime
@@ -10,8 +10,8 @@
10
10
 
11
11
  import type { ReactNode } from "react"
12
12
 
13
- import { formatDistance } from "./runtime.ts"
14
- import type { LiveSearchState } from "./types.ts"
13
+ import { formatDistance } from "#poi/runtime"
14
+ import type { LiveSearchState } from "#poi/types"
15
15
 
16
16
  export interface LiveResultsBlockProps {
17
17
  /**
@@ -11,6 +11,10 @@
11
11
 
12
12
  import { type ReactNode, useState } from "react"
13
13
 
14
+ import { POI_DEFAULT_TEXT, POI_PRESETS } from "#poi/runtime"
15
+ import type { LoadPOIRuntime, POILiveSearch } from "#poi/types"
16
+ import { usePOISearch } from "#poi/usePOISearch"
17
+
14
18
  import { ClientOnly } from "../common/ClientOnly.tsx"
15
19
  import { KindBadge } from "../common/KindBadge.tsx"
16
20
  import { PresetChips, type Preset } from "../common/PresetChips.tsx"
@@ -18,10 +22,7 @@ import { AbstainPanel } from "./AbstainPanel.tsx"
18
22
  import { LiveResultsBlock } from "./LiveResultsBlock.tsx"
19
23
  import { OverpassBlock } from "./OverpassBlock.tsx"
20
24
  import { QueryInput } from "./QueryInput.tsx"
21
- import { POI_DEFAULT_TEXT, POI_PRESETS } from "./runtime.ts"
22
25
  import { SubjectPanel } from "./SubjectPanel.tsx"
23
- import type { LoadPOIRuntime, POILiveSearch } from "./types.ts"
24
- import { usePOISearch } from "./usePOISearch.ts"
25
26
 
26
27
  export interface POIExplorerProps {
27
28
  /**
@@ -10,7 +10,7 @@
10
10
 
11
11
  import type { ReactNode } from "react"
12
12
 
13
- import type { POISubject } from "./types.ts"
13
+ import type { POISubject } from "#poi/types"
14
14
 
15
15
  export interface SubjectPanelProps {
16
16
  subject: POISubject
@@ -13,7 +13,7 @@ import { createKindClassifier } from "@mailwoman/kind-classifier"
13
13
  import type { POIPhraseLookup } from "@mailwoman/kind-classifier"
14
14
  import { createPOIBrandLookup, createPOITaxonomyLookup } from "@mailwoman/poi-taxonomy/table"
15
15
 
16
- import type { POIRuntime } from "./types.ts"
16
+ import type { POIRuntime } from "#poi/types"
17
17
 
18
18
  /**
19
19
  * Build the POI runtime, dynamically importing the taxonomy + brand JSON so the tables land in their own chunk. A
@@ -31,8 +31,8 @@ export async function loadPOIRuntime(): Promise<POIRuntime> {
31
31
  import("@mailwoman/poi-taxonomy/data/brands.json").then((m) => m.default),
32
32
  ])
33
33
 
34
- const lookup = createPOITaxonomyLookup(table as unknown as Parameters<typeof createPOITaxonomyLookup>[0])
35
- const brands = createPOIBrandLookup(brandTable as unknown as Parameters<typeof createPOIBrandLookup>[0])
34
+ const lookup = createPOITaxonomyLookup(table as Parameters<typeof createPOITaxonomyLookup>[0])
35
+ const brands = createPOIBrandLookup(brandTable as Parameters<typeof createPOIBrandLookup>[0])
36
36
 
37
37
  // Adapt the taxonomy + brand lookups to the `POIPhraseLookup` shape the classifier expects: categories first, brands
38
38
  // on a category miss.
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Types for the POI explorer. The intent path (classification → subject → OverpassQL) is fully
7
7
  * self-contained over the pure `@mailwoman/*` packages; the live poi.db path is expressed only as an
8
- * INJECTED async function ({@link POILiveSearch}) so the httpvfs/worker machinery stays out of this
8
+ * INJECTED async function ({@link POILiveSearch}) so the httpvfs/worker implementation stays out of this
9
9
  * package's browser graph (it lives in the docs site, which knows where the layer is served).
10
10
  */
11
11
 
@@ -13,11 +13,11 @@ import { matchPOISubject } from "@mailwoman/kind-classifier"
13
13
  import { emitOverpassQL } from "@mailwoman/poi-taxonomy/overpass"
14
14
  import type { OverpassIntentLike } from "@mailwoman/poi-taxonomy/overpass"
15
15
  import { computeQueryShape } from "@mailwoman/query-shape"
16
- import { useCallback, useEffect, useRef, useState } from "react"
16
+ import { useCallback, useEffect, useEffectEvent, useState } from "react"
17
17
 
18
- import { useDebouncedValue } from "../common/useDebouncedValue.ts"
19
- import { loadPOIRuntime } from "./runtime.ts"
20
- import type { LiveSearchState, LoadPOIRuntime, POIExplorerResult, POILiveSearch, POIRuntime } from "./types.ts"
18
+ import { useDebouncedValue } from "#common/useDebouncedValue"
19
+ import { loadPOIRuntime } from "#poi/runtime"
20
+ import type { LiveSearchState, LoadPOIRuntime, POIExplorerResult, POILiveSearch, POIRuntime } from "#poi/types"
21
21
 
22
22
  export interface UsePOISearchOptions {
23
23
  /**
@@ -84,12 +84,12 @@ function buildOverpass(
84
84
  }
85
85
 
86
86
  const intent: OverpassIntentLike = {
87
- subject: { kind: "category", categoryID, matched: matchedPhrase },
87
+ subject: { kind: "category", categoryIDs: [categoryID], matched: matchedPhrase },
88
88
  ...(remainder ? { anchor: { text: remainder } } : {}),
89
89
  }
90
90
 
91
91
  try {
92
- return { overpassQL: emitOverpassQL(intent, category.osmTag ? { osmTag: category.osmTag } : {}) }
92
+ return { overpassQL: emitOverpassQL(intent, category.osmTag ? { osmTags: [category.osmTag] } : {}) }
93
93
  } catch (error) {
94
94
  return { overpassError: error instanceof Error ? error.message : String(error) }
95
95
  }
@@ -103,21 +103,31 @@ export function usePOISearch({
103
103
  debounceMs = 250,
104
104
  }: UsePOISearchOptions): UsePOISearch {
105
105
  const [runtime, setRuntime] = useState<POIRuntime | null>(null)
106
- const [result, setResult] = useState<POIExplorerResult | null>(null)
107
- const [liveSearch, setLiveSearch] = useState<LiveSearchState>({ status: "idle" })
106
+ /**
107
+ * The classify result KEYED BY the query that produced it. The visible result is derived during render
108
+ * (`storedResult.query === trimmedText ? … : null`), so a new query invalidates the old answer by derivation — the
109
+ * effect never writes state synchronously to "reset", which is the react(set-state-in-effect) shape the lint bump
110
+ * rightly flags.
111
+ */
112
+ const [storedResult, setStoredResult] = useState<{ query: string; value: POIExplorerResult } | null>(null)
113
+ /**
114
+ * Live-search state, keyed the same way: launched FOR a query, visible only while that query stands.
115
+ */
116
+ const [storedLive, setStoredLive] = useState<{ query: string; state: LiveSearchState } | null>(null)
108
117
 
109
118
  const debouncedText = useDebouncedValue(text, debounceMs)
119
+ const trimmedText = debouncedText.trim()
110
120
 
111
- // Capture the loader in a ref so the load fires exactly ONCE on mount, regardless of whether the
112
- // caller passes a fresh `loadRuntime` closure each render (an inline `async () => …` would otherwise
113
- // retrigger the effect → reload → re-render loop). The runtime is a load-once resource.
114
- const loadRuntimeRef = useRef(loadRuntime)
115
- loadRuntimeRef.current = loadRuntime
121
+ // The load fires exactly ONCE on mount regardless of whether the caller passes a fresh `loadRuntime`
122
+ // closure each render (an inline `async () => …` would otherwise retrigger the effect → reload →
123
+ // re-render loop). `useEffectEvent` reads the LATEST closure without joining the dependency list —
124
+ // the runtime is a load-once resource.
125
+ const loadRuntimeEvent = useEffectEvent(() => loadRuntime())
116
126
 
117
127
  useEffect(() => {
118
128
  let cancelled = false
119
129
 
120
- loadRuntimeRef.current().then((loaded) => {
130
+ void loadRuntimeEvent().then((loaded) => {
121
131
  if (!cancelled) {
122
132
  setRuntime(loaded)
123
133
  }
@@ -129,20 +139,16 @@ export function usePOISearch({
129
139
  }, [])
130
140
 
131
141
  // Classify the debounced query and derive the subject + OverpassQL (async, so it lives in an effect).
142
+ // Every state write below happens after an await — invalidation on query change is handled by the
143
+ // key derivation above, not by a synchronous reset here.
132
144
  useEffect(() => {
133
145
  if (!runtime) return
134
146
 
135
- let cancelled = false
136
- const trimmed = debouncedText.trim()
137
-
138
- // A new query invalidates live results from the previous one.
139
- setLiveSearch({ status: "idle" })
147
+ const trimmed = trimmedText
140
148
 
141
- if (!trimmed) {
142
- setResult(null)
149
+ if (!trimmed) return
143
150
 
144
- return
145
- }
151
+ let cancelled = false
146
152
 
147
153
  const input = { raw: trimmed, normalized: trimmed }
148
154
  const shape = computeQueryShape(trimmed)
@@ -153,7 +159,7 @@ export function usePOISearch({
153
159
  const matched = kindResult.kind === "poi_query" ? matchPOISubject(trimmed, undefined, runtime.lexicon) : null
154
160
 
155
161
  if (!matched) {
156
- setResult({ kindResult })
162
+ setStoredResult({ query: trimmed, value: { kindResult } })
157
163
 
158
164
  return
159
165
  }
@@ -161,15 +167,18 @@ export function usePOISearch({
161
167
  // Brand subject: the lexicon carries the brand's canonical name as `categoryID` + its Wikidata QID. No category
162
168
  // record, no OverpassQL (brands are searched by QID against the layer's `brand_wikidata` index, not OSM tags).
163
169
  if ((matched.match.kind ?? "category") === "brand") {
164
- setResult({
165
- kindResult,
166
- subject: {
167
- kind: "brand",
168
- name: matched.match.categoryID,
169
- ...(matched.match.wikidata ? { wikidata: matched.match.wikidata } : {}),
170
- matchedPhrase: matched.match.matchedPhrase,
171
- confidence: matched.match.confidence,
172
- remainder: matched.remainder,
170
+ setStoredResult({
171
+ query: trimmed,
172
+ value: {
173
+ kindResult,
174
+ subject: {
175
+ kind: "brand",
176
+ name: matched.match.categoryID,
177
+ ...(matched.match.wikidata ? { wikidata: matched.match.wikidata } : {}),
178
+ matchedPhrase: matched.match.matchedPhrase,
179
+ confidence: matched.match.confidence,
180
+ remainder: matched.remainder,
181
+ },
173
182
  },
174
183
  })
175
184
 
@@ -179,29 +188,37 @@ export function usePOISearch({
179
188
  const category = runtime.lookup.getPOICategory(matched.match.categoryID)
180
189
 
181
190
  if (!category) {
182
- setResult({ kindResult })
191
+ setStoredResult({ query: trimmed, value: { kindResult } })
183
192
 
184
193
  return
185
194
  }
186
195
 
187
- setResult({
188
- kindResult,
189
- subject: {
190
- kind: "category",
191
- category,
192
- matchedPhrase: matched.match.matchedPhrase,
193
- confidence: matched.match.confidence,
194
- remainder: matched.remainder,
195
- buildLocal: runtime.lookup.requiresBuildLocalLayer(category),
196
+ setStoredResult({
197
+ query: trimmed,
198
+ value: {
199
+ kindResult,
200
+ subject: {
201
+ kind: "category",
202
+ category,
203
+ matchedPhrase: matched.match.matchedPhrase,
204
+ confidence: matched.match.confidence,
205
+ remainder: matched.remainder,
206
+ buildLocal: runtime.lookup.requiresBuildLocalLayer(category),
207
+ },
208
+ ...buildOverpass(runtime, matched.match.categoryID, matched.match.matchedPhrase, matched.remainder),
196
209
  },
197
- ...buildOverpass(runtime, matched.match.categoryID, matched.match.matchedPhrase, matched.remainder),
198
210
  })
199
211
  })
200
212
 
201
213
  return () => {
202
214
  cancelled = true
203
215
  }
204
- }, [debouncedText, runtime])
216
+ }, [trimmedText, runtime])
217
+
218
+ // Derived visibility: an answer stands only while its query does. A new query reads as null/idle
219
+ // with no reset write anywhere.
220
+ const result = storedResult?.query === trimmedText ? storedResult.value : null
221
+ const liveSearch: LiveSearchState = storedLive?.query === trimmedText ? storedLive.state : { status: "idle" }
205
222
 
206
223
  const subject = result?.subject
207
224
 
@@ -221,7 +238,7 @@ export function usePOISearch({
221
238
 
222
239
  if (subject.kind === "category" ? subject.buildLocal : !(brandLiveSearch && subject.wikidata)) return
223
240
 
224
- setLiveSearch({ status: "loading" })
241
+ setStoredLive({ query: trimmedText, state: { status: "loading" } })
225
242
 
226
243
  try {
227
244
  const outcome = await runLiveSearch(
@@ -241,16 +258,25 @@ export function usePOISearch({
241
258
  )
242
259
 
243
260
  if (outcome.status === "success") {
244
- setLiveSearch({ status: "success", hits: outcome.hits, centerName: outcome.centerName })
261
+ setStoredLive({
262
+ query: trimmedText,
263
+ state: { status: "success", hits: outcome.hits, centerName: outcome.centerName },
264
+ })
245
265
  } else if (outcome.status === "unplaced") {
246
- setLiveSearch({ status: "error", message: `couldn't place "${outcome.anchor}"` })
266
+ setStoredLive({ query: trimmedText, state: { status: "error", message: `couldn't place "${outcome.anchor}"` } })
247
267
  } else {
248
- setLiveSearch({ status: "error", message: "the published POI layer isn't reachable" })
268
+ setStoredLive({
269
+ query: trimmedText,
270
+ state: { status: "error", message: "the published POI layer isn't reachable" },
271
+ })
249
272
  }
250
273
  } catch {
251
- setLiveSearch({ status: "error", message: "the published POI layer isn't reachable" })
274
+ setStoredLive({
275
+ query: trimmedText,
276
+ state: { status: "error", message: "the published POI layer isn't reachable" },
277
+ })
252
278
  }
253
- }, [runLiveSearch, runtime, subject, brandLiveSearch])
279
+ }, [runLiveSearch, runtime, subject, brandLiveSearch, trimmedText])
254
280
 
255
281
  return { runtimeReady: runtime !== null, result, liveSearch, canSearchLive, searchLive }
256
282
  }
@@ -10,7 +10,7 @@
10
10
  * WOF opener, the FST fetch, the releases.json fetch) are INJECTED by the host as async functions, so
11
11
  * this module imports only React: no `onnxruntime-web`, no `sql.js-httpvfs`, no `maplibre-gl`, no
12
12
  * `fetch`-specific plumbing. That keeps it node-import-safe and root-exportable from
13
- * `@mailwoman/react` — the exact seam `PipelineRuntime` established, generalized to the loader itself.
13
+ * `@mailwoman/react` — the exact interface `PipelineRuntime` established, generalized to the loader itself.
14
14
  *
15
15
  * The hook is generic over `TAssets` (the opaque bundle the host's `loadAssets` returns — classifier,
16
16
  * FST, WOF lookup, calibrator, …) and `TRelease` (the host's release-manifest entry). The package
@@ -169,7 +169,7 @@ export interface DemoLoaderState<TAssets, TRelease extends DemoReleaseBase = Dem
169
169
  *
170
170
  * Sequence: on mount `loadManifest` runs and its `defaultVersion` becomes the selection; each version (or `forceWASM`)
171
171
  * change reloads the bundle via `loadAssets`, the previous load aborted first. The assets are revealed ATOMICALLY when
172
- * `loadAssets` resolves (so `ready` flips exactly once per load), and consumers gate on `ready`.
172
+ * `loadAssets` resolves (so `ready` flips exactly once per load), and consumers wait on `ready`.
173
173
  */
174
174
  export function useDemoRuntime<TAssets, TRelease extends DemoReleaseBase = DemoReleaseBase>(
175
175
  config: DemoRuntimeConfig<TAssets, TRelease>
@@ -189,15 +189,18 @@ export function useDemoRuntime<TAssets, TRelease extends DemoReleaseBase = DemoR
189
189
  // Latest-ref the injected loaders: a host that re-creates them each render (an inline arrow) must NOT retrigger the
190
190
  // load effects, which key ONLY on version/backend. The effects read `.current` at run time.
191
191
  const loadManifestRef = useRef(config.loadManifest)
192
- loadManifestRef.current = config.loadManifest
193
192
  const loadAssetsRef = useRef(config.loadAssets)
194
- loadAssetsRef.current = config.loadAssets
195
193
 
196
194
  // Latest manifest for the version-load effect, so it can resolve the release WITHOUT depending on `manifest`
197
195
  // identity — which would double-fire the load the instant the manifest first arrives (the selection transition
198
196
  // null → defaultVersion already fires it once).
199
197
  const manifestRef = useRef<DemoManifest<TRelease> | null>(null)
200
- manifestRef.current = manifest
198
+
199
+ useEffect(() => {
200
+ loadManifestRef.current = config.loadManifest
201
+ loadAssetsRef.current = config.loadAssets
202
+ manifestRef.current = manifest
203
+ }, [config.loadAssets, config.loadManifest, manifest])
201
204
 
202
205
  // Mount: fetch the manifest, then select the default version.
203
206
  useEffect(() => {
@@ -8,8 +8,11 @@
8
8
  * Remix, Docusaurus). It renders `fallback` on the server and the first client paint, then swaps to
9
9
  * `children()` once mounted — keeping timers, clipboard, and dynamic imports off the server render.
10
10
  *
11
- * The mount flag is derived from a `useEffect` that fires only in the browser; this is a legitimate
12
- * external-sync effect (bridging the server/client rendering boundary), not derived state.
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.
13
16
  */
14
17
  import { type ReactNode } from "react";
15
18
  export interface ClientOnlyProps {
@@ -1 +1 @@
1
- {"version":3,"file":"ClientOnly.d.ts","sourceRoot":"","sources":["../../common/ClientOnly.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAA;AAE3D,MAAM,WAAW,eAAe;IAC/B;;OAEG;IACH,QAAQ,EAAE,MAAM,SAAS,CAAA;IACzB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAe,EAAE,EAAE,eAAe,GAAG,SAAS,CAMpF"}
1
+ {"version":3,"file":"ClientOnly.d.ts","sourceRoot":"","sources":["../../lib/common/ClientOnly.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAA;AAS5D,MAAM,WAAW,eAAe;IAC/B;;OAEG;IACH,QAAQ,EAAE,MAAM,SAAS,CAAA;IACzB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAA;CACpB;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAe,EAAE,EAAE,eAAe,GAAG,SAAS,CAQpF"}
@@ -8,13 +8,21 @@
8
8
  * Remix, Docusaurus). It renders `fallback` on the server and the first client paint, then swaps to
9
9
  * `children()` once mounted — keeping timers, clipboard, and dynamic imports off the server render.
10
10
  *
11
- * The mount flag is derived from a `useEffect` that fires only in the browser; this is a legitimate
12
- * external-sync effect (bridging the server/client rendering boundary), not derived state.
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.
13
16
  */
14
- import { useEffect, useState } from "react";
17
+ import { useSyncExternalStore } from "react";
18
+ /**
19
+ * The boundary never changes after mount, so nothing ever notifies.
20
+ */
21
+ function subscribeNever() {
22
+ return () => { };
23
+ }
15
24
  export function ClientOnly({ children, fallback = null }) {
16
- const [mounted, setMounted] = useState(false);
17
- useEffect(() => setMounted(true), []);
25
+ const mounted = useSyncExternalStore(subscribeNever, () => true, () => false);
18
26
  return mounted ? children() : fallback;
19
27
  }
20
28
  //# sourceMappingURL=ClientOnly.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClientOnly.js","sourceRoot":"","sources":["../../common/ClientOnly.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAkB,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAa3D,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,EAAmB;IACxE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE7C,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAA;IAErC,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;AACvC,CAAC"}
1
+ {"version":3,"file":"ClientOnly.js","sourceRoot":"","sources":["../../lib/common/ClientOnly.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAkB,oBAAoB,EAAE,MAAM,OAAO,CAAA;AAE5D;;GAEG;AACH,SAAS,cAAc;IACtB,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;AAChB,CAAC;AAaD,MAAM,UAAU,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,EAAmB;IACxE,MAAM,OAAO,GAAG,oBAAoB,CACnC,cAAc,EACd,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACX,CAAA;IAED,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAA;AACvC,CAAC"}