---
phase: 02-browse
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - possession/tui/__init__.py
  - possession/tui/app.py
  - possession/tui/screens/__init__.py
  - possession/tui/screens/main.py
  - possession/__main__.py
autonomous: true
requirements: [NAV-01]

must_haves:
  truths:
    - "Running `possession` launches a full-screen Textual TUI instead of printing to stdout"
    - "The TUI shows a DataTable with columns: Name, Description, Location, Category, Cost — populated from list_items()"
    - "Pressing j moves the cursor down one row; pressing k moves it up one row"
    - "Pressing G jumps to the last row; pressing gg jumps to the first row"
    - "The TUI can be exited with q or Ctrl+C"
  artifacts:
    - path: "possession/tui/__init__.py"
      provides: "TUI package marker"
    - path: "possession/tui/app.py"
      provides: "PossessionApp(App) Textual application class"
      exports: ["PossessionApp"]
    - path: "possession/tui/screens/__init__.py"
      provides: "Screens package marker"
    - path: "possession/tui/screens/main.py"
      provides: "MainScreen(Screen) with DataTable and VIM keybindings"
      exports: ["MainScreen"]
      min_lines: 60
    - path: "possession/__main__.py"
      provides: "Updated entry point that launches PossessionApp"
  key_links:
    - from: "possession/__main__.py"
      to: "possession/tui/app.py"
      via: "PossessionApp(db_path=resolved_path).run()"
      pattern: "PossessionApp.*run\\(\\)"
    - from: "possession/tui/app.py"
      to: "possession/tui/screens/main.py"
      via: "SCREENS dict and push_screen or INITIAL_SCREEN"
      pattern: "MainScreen|push_screen"
    - from: "possession/tui/screens/main.py"
      to: "possession.models.list_items"
      via: "on_mount or compose calling list_items(self.app.db_path)"
      pattern: "list_items"
---

<objective>
Build the Textual TUI application shell and the main item-list screen with VIM-style keyboard navigation.

Purpose: This is the foundation of the entire TUI. Phase 3 features (quick-add, edit, delete) all live within this shell. Getting the DataTable and VIM nav right here means Phase 3 can focus purely on actions.

Output: A working `possession` CLI that launches a Textual full-screen TUI displaying all inventory items in a DataTable with j/k/gg/G navigation.
</objective>

<execution_context>
@/Users/paritoshpatil/.claude/get-shit-done/workflows/execute-plan.md
@/Users/paritoshpatil/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/01-foundation/01-02-SUMMARY.md

@possession/db.py
@possession/models.py
@possession/__main__.py
@pyproject.toml
</context>

<tasks>

<task type="auto">
  <name>Task 1: TUI package structure and app shell</name>
  <files>
    possession/tui/__init__.py
    possession/tui/app.py
    possession/tui/screens/__init__.py
  </files>
  <action>
Create the TUI package directory structure and the root Textual app class.

**possession/tui/__init__.py** — empty package marker (0 bytes, just a comment is fine).

**possession/tui/screens/__init__.py** — empty package marker.

**possession/tui/app.py** — define `PossessionApp(App)`:

```python
from pathlib import Path
from typing import Optional
from textual.app import App, ComposeResult
from possession.tui.screens.main import MainScreen

class PossessionApp(App):
    """Possession terminal inventory manager."""

    CSS = """
    Screen {
        background: $surface;
    }
    """

    def __init__(self, db_path: Path, **kwargs):
        super().__init__(**kwargs)
        self.db_path = db_path

    def on_mount(self) -> None:
        self.push_screen(MainScreen())
```

Keep CSS minimal — Textual's default dark theme is sufficient for Phase 2. Do NOT add colors or custom palettes yet (Phase 3 can polish). Use `Optional` from `typing`, not `X | None` union syntax — Python 3.9 compatibility.
  </action>
  <verify>
    <automated>cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2 && python3 -c "from possession.tui.app import PossessionApp; print('import ok')"</automated>
    <manual>Check that possession/tui/ directory has __init__.py, app.py, and screens/__init__.py</manual>
  </verify>
  <done>
    `from possession.tui.app import PossessionApp` succeeds without ImportError. possession/tui/ and possession/tui/screens/ directories exist with __init__.py markers.
  </done>
</task>

<task type="auto">
  <name>Task 2: Main screen with DataTable and VIM keybindings</name>
  <files>
    possession/tui/screens/main.py
  </files>
  <action>
Create `MainScreen(Screen)` in `possession/tui/screens/main.py`.

**Columns to display:** Name, Description, Location (format: "Room > Container" or just "Room" if no container, or blank if neither), Category, Cost (format: "$X.XX" or blank if None).

**Data loading:** In `on_mount`, call `list_items(self.app.db_path)` (imported from `possession.models`). Build the DataTable from the returned list of dicts. Each dict has keys: `id, name, description, room_name, container_name, category_name, purchase_date, cost`.

**Location formatting helper:**
```python
def _fmt_location(row: dict) -> str:
    room = row.get("room_name") or ""
    container = row.get("container_name") or ""
    if room and container:
        return f"{room} > {container}"
    return room or container or ""
```

**Cost formatting helper:**
```python
def _fmt_cost(cost) -> str:
    if cost is None:
        return ""
    return f"${cost:.2f}"
```

**VIM keybindings:** Implement using `BINDINGS` class variable and action methods. Use Textual's `DataTable.action_scroll_cursor_down/up` for j/k. For gg/G, use a custom approach:

```python
BINDINGS = [
    ("j", "cursor_down", "Down"),
    ("k", "cursor_up", "Up"),
    ("G", "cursor_bottom", "Bottom"),
    ("q", "app.pop_screen", "Back"),
]
```

For `gg` (two-key sequence): use `on_key` handler to detect `g` pressed twice in succession. Track state with `self._last_key: str = ""`. In `on_key(self, event: events.Key)`:
```python
if event.key == "g":
    if self._last_key == "g":
        self._last_key = ""
        self.action_cursor_top()
        return
    self._last_key = "g"
else:
    self._last_key = ""
```

Action methods:
```python
def action_cursor_down(self) -> None:
    self.query_one(DataTable).action_scroll_cursor_down()

def action_cursor_up(self) -> None:
    self.query_one(DataTable).action_scroll_cursor_up()

def action_cursor_bottom(self) -> None:
    table = self.query_one(DataTable)
    table.move_cursor(row=table.row_count - 1)

def action_cursor_top(self) -> None:
    table = self.query_one(DataTable)
    table.move_cursor(row=0)
```

**compose method:** Return a DataTable widget with `cursor_type="row"` and `show_header=True`. Add columns in compose, load rows in on_mount.

**Store item IDs:** Store the list of item dicts in `self._items: list` (set in on_mount) for future use by Phase 3. Keyed by DataTable row index.

Use `Optional` from `typing` not `X | None`. Python 3.9 compatible syntax throughout.
  </action>
  <verify>
    <automated>cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2 && python3 -c "from possession.tui.screens.main import MainScreen; print('import ok')"</automated>
    <manual>None</manual>
    <sampling_rate>run after this task, before Task 3</sampling_rate>
  </verify>
  <done>
    `from possession.tui.screens.main import MainScreen` imports without error. File has at least 60 lines. DataTable columns (Name, Description, Location, Category, Cost) are defined. BINDINGS include j, k, G, q.
  </done>
</task>

<task type="auto">
  <name>Task 3: Wire app into CLI entry point and install dependencies</name>
  <files>
    possession/__main__.py
  </files>
  <action>
Update `possession/__main__.py` to launch the TUI instead of printing to stdout.

Replace the body of `main()` after `resolved_path = init_db(db_override)`:

```python
from possession.tui.app import PossessionApp
app = PossessionApp(db_path=resolved_path)
app.run()
```

Remove the old `print(f"Database ready: {resolved_path}")` line.

The full updated main() should look like:
```python
def main():
    parser = argparse.ArgumentParser(
        description="possession-terminal: home inventory manager"
    )
    parser.add_argument(
        "--db",
        type=str,
        default=None,
        help="Path to SQLite database file",
    )
    args = parser.parse_args()

    db_override = args.db or os.environ.get("POSSESSION_DB")
    resolved_path = init_db(db_override)

    from possession.tui.app import PossessionApp
    PossessionApp(db_path=resolved_path).run()
```

After updating __main__.py, ensure textual is installed:
```bash
pip3 install -e ".[dev]" 2>/dev/null || pip3 install textual>=0.47.0
```

Run a headless smoke test to confirm the app can be instantiated without crashing:
```bash
python3 -c "
from pathlib import Path
import tempfile, os
from possession.db import init_db
from possession.tui.app import PossessionApp
with tempfile.TemporaryDirectory() as d:
    db = init_db(str(Path(d) / 'test.db'))
    app = PossessionApp(db_path=db)
    print('App instantiated OK')
"
```
  </action>
  <verify>
    <automated>cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2 && python3 -c "
from pathlib import Path
import tempfile
from possession.db import init_db
from possession.tui.app import PossessionApp
with tempfile.TemporaryDirectory() as d:
    from pathlib import Path
    db = init_db(str(Path(d) / 'test.db'))
    app = PossessionApp(db_path=db)
    print('PASS: App instantiated without error')
"</automated>
    <manual>None</manual>
    <sampling_rate>run after this task commits, before next plan begins</sampling_rate>
  </verify>
  <done>
    `possession/__main__.py` no longer prints "Database ready:" — it launches PossessionApp. The headless instantiation test prints "PASS: App instantiated without error". textual is importable.
  </done>
</task>

</tasks>

<verification>
After all 3 tasks complete:

1. Package structure check:
```bash
cd /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2
python3 -c "
from possession.tui.app import PossessionApp
from possession.tui.screens.main import MainScreen
print('All imports OK')
"
```

2. Confirm __main__ no longer prints (check source):
```bash
grep -n "print" /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2/possession/__main__.py
```
Should return nothing (no print statements).

3. Confirm DataTable columns are defined in main.py:
```bash
grep -n "add_column\|Name\|Description\|Location\|Category\|Cost" /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2/possession/tui/screens/main.py
```

4. Confirm BINDINGS has j, k, G:
```bash
grep -n '"j"\|"k"\|"G"' /Users/paritoshpatil/Personal/claude-projects/possession-terminal-2/possession/tui/screens/main.py
```
</verification>

<success_criteria>
1. `possession/tui/` package exists with app.py and screens/main.py
2. `PossessionApp` and `MainScreen` import without error
3. `__main__.py` launches PossessionApp (not print)
4. DataTable has 5 columns: Name, Description, Location, Category, Cost
5. BINDINGS cover j (down), k (up), G (bottom), q (back)
6. gg (double-g) triggers go-to-top via on_key handler
7. All code uses Optional[X] syntax — Python 3.9 compatible
</success_criteria>

<output>
After completion, create `.planning/phases/02-browse/02-01-SUMMARY.md` using the standard summary template.
</output>
