|
| 1 | +# Report a Bug — prefilled GitHub issue (+ Storybook bootstrap) |
| 2 | + |
| 3 | +**Date:** 2026-06-14 |
| 4 | +**Branch:** `rewrite/web-foundation` |
| 5 | +**Status:** Design — pending implementation plan |
| 6 | + |
| 7 | +## Summary |
| 8 | + |
| 9 | +The web/ rewrite is a full redesign. New UI means new rough edges, and the only |
| 10 | +current path to report anything is buried three clicks deep (App menu → Help → |
| 11 | +"Contact us" / GitHub link). This adds a **prominent, persistent "Report a bug" |
| 12 | +entry point** that produces an **actionable, prefilled GitHub issue** — zero new |
| 13 | +backend, structured context auto-attached. |
| 14 | + |
| 15 | +It also **bootstraps Storybook** in the rewrite (none exists today), with the |
| 16 | +Report-a-bug components as its first inhabitants, establishing the pattern for the |
| 17 | +rest of the rewrite's UI primitives. |
| 18 | + |
| 19 | +## Goal & success criteria |
| 20 | + |
| 21 | +- A user who hits a bug can report it in **≤2 clicks** from any screen. |
| 22 | +- The resulting GitHub issue contains enough context (description + app version + |
| 23 | + browser/OS + view + optional DSL) to **reproduce without a back-and-forth**. |
| 24 | +- No proprietary diagram leaks **without informed consent** (public-issue warning). |
| 25 | +- Storybook runs in `web/` (`pnpm storybook`) showing the new components' states |
| 26 | + against the real Drafting Table tokens + dark ink surface. |
| 27 | + |
| 28 | +## Context & constraints |
| 29 | + |
| 30 | +- **Stack:** React 19 + Vite 8 + Tailwind 3 + Vitest/Testing Library. UI built |
| 31 | + from `web/src/ui/*` primitives (Drafting Table design system: semantic tokens, |
| 32 | + dark "ink" surfaces for chrome/modals, "paper" for the canvas). |
| 33 | +- **Backend is frozen** — no new Firebase cloud functions. This rules out a |
| 34 | + server-side report sink and is *why* we use a client-only GitHub-prefill flow. |
| 35 | +- **Audience is developers**; the repo is public and already linked in Help |
| 36 | + (`github.com/ZenUml/web-sequence`), so GitHub issues are a natural, zero-infra |
| 37 | + destination. |
| 38 | +- **Global stylesheet:** `web/src/styles/globals.css` (imported in `main.tsx`). |
| 39 | +- **No Storybook today:** no `.storybook/`, no `*.stories.*`, no SB deps. Vite 8 |
| 40 | + needs **Storybook 9.x** (SB 8 topped out at Vite 6; Vite 8 / Rolldown support |
| 41 | + landed and shipped — storybook#33789, closed "Done", Feb 2026). |
| 42 | + |
| 43 | +## Decisions (from brainstorming) |
| 44 | + |
| 45 | +| Question | Decision | |
| 46 | +|---|---| |
| 47 | +| Purpose | **Bug reports for the redesign rollout** — optimized for actionable, context-rich reports. | |
| 48 | +| Destination | **Prefilled GitHub issue** (client-only; no backend). | |
| 49 | +| Include diagram DSL? | **Yes, via an opt-in checkbox defaulted ON**, clearly labeled "will be public." | |
| 50 | +| Placement | **Floating button, bottom-right** (keeps the header's three-verb group pure). | |
| 51 | +| Storybook | **Bootstrap Storybook 9 in this feature**; ship the first stories here. | |
| 52 | + |
| 53 | +Reversible calls made by the author (not separately confirmed; cheap to change): |
| 54 | +- **FAB is app-wide** (editor + hub), so breakage on any screen is reportable. |
| 55 | + DSL capture is only offered when there is editor content. |
| 56 | +- **Fallback links to the existing Contact us page** (`zenuml.com/docs/about/contact-us`), |
| 57 | + not a `mailto:` — there is no verified support email to hardcode. |
| 58 | + |
| 59 | +## Architecture & components |
| 60 | + |
| 61 | +All new code under `web/src/`. |
| 62 | + |
| 63 | +| File | Responsibility | |
| 64 | +|---|---| |
| 65 | +| `services/bugReport.ts` | **Pure** `buildIssueUrl(input): string`. The only piece with real logic → the most-tested. Builds title + markdown body + `labels`, URL-encodes, and enforces the length budget (truncates DSL last). No DOM, no globals — takes everything as input. | |
| 66 | +| `components/feedback/ReportBugButton.tsx` | The FAB — fixed bottom-right pill (🐞 "Report a bug"), built from `ui/Button` + tokens. Icon-only below `md`. Opens the modal. | |
| 67 | +| `components/feedback/ReportBugModal.tsx` | `Dialog`/`DialogContent` (dark ink surface) holding: description `Textarea`; DSL toggle (`ui/Switch`, default **on**, "Include my diagram code — will be public"); an "Attached:" summary listing exactly what goes in the issue; primary **Open GitHub issue** button; quiet "No GitHub account? **Contact us**" link. | |
| 68 | +| `app/AppRoot.tsx` (wiring) | Renders `ReportBugButton` app-wide; supplies current DSL (from the editor store), `APP_VERSION`, current view, signed-in flag, and the analytics hook. On submit: `window.open(url, '_blank', 'noopener,noreferrer')`. | |
| 69 | + |
| 70 | +**Boundaries:** the button knows nothing about GitHub; the modal collects intent; |
| 71 | +`buildIssueUrl` is a pure transform; AppRoot is the only place that touches the |
| 72 | +store/window. Each is understandable and testable in isolation. |
| 73 | + |
| 74 | +## `buildIssueUrl` contract |
| 75 | + |
| 76 | +Input: |
| 77 | + |
| 78 | +```ts |
| 79 | +interface BuildIssueInput { |
| 80 | + description: string; // required, non-empty (UI gates this) |
| 81 | + includeDsl: boolean; |
| 82 | + dsl?: string; // current editor source; omitted when no content |
| 83 | + appVersion: string; // e.g. "2026.6.7" |
| 84 | + userAgent: string; // navigator.userAgent |
| 85 | + view: 'editor' | 'hub' | string; |
| 86 | + signedIn: boolean; |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +Output URL: |
| 91 | +`https://github.com/ZenUml/web-sequence/issues/new?title=<t>&body=<b>&labels=bug` |
| 92 | + |
| 93 | +- **Title** = first non-empty line of `description`, trimmed to ~80 chars. |
| 94 | +- **Body** (markdown): |
| 95 | + |
| 96 | + ```markdown |
| 97 | + **Describe the bug** |
| 98 | + <description> |
| 99 | + |
| 100 | + **Environment** |
| 101 | + - App version: 2026.6.7 |
| 102 | + - Browser: <userAgent> |
| 103 | + - View: editor · Signed in: no |
| 104 | + |
| 105 | + <details><summary>Diagram DSL</summary> |
| 106 | + |
| 107 | + ``` |
| 108 | + <dsl — only when includeDsl && dsl> |
| 109 | + ``` |
| 110 | + </details> |
| 111 | + ``` |
| 112 | + |
| 113 | +- **`labels=bug`** — best-effort. GitHub applies it only if the label exists on |
| 114 | + the repo and is ignored otherwise (never errors). (Plan: confirm a `bug` label |
| 115 | + exists; if not, either create it once or drop the param.) |
| 116 | + |
| 117 | +### Edge cases |
| 118 | + |
| 119 | +- **URL length (414 URI Too Long).** GitHub rejects oversized prefills. The |
| 120 | + builder caps the encoded URL to a safe budget (~6 KB). DSL is the elastic part: |
| 121 | + if it overflows, it is truncated with a `… (truncated — please paste the rest)` |
| 122 | + marker so the report still goes through. Description + environment are never |
| 123 | + dropped. |
| 124 | +- **Empty description.** UI disables Open until there is text; `buildIssueUrl` is |
| 125 | + not called. |
| 126 | +- **No DSL available.** When `dsl` is empty/whitespace, the modal hides the DSL |
| 127 | + toggle entirely and the body omits the `<details>` block. |
| 128 | + |
| 129 | +## Visibility & telemetry |
| 130 | + |
| 131 | +- FAB rendered app-wide; styled quiet (low-emphasis until hover) so it doesn't |
| 132 | + fight the diagram. Not dismissable — prominence is the point. |
| 133 | +- Two Mixpanel events via the existing `useAnalytics`: |
| 134 | + - `bug_report_opened` — modal opened. |
| 135 | + - `bug_report_submitted` — `{ included_dsl: boolean, view: string }`. |
| 136 | + This tells us whether the redesign-QA channel is actually used. |
| 137 | + |
| 138 | +## Storybook bootstrap |
| 139 | + |
| 140 | +First Storybook in the rewrite. Keep it minimal and token-faithful. |
| 141 | + |
| 142 | +- **Deps (devDependencies):** `storybook`, `@storybook/react-vite` (latest 9.x), |
| 143 | + `@storybook/addon-a11y` (cheap, useful for a contrast-sensitive design system). |
| 144 | + Pin exact versions at install; **boot `storybook dev` once** to confirm the |
| 145 | + Rolldown/Vite 8 builder starts before claiming done. |
| 146 | +- **`web/.storybook/main.ts`:** framework `@storybook/react-vite`; stories glob |
| 147 | + `../src/**/*.stories.@(ts|tsx)`; addons `[a11y]`. Inherits the project Vite |
| 148 | + config (Tailwind via the existing `postcss.config.js`, so utilities + tokens |
| 149 | + resolve with no extra wiring). |
| 150 | +- **`web/.storybook/preview.ts`:** `import '../src/styles/globals.css'` so tokens |
| 151 | + load; a global decorator that wraps every story in the **dark ink surface** |
| 152 | + (matching where this UI lives) with the app base font; `parameters.backgrounds` |
| 153 | + offering ink (default) + paper. |
| 154 | +- **Scripts (`web/package.json`):** |
| 155 | + - `"storybook": "storybook dev -p 6006"` |
| 156 | + - `"build-storybook": "storybook build"` |
| 157 | +- **Risk note:** Vite 8 is new; if an addon lags, drop to core + react-vite only. |
| 158 | + CI wiring for `build-storybook` is **out of scope** here (left for a follow-up) |
| 159 | + — this feature only stands up local Storybook + the first stories. |
| 160 | + |
| 161 | +### Stories to ship (colocated `*.stories.tsx`, matching the test-colocation pattern) |
| 162 | + |
| 163 | +`components/feedback/ReportBugButton.stories.tsx`: |
| 164 | +- **Default** — desktop pill. |
| 165 | +- **Compact** — icon-only (mobile viewport). |
| 166 | + |
| 167 | +`components/feedback/ReportBugModal.stories.tsx` (rendered `open`): |
| 168 | +- **Empty** — Open disabled. |
| 169 | +- **Filled** — description present, Open enabled. |
| 170 | +- **DslIncluded** — toggle on, summary shows DSL will be attached. |
| 171 | +- **DslExcluded** — toggle off. |
| 172 | +- **NoEditorContent** — DSL toggle hidden. |
| 173 | +- **Anonymous** vs **SignedIn** — environment summary differs. |
| 174 | + |
| 175 | +These stories double as the visual documentation of every state. |
| 176 | + |
| 177 | +## Build sequence |
| 178 | + |
| 179 | +1. **`buildIssueUrl` + unit tests** (TDD) — pure, no UI dependency. Lock the |
| 180 | + contract (encoding, DSL on/off, truncation, title derivation, label) first. |
| 181 | +2. **`ReportBugModal`** — wired to `buildIssueUrl`; component tests. |
| 182 | +3. **`ReportBugButton`** — FAB; component test. |
| 183 | +4. **AppRoot wiring** — store/version/analytics/`window.open`; telemetry. |
| 184 | +5. **Storybook bootstrap** — deps, `.storybook/`, scripts; boot once. |
| 185 | +6. **Stories** for both components. |
| 186 | +7. **E2E + screenshot** — one Playwright pass: FAB visible → open modal → fill → |
| 187 | + assert the opened URL contains the description (intercept `window.open`). |
| 188 | + Capture the milestone screenshot. |
| 189 | + |
| 190 | +## Testing & acceptance |
| 191 | + |
| 192 | +- **Unit** `services/bugReport.test.ts`: URL encoding; DSL included vs excluded; |
| 193 | + truncation past budget keeps description + appends marker; title from first |
| 194 | + line; `labels=bug` present. |
| 195 | +- **Component** `ReportBugModal.test.tsx`: DSL toggle defaults on; Open disabled |
| 196 | + when empty; Open calls the opener with a URL containing the description; DSL |
| 197 | + toggle hidden when no editor content; Contact-us link present. |
| 198 | + `ReportBugButton.test.tsx`: renders, opens modal on click. |
| 199 | +- **E2E** (`web/e2e/`): the journey above + screenshot (per the |
| 200 | + ≥1-screenshot-per-milestone convention). |
| 201 | +- **Storybook**: `storybook dev` boots; both stories render against ink tokens. |
| 202 | + |
| 203 | +## Out of scope (YAGNI) |
| 204 | + |
| 205 | +- Server-side report sink / triage dashboard (backend frozen). |
| 206 | +- Screenshot attachment (GitHub prefill is URL-only; no file upload via query). |
| 207 | +- Category dropdowns / separate "steps to reproduce" field (body template carries |
| 208 | + light structure instead). |
| 209 | +- Feature-request path (this entry point is bug-only). |
| 210 | +- `build-storybook` in CI (follow-up). |
| 211 | + |
| 212 | +## Risks & open questions |
| 213 | + |
| 214 | +- **Vite 8 ↔ Storybook addon lag.** Mitigation: minimal addon set; core + |
| 215 | + react-vite is the floor. |
| 216 | +- **`bug` label may not exist** on the repo → param silently ignored. Plan |
| 217 | + resolves: create the label once or drop the param. |
| 218 | +- **URL budget tuning** — 6 KB is a conservative starting point; verify against a |
| 219 | + real long-DSL case during E2E. |
0 commit comments