@agent-native/core-corpus 0.217.0 → 0.219.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +5 -3
- package/corpus/templates/analytics/CHANGELOG.md +2 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/CHANGELOG.md +7 -0
- package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
- package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
- package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/i18n-data.ts +8 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
- package/corpus/templates/design/CHANGELOG.md +8 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
- package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
- package/corpus/templates/design/app/i18n/en-US.ts +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +18 -64
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
- package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
- package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
- package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
- package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
- package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
- package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
- package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +1 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -1645,6 +1645,14 @@ const enUS = {
|
|
|
1645
1645
|
replyCount_few: "{{count}} replies",
|
|
1646
1646
|
replyCount_many: "{{count}} replies",
|
|
1647
1647
|
replyCount_other: "{{count}} replies",
|
|
1648
|
+
stackCollapse: "Collapse",
|
|
1649
|
+
stackShowAll_zero: "Show all {{count}} comments here",
|
|
1650
|
+
stackShowAll_one: "Show {{count}} comment here",
|
|
1651
|
+
stackShowAll_two: "Show all {{count}} comments here",
|
|
1652
|
+
stackShowAll_few: "Show all {{count}} comments here",
|
|
1653
|
+
stackShowAll_many: "Show all {{count}} comments here",
|
|
1654
|
+
stackShowAll_other: "Show all {{count}} comments here",
|
|
1655
|
+
stackMore: "+{{count}} more",
|
|
1648
1656
|
commentActions: "Comment actions",
|
|
1649
1657
|
checkSaved: "Check saved comment",
|
|
1650
1658
|
edit: "Edit",
|
|
@@ -22,7 +22,7 @@ proof_requirements:
|
|
|
22
22
|
]
|
|
23
23
|
evidence: []
|
|
24
24
|
superseded_by: null
|
|
25
|
-
last_reviewed: "2026-10-
|
|
25
|
+
last_reviewed: "2026-10-22"
|
|
26
26
|
---
|
|
27
27
|
|
|
28
28
|
# Comments
|
|
@@ -40,6 +40,7 @@ A reviewer comments on two Blocks in a brief, replies with a Page reference, and
|
|
|
40
40
|
- A Comment thread is owned by one Page and may target one or several Blocks or ranges in its Blocks fields.
|
|
41
41
|
- A thread on a Collection row can instead target one Property value, such as Status or a date. It records the Property's name and the value it had when the thread started. Relations, rollups, and formulas are the exception: what they show depends on who is reading, so the thread records no value and each reader sees their own current one. Once a recorded value changes, the thread shows what it was; once the Property is deleted, the thread says so. It never moves itself onto the newer value. A Property thread has no quoted text, and replies follow the ordinary thread rules.
|
|
42
42
|
- A Collection table shows how many open threads each row has, and marks each cell whose Property has an open thread. The row's count opens the row's Comments. A cell's marker opens a card on the cell with that Property's open threads, where they can be read, replied to, and resolved without leaving the table, and the card can open the row at any of them. Any cell also offers a Comment button to anyone who can comment on the row, which starts a thread on its Property in the same card; on touch screens, where nothing hovers, the button shows on a selected row and starts the thread in the row's peek, or on the row's full page when the Collection opens rows as pages. Counts include only rows the viewer can read, so a row they cannot open adds nothing. One Action reads every open thread in a Collection, with each thread's row and target, so an agent need not read row by row.
|
|
43
|
+
- In a Page's comment margin, a Block with two or more open threads folds into one stack. It shows the thread last clicked or rested on, else the one with the newest comment, with its reply count and the other threads' authors. Clicking the stack, or any highlight in the Block, opens it in place on that thread; Collapse or a click elsewhere folds it with that thread in front, and only one stack is open at a time. A new comment on a folded stack opens on top of it without opening it, and once posted it is the stack's front. Each Blocks-field tab keeps its own stacks; Property threads, the Comments panel, and the narrow-screen popover never stack.
|
|
43
44
|
- Rich Comment bodies use the shared Blocks-field grammar; replies and mentions retain the same Page authority.
|
|
44
45
|
- Anchors follow stable Block identity where possible and preserve historical target context after deletion rather than attaching to plausible new text.
|
|
45
46
|
- Resolve, reopen, edit, reply, and notification operations use shared Actions and record attributable change.
|
|
@@ -75,6 +76,10 @@ Given a Comment that mentions a teammate and Claude, when it is sent, both names
|
|
|
75
76
|
|
|
76
77
|
Given a Collection row whose Status is Blocked, when a commenter starts a thread on Status from the row's peek, the thread reads "Status · Blocked" with no "Highlight unavailable" warning, and the Status cell in the table shows a marker that opens that thread. When Status later changes to Done, the thread reads "was Blocked"; when the Property is deleted, it reads "Status (removed)". A thread started on a Property that is not on the Page, or on a Property and quoted text at once, is rejected.
|
|
77
78
|
|
|
79
|
+
### Read a busy paragraph
|
|
80
|
+
|
|
81
|
+
Given a paragraph with five open threads and another with one, the margin shows one stack beside the first paragraph, with the newest thread in front and "+4 more", and the single thread's card unchanged beside its own paragraph. Resting the pointer on another highlight in the first paragraph brings its thread to the front. Clicking it opens the stack on that thread with a Collapse row above; Collapse folds it again with that thread still in front. Commenting on new text in that paragraph opens the composer on top of the folded stack, and the posted comment becomes its front.
|
|
82
|
+
|
|
78
83
|
### Comment on a cell from the table
|
|
79
84
|
|
|
80
85
|
Given a Collection table and a commenter who cannot edit its rows, when they hover a row's Due cell, a Comment button appears. Posting from the card it opens starts a thread that reads "Due · Nov 2, 2026" without opening the row, and the cell then shows a marker and the row's count goes up. Clicking the marker reopens the card on that thread, where it can be answered, and Open row opens the row's peek on it. When an editor resolves the thread from the same card, the marker and the count clear, and an editor who clicks the cell itself still edits its value.
|
|
@@ -137,6 +142,8 @@ A Comment AI proposal now renders in its source thread in place of the receipt r
|
|
|
137
142
|
|
|
138
143
|
A thread can target one Property value. `content-database-comments.db.test.ts` proves the recorded name and value survive a value change, a relation's linked rows are never recorded for other readers, invalid targets are rejected, per-row and per-Property open counts follow row access and resolution, and the Collection-wide Comment read returns only readable rows. `comment-property-target.test.ts` covers how each Property type reads as text. Rich-text Property values do not yet offer a Property target; they keep body-style quoted Comments.
|
|
139
144
|
|
|
145
|
+
A Block with several open threads stacks in the margin. `comment-stacks.test.ts` covers how threads group by Block and which one fronts a stack; `CommentsSidebar.stacks.test.tsx` covers folding, hover, opening from the card or a highlight, Collapse and focus, one open stack, and new comments on folded and open stacks; `CommentsSidebar.stack-tabs.test.tsx` covers Blocks-field tabs and Property threads. A recorded local run walked the "Read a busy paragraph" story with fake data.
|
|
146
|
+
|
|
140
147
|
## Proof plan
|
|
141
148
|
|
|
142
149
|
1. Create, reply, mention, resolve, reopen, edit, and delete Comments through UI and Actions.
|
|
@@ -483,6 +483,9 @@ function diffTokens(text: string): string[] {
|
|
|
483
483
|
// is no longer than either, as diff-match-patch's semantic cleanup does.
|
|
484
484
|
// Equalities between replacements stay, so separate word swaps remain
|
|
485
485
|
// separately reviewable, and a line never folds, so blocks stay separate.
|
|
486
|
+
// The exception is a line the merged run begins or ends with: that is one
|
|
487
|
+
// insertion (text plus a new paragraph) Myers aligned across the existing
|
|
488
|
+
// separator, and it would otherwise split whenever another hunk exists.
|
|
486
489
|
type ChangeRun = {
|
|
487
490
|
deleted: string;
|
|
488
491
|
inserted: string;
|
|
@@ -518,12 +521,17 @@ function foldShortEqualities(parts: DiffPart[]): DiffPart[] {
|
|
|
518
521
|
const right = segments[index + 1];
|
|
519
522
|
if (typeof equality !== "string" || typeof left !== "object") continue;
|
|
520
523
|
if (typeof right !== "object") continue;
|
|
524
|
+
const insertions = left.ownDeleted === 0 && right.ownDeleted === 0;
|
|
521
525
|
const oneSided =
|
|
522
|
-
(left.
|
|
523
|
-
|
|
526
|
+
insertions || (left.ownInserted === 0 && right.ownInserted === 0);
|
|
527
|
+
const merged = insertions
|
|
528
|
+
? left.inserted + equality + right.inserted
|
|
529
|
+
: left.deleted + equality + right.deleted;
|
|
524
530
|
if (
|
|
525
531
|
!oneSided ||
|
|
526
|
-
equality.includes("\n")
|
|
532
|
+
(equality.includes("\n") &&
|
|
533
|
+
!merged.startsWith(equality) &&
|
|
534
|
+
!merged.endsWith(equality)) ||
|
|
527
535
|
equality.length > Math.max(left.ownDeleted, left.ownInserted) ||
|
|
528
536
|
equality.length > Math.max(right.ownDeleted, right.ownInserted)
|
|
529
537
|
)
|
|
@@ -814,16 +822,24 @@ function wholeWordReplacements(
|
|
|
814
822
|
const removed = operation.before.changedText;
|
|
815
823
|
const inserted = operation.after.changedText;
|
|
816
824
|
const lexicalChange = /[\p{L}\p{M}\p{N}]/u.test(removed + inserted);
|
|
825
|
+
// An edge that is a line break, as when a whole paragraph is deleted,
|
|
826
|
+
// sits beside the neighboring block's word without splitting it.
|
|
827
|
+
const lineEdge = (edge: (text: string) => boolean) =>
|
|
828
|
+
edge(removed) && (!inserted || edge(inserted));
|
|
829
|
+
const widensBefore =
|
|
830
|
+
isWordBefore(before, from) && !lineEdge((text) => text.startsWith("\n"));
|
|
831
|
+
const widensAfter =
|
|
832
|
+
isWordAt(before, to) && !lineEdge((text) => text.endsWith("\n"));
|
|
817
833
|
let group = { from, to, members: [operation] };
|
|
818
834
|
if (
|
|
819
835
|
lexicalChange &&
|
|
820
836
|
!/<br\/?\s*>/u.test(removed + inserted) &&
|
|
821
837
|
(removed || !/\s/u.test(inserted)) &&
|
|
822
|
-
(
|
|
838
|
+
(widensBefore || widensAfter)
|
|
823
839
|
) {
|
|
824
|
-
while (isWordBefore(before, group.from))
|
|
840
|
+
while (widensBefore && isWordBefore(before, group.from))
|
|
825
841
|
group.from -= characterBefore(before, group.from).length;
|
|
826
|
-
while (isWordAt(before, group.to))
|
|
842
|
+
while (widensAfter && isWordAt(before, group.to))
|
|
827
843
|
group.to += characterAt(before, group.to).length;
|
|
828
844
|
}
|
|
829
845
|
// Two edits inside one word are one review decision.
|
|
@@ -1022,6 +1038,7 @@ export function markdownSuggestionOperationsForReplacements(input: {
|
|
|
1022
1038
|
const [{ from, to }] = replacements;
|
|
1023
1039
|
if (
|
|
1024
1040
|
operations.length > 1 &&
|
|
1041
|
+
!before.slice(from, to).includes("\n") &&
|
|
1025
1042
|
operations.every(
|
|
1026
1043
|
(operation) =>
|
|
1027
1044
|
operation.anchor.from >= from && operation.anchor.to <= to,
|
|
@@ -650,12 +650,21 @@ function resolveVerbatimRun(
|
|
|
650
650
|
export type SuggestionFormattingSlicePart =
|
|
651
651
|
| { type: "text"; text: string; marks: NonNullable<PMNode["marks"]> }
|
|
652
652
|
| { type: "break"; text: string }
|
|
653
|
-
| { type: "indent"; text: string }
|
|
653
|
+
| { type: "indent"; text: string }
|
|
654
|
+
| { type: "block"; text: string };
|
|
654
655
|
|
|
656
|
+
const EMPTY_BLOCK = "<empty-block/>";
|
|
657
|
+
// The syntax that opens a line's block: a heading, list item, task, or quote.
|
|
658
|
+
const BLOCK_MARKER = /^(?:#{1,6} |(?:> )+|[-*+] (?:\[[ xX]\] )?|\d+[.)] )/;
|
|
659
|
+
|
|
660
|
+
// `blocks` admits block syntax and empty paragraphs. Without it a gap holds
|
|
661
|
+
// only line breaks and indentation, which is what places a zero-width
|
|
662
|
+
// boundary on one side of a gap or the other.
|
|
655
663
|
function structuralGapParts(
|
|
656
664
|
source: string,
|
|
657
665
|
from: number,
|
|
658
666
|
to: number,
|
|
667
|
+
blocks = true,
|
|
659
668
|
): SuggestionFormattingSlicePart[] | null {
|
|
660
669
|
const parts: SuggestionFormattingSlicePart[] = [];
|
|
661
670
|
let offset = from;
|
|
@@ -678,15 +687,36 @@ function structuralGapParts(
|
|
|
678
687
|
source.lastIndexOf("\r", offset - 1),
|
|
679
688
|
) + 1;
|
|
680
689
|
const atLineStart = /^\t*$/.test(source.slice(lineStart, offset));
|
|
681
|
-
if (!atLineStart
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
+
if (!atLineStart) return null;
|
|
691
|
+
if (source[offset] === "\t") {
|
|
692
|
+
const indentFrom = offset;
|
|
693
|
+
while (offset < to && source[offset] === "\t") offset += 1;
|
|
694
|
+
parts.push({
|
|
695
|
+
type: "indent",
|
|
696
|
+
text: "⇥".repeat(offset - indentFrom),
|
|
697
|
+
});
|
|
698
|
+
const heading = /^(?:#{1,6}) /.exec(source.slice(offset, to));
|
|
699
|
+
if (heading) offset += heading[0].length;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (!blocks) return null;
|
|
703
|
+
// An empty paragraph or a block's opening syntax has no text of its own,
|
|
704
|
+
// so it is shown as written: a pilcrow for the paragraph, the marker
|
|
705
|
+
// otherwise. A marker is only whole when the slice holds all of it.
|
|
706
|
+
const lineEnd = offset + EMPTY_BLOCK.length;
|
|
707
|
+
if (
|
|
708
|
+
source.startsWith(EMPTY_BLOCK, offset) &&
|
|
709
|
+
lineEnd <= to &&
|
|
710
|
+
(lineEnd === source.length || /[\r\n]/.test(source[lineEnd]!))
|
|
711
|
+
) {
|
|
712
|
+
parts.push({ type: "block", text: "¶" });
|
|
713
|
+
offset = lineEnd;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const marker = BLOCK_MARKER.exec(source.slice(offset, to));
|
|
717
|
+
if (!marker) return null;
|
|
718
|
+
parts.push({ type: "block", text: marker[0] });
|
|
719
|
+
offset += marker[0].length;
|
|
690
720
|
}
|
|
691
721
|
return parts;
|
|
692
722
|
}
|
|
@@ -894,7 +924,7 @@ export function suggestionFormattingSourceRange(
|
|
|
894
924
|
sourceTo: run.sourceFrom,
|
|
895
925
|
offset: run.from,
|
|
896
926
|
structural: Boolean(
|
|
897
|
-
structuralGapParts(source, previousSourceTo, run.sourceFrom),
|
|
927
|
+
structuralGapParts(source, previousSourceTo, run.sourceFrom, false),
|
|
898
928
|
),
|
|
899
929
|
afterText: index > 0,
|
|
900
930
|
beforeText: true,
|
|
@@ -910,7 +940,7 @@ export function suggestionFormattingSourceRange(
|
|
|
910
940
|
sourceTo: source.length,
|
|
911
941
|
offset: previousTextTo,
|
|
912
942
|
structural: Boolean(
|
|
913
|
-
structuralGapParts(source, previousSourceTo, source.length),
|
|
943
|
+
structuralGapParts(source, previousSourceTo, source.length, false),
|
|
914
944
|
),
|
|
915
945
|
afterText: mapped.runs.length > 0,
|
|
916
946
|
beforeText: false,
|
|
@@ -983,13 +1013,13 @@ export function suggestionFormattingSourceRange(
|
|
|
983
1013
|
if (
|
|
984
1014
|
gap.afterText &&
|
|
985
1015
|
offset > gap.sourceFrom &&
|
|
986
|
-
structuralGapParts(source, gap.sourceFrom, offset)
|
|
1016
|
+
structuralGapParts(source, gap.sourceFrom, offset, false)
|
|
987
1017
|
)
|
|
988
1018
|
candidates.push({ offset: gap.offset, affinity: "left" });
|
|
989
1019
|
if (
|
|
990
1020
|
gap.beforeText &&
|
|
991
1021
|
offset < gap.sourceTo &&
|
|
992
|
-
(structuralGapParts(source, offset, gap.sourceTo) ||
|
|
1022
|
+
(structuralGapParts(source, offset, gap.sourceTo, false) ||
|
|
993
1023
|
!/[\r\n]/.test(opening) ||
|
|
994
1024
|
(gap.beforeCode && /^[^\r\n]*\r?\n$/.test(opening)))
|
|
995
1025
|
)
|
|
@@ -11,6 +11,8 @@ export type SuggestionPresentationContext = {
|
|
|
11
11
|
export type SuggestionPresentationNode =
|
|
12
12
|
| { type: "text"; value: string }
|
|
13
13
|
| { type: "indent"; value: string }
|
|
14
|
+
| { type: "block"; value: string }
|
|
15
|
+
| { type: "lineBreak" }
|
|
14
16
|
| {
|
|
15
17
|
type: "strong" | "emphasis" | "strike" | "code" | "underline";
|
|
16
18
|
children: SuggestionPresentationNode[];
|
|
@@ -120,7 +122,12 @@ function stripInlineContext(
|
|
|
120
122
|
const value = node.value.slice(context.length);
|
|
121
123
|
return value ? { type: "text", value } : null;
|
|
122
124
|
}
|
|
123
|
-
if (
|
|
125
|
+
if (
|
|
126
|
+
node.type === "indent" ||
|
|
127
|
+
node.type === "block" ||
|
|
128
|
+
node.type === "lineBreak"
|
|
129
|
+
)
|
|
130
|
+
return node;
|
|
124
131
|
const children = node.children.flatMap((child) => {
|
|
125
132
|
const strippedChild = stripNode(child);
|
|
126
133
|
return strippedChild ? [strippedChild] : [];
|
|
@@ -246,11 +253,19 @@ function computeSuggestionTextPresentationForSource(
|
|
|
246
253
|
? text.replace(/ /g, "·").replace(/\t/g, "⇥")
|
|
247
254
|
: text
|
|
248
255
|
).replace(/\n/g, "↵");
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
256
|
+
// A line break beside text breaks the line; on its own it stays a visible ↵.
|
|
257
|
+
const breaksLines = parts.some(
|
|
258
|
+
(part) => part.type === "text" && /\S/.test(part.text),
|
|
259
|
+
);
|
|
260
|
+
return parts.flatMap((part): SuggestionPresentationNode[] =>
|
|
261
|
+
part.type === "indent" || part.type === "block"
|
|
262
|
+
? [{ type: part.type, value: part.text }]
|
|
252
263
|
: part.type === "break"
|
|
253
|
-
? [
|
|
264
|
+
? [
|
|
265
|
+
breaksLines
|
|
266
|
+
? { type: "lineBreak" }
|
|
267
|
+
: { type: "text", value: part.text },
|
|
268
|
+
]
|
|
254
269
|
: presentationNode(
|
|
255
270
|
{ type: "text", text: visibleText(part.text), marks: part.marks },
|
|
256
271
|
(text) => text,
|
|
@@ -56,14 +56,14 @@ export const interactLinksBridgeScript: string = `"use strict";
|
|
|
56
56
|
}
|
|
57
57
|
e.preventDefault();
|
|
58
58
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
59
|
+
var message = {
|
|
60
|
+
type: "prototype-navigate",
|
|
61
|
+
href: target,
|
|
62
|
+
screen: screenName(target)
|
|
63
|
+
};
|
|
64
|
+
var transition = link.getAttribute("data-an-prototype-transition");
|
|
65
|
+
if (transition !== null) message.transition = transition;
|
|
66
|
+
window.parent.postMessage(message, "*");
|
|
67
67
|
} catch (_err) {
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
@@ -5,6 +5,10 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
5
5
|
|
|
6
6
|
## 2026-10-22
|
|
7
7
|
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Prototype links can play code-authored screen transitions.
|
|
11
|
+
|
|
8
12
|
### Fixed
|
|
9
13
|
|
|
10
14
|
- Collapsed shader layers no longer interrupt other shaders on the canvas
|
|
@@ -15,10 +19,12 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
15
19
|
|
|
16
20
|
### Added
|
|
17
21
|
|
|
22
|
+
- 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
|
|
18
23
|
- Pulsing Border adds moving color highlights along an element’s outline, with concentrated bright cores, colored falloff, and controls for motion and shape.
|
|
19
24
|
|
|
20
25
|
### Improved
|
|
21
26
|
|
|
27
|
+
- 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.
|
|
22
28
|
- Effect thumbnails now show how shaders treat the Agent-Native mark against a quiet tonal background, making their differences easier to judge.
|
|
23
29
|
- Native shader previews use less GPU memory when many mesh effects are visible.
|
|
24
30
|
- Pulsing Border uses the standard Linear and Angular gradient picker for its moving border colors, with up to eight editable stops and a separate Background color. Thickness sets the bright edge, Softness feathers it, Bloom adjusts glow brightness, and Glow spread sets the halo radius. Smoke, margins, offsets, and layout settings stay grouped in collapsible sections.
|
|
@@ -51,6 +57,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
51
57
|
|
|
52
58
|
### Fixed
|
|
53
59
|
|
|
60
|
+
- The Design / Full app toggle on the home page no longer hides behind the Connect AI card.
|
|
54
61
|
- Read-only Design views render native shader previews in nested frames.
|
|
55
62
|
- Selected screens with shader effects can be exported together as a PNG
|
|
56
63
|
- Shader previews stay aligned while you zoom.
|
|
@@ -128,6 +135,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
128
135
|
|
|
129
136
|
### Improved
|
|
130
137
|
|
|
138
|
+
- Design pages use localized visibility badges and a shared ownership filter.
|
|
131
139
|
- Animated shader fills render together more efficiently and pause work outside the visible canvas.
|
|
132
140
|
- Shader property sliders preview changes while dragging
|
|
133
141
|
- Canvas selection, layer controls, text insertion, grouping, and auto-layout behave more predictably.
|
|
@@ -27,6 +27,10 @@ import {
|
|
|
27
27
|
import { editedStableSourceElement } from "@shared/code-layer";
|
|
28
28
|
import type { InteractionState } from "@shared/interaction-states";
|
|
29
29
|
import type { MotionEasingVariable } from "@shared/motion-easing";
|
|
30
|
+
import {
|
|
31
|
+
decodeMotionPrototypeTransitionAttribute,
|
|
32
|
+
type MotionPrototypeTransitionDecodeResult,
|
|
33
|
+
} from "@shared/motion-prototype-transition";
|
|
30
34
|
import {
|
|
31
35
|
appendPenNode,
|
|
32
36
|
clonePenPath,
|
|
@@ -951,7 +955,12 @@ interface DesignCanvasProps {
|
|
|
951
955
|
repromptDraftRequest?: RepromptDraftRequest | null;
|
|
952
956
|
onRepromptDraftConsumed?: (nonce: number) => void;
|
|
953
957
|
nodeRewriteCanvasTarget?: boolean;
|
|
954
|
-
onPrototypeNavigate?: (
|
|
958
|
+
onPrototypeNavigate?: (
|
|
959
|
+
screen: string,
|
|
960
|
+
href: string,
|
|
961
|
+
transition: MotionPrototypeTransitionDecodeResult,
|
|
962
|
+
sourceFrame: HTMLIFrameElement,
|
|
963
|
+
) => void;
|
|
955
964
|
motionTracks?: MotionTrackWire[];
|
|
956
965
|
motionEasingVariables?: MotionEasingVariable[];
|
|
957
966
|
motionDefaultEase?: string;
|
|
@@ -5775,9 +5784,16 @@ export function DesignCanvas({
|
|
|
5775
5784
|
return;
|
|
5776
5785
|
}
|
|
5777
5786
|
if (e.data.type === "prototype-navigate") {
|
|
5787
|
+
const sourceFrame = iframeRef.current;
|
|
5788
|
+
if (!sourceFrame || sourceFrame.contentWindow !== e.source) return;
|
|
5789
|
+
const transition = decodeMotionPrototypeTransitionAttribute(
|
|
5790
|
+
e.data.transition,
|
|
5791
|
+
);
|
|
5778
5792
|
onPrototypeNavigate?.(
|
|
5779
5793
|
String(e.data.screen || ""),
|
|
5780
5794
|
String(e.data.href || ""),
|
|
5795
|
+
transition,
|
|
5796
|
+
sourceFrame,
|
|
5781
5797
|
);
|
|
5782
5798
|
return;
|
|
5783
5799
|
}
|
|
@@ -8517,6 +8533,7 @@ export function DesignCanvas({
|
|
|
8517
8533
|
? ""
|
|
8518
8534
|
: undefined,
|
|
8519
8535
|
}}
|
|
8536
|
+
data-an-prototype-preview={interactMode ? "" : undefined}
|
|
8520
8537
|
data-screen-iframe-id={
|
|
8521
8538
|
boardSurface
|
|
8522
8539
|
? undefined
|
|
@@ -92,14 +92,14 @@
|
|
|
92
92
|
|
|
93
93
|
e.preventDefault();
|
|
94
94
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
95
|
+
var message: Record<string, unknown> = {
|
|
96
|
+
type: "prototype-navigate",
|
|
97
|
+
href: target,
|
|
98
|
+
screen: screenName(target),
|
|
99
|
+
};
|
|
100
|
+
var transition = link.getAttribute("data-an-prototype-transition");
|
|
101
|
+
if (transition !== null) message.transition = transition;
|
|
102
|
+
(window.parent as Window).postMessage(message, "*");
|
|
103
103
|
} catch (_err) {
|
|
104
104
|
// coercion-ok: the editor is gone, so there is no screen to switch to.
|
|
105
105
|
return;
|
|
@@ -684,6 +684,14 @@ export default function PromptPopover({
|
|
|
684
684
|
const showCreativeContextPicker =
|
|
685
685
|
Boolean(onCreativeContextChange) &&
|
|
686
686
|
(creativeContextsLoading || creativeContexts.length > 0);
|
|
687
|
+
const creationModeToggle =
|
|
688
|
+
creationMode && onCreationModeChange ? (
|
|
689
|
+
<CreationModeToggle
|
|
690
|
+
mode={creationMode}
|
|
691
|
+
onChange={onCreationModeChange}
|
|
692
|
+
disabled={loading || uploading || submitting}
|
|
693
|
+
/>
|
|
694
|
+
) : null;
|
|
687
695
|
const Composer = composerComponent ?? LazyPromptComposer;
|
|
688
696
|
const promptComposer = (
|
|
689
697
|
<Composer
|
|
@@ -729,25 +737,19 @@ export default function PromptPopover({
|
|
|
729
737
|
: `seed:${initialTextKey ?? 0}`
|
|
730
738
|
}
|
|
731
739
|
contextMenuItems={contextMenuItems ?? []}
|
|
740
|
+
// Inside the composer so an attached setup card above meets the box directly.
|
|
741
|
+
toolbarSlot={inline ? creationModeToggle : undefined}
|
|
732
742
|
/>
|
|
733
743
|
);
|
|
734
744
|
|
|
735
745
|
const content = (
|
|
736
746
|
<>
|
|
737
|
-
{
|
|
747
|
+
{!inline && (
|
|
738
748
|
<div className="flex items-center justify-between gap-2 px-3.5 pt-3 pb-2">
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
)}
|
|
744
|
-
{creationMode && onCreationModeChange ? (
|
|
745
|
-
<CreationModeToggle
|
|
746
|
-
mode={creationMode}
|
|
747
|
-
onChange={onCreationModeChange}
|
|
748
|
-
disabled={loading || uploading || submitting}
|
|
749
|
-
/>
|
|
750
|
-
) : null}
|
|
749
|
+
<span className="text-sm font-medium text-foreground/90">
|
|
750
|
+
{title}
|
|
751
|
+
</span>
|
|
752
|
+
{creationModeToggle}
|
|
751
753
|
</div>
|
|
752
754
|
)}
|
|
753
755
|
|
|
@@ -1459,7 +1459,7 @@ export default {
|
|
|
1459
1459
|
sourceWriteUnavailable:
|
|
1460
1460
|
"Motion code can currently be written only to inline HTML designs.",
|
|
1461
1461
|
unresolvedStylesheets:
|
|
1462
|
-
"Resolve external
|
|
1462
|
+
"Resolve external or runtime-generated styles before editing or adding Motion.",
|
|
1463
1463
|
sourceConflict:
|
|
1464
1464
|
"Motion source changed while this timeline had unsaved edits. Reload from code to discard those edits and use the current source.",
|
|
1465
1465
|
reloadFromSource: "Reload from code",
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useSetPageTitle,
|
|
15
15
|
} from "@agent-native/toolkit/app-shell";
|
|
16
16
|
import { ShareButton } from "@agent-native/toolkit/app/sharing";
|
|
17
|
-
import { VisibilityBadge } from "@agent-native/toolkit/sharing";
|
|
17
|
+
import { VisibilityBadge } from "@agent-native/toolkit/app/sharing/VisibilityBadge";
|
|
18
18
|
import {
|
|
19
19
|
IconCheckbox,
|
|
20
20
|
IconChecks,
|
|
@@ -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";
|
|
@@ -616,11 +614,6 @@ export default function Index() {
|
|
|
616
614
|
? chosenDesignSystemId
|
|
617
615
|
: templateDesignSystemId;
|
|
618
616
|
|
|
619
|
-
const ownerFilterLabels: Record<DesignFilter, string> = {
|
|
620
|
-
all: t("home.ownedByAnyone"),
|
|
621
|
-
mine: t("home.ownedByMe"),
|
|
622
|
-
"not-mine": t("home.sharedWithMe"),
|
|
623
|
-
};
|
|
624
617
|
const showAuthors = designFilter !== "mine";
|
|
625
618
|
const selectedDesignCount = selectedDesignIds.size;
|
|
626
619
|
const isSelectingDesigns = selectedDesignCount > 0;
|
|
@@ -743,17 +736,19 @@ export default function Index() {
|
|
|
743
736
|
);
|
|
744
737
|
}, []);
|
|
745
738
|
|
|
746
|
-
const handleDesignFilterChange = useCallback(
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
);
|
|
757
752
|
|
|
758
753
|
useEffect(() => {
|
|
759
754
|
if (
|
|
@@ -1572,51 +1567,10 @@ export default function Index() {
|
|
|
1572
1567
|
}
|
|
1573
1568
|
search={searchInput}
|
|
1574
1569
|
recentActions={
|
|
1575
|
-
<
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
variant="outline"
|
|
1580
|
-
size="sm"
|
|
1581
|
-
aria-label={ownerFilterLabels[designFilter]}
|
|
1582
|
-
className="h-9 shrink-0 gap-2"
|
|
1583
|
-
>
|
|
1584
|
-
<span className="grid">
|
|
1585
|
-
{Object.entries(ownerFilterLabels).map(
|
|
1586
|
-
([filter, label]) => (
|
|
1587
|
-
<span
|
|
1588
|
-
key={filter}
|
|
1589
|
-
aria-hidden="true"
|
|
1590
|
-
className="invisible col-start-1 row-start-1 whitespace-nowrap"
|
|
1591
|
-
>
|
|
1592
|
-
{label}
|
|
1593
|
-
</span>
|
|
1594
|
-
),
|
|
1595
|
-
)}
|
|
1596
|
-
<span className="col-start-1 row-start-1 whitespace-nowrap">
|
|
1597
|
-
{ownerFilterLabels[designFilter]}
|
|
1598
|
-
</span>
|
|
1599
|
-
</span>
|
|
1600
|
-
<IconChevronDown className="size-4" aria-hidden="true" />
|
|
1601
|
-
</Button>
|
|
1602
|
-
</DropdownMenuTrigger>
|
|
1603
|
-
<DropdownMenuContent align="start">
|
|
1604
|
-
<DropdownMenuRadioGroup
|
|
1605
|
-
value={designFilter}
|
|
1606
|
-
onValueChange={handleDesignFilterChange}
|
|
1607
|
-
>
|
|
1608
|
-
<DropdownMenuRadioItem value="all" indicator="check">
|
|
1609
|
-
{ownerFilterLabels.all}
|
|
1610
|
-
</DropdownMenuRadioItem>
|
|
1611
|
-
<DropdownMenuRadioItem value="mine" indicator="check">
|
|
1612
|
-
{ownerFilterLabels.mine}
|
|
1613
|
-
</DropdownMenuRadioItem>
|
|
1614
|
-
<DropdownMenuRadioItem value="not-mine" indicator="check">
|
|
1615
|
-
{ownerFilterLabels["not-mine"]}
|
|
1616
|
-
</DropdownMenuRadioItem>
|
|
1617
|
-
</DropdownMenuRadioGroup>
|
|
1618
|
-
</DropdownMenuContent>
|
|
1619
|
-
</DropdownMenu>
|
|
1570
|
+
<OwnershipFilter
|
|
1571
|
+
value={designFilter === "not-mine" ? "shared" : designFilter}
|
|
1572
|
+
onChange={handleDesignFilterChange}
|
|
1573
|
+
/>
|
|
1620
1574
|
}
|
|
1621
1575
|
templates={
|
|
1622
1576
|
templatesError ? (
|