From 632822fe1b0cbb45dbae634ffcbe78b6d56eb3a8 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Thu, 23 Jul 2026 09:38:40 +0200 Subject: [PATCH] Remove remote editing from the Git sidebar --- README.md | 2 +- crates/bds-core/src/engine/git.rs | 16 --------- crates/bds-core/tests/git_engine.rs | 5 ++- crates/bds-ui/src/app.rs | 4 --- crates/bds-ui/src/app/git_handlers.rs | 24 +++---------- crates/bds-ui/src/views/git.rs | 52 +++++++++++++++------------ locales/ui/de.ftl | 2 -- locales/ui/en.ftl | 2 -- locales/ui/es.ftl | 2 -- locales/ui/fr.ftl | 2 -- locales/ui/it.ftl | 2 -- specs/git.allium | 7 ---- specs/sidebar_views.allium | 9 ++++- 13 files changed, 46 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index f493174..928c732 100644 --- a/README.md +++ b/README.md @@ -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 with repository initialization, Git LFS image tracking, status and diffs, branch/file history, commits, remotes, 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, 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. diff --git a/crates/bds-core/src/engine/git.rs b/crates/bds-core/src/engine/git.rs index 862145f..84ea603 100644 --- a/crates/bds-core/src/engine/git.rs +++ b/crates/bds-core/src/engine/git.rs @@ -502,22 +502,6 @@ impl GitEngine { }) } - pub fn set_remote(&self, remote_url: &str) -> Result<(), GitError> { - let remote_url = remote_url.trim(); - if remote_url.is_empty() { - return Err(GitError::Validation("remote URL is required".into())); - } - let args = if self - .optional_output(&["remote", "get-url", "origin"], GitOperation::Remote)? - .is_some() - { - ["remote", "set-url", "origin", remote_url] - } else { - ["remote", "add", "origin", remote_url] - }; - self.run_local(&args, GitOperation::Remote).map(|_| ()) - } - pub fn commit_all(&self, message: &str) -> Result { let message = message.trim(); if message.is_empty() { diff --git a/crates/bds-core/tests/git_engine.rs b/crates/bds-core/tests/git_engine.rs index 434d592..b099976 100644 --- a/crates/bds-core/tests/git_engine.rs +++ b/crates/bds-core/tests/git_engine.rs @@ -134,7 +134,10 @@ fn remote_state_history_fetch_and_fast_forward_pull_use_real_refs() { git(&local, &["add", "-A"]); git(&local, &["commit", "-m", "local one"]); let engine = GitEngine::new(&local); - engine.set_remote(remote.to_str().unwrap()).unwrap(); + git( + &local, + &["remote", "add", "origin", remote.to_str().unwrap()], + ); git(&local, &["push", "-u", "origin", "master"]); git( diff --git a/crates/bds-ui/src/app.rs b/crates/bds-ui/src/app.rs index 3ef79b1..89fc27b 100644 --- a/crates/bds-ui/src/app.rs +++ b/crates/bds-ui/src/app.rs @@ -311,10 +311,8 @@ pub enum Message { repository_dir: PathBuf, result: Result, }, - GitRemoteInputChanged(String), GitCommitMessageChanged(String), GitInitialize, - GitSetRemote, GitCommit, GitFetch, GitPull, @@ -2855,10 +2853,8 @@ impl BdsApp { // ── Git ── message @ (Message::GitRefresh | Message::GitLoaded { .. } - | Message::GitRemoteInputChanged(_) | Message::GitCommitMessageChanged(_) | Message::GitInitialize - | Message::GitSetRemote | Message::GitCommit | Message::GitFetch | Message::GitPull diff --git a/crates/bds-ui/src/app/git_handlers.rs b/crates/bds-ui/src/app/git_handlers.rs index 8ce2408..3e30bf2 100644 --- a/crates/bds-ui/src/app/git_handlers.rs +++ b/crates/bds-ui/src/app/git_handlers.rs @@ -25,30 +25,14 @@ impl BdsApp { } Task::none() } - Message::GitRemoteInputChanged(value) => { - self.git_state.remote_input = value; - Task::none() - } Message::GitCommitMessageChanged(value) => { self.git_state.commit_message = value; Task::none() } - Message::GitInitialize => { - let remote = self.git_state.remote_input.trim().to_string(); - self.run_local_git_action(GitOperation::Initialize, move |engine| { - engine.initialize()?; - if !remote.is_empty() { - engine.set_remote(&remote)?; - } - Ok(()) - }) - } - Message::GitSetRemote => { - let remote = self.git_state.remote_input.trim().to_string(); - self.run_local_git_action(GitOperation::Remote, move |engine| { - engine.set_remote(&remote) - }) - } + Message::GitInitialize => self + .run_local_git_action(GitOperation::Initialize, |engine| { + engine.initialize().map(|_| ()) + }), Message::GitCommit => { let message = self.git_state.commit_message.clone(); self.run_local_git_action(GitOperation::Commit, move |engine| { diff --git a/crates/bds-ui/src/views/git.rs b/crates/bds-ui/src/views/git.rs index 3bb74a0..fb6d458 100644 --- a/crates/bds-ui/src/views/git.rs +++ b/crates/bds-ui/src/views/git.rs @@ -35,7 +35,6 @@ pub struct GitUiState { pub files: Vec, pub history: Vec, pub remote: GitRemoteState, - pub remote_input: String, pub commit_message: String, pub loading: bool, pub error: Option, @@ -61,7 +60,6 @@ impl Default for GitUiState { ahead: 0, behind: 0, }, - remote_input: String::new(), commit_message: String::new(), loading: false, error: None, @@ -72,7 +70,6 @@ impl Default for GitUiState { impl GitUiState { pub fn apply_snapshot(&mut self, snapshot: GitSnapshot) { - self.remote_input = snapshot.repository.remote_url.clone().unwrap_or_default(); self.repository = snapshot.repository; self.files = snapshot.files; self.history = snapshot.history; @@ -151,11 +148,6 @@ pub fn sidebar_view( text(t(locale, "git.notRepository")) .size(12) .color(Color::from_rgb(0.6, 0.6, 0.65)), - text_input(&t(locale, "git.remoteOptional"), &state.remote_input) - .on_input(Message::GitRemoteInputChanged) - .size(12) - .padding([6, 8]) - .style(inputs::field_style), button(text(t(locale, "git.initialize")).size(12)) .on_press(Message::GitInitialize) .padding([5, 8]) @@ -209,21 +201,6 @@ pub fn sidebar_view( .into(), ); } - content.push( - row![ - text_input(&t(locale, "git.remoteUrl"), &state.remote_input) - .on_input(Message::GitRemoteInputChanged) - .size(11) - .padding([5, 7]) - .style(inputs::field_style), - button(text(t(locale, "git.saveRemote")).size(11)) - .on_press(Message::GitSetRemote) - .padding([5, 7]) - .style(inputs::secondary_button), - ] - .spacing(4) - .into(), - ); content.push(section_title( tw( locale, @@ -529,3 +506,32 @@ impl fmt::Display for PathTitle<'_> { } use std::fmt; + +#[cfg(test)] +mod tests { + use super::*; + use iced::advanced::widget::{Tree, tree::Tag}; + use iced::widget::text_input; + + type TextInputState = + text_input::State<::Paragraph>; + + fn count_text_inputs(tree: &Tree) -> usize { + usize::from(tree.tag == Tag::of::()) + + tree.children.iter().map(count_text_inputs).sum::() + } + + #[test] + fn repository_location_is_never_editable_in_the_git_sidebar() { + let not_initialized = GitUiState::default(); + let inactive_tree = + Tree::new(sidebar_view(¬_initialized, false, UiLocale::En).as_widget()); + assert_eq!(count_text_inputs(&inactive_tree), 0); + + let mut initialized = GitUiState::default(); + initialized.repository.is_initialized = true; + initialized.repository.remote_url = Some("ssh://example.test/blog.git".into()); + let active_tree = Tree::new(sidebar_view(&initialized, false, UiLocale::En).as_widget()); + assert_eq!(count_text_inputs(&active_tree), 1); + } +} diff --git a/locales/ui/de.ftl b/locales/ui/de.ftl index cba4fda..0128f82 100644 --- a/locales/ui/de.ftl +++ b/locales/ui/de.ftl @@ -700,14 +700,12 @@ find-noMatches = Keine Treffer gefunden find-replacedCount = { $count } Treffer ersetzt git-loading = Repository wird geladen… git-notRepository = Dieses Projekt ist noch kein Git-Repository. -git-remoteOptional = Remote-URL (optional) git-initialize = Git initialisieren git-fetch = Abrufen git-pull = Herunterladen git-push = Hochladen git-airplaneBlocked = Netzwerk-Git-Aktionen sind im Flugmodus nicht verfügbar. git-remoteUrl = Origin-URL -git-saveRemote = Speichern git-changesCount = Änderungen ({ $count }) git-commitMessage = Commit-Nachricht git-commit = Commit diff --git a/locales/ui/en.ftl b/locales/ui/en.ftl index 700aa9f..987b7c0 100644 --- a/locales/ui/en.ftl +++ b/locales/ui/en.ftl @@ -700,14 +700,12 @@ find-noMatches = No matches found find-replacedCount = Replaced { $count } matches git-loading = Loading repository… git-notRepository = This project is not a Git repository yet. -git-remoteOptional = Remote URL (optional) git-initialize = Initialize Git git-fetch = Fetch git-pull = Pull git-push = Push git-airplaneBlocked = Network Git actions are unavailable in airplane mode. git-remoteUrl = Origin URL -git-saveRemote = Save git-changesCount = Changes ({ $count }) git-commitMessage = Commit message git-commit = Commit diff --git a/locales/ui/es.ftl b/locales/ui/es.ftl index fa34c72..5060ef3 100644 --- a/locales/ui/es.ftl +++ b/locales/ui/es.ftl @@ -700,14 +700,12 @@ find-noMatches = No se encontraron coincidencias find-replacedCount = Se reemplazaron { $count } coincidencias git-loading = Cargando repositorio… git-notRepository = Este proyecto aún no es un repositorio Git. -git-remoteOptional = URL remota (opcional) git-initialize = Inicializar Git 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-remoteUrl = URL de origin -git-saveRemote = Guardar git-changesCount = Cambios ({ $count }) git-commitMessage = Mensaje de commit git-commit = Commit diff --git a/locales/ui/fr.ftl b/locales/ui/fr.ftl index b45b82e..c38b0c3 100644 --- a/locales/ui/fr.ftl +++ b/locales/ui/fr.ftl @@ -700,14 +700,12 @@ find-noMatches = Aucun résultat find-replacedCount = { $count } résultats remplacés git-loading = Chargement du dépôt… git-notRepository = Ce projet n’est pas encore un dépôt Git. -git-remoteOptional = URL distante (facultative) git-initialize = Initialiser Git git-fetch = Récupérer git-pull = Tirer git-push = Pousser git-airplaneBlocked = Les actions Git réseau sont indisponibles en mode avion. git-remoteUrl = URL d’origine -git-saveRemote = Enregistrer git-changesCount = Modifications ({ $count }) git-commitMessage = Message de commit git-commit = Commit diff --git a/locales/ui/it.ftl b/locales/ui/it.ftl index 4cff760..9c7b398 100644 --- a/locales/ui/it.ftl +++ b/locales/ui/it.ftl @@ -700,14 +700,12 @@ find-noMatches = Nessuna corrispondenza find-replacedCount = Sostituite { $count } corrispondenze git-loading = Caricamento del repository… git-notRepository = Questo progetto non è ancora un repository Git. -git-remoteOptional = URL remoto (facoltativo) git-initialize = Inizializza Git git-fetch = Recupera git-pull = Scarica git-push = Carica git-airplaneBlocked = Le azioni Git di rete non sono disponibili in modalità aereo. git-remoteUrl = URL origin -git-saveRemote = Salva git-changesCount = Modifiche ({ $count }) git-commitMessage = Messaggio di commit git-commit = Commit diff --git a/specs/git.allium b/specs/git.allium index 20c2cd9..b0125c6 100644 --- a/specs/git.allium +++ b/specs/git.allium @@ -87,7 +87,6 @@ surface GitControlSurface { GitHistoryRequested(project, branch) GitFileHistoryRequested(project, file_path) GitRemoteStateRequested(project) - GitRemoteSetRequested(project, remote_url) GitFetchRequested(project) GitPullRequested(project) GitPushRequested(project) @@ -153,12 +152,6 @@ rule GetRemoteState { ensures: GitRemoteStateReport(project, local_branch, upstream_branch, ahead, behind) } -rule SetRemote { - when: GitRemoteSetRequested(project, remote_url) - -- Creates origin when absent and replaces its URL when already configured. - ensures: GitRemoteUpdated(project, remote_url) -} - rule Fetch { when: GitFetchRequested(project) ensures: RemoteRefsUpdated(project) diff --git a/specs/sidebar_views.allium b/specs/sidebar_views.allium index 0ba9573..07a517c 100644 --- a/specs/sidebar_views.allium +++ b/specs/sidebar_views.allium @@ -674,8 +674,10 @@ rule ImportListClick { -- The sidebar must surface these capabilities directly. -- State: not_a_repo --- Remote URL text input + "Initialize Git" button. +-- "Initialize Git" button for the current blog project directory. -- Init progress with phase/percentage/detail, collapsible transcript. +-- The remote URL is discovered from the repository and is never editable +-- from the sidebar. -- State: active_repo value GitActiveView { @@ -702,6 +704,11 @@ surface GitActiveViewSurface { provides: GitCommitRequested(message) + + @guarantee CurrentProjectRepository + -- Git operations always target the current blog project's directory. + -- Any origin URL is discovered from that repository and cannot be + -- entered or changed in this surface. } value GitStatusFile {