Skip to content

AppManagementPage's search filter calls .toLowerCase() on app.label, which throws when the label is the keyed I18nLabel form #4343

Description

@yinlianghui

Found while implementing #4307 (console i18n conversion for this page). Filed unassigned, NOT fixed there — out of that card's scope, which is the page's own chrome.

Fact

apps/console/src/pages/system/AppManagementPage.tsx, the search filter:

const filteredApps = (apps || []).filter((app) => {
  if (!searchQuery) return true;
  const q = searchQuery.toLowerCase();
  return (
    (app.name || '').toLowerCase().includes(q) ||
    (app.label || '').toLowerCase().includes(q) ||       // ← here
    (app.description || '').toLowerCase().includes(q)
  );
});

app.label may be objectui's KEYED i18n label form — an object { key, defaultValue?, params? }. The same file resolves exactly that shape through resolveKeyedI18nLabel for display, which is why the resolver is imported there at all. An object is truthy, so (app.label || '') yields the object and .toLowerCase is undefined:

TypeError: (l || "").toLowerCase is not a function

app.description is the same shape and the same hazard on the line below it.

Impact

The throw happens inside filter during render, so it takes out the page rather than degrading search — but only once the operator types something (if (!searchQuery) return true short-circuits the empty case). So the page loads fine and breaks on first keystroke.

Not measured, and it decides the severity: whether any shipping app metadata actually carries a keyed label/description today. The render path is defensive about it and the search path is not, which is the inconsistency worth recording either way; the grading of "live crash" versus "latent" is the triage round's, not this filing's.

Suggested shape

Route both reads through the resolver the file already imports, so search matches what the user actually sees rather than a second, less capable reading of the same field:

(resolveKeyedI18nLabel(app.label) || '').toLowerCase().includes(q)

Worth checking whether sibling list/search surfaces filter on a raw label the same way before fixing just this one.

Related: #4163 (the I18nLabel audit — that one is the spec's INLINE per-locale map form; this is objectui's keyed form, a different shape with the same class of unguarded read).


Generated by Claude Code

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions