Second increment of the role-resolver port. New `role_defaults` module ports
all 43 `registerRole` rules from role-definitions/index.ts + `applyDefaults` +
`detectThemeFromNode`. `resolve_tree_roles` now, after inferring a node's role
(I1), fills in that role's default visual/layout properties — theme-aware
(card/input/navbar/divider fills), parent-aware (cards stretch in a horizontal
row), and canvas-width-aware (mobile vs desktop padding).
Defaults are applied via a JSON round-trip merge that sets ONLY keys the model
left unset (AI-explicit always wins), mirroring the TS `record[key] ===
undefined` check; jian's camelCase + flattened container/text props let the
merge map straight onto the schema with no per-field hand-mapping. On any
(de)serialize failure the node is left untouched, so defaults can never drop a
node. Theme + canvas width are derived from the plan's root frame in
run_subtask and threaded down the walk.
The tiny-card guard now applies to EXPLICIT roles too (not just inferred): an
LLM-emitted role:"card"/"stat-card" on a node too small to be a card is stripped
before the defaults pass, so it can't inflate a 6×6 dot into a padded, shadowed
card (Codex review).
29 tests (theme detection, set-if-absent, AI-explicit-wins, theme/parent/
canvas-width variants, CJK typography, divider orientation, explicit+inferred
tiny-card strip, unknown role). Verified on a MiniMax-M3 run: 106 nodes,
search-bars/cards received role defaults, an explicit cornerRadius preserved.