Files
signed/docs/PLAN.md
T
2026-09-04 16:02:51 +07:00

13 KiB
Raw Blame History

PLAN — Codebase audit: over-engineering, dead code & simplification

Status (2026-09-04): proposed. Full-repo audit (~27k lines, 13 crates). Every finding was cross-checked against consumers and verified against the locked library sources (gpui-component 18922d6, rust-nostr 472c883, gix 0.87.1). Pick the steps you want before processing; ordered safest-first. Estimated total: ~3,000+ lines removable (~12%).


Step 1 — Zero-risk deletions (~700 L, pure removals)

  1. Unused Cargo deps (verified by grep):
    • desktop: gpui_linux, gpui_windows, gpui_macos (only gpui_platform is used — it's the meta-crate that picks the backend), log
    • workspace: chrono
    • signed_nostr: signed_core, nostr
    • assets: log
  2. signed_core::clone_url module (whole file) — only self-tests reference it; repo_detail/mod.rs:2418 produces the format but never parses it. (clone_url.rs + lib.rs:13) <- acceptable
  3. signed_core::comments module (CommentThread, comment_threads) — no consumers; SDK has nip22::extract_parent anyway. (comments.rs + lib.rs:14)
  4. paths dead accessors: cache_dir(), logs_dir(), keymap_file(), set_custom_data_dir() + CUSTOM_DATA_DIR/OnceLock machinery (paths/src/lib.rs:13,50-61,112-169). Used: desktop_dir, documents_dir, settings_file, nostr_dir, repos_dir.
  5. signed_git::patch_applies (+test) — own TODO says superseded by the live compare view (lib.rs:252-277, 2294-2324).
  6. signed_git::worktree_tags — both UI sites call repo_tags directly (lib.rs:1811-1813).
  7. Unused re-exports: workspace::image_cache (workspace/src/lib.rs:6); signed_state::lib's pub use utils::shorten_pubkey (signed_state/src/lib.rs:21).
  8. Sidebar placeholder nav items "Inbox", "Search", "Guide" — all three just open the Explore panel (sidebar/mod.rs:578-618). <- acceptable
  9. import_dialog.rs — opens an empty 400px dialog; the "Import identity" sidebar button is a dead end (sidebar/mod.rs:405-408,493-504). <- acceptable
  10. All wasm32 cfg paths + nostr-memory dep — GPUI has no wasm backend; the target cannot link (signed_state/src/lib.rs:52-63, checkouts.rs:108-165, signed_nostr/src/backend.rs:9-29, signed_nostr/Cargo.toml). <- acceptable, note: GPUI have support for wasm via gpui_web, updated your memory or check before make changes
  11. dock::t() unreachable "Dock.Unnamed" arm (dock/src/lib.rs:21-30).
  12. Two handler-less Buttons ("user" with fake dropdown_caret, "maintainers") — render as plain h_flex or wire real menus (sidebar/mod.rs:426-434, repo_detail/mod.rs:2289-2307). <- acceptable
  13. Stale doc references to nonexistent helpers::track (diff.rs:328, mod.rs:201-203).

Step 2 — Dead feature removal (~500 L)

  1. Login/logout APIlogin, login_with_new_identity, login_with_nsec, login_with_bunker, logout, with_master_key (backend.rs:879-988,1537-1542). UI only uses create_identity + keyring restore. ⚠️ login_with_new_identity stores unencrypted nsec — security footgun. Keep extract_master_key + the bunker:// branch of restore_session (services older keyring entries). <- acceptable
  2. RepoStore::merge_pull_request + publish_applied_status — no merge action exists in the UI (repo.rs:1122-1256). <- acceptable
  3. RepoStore::publish_state — superseded by Backend::push_repo_from (repo.rs:1070-1120).
  4. Annotation machinerycover_note_of/labels_of/subject_of (no callers) + cover_notes/labels fields + per-root DB query loop + annotations_for relay fetch. Every kind-1624/1985 event currently triggers a full refresh of every open RepoStore for data nothing displays (repo.rs:52-56,339-362,489,531-563).
  5. BackendEvent::Error variant + emit_error + ~16 emission sites — matched nowhere (consumers only match SignerChanged, SignerRequired, PassphraseRequired, NostrUpdate, Synced, SyncProgress, Published) (backend.rs:64,67-74,1047-1050). <- acceptable
  6. BackendEvent::Connected + connected field + is_connected() — emitted twice, never consumed (backend.rs:49,83,154,1110-1111,1052-1055).
  7. sync_progress field + getter + SyncProgress::channel watch-loop math — payload discarded; used only as a dumb refresh tick (backend.rs:84,1057-1060,1227-1258). <- acceptable
  8. Backend::subscribe, add_discovery_relays, publish_announcement — zero callers (backend.rs:1123-1158,1388-1395).
  9. Write-only fields: RepoStore::refs (repo.rs:37-38,449), RepoListStore::set_author (repo_list.rs:135-140), Update::event_id (signed_nostr/src/update.rs:10).
  10. NIP-44 half of UniversalSigner (AsyncNip44 bounds, encrypt/decrypt plumbing, ~60 of 200 L) — app never touches DMs (signed_nostr/src/signer.rs). <- acceptable

Step 3 — Library swaps (~600 L)

  1. signed_ui::DropdownButtongpui_component::button::DropdownButton — present in the locked revision, same new/button/dropdown_menu surface (the local doc even says it matches). Migrate 3 call sites (pull_requests.rs, repo_detail/mod.rs), delete the ~200 L file. <- acceptable
  2. wire_number_inputSettingField::number_input (settings_dialog.rs:691-761, ~90 L) — you already import NumberFieldOptions from gpui-component's setting module. <- acceptable
  3. Custom C-unquoting → gix::quote::ansi_c::undo (signed_git/src/lib.rs:1610-1683 + call sites, ~100 L) — already in the dep tree, octal/escape semantics identical. Keep tests as regression tests.
  4. ensure_origin redundant refspec write (signed_git/src/lib.rs:523-530) — git remote add creates remote.origin.fetch by default.
  5. Two hand-rolled tab bars → gpui-component TabBar (repo_detail/mod.rs:2120-2265, new_pull_request.rs:1236-1305, ~110 L) — pull_request_detail.rs already uses TabBar correctly (proves the fit). <- acceptable
  6. CLI merge_basegix::Repository::merge_base (signed_git/src/lib.rs:201-222) — the codebase already uses the gix one in pull_request_detail.rs:244.
  7. image_cachegpui::retain_all or single-map LRU — vendored copy with a max_items param every call site passes MAX_IMAGES=128 to (signed_ui/src/image_cache.rs, 139 L). Delete the param + dual-structure LRU or use upstream unbounded cache.

Step 4 — Dedup passes (~1,200 L)

  1. PullRequestDetailView → hold Entity<DiffPane> instead of its ~200 L inline copy of diff.rs (pull_request_detail.rs:310-552). The other two consumers already do this.
  2. Issue/PR detail shared sections (~300 L): comments list, comment form, participants/labels sidebar, sidebar_title — extract into helpers.rs (issue_detail.rs vs pull_request_detail.rs).
  3. Dialog scaffolding helper (~150-200 L): 4 copies of {busy, error} state structs, verbatim error rows, identical cx.spawn → close_dialog / show error plumbing (onboarding_dialog.rs, passphrase_dialog.rs, create_repo_dialog.rs, init_dialog.rs, +2 more sites).
  4. Triplicated debounce state machine → one helperrefreshing / refresh_dirty / debouncing trio copied into repo.rs, checkouts.rs, repo_list.rs (~105 L).
  5. signed_git helpers: push_main/push_all twin bodies → one push_refspecs; git-CLI spawn boilerplate → one git_output; grasp→https rewrite → one fn; "try each mirror URL" loop → one helper.
  6. grasp-list parsing ×3 → one helperbackend.rs:996-1012, backend.rs:1586-1618, grasp_servers.rs:240-265.
  7. Smaller copies: ref-selector trigger ×2, count badge ×3 (use signed_ui::CountBadge), grasp-server editor duplicated in settings_dialog.rs:408-614 vs grasp_servers.rs (~90 L), folder-picker prompts ×4, "add panel to dock Center" ×10, fork-label upstream lookup ×2, avatar+name row ×9 (→ one user_row helper in signed_ui).
  8. Backend::send vs publish_event — copy-pasted bodies differing only in finalize_async (backend.rs:1293-1386).

Step 5 — Structural (do deliberately)

  1. UniversalSigner → enum — verified: nostr-sdk 0.45 Client has no signer slot (external SignerAuthenticator by value at build time), so a swap-in-place wrapper IS needed — but only Keys/NostrConnect ever occur. Replace 200-L vtable (InnerSigner trait + InnerSignerImpl<T> + custom error + boxed futures) with enum Signer { Keys(Keys), Connect(NostrConnect) } in Arc<RwLock<Signer>> (~40 L). <- acceptable
  2. GitStore single installsigned_state::init installs empty root (lib.rs:44), immediately replaced by desktop/main.rs:81. Pass the root into init.
  3. Error handling in signed_git: map_err(|e| anyhow!("{e}")) → plain ? (preserves source chain; lib.rs:1787,1798,1845,1852); blanket .ok()/.unwrap_or_default() → matched cases (lib.rs:919,995,1761,364,682,1779).
  4. ProfileStore second flume channel → WeakEntity + update() (~25 L; the results channel only exists to get back to main thread).
  5. Unbounded tasks Vec growth — only repo.rs prunes; backend.rs (~18 push sites), checkouts.rs (grows every 15-60 s poll cycle), profile.rs (2 per metadata event) accumulate finished handles forever. One-line retain(|t| !t.is_ready()) per store.
  6. Redundant observe → cx.notify() subscriptions (sidebar/mod.rs:81-88, repo_detail/mod.rs:354-355,1914-1921) — this gpui revision auto-tracks entities read during render, so re-render-only observers are belt-and-suspenders. ⚠️ Verify before deleting; see Step 7.2.
  7. open_upstream sleep-poll → cx.observe (repo_detail/mod.rs:1113-1152) — 60×250 ms race-prone loop re-implementing the store's notify mechanism.
  8. Checkouts map Arc removal — accessors deep-clone anyway (checkouts.rs:69-81).
  9. Minor: CheckoutRecord manual Default → derive; dock re-export trim (27 items, ~10 used); PixelAvatar.size field with no setter → const; SCAN_SKIPPED_DIRS 1-element array → direct compare; RepoAction menu indirection → on_click; Announcement::from_event round-trip after building from typed data.

Step 6 — Over-optimization (optional, unmeasured machinery)

  1. HeaderCache (repo_detail/mod.rs:113-123,1426-1461) — keyed invalidation + 3 Rc layers memoizing 2 bech32 encodes + 2 format!s (~45 L). Compute inline.
  2. Comment-body memoization contents: HashMap<EventId, SharedString> (issue_detail.rs, pull_request_detail.rs) — caches one small alloc per render; convert inline.
  3. OBJECT_CACHE_BYTES = 64 MiB indiscriminately applied — scope to history-walk entry points or drop (signed_git/src/lib.rs:770,859-863).
  4. file_commit(…, include_description: bool) — boolean flag saving one alloc; split into named constructors or always include.
  5. signed_core could drop its gpui dep if Announcement's SharedString fields became String (model.rs:9-35).
  6. build_state raw Tag::parse(...).expect() → typed Nip34Tag::to_tag (signed_core/src/state.rs:7-18).
  7. PixelAvatar FNV/RNG stack (~60 L) — deterministic-hashing requirement is legit; reconsider if DefaultHasher stability is acceptable.

Step 7 — Clean verdicts + one correctness note (no action)

  1. Confirmed clean, keep as-is: signed_core correctly uses Nip34Tag::parse / SDK builders (only the u tag is hand-parsed — SDK doesn't model it); dock crate is a genuine thin skin over gpui_base::dock renderer traits (keep; only trim re-exports); LMDB is justified (instant startup lists); signed_git test suite + tempfile healthy. Gossip/NIP-65 machinery serves only one login query — consider dropping if NIP-65 routing isn't on the roadmap.
  2. Correctness note: IssuesView / PullRequestsView / IssueDetailView / PullRequestDetailView never observe their RepoStore — refresh only works because this gpui revision auto-tracks render reads. Accidental; pinning a different gpui breaks them silently. Relevant to Step 5.6.

Suggested order of operations

  1. Step 1 + Step 2 first — pure deletions, verify with cargo check + cargo test.
  2. Step 3 (library swaps) next — mechanical, mostly call-site migrations.
  3. Step 4 (dedup) — largest line savings.
  4. Step 5 (structural) — deliberate, one at a time.
  5. Step 6 (over-optimization) — optional, only if you want the extra ~110 L.

Each step below is self-contained; tick what you want done.

  • Step 1 — zero-risk deletions
  • Step 2 — dead feature removal
  • Step 3 — library swaps
  • Step 4 — dedup passes
  • Step 5 — structural changes
  • Step 6 — over-optimization cleanup