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