* feat(secrets): add optional descriptions to workspace secrets
Workspace secrets already have a backing credential row with a description
column, but nothing surfaced it. Teammates had no way to record what a
secret is for.
- Add a Description field to the secret detail page, matching the
integrations credential page, gated on workspace-secret admin
- Fold the value and description editors into one Save/Discard pair and
one unsaved-changes guard; two guards cannot coexist, since each seeds
its own same-URL history entry
- Match descriptions in the secrets settings search
- Expose description on GET/PUT /api/v2/secrets and in the CLI
Descriptions are workspace-only: env_personal credential rows are
per-workspace mirrors of one user-global secret, so one saved there would
exist in a single workspace, and a personal secret has no teammates to
inform. The API rejects a description on personal scope rather than
silently dropping it, and omitting it on PUT leaves any existing
description untouched so a value rotation cannot erase it.
* fix(secrets): address review findings on secret descriptions
- Patch the credential detail cache optimistically on update. `onMutate`
cancelled the detail query but only patched the lists, so a detail-backed
editor stayed dirty after a successful save until the refetch landed —
long enough for Discard to restore the pre-save value over the committed
one, and for Back to open the unsaved-changes guard.
- Memoize `useSecretValue`'s returned callbacks and object, per the hook
convention, so the composed form's save/discard stop churning per render.
- Reject a description on a personal secret in the domain layer rather than
only at the v2 boundary. The internal credential update path accepted one
for any type, writing data every reader hides.
- Normalize an empty description to null so the API and UI agree.
- Correct the secrets documentation, which described a Display Name field
the detail view does not have and omitted the scope rule.
- Drop the CLI's copy of the 500-character bound; it can't import the
contract, so a copy only drifts from the message the API already returns.
- Collapse a redundant save guard and align the description write gate with
the render gate.
Leaves the integrations credential page byte-identical to staging.
* fix(secrets): keep the API docs example and CLI column order stable
Backward-compatibility fixes for anyone who never sets a description.
- Move the blank-to-null normalization out of the contract and into the
route. A Zod `.transform()` on any property drops the whole request
schema's OpenAPI examples, which had silently removed the Set Secret
request example from the published docs.
- Append the CLI `description` column instead of inserting it before
`updated`. `--output text` is positional, so inserting would shift every
field an existing script cuts.
- Reject a description on a personal secret with a message that says so,
rather than dropping the field and falling through to the generic
"no updatable fields" error.