banner_studio/INTERACTION_STANDARDS.md
Simeon Schecter 988a47c797 Initial commit: Day 1 + Day 2 of the vertical slice
Day 1 (monorepo + Node layout engine):
- Turborepo + pnpm workspaces with apps/web, apps/render-worker, and
  packages for types, layout-engine, prompts, api-lib.
- @banner-studio/types: BannerSpec contract, every layer kind, ResolvedLayer,
  zod schemas mirroring each interface.
- @banner-studio/layout-engine: Dropflow WASM wrapper, text measurement,
  shrink-to-fit, push_siblings, resolveLayout. Snapshot-tested.

Day 2 (browser parity + AI pipeline):
- Layout engine ./browser subpath: same resolveLayout in the browser via
  Dropflow WASM build. Quarantined wasm-locator import (dropflow 0.5.1
  exports gap).
- Cross-group push_siblings bug fix: deltas now thread through group
  recursion via a shared accumulator; regression test added.
- DEMO_TEMPLATE_300x250 promoted to packages/layout-engine/src/templates/.
- @banner-studio/prompts: versioned extract + generate prompts with
  zod-defined tool schemas (claude-sonnet-4-6, forced tool-use).
- @banner-studio/api-lib: CSV feed loader, extract/generate/route-node/
  assemble agents, orchestrator returning fully-resolved BannerSpec.
  Generate agent retries on character-limit overflow.
- apps/web (Next.js 14 App Router): /api/generate route, /parity diff page,
  promise-singleton browser engine init.
- feeds/demo.csv with five hand-authored rows of varied length.
- SLICE_DEVIATIONS.md documents the five intentional gaps from
  ARCHITECTURE.md with V1 reversal paths.

Verified end-to-end: POST /api/generate against the live Claude API
returns three resolved BannerSpecs and two honestly-skipped rows
(overflow after two attempts). 26 unit + integration tests passing.
2026-05-15 10:25:21 -04:00

12 KiB
Raw Blame History

INTERACTION_STANDARDS.md

The canonical interaction reference for the banner production platform. These are non-negotiable. A developer who implements something different is fixing a bug, not making a design choice.

Conventions:

  • Cmd on macOS = Ctrl on Windows/Linux. The shortcuts below are written with Cmd for brevity; the implementation must bind both.
  • Shift, Alt, and Cmd modifiers compose as written.
  • Every interaction is undoable except where explicitly noted.
  • Behaviors apply across every editable surface in the app (template builder canvas, review grid edits, property panels) unless surface-specific overrides are called out.

Selection

Action Result
Single click on a layer Select the layer
Single click on empty canvas Deselect everything
Double click on a text layer Enter text edit mode, cursor at click position
Double click on a group Drill into the group (select first child); deeper double-clicks descend further
Escape Exit text edit mode → exit nested group → deselect (cascading)
Tab (with selection) Cycle to next sibling in z-order
Shift + Tab Cycle to previous sibling in z-order
Cmd + A Select all layers in the active artboard
Click + drag on empty canvas Marquee select (V2 — out of slice)
Shift + click Add to selection (multi-select)
Cmd + click Toggle inclusion in selection
Right-click Context menu — always a duplicate of UI affordances, never the only path

Selection is always visible: every selected layer has a 1px outline in the system accent color, plus a bounding box for the multi-selection.

Multi-select and group operations

Action Result
Drag any selected layer All selected layers move together, preserving relative position
Arrow keys with multi-select Nudge all selected layers by 1px
Property panel edit with multi-select Applies to all selected layers; conflicting values show "Mixed" and editing replaces
Resize handle with multi-select Scales the bounding box; children scale proportionally
Align actions (left/center/right/top/middle/bottom) Align selected layers relative to the bounding box, or to the artboard if only one layer is selected
Distribute (horizontal/vertical) Available with 3+ layers selected
Delete / Backspace Delete all selected layers (undoable, no confirmation)
Cmd + G Group selected layers into a new GroupLayer with group_behavior: 'fixed'
Cmd + Shift + G Ungroup the selected group, lifting children to the parent
Cmd + L Lock selected layers (BaseLayer.locked = true)
Cmd + Shift + L Unlock
Cmd + ; Toggle visibility (BaseLayer.visible)

Locked layers cannot be selected by click on the canvas — they must be selected via the layers panel. Hidden layers don't render and cannot be selected.

Movement

Action Result
/ / / Nudge 1px
Shift + arrow Nudge 10px
Drag Free move
Shift + drag Constrain to nearest axis (horizontal or vertical)
Alt + drag Duplicate and move — standard creative-tool behavior
Cmd + drag Disable snapping for this gesture

Snapping engages when the dragged element is within 4px of a snap target (artboard edge, safe zone, sibling edge or center, sibling baseline). Snap targets render a 1px guide before they engage. Cmd held during the drag suppresses all snapping.

Zoom

Action Result
Cmd + scroll Zoom to cursor
Cmd + = Zoom in
Cmd + - Zoom out
Cmd + 0 Fit all artboards in view
Cmd + 1 100% (1:1 pixel)
Cmd + 2 200%
Space + drag Pan, even when a selection tool is active

Zoom is continuous, not stepped. Browser zoom is disabled inside the editor — Cmd + scroll is always canvas zoom.

History

Action Result
Cmd + Z Undo
Cmd + Shift + Z Redo
Cmd + Y Redo (alt)

Undo is unlimited within the session, including in text edit mode, in the property panel, and in the review grid. Undo never has exceptions. This is treated as a correctness property of the application, not a feature.

History persists across reloads via the version log for major changes (every state change is auto-saved). In-session undo is fine-grained; cross-session undo operates at version granularity.

Text editing

Action Result
Double-click a text layer Enter edit mode, cursor at click position
Cmd + B Bold (toggles typography.font_weight within selection)
Cmd + I Italic
Cmd + U Underline
Cmd + A Select all text in the layer
Cmd + arrow Word-wise / line-wise cursor movement (standard OS behavior)
Shift + arrow Extend selection
Escape Commit edit and exit edit mode
Tab Commit edit and select next layer in z-order
Enter (in single-line text) Commit and exit
Shift + Enter Line break within multi-line text
Cmd + Z in edit mode Undo last text change (does not exit edit mode)

Live text fit happens as the user types: Dropflow recalculates layout on every change, the canvas updates within 100ms, push_siblings cascades visibly. If the content exceeds the character limit, the layer renders with a limit-exceeded badge but does not block typing — the user sees the consequence and decides.

Copy / paste

Action Result
Cmd + C Copy selected layer(s) with all properties
Cmd + V Paste to the active artboard at the source layer's relative coordinates
Cmd + Shift + V Paste in place — exact same coordinates as the source
Cmd + D Duplicate in place, offset by (+10, +10) on the same artboard
Cmd + X Cut (copy + delete)

Paste always:

  • Lands on the active artboard
  • Preserves every visual property (typography, color, opacity, rotation, anchors)
  • Preserves the layer's behavior rules (push_siblings, character constraints)
  • Re-anchors push_siblings.layer_id references to copies of siblings if the siblings were copied too; otherwise the rule is preserved with the original target ID
  • Is undoable

Pasting across artboards of different sizes preserves the layer's pixel dimensions, not its proportional position — designers think in pixels for ad layouts.

Layer manipulation

Action Result
Cmd + ] Bring forward one z-index
Cmd + Shift + ] Bring to front
Cmd + [ Send backward one z-index
Cmd + Shift + [ Send to back
Drag in layers panel Reorder z-index by dragging
Cmd + G Group
Cmd + Shift + G Ungroup
Cmd + L / Cmd + Shift + L Lock / unlock
Cmd + ; Toggle visibility
Cmd + R Rename selected layer (focus name field in layers panel)

The layers panel mirrors the canvas selection bidirectionally. Selecting in the panel selects on canvas; selecting on canvas highlights in the panel and auto-scrolls if needed.

Review grid shortcuts

Action Result
/ Next / previous banner
/ Next / previous row in the grid
Enter Open the focused banner detail (full-size view + AI reasoning panel)
Escape Close detail / return to grid
A Approve the focused banner
R Reject the focused banner (prompts for one-line reason)
G Regenerate the focused banner (shows pre-flight: overrides preserved, conflicts likely)
E Edit the focused field (or activate inline edit if a field is in focus)
Cmd + Enter Approve all banners in the campaign (only if all have status in_review and no unresolved conflicts)
Space Play / pause animation on the focused banner
? Show this shortcut sheet as an overlay

Approval and rejection are bound to single keystrokes (A, R) because they are the most repeated actions in the review path. Regenerate is G deliberately — not Cmd+G, which is group — because regenerate is a single-banner action with a pre-flight modal that prevents accidental destruction.

The shortcut sheet (?) is the canonical onboarding aid for the review grid. Producers should not need a tutorial.

Touchpad and gesture

Gesture Result
Two-finger scroll Pan the canvas (no scroll bars are visible during; the canvas is the document)
Pinch Zoom to pinch center, continuous
Two-finger horizontal scroll on review grid Pan horizontally through banners
Three-finger swipe OS-level navigation (do not capture)
Double-tap with two fingers Fit to view (Cmd + 0 equivalent)

Browser-level pinch zoom is disabled in the editor and in the review grid — pinch is always canvas zoom. Two-finger scroll on the canvas pans, not page-scrolls. The canvas captures these gestures when the cursor is over it; outside the canvas, the page behaves normally.

Gesture conflict handling:

  • If the cursor is over an inline text input or scrollable panel, two-finger scroll scrolls that element, not the canvas.
  • Pinch over a scrollable panel still zooms the canvas (the page should never zoom; this is consistent everywhere in the editor).
  • Space + drag (panning shortcut) wins over gesture-based pan when both fire — keyboard intent is explicit.

Property panel

Action Result
Type in any numeric field Live update on commit (blur or Enter)
Drag the field label Scrub the numeric value (Photoshop-style)
Shift + scrub 10× speed
Alt + scrub 0.1× speed
Tab Move to next field
Color swatch click Open color picker; selection updates live as the picker moves
Escape in a field Revert to the field's value before edit

All property panel edits are undoable. The panel never shows a "Save" or "Apply" button.

Asset library (V1)

Action Result
Drag an asset onto a smart asset slot Bind the asset's variant group to the slot
Drag an asset onto the canvas Reject — assets must enter through smart asset slots, not free placement (anti-pattern #32)
Click an asset Open asset detail (metadata, rights, approved crops)
Cmd + F in the library Focus search
Filter pills (region, market, language, campaign type) Multi-select OR within a pill, AND across pills

Conflicts to resolve before build

  • Cmd + L for lock vs. layer-level locking. Anti-pattern document and FRUSTRATION_LIST.md both reference field-level locking (HumanOverride.locked). This document defines Cmd + L as layer-level lock (BaseLayer.locked). These are two different locks. Confirm naming and keybinding to avoid user confusion — likely: Cmd + L locks the layer; field-level lock is a toggle in the review grid's override pip menu, not a global keystroke.
  • Marquee select. Listed as V2 (out of slice) consistently with the source document. Confirm V1 includes it; if not, surface in the V1 acceptance criteria.
  • Browser zoom disabled in the editor. Disabling browser zoom is technically tricky (browsers don't fully let you). Document the chosen approach (e.g., a CSS touch-action: none on the canvas wrapper + intercepting Cmd + scroll) before implementation so behavior is consistent across Chrome/Safari/Firefox.
  • Cmd + Y vs. Cmd + Shift + Z for redo. Both bound. Adobe convention is Cmd + Shift + Z; some users expect Cmd + Y. Both work; no conflict — but document that Cmd + Y is an alias, not a separate action.
  • Tab cycles z-order vs. Tab in text edit. Tab exits text edit mode and selects the next layer. This means Tab cannot insert a tab character into text. For ad copy this is fine (ad copy has no tabs), but flag in case a future text feature needs literal tabs.