thedivergentai/godot-composition-apps
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.
npx skills add https://github.com/thedivergentai/GD-Agentic-Skills --skill godot-composition-apps
| Root node / task | Route |
|------------------|-------|
| Control, EditorPlugin, tool window, settings dock, form UI | Stay here — Orchestrator + components |
| Player, Enemy, Weapon, Hitbox, gameplay CharacterBody | godot-composition — 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.
Before writing a script, ask: "If I attached this script to a literal rock, would it still function?"
AuthComponent on a rock allows the rock to log in. (Context Agnostic)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.
Treat the Root Node as an empty Backpack.
SubmitButton extends AnimatedButton extends BaseButton.AnimationComponent and HAS-A NetworkRequestComponent.| 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.
> MANDATORY: Read the matching script before implementing the pattern. Do not reinvent Orchestrator wiring inline.
MANDATORY first read — Attach-candidate-to-literal-rock harness that fails hard-coupled components early.
MANDATORY when creating any App/UI root — Signal-up / call-down wiring skeleton (0% business math).
MANDATORY for VLS — Logic emits state_changed; visuals/animations react without logic knowing AnimationPlayer/Theme.
Shared component lifecycle + dependency validation for app workers.
Typed export / registry injection so Orchestrators avoid brittle $ paths.
Context-agnostic clipboard worker — pairs with orchestrator toast pattern (see references).
Resource-backed config for tool settings and form defaults.
MANDATORY for saveable UI/tool state — Registers Saveable group + get_save_data() without putting I/O in visuals.
Ordered multi-step tool workflows (wizard pages, export pipelines) as child steps.
Only when an app/tool simulates entities; prefer godot-composition for real games.
Root script (LoginScreen.gd, UserProfile.gd, EditorPlugin dock root) is an Orchestrator:
MANDATORY: Extend patterns from 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 |
| Theme ownership | Separate theme component — never mutated inside form logic |
| Focus ownership | Orchestrator grants/releases Control focus; components never steal siblings' focus |
class_name on components; no untyped core architecture.@export var auth: AuthComponent (Inspector / %UniqueNames). NEVER get_node("Path/To/Child") for components.@export or function args.@export / %._on_signal delegators.Do not put save I/O or Theme mutation inside form Controls. Route through components:
add_to_group("Saveable") + get_save_data().grab_focus() after validation failures so logic stays Control-agnostic.# 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()
Prefer Engine.register_singleton() for lightweight non-Node services (Auth, Config) instead of dozens of Autoload Nodes [6].
MANDATORY comp_logic_visual_syncer.gd — logic never calls AnimationPlayer.play().
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. Do NOT Load when comp_orchestrator_base.gd covers your screen.
> 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.
has_method) that let composition work without deep inheritance trees.@export dependency injection that replaces brittle get_node paths in the Inspector.%UniqueName for Orchestrator-local Control/Button wiring without string path fragility..tres configs so values stay outside logic components.class_name fluency required before dependency injection and rock-testable components..tres configs that feed data-driven components.Take thedivergentai/godot-composition-apps from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.