Commit Graph

37 Commits

Author SHA1 Message Date
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
jbrechtel 6de6b6e2b2 Add LLM prompts for converting web recipes to Cooklang
- prompts/cooklang-converter.md: Comprehensive guide with full syntax
  reference, examples, and 12 conversion guidelines
- prompts/system-prompt.md: Concise system prompt for direct API use
- prompts/user-prompt-template.md: Template with {{PLACEHOLDER}}s for
  injecting specific recipe text
- prompts/test-cases.md: 3 test cases with source recipes and expected
  Cooklang output covering fractions, multi-word ingredients, sections,
  timers, notes, and complex multi-component recipes
2026-05-19 07:53:09 -04:00
jbrechtel d52de3679e scripts/run: mount recipe directory and publish port
- scripts/run now takes a recipe directory as its first argument,
  resolves it to an absolute path, and mounts it at /recipes in the
  Docker container
- Parses --port flag to map container port 8080 to the requested
  host port (default 8080)
- Passes --recipe-dir /recipes --port 8080 to the server
- Passes --host and any additional args through to the server
- Exits with an error if the recipe directory doesn't exist
- Also accept --host flag and pass remaining args through

Example: ./scripts/run ~/my-recipes --port 9090
2026-05-19 07:29:04 -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
jbrechtel ffb1931c7d Phase 5: Compound quantities, trailing-unit format, and edge cases
- Add splitAmountUnit to parse quantities with unit attached without
  % separator (e.g., @water{1,1/2cups}, @gelatine{3tsp})
- Add isNumericChar helper for detecting numeric quantity characters
- Add compound quantity test (1,1/2 cups = 3/2)
- Add no-separator unit test (3tsp)
- All 26 tests passing, all 4 example files parse correctly
2026-05-18 23:02:53 -04:00
jbrechtel c68d80e9c1 Phase 4: Recipe references + all 4 example file validation
- Fix pRecipeRef to include ./ prefix in stored path
- Add recipe reference test
- Add parse-validation tests for all 4 example .cook files
  (EasyPancakes, FriedRice, CoffeeSouffle, OlivierSalad)
- All 24 tests passing, all examples parse without error
2026-05-18 22:59:44 -04:00
jbrechtel b5482d8785 Phase 3: End-of-line and inline block comment parsing
- Add pEndComment parser for -- end-of-line comments
- Add pInlineComment parser for [- ... -] block comments
- Update pText to stop before -- and [- sequences using notFollowedBy
- Add tests for both comment types (2 new tests, 19 total)
2026-05-18 22:58:01 -04:00
jbrechtel e63aeedc39 Phase 2: Ingredient, cookware, and timer parsing
- Add ingredient parser: @name{quantity%unit}(preparation)
  - Handles single-word names without braces
  - Handles multi-word names with braces
  - Handles quantity (with unit) and preparation text
- Add cookware parser: #name or #multi word name{}
  - Uses shared pNameWithBraces helper
- Add timer parser: ~name{duration%unit} or ~{duration%unit}
  - Separate named/unnamed timer branches for correct brace handling
- Add quantity parsing: {2}, {1%kg}, {1/2%tbsp}, {=1%tsp}
  - Handles simple integers, fractions (1/2), compound (1,1/2)
- Extract shared pNameWithQuantity and pNameWithBraces helpers
- Update EasyPancakes tests to verify ingredient/cookware/timer parsing
- All 17 tests passing
2026-05-18 22:51:24 -04:00
jbrechtel 30dd268f2c Phase 1: Model update + file-level parser (text-only steps)
- Update Roux.Types: add SectionBodyItem (SecStep/SecComment/SecNote),
  StepEndComment, remove StepNote (notes are now section-level)
- Rewrite Roux.Parser using Parsec for inline elements, text-splitting
  for file-level structure
- Handle full-line -- comments, > notes, = Name = section headers
- Handle = Name (w/ and w/o closing =), == Name == variations
- Handle end-of-line backslash line breaks within steps
- Update tests to match new model + 3 EasyPancakes integration tests
- Add expectTextStep helper at module level
2026-05-18 22:41:38 -04:00
jbrechtel 9759a4c3a3 Add fourmolu + hlint with scripts and git hooks (Atlas-style)
- Create scripts/build, scripts/test, scripts/run, scripts/install-hooks
- Create git-hooks/pre-commit (auto-format staged .hs files)
- Create git-hooks/pre-push (check formatting + hlint before push)
- Handle git worktrees correctly in hook installation
- Run fourmolu on all source files to fix existing formatting
2026-05-18 22:24:59 -04:00
jbrechtel 235adce596 Add minimal Cooklang parser with first tests
- Create Roux.Parser with parseCookFile (splits paragraphs, treats
  each as a plain-text step, no metadata parsing yet)
- Add emptyMetadata to Roux.Types for convenience
- Add test dependencies: hspec, tasty-golden, tasty-hspec
- Add ParserSpec with 4 tests covering single step, multiple steps,
  empty input, and whitespace trimming
- Wire ParserSpec into test runner via tasty-hspec
2026-05-18 22:21:32 -04:00
jbrechtel e907a03b09 Define Cooklang data model and add DerivingStrategies + containers deps
- Add full Cooklang (.cook) recipe data model to Roux.Types:
  Recipe, Section, Step, StepItem (with StepText/StepIngredient/
  StepCookware/StepTimer/StepRecipeRef/StepNote/StepComment/StepBreak),
  Ingredient, Cookware, Timer, RecipeRef, Quantity, Duration, Metadata
- Enable DerivingStrategies extension project-wide
- Add containers to build dependencies
2026-05-18 22:15:21 -04:00
jbrechtel 130dc5326d Haskell project 2026-05-18 22:00:30 -04:00
jbrechtel 39026e3f35 AGENTS.md and hs script 2026-05-18 21:48:59 -04:00
jbrechtel 321df6badc Elaborates more of the project description 2026-05-18 21:44:55 -04:00
jbrechtel aeb032209c Initial commit 2026-05-18 21:40:40 -04:00