Skip to content
Merged
32 changes: 28 additions & 4 deletions apps/staged/src/lib/features/branches/BranchCard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
let commitDiffSha = $state<string | null>(null);

// Note modal (opened by clicking a note in the timeline)
let openNote = $state<{ title: string; content: string } | null>(null);
let openNote = $state<{ title: string; content: string; sessionId?: string } | null>(null);

// Image viewer modal (opened by clicking an image in the timeline)
let viewImageId = $state<string | null>(null);
Expand Down Expand Up @@ -351,12 +351,21 @@
// Timeline item interactions
// =========================================================================

/** Look up note info from timeline data by session ID (for cross-modal navigation). */
function findNoteForSession(
sessionId: string
): { id: string; title: string; content: string } | null {
const note = timeline?.notes.find((n) => n.sessionId === sessionId && n.content?.trim());
if (!note) return null;
return { id: note.id, title: note.title, content: note.content };
}

function handleCommitClick(sha: string) {
commitDiffSha = sha;
}

function handleNoteClick(_noteId: string, title: string, content: string) {
openNote = { title, content };
function handleNoteClick(_noteId: string, title: string, content: string, sessionId?: string) {
openNote = { title, content, sessionId };
}

async function handleReviewClick(reviewId: string) {
Expand Down Expand Up @@ -803,7 +812,16 @@
{/if}

{#if openNote}
<NoteModal title={openNote.title} content={openNote.content} onClose={() => (openNote = null)} />
<NoteModal
title={openNote.title}
content={openNote.content}
sessionId={openNote.sessionId}
onClose={() => (openNote = null)}
onOpenSession={(sid) => {
openNote = null;
sessionMgr.openSessionId = sid;
}}
/>
{/if}

{#if viewImageId}
Expand Down Expand Up @@ -847,6 +865,12 @@
repoDir={branch.worktreePath}
branchId={branch.id}
projectId={branch.projectId}
noteInfo={findNoteForSession(sessionMgr.openSessionId)}
onOpenNote={(noteId, title, content) => {
const sid = sessionMgr.openSessionId;
sessionMgr.openSessionId = null;
openNote = { title, content, sessionId: sid ?? undefined };
}}
onClose={async () => {
const closedSessionId = sessionMgr.openSessionId;
sessionMgr.openSessionId = null;
Expand Down
57 changes: 40 additions & 17 deletions apps/staged/src/lib/features/notes/NoteModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
title: string;
content: string;
onClose: () => void;
/** When set, shows a button to open the associated chat session. */
sessionId?: string | null;
onOpenSession?: (sessionId: string) => void;
}

let { title, content, onClose }: Props = $props();
let { title, content, onClose, sessionId, onOpenSession }: Props = $props();

let copied = $state(false);
const backdropDismiss = createBackdropDismissHandlers({ onDismiss: () => onClose() });
Expand Down Expand Up @@ -163,7 +166,9 @@
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="modal" role="presentation" onclick={(e) => e.stopPropagation()}>
<header class="modal-header">
<h2 class="modal-title">{title}</h2>
<div class="header-content">
<span class="header-title">{title}</span>
</div>
<InContentSearch
visible={searchVisible}
{matchCount}
Expand All @@ -175,7 +180,7 @@
/>
<div class="header-actions">
<button
class="share-btn"
class="header-btn"
class:copied
onclick={handleShare}
title={copied ? 'Copied!' : 'Copy note to clipboard'}
Expand All @@ -186,6 +191,15 @@
<Copy size={16} />
{/if}
</button>
{#if sessionId && onOpenSession}
<button
class="header-btn"
onclick={() => onOpenSession?.(sessionId!)}
title="Open chat session"
>
View chat
</button>
{/if}
<button class="close-btn" onclick={onClose} title="Close (Esc)">
<X size={16} />
</button>
Expand Down Expand Up @@ -218,9 +232,9 @@
.modal {
display: flex;
flex-direction: column;
width: 640px;
max-width: 90vw;
max-height: 80vh;
width: 700px;
height: 80vh;
max-height: 900px;
background: var(--bg-chrome);
border-radius: 12px;
overflow: hidden;
Expand All @@ -232,21 +246,27 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
padding: 12px 16px;
border-bottom: 1px solid var(--border-subtle);
flex-shrink: 0;
gap: 12px;
}

.modal-title {
margin: 0;
font-size: var(--size-base);
.header-content {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
flex: 1;
}

.header-title {
font-size: var(--size-sm);
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}

.close-btn {
Expand Down Expand Up @@ -277,28 +297,31 @@
flex-shrink: 0;
}

.share-btn {
.header-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
padding: 4px 10px;
background: none;
border: none;
border: 1px solid var(--border-muted);
border-radius: 6px;
color: var(--text-muted);
cursor: pointer;
flex-shrink: 0;
font-size: 12px;
transition:
color 0.1s,
background-color 0.1s;
background-color 0.1s,
border-color 0.1s;
}

.share-btn:hover {
.header-btn:hover {
color: var(--text-primary);
background: var(--bg-hover);
border-color: var(--text-muted);
}

.share-btn.copied {
.header-btn.copied {
color: var(--status-added);
}

Expand Down
30 changes: 27 additions & 3 deletions apps/staged/src/lib/features/projects/ProjectSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@
})
);

let openNote = $state<{ title: string; content: string } | null>(null);
let openNote = $state<{ title: string; content: string; sessionId?: string } | null>(null);
let openSessionId = $state<string | null>(null);

function formatRelativeTime(timestampMs: number): string {
Expand Down Expand Up @@ -614,7 +614,11 @@
onItemClick={isRunning || isFailed
? undefined
: () => {
openNote = { title: note.title, content: note.content };
openNote = {
title: note.title,
content: note.content,
sessionId: note.sessionId ?? undefined,
};
}}
onSessionClick={(sid) => {
openSessionId = sid;
Expand Down Expand Up @@ -646,13 +650,33 @@
</div>

{#if openNote}
<NoteModal title={openNote.title} content={openNote.content} onClose={() => (openNote = null)} />
<NoteModal
title={openNote.title}
content={openNote.content}
sessionId={openNote.sessionId}
onClose={() => (openNote = null)}
onOpenSession={(sid) => {
openNote = null;
openSessionId = sid;
}}
/>
{/if}

{#if openSessionId}
{@const noteForSession = projectNotes.find(
(n) => n.sessionId === openSessionId && n.content?.trim()
)}
<SessionModal
sessionId={openSessionId}
projectId={project.id}
noteInfo={noteForSession
? { id: noteForSession.id, title: noteForSession.title, content: noteForSession.content }
: null}
onOpenNote={(_noteId, title, content) => {
const sid = openSessionId;
openSessionId = null;
openNote = { title, content, sessionId: sid ?? undefined };
}}
onClose={() => {
openSessionId = null;
loadProjectNotes();
Expand Down
38 changes: 37 additions & 1 deletion apps/staged/src/lib/features/sessions/SessionModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,12 @@
branchId?: string | null;
/** Project ID — when provided, enables image attachment on replies. */
projectId?: string | null;
/** When set, shows a button to open the associated note. */
noteInfo?: { id: string; title: string; content: string } | null;
onOpenNote?: (noteId: string, title: string, content: string) => void;
}

let { sessionId, onClose, repoDir, branchId, projectId }: Props = $props();
let { sessionId, onClose, repoDir, branchId, projectId, noteInfo, onOpenNote }: Props = $props();

// =========================================================================
// State
Expand Down Expand Up @@ -820,6 +823,15 @@
onClose={closeSearch}
/>
<div class="header-actions">
{#if noteInfo && onOpenNote}
<button
class="header-btn"
onclick={() => onOpenNote?.(noteInfo!.id, noteInfo!.title, noteInfo!.content)}
title="Open note"
>
View note
</button>
{/if}
<button class="close-btn" onclick={requestClose} title="Close (Esc)">
<X size={16} />
</button>
Expand Down Expand Up @@ -1249,6 +1261,30 @@
flex-shrink: 0;
}

.header-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 4px 10px;
background: none;
border: 1px solid var(--border-muted);
border-radius: 6px;
color: var(--text-muted);
cursor: pointer;
flex-shrink: 0;
font-size: 12px;
transition:
color 0.1s,
background-color 0.1s,
border-color 0.1s;
}

.header-btn:hover {
color: var(--text-primary);
background: var(--bg-hover);
border-color: var(--text-muted);
}

.close-btn {
display: flex;
align-items: center;
Expand Down
4 changes: 2 additions & 2 deletions apps/staged/src/lib/features/timeline/BranchTimeline.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
deletingItems?: { type: 'commit' | 'note' | 'review' | 'image'; id: string }[];
onSessionClick?: (sessionId: string) => void;
onCommitClick?: (sha: string) => void;
onNoteClick?: (noteId: string, title: string, content: string) => void;
onNoteClick?: (noteId: string, title: string, content: string, sessionId?: string) => void;
onReviewClick?: (reviewId: string) => void;
onImageClick?: (imageId: string) => void;
onDeleteCommit?: (sha: string, sessionId?: string) => void;
Expand Down Expand Up @@ -351,7 +351,7 @@
if (item.type === 'commit' && item.commitSha && onCommitClick) {
onCommitClick(item.commitSha);
} else if (item.type === 'note' && item.noteId && onNoteClick) {
onNoteClick(item.noteId, item.noteTitle ?? '', item.noteContent ?? '');
onNoteClick(item.noteId, item.noteTitle ?? '', item.noteContent ?? '', item.sessionId);
} else if (item.type === 'review' && item.reviewId && onReviewClick) {
onReviewClick(item.reviewId);
} else if (item.type === 'image' && item.imageId && onImageClick) {
Expand Down