docs: add cooking log design spec

This commit is contained in:
2026-05-25 21:45:49 -04:00
parent ed015d3799
commit f2bd667b75
2 changed files with 147 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
# 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:
```sql
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`:
```haskell
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
```html
<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`):
```javascript
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