@burdenoff/website-sdk 2026.921.1 → 2026.922.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.
package/dist/index.mjs CHANGED
@@ -7999,6 +7999,7 @@ function toPayload(staged) {
7999
7999
  textContent
8000
8000
  }));
8001
8001
  }
8002
+ var SHORT_VIEWPORT_PX = 420;
8002
8003
  var EXPLORE_CSS = `
8003
8004
  @keyframes boff-explore-pulse {
8004
8005
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
@@ -8033,6 +8034,12 @@ var EXPLORE_CSS = `
8033
8034
  .boff-explore-scroll {
8034
8035
  scrollbar-width: thin;
8035
8036
  scrollbar-color: hsl(var(--border)) transparent;
8037
+ /* Stop the transcript handing its scroll to the page. Without this, the first
8038
+ swipe that exhausts the transcript chains to the document \u2014 and on a host site
8039
+ that is 65-70% footer, which is how a phone ends up showing nothing but the
8040
+ footer. Engines that do not support this property ignore the line and keep
8041
+ today's behaviour; the scroll correction above covers them. */
8042
+ overscroll-behavior: contain;
8036
8043
  }
8037
8044
  .boff-explore-scroll::-webkit-scrollbar { width: 10px; }
8038
8045
  .boff-explore-scroll::-webkit-scrollbar-track { background: transparent; }
@@ -8150,6 +8157,68 @@ var EXPLORE_CSS = `
8150
8157
  .boff-explore-busy::after { animation: none; }
8151
8158
  .boff-explore-rise { animation: none; opacity: 1; transform: none; }
8152
8159
  }
8160
+
8161
+ /* ---- Pane height ----------------------------------------------------------------------
8162
+ This used to be one inline declaration, \`height: calc(100dvh - 64px)\`, and it carried two
8163
+ defects. An inline style cannot carry a fallback, so every engine without \`dvh\` (iOS
8164
+ Safari < 15.4, Chrome/WebView < 108, Samsung Internet < 21, Firefox < 101) dropped it
8165
+ whole; and the 64px came from a prop hard-coded to 64 on all 41 host pages that matches
8166
+ none of them \u2014 the burdenoff/algoshred chrome is 80px, the sticky-header sites are 65.
8167
+
8168
+ A class plus custom properties lets the cascade do the work: \`vh\` is the floor and \`dvh\`
8169
+ an upgrade where it exists, while the two measured values stay *lengths*, never a height.
8170
+ That distinction is the whole prerender contract. The host sites snapshot \`/explore\` by
8171
+ loading the built SPA in headless Chromium and writing the POST-HYDRATION DOM to
8172
+ dist/explore/index.html, so every attribute and inline value this component sets after
8173
+ mount IS the static HTML CloudFront serves. A measured pixel *height* would therefore
8174
+ hand every phone the prerender viewport's height until the bundle executes; a measured
8175
+ *offset* is a site-header height, which is the same on every device to within a few
8176
+ pixels, and the height built from it stays viewport-relative on the visitor's own screen.
8177
+
8178
+ --boff-explore-offset chrome above the pane (measured root top; the prop pre-measure)
8179
+ --boff-explore-inset the slice of the layout viewport hidden below the visible area \u2014
8180
+ the on-screen keyboard, which \`dvh\` does not track. Zero unless
8181
+ something is actually covering the bottom, so a snapshot taken
8182
+ with no keyboard open never carries it. */
8183
+ .boff-explore-viewport {
8184
+ height: calc(100vh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
8185
+ }
8186
+ @supports (height: 100dvh) {
8187
+ .boff-explore-viewport {
8188
+ height: calc(100dvh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
8189
+ }
8190
+ }
8191
+
8192
+ /* Landscape phones and other short viewports: a one-screen pane leaves a ~124px transcript
8193
+ sliver under ~610px of chrome and content. At or below the threshold the pane stops
8194
+ pretending to be a window and lets the page's own scrolling carry the transcript. The
8195
+ min-height is the available height rather than a fixed rem value, so the fallback can
8196
+ never be taller than the screen it is rescuing. */
8197
+ @media (max-height: ${SHORT_VIEWPORT_PX}px) {
8198
+ .boff-explore-viewport {
8199
+ height: auto;
8200
+ min-height: calc(100vh - var(--boff-explore-offset, 0px));
8201
+ }
8202
+ }
8203
+ @supports (height: 100dvh) {
8204
+ @media (max-height: ${SHORT_VIEWPORT_PX}px) {
8205
+ .boff-explore-viewport {
8206
+ min-height: calc(100dvh - var(--boff-explore-offset, 0px));
8207
+ }
8208
+ }
8209
+ }
8210
+
8211
+ /* ---- Composer dock --------------------------------------------------------------------
8212
+ Three declarations, each the fallback for the one after it: a plain 1rem where \`max()\`
8213
+ is unknown, then the measured overlap of any FOREIGN fixed chrome sitting over the
8214
+ composer (the host sites' cookie banner is 229px tall and lands exactly there), then the
8215
+ same plus the home-indicator inset. \`--boff-explore-chrome\` is set on the root only once
8216
+ an overlap has actually been measured, so everywhere else this collapses to 1rem. */
8217
+ .boff-explore-composer {
8218
+ padding-bottom: 1rem;
8219
+ padding-bottom: max(1rem, var(--boff-explore-chrome, 0px));
8220
+ padding-bottom: max(1rem, env(safe-area-inset-bottom), var(--boff-explore-chrome, 0px));
8221
+ }
8153
8222
  `;
8154
8223
  function ExploreStyles() {
8155
8224
  return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -8171,6 +8240,8 @@ var CAPABILITIES = [
8171
8240
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
8172
8241
  }
8173
8242
  ];
8243
+ var MIN_VISIBLE_FRACTION = 0.25;
8244
+ var SDK_OVERLAY_SELECTOR = "[data-boff-explore]";
8174
8245
  var CTA_VARIANTS = {
8175
8246
  CONTACT: "default",
8176
8247
  WAITLIST: "default",
@@ -8186,6 +8257,28 @@ function normalizeOffset(offset) {
8186
8257
  if (offset === void 0) return "0px";
8187
8258
  return typeof offset === "number" ? `${offset}px` : offset;
8188
8259
  }
8260
+ function isOwnElement(el, root) {
8261
+ if (el === document.body || el === document.documentElement) return true;
8262
+ if (root.contains(el)) return true;
8263
+ return el.closest(SDK_OVERLAY_SELECTOR) !== null;
8264
+ }
8265
+ function visualViewportHeight() {
8266
+ return window.visualViewport?.height ?? window.innerHeight;
8267
+ }
8268
+ function clamp(value, min, max) {
8269
+ return Math.min(Math.max(value, min), max);
8270
+ }
8271
+ function isRestoredNavigation() {
8272
+ const entries = window.performance?.getEntriesByType?.("navigation");
8273
+ const entry = entries?.[0];
8274
+ if (!entry) return true;
8275
+ const navigation = entry;
8276
+ return navigation.type === "reload" || navigation.type === "back_forward";
8277
+ }
8278
+ function pageOwnsScrolling(el) {
8279
+ if (window.innerHeight > SHORT_VIEWPORT_PX) return false;
8280
+ return el.scrollHeight - el.clientHeight <= 1;
8281
+ }
8189
8282
  function isInternalPath(url) {
8190
8283
  return url.startsWith("/") && !url.startsWith("//");
8191
8284
  }
@@ -9034,6 +9127,8 @@ function ExplorePage({
9034
9127
  const pickQueueRef = useRef(Promise.resolve());
9035
9128
  const [shareLabel, setShareLabel] = useState(T.share);
9036
9129
  const [shareUrl, setShareUrl] = useState(null);
9130
+ const rootRef = useRef(null);
9131
+ const composerRef = useRef(null);
9037
9132
  const scrollRef = useRef(null);
9038
9133
  const latestAssistantRef = useRef(null);
9039
9134
  const alignedForRef = useRef(null);
@@ -9061,11 +9156,196 @@ function ExplorePage({
9061
9156
  el.style.height = "auto";
9062
9157
  el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
9063
9158
  }, [draft]);
9159
+ const [paneOffset, setPaneOffset] = useState(null);
9160
+ const [bottomInset, setBottomInset] = useState(0);
9161
+ const paneOffsetRef = useRef(0);
9162
+ const measurePane = useCallback(() => {
9163
+ if (heightMode !== "viewport") return;
9164
+ const el = rootRef.current;
9165
+ if (!el) return;
9166
+ const layoutHeight = window.innerHeight;
9167
+ if (layoutHeight === 0) return;
9168
+ const top = Math.round(
9169
+ clamp(el.getBoundingClientRect().top, 0, layoutHeight)
9170
+ );
9171
+ const viewport = window.visualViewport;
9172
+ const inset = viewport ? Math.round(
9173
+ clamp(
9174
+ layoutHeight - viewport.height - viewport.offsetTop,
9175
+ 0,
9176
+ layoutHeight
9177
+ )
9178
+ ) : 0;
9179
+ paneOffsetRef.current = top;
9180
+ setPaneOffset((current) => current === top ? current : top);
9181
+ setBottomInset((current) => current === inset ? current : inset);
9182
+ }, [heightMode]);
9183
+ const [chromeOverlap, setChromeOverlap] = useState(0);
9184
+ const measureChrome = useCallback(() => {
9185
+ const root = rootRef.current;
9186
+ const dock = composerRef.current;
9187
+ if (!root || !dock) return;
9188
+ if (typeof document.elementFromPoint !== "function") return;
9189
+ const viewportHeight = visualViewportHeight();
9190
+ const rect = dock.getBoundingClientRect();
9191
+ if (rect.width === 0 || viewportHeight === 0) return;
9192
+ if (rect.bottom <= 0 || rect.top >= viewportHeight) return;
9193
+ const edge = Math.min(rect.bottom, viewportHeight);
9194
+ const hit = document.elementFromPoint(rect.left + rect.width / 2, edge - 1);
9195
+ if (!hit || isOwnElement(hit, root)) {
9196
+ setChromeOverlap(0);
9197
+ return;
9198
+ }
9199
+ const overlap = Math.round(edge - hit.getBoundingClientRect().top);
9200
+ setChromeOverlap(overlap > 0 && overlap < viewportHeight ? overlap : 0);
9201
+ }, []);
9202
+ const remeasure = useCallback(() => {
9203
+ measurePane();
9204
+ measureChrome();
9205
+ }, [measurePane, measureChrome]);
9206
+ useEffect(() => {
9207
+ if (typeof window === "undefined") return;
9208
+ let frame = 0;
9209
+ const run = () => {
9210
+ frame = 0;
9211
+ remeasure();
9212
+ };
9213
+ const schedule = () => {
9214
+ if (frame) return;
9215
+ if (typeof window.requestAnimationFrame !== "function") {
9216
+ remeasure();
9217
+ return;
9218
+ }
9219
+ frame = window.requestAnimationFrame(run);
9220
+ };
9221
+ remeasure();
9222
+ const viewport = window.visualViewport;
9223
+ window.addEventListener("resize", schedule);
9224
+ window.addEventListener("orientationchange", schedule);
9225
+ viewport?.addEventListener("resize", schedule);
9226
+ viewport?.addEventListener("scroll", schedule);
9227
+ const observer = typeof MutationObserver === "function" ? new MutationObserver((records) => {
9228
+ const el = rootRef.current;
9229
+ if (el && records.every((record) => el.contains(record.target)))
9230
+ return;
9231
+ schedule();
9232
+ }) : null;
9233
+ observer?.observe(document.body, { childList: true, subtree: true });
9234
+ return () => {
9235
+ if (frame) window.cancelAnimationFrame(frame);
9236
+ window.removeEventListener("resize", schedule);
9237
+ window.removeEventListener("orientationchange", schedule);
9238
+ viewport?.removeEventListener("resize", schedule);
9239
+ viewport?.removeEventListener("scroll", schedule);
9240
+ observer?.disconnect();
9241
+ setPaneOffset(null);
9242
+ setBottomInset(0);
9243
+ setChromeOverlap(0);
9244
+ };
9245
+ }, [remeasure]);
9246
+ useEffect(() => {
9247
+ measureChrome();
9248
+ }, [measureChrome, paneOffset, bottomInset]);
9249
+ useEffect(() => {
9250
+ if (heightMode !== "viewport" || typeof window === "undefined") return;
9251
+ const { history: browserHistory } = window;
9252
+ const previousRestoration = "scrollRestoration" in browserHistory ? browserHistory.scrollRestoration : null;
9253
+ if (previousRestoration !== null)
9254
+ browserHistory.scrollRestoration = "manual";
9255
+ let frame = 0;
9256
+ let visitorMoved = false;
9257
+ const onVisitorMove = () => {
9258
+ visitorMoved = true;
9259
+ };
9260
+ const align = () => {
9261
+ if (visitorMoved) return;
9262
+ const el = rootRef.current;
9263
+ if (!el) return;
9264
+ const viewportHeight = visualViewportHeight();
9265
+ const rect = el.getBoundingClientRect();
9266
+ if (rect.height === 0 || viewportHeight === 0) return;
9267
+ const visible = Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0);
9268
+ const reachable = Math.min(rect.height, viewportHeight);
9269
+ if (visible >= reachable * MIN_VISIBLE_FRACTION) return;
9270
+ window.scrollTo({
9271
+ top: Math.max(0, window.scrollY + rect.top),
9272
+ left: window.scrollX,
9273
+ behavior: "auto"
9274
+ });
9275
+ if (typeof document.elementFromPoint === "function") {
9276
+ const probe = document.elementFromPoint(
9277
+ Math.max(0, rect.left + rect.width / 2),
9278
+ 1
9279
+ );
9280
+ if (probe && !isOwnElement(probe, el)) {
9281
+ const chrome = probe.getBoundingClientRect().bottom;
9282
+ if (chrome > 0 && chrome < viewportHeight / 2) {
9283
+ window.scrollBy({ top: -chrome, left: 0, behavior: "auto" });
9284
+ }
9285
+ }
9286
+ }
9287
+ remeasure();
9288
+ };
9289
+ const alignIfRestored = () => {
9290
+ if (!isRestoredNavigation()) return;
9291
+ align();
9292
+ };
9293
+ alignIfRestored();
9294
+ if (typeof window.requestAnimationFrame === "function") {
9295
+ frame = window.requestAnimationFrame(alignIfRestored);
9296
+ }
9297
+ const onPageShow = (event) => {
9298
+ if (!event.persisted) {
9299
+ alignIfRestored();
9300
+ return;
9301
+ }
9302
+ visitorMoved = false;
9303
+ align();
9304
+ };
9305
+ window.addEventListener("pageshow", onPageShow);
9306
+ window.addEventListener("wheel", onVisitorMove, { passive: true });
9307
+ window.addEventListener("touchmove", onVisitorMove, { passive: true });
9308
+ window.addEventListener("keydown", onVisitorMove, { passive: true });
9309
+ return () => {
9310
+ if (frame) window.cancelAnimationFrame(frame);
9311
+ window.removeEventListener("pageshow", onPageShow);
9312
+ window.removeEventListener("wheel", onVisitorMove);
9313
+ window.removeEventListener("touchmove", onVisitorMove);
9314
+ window.removeEventListener("keydown", onVisitorMove);
9315
+ if (previousRestoration !== null)
9316
+ browserHistory.scrollRestoration = previousRestoration;
9317
+ };
9318
+ }, [heightMode, remeasure]);
9064
9319
  const handleScroll = useCallback(() => {
9065
9320
  const el = scrollRef.current;
9066
9321
  if (!el) return;
9067
9322
  stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
9068
9323
  }, []);
9324
+ useEffect(() => {
9325
+ if (typeof window === "undefined") return;
9326
+ let frame = 0;
9327
+ const run = () => {
9328
+ frame = 0;
9329
+ const el = scrollRef.current;
9330
+ const dock = composerRef.current;
9331
+ if (!el || !dock) return;
9332
+ if (!pageOwnsScrolling(el)) return;
9333
+ stickToBottomRef.current = dock.getBoundingClientRect().bottom - visualViewportHeight() < 80;
9334
+ };
9335
+ const schedule = () => {
9336
+ if (frame) return;
9337
+ if (typeof window.requestAnimationFrame !== "function") {
9338
+ run();
9339
+ return;
9340
+ }
9341
+ frame = window.requestAnimationFrame(run);
9342
+ };
9343
+ window.addEventListener("scroll", schedule, { passive: true });
9344
+ return () => {
9345
+ if (frame) window.cancelAnimationFrame(frame);
9346
+ window.removeEventListener("scroll", schedule);
9347
+ };
9348
+ }, []);
9069
9349
  const latestAssistantId = useMemo(() => {
9070
9350
  for (let i = messages.length - 1; i >= 0; i -= 1) {
9071
9351
  const message = messages[i];
@@ -9081,16 +9361,32 @@ function ExplorePage({
9081
9361
  if (!stickToBottomRef.current) return;
9082
9362
  if (anchor.offsetHeight === 0) return;
9083
9363
  alignedForRef.current = latestAssistantId;
9084
- const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
9085
- el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
9364
+ if (!pageOwnsScrolling(el)) {
9365
+ const delta2 = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
9366
+ el.scrollTop = Math.max(0, el.scrollTop + delta2 - 12);
9367
+ return;
9368
+ }
9369
+ const delta = Math.round(
9370
+ anchor.getBoundingClientRect().top - paneOffsetRef.current - 12
9371
+ );
9372
+ if (delta !== 0) window.scrollBy({ top: delta, left: 0, behavior: "auto" });
9086
9373
  }, [latestAssistantId, messages]);
9087
9374
  useEffect(() => {
9375
+ if (messages.length === 0) return;
9088
9376
  if (!stickToBottomRef.current) return;
9089
9377
  if (latestAssistantId && alignedForRef.current === latestAssistantId)
9090
9378
  return;
9091
9379
  const el = scrollRef.current;
9092
9380
  if (!el) return;
9093
- el.scrollTop = el.scrollHeight;
9381
+ if (!pageOwnsScrolling(el)) {
9382
+ el.scrollTop = el.scrollHeight;
9383
+ return;
9384
+ }
9385
+ const dock = composerRef.current ?? el;
9386
+ const delta = Math.round(
9387
+ dock.getBoundingClientRect().bottom - visualViewportHeight()
9388
+ );
9389
+ if (delta > 0) window.scrollBy({ top: delta, left: 0, behavior: "auto" });
9094
9390
  }, [activity, latestAssistantId, messages]);
9095
9391
  const handleShare = useCallback(async () => {
9096
9392
  setShareLabel(T.copying);
@@ -9188,16 +9484,27 @@ function ExplorePage({
9188
9484
  const captchaOn = Boolean(
9189
9485
  catalog.captchaRequired && catalog.recaptchaSiteKey
9190
9486
  );
9191
- const rootStyle = heightMode === "viewport" ? { height: `calc(100dvh - ${normalizeOffset(viewportOffset)})` } : void 0;
9487
+ const rootStyle = {};
9488
+ if (heightMode === "viewport") {
9489
+ rootStyle["--boff-explore-offset"] = paneOffset === null ? normalizeOffset(viewportOffset) : `${paneOffset}px`;
9490
+ if (bottomInset > 0) {
9491
+ rootStyle["--boff-explore-inset"] = `${bottomInset}px`;
9492
+ }
9493
+ }
9494
+ if (chromeOverlap > 0) {
9495
+ rootStyle["--boff-explore-chrome"] = `${chromeOverlap}px`;
9496
+ }
9192
9497
  return /* @__PURE__ */ jsxs(
9193
9498
  "div",
9194
9499
  {
9500
+ ref: rootRef,
9195
9501
  "data-boff-explore": "page",
9196
9502
  "data-phase": phase,
9197
9503
  "data-locale": activeLocale,
9198
9504
  dir: rtl ? "rtl" : void 0,
9199
9505
  className: cn(
9200
9506
  "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
9507
+ heightMode === "viewport" && "boff-explore-viewport",
9201
9508
  heightMode === "auto" && "min-h-[70vh]",
9202
9509
  className
9203
9510
  ),
@@ -9431,249 +9738,257 @@ function ExplorePage({
9431
9738
  ]
9432
9739
  }
9433
9740
  ) : null,
9434
- /* @__PURE__ */ jsx("div", { className: "border-t border-border bg-background/85 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3 backdrop-blur-md", children: /* @__PURE__ */ jsxs(
9435
- "form",
9741
+ /* @__PURE__ */ jsx(
9742
+ "div",
9436
9743
  {
9437
- "data-boff-explore": "composer",
9438
- className: "mx-auto w-full max-w-3xl",
9439
- onSubmit: (event) => {
9440
- event.preventDefault();
9441
- submitDraft();
9442
- },
9443
- children: [
9444
- error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
9445
- staged.length > 0 ? /* @__PURE__ */ jsx(
9446
- "ul",
9447
- {
9448
- "data-boff-explore": "attachments",
9449
- className: "mb-2 flex flex-wrap gap-1.5",
9450
- "aria-label": T.attach,
9451
- children: staged.map((entry) => /* @__PURE__ */ jsxs(
9452
- "li",
9744
+ ref: composerRef,
9745
+ "data-boff-explore": "composer-dock",
9746
+ className: "boff-explore-composer border-t border-border bg-background/85 px-4 pt-3 backdrop-blur-md",
9747
+ children: /* @__PURE__ */ jsxs(
9748
+ "form",
9749
+ {
9750
+ "data-boff-explore": "composer",
9751
+ className: "mx-auto w-full max-w-3xl",
9752
+ onSubmit: (event) => {
9753
+ event.preventDefault();
9754
+ submitDraft();
9755
+ },
9756
+ children: [
9757
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
9758
+ staged.length > 0 ? /* @__PURE__ */ jsx(
9759
+ "ul",
9760
+ {
9761
+ "data-boff-explore": "attachments",
9762
+ className: "mb-2 flex flex-wrap gap-1.5",
9763
+ "aria-label": T.attach,
9764
+ children: staged.map((entry) => /* @__PURE__ */ jsxs(
9765
+ "li",
9766
+ {
9767
+ "data-boff-explore": "attachment",
9768
+ "data-text-extracted": entry.textExtracted ? "true" : "false",
9769
+ className: "flex max-w-full items-center gap-1.5 rounded-full border border-border bg-muted/60 py-1 pl-2.5 pr-1 text-xs",
9770
+ children: [
9771
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
9772
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
9773
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
9774
+ formatBytes(entry.size),
9775
+ entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
9776
+ ] }),
9777
+ /* @__PURE__ */ jsx(
9778
+ "button",
9779
+ {
9780
+ type: "button",
9781
+ "data-boff-explore": "attachment-remove",
9782
+ onClick: () => removeAttachment(entry.id),
9783
+ "aria-label": fill(T.removeAttachment, { name: entry.name }),
9784
+ className: "ml-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground",
9785
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
9786
+ }
9787
+ )
9788
+ ]
9789
+ },
9790
+ entry.id
9791
+ ))
9792
+ }
9793
+ ) : null,
9794
+ attachmentNotice !== null ? /* @__PURE__ */ jsx(
9795
+ "p",
9453
9796
  {
9454
- "data-boff-explore": "attachment",
9455
- "data-text-extracted": entry.textExtracted ? "true" : "false",
9456
- className: "flex max-w-full items-center gap-1.5 rounded-full border border-border bg-muted/60 py-1 pl-2.5 pr-1 text-xs",
9797
+ "data-boff-explore": "attachment-notice",
9798
+ role: "status",
9799
+ className: "mb-2 text-xs text-destructive",
9800
+ children: attachmentNotice
9801
+ }
9802
+ ) : null,
9803
+ /* @__PURE__ */ jsxs(
9804
+ "div",
9805
+ {
9806
+ className: cn(
9807
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
9808
+ busy && "boff-explore-busy",
9809
+ overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
9810
+ ),
9457
9811
  children: [
9458
- /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
9459
- /* @__PURE__ */ jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
9460
- /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
9461
- formatBytes(entry.size),
9462
- entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
9463
- ] }),
9464
9812
  /* @__PURE__ */ jsx(
9465
- "button",
9813
+ Textarea,
9466
9814
  {
9815
+ ref: textareaRef,
9816
+ value: draft,
9817
+ rows: 1,
9818
+ disabled: composerDisabled,
9819
+ onChange: (event) => setDraft(event.target.value),
9820
+ onKeyDown: handleKeyDown,
9821
+ onCompositionStart: () => {
9822
+ composingRef.current = true;
9823
+ },
9824
+ onCompositionEnd: () => {
9825
+ composingRef.current = false;
9826
+ },
9827
+ "aria-label": "Ask a question",
9828
+ placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
9829
+ product: focusedProductName
9830
+ }) : T.placeholder,
9831
+ className: "max-h-[200px] min-h-[44px] w-full min-w-0 flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-base shadow-none focus-visible:ring-0 sm:text-sm"
9832
+ }
9833
+ ),
9834
+ /* @__PURE__ */ jsx(
9835
+ "input",
9836
+ {
9837
+ ref: fileInputRef,
9838
+ type: "file",
9839
+ multiple: true,
9840
+ className: "hidden",
9841
+ onChange: (event) => {
9842
+ void handleFilesPicked(event.target.files);
9843
+ event.target.value = "";
9844
+ }
9845
+ }
9846
+ ),
9847
+ /* @__PURE__ */ jsx(
9848
+ Button,
9849
+ {
9850
+ "data-boff-explore": "attach",
9851
+ type: "button",
9852
+ size: "icon",
9853
+ variant: "ghost",
9854
+ disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
9855
+ "aria-label": T.attach,
9856
+ title: `${T.attach} \u2014 ${fill(T.attachHint, {
9857
+ max: String(MAX_ATTACHMENTS),
9858
+ size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
9859
+ })}`,
9860
+ onClick: () => fileInputRef.current?.click(),
9861
+ children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
9862
+ }
9863
+ ),
9864
+ speech.supported ? /* @__PURE__ */ jsx(
9865
+ Button,
9866
+ {
9867
+ "data-boff-explore": "mic",
9868
+ "data-listening": speech.listening ? "true" : "false",
9467
9869
  type: "button",
9468
- "data-boff-explore": "attachment-remove",
9469
- onClick: () => removeAttachment(entry.id),
9470
- "aria-label": fill(T.removeAttachment, { name: entry.name }),
9471
- className: "ml-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-background hover:text-foreground",
9472
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
9870
+ size: "icon",
9871
+ variant: speech.listening ? "default" : "ghost",
9872
+ disabled: composerDisabled,
9873
+ "aria-label": speech.listening ? T.stopDictating : T.dictate,
9874
+ "aria-pressed": speech.listening,
9875
+ title: speech.listening ? T.stopDictating : T.dictate,
9876
+ onClick: speech.toggle,
9877
+ className: cn(speech.listening && "animate-pulse"),
9878
+ children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" })
9879
+ }
9880
+ ) : null,
9881
+ busy ? /* @__PURE__ */ jsx(
9882
+ Button,
9883
+ {
9884
+ "data-boff-explore": "stop",
9885
+ type: "button",
9886
+ size: "icon",
9887
+ variant: "secondary",
9888
+ "aria-label": T.stop,
9889
+ onClick: stop,
9890
+ children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
9891
+ }
9892
+ ) : /* @__PURE__ */ jsx(
9893
+ Button,
9894
+ {
9895
+ "data-boff-explore": "send",
9896
+ type: "submit",
9897
+ size: "icon",
9898
+ disabled: !canSubmit,
9899
+ "aria-label": T.send,
9900
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
9473
9901
  }
9474
9902
  )
9475
9903
  ]
9476
- },
9477
- entry.id
9478
- ))
9479
- }
9480
- ) : null,
9481
- attachmentNotice !== null ? /* @__PURE__ */ jsx(
9482
- "p",
9483
- {
9484
- "data-boff-explore": "attachment-notice",
9485
- role: "status",
9486
- className: "mb-2 text-xs text-destructive",
9487
- children: attachmentNotice
9488
- }
9489
- ) : null,
9490
- /* @__PURE__ */ jsxs(
9491
- "div",
9492
- {
9493
- className: cn(
9494
- "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
9495
- busy && "boff-explore-busy",
9496
- overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
9904
+ }
9497
9905
  ),
9498
- children: [
9499
- /* @__PURE__ */ jsx(
9500
- Textarea,
9906
+ speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxs(
9907
+ "p",
9908
+ {
9909
+ "data-boff-explore": "dictation",
9910
+ className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
9911
+ "aria-live": "polite",
9912
+ children: [
9913
+ /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
9914
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
9915
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
9916
+ ] }),
9917
+ /* @__PURE__ */ jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
9918
+ ]
9919
+ }
9920
+ ) : null,
9921
+ speech.error !== null ? /* @__PURE__ */ jsx(
9922
+ "p",
9923
+ {
9924
+ "data-boff-explore": "dictation-error",
9925
+ className: "mt-2 text-xs text-destructive",
9926
+ children: speech.error
9927
+ }
9928
+ ) : null,
9929
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
9930
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
9931
+ remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
9932
+ remainingToday,
9933
+ " message",
9934
+ remainingToday === 1 ? "" : "s",
9935
+ " left today"
9936
+ ] }) : null,
9937
+ /* @__PURE__ */ jsxs(
9938
+ "span",
9501
9939
  {
9502
- ref: textareaRef,
9503
- value: draft,
9504
- rows: 1,
9505
- disabled: composerDisabled,
9506
- onChange: (event) => setDraft(event.target.value),
9507
- onKeyDown: handleKeyDown,
9508
- onCompositionStart: () => {
9509
- composingRef.current = true;
9510
- },
9511
- onCompositionEnd: () => {
9512
- composingRef.current = false;
9513
- },
9514
- "aria-label": "Ask a question",
9515
- placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
9516
- product: focusedProductName
9517
- }) : T.placeholder,
9518
- className: "max-h-[200px] min-h-[44px] w-full min-w-0 flex-1 resize-none border-0 bg-transparent px-2 py-2.5 text-base shadow-none focus-visible:ring-0 sm:text-sm"
9940
+ className: cn(
9941
+ "ml-auto tabular-nums",
9942
+ overLimit && "font-medium text-destructive"
9943
+ ),
9944
+ children: [
9945
+ draft.length.toLocaleString(),
9946
+ " / ",
9947
+ maxLength.toLocaleString()
9948
+ ]
9519
9949
  }
9520
- ),
9950
+ )
9951
+ ] }),
9952
+ shareUrl ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
9521
9953
  /* @__PURE__ */ jsx(
9522
9954
  "input",
9523
9955
  {
9524
- ref: fileInputRef,
9525
- type: "file",
9526
- multiple: true,
9527
- className: "hidden",
9528
- onChange: (event) => {
9529
- void handleFilesPicked(event.target.files);
9530
- event.target.value = "";
9531
- }
9956
+ "data-boff-explore": "share-url",
9957
+ readOnly: true,
9958
+ value: shareUrl,
9959
+ "aria-label": "Read-only link to this conversation",
9960
+ onFocus: (event) => event.currentTarget.select(),
9961
+ className: "min-w-0 flex-1 rounded-md border border-border bg-muted px-2 py-1 text-xs text-muted-foreground"
9532
9962
  }
9533
9963
  ),
9534
9964
  /* @__PURE__ */ jsx(
9535
- Button,
9965
+ "button",
9536
9966
  {
9537
- "data-boff-explore": "attach",
9538
9967
  type: "button",
9539
- size: "icon",
9540
- variant: "ghost",
9541
- disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
9542
- "aria-label": T.attach,
9543
- title: `${T.attach} \u2014 ${fill(T.attachHint, {
9544
- max: String(MAX_ATTACHMENTS),
9545
- size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
9546
- })}`,
9547
- onClick: () => fileInputRef.current?.click(),
9548
- children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
9968
+ className: "shrink-0 text-xs underline underline-offset-2",
9969
+ onClick: () => setShareUrl(null),
9970
+ children: "Done"
9549
9971
  }
9550
- ),
9551
- speech.supported ? /* @__PURE__ */ jsx(
9552
- Button,
9553
- {
9554
- "data-boff-explore": "mic",
9555
- "data-listening": speech.listening ? "true" : "false",
9556
- type: "button",
9557
- size: "icon",
9558
- variant: speech.listening ? "default" : "ghost",
9559
- disabled: composerDisabled,
9560
- "aria-label": speech.listening ? T.stopDictating : T.dictate,
9561
- "aria-pressed": speech.listening,
9562
- title: speech.listening ? T.stopDictating : T.dictate,
9563
- onClick: speech.toggle,
9564
- className: cn(speech.listening && "animate-pulse"),
9565
- children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" })
9566
- }
9567
- ) : null,
9568
- busy ? /* @__PURE__ */ jsx(
9569
- Button,
9972
+ )
9973
+ ] }) : null,
9974
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
9975
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: T.disclaimerShort }),
9976
+ /* @__PURE__ */ jsx(
9977
+ "button",
9570
9978
  {
9571
- "data-boff-explore": "stop",
9572
9979
  type: "button",
9573
- size: "icon",
9574
- variant: "secondary",
9575
- "aria-label": T.stop,
9576
- onClick: stop,
9577
- children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
9578
- }
9579
- ) : /* @__PURE__ */ jsx(
9580
- Button,
9581
- {
9582
- "data-boff-explore": "send",
9583
- type: "submit",
9584
- size: "icon",
9585
- disabled: !canSubmit,
9586
- "aria-label": T.send,
9587
- children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
9980
+ "data-boff-explore": "disclaimer",
9981
+ onClick: () => setDisclaimerOpen(true),
9982
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
9983
+ children: T.disclaimer
9588
9984
  }
9589
9985
  )
9590
- ]
9591
- }
9592
- ),
9593
- speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxs(
9594
- "p",
9595
- {
9596
- "data-boff-explore": "dictation",
9597
- className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
9598
- "aria-live": "polite",
9599
- children: [
9600
- /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
9601
- /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
9602
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
9603
- ] }),
9604
- /* @__PURE__ */ jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
9605
- ]
9606
- }
9607
- ) : null,
9608
- speech.error !== null ? /* @__PURE__ */ jsx(
9609
- "p",
9610
- {
9611
- "data-boff-explore": "dictation-error",
9612
- className: "mt-2 text-xs text-destructive",
9613
- children: speech.error
9614
- }
9615
- ) : null,
9616
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
9617
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
9618
- remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
9619
- remainingToday,
9620
- " message",
9621
- remainingToday === 1 ? "" : "s",
9622
- " left today"
9623
- ] }) : null,
9624
- /* @__PURE__ */ jsxs(
9625
- "span",
9626
- {
9627
- className: cn(
9628
- "ml-auto tabular-nums",
9629
- overLimit && "font-medium text-destructive"
9630
- ),
9631
- children: [
9632
- draft.length.toLocaleString(),
9633
- " / ",
9634
- maxLength.toLocaleString()
9635
- ]
9636
- }
9637
- )
9638
- ] }),
9639
- shareUrl ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
9640
- /* @__PURE__ */ jsx(
9641
- "input",
9642
- {
9643
- "data-boff-explore": "share-url",
9644
- readOnly: true,
9645
- value: shareUrl,
9646
- "aria-label": "Read-only link to this conversation",
9647
- onFocus: (event) => event.currentTarget.select(),
9648
- className: "min-w-0 flex-1 rounded-md border border-border bg-muted px-2 py-1 text-xs text-muted-foreground"
9649
- }
9650
- ),
9651
- /* @__PURE__ */ jsx(
9652
- "button",
9653
- {
9654
- type: "button",
9655
- className: "shrink-0 text-xs underline underline-offset-2",
9656
- onClick: () => setShareUrl(null),
9657
- children: "Done"
9658
- }
9659
- )
9660
- ] }) : null,
9661
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
9662
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: T.disclaimerShort }),
9663
- /* @__PURE__ */ jsx(
9664
- "button",
9665
- {
9666
- type: "button",
9667
- "data-boff-explore": "disclaimer",
9668
- onClick: () => setDisclaimerOpen(true),
9669
- className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
9670
- children: T.disclaimer
9671
- }
9672
- )
9673
- ] })
9674
- ]
9986
+ ] })
9987
+ ]
9988
+ }
9989
+ )
9675
9990
  }
9676
- ) })
9991
+ )
9677
9992
  ]
9678
9993
  }
9679
9994
  );