feat(b3-1): interest dimensions dual-source — yacht dims for the recommender

Per docs/superpowers/audits/alpha-uat-master.md Bucket 3 #1. When a
yacht is linked to the interest the rep can flip a per-interest toggle
so the berth recommender reads dimensions off the yacht record instead
of the rep-entered desired_* columns.

- Migration 0087 + interests.useYachtDimensions boolean (default false).
- Validator (createInterestSchema) accepts the new field; service insert
  + update paths spread it through automatically.
- berth-recommender.service.loadInterestInput dual-source resolution:
  when toggle=true AND yachtId is set AND the yacht has at least one
  measurement on file, the recommender uses the yacht's length / width /
  draft instead of the desired_* values. Falls back to the desired
  columns whenever any precondition fails (no yacht link, toggle off,
  or the yacht carries no measurements). Returned InterestInput gains
  a `dimensionsSource: 'interest' | 'yacht'` trace field.
- Interest form: under the "Berth size desired" section, when a yacht
  is linked, a checkbox surfaces — "Use the linked yacht's dimensions
  for the recommender". When checked, the three dimension inputs grey
  out (DimensionInput gains a `disabled` prop) so the rep can't
  accidentally edit the now-overridden values. Hint text spells out
  the fallback behaviour.

Verified: tsc clean, 1493/1493 vitest, migration applied.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-25 17:22:57 +02:00
parent 8998f68c0f
commit da391b1830
5 changed files with 70 additions and 0 deletions

View File

@@ -860,6 +860,9 @@ interface DimensionInputProps {
unit: 'ft' | 'm';
ftValue: string | number | undefined;
onChangeFt: (next: string | undefined) => void;
/** B3 #1: greyed out when the interest is set to read dimensions
* off the linked yacht instead of these fields. */
disabled?: boolean;
}
/**
@@ -878,6 +881,7 @@ function DimensionInput({
unit,
ftValue,
onChangeFt,
disabled,
}: DimensionInputProps) {
const focusedRef = useRef(false);
const [display, setDisplay] = useState<string>(() => computeDisplay(ftValue, unit));
@@ -904,6 +908,7 @@ function DimensionInput({
min={0}
placeholder={placeholder}
value={display}
disabled={disabled}
onFocus={() => {
focusedRef.current = true;
}}