mirror of
https://github.com/PR0M3TH3AN/bitgate.git
synced 2026-11-02 03:21:01 +00:00
Add --bitgate-* theme tokens and a build-injected VERSION.
Widget styles now prefer --bitgate-* custom properties, falling back to the deprecated --gov-* names and then the existing defaults so current pages and overrides keep working. Browser bundles and ESM entry points expose VERSION (and window.bitgate.version) from package.json. Document allowlist on the widget policy preset list. Sync the lockfile so npm ci sees @bitgate/standalone. Co-authored-by: thePR0M3TH3AN <[email protected]>
This commit is contained in:
1 parent
90d4290989
commit
9e6f6c659e
24 files changed
+369
-22
No files matched your search
@@ -34,10 +34,14 @@ both with every descendant. Nothing else to wire.
|
||||
| --- | --- |
|
||||
| `relays` | Comma-separated relay URLs (required) |
|
||||
| `root` | Root administrator pubkey, from your deployment config |
|
||||
| `policy` | `social`, `commerce`, or `admin-only` (default) |
|
||||
| `policy` | `social`, `commerce`, `admin-only` (default), or `allowlist` |
|
||||
| `viewer` | Viewer pubkey, if known at render time |
|
||||
| `application` / `namespace` | Storage and event namespacing |
|
||||
|
||||
`allowlist` is the symmetric twin of a denylist: only publishers with a
|
||||
`user-allow` contribution are shown, unlisted ones are hidden across all four
|
||||
dimensions, and protected actors (root, moderators) are never gated.
|
||||
|
||||
### Target attributes
|
||||
|
||||
Any target-bearing element reads its target from markup:
|
||||
@@ -161,13 +165,44 @@ button teaches nothing about the authority model. The command layer enforces
|
||||
the same check independently — the UI is an explanation, never the gate.
|
||||
|
||||
**Styling is yours.** Elements expose `part` attributes and inherit `color` and
|
||||
`font` from the host. Override the CSS custom properties (`--gov-fg`,
|
||||
`--gov-surface`, `--gov-warn`, `--gov-danger`, `--gov-radius`) or target parts:
|
||||
`font` from the host. Override the CSS custom properties or target parts:
|
||||
|
||||
| Token | Purpose |
|
||||
| --- | --- |
|
||||
| `--bitgate-fg` | Foreground / text (`currentColor`) |
|
||||
| `--bitgate-muted` | Secondary text |
|
||||
| `--bitgate-border` | Control borders |
|
||||
| `--bitgate-surface` | Button / panel background |
|
||||
| `--bitgate-warn` | Warning copy |
|
||||
| `--bitgate-danger` | Danger / destructive accent |
|
||||
| `--bitgate-radius` | Control corner radius |
|
||||
|
||||
Each token prefers `--bitgate-*`, then the deprecated `--gov-*` name
|
||||
(`--gov-fg`, `--gov-muted`, `--gov-border`, `--gov-surface`, `--gov-warn`,
|
||||
`--gov-danger`, `--gov-radius`), then the built-in default. Existing `--gov-*`
|
||||
overrides keep working; new pages should set `--bitgate-*`. `::part()` names
|
||||
are unchanged.
|
||||
|
||||
```css
|
||||
bitgate-veil { --bitgate-warn: #c2410c; }
|
||||
bitgate-veil::part(veil) { border: 1px solid red; }
|
||||
```
|
||||
|
||||
## Version
|
||||
|
||||
The library version is the `package.json` version, injected into browser
|
||||
bundles at build time:
|
||||
|
||||
```js
|
||||
import { VERSION } from "@bitgate/widget";
|
||||
console.log(VERSION); // e.g. "0.1.0"
|
||||
console.log(window.bitgate.version); // same string, for script-tag hosts
|
||||
```
|
||||
|
||||
`window.bitgate` is created if missing; if a host already put an object there,
|
||||
only `.version` is written. Spec / wire versions (`bitgate/1`, policy
|
||||
`schemaVersion`) are independent of this package version.
|
||||
|
||||
## Environment
|
||||
|
||||
`defineBitGateElements()` is safe to call repeatedly and safe to import in
|
||||
|
||||
@@ -19,6 +19,8 @@ import { build, context } from "esbuild";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { bitgateVersionPlugin } from "../../scripts/esbuild-bitgate-version.mjs";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
/** @type {import("esbuild").BuildOptions} */
|
||||
@@ -37,6 +39,7 @@ const options = {
|
||||
banner: {
|
||||
js: "/*! BitGate widget — GPL-3.0-or-later — https://github.com/PR0M3TH3AN/Nostr-Governance */",
|
||||
},
|
||||
plugins: [bitgateVersionPlugin()],
|
||||
};
|
||||
|
||||
const watch = process.argv.includes("--watch");
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// Second, nothing here computes policy. The console displays effective state
|
||||
// and issues commands; every accept/reject decision belongs to the runtime.
|
||||
|
||||
import { GovernanceElement, defineElement, escapeHtml, shortenKey } from "./base.js";
|
||||
import { GovernanceElement, defineElement, escapeHtml, shortenKey, themeVar } from "./base.js";
|
||||
|
||||
/**
|
||||
* @typedef {import('@bitgate/runtime').GovernanceCommands} GovernanceCommands
|
||||
@@ -213,10 +213,10 @@ export class GovernanceAdminPanel extends GovernanceElement {
|
||||
this.renderHtml(`
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
td, th { text-align: left; padding: 0.3em 0.5em; border-bottom: 1px solid var(--gov-border); }
|
||||
td, th { text-align: left; padding: 0.3em 0.5em; border-bottom: 1px solid ${themeVar("border")}; }
|
||||
code { font-size: 0.9em; }
|
||||
h3 { margin: 0; font-size: 1em; }
|
||||
.stale { color: var(--gov-warn); }
|
||||
.stale { color: ${themeVar("warn")}; }
|
||||
</style>
|
||||
<div class="stack" part="panel">
|
||||
${
|
||||
|
||||
+54
-13
@@ -27,34 +27,75 @@ export function canRegisterElements() {
|
||||
return typeof globalThis.HTMLElement === "function" && typeof globalThis.customElements === "object";
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values for widget theme tokens.
|
||||
*
|
||||
* `--gov-*` names are the historical prefix (from Nostr-Governance). `--bitgate-*`
|
||||
* is the agreed prefix. Usage sites prefer the new name, then the old one, then
|
||||
* this default, so existing pages and `--gov-*` overrides keep working.
|
||||
*
|
||||
* @type {Readonly<{
|
||||
* fg: string,
|
||||
* muted: string,
|
||||
* border: string,
|
||||
* surface: string,
|
||||
* warn: string,
|
||||
* danger: string,
|
||||
* radius: string,
|
||||
* }>}
|
||||
*/
|
||||
export const THEME_DEFAULTS = Object.freeze({
|
||||
fg: "currentColor",
|
||||
muted: "color-mix(in srgb, currentColor 60%, transparent)",
|
||||
border: "color-mix(in srgb, currentColor 20%, transparent)",
|
||||
surface: "color-mix(in srgb, canvas 96%, currentColor)",
|
||||
warn: "#b45309",
|
||||
danger: "#b91c1c",
|
||||
radius: "8px",
|
||||
});
|
||||
|
||||
/**
|
||||
* CSS value for a theme token: `--bitgate-*` wins, then deprecated `--gov-*`,
|
||||
* then the existing default.
|
||||
*
|
||||
* `--bitgate-*` is deliberately not assigned on `:host`. Assigning it would
|
||||
* make `var(--bitgate-*, …)` always hit and ignore `--gov-*` overrides.
|
||||
*
|
||||
* @param {keyof typeof THEME_DEFAULTS} name
|
||||
* @returns {string}
|
||||
*/
|
||||
export function themeVar(name) {
|
||||
return `var(--bitgate-${name}, var(--gov-${name}, ${THEME_DEFAULTS[name]}))`;
|
||||
}
|
||||
|
||||
/** Base styles shared by every element, scoped inside each shadow root. */
|
||||
export const BASE_STYLES = `
|
||||
:host {
|
||||
--gov-fg: currentColor;
|
||||
--gov-muted: color-mix(in srgb, currentColor 60%, transparent);
|
||||
--gov-border: color-mix(in srgb, currentColor 20%, transparent);
|
||||
--gov-surface: color-mix(in srgb, canvas 96%, currentColor);
|
||||
--gov-warn: #b45309;
|
||||
--gov-danger: #b91c1c;
|
||||
--gov-radius: 8px;
|
||||
--gov-fg: ${THEME_DEFAULTS.fg};
|
||||
--gov-muted: ${THEME_DEFAULTS.muted};
|
||||
--gov-border: ${THEME_DEFAULTS.border};
|
||||
--gov-surface: ${THEME_DEFAULTS.surface};
|
||||
--gov-warn: ${THEME_DEFAULTS.warn};
|
||||
--gov-danger: ${THEME_DEFAULTS.danger};
|
||||
--gov-radius: ${THEME_DEFAULTS.radius};
|
||||
display: block;
|
||||
color: var(--gov-fg);
|
||||
color: ${themeVar("fg")};
|
||||
font: inherit;
|
||||
}
|
||||
:host([hidden]) { display: none; }
|
||||
button {
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
background: var(--gov-surface);
|
||||
border: 1px solid var(--gov-border);
|
||||
border-radius: var(--gov-radius);
|
||||
background: ${themeVar("surface")};
|
||||
border: 1px solid ${themeVar("border")};
|
||||
border-radius: ${themeVar("radius")};
|
||||
padding: 0.4em 0.8em;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover:not(:disabled) { border-color: var(--gov-fg); }
|
||||
button:hover:not(:disabled) { border-color: ${themeVar("fg")}; }
|
||||
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.reason {
|
||||
color: var(--gov-muted);
|
||||
color: ${themeVar("muted")};
|
||||
font-size: 0.875em;
|
||||
}
|
||||
.row { display: flex; gap: 0.5em; align-items: center; flex-wrap: wrap; }
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { VERSION } from "@bitgate/core";
|
||||
|
||||
/**
|
||||
* Publish `window.bitgate.version` for script-tag integrators.
|
||||
*
|
||||
* Additive: creates `window.bitgate` when missing, otherwise writes `version`
|
||||
* onto the existing object so a host-provided namespace is not replaced.
|
||||
* No-op in Node (no `window`).
|
||||
*
|
||||
* @param {Record<string, any>} [target] - Defaults to `globalThis.window`
|
||||
* @returns {Record<string, any>|null} The namespace that received `version`
|
||||
*/
|
||||
export function exposeBitGateVersion(target) {
|
||||
const root =
|
||||
target ??
|
||||
(typeof globalThis.window !== "undefined"
|
||||
? /** @type {Record<string, any>} */ (globalThis.window)
|
||||
: null);
|
||||
if (!root || typeof root !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existing = root.bitgate;
|
||||
const ns =
|
||||
existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
|
||||
ns.version = VERSION;
|
||||
root.bitgate = ns;
|
||||
return ns;
|
||||
}
|
||||
@@ -21,9 +21,11 @@ export {
|
||||
} from "@bitgate/runtime";
|
||||
|
||||
export {
|
||||
VERSION,
|
||||
SOCIAL_POLICY,
|
||||
COMMERCE_POLICY,
|
||||
ADMIN_ONLY_POLICY,
|
||||
ALLOWLIST_POLICY,
|
||||
POLICY_PRESETS,
|
||||
getPolicyPreset,
|
||||
createPolicyDefinition,
|
||||
@@ -42,6 +44,7 @@ export {
|
||||
|
||||
export {
|
||||
BASE_STYLES,
|
||||
THEME_DEFAULTS,
|
||||
GovernanceElement,
|
||||
canRegisterElements,
|
||||
defineElement,
|
||||
@@ -49,8 +52,11 @@ export {
|
||||
requestContext,
|
||||
shortenKey,
|
||||
targetFromAttributes,
|
||||
themeVar,
|
||||
} from "./base.js";
|
||||
|
||||
export { exposeBitGateVersion } from "./expose-version.js";
|
||||
|
||||
export {
|
||||
BitGateError,
|
||||
BitGateProvider,
|
||||
@@ -76,9 +82,12 @@ export {
|
||||
} from "./admin.js";
|
||||
|
||||
import { defineAdminElements } from "./admin.js";
|
||||
import { exposeBitGateVersion } from "./expose-version.js";
|
||||
import { defineProviderElements } from "./provider.js";
|
||||
import { defineViewerElements } from "./viewer.js";
|
||||
|
||||
exposeBitGateVersion();
|
||||
|
||||
/**
|
||||
* Register every governance element.
|
||||
*
|
||||
|
||||
@@ -151,7 +151,7 @@ export class BitGateProvider extends ProviderBase {
|
||||
|
||||
if (!policy) {
|
||||
throw new Error(
|
||||
`Unknown policy preset "${policyName}". Use social, commerce, admin-only, or set .policy directly.`,
|
||||
`Unknown policy preset "${policyName}". Use social, commerce, admin-only, allowlist, or set .policy directly.`,
|
||||
);
|
||||
}
|
||||
if (relays.length === 0) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
escapeHtml,
|
||||
shortenKey,
|
||||
targetFromAttributes,
|
||||
themeVar,
|
||||
} from "./base.js";
|
||||
|
||||
/**
|
||||
@@ -161,7 +162,7 @@ export class GovernanceVeil extends GovernanceElement {
|
||||
position: absolute; inset: 0; display: grid; place-content: center;
|
||||
gap: 0.5em; text-align: center; padding: 1em;
|
||||
}
|
||||
.warn { color: var(--gov-warn); }
|
||||
.warn { color: ${themeVar("warn")}; }
|
||||
</style>
|
||||
<div class="overlay" part="container">
|
||||
<div class="${blurred ? "blurred" : ""}" part="content"><slot></slot></div>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { build } from "esbuild";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { bitgateVersionPlugin, readLibraryVersion } from "../../../scripts/esbuild-bitgate-version.mjs";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const versionJs = join(HERE, "..", "..", "core", "src", "version.js");
|
||||
|
||||
describe("build-time version injection", () => {
|
||||
it("replaces the version module with package.json's version", async () => {
|
||||
const version = readLibraryVersion();
|
||||
const result = await build({
|
||||
entryPoints: [versionJs],
|
||||
bundle: true,
|
||||
write: false,
|
||||
format: "esm",
|
||||
platform: "neutral",
|
||||
plugins: [bitgateVersionPlugin(version)],
|
||||
});
|
||||
|
||||
const bundled = result.outputFiles[0].text;
|
||||
expect(bundled).toContain(`VERSION = ${JSON.stringify(version)}`);
|
||||
expect(bundled).not.toContain("Keep this string in lockstep");
|
||||
});
|
||||
});
|
||||
@@ -135,6 +135,11 @@ describe("<bitgate-provider>", () => {
|
||||
expect(provider.runtime.policies.policy.id).toBe("bitgate-commerce");
|
||||
});
|
||||
|
||||
it("accepts the allowlist policy preset", async () => {
|
||||
const provider = await mountProvider({ policy: "allowlist" });
|
||||
expect(provider.runtime.policies.policy.id).toBe("bitgate-allowlist");
|
||||
});
|
||||
|
||||
it("defaults to administrative-only governance", async () => {
|
||||
const provider = await mountProvider({ policy: null });
|
||||
expect(provider.runtime.policies.policy.id).toBe("bitgate-admin-only");
|
||||
@@ -154,6 +159,7 @@ describe("<bitgate-provider>", () => {
|
||||
const provider = await mountProvider({ policy: "invented" });
|
||||
expect(provider.runtime).toBeNull();
|
||||
expect(provider.getAttribute("data-error")).toMatch(/Unknown policy preset/);
|
||||
expect(provider.getAttribute("data-error")).toMatch(/allowlist/);
|
||||
});
|
||||
|
||||
it("reports a missing relays attribute", async () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { BASE_STYLES, THEME_DEFAULTS, themeVar } from "../src/base.js";
|
||||
|
||||
describe("theme tokens", () => {
|
||||
it("resolves --bitgate-* first, then deprecated --gov-*, then the default", () => {
|
||||
for (const name of Object.keys(THEME_DEFAULTS)) {
|
||||
expect(themeVar(/** @type {keyof typeof THEME_DEFAULTS} */ (name))).toBe(
|
||||
`var(--bitgate-${name}, var(--gov-${name}, ${THEME_DEFAULTS[name]}))`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps --gov-* host defaults so existing overrides still apply", () => {
|
||||
for (const [name, value] of Object.entries(THEME_DEFAULTS)) {
|
||||
expect(BASE_STYLES).toContain(`--gov-${name}: ${value}`);
|
||||
}
|
||||
});
|
||||
|
||||
it("does not assign --bitgate-* on :host (that would shadow --gov-* overrides)", () => {
|
||||
expect(BASE_STYLES).not.toMatch(/--bitgate-[a-z]+:/);
|
||||
});
|
||||
|
||||
it("uses the fallback chain at every painted token", () => {
|
||||
expect(BASE_STYLES).toContain(themeVar("fg"));
|
||||
expect(BASE_STYLES).toContain(themeVar("muted"));
|
||||
expect(BASE_STYLES).toContain(themeVar("border"));
|
||||
expect(BASE_STYLES).toContain(themeVar("surface"));
|
||||
expect(BASE_STYLES).toContain(themeVar("radius"));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { VERSION, exposeBitGateVersion } from "../src/index.js";
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const rootPkg = JSON.parse(readFileSync(join(HERE, "..", "..", "..", "package.json"), "utf8"));
|
||||
|
||||
describe("VERSION", () => {
|
||||
it("matches package.json", () => {
|
||||
expect(VERSION).toBe(rootPkg.version);
|
||||
});
|
||||
|
||||
it("is published on window.bitgate when the widget is imported", () => {
|
||||
expect(/** @type {any} */ (window).bitgate.version).toBe(VERSION);
|
||||
});
|
||||
|
||||
it("writes version onto an existing window.bitgate object", () => {
|
||||
const target = { bitgate: { extra: true } };
|
||||
exposeBitGateVersion(target);
|
||||
expect(target.bitgate.version).toBe(VERSION);
|
||||
expect(target.bitgate.extra).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user