Skip to content

feat: persistent indicator for overridden secrets#935

Open
tomaskir wants to merge 2 commits into
phasehq:mainfrom
tomaskir:feat/overridden-secret-indicator
Open

feat: persistent indicator for overridden secrets#935
tomaskir wants to merge 2 commits into
phasehq:mainfrom
tomaskir:feat/overridden-secret-indicator

Conversation

@tomaskir

@tomaskir tomaskir commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Overview

When a secret has an active personal override, the only cue in the environment view is the Override button's icon turning amber - and that button lives in the row's hover-gated action zone, so it is effectively invisible until you hover, and below 2xl it has no text label at all. For a feature whose whole point is "you are running a different value than your team," the active state is too easy to miss. There is no row-level indicator, no count, and no way to filter to overridden secrets.

This surfaces the override state persistently. Closes #929.

Proposed Changes

  • Persistent "Overridden" chip on overridden secret rows in the environment view, always visible (not hover-gated). A left-to-right gradient dissolves long values into the row background before the chip so the value never hard-overlaps it (same overlay technique introduced in feat: app secrets home improvements #752). The chip carries a tooltip; when your override value differs from the displayed team value the tooltip says so explicitly. The chip and gradient fade out only while the value field itself is focused (editing), so the field reclaims full width.
  • Active-override count and a filter toggle in the environment toolbar (next to Sort), styled to match the Sort control. Shows N Override(s); clicking it filters the list to only overridden secrets and turns amber while active. Rendered only when at least one active override exists, and it auto-clears if the last override is removed.
  • Extracted helpers secretHasActiveOverride, countActiveOverrides, and overrideValueDiffers into frontend/utils/secrets.ts, with unit tests. Both the row and the page consume them.

No schema/API work: GetSecrets already returns override { value isActive } and it is decrypted on the environment page, so this is a pure frontend surfacing change.

Screenshots or Demo

image

Release Notes

Overridden secrets now show a persistent "Overridden" badge in the environment view, and the toolbar shows a count of active overrides with a one-click filter to view only overridden secrets. Previously the only indication was an amber icon visible on hover.

Open Questions

  • Scope: this implements the row badge, count, filter, and value-differs hint. The maintainer noted a broader UX overhaul is coming; happy to trim to just the persistent badge if you would rather keep this minimal ahead of that.
  • The gradient fade reuses zinc stops from feat: app secrets home improvements #752, matched to the env list background (zinc-100/200 light, zinc-800/700 dark). Rows with modified/new/staged tints keep their tint behind the fade; called out in case you want the fade tint-aware.

Testing

  • Added a personal override helpers suite in frontend/tests/utils/secrets.test.ts covering secretHasActiveOverride (active/inactive/absent), countActiveOverrides (mixed list + empty), and overrideValueDiffers (all four branches). Full file: 96/96 pass.
  • eslint clean on all changed files (one pre-existing unrelated react-hooks/exhaustive-deps warning on page.tsx).
  • Production build (yarn build) succeeds.
  • Manually verified in the dev stack (Firefox only): chip rest/hover/focus states, fade over long values, tooltip text, the toolbar count + filter, and that the chip stays put when clicking hover-toolbar buttons (only hides while the value field is focused).

Reviewer Focus

  • frontend/components/environments/secrets/SecretRow.tsx - the chip + decorative gradient layer. Note the split (gradient is pointer-events-none/aria-hidden, chip is its own sibling so its native title is not suppressed; both z-30 over the z-20 field wrapper). Visibility is driven by a valueFocused state tied to the textarea's own focus/blur, deliberately not group-focus-within - the latter also fires when a tabIndex=-1 toolbar button is clicked, which would make the chip vanish.
  • frontend/app/[team]/apps/[app]/environments/[environment]/[[...path]]/page.tsx - showOverriddenOnly state, the count memo, the filter folded into filteredSecrets/folders/dynamic, and the toolbar button.
  • frontend/utils/secrets.ts - the three helpers.

Additional Context

Closes #929. Fade technique follows #752 (app secrets home improvements).

How to Test the Changes Locally

  1. Run the dev stack (docker compose --env-file .env.dev -f dev-docker-compose.yml up -d).
  2. Create or open an app environment with at least one saved secret.
  3. On a saved row, open Override, set a value, keep it active, Save.
  4. Confirm: a persistent "Overridden" chip on the row; a long value fades into the row before the chip; hovering the chip shows the hint; the chip stays when you click reveal/share/etc. and only hides while the value field is focused; the toolbar shows 1 Override next to Sort; clicking it filters to overridden secrets only. Add a second override to see the count increment.

Did You...

  • Ensure linting passes (code style checks)?
  • Update dependencies and lockfiles (if required) - not required
  • Update migrations (if required) - not required
  • Regenerate graphql schema and types (if required) - not required
  • Verify the app builds locally?
  • Manually test the changes on different browsers/devices? - tested on Firefox only

Surface active personal overrides in the environment view instead of
relying solely on the hover-gated amber Override button.

- Persistent "Overridden" chip on overridden secret rows. A left-to-right
  gradient dissolves long values into the row background before the chip
  (mirrors the action-button overlay from phasehq#752) instead of overlapping it,
  and the chip carries a tooltip noting your value differs from the team
  value.
- Active-override count and a filter toggle in the environment toolbar to
  show only overridden secrets.
- Extract secretHasActiveOverride / countActiveOverrides /
  overrideValueDiffers helpers into utils/secrets with unit tests.

Closes phasehq#929
@rohan-chaturvedi rohan-chaturvedi self-requested a review June 28, 2026 09:41

@rohan-chaturvedi rohan-chaturvedi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the PR @tomaskir! A couple of suggestions

  • toned down the visual noise of the persistent chip by removing the FULL CAPS label. It was a bit much, and not in line with the rest of our design language.
  • lets move the filter action into a broader filter menu that will allow filtering by other secret properties as well, such as secret type. We can handle this in a separate PR

}
className={clsx(
'absolute right-2 top-1/2 -translate-y-1/2 z-30 cursor-help transition ease',
'flex items-center gap-1 shrink-0 rounded-full px-2 py-0.5',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
'flex items-center gap-1 shrink-0 rounded-full px-2 py-0.5',
'flex items-center gap-1 shrink-0 rounded-full px-2 py-1',

'absolute right-2 top-1/2 -translate-y-1/2 z-30 cursor-help transition ease',
'flex items-center gap-1 shrink-0 rounded-full px-2 py-0.5',
'bg-amber-400/10 text-amber-500 ring-1 ring-inset ring-amber-400/30',
'text-2xs font-medium uppercase tracking-wider',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
'text-2xs font-medium uppercase tracking-wider',
'text-2xs',

)}
>
<FaUserEdit className="shrink-0" />
<span>Overridden</span>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
<span>Overridden</span>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The persistent indicator is a good idea, but the labelled chip in full CAPS is a bit too noisy IMO. Given the fact that its persistent and a distinct amber color, just an icon chip works fine here:

Image

Comment on lines +1390 to +1409
{activeOverrideCount > 0 && (
<button
type="button"
onClick={() => setShowOverriddenOnly((prev) => !prev)}
title={
showOverriddenOnly
? 'Show all secrets'
: 'Show only secrets with an active personal override'
}
className={clsx(
'bg-zinc-100 dark:bg-zinc-800 transition ease px-2 py-1.5 text-2xs 2xl:text-sm rounded-md flex items-center gap-2',
showOverriddenOnly
? 'text-amber-500'
: 'text-neutral-500 hover:text-neutral-900 dark:hover:text-neutral-100'
)}
>
<FaUserEdit />
{activeOverrideCount} {activeOverrideCount === 1 ? 'Override' : 'Overrides'}
</button>
)}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Rather than a dedicated button here to filter by override, I would rather we add a proper filter menu with options to filter secrets by:

  • Sealed type
  • Secret type
  • Config type
  • Booleans
  • Overrides
  • Dynamic
  • Rotating

For this PR, I'd suggest we remove this button entirely and add this filter in a separate PR with broader utility

@rohan-chaturvedi

Copy link
Copy Markdown
Member

@tomaskir I've opened this PR #940 to add the secret filter menu, which includes a filter for overridden secrets. Take a look if you can and let me know what you think!

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.

Overridden secrets have no persistent visual indicator in the UI

3 participants