Skip to content

FE-564: Flexible turn-response model#27

Merged
lunelson merged 7 commits into
mainfrom
ln/fe-564-flexible-turn-response-model
Apr 7, 2026
Merged

FE-564: Flexible turn-response model#27
lunelson merged 7 commits into
mainfrom
ln/fe-564-flexible-turn-response-model

Conversation

@lunelson
Copy link
Copy Markdown
Contributor

@lunelson lunelson commented Apr 7, 2026

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?

  • persist choice-turn replies as structured data-turn-response parts with a human-readable summary seam
  • support zero, one, or many selected options plus optional free-text
  • keep transcript hydration, resume, and interviewer-context projection aligned with the structured response shape

Why?

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.

@linear
Copy link
Copy Markdown

linear Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor Author

lunelson commented Apr 7, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@lunelson lunelson changed the title chore: restore lint baseline and fix deprecated APIs FE-564: Flexible turn-response model Apr 7, 2026
@lunelson lunelson marked this pull request as ready for review April 7, 2026 16:17
Copy link
Copy Markdown
Contributor Author

lunelson commented Apr 7, 2026

Merge activity

  • Apr 7, 4:18 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Apr 7, 4:24 PM UTC: Graphite rebased this pull request as part of a merge.
  • Apr 7, 4:24 PM UTC: @lunelson merged this pull request with Graphite.

@lunelson lunelson changed the base branch from ln/fe-559-migrate-sdk to graphite-base/27 April 7, 2026 16:21
lunelson added a commit that referenced this pull request Apr 7, 2026
…-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.
@lunelson lunelson changed the base branch from graphite-base/27 to main April 7, 2026 16:22
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 7, 2026

🤖 Augment PR Summary

Summary: 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:

  • Adds a data-turn-response part schema (selectedOptionIds[] + optional freeText with a “free-text required when no options” rule).
  • Updates the client turn card to support multi-select (checkboxes), optional additional context, and explicit submit actions for selected vs free-text-only responses.
  • Updates the /turns/:turnId/select endpoint to accept positions[] and/or freeText, persist both the structured part and a human-readable summary seam, and support many-selections.
  • Adjusts interviewer context projection to emit response-shaped history when structured response data exists.
  • Expands regression coverage across client/server tests for persistence, hydration, and projection.
  • Tooling/infra: introduces oxlint config + typecheck for Vite/Drizzle config; tweaks markdown/highlighting boundaries and observer generation API usage.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

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

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

Comment thread src/server/app.ts
};
const selectedOptionIds = selectedOptions.map((option) => option.id);
const selectedOptionContents = selectedOptions.map((option) => option.content);
const responseText = formatTurnResponseText({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Fix This in Augment

🤖 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Comment thread src/server/context.ts
lines.push(questionLine);
}
if (turn.answer) lines.push(`Answer: ${turn.answer}`);
const selectedOptions =
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Fix This in Augment

🤖 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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

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.

1 participant