Files
halfstreet/docs/superpowers/specs/2026-05-17-bug-reporting-design.md
ejlewis 4f6460297f docs: design for Bugpin + Bugsink bug reporting
Footer link triggers Bugpin's screenshot widget (lazy-loaded) which forwards to
GitHub Issues. Bugsink captures uncaught JS errors via the Sentry SDK. Both
servers live on the half.st host.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 22:57:34 -05:00

7.3 KiB
Raw Permalink Blame History

Bug reporting: Bugpin (user) + Bugsink (auto)

Status: Approved design, ready for implementation plan. Date: 2026-05-17

Goal

Let visitors report bugs in the Halfstreet site with a screenshot, and capture uncaught JavaScript errors automatically. Reports should flow to systems already running on the half.st host so there is no new infrastructure to manage.

Two distinct flows

Bugpin Bugsink
Trigger Visitor clicks "Report a Bug" in footer Uncaught JS error
Capture Screenshot, annotations, free-text note, console/network metadata Stack trace, breadcrumbs
Destination Bugpin portal → auto-creates a GitHub Issue in the halfstreet repo Bugsink portal only
Library bugpin.half.st/widget.js (lazy-loaded on click) @sentry/browser npm package (loads on every page)

The two are intentionally separate. Bugpin owns user-facing reports; Bugsink owns automatic exception capture. Sentry's user-feedback widget is not used — that would overlap Bugpin and muddle the responsibilities.

Components

src/pages/index.astro

Add "Report a Bug" to the footer chain after the existing links. Render as a <button> styled to match the surrounding <a> elements so it can dispatch JS without a hash-link navigation.

Final order: © 2026 Ethan J Lewis | GNU 3.0 | Build #<n> | Source Code | Report a Bug

The button only renders when world.ui.bugReport?.enabled is true and a Bugpin server URL is configured. If either is missing, the button is omitted (local dev stays clean).

2. Config

src/world/ui.md — add a bugReport frontmatter block:

bugReport:
  enabled: true
  label: "Report a Bug"
  bugpin:
    serverUrl: "https://bugpin.half.st"
    apiKey: "proj_07df4bf91f12445b8ef8c723e865ed7b"
  bugsink:
    enabled: true
    dsn: "https://231ef18b6b4f426ca249778cfddf821c@bugsink.half.st/1"

The Bugpin project API key and the Bugsink DSN are both designed to ship to clients (they are public credentials that authenticate the project, not the operator), so checking them into the repo is acceptable.

src/world/schema.ts — extend uiFrontmatterSchema with an optional bugReport object:

bugReport: z.object({
  enabled: z.boolean().default(false),
  label: z.string().trim().min(1).default('Report a Bug'),
  bugpin: z.object({
    serverUrl: z.url(),
    apiKey: z.string().trim().min(1),
  }).optional(),
  bugsink: z.object({
    enabled: z.boolean().default(true),
    dsn: z.url(),
  }).optional(),
}).optional(),

src/world/types.ts — extend UiConfig with the matching TypeScript shape.

3. Bugpin lazy loader

New module: src/ui/bug-report.ts

Responsibilities:

  • Read Bugpin config from a data-* attribute on the footer button (so the module stays free of Astro imports).
  • On first click, inject <script src="${serverUrl}/widget.js" data-api-key="${apiKey}" async> into <head>. Hide Bugpin's built-in floating button using whatever option the widget exposes (likely data-hidden="true" or a CSS rule scoped via the widget's Shadow DOM hook — confirm against the install guide during implementation).
  • Once the script resolves, call window.BugPin.open().
  • Cache the loaded state on a module-level flag; subsequent clicks just call open() again.
  • If the script fails to load (offline, server down), log to the console and surface a minimal inline message ("Couldn't open bug reporter — try refreshing"). Do not break the page.

Wire-up in src/pages/index.astro:

  • The footer button gets data-bug-report-trigger, data-bugpin-server, data-bugpin-key attributes.
  • A new <script> block imports ../ui/bug-report.ts alongside the existing terminal/theme imports.

The lazy-load is deliberate: ~150 KB of widget code stays off the cold load for visitors who never click.

4. Bugsink init

New module: src/ui/error-tracking.ts

Responsibilities:

  • Add @sentry/browser as a dependency.
  • Read the DSN from a data-bugsink-dsn attribute set on <body> (or a meta tag) so the module is server-render-agnostic.
  • On import, call:
    Sentry.init({
      dsn,
      tracesSampleRate: 0,
      replaysSessionSampleRate: 0,
      replaysOnErrorSampleRate: 0,
      integrations: [],
    })
    
  • If dsn is missing or bugsink.enabled is false, no-op.

Error-capture only — no performance tracing, no session replay. Keeps the bundle small and avoids accidentally shipping a replay tool we didn't design for.

Wire-up in src/pages/index.astro:

  • A new <script> block imports ../ui/error-tracking.ts near the existing imports.

5. Bugpin GitHub wiring

Out of scope for the code change, but recorded here so it isn't lost during implementation:

  1. In the halfstreet GitHub repo, create a classic personal access token (or fine-grained equivalent) with repo scope.
  2. In Bugpin admin (https://bugpin.half.st/portal): Projects → halfstreet → Integrations → GitHub. Paste the token, select the halfstreet repo, set default labels (e.g., bug, user-report).
  3. Verify by submitting a test report from the footer link and confirming an issue appears in GitHub.

Out of scope

  • Markdown files under src/world/bugs/. Original TODO #45 proposed this; explicitly dropped — Bugpin's portal + GitHub Issues replace that storage path.
  • Gitea mirroring. Bugpin does not natively support Gitea. Deferred until there's clear value in a Bugpin → Gitea bridge. The front-end design does not preclude this; it would be a server-side addition on the Bugpin host.
  • Sentry user-feedback widget. Bugpin owns user reports; Bugsink stays auto-only.
  • PII redaction policy. Bugpin's widget includes a built-in blur tool; Bugsink only captures stack traces, not request bodies. No additional scrubbing layer.

Risks / things to verify during implementation

  • Bugpin "hide default button" mechanism. Docs mention a floating launcher; the exact opt-out attribute needs to be confirmed against the install guide or the widget source. If no opt-out exists, fall back to CSS that hides the widget's launcher selector inside its Shadow DOM root (or wrap with a custom-element rule).
  • Bugsink Sentry SDK compatibility. Bugsink claims Sentry-SDK compatibility but tested feature surface varies. Verify with @sentry/browser v9 (current stable as of 2026-05) — if a specific minor version is needed, pin it.
  • Cold-start cost. Bugsink adds ~3040 KB gzipped to the page. Acceptable for an authored, low-traffic site; flag if budget gets tight.

TODOs.md update

Replace line 45 with:

- [ ] Add a "Report a Bug" footer link backed by Bugpin (widget at bugpin.half.st → forwards to GitHub Issues). Add Bugsink (@sentry/browser → bugsink.half.st) for automatic JS error capture.

Mark complete once the implementation lands and a test report has appeared in both Bugpin's portal and GitHub.

Files touched (preview)

  • src/world/ui.md — add bugReport block.
  • src/world/schema.ts — extend uiFrontmatterSchema.
  • src/world/types.ts — extend UiConfig.
  • src/pages/index.astro — render the button conditionally, wire data attributes, import the two new UI modules.
  • src/ui/bug-report.tsnew, lazy loads the Bugpin widget on click.
  • src/ui/error-tracking.tsnew, initializes Sentry SDK against Bugsink DSN.
  • package.json / package-lock.json — add @sentry/browser.
  • src/world/TODOs.md — rewrite line 45.