# Component Dispatch (Phase 3 short-circuit)

> **TLDR**  -  When `taskType === "component"` (Figma URL in task description or instruction-driven figma workflow), multi-agent Phase 3 **does not run the TDD loop**. It delegates the entire phase to the enabled `ai-<platform>-toolkit` **marketplace plugin's** component skill (`create-component`, falling back to `create-ui-component`) via the Skill tool. Implementation lives in the plugin; multi-agent's job is classification, dispatch, and state report. The pipeline no longer bundles its own `figma-to-component` orchestrator  -  component skills live in one place, the plugin marketplace.

This doc is referenced from `$HOME/.claude/multi-agent-refs/phases/phase-3-dev.md`. Keeping it separate lets `phase-3-dev.md` remain tight (it's already the largest phase doc) and gives the orchestrator-report contract a stable URL for both Claude-side and Copilot-side implementations.

## Entry conditions

Phase 3 checks, in order:

1. `agent-state.json` has `taskType: "component"` (set by Phase 0 Step 7).
2. `agent-state.json` has a non-null `figmaUrl`.

Either missing → **HALT.** Log the anomaly to `agent-log.md` ("component dispatch
expected but state incomplete: <which field>") and stop with a user-visible error
naming the missing field and pointing at `phase0-exit-gate.mjs`.

> Earlier wording sent an incomplete-state component task down the generic TDD path,
> which contradicted the sentence that followed it: taking the generic path **is**
> skipping the Figma work.
> It also authorised the exact degradation that broke a real run  -  Phase 0 never
> wrote `agent-state.json`, so `taskType` was absent, so a Figma-driven screen was
> built through the generic path with no token-compliance check, no Code Connect
> publish and no component review. Spacing came out `16` where the frame said
> `Spacing/12`, and half the branch's commits were rework.
>
> The Phase 0 exit gate now prevents reaching Phase 3 in that state at all; this
> halt is the second line of defence. A component task that cannot be dispatched as
> one must fail loudly, because the generic path produces artefacts that look
> finished and are not.

## Plugin skill resolution

Scope first, then platform. A screen and a single component are different jobs and
the plugin ships a skill for each; routing a screen to the component skill is why one
run produced entities and a mapper but left the screen half-wired.

| `state.componentScope` | Meaning | iOS skill | Android skill |
|---|---|---|---|
| `screen` (default when the frame is a full screen, or the task names a screen) | Full clean-architecture vertical: Entity → Repository → Mapper → UseCase → LocalizedText → AnalyticsTracking → CoordinatorEvent → ViewModel → Scene → Preview, then verify | `ai-ios-toolkit:create-screen` | `ai-android-toolkit:create-screen` |
| `component` | One reusable UI component (Configuration / View / +Modifiers / Code Connect) | `ai-ios-toolkit:create-component` (fallback `create-ui-component`) | `ai-android-toolkit:create-component` (fallback `create-ui-component`) |
| `evolve` | Change an existing component | `evolve-component` (fallback `evolve-ui-component`) | same |

```
web, multi-*      → HALT with clear error (no web target)
```

Phase 0 Step 7 sets `state.componentScope` alongside `taskType`: a Figma frame that
is a full screen, or a task whose title names a screen, is `screen`; a frame that is
a single atom is `component`. When it cannot be decided, ask  -  do not default to
`component`, because the screen path is a superset and the component path silently
omits the wiring.

**Pre-implementation validation is not optional on iOS.** Before the create skill
runs, dispatch `<toolkit>:figma-validate` (probed like every dual-name skill; a toolkit that ships none records the skip) for the frame. It checks
registry presence, Code Connect strategy, **design token compliance**, dependency
readiness, atomic scope and already-implemented status in about ten seconds. Those
are precisely the checks whose absence produced guessed spacing and an unpublished
Code Connect binding. A `figma-validate` failure halts the dispatch.

**Dual-name resolution.** The public (`multi-agent-plugins`) and a corporate/private marketplace named the same skill differently  -  `create-component` vs `create-ui-component`. Dispatch tries `create-component` first; if it is not available in the current repo, tries `create-ui-component`. (Same dual-name rule applies when `taskType` maps to evolve → `evolve-component`/`evolve-ui-component`, or fix → `fix-bug`.)

If **neither** resolves, the platform's `ai-<platform>-toolkit` plugin is not enabled in this repo. **Halt with a user-visible error**: "component task requires the ai-<platform>-toolkit plugin enabled in this repo (`.claude/settings.local.json`)." Do not silently fall back to TDD  -  a component task ran through the bugfix path would produce wrong artefacts.

## Dispatch call

Emit `→ dispatching create-component <componentName>` via the progress contract, then invoke the resolved plugin skill via the **Skill tool** with:

- `figmaUrl`  -  primary argument (the plugin skill's `<figma-url>`)
- the component name + the analysis Section 6 (Bileşen Envanteri) row + Section 13.1 conventions as context (the plugin skill does not re-read the pipeline's analysis doc on its own  -  pass what it needs)
- `mode`  -  `"dev"` vs `"full"`, from `state.onlyDevelop`, so the plugin can elide tests/wiki on a Short run

Plugin skills are user-facing lifecycle skills; they do **not** accept an `agentState` path and do **not** write `agent-state.json`. State tracking therefore moves to the dispatch layer (next section).

## Subphase contract (dispatch-layer owned)

Because the plugin skill does not write pipeline state, **multi-agent's dispatch layer owns `state.phases["3"].subphases[]`**, not the skill. On dispatch, seed one coarse component-build subphase; on return, finalize it:

```json
{
  "id": "3.component",
  "name": "component-build (plugin: create-component)",
  "status": "done",
  "startedAt": "2026-04-16T18:12:02Z",
  "endedAt":   "2026-04-16T18:13:47Z",
  "buildStatus": "green",
  "testStatus": "green"
}
```

This is the accepted behavioral change from the old bundled orchestrator: subphase granularity is coarse (one component-build row) rather than the former 3.0-3.8 breakdown, because the plugin reports at its own granularity. If the plugin emits structured progress, the dispatch layer MAY map it to finer subphase rows, but coarse is the contract floor. Multi-agent's `phase-tracker` reads the array unchanged.

## Multi-repo report

When multi-repo mode is active (`state.projects.length > 1`), the dispatch layer passes `state.projects[]` to the plugin skill and sequences writes across the repo graph:

- Tokens, accessibility metadata, testing identifiers, localisation keys → `repos.common`
- View / Configuration / Modifiers / Preview → `repos.components`
- Wiki markdown → `repos.wiki` (if `wiki.mode === submodule` or `separate-repo`) or `repos.components/.wiki/components/` (if `wiki.mode === in-repo`)

Multi-agent does **not** split the component task into per-repo Phase 3 runs  -  the dispatch layer sequences the plugin invocation so token writes to `common` precede component reads in `components`. If the plugin skill is not repo-graph aware, dispatch runs it against `repos.components` and performs the `common` token/key writes itself around the call.

## Failure + resume

On failure (the plugin skill returns an unrecoverable build/test error, or the dispatch hits retry cap):

1. The dispatch layer persists `{error, buildLog, testLog}` to `state.phases["3"].errors[]`.
2. Multi-agent increments `state.phases["3"].retryCount`.
3. Retry re-invokes the plugin skill (it is idempotent on an existing component; it reconciles rather than duplicating). There is no bundled `phase-<N>` resume anymore.
4. Hard kill at `retryCount === 3` → surface the errors to the user, halt Phase 3. Do not loop indefinitely.

## Short-run behaviour

When the Phase 0 Step 7.5 depth picker answered Short (`state.onlyDevelop === true`), the dispatch layer passes `mode: "dev"` so the plugin skill can elide unit tests and wiki (structural + snapshot still required; wiki deferred to Phase 7). If the plugin does not honor a `mode` hint, dispatch simply skips the post-build wiki step itself.

Phase 4 runs in a Short run as it does in a Full one, and its reviewer count is **not** Phase 3's concern  -  the Step 1.77 scope gate decides that from diff risk, independently of `mode`. What the dispatch layer owes Phase 4 is the record of which plugin skill it delegated to, appended to `state.telemetry.skillCalls[]`, so the review can check the delivered component against the criteria that skill imposes.

## Cross-CLI behaviour (intentional divergence)

Component dispatch is **no longer byte-identical across CLIs** and that is by design (see `cross-cli-contract.md` section 1.1):

- **Claude Code**: dispatches to the enabled `ai-<platform>-toolkit` marketplace plugin via the Skill tool (this doc).
- **Copilot CLI**: has no plugin loader; it continues to use its standalone `~/.copilot/skills/figma-*` skill copies (frozen fallback). Copilot's resolution + progress lines follow those local skills.

What still MUST match across CLIs: the `taskType === "component"` classification, the `state.phases["3"].subphases[]` shape the dispatch layer writes, and the Short-run elision semantics. Figma-skill *inventory* parity is no longer enforced. `smoke-cross-cli-behavior.sh` asserts only the classification + state-shape axis for components.
