Skip to content

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

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

feat(web): per-device provider settings#4479
t3dotgg wants to merge 8 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 refactor with new session-scope gating and optimistic permission fallbacks; incorrect access classification could expose controls that RPC rejects, though writes remain server-enforced.

Overview
Providers settings are no longer tied to the primary device. The panel is extracted into ProviderSettingsPanel and, when more than one execution environment exists, adds a Devices picker (primary, T3 Connect, SSH, etc.) with connection status on each row.

All provider actions are scoped to the selected environmentId: settings read/write, refresh, one-click updates, and the add-instance dialog. Server commands already carried environmentId; the UI now passes it consistently.

Access is gated before showing editable controls: connection phase, whether server config is loaded, and whether the session has orchestration:operate (via new per-environment /api/auth/session atoms in client-runtime and useEnvironmentSessionState on web). Missing permission shows the real layout read-only (inert) with a limited-permissions notice; disconnected or loading devices get explicit unavailable/loading copy instead of broken controls.

Other behavior tweaks: deleting or resetting a provider instance on one device no longer clears shared favorites/model preferences in the same settings patch; default provider slots on older remote servers can omit legacy driver blobs without breaking the panel. Single primary-only setups keep the previous layout without the Devices section.

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

Note

Add per-device provider settings with environment selection to the Providers settings panel

  • Replaces the single primary-environment ProviderSettingsPanel with a multi-device aware version in ProviderSettingsPanel.tsx that lets users select an execution environment and manage providers per device.
  • Adds access-gating logic in ProviderSettingsPanel.logic.ts to classify each environment as editable, read-only, loading, unavailable, or error based on connection phase, config availability, and session scopes.
  • Extends createEnvironmentSessionAtoms in packages/client-runtime/src/state/session.ts with sessionStateAtom and sessionStateValueAtom that fetch /api/auth/session per environment using SWR semantics (30s stale, 5m idle TTL).
  • Adds useEnvironmentSessionState hook in apps/web/src/state/session.ts exposing auth session data with isPending/hasError flags for UI gating.
  • Moves utility functions (durationToSeconds, normalizeIntervalSeconds, backgroundActivityOverrideSettings) from SettingsPanels.tsx to SettingsPanels.logic.ts and removes the old ProviderSettingsPanel implementation from SettingsPanels.tsx.
  • Risk: ProviderSettingsPanel is no longer exported from SettingsPanels.tsx; the import path is updated in settings.providers.tsx but any other consumer importing from the old path will break at runtime.

Macroscope summarized 688524b.

Summary by CodeRabbit

  • New Features

    • Added environment-specific provider settings, including environment selection, access controls, connection status, health refresh, model preferences, and provider instance management.
    • Added read-only, loading, unavailable, error, and update-failure states.
    • Added policy explanation tooltips in settings.
    • Added clearer connection-status indicators and transition animations.
  • Bug Fixes

    • Provider settings now correctly read and update the selected environment’s configuration.
  • Tests

    • Added coverage for environment routing, access states, provider settings, and reset behavior.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.68% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding per-device provider settings for the web application.
Description check ✅ Passed The description clearly explains the problem, implementation, UI behavior, risks, and verification, but omits the template's explicit UI screenshots/video and checklist.
✨ 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 new feature for per-device provider settings with ~1400 lines of new code, new state management, and permission controls. Combined with an unresolved review comment identifying an access control bug in the dialog handling, this warrants human review.

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

@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.

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
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx Outdated
@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.

colonelpanic8 added a commit to colonelpanic8/t3code that referenced this pull request Jul 28, 2026
@t3dotgg
t3dotgg force-pushed the t3code/add-device-provider-controls branch from 370174b to 1913ad4 Compare July 31, 2026 04:37
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx
@t3dotgg

t3dotgg commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

Simplification pass in ca0d588, which also resolves the two open findings:

runProviderUpdate started-flag race (high, Macroscope) — replaced the started flag set inside the setUpdatingProviderDrivers updater with a synchronous ref guard (updatingDriversRef), mirroring the existing refreshingRef pattern in refreshProviders. The dispatch decision no longer depends on when React runs the updater.

Session fetch failure showed read-only (medium, Bugbot) — resolvePrimaryOperateAccess now takes hasError; a failed session fetch with no cached data resolves to granted with a comment explaining it is a transport problem, not a permission decision — the RPC layer still rejects unauthorized writes. Covered by a new test.

Cleanup from parallel reuse/simplification/efficiency/altitude reviews:

  • Connection dot/ping styling now comes from shared connectionPhaseDotClassName/connectionPhasePingClassName in ConnectionStatusDot.tsx instead of a third inline copy
  • The React Compiler hook harness and element-tree walker moved to src/test/ and are imported by both new test files (~130 duplicated lines removed)
  • durationToSeconds, normalizeIntervalSeconds, backgroundActivityOverrideSettings, PROVIDER_HEALTH_INTERVAL_STEP_SECONDS moved to SettingsPanels.logic.ts; PolicyTooltip to settingsLayout.tsx — the providers panel no longer imports from the SettingsPanels component file
  • Removed the setState-in-useEffect selection sync; selection is derived, so a device that briefly disappears regains its prior selection on reconnect
  • The primary-session atom only mounts for browser sessions against the primary; switching to a remote device no longer fires a pointless session fetch
  • Reused EnvironmentConnectionPhase, isElectron, and the shared EMPTY_SERVER_PROVIDERS; dropped the indefinitely-spinning loader in the unavailable row

Verification: typecheck passes; 195 files / 1,721 tests pass; lint clean on all touched files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (4)
apps/web/src/components/settings/ProviderSettingsPanel.tsx (2)

520-556: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist the local type declarations to module scope.

InstanceRow (lines 520-526) and LegacyProviderSettings (line 551) are declared inside the component, and LegacyProviderSettings is re-declared inside the driver loop and again in resetDefaultInstance (line 667). Move both to module scope so the same alias serves all three sites and the component body holds only runtime logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/settings/ProviderSettingsPanel.tsx` around lines 520
- 556, Move the InstanceRow and LegacyProviderSettings type aliases from the
component body to module scope, then remove the duplicate LegacyProviderSettings
declarations inside the visibleDriverKinds loop and resetDefaultInstance. Reuse
the module-level aliases at all existing sites while leaving the runtime logic
unchanged.

296-300: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the optimistic operateAccess="granted" default for non-primary devices.

Line 299 grants operate access to every non-primary environment and to Electron primary sessions. The PR description explains the reason: the client cannot observe remote session scopes, and the server stays authoritative. Add that rationale next to the call so a later reader does not treat it as a client-side authorization decision.

♻️ Proposed comment
   if (isPrimary && !isElectron) {
     return <PrimarySessionGatedProviderSettings environment={environment} />;
   }
+  // Remote and desktop-bridge sessions have no observable scope list on the
+  // client, so operate access is assumed granted; the server rejects writes
+  // this session is not allowed to make.
   return <AccessGatedProviderSettings environment={environment} operateAccess="granted" />;

As per coding guidelines: "Use comments mainly to describe how a function is used".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/settings/ProviderSettingsPanel.tsx` around lines 296
- 300, Add a concise inline comment beside the operateAccess="granted" prop in
the provider-selection function, documenting that this is an optimistic client
default because remote session scopes are not observable and the server remains
authoritative for authorization.

Source: Coding guidelines

apps/web/src/components/settings/AddProviderInstanceDialog.environment.test.tsx (1)

6-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the write, or narrow the test name.

The test name claims that the dialog "reads and writes settings through the supplied environment". The assertions at lines 51-52 only prove that both hooks received remoteEnvironmentId. No write is exercised, because handleSave is never called. update also returns a new vi.fn() on every call (line 8), so the updater the component holds is not observable from the test. Return a stable updater mock and assert that saving reaches it.

♻️ Proposed change
 const settingsHooks = vi.hoisted(() => ({
   read: vi.fn(() => ({ providerInstances: {} })),
-  update: vi.fn(() => vi.fn()),
+  updateSettings: vi.fn(),
+  update: vi.fn(),
 }));

Wire update to return settingsHooks.updateSettings, reset both in beforeEach, then invoke the save handler from the rendered element tree and assert that settingsHooks.updateSettings received the new providerInstances map.

Also applies to: 42-53

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/web/src/components/settings/AddProviderInstanceDialog.environment.test.tsx`
around lines 6 - 9, Update the environment test’s settings hook mocks so update
returns a stable settingsHooks.updateSettings mock, and reset both hook mocks in
beforeEach. Exercise the dialog’s save handler through the rendered element
tree, then assert settingsHooks.updateSettings receives the updated
providerInstances map, preserving the test’s existing remoteEnvironmentId
assertions.
apps/web/src/components/settings/ProviderSettingsPanel.environment.test.tsx (1)

123-126: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the fixed microtask wait with a deterministic completion signal.

flushPromises awaits two microtasks. The count is arbitrary: it matches the current number of await points inside refreshProviders and runProviderUpdate. If the component adds one more await, the assertions at lines 157 and 168 run before the command dispatch and the test fails for the wrong reason. Await the promise the mocked command returns, or expose the dispatch promise from the handler, so the test waits on a real receipt.

As per coding guidelines: "Tests must wait for typed receipts and worker drains in event-sourced async flows; do not use sleeps, polling, or arbitrary timeouts to make tests pass."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/src/components/settings/ProviderSettingsPanel.environment.test.tsx`
around lines 123 - 126, Replace the fixed two-microtask implementation of
flushPromises in the provider settings tests with a deterministic completion
signal tied to the mocked command promise or the handler’s dispatch promise.
Update the test setup and assertions around refreshProviders and
runProviderUpdate to await that typed receipt, ensuring command completion is
observed without arbitrary waits.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/src/components/settings/ProviderSettingsPanel.tsx`:
- Around line 557-570: Update the provider-instance setup around driver,
legacyConfig, and defaultLegacyConfig to handle missing legacy provider entries
without non-null assertions. Match resetDefaultInstance’s existing undefined
behavior: only construct the fallback effectiveInstance and compute
legacy-config dirtiness when the corresponding legacy entries exist, while
preserving explicitInstance handling for drivers without legacy configuration.

---

Nitpick comments:
In
`@apps/web/src/components/settings/AddProviderInstanceDialog.environment.test.tsx`:
- Around line 6-9: Update the environment test’s settings hook mocks so update
returns a stable settingsHooks.updateSettings mock, and reset both hook mocks in
beforeEach. Exercise the dialog’s save handler through the rendered element
tree, then assert settingsHooks.updateSettings receives the updated
providerInstances map, preserving the test’s existing remoteEnvironmentId
assertions.

In `@apps/web/src/components/settings/ProviderSettingsPanel.environment.test.tsx`:
- Around line 123-126: Replace the fixed two-microtask implementation of
flushPromises in the provider settings tests with a deterministic completion
signal tied to the mocked command promise or the handler’s dispatch promise.
Update the test setup and assertions around refreshProviders and
runProviderUpdate to await that typed receipt, ensuring command completion is
observed without arbitrary waits.

In `@apps/web/src/components/settings/ProviderSettingsPanel.tsx`:
- Around line 520-556: Move the InstanceRow and LegacyProviderSettings type
aliases from the component body to module scope, then remove the duplicate
LegacyProviderSettings declarations inside the visibleDriverKinds loop and
resetDefaultInstance. Reuse the module-level aliases at all existing sites while
leaving the runtime logic unchanged.
- Around line 296-300: Add a concise inline comment beside the
operateAccess="granted" prop in the provider-selection function, documenting
that this is an optimistic client default because remote session scopes are not
observable and the server remains authoritative for authorization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c185c90-e3b8-41da-becd-d207c2b3b28c

📥 Commits

Reviewing files that changed from the base of the PR and between e259dd2 and ca0d588.

📒 Files selected for processing (14)
  • apps/web/src/components/ConnectionStatusDot.tsx
  • apps/web/src/components/settings/AddProviderInstanceDialog.environment.test.tsx
  • apps/web/src/components/settings/AddProviderInstanceDialog.tsx
  • apps/web/src/components/settings/ProviderSettingsPanel.environment.test.tsx
  • apps/web/src/components/settings/ProviderSettingsPanel.logic.test.ts
  • apps/web/src/components/settings/ProviderSettingsPanel.logic.ts
  • apps/web/src/components/settings/ProviderSettingsPanel.tsx
  • apps/web/src/components/settings/SettingsPanels.logic.ts
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/settings/settingsLayout.tsx
  • apps/web/src/routes/settings.providers.tsx
  • apps/web/src/state/server.ts
  • apps/web/src/test/reactElementTree.ts
  • apps/web/src/test/reactHookHarness.ts

Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx Outdated
@t3dotgg
t3dotgg force-pushed the t3code/add-device-provider-controls branch from ca0d588 to 7a3c281 Compare August 4, 2026 21:19
Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx Outdated

@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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

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 997cfa5. Configure here.

Comment thread apps/web/src/components/settings/ProviderSettingsPanel.tsx
t3dotgg and others added 7 commits August 5, 2026 19:10
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>
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>
- 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>
Cleanup pass over the providers panel from parallel reuse, simplification,
efficiency, and altitude reviews:

- Share the connection-phase dot/ping mapping from ConnectionStatusDot instead
  of a third inline copy
- Extract the React Compiler hook harness and element-tree walker into
  src/test and reuse them from both new test files
- Move interval/background-activity helpers and PolicyTooltip out of the
  SettingsPanels component file into SettingsPanels.logic and settingsLayout
- Drop the setState-in-useEffect selection sync; the effective selection is
  derived, so a device that reconnects regains its prior selection
- Only mount the primary-session atom when a browser session is actually
  gated by it; remote devices skip the SWR fetch entirely
- Guard runProviderUpdate re-entry with a ref instead of a state-updater flag
- Treat a failed session fetch as transport trouble rather than denied access
- Reuse EnvironmentConnectionPhase, isElectron, and the shared
  EMPTY_SERVER_PROVIDERS constant; remove the indefinite loading spinner

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A remote device may run a server version whose settings predate a driver in
this build's DRIVER_OPTIONS, so the legacy providers mirror can lack the
entry. The default-slot loop asserted it non-null and would throw during
render; skip the slot instead when neither an explicit instance nor a legacy
blob exists, matching resetDefaultInstance's existing guard.

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

The missing-legacy guard used continue, which also skipped the custom
instance rows appended later in the same loop iteration. Only the default
slot depends on the legacy blob, so skip just that row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotgg force-pushed the t3code/add-device-provider-controls branch from 6d9de69 to f43e38e Compare August 6, 2026 02:16
@t3dotgg

t3dotgg commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

Rebased onto latest main (f43e38e). Conflicts were confined to imports in SettingsPanels.tsx against the Appearance font-settings work (#5103, #5397); no logic overlap. Dropped four imports the extraction left unused on the rebased base.

Verification after rebase: web typecheck clean, 210 test files / 1,826 tests passing, lint clean on changed files.

Resolved the two open threads: the Macroscope non-primary operateAccess finding is a documented scope limitation (no client channel exposes remote-environment scopes; RPC stays authoritative — see reply on the thread), and the Bugbot read-only snapshot finding was confirmed false positive against ProviderInstanceRegistryHydration.

Non-primary environments previously assumed operate access, so a
narrow-scope credential was offered edit controls whose writes the
environment RPC would reject. Each environment's /api/auth/session now
answers what this client may change, and sessions without
orchestration:operate see the full provider layout greyed out and inert
behind a limited-permissions notice instead of a separate stripped view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg

t3dotgg commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

688524b replaces the optimistic non-primary access assumption with real per-environment permissions, per discussion:

  • fetchEnvironmentSessionState in client-runtime reads /api/auth/session on any environment using the prepared connection's credential (cookie / bearer / DPoP), following the existing shell-snapshot HTTP pattern. No new server surface: the endpoint already reports granted scopes for every session method.
  • The session atoms factory exposes an SWR-backed sessionStateAtom per environment, keyed off the prepared connection so a re-pair with different scopes re-resolves automatically.
  • The providers panel derives operateAccess for non-primary devices from those scopes. Sessions without orchestration:operate now see the full provider layout greyed out and inert with a "Limited permissions" notice, instead of the separate stripped read-only list (which is deleted).
  • Older remote servers that predate scope reporting, and transport failures, stay optimistic — the environment RPC layer remains authoritative.

Verification: web + client-runtime + mobile typecheck clean, 1,831 web tests and 522 client-runtime tests passing (new coverage for resolveRemoteOperateAccess and the inert read-only rendering), lint clean.

</div>
</SettingsSection>

{isAddInstanceDialogOpen ? (

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.

🟠 High settings/ProviderSettingsPanel.tsx:892

When readOnly becomes true while the add-instance dialog is already open, the main provider layout becomes inert but AddProviderInstanceDialog is rendered outside that wrapper and remains fully interactive, so the user can still submit new provider instances via updateSettings despite the session no longer having operate permission. Consider closing the dialog (or gating it) when readOnly transitions to true, since the inert wrapper only covers the settings rows below it.

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

When `readOnly` becomes `true` while the add-instance dialog is already open, the main provider layout becomes `inert` but `AddProviderInstanceDialog` is rendered outside that wrapper and remains fully interactive, so the user can still submit new provider instances via `updateSettings` despite the session no longer having operate permission. Consider closing the dialog (or gating it) when `readOnly` transitions to `true`, since the `inert` wrapper only covers the settings rows below it.

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