From 25b98d7c4dde9bbfe20d6337df2eb5af3ff01ce4 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Fri, 10 Jul 2026 12:24:14 +0200 Subject: [PATCH 1/3] feat(ui): Implement theme persistence and system preference support Adds comprehensive theme management across the application, allowing users to save their preferred color scheme (light/dark) or automatically follow the operating system's settings. This required updating state management in App.svelte to handle theme loading and applying changes dynamically via CSS variables. The styling layer was significantly updated with new CSS variables and rules for both dark and light modes, ensuring visual consistency across all components like dialogs, buttons, and sidebars. - Added logic to detect system color scheme preference - Implemented full support for persistent light/dark mode switching - Overhauled global CSS variables for improved theme adaptability --- src/App.svelte | 49 +- src/app.css | 533 ++++++++++++++++++- src/lib/TitleBar.svelte | 38 +- src/lib/components/AppSettingsDialog.svelte | 36 +- src/lib/components/RepoLoadingOverlay.svelte | 41 ++ src/lib/types.ts | 1 + src/main.ts | 17 + 7 files changed, 680 insertions(+), 35 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 6dd6fdb..e871891 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -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} { appSettingsOpen = false; }} /> diff --git a/src/app.css b/src/app.css index 438e44a..3af3a87 100644 --- a/src/app.css +++ b/src/app.css @@ -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; } diff --git a/src/lib/TitleBar.svelte b/src/lib/TitleBar.svelte index e505e47..3d14bea 100644 --- a/src/lib/TitleBar.svelte +++ b/src/lib/TitleBar.svelte @@ -24,22 +24,44 @@ export let onToggleAutoRefresh: () => void = () => {}; export let onOpenSettings: () => void = () => {}; - const win = getCurrentWindow(); + let win: ReturnType | 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(); + }
@@ -200,12 +222,12 @@
- -
diff --git a/src/lib/components/AppSettingsDialog.svelte b/src/lib/components/AppSettingsDialog.svelte index 099f5a4..345d936 100644 --- a/src/lib/components/AppSettingsDialog.svelte +++ b/src/lib/components/AppSettingsDialog.svelte @@ -1,19 +1,22 @@ @@ -38,6 +41,31 @@
{ event.preventDefault(); save(); }}> +
+
+
+ +
+ + + +
+
+