docs: add design and plan for inline title editing

This commit is contained in:
2026-05-21 21:28:00 -04:00
parent d64129fc9f
commit 59fd96b57d
2 changed files with 445 additions and 0 deletions
@@ -0,0 +1,78 @@
# Inline Recipe Title Editing
Date: 2026-05-21
## Summary
Add in-page editing for recipe titles. Clicking the recipe title on the detail page makes it editable inline (using `contenteditable`). Pressing Enter or clicking away saves the change via a PATCH request. Escape cancels. The DOM updates in-place on success — no page reload.
## Motivation
This is the first step toward making all recipe fields editable in-page, keeping the layout stable during editing.
## Client-side interaction
1. The `<h1>` recipe title on the recipe detail page gets a click handler.
2. On click:
- `contenteditable` is set to `true` on the `<h1>`.
- The element receives focus (via `.focus()` at the end of the text).
- A CSS class `.roux-editing` is added for a subtle visual cue (faint dashed border / changed cursor).
- A hidden `<span id="roux-title-original">` stores the original text for revert.
3. On **Enter** (without Shift): prevent default newline, trigger save.
4. On **Escape**: revert to the original text, remove `contenteditable`, remove `.roux-editing`.
5. On **blur** (clicking away): trigger save.
6. **Save flow**:
- Read the current text content from the `<h1>`.
- If unchanged from original, do nothing (just exit edit mode).
- Send `PATCH /recipes/{filename}/title` with JSON body `{ "title": "New Title" }`.
- On 200 OK: update the `<h1>` text to the server's response (the server normalizes/trims it), remove `contenteditable`, remove `.roux-editing`.
- On error: revert to original text, remove edit mode, show a brief error indication.
7. **Hover hint**: a subtle pencil icon or "Edit" text appears next to the title on hover (CSS only — no extra JS needed).
## Server-side
### New route: `PATCH /recipes/{filename}/title`
**Handler**: `titleUpdateHandler` in `Roux.Server` module.
1. Validate HTTP method is PATCH (return 405 otherwise).
2. Parse JSON body: expects `{ "title": "..." }`. Return 400 if missing or invalid.
3. Look up the recipe file:
- Strip `.cook` extension, lowercase, same pattern as existing `lookupRecipe`.
- Read the `.cook` file from disk.
4. Update the title in the YAML front matter:
- Find the `title:` line in the front matter block (text between the opening `---\n` and `\n---`).
- Replace its value with the new title.
- If no `title:` field exists, insert one after the opening `---`.
- Write the file back to disk.
5. The existing fsnotify watcher picks up the change and updates the shared IORef state. SSE events notify other connected clients.
6. Return `200 OK` with JSON `{ "success": true, "title": "<normalized title>" }`.
### Error handling
- Recipe file not found → 404.
- File read/write error → 500 with JSON `{ "error": "Could not update recipe file" }`.
- Empty title (after trimming) → 400 with JSON `{ "error": "Title cannot be empty" }`.
- Client-side: on any error, revert to original text and show a brief visual error state.
### YAML update logic
Reuses the same pattern as the existing `updateImageMetadata` / `addOrUpdateYamlKey` functions in `Server.hs`. A dedicated `updateTitleInCookFile` function reads the file, performs the replacement, and writes it back.
## Files changed
- **`src/Roux/Server.hs`**: Add `titleUpdateHandler` and route registration.
- **`src/Roux/Html.hs`**: Add inline JS for title editing (`titleEditJs`), add CSS for `.roux-editing` state and hover hint.
## What's not included (future steps)
- Editing other recipe fields (source, description, servings, etc.)
- Debouncing or optimistic updates
- Keyboard shortcut indicators
## Self-review
- No placeholders or TODOs.
- Internally consistent: client save matches server endpoint, error handling is symmetric.
- Scoped appropriately for a single implementation plan (one server handler + one client JS block).
- No ambiguity: `contenteditable` behavior, PATCH route, YAML update pattern are all explicit.