Sessions/Activity panels empty: dashboard.js calls JSON-RPC methods that do not exist #13
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_browser#13
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Sessions and Activity Log panels in the Browser dashboard are permanently empty because
dashboard.jscalls JSON-RPC methods that don't exist on the server (and expects a richer shape than what does exist).Repro
Sessionsstat stays at0;Activity Logkeeps rendering "No activity for this session".Root cause: three mismatches in
crates/hero_browser_ui/static/js/dashboard.jsdashboard.js:66rpc("browser.list_sessions")browser_list(underscore). Dotted name returns{"error":{"code":-32601,"message":"Method not found: browser.list_sessions"}}.fetchSessionstry/catchswallows it ->{sessions:[],count:0,max:50}-> permanent empty state.dashboard.js:75,dashboard.js:84rpc("browser.get_session_activity"),rpc("browser.get_activity")GET /api/activityandGET /api/activity/{browser_id}on the rpc.sock HTTP listener (hero_browser_server/src/main.rs:487-490).dashboard.js:157,dashboard.js:163{sessions: [{id, pages: [...]}, ...]}and accessess.pages.lengthper sessionbrowser_listreturns{browsers: ["<uuid>", ...], count, max}-- array of bare IDs, nopages. Even after the method-name fix, the renderer wouldTypeErrorons.pages.length.OpenRPC spec confirms the gap
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: callbrowser_list, return{sessions: data.browsers.map(id => ({id, pages: []})), count: data.count, max: data.max}(or droppagesfrom the renderer).fetchActivity/fetchActivityForSession: switch tofetch("api/activity")/fetch("api/activity/" + encodeURIComponent(browser_id))(the existing REST endpoints), or remove the Activity Log panel until backend support exists.B. Add the missing JSON-RPC methods on the server (more correct, longer)
browser.list_sessionsreturning{sessions: [{id, pages: [{id, url, title}, ...], created_at}, ...], count, max}to match what the dashboard wants.browser.get_activity/browser.get_session_activitywrappers around the existingActivityLog(hero_browser_core::activity).dashboard.jsessentially as-is (only the namespace style needs aligning to whatever the spec says).hero_browser_server,hero_browser_sdk, and the openrpc.json that gets compiled into the SDK at build time.Notes
m.name.match(/^([a-z0-9]+)[._]/)); worth aligning everything either way before this grows further.extraInfoFn(dashboard.js:246) is already callingbrowser_listcorrectly -- onlyfetchSessions/fetchActivity*need rewiring.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'smatch req.method.as_str()(crates/hero_browser_server/src/rpc_handler.rs:92) does not recognize, so every refresh fires theMethod 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 inapi_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 identicalSessionsResponseshapes from both surfaces. The activity endpoints shareActivityLog::entries()plus the same browser-id filter, so they collapse naturally into thin wrappers around the existing core API.openrpc.jsonandopenrpc.rsare extended together so the SDK macro (openrpc_client!atcrates/hero_browser_sdk/src/lib.rs:29) keeps compiling and gainslist_sessions,get_activity,get_session_activitytyped wrappers as a free side effect.Requirements
browser_list_sessions,browser_get_activity,browser_get_session_activity(underscore style, matching existing convention incrates/hero_browser_server/src/rpc_handler.rs)./api/sessions,/api/activity,/api/activity/{browser_id}continue to work unchanged.SessionsResponseshapes.crates/hero_browser_server/openrpc.jsonandcrates/hero_browser_server/src/openrpc.rsupdated together so SDK build does not drift.crates/hero_browser_ui/templates/index.htmldocuments the real RPC + REST surface.crates/hero_browser_server/src/main.rstestsmodule (sincerpc_handler.rshas no test module today, follow the existingtest_router()pattern there).Files to Modify/Create
crates/hero_browser_server/src/sessions.rs(NEW) — module exposingSessionInfo,PageInfo,SessionsResponse, pluspub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse. Lives inhero_browser_server(not core) becauseSessionsResponseis a transport DTO withSerialize-only semantics;hero_browser_coreis otherwise transport-agnostic and the JSON shape belongs to the server boundary.crates/hero_browser_server/src/main.rs— declaremod sessions;, drop the inlined page-walk, makeapi_get_sessionscallsessions::collect_sessions, re-export the types fromsessions(or keep using them via the module path), add two newtests::test_routerroutes 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(usestate.poolandstate.activity_log, call intocrate::sessions::collect_sessionsfor sessions, intostate.activity_log.entries()for activity, with the same browser-id filter asapi_get_activity_for_sessionfor the per-session variant). Add#[cfg(test)] mod testsexercising 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 tobrowser_list.crates/hero_browser_server/src/openrpc.rs— three newmethod(...)entries adjacent tobrowser_listsorpc.discovermatchesopenrpc.json.crates/hero_browser_ui/static/js/dashboard.js— replace three dotted method names with underscore equivalents infetchSessions,fetchActivityForSession,fetchActivity. Confirmdata.sessions[i].browser_id/pages[i].page_id/urlfields andActivityEntryfield 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}/streamSSE.Implementation Plan
Step 1: Extract shared sessions helper
Files:
crates/hero_browser_server/src/sessions.rs(NEW),crates/hero_browser_server/src/main.rscrates/hero_browser_server/src/sessions.rswithSessionInfo,PageInfo,SessionsResponse(moved verbatim frommain.rs:88-105, re-exportedpub) andpub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponsecontaining the exact loop currently atmain.rs:167-190.main.rs, addmod sessions;, delete the local struct definitions and the inlined loop, and rewriteapi_get_sessionsas a one-liner:Json(sessions::collect_sessions(&state.pool).await).cargo check -p hero_browser_serverpasses and thattest_sessions_returns_jsonstill asserts the expected shape (no change needed; test only checkssessionsandcountkeys).Dependencies: none
Step 2: Add three new JSON-RPC methods in rpc_handler.rs
Files:
crates/hero_browser_server/src/rpc_handler.rsmatch req.method.as_str()block (place them adjacent tobrowser_listat 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 returnsResult<Value, String>, use a localmatchrather than?."browser_get_activity"->Ok(serde_json::to_value(&state.activity_log.entries()).unwrap_or(json!([])))."browser_get_session_activity"-> requirebrowser_id(use the existingp(params, "browser_id")+ missing-param error pattern frombrowser_destroy), thenstate.activity_log.entries().into_iter().filter(|e| e.browser_id.as_deref() == Some(&bid)).collect::<Vec<_>>()and serialize.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.rsopenrpc.json, insert three new entries after thebrowser_listblock (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: parambrowser_id(string); result schema same asbrowser_get_activity.openrpc.rs, insert three matchingmethod(...)entries adjacent to thebrowser_listregistration (line 38).cargo build -p hero_browser_sdk) must succeed because theopenrpc_client!macro consumesopenrpc.jsonat 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#[cfg(test)] mod testsat the bottom ofrpc_handler.rs.AppState(pool+activity_log) the same waymain.rstest_router()does (lines 615-618).JsonRpcRequestand callrpc_handlerdirectly viaaxum::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[]; afteractivity_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 missingbrowser_idparam yields-32602.main.rs(orsessions.rs)test_rest_and_rpc_sessions_matchthat calls bothapi_get_sessions(via the test router) andcollect_sessionsdirectly, thenserde_json::to_valueboth andassert_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.jsrpc("browser.list_sessions")->rpc("browser_list_sessions").rpc("browser.get_session_activity", { browser_id: browserId })->rpc("browser_get_session_activity", { browser_id: browserId }).rpc("browser.get_activity")->rpc("browser_get_activity").rpc("browser_list")), which is already correct.renderSessionsreadsdata.sessions[i].browser_id,data.pages[i].page_id,data.count,data.max— all matchSessionsResponseexactly.renderActivityForSessionreadse.timestamp,e.command,e.args_summary— all matchActivityEntry.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<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 specificbrowser_id.browser_list— List browser IDs only (used by the connection-status widget).main.rs:486-495:GET /api/sessionsGET /api/activityGET /api/activity/{browser_id}GET /api/sessions/{browser_id}/pages/{page_id}/stream(SSE,?fps=1-10).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
browser_create, the Sessions panel shows the new browser ID and its page count within one 3 s refresh tick.browser_list_sessions,browser_get_activity,browser_get_session_activityall return the documented shapes when called via curl onrpc.sock(and via TCP127.0.0.1:8884/rpc)./api/sessionsreturns the byte-identical shape asbrowser_list_sessions(verified by parity test in Step 4).cargo test --workspace(ormake test) still pass.rpc_handler.rs.console.error/ unhandled rejection in DevTools during a normal refresh cycle.browser.list_sessions/browser.get_activity/browser.get_session_activity.cargo build -p hero_browser_sdkstill succeeds (openrpc.json+openrpc_client!macro stay consistent and now expose three new typed wrappers).Notes
service_methodunderscore style for ALL JSON-RPC methods (verified across 50+ existing methods inrpc_handler.rsandopenrpc.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.openrpc_client!macro (crates/hero_browser_sdk/src/lib.rs:29) fromcrates/hero_browser_server/openrpc.json. Any signature drift betweenopenrpc.jsonandopenrpc.rs/ actual handler signatures will break the SDK build. Keep them aligned in the same commit./rpcproxy of its own; percrates/hero_browser_ui/src/main.rs:4, RPC routing is handled byhero_router, which routes<prefix>/rpcdirectly torpc.sock. So the new methods are reachable from the dashboard the moment the server registers them — no UI-side proxy work needed.SessionsResponseis purely a server-boundary DTO; do NOT hoist it intohero_browser_core(which has noserde_json::Value/ Axum dependency surface). The new modulecrates/hero_browser_server/src/sessions.rsis the lowest layer that already owns the JSON contract.api_get_activity_for_session(main.rs:199-209) and the newbrowser_get_session_activityRPC method use identical filter logic — consider extracting that one-liner intocrate::sessionstoo (e.g.pub fn activity_for_browser(log: &ActivityLog, browser_id: &str) -> Vec<ActivityEntry>) for symmetry.make test(which runscargo test --workspace --lib --bins --tests) perMakefile:107-108.make smoke-testruns only the in-process binary tests inmain.rsand is the fastest signal for the new parity test.Test Results
New tests for issue #13 (all passing)
test_rpc_browser_list_sessions_returns_shapetest_rpc_browser_get_activity_returns_arraytest_rpc_browser_get_session_activity_filters_by_browser_idtest_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 newopenrpc.jsonentries do not break theopenrpc_client!macro consumer.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, andpub async fn collect_sessions(pool: &BrowserPool) -> SessionsResponse. Both REST and RPC handlers call it.crates/hero_browser_server/src/main.rs— addedmod sessions;, removed inlined struct definitions and page-walk loop, rewroteapi_get_sessionsas a one-liner that callssessions::collect_sessions.crates/hero_browser_server/src/rpc_handler.rs— three new match arms registered next tobrowser_list:browser_list_sessions— delegates tosessions::collect_sessions.browser_get_activity— returnsstate.activity_log.entries().browser_get_session_activity— requiredbrowser_idparam (returns-32602if missing); filters activity byentry.browser_id.as_deref() == Some(&browser_id)(byte-identical toapi_get_activity_for_sessioninmain.rs).crates/hero_browser_server/openrpc.jsonandcrates/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-nestedjson!schemas inopenrpc.rsexceed 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_sessionsbrowser.get_session_activity->browser_get_session_activitybrowser.get_activity->browser_get_activityRenderers 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_sessionsetc.) replaced with the real underscore RPC method names; new "REST Endpoints" subsection added documentingGET /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_sessionshelper,api_get_sessionsREST handler, andbrowser_list_sessionsRPC handler all serialize to byte-identical JSON. Defends the single-source-of-truth invariant.SDK build verification
cargo build -p hero_browser_sdksucceeds. Theopenrpc_client!macro consumes the updatedopenrpc.jsonand now generates typed wrappers forbrowser_list_sessions,browser_get_activity, andbrowser_get_session_activityautomatically.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):Dashboard observed:
browser_get_session_activity.Acceptance criteria
browser_create, the Sessions panel shows the new browser ID and its page count./api/sessionsreturns byte-identical shape asbrowser_list_sessions(parity test).browser.list_sessions/browser.get_activity/browser.get_session_activity.cargo build -p hero_browser_sdksucceeds.