Files
bitgate/packages/widget/README.md
T
Cursor AgentandthePR0M3TH3AN 9e6f6c659e 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]>
2026-10-08 19:14:44 +00:00

211 lines
7.0 KiB
Markdown

# @bitgate/widget
Drop-in custom elements. Framework-agnostic, no bundler required by consumers,
and built strictly on the published API.
## Install
```bash
npm install && npm run build:widget
```
Copy `packages/widget/dist/bitgate.js` (and its `.map`) onto your own origin.
The bundle is deliberately not committed: build it from reviewed source rather
than trusting a prebuilt minified blob for a moderation widget.
## The one-element version
```html
<script type="module" src="/vendor/bitgate/bitgate.js"></script>
<bitgate-provider relays="wss://relay.example" root="<root-pubkey>" policy="social">
<bitgate-veil profile="feed" target-user="<author-pubkey>">
<img src="thumbnail.jpg" alt="" />
</bitgate-veil>
</bitgate-provider>
```
`<bitgate-provider>` builds the runtime, loads administrative state, and shares
both with every descendant. Nothing else to wire.
### Provider attributes
| Attribute | Meaning |
| --- | --- |
| `relays` | Comma-separated relay URLs (required) |
| `root` | Root administrator pubkey, from your deployment config |
| `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:
```html
<bitgate-veil target-user="<pubkey>">…</bitgate-veil>
<bitgate-veil target-event="<id>" target-author="<pubkey>">…</bitgate-veil>
<bitgate-veil target-address="30078:<pubkey>:sku-001">…</bitgate-veil>
```
Assigning `.target` in JavaScript still works and takes precedence.
### With BitLogin
```js
const provider = document.querySelector("bitgate-provider");
await provider.ready;
await provider.useSigner(window.nostr);
```
Await `ready` rather than listening for `bitgate:ready` — the event fires during
element upgrade, which is before a script that runs after parsing could attach a
listener.
## The rule these elements follow
**They render decisions and issue commands. They compute no policy.**
If an element here ever needed to compare a count to a threshold, that would
mean the policy definition is missing a profile — not that the widget needs a
number. Keeping that line is the whole reason the engine was extracted.
## Elements
### Viewer-facing
| Element | Purpose |
| --- | --- |
| `<bitgate-veil>` | Wraps content and applies the decision: blur on `restrict`, disclosure on `hide`, warning on `warn` |
| `<bitgate-report>` | Report dialog. Requires no capability — anyone may report |
| `<bitgate-status>` | Readout of effect, reasons, and evidence |
### Moderator-facing
| Element | Purpose |
| --- | --- |
| `<bitgate-capabilities>` | What the signed-in account may actually do |
| `<bitgate-action>` | A capability-gated button that explains refusals |
| `<bitgate-admin-panel>` | Effective state, contributor attribution, and controls |
## Usage
```html
<script type="module">
import { createBitGate, createCommands } from "@bitgate/runtime";
import { defineBitGateElements } from "@bitgate/widget";
defineBitGateElements();
const runtime = createBitGate({
applicationId: "my-app",
namespace: "myapp",
root: ROOT_PUBKEY,
transport: myRelayAdapter,
policy: myPolicy,
now: () => Math.floor(Date.now() / 1000),
});
await runtime.loadAdministrativeState();
const veil = document.querySelector("bitgate-veil");
veil.runtime = runtime;
veil.target = { type: "event", id: eventId, author: authorPubkey };
</script>
<bitgate-veil profile="feed">
<img src="thumbnail.jpg" alt="" />
</bitgate-veil>
```
Elements receive a runtime; they never construct one. A widget that owned relay
connections would make it impossible to share one governance runtime across a
page.
## Events
All events are `composed` so they cross shadow boundaries.
| Event | Fired when |
| --- | --- |
| `bitgate:revealed` | The viewer chose to show hidden content |
| `bitgate:reported` / `bitgate:report-failed` | A report was published or refused |
| `bitgate:action-completed` / `:action-refused` / `:action-failed` | An action resolved |
| `bitgate:account-restricted` | The admin panel restricted an account |
## Wording
The engine emits stable reason identifiers, never prose, so applications phrase
them for their own audience:
```js
veil.reasonText = {
"admin-user-deny": "This seller is suspended",
"trusted-report-threshold": "Flagged by several buyers you follow",
};
```
## Design decisions worth knowing
**Hidden content is removed from the accessibility tree**, not merely covered.
A screen reader must not read out something the viewer was not meant to see.
**A hidden decision renders a disclosure, not nothing.** Content that silently
vanishes is indistinguishable from a bug and leaves no route to appeal. Where
the profile permits an override, a "Show anyway" control appears; where it does
not, the reason still does.
**Refused actions stay visible and explain themselves.** A moderator who cannot
see that they lack a capability will assume the tool is broken, and a hidden
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 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
Node — registration is a no-op without a DOM, so a server-rendering host can
import the helpers without guarding.