Conversation
Centralize validation for SECONDARY_PATH and SECONDARY_LOG_PATH so all entrypoints enforce the same absolute-local-path rules. Reject remote/UNC-style secondary paths during config loading, keep SECONDARY_LOG_PATH optional, and update the CLI installer to retry on invalid secondary path input instead of aborting. Add coverage for config parsing, migration, installer, runtime validation, and TUI flows.
Refactor new-install to use a shared reset plan and a single source of truth for preserved entries (build/env/identity). Route --new-install --cli through CLI confirmation only, keep TUI confirmation as a pure adapter, and propagate TUI runner errors instead of swallowing them. Update related help/log messaging and add tests for new-install planning, CLI confirm behavior, TUI confirm rendering/error handling, and reset/preserve consistency.
Introduce a shared decision flow for pre-existing backup.env with four explicit actions: Overwrite, Edit existing, Keep existing & continue, and Cancel. Update CLI prompts to support all modes (including Edit existing and explicit Cancel), update TUI action mapping to the same semantics, and treat “keep existing” as continue (not abort). Ensure TUI post-config steps are skipped consistently when configuration wizard is skipped (AGE setup, post-install audit, Telegram pairing), while finalization steps still run. Propagate CheckExistingConfig runner errors instead of swallowing them. Add/adjust unit tests for decision resolution, CLI prompts, TUI actions, runner error propagation, and prepareBaseTemplate behavior. Update INSTALL and CLI_REFERENCE docs to match the new aligned behavior.
Tighten AGE setup consistency after the shared CLI/TUI refactor. Reuse a shared private-key validator so the TUI rejects malformed AGE identities before they reach the orchestrator, eliminating silent retry loops. Extend the AGE setup workflow to return explicit outcome details (recipient path, wrote file vs reused existing recipients) and update install TUI messaging to report “saved” only on real writes, while showing reuse clearly when existing recipient configuration is kept. Add regression coverage for private-key validation, reuse-vs-write setup results, and the updated TUI wizard behavior.
Introduce a shared Telegram setup bootstrap so CLI and TUI use the same eligibility rules before showing pairing steps. Stop the TUI from falling back to raw backup.env parsing, skip Telegram setup consistently when config loading fails, personal mode is selected, or no Server ID is available, and centralize skip-reason logging in the command layer. Update the TUI install flow to log shared Telegram bootstrap outcomes, add dedicated tests for bootstrap/CLI/TUI behavior, align user-facing docs, and remove now-unreachable TUI branches left over from the old local decision logic.
Restore consistent decrypt prompt behavior between CLI and TUI by treating "0" as an explicit abort in both flows. Update the TUI decrypt secret prompt to advertise the exit semantics clearly, return ErrDecryptAborted on zero input, and keep Cancel as an equivalent exit path. Adjust TUI simulation coverage so the shared decrypt workflow no longer carries a UI-specific semantic drift on secret entry.
Add deterministic end-to-end smoke tests for RunDecryptWorkflowTUI so the real decrypt TUI production path is covered from entrypoint through source selection, candidate selection, secret prompt, destination prompt, and final bundle creation. Introduce test-only helpers for a real AGE-encrypted raw-backup fixture, serialized TUI simulation across multi-screen workflows, bundle-content inspection, and guarded workflow execution. Verify both the success path (including final *.decrypted.bundle.tar contents, metadata, and checksum) and clean abort at the decrypt secret prompt, without changing production behavior.
Introduce a shared env-template helper for secondary storage state and use it from both installer flows so disabling secondary storage always writes the same canonical config: SECONDARY_ENABLED=false, SECONDARY_PATH=, and SECONDARY_LOG_PATH=. This removes the previous TUI-only drift where editing an existing backup.env could leave stale secondary paths after the user disabled the feature. Add focused unit coverage for the shared helper plus CLI and TUI regression tests covering disabled state and clearing of pre-existing secondary values, and clarify the installer docs to note that disabling secondary storage clears the saved secondary paths.
Introduce shared cron parsing/normalization for install workflows and align CLI with the existing TUI cron capability. Add a neutral internal cron helper package, collect cron time during the CLI install wizard, propagate an explicit CronSchedule through the CLI install result, and make install-time cron finalization honor wizard-selected/default cron values instead of falling back to env overrides after a normal wizard run. Keep skip-config-wizard and upgrade flows on their existing env/default behavior, update the TUI wizard to reuse the same cron validation logic, add regression coverage for shared cron parsing, CLI prompt/result propagation, and install schedule precedence, and update install/CLI docs to reflect cron selection in both modes.
Close the remaining cron-install test gaps after aligning CLI and TUI scheduling behavior. Add a TUI wizard regression test that proves blank cron input resolves to the installer default (02:00) even when CRON_SCHEDULE is set in the environment, and add a CLI wizard regression test that aborting exactly at the cron prompt propagates the interactive abort and leaves backup.env unwritten. Introduce minimal test seams for the install wizard runner and cron prompt boundary to exercise the real command/wizard paths without changing production semantics.
Reduces flakiness in the decrypt TUI end-to-end tests when run with coverage enabled or under package-level load. - increases simulated input delays - extends end-to-end test timeouts and contexts - avoids false negatives without changing production code
Avoid nil-pointer panics in runInstallTUI when the bootstrap logger is not provided. Guard the AGE encryption success-path Info logs and the configuration-saved Debug log with bootstrap nil checks, preserving existing behavior when bootstrap is available.
Prevent nil-pointer panics in the newkey flow by routing final success messages through a shared helper. When a bootstrap logger is available, keep using bootstrap.Info; otherwise fall back to stdout so both CLI and TUI paths remain safe and user-visible. Also add targeted tests for bootstrap and nil-bootstrap cases.
Prevent decrypt path conflict prompts from accepting the same destination path again. Add shared validation that rejects empty or normalized-equivalent paths to the existing target, apply it in both TUI and CLI flows, and update tests to cover valid edits plus normalized-path rejection and retry behavior.
Replace the single-case select in printNetworkRollbackCountdown with a direct receive from ticker.C. The loop behavior remains unchanged: the countdown still updates on each tick and keeps the explicit continue for clarity.
Load the existing config in --newkey so AGE_RECIPIENT_FILE is preserved when configured, instead of always forcing the default recipient path. Also update the success message to report the effective recipient file, add tests for custom/default paths and invalid configs, and align the CLI docs.
Add targeted tests for the TUI wizards to cover the remaining uncovered branches. - cover nil and whitespace-only preserved entries in new install tests - cover Telegram setup bootstrap error propagation - verify persisted identity rendering in the Server ID panel - verify truncation of long registration failure messages - verify ESC behavior after successful verification - exercise default async wrappers used by the Telegram setup wizard LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add missing unit tests for new install and Telegram setup flows, covering edge cases, UI state rendering, error propagation, ESC handling, and async helpers. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Refactor telegram_setup_tui_test to remove hardcoded layout index assumptions when extracting Telegram setup views. The helper now finds the Pages container and the relevant widgets by type and title instead of relying on UI item ordering. The default wrapper test also replaces sleep-based update timing with direct QueueUpdateDraw synchronization, keeping only a watchdog timeout to avoid hangs. LiveReview Pre-Commit Check: skipped (iter:3, coverage:0%)
Fix the used space calculation in the local and secondary storage backends by using Blocks-Bfree instead of Total-Bavail, so reserved blocks are no longer reported as "used". Centralize statfs space conversion in a shared helper and apply the same semantics to the PVE collector. Also propagate UsedSpace through the orchestrator and notifications, removing the recomputation from free/total so displayed usage values and percentages match actual filesystem usage. Update and strengthen tests for Bavail != Bfree cases and for end-to-end propagation of the corrected values. LiveReview Pre-Commit Check: skipped (iter:2, coverage:73%)
…aths Prevent duplicate Close() calls in createBundle() and extractRegularFile() while preserving explicit close error handling. The patch replaces bare deferred closes with guarded defers so resources are only closed if they were not already closed explicitly, and it keeps the explicit Close() calls where finalization order and returned errors still matter. Test coverage was also improved to verify that output files are correctly closed in both success and error paths for bundle creation and regular file extraction. LiveReview Pre-Commit Check: skipped (iter:2, coverage:100%)
Prevent data loss when replacing the AGE recipient file by changing the backup logic to copy the existing file into a .bak-* backup instead of renaming or deleting the active file. The new recipient file is now written atomically through a temporary file and rename, and the workflow creates the backup only at commit time, after the new recipients have been collected. Backup failures are now propagated instead of being logged and ignored, so aborts and write errors leave the current recipient file intact. Regression tests were added to cover abort, backup failure, write failure, and successful overwrite paths. LiveReview Pre-Commit Check: skipped (iter:3, coverage:79%)
extractRegularFile now writes regular-file restores to a sibling temp file and renames it into place only after content copy and close succeed. This prevents truncated archive entries from clobbering an existing target or leaving partial files behind on restore failure. Also improves temp-file handling by reducing name collision risk with an atomic sequence, surfacing deferred close errors, and logging unexpected temp cleanup failures. Tests were added/updated to verify success cleanup, failed-copy cleanup, and preservation of a pre-existing target on copy errors.` LiveReview Pre-Commit Check: skipped (iter:2, coverage:48%)
Align the notification configuration with the shipped template and documentation by making EMAIL_ENABLED default to false when unset. Restore compatibility with legacy *_ENABLE notification flags during runtime loading and legacy env migration, add the missing Gotify environment overrides, and update tests and docs to reflect the corrected behavior. LiveReview Pre-Commit Check: skipped (iter:2, coverage:67%)
Replace the shift-based exponential backoff used by cloud remote checks and upload retries with a bounded retry schedule. This keeps the current retry timings for normal cases (2s, 4s, 8s, 16s) while capping larger attempts at 30s and removing the overflow-prone `1<<attempt` pattern. Also add regression coverage for large attempts and for the actual sleep durations used by cloud retry paths. LiveReview Pre-Commit Check: skipped (iter:2, coverage:83%)
Use os.Lstat in verifyBinaryIntegrity to validate the executable path before opening it, so symlinked executables are rejected correctly. Open the file only when computing the checksum, removing the unused rewind path and making the integrity check flow clearer. Update the related tests to cover the symlink case and the stat failure path. LiveReview Pre-Commit Check: skipped (iter:2, coverage:6%)
Ensure GFS retention consistently normalizes RETENTION_DAILY<=0 to an effective daily minimum of 1 across classification, storage backends, and runtime summaries. This prevents current-period backups from being misclassified or deleted when daily retention is unset/zero, aligns internal comments and tests with the documented contract, and separates pure GFS config normalization from the logging wrapper used in operational paths. LiveReview Pre-Commit Check: skipped (iter:2, coverage:67%)
Propagate context-aware cancellation through notification retry and diagnostic flows. Switch email mail-log inspection helpers to use context and CommandContext, replace blocking sleeps with a context-aware wait helper, and stop relay/webhook retry loops immediately when the context is canceled. Add focused tests covering cancellation behavior. LiveReview Pre-Commit Check: ran (iter:1, coverage:0%)
Normalize bundle path handling when BundleAssociatedFiles is enabled so storage backends accept either the raw archive path or an already bundled path without deriving *.bundle.tar.bundle.tar. Reuse a shared helper for canonical bundle naming, add regression tests for double-suffix decoys, and log unexpected bundle stat errors while still ignoring missing optional bundle files. LiveReview Pre-Commit Check: skipped (iter:2, coverage:85%)
Use file-handle metadata operations during restore extraction so FakeFS-backed tests and non-host paths do not fail on chmod/chown. Normalize FakeFS temp-path handling for already-resolved sandbox paths, add focused regression coverage for full and selective archive extraction, and keep the restore workflow tests green for fallback, SAFE cluster export, PBS staged restore, and fstab abort flows. LiveReview Pre-Commit Check: skipped (iter:2, coverage:49%)
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
internal/environment/detect_additional_test.go (1)
266-278:⚠️ Potential issue | 🟡 MinorAdd
t.Setenvfor test isolation consistency.Unlike
TestExtendPath, this test doesn't set a known initial PATH before callingextendPath(). This means:
- The test depends on whatever PATH exists in the environment
- PATH modifications from
extendPath()persist after the test (no automatic cleanup)For consistency with the migration pattern and proper test isolation:
Proposed fix
// TestExtendPathIdempotent tests that extendPath doesn't duplicate paths func TestExtendPathIdempotent(t *testing.T) { + t.Setenv("PATH", "/usr/local/bin") + // Call extendPath twice extendPath() pathAfterFirst := os.Getenv("PATH")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/environment/detect_additional_test.go` around lines 266 - 278, TestExtendPathIdempotent relies on the real environment PATH and leaves changes behind; update the test to use t.Setenv to set a known initial PATH before invoking extendPath so the test is isolated and changes are automatically reverted, then call extendPath twice and compare os.Getenv("PATH") values as before; locate TestExtendPathIdempotent and add a t.Setenv("PATH", "<known-value>") (e.g., an empty or minimal PATH string) at the start to ensure determinism and cleanup.internal/tui/wizard/install.go (1)
657-658:⚠️ Potential issue | 🟠 MajorReject non-regular config paths before showing this prompt.
This only checks that
os.Statsucceeds. A directory/FIFO/socket will still enter the TUI flow here, while the CLI path rejects non-regular files up front incmd/proxsave/install_existing_config.go, Lines 28-38. That breaks the new CLI/TUI alignment and defers the failure until later install steps.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/tui/wizard/install.go` around lines 657 - 658, The CheckExistingConfig function currently only verifies that os.Stat succeeds and therefore allows non-regular paths (directories, FIFOs, sockets) into the TUI flow; update CheckExistingConfig to call os.Stat to obtain FileInfo and reject any path where fi.Mode().IsRegular() is false (return the same error/ExistingConfigAction used by the CLI flow), so non-regular config paths are rejected before showing the prompt; reference the CheckExistingConfig function and ensure the returned error behavior matches the existing CLI check for consistency.
🧹 Nitpick comments (8)
cmd/proxsave/main.go (1)
1569-1570: Thecontinuestatement is redundant.Since
<-ticker.Cis the last statement in the loop body, thecontinuehas no effect and can be removed for cleaner code.♻️ Suggested simplification
- <-ticker.C - continue + <-ticker.C🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/proxsave/main.go` around lines 1569 - 1570, Remove the redundant `continue` that follows the `<-ticker.C` receive; since `<-ticker.C` is the last statement in the loop body, the `continue` has no effect—delete the `continue` line (near the `ticker` receive) so the loop behavior remains unchanged and the code is cleaner.internal/tui/screen_test.go (1)
11-25: Consider extending helper to handle additional tview primitives.The
primitiveContainsTexthelper currently only handles*tview.TextViewand*tview.Flex. While this is sufficient for the current tests, you may want to add support for*tview.Boxor use a more general approach if future tests need broader coverage.♻️ Optional: Add fallback for other primitive types
func primitiveContainsText(p tview.Primitive, want string) bool { switch v := p.(type) { case nil: return false case *tview.TextView: return strings.Contains(v.GetText(false), want) case *tview.Flex: for i := 0; i < v.GetItemCount(); i++ { if primitiveContainsText(v.GetItem(i), want) { return true } } + case *tview.Grid: + for row := 0; row < v.GetRowCount(); row++ { + for col := 0; col < v.GetColumnCount(); col++ { + if primitiveContainsText(v.GetItem(row, col), want) { + return true + } + } + } } return false }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/tui/screen_test.go` around lines 11 - 25, The helper primitiveContainsText only inspects *tview.TextView and *tview.Flex, so extend it to also check other relevant primitives (e.g., *tview.Box, *tview.Form, or any primitive exposing text) or add a generic fallback: detect and extract visible text where possible (for example use v.GetText for Box-like types or iterate contained items for containers), and ensure primitiveContainsText calls that extraction for types beyond *tview.TextView/*tview.Flex so tests that render text in other primitives succeed; update the type switch in primitiveContainsText to include cases for *tview.Box and any other concrete primitives you expect, plus a default/fallback branch that attempts a safe string probe.internal/tui/abort_context_test.go (1)
129-152: These stop-path tests don't prove the app actually kept running.Lines 137-138 and 150-151 only assert the final error after a delayed
Stop(). An implementation that returnsnilimmediately would still satisfy both tests, so they don't really verify "runs until stopped". Consider synchronizing on a started signal, or asserting thatRunWithContextstays blocked until the goroutine callsStop().🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/tui/abort_context_test.go` around lines 129 - 152, The tests currently only check the final error and can pass if RunWithContext returns immediately; update both TestAppRunWithContext_NilContextRunsUntilStopped and TestAppRunWithContext_ReturnsNilWhenStoppedWithoutCancellation to synchronize on the app actually starting before calling app.Stop() — e.g., have newSimulationApp provide a started channel or WaitGroup signal the test can wait on (or otherwise expose a Started() hook), then launch the goroutine to sleep and call app.Stop() only after that start signal, and finally assert that app.RunWithContext(nil) / app.RunWithContext(context.Background()) remains blocked until Stop is invoked and returns nil.internal/orchestrator/workflow_ui_cli_test.go (1)
13-40: MakecaptureCLIStdoutfailure-safe.If
fn()panics or callst.Fatal, Lines 35-39 never execute. That leavesos.Stdoutredirected and theio.Copygoroutine blocked on the still-open pipe, which can bleed into later tests.♻️ Suggested change
func captureCLIStdout(t *testing.T, fn func()) string { t.Helper() oldStdout := os.Stdout r, w, err := os.Pipe() if err != nil { t.Fatalf("os.Pipe: %v", err) } - os.Stdout = w - t.Cleanup(func() { - os.Stdout = oldStdout - }) var buf bytes.Buffer done := make(chan struct{}) go func() { _, _ = io.Copy(&buf, r) close(done) }() + + os.Stdout = w + defer func() { + os.Stdout = oldStdout + _ = w.Close() + <-done + _ = r.Close() + }() fn() - _ = w.Close() - <-done - _ = r.Close() - - os.Stdout = oldStdout return buf.String() }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/orchestrator/workflow_ui_cli_test.go` around lines 13 - 40, The captureCLIStdout helper currently restores stdout and closes the pipe only after fn() returns, so if fn() panics or calls t.Fatal the cleanup never runs; fix captureCLIStdout by adding a defer immediately after creating r,w and starting the io.Copy goroutine that 1) closes w, 2) waits for the goroutine via <-done, and 3) closes r (and keep the existing t.Cleanup for restoring os.Stdout); update the function captureCLIStdout to use that defer (referencing r, w, done) so stdout is restored and the io.Copy goroutine can always exit even when fn() fails.internal/config/validation_secondary_test.go (1)
24-37: Consider addingt.Parallel()to subtests for faster test execution.The parent tests call
t.Parallel(), but the subtests don't. Adding parallelization to subtests can improve test execution time.♻️ Optional: Add t.Parallel() to subtests
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + t.Parallel() err := ValidateRequiredSecondaryPath(tt.path)Apply similar change to the other two test functions.
Also applies to: 55-68, 85-98
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/config/validation_secondary_test.go` around lines 24 - 37, Add t.Parallel() to each subtest goroutine so they run concurrently: inside the t.Run anonymous func that calls ValidateRequiredSecondaryPath (the subtests iterating over tests), insert t.Parallel() as the first statement. Apply the same change to the other two similar test functions in this file that use t.Run (the subtests around lines referenced in the comment) so all subtests run in parallel.internal/orchestrator/workflow_ui_tui_decrypt_prompts.go (1)
124-124: Minor: Doublefilepath.Cleanon new path.
promptNewPathInputTUIappliesfilepath.Cleanat line 124, and the callerpromptExistingPathDecisionTUIapplies it again at line 74. While this is harmless (idempotent), you could consider removing one for clarity.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go` at line 124, The code calls filepath.Clean twice for the same path (once in promptNewPathInputTUI and again in promptExistingPathDecisionTUI); remove the redundant call by deleting the filepath.Clean invocation in promptNewPathInputTUI so it simply returns newPath (let the caller promptExistingPathDecisionTUI perform the single Clean), or alternatively remove the Clean in promptExistingPathDecisionTUI if you prefer centralizing normalization in the callee—update whichever function you choose (promptNewPathInputTUI or promptExistingPathDecisionTUI) so only one filepath.Clean is applied.cmd/proxsave/encryption_setup_test.go (1)
14-40: Note: Similar test UI stub exists in age_setup_workflow_test.go.The
testAgeSetupUIimplementation is nearly identical tomockAgeSetupUIininternal/orchestrator/age_setup_workflow_test.go. Consider extracting a shared test helper to reduce duplication, though this is a minor concern for test code.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/proxsave/encryption_setup_test.go` around lines 14 - 40, The test UI stub testAgeSetupUI duplicates mockAgeSetupUI from internal/orchestrator/age_setup_workflow_test.go; refactor by extracting a shared test helper (e.g., a reusable struct and methods like ConfirmOverwriteExistingRecipient, CollectRecipientDraft, ConfirmAddAnotherRecipient) into a common test package or a test helper file used by both tests, replace both local definitions with imports/uses of that shared helper, and keep the existing method signatures and field names (overwrite, drafts, addMore) so existing tests compile without changes.internal/orchestrator/age_setup_ui_cli.go (1)
32-71: Consider handling invalid menu options explicitly.The switch statement handles options
"1","2","3"but doesn't have a default case for other inputs. IfpromptOptionAgereturns an unexpected value (e.g.,"5"or empty string), the loop silently continues and re-prompts.This is likely acceptable behavior (re-prompt on invalid input), but an explicit default case would make the intent clearer.
💡 Optional: Add explicit default case
switch option { case "1": // ... case "2": // ... case "3": // ... + default: + fmt.Println("Invalid option. Please select 1-4.") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/orchestrator/age_setup_ui_cli.go` around lines 32 - 71, The CollectRecipientDraft loop lacks an explicit default branch for the switch over option returned by promptOptionAge; add a default case in the switch (inside CollectRecipientDraft) that handles unexpected values by warning the user (e.g., via u.warn or fmt.Println) and continuing the loop so the intent to re-prompt is explicit; reference the symbols CollectRecipientDraft, promptOptionAge, AgeRecipientDraft, AgeRecipientInputExisting/AgeRecipientInputPassphrase/AgeRecipientInputPrivateKey and ErrAgeRecipientSetupAborted when locating where to add the default branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/proxsave/install.go`:
- Around line 555-557: The function detectTelegramCode is dead/unreferenced and
should be removed to resolve the U1000 static analysis failure; delete the
detectTelegramCode wrapper function (which simply calls
detectTelegramCodeWithContext) and ensure there are no callers expecting it—keep
detectTelegramCodeWithContext as the canonical implementation and update any
import/docs if they referenced the removed symbol.
In `@cmd/proxsave/telegram_setup_cli.go`:
- Around line 83-87: The code prints status.Message directly (in
cmd/proxsave/telegram_setup_cli.go where msg :=
strings.TrimSpace(status.Message) and fmt.Printf("Telegram: %s\n", msg) is
used), which can echo terminal escape sequences from the raw HTTP body built in
internal/notify/telegram_registration.go; fix it by sanitizing msg before
printing (e.g., strip ANSI/terminal escape sequences and control characters,
fallback to a safe truncated string), and use the sanitized value in the
fmt.Printf call so untrusted server-controlled text cannot inject terminal
escapes into the user's shell.
In `@internal/identity/identity.go`:
- Around line 90-91: The existing-id upgrade path swallows context cancellation
because maybeUpgradeIdentityFileWithContext doesn't propagate errors from
writeIdentityFileWithContext; update maybeUpgradeIdentityFileWithContext to
return an error and ensure DetectWithContext checks and returns that error
(including context.Canceled/context.DeadlineExceeded) instead of always
returning success; specifically propagate errors coming out of
writeIdentityFileWithContext through maybeUpgradeIdentityFileWithContext and
have DetectWithContext (the branch that currently calls
maybeUpgradeIdentityFileWithContext) return the error immediately when non-nil
so cancellations/timeout abort detection; apply the same pattern to the other
upgrade-call site(s) that currently ignore the returned error.
In `@internal/notify/email_relay_test.go`:
- Line 8: The test has a data race because the server goroutine mutates attempts
while the test goroutine reads it; change the attempts counter in
internal/notify/email_relay_test.go to an atomic integer (e.g., int32 or int64)
and use sync/atomic operations: replace direct increments in the handler (where
attempts is modified) with atomic.AddInt32/AddInt64 and replace reads in the
test assertions with atomic.LoadInt32/LoadInt64 so all access to attempts is
atomic and race-free.
In `@internal/orchestrator/age_setup_workflow.go`:
- Around line 245-252: mapAgeSetupAbort currently returns raw context.Canceled
from the TUI adapter calls
(ConfirmOverwriteExistingRecipient/ConfirmAddAnotherRecipient), bypassing the
expected normalization; update mapAgeSetupAbort to call the existing
normalization helper mapInputAbortToAgeAbort(err) (falling back to
ErrAgeRecipientSetupAborted when appropriate) so any context.Canceled or
input-abort errors from the TUI are normalized the same way as the CLI path;
reference mapAgeSetupAbort and mapInputAbortToAgeAbort to implement this change.
In `@internal/orchestrator/decrypt_tui_e2e_helpers_test.go`:
- Around line 53-59: The test currently locks decryptTUIE2EMu and sets orig :=
newTUIApp but only registers rollback after screen.Init(), so if screen.Init()
fails the mutex remains locked and newTUIApp isn't restored; move the
cleanup/rollback registration to immediately after acquiring the lock (i.e.,
call t.Cleanup to restore newTUIApp and unlock decryptTUIE2EMu right after
decryptTUIE2EMu.Lock() and setting orig) so that regardless of screen.Init() or
any early t.Fatalf, the mutex is unlocked and newTUIApp is rolled back.
In `@internal/orchestrator/decrypt_tui_simulation_test.go`:
- Around line 60-72: The test
TestTUIWorkflowUIPromptDecryptSecret_ZeroInputAborts verifies that entering the
rune '0' is treated as an abort and returns ErrDecryptAborted; update the
user-facing prompts in newTUIWorkflowUI and the PromptDecryptSecret flow to
explicitly mention that entering "0" will abort the operation (and/or add a
short help line displayed with path/candidate/secret prompts) so users know "0"
is a reserved abort sentinel while preserving the current ErrDecryptAborted
behavior.
In `@internal/orchestrator/encryption.go`:
- Around line 384-390: The backup path uses
recipientTime(tp).Format("20060102-150405") which is second-resolution and can
collide if two operations run in the same second; change the backup name
generation (the backupPath construction) to include higher-resolution time (e.g.
nanoseconds via Format with .000000000 or append time.UnixNano()) or a short
random/UUID suffix so each backupPath is unique, then call
copyRecipientFileWithDeps(fs, path, backupPath, perm) as before; update any
tests or callers that rely on the old format and keep references to
recipientTime and copyRecipientFileWithDeps intact.
In `@internal/orchestrator/notification_adapter.go`:
- Around line 163-176: The code renders "0%" by calling calculateUsagePercent
when total==0 before warning, so move or add the inconsistent-usage warning to
run when used>0 && total==0 prior to formatting the percent: ensure
n.warnOnInconsistentUsageStats is invoked (or calculateUsagePercent returns an
indicator) before calling calculateUsagePercent/formatPercentString so you log
diagnostic info for cases where used>0 and total==0; update the local and
secondary blocks (the calls around n.warnOnInconsistentUsageStats,
calculateUsagePercent, and formatPercentString) and the other similar blocks
that handle usage stats to follow this order or add the check inside
calculateUsagePercent to trigger n.warnOnInconsistentUsageStats for the used>0
&& total==0 case.
In `@internal/orchestrator/restore_tui.go`:
- Around line 998-1002: The error path returns without waiting for the
background ticker goroutine to exit, which can allow that goroutine to touch app
after unwinding; after app.RunWithContext(ctx) returns an error, close stopCh
and stop ticker as you're doing, then block waiting for the goroutine completion
signal (the done channel used on the success path) before returning; update the
error branch around app.RunWithContext to close stopCh, ticker.Stop(), then read
from done (or otherwise wait for the same goroutine completion signal) so the
ticker goroutine cannot run after the function begins unwinding.
In `@internal/orchestrator/restore.go`:
- Around line 1578-1586: In extractDirectory, avoid creating directories with
the archived header.Mode up-front; instead create the directory with a temporary
owner-accessible mode (e.g., 0700) via restoreFS.MkdirAll(target, ...) so
non-root processes can open it, then open the directory with
restoreFS.Open(target) and only after obtaining the handle apply the final mode
from header.Mode (e.g., via Chmod/restoreFS.Chmod) to preserve archive
permissions; update the code paths around restoreFS.MkdirAll, restoreFS.Open and
the subsequent mode application to follow this sequence.
- Around line 37-38: The current temp-file naming logic (using
restoreTempSequence and composing temp name by appending a suffix to the final
basename) can produce ENAMETOOLONG for targets near NAME_MAX; change the
temp-file creation to create the temporary file in the same directory without
depending on the final basename by calling
restoreFS.CreateTemp(filepath.Dir(target), ".proxsave-tmp-*") (replace the
manual temp-name assembly used around the restoreTempSequence usage and where
restoreGlob is used to find temp files), write to that tempfile, fsync/close as
before, then atomically rename it to target; ensure any cleanup or glob-based
detection is adjusted to match the new pattern.
In `@internal/orchestrator/tui_simulation_test.go`:
- Around line 31-57: The draw-handshake can hang because the injector goroutine
in newTUIApp waits indefinitely on readyCh; change the implementation to include
a cancellable or timeout path (e.g., accept a context with cancel or use a
time.After) so the goroutine selects between readyCh and a done/timeout signal
and exits if the prompt never draws; ensure SetAfterDrawFunc still closes
readyCh and writes to drawCh on first draw, and update the injector goroutine
(the closure started inside injectOnce.Do that calls screen.InjectKey) to return
on ctx.Done/timeouts to fail fast and avoid blocking tests.
In `@internal/storage/storage_test.go`:
- Around line 1642-1657: The UsedSpace assertion is flaky because
stats.UsedSpace and wantUsed are derived from separate safefs.Statfs snapshots
of the whole mount; replace the fragile check by either stubbing safefs.Statfs
in the test to return a stable, controlled Statfs result (so
SpaceUsageFromStatfs(wrappedStat) yields a deterministic wantUsed), or change
the assertion around stats.UsedSpace/ wantUsed to a much looser invariant that
does not rely on mount-wide stability (e.g., only assert that diff is below a
large, explicit upper bound or remove the check entirely); update references to
safefs.Statfs, SpaceUsageFromStatfs, stats.UsedSpace, wantUsed, and tolerance
accordingly so the test no longer depends on concurrent filesystem activity.
In `@internal/tui/wizard/age.go`:
- Around line 121-122: Replace the hard-coded context.Background() used when
launching the AGE confirmation dialogs with the caller-provided context (e.g.,
ctx) so the modal lifecycles respect cancellation; specifically, pass the
surrounding function's context into ageWizardRunner (and any modal start calls
for the confirmation dialogs) in both places where context.Background() is
currently used (the two ageWizardRunner invocations), ensuring the dialogs are
cancelled when the parent flow is cancelled.
In `@internal/tui/wizard/install.go`:
- Around line 705-706: The call to checkExistingConfigRunner currently uses
context.Background(), which drops the install's parent context; replace
context.Background() with the install flow's cancellable context variable (e.g.,
ctx or parentCtx used in the surrounding function) so
checkExistingConfigRunner(ctx, app, flex, modal) receives the real install
context and can be cancelled; ensure the chosen context variable is in scope
where the call to checkExistingConfigRunner is made and propagate that change
only to this call.
- Around line 665-672: The modal currently defaults focus to the first
(destructive) button and does not validate file type; before showing the modal
validate the target path using the same regular-file check (call IsRegular() or
equivalent on the config path) and return/error if not regular, and set the
modal's safe default button by calling modal.SetFocus(2) so "Keep & continue" is
focused; update the modal creation flow (where modal is constructed and
escapedConfigPath is used) to perform the IsRegular() check first and then call
modal.SetFocus(2) on the modal instance.
In `@internal/tui/wizard/new_install.go`:
- Around line 21-38: formatPreservedEntries currently calls os.Stat on the raw
trimmed entry (which resolves against the process CWD); change
formatPreservedEntries to accept a baseDir string and, before calling os.Stat,
resolve the entry against baseDir (e.g., via filepath.Join(baseDir, trimmed) or
equivalent) so directory checks use the dialog's target dir; update all callers
to pass the appropriate baseDir and apply the same resolution fix to the other
identical occurrence referenced in the diff.
---
Outside diff comments:
In `@internal/environment/detect_additional_test.go`:
- Around line 266-278: TestExtendPathIdempotent relies on the real environment
PATH and leaves changes behind; update the test to use t.Setenv to set a known
initial PATH before invoking extendPath so the test is isolated and changes are
automatically reverted, then call extendPath twice and compare os.Getenv("PATH")
values as before; locate TestExtendPathIdempotent and add a t.Setenv("PATH",
"<known-value>") (e.g., an empty or minimal PATH string) at the start to ensure
determinism and cleanup.
In `@internal/tui/wizard/install.go`:
- Around line 657-658: The CheckExistingConfig function currently only verifies
that os.Stat succeeds and therefore allows non-regular paths (directories,
FIFOs, sockets) into the TUI flow; update CheckExistingConfig to call os.Stat to
obtain FileInfo and reject any path where fi.Mode().IsRegular() is false (return
the same error/ExistingConfigAction used by the CLI flow), so non-regular config
paths are rejected before showing the prompt; reference the CheckExistingConfig
function and ensure the returned error behavior matches the existing CLI check
for consistency.
---
Nitpick comments:
In `@cmd/proxsave/encryption_setup_test.go`:
- Around line 14-40: The test UI stub testAgeSetupUI duplicates mockAgeSetupUI
from internal/orchestrator/age_setup_workflow_test.go; refactor by extracting a
shared test helper (e.g., a reusable struct and methods like
ConfirmOverwriteExistingRecipient, CollectRecipientDraft,
ConfirmAddAnotherRecipient) into a common test package or a test helper file
used by both tests, replace both local definitions with imports/uses of that
shared helper, and keep the existing method signatures and field names
(overwrite, drafts, addMore) so existing tests compile without changes.
In `@cmd/proxsave/main.go`:
- Around line 1569-1570: Remove the redundant `continue` that follows the
`<-ticker.C` receive; since `<-ticker.C` is the last statement in the loop body,
the `continue` has no effect—delete the `continue` line (near the `ticker`
receive) so the loop behavior remains unchanged and the code is cleaner.
In `@internal/config/validation_secondary_test.go`:
- Around line 24-37: Add t.Parallel() to each subtest goroutine so they run
concurrently: inside the t.Run anonymous func that calls
ValidateRequiredSecondaryPath (the subtests iterating over tests), insert
t.Parallel() as the first statement. Apply the same change to the other two
similar test functions in this file that use t.Run (the subtests around lines
referenced in the comment) so all subtests run in parallel.
In `@internal/orchestrator/age_setup_ui_cli.go`:
- Around line 32-71: The CollectRecipientDraft loop lacks an explicit default
branch for the switch over option returned by promptOptionAge; add a default
case in the switch (inside CollectRecipientDraft) that handles unexpected values
by warning the user (e.g., via u.warn or fmt.Println) and continuing the loop so
the intent to re-prompt is explicit; reference the symbols
CollectRecipientDraft, promptOptionAge, AgeRecipientDraft,
AgeRecipientInputExisting/AgeRecipientInputPassphrase/AgeRecipientInputPrivateKey
and ErrAgeRecipientSetupAborted when locating where to add the default branch.
In `@internal/orchestrator/workflow_ui_cli_test.go`:
- Around line 13-40: The captureCLIStdout helper currently restores stdout and
closes the pipe only after fn() returns, so if fn() panics or calls t.Fatal the
cleanup never runs; fix captureCLIStdout by adding a defer immediately after
creating r,w and starting the io.Copy goroutine that 1) closes w, 2) waits for
the goroutine via <-done, and 3) closes r (and keep the existing t.Cleanup for
restoring os.Stdout); update the function captureCLIStdout to use that defer
(referencing r, w, done) so stdout is restored and the io.Copy goroutine can
always exit even when fn() fails.
In `@internal/orchestrator/workflow_ui_tui_decrypt_prompts.go`:
- Line 124: The code calls filepath.Clean twice for the same path (once in
promptNewPathInputTUI and again in promptExistingPathDecisionTUI); remove the
redundant call by deleting the filepath.Clean invocation in
promptNewPathInputTUI so it simply returns newPath (let the caller
promptExistingPathDecisionTUI perform the single Clean), or alternatively remove
the Clean in promptExistingPathDecisionTUI if you prefer centralizing
normalization in the callee—update whichever function you choose
(promptNewPathInputTUI or promptExistingPathDecisionTUI) so only one
filepath.Clean is applied.
In `@internal/tui/abort_context_test.go`:
- Around line 129-152: The tests currently only check the final error and can
pass if RunWithContext returns immediately; update both
TestAppRunWithContext_NilContextRunsUntilStopped and
TestAppRunWithContext_ReturnsNilWhenStoppedWithoutCancellation to synchronize on
the app actually starting before calling app.Stop() — e.g., have
newSimulationApp provide a started channel or WaitGroup signal the test can wait
on (or otherwise expose a Started() hook), then launch the goroutine to sleep
and call app.Stop() only after that start signal, and finally assert that
app.RunWithContext(nil) / app.RunWithContext(context.Background()) remains
blocked until Stop is invoked and returns nil.
In `@internal/tui/screen_test.go`:
- Around line 11-25: The helper primitiveContainsText only inspects
*tview.TextView and *tview.Flex, so extend it to also check other relevant
primitives (e.g., *tview.Box, *tview.Form, or any primitive exposing text) or
add a generic fallback: detect and extract visible text where possible (for
example use v.GetText for Box-like types or iterate contained items for
containers), and ensure primitiveContainsText calls that extraction for types
beyond *tview.TextView/*tview.Flex so tests that render text in other primitives
succeed; update the type switch in primitiveContainsText to include cases for
*tview.Box and any other concrete primitives you expect, plus a default/fallback
branch that attempts a safe string probe.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 53ae7f19-6431-4f0d-8829-b7e5bae63fc4
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (138)
cmd/proxsave/encryption_setup.gocmd/proxsave/encryption_setup_test.gocmd/proxsave/helpers_test.gocmd/proxsave/install.gocmd/proxsave/install_existing_config.gocmd/proxsave/install_existing_config_test.gocmd/proxsave/install_test.gocmd/proxsave/install_tui.gocmd/proxsave/main.gocmd/proxsave/new_install.gocmd/proxsave/new_install_test.gocmd/proxsave/newkey.gocmd/proxsave/newkey_test.gocmd/proxsave/prompts.gocmd/proxsave/runtime_helpers.gocmd/proxsave/runtime_helpers_more_test.gocmd/proxsave/schedule_helpers.gocmd/proxsave/schedule_helpers_test.gocmd/proxsave/telegram_setup_cli.gocmd/proxsave/telegram_setup_cli_test.gocmd/proxsave/upgrade.godocs/BACKUP_ENV_MAPPING.mddocs/CLI_REFERENCE.mddocs/CLOUD_STORAGE.mddocs/CONFIGURATION.mddocs/ENCRYPTION.mddocs/INSTALL.mddocs/MIGRATION_GUIDE.mdgo.modinternal/backup/collector_pve.gointernal/backup/collector_pve_additional_test.gointernal/config/config.gointernal/config/config_test.gointernal/config/env_mutation.gointernal/config/env_mutation_test.gointernal/config/migration.gointernal/config/migration_test.gointernal/config/templates/backup.envinternal/config/validation_secondary.gointernal/config/validation_secondary_test.gointernal/cron/cron.gointernal/cron/cron_test.gointernal/environment/detect_additional_test.gointernal/identity/identity.gointernal/identity/identity_test.gointernal/logging/bootstrap.gointernal/logging/bootstrap_test.gointernal/notify/context_helpers.gointernal/notify/email.gointernal/notify/email_mailq_test.gointernal/notify/email_parsing_test.gointernal/notify/email_relay.gointernal/notify/email_relay_test.gointernal/notify/webhook.gointernal/notify/webhook_test.gointernal/orchestrator/additional_helpers_test.gointernal/orchestrator/age_setup_ui.gointernal/orchestrator/age_setup_ui_cli.gointernal/orchestrator/age_setup_workflow.gointernal/orchestrator/age_setup_workflow_test.gointernal/orchestrator/backup_sources.gointernal/orchestrator/backup_sources_test.gointernal/orchestrator/bundle_test.gointernal/orchestrator/decrypt_additional_test.gointernal/orchestrator/decrypt_test.gointernal/orchestrator/decrypt_tui.gointernal/orchestrator/decrypt_tui_e2e_helpers_test.gointernal/orchestrator/decrypt_tui_e2e_test.gointernal/orchestrator/decrypt_tui_simulation_test.gointernal/orchestrator/decrypt_tui_test.gointernal/orchestrator/decrypt_workflow_ui.gointernal/orchestrator/decrypt_workflow_ui_test.gointernal/orchestrator/deps_test.gointernal/orchestrator/encryption.gointernal/orchestrator/encryption_exported_test.gointernal/orchestrator/encryption_more_test.gointernal/orchestrator/encryption_test.gointernal/orchestrator/helpers_test.gointernal/orchestrator/network_apply_workflow_ui.gointernal/orchestrator/notification_adapter.gointernal/orchestrator/notification_adapter_test.gointernal/orchestrator/orchestrator.gointernal/orchestrator/restore.gointernal/orchestrator/restore_access_control_ui_additional_test.gointernal/orchestrator/restore_errors_test.gointernal/orchestrator/restore_firewall_additional_test.gointernal/orchestrator/restore_ha.gointernal/orchestrator/restore_ha_additional_test.gointernal/orchestrator/restore_test.gointernal/orchestrator/restore_tui.gointernal/orchestrator/restore_tui_simulation_test.gointernal/orchestrator/restore_tui_test.gointernal/orchestrator/restore_workflow_integration_test.gointernal/orchestrator/storage_adapter.gointernal/orchestrator/storage_adapter_test.gointernal/orchestrator/telegram_setup_bootstrap.gointernal/orchestrator/telegram_setup_bootstrap_test.gointernal/orchestrator/tui_screen_env.gointernal/orchestrator/tui_simulation_test.gointernal/orchestrator/workflow_ui_cli.gointernal/orchestrator/workflow_ui_cli_test.gointernal/orchestrator/workflow_ui_tui_decrypt.gointernal/orchestrator/workflow_ui_tui_decrypt_prompts.gointernal/orchestrator/workflow_ui_tui_decrypt_test.gointernal/orchestrator/workflow_ui_tui_restore.gointernal/orchestrator/workflow_ui_tui_restore_test.gointernal/orchestrator/workflow_ui_tui_shared.gointernal/safefs/safefs.gointernal/safefs/safefs_test.gointernal/security/security.gointernal/security/security_test.gointernal/storage/backup_files.gointernal/storage/cloud.gointernal/storage/cloud_test.gointernal/storage/local.gointernal/storage/retention.gointernal/storage/retention_normalize.gointernal/storage/retention_test.gointernal/storage/secondary.gointernal/storage/secondary_test.gointernal/storage/storage_test.gointernal/tui/abort_context_test.gointernal/tui/app.gointernal/tui/screen.gointernal/tui/screen_test.gointernal/tui/wizard/age.gointernal/tui/wizard/age_test.gointernal/tui/wizard/age_ui_adapter.gointernal/tui/wizard/age_ui_adapter_test.gointernal/tui/wizard/install.gointernal/tui/wizard/install_test.gointernal/tui/wizard/new_install.gointernal/tui/wizard/new_install_test.gointernal/tui/wizard/post_install_audit_tui.gointernal/tui/wizard/post_install_audit_tui_test.gointernal/tui/wizard/screen.gointernal/tui/wizard/telegram_setup_tui.gointernal/tui/wizard/telegram_setup_tui_test.go
💤 Files with no reviewable changes (2)
- internal/orchestrator/restore_workflow_integration_test.go
- internal/orchestrator/decrypt_tui_test.go
Initialize the PATH environment variable to an empty string in TestExtendPathIdempotent (t.Setenv("PATH", "")). This ensures the test runs with a clean PATH, preventing flakiness from existing PATH entries and verifying extendPath remains idempotent when invoked multiple times.
LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Treat existing config paths that are not regular files as errors. Check os.Stat mode and return ExistingConfigCancel with a descriptive error if the path is not a regular file (e.g. a directory). Add TestCheckExistingConfigRejectsNonRegularPath to verify the new behavior. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Remove the no-op continue after <-ticker.C in cmd/proxsave/main.go. This does not change behavior; it only simplifies the loop control flow. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Extend primitiveContainsText to inspect more tview primitives and add fallback coverage so text assertions work across boxes, forms, modals, and nested containers. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Synchronize RunWithContext tests on actual app startup and assert they stay blocked until Stop is called, preventing false positives from early returns. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Make captureCLIStdout defer pipe shutdown and goroutine cleanup so stdout capture is restored even if the callback panics or aborts the test early. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Add t.Parallel() to the secondary path validation subtests so the table-driven cases execute concurrently without changing assertions. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
… flow Drop the redundant filepath.Clean call from the new-path prompt and keep normalization in the caller so the destination path is cleaned exactly once. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Extract the duplicated age setup test UI into a reusable helper under internal/testutil and reuse it in both cmd/proxsave and internal/orchestrator tests. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Delete the dead detectTelegramCode helper now that install flow calls detectTelegramCodeWithContext directly, resolving the staticcheck U1000 failure. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Strip terminal escape sequences and control characters from Telegram registration messages before printing them, with a safe quoted fallback for untrusted server text. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Return upgrade write failures from maybeUpgradeIdentityFileWithContext and surface them from DetectWithContext so context cancellation and timeouts abort identity detection instead of being swallowed. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Replace shared retry attempt counters in email relay tests with sync/atomic operations to remove handler/test goroutine races. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Route mapAgeSetupAbort through mapInputAbortToAgeAbort so context cancellations and input-abort signals from TUI prompts are normalized to ErrAgeRecipientSetupAborted like the CLI path. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Move the decrypt TUI e2e helper cleanup right after locking and saving newTUIApp so the mutex is always unlocked and the app factory is restored even if screen initialization fails early. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Include nanosecond resolution in AGE recipient backup filenames so concurrent operations in the same second do not reuse the same .bak path. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Log inconsistent storage usage stats when used bytes are reported but total capacity is zero, while keeping displayed usage at 0% for unknown totals. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Synchronize the network-apply TUI error path with the countdown goroutine teardown by waiting on the done channel after stopping the ticker and closing stopCh. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Create restore temp files with CreateTemp in the target directory instead of deriving names from the final basename, avoiding ENAMETOOLONG collisions while preserving atomic rename semantics. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Create restored directories with a temporary owner-accessible mode, open them first, and only then apply the archived permissions so restrictive modes do not block non-root restore flows. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Make the TUI simulation injector exit on cleanup or initial-draw timeout instead of waiting indefinitely on readyCh, while preserving the first-draw synchronization used by the tests. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Replace the secondary storage UsedSpace check based on a second Statfs snapshot with stable space-usage invariants so the test no longer depends on concurrent filesystem activity. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Replace context.Background() in AGE overwrite/add-recipient confirmation modals with the parent flow context so dialog lifecycles respect cancellation. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Set the existing configuration modal to focus "Keep & continue" by default, while keeping the existing regular-file validation for config paths. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Pass the parent install flow context into CheckExistingConfig and checkExistingConfigRunner so the existing-config modal can be cancelled with the rest of the TUI install workflow. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
Make preserved-entry formatting stat paths relative to the target base directory instead of the process working directory, so directory markers in the confirmation dialog are accurate. LiveReview Pre-Commit Check: skipped (iter:1, coverage:0%)
@internal/tui/wizard/telegram_setup_tui.goaround lines 281 - 291, The ESC key handler reads result.Verified without holding the mutex that checkHandler uses; acquire the same mutex that guards result.Verified (i.e., the mutex used in checkHandler/telegramSetupQueueUpdateDraw) before reading result.Verified in the app.SetInputCapture callback, then release it immediately (use a brief lock/unlock or defer unlock) and then call doClose(true/false) based on the protected value; ensure you reference the same mutex variable used by checkHandler to avoid introducing a new synchronization primitive.Summary by CodeRabbit
New Features
Bug Fixes
Documentation