# FEATURE_DATA & EMBEDDED_ARTIFACTS Build Reference

> Reference for `/ba-generate-html` skill (step-02-build-data) — complete mapping specification for the interactive HTML.
> **Canonical field mapping reference:** See also `data-mapping.md` for the authoritative field name mapping between module JSON files and HTML data objects.

## 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 index.json
      trigger: master.cadrage.trigger || "",            // flat string from index.json
      currentSituation: master.cadrage.asIs || "",      // flat string from index.json
      desiredSituation: master.cadrage.toBe || "",      // flat string from index.json
      painPoints: (master.cadrage.stakeholders || []).flatMap(s => s.painPoints || []),
      acceptanceCriteria: (master.cadrage.acceptanceCriteria || []).map(c => c.criterion)
    },
    scope: {
      // CONVERT index.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 })
    },
    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
    // FLAT-FILE: Data comes from collected_data.modules[moduleCode] (flat files), NOT module index.json
    "{moduleCode}": {
      // const mod = collected_data.modules[moduleCode];
      useCases: mod.usecases?.useCases || [],
      businessRules: mod.rules?.rules || [],
      // ENTITY SAFETY NET: map fields[] → attributes[] if agent deviated from canonical format
      entities: (mod.entities?.entities || []).map(ent => ({
        name: ent.name,
        description: ent.description || "",
        attributes: (ent.attributes || []).length > 0
          ? ent.attributes.map(a => ({ name: a.name, type: a.type || "string", required: a.required || false, description: a.description || "" }))
          : (ent.fields || []).map(f => ({ name: f.name, type: f.type || "string", required: f.required || false, description: f.description || "" })),
        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: mod.permissions?.matrix || mod.permissions?.permissions || [],
      apiEndpoints: mod.usecases?.apiEndpoints || []
    }
  },
  consolidation: {
    integrations: master.consolidation.crossModuleInteractions || [],
    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 index.json
2. Extract cadrage from master cadrage.json (CONVERT scope keys)
3. Extract stakeholders from master cadrage.stakeholders
4. Iterate ALL modules and populate moduleSpecs (THIS IS CRITICAL — empty moduleSpecs = BUG)
5. For EACH module, read FLAT FILES from `collected_data.modules[moduleCode]`:
   - `entities.json` → entities
   - `rules.json` → businessRules
   - `usecases.json` → useCases
   - `permissions.json` → permissions
   - `screens.json` → wireframes
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 module JSON files: `mockupFormat` → `format`, `mockup` → `content`.
> The HTML renderer reads `wf.format` and `wf.content` — using module JSON file names will produce EMPTY mockups.

```javascript
const EMBEDDED_ARTIFACTS = {
  wireframes: {
    // PER-MODULE keyed object (NOT a flat array)
    // FLAT-FILE: Read from screens.json in each module directory (collected in step-01)
    // const mod = collected_data.modules[moduleCode];
    // const screens = mod.screens?.screens || [];
    [moduleCode]: screens
      .filter(s => s.wireframe || s.mockup || s.mockupFormat)
      .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 `screens.json` flat file from `collected_data.modules[moduleCode].screens`, filter screens with wireframe data, **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
