Commit Graph

89 Commits

Author SHA1 Message Date
jbrechtel d29d8b0af0 fix: separate steps with blank lines in Cooklang renderer
Build and Deploy / build-and-deploy (push) Successful in 1m17s
The Cooklang parser (splitByBlankLines) identifies step boundaries by
blank lines (\n\n). renderSection was joining body items with a single
newline (\n), causing multiple steps to be parsed as one. Fixed by using
\n\n as the separator between body items, matching the spec.
2026-05-20 16:04:16 -04:00
jbrechtel c33a51d80b test: add step count preservation tests for SchemaOrg → Cooklang conversion
Build and Deploy / build-and-deploy (push) Successful in 1m14s
Adds checkStepCount helper that verifies the number of instructions in
the parsed schema.org recipe matches the number of SecStep items in
the Method section of the resulting Cooklang Recipe. Tests both
fried-rice (4 steps) and carrot-risotto (7 steps).
2026-05-20 15:57:14 -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 d77f4eb59f fix: persist SSE debounce across page reloads via sessionStorage
Build and Deploy / build-and-deploy (push) Successful in 1m20s
The in-memory lastReload variable was lost on location.reload() because
the JavaScript context is destroyed. This caused a reload loop when the
watcher fired multiple SSE events for the same file change — each reload
reset the debounce to 0, so the next event would trigger another reload.

Switch to sessionStorage, which persists across page loads within the
same browser tab session. Also increases the debounce window to 3000ms
to account for page load time.
2026-05-20 15:42:01 -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 72535fb3ec fix: disable stdout/stderr buffering for Docker log visibility
Build and Deploy / build-and-deploy (push) Successful in 1m23s
2026-05-20 14:38:52 -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 81723622be chore: re-export CooklangPrint from Roux module
Build and Deploy / build-and-deploy (push) Successful in 1m57s
2026-05-20 13:23:12 -04:00
jbrechtel 6e895f7286 feat: add /import route for recipe URL import 2026-05-20 13:22:27 -04:00
jbrechtel 4738972046 feat: add import form and result page templates 2026-05-20 13:07:31 -04:00
jbrechtel e1c2a4bdc5 feat: add CooklangPrint module for rendering Recipe to .cook text 2026-05-20 13:06:23 -04:00
jbrechtel 751a9f853c docs: add recipe import design spec
Build and Deploy / build-and-deploy (push) Successful in 1m17s
2026-05-20 12:55:21 -04:00
jbrechtel 602324a699 chore: use persistent Docker volume for stack-root instead of host directory
Build and Deploy / build-and-deploy (push) Successful in 1m36s
This allows all git worktrees to share a single cached GHC and
dependency store, avoiding recompilation per worktree.
2026-05-20 12:09:03 -04:00
jbrechtel 2628fcf6d9 live update docs
Build and Deploy / build-and-deploy (push) Successful in 1m22s
2026-05-20 11:57:58 -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 71cef21daf feat: add SSE client JS for live recipe reload on detail and index pages
Build and Deploy / build-and-deploy (push) Successful in 1m39s
2026-05-20 10:26:50 -04:00
jbrechtel f5c693a2b5 feat: add SSE endpoint and ChangeLog for live recipe reload 2026-05-20 10:21:57 -04:00
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