Skip to content

feat(mora-i18n): PR 3 — narrow age picker + in-app language switch#128

Merged
youtalk merged 6 commits intomainfrom
feat/mora-i18n/03-age-narrow-and-language-switch
Apr 27, 2026
Merged

feat(mora-i18n): PR 3 — narrow age picker + in-app language switch#128
youtalk merged 6 commits intomainfrom
feat/mora-i18n/03-age-narrow-and-language-switch

Conversation

@youtalk
Copy link
Copy Markdown
Owner

@youtalk youtalk commented Apr 27, 2026

Summary

Final PR in the multi-L1 i18n stack. Behavioral changes for the dev install:

  • Narrow Step 2 age picker to 3 tiles (6 / 7 / 8), default 7 — matches the dyslexia intervention window (JP 小学校低学年). Tile font scaled from 72pt → 120pt to fill the freed real estate.
  • Add 4 new MoraStrings fields (homeChangeLanguageButton, languageSwitchSheetTitle, languageSwitchSheetCancel, languageSwitchSheetConfirm) authored across all five tables (JP entry / core / advanced + KO + EN).
  • Extract LanguagePicker view component from LanguagePickerView's row list, so the same row UI is reused by onboarding Step 1 and the new switch sheet.
  • Add LanguageSwitchSheet (ObservableObject model + LanguageSwitchSheetView) with confirm-disabled-when-unchanged guard.
  • Wire globe button into HomeView next to the wordmark; tap presents the sheet, commit writes LearnerProfile.l1Identifier and dismisses without disturbing other profile state (age / level / interests / font preserved).

Part of the multi-L1 i18n stack. See docs/superpowers/plans/2026-04-26-i18n-and-age-difficulty.md and docs/superpowers/specs/2026-04-26-i18n-and-age-difficulty-design.md §7.2 (age narrowing) and §7.3 (in-app language switch).

Test plan

  • swift test — MoraCore (149), MoraEngines (1), MoraUI (74), MoraTesting (22). All 0 failures.
  • xcodegen generate && xcodebuild build -project Mora.xcodeproj -scheme Mora -destination 'generic/platform=iOS Simulator' -configuration Debug CODE_SIGNING_ALLOWED=NO → BUILD SUCCEEDED.
  • swift-format lint --strict --recursive Mora Packages/*/Sources Packages/*/Tests → clean.
  • Dev iPad smoke (in-app switch): tap globe → sheet appears with にほんご selected → switch to 한국어 → confirm → Home re-renders in Korean → switch back. Streak count and yokai cameos preserved.
  • Simulator smoke (narrowed age picker): fresh install → Step 2 shows exactly 3 tiles (6 / 7 / 8) with 7 pre-selected → tap 6 → onboarding completes → Home renders the JP entry-tier table.

🤖 Generated with Claude Code

youtalk and others added 5 commits April 27, 2026 06:26
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Adds a globe button between the wordmark and the spacer in HomeView's
header. Tapping it presents LanguageSwitchSheetView via .sheet(item:);
onCommit writes the new l1Identifier and saves the model context.
Promotes @Environment(\.modelContext) to unconditional (removing the
#if DEBUG guard) so the save call is available in non-debug builds.
Adds HomeViewLanguageSwitchTests smoke-testing the a11y label string.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 27, 2026 13:51
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Completes the multi‑L1 i18n stack by narrowing the onboarding age picker and adding an in‑app L1 language switcher accessible from Home.

Changes:

  • Narrow Step 2 age picker to ages 6/7/8 with default age 7, and adjust tile layout/font sizing.
  • Add an in-app language switch sheet (model + SwiftUI view) and wire it to a new Home header globe button.
  • Extend MoraStrings with four new fields and populate them across JP/KO/EN tables, with tests updated accordingly.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Packages/MoraUI/Tests/MoraUITests/LanguageSwitchSheetTests.swift Adds unit tests for the language switch sheet model callbacks and disabled/enabled confirm logic.
Packages/MoraUI/Tests/MoraUITests/LanguageAgeFlowTests.swift Pins narrowed age options and default age via tests.
Packages/MoraUI/Tests/MoraUITests/HomeViewLanguageSwitchTests.swift Adds a basic test asserting the new string field value for the Home globe button a11y label.
Packages/MoraUI/Sources/MoraUI/LanguageAge/LanguageSwitchSheet.swift Introduces the sheet model + view for switching L1 from Home.
Packages/MoraUI/Sources/MoraUI/LanguageAge/LanguagePickerView.swift Refactors onboarding Step 1 to reuse the extracted LanguagePicker component.
Packages/MoraUI/Sources/MoraUI/LanguageAge/LanguagePicker.swift Adds reusable language picker row list UI used by onboarding and the new sheet.
Packages/MoraUI/Sources/MoraUI/LanguageAge/LanguageAgeFlow.swift Adds ageOptions and defaultAge, updates state default selection.
Packages/MoraUI/Sources/MoraUI/LanguageAge/AgePickerView.swift Updates Step 2 UI to render only the narrowed age options and larger tile font.
Packages/MoraUI/Sources/MoraUI/Home/HomeView.swift Adds globe button + sheet presentation; commits updated l1Identifier to SwiftData.
Packages/MoraCore/Tests/MoraCoreTests/LocaleScriptBudgetTests.swift Extends string-field enumeration to include the four new MoraStrings fields.
Packages/MoraCore/Sources/MoraCore/MoraStrings.swift Adds four new MoraStrings fields and wires them through the initializer.
Packages/MoraCore/Sources/MoraCore/KoreanL1Profile.swift Supplies the four new in-app language-switch strings for KO.
Packages/MoraCore/Sources/MoraCore/JapaneseL1Profile.swift Supplies the four new in-app language-switch strings for JP entry/core/advanced tables.
Packages/MoraCore/Sources/MoraCore/EnglishL1Profile.swift Supplies the four new in-app language-switch strings for EN.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +116 to +120
onCommit: { newID in
profile.l1Identifier = newID
try? modelContext.save()
languageSheet = nil
},
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

onCommit updates profile.l1Identifier but swallows persistence errors via try? modelContext.save(). If save() throws, the sheet still dismisses and the in-memory change may be lost on next launch. Handle the error (e.g., do/catch with assertionFailure/logging and revert profile.l1Identifier or keep the sheet open) instead of ignoring it.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch — fixed in 2213f41. Replaced try? modelContext.save() with do/catch; on failure I revert profile.l1Identifier to its previous value, log via Logger (tech.reenable.Mora / HomeView category), and leave the sheet open so the user sees the change didn't take. Sheet still dismisses on success.

Comment on lines +31 to +42
public func simulateSelect(identifier: String) {
pickedID = identifier
}

public func simulateConfirm() {
guard !isConfirmDisabled else { return }
onCommit(pickedID)
}

public func simulateCancel() {
onCancel()
}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The methods simulateSelect / simulateConfirm / simulateCancel are part of the production LanguageSwitchSheet public API and are invoked by the SwiftUI view. “simulate*” reads like a test-only helper; consider renaming them to user actions (e.g., select(identifier:), confirm(), cancel()) and/or lowering their visibility (internal) so the public surface matches real behavior.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Agreed — fixed in 2213f41. Renamed simulateSelect/Confirm/Cancelselect(identifier:) / confirm() / cancel(). They're called from the toolbar buttons in LanguageSwitchSheetView, so the names should describe real user actions rather than test simulation. Tests updated to match.

var step: Step = .language
var selectedLanguageID: String
var selectedAge: Int? = 8 // pre-selected per spec §6.2
var selectedAge: Int? = LanguageAgeFlow.defaultAge // pre-selected per spec §6.2
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The inline comment references spec §6.2, but this change makes the default age driven by LanguageAgeFlow.defaultAge (documented as spec §7.2). Update the comment to avoid pointing future readers at the wrong spec section.

Suggested change
var selectedAge: Int? = LanguageAgeFlow.defaultAge // pre-selected per spec §6.2
var selectedAge: Int? = LanguageAgeFlow.defaultAge // pre-selected per spec §7.2

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 2213f41 — updated the comment to point at spec §7.2 (the new section that covers age narrowing in the multi-L1 i18n design).

- HomeView: replace `try? modelContext.save()` with do/catch; on failure,
  revert the in-memory `profile.l1Identifier` so it doesn't drift from
  disk, log via OSLog, and leave the sheet open so the user sees the
  tap didn't take instead of silently losing it on next launch.
- LanguageSwitchSheet: rename `simulateSelect/Confirm/Cancel` →
  `select/confirm/cancel`. They are part of the production public API
  driven by the toolbar buttons, not test-only helpers — the names
  should match real user actions.
- LanguageAgeState: fix spec reference §6.2 → §7.2 in the
  `selectedAge` initializer comment (matches the plan's PR 3 section).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@youtalk youtalk merged commit a7994de into main Apr 27, 2026
5 checks passed
@youtalk youtalk deleted the feat/mora-i18n/03-age-narrow-and-language-switch branch April 27, 2026 14:26
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