From 972e11718cb8e6507d7d527d271a24d14d4a1ec9 Mon Sep 17 00:00:00 2001 From: kitadmin Date: Thu, 30 Apr 2026 03:42:32 +0000 Subject: [PATCH] feat(onboarding): 5 clickable mnemonic suggestions per letter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dot words: ≤3 letters (was ≤2) — two-letter words like "go/it" were too limiting - Dash words: ≥4 letters (unchanged in practice) - Added DOT_WORDS and DASH_WORDS banks (~60 and ~200 words) - suggestPhrases() generates 5 random valid suggestions per letter - MnemonicBuilder shows clickable suggestion pills; "↻ new" refreshes them - Selected suggestion highlights in gold; user can still type their own Co-Authored-By: Claude Sonnet 4.6 --- client/src/components/MnemonicBuilder.tsx | 76 ++++++++++++++++++++--- client/src/lib/morse.test.ts | 56 +++++++++++++++-- client/src/lib/morse.ts | 68 +++++++++++++++++++- 3 files changed, 185 insertions(+), 15 deletions(-) diff --git a/client/src/components/MnemonicBuilder.tsx b/client/src/components/MnemonicBuilder.tsx index cc31eff..b0029e4 100644 --- a/client/src/components/MnemonicBuilder.tsx +++ b/client/src/components/MnemonicBuilder.tsx @@ -1,5 +1,5 @@ -import { useState } from 'react' -import { NOVICE_LETTERS, MORSE, validatePhrase } from '../lib/morse' +import { useState, useEffect } from 'react' +import { NOVICE_LETTERS, MORSE, validatePhrase, suggestPhrases } from '../lib/morse' type Props = { token: string @@ -13,6 +13,7 @@ export function MnemonicBuilder({ token, onComplete }: Props) { const [phrase, setPhrase] = useState('') const [saving, setSaving] = useState(false) const [error, setError] = useState(null) + const [suggestions, setSuggestions] = useState([]) const letter = LETTERS[index] const pattern = MORSE[letter] ?? '' @@ -20,6 +21,12 @@ export function MnemonicBuilder({ token, onComplete }: Props) { const display = pattern.split('').map(c => c === '.' ? '·' : '—').join(' ') const validation = phrase.trim() ? validatePhrase(phrase.trim(), letter) : null + useEffect(() => { + setSuggestions(suggestPhrases(letter, 5)) + setPhrase('') + setError(null) + }, [letter]) + async function save() { if (!validation?.valid) return setSaving(true) @@ -47,8 +54,6 @@ export function MnemonicBuilder({ token, onComplete }: Props) { onComplete() } else { setIndex(i => i + 1) - setPhrase('') - setError(null) } } @@ -88,14 +93,14 @@ export function MnemonicBuilder({ token, onComplete }: Props) { Create your mnemonic phrase:

- {pattern.length} word{pattern.length !== 1 ? 's' : ''} — short words (1-2 letters) for dots, - long words (3+ letters) for dashes. + {pattern.length} word{pattern.length !== 1 ? 's' : ''} — + {' '}≤3 letters for dots · (short), 4+ letters for dashes — (long).

{ setPhrase(e.target.value); setError(null) }} - placeholder={`${pattern.length} word${pattern.length !== 1 ? 's' : ''}, e.g. "${examplePhrase(pattern)}"`} + placeholder={`e.g. "${examplePhrase(pattern)}"`} disabled={saving} style={{ marginBottom: '0.5rem' }} /> @@ -115,6 +120,61 @@ export function MnemonicBuilder({ token, onComplete }: Props) {

{error}

)} + {/* Suggestions */} +
+
+ + Suggestions — click to use: + + +
+
+ {suggestions.map(s => ( + + ))} +
+
+ {/* Actions */}