Skip to content

Commit 1102370

Browse files
committed
Merge remote-tracking branch 'origin/rewrite/web-foundation' into chore/merge-rewrite-web-foundation
# Conflicts: # web/index.html # web/src/components/home/HomeView.tsx
2 parents b490a8d + 2295c1c commit 1102370

57 files changed

Lines changed: 5310 additions & 413 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/superpowers/plans/2026-06-13-editor-landing-page.md

Lines changed: 555 additions & 0 deletions
Large diffs are not rendered by default.

docs/superpowers/plans/2026-06-14-report-a-bug.md

Lines changed: 978 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Editor as Landing Page — Design Spec
2+
3+
Branch: `rewrite/web-foundation`, target `web/` React 19 codebase.
4+
Decision: bare `/` lands in the **editor** (resume last-opened diagram, else sample),
5+
demoting the hub (HomeView) to an explicit `?view=diagrams` address. This restores the
6+
legacy product's landing behavior on the rewrite's architecture.
7+
8+
## Why (data, 2026-06-13)
9+
10+
Mixpanel project Diagramly.Ai (3373228), web-sequence = `FireWeb` / `WA:*` events:
11+
12+
- `WA:create_diagram`: **26 unique users / 54 events in 30 days** (~12–15/week)
13+
- `WA:download_png` 5–9 uniques/week; `WA:copy_png` 5–8/week; `WA:login_google` 1/30d
14+
- `WA:*` instrumentation only began reporting week of 2026-06-01; legacy events
15+
(`pageView`, `userEdit`, `hasCode`) return zero rows — that pipeline is dead.
16+
17+
Implications:
18+
19+
1. **A/B testing is infeasible.** ~15 weekly actives split into two arms needs ~400
20+
users/arm to detect even a large effect at 80% power → ~a year per verdict.
21+
2. **Essentially every visitor is new or occasional**, arriving to draw one diagram.
22+
A dashboard front door is a lobby in front of the tool. Dashboards pay off when
23+
users accumulate libraries; the data says almost nobody has one.
24+
25+
## Confirmed decisions
26+
27+
1. **Landing doc**: last-opened diagram (localStorage last-code), else the sample
28+
diagram for first-timers. Exactly the legacy chain.
29+
2. **Hub address**: `?view=diagrams` search param (consistent with the existing
30+
single-route `?id=`/`?embed` style; no Firebase hosting changes). Hub UI unchanged.
31+
3. **Validation**: pre/post telemetry, not A/B. Ship to 100%, compare 3–4 weeks
32+
before/after.
33+
34+
## Design
35+
36+
### Routing (`web/src/app/router.tsx`, `web/src/app/AppRoot.tsx`)
37+
38+
- `validateSearch` gains `view: s.view as string | undefined`.
39+
- `isHomeMode = search.view === 'diagrams' && !idParam && !shareToken && !isEmbed &&
40+
!runtime.embedCode` — i.e. the hub is now opt-in; `id`/`share-token`/`embed` take
41+
precedence over `view` if both appear.
42+
- `goHome()` navigates to `?view=diagrams` (today it clears params to `/`).
43+
- All existing deep links (`?id=`, `?share-token=`, `?embed`, `?code=`) untouched.
44+
45+
### Boot on bare `/`
46+
47+
No new machinery. `useBootItem` (`web/src/hooks/useBootItem.ts`) already resolves:
48+
share → `?code=``?id=` → last-code (`preserveLastCode`) → `newItem()` (sample).
49+
The hub previously skipped boot on bare `/` (`skip = isHomeMode`); with `isHomeMode`
50+
now opt-in, bare `/` flows through the chain. Returning users resume their last
51+
diagram; first-timers get the sample in the editor.
52+
53+
### Hub stays, demoted
54+
55+
HomeView is unchanged and reachable via the editor's "Your diagrams" breadcrumb
56+
(→ `?view=diagrams`), bookmarkable. Hub-internal navigation (card → `?id=`,
57+
New → editor) already works.
58+
59+
### Telemetry (pre/post validation)
60+
61+
Through the existing `track()` helper:
62+
63+
- `landed_in_editor` `{ bootKind: 'lastcode'|'new'|'item'|'shared'|'code' }` — once
64+
per boot when the editor is the landing surface.
65+
- `hub_opened` `{ source: 'landing-param'|'breadcrumb' }` — measures real demand for
66+
the dashboard.
67+
- `first_edit` — once per session, on the first user-initiated code change.
68+
(Verified: `web/` has no edit tracking today — the legacy `userEdit` event was
69+
never ported — so this is a new event.)
70+
71+
Decision metrics: % of sessions reaching an edit; time-to-first-edit; `hub_opened`
72+
rate. Compare 3–4 weeks pre/post alongside `WA:*` baselines.
73+
74+
### Tests
75+
76+
- AppRoot unit tests asserting `'/' → HomeView` flip to `'/' → editor` and
77+
`'?view=diagrams' → HomeView`.
78+
- E2E helpers re-ground: `gotoHome``/?view=diagrams`; `openEditor``/` (the
79+
staging-gate specs that click through HomeView to reach the editor get simpler).
80+
- `resolveBootItem` is pure and already unit-tested; landing logic needs no new
81+
resolver tests, only the `isHomeMode` gate tests.
82+
83+
## Risks / trade-offs
84+
85+
- First-timers land on an editor with sample code instead of an explanatory empty
86+
state — the original product's bet, restated deliberately. The sample is the
87+
explanation.
88+
- Pre/post comparison has seasonal confounds; accepted at this traffic level.
89+
- Any bookmarks/links to the hub-as-`/` (pre-change staging) silently become editor
90+
landings — acceptable; the hub was only the default for ~2 weeks on staging.
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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.

web/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ extension.zip
1010
test-results/
1111
playwright-report/
1212
.playwright/
13+
storybook-static

web/.storybook/main.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { StorybookConfig } from '@storybook/react-vite';
2+
3+
// react-vite inherits web/vite.config.ts automatically (Tailwind via postcss,
4+
// path resolution, plugins), so no viteFinal override is needed for these
5+
// components. In SB9+, viewport/controls/actions are part of core — only a11y is
6+
// listed here.
7+
const config: StorybookConfig = {
8+
stories: ['../src/**/*.stories.@(ts|tsx)'],
9+
addons: ['@storybook/addon-a11y'],
10+
framework: { name: '@storybook/react-vite', options: {} },
11+
};
12+
13+
export default config;

web/.storybook/preview.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { Preview } from '@storybook/react-vite';
2+
import '../src/styles/globals.css';
3+
4+
// This UI lives on the dark "ink" chrome surface, so wrap every story in it and
5+
// load the global stylesheet so Drafting Table tokens + fonts resolve.
6+
const preview: Preview = {
7+
parameters: { layout: 'fullscreen' },
8+
decorators: [
9+
(Story) => (
10+
<div
11+
className="bg-ink-900 text-ondark-strong font-sans"
12+
style={{ minHeight: '100vh', width: '100%', padding: '2rem' }}
13+
>
14+
<Story />
15+
</div>
16+
),
17+
],
18+
};
19+
20+
export default preview;

web/e2e/helpers/editor.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,21 @@ export function editorRoot(page: Page): Locator {
4040
* interactive (content focusable). Uses a relative URL so the config's baseURL
4141
* drives where we point.
4242
*
43-
* Hub routing (PR #800/#801): "/" lands on the HomeView library when no diagram
44-
* id is in the URL — the editor is one "New" click away. Mirrors the same
45-
* click-through the root production-build spec uses; tolerant of flows that
46-
* land directly in the editor (?id= / embed) where home-view never appears.
43+
* Editor-as-landing (2026-06-13): "/" now lands directly in the editor (resume
44+
* last diagram, else sample). The home-view click-through below is kept as a
45+
* tolerant fallback — it no-ops on the current default but survives a revert and
46+
* still works for any flow that opens the hub first (?view=diagrams). The probe
47+
* timeout is short because home-view is now the rare path: on the editor-landing
48+
* default it will never appear, so we don't want to tax every editor test by
49+
* waiting long for it.
4750
*/
4851
export async function seedAndOpen(page: Page): Promise<void> {
4952
await page.addInitScript((flags: Record<string, string>) => {
5053
for (const [k, v] of Object.entries(flags)) localStorage.setItem(k, v);
5154
}, SEED_FLAGS);
5255
await page.goto('/', { waitUntil: 'networkidle' });
5356
const homeView = page.getByTestId('home-view');
54-
if (await homeView.isVisible({ timeout: 3000 }).catch(() => false)) {
57+
if (await homeView.isVisible({ timeout: 1000 }).catch(() => false)) {
5558
const emptyCta = page.getByTestId('home-empty-new');
5659
if (await emptyCta.isVisible({ timeout: 1000 }).catch(() => false)) {
5760
await emptyCta.click();

0 commit comments

Comments
 (0)