Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions apps/staged/src/lib/features/diff/DiffCommentsSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@
import Check from '@lucide/svelte/icons/check';
import ChevronRight from '@lucide/svelte/icons/chevron-right';
import Copy from '@lucide/svelte/icons/copy';
import FileText from '@lucide/svelte/icons/file-text';
import GitCommitVertical from '@lucide/svelte/icons/git-commit-vertical';
import MessageSquare from '@lucide/svelte/icons/message-square';
import Trash2 from '@lucide/svelte/icons/trash-2';
import Undo2 from '@lucide/svelte/icons/undo-2';
import { Button } from '$lib/components/ui/button';
import type { Comment, CommentSessionState } from '../../types';
import Spinner from '../../shared/Spinner.svelte';
import { getCommentSessionDisplay } from './commentSessionDisplay';
import { formatLineRange, truncateText } from './diffModalHelpers';

interface Props {
Expand Down Expand Up @@ -61,27 +59,17 @@
warning/message icons, mirroring the stateful inline-diff buttons. -->
<span class="comment-session-badges">
{#if noteState !== 'idle'}
<span
class="comment-session-badge note"
title={noteState === 'running' ? 'Note session in progress' : 'Note ready'}
>
{#if noteState === 'running'}
<Spinner size={12} />
{:else}
<FileText size={12} />
{/if}
{@const noteDisplay = getCommentSessionDisplay('note', noteState, 'badge')}
{@const NoteIcon = noteDisplay.icon}
<span class="comment-session-badge note" title={noteDisplay.title}>
<NoteIcon size={12} />
</span>
{/if}
{#if commitState !== 'idle'}
<span
class="comment-session-badge commit"
title={commitState === 'running' ? 'Commit session in progress' : 'Commit ready'}
>
{#if commitState === 'running'}
<Spinner size={12} />
{:else}
<GitCommitVertical size={12} />
{/if}
{@const commitDisplay = getCommentSessionDisplay('commit', commitState, 'badge')}
{@const CommitIcon = commitDisplay.icon}
<span class="comment-session-badge commit" title={commitDisplay.title}>
<CommitIcon size={12} />
</span>
{/if}
</span>
Expand Down
22 changes: 13 additions & 9 deletions apps/staged/src/lib/features/diff/DiffModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
BranchSessionLaunchStatus,
Comment,
CommentActionContext,
CommentSessionState,
CommitTimelineItem,
SessionStatus,
SmartDiffAnnotation,
Expand Down Expand Up @@ -517,10 +518,10 @@
}

function handleNewNote(comment: Comment, event: MouseEvent) {
// Route by the linked note session's state (idle → start, running → open
// session, completed → open note).
// Route by the linked note session's state (idle → start, queued/running →
// open session, completed → open note).
const state = getCommentNoteState(comment);
if (state === 'running' && comment.noteSessionId) {
if ((state === 'queued' || state === 'running') && comment.noteSessionId) {
openSessionId = comment.noteSessionId;
return;
}
Expand All @@ -539,10 +540,10 @@
}

function handleNewCommit(comment: Comment, event: MouseEvent) {
// Route by the linked commit session's state (idle → start, running → open
// session, completed → show the commit in the open diff viewer).
// Route by the linked commit session's state (idle → start, queued/running →
// open session, completed → show the commit in the open diff viewer).
const state = getCommentCommitState(comment);
if (state === 'running' && comment.commitSessionId) {
if ((state === 'queued' || state === 'running') && comment.commitSessionId) {
openSessionId = comment.commitSessionId;
return;
}
Expand Down Expand Up @@ -699,10 +700,13 @@
}

/** Map a linked session id's status to the Note/Commit button state. */
function sessionStateFor(sessionId: string | null): 'idle' | 'running' | 'completed' {
function sessionStateFor(sessionId: string | null): CommentSessionState {
if (!sessionId) return 'idle';
switch (sessionStatusById.get(sessionId)) {
// Keep `queued` distinct from `running` so the UI can show the same
// Clock icon the timeline rows use for queued sessions.
case 'queued':
return 'queued';
case 'running':
return 'running';
case 'completed':
Expand All @@ -714,11 +718,11 @@
}
}

function getCommentNoteState(comment: Comment): 'idle' | 'running' | 'completed' {
function getCommentNoteState(comment: Comment): CommentSessionState {
return sessionStateFor(comment.noteSessionId);
}

function getCommentCommitState(comment: Comment): 'idle' | 'running' | 'completed' {
function getCommentCommitState(comment: Comment): CommentSessionState {
return sessionStateFor(comment.commitSessionId);
}

Expand Down
27 changes: 11 additions & 16 deletions apps/staged/src/lib/features/diff/ReviewCommentActions.svelte
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<script lang="ts">
import Check from '@lucide/svelte/icons/check';
import FileText from '@lucide/svelte/icons/file-text';
import GitCommitVertical from '@lucide/svelte/icons/git-commit-vertical';
import GitPullRequest from '@lucide/svelte/icons/git-pull-request';
import Spinner from '../../shared/Spinner.svelte';
import type { Comment, CommentSessionState, GithubButtonState } from '../../types';
import { getCommentSessionDisplay } from './commentSessionDisplay';

interface Props {
comment: Comment | null;
Expand All @@ -31,6 +30,10 @@
}: Props = $props();

let pendingAction = $state<'note' | 'commit' | 'github' | null>(null);
let noteDisplay = $derived(getCommentSessionDisplay('note', noteState, 'action'));
let NoteIcon = $derived(noteDisplay.icon);
let commitDisplay = $derived(getCommentSessionDisplay('commit', commitState, 'action'));
let CommitIcon = $derived(commitDisplay.icon);

async function withSavedComment(
action: 'note' | 'commit' | 'github',
Expand All @@ -53,17 +56,13 @@
class="comment-action-btn note-btn"
class:session-active={noteState !== 'idle'}
onclick={(event) => withSavedComment('note', (savedComment) => onNote(savedComment, event))}
title={noteState === 'running'
? 'Note session in progress'
: noteState === 'completed'
? 'Open note'
: 'New note (Option+click to skip dialog)'}
title={noteDisplay.title}
disabled={pendingAction !== null}
>
{#if pendingAction === 'note' || noteState === 'running'}
{#if pendingAction === 'note'}
<Spinner size={12} />
{:else}
<FileText size={12} />
<NoteIcon size={12} />
{/if}
<span>Note</span>
</button>
Expand All @@ -72,17 +71,13 @@
class="comment-action-btn commit-btn"
class:session-active={commitState !== 'idle'}
onclick={(event) => withSavedComment('commit', (savedComment) => onCommit(savedComment, event))}
title={commitState === 'running'
? 'Commit session in progress'
: commitState === 'completed'
? 'Show commit'
: 'New commit (Option+click to skip dialog)'}
title={commitDisplay.title}
disabled={pendingAction !== null}
>
{#if pendingAction === 'commit' || commitState === 'running'}
{#if pendingAction === 'commit'}
<Spinner size={12} />
{:else}
<GitCommitVertical size={12} />
<CommitIcon size={12} />
{/if}
<span>Commit</span>
</button>
Expand Down
51 changes: 51 additions & 0 deletions apps/staged/src/lib/features/diff/commentSessionDisplay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Clock from '@lucide/svelte/icons/clock';
import FileText from '@lucide/svelte/icons/file-text';
import GitCommitVertical from '@lucide/svelte/icons/git-commit-vertical';
import Spinner from '../../shared/Spinner.svelte';
import type { CommentSessionState } from '../../types';

export type CommentSessionKind = 'note' | 'commit';
export type CommentSessionDisplayContext = 'action' | 'badge';

export function getCommentSessionDisplay(
kind: CommentSessionKind,
state: CommentSessionState,
context: CommentSessionDisplayContext
) {
const kindLabel = kind === 'note' ? 'Note' : 'Commit';

switch (state) {
case 'queued':
return {
icon: Clock,
title: `${kindLabel} session queued`,
};
case 'running':
return {
icon: Spinner,
title: `${kindLabel} session in progress`,
};
case 'completed':
if (kind === 'note') {
return {
icon: FileText,
title: context === 'action' ? 'Open note' : 'Note ready',
};
}

return {
icon: GitCommitVertical,
title: context === 'action' ? 'Show commit' : 'Commit ready',
};
case 'idle':
return kind === 'note'
? {
icon: FileText,
title: 'New note (Option+click to skip dialog)',
}
: {
icon: GitCommitVertical,
title: 'New commit (Option+click to skip dialog)',
};
}
}
6 changes: 3 additions & 3 deletions apps/staged/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,10 +500,10 @@ export type GithubButtonState = 'idle' | 'sending' | 'sent' | 'stale';

/**
* State of a review comment's "Note"/"Commit" action, derived from the linked
* session's status. `queued` is treated as `running`, and `error`/`cancelled`
* collapse back to `idle` so the user can retry.
* session's status. `queued` mirrors the timeline's queued state (Clock icon),
* and `error`/`cancelled` collapse back to `idle` so the user can retry.
*/
export type CommentSessionState = 'idle' | 'running' | 'completed';
export type CommentSessionState = 'idle' | 'queued' | 'running' | 'completed';

// =============================================================================
// Hashtag references
Expand Down