FE-564: Flexible turn-response model#27
Conversation
…-ai/sdk client.messages.stream() (#25) ## Stack Context This stack finishes the SDK migration branch and then layers the flexible turn-response model on top of it. PR #25 is the parent branch for that work; PR #27 carries the response-model changes. ## What? - migrate the interview and observer path onto the current SDK/tooling seam - harden the workspace, controller, rendering, and test boundaries around that seam - project the live turn card before route invalidation so follow-on response-model work has a stable UI boundary ## Why? The FE-564 response-model work needed to be split out on top of `a3c46d1`. That makes this PR the foundation branch again: it now owns the migration, workspace stabilization, and live turn-card projection that the child branch depends on. Updating the metadata keeps the stack legible after the branch split.
🤖 Augment PR SummarySummary: This PR remodels choice-turn replies into a structured “turn response” model so interviews can capture zero/one/many option selections plus optional/required free-text. Changes:
🤖 Was this summary useful? React with 👍 or 👎 |
| }; | ||
| const selectedOptionIds = selectedOptions.map((option) => option.id); | ||
| const selectedOptionContents = selectedOptions.map((option) => option.content); | ||
| const responseText = formatTurnResponseText({ |
There was a problem hiding this comment.
formatTurnResponseText() can still return an empty string if selected option content is empty and no freeText is provided; that would persist a data-turn-response without a corresponding text/answer seam. This could break transcript/resume assumptions that every user response yields non-empty visible text.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| const hasSelection = turn.options.some((o) => o.is_selected); | ||
| const options = turn.options ?? []; | ||
| const persistedSelections = options.filter((option) => option.is_selected).map((option) => option.position); | ||
| const [selectedPositions, setSelectedPositions] = useState<number[]>(persistedSelections); |
There was a problem hiding this comment.
selectedPositions is initialized from persistedSelections only once; if turn.options later updates (e.g., post-invalidate/hydration) the checkbox state can drift from the persisted selection while inputs are locked by hasPersistedSelection. This can leave the card showing unchecked options even though the server considers the turn answered.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| lines.push(questionLine); | ||
| } | ||
| if (turn.answer) lines.push(`Answer: ${turn.answer}`); | ||
| const selectedOptions = |
There was a problem hiding this comment.
buildInterviewerContext() derives chosen options from option.is_selected but derives free-text from persisted data-turn-response; if those two sources ever diverge, interviewer context could become internally inconsistent with the canonical persisted response. Consider projecting both chosen options and free-text from the same persisted response record when present.
Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| } | ||
| const uniquePositions = [...new Set(positions)]; | ||
| const selectedOptions = findTurnOptionsByPositions(turn, uniquePositions); | ||
| if (selectedOptions.length !== uniquePositions.length) { |
There was a problem hiding this comment.
If positions contains anything not found in turn.options, submitTurnResponse() returns early without surfacing an error, which could make stale-state cases look like a no-op to users. Consider ensuring this path reports a failure through the existing mutation error surface so the UI can explain what happened.
Severity: low
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
3852b56 to
67e3a59
Compare

Stack Context
This stack continues from PR #25 by remodeling choice-turn replies into structured turn responses.
PR #25 establishes the migration and live-turn-card seam; this PR carries the flexible response model above it.
What?
data-turn-responseparts with a human-readable summary seamWhy?
The old flow assumed one immediate option selection mapped to one scalar answer string.
This branch retires that assumption so the interview UI can capture richer responses without losing persistence, hydration, or context coherence.
It also formalizes the split from
a3c46d1, so the response-model work now lives on its own FE-564 branch and PR.