@agent-native/core 0.179.0-nightly-20260914194938 → 0.179.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 (28) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/CHANGELOG.md +32 -0
  3. package/corpus/templates/clips/CHANGELOG.md +19 -0
  4. package/corpus/templates/content/CHANGELOG.md +20 -0
  5. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +16 -7
  6. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -55
  7. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +3 -3
  8. package/corpus/templates/content/app/components/editor/suggestions/markdown-operation.ts +98 -2
  9. package/corpus/templates/content/changelog/2026-09-13-suggestion-previews-and-highlights-stay-aligned-near-lists.md +5 -0
  10. package/corpus/templates/content/shared/suggestion-text.ts +20 -4
  11. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +17 -5
  12. package/corpus/templates/design/.generated/bridge/source-location.generated.ts +17 -5
  13. package/corpus/templates/design/CHANGELOG.md +60 -0
  14. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +39 -7
  15. package/corpus/templates/design/app/components/design/bridge/source-location.bridge.ts +37 -4
  16. package/corpus/templates/design/app/pages/design-editor/source-location.ts +18 -0
  17. package/corpus/templates/design/changelog/2026-09-14-selecting-an-element-in-a-live-react-app-now-anchors-to-the-.md +6 -0
  18. package/corpus/templates/factory/CHANGELOG.md +19 -0
  19. package/corpus/templates/forms/CHANGELOG.md +6 -0
  20. package/corpus/templates/mail/CHANGELOG.md +34 -0
  21. package/corpus/templates/slides/CHANGELOG.md +15 -0
  22. package/dist/automation/index.d.ts +1 -1
  23. package/dist/notifications/routes.d.ts +1 -1
  24. package/dist/observability/routes.d.ts +5 -5
  25. package/dist/progress/routes.d.ts +1 -1
  26. package/dist/resources/handlers.d.ts +1 -1
  27. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  28. package/package.json +4 -4
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 9299
34
+ - template files: 9301
@@ -3,6 +3,38 @@
3
3
  All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-12
7
+
8
+ ### Improved
9
+
10
+ - Sign-in panel moves clear of product content
11
+
12
+ ### Fixed
13
+
14
+ - Dashboard pages no longer refresh after unrelated agent actions
15
+
16
+ ## 2026-09-11
17
+
18
+ ### Fixed
19
+
20
+ - Analytics keeps event receipts until BigQuery confirms delivery
21
+ - Ask sidebar controls stay on one row for a cleaner chat list.
22
+ - Coverage-sensitive searches now return a clearly labeled partial answer when a full provider scan cannot finish
23
+ - Retention metrics stay visibly unknown when analytics event coverage has gaps
24
+ - The 7d Rolling Signed-In Return Rate chart no longer drops to 0% for the newest days; a point appears only once its 7-day or 14-day return window has fully elapsed, and the 7d/30d ranges now render.
25
+
26
+ ## 2026-09-10
27
+
28
+ ### Improved
29
+
30
+ - Dashboards render charts immediately instead of animating every panel on load, so large dashboards no longer freeze the page.
31
+
32
+ ### Fixed
33
+
34
+ - Agent context links for dashboards, analyses, and session replays now work for external agents instead of returning Unauthorized
35
+ - BigQuery active-user charts now show the full selected history.
36
+ - Chart editor dialogs stay centered when editing dashboards
37
+
6
38
  ## 2026-09-09
7
39
 
8
40
  ### Improved
@@ -3,10 +3,29 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-13
7
+
8
+ ### Improved
9
+
10
+ - Members now know to ask an organization admin to create the first Space.
11
+
12
+ ### Fixed
13
+
14
+ - Dictate stays available by default in Clips Desktop.
15
+
16
+ ## 2026-09-12
17
+
18
+ ### Fixed
19
+
20
+ - Archive and Move to trash actions now use clear, action-specific labels in Clips.
21
+ - Clips sign-in prompts now focus on the requested action with larger buttons.
22
+ - Meetings stays in navigation and links to its Labs setting when unavailable.
23
+
6
24
  ## 2026-09-10
7
25
 
8
26
  ### Fixed
9
27
 
28
+ - Deleting your only organization no longer leaves a "Couldn't load organization branding" error above the create-an-organization card in Settings.
10
29
  - Builder and other MCP connections now open setup in a new tab.
11
30
 
12
31
  ## 2026-09-09
@@ -3,6 +3,26 @@
3
3
  All notable user-facing changes to Agent-Native Content are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-14
7
+
8
+ ### Fixed
9
+
10
+ - The block drag-handle menu now always opens beside the block instead of sometimes jumping to the corner of the window.
11
+ - Pasting a checklist from Notion or GitHub now keeps its checkboxes instead of turning into plain bullets.
12
+ - Asking the agent about Notion no longer reports "not connected" when the Notion MCP server is connected — it now names the account connection and the MCP connection separately.
13
+
14
+ ## 2026-09-12
15
+
16
+ ### Changed
17
+
18
+ - Databases are now called **collections** across Content — same pages, views, and sharing, friendlier name.
19
+
20
+ ## 2026-09-10
21
+
22
+ ### Fixed
23
+
24
+ - Opening a page link your account cannot read now lands on a page you can open and says why, instead of stopping on a "Document unavailable" screen — so a brand-new account no longer arrives on a dead end after signing up.
25
+
6
26
  ## 2026-09-09
7
27
 
8
28
  ### Added
@@ -248,6 +248,12 @@ export function findPendingCommentOffset(
248
248
 
249
249
  type ThreadLayoutIdentity = { threadId: string; comments: readonly unknown[] };
250
250
 
251
+ // Stable identities: a fresh `[]` default re-keys every downstream useMemo,
252
+ // which rebuilds the anchor observers on every render.
253
+ const NO_THREADS: CommentThread[] = [];
254
+ const NO_SUGGESTIONS: ResourceSuggestion[] = [];
255
+ const NO_DRAFT_SUGGESTIONS: DraftSuggestion[] = [];
256
+
251
257
  export function estimateThreadCardHeight(thread: ThreadLayoutIdentity) {
252
258
  return 80 + Math.max(0, thread.comments.length - 1) * 44;
253
259
  }
@@ -677,7 +683,7 @@ export function CommentsSidebar({
677
683
  compact = false,
678
684
  replyDrafts,
679
685
  documentId,
680
- threads = [],
686
+ threads = NO_THREADS,
681
687
  isLoading = false,
682
688
  pendingComment,
683
689
  pendingTargetValid = true,
@@ -700,8 +706,8 @@ export function CommentsSidebar({
700
706
  canResolve = false,
701
707
  alignToAnchors = true,
702
708
  forceVisible = false,
703
- suggestions = [],
704
- draftSuggestions = [],
709
+ suggestions = NO_SUGGESTIONS,
710
+ draftSuggestions = NO_DRAFT_SUGGESTIONS,
705
711
  onMaterializeDraft,
706
712
  canDecideSuggestions = false,
707
713
  decidingSuggestion = false,
@@ -1105,13 +1111,16 @@ export function CommentsSidebar({
1105
1111
  [],
1106
1112
  );
1107
1113
 
1114
+ // Only the presence of a pending comment moves the lane; its draft text
1115
+ // changes on every keystroke and must not re-key the anchor observers.
1116
+ const hasPendingComment = !!pendingComment;
1108
1117
  const recomputeOffsets = useCallback(() => {
1109
1118
  const container = scrollContainerRef?.current ?? null;
1110
1119
  if (!container || inlineThreads.length === 0) {
1111
1120
  setThreadPositions((prev) => (prev.size === 0 ? prev : new Map()));
1112
1121
  setPendingOffset((prev) => {
1113
1122
  const next =
1114
- pendingComment && alignToAnchors
1123
+ hasPendingComment && alignToAnchors
1115
1124
  ? findPendingCommentOffset(container, sidebarRef.current)
1116
1125
  : null;
1117
1126
  return prev === next ? prev : next;
@@ -1131,7 +1140,7 @@ export function CommentsSidebar({
1131
1140
  if (position) positions.set(thread.threadId, position);
1132
1141
  }
1133
1142
  const nextPendingOffset =
1134
- pendingComment && alignToAnchors
1143
+ hasPendingComment && alignToAnchors
1135
1144
  ? findPendingCommentOffset(container, layoutContainer)
1136
1145
  : null;
1137
1146
  setThreadPositions((prev) => {
@@ -1152,7 +1161,7 @@ export function CommentsSidebar({
1152
1161
  setPendingOffset((prev) =>
1153
1162
  prev === nextPendingOffset ? prev : nextPendingOffset,
1154
1163
  );
1155
- }, [alignToAnchors, inlineThreads, pendingComment, scrollContainerRef]);
1164
+ }, [alignToAnchors, inlineThreads, hasPendingComment, scrollContainerRef]);
1156
1165
 
1157
1166
  useEffect(() => {
1158
1167
  const container = scrollContainerRef?.current ?? null;
@@ -1186,7 +1195,7 @@ export function CommentsSidebar({
1186
1195
  window.removeEventListener("resize", schedule);
1187
1196
  };
1188
1197
  // eslint-disable-next-line react-hooks/exhaustive-deps
1189
- }, [openThreadKey, pendingComment, recomputeOffsets]);
1198
+ }, [openThreadKey, hasPendingComment, recomputeOffsets]);
1190
1199
 
1191
1200
  useEffect(() => {
1192
1201
  const openIds = new Set(inlineThreads.map((thread) => thread.threadId));
@@ -26,6 +26,7 @@ import {
26
26
  import type { ResourceSuggestion } from "@agent-native/core/review";
27
27
  import { normalizeDocumentTitle } from "@agent-native/core/shared";
28
28
  import type { Document, DocumentSyncStatus } from "@shared/api";
29
+ import { canonicalizeNfm } from "@shared/nfm";
29
30
  import {
30
31
  SuggestionFormattingMappingError,
31
32
  suggestionMarkedSourceRanges,
@@ -76,7 +77,7 @@ import {
76
77
  SheetTitle,
77
78
  } from "@/components/ui/sheet";
78
79
  import { flushDocumentPropertyWrites } from "@/hooks/document-property-persistence";
79
- import { useComments } from "@/hooks/use-comments";
80
+ import { useComments, type CommentThread } from "@/hooks/use-comments";
80
81
  import {
81
82
  useCreateContentDatabase,
82
83
  useDeleteContentDatabase,
@@ -207,6 +208,14 @@ import type {
207
208
  VisualEditorPersistenceController,
208
209
  } from "./VisualEditor";
209
210
 
211
+ const NO_COMMENT_THREADS: CommentThread[] = [];
212
+
213
+ export function documentEditorCommentThreads(
214
+ threads: CommentThread[] | null | undefined,
215
+ ) {
216
+ return threads ?? NO_COMMENT_THREADS;
217
+ }
218
+
210
219
  const TAB_ID = generateTabId();
211
220
 
212
221
  export function applyHistoryToDocumentBody(
@@ -325,6 +334,19 @@ export function suggestionPresentation(
325
334
  }
326
335
  const range = resolveMarkdownSuggestionRange(currentMarkdown, operation);
327
336
  if (!range) return null;
337
+ const editorMarkdown = canonicalizeNfm(currentMarkdown);
338
+ const currentText = currentMarkdown.slice(range.from, range.to);
339
+ const editorRange = resolveMarkdownSuggestionRange(editorMarkdown, {
340
+ before: { markdown: currentMarkdown, changedText: currentText },
341
+ after: { markdown: currentMarkdown, changedText: currentText },
342
+ anchor: {
343
+ from: range.from,
344
+ to: range.to,
345
+ prefix: currentMarkdown.slice(Math.max(0, range.from - 32), range.from),
346
+ suffix: currentMarkdown.slice(range.to, range.to + 32),
347
+ },
348
+ });
349
+ if (!editorRange) return null;
328
350
  return {
329
351
  id: suggestion.id,
330
352
  kind: operation.kind as VisualEditorSuggestion["kind"],
@@ -341,9 +363,12 @@ export function suggestionPresentation(
341
363
  to: operationAnchor.from + after.changedText.length,
342
364
  },
343
365
  anchor: {
344
- from: range.from,
345
- prefix: currentMarkdown.slice(Math.max(0, range.from - 32), range.from),
346
- suffix: currentMarkdown.slice(range.to, range.to + 32),
366
+ from: editorRange.from,
367
+ prefix: editorMarkdown.slice(
368
+ Math.max(0, editorRange.from - 32),
369
+ editorRange.from,
370
+ ),
371
+ suffix: editorMarkdown.slice(editorRange.to, editorRange.to + 32),
347
372
  },
348
373
  presentation: "canonical",
349
374
  };
@@ -1103,6 +1128,27 @@ export function positionAnchoredCommentCard({
1103
1128
  };
1104
1129
  }
1105
1130
 
1131
+ export type AnchoredCommentPosition = {
1132
+ left: number;
1133
+ top: number;
1134
+ width: number;
1135
+ placement: "above" | "below";
1136
+ };
1137
+
1138
+ export function sameAnchoredCommentPosition(
1139
+ left: AnchoredCommentPosition | null,
1140
+ right: AnchoredCommentPosition | null,
1141
+ ) {
1142
+ if (left === right) return true;
1143
+ if (!left || !right) return false;
1144
+ return (
1145
+ left.left === right.left &&
1146
+ left.top === right.top &&
1147
+ left.width === right.width &&
1148
+ left.placement === right.placement
1149
+ );
1150
+ }
1151
+
1106
1152
  export function pendingCommentTargetMatches(
1107
1153
  marked: Iterable<Pick<Element, "textContent">>,
1108
1154
  quotedText: string,
@@ -3758,8 +3804,13 @@ function PageEditorSessionBody({
3758
3804
  const replyDrafts = useCommentReplyDrafts(documentId, session?.email);
3759
3805
  const [pendingCommentTargetValid, setPendingCommentTargetValid] =
3760
3806
  useState(true);
3807
+ // Keyed by the selection, never by the draft text: re-running this on each
3808
+ // keystroke blanks the target back to invalid for a frame, which shows the
3809
+ // "select text" alert and disables Submit inside the open composer.
3810
+ const pendingCommentTargetId = pendingComment?.id ?? null;
3811
+ const pendingCommentQuotedText = pendingComment?.quotedText ?? null;
3761
3812
  useLayoutEffect(() => {
3762
- if (!pendingComment) {
3813
+ if (!pendingCommentTargetId || pendingCommentQuotedText === null) {
3763
3814
  setPendingCommentTargetValid(true);
3764
3815
  return;
3765
3816
  }
@@ -3777,7 +3828,7 @@ function PageEditorSessionBody({
3777
3828
  ".comment-highlight--pending",
3778
3829
  );
3779
3830
  setPendingCommentTargetValid(
3780
- pendingCommentTargetMatches(marked, pendingComment.quotedText),
3831
+ pendingCommentTargetMatches(marked, pendingCommentQuotedText),
3781
3832
  );
3782
3833
  });
3783
3834
  };
@@ -3795,7 +3846,7 @@ function PageEditorSessionBody({
3795
3846
  cancelAnimationFrame(frame);
3796
3847
  observer.disconnect();
3797
3848
  };
3798
- }, [pendingComment]);
3849
+ }, [pendingCommentTargetId, pendingCommentQuotedText]);
3799
3850
  const [focusSuggestionId, setFocusSuggestionId] = useState<string | null>(
3800
3851
  null,
3801
3852
  );
@@ -3806,12 +3857,8 @@ function PageEditorSessionBody({
3806
3857
  const documentLayoutRef = useRef<HTMLDivElement>(null);
3807
3858
  const commentLaneRef = useRef<HTMLElement>(null);
3808
3859
  const anchoredCommentRef = useRef<HTMLElement>(null);
3809
- const [anchoredCommentPosition, setAnchoredCommentPosition] = useState<{
3810
- left: number;
3811
- top: number;
3812
- width: number;
3813
- placement: "above" | "below";
3814
- } | null>(null);
3860
+ const [anchoredCommentPosition, setAnchoredCommentPosition] =
3861
+ useState<AnchoredCommentPosition | null>(null);
3815
3862
  const [commentLaneOffset, setCommentLaneOffset] = useState(0);
3816
3863
  const hasUtilityRailSpace = useElementMinWidth(documentLayoutRef, 960);
3817
3864
  const hasInlineCommentSpace = useElementMinWidth(documentLayoutRef, 1088);
@@ -3852,45 +3899,6 @@ function PageEditorSessionBody({
3852
3899
  const hasFocusedCommentReply =
3853
3900
  replyDrafts.focus.current?.documentId === documentId;
3854
3901
 
3855
- useLayoutEffect(() => {
3856
- if (!pendingComment) {
3857
- setPendingCommentTargetValid(true);
3858
- return;
3859
- }
3860
- const scrollContainer = scrollContainerRef.current;
3861
- if (!scrollContainer) {
3862
- setPendingCommentTargetValid(false);
3863
- return;
3864
- }
3865
-
3866
- let frame = 0;
3867
- const update = () => {
3868
- cancelAnimationFrame(frame);
3869
- frame = requestAnimationFrame(() => {
3870
- const marked = scrollContainer.querySelectorAll(
3871
- ".comment-highlight--pending",
3872
- );
3873
- setPendingCommentTargetValid(
3874
- pendingCommentTargetMatches(marked, pendingComment.quotedText),
3875
- );
3876
- });
3877
- };
3878
- setPendingCommentTargetValid(false);
3879
- update();
3880
- const observer = new MutationObserver(update);
3881
- observer.observe(scrollContainer, {
3882
- childList: true,
3883
- subtree: true,
3884
- characterData: true,
3885
- attributes: true,
3886
- attributeFilter: ["class"],
3887
- });
3888
- return () => {
3889
- cancelAnimationFrame(frame);
3890
- observer.disconnect();
3891
- };
3892
- }, [pendingComment]);
3893
-
3894
3902
  useEffect(() => {
3895
3903
  if (utilityPanel) setLastUtilityPanel(utilityPanel);
3896
3904
  }, [utilityPanel]);
@@ -4102,6 +4110,13 @@ function PageEditorSessionBody({
4102
4110
  ) as HTMLElement | null;
4103
4111
  if (!scrollContainer || !scrollContent) return;
4104
4112
  let frame = 0;
4113
+ // The observers below watch the card itself, and the placement is derived
4114
+ // from the card's own measured height. Committing an unchanged position
4115
+ // would feed that measurement back in as a fresh re-render every frame.
4116
+ const commit = (next: AnchoredCommentPosition) =>
4117
+ setAnchoredCommentPosition((previous) =>
4118
+ sameAnchoredCommentPosition(previous, next) ? previous : next,
4119
+ );
4105
4120
  const update = () => {
4106
4121
  cancelAnimationFrame(frame);
4107
4122
  frame = requestAnimationFrame(() => {
@@ -4123,7 +4138,7 @@ function PageEditorSessionBody({
4123
4138
  : ".comment-highlight--pending",
4124
4139
  ) as HTMLElement | null;
4125
4140
  if (!marked) {
4126
- setAnchoredCommentPosition(
4141
+ commit(
4127
4142
  positionUnanchoredCommentCard({
4128
4143
  containerRect: scrollContent.getBoundingClientRect(),
4129
4144
  boundaryRect: scrollContainer.getBoundingClientRect(),
@@ -4139,7 +4154,7 @@ function PageEditorSessionBody({
4139
4154
  const boundaryRect = scrollContainer.getBoundingClientRect();
4140
4155
  const cardHeight =
4141
4156
  anchoredCommentRef.current?.getBoundingClientRect().height ?? 180;
4142
- setAnchoredCommentPosition(
4157
+ commit(
4143
4158
  positionAnchoredCommentCard({
4144
4159
  anchorRect,
4145
4160
  containerRect,
@@ -4431,7 +4446,7 @@ function PageEditorSessionBody({
4431
4446
  compact={!hasInlineCommentSpace}
4432
4447
  replyDrafts={replyDrafts}
4433
4448
  documentId={documentId}
4434
- threads={threads ?? []}
4449
+ threads={documentEditorCommentThreads(threads)}
4435
4450
  isLoading={commentsLoading}
4436
4451
  pendingComment={pendingComment}
4437
4452
  pendingTargetValid={pendingCommentTargetValid}
@@ -6,7 +6,7 @@ import { SuggestionFormattingMappingError } from "@shared/suggestion-formatting"
6
6
 
7
7
  import {
8
8
  draftSuggestionAnchors,
9
- markdownSuggestionOperationsForReplacements,
9
+ markdownSuggestionOperationsForEditorRevision,
10
10
  } from "./markdown-operation";
11
11
 
12
12
  export function canonicalSuggestionRevision(
@@ -190,14 +190,14 @@ export function suggestionDraftOperations(
190
190
  session: SuggestionDraftSession,
191
191
  draftContent: string,
192
192
  ) {
193
- const operations = markdownSuggestionOperationsForReplacements({
193
+ const operations = markdownSuggestionOperationsForEditorRevision({
194
194
  before: session.baseContent,
195
195
  after: draftContent,
196
196
  replacements: session.replacementIntents ?? [],
197
197
  });
198
198
  if (!session.existingSuggestion || operations.length < 2) return operations;
199
199
  // An amendment retains the saved proposal's single-operation identity.
200
- return markdownSuggestionOperationsForReplacements({
200
+ return markdownSuggestionOperationsForEditorRevision({
201
201
  before: session.baseContent,
202
202
  after: draftContent,
203
203
  replacements: [
@@ -1,8 +1,10 @@
1
+ import { canonicalizeNfm } from "@shared/nfm";
1
2
  import {
2
3
  suggestionFormattingChanges,
3
4
  suggestionMarkedSourceRanges,
4
5
  SuggestionFormattingMappingError,
5
6
  } from "@shared/suggestion-formatting";
7
+ import { resolveMarkdownSuggestionRange } from "@shared/suggestion-rebase";
6
8
 
7
9
  export type MarkdownSuggestionOperation = {
8
10
  ordinal: number;
@@ -551,14 +553,108 @@ export function markdownSuggestionOperationsForReplacements(input: {
551
553
  return result;
552
554
  }
553
555
 
556
+ export function markdownSuggestionOperationsForEditorRevision(input: {
557
+ before: string;
558
+ after: string;
559
+ replacements: ReadonlyArray<{ from: number; to: number }>;
560
+ }): MarkdownSuggestionOperation[] {
561
+ const editorBefore = canonicalizeNfm(input.before);
562
+ const replacements = input.replacements.map(({ from, to }) => {
563
+ const changedText = input.before.slice(from, to);
564
+ const range = resolveMarkdownSuggestionRange(editorBefore, {
565
+ before: { markdown: input.before, changedText },
566
+ after: { markdown: input.before, changedText },
567
+ anchor: {
568
+ from,
569
+ to,
570
+ prefix: input.before.slice(Math.max(0, from - 32), from),
571
+ suffix: input.before.slice(to, to + 32),
572
+ },
573
+ });
574
+ if (!range) throw new SuggestionFormattingMappingError();
575
+ return range;
576
+ });
577
+ return markdownSuggestionOperationsForReplacements({
578
+ before: editorBefore,
579
+ after: input.after,
580
+ replacements,
581
+ }).map((operation, ordinal) => {
582
+ const range = resolveMarkdownSuggestionRange(input.before, operation);
583
+ if (!range) throw new SuggestionFormattingMappingError();
584
+ return operationForChange(
585
+ input.before,
586
+ range.from,
587
+ range.to,
588
+ operation.after.changedText,
589
+ ordinal,
590
+ );
591
+ });
592
+ }
593
+
554
594
  export function draftSuggestionAnchors(
555
595
  operations: readonly MarkdownSuggestionOperation[],
556
596
  draft: string,
557
597
  ): MarkdownSuggestionOperation["anchor"][] {
598
+ const canonical = operations.every(
599
+ (operation) =>
600
+ canonicalizeNfm(operation.after.markdown) === operation.after.markdown,
601
+ );
602
+ let proposedRaw = operations[0]?.before.markdown ?? draft;
603
+ for (const operation of [...operations].reverse()) {
604
+ proposedRaw =
605
+ proposedRaw.slice(0, operation.anchor.from) +
606
+ operation.after.changedText +
607
+ proposedRaw.slice(operation.anchor.to);
608
+ }
609
+ const parts = canonical ? null : diffParts(proposedRaw, draft);
610
+ if (!canonical && !parts) throw new SuggestionFormattingMappingError();
611
+ const boundaryMap = new Array<number>(proposedRaw.length + 1);
612
+ let rawOffset = 0;
613
+ let draftOffset = 0;
614
+ boundaryMap[0] = 0;
615
+ for (const part of parts ?? []) {
616
+ if (part.type === "insert") {
617
+ draftOffset += part.text.length;
618
+ boundaryMap[rawOffset] = draftOffset;
619
+ continue;
620
+ }
621
+ for (let index = 0; index < part.text.length; index += 1) {
622
+ rawOffset += 1;
623
+ if (part.type === "equal") draftOffset += 1;
624
+ boundaryMap[rawOffset] = draftOffset;
625
+ }
626
+ }
627
+ if (
628
+ !canonical &&
629
+ (rawOffset !== proposedRaw.length || draftOffset !== draft.length)
630
+ )
631
+ throw new SuggestionFormattingMappingError();
632
+
558
633
  let delta = 0;
559
634
  return operations.map((operation) => {
560
- const from = operation.anchor.from + delta;
561
- const to = from + operation.after.changedText.length;
635
+ if (canonical) {
636
+ const from = operation.anchor.from + delta;
637
+ const to = from + operation.after.changedText.length;
638
+ delta +=
639
+ operation.after.changedText.length -
640
+ operation.before.changedText.length;
641
+ return {
642
+ from,
643
+ to,
644
+ prefix: draft.slice(Math.max(0, from - 32), from),
645
+ suffix: draft.slice(to, to + 32),
646
+ };
647
+ }
648
+ const rawFrom = operation.anchor.from + delta;
649
+ const rawTo = rawFrom + operation.after.changedText.length;
650
+ const from = boundaryMap[rawFrom];
651
+ const to = boundaryMap[rawTo];
652
+ if (
653
+ from === undefined ||
654
+ to === undefined ||
655
+ draft.slice(from, to) !== operation.after.changedText
656
+ )
657
+ throw new SuggestionFormattingMappingError();
562
658
  delta +=
563
659
  operation.after.changedText.length - operation.before.changedText.length;
564
660
  return {
@@ -0,0 +1,5 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-13
4
+ ---
5
+ Suggestion previews and highlights stay aligned when editing text near lists.
@@ -1,5 +1,6 @@
1
- import { docToNfm, nfmToDoc, type PMNode } from "./nfm";
1
+ import { canonicalizeNfm, docToNfm, nfmToDoc, type PMNode } from "./nfm";
2
2
  import { suggestionFormattingSourceSlice } from "./suggestion-formatting";
3
+ import { resolveMarkdownSuggestionRange } from "./suggestion-rebase";
3
4
 
4
5
  export type SuggestionPresentationContext = {
5
6
  source: string;
@@ -177,10 +178,25 @@ export function suggestionTextPresentationForSource(
177
178
  ): SuggestionPresentationNode[] | null {
178
179
  if (context.source.slice(context.from, context.to) !== content) return null;
179
180
  if (content.length === 0 && context.from === context.to) return [];
181
+ const canonicalSource = canonicalizeNfm(context.source);
182
+ const canonicalRange = resolveMarkdownSuggestionRange(canonicalSource, {
183
+ before: { markdown: context.source, changedText: content },
184
+ after: { markdown: context.source, changedText: content },
185
+ anchor: {
186
+ from: context.from,
187
+ to: context.to,
188
+ prefix: context.source.slice(
189
+ Math.max(0, context.from - 32),
190
+ context.from,
191
+ ),
192
+ suffix: context.source.slice(context.to, context.to + 32),
193
+ },
194
+ });
195
+ if (!canonicalRange) return null;
180
196
  const parts = suggestionFormattingSourceSlice(
181
- context.source,
182
- context.from,
183
- context.to,
197
+ canonicalSource,
198
+ canonicalRange.from,
199
+ canonicalRange.to,
184
200
  );
185
201
  if (!parts) return null;
186
202
  const whitespaceOnly =
@@ -1259,13 +1259,25 @@ export const editorChromeBridgeScript: string = `"use strict";
1259
1259
  dist: true,
1260
1260
  build: true,
1261
1261
  ".next": true,
1262
- public: true
1262
+ public: true,
1263
+ ".vite": true
1263
1264
  };
1264
- function isProvenanceNoisePath(path) {
1265
+ var PROVENANCE_REACT_RUNTIME_MODULE_RE = /^(?:react|(?:react[-_])?jsx(?:-dev)?-runtime)(?:\\.development|\\.production(?:\\.min)?)?\\.(?:m?js|cjs)$/;
1266
+ var PROVENANCE_VITE_DEPS_SEGMENT_RE = /^deps(?:_|$)/;
1267
+ function isProvenanceNoisePath(path, localServedOutput) {
1265
1268
  var segments = path.split("/");
1269
+ for (var i = 0; i < segments.length - 1; i += 1) {
1270
+ if (PROVENANCE_VITE_DEPS_SEGMENT_RE.test(segments[i]) && PROVENANCE_REACT_RUNTIME_MODULE_RE.test(segments[i + 1])) {
1271
+ return true;
1272
+ }
1273
+ }
1266
1274
  for (var i = 0; i < segments.length; i += 1) {
1267
- if (PROVENANCE_NOISE_SEGMENTS[segments[i]]) return true;
1268
- if (segments[i] === "_next" && segments[i + 1] === "static") return true;
1275
+ var segment = segments[i];
1276
+ if (localServedOutput && (segment === "dist" || segment === "build")) {
1277
+ continue;
1278
+ }
1279
+ if (PROVENANCE_NOISE_SEGMENTS[segment]) return true;
1280
+ if (segment === "_next" && segments[i + 1] === "static") return true;
1269
1281
  }
1270
1282
  return false;
1271
1283
  }
@@ -1299,7 +1311,7 @@ export const editorChromeBridgeScript: string = `"use strict";
1299
1311
  if (!match) return null;
1300
1312
  var resolved = resolveProvenanceFrameUrl(match[2]);
1301
1313
  if (!resolved) return null;
1302
- if (!resolved.localServedOutput && isProvenanceNoisePath(resolved.sourceFile)) {
1314
+ if (isProvenanceNoisePath(resolved.sourceFile, resolved.localServedOutput)) {
1303
1315
  return null;
1304
1316
  }
1305
1317
  var line = parseInt(match[3], 10);
@@ -17,13 +17,25 @@ export const sourceLocationBridgeScript: string = `"use strict";
17
17
  dist: true,
18
18
  build: true,
19
19
  ".next": true,
20
- public: true
20
+ public: true,
21
+ ".vite": true
21
22
  };
22
- function isNoisePath(path) {
23
+ var REACT_RUNTIME_MODULE_RE = /^(?:react|(?:react[-_])?jsx(?:-dev)?-runtime)(?:\\.development|\\.production(?:\\.min)?)?\\.(?:m?js|cjs)$/;
24
+ var VITE_DEPS_SEGMENT_RE = /^deps(?:_|$)/;
25
+ function isNoisePath(path, localServedOutput) {
23
26
  var segments = path.split("/");
27
+ for (var i = 0; i < segments.length - 1; i += 1) {
28
+ if (VITE_DEPS_SEGMENT_RE.test(segments[i]) && REACT_RUNTIME_MODULE_RE.test(segments[i + 1])) {
29
+ return true;
30
+ }
31
+ }
24
32
  for (var i = 0; i < segments.length; i += 1) {
25
- if (NOISE_SEGMENTS[segments[i]]) return true;
26
- if (segments[i] === "_next" && segments[i + 1] === "static") return true;
33
+ var segment = segments[i];
34
+ if (localServedOutput && (segment === "dist" || segment === "build")) {
35
+ continue;
36
+ }
37
+ if (NOISE_SEGMENTS[segment]) return true;
38
+ if (segment === "_next" && segments[i + 1] === "static") return true;
27
39
  }
28
40
  return false;
29
41
  }
@@ -54,7 +66,7 @@ export const sourceLocationBridgeScript: string = `"use strict";
54
66
  var rawUrl = match[2];
55
67
  var resolved = resolveFrameUrl(rawUrl);
56
68
  if (!resolved) return null;
57
- if (!resolved.localServedOutput && isNoisePath(resolved.sourceFile)) {
69
+ if (isNoisePath(resolved.sourceFile, resolved.localServedOutput)) {
58
70
  return null;
59
71
  }
60
72
  var lineNumber = Number(match[3]);
@@ -3,6 +3,58 @@
3
3
  All notable user-facing changes to Design are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-16
7
+
8
+ ### Fixed
9
+
10
+ - A message that only mentions a design, or pastes Design's own guidance back into chat, no longer ends with a false "couldn't confirm that a Design artifact was saved" reply.
11
+
12
+ ## 2026-09-13
13
+
14
+ ### Fixed
15
+
16
+ - Connect Code imports explain when a design-system name is already in use
17
+ - Design captures are visible only to editors
18
+ - Design generation no longer stays active when a completion event is lost
19
+ - Design MCP servers now render screen exports as PNGs
20
+ - Design screen exports work without system-installed Chrome
21
+ - Fixed screen exports for designs shared with viewers
22
+ - Source-file actions return not-found when a design is missing or inaccessible
23
+ - Undo works immediately after editing a size field in the inspector
24
+
25
+ ### Security
26
+
27
+ - Public Design links no longer expose captured state data
28
+
29
+ ## 2026-09-12
30
+
31
+ ### Fixed
32
+
33
+ - A generated design now gets a prompt-based title when no title comes back.
34
+ - Canvas panning stays proportional at every zoom level.
35
+ - Closed shapes and pen paths support inside, center, and outside stroke alignment.
36
+ - Custom breakpoints can be added after Tablet, and Enter applies their width.
37
+ - Deselecting a text layer clears the leftover text highlight.
38
+ - Design generation no longer stays stuck when an agent run ends without its final chat update.
39
+ - Edit with AI shows the selected layer name and uses the current theme for its hover highlight.
40
+ - Gradient fills stay gradients when you edit them.
41
+ - Grouped layers are easier to edit: click selects the group, double-click opens a child, and paste preserves group placement.
42
+ - Interact mode keeps its toolbar and height controls within the available canvas.
43
+ - The inspector's export Preview now renders a thumbnail when a whole screen or
44
+ frame is selected, instead of always reporting "Preview unavailable" next to a
45
+ working Export button.
46
+ - The Questions panel no longer overlaps the navigation rail.
47
+
48
+ ## 2026-09-11
49
+
50
+ ### Fixed
51
+
52
+ - Moving the pointer over the canvas no longer takes focus away from a composer
53
+ or comment box that just opened, so you can type into it straight away.
54
+ - Screens no longer creep taller each time a selection handle reaches the bottom
55
+ edge of a frame — the editor's own overlays are no longer measured as page
56
+ content.
57
+
6
58
  ## 2026-09-10
7
59
 
8
60
  ### Added
@@ -15,6 +67,14 @@ from the command menu (Cmd+K → "What's new") or from Settings.
15
67
  - Opening or closing the left sidebar (or toggling minimal mode) no longer shifts designs on the canvas.
16
68
  - Breakpoint add control keeps a clear gap from the screen edge and shows immediate feedback while adding.
17
69
 
70
+ ### Fixed
71
+
72
+ - Clicking a button or other painted text element on the canvas selects that
73
+ element, not the wrapper the editor puts around its text — so its corner
74
+ radius, fill and component props are editable again.
75
+ - Exported SVGs no longer break on designs that use Alpine list or conditional blocks.
76
+ - PNG and PDF exports now render text with the design's own webfonts, so highlights, gradients and underlines stay aligned with the text.
77
+
18
78
  ### Changed
19
79
 
20
80
  - Design canvas chrome now overlays instead of resizing the board, so opening or closing the left panel and minimal mode no longer shifts screens on the canvas.
@@ -642,13 +642,45 @@ declare var __INITIAL_SOURCE_HEAD__: string;
642
642
  build: true,
643
643
  ".next": true,
644
644
  public: true,
645
+ ".vite": true,
645
646
  };
646
647
 
647
- function isProvenanceNoisePath(path: string): boolean {
648
+ // React 19 captures _debugStack inside the JSX runtime itself, so its
649
+ // module is always the top frame; recognised by a runtime module name
650
+ // INSIDE a Vite optimizer deps directory (`deps`/`deps_ssr`/`deps_temp_*`
651
+ // — the optimizer always writes pre-bundles there, even under a custom
652
+ // cacheDir with no node_modules segment) — never by basename alone, since
653
+ // an authored file that happens to be named react.js or jsx-runtime.js
654
+ // outside a deps directory is a real local file.
655
+ var PROVENANCE_REACT_RUNTIME_MODULE_RE =
656
+ /^(?:react|(?:react[-_])?jsx(?:-dev)?-runtime)(?:\.development|\.production(?:\.min)?)?\.(?:m?js|cjs)$/;
657
+ var PROVENANCE_VITE_DEPS_SEGMENT_RE = /^deps(?:_|$)/;
658
+
659
+ // localServedOutput (a /@fs/ frame) exempts dist/build only — a locally
660
+ // built package is a real local file. node_modules stays noise even
661
+ // through /@fs/: Vite resolves symlinks, so a linked workspace package
662
+ // never carries a node_modules segment, and third-party code always
663
+ // arrives here by its real path.
664
+ function isProvenanceNoisePath(
665
+ path: string,
666
+ localServedOutput: boolean,
667
+ ): boolean {
648
668
  var segments = path.split("/");
669
+ for (var i = 0; i < segments.length - 1; i += 1) {
670
+ if (
671
+ PROVENANCE_VITE_DEPS_SEGMENT_RE.test(segments[i]!) &&
672
+ PROVENANCE_REACT_RUNTIME_MODULE_RE.test(segments[i + 1]!)
673
+ ) {
674
+ return true;
675
+ }
676
+ }
649
677
  for (var i = 0; i < segments.length; i += 1) {
650
- if (PROVENANCE_NOISE_SEGMENTS[segments[i]!]) return true;
651
- if (segments[i] === "_next" && segments[i + 1] === "static") return true;
678
+ var segment = segments[i]!;
679
+ if (localServedOutput && (segment === "dist" || segment === "build")) {
680
+ continue;
681
+ }
682
+ if (PROVENANCE_NOISE_SEGMENTS[segment]) return true;
683
+ if (segment === "_next" && segments[i + 1] === "static") return true;
652
684
  }
653
685
  return false;
654
686
  }
@@ -710,11 +742,11 @@ declare var __INITIAL_SOURCE_HEAD__: string;
710
742
  var resolved = resolveProvenanceFrameUrl(match[2]!);
711
743
  if (!resolved) return null;
712
744
  // A Vite /@fs/ frame is a real local file even when its path contains
713
- // dist/ or node_modules/. Ordinary runtime/module frames retain the noise
714
- // filter so React internals never become element provenance.
745
+ // dist/ or build/ — but node_modules stays noise even through /@fs/, so a
746
+ // classic-transform createElement frame served from a symlinked
747
+ // dependency (react.development.js) never becomes element provenance.
715
748
  if (
716
- !resolved.localServedOutput &&
717
- isProvenanceNoisePath(resolved.sourceFile)
749
+ isProvenanceNoisePath(resolved.sourceFile, resolved.localServedOutput)
718
750
  ) {
719
751
  return null;
720
752
  }
@@ -76,13 +76,42 @@
76
76
  build: true,
77
77
  ".next": true,
78
78
  public: true,
79
+ ".vite": true,
79
80
  };
80
81
 
81
- function isNoisePath(path: string): boolean {
82
+ // React 19 captures _debugStack inside the JSX runtime itself, so its
83
+ // module is always the top frame; recognised by a runtime module name
84
+ // INSIDE a Vite optimizer deps directory (`deps`/`deps_ssr`/`deps_temp_*`
85
+ // — the optimizer always writes pre-bundles there, even under a custom
86
+ // cacheDir with no node_modules segment) — never by basename alone, since
87
+ // an authored file that happens to be named react.js or jsx-runtime.js
88
+ // outside a deps directory is a real local file.
89
+ var REACT_RUNTIME_MODULE_RE =
90
+ /^(?:react|(?:react[-_])?jsx(?:-dev)?-runtime)(?:\.development|\.production(?:\.min)?)?\.(?:m?js|cjs)$/;
91
+ var VITE_DEPS_SEGMENT_RE = /^deps(?:_|$)/;
92
+
93
+ // localServedOutput (a /@fs/ frame) exempts dist/build only — a locally
94
+ // built package is a real local file. node_modules stays noise even
95
+ // through /@fs/: Vite resolves symlinks, so a linked workspace package
96
+ // never carries a node_modules segment, and third-party code always
97
+ // arrives here by its real path.
98
+ function isNoisePath(path: string, localServedOutput: boolean): boolean {
82
99
  var segments = path.split("/");
100
+ for (var i = 0; i < segments.length - 1; i += 1) {
101
+ if (
102
+ VITE_DEPS_SEGMENT_RE.test(segments[i]!) &&
103
+ REACT_RUNTIME_MODULE_RE.test(segments[i + 1]!)
104
+ ) {
105
+ return true;
106
+ }
107
+ }
83
108
  for (var i = 0; i < segments.length; i += 1) {
84
- if (NOISE_SEGMENTS[segments[i]!]) return true;
85
- if (segments[i] === "_next" && segments[i + 1] === "static") return true;
109
+ var segment = segments[i]!;
110
+ if (localServedOutput && (segment === "dist" || segment === "build")) {
111
+ continue;
112
+ }
113
+ if (NOISE_SEGMENTS[segment]) return true;
114
+ if (segment === "_next" && segments[i + 1] === "static") return true;
86
115
  }
87
116
  return false;
88
117
  }
@@ -130,7 +159,11 @@
130
159
  var rawUrl = match[2]!;
131
160
  var resolved = resolveFrameUrl(rawUrl);
132
161
  if (!resolved) return null;
133
- if (!resolved.localServedOutput && isNoisePath(resolved.sourceFile)) {
162
+ // A Vite /@fs/ frame is a real local file even when its path contains
163
+ // dist/ or build/ — but node_modules stays noise even through /@fs/, so a
164
+ // classic-transform createElement frame served from a symlinked
165
+ // dependency (react.development.js) never becomes element provenance.
166
+ if (isNoisePath(resolved.sourceFile, resolved.localServedOutput)) {
134
167
  return null;
135
168
  }
136
169
  var lineNumber = Number(match[3]);
@@ -34,14 +34,32 @@ const NOISE_SEGMENTS = new Set([
34
34
  "build",
35
35
  ".next",
36
36
  "public",
37
+ ".vite",
37
38
  ]);
38
39
 
39
40
  // V8 stack frame: "at Name (url:line:col)" or the anonymous "at url:line:col".
40
41
  const STACK_FRAME_RE =
41
42
  /^\s*at\s+(?:([^\s(]+)\s+\()?([^()\s][^()]*?):(\d+):(\d+)\)?\s*$/;
42
43
 
44
+ // React 19 captures _debugStack inside the JSX runtime itself, so its
45
+ // module is always the top frame; recognised by a runtime module name
46
+ // INSIDE a Vite optimizer deps directory (`deps`/`deps_ssr`/`deps_temp_*` —
47
+ // the optimizer always writes pre-bundles there, even under a custom
48
+ // cacheDir with no node_modules segment) — never by basename alone, since an
49
+ // authored file that happens to be named react.js or jsx-runtime.js outside
50
+ // a deps directory is a real local file.
51
+ const REACT_RUNTIME_MODULE_RE =
52
+ /^(?:react|(?:react[-_])?jsx(?:-dev)?-runtime)(?:\.development|\.production(?:\.min)?)?\.(?:m?js|cjs)$/;
53
+ const VITE_DEPS_SEGMENT_RE = /^deps(?:_|$)/;
54
+
43
55
  function isNoisePath(path: string): boolean {
44
56
  const segments = path.split("/");
57
+ const runtimeInsideDepsDir = segments.some(
58
+ (segment, index) =>
59
+ VITE_DEPS_SEGMENT_RE.test(segment) &&
60
+ REACT_RUNTIME_MODULE_RE.test(segments[index + 1] ?? ""),
61
+ );
62
+ if (runtimeInsideDepsDir) return true;
45
63
  if (segments.some((segment) => NOISE_SEGMENTS.has(segment))) return true;
46
64
  const nextIndex = segments.indexOf("_next");
47
65
  return nextIndex >= 0 && segments[nextIndex + 1] === "static";
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-14
4
+ ---
5
+
6
+ Selecting an element in a live React app now anchors to the authored component file and owner instead of the JSX runtime, so source edits and repeated-instance counts target the real code
@@ -3,20 +3,39 @@
3
3
  All notable user-facing changes to Chat are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-11
7
+
8
+ ### Improved
9
+
10
+ - The Audit tab shows pull request numbers beside GitHub item titles, collapses long summaries with view more, and uses a clear Open thread button.
11
+ - GitHub inbox rows now show source, pull request number, and author on one line; Slack list rows resolve stored member names instead of raw ids, and Slack detail drops the duplicate heading once the thread loads.
12
+ - The Inbox list now shows separate Routing and Automation columns so babysit and Slack workflow progress is visible beside escalation status.
13
+ - PR babysitting now follows unresolved bot review feedback, defers while Builder is active, and shows recommendation, decision, and because on the Audit tab.
14
+
6
15
  ## 2026-09-10
7
16
 
8
17
  ### Improved
9
18
 
10
19
  - Slack feedback marks clear bugs with 👀 again and requires that reaction before dispatching Builder.
11
20
 
21
+ ### Fixed
22
+
23
+ - Factory automations keep display names, Slack channels, and author filters after poll runs and metadata repair.
24
+ - Factory PR babysitting now nudges Builder on Clips, Design, and Content pull requests instead of parking them as owner-managed.
25
+
12
26
  ## 2026-09-09
13
27
 
28
+ ### Added
29
+
30
+ - A pull request that another request cannot unblock is now parked as stuck for a human to look at, rather than asked again on the next round.
31
+
14
32
  ### Improved
15
33
 
16
34
  - Slack feedback jobs skip threads whose parent already has 👀 or 🤖, instead of tagging Builder again.
17
35
 
18
36
  ### Fixed
19
37
 
38
+ - Factory asks Builder to fix pull-request feedback once per round of work, instead of repeating the request each time GitHub recalculates whether the branch merges cleanly.
20
39
  - Slack feedback keeps a claimed 👀/🤖 marker after polling, so those threads stay out of the review queue.
21
40
  - Slack feedback no longer moves an already-started thread to needs-manual just because the parent already has 👀 or 🤖.
22
41
 
@@ -3,6 +3,12 @@
3
3
  All notable user-facing changes to Agent-Native Forms are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-12
7
+
8
+ ### Fixed
9
+
10
+ - Creating a form no longer dead-ends when a field is missing its type; the agent is told exactly which field and which property to correct.
11
+
6
12
  ## 2026-08-25
7
13
 
8
14
  ### Fixed
@@ -3,6 +3,40 @@
3
3
  All notable user-facing changes to Agent-Native Mail are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-09-12
7
+
8
+ ### Improved
9
+
10
+ - Inbox tabs are easier to distinguish
11
+ - New-message compose opens in the main workspace by default
12
+
13
+ ### Fixed
14
+
15
+ - Closing a draft preserves its saved account and backend so recovery actions affect the correct mailbox.
16
+ - Harden draft close recovery and keyboard interaction states
17
+ - Keep mail autocomplete accessibility references valid while suggestions close or filter
18
+ - Mail keeps saved drafts on their owning backend and preserves them through Send Undo and close-all.
19
+ - Saved drafts stay in their original mailbox when reopened, autosaved, or discarded.
20
+ - Send status now follows provider results, undo stays available only before dispatch, and draft-save failures are visible. Closing drafts no longer claims a save is complete before persistence, and deleting a draft targets the draft endpoint.
21
+
22
+ ## 2026-09-11
23
+
24
+ ### Fixed
25
+
26
+ - Inbox tabs now load from a synced local index: counts match the rows shown, custom label and filter tabs only show unarchived mail, and the inbox stays fast under Gmail rate limits.
27
+ - Inline images in Gmail emails display reliably, including embedded image data
28
+ - Nested Gmail labels now render under their parent labels in the label list.
29
+ - On mobile the sidebar shows a close button instead of a pin control that did nothing.
30
+ - Opening a label no longer fails with a 502 while Gmail is rate limiting; the app now reports the brief pause with a retry time.
31
+ - The inbox tab bar stays on one line and scrolls instead of wrapping.
32
+
33
+ ## 2026-09-10
34
+
35
+ ### Fixed
36
+
37
+ - Attachments use your connected Builder.io storage in hosted Mail
38
+ - Fixed Google sign-in being blocked with an 'unverified password account' error for workspace users provisioned through cross-app SSO.
39
+
6
40
  ## 2026-09-09
7
41
 
8
42
  ### Improved
@@ -3,6 +3,20 @@
3
3
  All notable user-facing changes to Agent-Native Slides are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new").
5
5
 
6
+ ## 2026-09-12
7
+
8
+ ### Fixed
9
+
10
+ - Multi-object rotation keeps translated objects aligned
11
+ - Objects keep their position when ungrouping a rotated group.
12
+ - Preserve scaled child transforms and fractional matrix angles when ungrouping rotated groups
13
+
14
+ ## 2026-09-10
15
+
16
+ ### Fixed
17
+
18
+ - When Google Slides export falls back to downloading a .pptx, it now says so instead of offering a button that opens an empty Google Slides tab.
19
+
6
20
  ## 2026-09-09
7
21
 
8
22
  ### Improved
@@ -12,6 +26,7 @@ time from the command menu (Cmd+K → "What's new").
12
26
 
13
27
  ### Fixed
14
28
 
29
+ - Google Slides export is now marked unavailable instead of sending you to a broken Google sign-in page when the Google connection cannot be used.
15
30
  - When an agent's slide edit does not match the slide, it now says so instead of reporting an internal server error, so the agent can correct the edit rather than retrying the same one.
16
31
 
17
32
  ## 2026-09-03
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
186
186
  duplicate?: undefined;
187
187
  eventId?: undefined;
188
188
  } | {
189
+ error?: undefined;
189
190
  accepted: boolean;
190
191
  duplicate: boolean;
191
192
  eventId: string;
192
- error?: undefined;
193
193
  }>;
@@ -28,6 +28,6 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
28
28
  } | {
29
29
  count?: undefined;
30
30
  updated?: undefined;
31
- error?: undefined;
32
31
  ok: boolean;
32
+ error?: undefined;
33
33
  }>>;
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
- ok?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
47
  error?: undefined;
51
48
  ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
58
  id?: undefined;
60
59
  error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
48
48
  }>;
49
49
  /** DELETE /_agent-native/resources/:id — delete a resource */
50
50
  export declare function handleDeleteResource(event: any): Promise<{
51
- ok?: undefined;
52
51
  error: string;
52
+ ok?: undefined;
53
53
  } | {
54
54
  error?: undefined;
55
55
  ok: boolean;
@@ -34,10 +34,10 @@ export declare function resolveAgentEngineApiKeyWriteTarget(event: H3Event, scop
34
34
  export declare function createAgentEngineApiKeyHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
35
35
  error: any;
36
36
  } | {
37
+ error?: undefined;
37
38
  ok: boolean;
38
39
  key: string;
39
40
  baseUrlKey?: string;
40
41
  scope: AgentEngineApiKeyScope;
41
- error?: undefined;
42
42
  }>>;
43
43
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.179.0-nightly-20260914194938",
3
+ "version": "0.179.0",
4
4
  "description": "The agentic application framework for building autonomous agents with intuitive UIs",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -475,9 +475,9 @@
475
475
  "yaml": "^2.9.0",
476
476
  "yjs": "^13.6.32",
477
477
  "zod": "^4.3.6",
478
- "@agent-native/agentkit": "^0.2.0-nightly-20260914194938",
479
- "@agent-native/recap-cli": "0.5.29",
480
- "@agent-native/toolkit": "^0.20.0-nightly-20260914194938"
478
+ "@agent-native/agentkit": "^0.2.0",
479
+ "@agent-native/toolkit": "^0.20.0",
480
+ "@agent-native/recap-cli": "0.5.30"
481
481
  },
482
482
  "devDependencies": {
483
483
  "@ai-sdk/anthropic": "^3.0.71",