docs: add cooking log design spec
This commit is contained in:
@@ -9,3 +9,4 @@ build/
|
||||
.pi/
|
||||
__pycache__
|
||||
.worktrees/
|
||||
.superpowers/
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user