Commit Graph

40 Commits

Author SHA1 Message Date
jbrechtel 460b7d0fdc Store cook log DB in writable data dir, not read-only recipes mount
Build and Deploy / build-and-deploy (push) Successful in 2m43s
The cook log SQLite DB was created at recipeDir/cook-log.db. In the Docker
deployment /recipes is mounted read-only, so SQLite could never create the
file there — the cook log has never worked in the container. The writable
/data volume was mounted but unused.

- initDb now creates the DB's parent directory if missing, so startup
  initialization is self-contained and idempotent.
- app takes a dataDir and places cook-log.db there.
- Add optional --data-dir flag (defaults to the recipe dir, preserving
  local-dev behavior); Docker CMD passes --data-dir /data.

Verified end-to-end as root against a read-only recipe dir and a
non-existent data dir: the dir and DB are created and cook-log POST/GET
round-trips.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:53:35 -04:00
jbrechtel 3c3b6b853a Fix cook history ordering: sort months by date, not name
Build and Deploy / build-and-deploy (push) Successful in 1m46s
groupEntries was sorting month groups by reverse-alphabetical name
(e.g. 'June' before 'July'), producing oldest-first ordering.
Now sorts by the actual entry dates within each group (newest first).
Combined with the earlier DESC fix in fetchAllEntries, the cook
history page now shows newest months at top, newest entries within
each month at top.
2026-07-08 09:25:41 -04:00
jbrechtel 518d62fcb6 HLint 2026-05-31 14:46:58 -04:00
jbrechtel 0eb6e6ea3c UI adjustments: Import link in header, matching font sizes, cook history titles, click-to-upload images
- Add Import link to all page headers (landing, recipe, cook history, import)
- Match ingredient list font size to recipe method text (both 15px)
- Show recipe titles instead of filenames on Cook History page
- Make clicking anywhere on recipe hero image/placeholder trigger file upload dialog
2026-05-27 20:59:24 -04:00
jbrechtel 6a7374c912 Formatting
Build and Deploy / build-and-deploy (push) Successful in 14m49s
2026-05-26 07:25:18 -04:00
jbrechtel d22c8a79d4 Add landing page with cook log integration
Build and Deploy / build-and-deploy (push) Failing after 1m15s
- New landing page (/) based on roux-landing-mockup.html layout
  - Shuffle card that suggests random recipes
  - Recently cooked shelf (driven by cook log DB)
  - Recently added shelf (by file modification time)
- Existing recipe index moved to /recipes
- Updated nav links throughout to point to /recipes
- All hlint warnings fixed
2026-05-26 07:15:38 -04:00
jbrechtel 0be6e299d5 fix: normalize recipe filenames in cook-log (strip .cork ext, lowercase); also fetch with .cork fallback for legacy entries
Build and Deploy / build-and-deploy (push) Failing after 1m25s
2026-05-26 06:48:11 -04:00
jbrechtel 14f43a4ae9 fix: send URL-encoded form data instead of multipart, show errors inline, default empty date to today
Build and Deploy / build-and-deploy (push) Successful in 15m46s
2026-05-25 22:26:53 -04:00
jbrechtel ac7a979fe9 Formats repo
Build and Deploy / build-and-deploy (push) Successful in 15m3s
2026-05-25 22:11:28 -04:00
jbrechtel 0b6b9c3d17 fix: use Text for created_at, fix show-bug in ToJSON, add /cook-history route
Build and Deploy / build-and-deploy (push) Failing after 21s
2026-05-25 22:10:23 -04:00
jbrechtel c6a9c2dee4 feat: wire up cook log DB at server startup 2026-05-25 22:00:45 -04:00
jbrechtel ed015d3799 Fix all 20 hlint warnings across 5 source files
Build and Deploy / build-and-deploy (push) Successful in 12m38s
- Use newtype instead of data for single-field types (RouxConfig, ImportError)
- Replace maybe "" id with fromMaybe "" throughout
- Replace not (x `elem` ys) with x `notElem` ys
- Remove redundant brackets, redundant $, and eta-reduce lookupRecipe
- Use numeric underscore for 1_000_000
2026-05-25 21:30:21 -04:00
jbrechtel 69f4184f2e feat: add PATCH endpoint for recipe title updates 2026-05-21 21:31:33 -04:00
jbrechtel d64129fc9f fix: save recipe images to recipe-images/ subdirectory
Build and Deploy / build-and-deploy (push) Successful in 1m49s
Previously images were saved directly in the recipe directory
alongside .cook files. Now they go into <recipes-dir>/recipe-images/,
keeping the recipe directory clean and making the purpose of the
file clear from its path.
2026-05-21 21:06:07 -04:00
jbrechtel a509f44300 feat: add recipe image upload with in-place metadata update
Build and Deploy / build-and-deploy (push) Successful in 2m4s
Users can now upload an photo for any recipe directly from the
view page. The upload button appears as either a 'Replace' overlay
on existing images or an empty dashed placeholder for recipes
without one.

Upload flow:
1. Click upload button -> file picker opens (accepts image/*)
2. JavaScript reads the file as base64, POSTs to /upload-image
   with form fields: filename, file-b64, file-name
3. Server decodes the base64, saves to <recipe-dir>/<basename>.<ext>
4. Server updates the .cook file's YAML front matter, adding or
   updating the 'image:' key with a /recipe-images/ URL
5. Server returns JSON success, page reloads to show the image

Server routes added:
- POST /upload-image  — accepts base64-encoded image upload
- GET /recipe-images/<filename> — serves saved recipe images
  (extension-whitelisted to .jpg/.jpeg/.png/.gif/.webp)

Image files are stored alongside .cook files in the recipe directory
and served via the /recipe-images/ path. The fsnotify watcher detects
the .cook metadata change and triggers an SSE reload.
2026-05-21 20:43:58 -04:00
jbrechtel 76fab5c716 fix: save uploaded files to recipe-images/ subdir and inject source URL
Build and Deploy / build-and-deploy (push) Successful in 1m49s
2026-05-21 17:04:34 -04:00
jbrechtel 5117c9d445 fix: remove relative path fallback in findConvertScript, rely on PATH only
Build and Deploy / build-and-deploy (push) Successful in 1m33s
2026-05-21 11:42:06 -04:00
jbrechtel 16074b0060 fix: resolve convert-to-cooklang script path with makeAbsolute
Build and Deploy / build-and-deploy (push) Successful in 1m46s
2026-05-21 11:15:39 -04:00
jbrechtel 578d1ae068 feat: add LLM import pipeline with file upload and text input 2026-05-21 10:29:35 -04:00
jbrechtel 6081534c10 feat: save raw scraper JSON alongside .cook file on import
Build and Deploy / build-and-deploy (push) Successful in 1m18s
When importing a recipe, the raw JSON output from scrape-recipe is now
saved as <recipe-name>.json in the recipe directory alongside the .cook
file. This lets users inspect the intermediate schema.org representation
to debug step breakdown or ingredient parsing issues.
2026-05-20 15:50:17 -04:00
jbrechtel 7b7c522080 fix: force lazy ByteString evaluation before waitForProcess to avoid deadlock
Build and Deploy / build-and-deploy (push) Successful in 1m45s
The lazy I/O in LB.hGetContents returns a thunk immediately without
reading any data. If waitForProcess is called before the ByteString
is forced, the parent blocks on the process while the child blocks
on a full pipe buffer (no one is reading). Fix by computing LB.length
(which forces full traversal) before calling waitForProcess.
2026-05-20 15:35:22 -04:00
jbrechtel 7fb83230d3 fix: correct createProcess tuple order in readProcessBytes
Build and Deploy / build-and-deploy (push) Successful in 1m38s
createProcess returns (stdin, stdout, stderr, process), not
(stdout, stderr, _, process). Since stdin is Inherited (not CreatePipe),
it returns Nothing, not Just. The pattern (Just outH, Just errH, _, _)
was matching on the stdin and stdout fields, so it always fell through
to the error branch.
2026-05-20 15:18:11 -04:00
jbrechtel 6f610c5d86 fix: replace partial pattern in readProcessBytes with explicit case
Build and Deploy / build-and-deploy (push) Successful in 1m28s
Also adds type-safety conventions to AGENTS.md: no partial patterns on
IO results, no partial functions (head/tail/fromJust), always use case
with explicit branches.
2026-05-20 15:05:48 -04:00
jbrechtel 8d1ff6eb86 fix: read subprocess output as raw bytes to avoid locale encoding errors
Build and Deploy / build-and-deploy (push) Successful in 1m32s
readProcessWithExitCode returns stdout/stderr as String, which forces GHC
to decode raw bytes using the current locale encoding. In a Docker
container with C locale (default), non-ASCII UTF-8 bytes (like 0xE2)
cause 'invalid argument' errors.

Replace with readProcessBytes which uses createProcess + hSetBinaryMode
to capture stdout/stderr as raw lazy ByteStrings, bypassing locale
decoding entirely. JSON is decoded directly from raw bytes via A.decode.
2026-05-20 14:53:11 -04:00
jbrechtel ebeb52212d fix: properly percent-decode URL-encoded form values in import
Build and Deploy / build-and-deploy (push) Successful in 1m35s
The urldecode helper in parseFormBody was using Html.urlDecode which only
handled %%20, %%23, and %%25. Full URLs encode colons, slashes, and dots
as %%3A, %%2F, and %%2E, so they were never decoded. The scraper received
the still-encoded URL and failed to fetch it.

Replace Html.urlDecode with a proper percent-decode that handles all %%XX
hex sequences using digitToInt.
2026-05-20 14:46:43 -04:00
jbrechtel 98644be7ea feat: add structured logging to import pipeline
Build and Deploy / build-and-deploy (push) Successful in 1m17s
Logs each stage of the import with [roux] prefix: request URL, script
path, exit codes, stderr output, JSON parse status, conversion status,
and file write path. Error messages now include the attempted URL for
easier debugging.
2026-05-20 14:33:08 -04:00
jbrechtel 29952d456c fix: resolve scrape-recipe script path via PATH then fallback to scripts/
Build and Deploy / build-and-deploy (push) Successful in 1m20s
2026-05-20 14:25:37 -04:00
jbrechtel 6e895f7286 feat: add /import route for recipe URL import 2026-05-20 13:22:27 -04:00
jbrechtel 795a99ae2b fix: deduplicate SSE events and debounce client reload
Build and Deploy / build-and-deploy (push) Successful in 1m39s
2026-05-20 11:54:06 -04:00
jbrechtel f5c693a2b5 feat: add SSE endpoint and ChangeLog for live recipe reload 2026-05-20 10:21:57 -04:00
jbrechtel ce9062a4b3 fix: catch IOExceptions in reReadRecipe and wrap watchDir callback for resilience 2026-05-19 23:40:13 -04:00
jbrechtel 956a4bc7d7 fix: use watchDir callback instead of watchTreeChan for reliable event delivery 2026-05-19 23:35:32 -04:00
jbrechtel 5bfd3f2308 fix: use absolute dir path for watcher makeRelative consistency 2026-05-19 23:28:51 -04:00
jbrechtel d8e2cc101d fix: correct path handling in live recipe watching
- Strip .cook from map keys to match lookupRecipe behavior
- Use makeRelative for event paths to match relative map keys
- Filter watcher to only .cook files
- Log watcher thread exceptions
- Use imported isSuffixOf from Data.List
2026-05-19 23:23:04 -04:00
jbrechtel a2a8ea7b99 feat: watch recipe directory for live updates via fsnotify 2026-05-19 23:14:24 -04:00
jbrechtel 9252a76503 refactor: serve same index page for all sort routes (hash-driven client-side) 2026-05-19 21:40:34 -04:00
jbrechtel b0926ee60d Add three browse modes to index page: alphabetical, by tag, by course
- Add SortMode type (AlphaSort | TagSort | CourseSort) to Roux.Html
- Routes: / (alpha), /sorted/tags, /sorted/course
- AlphaSort: recipes sorted by title (from metadata) or filename
- TagSort: recipes grouped under tag headings, empty state when none
- CourseSort: recipes grouped by course/category metadata
- Navigation bar in index page with active-highlighted buttons
- Add metaCourse field to Metadata model (+ emptyMetadata update)
- Add nubOrd helper for deduplicating tag/course lists
- All 26 tests passing, hlint clean
2026-05-19 08:16:58 -04:00
jbrechtel 95a7521584 Fix all hlint warnings
- Html.hs: replace lambdas with section/composition where possible
- Html.hs: replace case/fromMaybe with fromMaybe
- Html.hs: add fromMaybe to imports, use unqualified
- Parser.hs: replace span with break (2 locations)
- Parser.hs: replace if/then/else with list comprehension
- Parser.hs: remove redundant catch-all patterns (break is exhaustive)
- Server.hs: eta reduce htmlResponse
- Add .hlint.yaml to suppress 'Avoid lambda' (suggested fix has
  precedence issues with blaze-html's infix (!) operator)
- All 26 tests passing, hlint: No hints, exit code 0
2026-05-19 07:25:59 -04:00
jbrechtel b775fd4f3f Recipe detail page with server-side HTML rendering
- Add Roux.Html.recipePage: full recipe detail page with:
  - Back navigation link
  - Title and metadata (servings, times, difficulty, etc.)
  - Ingredients summary list with quantities
  - Sections with named headings
  - Steps with inline element rendering:
    - Ingredients highlighted in pumpkin (with quantity badge)
    - Cookware in violet italic
    - Timers in azure with ⏱ icon
    - Comments in sand italic
    - Recipe refs as links
    - Line breaks
- Update Roux.Server: route /recipes/FILENAME to recipe page
  - Case-insensitive filename lookup, .cook extension optional
  - URL decoding for filenames
- Fix ingredient name parsing: restrict name characters to
  alphaNum + spaces/hyphens/apostrophes, preventing greedy
  consumption across special chars like ) and #
- Split name chars into pMultiNameChar (with spaces, before braces)
  and pSingleNameChar (without spaces, fallback)
- Suppress -Wname-shadowing in Html.hs (blaze-html exports clash
  with common variable names)
- All 26 tests passing, server smoke-tested with examples
2026-05-19 07:12:14 -04:00
jbrechtel 4943d4d42e Web server with recipe index page
- Add Roux.Server: WAI application using wai+warp directly
  - Scans .cook files from recipe directory on startup
  - GET / → HTML index page listing all recipes
  - Other routes → 404
- Add Roux.RecipeIndex: directory scanning and recipe parsing
  - scanRecipes: lists .cook files, parses each, extracts titles
  - Falls back to filename when metadata title is absent
- Add Roux.Html: blaze-html rendering with Pico CSS
  - indexPage: renders recipe list with links
  - urlEncode: percent-encodes filenames for URLs
- Update Roux.hs: re-exports Server.app and Types
- Add filepath to dependencies
2026-05-19 07:04:22 -04:00