docs: add design and plan for inline title editing
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user