4 files changed,
+48,
-15
+13,
-6
1@@ -1,12 +1,19 @@
2 <script lang="ts">
3 import { showCommitDialog } from '$lib/stores/ui.ts';
4- import { approvedCount } from '$lib/stores/files.ts';
5+ import { uncommittedCount, loadUncommittedFiles } from '$lib/stores/files.ts';
6
7 let message = $state('');
8 let commitMode = $state<'approved' | 'all'>('approved');
9 let committing = $state(false);
10 let result = $state<{ success: boolean; hash?: string; error?: string } | null>(null);
11
12+ // Refresh uncommitted file counts when dialog opens
13+ $effect(() => {
14+ if ($showCommitDialog) {
15+ loadUncommittedFiles();
16+ }
17+ });
18+
19 async function handleCommit() {
20 if (!message.trim()) return;
21 committing = true;
22@@ -79,19 +86,19 @@
23 onclick={() => (commitMode = 'approved')}
24 >
25 <div class="font-medium">Approved files only</div>
26- <div class="text-xs mt-0.5 opacity-70">{$approvedCount.approved} of {$approvedCount.total} files</div>
27+ <div class="text-xs mt-0.5 opacity-70">{$uncommittedCount.approved} of {$uncommittedCount.total} files</div>
28 </button>
29 <button
30 class="flex-1 text-left px-3 py-2 rounded-lg border text-sm transition-colors {commitMode === 'all' ? 'border-blue-600 bg-accent-blue/10 text-accent-blue' : 'border-border-strong text-tertiary hover:border-border-strong'}"
31 onclick={() => (commitMode = 'all')}
32 >
33 <div class="font-medium">All changes</div>
34- <div class="text-xs mt-0.5 opacity-70">{$approvedCount.total} files</div>
35+ <div class="text-xs mt-0.5 opacity-70">{$uncommittedCount.total} files</div>
36 </button>
37 </div>
38 </div>
39
40- {#if commitMode === 'approved' && $approvedCount.approved === 0}
41+ {#if commitMode === 'approved' && $uncommittedCount.approved === 0}
42 <div class="text-sm rounded-lg px-3 py-2 bg-accent-yellow/10 text-accent-yellow">
43 No files approved yet. Approve files in the sidebar first, or commit all changes.
44 </div>
45@@ -128,10 +135,10 @@
46 </button>
47 <button
48 class="text-sm px-4 py-2 rounded bg-green-600 text-white hover:bg-green-500 transition-colors disabled:opacity-50"
49- disabled={!message.trim() || committing || (commitMode === 'approved' && $approvedCount.approved === 0)}
50+ disabled={!message.trim() || committing || (commitMode === 'approved' && $uncommittedCount.approved === 0)}
51 onclick={handleCommit}
52 >
53- {committing ? 'Committing...' : commitMode === 'approved' ? `Commit ${$approvedCount.approved} files` : 'Commit all'}
54+ {committing ? 'Committing...' : commitMode === 'approved' ? `Commit ${$uncommittedCount.approved} files` : 'Commit all'}
55 </button>
56 </div>
57 </div>
+12,
-3
1@@ -1,10 +1,15 @@
2 <script lang="ts">
3 import { viewMode, showCommitDialog, showExportDialog, theme } from '$lib/stores/ui.ts';
4 import { stats, reviewStatus, submitReview } from '$lib/stores/review.ts';
5- import { diffMode, diffScope, baseBranch, approvedCount, diffStats, loadAllDiffs, setDiffScope } from '$lib/stores/files.ts';
6+ import { diffMode, diffScope, baseBranch, approvedCount, uncommittedCount, diffStats, loadAllDiffs, setDiffScope, loadUncommittedFiles } from '$lib/stores/files.ts';
7
8 let showReviewMenu = $state(false);
9
10+ function openReviewMenu() {
11+ showReviewMenu = !showReviewMenu;
12+ if (showReviewMenu) loadUncommittedFiles();
13+ }
14+
15 async function handleApprove() {
16 await submitReview('approved');
17 $showCommitDialog = true;
18@@ -131,7 +136,7 @@
19 <div class="relative">
20 <button
21 class="text-xs px-3 py-1.5 rounded bg-green-600 text-white hover:bg-green-500 transition-colors"
22- onclick={() => (showReviewMenu = !showReviewMenu)}
23+ onclick={openReviewMenu}
24 >
25 Review ▾
26 </button>
27@@ -151,10 +156,14 @@
28 <span class="text-accent-yellow mr-2">↻</span> Request Changes (send to AI)
29 </button>
30 <button
31- class="w-full text-left px-3 py-2 text-sm text-secondary hover:bg-hover transition-colors"
32+ class="w-full text-left px-3 py-2 text-sm transition-colors {$uncommittedCount.total === 0 ? 'text-muted cursor-not-allowed' : 'text-secondary hover:bg-hover'}"
33+ disabled={$uncommittedCount.total === 0}
34 onclick={() => { handleApprove(); showReviewMenu = false; }}
35 >
36 <span class="text-accent-green mr-2">✓</span> Approve & Commit
37+ {#if $uncommittedCount.total === 0}
38+ <span class="text-xs text-muted ml-1">(no uncommitted changes)</span>
39+ {/if}
40 </button>
41 </div>
42 {/if}
+19,
-4
1@@ -5,6 +5,7 @@ import { checkOutdated } from './review.ts';
2 export type DiffMode = 'full' | 'unstaged';
3
4 export const files = writable<FileChange[]>([]);
5+export const uncommittedFiles = writable<FileChange[]>([]);
6 export const selectedFile = writable<string | null>(null);
7 export const diffFiles = writable<DiffFile[]>([]);
8 export const loading = writable(false);
9@@ -44,6 +45,11 @@ export const approvedCount = derived(files, ($files) => {
10 return { approved, total: $files.length };
11 });
12
13+export const uncommittedCount = derived(uncommittedFiles, ($uncommittedFiles) => {
14+ const approved = $uncommittedFiles.filter((f) => f.approved).length;
15+ return { approved, total: $uncommittedFiles.length };
16+});
17+
18 export const diffStats = derived(files, ($files) => {
19 let additions = 0;
20 let deletions = 0;
21@@ -75,15 +81,18 @@ export async function loadFiles() {
22 loading.set(true);
23 try {
24 const scope = getStoreValue(diffScope);
25- const url = scope === 'worktree' ? '/api/files?scope=worktree' : '/api/files';
26- const res = await fetch(url);
27- const data = await res.json();
28
29 if (scope === 'worktree') {
30+ const res = await fetch('/api/files?scope=worktree');
31+ const data = await res.json();
32 files.set(data.files);
33 baseBranch.set(data.baseBranch);
34 } else {
35- files.set(Array.isArray(data) ? data : data.files || []);
36+ const res = await fetch('/api/files');
37+ const data = await res.json();
38+ const fileList = Array.isArray(data) ? data : data.files || [];
39+ files.set(fileList);
40+ uncommittedFiles.set(fileList);
41 baseBranch.set(null);
42 }
43
44@@ -102,6 +111,12 @@ export async function loadFiles() {
45 }
46 }
47
48+export async function loadUncommittedFiles() {
49+ const res = await fetch('/api/files');
50+ const data = await res.json();
51+ uncommittedFiles.set(Array.isArray(data) ? data : []);
52+}
53+
54 export async function loadAllDiffs(mode?: DiffMode) {
55 const currentMode = mode || getStoreValue(diffMode);
56 const currentScope = getStoreValue(diffScope);
+4,
-2
1@@ -36,13 +36,15 @@ export const POST: RequestHandler = async ({ request }) => {
2 if (action === 'approve') {
3 if (isWorktree) {
4 approveWorktreeFile(path);
5+ } else {
6+ await stageFile(path);
7 }
8- await stageFile(path);
9 } else if (action === 'unapprove') {
10 if (isWorktree) {
11 unapproveWorktreeFile(path);
12+ } else {
13+ await unstageFile(path);
14 }
15- await unstageFile(path);
16 } else if (action === 'reset') {
17 await resetFile(path);
18 } else if (action === 'reset-hunk') {