jettypod 4.4.118 → 4.4.121
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/.env +4 -3
- package/Cargo.lock +6450 -0
- package/Cargo.toml +35 -0
- package/README.md +5 -1
- package/TAURI-MIGRATION-PLAN.md +840 -0
- package/apps/dashboard/app/connect-claude/page.tsx +5 -6
- package/apps/dashboard/app/decision/[id]/page.tsx +63 -58
- package/apps/dashboard/app/demo/gates/page.tsx +43 -45
- package/apps/dashboard/app/design-system/page.tsx +868 -0
- package/apps/dashboard/app/globals.css +80 -4
- package/apps/dashboard/app/install-claude/page.tsx +4 -6
- package/apps/dashboard/app/login/page.tsx +72 -54
- package/apps/dashboard/app/page.tsx +101 -48
- package/apps/dashboard/app/settings/page.tsx +61 -13
- package/apps/dashboard/app/signup/page.tsx +242 -0
- package/apps/dashboard/app/subscribe/page.tsx +0 -2
- package/apps/dashboard/app/tests/page.tsx +37 -4
- package/apps/dashboard/app/welcome/page.tsx +13 -16
- package/apps/dashboard/app/work/[id]/page.tsx +117 -118
- package/apps/dashboard/app/work/[id]/proof/page.tsx +1489 -0
- package/apps/dashboard/components/AppShell.tsx +92 -85
- package/apps/dashboard/components/CardMenu.tsx +45 -12
- package/apps/dashboard/components/ClaudePanel.tsx +771 -850
- package/apps/dashboard/components/ClaudePanelInput.tsx +43 -15
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +17 -34
- package/apps/dashboard/components/CopyableId.tsx +3 -4
- package/apps/dashboard/components/DetailReviewActions.tsx +100 -0
- package/apps/dashboard/components/DragContext.tsx +134 -63
- package/apps/dashboard/components/DraggableCard.tsx +3 -5
- package/apps/dashboard/components/DropZone.tsx +6 -7
- package/apps/dashboard/components/EditableDetailDescription.tsx +7 -13
- package/apps/dashboard/components/EditableDetailTitle.tsx +6 -13
- package/apps/dashboard/components/EditableTitle.tsx +26 -7
- package/apps/dashboard/components/ElapsedTimer.tsx +66 -0
- package/apps/dashboard/components/EpicGroup.tsx +359 -0
- package/apps/dashboard/components/GateCard.tsx +79 -17
- package/apps/dashboard/components/GateChoiceCard.tsx +15 -18
- package/apps/dashboard/components/InstallClaudeScreen.tsx +15 -32
- package/apps/dashboard/components/JettyLoader.tsx +37 -0
- package/apps/dashboard/components/KanbanBoard.tsx +368 -958
- package/apps/dashboard/components/KanbanCard.tsx +740 -0
- package/apps/dashboard/components/LazyCard.tsx +62 -0
- package/apps/dashboard/components/LazyMarkdown.tsx +11 -0
- package/apps/dashboard/components/MainNav.tsx +38 -73
- package/apps/dashboard/components/MessageBlock.tsx +468 -0
- package/apps/dashboard/components/ModeStartCard.tsx +15 -16
- package/apps/dashboard/components/OnboardingWelcome.tsx +213 -0
- package/apps/dashboard/components/PlaceholderCard.tsx +3 -4
- package/apps/dashboard/components/ProjectSwitcher.tsx +30 -30
- package/apps/dashboard/components/PrototypeTimeline.tsx +72 -51
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +406 -388
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +373 -235
- package/apps/dashboard/components/ReviewFooter.tsx +139 -0
- package/apps/dashboard/components/SessionList.tsx +19 -19
- package/apps/dashboard/components/SubscribeContent.tsx +91 -47
- package/apps/dashboard/components/TestTree.tsx +16 -16
- package/apps/dashboard/components/TipCard.tsx +16 -17
- package/apps/dashboard/components/Toast.tsx +5 -6
- package/apps/dashboard/components/TypeIcon.tsx +55 -0
- package/apps/dashboard/components/ViewModeToolbar.tsx +104 -0
- package/apps/dashboard/components/WaveCompletionAnimation.tsx +52 -65
- package/apps/dashboard/components/WelcomeScreen.tsx +19 -35
- package/apps/dashboard/components/WorkItemHeader.tsx +4 -5
- package/apps/dashboard/components/WorkItemTree.tsx +11 -32
- package/apps/dashboard/components/settings/AccountSection.tsx +55 -35
- package/apps/dashboard/components/settings/AiContextSection.tsx +89 -0
- package/apps/dashboard/components/settings/ContextDocumentsSection.tsx +317 -0
- package/apps/dashboard/components/settings/EnvVarsSection.tsx +74 -152
- package/apps/dashboard/components/settings/GeneralSection.tsx +162 -56
- package/apps/dashboard/components/settings/ProjectStackSection.tsx +948 -0
- package/apps/dashboard/components/settings/SettingsLayout.tsx +4 -5
- package/apps/dashboard/components/ui/Button.tsx +104 -0
- package/apps/dashboard/components/ui/Input.tsx +78 -0
- package/apps/dashboard/components.json +1 -1
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +711 -418
- package/apps/dashboard/contexts/ConnectionStatusContext.tsx +25 -5
- package/apps/dashboard/contexts/UsageContext.tsx +87 -32
- package/apps/dashboard/dev.sh +35 -0
- package/apps/dashboard/eslint.config.mjs +9 -9
- package/apps/dashboard/hooks/useKanbanAnimation.ts +29 -0
- package/apps/dashboard/hooks/useKanbanUndo.ts +83 -0
- package/apps/dashboard/hooks/useWebSocket.ts +138 -83
- package/apps/dashboard/index.html +73 -0
- package/apps/dashboard/lib/constants.ts +43 -0
- package/apps/dashboard/lib/data-bridge.ts +722 -0
- package/apps/dashboard/lib/db.ts +69 -1265
- package/apps/dashboard/lib/environment-config.ts +173 -0
- package/apps/dashboard/lib/environment-verification.ts +119 -0
- package/apps/dashboard/lib/kanban-utils.ts +270 -0
- package/apps/dashboard/lib/proof-run.ts +495 -0
- package/apps/dashboard/lib/proof-scenario-runner.ts +346 -0
- package/apps/dashboard/lib/run-migrations.js +27 -2
- package/apps/dashboard/lib/service-recovery.ts +326 -0
- package/apps/dashboard/lib/session-state-machine.ts +1 -0
- package/apps/dashboard/lib/session-state-utils.ts +0 -164
- package/apps/dashboard/lib/session-stream-manager.ts +308 -134
- package/apps/dashboard/lib/shadows.ts +7 -0
- package/apps/dashboard/lib/stream-manager-registry.ts +46 -6
- package/apps/dashboard/lib/tauri-bridge.ts +102 -0
- package/apps/dashboard/lib/tauri.ts +106 -0
- package/apps/dashboard/lib/utils.ts +6 -0
- package/apps/dashboard/next-env.d.ts +1 -1
- package/apps/dashboard/package.json +21 -32
- package/apps/dashboard/public/bug-icon.png +0 -0
- package/apps/dashboard/public/buoy-icon.png +0 -0
- package/apps/dashboard/public/fonts/Satoshi-Variable.woff2 +0 -0
- package/apps/dashboard/public/fonts/Satoshi-VariableItalic.woff2 +0 -0
- package/apps/dashboard/public/in-flight-seagull.png +0 -0
- package/apps/dashboard/public/jetty-icon-loading-alt.svg +11 -0
- package/apps/dashboard/public/jetty-icon-loading.svg +11 -0
- package/apps/dashboard/public/jettypod_logo.png +0 -0
- package/apps/dashboard/public/pier-icon.png +0 -0
- package/apps/dashboard/public/star-icon.png +0 -0
- package/apps/dashboard/public/wrench-icon.png +0 -0
- package/apps/dashboard/scripts/tauri-build.js +228 -0
- package/apps/dashboard/scripts/upload-tauri-to-r2.js +125 -0
- package/apps/dashboard/scripts/ws-server.js +191 -0
- package/apps/dashboard/src/main.tsx +12 -0
- package/apps/dashboard/src/router.tsx +107 -0
- package/apps/dashboard/src/vite-env.d.ts +1 -0
- package/apps/dashboard/tsconfig.json +7 -12
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -1
- package/apps/dashboard/vite.config.ts +33 -0
- package/apps/update-server/src/index.ts +228 -80
- package/claude-hooks/global-guardrails.js +14 -13
- package/crates/jettypod-cli/Cargo.toml +19 -0
- package/crates/jettypod-cli/src/commands.rs +1249 -0
- package/crates/jettypod-cli/src/main.rs +595 -0
- package/crates/jettypod-core/Cargo.toml +26 -0
- package/crates/jettypod-core/build.rs +98 -0
- package/crates/jettypod-core/migrations/V1__baseline.sql +197 -0
- package/crates/jettypod-core/migrations/V2__work_items_indexes.sql +6 -0
- package/crates/jettypod-core/migrations/V3__qa_steps.sql +2 -0
- package/crates/jettypod-core/src/auth.rs +294 -0
- package/crates/jettypod-core/src/config.rs +397 -0
- package/crates/jettypod-core/src/db/mod.rs +507 -0
- package/crates/jettypod-core/src/db/recovery.rs +114 -0
- package/crates/jettypod-core/src/db/startup.rs +101 -0
- package/crates/jettypod-core/src/db/validate.rs +149 -0
- package/crates/jettypod-core/src/error.rs +76 -0
- package/crates/jettypod-core/src/git.rs +458 -0
- package/crates/jettypod-core/src/lib.rs +20 -0
- package/crates/jettypod-core/src/sessions.rs +625 -0
- package/crates/jettypod-core/src/skills.rs +556 -0
- package/crates/jettypod-core/src/work.rs +1086 -0
- package/crates/jettypod-core/src/worktree.rs +628 -0
- package/crates/jettypod-core/src/ws.rs +767 -0
- package/cucumber-test.cjs +6 -0
- package/cucumber.js +9 -3
- package/docs/COMMAND_REFERENCE.md +34 -0
- package/hooks/post-checkout +32 -75
- package/hooks/post-merge +111 -10
- package/jest.setup.js +1 -0
- package/jettypod.js +145 -116
- package/lib/bdd-preflight.js +96 -0
- package/lib/chore-taxonomy.js +33 -10
- package/lib/database.js +36 -16
- package/lib/db-watcher.js +1 -1
- package/lib/git-hooks/pre-commit +1 -1
- package/lib/jettypod-backup.js +27 -4
- package/lib/merge-lock.js +111 -253
- package/lib/migrations/027-plan-at-creation-column.js +3 -1
- package/lib/migrations/029-remove-autoincrement.js +307 -0
- package/lib/migrations/029-rename-corrupted-to-cleaned.js +149 -0
- package/lib/migrations/030-rejection-round-columns.js +54 -0
- package/lib/migrations/031-session-isolation-index.js +17 -0
- package/lib/migrations/index.js +47 -4
- package/lib/schema.js +10 -5
- package/lib/seed-onboarding.js +1 -1
- package/lib/update-command/index.js +9 -175
- package/lib/work-commands/index.js +144 -19
- package/lib/work-tracking/index.js +148 -27
- package/lib/worktree-diagnostics.js +16 -16
- package/lib/worktree-facade.js +1 -1
- package/lib/worktree-manager.js +8 -8
- package/lib/worktree-reconciler.js +5 -5
- package/package.json +9 -2
- package/scripts/ndjson-to-cucumber-json.js +152 -0
- package/scripts/postinstall.js +25 -0
- package/skills-templates/bug-mode/SKILL.md +79 -20
- package/skills-templates/bug-planning/SKILL.md +25 -29
- package/skills-templates/chore-mode/SKILL.md +171 -69
- package/skills-templates/chore-mode/verification.js +51 -10
- package/skills-templates/chore-planning/SKILL.md +47 -18
- package/skills-templates/design-system-selection/SKILL.md +273 -0
- package/skills-templates/epic-planning/SKILL.md +82 -48
- package/skills-templates/external-transition/SKILL.md +47 -47
- package/skills-templates/feature-planning/SKILL.md +173 -74
- package/skills-templates/production-mode/SKILL.md +69 -49
- package/skills-templates/request-routing/SKILL.md +4 -4
- package/skills-templates/simple-improvement/SKILL.md +74 -29
- package/skills-templates/speed-mode/SKILL.md +217 -141
- package/skills-templates/stable-mode/SKILL.md +148 -89
- package/apps/dashboard/README.md +0 -36
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +0 -386
- package/apps/dashboard/app/api/claude/[workItemId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/[workItemId]/route.ts +0 -167
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/content/route.ts +0 -52
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +0 -378
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/pin/route.ts +0 -24
- package/apps/dashboard/app/api/claude/sessions/cleanup/route.ts +0 -34
- package/apps/dashboard/app/api/claude/sessions/route.ts +0 -184
- package/apps/dashboard/app/api/decisions/[id]/route.ts +0 -25
- package/apps/dashboard/app/api/internal/set-project/route.ts +0 -17
- package/apps/dashboard/app/api/kanban/route.ts +0 -15
- package/apps/dashboard/app/api/settings/env-vars/route.ts +0 -125
- package/apps/dashboard/app/api/settings/general/route.ts +0 -21
- package/apps/dashboard/app/api/tests/route.ts +0 -9
- package/apps/dashboard/app/api/tests/run/route.ts +0 -82
- package/apps/dashboard/app/api/tests/run/stream/route.ts +0 -71
- package/apps/dashboard/app/api/tests/undefined/route.ts +0 -9
- package/apps/dashboard/app/api/usage/route.ts +0 -17
- package/apps/dashboard/app/api/work/[id]/description/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/epic/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/order/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/status/route.ts +0 -21
- package/apps/dashboard/app/api/work/[id]/title/route.ts +0 -21
- package/apps/dashboard/app/layout.tsx +0 -43
- package/apps/dashboard/components/UpgradeBanner.tsx +0 -29
- package/apps/dashboard/electron/ipc-handlers.js +0 -1028
- package/apps/dashboard/electron/main.js +0 -2124
- package/apps/dashboard/electron/preload.js +0 -123
- package/apps/dashboard/electron/session-manager.js +0 -141
- package/apps/dashboard/electron-builder.config.js +0 -357
- package/apps/dashboard/hooks/useClaudeSessions.ts +0 -299
- package/apps/dashboard/lib/claude-process-manager.ts +0 -492
- package/apps/dashboard/lib/db-bridge.ts +0 -282
- package/apps/dashboard/lib/prototypes.ts +0 -202
- package/apps/dashboard/lib/test-results-db.ts +0 -307
- package/apps/dashboard/lib/tests.ts +0 -282
- package/apps/dashboard/next.config.js +0 -50
- package/apps/dashboard/postcss.config.mjs +0 -7
- package/apps/dashboard/public/file.svg +0 -1
- package/apps/dashboard/public/globe.svg +0 -1
- package/apps/dashboard/public/next.svg +0 -1
- package/apps/dashboard/public/vercel.svg +0 -1
- package/apps/dashboard/public/window.svg +0 -1
- package/apps/dashboard/scripts/download-node.js +0 -104
- package/apps/dashboard/scripts/upload-to-r2.js +0 -89
- package/docs/bdd-guidance.md +0 -390
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useRef, useCallback } from 'react';
|
|
4
|
+
import { openDialog } from '@/lib/tauri';
|
|
5
|
+
import { dataBridge } from '@/lib/data-bridge';
|
|
6
|
+
import type { ContextDocument } from '@/lib/data-bridge';
|
|
7
|
+
|
|
8
|
+
interface ContextDocumentsSectionProps {
|
|
9
|
+
initialDocuments: ContextDocument[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ContextDocumentsSection({ initialDocuments }: ContextDocumentsSectionProps) {
|
|
13
|
+
const [documents, setDocuments] = useState<ContextDocument[]>(initialDocuments);
|
|
14
|
+
const [dropdownOpen, setDropdownOpen] = useState(false);
|
|
15
|
+
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
|
16
|
+
const [editingDoc, setEditingDoc] = useState<{ name: string; content: string }>({ name: '', content: '' });
|
|
17
|
+
const [isAdding, setIsAdding] = useState(false);
|
|
18
|
+
const [dragOver, setDragOver] = useState(false);
|
|
19
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
20
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
21
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
22
|
+
|
|
23
|
+
const handleAddText = () => {
|
|
24
|
+
setDropdownOpen(false);
|
|
25
|
+
setIsAdding(true);
|
|
26
|
+
setEditingDoc({ name: '', content: '' });
|
|
27
|
+
setValidationError(null);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const handleAddFile = async () => {
|
|
31
|
+
setDropdownOpen(false);
|
|
32
|
+
const selected = await openDialog({ multiple: true, title: 'Select context files' });
|
|
33
|
+
if (!selected) return;
|
|
34
|
+
const paths = Array.isArray(selected) ? selected : [selected];
|
|
35
|
+
for (const filePath of paths) {
|
|
36
|
+
if (typeof filePath === 'string') {
|
|
37
|
+
const name = filePath.split('/').pop() || filePath;
|
|
38
|
+
// Skip duplicates by name
|
|
39
|
+
if (documents.some(d => d.name === name)) continue;
|
|
40
|
+
const doc: ContextDocument = { type: 'file', name, path: filePath };
|
|
41
|
+
await dataBridge.addContextDocument(doc);
|
|
42
|
+
setDocuments(prev => [...prev, doc]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handleSaveNew = async () => {
|
|
48
|
+
if (!editingDoc.name.trim()) {
|
|
49
|
+
setValidationError('Name is required');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!editingDoc.content.trim()) {
|
|
53
|
+
setValidationError('Content is required');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setValidationError(null);
|
|
57
|
+
const doc: ContextDocument = { type: 'text', name: editingDoc.name.trim(), content: editingDoc.content };
|
|
58
|
+
await dataBridge.addContextDocument(doc);
|
|
59
|
+
setDocuments(prev => [...prev, doc]);
|
|
60
|
+
setIsAdding(false);
|
|
61
|
+
setEditingDoc({ name: '', content: '' });
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const handleCancelNew = () => {
|
|
65
|
+
setIsAdding(false);
|
|
66
|
+
setEditingDoc({ name: '', content: '' });
|
|
67
|
+
setValidationError(null);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleEdit = (index: number) => {
|
|
71
|
+
const doc = documents[index];
|
|
72
|
+
setEditingIndex(index);
|
|
73
|
+
setEditingDoc({ name: doc.name, content: doc.content || '' });
|
|
74
|
+
setValidationError(null);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleSaveEdit = async () => {
|
|
78
|
+
if (editingIndex === null) return;
|
|
79
|
+
if (!editingDoc.name.trim()) {
|
|
80
|
+
setValidationError('Name is required');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!editingDoc.content.trim()) {
|
|
84
|
+
setValidationError('Content is required');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
setValidationError(null);
|
|
88
|
+
const doc: ContextDocument = { type: 'text', name: editingDoc.name.trim(), content: editingDoc.content };
|
|
89
|
+
await dataBridge.updateContextDocument(editingIndex, doc);
|
|
90
|
+
setDocuments(prev => prev.map((d, i) => i === editingIndex ? doc : d));
|
|
91
|
+
setEditingIndex(null);
|
|
92
|
+
setEditingDoc({ name: '', content: '' });
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleCancelEdit = () => {
|
|
96
|
+
setEditingIndex(null);
|
|
97
|
+
setEditingDoc({ name: '', content: '' });
|
|
98
|
+
setValidationError(null);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleRemove = async (index: number) => {
|
|
102
|
+
await dataBridge.removeContextDocument(index);
|
|
103
|
+
setDocuments(prev => prev.filter((_, i) => i !== index));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleDragOver = useCallback((e: React.DragEvent) => {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
setDragOver(true);
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
|
112
|
+
if (containerRef.current && !containerRef.current.contains(e.relatedTarget as Node)) {
|
|
113
|
+
setDragOver(false);
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
const handleDrop = useCallback(async (e: React.DragEvent) => {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
setDragOver(false);
|
|
120
|
+
const files = Array.from(e.dataTransfer.files);
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
const filePath = (file as unknown as { path?: string }).path || file.name;
|
|
123
|
+
const name = file.name;
|
|
124
|
+
// Skip duplicates by name
|
|
125
|
+
if (documents.some(d => d.name === name)) continue;
|
|
126
|
+
const doc: ContextDocument = { type: 'file', name, path: filePath };
|
|
127
|
+
await dataBridge.addContextDocument(doc);
|
|
128
|
+
setDocuments(prev => [...prev, doc]);
|
|
129
|
+
}
|
|
130
|
+
}, [documents]);
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<div className="mt-8">
|
|
134
|
+
<div className="flex items-center justify-between mb-3">
|
|
135
|
+
<h3 className="text-[15px] font-medium text-zinc-900 dark:text-zinc-100">
|
|
136
|
+
Context Documents
|
|
137
|
+
</h3>
|
|
138
|
+
<div className="relative" ref={dropdownRef}>
|
|
139
|
+
<button
|
|
140
|
+
onClick={() => setDropdownOpen(!dropdownOpen)}
|
|
141
|
+
className="w-8 h-8 rounded-lg border-2 border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800 text-zinc-400 dark:text-zinc-500 text-lg flex items-center justify-center transition-all duration-200 hover:border-[#819D9F] hover:text-zinc-900 dark:hover:text-zinc-100 active:scale-95"
|
|
142
|
+
>
|
|
143
|
+
+
|
|
144
|
+
</button>
|
|
145
|
+
{dropdownOpen && (
|
|
146
|
+
<>
|
|
147
|
+
<div className="fixed inset-0 z-10" onClick={() => setDropdownOpen(false)} />
|
|
148
|
+
<div className="absolute top-[calc(100%+6px)] right-0 z-20 bg-white dark:bg-zinc-800 border border-zinc-200 dark:border-zinc-700 rounded-xl p-1.5 min-w-[220px] shadow-lg">
|
|
149
|
+
<button
|
|
150
|
+
onClick={handleAddFile}
|
|
151
|
+
className="flex items-center gap-2.5 px-3 py-2.5 rounded-lg w-full text-left text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
|
|
152
|
+
>
|
|
153
|
+
<svg className="w-[18px] h-[18px] text-zinc-400" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
|
|
154
|
+
<path d="M10 4v12M15 9.5c0 2.5-2.5 4.5-5 4.5S5 12 5 9.5V6a3 3 0 016 0v4a1.5 1.5 0 01-3 0V6.5" />
|
|
155
|
+
</svg>
|
|
156
|
+
Upload from device
|
|
157
|
+
</button>
|
|
158
|
+
<button
|
|
159
|
+
onClick={handleAddText}
|
|
160
|
+
className="flex items-center gap-2.5 px-3 py-2.5 rounded-lg w-full text-left text-sm text-zinc-700 dark:text-zinc-200 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
|
|
161
|
+
>
|
|
162
|
+
<svg className="w-[18px] h-[18px] text-zinc-400" viewBox="0 0 20 20" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
163
|
+
<path d="M4 5h12M4 10h8M4 15h5" />
|
|
164
|
+
<path d="M15 12v6M12 15h6" />
|
|
165
|
+
</svg>
|
|
166
|
+
Add text content
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
</>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<div
|
|
175
|
+
ref={containerRef}
|
|
176
|
+
onDragOver={handleDragOver}
|
|
177
|
+
onDragLeave={handleDragLeave}
|
|
178
|
+
onDrop={handleDrop}
|
|
179
|
+
className={`bg-zinc-50 dark:bg-zinc-800/50 rounded-xl border-2 transition-all duration-200 overflow-hidden ${
|
|
180
|
+
dragOver
|
|
181
|
+
? 'border-[#819D9F] shadow-[0_0_0_3px_rgba(129,157,159,0.15)]'
|
|
182
|
+
: 'border-zinc-200 dark:border-zinc-700'
|
|
183
|
+
}`}
|
|
184
|
+
>
|
|
185
|
+
{documents.length === 0 && !isAdding ? (
|
|
186
|
+
<div className="py-10 text-center text-zinc-400 dark:text-zinc-500 text-sm">
|
|
187
|
+
Drop files here or use + to add context
|
|
188
|
+
</div>
|
|
189
|
+
) : (
|
|
190
|
+
<>
|
|
191
|
+
{documents.map((doc, index) => (
|
|
192
|
+
editingIndex === index ? (
|
|
193
|
+
<div key={index} className="bg-zinc-100 dark:bg-zinc-900/50 border-b border-zinc-200/50 dark:border-zinc-700/40">
|
|
194
|
+
<div className="flex items-center gap-2.5 px-3.5 pt-3">
|
|
195
|
+
<Grip />
|
|
196
|
+
<TypeIcon type={doc.type} />
|
|
197
|
+
<input
|
|
198
|
+
type="text"
|
|
199
|
+
value={editingDoc.name}
|
|
200
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, name: e.target.value }))}
|
|
201
|
+
placeholder="Name this snippet..."
|
|
202
|
+
className="flex-1 bg-transparent border-none text-[13px] font-medium text-zinc-900 dark:text-zinc-100 outline-none"
|
|
203
|
+
/>
|
|
204
|
+
</div>
|
|
205
|
+
<div className="px-3.5 mt-2">
|
|
206
|
+
<textarea
|
|
207
|
+
value={editingDoc.content}
|
|
208
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, content: e.target.value }))}
|
|
209
|
+
placeholder="Type or paste your context text here..."
|
|
210
|
+
className="w-full bg-white dark:bg-zinc-800 border-2 border-zinc-200 dark:border-zinc-700 rounded-lg px-3 py-2.5 text-[13px] text-zinc-700 dark:text-zinc-200 leading-relaxed resize-y min-h-[72px] focus:outline-none focus:border-[#819D9F] focus:shadow-[0_0_0_3px_rgba(129,157,159,0.15)] transition-all"
|
|
211
|
+
rows={3}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<div className="flex items-center justify-end gap-2 px-3.5 py-2">
|
|
215
|
+
{validationError && (
|
|
216
|
+
<span className="text-[12px] text-red-500 dark:text-red-400 mr-auto">{validationError}</span>
|
|
217
|
+
)}
|
|
218
|
+
<button onClick={handleCancelEdit} className="px-3.5 py-1.5 rounded-lg border-2 border-zinc-200 dark:border-zinc-700 bg-transparent text-zinc-500 text-[13px] font-medium hover:border-zinc-300 dark:hover:border-zinc-600 hover:text-zinc-700 dark:hover:text-zinc-300 transition-all">Cancel</button>
|
|
219
|
+
<button onClick={handleSaveEdit} className="px-3.5 py-1.5 rounded-lg border-none bg-[#819D9F] text-zinc-900 text-[13px] font-medium hover:brightness-105 transition-all active:scale-[0.98]">Save</button>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
) : (
|
|
223
|
+
<div
|
|
224
|
+
key={index}
|
|
225
|
+
className="group flex items-center px-3.5 py-2.5 border-b border-zinc-200/50 dark:border-zinc-700/40 last:border-b-0 hover:bg-white/50 dark:hover:bg-white/[0.02] transition-colors"
|
|
226
|
+
>
|
|
227
|
+
<Grip />
|
|
228
|
+
<TypeIcon type={doc.type} />
|
|
229
|
+
<div className="flex-1 min-w-0 ml-2.5">
|
|
230
|
+
<div className="text-[13px] font-medium text-zinc-900 dark:text-zinc-100 truncate">{doc.name}</div>
|
|
231
|
+
<div className="text-[11px] text-zinc-400 dark:text-zinc-500 truncate font-mono mt-0.5">
|
|
232
|
+
{doc.type === 'file' ? doc.path : (doc.content || '').slice(0, 80) + ((doc.content || '').length > 80 ? '...' : '')}
|
|
233
|
+
</div>
|
|
234
|
+
</div>
|
|
235
|
+
<div className="flex gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
236
|
+
{doc.type === 'text' && (
|
|
237
|
+
<button onClick={() => handleEdit(index)} className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-zinc-400 hover:bg-zinc-200 dark:hover:bg-zinc-700 hover:text-zinc-700 dark:hover:text-zinc-200 transition-all text-[13px]" title="Edit">
|
|
238
|
+
✎
|
|
239
|
+
</button>
|
|
240
|
+
)}
|
|
241
|
+
<button onClick={() => handleRemove(index)} className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-zinc-400 hover:bg-red-100 dark:hover:bg-red-900/20 hover:text-red-600 dark:hover:text-red-400 transition-all text-[13px]" title="Remove">
|
|
242
|
+
×
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
)
|
|
247
|
+
))}
|
|
248
|
+
|
|
249
|
+
{isAdding && (
|
|
250
|
+
<div className="bg-zinc-100 dark:bg-zinc-900/50 border-b border-zinc-200/50 dark:border-zinc-700/40">
|
|
251
|
+
<div className="flex items-center gap-2.5 px-3.5 pt-3">
|
|
252
|
+
<Grip />
|
|
253
|
+
<TypeIcon type="text" />
|
|
254
|
+
<input
|
|
255
|
+
type="text"
|
|
256
|
+
value={editingDoc.name}
|
|
257
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, name: e.target.value }))}
|
|
258
|
+
placeholder="Name this snippet..."
|
|
259
|
+
className="flex-1 bg-transparent border-none text-[13px] font-medium text-zinc-900 dark:text-zinc-100 outline-none"
|
|
260
|
+
autoFocus
|
|
261
|
+
/>
|
|
262
|
+
</div>
|
|
263
|
+
<div className="px-3.5 mt-2">
|
|
264
|
+
<textarea
|
|
265
|
+
value={editingDoc.content}
|
|
266
|
+
onChange={e => setEditingDoc(prev => ({ ...prev, content: e.target.value }))}
|
|
267
|
+
placeholder="Type or paste your context text here..."
|
|
268
|
+
className="w-full bg-white dark:bg-zinc-800 border-2 border-zinc-200 dark:border-zinc-700 rounded-lg px-3 py-2.5 text-[13px] text-zinc-700 dark:text-zinc-200 leading-relaxed resize-y min-h-[72px] focus:outline-none focus:border-[#819D9F] focus:shadow-[0_0_0_3px_rgba(129,157,159,0.15)] transition-all"
|
|
269
|
+
rows={3}
|
|
270
|
+
/>
|
|
271
|
+
</div>
|
|
272
|
+
<div className="flex items-center justify-end gap-2 px-3.5 py-2">
|
|
273
|
+
{validationError && (
|
|
274
|
+
<span className="text-[12px] text-red-500 dark:text-red-400 mr-auto">{validationError}</span>
|
|
275
|
+
)}
|
|
276
|
+
<button onClick={handleCancelNew} className="px-3.5 py-1.5 rounded-lg border-2 border-zinc-200 dark:border-zinc-700 bg-transparent text-zinc-500 text-[13px] font-medium hover:border-zinc-300 dark:hover:border-zinc-600 hover:text-zinc-700 dark:hover:text-zinc-300 transition-all">Cancel</button>
|
|
277
|
+
<button onClick={handleSaveNew} className="px-3.5 py-1.5 rounded-lg border-none bg-[#819D9F] text-zinc-900 text-[13px] font-medium hover:brightness-105 transition-all active:scale-[0.98]">Save</button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
</>
|
|
282
|
+
)}
|
|
283
|
+
|
|
284
|
+
{dragOver && (
|
|
285
|
+
<div className="py-5 text-center border-t border-dashed border-zinc-300 dark:border-zinc-600">
|
|
286
|
+
<span className="text-[13px] font-medium text-[#819D9F]">Drop files to add as context</span>
|
|
287
|
+
</div>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function Grip() {
|
|
295
|
+
return (
|
|
296
|
+
<div className="flex flex-col gap-[1.5px] cursor-grab opacity-20 hover:opacity-60 transition-opacity p-1">
|
|
297
|
+
<span className="block w-2.5 h-[1.5px] bg-zinc-500 rounded-sm" />
|
|
298
|
+
<span className="block w-2.5 h-[1.5px] bg-zinc-500 rounded-sm" />
|
|
299
|
+
<span className="block w-2.5 h-[1.5px] bg-zinc-500 rounded-sm" />
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function TypeIcon({ type }: { type: 'file' | 'text' }) {
|
|
305
|
+
if (type === 'file') {
|
|
306
|
+
return (
|
|
307
|
+
<div className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#819D9F]/15 text-[#819D9F]">
|
|
308
|
+
F
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return (
|
|
313
|
+
<div className="w-[26px] h-[26px] rounded-md flex items-center justify-center text-[11px] font-bold shrink-0 bg-[#e57a44]/12 text-[#e57a44]">
|
|
314
|
+
T
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
}
|