cursedbelt 5.0.1 → 5.2.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 (137) hide show
  1. package/dist/react/engagement/beacon.d.ts +38 -0
  2. package/dist/react/engagement/beacon.d.ts.map +1 -0
  3. package/dist/react/engagement/beacon.js +62 -0
  4. package/dist/react/engagement/beacon.js.map +1 -0
  5. package/dist/react/engagement/beaconClient.d.ts +119 -0
  6. package/dist/react/engagement/beaconClient.d.ts.map +1 -0
  7. package/dist/react/engagement/beaconClient.js +203 -0
  8. package/dist/react/engagement/beaconClient.js.map +1 -0
  9. package/dist/react/engagement/index.d.ts +3 -0
  10. package/dist/react/engagement/index.d.ts.map +1 -0
  11. package/dist/react/engagement/index.js +5 -0
  12. package/dist/react/engagement/index.js.map +1 -0
  13. package/dist/react/lib/directUpload.d.ts +130 -0
  14. package/dist/react/lib/directUpload.d.ts.map +1 -0
  15. package/dist/react/lib/directUpload.js +228 -0
  16. package/dist/react/lib/directUpload.js.map +1 -0
  17. package/dist/react/media/FocusViewer.d.ts.map +1 -1
  18. package/dist/react/media/FocusViewer.js.map +1 -1
  19. package/dist/react/media/MediaThumb.d.ts.map +1 -1
  20. package/dist/react/media/MediaThumb.js.map +1 -1
  21. package/dist/react/media/VideoTurner.d.ts.map +1 -1
  22. package/dist/react/media/VideoTurner.js.map +1 -1
  23. package/dist/react/media-gallery/GalleryTable.js.map +1 -1
  24. package/dist/react/media-gallery/MediaGallery.d.ts.map +1 -1
  25. package/dist/react/media-gallery/MediaGallery.js.map +1 -1
  26. package/dist/styles-areas/activity-bar.css +8 -11
  27. package/dist/styles-areas/analytics.css +30 -653
  28. package/dist/styles-areas/auth.css +10 -135
  29. package/dist/styles-areas/calendar.css +22 -397
  30. package/dist/styles-areas/chart-table.css +8 -20
  31. package/dist/styles-areas/charts.css +9 -70
  32. package/dist/styles-areas/chat.css +17 -345
  33. package/dist/styles-areas/clipboard.css +7 -7
  34. package/dist/styles-areas/code-editor.css +8 -111
  35. package/dist/styles-areas/companion-link.css +8 -51
  36. package/dist/styles-areas/core.css +131 -2488
  37. package/dist/styles-areas/dashboard-grid.css +10 -65
  38. package/dist/styles-areas/data-table.css +30 -638
  39. package/dist/styles-areas/deep-link.css +7 -7
  40. package/dist/styles-areas/diff-viewer.css +8 -11
  41. package/dist/styles-areas/disk-usage.css +13 -243
  42. package/dist/styles-areas/emoji.css +9 -135
  43. package/dist/styles-areas/fields.css +32 -563
  44. package/dist/styles-areas/file-tree.css +8 -14
  45. package/dist/styles-areas/filter-rail.css +14 -216
  46. package/dist/styles-areas/folder-tree.css +12 -200
  47. package/dist/styles-areas/keep-awake.css +7 -7
  48. package/dist/styles-areas/layout-engine.css +12 -220
  49. package/dist/styles-areas/markdown.css +9 -68
  50. package/dist/styles-areas/master-detail.css +14 -220
  51. package/dist/styles-areas/master-lock.css +9 -113
  52. package/dist/styles-areas/media-gallery.css +15 -332
  53. package/dist/styles-areas/media.css +20 -623
  54. package/dist/styles-areas/nav.css +24 -469
  55. package/dist/styles-areas/notifications.css +12 -226
  56. package/dist/styles-areas/overlays.css +14 -114
  57. package/dist/styles-areas/palette.css +15 -121
  58. package/dist/styles-areas/pdf-viewer.css +8 -28
  59. package/dist/styles-areas/problem-detail.css +9 -78
  60. package/dist/styles-areas/rich-text.css +9 -148
  61. package/dist/styles-areas/sharing.css +13 -134
  62. package/dist/styles-areas/spreadsheet.css +8 -18
  63. package/dist/styles-areas/stats.css +19 -262
  64. package/dist/styles-areas/test-report.css +13 -114
  65. package/dist/styles-areas/virtual.css +8 -11
  66. package/dist/styles-areas/wizard.css +21 -362
  67. package/dist/styles-areas/workbench.css +27 -524
  68. package/dist/styles-areas/workbook-viewer.css +8 -25
  69. package/dist/styles-static.css +1 -0
  70. package/dist/styles.css +1 -0
  71. package/package.json +20 -2
  72. package/scripts/checkAreaStyles.spec.ts +158 -1
  73. package/scripts/checkAreaStyles.ts +226 -6
  74. package/scripts/cssRules.ts +8 -1
  75. package/scripts/gate.ts +1 -1
  76. package/scripts/generateAreaStyles.ts +130 -10
  77. package/scripts/paths.ts +1 -1
  78. package/scripts/styleAreas.ts +2 -0
  79. package/src/react/components/ActionBar.spec.tsx +1 -0
  80. package/src/react/engagement/beacon.tsx +63 -0
  81. package/src/react/engagement/beaconClient.spec.ts +138 -0
  82. package/src/react/engagement/beaconClient.ts +228 -0
  83. package/src/react/engagement/index.ts +13 -0
  84. package/src/react/file-tree/FileTree.tsx +3 -3
  85. package/src/react/lib/directUpload.spec.ts +128 -0
  86. package/src/react/lib/directUpload.ts +334 -0
  87. package/src/react/media/FocusViewer.tsx +1 -0
  88. package/src/react/media/MediaThumb.tsx +0 -1
  89. package/src/react/media/VideoTurner.tsx +0 -1
  90. package/src/react/media-gallery/GalleryTable.tsx +0 -1
  91. package/src/react/media-gallery/MediaGallery.tsx +0 -1
  92. package/src/styles-areas/activity-bar.css +8 -11
  93. package/src/styles-areas/analytics.css +30 -653
  94. package/src/styles-areas/auth.css +10 -135
  95. package/src/styles-areas/calendar.css +22 -397
  96. package/src/styles-areas/chart-table.css +8 -20
  97. package/src/styles-areas/charts.css +9 -70
  98. package/src/styles-areas/chat.css +17 -345
  99. package/src/styles-areas/clipboard.css +7 -7
  100. package/src/styles-areas/code-editor.css +8 -111
  101. package/src/styles-areas/companion-link.css +8 -51
  102. package/src/styles-areas/core.css +131 -2488
  103. package/src/styles-areas/dashboard-grid.css +10 -65
  104. package/src/styles-areas/data-table.css +30 -638
  105. package/src/styles-areas/deep-link.css +7 -7
  106. package/src/styles-areas/diff-viewer.css +8 -11
  107. package/src/styles-areas/disk-usage.css +13 -243
  108. package/src/styles-areas/emoji.css +9 -135
  109. package/src/styles-areas/fields.css +32 -563
  110. package/src/styles-areas/file-tree.css +8 -14
  111. package/src/styles-areas/filter-rail.css +14 -216
  112. package/src/styles-areas/folder-tree.css +12 -200
  113. package/src/styles-areas/keep-awake.css +7 -7
  114. package/src/styles-areas/layout-engine.css +12 -220
  115. package/src/styles-areas/markdown.css +9 -68
  116. package/src/styles-areas/master-detail.css +14 -220
  117. package/src/styles-areas/master-lock.css +9 -113
  118. package/src/styles-areas/media-gallery.css +15 -332
  119. package/src/styles-areas/media.css +20 -623
  120. package/src/styles-areas/nav.css +24 -469
  121. package/src/styles-areas/notifications.css +12 -226
  122. package/src/styles-areas/overlays.css +14 -114
  123. package/src/styles-areas/palette.css +15 -121
  124. package/src/styles-areas/pdf-viewer.css +8 -28
  125. package/src/styles-areas/problem-detail.css +9 -78
  126. package/src/styles-areas/rich-text.css +9 -148
  127. package/src/styles-areas/sharing.css +13 -134
  128. package/src/styles-areas/spreadsheet.css +8 -18
  129. package/src/styles-areas/stats.css +19 -262
  130. package/src/styles-areas/test-report.css +13 -114
  131. package/src/styles-areas/virtual.css +8 -11
  132. package/src/styles-areas/wizard.css +21 -362
  133. package/src/styles-areas/workbench.css +27 -524
  134. package/src/styles-areas/workbook-viewer.css +8 -25
  135. package/src/styles-static.css +1 -0
  136. package/src/styles.css +1 -0
  137. package/src/stylesAreas.spec.ts +12 -7
@@ -0,0 +1,38 @@
1
+ import { type ReportPlaceOptions } from "./beaconClient.js";
2
+ /**
3
+ * Report `place` whenever it changes.
4
+ *
5
+ * `place` should be the app's own route-manifest id — the same string the
6
+ * server resolves against. Pass `null` while the route is unresolved (a lazy
7
+ * view still loading, a signed-out shell) rather than a placeholder: a
8
+ * placeholder becomes a real row in the console.
9
+ */
10
+ export declare function useEngagementBeacon(place: string | null, options?: ReportPlaceOptions): void;
11
+ /**
12
+ * Report wherever the browser IS, and keep reporting as it moves.
13
+ *
14
+ * This is the form every app should use, and it is why adoption is one line
15
+ * rather than a per-app mapping: the client sends its LOCATION and the server
16
+ * resolves it against that app's own route manifest (`policy.ts:resolvePlace`).
17
+ * A `route.view → place id` table written in twelve app shells would be twelve
18
+ * places for the names to drift, and a drifted name reads on the board as a
19
+ * page nobody opens — a wrong answer, not a gap.
20
+ *
21
+ * Listens to `hashchange` and `popstate`, which between them cover both kinds of
22
+ * router — hash-routed apps like this one, and a pushState app. `reportPlace`
23
+ * de-duplicates, so a re-render costs nothing.
24
+ */
25
+ export declare function useLocationBeacon(options?: ReportPlaceOptions): void;
26
+ /**
27
+ * The location beacon as an element, for a shell that mounts declaratively.
28
+ *
29
+ * Deliberately takes no `place` prop. Offering both would mean two callers of
30
+ * one module-level de-duplication memory, and an app that passed a place would
31
+ * silently suppress the location half or vice versa — use
32
+ * {@link useEngagementBeacon} directly when an app really does know its own
33
+ * place ids.
34
+ */
35
+ export declare function EngagementBeacon(props: {
36
+ basePath?: string;
37
+ }): null;
38
+ //# sourceMappingURL=beacon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/beacon.tsx"],"names":[],"mappings":"AAQA,OAAO,EAAE,KAAK,kBAAkB,EAAoC,MAAM,mBAAmB,CAAC;AAE9F;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE,kBAAuB,GAAG,IAAI,CAQhG;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,kBAAuB,GAAG,IAAI,CAMxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGnE"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The React adapter over `beaconClient.ts` — one hook, one component.
3
+ *
4
+ * Separate file from the plain-JS half so an app with no React (or a test with
5
+ * no DOM) can import `reportPlace` without pulling React in, and so the app's
6
+ * own router stays the thing that decides what "a place" is.
7
+ */
8
+ import { useEffect } from "react";
9
+ import { reportPlace, startLocationBeacon } from "./beaconClient.js";
10
+ /**
11
+ * Report `place` whenever it changes.
12
+ *
13
+ * `place` should be the app's own route-manifest id — the same string the
14
+ * server resolves against. Pass `null` while the route is unresolved (a lazy
15
+ * view still loading, a signed-out shell) rather than a placeholder: a
16
+ * placeholder becomes a real row in the console.
17
+ */
18
+ export function useEngagementBeacon(place, options = {}) {
19
+ const basePath = options.basePath;
20
+ useEffect(() => {
21
+ if (!place)
22
+ return;
23
+ // Fire and forget; `reportPlace` never rejects. Not awaited on purpose —
24
+ // a route change must not wait on telemetry.
25
+ void reportPlace(place, basePath ? { basePath } : {});
26
+ }, [place, basePath]);
27
+ }
28
+ /**
29
+ * Report wherever the browser IS, and keep reporting as it moves.
30
+ *
31
+ * This is the form every app should use, and it is why adoption is one line
32
+ * rather than a per-app mapping: the client sends its LOCATION and the server
33
+ * resolves it against that app's own route manifest (`policy.ts:resolvePlace`).
34
+ * A `route.view → place id` table written in twelve app shells would be twelve
35
+ * places for the names to drift, and a drifted name reads on the board as a
36
+ * page nobody opens — a wrong answer, not a gap.
37
+ *
38
+ * Listens to `hashchange` and `popstate`, which between them cover both kinds of
39
+ * router — hash-routed apps like this one, and a pushState app. `reportPlace`
40
+ * de-duplicates, so a re-render costs nothing.
41
+ */
42
+ export function useLocationBeacon(options = {}) {
43
+ const basePath = options.basePath;
44
+ // Delegates to the plain-JS starter so there is ONE implementation of the
45
+ // listener set. Most apps call `startLocationBeacon()` from `main.tsx`
46
+ // instead; this exists for a shell that would rather own the lifetime.
47
+ useEffect(() => startLocationBeacon(basePath ? { basePath } : {}), [basePath]);
48
+ }
49
+ /**
50
+ * The location beacon as an element, for a shell that mounts declaratively.
51
+ *
52
+ * Deliberately takes no `place` prop. Offering both would mean two callers of
53
+ * one module-level de-duplication memory, and an app that passed a place would
54
+ * silently suppress the location half or vice versa — use
55
+ * {@link useEngagementBeacon} directly when an app really does know its own
56
+ * place ids.
57
+ */
58
+ export function EngagementBeacon(props) {
59
+ useLocationBeacon(props.basePath ? { basePath: props.basePath } : {});
60
+ return null;
61
+ }
62
+ //# sourceMappingURL=beacon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon.js","sourceRoot":"","sources":["../../../src/react/engagement/beacon.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAA2B,WAAW,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE9F;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAoB,EAAE,UAA8B,EAAE;IACzF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,yEAAyE;QACzE,6CAA6C;QAC7C,KAAK,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvD,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAAC,UAA8B,EAAE;IACjE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,0EAA0E;IAC1E,uEAAuE;IACvE,uEAAuE;IACvE,SAAS,CAAC,GAAG,EAAE,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC5D,iBAAiB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC;AACb,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `cursedbelt/react/engagement-beacon` — the engagement beacon's plain-JS half (no React). Lifted
3
+ * 2026-09-23 (cursedbelt 5.2.0, task 098-280's browser half) from `src/kit/engagement/beacon.client.ts`,
4
+ * identical in collections, family, music and roms (auth's differed only in a comment). The
5
+ * server half is `cursedbelt-server/engagement`.
6
+ *
7
+ * The browser half — report which PLACE the user is on, once per arrival.
8
+ *
9
+ * ── Why an explicit beacon and not the request log ──────────────────────────
10
+ * Most satellites are SPA-routed: moving from Notes' Files page to its Ask page
11
+ * fires zero navigations and, on a warm cache, zero HTTP requests. So the
12
+ * request metrics already in `metrics/` can tell you an app is used and cannot
13
+ * tell you which of its pages are — which is exactly the question the owner
14
+ * asked ("notes is used but its slideshow is not"). One small event, sent by
15
+ * the router, is the only thing that can answer it.
16
+ *
17
+ * ── It is ONE implementation, adopted; never per-app ────────────────────────
18
+ * Eleven hand-rolled beacons would be eleven different place vocabularies, and
19
+ * the console would be comparing strings that mean different things. Apps call
20
+ * `reportPlace` (or mount `useEngagementBeacon`) and send an id from their own
21
+ * route manifest; the server resolves it against that same manifest.
22
+ *
23
+ * ── What it does NOT send ───────────────────────────────────────────────────
24
+ * No path, no query, no title, no referrer, no identifiers of any kind — just
25
+ * `{ place }`. The account comes from the session cookie the request already
26
+ * carries, server-side. There is nothing in the payload that could carry a note
27
+ * id, a ROM title or a search string, which is the property that makes this
28
+ * safe to run inside private apps at all. See `policy.ts`.
29
+ *
30
+ * ── Failure is silence ──────────────────────────────────────────────────────
31
+ * Every call is fire-and-forget and swallows its error. Telemetry that can
32
+ * surface an error toast, block a route change, or reject an unhandled promise
33
+ * is telemetry that makes the app worse than having none.
34
+ *
35
+ * 🔴 …and "silence" includes the BROWSER's console, which is why the beacon
36
+ * asks whether there is a session before it reports (`SESSION_PATH` below).
37
+ * Swallowing the rejection in JS is not enough: a `fetch` that comes back 401
38
+ * leaves the browser's own *"Failed to load resource: the server responded with
39
+ * a status of 401"* in the console no matter what the caller does with the
40
+ * promise. Measured 2026-08-14 against live prod — `music` and `learn` both
41
+ * failed their signed-out route-health check on exactly this, with a correctly
42
+ * rendered sign-in card on screen and nothing else wrong. `apps/roms`' App.tsx
43
+ * already wrote the rule down for the feedback provider: *"an app that logs an
44
+ * error on its front door is an app nobody can tell apart from a broken one."*
45
+ * The recorder's 401 is deliberate and stays — `api.ts` mounts it AFTER the
46
+ * session gate on purpose, because the gate is what puts the user where
47
+ * `resolveUser` can read them — so the fix belongs on this side.
48
+ *
49
+ * It is also the semantically right answer, not merely a quieter one: the
50
+ * recorder attributes a view to an account, so a signed-out arrival could never
51
+ * have been recorded. Nothing that used to be counted stops being counted.
52
+ */
53
+ /** Where the recorder is mounted. Apps that remount it pass `basePath`. */
54
+ export declare const ENGAGEMENT_BASE_PATH = "/api/engagement";
55
+ /**
56
+ * The public session probe every satellite serves
57
+ * (`ssoConsumer.server.ts` → `{ authenticated, user?, accountsUrl }`).
58
+ *
59
+ * Deliberately NOT session-gated on the server, which is what makes it usable
60
+ * here: asking it costs one small same-origin GET and can never itself produce
61
+ * the console line this exists to avoid.
62
+ */
63
+ export declare const SESSION_PATH = "/api/auth/session";
64
+ export interface ReportPlaceOptions {
65
+ basePath?: string;
66
+ /** Injected in tests; defaults to the global. */
67
+ fetchImpl?: typeof fetch;
68
+ /** Override the session probe. Apps that remount the consumer elsewhere. */
69
+ sessionPath?: string;
70
+ /**
71
+ * Skip the session probe and report unconditionally.
72
+ *
73
+ * For a caller that ALREADY knows there is a session — a React tree below the
74
+ * authenticated branch, say — where the probe would be a second answer to a
75
+ * question already asked. Never set it from `main.tsx`, which runs before
76
+ * anything knows.
77
+ */
78
+ assumeSignedIn?: boolean;
79
+ }
80
+ /**
81
+ * Where the browser is, as a string the server can resolve.
82
+ *
83
+ * The hash when there is one (every satellite but roms routes on it), else the
84
+ * pathname. Never the search string: `normalizePlaceId` strips one anyway, and
85
+ * not sending it in the first place means user text does not travel at all.
86
+ */
87
+ export declare function currentLocationPlace(): string;
88
+ /** Reset the de-duplication memory. Tests, and a sign-out. */
89
+ export declare function resetEngagementBeacon(): void;
90
+ /**
91
+ * Report an arrival at `place`. Returns whether anything was sent.
92
+ *
93
+ * Not awaited by callers in practice — the promise is returned so a test can
94
+ * settle it without a timer.
95
+ */
96
+ export declare function reportPlace(place: string, options?: ReportPlaceOptions): Promise<boolean>;
97
+ /** Detaches the listeners `startLocationBeacon` installed. */
98
+ export type StopBeacon = () => void;
99
+ /**
100
+ * Start reporting wherever the browser is, and keep reporting as it moves.
101
+ *
102
+ * The form every app adopts, called once from `main.tsx` beside
103
+ * `initColorScheme()` — one line, no React, and no per-app mapping from a
104
+ * router's internal view name to a place id. The server resolves the LOCATION
105
+ * against that app's own `routes.manifest.json` (`policy.ts:resolvePlace`),
106
+ * which is the list the app already maintains and already gates on.
107
+ *
108
+ * `hashchange` and `popstate` between them cover both kinds of router, which is
109
+ * why BOTH are listened for when `collections` — hash-routed — only needs the
110
+ * first. The census that settled it was the previous generation's twelve apps:
111
+ * eleven hash routers and one pushState app. Keeping the second listener costs
112
+ * a no-op call, and losing it would be silent.
113
+ *
114
+ * Idempotent — a second call replaces the first rather than doubling every
115
+ * view, which matters because React 18 StrictMode mounts twice in development
116
+ * and a doubled count is a wrong number rather than a visible fault.
117
+ */
118
+ export declare function startLocationBeacon(options?: ReportPlaceOptions): StopBeacon;
119
+ //# sourceMappingURL=beaconClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beaconClient.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/beaconClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,sBAAsB,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CACzB;AAaD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAI7C;AAYD,8DAA8D;AAC9D,wBAAgB,qBAAqB,IAAI,IAAI,CAG5C;AAyBD;;;;;GAKG;AACH,wBAAsB,WAAW,CAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC9B,OAAO,CAAC,OAAO,CAAC,CA+BlB;AAED,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC;AAIpC;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAehF"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * `cursedbelt/react/engagement-beacon` — the engagement beacon's plain-JS half (no React). Lifted
3
+ * 2026-09-23 (cursedbelt 5.2.0, task 098-280's browser half) from `src/kit/engagement/beacon.client.ts`,
4
+ * identical in collections, family, music and roms (auth's differed only in a comment). The
5
+ * server half is `cursedbelt-server/engagement`.
6
+ *
7
+ * The browser half — report which PLACE the user is on, once per arrival.
8
+ *
9
+ * ── Why an explicit beacon and not the request log ──────────────────────────
10
+ * Most satellites are SPA-routed: moving from Notes' Files page to its Ask page
11
+ * fires zero navigations and, on a warm cache, zero HTTP requests. So the
12
+ * request metrics already in `metrics/` can tell you an app is used and cannot
13
+ * tell you which of its pages are — which is exactly the question the owner
14
+ * asked ("notes is used but its slideshow is not"). One small event, sent by
15
+ * the router, is the only thing that can answer it.
16
+ *
17
+ * ── It is ONE implementation, adopted; never per-app ────────────────────────
18
+ * Eleven hand-rolled beacons would be eleven different place vocabularies, and
19
+ * the console would be comparing strings that mean different things. Apps call
20
+ * `reportPlace` (or mount `useEngagementBeacon`) and send an id from their own
21
+ * route manifest; the server resolves it against that same manifest.
22
+ *
23
+ * ── What it does NOT send ───────────────────────────────────────────────────
24
+ * No path, no query, no title, no referrer, no identifiers of any kind — just
25
+ * `{ place }`. The account comes from the session cookie the request already
26
+ * carries, server-side. There is nothing in the payload that could carry a note
27
+ * id, a ROM title or a search string, which is the property that makes this
28
+ * safe to run inside private apps at all. See `policy.ts`.
29
+ *
30
+ * ── Failure is silence ──────────────────────────────────────────────────────
31
+ * Every call is fire-and-forget and swallows its error. Telemetry that can
32
+ * surface an error toast, block a route change, or reject an unhandled promise
33
+ * is telemetry that makes the app worse than having none.
34
+ *
35
+ * 🔴 …and "silence" includes the BROWSER's console, which is why the beacon
36
+ * asks whether there is a session before it reports (`SESSION_PATH` below).
37
+ * Swallowing the rejection in JS is not enough: a `fetch` that comes back 401
38
+ * leaves the browser's own *"Failed to load resource: the server responded with
39
+ * a status of 401"* in the console no matter what the caller does with the
40
+ * promise. Measured 2026-08-14 against live prod — `music` and `learn` both
41
+ * failed their signed-out route-health check on exactly this, with a correctly
42
+ * rendered sign-in card on screen and nothing else wrong. `apps/roms`' App.tsx
43
+ * already wrote the rule down for the feedback provider: *"an app that logs an
44
+ * error on its front door is an app nobody can tell apart from a broken one."*
45
+ * The recorder's 401 is deliberate and stays — `api.ts` mounts it AFTER the
46
+ * session gate on purpose, because the gate is what puts the user where
47
+ * `resolveUser` can read them — so the fix belongs on this side.
48
+ *
49
+ * It is also the semantically right answer, not merely a quieter one: the
50
+ * recorder attributes a view to an account, so a signed-out arrival could never
51
+ * have been recorded. Nothing that used to be counted stops being counted.
52
+ */
53
+ /** Where the recorder is mounted. Apps that remount it pass `basePath`. */
54
+ export const ENGAGEMENT_BASE_PATH = "/api/engagement";
55
+ /**
56
+ * The public session probe every satellite serves
57
+ * (`ssoConsumer.server.ts` → `{ authenticated, user?, accountsUrl }`).
58
+ *
59
+ * Deliberately NOT session-gated on the server, which is what makes it usable
60
+ * here: asking it costs one small same-origin GET and can never itself produce
61
+ * the console line this exists to avoid.
62
+ */
63
+ export const SESSION_PATH = "/api/auth/session";
64
+ /**
65
+ * The last place reported, so a router that re-renders does not re-report.
66
+ *
67
+ * A React router re-runs its effects on every state change that touches the
68
+ * route object, and a beacon per keystroke in a search box would turn one visit
69
+ * into forty views — inflating exactly the number the console reads as
70
+ * "interest". De-duplicating on the place id is what keeps a view meaning "an
71
+ * arrival".
72
+ */
73
+ let lastReported = null;
74
+ /**
75
+ * Where the browser is, as a string the server can resolve.
76
+ *
77
+ * The hash when there is one (every satellite but roms routes on it), else the
78
+ * pathname. Never the search string: `normalizePlaceId` strips one anyway, and
79
+ * not sending it in the first place means user text does not travel at all.
80
+ */
81
+ export function currentLocationPlace() {
82
+ const { hash, pathname } = window.location;
83
+ const raw = hash && hash !== "#" ? hash : pathname;
84
+ return raw.split("?")[0] ?? "/";
85
+ }
86
+ /**
87
+ * The answer to "is anyone signed in?", remembered for this page load.
88
+ *
89
+ * One probe per load, not one per navigation: the answer cannot change without
90
+ * a navigation that replaces the document (the SSO callback is a full page
91
+ * load), and re-asking on every hash change would trade one wasted request for
92
+ * dozens.
93
+ */
94
+ let sessionAnswer = null;
95
+ /** Reset the de-duplication memory. Tests, and a sign-out. */
96
+ export function resetEngagementBeacon() {
97
+ lastReported = null;
98
+ sessionAnswer = null;
99
+ }
100
+ /**
101
+ * Is there a session? Silent, cached, and false on any doubt.
102
+ *
103
+ * Fails CLOSED — a probe that throws, answers non-2xx or returns a body this
104
+ * cannot read means "do not report". The cost of a false negative is one
105
+ * uncounted view; the cost of a false positive is the console line on the front
106
+ * door that this whole path exists to prevent.
107
+ */
108
+ async function hasSession(options) {
109
+ const doFetch = options.fetchImpl ?? globalThis.fetch;
110
+ if (typeof doFetch !== "function")
111
+ return false;
112
+ try {
113
+ const res = await doFetch(options.sessionPath ?? SESSION_PATH, {
114
+ credentials: "same-origin",
115
+ });
116
+ if (!res.ok)
117
+ return false;
118
+ const body = (await res.json());
119
+ return body?.authenticated === true;
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ /**
126
+ * Report an arrival at `place`. Returns whether anything was sent.
127
+ *
128
+ * Not awaited by callers in practice — the promise is returned so a test can
129
+ * settle it without a timer.
130
+ */
131
+ export async function reportPlace(place, options = {}) {
132
+ if (!place || place === lastReported)
133
+ return false;
134
+ const doFetch = options.fetchImpl ?? globalThis.fetch;
135
+ if (typeof doFetch !== "function")
136
+ return false;
137
+ // 🔴 BEFORE the de-duplication is committed, not after. A signed-out arrival
138
+ // must leave no trace at all — recording it as `lastReported` would mean the
139
+ // view that happens once the visitor signs in and lands back on this same
140
+ // place is silently dropped as a duplicate.
141
+ if (options.assumeSignedIn !== true) {
142
+ sessionAnswer ??= hasSession(options);
143
+ if (!(await sessionAnswer))
144
+ return false;
145
+ }
146
+ if (place === lastReported)
147
+ return false;
148
+ lastReported = place;
149
+ try {
150
+ await doFetch(`${options.basePath ?? ENGAGEMENT_BASE_PATH}/view`, {
151
+ method: "POST",
152
+ headers: { "content-type": "application/json" },
153
+ // Same-origin credentials: the session cookie is the only identity
154
+ // this request carries, and it is the only one it may carry.
155
+ credentials: "same-origin",
156
+ body: JSON.stringify({ place }),
157
+ keepalive: true,
158
+ });
159
+ return true;
160
+ }
161
+ catch {
162
+ // See the header: telemetry never becomes the user's problem.
163
+ return false;
164
+ }
165
+ }
166
+ let running = null;
167
+ /**
168
+ * Start reporting wherever the browser is, and keep reporting as it moves.
169
+ *
170
+ * The form every app adopts, called once from `main.tsx` beside
171
+ * `initColorScheme()` — one line, no React, and no per-app mapping from a
172
+ * router's internal view name to a place id. The server resolves the LOCATION
173
+ * against that app's own `routes.manifest.json` (`policy.ts:resolvePlace`),
174
+ * which is the list the app already maintains and already gates on.
175
+ *
176
+ * `hashchange` and `popstate` between them cover both kinds of router, which is
177
+ * why BOTH are listened for when `collections` — hash-routed — only needs the
178
+ * first. The census that settled it was the previous generation's twelve apps:
179
+ * eleven hash routers and one pushState app. Keeping the second listener costs
180
+ * a no-op call, and losing it would be silent.
181
+ *
182
+ * Idempotent — a second call replaces the first rather than doubling every
183
+ * view, which matters because React 18 StrictMode mounts twice in development
184
+ * and a doubled count is a wrong number rather than a visible fault.
185
+ */
186
+ export function startLocationBeacon(options = {}) {
187
+ running?.();
188
+ const send = () => {
189
+ void reportPlace(currentLocationPlace(), options);
190
+ };
191
+ send();
192
+ window.addEventListener("hashchange", send);
193
+ window.addEventListener("popstate", send);
194
+ const stop = () => {
195
+ window.removeEventListener("hashchange", send);
196
+ window.removeEventListener("popstate", send);
197
+ if (running === stop)
198
+ running = null;
199
+ };
200
+ running = stop;
201
+ return stop;
202
+ }
203
+ //# sourceMappingURL=beaconClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beaconClient.js","sourceRoot":"","sources":["../../../src/react/engagement/beaconClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { ENGAGEMENT_BASE_PATH, type ReportPlaceOptions, reportPlace, resetEngagementBeacon, SESSION_PATH, type StopBeacon, startLocationBeacon, currentLocationPlace, } from './beaconClient.js';
2
+ export { EngagementBeacon, useEngagementBeacon, useLocationBeacon } from './beacon.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/react/engagement/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,oBAAoB,EACpB,KAAK,kBAAkB,EACvB,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,KAAK,UAAU,EACf,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,5 @@
1
+ // `cursedbelt/react/engagement-beacon` — the engagement beacon: `reportPlace` and
2
+ // `startLocationBeacon` (plain JS, no React) and the React hooks over them.
3
+ export { ENGAGEMENT_BASE_PATH, reportPlace, resetEngagementBeacon, SESSION_PATH, startLocationBeacon, currentLocationPlace, } from './beaconClient.js';
4
+ export { EngagementBeacon, useEngagementBeacon, useLocationBeacon } from './beacon.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/react/engagement/index.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,4EAA4E;AAC5E,OAAO,EACN,oBAAoB,EAEpB,WAAW,EACX,qBAAqB,EACrB,YAAY,EAEZ,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * `cursedbelt/react/upload-direct` — browser-side resumable upload, straight from the file input
3
+ * to binary-server.
4
+ *
5
+ * Lifted 2026-09-23 (cursedbelt 5.2.0, task 056-343's browser half) from `src/kit/directUpload.ts`,
6
+ * identical in collections and family. The server half — minting the session — is
7
+ * `cursedbelt-server/binary-store/upload-session`'s `createUploadSession`.
8
+ *
9
+ * 🔴 THIS FILE RUNS IN A BROWSER. It must never import `node:*`, `binaryStore.ts`, or
10
+ * anything that reaches a private key — see {@link UploadSession}. Keep it dependency-free.
11
+ *
12
+ * ## Why the bytes skip the app server
13
+ *
14
+ * Every satellite's upload route reads the whole body (`formData()` → `arrayBuffer()`) before
15
+ * handing it to binary-server, inside a unit capped at `MemoryMax=300M` on a shared t3.micro
16
+ * that also runs nginx and every other app. That is why the fleet's upload ceiling is 32 MB
17
+ * and why raising the number was never the fix: a 10 GB video does not fit in the process, in
18
+ * the nginx body limit, or in the Cloudflare Tunnel's ~100 MB request wall.
19
+ *
20
+ * So a large file does not go through the app server at all. The app server mints a session
21
+ * token naming exactly one destination key; the browser PUTs bounded parts directly to
22
+ * binary-server, which assembles them; the app server is told afterwards and records the
23
+ * metadata. No byte of video touches the EC2 box, which is the owner's standing rule for
24
+ * video (2026-08-12) and also the only shape that survives the memory limit.
25
+ *
26
+ * ## What makes it survive 10 GB
27
+ *
28
+ * - **Resume.** `GET /upload-chunk` reports which parts already landed, so an upload that
29
+ * dies at 90% resumes instead of restarting. That answer comes from the parts on disk, so
30
+ * it survives a binary-server restart too.
31
+ * - **Bounded concurrency.** A few parts in flight keeps a home upstream saturated without
32
+ * putting the whole file in memory. Only one part's bytes are ever read at a time per slot.
33
+ * - **Per-part retry.** A dropped part costs one part, not the upload.
34
+ * - **Slicing, not reading.** `Blob.slice` is lazy — the 10 GB file is never materialized.
35
+ */
36
+ /** Everything the browser needs to run an upload, minted server-side by `createUploadSession`
37
+ * in `cursedbelt-server/binary-store/upload-session`. Contains a capability token scoped to ONE key, and no key
38
+ * material — it is safe to hand to the page that will do the uploading. */
39
+ export interface UploadSession {
40
+ /** binary-server's public base, e.g. `https://binary-server.cursedalchemy.com`. */
41
+ baseUrl: string;
42
+ /** App-prefixed destination key, e.g. `family/media/<uuid>`. */
43
+ path: string;
44
+ /** Signed token authorizing this session (carries `sid`, `tc` and the source length `sz`). */
45
+ token: string;
46
+ /** Chunk session id. */
47
+ sid: string;
48
+ /** Total parts the file was split into. */
49
+ totalChunks: number;
50
+ /** Bytes per part — must match what the token's `tc` was computed from. */
51
+ chunkBytes: number;
52
+ }
53
+ export interface DirectUploadOptions {
54
+ /** 0..1, called as parts complete. */
55
+ onProgress?: (fraction: number, bytesSent: number, totalBytes: number) => void;
56
+ /** Parts in flight at once. Default 3 — enough to saturate a home upstream, few enough
57
+ * that a failure retries cheaply. */
58
+ concurrency?: number;
59
+ /** Attempts per part before giving up. Default 5, with backoff. */
60
+ retries?: number;
61
+ /**
62
+ * Called once, before any part is sent, when the store reports less free space than this
63
+ * upload is likely to need.
64
+ *
65
+ * 🔴 A WARNING and nothing else — it cannot stop the upload, and there is deliberately no
66
+ * way for it to. The owner's instruction (2026-08-13) is that space must never block them:
67
+ * *"Remove all blockers for space and only allow warning me. I'll manage space myself
68
+ * beyond that."* Returning `false` from here, or throwing, would quietly reinstate exactly
69
+ * the blocker that was removed.
70
+ */
71
+ onLowDisk?: (info: {
72
+ freeBytes: number;
73
+ needBytes: number;
74
+ }) => void;
75
+ /** Abort the whole upload (a cancel button, or unmount). */
76
+ signal?: AbortSignal;
77
+ /** Re-mint an expired session token mid-upload. A 10 GB upload can outlive any TTL short
78
+ * enough to be safe, so the page supplies a way to ask its own server for a fresh one
79
+ * for the SAME `sid`. Without it, a long upload simply fails on expiry. */
80
+ refreshToken?: () => Promise<string>;
81
+ }
82
+ /** What binary-server reports once the last part lands. */
83
+ export interface DirectUploadResult {
84
+ key: string;
85
+ size: number;
86
+ checksum: string | null;
87
+ }
88
+ /** Split `[0, total)` into `chunkBytes`-sized part indices. */
89
+ export declare const chunkCount: (totalBytes: number, chunkBytes: number) => number;
90
+ /** Which parts binary-server already holds, plus whether the object is already complete.
91
+ * A fresh session answers `{ received: [], complete: false }`, so callers need no special
92
+ * case for "first attempt". */
93
+ export declare function fetchSessionState(session: UploadSession, token?: string, signal?: AbortSignal): Promise<{
94
+ received: Set<number>;
95
+ complete: boolean;
96
+ size: number | null;
97
+ checksum: string | null;
98
+ /** Bytes binary-server still has room for, or null if it could not tell. */
99
+ freeBytes: number | null;
100
+ }>;
101
+ /** Thrown when binary-server has no room for the file. Distinct from a transport failure so a
102
+ * UI can say "the server is full" — which is actionable — rather than "upload failed", which
103
+ * invites the person to retry forever against a disk that will never have space. */
104
+ export declare class InsufficientStorageError extends Error {
105
+ readonly needBytes: number;
106
+ readonly freeBytes: number | null;
107
+ /**
108
+ * 🔴 Vestigial since 2026-08-13 and kept only so an older deployed binary-server can
109
+ * still be understood: THIS service no longer refuses an upload for disk space at all,
110
+ * so nothing here should be producing an overridable refusal any more. If you see one,
111
+ * the origin is running a build from before that change.
112
+ */
113
+ readonly overridable: boolean;
114
+ constructor(needBytes: number, freeBytes: number | null,
115
+ /**
116
+ * 🔴 Vestigial since 2026-08-13 and kept only so an older deployed binary-server can
117
+ * still be understood: THIS service no longer refuses an upload for disk space at all,
118
+ * so nothing here should be producing an overridable refusal any more. If you see one,
119
+ * the origin is running a build from before that change.
120
+ */
121
+ overridable?: boolean);
122
+ }
123
+ /**
124
+ * Upload `file` to binary-server in parts, resuming anything already delivered.
125
+ *
126
+ * Resolves once every part has landed and bs has assembled the object. The caller then tells
127
+ * its own app server to record the metadata — see each app's `/complete` route.
128
+ */
129
+ export declare function uploadDirect(file: Blob, session: UploadSession, opts?: DirectUploadOptions): Promise<DirectUploadResult>;
130
+ //# sourceMappingURL=directUpload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directUpload.d.ts","sourceRoot":"","sources":["../../../src/react/lib/directUpload.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;4EAE4E;AAC5E,MAAM,WAAW,aAAa;IAC7B,mFAAmF;IACnF,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,8FAA8F;IAC9F,KAAK,EAAE,MAAM,CAAC;IACd,wBAAwB;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IACnC,sCAAsC;IACtC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/E;0CACsC;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,4DAA4D;IAC5D,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;gFAE4E;IAC5E,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CACrC;AAED,2DAA2D;AAC3D,MAAM,WAAW,kBAAkB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAID,+DAA+D;AAC/D,eAAO,MAAM,UAAU,GAAI,YAAY,MAAM,EAAE,YAAY,MAAM,KAAG,MACpB,CAAC;AAcjD;;gCAEgC;AAChC,wBAAsB,iBAAiB,CACtC,OAAO,EAAE,aAAa,EACtB,KAAK,SAAgB,EACrB,MAAM,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC;IACV,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4EAA4E;IAC5E,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC,CAwBD;AAED;;qFAEqF;AACrF,qBAAa,wBAAyB,SAAQ,KAAK;IAEjD,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW;gBARX,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI;IACjC;;;;;OAKG;IACM,WAAW,UAAQ;CAS7B;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,aAAa,EACtB,IAAI,GAAE,mBAAwB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAyJ7B"}