A letter of the Schreibtafel in detail: stroke order, Ansatz and Auslauf, two tempi, look-alikes, the word (/tafel?g=<key>) - #457
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new scroll-to-detail RAF watcher leaks event listeners and the view state doesn’t reliably react to ?g= changes while mounted.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a shareable letter-detail panel to the public Schreibtafel (/tafel?g=<key>), extending the existing “tap to replay” written sheet with an in-page drill-down (stroke order stepper, entry/exit markers, tempo toggle, look-alike specimens, and a jump into Federprobe). This fits into the frontend’s public learning surfaces under app/src/sections/tafel/ and keeps the logic split between pure helpers + UI.
Changes:
- Add
LetterDetailUI plus pure helper logic (letterDetail.ts) and unit tests. - Wire
?g=selection throughTafelView+WrittenSheet(tap selects + highlights the active letter). - Update German locale copy, prerendered
/tafelHTML, and docs/changelog entries for the new deep-link behavior.
File summaries
| File | Description |
|---|---|
| docs/reference/frontend-stack.md | Documents /tafel[?g=<key>] and the new detail-panel behavior. |
| CHANGELOG.md | Adds an [Unreleased] entry describing /tafel?g=<key> letter detail. |
| app/src/sections/tafel/WrittenSheet.tsx | Adds selection plumbing (selectedKey, onSelect) and highlights the selected cell. |
| app/src/sections/tafel/TafelView.tsx | Introduces ?g= routing, renders LetterDetail, and adds scroll-into-view behavior. |
| app/src/sections/tafel/LetterDetail.tsx | New detail panel implementation (stroke order view, tempo toggle, look-alikes, “in word” jump). |
| app/src/sections/tafel/letterDetail.ts | New pure helper logic: stroke starts/count, look-alike mapping, example word selection. |
| app/src/sections/tafel/letterDetail.test.ts | Vitest coverage for the pure helper logic. |
| app/src/locales/de/tafel.ts | Adds/updates German strings for the detail panel and tap hint. |
| app/src/lib/seo/prerender.ts | Includes the new detail hint in the prerender body. |
| app/prerender/tafel.html | Regenerated prerendered HTML including the new hint line. |
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Both addressed in the last push: the scroll watch calls |
… a letter chosen while mounted switches to the written view Review round on #457: the requestAnimationFrame watch now calls stop() on its own exit (timeout or settled offset) instead of leaving the wheel/touchmove/keydown listeners attached; and `view` follows a ?g= change during an in-app navigation (state adjusted during render from the last seen key), so the detail panel always appears.
There was a problem hiding this comment.
🟡 Changes recommended
TafelView currently performs a state update during render, which can trigger React warnings and unstable rendering behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 10/10 changed files
- Comments generated: 1
- Review effort level: Lite
…auf, two tempi, look-alikes, the word (/tafel?g=<key>) Tapping a written letter on the Sütterlin sheet still re-writes it in place — and now opens it below the sheet: the stroke order as numbered starts on the finished form with a stepper that shows the letter after stroke n (the strokes to come as a hairline), the Ansatz and Auslauf of the Übergang as hollow rings, the live write-in at two tempi, the documented look-alikes written beside it (SpecimenStrip over lib/lesarten's table) and „im Wort sehen" — a jump into the Federprobe with the shortest bank word that shows the letter as written. The key rides the URL like the admin's focus (shareable; a deep link switches the sheet to the written view and keeps the detail aligned while the rows load, watching the detail's own offset frame by frame — a ResizeObserver on the body misses the rows' re-scale). Vision goal 3, „Buchstaben in Aktion"; the ligatures it names are not traced in the public source yet and so have no detail. Website audit 2026-08-29, item 5/8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012TVu1uouGhsjKCEVhWBinX
…h the PDF and the detail hint) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012TVu1uouGhsjKCEVhWBinX
… a letter chosen while mounted switches to the written view Review round on #457: the requestAnimationFrame watch now calls stop() on its own exit (timeout or settled offset) instead of leaving the wheel/touchmove/keydown listeners attached; and `view` follows a ?g= change during an in-app navigation (state adjusted during render from the last seen key), so the detail panel always appears.
bd38dd6 to
7bb0679
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
It contains a React anti-pattern (state updates during render) that can cause warnings and unnecessary re-renders in TafelView.tsx.
Review details
Suppressed comments (1)
app/src/sections/tafel/TafelView.tsx:240
- This updates state during render (
setSeenKey/setViewinside anifin the component body). React can warn (“Cannot update a component while rendering a different component”) and it can lead to extra renders or loops; it also runs in every GrundtafelSection even when the selected key isn’t on that sheet. SynchronizeviewwithselectedSlotin an effect instead.
const [seenKey, setSeenKey] = useState<string | null>(selectedKey);
if (selectedKey !== seenKey) {
setSeenKey(selectedKey);
if (selectedSlot) setView('written');
}
- Files reviewed: 10/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Kept as is: this is React's own "adjusting state when a prop changes" pattern (react.dev, You Might Not Need an Effect → "Storing information from previous renders"): the set is guarded by |
What
/tafel?g=<key>— a letter of the Schreibtafel in detail. Tapping a written letter on the Sütterlin sheet still re-writes it in place, and now opens it below the sheet:SpecimenStripoverlib/lesarten's table — n → u, e, m; ſ → f, s; s → ſ …).The key rides the URL like the admin's focus, so a letter is shareable; a deep link switches the sheet to the written view and scrolls to the detail once the sheet has settled. Pure helpers live in
sections/tafel/letterDetail.ts(pinned byletterDetail.test.ts); strings inlocales/de/tafel.tsunderdetail; the Tafel prerender carries the hint line.Why
Website audit 2026-08-29, item 5/8 — vision goal 3, „Buchstaben in Aktion": the Schreibtafel showed the finished forms and the write-in, but nothing a learner could step through.
Not in scope
The ligatures the look-alike table names (ch, ck, ſt, tz, qu) are not traced in the public source yet, so they have no detail; the page says so instead of showing an empty panel.
Verification
/verify-frontendon the dev server against the deployed API: tap a letter → detail opens and scrolls into view; stepper forward/back; tempo toggle; look-alike strip renders (and withdraws when nothing is writable); „im Wort sehen" lands in the Federprobe with the word; deep link?g=longsswitches to the written view and scrolls to the detail; desktop + mobile viewports, console clean.tsc,eslint,vitestgreen; prerender regenerated (drift test passes).This branch was stacked on #454 (merged) and is rebased onto
main.