@donartcha/openlag 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +82 -0
  3. package/bin/openlag.js +2 -0
  4. package/dist/assets/arc-4YUHkXo3.js +1 -0
  5. package/dist/assets/architectureDiagram-3BPJPVTR-WeGmL7HM.js +36 -0
  6. package/dist/assets/blockDiagram-GPEHLZMM-CtV7ubAx.js +132 -0
  7. package/dist/assets/c4Diagram-AAUBKEIU-DqYDW5c3.js +10 -0
  8. package/dist/assets/channel-Tsel3-MK.js +1 -0
  9. package/dist/assets/chunk-2J33WTMH-BE8P9tjh.js +1 -0
  10. package/dist/assets/chunk-4BX2VUAB-Bi7oLGF5.js +1 -0
  11. package/dist/assets/chunk-55IACEB6-D9Xhxp_r.js +1 -0
  12. package/dist/assets/chunk-727SXJPM-Dz8jKE60.js +206 -0
  13. package/dist/assets/chunk-AQP2D5EJ-BzmM0IeH.js +231 -0
  14. package/dist/assets/chunk-FMBD7UC4-Cvl5dpcx.js +15 -0
  15. package/dist/assets/chunk-ND2GUHAM-Dz2efqnq.js +1 -0
  16. package/dist/assets/chunk-QZHKN3VN-CwblgSnQ.js +1 -0
  17. package/dist/assets/classDiagram-4FO5ZUOK-Bgm-_cW8.js +1 -0
  18. package/dist/assets/classDiagram-v2-Q7XG4LA2-Bgm-_cW8.js +1 -0
  19. package/dist/assets/cose-bilkent-S5V4N54A-h_A3nZUx.js +1 -0
  20. package/dist/assets/cytoscape.esm-D_LviqZs.js +331 -0
  21. package/dist/assets/dagre-BM42HDAG-CN_B2Doz.js +4 -0
  22. package/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  23. package/dist/assets/diagram-2AECGRRQ-C9TAFwjG.js +43 -0
  24. package/dist/assets/diagram-5GNKFQAL-BThljQLo.js +10 -0
  25. package/dist/assets/diagram-KO2AKTUF-bRPq25Se.js +3 -0
  26. package/dist/assets/diagram-LMA3HP47-BubLCIus.js +24 -0
  27. package/dist/assets/diagram-OG6HWLK6-CJpfhIsS.js +24 -0
  28. package/dist/assets/erDiagram-TEJ5UH35-6Xkza9wL.js +85 -0
  29. package/dist/assets/flowDiagram-I6XJVG4X-Bq_to3hX.js +162 -0
  30. package/dist/assets/ganttDiagram-6RSMTGT7-C3CmvYl7.js +292 -0
  31. package/dist/assets/gitGraphDiagram-PVQCEYII-C93LTfrl.js +106 -0
  32. package/dist/assets/graph-CAnANduQ.js +1 -0
  33. package/dist/assets/index-0RMQQ34p.css +1 -0
  34. package/dist/assets/index-ByxguSZe.js +729 -0
  35. package/dist/assets/infoDiagram-5YYISTIA-CMfuwygl.js +2 -0
  36. package/dist/assets/init-Gi6I4Gst.js +1 -0
  37. package/dist/assets/ishikawaDiagram-YF4QCWOH-CbJ5ojDF.js +70 -0
  38. package/dist/assets/journeyDiagram-JHISSGLW-C_Xz8YyT.js +139 -0
  39. package/dist/assets/kanban-definition-UN3LZRKU-GVv_iRMq.js +89 -0
  40. package/dist/assets/katex-DkKDou_j.js +257 -0
  41. package/dist/assets/layout-DGIYPm2g.js +1 -0
  42. package/dist/assets/linear-BNEtUH2J.js +1 -0
  43. package/dist/assets/mindmap-definition-RKZ34NQL-DIsL0XSF.js +96 -0
  44. package/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  45. package/dist/assets/pieDiagram-4H26LBE5-CSCTSOjk.js +30 -0
  46. package/dist/assets/quadrantDiagram-W4KKPZXB-CQQ9OaFY.js +7 -0
  47. package/dist/assets/requirementDiagram-4Y6WPE33-Cjn3la_S.js +84 -0
  48. package/dist/assets/sankeyDiagram-5OEKKPKP-DoVspvVc.js +40 -0
  49. package/dist/assets/sequenceDiagram-3UESZ5HK-UsoGmL4w.js +162 -0
  50. package/dist/assets/stateDiagram-AJRCARHV-DLmf7Dc8.js +1 -0
  51. package/dist/assets/stateDiagram-v2-BHNVJYJU-jkiDZ_3u.js +1 -0
  52. package/dist/assets/timeline-definition-PNZ67QCA-HfyRxZ8p.js +120 -0
  53. package/dist/assets/vennDiagram-CIIHVFJN-B6pM3L33.js +34 -0
  54. package/dist/assets/wardley-L42UT6IY-B-LdKtrI.js +173 -0
  55. package/dist/assets/wardleyDiagram-YWT4CUSO-BD45zhOu.js +78 -0
  56. package/dist/assets/xychartDiagram-2RQKCTM6-zsDMbUiS.js +7 -0
  57. package/dist/cli/openlag.js +1793 -0
  58. package/dist/index.html +14 -0
  59. package/index.html +13 -0
  60. package/package.json +84 -0
  61. package/scripts/cli/build.ts +34 -0
  62. package/scripts/cli/dev.ts +35 -0
  63. package/scripts/cli/generate.ts +92 -0
  64. package/scripts/cli/init.ts +427 -0
  65. package/scripts/cli/lint.ts +29 -0
  66. package/scripts/cli/openlag.ts +110 -0
  67. package/scripts/cli/vite-bin.ts +8 -0
  68. package/scripts/core/parser/diagnostic.ts +34 -0
  69. package/scripts/core/parser/normalizer.ts +27 -0
  70. package/scripts/core/parser/scanner.ts +30 -0
  71. package/scripts/core/parser/schemas.ts +23 -0
  72. package/scripts/core/parser/types.ts +30 -0
  73. package/scripts/core/parser.ts +127 -0
  74. package/scripts/generate-relations.ts +53 -0
  75. package/scripts/lint/lint-engine.ts +85 -0
  76. package/scripts/lint/lint-profiles.ts +49 -0
  77. package/scripts/lint/lint-rules.ts +174 -0
  78. package/scripts/lint/lint-types.ts +43 -0
  79. package/src/App.tsx +164 -0
  80. package/src/components/DocumentationView.tsx +905 -0
  81. package/src/components/GraphView.tsx +529 -0
  82. package/src/components/GuideView.tsx +535 -0
  83. package/src/components/ImpactView.tsx +365 -0
  84. package/src/components/MarkdownRenderer.tsx +120 -0
  85. package/src/components/OrphansView.tsx +360 -0
  86. package/src/components/SettingsView.tsx +146 -0
  87. package/src/core/generated/relation-definitions.ts +622 -0
  88. package/src/core/graph/GraphQueryLayer.ts +194 -0
  89. package/src/core/registry/ArtifactRegistry.ts +19 -0
  90. package/src/core/registry/RelationRegistry.ts +27 -0
  91. package/src/core/semantic/artifact-layers.ts +43 -0
  92. package/src/core/semantic/ownership-rules.ts +13 -0
  93. package/src/core/semantic/types.ts +11 -0
  94. package/src/index.css +121 -0
  95. package/src/lib/reportUtils.ts +59 -0
  96. package/src/main.tsx +10 -0
  97. package/src/store.ts +146 -0
  98. package/src/types.ts +77 -0
  99. package/vite.config.ts +31 -0
@@ -0,0 +1,174 @@
1
+ import { LintIssue, LintProfile } from './lint-types.js';
2
+ import { OpenLagData, ParsedArtifact, ParsedRelation, ParseError } from '../core/parser.js';
3
+ import { ArtifactRegistry } from '../../src/core/registry/ArtifactRegistry.js';
4
+ import { RelationRegistry } from '../../src/core/registry/RelationRegistry.js';
5
+
6
+ export function runLintRules(data: OpenLagData, profile: LintProfile): LintIssue[] {
7
+ const issues: LintIssue[] = [];
8
+
9
+ const getSeverity = (rule: keyof LintProfile, artifactStatus?: string) => {
10
+ let severity = profile[rule];
11
+ if (!severity || severity === 'off') return 'off';
12
+
13
+ // Downgrade severities based on drafting status
14
+ if (artifactStatus === 'draft') {
15
+ if (rule !== 'brokenRelation' && rule !== 'invalidYaml' && rule !== 'duplicateId') {
16
+ severity = 'info';
17
+ }
18
+ } else if (artifactStatus === 'in_progress') {
19
+ if (rule !== 'brokenRelation' && rule !== 'invalidYaml' && rule !== 'duplicateId') {
20
+ if (severity === 'error') severity = 'warning';
21
+ }
22
+ }
23
+ return severity;
24
+ };
25
+
26
+ const addIssue = (rule: keyof LintProfile, message: string, file?: string, artifactId?: string, artifactStatus?: string) => {
27
+ const severity = getSeverity(rule, artifactStatus);
28
+ if (severity && severity !== 'off') {
29
+ issues.push({ severity, rule, message, file, artifactId });
30
+ }
31
+ };
32
+
33
+ // 1. Invalid YAML
34
+ for (const error of data.errors) {
35
+ addIssue('invalidYaml', error.message, error.file);
36
+ }
37
+
38
+ // Build ID map
39
+ const artifactMap = new Map<string, ParsedArtifact[]>();
40
+ for (const artifact of data.artifacts) {
41
+ const list = artifactMap.get(artifact.id) || [];
42
+ list.push(artifact);
43
+ artifactMap.set(artifact.id, list);
44
+ }
45
+
46
+ // 2. Duplicate IDs
47
+ for (const [id, artifacts] of artifactMap.entries()) {
48
+ if (artifacts.length > 1) {
49
+ addIssue('duplicateId', `${id} appears in ${artifacts.length} files`, artifacts[0].file, id);
50
+ }
51
+ }
52
+
53
+ // 3. Artifact type and minimal fields
54
+ for (const artifact of data.artifacts) {
55
+ if (!ArtifactRegistry.isValid(artifact.type)) {
56
+ addIssue('invalidArtifactType', `Invalid artifact type: ${artifact.type}`, artifact.file, artifact.id, artifact.status);
57
+ }
58
+ if (!artifact.type || !artifact.title) {
59
+ addIssue('missingRequiredFields', `Artifact missing type or title`, artifact.file, artifact.id, artifact.status);
60
+ }
61
+ }
62
+
63
+ // Compute relation maps
64
+ const targets = new Set(data.artifacts.map(a => a.id));
65
+ const implementationsByReq = new Map<string, ParsedRelation[]>();
66
+ const testsByReq = new Map<string, ParsedRelation[]>();
67
+ const reqsByCode = new Map<string, ParsedRelation[]>();
68
+ const reqsByTest = new Map<string, ParsedRelation[]>();
69
+
70
+ for (const relation of data.relations) {
71
+ if (!RelationRegistry.isValid(relation.type)) {
72
+ addIssue('invalidRelationType', `Invalid relation type: ${relation.type}`, relation.file, relation.from);
73
+ }
74
+
75
+ // 4. Broken relations
76
+ if (!targets.has(relation.to)) {
77
+ addIssue('brokenRelation', `${relation.from} -> ${relation.to} target does not exist`, relation.file, relation.from);
78
+ }
79
+
80
+ // Populate relation graphs for rules
81
+ if (relation.type === 'IMPLEMENTS') {
82
+ const list = implementationsByReq.get(relation.to) || [];
83
+ list.push(relation);
84
+ implementationsByReq.set(relation.to, list);
85
+
86
+ const listCode = reqsByCode.get(relation.from) || [];
87
+ listCode.push(relation);
88
+ reqsByCode.set(relation.from, listCode);
89
+ }
90
+
91
+ if (relation.type === 'TESTS' || relation.type === 'VALIDATES') {
92
+ const list = testsByReq.get(relation.to) || [];
93
+ list.push(relation);
94
+ testsByReq.set(relation.to, list);
95
+
96
+ const listTest = reqsByTest.get(relation.from) || [];
97
+ listTest.push(relation);
98
+ reqsByTest.set(relation.from, listTest);
99
+ }
100
+ }
101
+
102
+ // Rule functions per artifact
103
+ for (const artifact of data.artifacts) {
104
+ const isDraft = artifact.status === 'draft';
105
+ const isClosed = artifact.status === 'closed';
106
+ const isDeprecated = artifact.status === 'deprecated';
107
+
108
+ if (isDeprecated) continue; // Skip rules for deprecated except broken relations (already checked above)
109
+
110
+ if (artifact.type === 'REQUIREMENT') {
111
+ const hasImplementation = implementationsByReq.has(artifact.id);
112
+ const hasTest = testsByReq.has(artifact.id);
113
+
114
+ if (!hasImplementation) {
115
+ addIssue('requirementWithoutImplementation', `${artifact.id} lacks implementation`, artifact.file, artifact.id, artifact.status);
116
+ }
117
+
118
+ if (!hasTest) {
119
+ addIssue('requirementWithoutTest', `${artifact.id} has no tests linked`, artifact.file, artifact.id, artifact.status);
120
+ }
121
+ }
122
+
123
+ if (artifact.type === 'CODE_ENTITY') {
124
+ const hasReq = reqsByCode.has(artifact.id);
125
+ if (!hasReq) {
126
+ addIssue('codeWithoutRequirement', `${artifact.id} has no requirement associated`, artifact.file, artifact.id, artifact.status);
127
+ }
128
+ }
129
+
130
+ if (artifact.type === 'TEST') {
131
+ const hasReq = reqsByTest.has(artifact.id);
132
+ if (!hasReq) {
133
+ addIssue('orphanArtifact', `${artifact.id} is a test without associated requirement`, artifact.file, artifact.id, artifact.status);
134
+ }
135
+ }
136
+
137
+ if (isClosed) {
138
+ const outgoing = data.relations.filter(r => r.from === artifact.id);
139
+ for (const rel of outgoing) {
140
+ const targetArts = artifactMap.get(rel.to);
141
+ if (targetArts && targetArts[0]) {
142
+ const targetStatus = targetArts[0].status;
143
+ if (targetStatus === 'draft' || targetStatus === 'in_progress') {
144
+ // Check relation rules: RELATES_TO, DOCUMENTS, JUSTIFIES don't break closed state
145
+ if (rel.type !== 'RELATES_TO' && rel.type !== 'DOCUMENTS' && rel.type !== 'JUSTIFIES') {
146
+ addIssue('closedArtifactWithPendingRelations', `${artifact.id} is closed but links to ${targetStatus} artifact ${rel.to} via ${rel.type}`, artifact.file, artifact.id, artifact.status);
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ if (!artifact.ownership || Object.keys(artifact.ownership).length === 0) {
153
+ addIssue('missingOwnership', `${artifact.id} is closed but has no ownership defined`, artifact.file, artifact.id, artifact.status);
154
+ }
155
+ }
156
+
157
+ if (artifact.type === 'API' && (!artifact.ownership || Object.keys(artifact.ownership).length === 0)) {
158
+ addIssue('missingOwnership', `API ${artifact.id} should have ownership defined`, artifact.file, artifact.id, artifact.status);
159
+ }
160
+
161
+ // Check layer semantics
162
+ if (artifact.layer === 'BUSINESS') {
163
+ const outgoing = data.relations.filter(r => r.from === artifact.id);
164
+ for (const rel of outgoing) {
165
+ const relSemantics = rel.category;
166
+ if (relSemantics === 'OPERATIONAL') {
167
+ addIssue('invalidLayerRelation', `Business layer artifact ${artifact.id} should not have OPERATIONAL relations (${rel.type})`, artifact.file, artifact.id, artifact.status);
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ return issues;
174
+ }
@@ -0,0 +1,43 @@
1
+ export type LintSeverity = 'error' | 'warning' | 'info' | 'off';
2
+
3
+ export interface LintIssue {
4
+ severity: LintSeverity;
5
+ rule: string;
6
+ artifactId?: string;
7
+ message: string;
8
+ file?: string;
9
+ }
10
+
11
+ export interface LintSummary {
12
+ errors: number;
13
+ warnings: number;
14
+ info: number;
15
+ }
16
+
17
+ export interface LintReport {
18
+ profile: string;
19
+ summary: LintSummary;
20
+ issues: LintIssue[];
21
+ }
22
+
23
+ export interface LintProfile {
24
+ duplicateId: LintSeverity;
25
+ invalidYaml: LintSeverity;
26
+ brokenRelation: LintSeverity;
27
+ missingRequiredFields: LintSeverity;
28
+ requirementWithoutImplementation: LintSeverity;
29
+ requirementWithoutTest: LintSeverity;
30
+ codeWithoutRequirement: LintSeverity;
31
+ closedArtifactWithPendingRelations: LintSeverity;
32
+ orphanArtifact: LintSeverity;
33
+ invalidRelationType: LintSeverity;
34
+ invalidArtifactType: LintSeverity;
35
+ invalidLayerRelation?: LintSeverity;
36
+ missingOwnership?: LintSeverity;
37
+ }
38
+
39
+ export interface LintConfig {
40
+ defaultProfile: string;
41
+ failOnWarnings: boolean;
42
+ profiles: Record<string, Partial<LintProfile>>;
43
+ }
package/src/App.tsx ADDED
@@ -0,0 +1,164 @@
1
+ import React, { useEffect } from 'react';
2
+ import { useStore } from './store';
3
+ import { GraphView } from './components/GraphView';
4
+ import { DocumentationView } from './components/DocumentationView';
5
+ import { ImpactView } from './components/ImpactView';
6
+ import { OrphansView } from './components/OrphansView';
7
+ import { GuideView } from './components/GuideView';
8
+ import { SettingsView } from './components/SettingsView';
9
+ import { Network, FileText, GitPullRequest, Settings, Database, AlertCircle, BookOpen } from 'lucide-react';
10
+
11
+ export default function App() {
12
+ const {
13
+ initializeStore,
14
+ versions,
15
+ currentVersionId,
16
+ setVersion,
17
+ activeView,
18
+ setView,
19
+ isLoading,
20
+ systemVersions
21
+ } = useStore();
22
+
23
+ useEffect(() => {
24
+ initializeStore();
25
+ }, [initializeStore]);
26
+
27
+ return (
28
+ <div className="flex h-screen w-screen overflow-hidden bg-[#0a0a0a] text-[#e0e0e0] font-sans selection:bg-emerald-500/30 print-block">
29
+ {/* Sidebar Nav */}
30
+ <nav className="w-16 flex flex-col items-center py-6 bg-[#0c0c0c] text-white/40 border-r border-white/10 shrink-0">
31
+ <div className="w-8 h-8 bg-white flex items-center justify-center rounded-sm mb-8">
32
+ <div className="w-4 h-4 border-2 border-black rotate-45"></div>
33
+ </div>
34
+
35
+ <div className="flex flex-col gap-6 w-full items-center">
36
+ <button
37
+ onClick={() => setView('graph')}
38
+ className={`transition-all hover:text-white hover:opacity-100 ${activeView === 'graph' ? 'text-white opacity-100' : 'opacity-40'}`}
39
+ title="Graph View"
40
+ >
41
+ <Network size={20} strokeWidth={1.5} />
42
+ </button>
43
+ <button
44
+ onClick={() => setView('docs')}
45
+ className={`transition-all hover:text-white hover:opacity-100 ${activeView === 'docs' ? 'text-white opacity-100' : 'opacity-40'}`}
46
+ title="Documentation Engine"
47
+ >
48
+ <FileText size={20} strokeWidth={1.5} />
49
+ </button>
50
+ <button
51
+ onClick={() => setView('impact')}
52
+ className={`transition-all hover:text-white hover:opacity-100 ${activeView === 'impact' ? 'text-white opacity-100' : 'opacity-40'}`}
53
+ title="Impact Analysis"
54
+ >
55
+ <GitPullRequest size={20} strokeWidth={1.5} />
56
+ </button>
57
+ <button
58
+ onClick={() => setView('orphans')}
59
+ className={`transition-all hover:text-white hover:opacity-100 ${activeView === 'orphans' ? 'text-red-400 opacity-100' : 'opacity-40'}`}
60
+ title="Traceability GAPs"
61
+ >
62
+ <AlertCircle size={20} strokeWidth={1.5} />
63
+ </button>
64
+ <button
65
+ onClick={() => setView('guide')}
66
+ className={`transition-all hover:text-white hover:opacity-100 ${activeView === 'guide' ? 'text-amber-400 opacity-100' : 'opacity-40'}`}
67
+ title="Usage Guide"
68
+ >
69
+ <BookOpen size={20} strokeWidth={1.5} />
70
+ </button>
71
+ </div>
72
+
73
+ <div className="mt-auto">
74
+ <button
75
+ onClick={() => setView('settings')}
76
+ className={`transition-all hover:text-white hover:opacity-100 ${activeView === 'settings' ? 'text-white opacity-100' : 'opacity-40'}`}
77
+ title="Settings"
78
+ >
79
+ <Settings size={20} strokeWidth={1.5} />
80
+ </button>
81
+ </div>
82
+ </nav>
83
+
84
+ {/* Main Content Area */}
85
+ <div className="flex-1 flex flex-col min-w-0">
86
+
87
+ {/* Top Header */}
88
+ <header className="h-16 bg-[#0f0f0f] border-b border-white/10 flex items-center px-8 justify-between shrink-0 z-10">
89
+ <div className="font-serif text-xl italic tracking-tight flex items-center gap-2">
90
+ OpenLAG <span className="text-xs font-mono opacity-50 ml-2 not-italic">| Lifecycle Engine</span>
91
+ </div>
92
+
93
+ <div className="flex items-center gap-6">
94
+ <div className="flex items-center gap-3">
95
+ <div className="flex flex-col items-end">
96
+ <span className="text-[10px] opacity-40 uppercase tracking-widest">Doc Snapshot</span>
97
+ </div>
98
+ <select
99
+ value={currentVersionId || ''}
100
+ onChange={(e) => setVersion(e.target.value)}
101
+ className="bg-[#0c0c0c] border border-white/20 text-xs font-mono text-emerald-400 rounded-sm px-3 py-1.5 outline-none cursor-pointer hover:bg-white/5 transition-colors focus:border-emerald-400"
102
+ >
103
+ {versions.map(v => (
104
+ <option key={v.id} value={v.id} className="bg-[#0c0c0c] text-white">
105
+ {v.name} ({new Date(v.timestamp).toLocaleDateString()})
106
+ </option>
107
+ ))}
108
+ </select>
109
+ </div>
110
+
111
+ <div className="h-6 w-[1px] bg-white/10" />
112
+
113
+ <div className="flex items-center gap-3 group relative">
114
+ <div className="p-2 bg-emerald-500/10 rounded-full border border-emerald-500/20">
115
+ <Database size={14} className="text-emerald-400" />
116
+ </div>
117
+ <div className="flex flex-col">
118
+ <span className="text-[10px] opacity-40 uppercase tracking-widest leading-none">System State</span>
119
+ <div className="flex items-center gap-2 mt-1">
120
+ <span className="text-[10px] text-white/80 font-bold tracking-tight">
121
+ {systemVersions.length} Components Active
122
+ </span>
123
+ <div className="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-pulse shadow-[0_0_8px_rgba(16,185,129,0.5)]" />
124
+ </div>
125
+ </div>
126
+
127
+ {/* Tooltip on hover */}
128
+ <div className="absolute top-full right-0 mt-2 w-64 bg-[#0f0f0f] border border-white/10 p-4 rounded-sm shadow-2xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50">
129
+ <div className="text-[9px] uppercase tracking-widest text-[#888] font-bold mb-3 border-b border-white/5 pb-2">Active Inventory</div>
130
+ <div className="space-y-2">
131
+ {systemVersions.map(sv => (
132
+ <div key={sv.id} className="flex justify-between items-center text-[10px]">
133
+ <span className="text-white/60">{sv.component}</span>
134
+ <span className="font-mono text-emerald-500/60">{sv.version}</span>
135
+ </div>
136
+ ))}
137
+ {systemVersions.length === 0 && (
138
+ <div className="text-white/20 italic">No system components logged.</div>
139
+ )}
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </header>
145
+
146
+ {/* View Renderer */}
147
+ <main className="flex-1 relative overflow-hidden">
148
+ {isLoading && !currentVersionId ? (
149
+ <div className="absolute inset-0 flex items-center justify-center bg-white/50 backdrop-blur-sm z-50">
150
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
151
+ </div>
152
+ ) : null}
153
+
154
+ {activeView === 'graph' && <GraphView />}
155
+ {activeView === 'docs' && <DocumentationView />}
156
+ {activeView === 'impact' && <ImpactView />}
157
+ {activeView === 'orphans' && <OrphansView />}
158
+ {activeView === 'guide' && <GuideView />}
159
+ {activeView === 'settings' && <SettingsView />}
160
+ </main>
161
+ </div>
162
+ </div>
163
+ );
164
+ }