@tryvitris/website-harness 1.0.1 → 1.1.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 +76 -74
- package/agent/graph.yaml +769 -756
- package/agent/refs/admin.md +11 -11
- package/agent/refs/browser.md +6 -2
- package/agent/refs/business.md +20 -9
- package/agent/refs/compose.md +2 -0
- package/agent/refs/design-quality.md +2 -0
- package/agent/refs/direction.md +2 -0
- package/agent/refs/implement.md +8 -4
- package/agent/refs/scheduling.md +13 -0
- package/agent/refs/template-adaptation.md +2 -0
- package/agent/refs/template-library.md +2 -0
- package/agent/refs/typography.md +15 -0
- package/bin/api/agent/evaluate.js +7 -6
- package/bin/api/agent/grade.js +7 -6
- package/bin/api/cli/api.js +15 -14
- package/bin/api/cli.js +17 -16
- package/bin/api/engine.js +8 -7
- package/bin/api/hosting.js +9 -8
- package/bin/api/index.js +4 -3
- package/bin/api/render.js +2 -1
- package/bin/api/theme.js +2 -1
- package/bin/chunks/{chunk-M72KKZIB.js → chunk-5KRJQKHB.js} +4 -3
- package/bin/chunks/{chunk-72Y4YMTV.js → chunk-AD7WAHW2.js} +2 -2
- package/bin/chunks/{chunk-RE4GOHSU.js → chunk-AS74YXGM.js} +3 -3
- package/bin/chunks/{chunk-AUO5I56E.js → chunk-C3F7XQD2.js} +194 -1
- package/bin/chunks/{chunk-445FZZEC.js → chunk-DDJ3QCRR.js} +9 -9
- package/bin/chunks/{chunk-LKN75JGA.js → chunk-GE6UUEG2.js} +13 -8
- package/bin/chunks/chunk-GPS3SAGQ.js +1020 -0
- package/bin/chunks/{chunk-37F7XIMH.js → chunk-JTXFTNEA.js} +6 -1
- package/bin/chunks/{chunk-JBSGJR7X.js → chunk-KR5ZUZV2.js} +2 -2
- package/bin/chunks/{chunk-65LRZIBP.js → chunk-MAPY3YVE.js} +2 -2
- package/bin/chunks/{chunk-VKU5TY5C.js → chunk-MYX4FET4.js} +1 -1
- package/bin/chunks/{chunk-54QPYUM2.js → chunk-OZD7ZWDX.js} +1 -1
- package/bin/chunks/chunk-S2XVNTQP.js +90 -0
- package/bin/chunks/{chunk-UR4NWYVW.js → chunk-SBOUSO3S.js} +3 -3
- package/bin/chunks/{chunk-KIWEFRNG.js → chunk-SXPLCCO7.js} +6 -6
- package/bin/chunks/{chunk-H3BS2EBR.js → chunk-WIT6364Y.js} +4 -4
- package/bin/chunks/{chunk-GQPUILFR.js → chunk-WK7HJI5H.js} +1301 -1252
- package/bin/chunks/{design-context-store-ETVS4N6W.js → design-context-store-PGXCHUOF.js} +7 -6
- package/bin/chunks/{packets-XFXKHSEY.js → packets-B7MCJ42T.js} +7 -6
- package/bin/chunks/{preparation-3OR7LVPQ.js → preparation-MVKICQPD.js} +7 -6
- package/bin/cloudflare/backend.js +90 -11
- package/bin/types/src/agent/browser-typography.d.ts +33 -0
- package/bin/types/src/agent/theme-suite-identity.d.ts +1 -0
- package/bin/types/src/agent/typography-policy.d.ts +6 -0
- package/bin/types/src/build/index.d.ts +2 -0
- package/bin/types/src/cli/admin.d.ts +1 -1
- package/bin/types/src/compile/site.d.ts +1 -1
- package/bin/types/src/scheduling/config.d.ts +11 -0
- package/bin/types/src/scheduling/management.d.ts +2 -0
- package/bin/types/src/scheduling/styles.d.ts +1 -0
- package/bin/types/src/scheduling/widget.d.ts +2 -0
- package/bin/types/src/theme.d.ts +3 -1
- package/bin/types/src/types.d.ts +6 -1
- package/bin/types/src/typography.d.ts +27 -0
- package/bin/vitris-art.js +7 -6
- package/bin/vitris-build.js +5 -4
- package/bin/vitris-create-site.js +9 -8
- package/bin/vitris-cursor-hook.js +7 -6
- package/bin/vitris-eval.js +9 -8
- package/bin/vitris-go.js +9 -8
- package/bin/vitris-harvest.js +8 -7
- package/bin/vitris-knowledge.js +8 -7
- package/bin/vitris-launch-preflight.js +7 -6
- package/bin/vitris-motion-view.js +7 -6
- package/bin/vitris-references.js +5 -4
- package/bin/vitris-refresh.js +8 -7
- package/bin/vitris-stock.js +1 -1
- package/bin/vitris-templates.js +91 -1
- package/bin/vitris-themes.js +7 -6
- package/bin/vitris-upgrade.js +8 -7
- package/bin/vitris-verify-browser.js +33 -8
- package/bin/vitris-verify-performance.js +5 -4
- package/bin/vitris.js +17 -16
- package/design-system/MASTER.md +4 -0
- package/docs/ADMIN.md +85 -85
- package/docs/CLI.md +314 -308
- package/docs/SCHEDULING.md +67 -0
- package/package.json +5 -4
- package/templates/site/site.json +1 -0
- package/templates/site/theme/defaults.json +78 -17
- package/bin/chunks/chunk-L44ZTNDM.js +0 -332
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
motionCompositionPolicy,
|
|
11
11
|
observeMotion,
|
|
12
12
|
templateMotionHash,
|
|
13
|
+
typographyPolicy,
|
|
13
14
|
validateTemplateMotionPlan
|
|
14
|
-
} from "./chunks/chunk-
|
|
15
|
+
} from "./chunks/chunk-WK7HJI5H.js";
|
|
15
16
|
import "./chunks/chunk-KVMZSQ3D.js";
|
|
16
17
|
import "./chunks/chunk-WOORR67N.js";
|
|
17
18
|
import "./chunks/chunk-PRLL7B63.js";
|
|
@@ -26,7 +27,12 @@ import {
|
|
|
26
27
|
hasAnalyticsRuntime,
|
|
27
28
|
productionQuality
|
|
28
29
|
} from "./chunks/chunk-M2ETYSVJ.js";
|
|
29
|
-
import
|
|
30
|
+
import {
|
|
31
|
+
TYPOGRAPHY_SCENARIO,
|
|
32
|
+
captureTypography,
|
|
33
|
+
captureTypographyStates,
|
|
34
|
+
typographyConsistencyFailures
|
|
35
|
+
} from "./chunks/chunk-C3F7XQD2.js";
|
|
30
36
|
import "./chunks/chunk-BIEDSEL2.js";
|
|
31
37
|
import {
|
|
32
38
|
measurePerformance,
|
|
@@ -37,11 +43,14 @@ import {
|
|
|
37
43
|
} from "./chunks/chunk-WEFDVXFX.js";
|
|
38
44
|
import {
|
|
39
45
|
buildVerified
|
|
40
|
-
} from "./chunks/chunk-
|
|
41
|
-
import
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
} from "./chunks/chunk-WIT6364Y.js";
|
|
47
|
+
import {
|
|
48
|
+
loadSitePages
|
|
49
|
+
} from "./chunks/chunk-GPS3SAGQ.js";
|
|
50
|
+
import "./chunks/chunk-JTXFTNEA.js";
|
|
51
|
+
import "./chunks/chunk-GE6UUEG2.js";
|
|
44
52
|
import "./chunks/chunk-YYPYYOEV.js";
|
|
53
|
+
import "./chunks/chunk-S2XVNTQP.js";
|
|
45
54
|
import "./chunks/chunk-GZDLXYZV.js";
|
|
46
55
|
import "./chunks/chunk-G7MNMULR.js";
|
|
47
56
|
import {
|
|
@@ -340,6 +349,7 @@ var arg = (key) => {
|
|
|
340
349
|
};
|
|
341
350
|
var root = resolve(arg("--site") || ".");
|
|
342
351
|
var representative = process.argv.includes("--representative");
|
|
352
|
+
var typePolicy = typographyPolicy(root);
|
|
343
353
|
var concurrency = Number(arg("--concurrency") || process.env.VITRIS_BROWSER_CONCURRENCY || "1");
|
|
344
354
|
if (!Number.isInteger(concurrency) || concurrency < 1 || concurrency > 4) throw new Error("--concurrency must be an integer from 1 to 4");
|
|
345
355
|
var extraViewports = (arg("--extra-viewports") || "").split(",").filter(Boolean).map(Number);
|
|
@@ -373,10 +383,14 @@ var checks = [];
|
|
|
373
383
|
var routeChecks = [];
|
|
374
384
|
var compositionTargets = [];
|
|
375
385
|
var pagesToCheck = plan.pages.filter((p) => !representative || p.path === "/" || p.representative);
|
|
386
|
+
if (typePolicy.enabled && !representative) {
|
|
387
|
+
for (const page of await loadSitePages(root)) if (!pagesToCheck.some((p) => p.path === page.path)) pagesToCheck.push({ path: page.path, sections: [], purpose: "Authored route", representative: false });
|
|
388
|
+
}
|
|
376
389
|
if (!representative && existsSync(join2(dist, "cookie-compliance/index.html")) && !pagesToCheck.some((p) => p.path === "/cookie-compliance")) pagesToCheck.push({ path: "/cookie-compliance" });
|
|
377
390
|
var consentChecks = [];
|
|
378
391
|
var temporalMotion = [];
|
|
379
392
|
var routeJourneys = [];
|
|
393
|
+
var typography = [];
|
|
380
394
|
try {
|
|
381
395
|
try {
|
|
382
396
|
const chromium = await loadPlaywrightChromium();
|
|
@@ -424,6 +438,7 @@ try {
|
|
|
424
438
|
let firstVisit;
|
|
425
439
|
const cookieBanner = page.locator("#vitris-cookie-banner");
|
|
426
440
|
if (await cookieBanner.isVisible()) {
|
|
441
|
+
if (typePolicy.enabled) typography.push(await captureTypography(page, typePolicy.theme, p.path, width, "first-visit"));
|
|
427
442
|
if (p.path === "/") {
|
|
428
443
|
const path = `${directory}/first-visit-${width}${motion === "reduce" ? "" : "-normal"}.png`;
|
|
429
444
|
await page.screenshot({ path: join2(root, path) });
|
|
@@ -484,6 +499,7 @@ try {
|
|
|
484
499
|
await page.evaluate(`(async () => { await Promise.race([Promise.all([...document.images].filter(i=>i.getClientRects().length).map(i=>{i.loading='eager';return i.decode().catch(()=>{});})),new Promise(r=>setTimeout(r,5000))]); })()`);
|
|
485
500
|
const broken = await page.evaluate(`Array.from(document.images).filter(i=>i.getClientRects().length&&(!i.complete||!i.naturalWidth)).map(i=>i.getAttribute('src'))`);
|
|
486
501
|
failures.push(...broken.map((src) => `Broken tab image: ${src}`));
|
|
502
|
+
if (typePolicy.enabled) typography.push(await captureTypography(page, typePolicy.theme, p.path, width, `tab-${i}`));
|
|
487
503
|
}
|
|
488
504
|
if (await tabs.count()) await tabs.first().click();
|
|
489
505
|
for (const carousel of await page.locator('[data-island="carousel"]').all()) {
|
|
@@ -567,6 +583,7 @@ try {
|
|
|
567
583
|
const slug = p.path === "/" ? "home" : p.path.replace(/^\//, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/-+$/, "") || "home";
|
|
568
584
|
const screenshot = `${directory}/${slug}-${width}${motion === "reduce" ? "" : "-normal"}.png`;
|
|
569
585
|
await page.evaluate("window.VitrisMotion?.dispose(); window.VitrisCounters?.dispose()");
|
|
586
|
+
if (typePolicy.enabled) typography.push(...await captureTypographyStates(page, typePolicy.theme, p.path, width));
|
|
570
587
|
const unreadableMotion = await page.locator("[data-motion],[data-reveal],[data-motion-child],[data-reveal-child]").evaluateAll((es) => es.filter((e) => Number(getComputedStyle(e).opacity) < 0.99).map((e) => e.id || e.tagName));
|
|
571
588
|
if (unreadableMotion.length) failures.push(`Motion teardown left final static content hidden: ${unreadableMotion.join(", ")}`);
|
|
572
589
|
await page.evaluate("window.scrollTo(0,0)");
|
|
@@ -627,6 +644,10 @@ try {
|
|
|
627
644
|
}
|
|
628
645
|
const statusBefore = await page.locator('[role="status"]:visible').allTextContents();
|
|
629
646
|
const postsBefore = mockedLeadPosts;
|
|
647
|
+
if (typePolicy.enabled && !await form.evaluate((e) => e.checkValidity())) {
|
|
648
|
+
await form.locator("button[type=submit],input[type=submit],button:not([type])").first().click({ timeout: 3e3 });
|
|
649
|
+
typography.push(await captureTypography(page, typePolicy.theme, p.path, width, "form-invalid"));
|
|
650
|
+
}
|
|
630
651
|
let supported = true;
|
|
631
652
|
for (const field of await form.locator("input:not([type=hidden]):not([type=submit]),textarea,select").all()) {
|
|
632
653
|
const type = (await field.getAttribute("type") || "text").toLowerCase();
|
|
@@ -660,6 +681,7 @@ try {
|
|
|
660
681
|
const next = page.url();
|
|
661
682
|
const statusAfter = await page.locator('[role="status"]:visible').allTextContents();
|
|
662
683
|
const newStatus = statusAfter.some((text) => text.trim() && !statusBefore.includes(text));
|
|
684
|
+
if (typePolicy.enabled) typography.push(await captureTypography(page, typePolicy.theme, p.path, width, "form-result"));
|
|
663
685
|
if (mockedLeadPosts === postsBefore) failures.push("Lead form did not reach the mocked POST /api/leads adapter");
|
|
664
686
|
else if (!newStatus && !(new URL(next).searchParams.get("sent") && next !== before)) failures.push("Lead form has no new success state or destination");
|
|
665
687
|
} catch (error) {
|
|
@@ -680,8 +702,11 @@ try {
|
|
|
680
702
|
const compare = (a, b) => a.path.localeCompare(b.path) || a.width - b.width || String(a.motion || a.mode).localeCompare(String(b.motion || b.mode));
|
|
681
703
|
checks.sort(compare);
|
|
682
704
|
routeJourneys.sort(compare);
|
|
705
|
+
typography.sort((a, b) => compare(a, b) || a.state.localeCompare(b.state));
|
|
706
|
+
const typographyFailures = typePolicy.enabled ? [.../* @__PURE__ */ new Set([...typography.flatMap((c) => c.failures.map((f) => `${c.path} ${c.width}px ${c.state}: ${f}`)), ...typographyConsistencyFailures(typography)])] : [];
|
|
707
|
+
for (const failure of typographyFailures) console.log(`Typography: ${failure}`);
|
|
683
708
|
const templateMotion = existsSync(join2(root, TEMPLATE_MOTION_PATH)) ? await captureTemplateMotion(browser, root, `http://127.0.0.1:${address.port}/`, directory + "/template-motion", json(join2(root, TEMPLATE_MOTION_PATH))) : void 0;
|
|
684
|
-
save(join2(root, `agent/reviews/${name}.json`), { version: 4, sourceHash: sourceHash(root), distHash: fingerprint(root, ["dist"]), scope: name, concurrency, viewports, motionModes, staticMotionState: "complete-after-teardown", motionScenarioHash: MOTION_SCENARIO_HASH, temporalMotion, routeJourneys, checks, routeChecks, consentChecks, ...templateMotion ? { templateMotion } : {}, ...compositionMode ? { compositionTargets } : {} });
|
|
709
|
+
save(join2(root, `agent/reviews/${name}.json`), { version: 4, sourceHash: sourceHash(root), distHash: fingerprint(root, ["dist"]), scope: name, concurrency, viewports, motionModes, staticMotionState: "complete-after-teardown", motionScenarioHash: MOTION_SCENARIO_HASH, temporalMotion, routeJourneys, checks, routeChecks, consentChecks, ...typePolicy.enabled ? { typography: { scenario: TYPOGRAPHY_SCENARIO, captures: typography, failures: typographyFailures } } : {}, ...templateMotion ? { templateMotion } : {}, ...compositionMode ? { compositionTargets } : {} });
|
|
685
710
|
if (!representative && performancePolicy(root).enabled) {
|
|
686
711
|
const performance = await measurePerformance(root, `http://127.0.0.1:${address.port}`, { log: console.log });
|
|
687
712
|
if (performance.failures.length) {
|
|
@@ -690,7 +715,7 @@ try {
|
|
|
690
715
|
}
|
|
691
716
|
}
|
|
692
717
|
console.log(`${name}: ${checks.length} route/viewport checks`);
|
|
693
|
-
if (checks.some((c) => c.failures.length) || routeJourneys.some((j) => j.failures.length) || templateMotion?.captures.some((c) => c.failures.length)) process.exitCode = 1;
|
|
718
|
+
if (typographyFailures.length || checks.some((c) => c.failures.length) || routeJourneys.some((j) => j.failures.length) || templateMotion?.captures.some((c) => c.failures.length)) process.exitCode = 1;
|
|
694
719
|
} finally {
|
|
695
720
|
await browser?.close();
|
|
696
721
|
await new Promise((r) => server.close(() => r()));
|
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
} from "./chunks/chunk-WEFDVXFX.js";
|
|
8
8
|
import {
|
|
9
9
|
buildVerified
|
|
10
|
-
} from "./chunks/chunk-
|
|
11
|
-
import "./chunks/chunk-
|
|
12
|
-
import "./chunks/chunk-
|
|
13
|
-
import "./chunks/chunk-
|
|
10
|
+
} from "./chunks/chunk-WIT6364Y.js";
|
|
11
|
+
import "./chunks/chunk-GPS3SAGQ.js";
|
|
12
|
+
import "./chunks/chunk-JTXFTNEA.js";
|
|
13
|
+
import "./chunks/chunk-GE6UUEG2.js";
|
|
14
14
|
import "./chunks/chunk-YYPYYOEV.js";
|
|
15
|
+
import "./chunks/chunk-S2XVNTQP.js";
|
|
15
16
|
import "./chunks/chunk-GZDLXYZV.js";
|
|
16
17
|
import "./chunks/chunk-T6PE4KN4.js";
|
|
17
18
|
import "./chunks/chunk-7FS67SRF.js";
|
package/bin/vitris.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunks/chunk-
|
|
5
|
-
import "./chunks/chunk-
|
|
6
|
-
import "./chunks/chunk-
|
|
7
|
-
import "./chunks/chunk-
|
|
8
|
-
import "./chunks/chunk-
|
|
9
|
-
import "./chunks/chunk-
|
|
4
|
+
} from "./chunks/chunk-DDJ3QCRR.js";
|
|
5
|
+
import "./chunks/chunk-SXPLCCO7.js";
|
|
6
|
+
import "./chunks/chunk-AD7WAHW2.js";
|
|
7
|
+
import "./chunks/chunk-SBOUSO3S.js";
|
|
8
|
+
import "./chunks/chunk-KR5ZUZV2.js";
|
|
9
|
+
import "./chunks/chunk-5KRJQKHB.js";
|
|
10
10
|
import "./chunks/chunk-2NW7KSPA.js";
|
|
11
|
-
import "./chunks/chunk-
|
|
12
|
-
import "./chunks/chunk-
|
|
13
|
-
import "./chunks/chunk-
|
|
11
|
+
import "./chunks/chunk-MYX4FET4.js";
|
|
12
|
+
import "./chunks/chunk-AS74YXGM.js";
|
|
13
|
+
import "./chunks/chunk-MAPY3YVE.js";
|
|
14
14
|
import "./chunks/chunk-34H54KEY.js";
|
|
15
15
|
import "./chunks/chunk-JCRGE6AL.js";
|
|
16
16
|
import "./chunks/chunk-XL2DRAYI.js";
|
|
17
|
-
import "./chunks/chunk-
|
|
17
|
+
import "./chunks/chunk-OZD7ZWDX.js";
|
|
18
18
|
import "./chunks/chunk-XGTHGWPW.js";
|
|
19
|
-
import "./chunks/chunk-
|
|
19
|
+
import "./chunks/chunk-WK7HJI5H.js";
|
|
20
20
|
import "./chunks/chunk-KVMZSQ3D.js";
|
|
21
21
|
import "./chunks/chunk-WOORR67N.js";
|
|
22
22
|
import "./chunks/chunk-PRLL7B63.js";
|
|
@@ -26,15 +26,16 @@ import "./chunks/chunk-M4LNHYYR.js";
|
|
|
26
26
|
import "./chunks/chunk-TY33XMHW.js";
|
|
27
27
|
import "./chunks/chunk-HL6JVA4A.js";
|
|
28
28
|
import "./chunks/chunk-M2ETYSVJ.js";
|
|
29
|
-
import "./chunks/chunk-
|
|
29
|
+
import "./chunks/chunk-C3F7XQD2.js";
|
|
30
30
|
import "./chunks/chunk-BIEDSEL2.js";
|
|
31
31
|
import "./chunks/chunk-IKNMBGGD.js";
|
|
32
32
|
import "./chunks/chunk-WEFDVXFX.js";
|
|
33
|
-
import "./chunks/chunk-
|
|
34
|
-
import "./chunks/chunk-
|
|
35
|
-
import "./chunks/chunk-
|
|
36
|
-
import "./chunks/chunk-
|
|
33
|
+
import "./chunks/chunk-WIT6364Y.js";
|
|
34
|
+
import "./chunks/chunk-GPS3SAGQ.js";
|
|
35
|
+
import "./chunks/chunk-JTXFTNEA.js";
|
|
36
|
+
import "./chunks/chunk-GE6UUEG2.js";
|
|
37
37
|
import "./chunks/chunk-YYPYYOEV.js";
|
|
38
|
+
import "./chunks/chunk-S2XVNTQP.js";
|
|
38
39
|
import "./chunks/chunk-GZDLXYZV.js";
|
|
39
40
|
import "./chunks/chunk-G7MNMULR.js";
|
|
40
41
|
import "./chunks/chunk-T6PE4KN4.js";
|
package/design-system/MASTER.md
CHANGED
|
@@ -74,3 +74,7 @@ New authored sites and reauthored routes compose the entire temporal journey wit
|
|
|
74
74
|
Used-component CSS only, islands only when needed. Give the actual LCP image priority when there is one; lazy-load images outside the initial view. Avoid `transition: all`.
|
|
75
75
|
|
|
76
76
|
New builds use original Vitris SVG icons through paired `<vitris-icon name="phone"></vitris-icon>` tags. Read `agent/refs/icons.md`; use `vitris-icons inspect NAME --json` for supported families and sizing. Choose by meaning, keep a coherent family and visible service labels, and verify actual desktop/mobile rendering. The compiler emits only the geometry used by each route, with no client catalog. Studio industry support is curated to eight symbols; never synthesize unavailable variants.
|
|
77
|
+
|
|
78
|
+
## Shared typography for new sites
|
|
79
|
+
|
|
80
|
+
New redesigns enable site.json typography version 1. Follow agent/refs/typography.md: shared size, weight, leading, tracking and font roles in theme/defaults.json; 18px body, 16px controls, 14px supporting text and a 12px rendered floor. Ordinary H3s share one responsive treatment across pages. Named, justified role variants preserve deliberate hierarchy. Browser acceptance checks every authored route and exposed interaction states. Historical sites retain their styles until an explicit reviewed adoption; kit upgrades never enable this automatically.
|
package/docs/ADMIN.md
CHANGED
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
# Separate business admin
|
|
2
|
-
|
|
3
|
-
The business owns a separate installation, accounts, D1 database, R2 bucket and Worker at `admin.customer-domain.com`. No central Vitris login is required. Pages, Media, page builders and arbitrary HTML editing are outside scope. The public website keeps its forms, backend origin, tenant identity, consent behavior and release gates.
|
|
4
|
-
|
|
5
|
-
The harness pins `@tryvitris/website-admin-harness` and `@tryvitris/website-admin-contracts` to **0.1.0-alpha.
|
|
6
|
-
|
|
7
|
-
## Local preparation
|
|
8
|
-
|
|
9
|
-
New websites may opt into next-step instructions with `vitris site create ... --admin` or `vitris-create-site ... --admin`. This creates `ADMIN-SETUP.md` only. Continue prepare → `agent/CURRENT.md` → work → complete and stop at verified local preview as usual. Existing websites can run setup directly.
|
|
10
|
-
|
|
11
|
-
Inspect the site's launch configuration, forms, exact public origin, and actual backend tenant ID. A slug is not proof of tenant identity. Prepare the sibling admin with:
|
|
12
|
-
|
|
13
|
-
```sh
|
|
14
|
-
vitris admin setup --site ../acme --dir ../acme-admin --domain admin.customer-domain.com --website https://www.customer-domain.com --tenant EXISTING_TENANT_ID --backend-origin https://BACKEND --plan
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Equivalent standalone published command:
|
|
18
|
-
|
|
19
|
-
```sh
|
|
20
|
-
npx --yes @tryvitris/website-admin-harness@0.1.0-alpha.
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Review `../acme/artifacts/admin/setup-plan.json`, including its exact directories, origins, tenant, scopes, package version and launch digest. Set a new dedicated `VITRIS_ADMIN_API_TOKEN` in the operator environment: 32–200 URL-safe letters/digits/underscores/hyphens generated by a secure secret manager. Repeat the identical command with `--apply` replacing `--plan`. Never pass the credential as a command argument or print it. `--lead-write` must appear in both commands only if lead status updates are explicitly wanted; default scopes are `leads:read` and `analytics:read`.
|
|
24
|
-
|
|
25
|
-
Apply brands the installation from `launch.json.name`, writes public `launch.json.admin` metadata, and saves only its SHA-256 hash in the admin's ignored `.vitris/backend-grant.json`. Existing forms, backend URL and workflow progress stay intact. Matching retries are no-ops; changed launch bytes/plans, conflicting destinations/connections or changed authority require a new review. Do not delete setup receipts or use force to bypass conflicts. Keep `artifacts/`, `.vitris/`, `.env*`, `.dev.vars*`, `dist/` and local release bundles ignored. No tenant, cloud resource, account, secret, DNS record or deployment is created by setup.
|
|
26
|
-
|
|
27
|
-
Inside the admin, install dependencies and retain its lockfile. Read `agent/WORKFLOW.md`, `agent/refs/harness.md`, and `agent/refs/cloudflare.md` before changing the installation. `npm run db:local` and `npm run dev` use a separate local manifest/database with synthetic demo data. They do not establish production backend access or working email authentication.
|
|
28
|
-
|
|
29
|
-
## Backend compatibility and grants
|
|
30
|
-
|
|
31
|
-
This source adds the admin v1 API. **An already deployed website backend may still run an older revision.** Setup does not upgrade it. Identify its owner, deployed revision, schema and hostname; perform any backend release as a separately reviewed operation. Never deploy or migrate a shared backend as an admin setup side effect. No production endpoint or credential is included in this public repository, so an existing deployment's compatibility must be checked per installation.
|
|
32
|
-
|
|
33
|
-
`vitris admin status --site ../acme` reports local metadata only. After the grant is installed, use the dedicated credential in the operator environment to check the deployed API:
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
vitris admin verify --site ../acme --dir ../acme-admin
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Verify requires v1 identity matching installation, tenant and site origin, both read scopes, and valid production leads, test submissions and analytics responses. It reports counts only; zero rows are valid. It fails on missing routes, rejected/revoked credentials, identity mismatch, malformed records, redirects or transport failure. It does not certify collection, email, account login or deployment. The admin's published adapter likewise surfaces failures without substituting demo records.
|
|
40
|
-
|
|
41
|
-
The backend secret `ADMIN_CONNECTIONS` is a COMPLETE installation-keyed map. Each grant contains only `tenantId`, exact HTTPS `siteOrigin`, `tokenHash` and `scopes`. Retrieve the authoritative full map from the backend owner's secure configuration; Wrangler cannot recover a secret's value. For a confirmed brand-new backend only, initialize a private ignored file with `{}`. Do not treat an unknown existing map as empty.
|
|
42
|
-
|
|
43
|
-
```sh
|
|
44
|
-
vitris admin grant --dir ../acme-admin --grants PRIVATE_COMPLETE_MAP.json --out NEW_PRIVATE_MERGED_MAP.json
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
This local command preserves other grants, validates hashed entries and installation identity, refuses differing existing authority and refuses overwriting the output. Store input/output in ignored operator storage. Review the entire merged map before explicitly uploading it to the intended backend as `ADMIN_CONNECTIONS` via Wrangler secret stdin. Rotation/revocation is a separate reviewed complete-map update: replace the hash or remove only that installation. Never reuse forms, deployment, personal Cloudflare or session credentials.
|
|
48
|
-
|
|
49
|
-
Upload the matching RAW credential to this admin as `HARNESS_API_TOKEN`, through secret stdin. For example, from the admin directory, with the dedicated token already in the operator environment (PowerShell and POSIX shells both support this pipeline):
|
|
50
|
-
|
|
51
|
-
```sh
|
|
52
|
-
node -e "process.stdout.write(process.env.VITRIS_ADMIN_API_TOKEN || '')" | npx wrangler secret put HARNESS_API_TOKEN --config wrangler.jsonc
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Require a nonempty valid environment token before running this command. Install the reviewed complete backend map similarly, by piping the ignored file bytes to `wrangler secret put ADMIN_CONNECTIONS` with the backend's exact config/account. These are explicit cloud writes, outside local setup. Never log secret stdin or store raw tokens in files or public assets.
|
|
56
|
-
|
|
57
|
-
## API and stored data
|
|
58
|
-
|
|
59
|
-
Server-to-server requests use `Authorization: Bearer ...`, `x-vitris-installation` and `x-vitris-site-origin`. The routes are `/api/admin/v1/TENANT/status`, `/leads`, `/leads?mode=test`, `/analytics?days=14`, and optional POST `/leads/status` with JSON `{id,status}`. Cookie login, browser Origin requests, forms setup tokens and deployment tokens grant no API access. All responses are uncached; preview hosts cannot reach these routes.
|
|
60
|
-
|
|
61
|
-
Every query constrains the existing tenant ID. Production reads use `leads`, including retained legacy records. Test reads use `form_submissions WHERE mode='test'` and matching notification captures from `form_outbox`; test records are always read-only. Production enquiries count stored leads. Analytics reads current `analytics_events` with live/legacy source only and excludes preview traffic; consent still controls the existing collector. Dates are UTC; the accepted range is 1–90 days. Leads return the latest 200 at most, bounded below the published adapter's 4 MB limit; `truncated` signals a bounded result. This alpha has no historical pagination/export UI.
|
|
62
|
-
|
|
63
|
-
Status updates allow `new`, `contacted`, or `closed` and require `leads:write` in addition to the admin's authenticated role and CSRF protections. Cross-tenant and test IDs cannot be updated. They do not retry notifications, change recipients or change collection. Existing D1 migrations remain untouched; this API uses the current schema. Review missing migrations separately for older deployments.
|
|
64
|
-
|
|
65
|
-
## Explicit Cloudflare rollout
|
|
66
|
-
|
|
67
|
-
Use the customer's intended account and inspect `npx wrangler whoami` before any write. The admin defaults to Workers with Static Assets, D1, R2 and Cloudflare Email Service authentication. Its generated production config disables workers.dev and uses a canonical Custom Domain. Keep local and production manifests/resources separate.
|
|
68
|
-
|
|
69
|
-
1. In the admin directory, run `npm run plan`. Review Worker/resource names, account ID, zone ownership, sender and hostname. Run `npx wrangler d1 create ACME_ADMIN_DB` and `npx wrangler r2 bucket create ACME_ADMIN_ASSETS` only as explicit provisioning steps. Put their actual identifiers into `wrangler.jsonc`; use dedicated resources for this business.
|
|
70
|
-
2. Back up any existing target database. Review copied numbered migrations; apply only to the admin's intended database with `npx wrangler d1 migrations apply ACME_ADMIN_DB --remote --config wrangler.jsonc`. Do not apply admin migrations to the website backend database.
|
|
71
|
-
3. Run `npx vitris-admin owner --email OWNER_EMAIL` inside the admin. Review ignored `.vitris/bootstrap-owner.sql`, then apply it with `npx wrangler d1 execute ACME_ADMIN_DB --remote --config wrangler.jsonc --file .vitris/bootstrap-owner.sql`. Existing accounts are not promoted. Each installation owns its users and sessions.
|
|
72
|
-
4. Complete Cloudflare Email Service sender-domain onboarding and sending availability. Set `AUTH_FROM` and a restricted `AUTH_EMAIL` send binding in the production config. Verify provider/recipient restrictions, real delivery, single-use login and revocation. The alpha's stubbed email tests do not prove live delivery. Never enable production demo login or use local mode in production.
|
|
73
|
-
5. Merge/install the backend grant and install `HARNESS_API_TOKEN` through secret stdin as above. Verify the deployed backend API independently. If it needs a compatible release, review its impact and owner authorization separately, retaining existing records/accounts and tenant IDs.
|
|
74
|
-
6. Attach `admin.customer-domain.com` as the generated Worker Custom Domain in the active zone of this account. Verify DNS, TLS and canonical-host checks. Cross-account/external DNS requires a separate reviewed hostname strategy; a CNAME alone does not satisfy this contract.
|
|
75
|
-
7. Run `npm run doctor` and `npm run build` (dry run), review the configuration/release diff, then explicitly deploy the admin with `npm run deploy`. Verify the canonical HTTPS domain, actual owner login, signed-in permissions, revoked access and authoritative stored records. Record the deployed revision. Public website publication continues through its own consent and release gates.
|
|
76
|
-
|
|
77
|
-
### Websites without collection
|
|
78
|
-
|
|
79
|
-
A static website without a registered backend has no historical lead or analytics records to import. Prepare a dedicated website backend Worker, D1 schema, R2 and KV bindings (and optional Analytics Engine) from the harness's `cloudflare/` example. Review account/resource ownership, session secret, migrations and tenant/hostname registration before any cloud writes. Keep backend provisioning and deployment separate from the admin database and deployment.
|
|
80
|
-
|
|
81
|
-
Use `vitris forms inspect` and the plan/apply workflow in `docs/CONTACT-FORMS.md` to register approved origins, form schemas, Turnstile, notification sender and recipients. Preserve existing form endpoints until the owner selects the replacement flow. Validate test submissions in isolation first; do not send customer test notifications without authorization. Enable the existing analytics collector only with the site's consent policy and production hostname/tenant mapping. Scaffolding an admin, setting backendOrigin, or binding a subdomain enables none of this automatically.
|
|
82
|
-
|
|
83
|
-
## Validation
|
|
84
|
-
|
|
85
|
-
`npm run test:admin` runs isolated synthetic fixtures against actual local D1 and the published runtime Worker adapter, including installer plan/apply/recovery, conflicts, stale plans, identity/account isolation, unauthorized/revoked credentials, read-only grants, test separation and preview exclusion. No live challenge or mail is used: assets and backend transport are in-process test bindings; form rows and analytics are explicitly synthetic stored fixtures. `npm run verify:admin` installs all three exact public npm releases in a fresh ignored directory, then reruns acceptance against that runtime. Evidence stays in `artifacts/admin-integration/`. Run the repository's typecheck, full tests and package verification too.
|
|
1
|
+
# Separate business admin
|
|
2
|
+
|
|
3
|
+
The business owns a separate installation, accounts, D1 database, R2 bucket and Worker at `admin.customer-domain.com`. No central Vitris login is required. Pages, Media, page builders and arbitrary HTML editing are outside scope. The public website keeps its forms, backend origin, tenant identity, consent behavior and release gates.
|
|
4
|
+
|
|
5
|
+
The harness pins `@tryvitris/website-admin-harness` and `@tryvitris/website-admin-contracts` to **0.1.0-alpha.2**. Setup delegates to the published runtime's `/cli` export (`vitris-admin`); its Worker uses `/worker`. The separate initializer is `@tryvitris/create-website-admin-harness@0.1.0-alpha.2`. Never use a floating `alpha` tag for a reproducible installation. See the [upstream source](https://github.com/jackperrotta/website-admin-harness).
|
|
6
|
+
|
|
7
|
+
## Local preparation
|
|
8
|
+
|
|
9
|
+
New websites may opt into next-step instructions with `vitris site create ... --admin` or `vitris-create-site ... --admin`. This creates `ADMIN-SETUP.md` only. Continue prepare → `agent/CURRENT.md` → work → complete and stop at verified local preview as usual. Existing websites can run setup directly.
|
|
10
|
+
|
|
11
|
+
Inspect the site's launch configuration, forms, exact public origin, and actual backend tenant ID. A slug is not proof of tenant identity. Prepare the sibling admin with:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
vitris admin setup --site ../acme --dir ../acme-admin --domain admin.customer-domain.com --website https://www.customer-domain.com --tenant EXISTING_TENANT_ID --backend-origin https://BACKEND --plan
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Equivalent standalone published command:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx --yes @tryvitris/website-admin-harness@0.1.0-alpha.2 setup --site ../acme --dir ../acme-admin --domain admin.customer-domain.com --website https://www.customer-domain.com --tenant EXISTING_TENANT_ID --backend-origin https://BACKEND --plan
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Review `../acme/artifacts/admin/setup-plan.json`, including its exact directories, origins, tenant, scopes, package version and launch digest. Set a new dedicated `VITRIS_ADMIN_API_TOKEN` in the operator environment: 32–200 URL-safe letters/digits/underscores/hyphens generated by a secure secret manager. Repeat the identical command with `--apply` replacing `--plan`. Never pass the credential as a command argument or print it. `--lead-write` must appear in both commands only if lead status updates are explicitly wanted; default scopes are `leads:read` and `analytics:read`.
|
|
24
|
+
|
|
25
|
+
Apply brands the installation from `launch.json.name`, writes public `launch.json.admin` metadata, and saves only its SHA-256 hash in the admin's ignored `.vitris/backend-grant.json`. Existing forms, backend URL and workflow progress stay intact. Matching retries are no-ops; changed launch bytes/plans, conflicting destinations/connections or changed authority require a new review. Do not delete setup receipts or use force to bypass conflicts. Keep `artifacts/`, `.vitris/`, `.env*`, `.dev.vars*`, `dist/` and local release bundles ignored. No tenant, cloud resource, account, secret, DNS record or deployment is created by setup.
|
|
26
|
+
|
|
27
|
+
Inside the admin, install dependencies and retain its lockfile. Read `agent/WORKFLOW.md`, `agent/refs/harness.md`, and `agent/refs/cloudflare.md` before changing the installation. `npm run db:local` and `npm run dev` use a separate local manifest/database with synthetic demo data. They do not establish production backend access or working email authentication.
|
|
28
|
+
|
|
29
|
+
## Backend compatibility and grants
|
|
30
|
+
|
|
31
|
+
This source adds the admin v1 API. **An already deployed website backend may still run an older revision.** Setup does not upgrade it. Identify its owner, deployed revision, schema and hostname; perform any backend release as a separately reviewed operation. Never deploy or migrate a shared backend as an admin setup side effect. No production endpoint or credential is included in this public repository, so an existing deployment's compatibility must be checked per installation.
|
|
32
|
+
|
|
33
|
+
`vitris admin status --site ../acme` reports local metadata only. After the grant is installed, use the dedicated credential in the operator environment to check the deployed API:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
vitris admin verify --site ../acme --dir ../acme-admin
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Verify requires v1 identity matching installation, tenant and site origin, both read scopes, and valid production leads, test submissions and analytics responses. It reports counts only; zero rows are valid. It fails on missing routes, rejected/revoked credentials, identity mismatch, malformed records, redirects or transport failure. It does not certify collection, email, account login or deployment. The admin's published adapter likewise surfaces failures without substituting demo records.
|
|
40
|
+
|
|
41
|
+
The backend secret `ADMIN_CONNECTIONS` is a COMPLETE installation-keyed map. Each grant contains only `tenantId`, exact HTTPS `siteOrigin`, `tokenHash` and `scopes`. Retrieve the authoritative full map from the backend owner's secure configuration; Wrangler cannot recover a secret's value. For a confirmed brand-new backend only, initialize a private ignored file with `{}`. Do not treat an unknown existing map as empty.
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
vitris admin grant --dir ../acme-admin --grants PRIVATE_COMPLETE_MAP.json --out NEW_PRIVATE_MERGED_MAP.json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This local command preserves other grants, validates hashed entries and installation identity, refuses differing existing authority and refuses overwriting the output. Store input/output in ignored operator storage. Review the entire merged map before explicitly uploading it to the intended backend as `ADMIN_CONNECTIONS` via Wrangler secret stdin. Rotation/revocation is a separate reviewed complete-map update: replace the hash or remove only that installation. Never reuse forms, deployment, personal Cloudflare or session credentials.
|
|
48
|
+
|
|
49
|
+
Upload the matching RAW credential to this admin as `HARNESS_API_TOKEN`, through secret stdin. For example, from the admin directory, with the dedicated token already in the operator environment (PowerShell and POSIX shells both support this pipeline):
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
node -e "process.stdout.write(process.env.VITRIS_ADMIN_API_TOKEN || '')" | npx wrangler secret put HARNESS_API_TOKEN --config wrangler.jsonc
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Require a nonempty valid environment token before running this command. Install the reviewed complete backend map similarly, by piping the ignored file bytes to `wrangler secret put ADMIN_CONNECTIONS` with the backend's exact config/account. These are explicit cloud writes, outside local setup. Never log secret stdin or store raw tokens in files or public assets.
|
|
56
|
+
|
|
57
|
+
## API and stored data
|
|
58
|
+
|
|
59
|
+
Server-to-server requests use `Authorization: Bearer ...`, `x-vitris-installation` and `x-vitris-site-origin`. The routes are `/api/admin/v1/TENANT/status`, `/leads`, `/leads?mode=test`, `/analytics?days=14`, and optional POST `/leads/status` with JSON `{id,status}`. Cookie login, browser Origin requests, forms setup tokens and deployment tokens grant no API access. All responses are uncached; preview hosts cannot reach these routes.
|
|
60
|
+
|
|
61
|
+
Every query constrains the existing tenant ID. Production reads use `leads`, including retained legacy records. Test reads use `form_submissions WHERE mode='test'` and matching notification captures from `form_outbox`; test records are always read-only. Production enquiries count stored leads. Analytics reads current `analytics_events` with live/legacy source only and excludes preview traffic; consent still controls the existing collector. Dates are UTC; the accepted range is 1–90 days. Leads return the latest 200 at most, bounded below the published adapter's 4 MB limit; `truncated` signals a bounded result. This alpha has no historical pagination/export UI.
|
|
62
|
+
|
|
63
|
+
Status updates allow `new`, `contacted`, or `closed` and require `leads:write` in addition to the admin's authenticated role and CSRF protections. Cross-tenant and test IDs cannot be updated. They do not retry notifications, change recipients or change collection. Existing D1 migrations remain untouched; this API uses the current schema. Review missing migrations separately for older deployments.
|
|
64
|
+
|
|
65
|
+
## Explicit Cloudflare rollout
|
|
66
|
+
|
|
67
|
+
Use the customer's intended account and inspect `npx wrangler whoami` before any write. The admin defaults to Workers with Static Assets, D1, R2 and Cloudflare Email Service authentication. Its generated production config disables workers.dev and uses a canonical Custom Domain. Keep local and production manifests/resources separate.
|
|
68
|
+
|
|
69
|
+
1. In the admin directory, run `npm run plan`. Review Worker/resource names, account ID, zone ownership, sender and hostname. Run `npx wrangler d1 create ACME_ADMIN_DB` and `npx wrangler r2 bucket create ACME_ADMIN_ASSETS` only as explicit provisioning steps. Put their actual identifiers into `wrangler.jsonc`; use dedicated resources for this business.
|
|
70
|
+
2. Back up any existing target database. Review copied numbered migrations; apply only to the admin's intended database with `npx wrangler d1 migrations apply ACME_ADMIN_DB --remote --config wrangler.jsonc`. Do not apply admin migrations to the website backend database.
|
|
71
|
+
3. Run `npx vitris-admin owner --email OWNER_EMAIL` inside the admin. Review ignored `.vitris/bootstrap-owner.sql`, then apply it with `npx wrangler d1 execute ACME_ADMIN_DB --remote --config wrangler.jsonc --file .vitris/bootstrap-owner.sql`. Existing accounts are not promoted. Each installation owns its users and sessions.
|
|
72
|
+
4. Complete Cloudflare Email Service sender-domain onboarding and sending availability. Set `AUTH_FROM` and a restricted `AUTH_EMAIL` send binding in the production config. Verify provider/recipient restrictions, real delivery, single-use login and revocation. The alpha's stubbed email tests do not prove live delivery. Never enable production demo login or use local mode in production.
|
|
73
|
+
5. Merge/install the backend grant and install `HARNESS_API_TOKEN` through secret stdin as above. Verify the deployed backend API independently. If it needs a compatible release, review its impact and owner authorization separately, retaining existing records/accounts and tenant IDs.
|
|
74
|
+
6. Attach `admin.customer-domain.com` as the generated Worker Custom Domain in the active zone of this account. Verify DNS, TLS and canonical-host checks. Cross-account/external DNS requires a separate reviewed hostname strategy; a CNAME alone does not satisfy this contract.
|
|
75
|
+
7. Run `npm run doctor` and `npm run build` (dry run), review the configuration/release diff, then explicitly deploy the admin with `npm run deploy`. Verify the canonical HTTPS domain, actual owner login, signed-in permissions, revoked access and authoritative stored records. Record the deployed revision. Public website publication continues through its own consent and release gates.
|
|
76
|
+
|
|
77
|
+
### Websites without collection
|
|
78
|
+
|
|
79
|
+
A static website without a registered backend has no historical lead or analytics records to import. Prepare a dedicated website backend Worker, D1 schema, R2 and KV bindings (and optional Analytics Engine) from the harness's `cloudflare/` example. Review account/resource ownership, session secret, migrations and tenant/hostname registration before any cloud writes. Keep backend provisioning and deployment separate from the admin database and deployment.
|
|
80
|
+
|
|
81
|
+
Use `vitris forms inspect` and the plan/apply workflow in `docs/CONTACT-FORMS.md` to register approved origins, form schemas, Turnstile, notification sender and recipients. Preserve existing form endpoints until the owner selects the replacement flow. Validate test submissions in isolation first; do not send customer test notifications without authorization. Enable the existing analytics collector only with the site's consent policy and production hostname/tenant mapping. Scaffolding an admin, setting backendOrigin, or binding a subdomain enables none of this automatically.
|
|
82
|
+
|
|
83
|
+
## Validation
|
|
84
|
+
|
|
85
|
+
`npm run test:admin` runs isolated synthetic fixtures against actual local D1 and the published runtime Worker adapter, including installer plan/apply/recovery, conflicts, stale plans, identity/account isolation, unauthorized/revoked credentials, read-only grants, test separation and preview exclusion. No live challenge or mail is used: assets and backend transport are in-process test bindings; form rows and analytics are explicitly synthetic stored fixtures. `npm run verify:admin` installs all three exact public npm releases in a fresh ignored directory, then reruns acceptance against that runtime. Evidence stays in `artifacts/admin-integration/`. Run the repository's typecheck, full tests and package verification too.
|