Description
In Network Discovery, opening a discovered asset and saving the Asset Info card (e.g. after changing Asset Type to "Camera") fails with a red error box that reads literally [object Object]. Reported by SemoTech on Discord running v0.90.0, editing a discovered UniFi camera.
The failure is not related to the "Camera" type — camera is a valid value in both the Zod schema and the DB enum. It reproduces for any discovered asset whose Display Name is empty (which is every freshly discovered, unlabeled asset).
Root Cause
Two stacked bugs:
- The save genuinely fails with HTTP 400. The form sends
label: editLabel || null (apps/web/src/components/discovery/AssetDetailModal.tsx:228). With an empty Display Name that is label: null, but updateAssetSchema declares label: z.string().max(255).optional() (apps/api/src/routes/discovery.ts:337) — .optional() accepts undefined, not null (contrast notes on the next line, which uses .nullish()). zValidator rejects before the handler runs. The handler itself already handles null correctly (updates.label !== undefined → set null), so only the schema is wrong.
- The 400 body is rendered raw.
handleSaveInfo throws new Error(body?.error || ...) (AssetDetailModal.tsx:236). On a zValidator 400, body.error is the ZodError object, and new Error(object) stringifies it to [object Object]. The codebase already has extractApiError (apps/web/src/lib/apiError.ts) for exactly this — the sibling unlink handler uses it, but handleSaveInfo (line 236) and handleResetType (line 259) do not.
Proposed Fix
- API:
label: z.string().max(255).nullish() in updateAssetSchema (null = clear the display name, matching the handler's existing semantics).
- Web: route the save/reset error bodies through
extractApiError so any future validation failure renders as a readable message.
Affected Files
apps/api/src/routes/discovery.ts (schema)
apps/web/src/components/discovery/AssetDetailModal.tsx (error extraction, save + reset handlers)
Reported By
SemoTech on Discord (2026-07-03), v0.90.0. The same report also raised camera auto-identification as an enhancement — split into a separate issue (linked below).
Description
In Network Discovery, opening a discovered asset and saving the Asset Info card (e.g. after changing Asset Type to "Camera") fails with a red error box that reads literally
[object Object]. Reported by SemoTech on Discord running v0.90.0, editing a discovered UniFi camera.The failure is not related to the "Camera" type —
camerais a valid value in both the Zod schema and the DB enum. It reproduces for any discovered asset whose Display Name is empty (which is every freshly discovered, unlabeled asset).Root Cause
Two stacked bugs:
label: editLabel || null(apps/web/src/components/discovery/AssetDetailModal.tsx:228). With an empty Display Name that islabel: null, butupdateAssetSchemadeclareslabel: z.string().max(255).optional()(apps/api/src/routes/discovery.ts:337) —.optional()acceptsundefined, notnull(contrastnoteson the next line, which uses.nullish()).zValidatorrejects before the handler runs. The handler itself already handlesnullcorrectly (updates.label !== undefined→ set null), so only the schema is wrong.handleSaveInfothrowsnew Error(body?.error || ...)(AssetDetailModal.tsx:236). On a zValidator 400,body.erroris the ZodError object, andnew Error(object)stringifies it to[object Object]. The codebase already hasextractApiError(apps/web/src/lib/apiError.ts) for exactly this — the sibling unlink handler uses it, buthandleSaveInfo(line 236) andhandleResetType(line 259) do not.Proposed Fix
label: z.string().max(255).nullish()inupdateAssetSchema(null = clear the display name, matching the handler's existing semantics).extractApiErrorso any future validation failure renders as a readable message.Affected Files
apps/api/src/routes/discovery.ts(schema)apps/web/src/components/discovery/AssetDetailModal.tsx(error extraction, save + reset handlers)Reported By
SemoTech on Discord (2026-07-03), v0.90.0. The same report also raised camera auto-identification as an enhancement — split into a separate issue (linked below).