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.
This commit is contained in:
parent
f4026d3e55
commit
9b0354fb11
9 changed files with 34 additions and 15 deletions
15
README.md
15
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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<ActivityRenderer
|
||||
activity={item.activity}
|
||||
active={index === itemIndex}
|
||||
|
|
@ -242,7 +242,7 @@ export function SessionFeed({
|
|||
aria-label={t("review.results")}
|
||||
className="flex h-full snap-start snap-always items-stretch justify-center overflow-y-auto overscroll-y-contain"
|
||||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
{finished
|
||||
? (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<ActivityRenderer
|
||||
activity={activity}
|
||||
active={index === activityIndex}
|
||||
|
|
@ -353,7 +353,7 @@ export function StudyFeed({ deck }: { deck: Deck }) {
|
|||
aria-label={t("feed.results")}
|
||||
className="h-full snap-start snap-always overflow-y-auto overscroll-y-contain"
|
||||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<ResultsScreen
|
||||
deck={currentDeck}
|
||||
xp={xp}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { useI18n } from "@/lib/i18n";
|
|||
import { modelErrorCopy } from "@/lib/model-errors";
|
||||
import type { Activity } from "@/lib/schemas";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AiDisclaimer } from "@/components/AiDisclaimer";
|
||||
import { Button, Spinner } from "@/components/ui";
|
||||
import { ModelErrorActions } from "@/components/ModelErrorActions";
|
||||
|
||||
|
|
@ -186,8 +185,6 @@ export function ShortAnswer({ activity, active, language, onAnswered, onNext, is
|
|||
) : null}
|
||||
</div>
|
||||
|
||||
<AiDisclaimer />
|
||||
|
||||
<FeedbackPanel
|
||||
correct={state.correct}
|
||||
explanation={activity.explanation}
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ export function HomeworkGuide({
|
|||
aria-label={t("homework.guide.stepOf", { n: stepIndex + 1, total: steps.length })}
|
||||
className="flex h-full snap-start snap-always items-stretch justify-center overflow-y-auto overscroll-y-contain"
|
||||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="tint-sky inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50 backdrop-blur-sm">
|
||||
{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"
|
||||
>
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center]">
|
||||
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col gap-4 px-4 pb-24 pt-24 [justify-content:safe_center] [&>*]:shrink-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="tint-mint inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ring-white/50 backdrop-blur-sm">
|
||||
{t("homework.guide.solution")}
|
||||
|
|
|
|||
|
|
@ -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": "..." }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "freezmo",
|
||||
"version": "0.2.2",
|
||||
"version": "0.2.4",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -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`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue