Redesign the alerts editor (CanvasWorkspace) into the "Studio" layout #100

Closed
opened 2026-07-03 15:54:47 -05:00 by Gitchegumi · 0 comments
Gitchegumi commented 2026-07-03 15:54:47 -05:00 (Migrated from github.com)

Type: enhancement / UX redesign
Area: apps/web — /dashboard/[channelSlug]/alerts
Design reference: option 1b in the "Alerts Editor Redesign" design doc (Studio layout). Screenshots of the mock are the source of truth for visual details; this issue is the source of truth for behavior.


1. Motivation

The current alerts editor (apps/web/src/components/CanvasWorkspace.tsx, rendered by
apps/web/src/app/dashboard/[channelSlug]/alerts/page.tsx) has these problems:

  1. No clear primary workflow. Three co-equal panels (canvas list / preview + settings / "on-screen components") give no obvious starting point.
  2. The right panel is overloaded. It mixes layer listing, per-element property editing, audio settings, alert bindings, and a preview button in one scrolling column of expanding cards.
  3. Selection and expansion are conflated. Clicking a component card header both selects the element and toggles expansion; the chevron cannot close a card without also re-selecting it.
  4. Numeric inputs cause canvas jitter. X/Y/W/H inputs call patchElement on every onChange, so each keystroke re-renders and re-positions the element mid-edit.

2. Target layout (Studio)

A single full-viewport editor in five fixed regions:

┌──────────────────────────────────────────────────────────────┐
│ TOP BAR (52px)                                               │
├─────────┬──────────────────────────────────────┬─────────────┤
│ LEFT    │ CANVAS AREA                          │ INSPECTOR   │
│ PANEL   │ (flexible, checkerboard, centered    │ (264px)     │
│ (248px) │  stage, floating zoom toolbar)       │             │
├─────────┴──────────────────────────────────────┴─────────────┤
│ STATUS BAR (30px)                                            │
└──────────────────────────────────────────────────────────────┘

Keep the existing dark brand and all tokens from apps/web/src/app/globals.css
(--bg #18191d, --panel #23252b, --line rgba(204,219,220,.24), --accent #afe0ce tiffany,
--primary #4166f5 ultramarine, --attention #fca311 golden orange, Inter). Panel chrome color
in the mock: #1c1e23; canvas backdrop #131418 with a checkerboard
(repeating-conic-gradient(rgba(204,219,220,.05) 0% 25%, transparent 0% 50%), 26px tiles).

2.1 Top bar (52px, #1c1e23, bottom hairline)

Left → right:

  • GitchAlerts logo (30px, support-files/GitchAlerts-image-logo.svg).
  • Canvas switcher: pill showing current canvas name + dimensions + ▾; dropdown lists all overlay profiles, plus "New canvas" and "Duplicate". Replaces the current left "Canvases" panel as the primary switcher.
  • Live badge: pill "Live in OBS" (accent tint) when isActive; hidden/inactive style otherwise.
  • Spacer.
  • Save state text ("Saved just now" / "Saving…") — reflects the existing startTransition PATCH lifecycle.
  • Copy OBS URL (secondary button) — existing copyUrl().
  • ▶ Test alert (primary, golden-orange #fca311 with dark text) — existing testAlert().

2.2 Left panel (248px)

Three tabs at top: Layers | Alerts | Assets (segmented control; Alerts tab shows a bound-count badge in orange).

Layers tab (default):

  • Add-element row: four equal buttons — Text, Image, Shape, Audio — calling the existing addElement(type) (audio maps to the canvas audio settings, see 2.4).
  • Section label LAYERS · n.
  • One row per element, sorted by zIndex descending (top layer first): 18px type glyph, name, right-aligned visibility dot. Selected row = ultramarine tint background + outline.
  • Row behavior (fixes conflation): single click selects only. Hover reveals Hide / Lock / ⋯ (context menu: duplicate, delete, rename). Double-click renames inline. Drag to reorder = rewrites zIndex values (replaces the numeric "Layer" input).
  • Bottom (pinned): "CANVASES" label + pill list of profiles with + — secondary access to canvas switching.

Alerts tab: the current "Alert bindings" section, grouped by platform (platformLabel), each row a toggle + name + eventKey + inline Test link. Enabling a binding also enables the alert config globally if disabled (existing toggleAlert + enableAlertConfig behavior).

Assets tab: browsable list of WorkspaceAssets (image/video/audio) for drag-onto-canvas or click-to-assign to the selected image element.

2.3 Canvas area

  • Stage centered, aspect-ratio: settings.width / settings.height, dark translucent fill, 1px outline, large shadow. Scale-to-fit with zoom override.
  • Elements render exactly as today (position %, opacity, rotation, text styles). Selected element: 1.5px ultramarine outline, 8 resize handles (square, 9px), a name badge above the top-left corner, and a W × H dimension badge below the element (monospace).
  • Center snap guides stay (orange line + glow) — reuse snapToCanvasCenter.
  • Click empty stage = deselect.
  • Floating toolbar (bottom-center, pill): zoom − / % / +, divider, Fit, Snap toggle (accent when on), Dark bg toggle (existing background setting).
  • Canvas width/height/name editing moves into the canvas-switcher dropdown or a small "Canvas settings" popover from the top bar — no permanent settings strip under the stage.

2.4 Inspector (right, 264px) — replaces the "on-screen components" panel

Shows properties of the current selection only. Empty state (nothing selected): canvas-level settings (name, size, background, audio: stored sound + volume) plus a hint "Select an element to edit it".

Selected element, four hairline-separated sections:

  1. Header: element name (click to rename) + Hide / Lock actions.
  2. Position & size: 2-col grid of labeled inputs — X, Y, W, H, rotation, opacity — followed by an alignment row (left / center / right / middle) using existing centerElement.
  3. Content (text-ish elements): template textarea rendering {{tokens}} as highlighted chips (orange tint, monospace); hint "Type / to insert event data" opening a token autocomplete popover fed by EVENT_VARIABLES (replaces the always-visible token button list). Image elements: asset picker (existing "Stored asset" select) + media preview.
  4. Text (text-ish only): font family, size (px), text color, stroke color, stroke width, bold.
  5. Animation: In / Out selects + duration.

Numeric-input contract (fixes jitter) — applies to every numeric field in the editor:

  • Draft state pattern (generalize the existing canvasSizeDraft approach): onChange updates a local string draft only; commit (validate → clamp → patchElement/patchCanvas) happens on blur or Enter; Escape reverts to the committed value.
  • Arrow-Up/Down nudge ±1 (Shift = ±10) and commit immediately.
  • Canvas must never re-render an element from a half-typed value.

2.5 Status bar (30px, #16171b)

Left: 1920 × 1080 · 3 layers + audio · Bound to 4 alert types (bound count in accent). Right: contextual hints.

3. Non-goals

  • No API/schema changes: all existing endpoints (overlay-profiles, alert-configs, assets, test-alert) and canvas-schema.ts serialization stay as-is.
  • OverlayLayoutEditor (the /overlay/[layoutId]/edit page) and AlertCatalogManager are out of scope.
  • "Recent alerts" feed can remain below the editor or move behind a top-bar popover — implementer's choice, not a blocker.
  • No light theme.

4. Implementation plan (parallelizable workstreams)

Split CanvasWorkspace.tsx (1320 lines) into components under apps/web/src/components/canvas-editor/, with shared state lifted into a useCanvasEditor hook (or context) exposing: canvases, selected, selectedElement, selectElement(id), patchCanvas, patchElement (commit-only), addElement, deleteElement, reorderElement(id, newIndex), toggleAlert, testAlert, copyUrl, zoom state, snap state.

Keep styling in globals.css with a canvas-editor-* class prefix (or a co-located CSS module — match repo conventions).

Workstream A — Editor shell + top bar + status bar.
CanvasEditor.tsx (grid: 52px / 1fr / 30px rows; 248px / 1fr / 264px columns), EditorTopBar.tsx (canvas switcher dropdown w/ create, duplicate, rename, width/height, delete), EditorStatusBar.tsx. Wires existing createCanvas, deleteCanvas, patchCanvas, copyUrl, testAlert.

Workstream B — Left panel.
EditorLeftPanel.tsx with LayersTab.tsx (rows, hover actions, inline rename, drag-reorder → zIndex rewrite), AlertsTab.tsx (port alert-bindings section), AssetsTab.tsx. Depends only on the shared hook's interface.

Workstream C — Canvas stage.
EditorStage.tsx: port startElementPointer, transformElement, snapToCanvasCenter, resize handles; add zoom/fit state, floating toolbar, selection badges (name + dimensions). Preserve pointer-capture drag behavior and the moved guard that avoids PATCHes on plain clicks.

Workstream D — Inspector.
EditorInspector.tsx + NumericField.tsx (the commit-on-blur/Enter/arrow-nudge contract — single reusable component, unit-tested), sections per 2.4, token-chip textarea + / autocomplete from EVENT_VARIABLES.

Workstream E — Integration + cleanup.
Replace CanvasWorkspace usage in alerts/page.tsx, delete dead CSS (component-card-*, canvas-settings-grid, etc.), keep applyAlertAssignment export + its tests passing, add tests for NumericField and zIndex reorder.

Suggested order: A first (defines the shell + hook interface), then B/C/D in parallel, then E.

5. Acceptance criteria

  • /dashboard/[channelSlug]/alerts renders the five-region Studio layout; no third "components" column.
  • Clicking a layer row selects it (canvas outline + inspector update) and does nothing else; hover actions and ⋯ menu work without changing selection.
  • Typing in X/Y/W/H/rotation/opacity/size fields never moves the canvas element until Enter/blur; Escape reverts; arrow keys nudge ±1/±10.
  • Drag-reorder in the layers list persists new zIndex order; the numeric Layer input is gone.
  • Alert bindings live in the left panel Alerts tab with unchanged enable/test behavior.
  • Canvas-level settings (name, size, background, audio, active) reachable via top bar / empty-selection inspector; nothing lost from the old UI.
  • Copy OBS URL and Test alert are one click from anywhere in the editor.
  • Zoom −/+/Fit and Snap toggle work; center snap guides render while dragging.
  • All existing tests pass; serializeCanvasSettings payloads are byte-compatible with current output.
  • Keyboard: Delete removes selected element (unless last), Esc deselects, arrow keys nudge selected element on canvas.
**Type:** enhancement / UX redesign **Area:** `apps/web` — `/dashboard/[channelSlug]/alerts` **Design reference:** option **1b** in the "Alerts Editor Redesign" design doc (Studio layout). Screenshots of the mock are the source of truth for visual details; this issue is the source of truth for behavior. --- ## 1. Motivation The current alerts editor (`apps/web/src/components/CanvasWorkspace.tsx`, rendered by `apps/web/src/app/dashboard/[channelSlug]/alerts/page.tsx`) has these problems: 1. **No clear primary workflow.** Three co-equal panels (canvas list / preview + settings / "on-screen components") give no obvious starting point. 2. **The right panel is overloaded.** It mixes layer listing, per-element property editing, audio settings, alert bindings, and a preview button in one scrolling column of expanding cards. 3. **Selection and expansion are conflated.** Clicking a component card header both selects the element and toggles expansion; the chevron cannot close a card without also re-selecting it. 4. **Numeric inputs cause canvas jitter.** X/Y/W/H inputs call `patchElement` on every `onChange`, so each keystroke re-renders and re-positions the element mid-edit. ## 2. Target layout (Studio) A single full-viewport editor in five fixed regions: ``` ┌──────────────────────────────────────────────────────────────┐ │ TOP BAR (52px) │ ├─────────┬──────────────────────────────────────┬─────────────┤ │ LEFT │ CANVAS AREA │ INSPECTOR │ │ PANEL │ (flexible, checkerboard, centered │ (264px) │ │ (248px) │ stage, floating zoom toolbar) │ │ ├─────────┴──────────────────────────────────────┴─────────────┤ │ STATUS BAR (30px) │ └──────────────────────────────────────────────────────────────┘ ``` Keep the existing dark brand and all tokens from `apps/web/src/app/globals.css` (`--bg #18191d`, `--panel #23252b`, `--line rgba(204,219,220,.24)`, `--accent #afe0ce` tiffany, `--primary #4166f5` ultramarine, `--attention #fca311` golden orange, Inter). Panel chrome color in the mock: `#1c1e23`; canvas backdrop `#131418` with a checkerboard (`repeating-conic-gradient(rgba(204,219,220,.05) 0% 25%, transparent 0% 50%)`, 26px tiles). ### 2.1 Top bar (52px, `#1c1e23`, bottom hairline) Left → right: - GitchAlerts logo (30px, `support-files/GitchAlerts-image-logo.svg`). - **Canvas switcher**: pill showing current canvas name + dimensions + `▾`; dropdown lists all overlay profiles, plus "New canvas" and "Duplicate". Replaces the current left "Canvases" panel as the primary switcher. - **Live badge**: pill "Live in OBS" (accent tint) when `isActive`; hidden/inactive style otherwise. - Spacer. - Save state text ("Saved just now" / "Saving…") — reflects the existing `startTransition` PATCH lifecycle. - **Copy OBS URL** (secondary button) — existing `copyUrl()`. - **▶ Test alert** (primary, golden-orange `#fca311` with dark text) — existing `testAlert()`. ### 2.2 Left panel (248px) Three tabs at top: **Layers | Alerts | Assets** (segmented control; Alerts tab shows a bound-count badge in orange). **Layers tab (default):** - Add-element row: four equal buttons — Text, Image, Shape, Audio — calling the existing `addElement(type)` (audio maps to the canvas audio settings, see 2.4). - Section label `LAYERS · n`. - One row per element, sorted by `zIndex` descending (top layer first): 18px type glyph, name, right-aligned visibility dot. Selected row = ultramarine tint background + outline. - **Row behavior (fixes conflation):** single click selects only. Hover reveals Hide / Lock / `⋯` (context menu: duplicate, delete, rename). Double-click renames inline. Drag to reorder = rewrites `zIndex` values (replaces the numeric "Layer" input). - Bottom (pinned): "CANVASES" label + pill list of profiles with `+` — secondary access to canvas switching. **Alerts tab:** the current "Alert bindings" section, grouped by platform (`platformLabel`), each row a toggle + name + eventKey + inline Test link. Enabling a binding also enables the alert config globally if disabled (existing `toggleAlert` + `enableAlertConfig` behavior). **Assets tab:** browsable list of `WorkspaceAsset`s (image/video/audio) for drag-onto-canvas or click-to-assign to the selected image element. ### 2.3 Canvas area - Stage centered, `aspect-ratio: settings.width / settings.height`, dark translucent fill, 1px outline, large shadow. Scale-to-fit with zoom override. - Elements render exactly as today (position %, opacity, rotation, text styles). Selected element: 1.5px ultramarine outline, 8 resize handles (square, 9px), a name badge above the top-left corner, and a `W × H` dimension badge below the element (monospace). - Center snap guides stay (orange line + glow) — reuse `snapToCanvasCenter`. - Click empty stage = deselect. - **Floating toolbar** (bottom-center, pill): zoom − / % / +, divider, **Fit**, **Snap** toggle (accent when on), **Dark bg** toggle (existing `background` setting). - Canvas width/height/name editing moves into the canvas-switcher dropdown or a small "Canvas settings" popover from the top bar — no permanent settings strip under the stage. ### 2.4 Inspector (right, 264px) — replaces the "on-screen components" panel Shows properties of the **current selection only**. Empty state (nothing selected): canvas-level settings (name, size, background, audio: stored sound + volume) plus a hint "Select an element to edit it". Selected element, four hairline-separated sections: 1. **Header:** element name (click to rename) + Hide / Lock actions. 2. **Position & size:** 2-col grid of labeled inputs — X, Y, W, H, rotation, opacity — followed by an alignment row (left / center / right / middle) using existing `centerElement`. 3. **Content** (text-ish elements): template textarea rendering `{{tokens}}` as highlighted chips (orange tint, monospace); hint "Type `/` to insert event data" opening a token autocomplete popover fed by `EVENT_VARIABLES` (replaces the always-visible token button list). Image elements: asset picker (existing "Stored asset" select) + media preview. 4. **Text** (text-ish only): font family, size (px), text color, stroke color, stroke width, bold. 5. **Animation:** In / Out selects + duration. **Numeric-input contract (fixes jitter) — applies to every numeric field in the editor:** - Draft state pattern (generalize the existing `canvasSizeDraft` approach): `onChange` updates a local string draft only; commit (validate → clamp → `patchElement`/`patchCanvas`) happens on **blur or Enter**; Escape reverts to the committed value. - Arrow-Up/Down nudge ±1 (Shift = ±10) and commit immediately. - Canvas must never re-render an element from a half-typed value. ### 2.5 Status bar (30px, `#16171b`) Left: `1920 × 1080 · 3 layers + audio · Bound to 4 alert types` (bound count in accent). Right: contextual hints. ## 3. Non-goals - No API/schema changes: all existing endpoints (`overlay-profiles`, `alert-configs`, `assets`, `test-alert`) and `canvas-schema.ts` serialization stay as-is. - `OverlayLayoutEditor` (the `/overlay/[layoutId]/edit` page) and `AlertCatalogManager` are out of scope. - "Recent alerts" feed can remain below the editor or move behind a top-bar popover — implementer's choice, not a blocker. - No light theme. ## 4. Implementation plan (parallelizable workstreams) Split `CanvasWorkspace.tsx` (1320 lines) into components under `apps/web/src/components/canvas-editor/`, with shared state lifted into a `useCanvasEditor` hook (or context) exposing: `canvases, selected, selectedElement, selectElement(id), patchCanvas, patchElement (commit-only), addElement, deleteElement, reorderElement(id, newIndex), toggleAlert, testAlert, copyUrl, zoom state, snap state`. Keep styling in `globals.css` with a `canvas-editor-*` class prefix (or a co-located CSS module — match repo conventions). **Workstream A — Editor shell + top bar + status bar.** `CanvasEditor.tsx` (grid: 52px / 1fr / 30px rows; 248px / 1fr / 264px columns), `EditorTopBar.tsx` (canvas switcher dropdown w/ create, duplicate, rename, width/height, delete), `EditorStatusBar.tsx`. Wires existing `createCanvas`, `deleteCanvas`, `patchCanvas`, `copyUrl`, `testAlert`. **Workstream B — Left panel.** `EditorLeftPanel.tsx` with `LayersTab.tsx` (rows, hover actions, inline rename, drag-reorder → zIndex rewrite), `AlertsTab.tsx` (port alert-bindings section), `AssetsTab.tsx`. Depends only on the shared hook's interface. **Workstream C — Canvas stage.** `EditorStage.tsx`: port `startElementPointer`, `transformElement`, `snapToCanvasCenter`, resize handles; add zoom/fit state, floating toolbar, selection badges (name + dimensions). Preserve pointer-capture drag behavior and the `moved` guard that avoids PATCHes on plain clicks. **Workstream D — Inspector.** `EditorInspector.tsx` + `NumericField.tsx` (the commit-on-blur/Enter/arrow-nudge contract — single reusable component, unit-tested), sections per 2.4, token-chip textarea + `/` autocomplete from `EVENT_VARIABLES`. **Workstream E — Integration + cleanup.** Replace `CanvasWorkspace` usage in `alerts/page.tsx`, delete dead CSS (`component-card-*`, `canvas-settings-grid`, etc.), keep `applyAlertAssignment` export + its tests passing, add tests for `NumericField` and zIndex reorder. Suggested order: A first (defines the shell + hook interface), then B/C/D in parallel, then E. ## 5. Acceptance criteria - [ ] `/dashboard/[channelSlug]/alerts` renders the five-region Studio layout; no third "components" column. - [ ] Clicking a layer row selects it (canvas outline + inspector update) and does nothing else; hover actions and `⋯` menu work without changing selection. - [ ] Typing in X/Y/W/H/rotation/opacity/size fields never moves the canvas element until Enter/blur; Escape reverts; arrow keys nudge ±1/±10. - [ ] Drag-reorder in the layers list persists new `zIndex` order; the numeric Layer input is gone. - [ ] Alert bindings live in the left panel Alerts tab with unchanged enable/test behavior. - [ ] Canvas-level settings (name, size, background, audio, active) reachable via top bar / empty-selection inspector; nothing lost from the old UI. - [ ] Copy OBS URL and Test alert are one click from anywhere in the editor. - [ ] Zoom −/+/Fit and Snap toggle work; center snap guides render while dragging. - [ ] All existing tests pass; `serializeCanvasSettings` payloads are byte-compatible with current output. - [ ] Keyboard: Delete removes selected element (unless last), Esc deselects, arrow keys nudge selected element on canvas.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gitchegumi/GitchAlerts#100
No description provided.