Skip to content

feat: Tailor workspace + background action workers (closes Phase 1 item #6) - #28

Merged
Harikeshav-R merged 4 commits into
mainfrom
feat/tui-tailor-workspace
Aug 4, 2026
Merged

feat: Tailor workspace + background action workers (closes Phase 1 item #6)#28
Harikeshav-R merged 4 commits into
mainfrom
feat/tui-tailor-workspace

Conversation

@Harikeshav-R

Copy link
Copy Markdown
Owner

What & why

The final slice of Phase 1 item #6 (PROJECT.md §8, screen #4) — the Tailor workspace plus wiring the long-running AI/render/open services through Textual thread workers. With this, item #6 is complete and the Phase 1 core loop closes: onboard → master resume → scrape → score → tailor + cover letter → render → track, all from the CLI or the TUI.

Every Atlas service is synchronous and blocks — the AI provider chain runs a subprocess (Claude Code) or a network call (OpenRouter), and rendering runs WeasyPrint — so running them on Textual's event loop would freeze the UI (§8 requires long-running actions to run async). This PR adds the first worker in the codebase.

What changed

  • TailorWorkspaceScreen (atlas.tui.screens.tailor_workspace) — opened from Application detail (t). Shows the master-resume blocks, the latest tailored selections (content_id · included · reason · text), and a materials summary; runs four actions: Tailor (t), Cover letter (c), Re-render (r), Open (o). Read-only panes this PR — interactive editing is the deferred follow-up.
  • Thread workers — each action is @work(thread=True, exclusive=True, exit_on_error=False); on_worker_state_changed refreshes + toasts on success and surfaces the service's typed errors (TailoringError/CoverLetterError/RenderError/FileOpenError/…) on failure without tearing down the app.
  • AtlasApp boundaries — injected provider/renderer/opener/tailoring/render_config/renders_dir (all optional) + run_tailor/run_cover_letter/run_rerender/run_open (each a short session_scope + the service, mirroring the CLI) + an actions_enabled property.
  • atlas tui builds the provider chain + renderer best-effort (_build_tui_actions), catching ConfigError/RenderError/LLMError and launching browse-only with the Tailor actions disabled + an atlas doctor hint — the read/track screens need no AI, so a missing key never blocks the TUI.
  • atlas.tui.data.build_tailor_workspace — the pure view-model builder (reuses the resume/tailor/coverletter getters).

Decisions (confirmed up front)

  • Scope: view + run actions (Tailor / Cover / Re-render / Open). Interactive editing (include/exclude/pin), per-section regenerate, and the Questions tab stay with the deferred PR-2b tailoring depth.
  • AI not configured: launch browse-only (inject boundaries into AtlasApp; disable actions with a hint) rather than failing.

How thread workers are tested at 100% coverage

The key finding that de-risked this: await pilot.app.workers.wait_for_complete() awaits a thread worker deterministically inside run_test(), and exit_on_error=False + on_worker_state_changed makes the error path assertable. Hermeticism comes from injecting the existing FakeLLMProvider / FakePdfRenderer / FakeFileOpener + a tmp_path renders dir at the app boundary, so a worker never makes a real AI/render/subprocess call.

Key commits

  • feat(tui): add the tailor-workspace view-model builder
  • feat(tui): add the Tailor workspace screen with thread-worker actions
  • feat(cli): build the TUI's AI/render boundaries best-effort
  • docs: record the Tailor workspace and close Phase 1 item #6

Testing

  • ruff format --check + ruff check clean; mypy --strict clean on Linux and --platform win32.
  • Full suite 776 passing at 100% line + branch coverage. New Pilot tests drive each worker to completion and assert: the DB result (success), that nothing persisted (worker error handled), and that browse-only disables the actions.
  • uv build confirms the wheel is unchanged in shape (app.tcss + the new screen ship).

No migration; no dependency changes (textual/pytest-asyncio landed with the core-TUI PR). Closes #6.

Add build_tailor_workspace + TailorWorkspaceView / ResumeBlockView /
TailoredSelection to atlas.tui.data — the pure data layer the Tailor workspace
screen presents. It resolves the application and posting, reads the latest
master-resume blocks, and decodes the latest tailored resume's selections
(TailoredItem.model_validate) plus the latest cover letter's version/path.
Pure over an open session, so it's covered without a running terminal.
Add TailorWorkspaceScreen (opened from Application detail via `t`): it shows the
master-resume blocks, the latest tailored selections, and a materials summary,
and runs four actions — Tailor, Cover letter, Re-render, Open.

Every Atlas service is synchronous and blocks (subprocess AI, network,
WeasyPrint), so each action runs in a Textual thread worker
(@work(thread=True, exclusive=True, exit_on_error=False)) — the first worker in
the codebase — with on_worker_state_changed refreshing + toasting on success and
surfacing the service's typed errors on failure without tearing down the app.

AtlasApp gains injected provider/renderer/opener/tailoring/render_config
boundaries (all optional; opener defaults to default_file_opener) plus
run_tailor/run_cover_letter/run_rerender/run_open (each a short session_scope +
the service, mirroring the CLI construction) and an actions_enabled property —
so the app runs browse-only when the AI/render boundaries aren't available.
Tests drive each worker to completion via app.workers.wait_for_complete() with
injected fakes + a tmp_path renders dir, covering success, worker-error, and
browse-only-disabled paths.
Extend `atlas tui` to build the provider chain + renderer from config (the same
sequence as `atlas tailor`) and pass them, the file opener, and the tailoring /
render config into AtlasApp so the Tailor workspace's actions work. Wrapped in
_build_tui_actions, which catches ConfigError / RenderError / LLMError and falls
back to browse-only (all-None) with an `atlas doctor` hint — the read/track
screens need no AI, so a missing key never blocks launching the TUI. The real
app.run() stays the sole pragma'd edge; the best-effort build is plain branch
logic covered by CliRunner tests (success + both fallback branches).
Document the final slice of item #6 (the Tailor workspace + background action
workers + browse-only launch): the CHANGELOG Added entries, the README `atlas
tui` description, and the STATUS what-landed block. Tick item #6 done in
PROJECT §15 and mark the Phase 1 core loop complete in STATUS (top block, table
row), moving "Next up" to Phase 2 (discovery daemon). Note the deferred Phase-1
optional depth (PR-2b tailoring / interactive editing) as non-blocking.
@Harikeshav-R
Harikeshav-R merged commit dfb8beb into main Aug 4, 2026
10 checks passed
@Harikeshav-R
Harikeshav-R deleted the feat/tui-tailor-workspace branch August 4, 2026 18:24
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