Native-feel pass: PWA integration, system back, offline, haptics, undo
Mobile only (<=639px / coarse pointers); desktop keeps its behaviour. System integration - Manifest: id, display_override, monochrome icon, Make my homework shortcut and a share target (text/URL via GET, files via POST). iOS startup images for the common iPhone sizes. Pinch-zoom is allowed again (double-tap zoom is disabled via touch-action instead). - Share target: /share chooser with New deck / Make my homework; POSTed files wait in a 10-minute in-memory store, reach the wizard through sessionStorage and never touch disk. - Web Share: Share rows for decks (JSON file where supported) and the homework result, with a clipboard fallback and a toast. - Offline: a quiet pill appears and Freeze / mode cards are disabled with a clear reason; input is never lost (error cards keep Retry). - State: wizard drafts (text, options, photos up to a size budget) survive reloads and OS kills and are cleared on success; re-tapping the active tab scrolls to the top; the deck page shows a skeleton instead of a spinner. Navigation and gestures - System Back closes the topmost layer first: a small history-entry stack (lib/use-back-close.ts) covers the drawer, Settings sheet, install sheet, deck actions panel and one wizard step; navigations are never rewound and stale entries are skipped. - Destructive-but-reversible actions use Undo toasts: deleting a deck (no confirm) and removing a file in the wizard. Irreversible ones keep their confirm. Input and feedback - Every phone input got inputmode / enterkeyhint / autocomplete / autocapitalize / spellcheck; Enter on the last field advances the wizard; a clipboard-paste button appears where the API exists. - Haptics behind a Settings toggle (on by default): tab switches, toggles, correct/wrong, completion; Vibration API + the iOS hidden switch tick, throttled and silent everywhere else. - Wake lock during the study feed and homework guide, released on leave and re-acquired when the tab returns. Verified: new native suite 35/35 (manifest/head/viewport, offline, share target GET + POST, back-gesture layers, undo, share fallback, tab re-tap, haptics persistence, draft restore), regression 74/74, mobile 52/52, wizard 80/80, declutter 116/116, animation 8/8, i18n 25/25. Lint + build clean. SW freezmo-v23. Needs a real device (documented in the README): iOS haptic tick, system share sheet entry, wake-lock grant, iPhone startup images, iOS edge-swipe back, install prompts. Pull-to-refresh and the app badge were skipped on purpose.
This commit is contained in:
parent
e66141e9a0
commit
4369ba1a17
34 changed files with 1354 additions and 65 deletions
45
README.md
45
README.md
|
|
@ -53,6 +53,15 @@ no database, no data leaving your machine except the requests you send to Ollama
|
|||
*Edit* link back to its step. A flat Back/Cancel + Next/Freeze bar sits at the bottom above
|
||||
the safe area (the tab bar hides inside the wizard), and it is portalled to `<body>` so the
|
||||
screen entrance animation can never displace it. The desktop page is unchanged.
|
||||
- **Native-feel touches (phones)**: the Android Back gesture closes the topmost layer first
|
||||
(sheet, drawer, actions panel, then one wizard step); deleting a deck shows an *Undo*
|
||||
toast instead of a confirm; the deck menu can share a deck as JSON through the system share
|
||||
sheet (clipboard fallback); content shared from other apps opens a chooser and lands
|
||||
pre-filled in either wizard; haptics (Settings → Haptics, on by default) tick on tab
|
||||
switches, toggles, correct/wrong answers and completion; the screen stays awake during
|
||||
study sessions and homework guides; wizard drafts survive a reload or an OS kill; re-tapping
|
||||
the active tab scrolls to the top; an offline pill appears and generation is disabled with
|
||||
a reason until the connection is back.
|
||||
- **Photos and documents**: JPG/PNG/WEBP/HEIC images plus **PDF, DOCX, PPTX, ODT/ODP/ODS, TXT, MD, CSV**
|
||||
- Documents are read **in the browser**: PDF text layers are extracted, scanned PDF pages are rendered
|
||||
as images for the vision model, DOCX/PPTX text, speaker notes and embedded images are pulled out.
|
||||
|
|
@ -505,12 +514,13 @@ What's included:
|
|||
|
||||
| Piece | Detail |
|
||||
| --- | --- |
|
||||
| `app/manifest.ts` | `display: standalone`, portrait, theme/background colours, app shortcuts |
|
||||
| Icons | 192/512 px, a maskable 512 px icon, and 180/167/152 px `apple-touch-icon`s, all rendered from `assets/app-icon-source.jpeg` |
|
||||
| iOS meta | `apple-mobile-web-app-capable`, `-title`, `-status-bar-style`, `viewport-fit=cover` |
|
||||
| `app/manifest.ts` | `id`, `display: standalone`, portrait, theme/background colours, `any` + `maskable` + `monochrome` icons, three app shortcuts (New deck, Make my homework, Library) and a **share target** (text/URL via GET, files via POST) |
|
||||
| Icons | 192/512 px, a maskable 512 px icon, a monochrome SVG and 180/167/152 px `apple-touch-icon`s, all rendered from `assets/app-icon-source.jpeg` |
|
||||
| iOS meta | `apple-mobile-web-app-capable`, `-title`, `-status-bar-style`, `viewport-fit=cover`, plus **startup images** for the common iPhone sizes so launch has no white flash |
|
||||
| Service worker | `public/sw.js`, **production only**: cached app shell, stale-while-revalidate for static assets, network-first navigations with an `/offline` fallback. `/api/*` is never cached |
|
||||
| Offline page | `/offline` — "your decks are still here, reconnect to generate" |
|
||||
| Safe areas | `pb-safe` / `pt-safe` utilities plus `pb-tabbar` so nothing hides behind the tab bar |
|
||||
| Share target | `/share` chooser (New deck / Make my homework); POSTed files wait in a short-lived in-memory store, are handed to the wizard through `sessionStorage`, and are never written to disk |
|
||||
|
||||
The icons are generated by `scripts/make-icons.mjs` from `assets/app-icon-source.jpeg` (headless
|
||||
Chromium); it is a one-off tool and is not part of the app's dependencies. It also writes
|
||||
|
|
@ -884,10 +894,11 @@ Things I chose while building, with the reasoning:
|
|||
device, uses Chrome's deferred `beforeinstallprompt` for a one-tap install where available, and
|
||||
stays reachable from Settings. It is mounted in the root layout, so it can appear over any
|
||||
screen — including the key gate — without blocking the page underneath.
|
||||
30. **Phones don't zoom, and mobile fields are 16px.** The viewport sets `maximum-scale=1` /
|
||||
`user-scalable=no` for an app-like feel, and a mobile-only rule keeps every input and textarea at
|
||||
16px so iOS Safari's focus auto-zoom never fires (iOS ignores `user-scalable=no` for that).
|
||||
Desktop keeps the designed 12–14px field sizes.
|
||||
30. **Phones use touch-action, not a zoom lock.** The viewport allows pinch-zoom (accessibility),
|
||||
and double-tap zoom is disabled by `touch-action: manipulation` on the UI chrome instead. A
|
||||
mobile-only rule keeps every input and textarea at 16px so iOS Safari's focus auto-zoom never
|
||||
fires (iOS ignores `user-scalable=no` for that anyway). Desktop keeps the designed 12–14px
|
||||
field sizes.
|
||||
31. **The app icon drives the palette.** The navy rounded-square artwork in
|
||||
`assets/app-icon-source.jpeg` is the source of truth: `frost-*` is navy-tinted, the accent is
|
||||
glacier cyan, the ice scene uses the artwork's blues, the primary CTA is an icy gradient, and
|
||||
|
|
@ -970,6 +981,26 @@ Things I chose while building, with the reasoning:
|
|||
position across the entrance (5 frames, 60–450ms) from the tab bar, the drawer and the
|
||||
empty state, at 360/390, both themes, both languages and with reduced motion.
|
||||
|
||||
39. **Native feel is layered onto the platform, never around it.** The Back gesture closes the
|
||||
topmost layer through a small history-entry stack (`lib/use-back-close.ts`): each layer pushes
|
||||
a same-URL entry, only the top of the stack reacts, and a layer closed by a navigation leaves
|
||||
its entry for the permanent listener to skip. Haptics are best-effort behind a Settings toggle
|
||||
(Vibration API; the hidden `<input switch>` tick on iOS, throttled); the wake lock is requested
|
||||
only while a study feed or guide is open and released on leave. Destructive-but-reversible
|
||||
actions (delete deck, remove file) became Undo toasts; irreversible ones keep their confirm.
|
||||
Wizard drafts and the last URL are per-browser; the share target hands POSTed files to the
|
||||
client through a ten-minute in-memory store so nothing touches disk. Verified by the native
|
||||
suite (35 checks: manifest/head/viewport, offline banner + disabled generate, share target GET
|
||||
and POST, back-gesture closing for drawer/sheet/wizard, undo toast, share fallback, tab
|
||||
re-tap, haptics persistence, draft restore) plus the six standing suites.
|
||||
|
||||
**Needs a real device to verify** (emulation can't do these honestly): the iOS hidden-switch
|
||||
haptic tick, the Android/iOS **share target** entry in the system share sheet, the actual
|
||||
**wake lock** grant, the **startup images** on an installed iPhone, the iOS **edge-swipe**
|
||||
back gesture in standalone, and installability/rich-install prompts (Lighthouse covers the
|
||||
manifest side). Pull-to-refresh and the app badge were skipped on purpose — the Settings
|
||||
refresh icon already re-checks the connection, and there is no meaningful badge number.
|
||||
|
||||
14. **`gpt-oss:120b` does all content work — but it cannot read images** (`this model does not
|
||||
support image input`), so `VISION_MODEL` has to stay a multimodal model (`gemma4:31b-cloud`).
|
||||
Text-only inputs — pasted text, PDFs with a text layer, DOCX/PPTX/ODT — never touch the vision
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue