--- name: godot-composition-apps description: "Expert architectural standards for scalable Godot Apps, Tools, EditorPlugins, and Control-heavy UIs using Composition (Has-A Orchestrator + components). Use when building dashboards, tool windows, forms, settings panels, or EditorPlugin UIs. Do NOT use for gameplay entities (Player/Enemy/Weapon/Hitbox) — route those to godot-composition. Trigger keywords: Control, EditorPlugin, tool UI, Orchestrator, VLS, rock test, AuthComponent, ThemeManager, Saveable component, dependency injection." --- # Godot Composition & Architecture (Apps & UI) ## Decision Gate — App vs Gameplay Entity | Root node / task | Route | |------------------|-------| | Control, EditorPlugin, tool window, settings dock, form UI | **Stay here** — Orchestrator + components | | Player, Enemy, Weapon, Hitbox, gameplay CharacterBody | **[godot-composition](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition/SKILL.md)** — not this skill | **App-only gate:** If the node is a gameplay actor (Player/Enemy/Weapon/Hitbox), use godot-composition. This skill owns **Control / EditorPlugin / tool** composition. ## The Core Philosophy ### The Litmus Test (Rock Test) Before writing a script, ask: **"If I attached this script to a literal rock, would it still function?"** - **Pass:** An `AuthComponent` on a rock allows the rock to log in. (Context Agnostic) - **Fail:** A `LoginForm` script on a rock tries to grab text fields the rock doesn't have. (Coupled) **MANDATORY:** Validate new components with [comp_rock_test_boilerplate.gd](scripts/comp_rock_test_boilerplate.gd). ### The Backpack Model (Has-A > Is-A) Treat the Root Node as an empty **Backpack**. - **Wrong:** `SubmitButton` extends `AnimatedButton` extends `BaseButton`. - **Right:** Root **HAS-A** `AnimationComponent` and **HAS-A** `NetworkRequestComponent`. ## The Hierarchy of Power (Communication Rules) | Direction | Source → Target | Method | Reason | |-----------|-----------------|--------|--------| | **Downward** | Orchestrator → Component | **Function Call** | Manager owns the workers. | | **Upward** | Component → Orchestrator | **Signals** | Workers are blind. | | **Sideways** | Component A ↔ Component B | **FORBIDDEN** | Siblings never talk directly. | **Sideways Fix:** Component A signals the Orchestrator; Orchestrator calls Component B. ## Available Scripts > **MANDATORY**: Read the matching script before implementing the pattern. Do not reinvent Orchestrator wiring inline. ### [comp_rock_test_boilerplate.gd](scripts/comp_rock_test_boilerplate.gd) **MANDATORY first read** — Attach-candidate-to-literal-rock harness that fails hard-coupled components early. ### [comp_orchestrator_base.gd](scripts/comp_orchestrator_base.gd) **MANDATORY** when creating any App/UI root — Signal-up / call-down wiring skeleton (0% business math). ### [comp_logic_visual_syncer.gd](scripts/comp_logic_visual_syncer.gd) **MANDATORY** for VLS — Logic emits `state_changed`; visuals/animations react without logic knowing `AnimationPlayer`/`Theme`. ### [comp_base_component.gd](scripts/comp_base_component.gd) Shared component lifecycle + dependency validation for app workers. ### [comp_dependency_injector.gd](scripts/comp_dependency_injector.gd) Typed export / registry injection so Orchestrators avoid brittle `$` paths. ### [clipboard_copier.gd](scripts/clipboard_copier.gd) Context-agnostic clipboard worker — pairs with orchestrator toast pattern (see references). ### [comp_data_driven_config.gd](scripts/comp_data_driven_config.gd) Resource-backed config for tool settings and form defaults. ### [comp_persistence_component.gd](scripts/comp_persistence_component.gd) **MANDATORY for saveable UI/tool state** — Registers `Saveable` group + `get_save_data()` without putting I/O in visuals. ### [comp_ability_sequencer.gd](scripts/comp_ability_sequencer.gd) Ordered multi-step tool workflows (wizard pages, export pipelines) as child steps. ### [comp_health_component.gd](scripts/comp_health_component.gd) / [comp_hitbox_component.gd](scripts/comp_hitbox_component.gd) Only when an app/tool simulates entities; prefer godot-composition for real games. ## The Orchestrator Pattern Root script (`LoginScreen.gd`, `UserProfile.gd`, EditorPlugin dock root) is an **Orchestrator**: - Math/Logic: 0% · State wiring: 100% - Job: listen to component signals → call other component methods **MANDATORY:** Extend patterns from [comp_orchestrator_base.gd](scripts/comp_orchestrator_base.gd). | Concept | App/UI Example | |---------|----------------| | Orchestrator | `UserProfile.gd` / Editor dock root | | Logic component | `AuthValidator` | | VLS | `AuthVisualSyncer` via [comp_logic_visual_syncer.gd](scripts/comp_logic_visual_syncer.gd) | | Theme ownership | Separate theme component — never mutated inside form logic | | Focus ownership | Orchestrator grants/releases Control focus; components never steal siblings' focus | ## Implementation Standards 1. **Type Safety** — `class_name` on components; no untyped core architecture. 2. **Dependency Injection** — `@export var auth: AuthComponent` (Inspector / `%UniqueNames`). **NEVER** `get_node("Path/To/Child")` for components. 3. **Stateless workers** — Orchestrator passes data into functions; components do not scrape sibling Controls. ## NEVER Do (Expert Architectural Rules) ### Hierarchy & Dependencies - **NEVER use get_parent() to fetch data** — Inject via `@export` or function args. - **NEVER talk sideways** — Signal up; Orchestrator calls down. - **NEVER use brittle Node Paths** — Prefer `@export` / `%`. ### Logic & State - **NEVER put business logic in the Orchestrator** — Only `_on_signal` delegators. - **NEVER store global state in individual components** — Shared Context Resource or Autoload. - **NEVER assume a component's parent is a specific type** — Rock Test failure. ### Polish & Orchestration - **NEVER skip signal cleanup** — Disconnect on exit / use CONNECT_ONE_SHOT where appropriate. - **NEVER let Logic know about Visuals** — Emit; VLS / Orchestrator plays animations and applies Theme. ## Fragile App Workflow: Saveable + Theme Ownership Do **not** put save I/O or Theme mutation inside form Controls. Route through components: 1. **MANDATORY** [comp_persistence_component.gd](scripts/comp_persistence_component.gd) on the Orchestrator (or a dedicated Saveable child) — `add_to_group("Saveable")` + `get_save_data()`. 2. Theme / StyleBox changes belong in a theme component ([theme_manager.gd](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition-apps/resources/theme_manager.gd)) called **down** by the Orchestrator after logic signals success/failure. 3. Focus: Orchestrator owns `grab_focus()` after validation failures so logic stays Control-agnostic. ```gdscript # settings_dock_orchestrator.gd (pattern — wire via @export, not $) extends Control @export var persistence: CompPersistenceComponent @export var theme_mgr: Node # theme_manager.gd API @export var form_logic: Node func _ready() -> void: form_logic.settings_valid.connect(_on_settings_valid) form_logic.settings_invalid.connect(_on_settings_invalid) func _on_settings_valid(payload: Dictionary) -> void: theme_mgr.apply_user_theme(payload.get("theme_id")) # Save systems collect via Saveable group — persistence component stays dumb func _on_settings_invalid(field: StringName) -> void: # Orchestrator owns focus; logic never touches sibling LineEdits var target := get_node_or_null("%" + String(field)) if target is Control: target.grab_focus() ``` ## Expert Composition Patterns (Apps) ### 1. App-Level Service Locator Prefer `Engine.register_singleton()` for lightweight non-Node services (Auth, Config) instead of dozens of Autoload Nodes [6]. ### 2. Visual-Logic-Syncers (VLS) **MANDATORY** [comp_logic_visual_syncer.gd](scripts/comp_logic_visual_syncer.gd) — logic never calls `AnimationPlayer.play()`. ### 3. O(1) Component Registry Orchestrator Dictionary registry for dashboard modules — still no sideways calls; registry is Orchestrator-private lookup. > **MANDATORY** for clipboard/share orchestrator examples and service-locator depth: [app-orchestrator-examples.md](references/app-orchestrator-examples.md). **Do NOT Load** when [comp_orchestrator_base.gd](scripts/comp_orchestrator_base.gd) covers your screen. ## Reference > Progressive disclosure: open Official Documentation links only when researching a specific API; load Related Skills when routing to a peer domain — do not preload the whole lattice. ### Official Documentation - [Scene organization](https://docs.godotengine.org/en/stable/tutorials/best_practices/scene_organization.html) — Canonical signal-up / call-down ownership so Orchestrators wire components without sibling coupling. - [When and how to avoid using nodes for everything](https://docs.godotengine.org/en/stable/tutorials/best_practices/node_alternatives.html) — Prefer Resources/RefCounted for pure data and logic services so components stay lean and rock-testable. - [Godot interfaces](https://docs.godotengine.org/en/stable/tutorials/best_practices/godot_interfaces.html) — Duck-typed method contracts (`has_method`) that let composition work without deep inheritance trees. - [What are Godot classes?](https://docs.godotengine.org/en/stable/tutorials/best_practices/what_are_godot_classes.html) — Why Godot favors scene composition (Has-A) over classical Is-A hierarchies for reusable behaviors. - [Using signals](https://docs.godotengine.org/en/stable/getting_started/step_by_step/signals.html) — Upward component→Orchestrator events that keep workers blind to parents and siblings. - [GDScript exports](https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_exports.html) — Typed `@export` dependency injection that replaces brittle `get_node` paths in the Inspector. - [Scene Unique Nodes](https://docs.godotengine.org/en/stable/tutorials/scripting/scene_unique_nodes.html) — `%UniqueName` for Orchestrator-local Control/Button wiring without string path fragility. - [Resources](https://docs.godotengine.org/en/stable/tutorials/scripting/resources.html) — Data-driven `.tres` configs so values stay outside logic components. - [Groups](https://docs.godotengine.org/en/stable/tutorials/scripting/groups.html) — Mass registration (e.g. Saveable/Components) for Orchestrator registries without hard sibling refs. - [Autoloads versus regular nodes](https://docs.godotengine.org/en/stable/tutorials/best_practices/autoloads_versus_regular_nodes.html) — When a scene-local Orchestrator beats a global Autoload for app/UI composition. - [Singletons (Autoload)](https://docs.godotengine.org/en/stable/tutorials/scripting/singletons_autoload.html) — Safe registration of cross-scene services when a true app-level locator is justified. - [Saving games](https://docs.godotengine.org/en/stable/tutorials/io/saving_games.html) — Persistence patterns that map cleanly onto modular saveable components. ### Related Skills #### Prerequisites - [godot-project-foundations](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-project-foundations/SKILL.md) — Project layout, Autoload registration, and scene ownership that Orchestrators and components plug into. - [godot-gdscript-mastery](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-gdscript-mastery/SKILL.md) — Typed exports, signals, and `class_name` fluency required before dependency injection and rock-testable components. - [godot-composition](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-composition/SKILL.md) — Core Has-A component model (game-focused sibling); this skill specializes the same rules for Apps/Tools/UI. #### Complements - [godot-signal-architecture](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-signal-architecture/SKILL.md) — Connect flags, ghost cleanup, and EventBus patterns Orchestrators use for upward wiring. - [godot-autoload-architecture](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-autoload-architecture/SKILL.md) — Boot order and ownership when composition needs a thin global service instead of scene-local state. - [godot-resource-data-patterns](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md) — Custom Resources and hot-swap `.tres` configs that feed data-driven components. - [godot-ui-containers](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ui-containers/SKILL.md) — Control trees that should signal intent upward while Orchestrators call down into layout. - [godot-ui-theming](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ui-theming/SKILL.md) — Theme/visual syncers stay separate from auth/form logic under the VLS pattern. - [godot-scene-management](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-scene-management/SKILL.md) — Scene swaps must re-inject exports and reconnect Orchestrator wiring without sideways sibling links. - [godot-testing-patterns](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-testing-patterns/SKILL.md) — Rock-test and signal spies that prove components stay context-agnostic. #### Downstream / consumers - [godot-save-load-systems](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-save-load-systems/SKILL.md) — Consumes Saveable-group persistence components for modular app state. - [godot-ability-system](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-ability-system/SKILL.md) — Ability nodes as child components sequenced by an Orchestrator without inheritance trees. - [godot-state-machine-advanced](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-state-machine-advanced/SKILL.md) — State nodes compose beside logic/visual syncers; FSM owns transitions, not sibling chatter. #### Master - [godot-master](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-master/SKILL.md) — Library router and mirrored module entry; open when discovering which Domain Skill owns a cross-cutting architecture concern.