@agent-native/core-corpus 0.202.0 → 0.202.1-nightly-20261006213000
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/brain/.agents/skills/ingestion-and-connectors/SKILL.md +7 -2
- package/corpus/templates/brain/changelog/2026-10-06-zoom-transcripts-now-download.md +6 -0
- package/corpus/templates/brain/server/lib/connectors.ts +66 -4
- package/corpus/templates/brain/server/lib/zoom.ts +37 -4
- package/corpus/templates/brain/server/register-secrets.ts +3 -3
- package/corpus/templates/content/changelog/2026-10-06-generated-documents-from-chatgpt-open-in-the-full-content-ed.md +6 -0
- package/corpus/templates/content/server/lib/chatgpt-directory-tools.ts +0 -2
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +12 -6
- package/corpus/templates/design/actions/generate-design.ts +188 -45
- package/corpus/templates/design/actions/present-design-variants.ts +113 -39
- package/corpus/templates/design/app/hooks/use-question-flow.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +10 -2
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +5 -4
- package/corpus/templates/design/changelog/2026-10-05-design-keeps-exact-size-screens-at-their-requested-dimension.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/design/shared/canvas-dimensions.ts +204 -0
- package/corpus/templates/design/shared/responsive-frame-layout.ts +1 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +17 -5
- package/corpus/templates/mail/app/root.tsx +16 -1
- package/corpus/templates/mail/changelog/2026-10-02-pressing-r-on-a-message-in-the-list-opens-the-reply-window-e.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-06-generated-decks-from-chatgpt-open-in-the-full-slides-editor.md +6 -0
- package/corpus/templates/slides/server/lib/chatgpt-directory-tools.ts +0 -2
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -48,7 +48,8 @@ Zoom sources import cloud-recording transcripts through a Server-to-Server
|
|
|
48
48
|
OAuth app. Credentials are the vault secrets `ZOOM_ACCOUNT_ID`,
|
|
49
49
|
`ZOOM_CLIENT_ID`, and `ZOOM_CLIENT_SECRET`; the app needs scopes
|
|
50
50
|
`cloud_recording:read:list_account_recordings:admin` and
|
|
51
|
-
`cloud_recording:read:recording:admin
|
|
51
|
+
`cloud_recording:read:recording:admin`, plus
|
|
52
|
+
`cloud_recording:read:list_recording_files:admin` for transcript download URLs. Config is
|
|
52
53
|
`{"zoom":{"userIds":[...],"lookbackDays":7}}`. Without `userIds`, one
|
|
53
54
|
account-wide recording list (`/accounts/me/recordings`, which needs only the `:admin` scope) covers every user.
|
|
54
55
|
`userIds` (up to 50, user ID or email) narrows the import to those users and
|
|
@@ -63,7 +64,11 @@ account is imported. `update-source` replaces the whole `zoom` object, so send
|
|
|
63
64
|
every Zoom field you want to keep. Changing the filter or raising `lookbackDays` rewinds the next sync to
|
|
64
65
|
the `lookbackDays` window, so newly included meetings are backfilled (raise
|
|
65
66
|
`lookbackDays`, up to 30, to reach further back). Run stats report
|
|
66
|
-
`meetingsSkippedByFilter`
|
|
67
|
+
`meetingsSkippedByFilter`, `filterChanged`, `transcriptsWithoutDownloadUrl`,
|
|
68
|
+
`matchedMeetings` (ID, title, start, file types) and `skippedMeetings` (ID,
|
|
69
|
+
start). The account-wide list omits download URLs, so a matched meeting with a
|
|
70
|
+
finished transcript is looked up with `/meetings/{uuid}/recordings`
|
|
71
|
+
(`cloud_recording:read:list_recording_files:admin`) to get the transcript URL.
|
|
67
72
|
Sources auto-sync hourly; each run overlaps the previous one by one day to
|
|
68
73
|
catch late-processed transcripts, and captures dedupe by `zoom:<meeting uuid>`.
|
|
69
74
|
Captures use the organization audience. There are no Zoom webhooks.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: fixed
|
|
3
|
+
date: 2026-10-06
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Zoom transcripts now import. Zoom's account-wide recording list leaves out download links, so Brain now looks up each matching meeting to get its transcript. Each Zoom sync also lists which meetings matched or were skipped, and which files Zoom returned, to make problems easier to spot.
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
ZoomHttpError,
|
|
34
34
|
downloadZoomTranscript,
|
|
35
|
+
getZoomMeetingRecordings,
|
|
36
|
+
isReadyZoomTranscript,
|
|
35
37
|
fetchZoomAccessToken,
|
|
36
38
|
listZoomRecordings,
|
|
37
39
|
hasProcessingTranscript,
|
|
@@ -3030,6 +3032,8 @@ async function syncGranola(source: SourceRow): Promise<ConnectorSyncResult> {
|
|
|
3030
3032
|
|
|
3031
3033
|
const ZOOM_MAX_LOOKBACK_DAYS = 30;
|
|
3032
3034
|
const ZOOM_DEFAULT_LOOKBACK_DAYS = 7;
|
|
3035
|
+
const ZOOM_DIAGNOSTIC_LIMIT = 50;
|
|
3036
|
+
const ZOOM_DIAGNOSTIC_FILE_LIMIT = 10;
|
|
3033
3037
|
const ZOOM_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
3034
3038
|
|
|
3035
3039
|
function utcDate(ms: number): string {
|
|
@@ -3142,6 +3146,7 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
|
|
|
3142
3146
|
recordingListsScanned: 0,
|
|
3143
3147
|
meetingsSeen: 0,
|
|
3144
3148
|
meetingsSkippedByFilter: 0,
|
|
3149
|
+
transcriptsWithoutDownloadUrl: 0,
|
|
3145
3150
|
filterChanged,
|
|
3146
3151
|
transcriptsDownloaded: 0,
|
|
3147
3152
|
emptyTranscripts: 0,
|
|
@@ -3152,6 +3157,18 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
|
|
|
3152
3157
|
rateLimited: false,
|
|
3153
3158
|
};
|
|
3154
3159
|
const pendingMeetingStarts: string[] = [];
|
|
3160
|
+
// Meeting IDs and file types (titles only for matched meetings) show why a
|
|
3161
|
+
// meeting did or did not import.
|
|
3162
|
+
const matchedMeetings: Array<{
|
|
3163
|
+
id: string;
|
|
3164
|
+
topic: string | null;
|
|
3165
|
+
start: string;
|
|
3166
|
+
files: string[];
|
|
3167
|
+
filesOmitted?: number;
|
|
3168
|
+
}> = [];
|
|
3169
|
+
const skippedMeetings: Array<{ id: string; start: string }> = [];
|
|
3170
|
+
stats.matchedMeetings = matchedMeetings;
|
|
3171
|
+
stats.skippedMeetings = skippedMeetings;
|
|
3155
3172
|
|
|
3156
3173
|
try {
|
|
3157
3174
|
const accountId = await requireConnectorCredential(
|
|
@@ -3199,11 +3216,40 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
|
|
|
3199
3216
|
);
|
|
3200
3217
|
stats.meetingsSkippedByFilter =
|
|
3201
3218
|
Number(stats.meetingsSkippedByFilter) + listed.length - meetings.length;
|
|
3219
|
+
for (const meeting of listed) {
|
|
3220
|
+
if (skippedMeetings.length >= ZOOM_DIAGNOSTIC_LIMIT) break;
|
|
3221
|
+
if (!zoomMeetingMatchesFilter(meeting, meetingFilter)) {
|
|
3222
|
+
skippedMeetings.push({
|
|
3223
|
+
id: String(meeting.id),
|
|
3224
|
+
start: meeting.start_time,
|
|
3225
|
+
});
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3202
3228
|
const imported = await importedZoomExternalIds(
|
|
3203
3229
|
source.id,
|
|
3204
3230
|
meetings.map(zoomExternalId),
|
|
3205
3231
|
);
|
|
3206
3232
|
for (const meeting of meetings) {
|
|
3233
|
+
if (matchedMeetings.length < ZOOM_DIAGNOSTIC_LIMIT) {
|
|
3234
|
+
const files = meeting.recording_files ?? [];
|
|
3235
|
+
matchedMeetings.push({
|
|
3236
|
+
id: String(meeting.id),
|
|
3237
|
+
topic: meeting.topic ?? null,
|
|
3238
|
+
start: meeting.start_time,
|
|
3239
|
+
files: files
|
|
3240
|
+
.slice(0, ZOOM_DIAGNOSTIC_FILE_LIMIT)
|
|
3241
|
+
.map((file) =>
|
|
3242
|
+
[
|
|
3243
|
+
file.file_type,
|
|
3244
|
+
file.status ?? "",
|
|
3245
|
+
file.download_url ? "url" : "no-url",
|
|
3246
|
+
].join(":"),
|
|
3247
|
+
),
|
|
3248
|
+
...(files.length > ZOOM_DIAGNOSTIC_FILE_LIMIT
|
|
3249
|
+
? { filesOmitted: files.length - ZOOM_DIAGNOSTIC_FILE_LIMIT }
|
|
3250
|
+
: {}),
|
|
3251
|
+
});
|
|
3252
|
+
}
|
|
3207
3253
|
if (hasProcessingTranscript(meeting)) {
|
|
3208
3254
|
pendingMeetingStarts.push(meeting.start_time);
|
|
3209
3255
|
stats.pendingTranscripts = Number(stats.pendingTranscripts) + 1;
|
|
@@ -3212,12 +3258,28 @@ async function syncZoom(source: SourceRow): Promise<ConnectorSyncResult> {
|
|
|
3212
3258
|
stats.alreadyImported = Number(stats.alreadyImported) + 1;
|
|
3213
3259
|
continue;
|
|
3214
3260
|
}
|
|
3215
|
-
|
|
3261
|
+
let recordingFiles = meeting.recording_files ?? [];
|
|
3262
|
+
const readyTranscripts = recordingFiles.filter(isReadyZoomTranscript);
|
|
3263
|
+
if (
|
|
3264
|
+
readyTranscripts.length > 0 &&
|
|
3265
|
+
readyTranscripts.every((file) => !file.download_url)
|
|
3266
|
+
) {
|
|
3267
|
+
await renewRunLease(run);
|
|
3268
|
+
const detail = await zoomCall(
|
|
3269
|
+
"/meetings/{meetingUuid}/recordings",
|
|
3270
|
+
() => getZoomMeetingRecordings(token, meeting.uuid),
|
|
3271
|
+
);
|
|
3272
|
+
recordingFiles = detail.recording_files ?? [];
|
|
3273
|
+
}
|
|
3274
|
+
const transcripts = recordingFiles.filter(
|
|
3216
3275
|
(file): file is typeof file & { download_url: string } =>
|
|
3217
|
-
file
|
|
3218
|
-
file.status !== "processing" &&
|
|
3219
|
-
Boolean(file.download_url),
|
|
3276
|
+
isReadyZoomTranscript(file) && Boolean(file.download_url),
|
|
3220
3277
|
);
|
|
3278
|
+
stats.transcriptsWithoutDownloadUrl =
|
|
3279
|
+
Number(stats.transcriptsWithoutDownloadUrl) +
|
|
3280
|
+
recordingFiles.filter(
|
|
3281
|
+
(file) => isReadyZoomTranscript(file) && !file.download_url,
|
|
3282
|
+
).length;
|
|
3221
3283
|
for (const file of transcripts) {
|
|
3222
3284
|
await renewRunLease(run);
|
|
3223
3285
|
const vtt = await zoomCall("recording transcript download", () =>
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
normalizeZoomMeetingId,
|
|
3
|
-
normalizeZoomMeetingTopic,
|
|
4
|
-
} from "../../shared/zoom-meeting-filter.js";
|
|
5
1
|
import { sanitizeSensitiveText } from "./sensitivity-policy.js";
|
|
6
2
|
|
|
7
3
|
const ZOOM_API_BASE = "https://api.zoom.us/v2";
|
|
@@ -207,6 +203,30 @@ async function listZoomRecordingPages(
|
|
|
207
203
|
return meetings;
|
|
208
204
|
}
|
|
209
205
|
|
|
206
|
+
export function isReadyZoomTranscript(file: ZoomRecordingFile): boolean {
|
|
207
|
+
return file.file_type === "TRANSCRIPT" && file.status !== "processing";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Zoom requires a UUID that starts with "/" or contains "//" to be encoded twice.
|
|
211
|
+
export function zoomMeetingUuidPath(uuid: string): string {
|
|
212
|
+
const encoded = encodeURIComponent(uuid);
|
|
213
|
+
return uuid.startsWith("/") || uuid.includes("//")
|
|
214
|
+
? encodeURIComponent(encoded)
|
|
215
|
+
: encoded;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// The account-wide list omits download_url; this per-meeting lookup includes it.
|
|
219
|
+
export function getZoomMeetingRecordings(
|
|
220
|
+
token: string,
|
|
221
|
+
meetingUuid: string,
|
|
222
|
+
): Promise<ZoomMeeting> {
|
|
223
|
+
return zoomApiJson<ZoomMeeting>(
|
|
224
|
+
token,
|
|
225
|
+
`${ZOOM_API_BASE}/meetings/${zoomMeetingUuidPath(meetingUuid)}/recordings`,
|
|
226
|
+
"meeting recording lookup",
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
/**
|
|
211
231
|
* Redirects are followed by hand: the bearer token may only ride along to
|
|
212
232
|
* Zoom hosts, so a hop to a CDN or any other origin is fetched without it.
|
|
@@ -304,6 +324,19 @@ export interface ZoomMeetingFilter {
|
|
|
304
324
|
meetingTopics: Set<string>;
|
|
305
325
|
}
|
|
306
326
|
|
|
327
|
+
// Zoom shows meeting IDs as "123 4567 8901"; the API returns 12345678901.
|
|
328
|
+
export function normalizeZoomMeetingId(value: unknown): string | null {
|
|
329
|
+
if (typeof value !== "string" && typeof value !== "number") return null;
|
|
330
|
+
const id = String(value).replace(/[\s-]/g, "");
|
|
331
|
+
return /^\d{6,15}$/.test(id) ? id : null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function normalizeZoomMeetingTopic(value: unknown): string | null {
|
|
335
|
+
if (typeof value !== "string") return null;
|
|
336
|
+
const topic = value.trim().replace(/\s+/g, " ").toLowerCase();
|
|
337
|
+
return topic || null;
|
|
338
|
+
}
|
|
339
|
+
|
|
307
340
|
function normalizedSet(
|
|
308
341
|
raw: unknown,
|
|
309
342
|
normalize: (value: unknown) => string | null,
|
|
@@ -66,7 +66,7 @@ registerRequiredSecret({
|
|
|
66
66
|
key: "ZOOM_ACCOUNT_ID",
|
|
67
67
|
label: "Zoom Account ID",
|
|
68
68
|
description:
|
|
69
|
-
"Optional Zoom Server-to-Server OAuth account ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
|
|
69
|
+
"Optional Zoom Server-to-Server OAuth account ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin, cloud_recording:read:list_recording_files:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
|
|
70
70
|
docsUrl: "https://developers.zoom.us/docs/internal-apps/s2s-oauth/",
|
|
71
71
|
scope: "workspace",
|
|
72
72
|
kind: "api-key",
|
|
@@ -84,7 +84,7 @@ registerRequiredSecret({
|
|
|
84
84
|
key: "ZOOM_CLIENT_ID",
|
|
85
85
|
label: "Zoom Client ID",
|
|
86
86
|
description:
|
|
87
|
-
"Optional Zoom Server-to-Server OAuth client ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
|
|
87
|
+
"Optional Zoom Server-to-Server OAuth client ID for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin, cloud_recording:read:list_recording_files:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
|
|
88
88
|
docsUrl: "https://developers.zoom.us/docs/internal-apps/s2s-oauth/",
|
|
89
89
|
scope: "workspace",
|
|
90
90
|
kind: "api-key",
|
|
@@ -102,7 +102,7 @@ registerRequiredSecret({
|
|
|
102
102
|
key: "ZOOM_CLIENT_SECRET",
|
|
103
103
|
label: "Zoom Client Secret",
|
|
104
104
|
description:
|
|
105
|
-
"Optional Zoom Server-to-Server OAuth client secret for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
|
|
105
|
+
"Optional Zoom Server-to-Server OAuth client secret for cloud-recording transcript imports. The app needs scopes cloud_recording:read:list_account_recordings:admin, cloud_recording:read:list_recording_files:admin and cloud_recording:read:recording:admin; sources that set zoom.userIds also need cloud_recording:read:list_user_recordings:admin.",
|
|
106
106
|
docsUrl: "https://developers.zoom.us/docs/internal-apps/s2s-oauth/",
|
|
107
107
|
scope: "workspace",
|
|
108
108
|
kind: "api-key",
|
|
@@ -15,8 +15,6 @@ export const CHATGPT_DIRECTORY_PROFILE = {
|
|
|
15
15
|
connectorCatalog: CHATGPT_DIRECTORY_TOOL_NAMES,
|
|
16
16
|
widgets: true,
|
|
17
17
|
widgetDomain: "https://content.agent-native.com",
|
|
18
|
-
// TEMPORARY: omit frame domains and widget domain only for the scan A/B.
|
|
19
|
-
widgetDiagnostic: "no-frame-domains",
|
|
20
18
|
keyToolNames: [
|
|
21
19
|
"search-documents",
|
|
22
20
|
"get-document",
|
|
@@ -357,8 +357,9 @@ follow-up ambiguity instead of resolving it.
|
|
|
357
357
|
|
|
358
358
|
**Carry the form-factor answer through to generation — do not just ask and discard it.** Map the answer to the design's device SET, not to separate per-device screen files. Device widths of the SAME page are breakpoint frames of one document (see the `responsive-breakpoints` skill), never a `mobile.html` + `desktop.html` pair. Pass the answer through `generate-design`'s `devices` param — `("mobile"|"tablet"|"desktop")[]`, default `["desktop","mobile"]`:
|
|
359
359
|
|
|
360
|
+
- An exact pixel size in the prompt defines one fixed-size canvas. Use those exact dimensions, pass `devices: []` (or `responsive: false` to `present-design-variants`), and do not generate mobile or other device frames for it. Keep each action prompt to one distinct exact canvas size; make separate calls scoped to each screen when a request names different sizes.
|
|
360
361
|
- If the prompt/answer names specific devices, generate EXACTLY those, deduped ("mobile" only → one mobile frame; "mobile, tablet, desktop" → all three).
|
|
361
|
-
- If
|
|
362
|
+
- If no exact pixel size or form factor is specified — or the answer is "Both / responsive" or "Decide for me" — default to `["desktop","mobile"]`: a Desktop base + a Mobile frame only. Never auto-add a tablet, a redundant desktop, or a stray duplicate frame.
|
|
362
363
|
|
|
363
364
|
The WIDEST requested device is the base/primary frame; narrower devices become breakpoint frames (never at the primary width). Device frame sizes: mobile 390×844, tablet 768×1024, desktop 1440×900. `present-design-variants` still takes explicit `width`/`height` per variant to size its exploration screens (see Phase 2).
|
|
364
365
|
|
|
@@ -390,7 +391,7 @@ screen name.
|
|
|
390
391
|
|
|
391
392
|
Each `content` is a complete, self-contained document (Alpine.js + Tailwind via CDN, full `<head>`, CSS variables in `:root`). Variations should be **structurally and compositionally distinct** — different layout grammars, hierarchy, density, and focal points — never just color swaps. When a design system is linked, keep its tokens, typography, components, and imagery rules fixed across variants; vary those only when the user explicitly asks to explore a replacement system. Label the directions with concrete names ("Editorial split", not "Variant A").
|
|
392
393
|
|
|
393
|
-
Pass `width`/`height` on every variant to match the form-factor answer (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — the example above is desktop-sized. When `content` is omitted, `present-design-variants` infers a size from the prompt/label/description text and the width/height you pass still wins when given.
|
|
394
|
+
Pass `width`/`height` on every variant to match the form-factor answer (mobile ≈ 390×844, tablet ≈ 768×1024, desktop ≈ 1440×900) — the example above is desktop-sized. When the prompt specifies exact pixel dimensions, use that exact size on every variant and pass `responsive: false`; do not clamp or add mobile frames. When `content` is omitted, `present-design-variants` infers a size from the prompt/label/description text and the width/height you pass still wins when given.
|
|
394
395
|
|
|
395
396
|
Wait for the user's pick before refining. Once they choose, keep the selected
|
|
396
397
|
screen, delete the unchosen variant screens with `delete-file`, and continue
|
|
@@ -424,9 +425,12 @@ Pass the `devices` param (`("mobile"|"tablet"|"desktop")[]`, default `["desktop"
|
|
|
424
425
|
|
|
425
426
|
#### Non-web sizes — ad units, print one-pagers, social sizes
|
|
426
427
|
|
|
427
|
-
`canvasFrames` accepts
|
|
428
|
-
ad" style requests work the same way —
|
|
429
|
-
|
|
428
|
+
`canvasFrames` accepts exact `width`/`height` in px, so "create a 300x250
|
|
429
|
+
ad" style requests work the same way — use the requested dimensions verbatim
|
|
430
|
+
and pass `devices: []` so the fixed-size screen has no extra mobile view. Keep
|
|
431
|
+
each action prompt to one distinct exact size; use separate calls for screens
|
|
432
|
+
with different dimensions. This also applies to exact-size email and social
|
|
433
|
+
assets. The editor's own Frame tool preset
|
|
430
434
|
list (`app/components/design/inspector/frame-size-presets.ts`) documents the
|
|
431
435
|
canonical sizes to reuse instead of guessing:
|
|
432
436
|
|
|
@@ -475,7 +479,9 @@ by unlinking the system. It is the one check that can catch a design that
|
|
|
475
479
|
looks fine and is still not the user's.
|
|
476
480
|
|
|
477
481
|
After the audit is clean, call `take-design-screenshot` on each changed screen
|
|
478
|
-
(default: 1280px desktop + 375px mobile).
|
|
482
|
+
(default: 1280px desktop + 375px mobile). For an exact-size screen, capture
|
|
483
|
+
only its specified viewport by passing `widths: [width]` and
|
|
484
|
+
`heights: [height]`. Fix everything its `diagnostics`
|
|
479
485
|
report flags — real computed contrast ratios, horizontal/container overflow,
|
|
480
486
|
broken images, zero-size or off-screen text, console errors — before reporting
|
|
481
487
|
the design as ready; this is the same "visually inspect the result" pass, done
|