Skip to content

Commit 59aa672

Browse files
committed
refactor: split App god-struct into domain sub-states
Moved 43 domain-owned fields off the App god-struct (down from 104 to 66 pub lines in src/app.rs) into six focused sub-structs. Each sub-state owns its fields and the helper methods that operate purely on them. App keeps thin shims where external call-sites used to dispatch through its public surface. New sub-states in src/app/: - tunnel_state.rs (TunnelState) list, form, active, form_baseline, pending_delete, summaries_cache - status_state.rs (StatusCenter) status, toast, toast_queue and the set_/tick_ helpers - snippet_state.rs (SnippetState) store, form, pending, output, param_form, pending_terminal, form_baseline, pending_delete - provider_state.rs (ProviderState) config, form, syncing, sync_done, sync_had_errors, pending_delete, sync_history, form_baseline - form_state.rs (FormState) host, host_baseline, bulk_tag_editor, bulk_tag_undo, pending_discard_confirm - host_state.rs (HostState) ssh_config, list, patterns, display_list, render_cache, undo_stack, multi_select, sort_mode, group_by, view_mode, group_filter, group_tab_index, group_tab_order, group_host_counts Access patterns: app.tunnels.*, app.status_center.*, app.snippets.*, app.providers.*, app.forms.*, app.hosts_state.* Pure move. No behaviour changes. No new features. Visual goldens (apart from version string and What's New overlay embedding the CHANGELOG) and Cargo.toml semver patch bump only. Bumps version to 2.43.1. Closes #36
1 parent 33fa045 commit 59aa672

64 files changed

Lines changed: 4124 additions & 2831 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 2.43.1 - 2026-04-18
2+
3+
- feat: Tighter foundations for faster, safer releases.
4+
- change: App state is split into six focused domains (hosts, tunnels, snippets, providers, forms, status) so changes land in one place and are easier to reason about.
5+
- change: New behaviour tests cover status routing, toast expiry and provider ordering, catching regressions earlier.
6+
- change: Fewer surprise file reads at startup. Provider state loads explicitly in one place, so tests and tools run predictably.
7+
18
## 2.43.0 - 2026-04-18
29

310
- feat: Confident edits on every host, shared lines included.

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "purple-ssh"
3-
version = "2.43.0"
3+
version = "2.43.1"
44
edition = "2024"
55
description = "Open-source terminal SSH manager and SSH config editor. Search hundreds of hosts, sync from 16 clouds, transfer files, manage Docker and Podman over SSH, sign short-lived Vault SSH certs and expose an MCP server for AI agents. Rust TUI, MIT licensed."
66
license = "MIT"

llms.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ A: Press m in the host list to open the theme picker with live preview. 11 built
407407

408408
## Status
409409

410-
- Current version: 2.43.0 (April 2026)
410+
- Current version: 2.43.1 (April 2026)
411411
- Release cadence: approximately bi-weekly
412412
- Test suite: 6500+ tests (unit, integration, property-based, HTTP mocking and OpenSSH ground-truth cross-validation)
413413
- CI: fmt, clippy, build, test on macOS and Linux, cargo-deny, MSRV 1.86 check, rustdoc warnings, site sync, TUI smoke test, design system, message centralization, keybinding invariants and visual regression

scripts/check-design-system.sh

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,30 @@ if grep -rn 'super::footer_action\|super::footer_key_span' src/ui/ \
3131
fi
3232

3333
# 3. No old notification API outside method definitions and delegations.
34+
#
35+
# Exclusions:
36+
# - `src/app/status_state.rs` is the defining module, so its own inline
37+
# `#[cfg(test)] mod tests` may call its deprecated `set_*` methods to
38+
# verify their behaviour. The deprecation is already signalled by the
39+
# `#[deprecated]` attribute on the definition site.
40+
# - `src/app.rs` dispatches through `status_center.set_*` shims; the
41+
# regex is anchored to that file so the exception cannot leak.
3442
if grep -rn 'set_status\|set_background_status\|set_sticky_status\|set_info_status' \
3543
src/ --include='*.rs' \
3644
| grep -v 'tests\.rs' | grep -v 'test_' | grep -v '#\[deprecated' \
3745
| grep -v 'pub fn ' | grep -v 'pub use ' \
38-
| grep -v 'self\.set_' | grep -v '// ' | grep -v '/// ' \
46+
| grep -v 'self\.set_' | grep -Ev '^src/app\.rs:[0-9]+:.*status_center\.set_' \
47+
| grep -v '^src/app/status_state\.rs:' \
48+
| grep -v '// ' | grep -v '/// ' \
3949
| grep -q .; then
4050
echo "ERROR: Old notification API used. Use app.notify/notify_error/etc."
4151
grep -rn 'set_status\|set_background_status\|set_sticky_status\|set_info_status' \
4252
src/ --include='*.rs' \
4353
| grep -v 'tests\.rs' | grep -v 'test_' | grep -v '#\[deprecated' \
4454
| grep -v 'pub fn ' | grep -v 'pub use ' \
45-
| grep -v 'self\.set_' | grep -v '// ' | grep -v '/// '
55+
| grep -v 'self\.set_' | grep -Ev '^src/app\.rs:[0-9]+:.*status_center\.set_' \
56+
| grep -v '^src/app/status_state\.rs:' \
57+
| grep -v '// ' | grep -v '/// '
4658
exit 1
4759
fi
4860

site/page.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"url": "https://getpurple.sh",
4242
"downloadUrl": "https://getpurple.sh",
4343
"installUrl": "https://github.com/erickochen/purple/releases",
44-
"softwareVersion": "2.43.0",
44+
"softwareVersion": "2.43.1",
4545
"datePublished": "2024-10-01",
4646
"dateModified": "2026-04-18",
4747
"softwareRequirements": "macOS or Linux",
@@ -778,7 +778,7 @@ <h2 class="tagline">Open-source terminal SSH manager and SSH config editor for m
778778
<button class="copy-btn copy-inline" id="copy-btn" onclick="copy(this)" style="display:none">copy</button>
779779
</div>
780780
<div class="install-output" id="install-output" style="display:none">
781-
<div>Downloading purple v2.43.0 for darwin-arm64...</div>
781+
<div>Downloading purple v2.43.1 for darwin-arm64...</div>
782782
<div>Installing to /usr/local/bin/purple... <span class="success">done.</span></div>
783783
</div>
784784
<div class="alt-installs" id="alt-installs" style="display:none">
@@ -900,7 +900,7 @@ <h2 class="tagline">Open-source terminal SSH manager and SSH config editor for m
900900
<summary>How do I troubleshoot connection problems?</summary>
901901
<div class="answer">Run with <code>--verbose</code> to enable debug logging, then <code>purple logs --tail</code> in another terminal. Logs are written to <code>~/.purple/purple.log</code> with fault domain prefixes: <code>[external]</code> for remote/tool errors, <code>[config]</code> for local config issues. Set <code>PURPLE_LOG=trace</code> for maximum detail.</div>
902902
</details>
903-
<div class="man-foot"><span>purple v2.43.0</span><span>2026-04-18</span><span>PURPLE(1)</span></div>
903+
<div class="man-foot"><span>purple v2.43.1</span><span>2026-04-18</span><span>PURPLE(1)</span></div>
904904
</div>
905905
</div>
906906

site/worker.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ const LANDING_PAGE = `<!DOCTYPE html>
177177
"url": "https://getpurple.sh",
178178
"downloadUrl": "https://getpurple.sh",
179179
"installUrl": "https://github.com/erickochen/purple/releases",
180-
"softwareVersion": "2.43.0",
180+
"softwareVersion": "2.43.1",
181181
"datePublished": "2024-10-01",
182182
"dateModified": "2026-04-18",
183183
"softwareRequirements": "macOS or Linux",
@@ -914,7 +914,7 @@ footer .sep { margin: 0 0.3em; }
914914
<button class="copy-btn copy-inline" id="copy-btn" onclick="copy(this)" style="display:none">copy</button>
915915
</div>
916916
<div class="install-output" id="install-output" style="display:none">
917-
<div>Downloading purple v2.43.0 for darwin-arm64...</div>
917+
<div>Downloading purple v2.43.1 for darwin-arm64...</div>
918918
<div>Installing to /usr/local/bin/purple... <span class="success">done.</span></div>
919919
</div>
920920
<div class="alt-installs" id="alt-installs" style="display:none">
@@ -1036,7 +1036,7 @@ footer .sep { margin: 0 0.3em; }
10361036
<summary>How do I troubleshoot connection problems?</summary>
10371037
<div class="answer">Run with <code>--verbose</code> to enable debug logging, then <code>purple logs --tail</code> in another terminal. Logs are written to <code>~/.purple/purple.log</code> with fault domain prefixes: <code>[external]</code> for remote/tool errors, <code>[config]</code> for local config issues. Set <code>PURPLE_LOG=trace</code> for maximum detail.</div>
10381038
</details>
1039-
<div class="man-foot"><span>purple v2.43.0</span><span>2026-04-18</span><span>PURPLE(1)</span></div>
1039+
<div class="man-foot"><span>purple v2.43.1</span><span>2026-04-18</span><span>PURPLE(1)</span></div>
10401040
</div>
10411041
</div>
10421042
@@ -1500,7 +1500,7 @@ A: Press m in the host list to open the theme picker with live preview. 11 built
15001500
15011501
## Status
15021502
1503-
- Current version: 2.43.0 (April 2026)
1503+
- Current version: 2.43.1 (April 2026)
15041504
- Release cadence: approximately bi-weekly
15051505
- Test suite: 6500+ tests (unit, integration, property-based, HTTP mocking and OpenSSH ground-truth cross-validation)
15061506
- CI: fmt, clippy, build, test on macOS and Linux, cargo-deny, MSRV 1.86 check, rustdoc warnings, site sync, TUI smoke test, design system, message centralization, keybinding invariants and visual regression

src/animation.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ impl AnimationState {
163163
// Detail panel toggle
164164
if app.detail_toggle_pending {
165165
app.detail_toggle_pending = false;
166-
let opening = app.view_mode == crate::app::ViewMode::Detailed;
166+
let opening = app.hosts_state.view_mode == crate::app::ViewMode::Detailed;
167167
let start_progress =
168168
self.detail_anim_progress()
169169
.unwrap_or(if opening { 0.0 } else { 1.0 });
@@ -262,7 +262,7 @@ mod tests {
262262
let mut app = make_app();
263263
let mut anim = AnimationState::new();
264264
app.detail_toggle_pending = true;
265-
app.view_mode = crate::app::ViewMode::Detailed;
265+
app.hosts_state.view_mode = crate::app::ViewMode::Detailed;
266266
anim.detect_transitions(&mut app);
267267
assert!(anim.is_animating(&app));
268268
}
@@ -466,7 +466,7 @@ mod tests {
466466
let mut app = make_app();
467467
let mut anim = AnimationState::new();
468468
app.detail_toggle_pending = true;
469-
app.view_mode = crate::app::ViewMode::Detailed;
469+
app.hosts_state.view_mode = crate::app::ViewMode::Detailed;
470470
anim.detect_transitions(&mut app);
471471
assert!(!app.detail_toggle_pending);
472472
assert!(anim.detail_anim.is_some());
@@ -477,7 +477,7 @@ mod tests {
477477
let mut app = make_app();
478478
let mut anim = AnimationState::new();
479479
app.detail_toggle_pending = true;
480-
app.view_mode = crate::app::ViewMode::Compact;
480+
app.hosts_state.view_mode = crate::app::ViewMode::Compact;
481481
anim.detect_transitions(&mut app);
482482
assert!(anim.detail_anim.is_some());
483483
}
@@ -487,7 +487,7 @@ mod tests {
487487
let mut app = make_app();
488488
let mut anim = AnimationState::new();
489489
app.detail_toggle_pending = true;
490-
app.view_mode = crate::app::ViewMode::Detailed;
490+
app.hosts_state.view_mode = crate::app::ViewMode::Detailed;
491491
anim.detect_transitions(&mut app);
492492
let p = anim.detail_anim_progress();
493493
assert!(p.is_some());
@@ -505,7 +505,7 @@ mod tests {
505505
let mut app = make_app();
506506
let mut anim = AnimationState::new();
507507
app.detail_toggle_pending = true;
508-
app.view_mode = crate::app::ViewMode::Detailed;
508+
app.hosts_state.view_mode = crate::app::ViewMode::Detailed;
509509
anim.detect_transitions(&mut app);
510510
anim.detail_anim.as_mut().unwrap().start =
511511
Instant::now() - std::time::Duration::from_millis(300);
@@ -518,12 +518,12 @@ mod tests {
518518
let mut app = make_app();
519519
let mut anim = AnimationState::new();
520520
app.detail_toggle_pending = true;
521-
app.view_mode = crate::app::ViewMode::Detailed;
521+
app.hosts_state.view_mode = crate::app::ViewMode::Detailed;
522522
anim.detect_transitions(&mut app);
523523
let _ = anim.detail_anim_progress();
524524

525525
app.detail_toggle_pending = true;
526-
app.view_mode = crate::app::ViewMode::Compact;
526+
app.hosts_state.view_mode = crate::app::ViewMode::Compact;
527527
anim.detect_transitions(&mut app);
528528
assert!(anim.detail_anim.is_some());
529529
assert!(!anim.detail_anim.as_ref().unwrap().opening);
@@ -534,7 +534,7 @@ mod tests {
534534
let mut app = make_app();
535535
let mut anim = AnimationState::new();
536536
app.detail_toggle_pending = true;
537-
app.view_mode = crate::app::ViewMode::Detailed;
537+
app.hosts_state.view_mode = crate::app::ViewMode::Detailed;
538538
app.screen = Screen::Help {
539539
return_screen: Box::new(Screen::HostList),
540540
};

0 commit comments

Comments
 (0)