Skip to content

feat(ktt): Kasimir Teaches TLAs acronym trainer (flag OFF) - #2262

Merged
jung-thomas merged 29 commits into
DEVfrom
worktree-ktt-kasimir
Sep 12, 2026
Merged

jung-thomas merged 29 commits into
DEVfrom
worktree-ktt-kasimir

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

KTT — "Kasimir Teaches TLAs"

A tongue-in-cheek, Duolingo-style trainer at /explore/ktt/ teaching SAP three-letter acronyms, narrated by an animated Professor Kasimir. v1 = 4 units / 12 lessons / 48 acronyms; story-heavy core loop with quiz drills.

Built via subagent-driven development from docs/superpowers/plans/2026-09-11-ktt-kasimir-teaches-tlas.md (spec alongside it). 16 tasks, each spec+quality reviewed; one broad final whole-branch review (opus) + one scoped fix pass.

Architecture

  • Hugo page mounts one Vue 3 island (hugo-apps/src/ktt/) talking to a thin CAP service (srv/ktt-service.*).
  • Reuses existing TaskRecords/TaskType completion machinery via a new KTT_LESSON type — no new progress entity. KttLessons is a lightweight catalog entity so completion rows join to title/slug in MyCompletions.
  • Hybrid progress: localStorage + HANA when logged in (mergeProgress parity client↔server).
  • Lesson content generated at build time (scripts/generate-ktt-lessons.ts → hugo/data/ktt_lessons.json and a committed db/data/…-KttLessons.csv seed), human-grounded facts.

Feature gating

  • KTT_ENABLED DB feature flag, default OFF. Toggles without redeploy.
  • Fails closed with the flag off: write actions + the Lessons READ return 503; the island probes on mount and renders a coming-soon state without mounting the drill engine or touching localStorage. Nav entry stays (static build-time link).

Testing

  • Full unit suite green (9767 tests); KTT server suite + island tests + user-progress all pass, incl. a seed-relying regression test.
  • cds deploy --to sqlite::memory: clean (KttLessons.csv loads).
  • Self-skipping Playwright e2e smoke (test/e2e/ktt.spec.ts) runs post-deploy only.

Follow-ups (deliberately out of this PR)

  • Banter adapter (srv/lib/ktt/banter.js / srv/ktt-service.js): replace the unverified live-AI shape with OrchestrationClient.chatCompletion() per srv/lib/category-classifier-llm.js. Currently fail-open → users always get scripted fallback lines, never an error. Needs aiOrchestration binding + chat-settings deployment-ID.
  • Kasimir SVG collar z-order (KasimirStage.vue): move <g id="collar"> after the head group before flipping KTT_ENABLED ON (visual polish, needs live-browser iteration).
  • Deferred v1-scope items (not implemented): unit-level rollup as Missions/Groups; endless mode; per-acronym STEP rows; NGDS autosend; homepage/gameboard tie-ins.

Before flipping the flag ON

Verify on DEV first, then address the collar z-order polish item.

🤖 Generated with Claude Code

- Adds KTT_LESSON to the standalone type TaskType enum (db/schema.cds:19)
- Adds KTT_LESSON to the inline TaskRecords.taskType enum (db/schema.cds:195)
- Creates db/ktt.cds with KttLessons : cuid entity (legacyId, slug, unitId, title, order)
- Registers KttLessons in db/persistence.cds for hdbmigrationtable deploy
- Adds using from './ktt' to schema.cds for consistency with peer files
- Adds test/unit/ktt-model.test.js guarding enum + entity shape
- Add srv/lib/ktt/merge.js: pure mergeProgress(local, remote) helper
  (sorted union of mastered slugs, numeric max of xp/streak)
- Wire syncProgress action handler in ktt-service.js: reconstructs remote
  state from KTT_LESSON TaskRecords, merges with localJson, back-fills any
  locally-mastered lessons not yet in HANA via the Task-2 idempotency guard
- Add KttLessons to cds.entities destructure in init()
- Test: ktt-merge.test.js (pure unit), ktt-complete.test.js (served-mode
  syncProgress + no-double-insert assertion)
Extends getMyCompletedTutorials in srv/lib/user-progress.js to include
KTT_LESSON TaskRecords alongside TUTORIAL/PUZZLE/PETOBERFEST. Mirrors the
existing PETOBERFEST path: adds KTT_LESSON to the taskType IN list, collects
kttIds, fetches KttLessons metadata via SELECT.from(KttLessons), and maps
records to { kind: 'ktt', slug, title, completionDate, attemptNumber }.
No new module imports — KttLessons comes from cds.entities().
…r sync

- Lesson.vue: drives Task 9 engine, story/drill beats, KasimirStage mood
  binding (teaching/thinking/correct/wrong/celebrate), emits complete(lessonId)
- Results.vue: XP display, banter line, continue/back to map
- progress.ts: loadLocal/saveLocal (localStorage), mergeProgress (union mastered,
  max xp/streak) — exact parity with srv/lib/ktt/merge.js
- server.ts: isAuthenticated (checks body.authenticated NOT r.ok — Akamai gotcha),
  completeLesson, syncProgress, fetchBanter; all best-effort, native fetch
- App.vue: full screen state machine landing→map→lesson→results; saves progress
  locally first, then best-effort server sync if authenticated, banter opportunistic
- Tests: progress.test.ts (6), Lesson.test.ts (8); all 26 KTT island tests pass
- cp-list audit: no srv/lib/ktt/* reachable from content-store.js — no mta.yaml change
…Data banter URL, Fisher-Yates shuffle

1A: Lesson.vue emits complete(lessonId, sessionXp) — real engine XP, not +10 floor.
    defineEmits updated to (e:'complete', lessonId:string, xp:number)=>void.
1B: App.vue captures prevXp before merge; lastXp = updated.xp - prevXp (earned delta).
    Results now shows the session delta on every lesson, not cumulative.
2:  screen.value='results' moved ABOVE auth probe; isAuthenticated+completeLesson+syncProgress
    run as fire-and-forget .then() chain — Results screen appears immediately.
3:  fetchBanter URL changed to OData v4 function syntax: banter(context='...').
4:  drillChoices uses Fisher-Yates shuffle; Lesson.test.ts selects by text not index (shuffle-safe).
Tests: +2 new (two-drill 20 XP; emitted XP is delta not floor) — 28/28 pass (6 files).
Adds /explore/ktt/ route: _index.md content file (type:explore, layout:ktt)
and ktt.html layout with SSR unit list, #ktt-mount data attributes, noscript
fallback, and island-src.html partial for the content-hashed bundle reference.

Data-attribute escaping: double-quoted attribute + Hugo auto-escaping
(established pattern matching verb-spine.html); apostrophes in Kasimir's
dialog round-trip correctly through &#39; → ' → JSON.parse().
Extend generate-ktt-lessons.ts to also emit db/data/...-KttLessons.csv
(deterministic UUID per legacyId; slug == lesson id). Without this the
catalog shipped empty and every KTT completion was dropped from
MyCompletions (spec 7.1/7.2). Reconcile ktt-complete to clear the now-
seeded table before its fixture; add ktt-seed regression that relies on
the seed (no manual insert).
Gate the readonly Lessons projection with a before('READ') 503 reject so
the page fails closed via the runtime DB flag (spec 7.7). Island probes
isKttEnabled() on mount and renders a coming-soon state without mounting
the drill engine or touching localStorage when disabled. Cover both
states in App.test and add READ-gate assertions to ktt-flag-gate.
- server.ts completeLesson/syncProgress now use csrfFetch (@shared/csrf-fetch)
  to satisfy check-csrf-clients static guard for mutating requests
- regenerate db/last-dev/csn.json via cds build --production to include
  KttLessons (was missing → cds-build-staging check failed)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
jwtVerify used the real clock while the fixture signs exp = now(2026-09-11)+24h,
so the suite went red for every PR from 2026-09-12 on. Pass currentDate:
new Date(base.now) so exp is checked against the frozen fixture time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@jung-thomas
jung-thomas marked this pull request as ready for review September 12, 2026 16:08
@jung-thomas
jung-thomas merged commit d19b816 into DEV Sep 12, 2026
10 checks passed
@jung-thomas
jung-thomas deleted the worktree-ktt-kasimir branch September 12, 2026 16:08
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.

1 participant