@bridge-ds/toolbar 0.6.0 → 0.8.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-XrLgL3hM.js","sources":["../src/pass.ts","../src/BridgeToolbar.tsx","../src/scope.ts","../src/reads.ts","../src/config.ts"],"sourcesContent":["// The read pass Bridge Studio hands a team member's tab (Bridge's api/_lib/read-pass.ts): it lets the toolbar read the\n// change of a PRIVATE product through Bridge's read proxy, read only, for a few hours. Studio puts it in front of the\n// URL fragment (`#bridge-pass=<pass>&<the hash route>`): a fragment never reaches the preview host or its logs. The\n// toolbar takes it out of the address before the product's router reads the hash (at import), keeps it for this tab\n// (sessionStorage), and sends it with its reads as `&pass=` (still a \"simple\" request: no header, no credentials).\nimport type { StorageLike } from './session';\n\nexport const PASS_PARAM = 'bridge-pass';\nexport const PASS_KEY = 'bridge-toolbar:pass';\nconst PASS = /^[A-Za-z0-9._-]{16,4096}$/;\n\n/** The pass at the front (or the end) of a fragment, and the fragment without it. */\nexport function splitPass(hash: string): { pass: string | null; rest: string } {\n const h = hash.replace(/^#/, '');\n const front = /^bridge-pass=([^&]*)(?:&([\\s\\S]*))?$/.exec(h);\n if (front) return { pass: PASS.test(front[1]!) ? front[1]! : null, rest: front[2] ?? '' };\n const end = /^([\\s\\S]*?)&bridge-pass=([^&]*)$/.exec(h);\n if (end) return { pass: PASS.test(end[2]!) ? end[2]! : null, rest: end[1]! };\n return { pass: null, rest: h };\n}\n\n/**\n * Takes the pass out of the address (history.replaceState: no new entry, no reload) and keeps it for the tab. Returns\n * the pass of this tab: the one just taken, else the one kept. Blocked storage keeps it for this page only.\n */\nexport function takePass(loc: { href: string; hash: string }, storage: StorageLike | null, replace: (url: string) => void): string | null {\n const { pass, rest } = splitPass(loc.hash);\n if (/^#?bridge-pass=|&bridge-pass=/.test(loc.hash)) {\n const url = new URL(loc.href);\n url.hash = rest;\n replace(url.href.replace(/#$/, ''));\n }\n if (pass) {\n memory = pass;\n try {\n storage?.setItem(PASS_KEY, pass);\n } catch {\n // Blocked storage: this page keeps it.\n }\n return pass;\n }\n return readPass(storage);\n}\n\nlet memory: string | null = null;\n\n/** The pass of this tab, or null. */\nexport function readPass(storage: StorageLike | null): string | null {\n let kept: string | null = null;\n try {\n kept = storage?.getItem(PASS_KEY) ?? null;\n } catch {\n kept = null;\n }\n const p = kept ?? memory;\n return p && PASS.test(p) ? p : null;\n}\n\n/** Tests start fresh. */\nexport function resetPass() {\n memory = null;\n}\n\nfunction session(): Storage | null {\n try {\n return window.sessionStorage;\n } catch {\n return null;\n }\n}\n\n/** On import in a browser: the pass leaves the address before anything reads the hash. */\nexport function takePassFromAddress(): string | null {\n if (typeof window === 'undefined') return null;\n try {\n return takePass(window.location, session(), (url) => window.history.replaceState(window.history.state, '', url));\n } catch {\n return null;\n }\n}\n\n/** The pass of this tab, in a browser. */\nexport const tabPass = (): string | null => (typeof window === 'undefined' ? null : readPass(session()));\n\n/** `url` (another deployment of the product: Live product, This change) with the tab's pass in front of its fragment. */\nexport function withPass(url: string, pass: string | null): string {\n if (!pass || !PASS.test(pass)) return url;\n const u = new URL(url);\n const rest = splitPass(u.hash).rest;\n u.hash = `${PASS_PARAM}=${pass}${rest ? `&${rest}` : ''}`;\n return u.href;\n}\n","// The one line a product mounts at its root: <BridgeToolbar ds=\"owner/design-system\" product=\"checkout\" />. It renders\n// nothing and loads nothing unless Bridge Studio opened the page with `?bridge=<change>` (kept for the session), then\n// loads the toolbar itself as one lazy chunk, with its styles.\nimport { lazy, Suspense, useState } from 'react';\nimport { takePassFromAddress } from './pass';\nimport { readChanges, type BridgeBuild } from './session';\nimport type { ToolbarScope, ToolbarShop } from './scope';\nimport type { TogetherBase } from './together';\n\nexport interface BridgeToolbarProps {\n /** The design system repository, \"owner/name\". The toolbar reads its bridge.config.json when Studio opens the page. */\n ds?: string;\n /** This product's key in the design system's bridge.config.json products[] (\"checkout\"). */\n product?: string;\n /** Overrides of what the config says: production Studio, this product in production, its preview template. */\n studio?: string | null;\n production?: string | null;\n preview?: string | null;\n /**\n * Bridge's origin, whose read proxy serves the toolbar's GitHub reads (no limit of 60 an hour per visitor). Default\n * production Bridge (https://bridge.floriangouloubi.com); null reads GitHub directly. GitHub directly anyway when\n * Bridge refuses or cannot be reached.\n */\n proxy?: string | null;\n /**\n * The repository this deploy was built from (\"owner/name\", such as VERCEL_GIT_REPO_OWNER/VERCEL_GIT_REPO_SLUG). When\n * it is the product's design copy (products[].design.repo), the toolbar reads the copy's changes. Without it, the\n * `bridge-repo` Studio puts in the link decides.\n */\n repo?: string | null;\n /** The deploy environment (\"production\", \"preview\"), when the host tells it at build time (VERCEL_ENV). */\n deployEnv?: string | null;\n /** Every fact at once, read at build time: no config read at run time (the Admin demo passes it). */\n build?: BridgeBuild;\n /** A hook that says where the page is and moves it (theme, shop). Default: the attributes ThemeProvider sets on <html>. */\n scope?: () => ToolbarScope;\n /** The shops of a multi-brand product, for the screens list and \"Switch to <shop>\". */\n shops?: ToolbarShop[];\n /** The latest library's token files as a combination base: lets Studio's \"Try them together\" run here. */\n together?: () => Promise<TogetherBase>;\n}\n\nconst Toolbar = lazy(() => import('./Toolbar'));\n\n// Studio's read pass leaves the address now, on import: before the product renders and its hash router reads the\n// fragment (pass.ts).\ntakePassFromAddress();\n\nfunction asked(): boolean {\n let store: Storage | null = null;\n try {\n store = window.sessionStorage;\n } catch {\n store = null;\n }\n takePassFromAddress();\n return readChanges(window.location.search, store).length > 0;\n}\n\n/** Mounted once at the app root. Without a change from Studio it renders nothing and loads nothing. */\nexport function BridgeToolbar(props: BridgeToolbarProps) {\n const [on] = useState(() => typeof window !== 'undefined' && asked());\n if (!on) return null;\n return (\n <Suspense fallback={null}>\n <Toolbar {...props} />\n </Suspense>\n );\n}\n","// Where the page is (theme, brand, density, shop) and how the toolbar moves it there. A product passes its own hook\n// when it keeps these in state (the Admin demo's preferences); by default the toolbar reads the attributes the\n// design system's ThemeProvider (asRoot) puts on <html> and sets data-theme there.\nimport { useEffect, useMemo, useState } from 'react';\n\nexport interface ToolbarScope {\n theme: 'light' | 'dark';\n brand: string;\n density: string;\n /** The shop (tenant) the page shows, when the product has several. */\n shop?: string;\n /** Moves the page to a theme, a shop or both, in one step. */\n set: (next: { theme?: 'light' | 'dark'; shop?: string }) => void;\n}\n\n/** A shop of a multi-brand product: its id (in `?tenant=` links), its name and its brand. */\nexport interface ToolbarShop {\n id: string;\n name: string;\n brand: string;\n}\n\nconst read = () => {\n const el = document.documentElement;\n return {\n theme: (el.getAttribute('data-theme') === 'dark' ? 'dark' : 'light') as 'light' | 'dark',\n brand: el.getAttribute('data-brand') || 'bridge',\n density: el.getAttribute('data-density') || 'comfortable',\n };\n};\n\n/** The default scope: <html data-theme data-brand data-density>, kept in sync. */\nexport function useDocumentScope(): ToolbarScope {\n const [here, setHere] = useState(read);\n useEffect(() => {\n const mo = new MutationObserver(() => setHere((p) => {\n const n = read();\n return n.theme === p.theme && n.brand === p.brand && n.density === p.density ? p : n;\n }));\n mo.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme', 'data-brand', 'data-density'] });\n return () => mo.disconnect();\n }, []);\n return useMemo(\n () => ({\n ...here,\n set: (next) => {\n if (next.theme) document.documentElement.setAttribute('data-theme', next.theme);\n },\n }),\n [here],\n );\n}\n","// How the toolbar reads GitHub: through Bridge's read proxy (GET <proxy>/api/contribute?repo=<design system>&gh=<path>,\n// allow-listed, cached, with a read-only token on the server), so a visitor never meets GitHub's limit of 60 anonymous\n// reads an hour per address. When Bridge refuses or cannot be reached (an older Bridge, a design system it does not\n// serve, an origin its config does not name), the toolbar reads GitHub directly, as before, for the rest of the page.\n// The requests stay \"simple\" (GET, Accept only, no credentials): no CORS preflight, no cookie sent.\n\n/** Production Bridge: the read proxy the toolbar uses unless the product says otherwise. */\nexport const DEFAULT_PROXY = 'https://bridge.floriangouloubi.com';\n\nconst API = 'https://api.github.com';\nconst RAW = 'https://raw.githubusercontent.com';\n\nexport interface ReadsOptions {\n /** The design system repository (\"owner/name\"): Bridge serves its reads and those of the products it lists. */\n team: string;\n /** Bridge's origin, or null to read GitHub directly. */\n proxy: string | null;\n /**\n * A member's read pass from Bridge Studio (pass.ts): Bridge then reads the private product repositories it names for\n * this tab. Sent as a query parameter, so the request stays simple.\n */\n pass?: string | null;\n fetch?: typeof fetch;\n}\n\nexport interface Reads {\n /** A GitHub REST read under /repos/<owner>/<name>/..., as JSON. */\n api<T>(path: string): Promise<T>;\n /** A file of a repository at a ref (a sha or a branch; none: the default branch), as text. */\n file(owner: string, repo: string, ref: string | null, path: string): Promise<string>;\n}\n\nexport class ReadError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message);\n }\n}\n\nconst LIMIT = 'GitHub limits reads without an account to 60 an hour. Try again later.';\nconst errorOf = (status: number) => new ReadError(status === 403 || status === 429 ? LIMIT : `GitHub answered ${status}.`, status);\n\n/** Proxies that refused or did not answer on this page: every later read goes to GitHub directly. */\nconst down = new Set<string>();\n\n/** Tests start fresh. */\nexport function resetReads() {\n down.clear();\n}\n\n/**\n * Bridge's answer is GitHub's when it read GitHub (any status but its own refusals). Its own: 400 (a path it does not\n * take), 401 (no reader), 403 (not allowed, origin), 409 (app missing), 429 (busy), 5xx; then GitHub directly.\n */\nconst proxyRefused = (r: Response) => r.status === 400 || r.status === 401 || r.status === 403 || r.status === 409 || r.status === 429 || r.status >= 500;\n\nconst segments = (path: string) => path.split('/').map(encodeURIComponent).join('/');\n\nexport function githubReads({ team, proxy, pass = null, fetch: f = (...a) => fetch(...a) }: ReadsOptions): Reads {\n const base = proxy ? proxy.replace(/\\/+$/, '') : null;\n\n /** Bridge first, then GitHub. `direct` is GitHub's own URL for the same read. */\n async function read(gh: string, accept: string, direct: string): Promise<Response> {\n if (base && !down.has(base)) {\n try {\n const r = await f(`${base}/api/contribute?repo=${encodeURIComponent(team)}&gh=${encodeURIComponent(gh)}${pass ? `&pass=${encodeURIComponent(pass)}` : ''}`, { headers: { Accept: accept }, credentials: 'omit' });\n if (!proxyRefused(r)) return r;\n } catch {\n // Unreachable, or the browser refused the answer (CORS): GitHub directly.\n }\n down.add(base);\n }\n return f(direct, direct.startsWith(API) ? { headers: { Accept: accept } } : {});\n }\n\n return {\n async api<T>(path: string): Promise<T> {\n const r = await read(path, 'application/vnd.github+json', `${API}${path}`);\n if (!r.ok) throw errorOf(r.status);\n return (await r.json()) as T;\n },\n async file(owner, repo, ref, path) {\n const gh = `/repos/${owner}/${repo}/contents/${segments(path)}${ref ? `?ref=${encodeURIComponent(ref)}` : ''}`;\n const r = await read(gh, 'application/vnd.github.raw', `${RAW}/${owner}/${repo}/${ref ?? 'HEAD'}/${segments(path)}`);\n if (!r.ok) throw errorOf(r.status);\n return r.text();\n },\n };\n}\n","// Where a product's toolbar gets its facts when it is mounted with only the design system repository and its product\n// key: the design system's bridge.config.json, read once per session through Bridge's read proxy (GitHub directly when\n// Bridge refuses; public repositories, no account). Pure part (buildFromConfig) is tested; loadBuild adds the read and\n// the session cache.\nimport { DEFAULT_PROXY, githubReads, type Reads } from './reads';\nimport type { BridgeBuild } from './session';\n\ninterface ConfigLike {\n repo?: { owner?: string; name?: string; defaultBranch?: string };\n tokens?: { root?: string };\n previews?: { apps?: Record<string, string | undefined>; production?: Record<string, string | undefined> };\n products?: { key?: string; repo?: string; preview?: string; production?: string; design?: { repo?: string; preview?: string } }[];\n}\n\n/** Values the product sets itself win over the config: the Studio, production and preview URLs. */\nexport interface BuildOverrides {\n studio?: string | null;\n production?: string | null;\n preview?: string | null;\n deployEnv?: string | null;\n /**\n * The repository this deploy was built from (\"owner/name\", such as VERCEL_GIT_REPO_OWNER/VERCEL_GIT_REPO_SLUG): when\n * it is the design team's copy of the product (products[].design.repo), the toolbar reads the copy's explorations\n * and its preview pattern. Any other value is ignored.\n */\n repo?: string | null;\n /** Bridge's origin for reads (default production Bridge), null to read GitHub directly. */\n proxy?: string | null;\n}\n\nconst REPO = /^[\\w.-]+\\/[\\w.-]+$/;\n\n/**\n * The toolbar's facts for one product of a design system config: its repository (its own, else the design system's),\n * production Studio, its production URL and preview template. Null when the product is not in the config.\n */\nexport function buildFromConfig(raw: unknown, ds: string, product: string, overrides: BuildOverrides = {}): BridgeBuild | null {\n if (!raw || typeof raw !== 'object' || !REPO.test(ds)) return null;\n const config = raw as ConfigLike;\n const entry = (config.products ?? []).find((p) => p?.key === product);\n if (!entry) return null;\n const own = entry.repo && REPO.test(entry.repo) ? entry.repo : null;\n // A deploy of the design team's copy: the copy's explorations and previews.\n const copy = own && entry.design?.repo && REPO.test(entry.design.repo) && overrides.repo?.trim().toLowerCase() === entry.design.repo.toLowerCase() ? entry.design.repo : null;\n const [owner, repo] = (copy ?? own ?? ds).split('/') as [string, string];\n const pick = (v: string | null | undefined, fallback: string | null | undefined) => (v !== undefined ? v : (fallback ?? null));\n return {\n owner,\n repo,\n studio: pick(overrides.studio, config.previews?.production?.docs),\n production: pick(overrides.production, entry.production ?? config.previews?.production?.[product]),\n preview: pick(overrides.preview, copy ? entry.design?.preview : (entry.preview ?? config.previews?.apps?.[product])),\n deployEnv: overrides.deployEnv ?? null,\n ...(overrides.proxy !== undefined ? { proxy: overrides.proxy } : {}),\n defaultBranch: config.repo?.defaultBranch ?? 'main',\n tokensRoot: config.tokens?.root ?? 'packages/tokens/src',\n product,\n ds,\n };\n}\n\nconst cacheKey = (ds: string) => `bridge-toolbar:config:v1:${ds}`;\n\n/** Reads the design system's bridge.config.json (its default branch), kept for the session. */\nexport async function loadBuild(ds: string, product: string, overrides: BuildOverrides = {}, gh?: Reads): Promise<BridgeBuild> {\n let config: unknown = null;\n try {\n const cached = window.sessionStorage.getItem(cacheKey(ds));\n if (cached) config = JSON.parse(cached);\n } catch {\n // Storage blocked or a broken entry: read it again.\n }\n if (!config) {\n if (!REPO.test(ds)) throw new Error(`\"${ds}\" is not a GitHub repository (owner/name).`);\n const [owner, name] = ds.split('/') as [string, string];\n const reads = gh ?? githubReads({ team: ds, proxy: overrides.proxy === undefined ? DEFAULT_PROXY : overrides.proxy });\n const text = await reads.file(owner, name, null, 'bridge.config.json').catch((e: Error) => {\n throw new Error(`Could not read bridge.config.json from ${ds} (${e.message})`);\n });\n config = JSON.parse(text);\n try {\n window.sessionStorage.setItem(cacheKey(ds), JSON.stringify(config));\n } catch {\n // Storage blocked: the next page reads it again.\n }\n }\n const build = buildFromConfig(config, ds, product, overrides);\n if (!build) throw new Error(`No product \"${product}\" in ${ds}'s bridge.config.json.`);\n return build;\n}\n"],"names":["read"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { B, D, b, u } from "./index-DS5xot96.js";
1
+ import { B, D, b, u } from "./index-XrLgL3hM.js";
2
2
  export {
3
3
  B as BridgeToolbar,
4
4
  D as DEFAULT_PROXY,
package/dist/pass.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { StorageLike } from './session';
2
+ export declare const PASS_PARAM = "bridge-pass";
3
+ export declare const PASS_KEY = "bridge-toolbar:pass";
4
+ /** The pass at the front (or the end) of a fragment, and the fragment without it. */
5
+ export declare function splitPass(hash: string): {
6
+ pass: string | null;
7
+ rest: string;
8
+ };
9
+ /**
10
+ * Takes the pass out of the address (history.replaceState: no new entry, no reload) and keeps it for the tab. Returns
11
+ * the pass of this tab: the one just taken, else the one kept. Blocked storage keeps it for this page only.
12
+ */
13
+ export declare function takePass(loc: {
14
+ href: string;
15
+ hash: string;
16
+ }, storage: StorageLike | null, replace: (url: string) => void): string | null;
17
+ /** The pass of this tab, or null. */
18
+ export declare function readPass(storage: StorageLike | null): string | null;
19
+ /** Tests start fresh. */
20
+ export declare function resetPass(): void;
21
+ /** On import in a browser: the pass leaves the address before anything reads the hash. */
22
+ export declare function takePassFromAddress(): string | null;
23
+ /** The pass of this tab, in a browser. */
24
+ export declare const tabPass: () => string | null;
25
+ /** `url` (another deployment of the product: Live product, This change) with the tab's pass in front of its fragment. */
26
+ export declare function withPass(url: string, pass: string | null): string;
package/dist/reads.d.ts CHANGED
@@ -5,6 +5,11 @@ export interface ReadsOptions {
5
5
  team: string;
6
6
  /** Bridge's origin, or null to read GitHub directly. */
7
7
  proxy: string | null;
8
+ /**
9
+ * A member's read pass from Bridge Studio (pass.ts): Bridge then reads the private product repositories it names for
10
+ * this tab. Sent as a query parameter, so the request stays simple.
11
+ */
12
+ pass?: string | null;
8
13
  fetch?: typeof fetch;
9
14
  }
10
15
  export interface Reads {
@@ -19,4 +24,4 @@ export declare class ReadError extends Error {
19
24
  }
20
25
  /** Tests start fresh. */
21
26
  export declare function resetReads(): void;
22
- export declare function githubReads({ team, proxy, fetch: f }: ReadsOptions): Reads;
27
+ export declare function githubReads({ team, proxy, pass, fetch: f }: ReadsOptions): Reads;
@@ -90,6 +90,7 @@ function whereAmI(host, build) {
90
90
  const prodHost = build.production ? safe(() => new URL(build.production).host, null) : null;
91
91
  if (build.deployEnv === "production" || prodHost && host.toLowerCase() === prodHost.toLowerCase()) return "production";
92
92
  if (build.deployEnv === "preview") return "preview";
93
+ if (build.preview && isPreviewUrl(`https://${host}`, build.preview)) return "preview";
93
94
  return "local";
94
95
  }
95
96
  function shouldRender(input) {
@@ -302,16 +303,43 @@ function fromText(from, titleOf) {
302
303
  const names = from.map((n) => titleOf(n) ?? `#${n}`);
303
304
  return `from ${names.length <= 2 ? names.join(" and ") : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`}`;
304
305
  }
306
+ function branchOfHost(host, template) {
307
+ if (!template) return null;
308
+ const tplHost = /^https?:\/\/([^/?#]+)/i.exec(template)?.[1]?.toLowerCase();
309
+ if (!tplHost || !/\{branch\}/i.test(tplHost)) return null;
310
+ const [pre, post] = tplHost.split(/\{branch\}/i);
311
+ const esc = (x) => x.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
312
+ const m = new RegExp(`^${esc(pre).replace(/\\\{[a-z]+\\\}/gi, "[a-z0-9-]+")}([a-z0-9-]+)${esc(post).replace(/\\\{[a-z]+\\\}/gi, "[a-z0-9-]+")}$`).exec(host.toLowerCase().replace(/:\d+$/, ""));
313
+ return m?.[1] ?? null;
314
+ }
315
+ function branchTitle(branch) {
316
+ const words = (branch ?? "").replace(/^(explore|exploration|bridge|change|agent|feature|feat|fix|design)-/, "").split("-").filter(Boolean);
317
+ if (!words.length || words.every((w) => /^[0-9a-f]{6,}$|^\d+$/.test(w))) return null;
318
+ const text = words.join(" ");
319
+ return text.charAt(0).toUpperCase() + text.slice(1);
320
+ }
321
+ const READ_FAILED = "Bridge can't open this change's details here. Open it from Studio to see them.";
322
+ const READ_FAILED_TOGETHER = "Bridge can't open these changes' details here. Open them from Studio to see them.";
323
+ function canFlipSides(input) {
324
+ if (input.mode === "tokens") return true;
325
+ if (input.mode === null && !input.failed) return false;
326
+ return input.side === "after" ? Boolean(input.production) : Boolean(input.returnTo);
327
+ }
305
328
  export {
306
- placeToolbar as A,
307
- togetherLabel as B,
308
- changeSummary as C,
309
- dockAt as D,
310
- stepDock as E,
311
- DOCK_KEY as F,
329
+ TOGETHER_HINT as A,
330
+ isSubtle as B,
331
+ READ_FAILED as C,
332
+ isFlipKey as D,
333
+ placeToolbar as E,
334
+ togetherLabel as F,
335
+ changeSummary as G,
336
+ dockAt as H,
337
+ stepDock as I,
338
+ DOCK_KEY as J,
312
339
  MARKS_PARAM as M,
313
340
  OPEN_KEY as O,
314
341
  PARAM as P,
342
+ READ_FAILED_TOGETHER as R,
315
343
  SCROLL_PARAM as S,
316
344
  TOGETHER_EMPTY_LINE as T,
317
345
  roleOfToken as a,
@@ -328,17 +356,17 @@ export {
328
356
  liveTokensUrl as l,
329
357
  sameScreenOn as m,
330
358
  noVisibleDifference as n,
331
- studioChangesUrl as o,
332
- studioChangeUrl as p,
333
- contrastRatio as q,
359
+ branchTitle as o,
360
+ branchOfHost as p,
361
+ canFlipSides as q,
334
362
  readChanges as r,
335
363
  stepOfToken as s,
336
364
  tokenChanges as t,
337
- toHex as u,
338
- fromText as v,
365
+ studioChangesUrl as u,
366
+ studioChangeUrl as v,
339
367
  whereAmI as w,
340
- TOGETHER_HINT as x,
341
- isSubtle as y,
342
- isFlipKey as z
368
+ contrastRatio as x,
369
+ toHex as y,
370
+ fromText as z
343
371
  };
344
- //# sourceMappingURL=session-DAUBg64J.js.map
372
+ //# sourceMappingURL=session-BSLnT9Xu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-BSLnT9Xu.js","sources":["../src/session.ts"],"sourcesContent":["// The Bridge toolbar's pure logic: when it shows, which change it follows, what Before and After mean for that\n// change, and the screens it lists. A product opens it only when Bridge Studio links here with `?bridge=<change>`;\n// production renders nothing, except the Before side of a code change, which arrives with a handoff from a preview.\n\nexport interface StorageLike {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\n/**\n * What the toolbar knows about the product and its design system: from bridge.config.json (the product's entry in\n * products[] and previews), read at build time (the Admin demo) or from the design system repository at run time.\n */\nexport interface BridgeBuild {\n /** The repository whose pull requests `?bridge=<n>` names: the product's own repository, else the design system's. */\n owner: string;\n repo: string;\n /** Production Studio (previews.production.docs): the link back and the shipped tokens.css. */\n studio: string | null;\n /** The product in production (products[].production, else previews.production.<key>): Before of a code change. */\n production: string | null;\n /** The product's preview template (products[].preview): only these hosts may hand a code change over to production. */\n preview: string | null;\n /** VERCEL_ENV of the build ('production', 'preview', 'development'), null when unknown. */\n deployEnv: string | null;\n /** The library branch (repo.defaultBranch) and the token files' root (tokens.root): changes tried together. */\n defaultBranch?: string;\n tokensRoot?: string;\n /** The product's key (products[].key) and the design system repository (\"owner/name\"), for the way back to Studio. */\n product?: string;\n ds?: string;\n /** Bridge's origin for GitHub reads (its read proxy); default production Bridge, null to read GitHub directly. */\n proxy?: string | null;\n}\n\nexport const PARAM = 'bridge';\n/** Where After lives for a code change, carried to production on Before. */\nexport const RETURN_PARAM = 'bridge-return';\n/** Scroll position carried across a Before/After navigation. */\nexport const SCROLL_PARAM = 'bridge-scroll';\n/** How many areas a code change marks on the screen, carried from This change to Live product (markers.ts). */\nexport const MARKS_PARAM = 'bridge-marks';\nexport const SESSION_KEY = 'bridge-admin:change';\nexport const RETURN_KEY = 'bridge-admin:return';\nexport const DOCK_KEY = 'bridge-admin:toolbar-dock';\nexport const OPEN_KEY = 'bridge-admin:toolbar-open';\n/** Changes tried together: the picks of the tokens they set differently (core encodePicks), from Studio. */\nexport const USE_PARAM = 'bridge-use';\nexport const USE_KEY = 'bridge-admin:use';\n/**\n * The repository the change lives in, when it is not the product's own: the design team's copy (products[].design.repo).\n * Studio adds it to the link; the switch between This change and Live product keeps it (the whole query is carried).\n */\nexport const REPO_PARAM = 'bridge-repo';\nexport const REPO_KEY = 'bridge-admin:repo';\n/** At most this many changes are tried together. */\nexport const MAX_TOGETHER = 4;\n\nconst safe = <T>(fn: () => T, fallback: T): T => {\n try {\n return fn();\n } catch {\n return fallback;\n }\n};\n\n/**\n * The change the toolbar follows: `?bridge=<n>` wins and is kept for the session (the admin's hash router keeps the\n * query, a reload or a new path does not need it again); `?bridge=off` forgets it. Blocked storage only loses the\n * memory, never the toolbar of the current page.\n */\nexport function readChange(search: string, storage: StorageLike | null): number | null {\n return readChanges(search, storage)[0] ?? null;\n}\n\n/**\n * The change numbers in \"101\" or \"101,118,120\" (an encoded comma too): positive, at most 7 digits, no repeats, at\n * most MAX_TOGETHER. Anything else in the list makes it unreadable (empty), never a guess.\n */\nexport function parseChanges(raw: string | null | undefined): number[] {\n const parts = (raw ?? '').trim().split(/,|%2C/i).map((p) => p.trim());\n if (!parts.every((p) => /^\\d{1,7}$/.test(p) && Number(p) > 0)) return [];\n return [...new Set(parts.map(Number))].slice(0, MAX_TOGETHER);\n}\n\n/**\n * The changes the toolbar follows: one from \"Try it in the product\", 2 to 4 from \"Try them together\". Same memory as\n * one change: `?bridge=` wins and is kept for the session, `?bridge=off` forgets it, blocked storage only loses the\n * memory. The picks (`bridge-use`) travel with the list and are kept, or forgotten, with it.\n */\nexport function readChanges(search: string, storage: StorageLike | null): number[] {\n const params = new URLSearchParams(search);\n if (params.has(PARAM)) {\n const list = parseChanges(params.get(PARAM));\n if (!list.length) {\n if (storage) safe(() => (storage.removeItem(SESSION_KEY), storage.removeItem(USE_KEY)), undefined);\n return [];\n }\n if (storage) {\n safe(() => storage.setItem(SESSION_KEY, list.join(',')), undefined);\n const use = params.get(USE_PARAM);\n safe(() => (use && list.length > 1 ? storage.setItem(USE_KEY, use) : storage.removeItem(USE_KEY)), undefined);\n }\n return list;\n }\n return storage ? parseChanges(safe(() => storage.getItem(SESSION_KEY), null)) : [];\n}\n\nconst REPO_RE = /^[\\w.-]+\\/[\\w.-]+$/;\n\n/**\n * The repository that came with the change (REPO_PARAM): from the address, kept for the session like the change, else\n * the session's. Null when none or not \"owner/name\"; config.ts only honors it when it is the product's design copy.\n */\nexport function readRepo(search: string, storage: StorageLike | null): string | null {\n const params = new URLSearchParams(search);\n if (params.has(PARAM)) {\n const raw = params.get(REPO_PARAM)?.trim() ?? '';\n const repo = REPO_RE.test(raw) ? raw : null;\n if (storage) safe(() => (repo ? storage.setItem(REPO_KEY, repo) : storage.removeItem(REPO_KEY)), undefined);\n return repo;\n }\n const kept = storage ? safe(() => storage.getItem(REPO_KEY), null) : null;\n return kept && REPO_RE.test(kept) ? kept : null;\n}\n\n/** The picks that came with the list: from the address, else the session. */\nexport function readUse(search: string, storage: StorageLike | null): string | null {\n const params = new URLSearchParams(search);\n if (params.has(PARAM)) return params.get(USE_PARAM);\n return storage ? safe(() => storage.getItem(USE_KEY), null) : null;\n}\n\n/** A host that matches the product's preview template (`bridge-admin-git-{branch}-team.vercel.app`), https only. */\nexport function isPreviewUrl(raw: string | null | undefined, template: string | null): boolean {\n if (!raw || !template) return false;\n let url: URL;\n let tpl: URL;\n try {\n url = new URL(raw);\n tpl = new URL(template.replace(/\\{[a-z]+\\}/gi, 'x'));\n } catch {\n return false;\n }\n if (url.protocol !== 'https:' || url.username || url.password || url.port) return false;\n const tplHost = /^https?:\\/\\/([^/?#]+)/i.exec(template)?.[1]?.toLowerCase();\n if (!tplHost || tpl.protocol !== 'https:') return false;\n const pattern = new RegExp(`^${tplHost.split(/\\{[a-z]+\\}/i).map((p) => p.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')).join('[a-z0-9-]+')}$`);\n return pattern.test(url.hostname.toLowerCase());\n}\n\n/** The preview to return to from production, from `?bridge-return=` or the session. Only a preview host counts. */\nexport function readReturn(search: string, storage: StorageLike | null, template: string | null): string | null {\n const raw = new URLSearchParams(search).get(RETURN_PARAM);\n if (raw !== null) {\n if (isPreviewUrl(raw, template)) {\n const origin = new URL(raw).origin;\n if (storage) safe(() => storage.setItem(RETURN_KEY, origin), undefined);\n return origin;\n }\n return null;\n }\n const stored = storage ? safe(() => storage.getItem(RETURN_KEY), null) : null;\n return isPreviewUrl(stored, template) ? new URL(stored!).origin : null;\n}\n\nexport type Where = 'production' | 'preview' | 'local';\n\n/**\n * Which deployment this page is: the product in production by host or by build, a preview, or a local build. A host\n * that matches the preview template is a preview even when the build does not say (Cloudflare Pages, Netlify and other\n * hosts set no VERCEL_ENV).\n */\nexport function whereAmI(host: string, build: Pick<BridgeBuild, 'production' | 'deployEnv'> & Partial<Pick<BridgeBuild, 'preview'>>): Where {\n const prodHost = build.production ? safe(() => new URL(build.production!).host, null) : null;\n if (build.deployEnv === 'production' || (prodHost && host.toLowerCase() === prodHost.toLowerCase())) return 'production';\n if (build.deployEnv === 'preview') return 'preview';\n if (build.preview && isPreviewUrl(`https://${host}`, build.preview)) return 'preview';\n return 'local';\n}\n\n/**\n * Whether the toolbar renders. Never without a change from Studio. In production only for the Before side of a code\n * change, that is with a preview to return to: production on its own renders nothing.\n */\nexport function shouldRender(input: { change: number | null; where: Where; returnTo: string | null; together?: boolean }): boolean {\n if (input.change === null) return false;\n // Changes tried together run on production: no preview holds all of them.\n if (input.together) return true;\n if (input.where === 'production') return input.returnTo !== null;\n return true;\n}\n\n/** \"These 3 changes\": the After side of changes tried together, counting the ones switched on. */\nexport const togetherLabel = (n: number) => (n === 1 ? 'This change' : `These ${n} changes`);\n\n/** The one hint line of changes tried together: where they run and why. */\nexport const TOGETHER_HINT = \"The live product, with these changes' tokens applied in your browser only. No preview has all of them together.\";\n\n/** Show what changed found nothing on this screen for changes tried together. */\nexport const TOGETHER_EMPTY_LINE = \"These changes don't touch this screen. Try another screen.\";\n\n/** Switches one change on or off, keeping the order Studio listed them in. */\nexport function toggleIncluded(all: number[], included: number[], n: number, on: boolean): number[] {\n const next = new Set(included);\n if (on) next.add(n);\n else next.delete(n);\n return all.filter((x) => next.has(x));\n}\n\n/** Studio's Changes list, where changes are tried together (and the way back). */\nexport function studioChangesUrl(build: Pick<BridgeBuild, 'studio' | 'owner' | 'repo'>): string {\n return build.studio ? `${build.studio.replace(/\\/$/, '')}/studio/changes/` : `https://github.com/${build.owner}/${build.repo}/pulls`;\n}\n\nexport type ChangeMode = 'tokens' | 'code';\n\n/** Token files (DTCG and the contrast gate) and changesets only: Before swaps tokens live. Anything else is code. */\nexport function changeMode(files: string[]): ChangeMode {\n const tokenOnly = files.length > 0 && files.every((f) => f.endsWith('.tokens.json') || /contrast\\.checks\\.json$/.test(f) || f.startsWith('.changeset/'));\n return tokenOnly ? 'tokens' : 'code';\n}\n\n/** The live tokens (main's build, served by production Studio at `/tokens.css`), or null when not configured. */\nexport function liveTokensUrl(build: Pick<BridgeBuild, 'studio'>): string | null {\n return build.studio ? safe(() => new URL('tokens.css', build.studio!.replace(/\\/?$/, '/')).href, null) : null;\n}\n\n/**\n * Studio's page for the change. A change in a product's own repository (an exploration there) opens as\n * `?exploration=<n>&product=<key>`; a change of the design system repository as `?pr=<n>`.\n */\nexport function studioChangeUrl(build: Pick<BridgeBuild, 'studio' | 'owner' | 'repo' | 'product' | 'ds'>, change: number): string {\n if (!build.studio) return `https://github.com/${build.owner}/${build.repo}/pull/${change}`;\n const studio = build.studio.replace(/\\/$/, '');\n const own = build.product && build.ds && build.ds.toLowerCase() !== `${build.owner}/${build.repo}`.toLowerCase();\n return own ? `${studio}/studio/changes/?exploration=${change}&product=${encodeURIComponent(build.product!)}` : `${studio}/studio/changes/?pr=${change}`;\n}\n\n/** Shape of a visual impact manifest that the toolbar reads (written by @bridge-ds/visual). */\nexport interface ManifestShot {\n kind: 'story' | 'route';\n label: string;\n route?: string;\n tenant?: string;\n theme: string;\n status: string;\n percent: number;\n}\n\nexport interface Screen {\n label: string;\n route: string;\n tenant: string;\n theme: string;\n percent: number;\n}\n\n/** The product screens the change alters, most changed first, one entry per screen, shop and theme. */\nexport function changedScreens(shots: ManifestShot[] | null | undefined): Screen[] {\n if (!Array.isArray(shots)) return [];\n const seen = new Set<string>();\n return shots\n .filter((s) => s.kind === 'route' && s.route && s.tenant && (s.status === 'changed' || s.status === 'added'))\n .sort((a, b) => b.percent - a.percent)\n .filter((s) => {\n const key = `${s.route}|${s.tenant}|${s.theme}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n })\n .map((s) => ({ label: s.label, route: s.route!, tenant: s.tenant!, theme: s.theme, percent: s.percent }));\n}\n\n/**\n * The same page on another deployment (code changes: production for Before, the preview for After), with the\n * change, the way back and the scroll position in the query. The hash route (ra-core) is kept.\n */\nexport function sameScreenOn(target: string, here: { search: string; hash: string }, extra: { change: number; returnTo?: string | null; scroll?: number; marks?: number | null }): string {\n const url = new URL(target);\n const params = new URLSearchParams(here.search);\n params.delete(RETURN_PARAM);\n params.delete(SCROLL_PARAM);\n params.delete(MARKS_PARAM);\n params.set(PARAM, String(extra.change));\n if (extra.returnTo) params.set(RETURN_PARAM, extra.returnTo);\n if (extra.scroll && extra.scroll > 0) params.set(SCROLL_PARAM, String(Math.round(extra.scroll)));\n // The areas a code change marks on this screen: Live product says how many wait on This change.\n if (typeof extra.marks === 'number' && extra.marks >= 0) params.set(MARKS_PARAM, String(extra.marks));\n url.pathname = '/';\n url.search = params.toString();\n url.hash = here.hash;\n return url.href;\n}\n\n/** The scroll position a Before/After navigation carried, once: the caller removes it from the address. */\nexport function readScroll(search: string): number | null {\n const raw = new URLSearchParams(search).get(SCROLL_PARAM);\n return raw && /^\\d{1,7}$/.test(raw) ? Number(raw) : null;\n}\n\nexport type Dock = 'start' | 'center' | 'end';\nconst DOCKS: Dock[] = ['start', 'center', 'end'];\n\n/** Bottom right by default: the corner a back office leaves free most often. */\nexport function readDock(storage: StorageLike | null): Dock {\n const v = storage ? safe(() => storage.getItem(DOCK_KEY), null) : null;\n return DOCKS.includes(v as Dock) ? (v as Dock) : 'end';\n}\n\n/** One step left (-1) or right (+1), stopping at the edges. */\nexport function stepDock(dock: Dock, step: -1 | 1): Dock {\n return DOCKS[Math.min(DOCKS.length - 1, Math.max(0, DOCKS.indexOf(dock) + step))]!;\n}\n\n/** The dock nearest to where a drag ended: thirds of the viewport width. */\nexport function dockAt(x: number, width: number): Dock {\n if (width <= 0) return 'center';\n const r = x / width;\n return r < 1 / 3 ? 'start' : r > 2 / 3 ? 'end' : 'center';\n}\n\n/** Title of a change as people read it: conventional prefixes (\"tokens: \") dropped, first letter up. */\nexport function readableTitle(title: string): string {\n const t = title.replace(/^\\s*[a-z]+(\\([^)]*\\))?!?:\\s*/i, '').trim() || title.trim();\n return t.charAt(0).toUpperCase() + t.slice(1);\n}\n\n/** The B shortcut flips Before and After, unless the person is typing or holds a modifier. */\nexport function isFlipKey(e: { key: string; altKey: boolean; ctrlKey: boolean; metaKey: boolean; target: unknown }): boolean {\n if (e.altKey || e.ctrlKey || e.metaKey || e.key.toLowerCase() !== 'b') return false;\n const t = e.target as { tagName?: string; isContentEditable?: boolean; closest?: (s: string) => unknown } | null;\n if (!t) return true;\n if (t.isContentEditable) return false;\n const tag = (t.tagName ?? '').toLowerCase();\n if (tag === 'input' || tag === 'textarea' || tag === 'select') return false;\n return !(t.closest && t.closest('[role=\"combobox\"], [role=\"textbox\"], [role=\"searchbox\"]'));\n}\n\nexport interface Rect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\nconst overlaps = (a: Rect, b: Rect, margin: number) => a.left < b.right + margin && a.right > b.left - margin && a.top < b.bottom + margin && a.bottom > b.top - margin;\n\n/**\n * Where the toolbar goes so it never covers a primary or submit button of the page: the chosen dock if it is free,\n * else the opposite corner, else the centre, else the chosen dock lifted just above the buttons it would cover. The\n * docks sit inside the area [left, right] (the content column, so it never sits over the sidebar).\n */\nexport function placeToolbar(input: { left: number; right: number; vh: number; w: number; h: number; gutter: number; preferred: Dock; obstacles: Rect[]; margin?: number }): { dock: Dock; lift: number } {\n const { left: areaLeft, right: areaRight, vh, w, h, gutter, preferred, obstacles } = input;\n const margin = input.margin ?? 8;\n const rectAt = (dock: Dock, lift = 0): Rect => {\n const left = dock === 'start' ? areaLeft : dock === 'end' ? areaRight - w : (areaLeft + areaRight - w) / 2;\n const bottom = vh - gutter - lift;\n return { left, right: left + w, top: bottom - h, bottom };\n };\n const opposite: Dock = preferred === 'start' ? 'end' : 'start';\n const order: Dock[] = [preferred, opposite, ...DOCKS.filter((d) => d !== preferred && d !== opposite)];\n for (const dock of order) {\n const r = rectAt(dock);\n if (!obstacles.some((o) => overlaps(r, o, margin))) return { dock, lift: 0 };\n }\n // Every dock covers something: stay put and rise above the highest button in the way.\n const base = rectAt(preferred);\n const inWay = obstacles.filter((o) => o.left < base.right + margin && o.right > base.left - margin && o.bottom > base.top - margin);\n const top = Math.min(...inWay.map((o) => o.top));\n const lift = Math.max(0, Math.ceil(base.bottom - (top - margin)));\n return { dock: preferred, lift: Math.min(lift, Math.max(0, vh - h - 2 * gutter)) };\n}\n\n/** What one token file can change on screen: a theme and a brand, or every one of them (null). */\nfunction fileScope(file: string): { theme: string | null; brand: string | null } | 'none' {\n if (file.startsWith('.changeset/')) return 'none';\n let m = /(?:^|\\/)semantic\\/([a-z0-9-]+)\\.tokens\\.json$/i.exec(file);\n if (m && m[1] !== 'base') return { theme: m[1]!, brand: null };\n m = /(?:^|\\/)brands\\/([a-z0-9-]+)\\.([a-z0-9-]+)\\.tokens\\.json$/i.exec(file);\n if (m) return { theme: m[2]!, brand: m[1]! };\n m = /(?:^|\\/)brands\\/([a-z0-9-]+)\\.tokens\\.json$/i.exec(file);\n if (m) return { theme: null, brand: m[1]! };\n // Primitives, base, density, components and anything else: could change every theme and brand.\n return { theme: null, brand: null };\n}\n\nexport type NoDifference = { kind: 'theme'; here: string; touched: string } | { kind: 'brand'; here: string; touched: string };\n\n/**\n * Whether a token change shows nothing in this theme and brand, from the token files it edits (DTCG layers named\n * semantic/<theme>, brands/<brand>.<theme>, brands/<brand>). Null when it may show, or when that cannot be told:\n * never claims \"no difference\" on a guess.\n */\nexport function noVisibleDifference(files: string[], here: { theme: string; brand: string }): NoDifference | null {\n const scopes = files.map(fileScope).filter((s): s is { theme: string | null; brand: string | null } => s !== 'none');\n if (!scopes.length) return null;\n const hits = (theme: string, brand: string) => scopes.some((s) => (s.theme === null || s.theme === theme) && (s.brand === null || s.brand === brand));\n if (hits(here.theme, here.brand)) return null;\n const theme = scopes.find((s) => s.theme !== null && s.theme !== here.theme && (s.brand === null || s.brand === here.brand))?.theme;\n if (theme) return { kind: 'theme', here: here.theme, touched: theme };\n const brand = scopes.find((s) => s.brand !== null && s.brand !== here.brand)?.brand;\n return brand ? { kind: 'brand', here: here.brand, touched: brand } : null;\n}\n\n/** \"darker default background.\" The change title as a short phrase after \"This change:\". */\nexport function changeSummary(title: string): string {\n const t = readableTitle(title).replace(/[.\\s]+$/, '');\n if (!t) return '';\n const lower = /^[A-Z][a-z]/.test(t) ? t.charAt(0).toLowerCase() + t.slice(1) : t;\n return `${lower}.`;\n}\n\nexport interface TokenChange {\n /** Token path, \"color.bg.default\". */\n path: string;\n /** CSS variable the token builds to, \"--color-bg-default\". */\n cssVar: string;\n /** The role it plays, what designers call it: \"Page canvas\" (its description), \"Brand primary\" (a palette role). */\n name: string;\n /** The exact step, second: \"Primary 700\" for a palette step, null for a semantic token. */\n step: string | null;\n type: string | null;\n /** The theme and brand its file applies to (null: every one). */\n theme: string | null;\n brand: string | null;\n}\n\ntype TokenNode = { $value?: unknown; $type?: string; $description?: string; [k: string]: unknown };\ntype Leaf = { value: string; type: string | null; description: string | null };\n\nfunction leaves(node: unknown, path: string[] = [], inheritedType: string | null = null, out = new Map<string, Leaf>()): Map<string, Leaf> {\n if (!node || typeof node !== 'object') return out;\n const n = node as TokenNode;\n const type = typeof n.$type === 'string' ? n.$type : inheritedType;\n if ('$value' in n) {\n out.set(path.join('.'), { value: JSON.stringify(n.$value), type, description: typeof n.$description === 'string' ? n.$description : null });\n return out;\n }\n for (const [k, v] of Object.entries(n)) if (!k.startsWith('$')) leaves(v, [...path, k], type, out);\n return out;\n}\n\n/** Palette roles a brand re-points (brands/<brand>.tokens.json), in designers' words. */\nconst PALETTE_ROLES: Record<string, string> = { primary: 'Brand primary', link: 'Link', neutral: 'Neutral', accent: 'Accent', success: 'Success', warning: 'Warning', danger: 'Danger', info: 'Info', categorical: 'Chart colours' };\n\n/** The role a token plays: its description's first phrase, a palette role (\"Brand primary\"), else its path in words. */\nexport function roleOfToken(path: string, description: string | null): string {\n const first = description?.split(/[.:;(]/)[0]?.trim();\n if (first && first.length <= 40) return first;\n const m = /^palette\\.([a-z-]+)\\./.exec(path);\n if (m) return PALETTE_ROLES[m[1]!] ?? designerName(`palette.${m[1]}`, null);\n return designerName(path, null);\n}\n\n/** The palette step, shown second: \"palette.primary.700\" -> \"Primary 700\"; null for other tokens. */\nexport function stepOfToken(path: string): string | null {\n return path.startsWith('palette.') ? designerName(path, null) : null;\n}\n\n/** Designer name of a token: the first phrase of its description, else its path in words (\"Bg default\"). */\nexport function designerName(path: string, description: string | null): string {\n const first = description?.split(/[.:;(]/)[0]?.trim();\n if (first && first.length <= 40) return first;\n const words = path.replace(/^(color|palette)\\./, '').split('.').join(' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\n/**\n * The tokens one file changes between two versions of it (parsed DTCG JSON, null when the file is added or removed):\n * every leaf whose $value differs, with the theme and brand its layer applies to.\n */\nexport function tokenChanges(file: string, before: unknown, after: unknown): TokenChange[] {\n const scope = fileScope(file);\n if (scope === 'none') return [];\n const a = leaves(before);\n const b = leaves(after);\n const out: TokenChange[] = [];\n for (const path of new Set([...a.keys(), ...b.keys()])) {\n const x = a.get(path);\n const y = b.get(path);\n if (x?.value === y?.value) continue;\n out.push({\n path,\n cssVar: `--${path.split('.').join('-')}`,\n name: roleOfToken(path, y?.description ?? x?.description ?? null),\n step: stepOfToken(path),\n type: y?.type ?? x?.type ?? null,\n theme: scope.theme,\n brand: scope.brand,\n });\n }\n return out;\n}\n\n/** WCAG contrast ratio between two rgb() colours, or null when either is not rgb (a non-colour value). */\nexport function contrastRatio(a: string, b: string): number | null {\n const lum = (c: string) => {\n const m = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/.exec(c.trim());\n if (!m) return null;\n const [r, g, bl] = [m[1], m[2], m[3]].map((n) => {\n const v = Number(n) / 255;\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\n }) as [number, number, number];\n return 0.2126 * r + 0.7152 * g + 0.0722 * bl;\n };\n const x = lum(a);\n const y = lum(b);\n if (x === null || y === null) return null;\n return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);\n}\n\n/** Two colours under 1.1:1 read as the same at a glance: the swatch says \"Subtle change\" so it does not look broken. */\nexport const isSubtle = (a: string, b: string): boolean => {\n const r = contrastRatio(a, b);\n return r !== null && r < 1.1;\n};\n\n/** rgb(245, 247, 249) -> #f5f7f9; anything else as is. */\nexport function toHex(color: string): string {\n const m = /^rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)$/.exec(color.trim());\n if (!m) return color;\n const hex = [m[1], m[2], m[3]].map((n) => Number(n).toString(16).padStart(2, '0')).join('');\n return m[4] !== undefined && Number(m[4]) < 1 ? `#${hex}${Math.round(Number(m[4]) * 255).toString(16).padStart(2, '0')}` : `#${hex}`;\n}\n\n/** \"from Darker default background\", \"from #101 and #118\": which change a token's value comes from (changes tried together). */\nexport function fromText(from: number[], titleOf: (n: number) => string | null): string {\n const names = from.map((n) => titleOf(n) ?? `#${n}`);\n return `from ${names.length <= 2 ? names.join(' and ') : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`}`;\n}\n\n/**\n * The branch a preview host was built from, by the product's preview template (`https://{branch}.shop.pages.dev`,\n * `https://shop-git-{branch}-team.vercel.app`): the host label part `{branch}` stands for, or null when the host does\n * not match it.\n */\nexport function branchOfHost(host: string, template: string | null): string | null {\n if (!template) return null;\n const tplHost = /^https?:\\/\\/([^/?#]+)/i.exec(template)?.[1]?.toLowerCase();\n if (!tplHost || !/\\{branch\\}/i.test(tplHost)) return null;\n const [pre, post] = tplHost.split(/\\{branch\\}/i) as [string, string];\n const esc = (x: string) => x.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const m = new RegExp(`^${esc(pre).replace(/\\\\\\{[a-z]+\\\\\\}/gi, '[a-z0-9-]+')}([a-z0-9-]+)${esc(post).replace(/\\\\\\{[a-z]+\\\\\\}/gi, '[a-z0-9-]+')}$`).exec(host.toLowerCase().replace(/:\\d+$/, ''));\n return m?.[1] ?? null;\n}\n\n/**\n * A name for a change the toolbar could not read, from its branch: \"explore-add-a-toast-alert\" reads \"Add a toast\n * alert\" (Bridge names exploration branches explore/<what it does>). Null when there is no readable branch.\n */\nexport function branchTitle(branch: string | null): string | null {\n const words = (branch ?? '')\n .replace(/^(explore|exploration|bridge|change|agent|feature|feat|fix|design)-/, '')\n .split('-')\n .filter(Boolean);\n if (!words.length || words.every((w) => /^[0-9a-f]{6,}$|^\\d+$/.test(w))) return null;\n const text = words.join(' ');\n return text.charAt(0).toUpperCase() + text.slice(1);\n}\n\n/** What the toolbar says when Bridge could not read the change for it (a private product opened outside Studio). */\nexport const READ_FAILED = \"Bridge can't open this change's details here. Open it from Studio to see them.\";\nexport const READ_FAILED_TOGETHER = \"Bridge can't open these changes' details here. Open them from Studio to see them.\";\n\n/**\n * Whether Before and After can be switched. A token change switches in place once its tokens are read; a code change\n * opens the same screen on the other deployment, which needs no facts: when the change could not be read, a known\n * production URL (on This change) or way back (on Live product) still flips.\n */\nexport function canFlipSides(input: { mode: ChangeMode | null; failed: boolean; side: 'before' | 'after'; production: string | null; returnTo: string | null }): boolean {\n if (input.mode === 'tokens') return true;\n if (input.mode === null && !input.failed) return false;\n return input.side === 'after' ? Boolean(input.production) : Boolean(input.returnTo);\n}\n"],"names":["lift","theme","brand"],"mappings":"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"}
package/dist/session.d.ts CHANGED
@@ -79,8 +79,12 @@ export declare function isPreviewUrl(raw: string | null | undefined, template: s
79
79
  /** The preview to return to from production, from `?bridge-return=` or the session. Only a preview host counts. */
80
80
  export declare function readReturn(search: string, storage: StorageLike | null, template: string | null): string | null;
81
81
  export type Where = 'production' | 'preview' | 'local';
82
- /** Which deployment this page is: the product in production by host or by build, a preview, or a local build. */
83
- export declare function whereAmI(host: string, build: Pick<BridgeBuild, 'production' | 'deployEnv'>): Where;
82
+ /**
83
+ * Which deployment this page is: the product in production by host or by build, a preview, or a local build. A host
84
+ * that matches the preview template is a preview even when the build does not say (Cloudflare Pages, Netlify and other
85
+ * hosts set no VERCEL_ENV).
86
+ */
87
+ export declare function whereAmI(host: string, build: Pick<BridgeBuild, 'production' | 'deployEnv'> & Partial<Pick<BridgeBuild, 'preview'>>): Where;
84
88
  /**
85
89
  * Whether the toolbar renders. Never without a change from Studio. In production only for the Before side of a code
86
90
  * change, that is with a preview to return to: production on its own renders nothing.
@@ -240,3 +244,29 @@ export declare const isSubtle: (a: string, b: string) => boolean;
240
244
  export declare function toHex(color: string): string;
241
245
  /** "from Darker default background", "from #101 and #118": which change a token's value comes from (changes tried together). */
242
246
  export declare function fromText(from: number[], titleOf: (n: number) => string | null): string;
247
+ /**
248
+ * The branch a preview host was built from, by the product's preview template (`https://{branch}.shop.pages.dev`,
249
+ * `https://shop-git-{branch}-team.vercel.app`): the host label part `{branch}` stands for, or null when the host does
250
+ * not match it.
251
+ */
252
+ export declare function branchOfHost(host: string, template: string | null): string | null;
253
+ /**
254
+ * A name for a change the toolbar could not read, from its branch: "explore-add-a-toast-alert" reads "Add a toast
255
+ * alert" (Bridge names exploration branches explore/<what it does>). Null when there is no readable branch.
256
+ */
257
+ export declare function branchTitle(branch: string | null): string | null;
258
+ /** What the toolbar says when Bridge could not read the change for it (a private product opened outside Studio). */
259
+ export declare const READ_FAILED = "Bridge can't open this change's details here. Open it from Studio to see them.";
260
+ export declare const READ_FAILED_TOGETHER = "Bridge can't open these changes' details here. Open them from Studio to see them.";
261
+ /**
262
+ * Whether Before and After can be switched. A token change switches in place once its tokens are read; a code change
263
+ * opens the same screen on the other deployment, which needs no facts: when the change could not be read, a known
264
+ * production URL (on This change) or way back (on Live product) still flips.
265
+ */
266
+ export declare function canFlipSides(input: {
267
+ mode: ChangeMode | null;
268
+ failed: boolean;
269
+ side: 'before' | 'after';
270
+ production: string | null;
271
+ returnTo: string | null;
272
+ }): boolean;
package/dist/together.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { combineChanges, buildTokenSet, previewOverrides, cssVarOf, resolve, parseColor, cssOf } from "@bridge-ds/core";
2
2
  import { changeEdits, decodePicks } from "@bridge-ds/core";
3
- import { s as stepOfToken, a as roleOfToken } from "./session-DAUBg64J.js";
3
+ import { s as stepOfToken, a as roleOfToken } from "./session-BSLnT9Xu.js";
4
4
  function togetherBase(latest, options, modes) {
5
5
  return { latest, options, modes, main: buildTokenSet(latest, options) };
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/toolbar",
3
- "version": "0.6.0",
3
+ "version": "0.8.1",
4
4
  "type": "module",
5
5
  "description": "The Bridge toolbar for a product built with the Bridge design system: opened from Bridge Studio with ?bridge=<change>, it flips the real product between before and after and shows what changed.",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "build:package": "node ../../scripts/build-packages.mjs toolbar"
50
50
  },
51
51
  "dependencies": {
52
- "@bridge-ds/core": "^0.26.1",
52
+ "@bridge-ds/core": "^0.28.0",
53
53
  "lucide-react": "1.58.0"
54
54
  },
55
55
  "peerDependencies": {