Opt/light mode #23

Merged
Christoph merged 3 commits from opt/light_mode into main 2026-07-10 16:22:07 +00:00
7 changed files with 680 additions and 35 deletions
Showing only changes of commit 25b98d7c4d - Show all commits
+47 -2
View File
@@ -98,6 +98,7 @@
import type {
AiSettings,
AppTheme,
AnalyticsSettings,
CommitAiPhase,
ConflictFile,
@@ -165,6 +166,7 @@
const REPO_STATUS_CACHE_KEY = "gitlite.repoStatusCache.v1";
const AI_SETTINGS_KEY = "gitlite.aiSettings.v1";
const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1";
const APP_THEME_KEY = "gitlite.theme.v1";
const COMMIT_PANEL_HEIGHT_KEY = "gitlite.commitPanelHeight.v1";
const LEFT_SIDEBAR_WIDTH_KEY = "gitlite.leftSidebarWidth.v1";
const LEFT_BRANCH_PANEL_HEIGHT_KEY = "gitlite.leftBranchPanelHeight.v1";
@@ -239,6 +241,7 @@
let appSettingsOpen = false;
let analyticsNoticeOpen = false;
let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings();
let appTheme: AppTheme = loadThemePreference();
let localModelOptions: LocalModelOption[] = [];
let errorMessage = "";
let operation = "";
@@ -337,6 +340,7 @@
let fileHistoryResizeStartX = 0;
let fileHistoryResizeStartWidth = 0;
let fileHistoryCollapsed = true;
let themeMediaQuery: MediaQueryList | undefined;
// ── Derived ────────────────────────────────────────────────────────────────
@@ -389,13 +393,18 @@
$: leftSidebarRows = buildLeftSidebarRows(branchPanelCollapsed, stashPanelCollapsed, explorerPanelCollapsed);
$: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed;
$: applyThemePreference(appTheme);
// ── Lifecycle ──────────────────────────────────────────────────────────────
onMount(() => {
themeMediaQuery = window.matchMedia("(prefers-color-scheme: light)");
themeMediaQuery.addEventListener("change", handleSystemThemeChange);
void runStartupSequence();
});
onDestroy(() => {
themeMediaQuery?.removeEventListener("change", handleSystemThemeChange);
if (autoRefreshTimer) clearInterval(autoRefreshTimer);
if (backgroundRepoStatusTimer) clearInterval(backgroundRepoStatusTimer);
if (backgroundFetchTimer) clearInterval(backgroundFetchTimer);
@@ -754,11 +763,46 @@
trackEvent("app_started", { first_run: 1 });
}
function saveAppSettings(next: AnalyticsSettings) {
function loadThemePreference(): AppTheme {
try {
const stored = localStorage.getItem(APP_THEME_KEY);
if (stored === "system" || stored === "light" || stored === "dark") return stored;
} catch {
// Local storage is optional; system theme is a sane default.
}
return "system";
}
function persistThemePreference(next: AppTheme) {
try {
localStorage.setItem(APP_THEME_KEY, next);
} catch {
// Ignore storage quota/private-mode errors.
}
}
function applyThemePreference(next: AppTheme) {
const prefersLight = themeMediaQuery?.matches ?? window.matchMedia("(prefers-color-scheme: light)").matches;
const resolved = next === "system"
? prefersLight
? "light"
: "dark"
: next;
document.documentElement.dataset.themePreference = next;
document.documentElement.dataset.theme = resolved;
}
function handleSystemThemeChange() {
if (appTheme === "system") applyThemePreference(appTheme);
}
function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme) {
analyticsSettings = next;
appTheme = nextTheme;
persistAnalyticsSettings(next);
persistThemePreference(nextTheme);
appSettingsOpen = false;
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1 });
if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme });
}
function updateCommitMessage(message: string) {
@@ -3932,6 +3976,7 @@
{#if appSettingsOpen}
<AppSettingsDialog
analytics={analyticsSettings}
theme={appTheme}
onSave={saveAppSettings}
onClose={() => { appSettingsOpen = false; }}
/>
+512 -21
View File
@@ -29,26 +29,88 @@
--font-mono: "Cascadia Code", "SFMono-Regular", Consolas, monospace;
}
:root {
--app-color-scheme: dark;
--app-bg:
radial-gradient(circle at 22% 8%, rgba(189, 52, 254, 0.28), transparent 34%),
radial-gradient(circle at 74% 10%, rgba(65, 209, 255, 0.22), transparent 30%),
radial-gradient(circle at 88% 78%, rgba(255, 211, 67, 0.1), transparent 26%),
linear-gradient(135deg, #090912 0%, #111122 45%, #0b0b14 100%);
--app-button-bg: linear-gradient(180deg, rgba(42, 43, 70, 0.9), rgba(27, 28, 48, 0.92));
--app-input-bg: rgba(13, 14, 26, 0.78);
--app-select-option-bg: #1c2033;
--app-panel-highlight: linear-gradient(180deg, rgba(255,255,255,0.045), transparent 72%);
--app-panel-shadow: 0 18px 60px rgba(0,0,0,0.24), inset 0 1px 0 rgba(255,255,255,0.05);
--app-dialog-backdrop: #050712;
--app-dialog-bg: #111321;
--app-dialog-chrome: #171a2b;
--app-dialog-shadow: 0 24px 72px rgba(0, 0, 0, 0.6), 0 2px 12px rgba(0,0,0,0.4);
--app-settings-row-bg: #151827;
--app-scrollbar-thumb: #2a2f45;
--app-scrollbar-thumb-hover: #3a3f58;
}
:root[data-theme="light"] {
--color-ink: #172033;
--color-ink-muted: #475569;
--color-ink-faint: #728098;
--color-ink-dim: #5f6f89;
--color-ink-quiet: #66758d;
--color-surface: rgba(255, 255, 255, 0.86);
--color-surface-alt: #f3f6fb;
--color-surface-dim: rgba(244, 247, 252, 0.92);
--color-surface-hover: rgba(226, 233, 245, 0.82);
--color-surface-raised: rgba(255, 255, 255, 0.94);
--color-surface-solid: #ffffff;
--color-border: rgba(61, 89, 142, 0.24);
--color-border-subtle: rgba(34, 49, 78, 0.11);
--color-border-input: rgba(46, 117, 182, 0.28);
--color-primary: #315fd6;
--color-primary-dark: #284cb4;
--color-accent: #0f8fb5;
--color-bar: rgba(248, 251, 255, 0.94);
--color-bar-text: #172033;
--color-bar-muted: #64728a;
--app-color-scheme: light;
--app-bg:
radial-gradient(circle at 12% 0%, rgba(49, 95, 214, 0.12), transparent 30%),
radial-gradient(circle at 82% 4%, rgba(15, 143, 181, 0.12), transparent 28%),
linear-gradient(135deg, #f7f9fd 0%, #eef3f9 48%, #f8fafc 100%);
--app-button-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(235, 240, 249, 0.95));
--app-input-bg: rgba(255, 255, 255, 0.9);
--app-select-option-bg: #ffffff;
--app-panel-highlight: linear-gradient(180deg, rgba(255,255,255,0.72), transparent 70%);
--app-panel-shadow: 0 18px 48px rgba(28, 44, 74, 0.12), inset 0 1px 0 rgba(255,255,255,0.86);
--app-dialog-backdrop: rgba(232, 238, 248, 0.96);
--app-dialog-bg: #ffffff;
--app-dialog-chrome: #f4f7fc;
--app-dialog-shadow: 0 24px 72px rgba(28, 44, 74, 0.2), 0 2px 12px rgba(28,44,74,0.1);
--app-settings-row-bg: #f8fafd;
--app-scrollbar-thumb: #c2cada;
--app-scrollbar-thumb-hover: #aeb8cb;
}
@layer base {
*, *::before, *::after { box-sizing: border-box; }
html, body, #app { width: 100%; height: 100%; margin: 0; }
* { scrollbar-width: thin; scrollbar-color: #2a2f45 transparent; }
* { scrollbar-width: thin; scrollbar-color: var(--app-scrollbar-thumb) transparent; }
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #2a2f45; border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: #3a3f58; }
::-webkit-scrollbar-thumb { background: var(--app-scrollbar-thumb); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--app-scrollbar-thumb-hover); }
html { color-scheme: dark; }
html { color-scheme: var(--app-color-scheme); }
body {
overflow: hidden;
color: var(--color-ink);
background:
radial-gradient(circle at 22% 8%, rgba(189, 52, 254, 0.28), transparent 34%),
radial-gradient(circle at 74% 10%, rgba(65, 209, 255, 0.22), transparent 30%),
radial-gradient(circle at 88% 78%, rgba(255, 211, 67, 0.1), transparent 26%),
linear-gradient(135deg, #090912 0%, #111122 45%, #0b0b14 100%);
background: var(--app-bg);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
@@ -68,7 +130,7 @@
border: 1px solid var(--color-border);
border-radius: 8px;
color: var(--color-ink-muted);
background: linear-gradient(180deg, rgba(42, 43, 70, 0.9), rgba(27, 28, 48, 0.92));
background: var(--app-button-bg);
cursor: pointer;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
font-size: 13px;
@@ -85,7 +147,7 @@
border: 1px solid var(--color-border-input);
border-radius: 8px;
color: var(--color-ink);
background: rgba(13, 14, 26, 0.78);
background: var(--app-input-bg);
outline: none;
}
input:focus, textarea:focus, select:focus {
@@ -117,7 +179,7 @@
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(90, 140, 248, 0.18);
}
select option { background: #1c2033; color: var(--color-ink); }
select option { background: var(--app-select-option-bg); color: var(--color-ink); }
}
@layer components {
@@ -169,9 +231,9 @@
border: 1px solid var(--color-border);
border-radius: 12px;
background:
linear-gradient(180deg, rgba(255,255,255,0.045), transparent 72%),
var(--app-panel-highlight),
var(--color-surface);
box-shadow: 0 18px 60px rgba(0,0,0,0.24), inset 0 1px 0 rgba(255,255,255,0.05);
box-shadow: var(--app-panel-shadow);
backdrop-filter: blur(16px);
}
@@ -2630,7 +2692,7 @@
display: grid;
place-items: center;
padding: 24px;
background-color: #050712;
background-color: var(--app-dialog-backdrop);
background-image: none;
}
@@ -2645,9 +2707,9 @@
height: min(760px, 100%);
border: 1px solid var(--color-border);
border-radius: 12px;
background-color: #111321;
background-color: var(--app-dialog-bg);
background-image: none;
box-shadow: 0 24px 72px rgba(0, 0, 0, 0.6), 0 2px 12px rgba(0,0,0,0.4);
box-shadow: var(--app-dialog-shadow);
overflow: hidden;
}
.compare-dialog {
@@ -2795,6 +2857,56 @@
font-size: 15px;
font-weight: 750;
}
.settings-segmented {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 6px;
min-width: 0;
padding: 4px;
border: 1px solid var(--color-border-subtle);
border-radius: 8px;
background: var(--app-settings-row-bg);
}
.settings-segmented label {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
min-height: 32px;
padding: 0 10px;
border: 1px solid transparent;
border-radius: 6px;
color: var(--color-ink-dim);
cursor: pointer;
font-size: 12px;
font-weight: 800;
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.settings-segmented label:hover {
border-color: var(--color-border-subtle);
color: var(--color-ink);
background: var(--color-surface-hover);
}
.settings-segmented label.active {
border-color: var(--color-border-input);
color: var(--color-ink);
background: var(--color-surface-raised);
box-shadow: 0 1px 4px rgba(28,44,74,0.08);
}
.settings-segmented input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
.settings-segmented span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.settings-toggle-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
@@ -2804,7 +2916,7 @@
padding: 12px;
border: 1px solid var(--color-border-subtle);
border-radius: 8px;
background: #151827;
background: var(--app-settings-row-bg);
color: var(--color-ink-muted);
cursor: pointer;
}
@@ -2970,7 +3082,7 @@
gap: 8px;
}
.dialog-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--color-border-subtle); background: #171a2b; }
.dialog-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; border-bottom: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); }
.dialog-header > div:first-child { min-width: 0; }
.dialog-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex: 0 0 auto; min-width: 0; }
.compare-restore { max-width: 170px; min-width: 0; }
@@ -2984,7 +3096,7 @@
.dialog-body { display: grid; grid-template-columns: 280px minmax(0, 1fr); min-height: 0; }
.compare-dialog .dialog-body { grid-template-columns: minmax(260px, 320px) minmax(0, 1fr); }
.dialog-files { display: grid; align-content: start; gap: 4px; padding: 8px; overflow: auto; border-right: 1px solid var(--color-border-subtle); background: #171a2b; }
.dialog-files { display: grid; align-content: start; gap: 4px; padding: 8px; overflow: auto; border-right: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); }
.dialog-file-row {
display: grid;
@@ -3132,7 +3244,7 @@
letter-spacing: 0;
}
.dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-top: 1px solid var(--color-border-subtle); background: #171a2b; }
.dialog-footer { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 16px; border-top: 1px solid var(--color-border-subtle); background: var(--app-dialog-chrome); }
.dialog-footer-info { color: var(--color-ink-dim); font-size: 13px; font-weight: 700; }
.prepared-tag { display: inline-flex; align-items: center; gap: 4px; margin-right: auto; color: #4eca76; font-size: 12px; font-weight: 700; }
@@ -4388,6 +4500,381 @@
.resolve-path { overflow: hidden; color: var(--color-ink-faint); font-family: var(--font-mono); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
}
:root[data-theme="light"] button:hover:not(:disabled) {
background: var(--color-surface-hover);
}
:root[data-theme="light"] input:focus,
:root[data-theme="light"] textarea:focus,
:root[data-theme="light"] select:focus {
box-shadow: 0 0 0 3px rgba(49, 95, 214, 0.14), 0 10px 24px rgba(28, 44, 74, 0.06);
}
:root[data-theme="light"] .btn-primary {
border-color: rgba(49, 95, 214, 0.72);
background: linear-gradient(135deg, #315fd6 0%, #0f8fb5 100%);
box-shadow: 0 10px 24px rgba(49, 95, 214, 0.18);
}
:root[data-theme="light"] .btn-primary:hover:not(:disabled) {
border-color: rgba(15, 143, 181, 0.72);
background: linear-gradient(135deg, #3d6ee4 0%, #139dc7 100%);
}
:root[data-theme="light"] .btn-secondary {
border-color: rgba(15, 143, 181, 0.28);
color: #0c6683;
background: linear-gradient(180deg, rgba(15, 143, 181, 0.08), rgba(49, 95, 214, 0.06));
}
:root[data-theme="light"] .btn-secondary:hover:not(:disabled) {
color: #0d4f9e;
background: linear-gradient(180deg, rgba(15, 143, 181, 0.13), rgba(49, 95, 214, 0.1));
}
:root[data-theme="light"] .titlebar {
background:
linear-gradient(90deg, rgba(49, 95, 214, 0.08), rgba(15, 143, 181, 0.05), rgba(214, 156, 49, 0.04)),
var(--color-bar);
}
:root[data-theme="light"] .titlebar-brand,
:root[data-theme="light"] .titlebar-right {
border-color: rgba(34,49,78,0.1);
}
:root[data-theme="light"] .titlebar-brand-icon img {
filter: drop-shadow(0 1px 2px rgba(28,44,74,0.18));
}
:root[data-theme="light"] .tb-version {
color: rgba(23,32,51,0.36);
}
:root[data-theme="light"] .titlebar-info svg {
color: var(--color-accent);
}
:root[data-theme="light"] .tb-repo,
:root[data-theme="light"] .tb-no-repo {
color: var(--color-bar-muted);
}
:root[data-theme="light"] .tb-sep {
color: rgba(23,32,51,0.28);
}
:root[data-theme="light"] .tb-branch {
color: var(--color-bar-text);
}
:root[data-theme="light"] .tb-action:hover:not(:disabled),
:root[data-theme="light"] .tb-btn:hover:not(:disabled) {
background: rgba(49,95,214,0.08);
color: var(--color-ink);
}
:root[data-theme="light"] .titlebar-divider {
background: rgba(34,49,78,0.1);
}
:root[data-theme="light"] .tb-btn {
color: var(--color-bar-muted);
}
:root[data-theme="light"] .topbar {
border-color: var(--color-border);
background:
linear-gradient(90deg, rgba(49,95,214,0.06), rgba(15,143,181,0.045), rgba(214,156,49,0.035)),
var(--color-surface);
}
:root[data-theme="light"] .repo-tabbar,
:root[data-theme="light"] .repo-management-tools {
background: rgba(255,255,255,0.78);
}
:root[data-theme="light"] .repo-tab-wrap {
background: rgba(244,247,252,0.72);
}
:root[data-theme="light"] .repo-tab-wrap.active {
background: rgba(49,95,214,0.1);
}
:root[data-theme="light"] .repo-row {
border-bottom-color: rgba(34,49,78,0.08);
}
:root[data-theme="light"] .repo-row-main:hover:not(:disabled) {
background: rgba(49,95,214,0.07);
}
:root[data-theme="light"] .repo-row-icon {
border-left-color: rgba(34,49,78,0.08);
}
:root[data-theme="light"] .notice.error,
:root[data-theme="light"] .cred-error {
color: #b33445;
background: rgba(210, 55, 75, 0.08);
}
:root[data-theme="light"] .notice.busy {
color: #315fd6;
}
:root[data-theme="light"] .notice.conflict,
:root[data-theme="light"] .notice-action.conflict,
:root[data-theme="light"] .resolve-status,
:root[data-theme="light"] .resolve-conflict-label {
color: #96620f;
}
:root[data-theme="light"] .notice.rebase,
:root[data-theme="light"] .notice-action.rebase {
color: #6f3ca8;
}
:root[data-theme="light"] .update-toast {
box-shadow: 0 22px 56px rgba(28,44,74,0.18), 0 0 0 1px rgba(255,255,255,0.75) inset;
}
:root[data-theme="light"] .branch-filter-backdrop {
background: rgba(232,238,248,0.96);
}
:root[data-theme="light"] .branch-filter-dialog {
background:
linear-gradient(180deg, #ffffff 0%, #f4f7fc 72%),
#f4f7fc;
box-shadow: var(--app-dialog-shadow);
}
:root[data-theme="light"] .graph-branch-dialog-button,
:root[data-theme="light"] .branch-filter-option {
color: #16723b;
}
:root[data-theme="light"] .graph-branch-dialog-button span {
color: #ffffff;
background: #22834a;
}
:root[data-theme="light"] .main-panel {
background: var(--color-surface);
box-shadow: var(--app-panel-shadow);
}
:root[data-theme="light"] .repo-management {
background: var(--color-surface);
box-shadow: var(--app-panel-shadow);
}
:root[data-theme="light"] .repo-section {
background: rgba(255,255,255,0.62);
}
:root[data-theme="light"] .repo-section > header {
background: rgba(244,247,252,0.88);
}
:root[data-theme="light"] .repo-empty {
background: rgba(255,255,255,0.48);
}
:root[data-theme="light"] .repo-summary,
:root[data-theme="light"] .lane-header,
:root[data-theme="light"] .stash-toolbar,
:root[data-theme="light"] .dialog-footer,
:root[data-theme="light"] .cred-header {
background: var(--color-surface-dim);
}
:root[data-theme="light"] .file-row.selected,
:root[data-theme="light"] .branch-row.current,
:root[data-theme="light"] .file-history-row.active {
background: rgba(49,95,214,0.08);
}
:root[data-theme="light"] .graph-list {
background: rgba(248,250,253,0.9);
}
:root[data-theme="light"] .graph-gutter {
border-right-color: rgba(34,49,78,0.1);
background:
linear-gradient(90deg, rgba(49,95,214,0.035), rgba(255,255,255,0.18)),
rgba(238,243,249,0.78);
}
:root[data-theme="light"] .graph-dot {
border-color: #ffffff;
box-shadow: 0 0 0 1px rgba(34,49,78,0.16), 0 2px 6px rgba(28,44,74,0.12);
}
:root[data-theme="light"] .graph-dot.merge {
background: #ffffff;
box-shadow: 0 0 0 1px rgba(34,49,78,0.14);
}
:root[data-theme="light"] .commit-body {
background: rgba(255,255,255,0.72);
}
:root[data-theme="light"] .graph-row + .graph-row .commit-body {
border-top-color: rgba(34,49,78,0.08);
}
:root[data-theme="light"] .graph-row:hover .commit-body {
background: rgba(235,241,250,0.9);
}
:root[data-theme="light"] .graph-row.merge-row .commit-body {
background: rgba(248,244,252,0.82);
}
:root[data-theme="light"] .graph-row.tip-row .commit-body {
background:
linear-gradient(90deg, rgba(49,95,214,0.075), transparent 34%),
rgba(255,255,255,0.82);
}
:root[data-theme="light"] .commit-summary {
color: var(--color-ink);
}
:root[data-theme="light"] .commit-avatar {
border-color: rgba(49,95,214,0.18);
color: #315fd6;
background: rgba(232,238,248,0.92);
}
:root[data-theme="light"] .commit-kind {
background: rgba(244,247,252,0.82);
}
:root[data-theme="light"] .commit-hash {
color: #315fd6;
}
:root[data-theme="light"] .commit-branch-chip,
:root[data-theme="light"] .graph-hover-branches span {
border-color: rgba(31,128,76,0.22);
color: #146b3b;
background: rgba(224,246,233,0.92);
box-shadow: 0 8px 22px rgba(28,44,74,0.12);
}
:root[data-theme="light"] .graph-hover-branches span.remote,
:root[data-theme="light"] .branch-filter-option.remote {
border-color: rgba(49,95,214,0.22);
color: #315fd6;
background: rgba(231,237,255,0.92);
}
:root[data-theme="light"] .graph-hover-branches span.ahead {
border-color: rgba(150,98,15,0.28);
color: #96620f;
background: rgba(255,244,224,0.95);
}
:root[data-theme="light"] .graph-hover-branches span.behind {
border-color: rgba(49,95,214,0.26);
color: #315fd6;
background: rgba(231,237,255,0.95);
}
:root[data-theme="light"] .ref-list .ref-chip.head {
color: #ffffff;
background: linear-gradient(135deg, #0f8fb5, #315fd6);
}
:root[data-theme="light"] .ref-list .ref-chip.branch {
color: #16723b;
background: rgba(78,202,118,0.12);
}
:root[data-theme="light"] .ref-list .ref-chip.remote {
color: #315fd6;
background: rgba(49,95,214,0.09);
}
:root[data-theme="light"] .commit-files,
:root[data-theme="light"] .commit-file-button {
background: rgba(248,250,253,0.78);
}
:root[data-theme="light"] .file-history-rail-toggle {
background: linear-gradient(180deg, rgba(49,95,214,0.08), rgba(15,143,181,0.04));
}
:root[data-theme="light"] .file-history-rail-toggle:hover:not(:disabled) {
background: linear-gradient(180deg, rgba(49,95,214,0.12), rgba(15,143,181,0.08));
}
:root[data-theme="light"] .file-history-loading {
background:
radial-gradient(circle at center 42%, rgba(49,95,214,0.1), transparent 52%),
linear-gradient(180deg, rgba(244,247,252,0.88), transparent);
}
:root[data-theme="light"] .file-history-loading-icon {
filter:
drop-shadow(0 10px 14px rgba(28,44,74,0.18))
drop-shadow(0 0 12px rgba(49,95,214,0.18));
}
:root[data-theme="light"] .branch-context-menu {
box-shadow: 0 18px 50px rgba(28,44,74,0.18);
}
:root[data-theme="light"] .dialog-file-row:hover,
:root[data-theme="light"] .branch-row:hover,
:root[data-theme="light"] .explorer-row:hover,
:root[data-theme="light"] .commit-files-toggle:hover:not(:disabled) {
background: var(--color-surface-hover);
}
:root[data-theme="light"] .split-span.split-meta {
background: #e9eef7;
}
:root[data-theme="light"] .resolve-split,
:root[data-theme="light"] .resolve-num,
:root[data-theme="light"] .resolve-cell.empty,
:root[data-theme="light"] .resolve-num.empty {
background: rgba(234,239,248,0.72);
}
:root[data-theme="light"] .diff-line.meta,
:root[data-theme="light"] .resolve-split-marker {
color: #64728a;
}
:root[data-theme="light"] .cred-input input,
:root[data-theme="light"] .cred-expiry input[type="date"] {
background: rgba(255,255,255,0.92);
color-scheme: light;
}
:root[data-theme="light"] .cred-token-hint code {
color: #0d78a0;
}
:root[data-theme="light"] .status-panel-overlay {
background:
radial-gradient(circle at 50% 38%, rgba(49, 95, 214, 0.12), transparent 55%),
rgba(244, 247, 252, 0.68);
}
:root[data-theme="light"] .status-panel-overlay-card {
background:
linear-gradient(180deg, rgba(255,255,255,0.96), rgba(244,247,252,0.94)),
var(--color-surface-raised);
box-shadow: 0 18px 44px rgba(28,44,74,0.16), inset 0 1px 0 rgba(255,255,255,0.9);
}
/* Custom tooltip — appended to body, not clipped by overflow:hidden parents */
.path-tooltip {
position: fixed;
@@ -4476,6 +4963,10 @@
@media (max-width: 740px) {
body { overflow: auto; }
.shell-body { min-height: 100%; gap: 6px; }
.titlebar-info { padding: 0 8px; }
.titlebar-actions .tb-action:not(.auto-toggle):not(:last-child) { display: none; }
.tb-action { width: 36px; padding: 0 8px; }
.tb-btn { width: 34px; }
.workspace { grid-template-columns: 1fr; gap: 6px; }
.left-sidebar-resize-handle { display: none; }
.history-aside { grid-template-columns: 1fr; grid-template-rows: minmax(200px, 1fr) minmax(150px, 0.5fr); min-height: 380px; }
+30 -8
View File
@@ -24,22 +24,44 @@
export let onToggleAutoRefresh: () => void = () => {};
export let onOpenSettings: () => void = () => {};
const win = getCurrentWindow();
let win: ReturnType<typeof getCurrentWindow> | null = null;
let isMaximized = false;
let unlisten: (() => void) | undefined;
let appVersion = "";
onMount(async () => {
isMaximized = await win.isMaximized();
unlisten = await win.onResized(async () => {
try {
win = getCurrentWindow();
isMaximized = await win.isMaximized();
});
appVersion = await getVersion();
unlisten = await win.onResized(async () => {
isMaximized = await win?.isMaximized() ?? false;
});
} catch {
win = null;
}
try {
appVersion = await getVersion();
} catch {
appVersion = "";
}
});
onDestroy(() => {
unlisten?.();
});
function minimizeWindow() {
void win?.minimize();
}
function toggleMaximizeWindow() {
void win?.toggleMaximize();
}
function closeWindow() {
void win?.close();
}
</script>
<header class="titlebar" data-tauri-drag-region>
@@ -200,12 +222,12 @@
<div class="titlebar-divider" aria-hidden="true"></div>
<div class="titlebar-controls">
<button class="tb-btn" onclick={() => win.minimize()} title="Minimize" aria-label="Minimize">
<button class="tb-btn" onclick={minimizeWindow} title="Minimize" aria-label="Minimize">
<Minus size={12} aria-hidden="true" />
</button>
<button
class="tb-btn"
onclick={() => win.toggleMaximize()}
onclick={toggleMaximizeWindow}
title={isMaximized ? "Restore" : "Maximize"}
aria-label={isMaximized ? "Restore" : "Maximize"}
>
@@ -220,7 +242,7 @@
</svg>
{/if}
</button>
<button class="tb-btn close" onclick={() => win.close()} title="Close" aria-label="Close">
<button class="tb-btn close" onclick={closeWindow} title="Close" aria-label="Close">
<X size={12} aria-hidden="true" />
</button>
</div>
+32 -4
View File
@@ -1,19 +1,22 @@
<script lang="ts">
import { Check, Settings, X } from "@lucide/svelte";
import type { AnalyticsSettings } from "../types";
import type { AnalyticsSettings, AppTheme } from "../types";
interface Props {
analytics: AnalyticsSettings;
onSave: (settings: AnalyticsSettings) => void;
theme: AppTheme;
onSave: (settings: AnalyticsSettings, theme: AppTheme) => void;
onClose: () => void;
}
let { analytics, onSave = () => {}, onClose = () => {} }: Props = $props();
let { analytics, theme = "system", onSave = () => {}, onClose = () => {} }: Props = $props();
let analyticsEnabled = $state(true);
let selectedTheme = $state<AppTheme>("system");
$effect(() => {
analyticsEnabled = analytics.enabled;
selectedTheme = theme;
});
function save() {
@@ -21,7 +24,7 @@
...analytics,
enabled: analyticsEnabled,
noticeSeen: true,
});
}, selectedTheme);
}
</script>
@@ -38,6 +41,31 @@
</header>
<form class="app-settings-form" onsubmit={(event) => { event.preventDefault(); save(); }}>
<section class="settings-section">
<header>
<Settings size={16} aria-hidden="true" />
<div>
<span class="eyebrow">Appearance</span>
<h3>Theme</h3>
</div>
</header>
<div class="settings-segmented" role="radiogroup" aria-label="Theme">
<label class:active={selectedTheme === "system"}>
<input type="radio" bind:group={selectedTheme} value="system" />
<span>System</span>
</label>
<label class:active={selectedTheme === "light"}>
<input type="radio" bind:group={selectedTheme} value="light" />
<span>Light</span>
</label>
<label class:active={selectedTheme === "dark"}>
<input type="radio" bind:group={selectedTheme} value="dark" />
<span>Dark</span>
</label>
</div>
</section>
<section class="settings-section">
<header>
<Settings size={16} aria-hidden="true" />
@@ -187,6 +187,47 @@
animation: bar-slide 1.35s ease-in-out infinite;
}
:global(:root[data-theme="light"]) .repo-loading {
background:
radial-gradient(circle at 50% 42%, rgba(49, 95, 214, 0.12), transparent 34%),
linear-gradient(135deg, #f7f9fd 0%, #eef3f9 48%, #f8fafc 100%);
}
:global(:root[data-theme="light"]) .repo-loading-grid {
opacity: 0.38;
background-image:
linear-gradient(rgba(49, 95, 214, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(15, 143, 181, 0.08) 1px, transparent 1px);
}
:global(:root[data-theme="light"]) .repo-loading-card {
border-color: rgba(61, 89, 142, 0.2);
background:
linear-gradient(180deg, rgba(255,255,255,0.96), rgba(244,247,252,0.94)),
var(--color-surface-raised);
box-shadow:
0 26px 72px rgba(28,44,74,0.18),
inset 0 1px 0 rgba(255,255,255,0.9);
}
:global(:root[data-theme="light"]) .repo-loading-traces .trace {
filter: drop-shadow(0 0 8px rgba(49,95,214,0.22));
}
:global(:root[data-theme="light"]) .repo-loading-traces .trace-cut {
stroke: rgba(49,95,214,0.34);
}
:global(:root[data-theme="light"]) .repo-loading-icon {
filter:
drop-shadow(0 18px 24px rgba(28,44,74,0.2))
drop-shadow(0 0 18px rgba(49,95,214,0.18));
}
:global(:root[data-theme="light"]) .repo-loading-bar {
background: rgba(49,95,214,0.12);
}
@keyframes overlay-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes grid-drift { to { transform: translate3d(42px, 42px, 0); } }
@keyframes icon-float {
+1
View File
@@ -10,6 +10,7 @@ export type FileStatusKind =
export type CommitAiPhase = "idle" | "loading" | "ready" | "error";
export type CommitAiProvider = "local" | "openai" | "anthropic" | "custom";
export type CommitAiLocalProfile = "fast" | "balanced" | "detailed";
export type AppTheme = "system" | "light" | "dark";
export interface CommitAiStatus {
phase: CommitAiPhase;
+17
View File
@@ -3,12 +3,29 @@ import { mount } from "svelte";
import App from "./App.svelte";
import "./app.css";
const THEME_KEY = "gitlite.theme.v1";
const target = document.getElementById("app");
if (!target) {
throw new Error("App target element was not found.");
}
try {
const storedTheme = localStorage.getItem(THEME_KEY);
const preference = storedTheme === "light" || storedTheme === "dark" || storedTheme === "system"
? storedTheme
: "system";
const resolved = preference === "system" && window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: preference === "system"
? "dark"
: preference;
document.documentElement.dataset.themePreference = preference;
document.documentElement.dataset.theme = resolved;
} catch {
// Storage is best-effort; the CSS system theme fallback still applies.
}
const app = mount(App, { target });
export default app;