@tokenoftrust/storefront-runner 2.0.54 → 2.0.56
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.
|
@@ -128,7 +128,56 @@
|
|
|
128
128
|
function showMsg(kind, text) {
|
|
129
129
|
const el = $("msg");
|
|
130
130
|
el.className = "msg " + kind;
|
|
131
|
-
el.
|
|
131
|
+
el.replaceChildren();
|
|
132
|
+
if (typeof text === "string") {
|
|
133
|
+
el.textContent = text;
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (text && text.nodeType) el.appendChild(text);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function showProvisionPrompt(message) {
|
|
140
|
+
const wrap = document.createElement("div");
|
|
141
|
+
const line = document.createElement("p");
|
|
142
|
+
line.style.margin = "0 0 .6rem";
|
|
143
|
+
line.textContent = message || "This store's repo isn't provisioned yet.";
|
|
144
|
+
const btn = document.createElement("button");
|
|
145
|
+
btn.type = "button";
|
|
146
|
+
btn.id = "provisionBtn";
|
|
147
|
+
btn.className = "secondary";
|
|
148
|
+
btn.textContent = "Provision now?";
|
|
149
|
+
btn.title = "Create the store repo on this environment, then retry the invite";
|
|
150
|
+
btn.addEventListener("click", () => provisionStore(btn));
|
|
151
|
+
wrap.appendChild(line);
|
|
152
|
+
wrap.appendChild(btn);
|
|
153
|
+
showMsg("err", wrap);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function provisionStore(btn) {
|
|
157
|
+
if (btn) btn.disabled = true;
|
|
158
|
+
showMsg("", "Provisioning store repo…");
|
|
159
|
+
try {
|
|
160
|
+
const res = await fetch("/api/grants/provision-store", {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { "content-type": "application/json", accept: "application/json" },
|
|
163
|
+
body: JSON.stringify({ tenant, confirm: true }),
|
|
164
|
+
});
|
|
165
|
+
const body = await res.json().catch(() => ({}));
|
|
166
|
+
if (res.ok) {
|
|
167
|
+
const created = body.created ? "created" : "already present";
|
|
168
|
+
showMsg(
|
|
169
|
+
"ok",
|
|
170
|
+
"Store repo " + created + " for " + (body.tenant || tenant) +
|
|
171
|
+
". Click Send invite again.",
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
showMsg("err", body.error || ("Provision failed (HTTP " + res.status + ")."));
|
|
175
|
+
}
|
|
176
|
+
} catch (e) {
|
|
177
|
+
showMsg("err", "Provision failed: " + (e && e.message ? e.message : e));
|
|
178
|
+
} finally {
|
|
179
|
+
if (btn) btn.disabled = false;
|
|
180
|
+
}
|
|
132
181
|
}
|
|
133
182
|
|
|
134
183
|
async function invite() {
|
|
@@ -161,6 +210,11 @@
|
|
|
161
210
|
$("email").value = "";
|
|
162
211
|
if (noteEl) noteEl.value = "";
|
|
163
212
|
loadMembers();
|
|
213
|
+
} else if (
|
|
214
|
+
res.status === 409 &&
|
|
215
|
+
(body.code === "store-not-provisioned" || body.reason === "storeNotProvisioned")
|
|
216
|
+
) {
|
|
217
|
+
showProvisionPrompt(body.error);
|
|
164
218
|
} else {
|
|
165
219
|
showMsg("err", body.error || ("Invite failed (HTTP " + res.status + ")."));
|
|
166
220
|
}
|
|
@@ -1,40 +1,10 @@
|
|
|
1
|
-
/*
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/* Shared commerce chrome. Theme values come from the token contract in
|
|
2
|
+
docs/architecture/token-contract.md; tenant-private brand variables and
|
|
3
|
+
hard-coded brand colors do not belong here. Dark surfaces derive from
|
|
4
|
+
--color-text and --color-primary-contrast.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.member-cue / .announce-bar / .age-gate / .trust-marks) into a SINGLE stylesheet
|
|
8
|
-
every storefront links and themes via
|
|
9
|
-
tokens only. Per decisions raw-chrome-single-stylesheet + materialization-compat.
|
|
10
|
-
|
|
11
|
-
CONTRACT-TOKEN-ONLY (docs/architecture/token-contract.md, unit F1). Every value
|
|
12
|
-
here reads the shared `--color-*` / `--font-*` / `--radius-*` / `--shadow-*` /
|
|
13
|
-
`--container-max` vocabulary and NO tenant-private brand var (never `--navy`,
|
|
14
|
-
`--brand-red`, unprefixed `--sale`/`--ink`/`--surface`), and no hard-coded brand
|
|
15
|
-
hex. On-dark surfaces (footer, age-gate overlay, cat cards) derive from
|
|
16
|
-
`--color-text` (the deep ink surface) + `--color-primary-contrast` via
|
|
17
|
-
`color-mix`, so a tenant reskins the whole chrome by supplying tokens/aliases.
|
|
18
|
-
|
|
19
|
-
Token mapping from the old per-tenant vars (applied by the integration units
|
|
20
|
-
that alias each tenant's brand vocabulary onto the contract):
|
|
21
|
-
--navy / --brand-red -> --color-primary (nav active, cart, CTA fill)
|
|
22
|
-
--navy-dark -> --color-primary-hover
|
|
23
|
-
--sale -> --color-sale (flag / NEW / marquee emphasis)
|
|
24
|
-
--surface / --surface-2 / --border / --muted -> --color-{surface,surface-2,border,muted}
|
|
25
|
-
--ink / --ink-2 -> --color-text
|
|
26
|
-
--brand-blue -> --color-accent (member cue, promo/trust spark)
|
|
27
|
-
--container -> --container-max
|
|
28
|
-
footer / age-gate ink surfaces -> --color-text (+ --color-primary-contrast)
|
|
29
|
-
|
|
30
|
-
DELIVERY: platform static asset, served at /shared/commerce-chrome.css (root,
|
|
31
|
-
global — like /brand, /js). It is a LINKED sheet, not inline: commerce routes
|
|
32
|
-
(/products, /collections) carry linked <link> sheets but strip inline <style>,
|
|
33
|
-
which is exactly the PVC breakage this closes. The canonical chrome partial (F3)
|
|
34
|
-
links it; per-tenant chrome retires its copied mkt.css at integration (I1/I2/I3).
|
|
35
|
-
rawMarketingHtml.prefixTenantLinks exempts /shared/ so the link survives
|
|
36
|
-
path-prefix tenant routing (as it already does for /tenants/).
|
|
37
|
-
========================================================================== */
|
|
6
|
+
This linked platform asset is served at /shared/commerce-chrome.css.
|
|
7
|
+
rawMarketingHtml.prefixTenantLinks leaves /shared/ paths unprefixed. */
|
|
38
8
|
|
|
39
9
|
/* --- Layout --------------------------------------------------------------- */
|
|
40
10
|
/* Clip (not hidden) so a closed off-canvas mobile drawer never creates a
|
|
@@ -132,7 +102,7 @@ body { overflow-x: clip; }
|
|
|
132
102
|
.site-header { position: sticky; top: 0; z-index: 100; background: var(--color-surface); border-bottom: 1px solid var(--color-border); }
|
|
133
103
|
.site-header .header-row { gap: clamp(12px, 3vw, 40px); }
|
|
134
104
|
.header-row { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(12px, 2.5vw, 32px); padding-block: 14px; }
|
|
135
|
-
/* Logo: image mark
|
|
105
|
+
/* Logo: image mark and text wordmark are both supported. */
|
|
136
106
|
/* object-fit is load-bearing, not polish: with height pinned and width auto, a logo whose
|
|
137
107
|
box is capped by max-width is otherwise STRETCHED to fill it. Measured 2026-09-21 on a
|
|
138
108
|
real tenant: a 500x196 logo (ratio 2.55) rendered at 154x34 (ratio 4.53) on mobile.
|
|
@@ -196,11 +166,11 @@ body { overflow-x: clip; }
|
|
|
196
166
|
/* --- Primary nav + mega-menu ---------------------------------------------- */
|
|
197
167
|
.primary-nav { flex: 1; }
|
|
198
168
|
.primary-nav > ul { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(10px, 1.6vw, 22px); }
|
|
199
|
-
/* Flat links
|
|
169
|
+
/* Flat primary links */
|
|
200
170
|
.primary-nav a { font-weight: 600; font-size: .92rem; color: var(--color-text); padding: 6px 2px; border-bottom: 2px solid transparent; transition: color .15s, border-color .15s; }
|
|
201
171
|
.primary-nav a:hover { color: var(--color-primary); border-color: var(--color-primary); }
|
|
202
172
|
.primary-nav a.is-flag { color: var(--color-sale); }
|
|
203
|
-
/* Nav items with dropdowns
|
|
173
|
+
/* Nav items with dropdowns */
|
|
204
174
|
.primary-nav .nav-item { position: relative; }
|
|
205
175
|
.primary-nav .nav-item > a { display: inline-flex; align-items: center; gap: 5px; font-weight: 600; font-size: .92rem; color: var(--color-text); padding: 14px 2px; border-bottom: 2px solid transparent; transition: color .15s, border-color .15s; }
|
|
206
176
|
.primary-nav .nav-item > a:hover, .primary-nav .nav-item:focus-within > a { color: var(--color-primary); border-color: var(--color-primary); }
|
|
@@ -211,7 +181,7 @@ body { overflow-x: clip; }
|
|
|
211
181
|
display: inline-block; background: var(--color-sale); color: var(--color-primary-contrast); font-size: .58rem; font-weight: 800;
|
|
212
182
|
letter-spacing: .06em; padding: 2px 5px; border-radius: var(--radius-sm); margin-left: 6px; vertical-align: middle;
|
|
213
183
|
}
|
|
214
|
-
/* Second-row nav bar
|
|
184
|
+
/* Second-row nav bar and its container */
|
|
215
185
|
.primary-nav-bar { border-top: 1px solid var(--color-border); background: var(--color-surface); }
|
|
216
186
|
.primary-nav-bar .container { padding-block: 4px; }
|
|
217
187
|
.primary-nav-bar .primary-nav > ul { gap: clamp(10px, 1.4vw, 20px); }
|
|
@@ -352,7 +322,7 @@ a.article-card__tag:hover { text-decoration: underline; }
|
|
|
352
322
|
.footer-col ul { display: flex; flex-direction: column; gap: 9px; }
|
|
353
323
|
.footer-col a { color: color-mix(in srgb, var(--color-primary-contrast) 78%, transparent); font-size: .92rem; }
|
|
354
324
|
.footer-col a:hover { color: var(--color-primary-contrast); text-decoration: underline; }
|
|
355
|
-
/* Newsletter signup
|
|
325
|
+
/* Newsletter signup */
|
|
356
326
|
.footer-news { margin-top: 14px; }
|
|
357
327
|
.footer-news__row { display: flex; gap: 8px; }
|
|
358
328
|
.footer-news input[type="email"] { flex: 1; min-width: 0; border: 1px solid color-mix(in srgb, var(--color-primary-contrast) 22%, transparent); border-radius: var(--radius-md); padding: 10px 12px; font-size: .88rem; background: color-mix(in srgb, var(--color-text) 88%, var(--color-primary-contrast)); color: var(--color-primary-contrast); }
|
|
@@ -391,7 +361,7 @@ a.article-card__tag:hover { text-decoration: underline; }
|
|
|
391
361
|
}
|
|
392
362
|
.pay-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
|
|
393
363
|
.pay-badges span { font-size: .62rem; font-weight: 700; letter-spacing: .04em; color: color-mix(in srgb, var(--color-primary-contrast) 82%, transparent); border: 1px solid color-mix(in srgb, var(--color-primary-contrast) 22%, transparent); border-radius: var(--radius-sm); padding: 4px 7px; }
|
|
394
|
-
/* Trust marks strip
|
|
364
|
+
/* Trust marks strip */
|
|
395
365
|
.trust-marks { display: flex; flex-wrap: wrap; gap: 8px 18px; margin-top: 30px; padding-top: 20px; border-top: 1px solid color-mix(in srgb, var(--color-primary-contrast) 12%, transparent); }
|
|
396
366
|
.trust-marks span { display: inline-flex; align-items: center; gap: 7px; font-size: .82rem; font-weight: 600; color: color-mix(in srgb, var(--color-primary-contrast) 82%, transparent); }
|
|
397
367
|
.trust-marks svg { width: 17px; height: 17px; stroke: var(--color-accent); fill: none; stroke-width: 1.8; }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TENANT_CACHE projection for staff/operator store provision.
|
|
3
|
+
*
|
|
4
|
+
* Lives under src/config so pages/lib may call it without crossing the
|
|
5
|
+
* tenancy-resolver-fence (only config/middleware may import kvTenantResolver).
|
|
6
|
+
*/
|
|
7
|
+
import type { TenantConfig } from "@tot/public-runtime";
|
|
8
|
+
import { putTenant } from "@/config/kvTenantResolver";
|
|
9
|
+
|
|
10
|
+
export async function projectProvisionedTenant(
|
|
11
|
+
kv: KVNamespace,
|
|
12
|
+
config: TenantConfig,
|
|
13
|
+
repoMeta: { owner: string; repo: string; cloneUrl: string | null },
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
await putTenant(kv, config);
|
|
16
|
+
await kv.put(
|
|
17
|
+
`repo:${config.tenant_id}`,
|
|
18
|
+
JSON.stringify({
|
|
19
|
+
owner: repoMeta.owner,
|
|
20
|
+
repo: repoMeta.repo,
|
|
21
|
+
cloneUrl: repoMeta.cloneUrl,
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.56",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
@@ -479,6 +479,14 @@ export const ACTION_CATALOG = {
|
|
|
479
479
|
argsAllow: ["resource", "capability"], renderedAllow: [],
|
|
480
480
|
desc: "A ToT-staff operator entered a vendor tenant (impersonation boundary).",
|
|
481
481
|
},
|
|
482
|
+
"dashboard.store_provision": {
|
|
483
|
+
// grants/provision-store.ts — "Provision now?" from the Team invite dead-end when
|
|
484
|
+
// the store repo is not yet provisioned. Same near-side admission as team-invite
|
|
485
|
+
// (membership → merchant/dev, staff selection → admin). Args are counts/ids only.
|
|
486
|
+
actors: ["admin", "merchant", "dev"], source: "ui", sinks: LIFECYCLE,
|
|
487
|
+
argsAllow: ["resource"], renderedAllow: ["created", "repo"],
|
|
488
|
+
desc: "A store's forge repo was provisioned from the Team console.",
|
|
489
|
+
},
|
|
482
490
|
"cache.purged": {
|
|
483
491
|
// cache-purge.ts — bumps a tenant's edge page-cache version (the ETL load step calls
|
|
484
492
|
// it per tenant post-migration). Bearer-secret service call (no session) → emitted
|