From 3fdd6d34670600f8094808ebff011d54eb509193 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 10 Jul 2026 20:27:29 +0200 Subject: [PATCH 1/8] Update version to 0.200.10 --- package-lock.json | 4 ++-- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6de55ec..64406e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gitty", - "version": "2026.7.18", + "version": "0.200.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitty", - "version": "2026.7.18", + "version": "0.200.10", "dependencies": { "@lucide/svelte": "^1.21.0", "@tailwindcss/vite": "^4.3.1", diff --git a/package.json b/package.json index 127d8ed..7c40ac3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitty", - "version": "2026.7.18", + "version": "0.200.10", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index abc3d46..810dd2c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Gitty", - "version": "2026.7.18", + "version": "0.200.10", "identifier": "com.gitty", "build": { "beforeDevCommand": "npm run dev", From d3cde835186a4103a1e03580cb06a37ef503f3d8 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Fri, 10 Jul 2026 22:16:06 +0200 Subject: [PATCH 2/8] style(css): standardize code surface and highlight colors Introduces a comprehensive set of CSS variables defining surfaces, syntax elements, and diff coloring across both light and dark themes. This refactoring replaces numerous hardcoded color values throughout the stylesheet with these new variables, ensuring that all components consistently adhere to the defined theme palette. - Added detailed variable definitions for code surfaces (e.g., `--code-surface`, `--code-input-bg`). - Updated diff, hunk, and blame sections to use variable colors for better thematic consistency. - Replaced fixed hex codes with variables across various component backgrounds and text colors. --- src/app.css | 242 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 169 insertions(+), 73 deletions(-) diff --git a/src/app.css b/src/app.css index 71fcbe3..6dc8864 100644 --- a/src/app.css +++ b/src/app.css @@ -44,6 +44,27 @@ --app-settings-row-bg: #141b29; --app-scrollbar-thumb: #303a4f; --app-scrollbar-thumb-hover: #44506a; + + --code-surface: #111321; + --code-surface-raised: #171a2b; + --code-surface-subtle: #0d101a; + --code-surface-muted: #10131e; + --code-surface-meta: #0c0e18; + --code-input-bg: #0b0e18; + --code-hover-bg: #151a2b; + --code-add-text: #5dd88a; + --code-add-strong: #4eca76; + --code-add-bg: rgba(78, 202, 118, 0.09); + --code-add-gutter-bg: rgba(78, 202, 118, 0.1); + --code-delete-text: #ef8080; + --code-delete-strong: #e86060; + --code-delete-bg: rgba(232, 96, 96, 0.1); + --code-delete-gutter-bg: rgba(232, 96, 96, 0.12); + --code-hunk-text: #7aacff; + --code-hunk-bg: rgba(122, 172, 255, 0.08); + --code-match-text: #f3c969; + --code-match-bg: rgba(240, 182, 72, 0.22); + --code-match-gutter-bg: rgba(240, 182, 72, 0.2); } :root[data-theme="light"] { @@ -89,6 +110,27 @@ --app-settings-row-bg: #f8fafd; --app-scrollbar-thumb: #c2cada; --app-scrollbar-thumb-hover: #aeb8cb; + + --code-surface: #fbfcfe; + --code-surface-raised: #f1f5fa; + --code-surface-subtle: #f3f6fb; + --code-surface-muted: #edf1f7; + --code-surface-meta: #e9eef7; + --code-input-bg: #ffffff; + --code-hover-bg: #e8eef7; + --code-add-text: #146c37; + --code-add-strong: #19723d; + --code-add-bg: rgba(25, 114, 61, 0.1); + --code-add-gutter-bg: rgba(25, 114, 61, 0.14); + --code-delete-text: #a91f36; + --code-delete-strong: #b4233b; + --code-delete-bg: rgba(180, 35, 59, 0.09); + --code-delete-gutter-bg: rgba(180, 35, 59, 0.13); + --code-hunk-text: #245cc7; + --code-hunk-bg: rgba(36, 92, 199, 0.09); + --code-match-text: #744500; + --code-match-bg: rgba(230, 158, 31, 0.2); + --code-match-gutter-bg: rgba(230, 158, 31, 0.24); } @layer base { @@ -3124,7 +3166,7 @@ gap: 8px; padding: 7px 12px; border-bottom: 1px solid var(--color-border-subtle); - background: #171a2b; + background: var(--code-surface-raised); font-family: var(--font-mono); font-size: 12px; color: var(--color-ink-muted); @@ -3146,7 +3188,7 @@ font-size: 12px; line-height: 1.5; tab-size: 2; - background: #111321; + background: var(--code-surface); } .split-pane { @@ -3170,8 +3212,8 @@ white-space: pre-wrap; word-break: break-all; } - .split-span.split-meta { color: var(--color-ink-faint); background: #0c0e18; font-size: 11px; } - .split-span.split-hunk { color: #7aacff; background: rgba(122,172,255,0.08); padding: 3px 10px; } + .split-span.split-meta { color: var(--color-ink-faint); background: var(--code-surface-meta); font-size: 11px; } + .split-span.split-hunk { color: var(--code-hunk-text); background: var(--code-hunk-bg); padding: 3px 10px; } .split-num { padding: 0 6px 0 4px; @@ -3180,11 +3222,11 @@ font-size: 11px; user-select: none; border-right: 1px solid var(--color-border-subtle); - background: #0d101a; + background: var(--code-surface-subtle); } - .split-num.del { background: rgba(232,96,96,0.12); color: rgba(232,96,96,0.6); border-right-color: rgba(232,96,96,0.2); } - .split-num.add { background: rgba(78,202,118,0.1); color: rgba(78,202,118,0.6); border-right-color: rgba(78,202,118,0.2); } - .split-num.empty { background: #10131e; } + .split-num.del { background: var(--code-delete-gutter-bg); color: var(--code-delete-strong); border-right-color: rgba(232,96,96,0.2); } + .split-num.add { background: var(--code-add-gutter-bg); color: var(--code-add-strong); border-right-color: rgba(78,202,118,0.2); } + .split-num.empty { background: var(--code-surface-muted); } .split-cell { padding: 0 8px; @@ -3193,20 +3235,20 @@ min-width: 0; overflow: visible; } - .split-cell.del { background: rgba(232,96,96,0.1); color: #ef8080; } - .split-cell.add { background: rgba(78,202,118,0.09); color: #5dd88a; } - .split-cell.empty { background: #10131e; } + .split-cell.del { background: var(--code-delete-bg); color: var(--code-delete-text); } + .split-cell.add { background: var(--code-add-bg); color: var(--code-add-text); } + .split-cell.empty { background: var(--code-surface-muted); } /* Search-hit highlight: amber, distinct from add (green) / del (red). Higher specificity so it overrides the add/del backgrounds on a matched line. */ .split-diff .split-cell.match { - background: rgba(240,182,72,0.22); - color: #f3c969; + background: var(--code-match-bg); + color: var(--code-match-text); box-shadow: inset 2px 0 0 rgba(240,182,72,0.9); } .split-diff .split-num.match { - background: rgba(240,182,72,0.2); - color: rgba(240,182,72,0.9); + background: var(--code-match-gutter-bg); + color: var(--code-match-text); border-right-color: rgba(240,182,72,0.35); } @@ -3226,7 +3268,7 @@ text-transform: uppercase; letter-spacing: 0.05em; color: var(--color-ink-faint); - background: #0d101a; + background: var(--code-surface-subtle); } .split-col-label + .split-col-label { border-left: 1px solid var(--color-border-subtle); } .split-col-hash { @@ -3338,7 +3380,7 @@ .line-patch-scroll { min-height: 0; overflow: auto; - background: #0b0b14; + background: var(--code-surface); } .line-patch-hunk { @@ -3358,7 +3400,7 @@ min-width: 100%; padding: 7px 10px; border-bottom: 1px solid var(--color-border-subtle); - background: rgba(20, 22, 36, 0.96); + background: color-mix(in srgb, var(--code-surface-raised) 96%, transparent); } .line-patch-hunk-head code { color: var(--color-accent); @@ -3378,23 +3420,23 @@ padding: 0 8px; border: 1px solid var(--color-border-subtle); border-radius: 3px; - background: rgba(255, 255, 255, 0.03); + background: var(--code-surface-subtle); color: var(--color-ink); font-size: 12px; font-weight: 700; line-height: 1; } .line-patch-hunk-button:hover:not(:disabled) { - background: rgba(255, 255, 255, 0.08); + background: var(--code-hover-bg); } .line-patch-hunk-button.discard { border-color: rgba(255, 90, 103, 0.7); - color: #ffccd1; + color: var(--code-delete-text); } .line-patch-hunk-button.stage, .line-patch-hunk-button.unstage { border-color: rgba(78, 202, 118, 0.72); - color: #bff1ce; + color: var(--code-add-text); } .line-patch-lines { @@ -3412,12 +3454,12 @@ color: var(--color-ink-muted); } .line-patch-row.add { - background: rgba(78, 202, 118, 0.09); - color: #bff1ce; + background: var(--code-add-bg); + color: var(--code-add-text); } .line-patch-row.delete { - background: rgba(255, 90, 103, 0.1); - color: #ffccd1; + background: var(--code-delete-bg); + color: var(--code-delete-text); } .line-patch-row.meta { color: var(--color-ink-faint); @@ -3427,8 +3469,8 @@ text-align: center; user-select: none; } - .line-patch-row.add .line-patch-prefix { color: #4eca76; } - .line-patch-row.delete .line-patch-prefix { color: #ff6b7a; } + .line-patch-row.add .line-patch-prefix { color: var(--code-add-strong); } + .line-patch-row.delete .line-patch-prefix { color: var(--code-delete-strong); } .line-patch-row code { white-space: pre; font-family: var(--font-mono); @@ -3437,7 +3479,7 @@ .blame-body { min-height: 0; overflow: hidden; - background: #111321; + background: var(--code-surface); } .blame-code-header strong { @@ -3455,7 +3497,7 @@ min-height: 38px; padding: 6px 10px; border-bottom: 1px solid var(--color-border-subtle); - background: #111321; + background: var(--code-surface); } .blame-search-bar svg { position: absolute; @@ -3469,7 +3511,7 @@ padding: 0 34px; border: 1px solid var(--color-border-subtle); border-radius: 6px; - background: #0b0e18; + background: var(--code-input-bg); color: var(--color-ink); font-size: 12px; } @@ -3522,7 +3564,7 @@ border-bottom: 1px solid var(--color-border-subtle); } .blame-group.uncommitted { - background: #171725; + background: var(--code-surface-raised); } .blame-meta { @@ -3535,8 +3577,8 @@ min-width: 0; padding: 8px 12px; border-right: 1px solid var(--color-border-subtle); - background: #0d101a; - box-shadow: 8px 0 18px rgba(0, 0, 0, 0.18); + background: var(--code-surface-subtle); + box-shadow: 8px 0 18px color-mix(in srgb, var(--color-ink) 8%, transparent); } .blame-hash { align-self: flex-start; @@ -3545,7 +3587,7 @@ padding: 2px 7px; border: 1px solid rgba(90,140,248,0.2); border-radius: 5px; - background: #141b2d; + background: var(--code-surface-raised); color: var(--color-accent); font-size: 10.5px; font-weight: 700; @@ -3562,7 +3604,7 @@ } .blame-summary { overflow: hidden; - color: #aeb6d8; + color: var(--color-ink-muted); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; @@ -3572,7 +3614,7 @@ font-size: 10.5px; } .blame-group:hover .blame-meta { - background: #111728; + background: var(--code-hover-bg); } .blame-group.uncommitted .blame-hash, .blame-group.uncommitted .blame-author { @@ -3580,7 +3622,7 @@ } .blame-group.uncommitted .blame-hash { border-color: rgba(232, 180, 90, 0.26); - background: #271f14; + background: color-mix(in srgb, #e8b45a 13%, var(--code-surface)); } .blame-lines { @@ -3595,16 +3637,16 @@ min-height: 20px; } .blame-group:hover .blame-line-number { - background: #111728; + background: var(--code-hover-bg); } .blame-group:hover .blame-line-code { - background: #151a2b; + background: var(--code-hover-bg); } .blame-search-hit { padding: 0 1px; border-radius: 3px; - background: rgba(240,182,72,0.28); - color: #f3d487; + background: var(--code-match-bg); + color: var(--code-match-text); } .global-search-body { @@ -3800,8 +3842,9 @@ margin: 0; padding: 8px 10px; border-radius: 7px; - color: #5dd88a; - background: rgba(78,202,118,0.08); + border: 1px solid color-mix(in srgb, var(--code-add-strong) 18%, transparent); + color: var(--code-add-text); + background: var(--code-add-bg); font-family: var(--font-mono); font-size: 12px; line-height: 1.45; @@ -4321,14 +4364,14 @@ } .diff-line { display: block; white-space: pre-wrap; word-break: break-word; } .diff-line.meta { color: var(--color-ink-faint); } - .diff-line.hunk { color: #7aacff; background: rgba(122,172,255,0.07); } - .diff-line.add { color: #4eca76; background: rgba(78,202,118,0.09); } - .diff-line.del { color: #e86060; background: rgba(232,96,96,0.09); } + .diff-line.hunk { color: var(--code-hunk-text); background: var(--code-hunk-bg); } + .diff-line.add { color: var(--code-add-text); background: var(--code-add-bg); } + .diff-line.del { color: var(--code-delete-text); background: var(--code-delete-bg); } .diff-line.context { color: var(--color-ink-muted); } .diff-counts { display: flex; gap: 8px; font-family: var(--font-mono); font-size: 12px; font-weight: 700; } - .diff-counts .adds { color: #4eca76; } - .diff-counts .dels { color: #e86060; } + .diff-counts .adds { color: var(--code-add-strong); } + .diff-counts .dels { color: var(--code-delete-strong); } /* --- Conflict resolver --- */ @@ -4399,7 +4442,7 @@ overflow: hidden; border: 1px solid var(--color-border-subtle); border-radius: 6px; - background: rgba(0,0,0,0.12); + background: var(--code-surface); font-family: var(--font-mono); font-size: 12px; line-height: 1.5; @@ -4431,19 +4474,19 @@ font-weight: 800; text-transform: uppercase; } - .resolve-split-marker.unresolved { color: #ef8080; background: rgba(232,96,96,0.12); } + .resolve-split-marker.unresolved { color: var(--code-delete-text); background: var(--code-delete-gutter-bg); } .resolve-num { padding: 0 6px 0 4px; border-right: 1px solid var(--color-border-subtle); color: var(--color-ink-faint); - background: rgba(0,0,0,0.14); + background: var(--code-surface-subtle); text-align: right; user-select: none; } - .resolve-num.ours { color: rgba(78,202,118,0.65); background: rgba(78,202,118,0.11); border-right-color: rgba(78,202,118,0.2); } - .resolve-num.theirs { color: rgba(122,172,255,0.65); background: rgba(122,172,255,0.11); border-right-color: rgba(122,172,255,0.2); } - .resolve-num.empty { background: rgba(0,0,0,0.07); } + .resolve-num.ours { color: var(--code-add-strong); background: var(--code-add-gutter-bg); border-right-color: rgba(78,202,118,0.2); } + .resolve-num.theirs { color: var(--code-hunk-text); background: var(--code-hunk-bg); border-right-color: rgba(122,172,255,0.2); } + .resolve-num.empty { background: var(--code-surface-muted); } .resolve-cell { min-width: 0; @@ -4452,9 +4495,9 @@ color: var(--color-ink-muted); white-space: pre; } - .resolve-cell.ours { color: #5dd88a; background: rgba(78,202,118,0.1); } - .resolve-cell.theirs { color: #8fb4ff; background: rgba(90,140,248,0.11); } - .resolve-cell.empty { background: rgba(0,0,0,0.06); } + .resolve-cell.ours { color: var(--code-add-text); background: var(--code-add-bg); } + .resolve-cell.theirs { color: var(--code-hunk-text); background: var(--code-hunk-bg); } + .resolve-cell.empty { background: var(--code-surface-muted); } .resolve-cell.dimmed { opacity: 0.42; filter: grayscale(0.5); } .resolve-context { margin: 0; padding: 2px 8px; overflow-x: auto; font-family: var(--font-mono); font-size: 12px; line-height: 1.5; tab-size: 2; color: var(--color-ink-muted); } @@ -4474,13 +4517,13 @@ .resolve-side.dimmed { opacity: 0.4; filter: grayscale(0.5); } .resolve-side-label { font-size: 11px; font-weight: 800; text-transform: uppercase; } - .resolve-side.ours .resolve-side-label { color: #4eca76; } - .resolve-side.theirs .resolve-side-label { color: #6a9aff; } + .resolve-side.ours .resolve-side-label { color: var(--code-add-strong); } + .resolve-side.theirs .resolve-side-label { color: var(--code-hunk-text); } .resolve-lines { margin: 0; overflow-x: auto; font-family: var(--font-mono); font-size: 12px; line-height: 1.5; tab-size: 2; } .resolve-line { display: block; white-space: pre-wrap; word-break: break-word; } - .resolve-line.ours { color: #4eca76; } - .resolve-line.theirs { color: #7aacff; } + .resolve-line.ours { color: var(--code-add-text); } + .resolve-line.theirs { color: var(--code-hunk-text); } .resolve-line.context { color: var(--color-ink-muted); } .resolve-binary { display: grid; align-content: start; gap: 12px; padding: 4px; } @@ -4834,22 +4877,75 @@ 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"] .global-search-tabs, +:root[data-theme="light"] .file-search-history, +:root[data-theme="light"] .file-search-history-head { + background: var(--color-surface-dim); +} + +:root[data-theme="light"] .discard-target { + background: var(--code-surface-subtle); +} + +:root[data-theme="light"] .commit-amend-toggle input { + border-color: var(--color-border-input); + background: var(--app-input-bg); +} + +:root[data-theme="light"] .commit-amend-toggle input:checked { + border-color: var(--color-primary); + box-shadow: inset 0 0 0 2px #ffffff; +} + +:root[data-theme="light"] .cred-segment { + border-color: rgba(49,95,214,0.2); + background: #eef3f9; +} + +:root[data-theme="light"] .cred-seg-btn.active { + border-color: rgba(49,95,214,0.26); + background: linear-gradient(135deg, rgba(49,95,214,0.12), rgba(15,143,181,0.08)); + box-shadow: 0 8px 18px rgba(28,44,74,0.1), inset 0 1px 0 rgba(255,255,255,0.9); +} + +:root[data-theme="light"] .cred-close { + border-color: var(--color-border-subtle); + color: var(--color-ink-dim); + background: rgba(255,255,255,0.76); +} + +:root[data-theme="light"] .cred-close:hover:not(:disabled) { + border-color: var(--color-border-input); + color: var(--color-ink); + background: var(--color-surface-hover); +} + +:root[data-theme="light"] .status-badge.modified, +:root[data-theme="light"] .resolve-status, +:root[data-theme="light"] .resolve-conflict-label { + color: #8a580a; +} + +:root[data-theme="light"] .status-badge.added, +:root[data-theme="light"] .status-badge.untracked, +:root[data-theme="light"] .pill-active, +:root[data-theme="light"] .prepared-tag { + color: #19723d; +} + +:root[data-theme="light"] .status-badge.deleted { + color: #b4233b; +} + +:root[data-theme="light"] .status-badge.renamed { + color: #245cc7; +} + :root[data-theme="light"] .cred-input input, :root[data-theme="light"] .cred-expiry input[type="date"] { background: rgba(255,255,255,0.92); From d0bca6236206b41694873aefc6772cecd02fa1e1 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Fri, 10 Jul 2026 22:21:53 +0200 Subject: [PATCH 3/8] refactor(ui): Improve title bar layout using flex and grid The structure of the application title bar has been updated to utilize modern CSS layout techniques like Grid and Flexbox. This refactoring improves the responsiveness and organization of key elements such as repository name, branch indicator, and synchronization status. The changes ensure better alignment and handling of varying content lengths across different viewports. - Restructures repo/branch info into a dedicated context container - Uses flex properties for dynamic sizing of title bar sections - Groups sync indicators into a cohesive unit --- src/app.css | 19 ++++++++++++++----- src/lib/TitleBar.svelte | 28 +++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/src/app.css b/src/app.css index 6dc8864..d673c57 100644 --- a/src/app.css +++ b/src/app.css @@ -610,7 +610,8 @@ } .titlebar-info { - display: flex; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 6px; padding: 0 12px; @@ -618,12 +619,20 @@ height: 100%; overflow: hidden; } - .titlebar-info svg { color: var(--color-accent); flex-shrink: 0; } + .titlebar-context { + display: flex; + align-items: center; + min-width: 0; + gap: 6px; + overflow: hidden; + } + .titlebar-context svg { color: var(--color-accent); flex: 0 0 auto; } - .tb-repo { color: var(--color-bar-muted); font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 160px; } - .tb-sep { color: rgba(255,255,255,0.22); font-size: 13px; } - .tb-branch { color: #f5f7ff; font-size: 12px; font-weight: 700; font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; } + .tb-repo { flex: 0 1 160px; min-width: 0; color: var(--color-bar-muted); font-size: 12px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .tb-sep { flex: 0 0 auto; color: rgba(255,255,255,0.22); font-size: 13px; } + .tb-branch { flex: 1 1 auto; min-width: 0; max-width: 220px; color: #f5f7ff; font-size: 12px; font-weight: 700; font-family: var(--font-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + .tb-sync-group { display: inline-flex; align-items: center; gap: 4px; min-width: max-content; } .tb-sync { display: inline-flex; align-items: center; padding: 1px 6px; border-radius: 999px; font-size: 11px; font-weight: 800; white-space: nowrap; flex-shrink: 0; } .tb-sync.ahead { color: #e0a040; background: rgba(224,160,64,0.13); } .tb-sync.behind { color: #7aacff; background: rgba(122,172,255,0.13); } diff --git a/src/lib/TitleBar.svelte b/src/lib/TitleBar.svelte index 3d14bea..32eff55 100644 --- a/src/lib/TitleBar.svelte +++ b/src/lib/TitleBar.svelte @@ -79,17 +79,23 @@
{#if hasRepository} - {#if repoName} - {repoName} - - {/if} -
+ diff --git a/src/lib/components/ReflogDialog.svelte b/src/lib/components/ReflogDialog.svelte new file mode 100644 index 0000000..764fe4c --- /dev/null +++ b/src/lib/components/ReflogDialog.svelte @@ -0,0 +1,80 @@ + + + diff --git a/src/lib/git.ts b/src/lib/git.ts index 56e7ee4..6bf434c 100644 --- a/src/lib/git.ts +++ b/src/lib/git.ts @@ -10,6 +10,9 @@ import type { GitCommit, GitCommitComparison, GitRepositoryFile, + RebaseCommit, + RebasePlanItem, + ReflogEntry, GitSearchHit, GitStash, GitStatus, @@ -306,6 +309,26 @@ export function rebaseAbort(path: string): Promise { return invoke("rebase_abort", { path }); } +export function listInteractiveRebaseCommits(path: string, base: string): Promise { + return invoke("list_interactive_rebase_commits", { path, base }); +} + +export function startInteractiveRebase( + path: string, + base: string, + plan: RebasePlanItem[], +): Promise { + return invoke("start_interactive_rebase", { path, base, plan }); +} + +export function listReflog(path: string, limit = 250): Promise { + return invoke("list_reflog", { path, limit }); +} + +export function restoreReflogEntry(path: string, commit: string, branch: string): Promise { + return invoke("restore_reflog_entry", { path, commit, branch }); +} + export function listRepositoryFiles(path: string): Promise { return invoke("list_repository_files", { path }); } diff --git a/src/lib/types.ts b/src/lib/types.ts index b761c75..0b9a25e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -198,6 +198,31 @@ export interface GitBlameResult { lines: GitBlameLine[]; } +export type RebaseAction = "pick" | "reword" | "squash" | "fixup" | "drop"; + +export interface RebaseCommit { + hash: string; + short_hash: string; + summary: string; + author_name: string; + date: string; +} + +export interface RebasePlanItem { + hash: string; + action: RebaseAction; + message: string | null; +} + +export interface ReflogEntry { + hash: string; + short_hash: string; + selector: string; + action: string; + author_name: string; + date: string; +} + export interface StoredCredential { username: string; password: string; From 791275f34178e1c4b2bd700fe4d64b2cacb6ffc2 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Fri, 10 Jul 2026 22:53:24 +0200 Subject: [PATCH 5/8] refactor(git): improve interactive rebase workflow robustness This commit removes the dedicated feature QA page and significantly refactors the internal Git library logic for handling complex workflows, particularly interactive rebase. The changes introduce helper functions to manage temporary files and ensure proper cleanup of rebase artifacts regardless of success or failure. This improves the reliability of advanced git operations within the application. - Added comprehensive cleanup routines for rebase helpers - Centralized constants for rebase file names - Removed obsolete QA feature code --- feature-qa.html | 1 - src-tauri/src/git.rs | 94 ++++++++++++++++++++++++++++++++------------ src/feature-qa.ts | 27 ------------- 3 files changed, 69 insertions(+), 53 deletions(-) delete mode 100644 feature-qa.html delete mode 100644 src/feature-qa.ts diff --git a/feature-qa.html b/feature-qa.html deleted file mode 100644 index 11cb091..0000000 --- a/feature-qa.html +++ /dev/null @@ -1 +0,0 @@ -Git features QA
diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index 59c03a7..d67b573 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -210,6 +210,10 @@ pub struct ReflogEntry { const SEQUENCE_EDITOR_PLAN_ENV: &str = "GITTY_SEQUENCE_EDITOR_PLAN"; const COMMIT_EDITOR_QUEUE_ENV: &str = "GITTY_COMMIT_EDITOR_QUEUE"; +const REBASE_TODO_FILE: &str = "gitty-interactive-rebase-todo"; +const REWORD_QUEUE_FILE: &str = "gitty-interactive-rebase-messages"; +const SEQUENCE_HELPER_STEM: &str = ".gitty-sequence-editor"; +const COMMIT_HELPER_STEM: &str = ".gitty-commit-editor"; pub fn run_sequence_editor_if_requested() -> Option> { let executable = env::current_exe().ok()?; @@ -1632,33 +1636,24 @@ pub async fn start_interactive_rebase( let todo = build_rebase_todo(&available, &plan)?; let reword_queue = build_reword_queue(&available, &plan)?; let git_dir = git_dir_for_repo(&repo)?; - let todo_path = git_dir.join("gitty-interactive-rebase-todo"); - let reword_queue_path = git_dir.join("gitty-interactive-rebase-messages"); + cleanup_interactive_rebase_helpers(&repo); + let todo_path = git_dir.join(REBASE_TODO_FILE); + let reword_queue_path = git_dir.join(REWORD_QUEUE_FILE); fs::write(&todo_path, todo) .map_err(|err| format!("Could not prepare interactive rebase plan: {err}"))?; fs::write(&reword_queue_path, reword_queue) .map_err(|err| format!("Could not prepare reword messages: {err}"))?; - let sequence_helper_name = if cfg!(windows) { - format!(".gitty-sequence-editor-{}.exe", std::process::id()) - } else { - format!(".gitty-sequence-editor-{}", std::process::id()) - }; - let commit_helper_name = if cfg!(windows) { - format!(".gitty-commit-editor-{}.exe", std::process::id()) - } else { - format!(".gitty-commit-editor-{}", std::process::id()) - }; - let sequence_helper_path = repo.join(&sequence_helper_name); - let commit_helper_path = repo.join(&commit_helper_name); + let sequence_helper_path = repo.join(sequence_helper_name()); + let commit_helper_path = repo.join(commit_helper_name()); let current_exe = env::current_exe() .map_err(|err| format!("Could not locate the Gitty executable: {err}"))?; fs::copy(¤t_exe, &sequence_helper_path) .and_then(|_| fs::copy(¤t_exe, &commit_helper_path)) .map_err(|err| format!("Could not prepare interactive rebase helpers: {err}"))?; - let sequence_editor_command = format!("./{sequence_helper_name}"); - let commit_editor_command = format!("./{commit_helper_name}"); + let sequence_editor_command = format!("./{}", sequence_helper_name()); + let commit_editor_command = format!("./{}", commit_helper_name()); let output = git_command() .arg("-C") .arg(&repo) @@ -1670,11 +1665,14 @@ pub async fn start_interactive_rebase( .output() .map_err(|err| format!("Could not start Git. Is Git installed? {err}")); + let result = rebase_status_or_error(&repo, output?, "Interactive rebase failed", true); let _ = fs::remove_file(&todo_path); - let _ = fs::remove_file(&reword_queue_path); let _ = fs::remove_file(&sequence_helper_path); - let _ = fs::remove_file(&commit_helper_path); - rebase_status_or_error(&repo, output?, "Interactive rebase failed", true) + if !matches!(&result, Ok(status) if status.rebase_in_progress) { + let _ = fs::remove_file(&reword_queue_path); + let _ = fs::remove_file(&commit_helper_path); + } + result }) .await .map_err(|err| format!("Could not run interactive rebase: {err}"))? @@ -1687,15 +1685,27 @@ pub fn rebase_continue(path: String) -> Result { return Err("No rebase is currently in progress.".to_string()); } - let output = git_command() - .arg("-C") - .arg(&repo) - .args(["rebase", "--continue"]) - .env("GIT_EDITOR", "true") + let git_dir = git_dir_for_repo(&repo)?; + let queue_path = git_dir.join(REWORD_QUEUE_FILE); + let helper_path = repo.join(commit_helper_name()); + let mut command = git_command(); + command.arg("-C").arg(&repo).args(["rebase", "--continue"]); + if queue_path.exists() && helper_path.exists() { + command + .env("GIT_EDITOR", format!("./{}", commit_helper_name())) + .env(COMMIT_EDITOR_QUEUE_ENV, &queue_path); + } else { + command.env("GIT_EDITOR", "true"); + } + let output = command .output() .map_err(|err| format!("Could not start Git. Is Git installed? {err}"))?; - rebase_status_or_error(&repo, output, "Rebase continue failed", false) + let result = rebase_status_or_error(&repo, output, "Rebase continue failed", false); + if !matches!(&result, Ok(status) if status.rebase_in_progress) { + cleanup_interactive_rebase_helpers(&repo); + } + result } #[tauri::command] @@ -1706,6 +1716,7 @@ pub fn rebase_abort(path: String) -> Result { } run_git(&repo, ["rebase", "--abort"])?; + cleanup_interactive_rebase_helpers(&repo); status_for_repo(&repo) } @@ -1911,6 +1922,38 @@ fn git_dir_for_repo(repo: &Path) -> Result { } } +fn sequence_helper_name() -> &'static str { + if cfg!(windows) { + ".gitty-sequence-editor.exe" + } else { + SEQUENCE_HELPER_STEM + } +} + +fn commit_helper_name() -> &'static str { + if cfg!(windows) { + ".gitty-commit-editor.exe" + } else { + COMMIT_HELPER_STEM + } +} + +fn cleanup_interactive_rebase_helpers(repo: &Path) { + if let Ok(git_dir) = git_dir_for_repo(repo) { + let _ = fs::remove_file(git_dir.join(REBASE_TODO_FILE)); + let _ = fs::remove_file(git_dir.join(REWORD_QUEUE_FILE)); + } + let _ = fs::remove_file(repo.join(sequence_helper_name())); + let _ = fs::remove_file(repo.join(commit_helper_name())); +} + +fn is_interactive_rebase_helper_path(path: &str) -> bool { + matches!( + path.replace('\\', "/").rsplit('/').next(), + Some(name) if name == sequence_helper_name() || name == commit_helper_name() + ) +} + fn parse_reflog(output: &[u8]) -> Result, String> { let mut entries = Vec::new(); for raw in output.split(|byte| *byte == 0x1e) { @@ -2946,6 +2989,7 @@ fn status_for_repo(repo: &Path) -> Result { )?; let (branch, mut files) = parse_status_output(&output)?; detect_worktree_renames(repo, &mut files); + files.retain(|file| !is_interactive_rebase_helper_path(&file.path)); Ok(GitStatus { repo_path: repo.to_string_lossy().to_string(), diff --git a/src/feature-qa.ts b/src/feature-qa.ts deleted file mode 100644 index 37701a2..0000000 --- a/src/feature-qa.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { mount } from "svelte"; -import "./app.css"; -import InteractiveRebaseDialog from "./lib/components/InteractiveRebaseDialog.svelte"; -import ReflogDialog from "./lib/components/ReflogDialog.svelte"; - -const target = document.getElementById("qa")!; -const branches = [ - { name: "features/rewrite-history", current: true, remote: false }, - { name: "main", current: false, remote: false }, - { name: "origin/main", current: false, remote: true }, -]; -const commits = [ - { hash: "1111111111111111111111111111111111111111", short_hash: "1111111", summary: "Add reflog backend", author_name: "Ada", date: "2026-07-10T09:10:00+02:00" }, - { hash: "2222222222222222222222222222222222222222", short_hash: "2222222", summary: "Build interactive rebase dialog", author_name: "Linus", date: "2026-07-10T10:20:00+02:00" }, - { hash: "3333333333333333333333333333333333333333", short_hash: "3333333", summary: "Polish recovery workflow", author_name: "Grace", date: "2026-07-10T11:30:00+02:00" }, -]; -const entries = [ - { hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", short_hash: "aaaaaaa", selector: "HEAD@{0}", action: "commit: Add recovery workflow", author_name: "Ada", date: "2026-07-10T12:00:00+02:00" }, - { hash: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", short_hash: "bbbbbbb", selector: "HEAD@{1}", action: "rebase (finish): returning to refs/heads/feature", author_name: "Ada", date: "2026-07-10T11:00:00+02:00" }, - { hash: "cccccccccccccccccccccccccccccccccccccccc", short_hash: "ccccccc", selector: "HEAD@{2}", action: "checkout: moving from main to feature", author_name: "Ada", date: "2026-07-10T10:00:00+02:00" }, -]; - -if (location.hash === "#reflog") { - mount(ReflogDialog, { target, props: { entries, currentHash: entries[0].hash, isLoading: false, isBusy: false, operation: "", error: "", onPreview: () => {}, onRestore: (_entry, branch) => { document.title = `Recovered ${branch}`; }, onClose: () => {} } }); -} else { - mount(InteractiveRebaseDialog, { target, props: { branches, currentBranch: branches[0].name, base: "main", commits, isLoading: false, isBusy: false, operation: "", error: "", onBaseChange: () => {}, onStart: (plan) => { document.title = `Rebase ${plan.length} commits`; }, onClose: () => {} } }); -} From c747e02f298e73b5a56357fb78562b6fc2dbaed5 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Fri, 10 Jul 2026 22:56:48 +0200 Subject: [PATCH 6/8] feat(git): improve interactive rebase base handling and reflog display The git module now correctly handles scenarios where the selected base branch is diverged from HEAD. This removes unnecessary ancestor checks during rebase planning, improving overall robustness of the feature. Additionally, the ReflogDialog component was updated to reliably retrieve the current HEAD hash for accurate display in the UI. - Removed strict ancestor checking when starting interactive rebase operations. - Updated App.svelte to accurately find and use the current HEAD entry from reflog data. --- src-tauri/src/git.rs | 47 ++++++++++++++++++++++---------------------- src/App.svelte | 2 +- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index d67b573..4179308 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -1625,11 +1625,6 @@ pub async fn start_interactive_rebase( } let base_hash = verify_commit(&repo, &base)?; - ensure_ancestor( - &repo, - &base_hash, - "The selected base must be an ancestor of HEAD.", - )?; let available = interactive_rebase_commits_for_repo(&repo, &base_hash)?; validate_rebase_plan(&available, &plan)?; @@ -1765,11 +1760,6 @@ fn interactive_rebase_commits_for_repo( base: &str, ) -> Result, String> { let base_hash = verify_commit(repo, base)?; - ensure_ancestor( - repo, - &base_hash, - "The selected base must be an ancestor of HEAD.", - )?; let range = format!("{base_hash}..HEAD"); let merges = run_git(repo, ["rev-list", "--merges", range.as_str()])?; @@ -1809,20 +1799,6 @@ fn interactive_rebase_commits_for_repo( Ok(commits) } -fn ensure_ancestor(repo: &Path, commit: &str, message: &str) -> Result<(), String> { - let output = git_command() - .arg("-C") - .arg(repo) - .args(["merge-base", "--is-ancestor", commit, "HEAD"]) - .output() - .map_err(|err| format!("Could not start Git. Is Git installed? {err}"))?; - if output.status.success() { - Ok(()) - } else { - Err(message.to_string()) - } -} - fn validate_rebase_plan(commits: &[RebaseCommit], plan: &[RebasePlanItem]) -> Result<(), String> { if commits.is_empty() { return Err("There are no commits to rebase onto the selected base.".to_string()); @@ -5126,6 +5102,29 @@ mod tests { ); } + #[test] + fn interactive_rebase_accepts_a_diverged_base_branch() { + let repo = init_temp_repo("interactive_rebase_diverged"); + commit_initial_file(&repo.path); + let main_branch = git_output_test(&repo.path, ["branch", "--show-current"]); + run_git_test(&repo.path, ["checkout", "-q", "-b", "feature"]); + fs::write(repo.path.join("feature.txt"), "feature\n") + .expect("feature file should be written"); + run_git_test(&repo.path, ["add", "feature.txt"]); + run_git_test(&repo.path, ["commit", "-q", "-m", "feature commit"]); + run_git_test(&repo.path, ["checkout", "-q", main_branch.as_str()]); + fs::write(repo.path.join("main.txt"), "main\n").expect("main file should be written"); + run_git_test(&repo.path, ["add", "main.txt"]); + run_git_test(&repo.path, ["commit", "-q", "-m", "main advanced"]); + run_git_test(&repo.path, ["checkout", "-q", "feature"]); + + let commits = interactive_rebase_commits_for_repo(&repo.path, &main_branch) + .expect("diverged base should be accepted"); + + assert_eq!(commits.len(), 1); + assert_eq!(commits[0].summary, "feature commit"); + } + #[test] fn reflog_restore_creates_a_recovery_branch_without_resetting_existing_branch() { let repo = init_temp_repo("reflog_restore"); diff --git a/src/App.svelte b/src/App.svelte index bf6d18a..abac9f0 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -4221,7 +4221,7 @@ {#if reflogOpen} entry.selector === "HEAD@{0}")?.hash ?? ""} isLoading={reflogLoading} {isBusy} {operation} From b181b384e7fe094219570ec5f6d133144325e5a1 Mon Sep 17 00:00:00 2001 From: Christoph Date: Fri, 10 Jul 2026 23:09:04 +0200 Subject: [PATCH 7/8] Update version to 2026.7.19 --- package-lock.json | 4 ++-- package.json | 2 +- src-tauri/tauri.conf.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64406e9..cf32cc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gitty", - "version": "0.200.10", + "version": "2026.7.19", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gitty", - "version": "0.200.10", + "version": "2026.7.19", "dependencies": { "@lucide/svelte": "^1.21.0", "@tailwindcss/vite": "^4.3.1", diff --git a/package.json b/package.json index 7c40ac3..76e1a92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitty", - "version": "0.200.10", + "version": "2026.7.19", "private": true, "type": "module", "scripts": { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 810dd2c..418098d 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Gitty", - "version": "0.200.10", + "version": "2026.7.19", "identifier": "com.gitty", "build": { "beforeDevCommand": "npm run dev", From 6729d61dca6e08bc84483e28e0669c21c0120f2c Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Sat, 11 Jul 2026 15:03:01 +0200 Subject: [PATCH 8/8] feat(language): implement internationalization and help overlay This update introduces comprehensive language support (English/German) across the application, enabling localization for UI elements and settings dialogs. It also adds a dedicated Help Overlay component with detailed guides on using Gitty's core Git features. - Adds language selection to App Settings - Implements German translations in key areas - Introduces global help documentation accessible via Ctrl+/ --- src/App.svelte | 58 +- src/app.css | 4 + src/lib/TitleBar.svelte | 20 +- src/lib/components/AppSettingsDialog.svelte | 61 +- src/lib/components/HelpOverlay.svelte | 798 ++++++++++++++++++++ src/lib/types.ts | 1 + 6 files changed, 917 insertions(+), 25 deletions(-) create mode 100644 src/lib/components/HelpOverlay.svelte diff --git a/src/App.svelte b/src/App.svelte index a29bd62..a68be1b 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -21,6 +21,7 @@ import ExplorerPanel from "./lib/components/ExplorerPanel.svelte"; import FileHistoryPanel from "./lib/components/FileHistoryPanel.svelte"; import GlobalSearchDialog from "./lib/components/GlobalSearchDialog.svelte"; + import HelpOverlay from "./lib/components/HelpOverlay.svelte"; import HistoryPanel from "./lib/components/HistoryPanel.svelte"; import InteractiveRebaseDialog from "./lib/components/InteractiveRebaseDialog.svelte"; import LinePatchDialog from "./lib/components/LinePatchDialog.svelte"; @@ -104,6 +105,7 @@ import type { AiSettings, + AppLanguage, AppTheme, AnalyticsSettings, CommitAiPhase, @@ -176,6 +178,7 @@ const AI_SETTINGS_KEY = "gitlite.aiSettings.v1"; const ANALYTICS_SETTINGS_KEY = "gitlite.analyticsSettings.v1"; const APP_THEME_KEY = "gitlite.theme.v1"; + const APP_LANGUAGE_KEY = "gitlite.language.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"; @@ -248,9 +251,11 @@ let aiSettings: AiSettings = defaultAiSettings(); let aiSettingsOpen = false; let appSettingsOpen = false; + let helpOpen = false; let analyticsNoticeOpen = false; let analyticsSettings: AnalyticsSettings = defaultAnalyticsSettings(); let appTheme: AppTheme = loadThemePreference(); + let appLanguage: AppLanguage = loadLanguagePreference(); let localModelOptions: LocalModelOption[] = []; let errorMessage = ""; let operation = ""; @@ -412,6 +417,7 @@ $: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed; $: applyThemePreference(appTheme); + $: applyLanguagePreference(appLanguage); // ── Lifecycle ────────────────────────────────────────────────────────────── @@ -672,7 +678,7 @@ } async function autoRefreshTick() { - if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || interactiveRebaseOpen || reflogOpen || newBranchCommit || globalSearchOpen) return; + if (!autoRefreshEnabled || activeView !== "repository" || !activeRepoPath || isBusy || autoRefreshInFlight || resolveDialogOpen || compareDialogOpen || compareSelectOpen || interactiveRebaseOpen || reflogOpen || newBranchCommit || globalSearchOpen || helpOpen) return; const path = activeRepoPath; autoRefreshInFlight = true; try { @@ -813,6 +819,29 @@ } } + function loadLanguagePreference(): AppLanguage { + try { + const stored = localStorage.getItem(APP_LANGUAGE_KEY); + if (stored === "en" || stored === "de") return stored; + } catch { + // Local storage is optional; English is the first-start default. + } + return "en"; + } + + function persistLanguagePreference(next: AppLanguage) { + try { + localStorage.setItem(APP_LANGUAGE_KEY, next); + } catch { + // Ignore storage quota/private-mode errors. + } + } + + function applyLanguagePreference(next: AppLanguage) { + document.documentElement.lang = next; + document.documentElement.dataset.language = next; + } + function applyThemePreference(next: AppTheme) { const prefersLight = themeMediaQuery?.matches ?? window.matchMedia("(prefers-color-scheme: light)").matches; const resolved = next === "system" @@ -828,13 +857,15 @@ if (appTheme === "system") applyThemePreference(appTheme); } - function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme) { + function saveAppSettings(next: AnalyticsSettings, nextTheme: AppTheme, nextLanguage: AppLanguage) { analyticsSettings = next; appTheme = nextTheme; + appLanguage = nextLanguage; persistAnalyticsSettings(next); persistThemePreference(nextTheme); + persistLanguagePreference(nextLanguage); appSettingsOpen = false; - if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme }); + if (next.enabled) trackEvent("settings_saved", { analytics_enabled: 1, theme: nextTheme, language: nextLanguage }); } function updateCommitMessage(message: string) { @@ -3259,6 +3290,11 @@ trackEvent("global_search_opened"); } + function openHelp() { + helpOpen = true; + trackEvent("help_opened"); + } + async function compareSelectedCommits() { if (!canCompare) return; await runOperation("Comparing commits", async () => { @@ -3449,6 +3485,15 @@ // ── Event handlers ───────────────────────────────────────────────────────── function handleWindowKeydown(event: KeyboardEvent) { + if ((event.ctrlKey || event.metaKey) && event.key === "/") { + event.preventDefault(); + openHelp(); + return; + } + if (event.key === "Escape" && helpOpen) { + helpOpen = false; + return; + } if (event.key === "Escape" && repoTabContextMenu) closeRepoTabContextMenu(); else if (event.key === "Escape" && pendingDiscard && !isBusy) closeDiscardConfirm(); else if (event.key === "Escape" && compareDialogOpen) closeCompareDialog(); @@ -3499,6 +3544,8 @@ onOpenInExplorer={openActiveRepoInExplorer} onToggleAutoRefresh={toggleAutoRefresh} onOpenSettings={() => { appSettingsOpen = true; }} + onOpenHelp={openHelp} + language={appLanguage} />
@@ -4113,11 +4160,16 @@ { appSettingsOpen = false; }} /> {/if} +{#if helpOpen} + { helpOpen = false; }} /> +{/if} + {#if linePatchOpen && linePatchFile} void = () => {}; export let onOpenInExplorer: () => void = () => {}; export let onToggleAutoRefresh: () => void = () => {}; + export let onOpenHelp: () => void = () => {}; export let onOpenSettings: () => void = () => {}; + export let language: "en" | "de" = "en"; let win: ReturnType | null = null; let isMaximized = false; @@ -238,14 +240,24 @@ Auto + +
diff --git a/src/lib/components/AppSettingsDialog.svelte b/src/lib/components/AppSettingsDialog.svelte index 345d936..d3053d0 100644 --- a/src/lib/components/AppSettingsDialog.svelte +++ b/src/lib/components/AppSettingsDialog.svelte @@ -1,22 +1,26 @@