Skip to content

feat(web): per-device provider settings#4479

Open
t3dotgg wants to merge 3 commits into
mainfrom
t3code/add-device-provider-controls
Open

feat(web): per-device provider settings#4479
t3dotgg wants to merge 3 commits into
mainfrom
t3code/add-device-provider-controls

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Jul 24, 2026

Copy link
Copy Markdown
Member

Problem

Providers settings were hardwired to the primary backend. When working through T3 Connect or app.t3.codes, remote boxes could not be configured — controls were missing or ineffective.

What this does

  • Lists every registered execution environment (primary, T3 Connect, SSH, desktop-local) and lets you pick one.
  • Scopes provider reads, settings writes, status refreshes, provider updates, and instance creation to the selected environment.
  • Adds honest states for loading config, offline/reconnecting, connection errors, no environments, and known read-only sessions.
  • Coalesces nullable remote provider snapshots before existing array logic.
  • Keeps shared model favorites/preferences intact when deleting or resetting provider config on one device.

Single local-primary setups keep the existing Providers layout with no extra device section.

No contracts, server RPC, relay, or permission-scope changes — the environment-scoped commands already carry environmentId.

Verification

  • pnpm --filter @t3tools/web typecheck — passed
  • pnpm --filter @t3tools/web test — 172 files / 1,519 tests passed
  • Lint on changed files — passed
  • Browser check of /settings/providers: provider rows render, add-instance dialog opens/cancels, refresh completes, no Providers-specific console errors

New tests cover environment ordering/selection fallback, all access states, non-primary command routing, add-instance scoping, nullable snapshots, and shared-preference preservation. Interactive multi-device and disconnected-device checks were blocked by single-environment runtime state and are covered by those tests instead.

🤖 Generated with Claude Code


Note

Medium Risk
Large settings UI move with per-environment writes and permission-sensitive edit vs read-only paths; delete/reset semantics changed for shared model prefs without backend changes.

Overview
Providers settings are no longer tied to the primary device. The panel lives in ProviderSettingsPanel and adds a Devices picker (hidden when only the local primary is registered) so reads, writes, refreshes, updates, and add-instance flow all go through environmentId.

Connection and orchestration:operate gating drive editable vs read-only status rows vs loading/unavailable/error UI. AddProviderInstanceDialog takes environmentId and environmentLabel; copy names the target environment.

Behavior change: deleting a custom instance or resetting a default driver on one device only patches providerInstances (and legacy providers on reset)—favorites and providerModelPreferences are left alone on that environment. Nullable remote provider atoms are coalesced to [] before existing card logic runs.

Reviewed by Cursor Bugbot for commit 370174b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add per-device environment selection to provider settings

  • Refactors ProviderSettingsPanel to support selecting among multiple environments, routing all provider reads, writes, and commands to the selected environment via useEnvironmentSettings/useUpdateEnvironmentSettings.
  • Introduces SelectedEnvironmentProviderSettings to gate editability based on connection state and operate permissions, surfacing read-only and loading/error states via new ReadOnlyProviderSettings and classifyProviderEnvironmentAccess logic.
  • AddProviderInstanceDialog now requires environmentId and environmentLabel props, scoping provider instance creation to a specific environment and displaying the environment label in the dialog description.
  • Behavioral Change: deleting a provider instance no longer clears favorites or providerModelPreferences; resetting the default instance no longer clears shared preferences.

Macroscope summarized 370174b.

Providers settings were hardwired to the primary backend, so remote boxes
reached through T3 Connect or app.t3.codes could not be configured.

- List every registered execution environment and let one be selected
- Scope provider reads, settings writes, refreshes, updates, and instance
  creation to the selected environment
- Gate controls on raw server config, connection phase, and operate scope
- Keep shared model preferences intact when removing per-device config

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a771513d-0de0-41dc-93ed-06236fb39de4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/add-device-provider-controls

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.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 24, 2026
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.logic.ts
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx
@macroscopeapp

macroscopeapp Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a significant new feature (per-device provider settings) with substantial new code including new components, environment selection logic, and access control. There is also an unresolved comment identifying a potential bug in the access control flow for remote environments.

You can customize Macroscope's approvability policy. Learn more.

Addresses review findings on the per-device providers panel.

- Treat unresolved primary session scopes as loading instead of editable, so
  controls are never offered before permissions are known
- Render provider status rows for read-only sessions instead of replacing the
  whole panel with a blocking message
- Distinguish a hydrating environment catalog from having no devices

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@t3dotgg

t3dotgg commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Addressed all three Bugbot findings in 6a822e6.

Unknown operate access treated as editable (medium) — classifyProviderEnvironmentAccess now takes operateAccess: "granted" | "denied" | "pending" instead of boolean | null. A pending primary session resolves to the loading state, so controls are never offered before permissions are known. An unauthenticated session is now denied rather than falling through to editable.

Read-only blocked provider viewing (medium) — read-only sessions now render a ReadOnlyProviderSettings section listing each provider with its status dot, driver label, version, and auth summary, plus one line explaining that configuration cannot be changed. Previously the whole panel was replaced by that message, which contradicted its own copy.

Loading catalog looked empty (low) — the panel now reads isReady from useEnvironments() and shows "Loading devices" while the catalog hydrates, instead of claiming "No connected devices" moments before the primary environment appears.

Verification: pnpm --filter @t3tools/web typecheck passes; pnpm --filter @t3tools/web test passes 172 files / 1,520 tests (one new case covering the pending-access path); lint clean on changed files.

// Remote connection brokers request the standard client scopes, which include
// orchestration:operate. The environment RPC layer remains authoritative if a
// custom remote credential grants less access.
const operateAccess: ProviderOperateAccess = !isPrimary

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium settings/ProviderSettingsPanel.tsx:303

operateAccess is hardcoded to "granted" for every non-primary environment, so a remote environment connected with a credential that lacks orchestration:operate renders the full editable UI instead of ReadOnlyProviderSettings. The user is offered edit controls that the authoritative environment RPC will reject, producing failed writes. As the adjacent comment notes, lower-scope remote credentials are a supported case, so remote access must be derived from that environment's actual session scopes rather than assumed.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/web/src/components/settings/ProviderSettingsPanel.tsx around line 303:

`operateAccess` is hardcoded to `"granted"` for every non-primary environment, so a remote environment connected with a credential that lacks `orchestration:operate` renders the full editable UI instead of `ReadOnlyProviderSettings`. The user is offered edit controls that the authoritative environment RPC will reject, producing failed writes. As the adjacent comment notes, lower-scope remote credentials are a supported case, so remote access must be derived from that environment's actual session scopes rather than assumed.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6a822e6. Configure here.

Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx Outdated
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx Outdated
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx Outdated
- Extract resolvePrimaryOperateAccess so SWR revalidation no longer reports
  pending while cached session data is available, which was unmounting the
  provider editor and discarding in-progress edits
- Carry a reason on the loading state so waiting on permissions is not
  described as waiting on device configuration
- Name read-only provider rows with deriveProviderInstanceEntries so multiple
  instances of one driver stay distinguishable

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@t3dotgg

t3dotgg commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Addressed the latest findings in 370174b.

Session refresh unmounts editor (high, Bugbot) — Real regression from my previous fix, thanks. usePrimarySessionState is SWR-backed, so isPending goes true on every background revalidation; treating that as unknown flipped a working panel back to loading and dropped in-progress edits. Access resolution moved into a pure resolvePrimaryOperateAccess that prefers cached session data and only reports pending when no session has resolved yet. Covered by a test asserting granted while isPending: true with cached data.

Loading copy misstates wait reason (low, Bugbot) — The loading state now carries reason: "config" | "permissions", and the permissions case reads "Checking what this session is allowed to change." Config is checked first, so the copy always matches the actual wait.

Read-only names ignore labels (medium, Bugbot) — ReadOnlyProviderSettings now derives rows via deriveProviderInstanceEntries, the same helper the pickers use, so displayName and instance-slug fallbacks apply and two instances of one driver stay distinguishable.

Non-primary operateAccess hardcoded to "granted" (medium, Macroscope) — Accurate description, deliberately unchanged. I checked what the client can observe for a remote environment: the WS welcome payload, ServerConfig.auth, and the stored bearer/DPoP credentials carry no granted scopes (AuthAccessTokenResult.scope is discarded at exchange in apps/web/src/connection/platform.ts), and subscribeAuthAccess requires access:read, which AuthStandardClientScopes excludes. So there is no way to know a remote session's scopes today without either persisting the exchange scope or widening requested scopes — both beyond this PR. Remote sessions are minted with orchestration:operate, the server RPC layer stays authoritative, and a narrower custom credential surfaces as a rejected write rather than silent corruption. Comment in the code documents the asymmetry; happy to do the exchange-scope plumbing as a follow-up if preferred.

Verification: typecheck passes; 172 files / 1,524 tests pass (4 new cases for the access-resolution paths); lint clean on changed files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant