Renames the project and makes it as easy to adopt as BitLogin, which was the stated bar and which it did not previously meet. Rename (ADR 0005): @nostr-governance/* -> @bitgate/*, governance-* elements -> bitgate-*, createGovernanceRuntime -> createBitGate. "Governance" reads as DAO voting to this audience and named a category rather than a product; BitGate names the benefit and joins the bit- family that BitVid and BitRoad compose with. Domain types keep their names — the product is BitGate, the subject matter is still governance. The migration plan is left as written; this ADR records the change rather than rewriting history. Drop-in distribution (ADR 0006): adopting BitGate meant supplying a transport, authoring a policy, and wiring every element in JavaScript. Now: - createRelayTransport(urls) — WebSocket transport with reconnection, backoff, subscription replay, and cross-relay dedupe - policy presets (social, commerce, admin-only) addressable by name; the engine still carries no thresholds of its own - <bitgate-provider> — configuration in markup, with descendants finding the runtime through a bubbling context event rather than DOM walking, which breaks across shadow boundaries - npm run build:widget — a self-contained dist/bitgate.js That last one came from running it in a browser rather than from a test. Bare specifiers resolve in Node and vitest but not in a browser, so the widget source worked everywhere except a plain static page — the one place it exists to serve. The bundle is not committed: consumers build from reviewed source. Also found by running it: the provider's ready event fires during element upgrade, before a page that defines elements after parsing can attach a listener, so it now exposes `ready` as a promise too. Four bugs fixed along the way, three found by new tests and one by the browser: admin state stayed silent when a roster change altered capabilities but no denials; the relay transport sent every subscription twice on connect; the commerce preset computed a meaningless visibility verdict at checkout and could hide a listing from its own seller; <bitgate-status> threw on an unusable decision. 759 tests; lint, typecheck, and both builds clean.
2.2 KiB
ADR 0006 — Ship a built, self-contained widget
Status: accepted
Context
BitGate was a well-factored SDK that was not droppable into a page. Adopting it required supplying a transport adapter, authoring a policy definition, and wiring every element in JavaScript. BitLogin sets a much higher bar: copy two files, add one script tag and one element.
Testing the widget in a real browser exposed a failure no unit test could
catch. packages/widget/src/index.js imports @bitgate/core and
@bitgate/runtime as bare specifiers. Node and the test runner resolve those
through node_modules; a browser cannot resolve them at all. The source worked
everywhere except the one place the widget exists to serve.
Decision
Three pieces close the gap.
-
createRelayTransport(urls)— a small WebSocket transport, so nobody writes an adapter to get started. It speaks enough of the protocol to load state, subscribe, and publish; it is not a general-purpose relay client. -
Policy presets —
social,commerce,admin-only, addressable by name. The engine still carries no thresholds: these are application policies that happen to ship in the box, and every number in them is documented as a starting point. -
<bitgate-provider>— configuration in markup. Descendants find the runtime by firing a bubbling context-request event rather than walking up the DOM, which would break across shadow boundaries.
Plus a build step (npm run build:widget) producing a single self-contained
dist/bitgate.js via esbuild.
Consequences
A static page needs one script tag and one element, matching BitLogin.
The build output is deliberately not committed. Consumers build from reviewed source and self-host the result, as BitLogin instructs, because a moderation widget is exactly the kind of artifact where reading the source matters and trusting a prebuilt minified blob does not.
The provider exposes ready as a promise as well as an event. The event fires
during element upgrade — before a page that calls defineBitGateElements()
after parsing could attach a listener — so an event alone was a race that
silently lost. This was found by running the demo, not by a test.