Skip to content

Remote Hosting UI in ad4m-connect#745

Merged
lucksus merged 12 commits intodevfrom
feat/remote-hosting-in-ad4m-connect
Mar 16, 2026
Merged

Remote Hosting UI in ad4m-connect#745
lucksus merged 12 commits intodevfrom
feat/remote-hosting-in-ad4m-connect

Conversation

@jhweir
Copy link
Contributor

@jhweir jhweir commented Mar 12, 2026

Remote Hosting UI in ad4m-connect

Branch: feat/remote-hosting-in-ad4m-connect
Base: dev

Summary

Adds a complete remote hosting flow to the ad4m-connect modal, allowing users to browse available remote hosts, view pricing/model details, authenticate, and manage credits — all without leaving the connection dialog.

What Changed

New Views (3 components)

  • HostBrowser — Scrollable list of available remote hosts with avatar, location, AI model chips, and rate previews. Supports "Last used" pinning, manual URL entry, and responsive height that adapts to the viewport.
  • HostDetail — Full detail view for a selected host showing profile, all rates with formatted pricing, and a "Connect" action.
  • LoggedInDashboard — Post-auth dashboard showing connected host, credit balance (with depleted state), wallet address management, and HOT top-up buttons.

Modified Views

  • ConnectionOptions — Replaced the old "Remote Node" section with a "Browse Remote Hosts" button (gated behind hosting: true option). Simplified props.
  • web.ts (orchestrator) — Extended view routing to include host-browser, host-detail, and logged-in-dashboard. Added event handlers for the full hosting flow. Settings button routes to dashboard when connected to a host, with a pulsing animation on low credit.

Foundation

  • types.ts — Added RemoteHost, PricingItem, UserInfo types. Extended Ad4mConnectOptions with hosting?: boolean and hostIndexUrl?: string.
  • core.ts — Added hosting state (connectedHost, userInfo), credit polling (30s interval), creditdepleted/userinfochange events, localStorage persistence of last-used host.
  • services/hostIndex.ts — Service layer with fetchHosts(), fetchUserInfo(), requestPayment(). Includes mock data (10 hosts with base64 SVG avatars) behind a USE_MOCK flag.
  • utils.ts — Shared getInitials() and getHue() helpers for avatar fallbacks.
  • IconsGlobeIcon, CreditIcon, WalletIcon components + barrel export.

GraphQL Contract (executor-side)

  • HostingUserInfo and PaymentRequestResult types
  • hostingGetUserInfo query, hostingRequestPayment / hostingSetWalletAddress mutations
  • Client-side methods in AgentClient

Architecture

ConnectionOptions
  └─ "Remote Node" ──▶ HostBrowser
                                  └─ select host ──▶ HostDetail
                                                        └─ "Connect" ──▶ Remote Auth (existing)
                                                                              └─ auth success ──▶ LoggedInDashboard

Settings button (when connected) ──▶ LoggedInDashboard

Views communicate upward via CustomEvent dispatch. The orchestrator (web.ts) listens and routes. core.ts manages state and polling.

How to Test

  1. In ad4m/connect: pnpm build
  2. In consuming app (e.g. Flux), ensure hosting: true is passed to getAd4mClient() options
  3. The "Connect to Remote Node" button appears on the connection screen
  4. Mock data serves 10 hosts (5 with images, 5 with CSS fallback avatars)

Commits

Commit Description
213b6b17 Foundation — types, service layer, icons
c8856b1f Views — HostBrowser, HostDetail, LoggedInDashboard
92d57d5e Wiring — modal routing, event handlers, settings button
cdf5506d Polish — DRY helpers, base64 mock images, responsive list, COEP fix

Files Changed

19 files+1,554 / -125 lines

Area Files
New components HostBrowser.ts, HostDetail.ts, LoggedInDashboard.ts
New services hostIndex.ts
New icons GlobeIcon.ts, CreditIcon.ts, WalletIcon.ts, icons/index.ts
Modified ConnectionOptions.ts, web.ts, core.ts, types.ts, utils.ts, shared-styles.ts
GraphQL contract graphql_types.rs, mutation_resolvers.rs, query_resolvers.rs, AgentClient.ts, RuntimeResolver.ts

Notes

  • Mock data uses inline base64 SVG data URIs for profile images to avoid COEP issues in Electron/Flux
  • Avatar system has a CSS fallback (hue-based colored circle with initials) when profilePicUrl is empty
  • Host list max-height is responsive: min(500px, calc(100vh - 380px))
  • Credit polling and creditdepleted event are wired but dashboard top-up flow requires real API integration
  • USE_MOCK = true in hostIndex.ts — flip to false when the real hosting index API is ready

Summary by CodeRabbit

  • New Features

    • Host browser, host detail, and logged-in dashboard: browse/select hosts, view host profiles, manage wallet and top-ups
    • Hosting integration: host lookup, user info & payment APIs, credit polling, connect-to-host flow, and direct client initialization
    • New UI icons (globe, credit, wallet) and avatar rendering; utilities for initials and color generation
  • Refactor

    • Simplified remote connection UX and navigation for hosting flows
  • Style

    • Minor spacing tweak to shared container styles
  • Chores

    • Removed example/demo static pages and Electron/web example scaffolding

jhweir added 4 commits March 12, 2026 12:12
- Add RemoteHost, PricingItem, UserInfo types + hostIndexUrl option
- Add services/hostIndex.ts with fetchHosts, fetchUserInfo, requestPayment (mock data)
- Add hosting state to core.ts: connectedHost, userInfo, credit polling, requestTopUp
- Add GlobeIcon, CreditIcon, WalletIcon SVG components
- HostBrowser: scrollable host list with cards, manual URL entry, last-used pinning
- HostDetail: full host profile with rates table, model chips, connect button
- LoggedInDashboard: credit balance, wallet address management, top-up buttons, depleted state
- ConnectionOptions: replace Remote Node section with Browse Remote Hosts button
- web.ts: add host-browser, host-detail, logged-in-dashboard view routes
- Auth success now navigates to dashboard when a host is selected
- Settings button routes to dashboard for remote hosts, pulses red on low credit
…64 mock images

- Move getInitials/getHue to utils.ts (shared by HostBrowser + HostDetail)
- Replace external image URLs with inline base64 SVG data URIs (COEP-safe)
- Make host list responsive: max-height uses min(500px, calc(100vh - 380px))
- Modal uses flex column to pass height constraints to child views
- Minor style fixes (shared-styles, ConnectionOptions cleanup)
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Adds hosting support: new icon components, host browsing/detail/dashboard LitElements, host-index service with mock data, core hosting state and credit-polling/payment methods, avatar utilities, web UI wiring for host flows, small style tweak, Vite config, and removal of several example pages.

Changes

Cohort / File(s) Summary
Icon Components
connect/src/components/icons/CreditIcon.ts, connect/src/components/icons/GlobeIcon.ts, connect/src/components/icons/WalletIcon.ts, connect/src/components/icons/index.ts
Added three SVG icon components and re-exported them from the icons index.
Hosting UI Components
connect/src/components/views/HostBrowser.ts, connect/src/components/views/HostDetail.ts, connect/src/components/views/LoggedInDashboard.ts
Added LitElement views for browsing hosts, host detail/profile & pricing, and a logged-in dashboard with wallet/top-up UI and events.
Connection Options
connect/src/components/views/ConnectionOptions.ts
Replaced legacy remote-URL flow with hosting flow: removed remote URL state/handlers, added showHosting flag and browseHosts() dispatch, simplified remote UI.
Core Hosting Logic
connect/src/core.ts
Extended Ad4mConnect with hosting state (connectedHost, userInfo, hostIndexUrl), credit polling, requestTopUp, setConnectedHost persistence, and localStorage interactions.
Host Index Service
connect/src/services/hostIndex.ts
New host-index API: fetchHosts, fetchUserInfo, requestPayment with MOCK dataset and USE_MOCK toggle and network fallbacks.
Types & Utils
connect/src/types.ts, connect/src/utils.ts
Added types RemoteHost, PricingItem, UserInfo; added getInitials and getHue utilities for avatar generation.
Avatar Helper
connect/src/components/shared/avatar.ts
Added renderHostAvatar to render profile image or initials fallback with hue-based background.
Web Integration
connect/src/web.ts
Integrated hosting into web UI: new views (host-browser, host-detail, logged-in-dashboard), host selection/auth flow, credit polling wiring, and routing updates.
Styles
connect/src/styles/shared-styles.ts
Adjusted .container padding (+2px).
Build Config
connect/vite.config.ts
Added Vite config excluding electron from optimizeDeps.
Examples / Public
connect/example/*, connect/public/dialog.html, connect/index.html
Removed multiple example HTML/JS and package manifests; replaced connect/index.html script to initialize via getAd4mClient.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant ConnOpt as ConnectionOptions
    participant Browser as HostBrowser
    participant Index as HostIndexService
    participant Detail as HostDetail
    participant Core as Ad4mConnect
    participant Dashboard as LoggedInDashboard

    User->>ConnOpt: Click "Browse Hosts"
    ConnOpt->>Browser: Open host-browser
    Browser->>Index: fetchHosts(indexUrl)
    Index-->>Browser: return hosts
    User->>Browser: select host
    Browser->>Detail: Open host-detail(selected)
    User->>Detail: Click "Connect"
    Detail->>Core: proceedToAuth / validate host
    Core->>Index: fetchUserInfo(hostUrl, token)
    Index-->>Core: return userInfo
    Core->>Core: startCreditPolling()
    Core-->>Dashboard: show logged-in-dashboard with host & userInfo
    User->>Dashboard: requestTopUp(amount)
    Dashboard->>Core: requestTopUp(amount)
    Core->>Index: requestPayment(hostUrl, token, amount)
    Index-->>Core: payment result
    Core-->>Dashboard: emit payment result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lucksus

Poem

🐰 I hopped through hosts both near and far,
Icons gleam like moonlit stars.
Wallets, credits, dashboards bright,
I bind remote hosts with nimble light.
Hop on — connect and take flight! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Remote Hosting UI in ad4m-connect' clearly and concisely describes the primary change—adding a complete remote hosting user interface to the ad4m-connect modal, which aligns with the PR's main objective of enabling users to browse remote hosts, authenticate, and manage credits.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/remote-hosting-in-ad4m-connect
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@connect/src/components/views/ConnectionOptions.ts`:
- Around line 11-12: In ConnectionOptions, the current conditional rendering can
produce an empty modal when isMobile is true and showHosting is false; update
the render logic (the conditional branches that decide whether to render the
local-node and remote-node boxes around the local-node / remote-node elements
referenced in the component) so that at least one action is shown: either always
render the remote-node box when isMobile, or add a fallback branch that forces
showHosting to true or renders a primary fallback action/button when both
local-node and remote-node would be skipped; modify the conditions that use the
showHosting property and isMobile flag so the modal cannot render with no
actions.

In `@connect/src/components/views/HostBrowser.ts`:
- Around line 277-295: Host rows are not keyboard-accessible because the
clickable element is a plain div; update the host row rendering in HostBrowser
(the div with class "host-card" that calls selectHost(host)) to be focusable and
operable via keyboard by adding tabindex="0", role="button", appropriate
aria-label/aria-pressed or aria-selected reflecting lastHostId, and attach a
keydown handler that listens for Enter and Space to call this.selectHost(host);
ensure visual focus styling is preserved and keep existing click behavior, and
adjust any related helpers like selectHost(host) or formatRates only if needed
to support being invoked from keyboard events.
- Around line 210-223: In connectManualUrl(), validate the manualUrl before
constructing the RemoteHost: wrap new URL(url) parsing in a try/catch to handle
malformed URLs and return early with user feedback on parse error, and
explicitly check that parsedUrl.protocol is 'ws:' or 'wss:' (reject other
schemes) before creating the synthetic RemoteHost object (id
`manual-${Date.now()}`, name from parsedUrl.hostname, url, etc.); on validation
failure, do not call new URL() or create the RemoteHost and instead surface an
appropriate error state/message to the user.

In `@connect/src/components/views/HostDetail.ts`:
- Around line 134-139: The formatPrice method treats 0 as falling through to
exponential notation; update the private method formatPrice(price: number) to
explicitly handle price === 0 (or price === 0.0) and return a human-friendly
representation (e.g., "0.00") instead of toExponential; keep the existing
branches for other ranges so values >= 0.01 still use toFixed(2) and the
small-value fallbacks remain unchanged.
- Around line 111-113: In HostDetail, replace the non-semantic back control (the
clickable <div> inside the HostDetail component) with a real <button> element
and give it a distinct class (e.g., "back-button") so it becomes
keyboard-focusable and activatable by Enter/Space; update the global CSS rule
that currently targets all buttons ("button { width: 100%; }") to instead target
the connect action only (e.g., ".connect-button { width: 100%; }") and add
specific styles for ".back-button" to remove default button visuals and preserve
the intended look (e.g., unset default padding/border/background, set cursor:
pointer, display/align as needed). Ensure the back button has an accessible name
(text or aria-label) and reuse the existing click handler (onBack or similar) on
the new <button> element.

In `@connect/src/core.ts`:
- Around line 50-54: The code currently JSON.parse's persisted "ad4m-last-host"
into this.connectedHost (type RemoteHost) which can produce a partial object
missing fields like location, rates, and aiModels; instead, change the restore
logic to parse into a dedicated persisted host shape (e.g., PersistedHost) and
rehydrate/merge it with a full RemoteHost before assigning to connectedHost, or
call an existing initializer/constructor that fills defaults; update the restore
block that uses getLocal("ad4m-last-host") and the assignment to
this.connectedHost so it reconstructs a complete RemoteHost (or calls
setConnectedHost with the parsed id/url/name) rather than directly assigning the
partial parsed object.

In `@connect/src/services/hostIndex.ts`:
- Around line 167-168: The current derivation of the REST baseUrl from hostUrl
always maps both ws:// and wss:// to https://; change the logic so it preserves
TLS intent by mapping wss:// -> https:// and ws:// -> http:// before stripping
the trailing /graphql. Update the baseUrl computation that uses hostUrl
(variable name: baseUrl, hostUrl) to choose the scheme based on
hostUrl.startsWith('wss://') vs 'ws://' and then remove /graphql, and make the
same change for the other identical occurrence referenced around the code (the
second spot at lines 187-188) so /api/user-info and /api/request-payment use the
correct http/https scheme.
- Around line 139-145: The module currently forces mock mode by setting USE_MOCK
= true which short‑circuits public APIs like fetchHosts; change this so mocks
are opt‑in by default (set USE_MOCK = false) and drive it from an explicit
runtime/build flag (e.g., process.env.CONNECT_USE_MOCK or a config value)
instead of a hardcoded true value; update all other mock gates in this file (the
additional mock blocks referenced around the other sections) to read the same
runtime flag so dev/test can enable mocks while production uses the real
backend.

In `@connect/src/utils.ts`:
- Around line 72-75: getInitials uses name.trim() for splitting but falls back
to slicing the original name, causing leading spaces or whitespace-only names to
produce incorrect initials; change the function to normalize once (e.g., const
clean = name.trim()), build words from clean, and in the single-word branch
return clean.slice(0,2).toUpperCase(); also handle the empty clean case (return
an empty string or a defined default) so whitespace-only inputs don't produce a
space character.

In `@connect/src/web.ts`:
- Around line 175-179: The handler for 'userinfochange' currently overwrites
this.userInfo with e.detail, making ephemeral local changes (like a saved wallet
address) lost when the next polling event arrives; change the handler in
connect/src/web.ts to merge incoming e.detail into the existing this.userInfo
instead of replacing it (e.g., preserve locally-saved fields such as the wallet
address or a savedAddress flag), or persist the saved wallet to the
authoritative service/core before updating UI so subsequent 'userinfochange'
events include the saved value; update the event listener that assigns
this.userInfo and this.lowCredit to perform a merge/merge-with-precedence (keep
local saved fields) or trigger the core API to store the saved wallet prior to
assigning e.detail.
- Around line 273-275: Don't assign and persist the selected host before
performing reachability and AD4M validation; instead, test the candidate URL
first and only update this.core.url and call setLocal("ad4m-url", ...) after
validation succeeds. Use a temporary variable (e.g., candidateUrl) to hold
host.url, run the existing reachability/AD4M checks against that variable, and
only on success set this.core.url = candidateUrl and setLocal("ad4m-url",
candidateUrl); leave storage untouched on validation failure (also apply this
change to the same logic covering the block referenced at lines 277-292).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3782fb5-d5df-443a-8200-0517d7af0152

📥 Commits

Reviewing files that changed from the base of the PR and between 47b1a72 and cdf5506.

📒 Files selected for processing (14)
  • connect/src/components/icons/CreditIcon.ts
  • connect/src/components/icons/GlobeIcon.ts
  • connect/src/components/icons/WalletIcon.ts
  • connect/src/components/icons/index.ts
  • connect/src/components/views/ConnectionOptions.ts
  • connect/src/components/views/HostBrowser.ts
  • connect/src/components/views/HostDetail.ts
  • connect/src/components/views/LoggedInDashboard.ts
  • connect/src/core.ts
  • connect/src/services/hostIndex.ts
  • connect/src/styles/shared-styles.ts
  • connect/src/types.ts
  • connect/src/utils.ts
  • connect/src/web.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
connect/src/core.ts (1)

312-316: Consider persisting the full host object for richer UI on reload.

setConnectedHost only persists id, url, and name, so on reload the dashboard shows placeholder values for location, rates, and models until the user re-browses hosts. If localStorage size isn't a concern, persisting the full RemoteHost would provide a better UX.

💡 Suggested improvement
 setConnectedHost(host: RemoteHost): void {
   this.connectedHost = host;
-  setLocal('ad4m-last-host', JSON.stringify({ id: host.id, url: host.url, name: host.name }));
+  setLocal('ad4m-last-host', JSON.stringify(host));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/core.ts` around lines 312 - 316, Persist the full RemoteHost
object instead of only id/url/name in setConnectedHost: update the
setConnectedHost method to store JSON.stringify(host) via the existing
setLocal('ad4m-last-host', ...) call so the entire RemoteHost (including
location, rates, models, etc.) is available on reload for richer UI; ensure you
still set this.connectedHost = host before persisting and keep the same storage
key.
connect/src/web.ts (2)

334-340: Wallet address persistence is incomplete.

The TODO comment acknowledges that handleSetWalletAddress only updates local state. While the userinfochange handler preserves this value temporarily, it will be lost on page reload since there's no server-side persistence.

Would you like me to help draft a setWalletAddress method in core.ts that calls the hosting API to persist the wallet address server-side?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/web.ts` around lines 334 - 340, handleSetWalletAddress currently
only mutates local this.userInfo so the hotWalletAddress is lost on reload;
implement a persistence path by adding a setWalletAddress method in core.ts that
calls the hosting API endpoint to persist the address (accepting user id/session
and address), and then modify handleSetWalletAddress to call
core.setWalletAddress(e.detail.address), await its result, handle errors
(log/display), and only update this.userInfo (this.userInfo = {
...this.userInfo, hotWalletAddress: ... }) after a successful API response; keep
references to the existing method names (handleSetWalletAddress, userInfo) and
add the new core.setWalletAddress function to locate the changes.

344-358: Dead code: connectRemoteNode is no longer wired.

This handler reads e.detail.remoteUrl which was emitted by the old ConnectionOptions component. Since the component no longer dispatches connect-remote-node events and renderViews() doesn't wire this handler, this code is unreachable.

🧹 Suggested cleanup

Either remove the method entirely or add a deprecation comment if backward compatibility is planned:

-  private async connectRemoteNode(e: CustomEvent) {
-    // Legacy direct-URL connection (kept for backward compat if needed)
-    this.core.url = e.detail.remoteUrl;
-    setLocal("ad4m-url", this.core.url);
-
-    try {
-      await connectWebSocket(e.detail.remoteUrl);
-      const isValidAd4mApi = await this.core.isValidAd4mAPI();
-      if (!isValidAd4mApi) throw new Error("Server is reachable but doesn't appear to be an AD4M executor");
-
-      this.currentView = "remote-authentication";
-    } catch (error) {
-      console.error('[Ad4m Connect UI] Remote connection failed:', error);
-    }
-  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/web.ts` around lines 344 - 358, The connectRemoteNode method is
dead code (no event wiring) so either remove it or mark it clearly deprecated;
to fix, delete the private async connectRemoteNode(...) method and any unused
imports/references (connectWebSocket, setLocal usage, currentView set, core.url
assignment) OR add a clear deprecation comment above the method and restore the
event wiring in renderViews() so connect-remote-node events call
connectRemoteNode; reference the method name connectRemoteNode, the helper
connectWebSocket, setLocal("ad4m-url", ...), core.isValidAd4mAPI(), and
currentView = "remote-authentication" when deciding which approach to apply.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@connect/src/components/views/HostBrowser.ts`:
- Around line 275-277: Replace the non-semantic clickable div used for the back
control with a real button element so it can receive keyboard focus and
activation: change the element rendering that currently outputs <div
class="back-button" `@click`=${this.back}> to a <button class="back-button"
`@click`=${this.back}> and keep using the same back handler (this.back) and CSS
class; also add the suggested CSS (button.back-button { all: unset; cursor:
pointer; }) alongside HostBrowser's styles to preserve visual appearance and
pointer behavior.

---

Nitpick comments:
In `@connect/src/core.ts`:
- Around line 312-316: Persist the full RemoteHost object instead of only
id/url/name in setConnectedHost: update the setConnectedHost method to store
JSON.stringify(host) via the existing setLocal('ad4m-last-host', ...) call so
the entire RemoteHost (including location, rates, models, etc.) is available on
reload for richer UI; ensure you still set this.connectedHost = host before
persisting and keep the same storage key.

In `@connect/src/web.ts`:
- Around line 334-340: handleSetWalletAddress currently only mutates local
this.userInfo so the hotWalletAddress is lost on reload; implement a persistence
path by adding a setWalletAddress method in core.ts that calls the hosting API
endpoint to persist the address (accepting user id/session and address), and
then modify handleSetWalletAddress to call
core.setWalletAddress(e.detail.address), await its result, handle errors
(log/display), and only update this.userInfo (this.userInfo = {
...this.userInfo, hotWalletAddress: ... }) after a successful API response; keep
references to the existing method names (handleSetWalletAddress, userInfo) and
add the new core.setWalletAddress function to locate the changes.
- Around line 344-358: The connectRemoteNode method is dead code (no event
wiring) so either remove it or mark it clearly deprecated; to fix, delete the
private async connectRemoteNode(...) method and any unused imports/references
(connectWebSocket, setLocal usage, currentView set, core.url assignment) OR add
a clear deprecation comment above the method and restore the event wiring in
renderViews() so connect-remote-node events call connectRemoteNode; reference
the method name connectRemoteNode, the helper connectWebSocket,
setLocal("ad4m-url", ...), core.isValidAd4mAPI(), and currentView =
"remote-authentication" when deciding which approach to apply.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f431378a-d328-41f3-92b4-11a10dfcf7e5

📥 Commits

Reviewing files that changed from the base of the PR and between cdf5506 and adeb126.

📒 Files selected for processing (8)
  • connect/src/components/views/ConnectionOptions.ts
  • connect/src/components/views/HostBrowser.ts
  • connect/src/components/views/HostDetail.ts
  • connect/src/core.ts
  • connect/src/services/hostIndex.ts
  • connect/src/types.ts
  • connect/src/utils.ts
  • connect/src/web.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • connect/src/services/hostIndex.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
connect/src/web.ts (2)

241-268: Extract shared host-fetching logic.

browseHosts and retryFetchHosts duplicate the same try/catch/finally fetch logic. Consider extracting a shared method:

♻️ Suggested refactor
+  private async doFetchHosts() {
+    this.hostsLoading = true;
+    this.hostsError = null;
+
+    try {
+      this.hosts = await fetchHosts(this.core.hostIndexUrl);
+    } catch (error) {
+      console.error('[Ad4m Connect UI] Failed to fetch hosts:', error);
+      this.hostsError = error instanceof Error ? error.message : "Failed to load hosts";
+    } finally {
+      this.hostsLoading = false;
+    }
+  }
+
   private async browseHosts() {
-    this.hostsLoading = true;
-    this.hostsError = null;
     this.currentView = "host-browser";
-
-    try {
-      this.hosts = await fetchHosts(this.core.hostIndexUrl);
-    } catch (error) {
-      console.error('[Ad4m Connect UI] Failed to fetch hosts:', error);
-      this.hostsError = error instanceof Error ? error.message : "Failed to load hosts";
-    } finally {
-      this.hostsLoading = false;
-    }
+    await this.doFetchHosts();
   }

   private async retryFetchHosts() {
-    this.hostsLoading = true;
-    this.hostsError = null;
-
-    try {
-      this.hosts = await fetchHosts(this.core.hostIndexUrl);
-    } catch (error) {
-      console.error('[Ad4m Connect UI] Failed to fetch hosts:', error);
-      this.hostsError = error instanceof Error ? error.message : "Failed to load hosts";
-    } finally {
-      this.hostsLoading = false;
-    }
+    await this.doFetchHosts();
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/web.ts` around lines 241 - 268, browseHosts and retryFetchHosts
duplicate identical fetch/try-catch/finally logic; extract that into a single
private helper (e.g. fetchAndSetHosts or loadHosts) which accepts options like
setCurrentView flag. Move the shared logic that sets hostsLoading, hostsError,
awaits fetchHosts(this.core.hostIndexUrl), assigns this.hosts, logs errors and
clears hostsLoading into that helper; update browseHosts to set this.currentView
= "host-browser" then call the helper, and update retryFetchHosts to simply call
the helper. Ensure the helper uses the same error handling (console.error and
this.hostsError assignment) and preserves existing state changes.

335-341: Wallet address save is not persisted to the server.

The handleSetWalletAddress only updates userInfo locally. While the userinfochange handler preserves this value during polling, it will be lost on page reload. The comment acknowledges this needs a core method implementation.

Given this is a WIP PR, consider adding a TODO or tracking issue to implement the server-side persistence.

Would you like me to open an issue to track implementing the wallet address persistence via a core API method?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/web.ts` around lines 335 - 341, handleSetWalletAddress currently
only mutates the local this.userInfo and will be lost on reload; update it to
call the appropriate core/API persistence method (e.g., add a call like
this.core.saveWalletAddress or userService.updateWallet to persist
e.detail.address) and only update this.userInfo after a successful response,
handling and logging errors; if the core API implementation isn't available yet,
add a clear TODO comment in handleSetWalletAddress referencing the missing core
method and create/link a tracking issue so persistence is implemented later, and
ensure the userinfochange polling logic remains compatible with the persisted
value.
connect/src/core.ts (1)

279-298: Consider adding backoff on repeated polling failures.

Currently, if the host becomes unreachable, the polling will continue logging errors every 30 seconds indefinitely. Consider adding a failure counter and pausing/slowing polling after consecutive failures.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/core.ts` around lines 279 - 298, The startCreditPolling
implementation currently polls indefinitely and logs errors on each failure;
modify startCreditPolling to track consecutive failures (e.g., failureCount),
implement an exponential backoff or capped backoff by increasing the interval
used for setInterval (or switching to a recursive setTimeout) after each failed
fetchUserInfo, reset failureCount to 0 on a successful response, cap the maximum
backoff (e.g., 5 minutes) and optionally dispatch an event (e.g.,
'creditpollingpaused' or 'creditpollingresumed') when you pause/resume; update
references to creditPollInterval and stopCreditPolling so clearing and
restarting the timer respects the current backoff interval and ensure failures
don’t leak timers when stopCreditPolling is called.
connect/src/components/shared/avatar.ts (1)

14-24: Imperative DOM manipulation bypasses Lit's reactive rendering.

The onError handler uses document.createElement and replaceWith which breaks out of Lit's declarative model. If the component re-renders, Lit won't be aware of the replaced DOM node, potentially causing stale UI or errors.

Consider using reactive state instead:

♻️ Suggested approach using state

Instead of a standalone function, this could be a small Lit component or use a directive that tracks load failure via reactive state:

// Alternative: Use a stateful approach with `@state` in the consuming component
// or create a small LitElement for the avatar that tracks its own error state

import { html, TemplateResult } from "lit";
import { getInitials, getHue } from "../../utils";
import type { RemoteHost } from "../../types";

export function renderHostAvatar(
  host: RemoteHost, 
  cls: string, 
  failedIds?: Set<string>
): TemplateResult {
  const hue = getHue(host.id || host.name);
  const initials = getInitials(host.name);
  const hostKey = host.id || host.name;

  // If image previously failed or no URL, render fallback
  if (!host.profilePicUrl || failedIds?.has(hostKey)) {
    return html`<div class="${cls} fallback" style="background:hsl(${hue},60%,35%)">${initials}</div>`;
  }

  return html`<img class=${cls} src=${host.profilePicUrl} alt="" 
    `@error`=${(e: Event) => {
      // Dispatch event to parent to track failure and trigger re-render
      (e.target as HTMLElement).dispatchEvent(
        new CustomEvent('avatar-error', { detail: hostKey, bubbles: true, composed: true })
      );
    }} />`;
}

The consuming component would maintain a Set<string> of failed host IDs and re-render on error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@connect/src/components/shared/avatar.ts` around lines 14 - 24, The onError
handler in the avatar render uses imperative DOM manipulation
(document.createElement and replaceWith) which breaks Lit's reactive model;
change it to notify the parent so reactive state drives fallback rendering: in
the branch that returns the <img> (where host.profilePicUrl is present) replace
the imperative onError logic with an inline `@error` handler that dispatches a
CustomEvent (e.g., 'avatar-error') with a hostKey (host.id || host.name) as
detail, then update the consuming component to maintain a Set of failedIds and
render the fallback div when failedIds has the hostKey or when no profilePicUrl
(mirror the suggested pattern instead of using
document.createElement/replaceWith).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@connect/src/components/shared/avatar.ts`:
- Around line 14-24: The onError handler in the avatar render uses imperative
DOM manipulation (document.createElement and replaceWith) which breaks Lit's
reactive model; change it to notify the parent so reactive state drives fallback
rendering: in the branch that returns the <img> (where host.profilePicUrl is
present) replace the imperative onError logic with an inline `@error` handler that
dispatches a CustomEvent (e.g., 'avatar-error') with a hostKey (host.id ||
host.name) as detail, then update the consuming component to maintain a Set of
failedIds and render the fallback div when failedIds has the hostKey or when no
profilePicUrl (mirror the suggested pattern instead of using
document.createElement/replaceWith).

In `@connect/src/core.ts`:
- Around line 279-298: The startCreditPolling implementation currently polls
indefinitely and logs errors on each failure; modify startCreditPolling to track
consecutive failures (e.g., failureCount), implement an exponential backoff or
capped backoff by increasing the interval used for setInterval (or switching to
a recursive setTimeout) after each failed fetchUserInfo, reset failureCount to 0
on a successful response, cap the maximum backoff (e.g., 5 minutes) and
optionally dispatch an event (e.g., 'creditpollingpaused' or
'creditpollingresumed') when you pause/resume; update references to
creditPollInterval and stopCreditPolling so clearing and restarting the timer
respects the current backoff interval and ensure failures don’t leak timers when
stopCreditPolling is called.

In `@connect/src/web.ts`:
- Around line 241-268: browseHosts and retryFetchHosts duplicate identical
fetch/try-catch/finally logic; extract that into a single private helper (e.g.
fetchAndSetHosts or loadHosts) which accepts options like setCurrentView flag.
Move the shared logic that sets hostsLoading, hostsError, awaits
fetchHosts(this.core.hostIndexUrl), assigns this.hosts, logs errors and clears
hostsLoading into that helper; update browseHosts to set this.currentView =
"host-browser" then call the helper, and update retryFetchHosts to simply call
the helper. Ensure the helper uses the same error handling (console.error and
this.hostsError assignment) and preserves existing state changes.
- Around line 335-341: handleSetWalletAddress currently only mutates the local
this.userInfo and will be lost on reload; update it to call the appropriate
core/API persistence method (e.g., add a call like this.core.saveWalletAddress
or userService.updateWallet to persist e.detail.address) and only update
this.userInfo after a successful response, handling and logging errors; if the
core API implementation isn't available yet, add a clear TODO comment in
handleSetWalletAddress referencing the missing core method and create/link a
tracking issue so persistence is implemented later, and ensure the
userinfochange polling logic remains compatible with the persisted value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a5191b8-a7ff-4662-80c2-604d958b693b

📥 Commits

Reviewing files that changed from the base of the PR and between 80008b6 and aa91671.

📒 Files selected for processing (5)
  • connect/src/components/shared/avatar.ts
  • connect/src/components/views/HostBrowser.ts
  • connect/src/components/views/HostDetail.ts
  • connect/src/core.ts
  • connect/src/web.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • connect/src/components/views/HostBrowser.ts
  • connect/src/components/views/HostDetail.ts

@jhweir jhweir changed the title WIP: Remote Hosting UI in ad4m-connect Remote Hosting UI in ad4m-connect Mar 16, 2026
@lucksus lucksus merged commit 63d3227 into dev Mar 16, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants