qwerty-cli 0.0.1-alpha.13 → 0.0.1-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +6 -2
  2. package/dist/ConfigEditor-KAL2MLQO.js +2 -0
  3. package/dist/ConfigEditor-KAL2MLQO.js.map +1 -0
  4. package/dist/DictBrowser-OF274IOX.js +2 -0
  5. package/dist/DictBrowser-OF274IOX.js.map +1 -0
  6. package/dist/{HelpScreen-P4U5O4OP.js → HelpScreen-O5RSN3C4.js} +2 -2
  7. package/dist/PracticeScreen-FOCGEFWL.js +2 -0
  8. package/dist/PracticeScreen-FOCGEFWL.js.map +1 -0
  9. package/dist/{StatsViewer-3CUMIAV4.js → StatsViewer-XOOMUPGL.js} +2 -2
  10. package/dist/{StatsViewer-3CUMIAV4.js.map → StatsViewer-XOOMUPGL.js.map} +1 -1
  11. package/dist/{WordLookup-YIU6LP4H.js → WordLookup-MWWYQBCM.js} +2 -2
  12. package/dist/chunk-4EJYJITR.js +3 -0
  13. package/dist/chunk-4EJYJITR.js.map +1 -0
  14. package/dist/{chunk-NZ2B3PEW.js → chunk-6ROGUGNX.js} +2 -2
  15. package/dist/chunk-7LTZGB7F.js +2 -0
  16. package/dist/chunk-7LTZGB7F.js.map +1 -0
  17. package/dist/chunk-BIBS2Q3E.js +3 -0
  18. package/dist/chunk-BIBS2Q3E.js.map +1 -0
  19. package/dist/{chunk-2GTGXODM.js → chunk-CQRKGMPU.js} +2 -2
  20. package/dist/{chunk-TP77EGJ2.js → chunk-DURXS5MX.js} +2 -2
  21. package/dist/chunk-E6BBQALJ.js +4 -0
  22. package/dist/chunk-E6BBQALJ.js.map +1 -0
  23. package/dist/{chunk-UPA4JFCH.js → chunk-G3DQB7FI.js} +2 -2
  24. package/dist/{chunk-MPE25TTQ.js → chunk-HJIGZU3E.js} +2 -2
  25. package/dist/{chunk-6QICLHIY.js → chunk-NA5UNUVL.js} +2 -2
  26. package/dist/{chunk-IUFBN3RD.js → chunk-QG7ZTS2G.js} +2 -2
  27. package/dist/chunk-UWTJMVJ6.js +2 -0
  28. package/dist/chunk-UWTJMVJ6.js.map +1 -0
  29. package/dist/cli.js +1 -1
  30. package/dist/{config.impl-IYJ4ZUPE.js → config.impl-3O5SL5QY.js} +2 -2
  31. package/dist/{dict.impl-Y66SRRZL.js → dict.impl-JAQ2GXCS.js} +2 -2
  32. package/dist/doctor.impl-XVZCEC2O.js +4 -0
  33. package/dist/doctor.impl-XVZCEC2O.js.map +1 -0
  34. package/dist/menu.impl-4C7HT3VD.js +2 -0
  35. package/dist/{practice.impl-NJ3ZXJGV.js → practice.impl-OLNLJLIA.js} +2 -2
  36. package/dist/{stats.impl-IXVF3Q5Y.js → stats.impl-3I7BB7X3.js} +2 -2
  37. package/dist/{word.impl-C4AYZ3NC.js → word.impl-CUBY4EZ4.js} +2 -2
  38. package/package.json +1 -1
  39. package/dist/ConfigEditor-HAP5C2Y7.js +0 -2
  40. package/dist/ConfigEditor-HAP5C2Y7.js.map +0 -1
  41. package/dist/DictBrowser-7CIISVDN.js +0 -2
  42. package/dist/DictBrowser-7CIISVDN.js.map +0 -1
  43. package/dist/PracticeScreen-KHBUK6B6.js +0 -2
  44. package/dist/PracticeScreen-KHBUK6B6.js.map +0 -1
  45. package/dist/chunk-6KRVNT2S.js +0 -4
  46. package/dist/chunk-6KRVNT2S.js.map +0 -1
  47. package/dist/chunk-ELWVQGDK.js +0 -2
  48. package/dist/chunk-ELWVQGDK.js.map +0 -1
  49. package/dist/chunk-KBRGNL2D.js +0 -3
  50. package/dist/chunk-KBRGNL2D.js.map +0 -1
  51. package/dist/chunk-R6HQWKXU.js +0 -2
  52. package/dist/chunk-R6HQWKXU.js.map +0 -1
  53. package/dist/chunk-TYZPR2XT.js +0 -3
  54. package/dist/chunk-TYZPR2XT.js.map +0 -1
  55. package/dist/doctor.impl-5UHLQ4SZ.js +0 -4
  56. package/dist/doctor.impl-5UHLQ4SZ.js.map +0 -1
  57. package/dist/menu.impl-CAUMTTSH.js +0 -2
  58. /package/dist/{HelpScreen-P4U5O4OP.js.map → HelpScreen-O5RSN3C4.js.map} +0 -0
  59. /package/dist/{WordLookup-YIU6LP4H.js.map → WordLookup-MWWYQBCM.js.map} +0 -0
  60. /package/dist/{chunk-NZ2B3PEW.js.map → chunk-6ROGUGNX.js.map} +0 -0
  61. /package/dist/{chunk-2GTGXODM.js.map → chunk-CQRKGMPU.js.map} +0 -0
  62. /package/dist/{chunk-TP77EGJ2.js.map → chunk-DURXS5MX.js.map} +0 -0
  63. /package/dist/{chunk-UPA4JFCH.js.map → chunk-G3DQB7FI.js.map} +0 -0
  64. /package/dist/{chunk-MPE25TTQ.js.map → chunk-HJIGZU3E.js.map} +0 -0
  65. /package/dist/{chunk-6QICLHIY.js.map → chunk-NA5UNUVL.js.map} +0 -0
  66. /package/dist/{chunk-IUFBN3RD.js.map → chunk-QG7ZTS2G.js.map} +0 -0
  67. /package/dist/{config.impl-IYJ4ZUPE.js.map → config.impl-3O5SL5QY.js.map} +0 -0
  68. /package/dist/{dict.impl-Y66SRRZL.js.map → dict.impl-JAQ2GXCS.js.map} +0 -0
  69. /package/dist/{menu.impl-CAUMTTSH.js.map → menu.impl-4C7HT3VD.js.map} +0 -0
  70. /package/dist/{practice.impl-NJ3ZXJGV.js.map → practice.impl-OLNLJLIA.js.map} +0 -0
  71. /package/dist/{stats.impl-IXVF3Q5Y.js.map → stats.impl-3I7BB7X3.js.map} +0 -0
  72. /package/dist/{word.impl-C4AYZ3NC.js.map → word.impl-CUBY4EZ4.js.map} +0 -0
@@ -1,2 +1,2 @@
1
- import{c as w}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as h}from"./chunk-UPA4JFCH.js";import{d as g}from"./chunk-6QICLHIY.js";import{a as u}from"./chunk-6KRVNT2S.js";import i from"chalk";import{readdir as k}from"fs/promises";async function j(){try{return(await k(u.dictsDir)).filter(s=>s.endsWith(".json")&&!s.endsWith(".meta.json")).map(s=>s.replace(/\.json$/,""))}catch{return[]}}async function N(c,s){let p=c.toLowerCase(),a=await j();if(a.length===0){console.log(i.yellow("No local dictionaries. Run `qwerty dict pull <id>` first."));return}let l=[];for(let o of a){let t=await w(o);if(t)for(let n of t){let e=n.name.toLowerCase();(s.exact?e===p:e.includes(p))&&l.push({dictId:o,word:n})}}if(l.length===0){console.log(i.yellow(`No matches for "${c}" in ${a.length} local dictionaries`));return}let d=new Map;for(let o of l){let t=d.get(o.word.name)??[];t.push(o),d.set(o.word.name,t)}let y=await h();for(let[o,t]of d){let n=t[0].word;console.log(),console.log(i.bold.white(o));let e=n.usphone?`US /${n.usphone}/`:"",m=n.ukphone?`UK /${n.ukphone}/`:"";(e||m)&&console.log(i.dim(` ${[e,m].filter(Boolean).join(" ")}`));for(let r of n.trans??[])console.log(i.cyan(` \xB7 ${r}`));let $=await Promise.all(t.map(async r=>(await g(r.dictId))?.name??r.dictId));console.log(i.dim(` in: ${$.join(", ")}`));let f=y[o];f&&console.log(i.dim(` mistakes: ${f.count} (last ${f.lastSeen.slice(0,10)})`))}console.log()}export{N as runWordLookup};
2
- //# sourceMappingURL=word.impl-C4AYZ3NC.js.map
1
+ import{c as w}from"./chunk-DURXS5MX.js";import"./chunk-7LTZGB7F.js";import{a as h}from"./chunk-G3DQB7FI.js";import{d as g}from"./chunk-NA5UNUVL.js";import{a as u}from"./chunk-E6BBQALJ.js";import i from"chalk";import{readdir as k}from"fs/promises";async function j(){try{return(await k(u.dictsDir)).filter(s=>s.endsWith(".json")&&!s.endsWith(".meta.json")).map(s=>s.replace(/\.json$/,""))}catch{return[]}}async function N(c,s){let p=c.toLowerCase(),a=await j();if(a.length===0){console.log(i.yellow("No local dictionaries. Run `qwerty dict pull <id>` first."));return}let l=[];for(let o of a){let t=await w(o);if(t)for(let n of t){let e=n.name.toLowerCase();(s.exact?e===p:e.includes(p))&&l.push({dictId:o,word:n})}}if(l.length===0){console.log(i.yellow(`No matches for "${c}" in ${a.length} local dictionaries`));return}let d=new Map;for(let o of l){let t=d.get(o.word.name)??[];t.push(o),d.set(o.word.name,t)}let y=await h();for(let[o,t]of d){let n=t[0].word;console.log(),console.log(i.bold.white(o));let e=n.usphone?`US /${n.usphone}/`:"",m=n.ukphone?`UK /${n.ukphone}/`:"";(e||m)&&console.log(i.dim(` ${[e,m].filter(Boolean).join(" ")}`));for(let r of n.trans??[])console.log(i.cyan(` \xB7 ${r}`));let $=await Promise.all(t.map(async r=>(await g(r.dictId))?.name??r.dictId));console.log(i.dim(` in: ${$.join(", ")}`));let f=y[o];f&&console.log(i.dim(` mistakes: ${f.count} (last ${f.lastSeen.slice(0,10)})`))}console.log()}export{N as runWordLookup};
2
+ //# sourceMappingURL=word.impl-CUBY4EZ4.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwerty-cli",
3
- "version": "0.0.1-alpha.13",
3
+ "version": "0.0.1-alpha.16",
4
4
  "description": "Terminal clone of qwerty-learner: typing practice for English vocabulary, with chapters, dictation, mistake book, and audio.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,2 +0,0 @@
1
- import{b as M}from"./chunk-2GTGXODM.js";import{d as F}from"./chunk-ELWVQGDK.js";import{b as N,c as v,e as P}from"./chunk-IUFBN3RD.js";import{b as R,d as $,f as a}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as S}from"react";import{Box as p,Text as d,useInput as I}from"ink";import{jsx as c,jsxs as x}from"react/jsx-runtime";var f=[{kind:"dictRef",path:"defaultDict",labelKey:"defaultDict"},{kind:"enum",path:"defaultMode",labelKey:"defaultMode",options:["order","dictation","review","random","loop"]},{kind:"enum",path:"accent",labelKey:"accent",options:["us","uk"]},{kind:"enum",path:"language",labelKey:"language",options:["auto","zh","en"]},{kind:"enum",path:"mirror",labelKey:"mirror",options:["jsdelivr","github"]},{kind:"enum",path:"stealth",labelKey:"stealth",options:["off","menu","default"]},{kind:"int",path:"chapterSize",labelKey:"chapterSize",min:1,max:200},{kind:"bool",path:"autoplayPronunciation",labelKey:"autoplayPronunciation"},{kind:"bool",path:"sounds.master",labelKey:"soundsMaster"},{kind:"bool",path:"sounds.keystroke",labelKey:"soundsKeystroke"},{kind:"bool",path:"sounds.feedback",labelKey:"soundsFeedback"},{kind:"string",path:"sounds.keySoundName",labelKey:"soundsKeySound"}];function C(s,e){return e.split(".").reduce((r,l)=>{if(r&&typeof r=="object")return r[l]},s)}function U(){let s=R(),{cfg:e,setCfg:r}=M(),l=$(),y=N(e.defaultDict),[m,K]=S(0),[h,b]=S(!1),[w,g]=S(""),[A,u]=S(null),o=f[m],T=C(e,o.path),k=async i=>{try{let t=F(e,o.path,i);await r(t),b(!1),u(null)}catch(t){u(t.message)}};I((i,t)=>{if(h&&o.kind==="string"){if(t.escape){b(!1),u(null);return}if(t.return){k(w);return}if(t.backspace||t.delete){g(n=>n.slice(0,-1));return}i&&!t.ctrl&&!t.meta&&g(n=>n+i);return}if(h&&o.kind==="int"){if(t.escape){b(!1),u(null);return}if(t.return){k(w);return}if(t.backspace||t.delete){g(n=>n.slice(0,-1));return}/^[0-9]$/.test(i)&&g(n=>n+i);return}if(t.escape){s.back();return}if(t.upArrow){K(n=>(n-1+f.length)%f.length),u(null);return}if(t.downArrow){K(n=>(n+1)%f.length),u(null);return}if(o.kind==="bool"&&(i===" "||t.return)){k(T?"false":"true");return}if(o.kind==="enum"&&(t.leftArrow||t.rightArrow)){let n=o.options.indexOf(String(T)),D=t.rightArrow?1:-1,B=o.options[(n+D+o.options.length)%o.options.length];k(B);return}if(o.kind==="dictRef"&&t.return){s.navigate({name:"dict",params:{pickerMode:"set-default"}});return}(o.kind==="string"||o.kind==="int")&&t.return&&(g(String(T??"")),b(!0),u(null))});let V=Math.max(...f.map(i=>v(l.config.fields[i.labelKey])))+4;return x(p,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(d,{bold:!0,color:a.accent,children:l.config.title}),c(p,{marginTop:1,flexDirection:"column",flexGrow:1,children:f.map((i,t)=>{let n=t===m,D=C(e,i.path),B=j(i,D,n&&h?w:null,l,i.path==="defaultDict"?y:""),E=l.config.fields[i.labelKey],z=" ".repeat(Math.max(0,V-v(E)));return x(p,{children:[c(d,{color:n?a.accent:a.muted,children:n?"\u258C ":" "}),x(d,{bold:n,color:n?a.text:a.muted,children:[E,z]}),c(d,{color:n?a.accent:a.muted,children:B})]},i.path)})}),A&&c(p,{marginTop:1,children:x(d,{color:a.error,children:["! ",A]})}),c(p,{marginTop:1,children:c(d,{color:a.muted,children:L(o,h,l)})})]})}function j(s,e,r,l,y){if(r!==null)return`${r}_`;if(s.kind==="bool")return e?`\u2713 ${l.common.on}`:`\u2717 ${l.common.off}`;if(s.kind==="dictRef")return e?P(y||String(e),24):"\u2014";if(s.kind==="enum"){if(s.path==="stealth"){let m=String(e);return`< ${l.config.enumValues.stealth[m]??String(e)} >`}return`< ${e} >`}return String(e??"")}function L(s,e,r){return e?r.config.hints.editing:s.kind==="bool"?r.config.hints.bool:s.kind==="enum"?r.config.hints.enum:s.kind==="dictRef"?r.config.hints.dictRef:r.config.hints.stringOrInt}export{U as ConfigEditor};
2
- //# sourceMappingURL=ConfigEditor-HAP5C2Y7.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ui/screens/ConfigEditor.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { setByPath, type Config } from '../../infra/config-store.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { visibleWidth } from '../../util/text.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport type { Strings } from '../../i18n/strings.js';\n\ntype FieldSpec =\n | { kind: 'enum'; path: string; labelKey: keyof Strings['config']['fields']; options: string[] }\n | { kind: 'bool'; path: string; labelKey: keyof Strings['config']['fields'] }\n | { kind: 'int'; path: string; labelKey: keyof Strings['config']['fields']; min: number; max: number }\n | { kind: 'string'; path: string; labelKey: keyof Strings['config']['fields'] }\n | { kind: 'dictRef'; path: 'defaultDict'; labelKey: keyof Strings['config']['fields'] };\n\nconst FIELDS: FieldSpec[] = [\n { kind: 'dictRef', path: 'defaultDict', labelKey: 'defaultDict' },\n { kind: 'enum', path: 'defaultMode', labelKey: 'defaultMode', options: ['order', 'dictation', 'review', 'random', 'loop'] },\n { kind: 'enum', path: 'accent', labelKey: 'accent', options: ['us', 'uk'] },\n { kind: 'enum', path: 'language', labelKey: 'language', options: ['auto', 'zh', 'en'] },\n { kind: 'enum', path: 'mirror', labelKey: 'mirror', options: ['jsdelivr', 'github'] },\n { kind: 'enum', path: 'stealth', labelKey: 'stealth', options: ['off', 'menu', 'default'] },\n { kind: 'int', path: 'chapterSize', labelKey: 'chapterSize', min: 1, max: 200 },\n { kind: 'bool', path: 'autoplayPronunciation', labelKey: 'autoplayPronunciation' },\n { kind: 'bool', path: 'sounds.master', labelKey: 'soundsMaster' },\n { kind: 'bool', path: 'sounds.keystroke', labelKey: 'soundsKeystroke' },\n { kind: 'bool', path: 'sounds.feedback', labelKey: 'soundsFeedback' },\n { kind: 'string', path: 'sounds.keySoundName', labelKey: 'soundsKeySound' },\n];\n\nfunction getByPath(cfg: Config, path: string): unknown {\n return path.split('.').reduce<unknown>((acc, k) => {\n if (acc && typeof acc === 'object') return (acc as Record<string, unknown>)[k];\n return undefined;\n }, cfg);\n}\n\nexport function ConfigEditor() {\n const nav = useNav();\n const { cfg, setCfg } = useAppState();\n const t = useStrings();\n const defaultDictName = useDictName(cfg.defaultDict);\n const [selected, setSelected] = useState(0);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const field = FIELDS[selected]!;\n const currentValue = getByPath(cfg, field.path);\n\n const commit = async (raw: string) => {\n try {\n const next = setByPath(cfg, field.path, raw);\n await setCfg(next);\n setEditing(false);\n setError(null);\n } catch (err) {\n setError((err as Error).message);\n }\n };\n\n useInput((input, key) => {\n if (editing && field.kind === 'string') {\n if (key.escape) {\n setEditing(false);\n setError(null);\n return;\n }\n if (key.return) {\n void commit(draft);\n return;\n }\n if (key.backspace || key.delete) {\n setDraft((d) => d.slice(0, -1));\n return;\n }\n if (input && !key.ctrl && !key.meta) setDraft((d) => d + input);\n return;\n }\n if (editing && field.kind === 'int') {\n if (key.escape) {\n setEditing(false);\n setError(null);\n return;\n }\n if (key.return) {\n void commit(draft);\n return;\n }\n if (key.backspace || key.delete) {\n setDraft((d) => d.slice(0, -1));\n return;\n }\n if (/^[0-9]$/.test(input)) setDraft((d) => d + input);\n return;\n }\n\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.upArrow) {\n setSelected((i) => (i - 1 + FIELDS.length) % FIELDS.length);\n setError(null);\n return;\n }\n if (key.downArrow) {\n setSelected((i) => (i + 1) % FIELDS.length);\n setError(null);\n return;\n }\n if (field.kind === 'bool' && (input === ' ' || key.return)) {\n void commit(currentValue ? 'false' : 'true');\n return;\n }\n if (field.kind === 'enum' && (key.leftArrow || key.rightArrow)) {\n const idx = field.options.indexOf(String(currentValue));\n const delta = key.rightArrow ? 1 : -1;\n const next = field.options[(idx + delta + field.options.length) % field.options.length]!;\n void commit(next);\n return;\n }\n if (field.kind === 'dictRef' && key.return) {\n nav.navigate({ name: 'dict', params: { pickerMode: 'set-default' } });\n return;\n }\n if ((field.kind === 'string' || field.kind === 'int') && key.return) {\n setDraft(String(currentValue ?? ''));\n setEditing(true);\n setError(null);\n }\n });\n\n const labelW = Math.max(...FIELDS.map((f) => visibleWidth(t.config.fields[f.labelKey]))) + 4;\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.accent}>\n {t.config.title}\n </Text>\n\n <Box marginTop={1} flexDirection=\"column\" flexGrow={1}>\n {FIELDS.map((f, i) => {\n const active = i === selected;\n const value = getByPath(cfg, f.path);\n const display = renderValue(\n f,\n value,\n active && editing ? draft : null,\n t,\n f.path === 'defaultDict' ? defaultDictName : '',\n );\n const label = t.config.fields[f.labelKey];\n const pad = ' '.repeat(Math.max(0, labelW - visibleWidth(label)));\n return (\n <Box key={f.path}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted}>\n {label}\n {pad}\n </Text>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{display}</Text>\n </Box>\n );\n })}\n </Box>\n\n {error && (\n <Box marginTop={1}>\n <Text color={PALETTE.error}>! {error}</Text>\n </Box>\n )}\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{hintFor(field, editing, t)}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction renderValue(\n field: FieldSpec,\n value: unknown,\n draft: string | null,\n t: Strings,\n dictDisplayName: string,\n): string {\n if (draft !== null) return `${draft}_`;\n if (field.kind === 'bool') return value ? `✓ ${t.common.on}` : `✗ ${t.common.off}`;\n if (field.kind === 'dictRef') {\n if (!value) return '—';\n return truncateName(dictDisplayName || String(value), 24);\n }\n if (field.kind === 'enum') {\n if (field.path === 'stealth') {\n const v = String(value) as keyof Strings['config']['enumValues']['stealth'];\n const label = t.config.enumValues.stealth[v] ?? String(value);\n return `< ${label} >`;\n }\n return `< ${value} >`;\n }\n return String(value ?? '');\n}\n\nfunction hintFor(field: FieldSpec, editing: boolean, t: Strings): string {\n if (editing) return t.config.hints.editing;\n if (field.kind === 'bool') return t.config.hints.bool;\n if (field.kind === 'enum') return t.config.hints.enum;\n if (field.kind === 'dictRef') return t.config.hints.dictRef;\n return t.config.hints.stringOrInt;\n}\n"],"mappings":"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","names":["useState","Box","Text","useInput","jsx","jsxs","FIELDS","getByPath","cfg","path","acc","k","ConfigEditor","nav","useNav","setCfg","useAppState","t","useStrings","defaultDictName","useDictName","selected","setSelected","useState","editing","setEditing","draft","setDraft","error","setError","field","currentValue","commit","raw","next","setByPath","err","useInput","input","key","d","i","idx","delta","labelW","f","visibleWidth","Box","Text","PALETTE","active","value","display","renderValue","label","pad","hintFor","dictDisplayName","truncateName","v"]}
@@ -1,2 +0,0 @@
1
- import{a as F,d as H,f as N}from"./chunk-TP77EGJ2.js";import{b as z}from"./chunk-2GTGXODM.js";import"./chunk-ELWVQGDK.js";import{b as q,d as J,f as e}from"./chunk-R6HQWKXU.js";import{b as Y,c as _}from"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useEffect as at,useMemo as dt,useState as b}from"react";import{Box as l,Text as c,useInput as st,useStdout as mt}from"ink";import{useState as lt}from"react";import{Box as k,Text as M,useInput as ct}from"ink";import{jsx as v,jsxs as K}from"react/jsx-runtime";function j({title:D,items:g,onClose:T}){let f=g.map((s,m)=>s.disabled?-1:m).filter(s=>s>=0),o=f[0]??0,[B,P]=lt(o);ct((s,m)=>{if(m.escape){T();return}if(m.upArrow){let a=f.indexOf(B),d=f[(a-1+f.length)%f.length];d!==void 0&&P(d);return}if(m.downArrow){let a=f.indexOf(B),d=f[(a+1)%f.length];d!==void 0&&P(d);return}if(m.return){let a=g[B];a&&!a.disabled&&a.run();return}for(let a=0;a<g.length;a++){let d=g[a];if(!d.disabled&&d.key&&s===d.key){d.run();return}}});let I=Math.max(...g.map(s=>s.label.length)),L=Math.max(I+8,D.length+4,24);return K(k,{flexDirection:"column",borderStyle:"round",borderColor:e.accent,paddingX:2,paddingY:1,width:L,children:[v(k,{marginBottom:1,children:v(M,{bold:!0,color:e.accent,children:D})}),g.map((s,m)=>{let a=m===B,d=s.disabled?e.muted:a?e.text:e.muted;return K(k,{children:[v(M,{color:a?e.accent:e.muted,children:a?"\u258C ":" "}),v(M,{bold:a,color:d,children:s.label})]},m)}),v(k,{marginTop:1,children:v(M,{color:e.muted,children:"\u2191/\u2193 \xB7 Enter \xB7 Esc"})})]})}import{Fragment as ut,jsx as t,jsxs as h}from"react/jsx-runtime";function Mt({params:D}){let g=q(),{cfg:T,setCfg:f}=z(),o=J(),{stdout:B}=mt(),[P,I]=b([]),[L,s]=b(!0),[m,a]=b(0),[d,G]=b(""),[x,y]=b(null),[Q,S]=b(0),[E,p]=b(null),U=async()=>{let n=await _(),i=await Promise.all(n.map(async u=>({entry:u,local:await H(u.id)})));I(i),s(!1)};at(()=>{U()},[Q]);let w=dt(()=>d?P.filter(n=>Y([n.entry],d).length>0):P,[d,P]),C=Math.max(0,Math.min(w.length-1,m)),r=w[C],V=B?.rows??24,R=Math.max(6,V-8),W=Math.floor(R/2),$=Math.max(0,Math.min(w.length-R,C-W)),Z=Math.min(w.length,$+R),O=n=>{g.replace({name:"practice",params:{dictId:n,chapterIndex:0,mode:T.defaultMode,stealth:T.stealth==="default"}})},tt=async(n,i=!0)=>{await f({...T,defaultDict:n}),p(null),i&&(D?.pickerMode==="choose-then-practice"?O(n):g.back())},et=n=>{p(null),y({kind:"removing",id:n}),(async()=>{try{await N(n),y(null),S(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},nt=n=>{p(null),y({kind:"pulling",id:n}),(async()=>{try{await F(n),y(null),S(i=>i+1)}catch(i){y({kind:"error",id:n,msg:i.message})}})()},ot=()=>{p(null),y({kind:"refreshing"}),S(n=>n+1),y(null)};if(st((n,i)=>{if(E===null){if(i.escape){g.back();return}if(i.upArrow){a(u=>Math.max(0,u-1));return}if(i.downArrow){a(u=>Math.min(w.length-1,u+1));return}if(i.ctrl&&n==="k"){p("more");return}if(i.return){r&&p("item");return}if(i.backspace||i.delete){G(u=>u.slice(0,-1)),a(0);return}n&&!i.ctrl&&!i.meta&&n.length===1&&(G(u=>u+n),a(0))}}),L)return t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(c,{color:e.muted,children:o.dict.loading})});let rt=r?[{label:o.dict.action.setDefault,run:()=>{tt(r.entry.id,D?.pickerMode!==void 0)}},{label:o.dict.action.practice,run:()=>O(r.entry.id)},{label:o.dict.action.delete,disabled:!r.local,run:()=>et(r.entry.id)},{label:o.common.cancel,run:()=>p(null)}]:[],it=[{label:o.dict.command.pull,disabled:!r,run:()=>r&&nt(r.entry.id)},{label:o.dict.command.import,disabled:!0,run:()=>{}},{label:o.dict.command.refreshList,run:()=>ot()},{label:o.common.cancel,run:()=>p(null)}];return E==="item"&&r?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(j,{title:`${o.dict.action.title} \xB7 ${r.entry.name}`,items:rt,onClose:()=>p(null)})}):E==="more"?t(l,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(j,{title:o.dict.command.title,items:it,onClose:()=>p(null)})}):h(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[h(l,{children:[t(c,{bold:!0,color:e.accent,children:o.dict.title}),t(l,{flexGrow:1}),t(c,{color:e.muted,children:d?`${o.dict.filterPlaceholder}: ${d}_`:`${o.dict.filterPlaceholder}_`}),h(c,{color:e.muted,children:[" ",o.dict.entries(w.length)]})]}),h(l,{marginTop:1,flexGrow:1,children:[t(l,{flexDirection:"column",width:"75%",paddingRight:1,children:w.slice($,Z).map((n,i)=>{let A=$+i===C,X=T.defaultDict===n.entry.id;return h(l,{children:[t(l,{width:2,children:t(c,{color:A?e.accent:e.muted,children:A?"\u258C ":" "})}),t(l,{width:2,children:t(c,{color:n.local?e.accent:e.muted,children:n.local?"\u25CF":"\u25CB"})}),t(l,{width:2,children:t(c,{color:X?e.success:e.muted,children:X?"\u2605":" "})}),t(l,{flexGrow:1,children:t(c,{bold:A,color:A?e.text:e.muted,wrap:"truncate",children:n.entry.name})}),t(l,{width:6,children:t(c,{color:e.muted,children:String(n.entry.length).padStart(5)})})]},n.entry.id)})}),t(l,{flexDirection:"column",width:"25%",paddingLeft:1,children:r&&h(ut,{children:[t(c,{bold:!0,color:e.text,wrap:"wrap",children:r.entry.name}),t(c,{color:e.muted,children:r.entry.id}),t(l,{marginTop:1,children:h(c,{color:e.muted,wrap:"wrap",children:[r.entry.language," \xB7 ",r.entry.category]})}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.wordsLabel(r.entry.length)})}),r.entry.description&&t(l,{marginTop:1,children:t(c,{color:e.primary,wrap:"wrap",children:r.entry.description})}),r.entry.tags.length>0&&t(l,{marginTop:1,children:t(c,{color:e.muted,wrap:"wrap",children:o.dict.tagsLabel(r.entry.tags.join(", "))})}),t(l,{marginTop:1,children:t(c,{color:r.local?e.accent:e.muted,children:r.local?o.dict.local:o.dict.notLocal})}),T.defaultDict===r.entry.id&&t(l,{children:t(c,{color:e.success,children:o.dict.defaultMark})})]})})]}),x&&h(l,{marginTop:1,children:[x.kind==="pulling"&&t(c,{color:e.warning,children:o.dict.pulling(x.id)}),x.kind==="removing"&&t(c,{color:e.warning,children:o.dict.removing(x.id)}),x.kind==="refreshing"&&h(c,{color:e.warning,children:[o.dict.command.refreshList,"\u2026"]}),x.kind==="error"&&t(c,{color:e.error,children:o.dict.errorOn(x.id,x.msg)})]}),t(l,{marginTop:1,children:t(c,{color:e.muted,children:o.dict.footer})})]})}export{Mt as DictBrowser};
2
- //# sourceMappingURL=DictBrowser-7CIISVDN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ui/screens/DictBrowser.tsx","../src/ui/components/ActionPanel.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Box, Text, useInput, useStdout } from 'ink';\nimport { useNav, type DictParams } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { ActionPanel, type ActionItem } from '../components/ActionPanel.js';\nimport { loadRegistry } from '../../infra/registry-store.js';\nimport {\n isLocallyAvailable,\n pullDictionary,\n removeDictionary,\n} from '../../infra/dict-downloader.js';\nimport type { DictionaryEntry } from '../../domain/dictionary.js';\nimport { filterRegistry } from '../../domain/dictionary.js';\n\ntype Row = { entry: DictionaryEntry; local: boolean };\ntype PendingAction =\n | { kind: 'pulling'; id: string }\n | { kind: 'removing'; id: string }\n | { kind: 'refreshing' }\n | { kind: 'error'; id: string; msg: string }\n | null;\n\ntype Panel = null | 'item' | 'more';\n\nexport function DictBrowser({ params }: { params?: DictParams }) {\n const nav = useNav();\n const { cfg, setCfg } = useAppState();\n const t = useStrings();\n const { stdout } = useStdout();\n const [rows, setRows] = useState<Row[]>([]);\n const [loading, setLoading] = useState(true);\n const [selected, setSelected] = useState(0);\n const [filter, setFilter] = useState('');\n const [pending, setPending] = useState<PendingAction>(null);\n const [tick, setTick] = useState(0);\n const [panel, setPanel] = useState<Panel>(null);\n\n const refresh = async () => {\n const reg = await loadRegistry();\n const flagged = await Promise.all(\n reg.map(async (e) => ({ entry: e, local: await isLocallyAvailable(e.id) })),\n );\n setRows(flagged);\n setLoading(false);\n };\n\n useEffect(() => {\n void refresh();\n }, [tick]);\n\n const filtered = useMemo(\n () => (filter ? rows.filter((r) => filterRegistry([r.entry], filter).length > 0) : rows),\n [filter, rows],\n );\n const safeSelected = Math.max(0, Math.min(filtered.length - 1, selected));\n const current = filtered[safeSelected];\n\n const rowsTotal = stdout?.rows ?? 24;\n const visibleH = Math.max(6, rowsTotal - 8);\n const half = Math.floor(visibleH / 2);\n const start = Math.max(0, Math.min(filtered.length - visibleH, safeSelected - half));\n const end = Math.min(filtered.length, start + visibleH);\n\n const goPractice = (id: string) => {\n nav.replace({\n name: 'practice',\n params: {\n dictId: id,\n chapterIndex: 0,\n mode: cfg.defaultMode,\n stealth: cfg.stealth === 'default',\n },\n });\n };\n\n const doSetDefault = async (id: string, navigate = true) => {\n await setCfg({ ...cfg, defaultDict: id });\n setPanel(null);\n if (navigate) {\n if (params?.pickerMode === 'choose-then-practice') {\n goPractice(id);\n } else {\n nav.back();\n }\n }\n };\n\n const doDelete = (id: string) => {\n setPanel(null);\n setPending({ kind: 'removing', id });\n void (async () => {\n try {\n await removeDictionary(id);\n setPending(null);\n setTick((n) => n + 1);\n } catch (err) {\n setPending({ kind: 'error', id, msg: (err as Error).message });\n }\n })();\n };\n\n const doPull = (id: string) => {\n setPanel(null);\n setPending({ kind: 'pulling', id });\n void (async () => {\n try {\n await pullDictionary(id);\n setPending(null);\n setTick((n) => n + 1);\n } catch (err) {\n setPending({ kind: 'error', id, msg: (err as Error).message });\n }\n })();\n };\n\n const doRefreshList = () => {\n setPanel(null);\n setPending({ kind: 'refreshing' });\n setTick((n) => n + 1);\n setPending(null);\n };\n\n useInput((input, key) => {\n if (panel !== null) return;\n\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.upArrow) {\n setSelected((i) => Math.max(0, i - 1));\n return;\n }\n if (key.downArrow) {\n setSelected((i) => Math.min(filtered.length - 1, i + 1));\n return;\n }\n if (key.ctrl && input === 'k') {\n setPanel('more');\n return;\n }\n if (key.return) {\n if (current) setPanel('item');\n return;\n }\n if (key.backspace || key.delete) {\n setFilter((f) => f.slice(0, -1));\n setSelected(0);\n return;\n }\n if (input && !key.ctrl && !key.meta && input.length === 1) {\n setFilter((f) => f + input);\n setSelected(0);\n }\n });\n\n if (loading) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.dict.loading}</Text>\n </Box>\n );\n }\n\n const itemPanelItems: ActionItem[] = current\n ? [\n {\n label: t.dict.action.setDefault,\n run: () => void doSetDefault(current.entry.id, params?.pickerMode !== undefined),\n },\n {\n label: t.dict.action.practice,\n run: () => goPractice(current.entry.id),\n },\n {\n label: t.dict.action.delete,\n disabled: !current.local,\n run: () => doDelete(current.entry.id),\n },\n { label: t.common.cancel, run: () => setPanel(null) },\n ]\n : [];\n\n const morePanelItems: ActionItem[] = [\n {\n label: t.dict.command.pull,\n disabled: !current,\n run: () => current && doPull(current.entry.id),\n },\n {\n label: t.dict.command.import,\n disabled: true,\n run: () => undefined,\n },\n { label: t.dict.command.refreshList, run: () => doRefreshList() },\n { label: t.common.cancel, run: () => setPanel(null) },\n ];\n\n if (panel === 'item' && current) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <ActionPanel\n title={`${t.dict.action.title} · ${current.entry.name}`}\n items={itemPanelItems}\n onClose={() => setPanel(null)}\n />\n </Box>\n );\n }\n if (panel === 'more') {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <ActionPanel\n title={t.dict.command.title}\n items={morePanelItems}\n onClose={() => setPanel(null)}\n />\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Box>\n <Text bold color={PALETTE.accent}>\n {t.dict.title}\n </Text>\n <Box flexGrow={1} />\n <Text color={PALETTE.muted}>\n {filter ? `${t.dict.filterPlaceholder}: ${filter}_` : `${t.dict.filterPlaceholder}_`}\n </Text>\n <Text color={PALETTE.muted}>\n {' '}\n {t.dict.entries(filtered.length)}\n </Text>\n </Box>\n\n <Box marginTop={1} flexGrow={1}>\n <Box flexDirection=\"column\" width=\"75%\" paddingRight={1}>\n {filtered.slice(start, end).map((row, vi) => {\n const i = start + vi;\n const active = i === safeSelected;\n const isDefault = cfg.defaultDict === row.entry.id;\n return (\n <Box key={row.entry.id}>\n <Box width={2}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n </Box>\n <Box width={2}>\n <Text color={row.local ? PALETTE.accent : PALETTE.muted}>\n {row.local ? '●' : '○'}\n </Text>\n </Box>\n <Box width={2}>\n <Text color={isDefault ? PALETTE.success : PALETTE.muted}>\n {isDefault ? '★' : ' '}\n </Text>\n </Box>\n <Box flexGrow={1}>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted} wrap=\"truncate\">\n {row.entry.name}\n </Text>\n </Box>\n <Box width={6}>\n <Text color={PALETTE.muted}>{String(row.entry.length).padStart(5)}</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n\n <Box flexDirection=\"column\" width=\"25%\" paddingLeft={1}>\n {current && (\n <>\n <Text bold color={PALETTE.text} wrap=\"wrap\">\n {current.entry.name}\n </Text>\n <Text color={PALETTE.muted}>{current.entry.id}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted} wrap=\"wrap\">\n {current.entry.language} · {current.entry.category}\n </Text>\n </Box>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.dict.wordsLabel(current.entry.length)}</Text>\n </Box>\n {current.entry.description && (\n <Box marginTop={1}>\n <Text color={PALETTE.primary} wrap=\"wrap\">{current.entry.description}</Text>\n </Box>\n )}\n {current.entry.tags.length > 0 && (\n <Box marginTop={1}>\n <Text color={PALETTE.muted} wrap=\"wrap\">{t.dict.tagsLabel(current.entry.tags.join(', '))}</Text>\n </Box>\n )}\n <Box marginTop={1}>\n <Text color={current.local ? PALETTE.accent : PALETTE.muted}>\n {current.local ? t.dict.local : t.dict.notLocal}\n </Text>\n </Box>\n {cfg.defaultDict === current.entry.id && (\n <Box>\n <Text color={PALETTE.success}>{t.dict.defaultMark}</Text>\n </Box>\n )}\n </>\n )}\n </Box>\n </Box>\n\n {pending && (\n <Box marginTop={1}>\n {pending.kind === 'pulling' && (\n <Text color={PALETTE.warning}>{t.dict.pulling(pending.id)}</Text>\n )}\n {pending.kind === 'removing' && (\n <Text color={PALETTE.warning}>{t.dict.removing(pending.id)}</Text>\n )}\n {pending.kind === 'refreshing' && (\n <Text color={PALETTE.warning}>{t.dict.command.refreshList}…</Text>\n )}\n {pending.kind === 'error' && (\n <Text color={PALETTE.error}>{t.dict.errorOn(pending.id, pending.msg)}</Text>\n )}\n </Box>\n )}\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.dict.footer}</Text>\n </Box>\n </Box>\n );\n}\n","import { useState } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport { PALETTE } from './BigWord.js';\n\nexport type ActionItem = {\n key?: string;\n label: string;\n disabled?: boolean;\n run: () => void | Promise<void>;\n};\n\ntype Props = {\n title: string;\n items: ActionItem[];\n onClose: () => void;\n};\n\nexport function ActionPanel({ title, items, onClose }: Props) {\n const enabledIndices = items\n .map((it, i) => (it.disabled ? -1 : i))\n .filter((i) => i >= 0);\n const initial = enabledIndices[0] ?? 0;\n const [selected, setSelected] = useState(initial);\n\n useInput((input, key) => {\n if (key.escape) {\n onClose();\n return;\n }\n if (key.upArrow) {\n const cur = enabledIndices.indexOf(selected);\n const next = enabledIndices[(cur - 1 + enabledIndices.length) % enabledIndices.length];\n if (next !== undefined) setSelected(next);\n return;\n }\n if (key.downArrow) {\n const cur = enabledIndices.indexOf(selected);\n const next = enabledIndices[(cur + 1) % enabledIndices.length];\n if (next !== undefined) setSelected(next);\n return;\n }\n if (key.return) {\n const item = items[selected];\n if (item && !item.disabled) {\n void item.run();\n }\n return;\n }\n for (let i = 0; i < items.length; i++) {\n const it = items[i]!;\n if (it.disabled) continue;\n if (it.key && input === it.key) {\n void it.run();\n return;\n }\n }\n });\n\n const maxLabel = Math.max(...items.map((it) => it.label.length));\n const width = Math.max(maxLabel + 8, title.length + 4, 24);\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor={PALETTE.accent}\n paddingX={2}\n paddingY={1}\n width={width}\n >\n <Box marginBottom={1}>\n <Text bold color={PALETTE.accent}>\n {title}\n </Text>\n </Box>\n {items.map((it, i) => {\n const active = i === selected;\n const color = it.disabled\n ? PALETTE.muted\n : active\n ? PALETTE.text\n : PALETTE.muted;\n return (\n <Box key={i}>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>\n {active ? '▌ ' : ' '}\n </Text>\n <Text bold={active} color={color}>\n {it.label}\n </Text>\n </Box>\n );\n })}\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>↑/↓ · Enter · Esc</Text>\n </Box>\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useMemo","useState","Box","Text","useInput","useStdout","useState","Box","Text","useInput","jsx","jsxs","ActionPanel","title","items","onClose","enabledIndices","it","i","initial","selected","setSelected","useState","useInput","input","key","cur","next","item","maxLabel","width","Box","PALETTE","Text","active","color","Fragment","jsx","jsxs","DictBrowser","params","nav","useNav","cfg","setCfg","useAppState","t","useStrings","stdout","useStdout","rows","setRows","useState","loading","setLoading","selected","setSelected","filter","setFilter","pending","setPending","tick","setTick","panel","setPanel","refresh","reg","loadRegistry","flagged","e","isLocallyAvailable","useEffect","filtered","useMemo","r","filterRegistry","safeSelected","current","rowsTotal","visibleH","half","start","end","goPractice","id","doSetDefault","navigate","doDelete","removeDictionary","n","err","doPull","pullDictionary","doRefreshList","useInput","input","key","i","f","Box","Text","PALETTE","itemPanelItems","morePanelItems","ActionPanel","row","vi","active","isDefault"]}
@@ -1,2 +0,0 @@
1
- import{b as pt,e as xt,h as Tt}from"./chunk-NZ2B3PEW.js";import{a as st,b as ut,c as lt,d as dt,e as mt,f as ft}from"./chunk-KBRGNL2D.js";import{e as at}from"./chunk-TP77EGJ2.js";import{b as Y}from"./chunk-2GTGXODM.js";import"./chunk-ELWVQGDK.js";import{a as wt}from"./chunk-MPE25TTQ.js";import{a as _,b as bt,c as yt}from"./chunk-UPA4JFCH.js";import{b as ht,e as L}from"./chunk-IUFBN3RD.js";import{b as X,d as W,f as i,g as gt}from"./chunk-R6HQWKXU.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as j,useEffect as K,useRef as Z}from"react";import{Box as f,Text as g,useApp as le,useInput as F}from"ink";function It(t,e=Math.random){let r=[...t];for(let n=r.length-1;n>0;n--){let c=Math.floor(e()*(n+1)),l=r[n];r[n]=r[c],r[c]=l}return r}function St(t){let e=t>>>0;return()=>{e=e+1831565813>>>0;let r=Math.imul(e^e>>>15,1|e);return r=r+Math.imul(r^r>>>7,61|r)^r,((r^r>>>14)>>>0)/4294967296}}function kt(t,e){if(e<=0)throw new Error("chapterSize must be positive");let r=[];for(let n=0;n<t.length;n+=e)r.push(t.slice(n,n+e));return r}function Ct(t,e,r){if(e==="random"){let n=r===void 0?Math.random:St(r);return It(t,n)}return t}function G(t){return{target:t,typed:"",errorsThisWord:0}}function Mt(t,e){switch(e.type){case"reset":return{state:{...t,typed:""},effect:"none"};case"backspace":return t.typed.length===0?{state:t,effect:"none"}:{state:{...t,typed:t.typed.slice(0,-1)},effect:"none"};case"char":{let r=t.typed+e.ch,n=[...t.target].slice(0,[...r].length).join("");return r===n?r.length===t.target.length?{state:{...t,typed:r},effect:"correct"}:{state:{...t,typed:r},effect:"progress"}:{state:{...t,typed:"",errorsThisWord:t.errorsThisWord+1},effect:"wrong"}}}}function q(t,e=Date.now()){return t.length===0?{startedAt:e,results:[],current:null,finishedAt:e,playlist:t}:{startedAt:e,results:[],current:{wordIndex:0,wordStartedAt:e,input:G(t[0].name)},finishedAt:null,playlist:t}}function J(t,e,r=Date.now()){if(!t.current)return{session:t,effect:"none"};let{state:n,effect:c}=Mt(t.current.input,e);if(c==="correct"){let l={word:n.target,errors:n.errorsThisWord,durationMs:r-t.current.wordStartedAt},a=t.current.wordIndex+1,s=[...t.results,l];return a>=t.playlist.length?{session:{...t,results:s,current:null,finishedAt:r},effect:c}:{session:{...t,results:s,current:{wordIndex:a,wordStartedAt:r,input:G(t.playlist[a].name)}},effect:c}}return{session:{...t,current:{...t.current,input:n}},effect:c}}function Wt(t,e=Date.now()){if(!t.current)return{session:t,effect:"none"};let r={word:t.current.input.target,errors:0,durationMs:e-t.current.wordStartedAt,skipped:!0},n=t.current.wordIndex+1,c=[...t.results,r];return n>=t.playlist.length?{session:{...t,results:c,current:null,finishedAt:e},effect:"skipped"}:{session:{...t,results:c,current:{wordIndex:n,wordStartedAt:e,input:G(t.playlist[n].name)}},effect:"skipped"}}function Q(t){let e=t.results.reduce((c,l)=>c+l.errors,0),r=(t.finishedAt??Date.now())-t.startedAt,n={};for(let c of t.results)c.errors>0&&(n[c.word]=(n[c.word]??0)+c.errors);return{wordCount:t.results.length,errors:e,durationMs:r,perWordErrors:n}}import{useEffect as vt,useReducer as Jt,useRef as Qt,useState as Zt}from"react";import{useInput as te,useApp as ee}from"ink";function re(t,e){if(e.type==="start")return{session:q(e.playlist,e.now),lastEffect:null};if(e.type==="skip"){let r=Wt(t.session,e.now);return{session:r.session,lastEffect:r.effect}}if(e.type==="event"){if(e.key.backspace||e.key.delete){let c=J(t.session,{type:"backspace"},e.now);return{session:c.session,lastEffect:c.effect}}if(e.input.length===0)return t;let r=t.session,n=t.lastEffect;for(let c of e.input){let l=J(r,{type:"char",ch:c},e.now);if(r=l.session,n=l.effect,r.finishedAt!==null)break}return{session:r,lastEffect:n}}return t}function ne(t){let e=[...t];if(e.some(n=>n.codePointAt(0)>=128))return{kind:"ime",cleaned:""};let r=e.filter(n=>{let c=n.codePointAt(0);return c>=32&&c<=126}).join("");return{kind:r.length>0?"valid":"noise",cleaned:r}}function Et({playlist:t,onComplete:e,onTab:r,onEscape:n,onSkip:c,onImeBlock:l,onValidInput:a,enabled:s=!0}){let[d,y]=Jt(re,void 0,()=>({session:q(t,Date.now()),lastEffect:null})),M=Qt(!1),[I,S]=Zt(0),{exit:w}=ee();return te((p,x)=>{if(x.ctrl&&p==="c"){w();return}if(x.ctrl&&p==="n"){c?.(),y({type:"skip",now:Date.now()});return}if(x.escape){n?.();return}if(x.tab){r?.();return}if(x.upArrow||x.downArrow||x.leftArrow||x.rightArrow||x.return||x.ctrl||x.meta)return;let{kind:v,cleaned:R}=ne(p);if(v==="ime"){l?.();return}v!=="noise"&&(a?.(),y({type:"event",input:R,key:x,now:Date.now()}))},{isActive:s}),vt(()=>{d.session.finishedAt!==null&&!M.current&&(M.current=!0,e(d.session))},[d.session,e]),vt(()=>{if(d.session.finishedAt!==null)return;let p=setInterval(()=>S(x=>x+1),1e3);return()=>clearInterval(p)},[d.session.finishedAt]),{session:d.session,lastEffect:d.lastEffect,tick:I}}import{useEffect as oe,useRef as ce}from"react";function Bt(t){let e=ce(!1);return oe(()=>{e.current||(e.current=!0,st(!t.enabled).catch(()=>{}))},[t.enabled]),{keystroke:()=>t.enabled&&ut(),correct:()=>t.enabled&&lt(),wrong:()=>t.enabled&&dt(),pronounce:r=>{t.enabled&&t.autoplayPronunciation&&mt(r,t.accent)},prefetch:r=>{t.enabled&&ft(r,t.accent)}}}import{useCallback as ie}from"react";function At(t){return ie(async e=>{let r={ts:new Date().toISOString(),dictId:t.dictId,chapter:t.chapterIndex,mode:t.mode,wordCount:e.wordCount,errors:e.errors,durationMs:e.durationMs,perWordErrors:e.perWordErrors};await wt(r),Tt({dictId:t.dictId,chapterIndex:t.chapterIndex,mode:t.mode,wordCount:e.wordCount,errors:e.errors,durationMs:e.durationMs,perWordErrors:e.perWordErrors});let n=Object.entries(e.perWordErrors).filter(([,l])=>l>0);if(n.length===0)return;let c=await _();for(let[l,a]of n)c=yt(c,l,t.dictId,a);await bt(c)},[t.dictId,t.chapterIndex,t.mode])}import{Box as N,Text as m,useStdout as ae}from"ink";import{Fragment as ue,jsx as u,jsxs as A}from"react/jsx-runtime";var Pt=28;function $t(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function se(){let{stdout:t}=ae(),e=t?.columns??80;return Math.max(20,e-Pt)}function B({left:t,right:e}){let r=se();return A(N,{children:[u(N,{width:r,children:t}),u(N,{width:Pt,justifyContent:"flex-end",children:e})]})}function Nt(t){let e=W(),r=[...t.target],n=[...t.typed],c=A(N,{children:[r.map((S,w)=>{let p=w<n.length,x=t.hideTarget&&!p?"_":p?n[w]:S,v=t.error?i.error:p?i.accent:i.muted;return u(m,{bold:!0,color:v,children:x},w)}),t.phonetic&&A(ue,{children:[u(m,{children:" "}),u(m,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})]})]}),l=t.translation.length>0?u(m,{color:i.primary,children:t.translation[0]}):u(m,{children:" "}),a=t.info,s=Number.isInteger(a.accPct)?`${a.accPct}`:a.accPct.toFixed(1),d=!t.imeBlocked&&t.audioWarning!==null,y=t.imeBlocked?u(m,{color:i.warning,children:e.practice.imeWarningShort}):d?u(m,{color:i.warning,children:e.practice.audioWarningShort}):a.visible?u(m,{color:i.muted,children:`${a.dictName} \xB7 ${a.chapterLabel}`}):u(m,{children:" "}),M=t.imeBlocked||d?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:`${a.completed}/${a.total} \xB7 ${a.wpm}wpm \xB7 ${s}%`}):u(m,{children:" "}),I=t.imeBlocked||d?u(m,{children:" "}):a.visible?u(m,{color:i.muted,children:$t(a.elapsedMs)}):u(m,{children:" "});return A(N,{flexDirection:"column",children:[u(B,{left:c,right:y}),u(B,{left:u(m,{children:" "}),right:M}),u(B,{left:l,right:I})]})}function Rt(){let t=W();return A(N,{flexDirection:"column",children:[u(B,{left:u(m,{color:i.warning,children:t.stealth.paused}),right:u(m,{color:i.muted,children:t.stealth.pausedHintRight})}),u(B,{left:u(m,{children:" "}),right:A(m,{color:i.muted,children:["Esc ",t.common.back]})}),u(B,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}function Dt(t){let e=W(),r=Number.isInteger(t.accPct)?`${t.accPct}`:t.accPct.toFixed(1),n=`${e.stealth.chapterDone} \xB7 ${t.wordCount}w \xB7 ${t.wpm}wpm \xB7 ${r}% \xB7 ${$t(t.durationMs)}`;return A(N,{flexDirection:"column",children:[u(B,{left:u(m,{color:i.success,children:n}),right:u(m,{color:i.muted,children:e.stealth.nextHintRight})}),u(B,{left:u(m,{children:" "}),right:A(m,{color:i.muted,children:["Esc ",e.common.back]})}),u(B,{left:u(m,{children:" "}),right:u(m,{children:" "})})]})}import{jsx as o,jsxs as C}from"react/jsx-runtime";function ir({params:t}){let{dictId:e,chapterIndex:r,mode:n}=t,{cfg:c}=Y(),l=W(),[a,s]=j("loading"),[d,y]=j(null),[M,I]=j(null);return K(()=>{let S=!1;return s("loading"),y(null),I(null),(async()=>{try{let w=await at(e);if(S)return;if(n==="review"){let R=await _();if(S)return;let O=w.filter(V=>R[V.name]?.count).slice(0,c.chapterSize);if(O.length===0){I(l.practice.errors.noMistakes),s("error");return}y({playlist:O,totalChapters:1}),s("typing");return}let p=kt(w,c.chapterSize);if(p.length===0){I(l.practice.errors.dictEmpty(e)),s("error");return}let x=Math.max(0,Math.min(p.length-1,r)),v=Ct(p[x],n);y({playlist:v,totalChapters:p.length}),s("typing")}catch(w){if(S)return;I(w.message),s("error")}})(),()=>{S=!0}},[e,r,n,c.chapterSize,l]),a==="loading"?o(xe,{text:l.practice.loading,color:i.muted}):a==="error"?o(he,{msg:M??l.practice.errors.unknown}):d?o(de,{params:t,loaded:d,phase:a,setPhase:s},`${e}-${r}-${n}-${t.stealth?"s":"n"}`):null}function de({params:t,loaded:e,phase:r,setPhase:n}){let{dictId:c,chapterIndex:l,mode:a}=t,s=t.stealth===!0,{cfg:d}=Y(),y=X(),{exit:M}=le(),I=()=>y.stack.length>1?y.back():M(),S=At({dictId:c,chapterIndex:l,mode:a}),w=ht(c),p=Bt({enabled:!s&&d.sounds.master,accent:d.accent,autoplayPronunciation:!s&&d.autoplayPronunciation}),x=pt(),v=d.sounds.master?x.warning:null,R=Z(!1),O=Z(null),V=Z(-1),[Ot,et]=j(!1),[rt,Ft]=j(null),[nt,ot]=j(!1);K(()=>{if(rt===null)return;let h=setTimeout(()=>et(!1),2e3);return()=>clearTimeout(h)},[rt]);let{session:T,lastEffect:E,tick:Vt}=Et({playlist:e.playlist,enabled:r==="typing",onComplete:h=>{R.current||(R.current=!0,n("summary"),Promise.resolve(S(Q(h))).catch(b=>{console.error("Failed to persist session:",b)}))},onEscape:()=>n(r==="paused"?"typing":"paused"),onTab:s?void 0:()=>{let h=T.current?e.playlist[T.current.wordIndex]:void 0;h&&p.pronounce(h.name)},onImeBlock:()=>ot(!0),onValidInput:()=>ot(!1)});K(()=>{s||E!==null&&E!==O.current&&(O.current=E,E==="wrong"&&d.sounds.feedback&&p.wrong(),E==="progress"&&d.sounds.keystroke&&p.keystroke(),E==="correct"&&(d.sounds.feedback&&p.correct(),d.sounds.keystroke&&p.keystroke()))},[s,E,p,d.sounds.feedback,d.sounds.keystroke]),K(()=>{if(s)return;let h=T.current?.wordIndex??-1;if(h===-1||h===V.current)return;V.current=h;let b=e.playlist[h],$=e.playlist[h+1];b&&d.autoplayPronunciation&&p.pronounce(b.name),$&&p.prefetch($.name)},[s,T.current?.wordIndex,p,d.autoplayPronunciation,e.playlist]),F((h,b)=>{if(b.tab){et(!0),Ft(Date.now());return}},{isActive:s&&r==="typing"}),F((h,b)=>{if(b.return){n("typing");return}if(b.escape){I();return}},{isActive:r==="paused"}),F((h,b)=>{b.ctrl&&h==="c"&&(xt(!0),M())},{isActive:s&&r==="paused"}),F((h,b)=>{if(b.escape){I();return}if(b.return){let $=l+1;a==="loop"?y.replace({name:"practice",params:{dictId:c,chapterIndex:l,mode:a,stealth:t.stealth}}):a==="review"||$>=e.totalChapters?I():y.replace({name:"practice",params:{dictId:c,chapterIndex:$,mode:a,stealth:t.stealth}});return}if(h==="m"){y.replace({name:"practice",params:{dictId:c,chapterIndex:0,mode:"review",stealth:t.stealth}});return}},{isActive:r==="summary"});let P=T.results.length,_t=T.results.reduce((h,b)=>h+b.errors,0),z=Date.now()-T.startedAt,ct=z/6e4,it=ct>0?Math.round(P/ct*10)/10:0,k=r==="summary"?Q(T):null;if(s){if(r==="paused")return o(Rt,{});if(r==="summary"&&k){let D=k.durationMs/6e4,Ut=D>0?Math.round(k.wordCount/D*10)/10:0,Xt=Object.keys(k.perWordErrors).length,Yt=k.wordCount===0?1:Math.max(0,(k.wordCount-Xt)/k.wordCount),qt=Math.round(Yt*1e3)/10;return o(Dt,{wordCount:k.wordCount,errors:k.errors,durationMs:k.durationMs,wpm:Ut,accPct:qt})}let h=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],b=T.current?.input??{target:"",typed:"",errorsThisWord:0},$=new Set(T.results.filter(D=>D.errors>0).map(D=>D.word)).size,Ht=P===0?1:Math.max(0,(P-$)/P),Kt=Math.round(Ht*1e3)/10,zt=a==="review"?"review":`ch ${l+1}/${e.totalChapters}`;return o(Nt,{target:h?.name??"",typed:b.typed,hideTarget:a==="dictation",phonetic:Lt(h,d.accent),translation:h?.trans??[],error:E==="wrong",imeBlocked:nt,audioWarning:v,info:{visible:Ot,dictName:L(w,24),chapterLabel:zt,completed:P,total:e.playlist.length,wpm:it,accPct:Kt,elapsedMs:z}})}if(r==="paused")return o(pe,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:a,completed:P,total:e.playlist.length});if(r==="summary"&&k)return o(be,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:a,summary:k});let U=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],Gt=T.current?.input??{target:"",typed:"",errorsThisWord:0};return o(me,{dictName:w,chapterIndex:l,totalChapters:e.totalChapters,mode:a,accent:d.accent,completed:P,total:e.playlist.length,errors:_t,wpm:it,elapsedMs:z,target:U?.name??"",typed:Gt.typed,flashError:E==="wrong",hideTarget:a==="dictation",phonetic:Lt(U,d.accent),translation:U?.trans??[],imeBlocked:nt,audioWarning:v})}function Lt(t,e){return t?(e==="us"?t.usphone:t.ukphone)??null:null}function tt(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function me(t){let e=W(),r=t.total===0?0:t.completed/t.total;return C(f,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[o(fe,{dictName:t.dictName,chapterIndex:t.chapterIndex,totalChapters:t.totalChapters,mode:t.mode,accent:t.accent,completed:t.completed,total:t.total,elapsedMs:t.elapsedMs}),C(f,{flexGrow:1,flexDirection:"column",alignItems:"center",justifyContent:"center",children:[o(gt,{target:t.target,typed:t.typed,error:t.flashError,hideTarget:t.hideTarget}),t.phonetic&&o(f,{marginTop:1,children:o(g,{italic:!0,dimColor:!0,color:i.muted,children:t.phonetic})}),t.translation.length>0&&o(f,{marginTop:1,flexDirection:"column",alignItems:"center",children:t.translation.slice(0,2).map((n,c)=>o(g,{color:i.primary,children:n},c))}),t.imeBlocked&&o(f,{marginTop:1,children:o(g,{color:i.warning,children:e.practice.imeWarning})}),!t.imeBlocked&&t.audioWarning&&o(f,{marginTop:1,children:o(g,{color:i.warning,children:t.audioWarning})})]}),C(f,{flexDirection:"column",children:[o(jt,{frac:r}),o(f,{justifyContent:"center",marginTop:1,children:C(g,{color:i.muted,children:[t.completed,"/",t.total," \xB7 ",tt(t.elapsedMs)," \xB7 ",t.wpm," ",e.practice.statCards.wpm," \xB7 ",t.errors," ",e.practice.statCards.errors]})}),o(f,{justifyContent:"center",marginTop:1,children:o(g,{color:i.muted,children:e.practice.footers.typing})})]})]})}function fe(t){let e=W(),r=e.practice.modes[t.mode],n=e.practice.accents[t.accent],c=L(t.dictName,20),l=t.mode==="review"?`${c} \xB7 ${e.practice.reviewLabel} \xB7 ${n}`:`${c} \xB7 ${e.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${r} \xB7 ${n}`,a=`${t.completed}/${t.total} \xB7 ${tt(t.elapsedMs)}`;return C(f,{children:[o(g,{color:i.muted,children:l}),o(f,{flexGrow:1}),o(g,{color:i.muted,children:a})]})}function jt({frac:t}){let e=process.stdout.columns??80,r=Math.max(20,Math.min(72,e-16)),n=Math.round(r*Math.max(0,Math.min(1,t))),c=r-n;return C(f,{justifyContent:"center",children:[o(g,{color:i.accent,children:"\u2501".repeat(n)}),o(g,{color:i.muted,children:"\u2500".repeat(c)})]})}function pe(t){let e=W(),r=t.total===0?0:t.completed/t.total,n=t.mode==="review"?`${L(t.dictName,20)} \xB7 ${e.practice.reviewLabel}`:`${L(t.dictName,20)} \xB7 ${e.practice.pause.chapter(t.chapterIndex+1,t.totalChapters)}`;return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(g,{bold:!0,color:i.warning,children:e.practice.pause.title}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:n})}),o(f,{marginTop:2,children:o(jt,{frac:r})}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:e.practice.pause.progress(t.completed,t.total)})}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:e.practice.pause.hint})})]})}function he({msg:t}){let e=W();return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(g,{color:i.error,children:t}),o(f,{marginTop:2,children:C(g,{color:i.muted,children:["Esc ",e.common.back]})}),o(ge,{})]})}function ge(){let t=X();return F((e,r)=>{r.escape&&t.back()}),null}function xe({text:t,color:e}){return o(f,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(g,{color:e,children:t})})}function be(t){let{summary:e}=t,r=e.durationMs/6e4,n=r>0?Math.round(e.wordCount/r*10)/10:0,c=Object.keys(e.perWordErrors).length,l=e.wordCount===0?1:Math.max(0,(e.wordCount-c)/e.wordCount),a=Math.round(l*1e3)/10,s=W(),d=s.practice.modes[t.mode],y=L(t.dictName,20),M=t.mode==="review"?`${y} \xB7 ${s.practice.reviewLabel}`:`${y} \xB7 ${s.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${d}`,S=`Enter ${t.mode==="loop"?s.practice.summary.loopAgain:t.mode==="review"||t.chapterIndex+1>=t.totalChapters?s.practice.summary.backMenu:s.practice.summary.nextChapter} \xB7 m ${s.practice.summary.reviewMistakes} \xB7 Esc ${s.practice.summary.backMenu}`;return C(f,{flexDirection:"column",alignItems:"center",justifyContent:"center",paddingY:1,width:"100%",height:"100%",children:[o(g,{bold:!0,color:i.success,children:s.practice.chapterComplete}),o(f,{marginTop:1,children:o(g,{color:i.muted,children:M})}),C(f,{marginTop:3,flexDirection:"row",justifyContent:"center",children:[o(H,{label:s.practice.statCards.words,value:String(e.wordCount),color:i.text}),o(H,{label:s.practice.statCards.errors,value:String(e.errors),color:e.errors>0?i.error:i.muted}),o(H,{label:s.practice.statCards.wpm,value:String(n),color:i.accent}),o(H,{label:s.practice.statCards.accuracy,value:`${a}%`,color:i.accent})]}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:s.practice.statCards.elapsed(tt(e.durationMs))})}),o(f,{flexGrow:1}),o(f,{marginTop:2,children:o(g,{color:i.muted,children:S})})]})}function H({label:t,value:e,color:r}){return C(f,{flexDirection:"column",alignItems:"center",marginX:3,children:[o(g,{bold:!0,color:r,children:e}),o(g,{color:i.muted,children:t})]})}export{ir as PracticeScreen};
2
- //# sourceMappingURL=PracticeScreen-KHBUK6B6.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ui/screens/PracticeScreen.tsx","../src/util/shuffle.ts","../src/domain/chapters.ts","../src/domain/input-buffer.ts","../src/domain/session.ts","../src/ui/hooks/useWordLoop.ts","../src/ui/hooks/useAudio.ts","../src/ui/hooks/useSessionPersistence.ts","../src/ui/screens/StealthPracticeLayout.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { Word } from '../../domain/dictionary.js';\nimport type { Mode } from '../../domain/chapters.js';\nimport { chunkChapters, buildPlaylist } from '../../domain/chapters.js';\nimport { sessionSummary } from '../../domain/session.js';\nimport { loadMistakes } from '../../domain/mistakes.js';\nimport { ensureDictionary } from '../../infra/dict-downloader.js';\nimport { useWordLoop } from '../hooks/useWordLoop.js';\nimport { useAudio } from '../hooks/useAudio.js';\nimport { useSessionPersistence } from '../hooks/useSessionPersistence.js';\nimport { useNav, type PracticeParams } from '../nav.js';\nimport { useAppState } from '../app-state.js';\nimport { useAudioStatus } from '../audio-context.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { BigWord, PALETTE } from '../components/BigWord.js';\nimport { truncateName } from '../../util/dict-name.js';\nimport { StealthTyping, StealthPaused, StealthSummary } from './StealthPracticeLayout.js';\nimport { setSilentExit } from '../../util/post-exit-action.js';\n\ntype Phase = 'loading' | 'typing' | 'paused' | 'summary' | 'error';\n\ntype Loaded = {\n playlist: Word[];\n totalChapters: number;\n};\n\nexport function PracticeScreen({ params }: { params: PracticeParams }) {\n const { dictId, chapterIndex, mode } = params;\n const { cfg } = useAppState();\n const t = useStrings();\n\n const [phase, setPhase] = useState<Phase>('loading');\n const [loaded, setLoaded] = useState<Loaded | null>(null);\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n setPhase('loading');\n setLoaded(null);\n setErrorMsg(null);\n (async () => {\n try {\n const words = await ensureDictionary(dictId);\n if (cancelled) return;\n if (mode === 'review') {\n const book = await loadMistakes();\n if (cancelled) return;\n const reviewWords = words.filter((w) => book[w.name]?.count).slice(0, cfg.chapterSize);\n if (reviewWords.length === 0) {\n setErrorMsg(t.practice.errors.noMistakes);\n setPhase('error');\n return;\n }\n setLoaded({ playlist: reviewWords, totalChapters: 1 });\n setPhase('typing');\n return;\n }\n const chapters = chunkChapters(words, cfg.chapterSize);\n if (chapters.length === 0) {\n setErrorMsg(t.practice.errors.dictEmpty(dictId));\n setPhase('error');\n return;\n }\n const idx = Math.max(0, Math.min(chapters.length - 1, chapterIndex));\n const playlist = buildPlaylist(chapters[idx]!, mode);\n setLoaded({ playlist, totalChapters: chapters.length });\n setPhase('typing');\n } catch (err) {\n if (cancelled) return;\n setErrorMsg((err as Error).message);\n setPhase('error');\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [dictId, chapterIndex, mode, cfg.chapterSize, t]);\n\n if (phase === 'loading') {\n return <Centered text={t.practice.loading} color={PALETTE.muted} />;\n }\n if (phase === 'error') {\n return <ErrorView msg={errorMsg ?? t.practice.errors.unknown} />;\n }\n if (!loaded) return null;\n\n return (\n <PracticeRunner\n key={`${dictId}-${chapterIndex}-${mode}-${params.stealth ? 's' : 'n'}`}\n params={params}\n loaded={loaded}\n phase={phase}\n setPhase={setPhase}\n />\n );\n}\n\nfunction PracticeRunner({\n params,\n loaded,\n phase,\n setPhase,\n}: {\n params: PracticeParams;\n loaded: Loaded;\n phase: Phase;\n setPhase: (p: Phase) => void;\n}) {\n const { dictId, chapterIndex, mode } = params;\n const stealth = params.stealth === true;\n const { cfg } = useAppState();\n const nav = useNav();\n const { exit } = useApp();\n const goBack = () => (nav.stack.length > 1 ? nav.back() : exit());\n const persist = useSessionPersistence({ dictId, chapterIndex, mode });\n const dictName = useDictName(dictId);\n\n const audio = useAudio({\n enabled: !stealth && cfg.sounds.master,\n accent: cfg.accent,\n autoplayPronunciation: !stealth && cfg.autoplayPronunciation,\n });\n const audioStatus = useAudioStatus();\n // Only surface the warning when the user opted in to sounds — if they\n // disabled sounds via config, \"no player found\" isn't a problem to flag.\n const audioWarn = cfg.sounds.master ? audioStatus.warning : null;\n\n const finishedRef = useRef(false);\n const lastEffectRef = useRef<string | null>(null);\n const lastIndexRef = useRef<number>(-1);\n const [infoVisible, setInfoVisible] = useState(false);\n const [infoShownAt, setInfoShownAt] = useState<number | null>(null);\n const [imeBlocked, setImeBlocked] = useState(false);\n\n useEffect(() => {\n if (infoShownAt === null) return;\n const id = setTimeout(() => setInfoVisible(false), 2000);\n return () => clearTimeout(id);\n }, [infoShownAt]);\n\n const { session, lastEffect, tick } = useWordLoop({\n playlist: loaded.playlist,\n enabled: phase === 'typing',\n onComplete: (s) => {\n if (finishedRef.current) return;\n finishedRef.current = true;\n setPhase('summary');\n Promise.resolve(persist(sessionSummary(s))).catch((err) => {\n console.error('Failed to persist session:', err);\n });\n },\n onEscape: () => setPhase(phase === 'paused' ? 'typing' : 'paused'),\n onTab: stealth\n ? undefined\n : () => {\n const cur = session.current ? loaded.playlist[session.current.wordIndex] : undefined;\n if (cur) void audio.pronounce(cur.name);\n },\n onImeBlock: () => setImeBlocked(true),\n onValidInput: () => setImeBlocked(false),\n });\n\n useEffect(() => {\n if (stealth) return;\n if (lastEffect === null) return;\n if (lastEffect === lastEffectRef.current) return;\n lastEffectRef.current = lastEffect;\n if (lastEffect === 'wrong' && cfg.sounds.feedback) audio.wrong();\n if (lastEffect === 'progress' && cfg.sounds.keystroke) audio.keystroke();\n if (lastEffect === 'correct') {\n if (cfg.sounds.feedback) audio.correct();\n if (cfg.sounds.keystroke) audio.keystroke();\n }\n }, [stealth, lastEffect, audio, cfg.sounds.feedback, cfg.sounds.keystroke]);\n\n useEffect(() => {\n if (stealth) return;\n const idx = session.current?.wordIndex ?? -1;\n if (idx === -1) return;\n if (idx === lastIndexRef.current) return;\n lastIndexRef.current = idx;\n const cur = loaded.playlist[idx];\n const next = loaded.playlist[idx + 1];\n if (cur && cfg.autoplayPronunciation) audio.pronounce(cur.name);\n if (next) audio.prefetch(next.name);\n }, [stealth, session.current?.wordIndex, audio, cfg.autoplayPronunciation, loaded.playlist]);\n\n void tick;\n\n useInput(\n (_input, key) => {\n // Node's readline normalizes byte 0x09 (Ctrl+I) to {name:'tab', ctrl:false},\n // so key.ctrl && input === 'i' would never match. Tab and Ctrl+I both arrive\n // here as key.tab — bind to that. In stealth mode Tab has no other use\n // (onTab is disabled below), so this is non-conflicting.\n if (key.tab) {\n setInfoVisible(true);\n setInfoShownAt(Date.now());\n return;\n }\n },\n { isActive: stealth && phase === 'typing' },\n );\n\n useInput(\n (_input, key) => {\n if (key.return) {\n setPhase('typing');\n return;\n }\n if (key.escape) {\n goBack();\n return;\n }\n },\n { isActive: phase === 'paused' },\n );\n\n // Stealth + paused only: Ctrl+C exits silently — erase the 3 inline rows\n // (handled in practice.impl.ts after waitUntilExit) and skip the session\n // report. Normal Ctrl+C from typing keeps the 3 rows in scrollback.\n useInput(\n (input, key) => {\n if (key.ctrl && input === 'c') {\n setSilentExit(true);\n exit();\n }\n },\n { isActive: stealth && phase === 'paused' },\n );\n\n useInput(\n (input, key) => {\n if (key.escape) {\n goBack();\n return;\n }\n if (key.return) {\n const nextIdx = chapterIndex + 1;\n if (mode === 'loop') {\n nav.replace({\n name: 'practice',\n params: { dictId, chapterIndex, mode, stealth: params.stealth },\n });\n } else if (mode === 'review' || nextIdx >= loaded.totalChapters) {\n goBack();\n } else {\n nav.replace({\n name: 'practice',\n params: { dictId, chapterIndex: nextIdx, mode, stealth: params.stealth },\n });\n }\n return;\n }\n if (input === 'm') {\n nav.replace({\n name: 'practice',\n params: { dictId, chapterIndex: 0, mode: 'review', stealth: params.stealth },\n });\n return;\n }\n },\n { isActive: phase === 'summary' },\n );\n\n const completed = session.results.length;\n const errors = session.results.reduce((a, r) => a + r.errors, 0);\n const elapsedMs = Date.now() - session.startedAt;\n const minutes = elapsedMs / 60000;\n const wpm = minutes > 0 ? Math.round((completed / minutes) * 10) / 10 : 0;\n\n const summary = phase === 'summary' ? sessionSummary(session) : null;\n\n if (stealth) {\n if (phase === 'paused') return <StealthPaused />;\n if (phase === 'summary' && summary) {\n const sMinutes = summary.durationMs / 60000;\n const sWpm = sMinutes > 0 ? Math.round((summary.wordCount / sMinutes) * 10) / 10 : 0;\n const sErrWords = Object.keys(summary.perWordErrors).length;\n const sAcc =\n summary.wordCount === 0 ? 1 : Math.max(0, (summary.wordCount - sErrWords) / summary.wordCount);\n const sAccPct = Math.round(sAcc * 1000) / 10;\n return (\n <StealthSummary\n wordCount={summary.wordCount}\n errors={summary.errors}\n durationMs={summary.durationMs}\n wpm={sWpm}\n accPct={sAccPct}\n />\n );\n }\n const currentWord = session.current\n ? loaded.playlist[session.current.wordIndex]\n : loaded.playlist[loaded.playlist.length - 1];\n const inputState = session.current?.input ?? { target: '', typed: '', errorsThisWord: 0 };\n const errWords = new Set(\n session.results.filter((r) => r.errors > 0).map((r) => r.word),\n ).size;\n const accFrac =\n completed === 0 ? 1 : Math.max(0, (completed - errWords) / completed);\n const accPct = Math.round(accFrac * 1000) / 10;\n const chapterLabel =\n mode === 'review'\n ? 'review'\n : `ch ${chapterIndex + 1}/${loaded.totalChapters}`;\n return (\n <StealthTyping\n target={currentWord?.name ?? ''}\n typed={inputState.typed}\n hideTarget={mode === 'dictation'}\n phonetic={pickPhonetic(currentWord, cfg.accent)}\n translation={currentWord?.trans ?? []}\n error={lastEffect === 'wrong'}\n imeBlocked={imeBlocked}\n audioWarning={audioWarn}\n info={{\n visible: infoVisible,\n dictName: truncateName(dictName, 24),\n chapterLabel,\n completed,\n total: loaded.playlist.length,\n wpm,\n accPct,\n elapsedMs,\n }}\n />\n );\n }\n\n if (phase === 'paused') {\n return (\n <PausedView\n dictName={dictName}\n chapterIndex={chapterIndex}\n totalChapters={loaded.totalChapters}\n mode={mode}\n completed={completed}\n total={loaded.playlist.length}\n />\n );\n }\n\n if (phase === 'summary' && summary) {\n return (\n <SummaryView\n dictName={dictName}\n chapterIndex={chapterIndex}\n totalChapters={loaded.totalChapters}\n mode={mode}\n summary={summary}\n />\n );\n }\n\n const currentWord = session.current\n ? loaded.playlist[session.current.wordIndex]\n : loaded.playlist[loaded.playlist.length - 1];\n const inputState = session.current?.input ?? { target: '', typed: '', errorsThisWord: 0 };\n\n return (\n <TypingLayout\n dictName={dictName}\n chapterIndex={chapterIndex}\n totalChapters={loaded.totalChapters}\n mode={mode}\n accent={cfg.accent}\n completed={completed}\n total={loaded.playlist.length}\n errors={errors}\n wpm={wpm}\n elapsedMs={elapsedMs}\n target={currentWord?.name ?? ''}\n typed={inputState.typed}\n flashError={lastEffect === 'wrong'}\n hideTarget={mode === 'dictation'}\n phonetic={pickPhonetic(currentWord, cfg.accent)}\n translation={currentWord?.trans ?? []}\n imeBlocked={imeBlocked}\n audioWarning={audioWarn}\n />\n );\n}\n\nfunction pickPhonetic(word: Word | undefined, accent: 'us' | 'uk'): string | null {\n if (!word) return null;\n const p = accent === 'us' ? word.usphone : word.ukphone;\n return p ?? null;\n}\n\nfunction fmtTime(ms: number): string {\n const total = Math.floor(ms / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nfunction TypingLayout(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n accent: 'us' | 'uk';\n completed: number;\n total: number;\n errors: number;\n wpm: number;\n elapsedMs: number;\n target: string;\n typed: string;\n flashError: boolean;\n hideTarget: boolean;\n phonetic: string | null;\n translation: string[];\n imeBlocked: boolean;\n audioWarning: string | null;\n}) {\n const t = useStrings();\n const progressFrac = props.total === 0 ? 0 : props.completed / props.total;\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <StatusBar\n dictName={props.dictName}\n chapterIndex={props.chapterIndex}\n totalChapters={props.totalChapters}\n mode={props.mode}\n accent={props.accent}\n completed={props.completed}\n total={props.total}\n elapsedMs={props.elapsedMs}\n />\n\n <Box flexGrow={1} flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\">\n <BigWord\n target={props.target}\n typed={props.typed}\n error={props.flashError}\n hideTarget={props.hideTarget}\n />\n\n {props.phonetic && (\n <Box marginTop={1}>\n <Text italic dimColor color={PALETTE.muted}>\n {props.phonetic}\n </Text>\n </Box>\n )}\n\n {props.translation.length > 0 && (\n <Box marginTop={1} flexDirection=\"column\" alignItems=\"center\">\n {props.translation.slice(0, 2).map((tr, i) => (\n <Text key={i} color={PALETTE.primary}>\n {tr}\n </Text>\n ))}\n </Box>\n )}\n\n {props.imeBlocked && (\n <Box marginTop={1}>\n <Text color={PALETTE.warning}>{t.practice.imeWarning}</Text>\n </Box>\n )}\n\n {!props.imeBlocked && props.audioWarning && (\n <Box marginTop={1}>\n <Text color={PALETTE.warning}>{props.audioWarning}</Text>\n </Box>\n )}\n </Box>\n\n <Box flexDirection=\"column\">\n <ProgressBar frac={progressFrac} />\n <Box justifyContent=\"center\" marginTop={1}>\n <Text color={PALETTE.muted}>\n {props.completed}/{props.total} · {fmtTime(props.elapsedMs)} · {props.wpm} {t.practice.statCards.wpm} · {props.errors} {t.practice.statCards.errors}\n </Text>\n </Box>\n <Box justifyContent=\"center\" marginTop={1}>\n <Text color={PALETTE.muted}>{t.practice.footers.typing}</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n\nfunction StatusBar(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n accent: 'us' | 'uk';\n completed: number;\n total: number;\n elapsedMs: number;\n}) {\n const t = useStrings();\n const modeName = t.practice.modes[props.mode];\n const accentName = t.practice.accents[props.accent];\n const name = truncateName(props.dictName, 20);\n const left =\n props.mode === 'review'\n ? `${name} · ${t.practice.reviewLabel} · ${accentName}`\n : `${name} · ${t.practice.chapterLabel(props.chapterIndex + 1, props.totalChapters)} · ${modeName} · ${accentName}`;\n const right = `${props.completed}/${props.total} · ${fmtTime(props.elapsedMs)}`;\n return (\n <Box>\n <Text color={PALETTE.muted}>{left}</Text>\n <Box flexGrow={1} />\n <Text color={PALETTE.muted}>{right}</Text>\n </Box>\n );\n}\n\nfunction ProgressBar({ frac }: { frac: number }) {\n const cols = process.stdout.columns ?? 80;\n const width = Math.max(20, Math.min(72, cols - 16));\n const filled = Math.round(width * Math.max(0, Math.min(1, frac)));\n const empty = width - filled;\n return (\n <Box justifyContent=\"center\">\n <Text color={PALETTE.accent}>{'━'.repeat(filled)}</Text>\n <Text color={PALETTE.muted}>{'─'.repeat(empty)}</Text>\n </Box>\n );\n}\n\nfunction PausedView(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n completed: number;\n total: number;\n}) {\n const t = useStrings();\n const frac = props.total === 0 ? 0 : props.completed / props.total;\n const subtitle =\n props.mode === 'review'\n ? `${truncateName(props.dictName, 20)} · ${t.practice.reviewLabel}`\n : `${truncateName(props.dictName, 20)} · ${t.practice.pause.chapter(props.chapterIndex + 1, props.totalChapters)}`;\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.warning}>\n {t.practice.pause.title}\n </Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{subtitle}</Text>\n </Box>\n <Box marginTop={2}>\n <ProgressBar frac={frac} />\n </Box>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.practice.pause.progress(props.completed, props.total)}</Text>\n </Box>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>{t.practice.pause.hint}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction ErrorView({ msg }: { msg: string }) {\n const t = useStrings();\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.error}>{msg}</Text>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>Esc {t.common.back}</Text>\n </Box>\n <BackKey />\n </Box>\n );\n}\n\nfunction BackKey() {\n const nav = useNav();\n useInput((_input, key) => {\n if (key.escape) nav.back();\n });\n return null;\n}\n\nfunction Centered({ text, color }: { text: string; color: string }) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={color}>{text}</Text>\n </Box>\n );\n}\n\nfunction SummaryView(props: {\n dictName: string;\n chapterIndex: number;\n totalChapters: number;\n mode: Mode;\n summary: { wordCount: number; errors: number; durationMs: number; perWordErrors: Record<string, number> };\n}) {\n const { summary } = props;\n const minutes = summary.durationMs / 60000;\n const wpm = minutes > 0 ? Math.round((summary.wordCount / minutes) * 10) / 10 : 0;\n const errorWords = Object.keys(summary.perWordErrors).length;\n const acc = summary.wordCount === 0 ? 1 : Math.max(0, (summary.wordCount - errorWords) / summary.wordCount);\n const accPct = Math.round(acc * 1000) / 10;\n\n const t = useStrings();\n const modeName = t.practice.modes[props.mode];\n const name = truncateName(props.dictName, 20);\n const subtitle =\n props.mode === 'review'\n ? `${name} · ${t.practice.reviewLabel}`\n : `${name} · ${t.practice.chapterLabel(props.chapterIndex + 1, props.totalChapters)} · ${modeName}`;\n\n const nextLabel =\n props.mode === 'loop'\n ? t.practice.summary.loopAgain\n : props.mode === 'review' || props.chapterIndex + 1 >= props.totalChapters\n ? t.practice.summary.backMenu\n : t.practice.summary.nextChapter;\n const footer = `Enter ${nextLabel} · m ${t.practice.summary.reviewMistakes} · Esc ${t.practice.summary.backMenu}`;\n\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.success}>\n {t.practice.chapterComplete}\n </Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{subtitle}</Text>\n </Box>\n\n <Box marginTop={3} flexDirection=\"row\" justifyContent=\"center\">\n <StatCard label={t.practice.statCards.words} value={String(summary.wordCount)} color={PALETTE.text} />\n <StatCard\n label={t.practice.statCards.errors}\n value={String(summary.errors)}\n color={summary.errors > 0 ? PALETTE.error : PALETTE.muted}\n />\n <StatCard label={t.practice.statCards.wpm} value={String(wpm)} color={PALETTE.accent} />\n <StatCard label={t.practice.statCards.accuracy} value={`${accPct}%`} color={PALETTE.accent} />\n </Box>\n\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>{t.practice.statCards.elapsed(fmtTime(summary.durationMs))}</Text>\n </Box>\n\n <Box flexGrow={1} />\n\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>{footer}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction StatCard({ label, value, color }: { label: string; value: string; color: string }) {\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" marginX={3}>\n <Text bold color={color}>{value}</Text>\n <Text color={PALETTE.muted}>{label}</Text>\n </Box>\n );\n}\n","export function shuffle<T>(arr: readonly T[], rng: () => number = Math.random): T[] {\n const out = [...arr];\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(rng() * (i + 1));\n const tmp = out[i]!;\n out[i] = out[j]!;\n out[j] = tmp;\n }\n return out;\n}\n\nexport function mulberry32(seed: number): () => number {\n let t = seed >>> 0;\n return () => {\n t = (t + 0x6d2b79f5) >>> 0;\n let r = Math.imul(t ^ (t >>> 15), 1 | t);\n r = (r + Math.imul(r ^ (r >>> 7), 61 | r)) ^ r;\n return ((r ^ (r >>> 14)) >>> 0) / 4294967296;\n };\n}\n","import type { Word } from './dictionary.js';\nimport { shuffle, mulberry32 } from '../util/shuffle.js';\n\nexport type Mode = 'order' | 'dictation' | 'review' | 'random' | 'loop';\n\nexport function chunkChapters(words: Word[], chapterSize: number): Word[][] {\n if (chapterSize <= 0) throw new Error('chapterSize must be positive');\n const chunks: Word[][] = [];\n for (let i = 0; i < words.length; i += chapterSize) {\n chunks.push(words.slice(i, i + chapterSize));\n }\n return chunks;\n}\n\nexport function chapterCount(totalWords: number, chapterSize: number): number {\n return Math.ceil(totalWords / chapterSize);\n}\n\n/**\n * Build a play list for a chapter, applying the given mode.\n * - order: return as-is\n * - dictation: same as order (hide-the-word behavior is UI-only)\n * - random: single shuffled pass\n * - loop: return as-is; the practice screen drives the repeat\n * - review: caller passes the mistake-book words, we just chunk those\n */\nexport function buildPlaylist(chapter: Word[], mode: Mode, seed?: number): Word[] {\n if (mode === 'random') {\n const rng = seed === undefined ? Math.random : mulberry32(seed);\n return shuffle(chapter, rng);\n }\n return chapter;\n}\n","export type InputState = {\n target: string;\n typed: string;\n errorsThisWord: number;\n};\n\nexport type InputEvent =\n | { type: 'char'; ch: string }\n | { type: 'backspace' }\n | { type: 'reset' };\n\nexport type InputEffect = 'none' | 'progress' | 'wrong' | 'correct' | 'skipped';\n\nexport function initialState(target: string): InputState {\n return { target, typed: '', errorsThisWord: 0 };\n}\n\nexport function reduce(state: InputState, ev: InputEvent): { state: InputState; effect: InputEffect } {\n switch (ev.type) {\n case 'reset':\n return { state: { ...state, typed: '' }, effect: 'none' };\n case 'backspace': {\n if (state.typed.length === 0) return { state, effect: 'none' };\n return { state: { ...state, typed: state.typed.slice(0, -1) }, effect: 'none' };\n }\n case 'char': {\n const candidate = state.typed + ev.ch;\n // Compare by code-point index, not byte length, to handle unicode safely.\n const targetUpToCandidate = [...state.target].slice(0, [...candidate].length).join('');\n if (candidate === targetUpToCandidate) {\n if (candidate.length === state.target.length) {\n return { state: { ...state, typed: candidate }, effect: 'correct' };\n }\n return { state: { ...state, typed: candidate }, effect: 'progress' };\n }\n return {\n state: { ...state, typed: '', errorsThisWord: state.errorsThisWord + 1 },\n effect: 'wrong',\n };\n }\n }\n}\n","import type { Word } from './dictionary.js';\nimport { initialState, reduce, type InputEvent, type InputState, type InputEffect } from './input-buffer.js';\n\nexport type SessionWordResult = { word: string; errors: number; durationMs: number; skipped?: boolean };\n\nexport type Session = {\n startedAt: number;\n results: SessionWordResult[];\n current: { wordIndex: number; wordStartedAt: number; input: InputState } | null;\n finishedAt: number | null;\n playlist: Word[];\n};\n\nexport function startSession(playlist: Word[], now = Date.now()): Session {\n if (playlist.length === 0) {\n return { startedAt: now, results: [], current: null, finishedAt: now, playlist };\n }\n return {\n startedAt: now,\n results: [],\n current: { wordIndex: 0, wordStartedAt: now, input: initialState(playlist[0]!.name) },\n finishedAt: null,\n playlist,\n };\n}\n\nexport function feedSession(session: Session, ev: InputEvent, now = Date.now()): { session: Session; effect: InputEffect } {\n if (!session.current) return { session, effect: 'none' };\n const { state, effect } = reduce(session.current.input, ev);\n if (effect === 'correct') {\n const finished: SessionWordResult = {\n word: state.target,\n errors: state.errorsThisWord,\n durationMs: now - session.current.wordStartedAt,\n };\n const nextIndex = session.current.wordIndex + 1;\n const results = [...session.results, finished];\n if (nextIndex >= session.playlist.length) {\n return {\n session: { ...session, results, current: null, finishedAt: now },\n effect,\n };\n }\n return {\n session: {\n ...session,\n results,\n current: {\n wordIndex: nextIndex,\n wordStartedAt: now,\n input: initialState(session.playlist[nextIndex]!.name),\n },\n },\n effect,\n };\n }\n return {\n session: {\n ...session,\n current: { ...session.current, input: state },\n },\n effect,\n };\n}\n\nexport function skipSession(session: Session, now = Date.now()): { session: Session; effect: InputEffect } {\n if (!session.current) return { session, effect: 'none' };\n const result: SessionWordResult = {\n word: session.current.input.target,\n errors: 0,\n durationMs: now - session.current.wordStartedAt,\n skipped: true,\n };\n const nextIndex = session.current.wordIndex + 1;\n const results = [...session.results, result];\n if (nextIndex >= session.playlist.length) {\n return {\n session: { ...session, results, current: null, finishedAt: now },\n effect: 'skipped',\n };\n }\n return {\n session: {\n ...session,\n results,\n current: {\n wordIndex: nextIndex,\n wordStartedAt: now,\n input: initialState(session.playlist[nextIndex]!.name),\n },\n },\n effect: 'skipped',\n };\n}\n\nexport function sessionSummary(session: Session): {\n wordCount: number;\n errors: number;\n durationMs: number;\n perWordErrors: Record<string, number>;\n} {\n const errors = session.results.reduce((a, r) => a + r.errors, 0);\n const durationMs =\n (session.finishedAt ?? Date.now()) - session.startedAt;\n const perWordErrors: Record<string, number> = {};\n for (const r of session.results) {\n if (r.errors > 0) perWordErrors[r.word] = (perWordErrors[r.word] ?? 0) + r.errors;\n }\n return { wordCount: session.results.length, errors, durationMs, perWordErrors };\n}\n","import { useEffect, useReducer, useRef, useState } from 'react';\nimport { useInput, useApp } from 'ink';\nimport type { Word } from '../../domain/dictionary.js';\nimport { startSession, feedSession, skipSession, type Session } from '../../domain/session.js';\nimport type { InputEffect } from '../../domain/input-buffer.js';\n\ntype Action =\n | { type: 'event'; input: string; key: { backspace?: boolean; delete?: boolean; tab?: boolean; escape?: boolean; return?: boolean; ctrl?: boolean }; now: number }\n | { type: 'start'; playlist: Word[]; now: number }\n | { type: 'skip'; now: number };\n\nfunction reducer(state: { session: Session; lastEffect: InputEffect | null }, action: Action) {\n if (action.type === 'start') {\n return { session: startSession(action.playlist, action.now), lastEffect: null };\n }\n if (action.type === 'skip') {\n const r = skipSession(state.session, action.now);\n return { session: r.session, lastEffect: r.effect };\n }\n if (action.type === 'event') {\n if (action.key.backspace || action.key.delete) {\n const r = feedSession(state.session, { type: 'backspace' }, action.now);\n return { session: r.session, lastEffect: r.effect };\n }\n if (action.input.length === 0) return state;\n let session = state.session;\n let lastEffect: InputEffect | null = state.lastEffect;\n for (const c of action.input) {\n const r = feedSession(session, { type: 'char', ch: c }, action.now);\n session = r.session;\n lastEffect = r.effect;\n if (session.finishedAt !== null) break;\n }\n return { session, lastEffect };\n }\n return state;\n}\n\n// Classify a typed batch from Ink's useInput. Single source of truth so the\n// IME-detection logic stays unit-testable without rendering Ink.\n// kind='ime' → contains at least one non-ASCII codepoint (>= 0x80, e.g.\n// CJK / Latin-1 supplement); reject the batch and show hint.\n// kind='valid' → all-ASCII printable; `cleaned` has the dispatchable subset.\n// kind='noise' → ASCII control (DEL, NUL, etc.) or empty; ignore silently.\nexport type InputBatchKind = 'ime' | 'valid' | 'noise';\nexport function classifyInputBatch(input: string): { kind: InputBatchKind; cleaned: string } {\n const chars = [...input];\n if (chars.some((c) => c.codePointAt(0)! >= 0x80)) {\n return { kind: 'ime', cleaned: '' };\n }\n const cleaned = chars\n .filter((c) => {\n const cp = c.codePointAt(0)!;\n return cp >= 0x20 && cp <= 0x7e;\n })\n .join('');\n return { kind: cleaned.length > 0 ? 'valid' : 'noise', cleaned };\n}\n\nexport type UseWordLoopOpts = {\n playlist: Word[];\n onComplete: (session: Session) => void;\n onTab?: () => void;\n onEscape?: () => void;\n onSkip?: () => void;\n // Fires when the typed batch contains any codepoint > 0x7E — almost always\n // means the user's IME is active (CJK input method). The batch is rejected\n // entirely so the wrong characters aren't fed to the session.\n onImeBlock?: () => void;\n // Fires when a non-empty all-ASCII batch is about to dispatch. Used to\n // clear a previously-set IME warning so it auto-dismisses on the first\n // successful keystroke.\n onValidInput?: () => void;\n enabled?: boolean;\n};\n\nexport function useWordLoop({\n playlist,\n onComplete,\n onTab,\n onEscape,\n onSkip,\n onImeBlock,\n onValidInput,\n enabled = true,\n}: UseWordLoopOpts) {\n const [state, dispatch] = useReducer(reducer, undefined, () => ({\n session: startSession(playlist, Date.now()),\n lastEffect: null as InputEffect | null,\n }));\n const completedRef = useRef(false);\n const [tick, setTick] = useState(0);\n const { exit } = useApp();\n\n useInput(\n (input, key) => {\n if (key.ctrl && input === 'c') {\n exit();\n return;\n }\n if (key.ctrl && input === 'n') {\n onSkip?.();\n dispatch({ type: 'skip', now: Date.now() });\n return;\n }\n if (key.escape) {\n onEscape?.();\n return;\n }\n if (key.tab) {\n onTab?.();\n return;\n }\n if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.return) return;\n if (key.ctrl || key.meta) return;\n const { kind, cleaned } = classifyInputBatch(input);\n if (kind === 'ime') {\n onImeBlock?.();\n return;\n }\n if (kind === 'noise') return;\n onValidInput?.();\n dispatch({ type: 'event', input: cleaned, key, now: Date.now() });\n },\n { isActive: enabled },\n );\n\n useEffect(() => {\n if (state.session.finishedAt !== null && !completedRef.current) {\n completedRef.current = true;\n onComplete(state.session);\n }\n }, [state.session, onComplete]);\n\n useEffect(() => {\n if (state.session.finishedAt !== null) return;\n const id = setInterval(() => setTick((t) => t + 1), 1000);\n return () => clearInterval(id);\n }, [state.session.finishedAt]);\n\n return { session: state.session, lastEffect: state.lastEffect, tick };\n}\n","import { useEffect, useRef } from 'react';\nimport {\n initAudio,\n playCorrect,\n playWrong,\n playKeystroke,\n playPronunciation,\n prefetchPronunciation,\n} from '../../infra/audio.js';\n\ntype Opts = {\n enabled: boolean;\n accent: 'us' | 'uk';\n autoplayPronunciation: boolean;\n};\n\nexport type AudioControls = {\n keystroke: () => void;\n correct: () => void;\n wrong: () => void;\n pronounce: (word: string) => void;\n prefetch: (word: string) => void;\n};\n\nexport function useAudio(opts: Opts): AudioControls {\n const initedRef = useRef(false);\n useEffect(() => {\n if (initedRef.current) return;\n initedRef.current = true;\n initAudio(!opts.enabled).catch(() => undefined);\n }, [opts.enabled]);\n\n return {\n keystroke: () => opts.enabled && playKeystroke(),\n correct: () => opts.enabled && playCorrect(),\n wrong: () => opts.enabled && playWrong(),\n pronounce: (word) => {\n if (!opts.enabled) return;\n if (opts.autoplayPronunciation) void playPronunciation(word, opts.accent);\n },\n prefetch: (word) => {\n if (!opts.enabled) return;\n void prefetchPronunciation(word, opts.accent);\n },\n };\n}\n","import { useCallback } from 'react';\nimport { appendSession, type SessionRecord } from '../../domain/stats.js';\nimport { loadMistakes, saveMistakes, bump } from '../../domain/mistakes.js';\nimport { addChapter as trackChapter } from '../../infra/session-tracker.js';\nimport type { Mode } from '../../domain/chapters.js';\n\ntype Summary = {\n wordCount: number;\n errors: number;\n durationMs: number;\n perWordErrors: Record<string, number>;\n};\n\nexport function useSessionPersistence(meta: { dictId: string; chapterIndex: number; mode: Mode }) {\n return useCallback(\n async (summary: Summary): Promise<void> => {\n const rec: SessionRecord = {\n ts: new Date().toISOString(),\n dictId: meta.dictId,\n chapter: meta.chapterIndex,\n mode: meta.mode,\n wordCount: summary.wordCount,\n errors: summary.errors,\n durationMs: summary.durationMs,\n perWordErrors: summary.perWordErrors,\n };\n await appendSession(rec);\n trackChapter({\n dictId: meta.dictId,\n chapterIndex: meta.chapterIndex,\n mode: meta.mode,\n wordCount: summary.wordCount,\n errors: summary.errors,\n durationMs: summary.durationMs,\n perWordErrors: summary.perWordErrors,\n });\n const dirty = Object.entries(summary.perWordErrors).filter(([, n]) => n > 0);\n if (dirty.length === 0) return;\n let book = await loadMistakes();\n for (const [word, n] of dirty) book = bump(book, word, meta.dictId, n);\n await saveMistakes(book);\n },\n [meta.dictId, meta.chapterIndex, meta.mode],\n );\n}\n","import type { ReactNode } from 'react';\nimport { Box, Text, useStdout } from 'ink';\nimport { PALETTE } from '../components/BigWord.js';\nimport { useStrings } from '../../i18n/context.js';\n\nconst RIGHT_WIDTH = 28;\n\nfunction fmtTime(ms: number): string {\n const total = Math.floor(ms / 1000);\n const m = Math.floor(total / 60);\n const s = total % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\nfunction useLeftWidth(): number {\n const { stdout } = useStdout();\n const cols = stdout?.columns ?? 80;\n return Math.max(20, cols - RIGHT_WIDTH);\n}\n\nfunction Row({ left, right }: { left: ReactNode; right: ReactNode }) {\n const leftWidth = useLeftWidth();\n return (\n <Box>\n <Box width={leftWidth}>{left}</Box>\n <Box width={RIGHT_WIDTH} justifyContent=\"flex-end\">\n {right}\n </Box>\n </Box>\n );\n}\n\nexport function StealthTyping(props: {\n target: string;\n typed: string;\n hideTarget: boolean;\n phonetic: string | null;\n translation: string[];\n error: boolean;\n imeBlocked: boolean;\n audioWarning: string | null;\n info: {\n visible: boolean;\n dictName: string;\n chapterLabel: string;\n completed: number;\n total: number;\n wpm: number;\n accPct: number;\n elapsedMs: number;\n };\n}) {\n const t = useStrings();\n const target = [...props.target];\n const typed = [...props.typed];\n\n // Row 1: word chars + inline phonetic (two-space gap, italic + dim muted).\n // error=true flashes the entire word red; phonetic remains dim regardless.\n const wordCell = (\n <Box>\n {target.map((ch, i) => {\n const isTyped = i < typed.length;\n const display = props.hideTarget && !isTyped ? '_' : isTyped ? typed[i]! : ch;\n const color = props.error ? PALETTE.error : isTyped ? PALETTE.accent : PALETTE.muted;\n return (\n <Text key={i} bold color={color}>\n {display}\n </Text>\n );\n })}\n {props.phonetic && (\n <>\n <Text> </Text>\n <Text italic dimColor color={PALETTE.muted}>\n {props.phonetic}\n </Text>\n </>\n )}\n </Box>\n );\n\n // Row 3: first translation in primary cyan (matches TypingLayout translation color)\n const translationCell = props.translation.length > 0 ? (\n <Text color={PALETTE.primary}>{props.translation[0]!}</Text>\n ) : (\n <Text> </Text>\n );\n\n const info = props.info;\n const accFmt = Number.isInteger(info.accPct) ? `${info.accPct}` : info.accPct.toFixed(1);\n\n // Right column priority: imeBlocked > audioWarning > info > idle.\n // imeBlocked: warning indicator on row 1, rows 2/3 blank.\n // audioWarning (and no IME issue): `! audio` short marker on row 1, rows 2/3 blank.\n // info.visible: dict / progress / time on rows 1/2/3.\n // idle: all three rows blank.\n const showAudio = !props.imeBlocked && props.audioWarning !== null;\n const right1 = props.imeBlocked ? (\n <Text color={PALETTE.warning}>{t.practice.imeWarningShort}</Text>\n ) : showAudio ? (\n <Text color={PALETTE.warning}>{t.practice.audioWarningShort}</Text>\n ) : info.visible ? (\n <Text color={PALETTE.muted}>{`${info.dictName} · ${info.chapterLabel}`}</Text>\n ) : (\n <Text> </Text>\n );\n const right2 = props.imeBlocked || showAudio ? (\n <Text> </Text>\n ) : info.visible ? (\n <Text color={PALETTE.muted}>{`${info.completed}/${info.total} · ${info.wpm}wpm · ${accFmt}%`}</Text>\n ) : (\n <Text> </Text>\n );\n const right3 = props.imeBlocked || showAudio ? (\n <Text> </Text>\n ) : info.visible ? (\n <Text color={PALETTE.muted}>{fmtTime(info.elapsedMs)}</Text>\n ) : (\n <Text> </Text>\n );\n\n // Layout: 3 rendered rows. Middle row's left is blank → acts as a 1-line\n // visual gap between word+phonetic and translation. Right column keeps all\n // three info slots (dict / progress / time) when info.visible is true.\n return (\n <Box flexDirection=\"column\">\n <Row left={wordCell} right={right1} />\n <Row left={<Text> </Text>} right={right2} />\n <Row left={translationCell} right={right3} />\n </Box>\n );\n}\n\nexport function StealthPaused() {\n const t = useStrings();\n return (\n <Box flexDirection=\"column\">\n <Row\n left={<Text color={PALETTE.warning}>{t.stealth.paused}</Text>}\n right={<Text color={PALETTE.muted}>{t.stealth.pausedHintRight}</Text>}\n />\n <Row left={<Text> </Text>} right={<Text color={PALETTE.muted}>Esc {t.common.back}</Text>} />\n <Row left={<Text> </Text>} right={<Text> </Text>} />\n </Box>\n );\n}\n\nexport function StealthSummary(props: {\n wordCount: number;\n errors: number;\n durationMs: number;\n wpm: number;\n accPct: number;\n}) {\n const t = useStrings();\n const accFmt = Number.isInteger(props.accPct) ? `${props.accPct}` : props.accPct.toFixed(1);\n const line = `${t.stealth.chapterDone} · ${props.wordCount}w · ${props.wpm}wpm · ${accFmt}% · ${fmtTime(props.durationMs)}`;\n return (\n <Box flexDirection=\"column\">\n <Row\n left={<Text color={PALETTE.success}>{line}</Text>}\n right={<Text color={PALETTE.muted}>{t.stealth.nextHintRight}</Text>}\n />\n <Row left={<Text> </Text>} right={<Text color={PALETTE.muted}>Esc {t.common.back}</Text>} />\n <Row left={<Text> </Text>} right={<Text> </Text>} />\n </Box>\n );\n}\n"],"mappings":"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","names":["useState","useEffect","useRef","Box","Text","useApp","useInput","shuffle","arr","rng","out","i","j","tmp","mulberry32","seed","t","chunkChapters","words","chapterSize","chunks","i","buildPlaylist","chapter","mode","seed","rng","mulberry32","shuffle","initialState","target","reduce","state","ev","candidate","targetUpToCandidate","startSession","playlist","now","initialState","feedSession","session","ev","state","effect","reduce","finished","nextIndex","results","skipSession","result","sessionSummary","errors","a","r","durationMs","perWordErrors","useEffect","useReducer","useRef","useState","useInput","useApp","reducer","state","action","startSession","skipSession","r","feedSession","session","lastEffect","classifyInputBatch","input","chars","c","cleaned","cp","useWordLoop","playlist","onComplete","onTab","onEscape","onSkip","onImeBlock","onValidInput","enabled","dispatch","useReducer","completedRef","useRef","tick","setTick","useState","exit","useApp","useInput","key","kind","useEffect","id","t","useEffect","useRef","useAudio","opts","initedRef","useRef","useEffect","initAudio","playKeystroke","playCorrect","playWrong","word","playPronunciation","prefetchPronunciation","useCallback","useSessionPersistence","meta","useCallback","summary","rec","appendSession","addChapter","dirty","n","book","loadMistakes","word","bump","saveMistakes","Box","Text","useStdout","Fragment","jsx","jsxs","RIGHT_WIDTH","fmtTime","ms","total","m","s","useLeftWidth","stdout","useStdout","cols","Row","left","right","leftWidth","Box","StealthTyping","props","t","useStrings","target","typed","wordCell","ch","i","isTyped","display","color","PALETTE","Text","translationCell","info","accFmt","showAudio","right1","right2","right3","StealthPaused","StealthSummary","line","jsx","jsxs","PracticeScreen","params","dictId","chapterIndex","mode","cfg","useAppState","t","useStrings","phase","setPhase","useState","loaded","setLoaded","errorMsg","setErrorMsg","useEffect","cancelled","words","ensureDictionary","book","loadMistakes","reviewWords","w","chapters","chunkChapters","idx","playlist","buildPlaylist","err","Centered","PALETTE","ErrorView","PracticeRunner","stealth","nav","useNav","exit","useApp","goBack","persist","useSessionPersistence","dictName","useDictName","audio","useAudio","audioStatus","useAudioStatus","audioWarn","finishedRef","useRef","lastEffectRef","lastIndexRef","infoVisible","setInfoVisible","infoShownAt","setInfoShownAt","imeBlocked","setImeBlocked","id","session","lastEffect","tick","useWordLoop","s","sessionSummary","cur","next","useInput","_input","key","input","setSilentExit","nextIdx","completed","errors","a","r","elapsedMs","minutes","wpm","summary","StealthPaused","sMinutes","sWpm","sErrWords","sAcc","sAccPct","StealthSummary","currentWord","inputState","errWords","accFrac","accPct","chapterLabel","StealthTyping","pickPhonetic","truncateName","PausedView","SummaryView","TypingLayout","word","accent","fmtTime","ms","total","m","props","progressFrac","Box","StatusBar","BigWord","Text","tr","i","ProgressBar","modeName","accentName","name","left","right","frac","cols","width","filled","empty","subtitle","msg","BackKey","text","color","errorWords","acc","footer","StatCard","label","value"]}
@@ -1,4 +0,0 @@
1
- import{homedir as m}from"os";import{join as e,resolve as d}from"path";import{mkdir as s}from"fs/promises";import{fileURLToPath as p}from"url";var i=e(m(),".qwerty-cli"),n={root:i,config:e(i,"config.json"),registry:e(i,"registry.json"),dictsDir:e(i,"dicts"),dictFile:r=>e(i,"dicts",`${r}.json`),dictMeta:r=>e(i,"dicts",`${r}.meta.json`),stats:e(i,"stats.jsonl"),mistakes:e(i,"mistakes.json"),audioCacheDir:e(i,"cache","audio"),audioCache:(r,t)=>e(i,"cache","audio",`${encodeURIComponent(r.toLowerCase())}-${t}.mp3`)};async function T(){await s(n.dictsDir,{recursive:!0}),await s(n.audioCacheDir,{recursive:!0})}function k(){let r=p(import.meta.url);return d(r,"..","..","assets")}import{mkdir as a,readFile as c,rename as f,writeFile as l,appendFile as g,access as w}from"fs/promises";import{dirname as u}from"path";import{randomBytes as y}from"crypto";async function P(r){try{return await w(r),!0}catch{return!1}}async function S(r){try{let t=await c(r,"utf8");return JSON.parse(t)}catch(t){if(t.code==="ENOENT")return null;throw t}}async function $(r,t){await a(u(r),{recursive:!0});let o=`${r}.${process.pid}.${y(4).toString("hex")}.tmp`;await l(o,JSON.stringify(t,null,2),"utf8"),await f(o,r)}async function D(r,t){await a(u(r),{recursive:!0}),await g(r,JSON.stringify(t)+`
2
- `,"utf8")}async function C(r){try{return(await c(r,"utf8")).split(`
3
- `).filter(o=>o.trim().length>0).map(o=>JSON.parse(o))}catch(t){if(t.code==="ENOENT")return[];throw t}}export{n as a,T as b,k as c,P as d,S as e,$ as f,D as g,C as h};
4
- //# sourceMappingURL=chunk-6KRVNT2S.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/infra/paths.ts","../src/infra/fs-store.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { mkdir } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\n\nconst ROOT = join(homedir(), '.qwerty-cli');\n\nexport const paths = {\n root: ROOT,\n config: join(ROOT, 'config.json'),\n registry: join(ROOT, 'registry.json'),\n dictsDir: join(ROOT, 'dicts'),\n dictFile: (id: string) => join(ROOT, 'dicts', `${id}.json`),\n dictMeta: (id: string) => join(ROOT, 'dicts', `${id}.meta.json`),\n stats: join(ROOT, 'stats.jsonl'),\n mistakes: join(ROOT, 'mistakes.json'),\n audioCacheDir: join(ROOT, 'cache', 'audio'),\n audioCache: (word: string, accent: 'us' | 'uk') =>\n join(ROOT, 'cache', 'audio', `${encodeURIComponent(word.toLowerCase())}-${accent}.mp3`),\n};\n\nexport async function ensureDirs(): Promise<void> {\n await mkdir(paths.dictsDir, { recursive: true });\n await mkdir(paths.audioCacheDir, { recursive: true });\n}\n\nexport function packageAssetsDir(): string {\n // Works for both bundled (dist/cli.js) and source (src/infra/paths.ts) layouts:\n // 2 levels up from this file lands at the package root.\n const here = fileURLToPath(import.meta.url);\n return resolve(here, '..', '..', 'assets');\n}\n","import { mkdir, readFile, rename, writeFile, appendFile, access } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { randomBytes } from 'node:crypto';\n\nexport async function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function readJson<T>(path: string): Promise<T | null> {\n try {\n const buf = await readFile(path, 'utf8');\n return JSON.parse(buf) as T;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n throw err;\n }\n}\n\nexport async function writeJsonAtomic(path: string, value: unknown): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const tmp = `${path}.${process.pid}.${randomBytes(4).toString('hex')}.tmp`;\n await writeFile(tmp, JSON.stringify(value, null, 2), 'utf8');\n await rename(tmp, path);\n}\n\nexport async function appendJsonl(path: string, line: unknown): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n await appendFile(path, JSON.stringify(line) + '\\n', 'utf8');\n}\n\nexport async function readJsonl<T>(path: string): Promise<T[]> {\n try {\n const buf = await readFile(path, 'utf8');\n return buf\n .split('\\n')\n .filter((l) => l.trim().length > 0)\n .map((l) => JSON.parse(l) as T);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];\n throw err;\n }\n}\n"],"mappings":"AAAA,OAAS,WAAAA,MAAe,KACxB,OAAS,QAAAC,EAAM,WAAAC,MAAe,OAC9B,OAAS,SAAAC,MAAa,cACtB,OAAS,iBAAAC,MAAqB,MAE9B,IAAMC,EAAOJ,EAAKD,EAAQ,EAAG,aAAa,EAE7BM,EAAQ,CACnB,KAAMD,EACN,OAAQJ,EAAKI,EAAM,aAAa,EAChC,SAAUJ,EAAKI,EAAM,eAAe,EACpC,SAAUJ,EAAKI,EAAM,OAAO,EAC5B,SAAWE,GAAeN,EAAKI,EAAM,QAAS,GAAGE,CAAE,OAAO,EAC1D,SAAWA,GAAeN,EAAKI,EAAM,QAAS,GAAGE,CAAE,YAAY,EAC/D,MAAON,EAAKI,EAAM,aAAa,EAC/B,SAAUJ,EAAKI,EAAM,eAAe,EACpC,cAAeJ,EAAKI,EAAM,QAAS,OAAO,EAC1C,WAAY,CAACG,EAAcC,IACzBR,EAAKI,EAAM,QAAS,QAAS,GAAG,mBAAmBG,EAAK,YAAY,CAAC,CAAC,IAAIC,CAAM,MAAM,CAC1F,EAEA,eAAsBC,GAA4B,CAChD,MAAMP,EAAMG,EAAM,SAAU,CAAE,UAAW,EAAK,CAAC,EAC/C,MAAMH,EAAMG,EAAM,cAAe,CAAE,UAAW,EAAK,CAAC,CACtD,CAEO,SAASK,GAA2B,CAGzC,IAAMC,EAAOR,EAAc,YAAY,GAAG,EAC1C,OAAOF,EAAQU,EAAM,KAAM,KAAM,QAAQ,CAC3C,CC/BA,OAAS,SAAAC,EAAO,YAAAC,EAAU,UAAAC,EAAQ,aAAAC,EAAW,cAAAC,EAAY,UAAAC,MAAc,cACvE,OAAS,WAAAC,MAAe,OACxB,OAAS,eAAAC,MAAmB,SAE5B,eAAsBC,EAAOC,EAAgC,CAC3D,GAAI,CACF,aAAMJ,EAAOI,CAAI,EACV,EACT,MAAQ,CACN,MAAO,EACT,CACF,CAEA,eAAsBC,EAAYD,EAAiC,CACjE,GAAI,CACF,IAAME,EAAM,MAAMV,EAASQ,EAAM,MAAM,EACvC,OAAO,KAAK,MAAME,CAAG,CACvB,OAASC,EAAK,CACZ,GAAKA,EAA8B,OAAS,SAAU,OAAO,KAC7D,MAAMA,CACR,CACF,CAEA,eAAsBC,EAAgBJ,EAAcK,EAA+B,CACjF,MAAMd,EAAMM,EAAQG,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EAC9C,IAAMM,EAAM,GAAGN,CAAI,IAAI,QAAQ,GAAG,IAAIF,EAAY,CAAC,EAAE,SAAS,KAAK,CAAC,OACpE,MAAMJ,EAAUY,EAAK,KAAK,UAAUD,EAAO,KAAM,CAAC,EAAG,MAAM,EAC3D,MAAMZ,EAAOa,EAAKN,CAAI,CACxB,CAEA,eAAsBO,EAAYP,EAAcQ,EAA8B,CAC5E,MAAMjB,EAAMM,EAAQG,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EAC9C,MAAML,EAAWK,EAAM,KAAK,UAAUQ,CAAI,EAAI;AAAA,EAAM,MAAM,CAC5D,CAEA,eAAsBC,EAAaT,EAA4B,CAC7D,GAAI,CAEF,OADY,MAAMR,EAASQ,EAAM,MAAM,GAEpC,MAAM;AAAA,CAAI,EACV,OAAQU,GAAMA,EAAE,KAAK,EAAE,OAAS,CAAC,EACjC,IAAKA,GAAM,KAAK,MAAMA,CAAC,CAAM,CAClC,OAASP,EAAK,CACZ,GAAKA,EAA8B,OAAS,SAAU,MAAO,CAAC,EAC9D,MAAMA,CACR,CACF","names":["homedir","join","resolve","mkdir","fileURLToPath","ROOT","paths","id","word","accent","ensureDirs","packageAssetsDir","here","mkdir","readFile","rename","writeFile","appendFile","access","dirname","randomBytes","exists","path","readJson","buf","err","writeJsonAtomic","value","tmp","appendJsonl","line","readJsonl","l"]}
@@ -1,2 +0,0 @@
1
- import{a,e as g,f as m}from"./chunk-6KRVNT2S.js";import{z as n}from"zod";var d=n.object({mirror:n.enum(["jsdelivr","github"]).default("jsdelivr"),accent:n.enum(["us","uk"]).default("us"),chapterSize:n.number().int().positive().max(200).default(20),sounds:n.object({master:n.boolean().default(!0),keystroke:n.boolean().default(!0),feedback:n.boolean().default(!0),keySoundName:n.string().default("default")}).default({master:!0,keystroke:!0,feedback:!0,keySoundName:"default"}),autoplayPronunciation:n.boolean().default(!0),defaultMode:n.enum(["order","dictation","review","random","loop"]).default("order"),defaultDict:n.string().optional(),language:n.enum(["auto","zh","en"]).default("auto"),stealth:n.enum(["off","menu","default"]).default("off")}),i=null;function k(){return i||(i=d.parse({}),i)}async function j(){let e=await g(a.config);if(!e)return k();let o=d.safeParse(e);if(!o.success)throw new Error(`Invalid config at ${a.config}: ${o.error.message}`);return o.data}async function x(e){await m(a.config,e)}function $(e,o){let f=o.split("."),t=e;for(let r of f){if(t===null||typeof t!="object")return;t=t[r]}return t}function P(e,o,f){let t=o.split(".");if(t.length===0)throw new Error("Empty config key");let r=JSON.parse(JSON.stringify(e)),s=r;for(let u=0;u<t.length-1;u++){let w=t[u],c=s[w];if(typeof c!="object"||c===null)throw new Error(`Cannot set ${o}: ${t.slice(0,u+1).join(".")} is not an object`);s=c}let p=t[t.length-1];s[p]=y(f);let l=d.safeParse(r);if(!l.success)throw new Error(`Invalid value for ${o}: ${l.error.issues[0]?.message??"unknown"}`);return l.data}function y(e){return e==="true"?!0:e==="false"?!1:e==="null"?null:/^-?\d+$/.test(e)||/^-?\d+\.\d+$/.test(e)?Number(e):e}export{j as a,x as b,$ as c,P as d};
2
- //# sourceMappingURL=chunk-ELWVQGDK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/infra/config-store.ts"],"sourcesContent":["import { z } from 'zod';\nimport { paths } from './paths.js';\nimport { readJson, writeJsonAtomic } from './fs-store.js';\n\nexport const ConfigSchema = z.object({\n mirror: z.enum(['jsdelivr', 'github']).default('jsdelivr'),\n accent: z.enum(['us', 'uk']).default('us'),\n chapterSize: z.number().int().positive().max(200).default(20),\n sounds: z\n .object({\n master: z.boolean().default(true),\n keystroke: z.boolean().default(true),\n feedback: z.boolean().default(true),\n keySoundName: z.string().default('default'),\n })\n .default({ master: true, keystroke: true, feedback: true, keySoundName: 'default' }),\n autoplayPronunciation: z.boolean().default(true),\n defaultMode: z.enum(['order', 'dictation', 'review', 'random', 'loop']).default('order'),\n defaultDict: z.string().optional(),\n language: z.enum(['auto', 'zh', 'en']).default('auto'),\n stealth: z.enum(['off', 'menu', 'default']).default('off'),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\n// Defaults are computed lazily at first loadConfig() call to keep zod default\n// synthesis (~5-10ms) out of the boot module-graph evaluation.\nlet cachedDefaults: Config | null = null;\nfunction getDefaults(): Config {\n if (cachedDefaults) return cachedDefaults;\n cachedDefaults = ConfigSchema.parse({});\n return cachedDefaults;\n}\n\nexport async function loadConfig(): Promise<Config> {\n const raw = await readJson<unknown>(paths.config);\n if (!raw) return getDefaults();\n const result = ConfigSchema.safeParse(raw);\n if (!result.success) {\n throw new Error(`Invalid config at ${paths.config}: ${result.error.message}`);\n }\n return result.data;\n}\n\nexport async function saveConfig(cfg: Config): Promise<void> {\n await writeJsonAtomic(paths.config, cfg);\n}\n\nexport function getByPath(cfg: Config, path: string): unknown {\n const parts = path.split('.');\n let cur: unknown = cfg;\n for (const p of parts) {\n if (cur === null || typeof cur !== 'object') return undefined;\n cur = (cur as Record<string, unknown>)[p];\n }\n return cur;\n}\n\nexport function setByPath(cfg: Config, path: string, rawValue: string): Config {\n const parts = path.split('.');\n if (parts.length === 0) throw new Error('Empty config key');\n const clone: Record<string, unknown> = JSON.parse(JSON.stringify(cfg));\n let cur: Record<string, unknown> = clone;\n for (let i = 0; i < parts.length - 1; i++) {\n const k = parts[i]!;\n const next = cur[k];\n if (typeof next !== 'object' || next === null) {\n throw new Error(`Cannot set ${path}: ${parts.slice(0, i + 1).join('.')} is not an object`);\n }\n cur = next as Record<string, unknown>;\n }\n const leaf = parts[parts.length - 1]!;\n cur[leaf] = coerce(rawValue);\n const validated = ConfigSchema.safeParse(clone);\n if (!validated.success) {\n throw new Error(`Invalid value for ${path}: ${validated.error.issues[0]?.message ?? 'unknown'}`);\n }\n return validated.data;\n}\n\nfunction coerce(v: string): unknown {\n if (v === 'true') return true;\n if (v === 'false') return false;\n if (v === 'null') return null;\n if (/^-?\\d+$/.test(v)) return Number(v);\n if (/^-?\\d+\\.\\d+$/.test(v)) return Number(v);\n return v;\n}\n"],"mappings":"iDAAA,OAAS,KAAAA,MAAS,MAIX,IAAMC,EAAeC,EAAE,OAAO,CACnC,OAAQA,EAAE,KAAK,CAAC,WAAY,QAAQ,CAAC,EAAE,QAAQ,UAAU,EACzD,OAAQA,EAAE,KAAK,CAAC,KAAM,IAAI,CAAC,EAAE,QAAQ,IAAI,EACzC,YAAaA,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,EAC5D,OAAQA,EACL,OAAO,CACN,OAAQA,EAAE,QAAQ,EAAE,QAAQ,EAAI,EAChC,UAAWA,EAAE,QAAQ,EAAE,QAAQ,EAAI,EACnC,SAAUA,EAAE,QAAQ,EAAE,QAAQ,EAAI,EAClC,aAAcA,EAAE,OAAO,EAAE,QAAQ,SAAS,CAC5C,CAAC,EACA,QAAQ,CAAE,OAAQ,GAAM,UAAW,GAAM,SAAU,GAAM,aAAc,SAAU,CAAC,EACrF,sBAAuBA,EAAE,QAAQ,EAAE,QAAQ,EAAI,EAC/C,YAAaA,EAAE,KAAK,CAAC,QAAS,YAAa,SAAU,SAAU,MAAM,CAAC,EAAE,QAAQ,OAAO,EACvF,YAAaA,EAAE,OAAO,EAAE,SAAS,EACjC,SAAUA,EAAE,KAAK,CAAC,OAAQ,KAAM,IAAI,CAAC,EAAE,QAAQ,MAAM,EACrD,QAASA,EAAE,KAAK,CAAC,MAAO,OAAQ,SAAS,CAAC,EAAE,QAAQ,KAAK,CAC3D,CAAC,EAMGC,EAAgC,KACpC,SAASC,GAAsB,CAC7B,OAAID,IACJA,EAAiBF,EAAa,MAAM,CAAC,CAAC,EAC/BE,EACT,CAEA,eAAsBE,GAA8B,CAClD,IAAMC,EAAM,MAAMC,EAAkBC,EAAM,MAAM,EAChD,GAAI,CAACF,EAAK,OAAOF,EAAY,EAC7B,IAAMK,EAASR,EAAa,UAAUK,CAAG,EACzC,GAAI,CAACG,EAAO,QACV,MAAM,IAAI,MAAM,qBAAqBD,EAAM,MAAM,KAAKC,EAAO,MAAM,OAAO,EAAE,EAE9E,OAAOA,EAAO,IAChB,CAEA,eAAsBC,EAAWC,EAA4B,CAC3D,MAAMC,EAAgBJ,EAAM,OAAQG,CAAG,CACzC,CAEO,SAASE,EAAUF,EAAaG,EAAuB,CAC5D,IAAMC,EAAQD,EAAK,MAAM,GAAG,EACxBE,EAAeL,EACnB,QAAWM,KAAKF,EAAO,CACrB,GAAIC,IAAQ,MAAQ,OAAOA,GAAQ,SAAU,OAC7CA,EAAOA,EAAgCC,CAAC,CAC1C,CACA,OAAOD,CACT,CAEO,SAASE,EAAUP,EAAaG,EAAcK,EAA0B,CAC7E,IAAMJ,EAAQD,EAAK,MAAM,GAAG,EAC5B,GAAIC,EAAM,SAAW,EAAG,MAAM,IAAI,MAAM,kBAAkB,EAC1D,IAAMK,EAAiC,KAAK,MAAM,KAAK,UAAUT,CAAG,CAAC,EACjEK,EAA+BI,EACnC,QAASC,EAAI,EAAGA,EAAIN,EAAM,OAAS,EAAGM,IAAK,CACzC,IAAMC,EAAIP,EAAMM,CAAC,EACXE,EAAOP,EAAIM,CAAC,EAClB,GAAI,OAAOC,GAAS,UAAYA,IAAS,KACvC,MAAM,IAAI,MAAM,cAAcT,CAAI,KAAKC,EAAM,MAAM,EAAGM,EAAI,CAAC,EAAE,KAAK,GAAG,CAAC,mBAAmB,EAE3FL,EAAMO,CACR,CACA,IAAMC,EAAOT,EAAMA,EAAM,OAAS,CAAC,EACnCC,EAAIQ,CAAI,EAAIC,EAAON,CAAQ,EAC3B,IAAMO,EAAYzB,EAAa,UAAUmB,CAAK,EAC9C,GAAI,CAACM,EAAU,QACb,MAAM,IAAI,MAAM,qBAAqBZ,CAAI,KAAKY,EAAU,MAAM,OAAO,CAAC,GAAG,SAAW,SAAS,EAAE,EAEjG,OAAOA,EAAU,IACnB,CAEA,SAASD,EAAOE,EAAoB,CAClC,OAAIA,IAAM,OAAe,GACrBA,IAAM,QAAgB,GACtBA,IAAM,OAAe,KACrB,UAAU,KAAKA,CAAC,GAChB,eAAe,KAAKA,CAAC,EAAU,OAAOA,CAAC,EACpCA,CACT","names":["z","ConfigSchema","z","cachedDefaults","getDefaults","loadConfig","raw","readJson","paths","result","saveConfig","cfg","writeJsonAtomic","getByPath","path","parts","cur","p","setByPath","rawValue","clone","i","k","next","leaf","coerce","validated","v"]}
@@ -1,3 +0,0 @@
1
- import{a as f,b as y,c as p,d as g}from"./chunk-6KRVNT2S.js";import{spawn as w}from"child_process";import{appendFile as x,mkdir as v,rename as Q,writeFile as A}from"fs/promises";import{join as c,dirname as h}from"path";function S(e){return e.replace(/'/g,"''")}function b(e,n){let t=S(e);return n==="wav"?["-NoProfile","-Command",`(New-Object System.Media.SoundPlayer '${t}').PlaySync();`]:["-NoProfile","-ExecutionPolicy","Bypass","-Command",`Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]::new('${t}')); $p.Play(); Start-Sleep -Milliseconds 3000`]}var T=[{kind:"afplay",platform:"unix",cmd:"afplay",args:e=>[e],supports:"both"},{kind:"ffplay",platform:"unix",cmd:"ffplay",args:e=>["-nodisp","-autoexit","-loglevel","quiet",e],supports:"both"},{kind:"mpg123",platform:"unix",cmd:"mpg123",args:e=>["-q",e],supports:"mp3"},{kind:"paplay",platform:"unix",cmd:"paplay",args:e=>[e],supports:"wav"},{kind:"aplay",platform:"unix",cmd:"aplay",args:e=>["-q",e],supports:"wav"},{kind:"powershell",platform:"win",cmd:"powershell",args:b,supports:"both"},{kind:"pwsh",platform:"win",cmd:"pwsh",args:b,supports:"both"}],$="https://dict.youdao.com/dictvoice?audio=",r=null,d=null;async function C(e){return new Promise(n=>{let t=w("where.exe",[e],{stdio:"ignore",windowsHide:!0});t.on("error",()=>n(!1)),t.on("exit",a=>n(a===0)),setTimeout(()=>{t.kill(),n(!1)},150)})}async function q(e){return new Promise(n=>{let t=w(e,["--version"],{stdio:"ignore"});t.on("error",()=>n(!1)),t.on("exit",()=>n(!0)),setTimeout(()=>{t.kill(),n(!1)},150)})}async function D(e){return process.platform==="win32"?C(e):q(e)}async function R(){let e=process.platform==="win32"?"win":"unix",n=T.filter(o=>o.platform===e),t=await Promise.all(n.map(async o=>[o,await D(o.cmd)])),a=null,i=null;for(let[o,u]of t)if(u&&(!a&&(o.supports==="wav"||o.supports==="both")&&(a=o),!i&&(o.supports==="mp3"||o.supports==="both")&&(i=o),a&&i))break;return{wav:a,mp3:i}}async function N(){return(await import("p-queue")).default}async function _(e){if(r)return r;let n=await N();if(e)return r={disabled:!0,wavPlayer:null,mp3Player:null,warning:null,keyQueue:new n({concurrency:1}),feedbackQueue:new n({concurrency:1}),pronQueue:new n({concurrency:1})},r;let{wav:t,mp3:a}=await R(),i=null;return!t&&!a?i=process.platform==="win32"?"No audio player found on PATH (looked for pwsh/powershell). Install PowerShell or add ffplay to PATH. Sounds disabled.":"No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay). Sounds disabled.":a||(i="No MP3 player found; word pronunciations will be skipped."),r={disabled:!t&&!a,wavPlayer:t,mp3Player:a,warning:i,keyQueue:new n({concurrency:2}),feedbackQueue:new n({concurrency:1}),pronQueue:new n({concurrency:1})},r}function I(){let e=process.env.QWERTY_AUDIO_DEBUG;return!!e&&e!=="0"&&e.toLowerCase()!=="false"}function E(){return c(f.root,"cache","audio-debug.log")}async function B(e){try{let n=E();await v(h(n),{recursive:!0}),await x(n,`${e}
2
- `)}catch{}}function F(e,n,t){let a=I();try{let i=e.args(n,t),o=w(e.cmd,i,{detached:!0,stdio:a?["ignore","pipe","pipe"]:"ignore",windowsHide:!0});if(o.on("error",()=>{}),a){let u=new Date().toISOString(),l=[];o.stderr?.on("data",s=>l.push(s)),o.stdout?.resume(),o.on("exit",s=>{let P=Buffer.concat(l).toString("utf8").trim();B(`[${u}] ${e.cmd} ${i.join(" ")} | exit=${s??"null"}${P?` | stderr=${P.replace(/\n/g," ")}`:""}`)})}o.unref()}catch{}}function m(e,n){if(!r||r.disabled)return;let t=n==="wav"?r.wavPlayer:r.mp3Player;t&&F(t,e,n)}function j(){!r||r.disabled||r.keyQueue.size>=2||r.keyQueue.add(async()=>{m(c(p(),"sounds","key-default.wav"),"wav"),await new Promise(e=>setTimeout(e,30))})}function L(){!r||r.disabled||r.feedbackQueue.add(async()=>{m(c(p(),"sounds","correct.wav"),"wav"),await new Promise(e=>setTimeout(e,50))})}function z(){!r||r.disabled||r.feedbackQueue.add(async()=>{m(c(p(),"sounds","wrong.wav"),"wav"),await new Promise(e=>setTimeout(e,50))})}async function O(){return d||(d=(await import("undici")).request,d)}async function k(e,n){let t=f.audioCache(e,n);if(await g(t))return t;await v(h(t),{recursive:!0});let a=n==="us"?2:1,i=`${$}${encodeURIComponent(e)}&type=${a}`;try{let u=await(await O())(i,{headersTimeout:8e3,bodyTimeout:2e4});if(u.statusCode>=400)return null;let l=Buffer.from(await u.body.arrayBuffer());if(l.length<1024)return null;let s=`${t}.tmp`;return await A(s,l),await Q(s,t),t}catch{return null}}async function G(e,n){!r||r.disabled||!r.mp3Player||(await y(),await r.pronQueue.add(async()=>{let t=await k(e,n);t&&m(t,"mp3")}))}async function Y(e,n){!r||r.disabled||!r.mp3Player||(await y(),r.pronQueue.add(async()=>{await k(e,n)}))}function J(){return r?.warning??null}function V(){return r?.disabled??!0}function X(){let e=r?.wavPlayer,n=r?.mp3Player;return{wav:e?{kind:e.kind,cmd:e.cmd}:null,mp3:n?{kind:n.kind,cmd:n.cmd}:null}}export{_ as a,j as b,L as c,z as d,G as e,Y as f,J as g,V as h,X as i};
3
- //# sourceMappingURL=chunk-KBRGNL2D.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/infra/audio.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { appendFile, mkdir, rename, writeFile } from 'node:fs/promises';\nimport { join, dirname } from 'node:path';\nimport { paths, packageAssetsDir, ensureDirs } from './paths.js';\nimport { exists } from './fs-store.js';\n\n// undici and p-queue are dynamically imported at first use to keep them out\n// of the boot module graph — saves ~50ms at startup for the menu/--version\n// paths that don't touch audio at all.\ntype RequestFn = typeof import('undici').request;\ntype PQueueCtor = typeof import('p-queue').default;\n\ntype PlayerKind = 'afplay' | 'paplay' | 'aplay' | 'mpg123' | 'ffplay' | 'powershell' | 'pwsh';\n\ntype Player = {\n kind: PlayerKind;\n cmd: string;\n args: (file: string, fileKind: 'wav' | 'mp3') => string[];\n supports: 'wav' | 'mp3' | 'both';\n platform: 'unix' | 'win';\n};\n\n// Escape single quotes inside PowerShell single-quoted string literal: ' → ''.\n// This is the only escaping required because single-quoted PowerShell strings\n// don't interpolate $(...) or backticks.\nfunction escapePwshPath(file: string): string {\n return file.replace(/'/g, \"''\");\n}\n\n// Build PowerShell args. WAV uses SoundPlayer.PlaySync() which blocks the\n// child until audio finishes (clean, fast exit). MP3 uses MediaPlayer with a\n// 3s Start-Sleep — MediaPlayer.Play() is non-blocking, so we keep the process\n// alive long enough for typical pronunciations to finish.\nfunction buildPwshArgs(file: string, fileKind: 'wav' | 'mp3'): string[] {\n const escaped = escapePwshPath(file);\n if (fileKind === 'wav') {\n return [\n '-NoProfile',\n '-Command',\n `(New-Object System.Media.SoundPlayer '${escaped}').PlaySync();`,\n ];\n }\n return [\n '-NoProfile',\n '-ExecutionPolicy', 'Bypass',\n '-Command',\n `Add-Type -AssemblyName presentationCore; $p = New-Object System.Windows.Media.MediaPlayer; $p.Open([Uri]::new('${escaped}')); $p.Play(); Start-Sleep -Milliseconds 3000`,\n ];\n}\n\nconst CANDIDATES: Player[] = [\n { kind: 'afplay', platform: 'unix', cmd: 'afplay', args: (f) => [f], supports: 'both' },\n { kind: 'ffplay', platform: 'unix', cmd: 'ffplay', args: (f) => ['-nodisp', '-autoexit', '-loglevel', 'quiet', f], supports: 'both' },\n { kind: 'mpg123', platform: 'unix', cmd: 'mpg123', args: (f) => ['-q', f], supports: 'mp3' },\n { kind: 'paplay', platform: 'unix', cmd: 'paplay', args: (f) => [f], supports: 'wav' },\n { kind: 'aplay', platform: 'unix', cmd: 'aplay', args: (f) => ['-q', f], supports: 'wav' },\n // Windows: powershell.exe (PowerShell 5.1) first — ships in every Windows\n // install and runs on .NET Framework, where System.Media.SoundPlayer and\n // PresentationCore (WPF) MediaPlayer are always reachable. pwsh (PowerShell\n // 7+) is the fallback: it only exists when the user installed it, and on a\n // bare .NET runtime SoundPlayer/MediaPlayer can't load because they live in\n // the Microsoft.WindowsDesktop.App shared framework. Preferring pwsh first\n // caused silent failure for users without the Desktop Runtime.\n { kind: 'powershell', platform: 'win', cmd: 'powershell', args: buildPwshArgs, supports: 'both' },\n { kind: 'pwsh', platform: 'win', cmd: 'pwsh', args: buildPwshArgs, supports: 'both' },\n];\n\nconst PRON_API = 'https://dict.youdao.com/dictvoice?audio=';\n\ntype AudioRuntime = {\n disabled: boolean;\n wavPlayer: Player | null;\n mp3Player: Player | null;\n warning: string | null;\n keyQueue: InstanceType<PQueueCtor>;\n feedbackQueue: InstanceType<PQueueCtor>;\n pronQueue: InstanceType<PQueueCtor>;\n};\n\nlet runtime: AudioRuntime | null = null;\nlet cachedRequest: RequestFn | null = null;\n\n// Windows probe: where.exe is reliable for PATH lookup (exit 0 = found).\n// PowerShell 5.x doesn't accept --version, so the old Unix-style probe was\n// timing out and miscategorising every Windows install as \"no audio\".\nasync function isExecutableWin(cmd: string): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = spawn('where.exe', [cmd], { stdio: 'ignore', windowsHide: true });\n probe.on('error', () => resolve(false));\n probe.on('exit', (code) => resolve(code === 0));\n setTimeout(() => {\n probe.kill();\n resolve(false);\n }, 150);\n });\n}\n\nasync function isExecutableUnix(cmd: string): Promise<boolean> {\n return new Promise((resolve) => {\n const probe = spawn(cmd, ['--version'], { stdio: 'ignore' });\n probe.on('error', () => resolve(false));\n probe.on('exit', () => resolve(true));\n setTimeout(() => {\n probe.kill();\n resolve(false);\n }, 150);\n });\n}\n\nasync function isExecutable(cmd: string): Promise<boolean> {\n return process.platform === 'win32' ? isExecutableWin(cmd) : isExecutableUnix(cmd);\n}\n\nasync function detect(): Promise<{ wav: Player | null; mp3: Player | null }> {\n // Filter by platform — no point probing afplay on Windows or pwsh on Linux.\n // Parallel probe within the filtered set keeps boot time at ~150ms regardless.\n const wantPlatform: 'unix' | 'win' = process.platform === 'win32' ? 'win' : 'unix';\n const eligible = CANDIDATES.filter((p) => p.platform === wantPlatform);\n const checks = await Promise.all(\n eligible.map(async (p) => [p, await isExecutable(p.cmd)] as const),\n );\n let wav: Player | null = null;\n let mp3: Player | null = null;\n for (const [p, ok] of checks) {\n if (!ok) continue;\n if (!wav && (p.supports === 'wav' || p.supports === 'both')) wav = p;\n if (!mp3 && (p.supports === 'mp3' || p.supports === 'both')) mp3 = p;\n if (wav && mp3) break;\n }\n return { wav, mp3 };\n}\n\nasync function loadPQueue(): Promise<PQueueCtor> {\n const mod = await import('p-queue');\n return mod.default;\n}\n\nexport async function initAudio(disabledByConfig: boolean): Promise<AudioRuntime> {\n if (runtime) return runtime;\n const PQueue = await loadPQueue();\n if (disabledByConfig) {\n runtime = {\n disabled: true,\n wavPlayer: null,\n mp3Player: null,\n warning: null,\n keyQueue: new PQueue({ concurrency: 1 }),\n feedbackQueue: new PQueue({ concurrency: 1 }),\n pronQueue: new PQueue({ concurrency: 1 }),\n };\n return runtime;\n }\n const { wav, mp3 } = await detect();\n let warning: string | null = null;\n if (!wav && !mp3) {\n warning =\n process.platform === 'win32'\n ? 'No audio player found on PATH (looked for pwsh/powershell). Install PowerShell or add ffplay to PATH. Sounds disabled.'\n : 'No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay). Sounds disabled.';\n } else if (!mp3) {\n warning = 'No MP3 player found; word pronunciations will be skipped.';\n }\n runtime = {\n disabled: !wav && !mp3,\n wavPlayer: wav,\n mp3Player: mp3,\n warning,\n keyQueue: new PQueue({ concurrency: 2 }),\n feedbackQueue: new PQueue({ concurrency: 1 }),\n pronQueue: new PQueue({ concurrency: 1 }),\n };\n return runtime;\n}\n\nfunction audioDebugEnabled(): boolean {\n const v = process.env.QWERTY_AUDIO_DEBUG;\n return !!v && v !== '0' && v.toLowerCase() !== 'false';\n}\n\nfunction audioDebugLogPath(): string {\n return join(paths.root, 'cache', 'audio-debug.log');\n}\n\nasync function appendAudioDebug(line: string): Promise<void> {\n // Best-effort write; never block the audio path and never surface errors.\n try {\n const p = audioDebugLogPath();\n await mkdir(dirname(p), { recursive: true });\n await appendFile(p, `${line}\\n`);\n } catch {\n /* ignore */\n }\n}\n\nfunction spawnPlay(player: Player, file: string, fileKind: 'wav' | 'mp3'): void {\n const debug = audioDebugEnabled();\n try {\n const args = player.args(file, fileKind);\n const child = spawn(player.cmd, args, {\n detached: true,\n stdio: debug ? ['ignore', 'pipe', 'pipe'] : 'ignore',\n windowsHide: true,\n });\n child.on('error', () => {\n // swallow; runtime will be disabled if many failures stack up\n });\n if (debug) {\n const ts = new Date().toISOString();\n const stderrChunks: Buffer[] = [];\n child.stderr?.on('data', (b: Buffer) => stderrChunks.push(b));\n child.stdout?.resume();\n child.on('exit', (code) => {\n const err = Buffer.concat(stderrChunks).toString('utf8').trim();\n void appendAudioDebug(\n `[${ts}] ${player.cmd} ${args.join(' ')} | exit=${code ?? 'null'}${err ? ` | stderr=${err.replace(/\\n/g, ' ')}` : ''}`,\n );\n });\n }\n child.unref();\n } catch {\n /* fail-soft */\n }\n}\n\nfunction playFile(file: string, kind: 'wav' | 'mp3'): void {\n if (!runtime || runtime.disabled) return;\n const player = kind === 'wav' ? runtime.wavPlayer : runtime.mp3Player;\n if (!player) return;\n spawnPlay(player, file, kind);\n}\n\nexport function playKeystroke(): void {\n if (!runtime || runtime.disabled) return;\n // Drop if queue is saturated; we never want to delay typing.\n if (runtime.keyQueue.size >= 2) return;\n void runtime.keyQueue.add(async () => {\n playFile(join(packageAssetsDir(), 'sounds', 'key-default.wav'), 'wav');\n await new Promise((r) => setTimeout(r, 30));\n });\n}\n\nexport function playCorrect(): void {\n if (!runtime || runtime.disabled) return;\n void runtime.feedbackQueue.add(async () => {\n playFile(join(packageAssetsDir(), 'sounds', 'correct.wav'), 'wav');\n await new Promise((r) => setTimeout(r, 50));\n });\n}\n\nexport function playWrong(): void {\n if (!runtime || runtime.disabled) return;\n void runtime.feedbackQueue.add(async () => {\n playFile(join(packageAssetsDir(), 'sounds', 'wrong.wav'), 'wav');\n await new Promise((r) => setTimeout(r, 50));\n });\n}\n\nasync function loadRequest(): Promise<RequestFn> {\n if (cachedRequest) return cachedRequest;\n const mod = await import('undici');\n cachedRequest = mod.request;\n return cachedRequest;\n}\n\nasync function downloadPronunciation(word: string, accent: 'us' | 'uk'): Promise<string | null> {\n const cacheFile = paths.audioCache(word, accent);\n if (await exists(cacheFile)) return cacheFile;\n await mkdir(dirname(cacheFile), { recursive: true });\n const type = accent === 'us' ? 2 : 1;\n const url = `${PRON_API}${encodeURIComponent(word)}&type=${type}`;\n try {\n const request = await loadRequest();\n const res = await request(url, { headersTimeout: 8000, bodyTimeout: 20000 });\n if (res.statusCode >= 400) return null;\n const buf = Buffer.from(await res.body.arrayBuffer());\n if (buf.length < 1024) return null;\n const tmp = `${cacheFile}.tmp`;\n await writeFile(tmp, buf);\n await rename(tmp, cacheFile);\n return cacheFile;\n } catch {\n return null;\n }\n}\n\nexport async function playPronunciation(word: string, accent: 'us' | 'uk'): Promise<void> {\n if (!runtime || runtime.disabled || !runtime.mp3Player) return;\n await ensureDirs();\n await runtime.pronQueue.add(async () => {\n const file = await downloadPronunciation(word, accent);\n if (file) playFile(file, 'mp3');\n });\n}\n\nexport async function prefetchPronunciation(word: string, accent: 'us' | 'uk'): Promise<void> {\n if (!runtime || runtime.disabled || !runtime.mp3Player) return;\n await ensureDirs();\n void runtime.pronQueue.add(async () => {\n await downloadPronunciation(word, accent);\n });\n}\n\nexport function audioWarning(): string | null {\n return runtime?.warning ?? null;\n}\n\nexport function audioDisabled(): boolean {\n return runtime?.disabled ?? true;\n}\n\nexport type PlayerSummary = { kind: PlayerKind; cmd: string } | null;\n\nexport function audioPlayers(): { wav: PlayerSummary; mp3: PlayerSummary } {\n const w = runtime?.wavPlayer;\n const m = runtime?.mp3Player;\n return {\n wav: w ? { kind: w.kind, cmd: w.cmd } : null,\n mp3: m ? { kind: m.kind, cmd: m.cmd } : null,\n };\n}\n\n// Exposed for unit tests only — verifies PowerShell command construction and\n// candidate priority order.\nexport const __test = {\n buildPwshArgs,\n escapePwshPath,\n CANDIDATES,\n};\n"],"mappings":"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","names":["spawn","appendFile","mkdir","rename","writeFile","join","dirname","escapePwshPath","file","buildPwshArgs","fileKind","escaped","CANDIDATES","f","PRON_API","runtime","cachedRequest","isExecutableWin","cmd","resolve","probe","spawn","code","isExecutableUnix","isExecutable","detect","wantPlatform","eligible","p","checks","wav","mp3","ok","loadPQueue","initAudio","disabledByConfig","PQueue","warning","audioDebugEnabled","v","audioDebugLogPath","join","paths","appendAudioDebug","line","mkdir","dirname","appendFile","spawnPlay","player","debug","args","child","ts","stderrChunks","b","err","playFile","kind","playKeystroke","packageAssetsDir","r","playCorrect","playWrong","loadRequest","downloadPronunciation","word","accent","cacheFile","exists","type","url","res","buf","tmp","writeFile","rename","playPronunciation","ensureDirs","prefetchPronunciation","audioWarning","audioDisabled","audioPlayers","w","m"]}
@@ -1,2 +0,0 @@
1
- import{createContext as k,useContext as E,useState as L,useCallback as g}from"react";import{jsx as v}from"react/jsx-runtime";var h=k(null);function N({initial:t,children:e}){let[n,r]=L([t]),s=g(i=>{r(o=>[...o,i])},[]),a=g(i=>{r(o=>o.length===0?[i]:[...o.slice(0,-1),i])},[]),u=g(()=>{r(i=>i.length>1?i.slice(0,-1):i)},[]),c=g(i=>{r([i])},[]),l=n[n.length-1];return v(h.Provider,{value:{current:l,stack:n,navigate:s,replace:a,back:u,reset:c},children:e})}function D(){let t=E(h);if(!t)throw new Error("useNav must be used inside NavProvider");return t}import{createContext as x,useContext as S,useMemo as $}from"react";var d={app:{title:"qwerty",subtitle:"typing practice for the terminal"},common:{back:"back",quit:"quit",on:"on",off:"off",cancel:"cancel"},mainMenu:{items:{practiceLabel:"Practice",practiceHintWith:t=>`start ${t}`,practiceHintNone:"pick a dictionary",dictLabel:"Dictionaries",dictHint:"browse, pull, set default",wordLabel:"Word lookup",wordHint:"search local dicts",statsLabel:"Stats",statsHint:"history & trends",configLabel:"Config",configHint:"edit preferences",stealthLabel:"Stealth",stealthHint:"quiet practice mode",quitLabel:"Quit",quitHint:"Esc or Ctrl+C also exits"},hint:"\u2191/\u2193 navigate \xB7 Enter select \xB7 letters jump",helpHint:"? help"},dict:{title:"Dictionaries",loading:"loading dictionaries\u2026",entries:t=>`${t} entries`,filterPlaceholder:"type to filter",local:"local \u2713",notLocal:"not local",defaultMark:"default \u2605",tagsLabel:t=>`tags: ${t}`,wordsLabel:t=>`${t} words`,pulling:t=>`pulling ${t}\u2026`,removing:t=>`removing ${t}\u2026`,errorOn:(t,e)=>`error on ${t}: ${e}`,footer:"\u2191/\u2193 select \xB7 Enter actions \xB7 Ctrl+K more \xB7 Esc back",action:{title:"current dictionary",setDefault:"set as default",practice:"practice now",delete:"delete local"},command:{title:"more actions",pull:"pull selected",import:"import .json",refreshList:"update dictionary list"}},config:{title:"Config",fields:{defaultDict:"default dict",defaultMode:"default mode",accent:"accent",mirror:"dict mirror",chapterSize:"chapter size",autoplayPronunciation:"autoplay pronunciation",soundsMaster:"sounds master",soundsKeystroke:"sounds keystroke",soundsFeedback:"sounds feedback",soundsKeySound:"sounds key sound",language:"language",stealth:"stealth mode"},enumValues:{stealth:{off:"off",menu:"show in menu",default:"default practice"}},hints:{editing:"type to edit \xB7 Enter save \xB7 Esc cancel",bool:"space toggle \xB7 \u2191/\u2193 move \xB7 Esc back",enum:"\u2190/\u2192 cycle \xB7 \u2191/\u2193 move \xB7 Esc back",dictRef:"Enter pick dict \xB7 \u2191/\u2193 move \xB7 Esc back",stringOrInt:"Enter edit \xB7 \u2191/\u2193 move \xB7 Esc back"}},stats:{title:"Stats \xB7 overview",loading:"loading stats\u2026",none:"No practice history yet.",nonePractice:"Run a practice session first.",lifetime:"lifetime",sessions:"sessions",words:"words",errors:"errors",wpm:"wpm",accuracy:"accuracy",streak:"streak",last:t=>`last ${t} days (\u2190/\u2192 cycle window)`,cycleWindow:"\u2190/\u2192 cycle window \xB7 Esc back",recent:"recent sessions",topMistakes:"top mistakes",footer:"\u2190/\u2192 cycle window \xB7 Esc back",maxLabel:"max",recentUnits:{words:"w",errors:"err",wpm:"wpm"},multiDictSuffix:t=>` +${t} more`,bars:{speed:"speed",accuracy:"accuracy",sessions:"sessions"}},word:{title:"Word lookup",indexing:"indexing local dictionaries\u2026",none:"No local dictionaries.",pullFirst:"Pull one in Dictionaries first.",countAcross:t=>`${t} words across local dicts`,noMatches:t=>`no matches for "${t}"`,inDict:t=>`in: ${t}`,mistakes:(t,e)=>`mistakes: ${t} (last ${e})`,footer:"type to filter \xB7 \u2191/\u2193 select \xB7 Esc back"},practice:{loading:"loading\u2026",paused:"PAUSED",chapterComplete:"CHAPTER COMPLETE",chapterLabel:(t,e)=>`chapter ${t}/${e}`,reviewLabel:"review",statusBar:{mode:"mode",accent:"accent"},modes:{order:"order",dictation:"dictation",review:"review",random:"random",loop:"loop"},accents:{us:"us",uk:"uk"},statCards:{words:"words",errors:"errors",wpm:"wpm",accuracy:"accuracy",elapsed:t=>`elapsed ${t}`},pause:{title:"PAUSED",chapter:(t,e)=>`chapter ${t}/${e}`,progress:(t,e)=>`${t}/${e}`,hint:"Enter resume \xB7 Esc back to menu"},summary:{loopAgain:"again",nextChapter:"next chapter",reviewMistakes:"review mistakes",backMenu:"back to menu"},footers:{typing:"Ctrl+N skip \xB7 Esc pause \xB7 Tab replay"},errors:{noMistakes:"No mistakes to review yet. Practice some chapters first.",dictEmpty:t=>`Dictionary ${t} is empty.`,unknown:"Unknown error"},imeWarning:"! Non-English input detected \u2014 switch your IME to English.",imeWarningShort:"! IME",audioWarningShort:"! audio"},audio:{noPlayer:"! No audio player found on PATH (looked for afplay/ffplay/mpg123/paplay/aplay/powershell). Sounds disabled."},report:{title:"Session summary",duration:"duration",practiced:"practiced",chapters:"chapters",words:"words",accuracy:"accuracy",wpm:"wpm",newMistakes:"new mistakes",farewell:"see you next time.",notPracticed:"no practice this run"},help:{title:"Help",subtitle:"all shortcuts",sections:{main:"main menu",practice:"practice",dict:"dictionaries",config:"config",stats:"stats",word:"word lookup",global:"global"},keys:{navigate:"\u2191/\u2193 navigate items",select:"Enter confirm / continue",letterJump:"letter jump to menu item",pause:"Esc pause practice",skip:"Ctrl+N skip current word (neutral)",replay:"Tab replay pronunciation",resume:"Enter resume from pause",backMenu:"Esc back to previous screen",backScreen:"Esc close panel or back",nextChapter:"Enter next chapter",reviewMistakes:"m review mistakes",filter:"type to filter list",itemActions:"Enter open actions panel",moreActions:"Ctrl+K more actions panel",cycleWindow:"\u2190/\u2192 cycle day window",stealthToggle:"Ctrl+I toggle stealth info row",helpScreen:"? open this help screen",quit:"Ctrl+C quit immediately"},footer:"Esc back"},stealth:{paused:"paused",chapterDone:"chapter done",resumeHint:"Enter resume \xB7 Esc menu",nextHint:"Enter next \xB7 Esc menu",pausedHintRight:"Enter resume",nextHintRight:"Enter next",infoChipLabel:"info",infoFmt:(t,e,n,r,s,a)=>`${t} \xB7 ${e} \xB7 ${n}/${r} \xB7 ${s} wpm \xB7 ${a}%`}},p={app:{title:"qwerty",subtitle:"\u7EC8\u7AEF\u952E\u76D8\u7EC3\u4E60"},common:{back:"\u8FD4\u56DE",quit:"\u9000\u51FA",on:"\u5F00",off:"\u5173",cancel:"\u53D6\u6D88"},mainMenu:{items:{practiceLabel:"\u7EC3\u4E60",practiceHintWith:t=>`\u5F00\u59CB ${t}`,practiceHintNone:"\u8BF7\u5148\u9009\u8BCD\u5178",dictLabel:"\u8BCD\u5178",dictHint:"\u6D4F\u89C8\u3001\u4E0B\u8F7D\u3001\u8BBE\u4E3A\u9ED8\u8BA4",wordLabel:"\u67E5\u8BCD",wordHint:"\u5728\u672C\u5730\u8BCD\u5178\u4E2D\u641C\u7D22",statsLabel:"\u7EDF\u8BA1",statsHint:"\u5386\u53F2\u4E0E\u8D8B\u52BF",configLabel:"\u8BBE\u7F6E",configHint:"\u4FEE\u6539\u504F\u597D",stealthLabel:"\u795E\u9690",stealthHint:"\u795E\u9690\u7EC3\u4E60\u6A21\u5F0F",quitLabel:"\u9000\u51FA",quitHint:"Esc \u6216 Ctrl+C \u9000\u51FA"},hint:"\u2191/\u2193 \u79FB\u52A8 \xB7 Enter \u786E\u8BA4 \xB7 \u5B57\u6BCD\u76F4\u8FBE",helpHint:"? \u5E2E\u52A9"},dict:{title:"\u8BCD\u5178",loading:"\u52A0\u8F7D\u8BCD\u5178\u4E2D\u2026",entries:t=>`${t} \u90E8\u8BCD\u5178`,filterPlaceholder:"\u8F93\u5165\u8FC7\u6EE4",local:"\u5DF2\u4E0B\u8F7D \u2713",notLocal:"\u672A\u4E0B\u8F7D",defaultMark:"\u9ED8\u8BA4 \u2605",tagsLabel:t=>`\u6807\u7B7E:${t}`,wordsLabel:t=>`${t} \u8BCD`,pulling:t=>`\u62C9\u53D6 ${t} \u4E2D\u2026`,removing:t=>`\u5220\u9664 ${t} \u4E2D\u2026`,errorOn:(t,e)=>`${t} \u51FA\u9519:${e}`,footer:"\u2191/\u2193 \u9009\u62E9 \xB7 Enter \u64CD\u4F5C \xB7 Ctrl+K \u66F4\u591A \xB7 Esc \u8FD4\u56DE",action:{title:"\u5F53\u524D\u8BCD\u5178",setDefault:"\u8BBE\u4E3A\u9ED8\u8BA4",practice:"\u7ACB\u5373\u7EC3\u4E60",delete:"\u5220\u9664\u672C\u5730"},command:{title:"\u66F4\u591A\u529F\u80FD",pull:"\u62C9\u53D6\u9009\u4E2D",import:"\u5BFC\u5165 .json",refreshList:"\u66F4\u65B0\u8BCD\u5178\u5217\u8868"}},config:{title:"\u8BBE\u7F6E",fields:{defaultDict:"\u9ED8\u8BA4\u8BCD\u5178",defaultMode:"\u9ED8\u8BA4\u6A21\u5F0F",accent:"\u53D1\u97F3",mirror:"\u8BCD\u5178\u955C\u50CF\u6E90",chapterSize:"\u7AE0\u8282\u5355\u8BCD\u6570",autoplayPronunciation:"\u81EA\u52A8\u64AD\u653E\u53D1\u97F3",soundsMaster:"\u97F3\u6548\u603B\u5F00\u5173",soundsKeystroke:"\u6309\u952E\u97F3",soundsFeedback:"\u53CD\u9988\u97F3",soundsKeySound:"\u6309\u952E\u97F3\u8272",language:"\u8BED\u8A00",stealth:"\u795E\u9690\u6A21\u5F0F"},enumValues:{stealth:{off:"\u5173\u95ED",menu:"\u4E3B\u83DC\u5355\u663E\u793A",default:"\u9ED8\u8BA4\u7EC3\u4E60\u6A21\u5F0F"}},hints:{editing:"\u8F93\u5165\u4FEE\u6539 \xB7 Enter \u4FDD\u5B58 \xB7 Esc \u53D6\u6D88",bool:"\u7A7A\u683C\u5207\u6362 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",enum:"\u2190/\u2192 \u5207\u6362 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",dictRef:"Enter \u9009\u8BCD\u5178 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE",stringOrInt:"Enter \u7F16\u8F91 \xB7 \u2191/\u2193 \u79FB\u52A8 \xB7 Esc \u8FD4\u56DE"}},stats:{title:"\u7EDF\u8BA1 \xB7 \u6982\u89C8",loading:"\u52A0\u8F7D\u7EDF\u8BA1\u4E2D\u2026",none:"\u8FD8\u6CA1\u6709\u7EC3\u4E60\u8BB0\u5F55\u3002",nonePractice:"\u5148\u6765\u4E00\u6B21\u7EC3\u4E60\u5427\u3002",lifetime:"\u7D2F\u8BA1",sessions:"\u4F1A\u8BDD",words:"\u8BCD\u6570",errors:"\u9519\u8BEF",wpm:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",streak:"\u8FDE\u7EED\u5929\u6570",last:t=>`\u6700\u8FD1 ${t} \u5929 (\u2190/\u2192 \u5207\u6362\u7A97\u53E3)`,cycleWindow:"\u2190/\u2192 \u5207\u6362\u7A97\u53E3 \xB7 Esc \u8FD4\u56DE",recent:"\u6700\u8FD1\u4F1A\u8BDD",topMistakes:"\u9AD8\u9891\u9519\u8BCD",footer:"\u2190/\u2192 \u5207\u6362\u7A97\u53E3 \xB7 Esc \u8FD4\u56DE",maxLabel:"\u6700\u5927",recentUnits:{words:"\u8BCD",errors:"\u9519",wpm:"\u901F"},multiDictSuffix:t=>` \u7B49 ${t} \u90E8`,bars:{speed:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",sessions:"\u4F1A\u8BDD"}},word:{title:"\u67E5\u8BCD",indexing:"\u7D22\u5F15\u672C\u5730\u8BCD\u5178\u4E2D\u2026",none:"\u6CA1\u6709\u672C\u5730\u8BCD\u5178\u3002",pullFirst:"\u5148\u5728\u300C\u8BCD\u5178\u300D\u4E2D\u62C9\u53D6\u4E00\u90E8\u3002",countAcross:t=>`\u672C\u5730\u8BCD\u5178\u5171 ${t} \u8BCD`,noMatches:t=>`\u6CA1\u6709\u5339\u914D\u300C${t}\u300D\u7684\u8BCD`,inDict:t=>`\u6765\u6E90:${t}`,mistakes:(t,e)=>`\u9519\u8FC7 ${t} \u6B21 (\u6700\u8FD1 ${e})`,footer:"\u8F93\u5165\u8FC7\u6EE4 \xB7 \u2191/\u2193 \u9009\u62E9 \xB7 Esc \u8FD4\u56DE"},practice:{loading:"\u52A0\u8F7D\u4E2D\u2026",paused:"\u5DF2\u6682\u505C",chapterComplete:"\u672C\u7AE0\u5B8C\u6210",chapterLabel:(t,e)=>`\u7B2C ${t}/${e} \u7AE0`,reviewLabel:"\u590D\u4E60",statusBar:{mode:"\u6A21\u5F0F",accent:"\u53D1\u97F3"},modes:{order:"\u987A\u5E8F",dictation:"\u9ED8\u5199",review:"\u590D\u4E60",random:"\u4E71\u5E8F",loop:"\u5FAA\u73AF"},accents:{us:"\u7F8E",uk:"\u82F1"},statCards:{words:"\u8BCD\u6570",errors:"\u9519\u8BEF",wpm:"\u901F\u5EA6",accuracy:"\u51C6\u786E\u7387",elapsed:t=>`\u8017\u65F6 ${t}`},pause:{title:"\u5DF2\u6682\u505C",chapter:(t,e)=>`\u7B2C ${t}/${e} \u7AE0`,progress:(t,e)=>`${t}/${e}`,hint:"Enter \u7EE7\u7EED \xB7 Esc \u8FD4\u56DE\u83DC\u5355"},summary:{loopAgain:"\u518D\u6765\u4E00\u904D",nextChapter:"\u4E0B\u4E00\u7AE0",reviewMistakes:"\u590D\u4E60\u9519\u8BCD",backMenu:"\u8FD4\u56DE\u83DC\u5355"},footers:{typing:"Ctrl+N \u8DF3\u8FC7 \xB7 Esc \u6682\u505C \xB7 Tab \u91CD\u64AD"},errors:{noMistakes:"\u9519\u8BCD\u672C\u662F\u7A7A\u7684\u3002\u5148\u7EC3\u4E60\u51E0\u7AE0\u5427\u3002",dictEmpty:t=>`\u8BCD\u5178 ${t} \u662F\u7A7A\u7684\u3002`,unknown:"\u672A\u77E5\u9519\u8BEF"},imeWarning:"! \u68C0\u6D4B\u5230\u4E2D\u6587/\u65E5\u6587/\u97E9\u6587\u8F93\u5165\uFF0C\u8BF7\u5207\u6362\u5230\u82F1\u6587\u8F93\u5165\u6CD5",imeWarningShort:"! \u4E2D\u6587\u8F93\u5165",audioWarningShort:"! \u65E0\u97F3\u6548"},audio:{noPlayer:"! \u672A\u5728 PATH \u4E2D\u627E\u5230\u97F3\u9891\u64AD\u653E\u5668(\u5C1D\u8BD5 afplay/ffplay/mpg123/paplay/aplay/powershell)\u3002\u97F3\u6548\u5DF2\u7981\u7528\u3002"},report:{title:"\u672C\u6B21\u4F1A\u8BDD",duration:"\u603B\u65F6\u957F",practiced:"\u7EC3\u4E60\u7528\u65F6",chapters:"\u5B8C\u6210\u7AE0\u8282",words:"\u8BCD\u6570",accuracy:"\u51C6\u786E\u7387",wpm:"\u901F\u5EA6",newMistakes:"\u65B0\u9519\u8BCD",farewell:"\u4E0B\u6B21\u89C1\u3002",notPracticed:"\u672C\u6B21\u672A\u7EC3\u4E60"},help:{title:"\u5E2E\u52A9",subtitle:"\u5168\u90E8\u5FEB\u6377\u952E",sections:{main:"\u4E3B\u83DC\u5355",practice:"\u7EC3\u4E60",dict:"\u8BCD\u5178",config:"\u8BBE\u7F6E",stats:"\u7EDF\u8BA1",word:"\u67E5\u8BCD",global:"\u5168\u5C40"},keys:{navigate:"\u2191/\u2193 \u79FB\u52A8\u9009\u9879",select:"Enter \u786E\u8BA4 / \u7EE7\u7EED",letterJump:"\u5B57\u6BCD\u952E \u76F4\u8FBE\u83DC\u5355\u9879",pause:"Esc \u6682\u505C\u7EC3\u4E60",skip:"Ctrl+N \u8DF3\u8FC7\u5F53\u524D\u8BCD(\u4E0D\u8BA1\u9519)",replay:"Tab \u91CD\u64AD\u53D1\u97F3",resume:"Enter \u7EE7\u7EED\u7EC3\u4E60",backMenu:"Esc \u8FD4\u56DE\u4E0A\u4E00\u5C4F",backScreen:"Esc \u5173\u95ED\u9762\u677F / \u8FD4\u56DE",nextChapter:"Enter \u4E0B\u4E00\u7AE0",reviewMistakes:"m \u590D\u4E60\u9519\u8BCD",filter:"\u8F93\u5165 \u8FC7\u6EE4\u5217\u8868",itemActions:"Enter \u5F39\u51FA\u52A8\u4F5C\u9762\u677F",moreActions:"Ctrl+K \u5F39\u51FA\u66F4\u591A\u529F\u80FD",cycleWindow:"\u2190/\u2192 \u5207\u6362\u65E5\u7A97\u53E3",stealthToggle:"Ctrl+I \u5207\u6362\u795E\u9690\u4FE1\u606F\u884C",helpScreen:"? \u6253\u5F00\u672C\u5E2E\u52A9\u9875",quit:"Ctrl+C \u7ACB\u5373\u9000\u51FA"},footer:"Esc \u8FD4\u56DE"},stealth:{paused:"paused",chapterDone:"chapter done",resumeHint:"Enter resume \xB7 Esc menu",nextHint:"Enter next \xB7 Esc menu",pausedHintRight:"Enter \u7EE7\u7EED",nextHintRight:"Enter \u4E0B\u4E00\u7AE0",infoChipLabel:"\u4FE1\u606F",infoFmt:(t,e,n,r,s,a)=>`${t} \xB7 ${e} \xB7 ${n}/${r} \xB7 ${s} wpm \xB7 ${a}%`}};function b(t){if(!t)return null;let e=t.toLowerCase();return e.startsWith("zh")?"zh":e.startsWith("en")?"en":null}function m(t){if(t==="zh"||t==="en")return t;let e=process.env.LC_ALL||process.env.LC_MESSAGES||process.env.LANG||process.env.LANGUAGE,n=b(e);if(n)return n;try{let r=Intl.DateTimeFormat().resolvedOptions().locale,s=b(r);if(s)return s}catch{}return"en"}import{jsx as C}from"react/jsx-runtime";var w=x(null);function I({pref:t,children:e}){let n=$(()=>{let r=m(t);return{lang:r,t:r==="zh"?p:d}},[t]);return C(w.Provider,{value:n,children:e})}function K(){let t=S(w);if(!t)throw new Error("useStrings must be used inside StringsProvider");return t.t}function O(t){let e=m(t);return{lang:e,t:e==="zh"?p:d}}import{Box as P,Text as H}from"ink";import{jsx as y}from"react/jsx-runtime";var f={accent:"#5eead4",muted:"#6b7280",text:"#e5e7eb",primary:"#7dcfff",success:"#86efac",warning:"#fbbf24",error:"#f87171"};function G({target:t,typed:e,error:n=!1,hideTarget:r=!1}){let s=[...t],a=[...e];return y(P,{paddingY:4,justifyContent:"center",children:s.map((u,c)=>{let l=c<a.length,i=r&&!l?"_":l?a[c]:u,o=n?f.error:l?f.accent:f.muted;return y(H,{bold:!0,color:o,children:i},c)})})}export{N as a,D as b,I as c,K as d,O as e,f,G as g};
2
- //# sourceMappingURL=chunk-R6HQWKXU.js.map