@agent-native/core 0.154.4 → 0.155.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/account-health/SKILL.md +76 -0
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +3 -0
- package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +2 -1
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +6 -5
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/account-deep-dive.ts +3 -2
- package/corpus/templates/analytics/actions/bigquery.ts +1 -1
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +4 -1
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +12 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +5 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer/property-label.ts +32 -0
- package/corpus/templates/analytics/changelog/2026-08-13-account-health-readouts-verify-customer-scope-and-contract-metrics.md +7 -0
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +43 -17
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +17 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/calendar/AGENTS.md +2 -0
- package/corpus/templates/calendar/DEVELOPING.md +4 -4
- package/corpus/templates/calendar/actions/get-settings.ts +4 -11
- package/corpus/templates/calendar/actions/update-availability.ts +15 -4
- package/corpus/templates/calendar/actions/update-settings.ts +15 -4
- package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +9 -3
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +21 -19
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +9 -6
- package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +25 -3
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +10 -3
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +9 -9
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -3
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +10 -4
- package/corpus/templates/calendar/app/i18n-data.ts +30 -0
- package/corpus/templates/calendar/app/lib/availability-schedule.ts +53 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +5 -0
- package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +90 -28
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +135 -61
- package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +30 -15
- package/corpus/templates/calendar/app/pages/Settings.tsx +60 -1
- package/corpus/templates/calendar/changelog/2026-08-13-find-a-time-now-closes-from-a-clear-header-action.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +2 -1
- package/corpus/templates/calendar/server/handlers/settings.ts +12 -16
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/calendar/shared/availability-schedule.ts +64 -0
- package/corpus/templates/calendar/shared/calendar-week.ts +21 -0
- package/corpus/templates/calendar/shared/settings.ts +44 -0
- package/corpus/templates/clips/AGENTS.md +2 -0
- package/corpus/templates/clips/actions/add-comment.ts +5 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +31 -1
- package/corpus/templates/clips/actions/get-recording-player-data.ts +9 -0
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +21 -1
- package/corpus/templates/clips/actions/reply-to-comment.ts +5 -2
- package/corpus/templates/clips/actions/update-comment.ts +9 -2
- package/corpus/templates/clips/app/components/library/search-bar.tsx +26 -13
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +135 -3
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +5 -99
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +7 -3
- package/corpus/templates/clips/app/components/player/video-player.tsx +84 -24
- package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +11 -3
- package/corpus/templates/clips/app/i18n/en-US.ts +12 -0
- package/corpus/templates/clips/app/lib/public-meeting.ts +11 -3
- package/corpus/templates/clips/app/lib/search-focus.ts +15 -0
- package/corpus/templates/clips/app/root.tsx +2 -1
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +94 -26
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +24 -7
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +14 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +8 -4
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +72 -26
- package/corpus/templates/clips/changelog/2026-08-13-inline-markdown-comments.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-13-private-meeting-agent-links.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +116 -19
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +13 -29
- package/corpus/templates/clips/desktop/src/lib/recording-request.ts +39 -0
- package/corpus/templates/clips/desktop/src/lib/settings-navigation.ts +11 -0
- package/corpus/templates/clips/desktop/src/lib/whisper-model-picker.ts +7 -0
- package/corpus/templates/clips/desktop/src/styles.css +117 -0
- package/corpus/templates/clips/server/db/index.ts +10 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/recordings.ts +3 -1
- package/corpus/templates/clips/server/lib/seekable-media-state.ts +90 -0
- package/corpus/templates/clips/server/plugins/db.ts +5 -0
- package/corpus/templates/clips/server/routes/api/public-meeting.get.ts +37 -4
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +16 -5
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +3 -0
- package/corpus/templates/clips/shared/meeting-agent-access.ts +5 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +2 -2
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +20 -4
- package/corpus/templates/content/actions/_database-row-mutation.ts +1490 -0
- package/corpus/templates/content/actions/_database-utils.ts +44 -0
- package/corpus/templates/content/actions/_property-utils.ts +4 -1
- package/corpus/templates/content/actions/add-comment.ts +2 -1
- package/corpus/templates/content/actions/add-database-item.ts +62 -240
- package/corpus/templates/content/actions/configure-document-property.ts +165 -1
- package/corpus/templates/content/actions/delete-document-property.ts +18 -0
- package/corpus/templates/content/actions/set-document-property.ts +78 -1
- package/corpus/templates/content/actions/update-comment.ts +1 -1
- package/corpus/templates/content/actions/update-database-item.ts +62 -0
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +43 -796
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +24 -15
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +10 -8
- package/corpus/templates/content/app/hooks/use-content-database.ts +33 -32
- package/corpus/templates/content/changelog/2026-08-10-database-row-actions-now-validate-exact-schemas-and-safe-ret.md +6 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +2 -0
- package/corpus/templates/content/server/db/schema.ts +39 -3
- package/corpus/templates/content/server/plugins/db.ts +30 -0
- package/corpus/templates/content/shared/api.ts +75 -2
- package/corpus/templates/design/actions/generate-design.ts +10 -7
- package/corpus/templates/design/actions/present-design-variants.ts +35 -6
- package/corpus/templates/design/actions/run-design-audit.ts +33 -0
- package/corpus/templates/design/changelog/2026-08-13-the-design-audit-now-catches-a-screen-that-renders-behind-an.md +6 -0
- package/corpus/templates/design/shared/design-review.ts +1 -0
- package/corpus/templates/design/shared/html-integrity.ts +66 -0
- package/corpus/templates/dispatch/.env.example +5 -0
- package/corpus/templates/dispatch/AGENTS.md +24 -19
- package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
- package/corpus/templates/dispatch/server/lib/identity-sso.ts +21 -90
- package/corpus/templates/dispatch/server/plugins/identity-sso.ts +3 -12
- package/corpus/templates/dispatch/shared/feature-flags.ts +4 -0
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +23 -0
- package/corpus/templates/factory/actions/view-screen.ts +17 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +239 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +2 -0
- package/corpus/templates/factory/app/components/factory/FactoryInspector.tsx +130 -7
- package/corpus/templates/factory/app/i18n/en-US.ts +20 -0
- package/corpus/templates/factory/app/routes/factory.tsx +41 -0
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/server/factory-graph/contracts.ts +2 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -0
- package/corpus/templates/factory/server/plugins/dispatch-db.ts +1 -0
- package/corpus/templates/factory/server/plugins/setup-dispatch.ts +3 -0
- package/corpus/templates/mail/app/lib/native-mcp-exclusions.ts +3 -0
- package/corpus/templates/mail/vite.config.ts +7 -0
- package/corpus/templates/plan/actions/reply-to-plan-comment.ts +7 -2
- package/corpus/templates/plan/actions/resolve-plan-comment.ts +2 -2
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/app/pages/PlansPage.tsx +29 -20
- package/corpus/templates/slides/actions/add-slide-comment.ts +1 -1
- package/corpus/templates/slides/actions/update-slide-comment.ts +1 -1
- package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +9 -6
- package/dist/action.d.ts +22 -0
- package/dist/action.js +27 -0
- package/dist/agent/production-agent.js +5 -1
- package/dist/agent/thread-data-builder.js +17 -1
- package/dist/cli/skills-content/help.js +6 -4
- package/dist/cli/skills.js +17 -2
- package/dist/cli/workspacify.d.ts +5 -0
- package/dist/cli/workspacify.js +65 -8
- package/dist/client/AgentPanel.d.ts +5 -0
- package/dist/client/AgentPanel.js +20 -4
- package/dist/client/AssistantChat.d.ts +21 -0
- package/dist/client/AssistantChat.js +66 -68
- package/dist/client/active-run-state.d.ts +14 -1
- package/dist/client/active-run-state.js +0 -0
- package/dist/client/agent-chat-adapter.js +107 -5
- package/dist/client/analytics.js +9 -2
- package/dist/client/chat/tool-call-display.js +4 -4
- package/dist/client/chat-first/apps-rail.js +9 -4
- package/dist/client/chat-first/primary-nav.d.ts +2 -1
- package/dist/client/chat-first/primary-nav.js +3 -3
- package/dist/client/chat-first/types.d.ts +5 -1
- package/dist/client/markdown/InlineMarkdown.d.ts +23 -0
- package/dist/client/markdown/InlineMarkdown.js +65 -0
- package/dist/client/markdown/index.d.ts +1 -0
- package/dist/client/markdown/index.js +1 -0
- package/dist/client/review/ReviewThreadPanel.js +2 -1
- package/dist/client/run-stream-ownership.d.ts +39 -0
- package/dist/client/run-stream-ownership.js +65 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +10 -0
- package/dist/feature-flags/store.js +112 -1
- package/dist/guards/no-env-credentials.js +4 -0
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/integrations/adapters/slack.js +5 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/resources/agents.js +40 -5
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/metadata.d.ts +10 -0
- package/dist/resources/metadata.js +7 -3
- package/dist/review/actions/create-review-comment.js +1 -1
- package/dist/review/actions/reply-review-comment.js +1 -1
- package/dist/review/actions/resolve-review-thread.js +1 -1
- package/dist/server/action-routes.js +42 -8
- package/dist/server/agent-discovery.js +14 -3
- package/dist/server/core-routes-plugin.js +1 -1
- package/dist/server/identity-sso-migrations.d.ts +7 -0
- package/dist/server/identity-sso-migrations.js +36 -0
- package/dist/server/identity-sso-store.d.ts +3 -2
- package/dist/server/identity-sso-store.js +8 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/release-migrations.js +4 -0
- package/dist/tracking/providers.js +13 -3
- package/dist/workspace-files/tool.js +1 -1
- package/package.json +3 -2
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
|
|
2
2
|
import { emailToName } from "@agent-native/core/client/collab";
|
|
3
3
|
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
+
import {
|
|
5
|
+
InlineMarkdown,
|
|
6
|
+
type InlineMarkdownProtectedSpan,
|
|
7
|
+
} from "@agent-native/core/client/markdown";
|
|
4
8
|
import {
|
|
5
9
|
IconCheck,
|
|
6
10
|
IconMessageCircle,
|
|
@@ -40,23 +44,28 @@ import { CommentComposer, type MentionEntry } from "./CommentComposer";
|
|
|
40
44
|
|
|
41
45
|
/**
|
|
42
46
|
* Render a comment body, styling any `@mention` tokens that match the comment's
|
|
43
|
-
* stored mentions.
|
|
47
|
+
* stored mentions. Raw HTML is never interpreted.
|
|
44
48
|
*/
|
|
45
|
-
function
|
|
49
|
+
function commentMentionSpans(
|
|
50
|
+
mentions: CommentMention[],
|
|
51
|
+
): InlineMarkdownProtectedSpan[] {
|
|
46
52
|
const labels = Array.from(
|
|
47
53
|
new Set(mentions.map((m) => m.name).filter((n): n is string => !!n)),
|
|
48
54
|
).sort((a, b) => b.length - a.length);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
return labels.map((label) => ({
|
|
56
|
+
source: `@${label}`,
|
|
57
|
+
label: `@${label}`,
|
|
58
|
+
className: "comment-mention",
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderCommentBody(content: string, mentions: CommentMention[]) {
|
|
63
|
+
return (
|
|
64
|
+
<InlineMarkdown
|
|
65
|
+
content={content}
|
|
66
|
+
inline
|
|
67
|
+
protectedSpans={commentMentionSpans(mentions)}
|
|
68
|
+
/>
|
|
60
69
|
);
|
|
61
70
|
}
|
|
62
71
|
|
|
@@ -942,9 +951,9 @@ function ThreadView({
|
|
|
942
951
|
{formatDate(c.created_at)}
|
|
943
952
|
</span>
|
|
944
953
|
</div>
|
|
945
|
-
<
|
|
954
|
+
<div className="text-[13px] text-foreground/90 pl-8 leading-relaxed">
|
|
946
955
|
{renderCommentBody(c.content, c.mentions)}
|
|
947
|
-
</
|
|
956
|
+
</div>
|
|
948
957
|
</div>
|
|
949
958
|
))}
|
|
950
959
|
</div>
|
|
@@ -1631,6 +1631,11 @@ function DatabaseTable({
|
|
|
1631
1631
|
) {
|
|
1632
1632
|
if (!databaseId) return null;
|
|
1633
1633
|
if (isWorkspaceCatalog) return null;
|
|
1634
|
+
const mutationContract = data?.mutationContract;
|
|
1635
|
+
if (!mutationContract) {
|
|
1636
|
+
toast.error(dbText("failedToCreateRow"));
|
|
1637
|
+
return null;
|
|
1638
|
+
}
|
|
1634
1639
|
const propertyValues = {
|
|
1635
1640
|
...databasePropertyValuesForNewItem(filters, properties, filterMode),
|
|
1636
1641
|
...propertyValueOverrides,
|
|
@@ -1638,8 +1643,10 @@ function DatabaseTable({
|
|
|
1638
1643
|
let response;
|
|
1639
1644
|
try {
|
|
1640
1645
|
response = await addItem.mutateAsync({
|
|
1641
|
-
|
|
1642
|
-
|
|
1646
|
+
target: mutationContract.target,
|
|
1647
|
+
expectedSchemaRevision: mutationContract.schemaRevision,
|
|
1648
|
+
idempotencyKey: crypto.randomUUID(),
|
|
1649
|
+
...(title.trim() ? { title } : {}),
|
|
1643
1650
|
propertyValues:
|
|
1644
1651
|
Object.keys(propertyValues).length > 0 ? propertyValues : undefined,
|
|
1645
1652
|
});
|
|
@@ -1650,12 +1657,7 @@ function DatabaseTable({
|
|
|
1650
1657
|
});
|
|
1651
1658
|
return null;
|
|
1652
1659
|
}
|
|
1653
|
-
const createdItem =
|
|
1654
|
-
databaseId,
|
|
1655
|
-
parentDocument: document,
|
|
1656
|
-
title,
|
|
1657
|
-
propertyValues,
|
|
1658
|
-
});
|
|
1660
|
+
const createdItem = response.createdItem ?? null;
|
|
1659
1661
|
const needsPreview =
|
|
1660
1662
|
!!createdItem &&
|
|
1661
1663
|
databaseCreatedItemNeedsPreview(items, createdItem, options);
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
CancelPreparedBuilderSourceUpdateResponse,
|
|
13
13
|
ChangeContentDatabaseSourceRoleRequest,
|
|
14
14
|
ContentDatabaseResponse,
|
|
15
|
+
ContentDatabaseRowMutationResult,
|
|
15
16
|
ContentDatabaseSourceAttachmentAck,
|
|
16
17
|
ContentDatabaseSourceAttachmentResult,
|
|
17
18
|
ContentDatabaseItemsPageResponse,
|
|
@@ -819,40 +820,40 @@ export function useTrashedContentDatabases() {
|
|
|
819
820
|
|
|
820
821
|
export function useAddDatabaseItem(documentId: string) {
|
|
821
822
|
const queryClient = useQueryClient();
|
|
822
|
-
return useActionMutation<
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
)
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
823
|
+
return useActionMutation<
|
|
824
|
+
ContentDatabaseRowMutationResult,
|
|
825
|
+
AddDatabaseItemRequest
|
|
826
|
+
>("add-database-item", {
|
|
827
|
+
skipActionQueryInvalidation: true,
|
|
828
|
+
onSuccess: (data) => {
|
|
829
|
+
if (data.createdItem) {
|
|
830
|
+
queryClient.setQueriesData<ContentDatabaseResponse>(
|
|
831
|
+
{
|
|
832
|
+
queryKey: ["action", "get-content-database"],
|
|
833
|
+
predicate: (query) => {
|
|
834
|
+
if (
|
|
835
|
+
!isContentDatabaseQueryForDocument(query.queryKey, documentId)
|
|
836
|
+
) {
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
const params = query.queryKey[2] as {
|
|
840
|
+
tableQuery?: unknown;
|
|
841
|
+
};
|
|
842
|
+
return params.tableQuery === undefined;
|
|
842
843
|
},
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
}
|
|
844
|
+
},
|
|
845
|
+
(current) =>
|
|
846
|
+
applyOptimisticItemToContentDatabase(current, data.createdItem!),
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
queryClient.invalidateQueries({
|
|
850
|
+
queryKey: contentDatabaseQueryKey(documentId),
|
|
851
|
+
});
|
|
852
|
+
queryClient.invalidateQueries({
|
|
853
|
+
queryKey: ["action", "list-documents"],
|
|
854
|
+
});
|
|
854
855
|
},
|
|
855
|
-
);
|
|
856
|
+
});
|
|
856
857
|
}
|
|
857
858
|
|
|
858
859
|
export function useSubmitContentDatabaseForm(documentId: string) {
|
|
@@ -9,7 +9,7 @@ This generated matrix tracks whether high-value Content UI operations use the sa
|
|
|
9
9
|
| database.lifecycle-and-trash | database | Create, soft-delete, restore, list, and inspect content databases | action-backed | `create-content-database`, `create-inline-content-database`, `delete-content-database`, `get-content-database`, `list-content-databases`, `list-trashed-content-databases`, `restore-content-database` | `app/components/editor/SlashCommandMenu.tsx`, `app/hooks/use-content-database.ts`, `app/hooks/use-documents.ts` | Database pages and database records are created, read, soft-deleted, restored, and listed. | - | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts` | `database-source-scope` | - |
|
|
10
10
|
| database.private-preview-drafts | database | Persist and reconcile a user's private database-page preview draft | action-backed | `get-preview-document-draft`, `update-preview-document-draft` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-documents.ts` | A user's private preview draft is read, saved, conflict-checked, or deleted without changing the shared database page until the normal save flow applies it. | These per-user editor-state actions are intentionally hidden from agent tools because preview drafts are a private UI recovery mechanism. | - | P1 | covered | `actions/preview-document-draft.db.test.ts` | - | - |
|
|
11
11
|
| database.properties-and-view-config | database | Configure properties, values, ordering, and saved views | action-backed | `configure-document-property`, `delete-document-property`, `duplicate-document-property`, `get-content-database-personal-view`, `list-document-properties`, `reorder-document-property`, `set-document-property`, `update-content-database-personal-view`, `update-content-database-view` | `app/components/editor/DocumentProperties.tsx`, `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-document-properties.ts` | Property schemas, property values, property order, filters, sorts, grouping, hidden columns, view type, and view settings are stored. | - | - | P0 | covered | `actions/bind-content-database-source-field.db.test.ts`, `actions/content-database-source-actions.test.ts`, `actions/resync-content-database-source.db.test.ts` | `database-source-scope` | - |
|
|
12
|
-
| database.rows | database | Add, duplicate, move, open, edit, and remove database rows | action-backed | `add-database-item`, `upsert-database-item-by-key`, `remove-database-items`, `duplicate-database-items`, `duplicate-database-item`, `migrate-content-database-rows`, `move-database-item`, `set-document-property`
|
|
12
|
+
| database.rows | database | Add, duplicate, move, open, edit, and remove database rows | action-backed | `add-database-item`, `update-database-item`, `upsert-database-item-by-key`, `remove-database-items`, `duplicate-database-items`, `duplicate-database-item`, `migrate-content-database-rows`, `move-database-item`, `set-document-property` | `app/components/editor/DocumentDatabase.tsx`, `app/components/editor/database/DatabaseView.tsx` | Database row memberships and ordering are created, duplicated, moved, edited, and removed without deleting the backing page; bounded migrations atomically update row bodies and properties through the same canonical data model. | - | - | P0 | covered | `actions/database-row-batch-actions.db.test.ts`, `actions/upsert-database-item-by-key.db.test.ts`, `actions/migrate-content-database-rows.db.test.ts`, `parity/__tests__/database-row-batch-reliability.test.ts` | `database-bulk-row-reliability` | - |
|
|
13
13
|
| database.table-query-page | database | Query one constrained page while retaining database metadata | action-backed | `query-content-database-items` | `app/components/editor/database/DatabaseView.tsx`, `app/hooks/use-content-database.ts` | - | This UI-only bounded projection is intentionally hidden with agentTool: false; agents use get-content-database for the complete database contract. | - | P0 | covered | `actions/content-database-lifecycle.db.test.ts`, `app/hooks/use-content-database.test.ts` | - | - |
|
|
14
14
|
| editor.agent-assist-prompts | editor | Ask AI from slash generation or comment context | client-assist | - | `app/components/editor/SlashCommandMenu.tsx`, `app/components/editor/CommentsSidebar.tsx` | No direct durable mutation; the prompt asks the agent to use document actions when it decides to write. | - | - | P1 | none | - | - | - |
|
|
15
15
|
| editor.client-formatting-and-insertions | editor | Rich text formatting, selection state, slash block insertion, and copy actions | client-only-ephemeral | - | `app/components/editor/BubbleToolbar.tsx`, `app/components/editor/SlashCommandMenu.tsx`, `app/components/editor/DocumentToolbar.tsx` | - | - | - | P1 | none | - | - | - |
|
|
@@ -345,6 +345,7 @@ export const parityMatrix: ParityRow[] = [
|
|
|
345
345
|
status: "action-backed",
|
|
346
346
|
actions: [
|
|
347
347
|
"add-database-item",
|
|
348
|
+
"update-database-item",
|
|
348
349
|
"upsert-database-item-by-key",
|
|
349
350
|
"remove-database-items",
|
|
350
351
|
"duplicate-database-items",
|
|
@@ -360,6 +361,7 @@ export const parityMatrix: ParityRow[] = [
|
|
|
360
361
|
followUpPR: null,
|
|
361
362
|
coverageRefs: [
|
|
362
363
|
"actions/database-row-batch-actions.db.test.ts",
|
|
364
|
+
"actions/upsert-database-item-by-key.db.test.ts",
|
|
363
365
|
"actions/migrate-content-database-rows.db.test.ts",
|
|
364
366
|
"parity/__tests__/database-row-batch-reliability.test.ts",
|
|
365
367
|
],
|
|
@@ -234,6 +234,7 @@ export const contentDatabases = table(
|
|
|
234
234
|
ownerBlockId: text("owner_block_id"),
|
|
235
235
|
title: text("title").notNull().default("Untitled database"),
|
|
236
236
|
systemRole: text("system_role"),
|
|
237
|
+
naturalKeyPropertyId: text("natural_key_property_id"),
|
|
237
238
|
viewConfigJson: text("view_config_json").notNull().default("{}"),
|
|
238
239
|
filesSystemPropertiesSeeded: integer("files_system_properties_seeded")
|
|
239
240
|
.notNull()
|
|
@@ -287,9 +288,9 @@ export const contentDatabaseItems = table(
|
|
|
287
288
|
],
|
|
288
289
|
);
|
|
289
290
|
|
|
290
|
-
// Opt-in stable-key claims are the durable concurrency fence for
|
|
291
|
-
//
|
|
292
|
-
// property
|
|
291
|
+
// Opt-in stable-key claims are the durable concurrency fence for configured
|
|
292
|
+
// natural-key upserts. Ordinary property editing stays independent until a
|
|
293
|
+
// text property is explicitly selected as the database's natural key.
|
|
293
294
|
export const contentDatabaseItemKeyClaims = table(
|
|
294
295
|
"content_database_item_key_claims",
|
|
295
296
|
{
|
|
@@ -494,6 +495,41 @@ export const contentDatabaseMigrationReceipts = table(
|
|
|
494
495
|
],
|
|
495
496
|
);
|
|
496
497
|
|
|
498
|
+
export const contentDatabaseRowMutationReceipts = table(
|
|
499
|
+
"content_database_row_mutation_receipts",
|
|
500
|
+
{
|
|
501
|
+
id: text("id").primaryKey(),
|
|
502
|
+
ownerEmail: text("owner_email").notNull().default("local@localhost"),
|
|
503
|
+
orgId: text("org_id"),
|
|
504
|
+
spaceId: text("space_id").notNull(),
|
|
505
|
+
databaseId: text("database_id").notNull(),
|
|
506
|
+
databaseDocumentId: text("database_document_id").notNull(),
|
|
507
|
+
operation: text("operation").notNull(),
|
|
508
|
+
itemId: text("item_id").notNull(),
|
|
509
|
+
documentId: text("document_id").notNull(),
|
|
510
|
+
idempotencyKey: text("idempotency_key").notNull(),
|
|
511
|
+
payloadDigest: text("payload_digest").notNull(),
|
|
512
|
+
schemaRevision: text("schema_revision").notNull(),
|
|
513
|
+
preRowRevision: text("pre_row_revision"),
|
|
514
|
+
postRowRevision: text("post_row_revision").notNull(),
|
|
515
|
+
resultJson: text("result_json").notNull().default("{}"),
|
|
516
|
+
createdAt: text("created_at").notNull().default(now()),
|
|
517
|
+
updatedAt: text("updated_at").notNull().default(now()),
|
|
518
|
+
},
|
|
519
|
+
(receipt) => [
|
|
520
|
+
uniqueIndex(
|
|
521
|
+
"content_database_row_mutation_receipts_database_key_unique",
|
|
522
|
+
).on(receipt.databaseId, receipt.idempotencyKey),
|
|
523
|
+
index("content_database_row_mutation_receipts_owner_database_idx").on(
|
|
524
|
+
receipt.ownerEmail,
|
|
525
|
+
receipt.databaseId,
|
|
526
|
+
),
|
|
527
|
+
index("content_database_row_mutation_receipts_document_idx").on(
|
|
528
|
+
receipt.documentId,
|
|
529
|
+
),
|
|
530
|
+
],
|
|
531
|
+
);
|
|
532
|
+
|
|
497
533
|
export const documentPropertyValues = table("document_property_values", {
|
|
498
534
|
id: text("id").primaryKey(),
|
|
499
535
|
ownerEmail: text("owner_email").notNull().default("local@localhost"),
|
|
@@ -1011,6 +1011,36 @@ export const runContentMigrations = runMigrations(
|
|
|
1011
1011
|
ALTER TABLE document_blocks ALTER COLUMN addressable SET DEFAULT true`,
|
|
1012
1012
|
},
|
|
1013
1013
|
},
|
|
1014
|
+
{
|
|
1015
|
+
version: 84,
|
|
1016
|
+
name: "content-database-row-mutation-contract",
|
|
1017
|
+
sql: `ALTER TABLE content_databases ADD COLUMN IF NOT EXISTS natural_key_property_id TEXT;
|
|
1018
|
+
CREATE TABLE IF NOT EXISTS content_database_row_mutation_receipts (
|
|
1019
|
+
id TEXT PRIMARY KEY,
|
|
1020
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1021
|
+
org_id TEXT,
|
|
1022
|
+
space_id TEXT NOT NULL,
|
|
1023
|
+
database_id TEXT NOT NULL,
|
|
1024
|
+
database_document_id TEXT NOT NULL,
|
|
1025
|
+
operation TEXT NOT NULL,
|
|
1026
|
+
item_id TEXT NOT NULL,
|
|
1027
|
+
document_id TEXT NOT NULL,
|
|
1028
|
+
idempotency_key TEXT NOT NULL,
|
|
1029
|
+
payload_digest TEXT NOT NULL,
|
|
1030
|
+
schema_revision TEXT NOT NULL,
|
|
1031
|
+
pre_row_revision TEXT,
|
|
1032
|
+
post_row_revision TEXT NOT NULL,
|
|
1033
|
+
result_json TEXT NOT NULL DEFAULT '{}',
|
|
1034
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1035
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1036
|
+
);
|
|
1037
|
+
CREATE UNIQUE INDEX IF NOT EXISTS content_database_row_mutation_receipts_database_key_unique
|
|
1038
|
+
ON content_database_row_mutation_receipts (database_id, idempotency_key);
|
|
1039
|
+
CREATE INDEX IF NOT EXISTS content_database_row_mutation_receipts_owner_database_idx
|
|
1040
|
+
ON content_database_row_mutation_receipts (owner_email, database_id);
|
|
1041
|
+
CREATE INDEX IF NOT EXISTS content_database_row_mutation_receipts_document_idx
|
|
1042
|
+
ON content_database_row_mutation_receipts (document_id)`,
|
|
1043
|
+
},
|
|
1014
1044
|
],
|
|
1015
1045
|
{ table: "content_migrations" },
|
|
1016
1046
|
);
|
|
@@ -225,6 +225,7 @@ export interface ConfigureDocumentPropertyRequest {
|
|
|
225
225
|
description?: string;
|
|
226
226
|
visibility?: DocumentPropertyVisibility;
|
|
227
227
|
options?: DocumentPropertyOptions;
|
|
228
|
+
naturalKey?: boolean;
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
export interface SetDocumentPropertyRequest {
|
|
@@ -258,8 +259,10 @@ export interface ReorderDocumentPropertyRequest {
|
|
|
258
259
|
export interface ContentDatabase {
|
|
259
260
|
id: string;
|
|
260
261
|
documentId: string;
|
|
262
|
+
spaceId?: string | null;
|
|
261
263
|
title: string;
|
|
262
264
|
systemRole?: string | null;
|
|
265
|
+
naturalKeyPropertyId?: string | null;
|
|
263
266
|
description?: string;
|
|
264
267
|
viewConfig: ContentDatabaseViewConfig;
|
|
265
268
|
createdAt: string;
|
|
@@ -469,6 +472,62 @@ export interface ContentDatabaseItem {
|
|
|
469
472
|
// a secondary source contributes on top of it. Absent for non-federated rows.
|
|
470
473
|
canonicalKey?: string | null;
|
|
471
474
|
sourceOverlays?: ContentDatabaseSourceOverlay[];
|
|
475
|
+
rowRevision?: string;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
export interface ContentDatabaseMutationTarget {
|
|
479
|
+
authorityScope:
|
|
480
|
+
| { kind: "personal"; id: string }
|
|
481
|
+
| { kind: "organization"; id: string };
|
|
482
|
+
spaceId: string;
|
|
483
|
+
databaseId: string;
|
|
484
|
+
databaseDocumentId: string;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export interface ContentDatabaseMutationContract {
|
|
488
|
+
target: ContentDatabaseMutationTarget;
|
|
489
|
+
schemaRevision: string;
|
|
490
|
+
naturalKeyPropertyId: string | null;
|
|
491
|
+
properties: Array<{
|
|
492
|
+
id: string;
|
|
493
|
+
name: string;
|
|
494
|
+
type: DocumentPropertyType;
|
|
495
|
+
writable: boolean;
|
|
496
|
+
sourceManaged: boolean;
|
|
497
|
+
acceptedShape: string | null;
|
|
498
|
+
options: DocumentPropertyOptions;
|
|
499
|
+
}>;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export interface ContentDatabaseRowMutationReceipt {
|
|
503
|
+
receiptId: string;
|
|
504
|
+
operation: "create" | "update" | "upsert";
|
|
505
|
+
outcome: "created" | "updated" | "unchanged";
|
|
506
|
+
target: ContentDatabaseMutationTarget;
|
|
507
|
+
schemaRevision: string;
|
|
508
|
+
row: {
|
|
509
|
+
itemId: string;
|
|
510
|
+
documentId: string;
|
|
511
|
+
urlPath: string;
|
|
512
|
+
rowRevision: string;
|
|
513
|
+
};
|
|
514
|
+
affected: { title: boolean; propertyIds: string[] };
|
|
515
|
+
idempotency: {
|
|
516
|
+
key: string;
|
|
517
|
+
result: "applied" | "replayed";
|
|
518
|
+
payloadDigest: string;
|
|
519
|
+
};
|
|
520
|
+
revisions: { before: string | null; after: string };
|
|
521
|
+
readback: {
|
|
522
|
+
verified: true;
|
|
523
|
+
title: string;
|
|
524
|
+
propertyValues: Record<string, DocumentPropertyValue>;
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export interface ContentDatabaseRowMutationResult {
|
|
529
|
+
receipt: ContentDatabaseRowMutationReceipt;
|
|
530
|
+
createdItem?: ContentDatabaseItem;
|
|
472
531
|
}
|
|
473
532
|
|
|
474
533
|
// A secondary source's read-only contribution to a federated row, matched on the
|
|
@@ -828,6 +887,7 @@ export interface ContentDatabaseResponse {
|
|
|
828
887
|
removedCount?: number;
|
|
829
888
|
timings?: BuilderActionTiming[];
|
|
830
889
|
tableQueryMode?: "server" | "client-required";
|
|
890
|
+
mutationContract?: ContentDatabaseMutationContract;
|
|
831
891
|
/** Client-only optimistic state while real provider rows are being attached. */
|
|
832
892
|
attachPreview?: {
|
|
833
893
|
sourceTable: string;
|
|
@@ -892,9 +952,22 @@ export interface CreateInlineDatabaseResponse {
|
|
|
892
952
|
}
|
|
893
953
|
|
|
894
954
|
export interface AddDatabaseItemRequest {
|
|
895
|
-
|
|
955
|
+
target: ContentDatabaseMutationTarget;
|
|
956
|
+
expectedSchemaRevision: string;
|
|
957
|
+
idempotencyKey: string;
|
|
896
958
|
title?: string;
|
|
897
|
-
propertyValues?: Record<string,
|
|
959
|
+
propertyValues?: Record<string, unknown>;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
export interface UpdateDatabaseItemRequest extends AddDatabaseItemRequest {
|
|
963
|
+
itemId: string;
|
|
964
|
+
documentId: string;
|
|
965
|
+
expectedRowRevision: string;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
export interface UpsertDatabaseItemByKeyRequest extends AddDatabaseItemRequest {
|
|
969
|
+
keyValue: string;
|
|
970
|
+
expectedRowRevision: string | null;
|
|
898
971
|
}
|
|
899
972
|
|
|
900
973
|
export interface SubmitContentDatabaseFormRequest {
|
|
@@ -171,12 +171,15 @@ const reuseLabelSchema = z
|
|
|
171
171
|
}
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
function classifyBreakpointSet(
|
|
175
|
+
value: unknown,
|
|
176
|
+
): "absent" | "malformed" | "present" {
|
|
177
|
+
if (value === null || value === undefined) return "absent";
|
|
178
|
+
if (typeof value !== "object" || Array.isArray(value)) return "malformed";
|
|
178
179
|
const breakpoints = (value as { breakpoints?: unknown }).breakpoints;
|
|
179
|
-
|
|
180
|
+
if (breakpoints === undefined) return "malformed";
|
|
181
|
+
if (!Array.isArray(breakpoints)) return "malformed";
|
|
182
|
+
return breakpoints.length > 0 ? "present" : "malformed";
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
function jsonValuesEqual(left: unknown, right: unknown): boolean {
|
|
@@ -1137,7 +1140,7 @@ const generateDesignAction = defineAction({
|
|
|
1137
1140
|
mergedData.breakpointSetUpdatedAt = updatedAt;
|
|
1138
1141
|
} else if (
|
|
1139
1142
|
generatedBreakpointSet.length > 0 &&
|
|
1140
|
-
|
|
1143
|
+
classifyBreakpointSet(mergedData.breakpointSet) === "absent"
|
|
1141
1144
|
) {
|
|
1142
1145
|
mergedData.breakpointSet = {
|
|
1143
1146
|
id: "generated-responsive",
|
|
@@ -1200,7 +1203,7 @@ const generateDesignAction = defineAction({
|
|
|
1200
1203
|
devices && devices.length > 0
|
|
1201
1204
|
? jsonValuesEqual(currentBreakpointWidths, expectedBreakpointWidths)
|
|
1202
1205
|
: generatedBreakpointSet.length === 0 ||
|
|
1203
|
-
|
|
1206
|
+
classifyBreakpointSet(current.breakpointSet) !== "absent";
|
|
1204
1207
|
return framesApplied && breakpointSetApplied;
|
|
1205
1208
|
},
|
|
1206
1209
|
});
|
|
@@ -89,12 +89,25 @@ async function hasLinkedDesignSystem(designId: string): Promise<boolean> {
|
|
|
89
89
|
return Boolean(design?.designSystemId?.trim());
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Absent, or present but unreadable — the caller must not treat these alike.
|
|
94
|
+
* A malformed set is still the user's data: overwriting it with the generated
|
|
95
|
+
* default silently discards breakpoints they configured, so it reports
|
|
96
|
+
* `"malformed"` and the install is skipped rather than clobbering it.
|
|
97
|
+
*/
|
|
98
|
+
function classifyBreakpointSet(
|
|
99
|
+
value: unknown,
|
|
100
|
+
): "absent" | "malformed" | "present" {
|
|
101
|
+
if (value === null || value === undefined) return "absent";
|
|
102
|
+
if (typeof value !== "object" || Array.isArray(value)) return "malformed";
|
|
96
103
|
const breakpoints = (value as { breakpoints?: unknown }).breakpoints;
|
|
97
|
-
|
|
104
|
+
if (breakpoints === undefined) return "malformed";
|
|
105
|
+
if (!Array.isArray(breakpoints)) return "malformed";
|
|
106
|
+
return breakpoints.length > 0 ? "present" : "malformed";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function hasBreakpointSet(value: unknown): boolean {
|
|
110
|
+
return classifyBreakpointSet(value) === "present";
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
function designDeepLink(designId: string): string {
|
|
@@ -1042,9 +1055,16 @@ export default defineAction({
|
|
|
1042
1055
|
});
|
|
1043
1056
|
}
|
|
1044
1057
|
|
|
1058
|
+
// Presenting options should not silently reconfigure the design. When it
|
|
1059
|
+
// does, the overview paints an extra preview beside EVERY primary frame
|
|
1060
|
+
// from then on — including screens created later — so the caller is told.
|
|
1061
|
+
let installedBreakpointSet = false;
|
|
1062
|
+
|
|
1045
1063
|
await mutateDesignData({
|
|
1046
1064
|
designId,
|
|
1047
1065
|
mutate: (current, { updatedAt }) => {
|
|
1066
|
+
installedBreakpointSet =
|
|
1067
|
+
classifyBreakpointSet(current.breakpointSet) === "absent";
|
|
1048
1068
|
// Placement depends on the breakpoint set in effect after this call, so
|
|
1049
1069
|
// it is resolved here (inside the compare-and-set body) rather than
|
|
1050
1070
|
// against a design snapshot that a concurrent write may have moved on
|
|
@@ -1106,7 +1126,9 @@ export default defineAction({
|
|
|
1106
1126
|
canvasFrames: mergedFrames.canvasFrames,
|
|
1107
1127
|
screenMetadata: previousMetadata,
|
|
1108
1128
|
designVariantSets: previousVariantSets,
|
|
1109
|
-
|
|
1129
|
+
// Only when genuinely absent. A malformed set is the user's data in
|
|
1130
|
+
// a shape this action cannot read, not a blank slate to overwrite.
|
|
1131
|
+
...(classifyBreakpointSet(current.breakpointSet) !== "absent"
|
|
1110
1132
|
? {}
|
|
1111
1133
|
: {
|
|
1112
1134
|
breakpointSet: {
|
|
@@ -1227,6 +1249,13 @@ export default defineAction({
|
|
|
1227
1249
|
embed: true,
|
|
1228
1250
|
cleanedUpPreviousVariantScreens: variantSetCleanup.removedFileIds.length,
|
|
1229
1251
|
deletedSupersededSetIds: variantSetCleanup.removedSetIds,
|
|
1252
|
+
...(installedBreakpointSet
|
|
1253
|
+
? {
|
|
1254
|
+
installedBreakpointSet: DEFAULT_RESPONSIVE_BREAKPOINTS.map(
|
|
1255
|
+
(breakpoint) => breakpoint.widthPx,
|
|
1256
|
+
),
|
|
1257
|
+
}
|
|
1258
|
+
: {}),
|
|
1230
1259
|
fallbackInstructions: FALLBACK_INSTRUCTIONS,
|
|
1231
1260
|
nextRequiredAction:
|
|
1232
1261
|
'Wait for the user to pick a screen in chat. Then delete each unchosen variant screen with delete-file at most once, call get-design-snapshot exactly once with fileId for the chosen screen, and call edit-design with that same fileId in a bounded pass. Use mode "replace-file" to replace the representative direction screen with a complete but compact requested app/product UI in the chosen visual style. Prioritize the primary workflow and render secondary details as visible controls, states, or affordances if the full feature list is too large for one reliable edit. Do not leave a direction board, variant brief, or summary card as the final result. Do not repeat delete/snapshot cycles. Do not call generate-design after a variant pick. Stop after the first successful edit-design save.',
|
|
@@ -25,6 +25,10 @@ import type {
|
|
|
25
25
|
A11yFindingCategory,
|
|
26
26
|
A11ySeverity,
|
|
27
27
|
} from "../shared/design-review.js";
|
|
28
|
+
import {
|
|
29
|
+
describeDesignHtmlIntegrityIssue,
|
|
30
|
+
inspectDesignHtmlDocumentIntegrity,
|
|
31
|
+
} from "../shared/html-integrity.js";
|
|
28
32
|
|
|
29
33
|
// ---------------------------------------------------------------------------
|
|
30
34
|
// HTML helpers (static analysis — no DOM runtime available server-side)
|
|
@@ -259,6 +263,34 @@ export function checkTapTargets(html: string): A11yFinding[] {
|
|
|
259
263
|
return findings;
|
|
260
264
|
}
|
|
261
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Screens that render with an Alpine-controlled element covering them. Every
|
|
268
|
+
* other check here is a regex over the raw string; this one delegates to the
|
|
269
|
+
* save-time integrity parser so the audit and the write gate cannot drift into
|
|
270
|
+
* disagreeing about the same document. It is the only check that can see the
|
|
271
|
+
* failure a screenshot cannot: `take-design-screenshot` waits for Alpine to
|
|
272
|
+
* settle, so a pre-Alpine cover never appears in the agent's own capture.
|
|
273
|
+
*/
|
|
274
|
+
export function checkRenderBlockingOverlays(html: string): A11yFinding[] {
|
|
275
|
+
const result = inspectDesignHtmlDocumentIntegrity(html);
|
|
276
|
+
const issues = [...(result.detail ?? []), ...(result.advisory ?? [])].filter(
|
|
277
|
+
(issue) =>
|
|
278
|
+
issue.issue === "runtime-overlay-unhidden" ||
|
|
279
|
+
issue.issue === "runtime-cloak-missing" ||
|
|
280
|
+
issue.issue === "runtime-alpine-missing",
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return issues.map((issue, index) => ({
|
|
284
|
+
id: `render-blocking-overlay:${issue.issue}-${index}`,
|
|
285
|
+
severity: "error" as A11ySeverity,
|
|
286
|
+
category: "render-blocking-overlay" as A11yFindingCategory,
|
|
287
|
+
message:
|
|
288
|
+
"An Alpine-controlled element can cover the screen before Alpine starts.",
|
|
289
|
+
detail: describeDesignHtmlIntegrityIssue(issue),
|
|
290
|
+
fixAvailable: false,
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
|
|
262
294
|
/** Check for animations/transitions without a prefers-reduced-motion guard. */
|
|
263
295
|
function checkReducedMotion(html: string): A11yFinding[] {
|
|
264
296
|
const findings: A11yFinding[] = [];
|
|
@@ -848,6 +880,7 @@ export default defineAction({
|
|
|
848
880
|
...checkReducedMotion(html),
|
|
849
881
|
...checkFocusVisibility(html),
|
|
850
882
|
...checkContrastHint(html),
|
|
883
|
+
...checkRenderBlockingOverlays(html),
|
|
851
884
|
...tokenDriftFindings,
|
|
852
885
|
...designSystemFindings,
|
|
853
886
|
];
|