Color-code Git commit sync status

This commit is contained in:
2026-07-23 09:46:50 +02:00
parent 632822fe1b
commit 3e3b2d9698
8 changed files with 91 additions and 12 deletions

View File

@@ -27,7 +27,7 @@ The project is under active development. Core blogging workflows are broadly ava
- Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing.
- Persistent conversational AI with safe Markdown, streamed and cancellable responses, model/session/token tracking, bounded project-aware blog tools, and localized conversation management in the Chat workspace. Allowlisted render tools add persistent native cards, charts, forms, lists, metrics, mind maps, tables, and tabs without executing assistant-provided HTML or JavaScript.
- SSH-agent-based SCP or rsync publishing.
- Integrated Git workflow for each blog project's current repository, with repository initialization, read-only origin discovery, Git LFS image tracking, status and diffs, branch/file history, commits, cancellable fetch/pull/push, and post-pull filesystem reconciliation; network actions respect airplane mode.
- Integrated Git workflow for each blog project's current repository, with repository initialization, read-only origin discovery, Git LFS image tracking, status and diffs, branch/file history with bDS2-compatible sync-status colors, commits, cancellable fetch/pull/push, and post-pull filesystem reconciliation; network actions respect airplane mode.
- Site, media, and translation validation plus `ruds://new-post` Blogmark capture and Lua transforms; captures open directly in the post editor and defer automatic translation until an explicit manual save, which queues one task per still-missing language. Publishing never starts automatic translation. bDS2 keeps its separate `bds2://` bookmarklet protocol.
RuDS uses no JavaScript application runtime and loads no CSS or JavaScript from CDNs. The preview is served by the Rust application and displayed by the operating-system webview.

View File

@@ -7,7 +7,7 @@ use bds_core::engine::git::{
use bds_core::i18n::UiLocale;
use iced::widget::text::{Shaping, Wrapping};
use iced::widget::{Space, button, column, container, row, scrollable, text, text_input};
use iced::{Color, Element, Font, Length};
use iced::{Alignment, Background, Color, Element, Font, Length};
use crate::app::Message;
use crate::components::inputs;
@@ -192,7 +192,16 @@ pub fn sidebar_view(
.spacing(4)
.wrap();
let mut content: Vec<Element<'static, Message>> = vec![header.into(), actions.into()];
let sync_legend = row![
sync_legend_item(SyncStatus::Both, locale),
sync_legend_item(SyncStatus::LocalOnly, locale),
sync_legend_item(SyncStatus::RemoteOnly, locale),
]
.spacing(8)
.wrap();
let mut content: Vec<Element<'static, Message>> =
vec![header.into(), sync_legend.into(), actions.into()];
if offline_mode {
content.push(
text(t(locale, "git.airplaneBlocked"))
@@ -299,17 +308,15 @@ fn history_button(commit: &GitCommit) -> Element<'static, Message> {
let hash = commit.hash.clone();
let subject = commit.subject.clone().unwrap_or_else(|| hash.clone());
let short = hash.chars().take(7).collect::<String>();
let marker = match commit.sync_status {
SyncStatus::Both => "",
SyncStatus::LocalOnly => "",
SyncStatus::RemoteOnly => "",
};
button(
column![
text(subject.clone()).size(11),
text(format!("{marker} {short}"))
.size(10)
.color(Color::from_rgb(0.5, 0.5, 0.56)),
row![
sync_status_dot(commit.sync_status),
text(short).size(10).color(Color::from_rgb(0.5, 0.5, 0.56)),
]
.spacing(4)
.align_y(Alignment::Center),
]
.spacing(1),
)
@@ -320,6 +327,45 @@ fn history_button(commit: &GitCommit) -> Element<'static, Message> {
.into()
}
fn sync_legend_item(status: SyncStatus, locale: UiLocale) -> Element<'static, Message> {
let key = match status {
SyncStatus::Both => "git.synced",
SyncStatus::LocalOnly => "git.localOnly",
SyncStatus::RemoteOnly => "git.remoteOnly",
};
row![
sync_status_dot(status),
text(t(locale, key))
.size(10)
.color(Color::from_rgb(0.5, 0.5, 0.56)),
]
.spacing(4)
.align_y(Alignment::Center)
.into()
}
fn sync_status_dot(status: SyncStatus) -> Element<'static, Message> {
let color = sync_status_color(status);
container(Space::new(8, 8))
.style(move |_: &iced::Theme| container::Style {
background: Some(Background::Color(color)),
border: iced::Border {
radius: 4.0.into(),
..iced::Border::default()
},
..container::Style::default()
})
.into()
}
fn sync_status_color(status: SyncStatus) -> Color {
match status {
SyncStatus::Both => Color::from_rgb8(0x73, 0xC9, 0x91),
SyncStatus::LocalOnly => Color::from_rgb8(0xCC, 0xA7, 0x00),
SyncStatus::RemoteOnly => Color::from_rgb8(0x75, 0xBE, 0xFF),
}
}
fn network_output(run: &GitNetworkRunState, locale: UiLocale) -> Element<'static, Message> {
let output = run
.output
@@ -534,4 +580,20 @@ mod tests {
let active_tree = Tree::new(sidebar_view(&initialized, false, UiLocale::En).as_widget());
assert_eq!(count_text_inputs(&active_tree), 1);
}
#[test]
fn commit_sync_colors_match_bds2() {
assert_eq!(
sync_status_color(SyncStatus::Both),
Color::from_rgb8(0x73, 0xC9, 0x91)
);
assert_eq!(
sync_status_color(SyncStatus::LocalOnly),
Color::from_rgb8(0xCC, 0xA7, 0x00)
);
assert_eq!(
sync_status_color(SyncStatus::RemoteOnly),
Color::from_rgb8(0x75, 0xBE, 0xFF)
);
}
}

View File

@@ -705,6 +705,9 @@ git-fetch = Abrufen
git-pull = Herunterladen
git-push = Hochladen
git-airplaneBlocked = Netzwerk-Git-Aktionen sind im Flugmodus nicht verfügbar.
git-synced = Synchronisiert
git-localOnly = Nur lokal
git-remoteOnly = Nur remote
git-remoteUrl = Origin-URL
git-changesCount = Änderungen ({ $count })
git-commitMessage = Commit-Nachricht

View File

@@ -705,6 +705,9 @@ git-fetch = Fetch
git-pull = Pull
git-push = Push
git-airplaneBlocked = Network Git actions are unavailable in airplane mode.
git-synced = Synced
git-localOnly = Local only
git-remoteOnly = Remote only
git-remoteUrl = Origin URL
git-changesCount = Changes ({ $count })
git-commitMessage = Commit message

View File

@@ -705,6 +705,9 @@ git-fetch = Obtener
git-pull = Descargar
git-push = Subir
git-airplaneBlocked = Las acciones Git de red no están disponibles en modo avión.
git-synced = Sincronizado
git-localOnly = Solo local
git-remoteOnly = Solo remoto
git-remoteUrl = URL de origin
git-changesCount = Cambios ({ $count })
git-commitMessage = Mensaje de commit

View File

@@ -705,6 +705,9 @@ git-fetch = Récupérer
git-pull = Tirer
git-push = Pousser
git-airplaneBlocked = Les actions Git réseau sont indisponibles en mode avion.
git-synced = Synchronisé
git-localOnly = Local uniquement
git-remoteOnly = Distant uniquement
git-remoteUrl = URL dorigine
git-changesCount = Modifications ({ $count })
git-commitMessage = Message de commit

View File

@@ -705,6 +705,9 @@ git-fetch = Recupera
git-pull = Scarica
git-push = Carica
git-airplaneBlocked = Le azioni Git di rete non sono disponibili in modalità aereo.
git-synced = Sincronizzato
git-localOnly = Solo locale
git-remoteOnly = Solo remoto
git-remoteUrl = URL origin
git-changesCount = Modifiche ({ $count })
git-commitMessage = Messaggio di commit

View File

@@ -765,7 +765,9 @@ surface GitHistoryEntryView {
@guarantee SyncStatusIndicator
-- sync_status rendered as a coloured dot.
-- "synced" = both local and remote. "local_only" = local only. "remote_only" = remote only.
-- "synced" = both local and remote, green #73c991.
-- "local_only" = local only, warning yellow #cca700.
-- "remote_only" = remote only, information blue #75beff.
-- A colour legend is shown in the git view header.
}