Files
docs/website/HERO_EDITOR.md
whitlocktech 3b076df09b Hero Phase 2: editor shell + background/overlay + draft/preview/publish
Second phase of the hero canvas editor (see HERO_EDITOR.md).

- new lib/heroLayout.js: shared defaultLayout/buildOverlay/heroBackground/
  parseLayout used by both the portal and the editor (Portal refactored onto it)
- new admin view HeroEditor.jsx at /admin/hero (+ sidebar nav + route):
  - live canvas preview (16:9) rendering the draft via HeroElement
  - background panel: image upload (/admin/uploads, >1MB warning), 3x3 position
    grid, overlay opacity slider — all update the canvas in real time
  - debounced (800ms) auto-save to hero_layout_draft
  - Publish (writes hero_layout + draft), Preview (opens /?preview=1), Revert
- Portal: ?preview=1 renders the draft via the admin settings endpoint, with a
  "showing unpublished draft" banner; normal load renders the published layout

No schema/dep changes. Verified end to end: overlay/position update the canvas,
auto-save writes the draft, publish updates the live portal, preview shows the
draft while the public page shows live. Element drag/properties land in Phase 3.

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

6.6 KiB
Raw 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. Add/select/move (native pointer)/delete/z-order; text_block + buttons property panels. Exit: add a heading + CTA row, drag to place, publish, see it live.
  • Phase 4 — moon + badge + image + resize + snap grid. Remaining element types, resize handles, 8px snap. Exit: place a moon and an uploaded image, resize, publish.

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).