Compare commits

...
12 Commits
Author SHA1 Message Date
Christoph ff00925b13 chore(build): automate PKGBUILD version updates
This update enhances the build process by ensuring that the package definition file (PKGBUILD) is automatically updated with the current application version and release number. Changes are applied across both local build scripts and CI/CD workflows to maintain consistency. This prevents manual synchronization errors when building releases.

- Updates PKGBUILD version fields using package.json
- Ensures PKGBUILD is staged during final commit in CI/CD
- Improves robustness of the automated build pipeline
2026-07-11 23:27:59 +02:00
Christoph Brandau 982dbf136d feat(toolbar): overhaul repository action bar UI and status display
This refactors the entire appearance and structure of the repository toolbar, modernizing its layout and improving visual consistency across different states. The component now dynamically displays remote tracking information (ahead/behind) directly on Pull and Push buttons for immediate user feedback.

- Implemented a comprehensive CSS overhaul using grid and flexbox for better responsiveness.
- Added support for German localization within the toolbar actions.
- Enhanced visibility of sync status by displaying commit counts next to pull/push buttons.
2026-07-11 23:11:34 +02:00
Christoph Brandau 646dcc341e feat(help): Add comprehensive German documentation for Git concepts
The help overlay component has been significantly expanded with detailed sections covering advanced version control topics in German. These additions provide users with deep dives into core Git mechanics, complex workflows, and troubleshooting guides. This greatly enhances the user's ability to understand and utilize professional Git practices within the application.

- Detailed explanations of the Staging Area and Index concepts.
- Guides for advanced operations like interactive rebase and cherry-picking.
- Comprehensive sections on common errors (e.g., detached HEAD, non-fast-forward).
2026-07-11 22:52:31 +02:00
Christoph 5f3e55dcd7 chore: update package version and enhance build scripts
This commit updates the package version to 2026.7.19 and modifies
the build scripts to include additional commands for better
development workflow. The main window of the application is also set
to maximize upon launch.

- Updated package version in PKGBUILD
- Added new build commands in settings.local.json
- Maximized the main application window on startup
2026-07-11 20:02:23 +02:00
Christoph fdbff8175e style(ui): Move Buttons under the Repo selection 2026-07-11 18:04:49 +02:00
Christoph a6e7e991dd refactor(tauri): Improve builder initialization readability
The main application setup in src-tauri/src/main.rs has been refactored to enhance local variable scoping. By assigning the result of tauri::Builder::default() to a named variable, the code becomes clearer and more readable without changing runtime behavior.
2026-07-11 17:42:22 +02:00
Christoph 3491b8efb3 Merge remote-tracking branch 'origin/main' 2026-07-11 17:40:31 +02:00
Christoph 59a8f34e0e chore(config): update gitignore to exclude build artifacts
Updates the .gitignore file to ensure that generated package files and local build directories are ignored by Git. This prevents unnecessary binary data from being tracked in the repository history, keeping the project clean.

- Ignore pkg directory
- Ensure exclusion of tarball packages
2026-07-11 17:35:48 +02:00
Christoph 286b106baa feat(build): add PKGBUILD for package distribution
This introduces the necessary PKGBUILD file to define how Gitty should be built and packaged for system installation. It sets up the build process using npm and Tauri CLI, ensuring all dependencies are met before compiling the application binary. The script also handles installing required assets like icons, desktop entries, and documentation into the package structure.

- Defines build steps using tauri/npm
- Installs binaries, icons, and desktop entry files
- Sets up standard package metadata (pkgname, depends)
2026-07-11 17:34:48 +02:00
Christoph 670e7e24fe for linux build 2026-07-11 17:31:16 +02:00
Christoph Brandau 6729d61dca 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+/
2026-07-11 15:03:01 +02:00
Christoph b181b384e7 Update version to 2026.7.19 2026-07-10 23:09:04 +02:00
17 changed files with 2229 additions and 240 deletions
+13 -1
View File
@@ -104,7 +104,19 @@
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.5/src/**)",
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-runtime-wry-2.11.4/src/**)",
"Read(//home/christoph/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tauri-2.11.5/src/window/**)",
"WebSearch"
"WebSearch",
"Bash(node -p \"require\\('./package.json'\\).version\")",
"Bash(bash -n PKGBUILD)",
"Bash(makepkg --printsrcinfo)",
"Bash(npx tauri *)",
"Bash(echo \"---exit code:$?---\")",
"Bash(git -C /mnt/data/Development/GitLite status --short)",
"Bash(makepkg -f --noconfirm)",
"Bash(tar -tf gitty-2026.7.19-1-x86_64.pkg.tar.zst)",
"Bash(cp /mnt/data/Development/GitLite/PKGBUILD .)",
"Bash(sed -i 's/^pkgrel=1/pkgrel=3/' PKGBUILD)",
"Bash(node -e \"const fs=require\\('fs'\\); const version='2026.8.1'; const pkgbuild=fs.readFileSync\\('PKGBUILD','utf8'\\).replace\\(/^pkgver=.*\\\\$/m, 'pkgver='+version\\).replace\\(/^pkgrel=.*\\\\$/m, 'pkgrel=1'\\); fs.writeFileSync\\('PKGBUILD', pkgbuild\\);\")",
"Bash(rm PKGBUILD)"
]
}
}
+5 -1
View File
@@ -87,6 +87,10 @@ jobs:
run: |
node -e "const fs=require('fs'); const path='src-tauri/tauri.conf.json'; const config=JSON.parse(fs.readFileSync(path,'utf8')); config.version=process.env.RELEASE_VERSION; fs.writeFileSync(path, JSON.stringify(config,null,2)+'\n');"
- name: Update PKGBUILD Version
run: |
node -e "const fs=require('fs'); const version=require('./package.json').version; const pkgbuild=fs.readFileSync('PKGBUILD','utf8').replace(/^pkgver=.*$/m, 'pkgver='+version).replace(/^pkgrel=.*$/m, 'pkgrel=1'); fs.writeFileSync('PKGBUILD', pkgbuild);"
- name: Commit and Push Changes
if: matrix.platform == 'windows-latest'
shell: powershell
@@ -95,7 +99,7 @@ jobs:
git config user.email '${{ vars.EMAIL_GIT }}'
# npm version also bumps the version inside package-lock.json, so stage it
# too -- otherwise it stays as an unstaged change and blocks the rebase.
git add package.json package-lock.json src-tauri/tauri.conf.json
git add package.json package-lock.json src-tauri/tauri.conf.json PKGBUILD
git commit -m 'Update version to ${{ github.ref_name }}'
if ($LASTEXITCODE -ne 0) {
+3
View File
@@ -7,3 +7,6 @@
~
.codex*
target
*.pkg.tar.zst
pkg
+66
View File
@@ -0,0 +1,66 @@
# Maintainer: Christoph Brandau <c.brandau91@googlemail.com>
#
# Local/in-tree PKGBUILD: builds straight from this working directory (no
# source download, no tauri-bundler/AppImage step) and installs the raw
# binary + desktop entry. Run `makepkg -si` from the repo root.
pkgname=gitty
pkgver=2026.7.19
pkgrel=1
pkgdesc="A lightweight, modern Git client built with Tauri"
arch=('x86_64')
url="https://git.cbsk-tech.de/Christoph/GitLite"
license=('MIT')
depends=('webkit2gtk-4.1' 'gtk3' 'git' 'hicolor-icon-theme')
makedepends=('rust' 'nodejs' 'npm')
options=('!lto')
source=()
sha256sums=()
# Keeps pkgver in sync with package.json (the release CI bumps that file).
pkgver() {
cd "$startdir"
node -p "require('./package.json').version"
}
build() {
cd "$startdir"
npm ci
# Build through the Tauri CLI (not a raw `cargo build --release`): it enables
# the `custom-protocol` cargo feature that makes the binary load the embedded
# `dist/` assets instead of the Vite dev server URL (127.0.0.1:1420), and runs
# `beforeBuildCommand` (`npm run build`) for us. `--no-bundle` skips deb/rpm/
# appimage packaging (and with it linuxdeploy/FUSE) since pacman owns that here.
npm run tauri -- build --no-bundle
}
package() {
cd "$startdir"
install -Dm755 "src-tauri/target/release/gitty" "$pkgdir/usr/bin/gitty"
install -Dm644 "src-tauri/icons/32x32.png" \
"$pkgdir/usr/share/icons/hicolor/32x32/apps/gitty.png"
install -Dm644 "src-tauri/icons/128x128.png" \
"$pkgdir/usr/share/icons/hicolor/128x128/apps/gitty.png"
install -Dm644 "src-tauri/icons/128x128@2x.png" \
"$pkgdir/usr/share/icons/hicolor/256x256@2/apps/gitty.png"
install -Dm644 "src-tauri/icons/icon.png" \
"$pkgdir/usr/share/icons/hicolor/512x512/apps/gitty.png"
install -d "$pkgdir/usr/share/applications"
cat > "$pkgdir/usr/share/applications/gitty.desktop" <<-EOF
[Desktop Entry]
Type=Application
Name=Gitty
Comment=$pkgdesc
Exec=gitty
Icon=gitty
Terminal=false
Categories=Development;RevisionControl;
StartupWMClass=gitty
EOF
install -Dm644 "README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
}
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "gitty",
"version": "0.200.10",
"version": "2026.7.19",
"private": true,
"type": "module",
"scripts": {
+6
View File
@@ -27,6 +27,12 @@ tauri-build = { version = "2", features = [] }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-single-instance = "2"
# Self-updating only makes sense for install methods this app ships itself
# (NSIS on Windows, the .app bundle on macOS). On Linux the app is meant to be
# installed via the system package manager (e.g. the PKGBUILD), which owns
# updates instead — so the updater plugin isn't even compiled in there.
[target.'cfg(not(any(target_os = "android", target_os = "ios", target_os = "linux")))'.dependencies]
tauri-plugin-updater = "2"
# ── Build profiles ───────────────────────────────────────────────────────────
+1 -2
View File
@@ -2,8 +2,7 @@
"identifier": "desktop-capability",
"platforms": [
"macOS",
"windows",
"linux"
"windows"
],
"windows": [
"main"
+11 -3
View File
@@ -33,6 +33,7 @@ fn close_splashscreen(app: tauri::AppHandle) -> Result<(), String> {
}
if let Some(window) = app.get_webview_window("main") {
let _ = window.maximize();
window
.show()
.map_err(|error| format!("failed to show main window: {error}"))?;
@@ -52,7 +53,7 @@ async fn main() {
return;
}
tauri::Builder::default()
let builder =tauri::Builder::default()
.plugin(tauri_plugin_single_instance::init(|app, _, _| {
#[cfg(desktop)]
let _ = app
@@ -60,7 +61,6 @@ async fn main() {
.expect("no main window")
.set_focus();
}))
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(
tauri_plugin_aptabase::Builder::new("A-SH-1344793789")
.with_options(tauri_plugin_aptabase::InitOptions {
@@ -71,7 +71,15 @@ async fn main() {
)
.manage(SearchCancellationState::default())
.manage(commit_ai::CommitAiEngine::new())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_dialog::init());
// Linux installs are expected to come from the system package manager (see the
// PKGBUILD), which owns updates itself — the self-updater is only wired up for
// the platforms whose install method this app ships (NSIS/Windows, .app/macOS).
#[cfg(not(target_os = "linux"))]
let builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
builder
.invoke_handler(tauri::generate_handler![
open_repository,
clone_repository,
+1 -1
View File
@@ -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",
+81 -17
View File
@@ -6,6 +6,7 @@
import { AlertCircle, BookOpen, Cherry, Download, FolderOpen, GitBranch, GitMerge, LoaderCircle, Plus, Search, Star, X } from "@lucide/svelte";
import TitleBar from "./lib/TitleBar.svelte";
import RepoToolbar from "./lib/RepoToolbar.svelte";
import AiSettingsDialog from "./lib/components/AiSettingsDialog.svelte";
import AnalyticsNoticeDialog from "./lib/components/AnalyticsNoticeDialog.svelte";
import AppSettingsDialog from "./lib/components/AppSettingsDialog.svelte";
@@ -21,6 +22,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 +106,7 @@
import type {
AiSettings,
AppLanguage,
AppTheme,
AnalyticsSettings,
CommitAiPhase,
@@ -176,6 +179,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 +252,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 +418,7 @@
$: allLeftPanelsCollapsed = branchPanelCollapsed && stashPanelCollapsed && explorerPanelCollapsed;
$: applyThemePreference(appTheme);
$: applyLanguagePreference(appLanguage);
// ── Lifecycle ──────────────────────────────────────────────────────────────
@@ -672,7 +679,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 +820,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 +858,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 +3291,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 +3486,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();
@@ -3484,21 +3530,9 @@
behind={status?.behind ?? 0}
repoName={activeRepoPath ? (activeRepoPath.split(/[\\/]/).filter(Boolean).slice(-1)[0] ?? "") : ""}
hasRepository={workspaceActive}
{isBusy}
{operation}
{autoRefreshEnabled}
{autoRefreshInFlight}
onFetch={fetchRepo}
onPull={pullRepo}
onPush={pushRepo}
onRefresh={refreshRepo}
onSearch={openGlobalSearchDialog}
onCompare={openCompareSelect}
onInteractiveRebase={openInteractiveRebase}
onReflog={openReflog}
onOpenInExplorer={openActiveRepoInExplorer}
onToggleAutoRefresh={toggleAutoRefresh}
onOpenSettings={() => { appSettingsOpen = true; }}
onOpenHelp={openHelp}
language={appLanguage}
/>
<div class="shell-body">
@@ -3563,6 +3597,31 @@
</button>
</header>
<!-- Repo actions live under the tab bar and disappear in Repository
Management, where none of them are applicable. -->
{#if workspaceActive}
<RepoToolbar
hasRepository={workspaceActive}
{isBusy}
{operation}
ahead={status?.ahead ?? 0}
behind={status?.behind ?? 0}
{autoRefreshEnabled}
{autoRefreshInFlight}
language={appLanguage}
onFetch={fetchRepo}
onPull={pullRepo}
onPush={pushRepo}
onRefresh={refreshRepo}
onSearch={openGlobalSearchDialog}
onCompare={openCompareSelect}
onInteractiveRebase={openInteractiveRebase}
onReflog={openReflog}
onOpenInExplorer={openActiveRepoInExplorer}
onToggleAutoRefresh={toggleAutoRefresh}
/>
{/if}
{#if repoTabContextMenu}
{@const menu = repoTabContextMenu}
{@const contextTab = repoTabs.find((tab) => sameRepoPath(tab.path, menu.path))}
@@ -4113,11 +4172,16 @@
<AppSettingsDialog
analytics={analyticsSettings}
theme={appTheme}
language={appLanguage}
onSave={saveAppSettings}
onClose={() => { appSettingsOpen = false; }}
/>
{/if}
{#if helpOpen}
<HelpOverlay language={appLanguage} onClose={() => { helpOpen = false; }} />
{/if}
{#if linePatchOpen && linePatchFile}
<LinePatchDialog
file={linePatchFile}
+203 -8
View File
@@ -541,6 +541,7 @@
.shell {
--app-titlebar-height: 42px;
--app-toolbar-height: 58px;
display: flex;
flex-direction: column;
height: 100%;
@@ -562,7 +563,7 @@
position: relative;
z-index: 500;
display: grid;
grid-template-columns: auto 1fr auto;
grid-template-columns: auto 1fr auto auto;
align-items: center;
height: var(--app-titlebar-height);
background:
@@ -639,8 +640,187 @@
.tb-no-repo { color: var(--color-bar-muted); font-size: 12px; font-style: italic; }
.titlebar-right { display: flex; align-items: stretch; height: 100%; border-left: 1px solid rgba(255,255,255,0.06); }
.titlebar-actions { display: flex; align-items: stretch; }
/* App-global actions (help, settings) that must stay reachable in every view,
including Repository Management where the repo toolbar is hidden. */
.titlebar-globals {
display: flex;
align-items: stretch;
height: 100%;
}
/* Repository actions are ordered by workflow: sync, inspect/history, utilities. */
.repo-toolbar {
display: flex;
align-items: flex-end;
gap: 8px;
height: var(--app-toolbar-height);
padding: 5px 8px;
border: 1px solid var(--color-border-subtle);
border-radius: 10px;
background: rgba(16, 17, 29, 0.9);
flex-shrink: 0;
overflow: visible;
user-select: none;
}
.repo-toolbar-sync {
display: grid;
grid-template-rows: 11px 34px;
gap: 2px;
align-self: stretch;
min-width: 0;
}
.repo-toolbar-group-label {
padding-left: 6px;
color: var(--color-ink-faint);
font-size: 8px;
font-weight: 850;
line-height: 11px;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.repo-action-group {
display: flex;
align-items: stretch;
height: 34px;
min-width: 0;
border: 1px solid var(--color-border-subtle);
border-radius: 7px;
background: rgba(255,255,255,0.018);
}
.repo-action-group > .repo-action,
.repo-action-group > .repo-history-wrap > .repo-action {
border-right: 1px solid var(--color-border-subtle);
}
.repo-action-group > .repo-action:last-child,
.repo-action-group > .repo-history-wrap:last-child > .repo-action {
border-right: 0;
}
.repo-action {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-width: 38px;
height: 32px;
min-height: 32px;
padding: 0 12px;
border: 0;
border-radius: 0;
color: var(--color-ink-muted);
background: transparent;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
cursor: pointer;
transition: color 120ms ease, background 120ms ease;
-webkit-app-region: no-drag;
}
.repo-action:first-child { border-radius: 6px 0 0 6px; }
.repo-action:last-child { border-radius: 0 6px 6px 0; }
.repo-action:only-child { border-radius: 6px; }
.repo-action:hover:not(:disabled),
.repo-action.active {
color: var(--color-ink);
background: var(--color-surface-hover);
}
.repo-action:disabled { opacity: 0.34; cursor: not-allowed; }
.repo-action.sync-primary { color: var(--color-accent); }
.repo-action.sync-primary:hover:not(:disabled) { background: rgba(90,140,248,0.1); }
.repo-action-label { font-size: 12px; font-weight: 750; }
.repo-action-count {
color: currentColor;
font-family: var(--font-mono);
font-size: 9.5px;
font-weight: 850;
}
.repo-action-count.behind { color: #7aacff; }
.repo-action-count.ahead { color: #e0a040; }
.repo-toolbar-divider {
width: 1px;
height: 30px;
margin: 0 1px 2px;
background: var(--color-border-subtle);
flex: 0 0 auto;
}
.repo-toolbar-spacer { flex: 1 1 auto; min-width: 6px; }
.repo-inspect-actions,
.repo-utility-actions { margin-bottom: 1px; }
.repo-history-wrap { position: relative; height: 32px; }
.history-trigger { border-radius: 0 6px 6px 0; }
.history-trigger .history-chevron { transition: transform 120ms ease; }
.history-trigger.active .history-chevron { transform: rotate(180deg); }
.repo-history-menu {
position: absolute;
z-index: 90;
top: calc(100% + 7px);
right: -1px;
display: grid;
width: 240px;
padding: 5px;
border: 1px solid var(--color-border);
border-radius: 9px;
background: var(--color-surface-raised);
box-shadow: 0 16px 42px rgba(0,0,0,0.36);
}
.repo-history-menu > button {
display: grid;
grid-template-columns: 24px minmax(0, 1fr);
align-items: center;
gap: 8px;
min-height: 48px;
padding: 7px 8px;
border: 0;
border-radius: 6px;
color: var(--color-ink-muted);
background: transparent;
text-align: left;
}
.repo-history-menu > button:hover { color: var(--color-ink); background: var(--color-surface-hover); }
.repo-history-menu > button > svg { color: var(--color-accent); }
.repo-history-menu > button > span { display: grid; gap: 2px; min-width: 0; }
.repo-history-menu strong { color: inherit; font-size: 11px; }
.repo-history-menu small { overflow: hidden; color: var(--color-ink-faint); font-size: 9.5px; text-overflow: ellipsis; white-space: nowrap; }
.repo-utility-actions { flex: 0 0 auto; }
.repo-action.icon-action { width: 36px; padding: 0; }
.repo-auto-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
height: 32px;
min-height: 32px;
padding: 0 8px 0 10px;
border: 0;
border-radius: 0 6px 6px 0;
color: var(--color-ink-muted);
background: transparent;
font-size: 11.5px;
font-weight: 750;
}
.repo-auto-toggle:hover { color: var(--color-ink); background: var(--color-surface-hover); }
.repo-switch {
position: relative;
width: 27px;
height: 15px;
border-radius: 999px;
background: var(--color-border-input);
transition: background 140ms ease;
}
.repo-switch > span {
position: absolute;
top: 2px;
left: 2px;
width: 11px;
height: 11px;
border-radius: 50%;
background: #ffffff;
box-shadow: 0 1px 3px rgba(0,0,0,0.28);
transition: transform 140ms ease;
}
.repo-auto-toggle.active { color: #4eca76; }
.repo-auto-toggle.active .repo-switch { background: #26924f; }
.repo-auto-toggle.active .repo-switch > span { transform: translateX(12px); }
.repo-switch.busy { box-shadow: 0 0 0 3px rgba(78,202,118,0.12); }
.tb-action {
display: flex;
@@ -655,6 +835,7 @@
color: var(--color-bar-muted);
font-size: 12px;
cursor: pointer;
flex-shrink: 0;
transition: background 120ms ease, color 120ms ease;
-webkit-app-region: no-drag;
}
@@ -664,9 +845,9 @@
.tb-action.auto-toggle.active:hover:not(:disabled) { color: #6ee090; background: rgba(78,202,118,0.1); }
.tb-action-label { font-size: 12px; font-weight: 600; }
.titlebar-divider { width: 1px; margin: 8px 0; background: rgba(255,255,255,0.08); flex-shrink: 0; }
.titlebar-divider { width: 1px; margin: 8px 6px; background: rgba(255,255,255,0.08); flex-shrink: 0; }
.titlebar-controls { display: flex; align-items: stretch; height: 100%; }
.titlebar-controls { display: flex; align-items: stretch; height: 100%; border-left: 1px solid rgba(255,255,255,0.06); }
.tb-btn {
display: flex;
@@ -3083,6 +3264,10 @@
white-space: nowrap;
}
.settings-segmented.settings-language {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.settings-toggle-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
@@ -4716,10 +4901,15 @@
}
:root[data-theme="light"] .titlebar-brand,
:root[data-theme="light"] .titlebar-right {
:root[data-theme="light"] .titlebar-controls {
border-color: rgba(34,49,78,0.1);
}
:root[data-theme="light"] .repo-toolbar {
border-color: var(--color-border);
background: rgba(255,255,255,0.78);
}
:root[data-theme="light"] .titlebar-brand-icon img {
filter: drop-shadow(0 1px 2px rgba(28,44,74,0.18));
}
@@ -5187,6 +5377,7 @@
.branch-actions { flex-direction: column; align-items: flex-end; gap: 4px; }
.branch-actions button { width: 100%; }
.tb-action-label { display: none; }
.repo-action-label.utility-label { display: none; }
}
/* Narrow: single column */
@@ -5194,8 +5385,7 @@
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-action { padding: 0 9px; }
.tb-btn { width: 34px; }
.workspace { grid-template-columns: 1fr; gap: 6px; }
.left-sidebar-resize-handle { display: none; }
@@ -5261,4 +5451,9 @@
.dialog-files { border-right: none; border-bottom: 1px solid var(--color-border-subtle); }
.branch-actions { flex-direction: row; justify-content: flex-start; }
.tb-action-label { display: none; }
.repo-action-label { display: none; }
.repo-action { padding-inline: 8px; }
.repo-toolbar { gap: 5px; padding-inline: 6px; }
.repo-toolbar-divider { margin-inline: 0; }
.repo-auto-toggle { padding-left: 7px; gap: 6px; }
}
+218
View File
@@ -0,0 +1,218 @@
<script lang="ts">
import {
ChevronDown,
CloudDownload,
Download,
FolderOpen,
GitCompare,
History,
ListRestart,
LoaderCircle,
RefreshCw,
Search,
Upload,
} from "@lucide/svelte";
export let hasRepository: boolean = false;
export let isBusy: boolean = false;
export let operation: string = "";
export let ahead: number = 0;
export let behind: number = 0;
export let autoRefreshEnabled: boolean = true;
export let autoRefreshInFlight: boolean = false;
export let language: "en" | "de" = "en";
export let onFetch: () => void = () => {};
export let onPull: () => void = () => {};
export let onPush: () => void = () => {};
export let onRefresh: () => void = () => {};
export let onSearch: () => void = () => {};
export let onCompare: () => void = () => {};
export let onInteractiveRebase: () => void = () => {};
export let onReflog: () => void = () => {};
export let onOpenInExplorer: () => void = () => {};
export let onToggleAutoRefresh: () => void = () => {};
let historyOpen = false;
let toolbarElement: HTMLDivElement;
$: isGerman = language === "de";
function runHistoryAction(action: () => void) {
historyOpen = false;
action();
}
function handleWindowClick(event: MouseEvent) {
if (historyOpen && toolbarElement && !toolbarElement.contains(event.target as Node)) {
historyOpen = false;
}
}
function handleWindowKeydown(event: KeyboardEvent) {
if (event.key === "Escape" && historyOpen) {
event.stopPropagation();
historyOpen = false;
}
}
</script>
<svelte:window onclick={handleWindowClick} onkeydown={handleWindowKeydown} />
<div bind:this={toolbarElement} class="repo-toolbar" role="toolbar" aria-label={isGerman ? "Repository-Aktionen" : "Repository actions"}>
<div class="repo-toolbar-sync">
<span class="repo-toolbar-group-label">Sync</span>
<div class="repo-action-group repo-sync-actions">
<button
class="repo-action fetch"
onclick={onFetch}
disabled={!hasRepository || isBusy}
title="Fetch"
aria-label="Fetch"
>
{#if operation === "Fetching"}
<LoaderCircle class="spin" size={15} aria-hidden="true" />
{:else}
<CloudDownload size={15} aria-hidden="true" />
{/if}
<span class="repo-action-label">Fetch</span>
</button>
<button
class="repo-action sync-primary"
onclick={onPull}
disabled={!hasRepository || isBusy}
title="Pull"
aria-label={behind > 0
? `Pull, ${behind} ${isGerman ? (behind === 1 ? "Remote-Commit voraus" : "Remote-Commits voraus") : (behind === 1 ? "commit behind" : "commits behind")}`
: "Pull"}
>
{#if operation === "Pulling"}
<LoaderCircle class="spin" size={15} aria-hidden="true" />
{:else}
<Download size={15} aria-hidden="true" />
{/if}
<span class="repo-action-label">Pull</span>
{#if behind > 0}<span class="repo-action-count behind">{behind}</span>{/if}
</button>
<button
class="repo-action sync-primary"
onclick={onPush}
disabled={!hasRepository || isBusy}
title="Push"
aria-label={ahead > 0
? `Push, ${ahead} ${isGerman ? (ahead === 1 ? "lokaler Commit voraus" : "lokale Commits voraus") : (ahead === 1 ? "commit ahead" : "commits ahead")}`
: "Push"}
>
{#if operation === "Pushing"}
<LoaderCircle class="spin" size={15} aria-hidden="true" />
{:else}
<Upload size={15} aria-hidden="true" />
{/if}
<span class="repo-action-label">Push</span>
{#if ahead > 0}<span class="repo-action-count ahead">{ahead}</span>{/if}
</button>
</div>
</div>
<div class="repo-toolbar-divider" aria-hidden="true"></div>
<div class="repo-action-group repo-inspect-actions">
<button
class="repo-action"
onclick={onSearch}
disabled={!hasRepository || isBusy}
title={isGerman ? "Globale Suche" : "Global search"}
aria-label={isGerman ? "Globale Suche" : "Global search"}
>
<Search size={15} aria-hidden="true" />
<span class="repo-action-label">{isGerman ? "Suchen" : "Search"}</span>
</button>
<button
class="repo-action"
onclick={onCompare}
disabled={!hasRepository || isBusy}
title={isGerman ? "Commits vergleichen" : "Compare commits"}
aria-label={isGerman ? "Commits vergleichen" : "Compare commits"}
>
<GitCompare size={15} aria-hidden="true" />
<span class="repo-action-label">{isGerman ? "Vergleichen" : "Compare"}</span>
</button>
<div class="repo-history-wrap">
<button
class="repo-action history-trigger"
class:active={historyOpen}
type="button"
onclick={() => { historyOpen = !historyOpen; }}
disabled={!hasRepository || isBusy}
aria-haspopup="menu"
aria-expanded={historyOpen}
title={isGerman ? "Verlauf und Rebase" : "History and rebase"}
>
<History size={15} aria-hidden="true" />
<span class="repo-action-label">{isGerman ? "Verlauf" : "History"}</span>
<ChevronDown class="history-chevron" size={13} aria-hidden="true" />
</button>
{#if historyOpen}
<div class="repo-history-menu" role="menu">
<button type="button" role="menuitem" onclick={() => runHistoryAction(onReflog)}>
<History size={15} aria-hidden="true" />
<span>
<strong>Reflog</strong>
<small>{isGerman ? "Lokale Referenzbewegungen" : "Local reference movements"}</small>
</span>
</button>
<button type="button" role="menuitem" onclick={() => runHistoryAction(onInteractiveRebase)}>
<ListRestart size={15} aria-hidden="true" />
<span>
<strong>{isGerman ? "Interaktiver Rebase" : "Interactive rebase"}</strong>
<small>{isGerman ? "Commits ordnen und zusammenfassen" : "Reorder and combine commits"}</small>
</span>
</button>
</div>
{/if}
</div>
</div>
<div class="repo-toolbar-spacer"></div>
<div class="repo-toolbar-divider utility" aria-hidden="true"></div>
<div class="repo-action-group repo-utility-actions">
<button
class="repo-action"
onclick={onOpenInExplorer}
disabled={!hasRepository || isBusy}
title={isGerman ? "Repository im Explorer öffnen" : "Open repository in Explorer"}
aria-label={isGerman ? "Repository im Explorer öffnen" : "Open repository in Explorer"}
>
<FolderOpen size={15} aria-hidden="true" />
<span class="repo-action-label utility-label">Explorer</span>
</button>
<button
class="repo-action icon-action"
onclick={onRefresh}
disabled={isBusy || !hasRepository}
title={isGerman ? "Manuell aktualisieren" : "Refresh now"}
aria-label={isGerman ? "Manuell aktualisieren" : "Refresh now"}
>
<RefreshCw class={operation === "Refreshing" ? "spin" : ""} size={15} aria-hidden="true" />
</button>
<button
class="repo-auto-toggle"
class:active={autoRefreshEnabled}
onclick={onToggleAutoRefresh}
aria-pressed={autoRefreshEnabled}
title={autoRefreshEnabled
? (isGerman ? "Auto-Aktualisierung ausschalten" : "Turn auto-refresh off")
: (isGerman ? "Auto-Aktualisierung einschalten" : "Turn auto-refresh on")}
>
<span>{isGerman ? "Auto" : "Auto"}</span>
<span class="repo-switch" class:busy={autoRefreshInFlight} aria-hidden="true"><span></span></span>
</button>
</div>
</div>
+46 -186
View File
@@ -2,7 +2,7 @@
import { onDestroy, onMount } from "svelte";
import { getVersion } from "@tauri-apps/api/app";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { CloudDownload, Download, FolderOpen, GitBranch, GitCompare, History, ListRestart, LoaderCircle, Minus, RefreshCw, Search, Settings, Upload, X } from "@lucide/svelte";
import { CircleHelp, GitBranch, Minus, Settings, X } from "@lucide/svelte";
import iconUrl from "../../src-tauri/icons/icon.png";
export let branch: string = "";
@@ -10,21 +10,9 @@
export let behind: number = 0;
export let repoName: string = "";
export let hasRepository: boolean = false;
export let isBusy: boolean = false;
export let operation: string = "";
export let autoRefreshEnabled: boolean = true;
export let autoRefreshInFlight: boolean = false;
export let onFetch: () => void = () => {};
export let onPull: () => void = () => {};
export let onPush: () => void = () => {};
export let onRefresh: () => void = () => {};
export let onSearch: () => void = () => {};
export let onCompare: () => void = () => {};
export let onInteractiveRebase: () => void = () => {};
export let onReflog: () => 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<typeof getCurrentWindow> | null = null;
let isMaximized = false;
@@ -104,177 +92,49 @@
{/if}
</div>
<!-- Right: actions + window controls -->
<div class="titlebar-right">
<div class="titlebar-actions" role="toolbar" aria-label="Repository actions">
<button
class="tb-action"
onclick={onOpenInExplorer}
disabled={!hasRepository || isBusy}
title="Open repository in Explorer"
aria-label="Open repository in Explorer"
>
<FolderOpen size={14} aria-hidden="true" />
<span class="tb-action-label">Explorer</span>
</button>
<!-- Right: app-global actions + window controls -->
<div class="titlebar-globals">
<button
class="tb-action"
onclick={onOpenHelp}
title={language === "de" ? "Hilfe (Ctrl+/)" : "Help (Ctrl+/)"}
aria-label={language === "de" ? "Hilfe öffnen" : "Open help"}
>
<CircleHelp size={14} aria-hidden="true" />
</button>
<button
class="tb-action"
onclick={onOpenSettings}
title={language === "de" ? "Einstellungen" : "Settings"}
aria-label={language === "de" ? "Einstellungen" : "Settings"}
>
<Settings size={14} aria-hidden="true" />
</button>
</div>
<button
class="tb-action"
onclick={onSearch}
disabled={!hasRepository || isBusy}
title="Global search"
aria-label="Global search"
>
<Search size={14} aria-hidden="true" />
<span class="tb-action-label">Search</span>
</button>
<button
class="tb-action"
onclick={onCompare}
disabled={!hasRepository || isBusy}
title="Compare commits"
aria-label="Compare commits"
>
<GitCompare size={14} aria-hidden="true" />
<span class="tb-action-label">Compare</span>
</button>
<button
class="tb-action"
onclick={onInteractiveRebase}
disabled={!hasRepository || isBusy}
title="Interactive rebase"
aria-label="Interactive rebase"
>
<ListRestart size={14} aria-hidden="true" />
<span class="tb-action-label">Rebase</span>
</button>
<button
class="tb-action"
onclick={onReflog}
disabled={!hasRepository || isBusy}
title="Reflog"
aria-label="Reflog"
>
<History size={14} aria-hidden="true" />
<span class="tb-action-label">Reflog</span>
</button>
<button
class="tb-action"
onclick={onFetch}
disabled={!hasRepository || isBusy}
title="Fetch"
aria-label="Fetch"
>
{#if operation === "Fetching"}
<LoaderCircle class="spin" size={14} aria-hidden="true" />
{:else}
<CloudDownload size={14} aria-hidden="true" />
{/if}
<span class="tb-action-label">Fetch</span>
</button>
<button
class="tb-action"
onclick={onPull}
disabled={!hasRepository || isBusy}
title="Pull"
aria-label="Pull"
>
{#if operation === "Pulling"}
<LoaderCircle class="spin" size={14} aria-hidden="true" />
{:else}
<Download size={14} aria-hidden="true" />
{/if}
<span class="tb-action-label">Pull</span>
</button>
<button
class="tb-action"
onclick={onPush}
disabled={!hasRepository || isBusy}
title="Push"
aria-label="Push"
>
{#if operation === "Pushing"}
<LoaderCircle class="spin" size={14} aria-hidden="true" />
{:else}
<Upload size={14} aria-hidden="true" />
{/if}
<span class="tb-action-label">Push</span>
</button>
<button
class="tb-action"
onclick={onRefresh}
disabled={isBusy || !hasRepository}
title="Refresh"
aria-label="Refresh"
>
<RefreshCw
class={operation === "Refreshing" ? "spin" : ""}
size={14}
aria-hidden="true"
/>
<span class="tb-action-label">Refresh</span>
</button>
<button
class="tb-action auto-toggle"
class:active={autoRefreshEnabled}
onclick={onToggleAutoRefresh}
aria-pressed={autoRefreshEnabled}
title={autoRefreshEnabled ? "Auto-refresh on — click to disable" : "Auto-refresh off — click to enable"}
aria-label="Toggle auto-refresh"
>
<RefreshCw
class={autoRefreshInFlight ? "spin" : ""}
size={14}
aria-hidden="true"
/>
<span class="tb-action-label">Auto</span>
</button>
<button
class="tb-action"
onclick={onOpenSettings}
title="Settings"
aria-label="Settings"
>
<Settings size={14} aria-hidden="true" />
<span class="tb-action-label">Settings</span>
</button>
</div>
<div class="titlebar-divider" aria-hidden="true"></div>
<div class="titlebar-controls">
<button class="tb-btn" onclick={minimizeWindow} title="Minimize" aria-label="Minimize">
<Minus size={12} aria-hidden="true" />
</button>
<button
class="tb-btn"
onclick={toggleMaximizeWindow}
title={isMaximized ? "Restore" : "Maximize"}
aria-label={isMaximized ? "Restore" : "Maximize"}
>
{#if isMaximized}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="3" y="1" width="8" height="8" rx="1" stroke="currentColor" stroke-width="1.5" />
<path d="M1 3v7a1 1 0 0 0 1 1h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
{:else}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="1" y="1" width="10" height="10" rx="1" stroke="currentColor" stroke-width="1.5" />
</svg>
{/if}
</button>
<button class="tb-btn close" onclick={closeWindow} title="Close" aria-label="Close">
<X size={12} aria-hidden="true" />
</button>
</div>
<div class="titlebar-controls">
<button class="tb-btn" onclick={minimizeWindow} title="Minimize" aria-label="Minimize">
<Minus size={12} aria-hidden="true" />
</button>
<button
class="tb-btn"
onclick={toggleMaximizeWindow}
title={isMaximized ? "Restore" : "Maximize"}
aria-label={isMaximized ? "Restore" : "Maximize"}
>
{#if isMaximized}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="3" y="1" width="8" height="8" rx="1" stroke="currentColor" stroke-width="1.5" />
<path d="M1 3v7a1 1 0 0 0 1 1h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
{:else}
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true">
<rect x="1" y="1" width="10" height="10" rx="1" stroke="currentColor" stroke-width="1.5" />
</svg>
{/if}
</button>
<button class="tb-btn close" onclick={closeWindow} title="Close" aria-label="Close">
<X size={12} aria-hidden="true" />
</button>
</div>
</header>
+43 -18
View File
@@ -1,22 +1,26 @@
<script lang="ts">
import { Check, Settings, X } from "@lucide/svelte";
import type { AnalyticsSettings, AppTheme } from "../types";
import { Check, Languages, Settings, X } from "@lucide/svelte";
import type { AnalyticsSettings, AppLanguage, AppTheme } from "../types";
interface Props {
analytics: AnalyticsSettings;
theme: AppTheme;
onSave: (settings: AnalyticsSettings, theme: AppTheme) => void;
language: AppLanguage;
onSave: (settings: AnalyticsSettings, theme: AppTheme, language: AppLanguage) => void;
onClose: () => void;
}
let { analytics, theme = "system", onSave = () => {}, onClose = () => {} }: Props = $props();
let { analytics, theme = "system", language = "en", onSave = () => {}, onClose = () => {} }: Props = $props();
let analyticsEnabled = $state(true);
let selectedTheme = $state<AppTheme>("system");
let selectedLanguage = $state<AppLanguage>("en");
const isGerman = $derived(selectedLanguage === "de");
$effect(() => {
analyticsEnabled = analytics.enabled;
selectedTheme = theme;
selectedLanguage = language;
});
function save() {
@@ -24,18 +28,18 @@
...analytics,
enabled: analyticsEnabled,
noticeSeen: true,
}, selectedTheme);
}, selectedTheme, selectedLanguage);
}
</script>
<div class="dialog-backdrop" role="presentation">
<div class="dialog app-settings-dialog" role="dialog" aria-modal="true" aria-label="Settings" tabindex="-1">
<div class="dialog app-settings-dialog" role="dialog" aria-modal="true" aria-label={isGerman ? "Einstellungen" : "Settings"} tabindex="-1">
<header class="dialog-header">
<div>
<span class="eyebrow">Gitty</span>
<h2 class="dialog-title">Settings</h2>
<h2 class="dialog-title">{isGerman ? "Einstellungen" : "Settings"}</h2>
</div>
<button class="dialog-close" type="button" onclick={onClose} title="Close">
<button class="dialog-close" type="button" onclick={onClose} title={isGerman ? "Schließen" : "Close"}>
<X size={18} aria-hidden="true" />
</button>
</header>
@@ -45,23 +49,44 @@
<header>
<Settings size={16} aria-hidden="true" />
<div>
<span class="eyebrow">Appearance</span>
<h3>Theme</h3>
<span class="eyebrow">{isGerman ? "Darstellung" : "Appearance"}</span>
<h3>{isGerman ? "Farbschema" : "Theme"}</h3>
</div>
</header>
<div class="settings-segmented" role="radiogroup" aria-label="Theme">
<div class="settings-segmented" role="radiogroup" aria-label={isGerman ? "Farbschema" : "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>
<span>{isGerman ? "Hell" : "Light"}</span>
</label>
<label class:active={selectedTheme === "dark"}>
<input type="radio" bind:group={selectedTheme} value="dark" />
<span>Dark</span>
<span>{isGerman ? "Dunkel" : "Dark"}</span>
</label>
</div>
</section>
<section class="settings-section">
<header>
<Languages size={16} aria-hidden="true" />
<div>
<span class="eyebrow">{isGerman ? "Sprache" : "Language"}</span>
<h3>{isGerman ? "App-Sprache" : "App language"}</h3>
</div>
</header>
<div class="settings-segmented settings-language" role="radiogroup" aria-label={isGerman ? "App-Sprache" : "App language"}>
<label class:active={selectedLanguage === "en"}>
<input type="radio" bind:group={selectedLanguage} value="en" />
<span>EN · English</span>
</label>
<label class:active={selectedLanguage === "de"}>
<input type="radio" bind:group={selectedLanguage} value="de" />
<span>DE · Deutsch</span>
</label>
</div>
</section>
@@ -71,24 +96,24 @@
<Settings size={16} aria-hidden="true" />
<div>
<span class="eyebrow">Analytics</span>
<h3>Anonymous usage analytics</h3>
<h3>{isGerman ? "Anonyme Nutzungsanalyse" : "Anonymous usage analytics"}</h3>
</div>
</header>
<label class="settings-toggle-row">
<input type="checkbox" bind:checked={analyticsEnabled} />
<span>
<strong>Allow anonymous Aptabase events</strong>
<small>No repository paths, remotes, branches, commit messages, file names, diffs, credentials, or code are sent.</small>
<strong>{isGerman ? "Anonyme Aptabase-Ereignisse erlauben" : "Allow anonymous Aptabase events"}</strong>
<small>{isGerman ? "Es werden keine Repository-Pfade, Remotes, Branches, Commit-Nachrichten, Dateinamen, Diffs, Zugangsdaten oder Code übertragen." : "No repository paths, remotes, branches, commit messages, file names, diffs, credentials, or code are sent."}</small>
</span>
</label>
</section>
<div class="new-branch-actions">
<button class="btn-secondary" type="button" onclick={onClose}>Cancel</button>
<button class="btn-secondary" type="button" onclick={onClose}>{isGerman ? "Abbrechen" : "Cancel"}</button>
<button class="btn-primary" type="submit">
<Check size={16} aria-hidden="true" />
Save
{isGerman ? "Speichern" : "Save"}
</button>
</div>
</form>
File diff suppressed because it is too large Load Diff
+1
View File
@@ -11,6 +11,7 @@ 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 type AppLanguage = "en" | "de";
export interface CommitAiStatus {
phase: CommitAiPhase;