Skip to content

A letter of the Schreibtafel in detail: stroke order, Ansatz and Auslauf, two tempi, look-alikes, the word (/tafel?g=<key>) - #457

Merged
MarkusNeusinger merged 3 commits into
mainfrom
claude/tafel-buchstabe-detail
Aug 30, 2026
Merged

A letter of the Schreibtafel in detail: stroke order, Ansatz and Auslauf, two tempi, look-alikes, the word (/tafel?g=<key>)#457
MarkusNeusinger merged 3 commits into
mainfrom
claude/tafel-buchstabe-detail

Conversation

@MarkusNeusinger

Copy link
Copy Markdown
Owner

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:

  • Stroke order: numbered starts on the finished form, plus a stepper („Zug 2 von 3") that shows the letter after stroke n with the strokes still to come as a hairline.
  • Ansatz and Auslauf of the Übergang as hollow rings on the form.
  • Two tempi: the live write-in at normal speed and „langsam" (3×).
  • Look-alikes: the documented confusables written beside it (the shared SpecimenStrip over lib/lesarten's table — n → u, e, m; ſ → f, s; s → ſ …).
  • „im Wort sehen": a jump into the Federprobe with the shortest bank word that shows the letter as written (a non-final s for the ſ, a final s for the round s, an initial capital).

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 by letterDetail.test.ts); strings in locales/de/tafel.ts under detail; 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-frontend on 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=longs switches to the written view and scrolls to the detail; desktop + mobile viewports, console clean. tsc, eslint, vitest green; prerender regenerated (drift test passes).

This branch was stacked on #454 (merged) and is rebased onto main.

Copilot AI lite review requested due to automatic review settings August 29, 2026 23:23

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 LetterDetail UI plus pure helper logic (letterDetail.ts) and unit tests.
  • Wire ?g= selection through TafelView + WrittenSheet (tap selects + highlights the active letter).
  • Update German locale copy, prerendered /tafel HTML, 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.

Comment thread app/src/sections/tafel/TafelView.tsx
Comment thread app/src/sections/tafel/TafelView.tsx
@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

Both addressed in the last push: the scroll watch calls stop() on its own exit (timeout or settled offset), so the wheel/touchmove/keydown listeners never outlive it; and view now follows a ?g= change while the section stays mounted (last-seen-key state adjusted during render → written view), so an in-app navigation to /tafel?g=… opens the detail as a fresh load does.

MarkusNeusinger added a commit that referenced this pull request Aug 29, 2026
… 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.
Copilot AI review requested due to automatic review settings August 29, 2026 23:32

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread app/src/sections/tafel/TafelView.tsx
MarkusNeusinger and others added 3 commits August 30, 2026 07:41
…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.
Copilot AI review requested due to automatic review settings August 30, 2026 05:42
@MarkusNeusinger
MarkusNeusinger force-pushed the claude/tafel-buchstabe-detail branch from bd38dd6 to 7bb0679 Compare August 30, 2026 05:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 / setView inside an if in 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. Synchronize view with selectedSlot in 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

@MarkusNeusinger

Copy link
Copy Markdown
Owner Author

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 selectedKey !== seenKey, so it runs once per key change and React re-renders immediately without committing — no loop, no stale frame. An effect would do the same one render later and trips the repo's react-hooks/set-state-in-effect rule.

@MarkusNeusinger
MarkusNeusinger merged commit 28ce5c6 into main Aug 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants