# Helper Page Lifecycle Fix v2 — Same-Connection Close + Open

**Date:** 2026-04-14
**Status:** Approved

## Problem

Two issues remain after v1:

1. **Process restart leaves orphaned helper pages**: When the sg_claw process restarts, the old helper page tab remains open in the browser. The new process opens another one.
2. **Helper page is visible**: Uses `sgBrowerserOpenPage` (visible tab API) instead of `sgHideBrowerserOpenPage` (hidden domain API).

## Root Cause of v1 Failure

The v1 `close_helper_page` function created a **second** WebSocket connection to the browser during `Drop`. This likely conflicted with the existing bootstrap connection, causing the browser's WebSocket state to become confused.

## Solution

Send the close command on the **same** WebSocket connection used for bootstrap, before sending the open command:

1. Connect to browser WS
2. Register as "web" role
3. **Blindly send** `sgHideBrowerserClosePage(helper_url)` — closes any orphaned page from a previous process run
4. Send `sgHideBrowerserOpenPage(helper_url)` — opens the new helper page
5. Poll `/sgclaw/callback/ready` for page readiness

Both `use_hidden_domain = true` and the close+open logic are combined into a single change.

## Why This Works

- **Same connection**: Only one WebSocket connection to the browser. No conflict with existing connections.
- **Best-effort close**: If no orphaned page exists (first run ever), the close command is silently ignored by the browser. This does not affect the subsequent open command.
- **Fire-and-forget**: Both close and open commands use the same fire-and-forget semantics as the existing bootstrap command.

## API Reference

| API | Wire format | Effect |
|-----|------------|--------|
| `sgHideBrowerserOpenPage` (API #6) | `[requesturl, "sgHideBrowerserOpenPage", url]` | Opens in hidden domain |
| `sgHideBrowerserClosePage` (API #68) | `[requesturl, "sgHideBrowerserClosePage", url]` | Closes hidden domain page |

## Affected Files

| File | Change |
|------|--------|
| `src/browser/callback_host.rs` | In `bootstrap_helper_page`: add close command before open command |
| `src/service/server.rs` | Change `use_hidden_domain` from `false` to `true` |

## What Does NOT Change

- `callback_backend.rs` — `SHOW_AREA`, `build_command` unchanged
- `sgBrowserExcuteJsCodeByDomain` area parameter — stays `"show"`
- Helper page HTML content — unchanged
- `Drop for LiveBrowserCallbackHost` — remains simple (shutdown only, no close attempt)
- `cached_host` in `mod.rs` — remains lifted to outer loop
