diff --git a/README.md b/README.md index cd73518..d39581e 100644 --- a/README.md +++ b/README.md @@ -1398,6 +1398,21 @@ Things I chose while building, with the reasoning: phase-1 suite (43 checks, including the real route mapping against a stub Ollama returning a monthly-limit body, a two-device sync round-trip, and the generation guards below). +65. **Slide content never flex-shrinks, and the AI note appears once.** A long typed answer in a + short-answer activity shrank the textarea inside the slide's flex column (58px instead of its + ~82px), clipping the user's text mid-line on a phone; every direct child of a study/review/guide + slide is now `shrink-0` and the slide scrolls instead. The same screenshot showed **two** AI + disclaimers — the activity's own after the grading box plus the feed's row. The activity-level + copy is gone: the feed/guide row (which also carries the report control) is the single one, and + the phase-2 suite asserts the note sits below the graded feedback rather than right after it. + +64. **Ordering cards only where an order exists.** "Sort these" was landing on decks whose notes + have no sequence at all (a list of works, authors or terms), which the learner cannot reason + about. The deck prompt now allows `ordering` only for real sequences — chronological events, the + stages of a process, steps of a procedure, a lifecycle, a ranked scale — and points at matching, + multiple choice or true/false when there is none, with one or two ordering cards per deck as the + usual ceiling. + 63. **Frutiger Aero is the third theme.** Settings → Theme offers Light, Dark and Frutiger Aero. Aero is not a dark/light variant — it replaces the palette and the scene: the wallpaper is *“The S Aesthetic That You Definitely Recognize Frutiger Aero”* via WallpaperSafari diff --git a/components/SessionFeed.tsx b/components/SessionFeed.tsx index 40dc1f5..8e8d5c7 100644 --- a/components/SessionFeed.tsx +++ b/components/SessionFeed.tsx @@ -202,7 +202,7 @@ export function SessionFeed({ aria-label={t("feed.activityOf", { n: itemIndex + 1, total })} className="flex h-full snap-start snap-always items-stretch justify-center overflow-y-auto overscroll-y-contain" > -
+
-
+
{finished ? ( <> diff --git a/components/StudyFeed.tsx b/components/StudyFeed.tsx index b9d8a6d..5493dee 100644 --- a/components/StudyFeed.tsx +++ b/components/StudyFeed.tsx @@ -315,7 +315,7 @@ export function StudyFeed({ deck }: { deck: Deck }) { aria-label={t("feed.activityOf", { n: activityIndex + 1, total })} className="flex h-full snap-start snap-always items-stretch justify-center overflow-y-auto overscroll-y-contain" > -
+
-
+
- - -
+
{t("homework.guide.stepOf", { n: stepIndex + 1, total: steps.length })} @@ -291,7 +291,7 @@ export function HomeworkGuide({ aria-label={t("homework.guide.solution")} className="h-full snap-start snap-always overflow-y-auto overscroll-y-contain" > -
+
{t("homework.guide.solution")} diff --git a/lib/prompts.ts b/lib/prompts.ts index 2e0ceba..447dd5b 100644 --- a/lib/prompts.ts +++ b/lib/prompts.ts @@ -86,6 +86,11 @@ export const ACTIVITY_JSON_SPEC = `Each activity is one JSON object with a "type { "type": "matching", "topic": "short label", "instruction": "Match each term to its definition.", "pairs": [{ "term": "Osmosis", "definition": "diffusion of water across a membrane" }], "explanation": "..." } 6. ordering - 3 to 7 items given in the CORRECT order (the app shuffles them) + Use it ONLY when the notes contain a real sequence the learner can reason about: historical or + chronological events, the stages of a process, steps of a procedure, a lifecycle, a ranked scale. + If the notes only list unrelated things (works, authors, terms, places, values), do NOT invent an + order for them — use matching, multiple choice or true/false instead. One or two ordering cards + per deck is usually plenty. Items must NEVER contain dates, years, clock times, step numbers or ordinals ("first", "step 2") — the learner has to work out the order from what each item says, not read it off. { "type": "ordering", "topic": "short label", "instruction": "Put the events in the order they happened.", "items": ["The Allies land in Normandy", "Paris is liberated", "Germany surrenders"], "explanation": "..." } diff --git a/package.json b/package.json index a4f5940..2054746 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freezmo", - "version": "0.2.2", + "version": "0.2.4", "private": true, "license": "GPL-3.0-only", "scripts": { diff --git a/public/sw.js b/public/sw.js index e2eb758..b42bc50 100644 --- a/public/sw.js +++ b/public/sw.js @@ -9,7 +9,7 @@ * * Bumping CACHE_VERSION is how a new deploy invalidates old assets. */ -const CACHE_VERSION = "freezmo-v56"; +const CACHE_VERSION = "freezmo-v57"; const SHELL_CACHE = `${CACHE_VERSION}-shell`; const ASSET_CACHE = `${CACHE_VERSION}-assets`; diff --git a/tests/phase2-test.mjs b/tests/phase2-test.mjs index 3edd76f..ca99b52 100644 --- a/tests/phase2-test.mjs +++ b/tests/phase2-test.mjs @@ -142,13 +142,15 @@ const browser = await launchBrowser(); await page.getByRole("button", { name: "Submit answer" }).click(); await page.waitForTimeout(800); check("grading: AI feedback rendered", await page.getByText("Nice — scattering does the work.").isVisible()); - // Scope it to the placement: the disclaimer directly under the graded box. + // The disclaimer sits below the graded feedback (the feed's row carries it; + // the activity no longer duplicates it right after the grading box). const feedbackDisclaimer = await page.evaluate(() => { const feedback = [...document.querySelectorAll("p")].find((el) => el.textContent.includes("Nice — scattering does the work.")); - const next = feedback?.closest("div")?.nextElementSibling; - return Boolean(next?.textContent?.includes("Freezmo uses AI")); + const note = [...document.querySelectorAll("p")].find((el) => el.textContent.includes("Freezmo uses AI")); + if (!feedback || !note) return false; + return note.getBoundingClientRect().top > feedback.getBoundingClientRect().bottom - 4; }); - check("grading: AI disclaimer under the feedback", feedbackDisclaimer); + check("grading: AI disclaimer below the feedback", feedbackDisclaimer); await context.close(); }