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
13 changes: 13 additions & 0 deletions .changeset/open-where-you-left-off.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'fiber': patch
---

Open where you left off.

Collections, requests and responses were already durable. Everything about *looking* at them was not: which endpoint was open, which tab of it, which sidebar tab, which loader folders were expanded, and whatever had been typed into the scratch request. Every launch started on an empty scratch request with the sidebar back on Collections — which is a small thing after a deliberate quit, and a rude one after an auto-update restart nobody asked for.

All of it is now stored, and restored before the first frame rather than a moment after it, so the window opens as it was rather than visibly rearranging itself. A stored endpoint that no longer exists — deleted elsewhere, or dropped by a loader — falls back to the scratch request instead of leaving the pane blank.

An update restart also flushes the debounced section writes now. It never did: Rust holds an exit long enough for the frontend to save, but only for an exit it did not ask for itself, and a restart carries a code that sails straight past that. The last few hundred milliseconds of edits went with the old process. The same flush runs on `pagehide`, which covers a webview torn down and brought back without going through either quit path.

Window size and position needed nothing — `tauri-plugin-window-state` already writes those on exit, and the restart path does run through exit.
40 changes: 20 additions & 20 deletions src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
type DragHint
} from '$lib/dnd.svelte';
import { history, type HistoryEntry } from '$lib/history.svelte';
import { session } from '$lib/session.svelte';
import { theme } from '$lib/theme.svelte';
import DotLoader from '$lib/components/DotLoader.svelte';
import McpTab from '$lib/components/McpTab.svelte';
Expand All @@ -32,8 +33,6 @@

let { onOpenSettings, onPickHistory }: Props = $props();

let tab = $state<'collections' | 'history' | 'mcp'>('collections');

// Read from the bundle rather than package.json, so what the footer shows is
// the version that is actually running — which is the number to quote when
// something misbehaves.
Expand Down Expand Up @@ -211,26 +210,27 @@
return order.map((tag) => ({ tag, rows: groups.get(tag)! }));
}

function tagKey(sectionId: string, tag: string): string {
return `${sectionId}\0${tag}`;
}

/**
* Folders the user has opened. Closed is the default: a loader can report
* hundreds of endpoints across a dozen tags, and opening a collection to a
* wall of them is no better than not grouping at all. The folder names are
* the map; you open the one you want.
*
* Which ones are open is kept in the session rather than here, so the
* folders you had open are still open the next time the app starts.
*/
let openTags = $state<Record<string, boolean>>({});

function tagKey(sectionId: string, tag: string): string {
return `${sectionId}\0${tag}`;
}

function tagOpen(sectionId: string, tag: string): boolean {
if (searching || !tag) return true;
return openTags[tagKey(sectionId, tag)] ?? false;
return session.openTags[tagKey(sectionId, tag)] ?? false;
}

function toggleTag(sectionId: string, tag: string): void {
const key = tagKey(sectionId, tag);
openTags[key] = !openTags[key];
session.openTags[key] = !session.openTags[key];
}

function loadMoreEndpoints(sectionId: string): void {
Expand Down Expand Up @@ -303,7 +303,7 @@
* kept showing that entry rather than the request's own current response.
*/
function showCollections() {
tab = 'collections';
session.sidebarTab = 'collections';
history.stopViewing();
}

Expand All @@ -312,7 +312,7 @@
* were looking at stops overriding the response pane.
*/
function showMcp() {
tab = 'mcp';
session.sidebarTab = 'mcp';
history.stopViewing();
}

Expand Down Expand Up @@ -807,18 +807,18 @@
<aside class="flex flex-col border-r border-border bg-panel min-h-0 h-full">
<header class="flex items-center gap-1 px-2 h-11 border-b border-border shrink-0">
<button
class="px-2 py-1 rounded text-xs transition-colors {tab === 'collections'
class="px-2 py-1 rounded text-xs transition-colors {session.sidebarTab === 'collections'
? 'bg-raised text-text'
: 'text-muted hover:text-text'}"
onclick={() => showCollections()}
>
Collections
</button>
<button
class="px-2 py-1 rounded text-xs transition-colors {tab === 'history'
class="px-2 py-1 rounded text-xs transition-colors {session.sidebarTab === 'history'
? 'bg-raised text-text'
: 'text-muted hover:text-text'}"
onclick={() => (tab = 'history')}
onclick={() => (session.sidebarTab = 'history')}
>
History
</button>
Expand All @@ -828,22 +828,22 @@
rows.
-->
<button
class="px-2 py-1 rounded text-xs transition-colors {tab === 'mcp'
class="px-2 py-1 rounded text-xs transition-colors {session.sidebarTab === 'mcp'
? 'bg-raised text-text'
: 'text-muted hover:text-text'}"
onclick={() => showMcp()}
>
MCP
</button>
{#if tab === 'history'}
{#if session.sidebarTab === 'history'}
<button
class="ml-auto p-1 rounded text-muted hover:bg-raised hover:text-text transition-colors"
title="Clear history"
onclick={() => history.clear()}
>
<span class="i-lucide-trash-2"></span>
</button>
{:else if tab === 'collections'}
{:else if session.sidebarTab === 'collections'}
<!--
square-plus and folder-plus rather than file-plus and folder-plus: the
geometric pair reads better at 24px, and it happens to solve the
Expand Down Expand Up @@ -888,9 +888,9 @@
{/if}
</header>

{#if tab === 'mcp'}
{#if session.sidebarTab === 'mcp'}
<McpTab />
{:else if tab === 'collections'}
{:else if session.sidebarTab === 'collections'}
<div class="px-2 py-2 shrink-0">
<div class="relative">
<span
Expand Down
Loading
Loading