Skip to content

Quick-search: Enter never opens a tile (TypeError on numeric placement id), and every tile is dimmed including matches #95

Description

@rubenvdlinde

Summary

WorkspaceApp.vue::activateSearchResult() throws a TypeError on every invocation, so pressing Enter on a quick-search match never opens the tile (tile-quick-search REQ-QSEARCH-003, "Enter opens the selected tile").

A second, closely related defect in applySearchDimming() means the de-emphasis is applied to every tile including the matches.

Both have the same root cause: placement ids are numbers, and both functions treat them as strings.

Found during hydra gate-19 round 2 (#92). Measured in CI, then isolated statically.

Defect 1 — Enter never activates (crash)

src/views/WorkspaceApp.vue:

activateSearchResult(item) {
    const placementId = item?.placement?.id
    if (!placementId || !this.$el) { return }
    const el = this.$el.querySelector(
        `.launchpad-grid-item[data-placement-id="${placementId.replace(/"/g, '\\"')}"]`,
    )

item.placement.id is the WidgetPlacement entity id — an integer through jsonSerialize(). Number.prototype.replace does not exist, so placementId.replace(...) raises TypeError: placementId.replace is not a function. The guard above it does not catch this: a non-zero integer is truthy, so execution reaches the crash.

The exception is thrown inside a Vue event handler, so nothing surfaces to the user — Enter simply does nothing.

How it was isolated

The e2e test at tests/e2e/tile-quick-search.spec.ts pressed Enter on a single match and recorded clicks with a capture-phase listener. It failed with an empty activation list. Two splitting probes were added and both passed in CI run 31483882342:

  • the rendered cells do carry a[href] (so activateSearchResult()'s fallback-to-focus() branch is not the explanation);
  • the search input does still hold focus when Enter is pressed.

The sibling test proving aria-activedescendant selection tracking passes, so the selection mechanism is sound. Anchor present, focus correct, selection correct, no click — which the TypeError explains exactly.

Defect 2 — every tile is dimmed, including the matches

Same file:

const id = el.getAttribute('data-placement-id')   // ALWAYS a string
el.classList.toggle('launchpad-grid-item--dimmed', matchIds.includes(id) === false)

matchIds comes from the search results, i.e. the same numeric placement ids. Array.prototype.includes is strict-equality, so [12, 13].includes('12') is falseno tile ever matches, and the class is applied to all of them.

REQ-QSEARCH-002 requires non-matching tiles to be de-emphasised; applying it to matches as well defeats the feature.

A note on the test that did not catch Defect 2

tests/e2e/tile-quick-search.spec.ts currently asserts dimmed count > 0 after typing. That is satisfied by "everything is dimmed", so it passes on the buggy behaviour. It should assert that the tiles matching the query are NOT dimmed while the others are. That tightening is deliberately not applied in #92, because on current development it would be red — it belongs with the fix.

Suggested fix

Coerce once, at the boundary, in both functions:

const placementId = String(item?.placement?.id ?? '')
if (placementId === '' || !this.$el) { return }
const matchSet = new Set((matchIds ?? []).map(String))

el.classList.toggle('launchpad-grid-item--dimmed', matchSet.has(id) === false)

Coverage status

tile-quick-search::enter-opens-the-selected-tile is left as an open gate-19 finding — not annotated with @e2e exclude — because the scenario is browser-observable and the reason it has no passing test is that the product is broken. The other 8 scenarios in REQ-QSEARCH-001..003 have real, passing tests on #92.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions