This post builds on Part 30. Until now, Bausteinsicht diagrams were pure visualization — you could see a system exists, but there was no way into it. With the link field on elements (docToolchain/Bausteinsicht#483), that changes: every element can carry a link that lands as a clickable link attribute on the shape during draw.io export.

That turns a diagram into a real entry point into the surrounding documentation landscape — a jump to an ADR, a Jira ticket, or a Confluence page, straight from the shape.

Not to be confused with the ADR registry from Part 11 (specification.decisions, navigable via bausteinsicht adr list/show). The link field here is simpler: a raw URL field directly on the element, with no dedicated registry — for anything you want to link externally, not just structured ADRs.

Any element can optionally get a link field:

"api": {
  "kind": "container", "title": "REST API", "technology": "Go",
  "link": "examples/teil_31_adr.html"
}

On the next bausteinsicht sync, this value lands as the link attribute on the <object> in the draw.io XML — in the diagram, the shape becomes directly clickable and opens the link in the browser.

An ADR as a Standalone Page

Instead of linking to some external URL, this example shows something more tangible: a self-written Architecture Decision Record, built to HTML with Asciidoctor and embedded directly in this tutorial post.

The ADR describes exactly the rule demonstrated below — why drill-down links take priority over user-defined links:

asciidoctor examples/teil_31_adr.adoc -o examples/teil_31_adr.html

The content pipeline (infra/builder/build-adr-html.sh) handles this automatically on every build — analogous to the existing draw.io sync step. Every examples/*_adr.adoc file is built to HTML and served as a static asset.

Embedded, it looks like this:

No page change needed — the linked ADR is readable right here, exactly as it would appear when clicking the api shape in the diagram.

What happens when an element is both the scope of a detail view (which automatically gets a drill-down link) and carries a user-defined link? Both would claim the same shape.

SituationResult

Only a user-defined link

Link is preserved, points to the given URL

Only drill-down (element is scope of a view)

Automatic link to the detail view (data:page/id,view-<viewID>)

Both at once

Drill-down wins — the user-defined link is discarded, sync warns

The reasoning is in the embedded ADR above — in short: drill-down is navigation within the diagram series and is expected to take precedence over a jump to the outside.

The Example Model

teil_31.jsonc models a small online shop with four elements that show four different link use cases:

  • onlineshop.gatewaylink to an external GitHub issue (docToolchain/Bausteinsicht#483, the ticket that introduced this feature). No conflict, the link survives the export.

  • onlineshop.apilink to a whole page, examples/teil_31_adr.html. No conflict, the link survives the export.

  • onlineshop.payment — is scope of the payment-details view and has a link. Here the priority rule kicks in: the drill-down link wins, and sync prints a warning.

  • onlineshop.dblink with an anchor on an absolute URL, https://paul-fleischmann.com/projekte/bausteinsicht/tutorial/examples/teil_31_adr.html#_modellelement_database_postgresql, jumps straight to the section describing the Database component instead of the top of the page.

{
  // Teil 31: Klickbare Links in exportierten Diagrammen — Online Shop Beispiel
  "specification": {
    "elements": {
      "actor":     { "notation": "Actor" },
      "system":    { "notation": "Software System", "container": true },
      "container": { "notation": "Container",        "container": true }
    },
    "relationships": {
      "uses": { "notation": "uses" }
    }
  },
  "model": {
    "customer": { "kind": "actor", "title": "Customer" },
    "onlineshop": {
      "kind": "system", "title": "Online Shop",
      "children": {
        // Use Case: externer Ticket-Link — verweist auf das GitHub-Issue,
        // das dieses Feature eingeführt hat. Kein Konflikt, der Link bleibt
        // im Export erhalten.
        "gateway": {
          "kind": "container", "title": "API Gateway", "technology": "Go",
          "link": "https://github.com/docToolchain/bausteinsicht/issues/483"
        },
        // Klarer Fall: user-definierter Link auf eine ganze Seite, kein
        // Konflikt — dieses Element ist nirgends der `scope` einer View,
        // der Link bleibt also im draw.io-Export erhalten wie modelliert.
        "api": {
          "kind": "container", "title": "REST API", "technology": "Go",
          "link": "examples/teil_31_adr.html"
        },
        // Priority-Demo: dieses Element ist gleichzeitig `scope` der
        // "payment-details"-View UND hat einen user-definierten Link.
        // Der Drill-Down-Link gewinnt (ADR-009) — sync gibt eine Warnung aus.
        "payment": {
          "kind": "container", "title": "Payment Service", "technology": "Go",
          "link": "examples/teil_31_adr.html#_entscheidung"
        },
        // Use Case: Deep-Link mit Anchor — verweist nicht nur auf die Seite,
        // sondern direkt auf den Abschnitt, der diese Komponente beschreibt.
        // Anders als beim `api`-Element ist der Link hier absolut (volle
        // Produktions-URL): beim Inline-SVG-Export (siehe unten) überlebt
        // nur eine absolute URL unverändert — ein relativer Link würde gegen
        // den internen Pfad der draw.io-CLI aufgelöst und liefe ins Leere.
        "db": {
          "kind": "container", "title": "Database", "technology": "PostgreSQL",
          "link": "https://paul-fleischmann.com/projekte/bausteinsicht/tutorial/examples/teil_31_adr.html#_modellelement_database_postgresql"
        }
      }
    }
  },
  "relationships": [
    { "from": "customer",              "to": "onlineshop.gateway", "label": "kauft ein",            "kind": "uses" },
    { "from": "onlineshop.gateway",     "to": "onlineshop.api",     "label": "routet zu",             "kind": "uses" },
    { "from": "onlineshop.api",         "to": "onlineshop.payment", "label": "delegiert Zahlung",     "kind": "uses" },
    { "from": "onlineshop.payment",     "to": "onlineshop.db",      "label": "schreibt Transaktion",  "kind": "uses" }
  ],
  "views": {
    "context":    { "title": "System Context", "include": ["customer", "onlineshop"] },
    "containers": { "title": "Container View", "scope": "onlineshop", "include": ["customer", "onlineshop.*"] },
    "payment-details": {
      "title": "Payment Service — Details",
      "scope": "onlineshop.payment",
      "include": ["onlineshop.api", "onlineshop.payment", "onlineshop.db"]
    }
  }
}

Running bausteinsicht sync

bausteinsicht sync --model teil_31.jsonc

The output shows the priority warning for onlineshop.payment:

WARNING: element onlineshop.payment: user-defined link overridden by drill-down link (ADR-009)
Forward (model → draw.io): 14 added, 0 updated, 0 deleted

The resulting draw.io XML shows the difference between all four cases directly:

<!-- onlineshop.gateway — external ticket link survives -->
<object label="API Gateway" link="https://github.com/docToolchain/bausteinsicht/issues/483" ...>

<!-- onlineshop.api — user-defined page link survives -->
<object label="REST API" link="examples/teil_31_adr.html" ...>

<!-- onlineshop.payment — drill-down wins, user link was discarded -->
<object label="Payment Service" link="data:page/id,view-payment-details" ...>

<!-- onlineshop.db — deep link with anchor, absolute URL survives -->
<object label="Database" link="https://paul-fleischmann.com/projekte/bausteinsicht/tutorial/examples/teil_31_adr.html#_modellelement_database_postgresql" ...>

Result in the Diagram

You can find the draw.io file here: teil_31.drawio

In the draw.io viewer, all four shapes are clickable, but each with a different target:

  • API Gateway opens the GitHub issue in a new tab.

  • REST API opens the embedded ADR — the same page shown further up.

  • Payment Service instead jumps to the payment-details view — drill-down navigation stays intact even though a link field is also set in the model.

  • Database opens the same ADR as REST API, but scrolled straight to the section describing the Database component instead of the top of the page.

Generated PNG files:

containers
context
payment-details

Generated PlantUML diagrams:

Diagram
Diagram
Diagram

The interactive viewer.diagrams.net iframe above loads the raw .drawio XML at runtime in the browser — that only works if the file is publicly reachable at the given URL. There’s an alternative that needs no external service and no network round trip at all: bausteinsicht export --image-format svg uses the real draw.io CLI (not Kroki), and it preserves link attributes as real <a xlink:href> elements in the SVG. Embedded as inline markup directly in the HTML — not as <img src="…​svg">, which would swallow the links — individual shapes in it are genuinely clickable, with no JavaScript and no external service.

The content pipeline (infra/builder/generate-drawio-svg-inline.sh) generates this on every build:

bausteinsicht export --image-format svg --output out/

For the Container View, the embedded result looks like this:

Online Shop
REST API
[Go]
Database
[PostgreSQL]
API Gateway
[Go]
Payment Service
[Go]
Customer
kauft ein
routet zu
delegiert Zahlung
schreibt Transaktion
← System Context
Container View

Source: site/content/projekte/bausteinsicht/tutorial/examples/teil_31.jsonc
Last synced: 2026-08-10 03:49
Generated by Bausteinsicht
Legend
Actor
Container
Software System
Text is not SVG - cannot display

Two limitations compared to the interactive viewer:

  • Only absolute URLs work — anchors included. API Gateway (https://github.com/…​;) is correctly clickable, and so is Database (https://paul-fleischmann.com/…​teil_31_adr.html#_modellelement_database_postgresql) — the click lands right in the section describing the Database component. REST API, on the other hand, has a relative link (examples/teil_31_adr.html, no domain) — the draw.io CLI resolves that during export against its own install directory, not against the page the SVG will later be embedded in:

    <a xlink:href="file:///opt/drawio/resources/app.asar/drawio/src/main/webapp/examples/teil_31_adr.html">

    In the browser, that click goes nowhere. For relative links to work here, they’d need to be modeled as absolute URLs — with or without an anchor, that part doesn’t matter as long as the domain is there.

  • Drill-down links are lost entirely. In the interactive viewer, Payment Service has a clickable jump to the payment-details view (data:page/id,…​). That internal URL scheme isn’t navigable in a browser, so the draw.io CLI leaves the shape without an <a> wrapper altogether during SVG export. Multi-page navigation between views only works in the interactive iframe.

So this approach only pays off when every relevant link is an absolute URL and no drill-down navigation is needed — for example, a single, self-contained diagram with external ticket or doc links.

SituationRecommendationExample above

Element has an associated ADR

link to the built ADR HTML page

onlineshop.api

Element is the subject of an open ticket

link directly to the Jira/GitHub issue

onlineshop.gateway

Only one specific section of a page is relevant

link with an anchor (#section-id) instead of the whole page

onlineshop.db

Element has extensive documentation in Confluence

link to the Confluence page — works exactly like the GitHub issue link, just with a different URL

Element is scope of a detail view

Don’t set link — drill-down wins anyway (ADR-009)

onlineshop.payment

What Comes Next

This closes another chapter of the Bausteinsicht tutorial series: from pure visualization (Part 1) to diagrams that link directly into the rest of the documentation landscape.

The most important entry points for quick reference:

Official documentation: User Manual · Tutorial on doctoolchain.org