Commit Graph

117 Commits

Author SHA1 Message Date
jbrechtel 7a6d3653a7 Merge branch 'live' into main — live recipe watching via fsnotify
Build and Deploy / build-and-deploy (push) Successful in 4m30s
2026-05-20 06:30:07 -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 a7434eced7 Merge branch 'utf8'
Build and Deploy / build-and-deploy (push) Successful in 1m43s
2026-05-19 23:29:45 -04:00
jbrechtel 5bfd3f2308 fix: use absolute dir path for watcher makeRelative consistency 2026-05-19 23:28:51 -04:00
jbrechtel 166a2e7800 Handles UTF8 recipe files 2026-05-19 23:28:37 -04:00
jbrechtel 8f8dbecea5 refactor: replace aeson ToJSON/FromJSON with json-fleece schemas
- RecipeSearchEntry: replace genericToJSON ToJSON with Fleece schema
  using object combinators (required, optionalNullable, list text)
- SchemaOrgRecipe: replace manual FromJSON instance with Fleece decoder
  that reuses existing helper functions for quirky fields
- Html.hs: use Fleece.Aeson.Encoder.encode instead of Data.Aeson.encode
- stack.yaml: add shrubbery + json-fleece extra-deps
- package.yaml: add json-fleece-aeson + json-fleece-core deps
- Remove unused LANGUAGE pragmas and redundant brackets
2026-05-19 23:24:24 -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 a8402cf831 chore: add fsnotify dependency for live recipe watching 2026-05-19 23:08:02 -04:00
jbrechtel baa3c2434f feat: add recipe scraping script and update Docker image
Build and Deploy / build-and-deploy (push) Successful in 1m55s
- scripts/scrape-recipe: Python script using recipe-scrapers to extract
  schema.org JSON-LD from a recipe URL (or local HTML file for testing)
- Dockerfile: add Python3 + recipe-scrapers in virtualenv, install the
  scrape-recipe script at /usr/local/bin/scrape-recipe
2026-05-19 23:07:53 -04:00
jbrechtel 924aff38ec Don't lint in CI
Build and Deploy / build-and-deploy (push) Successful in 4m5s
2026-05-19 23:05:58 -04:00
jbrechtel f9ee6240c3 feat: add deployment configuration
Build and Deploy / build-and-deploy (push) Failing after 1m39s
- Dockerfile: production image for roux-server (Debian slim, tini, binary)
- docker-compose.yml: production compose with recipe/data volume mounts
- .gitea/workflows/deploy.yaml: Gitea Actions pipeline that builds, tests,
  pushes Docker image, and deploys via SSH

Follows the same pattern used by the Atlas project.
2026-05-19 22:58:52 -04:00
jbrechtel 042bcc9e51 Add implementation plan for live recipe watching 2026-05-19 22:58:07 -04:00
jbrechtel a70cd86afd fix: hlint warnings in SchemaOrg module
- Remove unused DeriveGeneric pragma
- Use newtype for SchemaOrgPerson and SchemaOrgImageObject
- Remove redundant brackets around string literal
2026-05-19 22:54:46 -04:00
jbrechtel c7bff79371 Add design doc for live recipe watching via fsnotify 2026-05-19 22:53:55 -04:00
jbrechtel 340b95cf79 chore: format with fourmolu and add SchemaOrg to cabal 2026-05-19 22:52:12 -04:00
jbrechtel 73d6e870fe feat: re-export SchemaOrg types from Roux module 2026-05-19 22:51:50 -04:00
jbrechtel 3e55f0a7d2 feat: add schemaOrgToCooklang conversion function 2026-05-19 22:50:48 -04:00
jbrechtel 1addddd8fc feat: add parseISODuration for ISO 8601 duration parsing 2026-05-19 22:46:57 -04:00
jbrechtel 9586afcb46 feat: add SchemaOrgRecipe types and FromJSON parsing 2026-05-19 22:44:03 -04:00
jbrechtel 09952f0c43 Add implementation plan for schema.org JSON-LD recipe types 2026-05-19 22:37:08 -04:00
jbrechtel 7568f7725e Add design doc for schema.org JSON-LD recipe types 2026-05-19 22:33:02 -04:00
jbrechtel 431386b292 feat: style search input with color scheme and auto-focus on load
- Adds CSS for input[type=search] using theme colors (bg, text, accent)
- Adds autofocus attribute so search box is focused on page load
2026-05-19 22:20:15 -04:00
jbrechtel cef407c31b fix: escape single quotes in JS renderFlatList string
Haskell\'s \' within double-quoted string literals is interpreted as
just \' (no-op escape). Switched to \\' to produce \' in the JS
output, which correctly escapes the single quotes inside the
single-quoted JS string literal for the CSS class name.
2026-05-19 22:00:46 -04:00
jbrechtel f74c431588 chore: add fourmolu pre-commit hook
Installs a pre-commit hook that checks all staged .hs files are
formatted by fourmolu before allowing a commit. Configured via
core.hooksPath = .githooks.

Run: ./hs fourmolu -i <files>  to format
2026-05-19 21:56:27 -04:00
jbrechtel 8105cee498 fix: use preEscapedText for inline JS script content
H.toHtml was escaping the JS content, causing the script tag to be empty.
H.preEscapedText preserves the text as-is, which is correct for script tags.
2026-05-19 21:55:21 -04:00
jbrechtel 6970725a35 chore: add implementation plan for type-ahead search 2026-05-19 21:41:41 -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 d5e9c7e0d3 feat: replace server-rendered index with JS-rendered shell page
- Rewrites indexPage to render a shell page with embedded JSON + JS
- Adds searchJs with vanilla JS for client-side list rendering, filtering, sorting
- Hash-based sort modes (#alpha, #tags, #course)
- Search filters by title and filename (case-insensitive)
- Fix: recipe links use /recipes/ prefix and encodeURIComponent for # encoding
- Fix: avoid double-render on initial page load
2026-05-19 21:40:02 -04:00
jbrechtel 954bad87bd feat: replace server-rendered index with JS-rendered shell page 2026-05-19 21:35:52 -04:00
jbrechtel 632f61820b feat: add RecipeSearchEntry JSON encoding for search payload 2026-05-19 21:29:41 -04:00
jbrechtel 61484cecc5 Add design doc for type-ahead search feature 2026-05-19 21:21:10 -04:00
jbrechtel 43e1299616 Formats code 2026-05-19 20:21:43 -04:00
jbrechtel f8114af4a9 Add nytToCooklang conversion from NYTRecipe to Data.CookLang.Recipe
- Add nytToCooklang function to Roux.NYTimes
- buildMetadata: maps title, URL, description, time, servings
- buildSections: creates sections for ingredient groups then step groups
- ingredientGroupToSection: converts NYT ingredient items to
  Cooklang step items with @ingredient references
- stepGroupToSection: converts method steps into Cooklang steps
- Add parseDuration, parseServings, parseNYTQuantity helpers
- Add extractIngredientName heuristic for core ingredient extraction
- Add 2 conversion tests verifying both example recipes
- All 32 tests passing, hlint clean
2026-05-19 16:03:43 -04:00
jbrechtel 4055bf62c1 Extract Cooklang types into Data.CookLang module
- Create src/Data/CookLang.hs with all recipe types (Recipe, Section,
  StepItem, Ingredient, Cookware, Timer, RecipeRef, Quantity, Duration,
  Metadata, emptyMetadata)
- Data.CookLang has zero Roux imports — can be extracted to its own
  package
- Roux.Types now just re-exports from Data.CookLang
- Update all Roux.* modules and tests to import Data.CookLang directly
- All 30 tests passing, hlint clean
2026-05-19 15:59:59 -04:00
jbrechtel 21a4cea083 Add NYTimes recipe extraction from __NEXT_DATA__ JSON tag
- Add Roux.NYTimes module with:
  - extractNextData: finds <script id="__NEXT_DATA__"> tag in HTML
    and parses its JSON content via aeson
  - parseNYTRecipe: maps NYTimes recipe JSON structure into a
    typed intermediate representation (NYTRecipe, NYTIngredientGroup,
    NYTIngredientItem, NYTStepGroup, NYTStepItem)
- Supports all NYTimes Cooking data: title, URL, total time, yield,
  topnote, ingredient groups (with section names), step groups
- Add aeson to dependencies
- Add 4 tests: extraction + parsing for both fried-rice and
  carrot-risotto example HTML files
- Wire NYTimesSpec into test runner
2026-05-19 15:51:37 -04:00
jbrechtel 262faca90e Render source metadata as '↗ Original recipe' link next to title
- When recipe has a source: field in YAML metadata, shows a link
  to the right of the recipe title
- Link opens in new tab with ↗ icon and 'Original recipe' text
- Hidden when no source is present
- Title row uses flexbox: title on left, source link on right
2026-05-19 15:31:12 -04:00
jbrechtel 3a1bc37b45 Formats repo 2026-05-19 15:27:39 -04:00
jbrechtel f902ec65b9 Natural ingredient quantity rendering
- showQuantity now produces natural text (4 cups, 125 g, 1/2 tsp)
  instead of Cooklang internal format (4%cup, 125%g, 1/2%tsp)
- Add pluralize helper: cups, tbsp, lbs, pinches, etc.
  Handles common units; unknown units stay unchanged
- Add formatFraction: displays 3/2 as '1 1/2', 1/2 as '1/2'
- No % signs in displayed quantities
2026-05-19 13:34:23 -04:00
jbrechtel 1314c38e85 Commit pending changes: check-recipes tool and reconstructed test file 2026-05-19 13:29:27 -04:00
jbrechtel 2641c84ee3 Remove local-only symlink and debug file 2026-05-19 12:58:49 -04:00
jbrechtel c55a54796a Fix parser for personal recipes: newlines, ranges, &, markdown-style headings
Three categories of fixes:

1. Multi-line steps: removed \n from pText exclusion list so newlines
   within a step are consumed as regular text
2. Range timers (e.g. ~{14-18%minutes}): handle hyphen-separated
   ranges in parseSimpleRational by taking the lower value
3. & in ingredient names: added & to pSingleNameChar and
   pMultiNameChar char sets
4. Markdown-style # headings: added pAnyChar fallback so # followed
   by invalid cookware name falls through to text consumption
5. Note detection: tightened > check to require >  (with space)
   or > followed by non->, preventing >> metadata from being
   treated as notes
2026-05-19 12:58:43 -04:00
jbrechtel 1e150e703e Replace Pompiere font with Lexend Peta for headings
- Updated Google Fonts import
- h1, h3 now use Lexend Peta (sans-serif, 400 weight, 0.02em letter-spacing)
- Updated utility class name
2026-05-19 10:57:37 -04:00
jbrechtel 5eafbbe14c Add YAML front matter parsing and use metadata titles in UI
- Add extractFrontMatter: parses YAML front matter (---...---) into
  a Metadata value, returns remaining body for Cooklang parsing
- Add parseYamlLines: extracts known keys (title, author, source,
  difficulty, cuisine, course, diet, tags, image, description)
  into canonical Metadata fields
- Add parseTagList: handles both [tag1, tag2] and tag1, tag2 formats
- Index page recipeItem now takes RecipeInfo and displays riTitle
  (from metadata) instead of raw filename
- All three sort modes (alpha, tag, course) use title-aware display
- OlivierSalad example shows its metadata title correctly
2026-05-19 10:15:14 -04:00
jbrechtel 9e15126374 Custom checkbox with dark checkmark
- Replace native checkbox with appearance:none custom styling
- Unchecked: transparent bg, terracotta border
- Checked: terracotta background, dark checkmark (✓) in #3D2C1E
- Same styling for both ingredient rows and method steps
2026-05-19 10:07:04 -04:00
jbrechtel 4f02599e63 Gray checkbox styling and checked-state dimming
- Checkbox: accent-color terracotta, border rgba(184,92,56,0.4),
  transparent background (not dark/black)
- Checked ingredient rows: :has(input:checked) → opacity 0.45
- Checked steps: :has(input:checked) → opacity 0.45
- Removed unused .step.done classes
2026-05-19 10:03:58 -04:00
jbrechtel 0bb198555b Use Quicksand for body, Pompiere for headings
- Import both Google Fonts in one @import
- body: Quicksand (sans-serif) for main content
- h1, h3: Pompiere (cursive) for titles and section headers
- h2: Quicksand 600 weight, uppercase for subsection labels
- Add .pompiere and .quicksand utility classes
2026-05-19 09:59:59 -04:00
jbrechtel d1582e9034 Recipe page restyled to match mockup design
- Add Pompiere font from Google Fonts
- Color theme: #F5EFE0 (bg), #3D2C1E (text), #B85C38 (accent)
- Two-column layout (ingredients | method) matching mockup
- Horizontal metadata bar: Serves, Prep, Cook, Total
- Course badge and description paragraph
- Ingredients: checkbox rows with quantity prefix, grouped by section
- Method: numbered steps with checkboxes
- Notes: ¶ ornament + paragraph style
- Index page: styled navbar with sort mode buttons
- Index page: styled recipe list with separators
- Custom CSS via internal stylesheet (no Pico CSS override except
  link for reset/utility classes)
- Add LambdaCase and TupleSections to default extensions
- All 26 tests passing, hlint clean
2026-05-19 09:54:08 -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