Skip to content

[UI][API] Device group edit and drag-and-drop assignment 404 — web sends PUT /device-groups/:id, only PATCH is registered #3428

Description

@ToddHebebrand

Description

Everything on the Device Groups page that writes to an existing group is dead. The page sends PUT /device-groups/:id; the router only registers .patch('/:id'). There is no PUT handler, so every one of these 404s at the framework level:

  • Editing a group (name, description, type, policy) via the edit modal
  • Drag-and-drop device assignment between groups
  • Any other caller of the shared persistGroup helper (DeviceGroupsPage.tsx:468-471)

Live proof

Against a seeded local stack on origin/main (f2d7fda83), authenticated as admin. The discriminator is the body, not the status — both cases return 404 and only the body distinguishes "no such row" from "no such route":

PATCH /api/v1/device-groups/00000000-...-000  ->  404  {"error":"Group not found"}
PUT   /api/v1/device-groups/00000000-...-000  ->  404  {"error":"Not Found","path":"/api/v1/device-groups/00000000-...-000"}
GET   /api/v1/device-groups                   ->  200

PATCH reaches the handler and reports a missing row. PUT never reaches a handler — that is Hono's default not-found envelope with the path echoed back.

Route registrations in apps/api/src/routes/groups.ts are get, get, post, patch, delete, getno put.

UI symptom

persistGroup throws a fixed new Error("Failed to update group") on !response.ok, so the user gets a generic failure with no indication that the request never hit a route. Drag-and-drop appears to do nothing.

Proposed fix

Change the client to PATCH, which is the verb the API actually implements and the correct semantics for a partial update — the payload deliberately omits rules (see the comment at DeviceGroupsPage.tsx:458-459), which is a merge, not a replace.

Do not add a PUT alias: the existing handler treats undefined as "leave this field alone", which is PATCH semantics. A PUT that behaved as a true replace would silently clear filterConditions and rules on every device-assignment write.

While in there, persistGroup should surface the server's message instead of the fixed string — PR #3423 does exactly that for the create/edit submit path, and this helper deserves the same treatment.

Affected files

  • apps/web/src/components/devices/DeviceGroupsPage.tsx:468-471 (the PUT), :455-475 (persistGroup)
  • apps/api/src/routes/groups.ts:537 (the PATCH handler that exists)

Context

Found while verifying #3159. Device Groups has four independent write defects right now, which is worth seeing together: create rejected filterConditions: null and silently dropped deviceIds (#3159, fixed in #3423), create 400s for multi-org partners (#3425), edit and drag-drop 404 (this issue), and bulk actions 404 (filed separately). Not a v0.105.0 regression — pre-existing.

Reported By: internal Playwright UI QA sweep 2026-08-11, evidence in docs/testing/FEATURE_TEST_LOG.md.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions