feat(uat-batch-12): password-reveal env messaging + berth Latest-stage sortable

- registry-driven-form password-reveal eye toggle: when the value is
  resolved from env / default fallback (not port / global override),
  the toggle is now disabled with a tooltip explaining "Value comes
  from the environment. Configure in admin to enable reveal." Stops
  the silent-no-op confusion that read as a broken toggle.
- Berth list: 'Latest deal stage' column dropped enableSorting:false.
  Service-side adds a stageSort correlated subquery that ranks each
  berth by the highest active interest's pipelineStage (enquiry=1 →
  contract=7); NULLS LAST regardless of direction so empty rows
  always land at the bottom.

tsc clean. 1419/1419 vitest pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 18:11:17 +02:00
parent 901fc363a5
commit ca51000401
3 changed files with 58 additions and 3 deletions

View File

@@ -436,7 +436,27 @@ function SettingField({
type="button"
variant="ghost"
size="sm"
disabled={reveal.isPending}
disabled={
reveal.isPending ||
// Disable when the value is resolved from env/default and the
// rep hasn't typed anything yet — there's no in-app cleartext
// path for those, and silently no-op'ing was indistinguishable
// from a broken toggle.
(!showSecret &&
resolved?.isSet === true &&
(resolved?.source === 'env' || resolved?.source === 'default') &&
!(typeof draft === 'string' && draft.length > 0))
}
title={
!showSecret &&
resolved?.isSet === true &&
(resolved?.source === 'env' || resolved?.source === 'default') &&
!(typeof draft === 'string' && draft.length > 0)
? 'Value comes from the environment. Configure in admin to enable reveal.'
: showSecret
? 'Hide value'
: 'Reveal value'
}
onClick={() => {
if (showSecret) {
// Hide. If this draft came from the server reveal, drop it so

View File

@@ -273,7 +273,7 @@ export const berthColumns: ColumnDef<BerthRow, unknown>[] = [
{
id: 'latestInterestStage',
header: 'Latest deal stage',
enableSorting: false,
enableSorting: true,
cell: ({ row }) => {
const s = row.original.latestInterestStage;
if (!s) return <span className="text-muted-foreground">-</span>;