@bitvea/feedback-toolbar 0.0.1

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 (176) hide show
  1. package/README.md +262 -0
  2. package/dist/FeedbackPanel.d.ts +117 -0
  3. package/dist/FeedbackPanel.d.ts.map +1 -0
  4. package/dist/FeedbackPanel.js +622 -0
  5. package/dist/FeedbackPanel.js.map +1 -0
  6. package/dist/FeedbackToolbar.d.ts +54 -0
  7. package/dist/FeedbackToolbar.d.ts.map +1 -0
  8. package/dist/FeedbackToolbar.js +1149 -0
  9. package/dist/FeedbackToolbar.js.map +1 -0
  10. package/dist/InboxSheet.d.ts +80 -0
  11. package/dist/InboxSheet.d.ts.map +1 -0
  12. package/dist/InboxSheet.js +580 -0
  13. package/dist/InboxSheet.js.map +1 -0
  14. package/dist/PinLayer.d.ts +59 -0
  15. package/dist/PinLayer.d.ts.map +1 -0
  16. package/dist/PinLayer.js +1702 -0
  17. package/dist/PinLayer.js.map +1 -0
  18. package/dist/RichText.d.ts +6 -0
  19. package/dist/RichText.d.ts.map +1 -0
  20. package/dist/RichText.js +135 -0
  21. package/dist/RichText.js.map +1 -0
  22. package/dist/browser/entry.d.ts +3 -0
  23. package/dist/browser/entry.d.ts.map +1 -0
  24. package/dist/browser/entry.js +13 -0
  25. package/dist/browser/entry.js.map +1 -0
  26. package/dist/browser/mount.d.ts +37 -0
  27. package/dist/browser/mount.d.ts.map +1 -0
  28. package/dist/browser/mount.js +150 -0
  29. package/dist/browser/mount.js.map +1 -0
  30. package/dist/browser/toolbar.js +38 -0
  31. package/dist/browser/toolbar.js.map +7 -0
  32. package/dist/capture.d.ts +58 -0
  33. package/dist/capture.d.ts.map +1 -0
  34. package/dist/capture.js +98 -0
  35. package/dist/capture.js.map +1 -0
  36. package/dist/cli/bin.d.ts +3 -0
  37. package/dist/cli/bin.d.ts.map +1 -0
  38. package/dist/cli/bin.js +196 -0
  39. package/dist/cli/bin.js.map +1 -0
  40. package/dist/cli/fs.d.ts +16 -0
  41. package/dist/cli/fs.d.ts.map +1 -0
  42. package/dist/cli/fs.js +33 -0
  43. package/dist/cli/fs.js.map +1 -0
  44. package/dist/cli/init.d.ts +75 -0
  45. package/dist/cli/init.d.ts.map +1 -0
  46. package/dist/cli/init.js +266 -0
  47. package/dist/cli/init.js.map +1 -0
  48. package/dist/cli/register.d.ts +55 -0
  49. package/dist/cli/register.d.ts.map +1 -0
  50. package/dist/cli/register.js +84 -0
  51. package/dist/cli/register.js.map +1 -0
  52. package/dist/cli/registration.d.ts +37 -0
  53. package/dist/cli/registration.d.ts.map +1 -0
  54. package/dist/cli/registration.js +69 -0
  55. package/dist/cli/registration.js.map +1 -0
  56. package/dist/cli/strategies.d.ts +98 -0
  57. package/dist/cli/strategies.d.ts.map +1 -0
  58. package/dist/cli/strategies.js +198 -0
  59. package/dist/cli/strategies.js.map +1 -0
  60. package/dist/client.d.ts +59 -0
  61. package/dist/client.d.ts.map +1 -0
  62. package/dist/client.js +113 -0
  63. package/dist/client.js.map +1 -0
  64. package/dist/config.d.ts +92 -0
  65. package/dist/config.d.ts.map +1 -0
  66. package/dist/config.js +65 -0
  67. package/dist/config.js.map +1 -0
  68. package/dist/deepLink.d.ts +86 -0
  69. package/dist/deepLink.d.ts.map +1 -0
  70. package/dist/deepLink.js +185 -0
  71. package/dist/deepLink.js.map +1 -0
  72. package/dist/fontFace.d.ts +70 -0
  73. package/dist/fontFace.d.ts.map +1 -0
  74. package/dist/fontFace.js +88 -0
  75. package/dist/fontFace.js.map +1 -0
  76. package/dist/gating.d.ts +32 -0
  77. package/dist/gating.d.ts.map +1 -0
  78. package/dist/gating.js +49 -0
  79. package/dist/gating.js.map +1 -0
  80. package/dist/icons.d.ts +68 -0
  81. package/dist/icons.d.ts.map +1 -0
  82. package/dist/icons.js +97 -0
  83. package/dist/icons.js.map +1 -0
  84. package/dist/index.d.ts +14 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +68 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/markdown.d.ts +59 -0
  89. package/dist/markdown.d.ts.map +1 -0
  90. package/dist/markdown.js +130 -0
  91. package/dist/markdown.js.map +1 -0
  92. package/dist/motion.d.ts +178 -0
  93. package/dist/motion.d.ts.map +1 -0
  94. package/dist/motion.js +302 -0
  95. package/dist/motion.js.map +1 -0
  96. package/dist/preferences.d.ts +71 -0
  97. package/dist/preferences.d.ts.map +1 -0
  98. package/dist/preferences.js +195 -0
  99. package/dist/preferences.js.map +1 -0
  100. package/dist/rasterise.d.ts +45 -0
  101. package/dist/rasterise.d.ts.map +1 -0
  102. package/dist/rasterise.js +490 -0
  103. package/dist/rasterise.js.map +1 -0
  104. package/dist/selector.d.ts +19 -0
  105. package/dist/selector.d.ts.map +1 -0
  106. package/dist/selector.js +142 -0
  107. package/dist/selector.js.map +1 -0
  108. package/dist/session.d.ts +19 -0
  109. package/dist/session.d.ts.map +1 -0
  110. package/dist/session.js +36 -0
  111. package/dist/session.js.map +1 -0
  112. package/dist/shortcut.d.ts +88 -0
  113. package/dist/shortcut.d.ts.map +1 -0
  114. package/dist/shortcut.js +149 -0
  115. package/dist/shortcut.js.map +1 -0
  116. package/dist/tokens.d.ts +709 -0
  117. package/dist/tokens.d.ts.map +1 -0
  118. package/dist/tokens.js +749 -0
  119. package/dist/tokens.js.map +1 -0
  120. package/dist/vendor/types/account.d.ts +26 -0
  121. package/dist/vendor/types/account.d.ts.map +1 -0
  122. package/dist/vendor/types/account.js +23 -0
  123. package/dist/vendor/types/account.js.map +1 -0
  124. package/dist/vendor/types/apiKey.d.ts +38 -0
  125. package/dist/vendor/types/apiKey.d.ts.map +1 -0
  126. package/dist/vendor/types/apiKey.js +36 -0
  127. package/dist/vendor/types/apiKey.js.map +1 -0
  128. package/dist/vendor/types/auth.d.ts +10 -0
  129. package/dist/vendor/types/auth.d.ts.map +1 -0
  130. package/dist/vendor/types/auth.js +15 -0
  131. package/dist/vendor/types/auth.js.map +1 -0
  132. package/dist/vendor/types/entity.d.ts +74 -0
  133. package/dist/vendor/types/entity.d.ts.map +1 -0
  134. package/dist/vendor/types/entity.js +111 -0
  135. package/dist/vendor/types/entity.js.map +1 -0
  136. package/dist/vendor/types/feedback.d.ts +285 -0
  137. package/dist/vendor/types/feedback.d.ts.map +1 -0
  138. package/dist/vendor/types/feedback.js +286 -0
  139. package/dist/vendor/types/feedback.js.map +1 -0
  140. package/dist/vendor/types/index.d.ts +14 -0
  141. package/dist/vendor/types/index.d.ts.map +1 -0
  142. package/dist/vendor/types/index.js +16 -0
  143. package/dist/vendor/types/index.js.map +1 -0
  144. package/dist/vendor/types/invite.d.ts +42 -0
  145. package/dist/vendor/types/invite.d.ts.map +1 -0
  146. package/dist/vendor/types/invite.js +30 -0
  147. package/dist/vendor/types/invite.js.map +1 -0
  148. package/dist/vendor/types/mcpToken.d.ts +38 -0
  149. package/dist/vendor/types/mcpToken.d.ts.map +1 -0
  150. package/dist/vendor/types/mcpToken.js +39 -0
  151. package/dist/vendor/types/mcpToken.js.map +1 -0
  152. package/dist/vendor/types/organization.d.ts +13 -0
  153. package/dist/vendor/types/organization.d.ts.map +1 -0
  154. package/dist/vendor/types/organization.js +12 -0
  155. package/dist/vendor/types/organization.js.map +1 -0
  156. package/dist/vendor/types/project.d.ts +73 -0
  157. package/dist/vendor/types/project.d.ts.map +1 -0
  158. package/dist/vendor/types/project.js +117 -0
  159. package/dist/vendor/types/project.js.map +1 -0
  160. package/dist/vendor/types/role.d.ts +12 -0
  161. package/dist/vendor/types/role.d.ts.map +1 -0
  162. package/dist/vendor/types/role.js +20 -0
  163. package/dist/vendor/types/role.js.map +1 -0
  164. package/dist/vendor/types/roleDefinition.d.ts +61 -0
  165. package/dist/vendor/types/roleDefinition.d.ts.map +1 -0
  166. package/dist/vendor/types/roleDefinition.js +45 -0
  167. package/dist/vendor/types/roleDefinition.js.map +1 -0
  168. package/dist/vendor/types/team.d.ts +25 -0
  169. package/dist/vendor/types/team.d.ts.map +1 -0
  170. package/dist/vendor/types/team.js +17 -0
  171. package/dist/vendor/types/team.js.map +1 -0
  172. package/dist/vendor/types/util.d.ts +39 -0
  173. package/dist/vendor/types/util.d.ts.map +1 -0
  174. package/dist/vendor/types/util.js +31 -0
  175. package/dist/vendor/types/util.js.map +1 -0
  176. package/package.json +59 -0
package/dist/tokens.js ADDED
@@ -0,0 +1,749 @@
1
+ import { TOOLBAR_FONT_MONO, TOOLBAR_FONT_SANS } from "./fontFace.js";
2
+ /**
3
+ * The BitVea brand orange, as the logo draws it.
4
+ *
5
+ * It used to be the toolbar's whole action system: every primary button, the
6
+ * active bar tool, the count bubble, the avatars, the status chip and the
7
+ * checkbox were orange. That put the accent on metadata and on chrome, which
8
+ * is the one thing DESIGN.md's colour section forbids - ink carries the CTA,
9
+ * and colour is reserved. So the accent is now IDENTITY ONLY: it survives on
10
+ * the pin teardrop, which has to be findable over arbitrary host content and
11
+ * is the one object in the product that means "BitVea Feedback is here".
12
+ */
13
+ export const accent = {
14
+ start: "#DF6520",
15
+ end: "#F97316",
16
+ /** 135deg, the direction the mark's own gradient runs. */
17
+ gradient: "linear-gradient(135deg, #DF6520 0%, #F97316 100%)",
18
+ onAccent: "#ffffff",
19
+ };
20
+ export const lightPalette = {
21
+ ink: "#171717",
22
+ onInk: "#ffffff",
23
+ body: "#4d4d4d",
24
+ mute: "#888888",
25
+ // 5.9:1 on a 14%-orange wash over white.
26
+ accentInk: "#9a3f0c",
27
+ link: "#0070f3",
28
+ hairline: "#ebebeb",
29
+ hairlineStrong: "#a1a1a1",
30
+ canvas: "#ffffff",
31
+ canvasSoft: "#fafafa",
32
+ canvasSoft2: "#f5f5f5",
33
+ error: "#ee0000",
34
+ success: "#0070f3",
35
+ status: {
36
+ // warning-soft / warning-deep: 4.5:1.
37
+ OPEN: { background: "#ffefcf", color: "#ab570a" },
38
+ // link-bg-soft / link-deep: 4.5:1.
39
+ IN_REVIEW: { background: "#d3e5ff", color: "#0761d1" },
40
+ // canvas-soft-2 / body: settled, not coloured.
41
+ DONE: { background: "#f5f5f5", color: "#4d4d4d" },
42
+ },
43
+ barSurface: "#ffffff",
44
+ barBorder: "rgba(0, 0, 0, 0.08)",
45
+ barInk: "#171717",
46
+ barInkMute: "#888888",
47
+ barHover: "rgba(0, 0, 0, 0.05)",
48
+ pickTint: "rgba(23, 23, 23, 0.04)",
49
+ inkSoft: "rgba(23, 23, 23, 0.08)",
50
+ markerRing: "#ffffff",
51
+ };
52
+ /**
53
+ * The dark palette, taken verbatim from `packages/ui/src/styles/globals.css`'s
54
+ * `.dark` block.
55
+ *
56
+ * It used to run the OPPOSITE direction from the dashboard's: canvas #0f0f0f
57
+ * was DARKER than canvasSoft #161616, while globals.css raises canvas #1f1f1f
58
+ * above canvas-soft #171717. Two dark theories in one product means every rule
59
+ * of the form "the row is canvas on a canvasSoft sheet" inverts when a
60
+ * reviewer crosses from the toolbar to the dashboard looking at the same
61
+ * feedback. One ladder, defined once, mirrored here.
62
+ */
63
+ export const darkPalette = {
64
+ ink: "#ffffff",
65
+ onInk: "#171717",
66
+ body: "#a1a1a1",
67
+ mute: "#737373",
68
+ // 7.1:1 on a 14%-orange wash over the dark canvas.
69
+ accentInk: "#fb923c",
70
+ link: "#3291ff",
71
+ hairline: "#2e2e2e",
72
+ hairlineStrong: "#545454",
73
+ canvas: "#1f1f1f",
74
+ canvasSoft: "#171717",
75
+ canvasSoft2: "#292929",
76
+ error: "#ff4d4d",
77
+ success: "#3291ff",
78
+ status: {
79
+ // The dark warning pair, straight from globals.css: 12:1.
80
+ OPEN: { background: "#3a2b10", color: "#ffefcf" },
81
+ // link-bg-soft #10233f, but the ink is `--link` #3291ff rather than the
82
+ // dashboard's `--link-deep` #0070f3: #0070f3 on #10233f measures 3.5:1,
83
+ // and this chip is 12px. Same reasoning as `accentInk` above - the pair
84
+ // is chosen per palette so the READING survives the flip, which the
85
+ // literal value would not.
86
+ IN_REVIEW: { background: "#10233f", color: "#3291ff" },
87
+ DONE: { background: "#292929", color: "#a1a1a1" },
88
+ },
89
+ // The reference bar is near-black rather than pure black: pure black loses
90
+ // the hairline border against an OLED-black host page.
91
+ barSurface: "#0a0a0a",
92
+ barBorder: "rgba(255, 255, 255, 0.08)",
93
+ barInk: "#ededed",
94
+ barInkMute: "#8f8f8f",
95
+ barHover: "rgba(255, 255, 255, 0.08)",
96
+ pickTint: "rgba(255, 255, 255, 0.06)",
97
+ inkSoft: "rgba(255, 255, 255, 0.10)",
98
+ markerRing: "#0a0a0a",
99
+ };
100
+ export const radii = {
101
+ /** 4px. Concentric inner corner inside an 8px track, and the pin's point. */
102
+ xs: "4px",
103
+ sm: "6px",
104
+ md: "8px",
105
+ lg: "12px",
106
+ pill: "100px",
107
+ };
108
+ export const spacing = {
109
+ xxs: "4px",
110
+ xs: "8px",
111
+ sm: "12px",
112
+ md: "16px",
113
+ lg: "24px",
114
+ };
115
+ /**
116
+ * The margin an anchored surface keeps against the viewport's edge, as a
117
+ * NUMBER - the shape `DRAG_THRESHOLD` below already takes, and for the same
118
+ * reason: every use of it is arithmetic.
119
+ *
120
+ * Here rather than beside the clamps in PinLayer.tsx, which is where it began.
121
+ * The gutter appears on both axes and in both spellings - a `calc()` in a
122
+ * `maxWidth` and a `maxHeight`, and a `Math.min`/`Math.max` pair per axis in
123
+ * the code that decides where a card lands - and a token file cannot import a
124
+ * component's constant, so a copy in PinLayer meant tokens.ts held its own. It
125
+ * had five spellings between them (`16`, and four `32`s inside `calc()`
126
+ * strings) under a comment claiming they agreed "by construction". They do
127
+ * now.
128
+ *
129
+ * A card whose `maxWidth` disagreed with the clamp that positions it is
130
+ * pushed off the very edge the clamp believes it is holding it inside, which
131
+ * is not a hypothetical: it is `sizes.thread`'s own story one field down.
132
+ */
133
+ export const VIEWPORT_GUTTER = 16;
134
+ /**
135
+ * Box sizes that are not spacing and not radii: the handful of fixed squares
136
+ * and bubbles this package draws. They live here because every one of them
137
+ * used to be an off-grid literal spelled at its call site (13px, 14px, 9px,
138
+ * 3px), and a literal at a call site is how the next one gets invented.
139
+ */
140
+ export const sizes = {
141
+ /** 16px form control, so the checkbox matches the 16px icons beside it. */
142
+ control: "16px",
143
+ /** 28px: the sheet's close button and the pin marker's teardrop. */
144
+ icon: "28px",
145
+ /** 18px count bubble - the smallest square that holds 12px mono legibly. */
146
+ badge: "18px",
147
+ /** 8px unread dot. */
148
+ dot: "8px",
149
+ /**
150
+ * 24px: the square a per-message action in a thread is drawn in (issue #20),
151
+ * and the thread's own close control, which used to be an invented 20px.
152
+ * A 16px glyph on DESIGN.md's `spacing.xxs` all round, so a row of them
153
+ * matches the 24px `Avatar` opposite it.
154
+ */
155
+ action: "24px",
156
+ /**
157
+ * The thread card's width, and the pin composer's - issue #20 says in as
158
+ * many words that "both the pin popover and the composer-only variant share
159
+ * the 280px constant, widen both consistently", so they share this one.
160
+ *
161
+ * 440px is the Vercel comment thread's content width the issue names. It was
162
+ * two `"280px"` string literals at two call sites, which is exactly the
163
+ * shape the header comment above warns about: a THIRD place - the composer's
164
+ * own right-edge clamp - carried `280 + padding + border` as a hard-coded
165
+ * `300`, so widening one literal would have left the clamp holding the
166
+ * composer 140px inside an edge it thought it was hugging.
167
+ *
168
+ * A `px` string rather than a number because everything else in this object
169
+ * is one; the two call sites that need arithmetic parse it, which is one
170
+ * `parseFloat` against three places to forget.
171
+ */
172
+ thread: "440px",
173
+ /**
174
+ * The ceiling a thread's message list scrolls inside, rather than growing
175
+ * past the viewport - issue #20's second acceptance line.
176
+ *
177
+ * Two caps, and both are needed. The `calc()` is `VIEWPORT_GUTTER` per
178
+ * side - the same margin the card's own `maxWidth` and the clamp that
179
+ * places it are built from - and is what stops a long thread running off
180
+ * the screen. The 480px is what stops a merely
181
+ * longish one filling a tall monitor: this card floats over the element it
182
+ * is talking about, and a review tool that covers the thing under review is
183
+ * the complaint the pin layer's own dismissal comment already answers once.
184
+ */
185
+ threadMaxHeight: `min(480px, calc(100vh - ${VIEWPORT_GUTTER * 2}px))`,
186
+ };
187
+ /**
188
+ * Motion, from DESIGN.md's `## Motion` section - which this package's need is
189
+ * what added. See the provenance note at the head of this file: the toolbar
190
+ * renders through inline style inside somebody else's page, so there is no
191
+ * cascade to read a `--duration-fast` out of and the values are reproduced
192
+ * here, exactly as the colours and the type scale are.
193
+ *
194
+ * `enterScale` and `enterShift` are tokens rather than literals on purpose.
195
+ * The acceptance criterion for issue #24 is "no inline durations", and a bare
196
+ * `0.96` spelled at four call sites is the same defect one level down - the
197
+ * fifth surface is where somebody writes 0.95 and the product has two enters.
198
+ *
199
+ * Nothing here is a keyframe or a loop. Every value is one leg of a state
200
+ * transition a reviewer caused, which is the whole of the posture DESIGN.md
201
+ * states: small, fast, over before it is noticed.
202
+ */
203
+ export const motion = {
204
+ duration: {
205
+ /** 80ms - hover, press, a badge whose number changed. */
206
+ instant: "80ms",
207
+ /** 120ms - the default, and every surface enter. */
208
+ fast: "120ms",
209
+ /**
210
+ * 180ms - the ceiling, and the bar's collapse (issue #22) is its one
211
+ * consumer: it is the only transition here whose box changes shape
212
+ * underneath it, so `fast` lands before the eye has followed the pill's
213
+ * snap. Anything else reaching for this step needs the written reason
214
+ * DESIGN.md's duration table asks for.
215
+ */
216
+ settle: "180ms",
217
+ },
218
+ easing: {
219
+ /** Ease-out, for anything arriving. */
220
+ enter: "cubic-bezier(0, 0, 0.2, 1)",
221
+ /** Ease-in, for anything leaving. */
222
+ exit: "cubic-bezier(0.4, 0, 1, 1)",
223
+ /** Symmetric, for a property changing in place on a stationary element. */
224
+ inOut: "cubic-bezier(0.4, 0, 0.2, 1)",
225
+ },
226
+ /** The one scale step an entering surface takes. */
227
+ enterScale: 0.96,
228
+ /**
229
+ * The one squash a control takes under the pointer. Shallower than
230
+ * `enterScale` because a button that moves as far as a popover reads as
231
+ * broken rather than as pressed.
232
+ */
233
+ pressScale: 0.97,
234
+ /** The one travel distance, for a surface that slides rather than scales. */
235
+ enterShift: "4px",
236
+ };
237
+ /**
238
+ * The scale in order, so "one step faster" is arithmetic rather than a guess.
239
+ * Module scope rather than rebuilt per call, and `MotionDuration[]` rather
240
+ * than a tuple so `indexOf` accepts any member of the union.
241
+ */
242
+ const DURATION_STEPS = [
243
+ motion.duration.instant,
244
+ motion.duration.fast,
245
+ motion.duration.settle,
246
+ ];
247
+ /**
248
+ * "An exit is one step faster than its matching enter", as a function.
249
+ *
250
+ * DESIGN.md's duration table states that rule and issue #24's helper claimed
251
+ * to enforce it centrally - while hard-coding `instant` for every exit. That
252
+ * was one step faster for the two surfaces on `fast` and TWO steps faster for
253
+ * anything on `settle`, which is exactly the step issue #22's collapse then
254
+ * took. A rule enforced only for the durations that existed when it was
255
+ * written is not enforced.
256
+ *
257
+ * Both halves of an exit read this: the `transition` a surface leaves on, and
258
+ * the timer that then takes it out of the DOM. DESIGN.md says the removal is
259
+ * deferred "by exactly the exit duration", and two literals that happen to
260
+ * agree today is how that stops being true.
261
+ *
262
+ * `instant` has nowhere faster to go and answers itself, which is the right
263
+ * degradation - a surface entering at 80ms is already at the floor.
264
+ */
265
+ export function exitFor(enter) {
266
+ // `noUncheckedIndexedAccess` is on, and step 0 - or an `enter` the type
267
+ // says cannot arrive - lands on the floor either way.
268
+ return (DURATION_STEPS[DURATION_STEPS.indexOf(enter) - 1] ?? motion.duration.instant);
269
+ }
270
+ /**
271
+ * A `transition` shorthand, composed rather than spelled.
272
+ *
273
+ * This is what makes "no inline durations" mechanically true instead of a
274
+ * promise: the only way to write a transition in this package is to name a
275
+ * duration token and an easing token, and a call site that wanted `140ms`
276
+ * would have to add it to `motion.duration` first, where a reviewer sees it.
277
+ *
278
+ * Deliberately not `transition: all`. `all` transitions properties nobody
279
+ * chose, `width` and `top` among them, and DESIGN.md forbids animating those
280
+ * over a host page for the reflow they cause. Every property is named.
281
+ *
282
+ * `reduced` IS REQUIRED, and that is the whole design of this signature.
283
+ * DESIGN.md's Reduced Motion rule is a system-layer promise, and the first
284
+ * draft of issue #24 kept it by writing `reduced ? [a, b] : [a, b, "transform"]`
285
+ * at seven call sites - seven independent restatements of one rule, none of
286
+ * them checked, and an eighth control added later would simply have omitted it
287
+ * and honoured nothing. Making the preference an argument nobody can leave out
288
+ * moves the rule to the token layer where the rest of this file lives: a call
289
+ * site now states the preference it read and this function decides what that
290
+ * means. Pass `false` only where there is genuinely no reviewer to ask.
291
+ */
292
+ export function transition(properties, duration, easing, reduced) {
293
+ // Under `prefers-reduced-motion: reduce`, `transform` comes out of the list
294
+ // and the rest clamps to `instant`. Both halves are DESIGN.md's, and the
295
+ // colour and opacity cross-fades deliberately survive: being told that a
296
+ // resolve landed is information, and a cross-fade is not vestibular motion.
297
+ const named = reduced
298
+ ? properties.filter((property) => property !== "transform")
299
+ : properties;
300
+ const length = reduced ? motion.duration.instant : duration;
301
+ return named.map((p) => `${p} ${length} ${easing}`).join(", ");
302
+ }
303
+ /**
304
+ * The transform half of the same rule, so it cannot be honoured by one and
305
+ * forgotten by the other.
306
+ *
307
+ * A reduced-motion surface is already at its final position and size on its
308
+ * first frame - so there is no offset to animate out of, which is why this
309
+ * answers `none` rather than shortening anything. `transition` above drops
310
+ * `transform` from the property list in the same breath; a call site that used
311
+ * one of these and not the other would either animate a transform it promised
312
+ * not to, or leave a surface parked at an offset with nothing to move it.
313
+ */
314
+ export function motionTransform(value, reduced) {
315
+ return reduced ? "none" : value;
316
+ }
317
+ /**
318
+ * How long an exit lasts, in milliseconds, for the deferred-unmount timer.
319
+ *
320
+ * A number as well as a string because a `setTimeout` cannot take "80ms" and
321
+ * hard-coding 80 beside the token is how the two drift apart. Parsed from the
322
+ * token rather than declared twice.
323
+ */
324
+ export function durationMs(duration) {
325
+ return Number.parseFloat(duration);
326
+ }
327
+ /**
328
+ * The type scale, as whole roles rather than as a `font:` shorthand.
329
+ *
330
+ * Named `typeScale` rather than `type`: a binding called `type` cannot be
331
+ * imported or re-exported in TypeScript without colliding with the `import {
332
+ * type X }` modifier syntax, so the plain name would have forced every call
333
+ * site into a namespace import.
334
+ *
335
+ * Every one of these used to be spelled `font: 400 13px ...` at the call site,
336
+ * and the shorthand RESETS letter-spacing to normal - so the entire injected
337
+ * surface rendered at default tracking while DESIGN.md's own Typography
338
+ * section calls negative tracking "part of the voice". Size, weight and
339
+ * tracking now travel together in one object, because in an inline-style
340
+ * codebase anything that can be spelled separately eventually is.
341
+ *
342
+ * Every property is stated, including the ones that look like defaults: the
343
+ * toolbar inherits from a host page that may set `letter-spacing`,
344
+ * `line-height` or a font stack on `*`, and an unstated property is the host's
345
+ * to decide.
346
+ */
347
+ /**
348
+ * DESIGN.md's two faces, first, then the fallbacks.
349
+ *
350
+ * The head of each stack is the namespaced family `fontFace.ts` loads into the
351
+ * host document - see the note there for why naming plain `Geist` misses on a
352
+ * customer's page AND on this product's own preview route. `Geist` and
353
+ * `Geist Mono` stay second: a host that genuinely has the face under its real
354
+ * name gets it without a download.
355
+ *
356
+ * These were `Inter, system-ui, …` and `ui-monospace, …` - DESIGN.md's stacks
357
+ * with the brand face deleted from the front of both, which is why the toolbar
358
+ * rendered in the reviewed site's own font.
359
+ */
360
+ const SANS_STACK = `"${TOOLBAR_FONT_SANS}", Geist, Inter, system-ui, -apple-system, sans-serif`;
361
+ const MONO_STACK = `"${TOOLBAR_FONT_MONO}", "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace`;
362
+ export const typeScale = {
363
+ /** body-sm. The default voice of every popover. */
364
+ bodySm: {
365
+ fontFamily: SANS_STACK,
366
+ fontSize: "14px",
367
+ fontWeight: 400,
368
+ lineHeight: "20px",
369
+ letterSpacing: "-0.28px",
370
+ },
371
+ /** body-sm-strong. Names, button labels, the one emphasis this UI has. */
372
+ bodySmStrong: {
373
+ fontFamily: SANS_STACK,
374
+ fontSize: "14px",
375
+ fontWeight: 500,
376
+ lineHeight: "20px",
377
+ letterSpacing: "-0.28px",
378
+ },
379
+ /** display-sm, 600 - the weight ceiling, and only at 20px. */
380
+ displaySm: {
381
+ fontFamily: SANS_STACK,
382
+ fontSize: "20px",
383
+ fontWeight: 600,
384
+ lineHeight: "28px",
385
+ letterSpacing: "-0.6px",
386
+ },
387
+ /** caption. Footnotes and secondary lines that are not technical facts. */
388
+ caption: {
389
+ fontFamily: SANS_STACK,
390
+ fontSize: "12px",
391
+ fontWeight: 400,
392
+ lineHeight: "16px",
393
+ letterSpacing: "0px",
394
+ },
395
+ /** caption-mono. Every identifier: branch, commit, path, timestamp, status. */
396
+ captionMono: {
397
+ fontFamily: MONO_STACK,
398
+ fontSize: "12px",
399
+ fontWeight: 400,
400
+ lineHeight: "16px",
401
+ letterSpacing: "0px",
402
+ },
403
+ /** code, 13px/20px. The captured selector, and nothing else so far. */
404
+ code: {
405
+ fontFamily: MONO_STACK,
406
+ fontSize: "13px",
407
+ fontWeight: 400,
408
+ lineHeight: "20px",
409
+ letterSpacing: "0px",
410
+ },
411
+ };
412
+ /**
413
+ * The two faces, for the places that need a family without a whole role.
414
+ *
415
+ * NOT `Geist`. No `@font-face` in this repo registers that family: `next/font`
416
+ * exposes a hashed name through `--font-geist` and nothing else, so naming
417
+ * "Geist" here has never rendered the brand face on any surface, the preview
418
+ * harness included - it silently fell through to Inter or to system-ui. These
419
+ * are DESIGN.md's own sanctioned substitutes ("Note on Font Substitutes":
420
+ * Inter 400/500/600 for the geometric sans, a monospace of the same voice for
421
+ * the technical layer). Self-hosting the real faces inside this bundle is
422
+ * blocked on a licence decision and on CLAUDE.md section 8's size ceiling.
423
+ *
424
+ * And NOT `var(--font-geist)` at the head of the stack either. A `var()`
425
+ * reference to an undefined custom property makes the whole declaration
426
+ * invalid at computed-value time; `font-family` is inherited, so the toolbar
427
+ * would then inherit the HOST page's typeface on every site that does not
428
+ * define that variable - while looking correct inside apps/web, which does.
429
+ */
430
+ export const fonts = {
431
+ sans: typeScale.bodySm.fontFamily,
432
+ mono: typeScale.captionMono.fontFamily,
433
+ };
434
+ /**
435
+ * The properties a host page's own element rules can set on markup this
436
+ * package renders unstyled, spread FIRST into every `form`, `label`, `header`,
437
+ * `section` and `p` here.
438
+ *
439
+ * The package ships no stylesheet, so nothing leaks OUT. Nothing stopped a
440
+ * host's `form { margin: 0 0 1rem; padding: 1rem; border: 1px solid #ccc }`
441
+ * from leaking IN and repainting the composer, which is the same class of bug
442
+ * as the `box-sizing` note in FeedbackToolbar.tsx and was invisible on the
443
+ * preview route only because apps/web loads Tailwind's preflight.
444
+ */
445
+ export const resetBox = {
446
+ margin: 0,
447
+ padding: 0,
448
+ border: 0,
449
+ background: "transparent",
450
+ boxSizing: "border-box",
451
+ };
452
+ /**
453
+ * Visually hidden, still in the accessibility tree AND still in `textContent`.
454
+ *
455
+ * An icon-only control needs a name, and `aria-label` alone would give it one
456
+ * only for a screen reader: `toHaveText` and `Node.textContent` - which is how
457
+ * both the e2e suite and the unit tests read the resolve control's label - see
458
+ * nothing an `aria-label` says. So the label is real text, clipped.
459
+ *
460
+ * Clipped rather than `display: none` or `visibility: hidden`, because both of
461
+ * those take the text OUT of the accessibility tree and out of `textContent`,
462
+ * which is the whole thing being preserved. The `clip` / `clipPath` pair is
463
+ * belt and braces: `clip` is deprecated and `clip-path` is what actually
464
+ * applies in a modern engine, and this package cannot rely on a host stylesheet
465
+ * to have shipped either.
466
+ */
467
+ export const srOnly = {
468
+ position: "absolute",
469
+ width: "1px",
470
+ height: "1px",
471
+ margin: "-1px",
472
+ padding: 0,
473
+ border: 0,
474
+ overflow: "hidden",
475
+ clip: "rect(0 0 0 0)",
476
+ clipPath: "inset(50%)",
477
+ whiteSpace: "nowrap",
478
+ };
479
+ /** The bar's fixed geometry, from the reference's computed CSS. */
480
+ export const bar = {
481
+ // 8px, DESIGN.md's `rounded.md`. It was 32px, which is on no step in the
482
+ // radius table and put an in-product overlay in marketing-pill territory.
483
+ // The BUTTONS inside it stay pill-shaped: `icon-button-circular` specifies
484
+ // `rounded.full`, and pill on a 36px box is arithmetically the same thing.
485
+ radius: radii.md,
486
+ padding: spacing.xxs,
487
+ gap: spacing.xs,
488
+ /** 36x36 icon buttons, so a 4px-padded bar lands at the reference height. */
489
+ button: "36px",
490
+ /**
491
+ * The bar's height, in BOTH forms: a 36px button, 4px of padding either
492
+ * side and the 1px hairline, which is also the collapsed circle's diameter.
493
+ *
494
+ * Written down rather than left implicit because the layout now has to do
495
+ * arithmetic with it: a mid-edge dock centres the BAR on the viewport, and
496
+ * doing that without moving it when a popover opens means offsetting the
497
+ * dock's own padding by half this. See `dockLayout`.
498
+ */
499
+ height: "46px",
500
+ /**
501
+ * Issue #21's "standard viewport inset", on all four sides.
502
+ *
503
+ * It was `bottom`, because the bottom edge was the only dock there was. The
504
+ * dock is now a box inset from every edge by this one value, which is what
505
+ * makes "the bar always ends flush to an edge with the standard viewport
506
+ * inset" one number rather than four pieces of per-edge arithmetic.
507
+ */
508
+ inset: spacing.xs,
509
+ };
510
+ /**
511
+ * How far a pointer has to travel before a press on the bar is a DRAG rather
512
+ * than a click. Issue #21.
513
+ *
514
+ * A hit-test distance, deliberately not `motion.enterShift`: that is how far
515
+ * an entering surface travels, and the two agreeing on 4px today is not a
516
+ * reason for one to be defined in terms of the other. 6px swallows the wobble
517
+ * a trackpad click or a thumb tap produces, and is well inside the collapsed
518
+ * dot's own 46px box - so a reviewer who means to drag the dot has crossed it
519
+ * long before the pointer leaves the control.
520
+ */
521
+ export const DRAG_THRESHOLD = 6;
522
+ /**
523
+ * The two axes per dock, as a table rather than a `split("-")`.
524
+ *
525
+ * A table because it is also the guard: `isToolbarDock` asks whether the value
526
+ * is one of these keys, and a parser built on string splitting would accept
527
+ * `"middle-center"` and every other spelling nobody designed for.
528
+ */
529
+ const DOCK_AXES = {
530
+ "top-left": { vertical: "top", horizontal: "left" },
531
+ "top-center": { vertical: "top", horizontal: "center" },
532
+ "top-right": { vertical: "top", horizontal: "right" },
533
+ "middle-left": { vertical: "middle", horizontal: "left" },
534
+ "middle-right": { vertical: "middle", horizontal: "right" },
535
+ "bottom-left": { vertical: "bottom", horizontal: "left" },
536
+ "bottom-center": { vertical: "bottom", horizontal: "center" },
537
+ "bottom-right": { vertical: "bottom", horizontal: "right" },
538
+ };
539
+ /**
540
+ * The dock a reviewer who has never dragged anything gets, and the one issue
541
+ * #21 calls the default.
542
+ */
543
+ export const DEFAULT_DOCK = "bottom-center";
544
+ /**
545
+ * Every dock, in the reading order the keyboard control lays them out: three
546
+ * across the top, the two mid-edges, three across the bottom. Exported so the
547
+ * 3x3 grid in the account surface and the tests do not each spell the list.
548
+ */
549
+ export const TOOLBAR_DOCKS = Object.keys(DOCK_AXES);
550
+ /**
551
+ * Beside `isThemeMode`, and for the same reason: this value comes back out of
552
+ * the HOST's `localStorage`, so it can be anything their code, an extension or
553
+ * a newer build of this toolbar wrote there. `hasOwnProperty` rather than
554
+ * `in`, which would accept `"toString"`.
555
+ */
556
+ export function isToolbarDock(value) {
557
+ return (typeof value === "string" &&
558
+ Object.prototype.hasOwnProperty.call(DOCK_AXES, value));
559
+ }
560
+ const CROSS = {
561
+ left: "flex-start",
562
+ center: "center",
563
+ right: "flex-end",
564
+ };
565
+ /**
566
+ * Everything that changes with the dock, derived in one place.
567
+ *
568
+ * Four call sites used to be four independent decisions waiting to disagree -
569
+ * the dock's flex triple, the popover's origin, the popover's gap and the
570
+ * badge's corner. They are all the same two facts (which vertical band, which
571
+ * horizontal band), so they are answered together.
572
+ */
573
+ export function dockLayout(dock) {
574
+ const { vertical, horizontal } = DOCK_AXES[dock];
575
+ const top = vertical === "top";
576
+ return {
577
+ vertical,
578
+ horizontal,
579
+ flexDirection: top ? "column-reverse" : "column",
580
+ // The bar's own edge at every dock - `column-reverse` has already moved
581
+ // that end to the top of the viewport for a top dock - so the popover
582
+ // stacks AWAY from the pill and the pill itself never moves. A mid-edge
583
+ // dock is then a bottom padding rather than a `justify-content`, which is
584
+ // the difference between centring the bar and centring the bar plus
585
+ // whatever it has open.
586
+ paddingBottom: vertical === "middle" ? `calc(50vh - ${bar.height} / 2)` : bar.inset,
587
+ alignItems: CROSS[horizontal],
588
+ popoverOrigin: `${top ? "top" : "bottom"} ${horizontal}`,
589
+ popoverGap: top ? "marginTop" : "marginBottom",
590
+ badge: {
591
+ ...(top ? { bottom: 0 } : { top: 0 }),
592
+ ...(horizontal === "right" ? { left: 0 } : { right: 0 }),
593
+ },
594
+ // The icon is drawn pointing right, so 0 is the right edge and the rest
595
+ // follow clockwise. A mid-edge dock points sideways; top and bottom point
596
+ // at the edge they are flush against.
597
+ chevron: vertical === "top"
598
+ ? -90
599
+ : vertical === "bottom"
600
+ ? 90
601
+ : horizontal === "left"
602
+ ? 180
603
+ : 0,
604
+ };
605
+ }
606
+ // DESIGN.md's elevation colours, the same four `--elevation-*` values
607
+ // globals.css defines, spelled here because this package has no cascade to
608
+ // read them from.
609
+ const LIGHT_RING = "#00000014";
610
+ const LIGHT_DROP_SOFT = "#00000005";
611
+ const LIGHT_DROP = "#0000000a";
612
+ const LIGHT_DROP_DEEP = "#0000000f";
613
+ const DARK_RING = "#ffffff1a";
614
+ const DARK_DROP_SOFT = "#00000029";
615
+ const DARK_DROP = "#0000003d";
616
+ const DARK_DROP_DEEP = "#0000004d";
617
+ const lightShadows = {
618
+ // The one stated divergence from the elevation table, and the reason is in
619
+ // the interface above: a chip over unknown content is not a card on a page.
620
+ // Its ring is INSET like every other, so the border it already draws is the
621
+ // only edge it has.
622
+ bar: `0 0 0 1px ${LIGHT_RING} inset, 0 4px 32px rgba(0, 0, 0, 0.12)`,
623
+ // Level 5 - modal / dialog surfaces and dropdown menus, verbatim.
624
+ popover: `0px 1px 1px ${LIGHT_DROP_SOFT}, 0px 8px 16px -4px ${LIGHT_DROP}, 0px 24px 32px -8px ${LIGHT_DROP_DEEP}, 0 0 0 1px ${LIGHT_RING} inset`,
625
+ // Level 4 - callout panels. The old marker stack ran to 18% black, three
626
+ // times the system's deepest step.
627
+ marker: `0px 2px 2px ${LIGHT_DROP}, 0px 8px 16px -4px ${LIGHT_DROP}, 0 0 0 1px ${LIGHT_RING} inset`,
628
+ insetHairline: `0 0 0 1px ${LIGHT_RING} inset`,
629
+ focusRing: "0 0 0 2px rgba(23, 23, 23, 0.55)",
630
+ };
631
+ const darkShadows = {
632
+ // The reference's own value: a white glow, which is what makes a near-black
633
+ // pill legible over near-black content. Every layer of DESIGN.md's stack is
634
+ // black at 2-6%, which under a #0a0a0a pill on a dark host paints nothing at
635
+ // all - so the glow stays and the missing INSET RING is added beside it.
636
+ // Those are two separate acts and only the second one is a fix.
637
+ bar: `0 0 0 1px ${DARK_RING} inset, 0 4px 32px rgba(255, 255, 255, 0.10)`,
638
+ popover: `0px 1px 1px ${DARK_DROP_SOFT}, 0px 8px 16px -4px ${DARK_DROP}, 0px 24px 32px -8px ${DARK_DROP_DEEP}, 0 0 0 1px ${DARK_RING} inset`,
639
+ marker: `0px 2px 2px ${DARK_DROP}, 0px 8px 16px -4px ${DARK_DROP}, 0 0 0 1px ${DARK_RING} inset`,
640
+ insetHairline: `0 0 0 1px ${DARK_RING} inset`,
641
+ focusRing: "0 0 0 2px rgba(255, 255, 255, 0.65)",
642
+ };
643
+ export const lightTheme = {
644
+ resolved: "light",
645
+ colors: lightPalette,
646
+ shadows: lightShadows,
647
+ };
648
+ export const darkTheme = {
649
+ resolved: "dark",
650
+ colors: darkPalette,
651
+ shadows: darkShadows,
652
+ };
653
+ export function themeFor(resolved) {
654
+ return resolved === "dark" ? darkTheme : lightTheme;
655
+ }
656
+ /**
657
+ * Where the reviewer's manual override is remembered. `localStorage` on the
658
+ * host origin, because the preference belongs to this person on this app and
659
+ * there is nowhere else to put it that survives a reload without a round trip
660
+ * the disabled-build requirement forbids.
661
+ */
662
+ export const THEME_STORAGE_KEY = "bitvea.feedback-toolbar.theme";
663
+ /** Media query the default follows, read from the host document. */
664
+ export const DARK_SCHEME_QUERY = "(prefers-color-scheme: dark)";
665
+ /**
666
+ * The other preference the toolbar reads from the HOST document, beside it
667
+ * because they are answered the same way - one `matchMedia` subscription each,
668
+ * through `useSyncExternalStore` so there is a server snapshot.
669
+ *
670
+ * It is the host's document rather than ours because there is only one: the
671
+ * reviewer's own OS setting, which the page the toolbar is injected into has
672
+ * no say in. See `usePrefersReducedMotion` in motion.ts.
673
+ */
674
+ export const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
675
+ export function isThemeMode(value) {
676
+ return value === "system" || value === "light" || value === "dark";
677
+ }
678
+ /**
679
+ * Above almost everything. Host apps routinely use z-indexes in the thousands
680
+ * for their own modals; the toolbar has to sit above those to be usable at
681
+ * all, and it is the reviewer's own overlay rather than part of the page.
682
+ */
683
+ export const Z_INDEX = 2_147_483_000;
684
+ /**
685
+ * The toolbar's own internal stacking order, inside the root that `Z_INDEX`
686
+ * lifts above the host page.
687
+ *
688
+ * This exists because every one of these surfaces used to pick its own number
689
+ * and the bar picked none at all - it inherited `z-index: auto`, which loses
690
+ * to any positive value in the same stacking context. The consequences were
691
+ * not cosmetic: the picking overlay painted over the bar and swallowed the
692
+ * click that cancels picking, leaving a reviewer with no way out of crosshair
693
+ * mode, and the inbox sheet covered the account button on any viewport under
694
+ * about 1024px.
695
+ *
696
+ * So the order is written down once, and the rule it encodes is that THE BAR
697
+ * IS ALWAYS REACHABLE. It is the only persistent chrome the toolbar has; every
698
+ * other surface is something the bar opened and the bar has to be able to
699
+ * close. Everything else stacks under it, most transient first.
700
+ *
701
+ * `thread` sits above `surface` rather than tying with it, and issue #23 is
702
+ * why. A z-index only orders the siblings that declare one; equal values fall
703
+ * back to DOM order, and the inbox sheet is rendered after the pin layer in
704
+ * FeedbackToolbar.tsx - so a thread parked at `surface` loses to the sheet on
705
+ * any pin under its 340px. That is the common case rather than the exotic one:
706
+ * `focusPin` deliberately leaves the sheet open when a reviewer clicks a row,
707
+ * because they are working down a list, and the thread they just asked for is
708
+ * then the surface they are reading. It goes above the list they asked from.
709
+ *
710
+ * The other half of #23 was that an anchored thread never reached this ladder
711
+ * at all. It renders inside its marker's wrapper, and that wrapper is
712
+ * `position: fixed` with a z-index - a stacking context whatever its opacity -
713
+ * so the rung a thread paints at is the one the WRAPPER declares, not one the
714
+ * thread could set on itself. `PinMarker` therefore moves its wrapper between
715
+ * `marker` and `thread` as the thread opens, and nothing inside that wrapper
716
+ * declares a z-index of its own. Read this list as the order that actually
717
+ * renders; when it stops being that, the code is what is wrong.
718
+ *
719
+ * WHAT IS DELIBERATELY NOT IN THIS LADDER: the note and account popovers.
720
+ * `surface` used to be documented as "threads, composers, popovers and the
721
+ * inbox sheet" and only the sheet was ever really there - the popovers are
722
+ * children of the DOCK, so they ride its `bar` rung and have never declared
723
+ * one of their own. Splitting `thread` out of `surface` for issue #23 made
724
+ * that list true for threads and composers and left the popovers unmentioned,
725
+ * which is worse than the wrong entry: a reader could not place them at all.
726
+ * They are chrome rather than surfaces. They are anchored to the bar, opened
727
+ * and closed by the button beside them, and clamped to the same strip, so
728
+ * "above everything, with the bar" is the whole of their layering and a rung
729
+ * would be a second place to state it.
730
+ */
731
+ export const layers = {
732
+ /** The picking wash. Under everything, including its own markers. */
733
+ picker: Z_INDEX - 5,
734
+ /** Pin markers: part of the page's review state, so below any open surface. */
735
+ marker: Z_INDEX - 4,
736
+ /** Panels the bar opened and the bar closes again: the inbox sheet. */
737
+ surface: Z_INDEX - 3,
738
+ /**
739
+ * The one thread the reviewer is reading, and the composer they are writing
740
+ * the next one in - a composer is a thread that does not exist yet, so it
741
+ * shares this rung and wins the tie by being rendered last.
742
+ */
743
+ thread: Z_INDEX - 2,
744
+ /** Alerts that must be seen over an open surface. */
745
+ alert: Z_INDEX - 1,
746
+ /** The floating bar. Nothing the toolbar renders may cover it. */
747
+ bar: Z_INDEX,
748
+ };
749
+ //# sourceMappingURL=tokens.js.map