tenbo-dashboard 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.
- package/README.md +32 -0
- package/bin/tenbo-dashboard.mjs +67 -0
- package/index.html +12 -0
- package/package.json +75 -0
- package/scripts/compute-metrics.test.ts +35 -0
- package/scripts/compute-metrics.ts +46 -0
- package/scripts/next-id.test.ts +128 -0
- package/scripts/next-id.ts +183 -0
- package/scripts/validate-cli.test.ts +152 -0
- package/scripts/validate-cli.ts +181 -0
- package/src/App.tsx +152 -0
- package/src/ErrorBoundary.tsx +25 -0
- package/src/api/client.ts +41 -0
- package/src/api/lib/frontmatterScan.ts +37 -0
- package/src/api/lib/health/agingTodos.test.ts +32 -0
- package/src/api/lib/health/agingTodos.ts +97 -0
- package/src/api/lib/health/archCompliance.test.ts +26 -0
- package/src/api/lib/health/archCompliance.ts +53 -0
- package/src/api/lib/health/collectAll.test.ts +49 -0
- package/src/api/lib/health/collectAll.ts +78 -0
- package/src/api/lib/health/config.test.ts +45 -0
- package/src/api/lib/health/config.ts +54 -0
- package/src/api/lib/health/coupling.test.ts +40 -0
- package/src/api/lib/health/coupling.ts +66 -0
- package/src/api/lib/health/deadCode.test.ts +23 -0
- package/src/api/lib/health/deadCode.ts +76 -0
- package/src/api/lib/health/docDrift.test.ts +54 -0
- package/src/api/lib/health/docDrift.ts +99 -0
- package/src/api/lib/health/hotspotFiles.test.ts +49 -0
- package/src/api/lib/health/hotspotFiles.ts +70 -0
- package/src/api/lib/health/importGraph.test.ts +29 -0
- package/src/api/lib/health/importGraph.ts +51 -0
- package/src/api/lib/health/layerFiles.test.ts +46 -0
- package/src/api/lib/health/layerFiles.ts +40 -0
- package/src/api/lib/health/redundancy.test.ts +24 -0
- package/src/api/lib/health/redundancy.ts +162 -0
- package/src/api/lib/health/testCoverage.test.ts +33 -0
- package/src/api/lib/health/testCoverage.ts +55 -0
- package/src/api/lib/health/types.test.ts +29 -0
- package/src/api/lib/health/types.ts +85 -0
- package/src/api/lib/http.ts +34 -0
- package/src/api/lib/metrics.test.ts +81 -0
- package/src/api/lib/metrics.ts +78 -0
- package/src/api/lib/metricsRefresh.test.ts +54 -0
- package/src/api/lib/metricsRefresh.ts +147 -0
- package/src/api/lib/phases.test.ts +76 -0
- package/src/api/lib/phases.ts +48 -0
- package/src/api/lib/relationships.ts +65 -0
- package/src/api/lib/repoRoot.ts +12 -0
- package/src/api/lib/tenboFs.layerContent.test.ts +36 -0
- package/src/api/lib/tenboFs.ts +237 -0
- package/src/api/lib/tenboFs.v2.test.ts +86 -0
- package/src/api/lib/tenboFs.workspaceContent.test.ts +42 -0
- package/src/api/lib/validator.docUpdate.test.ts +99 -0
- package/src/api/lib/validator.phases.test.ts +98 -0
- package/src/api/lib/validator.relationships.test.ts +126 -0
- package/src/api/lib/validator.specLinks.test.ts +96 -0
- package/src/api/lib/validator.ts +311 -0
- package/src/api/lib/validator.v2.test.ts +55 -0
- package/src/api/lib/yamlOrdered.ts +60 -0
- package/src/api/plugin.ts +31 -0
- package/src/api/routes/items.ts +25 -0
- package/src/api/routes/layerContent.ts +22 -0
- package/src/api/routes/layerDocs.ts +22 -0
- package/src/api/routes/open.ts +18 -0
- package/src/api/routes/related.ts +11 -0
- package/src/api/routes/reorder.ts +15 -0
- package/src/api/routes/state.ts +23 -0
- package/src/api/routes/watch.ts +31 -0
- package/src/css-modules.d.ts +4 -0
- package/src/hooks/useApiPatch.ts +9 -0
- package/src/hooks/useLayerContent.ts +25 -0
- package/src/hooks/usePrompt.ts +23 -0
- package/src/hooks/useTenboState.ts +35 -0
- package/src/main.tsx +13 -0
- package/src/prompts/populateLayer.ts +12 -0
- package/src/router/routes.ts +98 -0
- package/src/router/useHashRoute.test.ts +67 -0
- package/src/router/useHashRoute.ts +15 -0
- package/src/styles.css +228 -0
- package/src/test-setup.ts +1 -0
- package/src/types.ts +202 -0
- package/src/ui/DotGrid.module.css +7 -0
- package/src/ui/DotGrid.tsx +87 -0
- package/src/ui/EmptyState.module.css +11 -0
- package/src/ui/EmptyState.tsx +10 -0
- package/src/ui/FindingModal/EvidenceSection.tsx +51 -0
- package/src/ui/FindingModal/FindingModal.module.css +31 -0
- package/src/ui/FindingModal/FindingModal.test.tsx +36 -0
- package/src/ui/FindingModal/HeadlineSection.tsx +17 -0
- package/src/ui/FindingModal/RelatedFindingsSection.tsx +29 -0
- package/src/ui/FindingModal/SuggestionSection.tsx +13 -0
- package/src/ui/FindingModal/index.tsx +36 -0
- package/src/ui/HealthPage/Digest.tsx +29 -0
- package/src/ui/HealthPage/FindingRow.tsx +21 -0
- package/src/ui/HealthPage/HealthPage.module.css +45 -0
- package/src/ui/HealthPage/HealthPage.test.tsx +49 -0
- package/src/ui/HealthPage/HealthPage.tsx +66 -0
- package/src/ui/HealthPage/LayerCard.tsx +36 -0
- package/src/ui/HealthPage/SeverityIcon.tsx +29 -0
- package/src/ui/HealthPage/SeverityLegend.tsx +27 -0
- package/src/ui/HealthPage/severity.test.ts +46 -0
- package/src/ui/HealthPage/severity.ts +21 -0
- package/src/ui/ItemCard.module.css +131 -0
- package/src/ui/ItemCard.tsx +117 -0
- package/src/ui/ItemModal/DescriptionField.tsx +22 -0
- package/src/ui/ItemModal/EnrichmentSections.tsx +49 -0
- package/src/ui/ItemModal/ItemModal.module.css +200 -0
- package/src/ui/ItemModal/LinksSection.tsx +26 -0
- package/src/ui/ItemModal/NotesSection.tsx +18 -0
- package/src/ui/ItemModal/PhasesSection.tsx +79 -0
- package/src/ui/ItemModal/RelatedSection.tsx +26 -0
- package/src/ui/ItemModal/RelationshipsSection.tsx +69 -0
- package/src/ui/ItemModal/StatusLayerControls.tsx +43 -0
- package/src/ui/ItemModal/TitleField.tsx +21 -0
- package/src/ui/ItemModal/index.tsx +117 -0
- package/src/ui/KanbanColumn.module.css +20 -0
- package/src/ui/KanbanColumn.tsx +36 -0
- package/src/ui/LayerDetailPage/LayerDetailPage.module.css +11 -0
- package/src/ui/LayerDetailPage/LayerDetailPage.test.tsx +30 -0
- package/src/ui/LayerDetailPage/LayerDetailPage.tsx +59 -0
- package/src/ui/LayerDetailPage/SignalSection.tsx +33 -0
- package/src/ui/LayerDrawer.module.css +60 -0
- package/src/ui/LayerDrawer.tsx +69 -0
- package/src/ui/LayerKanban.module.css +45 -0
- package/src/ui/LayerKanban.tsx +65 -0
- package/src/ui/LayerPage/FillThisInButton.tsx +11 -0
- package/src/ui/LayerPage/LayerPage.module.css +52 -0
- package/src/ui/LayerPage/LayerPage.tsx +76 -0
- package/src/ui/LayerPage/MetricsBadges.tsx +18 -0
- package/src/ui/LayerPage/isTemplateOnly.test.ts +45 -0
- package/src/ui/LayerPage/isTemplateOnly.ts +19 -0
- package/src/ui/LayerPage/tabs/CodeMapTab.tsx +19 -0
- package/src/ui/LayerPage/tabs/DeepDivesTab.tsx +35 -0
- package/src/ui/LayerPage/tabs/IntentTab.tsx +19 -0
- package/src/ui/LayerPage/tabs/PlainEnglishTab.tsx +19 -0
- package/src/ui/Markdown.module.css +115 -0
- package/src/ui/Markdown.tsx +27 -0
- package/src/ui/RoadmapPage/RoadmapPage.module.css +93 -0
- package/src/ui/RoadmapPage/RoadmapPage.tsx +255 -0
- package/src/ui/ScopePage/ScopePage.module.css +72 -0
- package/src/ui/ScopePage/ScopePage.tsx +131 -0
- package/src/ui/SummaryStrip.module.css +18 -0
- package/src/ui/SummaryStrip.tsx +16 -0
- package/src/ui/TaskList.module.css +136 -0
- package/src/ui/TaskList.tsx +91 -0
- package/src/ui/TopBar.module.css +78 -0
- package/src/ui/TopBar.tsx +48 -0
- package/src/ui/WorkspacePage/WorkspacePage.module.css +24 -0
- package/src/ui/WorkspacePage/WorkspacePage.tsx +36 -0
- package/src/ui/WorkspacePage/tabs/GlossaryTab.tsx +6 -0
- package/src/ui/WorkspacePage/tabs/OverviewTab.tsx +28 -0
- package/src/ui/WorkspacePage/tabs/PrinciplesTab.tsx +6 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +9 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { CornerLeftUp, ArrowLeftRight } from 'lucide-react';
|
|
2
|
+
import type { Item, Status } from '../types';
|
|
3
|
+
import { effectiveStatus } from '../api/lib/phases';
|
|
4
|
+
import styles from './TaskList.module.css';
|
|
5
|
+
|
|
6
|
+
export interface FlatItem {
|
|
7
|
+
item: Item;
|
|
8
|
+
scopeId: string;
|
|
9
|
+
layerName?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
items: FlatItem[];
|
|
14
|
+
onRowClick: (scopeId: string, item: Item) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const STATUS_ORDER: Status[] = ['now', 'next', 'later', 'done'];
|
|
18
|
+
const STATUS_LABEL: Record<Status, string> = { now: 'Now', next: 'Next', later: 'Later', done: 'Done' };
|
|
19
|
+
|
|
20
|
+
export function TaskList({ items, onRowClick }: Props) {
|
|
21
|
+
const sorted = STATUS_ORDER.flatMap(s =>
|
|
22
|
+
items.filter(({ item }) => effectiveStatus(item) === s)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if (items.length === 0) {
|
|
26
|
+
return <div className={styles.empty}>No items to show.</div>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className={styles.wrap}>
|
|
31
|
+
<table className={styles.table}>
|
|
32
|
+
<thead>
|
|
33
|
+
<tr>
|
|
34
|
+
<th className={styles.thStatus}>Status</th>
|
|
35
|
+
<th className={styles.thId}>ID</th>
|
|
36
|
+
<th className={styles.thPriority}>Priority</th>
|
|
37
|
+
<th className={styles.thTitle}>Title</th>
|
|
38
|
+
<th className={styles.thLayer}>Layer</th>
|
|
39
|
+
<th className={styles.thType}>Type</th>
|
|
40
|
+
</tr>
|
|
41
|
+
</thead>
|
|
42
|
+
<tbody>
|
|
43
|
+
{sorted.map(({ item, scopeId, layerName }) => {
|
|
44
|
+
const status = effectiveStatus(item);
|
|
45
|
+
return (
|
|
46
|
+
<tr
|
|
47
|
+
key={item.id}
|
|
48
|
+
className={styles.row}
|
|
49
|
+
onClick={() => onRowClick(scopeId, item)}
|
|
50
|
+
tabIndex={0}
|
|
51
|
+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onRowClick(scopeId, item); } }}
|
|
52
|
+
>
|
|
53
|
+
<td>
|
|
54
|
+
<span className={`${styles.badge} ${styles[`badge_${status}`]}`}>
|
|
55
|
+
{STATUS_LABEL[status]}
|
|
56
|
+
</span>
|
|
57
|
+
</td>
|
|
58
|
+
<td>
|
|
59
|
+
<div className={styles.tdId}>
|
|
60
|
+
<span className={styles.idText}>{item.id}</span>
|
|
61
|
+
{item.spawned_from && (
|
|
62
|
+
<span className={styles.relChip} title={`Spawned from ${item.spawned_from}`}>
|
|
63
|
+
<CornerLeftUp size={10} strokeWidth={1.75} />
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
66
|
+
{item.related && item.related.length > 0 && (
|
|
67
|
+
<span className={styles.relChip} title={`Related to ${item.related.length} item(s)`}>
|
|
68
|
+
<ArrowLeftRight size={10} strokeWidth={1.75} /> {item.related.length}
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
</td>
|
|
73
|
+
<td>
|
|
74
|
+
{item.priority
|
|
75
|
+
? <span className={`${styles.priority} ${styles[`pri_${item.priority}`]}`}>{item.priority.toUpperCase()}</span>
|
|
76
|
+
: <span className={styles.dash}>—</span>
|
|
77
|
+
}
|
|
78
|
+
</td>
|
|
79
|
+
<td>
|
|
80
|
+
<span className={styles.titleText}>{item.title}</span>
|
|
81
|
+
</td>
|
|
82
|
+
<td className={styles.tdLayer}>{layerName ?? item.layer ?? <span className={styles.dash}>—</span>}</td>
|
|
83
|
+
<td className={styles.tdType}>{item.type ?? <span className={styles.dash}>—</span>}</td>
|
|
84
|
+
</tr>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.header {
|
|
2
|
+
position: sticky;
|
|
3
|
+
top: 0;
|
|
4
|
+
z-index: var(--z-header, 50);
|
|
5
|
+
display: flex;
|
|
6
|
+
align-items: stretch;
|
|
7
|
+
gap: 24px;
|
|
8
|
+
padding: 0 16px;
|
|
9
|
+
height: 48px;
|
|
10
|
+
background: var(--console-black);
|
|
11
|
+
border-bottom: 1px solid var(--console-mist);
|
|
12
|
+
}
|
|
13
|
+
.brand {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
font-size: 14px;
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
color: var(--console-snow);
|
|
19
|
+
letter-spacing: -0.005em;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Mode nav — DESIGN.md spec: 2px Signal Cyan bottom strut on active, no pill, no fill. */
|
|
23
|
+
.modeToggle {
|
|
24
|
+
display: flex;
|
|
25
|
+
gap: 0;
|
|
26
|
+
height: 100%;
|
|
27
|
+
}
|
|
28
|
+
.modeButton, .modeActive {
|
|
29
|
+
position: relative;
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
height: 100%;
|
|
33
|
+
padding: 0 12px;
|
|
34
|
+
background: transparent;
|
|
35
|
+
border: none;
|
|
36
|
+
border-radius: 0;
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
color: var(--console-fog);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
transition: color var(--dur-state) var(--ease);
|
|
42
|
+
}
|
|
43
|
+
.modeButton:hover, .modeActive:hover { color: var(--console-snow); }
|
|
44
|
+
.modeActive {
|
|
45
|
+
color: var(--signal-cyan);
|
|
46
|
+
}
|
|
47
|
+
.modeActive::after {
|
|
48
|
+
content: '';
|
|
49
|
+
position: absolute;
|
|
50
|
+
left: 0;
|
|
51
|
+
right: 0;
|
|
52
|
+
bottom: -1px;
|
|
53
|
+
height: 2px;
|
|
54
|
+
background: var(--signal-cyan);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.spacer {
|
|
58
|
+
flex: 1;
|
|
59
|
+
}
|
|
60
|
+
.validateBtn {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 6px;
|
|
64
|
+
}
|
|
65
|
+
.dot {
|
|
66
|
+
width: 8px;
|
|
67
|
+
height: 8px;
|
|
68
|
+
border-radius: var(--r-full);
|
|
69
|
+
}
|
|
70
|
+
.label {
|
|
71
|
+
font-size: 12px;
|
|
72
|
+
color: var(--console-fog);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Center the Reload button vertically with the header */
|
|
76
|
+
.header > :global(.outlined-button) {
|
|
77
|
+
align-self: center;
|
|
78
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { RefreshCw } from 'lucide-react';
|
|
2
|
+
import type { Mode } from '../router/routes';
|
|
3
|
+
import styles from './TopBar.module.css';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
mode: Mode;
|
|
7
|
+
onSelectMode: (m: Mode) => void;
|
|
8
|
+
onReload: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TopBar({ mode, onSelectMode, onReload }: Props) {
|
|
12
|
+
return (
|
|
13
|
+
<header className={styles.header}>
|
|
14
|
+
<strong className={styles.brand}>tenbo</strong>
|
|
15
|
+
<nav className={styles.modeToggle} role="tablist" aria-label="Top-level view">
|
|
16
|
+
<button
|
|
17
|
+
role="tab"
|
|
18
|
+
aria-selected={mode === 'roadmap'}
|
|
19
|
+
className={mode === 'roadmap' ? styles.modeActive : styles.modeButton}
|
|
20
|
+
onClick={() => onSelectMode('roadmap')}
|
|
21
|
+
>
|
|
22
|
+
Roadmap
|
|
23
|
+
</button>
|
|
24
|
+
<button
|
|
25
|
+
role="tab"
|
|
26
|
+
aria-selected={mode === 'health'}
|
|
27
|
+
className={mode === 'health' ? styles.modeActive : styles.modeButton}
|
|
28
|
+
onClick={() => onSelectMode('health')}
|
|
29
|
+
>
|
|
30
|
+
Health
|
|
31
|
+
</button>
|
|
32
|
+
<button
|
|
33
|
+
role="tab"
|
|
34
|
+
aria-selected={mode === 'docs'}
|
|
35
|
+
className={mode === 'docs' ? styles.modeActive : styles.modeButton}
|
|
36
|
+
onClick={() => onSelectMode('docs')}
|
|
37
|
+
>
|
|
38
|
+
Docs
|
|
39
|
+
</button>
|
|
40
|
+
</nav>
|
|
41
|
+
<div className={styles.spacer} />
|
|
42
|
+
<button onClick={onReload} className="outlined-button icon-text">
|
|
43
|
+
<RefreshCw size={14} strokeWidth={1.75} />
|
|
44
|
+
Reload
|
|
45
|
+
</button>
|
|
46
|
+
</header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.page {
|
|
2
|
+
max-width: 960px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 0 32px 48px;
|
|
5
|
+
}
|
|
6
|
+
.breadcrumb {
|
|
7
|
+
padding: 12px 32px;
|
|
8
|
+
border-bottom: 1px solid var(--console-mist);
|
|
9
|
+
font-size: 12px;
|
|
10
|
+
font-weight: 500;
|
|
11
|
+
letter-spacing: 0.02em;
|
|
12
|
+
color: var(--console-fog);
|
|
13
|
+
margin: 0 -32px;
|
|
14
|
+
}
|
|
15
|
+
.tabs {
|
|
16
|
+
display: flex;
|
|
17
|
+
gap: 0;
|
|
18
|
+
padding: 16px 32px 8px;
|
|
19
|
+
border-bottom: 1px solid var(--console-mist);
|
|
20
|
+
margin: 0 -32px 24px;
|
|
21
|
+
}
|
|
22
|
+
.body {
|
|
23
|
+
padding-top: 8px;
|
|
24
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Route, WorkspaceTab } from '../../router/routes';
|
|
2
|
+
import type { TenboState } from '../../types';
|
|
3
|
+
import { OverviewTab } from './tabs/OverviewTab';
|
|
4
|
+
import { PrinciplesTab } from './tabs/PrinciplesTab';
|
|
5
|
+
import { GlossaryTab } from './tabs/GlossaryTab';
|
|
6
|
+
import styles from './WorkspacePage.module.css';
|
|
7
|
+
|
|
8
|
+
const TABS: WorkspaceTab[] = ['overview', 'principles', 'glossary'];
|
|
9
|
+
|
|
10
|
+
const TAB_LABELS: Record<WorkspaceTab, string> = {
|
|
11
|
+
'overview': 'Project overview',
|
|
12
|
+
'principles': 'Principles',
|
|
13
|
+
'glossary': 'Glossary',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function WorkspacePage(props: { tab: WorkspaceTab; navigate: (r: Route) => void; state: TenboState }) {
|
|
17
|
+
return (
|
|
18
|
+
<div className={styles.page}>
|
|
19
|
+
<nav aria-label="breadcrumb" className={styles.breadcrumb}>
|
|
20
|
+
<span>Project</span>
|
|
21
|
+
</nav>
|
|
22
|
+
<nav className={styles.tabs}>
|
|
23
|
+
{TABS.map((t) => (
|
|
24
|
+
<button key={t} aria-pressed={props.tab === t} onClick={() => props.navigate({ kind: 'docs-project', tab: t })}>
|
|
25
|
+
{TAB_LABELS[t]}
|
|
26
|
+
</button>
|
|
27
|
+
))}
|
|
28
|
+
</nav>
|
|
29
|
+
<div className={styles.body}>
|
|
30
|
+
{props.tab === 'overview' && <OverviewTab state={props.state} navigate={props.navigate} />}
|
|
31
|
+
{props.tab === 'principles' && <PrinciplesTab state={props.state} />}
|
|
32
|
+
{props.tab === 'glossary' && <GlossaryTab state={props.state} />}
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Markdown } from '../../Markdown';
|
|
2
|
+
import type { Route } from '../../../router/routes';
|
|
3
|
+
import type { TenboState } from '../../../types';
|
|
4
|
+
|
|
5
|
+
export function OverviewTab({ state }: { state: TenboState; navigate: (r: Route) => void }) {
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<Markdown source={state.workspaceContent.overviewMd} />
|
|
9
|
+
|
|
10
|
+
{state.crossCutting.length > 0 && (
|
|
11
|
+
<section style={{ marginTop: 24 }}>
|
|
12
|
+
<h2 style={{ fontSize: 14, fontWeight: 700, marginBottom: 12 }}>Cross-cutting concerns</h2>
|
|
13
|
+
{state.crossCutting.map((cc) => (
|
|
14
|
+
<div key={cc.id} style={{ padding: '8px 0', borderBottom: '1px solid var(--border, #eee)' }}>
|
|
15
|
+
<span style={{ fontSize: 11, color: 'var(--muted, #888)', marginRight: 8 }}>{cc.id}</span>
|
|
16
|
+
<span style={{ fontWeight: 500 }}>{cc.description}</span>
|
|
17
|
+
{cc.spans.length > 0 && (
|
|
18
|
+
<div style={{ fontSize: 12, color: 'var(--muted, #888)', marginTop: 2 }}>
|
|
19
|
+
Spans: {cc.spans.join(', ')}
|
|
20
|
+
</div>
|
|
21
|
+
)}
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
</section>
|
|
25
|
+
)}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noUnusedLocals": true,
|
|
10
|
+
"noUnusedParameters": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"allowImportingTsExtensions": false,
|
|
15
|
+
"noEmit": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src", "tests", "vite.config.ts"]
|
|
18
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { tenboApiPlugin } from './src/api/plugin';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react(), tenboApiPlugin()],
|
|
7
|
+
server: { port: 5174 },
|
|
8
|
+
test: { environment: 'jsdom', globals: true, setupFiles: ['./src/test-setup.ts'] },
|
|
9
|
+
});
|