From 9d6ef90f0289870fe9bed013c1e8abcc82c55dfa Mon Sep 17 00:00:00 2001 From: Martin Dvorak Date: Thu, 8 Oct 2026 21:50:18 +0000 Subject: [PATCH] Keep the library header steady across tabs; simplify the empty states MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Header & action bar - Import JSON no longer disappears on the Homework tab: it sits directly under the "Your icebox" header/subtitle, above the tab switcher, on every tab — so switching pools no longer shifts the layout or hides actions. It is one secondary button at all sizes now, not a quiet link when empty. - The decks-only "Homework Solver" header action is gone; the homework tab's empty-state CTA and the drawer cover starting a new solve. Empty states - Dropped the "Using your own Ollama models — check the connection in Settings." line (and its two dictionary keys) everywhere. - Both tabs share one structure: ice icon -> heading -> short description -> primary CTA button, starting at the same height: Study Decks: "No decks yet" -> "Freeze a page of notes into flashcards and quizzes." -> "Create study deck" Homework: "No homework yet" -> "Solved assignments and step-by-step guides show up here." -> "Solve homework" - CTA labels read as actions on the standardized nouns; removed the now unused actionStudyDeck/actionHomework keys. Tabs - Link segments are 44px tall at every breakpoint (min-height in the base .segmented > a rule). Service worker: freezmo-v11. Verified: library-consistency suite 49/49 (Import visible with a fixed position on both tabs, no Ollama text, empty states at the same y, primary ice CTA buttons >=44px, deep links, deletion, redirects, desktop, 360px, axe en+cs light+dark), mobile 51/51, i18n 27/27, desktop regression 74/74. Lint + build clean. --- app/globals.css | 4 +- components/LibraryScreen.tsx | 164 ++++++++++------------------------- lib/i18n-dict.ts | 24 ++--- public/sw.js | 2 +- 4 files changed, 55 insertions(+), 139 deletions(-) diff --git a/app/globals.css b/app/globals.css index d2ae8e0..a221703 100644 --- a/app/globals.css +++ b/app/globals.css @@ -499,6 +499,7 @@ body { align-items: center; justify-content: center; gap: 0.375rem; + min-height: 2.75rem; border-radius: 0.75rem; padding: 0.42rem 0.5rem; font-size: 0.875rem; @@ -727,8 +728,7 @@ body { @media (max-width: 639px) { .touch-target { min-height: 44px; min-width: 44px; } .nav-row { min-height: 44px; } - .segmented > button, - .segmented > a { min-height: 44px; } + .segmented > button { min-height: 44px; } .folder-pill { min-height: 44px; } } diff --git a/components/LibraryScreen.tsx b/components/LibraryScreen.tsx index 390050b..01181ed 100644 --- a/components/LibraryScreen.tsx +++ b/components/LibraryScreen.tsx @@ -6,13 +6,12 @@ import { useMemo, useRef, useState, useSyncExternalStore } from "react"; import { DeckCard } from "@/components/DeckCard"; import { FolderBar } from "@/components/FolderBar"; import { HomeworkCard } from "@/components/HomeworkCard"; -import { FolderIcon, SnowflakeIcon, SparklesIcon, UploadIcon } from "@/components/icons"; +import { FolderIcon, UploadIcon } from "@/components/icons"; import { Button, EmptyState } from "@/components/ui"; import { folderStore, renameFolder } from "@/lib/folders"; import { deleteHomework, homeworkStore, sortHomework } from "@/lib/homework"; import { useI18n } from "@/lib/i18n"; import { ALL_FOLDERS, folderSelection, useSelectedFolder } from "@/lib/nav"; -import { openSettings } from "@/lib/ui-events"; import { UNORGANIZED_ID, type Deck } from "@/lib/schemas"; import { deckStore, deleteFolder, importDeck, sortDecks, upsertDeck } from "@/lib/storage"; import { cn } from "@/lib/utils"; @@ -118,89 +117,47 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" |
{/* page header ---------------------------------------------------- */}
-
-
-

- {t("library.title")} -

-

- {t("library.subtitle")} -

-
+
+

+ {t("library.title")} +

+

+ {t("library.subtitle")} +

+
-
- {/* Context action: Import for decks (a quiet link while the pool - is empty), start-a-new-solve for homework. */} - {tab === "decks" ? ( - isEmpty ? ( - - ) : ( - <> - - - - ) - ) : homework.length > 0 ? ( - <> - - - {t("actionHomework")} - - - - {t("actionHomework")} - - - ) : null} - { - void handleImport(event.target.files?.[0]); - event.target.value = ""; - }} - /> -
+ {/* Import sits above the tabs on every tab, so the header never + shifts when you switch pools. */} +
+ + { + void handleImport(event.target.files?.[0]); + event.target.value = ""; + }} + /> +
- {/* Two pools, never mixed: study decks and saved homework. */} -
- - {t("tabDecks")} - - - {t("tabHomework")} - -
+ {/* Two pools, never mixed: study decks and saved homework. */} +
+ + {t("tabDecks")} + + + {t("tabHomework")} +
{/* Secondary stats folded into the header as quiet text on desktop, @@ -263,34 +220,12 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" | - - - - - {t("emptyStateCreateDeck")} - + + {t("emptyStateCreateDeck")} } > {t("library.emptyBody")} - - {t("library.emptyHint")}{" "} - - . - ) : null} @@ -424,19 +359,8 @@ export function LibraryScreen({ initialTab = "decks" }: { initialTab?: "decks" | - - - - - {t("emptyStateHomework")} - + + {t("emptyStateHomework")} } > diff --git a/lib/i18n-dict.ts b/lib/i18n-dict.ts index 243fded..44e4146 100644 --- a/lib/i18n-dict.ts +++ b/lib/i18n-dict.ts @@ -223,7 +223,7 @@ const en: Record = { "homework.history.openTitle": "Open “{title}”", "homework.history.deleteConfirm": "Delete this result?", "homework.emptyTitle": "No homework yet", - "homework.emptyBody": "Solved assignments and step-by-step guides you save show up here.", + "homework.emptyBody": "Solved assignments and step-by-step guides show up here.", "homework.open": "Open", "homework.steps.one": "{n} step", "homework.steps.few": "{n} steps", @@ -234,10 +234,8 @@ const en: Record = { "tabHomework": "Homework", /* quick actions (library empty state + /create switcher) ----------- */ - "emptyStateCreateDeck": "Study Deck", - "emptyStateHomework": "Homework Solver", - "actionStudyDeck": "Study Deck", - "actionHomework": "Homework Solver", + "emptyStateCreateDeck": "Create study deck", + "emptyStateHomework": "Solve homework", /* library --------------------------------------------------------- */ "library.title": "Your icebox", @@ -250,9 +248,7 @@ const en: Record = { "library.deckMoved": "Deck moved.", "library.deckMovedUnorganized": "Deck moved to Unorganized.", "library.noDecks": "No decks yet", - "library.emptyBody": "Freeze a page of notes into a feed of flashcards and quizzes.", - "library.emptyHint": "Using your own Ollama models —", - "library.emptySettings": "check the connection in Settings", + "library.emptyBody": "Freeze a page of notes into flashcards and quizzes.", "library.renameFolderAria": "Folder name", "library.nothingUnfiled": "Nothing unfiled right now.", "library.emptyFolder": "Empty folder — freeze a deck into it or move one over from Unorganized.", @@ -817,7 +813,7 @@ const cs: Record = { "homework.history.openTitle": "Otevřít „{title}“", "homework.history.deleteConfirm": "Smazat tento výsledek?", "homework.emptyTitle": "Zatím žádné úkoly", - "homework.emptyBody": "Uložená řešení úkolů a návody krok za krokem se objeví tady.", + "homework.emptyBody": "Vyřešená zadání a návody krok za krokem se objeví tady.", "homework.open": "Otevřít", "homework.steps.one": "{n} krok", "homework.steps.few": "{n} kroky", @@ -828,10 +824,8 @@ const cs: Record = { "tabHomework": "Úkoly", /* quick actions (library empty state + /create switcher) ----------- */ - "emptyStateCreateDeck": "Studijní balíček", - "emptyStateHomework": "Řešitel úkolů", - "actionStudyDeck": "Studijní balíček", - "actionHomework": "Řešitel úkolů", + "emptyStateCreateDeck": "Vytvořit studijní balíček", + "emptyStateHomework": "Vyřešit úkol", /* library --------------------------------------------------------- */ "library.title": "Tvoje mraznička", @@ -844,9 +838,7 @@ const cs: Record = { "library.deckMoved": "Sada přesunuta.", "library.deckMovedUnorganized": "Sada přesunuta do Nezařazených.", "library.noDecks": "Zatím žádné sady", - "library.emptyBody": "Zmraz stránku poznámek do feedu kartiček a kvízů.", - "library.emptyHint": "S tvými vlastními modely Ollama —", - "library.emptySettings": "zkontroluj připojení v Nastavení", + "library.emptyBody": "Zmraz stránku poznámek do kartiček a kvízů.", "library.renameFolderAria": "Název složky", "library.nothingUnfiled": "Teď tu nic nezařazeného není.", "library.emptyFolder": "Prázdná složka — zmraz do ní sadu nebo ji sem přesuň z Nezařazených.", diff --git a/public/sw.js b/public/sw.js index 4ef9ccb..7eb3601 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-v10"; +const CACHE_VERSION = "freezmo-v11"; const SHELL_CACHE = `${CACHE_VERSION}-shell`; const ASSET_CACHE = `${CACHE_VERSION}-assets`;