@agent-native/core-corpus 0.216.0 → 0.218.0

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 (196) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
  3. package/corpus/templates/analytics/AGENTS.md +1 -1
  4. package/corpus/templates/analytics/CHANGELOG.md +3 -0
  5. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
  6. package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
  7. package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
  8. package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
  9. package/corpus/templates/analytics/actions/view-screen.ts +29 -3
  10. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
  11. package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
  12. package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
  13. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
  14. package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
  15. package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
  16. package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
  17. package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
  19. package/corpus/templates/analytics/server/db/schema.ts +67 -0
  20. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
  21. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
  22. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
  23. package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
  24. package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
  25. package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
  26. package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
  27. package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
  28. package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
  29. package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
  30. package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
  31. package/corpus/templates/analytics/server/plugins/db.ts +46 -0
  32. package/corpus/templates/analytics/shared/output-use.ts +205 -0
  33. package/corpus/templates/analytics/shared/session-events.ts +6 -0
  34. package/corpus/templates/assets/CHANGELOG.md +6 -0
  35. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
  36. package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  37. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
  38. package/corpus/templates/brain/app/routes/sources.tsx +1 -1
  39. package/corpus/templates/brain/vitest.config.ts +7 -0
  40. package/corpus/templates/calendar/CHANGELOG.md +12 -0
  41. package/corpus/templates/calendar/actions/create-event.ts +2 -4
  42. package/corpus/templates/calendar/actions/update-event.ts +35 -6
  43. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
  44. package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
  45. package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
  46. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  47. package/corpus/templates/clips/CHANGELOG.md +7 -20
  48. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
  49. package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
  50. package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
  51. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
  52. package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
  54. package/corpus/templates/community/qr/package.json +1 -1
  55. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
  56. package/corpus/templates/content/CHANGELOG.md +10 -0
  57. package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
  58. package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
  59. package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
  60. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
  61. package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
  62. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
  63. package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
  64. package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
  65. package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
  66. package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
  67. package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
  68. package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
  69. package/corpus/templates/content/actions/_database-utils.ts +93 -7
  70. package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
  71. package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
  72. package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
  73. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
  74. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
  75. package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
  76. package/corpus/templates/content/actions/delete-document.ts +41 -3
  77. package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
  78. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
  79. package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
  80. package/corpus/templates/content/actions/get-document.ts +2 -0
  81. package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
  82. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
  83. package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
  84. package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
  85. package/corpus/templates/content/actions/update-document.ts +139 -20
  86. package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
  87. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
  88. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
  89. package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
  90. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
  91. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
  92. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
  93. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
  94. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
  95. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
  96. package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
  97. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
  98. package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
  99. package/corpus/templates/content/app/global.css +31 -0
  100. package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
  101. package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
  102. package/corpus/templates/content/app/i18n-data.ts +1 -0
  103. package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
  104. package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
  105. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
  106. package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
  107. package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
  108. package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
  109. package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
  110. package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
  111. package/corpus/templates/content/parity/matrix.md +42 -42
  112. package/corpus/templates/content/parity/matrix.ts +3 -1
  113. package/corpus/templates/content/server/db/schema.ts +20 -0
  114. package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
  115. package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
  116. package/corpus/templates/content/server/live-copy/binding.ts +31 -0
  117. package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
  118. package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
  119. package/corpus/templates/content/server/live-copy/holds.ts +59 -0
  120. package/corpus/templates/content/server/live-copy/persist.ts +656 -0
  121. package/corpus/templates/content/server/live-copy/switch.ts +136 -0
  122. package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
  123. package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
  124. package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
  125. package/corpus/templates/content/server/live-copy/write.ts +85 -0
  126. package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
  127. package/corpus/templates/content/server/plugins/collab.ts +3 -0
  128. package/corpus/templates/content/server/plugins/db.ts +28 -0
  129. package/corpus/templates/content/shared/api.ts +30 -1
  130. package/corpus/templates/content/shared/database-item-layout.ts +54 -22
  131. package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
  132. package/corpus/templates/content/shared/og-description.ts +258 -36
  133. package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
  134. package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
  135. package/corpus/templates/content/shared/suggestion-text.ts +20 -5
  136. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
  137. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
  138. package/corpus/templates/design/CHANGELOG.md +5 -0
  139. package/corpus/templates/design/actions/get-getting-started.ts +53 -0
  140. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
  141. package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
  142. package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
  143. package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
  144. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
  145. package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
  146. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
  147. package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
  148. package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
  149. package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
  150. package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
  151. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
  152. package/corpus/templates/design/app/global.css +6 -0
  153. package/corpus/templates/design/app/i18n/en-US.ts +17 -0
  154. package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
  155. package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
  156. package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
  157. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  158. package/corpus/templates/design/app/pages/Index.tsx +28 -67
  159. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
  160. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
  161. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
  162. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
  163. package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
  164. package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
  165. package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
  166. package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
  167. package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
  168. package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
  169. package/corpus/templates/design/server/lib/getting-started.ts +267 -0
  170. package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
  171. package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
  172. package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
  173. package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
  174. package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
  175. package/corpus/templates/design/shared/design-template-presets.ts +8 -0
  176. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  177. package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
  178. package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  179. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
  180. package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
  181. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  182. package/corpus/templates/plan/CHANGELOG.md +6 -0
  183. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
  184. package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  185. package/corpus/templates/slides/CHANGELOG.md +7 -0
  186. package/corpus/templates/slides/actions/delete-deck.ts +7 -0
  187. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  188. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
  189. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  190. package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
  191. package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
  192. package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
  193. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
  194. package/package.json +1 -1
  195. package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  196. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
@@ -36,13 +36,13 @@ import {
36
36
  type TiptapComposerHandle,
37
37
  } from "@agent-native/toolkit/app/chat/composer/index";
38
38
  import { ClientOnly } from "@agent-native/toolkit/app/shared";
39
+ import { OwnershipFilter } from "@agent-native/toolkit/app/sharing/OwnershipFilter";
39
40
  import { designTemplateRetryKey } from "@shared/design-template-retry";
40
41
  import { FULL_APP_BUILDING_LAB } from "@shared/labs";
41
42
  import { derivePromptTitle } from "@shared/prompt-title";
42
43
  import {
43
44
  IconArrowRight,
44
45
  IconChecks,
45
- IconChevronDown,
46
46
  IconChevronLeft,
47
47
  IconChevronRight,
48
48
  IconPlus,
@@ -91,8 +91,6 @@ import {
91
91
  DropdownMenu,
92
92
  DropdownMenuContent,
93
93
  DropdownMenuItem,
94
- DropdownMenuRadioGroup,
95
- DropdownMenuRadioItem,
96
94
  DropdownMenuTrigger,
97
95
  } from "@/components/ui/dropdown-menu";
98
96
  import { Input } from "@/components/ui/input";
@@ -145,6 +143,7 @@ interface DesignListResult {
145
143
  }
146
144
 
147
145
  const DESIGN_PAGE_SIZE = 50;
146
+ const HOME_TEMPLATE_LIMIT = 8;
148
147
  const HOME_LIBRARY_HAS_RECENTS_STORAGE_KEY = "design-home-has-recents";
149
148
 
150
149
  function readHomeLibraryHasRecents():
@@ -593,6 +592,13 @@ export default function Index() {
593
592
  },
594
593
  [creativeContextState, t],
595
594
  );
595
+ const homeTemplates = useMemo(
596
+ () =>
597
+ templateOptions
598
+ .filter((template) => template.isBuiltIn)
599
+ .slice(0, HOME_TEMPLATE_LIMIT),
600
+ [templateOptions],
601
+ );
596
602
  const selectedTemplate =
597
603
  templateOptions.find((template) => template.id === newTemplateId) ?? null;
598
604
  const templateDesignSystemId =
@@ -608,11 +614,6 @@ export default function Index() {
608
614
  ? chosenDesignSystemId
609
615
  : templateDesignSystemId;
610
616
 
611
- const ownerFilterLabels: Record<DesignFilter, string> = {
612
- all: t("home.ownedByAnyone"),
613
- mine: t("home.ownedByMe"),
614
- "not-mine": t("home.sharedWithMe"),
615
- };
616
617
  const showAuthors = designFilter !== "mine";
617
618
  const selectedDesignCount = selectedDesignIds.size;
618
619
  const isSelectingDesigns = selectedDesignCount > 0;
@@ -735,17 +736,19 @@ export default function Index() {
735
736
  );
736
737
  }, []);
737
738
 
738
- const handleDesignFilterChange = useCallback((next: string) => {
739
- if (next !== "all" && next !== "mine" && next !== "not-mine") return;
740
- designFilterWasSelectedRef.current = true;
741
- const nextFilter: DesignFilter = next;
742
- setDesignFilter(nextFilter);
743
- writeStoredDesignFilter(nextFilter);
744
- setPage(1);
745
- setSelectedDesignIds((current) =>
746
- current.size === 0 ? current : new Set(),
747
- );
748
- }, []);
739
+ const handleDesignFilterChange = useCallback(
740
+ (next: "all" | "mine" | "shared") => {
741
+ designFilterWasSelectedRef.current = true;
742
+ const nextFilter: DesignFilter = next === "shared" ? "not-mine" : next;
743
+ setDesignFilter(nextFilter);
744
+ writeStoredDesignFilter(nextFilter);
745
+ setPage(1);
746
+ setSelectedDesignIds((current) =>
747
+ current.size === 0 ? current : new Set(),
748
+ );
749
+ },
750
+ [],
751
+ );
749
752
 
750
753
  useEffect(() => {
751
754
  if (
@@ -1564,51 +1567,10 @@ export default function Index() {
1564
1567
  }
1565
1568
  search={searchInput}
1566
1569
  recentActions={
1567
- <DropdownMenu>
1568
- <DropdownMenuTrigger asChild>
1569
- <Button
1570
- type="button"
1571
- variant="outline"
1572
- size="sm"
1573
- aria-label={ownerFilterLabels[designFilter]}
1574
- className="h-9 shrink-0 gap-2"
1575
- >
1576
- <span className="grid">
1577
- {Object.entries(ownerFilterLabels).map(
1578
- ([filter, label]) => (
1579
- <span
1580
- key={filter}
1581
- aria-hidden="true"
1582
- className="invisible col-start-1 row-start-1 whitespace-nowrap"
1583
- >
1584
- {label}
1585
- </span>
1586
- ),
1587
- )}
1588
- <span className="col-start-1 row-start-1 whitespace-nowrap">
1589
- {ownerFilterLabels[designFilter]}
1590
- </span>
1591
- </span>
1592
- <IconChevronDown className="size-4" aria-hidden="true" />
1593
- </Button>
1594
- </DropdownMenuTrigger>
1595
- <DropdownMenuContent align="start">
1596
- <DropdownMenuRadioGroup
1597
- value={designFilter}
1598
- onValueChange={handleDesignFilterChange}
1599
- >
1600
- <DropdownMenuRadioItem value="all" indicator="check">
1601
- {ownerFilterLabels.all}
1602
- </DropdownMenuRadioItem>
1603
- <DropdownMenuRadioItem value="mine" indicator="check">
1604
- {ownerFilterLabels.mine}
1605
- </DropdownMenuRadioItem>
1606
- <DropdownMenuRadioItem value="not-mine" indicator="check">
1607
- {ownerFilterLabels["not-mine"]}
1608
- </DropdownMenuRadioItem>
1609
- </DropdownMenuRadioGroup>
1610
- </DropdownMenuContent>
1611
- </DropdownMenu>
1570
+ <OwnershipFilter
1571
+ value={designFilter === "not-mine" ? "shared" : designFilter}
1572
+ onChange={handleDesignFilterChange}
1573
+ />
1612
1574
  }
1613
1575
  templates={
1614
1576
  templatesError ? (
@@ -1618,10 +1580,9 @@ export default function Index() {
1618
1580
  />
1619
1581
  ) : (
1620
1582
  <DesignTemplateLibrary
1621
- templates={templateOptions.filter(
1622
- (template) => template.isBuiltIn,
1623
- )}
1583
+ templates={homeTemplates}
1624
1584
  loading={templatesLoading}
1585
+ previewParam="previewTemplateId"
1625
1586
  />
1626
1587
  )
1627
1588
  }
@@ -23,6 +23,11 @@ import {
23
23
  waitForShaderWriteToSettle,
24
24
  } from "@/components/design/shader-source-coordination";
25
25
  import type { ElementInfo } from "@/components/design/types";
26
+ import {
27
+ runCanvasOperation,
28
+ type CanvasOperationHandle,
29
+ } from "@/lib/canvas-outcome-tracking";
30
+ import { canvasStyleKind } from "@/lib/canvas-style-kind";
26
31
  import {
27
32
  bridgeSourceIdForCodeLayerNode,
28
33
  codeLayerPatchMessage,
@@ -186,7 +191,29 @@ export function styleWriteIsRetargeted(
186
191
  );
187
192
  }
188
193
 
194
+ export type CommitVisualStylesOptions = {
195
+ runtimeApplied?: boolean;
196
+ elementInfo?: ElementInfo;
197
+ originalStyles?: Record<string, string>;
198
+ pendingUndoGestureId?: string;
199
+ preserveSelection?: boolean;
200
+ routePath?: string;
201
+ requirePersistedSourceAcceptance?: boolean;
202
+ };
203
+
189
204
  export function runCommitVisualStyles(
205
+ args: CommitVisualStylesArgs,
206
+ selector: string,
207
+ styles: Record<string, string>,
208
+ options: CommitVisualStylesOptions = {},
209
+ ): void {
210
+ runCanvasOperation("style_commit", (op) =>
211
+ commitVisualStylesWithOutcome(op, args, selector, styles, options),
212
+ );
213
+ }
214
+
215
+ function commitVisualStylesWithOutcome(
216
+ op: CanvasOperationHandle,
190
217
  {
191
218
  activeBreakpointUpperBoundPx,
192
219
  activeBreakpointWidthStateRef,
@@ -232,22 +259,15 @@ export function runCommitVisualStyles(
232
259
  }: CommitVisualStylesArgs,
233
260
  selector: string,
234
261
  styles: Record<string, string>,
235
- options: {
236
- runtimeApplied?: boolean;
237
- elementInfo?: ElementInfo;
238
- originalStyles?: Record<string, string>;
239
- pendingUndoGestureId?: string;
240
- preserveSelection?: boolean;
241
- routePath?: string;
242
- requirePersistedSourceAcceptance?: boolean;
243
- } = {},
244
- ) {
262
+ options: CommitVisualStylesOptions,
263
+ ): void {
245
264
  trace("persist", "commit-styles", {
246
265
  selector: typeof selector === "string" ? selector : null,
247
266
  props: Object.keys(styles ?? {}),
248
267
  });
249
- if (!activeFile || !canEditDesign) return;
250
- if (!canApplyContentEdit(activeFile.id)) return;
268
+ op.setStyleKind(canvasStyleKind(Object.keys(styles ?? {})));
269
+ if (!activeFile || !canEditDesign) return op.skip("no_edit_rights");
270
+ if (!canApplyContentEdit(activeFile.id)) return op.skip("edit_blocked");
251
271
  // Cross-pipeline write race guard (see shader-source-coordination.ts): a shader
252
272
  // apply/remove/knob-commit for this same file goes through a completely
253
273
  // separate round trip (read-source-file -> apply-source-edit) than this
@@ -260,15 +280,24 @@ export function runCommitVisualStyles(
260
280
  // microtask tick to the hot path or breaks the same-tick multi-property
261
281
  // composition the baseContent comment below depends on.
262
282
  if (isShaderWriteInFlight(activeFile.id)) {
263
- void waitForShaderWriteToSettle(activeFile.id).then(() => {
264
- commitVisualStyles(selector, styles, options);
265
- });
283
+ // The re-run joins this operation, so the edit counts once, by its result.
284
+ const resumeAfterShaderWrite = op.continueLater();
285
+ void waitForShaderWriteToSettle(activeFile.id).then(
286
+ () =>
287
+ resumeAfterShaderWrite(() =>
288
+ commitVisualStyles(selector, styles, options),
289
+ ),
290
+ () =>
291
+ resumeAfterShaderWrite((shaderOp) =>
292
+ shaderOp.fail("shader_write_failed"),
293
+ ),
294
+ );
266
295
  return;
267
296
  }
268
297
  const entries = Object.entries(styles).filter(
269
298
  ([, value]) => value !== undefined,
270
299
  );
271
- if (entries.length === 0) return;
300
+ if (entries.length === 0) return op.skip("empty_styles");
272
301
  const liveTargetInfo = isRunningAppSourceType(activeCanvasSourceType)
273
302
  ? (options.elementInfo ?? selectedElement ?? undefined)
274
303
  : undefined;
@@ -306,7 +335,7 @@ export function runCommitVisualStyles(
306
335
  }
307
336
  }
308
337
  onNoRenderedBox?.();
309
- return;
338
+ return op.fail("no_rendered_box");
310
339
  }
311
340
  upsertMotionKeyframesFromStyles(styles, options.elementInfo, selector);
312
341
  if (isRunningAppSourceType(activeCanvasSourceType)) {
@@ -333,7 +362,7 @@ export function runCommitVisualStyles(
333
362
  preserveSelection: options.preserveSelection,
334
363
  routePath: options.routePath,
335
364
  });
336
- return;
365
+ return op.defer("runtime_pending");
337
366
  }
338
367
  // Read through the editor's source boundary so pending linked projections
339
368
  // and synchronous local writes compose before this full-document commit.
@@ -347,7 +376,7 @@ export function runCommitVisualStyles(
347
376
  toast.error(t("designEditor.patchProof.snapshotNotLoaded"), {
348
377
  duration: 4000,
349
378
  });
350
- return;
379
+ return op.fail("snapshot_not_loaded");
351
380
  }
352
381
  const [firstProperty, firstValue] = entries[0];
353
382
  const projection =
@@ -419,27 +448,29 @@ export function runCommitVisualStyles(
419
448
  t("designEditor.componentInstances.linkedEditScopeUnsupported"),
420
449
  { duration: 4000 },
421
450
  );
422
- return;
451
+ return op.fail("linked_scope_unsupported");
423
452
  }
424
453
  if (!durableNodeId) {
425
454
  toast.error(t("designEditor.patchProof.selectorMissing"), {
426
455
  duration: 4000,
427
456
  });
428
- return;
457
+ return op.fail("selector_missing");
429
458
  }
430
459
  if (!applyLinkedComponentEdit) {
431
460
  toast.error(
432
461
  t("designEditor.componentInstances.linkedEditSourceUnsupported"),
433
462
  { duration: 4000 },
434
463
  );
435
- return;
464
+ return op.fail("linked_source_unsupported");
436
465
  }
437
466
  if (!options.requirePersistedSourceAcceptance) sendRuntimeStylePreview();
438
467
  applyLinkedComponentEdit(activeFile.id, durableNodeId, {
439
468
  kind: "styleBatch",
440
469
  values: Object.fromEntries(entries),
441
470
  });
442
- return;
471
+ // The linked edit is a queued server mutation whose failure is not
472
+ // reported back here, so this commit never counts as a success.
473
+ return op.defer("linked_edit_queued");
443
474
  }
444
475
 
445
476
  if (targetNode && lastDuplicateTransformRef.current) {
@@ -525,7 +556,13 @@ export function runCommitVisualStyles(
525
556
  error: resolutionFailure,
526
557
  createdAt: Date.now(),
527
558
  });
528
- return;
559
+ return op.fail(
560
+ targetResolution.status === "ambiguous"
561
+ ? "selector_ambiguous"
562
+ : isClientRenderedMountShell(projection)
563
+ ? "client_rendered_shell"
564
+ : "node_unresolved",
565
+ );
529
566
  }
530
567
  setPatchProofForCurrentFile({
531
568
  id: proofId,
@@ -552,7 +589,9 @@ export function runCommitVisualStyles(
552
589
  });
553
590
  if (!options.requirePersistedSourceAcceptance) sendRuntimeStylePreview();
554
591
  const committedSelection = selectedElementRef.current;
555
- commitAfterPaint(() => {
592
+ const resumeAfterPaint = op.continueLater();
593
+ commitAfterPaint(() => resumeAfterPaint(commitStylesAfterPaint));
594
+ function commitStylesAfterPaint(op: CanvasOperationHandle): void {
556
595
  const latestContent = getScreenContent(activeFile.id);
557
596
 
558
597
  // §6.4 — Breakpoint-scoped editing (Framer cascade). Reuses the
@@ -651,7 +690,7 @@ export function runCommitVisualStyles(
651
690
  ? { ...prev, status: "failed", error: failureMessage }
652
691
  : prev,
653
692
  );
654
- return;
693
+ return op.fail("patch_failed");
655
694
  }
656
695
  const resolvedNextContentBeforeFontLink = commitResolution.content;
657
696
 
@@ -696,7 +735,7 @@ export function runCommitVisualStyles(
696
735
  ? { ...previous, status: "failed", error: message }
697
736
  : previous,
698
737
  );
699
- return;
738
+ return op.fail("integrity_failed");
700
739
  }
701
740
 
702
741
  const nextProjection = buildCodeLayerProjection(resolvedNextContent, {
@@ -735,7 +774,10 @@ export function runCommitVisualStyles(
735
774
  setPatchProofForCurrentFile((prev) =>
736
775
  prev?.id === proofId ? { ...prev, status: "queued" } : prev,
737
776
  );
738
- queueFileContentSave(activeFile.id, resolvedNextContent, {
777
+ // This commit only lands once the server accepts the save, so the save's
778
+ // result decides the outcome.
779
+ const resumeAfterSave = op.continueLater();
780
+ const saved = queueFileContentSave(activeFile.id, resolvedNextContent, {
739
781
  expectedVersionHash: sourceContentHash(latestContent),
740
782
  syncCollab: false,
741
783
  immediate: true,
@@ -828,6 +870,18 @@ export function runCommitVisualStyles(
828
870
  }
829
871
  },
830
872
  });
873
+ void Promise.resolve(saved).then(
874
+ (result) =>
875
+ resumeAfterSave((saveOp) => {
876
+ if (result === "persisted") saveOp.succeed();
877
+ else if (result === "conflict") saveOp.fail("not_persisted");
878
+ else if (result === false) saveOp.skip("no_edit_rights");
879
+ // Offline, superseded, aborted or an error: the save's own count
880
+ // carries which, so this one does not claim a failure for it.
881
+ else saveOp.defer("save_unfinished");
882
+ }),
883
+ () => resumeAfterSave((saveOp) => saveOp.fail("exception")),
884
+ );
831
885
  return;
832
886
  }
833
887
 
@@ -915,6 +969,9 @@ export function runCommitVisualStyles(
915
969
  setContentRenderRevision((revision) => revision + 1);
916
970
  }
917
971
  }
972
+ // Applied on the canvas. Whether it persisted is the save's own outcome;
973
+ // only a caller that requires server acceptance waits for it.
974
+ op.succeed();
918
975
  if (options.preserveSelection) return;
919
976
  // The user may have selected something else before this commit landed.
920
977
  if (selectedElementRef.current !== committedSelection) return;
@@ -961,5 +1018,5 @@ export function runCommitVisualStyles(
961
1018
  ),
962
1019
  };
963
1020
  });
964
- });
1021
+ }
965
1022
  }
@@ -7,6 +7,12 @@ import type { QueryClient } from "@tanstack/react-query";
7
7
  import type { Dispatch, RefObject, SetStateAction } from "react";
8
8
  import { toast } from "sonner";
9
9
 
10
+ import {
11
+ recordCanvasOutcome,
12
+ runDetachedCanvasOperation,
13
+ type CanvasOperationHandle,
14
+ type CanvasOutcomeReason,
15
+ } from "@/lib/canvas-outcome-tracking";
10
16
  import type { DesignSaveOutboxEntry } from "@/lib/design-save-outbox";
11
17
  import { updateFileResultPersistedContent } from "@/lib/design-save-outbox";
12
18
  import type { PatchProofState } from "@/pages/design-editor/command-types";
@@ -23,6 +29,7 @@ import {
23
29
  isContentPatchRejection,
24
30
  isDesignSaveSuccessConflict,
25
31
  patchProofStatusAfterPersistedSave,
32
+ type DesignSaveFailureKind,
26
33
  } from "@/pages/design-editor/save-failure";
27
34
  import { threeWayMergeContent } from "@/pages/design-editor/three-way-merge";
28
35
 
@@ -315,6 +322,15 @@ export function runFileContentSaveKeepalive(
315
322
  .catch(() => {});
316
323
  }
317
324
 
325
+ const SAVE_FAILURE_REASONS: Record<DesignSaveFailureKind, CanvasOutcomeReason> =
326
+ {
327
+ conflict: "conflict",
328
+ "intentional-abort": "aborted",
329
+ "invalid-html": "invalid_html",
330
+ offline: "offline",
331
+ other: "error",
332
+ };
333
+
318
334
  export function runSaveFileContent(
319
335
  {
320
336
  acknowledgeOutboxEntry,
@@ -339,7 +355,10 @@ export function runSaveFileContent(
339
355
  outboxJournalPromise?: Promise<boolean>,
340
356
  onPersistedContent?: (acceptedContent: string) => void,
341
357
  ): Promise<FileContentSaveCompletion> {
342
- if (!canEditDesignRef.current) return Promise.resolve("failed");
358
+ if (!canEditDesignRef.current) {
359
+ recordCanvasOutcome("save", "skipped", "no_edit_rights", { designId });
360
+ return Promise.resolve("failed");
361
+ }
343
362
  markPendingLocalFileContent(
344
363
  pending.id,
345
364
  pending.content,
@@ -420,6 +439,7 @@ export function runSaveFileContent(
420
439
  const attempt = async (
421
440
  pending: FileContentSaveRequest,
422
441
  mergesLeft: number,
442
+ op: CanvasOperationHandle,
423
443
  ): Promise<FileContentSaveCompletion> => {
424
444
  // An identity migration is disposable. Never send a queued old snapshot
425
445
  // after a newer source publication or user edit has replaced it.
@@ -429,6 +449,7 @@ export function runSaveFileContent(
429
449
  ) {
430
450
  if (queuedOutboxEntry)
431
451
  void acknowledgeOutboxEntry(queuedOutboxEntry).catch(() => {});
452
+ op.skip("superseded");
432
453
  return "failed";
433
454
  }
434
455
  try {
@@ -486,6 +507,7 @@ export function runSaveFileContent(
486
507
  ) {
487
508
  if (outboxEntry)
488
509
  void acknowledgeOutboxEntry(outboxEntry).catch(() => {});
510
+ op.skip("superseded");
489
511
  return "failed";
490
512
  }
491
513
  const resultInfo = result as
@@ -613,6 +635,8 @@ export function runSaveFileContent(
613
635
  }
614
636
  : { ...prev, status };
615
637
  });
638
+ if (persistedContentMatches) op.succeed();
639
+ else op.fail("stale_mirror");
616
640
  return persistedContentMatches ? "persisted" : "conflict";
617
641
  } catch (error) {
618
642
  if (
@@ -621,6 +645,7 @@ export function runSaveFileContent(
621
645
  ) {
622
646
  if (queuedOutboxEntry)
623
647
  void acknowledgeOutboxEntry(queuedOutboxEntry).catch(() => {});
648
+ op.skip("superseded");
624
649
  return "failed";
625
650
  }
626
651
  const failureKind = classifyDesignSaveFailure(error, navigator.onLine);
@@ -630,7 +655,7 @@ export function runSaveFileContent(
630
655
  pending.identityMigrationSourceContent === undefined
631
656
  ) {
632
657
  const mergedRequest = await mergeIntoLiveContent(pending);
633
- if (mergedRequest) return attempt(mergedRequest, mergesLeft - 1);
658
+ if (mergedRequest) return attempt(mergedRequest, mergesLeft - 1, op);
634
659
  }
635
660
  if (failureKind === "conflict") {
636
661
  rollbackPendingLocalFileContent(pending.id, pending.content);
@@ -671,6 +696,10 @@ export function runSaveFileContent(
671
696
  }
672
697
  : prev,
673
698
  );
699
+ if (failureKind === "intentional-abort") op.skip("aborted");
700
+ else if (failureKind === "offline" && outboxEntryJournaled) {
701
+ op.defer("offline_queued");
702
+ } else op.fail(SAVE_FAILURE_REASONS[failureKind]);
674
703
  return failureKind === "offline" && outboxEntryJournaled
675
704
  ? "retryable"
676
705
  : failureKind === "conflict"
@@ -681,7 +710,13 @@ export function runSaveFileContent(
681
710
  const previous = fileSaveChainsRef.current[pending.id] ?? Promise.resolve();
682
711
  const current = previous
683
712
  .catch(() => {})
684
- .then(() => attempt(pending, MAX_CONFLICT_MERGES));
713
+ .then(() =>
714
+ runDetachedCanvasOperation(
715
+ "save",
716
+ (op) => attempt(pending, MAX_CONFLICT_MERGES, op),
717
+ { designId },
718
+ ),
719
+ );
685
720
  const chain = current.then(() => {});
686
721
  fileSaveChainsRef.current[pending.id] = chain;
687
722
  void current.finally(() => {
@@ -40,6 +40,8 @@ import {
40
40
  sendBuilderSelectionContext,
41
41
  } from "@/lib/builder-host-chat";
42
42
  import { isBuilderHostEmbed } from "@/lib/builder-host-origin";
43
+ import { leaveCanvasOutcomeDesign } from "@/lib/canvas-load-outcome";
44
+ import { setCanvasOutcomeContext } from "@/lib/canvas-outcome-tracking";
43
45
  import { isEmbedChromeRequested } from "@/lib/embed-chrome";
44
46
  import { SHELL_DESIGN_ID } from "@/lib/shell-design";
45
47
 
@@ -87,6 +89,13 @@ export function useEditorCore() {
87
89
  const isSignedIn = Boolean(session?.email);
88
90
  const sessionResolved = !sessionLoading;
89
91
  const location = useLocation();
92
+ useEffect(() => {
93
+ setCanvasOutcomeContext({
94
+ designId: id,
95
+ signedIn: sessionResolved ? isSignedIn : undefined,
96
+ });
97
+ }, [id, isSignedIn, sessionResolved]);
98
+ useEffect(() => leaveCanvasOutcomeDesign, []);
90
99
  usePerformanceBufferGuard();
91
100
  const initialEditorUrlRef = useRef<{
92
101
  designId: string | undefined;
@@ -35,6 +35,7 @@ import { useDesignSystemWorkflows } from "@/hooks/use-design-system-workflows";
35
35
  import { useDesignSystems } from "@/hooks/use-design-systems";
36
36
  import { useEditorPreferences } from "@/hooks/use-editor-preferences";
37
37
  import { isHostedInProcess } from "@/host/design-host-channel";
38
+ import { recordSaveReplayOutcomes } from "@/lib/canvas-outcome-tracking";
38
39
  import {
39
40
  acknowledgeDesignSaveOutboxEntry,
40
41
  createDesignSaveOutboxEntry,
@@ -315,6 +316,7 @@ export function useEditorFilesAndSaving({
315
316
  designId: id,
316
317
  actorScope: designSaveActorScope,
317
318
  });
319
+ recordSaveReplayOutcomes(result, id);
318
320
  if (result.rebased.length > 0) {
319
321
  toast.error(t("designEditor.toasts.saveConflict"), {
320
322
  id: "design-save-conflict:outbox",
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-16
4
+ ---
5
+
6
+ Design pages use localized visibility badges and a shared ownership filter.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-10-21
4
+ ---
5
+
6
+ A Getting started checklist at the bottom of the sidebar tracks your first design, brand, sharing, teammates, and templates, and checks steps off as you finish them
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-21
4
+ ---
5
+
6
+ Templates now open a read-only preview from the card, and the home library shows more starters, including dashboard, messaging, task board and design studio screens.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-22
4
+ ---
5
+
6
+ Collapsed shader layers no longer interrupt other shaders on the canvas
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-22
4
+ ---
5
+
6
+ Shader fills keep rendering when other screen content cannot be captured
@@ -29,6 +29,18 @@
29
29
  "maxApproximated": 0,
30
30
  "sourceHash": "ed09aaa70a136c0b2c479da951e9d58777e9f626b791dba45f74f9c83356b15c"
31
31
  },
32
+ "preset-analytics-dashboard": {
33
+ "maxDiffPercent": 1.229,
34
+ "maxOmitted": 0,
35
+ "maxApproximated": 0,
36
+ "sourceHash": "4906ac5a06738a6158fbf367b52bf690d1e339bb09367f916a78e1bbabc1ffbe"
37
+ },
38
+ "preset-design-studio": {
39
+ "maxDiffPercent": 6.815,
40
+ "maxOmitted": 0,
41
+ "maxApproximated": 0,
42
+ "sourceHash": "8b9b89e2a42d5e6fead4d642afe9c6dd60c6d50a7d367c893689bedea8a8b27b"
43
+ },
32
44
  "preset-display-ad": {
33
45
  "maxDiffPercent": 9.364,
34
46
  "maxOmitted": 0,
@@ -83,6 +95,12 @@
83
95
  "maxApproximated": 0,
84
96
  "sourceHash": "69ffb5a9b551bf1a21177334be2c799a97cdd803a3fc8ce97383ab4eb7058601"
85
97
  },
98
+ "preset-messaging-app": {
99
+ "maxDiffPercent": 1.049,
100
+ "maxOmitted": 0,
101
+ "maxApproximated": 0,
102
+ "sourceHash": "bbd041c27252ed44bb3b0c5034c7ac65bac77b658367900410f672b137b6c36e"
103
+ },
86
104
  "preset-one-pager": {
87
105
  "maxDiffPercent": 0.481,
88
106
  "maxOmitted": 0,
@@ -95,6 +113,12 @@
95
113
  "maxApproximated": 0,
96
114
  "sourceHash": "08f864b21b806752f7052096229593809d49e73d63e3ab8b7e4e0ebc50346cc0"
97
115
  },
116
+ "preset-project-tasks": {
117
+ "maxDiffPercent": 13.965,
118
+ "maxOmitted": 0,
119
+ "maxApproximated": 0,
120
+ "sourceHash": "719e35f7cf11e46e1e16b8aa1c71aac3df32ebffe9c5e1c2f3b07e1fca1866c4"
121
+ },
98
122
  "preset-social-brutalist": {
99
123
  "maxDiffPercent": 2.866,
100
124
  "maxOmitted": 0,