Skip to content

feat(demo): add disabled badges + read-only fieldsets across settings UI#187

Merged
TerrifiedBug merged 3 commits intomainfrom
feat/demo-ui-badges
Apr 27, 2026
Merged

feat(demo): add disabled badges + read-only fieldsets across settings UI#187
TerrifiedBug merged 3 commits intomainfrom
feat/demo-ui-badges

Conversation

@TerrifiedBug
Copy link
Copy Markdown
Owner

Summary

Pairs with #185 to make the read-only nature of the public demo obvious before a user interacts. Backend rejects credential/identity mutations in demo, but the UI was still letting users type into API key, S3 secret, Git PAT, OIDC client secret, and SCIM token fields and only surfacing the rejection on submit.

New primitives

src/components/demo-disabled.tsx:

  • <DemoDisabledBadge /> — inline lock badge for card titles
  • <DemoDisabledNotice /> — amber info banner above a section
  • <DemoDisabledFieldset> — wraps content in <fieldset disabled> + notice; passthrough outside demo mode

All three short-circuit to no-op when `isDemoMode()` is false, so non-demo deployments are unaffected.

Surfaces updated

  • `/settings/ai` — provider, base URL, API key, model, save/test
  • `/settings/backup` — storage backend selector, S3 credentials, schedule, manual backup, restore, delete
  • `/settings/auth` — OIDC issuer/client/secret, group mappings
  • `/settings/scim` — enable toggle, base URL, token generator
  • `/environments/[id]` Git Integration tab — repo URL, branch, PAT, mode
  • `/environments/[id]` Agent Enrollment tab — Generate/Revoke disabled; Quick Start snippets remain visible with a clearly-fake `vf_enroll_demo_example_...` placeholder so the install flow still demonstrates how it would work

Test plan

  • `pnpm vitest run` -- 2532 tests pass
  • `tsc --noEmit` clean
  • Smoke in demo: badges visible, inputs disabled, fieldset notices shown
  • Smoke in non-demo: no badges, no fieldsets, behaviour unchanged

…ngs UI

Backend already rejects credential and identity mutations in demo mode
(PR #185), but the UI still let users type into fields and click Save,
producing toast errors at submit time. This adds frontend signaling so
the read-only state is obvious before the user interacts.

Adds three small primitives in src/components/demo-disabled.tsx:
- DemoDisabledBadge -- inline "Demo" lock badge for card titles
- DemoDisabledNotice -- amber info banner above a section
- DemoDisabledFieldset -- wraps content in <fieldset disabled> + notice;
  passthrough outside demo mode

Integrates them across the five settings surfaces flagged in the audit
plus the per-environment enrollment token panel:
- /settings/ai (AI provider + API key)
- /settings/backup (S3 storage + manual backups + schedule)
- /settings/auth (OIDC + IdP team mappings)
- /settings/scim (SCIM provisioning + bearer token)
- /environments/[id] (Git Integration tab + Agent Enrollment tab)

For agent enrollment specifically: the Generate / Revoke buttons are
disabled in demo, the Quick Start install snippets remain visible
(now showing a clearly-fake "vf_enroll_demo_example_..." placeholder)
so the demo still demonstrates the install flow without minting real
tokens. The /api/agent/enroll endpoint already returns 403 in demo, so
the placeholder cannot be redeemed.

Components are no-ops outside demo mode (isDemoMode() short-circuits),
so non-demo deployments are unchanged.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 27, 2026

Greptile Summary

This PR introduces three new UI primitives (DemoDisabledBadge, DemoDisabledNotice, DemoDisabledFieldset) and applies them across six settings/environment surfaces to make read-only demo-mode state visually obvious before users interact. All three primitives short-circuit to no-ops when isDemoMode() is false, so production deployments are unaffected.

  • P1 — backup-settings.tsx: A standalone <DemoDisabledNotice> is placed directly before <DemoDisabledFieldset> (which has no message prop), causing DemoDisabledFieldset to also render its internal default notice — two amber banners appear back-to-back in demo mode. Pass the custom message to DemoDisabledFieldset and remove the outer <DemoDisabledNotice>.

Confidence Score: 4/5

Safe to merge after fixing the double-notice in backup-settings; all other surfaces are correct and non-demo deployments are unaffected.

One P1 correctness bug (duplicate amber banner in demo mode) keeps this below a clean 5. The rest of the implementation is straightforward and correct.

src/app/(dashboard)/settings/_components/backup-settings.tsx — standalone DemoDisabledNotice must be removed in favour of passing its message to DemoDisabledFieldset.

Important Files Changed

Filename Overview
src/components/demo-disabled.tsx New primitives (DemoDisabledBadge, DemoDisabledNotice, DemoDisabledFieldset) are correct; all short-circuit to no-op outside demo mode, and the fieldset approach is valid HTML for disabling native form controls.
src/app/(dashboard)/settings/_components/backup-settings.tsx A standalone DemoDisabledNotice rendered directly before DemoDisabledFieldset (which also renders a notice internally) causes two amber banners to appear in demo mode.
src/app/(dashboard)/settings/_components/auth-settings.tsx DemoDisabledFieldset correctly wraps both OIDC and IdP Group Mapping forms with appropriate messages; fieldset-over-form disables all descendant controls as expected.
src/app/(dashboard)/environments/[id]/page.tsx Generate/Revoke buttons correctly disabled via isDemoMode(); Quick Start snippets shown with clearly-fake placeholder token; no logic issues.
src/components/environment/git-sync-section.tsx DemoDisabledFieldset wraps the full Git Integration form content cleanly; no issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["isDemoMode()\nNEXT_PUBLIC_VF_DEMO_MODE === 'true'"] -->|false| B["Return children unchanged\n(no-op passthrough)"]
    A -->|true| C["DemoDisabledFieldset"]
    C --> D["Render DemoDisabledNotice\n(amber banner with message)"]
    C --> E["Render fieldset disabled\n+ opacity-70"]
    E --> F["All native inputs/buttons/selects\ninside are disabled by HTML spec"]

    G["Surfaces using DemoDisabledFieldset"] --> H["ai-settings: AI provider form"]
    G --> I["auth-settings: OIDC form\n+ IdP Group Mappings form"]
    G --> J["backup-settings: All backup cards"]
    G --> K["scim-settings: SCIM toggle + token"]
    G --> L["git-sync-section: Git config form"]

    M["enrollment tab (page.tsx)"] -->|"isDemoMode()"| N["Generate/Revoke buttons\ndisabled=true"]
    M -->|"isDemoMode()"| O["Show Quick Start\nwith DEMO_EXAMPLE_ENROLL_TOKEN"]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/app/(dashboard)/settings/_components/backup-settings.tsx
Line: 221-224

Comment:
**Double amber notice rendered in demo mode**

A standalone `<DemoDisabledNotice>` is placed immediately before `<DemoDisabledFieldset>`, but `DemoDisabledFieldset` unconditionally calls `<DemoDisabledNotice message={message}>` internally. Since no `message` prop is passed to `DemoDisabledFieldset` here, in demo mode two banners appear back-to-back: the explicit custom one, then the default "This area is read-only in the public demo. Self-host VectorFlow to configure it." fallback.

Remove the standalone `<DemoDisabledNotice>` and pass its message to `DemoDisabledFieldset` instead:

```suggestion
      <DemoDisabledFieldset message="Backup creation, restore, and S3 storage configuration are disabled in the public demo. The buttons and inputs below are read-only.">
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(demo): add disabled badges and read..." | Re-trigger Greptile

Comment thread src/app/(dashboard)/settings/_components/backup-settings.tsx Outdated
TerrifiedBug and others added 2 commits April 27, 2026 09:23
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
…tion

The Greptile suggestion accepted on PR #187 dropped the function's
`return (` wrapper and the outer `<div>` while leaving two opening
`<DemoDisabledFieldset>` tags, producing a JSX parse error in CI.

Restores the return wrapper and consolidates to a single
`<DemoDisabledFieldset message=...>` (which already renders the notice
internally via its `message` prop, matching the suggestion's intent).
Also drops the now-unused DemoDisabledFieldset / DemoDisabledNotice
imports flagged by no-unused-vars.
@TerrifiedBug TerrifiedBug merged commit 546ae65 into main Apr 27, 2026
10 checks passed
@TerrifiedBug TerrifiedBug deleted the feat/demo-ui-badges branch April 27, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant