Skip to content

French-flag redesign: warm palette, wide-screen shell, and scenario roadmap - #43

Merged
CodeWithOz merged 33 commits into
mainfrom
feature/french-flag-redesign
Jul 12, 2026
Merged

French-flag redesign: warm palette, wide-screen shell, and scenario roadmap#43
CodeWithOz merged 33 commits into
mainfrom
feature/french-flag-redesign

Conversation

@CodeWithOz

@CodeWithOz CodeWithOz commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Implements the approved Claude Design mockups for Parle: a French-flag-inspired
(blue/white/red) redesign replacing the dark theme, a wide-screen app shell
(nav rail + pinned footer + responsive roadmap sidebar), and the feature that
started the whole redesign — an editable, AI-tracked "scenario roadmap" for
role-play practice.

  • New design tokens + tablet(760px)/desktop(1200px) breakpoints, applied
    app-wide (every screen re-skinned: Free Talk, Practice Mode picker, both TEF
    flows, summaries, topic history, settings)
  • App shell: nav rail (+ "Past topic suggestions" entry), top bar, and a
    footer permanently pinned to the bottom — mic reads dominantly red (muted
    idle → vivid recording)
  • Scenario roadmap: an always-visible, editable step outline that
    auto-advances from AI inference during conversation (single- and
    multi-character scenarios), never regressing a step. Steps are
    AI-generated as part of the existing scenario-planning call (no extra
    request), with a heuristic fallback only when that call fails.
  • Existing saved scenarios without a roadmap get one proactively when you hit
    "Start" — generated and saved back onto the same scenario, not duplicated
  • Responsive collapse: nav rail (labels → icons → hamburger) and roadmap
    sidebar (static column → edge-tab drawer → bottom sheet) across
    desktop/tablet/mobile
  • Race-condition fix: superseded scenario-planning requests are now aborted
    via AbortSignal + a request-token guard, so a stale response can never
    overwrite a newer one
  • Tablet roadmap drawer: vertically centered near its trigger by default
    (margin-based centering, compatible with vaul's own slide animation),
    capped/scrollable for long content, with its drag handle moved to the left
    edge to match the horizontal slide direction

Process

Built via the repo's /dev workflow (TDD → build → review → browser
verification → docs), run manually across several passes since the custom
subagent types aren't registered in this tool session — each phase was
filled by a general-purpose agent given that role's exact instructions.
Manual code review (Opus, CodeRabbit was unavailable) found 0
Critical/Warning issues. All fixes from two rounds of live-usage testing are
included (multi-character roadmap not advancing, non-functional drag handles,
footer/sidebar misalignment, marker centering, and the two issues above).

Test plan

  • npm test — 601 passing (94 new across this branch)
  • npm run build — clean
  • Browser-verified: responsive shell at desktop/tablet/mobile, roadmap
    sidebar/drawer/sheet, drag-and-drop reorder, quick-start regeneration
    (with and without an existing roadmap), nav rail additions, tablet
    drawer centering/scroll/handle position — all via mocked
    OpenAI/seeded localStorage since no live API key is available in this
    environment

Summary by CodeRabbit

  • New Features

    • Added editable scenario roadmaps with step ordering, drag-and-drop reordering, progress tracking, and responsive navigation.
    • Roadmaps now advance automatically without moving backward and can be regenerated for saved scenarios.
    • Added mobile and desktop navigation improvements, including past topic access.
  • Style

    • Refreshed the interface with a French-inspired color palette, updated layouts, responsive breakpoints, and redesigned session controls.
  • Bug Fixes

    • Improved cancellation of outdated scenario-generation requests and preserved saved scenario details during regeneration.
  • Tests

    • Added coverage for roadmap editing, persistence, navigation, response handling, cancellation, and responsive design.

claude added 26 commits July 8, 2026 10:16
Adds failing tests (feature not yet implemented) covering:
- roadmap step status derivation + non-regressing auto-advance (utils/roadmapStepStatus.ts)
- Scenario.steps localStorage persistence + getScenarioSteps accessor (services/scenarioService.ts)
- conditional "currentStepIndex" response schema field (services/geminiService.ts)
- ScenarioSetup roadmap editor (add/remove/edit/reorder steps) UI contract
- ORB_STATE_COLORS export pinning the mic orb to a red-family palette
- index.css @theme tablet/desktop breakpoint tokens

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011uZQPdbxtsHWKe8Ur5PacX
- index.css: Tailwind v4 @theme block with tablet(760px)/desktop(1200px)
  breakpoints and a blue/white/red/navy/cream palette; swap dark slate
  body theme for the light cream backdrop.
- utils/roadmapStepStatus.ts: getRoadmapStepStatus/advanceRoadmapStep
  pure helpers for the scenario roadmap sidebar (never-regress auto-advance).
Export ORB_STATE_COLORS from components/Orb.tsx as the single source of
truth for per-state mic colors: muted red-pink IDLE, vivid red RECORDING,
neutral navy PROCESSING, blue PLAYING. Mic now reads as the dominantly
red control per the French-flag redesign.
…chema

- types.ts: ScenarioStep, Scenario.steps, VoiceResponse.currentStepIndex
- scenarioService.ts: getScenarioSteps() defensive accessor,
  seedRoadmapStepsFromSummary() heuristic seed for the roadmap editor, and
  a roadmap-tracking instruction block appended to the role-play system
  instruction when steps are present.
- geminiService.ts: RoadmapSingleCharacterSchema/ROADMAP_RESPONSE_SCHEMA —
  a separate conditional schema branch (mirrors the isTefQuestioning
  precedent) so "currentStepIndex" is only ever present in the response
  schema when activeScenario.steps is non-empty; extracted and forwarded
  on VoiceResponse the same way isRepeat/conceptLabels are.
Add controlled roadmapSteps/onRoadmapStepsChange props and render an
editable ordered step list (reorder via move-up/down, inline edit, remove,
add) in the AI-summary-confirmed branch, per wireframe 3d/4d. Drag handle
(⋮⋮) is decorative affordance; move-up/down buttons are the functional
reorder mechanism. handleStartPractice now builds Scenario.steps from the
editor state.
Restructure App.tsx into the 3-region shell from wireframe 2a: TopBar
(brand + settings + mobile hamburger menu), NavRail (Free Talk / Role
Play / TEF Ad / TEF Questions mode switcher, CSS-only tablet/desktop
responsive collapse), center conversation content (no longer capped at
max-w-2xl on wide screens), and a right-side ScenarioRoadmap sidebar
(desktop static column, tablet edge-tab + right drawer, mobile chip +
bottom sheet) shown only for active role-play scenarios with steps.

The footer (exit/start control + mic orb + speed) is now a single
persistent element pinned to the viewport bottom at every breakpoint,
built by restyling the existing compact Controls+Orb bar rather than
rebuilding — mic orb no longer moves to a centered landing position.

Nav mode switching reuses existing handlers verbatim (handleOpenScenarioSetup,
handleOpenTefAdSetup, setTefQuestioningMode('setup'), handleExitScenario,
handleExitTefAd, handleExitTefQuestioning) via a new handleNavSelect —
no forked exit/entry logic. Recolored App.tsx and Controls.tsx to the
French-flag light palette from index.css.

New components: components/NavRail.tsx, components/TopBar.tsx,
components/ScenarioRoadmap.tsx.

Test fix: __tests__/scenarioDescriptionRecordingAbortDiscard.test.tsx
queried `getByRole('button', { name: /Role Play/i })` unscoped, which
is now ambiguous because the persistent nav rail also exposes a
"Role Play" control by design. Added a data-testid to the test's own
PracticeModeSheet mock and scoped the query to it (mock has no other
distinguishing markup). Also hardened TopBar's mobile menu and the
roadmap drawer to only mount their contents while open, rather than
relying on vaul's internal presence handling, since this test's vaul
mock renders Drawer.Root children unconditionally.
ScenarioSetup is touched in this pass for the roadmap editor (and its
own wireframe screens 3c/3d are shown fully re-skinned in the approved
design), so the whole modal — inputs, saved-scenario list, transcript
picker, character cards, action buttons — moves from dark slate to the
parle-* light tokens for visual consistency with the new app shell.
Left the yellow API-key warning banner unchanged: it's a documented
cross-cutting pattern (AGENTS.md) shared with components pass 2 will
still be re-skinning, so changing it only here would fragment it.
Re-skin the practice-mode bottom sheet (wireframe 3b/4b) to the French-flag
light blue/white/red palette established in pass 1: white sheet on a
parle-navy scrim, parle-navy text, parle-blue mode cards, and the "past
topic suggestions" row folded from violet into the blue accent family.
Re-skin AdPersuasionSetup and AdQuestioningSetup (wireframe 3e/4e, 3g/4g)
to the light blue/white/red palette: white modal cards, parle-navy text,
parle-blue dropzone/CTAs, parle-red error states. The missing-Gemini-key
warning banner keeps its amber/yellow semantics (AGENTS.md) but switches
to light-mode-appropriate amber-50/amber-400/amber-900 instead of the old
dark-theme bg-yellow-900/border-yellow-600 pair; "green" success states
(Ad Analyzed, Start Conversation) fold into the blue accent family since
green has no place in the French-flag 3-color system.

PersuasionTimer/QuestioningTimer (TEF topbar chips) move off slate/amber
onto parle tokens, using two shades of red to preserve the low/critical
urgency gradient without reintroducing amber outside the credentials
banner.

Also fixes two dark-slate remnants in App.tsx that pass 1 missed: the
landing-view status text colors and the TEF "Time's Up" overlay (now
white card on a parle-navy scrim, matching every other modal).

Updates one test (adPersuasionCredentials.test.ts) whose regex asserted
the old bg-yellow-/border-yellow- class names — purely a color-class
string update, the assertion still verifies the warning banner renders.
Re-skin the post-exercise summary family (wireframe 3i/4i) to the light
palette: TefAdSummary, TefQuestioningSummary, ScenarioReviewSummary (the
role-play equivalent), TefReviewPanel and ScenarioStandardizationReviewPanel
(embedded review content), and TefTopicSuggestionsList (shared topic-card
renderer) all move from slate cards to white cards on a parle-navy scrim.

Per the wireframe's wf-pill convention, "met"/positive states map to blue
and "unmet"/negative states map to red — not green/red — so all green
accents (checkmarks, "What Went Well", vocabulary corrections) fold into
the blue family, and TefAdSummary's Done button changes from amber to blue
to match TefQuestioningSummary (amber stays reserved for the credentials
warning banner per AGENTS.md). The violet "Topics You Could Have
Mentioned" heading also folds into blue, consistent with the rest of the
scope. Review-panel error states (not the credentials banner) map to red
as a destructive/failure state rather than amber.

No test changes needed — the review-panel and summary tests query by role
and text, not class strings.
Re-skin TefTopicHistorySheet (wireframe 3j/4j), TefRecentAdsCarousel,
AdThumbnail, and ApiKeySetup (Settings, wireframe 3k/4k) to the light
blue/white/red palette: white cards on a parle-navy scrim, parle-navy
text/borders, parle-blue primary actions. Green "most recent"/"Start"
accents fold into blue; the persuasion/questioning exercise-type badges
in topic history now differentiate via blue vs. red (previously blue vs.
emerald) since the palette only carries two content accent colors.

ImageLightbox keeps its full-bleed dark backdrop (a photo viewer stays
dark regardless of app theme, and AGENTS.md/task guidance explicitly
allows dark overlay scrims to remain) but swaps generic Tailwind slate
for the app's own parle-navy-900 dark token so the chrome reads as part
of the same design system rather than a leftover default.

No test changes needed here — tefTopicHistorySheet.test.tsx and
tefRecentAdsCarousel.test.tsx query by role/text, not class strings.
Re-skin the chat surfaces that render inside the new light shell's center
column: ConversationHistory (chat bubbles), ConversationHint, and
PracticeGuidePanel. Bubbles now match the wireframe's literal treatment —
white agent bubbles / light-blue user bubbles with navy borders and text,
rather than the old dark-slate-with-white-text messenger look. The
"Audio unavailable" inline warning moves from yellow to red (a failure
indicator, not the reserved credentials banner). PracticeGuidePanel's
violet accent folds into the blue family per the 3-color palette.
Matches the light-mode amber treatment already applied to
AdPersuasionSetup/AdQuestioningSetup in the redesign pass, since it was
left out of that pass's scope.
- Disable inactive modes in TopBar's mobile menu to match NavRail's
  guardrail against stacking a setup flow on a running session (review S1)
- Recolor the landing view's status-text warnings (missing API key,
  connection error) from dark-theme yellow/red-400 to the light-theme
  amber/parle-red tokens used everywhere else in the redesign
Add an AGENTS.md pattern section for the scenario roadmap's conditional
response-schema branch (RoadmapSingleCharacterSchema / currentStepIndex)
and the never-regress auto-advance rule in advanceRoadmapStep(), modeled
on the existing TEF Ad Questioning schema-selection section. Notes the
sentence-split seeding heuristic (seedRoadmapStepsFromSummary) as an
intentional, user-editable simplification rather than a bug, adds
corresponding review-agent notes, and a Version History entry.

Update README.md: describe the roadmap capability under Scenario
role-play, note the French-flag design tokens/breakpoints under Tech
stack, and add NavRail/TopBar/ScenarioRoadmap to the project layout
table.
Pins down the contract for replacing the sentence-split heuristic with
a real AI call: ScenarioSummarySchema must require a 2-8 entry `steps`
array, processScenarioDescriptionOpenAI must surface it in its JSON
result (and fall back to `steps: []` on error), and App.tsx must prefer
those AI steps over seedRoadmapStepsFromSummary. Fails before the
implementation exists.
Extend the existing OpenAI scenario-planning call (structured output,
already producing summary + characters) to also return a `steps: string[]`
field (2-8 entries) describing the roadmap beats — no extra request, same
latency/cost as before.

App.tsx now prefers these AI-generated steps when seeding the roadmap
editor, falling back to the sentence-split heuristic
(seedRoadmapStepsFromSummary) only when the AI call fails or returns no
usable steps (e.g. a non-JSON legacy response).
Replaces the now-outdated "sentence-split seeding is intentional, don't
swap in an AI call" guidance with the actual current behavior: the AI
call is now the primary source, and the heuristic is a documented
fallback that must not be removed. Updates the matching review-agent
note and adds a Version History entry.
Covers: multi-character roadmap schema/auto-advance, functional
drag-and-drop reorder in the roadmap editor, the nav rail's new "Past
topic suggestions" entry, and quick-start regeneration behavior for
saved scenarios without a roadmap yet.
createMultiCharacterSchema (and its multi-character system instruction)
never carried the roadmap currentStepIndex field — only the
single-character schema branch had it. Since characters.length > 1 is
checked before the roadmap-steps check in schema selection, any
multi-character scenario with roadmap steps (e.g. a bakery visit with
a Baker + Cashier, the exact example used throughout this feature's own
design mockups) silently dropped the field, so the roadmap sidebar
never advanced past step 1.

createMultiCharacterSchema now conditionally extends its base shape
with currentStepIndex when the scenario has non-empty steps, mirroring
the existing conditional-schema precedent (isTefQuestioning /
RoadmapSingleCharacterSchema). generateMultiCharacterSystemInstruction
now also appends the roadmap instruction section.
The unicode "▶" character renders off-center within its circular badge
across common fonts (inconsistent glyph metrics) — flex centering alone
can't fix that. Replaced it with a small inline SVG triangle, which
centers pixel-perfectly regardless of font.
The ⋮⋮ handle had no event handlers — dragging it did nothing; only the
up/down buttons actually reordered steps. Each step row is now a native
HTML5 drag source/drop target (draggable + dragstart/dragover/drop),
so dragging a row and dropping it on another row's position reorders
the list. The up/down buttons are unchanged.

feat: quick-starting a saved scenario without a roadmap now regenerates one

Clicking "Start" on a saved scenario with no `steps` (e.g. every scenario
saved before the roadmap feature existed) used to jump straight into
practice, permanently skipping the roadmap editor for it — the only way
to get a roadmap onto it was recreating the scenario from scratch.

handleQuickStart now branches: scenarios that already have steps keep
the unchanged fast path (onStartPractice directly); scenarios without
steps instead call the new onRegenerateRoadmap callback. The roadmap
editor's local "Start Practice" also gains a regeneratingScenario prop
so it reuses that scenario's existing id/createdAt on save (an update
in place) instead of generating a new scenario (which would have
produced a duplicate entry).
…rail extras in App.tsx

Three App.tsx changes bundled together since they touch overlapping
regions of the same handlers/JSX shell:

1. Multi-character response branch now calls advanceRoadmapStep the
   same way the single-character branch already did, consuming the
   currentStepIndex field added to the multi-character schema (see the
   geminiService.ts commit) — completes the fix for the roadmap being
   stuck on step 1 for multi-character scenarios.

2. handleRegenerateRoadmapForScenario (+ shared
   processScenarioDescriptionAndPopulate helper extracted from
   handleSubmitScenarioDescription) implements the app-side half of
   quick-start roadmap regeneration for saved scenarios without steps.

3. App shell restructure: the nav rail and roadmap sidebar are now both
   full-height siblings of a center column containing <main> + the
   footer, instead of the nav rail being nested in a row above the
   footer. Previously <main> centered its content across (row width -
   nav rail width), while the footer centered across the full row
   width, producing a real pixel offset between the two — verified via
   getBoundingClientRect() before/after (was offset, now 0px, widths
   and positions identical). The nav rail also gains an
   onOpenTopicHistory prop, rendering a "Past topics" entry (with a
   divider) below the four mode items — mirrors the same divider/entry
   pattern already used in PracticeModeSheet's bottom sheet, and opens
   the same TefTopicHistorySheet.
Pins down the fix for a real race condition found in live usage:
clicking "Start" on a saved scenario twice in a row (e.g. navigating
back and retrying) fired two concurrent AI roadmap-planning requests
with no cancellation, and whichever settled last silently overwrote
the other's data — independent of which one the user actually meant
to keep.
Accepts an optional signal, passed to the LangChain invoke() call via
RunnableConfig.signal so an aborted request's underlying HTTP call is
actually cancelled rather than left running to completion. An
abort-like error (isAbortLikeError) is re-thrown instead of being
swallowed into the generic fallback response, so callers can tell an
intentional cancel apart from a real failure.
…ositioning

Two App.tsx changes bundled together (both touch the roadmap sidebar's
tablet drawer / scenario-setup regions):

1. Race-condition fix: processScenarioDescriptionAndPopulate now aborts
   any in-flight scenario-planning request (and bumps a request token)
   before starting a new one, via a new cancelScenarioPlanningRequest
   helper. Both the success and error paths check that token before
   touching state, so a stale response can never win regardless of
   network settle order. Also called from handleCloseScenarioSetup, so
   an abandoned request can't write stale state after the modal closes.

2. Tablet roadmap drawer positioning fix: previously pinned edge-to-edge
   top-to-bottom (`inset-y-0`) regardless of content height, and its
   drag handle stayed at the top (a leftover from the bottom-sheet
   variant) despite the drawer sliding in from the right. Now:
   - Vertically centered near the edge tab by default, using
     `inset-y-0 + my-auto + h-fit` (margin-based centering, not
     `transform`, since vaul already drives the slide animation via an
     inline `transform` on the same element — verified empirically that
     centering-via-margin and vaul's positioning don't conflict, while
     centering-via-translate would have been silently clobbered).
   - Capped at `max-h-[90dvh]` so long roadmaps still get an internal
     scrollbar and effectively span the height, rather than overflowing
     the viewport uncapped.
   - The drag handle moved to the left edge (the side facing back into
     the main content) with a vertical orientation, matching the
     drawer's horizontal slide direction — dragging from the top
     incorrectly implied a vertical gesture. Handle sizing needed `!`
     (important) overrides since vaul injects its own
     `[data-vaul-handle]` size rule at runtime, which otherwise wins the
     specificity tie against a plain Tailwind utility class.
   - Verified via getBoundingClientRect()/getComputedStyle() in a real
     browser: short content centers correctly (equal top/bottom margin),
     long content caps at 90dvh and stays scrollable, and the mobile
     bottom-sheet variant (handle on top, pinned to bottom) is
     unaffected.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
parle 65d4333 Commit Preview URL

Branch Preview URL
Jul 12 2026, 06:07 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
parle-personal 65d4333 Commit Preview URL

Branch Preview URL
Jul 12 2026, 06:08 PM

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Walkthrough

Adds editable scenario roadmaps with AI-reported progress, monotonic advancement, persistence, regeneration, and cancellation-safe planning. It also introduces responsive navigation, roadmap presentation, a unified parle visual theme, and extensive tests for roadmap, schema, planning, and UI behavior.

Changes

Scenario roadmap flow

Layer / File(s) Summary
Roadmap contracts and AI integration
types.ts, utils/roadmapStepStatus.ts, services/geminiService.ts, services/openaiService.ts, services/scenarioService.ts
Defines roadmap data and progress contracts, selects roadmap response schemas, propagates currentStepIndex, generates AI steps, and supports abort-aware planning.
Application wiring and editing
App.tsx, components/ScenarioSetup.tsx, components/ScenarioRoadmap.tsx
Adds roadmap state, cancellation guards, auto-advance handling, responsive display, editing, drag reordering, regeneration, and saved-scenario identity reuse.
Navigation and visual shell
components/NavRail.tsx, components/TopBar.tsx, index.css
Adds responsive navigation, mobile mode selection, roadmap placement, breakpoints, and the parle theme.
Validation coverage
__tests__/*roadmap*, __tests__/ScenarioSetup.*, __tests__/openaiService.*, __tests__/scenarioService.*
Tests schema branching, step generation, cancellation, persistence, progression, regeneration, navigation, and roadmap editing.
Component theme migration
components/*
Updates existing modal, timer, conversation, review, topic, and control surfaces to the parle color palette.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ScenarioSetup
  participant App
  participant OpenAI
  participant Gemini
  participant ScenarioRoadmap
  User->>ScenarioSetup: edit or submit scenario
  ScenarioSetup->>App: provide roadmap configuration
  App->>OpenAI: request summary and steps
  OpenAI-->>App: return structured roadmap
  App->>Gemini: start roadmap-aware session
  Gemini-->>App: return currentStepIndex
  App->>ScenarioRoadmap: update monotonic progress
Loading

Possibly related PRs

Poem

A rabbit hops along the trail,
With numbered steps and ears held high.
AI marks progress without fail,
While colors bloom beneath the sky.
Regenerate, reorder, play—
The roadmap guides the way!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main changes: the French-flag redesign, wide-screen shell, and new scenario roadmap.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/french-flag-redesign

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
components/ScenarioRoadmap.tsx (1)

27-55: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Expose roadmap status to assistive technologies.

The done/current/upcoming state is conveyed only visually; the markers are aria-hidden, so screen readers receive identical step text for every item. Add aria-current="step" for the current item and a visually hidden status label for all three states.

Proposed accessibility fix
           <li
             key={step.id}
             className={`flex items-start gap-2.5 rounded-lg px-2.5 py-2 text-sm transition-colors ${
               isCurrent
                 ? 'bg-parle-blue-100 border border-parle-blue-500 text-parle-navy-900 font-medium'
                 : 'text-parle-navy-700'
             }`}
+            aria-current={isCurrent ? 'step' : undefined}
           >
+            <span className="sr-only">
+              {isDone ? 'Completed: ' : isCurrent ? 'Current: ' : 'Upcoming: '}
+            </span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ScenarioRoadmap.tsx` around lines 27 - 55, Add accessible roadmap
status in the list-item rendering around the existing isDone and isCurrent state
logic: set aria-current="step" only on the current item, and add a visually
hidden label identifying each step as done, current, or upcoming. Keep the
decorative marker aria-hidden and preserve the existing visual styling and step
text.
services/geminiService.ts (1)

226-240: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated response-schema selection logic into a shared helper.

These two IIFEs in createChatSession() and sendVoiceMessage() implement identical branching logic (multi-character → no-scenario → isTefQuestioning → roadmap → default). This is the exact class of duplication that already caused the bug this PR fixes elsewhere (the multi-character schema silently missing currentStepIndex because the roadmap branch wasn't added to every code path). Any future schema branch added to only one copy will silently regress the other.

♻️ Proposed extraction of shared schema-selection logic
+// Shared response-schema resolution, used by both createChatSession() and
+// sendVoiceMessage() to avoid the two call sites drifting out of sync when a
+// new schema branch (e.g. roadmap) is added.
+function resolveResponseSchema() {
+  if (activeScenario && activeScenario.characters && activeScenario.characters.length > 1) {
+    return createGeminiMultiCharacterSchema(activeScenario);
+  }
+  if (!activeScenario) {
+    return FREE_CONVERSATION_RESPONSE_SCHEMA;
+  }
+  if (activeScenario.isTefQuestioning) {
+    return TEF_QUESTIONING_RESPONSE_SCHEMA;
+  }
+  if (activeScenario.steps && activeScenario.steps.length > 0) {
+    return ROADMAP_RESPONSE_SCHEMA;
+  }
+  return SINGLE_CHARACTER_RESPONSE_SCHEMA;
+}

Then in createChatSession():

-  const responseSchema = (() => {
-    if (activeScenario && activeScenario.characters && activeScenario.characters.length > 1) {
-      return createGeminiMultiCharacterSchema(activeScenario);
-    }
-    if (!activeScenario) {
-      return FREE_CONVERSATION_RESPONSE_SCHEMA;
-    }
-    if (activeScenario.isTefQuestioning) {
-      return TEF_QUESTIONING_RESPONSE_SCHEMA;
-    }
-    if (activeScenario.steps && activeScenario.steps.length > 0) {
-      return ROADMAP_RESPONSE_SCHEMA;
-    }
-    return SINGLE_CHARACTER_RESPONSE_SCHEMA;
-  })();
+  const responseSchema = resolveResponseSchema();

And in sendVoiceMessage():

-    const responseSchemaForThisRequest = (() => {
-      if (activeScenario && activeScenario.characters && activeScenario.characters.length > 1) {
-        return createGeminiMultiCharacterSchema(activeScenario);
-      }
-      if (!activeScenario) {
-        return FREE_CONVERSATION_RESPONSE_SCHEMA;
-      }
-      if (activeScenario.isTefQuestioning) {
-        return TEF_QUESTIONING_RESPONSE_SCHEMA;
-      }
-      if (activeScenario.steps && activeScenario.steps.length > 0) {
-        return ROADMAP_RESPONSE_SCHEMA;
-      }
-      return SINGLE_CHARACTER_RESPONSE_SCHEMA;
-    })();
+    const responseSchemaForThisRequest = resolveResponseSchema();

Also applies to: 734-748

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/geminiService.ts` around lines 226 - 240, Extract the duplicated
response-schema branching from createChatSession() and sendVoiceMessage() into
one shared helper, using the existing ordering: multi-character, no scenario,
TEF questioning, roadmap, then single-character fallback. Replace both IIFEs
with calls to that helper so future schema branches are maintained in one place.
services/scenarioService.ts (1)

183-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication: reuse getScenarioSteps instead of re-deriving steps.

getScenarioSteps(scenario) (lines 11-13) already encapsulates the scenario?.steps ?? [] normalization this file exports specifically to avoid ad-hoc null-checking elsewhere. generateRoadmapInstructionSection re-implements the same pattern inline instead of calling it.

♻️ Proposed refactor
 function generateRoadmapInstructionSection(scenario: Scenario): string {
-  const steps = scenario.steps ?? [];
+  const steps = getScenarioSteps(scenario);
   if (steps.length === 0) return '';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/scenarioService.ts` around lines 183 - 186, Update
generateRoadmapInstructionSection to obtain steps through the existing
getScenarioSteps(scenario) helper instead of directly normalizing
scenario.steps. Preserve the current empty-steps early return and subsequent
behavior.
__tests__/roadmapStepStatus.test.ts (1)

108-151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a stepsLength === 0 test case for advanceRoadmapStep.

getRoadmapStepStatus explicitly tests the stepsLength <= 0 boundary (lines 92-95), but advanceRoadmapStep has no equivalent case. Since clamping into [0, stepsLength - 1] is ill-defined when stepsLength is 0, pinning the expected behavior here would guard against a future implementation regression (e.g., returning -1 or NaN).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@__tests__/roadmapStepStatus.test.ts` around lines 108 - 151, Add a focused
`stepsLength === 0` test to the `advanceRoadmapStep` suite, covering
representative inputs and asserting the intended finite boundary behavior
without returning `-1` or `NaN`. Keep the existing clamping and progression
tests unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@App.tsx`:
- Around line 1449-1459: Ensure closing the scenario setup always clears
isProcessingScenario even when it invalidates an in-flight request. Update
handleCloseScenarioSetup to reset the flag, or make the finally block in
processScenarioDescriptionAndPopulate clear it unconditionally while preserving
stale-request protection for other state updates.

In `@components/Orb.tsx`:
- Around line 11-12: Update the IDLE entry in ORB_STATE_COLORS to use a darker
red that provides stronger contrast against the white microphone icon, while
leaving the other orb state colors unchanged.

In `@components/ScenarioSetup.tsx`:
- Around line 186-193: Validate sourceIndex in handleRoadmapStepDrop before
calling splice, rejecting values below zero or at least roadmapSteps.length in
addition to NaN and targetIndex. Return without changing the roadmap for invalid
external drag data, while preserving the existing reorder behavior for valid
indices.

In `@index.css`:
- Line 39: Update the font-family declaration in the visible CSS rule to remove
quotes around the Inter font name, while preserving the sans-serif fallback.

---

Nitpick comments:
In `@__tests__/roadmapStepStatus.test.ts`:
- Around line 108-151: Add a focused `stepsLength === 0` test to the
`advanceRoadmapStep` suite, covering representative inputs and asserting the
intended finite boundary behavior without returning `-1` or `NaN`. Keep the
existing clamping and progression tests unchanged.

In `@components/ScenarioRoadmap.tsx`:
- Around line 27-55: Add accessible roadmap status in the list-item rendering
around the existing isDone and isCurrent state logic: set aria-current="step"
only on the current item, and add a visually hidden label identifying each step
as done, current, or upcoming. Keep the decorative marker aria-hidden and
preserve the existing visual styling and step text.

In `@services/geminiService.ts`:
- Around line 226-240: Extract the duplicated response-schema branching from
createChatSession() and sendVoiceMessage() into one shared helper, using the
existing ordering: multi-character, no scenario, TEF questioning, roadmap, then
single-character fallback. Replace both IIFEs with calls to that helper so
future schema branches are maintained in one place.

In `@services/scenarioService.ts`:
- Around line 183-186: Update generateRoadmapInstructionSection to obtain steps
through the existing getScenarioSteps(scenario) helper instead of directly
normalizing scenario.steps. Preserve the current empty-steps early return and
subsequent behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f023c4d8-22a8-4a8b-a3d3-8d2478d4a9ec

📥 Commits

Reviewing files that changed from the base of the PR and between 2057b4b and 8be8f6d.

📒 Files selected for processing (51)
  • AGENTS.md
  • App.tsx
  • README.md
  • __tests__/NavRail.topicHistory.test.tsx
  • __tests__/ScenarioSetup.quickStartRegenerate.test.tsx
  • __tests__/ScenarioSetup.roadmapEditor.dragReorder.test.tsx
  • __tests__/ScenarioSetup.roadmapEditor.test.tsx
  • __tests__/adPersuasionCredentials.test.ts
  • __tests__/openaiService.roadmapSteps.test.ts
  • __tests__/openaiService.scenarioDescriptionAbort.test.ts
  • __tests__/orbStateColors.test.ts
  • __tests__/responsiveBreakpoints.test.ts
  • __tests__/roadmapMultiCharacterAutoAdvance.source.test.ts
  • __tests__/roadmapMultiCharacterSchema.test.ts
  • __tests__/roadmapSchemaSelection.test.ts
  • __tests__/roadmapStepStatus.test.ts
  • __tests__/scenarioDescriptionAbort.source.test.ts
  • __tests__/scenarioDescriptionAiRoadmapSteps.source.test.ts
  • __tests__/scenarioDescriptionRecordingAbortDiscard.test.tsx
  • __tests__/scenarioService.roadmapSteps.test.ts
  • components/AdPersuasionSetup.tsx
  • components/AdQuestioningSetup.tsx
  • components/AdThumbnail.tsx
  • components/ApiKeySetup.tsx
  • components/Controls.tsx
  • components/ConversationHint.tsx
  • components/ConversationHistory.tsx
  • components/ImageLightbox.tsx
  • components/NavRail.tsx
  • components/Orb.tsx
  • components/PersuasionTimer.tsx
  • components/PracticeGuidePanel.tsx
  • components/PracticeModeSheet.tsx
  • components/QuestioningTimer.tsx
  • components/ScenarioReviewSummary.tsx
  • components/ScenarioRoadmap.tsx
  • components/ScenarioSetup.tsx
  • components/ScenarioStandardizationReviewPanel.tsx
  • components/TefAdSummary.tsx
  • components/TefQuestioningSummary.tsx
  • components/TefRecentAdsCarousel.tsx
  • components/TefReviewPanel.tsx
  • components/TefTopicHistorySheet.tsx
  • components/TefTopicSuggestionsList.tsx
  • components/TopBar.tsx
  • index.css
  • services/geminiService.ts
  • services/openaiService.ts
  • services/scenarioService.ts
  • types.ts
  • utils/roadmapStepStatus.ts

Comment thread App.tsx
Comment on lines 1449 to 1459
} catch (error) {
if (requestId !== scenarioPlanningRequestIdRef.current) return; // stale/aborted — ignore
console.error('Error processing scenario:', error);
setAiSummary('I understand your scenario. Ready to begin when you are!');
setScenarioCharacters([]); // Fallback to single-character
setRoadmapSteps(['']);
setScenarioCharacters(fallbackCharacters);
} finally {
setIsProcessingScenario(false);
if (requestId === scenarioPlanningRequestIdRef.current) {
setIsProcessingScenario(false);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP -C3 'isProcessingScenario' components/ScenarioSetup.tsx

Repository: CodeWithOz/parle

Length of output: 1427


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the scenario setup open/close flow and processing-state handling in App.tsx.
ast-grep outline App.tsx --view expanded | sed -n '1,260p'

printf '\n---- relevant references ----\n'
rg -n -C 4 'handle(Open|Close)ScenarioSetup|cancelScenarioPlanningRequest|setIsProcessingScenario|isProcessingScenario|ScenarioSetup' App.tsx

Repository: CodeWithOz/parle

Length of output: 6916


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether ScenarioSetup exposes close/cancel controls that remain usable while processing.
ast-grep outline components/ScenarioSetup.tsx --view expanded | sed -n '1,260p'

printf '\n---- close/cancel controls ----\n'
rg -n -C 4 'onClose|onCancel|Close|Cancel|handleClose|handleCancel|dismiss|back|nav' components/ScenarioSetup.tsx

Repository: CodeWithOz/parle

Length of output: 20650


isProcessingScenario can stay true after closing the setup mid-request (App.tsx:1179-1459). handleCloseScenarioSetup() bumps the request token, so the guarded finally in processScenarioDescriptionAndPopulate() skips setIsProcessingScenario(false) for the aborted request. Reset the flag in the close handler or clear it unconditionally in finally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@App.tsx` around lines 1449 - 1459, Ensure closing the scenario setup always
clears isProcessingScenario even when it invalidates an in-flight request.
Update handleCloseScenarioSetup to reset the flag, or make the finally block in
processScenarioDescriptionAndPopulate clear it unconditionally while preserving
stale-request protection for other state updates.

Comment thread components/Orb.tsx Outdated
Comment on lines +11 to +12
export const ORB_STATE_COLORS: Record<'IDLE' | 'RECORDING' | 'PROCESSING' | 'PLAYING', string> = {
IDLE: '#d9827e', // muted red-pink — recognizably red, low saturation

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Increase idle orb contrast.

#d9827e provides only about 2.8:1 contrast against the white microphone icon, making the idle control harder to perceive. Use a darker idle red or a dark idle icon color.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/Orb.tsx` around lines 11 - 12, Update the IDLE entry in
ORB_STATE_COLORS to use a darker red that provides stronger contrast against the
white microphone icon, while leaving the other orb state colors unchanged.

Comment on lines +186 to +193
const handleRoadmapStepDrop = (targetIndex: number) => (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const sourceIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
if (Number.isNaN(sourceIndex) || sourceIndex === targetIndex) return;
const next = roadmapSteps.slice();
const [moved] = next.splice(sourceIndex, 1);
next.splice(targetIndex, 0, moved);
onRoadmapStepsChange(next);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate the drag source index before mutating the roadmap.

An external drop can supply -1 or an out-of-range value. splice() then moves the wrong item or inserts undefined; subsequently, handleStartPractice() crashes on text.trim().

Proposed fix
   const handleRoadmapStepDrop = (targetIndex: number) => (e: React.DragEvent<HTMLDivElement>) => {
     e.preventDefault();
-    const sourceIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
-    if (Number.isNaN(sourceIndex) || sourceIndex === targetIndex) return;
+    const sourceIndexValue = e.dataTransfer.getData('text/plain');
+    const sourceIndex = Number(sourceIndexValue);
+    if (
+      sourceIndexValue === '' ||
+      !Number.isInteger(sourceIndex) ||
+      sourceIndex < 0 ||
+      sourceIndex >= roadmapSteps.length ||
+      sourceIndex === targetIndex
+    ) return;
     const next = roadmapSteps.slice();

Based on learnings: preserve documented intentional patterns when the fix retains their benefits.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleRoadmapStepDrop = (targetIndex: number) => (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const sourceIndex = parseInt(e.dataTransfer.getData('text/plain'), 10);
if (Number.isNaN(sourceIndex) || sourceIndex === targetIndex) return;
const next = roadmapSteps.slice();
const [moved] = next.splice(sourceIndex, 1);
next.splice(targetIndex, 0, moved);
onRoadmapStepsChange(next);
const handleRoadmapStepDrop = (targetIndex: number) => (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const sourceIndexValue = e.dataTransfer.getData('text/plain');
const sourceIndex = Number(sourceIndexValue);
if (
sourceIndexValue === '' ||
!Number.isInteger(sourceIndex) ||
sourceIndex < 0 ||
sourceIndex >= roadmapSteps.length ||
sourceIndex === targetIndex
) return;
const next = roadmapSteps.slice();
const [moved] = next.splice(sourceIndex, 1);
next.splice(targetIndex, 0, moved);
onRoadmapStepsChange(next);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ScenarioSetup.tsx` around lines 186 - 193, Validate sourceIndex in
handleRoadmapStepDrop before calling splice, rejecting values below zero or at
least roadmapSteps.length in addition to NaN and targetIndex. Return without
changing the roadmap for invalid external drag data, while preserving the
existing reorder behavior for valid indices.

Source: Learnings

Comment thread index.css
}

body {
font-family: 'Inter', sans-serif;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove quotes around the font-family name per Stylelint.

Static analysis flags font-family-name-quotes on this line.

🔧 Proposed fix
-  font-family: 'Inter', sans-serif;
+  font-family: Inter, sans-serif;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
font-family: 'Inter', sans-serif;
font-family: Inter, sans-serif;
🧰 Tools
🪛 Stylelint (17.14.0)

[error] 39-39: Expected no quotes around "Inter" (font-family-name-quotes)

(font-family-name-quotes)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@index.css` at line 39, Update the font-family declaration in the visible CSS
rule to remove quotes around the Inter font name, while preserving the
sans-serif fallback.

Source: Linters/SAST tools

claude added 7 commits July 12, 2026 18:05
cancelScenarioPlanningRequest() bumps the request token to invalidate
any in-flight roadmap-generation request, but that also means the
request's own `finally` block (guarded by that same token) skips
clearing isProcessingScenario. Left uncleared, reopening the setup
flow would show "Processing..." indefinitely with nothing actually in
flight. handleCloseScenarioSetup now clears it directly.

Code review fix.
#d9827e (the original muted red-pink) measured ~2.8:1 contrast against
the white mic icon, below WCAG's 3:1 minimum for UI components.
#ce5f5a improves this to ~3.9:1 while staying at 54% saturation —
still clearly less saturated than RECORDING's 62%, preserving the
muted-vs-vivid distinction.

Code review fix.
Native HTML5 drag data isn't guaranteed to come from this list (e.g.
dragging text/a link onto a row from elsewhere), so sourceIndex wasn't
guaranteed to be a valid array index. A negative value would splice
from the wrong end; an out-of-range value would splice in `undefined`
as a bogus step. Now rejected alongside the existing NaN/self-drop
checks.

Code review fix.
Done/current/upcoming was conveyed only by color and a decorative
(aria-hidden) marker. Added aria-current="step" on the current item
and a visually hidden status label per step.

Code review nitpick.
Locks in existing (already-correct) behavior: the clampIndex guard
returns 0 rather than NaN/-1 when there are no valid indices to clamp
into. No production code change.

Code review nitpick.
createChatSession() and sendVoiceMessage() each had an identical
schema-selection IIFE (multi-character, no scenario, TEF questioning,
roadmap, single-character fallback). Extracted to selectResponseSchema()
so future schema branches are maintained in one place instead of two.
Behavior unchanged — same branching order, same schemas.

Code review nitpick.
Was duplicating the same scenario.steps ?? [] normalization that
getScenarioSteps() already centralizes. Behavior unchanged.

Code review nitpick.
@CodeWithOz
CodeWithOz merged commit cced5a2 into main Jul 12, 2026
4 checks passed
@CodeWithOz
CodeWithOz deleted the feature/french-flag-redesign branch July 12, 2026 19:11
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.

2 participants