Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b5212a3
fix(web/hub): caret half of split New button was h-8 vs Button md h-9…
MrCoder Jun 11, 2026
ba6cd1a
docs(spec): editor-as-landing-page design — bare / lands in editor (r…
MrCoder Jun 13, 2026
c9fc9fa
docs(plan): editor-as-landing-page — 6 TDD tasks (router ?view, isHom…
MrCoder Jun 13, 2026
9d29785
feat(web/router): add ?view search param (hub address for editor-land…
MrCoder Jun 13, 2026
a21030c
feat(web/hub)!: editor is the landing page — hub moves to ?view=diagr…
MrCoder Jun 13, 2026
47ab41f
feat(web/telemetry): emit landed_in_editor{bootKind} via useBootItem …
MrCoder Jun 13, 2026
aad853c
feat(web/telemetry): emit hub_opened{source} and first_edit (once/mou…
MrCoder Jun 13, 2026
a6ab3d6
fix(web/telemetry): breadcrumb→hub emitted both hub_opened{breadcrumb…
MrCoder Jun 13, 2026
521ba50
test(web/e2e): re-ground seedAndOpen for editor-as-landing — / lands …
MrCoder Jun 13, 2026
36f6714
fix(web/telemetry): re-arm hub_opened{landing-param} on leaving the h…
MrCoder Jun 13, 2026
2267953
fix(web/hub): template quick-picks create directly instead of reopeni…
MrCoder Jun 13, 2026
1b02156
fix(web/brand): use the official ZenUML logo in hub + editor headers …
MrCoder Jun 13, 2026
73ab92c
fix(web/theme): convert all modals + renderer header to the dark ink …
MrCoder Jun 13, 2026
a0b68d3
test(web): P5 regression — login modal closes once authenticated
MrCoder Jun 13, 2026
edae7b6
feat(web): integrate @zenuml/core LSP into CM6 DSL editor — hover, do…
MrCoder Jun 13, 2026
87ae78d
fix(web/editor): guard the LSP worker behind a Worker-availability ch…
MrCoder Jun 13, 2026
0286893
feat(web/header): surface a Pricing link in the header for signed-out…
MrCoder Jun 14, 2026
5226132
feat(web/preview): render the native SVG (fit-to-width) instead of HT…
MrCoder Jun 14, 2026
6f9cd27
fix(web/pages): reconcile the top-level js/css mirror to currentPageI…
MrCoder Jun 14, 2026
6f62604
fix(web/preview): acknowledged render delivery so the preview re-rend…
MrCoder Jun 14, 2026
5751479
fix(web/pricing): anonymous Upgrade opens sign-in then resumes Paddle…
MrCoder Jun 14, 2026
8ea9591
docs(web): design spec for prominent Report-a-bug entry point + Story…
MrCoder Jun 14, 2026
99c75be
docs(web): implementation plan for Report-a-bug entry point + Storybo…
MrCoder Jun 14, 2026
f9c64f8
feat(web/feedback): pure buildIssueUrl helper for prefilled GitHub bu…
MrCoder Jun 14, 2026
966323f
feat(web/feedback): ReportBugModal — description + public-DSL toggle …
MrCoder Jun 14, 2026
b9ac835
feat(web/feedback): ReportBugButton fixed FAB owning the bug-report m…
MrCoder Jun 14, 2026
6a0fa26
feat(web/feedback): render Report-a-bug FAB app-wide (hub + editor) w…
MrCoder Jun 14, 2026
12076a0
chore(web): bootstrap Storybook (react-vite) on the dark ink surface
MrCoder Jun 14, 2026
34044dd
docs(web/feedback): Storybook stories for ReportBugButton + ReportBug…
MrCoder Jun 14, 2026
9c32296
test(web/feedback): e2e — FAB opens modal and builds prefilled GitHub…
MrCoder Jun 14, 2026
b3b6e4a
fix(web): pin Storybook deps in standalone web/pnpm-lock.yaml (--igno…
MrCoder Jun 14, 2026
a42b2be
fix(web/feedback): lift Report-a-bug FAB above the console status ban…
MrCoder Jun 14, 2026
2295c1c
fix(web): add canonical ZenUML favicon (tab icon was empty — no icon …
MrCoder Jun 14, 2026
1102370
Merge remote-tracking branch 'origin/rewrite/web-foundation' into cho…
MrCoder Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
555 changes: 555 additions & 0 deletions docs/superpowers/plans/2026-06-13-editor-landing-page.md

Large diffs are not rendered by default.

978 changes: 978 additions & 0 deletions docs/superpowers/plans/2026-06-14-report-a-bug.md

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions docs/superpowers/specs/2026-06-13-editor-landing-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Editor as Landing Page — Design Spec

Branch: `rewrite/web-foundation`, target `web/` React 19 codebase.
Decision: bare `/` lands in the **editor** (resume last-opened diagram, else sample),
demoting the hub (HomeView) to an explicit `?view=diagrams` address. This restores the
legacy product's landing behavior on the rewrite's architecture.

## Why (data, 2026-06-13)

Mixpanel project Diagramly.Ai (3373228), web-sequence = `FireWeb` / `WA:*` events:

- `WA:create_diagram`: **26 unique users / 54 events in 30 days** (~12–15/week)
- `WA:download_png` 5–9 uniques/week; `WA:copy_png` 5–8/week; `WA:login_google` 1/30d
- `WA:*` instrumentation only began reporting week of 2026-06-01; legacy events
(`pageView`, `userEdit`, `hasCode`) return zero rows — that pipeline is dead.

Implications:

1. **A/B testing is infeasible.** ~15 weekly actives split into two arms needs ~400
users/arm to detect even a large effect at 80% power → ~a year per verdict.
2. **Essentially every visitor is new or occasional**, arriving to draw one diagram.
A dashboard front door is a lobby in front of the tool. Dashboards pay off when
users accumulate libraries; the data says almost nobody has one.

## Confirmed decisions

1. **Landing doc**: last-opened diagram (localStorage last-code), else the sample
diagram for first-timers. Exactly the legacy chain.
2. **Hub address**: `?view=diagrams` search param (consistent with the existing
single-route `?id=`/`?embed` style; no Firebase hosting changes). Hub UI unchanged.
3. **Validation**: pre/post telemetry, not A/B. Ship to 100%, compare 3–4 weeks
before/after.

## Design

### Routing (`web/src/app/router.tsx`, `web/src/app/AppRoot.tsx`)

- `validateSearch` gains `view: s.view as string | undefined`.
- `isHomeMode = search.view === 'diagrams' && !idParam && !shareToken && !isEmbed &&
!runtime.embedCode` — i.e. the hub is now opt-in; `id`/`share-token`/`embed` take
precedence over `view` if both appear.
- `goHome()` navigates to `?view=diagrams` (today it clears params to `/`).
- All existing deep links (`?id=`, `?share-token=`, `?embed`, `?code=`) untouched.

### Boot on bare `/`

No new machinery. `useBootItem` (`web/src/hooks/useBootItem.ts`) already resolves:
share → `?code=` → `?id=` → last-code (`preserveLastCode`) → `newItem()` (sample).
The hub previously skipped boot on bare `/` (`skip = isHomeMode`); with `isHomeMode`
now opt-in, bare `/` flows through the chain. Returning users resume their last
diagram; first-timers get the sample in the editor.

### Hub stays, demoted

HomeView is unchanged and reachable via the editor's "Your diagrams" breadcrumb
(→ `?view=diagrams`), bookmarkable. Hub-internal navigation (card → `?id=`,
New → editor) already works.

### Telemetry (pre/post validation)

Through the existing `track()` helper:

- `landed_in_editor` `{ bootKind: 'lastcode'|'new'|'item'|'shared'|'code' }` — once
per boot when the editor is the landing surface.
- `hub_opened` `{ source: 'landing-param'|'breadcrumb' }` — measures real demand for
the dashboard.
- `first_edit` — once per session, on the first user-initiated code change.
(Verified: `web/` has no edit tracking today — the legacy `userEdit` event was
never ported — so this is a new event.)

Decision metrics: % of sessions reaching an edit; time-to-first-edit; `hub_opened`
rate. Compare 3–4 weeks pre/post alongside `WA:*` baselines.

### Tests

- AppRoot unit tests asserting `'/' → HomeView` flip to `'/' → editor` and
`'?view=diagrams' → HomeView`.
- E2E helpers re-ground: `gotoHome` → `/?view=diagrams`; `openEditor` → `/` (the
staging-gate specs that click through HomeView to reach the editor get simpler).
- `resolveBootItem` is pure and already unit-tested; landing logic needs no new
resolver tests, only the `isHomeMode` gate tests.

## Risks / trade-offs

- First-timers land on an editor with sample code instead of an explanatory empty
state — the original product's bet, restated deliberately. The sample is the
explanation.
- Pre/post comparison has seasonal confounds; accepted at this traffic level.
- Any bookmarks/links to the hub-as-`/` (pre-change staging) silently become editor
landings — acceptable; the hub was only the default for ~2 weeks on staging.
219 changes: 219 additions & 0 deletions docs/superpowers/specs/2026-06-14-report-a-bug-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Report a Bug — prefilled GitHub issue (+ Storybook bootstrap)

**Date:** 2026-06-14
**Branch:** `rewrite/web-foundation`
**Status:** Design — pending implementation plan

## Summary

The web/ rewrite is a full redesign. New UI means new rough edges, and the only
current path to report anything is buried three clicks deep (App menu → Help →
"Contact us" / GitHub link). This adds a **prominent, persistent "Report a bug"
entry point** that produces an **actionable, prefilled GitHub issue** — zero new
backend, structured context auto-attached.

It also **bootstraps Storybook** in the rewrite (none exists today), with the
Report-a-bug components as its first inhabitants, establishing the pattern for the
rest of the rewrite's UI primitives.

## Goal & success criteria

- A user who hits a bug can report it in **≤2 clicks** from any screen.
- The resulting GitHub issue contains enough context (description + app version +
browser/OS + view + optional DSL) to **reproduce without a back-and-forth**.
- No proprietary diagram leaks **without informed consent** (public-issue warning).
- Storybook runs in `web/` (`pnpm storybook`) showing the new components' states
against the real Drafting Table tokens + dark ink surface.

## Context & constraints

- **Stack:** React 19 + Vite 8 + Tailwind 3 + Vitest/Testing Library. UI built
from `web/src/ui/*` primitives (Drafting Table design system: semantic tokens,
dark "ink" surfaces for chrome/modals, "paper" for the canvas).
- **Backend is frozen** — no new Firebase cloud functions. This rules out a
server-side report sink and is *why* we use a client-only GitHub-prefill flow.
- **Audience is developers**; the repo is public and already linked in Help
(`github.com/ZenUml/web-sequence`), so GitHub issues are a natural, zero-infra
destination.
- **Global stylesheet:** `web/src/styles/globals.css` (imported in `main.tsx`).
- **No Storybook today:** no `.storybook/`, no `*.stories.*`, no SB deps. Vite 8
needs **Storybook 9.x** (SB 8 topped out at Vite 6; Vite 8 / Rolldown support
landed and shipped — storybook#33789, closed "Done", Feb 2026).

## Decisions (from brainstorming)

| Question | Decision |
|---|---|
| Purpose | **Bug reports for the redesign rollout** — optimized for actionable, context-rich reports. |
| Destination | **Prefilled GitHub issue** (client-only; no backend). |
| Include diagram DSL? | **Yes, via an opt-in checkbox defaulted ON**, clearly labeled "will be public." |
| Placement | **Floating button, bottom-right** (keeps the header's three-verb group pure). |
| Storybook | **Bootstrap Storybook 9 in this feature**; ship the first stories here. |

Reversible calls made by the author (not separately confirmed; cheap to change):
- **FAB is app-wide** (editor + hub), so breakage on any screen is reportable.
DSL capture is only offered when there is editor content.
- **Fallback links to the existing Contact us page** (`zenuml.com/docs/about/contact-us`),
not a `mailto:` — there is no verified support email to hardcode.

## Architecture & components

All new code under `web/src/`.

| File | Responsibility |
|---|---|
| `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. |
| `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. |
| `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. |
| `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')`. |

**Boundaries:** the button knows nothing about GitHub; the modal collects intent;
`buildIssueUrl` is a pure transform; AppRoot is the only place that touches the
store/window. Each is understandable and testable in isolation.

## `buildIssueUrl` contract

Input:

```ts
interface BuildIssueInput {
description: string; // required, non-empty (UI gates this)
includeDsl: boolean;
dsl?: string; // current editor source; omitted when no content
appVersion: string; // e.g. "2026.6.7"
userAgent: string; // navigator.userAgent
view: 'editor' | 'hub' | string;
signedIn: boolean;
}
```

Output URL:
`https://github.com/ZenUml/web-sequence/issues/new?title=<t>&body=<b>&labels=bug`

- **Title** = first non-empty line of `description`, trimmed to ~80 chars.
- **Body** (markdown):

```markdown
**Describe the bug**
<description>

**Environment**
- App version: 2026.6.7
- Browser: <userAgent>
- View: editor · Signed in: no

<details><summary>Diagram DSL</summary>

```
<dsl — only when includeDsl && dsl>
```
</details>
```

- **`labels=bug`** — best-effort. GitHub applies it only if the label exists on
the repo and is ignored otherwise (never errors). (Plan: confirm a `bug` label
exists; if not, either create it once or drop the param.)

### Edge cases

- **URL length (414 URI Too Long).** GitHub rejects oversized prefills. The
builder caps the encoded URL to a safe budget (~6 KB). DSL is the elastic part:
if it overflows, it is truncated with a `… (truncated — please paste the rest)`
marker so the report still goes through. Description + environment are never
dropped.
- **Empty description.** UI disables Open until there is text; `buildIssueUrl` is
not called.
- **No DSL available.** When `dsl` is empty/whitespace, the modal hides the DSL
toggle entirely and the body omits the `<details>` block.

## Visibility & telemetry

- FAB rendered app-wide; styled quiet (low-emphasis until hover) so it doesn't
fight the diagram. Not dismissable — prominence is the point.
- Two Mixpanel events via the existing `useAnalytics`:
- `bug_report_opened` — modal opened.
- `bug_report_submitted` — `{ included_dsl: boolean, view: string }`.
This tells us whether the redesign-QA channel is actually used.

## Storybook bootstrap

First Storybook in the rewrite. Keep it minimal and token-faithful.

- **Deps (devDependencies):** `storybook`, `@storybook/react-vite` (latest 9.x),
`@storybook/addon-a11y` (cheap, useful for a contrast-sensitive design system).
Pin exact versions at install; **boot `storybook dev` once** to confirm the
Rolldown/Vite 8 builder starts before claiming done.
- **`web/.storybook/main.ts`:** framework `@storybook/react-vite`; stories glob
`../src/**/*.stories.@(ts|tsx)`; addons `[a11y]`. Inherits the project Vite
config (Tailwind via the existing `postcss.config.js`, so utilities + tokens
resolve with no extra wiring).
- **`web/.storybook/preview.ts`:** `import '../src/styles/globals.css'` so tokens
load; a global decorator that wraps every story in the **dark ink surface**
(matching where this UI lives) with the app base font; `parameters.backgrounds`
offering ink (default) + paper.
- **Scripts (`web/package.json`):**
- `"storybook": "storybook dev -p 6006"`
- `"build-storybook": "storybook build"`
- **Risk note:** Vite 8 is new; if an addon lags, drop to core + react-vite only.
CI wiring for `build-storybook` is **out of scope** here (left for a follow-up)
— this feature only stands up local Storybook + the first stories.

### Stories to ship (colocated `*.stories.tsx`, matching the test-colocation pattern)

`components/feedback/ReportBugButton.stories.tsx`:
- **Default** — desktop pill.
- **Compact** — icon-only (mobile viewport).

`components/feedback/ReportBugModal.stories.tsx` (rendered `open`):
- **Empty** — Open disabled.
- **Filled** — description present, Open enabled.
- **DslIncluded** — toggle on, summary shows DSL will be attached.
- **DslExcluded** — toggle off.
- **NoEditorContent** — DSL toggle hidden.
- **Anonymous** vs **SignedIn** — environment summary differs.

These stories double as the visual documentation of every state.

## Build sequence

1. **`buildIssueUrl` + unit tests** (TDD) — pure, no UI dependency. Lock the
contract (encoding, DSL on/off, truncation, title derivation, label) first.
2. **`ReportBugModal`** — wired to `buildIssueUrl`; component tests.
3. **`ReportBugButton`** — FAB; component test.
4. **AppRoot wiring** — store/version/analytics/`window.open`; telemetry.
5. **Storybook bootstrap** — deps, `.storybook/`, scripts; boot once.
6. **Stories** for both components.
7. **E2E + screenshot** — one Playwright pass: FAB visible → open modal → fill →
assert the opened URL contains the description (intercept `window.open`).
Capture the milestone screenshot.

## Testing & acceptance

- **Unit** `services/bugReport.test.ts`: URL encoding; DSL included vs excluded;
truncation past budget keeps description + appends marker; title from first
line; `labels=bug` present.
- **Component** `ReportBugModal.test.tsx`: DSL toggle defaults on; Open disabled
when empty; Open calls the opener with a URL containing the description; DSL
toggle hidden when no editor content; Contact-us link present.
`ReportBugButton.test.tsx`: renders, opens modal on click.
- **E2E** (`web/e2e/`): the journey above + screenshot (per the
≥1-screenshot-per-milestone convention).
- **Storybook**: `storybook dev` boots; both stories render against ink tokens.

## Out of scope (YAGNI)

- Server-side report sink / triage dashboard (backend frozen).
- Screenshot attachment (GitHub prefill is URL-only; no file upload via query).
- Category dropdowns / separate "steps to reproduce" field (body template carries
light structure instead).
- Feature-request path (this entry point is bug-only).
- `build-storybook` in CI (follow-up).

## Risks & open questions

- **Vite 8 ↔ Storybook addon lag.** Mitigation: minimal addon set; core +
react-vite is the floor.
- **`bug` label may not exist** on the repo → param silently ignored. Plan
resolves: create the label once or drop the param.
- **URL budget tuning** — 6 KB is a conservative starting point; verify against a
real long-DSL case during E2E.
1 change: 1 addition & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ extension.zip
test-results/
playwright-report/
.playwright/
storybook-static
13 changes: 13 additions & 0 deletions web/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { StorybookConfig } from '@storybook/react-vite';

// react-vite inherits web/vite.config.ts automatically (Tailwind via postcss,
// path resolution, plugins), so no viteFinal override is needed for these
// components. In SB9+, viewport/controls/actions are part of core — only a11y is
// listed here.
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx)'],
addons: ['@storybook/addon-a11y'],
framework: { name: '@storybook/react-vite', options: {} },
};

export default config;
20 changes: 20 additions & 0 deletions web/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Preview } from '@storybook/react-vite';
import '../src/styles/globals.css';

// This UI lives on the dark "ink" chrome surface, so wrap every story in it and
// load the global stylesheet so Drafting Table tokens + fonts resolve.
const preview: Preview = {
parameters: { layout: 'fullscreen' },
decorators: [
(Story) => (
<div
className="bg-ink-900 text-ondark-strong font-sans"
style={{ minHeight: '100vh', width: '100%', padding: '2rem' }}
>
<Story />
</div>
),
],
};

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