Fix recipe text loss, calendar overflow on phones, and silent failures - #1
Merged
Merged
Conversation
Recipe editing (the reported bug): - The HTML strip used /<[^>]*>?/gm, whose optional closing `>` made a bare `<` swallow the rest of the line: "temp is <165 F" rendered as "temp is ". It ran at display time and again when opening the editor, so saving persisted the truncation. Replaced with a known-tag-only pattern in src/lib/html.ts, shared by all three call sites. - Recipes whose stored ingredients were objects rather than strings were dropped by asStringArray: the recipe showed "No ingredients listed", the editor opened blank, and saving replaced the real data with []. asStringArray now recovers text from the object shapes scrapers emit, and saveEdit refuses to write an empty list over stored content. - Added a Cancel button. Edit mode previously offered only Save, which always writes, so a mistaken tap on the pencil had to be committed. - The import path bypassed the length caps entirely; scraped titles, lines, and list length are now bounded server-side. Silent failures: - schema.sql never added `items` to the supabase_realtime publication, so every page's live-update subscription connected, reported success, and never fired. Also sets REPLICA IDENTITY FULL (delete payloads carried only the primary key) and NOT NULL on user_id, guarded so it reports rather than fails. Whole file is now re-runnable. - CSV import reported the wrong spreadsheet line for every issue after a blank row, because blank rows are dropped during parsing. Rows now carry their original line number. - ICS folding counted UTF-16 code units while promising octets, so emoji summaries exceeded the 75-octet limit and could be cut mid-surrogate. - Browser and server minted different random ids for calendar entries without one, so per-calendar status never matched and errors were swallowed. - todos saveEdit ignored its update error; a failed rename looked like it worked. Other: - maintenance, credit-cards, and calendar froze "today" at mount, so a tab left open overnight showed stale overdue counts. Extracted the rollover handling meals already had into useToday and shared it. - Added a per-user throttle to the vision-scan route, the only endpoint that spends money per call. - PWA icons declared the 1024x338 masthead as 192x192/512x512/maskable; generated real square icons. Removed ~1.1MB of unreferenced images and the unused @supabase/ssr dependency. - README: wrong clone directory, two Google Calendar features documented as present that have no UI, and no mention that `npm run build` requires the Supabase env vars. Verified: lint clean, 89 tests pass (12 new covering these regressions), production build succeeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkRigAT6CpLEjV7SzKMdZq
…t lists Calendar month grid (reported: "doesn't fit on the iPhone screen"): - MonthGrid used `repeat(7, 1fr)` with square cells. A bare `1fr` floors each track at min-content, and for a square cell that floor comes from its *height* (~55px), so seven cells plus gaps measured 479px against a 393px viewport. The whole page scrolled sideways, Saturday was clipped, and the last row escaped its card. Switched to minmax(0, 1fr) plus minWidth: 0 on the cells; measured in Chromium at iPhone SE and iPhone 14 Pro widths, the document now matches the viewport exactly at both. - Tuned the cell contents for the resulting ~41px box: two event bars instead of three, and clamp() font sizes for the date and overflow count. - .container used min-height: 100vh, which iOS Safari resolves against the large viewport, so every page kept a strip of phantom scroll. Added 100dvh with the 100vh left as fallback. - The bottom nav folded env(safe-area-inset-bottom) into padding while box-sizing is border-box, leaving ~46px of usable height on a home-indicator iPhone. It now grows the box instead, and body padding-bottom matches. - Ten destinations cannot fit a phone, so the nav scrolls — but that left Calendar (8th) off-screen with no hint it was there. It now scrolls the active tab into view on navigation, and tighter tab metrics show ~2 more per screen. Restructuring to fewer primary tabs is still worth considering. Recipe importer (foodiefiber.com): - That site publishes schema.org Article only — no Recipe — so extraction falls to the DOM heuristic, which took the first list with more than two items. The page splits ingredients into "For The Pasta" (1 item), "For The Shrimp" (1 item) and "For The Sauce" (10), so a shrimp pasta imported with neither shrimp nor pasta and nothing signalling the loss. - Replaced with per-list scoring (a majority of lines must lead with a quantity or carry a unit word) plus merging of sibling lists that share a parent, which is how one recipe's sections are marked up. The heading above each list becomes a section label, matching the Sanity path's formatting. Verified against the live page: 15 lines, shrimp and pasta present, nav menus and the prose tips list correctly rejected. - Moved the helper to src/lib/recipe-extract.ts so it can be unit tested; "use server" modules may only export async functions. Note: that page carries no serving count in any form, so ingredient scaling stays unavailable for it regardless of extraction. Verified: lint clean, 100 tests pass (11 new), production build succeeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkRigAT6CpLEjV7SzKMdZq
Pressing any button inside a .card put the whole container into :active, and `transform: scale(0.98)` moved everything in it. Displacement grows with distance from the card's centre, so on the Recipes tab — where one .card wraps the entire list and an expanded recipe is ~1600px tall — the scale buttons sit far enough from centre to shift 12.7px while held, animated over 0.2s. That is half the button's own height sliding out from under the finger mid-press, which made the 1x/1.5x/2x control unreliable to hit. Measured in Chromium at iPhone 14 Pro width, pressing the "2x" button: before y=194.7 -> while held y=207.4 (12.7px, transform: matrix(0.98…)) after y=194.7 -> while held y=194.7 ( 0.0px, transform: none) Only 2 of the ~22 cards in the app are interactive — the home dashboard tiles, which are links — so the feedback is now scoped to `a.card` / `button.card`. Those still scale on press; content containers no longer move. The transform transition is scoped the same way. Also added touch-action: manipulation to the scale buttons. RecipeDisplay was the only recipe component without it, so those buttons still carried the 300ms tap delay and double-tap-to-zoom. Verified: lint clean, 103 tests pass (3 new), production build succeeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkRigAT6CpLEjV7SzKMdZq
justonecookbook.com/japanese-beef-curry imported with visible markup in almost every step — "Cut 2 onions into thin slices.", "When it's hot" — and mangled ingredient lines like "2 onions ((large; 1¼ lb, 567 g))". Both come from the source, not from our processing (checked: the raw JSON-LD holds 42 literal   and 8 ', and zero &#32;, so we are not double-encoding). - Entities: the site puts numeric HTML entities inside JSON-LD strings. JSON is not HTML, so JSON.parse leaves them as visible text and nothing downstream decodes them. The DOM-scraping paths never hit this because cheerio's .text() decodes for us, which is why it went unnoticed. Added decodeEntities() for numeric (decimal and hex) plus the named set recipe sites actually emit, leaving unknown or out-of-range entities untouched rather than replacing them with a placeholder glyph. - Artefacts: "2 onions ((large; 567 g))" is verbatim WP Recipe Maker output — it emits a double space when a quantity has no unit, and wraps an already bracketed note in another pair. Deterministic, so cleanRecipeLine() collapses doubled parens and runs of whitespace. Genuinely nested parens are left alone. Applied at the import boundary and again at render, so recipes already saved with entities in them clean up without a data migration. Live re-import of that page: all 24 ingredients and 27 steps now read as plain text. foodiefiber still returns its 15 lines, unchanged. Verified: lint clean, 111 tests pass (11 new), production build succeeds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkRigAT6CpLEjV7SzKMdZq
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four commits from a repo review, each verified against the real thing rather than by inspection.
Recipes
<ate the rest of the line. The HTML strip used/<[^>]*>?/gm, whose optional closing>meant"cook until temp is <165 F"rendered as"cook until temp is ". It ran at display and when opening the editor, so saving persisted the truncation.[]. Now the object shapes are recovered, and a save can never overwrite stored content with an empty list. /'showing as literal text. Sites put numeric HTML entities inside JSON-LD strings; JSON isn't HTML, so nothing decoded them. The DOM-scraping paths never hit this because cheerio's.text()decodes for us. Also tidies WP Recipe Maker's doubled parens (((divided))) and double spaces.Calendar / layout on iPhone
MonthGridusedrepeat(7, 1fr)with square cells. A bare1frfloors each track at min-content, and for a square cell that floor comes from its height, so seven cells measured 479px against a 393px viewport — the page scrolled sideways, Saturday was clipped, and the last row escaped its card. Measured in Chromium at iPhone SE and iPhone 14 Pro widths, the document now matches the viewport exactly at both.Also:
min-height: 100vh→ added100dvh(iOS resolves100vhagainst the large viewport, giving every page phantom scroll); the bottom nav grows to include the home-indicator inset instead of eating its own content height; and the nav scrolls the active tab into view, since Calendar is the 8th of 10 tabs and was off-screen with no hint it existed.Ingredient scaler was hard to tap
.card:active { transform: scale(0.98) }applied to every card. Displacement grows with distance from the card's centre, so on Recipes — where one card wraps the whole list and an expanded recipe is ~1600px tall — the scale buttons shifted 12.7px while held, half the button's own height sliding out from under the finger. Press feedback is now scoped toa.card/button.card; only 2 of the ~22 cards in the app are actually pressable.Silent failures
schema.sqlnever enabled Realtime. Every page subscribes topostgres_changesonitems, but the table was never added to thesupabase_realtimepublication — so subscriptions connect, report success, and never fire. Also setsREPLICA IDENTITY FULL(delete payloads carried only the primary key) andNOT NULLonuser_id, guarded so it reports rather than fails. The whole file is now re-runnable and touches no rows.todosrename ignored its update error, so a failed rename looked like it worked.useToday.Housekeeping
Real square PWA icons (the manifest declared the 1024×338 masthead as 192×192 and maskable), ~1.1 MB of unreferenced images removed, unused
@supabase/ssrdropped, and README corrections (wrong clone directory, two Google Calendar features documented as present that have no UI, missing build-env note).Verification
Lint clean, 111 tests pass (34 new, pinning each regression above), production build succeeds. Live re-imports confirmed against both problem sites.
After merging
Re-run
schema.sqlin the Supabase SQL editor — that's the one fix that lives in the database rather than the code, so merging alone won't apply it. It's guarded and re-runnable, and deletes nothing.Already-saved recipes clean up on next view without a migration; no reimport needed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01GkRigAT6CpLEjV7SzKMdZq
Generated by Claude Code