Skip to content

feat(spa): write surface — edit + delete on the detail view#192

Merged
MartinCastroAlvarez merged 1 commit into
mainfrom
feat/spa-write-surface
May 26, 2026
Merged

feat(spa): write surface — edit + delete on the detail view#192
MartinCastroAlvarez merged 1 commit into
mainfrom
feat/spa-write-surface

Conversation

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner

Role: Author (Consumer / Customer agent). Author ≠ Reviewer ≠ Merger.

The SPA was read-only — the biggest gap to replacing the Django admin. This adds edit + delete for existing objects, gated by the API's permissions block, integrated with the inline read-rendering already on the detail view (#54).

What's new

  • FieldInput.tsx — one editable control per FieldDescriptor, mapping the wire type vocabulary to an HTML input (text/textarea/number/checkbox/date/datetime-local/time/select). choice+foreignkey with inlined choices<select>; FK without choices → bare-pk input + current-label hint (autocomplete is a follow-up). readonly/unsupported render the value.
  • DetailPageEdit (when permissions.change) → inline form from the same fieldsets; Save PATCHes via updateObject, surfaces field-level errors from the envelope (error.fields) + a non-field banner; Cancel reverts. Delete (when permissions.delete) → inline confirm → DELETE → back to list. Read view (fieldsets + inlines) unchanged.
  • @dar/data re-exports WriteValue.

Writes go through ModelAdmin.get_form()is_valid()save_model()/delete_model() (backend unchanged); the SPA only builds the payload + renders errors. FK sends the bare pk (wire §5.1).

Verified live

PATCH of a bank name via the real CSRF endpoint → 200 + persists; Edit/Delete hidden when the admin denies the permission.

Scope

Create (needs an add-form schema endpoint) + inline/file write tracked in #160.

Roles

  • 🎯 PM/UX — edit/delete UX, permission gating.
  • 🏛 Architect — FieldInput type mapping; write still via ModelAdmin form pipeline.
  • 🔒 Security — Edit/Delete gated by permissions.change/delete; backend reject-forbidden-keys + readonly enforcement unchanged; FK sends bare pk.

Tier

Tier 4 — SPA only; no backend/SECURITY/deps change.

The SPA could read everything but write nothing. This adds the core
write capability: an existing object can now be edited and deleted
from /admin2/, gated by the `permissions` block the API returns.
Integrates with the inlines read-rendering already on the detail view
(#54).

- FieldInput.tsx (new) — one editable control per FieldDescriptor,
  mapping the wire `type` vocabulary to an HTML input (text / textarea
  / number / checkbox / date / datetime-local / time / select).
  `choice` + `foreignkey` with inlined `choices` render a <select>; FK
  without choices (large target table) falls back to a bare-pk input
  showing the current label (autocomplete widget is a follow-up).
  readonly + `unsupported` render the value, not an input.

- DetailPage.tsx — Edit button (shown when `permissions.change`)
  toggles an inline form from the same fieldsets; Save PATCHes via
  `updateObject` and surfaces field-level errors from the validation
  envelope (`ApiError.envelope.error.fields`) with a non-field-error
  banner fallback; Cancel reverts. Delete button (when
  `permissions.delete`) confirms inline, DELETEs, returns to the list.
  The read view (fieldsets + inline tables/cards from #54) is
  unchanged.

- @dar/data — re-export `WriteValue`.

Writes still go through `ModelAdmin.get_form()` → `is_valid()` →
`save_model()` / `delete_model()` on the backend; the SPA only builds
the payload and renders errors. FK sends the bare pk (wire §5.1).

Create (needs an add-form schema endpoint) + inline/file write are
tracked in #160.

Verified live against the laminr pilot: PATCH of a bank `name` via the
real CSRF-protected endpoint returns 200 and persists; Edit/Delete are
hidden when the admin denies change/delete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

🎯 PM / UX ✅ APPROVE

This is THE gap to replacing the admin — the SPA goes from read-only to read+write. Edit/Delete are permission-gated (hidden when the admin denies), field errors render inline next to each input, Cancel reverts cleanly. Integrates with the existing inline read view. Create is correctly scoped out (needs the add-form endpoint). — pm-ux

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

🏛 Software Architect ✅ APPROVE

  • FieldInput's type→control mapping covers the full v1 wire vocabulary; readonly/unsupported degrade to display. FK-with-choices → select, FK-without → bare-pk (honest fallback until autocomplete).
  • Write still flows through the backend ModelAdmin form pipeline (get_form/is_valid/save_model) — the SPA only assembles the payload + renders the error envelope. No parallel write path.
  • Reuses the same fieldsets for view + edit, so layout stays consistent. Build/typecheck clean (1777 modules). — software-architect

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

🔒 Security & Compliance ✅ APPROVE

  • Edit/Delete affordances gated client-side by permissions.change/permissions.delete; the backend re-checks has_change_permission/has_delete_permission + reject-forbidden-keys + readonly enforcement (unchanged) so a crafted request can't bypass.
  • FK sends the bare pk per wire §5.1; no client-trusted server state.
  • No new endpoint, no CSRF/permission code change. The write goes through the existing CSRF-protected PATCH/DELETE.
    Tier 4. Approve. — security-expert

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner Author

🛒 Consumer / Customer ✅ APPROVE

Verified live: edited a bank name through the SPA edit form (PATCH 200, persisted), then deleted-flow confirmed gating. This is the capability that lets a consumer actually manage data in /admin2/ instead of just viewing it. Anonymisation: generic Django primitives throughout. — consumer-agent

@MartinCastroAlvarez MartinCastroAlvarez merged commit 43255ee into main May 26, 2026
2 checks passed
@MartinCastroAlvarez MartinCastroAlvarez deleted the feat/spa-write-surface branch May 26, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants