# State

dpg has no shared mutable state between pipeline steps at runtime. State is passed **forward only** via function return values and on-disk artifacts. The one exception is the `RunLog` module-level singleton which accumulates telemetry across all steps.

This document traces every state object: its shape, what triggers a state change, which functions consume it, and what side effects it produces.

---

## STATE: IngestedInputs

```
FILE: pipeline/step-01-input-ingestion/pipeline/ingestion.py
```

**Shape:**
```python
@dataclass
class IngestedInputs:
    user_prompt:  str | None
    images_dir:   Path | None
    image_paths:  list[Path]   # sorted list of image files in images_dir
```

**Created by:** `ingest()` — step-01

**Initial state:**
```python
IngestedInputs(user_prompt=None, images_dir=None, image_paths=[])
```

**After step-01 with prompt only:**
```python
IngestedInputs(
    user_prompt="A warehouse management system",
    images_dir=None,
    image_paths=[]
)
```

**After step-01 with images:**
```python
IngestedInputs(
    user_prompt="...",
    images_dir=Path("/path/to/images/"),
    image_paths=[Path("images/01_dashboard.png"), ...]
)
```

**Consumed by:**
- `orchestrator.generate_project()` — checks `images_dir is not None` to branch step-02 path
- `prd_generator.generate_from_prompt()` — reads `user_prompt`
- `prd_generator.generate_prd()` — reads `images_dir`
- `backend_gen.Orchestrator.run()` — reads `user_prompt`, `image_paths`
- `multi_page_service.generate_app_ir()` — reads `images_dir`, `user_prompt`

**Side effects:**
- Written to disk: `s01_out/ingested_inputs.json`

**What triggers a state change:** Nothing — `IngestedInputs` is immutable after construction.

---

## STATE: LoadedPRD

```
FILE: shared/media/prd_loader.py
```

**Shape:**
```python
@dataclass
class LoadedPRD:
    path:     Path
    text:     str            # full PRD text (markdown)
    images:   list[PRDImage] # embedded images (from PDF/PPTX source)
    ddl_path: Path | None    # path to schema.sql, set after step-02

@dataclass
class PRDImage:
    media_type: str  # "image/png", "image/jpeg", etc.
    data:       str  # base64-encoded bytes
```

**Created by:** `prd_generator.generate_from_prompt()` or `generate_prd()` — step-02

**Before step-02:** Does not exist.

**After step-02 (prompt mode):**
```python
LoadedPRD(
    path=Path("s02_out/full_prd.md"),
    text="# Warehouse Management System\n...",
    images=[],           # no embedded images in prompt mode
    ddl_path=Path("s02_out/schema.sql")
)
```

**After step-02 (image mode):**
```python
LoadedPRD(
    path=Path("s02_out/full_prd.md"),
    text="...",
    images=[PRDImage(media_type="image/png", data="iVBORw0KGgo...")],
    ddl_path=Path("s02_out/schema.sql")  # may be None if mantara sub-pipeline skips
)
```

**Consumed by:**
- `orchestrator` — checks `ddl_path is not None` (raises `ValueError` if None)
- `backend_gen.Orchestrator.run()` — reads `path` (ddl_file), `path` (prd_file)
- `multi_page_service.generate_app_ir()` — reads `text` (prd_text), `images_as_dicts` (prd_images)

**Side effects:**
- Written to disk: `s02_out/full_prd.md`, `s02_out/schema.sql`, `s02_out/metadata.json`

---

## STATE: api_manifest

```
FILE: pipeline/step-03-backend-generation/pipeline/backend_gen/manifest_generator.py
```

**Shape:**
```json
{
  "version": "1.0",
  "modules": [
    {
      "name": "orders",
      "prefix": "/orders",
      "endpoints": [
        {
          "method": "GET",
          "path": "/orders",
          "summary": "List all orders",
          "request_body": null,
          "response_schema": { "items": "Order" }
        }
      ]
    }
  ],
  "enums": {
    "OrderStatus": ["pending", "processing", "shipped", "delivered"]
  }
}
```

**Created by:** `manifest_generator.generate_manifest_json()` — step-03, sub-step 4/9

**Consumed by:**
- `multi_page_service.generate_app_ir()` — summarized for page detection context
- `react_gen.generate_react_pages()` — passed to `generate_page_react_code()` for API wiring
- `scaffolder.scaffold_frontend()` — for generating frontend API client config

**Side effects:**
- Written to disk: `project_root/api_manifest.json`, `s03_out/api_manifest.json`

---

## STATE: AppPlan

```
FILE: shared/schemas/app_plan.py
```

**Shape:**
```python
class AppPlan(StrictBase):
    app_name:      str
    pages:         list[PageNode]
    shared_state:  list[SharedStateField]
    default_route: str
    design_system: DesignSystem

class PageNode(StrictBase):
    page_id:       str         # snake_case, e.g. "order_list"
    page_title:    str
    route_path:    str         # "/orders"
    route_params:  list[str]   # ["id"] for "/orders/:id"
    image_indices: list[int]   # which encoded_images belong to this page
    description:   str
    navigates_to:  list[str]   # other page_ids this page links to

class DesignSystem(StrictBase):
    theme_mode:           "light" | "dark"
    color_primary:        str  # e.g. "#1677ff"
    font_family:          str
    font_size_base:       int
    border_radius:        int
    component_overrides:  dict
```

**Created by:** `page_detection_service.detect_pages()` — step-04 sub-step

**What triggers a state change:**
1. LLM response parsed via `extract_json_object()` + `AppPlan.model_validate()`
2. `sanitize_page_id()` normalizes each `page.page_id` in-place after detection

**Consumed by:**
- `generate_app_ir()` — iterates pages to generate IR bundles
- `generate_react_pages()` — accesses `app_plan.pages` for TSX generation
- `generate_router_code()` — reads pages + routes for deterministic App.tsx
- `generate_context_code()` — reads shared_state + design_system
- `scaffold_frontend()` — reads design_system for Ant Design theme config

---

## STATE: AppIRBundle

```
FILE: shared/schemas/app_ir.py
```

**Shape:**
```python
@dataclass
class AppIRBundle:
    app_plan:       AppPlan
    ir_pages:       list[IRPage]
    run_id:         str
    encoded_images: list[EncodedImage]

@dataclass
class IRPage:
    page_node:  PageNode
    ir_bundle:  IRBundle

@dataclass
class EncodedImage:
    path:        Path
    media_type:  str
    base64_data: str
```

**Created by:** `multi_page_service.generate_app_ir()` — step-04

**Consumed by:**
- `generate_react_pages()` — reads `ir_pages` to generate TSX per page
- `orchestrator` — writes IR JSON to disk, passes to step-05

**Side effects:**
- Written to disk: `s04_out/ir/app_plan.json`, `s04_out/ir/<page_id>_ir.json` × N

---

## STATE: IRBundle (12-section UI specification)

```
FILE: shared/schemas/ir_bundle.py
```

**Shape (top-level sections):**
```python
class IRBundle(StrictBase):
    page_ir:          PageIR          # page metadata, style, accessibility
    data_ir:          DataIR          # state fields, derived fields, custom types
    data_fetch_ir:    DataFetchIR     # runtime API calls, loading/error states
    data_model_ir:    DataModelIR     # entities, relationships
    behaviour_ir:     BehaviourIR     # events, actions, validation, side effects
    component_ir:     ComponentIR     # Ant Design component definitions
    layout_ir:        LayoutIR        # component tree + layout config
    navigation_ir:    NavigationIR    # tabs, modals, drawers, routes
    realtime_ir:      RealtimeIR      # timers, polling
    metadata:         MetadataIR      # version, generation info
    page_data_contract: PageDataContract | None  # external service table defs
    schema_ir:        SchemaIR | None # injected schema identification data
```

**Created by:** `ir_generation_service.generate_ir_bundle_for_page()` — step-04

**How it's created:** LLM generates JSON string → `extract_json_object()` → `IRBundle.model_validate()`

**Consumed by:** `react_generation_service.generate_page_react_code()` — the full bundle is serialized as JSON in the prompt sent to Sonnet for TSX generation.

**Side effects:**
- Written to disk: `s04_out/ir/<page_id>_ir.json`
- Written to disk: `project_root/ir/<page_id>_ir.json`

---

## STATE: MultiPageBundle

```
FILE: shared/schemas/multi_page_bundle.py
```

**Shape:**
```python
class MultiPageBundle(StrictBase):
    app_plan:     AppPlan
    pages:        list[PageBundle]
    router_code:  str          # App.tsx content (deterministic)
    context_code: str          # AppContext.tsx content (deterministic)
    run_id:       str

class PageBundle(StrictBase):
    page_node:  PageNode
    ir_bundle:  IRBundle
    react_code: str            # TSX source for this page
```

**Created by:** `generate_react_pages()` — step-05

**Consumed by:**
- `orchestrator` — calls `write_generated_files()` with `router_code`, `context_code`, `pages`

**Side effects (via orchestrator):**
- Written to disk: `frontend_dir/src/App.tsx`
- Written to disk: `frontend_dir/src/AppContext.tsx`
- Written to disk: `frontend_dir/src/pages/<PascalCase>Page.tsx` × N
- Written to disk: `s05_out/pages/<PascalCase>Page.tsx` × N
- Written to disk: `s05_out/AppContext.tsx`, `s05_out/App.tsx`

---

## STATE: RunLog (global singleton)

```
FILE: shared/run_log.py
```

**Lifetime:** Created at start of `generate_project()`, finalized in its `finally:` block. Module-level `_active` variable holds the reference. Cleared to `None` after finalization.

**Shape before finalization:**
```python
RunLog {
    run_id:      str,
    started_at:  str,
    _t0:         float,         # perf_counter() at creation
    duration_ms: None,          # set by finalize()
    steps:       list[StepEvent],
    _stack:      list[StepEvent],
    summary:     dict
}
```

**What triggers a state change:**
- `with run_log.step("name")` — appends new StepEvent, pushes to _stack
- `run_log.record_llm_call(...)` — appends LLMCall to top-of-stack StepEvent
- `run_log.finalize(output_dir)` — sets duration_ms, writes to disk

**Side effects (on `finalize()`):**
- Written: `master_out/run_log.json`
- Written: `master_out/run_log.md`

**Components that read from RunLog:**
- `timed_llm_call()` — calls `get_active().record_llm_call()`
- `BedrockLLMClient.generate()` — calls `get_active().record_llm_call()`
- `orchestrator` — reads `run_log.total_llm_calls`, `run_log.total_input_tokens`, etc. for final print

---

## Related source files

- [pipeline/step-01-input-ingestion/pipeline/ingestion.py](../pipeline/step-01-input-ingestion/pipeline/ingestion.py)
- [shared/media/prd_loader.py](../shared/media/prd_loader.py)
- [shared/schemas/app_plan.py](../shared/schemas/app_plan.py)
- [shared/schemas/ir_bundle.py](../shared/schemas/ir_bundle.py)
- [shared/schemas/app_ir.py](../shared/schemas/app_ir.py)
- [shared/schemas/multi_page_bundle.py](../shared/schemas/multi_page_bundle.py)
- [shared/run_log.py](../shared/run_log.py)
