@agent-native/core 0.99.0 → 0.99.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +23 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +33 -38
  5. package/corpus/core/src/audit/config.ts +0 -1
  6. package/corpus/core/src/chat-threads/store.ts +57 -41
  7. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +1 -1
  8. package/corpus/core/src/client/AssistantChat.tsx +1 -2
  9. package/corpus/core/src/client/analytics.ts +13 -0
  10. package/corpus/core/src/client/chat/message-components.tsx +15 -3
  11. package/corpus/core/src/client/chat/tool-call-display.tsx +18 -1
  12. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -9
  13. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +0 -133
  14. package/corpus/core/src/client/index.ts +7 -0
  15. package/corpus/core/src/client/settings/DemoModeSection.tsx +9 -65
  16. package/corpus/core/src/client/settings/SettingsPanel.tsx +1 -1
  17. package/corpus/core/src/client/use-db-sync.ts +3 -15
  18. package/corpus/core/src/client/use-demo-mode-status.ts +16 -30
  19. package/corpus/core/src/demo/browser-state.ts +47 -0
  20. package/corpus/core/src/demo/fetch-interceptor.ts +12 -59
  21. package/corpus/core/src/deploy/workspace-deploy.ts +2 -2
  22. package/corpus/core/src/server/action-discovery.ts +0 -1
  23. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +13 -0
  24. package/corpus/core/src/server/agent-teams.ts +17 -0
  25. package/corpus/core/src/server/core-routes-plugin.ts +0 -30
  26. package/corpus/core/src/server/google-oauth.ts +6 -2
  27. package/corpus/core/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
  28. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +48 -7
  29. package/corpus/templates/analytics/AGENTS.md +4 -4
  30. package/corpus/templates/analytics/actions/compose-dashboard.ts +12 -1
  31. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +15 -4
  32. package/corpus/templates/analytics/actions/mutate-dashboard.ts +2 -1
  33. package/corpus/templates/analytics/actions/update-dashboard.ts +14 -0
  34. package/corpus/templates/analytics/app/assets/replay-cursor.svg +17 -0
  35. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -3
  36. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -27
  37. package/corpus/templates/analytics/app/global.css +17 -9
  38. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +32 -4
  39. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -1
  40. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +21 -0
  41. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +14 -0
  42. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +1 -1
  43. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +77 -23
  44. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  45. package/corpus/templates/analytics/changelog/2026-07-13-all-time-dashboard-filters-no-longer-send-invalid-dates-to-b.md +6 -0
  46. package/corpus/templates/analytics/changelog/2026-07-13-dashboard-charts-now-honor-the-selected-time-range-by-defaul.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-07-13-error-issue-titles-wrap-instead-of-overflowing.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-13-keep-real-authorized-identities-in-analytics-incident-lookup.md +6 -0
  49. package/corpus/templates/analytics/changelog/2026-07-13-saving-a-new-dashboard-view-no-longer-fails-with-a-server-er.md +6 -0
  50. package/corpus/templates/analytics/changelog/2026-07-13-session-replay-cursors-stay-hidden-until-their-first-recorde.md +6 -0
  51. package/corpus/templates/analytics/changelog/2026-07-13-session-replay-links-now-support-cmd-click-and-middle-click-.md +6 -0
  52. package/corpus/templates/analytics/changelog/2026-07-13-the-analytics-agents-tab-is-available-again-from-the-main-na.md +6 -0
  53. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
  54. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +4 -0
  55. package/corpus/templates/analytics/server/lib/dashboard-time-scope.ts +170 -0
  56. package/corpus/templates/analytics/server/lib/dashboards-store.ts +43 -4
  57. package/corpus/templates/analytics/server/lib/error-capture.ts +30 -67
  58. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +20 -5
  59. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +1 -1
  60. package/corpus/templates/analytics/server/lib/session-replay.ts +5 -111
  61. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -0
  62. package/corpus/templates/assets/app/routes/library.tsx +3 -4
  63. package/corpus/templates/assets/changelog/2026-07-13-internal-library-links-open-in-new-tabs.md +6 -0
  64. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +43 -40
  65. package/corpus/templates/calendar/app/pages/ManageBookingPage.tsx +6 -8
  66. package/corpus/templates/calendar/changelog/2026-07-13-booking-links-and-previews-open-in-new-tabs.md +6 -0
  67. package/corpus/templates/clips/app/components/library/recording-card.tsx +27 -26
  68. package/corpus/templates/clips/app/components/library/space-card.tsx +4 -6
  69. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +4 -7
  70. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +19 -9
  71. package/corpus/templates/clips/app/routes/share.$shareId.tsx +5 -3
  72. package/corpus/templates/clips/changelog/2026-07-13-recording-links-open-in-new-tabs.md +6 -0
  73. package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +6 -8
  74. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +5 -3
  75. package/corpus/templates/content/app/components/editor/database/FormView.tsx +6 -9
  76. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +18 -16
  77. package/corpus/templates/content/changelog/2026-07-13-document-links-open-in-new-tabs.md +6 -0
  78. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +33 -16
  79. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -1
  80. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +4 -4
  81. package/corpus/templates/design/app/pages/DesignSystems.tsx +8 -10
  82. package/corpus/templates/design/app/pages/Index.tsx +6 -1
  83. package/corpus/templates/design/app/pages/Present.tsx +12 -6
  84. package/corpus/templates/design/changelog/2026-07-13-design-navigation-links-open-in-new-tabs.md +6 -0
  85. package/corpus/templates/design/changelog/2026-07-13-design-session-replays-include-preview-iframes.md +6 -0
  86. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +11 -11
  87. package/corpus/templates/forms/app/pages/FormsListPage.tsx +6 -8
  88. package/corpus/templates/forms/changelog/2026-07-13-form-navigation-links-open-in-new-tabs.md +6 -0
  89. package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +3 -8
  90. package/corpus/templates/mail/changelog/2026-07-13-settings-links-open-in-new-tabs.md +6 -0
  91. package/dist/agent/production-agent.d.ts +9 -0
  92. package/dist/agent/production-agent.d.ts.map +1 -1
  93. package/dist/agent/production-agent.js +25 -37
  94. package/dist/agent/production-agent.js.map +1 -1
  95. package/dist/audit/config.d.ts.map +1 -1
  96. package/dist/audit/config.js +0 -1
  97. package/dist/audit/config.js.map +1 -1
  98. package/dist/chat-threads/store.d.ts.map +1 -1
  99. package/dist/chat-threads/store.js +47 -33
  100. package/dist/chat-threads/store.js.map +1 -1
  101. package/dist/cli/pr-visual-recap-workflow.d.ts +1 -1
  102. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  103. package/dist/cli/pr-visual-recap-workflow.js +1 -1
  104. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  105. package/dist/client/AssistantChat.d.ts.map +1 -1
  106. package/dist/client/AssistantChat.js +1 -1
  107. package/dist/client/AssistantChat.js.map +1 -1
  108. package/dist/client/analytics.d.ts.map +1 -1
  109. package/dist/client/analytics.js +11 -0
  110. package/dist/client/analytics.js.map +1 -1
  111. package/dist/client/chat/message-components.d.ts.map +1 -1
  112. package/dist/client/chat/message-components.js +9 -3
  113. package/dist/client/chat/message-components.js.map +1 -1
  114. package/dist/client/chat/tool-call-display.d.ts +3 -1
  115. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  116. package/dist/client/chat/tool-call-display.js +10 -2
  117. package/dist/client/chat/tool-call-display.js.map +1 -1
  118. package/dist/client/extensions/EmbeddedExtension.js +2 -6
  119. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  120. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  121. package/dist/client/extensions/ExtensionViewer.js +2 -59
  122. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  123. package/dist/client/index.d.ts +2 -0
  124. package/dist/client/index.d.ts.map +1 -1
  125. package/dist/client/index.js +2 -0
  126. package/dist/client/index.js.map +1 -1
  127. package/dist/client/settings/DemoModeSection.d.ts +1 -13
  128. package/dist/client/settings/DemoModeSection.d.ts.map +1 -1
  129. package/dist/client/settings/DemoModeSection.js +5 -52
  130. package/dist/client/settings/DemoModeSection.js.map +1 -1
  131. package/dist/client/settings/SettingsPanel.js +1 -1
  132. package/dist/client/settings/SettingsPanel.js.map +1 -1
  133. package/dist/client/use-db-sync.d.ts.map +1 -1
  134. package/dist/client/use-db-sync.js +3 -7
  135. package/dist/client/use-db-sync.js.map +1 -1
  136. package/dist/client/use-demo-mode-status.d.ts +3 -4
  137. package/dist/client/use-demo-mode-status.d.ts.map +1 -1
  138. package/dist/client/use-demo-mode-status.js +8 -23
  139. package/dist/client/use-demo-mode-status.js.map +1 -1
  140. package/dist/collab/struct-routes.d.ts +1 -1
  141. package/dist/demo/browser-state.d.ts +10 -0
  142. package/dist/demo/browser-state.d.ts.map +1 -0
  143. package/dist/demo/browser-state.js +49 -0
  144. package/dist/demo/browser-state.js.map +1 -0
  145. package/dist/demo/fetch-interceptor.d.ts +2 -7
  146. package/dist/demo/fetch-interceptor.d.ts.map +1 -1
  147. package/dist/demo/fetch-interceptor.js +12 -56
  148. package/dist/demo/fetch-interceptor.js.map +1 -1
  149. package/dist/deploy/workspace-deploy.js +2 -2
  150. package/dist/deploy/workspace-deploy.js.map +1 -1
  151. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  152. package/dist/notifications/routes.d.ts +2 -2
  153. package/dist/observability/routes.d.ts +5 -5
  154. package/dist/resources/handlers.d.ts +3 -3
  155. package/dist/secrets/routes.d.ts +9 -9
  156. package/dist/server/action-discovery.d.ts.map +1 -1
  157. package/dist/server/action-discovery.js +0 -1
  158. package/dist/server/action-discovery.js.map +1 -1
  159. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  160. package/dist/server/agent-chat/action-filters-a2a.js +11 -1
  161. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  162. package/dist/server/agent-teams.d.ts.map +1 -1
  163. package/dist/server/agent-teams.js +15 -1
  164. package/dist/server/agent-teams.js.map +1 -1
  165. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  166. package/dist/server/core-routes-plugin.js +0 -27
  167. package/dist/server/core-routes-plugin.js.map +1 -1
  168. package/dist/server/google-oauth.d.ts.map +1 -1
  169. package/dist/server/google-oauth.js +6 -2
  170. package/dist/server/google-oauth.js.map +1 -1
  171. package/dist/server/transcribe-voice.d.ts +1 -1
  172. package/dist/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
  173. package/package.json +2 -2
  174. package/src/templates/workspace-core/.agents/skills/native-navigation/SKILL.md +67 -0
  175. package/corpus/core/src/demo/actions/toggle-demo-mode.ts +0 -23
  176. package/corpus/core/src/demo/config.ts +0 -57
  177. package/dist/demo/actions/toggle-demo-mode.d.ts +0 -8
  178. package/dist/demo/actions/toggle-demo-mode.d.ts.map +0 -1
  179. package/dist/demo/actions/toggle-demo-mode.js +0 -21
  180. package/dist/demo/actions/toggle-demo-mode.js.map +0 -1
  181. package/dist/demo/config.d.ts +0 -9
  182. package/dist/demo/config.d.ts.map +0 -1
  183. package/dist/demo/config.js +0 -58
  184. package/dist/demo/config.js.map +0 -1
@@ -1339,13 +1339,12 @@ function AllAssetsBrowser() {
1339
1339
  </div>
1340
1340
  </button>
1341
1341
  {(asset as any).libraryTitle ? (
1342
- <button
1343
- type="button"
1344
- onClick={() => navigate(`/library/${asset.libraryId}`)}
1342
+ <Link
1343
+ to={`/library/${asset.libraryId}`}
1345
1344
  className="absolute bottom-2 left-2 z-10 max-w-[calc(100%-1rem)] truncate rounded-full bg-background/95 px-2.5 py-1 text-[11px] font-medium shadow-sm transition hover:bg-background"
1346
1345
  >
1347
1346
  {(asset as any).libraryTitle}
1348
- </button>
1347
+ </Link>
1349
1348
  ) : null}
1350
1349
  <TooltipProvider>
1351
1350
  <Tooltip>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Internal library links now support opening in a new tab.
@@ -48,7 +48,7 @@ import {
48
48
  } from "date-fns";
49
49
  import { nanoid } from "nanoid";
50
50
  import { useEffect, useMemo, useRef, useState } from "react";
51
- import { useNavigate, useSearchParams } from "react-router";
51
+ import { Link, useNavigate, useSearchParams } from "react-router";
52
52
  import { toast } from "sonner";
53
53
 
54
54
  import { CloudUpgrade } from "@/components/CloudUpgrade";
@@ -915,12 +915,10 @@ export default function BookingLinksPage({
915
915
  toast.error(t("common.clipboardUnavailable"));
916
916
  }
917
917
 
918
- function openPreview(slug: string) {
919
- // For local preview, use the local path
920
- const localPath = bookingUsername
918
+ function bookingPreviewPath(slug: string) {
919
+ return bookingUsername
921
920
  ? `/book/${bookingUsername}/${slug}`
922
921
  : `/book/${slug}`;
923
- window.open(localPath, "_blank", "noopener,noreferrer");
924
922
  }
925
923
 
926
924
  const handleSaveRef = useRef(handleSave);
@@ -952,14 +950,13 @@ export default function BookingLinksPage({
952
950
  }
953
951
  return {
954
952
  left: (
955
- <button
956
- type="button"
957
- onClick={() => navigate("/booking-links")}
953
+ <Link
954
+ to="/booking-links"
958
955
  className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
959
956
  >
960
957
  <IconChevronLeft className="h-4 w-4" />
961
958
  {t("bookingLinks.back")}
962
- </button>
959
+ </Link>
963
960
  ),
964
961
  right: selectedLink ? (
965
962
  <div className="flex items-center gap-1.5">
@@ -1008,14 +1005,19 @@ export default function BookingLinksPage({
1008
1005
  <Tooltip>
1009
1006
  <TooltipTrigger asChild>
1010
1007
  <Button
1011
- type="button"
1008
+ asChild
1012
1009
  variant="ghost"
1013
1010
  size="icon"
1014
- onClick={() => openPreview(draft.slug)}
1015
1011
  className={cn("h-8 w-8", BRAND_ICON_LINK_CLASS)}
1016
1012
  aria-label={t("bookingLinks.openBookingLink")}
1017
1013
  >
1018
- <IconExternalLink className="h-4 w-4" />
1014
+ <a
1015
+ href={bookingPreviewPath(draft.slug)}
1016
+ target="_blank"
1017
+ rel="noopener noreferrer"
1018
+ >
1019
+ <IconExternalLink className="h-4 w-4" />
1020
+ </a>
1019
1021
  </Button>
1020
1022
  </TooltipTrigger>
1021
1023
  <TooltipContent>{t("bookingLinks.openLink")}</TooltipContent>
@@ -1300,14 +1302,19 @@ export default function BookingLinksPage({
1300
1302
  <Tooltip>
1301
1303
  <TooltipTrigger asChild>
1302
1304
  <Button
1303
- type="button"
1305
+ asChild
1304
1306
  variant="ghost"
1305
1307
  size="icon"
1306
- onClick={() => openPreview(draft.slug)}
1307
1308
  className={cn("h-8 w-8", BRAND_ICON_LINK_CLASS)}
1308
1309
  aria-label={t("bookingLinks.openBookingPageNewTab")}
1309
1310
  >
1310
- <IconExternalLink className="h-4 w-4" />
1311
+ <a
1312
+ href={bookingPreviewPath(draft.slug)}
1313
+ target="_blank"
1314
+ rel="noopener noreferrer"
1315
+ >
1316
+ <IconExternalLink className="h-4 w-4" />
1317
+ </a>
1311
1318
  </Button>
1312
1319
  </TooltipTrigger>
1313
1320
  <TooltipContent>
@@ -1499,7 +1506,7 @@ export default function BookingLinksPage({
1499
1506
  availability={availability ?? undefined}
1500
1507
  bookingUrl={previewUrl}
1501
1508
  onCopy={() => void copyPreviewUrl(draft.slug)}
1502
- onOpen={() => openPreview(draft.slug)}
1509
+ openHref={bookingPreviewPath(draft.slug)}
1503
1510
  onCollapse={() => setIsPreviewCollapsed(true)}
1504
1511
  />
1505
1512
  )}
@@ -1597,9 +1604,8 @@ export default function BookingLinksPage({
1597
1604
  >
1598
1605
  <div className="flex flex-col gap-3 px-4 py-4 sm:flex-row sm:items-center sm:gap-4 sm:px-5">
1599
1606
  {/* Info — clickable to edit */}
1600
- <button
1601
- type="button"
1602
- onClick={() => navigate(`/booking-links/${link.id}`)}
1607
+ <Link
1608
+ to={`/booking-links/${link.id}`}
1603
1609
  className="min-w-0 flex-1 text-left"
1604
1610
  >
1605
1611
  <div className="flex items-center gap-2">
@@ -1622,7 +1628,7 @@ export default function BookingLinksPage({
1622
1628
  {availability.timezone}
1623
1629
  </p>
1624
1630
  )}
1625
- </button>
1631
+ </Link>
1626
1632
 
1627
1633
  {/* Actions */}
1628
1634
  <div className="flex shrink-0 items-center gap-2">
@@ -1642,19 +1648,17 @@ export default function BookingLinksPage({
1642
1648
  <IconLink className="h-3.5 w-3.5" />
1643
1649
  {t("bookingLinks.copyLink")}
1644
1650
  </button>
1645
- <button
1646
- type="button"
1647
- onClick={(e) => {
1648
- e.stopPropagation();
1649
- openPreview(link.slug);
1650
- }}
1651
+ <a
1652
+ href={bookingPreviewPath(link.slug)}
1653
+ target="_blank"
1654
+ rel="noopener noreferrer"
1651
1655
  className={cn(
1652
1656
  "flex h-9 w-9 items-center justify-center rounded-full border",
1653
1657
  BRAND_PILL_LINK_CLASS,
1654
1658
  )}
1655
1659
  >
1656
1660
  <IconExternalLink className="h-4 w-4" />
1657
- </button>
1661
+ </a>
1658
1662
  </>
1659
1663
  )}
1660
1664
 
@@ -1669,12 +1673,10 @@ export default function BookingLinksPage({
1669
1673
  </button>
1670
1674
  </DropdownMenuTrigger>
1671
1675
  <DropdownMenuContent align="end">
1672
- <DropdownMenuItem
1673
- onClick={() =>
1674
- navigate(`/booking-links/${link.id}`)
1675
- }
1676
- >
1677
- {t("eventForm.edit")}
1676
+ <DropdownMenuItem asChild>
1677
+ <Link to={`/booking-links/${link.id}`}>
1678
+ {t("eventForm.edit")}
1679
+ </Link>
1678
1680
  </DropdownMenuItem>
1679
1681
  <DropdownMenuItem
1680
1682
  onClick={() => {
@@ -1949,7 +1951,7 @@ function BookingPreview({
1949
1951
  availability,
1950
1952
  bookingUrl,
1951
1953
  onCopy,
1952
- onOpen,
1954
+ openHref,
1953
1955
  onCollapse,
1954
1956
  }: {
1955
1957
  title: string;
@@ -1961,7 +1963,7 @@ function BookingPreview({
1961
1963
  availability?: AvailabilityConfig;
1962
1964
  bookingUrl?: string;
1963
1965
  onCopy?: () => void;
1964
- onOpen?: () => void;
1966
+ openHref?: string;
1965
1967
  onCollapse?: () => void;
1966
1968
  }) {
1967
1969
  const t = useT();
@@ -2138,19 +2140,20 @@ function BookingPreview({
2138
2140
  <TooltipContent>{t("bookingLinks.copyLink")}</TooltipContent>
2139
2141
  </Tooltip>
2140
2142
  )}
2141
- {onOpen && (
2143
+ {openHref && (
2142
2144
  <Tooltip>
2143
2145
  <TooltipTrigger asChild>
2144
- <button
2145
- type="button"
2146
- onClick={onOpen}
2146
+ <a
2147
+ href={openHref}
2148
+ target="_blank"
2149
+ rel="noopener noreferrer"
2147
2150
  className={cn(
2148
2151
  "flex h-6 w-6 items-center justify-center rounded",
2149
2152
  BRAND_ICON_LINK_CLASS,
2150
2153
  )}
2151
2154
  >
2152
2155
  <IconExternalLink className="h-3.5 w-3.5" />
2153
- </button>
2156
+ </a>
2154
2157
  </TooltipTrigger>
2155
2158
  <TooltipContent>
2156
2159
  {t("bookingLinks.openInteractiveBookingLink")}
@@ -9,7 +9,7 @@ import {
9
9
  import { useQuery, useMutation } from "@tanstack/react-query";
10
10
  import { format, parseISO } from "date-fns";
11
11
  import { useState } from "react";
12
- import { useParams, useNavigate } from "react-router";
12
+ import { Link, useParams, useNavigate } from "react-router";
13
13
  import { toast } from "sonner";
14
14
 
15
15
  import {
@@ -118,13 +118,11 @@ export function ManageBookingPage() {
118
118
  {t("manageBooking.cancelledSuffix")}
119
119
  </p>
120
120
  {booking.slug && (
121
- <Button
122
- variant="outline"
123
- className="mt-6 gap-2"
124
- onClick={() => navigate(`/book/${booking.slug}`)}
125
- >
126
- <IconCalendarPlus className="h-4 w-4" />
127
- {t("manageBooking.reschedule")}
121
+ <Button asChild variant="outline" className="mt-6 gap-2">
122
+ <Link to={`/book/${booking.slug}`}>
123
+ <IconCalendarPlus className="h-4 w-4" />
124
+ {t("manageBooking.reschedule")}
125
+ </Link>
128
126
  </Button>
129
127
  )}
130
128
  </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Booking links and previews now support opening in a new tab.
@@ -13,7 +13,7 @@ import {
13
13
  IconAlertTriangle,
14
14
  } from "@tabler/icons-react";
15
15
  import { useCallback, useMemo, useState } from "react";
16
- import { useNavigate } from "react-router";
16
+ import { Link } from "react-router";
17
17
 
18
18
  import { ViewedByPopover } from "@/components/sharing/viewed-by-popover";
19
19
  import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
@@ -85,7 +85,6 @@ export function RecordingCard({
85
85
  onTrash,
86
86
  readOnly = false,
87
87
  }: RecordingCardProps) {
88
- const navigate = useNavigate();
89
88
  const t = useT();
90
89
  const { formatDate, formatRelativeTime } = useFormatters();
91
90
  const [hovered, setHovered] = useState(false);
@@ -141,26 +140,23 @@ export function RecordingCard({
141
140
 
142
141
  const recordingPath = `/r/${recording.id}`;
143
142
 
144
- const handleOpen = useCallback(
145
- (event: React.MouseEvent<HTMLElement>) => {
146
- const shouldOpenNewTab =
147
- event.button === 1 ||
148
- (event.button === 0 && (event.metaKey || event.ctrlKey));
149
-
150
- if (shouldOpenNewTab) {
151
- event.preventDefault();
152
- event.stopPropagation();
153
- window.open(recordingPath, "_blank", "noopener,noreferrer");
143
+ const handleLinkClick = useCallback(
144
+ (event: React.MouseEvent<HTMLAnchorElement>) => {
145
+ if (
146
+ !selectionMode ||
147
+ event.button !== 0 ||
148
+ event.metaKey ||
149
+ event.ctrlKey ||
150
+ event.shiftKey ||
151
+ event.altKey
152
+ ) {
154
153
  return;
155
154
  }
156
155
 
157
- if (selectionMode) {
158
- onToggleSelect?.(recording.id);
159
- } else {
160
- navigate(recordingPath);
161
- }
156
+ event.preventDefault();
157
+ onToggleSelect?.(recording.id);
162
158
  },
163
- [navigate, onToggleSelect, recording.id, recordingPath, selectionMode],
159
+ [onToggleSelect, recording.id, selectionMode],
164
160
  );
165
161
 
166
162
  const handleCheckbox = useCallback(
@@ -182,8 +178,6 @@ export function RecordingCard({
182
178
  return (
183
179
  <div
184
180
  role="article"
185
- onClick={handleOpen}
186
- onAuxClick={handleOpen}
187
181
  onMouseEnter={() => setHovered(true)}
188
182
  onMouseLeave={() => setHovered(false)}
189
183
  className={cn(
@@ -193,8 +187,15 @@ export function RecordingCard({
193
187
  selected && "ring-2 ring-primary border-primary",
194
188
  )}
195
189
  >
190
+ <Link
191
+ to={recordingPath}
192
+ aria-label={displayTitle}
193
+ onClick={handleLinkClick}
194
+ className="absolute inset-0 z-0 rounded-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-inset"
195
+ />
196
+
196
197
  {/* Thumbnail */}
197
- <div className="relative aspect-video bg-muted overflow-hidden">
198
+ <div className="relative z-10 aspect-video overflow-hidden bg-muted pointer-events-none">
198
199
  {displayThumbnail ? (
199
200
  // eslint-disable-next-line jsx-a11y/alt-text
200
201
  <img
@@ -229,7 +230,7 @@ export function RecordingCard({
229
230
  {canSelect && (selectionMode || hovered || selected) && (
230
231
  <div
231
232
  onClick={handleCheckbox}
232
- className="absolute top-2 start-2 flex h-5 w-5 items-center justify-center rounded bg-background/90 border border-border"
233
+ className="pointer-events-auto absolute top-2 start-2 flex h-5 w-5 items-center justify-center rounded border border-border bg-background/90"
233
234
  >
234
235
  <Checkbox
235
236
  checked={selected}
@@ -285,7 +286,7 @@ export function RecordingCard({
285
286
  <button
286
287
  type="button"
287
288
  onClick={handleRemoveFailed}
288
- className="rounded border border-border px-1.5 py-0.5 text-[10px] text-foreground hover:bg-accent"
289
+ className="pointer-events-auto rounded border border-border px-1.5 py-0.5 text-[10px] text-foreground hover:bg-accent"
289
290
  >
290
291
  {t("clipsFinalRaw.remove")}
291
292
  </button>
@@ -296,7 +297,7 @@ export function RecordingCard({
296
297
  </div>
297
298
 
298
299
  {/* Body */}
299
- <div className="flex-1 p-3 space-y-2">
300
+ <div className="relative z-10 flex-1 space-y-2 p-3 pointer-events-none">
300
301
  <div className="flex items-start gap-2">
301
302
  <div className="min-w-0 flex-1">
302
303
  {hasDefaultTitle ? (
@@ -319,7 +320,7 @@ export function RecordingCard({
319
320
  {recording.viewCount > 0 && !readOnly ? (
320
321
  <ViewedByPopover
321
322
  recordingId={recording.id}
322
- className="underline-offset-2 hover:underline hover:text-foreground"
323
+ className="pointer-events-auto underline-offset-2 hover:underline hover:text-foreground"
323
324
  >
324
325
  {t("clipsFinalRaw.viewsCount", {
325
326
  count: recording.viewCount,
@@ -341,7 +342,7 @@ export function RecordingCard({
341
342
  <DropdownMenu>
342
343
  <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
343
344
  <button
344
- className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
345
+ className="pointer-events-auto rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
345
346
  aria-label={t("clipsFinalRaw.recordingMenu")}
346
347
  >
347
348
  <IconDots className="h-4 w-4" />
@@ -1,5 +1,5 @@
1
1
  import { IconUsersGroup, IconVideo } from "@tabler/icons-react";
2
- import { useNavigate } from "react-router";
2
+ import { Link } from "react-router";
3
3
 
4
4
  import { cn } from "@/lib/utils";
5
5
 
@@ -19,15 +19,13 @@ interface SpaceCardProps {
19
19
  }
20
20
 
21
21
  export function SpaceCard({ space, className }: SpaceCardProps) {
22
- const navigate = useNavigate();
23
22
  const color = space.color || "hsl(var(--primary))";
24
23
  const members = space.memberEmails ?? [];
25
24
  const initial = (space.name.trim().slice(0, 1) || "S").toUpperCase();
26
25
 
27
26
  return (
28
- <button
29
- type="button"
30
- onClick={() => navigate(`/spaces/${space.id}`)}
27
+ <Link
28
+ to={`/spaces/${space.id}`}
31
29
  className={cn(
32
30
  "group flex flex-col overflow-hidden rounded-lg border border-border bg-card text-start",
33
31
  "hover:border-primary/40",
@@ -92,6 +90,6 @@ export function SpaceCard({ space, className }: SpaceCardProps) {
92
90
  </div>
93
91
  )}
94
92
  </div>
95
- </button>
93
+ </Link>
96
94
  );
97
95
  }
@@ -58,13 +58,10 @@ export function SignInPromptDialog({
58
58
  <Button variant="ghost" onClick={() => onOpenChange(false)}>
59
59
  {t("signInPrompt.notNow")}
60
60
  </Button>
61
- <Button
62
- onClick={() => {
63
- onSignIn?.();
64
- window.location.href = buildSignInHref(returnTo);
65
- }}
66
- >
67
- {t("signInPrompt.signIn")}
61
+ <Button asChild>
62
+ <a href={buildSignInHref(returnTo)} onClick={() => onSignIn?.()}>
63
+ {t("signInPrompt.signIn")}
64
+ </a>
68
65
  </Button>
69
66
  </DialogFooter>
70
67
  </DialogContent>
@@ -35,7 +35,13 @@ import {
35
35
  } from "@tabler/icons-react";
36
36
  import { useQuery } from "@tanstack/react-query";
37
37
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
38
- import { useParams, useNavigate, NavLink, useSearchParams } from "react-router";
38
+ import {
39
+ Link,
40
+ useParams,
41
+ useNavigate,
42
+ NavLink,
43
+ useSearchParams,
44
+ } from "react-router";
39
45
  import { toast } from "sonner";
40
46
 
41
47
  import { EditableRecordingTitle } from "@/components/editable-recording-title";
@@ -712,8 +718,8 @@ export default function RecordingPage() {
712
718
  {(playerDataQ.error as Error | undefined)?.message ??
713
719
  t("recordingPage.noAccess")}
714
720
  </p>
715
- <Button onClick={() => navigate("/")} variant="outline">
716
- {t("recordingPage.backToLibrary")}
721
+ <Button asChild variant="outline">
722
+ <Link to="/">{t("recordingPage.backToLibrary")}</Link>
717
723
  </Button>
718
724
  </div>
719
725
  );
@@ -772,13 +778,15 @@ export default function RecordingPage() {
772
778
  <div className="flex min-h-screen w-full flex-col bg-background">
773
779
  <header className="flex min-w-0 shrink-0 items-center gap-3 border-b border-border px-3 py-2 sm:px-4 sm:py-3">
774
780
  <Button
781
+ asChild
775
782
  variant="ghost"
776
783
  size="icon"
777
784
  className="shrink-0"
778
- onClick={() => navigate("/")}
779
785
  aria-label={t("recordingPage.back")}
780
786
  >
781
- <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
787
+ <Link to="/">
788
+ <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
789
+ </Link>
782
790
  </Button>
783
791
  <div className="min-w-0 flex-1">
784
792
  <p className="truncate text-sm font-medium">{visibleTitle}</p>
@@ -947,8 +955,8 @@ export default function RecordingPage() {
947
955
  : t("recordingPage.retryUpload")
948
956
  : t("recordingPage.checkAgain")}
949
957
  </Button>
950
- <Button onClick={() => navigate("/")} variant="ghost" size="sm">
951
- {t("recordingPage.backToLibrary")}
958
+ <Button asChild variant="ghost" size="sm">
959
+ <Link to="/">{t("recordingPage.backToLibrary")}</Link>
952
960
  </Button>
953
961
  </div>
954
962
  </div>
@@ -1122,13 +1130,15 @@ export default function RecordingPage() {
1122
1130
  <div className="flex w-full min-w-0 flex-col xl:flex-1">
1123
1131
  <header className="flex min-w-0 shrink-0 items-center gap-2 border-b border-border px-3 py-2 sm:gap-3 sm:px-4 sm:py-3">
1124
1132
  <Button
1133
+ asChild
1125
1134
  variant="ghost"
1126
1135
  size="icon"
1127
1136
  className="shrink-0"
1128
- onClick={() => navigate("/")}
1129
1137
  aria-label={t("recordingPage.back")}
1130
1138
  >
1131
- <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
1139
+ <Link to="/">
1140
+ <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
1141
+ </Link>
1132
1142
  </Button>
1133
1143
  <div className="flex-1 min-w-0">
1134
1144
  <EditableRecordingTitle
@@ -32,7 +32,7 @@ import {
32
32
  type LoaderFunctionArgs,
33
33
  type MetaFunction,
34
34
  } from "react-router";
35
- import { useLoaderData, useNavigate, useParams } from "react-router";
35
+ import { Link, useLoaderData, useNavigate, useParams } from "react-router";
36
36
  import { toast } from "sonner";
37
37
 
38
38
  import { CaptureInstallButton } from "@/components/capture-install-options";
@@ -817,12 +817,14 @@ export default function ShareRoute() {
817
817
  <header className="flex min-w-0 shrink-0 flex-wrap items-center gap-2 border-b border-border px-3 py-2 sm:gap-3 sm:px-4 sm:py-3 lg:flex-nowrap">
818
818
  {session ? (
819
819
  <Button
820
+ asChild
820
821
  variant="ghost"
821
822
  size="icon"
822
- onClick={() => navigate("/")}
823
823
  aria-label={t("sharePage.backToHome")}
824
824
  >
825
- <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
825
+ <Link to="/">
826
+ <IconArrowLeft className="h-4 w-4 rtl:-scale-x-100" />
827
+ </Link>
826
828
  </Button>
827
829
  ) : null}
828
830
  <div className="min-w-0 flex-1">
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-13
4
+ ---
5
+
6
+ Recording cards and player back links now support opening in a new tab.
@@ -1,7 +1,7 @@
1
1
  import { useT } from "@agent-native/core/client";
2
2
  import { IconExternalLink, IconFileText } from "@tabler/icons-react";
3
3
  import { lazy, Suspense, useMemo } from "react";
4
- import { useNavigate } from "react-router";
4
+ import { Link } from "react-router";
5
5
 
6
6
  import { Button } from "@/components/ui/button";
7
7
  import { useDocument, useDocuments } from "@/hooks/use-documents";
@@ -66,7 +66,6 @@ export function ContentReferencePreview({
66
66
  referenceDepth?: number;
67
67
  }) {
68
68
  const t = useT();
69
- const navigate = useNavigate();
70
69
  const documentsQuery = useDocuments();
71
70
  const resolvedPath = useMemo(
72
71
  () => resolveContentReferencePath(sourcePath, currentPath),
@@ -113,16 +112,15 @@ export function ContentReferencePreview({
113
112
  </div>
114
113
  {document ? (
115
114
  <Button
116
- type="button"
115
+ asChild
117
116
  size="sm"
118
117
  variant="ghost"
119
118
  className="h-8 gap-1.5 px-2 text-xs"
120
- onClick={() =>
121
- navigate(`/page/${document.id}`, { flushSync: true })
122
- }
123
119
  >
124
- <IconExternalLink className="size-3.5" />
125
- {t("editor.reference.open")}
120
+ <Link to={`/page/${document.id}`}>
121
+ <IconExternalLink className="size-3.5" />
122
+ {t("editor.reference.open")}
123
+ </Link>
126
124
  </Button>
127
125
  ) : null}
128
126
  </div>
@@ -139,7 +139,7 @@ import {
139
139
  type PointerEvent as ReactPointerEvent,
140
140
  type ReactNode,
141
141
  } from "react";
142
- import { useLocation, useNavigate } from "react-router";
142
+ import { Link, useLocation, useNavigate } from "react-router";
143
143
  import { toast } from "sonner";
144
144
 
145
145
  import {
@@ -1670,14 +1670,16 @@ function DatabaseTable({
1670
1670
  <Tooltip>
1671
1671
  <TooltipTrigger asChild>
1672
1672
  <Button
1673
+ asChild
1673
1674
  type="button"
1674
1675
  variant="ghost"
1675
1676
  size="sm"
1676
1677
  aria-label={dbText("openAsFullPage")}
1677
1678
  className={databaseToolbarIconButtonClass()}
1678
- onClick={() => navigate(`/page/${databaseDocumentId}`)}
1679
1679
  >
1680
- <IconArrowsDiagonal className="size-3.5" />
1680
+ <Link to={`/page/${databaseDocumentId}`}>
1681
+ <IconArrowsDiagonal className="size-3.5" />
1682
+ </Link>
1681
1683
  </Button>
1682
1684
  </TooltipTrigger>
1683
1685
  <TooltipContent>{dbText("openAsFullPage")}</TooltipContent>
@@ -12,7 +12,7 @@ import {
12
12
  IconExternalLink,
13
13
  } from "@tabler/icons-react";
14
14
  import { useMemo, useState } from "react";
15
- import { useNavigate } from "react-router";
15
+ import { Link, useNavigate } from "react-router";
16
16
 
17
17
  import { Button } from "@/components/ui/button";
18
18
  import {
@@ -362,14 +362,11 @@ export function DatabaseFormView({
362
362
  {t("database.formSubmittedDescription")}
363
363
  </div>
364
364
  </div>
365
- <Button
366
- type="button"
367
- variant="outline"
368
- size="sm"
369
- onClick={() => navigate(`/page/${createdDocumentId}`)}
370
- >
371
- <IconExternalLink className="size-4" />
372
- {t("database.openPage")}
365
+ <Button asChild type="button" variant="outline" size="sm">
366
+ <Link to={`/page/${createdDocumentId}`}>
367
+ <IconExternalLink className="size-4" />
368
+ {t("database.openPage")}
369
+ </Link>
373
370
  </Button>
374
371
  </div>
375
372
  ) : null}