docs: implementation plan for bug reporting
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,653 @@
|
|||||||
|
# Bug Reporting Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Add a "Report a Bug" footer link that opens the Bugpin screenshot widget (forwards to GitHub Issues), plus auto error capture via Bugsink (Sentry-compatible) on every page.
|
||||||
|
|
||||||
|
**Architecture:** Two independent flows. Bugpin = user-initiated, lazy-loaded `<script>` widget from `bugpin.half.st`, triggered by a footer `<button>`. Bugsink = background, `@sentry/browser` SDK initialised on page load against a DSN at `bugsink.half.st`. Both credentials are public project tokens and live in `src/world/ui.md`. Both flows are no-ops when not configured.
|
||||||
|
|
||||||
|
**Tech Stack:** Astro 6, TypeScript, Zod 4, Vitest 4 (node env, no jsdom), `@sentry/browser` (new dep), Bugpin widget script.
|
||||||
|
|
||||||
|
**Spec:** `docs/superpowers/specs/2026-05-17-bug-reporting-design.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File map
|
||||||
|
|
||||||
|
- `src/world/schema.ts` — **modify**: extend `uiFrontmatterSchema` with optional `bugReport` block.
|
||||||
|
- `src/world/schema.test.ts` — **modify**: add accept/reject cases for `bugReport`.
|
||||||
|
- `src/world/types.ts` — **modify**: extend `UiConfig` to expose `bugReport`.
|
||||||
|
- `src/world/ui.md` — **modify**: add `bugReport` frontmatter.
|
||||||
|
- `src/ui/error-tracking.ts` — **create**: reads body data attrs, calls `Sentry.init`. No-op when DSN missing.
|
||||||
|
- `src/ui/bug-report.ts` — **create**: wires footer button click to lazy-load Bugpin widget, then call `BugPin.open()`. No-op when config missing.
|
||||||
|
- `src/pages/index.astro` — **modify**: render the footer button conditionally, attach data attrs to `<body>`, import the two new modules.
|
||||||
|
- `package.json` / `package-lock.json` — **modify**: add `@sentry/browser`.
|
||||||
|
- `src/world/TODOs.md:45` — **modify**: rewrite to reflect actual approach.
|
||||||
|
|
||||||
|
No DOM tests for the two new UI modules: the project's Vitest config runs in node with no jsdom. Coverage for those modules is `astro check` (type safety) + a manual smoke step at the end of the plan. Schema changes get full unit test coverage via the existing test file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Extend `uiFrontmatterSchema` with `bugReport` (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/world/schema.ts`
|
||||||
|
- Modify: `src/world/schema.test.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add the failing tests**
|
||||||
|
|
||||||
|
Append these inside the existing `describe('uiFrontmatterSchema', ...)` block in `src/world/schema.test.ts` (just before the closing `})` on line 120):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('accepts a bugReport block with bugpin and bugsink subconfigs', () => {
|
||||||
|
const data = {
|
||||||
|
pageTitle: 'Halfstreet',
|
||||||
|
description: 'A gothic mystery.',
|
||||||
|
footer: {
|
||||||
|
copyright: '© 2026 Ethan J Lewis',
|
||||||
|
links: [],
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect(() => uiFrontmatterSchema.parse(data)).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts ui config with no bugReport block at all', () => {
|
||||||
|
const data = {
|
||||||
|
pageTitle: 'Halfstreet',
|
||||||
|
description: 'A gothic mystery.',
|
||||||
|
footer: { copyright: '© 2026 Ethan J Lewis', links: [] },
|
||||||
|
}
|
||||||
|
expect(() => uiFrontmatterSchema.parse(data)).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects a bugpin block with a non-url serverUrl', () => {
|
||||||
|
const data = {
|
||||||
|
pageTitle: 'Halfstreet',
|
||||||
|
description: 'A gothic mystery.',
|
||||||
|
footer: { copyright: '© 2026 Ethan J Lewis', links: [] },
|
||||||
|
bugReport: {
|
||||||
|
enabled: true,
|
||||||
|
bugpin: { serverUrl: 'not-a-url', apiKey: 'proj_x' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect(() => uiFrontmatterSchema.parse(data)).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects a bugsink block with a non-url dsn', () => {
|
||||||
|
const data = {
|
||||||
|
pageTitle: 'Halfstreet',
|
||||||
|
description: 'A gothic mystery.',
|
||||||
|
footer: { copyright: '© 2026 Ethan J Lewis', links: [] },
|
||||||
|
bugReport: {
|
||||||
|
enabled: true,
|
||||||
|
bugsink: { dsn: 'whatever' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect(() => uiFrontmatterSchema.parse(data)).toThrow()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests, confirm they fail**
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test -- src/world/schema.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: the four new tests fail because `bugReport` is currently unknown (zod by default strips extras, so the "accepts" cases probably pass — but the "rejects" cases will fail because zod will accept anything for unknown fields). Either way, confirm the test runner reports at least the two "rejects" cases as failing.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add the schema fields**
|
||||||
|
|
||||||
|
In `src/world/schema.ts`, find `uiFrontmatterSchema` (starts at line 48). Add a `bugReport` field at the end of the object (after `features`, before the closing `})`). Final block:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const uiFrontmatterSchema = z.object({
|
||||||
|
pageTitle: z.string().trim().min(1),
|
||||||
|
description: z.string().trim().min(1),
|
||||||
|
robots: z.string().trim().min(1).default('noindex'),
|
||||||
|
themeColor: z.string().trim().min(1).default('#1a0d00'),
|
||||||
|
footer: z.object({
|
||||||
|
copyright: z.string().trim().min(1),
|
||||||
|
copyrightHref: z.url().optional(),
|
||||||
|
buildLabel: z.string().trim().min(1).default('Build #'),
|
||||||
|
showBuild: z.boolean().default(true),
|
||||||
|
links: z.array(z.object({
|
||||||
|
label: z.string().trim().min(1),
|
||||||
|
href: z.url(),
|
||||||
|
})).default([]),
|
||||||
|
}),
|
||||||
|
features: z.object({
|
||||||
|
chips: z.boolean().default(true),
|
||||||
|
lightMeter: z.boolean().default(true),
|
||||||
|
typedEffect: z.boolean().default(true),
|
||||||
|
roomScroll: z.boolean().default(true),
|
||||||
|
}).default({
|
||||||
|
chips: true,
|
||||||
|
lightMeter: true,
|
||||||
|
typedEffect: true,
|
||||||
|
roomScroll: true,
|
||||||
|
}),
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run tests, confirm pass**
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test -- src/world/schema.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all `uiFrontmatterSchema` tests pass (the original two + the four new ones).
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/world/schema.ts src/world/schema.test.ts
|
||||||
|
git commit -m "feat(world): add bugReport block to ui schema"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Surface `bugReport` on the `UiConfig` type
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/world/types.ts:64-85`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Extend the interface**
|
||||||
|
|
||||||
|
In `src/world/types.ts`, replace the existing `UiConfig` interface (currently lines 64–85) with:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export interface UiConfig {
|
||||||
|
pageTitle: string
|
||||||
|
description: string
|
||||||
|
robots: string
|
||||||
|
themeColor: string
|
||||||
|
footer: {
|
||||||
|
copyright: string
|
||||||
|
copyrightHref?: string
|
||||||
|
buildLabel: string
|
||||||
|
showBuild: boolean
|
||||||
|
links: Array<{
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
features: {
|
||||||
|
chips: boolean
|
||||||
|
lightMeter: boolean
|
||||||
|
typedEffect: boolean
|
||||||
|
roomScroll: boolean
|
||||||
|
}
|
||||||
|
bugReport?: {
|
||||||
|
enabled: boolean
|
||||||
|
label: string
|
||||||
|
bugpin?: {
|
||||||
|
serverUrl: string
|
||||||
|
apiKey: string
|
||||||
|
}
|
||||||
|
bugsink?: {
|
||||||
|
enabled: boolean
|
||||||
|
dsn: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify type check passes**
|
||||||
|
|
||||||
|
```
|
||||||
|
npx astro check
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 0 errors, 0 warnings (or at least no new errors introduced by this change).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/world/types.ts
|
||||||
|
git commit -m "feat(world): expose bugReport on UiConfig"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Add real config to `ui.md`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/world/ui.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Edit frontmatter**
|
||||||
|
|
||||||
|
Insert this block in `src/world/ui.md` between the `features:` block and the closing `---`, so the frontmatter ends like:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
features:
|
||||||
|
chips: true
|
||||||
|
lightMeter: true
|
||||||
|
typedEffect: true
|
||||||
|
roomScroll: true
|
||||||
|
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"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify the world loads**
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test -- src/world/buildWorld.test.ts
|
||||||
|
npm test -- src/world/loader.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all tests pass — the new frontmatter is valid.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/world/ui.md
|
||||||
|
git commit -m "feat(world): wire bugpin + bugsink credentials in ui.md"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: Install `@sentry/browser`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `package.json`, `package-lock.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Install**
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install @sentry/browser
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: a single new dependency added under `"dependencies"` in `package.json`; lockfile updates.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
|
```
|
||||||
|
node -e "require('@sentry/browser')"
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no output and exit code 0.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add package.json package-lock.json
|
||||||
|
git commit -m "chore: add @sentry/browser for bugsink integration"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: Create the Bugsink init module
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ui/error-tracking.ts`
|
||||||
|
|
||||||
|
No unit test — Vitest runs in node with no jsdom, and this module only does work in a browser. Coverage is `astro check` (types) + the manual smoke at the end.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the module**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/ui/error-tracking.ts
|
||||||
|
import * as Sentry from '@sentry/browser'
|
||||||
|
|
||||||
|
const dsn = document.body?.dataset.bugsinkDsn ?? ''
|
||||||
|
|
||||||
|
if (dsn) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn,
|
||||||
|
tracesSampleRate: 0,
|
||||||
|
replaysSessionSampleRate: 0,
|
||||||
|
replaysOnErrorSampleRate: 0,
|
||||||
|
integrations: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Type-check**
|
||||||
|
|
||||||
|
```
|
||||||
|
npx astro check
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 0 new errors.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/ui/error-tracking.ts
|
||||||
|
git commit -m "feat(ui): initialise sentry browser sdk against bugsink dsn"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: Create the Bugpin trigger module
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/ui/bug-report.ts`
|
||||||
|
|
||||||
|
Module reads config from the footer button's `data-*` attributes, lazy-loads the Bugpin widget on first click, then calls `BugPin.open()`. Subsequent clicks just call `open()` again.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the module**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// src/ui/bug-report.ts
|
||||||
|
|
||||||
|
interface BugPinAPI {
|
||||||
|
open: () => void
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
BugPin?: BugPinAPI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = document.querySelector<HTMLButtonElement>('[data-bug-report-trigger]')
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
const serverUrl = button.dataset.bugpinServer
|
||||||
|
const apiKey = button.dataset.bugpinKey
|
||||||
|
|
||||||
|
if (serverUrl && apiKey) {
|
||||||
|
let loadPromise: Promise<void> | null = null
|
||||||
|
|
||||||
|
const ensureLoaded = (): Promise<void> => {
|
||||||
|
if (window.BugPin) return Promise.resolve()
|
||||||
|
if (loadPromise) return loadPromise
|
||||||
|
|
||||||
|
loadPromise = new Promise<void>((resolve, reject) => {
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = `${serverUrl.replace(/\/$/, '')}/widget.js`
|
||||||
|
script.async = true
|
||||||
|
script.dataset.apiKey = apiKey
|
||||||
|
script.addEventListener('load', () => resolve())
|
||||||
|
script.addEventListener('error', () => {
|
||||||
|
loadPromise = null
|
||||||
|
reject(new Error(`Failed to load Bugpin widget from ${script.src}`))
|
||||||
|
})
|
||||||
|
document.head.appendChild(script)
|
||||||
|
})
|
||||||
|
|
||||||
|
return loadPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
button.disabled = true
|
||||||
|
try {
|
||||||
|
await ensureLoaded()
|
||||||
|
window.BugPin?.open()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[bug-report]', err)
|
||||||
|
} finally {
|
||||||
|
button.disabled = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Type-check**
|
||||||
|
|
||||||
|
```
|
||||||
|
npx astro check
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 0 new errors.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/ui/bug-report.ts
|
||||||
|
git commit -m "feat(ui): lazy-load bugpin widget on footer click"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 7: Wire footer button and module imports in `index.astro`
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/pages/index.astro`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add config bindings to the frontmatter script**
|
||||||
|
|
||||||
|
In the `---` block at the top of `src/pages/index.astro`, after `const remainingFooterLinks = footerLinks.slice(1)`, add:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const bugReport = ui?.bugReport
|
||||||
|
const bugpinConfig = bugReport?.enabled ? bugReport.bugpin : undefined
|
||||||
|
const bugsinkDsn =
|
||||||
|
bugReport?.enabled && bugReport.bugsink?.enabled ? bugReport.bugsink.dsn : ''
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add the `data-bugsink-dsn` attribute to `<body>`**
|
||||||
|
|
||||||
|
Replace the existing `<body>` opening tag with:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
<body data-bugsink-dsn={bugsinkDsn}>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add the footer button**
|
||||||
|
|
||||||
|
In the `<footer class="mystery-footer">` block (currently lines 89–109), append this AFTER the existing `{remainingFooterLinks.map(...)}` block, before the closing `</footer>`:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
{bugpinConfig && (
|
||||||
|
<>
|
||||||
|
<span aria-hidden="true">|</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mystery-footer-bug-report"
|
||||||
|
data-bug-report-trigger
|
||||||
|
data-bugpin-server={bugpinConfig.serverUrl}
|
||||||
|
data-bugpin-key={bugpinConfig.apiKey}
|
||||||
|
>{bugReport?.label ?? 'Report a Bug'}</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Import the two new modules**
|
||||||
|
|
||||||
|
Replace the existing module-import script block (currently lines 130–133):
|
||||||
|
|
||||||
|
```astro
|
||||||
|
<script>
|
||||||
|
import '../ui/terminal.ts'
|
||||||
|
import '../ui/theme.ts'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
with:
|
||||||
|
|
||||||
|
```astro
|
||||||
|
<script>
|
||||||
|
import '../ui/terminal.ts'
|
||||||
|
import '../ui/theme.ts'
|
||||||
|
import '../ui/error-tracking.ts'
|
||||||
|
import '../ui/bug-report.ts'
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Add CSS so the button matches the surrounding links**
|
||||||
|
|
||||||
|
Open `src/ui/crt.css` and grep for `.mystery-footer` to find the existing footer styles. Add a rule that makes the button look identical to the footer anchors. Append this block at the end of the existing `.mystery-footer` rules (search for `.mystery-footer a` and add directly after that selector's rule):
|
||||||
|
|
||||||
|
```css
|
||||||
|
.mystery-footer-bug-report {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.mystery-footer-bug-report:hover,
|
||||||
|
.mystery-footer-bug-report:focus-visible {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.mystery-footer-bug-report:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: progress;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If `.mystery-footer a` styles its anchors differently (e.g. specific color, no underline by default), match those properties instead. Look at the actual `a` rule and copy its `color`/`text-decoration` declarations into `.mystery-footer-bug-report` so the two render visually identically.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run the full type check and build**
|
||||||
|
|
||||||
|
```
|
||||||
|
npx astro check
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: 0 errors, build succeeds.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/pages/index.astro src/ui/crt.css
|
||||||
|
git commit -m "feat(ui): add Report a Bug footer button and wire bug tracking modules"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 8: Update TODOs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/world/TODOs.md:45`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Rewrite the TODO line**
|
||||||
|
|
||||||
|
Replace line 45 of `src/world/TODOs.md`:
|
||||||
|
|
||||||
|
```
|
||||||
|
- [ ] Set up BugPin as a self-hosted visual bug reporter for the site, then have incoming reports create markdown files under `src/world/bugs/` via a webhook or API bridge so bugs can be tracked in git alongside the game content. Include screenshot/annotation metadata in the markdown and decide whether these bug docs stay outside the world loader or get their own loader later.
|
||||||
|
```
|
||||||
|
|
||||||
|
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 after manual verification: a test report appears in the Bugpin portal AND a GitHub issue is created AND a thrown error appears in the Bugsink portal.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/world/TODOs.md
|
||||||
|
git commit -m "docs: rewrite TODO 45 to reflect Bugpin/Bugsink approach"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 9: Manual smoke test
|
||||||
|
|
||||||
|
**Pre-req:** the Bugpin GitHub integration must be configured server-side before user reports will create issues. See spec §5. Steps below test the widget plumbing regardless.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Start dev server**
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open the URL it prints (default `http://localhost:4321`).
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify the footer button renders**
|
||||||
|
|
||||||
|
In the rendered footer at the bottom of the page, confirm "Report a Bug" appears after "Source Code", styled like the surrounding links.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the widget lazy-loads**
|
||||||
|
|
||||||
|
Open DevTools → Network. Filter for `widget.js`. Confirm NO request to `bugpin.half.st/widget.js` on cold page load.
|
||||||
|
|
||||||
|
Click "Report a Bug". Confirm:
|
||||||
|
- A single GET to `https://bugpin.half.st/widget.js` (status 200).
|
||||||
|
- The Bugpin dialog opens.
|
||||||
|
- Take a screenshot, write "smoke test" in the note, submit.
|
||||||
|
- The dialog closes / shows a success state.
|
||||||
|
|
||||||
|
Re-click "Report a Bug". Confirm NO second `widget.js` request — only the dialog re-opens.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify Bugpin received the report**
|
||||||
|
|
||||||
|
Open `https://bugpin.half.st/portal`. Confirm the "smoke test" report appears with the screenshot attached.
|
||||||
|
|
||||||
|
If the GitHub integration is configured: confirm a matching issue appears in the halfstreet GitHub repo.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify Bugsink captures an uncaught error**
|
||||||
|
|
||||||
|
In the dev server's running page, open DevTools console and run:
|
||||||
|
|
||||||
|
```js
|
||||||
|
setTimeout(() => { throw new Error('bugsink smoke test') }, 0)
|
||||||
|
```
|
||||||
|
|
||||||
|
Open `https://bugsink.half.st`. Confirm a "bugsink smoke test" event appears within ~30 seconds.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Verify the launcher styling is acceptable**
|
||||||
|
|
||||||
|
After step 3, the Bugpin widget's built-in floating launcher may now also be visible on the page. If its presence is visually objectionable, add a CSS rule to `src/ui/crt.css` targeting the Bugpin host element (inspect the DOM to find the selector — Bugpin uses Shadow DOM with a custom-element host like `<bugpin-widget>` or similar). Example:
|
||||||
|
|
||||||
|
```css
|
||||||
|
bugpin-widget,
|
||||||
|
[data-bugpin-launcher] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If this turns out to be needed, commit the CSS as `style(ui): hide bugpin built-in launcher (we have our own trigger)`. If the launcher's appearance is fine as-is, skip this step.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Mark TODO #45 complete**
|
||||||
|
|
||||||
|
Once steps 1–5 all pass, edit `src/world/TODOs.md` line 45, change `- [ ]` to `- [x]`, and commit:
|
||||||
|
|
||||||
|
```
|
||||||
|
git add src/world/TODOs.md
|
||||||
|
git commit -m "chore: mark TODO 45 complete"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Done criteria
|
||||||
|
|
||||||
|
All of the following are true:
|
||||||
|
|
||||||
|
- `npm test` passes.
|
||||||
|
- `npx astro check` reports 0 errors.
|
||||||
|
- `npm run build` succeeds.
|
||||||
|
- Manual smoke (Task 9 steps 1–5) all pass.
|
||||||
|
- TODO #45 is `[x]`.
|
||||||
Reference in New Issue
Block a user