qwerty-cli 0.0.1-alpha.7 → 0.0.1-alpha.9

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 (49) hide show
  1. package/dist/ConfigEditor-GXFVIJP3.js +2 -0
  2. package/dist/ConfigEditor-GXFVIJP3.js.map +1 -0
  3. package/dist/DictBrowser-SZVB5W25.js +2 -0
  4. package/dist/DictBrowser-SZVB5W25.js.map +1 -0
  5. package/dist/HelpScreen-OUP5G5UG.js +2 -0
  6. package/dist/HelpScreen-OUP5G5UG.js.map +1 -0
  7. package/dist/PracticeScreen-LLUTKFXL.js +2 -0
  8. package/dist/PracticeScreen-LLUTKFXL.js.map +1 -0
  9. package/dist/StatsViewer-EY2N2LP3.js +2 -0
  10. package/dist/StatsViewer-EY2N2LP3.js.map +1 -0
  11. package/dist/WordLookup-UPEDLVKF.js +2 -0
  12. package/dist/WordLookup-UPEDLVKF.js.map +1 -0
  13. package/dist/chunk-2GTGXODM.js +2 -0
  14. package/dist/chunk-2GTGXODM.js.map +1 -0
  15. package/dist/chunk-2MRNI465.js +2 -0
  16. package/dist/chunk-2MRNI465.js.map +1 -0
  17. package/dist/chunk-6KRVNT2S.js +4 -0
  18. package/dist/chunk-6KRVNT2S.js.map +1 -0
  19. package/dist/chunk-6QICLHIY.js +2 -0
  20. package/dist/chunk-6QICLHIY.js.map +1 -0
  21. package/dist/chunk-ELWVQGDK.js +2 -0
  22. package/dist/chunk-ELWVQGDK.js.map +1 -0
  23. package/dist/chunk-MPE25TTQ.js +2 -0
  24. package/dist/chunk-MPE25TTQ.js.map +1 -0
  25. package/dist/chunk-QEX27D7F.js +2 -0
  26. package/dist/chunk-QEX27D7F.js.map +1 -0
  27. package/dist/chunk-RF5SVFBO.js +3 -0
  28. package/dist/chunk-RF5SVFBO.js.map +1 -0
  29. package/dist/chunk-TP77EGJ2.js +2 -0
  30. package/dist/chunk-TP77EGJ2.js.map +1 -0
  31. package/dist/chunk-UPA4JFCH.js +2 -0
  32. package/dist/chunk-UPA4JFCH.js.map +1 -0
  33. package/dist/chunk-UPYHZMDS.js +2 -0
  34. package/dist/chunk-UPYHZMDS.js.map +1 -0
  35. package/dist/cli.js +1 -3654
  36. package/dist/cli.js.map +1 -1
  37. package/dist/config.impl-IYJ4ZUPE.js +2 -0
  38. package/dist/config.impl-IYJ4ZUPE.js.map +1 -0
  39. package/dist/dict.impl-Y66SRRZL.js +4 -0
  40. package/dist/dict.impl-Y66SRRZL.js.map +1 -0
  41. package/dist/menu.impl-L5KAWNMC.js +2 -0
  42. package/dist/menu.impl-L5KAWNMC.js.map +1 -0
  43. package/dist/practice.impl-NYUJO5ER.js +2 -0
  44. package/dist/practice.impl-NYUJO5ER.js.map +1 -0
  45. package/dist/stats.impl-IXVF3Q5Y.js +7 -0
  46. package/dist/stats.impl-IXVF3Q5Y.js.map +1 -0
  47. package/dist/word.impl-C4AYZ3NC.js +2 -0
  48. package/dist/word.impl-C4AYZ3NC.js.map +1 -0
  49. package/package.json +2 -1
@@ -0,0 +1,2 @@
1
+ import{b as M}from"./chunk-2GTGXODM.js";import{d as F}from"./chunk-ELWVQGDK.js";import{b as N,c as v,d as P}from"./chunk-2MRNI465.js";import{b as R,d as $,f as a}from"./chunk-QEX27D7F.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-GXFVIJP3.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,2 @@
1
+ import{b as z}from"./chunk-2GTGXODM.js";import{a as F,d as H,f as N}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{b as q,d as J,f as e}from"./chunk-QEX27D7F.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-SZVB5W25.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,2 @@
1
+ import{b as p,d as a,f as i}from"./chunk-QEX27D7F.js";import{Box as l,Text as n,useInput as x}from"ink";import{jsx as o,jsxs as c}from"react/jsx-runtime";function f(){let m=p(),t=a();x((s,r)=>{r.escape&&m.back()});let e=t.help.keys,u=[{title:t.help.sections.global,keys:[e.helpScreen,e.quit]},{title:t.help.sections.main,keys:[e.navigate,e.select,e.letterJump,e.helpScreen]},{title:t.help.sections.practice,keys:[e.pause,e.skip,e.replay,e.resume,e.nextChapter,e.reviewMistakes,e.stealthToggle,e.backMenu]},{title:t.help.sections.dict,keys:[e.navigate,e.filter,e.itemActions,e.moreActions,e.backScreen]},{title:t.help.sections.config,keys:[e.navigate,e.select,e.backMenu]},{title:t.help.sections.stats,keys:[e.cycleWindow,e.backMenu]},{title:t.help.sections.word,keys:[e.filter,e.navigate,e.backMenu]}];return c(l,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[c(l,{children:[o(n,{bold:!0,color:i.accent,children:t.help.title}),o(n,{color:i.muted,children:" \xB7 "}),o(n,{color:i.muted,children:t.help.subtitle})]}),o(l,{marginTop:1,flexDirection:"column",flexGrow:1,children:u.map(s=>c(l,{flexDirection:"column",marginTop:1,children:[o(n,{bold:!0,color:i.text,children:s.title}),s.keys.map((r,k)=>o(l,{children:c(n,{color:i.muted,children:[" \xB7 ",r]})},k))]},s.title))}),o(l,{marginTop:1,children:o(n,{color:i.muted,children:t.help.footer})})]})}export{f as HelpScreen};
2
+ //# sourceMappingURL=HelpScreen-OUP5G5UG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/screens/HelpScreen.tsx"],"sourcesContent":["import { Box, Text, useInput } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { PALETTE } from '../components/BigWord.js';\n\nexport function HelpScreen() {\n const nav = useNav();\n const t = useStrings();\n\n useInput((_input, key) => {\n if (key.escape) nav.back();\n });\n\n const k = t.help.keys;\n const sections: Array<{ title: string; keys: string[] }> = [\n { title: t.help.sections.global, keys: [k.helpScreen, k.quit] },\n { title: t.help.sections.main, keys: [k.navigate, k.select, k.letterJump, k.helpScreen] },\n {\n title: t.help.sections.practice,\n keys: [k.pause, k.skip, k.replay, k.resume, k.nextChapter, k.reviewMistakes, k.stealthToggle, k.backMenu],\n },\n {\n title: t.help.sections.dict,\n keys: [k.navigate, k.filter, k.itemActions, k.moreActions, k.backScreen],\n },\n { title: t.help.sections.config, keys: [k.navigate, k.select, k.backMenu] },\n { title: t.help.sections.stats, keys: [k.cycleWindow, k.backMenu] },\n { title: t.help.sections.word, keys: [k.filter, k.navigate, k.backMenu] },\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.help.title}\n </Text>\n <Text color={PALETTE.muted}>{' · '}</Text>\n <Text color={PALETTE.muted}>{t.help.subtitle}</Text>\n </Box>\n\n <Box marginTop={1} flexDirection=\"column\" flexGrow={1}>\n {sections.map((sec) => (\n <Box key={sec.title} flexDirection=\"column\" marginTop={1}>\n <Text bold color={PALETTE.text}>\n {sec.title}\n </Text>\n {sec.keys.map((line, i) => (\n <Box key={i}>\n <Text color={PALETTE.muted}> · {line}</Text>\n </Box>\n ))}\n </Box>\n ))}\n </Box>\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.help.footer}</Text>\n </Box>\n </Box>\n );\n}\n"],"mappings":"sDAAA,OAAS,OAAAA,EAAK,QAAAC,EAAM,YAAAC,MAAgB,MAgC9B,OACE,OAAAC,EADF,QAAAC,MAAA,oBA3BC,SAASC,GAAa,CAC3B,IAAMC,EAAMC,EAAO,EACb,EAAIC,EAAW,EAErBC,EAAS,CAACC,EAAQC,IAAQ,CACpBA,EAAI,QAAQL,EAAI,KAAK,CAC3B,CAAC,EAED,IAAMM,EAAI,EAAE,KAAK,KACXC,EAAqD,CACzD,CAAE,MAAO,EAAE,KAAK,SAAS,OAAQ,KAAM,CAACD,EAAE,WAAYA,EAAE,IAAI,CAAE,EAC9D,CAAE,MAAO,EAAE,KAAK,SAAS,KAAM,KAAM,CAACA,EAAE,SAAUA,EAAE,OAAQA,EAAE,WAAYA,EAAE,UAAU,CAAE,EACxF,CACE,MAAO,EAAE,KAAK,SAAS,SACvB,KAAM,CAACA,EAAE,MAAOA,EAAE,KAAMA,EAAE,OAAQA,EAAE,OAAQA,EAAE,YAAaA,EAAE,eAAgBA,EAAE,cAAeA,EAAE,QAAQ,CAC1G,EACA,CACE,MAAO,EAAE,KAAK,SAAS,KACvB,KAAM,CAACA,EAAE,SAAUA,EAAE,OAAQA,EAAE,YAAaA,EAAE,YAAaA,EAAE,UAAU,CACzE,EACA,CAAE,MAAO,EAAE,KAAK,SAAS,OAAQ,KAAM,CAACA,EAAE,SAAUA,EAAE,OAAQA,EAAE,QAAQ,CAAE,EAC1E,CAAE,MAAO,EAAE,KAAK,SAAS,MAAO,KAAM,CAACA,EAAE,YAAaA,EAAE,QAAQ,CAAE,EAClE,CAAE,MAAO,EAAE,KAAK,SAAS,KAAM,KAAM,CAACA,EAAE,OAAQA,EAAE,SAAUA,EAAE,QAAQ,CAAE,CAC1E,EAEA,OACER,EAACU,EAAA,CAAI,cAAc,SAAS,SAAU,EAAG,SAAU,EAAG,MAAM,OAAO,OAAO,OACxE,UAAAV,EAACU,EAAA,CACC,UAAAX,EAACY,EAAA,CAAK,KAAI,GAAC,MAAOC,EAAQ,OACvB,WAAE,KAAK,MACV,EACAb,EAACY,EAAA,CAAK,MAAOC,EAAQ,MAAQ,oBAAQ,EACrCb,EAACY,EAAA,CAAK,MAAOC,EAAQ,MAAQ,WAAE,KAAK,SAAS,GAC/C,EAEAb,EAACW,EAAA,CAAI,UAAW,EAAG,cAAc,SAAS,SAAU,EACjD,SAAAD,EAAS,IAAKI,GACbb,EAACU,EAAA,CAAoB,cAAc,SAAS,UAAW,EACrD,UAAAX,EAACY,EAAA,CAAK,KAAI,GAAC,MAAOC,EAAQ,KACvB,SAAAC,EAAI,MACP,EACCA,EAAI,KAAK,IAAI,CAACC,EAAMC,IACnBhB,EAACW,EAAA,CACC,SAAAV,EAACW,EAAA,CAAK,MAAOC,EAAQ,MAAO,oBAAKE,GAAK,GAD9BC,CAEV,CACD,IAROF,EAAI,KASd,CACD,EACH,EAEAd,EAACW,EAAA,CAAI,UAAW,EACd,SAAAX,EAACY,EAAA,CAAK,MAAOC,EAAQ,MAAQ,WAAE,KAAK,OAAO,EAC7C,GACF,CAEJ","names":["Box","Text","useInput","jsx","jsxs","HelpScreen","nav","useNav","useStrings","useInput","_input","key","k","sections","Box","Text","PALETTE","sec","line","i"]}
@@ -0,0 +1,2 @@
1
+ import{a as ct,b as it,c as st,d as at,e as ut,f as lt,i as gt}from"./chunk-UPYHZMDS.js";import{b as Y}from"./chunk-2GTGXODM.js";import{a as ht}from"./chunk-MPE25TTQ.js";import{e as ot}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as V,b as ft,c as pt}from"./chunk-UPA4JFCH.js";import{b as dt,d as R}from"./chunk-2MRNI465.js";import{b as X,d as M,f as s,g as mt}from"./chunk-QEX27D7F.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useState as L,useEffect as H,useRef as Z}from"react";import{Box as m,Text as g,useApp as re,useInput as z}from"ink";function xt(t,e=Math.random){let r=[...t];for(let n=r.length-1;n>0;n--){let c=Math.floor(e()*(n+1)),u=r[n];r[n]=r[c],r[c]=u}return r}function bt(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 yt(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 wt(t,e,r){if(e==="random"){let n=r===void 0?Math.random:bt(r);return xt(t,n)}return t}function _(t){return{target:t,typed:"",errorsThisWord:0}}function Tt(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:_(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}=Tt(t.current.input,e);if(c==="correct"){let u={word:n.target,errors:n.errorsThisWord,durationMs:r-t.current.wordStartedAt},i=t.current.wordIndex+1,a=[...t.results,u];return i>=t.playlist.length?{session:{...t,results:a,current:null,finishedAt:r},effect:c}:{session:{...t,results:a,current:{wordIndex:i,wordStartedAt:r,input:_(t.playlist[i].name)}},effect:c}}return{session:{...t,current:{...t.current,input:n}},effect:c}}function It(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:_(t.playlist[n].name)}},effect:"skipped"}}function Q(t){let e=t.results.reduce((c,u)=>c+u.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 St,useReducer as zt,useRef as Ut,useState as Kt}from"react";import{useInput as Xt,useApp as Yt}from"ink";function qt(t,e){if(e.type==="start")return{session:q(e.playlist,e.now),lastEffect:null};if(e.type==="skip"){let r=It(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 u=J(r,{type:"char",ch:c},e.now);if(r=u.session,n=u.effect,r.finishedAt!==null)break}return{session:r,lastEffect:n}}return t}function Ct({playlist:t,onComplete:e,onTab:r,onEscape:n,onSkip:c,enabled:u=!0}){let[i,a]=zt(qt,void 0,()=>({session:q(t,Date.now()),lastEffect:null})),f=Ut(!1),[y,k]=Kt(0),{exit:I}=Yt();return Xt((b,d)=>{if(d.ctrl&&b==="c"){I();return}if(d.ctrl&&b==="n"){c?.(),a({type:"skip",now:Date.now()});return}if(d.escape){n?.();return}if(d.tab){r?.();return}if(d.upArrow||d.downArrow||d.leftArrow||d.rightArrow||d.return||d.ctrl||d.meta)return;let x=[...b].filter(W=>{let A=W.codePointAt(0);return A===32||A>=33&&A!==127}).join("");x.length!==0&&a({type:"event",input:x,key:d,now:Date.now()})},{isActive:u}),St(()=>{i.session.finishedAt!==null&&!f.current&&(f.current=!0,e(i.session))},[i.session,e]),St(()=>{if(i.session.finishedAt!==null)return;let b=setInterval(()=>k(d=>d+1),1e3);return()=>clearInterval(b)},[i.session.finishedAt]),{session:i.session,lastEffect:i.lastEffect,tick:y}}import{useEffect as Jt,useRef as Qt}from"react";function Mt(t){let e=Qt(!1);return Jt(()=>{e.current||(e.current=!0,ct(!t.enabled).catch(()=>{}))},[t.enabled]),{keystroke:()=>t.enabled&&it(),correct:()=>t.enabled&&st(),wrong:()=>t.enabled&&at(),pronounce:r=>{t.enabled&&t.autoplayPronunciation&&ut(r,t.accent)},prefetch:r=>{t.enabled&&lt(r,t.accent)}}}import{useCallback as Zt}from"react";function kt(t){return Zt(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 ht(r),gt({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(([,u])=>u>0);if(n.length===0)return;let c=await V();for(let[u,i]of n)c=pt(c,u,t.dictId,i);await ft(c)},[t.dictId,t.chapterIndex,t.mode])}import{Box as B,Text as p,useStdout as te}from"ink";import{jsx as l,jsxs as D}from"react/jsx-runtime";var vt=28;function Et(t){let e=Math.floor(t/1e3),r=Math.floor(e/60),n=e%60;return`${r}:${String(n).padStart(2,"0")}`}function ee(){let{stdout:t}=te(),e=t?.columns??80;return Math.max(20,e-vt)}function E({left:t,right:e}){let r=ee();return D(B,{children:[l(B,{width:r,children:t}),l(B,{width:vt,justifyContent:"flex-end",children:e})]})}function Wt(t){let e=[...t.target],r=[...t.typed],n=l(B,{children:e.map((I,b)=>{let d=b<r.length,x=t.hideTarget&&!d?"_":d?r[b]:I,W=t.error?s.error:d?s.accent:s.muted;return l(p,{bold:!0,color:W,children:x},b)})}),c=t.phonetic?l(p,{italic:!0,color:s.muted,children:t.phonetic}):l(p,{children:" "}),u=t.translation.length>0?l(p,{color:s.primary,children:t.translation[0]}):l(p,{children:" "}),i=t.info,a=Number.isInteger(i.accPct)?`${i.accPct}`:i.accPct.toFixed(1),f=i.visible?l(p,{color:s.muted,children:`${i.dictName} \xB7 ${i.chapterLabel}`}):l(p,{children:" "}),y=i.visible?l(p,{color:s.muted,children:`${i.completed}/${i.total} \xB7 ${i.wpm}wpm \xB7 ${a}%`}):l(p,{children:" "}),k=i.visible?l(p,{color:s.muted,children:Et(i.elapsedMs)}):l(p,{children:" "});return D(B,{flexDirection:"column",children:[l(E,{left:n,right:f}),l(E,{left:c,right:y}),l(E,{left:u,right:k})]})}function At(){let t=M();return D(B,{flexDirection:"column",children:[l(E,{left:l(p,{color:s.warning,children:t.stealth.paused}),right:l(p,{color:s.muted,children:t.stealth.pausedHintRight})}),l(E,{left:l(p,{children:" "}),right:D(p,{color:s.muted,children:["Esc ",t.common.back]})}),l(E,{left:l(p,{children:" "}),right:l(p,{children:" "})})]})}function Pt(t){let e=M(),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 ${Et(t.durationMs)}`;return D(B,{flexDirection:"column",children:[l(E,{left:l(p,{color:s.success,children:n}),right:l(p,{color:s.muted,children:e.stealth.nextHintRight})}),l(E,{left:l(p,{children:" "}),right:D(p,{color:s.muted,children:["Esc ",e.common.back]})}),l(E,{left:l(p,{children:" "}),right:l(p,{children:" "})})]})}import{jsx as o,jsxs as C}from"react/jsx-runtime";function qe({params:t}){let{dictId:e,chapterIndex:r,mode:n}=t,{cfg:c}=Y(),u=M(),[i,a]=L("loading"),[f,y]=L(null),[k,I]=L(null);return H(()=>{let b=!1;return a("loading"),y(null),I(null),(async()=>{try{let d=await ot(e);if(b)return;if(n==="review"){let j=await V();if(b)return;let O=d.filter(F=>j[F.name]?.count).slice(0,c.chapterSize);if(O.length===0){I(u.practice.errors.noMistakes),a("error");return}y({playlist:O,totalChapters:1}),a("typing");return}let x=yt(d,c.chapterSize);if(x.length===0){I(u.practice.errors.dictEmpty(e)),a("error");return}let W=Math.max(0,Math.min(x.length-1,r)),A=wt(x[W],n);y({playlist:A,totalChapters:x.length}),a("typing")}catch(d){if(b)return;I(d.message),a("error")}})(),()=>{b=!0}},[e,r,n,c.chapterSize,u]),i==="loading"?o(ue,{text:u.practice.loading,color:s.muted}):i==="error"?o(se,{msg:k??u.practice.errors.unknown}):f?o(ne,{params:t,loaded:f,phase:i,setPhase:a},`${e}-${r}-${n}-${t.stealth?"s":"n"}`):null}function ne({params:t,loaded:e,phase:r,setPhase:n}){let{dictId:c,chapterIndex:u,mode:i}=t,a=t.stealth===!0,{cfg:f}=Y(),y=X(),{exit:k}=re(),I=()=>y.stack.length>1?y.back():k(),b=kt({dictId:c,chapterIndex:u,mode:i}),d=dt(c),x=Mt({enabled:!a&&f.sounds.master,accent:f.accent,autoplayPronunciation:!a&&f.autoplayPronunciation}),W=Z(!1),A=Z(null),j=Z(-1),[O,F]=L(!1),[et,Nt]=L(null);H(()=>{if(et===null)return;let h=setTimeout(()=>F(!1),2e3);return()=>clearTimeout(h)},[et]);let{session:T,lastEffect:v,tick:Rt}=Ct({playlist:e.playlist,enabled:r==="typing",onComplete:h=>{W.current||(W.current=!0,n("summary"),Promise.resolve(b(Q(h))).catch(w=>{console.error("Failed to persist session:",w)}))},onEscape:()=>n(r==="paused"?"typing":"paused"),onTab:a?void 0:()=>{let h=T.current?e.playlist[T.current.wordIndex]:void 0;h&&x.pronounce(h.name)}});H(()=>{a||v!==null&&v!==A.current&&(A.current=v,v==="wrong"&&f.sounds.feedback&&x.wrong(),v==="progress"&&f.sounds.keystroke&&x.keystroke(),v==="correct"&&(f.sounds.feedback&&x.correct(),f.sounds.keystroke&&x.keystroke()))},[a,v,x,f.sounds.feedback,f.sounds.keystroke]),H(()=>{if(a)return;let h=T.current?.wordIndex??-1;if(h===-1||h===j.current)return;j.current=h;let w=e.playlist[h],$=e.playlist[h+1];w&&f.autoplayPronunciation&&x.pronounce(w.name),$&&x.prefetch($.name)},[a,T.current?.wordIndex,x,f.autoplayPronunciation,e.playlist]),z((h,w)=>{if(w.tab){F(!0),Nt(Date.now());return}},{isActive:a&&r==="typing"}),z((h,w)=>{if(w.return){n("typing");return}if(w.escape){I();return}},{isActive:r==="paused"}),z((h,w)=>{if(w.escape){I();return}if(w.return){let $=u+1;i==="loop"?y.replace({name:"practice",params:{dictId:c,chapterIndex:u,mode:i,stealth:t.stealth}}):i==="review"||$>=e.totalChapters?I():y.replace({name:"practice",params:{dictId:c,chapterIndex:$,mode:i,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,Dt=T.results.reduce((h,w)=>h+w.errors,0),U=Date.now()-T.startedAt,rt=U/6e4,nt=rt>0?Math.round(P/rt*10)/10:0,S=r==="summary"?Q(T):null;if(a){if(r==="paused")return o(At,{});if(r==="summary"&&S){let N=S.durationMs/6e4,Vt=N>0?Math.round(S.wordCount/N*10)/10:0,_t=Object.keys(S.perWordErrors).length,Gt=S.wordCount===0?1:Math.max(0,(S.wordCount-_t)/S.wordCount),Ht=Math.round(Gt*1e3)/10;return o(Pt,{wordCount:S.wordCount,errors:S.errors,durationMs:S.durationMs,wpm:Vt,accPct:Ht})}let h=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],w=T.current?.input??{target:"",typed:"",errorsThisWord:0},$=new Set(T.results.filter(N=>N.errors>0).map(N=>N.word)).size,jt=P===0?1:Math.max(0,(P-$)/P),Ot=Math.round(jt*1e3)/10,Ft=i==="review"?"review":`ch ${u+1}/${e.totalChapters}`;return o(Wt,{target:h?.name??"",typed:w.typed,hideTarget:i==="dictation",phonetic:$t(h,f.accent),translation:h?.trans??[],error:v==="wrong",info:{visible:O,dictName:R(d,24),chapterLabel:Ft,completed:P,total:e.playlist.length,wpm:nt,accPct:Ot,elapsedMs:U}})}if(r==="paused")return o(ie,{dictName:d,chapterIndex:u,totalChapters:e.totalChapters,mode:i,completed:P,total:e.playlist.length});if(r==="summary"&&S)return o(le,{dictName:d,chapterIndex:u,totalChapters:e.totalChapters,mode:i,summary:S});let K=T.current?e.playlist[T.current.wordIndex]:e.playlist[e.playlist.length-1],Lt=T.current?.input??{target:"",typed:"",errorsThisWord:0};return o(oe,{dictName:d,chapterIndex:u,totalChapters:e.totalChapters,mode:i,accent:f.accent,completed:P,total:e.playlist.length,errors:Dt,wpm:nt,elapsedMs:U,target:K?.name??"",typed:Lt.typed,flashError:v==="wrong",hideTarget:i==="dictation",phonetic:$t(K,f.accent),translation:K?.trans??[]})}function $t(t,e){if(!t)return null;let r=e==="us"?t.usphone:t.ukphone;return r?`/${r}/`: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 oe(t){let e=M(),r=t.total===0?0:t.completed/t.total;return C(m,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[o(ce,{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(m,{flexGrow:1,flexDirection:"column",alignItems:"center",justifyContent:"center",children:[o(mt,{target:t.target,typed:t.typed,error:t.flashError,hideTarget:t.hideTarget}),t.phonetic&&o(m,{marginTop:1,children:o(g,{italic:!0,color:s.muted,children:t.phonetic})}),t.translation.length>0&&o(m,{marginTop:1,flexDirection:"column",alignItems:"center",children:t.translation.slice(0,2).map((n,c)=>o(g,{color:s.primary,children:n},c))})]}),C(m,{flexDirection:"column",children:[o(Bt,{frac:r}),o(m,{justifyContent:"center",marginTop:1,children:C(g,{color:s.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(m,{justifyContent:"center",marginTop:1,children:o(g,{color:s.muted,children:e.practice.footers.typing})})]})]})}function ce(t){let e=M(),r=e.practice.modes[t.mode],n=e.practice.accents[t.accent],c=R(t.dictName,20),u=t.mode==="review"?`${c} \xB7 ${e.practice.reviewLabel} \xB7 ${n}`:`${c} \xB7 ${e.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${r} \xB7 ${n}`,i=`${t.completed}/${t.total} \xB7 ${tt(t.elapsedMs)}`;return C(m,{children:[o(g,{color:s.muted,children:u}),o(m,{flexGrow:1}),o(g,{color:s.muted,children:i})]})}function Bt({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(m,{justifyContent:"center",children:[o(g,{color:s.accent,children:"\u2501".repeat(n)}),o(g,{color:s.muted,children:"\u2500".repeat(c)})]})}function ie(t){let e=M(),r=t.total===0?0:t.completed/t.total,n=t.mode==="review"?`${R(t.dictName,20)} \xB7 ${e.practice.reviewLabel}`:`${R(t.dictName,20)} \xB7 ${e.practice.pause.chapter(t.chapterIndex+1,t.totalChapters)}`;return C(m,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(g,{bold:!0,color:s.warning,children:e.practice.pause.title}),o(m,{marginTop:1,children:o(g,{color:s.muted,children:n})}),o(m,{marginTop:2,children:o(Bt,{frac:r})}),o(m,{marginTop:1,children:o(g,{color:s.muted,children:e.practice.pause.progress(t.completed,t.total)})}),o(m,{marginTop:2,children:o(g,{color:s.muted,children:e.practice.pause.hint})})]})}function se({msg:t}){let e=M();return C(m,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(g,{color:s.error,children:t}),o(m,{marginTop:2,children:C(g,{color:s.muted,children:["Esc ",e.common.back]})}),o(ae,{})]})}function ae(){let t=X();return z((e,r)=>{r.escape&&t.back()}),null}function ue({text:t,color:e}){return o(m,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(g,{color:e,children:t})})}function le(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,u=e.wordCount===0?1:Math.max(0,(e.wordCount-c)/e.wordCount),i=Math.round(u*1e3)/10,a=M(),f=a.practice.modes[t.mode],y=R(t.dictName,20),k=t.mode==="review"?`${y} \xB7 ${a.practice.reviewLabel}`:`${y} \xB7 ${a.practice.chapterLabel(t.chapterIndex+1,t.totalChapters)} \xB7 ${f}`,b=`Enter ${t.mode==="loop"?a.practice.summary.loopAgain:t.mode==="review"||t.chapterIndex+1>=t.totalChapters?a.practice.summary.backMenu:a.practice.summary.nextChapter} \xB7 m ${a.practice.summary.reviewMistakes} \xB7 Esc ${a.practice.summary.backMenu}`;return C(m,{flexDirection:"column",alignItems:"center",justifyContent:"center",paddingY:1,width:"100%",height:"100%",children:[o(g,{bold:!0,color:s.success,children:a.practice.chapterComplete}),o(m,{marginTop:1,children:o(g,{color:s.muted,children:k})}),C(m,{marginTop:3,flexDirection:"row",justifyContent:"center",children:[o(G,{label:a.practice.statCards.words,value:String(e.wordCount),color:s.text}),o(G,{label:a.practice.statCards.errors,value:String(e.errors),color:e.errors>0?s.error:s.muted}),o(G,{label:a.practice.statCards.wpm,value:String(n),color:s.accent}),o(G,{label:a.practice.statCards.accuracy,value:`${i}%`,color:s.accent})]}),o(m,{marginTop:2,children:o(g,{color:s.muted,children:a.practice.statCards.elapsed(tt(e.durationMs))})}),o(m,{flexGrow:1}),o(m,{marginTop:2,children:o(g,{color:s.muted,children:b})})]})}function G({label:t,value:e,color:r}){return C(m,{flexDirection:"column",alignItems:"center",marginX:3,children:[o(g,{bold:!0,color:r,children:e}),o(g,{color:s.muted,children:t})]})}export{qe as PracticeScreen};
2
+ //# sourceMappingURL=PracticeScreen-LLUTKFXL.js.map
@@ -0,0 +1 @@
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 { 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';\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\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\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 });\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 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 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 />\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 ? `/${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}) {\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 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 </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\nexport type UseWordLoopOpts = {\n playlist: Word[];\n onComplete: (session: Session) => void;\n onTab?: () => void;\n onEscape?: () => void;\n onSkip?: () => void;\n enabled?: boolean;\n};\n\nexport function useWordLoop({ playlist, onComplete, onTab, onEscape, onSkip, enabled = true }: 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 cleaned = [...input].filter((c) => {\n const code = c.codePointAt(0)!;\n return code === 0x20 || (code >= 0x21 && code !== 0x7f);\n }).join('');\n if (cleaned.length === 0) return;\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 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 target = [...props.target];\n const typed = [...props.typed];\n\n // Row 1: bare word, no brackets, bold, with per-char colors matching BigWord rules.\n // error=true flashes the entire word red (matches BigWord behavior).\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 </Box>\n );\n\n // Row 2: phonetic only (translation moves to row 3)\n const phoneticCell = props.phonetic ? (\n <Text italic color={PALETTE.muted}>\n {props.phonetic}\n </Text>\n ) : (\n <Text> </Text>\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: idle = all blank; info-visible = dict / progress / time.\n const right1 = info.visible ? (\n <Text color={PALETTE.muted}>{`${info.dictName} · ${info.chapterLabel}`}</Text>\n ) : (\n <Text> </Text>\n );\n const right2 = info.visible ? (\n <Text color={PALETTE.muted}>{`${info.completed}/${info.total} · ${info.wpm}wpm · ${accFmt}%`}</Text>\n ) : (\n <Text> </Text>\n );\n const right3 = info.visible ? (\n <Text color={PALETTE.muted}>{fmtTime(info.elapsedMs)}</Text>\n ) : (\n <Text> </Text>\n );\n\n return (\n <Box flexDirection=\"column\">\n <Row left={wordCell} right={right1} />\n <Row left={phoneticCell} 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","useWordLoop","playlist","onComplete","onTab","onEscape","onSkip","enabled","dispatch","useReducer","completedRef","useRef","tick","setTick","useState","exit","useApp","useInput","input","key","cleaned","c","code","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","jsx","jsxs","RIGHT_WIDTH","fmtTime","ms","total","m","s","useLeftWidth","stdout","useStdout","cols","Row","left","right","leftWidth","Box","StealthTyping","props","target","typed","wordCell","ch","i","isTyped","display","color","PALETTE","Text","phoneticCell","translationCell","info","accFmt","right1","right2","right3","StealthPaused","useStrings","StealthSummary","t","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","finishedRef","useRef","lastEffectRef","lastIndexRef","infoVisible","setInfoVisible","infoShownAt","setInfoShownAt","id","session","lastEffect","tick","useWordLoop","s","sessionSummary","cur","next","useInput","_input","key","input","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","p","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"]}
@@ -0,0 +1,2 @@
1
+ import{b as N,c as $,d as L,e as j,g as Y}from"./chunk-MPE25TTQ.js";import{a as C,d as R}from"./chunk-UPA4JFCH.js";import{b as B,d as M}from"./chunk-2MRNI465.js";import{b as P,d as A,f as o}from"./chunk-QEX27D7F.js";import"./chunk-6QICLHIY.js";import"./chunk-6KRVNT2S.js";import{useEffect as U,useState as v}from"react";import{Box as s,Text as r,useInput as V,useStdout as q}from"ink";import{jsx as t,jsxs as c}from"react/jsx-runtime";var w=[7,14,30,90];function st(){let i=P(),e=A(),[n,m]=v(null),[x,f]=v(null),[p,T]=v(1);if(U(()=>{(async()=>{let[l,a]=await Promise.all([N(),C()]);m(l),f(a)})()},[]),V((l,a)=>{if(a.escape){i.back();return}a.rightArrow&&T(S=>(S+1)%w.length),a.leftArrow&&T(S=>(S-1+w.length)%w.length)}),!n||!x)return t(s,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:t(r,{color:o.muted,children:e.stats.loading})});if(n.length===0)return c(s,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[t(r,{color:o.muted,children:e.stats.none}),t(s,{marginTop:1,children:t(r,{color:o.muted,children:e.stats.nonePractice})}),t(s,{marginTop:2,children:c(r,{color:o.muted,children:["Esc ",e.common.back]})})]});let u=w[p],b=j(n),h=n.reduce((l,a)=>l+a.wordCount,0),F=n.reduce((l,a)=>l+a.errors,0),y=n.reduce((l,a)=>l+a.durationMs,0),O=n.reduce((l,a)=>l+(a.wordCount-Object.keys(a.perWordErrors).length),0),X=y>0?Math.round(h/(y/6e4)*10)/10:0,_=h===0?1:O/h,G=n.slice(-5).reverse(),W=R(x,8),d=Y(n,u),D=d.peakWpmLifetime>0?Math.min(1,d.avgWpm/d.peakWpmLifetime):0,I=Math.min(1,d.avgAccPct/100),E=u>0?Math.min(1,d.sessionsInWindow/u):0;return c(s,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[t(r,{bold:!0,color:o.accent,children:e.stats.title}),c(s,{marginTop:1,flexDirection:"column",children:[t(r,{color:o.muted,children:e.stats.lifetime}),c(s,{marginTop:1,children:[t(g,{label:e.stats.sessions,value:String(n.length)}),t(g,{label:e.stats.words,value:String(h)}),t(g,{label:e.stats.errors,value:String(F)}),t(g,{label:e.stats.wpm,value:String(X),accent:!0}),t(g,{label:e.stats.accuracy,value:`${Math.round(_*1e3)/10}%`,accent:!0}),t(g,{label:e.stats.streak,value:`${b}d`,accent:!0})]})]}),c(s,{marginTop:2,flexDirection:"column",children:[t(r,{color:o.muted,children:e.stats.last(u)}),c(s,{marginTop:1,flexDirection:"column",borderStyle:"round",borderColor:o.muted,paddingX:2,paddingY:0,children:[t(k,{label:e.stats.bars.speed,frac:D,valueText:`${d.avgWpm} wpm`,pct:Math.round(D*100)}),t(k,{label:e.stats.bars.accuracy,frac:I,valueText:`${d.avgAccPct}%`,pct:Math.round(I*100)}),t(k,{label:e.stats.bars.sessions,frac:E,valueText:`${d.sessionsInWindow} / ${u}`,pct:Math.round(E*100)})]})]}),c(s,{marginTop:2,flexDirection:"column",children:[t(r,{color:o.muted,children:e.stats.recent}),G.map((l,a)=>t(z,{session:l,units:e.stats.recentUnits},a))]}),W.length>0&&c(s,{marginTop:2,flexDirection:"column",children:[t(r,{color:o.muted,children:e.stats.topMistakes}),W.map(([l,a])=>t(H,{word:l,count:a.count,dictIds:a.dictIds,multiSuffix:e.stats.multiDictSuffix},l))]}),t(s,{flexGrow:1}),t(s,{marginTop:1,children:t(r,{color:o.muted,children:e.stats.footer})})]})}function z({session:i,units:e}){let n=B(i.dictId),m=M(n,14);return c(s,{children:[c(r,{color:o.muted,children:[" ",i.ts.replace("T"," ").slice(0,16)," "]}),t(r,{color:o.text,children:m.padEnd(14)}),c(r,{color:o.muted,children:[" ","ch",String(i.chapter+1).padStart(3)," ",i.mode.padEnd(9)," ",String(i.wordCount).padStart(3),e.words," ",i.errors,e.errors," ",$(i),e.wpm," ",Math.round(L(i)*1e3)/10,"%"]})]})}function H({word:i,count:e,dictIds:n,multiSuffix:m}){let x=n[0]??"",f=B(x),p=n.length>1?m(n.length-1):"";return c(s,{children:[c(r,{color:o.error,children:[" ",String(e).padStart(3)," "]}),t(r,{color:o.text,children:i.padEnd(20)}),c(r,{color:o.muted,children:[M(f,20),p]})]})}function g({label:i,value:e,accent:n=!1}){return c(s,{marginRight:3,children:[c(r,{color:o.muted,children:[i," "]}),t(r,{bold:!0,color:n?o.accent:o.text,children:e})]})}function k({label:i,frac:e,valueText:n,pct:m}){let{stdout:x}=q(),f=x?.columns??80,p=Math.max(20,Math.min(50,f-38)),T=Math.max(0,Math.min(1,e)),u=Math.round(p*T),b=p-u;return c(s,{children:[t(s,{width:9,children:t(r,{color:o.muted,children:i})}),t(r,{color:o.accent,children:"\u2501".repeat(u)}),t(r,{color:o.muted,children:"\u2500".repeat(b)}),t(s,{width:14,marginLeft:2,children:t(r,{color:o.text,children:n})}),t(s,{width:5,justifyContent:"flex-end",children:c(r,{color:o.muted,children:[m,"%"]})})]})}export{st as StatsViewer};
2
+ //# sourceMappingURL=StatsViewer-EY2N2LP3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/screens/StatsViewer.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Box, Text, useInput, useStdout } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport {\n loadSessions,\n computeWPM,\n accuracy,\n windowAggregate,\n dailyStreak,\n type SessionRecord,\n} from '../../domain/stats.js';\nimport { loadMistakes, topN, type MistakeBook } from '../../domain/mistakes.js';\nimport { truncateName } from '../../util/dict-name.js';\n\nconst DAY_WINDOWS = [7, 14, 30, 90];\n\nexport function StatsViewer() {\n const nav = useNav();\n const t = useStrings();\n const [sessions, setSessions] = useState<SessionRecord[] | null>(null);\n const [book, setBook] = useState<MistakeBook | null>(null);\n const [windowIdx, setWindowIdx] = useState(1);\n\n useEffect(() => {\n void (async () => {\n const [s, b] = await Promise.all([loadSessions(), loadMistakes()]);\n setSessions(s);\n setBook(b);\n })();\n }, []);\n\n useInput((_input, key) => {\n if (key.escape) {\n nav.back();\n return;\n }\n if (key.rightArrow) setWindowIdx((i) => (i + 1) % DAY_WINDOWS.length);\n if (key.leftArrow) setWindowIdx((i) => (i - 1 + DAY_WINDOWS.length) % DAY_WINDOWS.length);\n });\n\n if (!sessions || !book) {\n return (\n <Box alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.stats.loading}</Text>\n </Box>\n );\n }\n\n if (sessions.length === 0) {\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.stats.none}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.stats.nonePractice}</Text>\n </Box>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>Esc {t.common.back}</Text>\n </Box>\n </Box>\n );\n }\n\n const days = DAY_WINDOWS[windowIdx]!;\n const streak = dailyStreak(sessions);\n const totalWords = sessions.reduce((a, s) => a + s.wordCount, 0);\n const totalErrors = sessions.reduce((a, s) => a + s.errors, 0);\n const totalMs = sessions.reduce((a, s) => a + s.durationMs, 0);\n const firstTryWords = sessions.reduce(\n (a, s) => a + (s.wordCount - Object.keys(s.perWordErrors).length),\n 0,\n );\n const overallWpm = totalMs > 0 ? Math.round((totalWords / (totalMs / 60000)) * 10) / 10 : 0;\n const overallAcc = totalWords === 0 ? 1 : firstTryWords / totalWords;\n\n const recent = sessions.slice(-5).reverse();\n const top = topN(book, 8);\n\n const agg = windowAggregate(sessions, days);\n const speedPct = agg.peakWpmLifetime > 0 ? Math.min(1, agg.avgWpm / agg.peakWpmLifetime) : 0;\n const accPctFrac = Math.min(1, agg.avgAccPct / 100);\n const sessionsPctFrac = days > 0 ? Math.min(1, agg.sessionsInWindow / days) : 0;\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Text bold color={PALETTE.accent}>\n {t.stats.title}\n </Text>\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.lifetime}</Text>\n <Box marginTop={1}>\n <Stat label={t.stats.sessions} value={String(sessions.length)} />\n <Stat label={t.stats.words} value={String(totalWords)} />\n <Stat label={t.stats.errors} value={String(totalErrors)} />\n <Stat label={t.stats.wpm} value={String(overallWpm)} accent />\n <Stat label={t.stats.accuracy} value={`${Math.round(overallAcc * 1000) / 10}%`} accent />\n <Stat label={t.stats.streak} value={`${streak}d`} accent />\n </Box>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.last(days)}</Text>\n <Box marginTop={1} flexDirection=\"column\" borderStyle=\"round\" borderColor={PALETTE.muted} paddingX={2} paddingY={0}>\n <MetricBar\n label={t.stats.bars.speed}\n frac={speedPct}\n valueText={`${agg.avgWpm} wpm`}\n pct={Math.round(speedPct * 100)}\n />\n <MetricBar\n label={t.stats.bars.accuracy}\n frac={accPctFrac}\n valueText={`${agg.avgAccPct}%`}\n pct={Math.round(accPctFrac * 100)}\n />\n <MetricBar\n label={t.stats.bars.sessions}\n frac={sessionsPctFrac}\n valueText={`${agg.sessionsInWindow} / ${days}`}\n pct={Math.round(sessionsPctFrac * 100)}\n />\n </Box>\n </Box>\n\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.recent}</Text>\n {recent.map((s, i) => (\n <RecentRow key={i} session={s} units={t.stats.recentUnits} />\n ))}\n </Box>\n\n {top.length > 0 && (\n <Box marginTop={2} flexDirection=\"column\">\n <Text color={PALETTE.muted}>{t.stats.topMistakes}</Text>\n {top.map(([word, entry]) => (\n <MistakeRow\n key={word}\n word={word}\n count={entry.count}\n dictIds={entry.dictIds}\n multiSuffix={t.stats.multiDictSuffix}\n />\n ))}\n </Box>\n )}\n\n <Box flexGrow={1} />\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.stats.footer}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction RecentRow({\n session,\n units,\n}: {\n session: SessionRecord;\n units: { words: string; errors: string; wpm: string };\n}) {\n const name = useDictName(session.dictId);\n const display = truncateName(name, 14);\n return (\n <Box>\n <Text color={PALETTE.muted}> {session.ts.replace('T', ' ').slice(0, 16)} </Text>\n <Text color={PALETTE.text}>{display.padEnd(14)}</Text>\n <Text color={PALETTE.muted}>\n {' '}ch{String(session.chapter + 1).padStart(3)} {session.mode.padEnd(9)} {String(session.wordCount).padStart(3)}{units.words} {session.errors}{units.errors} {computeWPM(session)}{units.wpm} {Math.round(accuracy(session) * 1000) / 10}%\n </Text>\n </Box>\n );\n}\n\nfunction MistakeRow({\n word,\n count,\n dictIds,\n multiSuffix,\n}: {\n word: string;\n count: number;\n dictIds: string[];\n multiSuffix: (n: number) => string;\n}) {\n const firstId = dictIds[0] ?? '';\n const firstName = useDictName(firstId);\n const suffix = dictIds.length > 1 ? multiSuffix(dictIds.length - 1) : '';\n return (\n <Box>\n <Text color={PALETTE.error}> {String(count).padStart(3)} </Text>\n <Text color={PALETTE.text}>{word.padEnd(20)}</Text>\n <Text color={PALETTE.muted}>\n {truncateName(firstName, 20)}\n {suffix}\n </Text>\n </Box>\n );\n}\n\nfunction Stat({ label, value, accent = false }: { label: string; value: string; accent?: boolean }) {\n return (\n <Box marginRight={3}>\n <Text color={PALETTE.muted}>{label} </Text>\n <Text bold color={accent ? PALETTE.accent : PALETTE.text}>\n {value}\n </Text>\n </Box>\n );\n}\n\nfunction MetricBar({\n label,\n frac,\n valueText,\n pct,\n}: {\n label: string;\n frac: number;\n valueText: string;\n pct: number;\n}) {\n const { stdout } = useStdout();\n const cols = stdout?.columns ?? 80;\n const barWidth = Math.max(20, Math.min(50, cols - 38));\n const clamped = Math.max(0, Math.min(1, frac));\n const filled = Math.round(barWidth * clamped);\n const empty = barWidth - filled;\n return (\n <Box>\n <Box width={9}>\n <Text color={PALETTE.muted}>{label}</Text>\n </Box>\n <Text color={PALETTE.accent}>{'━'.repeat(filled)}</Text>\n <Text color={PALETTE.muted}>{'─'.repeat(empty)}</Text>\n <Box width={14} marginLeft={2}>\n <Text color={PALETTE.text}>{valueText}</Text>\n </Box>\n <Box width={5} justifyContent=\"flex-end\">\n <Text color={PALETTE.muted}>{pct}%</Text>\n </Box>\n </Box>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","Box","Text","useInput","useStdout","jsx","jsxs","DAY_WINDOWS","StatsViewer","nav","useNav","t","useStrings","sessions","setSessions","useState","book","setBook","windowIdx","setWindowIdx","useEffect","s","b","loadSessions","loadMistakes","useInput","_input","key","i","Box","Text","PALETTE","days","streak","dailyStreak","totalWords","a","totalErrors","totalMs","firstTryWords","overallWpm","overallAcc","recent","top","topN","agg","windowAggregate","speedPct","accPctFrac","sessionsPctFrac","Stat","MetricBar","RecentRow","word","entry","MistakeRow","session","units","name","useDictName","display","truncateName","computeWPM","accuracy","count","dictIds","multiSuffix","firstId","firstName","suffix","label","value","accent","frac","valueText","pct","stdout","useStdout","cols","barWidth","clamped","filled","empty"]}
@@ -0,0 +1,2 @@
1
+ import{c as b}from"./chunk-TP77EGJ2.js";import"./chunk-ELWVQGDK.js";import{a as M}from"./chunk-UPA4JFCH.js";import{b as B,d as h}from"./chunk-2MRNI465.js";import{b as L,d as g,f as t}from"./chunk-QEX27D7F.js";import"./chunk-6QICLHIY.js";import{a as I}from"./chunk-6KRVNT2S.js";import{useEffect as S,useState as u}from"react";import{Box as n,Text as r,useInput as C}from"ink";import{readdir as $}from"fs/promises";import{Fragment as G,jsx as o,jsxs as d}from"react/jsx-runtime";async function N(){try{return(await $(I.dictsDir)).filter(e=>e.endsWith(".json")&&!e.endsWith(".meta.json")).map(e=>e.replace(/\.json$/,""))}catch{return[]}}function O(){let i=L(),e=g(),[m,x]=u(""),[l,w]=u([]),[W,H]=u({}),[j,A]=u(!0),[k,f]=u(0);S(()=>{(async()=>{let a=await N(),c=[];for(let s of a){let y=await b(s);if(y)for(let E of y)c.push({dictId:s,word:E})}w(c),H(await M()),A(!1)})()},[]);let T=m.toLowerCase().trim(),p=T?l.filter(a=>a.word.name.toLowerCase().includes(T)).slice(0,50):[];if(C((a,c)=>{if(c.escape){i.back();return}if(c.upArrow){f(s=>Math.max(0,s-1));return}if(c.downArrow){f(s=>Math.min(p.length-1,s+1));return}if(c.backspace||c.delete){x(s=>s.slice(0,-1)),f(0);return}a&&!c.ctrl&&!c.meta&&a.trim().length>0&&(x(s=>s+a),f(0))}),j)return o(n,{alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:o(r,{color:t.muted,children:e.word.indexing})});if(l.length===0)return d(n,{flexDirection:"column",alignItems:"center",justifyContent:"center",width:"100%",height:"100%",children:[o(r,{color:t.muted,children:e.word.none}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.pullFirst})}),o(n,{marginTop:2,children:d(r,{color:t.muted,children:["[Esc] ",e.common.back]})})]});let D=p[k];return d(n,{flexDirection:"column",paddingX:2,paddingY:1,width:"100%",height:"100%",children:[d(n,{children:[o(r,{bold:!0,color:t.accent,children:e.word.title}),o(n,{flexGrow:1}),o(r,{color:t.muted,children:e.word.countAcross(l.length)})]}),d(n,{marginTop:1,children:[o(r,{color:t.muted,children:"> "}),o(r,{color:t.text,children:m}),o(r,{color:t.accent,children:"_"})]}),d(n,{marginTop:1,flexGrow:1,children:[d(n,{flexDirection:"column",width:"40%",children:[p.map((a,c)=>o(q,{hit:a,active:c===k},`${a.dictId}-${a.word.name}-${c}`)),p.length===0&&T&&o(r,{color:t.muted,children:e.word.noMatches(m)})]}),o(n,{flexDirection:"column",width:"60%",paddingLeft:2,children:D&&o(v,{hit:D,book:W})})]}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:e.word.footer})})]})}function q({hit:i,active:e}){let m=B(i.dictId);return d(n,{children:[o(r,{color:e?t.accent:t.muted,children:e?"\u258C ":" "}),o(r,{bold:e,color:e?t.text:t.muted,children:i.word.name.padEnd(20)}),o(r,{color:t.muted,children:h(m,18)})]})}function v({hit:i,book:e}){let m=g(),x=B(i.dictId);return d(G,{children:[o(r,{bold:!0,color:t.text,children:i.word.name}),d(n,{marginTop:1,children:[i.word.usphone&&d(r,{color:t.muted,children:["US /",i.word.usphone,"/ "]}),i.word.ukphone&&d(r,{color:t.muted,children:["UK /",i.word.ukphone,"/"]})]}),o(n,{marginTop:1,flexDirection:"column",children:(i.word.trans??[]).map((l,w)=>d(r,{color:t.primary,children:["\xB7 ",l]},w))}),o(n,{marginTop:1,children:o(r,{color:t.muted,children:m.word.inDict(h(x,22))})}),e[i.word.name]&&o(n,{marginTop:1,children:o(r,{color:t.error,children:m.word.mistakes(e[i.word.name].count,e[i.word.name].lastSeen.slice(0,10))})})]})}export{O as WordLookup};
2
+ //# sourceMappingURL=WordLookup-UPEDLVKF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/screens/WordLookup.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport { useNav } from '../nav.js';\nimport { useStrings } from '../../i18n/context.js';\nimport { useDictName } from '../registry-context.js';\nimport { PALETTE } from '../components/BigWord.js';\nimport { readdir } from 'node:fs/promises';\nimport { paths } from '../../infra/paths.js';\nimport { loadLocalDictionary } from '../../infra/dict-downloader.js';\nimport { loadMistakes, type MistakeBook } from '../../domain/mistakes.js';\nimport type { Word } from '../../domain/dictionary.js';\nimport { truncateName } from '../../util/dict-name.js';\n\ntype Hit = { dictId: string; word: Word };\n\nasync function listLocalDictIds(): Promise<string[]> {\n try {\n const files = await readdir(paths.dictsDir);\n return files\n .filter((f) => f.endsWith('.json') && !f.endsWith('.meta.json'))\n .map((f) => f.replace(/\\.json$/, ''));\n } catch {\n return [];\n }\n}\n\nexport function WordLookup() {\n const nav = useNav();\n const t = useStrings();\n const [query, setQuery] = useState('');\n const [allWords, setAllWords] = useState<Hit[]>([]);\n const [book, setBook] = useState<MistakeBook>({});\n const [loading, setLoading] = useState(true);\n const [selected, setSelected] = useState(0);\n\n useEffect(() => {\n void (async () => {\n const ids = await listLocalDictIds();\n const collected: Hit[] = [];\n for (const id of ids) {\n const words = await loadLocalDictionary(id);\n if (!words) continue;\n for (const w of words) collected.push({ dictId: id, word: w });\n }\n setAllWords(collected);\n setBook(await loadMistakes());\n setLoading(false);\n })();\n }, []);\n\n const q = query.toLowerCase().trim();\n const filtered = q\n ? allWords.filter((h) => h.word.name.toLowerCase().includes(q)).slice(0, 50)\n : [];\n\n useInput((input, key) => {\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.backspace || key.delete) {\n setQuery((s) => s.slice(0, -1));\n setSelected(0);\n return;\n }\n if (input && !key.ctrl && !key.meta && input.trim().length > 0) {\n setQuery((s) => s + 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.word.indexing}</Text>\n </Box>\n );\n }\n\n if (allWords.length === 0) {\n return (\n <Box flexDirection=\"column\" alignItems=\"center\" justifyContent=\"center\" width=\"100%\" height=\"100%\">\n <Text color={PALETTE.muted}>{t.word.none}</Text>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.word.pullFirst}</Text>\n </Box>\n <Box marginTop={2}>\n <Text color={PALETTE.muted}>[Esc] {t.common.back}</Text>\n </Box>\n </Box>\n );\n }\n\n const current = filtered[selected];\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1} width=\"100%\" height=\"100%\">\n <Box>\n <Text bold color={PALETTE.accent}>{t.word.title}</Text>\n <Box flexGrow={1} />\n <Text color={PALETTE.muted}>{t.word.countAcross(allWords.length)}</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{'> '}</Text>\n <Text color={PALETTE.text}>{query}</Text>\n <Text color={PALETTE.accent}>_</Text>\n </Box>\n\n <Box marginTop={1} flexGrow={1}>\n <Box flexDirection=\"column\" width=\"40%\">\n {filtered.map((h, i) => (\n <HitRow key={`${h.dictId}-${h.word.name}-${i}`} hit={h} active={i === selected} />\n ))}\n {filtered.length === 0 && q && (\n <Text color={PALETTE.muted}>{t.word.noMatches(query)}</Text>\n )}\n </Box>\n\n <Box flexDirection=\"column\" width=\"60%\" paddingLeft={2}>\n {current && <Detail hit={current} book={book} />}\n </Box>\n </Box>\n\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.word.footer}</Text>\n </Box>\n </Box>\n );\n}\n\nfunction HitRow({ hit, active }: { hit: Hit; active: boolean }) {\n const name = useDictName(hit.dictId);\n return (\n <Box>\n <Text color={active ? PALETTE.accent : PALETTE.muted}>{active ? '▌ ' : ' '}</Text>\n <Text bold={active} color={active ? PALETTE.text : PALETTE.muted}>\n {hit.word.name.padEnd(20)}\n </Text>\n <Text color={PALETTE.muted}>{truncateName(name, 18)}</Text>\n </Box>\n );\n}\n\nfunction Detail({ hit, book }: { hit: Hit; book: MistakeBook }) {\n const t = useStrings();\n const name = useDictName(hit.dictId);\n return (\n <>\n <Text bold color={PALETTE.text}>{hit.word.name}</Text>\n <Box marginTop={1}>\n {hit.word.usphone && (\n <Text color={PALETTE.muted}>US /{hit.word.usphone}/ </Text>\n )}\n {hit.word.ukphone && (\n <Text color={PALETTE.muted}>UK /{hit.word.ukphone}/</Text>\n )}\n </Box>\n <Box marginTop={1} flexDirection=\"column\">\n {(hit.word.trans ?? []).map((tr, i) => (\n <Text key={i} color={PALETTE.primary}>· {tr}</Text>\n ))}\n </Box>\n <Box marginTop={1}>\n <Text color={PALETTE.muted}>{t.word.inDict(truncateName(name, 22))}</Text>\n </Box>\n {book[hit.word.name] && (\n <Box marginTop={1}>\n <Text color={PALETTE.error}>\n {t.word.mistakes(book[hit.word.name]!.count, book[hit.word.name]!.lastSeen.slice(0, 10))}\n </Text>\n </Box>\n )}\n </>\n );\n}\n"],"mappings":"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","names":["useEffect","useState","Box","Text","useInput","readdir","Fragment","jsx","jsxs","listLocalDictIds","readdir","paths","f","WordLookup","nav","useNav","t","useStrings","query","setQuery","useState","allWords","setAllWords","book","setBook","loading","setLoading","selected","setSelected","useEffect","ids","collected","id","words","loadLocalDictionary","w","loadMistakes","q","filtered","h","useInput","input","key","i","Box","Text","PALETTE","current","HitRow","Detail","hit","active","name","useDictName","truncateName","tr"]}
@@ -0,0 +1,2 @@
1
+ import{b as o}from"./chunk-ELWVQGDK.js";import{createContext as s,useCallback as f,useContext as C,useState as c}from"react";import{jsx as g}from"react/jsx-runtime";var n=s(null);function d({initialCfg:t,children:i}){let[p,r]=c(t),a=f(async e=>{r(e),await o(e)},[]);return g(n.Provider,{value:{cfg:p,setCfg:a},children:i})}function l(){let t=C(n);if(!t)throw new Error("useAppState must be used inside AppStateProvider");return t}export{d as a,l as b};
2
+ //# sourceMappingURL=chunk-2GTGXODM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/app-state.tsx"],"sourcesContent":["import { createContext, useCallback, useContext, useState, type ReactNode } from 'react';\nimport { saveConfig as persistConfig, type Config } from '../infra/config-store.js';\n\ntype AppState = {\n cfg: Config;\n setCfg: (next: Config) => Promise<void>;\n};\n\nconst AppStateContext = createContext<AppState | null>(null);\n\nexport function AppStateProvider({\n initialCfg,\n children,\n}: {\n initialCfg: Config;\n children: ReactNode;\n}) {\n const [cfg, setCfgState] = useState<Config>(initialCfg);\n const setCfg = useCallback(async (next: Config) => {\n setCfgState(next);\n await persistConfig(next);\n }, []);\n return <AppStateContext.Provider value={{ cfg, setCfg }}>{children}</AppStateContext.Provider>;\n}\n\nexport function useAppState(): AppState {\n const ctx = useContext(AppStateContext);\n if (!ctx) throw new Error('useAppState must be used inside AppStateProvider');\n return ctx;\n}\n"],"mappings":"wCAAA,OAAS,iBAAAA,EAAe,eAAAC,EAAa,cAAAC,EAAY,YAAAC,MAAgC,QAsBxE,cAAAC,MAAA,oBAdT,IAAMC,EAAkBC,EAA+B,IAAI,EAEpD,SAASC,EAAiB,CAC/B,WAAAC,EACA,SAAAC,CACF,EAGG,CACD,GAAM,CAACC,EAAKC,CAAW,EAAIC,EAAiBJ,CAAU,EAChDK,EAASC,EAAY,MAAOC,GAAiB,CACjDJ,EAAYI,CAAI,EAChB,MAAMC,EAAcD,CAAI,CAC1B,EAAG,CAAC,CAAC,EACL,OAAOX,EAACC,EAAgB,SAAhB,CAAyB,MAAO,CAAE,IAAAK,EAAK,OAAAG,CAAO,EAAI,SAAAJ,EAAS,CACrE,CAEO,SAASQ,GAAwB,CACtC,IAAMC,EAAMC,EAAWd,CAAe,EACtC,GAAI,CAACa,EAAK,MAAM,IAAI,MAAM,kDAAkD,EAC5E,OAAOA,CACT","names":["createContext","useCallback","useContext","useState","jsx","AppStateContext","createContext","AppStateProvider","initialCfg","children","cfg","setCfgState","useState","setCfg","useCallback","next","saveConfig","useAppState","ctx","useContext"]}
@@ -0,0 +1,2 @@
1
+ import{c as a}from"./chunk-6QICLHIY.js";import{createContext as l,useContext as d,useEffect as p,useState as y}from"react";import{jsx as R}from"react/jsx-runtime";var f=l(null);function v({children:t}){let[r,e]=y(null),[o,n]=y(new Map);return p(()=>{let i=!1;return(async()=>{try{let s=await a();if(i)return;let c=new Map;for(let u of s)c.set(u.id,u);e(s),n(c)}catch{i||(e([]),n(new Map))}})(),()=>{i=!0}},[]),R(f.Provider,{value:{registry:r,byId:o},children:t})}function x(){let t=d(f);if(!t)throw new Error("useRegistry must be used inside RegistryProvider");return t}function C(t){let{byId:r}=x();return t?r.get(t)?.name??t:""}var b=/\x1b\[[0-9;]*m/g;function m(t){return t.replace(b,"")}function g(t){let r=m(t),e=0;for(let o of r){let n=o.codePointAt(0);e+=n>11904&&n<64256?2:1}return e}function D(t,r){if(g(t)<=r)return t;let e="",o=0;for(let n of t){let i=n.codePointAt(0),s=i>11904&&i<64256?2:1;if(o+s>r-1)break;e+=n,o+=s}return e+"\u2026"}export{v as a,C as b,g as c,D as d};
2
+ //# sourceMappingURL=chunk-2MRNI465.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/registry-context.tsx","../src/util/text.ts","../src/util/dict-name.ts"],"sourcesContent":["import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';\nimport { loadRegistry } from '../infra/registry-store.js';\nimport type { Registry, DictionaryEntry } from '../domain/dictionary.js';\n\ntype RegistryContextValue = {\n registry: Registry | null;\n byId: Map<string, DictionaryEntry>;\n};\n\nconst RegistryContext = createContext<RegistryContextValue | null>(null);\n\nexport function RegistryProvider({ children }: { children: ReactNode }) {\n const [registry, setRegistry] = useState<Registry | null>(null);\n const [byId, setById] = useState<Map<string, DictionaryEntry>>(new Map());\n\n useEffect(() => {\n let cancelled = false;\n (async () => {\n try {\n const reg = await loadRegistry();\n if (cancelled) return;\n const map = new Map<string, DictionaryEntry>();\n for (const e of reg) map.set(e.id, e);\n setRegistry(reg);\n setById(map);\n } catch {\n if (!cancelled) {\n setRegistry([]);\n setById(new Map());\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, []);\n\n return (\n <RegistryContext.Provider value={{ registry, byId }}>\n {children}\n </RegistryContext.Provider>\n );\n}\n\nexport function useRegistry(): RegistryContextValue {\n const ctx = useContext(RegistryContext);\n if (!ctx) throw new Error('useRegistry must be used inside RegistryProvider');\n return ctx;\n}\n\nexport function useDictName(id: string | undefined): string {\n const { byId } = useRegistry();\n if (!id) return '';\n const entry = byId.get(id);\n return entry?.name ?? id;\n}\n","const ANSI_RE = /\\x1b\\[[0-9;]*m/g;\n\nexport function stripAnsi(s: string): string {\n return s.replace(ANSI_RE, '');\n}\n\nexport function visibleWidth(s: string): number {\n const plain = stripAnsi(s);\n let w = 0;\n for (const ch of plain) {\n const code = ch.codePointAt(0)!;\n w += code > 0x2e80 && code < 0xfb00 ? 2 : 1;\n }\n return w;\n}\n\nexport function pad(s: string, n: number): string {\n return s + ' '.repeat(Math.max(0, n - visibleWidth(s)));\n}\n\nexport function truncate(s: string, max: number): string {\n if (visibleWidth(s) <= max) return s;\n const plain = stripAnsi(s);\n let out = '';\n let w = 0;\n for (const ch of plain) {\n const code = ch.codePointAt(0)!;\n const cw = code > 0x2e80 && code < 0xfb00 ? 2 : 1;\n if (w + cw > max - 1) break;\n out += ch;\n w += cw;\n }\n return out + '…';\n}\n","import { visibleWidth } from './text.js';\n\nexport function truncateName(name: string, max: number): string {\n if (visibleWidth(name) <= max) return name;\n let out = '';\n let w = 0;\n for (const ch of name) {\n const code = ch.codePointAt(0)!;\n const cw = code > 0x2e80 && code < 0xfb00 ? 2 : 1;\n if (w + cw > max - 1) break;\n out += ch;\n w += cw;\n }\n return out + '…';\n}\n"],"mappings":"wCAAA,OAAS,iBAAAA,EAAe,cAAAC,EAAY,aAAAC,EAAW,YAAAC,MAAgC,QAsC3E,cAAAC,MAAA,oBA7BJ,IAAMC,EAAkBC,EAA2C,IAAI,EAEhE,SAASC,EAAiB,CAAE,SAAAC,CAAS,EAA4B,CACtE,GAAM,CAACC,EAAUC,CAAW,EAAIC,EAA0B,IAAI,EACxD,CAACC,EAAMC,CAAO,EAAIF,EAAuC,IAAI,GAAK,EAExE,OAAAG,EAAU,IAAM,CACd,IAAIC,EAAY,GAChB,OAAC,SAAY,CACX,GAAI,CACF,IAAMC,EAAM,MAAMC,EAAa,EAC/B,GAAIF,EAAW,OACf,IAAMG,EAAM,IAAI,IAChB,QAAWC,KAAKH,EAAKE,EAAI,IAAIC,EAAE,GAAIA,CAAC,EACpCT,EAAYM,CAAG,EACfH,EAAQK,CAAG,CACb,MAAQ,CACDH,IACHL,EAAY,CAAC,CAAC,EACdG,EAAQ,IAAI,GAAK,EAErB,CACF,GAAG,EACI,IAAM,CACXE,EAAY,EACd,CACF,EAAG,CAAC,CAAC,EAGHX,EAACC,EAAgB,SAAhB,CAAyB,MAAO,CAAE,SAAAI,EAAU,KAAAG,CAAK,EAC/C,SAAAJ,EACH,CAEJ,CAEO,SAASY,GAAoC,CAClD,IAAMC,EAAMC,EAAWjB,CAAe,EACtC,GAAI,CAACgB,EAAK,MAAM,IAAI,MAAM,kDAAkD,EAC5E,OAAOA,CACT,CAEO,SAASE,EAAYC,EAAgC,CAC1D,GAAM,CAAE,KAAAZ,CAAK,EAAIQ,EAAY,EAC7B,OAAKI,EACSZ,EAAK,IAAIY,CAAE,GACX,MAAQA,EAFN,EAGlB,CCvDA,IAAMC,EAAU,kBAET,SAASC,EAAUC,EAAmB,CAC3C,OAAOA,EAAE,QAAQF,EAAS,EAAE,CAC9B,CAEO,SAASG,EAAaD,EAAmB,CAC9C,IAAME,EAAQH,EAAUC,CAAC,EACrBG,EAAI,EACR,QAAWC,KAAMF,EAAO,CACtB,IAAMG,EAAOD,EAAG,YAAY,CAAC,EAC7BD,GAAKE,EAAO,OAAUA,EAAO,MAAS,EAAI,CAC5C,CACA,OAAOF,CACT,CCZO,SAASG,EAAaC,EAAcC,EAAqB,CAC9D,GAAIC,EAAaF,CAAI,GAAKC,EAAK,OAAOD,EACtC,IAAIG,EAAM,GACNC,EAAI,EACR,QAAWC,KAAML,EAAM,CACrB,IAAMM,EAAOD,EAAG,YAAY,CAAC,EACvBE,EAAKD,EAAO,OAAUA,EAAO,MAAS,EAAI,EAChD,GAAIF,EAAIG,EAAKN,EAAM,EAAG,MACtBE,GAAOE,EACPD,GAAKG,CACP,CACA,OAAOJ,EAAM,QACf","names":["createContext","useContext","useEffect","useState","jsx","RegistryContext","createContext","RegistryProvider","children","registry","setRegistry","useState","byId","setById","useEffect","cancelled","reg","loadRegistry","map","e","useRegistry","ctx","useContext","useDictName","id","ANSI_RE","stripAnsi","s","visibleWidth","plain","w","ch","code","truncateName","name","max","visibleWidth","out","w","ch","code","cw"]}
@@ -0,0 +1,4 @@
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
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,2 @@
1
+ import{a as s,c,e as u}from"./chunk-6KRVNT2S.js";import{z as r}from"zod";var l=r.object({id:r.string(),name:r.string(),description:r.string().default(""),category:r.string().default(""),tags:r.array(r.string()).default([]),url:r.string(),length:r.number().int().nonnegative().default(0),language:r.string().default("en"),languageCategory:r.string().default("en"),defaultPronIndex:r.number().int().optional()}),g=r.array(l),p=r.object({name:r.string(),trans:r.array(r.string()).default([]),usphone:r.string().optional(),ukphone:r.string().optional(),notation:r.string().optional()}).passthrough(),w=r.array(p);function x(a,o,t={}){let e=o.trim().toLowerCase();return a.filter(n=>t.category&&n.category!==t.category||t.language&&n.language!==t.language?!1:e?n.id.toLowerCase().includes(e)||n.name.toLowerCase().includes(e)||n.description.toLowerCase().includes(e)||n.category.toLowerCase().includes(e)||n.tags.some(y=>y.toLowerCase().includes(e)):!0)}import{readFile as f}from"fs/promises";import{join as d}from"path";var i=null;async function m(){if(i)return i;let a=await u(s.registry);if(a){let e=g.safeParse(a);if(e.success)return i=e.data,i;console.warn(`Ignoring corrupt user registry at ${s.registry}: ${e.error.message}`)}let o=await f(d(c(),"registry.snapshot.json"),"utf8");return i=g.parse(JSON.parse(o)),i}async function b(a){return(await m()).find(t=>t.id===a)}export{w as a,x as b,m as c,b as d};
2
+ //# sourceMappingURL=chunk-6QICLHIY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/domain/dictionary.ts","../src/infra/registry-store.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const DictionaryEntrySchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string().default(''),\n category: z.string().default(''),\n tags: z.array(z.string()).default([]),\n url: z.string(),\n length: z.number().int().nonnegative().default(0),\n language: z.string().default('en'),\n languageCategory: z.string().default('en'),\n defaultPronIndex: z.number().int().optional(),\n});\n\nexport type DictionaryEntry = z.infer<typeof DictionaryEntrySchema>;\n\nexport const RegistrySchema = z.array(DictionaryEntrySchema);\nexport type Registry = DictionaryEntry[];\n\nexport const WordSchema = z\n .object({\n name: z.string(),\n trans: z.array(z.string()).default([]),\n usphone: z.string().optional(),\n ukphone: z.string().optional(),\n notation: z.string().optional(),\n })\n .passthrough();\n\nexport type Word = z.infer<typeof WordSchema>;\n\nexport const WordArraySchema = z.array(WordSchema);\n\nexport function filterRegistry(\n registry: Registry,\n query: string,\n opts: { category?: string; language?: string } = {},\n): Registry {\n const q = query.trim().toLowerCase();\n return registry.filter((d) => {\n if (opts.category && d.category !== opts.category) return false;\n if (opts.language && d.language !== opts.language) return false;\n if (!q) return true;\n return (\n d.id.toLowerCase().includes(q) ||\n d.name.toLowerCase().includes(q) ||\n d.description.toLowerCase().includes(q) ||\n d.category.toLowerCase().includes(q) ||\n d.tags.some((t) => t.toLowerCase().includes(q))\n );\n });\n}\n","import { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { RegistrySchema, type Registry, type DictionaryEntry } from '../domain/dictionary.js';\nimport { paths, packageAssetsDir } from './paths.js';\nimport { readJson } from './fs-store.js';\n\nlet cached: Registry | null = null;\n\nexport async function loadRegistry(): Promise<Registry> {\n if (cached) return cached;\n\n const userOverride = await readJson<unknown>(paths.registry);\n if (userOverride) {\n const parsed = RegistrySchema.safeParse(userOverride);\n if (parsed.success) {\n cached = parsed.data;\n return cached;\n }\n console.warn(`Ignoring corrupt user registry at ${paths.registry}: ${parsed.error.message}`);\n }\n\n const bundled = await readFile(join(packageAssetsDir(), 'registry.snapshot.json'), 'utf8');\n const parsed = RegistrySchema.parse(JSON.parse(bundled));\n cached = parsed;\n return cached;\n}\n\nexport async function findEntry(id: string): Promise<DictionaryEntry | undefined> {\n const reg = await loadRegistry();\n return reg.find((d) => d.id === id);\n}\n"],"mappings":"iDAAA,OAAS,KAAAA,MAAS,MAEX,IAAMC,EAAwBD,EAAE,OAAO,CAC5C,GAAIA,EAAE,OAAO,EACb,KAAMA,EAAE,OAAO,EACf,YAAaA,EAAE,OAAO,EAAE,QAAQ,EAAE,EAClC,SAAUA,EAAE,OAAO,EAAE,QAAQ,EAAE,EAC/B,KAAMA,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,EACpC,IAAKA,EAAE,OAAO,EACd,OAAQA,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC,EAChD,SAAUA,EAAE,OAAO,EAAE,QAAQ,IAAI,EACjC,iBAAkBA,EAAE,OAAO,EAAE,QAAQ,IAAI,EACzC,iBAAkBA,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,CAC9C,CAAC,EAIYE,EAAiBF,EAAE,MAAMC,CAAqB,EAG9CE,EAAaH,EACvB,OAAO,CACN,KAAMA,EAAE,OAAO,EACf,MAAOA,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC,EACrC,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,SAAUA,EAAE,OAAO,EAAE,SAAS,CAChC,CAAC,EACA,YAAY,EAIFI,EAAkBJ,EAAE,MAAMG,CAAU,EAE1C,SAASE,EACdC,EACAC,EACAC,EAAiD,CAAC,EACxC,CACV,IAAMC,EAAIF,EAAM,KAAK,EAAE,YAAY,EACnC,OAAOD,EAAS,OAAQI,GAClBF,EAAK,UAAYE,EAAE,WAAaF,EAAK,UACrCA,EAAK,UAAYE,EAAE,WAAaF,EAAK,SAAiB,GACrDC,EAEHC,EAAE,GAAG,YAAY,EAAE,SAASD,CAAC,GAC7BC,EAAE,KAAK,YAAY,EAAE,SAASD,CAAC,GAC/BC,EAAE,YAAY,YAAY,EAAE,SAASD,CAAC,GACtCC,EAAE,SAAS,YAAY,EAAE,SAASD,CAAC,GACnCC,EAAE,KAAK,KAAMC,GAAMA,EAAE,YAAY,EAAE,SAASF,CAAC,CAAC,EANjC,EAQhB,CACH,CCpDA,OAAS,YAAAG,MAAgB,cACzB,OAAS,QAAAC,MAAY,OAKrB,IAAIC,EAA0B,KAE9B,eAAsBC,GAAkC,CACtD,GAAID,EAAQ,OAAOA,EAEnB,IAAME,EAAe,MAAMC,EAAkBC,EAAM,QAAQ,EAC3D,GAAIF,EAAc,CAChB,IAAMG,EAASC,EAAe,UAAUJ,CAAY,EACpD,GAAIG,EAAO,QACT,OAAAL,EAASK,EAAO,KACTL,EAET,QAAQ,KAAK,qCAAqCI,EAAM,QAAQ,KAAKC,EAAO,MAAM,OAAO,EAAE,CAC7F,CAEA,IAAME,EAAU,MAAMC,EAASC,EAAKC,EAAiB,EAAG,wBAAwB,EAAG,MAAM,EAEzF,OAAAV,EADeM,EAAe,MAAM,KAAK,MAAMC,CAAO,CAAC,EAEhDP,CACT,CAEA,eAAsBW,EAAUC,EAAkD,CAEhF,OADY,MAAMX,EAAa,GACpB,KAAMY,GAAMA,EAAE,KAAOD,CAAE,CACpC","names":["z","DictionaryEntrySchema","RegistrySchema","WordSchema","WordArraySchema","filterRegistry","registry","query","opts","q","d","t","readFile","join","cached","loadRegistry","userOverride","readJson","paths","parsed","RegistrySchema","bundled","readFile","join","packageAssetsDir","findEntry","id","d"]}
@@ -0,0 +1,2 @@
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