feat: Hosting UI cleanup, free/paid toggle, and launcher improvements#763
feat: Hosting UI cleanup, free/paid toggle, and launcher improvements#763
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdded a global "free hosting enabled" flag and plumbing across DB, GraphQL API (query + admin mutation), Rust credit/hosting enforcement, runtime client, and UI context/components so the system can toggle and honor a global free-hosting mode. Changes
Sequence DiagramsequenceDiagram
actor User
participant UI as Hosting UI
participant Context as Ad4minContext
participant Client as RuntimeClient
participant API as GraphQL API
participant DB as Database
participant Enforcer as Credit Enforcer
User->>UI: Toggle "Paid hosting via wHOT"
UI->>Context: setFreeHostingEnabled(enabled)
Context->>Client: runtime.setFreeHostingEnabled(enabled)
Client->>API: Mutation: runtimeSetFreeHostingEnabled(enabled)
API->>DB: set_free_hosting_enabled(enabled)
DB-->>API: OK
API-->>Client: boolean result
Client-->>Context: result
Context->>UI: emit freeHostingEnabled change
Enforcer->>DB: get_free_hosting_enabled()
DB-->>Enforcer: true/false
alt Global free hosting enabled
Enforcer->>Enforcer: Skip per-user credit checks
else Global flag disabled
Enforcer->>DB: get_user_free_access(email)
DB-->>Enforcer: per-user status
end
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
rust-executor/src/lib.rs (1)
508-523:⚠️ Potential issue | 🟠 MajorAvoid failing into paid mode when global setting read errors.
Using
unwrap_or(false)at Line 508-509 silently forces paid behavior on transient DB failures. That can publish wrong hosting state. Also, the global flag should be read once per flush, not once per user.Proposed fix
- for email in &dirty_emails { - let global_free = Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) - .unwrap_or(false); + let global_free = match Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) { + Ok(v) => v, + Err(e) => { + warn!("Credit flush: get_free_hosting_enabled failed: {}. Falling back to free hosting.", e); + true + } + }; + for email in &dirty_emails { let free_access = if global_free { true } else {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rust-executor/src/lib.rs` around lines 508 - 523, The code currently calls Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()).unwrap_or(false) per user which silently treats DB read errors as paid (false) and does the lookup repeatedly; instead, call Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) once at the start of the flush and handle errors explicitly: if the call returns Ok(value) use that value, but if it returns Err(e) log the error (using error!) and set global_free = true to avoid forcing paid mode on transient failures; keep the existing per-user call to Ad4mDb::with_global_instance(|db| db.get_user_free_access(email)) and its error handling for get_user_free_access, and remove the unwrap_or(false) usage.ui/src/components/Hosting.tsx (2)
1304-1439:⚠️ Potential issue | 🟡 MinorShow a consistent badge when global free hosting is on.
In free-hosting mode every user has effective free access, but this card still badges only users whose stored per-user flag is true. The list becomes misleading right when the credit/free-access controls are hidden.
🔧 Suggested fix
- {(user as any).freeAccess && ( - <j-badge variant="success">Free Access</j-badge> - )} + {freeHostingEnabled ? ( + <j-badge variant="success">Free Hosting</j-badge> + ) : (user as any).freeAccess ? ( + <j-badge variant="success">Free Access</j-badge> + ) : null}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/components/Hosting.tsx` around lines 1304 - 1439, The user cards only show the "Free Access" j-badge when (user as any).freeAccess is true, which is misleading when global freeHostingEnabled is on; update the badge rendering inside the users.map card (where j-badge is rendered) to display when freeHostingEnabled || (user as any).freeAccess so every card shows a consistent "Free Access" badge in global free-hosting mode while leaving per-user logic unchanged elsewhere (e.g., toggles and credit controls).
843-894:⚠️ Potential issue | 🟠 MajorSkip paid-hosting bootstrap while free hosting is enabled.
This toggle only hides the paid-hosting UI. The mount effects still load any persisted hosting-index session and auto-fetch membrane proofs, so free mode continues calling paid-hosting services and can clear the saved session on a 401 before the admin re-enables paid hosting.
🔧 Suggested fix
- useEffect(() => { + useEffect(() => { + if (freeHostingEnabled) return; const loadHostSession = async () => { try { const reg = await invoke<{ @@ }; loadHostSession(); - }, []); + }, [freeHostingEnabled]); @@ useEffect(() => { - if (!client || !hostSession || membraneProofStatus === "done" || membraneProofStatus === "fetching" || membraneProofAttempted.current) return; + if ( + freeHostingEnabled || + !client || + !hostSession || + membraneProofStatus === "done" || + membraneProofStatus === "fetching" || + membraneProofAttempted.current + ) return; fetchMembraneProof(hostSession); - }, [client, hostSession]); + }, [client, hostSession, freeHostingEnabled]);Also applies to: 1152-1178
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ui/src/components/Hosting.tsx` around lines 843 - 894, The effects that load persisted paid-hosting state and auto-fetch membrane proofs should be skipped when the paid-hosting toggle is off; update the outer useEffect that defines loadHostSession and the subsequent useEffect that calls fetchMembraneProof to early-return when the paid-hosting flag is disabled (e.g. check your paid/free toggle like isPaidHostingEnabled or isFreeHostingEnabled at the top of both hooks), so loadHostSession does not call invoke/get_host_registration or call setHostSession/saveHostSession on 401 and the second effect does not call fetchMembraneProof(hostSession); keep the existing logic untouched otherwise and only add the guard around the existing code paths referencing loadHostSession, fetchMembraneProof, hostSession, membraneProofStatus, membraneProofAttempted, setHostSession, and saveHostSession.
🧹 Nitpick comments (1)
rust-executor/src/graphql/query_resolvers.rs (1)
1018-1021: Hoist the global flag out of the user loop.This value is constant for the whole response, but it is re-read for every user. Reading it once avoids extra DB lock churn and keeps a single snapshot if the toggle changes mid-iteration.
♻️ Suggested simplification
- for user in users { + let global_free = + Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()).unwrap_or(false); + + for user in users { // Count perspectives owned by this user let mut perspective_count = 0; for perspective in &all_perspectives { let handle = perspective.persisted.lock().await.clone(); if let Some(owners) = &handle.owners { @@ - let global_free = - Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()).unwrap_or(false); let free_access: bool = global_free || Ad4mDb::with_global_instance(|db| db.get_user_free_access(&user.username))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@rust-executor/src/graphql/query_resolvers.rs` around lines 1018 - 1021, Hoist the global free-hosting flag out of the per-user loop: call Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) once (assign to a local variable, e.g. global_free) before iterating users, then in the loop compute free_access as global_free || Ad4mDb::with_global_instance(|db| db.get_user_free_access(&user.username)); this avoids re-reading get_free_hosting_enabled per user and prevents extra DB locking and inconsistent snapshots.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/src/runtime/RuntimeResolver.ts`:
- Around line 516-519: The mock resolver's runtimeFreeHostingEnabled() currently
returns false which inverts the actual default; change the return value in the
RuntimeResolver.runtimeFreeHostingEnabled method from false to true so the mock
matches the new runtime default of "free hosting enabled" (and run/update any
affected tests that assume the old value).
In `@rust-executor/src/graphql/mutation_resolvers.rs`:
- Around line 65-69: The resolver currently checks global_free via
Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) and returns
early for credits, but later still calls runtime_request_payment which can
create HOT payment proposals; add the same global toggle guard before any call
to runtime_request_payment (or pass the global_free flag into/through the code
path) so that when get_free_hosting_enabled() is true no payment endpoint or
proposal is created or invoked; locate uses of runtime_request_payment in this
resolver and short-circuit them when global_free is true (or thread the flag to
downstream functions).
- Around line 2596-2607: The runtime_set_free_hosting_enabled mutation currently
uses check_capability(&context.capabilities, &AGENT_UPDATE_CAPABILITY) but must
be admin-only; replace that capability check with a guard that requires
context.is_admin_credential (e.g., if !context.is_admin_credential { return
Err(FieldError::new("admin credentials required", Value::null())); }), then
proceed to call Ad4mDb::with_global_instance and
db.set_free_hosting_enabled(enabled) as before; keep existing error mapping to
FieldError for the DB call (related symbols: runtime_set_free_hosting_enabled,
check_capability, AGENT_UPDATE_CAPABILITY, context.is_admin_credential,
Ad4mDb::with_global_instance, set_free_hosting_enabled).
In `@ui/src/components/Hosting.tsx`:
- Around line 1054-1148: The setup disclosure is currently a click-only j-flex
opener (the element that toggles setupInfoExpanded via setSetupInfoExpanded) and
leaves focusable content (the Let's Encrypt anchor) tabbable when collapsed;
replace that j-flex opener with a semantic button element that uses
aria-expanded={setupInfoExpanded} and aria-controls pointing to the panel, move
the onClick logic to the button and add onKeyDown handlers as needed (or rely on
native button behavior) to support keyboard activation, and update the hidden
panel styles (when setupInfoExpanded is false) to include visibility: hidden and
pointer-events: none so inner focusable elements (e.g., the Let's Encrypt <a>)
cannot receive focus while collapsed.
In `@ui/src/context/Ad4minContext.tsx`:
- Around line 351-367: The current loadHostingState inside the useEffect couples
both runtime calls so a single failure prevents updating either flag; change
loadHostingState to fetch each flag independently (e.g., call
state.client.runtime.multiUserEnabled() and
state.client.runtime.freeHostingEnabled() separately or use Promise.allSettled)
and update setState for each result individually so a failure of
freeHostingEnabled() does not suppress updating multiUserEnabled (refer to
loadHostingState, state.client, runtime.multiUserEnabled(),
runtime.freeHostingEnabled(), and setState).
---
Outside diff comments:
In `@rust-executor/src/lib.rs`:
- Around line 508-523: The code currently calls
Ad4mDb::with_global_instance(|db|
db.get_free_hosting_enabled()).unwrap_or(false) per user which silently treats
DB read errors as paid (false) and does the lookup repeatedly; instead, call
Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) once at the
start of the flush and handle errors explicitly: if the call returns Ok(value)
use that value, but if it returns Err(e) log the error (using error!) and set
global_free = true to avoid forcing paid mode on transient failures; keep the
existing per-user call to Ad4mDb::with_global_instance(|db|
db.get_user_free_access(email)) and its error handling for get_user_free_access,
and remove the unwrap_or(false) usage.
In `@ui/src/components/Hosting.tsx`:
- Around line 1304-1439: The user cards only show the "Free Access" j-badge when
(user as any).freeAccess is true, which is misleading when global
freeHostingEnabled is on; update the badge rendering inside the users.map card
(where j-badge is rendered) to display when freeHostingEnabled || (user as
any).freeAccess so every card shows a consistent "Free Access" badge in global
free-hosting mode while leaving per-user logic unchanged elsewhere (e.g.,
toggles and credit controls).
- Around line 843-894: The effects that load persisted paid-hosting state and
auto-fetch membrane proofs should be skipped when the paid-hosting toggle is
off; update the outer useEffect that defines loadHostSession and the subsequent
useEffect that calls fetchMembraneProof to early-return when the paid-hosting
flag is disabled (e.g. check your paid/free toggle like isPaidHostingEnabled or
isFreeHostingEnabled at the top of both hooks), so loadHostSession does not call
invoke/get_host_registration or call setHostSession/saveHostSession on 401 and
the second effect does not call fetchMembraneProof(hostSession); keep the
existing logic untouched otherwise and only add the guard around the existing
code paths referencing loadHostSession, fetchMembraneProof, hostSession,
membraneProofStatus, membraneProofAttempted, setHostSession, and
saveHostSession.
---
Nitpick comments:
In `@rust-executor/src/graphql/query_resolvers.rs`:
- Around line 1018-1021: Hoist the global free-hosting flag out of the per-user
loop: call Ad4mDb::with_global_instance(|db| db.get_free_hosting_enabled()) once
(assign to a local variable, e.g. global_free) before iterating users, then in
the loop compute free_access as global_free || Ad4mDb::with_global_instance(|db|
db.get_user_free_access(&user.username)); this avoids re-reading
get_free_hosting_enabled per user and prevents extra DB locking and inconsistent
snapshots.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e7f9fd9b-34c6-4e9d-a73c-0cd56cdff42c
📒 Files selected for processing (9)
core/src/runtime/RuntimeClient.tscore/src/runtime/RuntimeResolver.tsrust-executor/src/db.rsrust-executor/src/graphql/mutation_resolvers.rsrust-executor/src/graphql/query_resolvers.rsrust-executor/src/lib.rsui/src/components/Hosting.tsxui/src/components/Wallet.tsxui/src/context/Ad4minContext.tsx
1. RuntimeResolver mock — runtimeFreeHostingEnabled() now returns true to match the default 2. Payment guard — runtime_request_payment short-circuits with an error when free hosting is on 3. Admin-only mutation — runtime_set_free_hosting_enabled now checks is_admin_credential instead of capability 4. Accessibility — setup disclosure uses a semantic <button> with aria-expanded/aria-controls, collapsed panel has visibility: hidden + pointer-events: none 5. Independent loading — Promise.allSettled so one failure doesn't block the other 6. Hoisted global_free in lib.rs — read once before the loop, errors default to true (free) to avoid blocking users 7. Free Access badge — shows on all user cards when freeHostingEnabled is on 8. Skip paid-hosting effects — loadHostSession and membrane proof fetch early-return when free hosting is enabled 9. Hoisted global_free in query_resolvers — read once before the user loop
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
connect/src/components/views/LoggedInDashboard.ts (1)
376-438:⚠️ Potential issue | 🟡 MinorFinish the unit rename on the balance chip.
The wallet/top-up copy now says
wHOT, but the same screen still rendersHOTfor the remaining-balance label on Line 361. Mixing both labels in one dashboard reads like two different currencies.✏️ Small follow-up
- <span class="credit-label">HOT</span> + <span class="credit-label">wHOT</span>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@connect/src/components/views/LoggedInDashboard.ts` around lines 376 - 438, The balance unit label still shows "HOT" on the remaining-balance chip in LoggedInDashboard's render template; update that literal to "wHOT" so the UI consistently uses the same unit. Locate the remaining balance display in the LoggedInDashboard component (the render/template that shows the balance chip/remaining-balance label) and replace the "HOT" text with "wHOT" (and update any nearby unit strings or helper constants used for that label). Ensure any related tests or snapshots that assert the label are updated to expect "wHOT".
🧹 Nitpick comments (1)
connect/src/components/views/HostBrowser.ts (1)
279-281: Share the operation-rate predicate withHostDetail.This normalizes
"link write"locally, butconnect/src/components/views/HostDetail.tsstill uses an exactSet(["link write"])lookup. A host that publishes"Link Write"or extra whitespace will be excluded from the preview average here but still show up as token pricing in the detail view. Extract one helper and use it in both places so the two screens classify rates identically.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@connect/src/components/views/HostBrowser.ts` around lines 279 - 281, Extract a shared helper (e.g., normalizeRateDescription or isLinkWriteRate) that lowercases and trims rate.description and use it in both HostBrowser (where tokenRates is computed) and HostDetail (where Set(["link write"]) is currently used) so both components classify "link write" consistently; replace the inline filter r => r.description.trim().toLowerCase() !== "link write" and the exact Set lookup with calls to the new helper (export it from a common module or a nearby shared utils file) so variants like "Link Write" or extra whitespace are handled identically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@rust-executor/src/graphql/mutation_resolvers.rs`:
- Around line 65-69: The current reads of get_free_hosting_enabled() use
unwrap_or(false) so DB errors incorrectly fall back to "paid" mode; change those
call sites to fail-open (default to free) by replacing unwrap_or(false) with
unwrap_or(true) or otherwise treating Err as free. Update the global_free
assignment and the other occurrences inside check_compute_credits() and
runtime_request_payment() so that DB read failures result in free hosting (true)
rather than enabling payment logic, ensuring behavior matches
rust-executor/src/lib.rs.
- Around line 2604-2608: After toggling the global flag in
Ad4mDb::with_global_instance (the call to set_free_hosting_enabled), invalidate
connected user state so clients receive updated HostingUserInfo: add all
currently connected user emails (or their identifiers used by the credit flush
loop) to DIRTY_CREDIT_USERS or explicitly publish updated HostingUserInfo for
those users so the credit flush loop in rust-executor/src/lib.rs will push fresh
free_access/remaining_credits to clients; update the mutation_resolvers flow to
perform this after set_free_hosting_enabled (preserving existing error mapping)
and ensure any helper used to enumerate connected users matches the identifier
type expected by DIRTY_CREDIT_USERS.
---
Outside diff comments:
In `@connect/src/components/views/LoggedInDashboard.ts`:
- Around line 376-438: The balance unit label still shows "HOT" on the
remaining-balance chip in LoggedInDashboard's render template; update that
literal to "wHOT" so the UI consistently uses the same unit. Locate the
remaining balance display in the LoggedInDashboard component (the
render/template that shows the balance chip/remaining-balance label) and replace
the "HOT" text with "wHOT" (and update any nearby unit strings or helper
constants used for that label). Ensure any related tests or snapshots that
assert the label are updated to expect "wHOT".
---
Nitpick comments:
In `@connect/src/components/views/HostBrowser.ts`:
- Around line 279-281: Extract a shared helper (e.g., normalizeRateDescription
or isLinkWriteRate) that lowercases and trims rate.description and use it in
both HostBrowser (where tokenRates is computed) and HostDetail (where Set(["link
write"]) is currently used) so both components classify "link write"
consistently; replace the inline filter r => r.description.trim().toLowerCase()
!== "link write" and the exact Set lookup with calls to the new helper (export
it from a common module or a nearby shared utils file) so variants like "Link
Write" or extra whitespace are handled identically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 358e91c2-e721-415c-a884-1a94ec45edfd
📒 Files selected for processing (12)
connect/src/components/views/HostBrowser.tsconnect/src/components/views/HostDetail.tsconnect/src/components/views/LoggedInDashboard.tsconnect/src/types.tsconnect/src/web.tscore/src/runtime/RuntimeResolver.tsrust-executor/src/graphql/mutation_resolvers.rsrust-executor/src/graphql/query_resolvers.rsrust-executor/src/lib.rsui/src/components/Hosting.tsxui/src/components/Wallet.tsxui/src/context/Ad4minContext.tsx
✅ Files skipped from review due to trivial changes (2)
- connect/src/web.ts
- connect/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- core/src/runtime/RuntimeResolver.ts
- ui/src/components/Wallet.tsx
- ui/src/components/Hosting.tsx
Summary
Summary by CodeRabbit
New Features
Behavior Changes