@agent-native/core 0.77.6 → 0.77.7

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 (159) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +3 -3
  4. package/corpus/core/src/agent/thread-data-builder.ts +21 -2
  5. package/corpus/core/src/cli/create.ts +35 -1
  6. package/corpus/core/src/cli/skills.ts +8 -0
  7. package/corpus/core/src/client/AgentPanel.tsx +57 -25
  8. package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -1
  9. package/corpus/core/src/client/analytics.ts +24 -2
  10. package/corpus/core/src/client/use-agent-engine-configured.ts +29 -15
  11. package/corpus/core/src/client/use-chat-threads.ts +104 -55
  12. package/corpus/core/src/server/analytics.ts +27 -3
  13. package/corpus/core/src/sharing/access.ts +35 -13
  14. package/corpus/core/src/sharing/actions/share-resource.ts +32 -9
  15. package/corpus/core/src/sharing/actions/unshare-resource.ts +25 -2
  16. package/corpus/core/src/sharing/registry.ts +9 -0
  17. package/corpus/core/src/vite/client.ts +3 -0
  18. package/corpus/templates/analytics/actions/data-source-status.ts +25 -14
  19. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +2 -2
  20. package/corpus/templates/analytics/app/lib/data-source-status.ts +47 -6
  21. package/corpus/templates/analytics/app/lib/data-sources.ts +14 -3
  22. package/corpus/templates/analytics/app/pages/DataSources.tsx +24 -5
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +15 -43
  24. package/corpus/templates/analytics/changelog/2026-06-25-fixed-hubspot-data-source-status-so-private-app-tokens-legac.md +6 -0
  25. package/corpus/templates/analytics/netlify.toml +1 -0
  26. package/corpus/templates/analytics/server/lib/credential-keys.ts +3 -1
  27. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +12 -3
  28. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +5 -2
  29. package/corpus/templates/assets/changelog/2026-06-25-create-now-returns-from-agent-settings-to-the-asset-chat-eve.md +6 -0
  30. package/corpus/templates/assets/netlify.toml +1 -0
  31. package/corpus/templates/brain/netlify.toml +1 -0
  32. package/corpus/templates/calendar/app/i18n-data.ts +12 -0
  33. package/corpus/templates/calendar/app/lib/clipboard.ts +58 -0
  34. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +15 -9
  35. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +6 -2
  36. package/corpus/templates/calendar/changelog/2026-06-25-booking-link-copy-buttons-no-longer-fail-when-clipboard-perm.md +6 -0
  37. package/corpus/templates/calendar/netlify.toml +1 -0
  38. package/corpus/templates/chat/netlify.toml +1 -0
  39. package/corpus/templates/clips/AGENTS.md +9 -7
  40. package/corpus/templates/clips/actions/add-vocabulary-term.ts +2 -2
  41. package/corpus/templates/clips/actions/archive-recording.ts +5 -2
  42. package/corpus/templates/clips/actions/clear-edits.ts +5 -2
  43. package/corpus/templates/clips/actions/create-folder.ts +7 -2
  44. package/corpus/templates/clips/actions/delete-folder.ts +7 -4
  45. package/corpus/templates/clips/actions/delete-recording-permanent.ts +5 -2
  46. package/corpus/templates/clips/actions/finalize-recording.ts +5 -2
  47. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -1
  48. package/corpus/templates/clips/actions/list-notifications.ts +9 -5
  49. package/corpus/templates/clips/actions/list-organization-state.ts +2 -1
  50. package/corpus/templates/clips/actions/list-recordings.ts +4 -1
  51. package/corpus/templates/clips/actions/move-recording.ts +5 -1
  52. package/corpus/templates/clips/actions/rename-folder.ts +6 -3
  53. package/corpus/templates/clips/actions/request-transcript.ts +5 -2
  54. package/corpus/templates/clips/actions/restore-recording.ts +5 -2
  55. package/corpus/templates/clips/actions/save-browser-diagnostics.ts +21 -8
  56. package/corpus/templates/clips/actions/set-chapters.ts +5 -2
  57. package/corpus/templates/clips/actions/set-thumbnail.ts +5 -2
  58. package/corpus/templates/clips/actions/split-recording.ts +5 -2
  59. package/corpus/templates/clips/actions/stitch-recordings.ts +6 -2
  60. package/corpus/templates/clips/actions/trash-recording.ts +5 -2
  61. package/corpus/templates/clips/actions/undo-edit.ts +5 -2
  62. package/corpus/templates/clips/actions/view-screen.ts +2 -1
  63. package/corpus/templates/clips/app/components/library/recording-card.tsx +24 -7
  64. package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +5 -1
  65. package/corpus/templates/clips/changelog/2026-06-25-chrome-extension-recordings-now-upload-large-capture-bursts.md +6 -0
  66. package/corpus/templates/clips/changelog/2026-06-25-console-diagnostics-keep-more-useful-non-secret-details-when.md +6 -0
  67. package/corpus/templates/clips/changelog/2026-06-25-fixed-owner-recognition-so-clips-created-from-desktop-and-we.md +6 -0
  68. package/corpus/templates/clips/chrome-extension/package.json +3 -0
  69. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  70. package/corpus/templates/clips/chrome-extension/src/background.ts +85 -9
  71. package/corpus/templates/clips/chrome-extension/src/content-script.ts +55 -0
  72. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +79 -3
  73. package/corpus/templates/clips/chrome-extension/src/options.ts +10 -0
  74. package/corpus/templates/clips/chrome-extension/src/overlay.ts +6 -0
  75. package/corpus/templates/clips/chrome-extension/src/permission.ts +8 -1
  76. package/corpus/templates/clips/chrome-extension/src/popup.ts +65 -39
  77. package/corpus/templates/clips/chrome-extension/src/sentry.ts +206 -0
  78. package/corpus/templates/clips/chrome-extension/vite.config.ts +47 -0
  79. package/corpus/templates/clips/netlify.toml +1 -0
  80. package/corpus/templates/clips/server/db/index.ts +1 -0
  81. package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +35 -23
  82. package/corpus/templates/clips/server/lib/public-agent-context.ts +8 -1
  83. package/corpus/templates/clips/server/lib/recordings.ts +18 -2
  84. package/corpus/templates/clips/server/lib/slack-oauth.ts +9 -6
  85. package/corpus/templates/clips/server/plugins/onboarding-calendar.ts +6 -2
  86. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +2 -2
  87. package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +5 -2
  88. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -2
  89. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +5 -2
  90. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +5 -2
  91. package/corpus/templates/clips/shared/browser-diagnostics.ts +14 -4
  92. package/corpus/templates/content/netlify.toml +1 -0
  93. package/corpus/templates/design/netlify.toml +1 -0
  94. package/corpus/templates/dispatch/netlify.toml +1 -0
  95. package/corpus/templates/forms/netlify.toml +1 -0
  96. package/corpus/templates/macros/netlify.toml +1 -0
  97. package/corpus/templates/mail/app/components/email/EmailList.tsx +7 -3
  98. package/corpus/templates/mail/app/components/email/EmailThread.tsx +4 -1
  99. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +10 -2
  100. package/corpus/templates/mail/app/hooks/use-compose-state.ts +15 -5
  101. package/corpus/templates/mail/app/hooks/use-google-auth.ts +47 -9
  102. package/corpus/templates/mail/changelog/2026-06-25-account-avatars-stay-stable-while-mail-refreshes-google-acco.md +6 -0
  103. package/corpus/templates/mail/changelog/2026-06-25-mail-background-draft-saves-and-thread-prefetches-no-longer.md +6 -0
  104. package/corpus/templates/mail/netlify.toml +1 -0
  105. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +8 -0
  106. package/corpus/templates/plan/netlify.toml +1 -0
  107. package/corpus/templates/slides/netlify.toml +1 -0
  108. package/corpus/templates/videos/netlify.toml +1 -0
  109. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  110. package/dist/agent/thread-data-builder.js +23 -2
  111. package/dist/agent/thread-data-builder.js.map +1 -1
  112. package/dist/cli/create.d.ts.map +1 -1
  113. package/dist/cli/create.js +28 -1
  114. package/dist/cli/create.js.map +1 -1
  115. package/dist/cli/skills.d.ts +1 -1
  116. package/dist/cli/skills.d.ts.map +1 -1
  117. package/dist/cli/skills.js +8 -0
  118. package/dist/cli/skills.js.map +1 -1
  119. package/dist/client/AgentPanel.d.ts +8 -2
  120. package/dist/client/AgentPanel.d.ts.map +1 -1
  121. package/dist/client/AgentPanel.js +29 -15
  122. package/dist/client/AgentPanel.js.map +1 -1
  123. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  124. package/dist/client/MultiTabAssistantChat.js +4 -4
  125. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  126. package/dist/client/analytics.d.ts.map +1 -1
  127. package/dist/client/analytics.js +22 -2
  128. package/dist/client/analytics.js.map +1 -1
  129. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  130. package/dist/client/use-agent-engine-configured.js +23 -13
  131. package/dist/client/use-agent-engine-configured.js.map +1 -1
  132. package/dist/client/use-chat-threads.d.ts +4 -0
  133. package/dist/client/use-chat-threads.d.ts.map +1 -1
  134. package/dist/client/use-chat-threads.js +49 -4
  135. package/dist/client/use-chat-threads.js.map +1 -1
  136. package/dist/collab/routes.d.ts +2 -2
  137. package/dist/notifications/routes.d.ts +1 -1
  138. package/dist/observability/routes.d.ts +5 -5
  139. package/dist/resources/handlers.d.ts +2 -2
  140. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  141. package/dist/server/analytics.d.ts +4 -0
  142. package/dist/server/analytics.d.ts.map +1 -1
  143. package/dist/server/analytics.js +19 -3
  144. package/dist/server/analytics.js.map +1 -1
  145. package/dist/server/transcribe-voice.d.ts +1 -1
  146. package/dist/sharing/access.d.ts.map +1 -1
  147. package/dist/sharing/access.js +26 -12
  148. package/dist/sharing/access.js.map +1 -1
  149. package/dist/sharing/actions/share-resource.js +20 -9
  150. package/dist/sharing/actions/share-resource.js.map +1 -1
  151. package/dist/sharing/actions/unshare-resource.js +13 -2
  152. package/dist/sharing/actions/unshare-resource.js.map +1 -1
  153. package/dist/sharing/registry.d.ts +9 -0
  154. package/dist/sharing/registry.d.ts.map +1 -1
  155. package/dist/sharing/registry.js.map +1 -1
  156. package/dist/vite/client.d.ts.map +1 -1
  157. package/dist/vite/client.js +1 -0
  158. package/dist/vite/client.js.map +1 -1
  159. package/package.json +3 -3
@@ -5,5 +5,6 @@ publish = "templates/chat/dist"
5
5
  functions = "templates/chat/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
+ GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
8
9
  NITRO_PRESET = "netlify"
9
10
  NPM_CONFIG_PRODUCTION = "false"
@@ -64,13 +64,15 @@ Detailed media, meeting, dictation, editing, and sharing rules live in
64
64
  - Browser recordings can include redacted browser diagnostics captured during
65
65
  the recording session. `save-browser-diagnostics` is UI/internal and stores
66
66
  bounded console logs plus fetch/XHR method, URL path/query keys, status, and
67
- duration; it never captures headers, bodies, cookies, or query values. Use
68
- `get-recording-player-data` for full diagnostics when you have editor access.
69
- Public agent context exposes the redacted console stream (all levels) as
70
- `browserDiagnostics.consoleLogs` and the fetch/XHR stream as
71
- `browserDiagnostics.networkRequests` (method, sanitized URL with query values
72
- redacted, status, duration), plus `consoleIssues` and `failedNetworkRequests`
73
- highlights. All bounded; page URL, headers, bodies, and cookies stay omitted.
67
+ duration; it never captures headers, bodies, cookies, or network URL query
68
+ values. Console text keeps useful non-secret values while redacting
69
+ credential-looking keys/headers. Use `get-recording-player-data` for full
70
+ diagnostics when you have editor access. Public agent context exposes the
71
+ redacted console stream (all levels) as `browserDiagnostics.consoleLogs` and
72
+ the fetch/XHR stream as `browserDiagnostics.networkRequests` (method,
73
+ sanitized URL with query values redacted, status, duration), plus
74
+ `consoleIssues` and `failedNetworkRequests` highlights. All bounded; page
75
+ URL, headers, bodies, and cookies stay omitted.
74
76
  - The Chrome extension lives in `chrome-extension/`. It launches `/record` with
75
77
  `clipsExtensionId` and `clipsCaptureSessionId`, then the recorder sends
76
78
  `CLIPS_CAPTURE_START/STOP/CANCEL` back to the extension. The extension uses
@@ -21,7 +21,7 @@ import { createError } from "h3";
21
21
  import { z } from "zod";
22
22
 
23
23
  import { getDb, schema } from "../server/db/index.js";
24
- import { nanoid } from "../server/lib/recordings.js";
24
+ import { nanoid, ownerEmailMatches } from "../server/lib/recordings.js";
25
25
 
26
26
  export default defineAction({
27
27
  description:
@@ -66,7 +66,7 @@ export default defineAction({
66
66
  .from(schema.vocabulary)
67
67
  .where(
68
68
  and(
69
- eq(schema.vocabulary.ownerEmail, ownerEmail),
69
+ ownerEmailMatches(schema.vocabulary.ownerEmail, ownerEmail),
70
70
  eq(schema.vocabulary.term, term),
71
71
  ),
72
72
  )
@@ -11,7 +11,10 @@ import { and, eq } from "drizzle-orm";
11
11
  import { z } from "zod";
12
12
 
13
13
  import { getDb, schema } from "../server/db/index.js";
14
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
14
+ import {
15
+ getCurrentOwnerEmail,
16
+ ownerEmailMatches,
17
+ } from "../server/lib/recordings.js";
15
18
 
16
19
  export default defineAction({
17
20
  description:
@@ -29,7 +32,7 @@ export default defineAction({
29
32
  .where(
30
33
  and(
31
34
  eq(schema.recordings.id, args.id),
32
- eq(schema.recordings.ownerEmail, ownerEmail),
35
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
33
36
  ),
34
37
  );
35
38
  if (!existing) throw new Error(`Recording not found: ${args.id}`);
@@ -17,7 +17,10 @@ import { z } from "zod";
17
17
 
18
18
  import { DEFAULT_EDITS, serializeEdits } from "../app/lib/timestamp-mapping.js";
19
19
  import { getDb, schema } from "../server/db/index.js";
20
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
20
+ import {
21
+ getCurrentOwnerEmail,
22
+ ownerEmailMatches,
23
+ } from "../server/lib/recordings.js";
21
24
  import { assertNativeRecordingMedia } from "./lib/native-media.js";
22
25
 
23
26
  export default defineAction({
@@ -36,7 +39,7 @@ export default defineAction({
36
39
  .where(
37
40
  and(
38
41
  eq(schema.recordings.id, args.recordingId),
39
- eq(schema.recordings.ownerEmail, ownerEmail),
42
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
40
43
  ),
41
44
  );
42
45
  if (!existing) {
@@ -7,6 +7,7 @@ import { getDb, schema } from "../server/db/index.js";
7
7
  import {
8
8
  getCurrentOwnerEmail,
9
9
  nanoid,
10
+ ownerEmailMatches,
10
11
  requireOrganizationAccess,
11
12
  } from "../server/lib/recordings.js";
12
13
 
@@ -64,7 +65,9 @@ export default defineAction({
64
65
  : isNull(schema.folders.spaceId),
65
66
  ];
66
67
  if (!args.spaceId) {
67
- parentWhereClauses.push(eq(schema.folders.ownerEmail, ownerEmail));
68
+ parentWhereClauses.push(
69
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
70
+ );
68
71
  }
69
72
  const [parent] = await db
70
73
  .select({ id: schema.folders.id, spaceId: schema.folders.spaceId })
@@ -77,7 +80,9 @@ export default defineAction({
77
80
  // Next position within siblings
78
81
  const whereClauses = [eq(schema.folders.organizationId, organizationId)];
79
82
  if (!args.spaceId) {
80
- whereClauses.push(eq(schema.folders.ownerEmail, ownerEmail));
83
+ whereClauses.push(
84
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
85
+ );
81
86
  }
82
87
  whereClauses.push(
83
88
  args.spaceId
@@ -4,7 +4,10 @@ import { and, eq, inArray } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
7
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
7
+ import {
8
+ getCurrentOwnerEmail,
9
+ ownerEmailMatches,
10
+ } from "../server/lib/recordings.js";
8
11
 
9
12
  export default defineAction({
10
13
  description:
@@ -23,7 +26,7 @@ export default defineAction({
23
26
  .where(
24
27
  and(
25
28
  eq(schema.folders.id, args.id),
26
- eq(schema.folders.ownerEmail, ownerEmail),
29
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
27
30
  ),
28
31
  );
29
32
 
@@ -41,7 +44,7 @@ export default defineAction({
41
44
  .where(
42
45
  and(
43
46
  inArray(schema.folders.parentId, frontier),
44
- eq(schema.folders.ownerEmail, ownerEmail),
47
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
45
48
  ),
46
49
  );
47
50
  const nextIds = children.map((c) => c.id);
@@ -63,7 +66,7 @@ export default defineAction({
63
66
  .where(
64
67
  and(
65
68
  inArray(schema.folders.id, descendants),
66
- eq(schema.folders.ownerEmail, ownerEmail),
69
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
67
70
  ),
68
71
  );
69
72
 
@@ -15,7 +15,10 @@ import { and, eq } from "drizzle-orm";
15
15
  import { z } from "zod";
16
16
 
17
17
  import { getDb, schema } from "../server/db/index.js";
18
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
18
+ import {
19
+ getCurrentOwnerEmail,
20
+ ownerEmailMatches,
21
+ } from "../server/lib/recordings.js";
19
22
 
20
23
  export default defineAction({
21
24
  description:
@@ -33,7 +36,7 @@ export default defineAction({
33
36
  .where(
34
37
  and(
35
38
  eq(schema.recordings.id, args.id),
36
- eq(schema.recordings.ownerEmail, ownerEmail),
39
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
37
40
  ),
38
41
  );
39
42
  if (!existing) throw new Error(`Recording not found: ${args.id}`);
@@ -28,7 +28,10 @@ import {
28
28
  applyFaststart,
29
29
  hasPlayableMp4Metadata,
30
30
  } from "../server/lib/faststart.js";
31
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
31
+ import {
32
+ getCurrentOwnerEmail,
33
+ ownerEmailMatches,
34
+ } from "../server/lib/recordings.js";
32
35
  import {
33
36
  requiresConfiguredVideoStorage,
34
37
  STORAGE_SETUP_REQUIRED_REASON,
@@ -151,7 +154,7 @@ export default defineAction({
151
154
  .where(
152
155
  and(
153
156
  eq(schema.recordings.id, id),
154
- eq(schema.recordings.ownerEmail, ownerEmail),
157
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
155
158
  ),
156
159
  );
157
160
 
@@ -11,6 +11,7 @@ import { getDb, schema } from "../server/db/index.js";
11
11
  import {
12
12
  getCurrentOwnerEmail,
13
13
  nanoid,
14
+ ownerEmailMatches,
14
15
  parseSpaceIds,
15
16
  requireOrganizationAccess,
16
17
  stringifySpaceIds,
@@ -148,7 +149,7 @@ export default defineAction({
148
149
  .where(
149
150
  and(
150
151
  eq(schema.recordings.id, args.recordingId),
151
- eq(schema.recordings.ownerEmail, ownerEmail),
152
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
152
153
  ),
153
154
  );
154
155
  if (!existingRecording) {
@@ -11,11 +11,15 @@
11
11
  */
12
12
 
13
13
  import { defineAction } from "@agent-native/core";
14
- import { and, desc, eq, gte, inArray } from "drizzle-orm";
14
+ import { and, desc, gte, inArray } from "drizzle-orm";
15
15
  import { z } from "zod";
16
16
 
17
17
  import { getDb, schema } from "../server/db/index.js";
18
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
18
+ import {
19
+ getCurrentOwnerEmail,
20
+ ownerEmailMatches,
21
+ sameOwnerEmail,
22
+ } from "../server/lib/recordings.js";
19
23
 
20
24
  export default defineAction({
21
25
  description:
@@ -36,7 +40,7 @@ export default defineAction({
36
40
  title: schema.recordings.title,
37
41
  })
38
42
  .from(schema.recordings)
39
- .where(eq(schema.recordings.ownerEmail, me));
43
+ .where(ownerEmailMatches(schema.recordings.ownerEmail, me));
40
44
  if (myRecordings.length === 0) {
41
45
  return { items: [], count: 0 };
42
46
  }
@@ -82,7 +86,7 @@ export default defineAction({
82
86
 
83
87
  const items = [
84
88
  ...comments
85
- .filter((c) => c.authorEmail !== me)
89
+ .filter((c) => !sameOwnerEmail(c.authorEmail, me))
86
90
  .map((c) => ({
87
91
  id: `c:${c.id}`,
88
92
  kind: (mentionRegex.test(c.content) ? "mention" : "comment") as
@@ -95,7 +99,7 @@ export default defineAction({
95
99
  createdAt: c.createdAt,
96
100
  })),
97
101
  ...reactions
98
- .filter((r) => r.viewerEmail !== me)
102
+ .filter((r) => !sameOwnerEmail(r.viewerEmail, me))
99
103
  .map((r) => ({
100
104
  id: `r:${r.id}`,
101
105
  kind: "reaction" as const,
@@ -20,6 +20,7 @@ import { z } from "zod";
20
20
  import { getDb, schema } from "../server/db/index.js";
21
21
  import {
22
22
  getCurrentOwnerEmail,
23
+ ownerEmailMatches,
23
24
  requireOrganizationAccess,
24
25
  } from "../server/lib/recordings.js";
25
26
 
@@ -128,7 +129,7 @@ export default defineAction({
128
129
  eq(schema.folders.organizationId, organizationId),
129
130
  or(
130
131
  isNotNull(schema.folders.spaceId),
131
- eq(schema.folders.ownerEmail, ownerEmail),
132
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
132
133
  ),
133
134
  ),
134
135
  )
@@ -17,6 +17,7 @@ import { z } from "zod";
17
17
  import { getDb, schema } from "../server/db/index.js";
18
18
  import {
19
19
  getActiveOrganizationId,
20
+ ownerEmailMatches,
20
21
  parseSpaceIds,
21
22
  } from "../server/lib/recordings.js";
22
23
 
@@ -82,7 +83,9 @@ export default defineAction({
82
83
  if (args.view === "library") {
83
84
  const email = getRequestUserEmail();
84
85
  if (email) {
85
- whereClauses.push(eq(schema.recordings.ownerEmail, email));
86
+ whereClauses.push(
87
+ ownerEmailMatches(schema.recordings.ownerEmail, email),
88
+ );
86
89
  }
87
90
  if (orgId) {
88
91
  whereClauses.push(eq(schema.recordings.organizationId, orgId));
@@ -9,6 +9,7 @@ import {
9
9
  getCurrentOwnerEmail,
10
10
  parseSpaceIds,
11
11
  requireActiveOrganizationId,
12
+ sameOwnerEmail,
12
13
  } from "../server/lib/recordings.js";
13
14
 
14
15
  const moveRecordingSchema = z
@@ -69,7 +70,10 @@ export default defineAction({
69
70
  )
70
71
  .limit(1);
71
72
 
72
- if (!folder || (!folder.spaceId && folder.ownerEmail !== ownerEmail)) {
73
+ if (
74
+ !folder ||
75
+ (!folder.spaceId && !sameOwnerEmail(folder.ownerEmail, ownerEmail))
76
+ ) {
73
77
  throw new Error(`Folder not found: ${folderId}`);
74
78
  }
75
79
 
@@ -4,7 +4,10 @@ import { and, eq } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
7
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
7
+ import {
8
+ getCurrentOwnerEmail,
9
+ ownerEmailMatches,
10
+ } from "../server/lib/recordings.js";
8
11
 
9
12
  export default defineAction({
10
13
  description: "Rename a folder.",
@@ -22,7 +25,7 @@ export default defineAction({
22
25
  .where(
23
26
  and(
24
27
  eq(schema.folders.id, args.id),
25
- eq(schema.folders.ownerEmail, ownerEmail),
28
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
26
29
  ),
27
30
  );
28
31
 
@@ -36,7 +39,7 @@ export default defineAction({
36
39
  .where(
37
40
  and(
38
41
  eq(schema.folders.id, args.id),
39
- eq(schema.folders.ownerEmail, ownerEmail),
42
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
40
43
  ),
41
44
  );
42
45
 
@@ -50,7 +50,10 @@ import { and, eq } from "drizzle-orm";
50
50
  import { z } from "zod";
51
51
 
52
52
  import { getDb, schema } from "../server/db/index.js";
53
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
53
+ import {
54
+ getCurrentOwnerEmail,
55
+ ownerEmailMatches,
56
+ } from "../server/lib/recordings.js";
54
57
  import { normalizeLoomShareUrl } from "../shared/loom.js";
55
58
  import {
56
59
  buildCaptionSegmentsFromText,
@@ -574,7 +577,7 @@ async function completeReadyTranscript({
574
577
  .where(
575
578
  and(
576
579
  eq(schema.recordings.id, recordingId),
577
- eq(schema.recordings.ownerEmail, ownerEmail),
580
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
578
581
  ),
579
582
  )
580
583
  .limit(1);
@@ -11,7 +11,10 @@ import { and, eq } from "drizzle-orm";
11
11
  import { z } from "zod";
12
12
 
13
13
  import { getDb, schema } from "../server/db/index.js";
14
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
14
+ import {
15
+ getCurrentOwnerEmail,
16
+ ownerEmailMatches,
17
+ } from "../server/lib/recordings.js";
15
18
 
16
19
  export default defineAction({
17
20
  description: "Restore a recording from archive or trash back to the library.",
@@ -28,7 +31,7 @@ export default defineAction({
28
31
  .where(
29
32
  and(
30
33
  eq(schema.recordings.id, args.id),
31
- eq(schema.recordings.ownerEmail, ownerEmail),
34
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
32
35
  ),
33
36
  );
34
37
  if (!existing) throw new Error(`Recording not found: ${args.id}`);
@@ -24,7 +24,7 @@ import {
24
24
  type BrowserDiagnosticConsoleLevel,
25
25
  } from "../shared/browser-diagnostics.js";
26
26
 
27
- const REDACTION_VERSION = 1;
27
+ const REDACTION_VERSION = 2;
28
28
 
29
29
  const consoleLevelSchema = z.enum(["debug", "log", "info", "warn", "error"]);
30
30
 
@@ -53,12 +53,19 @@ function truncate(value: string, maxLength: number): string {
53
53
  return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
54
54
  }
55
55
 
56
- function redactString(value: string): string {
57
- return redactBrowserDiagnosticString(value);
56
+ function redactString(
57
+ value: string,
58
+ options?: { redactQueryValues?: boolean },
59
+ ): string {
60
+ return redactBrowserDiagnosticString(value, options);
61
+ }
62
+
63
+ function redactUrlString(value: string): string {
64
+ return redactBrowserDiagnosticString(value, { redactQueryValues: true });
58
65
  }
59
66
 
60
67
  function sanitizeUrl(raw: string): string {
61
- const redacted = redactString(raw);
68
+ const redacted = redactUrlString(raw);
62
69
  try {
63
70
  const parsed = new URL(redacted, "https://clips.local");
64
71
  parsed.username = "";
@@ -93,14 +100,17 @@ function sanitizeConsoleLog(entry: z.infer<typeof consoleLogSchema>): {
93
100
  stack?: string;
94
101
  } {
95
102
  const stack = entry.stack
96
- ? truncate(redactString(entry.stack), MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH)
103
+ ? truncate(
104
+ redactString(entry.stack, { redactQueryValues: true }),
105
+ MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH,
106
+ )
97
107
  : "";
98
108
  return {
99
109
  timestampMs: Math.round(entry.timestampMs),
100
110
  elapsedMs: Math.round(entry.elapsedMs),
101
111
  level: entry.level,
102
112
  message: truncate(
103
- redactString(entry.message),
113
+ redactString(entry.message, { redactQueryValues: true }),
104
114
  MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH,
105
115
  ),
106
116
  ...(stack ? { stack } : {}),
@@ -109,10 +119,13 @@ function sanitizeConsoleLog(entry: z.infer<typeof consoleLogSchema>): {
109
119
 
110
120
  function sanitizeNetworkRequest(entry: z.infer<typeof networkRequestSchema>) {
111
121
  const statusText = entry.statusText
112
- ? truncate(redactString(entry.statusText), 120)
122
+ ? truncate(redactString(entry.statusText, { redactQueryValues: true }), 120)
113
123
  : "";
114
124
  const error = entry.error
115
- ? truncate(redactString(entry.error), MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH)
125
+ ? truncate(
126
+ redactString(entry.error, { redactQueryValues: true }),
127
+ MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH,
128
+ )
116
129
  : "";
117
130
  return {
118
131
  timestampMs: Math.round(entry.timestampMs),
@@ -15,7 +15,10 @@ import { and, eq } from "drizzle-orm";
15
15
  import { z } from "zod";
16
16
 
17
17
  import { getDb, schema } from "../server/db/index.js";
18
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
18
+ import {
19
+ getCurrentOwnerEmail,
20
+ ownerEmailMatches,
21
+ } from "../server/lib/recordings.js";
19
22
 
20
23
  const ChapterSchema = z.object({
21
24
  startMs: z.coerce.number().int().min(0),
@@ -61,7 +64,7 @@ export default defineAction({
61
64
  .where(
62
65
  and(
63
66
  eq(schema.recordings.id, args.recordingId),
64
- eq(schema.recordings.ownerEmail, ownerEmail),
67
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
65
68
  ),
66
69
  );
67
70
  if (!existing) {
@@ -26,7 +26,10 @@ import { z } from "zod";
26
26
 
27
27
  import { parseEdits, serializeEdits } from "../app/lib/timestamp-mapping.js";
28
28
  import { getDb, schema } from "../server/db/index.js";
29
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
29
+ import {
30
+ getCurrentOwnerEmail,
31
+ ownerEmailMatches,
32
+ } from "../server/lib/recordings.js";
30
33
  import { assertNativeRecordingMedia } from "./lib/native-media.js";
31
34
 
32
35
  function decodeDataUrl(dataUrl: string): { bytes: Uint8Array; mime: string } {
@@ -88,7 +91,7 @@ export default defineAction({
88
91
  .where(
89
92
  and(
90
93
  eq(schema.recordings.id, args.recordingId),
91
- eq(schema.recordings.ownerEmail, ownerEmail),
94
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
92
95
  ),
93
96
  );
94
97
  if (!existing) {
@@ -20,7 +20,10 @@ import {
20
20
  serializeEdits,
21
21
  } from "../app/lib/timestamp-mapping.js";
22
22
  import { getDb, schema } from "../server/db/index.js";
23
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
23
+ import {
24
+ getCurrentOwnerEmail,
25
+ ownerEmailMatches,
26
+ } from "../server/lib/recordings.js";
24
27
  import { assertNativeRecordingMedia } from "./lib/native-media.js";
25
28
 
26
29
  export default defineAction({
@@ -44,7 +47,7 @@ export default defineAction({
44
47
  .where(
45
48
  and(
46
49
  eq(schema.recordings.id, args.recordingId),
47
- eq(schema.recordings.ownerEmail, ownerEmail),
50
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
48
51
  ),
49
52
  );
50
53
  if (!existing) {
@@ -34,7 +34,11 @@ import { z } from "zod";
34
34
 
35
35
  import { parseEdits, serializeEdits } from "../app/lib/timestamp-mapping.js";
36
36
  import { getDb, schema } from "../server/db/index.js";
37
- import { getCurrentOwnerEmail, nanoid } from "../server/lib/recordings.js";
37
+ import {
38
+ getCurrentOwnerEmail,
39
+ nanoid,
40
+ ownerEmailMatches,
41
+ } from "../server/lib/recordings.js";
38
42
  import { assertNativeRecordingMedia } from "./lib/native-media.js";
39
43
 
40
44
  export default defineAction({
@@ -95,7 +99,7 @@ export default defineAction({
95
99
  .where(
96
100
  and(
97
101
  inArray(schema.recordings.id, ids),
98
- eq(schema.recordings.ownerEmail, ownerEmail),
102
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
99
103
  ),
100
104
  );
101
105
  if (sources.length !== ids.length) {
@@ -11,7 +11,10 @@ import { and, eq } from "drizzle-orm";
11
11
  import { z } from "zod";
12
12
 
13
13
  import { getDb, schema } from "../server/db/index.js";
14
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
14
+ import {
15
+ getCurrentOwnerEmail,
16
+ ownerEmailMatches,
17
+ } from "../server/lib/recordings.js";
15
18
 
16
19
  export default defineAction({
17
20
  description:
@@ -29,7 +32,7 @@ export default defineAction({
29
32
  .where(
30
33
  and(
31
34
  eq(schema.recordings.id, args.id),
32
- eq(schema.recordings.ownerEmail, ownerEmail),
35
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
33
36
  ),
34
37
  );
35
38
  if (!existing) throw new Error(`Recording not found: ${args.id}`);
@@ -19,7 +19,10 @@ import {
19
19
  serializeEdits,
20
20
  } from "../app/lib/timestamp-mapping.js";
21
21
  import { getDb, schema } from "../server/db/index.js";
22
- import { getCurrentOwnerEmail } from "../server/lib/recordings.js";
22
+ import {
23
+ getCurrentOwnerEmail,
24
+ ownerEmailMatches,
25
+ } from "../server/lib/recordings.js";
23
26
  import { assertNativeRecordingMedia } from "./lib/native-media.js";
24
27
 
25
28
  export default defineAction({
@@ -38,7 +41,7 @@ export default defineAction({
38
41
  .where(
39
42
  and(
40
43
  eq(schema.recordings.id, args.recordingId),
41
- eq(schema.recordings.ownerEmail, ownerEmail),
44
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
42
45
  ),
43
46
  );
44
47
  if (!existing) {
@@ -23,6 +23,7 @@ import { z } from "zod";
23
23
  import { getDb, schema } from "../server/db/index.js";
24
24
  import {
25
25
  getActiveOrganizationId,
26
+ ownerEmailMatches,
26
27
  parseSpaceIds,
27
28
  } from "../server/lib/recordings.js";
28
29
  import { parseBrowserDiagnosticsRow } from "../shared/browser-diagnostics.js";
@@ -168,7 +169,7 @@ async function fetchFoldersForSpace(spaceId: string | null) {
168
169
  .from(schema.folders)
169
170
  .where(
170
171
  and(
171
- eq(schema.folders.ownerEmail, ownerEmail),
172
+ ownerEmailMatches(schema.folders.ownerEmail, ownerEmail),
172
173
  spaceId
173
174
  ? eq(schema.folders.spaceId, spaceId)
174
175
  : isNull(schema.folders.spaceId),
@@ -117,13 +117,29 @@ export function RecordingCard({
117
117
  return (local || "?").slice(0, 2).toUpperCase();
118
118
  }, [recording.ownerEmail]);
119
119
 
120
- const handleOpen = useCallback(() => {
121
- if (selectionMode) {
122
- onToggleSelect?.(recording.id);
123
- } else {
124
- navigate(`/r/${recording.id}`);
125
- }
126
- }, [navigate, onToggleSelect, recording.id, selectionMode]);
120
+ const recordingPath = `/r/${recording.id}`;
121
+
122
+ const handleOpen = useCallback(
123
+ (event: React.MouseEvent<HTMLElement>) => {
124
+ const shouldOpenNewTab =
125
+ event.button === 1 ||
126
+ (event.button === 0 && (event.metaKey || event.ctrlKey));
127
+
128
+ if (shouldOpenNewTab) {
129
+ event.preventDefault();
130
+ event.stopPropagation();
131
+ window.open(recordingPath, "_blank", "noopener,noreferrer");
132
+ return;
133
+ }
134
+
135
+ if (selectionMode) {
136
+ onToggleSelect?.(recording.id);
137
+ } else {
138
+ navigate(recordingPath);
139
+ }
140
+ },
141
+ [navigate, onToggleSelect, recording.id, recordingPath, selectionMode],
142
+ );
127
143
 
128
144
  const handleCheckbox = useCallback(
129
145
  (e: React.MouseEvent) => {
@@ -145,6 +161,7 @@ export function RecordingCard({
145
161
  <div
146
162
  role="article"
147
163
  onClick={handleOpen}
164
+ onAuxClick={handleOpen}
148
165
  onMouseEnter={() => setHovered(true)}
149
166
  onMouseLeave={() => setHovered(false)}
150
167
  className={cn(