Skip to content

Commit 995651f

Browse files
refactor(wiki-graph): components architecture
1 parent 66bb4c1 commit 995651f

37 files changed

Lines changed: 826 additions & 1257 deletions

GEMINI.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ This is an Angular/NX monorepo used for library research, prototyping, and build
2020
- Kebab-case for all file and folder names
2121
- Follow NX project conventions for imports and project boundaries
2222

23+
### Component Architecture
24+
- **UI Components**: Must ONLY have `input()` and `output()`. Never inject data services or manage external state directly.
25+
- **Container Components**: Responsible for wrapping UI components, composing layouts, rendering component structures and lists.
26+
- **Smart Components**: Responsible for knowing where data comes from (injecting state/data services like `GraphStateService`) and binding reactive state to containers and UI components.
27+
2328
### Research Workflows
2429

2530
This workspace has two research workflows, each managed by the `research-buddy` skill:

apps/wiki-graph/src/app/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Wiki Graph App
2+
3+
The `apps/wiki-graph/src/app` directory contains the main application architecture for the Wiki Connections Visualizer. It provides interactive visualization, search, filtering, and topological analysis of interlinked wiki markdown documents.
4+
5+
## Directory Structure
6+
7+
```text
8+
apps/wiki-graph/src/app/
9+
├── components/ # Component architecture (UI, Containers, Smart)
10+
│ ├── ui/ # Pure presentation UI components (Inputs & Outputs ONLY)
11+
│ ├── containers/ # Layout composition & wrapper container components
12+
│ └── smart/ # State-aware smart components (Service injectors)
13+
├── d3/ # D3 force simulation & SVG rendering engine
14+
├── models/ # Graph domain models & manifest types
15+
├── services/ # Parser service & reactive Signal state management
16+
├── app.config.ts # Application configuration & providers
17+
├── app.routes.ts # Route definitions
18+
└── README.md # Top-level application documentation
19+
```
20+
21+
## Component Architecture
22+
23+
1. **UI Components (`components/ui/`)**:
24+
- Pure presentational components receiving data strictly via `input()` signals and communicating user interactions strictly via `output()` events. No data/state services are injected.
25+
- Includes `GraphCanvasComponent`, `GraphToolbarUiComponent`, `NodeDetailUiComponent`.
26+
27+
2. **Container Components (`components/containers/`)**:
28+
- Structural composition components that assemble layout grids/containers, wrap UI components, and handle template rendering.
29+
- Includes `GraphViewportContainerComponent`.
30+
31+
3. **Smart Components (`components/smart/`)**:
32+
- State-aware orchestrators that inject data/state services (`GraphStateService`), query signals, and bind state inputs/action outputs.
33+
- Includes `WikiGraphPageComponent` (`smart/wiki-graph-smart/`).

apps/wiki-graph/src/app/app.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
<router-outlet />
1+
<router-outlet>
2+
</router-outlet>

apps/wiki-graph/src/app/app.routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export const appRoutes: Route[] = [
44
{
55
path: 'wiki-graph',
66
loadComponent: () =>
7-
import('./wiki-graph-page.component').then(
7+
import('./components/smart/wiki-graph-smart/wiki-graph-smart.component').then(
88
(m) => m.WikiGraphPageComponent
99
),
1010
},
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Component Architecture (UI / Containers / Smart)
2+
3+
This directory implements a strict three-tier component architecture for clarity, testability, and separation of concerns.
4+
5+
## Architectural Layers
6+
7+
### 1. UI Components (`components/ui/`)
8+
**Rule**: UI components can ONLY have `input()` signals and `output()` events. They must NEVER inject state/data services directly.
9+
10+
- **`GraphCanvasComponent` (`ui/graph-canvas/`)**:
11+
- Inputs: `graphData`, `visibleNodeIds`, `selectedNodeId`
12+
- Output: `nodeSelected`
13+
- **`GraphToolbarUiComponent` (`ui/graph-toolbar-ui/`)**:
14+
- Inputs: `nodeTypes`, `activeTypeFilters`, `searchQuery`, `activeTagFilter`, `allTags`, `visibleNodeCount`, `visibleEdgeCount`, `orphanCount`, `hubNodes`, `toolbarVisible`
15+
- Outputs: `typeToggled`, `searchChanged`, `tagChanged`, `orphanHighlighted`, `hubSelected`, `refreshRequested`, `toolbarVisibilityToggled`
16+
- **`NodeDetailUiComponent` (`ui/node-detail-ui/`)**:
17+
- Input: `node`
18+
- Output: `closed`
19+
20+
### 2. Container Components (`components/containers/`)
21+
**Rule**: Container components are responsible for wrapping UI components, composing layouts, and organizing lists/containers.
22+
23+
- **`GraphViewportContainerComponent` (`containers/graph-viewport-container/`)**:
24+
- Composes the interactive graph canvas, control toolbar UI, detail panel overlay, loading state, and error banner into a responsive viewport container.
25+
26+
### 3. Smart Components (`components/smart/`)
27+
**Rule**: Smart components know where data comes from. They inject state/data services (`GraphStateService`), bind reactive state to container/UI inputs, and map action outputs to service actions.
28+
29+
- **`WikiGraphPageComponent` (`smart/wiki-graph-smart/`)**:
30+
- Main page route orchestrator injecting `GraphStateService`.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<div class="wiki-graph-page">
2+
3+
<app-graph-canvas
4+
[graphData]="graphData()"
5+
[visibleNodeIds]="visibleNodeIds()"
6+
[selectedNodeId]="selectedNode()?.id ?? null"
7+
(nodeSelected)="nodeSelected.emit($event)"
8+
/>
9+
10+
<app-graph-toolbar-ui
11+
[nodeTypes]="nodeTypes()"
12+
[activeTypeFilters]="activeTypeFilters()"
13+
[searchQuery]="searchQuery()"
14+
[activeTagFilter]="activeTagFilter()"
15+
[allTags]="allTags()"
16+
[visibleNodeCount]="visibleNodeCount()"
17+
[visibleEdgeCount]="visibleEdgeCount()"
18+
[orphanCount]="orphanCount()"
19+
[hubNodes]="hubNodes()"
20+
[toolbarVisible]="toolbarVisible()"
21+
(typeToggled)="typeToggled.emit($event)"
22+
(searchChanged)="searchChanged.emit($event)"
23+
(tagChanged)="tagChanged.emit($event)"
24+
(orphanHighlighted)="orphanHighlighted.emit()"
25+
(hubSelected)="hubSelected.emit($event)"
26+
(refreshRequested)="refreshRequested.emit()"
27+
(toolbarVisibilityToggled)="toolbarVisibilityToggled.emit($event)"
28+
/>
29+
30+
<app-node-detail-ui
31+
[node]="selectedNode()"
32+
(closed)="detailClosed.emit()"
33+
/>
34+
35+
@if (isLoading()) {
36+
<div class="loading-overlay" role="status" aria-live="polite">
37+
<span class="spinner" aria-hidden="true"></span>Loading graph…
38+
</div>
39+
}
40+
41+
@if (error()) {
42+
<div class="error-banner" role="alert">
43+
<strong>Error:</strong> {{ error() }}
44+
</div>
45+
}
46+
47+
</div>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
:host { display: block; width: 100vw; height: 100vh; }
2+
3+
.wiki-graph-page {
4+
position: relative;
5+
width: 100%;
6+
height: 100%;
7+
background: #181825;
8+
color: #cdd6f4;
9+
overflow: hidden;
10+
}
11+
12+
app-graph-canvas {
13+
position: absolute;
14+
inset: 0;
15+
width: 100%;
16+
height: 100%;
17+
}
18+
19+
.loading-overlay {
20+
position: absolute;
21+
inset: 0;
22+
display: flex;
23+
align-items: center;
24+
justify-content: center;
25+
gap: 0.75rem;
26+
background: rgba(24, 24, 37, 0.8);
27+
z-index: 20;
28+
font-size: 1rem;
29+
color: #a6adc8;
30+
}
31+
32+
.spinner {
33+
width: 20px; height: 20px;
34+
border: 2px solid #585b70;
35+
border-top-color: #89b4fa;
36+
border-radius: 50%;
37+
animation: spin 0.8s linear infinite;
38+
}
39+
40+
@keyframes spin { to { transform: rotate(360deg); } }
41+
42+
.error-banner {
43+
position: absolute;
44+
top: 4rem; left: 50%;
45+
transform: translateX(-50%);
46+
background: #3b1219;
47+
border: 1px solid #f38ba8;
48+
border-radius: 6px;
49+
padding: 0.6rem 1.2rem;
50+
color: #f38ba8;
51+
font-size: 0.875rem;
52+
z-index: 20;
53+
max-width: 480px;
54+
text-align: center;
55+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
2+
import { GraphCanvasComponent } from '../../ui/graph-canvas/graph-canvas.component';
3+
import { GraphToolbarUiComponent } from '../../ui/graph-toolbar-ui/graph-toolbar-ui.component';
4+
import { NodeDetailUiComponent } from '../../ui/node-detail-ui/node-detail-ui.component';
5+
import type { GraphData, GraphNode, NodeType } from '../../../models/graph.models';
6+
7+
@Component({
8+
selector: 'app-graph-viewport-container',
9+
changeDetection: ChangeDetectionStrategy.OnPush,
10+
imports: [GraphCanvasComponent, GraphToolbarUiComponent, NodeDetailUiComponent],
11+
templateUrl: './graph-viewport-container.component.html',
12+
styleUrl: './graph-viewport-container.component.scss',
13+
})
14+
export class GraphViewportContainerComponent {
15+
graphData = input<GraphData | null>(null);
16+
visibleNodeIds = input<Set<string>>(new Set());
17+
selectedNode = input<GraphNode | null>(null);
18+
nodeTypes = input<NodeType[]>(['entity', 'concept', 'source']);
19+
activeTypeFilters = input<Set<NodeType>>(new Set());
20+
searchQuery = input<string>('');
21+
activeTagFilter = input<string | null>(null);
22+
allTags = input<string[]>([]);
23+
visibleNodeCount = input<number>(0);
24+
visibleEdgeCount = input<number>(0);
25+
orphanCount = input<number>(0);
26+
hubNodes = input<GraphNode[]>([]);
27+
toolbarVisible = input<boolean>(true);
28+
isLoading = input<boolean>(false);
29+
error = input<string | null>(null);
30+
31+
nodeSelected = output<string | null>();
32+
typeToggled = output<NodeType>();
33+
searchChanged = output<string>();
34+
tagChanged = output<string | null>();
35+
orphanHighlighted = output<void>();
36+
hubSelected = output<string>();
37+
refreshRequested = output<void>();
38+
toolbarVisibilityToggled = output<boolean>();
39+
detailClosed = output<void>();
40+
}

apps/wiki-graph/src/app/components/graph-canvas/graph-canvas.component.html

Lines changed: 0 additions & 6 deletions
This file was deleted.

apps/wiki-graph/src/app/components/graph-controls/graph-controls.component.html

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)