@agent-native/core 0.148.1 → 0.149.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-08-10-wide-tables-in-analytics-chat-now-scroll-horizontally-within.md +6 -0
  3. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +13 -53
  4. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -70
  5. package/corpus/templates/calendar/app/lib/event-layout.ts +122 -0
  6. package/corpus/templates/calendar/app/routes/_app.availability.tsx +1 -1
  7. package/corpus/templates/calendar/changelog/2026-08-10-opening-availability-settings-from-the-agent-lands-on-the-av.md +6 -0
  8. package/corpus/templates/calendar/vitest.config.ts +6 -1
  9. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +3 -1
  10. package/corpus/templates/clips/app/components/player/video-player.tsx +44 -4
  11. package/corpus/templates/clips/changelog/2026-08-10-fixed-transcription-failures-for-recordings-without-captured.md +6 -0
  12. package/corpus/templates/clips/changelog/2026-08-10-mobile-clips-can-now-enter-fullscreen-on-iphone-safari.md +6 -0
  13. package/corpus/templates/content/actions/_property-utils.ts +11 -1
  14. package/corpus/templates/content/actions/view-screen.ts +62 -12
  15. package/corpus/templates/content/server/plugins/agent-chat.ts +2 -0
  16. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +22 -0
  17. package/corpus/templates/design/AGENTS.md +3 -0
  18. package/corpus/templates/design/actions/create-design-from-template.ts +19 -0
  19. package/corpus/templates/design/actions/get-design-snapshot.ts +18 -0
  20. package/corpus/templates/design/actions/get-design-template.ts +178 -0
  21. package/corpus/templates/design/actions/list-designs.ts +3 -1
  22. package/corpus/templates/design/actions/view-screen.ts +31 -0
  23. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +225 -214
  24. package/corpus/templates/design/app/components/design/multi-screen/types.ts +3 -0
  25. package/corpus/templates/design/app/i18n-data.ts +78 -0
  26. package/corpus/templates/design/app/lib/design-authors.ts +64 -0
  27. package/corpus/templates/design/app/pages/Index.tsx +114 -4
  28. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +1 -0
  29. package/corpus/templates/design/changelog/2026-08-07-alt-dragging-an-element-to-copy-it-now-keeps-the-copy-visibl.md +6 -0
  30. package/corpus/templates/design/changelog/2026-08-07-design-cards-now-show-who-created-each-design-with-a-filter-.md +6 -0
  31. package/corpus/templates/design/changelog/2026-08-07-drawing-a-shape-on-the-overview-canvas-now-keeps-the-shape-s.md +6 -0
  32. package/corpus/templates/design/changelog/2026-08-07-hold-option-alt-while-dragging-a-frame-on-the-overview-canva.md +6 -0
  33. package/corpus/templates/design/server/lib/design-template-data.ts +143 -0
  34. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -0
  35. package/corpus/templates/forms/AGENTS.md +2 -2
  36. package/corpus/templates/forms/actions/create-form.ts +1 -1
  37. package/corpus/templates/forms/actions/update-form.ts +5 -5
  38. package/corpus/templates/forms/changelog/2026-08-10-forms-now-keep-agent-applied-field-changes-visible-after-an-.md +6 -0
  39. package/corpus/templates/forms/changelog/2026-08-10-published-form-links-always-use-the-working-public-form-url.md +6 -0
  40. package/corpus/templates/forms/server/plugins/agent-chat.ts +2 -2
  41. package/corpus/templates/slides/AGENTS.md +4 -3
  42. package/corpus/templates/slides/actions/import-google-slides-reference.ts +56 -10
  43. package/corpus/templates/slides/actions/patch-deck.ts +18 -2
  44. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
  45. package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
  46. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +22 -75
  47. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +5 -3
  48. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -1
  49. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +15 -4
  50. package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +116 -55
  51. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +27 -4
  52. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
  53. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +143 -18
  54. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  55. package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
  56. package/corpus/templates/slides/app/lib/generation-state.ts +7 -1
  57. package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
  58. package/corpus/templates/slides/app/pages/DeckEditor.tsx +44 -14
  59. package/corpus/templates/slides/app/pages/Index.tsx +124 -135
  60. package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
  61. package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
  62. package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
  63. package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
  64. package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
  65. package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
  66. package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
  67. package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
  68. package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
  69. package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
  70. package/corpus/templates/slides/changelog/2026-08-10-imported-reference-decks-now-show-clear-success-and-stay-sel.md +6 -0
  71. package/corpus/templates/slides/changelog/2026-08-10-pasted-google-slides-links-import-after-reconnecting-google-.md +6 -0
  72. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +4 -0
  73. package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
  74. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
  75. package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
  76. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +13 -0
  77. package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
  78. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  79. package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
  80. package/dist/agent/engine/registry.d.ts +8 -2
  81. package/dist/agent/engine/registry.js +63 -29
  82. package/dist/agent/production-agent.js +7 -0
  83. package/dist/agent/run-manager.d.ts +5 -4
  84. package/dist/agent/run-manager.js +6 -5
  85. package/dist/cli/claude-code-participant.d.ts +2 -1
  86. package/dist/cli/claude-code-participant.js +19 -0
  87. package/dist/cli/code-agent-executor.js +223 -0
  88. package/dist/cli/create-workspace.js +3 -3
  89. package/dist/cli/create.d.ts +3 -2
  90. package/dist/cli/create.js +20 -24
  91. package/dist/client/AgentPanel.js +8 -4
  92. package/dist/client/AssistantChat.js +34 -1
  93. package/dist/client/agent-chat-adapter.d.ts +1 -0
  94. package/dist/client/agent-chat-adapter.js +10 -2
  95. package/dist/client/chat/markdown-renderer.d.ts +1 -0
  96. package/dist/client/chat/markdown-renderer.js +4 -0
  97. package/dist/client/chat/message-components.d.ts +1 -0
  98. package/dist/client/chat/message-components.js +9 -1
  99. package/dist/client/chat/tool-call-display.d.ts +8 -0
  100. package/dist/client/chat/tool-call-display.js +30 -21
  101. package/dist/client/chat-first/app-pane.js +1 -1
  102. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  103. package/dist/client/chat-first/apps-rail.js +11 -11
  104. package/dist/client/chat-first/chat-history.d.ts +1 -1
  105. package/dist/client/chat-first/chat-history.js +2 -0
  106. package/dist/client/chat-first/primary-nav.js +1 -1
  107. package/dist/client/progress/RunsTray.js +14 -1
  108. package/dist/client/resources/McpConnectionSuggestion.js +2 -2
  109. package/dist/client/settings/SettingsTabsPage.d.ts +3 -1
  110. package/dist/client/settings/SettingsTabsPage.js +2 -2
  111. package/dist/client/sse-event-processor.d.ts +8 -0
  112. package/dist/client/sse-event-processor.js +13 -6
  113. package/dist/collab/awareness.d.ts +2 -2
  114. package/dist/collab/routes.d.ts +1 -1
  115. package/dist/deploy/build.d.ts +5 -7
  116. package/dist/deploy/build.js +19 -12
  117. package/dist/localization/default-messages.js +1 -1
  118. package/dist/notifications/routes.d.ts +3 -3
  119. package/dist/observability/routes.d.ts +6 -6
  120. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  121. package/dist/scripts/call-agent.js +3 -3
  122. package/dist/scripts/describe-workspace-apps.js +1 -1
  123. package/dist/server/agent-capabilities.js +0 -0
  124. package/dist/server/agent-chat/context-tools.js +1 -1
  125. package/dist/server/agent-chat/prompt-resources.js +1 -1
  126. package/dist/server/agent-chat-plugin.js +1 -1
  127. package/dist/server/auth.js +190 -42
  128. package/dist/server/better-auth-migrations.js +53 -0
  129. package/dist/server/http-response-telemetry.js +7 -1
  130. package/dist/server/onboarding-html.js +119 -33
  131. package/dist/server/transcribe-voice.d.ts +1 -1
  132. package/dist/shared/password-policy.d.ts +2 -1
  133. package/dist/shared/password-policy.js +2 -1
  134. package/dist/styles/agent-conversation.css +20 -3
  135. package/dist/styles/agent-native.css +14 -2
  136. package/dist/templates/default/app/i18n/ar-SA.ts +1 -1
  137. package/dist/templates/default/app/i18n/de-DE.ts +1 -1
  138. package/dist/templates/default/app/i18n/en-US.ts +1 -1
  139. package/dist/templates/default/app/i18n/es-ES.ts +1 -1
  140. package/dist/templates/default/app/i18n/fr-FR.ts +1 -1
  141. package/dist/templates/default/app/i18n/hi-IN.ts +1 -1
  142. package/dist/templates/default/app/i18n/ja-JP.ts +1 -1
  143. package/dist/templates/default/app/i18n/ko-KR.ts +1 -1
  144. package/dist/templates/default/app/i18n/pt-BR.ts +1 -1
  145. package/dist/templates/default/app/i18n/zh-CN.ts +1 -1
  146. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  147. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  148. package/dist/templates/workspace-root/pnpm-workspace.yaml +2 -0
  149. package/docs/content/cloneable-saas.mdx +6 -6
  150. package/docs/content/deployment.mdx +10 -8
  151. package/docs/content/environment-variables.mdx +4 -3
  152. package/package.json +2 -2
  153. package/src/templates/default/app/i18n/ar-SA.ts +1 -1
  154. package/src/templates/default/app/i18n/de-DE.ts +1 -1
  155. package/src/templates/default/app/i18n/en-US.ts +1 -1
  156. package/src/templates/default/app/i18n/es-ES.ts +1 -1
  157. package/src/templates/default/app/i18n/fr-FR.ts +1 -1
  158. package/src/templates/default/app/i18n/hi-IN.ts +1 -1
  159. package/src/templates/default/app/i18n/ja-JP.ts +1 -1
  160. package/src/templates/default/app/i18n/ko-KR.ts +1 -1
  161. package/src/templates/default/app/i18n/pt-BR.ts +1 -1
  162. package/src/templates/default/app/i18n/zh-CN.ts +1 -1
  163. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  164. package/src/templates/default/pnpm-workspace.yaml +1 -0
  165. package/src/templates/workspace-root/pnpm-workspace.yaml +2 -0
@@ -1353,6 +1353,9 @@ const enUS = {
1353
1353
  newDesign: "New Design",
1354
1354
  newDesignLower: "New design",
1355
1355
  createDesignProject: "Create a design project",
1356
+ createdBy: "Created by",
1357
+ allAuthors: "All authors",
1358
+ me: "Me",
1356
1359
  openingDesign: "Opening design...",
1357
1360
  skipToEditor: "Skip to editor",
1358
1361
  failedToCreateDesign: "Failed to create design",
@@ -15375,6 +15378,72 @@ const designComponentSourceOverrides = {
15375
15378
  },
15376
15379
  } satisfies Record<Exclude<LocaleCode, "en-US" | "zh-TW">, PartialMessages>;
15377
15380
 
15381
+ const designAuthorFilterOverrides = {
15382
+ "zh-CN": {
15383
+ home: {
15384
+ createdBy: "创建者",
15385
+ allAuthors: "所有作者",
15386
+ me: "我",
15387
+ },
15388
+ },
15389
+ "es-ES": {
15390
+ home: {
15391
+ createdBy: "Creado por",
15392
+ allAuthors: "Todos los autores",
15393
+ me: "Yo",
15394
+ },
15395
+ },
15396
+ "fr-FR": {
15397
+ home: {
15398
+ createdBy: "Créé par",
15399
+ allAuthors: "Tous les auteurs",
15400
+ me: "Moi",
15401
+ },
15402
+ },
15403
+ "de-DE": {
15404
+ home: {
15405
+ createdBy: "Erstellt von",
15406
+ allAuthors: "Alle Autoren",
15407
+ me: "Ich",
15408
+ },
15409
+ },
15410
+ "ja-JP": {
15411
+ home: {
15412
+ createdBy: "作成者",
15413
+ allAuthors: "すべての作成者",
15414
+ me: "自分",
15415
+ },
15416
+ },
15417
+ "ko-KR": {
15418
+ home: {
15419
+ createdBy: "만든 사람",
15420
+ allAuthors: "모든 작성자",
15421
+ me: "나",
15422
+ },
15423
+ },
15424
+ "pt-BR": {
15425
+ home: {
15426
+ createdBy: "Criado por",
15427
+ allAuthors: "Todos os autores",
15428
+ me: "Eu",
15429
+ },
15430
+ },
15431
+ "hi-IN": {
15432
+ home: {
15433
+ createdBy: "द्वारा बनाया गया",
15434
+ allAuthors: "सभी लेखक",
15435
+ me: "मैं",
15436
+ },
15437
+ },
15438
+ "ar-SA": {
15439
+ home: {
15440
+ createdBy: "أنشأه",
15441
+ allAuthors: "جميع المؤلفين",
15442
+ me: "أنا",
15443
+ },
15444
+ },
15445
+ } satisfies Record<Exclude<LocaleCode, "en-US" | "zh-TW">, PartialMessages>;
15446
+
15378
15447
  export const messagesByLocale = {
15379
15448
  "en-US": enUS,
15380
15449
  "zh-TW": mergeLocalizedMessages(
@@ -15429,6 +15498,7 @@ export const messagesByLocale = {
15429
15498
  designRuntimeIdentityAndBridgeOverrides["zh-CN"],
15430
15499
  designComponentInstanceOverrides["zh-CN"],
15431
15500
  designComponentSourceOverrides["zh-CN"],
15501
+ designAuthorFilterOverrides["zh-CN"],
15432
15502
  {
15433
15503
  root: {
15434
15504
  commandActions: "操作",
@@ -15508,6 +15578,7 @@ export const messagesByLocale = {
15508
15578
  designRuntimeIdentityAndBridgeOverrides["es-ES"],
15509
15579
  designComponentInstanceOverrides["es-ES"],
15510
15580
  designComponentSourceOverrides["es-ES"],
15581
+ designAuthorFilterOverrides["es-ES"],
15511
15582
  {
15512
15583
  root: {
15513
15584
  commandActions: "Acciones",
@@ -15589,6 +15660,7 @@ export const messagesByLocale = {
15589
15660
  designRuntimeIdentityAndBridgeOverrides["fr-FR"],
15590
15661
  designComponentInstanceOverrides["fr-FR"],
15591
15662
  designComponentSourceOverrides["fr-FR"],
15663
+ designAuthorFilterOverrides["fr-FR"],
15592
15664
  {
15593
15665
  root: {
15594
15666
  commandActions: "Actions",
@@ -15670,6 +15742,7 @@ export const messagesByLocale = {
15670
15742
  designRuntimeIdentityAndBridgeOverrides["de-DE"],
15671
15743
  designComponentInstanceOverrides["de-DE"],
15672
15744
  designComponentSourceOverrides["de-DE"],
15745
+ designAuthorFilterOverrides["de-DE"],
15673
15746
  {
15674
15747
  root: {
15675
15748
  commandActions: "Aktionen",
@@ -15751,6 +15824,7 @@ export const messagesByLocale = {
15751
15824
  designRuntimeIdentityAndBridgeOverrides["ja-JP"],
15752
15825
  designComponentInstanceOverrides["ja-JP"],
15753
15826
  designComponentSourceOverrides["ja-JP"],
15827
+ designAuthorFilterOverrides["ja-JP"],
15754
15828
  {
15755
15829
  root: {
15756
15830
  commandActions: "操作",
@@ -15833,6 +15907,7 @@ export const messagesByLocale = {
15833
15907
  designRuntimeIdentityAndBridgeOverrides["ko-KR"],
15834
15908
  designComponentInstanceOverrides["ko-KR"],
15835
15909
  designComponentSourceOverrides["ko-KR"],
15910
+ designAuthorFilterOverrides["ko-KR"],
15836
15911
  {
15837
15912
  root: {
15838
15913
  commandActions: "작업",
@@ -15913,6 +15988,7 @@ export const messagesByLocale = {
15913
15988
  designRuntimeIdentityAndBridgeOverrides["pt-BR"],
15914
15989
  designComponentInstanceOverrides["pt-BR"],
15915
15990
  designComponentSourceOverrides["pt-BR"],
15991
+ designAuthorFilterOverrides["pt-BR"],
15916
15992
  {
15917
15993
  root: {
15918
15994
  commandActions: "Ações",
@@ -15994,6 +16070,7 @@ export const messagesByLocale = {
15994
16070
  designRuntimeIdentityAndBridgeOverrides["hi-IN"],
15995
16071
  designComponentInstanceOverrides["hi-IN"],
15996
16072
  designComponentSourceOverrides["hi-IN"],
16073
+ designAuthorFilterOverrides["hi-IN"],
15997
16074
  {
15998
16075
  root: {
15999
16076
  commandActions: "क्रियाएं",
@@ -16075,6 +16152,7 @@ export const messagesByLocale = {
16075
16152
  designRuntimeIdentityAndBridgeOverrides["ar-SA"],
16076
16153
  designComponentInstanceOverrides["ar-SA"],
16077
16154
  designComponentSourceOverrides["ar-SA"],
16155
+ designAuthorFilterOverrides["ar-SA"],
16078
16156
  {
16079
16157
  root: {
16080
16158
  commandActions: "الإجراءات",
@@ -0,0 +1,64 @@
1
+ import { emailToName } from "@agent-native/core/client/collab";
2
+
3
+ /** Author-filter values that cannot collide with a real owner email. */
4
+ export const ALL_AUTHORS = "all";
5
+ export const MY_DESIGNS = "me";
6
+
7
+ export interface AuthoredDesign {
8
+ ownerEmail?: string | null;
9
+ }
10
+
11
+ export function normalizeAuthorEmail(
12
+ email: string | null | undefined,
13
+ ): string | null {
14
+ const trimmed = email?.trim();
15
+ return trimmed ? trimmed.toLowerCase() : null;
16
+ }
17
+
18
+ /** Distinct owner emails across the whole library, ordered by display name. */
19
+ export function collectAuthorEmails(designs: AuthoredDesign[]): string[] {
20
+ const byNormalized = new Map<string, string>();
21
+ for (const design of designs) {
22
+ const email = design.ownerEmail?.trim();
23
+ if (!email) continue;
24
+ const normalized = email.toLowerCase();
25
+ if (!byNormalized.has(normalized)) byNormalized.set(normalized, email);
26
+ }
27
+ return Array.from(byNormalized.values()).sort((a, b) =>
28
+ emailToName(a).localeCompare(emailToName(b)),
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Whether author bylines are worth showing on cards. A solo workspace would
34
+ * only ever repeat the viewer's own name, so bylines stay hidden until a
35
+ * second person is involved — either as an org member or as the owner of a
36
+ * design the viewer can see.
37
+ */
38
+ export function shouldShowAuthors(input: {
39
+ orgMemberCount: number | undefined;
40
+ authorEmails: string[];
41
+ }): boolean {
42
+ return (input.orgMemberCount ?? 0) > 1 || input.authorEmails.length > 1;
43
+ }
44
+
45
+ export function filterDesignsByAuthor<T extends AuthoredDesign>(
46
+ designs: T[],
47
+ author: string,
48
+ viewerEmail: string | null | undefined,
49
+ ): T[] {
50
+ if (author === ALL_AUTHORS) return designs;
51
+ const normalizedViewer = normalizeAuthorEmail(viewerEmail);
52
+ // An unknown viewer cannot own anything, so "mine" is empty rather than
53
+ // silently falling back to every design.
54
+ if (author === MY_DESIGNS) {
55
+ if (!normalizedViewer) return [];
56
+ return designs.filter(
57
+ (design) => normalizeAuthorEmail(design.ownerEmail) === normalizedViewer,
58
+ );
59
+ }
60
+ const normalizedAuthor = normalizeAuthorEmail(author);
61
+ return designs.filter(
62
+ (design) => normalizeAuthorEmail(design.ownerEmail) === normalizedAuthor,
63
+ );
64
+ }
@@ -1,14 +1,18 @@
1
+ import { emailToColor, emailToName } from "@agent-native/core/client/collab";
1
2
  import { type PromptComposerSubmitOptions } from "@agent-native/core/client/composer";
2
3
  import { useFeatureFlag } from "@agent-native/core/client/feature-flags";
3
4
  import {
4
5
  useActionQuery,
5
6
  useActionMutation,
7
+ useAvatarUrl,
8
+ useSession,
6
9
  } from "@agent-native/core/client/hooks";
7
10
  import {
8
11
  injectSessionReplayIframeBootstrap,
9
12
  SESSION_REPLAY_IFRAME_ATTRIBUTE,
10
13
  } from "@agent-native/core/client/host";
11
14
  import { useT } from "@agent-native/core/client/i18n";
15
+ import { useOrgMembers } from "@agent-native/core/client/org";
12
16
  import { CreativeContextShareSheet } from "@agent-native/creative-context/client";
13
17
  import {
14
18
  useSetHeaderActions,
@@ -49,6 +53,7 @@ import {
49
53
  AlertDialogHeader,
50
54
  AlertDialogTitle,
51
55
  } from "@/components/ui/alert-dialog";
56
+ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
52
57
  import { Button } from "@/components/ui/button";
53
58
  import { Checkbox } from "@/components/ui/checkbox";
54
59
  import {
@@ -58,6 +63,13 @@ import {
58
63
  DropdownMenuTrigger,
59
64
  } from "@/components/ui/dropdown-menu";
60
65
  import { Input } from "@/components/ui/input";
66
+ import {
67
+ Select,
68
+ SelectContent,
69
+ SelectItem,
70
+ SelectTrigger,
71
+ SelectValue,
72
+ } from "@/components/ui/select";
61
73
  import { Spinner } from "@/components/ui/spinner";
62
74
  import {
63
75
  Tooltip,
@@ -66,6 +78,14 @@ import {
66
78
  } from "@/components/ui/tooltip";
67
79
  import { useDesignSystems } from "@/hooks/use-design-systems";
68
80
  import { sendToDesignAgentChat } from "@/lib/agent-chat";
81
+ import {
82
+ ALL_AUTHORS,
83
+ collectAuthorEmails,
84
+ filterDesignsByAuthor,
85
+ MY_DESIGNS,
86
+ normalizeAuthorEmail,
87
+ shouldShowAuthors,
88
+ } from "@/lib/design-authors";
69
89
  import {
70
90
  clearPendingGeneration,
71
91
  writePendingGeneration,
@@ -80,6 +100,7 @@ interface Design {
80
100
  description?: string;
81
101
  projectType: ProjectType;
82
102
  designSystemId?: string | null;
103
+ ownerEmail?: string | null;
83
104
  createdAt?: string;
84
105
  updatedAt?: string;
85
106
  /** Preview HTML for the thumbnail. Only present when the list query asks
@@ -93,6 +114,7 @@ export default function Index() {
93
114
  const [searchParams, setSearchParams] = useSearchParams();
94
115
  const queryClient = useQueryClient();
95
116
  const [search, setSearch] = useState("");
117
+ const [author, setAuthor] = useState<string>(ALL_AUTHORS);
96
118
  const [deleteId, setDeleteId] = useState<string | null>(null);
97
119
  const [bulkDeleteOpen, setBulkDeleteOpen] = useState(false);
98
120
  const [selectedDesignIds, setSelectedDesignIds] = useState<Set<string>>(
@@ -134,6 +156,8 @@ export default function Index() {
134
156
  "list-design-templates",
135
157
  { includePreview: "true" },
136
158
  );
159
+ const { session } = useSession();
160
+ const { data: orgMembersPage } = useOrgMembers();
137
161
 
138
162
  const createMutation = useActionMutation("create-design");
139
163
  const createFromTemplateMutation = useActionMutation(
@@ -174,11 +198,27 @@ export default function Index() {
174
198
  const selectedTemplate =
175
199
  templateOptions.find((template) => template.id === newTemplateId) ?? null;
176
200
 
201
+ const viewerEmail = session?.email ?? null;
202
+ const authorEmails = useMemo(() => collectAuthorEmails(designs), [designs]);
203
+ const showAuthors = shouldShowAuthors({
204
+ orgMemberCount: orgMembersPage?.totalCount,
205
+ authorEmails,
206
+ });
207
+ const normalizedViewerEmail = normalizeAuthorEmail(viewerEmail);
208
+ const viewerHasDesigns = authorEmails.some(
209
+ (email) => normalizeAuthorEmail(email) === normalizedViewerEmail,
210
+ );
211
+ // One condition for both the control and the filtering it drives — a hidden
212
+ // control with a live filter leaves an empty grid the user cannot reset.
213
+ const canFilterByAuthor = authorEmails.length > 1;
214
+ const byAuthor = canFilterByAuthor
215
+ ? filterDesignsByAuthor(designs, author, viewerEmail)
216
+ : designs;
177
217
  const filtered = search
178
- ? designs.filter((d) =>
218
+ ? byAuthor.filter((d) =>
179
219
  d.title.toLowerCase().includes(search.toLowerCase()),
180
220
  )
181
- : designs;
221
+ : byAuthor;
182
222
  const selectedDesignCount = selectedDesignIds.size;
183
223
  const isSelectingDesigns = selectedDesignCount > 0;
184
224
  const allVisibleSelected =
@@ -313,6 +353,15 @@ export default function Index() {
313
353
  );
314
354
  }, []);
315
355
 
356
+ // Bulk actions operate on the visible set, so narrowing the visible set has
357
+ // to drop selections the user can no longer see.
358
+ const handleAuthorChange = useCallback((next: string) => {
359
+ setAuthor(next);
360
+ setSelectedDesignIds((current) =>
361
+ current.size === 0 ? current : new Set(),
362
+ );
363
+ }, []);
364
+
316
365
  const clearSelection = useCallback(() => {
317
366
  setSelectedDesignIds(new Set());
318
367
  }, []);
@@ -723,6 +772,37 @@ export default function Index() {
723
772
  useSetHeaderActions(
724
773
  designs.length > 0 ? (
725
774
  <div className="flex items-center gap-3">
775
+ {canFilterByAuthor ? (
776
+ <Select value={author} onValueChange={handleAuthorChange}>
777
+ <SelectTrigger
778
+ aria-label={t("home.createdBy")}
779
+ className="h-8 w-40 bg-accent/50 border-border text-sm text-foreground/90"
780
+ >
781
+ <SelectValue placeholder={t("home.createdBy")} />
782
+ </SelectTrigger>
783
+ <SelectContent>
784
+ <SelectItem value={ALL_AUTHORS}>
785
+ {t("home.allAuthors")}
786
+ </SelectItem>
787
+ {viewerHasDesigns ? (
788
+ <SelectItem value={MY_DESIGNS}>{t("home.me")}</SelectItem>
789
+ ) : null}
790
+ {authorEmails
791
+ .filter(
792
+ (email) =>
793
+ !(
794
+ viewerHasDesigns &&
795
+ normalizeAuthorEmail(email) === normalizedViewerEmail
796
+ ),
797
+ )
798
+ .map((email) => (
799
+ <SelectItem key={email} value={email}>
800
+ {emailToName(email)}
801
+ </SelectItem>
802
+ ))}
803
+ </SelectContent>
804
+ </Select>
805
+ ) : null}
726
806
  <div className="relative">
727
807
  <IconSearch className="absolute start-2.5 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground/70" />
728
808
  <Input
@@ -881,8 +961,16 @@ export default function Index() {
881
961
  {design.title}
882
962
  </h3>
883
963
  </div>
884
- <div className="text-xs text-muted-foreground/70">
885
- {formatDate(design.updatedAt || design.createdAt)}
964
+ <div className="flex items-center gap-1.5 text-xs text-muted-foreground/70">
965
+ <span className="shrink-0">
966
+ {formatDate(design.updatedAt || design.createdAt)}
967
+ </span>
968
+ {showAuthors && design.ownerEmail ? (
969
+ <>
970
+ <span aria-hidden>·</span>
971
+ <DesignAuthorByline email={design.ownerEmail} />
972
+ </>
973
+ ) : null}
886
974
  </div>
887
975
  </div>
888
976
  </>
@@ -1133,6 +1221,28 @@ export default function Index() {
1133
1221
  );
1134
1222
  }
1135
1223
 
1224
+ /** Who created a design, shown on its library card in shared workspaces. */
1225
+ function DesignAuthorByline({ email }: { email: string }) {
1226
+ const name = emailToName(email);
1227
+ const avatarUrl = useAvatarUrl(email);
1228
+
1229
+ return (
1230
+ <span className="flex min-w-0 items-center gap-1.5" title={email}>
1231
+ <Avatar className="size-4 shrink-0">
1232
+ {avatarUrl ? <AvatarImage src={avatarUrl} alt="" /> : null}
1233
+ <AvatarFallback
1234
+ /* guard:allow-raw-color — emailToColor ignores the theme, so the initial stays white in both. */
1235
+ className="text-[8px] font-semibold text-white"
1236
+ style={{ backgroundColor: emailToColor(email) }}
1237
+ >
1238
+ {name.trim().charAt(0).toUpperCase() || "?"}
1239
+ </AvatarFallback>
1240
+ </Avatar>
1241
+ <span className="truncate">{name}</span>
1242
+ </span>
1243
+ );
1244
+ }
1245
+
1136
1246
  /**
1137
1247
  * Render the design's index.html as a non-interactive thumbnail. The iframe
1138
1248
  * renders at a fixed natural size (so designs that assume a desktop viewport
@@ -182,6 +182,7 @@ export function designTemplateRefinementDirectives(
182
182
  `This design was copied from template "${templateId}". Its files, canvas dimensions, defaults, and locked layers already exist.`,
183
183
  ...designSystemTemplateEditDirectives(designSystemId),
184
184
  `Call \`get-design-snapshot --designId="${designId}"\` exactly once before editing.`,
185
+ `The copied screens are edited in place, so they stop showing the template once this run saves. \`view-screen\` reports the template's authoritative dimensions and fonts as \`design.createdFromTemplate\` on every turn — keep them unchanged. Call \`get-design-template --designId="${designId}"\` when you need the template's original markup or locked layers.`,
185
186
  "Refine the existing template with `edit-design`; do not call `generate-design`, `delete-file`, or create a replacement screen.",
186
187
  'Layers marked `data-agent-native-locked="true"` and everything inside them must remain byte-for-byte unchanged. The server rejects changes to locked backgrounds, logos, and other fixed template layers.',
187
188
  "Preserve canvasFrames and the template's width and height. Change only the unlocked content needed for the user's request.",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Alt-dragging an element to copy it now keeps the copy visible on the canvas instead of only adding it to the layer list.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Design cards now show who created each design, with a filter by creator when your workspace has more than one person.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Drawing a shape on the overview canvas now keeps the shape selected instead of its screen, so duplicating copies the shape rather than the whole screen.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Hold Option/Alt while dragging a frame on the overview canvas to drop a copy of it, including a whole multi-frame selection.
@@ -23,6 +23,128 @@ export function parseDesignTemplateData(
23
23
  }
24
24
  }
25
25
 
26
+ export interface DesignTemplateSourceFile {
27
+ designFileId: string;
28
+ templateFileId: string;
29
+ filename: string | null;
30
+ width: number | null;
31
+ height: number | null;
32
+ }
33
+
34
+ export interface DesignTemplateSource {
35
+ templateId: string;
36
+ title: string | null;
37
+ category: string | null;
38
+ instantiatedAt: string | null;
39
+ appliedDesignSystemId: string | null;
40
+ /**
41
+ * Captured at copy time, not re-read from the template. Dimensions and fonts
42
+ * are small enough to restate on every turn, which is what stops a follow-up
43
+ * request from resizing the artboard or swapping the typeface. The full
44
+ * template markup stays behind `get-design-template`.
45
+ */
46
+ files: DesignTemplateSourceFile[];
47
+ fonts: string[];
48
+ }
49
+
50
+ const MAX_TRACKED_FONTS = 12;
51
+
52
+ /**
53
+ * Font drift is invisible in a layout diff and is the most common way a
54
+ * refined template stops looking like its template. Capture the declared
55
+ * families once so later turns can restate them without re-parsing markup.
56
+ */
57
+ export function extractTemplateFonts(html: string): string[] {
58
+ const fonts = new Set<string>();
59
+
60
+ for (const match of html.matchAll(/font-family\s*:\s*([^;}]+)/gi)) {
61
+ const family = match[1]
62
+ ?.split(",")[0]
63
+ ?.trim()
64
+ .replace(/^["']|["']$/g, "");
65
+ if (family && !/^(inherit|initial|unset|var\()/i.test(family)) {
66
+ fonts.add(family);
67
+ }
68
+ }
69
+ for (const link of html.matchAll(/fonts\.googleapis\.com\/[^"'\s>]+/gi)) {
70
+ for (const family of link[0].matchAll(/family=([^&:"']+)/gi)) {
71
+ let name: string;
72
+ try {
73
+ name = decodeURIComponent(family[1]!).replace(/\+/g, " ").trim();
74
+ } catch {
75
+ continue;
76
+ }
77
+ if (name) fonts.add(name);
78
+ }
79
+ }
80
+
81
+ return [...fonts].slice(0, MAX_TRACKED_FONTS);
82
+ }
83
+
84
+ function finiteNumber(value: unknown): number | null {
85
+ return typeof value === "number" && Number.isFinite(value)
86
+ ? Math.round(value)
87
+ : null;
88
+ }
89
+
90
+ /**
91
+ * Reads the template a design was created from. Returns null when the design
92
+ * was not created from a template; throws when the design claims a template
93
+ * but the record cannot be read, because silently treating that as "no
94
+ * template" would drop the template constraints the design is bound to.
95
+ */
96
+ export function readDesignTemplateSource(
97
+ data: Record<string, unknown>,
98
+ ): DesignTemplateSource | null {
99
+ const raw = data.templateSource;
100
+ if (raw === undefined || raw === null) return null;
101
+
102
+ const source = record(raw);
103
+ const templateId = source.templateId;
104
+ if (typeof templateId !== "string" || !templateId.trim()) {
105
+ throw new Error(
106
+ "Design records a templateSource without a readable templateId",
107
+ );
108
+ }
109
+
110
+ return {
111
+ templateId,
112
+ title: typeof source.title === "string" ? source.title : null,
113
+ category: typeof source.category === "string" ? source.category : null,
114
+ instantiatedAt:
115
+ typeof source.instantiatedAt === "string" ? source.instantiatedAt : null,
116
+ appliedDesignSystemId:
117
+ typeof source.appliedDesignSystemId === "string"
118
+ ? source.appliedDesignSystemId
119
+ : null,
120
+ files: (Array.isArray(source.files) ? source.files : []).flatMap(
121
+ (entry) => {
122
+ const file = record(entry);
123
+ const designFileId = file.designFileId;
124
+ const templateFileId = file.templateFileId;
125
+ if (
126
+ typeof designFileId !== "string" ||
127
+ typeof templateFileId !== "string"
128
+ ) {
129
+ return [];
130
+ }
131
+ return [
132
+ {
133
+ designFileId,
134
+ templateFileId,
135
+ filename: typeof file.filename === "string" ? file.filename : null,
136
+ width: finiteNumber(file.width),
137
+ height: finiteNumber(file.height),
138
+ },
139
+ ];
140
+ },
141
+ ),
142
+ fonts: (Array.isArray(source.fonts) ? source.fonts : []).filter(
143
+ (font): font is string => typeof font === "string",
144
+ ),
145
+ };
146
+ }
147
+
26
148
  /**
27
149
  * Templates are portable/shareable snapshots, so they must never retain
28
150
  * localhost bridge credentials. Reuse the same viewer-safe redaction applied
@@ -68,6 +190,27 @@ export function remapTemplateFileIds(
68
190
  return next;
69
191
  }
70
192
 
193
+ /**
194
+ * Exact frame lookup for one file. Unlike `firstTemplateDimensions` this never
195
+ * falls back to another screen's frame: a screen with no recorded frame must
196
+ * report "unknown", not a neighbour's dimensions.
197
+ */
198
+ export function templateFileDimensions(
199
+ data: Record<string, unknown>,
200
+ fileId: string,
201
+ ): { width: number | null; height: number | null } {
202
+ const frame = record(record(data.canvasFrames)[fileId]) as CanvasFrame;
203
+ const width =
204
+ typeof frame.width === "number" && Number.isFinite(frame.width)
205
+ ? Math.round(frame.width)
206
+ : null;
207
+ const height =
208
+ typeof frame.height === "number" && Number.isFinite(frame.height)
209
+ ? Math.round(frame.height)
210
+ : null;
211
+ return { width, height };
212
+ }
213
+
71
214
  export function firstTemplateDimensions(
72
215
  data: Record<string, unknown>,
73
216
  preferredFileId?: string,
@@ -27,6 +27,7 @@ const INITIAL_TOOL_NAMES = [
27
27
  "get-design-snapshot",
28
28
  "create-design",
29
29
  "create-design-from-template",
30
+ "get-design-template",
30
31
  "save-design-as-template",
31
32
  "open-visual-edit",
32
33
  "add-localhost-screens",
@@ -35,8 +35,8 @@ ladder.
35
35
  native inline summary/table and an "Open editor" expansion path.
36
36
  - For an anonymous feedback form or survey, create all fields in one
37
37
  `create-form` call with `status: "published"`, verify the persisted form, and
38
- return the action's exact public `/f/<slug>` response URL rather than only
39
- the private editor link.
38
+ copy the returned `publicUrl` verbatim. Never derive the link from `slug` or
39
+ omit the public `/f/<slug>` route segment.
40
40
  - To email the form owner when someone submits a response, set
41
41
  `settings.emailOnNewResponses: true` through `create-form` or `update-form`.
42
42
  Delivery uses the configured framework email provider (`RESEND_API_KEY` or
@@ -39,7 +39,7 @@ function formDeepLink(formId: string): string {
39
39
 
40
40
  export default defineAction({
41
41
  description:
42
- "Create a draft or published form. Set settings.anonymous=true to suppress submitter IP, identity, and source metadata, or settings.emailOnNewResponses=true to email the form owner when responses arrive. Published forms return a direct public response URL; drafts return an editor URL.",
42
+ "Create a draft or published form. Set settings.anonymous=true to suppress submitter IP, identity, and source metadata, or settings.emailOnNewResponses=true to email the form owner when responses arrive. Published results include a canonical publicUrl; copy it verbatim (it includes /f/<slug>) instead of deriving a URL from slug. Drafts return an editor URL.",
43
43
  schema: z.object({
44
44
  title: z.string().optional().describe("Form title"),
45
45
  description: z.string().optional().describe("Form description"),
@@ -142,11 +142,11 @@ export default defineAction({
142
142
  .set(updates)
143
143
  .where(eq(schema.forms.id, args.id));
144
144
 
145
- const [row] = await db
146
- .select()
147
- .from(schema.forms)
148
- .where(eq(schema.forms.id, args.id))
149
- .limit(1);
145
+ // Do not re-read after the write. On pooled Postgres a follow-up SELECT
146
+ // can land on a lagging replica and return the pre-update field array,
147
+ // which makes the agent report stale state and can overwrite a later edit
148
+ // with that stale snapshot. The values written above are already validated.
149
+ const row = { ...existing, ...updates } as typeof existing;
150
150
 
151
151
  invalidatePublicFormCache(existing, row);
152
152
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-10
4
+ ---
5
+
6
+ Forms now keep agent-applied field changes visible after an edit