mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-06 04:42:35 +00:00
Fix beta TUI startup preflight and installer fallback hints
This commit is contained in:
1 parent
ba93a1cd5a
commit
f02b360062
4 files changed
+73
-2
No files matched your search
@@ -240,3 +240,12 @@
|
||||
- Harness validation results:
|
||||
- `scripts/ai_tui2_agent_test.py --scenario extended`: passed
|
||||
- `scripts/ai_tui_agent_test.py --scenario core`: passed
|
||||
|
||||
## 2026-03-01 Installer + TUI v2 preflight fix (gray-screen mitigation)
|
||||
- Root cause found for "blank/gray screen" reports: TUI v2 was initializing `PasswordManager` inside Textual mount via `entry_service_factory`, so password/setup prompts could occur behind the Textual screen and appear as a frozen UI.
|
||||
- Updated `src/seedpass/cli/__init__.py` to pre-initialize entry service before launching Textual (`_prime_tui2_service`), then pass the ready service into `launch_tui2`.
|
||||
- Added explicit fallback/error behavior:
|
||||
- default `seedpass`: preflight failures now fall back to legacy TUI with details.
|
||||
- `seedpass tui2 --no-fallback-legacy`: preflight failures now return exit code 1 with a clear error.
|
||||
- Updated installer `scripts/install.sh` to run `seedpass tui2 --check` after install and warn when Textual runtime is unavailable, plus print a legacy fallback hint for users seeing blank/gray startup.
|
||||
- Added regression coverage in `src/tests/test_typer_cli.py` for preflight behavior and adapted existing TUI launch tests to mock preflight service creation.
|
||||
@@ -218,6 +218,14 @@ main() {
|
||||
if ! "$VENV_DIR/bin/python" -c "import seedpass.cli; print('ok')"; then
|
||||
print_error "SeedPass CLI import check failed."
|
||||
fi
|
||||
if "$VENV_DIR/bin/seedpass" tui2 --check >/tmp/seedpass-tui2-check.json 2>/dev/null; then
|
||||
if ! grep -q '"textual_available":[[:space:]]*true' /tmp/seedpass-tui2-check.json; then
|
||||
print_warning "TUI v2 runtime check reports Textual as unavailable."
|
||||
print_warning "You can run legacy mode with: seedpass --legacy-tui"
|
||||
fi
|
||||
else
|
||||
print_warning "Unable to run 'seedpass tui2 --check' during install validation."
|
||||
fi
|
||||
|
||||
deactivate
|
||||
|
||||
@@ -257,6 +265,7 @@ EOF2
|
||||
# 8. Final instructions
|
||||
print_success "Installation/update complete!"
|
||||
print_info "You can now launch the interactive TUI by typing: seedpass"
|
||||
print_info "If your terminal only shows a blank/gray screen, run: seedpass --legacy-tui"
|
||||
print_info "'seedpass' resolves to: $(command -v seedpass)"
|
||||
if [[ ":$PATH:" != *":$LAUNCHER_DIR:"* ]]; then
|
||||
print_warning "Directory '$LAUNCHER_DIR' is not in your PATH."
|
||||
|
||||
@@ -82,6 +82,20 @@ def _gui_backend_available() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _prime_tui2_service(
|
||||
ctx: typer.Context,
|
||||
) -> tuple[Optional[object], Optional[Exception]]:
|
||||
"""Initialize entry service before Textual mounts.
|
||||
|
||||
PasswordManager initialization may prompt for credentials. Doing that before
|
||||
entering Textual prevents hidden prompt stalls that appear as a blank screen.
|
||||
"""
|
||||
try:
|
||||
return _get_entry_service(ctx), None
|
||||
except Exception as exc: # pragma: no cover - defensive runtime path
|
||||
return None, exc
|
||||
|
||||
|
||||
@app.callback(invoke_without_command=True)
|
||||
def main(
|
||||
ctx: typer.Context,
|
||||
@@ -107,9 +121,18 @@ def main(
|
||||
tui = importlib.import_module("main")
|
||||
raise typer.Exit(tui.main(fingerprint=fingerprint))
|
||||
|
||||
service, preflight_error = _prime_tui2_service(ctx)
|
||||
if preflight_error is not None:
|
||||
typer.echo(
|
||||
"TUI v2 preflight failed; falling back to legacy TUI. "
|
||||
f"Details: {preflight_error}"
|
||||
)
|
||||
tui = importlib.import_module("main")
|
||||
raise typer.Exit(tui.main(fingerprint=fingerprint))
|
||||
|
||||
launched = launch_tui2(
|
||||
fingerprint=fingerprint,
|
||||
entry_service_factory=lambda: _get_entry_service(ctx),
|
||||
entry_service_factory=lambda: service,
|
||||
)
|
||||
if launched:
|
||||
return
|
||||
@@ -150,9 +173,24 @@ def tui2(
|
||||
return
|
||||
|
||||
fingerprint = (ctx.obj or {}).get("fingerprint")
|
||||
service, preflight_error = _prime_tui2_service(ctx)
|
||||
if preflight_error is not None:
|
||||
if fallback_legacy:
|
||||
typer.echo(
|
||||
"TUI v2 preflight failed; falling back to legacy TUI. "
|
||||
f"Details: {preflight_error}"
|
||||
)
|
||||
tui = importlib.import_module("main")
|
||||
raise typer.Exit(tui.main(fingerprint=fingerprint))
|
||||
typer.echo(
|
||||
f"TUI v2 preflight failed: {preflight_error}",
|
||||
err=True,
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
launched = launch_tui2(
|
||||
fingerprint=fingerprint,
|
||||
entry_service_factory=lambda: _get_entry_service(ctx),
|
||||
entry_service_factory=lambda: service,
|
||||
)
|
||||
if launched:
|
||||
return
|
||||
|
||||
@@ -981,6 +981,7 @@ def test_root_tui2_forward_fingerprint(monkeypatch):
|
||||
called["factory"] = kwargs.get("entry_service_factory")
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(cli, "_get_entry_service", lambda _ctx: object())
|
||||
monkeypatch.setattr(cli, "launch_tui2", fake_launch)
|
||||
result = runner.invoke(app, ["--fingerprint", "abc"])
|
||||
assert result.exit_code == 0
|
||||
@@ -1019,6 +1020,7 @@ def test_tui2_check(monkeypatch):
|
||||
|
||||
|
||||
def test_tui2_unavailable_without_fallback(monkeypatch):
|
||||
monkeypatch.setattr(cli, "_get_entry_service", lambda _ctx: object())
|
||||
monkeypatch.setattr(cli, "launch_tui2", lambda **_: False)
|
||||
result = runner.invoke(app, ["tui2", "--no-fallback-legacy"])
|
||||
assert result.exit_code == 1
|
||||
@@ -1027,6 +1029,7 @@ def test_tui2_unavailable_without_fallback(monkeypatch):
|
||||
|
||||
def test_tui2_fallback_legacy(monkeypatch):
|
||||
called = {}
|
||||
monkeypatch.setattr(cli, "_get_entry_service", lambda _ctx: object())
|
||||
monkeypatch.setattr(cli, "launch_tui2", lambda **_: False)
|
||||
|
||||
def fake_main(*, fingerprint=None):
|
||||
@@ -1046,6 +1049,7 @@ def test_tui2_fallback_legacy(monkeypatch):
|
||||
|
||||
def test_root_fallback_legacy_when_tui2_unavailable(monkeypatch):
|
||||
called = {}
|
||||
monkeypatch.setattr(cli, "_get_entry_service", lambda _ctx: object())
|
||||
monkeypatch.setattr(cli, "launch_tui2", lambda **_: False)
|
||||
|
||||
def fake_main(*, fingerprint=None):
|
||||
@@ -1063,6 +1067,17 @@ def test_root_fallback_legacy_when_tui2_unavailable(monkeypatch):
|
||||
assert "falling back to legacy TUI" in result.stdout
|
||||
|
||||
|
||||
def test_tui2_preflight_failure_without_fallback(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
cli,
|
||||
"_get_entry_service",
|
||||
lambda _ctx: (_ for _ in ()).throw(RuntimeError("unlock failed")),
|
||||
)
|
||||
result = runner.invoke(app, ["tui2", "--no-fallback-legacy"])
|
||||
assert result.exit_code == 1
|
||||
assert "preflight failed" in result.stderr.lower()
|
||||
|
||||
|
||||
def test_gui_command(monkeypatch):
|
||||
called = {}
|
||||
|
||||
|
||||
Reference in new issue
Block a user