# FEATURE_DATA & EMBEDDED_ARTIFACTS Build Reference

> Reference for step-05b-deploy.md — complete mapping specification for the interactive HTML.

## FEATURE_DATA Object

```javascript
const FEATURE_DATA = {
  metadata: {
    applicationName: master.application_name,
    applicationId: master.feature_id,
    version: master.version,
    createdAt: ISO_TIMESTAMP,
    analysisMode: master.metadata.analysisMode || "interactive" // always "interactive" — analysis is always interactive
  },
  cadrage: {
    // CONTEXT SECTION — merged from problem/asIs/toBe (lean cadrage)
    context: {
      problem: master.cadrage.problem || "",           // flat string from feature.json
      trigger: master.cadrage.trigger || "",            // flat string from feature.json
      currentSituation: master.cadrage.asIs || "",      // flat string from feature.json
      desiredSituation: master.cadrage.toBe || "",      // flat string from feature.json
      painPoints: (master.cadrage.stakeholders || []).flatMap(s => s.painPoints || []),
      acceptanceCriteria: (master.cadrage.acceptanceCriteria || []).map(c => c.criterion)
    },
    scope: {
      // CONVERT feature.json keys to HTML keys:
      // mustHave → vital, shouldHave → important, couldHave → optional, outOfScope → excluded
      // CRITICAL: Preserve per-item `module` assignment from coverageMatrix (NOT modules[0])
      vital: (master.cadrage.globalScope?.mustHave || master.cadrage.coverageMatrix?.filter(i => i.category === "mustHave") || [])
        .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
      important: (master.cadrage.globalScope?.shouldHave || master.cadrage.coverageMatrix?.filter(i => i.category === "shouldHave") || [])
        .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
      optional: (master.cadrage.globalScope?.couldHave || master.cadrage.coverageMatrix?.filter(i => i.category === "couldHave") || [])
        .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null }),
      excluded: (master.cadrage.globalScope?.outOfScope || master.cadrage.coverageMatrix?.filter(i => i.category === "outOfScope") || [])
        .map(item => typeof item === 'string' ? { name: item, description: "", module: null } : { name: item.item || item, description: item.notes || "", module: item.module || null })
    },
    risks: (master.cadrage.risks || []).map(r => ({
      description: r.description,
      probability: r.probability,
      impact: r.impact,
      mitigation: r.mitigation
    })),
    stakeholders: (master.cadrage.stakeholders || []).map(s => ({
      role: s.role,
      function: s.function,
      tasks: s.tasks || [],
      frequency: s.frequency || "",
      access: s.involvement || "",
      frustrations: (s.painPoints || []).join("\n")
    }))
  },
  modules: [
    // FOR EACH module in master.modules[]:
    {
      code: module.code,                              // PascalCase key (e.g., "GestionTemps")
      name: module.name || module.code,               // Display name (e.g., "Gestion du temps") — MANDATORY
      description: module.description || "",
      featureType: module.featureType || "data-centric",
      priority: module.priority || "must",
      estimatedComplexity: module.estimatedComplexity || "medium",
      entities: module.entities || [],
      anticipatedSections: module.anticipatedSections || [],
      dependencies: module.dependencies || [],
      dependents: module.dependents || []
    }
  ],
  moduleSpecs: {
    // CRITICAL: Must have ONE entry per module with ALL module data
    "{moduleCode}": {
      useCases: module.specification.useCases || [],
      businessRules: module.analysis.businessRules || [],
      // ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated from canonical format
      entities: (module.analysis.entities || []).map(ent => ({
        name: ent.name,
        description: ent.description || "",
        attributes: (ent.attributes || []).length > 0
          ? ent.attributes.map(a => ({ name: a.name, description: a.description || "" }))
          : (ent.fields || []).map(f => ({ name: f.name, description: f.description || f.type || "" })),
        relationships: (ent.relationships || []).length > 0
          ? ent.relationships.map(r =>
              typeof r === 'string' ? r : `${r.target} (${r.type}) - ${r.description || ""}`)
          : (ent.fields || []).filter(f => f.foreignKey)
              .map(f => `${f.foreignKey.table} (N:1) - ${f.description || f.name}`)
      })),
      permissions: module.specification.permissions || [],
      apiEndpoints: module.specification.apiEndpoints || []
    }
  },
  consolidation: {
    integrations: master.consolidation.integrations || [],
    sharedEntities: master.consolidation.sharedEntities || [],
    sequenceDiagrams: master.consolidation.e2eFlows || []
  },
  handoff: {
    complexity: master.handoff.complexity,
    implementationStrategy: master.handoff.implementationStrategy,
    moduleOrder: master.handoff.moduleOrder,
    filesToCreate: master.handoff.filesToCreate,
    brToCodeMapping: master.handoff.brToCodeMapping,
    apiEndpointSummary: master.handoff.apiEndpointSummary
  }
};
```

### Build Process

1. Extract metadata from master feature.json
2. Extract cadrage from master feature.json (CONVERT scope keys)
3. Extract stakeholders from master.stakeholders
4. Iterate ALL modules and populate moduleSpecs (THIS IS CRITICAL — empty moduleSpecs = BUG)
5. For EACH module, extract: useCases, businessRules, entities, permissions, apiEndpoints
6. Extract consolidation data (integrations, shared entities, E2E flows)
7. Extract handoff section (complexity, strategy, module order, file counts)

## EMBEDDED_ARTIFACTS Object

> **CRITICAL:** The wireframes object is keyed by moduleCode (NOT a flat array).
> Field names MUST be renamed from feature.json: `mockupFormat` → `format`, `mockup` → `content`.
> The HTML renderer reads `wf.format` and `wf.content` — using feature.json names will produce EMPTY mockups.

```javascript
const EMBEDDED_ARTIFACTS = {
  wireframes: {
    // PER-MODULE keyed object (NOT a flat array)
    // FOR EACH module: extract from specification.uiWireframes[] OR specification.wireframes[] (SAFETY NET)
    // IMPORTANT: The agent may write wireframes under either key name — always check BOTH
    [moduleCode]: (moduleFeature.specification.uiWireframes || moduleFeature.specification.wireframes || []).map(wf => ({
      screen: wf.screen || wf.name || wf.title || wf.id || "",  // SAFETY NET: fallback name/title/id → screen
      section: wf.section || "",                      // e.g. "list"
      format: wf.mockupFormat || "ascii",             // RENAME: mockupFormat → format
      content: wf.mockup || wf.ascii || wf.content || "",  // SAFETY NET: mockup/ascii/content → content
      svgContent: null,                              // Populated by SVG generation step (see wireframe-svg-style-guide.md)
      description: wf.description || "",
      elements: wf.elements || [],                    // [{ id, type, label }] or ["DataGrid", ...]
      actions: wf.actions || [],                      // [{ trigger, action }] or ["filter", ...]
      // SAFETY NET: convert object → array format if agent used { "Header": "PageHeader" } instead of [{ wireframeElement, reactComponent }]
      componentMapping: Array.isArray(wf.componentMapping) ? wf.componentMapping
        : typeof wf.componentMapping === 'object' && wf.componentMapping !== null
          ? Object.entries(wf.componentMapping).map(([k, v]) => ({ wireframeElement: k, reactComponent: v }))
          : [],
      layout: typeof wf.layout === 'object' ? wf.layout : null,  // SAFETY NET: ignore string layout ("grid")
      permissionsRequired: wf.permissionsRequired || []
    }))
  },
  e2eFlows: (master.consolidation?.e2eFlows || []).map(flow => ({
    name: flow.name,
    diagram: flow.steps.map(s => `${s.action}(${s.module})`).join(" ──→ "),
    steps: flow.steps || [],
    actors: [...new Set(flow.steps.map(s => s.permission?.split(".")[0]).filter(Boolean))].join(", "),
    modules: [...new Set(flow.steps.map(s => s.module))].join(" → ")
  })),
  dependencyGraph: {
    nodes: (master.modules || []).map(m => ({
      id: m.code, label: m.code, type: m.featureType || "data-centric"
    })),
    edges: (master.dependencyGraph?.edges || []).map(e => ({
      from: e.from, to: e.to, description: e.description || ""
    }))
  }
};
```

### Artifact Gathering

1. For EACH module: read `specification.uiWireframes[]` OR `specification.wireframes[]` (check BOTH keys — agent may use either), **rename fields** (`mockupFormat`→`format`, `mockup`/`ascii`/`content`→`content`, `screen`/`name`/`title`→`screen`), store under `wireframes[moduleCode]`
2. Read master's `consolidation.e2eFlows[]` and build e2eFlows array with diagram generation
3. Read master's `dependencyGraph` and build nodes/edges
4. Serialize as JSON with 2-space indentation
5. **SVG Wireframe Enrichment** (after wireframe extraction): For EACH wireframe with `content` (ASCII) and `svgContent === null`, generate SVG using parallel Task(sonnet) agents. See [wireframe-svg-style-guide.md](wireframe-svg-style-guide.md) for the complete prompt template and orchestration process. If generation fails for any wireframe → leave `svgContent` as null (graceful fallback to ASCII).

## Placeholder Replacement

1. Serialize FEATURE_DATA as JSON (2-space indent)
2. Serialize EMBEDDED_ARTIFACTS as JSON (2-space indent)
3. Replace `{{FEATURE_DATA}}` with serialized FEATURE_DATA
4. Replace `{{EMBEDDED_ARTIFACTS}}` with serialized EMBEDDED_ARTIFACTS
5. Replace `{{APPLICATION_NAME}}`, `{{APPLICATION_ID}}`, `{{VERSION}}`, `{{CREATED_AT}}`
6. Verify: grep for `{{` to confirm all placeholders replaced
