@assistkick/create 1.6.0 → 1.7.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/dist/bin/create.js +0 -0
- package/package.json +7 -9
- package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
- package/templates/assistkick-product-system/packages/frontend/package.json +5 -1
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +16 -7
- package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +363 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +6 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +92 -188
- package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +11 -20
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +15 -70
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +149 -77
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +254 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +216 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +163 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +64 -100
- package/templates/assistkick-product-system/packages/frontend/vite.config.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/lib/graph.ts +11 -5
- package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
- package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
- package/templates/skills/assistkick-debugger/SKILL.md +2 -2
- package/templates/skills/assistkick-developer/SKILL.md +3 -3
- package/templates/skills/assistkick-interview/SKILL.md +2 -2
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +0 -2666
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { GraphView } from '../components/GraphView';
|
|
3
|
+
import type { GraphViewHandle } from '../components/GraphView';
|
|
4
|
+
import { GraphLegend } from '../components/GraphLegend';
|
|
5
|
+
import { GraphSettings } from '../components/GraphSettings';
|
|
6
|
+
import { useGraphStore } from '../stores/useGraphStore';
|
|
7
|
+
import { useGraphUIStore } from '../stores/useGraphUIStore';
|
|
8
|
+
import { useSidePanelStore } from '../stores/useSidePanelStore';
|
|
9
|
+
|
|
10
|
+
export function GraphRoute() {
|
|
11
|
+
const graphData = useGraphStore((s) => s.graphData);
|
|
12
|
+
const graphError = useGraphStore((s) => s.error);
|
|
13
|
+
const openSidePanel = useSidePanelStore((s) => s.open);
|
|
14
|
+
const settingsOpen = useGraphUIStore((s) => s.settingsOpen);
|
|
15
|
+
const setSettingsOpen = useGraphUIStore((s) => s.setSettingsOpen);
|
|
16
|
+
const setOnFit = useGraphUIStore((s) => s.setOnFit);
|
|
17
|
+
const setOnEdgeClick = useGraphUIStore((s) => s.setOnEdgeClick);
|
|
18
|
+
|
|
19
|
+
const graphRef = useRef<GraphViewHandle>(null);
|
|
20
|
+
const [legendVisible] = useState(true);
|
|
21
|
+
|
|
22
|
+
// Register graph callbacks with the UI store
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setOnFit(() => graphRef.current?.fitToView());
|
|
25
|
+
setOnEdgeClick((neighborId: string) => {
|
|
26
|
+
const node = graphRef.current?.getNodeById(neighborId);
|
|
27
|
+
if (node) {
|
|
28
|
+
graphRef.current?.focusNode(neighborId);
|
|
29
|
+
openSidePanel(node);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
setOnFit(null);
|
|
34
|
+
setOnEdgeClick(null);
|
|
35
|
+
};
|
|
36
|
+
}, [setOnFit, setOnEdgeClick, openSidePanel]);
|
|
37
|
+
|
|
38
|
+
// Window resize
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const handler = () => graphRef.current?.fitToView();
|
|
41
|
+
window.addEventListener('resize', handler);
|
|
42
|
+
return () => window.removeEventListener('resize', handler);
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
// Escape clears graph selection
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const handler = (e: KeyboardEvent) => {
|
|
48
|
+
if (e.key === 'Escape') {
|
|
49
|
+
graphRef.current?.clearSelection();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
document.addEventListener('keydown', handler);
|
|
53
|
+
return () => document.removeEventListener('keydown', handler);
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const handleNodeClick = useCallback(async (node: any) => {
|
|
57
|
+
openSidePanel(node);
|
|
58
|
+
}, [openSidePanel]);
|
|
59
|
+
|
|
60
|
+
const handleTypeToggle = useCallback((newHiddenTypes: Set<string>) => {
|
|
61
|
+
graphRef.current?.setHiddenTypes(newHiddenTypes);
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const handleSearchChange = useCallback((query: string) => {
|
|
65
|
+
graphRef.current?.highlightBySearch(query);
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
if (graphError) {
|
|
69
|
+
return (
|
|
70
|
+
<div id="graph-container">
|
|
71
|
+
<div className="error-msg">Failed to load graph data. Is the server running?</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!graphData) return null;
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<>
|
|
80
|
+
<GraphView ref={graphRef} graphData={graphData} onNodeClick={handleNodeClick} visible={true} />
|
|
81
|
+
<GraphLegend
|
|
82
|
+
visible={legendVisible}
|
|
83
|
+
onTypeToggle={handleTypeToggle}
|
|
84
|
+
onSearchChange={handleSearchChange}
|
|
85
|
+
/>
|
|
86
|
+
<GraphSettings
|
|
87
|
+
isOpen={settingsOpen}
|
|
88
|
+
onClose={() => setSettingsOpen(false)}
|
|
89
|
+
graphRef={graphRef}
|
|
90
|
+
/>
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React, { useCallback } from 'react';
|
|
2
|
+
import { KanbanView } from '../components/KanbanView';
|
|
3
|
+
import { useGraphStore } from '../stores/useGraphStore';
|
|
4
|
+
import { useProjectStore } from '../stores/useProjectStore';
|
|
5
|
+
import { useSidePanelStore } from '../stores/useSidePanelStore';
|
|
6
|
+
import { useQaSheetStore } from '../stores/useQaSheetStore';
|
|
7
|
+
|
|
8
|
+
export function KanbanRoute() {
|
|
9
|
+
const graphData = useGraphStore((s) => s.graphData);
|
|
10
|
+
const selectedProjectId = useProjectStore((s) => s.selectedProjectId);
|
|
11
|
+
const openSidePanel = useSidePanelStore((s) => s.open);
|
|
12
|
+
const openQaSheet = useQaSheetStore((s) => s.open);
|
|
13
|
+
|
|
14
|
+
const handleIssuesClick = useCallback((featureId: string, featureName: string, column: string, notes: any[], reviews: any[]) => {
|
|
15
|
+
openQaSheet(featureId, featureName, column, notes, reviews);
|
|
16
|
+
}, [openQaSheet]);
|
|
17
|
+
|
|
18
|
+
if (!graphData) return null;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex w-full h-full overflow-hidden p-3">
|
|
22
|
+
<KanbanView
|
|
23
|
+
graphData={graphData}
|
|
24
|
+
projectId={selectedProjectId}
|
|
25
|
+
onCardClick={openSidePanel}
|
|
26
|
+
onIssuesClick={handleIssuesClick}
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TerminalView } from '../components/TerminalView';
|
|
3
|
+
import { useProjectStore } from '../stores/useProjectStore';
|
|
4
|
+
|
|
5
|
+
export function TerminalRoute() {
|
|
6
|
+
const projects = useProjectStore((s) => s.projects);
|
|
7
|
+
|
|
8
|
+
return <TerminalView visible={true} projects={projects} />;
|
|
9
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import type { Project } from './useProjectStore';
|
|
3
|
+
|
|
4
|
+
interface GitModalState {
|
|
5
|
+
project: Project | null;
|
|
6
|
+
open: (project: Project) => void;
|
|
7
|
+
close: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useGitModalStore = create<GitModalState>((set) => ({
|
|
11
|
+
project: null,
|
|
12
|
+
open: (project) => set({ project }),
|
|
13
|
+
close: () => set({ project: null }),
|
|
14
|
+
}));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { apiClient } from '../api/client';
|
|
3
|
+
import { useProjectStore } from './useProjectStore';
|
|
4
|
+
|
|
5
|
+
interface GraphState {
|
|
6
|
+
graphData: any | null;
|
|
7
|
+
error: string | null;
|
|
8
|
+
loading: boolean;
|
|
9
|
+
|
|
10
|
+
fetchGraph: (projectId?: string | null) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const useGraphStore = create<GraphState>((set) => ({
|
|
14
|
+
graphData: null,
|
|
15
|
+
error: null,
|
|
16
|
+
loading: true,
|
|
17
|
+
|
|
18
|
+
fetchGraph: async (projectId?: string | null) => {
|
|
19
|
+
try {
|
|
20
|
+
set({ loading: true });
|
|
21
|
+
const data = await apiClient.fetchGraph(projectId ?? undefined);
|
|
22
|
+
set({ graphData: data, error: null, loading: false });
|
|
23
|
+
} catch (err: any) {
|
|
24
|
+
set({ error: err.message, loading: false });
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Auto-fetch when selectedProjectId changes
|
|
30
|
+
let prevProjectId = useProjectStore.getState().selectedProjectId;
|
|
31
|
+
useProjectStore.subscribe((state) => {
|
|
32
|
+
if (state.selectedProjectId !== prevProjectId) {
|
|
33
|
+
prevProjectId = state.selectedProjectId;
|
|
34
|
+
useGraphStore.getState().fetchGraph(state.selectedProjectId);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
interface GraphUIState {
|
|
4
|
+
settingsOpen: boolean;
|
|
5
|
+
legendVisible: boolean;
|
|
6
|
+
|
|
7
|
+
onFit: (() => void) | null;
|
|
8
|
+
onEdgeClick: ((neighborId: string) => void) | null;
|
|
9
|
+
onSettingsToggle: () => void;
|
|
10
|
+
setOnFit: (fn: (() => void) | null) => void;
|
|
11
|
+
setOnEdgeClick: (fn: ((neighborId: string) => void) | null) => void;
|
|
12
|
+
setSettingsOpen: (open: boolean) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useGraphUIStore = create<GraphUIState>((set) => ({
|
|
16
|
+
settingsOpen: false,
|
|
17
|
+
legendVisible: true,
|
|
18
|
+
onFit: null,
|
|
19
|
+
onEdgeClick: null,
|
|
20
|
+
|
|
21
|
+
onSettingsToggle: () => set((s) => ({ settingsOpen: !s.settingsOpen })),
|
|
22
|
+
setOnFit: (fn) => set({ onFit: fn }),
|
|
23
|
+
setOnEdgeClick: (fn) => set({ onEdgeClick: fn }),
|
|
24
|
+
setSettingsOpen: (open) => set({ settingsOpen: open }),
|
|
25
|
+
}));
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { apiClient } from '../api/client';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = 'selectedProjectId';
|
|
5
|
+
|
|
6
|
+
export interface Project {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
isDefault: number;
|
|
10
|
+
archivedAt: string | null;
|
|
11
|
+
repoUrl: string | null;
|
|
12
|
+
githubInstallationId: string | null;
|
|
13
|
+
githubRepoFullName: string | null;
|
|
14
|
+
baseBranch: string | null;
|
|
15
|
+
createdAt: string;
|
|
16
|
+
updatedAt: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ProjectState {
|
|
20
|
+
projects: Project[];
|
|
21
|
+
selectedProjectId: string | null;
|
|
22
|
+
loading: boolean;
|
|
23
|
+
|
|
24
|
+
init: () => Promise<void>;
|
|
25
|
+
selectProject: (id: string) => void;
|
|
26
|
+
createProject: (name: string) => Promise<Project>;
|
|
27
|
+
renameProject: (id: string, name: string) => Promise<void>;
|
|
28
|
+
archiveProject: (id: string) => Promise<void>;
|
|
29
|
+
refetchProjects: () => Promise<Project[]>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const useProjectStore = create<ProjectState>((set, get) => ({
|
|
33
|
+
projects: [],
|
|
34
|
+
selectedProjectId: localStorage.getItem(STORAGE_KEY),
|
|
35
|
+
loading: true,
|
|
36
|
+
|
|
37
|
+
init: async () => {
|
|
38
|
+
const fetched = await get().refetchProjects();
|
|
39
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
40
|
+
if (stored && fetched.some((p) => p.id === stored)) {
|
|
41
|
+
set({ selectedProjectId: stored });
|
|
42
|
+
} else if (fetched.length > 0) {
|
|
43
|
+
const defaultProject = fetched.find((p) => p.isDefault === 1);
|
|
44
|
+
const fallback = defaultProject ? defaultProject.id : fetched[0].id;
|
|
45
|
+
set({ selectedProjectId: fallback });
|
|
46
|
+
localStorage.setItem(STORAGE_KEY, fallback);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
selectProject: (id) => {
|
|
51
|
+
set({ selectedProjectId: id });
|
|
52
|
+
localStorage.setItem(STORAGE_KEY, id);
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
refetchProjects: async () => {
|
|
56
|
+
try {
|
|
57
|
+
const data = await apiClient.fetchProjects();
|
|
58
|
+
set({ projects: data.projects, loading: false });
|
|
59
|
+
return data.projects as Project[];
|
|
60
|
+
} catch {
|
|
61
|
+
set({ projects: [], loading: false });
|
|
62
|
+
return [] as Project[];
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
createProject: async (name) => {
|
|
67
|
+
const data = await apiClient.createProject(name);
|
|
68
|
+
await get().refetchProjects();
|
|
69
|
+
get().selectProject(data.project.id);
|
|
70
|
+
return data.project;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
renameProject: async (id, name) => {
|
|
74
|
+
await apiClient.renameProject(id, name);
|
|
75
|
+
await get().refetchProjects();
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
archiveProject: async (id) => {
|
|
79
|
+
await apiClient.archiveProject(id);
|
|
80
|
+
const updated = await get().refetchProjects();
|
|
81
|
+
if (get().selectedProjectId === id && updated.length > 0) {
|
|
82
|
+
const defaultProject = updated.find((p) => p.isDefault === 1);
|
|
83
|
+
const fallback = defaultProject ? defaultProject.id : updated[0].id;
|
|
84
|
+
get().selectProject(fallback);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
|
|
3
|
+
interface QaSheetState {
|
|
4
|
+
isOpen: boolean;
|
|
5
|
+
featureId: string | null;
|
|
6
|
+
featureName: string;
|
|
7
|
+
column: string;
|
|
8
|
+
notes: any[];
|
|
9
|
+
reviews: any[];
|
|
10
|
+
|
|
11
|
+
open: (featureId: string, featureName: string, column: string, notes: any[], reviews: any[]) => void;
|
|
12
|
+
close: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const useQaSheetStore = create<QaSheetState>((set) => ({
|
|
16
|
+
isOpen: false,
|
|
17
|
+
featureId: null,
|
|
18
|
+
featureName: '',
|
|
19
|
+
column: '',
|
|
20
|
+
notes: [],
|
|
21
|
+
reviews: [],
|
|
22
|
+
|
|
23
|
+
open: (featureId, featureName, column, notes, reviews) =>
|
|
24
|
+
set({ isOpen: true, featureId, featureName, column, notes, reviews }),
|
|
25
|
+
|
|
26
|
+
close: () => set({ isOpen: false }),
|
|
27
|
+
}));
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { create } from 'zustand';
|
|
2
|
+
import { apiClient } from '../api/client';
|
|
3
|
+
|
|
4
|
+
interface WorkSummary {
|
|
5
|
+
cycle: number;
|
|
6
|
+
filesCreated: string[];
|
|
7
|
+
filesUpdated: string[];
|
|
8
|
+
filesDeleted: string[];
|
|
9
|
+
approach: string;
|
|
10
|
+
decisions: string[];
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Tasks {
|
|
15
|
+
total: number;
|
|
16
|
+
completed: number;
|
|
17
|
+
items: any[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SidePanelState {
|
|
21
|
+
isOpen: boolean;
|
|
22
|
+
node: any | null;
|
|
23
|
+
content: string;
|
|
24
|
+
workSummaries: WorkSummary[];
|
|
25
|
+
expandedSummaries: boolean;
|
|
26
|
+
tasks: Tasks | null;
|
|
27
|
+
expandedTasks: boolean;
|
|
28
|
+
|
|
29
|
+
open: (node: any) => Promise<void>;
|
|
30
|
+
close: () => void;
|
|
31
|
+
toggleSummaries: () => void;
|
|
32
|
+
toggleTasks: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const useSidePanelStore = create<SidePanelState>((set) => ({
|
|
36
|
+
isOpen: false,
|
|
37
|
+
node: null,
|
|
38
|
+
content: '',
|
|
39
|
+
workSummaries: [],
|
|
40
|
+
expandedSummaries: false,
|
|
41
|
+
tasks: null,
|
|
42
|
+
expandedTasks: false,
|
|
43
|
+
|
|
44
|
+
open: async (node: any) => {
|
|
45
|
+
set({
|
|
46
|
+
node,
|
|
47
|
+
workSummaries: [],
|
|
48
|
+
expandedSummaries: false,
|
|
49
|
+
tasks: null,
|
|
50
|
+
expandedTasks: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const detail = await apiClient.fetchNode(node.id);
|
|
55
|
+
set({ content: detail.content, isOpen: true });
|
|
56
|
+
|
|
57
|
+
if (node.type === 'feature' || node.id?.startsWith('feat_')) {
|
|
58
|
+
try {
|
|
59
|
+
const pStatus = await apiClient.getPipelineStatus(node.id);
|
|
60
|
+
set({
|
|
61
|
+
workSummaries: pStatus?.workSummaries?.length > 0 ? pStatus.workSummaries : [],
|
|
62
|
+
tasks: pStatus?.tasks ?? null,
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
// Pipeline status may not exist for all features
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error('Failed to fetch node:', err);
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
close: () => set({ isOpen: false }),
|
|
74
|
+
toggleSummaries: () => set((s) => ({ expandedSummaries: !s.expandedSummaries })),
|
|
75
|
+
toggleTasks: () => set((s) => ({ expandedTasks: !s.expandedTasks })),
|
|
76
|
+
}));
|
|
@@ -1,47 +1,79 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Map existing CSS custom properties to Tailwind theme tokens.
|
|
4
|
+
Token names are chosen so generated utility classes read naturally:
|
|
5
|
+
bg-surface, text-content, border-edge, bg-panel, etc. */
|
|
6
|
+
@theme {
|
|
7
|
+
--color-surface: var(--bg-primary);
|
|
8
|
+
--color-surface-alt: var(--bg-secondary);
|
|
9
|
+
--color-surface-raised: var(--bg-tertiary);
|
|
10
|
+
--color-edge: var(--border-color);
|
|
11
|
+
--color-content: var(--text-primary);
|
|
12
|
+
--color-content-secondary: var(--text-secondary);
|
|
13
|
+
--color-content-muted: var(--text-muted);
|
|
14
|
+
--color-accent: var(--accent);
|
|
15
|
+
--color-accent-secondary: var(--accent-secondary);
|
|
16
|
+
--color-completeness-bg: var(--completeness-bg);
|
|
17
|
+
--color-completeness-fill: var(--completeness-fill);
|
|
18
|
+
--color-panel: var(--panel-bg);
|
|
19
|
+
--color-panel-shadow: var(--panel-shadow);
|
|
20
|
+
--color-tab-active: var(--tab-active-bg);
|
|
21
|
+
--color-tab-hover: var(--tab-hover-bg);
|
|
22
|
+
--color-error: var(--error-color);
|
|
23
|
+
|
|
24
|
+
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", monospace;
|
|
25
|
+
--font-system: "Inter", system-ui, -apple-system, sans-serif;
|
|
26
|
+
}
|
|
27
|
+
|
|
1
28
|
/* Graph Visualization Web UI — Styles */
|
|
2
29
|
/* Minimal developer tool aesthetic, dark/light theme support */
|
|
3
30
|
|
|
4
31
|
:root {
|
|
5
32
|
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", monospace;
|
|
6
|
-
--font-system: system-ui, -apple-system, sans-serif;
|
|
33
|
+
--font-system: "Inter", system-ui, -apple-system, sans-serif;
|
|
7
34
|
}
|
|
8
35
|
|
|
9
36
|
[data-theme="dark"] {
|
|
10
|
-
--bg-primary: #
|
|
11
|
-
--bg-secondary: #
|
|
12
|
-
--bg-tertiary: #
|
|
13
|
-
--border-color: #
|
|
14
|
-
--text-primary: #
|
|
15
|
-
--text-secondary: #
|
|
16
|
-
--text-muted: #
|
|
17
|
-
--accent: #
|
|
18
|
-
--
|
|
19
|
-
--completeness-
|
|
20
|
-
--
|
|
21
|
-
--panel-
|
|
22
|
-
--
|
|
23
|
-
--tab-
|
|
24
|
-
--
|
|
37
|
+
--bg-primary: #0c0c0e;
|
|
38
|
+
--bg-secondary: #18191c;
|
|
39
|
+
--bg-tertiary: #222326;
|
|
40
|
+
--border-color: #2e2f33;
|
|
41
|
+
--text-primary: #f0f0f0;
|
|
42
|
+
--text-secondary: #9a9ba0;
|
|
43
|
+
--text-muted: #56575c;
|
|
44
|
+
--accent: #b5e853;
|
|
45
|
+
--accent-secondary: #f0a050;
|
|
46
|
+
--completeness-bg: #2e2f33;
|
|
47
|
+
--completeness-fill: #b5e853;
|
|
48
|
+
--panel-bg: #18191c;
|
|
49
|
+
--panel-shadow: rgba(0, 0, 0, 0.5);
|
|
50
|
+
--tab-active-bg: #222326;
|
|
51
|
+
--tab-hover-bg: #2e2f33;
|
|
52
|
+
--error-color: #f06060;
|
|
25
53
|
}
|
|
26
54
|
|
|
27
55
|
[data-theme="light"] {
|
|
28
|
-
--bg-primary: #
|
|
56
|
+
--bg-primary: #fafbfc;
|
|
29
57
|
--bg-secondary: #ffffff;
|
|
30
|
-
--bg-tertiary: #
|
|
31
|
-
--border-color: #
|
|
32
|
-
--text-primary: #
|
|
33
|
-
--text-secondary: #
|
|
34
|
-
--text-muted: #
|
|
35
|
-
--accent: #
|
|
36
|
-
--
|
|
37
|
-
--completeness-
|
|
58
|
+
--bg-tertiary: #f4f5f7;
|
|
59
|
+
--border-color: #e8e9ec;
|
|
60
|
+
--text-primary: #1a1a1a;
|
|
61
|
+
--text-secondary: #5a5b60;
|
|
62
|
+
--text-muted: #a0a1a6;
|
|
63
|
+
--accent: #4a9e1e;
|
|
64
|
+
--accent-secondary: #d08530;
|
|
65
|
+
--completeness-bg: #eeeff1;
|
|
66
|
+
--completeness-fill: #4a9e1e;
|
|
38
67
|
--panel-bg: #ffffff;
|
|
39
|
-
--panel-shadow: rgba(0, 0, 0, 0.
|
|
68
|
+
--panel-shadow: rgba(0, 0, 0, 0.08);
|
|
40
69
|
--tab-active-bg: #ffffff;
|
|
41
|
-
--tab-hover-bg: #
|
|
42
|
-
--error-color: #
|
|
70
|
+
--tab-hover-bg: #f0f1f3;
|
|
71
|
+
--error-color: #d93025;
|
|
43
72
|
}
|
|
44
73
|
|
|
74
|
+
/* Legacy CSS wrapped in @layer so Tailwind utilities take precedence */
|
|
75
|
+
@layer base {
|
|
76
|
+
|
|
45
77
|
* {
|
|
46
78
|
margin: 0;
|
|
47
79
|
padding: 0;
|
|
@@ -400,82 +432,12 @@ html, body, #root {
|
|
|
400
432
|
|
|
401
433
|
/* Kanban container */
|
|
402
434
|
#kanban-container {
|
|
403
|
-
display: none;
|
|
404
|
-
width: 100%;
|
|
405
|
-
height: calc(100vh - 44px);
|
|
406
|
-
overflow: auto;
|
|
407
|
-
padding: 12px;
|
|
435
|
+
display: none; /* legacy — KanbanRoute uses Tailwind now */
|
|
408
436
|
color: var(--text-secondary);
|
|
409
437
|
font-size: 12px;
|
|
410
438
|
}
|
|
411
439
|
|
|
412
|
-
/* Kanban board */
|
|
413
|
-
.kanban-board {
|
|
414
|
-
display: flex;
|
|
415
|
-
gap: 12px;
|
|
416
|
-
height: 100%;
|
|
417
|
-
min-width: min-content;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
.kanban-column {
|
|
421
|
-
flex: 1;
|
|
422
|
-
min-width: 220px;
|
|
423
|
-
max-width: 320px;
|
|
424
|
-
display: flex;
|
|
425
|
-
flex-direction: column;
|
|
426
|
-
background: var(--bg-secondary);
|
|
427
|
-
border: 1px solid var(--border-color);
|
|
428
|
-
border-radius: 6px;
|
|
429
|
-
overflow: hidden;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
.kanban-column-header {
|
|
433
|
-
display: flex;
|
|
434
|
-
align-items: center;
|
|
435
|
-
justify-content: space-between;
|
|
436
|
-
padding: 10px 12px;
|
|
437
|
-
border-bottom: 1px solid var(--border-color);
|
|
438
|
-
background: var(--bg-tertiary);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
.kanban-column-title {
|
|
442
|
-
font-weight: 600;
|
|
443
|
-
font-size: 12px;
|
|
444
|
-
color: var(--text-primary);
|
|
445
|
-
text-transform: uppercase;
|
|
446
|
-
letter-spacing: 0.5px;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
.kanban-column-count {
|
|
450
|
-
font-size: 11px;
|
|
451
|
-
color: var(--text-muted);
|
|
452
|
-
background: var(--bg-primary);
|
|
453
|
-
padding: 1px 6px;
|
|
454
|
-
border-radius: 8px;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
.kanban-column-body {
|
|
458
|
-
flex: 1;
|
|
459
|
-
overflow-y: auto;
|
|
460
|
-
padding: 8px;
|
|
461
|
-
display: flex;
|
|
462
|
-
flex-direction: column;
|
|
463
|
-
gap: 8px;
|
|
464
|
-
min-height: 60px;
|
|
465
|
-
transition: background 0.15s;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
.kanban-column-body.drop-target {
|
|
469
|
-
background: rgba(77, 171, 247, 0.05);
|
|
470
|
-
border: 1px dashed var(--accent);
|
|
471
|
-
border-radius: 4px;
|
|
472
|
-
margin: 4px;
|
|
473
|
-
padding: 4px;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
.kanban-column-body.drag-over {
|
|
477
|
-
background: rgba(77, 171, 247, 0.12);
|
|
478
|
-
}
|
|
440
|
+
/* Kanban board — layout now handled by Tailwind in KanbanView.tsx */
|
|
479
441
|
|
|
480
442
|
/* Kanban cards */
|
|
481
443
|
.kanban-card {
|
|
@@ -3804,3 +3766,5 @@ html, body, #root {
|
|
|
3804
3766
|
.git-modal-btn-danger:hover {
|
|
3805
3767
|
background: rgba(255, 107, 107, 0.1);
|
|
3806
3768
|
}
|
|
3769
|
+
|
|
3770
|
+
} /* end @layer base */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineConfig, loadEnv } from 'vite';
|
|
2
2
|
import react from '@vitejs/plugin-react';
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { dirname, resolve } from 'node:path';
|
|
5
6
|
|
|
@@ -12,7 +13,7 @@ export default defineConfig(({ mode }) => {
|
|
|
12
13
|
const backendPort = parseInt(env.PORT || '3000', 10);
|
|
13
14
|
|
|
14
15
|
return {
|
|
15
|
-
plugins: [react()],
|
|
16
|
+
plugins: [tailwindcss(), react()],
|
|
16
17
|
build: {
|
|
17
18
|
outDir: 'dist',
|
|
18
19
|
emptyOutDir: true,
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { eq, and } from 'drizzle-orm';
|
|
8
8
|
import { getDb, getProjectRoot as _getProjectRoot } from './db.js';
|
|
9
|
-
import { nodes, edges } from '../db/schema.js';
|
|
9
|
+
import { nodes, edges, projects } from '../db/schema.js';
|
|
10
10
|
|
|
11
11
|
export { getProjectRoot } from './db.js';
|
|
12
12
|
|
|
@@ -26,13 +26,19 @@ export const readGraph = async (projectId?: string) => {
|
|
|
26
26
|
? (() => { const ids = new Set(allNodes.map(n => n.id)); return allEdges.filter(e => ids.has(e.fromId) && ids.has(e.toId)); })()
|
|
27
27
|
: allEdges;
|
|
28
28
|
|
|
29
|
+
let projectRow;
|
|
30
|
+
if (projectId) {
|
|
31
|
+
const rows = await db.select().from(projects).where(eq(projects.id, projectId));
|
|
32
|
+
projectRow = rows[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
return {
|
|
30
36
|
version: '1.0',
|
|
31
37
|
project: {
|
|
32
|
-
name: '',
|
|
38
|
+
name: projectRow?.name || '',
|
|
33
39
|
description: '',
|
|
34
|
-
created_at: '',
|
|
35
|
-
updated_at: '',
|
|
40
|
+
created_at: projectRow?.createdAt || '',
|
|
41
|
+
updated_at: projectRow?.updatedAt || '',
|
|
36
42
|
},
|
|
37
43
|
nodes: allNodes.map(n => ({
|
|
38
44
|
id: n.id,
|
|
@@ -107,7 +113,7 @@ export const patchNode = async (id, fields) => {
|
|
|
107
113
|
throw new Error(`Node not found in graph: ${id}`);
|
|
108
114
|
}
|
|
109
115
|
|
|
110
|
-
const updates = {};
|
|
116
|
+
const updates: Record<string, unknown> = {};
|
|
111
117
|
if (fields.status !== undefined) updates.status = fields.status;
|
|
112
118
|
if (fields.completeness !== undefined) updates.completeness = fields.completeness;
|
|
113
119
|
if (fields.open_questions_count !== undefined) updates.openQuestionsCount = fields.open_questions_count;
|
|
@@ -226,8 +226,8 @@ npx tsx packages/shared/tools/search_nodes.ts --project-id <project_id> --comple
|
|
|
226
226
|
npx tsx packages/shared/tools/get_node.ts <node_id> --project-id <project_id>
|
|
227
227
|
npx tsx packages/shared/tools/get_node.ts --name "Node Name" --project-id <project_id>
|
|
228
228
|
```
|
|
229
|
-
Returns
|
|
230
|
-
with direction, relation type, name, type, and status.
|
|
229
|
+
Returns the node content (formatted as markdown) plus a Relations section listing
|
|
230
|
+
all connected nodes with direction, relation type, name, type, and status.
|
|
231
231
|
|
|
232
232
|
### add_node
|
|
233
233
|
```
|
|
@@ -314,7 +314,7 @@ The `search_nodes --query` tool returns graph-aware relevance-ranked results.
|
|
|
314
314
|
- **relates_to** — loose association (weakest)
|
|
315
315
|
|
|
316
316
|
## Rules
|
|
317
|
-
1.
|
|
317
|
+
1. All graph data is stored in SQLite (via Drizzle ORM) — always use the tools above to read and write data
|
|
318
318
|
2. Always call get_node before update_node — never update blind
|
|
319
319
|
3. Always record answers via resolve_question — don't just add a note
|
|
320
320
|
4. Nodes should reflect reality (codebase + user statements), not inferences —
|