From 9b0354fb11a49c3d7e5080c88f3d177bda0dad27 Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Tue, 13 Oct 2026 07:27:39 +0000 Subject: [PATCH] Fix the clipped answer field + the doubled AI note; ordering only where it fits UI bug from a phone screenshot (short answer, Czech): - The textarea was flex-shrunk inside the slide's column (58px instead of ~82px), clipping the typed answer mid-line. Every direct child of a study/review/guide slide is now shrink-0 and the slide scrolls instead. - Two AI disclaimers were visible at once: 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. The phase-2 check now asserts the note sits below the graded feedback. - The mobile suite's backdrop-tap check is deterministic too (the spring animation made the measured box mid-flight). Ordering activities: the deck prompt now allows them only when the notes hold a real sequence (chronological events, process stages, procedure steps, a lifecycle, a ranked scale) and points at matching / multiple choice / true-false when they do not - "sort these" was landing on decks with no order at all. Version 0.2.4, SW -> freezmo-v57. --- README.md | 15 +++++++++++++++ components/SessionFeed.tsx | 4 ++-- components/StudyFeed.tsx | 4 ++-- components/activities/ShortAnswer.tsx | 3 --- components/homework/HomeworkGuide.tsx | 4 ++-- lib/prompts.ts | 5 +++++ package.json | 2 +- public/sw.js | 2 +- tests/phase2-test.mjs | 10 ++++++---- 9 files changed, 34 insertions(+), 15 deletions(-) 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(); }