@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.
Files changed (38) hide show
  1. package/dist/bin/create.js +0 -0
  2. package/package.json +7 -9
  3. package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
  4. package/templates/assistkick-product-system/packages/frontend/package.json +5 -1
  5. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +16 -7
  6. package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +363 -0
  7. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +6 -8
  8. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +92 -188
  9. package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +11 -20
  10. package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +15 -70
  11. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +149 -77
  12. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +254 -0
  13. package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +216 -0
  14. package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +163 -0
  15. package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
  16. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
  17. package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
  18. package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
  19. package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
  20. package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
  21. package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
  22. package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
  23. package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
  24. package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
  25. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
  26. package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
  27. package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +87 -0
  28. package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
  29. package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
  30. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +64 -100
  31. package/templates/assistkick-product-system/packages/frontend/vite.config.ts +2 -1
  32. package/templates/assistkick-product-system/packages/shared/lib/graph.ts +11 -5
  33. package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
  34. package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
  35. package/templates/skills/assistkick-debugger/SKILL.md +2 -2
  36. package/templates/skills/assistkick-developer/SKILL.md +3 -3
  37. package/templates/skills/assistkick-interview/SKILL.md +2 -2
  38. 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,6 @@
1
+ import React from 'react';
2
+ import { UsersView } from '../components/UsersView';
3
+
4
+ export function UsersRoute() {
5
+ return <UsersView visible={true} />;
6
+ }
@@ -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: #1a1b1e;
11
- --bg-secondary: #25262b;
12
- --bg-tertiary: #2c2e33;
13
- --border-color: #373a40;
14
- --text-primary: #c1c2c5;
15
- --text-secondary: #909296;
16
- --text-muted: #5c5f66;
17
- --accent: #4dabf7;
18
- --completeness-bg: #373a40;
19
- --completeness-fill: #69db7c;
20
- --panel-bg: #25262b;
21
- --panel-shadow: rgba(0, 0, 0, 0.4);
22
- --tab-active-bg: #2c2e33;
23
- --tab-hover-bg: #373a40;
24
- --error-color: #ff6b6b;
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: #f8f9fa;
56
+ --bg-primary: #fafbfc;
29
57
  --bg-secondary: #ffffff;
30
- --bg-tertiary: #f1f3f5;
31
- --border-color: #dee2e6;
32
- --text-primary: #212529;
33
- --text-secondary: #495057;
34
- --text-muted: #adb5bd;
35
- --accent: #228be6;
36
- --completeness-bg: #e9ecef;
37
- --completeness-fill: #40c057;
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.15);
68
+ --panel-shadow: rgba(0, 0, 0, 0.08);
40
69
  --tab-active-bg: #ffffff;
41
- --tab-hover-bg: #e9ecef;
42
- --error-color: #e03131;
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 full .md content plus a Relations section listing all connected nodes
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. Never read or write graph.tson or .md files directly — always use the tools
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 —