Skip to content

WidgetForge

Summary: WidgetForge is the project's agent-drivable UMG authoring pipeline: declarative JSON specs compile into real WidgetBlueprints (WidgetForge.Apply), WidgetBlueprints export back into canonical specs (WidgetForge.Export), and widgets render headlessly to PNG for visual verification (WidgetForge.Render). A staleness gate makes human designers and agents safe co-authors of the same widgets: the widget asset is the source of truth, and Apply refuses to overwrite designer work it hasn't seen.

Why it exists

AI agents cannot drive the UMG Designer (drag-drop Slate UI), and UE Python cannot author widget trees (WidgetBlueprint.widget_tree is not reflected). The MCP bridge also blocks py console commands, so WidgetForge ships console commands backed by an editor-module C++ compiler (Source/ProjectEternalEditor/{Public,Private}/WidgetForge/). Prior-art survey (2026-07): three independent UE tools converged on the same JSON→regenerate architecture; none solved two-writer co-authoring. WidgetForge's model (export-first refresh + refuse-on-drift) follows the patterns that survived elsewhere (Slint, Android Studio layouts, W3C design tokens).

The co-authoring model (widget-as-truth)

Two writers touch the same widgets: a human in the UMG designer and an agent through specs. The contract that keeps them safe:

  • The widget asset is the source of truth. Specs in Tools/WidgetForge/specs/ are a full-fidelity, refreshable text mirror (and give binary UI assets reviewable git diffs).
  • Agents work Export-first: Export (refresh spec from widget) → edit spec → Apply.
  • The staleness gate makes the contract self-enforcing. Export stamps a tool-owned _meta block (sourceHash = hash of the widget's canonical tokenless export). Apply recomputes the hash of the current widget and refuses with status:"stale" on mismatch — a forgotten Export is a loud failure, never a silent clobber of designer edits. -Force overrides (audited as forcedOverwrite:true). After a successful Apply the spec's _meta is updated in place.
  • Apply rebuilds the widget tree from scratch (deterministic, idempotent) but never touches animations or the event graph; variable GUIDs are preserved by name, so bindings survive. Renames need "renamedFrom" on the node (one Apply) — GUIDs carry forward and animation bindings/graph references are fixed up; unmarked disappearing variables warn loudly.
  • Steady state: the designer opens any spec-managed WBP, edits anything, saves — done. The next agent Export folds those edits into the spec.

What can't round-trip (guarded, not silent): named-slot content (named_slots_unsupported refusal), tree-internal object pointers (export warns + skips). Everything else — any CPF_Edit property differing from its CDO, slots, classDefaults (diffed against the ParentClass CDO), tokens — round-trips byte-identically.

Commands

WidgetForge.Apply     <SpecJsonPath> [-Force]        # build/update WBP from spec (staleness-gated)
WidgetForge.Export    <AssetPath> <OutJsonPath> [<TokensPath>]   # widget -> canonical spec + _meta
WidgetForge.Render    <AssetPath> <W> <H> <OutPngPath>           # headless PNG (no PIE needed)
WidgetForge.RoundTrip <AssetPath> [<W> <H>]          # acceptance oracle: spec + pixel identity
WidgetForge.ApplyStyles <StylesJsonPath>             # CommonTextStyle-style Blueprint assets

Every call writes a JSON report to Saved/WidgetForge/last_result.jsonalways read it. Machine-readable status: ok, error, stale, pie_blocked, token_lint_failed, named_slots_unsupported, load_failed. All commands refuse during PIE (editor asset loading is disabled in play mode; pre-guard, Apply's create-path opened a modal "Overwrite Existing Object" dialog that silently blocked the game thread — the historical "MCP deadlock").

Apply wall-clock is ~0.9 s regardless of widget size (compile+save dominates; measured ×10 on a 5-widget and a 48-widget tree, 2026-07-02).

Tokens and styles

Tools/WidgetForge/tokens.json is the design-token source ("$token" in specs). Semantic duplicates must be aliases ("era-arcanum": "$arcane"); Apply/Export lint the file and fail on ambiguous duplicates, unknown refs, and cycles. Export reverse-maps values to $token/hex. Shared text looks live in style Blueprint assets (TextStyles.styles.json via ApplyStyles) — a surface that is simultaneously designer-native (Details panel) and agent-native (JSON).

Format reference

The spec format itself (widget nodes, slots, shorthands, CSS→UMG conversion rules, hard-won caveats) is documented in Tools/WidgetForge/SPEC_FORMAT.md. Design history and rejected alternatives (merge-based Apply, spec-as-truth, denylists): ImplementationDocs/WidgetForge_DesignerCoAuthoring.plan.md.