zz-meridian 0.14.1 → 0.15.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/README.md +5 -4
- package/dist/adopt.js +22 -6
- package/dist/migrations.js +27 -1
- package/dist/session.js +25 -0
- package/package.json +1 -1
- package/payload/CHANGELOG.md +94 -0
- package/payload/CONTRIBUTING.md +17 -6
- package/payload/README.md +6 -3
- package/payload/app/(dashboard)/(overview)/README.md +13 -9
- package/payload/app/(dashboard)/(overview)/loading.tsx +10 -2
- package/payload/app/(dashboard)/(overview)/page.tsx +3 -3
- package/payload/app/(dashboard)/analytics/README.md +18 -13
- package/payload/app/(dashboard)/analytics/loading.tsx +3 -2
- package/payload/app/(dashboard)/analytics/page.tsx +3 -3
- package/payload/app/(dashboard)/customers/README.md +8 -6
- package/payload/app/(dashboard)/customers/loading.tsx +3 -3
- package/payload/app/(dashboard)/customers/page.tsx +7 -7
- package/payload/app/(dashboard)/error.tsx +1 -1
- package/payload/app/(dashboard)/health/README.md +8 -7
- package/payload/app/(dashboard)/health/loading.tsx +3 -3
- package/payload/app/(dashboard)/health/page.tsx +6 -7
- package/payload/app/(dashboard)/keys/README.md +4 -2
- package/payload/app/(dashboard)/keys/loading.tsx +3 -2
- package/payload/app/(dashboard)/layout.tsx +8 -5
- package/payload/app/(dashboard)/members/README.md +7 -5
- package/payload/app/(dashboard)/members/loading.tsx +3 -2
- package/payload/app/(dashboard)/members/page.tsx +18 -3
- package/payload/app/(dashboard)/operations-tickets/README.md +61 -0
- package/payload/app/(dashboard)/operations-tickets/actions.ts +35 -0
- package/payload/app/(dashboard)/operations-tickets/loading.tsx +11 -0
- package/payload/app/(dashboard)/operations-tickets/page.tsx +44 -0
- package/payload/app/(dashboard)/releases/README.md +56 -0
- package/payload/app/(dashboard)/releases/loading.tsx +11 -0
- package/payload/app/(dashboard)/releases/page.tsx +43 -0
- package/payload/app/(dashboard)/requests/README.md +4 -2
- package/payload/app/(dashboard)/requests/[id]/loading.tsx +1 -1
- package/payload/app/(dashboard)/requests/loading.tsx +3 -3
- package/payload/app/(dashboard)/requests/page.tsx +4 -4
- package/payload/app/(dashboard)/settings/README.md +9 -13
- package/payload/app/(dashboard)/settings/actions.ts +4 -7
- package/payload/app/(dashboard)/settings/loading.tsx +2 -2
- package/payload/app/(dashboard)/settings/page.tsx +4 -7
- package/payload/app/(dashboard)/tests/README.md +56 -0
- package/payload/app/(dashboard)/tests/loading.tsx +11 -0
- package/payload/app/(dashboard)/tests/page.tsx +43 -0
- package/payload/app/embed/health/README.md +6 -6
- package/payload/app/embed/health/page.tsx +2 -2
- package/payload/app/embed/health/view.tsx +1 -1
- package/payload/app/embed/overview/README.md +3 -3
- package/payload/app/embed/requests/view.tsx +2 -2
- package/payload/app/sign-in/README.md +10 -15
- package/payload/app/sign-in/gated-panel.tsx +5 -10
- package/payload/app/sign-in/panel.tsx +11 -68
- package/payload/app/system/(atlas)/[section]/[id]/page.tsx +3 -3
- package/payload/app/system/(atlas)/_article.tsx +12 -4
- package/payload/app/system/(atlas)/layout.tsx +1 -1
- package/payload/app/system/(atlas)/page.tsx +4 -4
- package/payload/app/system/(atlas)/products/[id]/page.tsx +9 -0
- package/payload/app/system/preview/[section]/[card]/keys.ts +2 -0
- package/payload/app/system/preview/[section]/[card]/page.tsx +7 -1
- package/payload/decisions/0011-one-context-two-agents.md +1 -1
- package/payload/decisions/0012-roles-and-feature-access.md +1 -1
- package/payload/decisions/0014-the-atlas-is-held-to-the-floors.md +52 -0
- package/payload/docs/assistant.md +1 -1
- package/payload/docs/atlas-scenario-coverage.md +329 -0
- package/payload/docs/benchmark.md +7 -7
- package/payload/docs/distribution.md +4 -4
- package/payload/docs/product-workspaces.md +107 -0
- package/payload/docs/start-a-dashboard.md +11 -7
- package/payload/docs/surfaces.md +22 -7
- package/payload/docs/voice.md +1 -1
- package/payload/journeys/workspaces.ts +527 -0
- package/payload/package.json +2 -2
- package/payload/scripts/assistant.ts +11 -3
- package/payload/scripts/audit.ts +137 -25
- package/payload/scripts/brand.ts +4 -1
- package/payload/scripts/check.ts +2 -2
- package/payload/scripts/interactions.ts +3 -1
- package/payload/scripts/keyboard.ts +18 -28
- package/payload/scripts/lib/atlas.ts +44 -0
- package/payload/scripts/lib/chrome.ts +56 -15
- package/payload/scripts/lib/routes.ts +10 -0
- package/payload/scripts/lib/sample.ts +6 -3
- package/payload/scripts/lib/settle.ts +39 -0
- package/payload/scripts/live.ts +2 -2
- package/payload/scripts/navigate.ts +28 -15
- package/payload/scripts/perf.ts +9 -7
- package/payload/scripts/rail.ts +29 -11
- package/payload/scripts/registry.ts +10 -6
- package/payload/scripts/route-policy.ts +25 -3
- package/payload/scripts/shot.ts +3 -0
- package/payload/scripts/tokens.ts +3 -1
- package/payload/scripts/verify.baseline.json +26 -22
- package/payload/scripts/verify.config.ts +18 -12
- package/payload/scripts/verify.ts +2 -2
- package/payload/scripts/vitals.ts +24 -8
- package/payload/skills/zz-meridian/SKILL.md +10 -5
- package/payload/skills/zz-meridian/references/cache.md +43 -11
- package/payload/skills/zz-meridian/references/customize.md +92 -20
- package/payload/skills/zz-meridian/references/existing-project.md +46 -8
- package/payload/skills/zz-meridian/references/live.md +3 -0
- package/payload/skills/zz-meridian/references/update.md +3 -1
- package/payload/skills/zz-meridian/references/validation.md +16 -5
- package/payload/skills/zz-meridian/references/voice.md +1 -1
- package/payload/src/app.config.ts +29 -15
- package/payload/src/components/base/providers.tsx +8 -3
- package/payload/src/components/base/shell/README.md +11 -2
- package/payload/src/components/base/shell/index.tsx +159 -40
- package/payload/src/components/base/shell/leave-question.tsx +27 -0
- package/payload/src/components/base/shell/preview.tsx +20 -1
- package/payload/src/components/charts/uptime-bars/README.md +2 -2
- package/payload/src/components/charts/uptime-bars/index.tsx +1 -1
- package/payload/src/components/patterns/ask-about/README.md +1 -1
- package/payload/src/components/patterns/assistant/README.md +1 -1
- package/payload/src/components/patterns/command-palette/README.md +2 -2
- package/payload/src/components/patterns/command-palette/index.tsx +4 -2
- package/payload/src/components/patterns/data-table/README.md +3 -3
- package/payload/src/components/patterns/data-table/index.tsx +6 -3
- package/payload/src/components/patterns/data-table/use-query-state.ts +4 -2
- package/payload/src/components/patterns/data-table/use-search-draft.ts +28 -0
- package/payload/src/components/patterns/form-section/README.md +1 -1
- package/payload/src/components/patterns/form-section/index.tsx +4 -1
- package/payload/src/components/patterns/incident-card/README.md +5 -5
- package/payload/src/components/patterns/metric-tile/README.md +1 -1
- package/payload/src/components/patterns/metric-tile/index.tsx +3 -2
- package/payload/src/components/patterns/page-loading/README.md +71 -0
- package/payload/{app/(dashboard)/_loading.tsx → src/components/patterns/page-loading/index.tsx} +3 -3
- package/payload/src/components/patterns/page-loading/preview.tsx +28 -0
- package/payload/src/components/patterns/prose/README.md +3 -0
- package/payload/src/components/patterns/prose/index.tsx +3 -2
- package/payload/src/components/patterns/rail/README.md +5 -2
- package/payload/src/components/patterns/rail/index.tsx +20 -5
- package/payload/src/components/patterns/rail/preview.tsx +25 -63
- package/payload/src/components/patterns/shell-tools/README.md +2 -2
- package/payload/src/components/patterns/status-list/README.md +4 -4
- package/payload/src/components/templates/records/README.md +57 -0
- package/payload/src/components/templates/records/index.tsx +33 -0
- package/payload/src/components/templates/records/preview.tsx +76 -0
- package/payload/src/components/ui/breadcrumb/README.md +1 -1
- package/payload/src/components/ui/breadcrumb/index.tsx +5 -4
- package/payload/src/components/ui/copy-field/README.md +1 -1
- package/payload/src/components/ui/copy-field/index.tsx +2 -1
- package/payload/src/components/ui/dialog/preview.tsx +1 -1
- package/payload/src/components/ui/key-value/README.md +2 -2
- package/payload/src/components/ui/key-value/index.tsx +21 -9
- package/payload/src/components/ui/key-value/preview.tsx +1 -1
- package/payload/src/components/ui/search-input/README.md +1 -1
- package/payload/src/components/ui/tabs/preview.tsx +1 -1
- package/payload/src/components/ui/toast/index.tsx +5 -2
- package/payload/src/data/README.md +11 -3
- package/payload/src/data/access.ts +17 -6
- package/payload/src/data/alerts.ts +113 -0
- package/payload/src/data/analytics.ts +15 -9
- package/payload/src/data/collections.ts +105 -12
- package/payload/src/data/engineering.ts +36 -0
- package/payload/src/data/features.ts +10 -6
- package/payload/src/data/main.ts +36 -0
- package/payload/src/data/member-mutations.ts +8 -28
- package/payload/src/data/metrics.ts +5 -4
- package/payload/src/data/read.ts +18 -0
- package/payload/src/data/roles.ts +4 -4
- package/payload/src/data/sample.ts +3 -3
- package/payload/src/data/ticket-mutations.ts +142 -0
- package/payload/src/data/tickets.ts +33 -0
- package/payload/src/data/verification.ts +58 -0
- package/payload/src/data/write-boundary.ts +38 -0
- package/payload/src/lib/assistant/prompt.ts +1 -1
- package/payload/src/lib/assistant/respond.ts +2 -1
- package/payload/src/lib/collection.ts +27 -21
- package/payload/src/lib/live.ts +11 -1
- package/payload/src/lib/period.ts +3 -2
- package/payload/src/styles/motion.css +2 -2
- package/payload/src/styles/tokens.css +3 -1
- package/payload/src/system/atlas-shell.tsx +43 -26
- package/payload/src/system/card-stage.tsx +11 -5
- package/payload/src/system/content.ts +39 -18
- package/payload/src/system/fixtures/sample-engineering.ts +141 -0
- package/payload/src/system/fixtures/sample-ops.ts +9 -24
- package/payload/src/system/fixtures/sample.ts +1 -1
- package/payload/src/system/page-stage.tsx +1 -1
- package/payload/src/system/preview-loaders.ts +2 -0
- package/payload/src/system/registry.ts +2 -0
- package/payload/src/system/strata.tsx +12 -4
- package/payload/src/system/token-view.tsx +1 -1
- package/payload/src/views/analytics-context.ts +15 -6
- package/payload/src/views/analytics.tsx +43 -30
- package/payload/src/views/console-chrome.tsx +37 -19
- package/payload/src/views/customers-context.ts +10 -6
- package/payload/src/views/customers.tsx +17 -66
- package/payload/src/views/health-context.ts +6 -4
- package/payload/src/views/health.tsx +10 -19
- package/payload/src/views/keys.tsx +4 -3
- package/payload/src/views/main-summary.ts +46 -0
- package/payload/src/views/members.tsx +9 -8
- package/payload/src/views/no-access.tsx +26 -4
- package/payload/src/views/overview-context.ts +54 -4
- package/payload/src/views/overview.tsx +91 -8
- package/payload/src/views/releases-context.ts +100 -0
- package/payload/src/views/releases.tsx +110 -0
- package/payload/src/views/request.tsx +8 -8
- package/payload/src/views/requests.tsx +12 -27
- package/payload/src/views/sample-footer.tsx +1 -1
- package/payload/src/views/settings.tsx +19 -124
- package/payload/src/views/shell-alerts.tsx +27 -0
- package/payload/src/views/tests-context.ts +79 -0
- package/payload/src/views/tests.tsx +115 -0
- package/payload/src/views/tickets-context.ts +87 -0
- package/payload/src/views/tickets.tsx +239 -0
- package/payload/src/views/tools.ts +57 -11
- package/payload/src/views/workspace-chrome.tsx +79 -0
- package/payload/src/views/workspaces.ts +27 -0
- package/payload/tests/agent-page-spec.test.ts +21 -0
- package/payload/tests/analytics-figures.test.ts +75 -0
- package/payload/tests/analytics-summary.test.tsx +80 -0
- package/payload/tests/assistant-limits.test.ts +46 -0
- package/payload/tests/assistant-settings.test.tsx +2 -2
- package/payload/tests/atlas-layers.test.ts +88 -0
- package/payload/tests/atlas-product-slice.test.tsx +40 -0
- package/payload/tests/atlas-template-contracts.test.tsx +147 -0
- package/payload/tests/bypasses.test.ts +2 -2
- package/payload/tests/card-stage-hydration.test.tsx +84 -0
- package/payload/tests/chrome-navigation.test.tsx +100 -0
- package/payload/tests/counts-and-lists.test.tsx +156 -0
- package/payload/tests/customer-figures.test.ts +35 -0
- package/payload/tests/empty-views.test.tsx +4 -2
- package/payload/tests/engineering-records.test.ts +223 -0
- package/payload/tests/engineering-workflows.test.ts +114 -0
- package/payload/tests/feature-access.test.ts +12 -8
- package/payload/tests/honest-controls.test.tsx +59 -0
- package/payload/tests/key-value.test.tsx +47 -0
- package/payload/tests/leave-then-unload.test.tsx +62 -0
- package/payload/tests/live-client.test.tsx +41 -0
- package/payload/tests/loading-routes.test.tsx +5 -2
- package/payload/tests/member-duplicate.test.ts +32 -0
- package/payload/tests/members-optimistic.test.tsx +27 -2
- package/payload/tests/navigate-start.test.ts +39 -0
- package/payload/tests/no-access.test.tsx +23 -1
- package/payload/tests/page-loading.test.tsx +75 -0
- package/payload/tests/prose-memo.test.tsx +10 -0
- package/payload/tests/providers-hydration.test.tsx +75 -0
- package/payload/tests/query-rows.test.ts +30 -0
- package/payload/tests/query-state-address.test.tsx +32 -0
- package/payload/tests/rail-marker.test.tsx +46 -0
- package/payload/tests/records-consumers.test.tsx +101 -0
- package/payload/tests/records-template.test.tsx +38 -0
- package/payload/tests/release-phone-cell.test.ts +30 -0
- package/payload/tests/release-wording.test.ts +24 -0
- package/payload/tests/roles.test.ts +6 -4
- package/payload/tests/route-policy.test.ts +6 -1
- package/payload/tests/search-draft.test.tsx +37 -0
- package/payload/tests/settings-access.test.tsx +8 -10
- package/payload/tests/settings-agents.test.tsx +58 -0
- package/payload/tests/settings-save.test.tsx +79 -0
- package/payload/tests/setup.ts +5 -0
- package/payload/tests/shared-context.test.tsx +15 -6
- package/payload/tests/shell-address.test.tsx +59 -0
- package/payload/tests/shell-alerts.test.ts +87 -0
- package/payload/tests/shell-first-arrival.test.tsx +51 -0
- package/payload/tests/sign-in-honest.test.tsx +36 -0
- package/payload/tests/template-consumers.test.tsx +94 -0
- package/payload/tests/ticket-mutations.test.ts +72 -0
- package/payload/tests/ticket-owner.test.ts +35 -0
- package/payload/tests/tickets-view.test.tsx +87 -0
- package/payload/tests/toast-announce.test.tsx +15 -0
- package/payload/tests/truncate.test.tsx +40 -0
- package/payload/tests/unsaved-leave.test.tsx +160 -0
- package/payload/tests/verify-personas.test.ts +3 -3
- package/payload/tests/verify-sample-surfaces.test.ts +5 -0
- package/payload/tests/view-tool-needs.test.ts +1 -1
- package/payload/tests/workspace-access.test.ts +72 -0
- package/payload/tests/workspace-chrome.test.tsx +134 -0
- package/payload/tests/workspace-data-paths.test.ts +138 -0
- package/payload/tests/workspace-figures.test.ts +111 -0
- package/payload/tests/workspace-menu-access.test.tsx +90 -0
- package/payload/tests/workspace-overview.test.tsx +153 -0
- package/payload/tests/workspaces.test.ts +87 -0
- package/payload/vitest.config.ts +5 -1
- package/payload/tests/chrome-cleanup.test.ts +0 -35
- package/payload/tests/settings-disconnect-undo.test.tsx +0 -28
package/README.md
CHANGED
|
@@ -35,7 +35,8 @@ npx zz-meridian@latest skill [--global] # install only the agent skill
|
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Brand flags: `--name "Acme Ops"`, `--hex '#2E6BE4'` (or `--accent indigo|cobalt|jade|graphite`), `--theme dark|light`,
|
|
38
|
-
`--workspace`, `--timezone`, `--currency`, `--user`, `--role`.
|
|
38
|
+
`--workspace`, `--timezone`, `--currency`, `--user`, `--role`. `--workspace` sets `app.workspace`, the environment named
|
|
39
|
+
in a page's kicker ("Production"); it does not touch the navigation workspaces.
|
|
39
40
|
|
|
40
41
|
**adopt** copies Meridian's tokens, styles, components, gates and scripts into `src/`, `tokens/` and `scripts/`;
|
|
41
42
|
merges the dependencies and scripts it needs into your `package.json`; replaces your global stylesheet (yours is kept
|
|
@@ -61,9 +62,9 @@ writes nothing; run it first.
|
|
|
61
62
|
for you to merge.
|
|
62
63
|
- It adds the dependencies and scripts this release needs to `package.json` and updates Meridian's managed block in
|
|
63
64
|
`AGENTS.md`; a version or script you chose yourself becomes a migration to resolve, never a silent change.
|
|
64
|
-
- It reports a migration for each interface the release changed where one of your own files still has the old shape
|
|
65
|
-
|
|
66
|
-
`references/update.md` says
|
|
65
|
+
- It reports a migration for each interface the release changed where one of your own files still has the old shape,
|
|
66
|
+
such as `cache-components-config` or `nav-needs`. `cli/src/migrations.ts` holds every one; resolve them as the skill's
|
|
67
|
+
`references/update.md` says.
|
|
67
68
|
- Everything it did and everything left to do is in `.meridian/update/<version>/MERGE.md`. Resolve the items, then run
|
|
68
69
|
the pinned `npx zz-meridian@<version> update --finalize`; `--resume` continues an interrupted run and `--abort` restores
|
|
69
70
|
what it changed. Only finalize records the new version. `--finalize --verify` validates with the project's default
|
package/dist/adopt.js
CHANGED
|
@@ -72,15 +72,24 @@ function routes(root, dir) {
|
|
|
72
72
|
return [...new Set(out)].filter((r) => /^\/[\w/.-]*$/.test(r)).sort((a, b) => (a === '/' ? -1 : b === '/' ? 1 : a.localeCompare(b)));
|
|
73
73
|
}
|
|
74
74
|
const label = (route) => (route === '/' ? 'Home' : route.split('/').pop().replace(/[-_]+/g, ' ').replace(/^./, (c) => c.toUpperCase()));
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
/**
|
|
76
|
+
* The configuration `adopt` writes: the template's own file with the project's pages as its navigation. Everything that
|
|
77
|
+
* belongs to the sample is left out, because the project does not have it: the feature table (so every item is `public`,
|
|
78
|
+
* the honest default, until the fourth step of "Decide who may do what" in the Start guide points each at a feature of its own), every icon but the one the
|
|
79
|
+
* items use, and the sample's workspaces with the field that names one. It throws when the template has no navigation
|
|
80
|
+
* block to replace, since a configuration that still listed the sample's pages would be wrong without saying so.
|
|
81
|
+
*/
|
|
82
|
+
export function projectConfig(template, pages) {
|
|
83
|
+
if (!/export const nav: NavGroup\[\] = \[[\s\S]*?\n\];/.test(template))
|
|
84
|
+
throw new Error('the template\'s src/app.config.ts has no `export const nav: NavGroup[] = [ … ];` block to replace');
|
|
77
85
|
const items = pages.map((r) => ` { href: ${JSON.stringify(r)}, label: ${JSON.stringify(label(r))}, icon: LayoutGrid, needs: 'public' },`).join('\n');
|
|
78
86
|
return template
|
|
79
87
|
.replace(/^import \{[\s\S]*?\} from 'lucide-react';/m, "import { LayoutGrid, type LucideIcon } from 'lucide-react';")
|
|
80
|
-
// The
|
|
81
|
-
//
|
|
82
|
-
// this project's `src/data/` need not have, and a nav item without a `needs` is a type error since 0.13.0.
|
|
88
|
+
// The template's own `FEATURES` import would name a file this project's `src/data/` need not have, and a nav item
|
|
89
|
+
// without a `needs` is a type error since 0.13.0.
|
|
83
90
|
.replace(/^import \{ FEATURES \} from '\.\/data\/features\.ts';\n/m, '')
|
|
91
|
+
.replace(/\/\*\*(?:(?!\*\/)[\s\S])*\*\/\nexport const workspaces = \{[\s\S]*?\n\} as const;\nexport type WorkspaceId = [^\n]*\n\n/, '')
|
|
92
|
+
.replace(' workspace?: WorkspaceId;', '')
|
|
84
93
|
.replace(/export const nav: NavGroup\[\] = \[[\s\S]*?\n\];/, `/** Written by zz-meridian adopt from the project's pages: give each its own icon and group them. */\nexport const nav: NavGroup[] = [\n {\n items: [\n${items}\n ],\n },\n];`);
|
|
85
94
|
}
|
|
86
95
|
function verifyConfig() {
|
|
@@ -168,7 +177,14 @@ https://github.com/zhixuan312/zz-meridian/blob/master/skills/zz-meridian/referen
|
|
|
168
177
|
const raw = payloadBytes(rel);
|
|
169
178
|
own(rel, TS.test(rel) ? exactImports(rel, raw.toString('utf8'), known) : raw);
|
|
170
179
|
}
|
|
171
|
-
|
|
180
|
+
let config;
|
|
181
|
+
try {
|
|
182
|
+
config = projectConfig(readPayload('src/app.config.ts'), routes(root, dir));
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
return fail(e.message);
|
|
186
|
+
}
|
|
187
|
+
own('src/app.config.ts', exactImports('src/app.config.ts', config, known));
|
|
172
188
|
own('scripts/verify.config.ts', verifyConfig());
|
|
173
189
|
// Meridian's scripts are ES modules; a project whose package.json is not "type": "module" gets that for scripts/ only.
|
|
174
190
|
if (pkg.type !== 'module')
|
package/dist/migrations.js
CHANGED
|
@@ -243,7 +243,15 @@ export const RELEASE_MIGRATIONS = [
|
|
|
243
243
|
since: '0.13.0',
|
|
244
244
|
applies: navItemsWithoutNeeds,
|
|
245
245
|
summary: 'Every item in the project\'s `nav` carries a `needs`, so the rail shows only the destinations a person may reach.',
|
|
246
|
-
instructions: 'Read "
|
|
246
|
+
instructions: 'Read "5. Decide who may do what" in the Start guide (docs/start-a-dashboard.md). For each item in `src/app.config.ts`\'s `nav`, add `needs: \'public\'` when everyone may open that page, or point it at its feature (`needs: FEATURES.<id>.needs`); the fourth of its four steps covers it, and `scripts/check.ts` fails the project\'s gate, naming the href, on an item without one. `adopt` writes `needs: \'public\'` for a project it sets up from now on.',
|
|
247
|
+
checks: ['gate'],
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: 'agent-reads-own-group',
|
|
251
|
+
since: '0.15.0',
|
|
252
|
+
applies: ownGroupPagesWithoutAgentReads,
|
|
253
|
+
summary: 'A page under the route group the project keeps its console in says what the agent reads.',
|
|
254
|
+
instructions: 'The gate asked this of pages under `app/(dashboard)/` only; from 0.15.0 it asks it under whatever route group sits at the top of the app folder. Read "A page spec" in references/customize.md. Under `## Agents` in each page\'s README.md listed here, add `### What the agent reads`: what the page\'s shared context tells each agent, with a real line of it, and what the MCP App and the console assistant do with it. A copy of the rule kept in `scripts/check.local.ts` for the project\'s own group can be deleted.',
|
|
247
255
|
checks: ['gate'],
|
|
248
256
|
},
|
|
249
257
|
];
|
|
@@ -267,6 +275,24 @@ function navItemsWithoutNeeds(root) {
|
|
|
267
275
|
const typeHasNeeds = /export type NavItem[^;]*\bneeds\s*:/.test(body);
|
|
268
276
|
return items.some((l) => !/\bneeds\s*:/.test(l)) || (declaredType && !typeHasNeeds) ? [rel] : null;
|
|
269
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* The page specifications under a route group of the project's own, at the top of its app folder, that lack the part the
|
|
280
|
+
* gate asks for there from 0.15.0. The template's `(dashboard)` is left out: the rule has covered it since 0.9.0.
|
|
281
|
+
*/
|
|
282
|
+
function ownGroupPagesWithoutAgentReads(root) {
|
|
283
|
+
const files = [];
|
|
284
|
+
for (const app of ['app', 'src/app']) {
|
|
285
|
+
let groups = [];
|
|
286
|
+
try {
|
|
287
|
+
groups = fs.readdirSync(path.join(root, app), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\(.+\)$/.test(e.name) && e.name !== '(dashboard)').map((e) => e.name);
|
|
288
|
+
}
|
|
289
|
+
catch { /* no such folder */ }
|
|
290
|
+
for (const g of groups)
|
|
291
|
+
walk(root, `${app}/${g}`, files, /^README\.md$/);
|
|
292
|
+
}
|
|
293
|
+
const hits = files.filter((rel) => !/^## Agents\n[\s\S]*?^### What the agent reads$/m.test(text(root, rel) ?? ''));
|
|
294
|
+
return hits.length > 0 ? hits : null;
|
|
295
|
+
}
|
|
270
296
|
/** The page specifications under the console and embed folders that lack the part the 0.9.0 check requires. */
|
|
271
297
|
function pagesWithoutAgentReads(root) {
|
|
272
298
|
const files = [];
|
package/dist/session.js
CHANGED
|
@@ -289,6 +289,29 @@ function install(ctx, session, j, pm) {
|
|
|
289
289
|
return { ms, failure: r.status === 0 ? null : `${pm} install exited with ${r.status}; see evidence/0-install.log` };
|
|
290
290
|
}
|
|
291
291
|
const isInstall = (v) => v.command.endsWith(' install');
|
|
292
|
+
/**
|
|
293
|
+
* The managed files that are not the release's bytes and that no keep entry explains, as lines to print; none when
|
|
294
|
+
* there are none. Nothing else reports them between releases, and the next update would meet each as an edit nobody
|
|
295
|
+
* recorded a reason for. A keep register that cannot be read explains nothing, and the gate already says why.
|
|
296
|
+
*/
|
|
297
|
+
function unrecordedEdits(root, manifest) {
|
|
298
|
+
let kept = new Set();
|
|
299
|
+
try {
|
|
300
|
+
kept = new Set(parseKeep(fs.readFileSync(path.join(root, '.meridian', 'keep.json'), 'utf8')).map((k) => k.path));
|
|
301
|
+
}
|
|
302
|
+
catch { /* no register, or one the gate reports */ }
|
|
303
|
+
const edited = Object.entries(manifest.files)
|
|
304
|
+
.filter(([p, hash]) => !kept.has(p) && (!fs.existsSync(path.join(root, p)) || sha256(fs.readFileSync(path.join(root, p))) !== hash))
|
|
305
|
+
.map(([p]) => p)
|
|
306
|
+
.sort();
|
|
307
|
+
if (edited.length === 0)
|
|
308
|
+
return [];
|
|
309
|
+
return [
|
|
310
|
+
`${edited.length} managed ${edited.length === 1 ? 'file differs' : 'files differ'} from the release and ${edited.length === 1 ? 'is' : 'are'} not in .meridian/keep.json:`,
|
|
311
|
+
...edited.map((p) => ` ${p}${fs.existsSync(path.join(root, p)) ? '' : ' (missing)'}`),
|
|
312
|
+
'The next update will ask about each. Restore a file to the release, or name it in .meridian/keep.json with the reason it differs.',
|
|
313
|
+
];
|
|
314
|
+
}
|
|
292
315
|
export function start(ctx, o) {
|
|
293
316
|
const { root, log } = ctx;
|
|
294
317
|
const refuse = (cause) => { log(`zz-meridian update: ${cause}`); return 1; };
|
|
@@ -310,6 +333,8 @@ export function start(ctx, o) {
|
|
|
310
333
|
// Already current is not a refusal: there is nothing to do, and the run that asked has its answer.
|
|
311
334
|
if (manifest.version === ctx.version) {
|
|
312
335
|
log(`zz-meridian update: already at ${ctx.version}; nothing to update`);
|
|
336
|
+
for (const line of unrecordedEdits(root, manifest))
|
|
337
|
+
log(line);
|
|
313
338
|
return 0;
|
|
314
339
|
}
|
|
315
340
|
if (!isNewer(ctx.version, manifest.version))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zz-meridian",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Bring ZZ Meridian, a dashboard design system, into a Next.js project, or start a new dashboard on it. Copies the files in; nothing depends on this package at runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/payload/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,100 @@
|
|
|
2
2
|
|
|
3
3
|
Every release of ZZ Meridian, newest first. Versions follow semver: a removed or renamed token, prop or card is major; a new card, token or variant is minor; a corrected value is a patch. Each entry says what breaks and what to do instead.
|
|
4
4
|
|
|
5
|
+
## [0.15.0] · 2026-10-13
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Records, the first page template.** A page template arranges patterns for a kind of page, at layer 4: `Records` puts an optional row of summary tiles over a table with its toolbar, and marks its root `data-template="records"`. Templates live in `src/components/templates/`, are listed in the Atlas under Pages as `template-<folder>` with a real preview, and import no data, view, system or config module, so an adopted project receives them like any component. Pass the tiles as `summary` and the table as its children. It is `draft`: its loading, error, partial and read-only states are not specified yet.
|
|
10
|
+
- **The console's rail and palette take a navigation.** `ConsoleRail` and `ConsolePalette` take an optional `nav`: a product that draws other destinations than the `nav` in `src/app.config.ts` passes them, and they are drawn in their own groups and order, filtered by what `access` says the person may open, with a group left empty dropped and an empty list left empty. Without it both behave as before. `visible(only?, sourceNav?)` is the one filter both use. `ChromeAccess.viewAs` is now optional: a product with one session leaves it out and its menu has no View as group, where it used to have to pass an empty one and got an empty group for it. Nothing that passes `viewAs` changes.
|
|
11
|
+
- **Page loading is a card.** `LoadingPage`, `Busy`, `TilesSkeleton`, `ChartSkeleton` and `TableSkeleton`, the frame and the shapes every route's loading state is composed from, were a file of the sample under `app/`; they are now `@/components/patterns/page-loading`, listed in the Atlas at `/system/patterns/page-loading`, so an adopted project is given them and a template can use them. `TilesSkeleton` takes a `count` (three when it is left out). The sample's `app/(dashboard)/_loading.tsx` is gone and the dashboard's `loading.tsx` files, twelve of them now, import the card. A project made from an earlier release keeps its own copy of that file: an update writes the card beside it and changes neither the file nor the pages that import it.
|
|
12
|
+
- **The records template is `beta`: every state has a specimen, and it owns its loading arrangement.** `RecordsSkeleton({ tiles?, rows?, filters? })` is the arrangement while its data is on its way, composed from the page-loading card; Customers' `loading.tsx` and its page's fallback draw it, with the same markup as before. The preview adds Loading, Error, Partial (a figure that could not be worked out reads Unavailable and says why) and Read-only, and the template's spec gives each of the seven states one disposition, `Covered:` by a named specimen or `Inapplicable:` with the reason. `tests/atlas-template-contracts.test.tsx` holds the spec and the preview to each other, and `CONTRIBUTING.md` says a template owes this before it is `beta`.
|
|
13
|
+
- **Tickets, verification runs and releases are collections.** `tickets`, `tests` and `releases` join the sample's data with the shapes a small engineering team keeps: a ticket against a service with a priority, a state and an assignee; a run of a verification suite against one endpoint of one release; and a release with the verifications it requires before it can go. Every main role reads all three. Owner, Admin and Member create and change tickets; nobody removes one, and nobody writes a run or a release. A ticket is written only through one boundary, `ticketsFor(scope)` in `src/data/ticket-mutations.ts`, which the page's actions and the assistant's `create_tickets` and `update_tickets` both pass: it sets the id, the state `open` and both times itself, checks the service and the assignee against the collections, and allows only the five steps between open, waiting and closed. **Breaking for a project that pins its grant table:** every main role gains `tickets:read`, `tests:read` and `releases:read`, and Owner, Admin and Member gain `tickets:create` and `tickets:update`; add them to the pinned lists.
|
|
14
|
+
- **Operations tickets, the page a team works its tickets in.** `/operations-tickets` is a records page: three tiles count every open, waiting and closed ticket, and a table with search and filters for state and priority (kept in the address) shows each ticket's service, priority, state, assignee and how long ago it was opened and last changed. Anyone but a Viewer opens a ticket from `New ticket` and moves one along from its row's menu, which offers only the steps its state allows; a refusal comes back as a sentence inside the open form, which keeps what was typed. A Viewer sees every ticket and who has it, and a line saying who can write. The page, its tiles and the assistant's `tickets` view tool all read through `readTickets()` in `src/data/tickets.ts`, which joins each assignee's name on the server, so a reader who may not read members is shown the names and never the roster. `createTicket` and `updateTicket` answer a value, not a throw.
|
|
15
|
+
- **Tests and verification, and Releases: what ran and what is live.** `/tests` lists every verification run with its verdict, how long it took and, for a failure, why, and links each run to its release and to the requests of its endpoint. `/releases` shows which version is live for each service and environment, which releases are ready to go out and which are still in testing, and for those, which required verification has no run, failed or was skipped. Both are records pages, both are read by every role, and neither offers an action: nothing here reruns, deploys or rolls back. Three rules are worked out in one place, `src/data/verification.ts`, which pages, client views and the assistant's `tests` and `releases` view tools share: the latest run of a verification (the one that started last), what a release still waits for, and the live version. `readAll(name)` in `src/data/read.ts` reads every row of a collection, for a count or a join that must not be of a first page.
|
|
16
|
+
- **The coverage review of the whole sample is finished.** `docs/atlas-scenario-coverage.md` has a row for every job, page, pattern, chart and removal, each pointing at its Atlas entry and at the check that exercises it; it says what the review found and decided, and lists the files the work added, changed and retired. The contribution guide says that an example built for a named scenario in the product counts as the page that needs it, and that a wrapper made to fill a layer does not.
|
|
17
|
+
- **The product's workspaces are written down.** `docs/product-workspaces.md` is the guide to the sample as a product: its three workspaces and who each is for, where each of the thirteen destinations lives, how a page finds its workspace, who can do what, eight tasks to try, the three meanings of the word, how a product declares its own workspaces or none, and what the sample does not do. `docs/surfaces.md` gains layer 5 and the rows layer 4 was missing.
|
|
18
|
+
- **The sample's journeys run in a browser.** `journeys/workspaces.ts` walks the product as a person does, against a running build, and fails with the step that broke: from Main into a request's own page, through the rail's menu to Engineering, a ticket created there and counted by Main, a refresh, Back and Forward, a second independent browser, an address opened directly, the Atlas and its way back, the workspace already in view, an unsaved change that asks before leaving, a navigation that has not arrived, and a Viewer who enters every workspace, writes nothing and is given a way out of Members. It is the product's script, named under `browserChecks` in `scripts/verify.config.ts`, so `pnpm verify --full` runs it beside the audit; every name it looks for is read from `src/app.config.ts`, and a step whose pages a product has removed says `n/a`.
|
|
19
|
+
- **Leaving a form with unsaved changes asks first.** The shell exports `useUnsavedChanges(dirty)`, which a component calls to say it holds unsaved changes, and `useLeave()`, which answers `leave(go)`: `go` runs at once when nothing is unsaved, and otherwise only if the person chooses Leave in the "Leave without saving?" dialog. `FormSection` reports its `dirty` itself, so a page built from it needs nothing. While something is unsaved the shell holds a plain click on a link anywhere on the page, in the rail and in the drawer, before the link's own handler runs, and asks; Stay changes nothing, Leave goes to the link's address, query and fragment included. The Rail's menu, the command palette and a table row that opens an address go through `leave`, and a product that navigates from its own code should too. A link opened with a modifier key, in another tab or as a download is never held; closing or reloading the tab gets the browser's own warning; Back and Forward are the browser's and are not held. The question is a chunk of its own, fetched when a form first becomes unsaved, so no page pays for it on first load. `AppShell` now asks for the router: a test of yours that draws the shell with a stand-in for `next/navigation` needs `useRouter` in it.
|
|
20
|
+
- **The records template takes its summary as a fragment.** `summary` was an array of keyed tiles or one node; a fragment now counts as its children, so a page writes its tiles as it writes any other markup and the row still sizes its columns by their count.
|
|
21
|
+
- **The console's rail takes who is signed in as a value, and its waiting frame is exported.** `ConsoleRail` and `ConsolePalette` took `access` only as a promise a server layout resolves. A product that learns who is signed in in the browser passes the value itself, `access={chrome}` (`null` for nobody), and it is drawn at once; `RailPending`, the frame the rail holds while it waits, is exported from `src/views/console-chrome.tsx` for the product to hold meanwhile and to draw in the fallback of a boundary around the shell (#24).
|
|
22
|
+
- **`queryRows`, the meaning of a query, for a collection over a real store.** `queryRows(rows, key, query)` in `src/lib/collection.ts` filters, orders and pages a list as `where`, `sort`, `limit` and `offset` mean in the sample: a missing value matches only `ne` and sorts last, `contains` ignores case, `total` counts before the page is cut. `arrayCollection` calls it, and a collection that reads an API and filters in memory calls it too, where it had to copy the rules (#24).
|
|
23
|
+
- **`useSearchDraft`, for a search box over a view kept in the address.** It was a helper inside the Requests view; it is exported from the data table. The box shows each key, and the address is written once the typing has stopped for 250 ms, or at once when the search is cleared, so a word is one navigation and not one a letter. Customers, Operations tickets, Tests and verification and Releases use it now, as Requests did (#24).
|
|
24
|
+
- **`update` says which managed files are off the release.** A project already on the running version was told "nothing to update" whatever state it was in. It is now also given the managed files that differ from the release and are in no keep register, with what to do about them; nothing else looked between releases, so a product drifted silently and the next update met edits nobody had recorded a reason for (#23). The gate is unchanged: it does not fail on them.
|
|
25
|
+
- **A new project keeps the sample's workspaces only when they were asked for.** The skill told an agent to choose between keeping the three workspaces a created project starts with and taking them out. It now says which: keep them when the person asked for several places to look, take them out when they did not say (`references/customize.md`, "Navigation", has the steps). `create` itself still writes them.
|
|
26
|
+
- **A guide for a product whose data source signs in the person.** `references/existing-project.md` has "When the data source signs in the person, not the server": pages that read in the browser, where the boundary around a table with filters goes, the chrome given a value, and the assistant's route reading as the person. Beside "Adding the assistant" it now says what the layout's promise does to a page's HTML size, where a product's own assistant handoff is checked (a journey of its own) and where a product's own test setup goes (#22, #24).
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **The workspace menu offers only where a person may go.** The menu listed every workspace, whatever the person could open in it, and a workspace's row always opened its home: a person with nothing to open in a workspace was offered it and landed on a refusal, and one who could open its second destination but not its first was sent to the refusal first. A workspace is listed when the person may open at least one of its destinations, and its row opens the home or, when the home is not one, the first destination they may open. With one workspace left the rail has its account menu and no switcher. The sample's roles never showed it, since every persona has something in each workspace; a product with an operator-only workspace would have (#25).
|
|
31
|
+
- **A toast is announced once.** The stack was a polite live region and every toast in it has a role that is a live region of its own, so a screen reader read each message twice and a critical one both politely and assertively. The stack is no longer one (#23).
|
|
32
|
+
- **The rail's marker follows the link when the link moves without a navigation.** It was measured when the address changed and never again, so a rail that was resized, a font that arrived late or a group that changed height left the marker where the link had been. The list is observed and the marker measured again when its size changes (#23).
|
|
33
|
+
- **The first page of a visit does not animate in after a long wait.** The arrival is feedback that the page came, and a reader who has watched a skeleton for longer than the arrival lasts gets none from it, only a later page: on a slow phone the rise held a page's own text back by its whole length, and where that text was the largest paint it was charged to LCP. A first page that took longer to arrive than its arrival lasts (`dur-grow` plus `dur-enter`) is drawn where it ends; a quick first load arrives as before (#21).
|
|
34
|
+
- **A filter keeps a key in the address it did not write.** `useQueryState` rebuilt each write from the router's copy of the address, which holds only what the router wrote: a key another part of the page kept there with `history.replaceState` could be dropped by the next filter. It rebuilds from the address the browser is at (#22).
|
|
35
|
+
- **The assistant says where a change is made when a page makes it.** A collection that implements no operation, because its pages write through their own calls, had the assistant tell a person that nobody could add a record, with the form on the page. An operation named in `pageOnly` is said to be done on its page whether or not the collection implements it (#24).
|
|
36
|
+
- **`Prose` parses its text once per text.** It parsed its whole input on every render of its parent; it is memoised on its props, which are strings (#24).
|
|
37
|
+
- **The gate asks every console page what the agent reads, under whatever route group the project keeps.** `scripts/check.ts` and the sample detection in `scripts/lib/sample.ts` looked only under `app/(dashboard)`, so in an adopted project that kept its own group the rule matched nothing and the assistant walk-through found no sample page. Both follow the route group at the top of the app folder, by any name (#23).
|
|
38
|
+
- **The keyboard walk judges a stop once it has stopped moving.** It waited two frames after a key, and on a loaded runner the focus style painted later, so a skip link still sliding in was reported hidden. A stop is judged when its box has held for three frames, or after twelve (#23).
|
|
39
|
+
- **A page that fails CLS says what moved.** `scripts/vitals.ts` printed the number; it adds the three elements whose shifts sum highest (#24).
|
|
40
|
+
- **The weekly consumer check tests the releases it is given.** `cli/scripts/smoke.ts` updated a rebranded project to a fixed version 0.5.1, which `update` has refused as a downgrade since 0.5.2, and it expected `src/app.config.ts` unchanged by an update that, since 0.13.0, reports the migration whose edit the script itself then makes there. It updates to the patch after the running version and expects that edit.
|
|
41
|
+
- **A dashboard created from the package passes its own gate again.** Two things a created project was given failed there, and nothing in this repository ran as one: `tests/card-stage-hydration.test.tsx` imports a module the Atlas removal takes, so the project's type check failed on a file it was right to lose; and three tests of the workspaces (`tests/workspaces.test.ts`, `tests/workspace-access.test.ts`, `tests/workspace-chrome.test.tsx`) expected the Atlas's two navigation entries and the template's own name. `brand --no-atlas`, which every `create` runs, now removes every shipped test of a module it removes, and `tests/cli-atlas-prune.test.ts` holds that list against the tests; the three tests read whether the project has an Atlas, and its name from the configuration. Found by running the packed `create` and its verify, which the release workflow runs and a local gate does not.
|
|
42
|
+
- **A count opens the records it counts, and one definition says what has no owner.** Found by the final review's readers and its first sweep. Main's "Failing now" and the bell's "verifications are failing" count the latest run of each verification, and their link opened every failed run, the replaced ones too (1 counted, 2 listed in the sample): the Tests page gains a Runs filter, "All" or "Latest of each" (`runs=latest` in the address, and on the `view_tests` tool), and both links use it. The bell's alert for an unowned high-priority ticket opened every high-priority ticket; it opens the list on that ticket. A ticket whose assignee had been removed from the workspace read as unowned on the page and to the assistant, and as owned to the bell, which stayed quiet: `unownedTickets` in `src/views/tickets-context.ts` is now the one answer for all three. Operations drew only the newest unresolved incident while Main and the bell named every one: `readIncidents()` answers `open` beside `current`, and the page lists the others under "Also open". The Overview's context tells the assistant the Business region's other three figures (customers by status and by plan, and their thirty-day spend), which the page drew and the context left out. The Customers page reads every customer (`readAll`) for its tiles, not a first page of a hundred.
|
|
43
|
+
- **One address, one member.** Inviting an address the workspace already had wrote a second member with it. The member boundary (`src/data/member-mutations.ts`) refuses it, comparing addresses without their case: "Somebody with that address is already in the workspace." Found by inviting the same person twice in a browser; the behaviour is older than this work.
|
|
44
|
+
- **A release's phone row says what did not pass.** On a phone the Verification cell is one line, and it counted every required verification that had not passed as "waiting": a run that failed or was skipped read as one still to come. It now counts each kind, "1 of 2 passed, 1 failed" or "2 not run", from `unresolvedCounts` in `src/views/releases-context.ts`; the wide table already named each one, and the tile's definition and the line the assistant is told now say "has not passed" where they said "waits for". Found by a second independent look at the renders, with two sentences that claimed more than the product does: Settings no longer says the workspace's address is "used in links you share" (nothing builds a link from it), and Requests says it lists the most recent calls, which is what the sample holds. A second look at the guides corrected them against the code in thirteen places, among them what a new collection needs before anybody can read it (`customize.md`, "The assistant"), the columns a row of tiles takes, and which pages stay fresh without a reload.
|
|
45
|
+
- **Leaving asks once.** After a person answered "Leave", the shell still asked the browser to warn before unloading, so a move that ended in a full page load (a sign-out, an address the router hands to the browser) raised a second question. The guard stands down once Leave is chosen and stands up again with the next unsaved change.
|
|
46
|
+
- **A ticket action shows the person only a sentence written for them.** `createTicket` and `updateTicket` returned the message of any error; they now return the boundary's own refusal (`TicketRefusal`, exported from `src/data/ticket-mutations.ts`) and "The change did not go through." for anything else. Saving the workspace keeps the trimmed name in the field, as it is stored.
|
|
47
|
+
- **A browser check leaves a page that holds unsaved changes.** A page with unsaved changes asks the browser to warn before it unloads, and the warning waits for an answer. `scripts/lib/chrome.ts` had nobody to give one, so the next `open()` of any suite timed out after 30 s (measured on Settings with a typed-in name, once the shell began asking). The helper now answers that one question, as a check that opens another address has chosen to leave; an alert or a confirm a page raises itself is still met as it is.
|
|
48
|
+
- **A metric tile's note keeps the whole of itself when the tile cuts it.** The line where the change would sit (`note`, and the tile's own "No earlier period to compare") is one line with an ellipsis and had no title, so on a narrow tile the rest of the sentence could not be read at all. Found by the audit of the records template's Partial specimen, where "The error counter did not answer." was cut in a tile a third of a phone wide. A note that is text now carries its whole line in its title; a node the caller crafted is left to its author.
|
|
49
|
+
- **A record's page has a shell again, and so does every other route with a parameter.** `AppShell` called `usePathname()` at its top. Under Cache Components a route whose parameter the build did not know has no pathname until the request, and a component that asks for one waits; with nothing between the shell and the document, everything waited with it. The build's shell for `/requests/[id]` was **a file of 0 bytes**: no frame, no rail, no skeleton was prerendered for any record the build had not listed, which in a product is every record. An adopter whose layout put a `Suspense` above the shell got the other face of the same fault, that boundary's fallback and nothing else. The shell no longer reads the address: the drawer, the mark that holds a navigated page still and the rail each do, behind their own boundaries. The Atlas's shell did the same thing and so did its pages, and has the same fix. After: `/requests/[id]` prerenders **17.7 KB** (the frame and the page's skeleton), and all thirteen such shells in the build are non-empty. `scripts/route-policy.ts`, which `pnpm verify` runs after its build, now fails a route counted as partial whose shell for an unknown parameter is empty.
|
|
50
|
+
- **A key value list keeps a cut value whole, and counts its columns from its own width.** The card's spec has always said a long value truncates "with the full value in the title", and the component set none; and its two columns began at the window's 640px, though a card beside another is narrow in a wide window. At a 1024px window the request page's facts read "03 Oct …", "GET /v…", "Northwi…" and "us-eas…", each value about 50px wide, with the whole of it nowhere on the page. A value that is text now titles itself, a value drawn as a node takes the item's new `title`, and two columns begin at 640px of the list's own width. Found by the audit the first time it asked whether cut text is kept anywhere, which is also how the same omission turned up in `Breadcrumb`, `CopyField` (which titles what it shows: for a masked secret the mask, never the secret), the request page's trace, and three Atlas specimens. `KeyValue` also stopped dropping the hairline under the second-to-last row of a one-column list with an even number of rows.
|
|
51
|
+
- **`audit.ts --atlas` measures the Atlas, and says what it reached.** 0.14.0 widened it to "every entry the Atlas serves" by importing the Atlas's content module, which imports `server-only`, a marker only Next's bundler resolves. Plain Node cannot, the failure was caught, and the run went on with the cards alone: 142 of 172 routes, every token view, page spec and guide unvisited, and a last line that read the same either way. The entries are read through `scripts/lib/atlas.ts`, which resolves the marker and fails the run when the module cannot be read; the first line of a run says what it reached by kind, and `--list` prints the routes without opening a browser.
|
|
52
|
+
- **The audit sees three things it could not, and the Atlas is held to the vitals floor.** An SVG's text was exempt from the contrast pass (it is painted by `fill`, which a check that reads `color` cannot see); it is now measured by its `fill` against the page and every filled shape the SVG paints under it. Text cut with an ellipsis must keep the whole of itself in a `title`. And two things in the same place fail: two pieces of text whose glyph boxes share a third of a line, and a stroked line that runs through one. Each has a planted defect and a corrected twin in `scripts/fixtures/deep/`; the previous audit passed all six planted pages. Tried over every route before becoming failures: the product's 22 and the Atlas's 172 reported the defects above and three rules that were too wide (a clamp's hidden lines, a line scrolled out of a list, the text of a closed disclosure), each kept as a passing case. `scripts/vitals.ts` measures the Atlas's front page and one card page with everything else, and `--atlas` measures every entry: 101 of 101 under the floor, the worst LCP 0.92 s and the worst CLS 0.061.
|
|
53
|
+
- **The presses and the audit wait for the shell's late chrome, as the keyboard walk does.** The assistant's launcher arrives when the request's own work ends. The keyboard walk had been anchored to the page's own signal for it; the other two still waited a fixed time, and a suite that only counts or only looks does not fail when a control arrives late, it reports a page without it. Planted and measured: a dead control arriving 3.6 s after the page was never pressed and the run read `0 controls pressed … 0 issues`; an unnamed one arriving at 2.6 s passed the audit at five widths in both themes. The wait is one module, `scripts/lib/settle.ts`, and all three use it.
|
|
54
|
+
- **Every check measures at 1x, by hand and inside `pnpm verify`.** `scripts/lib/chrome.ts` opened a page at 2x unless `DPR` said otherwise while `pnpm verify` set 1, so one suite gave two answers for one page: a table 368px wide in a 356px frame at 2x that fits at 1x. The default is 1 everywhere, and `scripts/shot.ts`, whose pictures are for the eye, asks for 2 itself.
|
|
55
|
+
- **A page that arrived whole no longer empties while its code loads.** `Providers` read the stored appearance choices on mount as an ordinary update, and that update sits above every part of a page whose code arrives after the page does (a `dynamic()` component, a lazy panel). React answers an urgent update above such a part by discarding the server's HTML for it and drawing its Suspense fallback until the code is there, so the part vanished and came back. On the Atlas every card page did this: the specimen left, the specification jumped up the page and back, and all ten card pages sampled measured **CLS 0.30 to 0.61 against the 0.1 floor** on the phone profile (`/system/patterns/data-table` 0.500, `/system/patterns/trend-chart` 0.611). The read is a transition now, which waits for the part to hydrate first; the pre-paint script has already put the choice on the root, so nothing a reader sees is later. The card stage had the same fault from a second direction, for a reader in the light theme or with another accent: it learns the page's look as it hydrates, renders again at once, and was handing the specimen a new element under a pane keyed by the theme. The specimen is one element for the life of the stage and the single pane stays mounted. Measured after, same profile: **CLS 0.000** on the same pages, for the default reader, a reader in the light theme, and one with a stored theme, accent and density. An adopter's own `dynamic()` part under `Providers` was open to the first fault and is covered by the same change.
|
|
56
|
+
- **A check leaves no browser and no profile behind, however it ends.** Two faults in `scripts/lib/chrome.ts`, both measured before they were touched. A script ended by SIGHUP (a closed terminal), SIGQUIT or SIGKILL (a harness's timeout, `kill -9`) left a whole headless Chrome running under launchd: only SIGINT and SIGTERM were caught, and nothing can catch a kill. A thrown error, a rejection and `process.exit` were already clean. And every check, on its ordinary way out, left its profile: the removal was started in the background and `page.close(); process.exit()` ended the process before it finished, so three runs left three profiles and two days of checks left 590 of them, 29 GB, in the temp folder. The four ending signals are caught; a small shell watches the script and kills the browser once the script is gone, and only a process whose command line still names that browser's own profile; and a removal still under way at exit is finished there. After: nine endings, none leaves a browser, and three close-and-exit runs leave no profile.
|
|
57
|
+
- **A live stream's open is one read, not two.** The client reads every collection when its stream opens, and the stream's first frame is always a `resync` of every collection; `src/lib/live.ts` answered both, so every open read everything twice. Measured on a built app, a cold load of `/`: **two** refresh actions and **two** router reads of the page, where one of each is the design. The first frame on a stream is now taken as the read the open already began; a `resync` that arrives later is the server reporting lost hints and is still read. After: **one** action and **one** router read. What this does not change, and what an earlier note misread as "the hidden phone drawer prefetches too": four rail routes still have their `/_tree` prefetch sent twice on a cold load. The drawer is not in the document while it is closed and a desktop page holds one link per route; the second request is Next prefetching its visible links again after the router read that the open makes, and it stays, inside the prefetch budget, for as long as an open reads.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- **Requests, API keys and Members are built from the records template.** Their bodies were three hand-made stacks of the same arrangement; each is now `Records`, and each waits for its data in `RecordsSkeleton`. Nothing a reader sees changes, loaded or loading. API keys and Members keep their masthead in the view, because its button shares state with the sheets below it.
|
|
62
|
+
- **Customers works out its figures once.** The number of customers, their thirty-day spend and who is past due were added up twice, once for the tiles and once for what the assistant is told. `customerFigures(rows)` in `src/views/customers-context.ts` is now the one calculation both take, so the two cannot differ.
|
|
63
|
+
- **The sample's Health item no longer carries a count somebody typed.** `badge: '1'` sat in `src/app.config.ts` and said one incident was open whatever the incidents were. The Rail still draws a `badge` a product passes; pass one worked out from data.
|
|
64
|
+
- **The Atlas has six layers.** Layer 5, Product workspaces, is the product's guide, served at `/system/products/workspaces` and listed in the shelf under a 5. In layer 4 a page template is told from a page built on it: the shelf marks a template, an article names its layer ("Layer 4 · Page template" or "Layer 4 · Page"), a template lists the pages built from it, and a page links its template. The front door draws six layers and counts templates and pages apart. No address changed. `src/system/content.ts` gains `layerOf(entry)`, the kind `template` and an optional `template` on a page; a project that removes the Atlas now removes `tests/atlas-layers.test.ts` with it.
|
|
65
|
+
- **When traffic comes and where it comes from are rows.** Analytics' weekday-and-hour heatmap, its hours of the day and its regions were three constants returned behind a check of `requests:read`. They are now rows of two read-only collections, `hours` (168 rows) and `regions`, served to the tenant and asked under two new grants, `hours:read` and `regions:read`, which every main role holds beside the other dashboards' and which the Analytics page's need names in place of `requests:read`. A project that pins its own grant table adds the two grants to the roles that read Analytics. `analyticsFigures()` asks both and takes the tenant's own rows; the numbers Analytics draws are unchanged, and they are still the sample's own fixed shapes, not worked out from the request log. `demoHeatmap`, `requestsByHour` and `REGION_LATENCY` are no longer exported from `@/data/sample`: a page of yours that imported them reads the collections.
|
|
66
|
+
- **Settings shows nothing that nothing stands behind.** The sample's Settings loses its list of connected hosts with Disconnect, its Notifications section, the "Let assistants read dashboards" switch and its Danger zone with Delete workspace. The hosts and the switches kept their state only in the page, the sample sends no notification, and Delete workspace reported a deletion that never happened. Agents and MCP keeps the statement that every change an assistant proposes waits for approval. With the deletion go the things that existed only for it: the `delete-workspace` action, the Owner's `workspace:remove` grant, the `workspace` collection's remove operation and `workspaceRemove` in `SettingsMay`; `CONNECTED_HOSTS` is no longer exported from `@/data/sample`. A project that copied any of these keeps its own copy and loses nothing; one that really deletes a workspace adds the action, the grant and the operation with the record behind them. `scripts/verify.config.ts` finds `/settings` ready by the theme choice, where it looked for a switch.
|
|
67
|
+
- **Analytics opens with the period's four figures.** Business's home showed when traffic comes and from where, and neither spend nor a comparison. It now opens with Requests, Error rate, Latency p95 and Spend for the period chosen, each with its change against the period before and its daily shape, from the totals the Overview reads and under its labels (`OVERVIEW_METRICS`); one Meridian covers the page, so a day pointed at in a trend is read in the four figures, and the assistant is told the same four with the figure each is measured against. `AnalyticsData` gains `totals`, and the loading state gains four tiles.
|
|
68
|
+
- **The sample claims a period only for what follows it.** The weekday-and-hour heatmap, the regions, the endpoints and the Overview's response split are fixed figures, the same whatever the period. Analytics said they were "summed over the period" and "in the period", its context told the assistant so, and the Overview's cards and one of its findings said "in the period" and "the period's errors". The pages now claim no period for them, and Analytics' context says that they do not follow the one chosen.
|
|
69
|
+
- **Three more controls that reported what did not happen are gone.** Customers' Invite customer validated a form, said "Invitation sent" and wrote nothing; Operations' Subscribe to updates said updates would go to an address and kept nothing; the request's menu item Revoke the key only opened API keys, and is now Open API keys. `InviteCustomer` is no longer exported from `src/views/customers.tsx` and `SubscribeButton` from `src/views/health.tsx`; a page of yours that draws either keeps its own copy, with something behind it. Customers' two tiles lost a sparkline worked out from request trends by a constant: a tile draws a trend only where a series stands behind it.
|
|
70
|
+
- **The workspace record holds a name and an address, and the name has a reader.** Settings saved a time zone that nothing read (every date is cut on `app.timezone`) and a name it said the rail showed (the rail shows `app.name`). The time zone is gone from the Workspace section, from the `workspace` collection's fields, from `saveWorkspace`'s input and from `@/data/sample` (`TIMEZONES`). The Members page reads the record's name for a person who may read it and hands it to `MembersView` as `workspace`; the invitation sheet names the workspace by it, and no longer says an email is sent. Inviting writes a member with the status Invited and delivers nothing, so the sheet's button is Invite, its confirmation is "Invited {email}" with a line saying the member is listed as Invited, and a refusal's banner is "Not invited"; `scripts/live.ts`, which invites through the sheet, looks for the new words. Operations says its incidents were resolved in the last 90 days, where its context said "in the period" on a page with no period, and its navigation check in `scripts/verify.config.ts` points at the strip of days, since the masthead has no button. Settings' Address says it is not changed there, for everyone.
|
|
71
|
+
- **The guides describe the dashboard as it is.** The skill's customising guide says how a list page is arranged from the records template and its loading arrangement, where the page-loading shapes live, how a product declares workspaces (and that one navigation needs none), that the address alone decides the workspace, how a form reports unsaved changes to the shell, how a product writes its own alerts read, and what a copied sample page does not do. The skill, the adopter's guide and the starting guide no longer describe a layout that imports a clock and a typed list of alerts, a "health" page, or a context budget of 2,400 characters (it is 2,600). A project made by `create` starts with the sample's three workspaces, and the guide now says so and gives the two ways forward, keeping them or taking them out, with the list of what goes with them (run in a created project, whose checks pass after it). Also corrected against the code: `references/update.md` gains the `nav-needs` migration, whose own instruction named a section of the Start guide that does not exist; removing the Atlas from a clone is `node scripts/brand.ts --no-atlas`; `docs/surfaces.md` describes the Overview, Analytics, Requests and Settings as they are and lists Page loading; a Data table becomes cards under 640px of its own width, not below 768px of the window; `Freshness` takes a `Date`; `--workspace` names the environment line, not a navigation workspace; and what `needs: 'public'` means, which `adopt` writes. A project's own gate reads the installed skill and fails on a path the project does not have, so `tests/cli-skill-layouts.test.ts` now holds every path the skill names against what `create` leaves and what `adopt` gives: two paths written without `optional:` passed every check here and failed both consumers.
|
|
72
|
+
- **The sign-in page offers only what signs somebody in.** Its second panel took a work email, waited, and said "Check your inbox. We sent a sign-in link", and its SSO button said it was opening a company's sign-in; nothing was sent and nothing was opened, and the panel promised a free first workspace and named terms the product does not have. `SignInPanel` is removed from `app/sign-in/panel.tsx`. The page keeps the one way in that works, the people the console can be opened as (or the password panel when `DEMO_PASSWORD` is set), and says "Sign in as" and "Sign in" where it said "Open the demo". A product that emails a link or asks an identity provider adds that form with the action behind it. On a phone an unassigned ticket reads "Assigned to nobody", the word its table uses, and the sample's activity no longer names a workspace called "Workspace".
|
|
73
|
+
- **The Shell's preview asks the shell's own leave question.** It drew a copy of the dialog by hand; a button now opens `LeaveQuestion` itself, and the frame's drawing is labelled a diagram.
|
|
74
|
+
- **The Rail's preview draws the Rail.** Its hand-made list of item states is gone: the specimens are the real component, so rest and current are drawn in them and hover and focus are seen by using them.
|
|
75
|
+
- **Six layers, counted the same everywhere.** The README, the contribution guide, the package's description and the header of the generated tokens count six layers, numbered 0 to 5; the sixth, Product workspaces, is how a product is assembled from its pages, declared in the configuration and written down in its guide.
|
|
76
|
+
- **The sample's navigation is three workspaces, declared once.** `src/app.config.ts` exports `workspaces` (Main, Business and Engineering, each with a label, a sentence saying who it is for, and the entry it opens at) and each `nav` entry names its owner in an optional `workspace` field; `nav` is three groups with no label, one for each workspace, where it had `Operate` and `Workspace`. `src/views/workspaces.ts` derives each workspace's navigation from that one list and answers which workspace an address belongs to, from the address alone. A workspace is where a person looks, never what they may do: access is still each page's feature. Your own `src/app.config.ts` is yours and is not touched by an update; a navigation without the field compiles as before.
|
|
77
|
+
- **The sample's rail and palette follow the address into a workspace.** `src/views/workspace-chrome.tsx` is the product's adapter over the console chrome: `WorkspaceRail` and `WorkspacePalette` draw the destinations of the workspace the address is in, under its name, and the rail's menu goes to Main, Business or Engineering by opening that workspace's home; choosing the one in view does nothing, so a page and its filters stay. Nothing but the address says which workspace is in view: no cookie, no storage, no query. `ConsoleRail` and `ConsolePalette` are unchanged, and a product with one navigation keeps using them as it does.
|
|
78
|
+
- **A page a person may not open offers a way out.** `NoAccess` in the sample draws one link, to the first destination of the page's own workspace that the person may open, or to Main's home; it still says who can open the page and whom to ask.
|
|
79
|
+
- **The Atlas links back to the product by workspace.** Its shelf's foot had one link, "Open the template"; it now has "Back to Main" and a link into each other workspace.
|
|
80
|
+
- **Main's Overview summarises the whole product.** Below the traffic story the page has three quieter regions: Business (customers, how many of each status and plan, thirty-day spend), Operation (the services that are degraded or out and every unresolved incident, or that all is well) and Delivery (open and waiting tickets, failing verifications, and live, ready and in-testing releases). Every figure is of the whole collection and is worked out by the function its own page uses, so the two cannot differ; a figure whose page has a filter for it opens the list it counts. A region whose read fails says it is unavailable and the rest of the page still draws. The Overview's need now names every collection it reads, and the assistant is told the same figures under the labels their pages use. Each workspace's home says who the workspace is for, in its masthead.
|
|
81
|
+
- **The sample's page at `/health` is called Operations, as its entry is.** Its title, its loading state, its context, its view tool's title, its embed and its row in the Atlas say Operations; the address, the feature id and the tool's name do not change. A test or a script of yours that looks for the sample's title Health looks for Operations now.
|
|
82
|
+
- **`adopt` writes a configuration without the sample's workspaces.** A project that adopts Meridian gets its own pages as its navigation and none of the sample's workspace declaration; the function that writes it (`projectConfig`) is now tested on its own, where only the packed smoke ran it before.
|
|
83
|
+
- **The shell's alerts are worked out from reads.** The sample's bell showed a list somebody typed. `readAlerts()` in `src/data/alerts.ts` now tells every unresolved incident, every customer past due, how many verifications are failing and every high-priority ticket nobody has, by the functions the pages themselves use, each linked to the page that handles it; the layout hands the read to `ShellAlerts` unawaited, so the frame never waits for it, and a person who may not be told anything gets the tools with no alert. `src/data/sample.ts` no longer exports `ALERTS`: a product writes its own `readAlerts`.
|
|
84
|
+
- **The browser checks follow a rail that shows part of the navigation.** `scripts/navigate.ts` and `scripts/perf.ts` start each journey from the nearest route, in rail order, whose rail draws a link to the destination, and report a destination no other route's rail links to as a problem, never a pass; `scripts/rail.ts` reads the rail on every configured route no rail has linked to yet, so `pnpm verify --as` holds a persona against all of their routes; the assistant walk-through follows only links that are on screen. A product whose rail shows every destination on every page is measured exactly as before. In `scripts/navigate.ts`, `drawerRoutes` now answers `{ starts, drawn }` and `startFor` takes what each rail draws as a fourth argument.
|
|
85
|
+
- **The two write boundaries share their queue and their invalidation.** `src/data/write-boundary.ts` holds `writeQueue(name)` and `invalidate(tag)`, which the members boundary had to itself and the ticket boundary would have copied.
|
|
86
|
+
- **The Rail's page in the Atlas shows the rail, not a drawing of its menu.** Two of its specimens were a menu panel drawn by hand, because a menu mounts only while it is open. They are now the real rail: open the top row to see the account menu with one workspace, the switcher and its Show group with several (choosing one moves the check and the line under the product name), and the View as group with its personas. `brand --product` removes the test that holds this, as it removes the previews.
|
|
87
|
+
- **The Assistant card is `beta`, as it ships.** It was the one card still marked `draft` ("being specified; do not build from it") while the template shipped it, its spec covers every state and surface, and `pnpm verify --full` walks it in 23 steps. `stable` stays unused: it promises that changes are recorded per card and follows a review of each, which nobody has done.
|
|
88
|
+
- **The Prose card says where to render it, and what the other choice costs.** Prose has no client code of its own: rendered from a Server Component it costs a page no JavaScript, and imported into a client component it brings its renderer, a 143 KiB chunk a reading page then waits for before its text can paint. An adopter's document pages were reported at LCP 2.88 to 2.95 s that way. Nothing in the template does this (the one browser-side use is the assistant's panel, fetched when it is opened, and no prerendered page references the chunk), so this is a guide, in the card's spec: render Prose in the server page and pass it to a client view as `children`.
|
|
89
|
+
- **The default gate's tests cost a third of what they did.** Every test file started a process and a jsdom of its own, and that start was most of the tests' cost: 79 s of CPU for the 141 files `pnpm verify` runs, at the four workers the release runner has. `vitest.config.ts` now sets `pool: 'vmThreads'`, so a file is still a world of its own, as a context in a shared worker: 27 s of CPU, and 8 s on the clock where it was 20, with the same 698 cases passing. `tests/setup.ts` gives a jsdom context the web streams the assistant's client needs. The one test that waited 30 s for a browser that never answers is a test of the checks themselves and is now `tests/deep-chrome-cleanup.test.ts`, with the others no project is given. A project made earlier keeps its own `vitest.config.ts`; adding `pool: 'vmThreads'` to it is the whole change.
|
|
90
|
+
|
|
91
|
+
### Breaking
|
|
92
|
+
|
|
93
|
+
Three changes need a line from anyone updating a project. Each says what to do instead.
|
|
94
|
+
|
|
95
|
+
- **`resetCollections` is no longer exported from `src/lib/collection.ts`.** Nothing called it. A test of yours that did builds its collection with a tenant of its own, as the template's tests do.
|
|
96
|
+
- **The gate asks every page under the app folder's route group what the agent reads.** `scripts/check.ts` asked only under a folder named `(dashboard)`. In an adopted project that kept its own group, such as `app/(dash)/`, each page specification there is now asked for `### What the agent reads` under `## Agents`, as `references/customize.md`, "A page spec", describes. An update reports it as the `agent-reads-own-group` migration, naming the specifications that lack the part, so the project is told before its gate fails. A project that repeated the rule in `scripts/check.local.ts` for its own group can delete that copy.
|
|
97
|
+
- **The sample's helpers that did nothing are gone from the template.** `InviteCustomer`, `SubscribeButton`, `TIMEZONES` and `SignInPanel` are no longer in the sample's views, data and sign-in page, and `useSearchDraft` is exported from `src/components/patterns/data-table` and no longer from `src/views/requests.tsx`. These files are a project's own once it is created, and an update never touches them, so a created project keeps its copies; a clone of the template that imported any of them drops the import, or imports `useSearchDraft` from the data table.
|
|
98
|
+
|
|
5
99
|
## [0.14.1] · 2026-10-12
|
|
6
100
|
|
|
7
101
|
### Fixed
|
package/payload/CONTRIBUTING.md
CHANGED
|
@@ -6,7 +6,7 @@ This repository is the single source of truth for how a Meridian dashboard looks
|
|
|
6
6
|
|
|
7
7
|
A new card passes two questions before anyone specifies it:
|
|
8
8
|
|
|
9
|
-
- **Useful.** Name the job in one sentence, the page that needs it now, and the principle it serves. A card for an imagined future need is not proposed.
|
|
9
|
+
- **Useful.** Name the job in one sentence, the page that needs it now, and the principle it serves. A card for an imagined future need is not proposed. An example built for a named scenario in the product is the page that needs it: say which scenario and which page draws it. A wrapper made only to fill a layer is not, and is not proposed.
|
|
10
10
|
- **Unique.** Nothing in the system already does the job. Look one layer down first: a page need is met by arranging patterns, a pattern need by composing components, a component need by combining tokens and base roles. Go down a layer only when the layer below truly cannot express it.
|
|
11
11
|
|
|
12
12
|
## Making something new
|
|
@@ -17,6 +17,8 @@ A new card passes two questions before anyone specifies it:
|
|
|
17
17
|
4. **Build the card**: a folder in its layer holding `index.tsx` (the component), `preview.tsx` (every state) and `README.md` (the specification).
|
|
18
18
|
5. **Check it** against "Done means" and run `pnpm gate`.
|
|
19
19
|
|
|
20
|
+
A new page is also added to `PAGES` in `src/system/content.ts`, which the Atlas reads: its `id`, `title`, `route` (an address that opens, so a detail page names a real record), `spec` (the page's `README.md`), and `embed` or `template` when it has one.
|
|
21
|
+
|
|
20
22
|
## Where things live
|
|
21
23
|
|
|
22
24
|
| Layer | Folder | Holds |
|
|
@@ -25,9 +27,10 @@ A new card passes two questions before anyone specifies it:
|
|
|
25
27
|
| 1 Base | `src/styles/base.css`, `src/styles/motion.css`, `src/components/base/` | Element defaults, text roles, motion, the shell, surfaces, the app mark |
|
|
26
28
|
| 2 Components | `src/components/ui/` | Single-purpose parts |
|
|
27
29
|
| 3 Patterns | `src/components/patterns/`, `src/components/charts/` | Components composed for one dashboard job |
|
|
28
|
-
| 4 Pages | `app/(dashboard)/`, `app/embed/`, `src/views/` | Routes (console and embed); a view shared by both lives in `src/views/` |
|
|
30
|
+
| 4 Pages | `app/(dashboard)/`, `app/embed/`, `src/views/`, `src/components/templates/` | Routes (console and embed); a view shared by both lives in `src/views/`; a page template is a card folder in `src/components/templates/` (listed in Pages as `template-<folder>`) |
|
|
31
|
+
| 5 Product workspaces | `src/app.config.ts`, the product's adapter in `src/views/` (`workspaces.ts`, `workspace-chrome.tsx`), `docs/product-workspaces.md` | How a product is assembled from its pages, declared in the configuration and written down in the product's guide: the one layer that is not a folder of components |
|
|
29
32
|
|
|
30
|
-
A product's data comes from `src/data/` (`collections.ts`, where actions and the assistant change records; `read.ts`, the pages' authorized cached read; `access.ts`, who may do what) and the modules beside it, and nowhere else; the sample (ZZ Meridian's own dashboard) that the Atlas, the previews and the template's sample pages read lives in `src/system/fixtures/`. Formatting comes from `src/lib/format.ts` and `src/lib/format-date.ts`. A component never imports data: it defines the types it renders (`ActivityEvent`, `Incident`, `Service`) and data conforms to them.
|
|
33
|
+
A product's data comes from `src/data/` (`collections.ts`, where actions and the assistant change records; `read.ts`, the pages' authorized cached read; `access.ts`, who may do what) and the modules beside it, and nowhere else; the sample (ZZ Meridian's own dashboard) that the Atlas, the previews and the template's sample pages read lives in `src/system/fixtures/`. Formatting comes from `src/lib/format.ts` and `src/lib/format-date.ts`. A page template arranges patterns for a kind of page and keeps no state; outside its `preview.tsx` it imports nothing from `@/data`, `@/views`, `@/system` or `@/app.config`. Managed code may ask the configuration only for `app`, `nav`, `slug` and the types `NavGroup` and `NavItem`, which every project's `src/app.config.ts` exports (`tests/template-consumers.test.tsx` holds this). Before a template is `beta`, each of the seven states (Normal, Loading, Empty, Filtered-empty, Error, Partial, Read-only) has a disposition in the `## States` table of its README, either `Covered:` naming the preview specimen that draws it, or `Inapplicable:` with the reason; `tests/atlas-template-contracts.test.tsx` holds the two to each other, and a new template's preview is added to it. A component never imports data: it defines the types it renders (`ActivityEvent`, `Incident`, `Service`) and data conforms to them.
|
|
31
34
|
|
|
32
35
|
## Card anatomy
|
|
33
36
|
|
|
@@ -75,7 +78,7 @@ Write the status on the line under the title: `Status: beta`.
|
|
|
75
78
|
- Under reduced motion it shows its final state with nothing moving.
|
|
76
79
|
- Keyboard: every control reachable, operable and visibly focused; focus order follows reading order.
|
|
77
80
|
- An agent's work in it is marked (Agent mark, "via" attribution, or a Proposal); no agent write happens without one.
|
|
78
|
-
- A page shares a context both agents read: its scope and freshness, every figure with its unit, change and definition, what code derived from them, and what the data cannot say, within 2,
|
|
81
|
+
- A page shares a context both agents read: its scope and freshness, every figure with its unit, change and definition, what code derived from them, and what the data cannot say, within 2,600 characters. A figure an agent would otherwise recompute is computed in the context.
|
|
79
82
|
|
|
80
83
|
## Tools
|
|
81
84
|
|
|
@@ -88,8 +91,9 @@ Write the status on the line under the title: `Status: beta`.
|
|
|
88
91
|
| `rm -rf .next` before the next build, after editing a card's `README.md` | A production build in a working `.next` can serve the prerendered copy of the old text while the client hydrates the new one, which React reports as error #418 on that Atlas page. `pnpm dev` has no such copy, and a fresh clone (CI, a deployment) has none either | After changing what a card's page says |
|
|
89
92
|
| `node scripts/check.ts` | Every card has its README and preview, every README follows the anatomy, no literal colour in components, every token a spec names exists and is written with its own value, every embed view a document offers is a route, and an element that claims a control's role has a key handler | Gate |
|
|
90
93
|
| `pnpm typecheck`, `pnpm test` | TypeScript and the behaviour tests | Gate |
|
|
91
|
-
| `node scripts/audit.ts` | With the app running: every page and embed view at 2560, 1440, 1024, 768 and 390px in both themes (embeds at 720 and 420 on a simulated host ground). It measures inside open shadow roots too. Fails on sideways scroll, text clipped without an ellipsis, a control with no accessible name, more than one page scroller, rendered text under its WCAG minimum, a Tab stop with no focus ring (a change of outline, shadow or border when focus arrives), a custom element it cannot look inside (`unmeasured:`), and any uncaught exception or console error. Prints the design metrics: type sizes, weights, radii and the hierarchy ratio | Before release |
|
|
92
|
-
| `node scripts/audit.ts --atlas` | With the app running: the same audit over the Design Atlas, every card's page and its bare preview,
|
|
94
|
+
| `node scripts/audit.ts` | With the app running: every page and embed view at 2560, 1440, 1024, 768 and 390px in both themes (embeds at 720 and 420 on a simulated host ground). It measures inside open shadow roots too. Fails on sideways scroll, text clipped without an ellipsis, text cut with an ellipsis that keeps the whole of it in no `title`, two pieces of text drawn in the same place or a stroked line drawn through one, a control with no accessible name, more than one page scroller, rendered text under its WCAG minimum (an SVG's text too, read by its `fill` against what the SVG paints behind it), a Tab stop with no focus ring (a change of outline, shadow or border when focus arrives), a custom element it cannot look inside (`unmeasured:`), and any uncaught exception or console error. Prints the design metrics: type sizes, weights, radii and the hierarchy ratio | Before release |
|
|
95
|
+
| `node scripts/audit.ts --atlas` | With the app running: the same audit over the Design Atlas, at 1440 and 390px in both themes: every card's page and its bare preview, and every other entry the Atlas serves (the token views, the page specs, the guides). Its first line says what it reached, by kind, and `--list` prints the routes without opening a browser. Not part of `pnpm verify`, which stays fast: a card's preview is where a defect in its own box shows, so run it after changing a component | After changing a component |
|
|
96
|
+
| `node scripts/vitals.ts [--atlas]` | With a production build running: LCP, INP and CLS on a mid-range phone for every static page, the Atlas's front page and one card page among them. `--atlas` measures every Atlas entry instead, about fifteen minutes, and is not part of `pnpm verify` | Inside `pnpm verify --full`; `--atlas` after changing what every card page shares |
|
|
93
97
|
| `node scripts/interactions.ts` | With the app running: presses every control on every page (mouse at 1440px, taps at 390px) and follows every link. Reaches controls inside open shadow roots. Fails on a control that changes nothing, one something covers, and a link that answers 4xx | Before release |
|
|
94
98
|
| `node scripts/keyboard.ts` | With the app running: Tab through each page until focus comes back round, then Shift+Tab back. Fails on a stop with no focus ring, a control hidden under the sticky bar, a visible control never reached, and a reverse order that differs | Before release |
|
|
95
99
|
| `pnpm verify` | The gate once, one production build, the route policy, the size checks and a smoke of up to three routes on desktop and the phone drawer; ends with a coverage line, and the report is in `out/verify.txt` | Before finishing any change |
|
|
@@ -98,6 +102,13 @@ Write the status on the line under the title: `Status: beta`.
|
|
|
98
102
|
| `node scripts/brand.ts --name … [--hue … --chroma …]` | Renames and rebrands a copy in place; a brand hue becomes an accent preset that holds contrast in every theme | Starting a project |
|
|
99
103
|
| `node scripts/shot.ts <routes> --width 1440,390 --theme light,dark [--full]` | Screenshots into `out/shots/` | While designing |
|
|
100
104
|
|
|
105
|
+
A check is code, and it is wrong in the ways code is. Before a new check, or a change to one, is trusted:
|
|
106
|
+
|
|
107
|
+
- **It fails on purpose first.** Plant the defect in a fixture page (`scripts/fixtures/deep/`), add its case and its corrected twin to `expected.json`, and watch the old check pass the broken page and the new one fail it. A check that has only ever been seen green is a hypothesis.
|
|
108
|
+
- **It is tried on a shape it was not built for.** Run it over every route it will ever meet (`audit.ts --atlas`, the product's own routes, an adopter's if one is at hand) before it becomes a failure, and read what it reports: each line is a defect to fix or a rule that was too wide. Two defects in one night were in the checks themselves, and both were found by somebody else's route.
|
|
109
|
+
- **It measures in one set of conditions.** Every check opens a page at 1x unless `DPR` says otherwise, by hand and inside `pnpm verify` alike; only `scripts/shot.ts` draws at 2x. Anything with a duration in it is measured throttled (`scripts/vitals.ts`), never by an unthrottled page opened by hand.
|
|
110
|
+
- **It says what it reached.** A run that visits fewer pages than its name promises prints that, or fails; it never goes on quietly with less.
|
|
111
|
+
|
|
101
112
|
## Releasing
|
|
102
113
|
|
|
103
114
|
1. Add an entry to `CHANGELOG.md` under a new version. Semver: a removed or renamed token, prop or card is major; a new card, token or variant is minor; a corrected value is a patch. Say in one line what breaks and what to do instead.
|
package/payload/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/zz-meridian)
|
|
4
4
|
|
|
5
|
-
ZZ Meridian (Meridian, for short) is the ZZ family's design system for dashboards, and a working template built on it: one set of tokens,
|
|
5
|
+
ZZ Meridian (Meridian, for short) is the ZZ family's design system for dashboards, and a working template built on it: six layers, from one set of tokens, through four layers of React components, to a product assembled from its pages, all of which hold at a desk, on a phone, and inside a conversation where an agent reads the dashboard with you.
|
|
6
6
|
|
|
7
7
|
It is written so a new dashboard starts from a running product, not a blank page: clone it, rename it in one command, add one data module, and the rails, charts, tables, states, themes and the agent surface are already there and already consistent. The Design Atlas at `/system` is the specification, read from this repository; the template at `/` is the proof.
|
|
8
8
|
|
|
@@ -22,7 +22,7 @@ These govern every layer. When two choices both fit the specs, pick the one that
|
|
|
22
22
|
|
|
23
23
|
**Build up, never sideways.** Everything is assembled from the layer beneath it. A new page is an arrangement of patterns; a new pattern is an arrangement of components; a new value is a last resort, named by its role.
|
|
24
24
|
|
|
25
|
-
## The
|
|
25
|
+
## The six layers
|
|
26
26
|
|
|
27
27
|
| Layer | What it holds | Rule | Where |
|
|
28
28
|
|---|---|---|---|
|
|
@@ -30,7 +30,8 @@ These govern every layer. When two choices both fit the specs, pick the one that
|
|
|
30
30
|
| 1 Base | The ground and its light, element defaults, text roles, motion, the shell, the surfaces | Uses only tokens | `src/styles/`, `src/components/base/` |
|
|
31
31
|
| 2 Components | Single-purpose parts: button, field, table, dialog, badge, tabs… | Never knows which page it is on | `src/components/ui/` |
|
|
32
32
|
| 3 Patterns | Components composed for one dashboard job: rail, featured metric, Meridian charts, data table, proposal | Uses only the layers below | `src/components/patterns/`, `src/components/charts/` |
|
|
33
|
-
| 4 Pages | Routes on the console and views in an MCP host | Adds no styling of its own | `app/(dashboard)/`, `app/embed/`, `src/views/` |
|
|
33
|
+
| 4 Pages | Routes on the console and views in an MCP host, and the templates pages share | Adds no styling of its own | `app/(dashboard)/`, `app/embed/`, `src/views/`, `src/components/templates/` |
|
|
34
|
+
| 5 Product workspaces | How a product is assembled from its pages, declared in the configuration and written down in the product's guide: the one layer that is not a folder of components | A workspace is where a person looks, never what they may do | `src/app.config.ts`, `src/views/workspaces.ts`, `src/views/workspace-chrome.tsx`, `docs/product-workspaces.md` |
|
|
34
35
|
|
|
35
36
|
A change at the bottom moves everything above it, and nothing above may invent a value of its own.
|
|
36
37
|
|
|
@@ -64,7 +65,9 @@ Dark is the default, written on `:root`; the light theme follows the operating s
|
|
|
64
65
|
| `tokens/` | Layer 0: palette, core, the two themes, four accents, the compact density, and the resolver |
|
|
65
66
|
| `src/styles/` | `tokens.css` and `theme.css` (generated), `base.css`, `motion.css` |
|
|
66
67
|
| `src/components/{base,ui,patterns,charts}/` | Layers 1 to 3: one folder per card, holding the component, its `README.md` specification and its `preview.tsx` |
|
|
68
|
+
| `src/components/templates/` | Layer 4: the arrangements pages share (the Records template), one folder per template with its `README.md` and `preview.tsx` |
|
|
67
69
|
| `app/(dashboard)/`, `app/embed/`, `app/sign-in/` | Layer 4: the template's routes, each with its page specification |
|
|
70
|
+
| `src/app.config.ts`, `src/views/workspaces.ts`, `src/views/workspace-chrome.tsx`, `docs/product-workspaces.md` | Layer 5: how a product is assembled from its pages, declared in the configuration and written down in the product's guide |
|
|
68
71
|
| `app/system/` | The Design Atlas |
|
|
69
72
|
| `src/data/` | Your product's data seam: `collections.ts` is where pages, actions and the assistant read and change records |
|
|
70
73
|
| `src/system/fixtures/` | The sample: ZZ Meridian's own dashboard, which the Atlas, the previews and the sample pages read |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Overview
|
|
2
2
|
|
|
3
|
-
The Overview answers "how is the API doing?" for a period
|
|
3
|
+
The Overview is Main's home: it answers "how is the API doing?" for a period, with one featured figure and its trend, the three figures that qualify it, where the traffic goes and how it was answered, and then summarises the whole product in three quieter regions, Business, Operation and Delivery, each with a way into its detail, before what changed.
|
|
4
4
|
|
|
5
5
|
Status: beta
|
|
6
6
|
|
|
@@ -8,36 +8,38 @@ Status: beta
|
|
|
8
8
|
|
|
9
9
|
| Row | Split | Pattern | Holds |
|
|
10
10
|
|---|---|---|---|
|
|
11
|
-
| Masthead | | Page frame | Kicker `ZZ Meridian · Production`, title, one sentence; freshness, period select (7D, 30D, 90D, All), Export |
|
|
11
|
+
| Masthead | | Page frame | Kicker `ZZ Meridian · Production`, title, one sentence that ends with who Main is for (`workspaces.main.audience`, read from the declaration); freshness, period select (7D, 30D, 90D, All), Export |
|
|
12
12
|
| 1 | 2/3 | Featured metric with a Trend chart (`fill` height) | Requests in the period at hero size, the change against the previous period, a caption with the daily average and the busiest day, requests per day as an area (errors have their own tile: one chart, one measure) |
|
|
13
13
|
| 1 | 1/3 | Three Metric tiles, stacked | Error rate, Latency p95 (both: down is good), Spend (neutral) |
|
|
14
14
|
| 2 | 1/2 | Card with a Bar list | The busiest endpoints, the top one in the accent; a footer link to Analytics |
|
|
15
15
|
| 2 | 1/2 | Card with a Composition bar and a Trend chart | Responses by status class; latency p95 per day |
|
|
16
|
-
| 3 |
|
|
16
|
+
| 3 | tiles (3) | Three Cards, each a labelled region: a Key value list and a footer of links | Business: customers, how many of each status and plan, and spend over 30 days; links to Customers and Analytics. Operation: the services out of service or degraded and each unresolved incident, or "Every service is operational and nothing is open."; a link to Operations. Delivery: open and waiting tickets, failing verifications, and live, ready and in-testing releases; links to Operations tickets, Tests and verification and Releases |
|
|
17
|
+
| 4 | full | Card with an Activity feed | The five latest changes, people, the system and agents |
|
|
17
18
|
|
|
18
|
-
Everything in rows 1 and 2 shares one Meridian: point at a day in either chart and the featured figure, the tiles and both charts read that day.
|
|
19
|
+
The figures in the regions are of the whole collection and are worked out by the functions their own pages use (`customerFigures`, `ticketCounts`, `latestRuns`, `releaseFigures`); a figure that has a filter on its page links to the list it counts (open tickets to the open filter), and one that has none is plain text. Everything in rows 1 and 2 shares one Meridian: point at a day in either chart and the featured figure, the tiles and both charts read that day.
|
|
19
20
|
|
|
20
|
-
Below 1024px every row stacks: the featured card first, then the tiles (two across from 34rem, one below), then the cards. At 390px the masthead's actions wrap under the title: the period select, then Export. A page's actions are offered on every width, as the request log's export is: a person on a phone may want the CSV too (issue #13).
|
|
21
|
+
Below 1024px every row stacks (the three regions are three across from 48rem of the row's own width, and one below it): the featured card first, then the tiles (two across from 34rem, one below), then the cards. At 390px the masthead's actions wrap under the title: the period select, then Export. A page's actions are offered on every width, as the request log's export is: a person on a phone may want the CSV too (issue #13).
|
|
21
22
|
|
|
22
23
|
## States
|
|
23
24
|
|
|
24
25
|
| State | What shows |
|
|
25
26
|
|---|---|
|
|
26
27
|
| Loading | `loading.tsx` beside the page, in the route group `app/(dashboard)/(overview)/` so that it is the Overview's own and not every console route's: skeletons in the shape of these rows, so nothing jumps on arrival. The masthead (kicker, title, sentence, freshness) renders at once on a navigation; the page reads `?period=` inside its own boundaries, so the period select, the export and the figures stream in behind it, and their fallbacks hold their space |
|
|
27
|
-
| Empty (no traffic yet) | The
|
|
28
|
+
| Empty (no traffic yet) | The traffic rows are one card that says "No requests yet" with a link to API keys; no tile or chart is drawn. The three regions still draw beneath it |
|
|
29
|
+
| Region unavailable | A region whose read failed says "The business summary is unavailable" (or operation, or delivery) and draws neither a figure nor a link; the other regions and the rest of the page are unaffected |
|
|
28
30
|
| Partial (a series missing) | That tile shows a dash and "Not measured"; the chart breaks its line over missing days |
|
|
29
31
|
| Stale | Freshness turns to warning: "Stale · updated 47 min ago" |
|
|
30
|
-
| Error | `error.tsx`, inside the shell at the data width, like every page: the title says the view did not load and nothing was changed; one card says retrying usually works, with Retry (primary), a second way out read from `nav` (Check
|
|
32
|
+
| Error | `error.tsx`, inside the shell at the data width, like every page: the title says the view did not load and nothing was changed; one card says retrying usually works, with Retry (primary), a second way out read from `nav` (Check Operations where the product has one, the home page where it does not), and the reference |
|
|
31
33
|
|
|
32
34
|
## Data
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
The regions come from `readMainSummary()` in `src/data/main.ts`, which reads every customer, service, incident, ticket, verification run and release through `read()`, and the pure functions in `src/views/main-summary.ts` that build each region from the rows. The page needs every grant it reads (`FEATURES.overview.needs`) and neither members nor keys: a person lacking one is refused before anything is read. The traffic rows come from `src/system/fixtures/sample.ts`: `demoSeries(period)` (one point per day, and the previous period for the deltas), `demoTotals(period)`, `ENDPOINTS`, `STATUS_MIX`, `ACTIVITY`. Error rate counts 5xx and 429 over all requests; latency is the median of daily p95s; spend is metered usage before credits.
|
|
35
37
|
|
|
36
38
|
## Surfaces
|
|
37
39
|
|
|
38
40
|
- **Console**: as above.
|
|
39
41
|
- **Mobile**: one column; the featured chart is 220px tall; tiles two across or one.
|
|
40
|
-
- **Embed**: `/embed/overview` (tool `zz_meridian_overview { period }`). Inline: the three main tiles and the requests trend, with Ask on the chart. Fullscreen: these rows under the embed head.
|
|
42
|
+
- **Embed**: `/embed/overview` (tool `zz_meridian_overview { period }`). Inline: the three main tiles and the requests trend, with Ask on the chart. Fullscreen: these rows, the three regions among them, under the embed head.
|
|
41
43
|
|
|
42
44
|
## Agents
|
|
43
45
|
|
|
@@ -54,6 +56,8 @@ Unknown:
|
|
|
54
56
|
- Nothing is recorded between 20 Sept and 23 Sept: no incident and no activity event, so nothing on this page explains the rise on 21 and 22 Sept.
|
|
55
57
|
```
|
|
56
58
|
|
|
59
|
+
It also carries Main's figures under the labels their pages use, as plain numbers: `Customers`, with `By status`, `By plan` and `Spend, last 30 days` as the Business region draws them, `Open tickets`, `Waiting tickets`, `Failing now`, `Live versions`, `Ready to release` and `In testing`, and which services are out of service or degraded and which incidents are unresolved (or that every service is operational and none is open). A region that could not be read is told as unknown, missing and not zero.
|
|
60
|
+
|
|
57
61
|
It also says which endpoint contributes most errors, that requests follow the week, what the status classes mean against the error rate's definition, which incident sits beside a spike when one does (in 90 days: "Upload failures for files over 50 MB"), and that today is partial.
|
|
58
62
|
|
|
59
63
|
- **MCP App:** the text above as `ui/update-model-context`, with the same context as its structured part, from the inline view and from fullscreen alike, on load and on every change.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Card } from '@/components/ui/card';
|
|
2
2
|
import { Row } from '@/components/base/shell';
|
|
3
3
|
import { Skeleton } from '@/components/ui/skeleton';
|
|
4
|
-
import { LoadingPage } from '
|
|
4
|
+
import { LoadingPage } from '@/components/patterns/page-loading';
|
|
5
5
|
|
|
6
6
|
/** The rows of the Overview body, in its shape: the page streams them in behind its masthead, and a cold load shows them under the masthead skeleton. */
|
|
7
7
|
export function OverviewSkeleton() {
|
|
@@ -32,6 +32,14 @@ export function OverviewSkeleton() {
|
|
|
32
32
|
</Card>
|
|
33
33
|
))}
|
|
34
34
|
</Row>
|
|
35
|
+
<Row split="tiles">
|
|
36
|
+
{[0, 1, 2].map((i) => (
|
|
37
|
+
<Card key={i} className="gap-4 p-(--card-pad)">
|
|
38
|
+
<Skeleton className="h-4 w-28" />
|
|
39
|
+
{[0, 1, 2, 3].map((j) => <Skeleton key={j} className="h-3" />)}
|
|
40
|
+
</Card>
|
|
41
|
+
))}
|
|
42
|
+
</Row>
|
|
35
43
|
<Row>
|
|
36
44
|
<Card className="gap-4 p-(--card-pad)">
|
|
37
45
|
<Skeleton className="h-4 w-32" />
|
|
@@ -42,7 +50,7 @@ export function OverviewSkeleton() {
|
|
|
42
50
|
);
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
/** Overview on its way: the featured figure beside three tiles, two cards of rows, and the activity feed. */
|
|
53
|
+
/** Overview on its way: the featured figure beside three tiles, two cards of rows, the three regions, and the activity feed. */
|
|
46
54
|
export default function OverviewLoading() {
|
|
47
55
|
return (
|
|
48
56
|
<LoadingPage name="overview" title="Overview">
|
|
@@ -10,8 +10,8 @@ import { DEMO_NOW, DEMO_UPDATED_AT } from '@/data/sample';
|
|
|
10
10
|
import { overviewTool } from '@/views/tools';
|
|
11
11
|
import { cache } from 'react';
|
|
12
12
|
import { parsePeriod } from '@/lib/period';
|
|
13
|
-
import { app } from '@/app.config';
|
|
14
|
-
import { Busy } from '
|
|
13
|
+
import { app, workspaces } from '@/app.config';
|
|
14
|
+
import { Busy } from '@/components/patterns/page-loading';
|
|
15
15
|
import { OverviewSkeleton } from './loading';
|
|
16
16
|
|
|
17
17
|
export const metadata = { title: 'Overview' };
|
|
@@ -29,7 +29,7 @@ export default async function OverviewPage({ searchParams }: { searchParams: Sea
|
|
|
29
29
|
<PageFrame
|
|
30
30
|
kicker={<>{app.name} · {app.workspace}</>}
|
|
31
31
|
title="Overview"
|
|
32
|
-
description=
|
|
32
|
+
description={`Traffic, reliability and spend across every endpoint, for the period you choose. ${workspaces.main.audience}`}
|
|
33
33
|
meta={<Freshness updatedAt={DEMO_UPDATED_AT} now={DEMO_NOW} />}
|
|
34
34
|
actions={
|
|
35
35
|
<Suspense fallback={<Skeleton className="h-9 w-48 rounded-md" />}>
|