Sessions/Activity panels empty: dashboard.js calls JSON-RPC methods that do not exist #13

Closed
opened 2026-04-26 13:45:52 +00:00 by salmaelsoly · 3 comments
Member

Sessions and Activity Log panels in the Browser dashboard are permanently empty because dashboard.js calls JSON-RPC methods that don't exist on the server (and expects a richer shape than what does exist).

Repro

  1. From a fresh restart, create a session (any path):
    curl -sS --unix-socket ~/hero/var/sockets/hero_browser/rpc.sock http://localhost/rpc \
      -X POST -H 'content-type: application/json' \
      -d '{"jsonrpc":"2.0","id":1,"method":"browser_create","params":{}}'
    
  2. Backend confirms the session exists:
    curl -sS --unix-socket ~/hero/var/sockets/hero_browser/rpc.sock http://localhost/rpc \
      -X POST -H 'content-type: application/json' \
      -d '{"jsonrpc":"2.0","id":1,"method":"browser_list","params":{}}'
    -> {"result":{"browsers":["<uuid>"],"count":1,"max":50}}
    
  3. Dashboard "Browser Sessions" panel keeps rendering "No active sessions"; Sessions stat stays at 0; Activity Log keeps rendering "No activity for this session".

Root cause: three mismatches in crates/hero_browser_ui/static/js/dashboard.js

# Where Dashboard calls Reality
1 dashboard.js:66 rpc("browser.list_sessions") Server registers only browser_list (underscore). Dotted name returns {"error":{"code":-32601,"message":"Method not found: browser.list_sessions"}}. fetchSessions try/catch swallows it -> {sessions:[],count:0,max:50} -> permanent empty state.
2 dashboard.js:75, dashboard.js:84 rpc("browser.get_session_activity"), rpc("browser.get_activity") Neither exists on the JSON-RPC surface. Activity is only exposed as REST GET /api/activity and GET /api/activity/{browser_id} on the rpc.sock HTTP listener (hero_browser_server/src/main.rs:487-490).
3 dashboard.js:157, dashboard.js:163 Expects {sessions: [{id, pages: [...]}, ...]} and accesses s.pages.length per session Server's browser_list returns {browsers: ["<uuid>", ...], count, max} -- array of bare IDs, no pages. Even after the method-name fix, the renderer would TypeError on s.pages.length.

OpenRPC spec confirms the gap

$ curl -sS --unix-socket ~/hero/var/sockets/hero_browser/rpc.sock http://localhost/openrpc.json | \
    jq -r '.methods[].name' | grep -iE 'browser|session|activity'
browser_create
browser_destroy
browser_list

No browser.* dotted methods. No *activity* methods. No *sessions* methods.

Two ways to fix

A. Make the dashboard match what the server actually exposes (UI-only)

  • fetchSessions: call browser_list, return {sessions: data.browsers.map(id => ({id, pages: []})), count: data.count, max: data.max} (or drop pages from the renderer).
  • fetchActivity / fetchActivityForSession: switch to fetch("api/activity") / fetch("api/activity/" + encodeURIComponent(browser_id)) (the existing REST endpoints), or remove the Activity Log panel until backend support exists.
  • Smaller blast radius; ships in the same crate that #11 already touched.

B. Add the missing JSON-RPC methods on the server (more correct, longer)

  • Add browser.list_sessions returning {sessions: [{id, pages: [{id, url, title}, ...], created_at}, ...], count, max} to match what the dashboard wants.
  • Add browser.get_activity / browser.get_session_activity wrappers around the existing ActivityLog (hero_browser_core::activity).
  • Update the openrpc spec, regenerate the SDK, then leave dashboard.js essentially as-is (only the namespace style needs aligning to whatever the spec says).
  • Touches hero_browser_server, hero_browser_sdk, and the openrpc.json that gets compiled into the SDK at build time.

Notes

  • The dotted vs underscored naming is the same kind of inconsistency the API-Docs grouping fix in #11 had to paper over (m.name.match(/^([a-z0-9]+)[._]/)); worth aligning everything either way before this grows further.
  • If we go with A, the connection-status extraInfoFn (dashboard.js:246) is already calling browser_list correctly -- only fetchSessions/fetchActivity* need rewiring.
Sessions and Activity Log panels in the Browser dashboard are permanently empty because `dashboard.js` calls JSON-RPC methods that don't exist on the server (and expects a richer shape than what does exist). ## Repro 1. From a fresh restart, create a session (any path): ``` curl -sS --unix-socket ~/hero/var/sockets/hero_browser/rpc.sock http://localhost/rpc \ -X POST -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"browser_create","params":{}}' ``` 2. Backend confirms the session exists: ``` curl -sS --unix-socket ~/hero/var/sockets/hero_browser/rpc.sock http://localhost/rpc \ -X POST -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"browser_list","params":{}}' -> {"result":{"browsers":["<uuid>"],"count":1,"max":50}} ``` 3. Dashboard "Browser Sessions" panel keeps rendering "No active sessions"; `Sessions` stat stays at `0`; `Activity Log` keeps rendering "No activity for this session". ## Root cause: three mismatches in `crates/hero_browser_ui/static/js/dashboard.js` | # | Where | Dashboard calls | Reality | |---|---|---|---| | 1 | `dashboard.js:66` | `rpc("browser.list_sessions")` | Server registers only `browser_list` (underscore). Dotted name returns `{"error":{"code":-32601,"message":"Method not found: browser.list_sessions"}}`. `fetchSessions` `try/catch` swallows it -> `{sessions:[],count:0,max:50}` -> permanent empty state. | | 2 | `dashboard.js:75`, `dashboard.js:84` | `rpc("browser.get_session_activity")`, `rpc("browser.get_activity")` | Neither exists on the JSON-RPC surface. Activity is only exposed as REST `GET /api/activity` and `GET /api/activity/{browser_id}` on the rpc.sock HTTP listener (`hero_browser_server/src/main.rs:487-490`). | | 3 | `dashboard.js:157`, `dashboard.js:163` | Expects `{sessions: [{id, pages: [...]}, ...]}` and accesses `s.pages.length` per session | Server's `browser_list` returns `{browsers: ["<uuid>", ...], count, max}` -- array of bare IDs, no `pages`. Even after the method-name fix, the renderer would `TypeError` on `s.pages.length`. | ## OpenRPC spec confirms the gap ``` $ curl -sS --unix-socket ~/hero/var/sockets/hero_browser/rpc.sock http://localhost/openrpc.json | \ jq -r '.methods[].name' | grep -iE 'browser|session|activity' browser_create browser_destroy browser_list ``` No `browser.*` dotted methods. No `*activity*` methods. No `*sessions*` methods. ## Two ways to fix ### A. Make the dashboard match what the server actually exposes (UI-only) - `fetchSessions`: call `browser_list`, return `{sessions: data.browsers.map(id => ({id, pages: []})), count: data.count, max: data.max}` (or drop `pages` from the renderer). - `fetchActivity` / `fetchActivityForSession`: switch to `fetch("api/activity")` / `fetch("api/activity/" + encodeURIComponent(browser_id))` (the existing REST endpoints), or remove the Activity Log panel until backend support exists. - Smaller blast radius; ships in the same crate that #11 already touched. ### B. Add the missing JSON-RPC methods on the server (more correct, longer) - Add `browser.list_sessions` returning `{sessions: [{id, pages: [{id, url, title}, ...], created_at}, ...], count, max}` to match what the dashboard wants. - Add `browser.get_activity` / `browser.get_session_activity` wrappers around the existing `ActivityLog` (`hero_browser_core::activity`). - Update the openrpc spec, regenerate the SDK, then leave `dashboard.js` essentially as-is (only the namespace style needs aligning to whatever the spec says). - Touches `hero_browser_server`, `hero_browser_sdk`, and the openrpc.json that gets compiled into the SDK at build time. ## Notes - The dotted vs underscored naming is the same kind of inconsistency the API-Docs grouping fix in #11 had to paper over (`m.name.match(/^([a-z0-9]+)[._]/)`); worth aligning everything either way before this grows further. - If we go with **A**, the connection-status `extraInfoFn` (`dashboard.js:246`) is already calling `browser_list` correctly -- only `fetchSessions`/`fetchActivity*` need rewiring.
Author
Member

Implementation Spec for Issue #13 (revised — A+B)

Objective

Sessions/Activity panels populate; full JSON-RPC surface gains the missing methods so the dashboard stays purely RPC-based; REST endpoints remain unchanged.

Approach: A + B (UI rewire + new server RPC methods, sharing one helper)

The dashboard currently calls dotted RPC names (browser.list_sessions, browser.get_activity, browser.get_session_activity) that the server's match req.method.as_str() (crates/hero_browser_server/src/rpc_handler.rs:92) does not recognize, so every refresh fires the Method not found (-32601) branch and the panels stay empty. We rewire the dashboard to the existing underscore convention and at the same time register three new RPC methods on the server so the dashboard stays purely RPC. To avoid drift between REST and RPC, the page-walking logic currently inlined in api_get_sessions (crates/hero_browser_server/src/main.rs:163-191) is extracted into a single async helper that both REST and RPC handlers call — guaranteeing identical SessionsResponse shapes from both surfaces. The activity endpoints share ActivityLog::entries() plus the same browser-id filter, so they collapse naturally into thin wrappers around the existing core API. openrpc.json and openrpc.rs are extended together so the SDK macro (openrpc_client! at crates/hero_browser_sdk/src/lib.rs:29) keeps compiling and gains list_sessions, get_activity, get_session_activity typed wrappers as a free side effect.

Requirements

  • Dashboard Sessions / Activity panels populate correctly.
  • New JSON-RPC methods exist: browser_list_sessions, browser_get_activity, browser_get_session_activity (underscore style, matching existing convention in crates/hero_browser_server/src/rpc_handler.rs).
  • REST /api/sessions, /api/activity, /api/activity/{browser_id} continue to work unchanged.
  • Single source of truth for page-walking — REST and RPC must return identical SessionsResponse shapes.
  • crates/hero_browser_server/openrpc.json and crates/hero_browser_server/src/openrpc.rs updated together so SDK build does not drift.
  • Integration Guide tab in crates/hero_browser_ui/templates/index.html documents the real RPC + REST surface.
  • All existing tests pass; new RPC methods are covered by tests added to crates/hero_browser_server/src/main.rs tests module (since rpc_handler.rs has no test module today, follow the existing test_router() pattern there).

Files to Modify/Create

  • crates/hero_browser_server/src/sessions.rs (NEW) — module exposing SessionInfo, PageInfo, SessionsResponse, plus pub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse. Lives in hero_browser_server (not core) because SessionsResponse is a transport DTO with Serialize-only semantics; hero_browser_core is otherwise transport-agnostic and the JSON shape belongs to the server boundary.
  • crates/hero_browser_server/src/main.rs — declare mod sessions;, drop the inlined page-walk, make api_get_sessions call sessions::collect_sessions, re-export the types from sessions (or keep using them via the module path), add two new tests::test_router routes if needed for new RPC tests.
  • crates/hero_browser_server/src/rpc_handler.rs — three new match arms: browser_list_sessions, browser_get_activity, browser_get_session_activity (use state.pool and state.activity_log, call into crate::sessions::collect_sessions for sessions, into state.activity_log.entries() for activity, with the same browser-id filter as api_get_activity_for_session for the per-session variant). Add #[cfg(test)] mod tests exercising them via direct handler invocation.
  • crates/hero_browser_server/openrpc.json — three new method entries matching the existing entry shape (params/result schemas), placed adjacent to browser_list.
  • crates/hero_browser_server/src/openrpc.rs — three new method(...) entries adjacent to browser_list so rpc.discover matches openrpc.json.
  • crates/hero_browser_ui/static/js/dashboard.js — replace three dotted method names with underscore equivalents in fetchSessions, fetchActivityForSession, fetchActivity. Confirm data.sessions[i].browser_id / pages[i].page_id/url fields and ActivityEntry field names (timestamp, command, args_summary, browser_id) still match.
  • crates/hero_browser_ui/templates/index.html — fix Integration Guide "RPC Methods" bullets (lines 150-155) to list the real underscore RPC names; add a "REST Endpoints" subsection listing /api/sessions, /api/activity, /api/activity/{browser_id}, and the existing /api/sessions/{browser_id}/pages/{page_id}/stream SSE.

Implementation Plan

Step 1: Extract shared sessions helper

Files: crates/hero_browser_server/src/sessions.rs (NEW), crates/hero_browser_server/src/main.rs

  • Create crates/hero_browser_server/src/sessions.rs with SessionInfo, PageInfo, SessionsResponse (moved verbatim from main.rs:88-105, re-exported pub) and pub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse containing the exact loop currently at main.rs:167-190.
  • In main.rs, add mod sessions;, delete the local struct definitions and the inlined loop, and rewrite api_get_sessions as a one-liner: Json(sessions::collect_sessions(&state.pool).await).
  • Verify cargo check -p hero_browser_server passes and that test_sessions_returns_json still asserts the expected shape (no change needed; test only checks sessions and count keys).
    Dependencies: none

Step 2: Add three new JSON-RPC methods in rpc_handler.rs

Files: crates/hero_browser_server/src/rpc_handler.rs

  • Add three new arms to the match req.method.as_str() block (place them adjacent to browser_list at line 129).
    • "browser_list_sessions" -> Ok(serde_json::to_value(&crate::sessions::collect_sessions(&state.pool).await).map_err(|e| e.to_string())?). Since the surrounding closure returns Result<Value, String>, use a local match rather than ?.
    • "browser_get_activity" -> Ok(serde_json::to_value(&state.activity_log.entries()).unwrap_or(json!([]))).
    • "browser_get_session_activity" -> require browser_id (use the existing p(params, "browser_id") + missing-param error pattern from browser_destroy), then state.activity_log.entries().into_iter().filter(|e| e.browser_id.as_deref() == Some(&bid)).collect::<Vec<_>>() and serialize.
  • Use crate::sessions (added in Step 1) so there is exactly one collector.
    Dependencies: Step 1

Step 3: Update openrpc.json + openrpc.rs to declare the three new methods

Files: crates/hero_browser_server/openrpc.json, crates/hero_browser_server/src/openrpc.rs

  • In openrpc.json, insert three new entries after the browser_list block (line 88), matching the existing entry shape:
    • browser_list_sessions: no params; result schema {type: object, properties: {sessions: {type: array, items: {type: object}}, count: {type: integer}, max: {type: integer}}}.
    • browser_get_activity: no params; result schema {type: array, items: {type: object}}.
    • browser_get_session_activity: param browser_id (string); result schema same as browser_get_activity.
  • In openrpc.rs, insert three matching method(...) entries adjacent to the browser_list registration (line 38).
  • Confirm both files stay in lockstep — the SDK build (cargo build -p hero_browser_sdk) must succeed because the openrpc_client! macro consumes openrpc.json at compile time.
    Dependencies: none for content; should be paired with Step 2 to avoid landing methods in the spec that the server does not implement (or vice versa). Can be implemented in parallel with Step 2 and merged in the same PR.

Step 4: Add tests for the three new RPC methods

Files: crates/hero_browser_server/src/rpc_handler.rs

  • Add a #[cfg(test)] mod tests at the bottom of rpc_handler.rs.
  • Build a test AppState (pool + activity_log) the same way main.rs test_router() does (lines 615-618).
  • For each new method, construct a JsonRpcRequest and call rpc_handler directly via axum::extract::State (or reuse a local test router that mounts only /rpc). Three tests:
    • test_rpc_browser_list_sessions_returns_shape — empty pool returns {sessions: [], count: 0, max: <max>}.
    • test_rpc_browser_get_activity_returns_array — empty log returns []; after activity_log.log(...) returns one entry with the expected fields.
    • test_rpc_browser_get_session_activity_filters_by_browser_id — push two entries with different browser_ids, assert only the matching one comes back; assert missing browser_id param yields -32602.
  • Add a parity test in main.rs (or sessions.rs) test_rest_and_rpc_sessions_match that calls both api_get_sessions (via the test router) and collect_sessions directly, then serde_json::to_value both and assert_eq! — defends the single-source-of-truth invariant.
    Dependencies: Step 2 (and Step 1 for the parity test)

Step 5: Rewire dashboard.js to call the new RPC methods

Files: crates/hero_browser_ui/static/js/dashboard.js

  • Line 66: rpc("browser.list_sessions") -> rpc("browser_list_sessions").
  • Line 75: rpc("browser.get_session_activity", { browser_id: browserId }) -> rpc("browser_get_session_activity", { browser_id: browserId }).
  • Line 84: rpc("browser.get_activity") -> rpc("browser_get_activity").
  • Do NOT touch line 246 (rpc("browser_list")), which is already correct.
  • No renderer changes needed: renderSessions reads data.sessions[i].browser_id, data.pages[i].page_id, data.count, data.max — all match SessionsResponse exactly. renderActivityForSession reads e.timestamp, e.command, e.args_summary — all match ActivityEntry.
    Dependencies: Steps 1-3 (server must expose the methods first if dev-testing locally). Independent of Step 4 (test-only).

Step 6: Fix Integration Guide tab in index.html

Files: crates/hero_browser_ui/templates/index.html

  • Replace the "RPC Methods" <ul> at lines 150-155 with the real method names:
    • browser_list_sessions — List active browser sessions (one entry per browser, with pages).
    • browser_get_activity — Get all recent activity log entries (capped at 100).
    • browser_get_session_activity — Get activity entries filtered to a specific browser_id.
    • browser_list — List browser IDs only (used by the connection-status widget).
  • Add a new "REST Endpoints" subsection listing the four real REST routes from main.rs:486-495:
    • GET /api/sessions
    • GET /api/activity
    • GET /api/activity/{browser_id}
    • GET /api/sessions/{browser_id}/pages/{page_id}/stream (SSE, ?fps= 1-10).
  • Optionally add a one-liner pointing to the OpenRPC button in the navbar (base.html:27) and the API Docs tab.
    Dependencies: none. Can run in parallel with everything else.

Parallelization summary: Steps 1, 3, 6 are independent. Steps 2 and 4 depend on Step 1. Step 5 depends on Steps 1-3 for end-to-end testing but can be implemented independently. The orchestrator can run Steps 1+3+6 in parallel, then Step 2, then Steps 4+5 in parallel.

Acceptance Criteria

  • After browser_create, the Sessions panel shows the new browser ID and its page count within one 3 s refresh tick.
  • Activity Log panel populates with entries for the selected session.
  • browser_list_sessions, browser_get_activity, browser_get_session_activity all return the documented shapes when called via curl on rpc.sock (and via TCP 127.0.0.1:8884/rpc).
  • /api/sessions returns the byte-identical shape as browser_list_sessions (verified by parity test in Step 4).
  • All existing tests in cargo test --workspace (or make test) still pass.
  • New RPC methods are covered by tests in rpc_handler.rs.
  • No console.error / unhandled rejection in DevTools during a normal refresh cycle.
  • Integration Guide tab no longer references browser.list_sessions / browser.get_activity / browser.get_session_activity.
  • cargo build -p hero_browser_sdk still succeeds (openrpc.json + openrpc_client! macro stay consistent and now expose three new typed wrappers).

Notes

  • Convention reminder: this repo uses service_method underscore style for ALL JSON-RPC methods (verified across 50+ existing methods in rpc_handler.rs and openrpc.json). Do NOT introduce dotted names.
  • crates/hero_browser_ui/templates/base.html:7 <base href="{{ base_path }}/"> means dashboard.js must keep using bare relative URLs (fetch("rpc", ...), no leading slash). The rewire only touches method names, not the URL.
  • The SDK is generated at compile time via openrpc_client! macro (crates/hero_browser_sdk/src/lib.rs:29) from crates/hero_browser_server/openrpc.json. Any signature drift between openrpc.json and openrpc.rs / actual handler signatures will break the SDK build. Keep them aligned in the same commit.
  • The UI does not run a /rpc proxy of its own; per crates/hero_browser_ui/src/main.rs:4, RPC routing is handled by hero_router, which routes <prefix>/rpc directly to rpc.sock. So the new methods are reachable from the dashboard the moment the server registers them — no UI-side proxy work needed.
  • SessionsResponse is purely a server-boundary DTO; do NOT hoist it into hero_browser_core (which has no serde_json::Value / Axum dependency surface). The new module crates/hero_browser_server/src/sessions.rs is the lowest layer that already owns the JSON contract.
  • The REST handler api_get_activity_for_session (main.rs:199-209) and the new browser_get_session_activity RPC method use identical filter logic — consider extracting that one-liner into crate::sessions too (e.g. pub fn activity_for_browser(log: &ActivityLog, browser_id: &str) -> Vec<ActivityEntry>) for symmetry.
  • Test invocation: use make test (which runs cargo test --workspace --lib --bins --tests) per Makefile:107-108. make smoke-test runs only the in-process binary tests in main.rs and is the fastest signal for the new parity test.
## Implementation Spec for Issue #13 (revised — A+B) ### Objective Sessions/Activity panels populate; full JSON-RPC surface gains the missing methods so the dashboard stays purely RPC-based; REST endpoints remain unchanged. ### Approach: A + B (UI rewire + new server RPC methods, sharing one helper) The dashboard currently calls dotted RPC names (`browser.list_sessions`, `browser.get_activity`, `browser.get_session_activity`) that the server's `match req.method.as_str()` (`crates/hero_browser_server/src/rpc_handler.rs:92`) does not recognize, so every refresh fires the `Method not found` (-32601) branch and the panels stay empty. We rewire the dashboard to the existing underscore convention and at the same time register three new RPC methods on the server so the dashboard stays purely RPC. To avoid drift between REST and RPC, the page-walking logic currently inlined in `api_get_sessions` (`crates/hero_browser_server/src/main.rs:163-191`) is extracted into a single async helper that both REST and RPC handlers call — guaranteeing identical `SessionsResponse` shapes from both surfaces. The activity endpoints share `ActivityLog::entries()` plus the same browser-id filter, so they collapse naturally into thin wrappers around the existing core API. `openrpc.json` and `openrpc.rs` are extended together so the SDK macro (`openrpc_client!` at `crates/hero_browser_sdk/src/lib.rs:29`) keeps compiling and gains `list_sessions`, `get_activity`, `get_session_activity` typed wrappers as a free side effect. ### Requirements - Dashboard Sessions / Activity panels populate correctly. - New JSON-RPC methods exist: `browser_list_sessions`, `browser_get_activity`, `browser_get_session_activity` (underscore style, matching existing convention in `crates/hero_browser_server/src/rpc_handler.rs`). - REST `/api/sessions`, `/api/activity`, `/api/activity/{browser_id}` continue to work unchanged. - Single source of truth for page-walking — REST and RPC must return identical `SessionsResponse` shapes. - `crates/hero_browser_server/openrpc.json` and `crates/hero_browser_server/src/openrpc.rs` updated together so SDK build does not drift. - Integration Guide tab in `crates/hero_browser_ui/templates/index.html` documents the real RPC + REST surface. - All existing tests pass; new RPC methods are covered by tests added to `crates/hero_browser_server/src/main.rs` `tests` module (since `rpc_handler.rs` has no test module today, follow the existing `test_router()` pattern there). ### Files to Modify/Create - `crates/hero_browser_server/src/sessions.rs` (NEW) — module exposing `SessionInfo`, `PageInfo`, `SessionsResponse`, plus `pub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse`. Lives in `hero_browser_server` (not core) because `SessionsResponse` is a transport DTO with `Serialize`-only semantics; `hero_browser_core` is otherwise transport-agnostic and the JSON shape belongs to the server boundary. - `crates/hero_browser_server/src/main.rs` — declare `mod sessions;`, drop the inlined page-walk, make `api_get_sessions` call `sessions::collect_sessions`, re-export the types from `sessions` (or keep using them via the module path), add two new `tests::test_router` routes if needed for new RPC tests. - `crates/hero_browser_server/src/rpc_handler.rs` — three new match arms: `browser_list_sessions`, `browser_get_activity`, `browser_get_session_activity` (use `state.pool` and `state.activity_log`, call into `crate::sessions::collect_sessions` for sessions, into `state.activity_log.entries()` for activity, with the same browser-id filter as `api_get_activity_for_session` for the per-session variant). Add `#[cfg(test)] mod tests` exercising them via direct handler invocation. - `crates/hero_browser_server/openrpc.json` — three new method entries matching the existing entry shape (params/result schemas), placed adjacent to `browser_list`. - `crates/hero_browser_server/src/openrpc.rs` — three new `method(...)` entries adjacent to `browser_list` so `rpc.discover` matches `openrpc.json`. - `crates/hero_browser_ui/static/js/dashboard.js` — replace three dotted method names with underscore equivalents in `fetchSessions`, `fetchActivityForSession`, `fetchActivity`. Confirm `data.sessions[i].browser_id` / `pages[i].page_id`/`url` fields and `ActivityEntry` field names (`timestamp`, `command`, `args_summary`, `browser_id`) still match. - `crates/hero_browser_ui/templates/index.html` — fix Integration Guide "RPC Methods" bullets (lines 150-155) to list the real underscore RPC names; add a "REST Endpoints" subsection listing `/api/sessions`, `/api/activity`, `/api/activity/{browser_id}`, and the existing `/api/sessions/{browser_id}/pages/{page_id}/stream` SSE. ### Implementation Plan #### Step 1: Extract shared sessions helper Files: `crates/hero_browser_server/src/sessions.rs` (NEW), `crates/hero_browser_server/src/main.rs` - Create `crates/hero_browser_server/src/sessions.rs` with `SessionInfo`, `PageInfo`, `SessionsResponse` (moved verbatim from `main.rs:88-105`, re-exported `pub`) and `pub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse` containing the exact loop currently at `main.rs:167-190`. - In `main.rs`, add `mod sessions;`, delete the local struct definitions and the inlined loop, and rewrite `api_get_sessions` as a one-liner: `Json(sessions::collect_sessions(&state.pool).await)`. - Verify `cargo check -p hero_browser_server` passes and that `test_sessions_returns_json` still asserts the expected shape (no change needed; test only checks `sessions` and `count` keys). Dependencies: none #### Step 2: Add three new JSON-RPC methods in rpc_handler.rs Files: `crates/hero_browser_server/src/rpc_handler.rs` - Add three new arms to the `match req.method.as_str()` block (place them adjacent to `browser_list` at line 129). - `"browser_list_sessions"` -> `Ok(serde_json::to_value(&crate::sessions::collect_sessions(&state.pool).await).map_err(|e| e.to_string())?)`. Since the surrounding closure returns `Result<Value, String>`, use a local `match` rather than `?`. - `"browser_get_activity"` -> `Ok(serde_json::to_value(&state.activity_log.entries()).unwrap_or(json!([])))`. - `"browser_get_session_activity"` -> require `browser_id` (use the existing `p(params, "browser_id")` + missing-param error pattern from `browser_destroy`), then `state.activity_log.entries().into_iter().filter(|e| e.browser_id.as_deref() == Some(&bid)).collect::<Vec<_>>()` and serialize. - Use `crate::sessions` (added in Step 1) so there is exactly one collector. Dependencies: Step 1 #### Step 3: Update openrpc.json + openrpc.rs to declare the three new methods Files: `crates/hero_browser_server/openrpc.json`, `crates/hero_browser_server/src/openrpc.rs` - In `openrpc.json`, insert three new entries after the `browser_list` block (line 88), matching the existing entry shape: - `browser_list_sessions`: no params; result schema `{type: object, properties: {sessions: {type: array, items: {type: object}}, count: {type: integer}, max: {type: integer}}}`. - `browser_get_activity`: no params; result schema `{type: array, items: {type: object}}`. - `browser_get_session_activity`: param `browser_id` (string); result schema same as `browser_get_activity`. - In `openrpc.rs`, insert three matching `method(...)` entries adjacent to the `browser_list` registration (line 38). - Confirm both files stay in lockstep — the SDK build (`cargo build -p hero_browser_sdk`) must succeed because the `openrpc_client!` macro consumes `openrpc.json` at compile time. Dependencies: none for content; should be paired with Step 2 to avoid landing methods in the spec that the server does not implement (or vice versa). Can be implemented in parallel with Step 2 and merged in the same PR. #### Step 4: Add tests for the three new RPC methods Files: `crates/hero_browser_server/src/rpc_handler.rs` - Add a `#[cfg(test)] mod tests` at the bottom of `rpc_handler.rs`. - Build a test `AppState` (`pool` + `activity_log`) the same way `main.rs` `test_router()` does (lines 615-618). - For each new method, construct a `JsonRpcRequest` and call `rpc_handler` directly via `axum::extract::State` (or reuse a local test router that mounts only `/rpc`). Three tests: - `test_rpc_browser_list_sessions_returns_shape` — empty pool returns `{sessions: [], count: 0, max: <max>}`. - `test_rpc_browser_get_activity_returns_array` — empty log returns `[]`; after `activity_log.log(...)` returns one entry with the expected fields. - `test_rpc_browser_get_session_activity_filters_by_browser_id` — push two entries with different browser_ids, assert only the matching one comes back; assert missing `browser_id` param yields `-32602`. - Add a parity test in `main.rs` (or `sessions.rs`) `test_rest_and_rpc_sessions_match` that calls both `api_get_sessions` (via the test router) and `collect_sessions` directly, then `serde_json::to_value` both and `assert_eq!` — defends the single-source-of-truth invariant. Dependencies: Step 2 (and Step 1 for the parity test) #### Step 5: Rewire dashboard.js to call the new RPC methods Files: `crates/hero_browser_ui/static/js/dashboard.js` - Line 66: `rpc("browser.list_sessions")` -> `rpc("browser_list_sessions")`. - Line 75: `rpc("browser.get_session_activity", { browser_id: browserId })` -> `rpc("browser_get_session_activity", { browser_id: browserId })`. - Line 84: `rpc("browser.get_activity")` -> `rpc("browser_get_activity")`. - Do NOT touch line 246 (`rpc("browser_list")`), which is already correct. - No renderer changes needed: `renderSessions` reads `data.sessions[i].browser_id`, `data.pages[i].page_id`, `data.count`, `data.max` — all match `SessionsResponse` exactly. `renderActivityForSession` reads `e.timestamp`, `e.command`, `e.args_summary` — all match `ActivityEntry`. Dependencies: Steps 1-3 (server must expose the methods first if dev-testing locally). Independent of Step 4 (test-only). #### Step 6: Fix Integration Guide tab in index.html Files: `crates/hero_browser_ui/templates/index.html` - Replace the "RPC Methods" `<ul>` at lines 150-155 with the real method names: - `browser_list_sessions` — List active browser sessions (one entry per browser, with pages). - `browser_get_activity` — Get all recent activity log entries (capped at 100). - `browser_get_session_activity` — Get activity entries filtered to a specific `browser_id`. - `browser_list` — List browser IDs only (used by the connection-status widget). - Add a new "REST Endpoints" subsection listing the four real REST routes from `main.rs:486-495`: - `GET /api/sessions` - `GET /api/activity` - `GET /api/activity/{browser_id}` - `GET /api/sessions/{browser_id}/pages/{page_id}/stream` (SSE, `?fps=` 1-10). - Optionally add a one-liner pointing to the OpenRPC button in the navbar (`base.html:27`) and the API Docs tab. Dependencies: none. Can run in parallel with everything else. Parallelization summary: Steps 1, 3, 6 are independent. Steps 2 and 4 depend on Step 1. Step 5 depends on Steps 1-3 for end-to-end testing but can be implemented independently. The orchestrator can run Steps 1+3+6 in parallel, then Step 2, then Steps 4+5 in parallel. ### Acceptance Criteria - [ ] After `browser_create`, the Sessions panel shows the new browser ID and its page count within one 3 s refresh tick. - [ ] Activity Log panel populates with entries for the selected session. - [ ] `browser_list_sessions`, `browser_get_activity`, `browser_get_session_activity` all return the documented shapes when called via curl on `rpc.sock` (and via TCP `127.0.0.1:8884/rpc`). - [ ] `/api/sessions` returns the byte-identical shape as `browser_list_sessions` (verified by parity test in Step 4). - [ ] All existing tests in `cargo test --workspace` (or `make test`) still pass. - [ ] New RPC methods are covered by tests in `rpc_handler.rs`. - [ ] No `console.error` / unhandled rejection in DevTools during a normal refresh cycle. - [ ] Integration Guide tab no longer references `browser.list_sessions` / `browser.get_activity` / `browser.get_session_activity`. - [ ] `cargo build -p hero_browser_sdk` still succeeds (`openrpc.json` + `openrpc_client!` macro stay consistent and now expose three new typed wrappers). ### Notes - Convention reminder: this repo uses `service_method` underscore style for ALL JSON-RPC methods (verified across 50+ existing methods in `rpc_handler.rs` and `openrpc.json`). Do NOT introduce dotted names. - `crates/hero_browser_ui/templates/base.html:7` `<base href="{{ base_path }}/">` means dashboard.js must keep using bare relative URLs (`fetch("rpc", ...)`, no leading slash). The rewire only touches method names, not the URL. - The SDK is generated at compile time via `openrpc_client!` macro (`crates/hero_browser_sdk/src/lib.rs:29`) from `crates/hero_browser_server/openrpc.json`. Any signature drift between `openrpc.json` and `openrpc.rs` / actual handler signatures will break the SDK build. Keep them aligned in the same commit. - The UI does not run a `/rpc` proxy of its own; per `crates/hero_browser_ui/src/main.rs:4`, RPC routing is handled by `hero_router`, which routes `<prefix>/rpc` directly to `rpc.sock`. So the new methods are reachable from the dashboard the moment the server registers them — no UI-side proxy work needed. - `SessionsResponse` is purely a server-boundary DTO; do NOT hoist it into `hero_browser_core` (which has no `serde_json::Value` / Axum dependency surface). The new module `crates/hero_browser_server/src/sessions.rs` is the lowest layer that already owns the JSON contract. - The REST handler `api_get_activity_for_session` (`main.rs:199-209`) and the new `browser_get_session_activity` RPC method use identical filter logic — consider extracting that one-liner into `crate::sessions` too (e.g. `pub fn activity_for_browser(log: &ActivityLog, browser_id: &str) -> Vec<ActivityEntry>`) for symmetry. - Test invocation: use `make test` (which runs `cargo test --workspace --lib --bins --tests`) per `Makefile:107-108`. `make smoke-test` runs only the in-process binary tests in `main.rs` and is the fastest signal for the new parity test.
Author
Member

Test Results

  • Total: 14
  • Passed: 14
  • Failed: 0
  • Ignored: 0

New tests for issue #13 (all passing)

  • test_rpc_browser_list_sessions_returns_shape
  • test_rpc_browser_get_activity_returns_array
  • test_rpc_browser_get_session_activity_filters_by_browser_id
  • test_rest_and_rpc_sessions_match (REST/RPC parity invariant)

Test command

make test (cargo test --workspace --lib --bins --tests)

SDK build verification

cargo build -p hero_browser_sdk — success. Confirms the new openrpc.json entries do not break the openrpc_client! macro consumer.

## Test Results - Total: 14 - Passed: 14 - Failed: 0 - Ignored: 0 ### New tests for issue #13 (all passing) - `test_rpc_browser_list_sessions_returns_shape` - `test_rpc_browser_get_activity_returns_array` - `test_rpc_browser_get_session_activity_filters_by_browser_id` - `test_rest_and_rpc_sessions_match` (REST/RPC parity invariant) ### Test command `make test` (cargo test --workspace --lib --bins --tests) ### SDK build verification `cargo build -p hero_browser_sdk` — success. Confirms the new `openrpc.json` entries do not break the `openrpc_client!` macro consumer.
Author
Member

Implementation Summary

Approach A + B (UI rewire + new server RPC methods sharing one helper) shipped on branch development_dashboard_rpc_fix.

Changes

Server (Approach B)

  • crates/hero_browser_server/src/sessions.rs (new) — single source of truth: SessionInfo, PageInfo, SessionsResponse, and pub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse. Both REST and RPC handlers call it.
  • crates/hero_browser_server/src/main.rs — added mod sessions;, removed inlined struct definitions and page-walk loop, rewrote api_get_sessions as a one-liner that calls sessions::collect_sessions.
  • crates/hero_browser_server/src/rpc_handler.rs — three new match arms registered next to browser_list:
    • browser_list_sessions — delegates to sessions::collect_sessions.
    • browser_get_activity — returns state.activity_log.entries().
    • browser_get_session_activity — required browser_id param (returns -32602 if missing); filters activity by entry.browser_id.as_deref() == Some(&browser_id) (byte-identical to api_get_activity_for_session in main.rs).
  • crates/hero_browser_server/openrpc.json and crates/hero_browser_server/src/openrpc.rs — three new method declarations matching the existing entry shape; both files updated together so the SDK macro (openrpc_client!) keeps compiling and now exposes typed wrappers for the new methods.
  • crates/hero_browser_server/src/main.rs#![recursion_limit = "256"] at the crate root (the deeply-nested json! schemas in openrpc.rs exceed the default 128 limit).

UI (Approach A)

  • crates/hero_browser_ui/static/js/dashboard.js — three method-name renames (dotted to underscore) at lines 66, 75, 84:

    • browser.list_sessions -> browser_list_sessions
    • browser.get_session_activity -> browser_get_session_activity
    • browser.get_activity -> browser_get_activity

    Renderers untouched; existing field-name expectations (browser_id, pages, count, max, timestamp, command, args_summary) already matched the server response shapes.

  • crates/hero_browser_ui/templates/index.html — Integration Guide tab updated: the broken bullets (browser.list_sessions etc.) replaced with the real underscore RPC method names; new "REST Endpoints" subsection added documenting GET /api/sessions, GET /api/activity, GET /api/activity/{browser_id}, and the SSE stream route.

  • crates/hero_browser_ui/static/css/dashboard.css — light-mode bug fix discovered during manual testing: [data-bs-theme="light"] .session-item { background: #fff; } was clobbering .session-item.active's blue background due to equal specificity + later cascade position, leaving a selected session as white-on-white text. Scoped both light-mode overrides with :not(.active). Pre-existing bug, only became visible because the panel finally populates.

Tests

make test — 14 passing, 0 failing, 0 ignored.

Four new tests added in crates/hero_browser_server/src/rpc_handler.rs::tests:

  • test_rpc_browser_list_sessions_returns_shape — empty pool returns {sessions: [], count: 0, max: PoolConfig::default().max_browsers}.
  • test_rpc_browser_get_activity_returns_array — seeded entry roundtrips with correct fields.
  • test_rpc_browser_get_session_activity_filters_by_browser_id — filter correctness; missing param yields -32602.
  • test_rest_and_rpc_sessions_match — REST/RPC parity: collect_sessions helper, api_get_sessions REST handler, and browser_list_sessions RPC handler all serialize to byte-identical JSON. Defends the single-source-of-truth invariant.

SDK build verification

cargo build -p hero_browser_sdk succeeds. The openrpc_client! macro consumes the updated openrpc.json and now generates typed wrappers for browser_list_sessions, browser_get_activity, and browser_get_session_activity automatically.

Manual verification

Restarted via service_browser start --reset. Created sessions and pages via MCP tools, opened the dashboard at the router-mapped URL.

Verified via JSON-RPC (browser_list_sessions):

{
  "count": 2,
  "max": 50,
  "sessions": [
    {"browser_id": "ed5586c1-...", "pages": []},
    {"browser_id": "28605ed9-...", "pages": [{"page_id": "dcb70073-...", "url": "https://example.com/"}]}
  ]
}

Dashboard observed:

  • Sessions panel populates within one 3 s refresh tick.
  • Statistics card consistent (Sessions, Pages, Max, Activity).
  • Activity Log right pane populates per-session via browser_get_session_activity.
  • No console errors / unhandled rejections.
  • Selected session readable in both dark and light mode.

Acceptance criteria

  • After browser_create, the Sessions panel shows the new browser ID and its page count.
  • Activity Log panel populates with entries for the selected session.
  • All three new RPC methods return the documented shapes.
  • /api/sessions returns byte-identical shape as browser_list_sessions (parity test).
  • All existing + new tests pass.
  • No console errors.
  • Integration Guide tab no longer references browser.list_sessions / browser.get_activity / browser.get_session_activity.
  • cargo build -p hero_browser_sdk succeeds.
## Implementation Summary Approach **A + B** (UI rewire + new server RPC methods sharing one helper) shipped on branch `development_dashboard_rpc_fix`. ### Changes #### Server (Approach B) - `crates/hero_browser_server/src/sessions.rs` (new) — single source of truth: `SessionInfo`, `PageInfo`, `SessionsResponse`, and `pub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse`. Both REST and RPC handlers call it. - `crates/hero_browser_server/src/main.rs` — added `mod sessions;`, removed inlined struct definitions and page-walk loop, rewrote `api_get_sessions` as a one-liner that calls `sessions::collect_sessions`. - `crates/hero_browser_server/src/rpc_handler.rs` — three new match arms registered next to `browser_list`: - `browser_list_sessions` — delegates to `sessions::collect_sessions`. - `browser_get_activity` — returns `state.activity_log.entries()`. - `browser_get_session_activity` — required `browser_id` param (returns `-32602` if missing); filters activity by `entry.browser_id.as_deref() == Some(&browser_id)` (byte-identical to `api_get_activity_for_session` in `main.rs`). - `crates/hero_browser_server/openrpc.json` and `crates/hero_browser_server/src/openrpc.rs` — three new method declarations matching the existing entry shape; both files updated together so the SDK macro (`openrpc_client!`) keeps compiling and now exposes typed wrappers for the new methods. - `crates/hero_browser_server/src/main.rs` — `#![recursion_limit = "256"]` at the crate root (the deeply-nested `json!` schemas in `openrpc.rs` exceed the default 128 limit). #### UI (Approach A) - `crates/hero_browser_ui/static/js/dashboard.js` — three method-name renames (dotted to underscore) at lines 66, 75, 84: - `browser.list_sessions` -> `browser_list_sessions` - `browser.get_session_activity` -> `browser_get_session_activity` - `browser.get_activity` -> `browser_get_activity` Renderers untouched; existing field-name expectations (`browser_id`, `pages`, `count`, `max`, `timestamp`, `command`, `args_summary`) already matched the server response shapes. - `crates/hero_browser_ui/templates/index.html` — Integration Guide tab updated: the broken bullets (`browser.list_sessions` etc.) replaced with the real underscore RPC method names; new "REST Endpoints" subsection added documenting `GET /api/sessions`, `GET /api/activity`, `GET /api/activity/{browser_id}`, and the SSE stream route. - `crates/hero_browser_ui/static/css/dashboard.css` — light-mode bug fix discovered during manual testing: `[data-bs-theme="light"] .session-item { background: #fff; }` was clobbering `.session-item.active`'s blue background due to equal specificity + later cascade position, leaving a selected session as white-on-white text. Scoped both light-mode overrides with `:not(.active)`. Pre-existing bug, only became visible because the panel finally populates. ### Tests `make test` — 14 passing, 0 failing, 0 ignored. Four new tests added in `crates/hero_browser_server/src/rpc_handler.rs::tests`: - `test_rpc_browser_list_sessions_returns_shape` — empty pool returns `{sessions: [], count: 0, max: PoolConfig::default().max_browsers}`. - `test_rpc_browser_get_activity_returns_array` — seeded entry roundtrips with correct fields. - `test_rpc_browser_get_session_activity_filters_by_browser_id` — filter correctness; missing param yields `-32602`. - `test_rest_and_rpc_sessions_match` — REST/RPC parity: `collect_sessions` helper, `api_get_sessions` REST handler, and `browser_list_sessions` RPC handler all serialize to byte-identical JSON. Defends the single-source-of-truth invariant. ### SDK build verification `cargo build -p hero_browser_sdk` succeeds. The `openrpc_client!` macro consumes the updated `openrpc.json` and now generates typed wrappers for `browser_list_sessions`, `browser_get_activity`, and `browser_get_session_activity` automatically. ### Manual verification Restarted via `service_browser start --reset`. Created sessions and pages via MCP tools, opened the dashboard at the router-mapped URL. Verified via JSON-RPC (`browser_list_sessions`): ```json { "count": 2, "max": 50, "sessions": [ {"browser_id": "ed5586c1-...", "pages": []}, {"browser_id": "28605ed9-...", "pages": [{"page_id": "dcb70073-...", "url": "https://example.com/"}]} ] } ``` Dashboard observed: - Sessions panel populates within one 3 s refresh tick. - Statistics card consistent (Sessions, Pages, Max, Activity). - Activity Log right pane populates per-session via `browser_get_session_activity`. - No console errors / unhandled rejections. - Selected session readable in both dark and light mode. ### Acceptance criteria - [x] After `browser_create`, the Sessions panel shows the new browser ID and its page count. - [x] Activity Log panel populates with entries for the selected session. - [x] All three new RPC methods return the documented shapes. - [x] `/api/sessions` returns byte-identical shape as `browser_list_sessions` (parity test). - [x] All existing + new tests pass. - [x] No console errors. - [x] Integration Guide tab no longer references `browser.list_sessions` / `browser.get_activity` / `browser.get_session_activity`. - [x] `cargo build -p hero_browser_sdk` succeeds.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_browser#13
No description provided.