# Service Chat Web Console Design

## Background

The current natural-language entrypoint is the terminal client in `src/bin/sg_claw_client.rs`.
That client already talks to the existing service websocket, sends `ClientMessage`, and prints
`ServiceMessage` responses.

The repository also contains a separate browser callback helper at
`http://127.0.0.1:61058/sgclaw/browser-helper.html`. That page is part of the browser backend
execution path and must remain untouched.

For this slice, the authoritative boundary is:

- the new page may talk to the existing service websocket only
- the page must not talk to the browser websocket directly
- the page must not reuse or replace `browser-helper.html`
- the page must not change the service protocol or browser execution logic

## Problem Statement

Running `cargo run --bin sg_claw_client` and typing into stdin works, but it is inconvenient for
routine usage. The user wants a simple local HTML page with a websocket connection field, a natural-
language input box, and a send button.

The risk is scope drift: if the new page reaches into the browser-helper flow or changes backend
logic, it could damage the working Zhihu/browser path.

## Goal

Add a standalone local HTML console that connects to the existing service websocket and submits
natural-language tasks using the current `submit_task` message shape.

The page should be usable without changing `sg_claw`, `sg_claw_client`, `browser-helper.html`, or
any existing service/browser runtime behavior.

## Non-goals

This slice does not include:

- serving the page from the Rust service
- changing `ClientMessage` or `ServiceMessage`
- changing `src/service/server.rs`
- changing `src/browser/callback_host.rs`
- changing `src/browser/callback_backend.rs`
- changing the helper-page bootstrap flow
- adding authentication, persistence, or multi-session orchestration
- replacing the terminal client

## Chosen Approach

Choose Option A: add one standalone HTML file that opens in a normal browser and talks to the
existing service websocket at `ws://127.0.0.1:42321` by default.

Why this option:

- it is the narrowest possible change
- it reuses the already-working service protocol
- it does not alter the browser-helper path
- it keeps all runtime ownership in the existing Rust service

Rejected alternatives:

- extend `browser-helper.html` into a chat UI: wrong boundary; that page belongs to browser
  callback orchestration, not user task entry
- add a new HTTP server inside `sg_claw`: unnecessary for the requested scope
- replace the terminal client binary: not required; both clients can coexist

## File Placement

Create the page outside `frontend/runtime-host/`.

Chosen location:

- `frontend/service-console/sg_claw_service_console.html`

Reason:

- `frontend/runtime-host/` is reserved for SuperRPA runtime-host bundles
- the new page is a standalone local tool, not a Chromium-hosted bundle
- keeping it in its own directory makes the isolation explicit

## Page Architecture

The page is a single self-contained HTML file with inline CSS and inline JavaScript.
No build step and no frontend framework are required.

The page has three UI regions:

1. Connection bar
   - websocket URL input
   - connect/disconnect button
   - current connection state label

2. Message stream
   - appends service logs in arrival order
   - distinguishes connection info, task logs, errors, and final completion
   - keeps the current session visible until the page is refreshed

3. Task composer
   - one textarea for natural-language input
   - one send button
   - send disabled while the websocket is disconnected
   - while a task is in flight, keep the composer enabled and let repeated submits surface the
     existing service-side `busy` response rather than adding a new frontend queue

## Protocol Contract

The page must reuse the existing service protocol exactly.

### Outbound message

When the user clicks send, the page sends:

```json
{
  "type": "submit_task",
  "instruction": "<user input>",
  "conversation_id": "",
  "messages": [],
  "page_url": "",
  "page_title": ""
}
```

This matches the current terminal client shape in `src/bin/sg_claw_client.rs`.

### Inbound messages

The page displays these existing `ServiceMessage` variants:

- `status_changed` -> render as a compact connection/runtime status row
- `log_entry` -> append as a chronological task log row
- `task_complete` -> append as the terminal result row for that submission
- `busy` -> append as a visible refusal/error row without automatic retry

No new message type is introduced.

## Interaction Flow

1. User opens the local HTML file with a normal browser, typically via `file://`.
2. User connects to the service websocket.
3. The page shows websocket connection status locally.
4. User enters a natural-language instruction and clicks send.
5. The page sends one `submit_task` payload over the service websocket.
6. The service continues to execute tasks exactly as it already does.
7. Incoming service messages are appended to the message stream.
8. After `task_complete`, the websocket remains open so the user can send another task.

## Error Handling

The page handles only UI-local failures:

- websocket connect failure -> show connection error and keep send disabled
- websocket disconnect mid-session -> mark disconnected and require reconnect
- empty instruction -> block send and show inline validation
- `busy` response -> show as a visible service-side refusal without retry logic

The page does not add retries, protocol fallbacks, or browser-runtime recovery logic.

## Isolation From `browser-helper.html`

This is the critical constraint.

The new page must never:

- reference `/sgclaw/browser-helper.html`
- reference `/sgclaw/callback/ready`
- reference `/sgclaw/callback/events`
- reference `/sgclaw/callback/commands/next`
- reference `/sgclaw/callback/commands/ack`
- connect to `ws://127.0.0.1:12345`

The only network target owned by the page is the service websocket, defaulting to
`ws://127.0.0.1:42321`.

Because of that boundary, the page does not interfere with the helper-page bootstrap path.

## Test Strategy

This slice stays minimal, so the automated guard is also minimal.

### Automated regression

Add one focused integration test in `tests/service_console_html_test.rs` that reads the standalone
HTML source and asserts:

- the file exists at the agreed path and is resolved from `CARGO_MANIFEST_DIR` so the test is
  stable across working directories
- it contains the service websocket default URL
- it contains `submit_task` payload construction
- it does not contain helper-page URLs or callback-host endpoints
- it does not contain the browser websocket URL

This test is a scope guard, not a browser-E2E suite.

### Manual smoke verification

With the existing service binary running:

1. open the HTML file in a browser
2. connect to the service websocket
3. confirm local websocket open/close events and service `status_changed` messages both appear in the message stream
4. submit a natural-language task
5. confirm logs and completion render in the page
6. confirm the helper-page path remains unchanged because the page never references it

## Acceptance Criteria

The slice is complete when all of the following are true:

1. `frontend/service-console/sg_claw_service_console.html` exists.
2. The page connects to the existing service websocket without backend changes.
3. The page sends the existing `submit_task` shape and receives existing `ServiceMessage` events.
4. The page does not reference `browser-helper.html`, callback-host endpoints, or the browser
   websocket URL.
5. Existing browser-helper logic remains untouched.
6. The automated source guard passes.
7. Manual smoke verification confirms a task can be submitted from the HTML page.
