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

87 lines
4.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`](../../client/docs/README.md) → "UI scene conventions."