Code review findings on the "skip revoke when .crt is missing" migration
path:
- CliRunner._issue() took the skip path whenever has_issued_cert() was
False, which is also true for a typo'd/nonexistent CN — it would warn,
skip the revoke, and go on to build, package, and email a brand-new
certificate for a CN nobody asked to renew. The skip now only fires when
the CN has a current index.txt entry (via _load_current_certs()); an
unknown CN prints an error and exits 1 with nothing built. The TUI's
_process_cert() doesn't need the same guard — renewal there always opens
on an existing row (cn_readonly pins the CN), so a typo'd CN can't reach
the branch.
- Skipping the revoke leaves pki/reqs/<CN>.req and pki/private/<CN>.key in
place (normally revoke-issued archives both), which makes EasyRSA's
build-client-full abort. Both CliRunner._issue() and
CursesApp._process_cert() now check for those leftovers before building
and fail fast with the exact paths, rather than surfacing EasyRSA's
confusing error after the CA passphrase prompt. Neither path touches the
files itself.
Also: strengthened two under-specified tests (test_main_rejects_bad_days_flag
now checks the resolver's message text, not just "--days", which also
appears in argparse's unrelated error; test_show_cert_form_confirm now pins
the Enter-keypress count so a partial "days" field reversion is caught), and
folded a malformed CLAUDE.md table row into its neighbor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A blank/default days value round-trips as "" even from a reverted
three-field form (CertFormResult.days defaults to ""), so it would not
prove the Days field was traversed. Use a non-blank days="90" instead,
which only survives if the field exists and is validated by _submit().
clear() forces a full physical screen repaint on the next refresh();
erase() just blanks the buffer content and lets curses diff against
what's already on the terminal, sending only the changed cells. The
main cert list and the cert-entry form both redraw on every keystroke,
so clear() there meant retransmitting the whole screen on every arrow
key / typed character — slow and flickery over a low-bandwidth link
like a serial console.
One-shot dialogs (show_confirm, _msg, _error) draw once and don't
loop-redraw, so they keep clear() as-is — no benefit to changing them
and it's one less thing to get wrong.
Also handle curses.KEY_RESIZE explicitly in the main list loop: force
one real clear() right after a detected resize, since erase()'s diff
against the pre-resize screen model isn't reliable across a genuine
dimension change. Screen-size changes were already picked up on the
next redraw before this (draw() runs unconditionally every loop
iteration), this just guarantees a clean repaint at the moment of the
actual resize instead of trusting the diff engine through it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
show_confirm() sized its window purely from message content, with no
upper bound — a long line (the Cryptgeon password URL in the
post-issuance "send email?" prompt) could easily exceed a narrow
terminal's width (e.g. a serial console), making curses.newwin()
raise "curses function returned NULL" and crash the whole app.
show_cert_form() had the same unguarded newwin() call, just with a
fixed 13x62 size instead of content-driven.
Clamp both windows' height/width to the actual screen size, and wrap
newwin() itself in try/except as a last-resort fallback (declined
confirm / cancelled form) in case clamping still isn't enough.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tab (or Down/Enter on a field) now cycles through the three text fields
and then onto [ Continue ] and [ Cancel ] buttons at the bottom of the
form. Enter or Space activates the focused button. Ctrl-G still
confirms immediately from any position. Escape still cancels.
The form window is trimmed from h=16 to h=13 since the buttons replace
the now-redundant empty space below the password field.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ctrl-S (0x13) is the XOFF flow-control character; terminals intercept it
before curses sees it, causing output suspension in Konsole and others.
Replaced with Ctrl-G (0x07 / BEL) which has no terminal-level meaning.
Updated hint bar and test stub accordingly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>