ADR-009: Page-Based Drill-Down Navigation
Status
Accepted
Context
Bausteinsicht renders views as draw.io diagrams and lets architects navigate between abstraction levels (context → container → component). Early specification text (UC-7, AC-7.1) and a Key Design Decision in CLAUDE.md described this as single-page semantic zoom: all views on one draw.io canvas, where zooming in reveals the next level of detail and the parent view stays visible (smaller) above for orientation.
The implementation went a different way: one draw.io page per view, with sync-generated cross-page links on elements that have a deeper view, and a generated "back" button to return to the parent. This ADR records that the page-based approach is the accepted design and explains why, resolving the contradiction the documentation audit (#422) surfaced.
Evaluated Approaches
Approach A: Single-page semantic zoom (the original vision)
All views laid out on one canvas; drilling down is zooming; the parent stays on screen.
-
Keeps the parent visible for orientation
-
draw.io has no native semantic zoom / level-of-detail — this would require simulating it by scaling and positioning nested content, a fragile hack
-
Fights bidirectional sync: a single canvas mixing all levels is hard to diff and reconcile against the model
Approach B: One page per view + cross-page links (implemented)
Each view is its own draw.io page; sync adds page links to drill-down elements and a back-navigation button.
-
draw.io pages and page links are native features
-
Each page maps cleanly to one view, which the sync engine already resolves — diff/reconcile stays simple
-
Leaving the page loses the "parent visible" orientation (mitigated by the generated back button)
Weighted Pugh Matrix
Rating scale: -1 = worse than reference, 0 = same, +1 = better. Reference option: A (single-page zoom).
| Criterion | Weight | A: Single-page zoom (Ref) | B: Page-based |
|---|---|---|---|
Native draw.io support |
5 |
0 |
+1 |
Bidirectional-sync robustness |
4 |
0 |
+1 |
Implementation effort |
4 |
0 |
+1 |
Orientation (parent visible while drilling) |
3 |
0 |
-1 |
Per-level export/print |
2 |
0 |
+1 |
Weighted Results
| Criterion | A: Single-page zoom (Ref) | B: Page-based |
|---|---|---|
Native draw.io support (×5) |
0 |
+5 |
Bidirectional-sync robustness (×4) |
0 |
+4 |
Implementation effort (×4) |
0 |
+4 |
Orientation (×3) |
0 |
-3 |
Per-level export/print (×2) |
0 |
+2 |
Total |
0 |
+12 |
Decision
Accepted — page-based drill-down (Approach B).
Each view is rendered as its own draw.io page. Sync generates a cross-page link on every element that has a deeper view, and a "back" navigation button on child pages. This works with draw.io’s native page model instead of against it, and keeps the bidirectional sync simple (one page ↔ one view).
Consequences
Positive
-
Uses native draw.io pages and links — no fragile semantic-zoom emulation
-
Clean page ↔ view mapping keeps forward/reverse sync tractable
-
Each level exports and prints as a self-contained page
Negative
-
The parent view is not visible while viewing a child level; orientation relies on the generated back button and the page list
Risk
The lost "parent visible" orientation is a usability trade-off, not a correctness or security concern; it creates no new Chapter 11 risk and is explicitly accepted without a tracked risk.
Implementation
-
Page per view and cross-page links:
internal/sync/forward.go(pageID := "view-" + viewID,data:page/id,links) -
Back-navigation button:
internal/sync/forward.go(generatednav-back-cell)
Extension: User-Defined Element Links (#483)
Elements may carry an optional link field in the JSONC model, pointing to an external URL or document anchor (e.g. "link": "architecture.adoc#sec-backend"). This supports clickable diagram elements in SVG-based documentation pipelines.
Priority rule: Drill-down links take priority over user-defined links. When both exist for the same element, the drill-down link (data:page/id,…) is applied last and overwrites the user-defined value. A sync warning is emitted to alert the user.
Rationale: drill-down navigation is the primary navigation mechanism established by this ADR. Overriding it silently via a user-defined link would break diagram navigation without a visible signal.
Reverse sync: link is never read back from draw.io. It is model-authoritative; draw.io edits to the link attribute are overwritten on the next sync.
Extension: Doc/ADR Drilldown Icons (#543)
The element’s own shape link (Extension #483, above) can only ever point to one place at a time — and the priority rule means a drill-down link silently wins over a user-defined link when both apply to the same element. This extension gives the model↔documentation mapping its own, always-clickable carrier, independent of the shape’s own link attribute:
-
link(existing field): in addition to (optionally) owning the element’s shape link, it now also gets a small dedicated icon of its own, so it stays reachable even when a drill-down link owns the shape. -
decisions(existing field,element.go/badge.godecision badges): each decision badge is now a clickable icon, wrapped in its own<object link="…">, resolving to the referencedspecification.decisions[].filewhen present. A decision without afilestill renders its badge, just not clickable. -
docLinks(new field,Element.DocLinks/View.DocLinks,internal/model/types.go): a typed array of{type, href, title}entries —typeis a free string (not a closed schema enum) with conventional valuesarc42,adr,persona,prd,spec; each renders as its own icon with a type-specific glyph. Elements get a row of icons in their top-right corner; a view’s owndocLinksrender as a row on the page itself (positioned below the metadata/legend boxes, mirroring their placement convention — there is no notion of a "free diagram corner" in this codebase, only the max Y/X of existing content).
Mechanism: each icon is its own top-level <object id="doclink-…" link="…"> (never a bare <mxCell>, which cannot carry a link attribute in this codebase’s draw.io dialect), positioned via mxGraph’s parent ID attribute — either the element’s own on-page cell ID (element-level icons) or "1" (page-absolute, for view-level icons). Old icons are removed and recreated on every sync for elements (their position is a pure function of element width and icon count, so this is idempotent); the view-level row’s position is captured once and reused on subsequent syncs, avoiding drift. Implementation: internal/sync/doclinks.go.
Exclusion from reverse sync: icon cells are identified by the doclink- ID prefix and excluded from unmanaged-element detection and connector/relationship extraction in internal/sync/diff.go, the same way nav-back- back-navigation buttons are (#205) — otherwise reverse sync would try to import them as new model elements.
PNG degradation: PNG export discards link attributes entirely (same limitation the element shape’s own link already has, see Extension #483) — the icon shapes and glyphs still render, just without click behavior, giving a visible, non-interactive hint rather than silently vanishing.
Reverse sync: like link, none of link/decisions/docLinks icon state is ever read back from draw.io; the model is authoritative and icons are fully regenerated from it each sync.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.