From dfcec238dce0b0a10c447a59848c0ff0c649be74 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 18:17:38 -0700
Subject: [PATCH 1/7] Add menu bar
---
web/package.json | 1 +
web/src/app.css | 1 +
.../lib/components/menu-bar/MenuBar.svelte | 96 +++++
.../settings-popover/SettingsPopover.svelte | 44 --
.../components/settings/SettingsDialog.svelte | 77 ++++
.../SettingsGroup.svelte} | 5 +-
.../ShikiThemeSelector.svelte | 0
.../SimpleRadioGroup.svelte | 0
web/src/lib/diff-viewer.svelte.ts | 2 +
web/src/routes/+page.svelte | 392 ++++++++----------
web/src/routes/ActionsPopover.svelte | 37 --
web/src/routes/OpenDiffDialog.svelte | 9 +-
12 files changed, 348 insertions(+), 316 deletions(-)
create mode 100644 web/src/lib/components/menu-bar/MenuBar.svelte
delete mode 100644 web/src/lib/components/settings-popover/SettingsPopover.svelte
create mode 100644 web/src/lib/components/settings/SettingsDialog.svelte
rename web/src/lib/components/{settings-popover/SettingsPopoverGroup.svelte => settings/SettingsGroup.svelte} (80%)
rename web/src/lib/components/{settings-popover => settings}/ShikiThemeSelector.svelte (100%)
rename web/src/lib/components/{settings-popover => settings}/SimpleRadioGroup.svelte (100%)
delete mode 100644 web/src/routes/ActionsPopover.svelte
diff --git a/web/package.json b/web/package.json
index a0bad4f..a574bdc 100644
--- a/web/package.json
+++ b/web/package.json
@@ -40,6 +40,7 @@
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.3",
"tailwindcss": "^4.1.17",
+ "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3",
"vite": "^7.2.2",
diff --git a/web/src/app.css b/web/src/app.css
index c4176c9..ad33c77 100644
--- a/web/src/app.css
+++ b/web/src/app.css
@@ -1,5 +1,6 @@
@import "tailwindcss";
+@import "tw-animate-css";
@plugin "@iconify/tailwind4" {
prefixes: octicon;
}
diff --git a/web/src/lib/components/menu-bar/MenuBar.svelte b/web/src/lib/components/menu-bar/MenuBar.svelte
new file mode 100644
index 0000000..57ca547
--- /dev/null
+++ b/web/src/lib/components/menu-bar/MenuBar.svelte
@@ -0,0 +1,96 @@
+
+
+
+
+ diffs.dev
+
+
+
+
+
+ GitHub Repository
+
+
+
+
+
+ Chrome Extension
+
+
+
+
+
+ Firefox Add-on
+
+
+
+ {
+ viewer.settingsDialogOpen = true;
+ }}
+ >
+ Open Settings
+
+
+
+
+
+ File
+
+
+ {
+ viewer.openDiffDialogOpen = true;
+ }}
+ >
+ Open
+
+
+
+
+
+ View
+
+
+ {
+ viewer.expandAll();
+ }}
+ >
+ Expand All
+
+ {
+ viewer.collapseAll();
+ }}
+ >
+ Collapse All
+
+
+
+
+
diff --git a/web/src/lib/components/settings-popover/SettingsPopover.svelte b/web/src/lib/components/settings-popover/SettingsPopover.svelte
deleted file mode 100644
index 4b733d2..0000000
--- a/web/src/lib/components/settings-popover/SettingsPopover.svelte
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-{#snippet sectionHeader(text: string)}
-
{text}
-{/snippet}
-
-{#snippet globalThemeSetting()}
-
-
-
-
-
-{/snippet}
-
-
-
-
-
-
-
- {@render children?.()}
-
-
-
-
diff --git a/web/src/lib/components/settings/SettingsDialog.svelte b/web/src/lib/components/settings/SettingsDialog.svelte
new file mode 100644
index 0000000..1e4cef9
--- /dev/null
+++ b/web/src/lib/components/settings/SettingsDialog.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+{#snippet sectionHeader(text: string)}
+ {text}
+{/snippet}
+
+{#snippet globalThemeSetting()}
+
+
+
+
+
+{/snippet}
+
+
+
+
+
+
+
+
+ {@render globalThemeSetting()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/lib/components/settings-popover/SettingsPopoverGroup.svelte b/web/src/lib/components/settings/SettingsGroup.svelte
similarity index 80%
rename from web/src/lib/components/settings-popover/SettingsPopoverGroup.svelte
rename to web/src/lib/components/settings/SettingsGroup.svelte
index a8a2517..d57087e 100644
--- a/web/src/lib/components/settings-popover/SettingsPopoverGroup.svelte
+++ b/web/src/lib/components/settings/SettingsGroup.svelte
@@ -1,6 +1,3 @@
-
-
-
-
-
-
-
-
-
- {
- viewer.expandAll();
- open = false;
- }}
- >
- Expand All
-
- {
- viewer.collapseAll();
- open = false;
- }}
- >
- Collapse All
-
-
-
-
-
diff --git a/web/src/routes/OpenDiffDialog.svelte b/web/src/routes/OpenDiffDialog.svelte
index d0dbb7c..d04f571 100644
--- a/web/src/routes/OpenDiffDialog.svelte
+++ b/web/src/routes/OpenDiffDialog.svelte
@@ -280,13 +280,14 @@
{/snippet}
- Open new diff
-
+
-
+
Open New Diff
From 012c5a896559567e96232e5041ce75f6a3b67c70 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 18:19:49 -0700
Subject: [PATCH 2/7] Remove unused vars/imports
---
web/src/lib/components/settings/SettingsDialog.svelte | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/web/src/lib/components/settings/SettingsDialog.svelte b/web/src/lib/components/settings/SettingsDialog.svelte
index 1e4cef9..80881b4 100644
--- a/web/src/lib/components/settings/SettingsDialog.svelte
+++ b/web/src/lib/components/settings/SettingsDialog.svelte
@@ -4,12 +4,10 @@
From a6519cde619b15c66421c3348abd45999e01ce26 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 18:57:55 -0700
Subject: [PATCH 3/7] Add keybinds
---
.../lib/components/menu-bar/MenuBar.svelte | 15 ++++++---
web/src/lib/diff-viewer.svelte.ts | 15 +++++++++
web/src/lib/keybinds.svelte.ts | 31 +++++++++++++++++++
3 files changed, 57 insertions(+), 4 deletions(-)
create mode 100644 web/src/lib/keybinds.svelte.ts
diff --git a/web/src/lib/components/menu-bar/MenuBar.svelte b/web/src/lib/components/menu-bar/MenuBar.svelte
index 57ca547..543bc55 100644
--- a/web/src/lib/components/menu-bar/MenuBar.svelte
+++ b/web/src/lib/components/menu-bar/MenuBar.svelte
@@ -1,10 +1,15 @@
+{#snippet keybind(key: string)}
+ {Keybinds.getModifierKey()}+{key}
+{/snippet}
+
diffs.dev
@@ -45,12 +50,13 @@
{
- viewer.settingsDialogOpen = true;
+ viewer.openSettingsDialog();
}}
>
Open Settings
+ {@render keybind(",")}
@@ -60,12 +66,13 @@
{
- viewer.openDiffDialogOpen = true;
+ viewer.openOpenDiffDialog();
}}
>
Open
+ {@render keybind("O")}
diff --git a/web/src/lib/diff-viewer.svelte.ts b/web/src/lib/diff-viewer.svelte.ts
index afa747e..41ff4d7 100644
--- a/web/src/lib/diff-viewer.svelte.ts
+++ b/web/src/lib/diff-viewer.svelte.ts
@@ -17,6 +17,7 @@ import { VList } from "virtua/svelte";
import { Context, Debounced, watch } from "runed";
import { MediaQuery } from "svelte/reactivity";
import { ProgressBarState } from "$lib/components/progress-bar/index.svelte";
+import { Keybinds } from "./keybinds.svelte";
export const GITHUB_URL_PARAM = "github_url";
export const PATCH_URL_PARAM = "patch_url";
@@ -241,6 +242,20 @@ export class MultiFileDiffViewerState {
private constructor() {
// Make sure to revoke object URLs when the component is destroyed
onDestroy(() => this.clearImages());
+
+ const keybinds = new Keybinds();
+ keybinds.registerModifierBind("o", () => this.openOpenDiffDialog());
+ keybinds.registerModifierBind(",", () => this.openSettingsDialog());
+ }
+
+ openOpenDiffDialog() {
+ this.openDiffDialogOpen = true;
+ this.settingsDialogOpen = false;
+ }
+
+ openSettingsDialog() {
+ this.settingsDialogOpen = true;
+ this.openDiffDialogOpen = false;
}
filterFile(file: FileDetails): boolean {
diff --git a/web/src/lib/keybinds.svelte.ts b/web/src/lib/keybinds.svelte.ts
new file mode 100644
index 0000000..a11c73b
--- /dev/null
+++ b/web/src/lib/keybinds.svelte.ts
@@ -0,0 +1,31 @@
+import { onMount } from "svelte";
+import { on } from "svelte/events";
+
+export class Keybinds {
+ private static readonly IS_MAC = typeof navigator !== "undefined" && navigator.userAgent.includes("Mac");
+
+ static getModifierKey() {
+ return Keybinds.IS_MAC ? "⌘" : "Ctrl";
+ }
+
+ private readonly binds = new Map void>();
+
+ constructor() {
+ onMount(() => {
+ return on(document, "keydown", (event) => {
+ const modifierPressed = Keybinds.IS_MAC ? event.metaKey : event.ctrlKey;
+ if (modifierPressed) {
+ const bindAction = this.binds.get(event.key.toLowerCase());
+ if (bindAction) {
+ bindAction();
+ event.preventDefault();
+ }
+ }
+ });
+ });
+ }
+
+ registerModifierBind(key: string, action: () => void) {
+ this.binds.set(key.toLowerCase(), action);
+ }
+}
From a466e484ba6622fdf2a49a4916b363cfc197994a Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 19:04:38 -0700
Subject: [PATCH 4/7] Fix cursor on menu items
---
bun.lock | 3 +++
web/package.json | 1 +
web/src/lib/components/menu-bar/MenuBar.svelte | 8 ++++----
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/bun.lock b/bun.lock
index edc8ce7..5029b3b 100644
--- a/bun.lock
+++ b/bun.lock
@@ -43,6 +43,7 @@
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.3",
"tailwindcss": "^4.1.17",
+ "tw-animate-css": "^1.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3",
"vite": "^7.2.2",
@@ -1037,6 +1038,8 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
diff --git a/web/package.json b/web/package.json
index a574bdc..e8909ec 100644
--- a/web/package.json
+++ b/web/package.json
@@ -10,6 +10,7 @@
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
+ "check:all": "bun run check && bun run lint && vitest run",
"test": "vitest",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write .",
diff --git a/web/src/lib/components/menu-bar/MenuBar.svelte b/web/src/lib/components/menu-bar/MenuBar.svelte
index 543bc55..9554132 100644
--- a/web/src/lib/components/menu-bar/MenuBar.svelte
+++ b/web/src/lib/components/menu-bar/MenuBar.svelte
@@ -50,7 +50,7 @@
{
viewer.openSettingsDialog();
}}
@@ -66,7 +66,7 @@
{
viewer.openOpenDiffDialog();
}}
@@ -82,7 +82,7 @@
{
viewer.expandAll();
}}
@@ -90,7 +90,7 @@
Expand All
{
viewer.collapseAll();
}}
From 0c6f5d83733dc8013f0832a4bbc9af56e0d89245 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 19:11:32 -0700
Subject: [PATCH 5/7] Fix sidebar covering menu bar items
---
web/src/routes/+page.svelte | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index a1acb13..7e903de 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -118,7 +118,7 @@
class="absolute top-0 z-10 flex h-full w-full flex-col bg-neutral
data-[collapsed=true]:hidden
data-[side=left]:left-0 data-[side=left]:border-e data-[side=right]:right-0 data-[side=right]:order-10 data-[side=right]:border-s
- md:w-[350px] md:shadow-md lg:static
+ md:w-[350px] md:shadow-md lg:static lg:z-0
lg:h-auto lg:shadow-none"
data-side={globalOptions.sidebarLocation}
data-collapsed={viewer.sidebarCollapsed}
From c9971995df56089462ab0baff53bd1419810dc4b Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 19:42:22 -0700
Subject: [PATCH 6/7] Adjust sidebar auto-close behavior
---
.../lib/components/menu-bar/MenuBar.svelte | 6 +-
web/src/routes/+page.svelte | 182 +---------------
web/src/routes/Sidebar.svelte | 194 ++++++++++++++++++
3 files changed, 203 insertions(+), 179 deletions(-)
create mode 100644 web/src/routes/Sidebar.svelte
diff --git a/web/src/lib/components/menu-bar/MenuBar.svelte b/web/src/lib/components/menu-bar/MenuBar.svelte
index 9554132..0e27ddc 100644
--- a/web/src/lib/components/menu-bar/MenuBar.svelte
+++ b/web/src/lib/components/menu-bar/MenuBar.svelte
@@ -14,7 +14,7 @@
diffs.dev
-
+
File
-
+
{
@@ -80,7 +80,7 @@
View
-
+
{
diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte
index 7e903de..14afebd 100644
--- a/web/src/routes/+page.svelte
+++ b/web/src/routes/+page.svelte
@@ -1,82 +1,26 @@
@@ -112,107 +58,8 @@
-
-
-
-
-
-
- {#if viewer.fileTreeFilterDebounced.current}
-
- {/if}
-
-
-
- {#if viewer.filteredFileDetails.length !== viewer.fileDetails.length}
-
- Showing {viewer.filteredFileDetails.length} of {viewer.fileDetails.length} files
-
- {/if}
-
-
- {#snippet fileSnippet(value: FileDetails)}
-
scrollToFileClick(e, value.index)}
- use:focusFileDoubleClick={{ index: value.index }}
- onkeydown={(e) => e.key === "Enter" && viewer.scrollToFile(value.index)}
- role="button"
- tabindex="0"
- id={"file-tree-file-" + value.index}
- >
-
- {value.toFile.substring(value.toFile.lastIndexOf("/") + 1)}
- viewer.toggleChecked(value.index)}
- checked={viewer.fileStates[value.index].checked}
- />
-
- {/snippet}
-
- {#snippet nodeRenderer({ node, collapsed, toggleCollapse })}
- {@const folderIcon = collapsed ? "octicon--file-directory-fill-16" : "octicon--file-directory-open-fill-16"}
- {#if node.data.type === "file"}
- {@render fileSnippet(node.data.data as FileDetails)}
- {:else}
- e.key === "Enter" && toggleCollapse()}
- role="button"
- tabindex="0"
- >
-
- {node.data.data}
- {#if collapsed}
-
- {:else}
-
- {/if}
-
- {/if}
- {/snippet}
- {#snippet childWrapper({ node, collapsed, children })}
-
- {@render children({ node })}
-
- {/snippet}
-
-
-
-
+
+
{#if viewer.diffMetadata !== null}
@@ -285,20 +132,3 @@
-
-
diff --git a/web/src/routes/Sidebar.svelte b/web/src/routes/Sidebar.svelte
new file mode 100644
index 0000000..ad1be4f
--- /dev/null
+++ b/web/src/routes/Sidebar.svelte
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+ {#if viewer.fileTreeFilterDebounced.current}
+
+ {/if}
+
+
+
+ {#if viewer.filteredFileDetails.length !== viewer.fileDetails.length}
+
+ Showing {viewer.filteredFileDetails.length} of {viewer.fileDetails.length} files
+
+ {/if}
+
+
+ {#snippet fileSnippet(value: FileDetails)}
+
scrollToFileClick(e, value.index)}
+ use:focusFileDoubleClick={{ index: value.index }}
+ onkeydown={(e) => e.key === "Enter" && viewer.scrollToFile(value.index)}
+ role="button"
+ tabindex="0"
+ id={"file-tree-file-" + value.index}
+ >
+
+ {value.toFile.substring(value.toFile.lastIndexOf("/") + 1)}
+ viewer.toggleChecked(value.index)}
+ checked={viewer.fileStates[value.index].checked}
+ />
+
+ {/snippet}
+
+ {#snippet nodeRenderer({ node, collapsed, toggleCollapse })}
+ {@const folderIcon = collapsed ? "octicon--file-directory-fill-16" : "octicon--file-directory-open-fill-16"}
+ {#if node.data.type === "file"}
+ {@render fileSnippet(node.data.data as FileDetails)}
+ {:else}
+ e.key === "Enter" && toggleCollapse()}
+ role="button"
+ tabindex="0"
+ >
+
+ {node.data.data}
+ {#if collapsed}
+
+ {:else}
+
+ {/if}
+
+ {/if}
+ {/snippet}
+ {#snippet childWrapper({ node, collapsed, children })}
+
+ {@render children({ node })}
+
+ {/snippet}
+
+
+
+
+
+
From 27db22ab660b5c17713da813e3b27dc2329f9789 Mon Sep 17 00:00:00 2001
From: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
Date: Fri, 14 Nov 2025 20:19:59 -0700
Subject: [PATCH 7/7] Shrink settings dialog
---
web/src/lib/components/settings/SettingsDialog.svelte | 2 +-
web/src/routes/OpenDiffDialog.svelte | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/web/src/lib/components/settings/SettingsDialog.svelte b/web/src/lib/components/settings/SettingsDialog.svelte
index 80881b4..e00547a 100644
--- a/web/src/lib/components/settings/SettingsDialog.svelte
+++ b/web/src/lib/components/settings/SettingsDialog.svelte
@@ -38,7 +38,7 @@
class="fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
/>
Settings
diff --git a/web/src/routes/OpenDiffDialog.svelte b/web/src/routes/OpenDiffDialog.svelte
index d04f571..3c2c45c 100644
--- a/web/src/routes/OpenDiffDialog.svelte
+++ b/web/src/routes/OpenDiffDialog.svelte
@@ -285,7 +285,7 @@
class="fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
/>