Skip to content

Wayfinder: layout editor v1 design #82

Description

@jonocodes

Destination

First useful version of deckd's layout editor ships — icon picker, action editor, color picker, grid positioning writable from a desktop browser tab that saves to YAML and triggers hot-reload. The editor is a chrome view in the existing client, reusing the session view-pinning protocol (select_view/clear_view) to suspend auto-switching during editing.

Tickets

Notes

Decisions so far

  • V1 scope lock — Widget kinds: button, meter, stats. Button exposes id/label/icon/color/size/action (macro deferred). Meter exposes source/min/max. Stats exposes metrics list. Layout-level: match, display_name, theme, icon, jogstrip. Action: all four primitives. New widget defaults: kind=button, no defaults for icon/color/label. Amended per ADR-0010: gridsize span, drag-to-reorder list, viewport-preview pane.
  • YAML round-trip strategyruamel.yaml comment-preserving round-trip (not canonical model_dump()); files stay human-owned. Daemon holds the CommentedMap as edit surface, Pydantic validates only. Save = full-snapshot from client reconciled onto a fresh disk re-read, widgets matched by id (reorder/edit/add/delete, comments ride along); maps recurse, other sequences atomic. Add Layout-level duplicate-id validator; atomic temp-write + os.replace, natural watchfiles reload. New dep: ruamel.yaml. Delivers the mechanism Advanced field round-trip #89 needs; new-widget id generation + drag gesture deferred to Editor interaction model #87.
  • Daemon save_layout / create_layout API — write path is an HTTP endpoint, not a WS message (WS stays read/push-only). Ships save only: PUT /layouts/{id}, idempotent full-snapshot replace on the existing authed aiohttp surface. URL {id} authoritative — a match[0] change is rejected (409) as a rename; other match tokens editable. Validation failures → 400 with sanitized structured Pydantic errors (loc/msg/type only, strip input/ctx per the Core diagnostic surface for AI-assisted debugging #70 never-leak-payloads rule). Success → 200 echoing the canonical re-read ({ok,layout}); handler does not await watchfiles — request/response owns editor sync, WS layout push owns live-deck render. create (POST /layouts) deferred to New-layout creation flow #88.
  • Editor interaction model — v1 ships Variant A only: a full-screen edit chrome view (palette · live-grid canvas · properties panel) — the complete editor (add/edit/reorder/delete), desktop-first. Variant B (inline tap-to-inspect inspector) ruled out of scope — edit-existing only, additive post-v1. Repositioning is in v1 via A's canvas; new-widget id = button-<n> minted by the palette, editable, backstopped by the YAML round-trip strategy #85 uniqueness validator. Prototype: editor-interaction.html (branch prototype/87-editor-interaction).
  • Icon picker research — icon ref is {source,name} over two registries: lucide (~1,756 glyphs, bundled ~198 KB, kebab-case) and simple-icons (~3,450 brand logos, lazy 2.1 MB chunk, slug). No enumeration API today but both enumerable client-side. v1 picker = search-driven grid of rendered glyphs with source tabs (Lucide immediate, Brands lazy-loaded on tab open), virtualized results, selected-chip showing the source/name written to YAML. Mockup: icon-picker.html (branch prototype/86-icon-picker). Follow-ups (virtualization lib, build-time name index) → component-architecture.
  • New-layout creation flow — editor opens edit-what-you-see (pinned via select_view) + secondary picker of all layouts. Create via detect-and-offer (app resolved to default → prompt, prefill match from app_idwm_class) or manual "New layout". Create-on-first-save: in-memory draft, POST /layouts fires on first save with content; id/filename = slugified match[0], 409 on collision, mirrors Daemon save_layout / create_layout API #84's PUT contract. Browser case: is_browser → offer "browser" (match:[firefox]) or "this site" (match:[title:*<title>*] seeded from live title, user trims). "No layout yet" prompt is editor-only in v1. Draft abandon = clear_view, no file.
  • Advanced field round-trip — models are extra="forbid", so no unknown fields — just declared fields the v1 UI won't render (macro, media config, meter/stats sensor config). Preservation = client opaque pass-through: client edit model carries unrendered fields verbatim and echoes them in the full-snapshot PUT (keeps Daemon save_layout / create_layout API #84 full-replace; deletion = omission). UX = pure silent round-trip (no warn/toggle/badge). Unsupported-kind widgets (media, mediabrowser only) render as opaque placeholders — reorder/delete only, not palette-insertable. blank is editor-supported (takes only size; palette-insertable gap primitive) alongside button/meter/stats.
  • Overflow toggle in v1 scope?ships in v1: two-value control (shrink-to-fit/clip) writing Layout.overflow. Near-zero cost (validated Literal, already round-tripped per Advanced field round-trip #89) and ADR-0010 names the toggle as an editor component. Semantics (per reflow.ts): cells are always uniform squares — a button is never partially cut; clip drops whole trailing widgets that exceed window height, shrink-to-fit shrinks all cells uniformly (16px floor) so all fit.

Not yet specified

  • Client-side component architecture (form library, drag-drop lib)
  • Testing strategy
  • Reconsider the overflow default — currently shrink-to-fit (layouts.py:317); whether the default should be clip (drop-extras) is a daemon/ADR-0010 decision surfaced by Overflow toggle in v1 scope? #95, separate from the editor toggle. Doesn't block the editor.

Out of scope

  • Macro editor
  • Media widget config editor
  • Multi-widget copy/paste
  • Undo system
  • Inline tap-to-inspect inspector (Variant B) — on-device quick-edit popover; edit-existing only, can't add/delete/reorder. Additive polish on top of the v1 full-screen editor; revisit post-v1.
  • Layout deletion — removing a layout file from the editor; separate flow from creation (New-layout creation flow #88), not in v1.
  • Passive live-deck "no layout" hint — surfacing "this app has no layout" on the operational deck (not just inside the editor); post-v1 additive (New-layout creation flow #88 Q5).

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions