feat(admin): notes edit/delete UI + fix broken create flow (#71)#78
Merged
Conversation
Extract the per-note markup into themes/default/admin/_note_item.html plus a new _note_edit_form.html. Add ThemeEngine.render_partial() so the admin notes endpoints can return self-contained HTML fragments without the heavy default render context (no nav/favicon/featured_posts fetches).
Partials live only in the default theme — the AsyncHybridLoader fallback means blue-tech and terminal pick them up automatically. The three admin templates now {% include %} the shared partial.
Refs #71
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Branch /admin/notes POST/PUT/DELETE on the HX-Request header. HTMX requests get rendered HTML partials (_note_item.html); non-HTMX callers continue to get JSON (NoteResponse / {status: deleted}). Form-urlencoded payloads from HTMX forms are now accepted alongside JSON via parse_note_create / parse_note_update helpers.
Also: unchecking the is_public checkbox in the edit form now persists is_public=False (form data omits absent checkboxes, so the parser explicitly sets the field). Auth failures on HTMX requests now attach HX-Redirect: /auth/login so the browser bounces to login without any client JS.
Adds two new routes for inline editing: GET /admin/notes/{id}/edit (returns _note_edit_form.html) and GET /admin/notes/{id}/view (returns _note_item.html, used by the edit form's Cancel button).
Refs #71
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
13 new tests against the admin notes endpoints:
- JSON path returns NoteResponse; HTMX path returns rendered HTML partial
- Unchecked is_public checkbox persists False (both create and update)
- 404s on missing notes for PUT/DELETE/edit-form
- DELETE with HX-Request returns empty 200; without it returns {status: deleted}
- HX-Redirect header attached only on HTMX auth failures
Refs #71
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Required by Starlette/FastAPI's request.form() to parse application/x-www-form-urlencoded bodies, which the new HTMX admin notes flow relies on. Without it, PUT /admin/notes/{id} from the inline edit form returns 500 with 'The python-multipart library must be installed to use form parsing.'
Refs #71
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
There was a problem hiding this comment.
Pull request overview
This PR adds inline edit/delete controls for admin notes using HTMX while preserving the existing JSON API behavior for non-HTMX callers. It also introduces partial template rendering to support HTMX swaps and adds tests for the new dual-response behavior.
Changes:
- Adds shared note row + inline edit-form partials and updates bundled themes to include the shared note row.
- Updates admin notes endpoints to parse either JSON or form submissions and return HTML partials for HTMX requests.
- Adds
ThemeEngine.render_partial(...), new admin routes for edit/view partials, plus tests and thepython-multipartdependency.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| themes/terminal/admin/admin.html | Switches note rendering to shared _note_item partial. |
| themes/default/admin/admin.html | Switches note rendering to shared _note_item partial. |
| themes/default/admin/_note_item.html | New shared note row partial with Edit/Delete HTMX actions. |
| themes/default/admin/_note_edit_form.html | New shared inline edit form partial with Save/Cancel HTMX flow. |
| themes/blue-tech/admin/admin.html | Switches note rendering to shared _note_item partial. |
| src/squishmark/services/theme/engine.py | Adds render_partial() for lightweight partial rendering. |
| src/squishmark/routers/admin.py | Adds HTMX detection, dual JSON/form parsing helpers, partial-render helpers, and new edit/view routes; updates notes CRUD to branch on HTMX. |
| tests/test_admin_notes.py | Adds coverage for JSON vs HTMX behavior, checkbox semantics, and HX-Redirect auth behavior. |
| pyproject.toml | Adds python-multipart required for request.form() parsing. |
Three issues raised by Copilot's PR review: 1. parse_note_create/parse_note_update now catch JSON decode errors and Pydantic ValidationError, re-raising as HTTPException(422). Previously a malformed JSON body or missing required field would bubble up as 500 because the validation happened outside FastAPI's parameter binding. 2. Form parsing no longer coerces missing fields to empty strings. Absent fields are simply not included in the dict, so Pydantic enforces required-ness on create (path/text both 422 if missing) and absent text on update is treated as 'no change' (text=None) instead of overwriting with ''. 3. ThemeEngine.render_partial now saves and restores loader.current_theme in a try/finally so concurrent HTMX requests with different themes can't leak state into each other. Adds 6 new tests covering the 422 paths, missing-field behaviors on form data, and theme-state restoration (including on render exceptions). Refs #71, #78 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
x3ek
added a commit
that referenced
this pull request
May 16, 2026
Hit this on PR #78: all checks green, no required approvals, but gh pr merge returned mergeStateStatus=BLOCKED because the 3 Copilot review threads weren't resolved. Replying to a thread doesn't resolve it — it's a separate action via the resolveReviewThread GraphQL mutation. Document the resolve-all-threads one-liner so future PRs don't get stuck the same way. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
HX-Requestand return rendered HTML partials. JSON behavior is preserved for non-HTMX callers (curl, future API).Approach
themes/default/admin/:_note_item.html(read-only row with Edit + Delete) and_note_edit_form.html(inline edit form). The theme loader's default-theme fallback meansblue-techandterminalreuse them automatically — no per-theme duplication.ThemeEngine.render_partial(...)returns a small HTML snippet without the heavy default render context (no nav/favicon/featured_posts).admin.pygainsis_htmx,parse_note_create,parse_note_update,_to_note_response,_render_note_partialhelpers. POST/PUT/DELETE branch onHX-Request. Two new GET routes power the inline edit/cancel flow.HX-Redirect: /auth/loginso the browser bounces to login with no client JS.is_publicin an HTMX form PUT now persistsis_public=False(the parser sets it explicitly), so toggling a note from public→private actually works.python-multipartto dependencies — required by Starlette'srequest.form().Test plan
python scripts/run-checks.py— 4/4 checks pass (format, lint, 183 tests, pyright).tests/test_admin_notes.pycover the JSON path (preserved), the HTMX form path, the checkbox-off semantics, and HX-Redirect on auth failure.curl -X PUT /admin/notes/3 -H 'Content-Type: application/json' -d '{"text":"x"}'still returnsNoteResponseJSON.Follow-ups (will file after this PR is open)
Closes #71
🤖 Generated with Claude Code