Narrow ResultEntry to a discriminated union in the webclient
featureready-for-agent
## Parent
#3198
## What to build
The webclient consumes the OpenAPI-generated `ResultEntry` shape, which declares `type: string` with optional per-variant fields (`title_de`, `title_en`, `upcoming`, `next_occurrence_at`, `parsed_id`, `subtext_bold`). The invariant "`upcoming` is populated iff `type === \"lecture\"`" lives only in JSDoc, so every consumer reasserts the same string check before touching variant fields (`SearchResultItem.vue`, `SearchResultItemLink.vue`, `AppSearchBar.vue`, `lectureRow.ts`, `entityPath.ts`, `pages/search.vue`, `NavigationSearchBar.vue`).
Introduce a webclient-side TypeScript overlay that re-exports `ResultEntry` as a discriminated union keyed by `type`, with each variant carrying only its valid fields. The OpenAPI generator cannot synthesize this, so the generated `api_types/index.ts` is left untouched; the overlay is a separate module that consumers import instead of the raw `components["schemas"]["ResultEntry"]`.
After this slice:
- Every consumer imports the union; defensive `entry.upcoming ?? []` constructs collapse to direct access on the lecture variant.
- `entry.type !== "lecture"` guards become TypeScript narrowings.
- Search UI behaves identically across every variant (room, virtual_room, poi, building, site, address, lecture).
## Acceptance criteria
- [ ] A discriminated-union overlay for `ResultEntry` exists and is imported by every consumer of the raw shape.
- [ ] Variant fields are only visible on their valid variants; defensive optional chaining for those fields is removed.
- [ ] `entityPath.ts:isRoutableEntityType` is either removed (subsumed by the union) or recast to operate over the narrowed type.
- [ ] No consumer reaches past the discriminator with `as` casts.
- [ ] `pnpm --dir webclient type-check` passes.
- [ ] `pnpm --dir webclient lint` passes.
- [ ] Search dropdown and `/search` page render results unchanged for every variant (manual verification in dev mode).
## Blocked by
None - can start immediately.
0 条评论