Files
website/HERO_EDITOR.md
whitlocktech f69c86f737 Hero Phase 4: moon/badge/image elements + resize + snap grid
Final phase of the hero canvas editor (see HERO_EDITOR.md) — v1 complete.

- element tray adds moon, badge, and image; property panels:
  - moon: size / glow / color
  - badge: text / background / text color / corner radius
  - image: upload (/admin/uploads, >1MB warning) / width% / alt
- corner resize handle on selected elements (image→width%, moon→size,
  text_block→box width)
- 8px snap-grid toggle with a faint canvas grid overlay; drag snaps when on
- HeroElement: image element shows an "Upload an image" placeholder until a
  source is set (a srcless image never ships live)

Verified in-browser: all five element types add + edit; moon resized 64->104px
via the handle; snap grid overlays; a published moon + badge render on the live
portal; no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:47:05 -05:00

7.5 KiB
Raw Permalink Blame History

UOMysticmoon — Hero Canvas Editor Spec

Branch: hero-feature. Build contract for the WYSIWYG portal-hero editor. Derived from the design doc Hero Canvas Editor — Design Document, corrected to match the current codebase and with the open questions resolved. Same workflow as the wiki upgrade: design → phased build → verify.

1. Goal

Let staff compose the portal hero (background image, overlay opacity, and floating elements — text, CTA buttons, moon, badge, image) in-browser, then preview and publish — no source edits. Layout persists as JSON in the existing settings table.

2. Locked decisions

# Decision
Scope Full v1 — background/overlay, all element types, drag/resize/z-order, draft→preview→publish (built in phases)
CTA buttons First-class buttons element type (independently positioned), not baked into a text block
First run Pre-populate the canvas with today's hero (headline, subtitle, teaser, CTAs) as editable elements so nothing changes visually until edited
Drag Native Pointer Events (mouse/touch/pen), zero dependencies
Font size Stored in px (fixed reference canvas)
Image compression None server-side; client warns when a file is > ~1 MB
Preview ?preview=1 renders the draft by reading it through the authenticated admin settings endpoint
Other pages Out of scope for v1 (design allows a per-page key later)

3. Corrections to the design doc (current-code reality)

  1. Public settings is a whitelist, not getAll(). GET /api/v1/public/settingssettings.getPublic()PUBLIC_KEYS in settings.model.js. The doc's "no backend changes / picked up automatically" is wrong. Fix: add hero_layout to PUBLIC_KEYS (one line). hero_layout_draft stays out (admin-only) — which is why preview reads the draft via api.admin.getSettings().
  2. Moon is a reusable component (MoonDot.jsx, props size/glow), used in logo/login/maintenance — not "only the header." The moon element reuses it; it gains an optional color.
  3. Route vs. nav live in different files. /admin/hero route → App.jsx; sidebar link/title → NAV/TITLES in AdminLayout.jsx.
  4. Admin content area is maxWidth: 1000px — the editor canvas renders scaled-to-fit; percentage positions stay faithful.

Everything else in the doc matches (hardcoded HERO_BG + CTAs + homepage_teaser in Portal.jsx; updateSettings accepts arbitrary keys; /admin/uploads exists; default hero asset present; TEXT settings columns — no schema change).

4. Data model — no schema change

Two settings keys (TEXT): hero_layout (live) and hero_layout_draft (admin).

{
  "version": 1,
  "background": { "image_url": null, "position_x": "left", "position_y": "center", "size": "cover" },
  "overlay": { "opacity": 0.72 },
  "elements": [
    { "id": "uuid", "type": "text_block|buttons|moon|badge|image",
      "x": 50, "y": 42, "z": 1, "anchor": "center", "props": { /* per type */ } }
  ]
}

Positions are % of canvas (reference width 1080, matching .shell), so the layout adapts across viewports without breakpoint data. version is validated (=== 1) before use; anything else falls back.

Element props

Type Props
text_block lines: [{ text, tag(h1/h2/p/span), fontSize(px), color, weight }], align
buttons items: [{ label, to, variant(primary/ghost) }], align, gap
moon size, glow, color
badge text, bgColor, textColor, borderRadius
image src, width(%), alt

5. Backend changes

  • One line: add 'hero_layout' to PUBLIC_KEYS. No new routes/controllers — layout saves through the existing PUT /admin/settings; images via /admin/uploads.

6. Frontend changes

  • New client/src/components/HeroElement.jsx — renders one element by type (shared by the live portal and the editor canvas).
  • New client/src/routes/admin/views/HeroEditor.jsx — canvas + element tray + properties panel; native-pointer drag/resize; background/overlay panel; snap grid; auto-save draft, preview, publish, revert.
  • Edit Portal.jsx — parse hero_layout (or draft when ?preview=1 + admin), render elements, fall back to a DEFAULT_LAYOUT built from today's hero so the page is unchanged until edited.
  • Edit AdminLayout.jsx (nav) + App.jsx (route /admin/hero).
  • Edit MoonDot.jsx — optional color.
  • No client/src/api/client.js changes needed beyond what exists (admin.updateSettings, admin.getSettings, admin.upload).

7. Phased build (each phase: build → verify in preview → commit)

  • Phase 0 — Spec this document.
  • Phase 1 — Data path & renderer (verified 2026-06-28). hero_layout whitelisted; HeroElement.jsx; Portal renders the layout with a DEFAULT_LAYOUT fallback. Default render matches the old hero; publishing a layout re-renders; draft key not exposed publicly. Shared helpers moved to client/src/lib/heroLayout.js.
  • Phase 2 — Editor shell + background/overlay (verified 2026-06-28). /admin/hero view + sidebar nav; canvas live-preview; background upload + 3×3 position + overlay opacity; debounced draft auto-save; publish; ?preview=1 reads the draft (admin) with a banner; revert. Verified: overlay/position update the canvas, auto-save writes the draft, publish writes live, preview shows the draft while the normal portal shows live.
  • Phase 3 — Elements: select / drag / text_block / buttons (verified 2026-06-28). Element tray (+ Text / + Buttons); click-to-select with outline; native Pointer Events drag (% of canvas); Delete key + panel delete; z-order (send back / bring forward); text_block line editor (text/tag/size/color/bold, add/remove lines, align) and buttons editor (label/path/variant, add/remove). Verified: select shows the line editor, editing a line updates the canvas live, drag moved 50%→65%, add→3/delete→2 elements, empty-canvas click deselects.
  • Phase 4 — moon + badge + image + resize + snap grid (verified 2026-06-28). Tray adds moon/badge/image; property panels (moon: size/glow/color; badge: text/colors/radius; image: upload/width/alt); corner resize handle (image→width%, moon→size, text→box width); 8px snap-grid toggle with overlay; image placeholder until a file is chosen. Verified: each type adds + edits, resize moved a moon 64→104px, snap grid shows, and a published moon+badge render on the live portal.

Status: v1 feature-complete. All phases verified end-to-end; ready for PR. Deferred (noted in the design doc as follow-ups): 8-point resize (only a corner handle for now), per-viewport layouts, server-side image compression.

8. Edge cases (from the doc, carried forward)

  • JSON.parse wrapped in try/catch + version check → fall back to DEFAULT_LAYOUT.
  • Element ids via crypto.randomUUID() (never array index).
  • Empty elements → render DEFAULT_LAYOUT so the hero is never blank.
  • Last-write-wins on concurrent admin edits (acceptable for this shard).
  • Client-side warning for background files > ~1 MB (no hard block; 8 MB server cap).