@startuml !include <C4/C4_Container> Container(website, "Semantic Anchors Website", "Vite SPA", "Statische Website") Container(data, "Static Data", "JSON, AsciiDoc", "Anker, Kategorien, Metadata") Container(build, "Build Pipeline", "Node.js, GitHub Actions", "Generiert JSON, deployed zu GitHub Pages") System_Ext(github, "GitHub Repository", "Source Code, Issues, PRs") System_Ext(ghpages, "GitHub Pages", "Hosting") Rel(build, data, "Generiert", "Node.js Scripts") Rel(build, website, "Baut", "Vite") Rel(website, data, "Lädt", "fetch API") Rel(build, ghpages, "Deployed zu", "GitHub Actions") Rel(github, build, "Triggert", "Push Event") @enduml
Enthaltene Bausteine:
| Baustein | Verantwortung | Schnittstelle |
|---|---|---|
Semantic Anchors Website |
Präsentation, Interaktion, Navigation |
Browser (HTML/CSS/JS) |
Static Data |
Speicherung von Anker-Content und Metadata |
JSON Files, AsciiDoc Files |
Build Pipeline |
Transformation, Validierung, Deployment |
GitHub Actions, Node.js Scripts |
@startuml
package "Semantic Anchors Website" {
[App] as app
[Router] as router
[State Manager] as state
package "UI Components" {
[Card Grid Visualizer] as cardgrid
[Role Filter] as rolefilter
[Search Bar] as search
[Anchor Details View] as anchorview
[Language Switcher] as lang
[Theme Toggle] as theme
}
package "Data Layer" {
[API Client] as api
[AsciiDoc Renderer] as asciidoc
[i18n Module] as i18n
}
package "Utilities" {
[LocalStorage Manager] as storage
[URL Manager] as url
[Analytics (optional)] as analytics
}
}
[Static JSON Files] as json
[AsciiDoc Files] as adoc
app --> router
app --> state
router --> cardgrid
router --> anchorview
state --> rolefilter
state --> search
cardgrid --> api
rolefilter --> api
search --> api
anchorview --> asciidoc
api --> json
asciidoc --> adoc
lang --> i18n
theme --> storage
rolefilter --> storage
url --> router
@enduml
Komponenten-Beschreibung:
Verantwortung: * Initialisierung der Anwendung * Setup von Router, State Manager * Lädt initiale Daten (categories.json, roles.json)
Schnittstellen: * Input: Browser Load Event * Output: Initialisierte Komponenten
Implementierung:
// src/main.js
import { Router } from './router';
import { StateManager } from './state';
import { ApiClient } from './api';
class App {
async init() {
this.api = new ApiClient();
this.state = new StateManager();
this.router = new Router(this.state);
await this.loadInitialData();
this.router.start();
}
async loadInitialData() {
const [categories, roles, anchors] = await Promise.all([
this.api.getCategories(),
this.api.getRoles(),
this.api.getAnchors()
]);
this.state.set({ categories, roles, anchors });
}
}Verantwortung: * URL-basierte Navigation * Deep-Linking Support * History Management
Schnittstellen: * Input: URL Changes, Navigation Events * Output: Component Rendering
Routes:
* / → Home (Card Grid)
* /anchor/:id → Anchor Details
* /category/:id → Category View
* /role/:id → Role View
* /search?q=… → Search Results
Verantwortung: * Zentrale State-Verwaltung * Reaktive Updates * Filter- und Search-State
State-Struktur:
{
anchors: Anchor[],
categories: Category[],
roles: Role[],
filters: {
selectedRoles: string[],
selectedCategories: string[]
},
search: {
query: string,
results: Anchor[]
},
ui: {
language: 'en' | 'de',
theme: 'light' | 'dark'
}
}Verantwortung: * Rendert Kategorien als Card Grid mit Category Sections * Responsive Layout via CSS Grid (Karte öffnet Details-Modal) * Responsive Layout via CSS Grid
Technologie: Vanilla JavaScript, CSS Grid (keine externe Library)
Anmerkung: Ersetzt die ursprünglich geplante Apache ECharts Treemap (ADR-003) aufgrund fundamentaler Usability-Probleme (Text-Truncation, schlechter Kontrast, Viewport-Cut-off). Entscheidung dokumentiert in ADR-005.
Implementierung:
// src/components/card-grid.js
export class CardGrid {
constructor(containerEl, data) {
this.container = containerEl;
this.render(data);
}
render(categories) {
this.container.innerHTML = categories
.map(cat => this.renderSection(cat))
.join('');
}
renderSection(category) {
const cards = category.anchors.map(a => this.renderCard(a)).join('');
return `<section class="category-section" data-category="${category.id}">
<h2 class="category-heading">${category.name}</h2>
<div class="card-grid">${cards}</div>
</section>`;
}
renderCard(anchor) {
return `<article class="anchor-card" data-anchor="${anchor.id}">
<h3>${anchor.name}</h3>
<p class="anchor-description">${anchor.description || ''}</p>
</article>`;
}
}Verantwortung: * Multi-Select Dropdown für Rollen * Filter-Anwendung auf Anker-Liste * Persistierung in localStorage
UI:
<div class="role-filter">
<label>Filter by Role:</label>
<select multiple>
<option value="software-developer">Software Developer</option>
<option value="architect">Architect</option>
<!-- ... -->
</select>
<button class="clear-filter">Clear</button>
</div>Verantwortung: * Echtzeit-Suche mit Debounce * Relevanz-Scoring * Ergebnis-Highlighting
Implementierung:
export class SearchBar {
constructor(anchors) {
this.anchors = anchors;
this.index = this.buildIndex(anchors);
}
search(query, debounce = 300) {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
const results = this.index
.search(query)
.sort((a, b) => b.score - a.score);
this.onResults(results);
}, debounce);
}
buildIndex(anchors) {
// Tokenize names, proponents, tags
return anchors.map(anchor => ({
...anchor,
tokens: this.tokenize(anchor)
}));
}
}Verantwortung: * Anzeige eines einzelnen Ankers * AsciiDoc-Rendering * Related Anchors Navigation
Komponenten: * Header (Name, Full Name) * Metadata (Proponents, Categories, Roles) * Content (Core Concepts, When to Use) * Related Anchors (Links)
Verantwortung: * Konvertiert .adoc → HTML * Syntax-Highlighting * Caching
Implementierung:
import Asciidoctor from 'asciidoctor';
export class AsciiDocRenderer {
constructor() {
this.asciidoctor = Asciidoctor();
this.cache = new Map();
}
async render(filePath) {
if (this.cache.has(filePath)) {
return this.cache.get(filePath);
}
const response = await fetch(filePath);
const content = await response.text();
const html = this.asciidoctor.convert(content, {
safe: 'safe',
attributes: {
'source-highlighter': 'highlight.js'
}
});
this.cache.set(filePath, html);
return html;
}
}Verantwortung: * Lädt JSON-Daten * Error Handling * Caching
Endpunkte:
* GET /data/anchors.json
* GET /data/categories.json
* GET /data/roles.json
* GET /data/metadata.json
* GET /docs/anchors/{id}.adoc
Verantwortung: * Lädt Übersetzungen (en.json, de.json) * Übersetzt UI-Texte * Sprach-Wechsel
Implementierung:
export class I18n {
constructor(lang = 'en') {
this.lang = lang;
this.translations = {};
}
async load(lang) {
this.lang = lang;
this.translations = await fetch(`/i18n/${lang}.json`).then(r => r.json());
}
t(key) {
return this.translations[key] || key;
}
}Verantwortung: * Persistierung von User-Präferenzen * Theme, Language, Filter
Keys:
* semantic-anchors:theme → 'light' | 'dark'
* semantic-anchors:lang → 'en' | 'de'
* semantic-anchors:filters → JSON
@startuml
package "Build Pipeline" {
[Extract Metadata Script] as extract
[Validate Anchors Script] as validate
[Generate JSON Script] as generate
[Vite Build] as vite
[Deploy Script] as deploy
}
[AsciiDoc Files] as adoc
[JSON Files] as json
[dist/ folder] as dist
[GitHub Pages] as ghpages
adoc --> extract
extract --> validate
validate --> generate
generate --> json
json --> vite
vite --> dist
dist --> deploy
deploy --> ghpages
@enduml
Scripts:
Funktion: Liest alle .adoc Files, extrahiert Attributes
Input: docs/anchors/*.adoc
Output: anchors.yml (temporär)
Technologie: Node.js, @asciidoctor/core
Funktion: Prüft ob required Attributes vorhanden sind
Validierungen:
* id, name, categories, roles sind Pflicht
* categories existieren in categories.yml
* roles existieren in roles.yml
* Keine Duplikate
Funktion: Generiert JSON-Files für Website
Output:
* website/public/data/anchors.json
* website/public/data/categories.json
* website/public/data/roles.json
* website/public/data/metadata.json
Funktion: Bundelt JS/CSS, Minifiziert, Optimiert
Output: website/dist/
Features: * Tree-Shaking * Code-Splitting * Asset Optimization
@startuml
class Anchor {
+id: string
+name: string
+fullName: string
+categories: string[]
+roles: string[]
+proponents: Proponent[]
+coreConcepts: string[]
+whenToUse: string[]
+relatedAnchors: string[]
+tags: string[]
+filePath: string
}
class Category {
+id: string
+name: string
+nameDE: string
+description: string
+anchorCount: number
+anchors: string[]
+color: string
}
class Role {
+id: string
+name: string
+nameDE: string
+description: string
+anchorCount: number
+anchors: string[]
+icon: string
}
class Proponent {
+name: string
+publication: string
+year: number
+url: string
}
Anchor "1" -- "*" Category : categorized by
Anchor "1" -- "*" Role : relevant for
Anchor "1" *-- "*" Proponent : has
Anchor "*" -- "*" Anchor : related to
@enduml