@spacefast/common 0.0.24 → 0.2.1
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/brand-assets/LICENSE-Haskoy.txt +93 -0
- package/brand-assets/haskoy-latin-variable.woff2 +0 -0
- package/brand-assets/spacefast-favicon.svg +4 -4
- package/brand-assets/spacefast-sf-full-bleed.svg +3 -3
- package/brand-assets/spacefast-wordmark.svg +2 -4
- package/dist/agents/connect-targets.d.ts +6 -146
- package/dist/agents/connect-targets.js +63 -295
- package/dist/agents/private-key-oauth.d.ts +31 -2
- package/dist/agents/private-key-oauth.js +166 -92
- package/dist/agents/registry/core.d.ts +292 -0
- package/dist/agents/registry/core.js +1388 -0
- package/dist/agents/registry/deeplink-probes.d.ts +23 -0
- package/dist/agents/registry/deeplink-probes.js +18 -0
- package/dist/agents/registry/node.d.ts +56 -0
- package/dist/agents/registry/node.js +339 -0
- package/dist/brand-assets-build.js +3 -6
- package/dist/brand-assets.d.ts +5 -15
- package/dist/brand-assets.js +12 -14
- package/dist/brand.d.ts +17 -0
- package/dist/brand.js +24 -4
- package/dist/config/domains.d.ts +79 -12
- package/dist/config/domains.js +128 -29
- package/dist/config/index.js +1 -2
- package/dist/contracts/abuse.js +9 -24
- package/dist/contracts/access-profiles.d.ts +86 -0
- package/dist/contracts/access-profiles.js +89 -0
- package/dist/contracts/access.d.ts +24 -30
- package/dist/contracts/access.js +33 -64
- package/dist/contracts/activity.d.ts +18 -1
- package/dist/contracts/activity.js +72 -26
- package/dist/contracts/analytics.js +8 -10
- package/dist/contracts/api-keys.d.ts +77 -52
- package/dist/contracts/api-keys.js +44 -24
- package/dist/contracts/application-journal.d.ts +216 -0
- package/dist/contracts/application-journal.js +167 -0
- package/dist/contracts/auth.d.ts +90 -17
- package/dist/contracts/auth.js +86 -17
- package/dist/contracts/beta.d.ts +9 -4
- package/dist/contracts/beta.js +20 -16
- package/dist/contracts/billing.d.ts +2 -1
- package/dist/contracts/billing.js +37 -11
- package/dist/contracts/bootstrap.d.ts +791 -0
- package/dist/contracts/bootstrap.js +51 -0
- package/dist/contracts/builds.d.ts +209 -18
- package/dist/contracts/builds.js +93 -27
- package/dist/contracts/channels.d.ts +43 -0
- package/dist/contracts/channels.js +25 -9
- package/dist/contracts/cli.d.ts +7 -0
- package/dist/contracts/cli.js +6 -0
- package/dist/contracts/collab-public.d.ts +1 -1
- package/dist/contracts/collab-public.js +5 -8
- package/dist/contracts/comments.d.ts +44 -34
- package/dist/contracts/comments.js +107 -77
- package/dist/contracts/commerce.d.ts +165 -0
- package/dist/contracts/commerce.js +146 -0
- package/dist/contracts/common.d.ts +3 -3
- package/dist/contracts/common.js +20 -31
- package/dist/contracts/content-contract-verification.d.ts +521 -0
- package/dist/contracts/content-contract-verification.js +320 -0
- package/dist/contracts/content-platform-fixture.d.ts +1278 -0
- package/dist/contracts/content-platform-fixture.js +44 -0
- package/dist/contracts/content-program.d.ts +994 -0
- package/dist/contracts/content-program.js +658 -0
- package/dist/contracts/content-sync.d.ts +290 -0
- package/dist/contracts/content-sync.js +167 -0
- package/dist/contracts/content.d.ts +432 -0
- package/dist/contracts/content.js +303 -0
- package/dist/contracts/continuation.d.ts +17 -1
- package/dist/contracts/continuation.js +23 -23
- package/dist/contracts/countries.js +3 -4
- package/dist/contracts/crons.d.ts +52 -0
- package/dist/contracts/crons.js +60 -0
- package/dist/contracts/dashboard-prefs.d.ts +56 -0
- package/dist/contracts/dashboard-prefs.js +51 -0
- package/dist/contracts/device-auth.d.ts +234 -37
- package/dist/contracts/device-auth.js +267 -53
- package/dist/contracts/docs.d.ts +1 -5
- package/dist/contracts/docs.js +7 -15
- package/dist/contracts/domains.d.ts +501 -93
- package/dist/contracts/domains.js +193 -55
- package/dist/contracts/email-preferences.d.ts +3 -3
- package/dist/contracts/email-preferences.js +6 -7
- package/dist/contracts/enums.d.ts +27 -26
- package/dist/contracts/enums.js +68 -61
- package/dist/contracts/error-code-meta.d.ts +283 -7
- package/dist/contracts/error-code-meta.js +97 -5
- package/dist/contracts/error-codes.d.ts +5 -5
- package/dist/contracts/error-codes.js +82 -17
- package/dist/contracts/events.d.ts +100 -20
- package/dist/contracts/events.js +107 -20
- package/dist/contracts/execution.d.ts +48 -64
- package/dist/contracts/execution.js +52 -62
- package/dist/contracts/feature-lifecycle.d.ts +73 -27
- package/dist/contracts/feature-lifecycle.js +86 -26
- package/dist/contracts/features.d.ts +68 -19
- package/dist/contracts/features.js +42 -41
- package/dist/contracts/fixtures/content-platform-v1.json +1108 -0
- package/dist/contracts/frame-session.d.ts +192 -0
- package/dist/contracts/frame-session.js +164 -0
- package/dist/contracts/functions.d.ts +338 -164
- package/dist/contracts/functions.js +367 -198
- package/dist/contracts/git.d.ts +27 -33
- package/dist/contracts/git.js +10 -11
- package/dist/contracts/grant-copy.d.ts +43 -0
- package/dist/contracts/grant-copy.js +44 -0
- package/dist/contracts/grants.d.ts +62 -54
- package/dist/contracts/grants.js +124 -23
- package/dist/contracts/ids.d.ts +4 -1
- package/dist/contracts/ids.js +6 -3
- package/dist/contracts/internal.d.ts +8 -18
- package/dist/contracts/internal.js +13 -14
- package/dist/contracts/mcp.d.ts +28 -0
- package/dist/contracts/mcp.js +28 -0
- package/dist/contracts/me.d.ts +6 -1
- package/dist/contracts/me.js +12 -6
- package/dist/contracts/notifications.d.ts +27 -29
- package/dist/contracts/notifications.js +9 -4
- package/dist/contracts/oauth-resources.d.ts +112 -10
- package/dist/contracts/oauth-resources.js +116 -11
- package/dist/contracts/oauth-scope-actions.d.ts +90 -0
- package/dist/contracts/oauth-scope-actions.js +126 -0
- package/dist/contracts/operations.d.ts +27 -4
- package/dist/contracts/operations.js +12 -6
- package/dist/contracts/pages.d.ts +31 -7
- package/dist/contracts/pages.js +22 -5
- package/dist/contracts/partner-notifications.d.ts +3 -0
- package/dist/contracts/partner-notifications.js +65 -0
- package/dist/contracts/{platform.d.ts → partner.d.ts} +29 -42
- package/dist/contracts/{platform.js → partner.js} +27 -40
- package/dist/contracts/plan-policy.js +1 -1
- package/dist/contracts/principal-assertion.d.ts +43 -0
- package/dist/contracts/principal-assertion.js +67 -0
- package/dist/contracts/principals.d.ts +73 -0
- package/dist/contracts/principals.js +59 -0
- package/dist/contracts/privacy.d.ts +11 -0
- package/dist/contracts/privacy.js +17 -0
- package/dist/contracts/problem-document.js +3 -4
- package/dist/contracts/publish-archive.d.ts +269 -161
- package/dist/contracts/publish-archive.js +11 -1
- package/dist/contracts/push-new.d.ts +8 -8
- package/dist/contracts/quotas.js +5 -5
- package/dist/contracts/realtime.d.ts +52 -0
- package/dist/contracts/realtime.js +54 -0
- package/dist/contracts/repository-connections.d.ts +44 -31
- package/dist/contracts/repository-connections.js +17 -5
- package/dist/contracts/resources.d.ts +105 -14
- package/dist/contracts/resources.js +150 -26
- package/dist/contracts/route-inventory.d.ts +358 -0
- package/dist/contracts/route-inventory.js +141 -0
- package/dist/contracts/runtime-api.d.ts +206 -82
- package/dist/contracts/runtime-api.js +131 -70
- package/dist/contracts/runtime-app.d.ts +13 -23
- package/dist/contracts/runtime-app.js +39 -24
- package/dist/contracts/runtime-components.d.ts +143 -0
- package/dist/contracts/runtime-components.js +199 -0
- package/dist/contracts/runtime-db.d.ts +49 -23
- package/dist/contracts/runtime-db.js +87 -22
- package/dist/contracts/runtime-purge.d.ts +8 -0
- package/dist/contracts/runtime-purge.js +5 -0
- package/dist/contracts/runtime-services.d.ts +43 -75
- package/dist/contracts/runtime-services.js +29 -49
- package/dist/contracts/runtime-storage.d.ts +15 -14
- package/dist/contracts/runtime-storage.js +30 -14
- package/dist/contracts/sf-config-v1.d.ts +48 -14
- package/dist/contracts/slugs.js +13 -4
- package/dist/contracts/space-config.d.ts +283 -19
- package/dist/contracts/space-config.js +430 -80
- package/dist/contracts/spaces.d.ts +376 -290
- package/dist/contracts/spaces.js +200 -135
- package/dist/contracts/superadmin-activity.d.ts +3 -43
- package/dist/contracts/superadmin-activity.js +6 -30
- package/dist/contracts/superadmin-emails.d.ts +1 -0
- package/dist/contracts/superadmin-emails.js +17 -1
- package/dist/contracts/superadmin-runtime.d.ts +199 -0
- package/dist/contracts/superadmin-runtime.js +141 -0
- package/dist/contracts/superadmin-search.d.ts +22 -2
- package/dist/contracts/superadmin-search.js +14 -6
- package/dist/contracts/superadmin-spaces.d.ts +438 -189
- package/dist/contracts/superadmin-spaces.js +131 -25
- package/dist/contracts/superadmin-teams.d.ts +5 -2
- package/dist/contracts/superadmin-teams.js +1 -2
- package/dist/contracts/superadmin-tenants.d.ts +627 -12
- package/dist/contracts/superadmin-tenants.js +32 -13
- package/dist/contracts/superadmin.d.ts +232 -37
- package/dist/contracts/superadmin.js +106 -22
- package/dist/contracts/tags.d.ts +85 -58
- package/dist/contracts/tags.js +47 -37
- package/dist/contracts/teams.d.ts +86 -26
- package/dist/contracts/teams.js +90 -31
- package/dist/contracts/tenants.d.ts +135 -0
- package/dist/contracts/tenants.js +120 -0
- package/dist/contracts/test-triggers.d.ts +70 -0
- package/dist/contracts/test-triggers.js +116 -0
- package/dist/contracts/theme-json.js +3 -8
- package/dist/contracts/transfers.d.ts +1 -0
- package/dist/contracts/transfers.js +3 -2
- package/dist/contracts/usage.d.ts +209 -0
- package/dist/contracts/usage.js +124 -12
- package/dist/contracts/variables.d.ts +2 -2
- package/dist/contracts/variables.js +9 -13
- package/dist/contracts/webhooks.d.ts +13 -5
- package/dist/contracts/webhooks.js +3 -1
- package/dist/contracts/zero.d.ts +72 -81
- package/dist/contracts/zero.js +80 -78
- package/dist/dashboard-paths/index.d.ts +24 -21
- package/dist/dashboard-paths/index.js +35 -47
- package/dist/dashboard-paths/route-reservations.generated.d.ts +2 -0
- package/dist/dashboard-paths/route-reservations.generated.js +36 -0
- package/dist/docs/agent-arrival.d.ts +92 -0
- package/dist/docs/agent-arrival.js +227 -0
- package/dist/docs/agent-output-policy.d.ts +6 -0
- package/dist/docs/agent-output-policy.js +22 -0
- package/dist/docs/agent-prose.d.ts +12 -18
- package/dist/docs/agent-prose.js +73 -51
- package/dist/docs/agent-setup.d.ts +72 -41
- package/dist/docs/agent-setup.js +339 -436
- package/dist/docs/catalog.d.ts +153 -95
- package/dist/docs/catalog.js +163 -48
- package/dist/docs/error-docs.d.ts +2098 -1
- package/dist/docs/error-docs.js +421 -144
- package/dist/docs/index-build.d.ts +0 -1
- package/dist/docs/index-build.js +2 -3
- package/dist/docs/page-context-prompt.d.ts +77 -0
- package/dist/docs/page-context-prompt.js +131 -0
- package/dist/docs/reference-urls.d.ts +46 -0
- package/dist/docs/reference-urls.js +79 -0
- package/dist/docs/search.d.ts +6 -8
- package/dist/docs/search.js +10 -13
- package/dist/docs/skill-distribution.d.ts +4 -1
- package/dist/docs/skill-distribution.js +10 -5
- package/dist/docs/skills.d.ts +120 -120
- package/dist/docs/skills.js +121 -73
- package/dist/docs/start-prompts.d.ts +45 -0
- package/dist/docs/start-prompts.js +62 -0
- package/dist/domain-exploration.js +9 -13
- package/dist/og-template.d.ts +56 -0
- package/dist/og-template.js +56 -0
- package/dist/slug-policy/index.js +5 -11
- package/dist/test-helpers/fetch-stub.js +1 -0
- package/dist/utils/browser-credential.d.ts +13 -6
- package/dist/utils/browser-credential.js +14 -7
- package/dist/utils/build-settings.d.ts +21 -1
- package/dist/utils/build-settings.js +133 -15
- package/dist/utils/canonical-json.d.ts +3 -5
- package/dist/utils/canonical-json.js +4 -5
- package/dist/utils/cast-transport.d.ts +5 -17
- package/dist/utils/cast-transport.js +6 -19
- package/dist/utils/claim-token.js +1 -2
- package/dist/utils/comment-avatars.js +4 -6
- package/dist/utils/concurrency.js +3 -3
- package/dist/utils/content-type.d.ts +7 -6
- package/dist/utils/content-type.js +73 -51
- package/dist/utils/credential-policy.d.ts +50 -6
- package/dist/utils/credential-policy.js +126 -59
- package/dist/utils/dns-instructions.js +9 -9
- package/dist/utils/egress-policy.fixtures.json +1 -1
- package/dist/utils/error-code.js +1 -0
- package/dist/utils/generate-space-name.d.ts +3 -8
- package/dist/utils/generate-space-name.js +3 -8
- package/dist/utils/grant-decision.fixtures.json +2 -2
- package/dist/utils/grants.d.ts +0 -1
- package/dist/utils/grants.js +7 -8
- package/dist/utils/gravatar.d.ts +2 -4
- package/dist/utils/gravatar.js +6 -12
- package/dist/utils/id-hints.js +14 -23
- package/dist/utils/idempotency.d.ts +3 -3
- package/dist/utils/idempotency.js +6 -7
- package/dist/utils/local-space-state.d.ts +23 -26
- package/dist/utils/local-space-state.js +46 -46
- package/dist/utils/oauth-signed-query.d.ts +3 -5
- package/dist/utils/oauth-signed-query.js +6 -9
- package/dist/utils/one-shot-replay.d.ts +7 -6
- package/dist/utils/one-shot-replay.js +3 -5
- package/dist/utils/page-colors.d.ts +15 -19
- package/dist/utils/page-colors.js +23 -45
- package/dist/utils/page-fonts.d.ts +1 -1
- package/dist/utils/page-fonts.js +8 -20
- package/dist/utils/page-preview.d.ts +0 -1
- package/dist/utils/page-preview.js +0 -1
- package/dist/utils/pages.d.ts +33 -3
- package/dist/utils/pages.js +83 -30
- package/dist/utils/privacy-regions.d.ts +19 -0
- package/dist/utils/privacy-regions.js +91 -0
- package/dist/utils/publish-form-data.d.ts +4 -5
- package/dist/utils/publish-form-data.js +2 -3
- package/dist/utils/publish-policy.d.ts +11 -17
- package/dist/utils/publish-policy.fixtures.json +11 -5
- package/dist/utils/publish-policy.js +65 -110
- package/dist/utils/query-client.d.ts +2 -0
- package/dist/utils/query-client.js +10 -1
- package/dist/utils/runtime-paths.d.ts +4 -4
- package/dist/utils/runtime-paths.js +6 -8
- package/dist/utils/runtime-upload.d.ts +9 -0
- package/dist/utils/runtime-upload.js +12 -0
- package/dist/utils/secure-local-file.d.ts +3 -3
- package/dist/utils/secure-local-file.js +23 -40
- package/dist/utils/space-config.d.ts +12 -15
- package/dist/utils/space-config.js +13 -16
- package/dist/utils/static-runtime-policy.d.ts +10 -7
- package/dist/utils/static-runtime-policy.fixtures.json +38 -41
- package/dist/utils/static-runtime-policy.generated.d.ts +16 -0
- package/dist/utils/static-runtime-policy.generated.js +70 -0
- package/dist/utils/static-runtime-policy.js +31 -132
- package/dist/utils/storage-policy.js +1 -2
- package/dist/utils/upload-session.d.ts +4 -1
- package/dist/utils/upload-session.js +41 -19
- package/dist/utils/version-path.js +11 -19
- package/dist/utils/wpcom-auth-redirects.d.ts +5 -14
- package/dist/utils/wpcom-auth-redirects.js +1 -4
- package/dist/vocabulary.d.ts +13 -13
- package/dist/vocabulary.js +52 -27
- package/package.json +15 -18
- package/dist/agents/client-registry.d.ts +0 -22
- package/dist/agents/client-registry.js +0 -41
- package/dist/docs/agent-handoff-document.d.ts +0 -15
- package/dist/docs/agent-handoff-document.js +0 -180
- package/dist/docs/agent-solutions.d.ts +0 -110
- package/dist/docs/agent-solutions.js +0 -183
- package/dist/utils/query-keys.d.ts +0 -84
- package/dist/utils/query-keys.js +0 -108
|
@@ -1,44 +1,27 @@
|
|
|
1
|
-
// Readability engine for Spacefast-authored pages.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// colors the templates actually paint with:
|
|
7
|
-
//
|
|
8
|
-
// - `onAccent` — button-label color on accent fills (black or white).
|
|
9
|
-
// - `link` — the accent, lightness-adjusted until it meets WCAG AA
|
|
10
|
-
// against the surface it sits on. Hue and chroma survive,
|
|
11
|
-
// so it still reads as the brand color.
|
|
12
|
-
// - `pinned` — when a background is set, a complete coherent scheme
|
|
13
|
-
// (fg/muted/surface/border) derived from it and pinned for
|
|
14
|
-
// every visitor. A chosen background IS the page; the
|
|
15
|
-
// visitor's OS dark-mode setting must not repaint half the
|
|
16
|
-
// variables into an unreadable hybrid.
|
|
17
|
-
//
|
|
18
|
-
// Everything is pure and deterministic: publish finalize, dashboard previews,
|
|
19
|
-
// and tests all call the same functions and get the same pixels.
|
|
1
|
+
// Readability engine for Spacefast-authored pages. Users and agents push any
|
|
2
|
+
// accent or background; this module derives the colors the templates paint
|
|
3
|
+
// with so every page meets WCAG AA. A pushed background pins a whole scheme,
|
|
4
|
+
// because the visitor's OS dark-mode setting must not repaint half the
|
|
5
|
+
// variables into an unreadable hybrid.
|
|
20
6
|
import { formatHex, oklch, parse, wcagContrast } from "culori";
|
|
21
|
-
/** Default page palette
|
|
7
|
+
/** Default page palette, mirrored into the shared template CSS. */
|
|
22
8
|
export const PAGE_COLOR_DEFAULTS = {
|
|
23
|
-
accent: "#
|
|
24
|
-
bg: "#
|
|
25
|
-
fg: "#
|
|
26
|
-
muted: "#
|
|
27
|
-
card: "#
|
|
28
|
-
border: "#
|
|
29
|
-
darkBg: "#
|
|
30
|
-
darkFg: "#
|
|
31
|
-
darkMuted: "#
|
|
32
|
-
darkCard: "#
|
|
33
|
-
darkBorder: "#
|
|
9
|
+
accent: "#d8f24b",
|
|
10
|
+
bg: "#f7f5ef",
|
|
11
|
+
fg: "#0c0c0c",
|
|
12
|
+
muted: "#6e6a5e",
|
|
13
|
+
card: "#f7f5ef",
|
|
14
|
+
border: "#e5e1d4",
|
|
15
|
+
darkBg: "#0c0c0c",
|
|
16
|
+
darkFg: "#f0eee6",
|
|
17
|
+
darkMuted: "#a6a193",
|
|
18
|
+
darkCard: "#0c0c0c",
|
|
19
|
+
darkBorder: "#2b2823",
|
|
34
20
|
};
|
|
35
|
-
|
|
21
|
+
// WCAG AA: 4.5 for text, 3 for non-text control boundaries.
|
|
36
22
|
const TEXT_CONTRAST = 4.5;
|
|
37
|
-
/** Body-size button labels on accent fills. */
|
|
38
23
|
const UI_CONTRAST = 4.5;
|
|
39
|
-
/** Essential control boundaries against their painted surface. */
|
|
40
24
|
const CONTROL_CONTRAST = 3;
|
|
41
|
-
/** Primary foreground meets AA even on mid-tone custom backgrounds. */
|
|
42
25
|
const FG_CONTRAST = 4.5;
|
|
43
26
|
function safeParse(value) {
|
|
44
27
|
if (!value)
|
|
@@ -57,10 +40,9 @@ function isLightSurface(surface) {
|
|
|
57
40
|
return contrast("#000000", surface) >= contrast("#ffffff", surface);
|
|
58
41
|
}
|
|
59
42
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* cannot reach the target (e.g. a mid-gray surface).
|
|
43
|
+
* Moves `color`'s OKLCH lightness (hue and chroma preserved) just far enough
|
|
44
|
+
* to meet `target` on `surface`. Falls back to black or white when the full
|
|
45
|
+
* lightness range cannot reach it, as on a mid-gray surface.
|
|
64
46
|
*/
|
|
65
47
|
export function readableOn(color, surface, target) {
|
|
66
48
|
if (contrast(color, surface) >= target)
|
|
@@ -76,8 +58,7 @@ export function readableOn(color, surface, target) {
|
|
|
76
58
|
if (contrast(atLimit, surface) < target) {
|
|
77
59
|
return isLightSurface(surface) ? "#000000" : "#ffffff";
|
|
78
60
|
}
|
|
79
|
-
//
|
|
80
|
-
// the target; contrast is monotonic in lightness on a fixed surface side.
|
|
61
|
+
// Contrast is monotonic in lightness on a fixed surface side.
|
|
81
62
|
let searchLimit = limit;
|
|
82
63
|
let searchStart = base.l;
|
|
83
64
|
for (let i = 0; i < 24; i += 1) {
|
|
@@ -91,14 +72,12 @@ export function readableOn(color, surface, target) {
|
|
|
91
72
|
}
|
|
92
73
|
return formatHex({ ...base, l: searchLimit });
|
|
93
74
|
}
|
|
94
|
-
/** Button-label color for an accent fill: white when it can carry, else ink. */
|
|
95
75
|
export function onAccentColor(accent) {
|
|
96
76
|
return contrast("#ffffff", accent) >= UI_CONTRAST ? "#ffffff" : "#000000";
|
|
97
77
|
}
|
|
98
78
|
function clamp(value, min, max) {
|
|
99
79
|
return Math.min(max, Math.max(min, value));
|
|
100
80
|
}
|
|
101
|
-
/** Derive a complete coherent scheme from a pushed background color. */
|
|
102
81
|
function schemeFromBackground(background) {
|
|
103
82
|
const parsed = safeParse(background);
|
|
104
83
|
const bg = parsed ? formatHex(parsed) : PAGE_COLOR_DEFAULTS.bg;
|
|
@@ -136,10 +115,9 @@ function schemeFromBackground(background) {
|
|
|
136
115
|
},
|
|
137
116
|
};
|
|
138
117
|
}
|
|
139
|
-
/** The one entry point: raw theme in, everything the templates paint with out. */
|
|
140
118
|
export function derivePageColors(theme) {
|
|
141
119
|
const accentInput = theme.accent?.trim();
|
|
142
|
-
const accent = safeParse(accentInput) ? accentInput : PAGE_COLOR_DEFAULTS.accent;
|
|
120
|
+
const accent = accentInput && safeParse(accentInput) ? accentInput : PAGE_COLOR_DEFAULTS.accent;
|
|
143
121
|
const onAccent = onAccentColor(accent);
|
|
144
122
|
const background = theme.background?.trim();
|
|
145
123
|
if (background && safeParse(background)) {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export declare const PAGE_FONT_FACES: string;
|
|
2
|
-
export declare const PAGE_FONT_PRELOAD_HTML
|
|
2
|
+
export declare const PAGE_FONT_PRELOAD_HTML = "<link rel=\"preload\" href=\"https://wordpress.com/i/fonts/recoleta/400.woff2\" as=\"font\" type=\"font/woff2\" crossorigin>";
|
package/dist/utils/page-fonts.js
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// font
|
|
5
|
-
// immutable with CORS — see wwwHeaders in apps/www/src/lib/routing.mjs).
|
|
6
|
-
// `font-display:optional` renders the fallback and never swaps mid-view when a
|
|
7
|
-
// font arrives late — cached loads (everything after the first) get the brand
|
|
8
|
-
// fonts with no flicker either way.
|
|
1
|
+
// Recoleta is the only downloaded face (DESIGN.md §4); body and mono are system
|
|
2
|
+
// stacks. It is commercial, never committed to this repo, and loads from
|
|
3
|
+
// wordpress.com's font CDN so every space hostname shares one cache entry.
|
|
4
|
+
// `font-display:optional` never swaps mid-view when a font arrives late.
|
|
9
5
|
const RECOLETA_BASE = "https://wordpress.com/i/fonts/recoleta/";
|
|
10
|
-
const HASKOY_URL = "https://spacefast.com/assets/fonts/haskoy-latin-variable.woff2";
|
|
11
6
|
const RECOLETA_WEIGHTS = [300, 400, 500, 600, 700];
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export const PAGE_FONT_FACES = faces.join("");
|
|
17
|
-
// Only the faces the default pages actually render: Recoleta 400 for headings,
|
|
18
|
-
// Haskoy for everything else. Fonts are a cross-origin fetch; `crossorigin` is
|
|
19
|
-
// required — font requests are CORS mode, and a mismatched preload is discarded.
|
|
20
|
-
export const PAGE_FONT_PRELOAD_HTML = [`${RECOLETA_BASE}400.woff2`, HASKOY_URL]
|
|
21
|
-
.map((url) => `<link rel="preload" href="${url}" as="font" type="font/woff2" crossorigin>`)
|
|
22
|
-
.join("");
|
|
7
|
+
export const PAGE_FONT_FACES = RECOLETA_WEIGHTS.map((weight) => `@font-face{font-family:"Recoleta";src:url("${RECOLETA_BASE}${weight}.woff2") format("woff2");font-style:normal;font-weight:${weight};font-display:optional}`).join("");
|
|
8
|
+
// Only the weight the default pages render. `crossorigin` is required: font
|
|
9
|
+
// requests are CORS mode, and a mismatched preload is discarded.
|
|
10
|
+
export const PAGE_FONT_PRELOAD_HTML = `<link rel="preload" href="${RECOLETA_BASE}400.woff2" as="font" type="font/woff2" crossorigin>`;
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* the preview page is reachable only under this query flag.
|
|
4
4
|
*/
|
|
5
5
|
export const PAGE_PREVIEW_QUERY = "spacefast_view";
|
|
6
|
-
/** The preview page for a file whose bytes live at `fileHref`. */
|
|
7
6
|
export function previewPageHref(fileHref) {
|
|
8
7
|
return `${fileHref}${fileHref.includes("?") ? "&" : "?"}${PAGE_PREVIEW_QUERY}=1`;
|
|
9
8
|
}
|
package/dist/utils/pages.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type CustomPageId, type SitePageId } from "../contracts/pages.js";
|
|
1
|
+
import { type CustomPageId, type PlatformPageId, type SitePageId } from "../contracts/pages.js";
|
|
2
|
+
import { type DerivedPageColors } from "./page-colors.js";
|
|
2
3
|
export { PAGE_PREVIEW_QUERY, previewPageHref } from "./page-preview.js";
|
|
3
4
|
export type PageTheme = {
|
|
4
5
|
accent?: string | undefined;
|
|
@@ -21,7 +22,7 @@ export type PagePreviewFile = {
|
|
|
21
22
|
mime?: string;
|
|
22
23
|
};
|
|
23
24
|
export type PageExpansionContext = {
|
|
24
|
-
page: CustomPageId | "layout";
|
|
25
|
+
page: CustomPageId | PlatformPageId | "layout";
|
|
25
26
|
theme?: PageTheme;
|
|
26
27
|
content?: string;
|
|
27
28
|
displayPath?: string;
|
|
@@ -29,6 +30,9 @@ export type PageExpansionContext = {
|
|
|
29
30
|
itemCount?: number;
|
|
30
31
|
visibleItemCount?: number;
|
|
31
32
|
hideSpacefastBranding?: boolean;
|
|
33
|
+
supportUrl?: string;
|
|
34
|
+
partnerBranding?: boolean;
|
|
35
|
+
useSystemDisplayFont?: boolean;
|
|
32
36
|
files?: readonly PageFileEntry[];
|
|
33
37
|
file?: PagePreviewFile;
|
|
34
38
|
};
|
|
@@ -40,6 +44,13 @@ export type PageDiagnostic = {
|
|
|
40
44
|
};
|
|
41
45
|
export declare function isPagePublicPath(path: string): boolean;
|
|
42
46
|
export declare function escapePageHtml(value: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* The custom properties a theme paints with. A custom background pins a
|
|
49
|
+
* complete scheme for both OS modes; `--sf-bg` alone would leave dark-mode
|
|
50
|
+
* visitors with the dark foreground on a light background. Exported so the
|
|
51
|
+
* dashboard preview paints the same tokens from an unsaved draft.
|
|
52
|
+
*/
|
|
53
|
+
export declare function pageThemeCss(theme: PageTheme, derived?: DerivedPageColors): string;
|
|
43
54
|
export declare function expandPageHtml(source: string, context: PageExpansionContext): string;
|
|
44
55
|
export declare function validatePageSources(input: {
|
|
45
56
|
pages: Readonly<Record<string, string>>;
|
|
@@ -49,7 +60,26 @@ export declare function isCompleteHtmlDocument(source: string): boolean;
|
|
|
49
60
|
export declare function applyPageLayout(layout: string | null, content: string, context: Omit<PageExpansionContext, "page" | "content">): string;
|
|
50
61
|
export declare function inferPageThemeFromThemeJson(raw: string): Partial<PageTheme>;
|
|
51
62
|
export declare function defaultPageBody(id: SitePageId): string;
|
|
52
|
-
export declare const DEFAULT_PAGE_TEMPLATES:
|
|
63
|
+
export declare const DEFAULT_PAGE_TEMPLATES: {
|
|
64
|
+
"404": string;
|
|
65
|
+
denied: string;
|
|
66
|
+
access: string;
|
|
67
|
+
index: string;
|
|
68
|
+
preview: string;
|
|
69
|
+
};
|
|
70
|
+
export declare const DEFAULT_PLATFORM_PAGE_TEMPLATES: {
|
|
71
|
+
login: string;
|
|
72
|
+
undeployed: string;
|
|
73
|
+
suspended: string;
|
|
74
|
+
legal: string;
|
|
75
|
+
gone: string;
|
|
76
|
+
"runtime-error": string;
|
|
77
|
+
"rate-limited": string;
|
|
78
|
+
"tier-unavailable": string;
|
|
79
|
+
"proxy-error": string;
|
|
80
|
+
"method-not-allowed": string;
|
|
81
|
+
"content-too-large": string;
|
|
82
|
+
};
|
|
53
83
|
export declare const DEFAULT_LAYOUT_TEMPLATE: string;
|
|
54
84
|
export declare function defaultPageTemplate(id: SitePageId): string;
|
|
55
85
|
export declare function pageIdFromFilename(filename: string): CustomPageId | null;
|
package/dist/utils/pages.js
CHANGED
|
@@ -33,17 +33,17 @@ export function escapePageHtml(value) {
|
|
|
33
33
|
.replaceAll('"', """)
|
|
34
34
|
.replaceAll("'", "'");
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The custom properties a theme paints with. A custom background pins a
|
|
38
|
+
* complete scheme for both OS modes; `--sf-bg` alone would leave dark-mode
|
|
39
|
+
* visitors with the dark foreground on a light background. Exported so the
|
|
40
|
+
* dashboard preview paints the same tokens from an unsaved draft.
|
|
41
|
+
*/
|
|
42
|
+
export function pageThemeCss(theme, derived = derivePageColors(theme)) {
|
|
41
43
|
const root = [];
|
|
42
44
|
const dark = [];
|
|
43
45
|
if (theme.accent || theme.background) {
|
|
44
|
-
|
|
45
|
-
// `derived.accent` is the parse-validated input (or the default), never
|
|
46
|
-
// an arbitrary string straight from the theme.
|
|
46
|
+
// `derived.accent` is parse-validated, never a raw string from the theme.
|
|
47
47
|
if (theme.accent)
|
|
48
48
|
root.push(`--sf-accent:${derived.accent}`);
|
|
49
49
|
root.push(`--sf-on-accent:${derived.onAccent}`, `--sf-link:${derived.link}`);
|
|
@@ -59,7 +59,11 @@ function themeStyle(theme) {
|
|
|
59
59
|
if (root.length === 0)
|
|
60
60
|
return "";
|
|
61
61
|
const darkBlock = dark.length > 0 ? `@media(prefers-color-scheme:dark){:root{${dark.join(";")}}}` : "";
|
|
62
|
-
return
|
|
62
|
+
return `:root{${root.join(";")}}${darkBlock}`;
|
|
63
|
+
}
|
|
64
|
+
function themeStyle(theme) {
|
|
65
|
+
const css = pageThemeCss(theme);
|
|
66
|
+
return css ? `<style>${css}</style>` : "";
|
|
63
67
|
}
|
|
64
68
|
const runtimeSlot = (name, fallback) => `<!--sf-runtime:${name}:start-->${fallback}<!--sf-runtime:${name}:end-->`;
|
|
65
69
|
function renderFiles(files) {
|
|
@@ -72,8 +76,8 @@ function renderFiles(files) {
|
|
|
72
76
|
function renderFile(file) {
|
|
73
77
|
if (!file)
|
|
74
78
|
return '<p class="sf-empty">File unavailable.</p>';
|
|
75
|
-
// This page is itself the `?spacefast_view=1` representation, so
|
|
76
|
-
//
|
|
79
|
+
// This page is itself the `?spacefast_view=1` representation, so every link
|
|
80
|
+
// points straight at the canonical path.
|
|
77
81
|
const href = escapePageHtml(file.href);
|
|
78
82
|
const name = escapePageHtml(file.name);
|
|
79
83
|
const media = file.mime?.startsWith("image/")
|
|
@@ -81,11 +85,13 @@ function renderFile(file) {
|
|
|
81
85
|
: `<iframe class="sf-preview" src="${href}" title="Preview of ${name}" loading="lazy" sandbox></iframe>`;
|
|
82
86
|
return `<div class="sf-preview-wrap">${media}</div><dl class="sf-file-meta"><div><dt>Name</dt><dd>${name}</dd></div>${file.size ? `<div><dt>Size</dt><dd>${escapePageHtml(file.size)}</dd></div>` : ""}${file.mime ? `<div><dt>Type</dt><dd>${escapePageHtml(file.mime)}</dd></div>` : ""}</dl><div class="sf-actions"><a class="sf-button" href="${href}" download>Download</a><a class="sf-ghost" href="${href}">View raw</a></div>`;
|
|
83
87
|
}
|
|
84
|
-
function renderSpacefastBranding(hidden) {
|
|
88
|
+
function renderSpacefastBranding(hidden, supportUrl, partnerBranding) {
|
|
85
89
|
const wordmark = hidden
|
|
86
90
|
? ""
|
|
87
|
-
: `<span class="sf-powered" tabindex="0"><svg class="sf-wordmark" viewBox="0 0
|
|
88
|
-
|
|
91
|
+
: `<span class="sf-powered" tabindex="0"><svg class="sf-wordmark" viewBox="0 0 152 28.9062" role="img" aria-label="Spacefast"><path fill="currentColor" d="${SPACEFAST_WORDMARK_PATH}"></path></svg><span class="sf-powered-line"><a href="https://spacefast.com">Best way to share what your agent made</a></span></span>`;
|
|
92
|
+
const helpUrl = supportUrl ?? (partnerBranding ? null : "https://spacefast.com/help");
|
|
93
|
+
const help = helpUrl ? `<a class="sf-help" href="${escapePageHtml(helpUrl)}">Need help?</a>` : "";
|
|
94
|
+
return `<footer class="sf-colophon"><div class="sf-colophon-row">${wordmark}${help}</div></footer>`;
|
|
89
95
|
}
|
|
90
96
|
function renderElement(element, context) {
|
|
91
97
|
switch (element) {
|
|
@@ -110,7 +116,7 @@ function renderElement(element, context) {
|
|
|
110
116
|
return escapePageHtml(visible < count ? `${label} · showing first ${visible}` : label);
|
|
111
117
|
}
|
|
112
118
|
case "sf-spacefast-branding":
|
|
113
|
-
return renderSpacefastBranding(context.hideSpacefastBranding);
|
|
119
|
+
return renderSpacefastBranding(context.hideSpacefastBranding, context.supportUrl, context.partnerBranding);
|
|
114
120
|
case "sf-files":
|
|
115
121
|
return renderFiles(context.files ?? []);
|
|
116
122
|
case "sf-file":
|
|
@@ -128,15 +134,21 @@ const ELEMENT_PATTERN = /<\s*(sf-[a-z0-9-]+)\b[^>]*>(?:\s*<\s*\/\s*\1\s*>)?|<\s*
|
|
|
128
134
|
export function expandPageHtml(source, context) {
|
|
129
135
|
let output = source.replace(ELEMENT_PATTERN, (_match, paired, unary) => {
|
|
130
136
|
const name = (paired ?? unary)?.toLowerCase();
|
|
137
|
+
// SAFETY: The set membership check admits only names from the PageElement literal set.
|
|
131
138
|
return name && knownElements.has(name) ? renderElement(name, context) : _match;
|
|
132
139
|
});
|
|
133
140
|
if (output.includes("/*sf-default-fonts*/")) {
|
|
134
|
-
output = output.replaceAll("/*sf-default-fonts*/", PAGE_FONT_FACES);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
output = output.replaceAll("/*sf-default-fonts*/", context.useSystemDisplayFont ? "" : PAGE_FONT_FACES);
|
|
142
|
+
if (context.useSystemDisplayFont) {
|
|
143
|
+
output = output.replaceAll('--sf-display-font:"Recoleta",Georgia,serif', "--sf-display-font:var(--sf-font)");
|
|
144
|
+
}
|
|
145
|
+
// Preloading ahead of the inline stylesheet starts the cross-origin
|
|
146
|
+
// fetches with the parse.
|
|
147
|
+
output = context.useSystemDisplayFont
|
|
148
|
+
? output
|
|
149
|
+
: /<\s*style\b/i.test(output)
|
|
150
|
+
? output.replace(/<\s*style\b/i, (tag) => `${PAGE_FONT_PRELOAD_HTML}${tag}`)
|
|
151
|
+
: `${PAGE_FONT_PRELOAD_HTML}${output}`;
|
|
140
152
|
}
|
|
141
153
|
const tokens = themeStyle(context.theme ?? {});
|
|
142
154
|
if (tokens) {
|
|
@@ -187,8 +199,7 @@ export function validatePageSources(input) {
|
|
|
187
199
|
const path = sourcePath.includes("_pages/") ? sourcePath : `_pages/${sourcePath}`;
|
|
188
200
|
const filename = path.slice(path.lastIndexOf("_pages/") + 7);
|
|
189
201
|
const id = filename.replace(/\.html$/i, "");
|
|
190
|
-
//
|
|
191
|
-
// gate and listing pages it has no per-directory form.
|
|
202
|
+
// One review room per Space, so collab has no per-directory form.
|
|
192
203
|
if (!customPageIds.has(id) ||
|
|
193
204
|
filename !== `${id}.html` ||
|
|
194
205
|
(id === "collab" && path !== COLLAB_PAGE_SOURCE)) {
|
|
@@ -216,8 +227,7 @@ export function validatePageSources(input) {
|
|
|
216
227
|
path,
|
|
217
228
|
});
|
|
218
229
|
for (const element of foundElements(source)) {
|
|
219
|
-
// Collab elements
|
|
220
|
-
// review room, meaningless anywhere the SDK is not the point.
|
|
230
|
+
// Collab elements belong to the browser SDK, legal only in the review room.
|
|
221
231
|
if (id === "collab" && collabElements.has(element))
|
|
222
232
|
continue;
|
|
223
233
|
if (!knownElements.has(element))
|
|
@@ -255,8 +265,8 @@ export function validatePageSources(input) {
|
|
|
255
265
|
message: "The index page does not render <sf-files>.",
|
|
256
266
|
path,
|
|
257
267
|
});
|
|
258
|
-
// Without the lane slot the runtime
|
|
259
|
-
//
|
|
268
|
+
// Without the lane slot the runtime cannot inject the sign-in, password
|
|
269
|
+
// and request blocks, and the gate ships dead.
|
|
260
270
|
if (id === "access" && !/<\s*sf-access-lanes\b/i.test(source))
|
|
261
271
|
diagnostics.push({
|
|
262
272
|
severity: "error",
|
|
@@ -374,10 +384,9 @@ export function defaultPageBody(id) {
|
|
|
374
384
|
const source = DEFAULT_PAGE_TEMPLATES[id];
|
|
375
385
|
return source.match(/<body[^>]*>([\s\S]*?)<\/body>/i)?.[1] ?? source;
|
|
376
386
|
}
|
|
377
|
-
// The built-in palette runs through the same readability engine as pushed
|
|
378
|
-
// themes, so buttons and links remain readable for every accepted accent.
|
|
387
|
+
// The built-in palette runs through the same readability engine as pushed themes.
|
|
379
388
|
const defaultPageColors = derivePageColors({});
|
|
380
|
-
const sharedCss = `/*sf-default-fonts*/:root{color-scheme:light dark;--sf-accent:${PAGE_COLOR_DEFAULTS.accent};--sf-bg:${PAGE_COLOR_DEFAULTS.bg};--sf-fg:${PAGE_COLOR_DEFAULTS.fg};--sf-muted:${PAGE_COLOR_DEFAULTS.muted};--sf-card:${PAGE_COLOR_DEFAULTS.card};--sf-border:${PAGE_COLOR_DEFAULTS.border};--sf-link:${defaultPageColors.link};--sf-on-accent:${defaultPageColors.onAccent};--sf-error:#a82d1a;--sf-font
|
|
389
|
+
const sharedCss = `/*sf-default-fonts*/:root{color-scheme:light dark;--sf-accent:${PAGE_COLOR_DEFAULTS.accent};--sf-bg:${PAGE_COLOR_DEFAULTS.bg};--sf-fg:${PAGE_COLOR_DEFAULTS.fg};--sf-muted:${PAGE_COLOR_DEFAULTS.muted};--sf-card:${PAGE_COLOR_DEFAULTS.card};--sf-border:${PAGE_COLOR_DEFAULTS.border};--sf-link:${defaultPageColors.link};--sf-on-accent:${defaultPageColors.onAccent};--sf-error:#a82d1a;--sf-font:-apple-system,BlinkMacSystemFont,"SF Pro Text","SF Pro Display","Segoe UI",system-ui,Roboto,"Helvetica Neue",Arial,sans-serif;--sf-display-font:"Recoleta",Georgia,serif;--sf-mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace}*{box-sizing:border-box}html{min-width:320px;background:var(--sf-bg);-webkit-font-smoothing:antialiased}body{margin:0;min-height:100vh;background:var(--sf-bg);color:var(--sf-fg);font:14px/1.65 var(--sf-font)}a{color:inherit}.sf-page{min-height:100vh;display:flex;flex-direction:column;padding:clamp(22px,4vw,44px)}.sf-header{display:flex;align-items:center;min-height:32px}.sf-page-center .sf-header{justify-content:center}.sf-brand{display:inline-flex;min-width:0;align-items:center;gap:10px;color:var(--sf-fg);text-decoration:none}.sf-brand-mark{width:7px;height:7px;flex:none;background:var(--sf-accent)}.sf-logo{width:28px;height:28px;flex:none;object-fit:contain}.sf-brand strong{min-width:0;overflow-wrap:anywhere;font-size:13px;font-weight:600}.sf-main{flex:1;width:100%;padding:clamp(34px,9vh,84px) 0}.sf-page-plain .sf-main{max-width:680px}.sf-page-center .sf-main{max-width:620px;margin:auto;display:flex;flex-direction:column;align-items:center;text-align:center}.sf-eyebrow{margin:0 0 14px;color:var(--sf-muted);font:500 11px/1.5 var(--sf-mono);letter-spacing:.12em;text-transform:uppercase;overflow-wrap:anywhere}.sf-page-center .sf-eyebrow{margin-bottom:16px}h1{max-width:20ch;margin:0 0 12px;color:var(--sf-fg);font:400 clamp(1.9rem,5vw,2.8rem)/1.22 var(--sf-display-font);letter-spacing:-.012em;text-wrap:balance}.sf-copy,.sf-sub{max-width:52ch;margin:0;color:var(--sf-muted);text-wrap:pretty}.sf-inline-path{font-family:var(--sf-mono);font-size:.9em;color:var(--sf-fg);overflow-wrap:anywhere}.sf-actions{display:flex;flex-wrap:wrap;align-items:center;gap:14px;margin-top:22px}.sf-page-center .sf-actions{justify-content:center}.sf-button{display:inline-flex;min-height:44px;align-items:center;justify-content:center;border:0;background:var(--sf-accent);color:var(--sf-on-accent);padding:10px 16px;font:600 13px/1.4 var(--sf-font);text-decoration:none;cursor:pointer}.sf-ghost{color:var(--sf-muted);text-underline-offset:3px}.sf-button:hover{filter:brightness(1.06)}a:focus-visible,button:focus-visible{outline:2px solid var(--sf-link);outline-offset:2px}.sf-copy+.sf-copy{margin-top:12px}.sf-files{width:100%;margin:24px 0 0;padding:0;list-style:none}.sf-files li{display:flex;min-width:0;align-items:baseline;gap:18px;border-bottom:1px solid var(--sf-border)}.sf-files a{min-width:0;flex:1;padding:10px 2px;color:var(--sf-fg);font-weight:500;text-decoration:none}.sf-files a:hover{color:var(--sf-link)}.sf-file-name{overflow-wrap:anywhere}.sf-file-size{flex:none;padding:10px 2px;color:var(--sf-muted);font:12px/1.5 var(--sf-mono);white-space:nowrap}.sf-empty{max-width:420px;margin:24px 0 0;border:1px dashed var(--sf-border);padding:18px 20px;color:var(--sf-muted)}.sf-preview-wrap{width:min(100%,560px);aspect-ratio:16/9;margin:10px 0 18px;overflow:hidden;background:#fff;outline:1px solid color-mix(in srgb,var(--sf-fg) 10%,transparent);outline-offset:-1px}.sf-preview{display:block;width:100%;height:100%;border:0;object-fit:contain}.sf-file-meta{width:min(100%,560px);margin:0}.sf-file-meta div{display:flex;min-width:0;justify-content:space-between;gap:20px;border-bottom:1px solid var(--sf-border);padding:9px 2px}.sf-file-meta dt{color:var(--sf-fg);font-weight:600}.sf-file-meta dd{min-width:0;margin:0;color:var(--sf-muted);font-family:var(--sf-mono);overflow-wrap:anywhere;text-align:right}.sf-colophon{display:flex;flex-direction:column;align-items:center;gap:10px;padding:44px 0 26px;margin-top:auto;color:var(--sf-muted)}.sf-page-plain .sf-colophon{align-items:flex-start}.sf-colophon-row{display:flex;align-items:center;gap:20px;flex-wrap:wrap}.sf-powered{display:inline-flex;align-items:center;gap:10px;cursor:pointer;outline:none}.sf-powered-line{display:none;font-size:12px;color:var(--sf-muted)}.sf-powered:hover .sf-powered-line,.sf-powered:focus .sf-powered-line,.sf-powered:focus-within .sf-powered-line{display:inline}.sf-powered-line a{color:var(--sf-link);text-decoration:underline;text-underline-offset:3px}.sf-help{font-size:12px;color:var(--sf-muted);text-decoration:underline;text-underline-offset:3px}.sf-help:hover{color:var(--sf-fg)}.sf-wordmark{display:block;flex:none;width:auto;height:14px;color:var(--sf-fg)}@media(max-width:520px){.sf-page{padding:20px}.sf-button{min-height:48px}.sf-file-meta div{flex-direction:column;gap:2px}.sf-file-meta dd{text-align:left}.sf-colophon{padding-top:32px}}@media(prefers-color-scheme:dark){:root{--sf-bg:${PAGE_COLOR_DEFAULTS.darkBg};--sf-fg:${PAGE_COLOR_DEFAULTS.darkFg};--sf-muted:${PAGE_COLOR_DEFAULTS.darkMuted};--sf-card:${PAGE_COLOR_DEFAULTS.darkCard};--sf-border:${PAGE_COLOR_DEFAULTS.darkBorder};--sf-link:${defaultPageColors.linkDark};--sf-error:#ff8a7a}.sf-preview-wrap{outline-color:#ffffff1a}}@media(forced-colors:active){.sf-brand-mark{background:CanvasText}.sf-button{border:1px solid ButtonText}.sf-powered:focus{outline:1px solid CanvasText;outline-offset:3px}}`;
|
|
381
390
|
const accessCss = `.sf-page.access .sf-main{max-width:26rem;padding:clamp(42px,8vh,72px) 0}.sf-page.access h1{max-width:18ch;margin-bottom:14px;font:600 clamp(2rem,7vw,2.65rem)/1.12 var(--sf-font);letter-spacing:-.035em}.sf-page.access .sf-copy{font-size:15px;line-height:1.6}.sf-access-default{display:flex;width:100%;flex-direction:column;align-items:center}.sf-access-status{padding:10px 12px;border:1px solid color-mix(in srgb,var(--sf-fg) 10%,transparent);border-radius:8px;background:color-mix(in srgb,var(--sf-fg) 4%,transparent);color:var(--sf-fg)}.sf-access-lanes{display:flex;flex-direction:column;gap:16px;width:min(23rem,100%);margin-top:30px;text-align:left}.sf-access-lanes form{display:flex;flex-direction:column;gap:14px;margin:0}.sf-input{box-sizing:border-box;width:100%;min-height:46px;padding:11px 12px;border:1px solid color-mix(in srgb,var(--sf-fg) 18%,transparent);border-radius:8px;outline:2px solid transparent;outline-offset:-1px;background:color-mix(in srgb,var(--sf-bg) 92%,var(--sf-fg));color:var(--sf-fg);font:500 15px/1.45 var(--sf-font);appearance:none}.sf-input::placeholder{color:color-mix(in srgb,var(--sf-muted) 82%,transparent);font:inherit}.sf-input:hover{border-color:color-mix(in srgb,var(--sf-fg) 34%,transparent)}.sf-input:focus-visible{border-color:var(--sf-accent);outline-color:var(--sf-accent)}textarea.sf-input{min-height:92px;resize:vertical}.sf-label{color:var(--sf-fg);font:600 13px/1.4 var(--sf-font)}.sf-access-lanes .sf-button{width:100%;min-height:46px;border-radius:8px}.sf-access-or{display:flex;align-items:center;gap:12px;color:var(--sf-muted);font-size:13px}.sf-access-or:before,.sf-access-or:after{content:"";flex:1;border-top:1px solid color-mix(in srgb,var(--sf-fg) 12%,transparent)}.sf-error{margin:0;color:var(--sf-error);font-size:14px}.sf-access-password-row{display:flex;gap:8px}.sf-access-password-row .sf-input{min-width:0;flex:1}.sf-access-password-row .sf-button{width:auto;flex:none}.sf-access-request{padding-top:2px}.sf-access-request summary{padding:4px 0;cursor:pointer;color:var(--sf-muted);font:550 14px/1.5 var(--sf-font)}.sf-access-request summary:hover{color:var(--sf-fg)}.sf-access-request[open] summary{color:var(--sf-fg)}.sf-access-request form{margin-top:16px}@media(max-width:520px){.sf-page.access .sf-main{padding:38px 0}.sf-page.access h1{font-size:2rem}.sf-page.access .sf-copy{font-size:16px}.sf-input{min-height:48px;font-size:16px}.sf-access-lanes .sf-button{min-height:48px}.sf-access-password-row{flex-direction:column}.sf-access-password-row .sf-button{width:100%}}@media(prefers-color-scheme:dark){.sf-access-status{background:color-mix(in srgb,var(--sf-fg) 5%,transparent)}}`;
|
|
382
391
|
const pageHeader = '<header class="sf-header"><a class="sf-brand" href="/" aria-label="Homepage"><span class="sf-brand-mark" aria-hidden="true"></span><sf-logo></sf-logo><strong><sf-name></sf-name></strong></a></header>';
|
|
383
392
|
const pageFooter = "<sf-spacefast-branding></sf-spacefast-branding>";
|
|
@@ -394,14 +403,58 @@ export const DEFAULT_PAGE_TEMPLATES = {
|
|
|
394
403
|
index: document("index", "<sf-page-title></sf-page-title>", '<p class="sf-eyebrow"><sf-page-path></sf-page-path></p><h1><sf-page-title></sf-page-title></h1><p class="sf-sub"><sf-page-summary></sf-page-summary></p><sf-files></sf-files>', "plain"),
|
|
395
404
|
preview: document("preview", "<sf-page-title></sf-page-title>", '<p class="sf-eyebrow"><sf-page-path></sf-page-path></p><h1><sf-page-title></sf-page-title></h1><sf-file></sf-file>', "plain"),
|
|
396
405
|
};
|
|
406
|
+
const PLATFORM_PAGE_COPY = {
|
|
407
|
+
login: [401, "This space is for members", "Sign in to continue — you’ll come right back here."],
|
|
408
|
+
undeployed: [503, "Waiting for launch", "This space hasn’t been published yet. Check back soon."],
|
|
409
|
+
suspended: [402, "This space is paused", "Serving is on hold until billing is sorted out."],
|
|
410
|
+
legal: [
|
|
411
|
+
451,
|
|
412
|
+
"Unavailable for legal reasons",
|
|
413
|
+
"This space is blocked in response to a legal demand.",
|
|
414
|
+
],
|
|
415
|
+
gone: [404, "Nothing here", "This space is no longer available."],
|
|
416
|
+
"runtime-error": [
|
|
417
|
+
500,
|
|
418
|
+
"Something broke on our side",
|
|
419
|
+
"The runtime hit an error serving this page. Try again in a moment.",
|
|
420
|
+
],
|
|
421
|
+
"rate-limited": [
|
|
422
|
+
429,
|
|
423
|
+
"Slow down a second",
|
|
424
|
+
"Too many requests hit this space at once. Give it a moment and try again.",
|
|
425
|
+
],
|
|
426
|
+
"tier-unavailable": [503, "Back in a bit", "This space is temporarily unavailable."],
|
|
427
|
+
"proxy-error": [502, "The upstream missed the connection", "Try again in a moment."],
|
|
428
|
+
"method-not-allowed": [405, "That method won’t work here", "Try the request another way."],
|
|
429
|
+
"content-too-large": [413, "That request is too large", "Send a smaller request and try again."],
|
|
430
|
+
};
|
|
431
|
+
function platformDocument(id) {
|
|
432
|
+
const [status, title, description] = PLATFORM_PAGE_COPY[id];
|
|
433
|
+
return `<!doctype html>\n<html lang="en">\n<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex"><title>${title}</title><style>${sharedCss}</style></head>\n<body><div class="sf-page sf-page-center">${pageHeader}<main class="sf-main"><p class="sf-eyebrow">${status}</p><h1>${title}</h1><p class="sf-copy">${description}</p></main>${pageFooter}</div></body>\n</html>\n`;
|
|
434
|
+
}
|
|
435
|
+
export const DEFAULT_PLATFORM_PAGE_TEMPLATES = {
|
|
436
|
+
login: platformDocument("login"),
|
|
437
|
+
undeployed: platformDocument("undeployed"),
|
|
438
|
+
suspended: platformDocument("suspended"),
|
|
439
|
+
legal: platformDocument("legal"),
|
|
440
|
+
gone: platformDocument("gone"),
|
|
441
|
+
"runtime-error": platformDocument("runtime-error"),
|
|
442
|
+
"rate-limited": platformDocument("rate-limited"),
|
|
443
|
+
"tier-unavailable": platformDocument("tier-unavailable"),
|
|
444
|
+
"proxy-error": platformDocument("proxy-error"),
|
|
445
|
+
"method-not-allowed": platformDocument("method-not-allowed"),
|
|
446
|
+
"content-too-large": platformDocument("content-too-large"),
|
|
447
|
+
};
|
|
397
448
|
export const DEFAULT_LAYOUT_TEMPLATE = `<!doctype html>\n<html lang="en">\n<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title><sf-page-title></sf-page-title></title><style>${sharedCss}</style></head>\n<body><sf-content></sf-content></body>\n</html>\n`;
|
|
398
449
|
export function defaultPageTemplate(id) {
|
|
399
450
|
return DEFAULT_PAGE_TEMPLATES[id];
|
|
400
451
|
}
|
|
401
452
|
export function pageIdFromFilename(filename) {
|
|
402
453
|
const id = filename.replace(/\.html$/, "");
|
|
454
|
+
// SAFETY: The set membership check admits only ids from the CustomPageId literal set.
|
|
403
455
|
return customPageIds.has(id) ? id : null;
|
|
404
456
|
}
|
|
405
457
|
export function allDefaultPageTemplates() {
|
|
458
|
+
// SAFETY: SITE_PAGE_IDS maps every SitePageId to its corresponding default template entry.
|
|
406
459
|
return Object.fromEntries(SITE_PAGE_IDS.map((id) => [id, DEFAULT_PAGE_TEMPLATES[id]]));
|
|
407
460
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** EU 27 + CH, IS, LI, NO, GB. */
|
|
2
|
+
export declare const GDPR_COUNTRY_CODES: Set<string>;
|
|
3
|
+
export declare const GEO_COUNTRY_UNKNOWN = "unknown";
|
|
4
|
+
/**
|
|
5
|
+
* GDPR applicability FAILS CLOSED: an unknown or missing country is treated as
|
|
6
|
+
* inside the GDPR zone (wpcom `isGdprCountry`). Country codes are ISO 3166-1
|
|
7
|
+
* alpha-2, case-insensitive.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isGdprCountry(countryCode: string | null | undefined): boolean;
|
|
10
|
+
/** US states with comprehensive privacy laws, lowercase full names (wpcom `CCPARegions`). */
|
|
11
|
+
export declare const CCPA_REGIONS: Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* CCPA applicability FAILS OPEN: an unknown or missing region is NOT assumed
|
|
14
|
+
* to be a CCPA region (wpcom `pertainsToCCPA`: "if we aren't sure what the
|
|
15
|
+
* region is we shouldn't assume it pertains to CCPA"). Matches on substring,
|
|
16
|
+
* like wpcom, so a region string such as "california" or "California, US"
|
|
17
|
+
* both qualify.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isCcpaRegion(countryCode: string | null | undefined, region: string | null | undefined): boolean;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Privacy-regulation region lists, ported from wpcom's a8c-analytics
|
|
2
|
+
// (`a8c-analytics-src/kit/constants.ts`, verified 2026-08-23) so Spacefast's
|
|
3
|
+
// consent behavior matches WordPress.com's. One home for both the server-side
|
|
4
|
+
// Tag SDK and the dashboard consent engine.
|
|
5
|
+
/** EU 27 + CH, IS, LI, NO, GB. */
|
|
6
|
+
export const GDPR_COUNTRY_CODES = new Set([
|
|
7
|
+
"AT",
|
|
8
|
+
"BE",
|
|
9
|
+
"BG",
|
|
10
|
+
"CY",
|
|
11
|
+
"CZ",
|
|
12
|
+
"DE",
|
|
13
|
+
"DK",
|
|
14
|
+
"EE",
|
|
15
|
+
"ES",
|
|
16
|
+
"FI",
|
|
17
|
+
"FR",
|
|
18
|
+
"GR",
|
|
19
|
+
"HR",
|
|
20
|
+
"HU",
|
|
21
|
+
"IE",
|
|
22
|
+
"IT",
|
|
23
|
+
"LT",
|
|
24
|
+
"LU",
|
|
25
|
+
"LV",
|
|
26
|
+
"MT",
|
|
27
|
+
"NL",
|
|
28
|
+
"PL",
|
|
29
|
+
"PT",
|
|
30
|
+
"RO",
|
|
31
|
+
"SE",
|
|
32
|
+
"SI",
|
|
33
|
+
"SK",
|
|
34
|
+
"CH",
|
|
35
|
+
"IS",
|
|
36
|
+
"LI",
|
|
37
|
+
"NO",
|
|
38
|
+
"GB",
|
|
39
|
+
]);
|
|
40
|
+
export const GEO_COUNTRY_UNKNOWN = "unknown";
|
|
41
|
+
/**
|
|
42
|
+
* GDPR applicability FAILS CLOSED: an unknown or missing country is treated as
|
|
43
|
+
* inside the GDPR zone (wpcom `isGdprCountry`). Country codes are ISO 3166-1
|
|
44
|
+
* alpha-2, case-insensitive.
|
|
45
|
+
*/
|
|
46
|
+
export function isGdprCountry(countryCode) {
|
|
47
|
+
if (!countryCode || countryCode.toLowerCase() === GEO_COUNTRY_UNKNOWN) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return GDPR_COUNTRY_CODES.has(countryCode.toUpperCase());
|
|
51
|
+
}
|
|
52
|
+
/** US states with comprehensive privacy laws, lowercase full names (wpcom `CCPARegions`). */
|
|
53
|
+
export const CCPA_REGIONS = new Set([
|
|
54
|
+
"california",
|
|
55
|
+
"colorado",
|
|
56
|
+
"connecticut",
|
|
57
|
+
"delaware",
|
|
58
|
+
"florida",
|
|
59
|
+
"indiana",
|
|
60
|
+
"iowa",
|
|
61
|
+
"kentucky",
|
|
62
|
+
"maryland",
|
|
63
|
+
"minnesota",
|
|
64
|
+
"montana",
|
|
65
|
+
"nebraska",
|
|
66
|
+
"new hampshire",
|
|
67
|
+
"new jersey",
|
|
68
|
+
"oregon",
|
|
69
|
+
"rhode island",
|
|
70
|
+
"tennessee",
|
|
71
|
+
"texas",
|
|
72
|
+
"utah",
|
|
73
|
+
"virginia",
|
|
74
|
+
]);
|
|
75
|
+
/**
|
|
76
|
+
* CCPA applicability FAILS OPEN: an unknown or missing region is NOT assumed
|
|
77
|
+
* to be a CCPA region (wpcom `pertainsToCCPA`: "if we aren't sure what the
|
|
78
|
+
* region is we shouldn't assume it pertains to CCPA"). Matches on substring,
|
|
79
|
+
* like wpcom, so a region string such as "california" or "California, US"
|
|
80
|
+
* both qualify.
|
|
81
|
+
*/
|
|
82
|
+
export function isCcpaRegion(countryCode, region) {
|
|
83
|
+
if (countryCode && countryCode.toUpperCase() !== "US") {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (!region || region.toLowerCase() === GEO_COUNTRY_UNKNOWN) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const normalized = region.toLowerCase();
|
|
90
|
+
return [...CCPA_REGIONS].some((candidate) => normalized.includes(candidate));
|
|
91
|
+
}
|
|
@@ -16,17 +16,16 @@ export declare function createPublishFormData(input: {
|
|
|
16
16
|
upload: DirectPublishUpload;
|
|
17
17
|
clientAttribution: string;
|
|
18
18
|
spaceId?: string | undefined;
|
|
19
|
+
teamId?: string | undefined;
|
|
19
20
|
slug?: string | undefined;
|
|
20
21
|
title?: string | undefined;
|
|
21
22
|
description?: string | undefined;
|
|
22
23
|
/** Live promotion intent for archive builds; null produces an unpromoted ready version. */
|
|
23
24
|
channel?: "live" | null | undefined;
|
|
24
25
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* declared-session path; the multipart intake reads it from the `metadata`
|
|
29
|
-
* JSON field and folds it into `publishRequestSchema.sourceMetadata`.
|
|
26
|
+
* Build provenance for the version's build/deploy read models. A compiled
|
|
27
|
+
* runtime declares its artifact under its own key (e.g. `statticZero`); the
|
|
28
|
+
* multipart intake folds it into `publishRequestSchema.sourceMetadata`.
|
|
30
29
|
*/
|
|
31
30
|
sourceMetadata?: Record<string, unknown> | undefined;
|
|
32
31
|
/** Bounded build transcript recorded on the version when a build ran. */
|
|
@@ -3,11 +3,10 @@ export function createPublishFormData(input) {
|
|
|
3
3
|
const form = new FormData();
|
|
4
4
|
if (input.spaceId)
|
|
5
5
|
form.append("spaceId", input.spaceId);
|
|
6
|
+
if (input.teamId)
|
|
7
|
+
form.append("teamId", input.teamId);
|
|
6
8
|
if (input.description)
|
|
7
9
|
form.append("description", input.description);
|
|
8
|
-
// The multipart intake merges this JSON field into the parsed publish body
|
|
9
|
-
// (it reads `payload`/`publish`/`metadata`), so build provenance rides the
|
|
10
|
-
// one-shot form exactly as it rides the CLI's declared-session JSON body.
|
|
11
10
|
if (input.sourceMetadata || input.buildLog || input.channel !== undefined) {
|
|
12
11
|
form.append("metadata", JSON.stringify({
|
|
13
12
|
...(input.sourceMetadata ? { sourceMetadata: input.sourceMetadata } : {}),
|