Files
roux/docs/specs/2026-05-25-cooking-log-design.md
T

4.5 KiB

Cooking Log Design

Record dates and comments when cooking a recipe, viewable on the recipe page and across all recipes by date.

Data Model

SQLite schema

A single cook_log table:

CREATE TABLE IF NOT EXISTS cook_log (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    recipe_filename TEXT NOT NULL,
    cooked_date     TEXT NOT NULL,   -- ISO date (YYYY-MM-DD)
    comment         TEXT NOT NULL DEFAULT '',
    created_at      TEXT NOT NULL    -- ISO datetime
);

CREATE INDEX idx_cook_log_recipe ON cook_log(recipe_filename);
CREATE INDEX idx_cook_log_date   ON cook_log(cooked_date DESC);
  • recipe_filename is the lowercased filename with .cook stripped, matching the key used by the recipe index.
  • cooked_date stores the user-picked date (not necessarily today).
  • comment is optional (defaults to empty string).

Haskell types

New module Roux.CookLog:

data CookEntry = CookEntry
    { ceId             :: Int
    , ceRecipeFilename :: Text
    , ceCookedDate     :: Day
    , ceComment        :: Text
    , ceCreatedAt      :: UTCTime
    }
  • Day (from Data.Time.Calendar) for dates.
  • UTCTime for created_at.

API Endpoints

All routes live in Roux.Server following existing patterns.

Method Path Purpose
POST /cook-log/{filename} Record a cooking event Form body: cooked-date (required, YYYY-MM-DD) + comment (optional). Returns JSON { ok: true } on success, { error: "..." } on failure. Multiple entries for the same date are allowed — no uniqueness constraint.
GET /cook-log/{filename} Fetch entries for one recipe JSON array of CookEntry, sorted by cooked_date DESC
GET /cook-log Fetch all entries JSON array of CookEntry with recipe info, sorted by cooked_date DESC, for cross-date view

DB connection

SQLite database file at <recipeDir>/cook-log.db. Opened once at server startup, passed to handlers via a shared value (same IORef pattern as the recipe state map).

Library: sqlite-simple

Minimal dependency. Pure SQL, typed results via FromRow instances.

dependencies:
  - sqlite-simple

Frontend Rendering

"I cooked this" form at the top of the marginalia section

<h2>Cook history</h2>
<form class="roux-cook-log-form" data-filename="pancakes">
  <input type="date" name="cooked-date" value="2026-05-25">
  <input type="text" name="comment" placeholder="Optional note..." maxlength="500">
  <button type="submit">Log it</button>
</form>
  • <input type="date"> gives a native date picker, defaults to today.
  • Comment is an optional text input, max 500 chars.
  • Form POSTs via fetch() with FormData, no page reload.
  • On success, JS appends the new entry to the entry list below.
  • On error, JS shows the error message inline.

Entry list in the marginalia column

── Mar 15 ──
Used baking powder instead of soda, turned out fine

── Mar 2 ──
Added extra vanilla

── Feb 20 ──

Rendered style:

  • Date centered between dashes, bold/small.
  • Comment below, plain text.
  • No comment = just the date separator line.

Recipe page rendering

recipePage in Html.hs accepts cook entries as an additional parameter (or fetches them server-side during page render). The log section renders inside the marginalia column, after Tags and before Notes.

JavaScript

Lightweight inline script (new constant alongside recipeImageUploadJs):

document.querySelector('.roux-cook-log-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const form = e.target;
  const fd = new FormData(form);
  const res = await fetch('/cook-log/' + form.dataset.filename, { method: 'POST', body: fd });
  if (!res.ok) return;
  const entry = await res.json();
  // Prepend rendered entry to the list
});

Cross-Date View

  • Page at /cook-history showing all log entries grouped by month, newest first.
  • Each entry shows: date, recipe name (linked), comment (if any).
  • Accessible from a link in the main navbar ("Cook History").
  • SSR rendered (no additional JS needed).

Implementation Order

  1. Add sqlite-simple dependency and Roux.CookLog module (types + DB ops)
  2. Initialize DB at server startup (create table, open connection)
  3. Implement POST/GET endpoints for single-recipe log entries
  4. Integrate cook log entries into recipe page SSR in marginalia column
  5. Add "I cooked this" form with JS submit handler
  6. Implement cross-date view at /cook-history
  7. Add nav link to cook history