@ottocode/web-sdk 0.1.288 → 0.1.289

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.js CHANGED
@@ -31392,7 +31392,7 @@ var BrowserPanelToggle = memo49(function BrowserPanelToggle2() {
31392
31392
  });
31393
31393
  });
31394
31394
  // src/components/browser/BrowserViewerPanel.tsx
31395
- import { useEffect as useEffect56, useState as useState52 } from "react";
31395
+ import { useCallback as useCallback37, useEffect as useEffect56, useRef as useRef38, useState as useState52 } from "react";
31396
31396
  import {
31397
31397
  ChevronLeft as ChevronLeft2,
31398
31398
  ChevronRight as ChevronRight18,
@@ -31400,7 +31400,8 @@ import {
31400
31400
  Globe2 as Globe23,
31401
31401
  Plus as Plus9,
31402
31402
  RefreshCw as RefreshCw12,
31403
- Smartphone
31403
+ Smartphone,
31404
+ X as X23
31404
31405
  } from "lucide-react";
31405
31406
 
31406
31407
  // src/hooks/useSimulator.ts
@@ -31432,13 +31433,17 @@ function useSimulatorStatus() {
31432
31433
  import { jsx as jsx122, jsxs as jsxs106 } from "react/jsx-runtime";
31433
31434
  var DEFAULT_BROWSER_URL = "http://localhost:3000";
31434
31435
  var SIMULATOR_URL = "http://localhost:3200";
31436
+ var IFRAME_EMBED_TIMEOUT_MS = 6000;
31435
31437
  function normalizeBrowserUrl(value) {
31436
31438
  const trimmed = value.trim();
31437
31439
  if (!trimmed)
31438
31440
  return "";
31439
31441
  if (/^[a-z][a-z0-9+.-]*:/i.test(trimmed))
31440
31442
  return trimmed;
31441
- return `http://${trimmed}`;
31443
+ if (/^(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::\d+)?(?:\/|$)/i.test(trimmed)) {
31444
+ return `http://${trimmed}`;
31445
+ }
31446
+ return `https://${trimmed}`;
31442
31447
  }
31443
31448
  function isEmbeddableUrl(value) {
31444
31449
  if (!value)
@@ -31454,11 +31459,100 @@ function BrowserViewerPanel({ tab }) {
31454
31459
  const updateBrowserTabUrl = useViewerTabsStore((state) => state.updateBrowserTabUrl);
31455
31460
  const reloadBrowserTab = useViewerTabsStore((state) => state.reloadBrowserTab);
31456
31461
  const openBrowserTab = useViewerTabsStore((state) => state.openBrowserTab);
31462
+ const contentRef = useRef38(null);
31463
+ const iframeRef = useRef38(null);
31464
+ const loadingDoneTimeoutRef = useRef38(null);
31465
+ const iframeEmbedTimeoutRef = useRef38(null);
31466
+ const isLoadingRef = useRef38(false);
31457
31467
  const [draftUrl, setDraftUrl] = useState52(tab.url);
31468
+ const [historyEntries, setHistoryEntries] = useState52(() => tab.url ? [normalizeBrowserUrl(tab.url)] : []);
31469
+ const [historyIndex, setHistoryIndex] = useState52(tab.url ? 0 : -1);
31470
+ const [isLoading, setIsLoading] = useState52(() => isEmbeddableUrl(normalizeBrowserUrl(tab.url)));
31471
+ const [loadingProgress, setLoadingProgress] = useState52(() => isEmbeddableUrl(normalizeBrowserUrl(tab.url)) ? 12 : 0);
31472
+ const [embedError, setEmbedError] = useState52(null);
31458
31473
  const simulatorStatus = useSimulatorStatus();
31474
+ const nativeBrowser = typeof window !== "undefined" ? window.OTTO_NATIVE_BROWSER : undefined;
31475
+ const normalizedUrl = normalizeBrowserUrl(tab.url);
31476
+ const canRenderUrl = isEmbeddableUrl(normalizedUrl);
31477
+ const useNativeBrowser = Boolean(nativeBrowser?.isAvailable && canRenderUrl);
31478
+ const canGoBack = historyIndex > 0;
31479
+ const canGoForward = historyIndex >= 0 && historyIndex < historyEntries.length - 1;
31480
+ const clearIframeEmbedTimeout = useCallback37(() => {
31481
+ if (iframeEmbedTimeoutRef.current) {
31482
+ clearTimeout(iframeEmbedTimeoutRef.current);
31483
+ iframeEmbedTimeoutRef.current = null;
31484
+ }
31485
+ }, []);
31486
+ const completeLoading = useCallback37(() => {
31487
+ clearIframeEmbedTimeout();
31488
+ setEmbedError(null);
31489
+ setLoadingProgress(100);
31490
+ if (loadingDoneTimeoutRef.current) {
31491
+ clearTimeout(loadingDoneTimeoutRef.current);
31492
+ }
31493
+ loadingDoneTimeoutRef.current = setTimeout(() => {
31494
+ setIsLoading(false);
31495
+ setLoadingProgress(0);
31496
+ loadingDoneTimeoutRef.current = null;
31497
+ }, 180);
31498
+ }, [clearIframeEmbedTimeout]);
31459
31499
  useEffect56(() => {
31460
31500
  setDraftUrl(tab.url);
31461
31501
  }, [tab.url]);
31502
+ useEffect56(() => {
31503
+ isLoadingRef.current = isLoading;
31504
+ }, [isLoading]);
31505
+ useEffect56(() => {
31506
+ if (!isLoading)
31507
+ return;
31508
+ setLoadingProgress((progress) => progress <= 0 || progress >= 100 ? 12 : progress);
31509
+ const interval = setInterval(() => {
31510
+ setLoadingProgress((progress) => {
31511
+ if (progress >= 88)
31512
+ return progress;
31513
+ return Math.min(88, progress + Math.max(2, (90 - progress) * 0.12));
31514
+ });
31515
+ }, 250);
31516
+ return () => clearInterval(interval);
31517
+ }, [isLoading]);
31518
+ useEffect56(() => () => {
31519
+ if (loadingDoneTimeoutRef.current) {
31520
+ clearTimeout(loadingDoneTimeoutRef.current);
31521
+ }
31522
+ clearIframeEmbedTimeout();
31523
+ }, [clearIframeEmbedTimeout]);
31524
+ useEffect56(() => {
31525
+ if (!isLoading || !canRenderUrl || useNativeBrowser) {
31526
+ clearIframeEmbedTimeout();
31527
+ return;
31528
+ }
31529
+ clearIframeEmbedTimeout();
31530
+ iframeEmbedTimeoutRef.current = setTimeout(() => {
31531
+ setEmbedError("This site may block embedding in Otto, or it took too long to load.");
31532
+ setIsLoading(false);
31533
+ setLoadingProgress(0);
31534
+ }, IFRAME_EMBED_TIMEOUT_MS);
31535
+ return clearIframeEmbedTimeout;
31536
+ }, [canRenderUrl, clearIframeEmbedTimeout, isLoading, useNativeBrowser]);
31537
+ useEffect56(() => () => {
31538
+ if (nativeBrowser?.isAvailable) {
31539
+ nativeBrowser.unmount(tab.id);
31540
+ }
31541
+ }, [nativeBrowser, tab.id]);
31542
+ useEffect56(() => {
31543
+ const nextUrl = normalizeBrowserUrl(tab.url);
31544
+ if (!nextUrl)
31545
+ return;
31546
+ setHistoryEntries((entries) => {
31547
+ if (entries[historyIndex] === nextUrl)
31548
+ return entries;
31549
+ const nextEntries = entries.slice(0, historyIndex + 1);
31550
+ nextEntries.push(nextUrl);
31551
+ setHistoryIndex(nextEntries.length - 1);
31552
+ setIsLoading(isEmbeddableUrl(nextUrl));
31553
+ return nextEntries;
31554
+ });
31555
+ }, [tab.url, historyIndex]);
31462
31556
  useEffect56(() => {
31463
31557
  const url = simulatorStatus.data?.url ?? SIMULATOR_URL;
31464
31558
  if (tab.kind === "simulator" && simulatorStatus.data?.status === "connected" && url !== tab.url) {
@@ -31468,25 +31562,113 @@ function BrowserViewerPanel({ tab }) {
31468
31562
  });
31469
31563
  }
31470
31564
  }, [openBrowserTab, simulatorStatus.data, tab.kind, tab.url]);
31471
- const normalizedUrl = normalizeBrowserUrl(tab.url);
31472
- const canRenderUrl = isEmbeddableUrl(normalizedUrl);
31565
+ useEffect56(() => {
31566
+ if (!useNativeBrowser || !nativeBrowser?.isAvailable)
31567
+ return;
31568
+ const content = contentRef.current;
31569
+ if (!content)
31570
+ return;
31571
+ let cancelled = false;
31572
+ const mountNativeBrowser = () => {
31573
+ const rect = content.getBoundingClientRect();
31574
+ const visible = rect.width > 1 && rect.height > 1;
31575
+ if (!visible) {
31576
+ nativeBrowser.setVisible(tab.id, false);
31577
+ return;
31578
+ }
31579
+ nativeBrowser.mount({
31580
+ id: tab.id,
31581
+ url: normalizedUrl,
31582
+ reloadKey: tab.reloadKey,
31583
+ bounds: {
31584
+ x: rect.x,
31585
+ y: rect.y,
31586
+ width: rect.width,
31587
+ height: rect.height
31588
+ },
31589
+ visible
31590
+ }).then(() => {
31591
+ if (!cancelled && isLoadingRef.current)
31592
+ completeLoading();
31593
+ }).catch((error) => {
31594
+ if (cancelled)
31595
+ return;
31596
+ const message = error instanceof Error ? error.message : "Unable to open the native desktop webview.";
31597
+ setEmbedError(message);
31598
+ setIsLoading(false);
31599
+ setLoadingProgress(0);
31600
+ });
31601
+ };
31602
+ mountNativeBrowser();
31603
+ const resizeObserver = new ResizeObserver(mountNativeBrowser);
31604
+ resizeObserver.observe(content);
31605
+ window.addEventListener("resize", mountNativeBrowser);
31606
+ return () => {
31607
+ cancelled = true;
31608
+ resizeObserver.disconnect();
31609
+ window.removeEventListener("resize", mountNativeBrowser);
31610
+ };
31611
+ }, [
31612
+ completeLoading,
31613
+ nativeBrowser,
31614
+ normalizedUrl,
31615
+ tab.id,
31616
+ tab.reloadKey,
31617
+ useNativeBrowser
31618
+ ]);
31473
31619
  const navigate = (value) => {
31474
31620
  const nextUrl = normalizeBrowserUrl(value);
31621
+ if (!nextUrl)
31622
+ return;
31623
+ setEmbedError(null);
31624
+ setIsLoading(isEmbeddableUrl(nextUrl));
31625
+ setLoadingProgress(isEmbeddableUrl(nextUrl) ? 12 : 0);
31626
+ setHistoryEntries((entries) => {
31627
+ const currentUrl = entries[historyIndex];
31628
+ if (currentUrl === nextUrl)
31629
+ return entries;
31630
+ const nextEntries = entries.slice(0, historyIndex + 1);
31631
+ nextEntries.push(nextUrl);
31632
+ setHistoryIndex(nextEntries.length - 1);
31633
+ return nextEntries;
31634
+ });
31635
+ updateBrowserTabUrl(tab.id, nextUrl);
31636
+ };
31637
+ const goToHistoryIndex = (index) => {
31638
+ const nextUrl = historyEntries[index];
31639
+ if (!nextUrl)
31640
+ return;
31641
+ setHistoryIndex(index);
31642
+ setEmbedError(null);
31643
+ setIsLoading(isEmbeddableUrl(nextUrl));
31644
+ setLoadingProgress(isEmbeddableUrl(nextUrl) ? 12 : 0);
31475
31645
  updateBrowserTabUrl(tab.id, nextUrl);
31476
31646
  };
31647
+ const stopLoading = () => {
31648
+ try {
31649
+ iframeRef.current?.contentWindow?.stop();
31650
+ } catch {}
31651
+ setIsLoading(false);
31652
+ setLoadingProgress(0);
31653
+ clearIframeEmbedTimeout();
31654
+ if (nativeBrowser?.isAvailable) {
31655
+ nativeBrowser.setVisible(tab.id, false);
31656
+ }
31657
+ };
31477
31658
  const simulatorError = tab.kind === "simulator" && simulatorStatus.data?.status === "error" ? simulatorStatus.data.error : null;
31478
31659
  return /* @__PURE__ */ jsxs106("div", {
31479
31660
  className: "h-full w-full min-w-0 bg-background flex flex-col",
31480
31661
  children: [
31481
31662
  /* @__PURE__ */ jsx122("div", {
31482
- className: "shrink-0 border-b border-border bg-[#0b0b0d] text-zinc-400",
31663
+ className: "shrink-0 border-b border-border bg-sidebar text-muted-foreground",
31483
31664
  children: /* @__PURE__ */ jsxs106("div", {
31484
- className: "flex h-11 items-center gap-1 px-3",
31665
+ className: "relative flex h-11 items-center gap-1 px-3",
31485
31666
  children: [
31486
31667
  /* @__PURE__ */ jsx122("button", {
31487
31668
  type: "button",
31488
- disabled: true,
31489
- className: "flex h-8 w-8 items-center justify-center rounded-md text-zinc-600",
31669
+ onClick: () => goToHistoryIndex(historyIndex - 1),
31670
+ disabled: !canGoBack,
31671
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/40 disabled:hover:bg-transparent",
31490
31672
  title: "Back",
31491
31673
  children: /* @__PURE__ */ jsx122(ChevronLeft2, {
31492
31674
  className: "h-4 w-4"
@@ -31494,8 +31676,9 @@ function BrowserViewerPanel({ tab }) {
31494
31676
  }),
31495
31677
  /* @__PURE__ */ jsx122("button", {
31496
31678
  type: "button",
31497
- disabled: true,
31498
- className: "flex h-8 w-8 items-center justify-center rounded-md text-zinc-600",
31679
+ onClick: () => goToHistoryIndex(historyIndex + 1),
31680
+ disabled: !canGoForward,
31681
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/40 disabled:hover:bg-transparent",
31499
31682
  title: "Forward",
31500
31683
  children: /* @__PURE__ */ jsx122(ChevronRight18, {
31501
31684
  className: "h-4 w-4"
@@ -31503,25 +31686,45 @@ function BrowserViewerPanel({ tab }) {
31503
31686
  }),
31504
31687
  /* @__PURE__ */ jsx122("button", {
31505
31688
  type: "button",
31506
- onClick: () => reloadBrowserTab(tab.id),
31507
- disabled: !canRenderUrl,
31508
- title: "Reload",
31509
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-white/5 hover:text-zinc-200 disabled:text-zinc-600 disabled:hover:bg-transparent",
31510
- children: /* @__PURE__ */ jsx122(RefreshCw12, {
31689
+ onClick: () => {
31690
+ if (isLoading) {
31691
+ stopLoading();
31692
+ return;
31693
+ }
31694
+ setEmbedError(null);
31695
+ setIsLoading(true);
31696
+ setLoadingProgress(12);
31697
+ reloadBrowserTab(tab.id);
31698
+ },
31699
+ disabled: !isLoading && !canRenderUrl,
31700
+ title: isLoading ? "Stop loading" : "Reload",
31701
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/40 disabled:hover:bg-transparent",
31702
+ children: isLoading ? /* @__PURE__ */ jsx122(X23, {
31703
+ className: "h-4 w-4"
31704
+ }) : /* @__PURE__ */ jsx122(RefreshCw12, {
31511
31705
  className: "h-4 w-4"
31512
31706
  })
31513
31707
  }),
31514
31708
  /* @__PURE__ */ jsx122("form", {
31515
- className: "mx-2 min-w-48 flex-1",
31709
+ className: "relative mx-2 min-w-48 flex-1",
31516
31710
  onSubmit: (event) => {
31517
31711
  event.preventDefault();
31518
31712
  navigate(draftUrl);
31519
31713
  },
31520
- children: /* @__PURE__ */ jsx122("input", {
31521
- value: draftUrl,
31522
- onChange: (event) => setDraftUrl(event.target.value),
31523
- placeholder: "localhost:3000 or https://example.com",
31524
- className: "h-8 w-full rounded-md border border-zinc-800 bg-black/30 px-3 font-mono text-xs text-zinc-300 outline-none placeholder:text-zinc-600 focus:border-zinc-600"
31714
+ children: /* @__PURE__ */ jsxs106("div", {
31715
+ className: "relative h-8 overflow-hidden rounded-md border border-input bg-muted/30 focus-within:border-ring",
31716
+ children: [
31717
+ loadingProgress > 0 && /* @__PURE__ */ jsx122("div", {
31718
+ className: "absolute inset-y-0 left-0 bg-primary/10 transition-[width] duration-200 ease-out",
31719
+ style: { width: `${loadingProgress}%` }
31720
+ }),
31721
+ /* @__PURE__ */ jsx122("input", {
31722
+ value: draftUrl,
31723
+ onChange: (event) => setDraftUrl(event.target.value),
31724
+ placeholder: "localhost:3000 or https://example.com",
31725
+ className: "relative z-10 h-full w-full bg-transparent px-3 font-mono text-xs text-foreground outline-none placeholder:text-muted-foreground/60"
31726
+ })
31727
+ ]
31525
31728
  })
31526
31729
  }),
31527
31730
  /* @__PURE__ */ jsx122("button", {
@@ -31529,7 +31732,7 @@ function BrowserViewerPanel({ tab }) {
31529
31732
  onClick: () => window.open(normalizedUrl, "_blank", "noopener,noreferrer"),
31530
31733
  disabled: !canRenderUrl,
31531
31734
  title: "Open externally",
31532
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-white/5 hover:text-zinc-200 disabled:text-zinc-600 disabled:hover:bg-transparent",
31735
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground disabled:text-muted-foreground/40 disabled:hover:bg-transparent",
31533
31736
  children: /* @__PURE__ */ jsx122(ExternalLink11, {
31534
31737
  className: "h-4 w-4"
31535
31738
  })
@@ -31542,7 +31745,7 @@ function BrowserViewerPanel({ tab }) {
31542
31745
  newTab: true
31543
31746
  }),
31544
31747
  title: "New browser preview",
31545
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-white/5 hover:text-zinc-200",
31748
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground",
31546
31749
  children: /* @__PURE__ */ jsx122(Plus9, {
31547
31750
  className: "h-4 w-4"
31548
31751
  })
@@ -31554,7 +31757,7 @@ function BrowserViewerPanel({ tab }) {
31554
31757
  title: "Simulator"
31555
31758
  }),
31556
31759
  title: "Simulator preview",
31557
- className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-white/5 hover:text-zinc-200",
31760
+ className: "flex h-8 w-8 items-center justify-center rounded-md transition-colors hover:bg-accent hover:text-accent-foreground",
31558
31761
  children: /* @__PURE__ */ jsx122(Smartphone, {
31559
31762
  className: "h-4 w-4"
31560
31763
  })
@@ -31567,13 +31770,67 @@ function BrowserViewerPanel({ tab }) {
31567
31770
  children: simulatorError
31568
31771
  }),
31569
31772
  /* @__PURE__ */ jsx122("div", {
31773
+ ref: contentRef,
31570
31774
  className: "min-h-0 flex-1 bg-muted/20",
31571
- children: canRenderUrl ? /* @__PURE__ */ jsx122("iframe", {
31775
+ children: useNativeBrowser && canRenderUrl ? /* @__PURE__ */ jsx122("div", {
31776
+ className: "h-full w-full bg-background"
31777
+ }) : canRenderUrl && !embedError ? /* @__PURE__ */ jsx122("iframe", {
31778
+ ref: iframeRef,
31572
31779
  title: tab.title,
31573
31780
  src: normalizedUrl,
31781
+ onLoad: completeLoading,
31574
31782
  className: "h-full w-full border-0 bg-background",
31575
31783
  allow: "clipboard-read; clipboard-write; fullscreen; microphone; camera; geolocation; autoplay"
31576
- }, `${tab.id}:${tab.reloadKey}`) : /* @__PURE__ */ jsx122("div", {
31784
+ }, `${tab.id}:${tab.reloadKey}`) : embedError ? /* @__PURE__ */ jsx122("div", {
31785
+ className: "h-full w-full flex items-center justify-center p-6 text-center",
31786
+ children: /* @__PURE__ */ jsxs106("div", {
31787
+ className: "max-w-md rounded-lg border border-border bg-background p-6 shadow-sm",
31788
+ children: [
31789
+ /* @__PURE__ */ jsx122(Globe23, {
31790
+ className: "mx-auto mb-3 h-8 w-8 text-muted-foreground"
31791
+ }),
31792
+ /* @__PURE__ */ jsx122("h2", {
31793
+ className: "mb-2 text-sm font-semibold text-foreground",
31794
+ children: "This site can't be embedded"
31795
+ }),
31796
+ /* @__PURE__ */ jsx122("p", {
31797
+ className: "mb-4 text-xs leading-relaxed text-muted-foreground",
31798
+ children: embedError
31799
+ }),
31800
+ /* @__PURE__ */ jsxs106("div", {
31801
+ className: "flex flex-wrap justify-center gap-2",
31802
+ children: [
31803
+ /* @__PURE__ */ jsx122(Button, {
31804
+ type: "button",
31805
+ variant: "secondary",
31806
+ size: "sm",
31807
+ onClick: () => {
31808
+ setEmbedError(null);
31809
+ setIsLoading(true);
31810
+ setLoadingProgress(12);
31811
+ reloadBrowserTab(tab.id);
31812
+ },
31813
+ children: "Try again"
31814
+ }),
31815
+ nativeBrowser?.openWindow && /* @__PURE__ */ jsx122(Button, {
31816
+ type: "button",
31817
+ variant: "secondary",
31818
+ size: "sm",
31819
+ onClick: () => nativeBrowser.openWindow?.(normalizedUrl),
31820
+ children: "Open in desktop window"
31821
+ }),
31822
+ /* @__PURE__ */ jsx122(Button, {
31823
+ type: "button",
31824
+ variant: "secondary",
31825
+ size: "sm",
31826
+ onClick: () => window.open(normalizedUrl, "_blank", "noopener,noreferrer"),
31827
+ children: "Open externally"
31828
+ })
31829
+ ]
31830
+ })
31831
+ ]
31832
+ })
31833
+ }) : /* @__PURE__ */ jsx122("div", {
31577
31834
  className: "h-full w-full flex items-center justify-center p-6 text-center",
31578
31835
  children: /* @__PURE__ */ jsxs106("div", {
31579
31836
  className: "max-w-md rounded-lg border border-border bg-background p-6 shadow-sm",
@@ -31608,7 +31865,7 @@ function BrowserViewerPanel({ tab }) {
31608
31865
  }
31609
31866
  // src/components/workspace/ViewerTabs.tsx
31610
31867
  import { memo as memo50, useEffect as useEffect57 } from "react";
31611
- import { Code2, GitCommit as GitCommit5, Globe2 as Globe24, Smartphone as Smartphone2, X as X23 } from "lucide-react";
31868
+ import { Code2, GitCommit as GitCommit5, Globe2 as Globe24, Smartphone as Smartphone2, X as X24 } from "lucide-react";
31612
31869
  import { jsx as jsx123, jsxs as jsxs107 } from "react/jsx-runtime";
31613
31870
  function tabKindLabel(tab) {
31614
31871
  switch (tab.type) {
@@ -31778,6 +32035,8 @@ function renderTabContent(tab, closeTab, updateSessionFileOperationIndex) {
31778
32035
  });
31779
32036
  }
31780
32037
  }
32038
+ var VIEWER_MODE_TAB_BUTTON_BASE = "absolute top-0.5 z-20 h-7 w-9 rounded-full p-0 leading-none transform-none select-none bg-transparent focus:outline-none focus-visible:ring-2 focus-visible:ring-ring active:transform-none";
32039
+ var VIEWER_MODE_ICON_BASE_CLASS = "pointer-events-none absolute top-1/2 z-10 block h-4 w-4 -translate-x-1/2 -translate-y-1/2 shrink-0 transition-colors";
31781
32040
  var ViewerTabs = memo50(function ViewerTabs2() {
31782
32041
  const tabs = useViewerTabsStore((state) => state.tabs);
31783
32042
  const activeTabId = useViewerTabsStore((state) => state.activeTabId);
@@ -31830,28 +32089,31 @@ var ViewerTabs = memo50(function ViewerTabs2() {
31830
32089
  children: /* @__PURE__ */ jsxs107("div", {
31831
32090
  role: "tablist",
31832
32091
  "aria-label": "Viewer mode",
31833
- className: "relative h-8 inline-flex items-center rounded-full ring-1 ring-inset ring-sidebar-border bg-muted/40 p-0.5",
32092
+ className: "relative h-8 w-[4.75rem] shrink-0 rounded-full ring-1 ring-inset ring-sidebar-border bg-muted/40 p-0.5",
31834
32093
  children: [
31835
32094
  /* @__PURE__ */ jsx123("span", {
31836
32095
  "aria-hidden": "true",
31837
32096
  className: `absolute left-0.5 inset-y-0.5 w-9 rounded-full bg-background shadow-sm ring-1 ring-sidebar-border transition-transform duration-200 ease-out pointer-events-none ${activeMode === "preview" ? "translate-x-9" : "translate-x-0"}`
31838
32097
  }),
31839
- /* @__PURE__ */ jsxs107("button", {
32098
+ /* @__PURE__ */ jsx123(Code2, {
32099
+ "aria-hidden": "true",
32100
+ className: `${VIEWER_MODE_ICON_BASE_CLASS} left-5 ${activeMode === "work" ? "text-foreground" : "text-muted-foreground/70"}`
32101
+ }),
32102
+ /* @__PURE__ */ jsx123(Globe24, {
32103
+ "aria-hidden": "true",
32104
+ className: `${VIEWER_MODE_ICON_BASE_CLASS} left-14 ${activeMode === "preview" ? "text-foreground" : "text-muted-foreground/70"}`
32105
+ }),
32106
+ showWorkActivityDot && /* @__PURE__ */ jsx123("span", {
32107
+ className: "pointer-events-none absolute left-[2rem] top-1 z-10 h-1.5 w-1.5 rounded-full bg-primary"
32108
+ }),
32109
+ /* @__PURE__ */ jsx123("button", {
31840
32110
  type: "button",
31841
32111
  role: "tab",
31842
32112
  "aria-selected": activeMode === "work",
31843
32113
  onClick: () => setViewerMode("work"),
31844
32114
  title: "Work tabs",
31845
32115
  "aria-label": "Work tabs",
31846
- className: `relative z-10 h-7 w-9 inline-flex items-center justify-center rounded-full p-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors ${activeMode === "work" ? "text-foreground" : "text-muted-foreground/70 hover:text-foreground"}`,
31847
- children: [
31848
- /* @__PURE__ */ jsx123(Code2, {
31849
- className: "h-4 w-4"
31850
- }),
31851
- showWorkActivityDot && /* @__PURE__ */ jsx123("span", {
31852
- className: "absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-primary"
31853
- })
31854
- ]
32116
+ className: `${VIEWER_MODE_TAB_BUTTON_BASE} left-0.5`
31855
32117
  }),
31856
32118
  /* @__PURE__ */ jsx123("button", {
31857
32119
  type: "button",
@@ -31860,10 +32122,7 @@ var ViewerTabs = memo50(function ViewerTabs2() {
31860
32122
  onClick: handlePreviewMode,
31861
32123
  title: "Preview tabs",
31862
32124
  "aria-label": "Preview tabs",
31863
- className: `relative z-10 h-7 w-9 inline-flex items-center justify-center rounded-full p-0 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring transition-colors ${activeMode === "preview" ? "text-foreground" : "text-muted-foreground/70 hover:text-foreground"}`,
31864
- children: /* @__PURE__ */ jsx123(Globe24, {
31865
- className: "h-4 w-4"
31866
- })
32125
+ className: `${VIEWER_MODE_TAB_BUTTON_BASE} left-[2.375rem]`
31867
32126
  })
31868
32127
  ]
31869
32128
  })
@@ -31875,7 +32134,7 @@ var ViewerTabs = memo50(function ViewerTabs2() {
31875
32134
  const isActive = tab.id === activeTab.id;
31876
32135
  const activityKind = getTabActivityKind(tab);
31877
32136
  return /* @__PURE__ */ jsxs107("div", {
31878
- className: `group h-12 w-44 max-w-56 shrink-0 px-3 border-r border-sidebar-border flex items-center gap-2 text-left transition-colors ${isActive ? "bg-sidebar text-sidebar-foreground" : "border-b bg-background text-muted-foreground/70 hover:text-foreground hover:bg-sidebar-accent/40"}`,
32137
+ className: `group h-12 w-44 max-w-56 shrink-0 px-3 border-r border-b border-sidebar-border flex items-center gap-2 text-left transition-colors ${isActive ? "border-b-transparent bg-sidebar text-sidebar-foreground" : "bg-background text-muted-foreground/70 hover:text-foreground hover:bg-sidebar-accent/40"}`,
31879
32138
  title: `${tab.title}
31880
32139
  ${tabKindLabel(tab)}`,
31881
32140
  children: [
@@ -31900,7 +32159,7 @@ ${tabKindLabel(tab)}`,
31900
32159
  },
31901
32160
  title: "Close tab",
31902
32161
  className: "h-6 w-6 opacity-60 group-hover:opacity-100 shrink-0",
31903
- children: /* @__PURE__ */ jsx123(X23, {
32162
+ children: /* @__PURE__ */ jsx123(X24, {
31904
32163
  className: "h-3.5 w-3.5"
31905
32164
  })
31906
32165
  })
@@ -31918,7 +32177,7 @@ ${tabKindLabel(tab)}`,
31918
32177
  title: "Close all tabs and collapse viewer",
31919
32178
  "aria-label": "Close all tabs and collapse viewer",
31920
32179
  className: "h-8 w-8 inline-flex items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-destructive/10 hover:text-destructive",
31921
- children: /* @__PURE__ */ jsx123(X23, {
32180
+ children: /* @__PURE__ */ jsx123(X24, {
31922
32181
  className: "h-3.5 w-3.5"
31923
32182
  })
31924
32183
  })
@@ -31927,12 +32186,28 @@ ${tabKindLabel(tab)}`,
31927
32186
  })
31928
32187
  ]
31929
32188
  }),
31930
- /* @__PURE__ */ jsx123("div", {
31931
- className: "flex-1 min-h-0 overflow-hidden",
31932
- children: activeTab ? renderTabContent(activeTab, closeTab, updateSessionFileOperationIndex) : /* @__PURE__ */ jsx123("div", {
31933
- className: "flex h-full items-center justify-center bg-sidebar text-muted-foreground/60 text-sm",
31934
- children: activeMode === "work" ? "No work tabs open" : "No preview tabs open"
31935
- })
32189
+ /* @__PURE__ */ jsxs107("div", {
32190
+ className: "relative flex-1 min-h-0 overflow-hidden",
32191
+ children: [
32192
+ previewTabs.map((tab) => {
32193
+ const isActive = activeMode === "preview" && tab.id === activeTab?.id;
32194
+ return /* @__PURE__ */ jsx123("div", {
32195
+ "aria-hidden": !isActive,
32196
+ className: `absolute inset-0 ${isActive ? "block" : "hidden"}`,
32197
+ children: /* @__PURE__ */ jsx123(BrowserViewerPanel, {
32198
+ tab
32199
+ })
32200
+ }, tab.id);
32201
+ }),
32202
+ activeMode === "work" && activeTab && /* @__PURE__ */ jsx123("div", {
32203
+ className: "absolute inset-0",
32204
+ children: renderTabContent(activeTab, closeTab, updateSessionFileOperationIndex)
32205
+ }),
32206
+ !activeTab && /* @__PURE__ */ jsx123("div", {
32207
+ className: "absolute inset-0 flex items-center justify-center bg-sidebar text-muted-foreground/60 text-sm",
32208
+ children: activeMode === "work" ? "No work tabs open" : "No preview tabs open"
32209
+ })
32210
+ ]
31936
32211
  })
31937
32212
  ]
31938
32213
  });
@@ -31941,12 +32216,12 @@ ${tabKindLabel(tab)}`,
31941
32216
  import { memo as memo53, useEffect as useEffect60 } from "react";
31942
32217
 
31943
32218
  // src/components/onboarding/steps/ProviderSetupStep.tsx
31944
- import { memo as memo51, useEffect as useEffect58, useState as useState53, useRef as useRef38 } from "react";
32219
+ import { memo as memo51, useEffect as useEffect58, useState as useState53, useRef as useRef39 } from "react";
31945
32220
  import {
31946
32221
  Copy as Copy5,
31947
32222
  Check as Check16,
31948
32223
  CreditCard as CreditCard4,
31949
- X as X24,
32224
+ X as X25,
31950
32225
  Key as Key2,
31951
32226
  ExternalLink as ExternalLink12,
31952
32227
  ArrowRight as ArrowRight3,
@@ -32031,20 +32306,20 @@ var ProviderSetupStep = memo51(function ProviderSetupStep2({
32031
32306
  const [copilotCodeCopied, setCopilotCodeCopied] = useState53(false);
32032
32307
  const [copilotModalOpen, setCopilotModalOpen] = useState53(false);
32033
32308
  const [copilotLoading, setCopilotLoading] = useState53(false);
32034
- const copilotPollRef = useRef38(undefined);
32035
- const copilotCancelledRef = useRef38(false);
32036
- const copilotPollFnRef = useRef38(onPollCopilotDeviceFlow);
32309
+ const copilotPollRef = useRef39(undefined);
32310
+ const copilotCancelledRef = useRef39(false);
32311
+ const copilotPollFnRef = useRef39(onPollCopilotDeviceFlow);
32037
32312
  copilotPollFnRef.current = onPollCopilotDeviceFlow;
32038
32313
  const balance = useOttoRouterStore((s) => s.balance);
32039
32314
  const usdcBalance = useOttoRouterStore((s) => s.usdcBalance);
32040
32315
  const payg = useOttoRouterStore((s) => s.payg);
32041
32316
  const subscription = useOttoRouterStore((s) => s.subscription);
32042
32317
  const isBalanceLoading = useOttoRouterStore((s) => s.isLoading);
32043
- const apiKeyInputRef = useRef38(null);
32044
- const oauthCodeInputRef = useRef38(null);
32045
- const importPrivateKeyRef = useRef38(null);
32318
+ const apiKeyInputRef = useRef39(null);
32319
+ const oauthCodeInputRef = useRef39(null);
32320
+ const importPrivateKeyRef = useRef39(null);
32046
32321
  const isTopupModalOpen = useOttoRouterStore((s) => s.isTopupModalOpen);
32047
- const prevTopupModalOpen = useRef38(false);
32322
+ const prevTopupModalOpen = useRef39(false);
32048
32323
  const { fetchBalance } = useOttoRouterBalance("ottorouter");
32049
32324
  const effectivePayg = payg?.effectiveSpendableUsd ?? balance ?? 0;
32050
32325
  const setuStatusLabel = subscription?.active ? `GO ${(subscription.creditsRemaining ?? 0).toFixed(1)} credits` : `$${effectivePayg.toFixed(2)}`;
@@ -32743,7 +33018,7 @@ var ProviderSetupStep = memo51(function ProviderSetupStep2({
32743
33018
  type: "button",
32744
33019
  onClick: () => handleRemoveProvider(id),
32745
33020
  className: "ml-1 p-1 text-green-600/40 dark:text-green-500/40 hover:text-green-600/80 dark:hover:text-green-500/80 opacity-0 group-hover:opacity-100 transition-opacity",
32746
- children: /* @__PURE__ */ jsx124(X24, {
33021
+ children: /* @__PURE__ */ jsx124(X25, {
32747
33022
  className: "w-3 h-3"
32748
33023
  })
32749
33024
  })
@@ -32802,7 +33077,7 @@ var ProviderSetupStep = memo51(function ProviderSetupStep2({
32802
33077
  setApiKeyInput("");
32803
33078
  },
32804
33079
  className: "shrink-0 p-1.5 text-muted-foreground hover:text-foreground",
32805
- children: /* @__PURE__ */ jsx124(X24, {
33080
+ children: /* @__PURE__ */ jsx124(X25, {
32806
33081
  className: "w-4 h-4"
32807
33082
  })
32808
33083
  })
@@ -33563,7 +33838,7 @@ var ProviderSetupStep = memo51(function ProviderSetupStep2({
33563
33838
  });
33564
33839
 
33565
33840
  // src/components/onboarding/steps/DefaultsStep.tsx
33566
- import { memo as memo52, useState as useState54, useEffect as useEffect59, useId as useId5, useRef as useRef39 } from "react";
33841
+ import { memo as memo52, useState as useState54, useEffect as useEffect59, useId as useId5, useRef as useRef40 } from "react";
33567
33842
  import { ArrowLeft, Sparkles as Sparkles9, ChevronDown as ChevronDown15 } from "lucide-react";
33568
33843
  import { jsx as jsx125, jsxs as jsxs109, Fragment as Fragment48 } from "react/jsx-runtime";
33569
33844
  var DefaultsStep = memo52(function DefaultsStep2({
@@ -33581,7 +33856,7 @@ var DefaultsStep = memo52(function DefaultsStep2({
33581
33856
  const [selectedAgent, setSelectedAgent] = useState54(authStatus.defaults.agent || "build");
33582
33857
  const [selectedApproval, setSelectedApproval] = useState54(authStatus.defaults.toolApproval || "dangerous");
33583
33858
  const [guidedMode, setGuidedMode] = useState54(false);
33584
- const hasUserChangedProvider = useRef39(false);
33859
+ const hasUserChangedProvider = useRef40(false);
33585
33860
  const providerId = useId5();
33586
33861
  const modelId = useId5();
33587
33862
  const agentId = useId5();
@@ -34040,7 +34315,7 @@ var OnboardingModal = memo53(function OnboardingModal2({
34040
34315
  });
34041
34316
  });
34042
34317
  // src/components/dashboard/UsageDashboard.tsx
34043
- import { useCallback as useCallback37, useEffect as useEffect61, useMemo as useMemo33, useState as useState55 } from "react";
34318
+ import { useCallback as useCallback38, useEffect as useEffect61, useMemo as useMemo33, useState as useState55 } from "react";
34044
34319
  import { AlertTriangle as AlertTriangle3, ArrowLeft as ArrowLeft2, Globe2 as Globe25, RefreshCw as RefreshCw14 } from "lucide-react";
34045
34320
  import { jsx as jsx127, jsxs as jsxs111, Fragment as Fragment49 } from "react/jsx-runtime";
34046
34321
  function formatNumber(n) {
@@ -34618,7 +34893,7 @@ function UsageDashboard({ onBack }) {
34618
34893
  const [loading, setLoading] = useState55(false);
34619
34894
  const [error, setError] = useState55(null);
34620
34895
  const [scope, setScope] = useState55("project");
34621
- const fetchStats = useCallback37(async () => {
34896
+ const fetchStats = useCallback38(async () => {
34622
34897
  setLoading(true);
34623
34898
  setError(null);
34624
34899
  try {
@@ -34633,7 +34908,7 @@ function UsageDashboard({ onBack }) {
34633
34908
  useEffect61(() => {
34634
34909
  fetchStats();
34635
34910
  }, [fetchStats]);
34636
- const handleBack = useCallback37(() => {
34911
+ const handleBack = useCallback38(() => {
34637
34912
  if (onBack)
34638
34913
  return onBack();
34639
34914
  if (typeof window === "undefined")
@@ -34998,7 +35273,7 @@ function UsageDashboard({ onBack }) {
34998
35273
  });
34999
35274
  }
35000
35275
  // src/hooks/useClientEvents.ts
35001
- import { useEffect as useEffect62, useRef as useRef40 } from "react";
35276
+ import { useEffect as useEffect62, useRef as useRef41 } from "react";
35002
35277
  import { useQueryClient as useQueryClient23 } from "@tanstack/react-query";
35003
35278
  import {
35004
35279
  buildClientEventsStreamUrl,
@@ -35185,7 +35460,7 @@ async function maybeShowLocalAccessToast(baseUrl) {
35185
35460
  }
35186
35461
  function useClientEvents(activeSessionId) {
35187
35462
  const queryClient = useQueryClient23();
35188
- const activeSessionIdRef = useRef40(activeSessionId);
35463
+ const activeSessionIdRef = useRef41(activeSessionId);
35189
35464
  useEffect62(() => {
35190
35465
  activeSessionIdRef.current = activeSessionId;
35191
35466
  }, [activeSessionId]);
@@ -35268,7 +35543,7 @@ function useClientEvents(activeSessionId) {
35268
35543
  return buildClientEventsStreamUrl({ baseUrl: getBaseUrl() });
35269
35544
  }
35270
35545
  // src/hooks/useTheme.ts
35271
- import { useEffect as useEffect63, useState as useState56, useCallback as useCallback38, useMemo as useMemo34 } from "react";
35546
+ import { useEffect as useEffect63, useState as useState56, useCallback as useCallback39, useMemo as useMemo34 } from "react";
35272
35547
  var STORAGE_KEY3 = "otto-theme";
35273
35548
  function resolveInitialTheme() {
35274
35549
  if (typeof window === "undefined") {
@@ -35314,7 +35589,7 @@ function useTheme() {
35314
35589
  window.addEventListener("message", handler);
35315
35590
  return () => window.removeEventListener("message", handler);
35316
35591
  }, []);
35317
- const toggleTheme = useCallback38(() => {
35592
+ const toggleTheme = useCallback39(() => {
35318
35593
  setTheme((prev) => prev === "dark" ? "light" : "dark");
35319
35594
  }, []);
35320
35595
  return useMemo34(() => ({ theme, setTheme, toggleTheme }), [theme, toggleTheme]);
@@ -35348,7 +35623,7 @@ function useWorkingDirectory() {
35348
35623
  return dirName;
35349
35624
  }
35350
35625
  // src/hooks/useKeyboardShortcuts.ts
35351
- import { useEffect as useEffect65, useCallback as useCallback39 } from "react";
35626
+ import { useEffect as useEffect65, useCallback as useCallback40 } from "react";
35352
35627
 
35353
35628
  // src/stores/sidebarStore.ts
35354
35629
  import { create as create24 } from "zustand";
@@ -35402,7 +35677,7 @@ function useKeyboardShortcuts({
35402
35677
  const toggleResearch = useResearchStore((state) => state.toggleSidebar);
35403
35678
  const toggleTerminalPanel = useTerminalStore((state) => state.togglePanel);
35404
35679
  const currentSessionIndex = sessionIds.indexOf(activeSessionId || "");
35405
- const handleKeyDown = useCallback39((e) => {
35680
+ const handleKeyDown = useCallback40((e) => {
35406
35681
  const target = e.target;
35407
35682
  const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
35408
35683
  const isInTerminal = !!target.closest("[data-terminal-viewer]");
@@ -35667,7 +35942,7 @@ function useKeyboardShortcuts({
35667
35942
  // src/hooks/useImageUpload.ts
35668
35943
  import {
35669
35944
  useState as useState58,
35670
- useCallback as useCallback40,
35945
+ useCallback as useCallback41,
35671
35946
  useEffect as useEffect66
35672
35947
  } from "react";
35673
35948
  var SUPPORTED_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
@@ -35700,7 +35975,7 @@ function useImageUpload(options = {}) {
35700
35975
  const [isDragging, setIsDragging] = useState58(false);
35701
35976
  const [error, setError] = useState58(null);
35702
35977
  const maxSizeBytes = maxSizeMB * 1024 * 1024;
35703
- const validateFile = useCallback40((file) => {
35978
+ const validateFile = useCallback41((file) => {
35704
35979
  if (!SUPPORTED_TYPES.includes(file.type)) {
35705
35980
  return `Unsupported file type: ${file.type}. Supported: PNG, JPEG, GIF, WebP`;
35706
35981
  }
@@ -35709,7 +35984,7 @@ function useImageUpload(options = {}) {
35709
35984
  }
35710
35985
  return null;
35711
35986
  }, [maxSizeBytes, maxSizeMB]);
35712
- const addImages = useCallback40(async (files) => {
35987
+ const addImages = useCallback41(async (files) => {
35713
35988
  setError(null);
35714
35989
  const fileArray = Array.from(files);
35715
35990
  const remaining = maxImages - images.length;
@@ -35745,22 +36020,22 @@ function useImageUpload(options = {}) {
35745
36020
  setImages((prev) => [...prev, ...newImages]);
35746
36021
  }
35747
36022
  }, [images.length, maxImages, validateFile]);
35748
- const removeImage = useCallback40((id) => {
36023
+ const removeImage = useCallback41((id) => {
35749
36024
  setImages((prev) => prev.filter((img) => img.id !== id));
35750
36025
  setError(null);
35751
36026
  }, []);
35752
- const clearImages = useCallback40(() => {
36027
+ const clearImages = useCallback41(() => {
35753
36028
  setImages([]);
35754
36029
  setError(null);
35755
36030
  }, []);
35756
- const handleDragEnter = useCallback40((e) => {
36031
+ const handleDragEnter = useCallback41((e) => {
35757
36032
  e.preventDefault();
35758
36033
  e.stopPropagation();
35759
36034
  if (e.dataTransfer.types.includes("Files")) {
35760
36035
  setIsDragging(true);
35761
36036
  }
35762
36037
  }, []);
35763
- const handleDragLeave = useCallback40((e) => {
36038
+ const handleDragLeave = useCallback41((e) => {
35764
36039
  e.preventDefault();
35765
36040
  e.stopPropagation();
35766
36041
  const rect = e.currentTarget.getBoundingClientRect();
@@ -35770,11 +36045,11 @@ function useImageUpload(options = {}) {
35770
36045
  setIsDragging(false);
35771
36046
  }
35772
36047
  }, []);
35773
- const handleDragOver = useCallback40((e) => {
36048
+ const handleDragOver = useCallback41((e) => {
35774
36049
  e.preventDefault();
35775
36050
  e.stopPropagation();
35776
36051
  }, []);
35777
- const handleDrop = useCallback40((e) => {
36052
+ const handleDrop = useCallback41((e) => {
35778
36053
  e.preventDefault();
35779
36054
  e.stopPropagation();
35780
36055
  setIsDragging(false);
@@ -35786,7 +36061,7 @@ function useImageUpload(options = {}) {
35786
36061
  }
35787
36062
  }
35788
36063
  }, [addImages]);
35789
- const handlePaste = useCallback40((e) => {
36064
+ const handlePaste = useCallback41((e) => {
35790
36065
  const items = e.clipboardData?.items;
35791
36066
  if (!items)
35792
36067
  return;
@@ -35865,10 +36140,10 @@ function useImageUpload(options = {}) {
35865
36140
  };
35866
36141
  }
35867
36142
  // src/hooks/useSetuPayments.ts
35868
- import { useEffect as useEffect67, useRef as useRef41 } from "react";
36143
+ import { useEffect as useEffect67, useRef as useRef42 } from "react";
35869
36144
  function useSetuPayments(sessionId) {
35870
- const clientRef = useRef41(null);
35871
- const loadingToastIdRef = useRef41(null);
36145
+ const clientRef = useRef42(null);
36146
+ const loadingToastIdRef = useRef42(null);
35872
36147
  const setBalance = useOttoRouterStore((s) => s.setBalance);
35873
36148
  const setPaymentPending = useOttoRouterStore((s) => s.setPaymentPending);
35874
36149
  const removeToast = useToastStore((s) => s.removeToast);
@@ -36223,4 +36498,4 @@ export {
36223
36498
  API_BASE_URL
36224
36499
  };
36225
36500
 
36226
- //# debugId=B7D0C288A88BE19964756E2164756E21
36501
+ //# debugId=66B25A0616A3472664756E2164756E21