create-project-arch 1.0.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 (90) hide show
  1. package/README.md +58 -0
  2. package/dist/cli.js +232 -0
  3. package/dist/cli.test.js +8 -0
  4. package/package.json +29 -0
  5. package/templates/arch-ui/.arch/edges/decision_to_domain.json +4 -0
  6. package/templates/arch-ui/.arch/edges/milestone_to_task.json +4 -0
  7. package/templates/arch-ui/.arch/edges/task_to_decision.json +4 -0
  8. package/templates/arch-ui/.arch/edges/task_to_module.json +4 -0
  9. package/templates/arch-ui/.arch/graph.json +17 -0
  10. package/templates/arch-ui/.arch/nodes/decisions.json +4 -0
  11. package/templates/arch-ui/.arch/nodes/domains.json +4 -0
  12. package/templates/arch-ui/.arch/nodes/milestones.json +4 -0
  13. package/templates/arch-ui/.arch/nodes/modules.json +4 -0
  14. package/templates/arch-ui/.arch/nodes/tasks.json +4 -0
  15. package/templates/arch-ui/app/api/architecture/map/route.ts +13 -0
  16. package/templates/arch-ui/app/api/decisions/route.ts +23 -0
  17. package/templates/arch-ui/app/api/domain-docs/route.ts +89 -0
  18. package/templates/arch-ui/app/api/domains/route.ts +10 -0
  19. package/templates/arch-ui/app/api/graph/route.ts +16 -0
  20. package/templates/arch-ui/app/api/health/route.ts +44 -0
  21. package/templates/arch-ui/app/api/node-files/route.ts +173 -0
  22. package/templates/arch-ui/app/api/phases/route.ts +10 -0
  23. package/templates/arch-ui/app/api/route.ts +22 -0
  24. package/templates/arch-ui/app/api/search/route.ts +56 -0
  25. package/templates/arch-ui/app/api/task-doc/[taskId]/route.ts +60 -0
  26. package/templates/arch-ui/app/api/tasks/route.ts +36 -0
  27. package/templates/arch-ui/app/api/trace/file/route.ts +40 -0
  28. package/templates/arch-ui/app/api/trace/task/[taskId]/route.ts +12 -0
  29. package/templates/arch-ui/app/architecture/page.tsx +5 -0
  30. package/templates/arch-ui/app/globals.css +240 -0
  31. package/templates/arch-ui/app/health/page.tsx +48 -0
  32. package/templates/arch-ui/app/layout.tsx +19 -0
  33. package/templates/arch-ui/app/page.tsx +5 -0
  34. package/templates/arch-ui/app/work/page.tsx +265 -0
  35. package/templates/arch-ui/components/app-shell.tsx +171 -0
  36. package/templates/arch-ui/components/error-boundary.tsx +53 -0
  37. package/templates/arch-ui/components/graph/arch-node.tsx +77 -0
  38. package/templates/arch-ui/components/graph/build-graph-from-dataset.ts +196 -0
  39. package/templates/arch-ui/components/graph/build-initial-graph.ts +245 -0
  40. package/templates/arch-ui/components/graph/graph-context-menu.tsx +84 -0
  41. package/templates/arch-ui/components/graph/graph-doc-panel.tsx +46 -0
  42. package/templates/arch-ui/components/graph/graph-types.ts +82 -0
  43. package/templates/arch-ui/components/graph/use-auto-layout.ts +65 -0
  44. package/templates/arch-ui/components/graph/use-connection-validation.ts +62 -0
  45. package/templates/arch-ui/components/graph/use-flow-persistence.ts +48 -0
  46. package/templates/arch-ui/components/graph-canvas.tsx +670 -0
  47. package/templates/arch-ui/components/health-panel.tsx +49 -0
  48. package/templates/arch-ui/components/inspector-context.tsx +35 -0
  49. package/templates/arch-ui/components/inspector.tsx +895 -0
  50. package/templates/arch-ui/components/markdown-viewer.tsx +74 -0
  51. package/templates/arch-ui/components/sidebar.tsx +531 -0
  52. package/templates/arch-ui/components/topbar.tsx +187 -0
  53. package/templates/arch-ui/components/work-table.tsx +57 -0
  54. package/templates/arch-ui/components/workspace-context.tsx +274 -0
  55. package/templates/arch-ui/eslint.config.js +2 -0
  56. package/templates/arch-ui/global.d.ts +1 -0
  57. package/templates/arch-ui/lib/api.ts +93 -0
  58. package/templates/arch-ui/lib/arch-model.ts +113 -0
  59. package/templates/arch-ui/lib/graph-dataset.ts +756 -0
  60. package/templates/arch-ui/lib/graph-schema.ts +408 -0
  61. package/templates/arch-ui/lib/project-root.ts +52 -0
  62. package/templates/arch-ui/lib/types.ts +116 -0
  63. package/templates/arch-ui/next-env.d.ts +6 -0
  64. package/templates/arch-ui/next.config.js +17 -0
  65. package/templates/arch-ui/package.json +38 -0
  66. package/templates/arch-ui/postcss.config.mjs +6 -0
  67. package/templates/arch-ui/tailwind.config.ts +11 -0
  68. package/templates/arch-ui/tsconfig.json +21 -0
  69. package/templates/ui-package/eslint.config.mjs +4 -0
  70. package/templates/ui-package/package.json +26 -0
  71. package/templates/ui-package/src/accordion.tsx +10 -0
  72. package/templates/ui-package/src/badge.tsx +12 -0
  73. package/templates/ui-package/src/button.tsx +32 -0
  74. package/templates/ui-package/src/card.tsx +22 -0
  75. package/templates/ui-package/src/code.tsx +6 -0
  76. package/templates/ui-package/src/command.tsx +18 -0
  77. package/templates/ui-package/src/dialog.tsx +6 -0
  78. package/templates/ui-package/src/dropdown-menu.tsx +10 -0
  79. package/templates/ui-package/src/input.tsx +6 -0
  80. package/templates/ui-package/src/navigation-menu.tsx +6 -0
  81. package/templates/ui-package/src/scroll-area.tsx +6 -0
  82. package/templates/ui-package/src/select.tsx +6 -0
  83. package/templates/ui-package/src/separator.tsx +6 -0
  84. package/templates/ui-package/src/sheet.tsx +6 -0
  85. package/templates/ui-package/src/skeleton.tsx +6 -0
  86. package/templates/ui-package/src/table.tsx +26 -0
  87. package/templates/ui-package/src/tabs.tsx +14 -0
  88. package/templates/ui-package/src/toggle-group.tsx +10 -0
  89. package/templates/ui-package/src/utils.ts +3 -0
  90. package/templates/ui-package/tsconfig.json +10 -0
@@ -0,0 +1,173 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { NextRequest, NextResponse } from "next/server";
4
+ import { getProjectRoot, normalizePath } from "../../../lib/project-root";
5
+
6
+ export const runtime = "nodejs";
7
+
8
+ type NodeType = "phase" | "milestone" | "task" | "domain" | "file";
9
+
10
+ function isSafeSegment(value: string): boolean {
11
+ return /^[a-zA-Z0-9-]+$/.test(value);
12
+ }
13
+
14
+ async function collectFiles(
15
+ baseDir: string,
16
+ root: string,
17
+ skipDirs: Set<string>,
18
+ maxDepth = 2,
19
+ ): Promise<string[]> {
20
+ const output: string[] = [];
21
+
22
+ async function walk(currentDir: string, depth: number): Promise<void> {
23
+ if (depth > maxDepth) return;
24
+ const entries = await readdir(currentDir, { withFileTypes: true });
25
+ for (const entry of entries) {
26
+ const fullPath = path.join(currentDir, entry.name);
27
+ if (entry.isDirectory()) {
28
+ if (skipDirs.has(entry.name)) continue;
29
+ await walk(fullPath, depth + 1);
30
+ continue;
31
+ }
32
+ output.push(normalizePath(path.relative(root, fullPath)));
33
+ }
34
+ }
35
+
36
+ try {
37
+ await walk(baseDir, 0);
38
+ } catch (error) {
39
+ if (
40
+ error &&
41
+ typeof error === "object" &&
42
+ "code" in error &&
43
+ (error as { code?: string }).code === "ENOENT"
44
+ ) {
45
+ return [];
46
+ }
47
+ throw error;
48
+ }
49
+ return output.sort((a, b) => a.localeCompare(b));
50
+ }
51
+
52
+ async function findTaskFile(root: string, taskId: string): Promise<string | null> {
53
+ const [phase, milestone, taskNumber] = taskId.split("/");
54
+ if (!phase || !milestone || !taskNumber) return null;
55
+ if (!isSafeSegment(phase) || !isSafeSegment(milestone) || !isSafeSegment(taskNumber)) return null;
56
+
57
+ const tasksDir = path.join(root, "roadmap", "phases", phase, "milestones", milestone, "tasks");
58
+ const lanes = ["planned", "discovered", "backlog", "complete"] as const;
59
+ for (const lane of lanes) {
60
+ const laneDir = path.join(tasksDir, lane);
61
+ try {
62
+ const files = await readdir(laneDir);
63
+ const candidate = files.find(
64
+ (file) => file.startsWith(`${taskNumber}-`) && file.endsWith(".md"),
65
+ );
66
+ if (candidate)
67
+ return normalizePath(
68
+ path.join("roadmap", "phases", phase, "milestones", milestone, "tasks", lane, candidate),
69
+ );
70
+ } catch {
71
+ // Continue.
72
+ }
73
+ }
74
+ return null;
75
+ }
76
+
77
+ async function resolveFilesForNode(root: string, type: NodeType, id: string): Promise<string[]> {
78
+ if (type === "task") {
79
+ const found = await findTaskFile(root, id);
80
+ return found ? [found] : [];
81
+ }
82
+
83
+ if (type === "file") {
84
+ const normalized = normalizePath(id).replace(/^\/+/, "");
85
+ const allowedRoots = ["arch-domains/", "arch-model/", "architecture/", "roadmap/"];
86
+ if (!allowedRoots.some((prefix) => normalized.startsWith(prefix))) {
87
+ return [];
88
+ }
89
+ const absolutePath = path.join(root, normalized);
90
+ const relativeRoundTrip = normalizePath(path.relative(root, absolutePath));
91
+ if (relativeRoundTrip !== normalized) {
92
+ return [];
93
+ }
94
+ try {
95
+ await readFile(absolutePath, "utf8");
96
+ return [normalized];
97
+ } catch {
98
+ return [];
99
+ }
100
+ }
101
+
102
+ if (type === "domain") {
103
+ const domainsDir = path.join(root, "arch-domains");
104
+ try {
105
+ const files = await readdir(domainsDir);
106
+ const markdownFiles = files.filter((file) => file.toLowerCase().endsWith(".md"));
107
+ if (markdownFiles.length === 0) return [];
108
+
109
+ const normalizedId = id.trim().toLowerCase();
110
+ const normalizedDashId = normalizedId.replace(/\//g, "-");
111
+ const slug = normalizedId
112
+ .replace(/[^a-z0-9/ -]/g, "")
113
+ .replace(/[ /]+/g, "-")
114
+ .replace(/-+/g, "-")
115
+ .replace(/^-|-$/g, "");
116
+
117
+ const exactMatches = markdownFiles.filter((file) => {
118
+ const base = file.slice(0, -3).toLowerCase();
119
+ return base === normalizedId || base === normalizedDashId || base === slug;
120
+ });
121
+
122
+ const selected = exactMatches.length > 0 ? exactMatches : markdownFiles;
123
+ return selected
124
+ .map((file) => normalizePath(path.join("arch-domains", file)))
125
+ .sort((a, b) => a.localeCompare(b));
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+
131
+ if (type === "phase") {
132
+ const [phase] = id.split("/");
133
+ if (!phase || !isSafeSegment(phase)) return [];
134
+ const phaseDir = path.join(root, "roadmap", "phases", phase);
135
+ return collectFiles(phaseDir, root, new Set(["milestones", "tasks", "node_modules", ".git"]), 2);
136
+ }
137
+
138
+ const [phase, milestone] = id.split("/");
139
+ if (!phase || !milestone || !isSafeSegment(phase) || !isSafeSegment(milestone)) return [];
140
+ const milestoneDir = path.join(root, "roadmap", "phases", phase, "milestones", milestone);
141
+ return collectFiles(milestoneDir, root, new Set(["tasks", "node_modules", ".git"]), 3);
142
+ }
143
+
144
+ export async function GET(request: NextRequest) {
145
+ const root = getProjectRoot();
146
+ const type = request.nextUrl.searchParams.get("type") as NodeType | null;
147
+ const id = request.nextUrl.searchParams.get("id");
148
+
149
+ if (!type || !id) {
150
+ return NextResponse.json(
151
+ { success: false, errors: ["type and id query parameters are required"] },
152
+ { status: 400 },
153
+ );
154
+ }
155
+ if (!["phase", "milestone", "task", "domain", "file"].includes(type)) {
156
+ return NextResponse.json({ files: [] });
157
+ }
158
+
159
+ const files = await resolveFilesForNode(root, type, id);
160
+ const withContent = await Promise.all(
161
+ files.map(async (relativePath) => {
162
+ const absolutePath = path.join(root, relativePath);
163
+ const content = await readFile(absolutePath, "utf8");
164
+ return { path: relativePath, content };
165
+ }),
166
+ );
167
+
168
+ return NextResponse.json({
169
+ type,
170
+ id,
171
+ files: withContent,
172
+ });
173
+ }
@@ -0,0 +1,10 @@
1
+ import { NextResponse } from "next/server";
2
+ import { phases } from "project-arch";
3
+ import { getProjectRoot } from "../../../lib/project-root";
4
+
5
+ export const runtime = "nodejs";
6
+
7
+ export async function GET() {
8
+ const root = getProjectRoot();
9
+ return NextResponse.json(await phases.phaseList({ cwd: root }));
10
+ }
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from "next/server";
2
+
3
+ export const runtime = "nodejs";
4
+
5
+ export async function GET() {
6
+ return NextResponse.json({
7
+ service: "arch-api",
8
+ status: "ok",
9
+ endpoints: [
10
+ "GET /api/health",
11
+ "GET /api/graph",
12
+ "GET /api/architecture/map",
13
+ "GET /api/domains",
14
+ "GET /api/phases",
15
+ "GET /api/tasks",
16
+ "GET /api/trace/task/:taskId",
17
+ "GET /api/trace/file?path=<repoPath>",
18
+ "POST /api/tasks",
19
+ "POST /api/decisions",
20
+ ],
21
+ });
22
+ }
@@ -0,0 +1,56 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { readArchitectureMap } from "../../../lib/arch-model";
3
+ import { getProjectRoot } from "../../../lib/project-root";
4
+
5
+ export const runtime = "nodejs";
6
+
7
+ export async function GET(request: NextRequest) {
8
+ const root = getProjectRoot();
9
+ const query = (request.nextUrl.searchParams.get("q") ?? "").trim();
10
+ const lowerQuery = query.toLowerCase();
11
+ const map = await readArchitectureMap(root);
12
+
13
+ const results = [
14
+ ...map.nodes.tasks.map((task) => ({
15
+ id: `task:${task.id}`,
16
+ kind: "task" as const,
17
+ title: task.title,
18
+ subtitle: task.id,
19
+ route: "/work",
20
+ })),
21
+ ...map.nodes.decisions.map((decision) => ({
22
+ id: `decision:${decision.id}`,
23
+ kind: "decision" as const,
24
+ title: decision.title ?? decision.id,
25
+ subtitle: decision.id,
26
+ route: "/architecture?view=decisions",
27
+ })),
28
+ ...map.nodes.domains.map((domain) => ({
29
+ id: `domain:${domain.name}`,
30
+ kind: "domain" as const,
31
+ title: domain.name,
32
+ subtitle: domain.description,
33
+ route: "/architecture",
34
+ })),
35
+ ...map.nodes.modules.map((moduleRef) => ({
36
+ id: `module:${moduleRef.name}`,
37
+ kind: "module" as const,
38
+ title: moduleRef.name,
39
+ subtitle: moduleRef.type ?? "module",
40
+ route: "/architecture",
41
+ })),
42
+ ].filter((item) => {
43
+ if (!lowerQuery) return true;
44
+ return (
45
+ item.title.toLowerCase().includes(lowerQuery) ||
46
+ item.subtitle?.toLowerCase().includes(lowerQuery) ||
47
+ item.kind.toLowerCase().includes(lowerQuery)
48
+ );
49
+ });
50
+
51
+ return NextResponse.json({
52
+ query,
53
+ results: results.slice(0, 30),
54
+ });
55
+ }
56
+
@@ -0,0 +1,60 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { NextResponse } from "next/server";
4
+ import { getProjectRoot, normalizePath } from "../../../../lib/project-root";
5
+
6
+ export const runtime = "nodejs";
7
+
8
+ const lanes = ["planned", "discovered", "backlog", "complete"] as const;
9
+
10
+ function isSafeSegment(value: string): boolean {
11
+ return /^[a-zA-Z0-9-]+$/.test(value);
12
+ }
13
+
14
+ export async function GET(_: Request, context: { params: Promise<{ taskId: string }> }) {
15
+ const root = getProjectRoot();
16
+ const { taskId } = await context.params;
17
+ const decoded = decodeURIComponent(taskId);
18
+ const [phase, milestone, taskNumber] = decoded.split("/");
19
+
20
+ if (
21
+ !phase ||
22
+ !milestone ||
23
+ !taskNumber ||
24
+ !isSafeSegment(phase) ||
25
+ !isSafeSegment(milestone) ||
26
+ !isSafeSegment(taskNumber)
27
+ ) {
28
+ return NextResponse.json(
29
+ { success: false, errors: ["invalid task id; expected phase/milestone/task"] },
30
+ { status: 400 },
31
+ );
32
+ }
33
+
34
+ const tasksDir = path.join(root, "roadmap", "phases", phase, "milestones", milestone, "tasks");
35
+ for (const lane of lanes) {
36
+ const laneDir = path.join(tasksDir, lane);
37
+ try {
38
+ const files = await readdir(laneDir);
39
+ const candidate = files.find(
40
+ (file) => file.startsWith(`${taskNumber}-`) && file.endsWith(".md"),
41
+ );
42
+ if (!candidate) continue;
43
+ const fullPath = path.join(laneDir, candidate);
44
+ const markdown = await readFile(fullPath, "utf8");
45
+ return NextResponse.json({
46
+ id: decoded,
47
+ lane,
48
+ path: normalizePath(path.relative(root, fullPath)),
49
+ markdown,
50
+ });
51
+ } catch {
52
+ // Continue to other lanes.
53
+ }
54
+ }
55
+
56
+ return NextResponse.json(
57
+ { success: false, errors: [`task document not found for ${decoded}`] },
58
+ { status: 404 },
59
+ );
60
+ }
@@ -0,0 +1,36 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { tasks } from "project-arch";
3
+ import { readTasksNode } from "../../../lib/arch-model";
4
+ import { getProjectRoot } from "../../../lib/project-root";
5
+
6
+ export const runtime = "nodejs";
7
+
8
+ export async function GET() {
9
+ const root = getProjectRoot();
10
+ return NextResponse.json(await readTasksNode(root));
11
+ }
12
+
13
+ export async function POST(request: NextRequest) {
14
+ const root = getProjectRoot();
15
+ const body = (await request.json()) as { phase?: string; milestone?: string; title?: string };
16
+ if (!body.phase || !body.milestone) {
17
+ return NextResponse.json(
18
+ { success: false, errors: ["phase and milestone are required"] },
19
+ { status: 400 },
20
+ );
21
+ }
22
+ if (body.title && typeof body.title !== "string") {
23
+ return NextResponse.json(
24
+ { success: false, errors: ["title must be a string"] },
25
+ { status: 400 },
26
+ );
27
+ }
28
+ return NextResponse.json(
29
+ await tasks.taskCreate({
30
+ phase: body.phase,
31
+ milestone: body.milestone,
32
+ title: body.title,
33
+ cwd: root,
34
+ }),
35
+ );
36
+ }
@@ -0,0 +1,40 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { graph } from "project-arch";
3
+ import { readTaskToDecisionEdges, readTaskToModuleEdges } from "../../../../lib/arch-model";
4
+ import { getProjectRoot, mapTargetToModule, normalizePath } from "../../../../lib/project-root";
5
+
6
+ export const runtime = "nodejs";
7
+
8
+ export async function GET(request: NextRequest) {
9
+ const root = getProjectRoot();
10
+ process.env.PROJECT_ROOT = root;
11
+ const filePath = request.nextUrl.searchParams.get("path");
12
+ if (!filePath) {
13
+ return NextResponse.json(
14
+ { success: false, errors: ["path query parameter is required"] },
15
+ { status: 400 },
16
+ );
17
+ }
18
+ await graph.graphBuild();
19
+ const moduleName = mapTargetToModule(filePath);
20
+ const taskEdges = await readTaskToModuleEdges(root);
21
+ const decisionEdges = await readTaskToDecisionEdges(root);
22
+ const tasksForModule = [
23
+ ...new Set(
24
+ taskEdges.edges.filter((edge) => edge.module === moduleName).map((edge) => edge.task),
25
+ ),
26
+ ];
27
+ const decisionsForModule = [
28
+ ...new Set(
29
+ decisionEdges.edges
30
+ .filter((edge) => tasksForModule.includes(edge.task))
31
+ .map((edge) => edge.decision),
32
+ ),
33
+ ];
34
+ return NextResponse.json({
35
+ file: normalizePath(filePath),
36
+ module: moduleName,
37
+ tasks: tasksForModule,
38
+ decisions: decisionsForModule,
39
+ });
40
+ }
@@ -0,0 +1,12 @@
1
+ import { NextResponse } from "next/server";
2
+ import { graph } from "project-arch";
3
+ import { getProjectRoot } from "../../../../../lib/project-root";
4
+
5
+ export const runtime = "nodejs";
6
+
7
+ export async function GET(_: Request, context: { params: Promise<{ taskId: string }> }) {
8
+ const root = getProjectRoot();
9
+ process.env.PROJECT_ROOT = root;
10
+ const { taskId } = await context.params;
11
+ return NextResponse.json(await graph.graphTraceTask({ task: decodeURIComponent(taskId) }));
12
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ export default function ArchitecturePage() {
4
+ redirect("/work?view=architecture");
5
+ }
@@ -0,0 +1,240 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ color-scheme: dark;
7
+ --bg: #020617;
8
+ --panel: #0f172a;
9
+ --panel-2: #111827;
10
+ --border: #1e293b;
11
+ --border-strong: #334155;
12
+ --text: #e2e8f0;
13
+ --muted: #94a3b8;
14
+ }
15
+
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ html,
21
+ body {
22
+ margin: 0;
23
+ height: 100%;
24
+ background: radial-gradient(1300px 800px at 20% -10%, #0f172a, var(--bg));
25
+ color: var(--text);
26
+ font-family: 'IBM Plex Sans', 'Segoe UI', system-ui, sans-serif;
27
+ }
28
+
29
+ a {
30
+ color: inherit;
31
+ text-decoration: none;
32
+ }
33
+
34
+ .ui-card {
35
+ border-radius: 12px;
36
+ border: 1px solid var(--border-strong);
37
+ background: color-mix(in hsl, var(--panel) 90%, black);
38
+ box-shadow: 0 1px 0 #0005;
39
+ padding: 12px;
40
+ }
41
+
42
+ .ui-card-header {
43
+ display: flex;
44
+ justify-content: space-between;
45
+ align-items: center;
46
+ gap: 8px;
47
+ margin-bottom: 10px;
48
+ }
49
+
50
+ .ui-card-title {
51
+ margin: 0;
52
+ font-size: 15px;
53
+ }
54
+
55
+ .ui-card-description {
56
+ margin: 0;
57
+ color: var(--muted);
58
+ }
59
+
60
+ .ui-card-content {
61
+ display: grid;
62
+ gap: 10px;
63
+ }
64
+
65
+ .ui-btn {
66
+ border-radius: 10px;
67
+ border: 1px solid var(--border-strong);
68
+ padding: 8px 10px;
69
+ font-size: 13px;
70
+ cursor: pointer;
71
+ color: var(--text);
72
+ }
73
+
74
+ .ui-btn-default {
75
+ background: #1d4ed8;
76
+ border-color: #1d4ed8;
77
+ }
78
+
79
+ .ui-btn-secondary {
80
+ background: #374151;
81
+ }
82
+
83
+ .ui-btn-outline {
84
+ background: transparent;
85
+ }
86
+
87
+ .ui-btn-ghost {
88
+ background: transparent;
89
+ border-color: transparent;
90
+ }
91
+
92
+ .ui-badge {
93
+ border-radius: 999px;
94
+ padding: 3px 10px;
95
+ border: 1px solid var(--border-strong);
96
+ font-size: 12px;
97
+ }
98
+
99
+ .ui-badge-default {
100
+ background: #1e3a8a;
101
+ }
102
+
103
+ .ui-badge-secondary {
104
+ background: #5b21b6;
105
+ }
106
+
107
+ .ui-badge-success {
108
+ background: #14532d;
109
+ }
110
+
111
+ .ui-badge-warning {
112
+ background: #78350f;
113
+ }
114
+
115
+ .ui-badge-danger {
116
+ background: #7f1d1d;
117
+ }
118
+
119
+ .ui-input {
120
+ width: 100%;
121
+ border-radius: 10px;
122
+ border: 1px solid var(--border-strong);
123
+ background: #020617;
124
+ color: var(--text);
125
+ padding: 8px 10px;
126
+ }
127
+
128
+ .ui-table {
129
+ width: 100%;
130
+ border-collapse: collapse;
131
+ }
132
+
133
+ .ui-table-head,
134
+ .ui-table-cell {
135
+ text-align: left;
136
+ padding: 8px;
137
+ border-bottom: 1px solid var(--border);
138
+ }
139
+
140
+ .ui-table-row:hover {
141
+ background: #111827;
142
+ }
143
+
144
+ .ui-code {
145
+ font-family: 'IBM Plex Mono', ui-monospace, Menlo, Monaco, monospace;
146
+ background: #020617;
147
+ border: 1px solid var(--border-strong);
148
+ border-radius: 6px;
149
+ padding: 2px 6px;
150
+ font-size: 12px;
151
+ }
152
+
153
+ .ui-command {
154
+ display: grid;
155
+ gap: 10px;
156
+ }
157
+
158
+ .ui-command-list {
159
+ max-height: 240px;
160
+ overflow: auto;
161
+ display: grid;
162
+ gap: 6px;
163
+ }
164
+
165
+ .ui-command-item {
166
+ text-align: left;
167
+ border: 1px solid var(--border-strong);
168
+ border-radius: 8px;
169
+ background: #020617;
170
+ color: var(--text);
171
+ padding: 8px 10px;
172
+ cursor: pointer;
173
+ }
174
+
175
+ .ui-command-item:hover {
176
+ background: #1f2937;
177
+ }
178
+
179
+ .ui-tabs-list,
180
+ .ui-toggle-group {
181
+ display: flex;
182
+ gap: 8px;
183
+ flex-wrap: wrap;
184
+ }
185
+
186
+ .ui-tabs-trigger,
187
+ .ui-toggle-item {
188
+ border: 1px solid var(--border-strong);
189
+ border-radius: 10px;
190
+ background: #020617;
191
+ color: var(--text);
192
+ padding: 7px 10px;
193
+ cursor: pointer;
194
+ }
195
+
196
+ .ui-separator {
197
+ border: 0;
198
+ border-top: 1px solid var(--border);
199
+ }
200
+
201
+ .ui-scroll-area {
202
+ overflow: auto;
203
+ }
204
+
205
+ .ui-skeleton {
206
+ border-radius: 8px;
207
+ background: linear-gradient(90deg, #111827 0, #1f2937 50%, #111827 100%);
208
+ background-size: 200% 100%;
209
+ animation: shimmer 1.4s infinite;
210
+ height: 20px;
211
+ }
212
+
213
+ @keyframes shimmer {
214
+ from {
215
+ background-position: 200% 0;
216
+ }
217
+
218
+ to {
219
+ background-position: -200% 0;
220
+ }
221
+ }
222
+
223
+ .ui-dropdown,
224
+ .ui-accordion,
225
+ .ui-nav-menu {
226
+ display: grid;
227
+ gap: 6px;
228
+ }
229
+
230
+ .ui-dropdown-item {
231
+ font-size: 12px;
232
+ color: var(--muted);
233
+ }
234
+
235
+ .ui-accordion-item {
236
+ border: 1px solid var(--border-strong);
237
+ border-radius: 10px;
238
+ padding: 8px;
239
+ background: #020617;
240
+ }