@agent-native/core-corpus 0.201.0-nightly-20261005153421 → 0.201.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/security/SKILL.md +9 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +14 -4
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/analytics/CHANGELOG.md +6 -0
- package/corpus/templates/analytics/README.md +2 -2
- package/corpus/templates/analytics/changelog/2026-10-02-events-with-a-very-long-name-app-or-page-path-no-longer-caus.md +6 -0
- package/corpus/templates/analytics/package.json +1 -0
- package/corpus/templates/analytics/server/handlers/first-party-analytics.ts +9 -19
- package/corpus/templates/analytics/server/handlers/session-replay.ts +34 -47
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -36
- package/corpus/templates/analytics/server/lib/indexed-text.ts +53 -0
- package/corpus/templates/analytics/server/lib/request-errors.ts +73 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +6 -16
- package/corpus/templates/analytics/server/lib/session-replay.ts +12 -2
- package/corpus/templates/analytics/server/plugins/auth.ts +2 -1
- package/corpus/templates/analytics/server/plugins/otel.ts +6 -0
- package/corpus/templates/assets/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/assets/CHANGELOG.md +7 -0
- package/corpus/templates/assets/README.md +2 -2
- package/corpus/templates/assets/package.json +1 -0
- package/corpus/templates/assets/server/plugins/auth.ts +2 -1
- package/corpus/templates/assets/server/plugins/otel.ts +6 -0
- package/corpus/templates/brain/.agents/skills/brain/RUNBOOK.md +2 -1
- package/corpus/templates/brain/.agents/skills/brain/SKILL.md +5 -4
- package/corpus/templates/brain/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/brain/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/brain/CHANGELOG.md +6 -0
- package/corpus/templates/brain/README.md +12 -8
- package/corpus/templates/brain/changelog/2026-10-05-private-slack-channels-are-searchable-org-wide.md +6 -0
- package/corpus/templates/brain/package.json +1 -0
- package/corpus/templates/brain/server/lib/audiences.ts +1 -98
- package/corpus/templates/brain/server/lib/connectors.ts +2 -166
- package/corpus/templates/brain/server/plugins/otel.ts +6 -0
- package/corpus/templates/calendar/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/calendar/CHANGELOG.md +7 -0
- package/corpus/templates/calendar/README.md +2 -2
- package/corpus/templates/calendar/package.json +1 -0
- package/corpus/templates/calendar/server/plugins/auth.ts +2 -1
- package/corpus/templates/calendar/server/plugins/otel.ts +6 -0
- package/corpus/templates/chat/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/chat/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/chat/README.md +2 -2
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +9 -0
- package/corpus/templates/clips/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/clips/CHANGELOG.md +24 -0
- package/corpus/templates/clips/README.md +2 -2
- package/corpus/templates/clips/actions/lib/audio-chunk-list.ts +23 -0
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +187 -292
- package/corpus/templates/clips/actions/lib/chunked-transcription.ts +151 -0
- package/corpus/templates/clips/actions/lib/recording-audio-source.ts +191 -0
- package/corpus/templates/clips/actions/request-transcript.ts +81 -135
- package/corpus/templates/clips/actions/search-recordings.ts +19 -5
- package/corpus/templates/clips/app/components/clips-command-menu.tsx +5 -0
- package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -1
- package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -0
- package/corpus/templates/clips/app/hooks/use-library.ts +1 -0
- package/corpus/templates/clips/app/lib/local-recording-upload.ts +13 -6
- package/corpus/templates/clips/app/lib/recording-section.ts +1 -1
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +7 -5
- package/corpus/templates/clips/app/routes/record.tsx +16 -6
- package/corpus/templates/clips/changelog/2026-10-02-long-uploads-an-hour-or-more-now-transcribe-reliably-instead.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-confirming-discard-recording-now-returns-you-to-your-library.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recording-now-removes-its-failed-upload-card-fr.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-discarding-a-recovered-recording-no-longer-warns-that-it-s-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-05-search-results-identify-recordings-in-trash-and-open-them-wi.md +6 -0
- package/corpus/templates/clips/package.json +1 -0
- package/corpus/templates/clips/server/lib/builder-cdn.ts +5 -0
- package/corpus/templates/clips/server/lib/builder-media-compression.ts +2 -2
- package/corpus/templates/clips/server/plugins/auth.ts +2 -1
- package/corpus/templates/clips/server/plugins/otel.ts +6 -0
- package/corpus/templates/clips/shared/transcript-failure.ts +5 -0
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +8 -2
- package/corpus/templates/content/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/content/CHANGELOG.md +41 -0
- package/corpus/templates/content/README.md +2 -2
- package/corpus/templates/content/actions/_database-navigation.ts +523 -344
- package/corpus/templates/content/actions/_database-row-mutation.ts +44 -10
- package/corpus/templates/content/actions/_database-setup-discovery.ts +9 -3
- package/corpus/templates/content/actions/_database-utils.ts +299 -271
- package/corpus/templates/content/actions/_property-utils.ts +293 -190
- package/corpus/templates/content/actions/_relation-values.ts +3 -1
- package/corpus/templates/content/actions/get-content-database.ts +1 -0
- package/corpus/templates/content/actions/query-content-database-items.ts +32 -5
- package/corpus/templates/content/actions/set-document-property.ts +17 -11
- package/corpus/templates/content/app/components/editor/DocumentBlockFields.tsx +21 -6
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -43
- package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +159 -50
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +12 -28
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +6 -1
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +139 -70
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +16 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +180 -75
- package/corpus/templates/content/app/components/editor/database/DatabaseViewSkeleton.tsx +76 -0
- package/corpus/templates/content/app/components/editor/database/navigation-state.ts +60 -2
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +41 -42
- package/corpus/templates/content/app/components/editor/document-editor-layout.ts +18 -0
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +11 -3
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +15 -7
- package/corpus/templates/content/app/components/editor/suggestions/model.ts +13 -0
- package/corpus/templates/content/app/components/editor/suggestions/read-only-blocks.ts +136 -0
- package/corpus/templates/content/app/components/editor/table-view.ts +73 -0
- package/corpus/templates/content/app/components/layout/ContentStartupShell.tsx +1 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +5 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +57 -16
- package/corpus/templates/content/app/components/sidebar/MovePageDialog.tsx +4 -4
- package/corpus/templates/content/app/components/sidebar/SidebarNavigationRow.tsx +34 -10
- package/corpus/templates/content/app/global.css +16 -8
- package/corpus/templates/content/app/hooks/content-action-refresh.ts +14 -13
- package/corpus/templates/content/app/hooks/use-content-database.ts +72 -9
- package/corpus/templates/content/app/hooks/use-content-personal-navigation.ts +7 -3
- package/corpus/templates/content/app/hooks/use-documents.ts +27 -0
- package/corpus/templates/content/app/i18n-data.ts +3 -2
- package/corpus/templates/content/app/lib/content-landing.ts +3 -31
- package/corpus/templates/content/app/lib/files-navigation.ts +182 -10
- package/corpus/templates/content/app/lib/files-root-hint.ts +20 -32
- package/corpus/templates/content/app/lib/page-startup-hints.ts +108 -0
- package/corpus/templates/content/app/lib/private-link-redirect.ts +25 -0
- package/corpus/templates/content/app/lib/sidebar-layout-hint.ts +34 -1
- package/corpus/templates/content/app/lib/startup-timing.ts +2 -0
- package/corpus/templates/content/app/root.tsx +3 -1
- package/corpus/templates/content/app/routes/_app.home.tsx +10 -19
- package/corpus/templates/content/app/routes/p.$id.tsx +23 -5
- package/corpus/templates/content/changelog/2026-10-02-a-page-link-you-cant-open-now-says-so-instead-of-opening-an.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-after-a-refresh-the-files-sidebar-shows-the-open-page-s-fold.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-large-collections-open-faster.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-pages-stay-inside-the-window-at-every-width-long-inline-code.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-pages-with-open-comments-and-collections-with-sorted-or-filt.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-suggest-edits-works-on-pages-written-by-agents-including-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-10-02-while-you-suggest-edits-tables-and-other-blocks-that-suggest.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-collection-view-context-now-survives-quick-page-changes-when.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-collection-views-keep-their-saved-rows-accurate-during-refre.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-exact-saved-view-links-keep-collection-retry-available.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-expanding-a-files-folder-now-shares-one-navigation-read-acro.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-files-navigation-retries-temporary-failures-consistently.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-shared-page-owners-keep-edit-and-manage-controls-in-files-an.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-tagging-an-ai-in-a-comment-works-again-instead-of-failing-ri.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-the-home-loading-shell-keeps-the-layout-of-your-last-opened-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-05-typing-and-a-space-or-inserting-a-toggle-or-callout-no-longe.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.workspace.session-restore.md +4 -1
- package/corpus/templates/content/e2e/playwright.config.ts +1 -1
- package/corpus/templates/content/package.json +2 -1
- package/corpus/templates/content/scripts/seed-perf-database.mjs +600 -0
- package/corpus/templates/content/scripts/trace-startup.mjs +25 -3
- package/corpus/templates/content/server/db/index.ts +1 -1
- package/corpus/templates/content/server/lib/suggested-edits.ts +23 -26
- package/corpus/templates/content/server/plugins/auth.ts +2 -1
- package/corpus/templates/content/server/plugins/otel.ts +6 -0
- package/corpus/templates/content/shared/api.ts +17 -0
- package/corpus/templates/content/shared/suggestion-diff.ts +124 -38
- package/corpus/templates/content/shared/suggestion-formatting.ts +134 -3
- package/corpus/templates/crm/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/crm/package.json +1 -0
- package/corpus/templates/crm/server/plugins/otel.ts +6 -0
- package/corpus/templates/design/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/design/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/design/CHANGELOG.md +41 -0
- package/corpus/templates/design/README.md +2 -2
- package/corpus/templates/design/actions/list-designs.ts +11 -4
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +110 -32
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +28 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +1 -0
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +79 -0
- package/corpus/templates/design/app/components/layout/Header.tsx +2 -4
- package/corpus/templates/design/app/components/ui/tabs.tsx +59 -1
- package/corpus/templates/design/app/global.css +1 -34
- package/corpus/templates/design/app/i18n/en-US.ts +4 -0
- package/corpus/templates/design/app/lib/design-filter.ts +4 -2
- package/corpus/templates/design/app/pages/Index.tsx +91 -59
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +58 -17
- package/corpus/templates/design/changelog/2026-10-01-filter-designs-by-owner.md +6 -0
- package/corpus/templates/design/changelog/2026-10-03-layers-dropped-into-visible-canvas-space-beyond-a-screen-now.md +6 -0
- package/corpus/templates/design/changelog/2026-10-03-visible-screen-overflow-no-longer-routes-drops-to-the-canvas.md +6 -0
- package/corpus/templates/design/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-designs.md +6 -0
- package/corpus/templates/design/package.json +1 -0
- package/corpus/templates/design/server/plugins/auth.ts +2 -1
- package/corpus/templates/design/server/plugins/otel.ts +6 -0
- package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/dispatch/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/dispatch/README.md +2 -2
- package/corpus/templates/dispatch/package.json +1 -0
- package/corpus/templates/dispatch/server/plugins/otel.ts +6 -0
- package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +2 -1
- package/corpus/templates/factory/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/factory/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/factory/package.json +1 -0
- package/corpus/templates/factory/server/plugins/otel.ts +6 -0
- package/corpus/templates/forms/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/forms/README.md +2 -2
- package/corpus/templates/forms/package.json +1 -0
- package/corpus/templates/forms/server/plugins/auth.ts +2 -1
- package/corpus/templates/forms/server/plugins/otel.ts +6 -0
- package/corpus/templates/mail/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/mail/CHANGELOG.md +6 -0
- package/corpus/templates/mail/README.md +2 -2
- package/corpus/templates/mail/package.json +1 -0
- package/corpus/templates/mail/server/plugins/auth.ts +2 -1
- package/corpus/templates/mail/server/plugins/otel.ts +6 -0
- package/corpus/templates/plan/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/plan/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/plan/CHANGELOG.md +1 -0
- package/corpus/templates/plan/README.md +2 -2
- package/corpus/templates/plan/package.json +1 -0
- package/corpus/templates/plan/server/plugins/auth.ts +2 -1
- package/corpus/templates/plan/server/plugins/otel.ts +6 -0
- package/corpus/templates/slides/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/slides/.agents/skills/storing-data/SKILL.md +12 -7
- package/corpus/templates/slides/CHANGELOG.md +23 -0
- package/corpus/templates/slides/README.md +2 -2
- package/corpus/templates/slides/actions/list-decks.ts +11 -4
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +44 -36
- package/corpus/templates/slides/app/components/home/SlidesHomeLibrary.tsx +79 -0
- package/corpus/templates/slides/app/components/layout/Header.tsx +2 -15
- package/corpus/templates/slides/app/components/ui/tabs.tsx +59 -0
- package/corpus/templates/slides/app/global.css +3 -54
- package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
- package/corpus/templates/slides/app/lib/deck-filter.ts +5 -2
- package/corpus/templates/slides/app/pages/Index.tsx +102 -88
- package/corpus/templates/slides/changelog/2026-10-01-filter-decks-by-owner.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-02-hide-recent-until-decks-available.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-05-the-recent-tab-shows-loading-placeholders-until-your-decks-a.md +6 -0
- package/corpus/templates/slides/package.json +1 -0
- package/corpus/templates/slides/server/handlers/request-auth-context.ts +8 -0
- package/corpus/templates/slides/server/plugins/auth.ts +2 -1
- package/corpus/templates/slides/server/plugins/otel.ts +6 -0
- package/corpus/templates/tasks/.agents/skills/security/SKILL.md +9 -2
- package/corpus/templates/tasks/.agents/skills/storing-data/SKILL.md +12 -7
- package/package.json +1 -1
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Seeds one wide Content collection through the public action surface, shaped
|
|
3
|
+
// like a real priorities tracker: a few hundred rows, a dozen properties with
|
|
4
|
+
// several long text values, status/select/date/number/relation columns, row
|
|
5
|
+
// bodies with stored block identities, and saved views that sort and filter.
|
|
6
|
+
// Progress is written to a manifest so an interrupted run resumes instead of
|
|
7
|
+
// duplicating rows.
|
|
8
|
+
//
|
|
9
|
+
// node scripts/seed-perf-database.mjs --base-url http://127.0.0.1:8080 \
|
|
10
|
+
// --email perf-owner@example.local --password '...' \
|
|
11
|
+
// [--rows 230] [--title "Perf Task Priorities"] [--concurrency 6]
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
|
|
16
|
+
const args = new Map();
|
|
17
|
+
for (let i = 2; i < process.argv.length; i += 1) {
|
|
18
|
+
const arg = process.argv[i];
|
|
19
|
+
if (!arg.startsWith("--")) continue;
|
|
20
|
+
const next = process.argv[i + 1];
|
|
21
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
22
|
+
args.set(arg.slice(2), next);
|
|
23
|
+
i += 1;
|
|
24
|
+
} else {
|
|
25
|
+
args.set(arg.slice(2), "true");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function required(name) {
|
|
30
|
+
const value = args.get(name);
|
|
31
|
+
if (!value) throw new Error(`--${name} is required`);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const baseUrl = required("base-url").replace(/\/+$/, "");
|
|
36
|
+
const email = required("email");
|
|
37
|
+
const password = required("password");
|
|
38
|
+
function positiveInteger(name, fallback) {
|
|
39
|
+
const raw = args.get(name);
|
|
40
|
+
if (raw === undefined) return fallback;
|
|
41
|
+
const value = Number(raw);
|
|
42
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
43
|
+
throw new Error(`--${name} must be a positive integer, got "${raw}"`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rowCount = positiveInteger("rows", 230);
|
|
49
|
+
const title = args.get("title") ?? "Perf Task Priorities";
|
|
50
|
+
const concurrency = positiveInteger("concurrency", 6);
|
|
51
|
+
const allowRegister = args.get("register") !== "false";
|
|
52
|
+
const hostPart = new URL(baseUrl).host.replace(/[^a-z0-9.-]/gi, "_");
|
|
53
|
+
const emailPart = email.replace(/[^a-z0-9]/gi, "_");
|
|
54
|
+
const titlePart = title.replace(/[^a-z0-9]/gi, "_");
|
|
55
|
+
const defaultManifestStem = `perf-database-${hostPart}-${emailPart}-${titlePart}`;
|
|
56
|
+
const legacyDefaultManifestPath = resolve(`.tmp/${defaultManifestStem}.json`);
|
|
57
|
+
const manifestIdentity = JSON.stringify([
|
|
58
|
+
new URL(baseUrl).origin,
|
|
59
|
+
email,
|
|
60
|
+
title,
|
|
61
|
+
]);
|
|
62
|
+
const manifestSuffix = createHash("sha256")
|
|
63
|
+
.update(manifestIdentity)
|
|
64
|
+
.digest("hex")
|
|
65
|
+
.slice(0, 16);
|
|
66
|
+
const usesDefaultManifestPath = !args.has("manifest");
|
|
67
|
+
const manifestPath = resolve(
|
|
68
|
+
args.get("manifest") ?? `.tmp/${defaultManifestStem}-${manifestSuffix}.json`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (args.has("manifest-path-only")) {
|
|
72
|
+
console.log(manifestPath);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
usesDefaultManifestPath &&
|
|
78
|
+
!existsSync(manifestPath) &&
|
|
79
|
+
existsSync(legacyDefaultManifestPath)
|
|
80
|
+
) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`The legacy default manifest at "${legacyDefaultManifestPath}" has no fixture identity. Pass --manifest after confirming it belongs to this host, email, and title, or move it aside to start a separate fixture.`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Deterministic generator so reruns produce the same fixture shape.
|
|
87
|
+
let seed = 0x7a5c;
|
|
88
|
+
function random() {
|
|
89
|
+
seed = (seed * 1664525 + 1013904223) >>> 0;
|
|
90
|
+
return seed / 0x100000000;
|
|
91
|
+
}
|
|
92
|
+
function pick(values) {
|
|
93
|
+
return values[Math.floor(random() * values.length)];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const WORDS =
|
|
97
|
+
"follow up with the team about launch plan review draft schedule call confirm budget numbers send notes share update customer feedback check invoice renew contract book travel outline proposal finish migration test rollout write summary prepare agenda unblock hiring decision collect quotes compare vendors pick date reply thread".split(
|
|
98
|
+
" ",
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
function sentence(min = 8, max = 18) {
|
|
102
|
+
const length = min + Math.floor(random() * (max - min));
|
|
103
|
+
const words = Array.from({ length }, () => pick(WORDS));
|
|
104
|
+
words[0] = words[0][0].toUpperCase() + words[0].slice(1);
|
|
105
|
+
return `${words.join(" ")}.`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Long text cells like "Next action" and "Waiting on": 150-300 characters.
|
|
109
|
+
function longText() {
|
|
110
|
+
const target = 150 + Math.floor(random() * 150);
|
|
111
|
+
let text = sentence();
|
|
112
|
+
while (text.length < target) text += ` ${sentence()}`;
|
|
113
|
+
return text
|
|
114
|
+
.slice(0, target)
|
|
115
|
+
.replace(/\s+\S*$/, "")
|
|
116
|
+
.concat(".");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function rowBody() {
|
|
120
|
+
const parts = [`## ${sentence(3, 6).replace(/\.$/, "")}`];
|
|
121
|
+
const paragraphs = 2 + Math.floor(random() * 4);
|
|
122
|
+
for (let p = 0; p < paragraphs; p += 1) {
|
|
123
|
+
parts.push(Array.from({ length: 3 }, () => sentence()).join(" "));
|
|
124
|
+
}
|
|
125
|
+
parts.push(
|
|
126
|
+
Array.from(
|
|
127
|
+
{ length: 2 + Math.floor(random() * 3) },
|
|
128
|
+
() => `- ${sentence(4, 9)}`,
|
|
129
|
+
).join("\n"),
|
|
130
|
+
);
|
|
131
|
+
parts.push(sentence(10, 20));
|
|
132
|
+
return parts.join("\n\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isoDay(offsetDays) {
|
|
136
|
+
const date = new Date(Date.UTC(2026, 8, 1) + offsetDays * 86_400_000);
|
|
137
|
+
return date.toISOString().slice(0, 10);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const STATUS_OPTIONS = [
|
|
141
|
+
{ id: "not-started", name: "Not started", color: "gray" },
|
|
142
|
+
{ id: "in-progress", name: "In progress", color: "blue" },
|
|
143
|
+
{ id: "waiting", name: "Waiting", color: "yellow" },
|
|
144
|
+
{ id: "done", name: "Done", color: "green" },
|
|
145
|
+
];
|
|
146
|
+
const AREA_OPTIONS = [
|
|
147
|
+
{ id: "personal", name: "Personal", color: "purple" },
|
|
148
|
+
{ id: "builder", name: "Builder", color: "blue" },
|
|
149
|
+
{ id: "parasail", name: "Parasail", color: "orange" },
|
|
150
|
+
{ id: "home", name: "Home", color: "green" },
|
|
151
|
+
];
|
|
152
|
+
const PRIORITY_OPTIONS = [
|
|
153
|
+
{ id: "p0", name: "P0", color: "red" },
|
|
154
|
+
{ id: "p1", name: "P1", color: "orange" },
|
|
155
|
+
{ id: "p2", name: "P2", color: "yellow" },
|
|
156
|
+
{ id: "p3", name: "P3", color: "gray" },
|
|
157
|
+
];
|
|
158
|
+
const TAG_OPTIONS = [
|
|
159
|
+
{ id: "email", name: "Email", color: "blue" },
|
|
160
|
+
{ id: "call", name: "Call", color: "green" },
|
|
161
|
+
{ id: "writing", name: "Writing", color: "purple" },
|
|
162
|
+
{ id: "errand", name: "Errand", color: "orange" },
|
|
163
|
+
{ id: "deep-work", name: "Deep work", color: "red" },
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
// Property order is the column order a new table shows.
|
|
167
|
+
const PROPERTY_PLAN = [
|
|
168
|
+
{ key: "rank", name: "Rank", type: "number" },
|
|
169
|
+
{
|
|
170
|
+
key: "status",
|
|
171
|
+
name: "Status",
|
|
172
|
+
type: "status",
|
|
173
|
+
options: { options: STATUS_OPTIONS },
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
key: "area",
|
|
177
|
+
name: "Area",
|
|
178
|
+
type: "select",
|
|
179
|
+
options: { options: AREA_OPTIONS },
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
key: "priority",
|
|
183
|
+
name: "Priority",
|
|
184
|
+
type: "select",
|
|
185
|
+
options: { options: PRIORITY_OPTIONS },
|
|
186
|
+
},
|
|
187
|
+
{ key: "nextAction", name: "Next action", type: "text" },
|
|
188
|
+
{ key: "waitingOn", name: "Waiting on", type: "text" },
|
|
189
|
+
{ key: "notes", name: "Notes", type: "text" },
|
|
190
|
+
{ key: "due", name: "Due", type: "date" },
|
|
191
|
+
{ key: "lastTouched", name: "Last touched", type: "date" },
|
|
192
|
+
{ key: "effort", name: "Effort", type: "number" },
|
|
193
|
+
{
|
|
194
|
+
key: "tags",
|
|
195
|
+
name: "Tags",
|
|
196
|
+
type: "multi_select",
|
|
197
|
+
options: { options: TAG_OPTIONS },
|
|
198
|
+
},
|
|
199
|
+
{ key: "link", name: "Link", type: "url" },
|
|
200
|
+
{ key: "blockedBy", name: "Blocked by", type: "relation", relation: true },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
function rowValues(index, propertyIds) {
|
|
204
|
+
const status =
|
|
205
|
+
index % 7 === 0
|
|
206
|
+
? "done"
|
|
207
|
+
: index % 5 === 0
|
|
208
|
+
? "waiting"
|
|
209
|
+
: index % 3 === 0
|
|
210
|
+
? "in-progress"
|
|
211
|
+
: "not-started";
|
|
212
|
+
const values = {
|
|
213
|
+
[propertyIds.rank]: index + 1,
|
|
214
|
+
[propertyIds.status]: status,
|
|
215
|
+
[propertyIds.area]: AREA_OPTIONS[index % AREA_OPTIONS.length].id,
|
|
216
|
+
[propertyIds.priority]: pick(PRIORITY_OPTIONS).id,
|
|
217
|
+
[propertyIds.nextAction]: longText(),
|
|
218
|
+
[propertyIds.notes]: longText(),
|
|
219
|
+
[propertyIds.due]: { start: isoDay(index % 90), includeTime: false },
|
|
220
|
+
[propertyIds.lastTouched]: {
|
|
221
|
+
start: isoDay(-(index % 30)),
|
|
222
|
+
includeTime: false,
|
|
223
|
+
},
|
|
224
|
+
[propertyIds.effort]: 1 + Math.floor(random() * 8),
|
|
225
|
+
[propertyIds.tags]: TAG_OPTIONS.filter(() => random() < 0.35).map(
|
|
226
|
+
(option) => option.id,
|
|
227
|
+
),
|
|
228
|
+
[propertyIds.link]: `https://example.com/tasks/${index + 1}`,
|
|
229
|
+
};
|
|
230
|
+
if (status === "waiting" || index % 4 === 0) {
|
|
231
|
+
values[propertyIds.waitingOn] = longText();
|
|
232
|
+
}
|
|
233
|
+
return values;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function loadManifest() {
|
|
237
|
+
if (!existsSync(manifestPath)) {
|
|
238
|
+
return { properties: {}, rows: [], bodies: [], relations: [] };
|
|
239
|
+
}
|
|
240
|
+
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function saveManifest(manifest) {
|
|
244
|
+
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
245
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function signIn(accountEmail, accountPassword) {
|
|
249
|
+
const post = (path, payload) =>
|
|
250
|
+
fetch(`${baseUrl}${path}`, {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: { "content-type": "application/json", origin: baseUrl },
|
|
253
|
+
body: JSON.stringify(payload),
|
|
254
|
+
redirect: "manual",
|
|
255
|
+
});
|
|
256
|
+
let response = await post("/_agent-native/auth/login", {
|
|
257
|
+
email: accountEmail,
|
|
258
|
+
password: accountPassword,
|
|
259
|
+
});
|
|
260
|
+
if (!response.ok && allowRegister) {
|
|
261
|
+
await post("/_agent-native/auth/register", {
|
|
262
|
+
email: accountEmail,
|
|
263
|
+
password: accountPassword,
|
|
264
|
+
name: accountEmail.split("@")[0],
|
|
265
|
+
callbackURL: "/",
|
|
266
|
+
});
|
|
267
|
+
response = await post("/_agent-native/auth/login", {
|
|
268
|
+
email: accountEmail,
|
|
269
|
+
password: accountPassword,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if (!response.ok) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`Sign-in failed for ${accountEmail} (${response.status}): ${await response.text()}`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
const cookies = response.headers
|
|
278
|
+
.getSetCookie()
|
|
279
|
+
.map((cookie) => cookie.split(";")[0])
|
|
280
|
+
.filter(Boolean);
|
|
281
|
+
if (cookies.length === 0) {
|
|
282
|
+
throw new Error(`Sign-in for ${accountEmail} returned no session cookie`);
|
|
283
|
+
}
|
|
284
|
+
const cookie = cookies.join("; ");
|
|
285
|
+
const completed = await fetch(
|
|
286
|
+
`${baseUrl}/_agent-native/onboarding/first-run/complete`,
|
|
287
|
+
{
|
|
288
|
+
method: "POST",
|
|
289
|
+
headers: { "content-type": "application/json", origin: baseUrl, cookie },
|
|
290
|
+
body: "{}",
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
if (!completed.ok) {
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Completing first-run onboarding failed for ${accountEmail} (${completed.status})`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
return cookie;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async function callAction(
|
|
302
|
+
cookie,
|
|
303
|
+
name,
|
|
304
|
+
payload,
|
|
305
|
+
{ attempt = 1, retryable = true } = {},
|
|
306
|
+
) {
|
|
307
|
+
const response = await fetch(`${baseUrl}/_agent-native/actions/${name}`, {
|
|
308
|
+
method: "POST",
|
|
309
|
+
headers: {
|
|
310
|
+
"content-type": "application/json",
|
|
311
|
+
origin: baseUrl,
|
|
312
|
+
cookie,
|
|
313
|
+
},
|
|
314
|
+
body: JSON.stringify(payload),
|
|
315
|
+
});
|
|
316
|
+
if (response.ok) return response.json();
|
|
317
|
+
const text = await response.text();
|
|
318
|
+
const retryableStatus = response.status === 429 || response.status >= 500;
|
|
319
|
+
if (retryable && retryableStatus && attempt < 5) {
|
|
320
|
+
await new Promise((done) => setTimeout(done, 500 * 2 ** attempt));
|
|
321
|
+
return callAction(cookie, name, payload, {
|
|
322
|
+
attempt: attempt + 1,
|
|
323
|
+
retryable,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
throw new Error(`${name} failed (${response.status}): ${text.slice(0, 400)}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function readAction(cookie, name, params) {
|
|
330
|
+
const url = new URL(`${baseUrl}/_agent-native/actions/${name}`);
|
|
331
|
+
for (const [key, value] of Object.entries(params)) {
|
|
332
|
+
url.searchParams.set(
|
|
333
|
+
key,
|
|
334
|
+
typeof value === "string" ? value : JSON.stringify(value),
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
const response = await fetch(url, { headers: { cookie, origin: baseUrl } });
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`${name} failed (${response.status}): ${(await response.text()).slice(0, 400)}`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return response.json();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function runPool(items, label, worker) {
|
|
347
|
+
let next = 0;
|
|
348
|
+
let completed = 0;
|
|
349
|
+
const started = Date.now();
|
|
350
|
+
async function lane() {
|
|
351
|
+
while (next < items.length) {
|
|
352
|
+
const item = items[next];
|
|
353
|
+
next += 1;
|
|
354
|
+
await worker(item);
|
|
355
|
+
completed += 1;
|
|
356
|
+
if (completed % 50 === 0 || completed === items.length) {
|
|
357
|
+
const rate = completed / ((Date.now() - started) / 1000);
|
|
358
|
+
console.log(
|
|
359
|
+
`[perf database] ${label} ${completed}/${items.length} (${rate.toFixed(1)}/s)`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
await Promise.all(Array.from({ length: concurrency }, lane));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const manifest = loadManifest();
|
|
368
|
+
const cookie = await signIn(email, password);
|
|
369
|
+
|
|
370
|
+
if (!manifest.databaseId) {
|
|
371
|
+
const spaces = await readAction(cookie, "list-content-spaces", {});
|
|
372
|
+
const personal =
|
|
373
|
+
spaces.spaces.find((space) => space.kind === "personal") ??
|
|
374
|
+
spaces.spaces[0];
|
|
375
|
+
const created = await callAction(cookie, "create-content-database", {
|
|
376
|
+
spaceId: personal.id,
|
|
377
|
+
title,
|
|
378
|
+
idempotencyKey: `perf-database-${title}`,
|
|
379
|
+
});
|
|
380
|
+
const primary = created.properties.find(
|
|
381
|
+
(property) =>
|
|
382
|
+
property.definition.type === "blocks" &&
|
|
383
|
+
property.definition.options?.blocks?.primary === true,
|
|
384
|
+
);
|
|
385
|
+
if (!primary) throw new Error("New collection has no primary Blocks field");
|
|
386
|
+
manifest.spaceId = personal.id;
|
|
387
|
+
manifest.databaseId = created.database.id;
|
|
388
|
+
manifest.databaseDocumentId = created.database.documentId;
|
|
389
|
+
manifest.primaryBlocksPropertyId = primary.definition.id;
|
|
390
|
+
saveManifest(manifest);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const databaseId = manifest.databaseId;
|
|
394
|
+
const databaseDocumentId = manifest.databaseDocumentId;
|
|
395
|
+
|
|
396
|
+
const pendingProperties = PROPERTY_PLAN.filter(
|
|
397
|
+
(property) => !manifest.properties[property.key],
|
|
398
|
+
);
|
|
399
|
+
// A run stopped after a property was created but before the manifest saved
|
|
400
|
+
// it leaves that property in the collection; reuse it instead of adding a
|
|
401
|
+
// second one with the same name.
|
|
402
|
+
const existingProperties =
|
|
403
|
+
pendingProperties.length > 0
|
|
404
|
+
? (
|
|
405
|
+
await readAction(cookie, "get-content-database", {
|
|
406
|
+
databaseId,
|
|
407
|
+
limit: "0",
|
|
408
|
+
})
|
|
409
|
+
).properties
|
|
410
|
+
: [];
|
|
411
|
+
for (const property of pendingProperties) {
|
|
412
|
+
const existing = existingProperties.find(
|
|
413
|
+
(candidate) =>
|
|
414
|
+
candidate.definition.name === property.name &&
|
|
415
|
+
candidate.definition.type === property.type,
|
|
416
|
+
);
|
|
417
|
+
if (existing) {
|
|
418
|
+
manifest.properties[property.key] = existing.definition.id;
|
|
419
|
+
saveManifest(manifest);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
// A failed response can arrive after this non-idempotent create commits;
|
|
423
|
+
// stop here so the next run can recover it from the schema read above.
|
|
424
|
+
const response = await callAction(
|
|
425
|
+
cookie,
|
|
426
|
+
"configure-document-property",
|
|
427
|
+
{
|
|
428
|
+
documentId: databaseDocumentId,
|
|
429
|
+
databaseId,
|
|
430
|
+
name: property.name,
|
|
431
|
+
type: property.type,
|
|
432
|
+
...(property.options ? { options: property.options } : {}),
|
|
433
|
+
...(property.relation ? { options: { relation: { databaseId } } } : {}),
|
|
434
|
+
},
|
|
435
|
+
{ retryable: false },
|
|
436
|
+
);
|
|
437
|
+
const created = response.properties.find(
|
|
438
|
+
(candidate) => candidate.definition.name === property.name,
|
|
439
|
+
);
|
|
440
|
+
if (!created) throw new Error(`Property "${property.name}" was not created`);
|
|
441
|
+
manifest.properties[property.key] = created.definition.id;
|
|
442
|
+
saveManifest(manifest);
|
|
443
|
+
}
|
|
444
|
+
const propertyIds = manifest.properties;
|
|
445
|
+
|
|
446
|
+
const contract = (
|
|
447
|
+
await readAction(cookie, "get-content-database", { databaseId, limit: "0" })
|
|
448
|
+
).mutationContract;
|
|
449
|
+
if (!contract) throw new Error("Collection has no mutation contract");
|
|
450
|
+
|
|
451
|
+
const createdRows = new Map(manifest.rows.map((row) => [row.index, row]));
|
|
452
|
+
const pendingRows = Array.from(
|
|
453
|
+
{ length: rowCount },
|
|
454
|
+
(_, index) => index,
|
|
455
|
+
).filter((index) => !createdRows.has(index));
|
|
456
|
+
// Values come from the deterministic generator, so draw them in row order
|
|
457
|
+
// before the pool interleaves requests.
|
|
458
|
+
const plannedRows = new Map(
|
|
459
|
+
Array.from({ length: rowCount }, (_, index) => [
|
|
460
|
+
index,
|
|
461
|
+
{
|
|
462
|
+
title: `${sentence(3, 7).replace(/\.$/, "")} #${index + 1}`,
|
|
463
|
+
values: rowValues(index, propertyIds),
|
|
464
|
+
},
|
|
465
|
+
]),
|
|
466
|
+
);
|
|
467
|
+
await runPool(pendingRows, "rows", async (index) => {
|
|
468
|
+
const result = await callAction(cookie, "add-database-item", {
|
|
469
|
+
target: contract.target,
|
|
470
|
+
expectedSchemaRevision: contract.schemaRevision,
|
|
471
|
+
idempotencyKey: `perf-row-${databaseId}-${index}`,
|
|
472
|
+
title: plannedRows.get(index).title,
|
|
473
|
+
propertyValues: plannedRows.get(index).values,
|
|
474
|
+
});
|
|
475
|
+
const row = {
|
|
476
|
+
index,
|
|
477
|
+
itemId: result.receipt.row.itemId,
|
|
478
|
+
documentId: result.receipt.row.documentId,
|
|
479
|
+
};
|
|
480
|
+
createdRows.set(index, row);
|
|
481
|
+
manifest.rows.push(row);
|
|
482
|
+
if (manifest.rows.length % 25 === 0) saveManifest(manifest);
|
|
483
|
+
});
|
|
484
|
+
manifest.rows.sort((left, right) => left.index - right.index);
|
|
485
|
+
saveManifest(manifest);
|
|
486
|
+
|
|
487
|
+
const plannedBodies = new Map(
|
|
488
|
+
manifest.rows.map((row) => [row.index, rowBody()]),
|
|
489
|
+
);
|
|
490
|
+
const doneBodies = new Set(manifest.bodies);
|
|
491
|
+
await runPool(
|
|
492
|
+
manifest.rows.filter((row) => !doneBodies.has(row.index)),
|
|
493
|
+
"bodies",
|
|
494
|
+
async (row) => {
|
|
495
|
+
await callAction(cookie, "set-document-property", {
|
|
496
|
+
documentId: row.documentId,
|
|
497
|
+
databaseId,
|
|
498
|
+
propertyId: manifest.primaryBlocksPropertyId,
|
|
499
|
+
value: plannedBodies.get(row.index),
|
|
500
|
+
});
|
|
501
|
+
manifest.bodies.push(row.index);
|
|
502
|
+
if (manifest.bodies.length % 25 === 0) saveManifest(manifest);
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
saveManifest(manifest);
|
|
506
|
+
|
|
507
|
+
// About a third of rows are blocked by one or two earlier rows.
|
|
508
|
+
const doneRelations = new Set(manifest.relations);
|
|
509
|
+
const relationRows = manifest.rows.filter(
|
|
510
|
+
(row) => row.index > 2 && row.index % 3 === 1,
|
|
511
|
+
);
|
|
512
|
+
const plannedRelations = new Map(
|
|
513
|
+
relationRows.map((row) => [
|
|
514
|
+
row.index,
|
|
515
|
+
Array.from(
|
|
516
|
+
{ length: 1 + (row.index % 2) },
|
|
517
|
+
() => manifest.rows[Math.floor(random() * row.index)].documentId,
|
|
518
|
+
),
|
|
519
|
+
]),
|
|
520
|
+
);
|
|
521
|
+
await runPool(
|
|
522
|
+
relationRows.filter((row) => !doneRelations.has(row.index)),
|
|
523
|
+
"relations",
|
|
524
|
+
async (row) => {
|
|
525
|
+
await callAction(cookie, "set-document-property", {
|
|
526
|
+
documentId: row.documentId,
|
|
527
|
+
databaseId,
|
|
528
|
+
propertyId: propertyIds.blockedBy,
|
|
529
|
+
value: [...new Set(plannedRelations.get(row.index))],
|
|
530
|
+
});
|
|
531
|
+
manifest.relations.push(row.index);
|
|
532
|
+
if (manifest.relations.length % 25 === 0) saveManifest(manifest);
|
|
533
|
+
},
|
|
534
|
+
);
|
|
535
|
+
saveManifest(manifest);
|
|
536
|
+
|
|
537
|
+
if (!manifest.views) {
|
|
538
|
+
const rankSort = [{ key: propertyIds.rank, label: "Rank", direction: "asc" }];
|
|
539
|
+
const areaView = (id, name, areaId) => ({
|
|
540
|
+
id,
|
|
541
|
+
name,
|
|
542
|
+
type: "table",
|
|
543
|
+
sorts: rankSort,
|
|
544
|
+
filters: [
|
|
545
|
+
{
|
|
546
|
+
key: propertyIds.area,
|
|
547
|
+
label: "Area",
|
|
548
|
+
operator: "equals",
|
|
549
|
+
value: JSON.stringify([areaId]),
|
|
550
|
+
},
|
|
551
|
+
],
|
|
552
|
+
filterMode: "and",
|
|
553
|
+
});
|
|
554
|
+
await callAction(cookie, "update-content-database-view", {
|
|
555
|
+
databaseId,
|
|
556
|
+
viewConfig: {
|
|
557
|
+
activeViewId: "all-up",
|
|
558
|
+
views: [
|
|
559
|
+
{
|
|
560
|
+
id: "all-up",
|
|
561
|
+
name: "All Up",
|
|
562
|
+
type: "table",
|
|
563
|
+
sorts: rankSort,
|
|
564
|
+
filters: [
|
|
565
|
+
{
|
|
566
|
+
key: propertyIds.status,
|
|
567
|
+
label: "Status",
|
|
568
|
+
operator: "does_not_equal",
|
|
569
|
+
value: JSON.stringify(["done"]),
|
|
570
|
+
},
|
|
571
|
+
],
|
|
572
|
+
filterMode: "and",
|
|
573
|
+
wrapCells: true,
|
|
574
|
+
},
|
|
575
|
+
areaView("personal", "Personal", "personal"),
|
|
576
|
+
areaView("builder", "Builder", "builder"),
|
|
577
|
+
areaView("parasail", "Parasail", "parasail"),
|
|
578
|
+
],
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
manifest.views = true;
|
|
582
|
+
saveManifest(manifest);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
console.log(
|
|
586
|
+
JSON.stringify(
|
|
587
|
+
{
|
|
588
|
+
manifest: manifestPath,
|
|
589
|
+
databaseId,
|
|
590
|
+
databaseDocumentId,
|
|
591
|
+
path: `/page/${databaseDocumentId}`,
|
|
592
|
+
rows: manifest.rows.length,
|
|
593
|
+
bodies: manifest.bodies.length,
|
|
594
|
+
relations: manifest.relations.length,
|
|
595
|
+
properties: Object.keys(propertyIds).length + 1,
|
|
596
|
+
},
|
|
597
|
+
null,
|
|
598
|
+
2,
|
|
599
|
+
),
|
|
600
|
+
);
|
|
@@ -17,8 +17,9 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
17
17
|
// links to --click-path and time the new document).
|
|
18
18
|
//
|
|
19
19
|
// Layout stability: --stability follows every element marked
|
|
20
|
-
// `data-startup-anchor` (the title, the body,
|
|
21
|
-
//
|
|
20
|
+
// `data-startup-anchor` (the title, the body, a collection's tabs row and table,
|
|
21
|
+
// the sidebar's Search row, section headers, and first Files row, on
|
|
22
|
+
// placeholders and real elements alike) on
|
|
22
23
|
// every animation frame from the first frame it appears, and reports how far
|
|
23
24
|
// each moved. A run fails when any anchor moves more than --max-shift pixels
|
|
24
25
|
// (default 2). The script exits 1 when any run fails, and 2 when a run found
|
|
@@ -189,9 +190,21 @@ function installProbe(options) {
|
|
|
189
190
|
}
|
|
190
191
|
// Builds deployed before the app's own startup marks still get observed
|
|
191
192
|
// milestones: a newly mounted editor with text, and ten sidebar page links.
|
|
193
|
+
// The open page's own Files row shows when its expanded ancestors have
|
|
194
|
+
// loaded, which the first root row does not.
|
|
192
195
|
const seenEditors = new WeakSet();
|
|
193
196
|
let sidebarSeen = false;
|
|
197
|
+
let activeRowSeen = false;
|
|
194
198
|
const observe = () => {
|
|
199
|
+
if (
|
|
200
|
+
!activeRowSeen &&
|
|
201
|
+
document.querySelector(
|
|
202
|
+
'[data-paged-files-navigation] [aria-current="page"]',
|
|
203
|
+
)
|
|
204
|
+
) {
|
|
205
|
+
activeRowSeen = true;
|
|
206
|
+
performance.mark("trace:sidebar-active-row");
|
|
207
|
+
}
|
|
195
208
|
for (const editor of document.querySelectorAll(".ProseMirror")) {
|
|
196
209
|
if (seenEditors.has(editor) || !editor.textContent.trim()) continue;
|
|
197
210
|
seenEditors.add(editor);
|
|
@@ -268,6 +281,7 @@ function collect([since, documentId]) {
|
|
|
268
281
|
sidebarPainted: at(mark("sidebar-files-rows-dom:painted")),
|
|
269
282
|
sidebarDom: at(mark("sidebar-files-rows-dom")),
|
|
270
283
|
sidebarObserved: at(mark("trace:sidebar-dom")),
|
|
284
|
+
sidebarActiveRow: at(mark("trace:sidebar-active-row")),
|
|
271
285
|
editable: at(mark("content-editable")),
|
|
272
286
|
anchors: Object.fromEntries(
|
|
273
287
|
Object.entries(trace.anchors).map(([name, anchor]) => [
|
|
@@ -332,6 +346,7 @@ function summarizeRun(result) {
|
|
|
332
346
|
result.sidebarDom,
|
|
333
347
|
result.sidebarObserved,
|
|
334
348
|
),
|
|
349
|
+
sidebarActiveRow: result.sidebarActiveRow,
|
|
335
350
|
editable: result.editable,
|
|
336
351
|
frameworkRequests: requests.length,
|
|
337
352
|
sessionRequests: requests.filter(
|
|
@@ -401,6 +416,7 @@ async function waitForBody(page, since, documentId) {
|
|
|
401
416
|
while (Date.now() - started < timeoutMs) {
|
|
402
417
|
// A client-side redirect (for example `/` to `/home`) replaces the
|
|
403
418
|
// document mid-poll; keep polling the new one.
|
|
419
|
+
// A collection page has no body; it is ready once its rows are drawn.
|
|
404
420
|
const done = await page
|
|
405
421
|
.evaluate(
|
|
406
422
|
([s, id]) =>
|
|
@@ -415,7 +431,10 @@ async function waitForBody(page, since, documentId) {
|
|
|
415
431
|
(entry) =>
|
|
416
432
|
entry.startTime >= s &&
|
|
417
433
|
(!id || entry.detail?.documentId === id),
|
|
418
|
-
)
|
|
434
|
+
) ||
|
|
435
|
+
(s === 0 &&
|
|
436
|
+
!!document.documentElement.dataset
|
|
437
|
+
.contentDatabaseRowsVisibleDocumentId),
|
|
419
438
|
[since, documentId],
|
|
420
439
|
)
|
|
421
440
|
.catch(() => false);
|
|
@@ -570,6 +589,7 @@ for (let run = 0; run < runs; run += 1) {
|
|
|
570
589
|
"sidebarDom",
|
|
571
590
|
"bodyObserved",
|
|
572
591
|
"sidebarObserved",
|
|
592
|
+
"sidebarActiveRow",
|
|
573
593
|
"editable",
|
|
574
594
|
]) {
|
|
575
595
|
if (result[key] != null) result[key] += offset;
|
|
@@ -602,6 +622,7 @@ const report = {
|
|
|
602
622
|
p50: {
|
|
603
623
|
bodyVisible: percentile(metric("bodyVisible"), 50),
|
|
604
624
|
sidebarUsable: percentile(metric("sidebarUsable"), 50),
|
|
625
|
+
sidebarActiveRow: percentile(metric("sidebarActiveRow"), 50),
|
|
605
626
|
editable: percentile(metric("editable"), 50),
|
|
606
627
|
frameworkRequests: percentile(metric("frameworkRequests"), 50),
|
|
607
628
|
documentAfterSession: percentile(metric("documentAfterSession"), 50),
|
|
@@ -609,6 +630,7 @@ const report = {
|
|
|
609
630
|
p90: {
|
|
610
631
|
bodyVisible: percentile(metric("bodyVisible"), 90),
|
|
611
632
|
sidebarUsable: percentile(metric("sidebarUsable"), 90),
|
|
633
|
+
sidebarActiveRow: percentile(metric("sidebarActiveRow"), 90),
|
|
612
634
|
editable: percentile(metric("editable"), 90),
|
|
613
635
|
documentAfterSession: percentile(metric("documentAfterSession"), 90),
|
|
614
636
|
},
|
|
@@ -37,7 +37,7 @@ export const documentSearchIndex = registerSearchableResource({
|
|
|
37
37
|
type: "document",
|
|
38
38
|
table: schema.documents,
|
|
39
39
|
idColumn: schema.documents.id,
|
|
40
|
-
version:
|
|
40
|
+
version: 2,
|
|
41
41
|
load: async (ids) => {
|
|
42
42
|
// guard:allow-unscoped — the indexer projects changed documents by id with no caller; search-documents applies access live when reading the index.
|
|
43
43
|
const rows = await getDb()
|