c46fc75549
Adds a single bible-voice line ("Begin where the soil is bare.") that
surfaces immediately after BeginScreen dismisses on first run and
auto-dismisses when the player makes their first plant. Closes G2
first-impression UX gap from 2026-05-09 live UAT — the post-Begin state
no longer leaves a brand-new player staring at a 4×4 grid with no
instruction.
Implementation:
- content/seasons/01-soil/ui-strings.yaml: first_run_hint key added
(recommended copy from plan; bible voice — warm, specific, contemplative)
- src/content/schemas/ui-strings.ts: UiStringsSchema extended with
first_run_hint: z.string().min(1) — MANDATORY because Zod default strip
mode silently drops unknown keys from parsed.data
- src/store/session-slice.ts: firstRunHintDismissed + dismissFirstRunHint
added (session state ONLY — NOT persisted to V1Payload, no migrations[2])
- src/ui/first-run/FirstRunHint.tsx: subscribes to tiles slice, dismisses
on first plant !== null transition; renders externalized line via
uiStrings[1]?.first_run_hint
- src/ui/first-run/{index.ts}, src/ui/index.ts: barrel + re-export wired
- src/App.tsx: <FirstRunHint /> mounted between BeginScreen and SeedPicker
Vitest: 6 new behavioral cases green (hidden when Begin still up, hidden
when dismissed, renders externalized line, reads uiStrings, auto-dismisses
on first plant, stays dismissed on subsequent tile changes). 324/324
total green; npm run ci exits 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
42 lines
1.4 KiB
TypeScript
42 lines
1.4 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
/**
|
|
* Player-visible UI strings, externalized per CLAUDE.md "Code Style":
|
|
* "Player-visible strings are externalized in /content/, never hardcoded."
|
|
*
|
|
* One file per season under /content/seasons/<slug>/ui-strings.yaml. The
|
|
* loader (src/content/loader.ts) keys them by `season` so the runtime can
|
|
* resolve `uiStrings[1].begin.title` etc.
|
|
*/
|
|
export const UiStringsSchema = z.object({
|
|
season: z.number().int().min(0).max(7),
|
|
begin: z.object({
|
|
title: z.string().min(1),
|
|
subtitle: z.string().min(1),
|
|
cta: z.string().min(1),
|
|
}),
|
|
seed_picker: z.object({
|
|
title: z.string().min(1),
|
|
cancel: z.string().min(1),
|
|
}),
|
|
post_harvest_beat: z.array(z.string().min(1)).min(1),
|
|
journal: z.object({
|
|
empty_state: z.string().min(1),
|
|
back: z.string().min(1),
|
|
}),
|
|
settings: z.object({
|
|
title: z.string().min(1),
|
|
export: z.string().min(1),
|
|
import: z.string().min(1),
|
|
restore_snapshot: z.string().min(1),
|
|
persistence_denied_toast: z.string().min(1),
|
|
}),
|
|
plants: z.record(z.string(), z.string().min(1)),
|
|
// Plan 02-06 G2 — first-run instructional hint, externalized per STRY-09.
|
|
// Required because Zod default strip mode would silently drop this key
|
|
// from parsed.data and FirstRunHint would render null in production.
|
|
first_run_hint: z.string().min(1),
|
|
});
|
|
|
|
export type UiStrings = z.infer<typeof UiStringsSchema>;
|