cursedbelt 5.93.0 → 5.95.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 (95) hide show
  1. package/dist/react/components/AppShell.d.ts +12 -1
  2. package/dist/react/components/AppShell.d.ts.map +1 -1
  3. package/dist/react/components/AppShell.js +24 -5
  4. package/dist/react/components/AppShell.js.map +1 -1
  5. package/dist/react/components/ComboboxField.js +2 -2
  6. package/dist/react/components/ComboboxField.js.map +1 -1
  7. package/dist/react/components/CommentThread.d.ts +9 -3
  8. package/dist/react/components/CommentThread.d.ts.map +1 -1
  9. package/dist/react/components/CommentThread.js +25 -11
  10. package/dist/react/components/CommentThread.js.map +1 -1
  11. package/dist/react/components/DatePicker.d.ts +9 -5
  12. package/dist/react/components/DatePicker.d.ts.map +1 -1
  13. package/dist/react/components/DatePicker.js +192 -27
  14. package/dist/react/components/DatePicker.js.map +1 -1
  15. package/dist/react/components/InlineEditableText.d.ts +12 -2
  16. package/dist/react/components/InlineEditableText.d.ts.map +1 -1
  17. package/dist/react/components/InlineEditableText.js +11 -8
  18. package/dist/react/components/InlineEditableText.js.map +1 -1
  19. package/dist/react/components/VoiceNote.d.ts +20 -0
  20. package/dist/react/components/VoiceNote.d.ts.map +1 -0
  21. package/dist/react/components/VoiceNote.js +54 -0
  22. package/dist/react/components/VoiceNote.js.map +1 -0
  23. package/dist/react/components/VoiceRecorder.d.ts +35 -0
  24. package/dist/react/components/VoiceRecorder.d.ts.map +1 -0
  25. package/dist/react/components/VoiceRecorder.js +155 -0
  26. package/dist/react/components/VoiceRecorder.js.map +1 -0
  27. package/dist/react/components/suggestionList.js +2 -2
  28. package/dist/react/components/suggestionList.js.map +1 -1
  29. package/dist/react/index.d.ts +2 -0
  30. package/dist/react/index.d.ts.map +1 -1
  31. package/dist/react/index.js +2 -0
  32. package/dist/react/index.js.map +1 -1
  33. package/dist/react/lib/dateText.d.ts +21 -0
  34. package/dist/react/lib/dateText.d.ts.map +1 -0
  35. package/dist/react/lib/dateText.js +94 -0
  36. package/dist/react/lib/dateText.js.map +1 -0
  37. package/dist/react/lib/floatingStyles.d.ts +12 -0
  38. package/dist/react/lib/floatingStyles.d.ts.map +1 -1
  39. package/dist/react/lib/floatingStyles.js +12 -0
  40. package/dist/react/lib/floatingStyles.js.map +1 -1
  41. package/dist/react/map-frame/MapFrame.d.ts +219 -0
  42. package/dist/react/map-frame/MapFrame.d.ts.map +1 -0
  43. package/dist/react/map-frame/MapFrame.js +726 -0
  44. package/dist/react/map-frame/MapFrame.js.map +1 -0
  45. package/dist/react/map-frame/geo.d.ts +210 -0
  46. package/dist/react/map-frame/geo.d.ts.map +1 -0
  47. package/dist/react/map-frame/geo.js +345 -0
  48. package/dist/react/map-frame/geo.js.map +1 -0
  49. package/dist/react/map-frame/index.d.ts +5 -0
  50. package/dist/react/map-frame/index.d.ts.map +1 -0
  51. package/dist/react/map-frame/index.js +3 -0
  52. package/dist/react/map-frame/index.js.map +1 -0
  53. package/dist/styles-areas/analytics.css +11 -10
  54. package/dist/styles-areas/canvas-hud.css +5 -5
  55. package/dist/styles-areas/data-table.css +13 -12
  56. package/dist/styles-areas/fields.css +12 -10
  57. package/dist/styles-areas/stats.css +12 -11
  58. package/dist/styles-areas/wizard.css +6 -6
  59. package/dist/styles-areas/workbench.css +17 -17
  60. package/dist/styles-utilities.css +15 -0
  61. package/package.json +13 -1
  62. package/scripts/guardrails-baseline.json +1 -1
  63. package/scripts/styleAreas.ts +1 -1
  64. package/src/react/components/AppShell.spec.tsx +53 -0
  65. package/src/react/components/AppShell.tsx +79 -23
  66. package/src/react/components/ComboboxField.tsx +2 -2
  67. package/src/react/components/CommentThread.spec.tsx +25 -0
  68. package/src/react/components/CommentThread.tsx +60 -10
  69. package/src/react/components/ControlHeightScale.spec.tsx +2 -2
  70. package/src/react/components/DatePicker.spec.tsx +186 -5
  71. package/src/react/components/DatePicker.tsx +416 -63
  72. package/src/react/components/InlineEditableText.spec.tsx +29 -0
  73. package/src/react/components/InlineEditableText.tsx +26 -10
  74. package/src/react/components/VoiceNote.tsx +92 -0
  75. package/src/react/components/VoiceRecorder.spec.tsx +116 -0
  76. package/src/react/components/VoiceRecorder.tsx +224 -0
  77. package/src/react/components/suggestionList.tsx +2 -2
  78. package/src/react/index.ts +9 -0
  79. package/src/react/lib/dateText.ts +94 -0
  80. package/src/react/lib/floatingStyles.spec.ts +36 -1
  81. package/src/react/lib/floatingStyles.ts +13 -0
  82. package/src/react/map-frame/MapFrame.spec.ts +168 -0
  83. package/src/react/map-frame/MapFrame.tsx +954 -0
  84. package/src/react/map-frame/geo.spec.ts +393 -0
  85. package/src/react/map-frame/geo.ts +421 -0
  86. package/src/react/map-frame/index.ts +34 -0
  87. package/src/react/media/HoverScrubVideoThumb.spec.tsx +4 -0
  88. package/src/styles-areas/analytics.css +11 -10
  89. package/src/styles-areas/canvas-hud.css +5 -5
  90. package/src/styles-areas/data-table.css +13 -12
  91. package/src/styles-areas/fields.css +12 -10
  92. package/src/styles-areas/stats.css +12 -11
  93. package/src/styles-areas/wizard.css +6 -6
  94. package/src/styles-areas/workbench.css +17 -17
  95. package/src/styles-utilities.css +15 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * A map: Google's imagery underneath, our own interactive layer on top.
3
+ *
4
+ * cursedbelt/react/map-frame — lifted from apps/family (forge task 3700) so every app draws
5
+ * maps through ONE frame. The imagery comes from the app's own `/api/map` proxy
6
+ * (`imageSource` overrides the URL); `cursedbelt-server/maps-static` is that proxy's shared
7
+ * half. The owner's reports quoted below were made on family's atlas.
8
+ *
9
+ * ── Why not a map library ───────────────────────────────────────────────────
10
+ * Three reasons, and the first is not negotiable:
11
+ *
12
+ * 1. **The key can never reach the browser.** `GOOGLE_MAPS_API_KEY` is
13
+ * restricted by API and NOT by HTTP referrer, so a Maps JavaScript API embed
14
+ * — which requires the key in a script URL — would publish a credential
15
+ * anybody could spend. The imagery is therefore proxied by this app's own
16
+ * `/api/map`.
17
+ * 2. **It is cheaper.** A static render bills at roughly a third of a JS map
18
+ * load, and identical viewports are shared between every viewer by the
19
+ * server's cache.
20
+ * 3. **The overlay is the product.** What makes this a family atlas rather than
21
+ * a map is that a dot is a person you can open, an arc is a journey somebody
22
+ * made, and both are clickable, focusable and announced to a screen reader.
23
+ * Those are our SVG elements either way; a tile engine underneath would only
24
+ * add weight.
25
+ *
26
+ * ── 🔴 The alignment contract ───────────────────────────────────────────────
27
+ * The overlay is only correct if it uses the SAME projection as the imagery. It
28
+ * does — `shared/geo.ts` implements Google's own Web Mercator formula — and the
29
+ * two are locked together by:
30
+ *
31
+ * · one FIXED logical viewport (`VIEW_WIDTH` × `VIEW_HEIGHT`) for both,
32
+ * · `preserveAspectRatio="xMidYMid slice"` on the SVG, which now carries BOTH
33
+ * layers (see "one coordinate system" below),
34
+ * · the same `center` and integer `zoom` behind both.
35
+ *
36
+ * Break any one and every pin drifts from its coastline — more the further from
37
+ * the equator, which is exactly where this family's records are.
38
+ *
39
+ * ── 🔴 Why the imagery is an SVG `<image>` and not a sibling `<img>` ─────────
40
+ * It used to be a CSS-positioned `<img>` under the SVG, kept in step only by
41
+ * `object-fit: cover` matching `slice`. That is two coordinate systems agreeing
42
+ * by construction, which is fine while nothing MOVES — and stops being fine the
43
+ * moment a drag has to translate both by the same amount, because a viewport unit
44
+ * is not a CSS pixel and the conversion depends on the element's measured box.
45
+ * Putting the imagery inside the SVG makes the translation one number in one
46
+ * coordinate system, which is what makes the pan below correct rather than
47
+ * approximately correct.
48
+ *
49
+ * ── 🔴 The drag defect this file was rewritten for ──────────────────────────
50
+ * The owner's report, verbatim: *"Dragging on a map moves the nodes and paths
51
+ * while the map stays still and catches up after dropping."* Both halves of that
52
+ * were one cause. The old `onPointerMove` called `onViewChange` on EVERY move, so:
53
+ *
54
+ * · the SVG overlay re-projected instantly — pins and arcs moved with the finger;
55
+ * · the imagery is a NETWORK FETCH (`/api/map?lat=…&lng=…`), so it could only
56
+ * appear once a round trip completed, one round trip per mouse move, arriving
57
+ * long after the gesture. The map therefore lagged and "caught up" at the end.
58
+ *
59
+ * It was also **spending money on every mouse move**: each distinct center is a
60
+ * separate billable Google Static Maps render, so one drag across the atlas minted
61
+ * dozens of renders against the app's ceiling — for pictures nobody ever
62
+ * saw, because the next move superseded them.
63
+ *
64
+ * The fix is the one every map makes: a drag translates the WHOLE SCENE locally
65
+ * (`pan`, in viewport units, applied to imagery and overlay by a single
66
+ * `translate`), and the viewport is committed exactly ONCE, on release. So a drag
67
+ * is now one render rather than fifty, and the two layers cannot separate because
68
+ * neither one re-projects while the finger is down.
69
+ *
70
+ * ── …and why the released frame does not flash ──────────────────────────────
71
+ * Committing swaps `center`, which changes the image URL, which is a fetch. Naively
72
+ * that blanks the map for as long as the network takes — replacing a lag at the end
73
+ * of the drag with a hole at the end of the drag. So the frame keeps the imagery it
74
+ * has (`settled`) and DRAWS IT WHERE IT BELONGS in the new viewport: same math as
75
+ * a pin, applied to a picture. The old imagery slides out of frame, its blank edge
76
+ * follows the drag, and the new render replaces it when it lands. That is exactly
77
+ * what a tiled map does, without tiles.
78
+ *
79
+ * ── 🔴 Continuous gestures on top of an INTEGER-zoom imagery source ─────────
80
+ * The owner's second report: *"I wish zooming in and out was possible with mouse
81
+ * gestures… mobile should allow touch movements and zoom too."* The obvious fix —
82
+ * fetch a render per wheel tick — is the drag defect above wearing a different
83
+ * hat, and it bills for every frame.
84
+ *
85
+ * So a gesture runs entirely LOCALLY against a fractional zoom, and only the
86
+ * settled value is snapped and committed:
87
+ *
88
+ * · `live` holds the viewport the reader is currently manipulating. Its zoom may
89
+ * be 6.37; `project` and `imagePlacement` are both continuous, so the overlay
90
+ * and the imagery scale together through the whole gesture with no fetch.
91
+ * · the image URL is built from the PROPS — the last COMMITTED integer viewport —
92
+ * so no amount of wheeling or pinching can mint a render.
93
+ * · a wheel commits 220 ms after it stops; a pinch commits on release; a drag
94
+ * commits when it comes to rest. One billable render per gesture, same as before.
95
+ *
96
+ * The zoom is anchored under the cursor (`zoomAnchoredAt`), not about the frame's
97
+ * center, which is the difference between closing in on a town and watching it
98
+ * slide off the edge as you approach it.
99
+ *
100
+ * ── 🔴 …and the fraction SURVIVES the commit (2026-09-11) ───────────────────
101
+ * The imagery takes an integer zoom, so a commit rounds — and a notch is only
102
+ * ~0.26 of a level. Every notch that fell outside the settle window therefore
103
+ * rounded itself away: measured against the counter in
104
+ * `@satellites/maps-platform`, six notches 260 ms apart committed six times and
105
+ * left the map at **exactly the zoom it started on**, and no number of trackpad
106
+ * flicks (whose deltas are a tenth of a notch) could zoom it at all.
107
+ *
108
+ * 🔴 That bug was also the only reason the slow path looked cheap — six commits
109
+ * rounding to one integer are six identical URLs, so five were cache hits. So the
110
+ * two belong together: `wheelLeftover` carries the discarded fraction into the
111
+ * next notch's TARGET (never into the viewport it is anchored against), and
112
+ * family's `FORGE/apps/family/src/components/wheelBilling.test.tsx` pins the gesture's cost and the fact that it moved in
113
+ * the same assertions. Repair one without the other and the atlas starts billing
114
+ * a render per notch, which is the defect this whole section exists to prevent.
115
+ *
116
+ * ── Degrading with no key ───────────────────────────────────────────────────
117
+ * With no imagery the frame draws a graticule and keeps every pin, arc and
118
+ * interaction working. A family atlas with no coastlines is still a diagram of
119
+ * who went where; refusing to draw would be worse.
120
+ */
121
+ import React from "react";
122
+ import { type LatLng, type Viewport } from "./geo.js";
123
+ /**
124
+ * The one logical viewport, for both layers.
125
+ *
126
+ * 640×400 because Google caps a single static render at 640px before `scale`, and
127
+ * a fixed size is what keeps the cache key space tiny — see `/api/map`. The frame
128
+ * is then CSS-scaled to whatever the layout gives it.
129
+ */
130
+ export declare const VIEW_WIDTH = 640;
131
+ export declare const VIEW_HEIGHT = 400;
132
+ /** How long after the last wheel event the viewport is committed — i.e. how long
133
+ * after the reader stops before the sharp render is asked for. Long enough that
134
+ * a multi-notch scroll is ONE render, short enough not to feel sticky.
135
+ *
136
+ * 🔴 Exported so a smoke (family's `__smoke/atlas.spec.ts`) can DERIVE its billing ceiling from it
137
+ * rather than hard-coding a number that has to be loosened every time the Mac
138
+ * gets busy. See that file — a gate whose constant drifts upward under load is
139
+ * a gate that stops guarding the thing it was written for. */
140
+ export declare const WHEEL_SETTLE_MS = 220;
141
+ /** The URL of one rendered viewport. Pure, and exported so a test can name the
142
+ * picture it expects without restating the query string. */
143
+ export declare function mapImageSource(center: LatLng, zoom: number, theme: "light" | "dark"): string;
144
+ /**
145
+ * Where a already-rendered viewport sits inside the one being drawn now.
146
+ *
147
+ * Pure, and the whole reason a committed pan does not flash: it is the same
148
+ * projection a pin gets, applied to a 640×400 picture. `scale` is the zoom
149
+ * difference (a double-click leaves a half-size image covering the middle of the
150
+ * frame until the closer render lands), and the position is the settled center
151
+ * projected into the current view, minus half the scaled picture.
152
+ */
153
+ export declare function imagePlacement(settled: {
154
+ center: LatLng;
155
+ zoom: number;
156
+ }, view: Viewport): {
157
+ x: number;
158
+ y: number;
159
+ width: number;
160
+ height: number;
161
+ };
162
+ /**
163
+ * The viewport a completed drag lands on.
164
+ *
165
+ * Pure, because it is the one piece of the gesture worth pinning in a test: the
166
+ * center moves OPPOSITE the finger, through the projection, so a drag covers the
167
+ * same ground at every zoom.
168
+ */
169
+ export declare function pannedCenter(center: LatLng, zoom: number, pan: {
170
+ dx: number;
171
+ dy: number;
172
+ }): LatLng;
173
+ /**
174
+ * Pure: the part of the logical viewport a `box` of that size actually shows. The SVG is
175
+ * `xMidYMid slice`, so a box narrower than 16:10 crops the sides and a wider one crops top and
176
+ * bottom — a consumer that fits pins to the whole 640×400 would put some of them out of sight on a
177
+ * phone. Fit to this instead (its `width`/`height` are `zoomToFit`'s size), and keep a label inside
178
+ * `x … x + width`. An unmeasured box (0×0) shows the whole viewport.
179
+ */
180
+ export declare function visibleArea(box: {
181
+ width: number;
182
+ height: number;
183
+ }): {
184
+ x: number;
185
+ y: number;
186
+ width: number;
187
+ height: number;
188
+ };
189
+ /** Any other attribute (`data-*`, `aria-describedby`, an `id`) lands on the frame's root `<div>`. */
190
+ export interface MapFrameProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
191
+ center: LatLng;
192
+ zoom: number;
193
+ theme: "light" | "dark";
194
+ /** Drawn inside the SVG, in viewport coordinates. Given `project` so children
195
+ * never re-derive the math. */
196
+ children: (helpers: {
197
+ project: (point: LatLng) => {
198
+ x: number;
199
+ y: number;
200
+ };
201
+ view: Viewport;
202
+ }) => React.ReactNode;
203
+ /** A completed drag or a double-click asks for a new viewport. Absent means the
204
+ * map is fixed — which is right for a thumbnail and wrong for the atlas.
205
+ *
206
+ * 🔴 Called ONCE per gesture, on release — never per pointer move. See the
207
+ * file header: a call here is a billable render. */
208
+ onViewChange?: (next: {
209
+ center: LatLng;
210
+ zoom: number;
211
+ }) => void;
212
+ /** Announced to assistive technology. The map is decorative without one. */
213
+ label: string;
214
+ /** The URL of one rendered viewport. Defaults to {@link mapImageSource} — the app's own
215
+ * `/api/map` proxy, which `cursedbelt-server/maps-static` serves the shared half of. */
216
+ imageSource?: (center: LatLng, zoom: number, theme: "light" | "dark") => string;
217
+ }
218
+ export declare function MapFrame({ center, zoom, theme, children, onViewChange, label, className, style, imageSource, ...rest }: MapFrameProps): React.JSX.Element;
219
+ //# sourceMappingURL=MapFrame.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MapFrame.d.ts","sourceRoot":"","sources":["../../../src/react/map-frame/MapFrame.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuHG;AACH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACN,KAAK,MAAM,EAOX,KAAK,QAAQ,EAEb,MAAM,UAAU,CAAC;AAElB;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,MAAM,CAAC;AAC9B,eAAO,MAAM,WAAW,MAAM,CAAC;AA2B/B;;;;;;;+DAO+D;AAC/D,eAAO,MAAM,eAAe,MAAM,CAAC;AAyBnC;6DAC6D;AAC7D,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAK5F;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzC,IAAI,EAAE,QAAQ,GACZ;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAMzD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC7B,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAM3H;AAED,qGAAqG;AACrG,MAAM,WAAW,aAAc,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC;IAC5F,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB;oCACgC;IAChC,QAAQ,EAAE,CAAC,OAAO,EAAE;QACnB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrD,IAAI,EAAE,QAAQ,CAAC;KACf,KAAK,KAAK,CAAC,SAAS,CAAC;IACtB;;;;yDAIqD;IACrD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAChE,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd;6FACyF;IACzF,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,MAAM,CAAC;CAChF;AAoBD,wBAAgB,QAAQ,CAAC,EACxB,MAAM,EACN,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,KAAK,EACL,SAAS,EACT,KAAK,EACL,WAA4B,EAC5B,GAAG,IAAI,EACP,EAAE,aAAa,qBA2jBf"}