From 534d572154512c7103416c9a4e814756c638b718 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Sun, 13 Sep 2026 17:26:07 +0700 Subject: [PATCH] . --- crates/assets/src/lib.rs | 7 +- crates/signed_core/src/inbox.rs | 15 +- crates/signed_core/src/model.rs | 11 +- crates/signed_git/src/history.rs | 11 - crates/signed_git/src/lib.rs | 4 +- crates/signed_git/src/repo.rs | 12 +- crates/signed_git/src/tests.rs | 20 +- crates/signed_git/src/worktree.rs | 16 +- crates/signed_nostr/src/signer.rs | 55 ++--- crates/signed_state/src/backend.rs | 27 +-- crates/workspace/src/views/inbox.rs | 2 +- docs/TODO.md | 2 - docs/over-engineering-cleanup-plan.md | 300 -------------------------- 13 files changed, 54 insertions(+), 428 deletions(-) delete mode 100644 docs/over-engineering-cleanup-plan.md diff --git a/crates/assets/src/lib.rs b/crates/assets/src/lib.rs index ba31461..82dd078 100644 --- a/crates/assets/src/lib.rs +++ b/crates/assets/src/lib.rs @@ -39,12 +39,7 @@ impl Assets { .filter_map(|path| { let data = Self::get(path.as_ref())?; let name = path.strip_prefix("themes/").unwrap_or(path.as_ref()); - let content = match data.data { - std::borrow::Cow::Borrowed(bytes) => { - std::str::from_utf8(bytes).ok()?.to_owned() - } - std::borrow::Cow::Owned(bytes) => String::from_utf8(bytes).ok()?, - }; + let content = std::str::from_utf8(data.data.as_ref()).ok()?.to_owned(); Some((name.to_owned(), content)) }) .collect() diff --git a/crates/signed_core/src/inbox.rs b/crates/signed_core/src/inbox.rs index 138e7d6..582e9b0 100644 --- a/crates/signed_core/src/inbox.rs +++ b/crates/signed_core/src/inbox.rs @@ -19,8 +19,6 @@ pub struct InboxItem { pub root: EventId, /// The root event itself, when it is known locally. pub root_event: Option, - /// Kind of the root event, when it is known locally. - pub root_kind: Option, /// Repository the root belongs to, from the root's `a` tag. pub address: Option, /// Notification events directed at the user, newest first. @@ -45,13 +43,11 @@ impl InboxItem { } pub fn kind(&self) -> Option { - self.root_kind.or_else(|| { - self.root_event - .as_ref() - .or_else(|| self.own_events.first()) - .or_else(|| self.events.first()) - .map(|event| event.kind) - }) + self.root_event + .as_ref() + .or_else(|| self.own_events.first()) + .or_else(|| self.events.first()) + .map(|event| event.kind) } /// Timestamp of the newest event in the thread. @@ -202,7 +198,6 @@ where let mut item = InboxItem { root, - root_kind: root_event.as_ref().map(|event| event.kind), address: root_event .as_ref() .and_then(|event| event.tags.coordinates().next()), diff --git a/crates/signed_core/src/model.rs b/crates/signed_core/src/model.rs index b5bc37b..365864f 100644 --- a/crates/signed_core/src/model.rs +++ b/crates/signed_core/src/model.rs @@ -41,12 +41,10 @@ pub struct Upstream { /// Upstream repository coordinate when the `u` tag names a NIP-34 repository. /// `None` for the git-URL form. pub addr: Option, - /// Relay hint for the upstream, if the `u` tag carries one. - pub relay_hint: Option, } impl Upstream { - fn parse(raw: &str, relay_hint: Option<&str>) -> Self { + fn parse(raw: &str) -> Self { let coordinate = raw.split('|').next().unwrap_or(raw); let addr = coordinate .parse::() @@ -55,7 +53,6 @@ impl Upstream { Self { raw: raw.to_owned(), addr, - relay_hint: relay_hint.and_then(|hint| RelayUrl::parse(hint).ok()), } } @@ -317,7 +314,7 @@ impl Announcement { let values = tag.as_slice(); let raw = values.get(1).map(String::as_str).unwrap_or_default(); if !raw.is_empty() { - upstream = Some(Upstream::parse(raw, values.get(2).map(String::as_str))); + upstream = Some(Upstream::parse(raw)); } } } @@ -511,10 +508,6 @@ mod tests { upstream.raw, "30617:68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272:upstream|https://example.com/upstream.git" ); - assert_eq!( - upstream.relay_hint, - Some(RelayUrl::parse("wss://relay.example.com").expect("valid relay")) - ); assert_eq!( upstream.display().to_string(), "30617:68d81165918100b7da43fc28f7d1fc12554466e1115886b9e7bb326f65ec4272:upstream" diff --git a/crates/signed_git/src/history.rs b/crates/signed_git/src/history.rs index 5c8ca1f..f99a105 100644 --- a/crates/signed_git/src/history.rs +++ b/crates/signed_git/src/history.rs @@ -72,17 +72,6 @@ fn file_commit_with_description( }) } -/// Find the most recent commit that changed `rel`, a path relative to the worktree. -/// -/// `Ok(None)` when no commit touched the file, e.g. an untracked file. -pub fn last_commit(repo: &gix::Repository, rel: &Path) -> Result> { - let rel = rel.to_path_buf(); - Ok(last_commits(repo, std::slice::from_ref(&rel))? - .into_iter() - .next() - .map(|(_, commit)| commit)) -} - /// Newest commit touching each of `rels`, like `git log -1 -- ` per path. /// `rels` are paths relative to the worktree. /// diff --git a/crates/signed_git/src/lib.rs b/crates/signed_git/src/lib.rs index 19c668a..5d8dd65 100644 --- a/crates/signed_git/src/lib.rs +++ b/crates/signed_git/src/lib.rs @@ -16,8 +16,8 @@ pub use diff::{ worktree_commit_range_diff, }; pub use history::{ - CommitList, FileCommit, MAX_LISTED_COMMITS, all_commits, head_commit, last_commit, - worktree_all_commits, worktree_commit, worktree_commit_range_commits, worktree_last_commits, + CommitList, FileCommit, MAX_LISTED_COMMITS, all_commits, head_commit, worktree_all_commits, + worktree_commit, worktree_commit_range_commits, worktree_last_commits, }; pub use patch::{ apply_patch, format_patch_between, patch_commits, patch_diffs, split_patch_series, diff --git a/crates/signed_git/src/repo.rs b/crates/signed_git/src/repo.rs index 324b5d0..93d5998 100644 --- a/crates/signed_git/src/repo.rs +++ b/crates/signed_git/src/repo.rs @@ -1,6 +1,6 @@ use std::path::Path; -use anyhow::{Context, Result, bail}; +use anyhow::{Context, Result}; use crate::history::open_with_cache; use crate::worktree::{force_checkout, worktree_dirty}; @@ -171,12 +171,7 @@ pub fn init_repository(path: &Path, name: &str, description: &str) -> Result Result> { { let info = info?; if info.parent_ids().next().is_none() { - let id = info.id().to_string(); - return Ok((id.len() == 40).then_some(id)); + return Ok(Some(info.id().to_string())); } } diff --git a/crates/signed_git/src/tests.rs b/crates/signed_git/src/tests.rs index e157b47..890efb3 100644 --- a/crates/signed_git/src/tests.rs +++ b/crates/signed_git/src/tests.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process::Command; use super::*; @@ -578,7 +578,7 @@ fn git_run(dir: &Path, args: &[&str]) { } #[test] -fn last_commit_returns_most_recent_change() { +fn worktree_last_commits_returns_most_recent_change() { let (dir, repo) = fixture(&[("a.txt", b"one")]); commit_all(&repo, "initial"); @@ -589,9 +589,12 @@ fn last_commit_returns_most_recent_change() { std::fs::write(dir.path().join("b.txt"), b"other").expect("write"); commit_all(&repo, "add b"); - let commit = last_commit(&repo, Path::new("a.txt")) + let commit = worktree_last_commits(dir.path(), &[PathBuf::from("a.txt")]) .expect("lookup") - .expect("found"); + .into_iter() + .next() + .expect("found") + .1; assert_eq!(commit.summary, "change a"); assert_eq!(commit.author, "Test Author"); assert!(!commit.id.is_empty()); @@ -621,7 +624,7 @@ fn all_commits_lists_every_commit() { } #[test] -fn last_commit_reports_merge_commits() { +fn worktree_last_commits_reports_merge_commits() { let (dir, repo) = fixture(&[("a.txt", b"base")]); commit_all(&repo, "initial"); @@ -645,9 +648,12 @@ fn last_commit_reports_merge_commits() { // `--no-ff` forces a merge commit, it is the latest commit changing a.txt. run(&["merge", "--no-ff", "--no-edit", "feature"]); - let commit = last_commit(&repo, Path::new("a.txt")) + let commit = worktree_last_commits(dir.path(), &[PathBuf::from("a.txt")]) .expect("lookup") - .expect("found"); + .into_iter() + .next() + .expect("found") + .1; assert_eq!( commit.id, repo.head_id().expect("head").shorten_or_id().to_string() diff --git a/crates/signed_git/src/worktree.rs b/crates/signed_git/src/worktree.rs index 97a170a..dd68418 100644 --- a/crates/signed_git/src/worktree.rs +++ b/crates/signed_git/src/worktree.rs @@ -64,20 +64,10 @@ pub fn worktree_commits_ahead(workdir: &Path, base: &str, branch: &str) -> u32 { walk.filter_map(Result::ok).count().min(u32::MAX as usize) as u32 } -/// Resolve `rev` to a commit id, accepting full refs, -/// symbolic refs and the bare branch names callers pass, like git's DWIM. +/// Resolve `rev` to a commit id, accepting full refs or the bare branch names +/// callers pass. `gix`'s revision parser already applies git's ref DWIM. fn resolve_commit<'a>(repo: &'a gix::Repository, rev: &str) -> Option> { - if let Ok(id) = repo.rev_parse_single(rev.as_bytes()) { - return Some(id); - } - - // Branch names arrive bare, like git resolving `main`. - if rev.contains('/') { - return None; - } - - repo.rev_parse_single(format!("refs/heads/{rev}").as_bytes()) - .ok() + repo.rev_parse_single(rev.as_bytes()).ok() } /// Relative paths of all entries in the worktree, files and directories. diff --git a/crates/signed_nostr/src/signer.rs b/crates/signed_nostr/src/signer.rs index 6f00bf6..8ebf43e 100644 --- a/crates/signed_nostr/src/signer.rs +++ b/crates/signed_nostr/src/signer.rs @@ -5,32 +5,9 @@ use std::pin::Pin; use std::sync::{Arc, RwLock}; use nostr_connect::client::AuthUrlHandler; +use nostr_sdk::error::Error as SignerError; use nostr_sdk::prelude::*; -#[derive(Debug)] -pub struct UniversalSignerError(Box); - -impl fmt::Display for UniversalSignerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Error for UniversalSignerError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&*self.0) - } -} - -impl UniversalSignerError { - pub fn new(err: E) -> Self - where - E: Error + Send + Sync + 'static, - { - UniversalSignerError(Box::new(err)) - } -} - /// A type-erased signer whose inner signer can be swapped in-place. #[derive(Clone, Debug)] pub struct UniversalSigner { @@ -65,21 +42,21 @@ impl UniversalSigner { trait InnerSigner: fmt::Debug + Send + Sync + 'static { fn get_public_key_async( &self, - ) -> Pin> + Send + '_>>; + ) -> Pin> + Send + '_>>; fn sign_event_async( &self, unsigned: UnsignedEvent, - ) -> Pin> + Send + '_>>; + ) -> Pin> + Send + '_>>; fn nip44_encrypt_async<'a>( &'a self, public_key: &'a PublicKey, content: &'a str, - ) -> Pin> + Send + 'a>>; + ) -> Pin> + Send + 'a>>; fn nip44_decrypt_async<'a>( &'a self, public_key: &'a PublicKey, payload: &'a str, - ) -> Pin> + Send + 'a>>; + ) -> Pin> + Send + 'a>>; } #[derive(Debug)] @@ -94,22 +71,22 @@ where { fn get_public_key_async( &self, - ) -> Pin> + Send + '_>> { + ) -> Pin> + Send + '_>> { Box::pin(async move { AsyncGetPublicKey::get_public_key_async(&self.0) .await - .map_err(UniversalSignerError::new) + .map_err(SignerError::other) }) } fn sign_event_async( &self, unsigned: UnsignedEvent, - ) -> Pin> + Send + '_>> { + ) -> Pin> + Send + '_>> { Box::pin(async move { AsyncSignEvent::sign_event_async(&self.0, unsigned) .await - .map_err(UniversalSignerError::new) + .map_err(SignerError::other) }) } @@ -117,11 +94,11 @@ where &'a self, public_key: &'a PublicKey, content: &'a str, - ) -> Pin> + Send + 'a>> { + ) -> Pin> + Send + 'a>> { Box::pin(async move { AsyncNip44::nip44_encrypt_async(&self.0, public_key, content) .await - .map_err(UniversalSignerError::new) + .map_err(SignerError::other) }) } @@ -129,17 +106,17 @@ where &'a self, public_key: &'a PublicKey, payload: &'a str, - ) -> Pin> + Send + 'a>> { + ) -> Pin> + Send + 'a>> { Box::pin(async move { AsyncNip44::nip44_decrypt_async(&self.0, public_key, payload) .await - .map_err(UniversalSignerError::new) + .map_err(SignerError::other) }) } } impl AsyncGetPublicKey for UniversalSigner { - type Error = UniversalSignerError; + type Error = SignerError; fn get_public_key_async( &self, @@ -150,7 +127,7 @@ impl AsyncGetPublicKey for UniversalSigner { } impl AsyncSignEvent for UniversalSigner { - type Error = UniversalSignerError; + type Error = SignerError; fn sign_event_async( &self, @@ -162,7 +139,7 @@ impl AsyncSignEvent for UniversalSigner { } impl AsyncNip44 for UniversalSigner { - type Error = UniversalSignerError; + type Error = SignerError; fn nip44_encrypt_async<'a>( &'a self, diff --git a/crates/signed_state/src/backend.rs b/crates/signed_state/src/backend.rs index 3fe9696..3e6193c 100644 --- a/crates/signed_state/src/backend.rs +++ b/crates/signed_state/src/backend.rs @@ -1490,24 +1490,21 @@ const GRASP_RETRY_DELAY: Duration = Duration::from_secs(1); #[derive(Debug, Clone)] pub struct GraspServerResult { pub relay: RelayUrl, - pub git_url: String, /// `None` when the server accepted the data, the reason otherwise. pub reason: Option, } impl GraspServerResult { - fn ok(relay: RelayUrl, git_url: String) -> Self { + fn ok(relay: RelayUrl) -> Self { Self { relay, - git_url, reason: None, } } - fn failed(relay: RelayUrl, git_url: String, reason: impl Into) -> Self { + fn failed(relay: RelayUrl, reason: impl Into) -> Self { Self { relay, - git_url, reason: Some(reason.into()), } } @@ -1709,11 +1706,9 @@ async fn push_staged_to_grasps( for relay in servers { let Some(base) = grasp_base_url(relay) else { - outcome.servers.push(GraspServerResult::failed( - relay.clone(), - relay.to_string(), - "no domain", - )); + outcome + .servers + .push(GraspServerResult::failed(relay.clone(), "no domain")); continue; }; let git_url = format!("{base}/{owner}/{repo_id}.git"); @@ -1794,11 +1789,9 @@ async fn push_staged_to_grasps( log::warn!("grasp push failed: {relay}: {reason}"); outcome .servers - .push(GraspServerResult::failed(relay.clone(), git_url, reason)); + .push(GraspServerResult::failed(relay.clone(), reason)); } - None => outcome - .servers - .push(GraspServerResult::ok(relay.clone(), git_url)), + None => outcome.servers.push(GraspServerResult::ok(relay.clone())), } } @@ -1956,13 +1949,9 @@ mod tests { fn push_outcome_reports_partial_failures() { let outcome = PushOutcome { servers: vec![ - GraspServerResult::ok( - RelayUrl::parse("wss://gitnostr.com").expect("url"), - "https://gitnostr.com/npub1owner/repo.git".to_owned(), - ), + GraspServerResult::ok(RelayUrl::parse("wss://gitnostr.com").expect("url")), GraspServerResult::failed( RelayUrl::parse("wss://relay.ngit.dev").expect("url"), - "https://relay.ngit.dev/npub1owner/repo.git".to_owned(), "remote: ERR authorisation failed: No state events in purgatory\nfatal: ...", ), ], diff --git a/crates/workspace/src/views/inbox.rs b/crates/workspace/src/views/inbox.rs index 880a30f..fd062ad 100644 --- a/crates/workspace/src/views/inbox.rs +++ b/crates/workspace/src/views/inbox.rs @@ -403,7 +403,7 @@ impl InboxView { }; let root = item.root; - let kind = item.root_kind; + let kind = item.root_event.as_ref().map(|event| event.kind); let address = section.address.clone(); let first = entry_ix == 0; let last = entry_ix + 1 == section.entries.len(); diff --git a/docs/TODO.md b/docs/TODO.md index a12edc4..6bff069 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -1,7 +1,5 @@ # TODO -Deferred from `docs/over-engineering-cleanup-plan.md`. - ## `BackendEvent::SyncProgress` File: `crates/signed_state/src/backend.rs` diff --git a/docs/over-engineering-cleanup-plan.md b/docs/over-engineering-cleanup-plan.md deleted file mode 100644 index d15b11a..0000000 --- a/docs/over-engineering-cleanup-plan.md +++ /dev/null @@ -1,300 +0,0 @@ -# Over-engineering cleanup plan - -## Goal - -Remove code that exists but cannot be reached, and machinery that guards states no -caller can produce. Findings came from a four-way read-only audit of -`signed_state`, `workspace/views`, `signed_git`/`signed_core`, and -`dock`/`signed_ui`/misc, cross-checked against the pinned dependency sources in -`~/.cargo/git/checkouts/`. - -The main claim in each task below was verified by grepping callers, not by -reading the definition alone. Items that were only reported by the audit and not -independently reproduced are in Phase 4 and must be verified before deletion. - -## Rules for every step - -- Line numbers are from the current working tree and will drift. Re-grep before - editing; do not trust a number from this file after other tasks land. -- Delete code, do not comment it out, do not add `#[allow(dead_code)]`. -- If a symbol looks dead but is part of a public API or a feature that is only - not wired yet, stop and ask. -- Do not reintroduce the `views/repo` generation counters that were removed in - this working tree. -- Keep comments out. Remove any comment that describes the code being deleted. - -## Verification commands - -Always pass `--offline`; a plain `cargo` invocation re-resolves and fails in the -sandbox. - -``` -cargo fmt -p 2>/dev/null -cargo check --offline --workspace --all-targets -cargo clippy --offline --workspace --all-targets -cargo test --offline --workspace -``` - -`cargo fmt -- --check` prints unrelated "unstable features" noise on stable. -Filter with `grep -E "^Diff in"`. Never hand-reformat; rustfmt is authoritative. - ---- - -## Phase 1 - delete dead code - -No behavior change. Each task is independent; commit per crate. - -### 1.1 `RepoStore::version` - -File: `crates/signed_state/src/repo.rs` - -- [x] Remove field `version: u64` and its doc comment (the claim that views key - caches to it is false). -- [x] Remove initializers `version: 0` in both constructors. -- [x] Remove the bump `this.version = this.version.wrapping_add(1);`. -- [x] Remove `pub fn version(&self) -> u64`. - -Evidence: `grep -rn "\.version()" crates` has no call sites; the only read is the -accessor itself. - -Acceptance: `grep -rn "version" crates/signed_state/src/repo.rs` shows only -unrelated uses (none of the four removed sites). - -### 1.2 `clone_url` module - -Files: `crates/signed_core/src/clone_url.rs`, `crates/signed_core/src/lib.rs` - -- [x] Delete `clone_url.rs`. -- [x] Remove `mod clone_url;` and the `pub use clone_url::{CloneTarget, parse_clone_url};` - re-export. - -Evidence: only definition, its own test, and the re-export reference these. It -also reimplements percent-decoding. - -### 1.3 NIP-32 labels / cover-note helpers - -Files: `crates/signed_core/src/annotations.rs`, `crates/signed_core/src/filters.rs`, -`crates/signed_core/src/lib.rs` - -- [x] Verify each of `labels`, `subject_override`, `labels_and_subject`, - `cover_note`, `COVER_NOTE_KIND`, `annotations_for` for references outside - this crate. -- [x] Delete the ones with no production caller and drop them from the `lib.rs` - re-exports. -- [x] Keep anything still needed (for example `cover_note` / - `COVER_NOTE_KIND` may be used by the inbox view). - -Evidence: the UI uses `tags.hashtags()` directly in `views/discussion.rs`, not -these helpers. - -### 1.4 `Backend::emit_error` - -File: `crates/signed_state/src/backend.rs` - -- [x] Delete the method. No callers. - -### 1.5 `Backend::pushing_repos()` accessor - -File: `crates/signed_state/src/backend.rs` - -- [x] Delete the getter. The `Entity>` is used internally; only - the accessor is unused. -- [ ] Optional follow-up (separate task): nothing observes that entity, so it - could be a plain `HashSet` field. Defer; it is a refactor, not a deletion. - -### 1.6 `DropdownButton` speculative knobs - -File: `crates/signed_ui/src/dropdown_button.rs` - -- [x] Remove the `caret: Option` field and the `CaretBuilder` type - alias; it is never set, so the `unwrap_or_else` default always runs. - Inline the default caret. -- [x] Remove the `anchor()` builder method (never called; it is already marked - `#[allow(dead_code)]`). Keep the `anchor` field, which is set in the - constructor and used when rendering. - -### 1.7 `utils::shorten_pubkey` - -File: `crates/utils/src/pubkey.rs` - -- [x] Drop the `len` parameter; its only call site passes `4`. -- [x] Rename to a fixed-width helper if that reads better, or leave the name. - -It duplicates `signed_ui::middle_truncate` conceptually, but `utils` has no gpui -dependency, so do not move `middle_truncate`; just remove the speculative -parameter. - -### 1.8 Comment artifacts - -- [x] `crates/signed_state/src/repo.rs` - delete the comment that describes - querying cover notes and labels per root; no such query exists. -- [x] Remove any comment left dangling by the tasks above. - ---- - -## Phase 2 - remove guards that cannot fire - -Each changes behavior on paper but not in practice. Smoke test after each. - -### 2.1 `SignedDockSkin::render_dock` early return and frame duplication - -File: `crates/dock/src/dock_area.rs` - -Base (`gpui_base::dock`) computes `dock_extent`, returns before calling the -renderer when the extent is `px(0.)`, and wraps the renderer's output in -`dock_frame`. `dock_extent` is `px(0.)` exactly when `!open && !is_bottom`, which -is precisely the condition of the early return here. - -- [x] Delete the `if !open && !placement.is_bottom() { return div(); }` guard. -- [x] Stop re-applying the box. The chrome is now `.flex().size_full().relative()`, - the same shape the pinned reference skin (`crates/ui/src/dock/dock.rs`) uses; - base's `dock_frame` supplies the extent and the overflow clip. -- [x] Keep the closed-bottom strip height override. Note: base's `dock_frame` - hard-codes `CLOSED_BOTTOM_STRIP` (29px) with `overflow_hidden`, so the - `TAB_BAR_HEIGHT` (44px) override is clipped and has no visible effect. The - strip is 29px today; changing it needs an upstream change. -- [ ] Smoke test: open and close left, bottom, and right docks; check widths, - the bottom strip height, and resize handles. - -### 2.2 `push_staged_to_grasps` empty-refs guard - -File: `crates/signed_state/src/backend.rs` - -- [x] Delete the `if refs.is_empty() { return outcome; }` guard. - -All three call sites pass a non-empty `refs`: one passes a literal one-element -vec, one is inside `if !refs.is_empty()`, one is the `else` of that check. - -### 2.3 `InboxView` per-view debounce - -File: `crates/workspace/src/views/inbox.rs` - -The backend pump already coalesces relay bursts into one `NostrUpdate`, and -`query_inbox` reads only the local database. - -- [x] Delete the `REFRESH_DEBOUNCE` constant. -- [x] In `refresh`, drop the spawned timer; call `run_refresh` directly after - `refresh.request()` returns `Schedule`, matching `RepoStore::refresh`. -- [x] Keep `RefreshGate` for fold/overlap. -- [ ] Smoke test: inbox updates live as relay events land, with no added delay. - -### 2.4 `DockPlacement::Center` arms in `dock_toggle_button` (optional) - -File: `crates/dock/src/tab_panel.rs` - -- [x] The only call sites pass `Left`, `Bottom`, `Right`. The `designated` - match's `Center` arm now returns early instead of yielding a dead `None`; - the icon match keeps `Center => return None` for exhaustiveness. - -Low value; skip if it makes the match less readable. - ---- - -## Phase 3 - consolidate duplication (needs a decision) - -Verify the duplication before extracting; each could be intentional. - -### 3.1 `PullRequestsView` and `IssuesView` - -Files: `crates/workspace/src/views/pull_requests/mod.rs`, -`crates/workspace/src/views/issues/mod.rs` - -- [x] Confirm the shape. The two `rebuild`s are the same mechanic: one pass over a - root list, per-status counts, keep matching indices, early-return when - filter/indices/counts are unchanged, resize the item sizes, notify. -- [x] Extract `crates/workspace/src/views/status_list.rs` with `StatusCounts` and - `filter_by_status`. Both views now use it; the tuple counts were replaced by - `StatusCounts`. The notify decision stays local because it would need a trait - over the two filter enums. - -### 3.2 Relay URL normalize/display - -Files: `crates/workspace/src/views/sidebar/settings_dialog.rs`, -`crates/workspace/src/views/sidebar/grasp_servers.rs` - -- [x] Confirm both pairs do prepend-scheme, parse, dedupe, and host-display. -- [x] Extract `normalize_server` and `server_host` into `sidebar/mod.rs`. They live - in `workspace`, not `signed_ui`: `signed_ui` does not depend on `nostr`, and - these are used only by the two sidebar modules. Dedupe differs per caller - (`Vec` vs persisted `Vec`) and stays at the call site. - -### 3.3 `crates/dock` vs the pinned `gpui_component` dock renderer - -Files: `crates/dock/src/*` vs the pinned rev's `crates/ui/src/dock/*` - -- [x] Spike: `SignedTabGroupSkin` cannot delegate to the pinned upstream skin. - - `TabGroupSkin`, `TilesSkin` and `SkinShared` are `pub(crate)` in - `gpui_component::ui`; only the opaque `DockSkin` renderer is public, and it - holds that private shared state. - - `TabGroupRenderer`/`TilesRenderer` are all-or-nothing per method. The - Signed deltas (window controls, prev/next, plain-sidebar detection, i18n) - live *inside* `render_tab_bar` and `frame`. There is no hook below the whole - method, so "delegate and keep only the deltas" has no seam to hang on. - - Composing `Rc` would still leave `render_tab_bar` a near-full - reimplementation while adding a dependency on upstream internals, for no - line reduction. -- [x] Effort/risk: high effort, high churn, no achievable reduction on this rev. - `SignedTilesSkin` is the same shape. Recommend keeping the fork as-is. A - future upstream change (public `DockSkin` with per-part hooks) would be the - precondition for any delegation. - ---- - -## Phase 4 - triage flagged items - -Verify each, then delete or dismiss. These were reported by the audit but not -independently reproduced. - -- [ ] `signed_git/src/worktree.rs` - manual `refs/heads/{rev}` fallback; check - whether gix's ref DWIM already covers it. -- [ ] `signed_git/src/repo.rs` - `refs_with_prefix`; check - `repo.references()?.prefixed(prefix)`. -- [ ] `signed_git/src/patch.rs` - the mbox envelope is scanned twice; check - whether `patch_commits` can consume `split_patch_series` output, and - whether the hard-coded 40-hex checks should use `gix::ObjectId::from_hex`. -- [ ] `assets/src/lib.rs` - `themes` handles a `Cow::Owned` case the build - features cannot produce. -- [ ] `signed_state/src/backend.rs` - `GraspServerResult::git_url` populated but - never read. -- [ ] `signed_nostr/src/signer.rs` - `UniversalSignerError` vs - `nostr::Error::other`. Keep the `InnerSigner` erasure shim; only the error - wrapper is replaceable. -- [ ] `signed_git/src/history.rs` - `last_commit` referenced only from tests. -- [ ] `signed_git/src/repo.rs` - `init_repository` / `root_commit` 40-length - guards on an `ObjectId` string. -- [ ] `signed_core/src/model.rs` - `Upstream.relay_hint` parsed but unused in - production. -- [ ] `signed_core/src/inbox.rs` - `root_kind` duplicates `root_event`; confirm - before removing, it is read by the inbox view. - ---- - -## Do not touch - -- The two `pull_requests` generation counters (`load_generation` in - `detail.rs`, `compare_generation` in `new.rs`). Both were verified reachable. -- Tasks stored in a `Vec>` for lifetime cancellation. This is - intentional. -- `RefreshGate` on `CheckoutsStore`; its timer-driven debounce is load-bearing. -- The `dev`-time `init_dialog.rs` `.detach()`; the task owns a window-scoped - dialog and has no owning struct. - -## Acceptance criteria - -- Every removed symbol returns empty for a repo-wide grep. -- No new `#[allow(dead_code)]`. -- `cargo fmt -- --check` diff-free, `cargo check --offline --workspace - --all-targets` clean, `cargo clippy --offline --workspace --all-targets` - clean, `cargo test --offline --workspace` green. -- Manual smoke: open and close docks, watch the inbox update live, open a repo - and switch branches, publish a repo. - -## Suggested commit sequence - -1. `signed_state`: 1.1, 1.4, 1.5 (dead code), plus 1.8 comments in the same files. -2. `signed_core`: 1.2, 1.3 (dead modules). -3. `signed_ui` + `utils`: 1.6, 1.7. -4. `dock`: 2.1, 2.4. -5. `signed_state`: 2.2. -6. `workspace`: 2.3. -7. Phases 3 and 4 as separate, individually reviewed changes.