Skip to content

🪒 feat: Tighten Read-Only UX in Disabled Config Sections#76

Open
dustinhealy wants to merge 2 commits into
mainfrom
feat/readonly-section-ux
Open

🪒 feat: Tighten Read-Only UX in Disabled Config Sections#76
dustinhealy wants to merge 2 commits into
mainfrom
feat/readonly-section-ux

Conversation

@dustinhealy

@dustinhealy dustinhealy commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Cleans up the disabled-state UX in the config editor. The current behavior leaves a number of interactive affordances visible in fully read-only sections where the disabled state is ambiguous, misleading, or silently broken: a row-level pointer-events-none blocks legitimate header carets, an inline ToggleField still actuates and mutates state without surfacing the save bar, the BooleanChip stand-in stretches edge-to-edge in its flex slot, native disabled <select> controls keep their browser caret in a flat read-only view, expand carets on nested groups hint at write affordances that no longer exist, and the MCP and Custom Endpoints sections render inert "+ Create" buttons against a blank list when the user has no write capability.

This PR replaces each of those with cleaner read-only stand-ins. No behavior change for users who hold write capability on a section; all changes are gated on the existing disabled prop.

Change Type

  • New feature (non-breaking change which adds functionality)

Implementation

  • ConfigRow: drop row-level pointer-events-none when disabled. The catch-all overreached into the SectionHeader (blocking carets and other purely visual controls) and is unnecessary now that every value control inside SectionControls self-blocks via its own disabled attribute. Audited every controlType branch in FieldRenderer and every component in src/components/configuration/fields/ to confirm.
  • FieldRenderer (inline ToggleField): was missing the disabled prop. Inline toggles in disabled sections still actuated and silently mutated editedValues without triggering the save bar — the change registered as dirty against a tab the user could not save. Fixed.
  • FieldRenderer (BooleanChip): added self-start. SectionControls is a flex flex-col slot with default align-items: stretch, which made the chip span the full value area. Now sits at its natural pill width like ToggleField does.
  • FieldRenderer (NestedGroup): gains a disabled prop. When set, the caret/collapse button is dropped and the group renders flat with a static heading. Carets in fully read-only sections hinted at interactivity that no longer mattered. Threaded through all four NestedGroup call sites in FieldRenderer plus the "More settings" group in EndpointsRenderer.
  • fields/ListField: when disabled and the field has enum options, swap the native <select> for a static <span> showing the matched option label. Browsers render their own dropdown caret on disabled <select> regardless of any CSS, which conflicted with the new flat read-only look.
  • sections/EndpointsRenderer and sections/McpServersRenderer: hide the "+ Create endpoint" / "+ Create MCP server" button entirely when disabled rather than rendering an inert button next to a read-only list. When disabled and the list is empty, render a muted "No custom endpoints configured" / "No MCP servers configured" placeholder so the expanded section is not just empty space.
  • locales/en/translation.json: two new keys (com_config_no_custom_endpoints, com_config_no_mcp_servers). locize-i18n-sync handles the rest of the languages.

Testing

  • bun run lint: clean.
  • npx tsc --noEmit: clean.
  • Manual: walked through the disabled state for Custom Endpoints, MCP Servers, the Agents tab, and a few nested-group sections. Carets no longer render, create buttons no longer render, inline toggles can no longer be clicked, BooleanChip pills sit at natural pill width, list-of-enum fields render as static text, empty-section placeholders appear in the expected places.
  • Spot-checked the write path on the same sections to confirm: carets, toggles, selects, and create buttons all functional when the user holds write capability.

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • Local unit tests pass with my changes

Note

Low Risk
UI-only changes gated on the existing disabled prop; no auth, persistence, or API behavior changes.

Overview
Improves how read-only config sections look and behave when disabled is set, without changing the editable path for users who can write.

ConfigRow no longer applies row-level pointer-events-none on disabled rows, so header affordances are not blocked while individual controls still honor disabled.

FieldRenderer threads disabled into inline ToggleField (fixes toggles that could still mutate state), NestedGroup (flat static heading instead of expand/collapse), and BooleanChip (self-start so pills do not stretch full width). ListField shows enum values as static text instead of a disabled <select> with a dropdown caret.

Custom Endpoints and MCP Servers hide create buttons when disabled; empty read-only lists show new muted placeholders (com_config_no_custom_endpoints, com_config_no_mcp_servers). MCP still shows the generic “no entries” hint only when editable and empty.

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

The config editor previously left a number of interactive affordances
visible in fully read-only sections, where the disabled state was
either ambiguous, misleading, or actively broken.

ConfigRow no longer applies a row-level pointer-events-none when
disabled. The catch-all overreached into the SectionHeader (blocking
caret expansion and other purely visual controls) and is now
unnecessary because every value control inside SectionControls
self-blocks via its own disabled attribute.

The inline ToggleField at renderInlineField missed its disabled prop,
so inline toggles in disabled sections still actuated and silently
mutated editedValues without triggering the save bar. Fixed.

BooleanChip is the disabled-state stand-in for ToggleField, but the
SectionControls slot is a flex column with default align-items stretch,
so the chip rendered full-width across the value area. Added self-start
so it sits at its natural pill width.

NestedGroup gains a disabled prop. When set, the caret/collapse
affordance is dropped and the group renders flat with a static heading.
Carets in fully read-only sections were misleading because they hinted
at interactivity that no longer mattered. Threaded through the four
NestedGroup call sites in FieldRenderer plus the More settings group
in EndpointsRenderer.

ListField now swaps native select for a static span when disabled and
the field has enum options. Browsers render their own dropdown caret on
disabled selects which conflicted with the new flat read-only look.

CustomEndpointsRenderer and McpServersRenderer hide their Create button
entirely when disabled rather than rendering an inert button. When
disabled and there are no entries, a muted No custom endpoints
configured or No MCP servers configured placeholder is shown so the
expanded section is not just empty space. Two new English locale keys
were added; locize-i18n-sync handles the rest.

No behavior change for users with write capability on a section. All
removals are gated on the existing disabled prop.
@dustinhealy

Copy link
Copy Markdown
Contributor Author

@codex review

1 similar comment
@dustinhealy

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60ea4ddaa2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/configuration/sections/McpServersRenderer.tsx
Comment thread src/components/configuration/FieldRenderer.tsx Outdated
Two P3 codex findings on the read-only UX commit. McpServersRenderer rendered the generic com_config_no_entries fallback even when the new disabled-and-empty readonly message also fired, producing duplicate empty states; gating the generic fallback on !disabled matches the same pattern EndpointsRenderer uses. The NestedGroup disabled branch lost the data-section-id={sectionId} attribute that the enabled branch carries on its toggle button, so read-only nested sections dropped out of useActiveSection's scroll-tracking scan even though they remained reachable by id; restoring the attribute on the disabled header div keeps active-section highlighting consistent between read-only and editable sections.
@dustinhealy

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: 18631e7da1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@dustinhealy dustinhealy marked this pull request as ready for review June 16, 2026 08:05
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.

1 participant