Files
code_of_conquest_dnd/docs/adr/0001-editor-first-ui-scenes.md
Phillip Tarrant c933d12504 docs: record editor-first UI-scene convention (ADR 0001)
Capture the direction change from M3-b: UI screens are authored editor-first
as .tscn node trees (previewable/arrangeable in the Godot editor), scripts do
on-load work only — never build the tree in _ready(). Add ADR 0001, a
"UI scene conventions" how-to in client/docs, a §16 mandate in the charter,
and mark M3-a/M3-b done in the roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 10:04:57 -05:00

4.5 KiB
Raw Permalink Blame History

0001 — UI screens are editor-authored scenes, not code-built

  • Status: accepted
  • Date: 2026-07-11 (M3-b, Main Window shell)

Context

The M3-b Main Window shell was first built by constructing every Control node in _ready() (a pattern borrowed from theme_showcase.gd, where it was fine because the showcase is a throwaway proof). For a real screen this was wrong:

  • It does not preview in the Godot editor. Opening the .tscn showed a bare root Control — every widget only existed at runtime — so the human could not see or arrange the layout in the editor. Tuning meant editing pixel offsets in code, running, eyeballing, repeating.
  • It buried layout, styling, and content in GDScript, where it is verbose and hard to reason about, and it drifted from charter §16's own words: "each screen is recreated as a Godot 4.7 Control-node scene."

The human asked to be able to arrange the scene in the editor "as much as possible," with code only for genuine on-load work.

Decision

UI screens (and their sub-scenes) are authored editor-first as .tscn node trees. The split:

  • In the .tscn (editor): the whole node tree — containers, panels, labels, buttons, art-slot placeholders — with the shared Theme assigned on the scene root and every theme_type_variation set on its node. Authored placeholder text/values so the screen previews with representative content. Sub-screens are instanced (ExtResource PackedScene), not rebuilt.
  • In the script (on-load only): @onready references to named nodes; wiring (signals, the DM loop, injected services); animations (e.g. the dock slide); and binding state into the authored nodes (setting label text/colours from the model). Never construct the tree in _ready().
  • Data-driven repeats (turn-order tokens, consumable slots, later inventory grids) are a fixed set of authored nodes bound from state at runtime — spare slots hide. This keeps them previewable; combat/inventory become the writers later. (Author enough slots for the common case; if a screen ever needs a genuinely unbounded list, revisit with a code-instanced container then.)

Supporting conventions established alongside this decision:

  • Scene root sizing. Give the .tscn root a fixed size (e.g. 1920×1080) via offsets so it previews, but no full-rect anchors on the root — a root Control run via F6 is not sized by full-rect anchors and collapses to (0,0). The script keeps size = get_viewport_rect().size on _ready + size_changed (the M3-a lesson).
  • Button styling uses Button-base theme variations. Never apply a PanelContainer-base variation (DarkPanel, ParchmentCard) to a Button — a Button looks up normal/hover/pressed, which a panel-only variation lacks, so it renders unskinned. M3-b added Button-base DockButton and ParchmentButton to the theme for exactly this.
  • RichTextLabel prose reads its style from the theme. RichTextLabel does not inherit default_font/default_color, so without a theme entry its text renders near-white (unreadable on parchment). The theme now sets a base RichTextLabel style (dark INK_BODY serif + italic face). All colours still come from Palette via the theme builder — no hex in scripts or .tscn.

Consequences

Easier:

  • The human arranges any screen directly in the Godot editor; layout persists in the .tscn. Fine-tuning is drag-and-drop, not code-run-eyeball.
  • Screens preview with themed, representative content on open.
  • Scripts shrink to logic; less code, easier to review, focused files.
  • The look stays one system (theme assigned per scene root).

Caveats / harder:

  • Shader surfaces render only at runtime. DarkBay/VignetteOverlay set their ShaderMaterial in _ready() (M3-a accepted deviation), so their texture appears only on an F6 run — the editor shows layout but a plain panel behind it. Layout still previews fine.
  • Tests reference nodes by their authored @onready paths; renaming/moving a node in the editor means updating the path (and any test that reads it).
  • The visual gate stays a human F6 eyeball — headless tests prove structure (nodes mount, sizes are non-zero, bindings run) but not rendering.

Reference implementation

M3-b shell scenes: client/scenes/shell/{MainWindowShell,SystemDock,NarrationBook}.tscn with their client/scripts/ui/shell/*.gd scripts. Practical how-to lives in client/docs/README.md → "UI scene conventions."