Add planning docs: research, requirements, redesign plan
Research of current state, requirements for the web-first pivot (pantry polish -> auth -> MCP), and the phased implementation plan. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
173e74b4a9
commit
25a09b08fc
@@ -0,0 +1,231 @@
|
||||
# Implementation Plan
|
||||
|
||||
> Written 2026-06-22. Turns `requirements.md` into concrete, sequenced work.
|
||||
> Reflects the current priority order: **pantry polish → auth → MCP**, with the
|
||||
> MCP being the highest-value piece (Claude reads *and updates* the pantry, so
|
||||
> manual upkeep mostly goes away). Builds on the completed `/simplify` pass
|
||||
> (shared `kitchen/helpers.py`, dead config removed).
|
||||
>
|
||||
> Altitude: file/model/endpoint level. Not code — decisions and steps.
|
||||
|
||||
## Sequencing at a glance
|
||||
|
||||
| Phase | Goal | Risk | Depends on |
|
||||
|------|------|------|-----------|
|
||||
| 0 | ✅ Commit the simplify baseline — **done** | none | — |
|
||||
| 1 | Pantry: mobile-first, In/Low/Out, fast add | medium (model + UI) | 0 |
|
||||
| 2 | Auth (login-once) + prod baseline | low | — (can run parallel to 1) |
|
||||
| 3 | MCP server — read + update pantry from claude.ai | medium | 1, 2 |
|
||||
|
||||
Deferred (not in this plan): web what-can-i-cook rework, §4.1 substitutions,
|
||||
shopping flow rethink, web cook-logging. They stay roughly working but are not
|
||||
invested in here.
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Commit the baseline ✅ done
|
||||
|
||||
Tom has committed the simplify baseline. Starting point is clean.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Pantry polish (mobile-first)
|
||||
|
||||
The heart of the friction problem. Move from "spreadsheet of decimals" to
|
||||
"glance into the fridge", and rebuild the screen phone-first.
|
||||
|
||||
### 1.1 Model change — add presence state
|
||||
|
||||
`PantryItem` today: `quantity` (required Decimal), `unit`, `location`,
|
||||
`expiry_date`, `is_staple`.
|
||||
|
||||
- [ ] Add `state` to `PantryItem`: `CharField(choices=IN/LOW/OUT, default="in")`.
|
||||
New `TextChoices` like the existing `Location`.
|
||||
- [ ] Change `quantity` from `DecimalField` to an **optional integer**
|
||||
(`PositiveIntegerField(null=True, blank=True)`) — never going to have
|
||||
1.234 pieces of toast. Keep `unit` too, but neither is required on add.
|
||||
(Migration handles the Decimal→Integer + nullable change plus the
|
||||
backfill below in one step.)
|
||||
- [ ] Migration `0003_pantryitem_state_optional_quantity`. Data migration:
|
||||
backfill existing rows → `state = "out"` where `quantity == 0`, else `"in"`.
|
||||
- [ ] Decide the In/Low/Out ↔ legacy `quantity==0` / `is_staple` mapping in one
|
||||
place (see 1.5 — it ripples into matcher + shopping queries).
|
||||
|
||||
> **Decided:** quantity becomes an optional **integer** (not a decimal, not
|
||||
> free text). It's secondary metadata; In/Low/Out is the primary signal.
|
||||
|
||||
### 1.2 Presence filtering — stop relying on `quantity__gt=0`
|
||||
|
||||
Several queries use `quantity__gt=0` to mean "I have this". With optional
|
||||
quantity that breaks (null ≯ 0). Replace presence checks with state.
|
||||
|
||||
- [ ] `_pantry_context` (`views_htmx.py`): show **all** items, don't filter out
|
||||
depleted ones — "Out" rows render greyed (requirements §2.1). Annotate
|
||||
`is_expired` / `expiring_soon` as today.
|
||||
- [ ] `helpers.get_pantry_total` and the matcher's presence logic
|
||||
(`what_can_i_cook`, `recipes_page`): treat `state != "out"` as "have it";
|
||||
tolerate `quantity = None`. Minimal change only — full presence-based
|
||||
matcher rework stays **deferred**. Goal here is just "don't crash / don't
|
||||
silently show everything as missing."
|
||||
|
||||
### 1.3 Pantry UI — mobile-first rebuild
|
||||
|
||||
> **Decided:** the current design is poor — do a proper rethink of the pantry
|
||||
> screen (layout, interaction, hierarchy), but **keep the palette** (Tom likes
|
||||
> it). Mockup goes first (1.3.0) before touching Django templates.
|
||||
|
||||
**1.3.0 Design mockup first.** Produce a self-contained HTML mockup of the
|
||||
redesigned pantry (phone width, real palette) as an Artifact Tom can open on
|
||||
his phone and react to. Only wire it into Django templates once the look is
|
||||
agreed.
|
||||
|
||||
Replace the table-of-tiny-links (`partials/pantry_table.html`) with a
|
||||
phone-first card list.
|
||||
|
||||
- [ ] Per-item **card** (single column): ingredient name, location badge, and a
|
||||
**3-way In / Low / Out button group** as the primary control — proper
|
||||
buttons, ≥44px tap targets, not inline links. Expiry shown only if set, as
|
||||
a soft "use soon" pill.
|
||||
- [ ] Grouping: fridge / freezer / cupboard sections, collapsible or just
|
||||
stacked. Keep it one-column; no horizontal scroll.
|
||||
- [ ] State change endpoint: `POST /app/pantry/<id>/state/` (`to=in|low|out`),
|
||||
re-renders the list partial via HTMX. Replaces the quantity-edit flow as
|
||||
the main interaction.
|
||||
- [ ] Keep delete and fridge↔freezer move, but as secondary (smaller, maybe
|
||||
behind a tap). Expiry edit becomes optional, de-emphasised.
|
||||
|
||||
### 1.4 Fast add
|
||||
|
||||
- [ ] **Autocomplete**: `GET /app/pantry/search/?q=` returns matching
|
||||
`Ingredient`s (name + aliases), rendered as an HTMX dropdown under the add
|
||||
box. Tap a result → adds with sensible defaults (unit/location from the
|
||||
`Ingredient`, `state="in"`, no qty required).
|
||||
- [ ] **Quick-add chips**: render a row of one-tap chips from staples +
|
||||
recently-used ingredients (recent = distinct ingredients from recent
|
||||
`PantryItem`/`CookLog`). One tap adds/sets to In.
|
||||
- [ ] **Add bar placement**: sticky to the **bottom** of the viewport (thumb
|
||||
reach) on mobile, with the chips just above it. This is the make-or-break
|
||||
detail per requirements.
|
||||
- [ ] `pantry_add` already auto-creates unknown ingredients and merges into an
|
||||
existing same-location row — keep that, but set `state="in"` on
|
||||
add/merge, and stop requiring quantity.
|
||||
|
||||
### 1.5 CSS / layout (base.html)
|
||||
|
||||
- [ ] Shift `base.html` to mobile-first: the current `max-width: 960px` desktop
|
||||
table styling becomes the *enhancement*; default styles target a narrow
|
||||
phone screen. Bigger touch targets, larger tap-friendly buttons, bottom
|
||||
nav or bottom add bar.
|
||||
- [ ] Keep the existing dark palette (it's fine) — this is layout/sizing, not a
|
||||
re-theme.
|
||||
- [ ] Verify on a real phone-width viewport (e.g. 390px) — no horizontal
|
||||
scroll, controls reachable one-handed.
|
||||
|
||||
### 1.6 Tests
|
||||
|
||||
`kitchen/tests.py` is empty. Add the first real tests here:
|
||||
|
||||
- [ ] Model: state defaults, optional quantity, backfill migration sanity.
|
||||
- [ ] Views: add via autocomplete sets `state="in"`; state toggle endpoint;
|
||||
pantry context includes "Out" rows.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Auth (login once) + prod baseline
|
||||
|
||||
Low risk, can proceed alongside Phase 1. Closes the wide-open `/app/`.
|
||||
|
||||
### 2.1 Session auth, long-lived
|
||||
|
||||
- [ ] One real user account (document the `createsuperuser` / set-password
|
||||
step; don't commit credentials).
|
||||
- [ ] Settings:
|
||||
- `SESSION_COOKIE_AGE = 60*60*24*365` (~1 year)
|
||||
- `SESSION_EXPIRE_AT_BROWSER_CLOSE = False`
|
||||
- `SESSION_SAVE_EVERY_REQUEST = True` (sliding expiry — each visit renews
|
||||
the year, so an active user is effectively never logged out)
|
||||
- `LOGIN_URL` / `LOGIN_REDIRECT_URL`
|
||||
- [ ] Login page: `django.contrib.auth.views.LoginView` + a minimal template in
|
||||
the existing dark style. Logout link somewhere unobtrusive.
|
||||
- [ ] Protect `/app/`: simplest is Django 5.x `LoginRequiredMiddleware`, but it
|
||||
hits *every* view — so exempt `/api/` (token auth keeps its own) and the
|
||||
login view. If exemptions get fiddly, fall back to wrapping the
|
||||
`urls_htmx` includes / decorating the page views with `login_required`.
|
||||
**Pick one approach; don't mix.**
|
||||
|
||||
### 2.2 Drop the CSRF hacks
|
||||
|
||||
- [ ] With a real session + CSRF token (already wired in `base.html`), remove
|
||||
every `@csrf_exempt` from `views_htmx.py`. Verify HTMX POST/DELETE still
|
||||
work (the `htmx:configRequest` handler already sends `X-CSRFToken`).
|
||||
|
||||
### 2.3 Production baseline (cheap, fold in here)
|
||||
|
||||
- [ ] `DEBUG` from env (default False in prod).
|
||||
- [ ] `SECRET_KEY` from env; drop the committed `django-insecure-…` literal.
|
||||
- [ ] Keep `ALLOWED_HOSTS` as-is.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — MCP server (the goal)
|
||||
|
||||
Let claude.ai "cooking mode" read the pantry and write changes back, over the
|
||||
existing `/api/` (token auth, `IsAuthenticated`). This is what kills the
|
||||
"recite my whole pantry every time" chore.
|
||||
|
||||
### 3.1 Shape
|
||||
|
||||
- [ ] **Separate FastMCP service** (decided). Mirrors a pattern Tom already
|
||||
runs elsewhere: a standalone "ai service" FastMCP server that talks to the
|
||||
Django API with the `caine` token. Keeps concerns decoupled; the API is
|
||||
already the right interface and the `/simplify` pass left it clean.
|
||||
- [ ] Tools to expose (map to existing endpoints):
|
||||
- `get_pantry` → `GET /api/pantry/` (+ state)
|
||||
- `add_to_pantry` → `POST /api/bulk-pantry-add/`
|
||||
- `set_pantry_state` → new tiny endpoint or `PATCH /api/pantry/<id>/`
|
||||
setting `state` (in/low/out) — the conversational update path
|
||||
("used the last of the noodles" → set Out)
|
||||
- `what_can_i_cook` → `GET /api/what-can-i-cook/`
|
||||
- `log_cook` → `POST /api/log-cook/` (and make it accept `rating`, the
|
||||
one API gap from research §5)
|
||||
- [ ] Add the `state` field to the pantry serializer so MCP reads/writes it.
|
||||
|
||||
### 3.2 Transport, hosting, auth — **decided**
|
||||
|
||||
- **Transport:** remote MCP over HTTP (streamable HTTP / SSE), reachable at
|
||||
**`food.jihakuz.xyz/mcp`**. Not stdio.
|
||||
- **Hosting:** same box as Django, behind nginx at `/mcp`, as its own systemd
|
||||
service alongside `food.service`.
|
||||
- **Auth:** **bearer token** — single user, so one long high-entropy token
|
||||
checked by the MCP service (and/or nginx) is enough. It must be tight: this
|
||||
endpoint is internet-exposed (unlike `/api/` and `/admin/`, which nginx
|
||||
blocks externally), so the token is the only thing protecting it. Strong
|
||||
token, kept out of git (env/secret), TLS only.
|
||||
|
||||
### 3.3 Why the cooking blocks still work (no action, just the rationale)
|
||||
|
||||
MCP only feeds data/tools into the claude.ai conversation; it doesn't change
|
||||
how cooking mode renders. So Claude pulls real pantry contents *and* still
|
||||
produces its formatted cooking blocks — grounded instead of guessed.
|
||||
|
||||
---
|
||||
|
||||
## Decisions (locked)
|
||||
|
||||
1. **Quantity** → optional **integer**, secondary to In/Low/Out. ✅
|
||||
2. **MCP** → FastMCP "ai service" on the same box at `food.jihakuz.xyz/mcp`,
|
||||
bearer-token auth, talks to the Django API. ✅
|
||||
3. **Design** → rethink the pantry screen, keep the palette; mockup first. ✅
|
||||
|
||||
Still just an implementation detail (not a blocker): **auth enforcement** —
|
||||
`LoginRequiredMiddleware` + exemptions vs per-view `login_required`. Plan: try
|
||||
middleware, fall back if exemptions get fiddly.
|
||||
|
||||
## Definition of done (per phase)
|
||||
|
||||
- **P1:** Tom can add an item and flip In/Low/Out on his phone in a couple of
|
||||
taps, one-handed, no horizontal scroll; pantry stays glanceable.
|
||||
- **P2:** Visiting `/app/` requires login; logging in once sticks for ~a year;
|
||||
no `@csrf_exempt` left; `DEBUG=False` + env `SECRET_KEY` in prod.
|
||||
- **P3:** In claude.ai cooking mode, Claude can list the pantry and mark items
|
||||
In/Low/Out (and log a cook) without Tom typing the inventory.
|
||||
Reference in New Issue
Block a user