Prompt file imported from mnpham2101/JapaneseLearn (
.github/prompts/speckit.subtask.6-S-4-2.prompt.md). Copyright stays with the author.
Subtask 6.S.4.2 — Rust: propagate is_kanji through exercise_generator and vocabulary lib
Agent: rust-developer
Parent task: 6.S.4
Depends on: 6.S.4.1
Commit: one commit, one logical change
Subtask Goals
After this commit, kanji-front flashcards generated by FlashcardExerciseTransformer carry is_kanji: true. The field flows through FlashcardCardData → vocabulary lib.rs type conversion → FlashcardModel Slint struct. Existing JSON stacks round-trip correctly (is_kanji defaults to false on deserialization if absent). All existing tests pass; kanji-word tests assert is_kanji is correct.
Subtask Technical Approach
Files to be Changed
| File | Action | What changes |
|---|---|---|
lib/exercise_generator/src/models.rs |
modify | Add is_kanji: bool to FlashcardCardData |
lib/exercise_generator/src/flashcard_transformer.rs |
modify | Set is_kanji: true on kanji-front card |
lib/flashcard/src/lib.rs |
modify | Add is_kanji to CardData; update conversion functions |
lib/vocabulary/src/lib.rs |
modify | Set is_kanji in FlashcardCardData → FlashcardModel conversion |
exercise_generator/src/models.rs
#[derive(Debug, Clone)]
pub struct FlashcardCardData {
pub front: String,
pub back: String,
pub known: bool,
pub is_kanji: bool, // ← add this field
}
flashcard_transformer.rs — make_cards()
In the make_cards() function, the spelling card gets is_kanji: false and the kanji card gets is_kanji: true:
fn make_cards(word: &VocabularyWord) -> Vec<FlashcardCardData> {
let back = format_explanation(word);
let mut cards = vec![FlashcardCardData {
front: word.spelling.clone(),
back: back.clone(),
known: false,
is_kanji: false, // spelling front — not kanji
}];
if let Some(kanji) = &word.kanji {
cards.push(FlashcardCardData {
front: kanji.clone(),
back,
known: false,
is_kanji: true, // kanji front — large brush font
});
}
cards
}
Update existing inline tests to assert is_kanji:
#[test]
fn spelling_only_produces_one_card() {
// ...
assert!(!stacks[0].cards[0].is_kanji);
}
#[test]
fn kanji_word_produces_two_cards_sharing_same_back() {
// ...
assert!(!stacks[0].cards[0].is_kanji); // spelling card
assert!(stacks[0].cards[1].is_kanji); // kanji card
}
lib/flashcard/src/lib.rs — CardData persistence
Add is_kanji to the JSON persistence struct with a serde default so existing saved stacks (without the field) deserialize correctly:
#[derive(serde::Serialize, serde::Deserialize)]
struct CardData {
jap_obj: String,
explanation: String,
known: bool,
#[serde(default)]
is_kanji: bool, // ← add with default=false for backwards compat
}
Update to_stack_data() to copy the field:
.map(|c| CardData {
jap_obj: c.jap_obj.to_string(),
explanation: c.explanation.to_string(),
known: c.known,
is_kanji: c.is_kanji, // ← add
})
Update from_stack_data() to populate the Slint model field:
.map(|c| flashcard::FlashcardModel {
jap_obj: c.jap_obj.into(),
explanation: c.explanation.into(),
known: c.known,
is_kanji: c.is_kanji, // ← add
})
lib/vocabulary/src/lib.rs — FlashcardCardData → FlashcardModel
Locate the conversion in the on_generate_exercises_clicked handler where FlashcardCardData is mapped to flashcard::FlashcardModel. Add is_kanji:
flashcard::FlashcardModel {
jap_obj: card.front.clone().into(),
explanation: card.back.clone().into(),
known: card.known,
is_kanji: card.is_kanji, // ← add
}
Verification
cargo test -p exercise_generator # all 4 transformer tests pass
cargo build --bin japanese_learn # zero errors
cargo clippy --bin japanese_learn # zero warnings
cargo test -p flashcard-tests # all 8 CRUD tests pass
Generate flashcards from a vocabulary lesson that has kanji words. Inspect the resulting flashcard stack: kanji-front cards should display extra-large text with MS Mincho font.