.
Rust / build (macos-latest, stable) (push) Waiting to run
Rust / build (ubuntu-latest, stable) (push) Waiting to run
Rust / build (windows-latest, stable) (push) Waiting to run

This commit is contained in:
2026-09-13 17:26:07 +07:00
parent 4be75253cd
commit 534d572154
13 changed files with 54 additions and 428 deletions
+1 -6
View File
@@ -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()
+5 -10
View File
@@ -19,8 +19,6 @@ pub struct InboxItem {
pub root: EventId,
/// The root event itself, when it is known locally.
pub root_event: Option<Event>,
/// Kind of the root event, when it is known locally.
pub root_kind: Option<Kind>,
/// Repository the root belongs to, from the root's `a` tag.
pub address: Option<RepoAddr>,
/// Notification events directed at the user, newest first.
@@ -45,13 +43,11 @@ impl InboxItem {
}
pub fn kind(&self) -> Option<Kind> {
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()),
+2 -9
View File
@@ -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<RepoAddr>,
/// Relay hint for the upstream, if the `u` tag carries one.
pub relay_hint: Option<RelayUrl>,
}
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::<Coordinate>()
@@ -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"
-11
View File
@@ -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<Option<FileCommit>> {
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 -- <rel>` per path.
/// `rels` are paths relative to the worktree.
///
+2 -2
View File
@@ -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,
+3 -9
View File
@@ -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<Str
let mut index = repo.index_from_tree(&tree)?;
index.write(gix::index::write::Options::default())?;
let commit = commit.to_string();
if commit.len() != 40 {
bail!("unexpected initial commit id: {commit}");
}
Ok(commit)
Ok(commit.to_string())
}
/// The earliest unique commit of the repository at `repo_path`.
@@ -201,8 +196,7 @@ pub fn root_commit(repo_path: &Path) -> Result<Option<String>> {
{
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()));
}
}
+13 -7
View File
@@ -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()
+3 -13
View File
@@ -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<gix::Id<'a>> {
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.
+16 -39
View File
@@ -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<dyn Error + Send + Sync + 'static>);
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<E>(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<Box<dyn Future<Output = Result<PublicKey, UniversalSignerError>> + Send + '_>>;
) -> Pin<Box<dyn Future<Output = Result<PublicKey, SignerError>> + Send + '_>>;
fn sign_event_async(
&self,
unsigned: UnsignedEvent,
) -> Pin<Box<dyn Future<Output = Result<Event, UniversalSignerError>> + Send + '_>>;
) -> Pin<Box<dyn Future<Output = Result<Event, SignerError>> + Send + '_>>;
fn nip44_encrypt_async<'a>(
&'a self,
public_key: &'a PublicKey,
content: &'a str,
) -> Pin<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>>;
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + Send + 'a>>;
fn nip44_decrypt_async<'a>(
&'a self,
public_key: &'a PublicKey,
payload: &'a str,
) -> Pin<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>>;
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + Send + 'a>>;
}
#[derive(Debug)]
@@ -94,22 +71,22 @@ where
{
fn get_public_key_async(
&self,
) -> Pin<Box<dyn Future<Output = Result<PublicKey, UniversalSignerError>> + Send + '_>> {
) -> Pin<Box<dyn Future<Output = Result<PublicKey, SignerError>> + 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<Box<dyn Future<Output = Result<Event, UniversalSignerError>> + Send + '_>> {
) -> Pin<Box<dyn Future<Output = Result<Event, SignerError>> + 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<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>> {
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + 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<Box<dyn Future<Output = Result<String, UniversalSignerError>> + Send + 'a>> {
) -> Pin<Box<dyn Future<Output = Result<String, SignerError>> + 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,
+8 -19
View File
@@ -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<String>,
}
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<String>) -> Self {
fn failed(relay: RelayUrl, reason: impl Into<String>) -> 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: ...",
),
],
+1 -1
View File
@@ -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();
-2
View File
@@ -1,7 +1,5 @@
# TODO
Deferred from `docs/over-engineering-cleanup-plan.md`.
## `BackendEvent::SyncProgress`
File: `crates/signed_state/src/backend.rs`
-300
View File
@@ -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 <crate> 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<HashSet<RepoAddr>>` 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<CaretBuilder>` 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<RelayUrl>` vs persisted `Vec<String>`) 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<DockSkin>` 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<Task<..>>` 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.