Skip to content

feat(ui): rebuild Start a session on subturtle-ui - #68

Open
SomiVista wants to merge 2 commits into
claude/subscription-screen-subturtle-uifrom
claude/start-session-screen-subturtle-ui
Open

feat(ui): rebuild Start a session on subturtle-ui#68
SomiVista wants to merge 2 commits into
claude/subscription-screen-subturtle-uifrom
claude/start-session-screen-subturtle-ui

Conversation

@SomiVista

@SomiVista SomiVista commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Rebuilds Start a session (/sessions/new) on subturtle-ui, matching the design in the redesign project.

Second-to-last screen in the pilotui → subturtle-ui migration. Session logic is unchanged: the same dataProvider bundle controller, formData shape and defaults, isFormValid via the exposed selectionError/randomError, the freemium openVoiceCapModal gate, and pickPhraseIds + encodeSessionRequest/practice/live-session.

Library (ui/)

  • StSegmentedControl and StProgressBar, ported from the design system's components/core/SegmentedControl.jsx and ProgressBar.jsx. Two deliberate additions the React sources can't express with inline styles: :focus-visible rings, and block on the segmented control so it can stretch to a form field.
  • Seven Solar icons added to the ICONS allowlist.

Screen

Bundle picker Search, Recent / Due today / Never practised / All chips, and a colour-coded tile per bundle showing N phrases · N due.
Coach card Voice / Text segmented control in the header; voice cards carry the server list's descriptions and a Preview pill that plays a sample.
Summary Sticky panel: bundle, mode, coach voice, fallback, phrase range, the voice-minute meter, and the primary Start session button.
States Populated, empty (no bundles → New bundle), and a loading skeleton that holds the two-column shape.

The bundle tile colour is derived from the bundle id (djb2), so it is stable for the bundle's life and identical in the picker and the summary, with no schema change.

Two things that needed data, not just markup

Due counts. The picker's N due line and the Due today / Never practised chips need review state, and leitner_system is an owner-access collection the client cannot read. get-phrase-management-info now also returns duePhraseIds. The field is additive and its one existing caller (the Leitner phrase picker) ignores it.

Repeat your last session. Sourced from localStorage, written when a session starts — not from list-live-sessions, which is a Learner+ entitlement that throws for exactly the tier most likely to press the button. It fills the form in rather than starting immediately, so the setup stays visible before any voice minutes are spent. The trade-off is that it is per-browser.

Voice previews — needs a decision

The Preview pill plays a sample through the existing textToSpeechBase64 function, mapping each Gemini coach to the closest Google TTS voice. It conveys the tone the description promises but is not the exact voice the session will use — the Gemini Live prebuilt voices aren't addressable outside a live session. If that mismatch isn't acceptable, say so and I'll drop the pill; useVoicePreview.ts is self-contained and the mapping is one constant.

Verification

  • Frontend build, yarn test:unit — 53 passing, 10 new covering the due-count and never-practised logic in utils/bundleReviewState.ts.
  • Server build; duePhraseIds confirmed over the wire against a scratch server on :8091 (6 due across 2 seeded bundles).
  • Screens checked in the browser: populated, bundle selected, and empty.
  • Prettier clean (printWidth 160, single quotes).

Server tests were not run to completion: yarn test OOMs in Jest. I confirmed this is pre-existing — it reproduces on a clean tree with this change stashed, on both node 18 and 22.

pilotui stays installed and untouched elsewhere; the shared StartLiveSessionForm keeps its props, v-model and defineExpose contract, so the bundle-detail modal and the legacy OpenAI entry point still work.

🤖 Generated with Claude Code

Rebuilds /sessions/new — the page, the Gemini StartNew entry point, the shared
StartLiveSessionForm and VoicePicker — in the st- design language. All script
logic is carried over verbatim: the dataProvider bundle controller, GEMINI_VOICES,
the formData shape, isFormValid over the form's exposed selectionError/randomError,
the freemium and voice-cap gates, and pickPhraseIds + encodeSessionRequest.

Layout follows the design: a two-column grid with the bundle picker and coach card
on the left and a sticky summary panel on the right, carrying the selection, the
voice-minute or freemium meter, and the single Start button.

Adds StSegmentedControl and StProgressBar to subturtle-ui, ported from the design
system's SegmentedControl.jsx and ProgressBar.jsx. Both are real design-system
primitives and both are used again by Session history.

Three design elements are deliberately not shipped, for want of data:
- The "Due today" / "Never practised" bundle chips. Leitner due-counts live per
  phrase in a separate database and a bundle carries no last-practised marker, so
  only Recent / All are wired.
- "Repeat your last session". Its only source is the list-live-sessions RPC, which
  is Learner+ gated and throws for the free tier — not cheap, and broken for the
  tier most likely to be on this page.
- The voice "Preview" pill. CoachVoice reserves avatarUrl as null and carries no
  audio; there is no preview endpoint.

The bundle pick tile, coach voice tile and filter chip stay page-local: they are
single-screen specialisations with no design-system counterpart, following the
call already made for InlineNotice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rebuilds /sessions/new to the redesign: a two-column layout with the bundle
picker and coach card on the left and a sticky session summary on the right.

Session logic is unchanged — the same dataProvider bundle controller, form
shape and defaults, selectionError/randomError validation, freemium gate and
pickPhraseIds + encodeSessionRequest hand-off to /practice/live-session.

Library:
- StSegmentedControl and StProgressBar, ported from the design system's
  SegmentedControl.jsx / ProgressBar.jsx, plus seven Solar icons.

Screen:
- Bundle picker with search, Recent / Due today / Never practised / All chips,
  and a per-bundle "N phrases · N due" line on a colour-coded tile.
- Coach cards carry the server voice list's descriptions and a Preview pill
  that plays a sample through the existing textToSpeechBase64 function.
- Summary panel: bundle, mode, voice, fallback, phrase range, the voice-minute
  meter and the primary Start session button.
- "Repeat your last session" replays the last setup from localStorage rather
  than from list-live-sessions, which is Learner+ and throws for the free tier.

The picker's due counts need review state the client cannot read
(leitner_system is owner-access), so get-phrase-management-info returns an
additional duePhraseIds array. The field is additive; its only existing caller
ignores it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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