## ADDED Requirements

### Requirement: Structured pty_exited frame on WS protocol
The system MUST send a structured exit notification when a PTY process exits.

- **WHEN** a process running in a booterm terminal pane exits (via `handle.onExit`)
- **THEN** booterm MUST send a structured `pty_exited` JSON text frame on the WS connection containing: `type`, `exit_code`, `last_lines` (array of recent output lines from the ring buffer), `session_id`, `session_title`, `session_description`, `parent_agent`, `timed_out` (boolean)

#### Scenario: Normal process exit with metadata
- **WHEN** a user's SSH shell process exits with code 0 after producing output
- **AND** the terminal pane was registered with title "build", description "run tests", parentAgent "claude"
- **THEN** the `pty_exited` frame MUST contain `exit_code: 0`, at least one `last_lines` entry, `session_title: "build"`, `session_description: "run tests"`, `parent_agent: "claude"`, and `timed_out: false`

#### Scenario: Process exit with no output
- **WHEN** a process exits immediately without producing output
- **THEN** the `pty_exited` frame MUST contain an empty `last_lines` array and valid session metadata

#### Scenario: Timeout-triggered exit
- **WHEN** a process is killed by the idle timeout sweep (requires sweepExpired to be wired to an interval, which is a separate change)
- **THEN** the `pty_exited` frame MUST contain `timed_out: true` and the exit code from the tmux kill

### Requirement: pty_exited frame type in WsFrame contract
The system MUST register `pty_exited` as a valid frame type in the cross-app wire contract.

- **WHEN** the `pty_exited` frame schema is added to `WsFrameSchema` in `packages/contracts/src/ws-frames.ts`
- **THEN** it MUST be included in `KNOWN_FRAME_TYPES` and validate against the discriminated union

#### Scenario: Frame validates against schema
- **WHEN** a `pty_exited` frame with all required fields is parsed
- **THEN** the Zod validation MUST pass and the frame MUST NOT be dropped

#### Scenario: Frame missing required fields
- **WHEN** a `pty_exited` frame is missing the `exit_code` field
- **THEN** the Zod validation MUST fail and the frame MUST be dropped with a log warning

### Requirement: Client parse of pty_exited frame
The web frontend MUST recognize and parse `pty_exited` frames from the booterm WS.

- **WHEN** the web frontend receives a `pty_exited` frame over the terminal WS
- **THEN** `parseServerFrame` MUST recognize it and return a structured object with `session_id`, `pane_id`, `exit_code`, `last_lines`, and session metadata

#### Scenario: Client receives pty_exited
- **WHEN** the browser receives a `pty_exited` frame
- **THEN** the terminal MUST display a styled exit notification with the exit code and last output line(s)

#### Scenario: Client receives pty_exited with timeout
- **WHEN** the browser receives a `pty_exited` frame with `timed_out: true`
- **THEN** the terminal MUST display a timeout-specific notification message

### Requirement: Backward compatibility with bare exit frame
The client MUST NOT break when receiving the legacy bare exit frame.

- **WHEN** a booterm instance sends the old `{type: 'exit', code: N}` frame (pre-upgrade)
- **THEN** the client MUST gracefully handle it as before (display exit message, no crash)

#### Scenario: Legacy exit frame received
- **WHEN** the client receives `{type: 'exit', code: 1}`
- **THEN** the terminal MUST display the exit code message without throwing
