Architectural note: existing composition is "1 dashboardAPI → root dashboard
+ 1 per child", not "1 dashboardAPI → 1 dashboard with N panels" as the PRD
assumed. Each generated dashboard is laid out at template-authoring time
(explicit gridPos per panel inside config/<softwareType>.json); the composer's
job is to substitute per-instance templating variables and assemble the
cross-link list. So the PRD's "non-overlapping gridPos for N panels" lands as:
- perf: 50 children compose in <500ms (PRD N-1).
- uid-uniqueness: stableUid keyed on softwareType:nodeId never collides.
- byte-identical idempotency (PRD N-2): two consecutive compositions match.
- root links: one link per registered child.
No production code change — this slice just adds the perf/uniqueness/idempotency
guarantees as explicit tests so we can't regress.
Closes#35