@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.
@@ -2492,6 +2492,7 @@ function toPayload(staged) {
2492
2492
  textContent
2493
2493
  }));
2494
2494
  }
2495
+ var SHORT_VIEWPORT_PX = 420;
2495
2496
  var EXPLORE_CSS = `
2496
2497
  @keyframes boff-explore-pulse {
2497
2498
  0%, 80%, 100% { opacity: 0.25; transform: translateY(0); }
@@ -2526,6 +2527,12 @@ var EXPLORE_CSS = `
2526
2527
  .boff-explore-scroll {
2527
2528
  scrollbar-width: thin;
2528
2529
  scrollbar-color: hsl(var(--border)) transparent;
2530
+ /* Stop the transcript handing its scroll to the page. Without this, the first
2531
+ swipe that exhausts the transcript chains to the document \u2014 and on a host site
2532
+ that is 65-70% footer, which is how a phone ends up showing nothing but the
2533
+ footer. Engines that do not support this property ignore the line and keep
2534
+ today's behaviour; the scroll correction above covers them. */
2535
+ overscroll-behavior: contain;
2529
2536
  }
2530
2537
  .boff-explore-scroll::-webkit-scrollbar { width: 10px; }
2531
2538
  .boff-explore-scroll::-webkit-scrollbar-track { background: transparent; }
@@ -2643,6 +2650,68 @@ var EXPLORE_CSS = `
2643
2650
  .boff-explore-busy::after { animation: none; }
2644
2651
  .boff-explore-rise { animation: none; opacity: 1; transform: none; }
2645
2652
  }
2653
+
2654
+ /* ---- Pane height ----------------------------------------------------------------------
2655
+ This used to be one inline declaration, \`height: calc(100dvh - 64px)\`, and it carried two
2656
+ defects. An inline style cannot carry a fallback, so every engine without \`dvh\` (iOS
2657
+ Safari < 15.4, Chrome/WebView < 108, Samsung Internet < 21, Firefox < 101) dropped it
2658
+ whole; and the 64px came from a prop hard-coded to 64 on all 41 host pages that matches
2659
+ none of them \u2014 the burdenoff/algoshred chrome is 80px, the sticky-header sites are 65.
2660
+
2661
+ A class plus custom properties lets the cascade do the work: \`vh\` is the floor and \`dvh\`
2662
+ an upgrade where it exists, while the two measured values stay *lengths*, never a height.
2663
+ That distinction is the whole prerender contract. The host sites snapshot \`/explore\` by
2664
+ loading the built SPA in headless Chromium and writing the POST-HYDRATION DOM to
2665
+ dist/explore/index.html, so every attribute and inline value this component sets after
2666
+ mount IS the static HTML CloudFront serves. A measured pixel *height* would therefore
2667
+ hand every phone the prerender viewport's height until the bundle executes; a measured
2668
+ *offset* is a site-header height, which is the same on every device to within a few
2669
+ pixels, and the height built from it stays viewport-relative on the visitor's own screen.
2670
+
2671
+ --boff-explore-offset chrome above the pane (measured root top; the prop pre-measure)
2672
+ --boff-explore-inset the slice of the layout viewport hidden below the visible area \u2014
2673
+ the on-screen keyboard, which \`dvh\` does not track. Zero unless
2674
+ something is actually covering the bottom, so a snapshot taken
2675
+ with no keyboard open never carries it. */
2676
+ .boff-explore-viewport {
2677
+ height: calc(100vh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
2678
+ }
2679
+ @supports (height: 100dvh) {
2680
+ .boff-explore-viewport {
2681
+ height: calc(100dvh - var(--boff-explore-offset, 0px) - var(--boff-explore-inset, 0px));
2682
+ }
2683
+ }
2684
+
2685
+ /* Landscape phones and other short viewports: a one-screen pane leaves a ~124px transcript
2686
+ sliver under ~610px of chrome and content. At or below the threshold the pane stops
2687
+ pretending to be a window and lets the page's own scrolling carry the transcript. The
2688
+ min-height is the available height rather than a fixed rem value, so the fallback can
2689
+ never be taller than the screen it is rescuing. */
2690
+ @media (max-height: ${SHORT_VIEWPORT_PX}px) {
2691
+ .boff-explore-viewport {
2692
+ height: auto;
2693
+ min-height: calc(100vh - var(--boff-explore-offset, 0px));
2694
+ }
2695
+ }
2696
+ @supports (height: 100dvh) {
2697
+ @media (max-height: ${SHORT_VIEWPORT_PX}px) {
2698
+ .boff-explore-viewport {
2699
+ min-height: calc(100dvh - var(--boff-explore-offset, 0px));
2700
+ }
2701
+ }
2702
+ }
2703
+
2704
+ /* ---- Composer dock --------------------------------------------------------------------
2705
+ Three declarations, each the fallback for the one after it: a plain 1rem where \`max()\`
2706
+ is unknown, then the measured overlap of any FOREIGN fixed chrome sitting over the
2707
+ composer (the host sites' cookie banner is 229px tall and lands exactly there), then the
2708
+ same plus the home-indicator inset. \`--boff-explore-chrome\` is set on the root only once
2709
+ an overlap has actually been measured, so everywhere else this collapses to 1rem. */
2710
+ .boff-explore-composer {
2711
+ padding-bottom: 1rem;
2712
+ padding-bottom: max(1rem, var(--boff-explore-chrome, 0px));
2713
+ padding-bottom: max(1rem, env(safe-area-inset-bottom), var(--boff-explore-chrome, 0px));
2714
+ }
2646
2715
  `;
2647
2716
  function ExploreStyles() {
2648
2717
  return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_CSS } });
@@ -2664,6 +2733,8 @@ var CAPABILITIES = [
2664
2733
  body: "Get the sources and next steps \u2014 pricing, docs, a demo \u2014 without hunting."
2665
2734
  }
2666
2735
  ];
2736
+ var MIN_VISIBLE_FRACTION = 0.25;
2737
+ var SDK_OVERLAY_SELECTOR = "[data-boff-explore]";
2667
2738
  var CTA_VARIANTS = {
2668
2739
  CONTACT: "default",
2669
2740
  WAITLIST: "default",
@@ -2679,6 +2750,28 @@ function normalizeOffset(offset) {
2679
2750
  if (offset === void 0) return "0px";
2680
2751
  return typeof offset === "number" ? `${offset}px` : offset;
2681
2752
  }
2753
+ function isOwnElement(el, root) {
2754
+ if (el === document.body || el === document.documentElement) return true;
2755
+ if (root.contains(el)) return true;
2756
+ return el.closest(SDK_OVERLAY_SELECTOR) !== null;
2757
+ }
2758
+ function visualViewportHeight() {
2759
+ return window.visualViewport?.height ?? window.innerHeight;
2760
+ }
2761
+ function clamp(value, min, max) {
2762
+ return Math.min(Math.max(value, min), max);
2763
+ }
2764
+ function isRestoredNavigation() {
2765
+ const entries = window.performance?.getEntriesByType?.("navigation");
2766
+ const entry = entries?.[0];
2767
+ if (!entry) return true;
2768
+ const navigation = entry;
2769
+ return navigation.type === "reload" || navigation.type === "back_forward";
2770
+ }
2771
+ function pageOwnsScrolling(el) {
2772
+ if (window.innerHeight > SHORT_VIEWPORT_PX) return false;
2773
+ return el.scrollHeight - el.clientHeight <= 1;
2774
+ }
2682
2775
  function isInternalPath(url) {
2683
2776
  return url.startsWith("/") && !url.startsWith("//");
2684
2777
  }
@@ -3527,6 +3620,8 @@ function ExplorePage({
3527
3620
  const pickQueueRef = useRef(Promise.resolve());
3528
3621
  const [shareLabel, setShareLabel] = useState(T.share);
3529
3622
  const [shareUrl, setShareUrl] = useState(null);
3623
+ const rootRef = useRef(null);
3624
+ const composerRef = useRef(null);
3530
3625
  const scrollRef = useRef(null);
3531
3626
  const latestAssistantRef = useRef(null);
3532
3627
  const alignedForRef = useRef(null);
@@ -3554,11 +3649,196 @@ function ExplorePage({
3554
3649
  el.style.height = "auto";
3555
3650
  el.style.height = `${Math.min(el.scrollHeight, 200)}px`;
3556
3651
  }, [draft]);
3652
+ const [paneOffset, setPaneOffset] = useState(null);
3653
+ const [bottomInset, setBottomInset] = useState(0);
3654
+ const paneOffsetRef = useRef(0);
3655
+ const measurePane = useCallback(() => {
3656
+ if (heightMode !== "viewport") return;
3657
+ const el = rootRef.current;
3658
+ if (!el) return;
3659
+ const layoutHeight = window.innerHeight;
3660
+ if (layoutHeight === 0) return;
3661
+ const top = Math.round(
3662
+ clamp(el.getBoundingClientRect().top, 0, layoutHeight)
3663
+ );
3664
+ const viewport = window.visualViewport;
3665
+ const inset = viewport ? Math.round(
3666
+ clamp(
3667
+ layoutHeight - viewport.height - viewport.offsetTop,
3668
+ 0,
3669
+ layoutHeight
3670
+ )
3671
+ ) : 0;
3672
+ paneOffsetRef.current = top;
3673
+ setPaneOffset((current) => current === top ? current : top);
3674
+ setBottomInset((current) => current === inset ? current : inset);
3675
+ }, [heightMode]);
3676
+ const [chromeOverlap, setChromeOverlap] = useState(0);
3677
+ const measureChrome = useCallback(() => {
3678
+ const root = rootRef.current;
3679
+ const dock = composerRef.current;
3680
+ if (!root || !dock) return;
3681
+ if (typeof document.elementFromPoint !== "function") return;
3682
+ const viewportHeight = visualViewportHeight();
3683
+ const rect = dock.getBoundingClientRect();
3684
+ if (rect.width === 0 || viewportHeight === 0) return;
3685
+ if (rect.bottom <= 0 || rect.top >= viewportHeight) return;
3686
+ const edge = Math.min(rect.bottom, viewportHeight);
3687
+ const hit = document.elementFromPoint(rect.left + rect.width / 2, edge - 1);
3688
+ if (!hit || isOwnElement(hit, root)) {
3689
+ setChromeOverlap(0);
3690
+ return;
3691
+ }
3692
+ const overlap = Math.round(edge - hit.getBoundingClientRect().top);
3693
+ setChromeOverlap(overlap > 0 && overlap < viewportHeight ? overlap : 0);
3694
+ }, []);
3695
+ const remeasure = useCallback(() => {
3696
+ measurePane();
3697
+ measureChrome();
3698
+ }, [measurePane, measureChrome]);
3699
+ useEffect(() => {
3700
+ if (typeof window === "undefined") return;
3701
+ let frame = 0;
3702
+ const run = () => {
3703
+ frame = 0;
3704
+ remeasure();
3705
+ };
3706
+ const schedule = () => {
3707
+ if (frame) return;
3708
+ if (typeof window.requestAnimationFrame !== "function") {
3709
+ remeasure();
3710
+ return;
3711
+ }
3712
+ frame = window.requestAnimationFrame(run);
3713
+ };
3714
+ remeasure();
3715
+ const viewport = window.visualViewport;
3716
+ window.addEventListener("resize", schedule);
3717
+ window.addEventListener("orientationchange", schedule);
3718
+ viewport?.addEventListener("resize", schedule);
3719
+ viewport?.addEventListener("scroll", schedule);
3720
+ const observer = typeof MutationObserver === "function" ? new MutationObserver((records) => {
3721
+ const el = rootRef.current;
3722
+ if (el && records.every((record) => el.contains(record.target)))
3723
+ return;
3724
+ schedule();
3725
+ }) : null;
3726
+ observer?.observe(document.body, { childList: true, subtree: true });
3727
+ return () => {
3728
+ if (frame) window.cancelAnimationFrame(frame);
3729
+ window.removeEventListener("resize", schedule);
3730
+ window.removeEventListener("orientationchange", schedule);
3731
+ viewport?.removeEventListener("resize", schedule);
3732
+ viewport?.removeEventListener("scroll", schedule);
3733
+ observer?.disconnect();
3734
+ setPaneOffset(null);
3735
+ setBottomInset(0);
3736
+ setChromeOverlap(0);
3737
+ };
3738
+ }, [remeasure]);
3739
+ useEffect(() => {
3740
+ measureChrome();
3741
+ }, [measureChrome, paneOffset, bottomInset]);
3742
+ useEffect(() => {
3743
+ if (heightMode !== "viewport" || typeof window === "undefined") return;
3744
+ const { history: browserHistory } = window;
3745
+ const previousRestoration = "scrollRestoration" in browserHistory ? browserHistory.scrollRestoration : null;
3746
+ if (previousRestoration !== null)
3747
+ browserHistory.scrollRestoration = "manual";
3748
+ let frame = 0;
3749
+ let visitorMoved = false;
3750
+ const onVisitorMove = () => {
3751
+ visitorMoved = true;
3752
+ };
3753
+ const align = () => {
3754
+ if (visitorMoved) return;
3755
+ const el = rootRef.current;
3756
+ if (!el) return;
3757
+ const viewportHeight = visualViewportHeight();
3758
+ const rect = el.getBoundingClientRect();
3759
+ if (rect.height === 0 || viewportHeight === 0) return;
3760
+ const visible = Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0);
3761
+ const reachable = Math.min(rect.height, viewportHeight);
3762
+ if (visible >= reachable * MIN_VISIBLE_FRACTION) return;
3763
+ window.scrollTo({
3764
+ top: Math.max(0, window.scrollY + rect.top),
3765
+ left: window.scrollX,
3766
+ behavior: "auto"
3767
+ });
3768
+ if (typeof document.elementFromPoint === "function") {
3769
+ const probe = document.elementFromPoint(
3770
+ Math.max(0, rect.left + rect.width / 2),
3771
+ 1
3772
+ );
3773
+ if (probe && !isOwnElement(probe, el)) {
3774
+ const chrome = probe.getBoundingClientRect().bottom;
3775
+ if (chrome > 0 && chrome < viewportHeight / 2) {
3776
+ window.scrollBy({ top: -chrome, left: 0, behavior: "auto" });
3777
+ }
3778
+ }
3779
+ }
3780
+ remeasure();
3781
+ };
3782
+ const alignIfRestored = () => {
3783
+ if (!isRestoredNavigation()) return;
3784
+ align();
3785
+ };
3786
+ alignIfRestored();
3787
+ if (typeof window.requestAnimationFrame === "function") {
3788
+ frame = window.requestAnimationFrame(alignIfRestored);
3789
+ }
3790
+ const onPageShow = (event) => {
3791
+ if (!event.persisted) {
3792
+ alignIfRestored();
3793
+ return;
3794
+ }
3795
+ visitorMoved = false;
3796
+ align();
3797
+ };
3798
+ window.addEventListener("pageshow", onPageShow);
3799
+ window.addEventListener("wheel", onVisitorMove, { passive: true });
3800
+ window.addEventListener("touchmove", onVisitorMove, { passive: true });
3801
+ window.addEventListener("keydown", onVisitorMove, { passive: true });
3802
+ return () => {
3803
+ if (frame) window.cancelAnimationFrame(frame);
3804
+ window.removeEventListener("pageshow", onPageShow);
3805
+ window.removeEventListener("wheel", onVisitorMove);
3806
+ window.removeEventListener("touchmove", onVisitorMove);
3807
+ window.removeEventListener("keydown", onVisitorMove);
3808
+ if (previousRestoration !== null)
3809
+ browserHistory.scrollRestoration = previousRestoration;
3810
+ };
3811
+ }, [heightMode, remeasure]);
3557
3812
  const handleScroll = useCallback(() => {
3558
3813
  const el = scrollRef.current;
3559
3814
  if (!el) return;
3560
3815
  stickToBottomRef.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
3561
3816
  }, []);
3817
+ useEffect(() => {
3818
+ if (typeof window === "undefined") return;
3819
+ let frame = 0;
3820
+ const run = () => {
3821
+ frame = 0;
3822
+ const el = scrollRef.current;
3823
+ const dock = composerRef.current;
3824
+ if (!el || !dock) return;
3825
+ if (!pageOwnsScrolling(el)) return;
3826
+ stickToBottomRef.current = dock.getBoundingClientRect().bottom - visualViewportHeight() < 80;
3827
+ };
3828
+ const schedule = () => {
3829
+ if (frame) return;
3830
+ if (typeof window.requestAnimationFrame !== "function") {
3831
+ run();
3832
+ return;
3833
+ }
3834
+ frame = window.requestAnimationFrame(run);
3835
+ };
3836
+ window.addEventListener("scroll", schedule, { passive: true });
3837
+ return () => {
3838
+ if (frame) window.cancelAnimationFrame(frame);
3839
+ window.removeEventListener("scroll", schedule);
3840
+ };
3841
+ }, []);
3562
3842
  const latestAssistantId = useMemo(() => {
3563
3843
  for (let i = messages.length - 1; i >= 0; i -= 1) {
3564
3844
  const message = messages[i];
@@ -3574,16 +3854,32 @@ function ExplorePage({
3574
3854
  if (!stickToBottomRef.current) return;
3575
3855
  if (anchor.offsetHeight === 0) return;
3576
3856
  alignedForRef.current = latestAssistantId;
3577
- const delta = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
3578
- el.scrollTop = Math.max(0, el.scrollTop + delta - 12);
3857
+ if (!pageOwnsScrolling(el)) {
3858
+ const delta2 = anchor.getBoundingClientRect().top - el.getBoundingClientRect().top;
3859
+ el.scrollTop = Math.max(0, el.scrollTop + delta2 - 12);
3860
+ return;
3861
+ }
3862
+ const delta = Math.round(
3863
+ anchor.getBoundingClientRect().top - paneOffsetRef.current - 12
3864
+ );
3865
+ if (delta !== 0) window.scrollBy({ top: delta, left: 0, behavior: "auto" });
3579
3866
  }, [latestAssistantId, messages]);
3580
3867
  useEffect(() => {
3868
+ if (messages.length === 0) return;
3581
3869
  if (!stickToBottomRef.current) return;
3582
3870
  if (latestAssistantId && alignedForRef.current === latestAssistantId)
3583
3871
  return;
3584
3872
  const el = scrollRef.current;
3585
3873
  if (!el) return;
3586
- el.scrollTop = el.scrollHeight;
3874
+ if (!pageOwnsScrolling(el)) {
3875
+ el.scrollTop = el.scrollHeight;
3876
+ return;
3877
+ }
3878
+ const dock = composerRef.current ?? el;
3879
+ const delta = Math.round(
3880
+ dock.getBoundingClientRect().bottom - visualViewportHeight()
3881
+ );
3882
+ if (delta > 0) window.scrollBy({ top: delta, left: 0, behavior: "auto" });
3587
3883
  }, [activity, latestAssistantId, messages]);
3588
3884
  const handleShare = useCallback(async () => {
3589
3885
  setShareLabel(T.copying);
@@ -3681,16 +3977,27 @@ function ExplorePage({
3681
3977
  const captchaOn = Boolean(
3682
3978
  catalog.captchaRequired && catalog.recaptchaSiteKey
3683
3979
  );
3684
- const rootStyle = heightMode === "viewport" ? { height: `calc(100dvh - ${normalizeOffset(viewportOffset)})` } : void 0;
3980
+ const rootStyle = {};
3981
+ if (heightMode === "viewport") {
3982
+ rootStyle["--boff-explore-offset"] = paneOffset === null ? normalizeOffset(viewportOffset) : `${paneOffset}px`;
3983
+ if (bottomInset > 0) {
3984
+ rootStyle["--boff-explore-inset"] = `${bottomInset}px`;
3985
+ }
3986
+ }
3987
+ if (chromeOverlap > 0) {
3988
+ rootStyle["--boff-explore-chrome"] = `${chromeOverlap}px`;
3989
+ }
3685
3990
  return /* @__PURE__ */ jsxs(
3686
3991
  "div",
3687
3992
  {
3993
+ ref: rootRef,
3688
3994
  "data-boff-explore": "page",
3689
3995
  "data-phase": phase,
3690
3996
  "data-locale": activeLocale,
3691
3997
  dir: rtl ? "rtl" : void 0,
3692
3998
  className: cn(
3693
3999
  "flex w-full min-w-0 flex-col overflow-x-hidden bg-background text-foreground",
4000
+ heightMode === "viewport" && "boff-explore-viewport",
3694
4001
  heightMode === "auto" && "min-h-[70vh]",
3695
4002
  className
3696
4003
  ),
@@ -3924,249 +4231,257 @@ function ExplorePage({
3924
4231
  ]
3925
4232
  }
3926
4233
  ) : null,
3927
- /* @__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(
3928
- "form",
4234
+ /* @__PURE__ */ jsx(
4235
+ "div",
3929
4236
  {
3930
- "data-boff-explore": "composer",
3931
- className: "mx-auto w-full max-w-3xl",
3932
- onSubmit: (event) => {
3933
- event.preventDefault();
3934
- submitDraft();
3935
- },
3936
- children: [
3937
- error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
3938
- staged.length > 0 ? /* @__PURE__ */ jsx(
3939
- "ul",
3940
- {
3941
- "data-boff-explore": "attachments",
3942
- className: "mb-2 flex flex-wrap gap-1.5",
3943
- "aria-label": T.attach,
3944
- children: staged.map((entry) => /* @__PURE__ */ jsxs(
3945
- "li",
4237
+ ref: composerRef,
4238
+ "data-boff-explore": "composer-dock",
4239
+ className: "boff-explore-composer border-t border-border bg-background/85 px-4 pt-3 backdrop-blur-md",
4240
+ children: /* @__PURE__ */ jsxs(
4241
+ "form",
4242
+ {
4243
+ "data-boff-explore": "composer",
4244
+ className: "mx-auto w-full max-w-3xl",
4245
+ onSubmit: (event) => {
4246
+ event.preventDefault();
4247
+ submitDraft();
4248
+ },
4249
+ children: [
4250
+ error ? /* @__PURE__ */ jsx(ErrorBanner, { error, onRetry: retry, onReset: reset }) : null,
4251
+ staged.length > 0 ? /* @__PURE__ */ jsx(
4252
+ "ul",
4253
+ {
4254
+ "data-boff-explore": "attachments",
4255
+ className: "mb-2 flex flex-wrap gap-1.5",
4256
+ "aria-label": T.attach,
4257
+ children: staged.map((entry) => /* @__PURE__ */ jsxs(
4258
+ "li",
4259
+ {
4260
+ "data-boff-explore": "attachment",
4261
+ "data-text-extracted": entry.textExtracted ? "true" : "false",
4262
+ 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",
4263
+ children: [
4264
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
4265
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
4266
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
4267
+ formatBytes(entry.size),
4268
+ entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
4269
+ ] }),
4270
+ /* @__PURE__ */ jsx(
4271
+ "button",
4272
+ {
4273
+ type: "button",
4274
+ "data-boff-explore": "attachment-remove",
4275
+ onClick: () => removeAttachment(entry.id),
4276
+ "aria-label": fill(T.removeAttachment, { name: entry.name }),
4277
+ 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",
4278
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
4279
+ }
4280
+ )
4281
+ ]
4282
+ },
4283
+ entry.id
4284
+ ))
4285
+ }
4286
+ ) : null,
4287
+ attachmentNotice !== null ? /* @__PURE__ */ jsx(
4288
+ "p",
3946
4289
  {
3947
- "data-boff-explore": "attachment",
3948
- "data-text-extracted": entry.textExtracted ? "true" : "false",
3949
- 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",
4290
+ "data-boff-explore": "attachment-notice",
4291
+ role: "status",
4292
+ className: "mb-2 text-xs text-destructive",
4293
+ children: attachmentNotice
4294
+ }
4295
+ ) : null,
4296
+ /* @__PURE__ */ jsxs(
4297
+ "div",
4298
+ {
4299
+ className: cn(
4300
+ "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
4301
+ busy && "boff-explore-busy",
4302
+ overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
4303
+ ),
3950
4304
  children: [
3951
- /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground" }),
3952
- /* @__PURE__ */ jsx("span", { className: "truncate font-medium", title: entry.name, children: entry.name }),
3953
- /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-muted-foreground", children: [
3954
- formatBytes(entry.size),
3955
- entry.textExtracted ? "" : ` \xB7 ${T.attachmentNameOnly}`
3956
- ] }),
3957
4305
  /* @__PURE__ */ jsx(
3958
- "button",
4306
+ Textarea,
4307
+ {
4308
+ ref: textareaRef,
4309
+ value: draft,
4310
+ rows: 1,
4311
+ disabled: composerDisabled,
4312
+ onChange: (event) => setDraft(event.target.value),
4313
+ onKeyDown: handleKeyDown,
4314
+ onCompositionStart: () => {
4315
+ composingRef.current = true;
4316
+ },
4317
+ onCompositionEnd: () => {
4318
+ composingRef.current = false;
4319
+ },
4320
+ "aria-label": "Ask a question",
4321
+ placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
4322
+ product: focusedProductName
4323
+ }) : T.placeholder,
4324
+ 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"
4325
+ }
4326
+ ),
4327
+ /* @__PURE__ */ jsx(
4328
+ "input",
4329
+ {
4330
+ ref: fileInputRef,
4331
+ type: "file",
4332
+ multiple: true,
4333
+ className: "hidden",
4334
+ onChange: (event) => {
4335
+ void handleFilesPicked(event.target.files);
4336
+ event.target.value = "";
4337
+ }
4338
+ }
4339
+ ),
4340
+ /* @__PURE__ */ jsx(
4341
+ Button,
4342
+ {
4343
+ "data-boff-explore": "attach",
4344
+ type: "button",
4345
+ size: "icon",
4346
+ variant: "ghost",
4347
+ disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
4348
+ "aria-label": T.attach,
4349
+ title: `${T.attach} \u2014 ${fill(T.attachHint, {
4350
+ max: String(MAX_ATTACHMENTS),
4351
+ size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
4352
+ })}`,
4353
+ onClick: () => fileInputRef.current?.click(),
4354
+ children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
4355
+ }
4356
+ ),
4357
+ speech.supported ? /* @__PURE__ */ jsx(
4358
+ Button,
4359
+ {
4360
+ "data-boff-explore": "mic",
4361
+ "data-listening": speech.listening ? "true" : "false",
4362
+ type: "button",
4363
+ size: "icon",
4364
+ variant: speech.listening ? "default" : "ghost",
4365
+ disabled: composerDisabled,
4366
+ "aria-label": speech.listening ? T.stopDictating : T.dictate,
4367
+ "aria-pressed": speech.listening,
4368
+ title: speech.listening ? T.stopDictating : T.dictate,
4369
+ onClick: speech.toggle,
4370
+ className: cn(speech.listening && "animate-pulse"),
4371
+ children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" })
4372
+ }
4373
+ ) : null,
4374
+ busy ? /* @__PURE__ */ jsx(
4375
+ Button,
3959
4376
  {
4377
+ "data-boff-explore": "stop",
3960
4378
  type: "button",
3961
- "data-boff-explore": "attachment-remove",
3962
- onClick: () => removeAttachment(entry.id),
3963
- "aria-label": fill(T.removeAttachment, { name: entry.name }),
3964
- 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",
3965
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
4379
+ size: "icon",
4380
+ variant: "secondary",
4381
+ "aria-label": T.stop,
4382
+ onClick: stop,
4383
+ children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
4384
+ }
4385
+ ) : /* @__PURE__ */ jsx(
4386
+ Button,
4387
+ {
4388
+ "data-boff-explore": "send",
4389
+ type: "submit",
4390
+ size: "icon",
4391
+ disabled: !canSubmit,
4392
+ "aria-label": T.send,
4393
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
3966
4394
  }
3967
4395
  )
3968
4396
  ]
3969
- },
3970
- entry.id
3971
- ))
3972
- }
3973
- ) : null,
3974
- attachmentNotice !== null ? /* @__PURE__ */ jsx(
3975
- "p",
3976
- {
3977
- "data-boff-explore": "attachment-notice",
3978
- role: "status",
3979
- className: "mb-2 text-xs text-destructive",
3980
- children: attachmentNotice
3981
- }
3982
- ) : null,
3983
- /* @__PURE__ */ jsxs(
3984
- "div",
3985
- {
3986
- className: cn(
3987
- "flex min-w-0 items-end gap-2 rounded-2xl border bg-card p-2 shadow-sm transition-all",
3988
- busy && "boff-explore-busy",
3989
- overLimit ? "border-destructive/60" : "border-border focus-within:border-primary/40 focus-within:ring-1 focus-within:ring-ring"
4397
+ }
3990
4398
  ),
3991
- children: [
3992
- /* @__PURE__ */ jsx(
3993
- Textarea,
4399
+ speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxs(
4400
+ "p",
4401
+ {
4402
+ "data-boff-explore": "dictation",
4403
+ className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
4404
+ "aria-live": "polite",
4405
+ children: [
4406
+ /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
4407
+ /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
4408
+ /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
4409
+ ] }),
4410
+ /* @__PURE__ */ jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
4411
+ ]
4412
+ }
4413
+ ) : null,
4414
+ speech.error !== null ? /* @__PURE__ */ jsx(
4415
+ "p",
4416
+ {
4417
+ "data-boff-explore": "dictation-error",
4418
+ className: "mt-2 text-xs text-destructive",
4419
+ children: speech.error
4420
+ }
4421
+ ) : null,
4422
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4423
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
4424
+ remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
4425
+ remainingToday,
4426
+ " message",
4427
+ remainingToday === 1 ? "" : "s",
4428
+ " left today"
4429
+ ] }) : null,
4430
+ /* @__PURE__ */ jsxs(
4431
+ "span",
3994
4432
  {
3995
- ref: textareaRef,
3996
- value: draft,
3997
- rows: 1,
3998
- disabled: composerDisabled,
3999
- onChange: (event) => setDraft(event.target.value),
4000
- onKeyDown: handleKeyDown,
4001
- onCompositionStart: () => {
4002
- composingRef.current = true;
4003
- },
4004
- onCompositionEnd: () => {
4005
- composingRef.current = false;
4006
- },
4007
- "aria-label": "Ask a question",
4008
- placeholder: composerDisabled ? phase === "limited" ? "Message limit reached \u2014 please try again later" : "Explore is unavailable right now" : focusedProductName ? fill(T.placeholderProduct, {
4009
- product: focusedProductName
4010
- }) : T.placeholder,
4011
- 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"
4433
+ className: cn(
4434
+ "ml-auto tabular-nums",
4435
+ overLimit && "font-medium text-destructive"
4436
+ ),
4437
+ children: [
4438
+ draft.length.toLocaleString(),
4439
+ " / ",
4440
+ maxLength.toLocaleString()
4441
+ ]
4012
4442
  }
4013
- ),
4443
+ )
4444
+ ] }),
4445
+ shareUrl ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
4014
4446
  /* @__PURE__ */ jsx(
4015
4447
  "input",
4016
4448
  {
4017
- ref: fileInputRef,
4018
- type: "file",
4019
- multiple: true,
4020
- className: "hidden",
4021
- onChange: (event) => {
4022
- void handleFilesPicked(event.target.files);
4023
- event.target.value = "";
4024
- }
4449
+ "data-boff-explore": "share-url",
4450
+ readOnly: true,
4451
+ value: shareUrl,
4452
+ "aria-label": "Read-only link to this conversation",
4453
+ onFocus: (event) => event.currentTarget.select(),
4454
+ className: "min-w-0 flex-1 rounded-md border border-border bg-muted px-2 py-1 text-xs text-muted-foreground"
4025
4455
  }
4026
4456
  ),
4027
4457
  /* @__PURE__ */ jsx(
4028
- Button,
4029
- {
4030
- "data-boff-explore": "attach",
4031
- type: "button",
4032
- size: "icon",
4033
- variant: "ghost",
4034
- disabled: composerDisabled || staged.length >= MAX_ATTACHMENTS,
4035
- "aria-label": T.attach,
4036
- title: `${T.attach} \u2014 ${fill(T.attachHint, {
4037
- max: String(MAX_ATTACHMENTS),
4038
- size: String(MAX_ATTACHMENT_BYTES / (1024 * 1024))
4039
- })}`,
4040
- onClick: () => fileInputRef.current?.click(),
4041
- children: /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" })
4042
- }
4043
- ),
4044
- speech.supported ? /* @__PURE__ */ jsx(
4045
- Button,
4458
+ "button",
4046
4459
  {
4047
- "data-boff-explore": "mic",
4048
- "data-listening": speech.listening ? "true" : "false",
4049
4460
  type: "button",
4050
- size: "icon",
4051
- variant: speech.listening ? "default" : "ghost",
4052
- disabled: composerDisabled,
4053
- "aria-label": speech.listening ? T.stopDictating : T.dictate,
4054
- "aria-pressed": speech.listening,
4055
- title: speech.listening ? T.stopDictating : T.dictate,
4056
- onClick: speech.toggle,
4057
- className: cn(speech.listening && "animate-pulse"),
4058
- children: /* @__PURE__ */ jsx(Mic, { className: "h-4 w-4" })
4461
+ className: "shrink-0 text-xs underline underline-offset-2",
4462
+ onClick: () => setShareUrl(null),
4463
+ children: "Done"
4059
4464
  }
4060
- ) : null,
4061
- busy ? /* @__PURE__ */ jsx(
4062
- Button,
4465
+ )
4466
+ ] }) : null,
4467
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4468
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: T.disclaimerShort }),
4469
+ /* @__PURE__ */ jsx(
4470
+ "button",
4063
4471
  {
4064
- "data-boff-explore": "stop",
4065
4472
  type: "button",
4066
- size: "icon",
4067
- variant: "secondary",
4068
- "aria-label": T.stop,
4069
- onClick: stop,
4070
- children: /* @__PURE__ */ jsx(Square, { className: "h-4 w-4" })
4071
- }
4072
- ) : /* @__PURE__ */ jsx(
4073
- Button,
4074
- {
4075
- "data-boff-explore": "send",
4076
- type: "submit",
4077
- size: "icon",
4078
- disabled: !canSubmit,
4079
- "aria-label": T.send,
4080
- children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-4 w-4" })
4473
+ "data-boff-explore": "disclaimer",
4474
+ onClick: () => setDisclaimerOpen(true),
4475
+ className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4476
+ children: T.disclaimer
4081
4477
  }
4082
4478
  )
4083
- ]
4084
- }
4085
- ),
4086
- speech.listening || speech.interim !== "" ? /* @__PURE__ */ jsxs(
4087
- "p",
4088
- {
4089
- "data-boff-explore": "dictation",
4090
- className: "mt-2 flex items-center gap-2 text-xs text-muted-foreground",
4091
- "aria-live": "polite",
4092
- children: [
4093
- /* @__PURE__ */ jsxs("span", { className: "relative flex h-2 w-2 shrink-0", children: [
4094
- /* @__PURE__ */ jsx("span", { className: "absolute inline-flex h-full w-full animate-ping rounded-full bg-primary opacity-75" }),
4095
- /* @__PURE__ */ jsx("span", { className: "relative inline-flex h-2 w-2 rounded-full bg-primary" })
4096
- ] }),
4097
- /* @__PURE__ */ jsx("span", { className: "italic", children: speech.interim !== "" ? speech.interim : "Listening\u2026" })
4098
- ]
4099
- }
4100
- ) : null,
4101
- speech.error !== null ? /* @__PURE__ */ jsx(
4102
- "p",
4103
- {
4104
- "data-boff-explore": "dictation-error",
4105
- className: "mt-2 text-xs text-destructive",
4106
- children: speech.error
4107
- }
4108
- ) : null,
4109
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4110
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Enter to send \xB7 Shift + Enter for a new line" }),
4111
- remainingToday !== null ? /* @__PURE__ */ jsxs("span", { "data-boff-explore": "remaining", children: [
4112
- remainingToday,
4113
- " message",
4114
- remainingToday === 1 ? "" : "s",
4115
- " left today"
4116
- ] }) : null,
4117
- /* @__PURE__ */ jsxs(
4118
- "span",
4119
- {
4120
- className: cn(
4121
- "ml-auto tabular-nums",
4122
- overLimit && "font-medium text-destructive"
4123
- ),
4124
- children: [
4125
- draft.length.toLocaleString(),
4126
- " / ",
4127
- maxLength.toLocaleString()
4128
- ]
4129
- }
4130
- )
4131
- ] }),
4132
- shareUrl ? /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
4133
- /* @__PURE__ */ jsx(
4134
- "input",
4135
- {
4136
- "data-boff-explore": "share-url",
4137
- readOnly: true,
4138
- value: shareUrl,
4139
- "aria-label": "Read-only link to this conversation",
4140
- onFocus: (event) => event.currentTarget.select(),
4141
- className: "min-w-0 flex-1 rounded-md border border-border bg-muted px-2 py-1 text-xs text-muted-foreground"
4142
- }
4143
- ),
4144
- /* @__PURE__ */ jsx(
4145
- "button",
4146
- {
4147
- type: "button",
4148
- className: "shrink-0 text-xs underline underline-offset-2",
4149
- onClick: () => setShareUrl(null),
4150
- children: "Done"
4151
- }
4152
- )
4153
- ] }) : null,
4154
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
4155
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: T.disclaimerShort }),
4156
- /* @__PURE__ */ jsx(
4157
- "button",
4158
- {
4159
- type: "button",
4160
- "data-boff-explore": "disclaimer",
4161
- onClick: () => setDisclaimerOpen(true),
4162
- className: "shrink-0 underline underline-offset-2 transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
4163
- children: T.disclaimer
4164
- }
4165
- )
4166
- ] })
4167
- ]
4479
+ ] })
4480
+ ]
4481
+ }
4482
+ )
4168
4483
  }
4169
- ) })
4484
+ )
4170
4485
  ]
4171
4486
  }
4172
4487
  );