@agent-native/core 0.196.0-nightly-20260928172902 → 0.196.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/CHANGELOG.md +17 -0
- package/corpus/templates/assets/CHANGELOG.md +18 -0
- package/corpus/templates/brain/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/CHANGELOG.md +4 -0
- package/corpus/templates/clips/CHANGELOG.md +19 -0
- package/corpus/templates/clips/actions/create-recording-agent-link.ts +8 -2
- package/corpus/templates/clips/actions/update-recording.ts +5 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +10 -3
- package/corpus/templates/clips/changelog/2026-09-28-agent-links-stay-valid-when-clip-details-change.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-28-password-changes-immediately-invalidate-old-recording-links.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-28-password-protected-recording-links-stop-working-when-the-pas.md +6 -0
- package/corpus/templates/clips/server/db/schema.ts +3 -0
- package/corpus/templates/clips/server/lib/player-video-url.ts +9 -1
- package/corpus/templates/clips/server/lib/public-agent-context.ts +12 -4
- package/corpus/templates/clips/server/lib/share-password.ts +25 -0
- package/corpus/templates/clips/server/plugins/db.ts +36 -0
- package/corpus/templates/clips/server/routes/[...page].get.ts +8 -1
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +33 -6
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +34 -6
- package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +26 -5
- package/corpus/templates/content/CHANGELOG.md +1 -0
- package/corpus/templates/crm/CHANGELOG.md +12 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +265 -3
- package/corpus/templates/design/CHANGELOG.md +21 -0
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +463 -6
- package/corpus/templates/design/app/pages/Index.tsx +1 -1
- package/corpus/templates/design/changelog/2026-09-27-auto-focused-app-fields-no-longer-interrupt-canvas-shortcuts.md +6 -0
- package/corpus/templates/design/changelog/2026-09-28-model-picker-loads-available-models.md +6 -0
- package/corpus/templates/design/changelog/2026-09-28-template-menus-stay-available-on-hover.md +6 -0
- package/corpus/templates/dispatch/CHANGELOG.md +18 -0
- package/corpus/templates/forms/CHANGELOG.md +6 -0
- package/corpus/templates/mail/.env.example +1 -1
- package/corpus/templates/mail/CHANGELOG.md +33 -0
- package/corpus/templates/mail/changelog/2026-09-28-fixed-inbox-loading-stalls-and-showed-progress-during-google-2.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-28-fixed-inbox-loading-stalls-and-showed-progress-during-google.md +6 -0
- package/corpus/templates/mail/server/db/schema.ts +7 -0
- package/corpus/templates/mail/server/lib/ai-filter-backfill.ts +4 -0
- package/corpus/templates/mail/server/lib/jobs.ts +5 -2
- package/corpus/templates/mail/server/plugins/db.ts +16 -0
- package/corpus/templates/mail/server/plugins/mail-jobs.ts +355 -44
- package/corpus/templates/slides/CHANGELOG.md +28 -0
- package/corpus/templates/slides/app/pages/Index.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-09-28-model-picker-loads-available-models.md +6 -0
- package/corpus/templates/slides/changelog/2026-09-28-template-menus-stay-available-on-hover.md +6 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/auth/AuthPage.js +3 -2
- package/dist/client/org/OrgSwitcher.js +31 -19
- package/dist/client/settings/UsageSection.js +3 -3
- package/dist/client/use-action.js +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/jobs/background-automation-runner.d.ts +2 -0
- package/dist/jobs/background-automation-runner.js +20 -1
- package/dist/jobs/sweep-hooks.d.ts +6 -2
- package/dist/jobs/sweep-hooks.js +17 -9
- package/dist/localization/core-messages/ar-SA.js +2 -2
- package/dist/localization/core-messages/de-DE.js +2 -2
- package/dist/localization/core-messages/en-US.d.ts +2 -2
- package/dist/localization/core-messages/en-US.js +2 -2
- package/dist/localization/core-messages/es-ES.js +2 -2
- package/dist/localization/core-messages/fr-FR.js +2 -2
- package/dist/localization/core-messages/hi-IN.js +2 -2
- package/dist/localization/core-messages/ja-JP.js +2 -2
- package/dist/localization/core-messages/ko-KR.js +2 -2
- package/dist/localization/core-messages/pt-BR.js +2 -2
- package/dist/localization/core-messages/zh-CN.js +2 -2
- package/dist/localization/core-messages/zh-TW.js +2 -2
- package/dist/localization/core-messages.d.ts +2 -2
- package/dist/notifications/routes.d.ts +5 -5
- package/dist/observability/routes.d.ts +6 -6
- 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 +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +4 -4
- package/dist/server/agent-chat-plugin.js +6 -2
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/triggers/dispatcher.js +167 -36
- package/dist/triggers/event-queue.d.ts +18 -1
- package/dist/triggers/event-queue.js +143 -10
- package/package.json +4 -4
|
@@ -72,7 +72,10 @@ import {
|
|
|
72
72
|
import { isRecordingExpiredForViewer } from "../../../lib/recording-page-access.js";
|
|
73
73
|
import { getOrganizationRoleForEmail } from "../../../lib/recordings.js";
|
|
74
74
|
import { fetchS3ObjectByUrl } from "../../../lib/s3-upload-provider.js";
|
|
75
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
getRecordingAccessTokenResourceId,
|
|
77
|
+
verifySharePassword,
|
|
78
|
+
} from "../../../lib/share-password.js";
|
|
76
79
|
|
|
77
80
|
interface RecordingRow {
|
|
78
81
|
editsJson?: string | null;
|
|
@@ -80,6 +83,8 @@ interface RecordingRow {
|
|
|
80
83
|
organizationId?: string | null;
|
|
81
84
|
ownerEmail?: string | null;
|
|
82
85
|
password?: string | null;
|
|
86
|
+
sharePasswordVersion?: string | null;
|
|
87
|
+
updatedAt?: string | null;
|
|
83
88
|
sourceAppName?: string | null;
|
|
84
89
|
sourceWindowTitle?: string | null;
|
|
85
90
|
videoUrl?: string | null;
|
|
@@ -130,9 +135,15 @@ function isHttpsRequest(event: H3Event): boolean {
|
|
|
130
135
|
function setProtectedMediaAccessCookie(
|
|
131
136
|
event: H3Event,
|
|
132
137
|
recordingId: string,
|
|
138
|
+
password: string | null | undefined,
|
|
139
|
+
sharePasswordVersion: string | null | undefined,
|
|
133
140
|
): void {
|
|
134
141
|
const token = signShortLivedToken({
|
|
135
|
-
resourceId:
|
|
142
|
+
resourceId: getRecordingAccessTokenResourceId(
|
|
143
|
+
recordingId,
|
|
144
|
+
password,
|
|
145
|
+
sharePasswordVersion,
|
|
146
|
+
),
|
|
136
147
|
ttlSeconds: PROTECTED_MEDIA_ACCESS_TTL_SECONDS,
|
|
137
148
|
});
|
|
138
149
|
const secure = isHttpsRequest(event);
|
|
@@ -389,12 +400,17 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
389
400
|
const supplied = typeof q.password === "string" ? q.password : "";
|
|
390
401
|
|
|
391
402
|
let allowed = false;
|
|
403
|
+
const scopedRecordingId = getRecordingAccessTokenResourceId(
|
|
404
|
+
recordingId,
|
|
405
|
+
rec.password,
|
|
406
|
+
rec.sharePasswordVersion,
|
|
407
|
+
);
|
|
392
408
|
if (token) {
|
|
393
|
-
const result = verifyShortLivedToken(token,
|
|
409
|
+
const result = verifyShortLivedToken(token, scopedRecordingId);
|
|
394
410
|
if (result.ok) allowed = true;
|
|
395
411
|
}
|
|
396
412
|
if (!allowed && cookieToken) {
|
|
397
|
-
const result = verifyShortLivedToken(cookieToken,
|
|
413
|
+
const result = verifyShortLivedToken(cookieToken, scopedRecordingId);
|
|
398
414
|
if (result.ok) allowed = true;
|
|
399
415
|
}
|
|
400
416
|
if (
|
|
@@ -408,7 +424,12 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
408
424
|
setResponseStatus(event, 401);
|
|
409
425
|
return { error: "Password required", passwordRequired: true };
|
|
410
426
|
}
|
|
411
|
-
setProtectedMediaAccessCookie(
|
|
427
|
+
setProtectedMediaAccessCookie(
|
|
428
|
+
event,
|
|
429
|
+
recordingId,
|
|
430
|
+
rec.password,
|
|
431
|
+
rec.sharePasswordVersion,
|
|
432
|
+
);
|
|
412
433
|
}
|
|
413
434
|
|
|
414
435
|
if (isLoomEmbedBackedRecording(rec)) {
|
|
@@ -13,6 +13,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
13
13
|
|
|
14
14
|
### Added
|
|
15
15
|
|
|
16
|
+
- Ask the agent to turn comment, reply, and mention emails on or off.
|
|
16
17
|
- Chat messages can be reverted to their autosaved version, including a shortcut to restore the start of a chat.
|
|
17
18
|
|
|
18
19
|
### Improved
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# CRM changelog
|
|
2
2
|
|
|
3
|
+
## 2026-09-28
|
|
4
|
+
|
|
5
|
+
### Improved
|
|
6
|
+
|
|
7
|
+
- Ask CRM now starts with a focused title.
|
|
8
|
+
|
|
9
|
+
## 2026-09-27
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Custom CRM field labels and settings stay unchanged when records update
|
|
14
|
+
|
|
3
15
|
## 2026-08-11
|
|
4
16
|
|
|
5
17
|
### Improved
|
|
@@ -945,6 +945,7 @@ export const editorChromeBridgeScript: string = `"use strict";
|
|
|
945
945
|
var repairingEditorChromeHost = false;
|
|
946
946
|
var userFocusedElement = null;
|
|
947
947
|
var trustedFocusIntent = null;
|
|
948
|
+
var rovingGroupSelector = '[role="grid"], [role="listbox"], [role="menu"], [role="menubar"], [role="radiogroup"], [role="tablist"], [role="toolbar"], [role="tree"], [role="treegrid"]';
|
|
948
949
|
var focusTargetSelector = 'a[href], area[href], button, input:not([type="hidden"]), select, textarea, summary, iframe, audio[controls], video[controls], [tabindex], [contenteditable]:not([contenteditable="false"]), [role="button"], [role="link"], [role="switch"], [role="checkbox"], [role="radio"], [role="slider"], [role="spinbutton"], [role="menuitem"], [role="textbox"], [role="combobox"], [role="searchbox"]';
|
|
949
950
|
function isCanvasFocusTarget(target) {
|
|
950
951
|
return !!(target && (isEditorTypingTarget(target) || target.matches(focusTargetSelector)));
|
|
@@ -963,17 +964,275 @@ export const editorChromeBridgeScript: string = `"use strict";
|
|
|
963
964
|
}
|
|
964
965
|
return null;
|
|
965
966
|
}
|
|
966
|
-
function armTrustedFocusIntent(target, kind) {
|
|
967
|
+
function armTrustedFocusIntent(target, kind, key) {
|
|
968
|
+
var rovingGroup = kind === "navigation" && target ? target.closest(rovingGroupSelector) : null;
|
|
967
969
|
trustedFocusIntent = {
|
|
968
970
|
target,
|
|
969
971
|
kind,
|
|
972
|
+
...key ? { key } : {},
|
|
973
|
+
...rovingGroup ? {
|
|
974
|
+
rovingGroup,
|
|
975
|
+
activeDescendantBefore: rovingGroup.getAttribute(
|
|
976
|
+
"aria-activedescendant"
|
|
977
|
+
),
|
|
978
|
+
tabIndexesBefore: Array.from(
|
|
979
|
+
rovingGroup.querySelectorAll("[tabindex]")
|
|
980
|
+
).map(function(element) {
|
|
981
|
+
return {
|
|
982
|
+
element,
|
|
983
|
+
value: element.getAttribute("tabindex")
|
|
984
|
+
};
|
|
985
|
+
})
|
|
986
|
+
} : {},
|
|
970
987
|
expiresAt: Date.now() + 1e3
|
|
971
988
|
};
|
|
972
989
|
}
|
|
990
|
+
function clearNavigationFocusIntentAfterAppTasks() {
|
|
991
|
+
var navigationIntent = trustedFocusIntent;
|
|
992
|
+
window.setTimeout(function() {
|
|
993
|
+
window.setTimeout(function() {
|
|
994
|
+
if (trustedFocusIntent === navigationIntent) {
|
|
995
|
+
trustedFocusIntent = null;
|
|
996
|
+
}
|
|
997
|
+
}, 0);
|
|
998
|
+
}, 0);
|
|
999
|
+
}
|
|
1000
|
+
function isRovingFocusSibling(current, next, key, intent) {
|
|
1001
|
+
if (!current) return false;
|
|
1002
|
+
function isNativeRadio(element) {
|
|
1003
|
+
return element.tagName === "INPUT" && element.type === "radio";
|
|
1004
|
+
}
|
|
1005
|
+
if (isNativeRadio(current) && isNativeRadio(next)) {
|
|
1006
|
+
var radioName = current.name;
|
|
1007
|
+
var radioForm = current.form || current.closest("form");
|
|
1008
|
+
var radios = Array.from(
|
|
1009
|
+
current.getRootNode().querySelectorAll(
|
|
1010
|
+
'input[type="radio"]'
|
|
1011
|
+
)
|
|
1012
|
+
).filter(function(candidate) {
|
|
1013
|
+
var radio = candidate;
|
|
1014
|
+
return radioName !== "" && radio.name === radioName && (radio.form || radio.closest("form")) === radioForm;
|
|
1015
|
+
});
|
|
1016
|
+
var currentRadioIndex = radios.indexOf(current);
|
|
1017
|
+
var nextRadioIndex = radios.indexOf(next);
|
|
1018
|
+
var delta = key === "ArrowRight" || key === "ArrowDown" ? 1 : key === "ArrowLeft" || key === "ArrowUp" ? -1 : 0;
|
|
1019
|
+
if (key === "ArrowLeft" || key === "ArrowRight") {
|
|
1020
|
+
if (window.getComputedStyle(current).direction === "rtl") {
|
|
1021
|
+
delta *= -1;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
if (radios.length > 1 && currentRadioIndex !== -1 && nextRadioIndex !== -1 && delta !== 0) {
|
|
1025
|
+
return nextRadioIndex === (currentRadioIndex + delta + radios.length) % radios.length;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
var currentGroup = current.closest(rovingGroupSelector);
|
|
1029
|
+
if (!currentGroup || currentGroup !== next.closest(rovingGroupSelector) || intent.rovingGroup !== currentGroup) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
var role = currentGroup.getAttribute("role");
|
|
1033
|
+
var itemSelector = null;
|
|
1034
|
+
switch (role) {
|
|
1035
|
+
case "grid":
|
|
1036
|
+
case "treegrid":
|
|
1037
|
+
itemSelector = '[role="row"], [role="gridcell"], [role="columnheader"], [role="rowheader"], tr, td, th';
|
|
1038
|
+
break;
|
|
1039
|
+
case "listbox":
|
|
1040
|
+
itemSelector = '[role="option"]';
|
|
1041
|
+
break;
|
|
1042
|
+
case "menu":
|
|
1043
|
+
case "menubar":
|
|
1044
|
+
itemSelector = '[role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]';
|
|
1045
|
+
break;
|
|
1046
|
+
case "radiogroup":
|
|
1047
|
+
itemSelector = '[role="radio"], input[type="radio"]';
|
|
1048
|
+
break;
|
|
1049
|
+
case "tablist":
|
|
1050
|
+
itemSelector = '[role="tab"]';
|
|
1051
|
+
break;
|
|
1052
|
+
case "toolbar":
|
|
1053
|
+
itemSelector = 'button, a[href], [role="button"], [role="link"]';
|
|
1054
|
+
break;
|
|
1055
|
+
case "tree":
|
|
1056
|
+
itemSelector = '[role="treeitem"]';
|
|
1057
|
+
break;
|
|
1058
|
+
}
|
|
1059
|
+
if (!itemSelector) return false;
|
|
1060
|
+
var currentItem = current.closest(itemSelector);
|
|
1061
|
+
var nextItem = next.closest(itemSelector);
|
|
1062
|
+
if (!currentItem || !nextItem || currentItem === nextItem) return false;
|
|
1063
|
+
function belongsToGroup(item) {
|
|
1064
|
+
if (item.closest(rovingGroupSelector) !== currentGroup) return false;
|
|
1065
|
+
if (item.getAttribute("role")) return true;
|
|
1066
|
+
if (item.tagName !== "TR" && item.tagName !== "TD" && item.tagName !== "TH") {
|
|
1067
|
+
return true;
|
|
1068
|
+
}
|
|
1069
|
+
return item.closest('table[role="grid"], table[role="treegrid"]') === currentGroup;
|
|
1070
|
+
}
|
|
1071
|
+
function isGridRow(item) {
|
|
1072
|
+
return item.getAttribute("role") === "row" || item.tagName === "TR" && belongsToGroup(item);
|
|
1073
|
+
}
|
|
1074
|
+
function gridRows() {
|
|
1075
|
+
return Array.from(
|
|
1076
|
+
currentGroup.querySelectorAll('[role="row"], tr')
|
|
1077
|
+
).filter(function(row) {
|
|
1078
|
+
return belongsToGroup(row) && isGridRow(row);
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
function gridCells(row) {
|
|
1082
|
+
return Array.from(
|
|
1083
|
+
row.querySelectorAll(
|
|
1084
|
+
'[role="gridcell"], [role="columnheader"], [role="rowheader"], td, th'
|
|
1085
|
+
)
|
|
1086
|
+
).filter(function(cell) {
|
|
1087
|
+
if (!belongsToGroup(cell)) return false;
|
|
1088
|
+
if (cell.getAttribute("role")) {
|
|
1089
|
+
return cell.closest('[role="row"]') === row;
|
|
1090
|
+
}
|
|
1091
|
+
if (cell.tagName === "TD" || cell.tagName === "TH") {
|
|
1092
|
+
return cell.closest("tr") === row && cell.closest('table[role="grid"], table[role="treegrid"]') === currentGroup;
|
|
1093
|
+
}
|
|
1094
|
+
return cell.closest('[role="row"]') === row;
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
function ariaIndex(element, name, fallback) {
|
|
1098
|
+
var value = Number(element.getAttribute(name));
|
|
1099
|
+
return Number.isInteger(value) && value > 0 ? value - 1 : fallback;
|
|
1100
|
+
}
|
|
1101
|
+
function hasRovingFocusTransition(currentRovingItem2, nextRovingItem2) {
|
|
1102
|
+
var activeDescendant = currentGroup.getAttribute("aria-activedescendant");
|
|
1103
|
+
var previousActiveDescendant = intent.activeDescendantBefore || null;
|
|
1104
|
+
if (activeDescendant && activeDescendant !== previousActiveDescendant && (activeDescendant === nextRovingItem2.id || activeDescendant === next.id)) {
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
var previousTabIndex = intent.tabIndexesBefore?.find(function(entry) {
|
|
1108
|
+
return entry.element === currentRovingItem2;
|
|
1109
|
+
});
|
|
1110
|
+
var nextTabIndex = intent.tabIndexesBefore?.find(function(entry) {
|
|
1111
|
+
return entry.element === nextRovingItem2;
|
|
1112
|
+
});
|
|
1113
|
+
if (previousTabIndex?.value === "0" && currentRovingItem2.getAttribute("tabindex") !== "0" && nextTabIndex !== void 0 && nextTabIndex.value !== "0" && nextRovingItem2.getAttribute("tabindex") === "0") {
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
function isGridCell(item) {
|
|
1117
|
+
return item.getAttribute("role") === "gridcell" || item.getAttribute("role") === "columnheader" || item.getAttribute("role") === "rowheader" || item.tagName === "TD" || item.tagName === "TH";
|
|
1118
|
+
}
|
|
1119
|
+
return (role === "grid" || role === "treegrid") && isGridCell(currentRovingItem2) && isGridCell(nextRovingItem2) && current !== currentRovingItem2 && next !== nextRovingItem2 && current.matches(focusTargetSelector) && next.matches(focusTargetSelector);
|
|
1120
|
+
}
|
|
1121
|
+
if (role === "grid" || role === "treegrid") {
|
|
1122
|
+
var currentRow = currentItem.closest('[role="row"], tr');
|
|
1123
|
+
var nextRow = nextItem.closest('[role="row"], tr');
|
|
1124
|
+
if (!currentRow || !nextRow || !belongsToGroup(currentRow) || !belongsToGroup(nextRow)) {
|
|
1125
|
+
return false;
|
|
1126
|
+
}
|
|
1127
|
+
var rows = gridRows();
|
|
1128
|
+
var currentRowIndex = ariaIndex(
|
|
1129
|
+
currentRow,
|
|
1130
|
+
"aria-rowindex",
|
|
1131
|
+
rows.indexOf(currentRow)
|
|
1132
|
+
);
|
|
1133
|
+
var nextRowIndex = ariaIndex(
|
|
1134
|
+
nextRow,
|
|
1135
|
+
"aria-rowindex",
|
|
1136
|
+
rows.indexOf(nextRow)
|
|
1137
|
+
);
|
|
1138
|
+
var verticalDelta = key === "ArrowDown" ? 1 : key === "ArrowUp" ? -1 : 0;
|
|
1139
|
+
if (isGridRow(currentItem) && isGridRow(nextItem)) {
|
|
1140
|
+
return verticalDelta !== 0 && nextRowIndex === currentRowIndex + verticalDelta && hasRovingFocusTransition(currentItem, nextItem);
|
|
1141
|
+
}
|
|
1142
|
+
if (isGridRow(currentItem) || isGridRow(nextItem)) {
|
|
1143
|
+
if (role !== "treegrid" || currentRow !== nextRow) return false;
|
|
1144
|
+
var currentIsRow = isGridRow(currentItem);
|
|
1145
|
+
var cells = gridCells(currentRow);
|
|
1146
|
+
if (currentIsRow && key === "ArrowRight") {
|
|
1147
|
+
return cells[0] === nextItem && hasRovingFocusTransition(currentItem, nextItem);
|
|
1148
|
+
}
|
|
1149
|
+
if (!currentIsRow && key === "ArrowLeft") {
|
|
1150
|
+
return cells[0] === currentItem && hasRovingFocusTransition(currentItem, nextItem);
|
|
1151
|
+
}
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
var currentColumnIndex = ariaIndex(
|
|
1155
|
+
currentItem,
|
|
1156
|
+
"aria-colindex",
|
|
1157
|
+
gridCells(currentRow).indexOf(currentItem)
|
|
1158
|
+
);
|
|
1159
|
+
var nextColumnIndex = ariaIndex(
|
|
1160
|
+
nextItem,
|
|
1161
|
+
"aria-colindex",
|
|
1162
|
+
gridCells(nextRow).indexOf(nextItem)
|
|
1163
|
+
);
|
|
1164
|
+
if (key === "ArrowLeft" || key === "ArrowRight") {
|
|
1165
|
+
var columnDelta = key === "ArrowRight" ? 1 : -1;
|
|
1166
|
+
if (window.getComputedStyle(currentGroup).direction === "rtl") {
|
|
1167
|
+
columnDelta *= -1;
|
|
1168
|
+
}
|
|
1169
|
+
return currentRow === nextRow && nextColumnIndex === currentColumnIndex + columnDelta && hasRovingFocusTransition(currentItem, nextItem);
|
|
1170
|
+
}
|
|
1171
|
+
return verticalDelta !== 0 && nextRowIndex === currentRowIndex + verticalDelta && nextColumnIndex === currentColumnIndex && hasRovingFocusTransition(currentItem, nextItem);
|
|
1172
|
+
}
|
|
1173
|
+
function itemFor(target) {
|
|
1174
|
+
if (role === "tree") return target.closest('[role="treeitem"]');
|
|
1175
|
+
return target.closest(itemSelector);
|
|
1176
|
+
}
|
|
1177
|
+
function isNavigableTreeItem(item) {
|
|
1178
|
+
var currentItem2 = item;
|
|
1179
|
+
while (currentItem2 && currentItem2 !== currentGroup) {
|
|
1180
|
+
if (currentItem2.getAttribute("hidden") !== null || currentItem2.getAttribute("inert") !== null || currentItem2.getAttribute("aria-hidden") === "true") {
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
if (currentItem2 !== item && currentItem2.getAttribute("role") === "treeitem" && currentItem2.getAttribute("aria-expanded") === "false") {
|
|
1184
|
+
return false;
|
|
1185
|
+
}
|
|
1186
|
+
currentItem2 = currentItem2.parentElement;
|
|
1187
|
+
}
|
|
1188
|
+
return true;
|
|
1189
|
+
}
|
|
1190
|
+
var currentRovingItem = itemFor(current);
|
|
1191
|
+
var nextRovingItem = itemFor(next);
|
|
1192
|
+
if (!currentRovingItem || !nextRovingItem || !belongsToGroup(currentRovingItem) || !belongsToGroup(nextRovingItem)) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
var isRtl = window.getComputedStyle(currentGroup).direction === "rtl";
|
|
1196
|
+
var childKey = isRtl ? "ArrowLeft" : "ArrowRight";
|
|
1197
|
+
var parentKey = isRtl ? "ArrowRight" : "ArrowLeft";
|
|
1198
|
+
if (role === "tree" && key === childKey) {
|
|
1199
|
+
return isNavigableTreeItem(currentRovingItem) && isNavigableTreeItem(nextRovingItem) && nextRovingItem.parentElement?.closest('[role="treeitem"]') === currentRovingItem && hasRovingFocusTransition(currentRovingItem, nextRovingItem);
|
|
1200
|
+
}
|
|
1201
|
+
if (role === "tree" && key === parentKey) {
|
|
1202
|
+
return isNavigableTreeItem(currentRovingItem) && isNavigableTreeItem(nextRovingItem) && currentRovingItem.parentElement?.closest('[role="treeitem"]') === nextRovingItem && hasRovingFocusTransition(currentRovingItem, nextRovingItem);
|
|
1203
|
+
}
|
|
1204
|
+
var items = Array.from(currentGroup.querySelectorAll(itemSelector)).filter(
|
|
1205
|
+
function(item) {
|
|
1206
|
+
return belongsToGroup(item) && itemFor(item) === item && (role !== "tree" || isNavigableTreeItem(item));
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
var currentIndex = items.indexOf(currentRovingItem);
|
|
1210
|
+
var nextIndex = items.indexOf(nextRovingItem);
|
|
1211
|
+
if (currentIndex === -1 || nextIndex === -1) return false;
|
|
1212
|
+
var orientation = currentGroup.getAttribute("aria-orientation");
|
|
1213
|
+
if (!orientation) {
|
|
1214
|
+
orientation = role === "toolbar" || role === "menubar" || role === "tablist" || role === "radiogroup" ? "horizontal" : "vertical";
|
|
1215
|
+
}
|
|
1216
|
+
var delta = key === "ArrowRight" ? 1 : key === "ArrowLeft" ? -1 : key === "ArrowDown" ? 1 : key === "ArrowUp" ? -1 : 0;
|
|
1217
|
+
if (!delta) return false;
|
|
1218
|
+
if (orientation === "horizontal") {
|
|
1219
|
+
if (key !== "ArrowLeft" && key !== "ArrowRight") return false;
|
|
1220
|
+
if (window.getComputedStyle(currentGroup).direction === "rtl")
|
|
1221
|
+
delta *= -1;
|
|
1222
|
+
} else if (key !== "ArrowUp" && key !== "ArrowDown") {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
var expectedIndex = currentIndex + delta;
|
|
1226
|
+
if (role === "radiogroup" || role === "tablist" || role === "menu" || role === "menubar" || role === "toolbar") {
|
|
1227
|
+
expectedIndex = (expectedIndex + items.length) % items.length;
|
|
1228
|
+
}
|
|
1229
|
+
return nextIndex === expectedIndex && hasRovingFocusTransition(currentRovingItem, nextRovingItem);
|
|
1230
|
+
}
|
|
973
1231
|
function rememberUserFocusedElement(event) {
|
|
974
1232
|
var intent = trustedFocusIntent;
|
|
975
1233
|
var target = getCanvasFocusTarget(event);
|
|
976
|
-
|
|
1234
|
+
var navigationTargetMatches = intent?.kind !== "navigation" || target === intent.target || target !== null && isRovingFocusSibling(intent.target, target, intent.key, intent);
|
|
1235
|
+
if (!intent || Date.now() > intent.expiresAt || !target || !navigationTargetMatches || intent.kind !== "tab" && intent.kind !== "navigation" && (intent.target === null || intent.target !== target && !event.composedPath().includes(intent.target) && !intent.target.contains(target) && !target.contains(intent.target))) {
|
|
977
1236
|
trustedFocusIntent = null;
|
|
978
1237
|
userFocusedElement = null;
|
|
979
1238
|
return;
|
|
@@ -1024,7 +1283,10 @@ export const editorChromeBridgeScript: string = `"use strict";
|
|
|
1024
1283
|
var active = document.activeElement;
|
|
1025
1284
|
if (active instanceof Element && isCanvasFocusTarget(active)) {
|
|
1026
1285
|
userFocusedElement = active;
|
|
1027
|
-
if (keyEvent.key
|
|
1286
|
+
if (keyEvent.key.startsWith("Arrow")) {
|
|
1287
|
+
armTrustedFocusIntent(active, "navigation", keyEvent.key);
|
|
1288
|
+
clearNavigationFocusIntentAfterAppTasks();
|
|
1289
|
+
} else if (keyEvent.key === "Enter" || keyEvent.key === " " || keyEvent.key === "Escape") {
|
|
1028
1290
|
armTrustedFocusIntent(active, "activation");
|
|
1029
1291
|
}
|
|
1030
1292
|
}
|
|
@@ -3,15 +3,33 @@
|
|
|
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-28
|
|
7
|
+
|
|
8
|
+
### Improved
|
|
9
|
+
|
|
10
|
+
- Starter prompts now sit below the chat composer
|
|
11
|
+
- The design list search and import controls stay legible on narrow screens.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Canvas navigation and undo keep working when connected apps focus controls automatically
|
|
16
|
+
- The model picker shows available models when AI is configured.
|
|
17
|
+
- Template menus stay available when you hover over a card.
|
|
18
|
+
|
|
6
19
|
## 2026-09-27
|
|
7
20
|
|
|
8
21
|
### Improved
|
|
9
22
|
|
|
23
|
+
- Chrome local editing guidance uses sharper permission screenshots and a neutral backdrop
|
|
24
|
+
- Local access setup shows Chrome permission screenshots and confirms before closing
|
|
10
25
|
- The Connect AI setup card now has even spacing above and below the composer.
|
|
11
26
|
- Chrome local access guidance clarifies how to approve live editing
|
|
12
27
|
|
|
13
28
|
### Fixed
|
|
14
29
|
|
|
30
|
+
- Auto-focused app fields no longer interrupt canvas shortcuts while editing live screens
|
|
31
|
+
- Fix deeply nested drag-and-drop placement on Design canvases
|
|
32
|
+
- Personalized home suggestions work when responses include extra text
|
|
15
33
|
- Canvas shortcuts keep working when a connected app focuses an input.
|
|
16
34
|
- Drops into plain frames now stack above existing content, while auto-layout keeps the chosen insertion position.
|
|
17
35
|
- Layers dragged out of a frame now stay above the frame.
|
|
@@ -37,6 +55,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
37
55
|
|
|
38
56
|
### Fixed
|
|
39
57
|
|
|
58
|
+
- Opening a shared visual editor now completes its initial setup and hands visual edits back to your coding agent.
|
|
40
59
|
- Live previews keep canvas keyboard shortcuts available when app fields autofocus
|
|
41
60
|
- API key settings recover when loading stalls.
|
|
42
61
|
- Cmd+D copies avoid existing screens while reserving simultaneous duplicates.
|
|
@@ -66,6 +85,8 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
66
85
|
|
|
67
86
|
### Improved
|
|
68
87
|
|
|
88
|
+
- In the new Settings, Agent Observability is a page in the Design group for owners and admins, and the MCP server page says what a connected app can do in Design.
|
|
89
|
+
- Settings now opens from your name at the bottom of the sidebar, or with ⌘,.
|
|
69
90
|
- Design shows how to connect file storage before uploading media or fonts
|
|
70
91
|
- Attach Figma frames and websites from focused dialogs while keeping prompt drafts and uploaded design.md files intact.
|
|
71
92
|
- Recent designs now use the same consistent library card layout as templates.
|