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>
4.5 KiB
4.5 KiB
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
.tscnshowed 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 sharedThemeassigned on the scene root and everytheme_type_variationset on its node. Authored placeholder text/values so the screen previews with representative content. Sub-screens are instanced (ExtResourcePackedScene), not rebuilt. - In the script (on-load only):
@onreadyreferences 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
.tscnroot 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 keepssize = get_viewport_rect().sizeon_ready+size_changed(the M3-a lesson). - Button styling uses Button-base theme variations. Never apply a
PanelContainer-base variation (
DarkPanel,ParchmentCard) to aButton— a Button looks upnormal/hover/pressed, which apanel-only variation lacks, so it renders unskinned. M3-b added Button-baseDockButtonandParchmentButtonto the theme for exactly this. - RichTextLabel prose reads its style from the theme.
RichTextLabeldoes not inheritdefault_font/default_color, so without a theme entry its text renders near-white (unreadable on parchment). The theme now sets a baseRichTextLabelstyle (darkINK_BODYserif + italic face). All colours still come fromPalettevia 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/VignetteOverlayset theirShaderMaterialin_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
@onreadypaths; 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."