---
phase: 48-textual-tui-removal
plan: 001
type: execute
wave: 1
depends_on: []
files_modified:
  - paperforge/setup_wizard.py
  - paperforge/cli.py
  - pyproject.toml
  - scripts/validate_setup.py
autonomous: true
requirements:
  - DEPR-01
  - DEPR-03
user_setup: []

must_haves:
  truths:
    - "Running `paperforge setup` (bare, no --headless) prints a help message redirecting to `--headless` or the plugin settings tab — no NameError crash, no TUI launch attempt"
    - "setup_wizard.py contains zero Textual-related imports or classes"
    - "`rg 'from textual' paperforge/setup_wizard.py` returns no hits"
    - "`textual` is removed from pyproject.toml dependencies"
    - "headless_setup(), EnvChecker, AGENT_CONFIGS, _copy_file_incremental, _merge_env_incremental, _find_vault are all preserved and importable"
    - "cli.py `paperforge setup` help no longer references 'Textual-based'"
  artifacts:
    - path: paperforge/setup_wizard.py
      provides: "TUI-removed setup wizard with help-message main() only"
      min_lines: 300
      must_not_contain: "from textual"
    - path: paperforge/cli.py
      provides: "Updated setup parser help text"
      must_not_contain: "Textual-based"
    - path: pyproject.toml
      provides: "Dependencies without textual"
      must_not_contain: '"textual>=0.47.0"'
  key_links:
    - from: paperforge/setup_wizard.py::main()
      to: sys.stdout
      via: print()
      pattern: "paperforge setup --headless"
    - from: paperforge/cli.py
      to: paperforge/setup_wizard.py::headless_setup
      via: import on --headless flag
      pattern: "from paperforge.setup_wizard import headless_setup"
---

<objective>
Remove the broken Textual TUI from the setup wizard codebase. The `paperforge setup` bare command becomes a help-message redirect. `headless_setup()` and all shared utilities remain fully functional.

Purpose: Eliminate ~1200 lines of dead/deprecated TUI code that crashes on execution. The Textual TUI is unreachable from both real install paths (plugin settings tab uses `--headless`, AI agents use `--headless`).
Output: Clean setup_wizard.py with zero textual imports; updated CLI help; textual removed from project deps.
</objective>

<execution_context>
@C:/Users/Lin/.opencode/get-shit-done/workflows/execute-plan.md
@C:/Users/Lin/.opencode/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/48-textual-tui-removal/48-CONTEXT.md
@paperforge/cli.py
@paperforge/setup_wizard.py
@pyproject.toml
@scripts/validate_setup.py
</context>

<tasks>

<task type="auto">
  <name>Task 1: Remove TUI code from setup_wizard.py and replace main() with help message</name>
  <files>paperforge/setup_wizard.py</files>
  <action>
    Perform surgical removal of all Textual TUI code from setup_wizard.py while preserving headless_setup() and all shared utilities. The file is ~2261 lines. Remove these blocks in order:

    BLOCK A (lines ~30-43): Top-level `from textual` imports:
    - Remove: `from textual.app import App, ComposeResult`
    - Remove: `from textual.containers import Container, Horizontal, Vertical`
    - Remove: `from textual.message import Message`
    - Remove: `from textual.reactive import reactive`
    - Remove: `from textual.widgets import ( ... Button, ContentSwitcher, Footer, Header, Markdown, ProgressBar, Static, Tree, ... )`
    - Keep the blank line that separates imports from the AGENT_CONFIGS section.

    BLOCK B (lines ~436-1575): All TUI classes and custom messages:
    - Remove: `class StepScreen(Static)` and its entire body (compose, set_status methods)
    - Remove: `class WelcomeStep` through `class DoneStep` (all 7 concrete step classes)
    - Remove: `class StepPassed(Message)` and `class RestartWizard(Message)`
    - Remove: `class SetupWizardApp(App)` and its entire body (compose, on_mount, watch_*, action_*, etc.)
    - Also remove any inline `from textual.widgets import Input` statements that appear inside these classes (they will be removed with the classes).
    - Verify removal by ensuring all class names above no longer appear in the file.

    BLOCK C (lines ~2244-2257): Replace main() function:
    - REMOVE the existing `main()` function (currently creates SetupWizardApp and runs it)
    - ADD a new `main()` function that:
      ```python
      def main(argv: list[str] | None = None) -> int:
          """Print help message — Textual TUI removed."""
          print("=" * 60)
          print("  PaperForge Setup Wizard")
          print("=" * 60)
          print()
          print("The interactive Textual TUI has been removed.")
          print()
          print("To run setup non-interactively, use:")
          print("  paperforge setup --headless")
          print()
          print("Or configure PaperForge via the Obsidian plugin settings tab:")
          print("  1. Open Obsidian → Settings → Community Plugins → PaperForge")
          print("  2. Fill in your configuration")
          print("  3. Click 'Install'")
          print()
          return 0
      ```

    PRESERVE all of these (verify they remain in the file):
    - `AGENT_CONFIGS` dict (line ~49)
    - `class CheckResult` and `class EnvChecker` (lines ~122-435)
    - `_find_vault()` function (line ~1577)
    - `_substitute_vars()` function (line ~1586)
    - `_copy_file_incremental()` function (line ~1610)
    - `_write_text_incremental()` function (line ~1619)
    - `_copy_tree_incremental()` function (line ~1628)
    - `_merge_env_incremental()` function (line ~1645)
    - `_deploy_skill_directory()` function (line ~1684)
    - `_deploy_flat_command()` function (line ~1748)
    - `_deploy_rules_file()` function (line ~1778)
    - `headless_setup()` function (line ~1812)
    - `if __name__ == "__main__":` guard at end

    IMPORTANT: The resulting file must still be importable. `from paperforge.setup_wizard import headless_setup, AGENT_CONFIGS, EnvChecker, _find_vault` must work without errors.
  </action>
  <verify>
    <automated>
      # 1. Verify zero textual imports remain
      rg -n "from textual" paperforge/setup_wizard.py && echo "FAIL: textual imports remain" || echo "PASS: no textual imports"

      # 2. Verify TUI class names removed
      $classes = @("WelcomeStep", "DirOverviewStep", "VaultStep", "PlatformStep", "DeployStep", "DoneStep", "SetupWizardApp", "ContentSwitcher", "StepScreen", "StepPassed", "RestartWizard")
      $found = $false
      foreach ($c in $classes) { if (Select-String -Path paperforge/setup_wizard.py -Pattern "class $c" -Quiet) { Write-Host "FAIL: $c still present"; $found = $true } }
      if (-not $found) { Write-Host "PASS: all TUI classes removed" }

      # 3. Verify preserved classes/functions still exist
      $preserved = @("class CheckResult", "class EnvChecker", "def headless_setup", "def _find_vault", "def _copy_file_incremental", "def _merge_env_incremental", "AGENT_CONFIGS")
      $missing = $false
      foreach ($p in $preserved) { if (-not (Select-String -Path paperforge/setup_wizard.py -Pattern $p -Quiet)) { Write-Host "FAIL: $p is missing!"; $missing = $true } }
      if (-not $missing) { Write-Host "PASS: all preserved items present" }

      # 4. Verify new main() prints help
      $output = python -c "from paperforge.setup_wizard import main; main()"
      if ($output -match "paperforge setup --headless") { Write-Host "PASS: main() prints help with --headless redirect" } else { Write-Host "FAIL: main() output incorrect" }
    </automated>
  </verify>
  <done>
    - `rg "from textual" paperforge/setup_wizard.py` returns zero matches
    - All TUI class names removed: WelcomeStep, DirOverviewStep, VaultStep, PlatformStep, DeployStep, DoneStep, SetupWizardApp, ContentSwitcher, StepScreen, StepPassed, RestartWizard
    - All preserved items still present: AGENT_CONFIGS, CheckResult, EnvChecker, headless_setup, _find_vault, _copy_file_incremental, _merge_env_incremental
    - `from paperforge.setup_wizard import headless_setup, AGENT_CONFIGS, EnvChecker` succeeds without ImportError
    - Calling `main()` prints help message with `paperforge setup --headless` redirect
    - File still importable and syntactically valid Python
  </done>
</task>

<task type="auto">
  <name>Task 2: Update cli.py setup parser help text</name>
  <files>paperforge/cli.py</files>
  <action>
    Update the `paperforge setup` parser in cli.py to remove the "Textual-based" reference from the help text.

    Change line ~253:
    From: `p_setup = sub.add_parser("setup", help="Run the setup wizard (Textual-based)")`
    To:   `p_setup = sub.add_parser("setup", help="Set up PaperForge in a vault (use --headless for non-interactive)")`

    The `--headless` argument and all other arguments (--agent, --paddleocr-key, etc.) remain unchanged.
    The CLI dispatch logic (lines ~457-476) remains unchanged — the else branch calls `main()` which now prints help instead of launching the TUI.
  </action>
  <verify>
    <automated>
      # Verify help text updated
      if (Select-String -Path paperforge/cli.py -Pattern "Textual-based" -Quiet) { Write-Host "FAIL: old help text remains" } else { Write-Host "PASS: Textual-based removed" }
      if (Select-String -Path paperforge/cli.py -Pattern "Set up PaperForge in a vault" -Quiet) { Write-Host "PASS: new help text present" } else { Write-Host "FAIL: new help text not found" }
    </automated>
  </verify>
  <done>
    - `paperforge setup --help` shows updated description without "Textual-based"
    - CLI still dispatches --headless to headless_setup() correctly
    - All --headless arguments preserved and functional
  </done>
</task>

<task type="auto">
  <name>Task 3: Remove textual from project dependencies</name>
  <files>pyproject.toml, scripts/validate_setup.py</files>
  <action>
    Per DEPR-03, remove the `textual` package from dependencies since the TUI code is gone.

    In pyproject.toml:
    - Remove `"textual>=0.47.0",` from the `dependencies` list
    - Fix the trailing comma on the previous line (`"tenacity>=8.2.0",`) — it should remain as-is since `tqdm` still follows
    - Result: dependencies list should be requests, pymupdf, pillow, tenacity, tqdm, filelock (6 items)

    In scripts/validate_setup.py:
    - Remove `"textual": "textual"` from the `required` dict (line ~62)
    - The dict should become: `{"requests": "requests", "pymupdf": "fitz", "pillow": "PIL"}`
  </action>
  <verify>
    <automated>
      # Verify textual removed from pyproject.toml
      if (Select-String -Path pyproject.toml -Pattern "textual" -Quiet) { Write-Host "FAIL: textual still in pyproject.toml" } else { Write-Host "PASS: textual removed from pyproject.toml" }
      # Verify textual removed from validate_setup.py
      if (Select-String -Path scripts/validate_setup.py -Pattern "textual" -Quiet) { Write-Host "FAIL: textual still in validate_setup.py" } else { Write-Host "PASS: textual removed from validate_setup.py" }
    </automated>
  </verify>
  <done>
    - pyproject.toml dependencies list no longer contains "textual>=0.47.0"
    - scripts/validate_setup.py required dict no longer maps "textual"
    - `pip install -e .` succeeds (textual not required)
  </done>
</task>

</tasks>

<verification>
1. Verify setup_wizard.py compiles: `python -c "import py_compile; py_compile.compile('paperforge/setup_wizard.py', doraise=True)"`
2. Verify headless import works: `python -c "from paperforge.setup_wizard import headless_setup, AGENT_CONFIGS, EnvChecker, _find_vault; print('All imports OK')"`
3. Verify bare setup redirect: `python -c "from paperforge.setup_wizard import main; main()"` prints help message
4. Verify no textual anywhere in source: `rg -n "from textual" paperforge/`
5. Verify cli.py parses: `python -c "import ast; ast.parse(open('paperforge/cli.py').read()); print('cli.py syntax OK')"`
6. Run existing setup_wizard tests: `python -m pytest tests/test_setup_wizard.py -q --tb=short` (all 25+ tests should pass)
</verification>

<success_criteria>
- `paperforge setup` (bare) prints clean help message redirecting to `--headless` — no crash
- `paperforge setup --headless` runs headless_setup() as before (zero behavior change)
- `from paperforge.setup_wizard import headless_setup` works without ImportError
- `rg "from textual" paperforge/setup_wizard.py` returns zero matches
- Setup_wizard unit tests all pass (tests/test_setup_wizard.py)
- `textual` removed from pyproject.toml dependencies
</success_criteria>

<output>
After completion, create `.planning/phases/48-textual-tui-removal/48-001-SUMMARY.md`
</output>
