go to laast session

This commit is contained in:
2026-01-21 00:52:19 -05:00
parent a2e10688bf
commit 0b2d5cdd81
5 changed files with 74 additions and 2 deletions
+3
View File
@@ -43,3 +43,6 @@ client/dev-dist/
# Test results # Test results
e2e/test-results/ e2e/test-results/
# Ideas/notes
ideas.md
+2
View File
@@ -1,5 +1,7 @@
# Spiceflow # Spiceflow
The ~~spice~~ *tokens* must flow.
A mobile-friendly web app for controlling AI coding assistants (Claude Code, OpenCode) remotely. A mobile-friendly web app for controlling AI coding assistants (Claude Code, OpenCode) remotely.
``` ```
@@ -5,6 +5,7 @@
export let autoScroll: boolean = true; export let autoScroll: boolean = true;
export let provider: 'claude' | 'opencode' | 'tmux' = 'claude'; export let provider: 'claude' | 'opencode' | 'tmux' = 'claude';
export let showBigMode: boolean = false; export let showBigMode: boolean = false;
export let lastSessionId: string | null = null;
const dispatch = createEventDispatcher<{ const dispatch = createEventDispatcher<{
toggleAutoAccept: boolean; toggleAutoAccept: boolean;
@@ -13,6 +14,7 @@
refresh: void; refresh: void;
eject: void; eject: void;
bigMode: void; bigMode: void;
goToLastSession: void;
}>(); }>();
function handleToggleAutoScroll() { function handleToggleAutoScroll() {
@@ -54,6 +56,11 @@
open = false; open = false;
} }
function handleGoToLastSession() {
dispatch('goToLastSession');
open = false;
}
function handleClickOutside(event: MouseEvent) { function handleClickOutside(event: MouseEvent) {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
if (!target.closest('.settings-dropdown')) { if (!target.closest('.settings-dropdown')) {
@@ -140,6 +147,19 @@
<span class="text-sm text-zinc-200">Refresh</span> <span class="text-sm text-zinc-200">Refresh</span>
</button> </button>
<!-- Go to last session -->
{#if lastSessionId}
<button
on:click={handleGoToLastSession}
class="w-full flex items-center gap-3 px-3 py-2.5 hover:bg-zinc-700/50 transition-colors text-left"
>
<svg class="h-4 w-4 text-zinc-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
<span class="text-sm text-zinc-200">Last session</span>
</button>
{/if}
{#if provider !== 'tmux'} {#if provider !== 'tmux'}
<button <button
on:click={handleCondenseAll} on:click={handleCondenseAll}
+15 -1
View File
@@ -6,8 +6,9 @@
export let sessionId: string; export let sessionId: string;
export let autoScroll: boolean = true; export let autoScroll: boolean = true;
export let autoFocus: boolean = true; export let autoFocus: boolean = true;
export let lastSessionId: string | null = null;
const dispatch = createEventDispatcher<{ aliveChange: boolean }>(); const dispatch = createEventDispatcher<{ aliveChange: boolean; goToLastSession: void }>();
// ANSI to HTML converter for terminal colors // ANSI to HTML converter for terminal colors
const ansiConverter = new AnsiToHtml({ const ansiConverter = new AnsiToHtml({
@@ -588,6 +589,19 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
</svg> </svg>
</button> </button>
{#if lastSessionId}
<span class="w-px h-6 bg-zinc-700"></span>
<button
on:click={() => dispatch('goToLastSession')}
class="{btnBase} bg-spice-600/80 hover:bg-spice-600 text-white"
aria-label="Go to last session"
title="Go to last session"
>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
</button>
{/if}
</div> </div>
<!-- Hidden input for keyboard capture (invisible but functional for mobile) --> <!-- Hidden input for keyboard capture (invisible but functional for mobile) -->
+34 -1
View File
@@ -24,6 +24,7 @@
let autoScroll = true; let autoScroll = true;
let tmuxAlive = false; let tmuxAlive = false;
let isMobile = false; let isMobile = false;
let lastSessionId: string | null = null;
// Load auto-scroll preference from localStorage and detect mobile // Load auto-scroll preference from localStorage and detect mobile
onMount(() => { onMount(() => {
@@ -32,6 +33,8 @@
if (stored !== null) { if (stored !== null) {
autoScroll = stored === 'true'; autoScroll = stored === 'true';
} }
// Load last session from localStorage
lastSessionId = localStorage.getItem('spiceflow-last-session');
// Detect mobile (screen width < 640px or height < 450px) // Detect mobile (screen width < 640px or height < 450px)
const checkMobile = () => { const checkMobile = () => {
isMobile = window.innerWidth < 640 || window.innerHeight < 450; isMobile = window.innerWidth < 640 || window.innerHeight < 450;
@@ -42,11 +45,28 @@
} }
}); });
// Track session history when navigating to a new session
let previousSessionId: string | null = null;
$: if (browser && sessionId && sessionId !== previousSessionId) {
// Save the previous session as "last session" before switching
if (previousSessionId) {
localStorage.setItem('spiceflow-last-session', previousSessionId);
lastSessionId = previousSessionId;
}
previousSessionId = sessionId;
}
// Load session when sessionId changes (handles client-side navigation) // Load session when sessionId changes (handles client-side navigation)
$: if (sessionId) { $: if (sessionId) {
activeSession.load(sessionId); activeSession.load(sessionId);
} }
function goToLastSession() {
if (lastSessionId) {
goto(`/session/${lastSessionId}`);
}
}
function handleToggleAutoScroll(event: CustomEvent<boolean>) { function handleToggleAutoScroll(event: CustomEvent<boolean>) {
autoScroll = event.detail; autoScroll = event.detail;
if (browser) { if (browser) {
@@ -236,6 +256,7 @@
<SessionSettings <SessionSettings
{autoAcceptEdits} {autoAcceptEdits}
{autoScroll} {autoScroll}
{lastSessionId}
provider={session.provider} provider={session.provider}
showBigMode={isTmuxSession && isMobile} showBigMode={isTmuxSession && isMobile}
on:toggleAutoAccept={handleToggleAutoAccept} on:toggleAutoAccept={handleToggleAutoAccept}
@@ -244,6 +265,7 @@
on:refresh={handleRefresh} on:refresh={handleRefresh}
on:eject={handleEject} on:eject={handleEject}
on:bigMode={handleBigMode} on:bigMode={handleBigMode}
on:goToLastSession={goToLastSession}
/> />
<!-- Refresh button - desktop only --> <!-- Refresh button - desktop only -->
@@ -326,6 +348,17 @@
</svg> </svg>
Refresh Refresh
</button> </button>
{#if lastSessionId}
<button
on:click={() => { menuOpen = false; goToLastSession(); }}
class="w-full px-3 py-2 text-left text-sm hover:bg-zinc-700 flex items-center gap-2 transition-colors"
>
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
</svg>
Last session
</button>
{/if}
{#if !isTmuxSession} {#if !isTmuxSession}
<button <button
on:click={() => { menuOpen = false; messageList?.condenseAll(); }} on:click={() => { menuOpen = false; messageList?.condenseAll(); }}
@@ -385,7 +418,7 @@
</div> </div>
{:else if isTmuxSession} {:else if isTmuxSession}
<!-- Terminal view for tmux sessions --> <!-- Terminal view for tmux sessions -->
<TerminalView bind:this={terminalView} sessionId={sessionId || ''} {autoScroll} on:aliveChange={handleTmuxAliveChange} /> <TerminalView bind:this={terminalView} sessionId={sessionId || ''} {autoScroll} {lastSessionId} on:aliveChange={handleTmuxAliveChange} on:goToLastSession={goToLastSession} />
{:else} {:else}
<MessageList bind:this={messageList} messages={$activeSession.messages} streamingContent={$activeSession.streamingContent} isThinking={$activeSession.isThinking} provider={session?.provider} {autoScroll} /> <MessageList bind:this={messageList} messages={$activeSession.messages} streamingContent={$activeSession.streamingContent} isThinking={$activeSession.isThinking} provider={session?.provider} {autoScroll} />