Skip to content

lorenzo/start kw integration pr 9 - #207

Draft
lorenzoberts wants to merge 61 commits into
lorenzo/start-kw-integration-pr-8from
lorenzo/start-kw-integration-pr-9
Draft

lorenzoberts wants to merge 61 commits into
lorenzo/start-kw-integration-pr-8from
lorenzo/start-kw-integration-pr-9

Conversation

@lorenzoberts

Copy link
Copy Markdown
Collaborator

Stack created with GitHub Stacks CLI • Give Feedback 💬

This commit moves src/kw/actor.rs to src/kw/actor/mod.rs unchanged, so
its tests can live in a sibling file.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
This commit moves the actor's test module out of mod.rs into sibling
files. The tests themselves are unchanged; rustfmt rejoins a few lines
that fit once the module indent is gone. They are split into
actor_test.rs and deploy_test.rs to keep each file under 2,000 lines.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
InputContext and CurrentScreen now derive Default, and tests build them
with struct literals. KeyInput cannot derive Default because KeyCode and
KeyEventKind do not implement it, so its Default is a null press with no
modifiers.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
TerminalHandle::read_event and TerminalMessage::ReadEvent existed only
to block on the session from tests. Event delivery already goes through
TerminalHandle::poll_event and the input actor.

Removed read_event_returns_terminal_event_from_actor: it only asserted
that the deleted message returned the event stubbed on the session
mock. The input actor tests already assert that a session key is
delivered.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
ConfigState::page_size only reread the crate-visible field, and
PatchFeedIndex::get_patch only forwarded the private map. Tests read
those directly.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
apply_record, build_record, and latest_build_record had no production
callers. History tests now read through apply_record_for_branch and
build_records, which still check the stored records, overwrite,
coexistence, missing files, corrupt files, and newest-across-branches.

KwReadiness.kernel_image, build_record, and latest_build were written
and never read outside tests. evaluate_readiness still feeds them into
deploy_alone, and the tests assert that verdict. On unix the allow on KwHandle hid nothing; on non-unix `KwHandle::new` is unused until the next commit gates it.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
CachePolicy had no implementors. PatchFeedIndex stored target_list and
never read it, so new() no longer takes that string. The empty-state
test still checks the offset and representative ids; the list-name
assertion only read the removed getter.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
log_scan is compiled only on Unix, with the actor. Functions whose only
production caller is that actor are cfg(unix) too, including build
history, the image and deploy probes, and remote.config parsing.
FileSystemTrait::read_dir and KwHandle::new follow the same gate.

The UI still names several types on every platform, so those stay, with
#[cfg_attr(not(unix), expect(dead_code))] on the item the actor alone
fills in:

- KwError and KwStartError: variants only the actor constructs
- DeployOptions, StartRequest, and KwMessage: fields only the actor reads
- KwJobKind, KwPhase, and KwJobStatus: variants only the actor constructs
- TreeReadiness, DeployAloneRefusal, and BootOnceState
- RemoteRefusal

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
…llows

Config callers now import the re-exports from crate::config, so the
module-wide unused_imports allow is unnecessary. resolve_config_path
stays on the repository module; nothing else named the re-export.

#[expect(clippy::too_many_arguments)] is on five functions whose
arguments are independent inputs. Folding them into a struct would
only exist to be unpacked at the call:

- App::new
- LoreService::new
- evaluate_readiness
- prepare_deploy_blocking
- app_with_details_and_kw

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The readiness probes stay associated functions on a unit struct, with the same arguments and the same per-item unix gates. Private helpers whose names did not start with a verb are renamed to find_newest_image_in, read_image_mtime, read_boot_once_from_file, and resolve_xdg_kw_config_file.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
KwArgvService, RemoteConfigService, and LogScanService hold the former free functions as associated functions. Helpers that did not start with a verb are now find_reserved_option, find_reserved_long_option, find_unique_long_abbrev, find_reserved_short_cluster, find_exact_short, is_value_attached, read_remote_from_file, resolve_xdg_remote_config, find_first_error, and check_grub_missed_release.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
KwGitService holds the worktree probe, branch switch, and HEAD probe. Job running, cancel, and outcome mapping are private KwActor functions, along with prepare_deploy_blocking, which was a free function beside them. head_branch, outcome_after_building_cancel, and outcome_after_cancel are probe_head_branch, rewrite_outcome_after_building_cancel, and map_outcome_after_cancel so each name starts with a verb.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
ApplyPatchsetService and ReviewedReplyService hold the action functions. Apply keeps its own switch_to_branch: it takes a kernel tree, omits git's -- separator, and returns a formatted String, unlike KwGitService::switch_to_branch. Request builders that fed these actions are private App functions, renamed so each name starts with a verb.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Screen handlers now run on App. Each flow's help popup is an associated build_*_help_popup. Helpers that did not start with a verb are map_tail_read, is_snapshot_job_terminal, is_job_running, is_job_busy, and count_preview_scroll_lines.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The input dispatch, kw-status wait, and confirm-popup helpers are private AppActor functions. kw_status_changed, kw_ops_should_tail, on_input, and kw_job_is_running are await_kw_status_change, should_tail_kw_ops, dispatch_input, and is_kw_job_running so each name starts with a verb.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
ConfigService, ConfigParsingService, EnvOverrideService, and ConfigPathService hold the former free functions. normalize_derived_paths is a ConfigState method. unknown_target_kernel_tree is reject_unknown_kernel_tree so the name starts with a verb.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
…RendererService

The patch and cover preview helpers are associated functions on those services. bat, delta, and diff-so-fancy entry points are render_patch_with_bat, render_patch_with_delta, render_patch_with_diff_so_fancy, and render_cover_with_bat so each name starts with a verb.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Each screen module paints through a Painter. The frame layout is FramePainter and the loading overlay is LoadingPainter. Helpers that did not start with a verb are build_mode_spans, build_keys_hint_span, build_review_trailers_line, compute_log_scroll_offset, build_labeled_line, build_colored_label, build_field_line, center_rect, and advance_spinner.

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
ActorReplyService::send_actor_reply replaces the result-reply copies. They shared the same control flow and differed only in the static failure and dropped-receiver log lines, which each call site still passes through. Value replies that never log a failure use deliver_value.

The reply log events' tracing target, file and line now point at `infrastructure::actor_reply` instead of each actor's module, while message text, level and fields are unchanged (the `patch_hub` prefix filter still matches).

No tests removed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
…ices

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The type's methods split a patch into cover and diff, build a reply
template, and pull a git send-email command out of the patch page.
Reply would describe only the last two; the renderer also uses the
cover/diff split, which is not a reply. PatchsetTextService names the
shared job of turning patchset text into those three results.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The eyre hook runs when a Report is constructed, so a failed restore
printed "failed to restore terminal" on every eyre!, bail!, and
foreign-error conversion, not only on a fatal exit. The panic hook and
the eyre hook now share one process-wide guard: restore is attempted
once, and a failure is reported once.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Signed-off-by: lorenzoberts <lorenzobs@usp.br>
eyre calls the installed hook from capture_handler on every Report
construction (Report::from_std, from_adhoc, from_display, from_msg, and
from_boxed), which is `?` into eyre::Result, eyre!, and bail!. That
includes errors the app handles and keeps running after. A failed fetch
does bail! in src/app/screens/latest.rs:60, and
src/app/flows/open_patchset.rs:42 turns that Err into an error popup.
Restoring the terminal there dropped the alternate screen and raw mode
while the TUI was still running.

The eyre hook now only forwards to color_eyre
(src/infrastructure/errors.rs:73). The panic hook still restores first
(src/infrastructure/errors.rs:66-69).

main returns color_eyre::Result (src/main.rs:47). #[tokio::main]
propagates that Result, and std::process::Termination prints it with
Debug after main returns. The eyre hook does not print; it only captures
color_eyre's handler when the Report is built. The report would
therefore be printed into raw/alternate mode unless something restores
first.

Nothing on the fatal Err path did. AppActor::run (src/app/actor.rs:79)
returns I/O errors with `?` and does not shut the terminal down.
AppHandle::run_until_done (src/app/handle.rs:20-22) only joins that
task. main calls terminal_handle.shutdown (src/main.rs:190-193) only
after run_until_done returns Ok. That reaches
CrosstermTerminalSession::shutdown (src/terminal/session.rs:118-125),
which calls restore(). teardown_user_io
(src/infrastructure/terminal.rs:59-63) turns raw mode back on for an
editor; it is not process teardown.

Errors before init (src/main.rs:58, src/main.rs:61-62, src/main.rs:73,
src/main.rs:77) never enter raw mode, so they must not restore:
`LeaveAlternateScreen` would emit a stray escape sequence. init
(src/infrastructure/terminal.rs:28-41) rolls its own partial setup
back: LeaveAlternateScreen if raw mode fails, restore() if Terminal::new
fails. After init succeeds, bootstrap (src/main.rs:132-135), App::new
(src/main.rs:137-150), subscribe_app (src/main.rs:153-156),
run_until_done (src/main.rs:177-178), and input shutdown
(src/main.rs:179-182) can return Err before terminal shutdown. main
calls restore_once() (src/main.rs:200-202) before returning that
Err, so the runtime prints the report on a normal terminal.

TerminalRestoreGuard stays. A panic in a spawned task runs the panic
hook and then comes back as a join error from main, so both would
restore. The guard makes the second call a no-op.
restore_guard_runs_the_operation_once still covers that. A test that
the eyre hook does not restore is not feasible without a real terminal
or a new injection seam: install_hooks sets a process-global hook, and
restore() talks to crossterm.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Snapshots and edit-config drafts are projections of their source state, so
callers build them with From instead of dedicated converter methods.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Screen projections are derived from application state, so callers use From
instead of project helpers.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Cache keys are projections of their sources. Reviewed-reply indexes
became `collect_successful_indexes`. The preview constructor keeps its
arguments and takes a verb name.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
ConfigService::parse_non_empty replaces the eight blank-string match
guards in validate_update. None still skips the field. A blank Some
still takes that field's rejection. A non-blank Some still takes the
old unguarded arm.

page_size: blank is InvalidPageSize of the raw text; non-blank parses
the trimmed text as usize and reports InvalidPageSize of the raw text
on failure.
cache_dir: blank is InvalidDirectory("cache directory is empty");
non-blank still trims and validate_dir.
data_dir: blank is InvalidDirectory("data directory is empty");
non-blank still trims and validate_dir.
max_log_age: blank is InvalidMaxLogAge of the raw text; non-blank
parses the trimmed text as usize.
stay_on_applied_branch: blank is InvalidStayOnAppliedBranch of the raw
text; non-blank parses the trimmed text as bool.
kw_reboot_after_deploy: blank is InvalidKwRebootAfterDeploy of the raw
text; non-blank parses the trimmed text as bool.
kw_deploy_force: blank is InvalidKwDeployForce of the raw text;
non-blank parses the trimmed text as bool.
target_kernel_tree: blank is still Some(None); non-blank still accepts
a trimmed known key as Some(Some(key)) or InvalidTargetKernelTree.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Each guard's false case still reaches the same result as the arm it used
to fall through to.

view_model succeeded-with-warnings: an empty warning list still yields
(None, None), which was the wildcard arm. Failed is unchanged. Idle,
Running, Cancelled, and a missing job stay (None, None).
kw_job_running_popup: Ok with a non-running job still returns None, as
does a status error.
handle_job_event: an unsuccessful Exited outcome still becomes Failed.
WaitFailed and Cancelled are unchanged.
probe_head_branch: Ok with success still returns the trimmed branch; Ok
without success still warns and returns an empty string. Err is unchanged.
resolve_output_dir: an empty XDG_CACHE_HOME still uses $HOME/.cache, and
so does a missing variable. A non-empty value is still used as-is, and
HOME is still read only on the fallback.
check_kw_version: a parsed version below the floor is still Below. None
is still Unknown. A version at or above the floor is still Meets.
resolve_xdg_kw_config_file and resolve_xdg_remote_config: an empty
XDG_CONFIG_HOME still uses $HOME/.config via HOME.ok(), and so does a
missing variable.
terminal_event_from_crossterm_event: a non-release key still becomes
KeyInput. Release is still dropped. The wildcard for other crossterm
events stays.
map_tail_read: NotFound is still an empty tail. Any other IoError still
formats "(could not read log: ...)".
map_details_go_to_first_line_chord: a pending chord inside the timeout
still fires PreviewGoToFirstLine. An expired chord and no pending chord
still arm a new chord and return None.

KwJobStatus::running_indicator lists Idle, Succeeded, Failed, and
Cancelled instead of a wildcard; only Running still produces text.
is_snapshot_job_terminal is true only for Succeeded, Failed, and
Cancelled. is_job_running and is_kw_job_running are true only for
Running; should_tail_kw_ops uses is_kw_job_running. needs_boot_once_confirm
is true for On and Unknown when not yet acknowledged, and false for Off.
A deploy start still checks deploy_alone only for KwStartKind::Deploy.
An unverified kw version warning still fires for Below and Unknown, not
Meets. Boot-once refusal still fires for On and Unknown when the option
is not acknowledged.

Left in place: the select! precondition in AppActor, KeyCode::Enter while
a confirm popup is open (a closed popup still falls through to the
wildcard), and the argv pointer-equality guard (a different option still
falls through to the ambiguous-prefix rejection).

apply_kw_snapshot clears start_requested for every non-Idle status
(Running, Succeeded, Failed, and Cancelled). Idle leaves the flag
unchanged.
KwOpsViewModel.running is true only while the job is Running. Idle,
Succeeded, Failed, Cancelled, and a missing status stay false.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Bindings no longer spell the collection type before collect. Where the
following use does not pin the type, the same type is written as a
turbofish. Two sites infer it and drop the annotation.

collect::<Vec<String>>: edit-config kernel-tree keys, config
reject_unknown_kernel_tree keys, merged kw argv, the view-model reply
index list, and the bufreader test lines.
collect::<Vec<&str>>: lore tag parts, pre blocks, and patch preview
lines.
collect::<Vec<char>>: reserved short-cluster characters.
collect::<Vec<PatchTagSummary>>: fetched patchset tag summaries.
collect::<String>: the minor component of a kw version line.
collect::<HashMap<PathBuf, String>>: the remote-config test filesystem.
Inferred with no annotation: kw config keys inserted into
HashMap<String, String>, and get_page's Vec<&Patch> returned as
Option<Vec<&Patch>>.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Hoist crate paths and other multi-segment paths into use so call
sites name the imported item, or keep a module prefix when that
prefix carries the meaning. That covers fs, io, time, task, and
process, and names that would mislead if imported bare: fmt::Result,
io::Error, and ptr::eq.

Doc links drop fully qualified crate paths for the same reason.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Drop notes that record an old shape of the code, lab log paths, and a
leftover TODO. Sentences that still state a real constraint stay, with
that wording removed: PATCH_HUB_* overrides still apply onto config
state, and the remote parser still matches Hostname, Port, and User by
key rather than by position after Host.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Trim every comment block to five lines or fewer, including module
docs. Notes that capture behaviour the code cannot show stay: GNU
getopt prefix rules, kw exit codes, the kw O= resolution order, and
shutdown ordering.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Blank lines mark the phases of multi-step functions in the kw actor,
KwOps flow, and patchset apply: probe then switch then spawn, record
then status, and outcome checks then the command trace.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Panic sites in tests now name the call they were waiting on, so a
failure points at that call instead of a bare unwrap. The messages
stay short: "json parses", "build starts", "temp dir removes".

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Kw mock expectations now say how often they run and which arguments
they accept. Counts come from a run of the kw tests. Shared fixtures
use a measured range, because callers probe them a different number of
times: the recording shell, the ready-tree filesystem, and the remote
env helper.

.withf(|_| true) appears 7 times, each with .times(0). The call never
happens, so the argument is not observed:

- record_build on deploy-alone history, in deploy_history and in the
  deploy test that must not write a build record.
- execute when the kw binary is missing, and on the four
  evaluate_readiness paths that return before any shell command.

history.rs tests use the real store and set no mock expectations.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
App, config, lore, input, terminal, and infrastructure mocks now
state how often they run and which arguments they accept. Counts
come from a test run. Shared fixtures use a measured range when
callers read a different subset: config env keys, the apply shell,
and the clean worktree filesystem. A generated patchset branch name
is matched by its patchset- prefix so the count stays stable.

.withf(|_| true) or .withf(|_, _| true) appears 11 times, each with
.times(0). The call never happens, so the argument is not observed:
execute when a dependency check must not spawn, the apply refusal
that never touches the tree, and history lookups that must not run.

.withf(|| true) appears 18 times on methods with no arguments
(cache loads, terminal setup and shutdown, process wait and kill).
There is no argument to check.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Test-only builders and doubles now live in a helpers submodule next
to the tests that call them. None became Default: the built types do
not implement Default, or they are parsed from JSON or XML, and this
crate does not use cfg_attr(test, derive(Default)).

No test was renamed.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The filesystem, process, and kw readiness suites each owned a
TempDir(PathBuf) tuple. They now share TempDir { path } in
test_support, including the same Drop cleanup.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Deny unwrap_used and allow_attributes so new unwrap calls and
#[allow] attributes fail clippy. Warn on redundant type annotations
and single-variant match wildcards; CI denies warnings, so those
are clean too.

Test unwrap_err calls now use expect_err with a message for the
expected failure. The patch-renderer match names Default, and four
bindings drop type annotations rustc already infers.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
Paths that were hoisted onto their own use lines now sit in the std,
external, or crate group already present in the file.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
KwActor::spawn always runs in this unix-only binary, so main keeps the
handle itself. Shutdown is still after the input actor and before the
config actor.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The non-test helpers move into the helpers submodule next to the other
kw_ops fixtures. Test paths drop the unix segment.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
The root lint tables move under [workspace.lints] and both packages
inherit them. The proc-macro crate reports bad input with syn::Error
and tests use expect.

Signed-off-by: lorenzoberts <lorenzobs@usp.br>
@lorenzoberts
lorenzoberts added this pull request to stack #200 October 4, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant