# Code maintenance DREAM-967 - Cover the work package table code fully

Project: Stream Design System
Status: review approved
URL: https://community.openproject.org/work_packages/DREAM-967

## OPEN POINTS

_No work packages_

## Subtasks

_No work packages_

## Details

| Attribute | Value |
| --- | --- |
| Assignee | Alexander Coles |
| Accountable |  |
| Designer |  |
| Developers | Alexander Coles |
| Requires doc change | No |
| Date |  |
| Priority | Medium |
| Target versions | 18.1.0 |
| Sprint |  |
| Backlog bucket |  |
| Module |  |
| Accessibility | No |
| List |  |

## Estimates and progress

| Attribute | Value |
| --- | --- |
| Work |  |
| Remaining work |  |

## Description

### Context

Production code in `frontend/src/app/features/work-packages/components/wp-fast-table/` (82 files, excluding specs and `testing/`) at the <mention class="mention" data-id="DREAM-959" data-type="work_package" data-text="#DREAM-959">#DREAM-959</mention> phase 3 tip (`5703d94f08e`): statements 95.9%, branches 88.9%, functions 97.2%, lines 96.4%. 55 of 82 files have a gap; branches are the long pole. Full inventory with file:line evidence: `agent-plans/reviews/2026-11-01-wp-fast-table-coverage-gaps-claude.md`.

Command (works around the custom-esbuild builder writing no report by default):

``` bash
cd frontend
npm run test -- --browsers=chromium --coverage \
  --coverage-reporters=text --coverage-reporters=json --coverage-reporters=html \
  --coverage-include='src/app/features/work-packages/components/wp-fast-table/**/*.ts' \
  --include='src/app/features/work-packages/components/wp-fast-table/**/*.spec.ts'
```

### Goal

100% statements, branches, functions and lines for production `wp-fast-table` code, held by a coverage threshold so it cannot regress.

### Notes

Work in this order, one commit per group:

1.  Delete dead code instead of testing it: `WorkPackageTableEditingContext.stopEditing` and `TimelineRowBuilder.insert` have no callers.
2.  Decide the measurement scope: with `wp-fast-table` specs only, or including `wp-table/**` and the display-field specs, which exercise `highlighting.functions.ts` (moving to `core-common` separately), `rendered-columns` `count` and `linkToShow`. Then add the local specs that scope needs.
3.  Specs for untested behaviour, by user impact: switching the details pane on row click; drag-and-drop pickup, preview and rollback paths; grouped-row matching by `href`, multi-href and `%` values; edit activation edge cases (link clicks, read-only and cancelled activation); turning hierarchy mode off; then the smaller handlers and render passes.
4.  Rule on each defensive guard (destroyed table, missing element, row or dataset value) and record the ruling with file:line: reachable → pin with a spec; impossible by construction → remove the guard or narrow the type; questionable (for example Enter on a row without a view) → decide before writing code.
5.  Add the coverage threshold, with ignore hints only for instrumentation limits such as the `ngDevMode`-off branch in `rendered-occurrence-ledger.ts`.

Scheduled after <mention class="mention" data-id="DREAM-846" data-type="work_package" data-text="#DREAM-846">#DREAM-846</mention>; it changes nothing users see. Rough effort: about three days.

A run only counts as green when the whole Vitest summary is clean, including any `Errors` / `Unhandled Errors` block, and `npm test -- --browsers chromium --reporters dot --reporters github-actions` exits 0.

### Progress (2026-11-01)

First slice: <https://github.com/opf/openproject/pull/26512>, stacked on <https://github.com/opf/openproject/pull/26510>. It deletes the two dead helpers (step 1) and pins the reachable <mention class="mention" data-id="DREAM-874" data-type="work_package" data-text="#DREAM-874">#DREAM-874</mention> paths plus grouped row matching; each new spec was checked by breaking the line it pins.

| Metric | Before | After |
| --- | --- | --- |
| Statements | 95.9% | 97.0% |
| Branches | 88.9% | 91.4% |
| Functions | 97.2% | 98.0% |
| Lines | 96.4% | 97.3% |
| Uncovered points in <mention class="mention" data-id="DREAM-874" data-type="work_package" data-text="#DREAM-874">#DREAM-874</mention> code | 136 | 94 |

Open questions, pinned as today's behaviour and still to decide:

  - Hierarchy mode renders a leaf before its filtered-out parent when the leaf comes before its in-window grandparent. Probably a bug.
  - A cell position without a desired column lands in column 0 rather than the current cell.
  - Tab from a control in a cell that is not a walk target (for example an `aria-disabled` button) stays native and leaves the grid.
  - An editor overlay without a usable control receives focus itself; Tab inside an overlay that sits within the grid does not park the editor.
  - The grouped position check skips zero-count groups and rejects members drawn without their header. Can the API or the renderer produce either?
  - Whether the `settle` / `setHidden` destroyed guards, focusable drag-and-drop placeholders and a repeated context-menu keydown can occur.

Likely latent bug, not pinnable: `HierarchyTransformer` resets collapsed classes through `[class^="__hierarchy-group-"]`, which never matches a table row, so the reset never runs.

Narrowing that would remove guards which are unreachable today:

  - `RenderedOccurrence.element` non-null; this also retires the "carry vs repaint" branch in row positions, because `refreshRow` returns the same element.
  - Keyboard navigation's `view` required; `groupIdentifier` required for group occurrences; `FocusPosition` split into row and cell variants. (The `extend` intent was narrowed in <https://github.com/opf/openproject/pull/26510>; group sums typing is <mention class="mention" data-id="OP-20664" data-type="work_package" data-text="#OP-20664">#OP-20664</mention>.)
  - A non-null `table.tableElement` instead of `tbody.closest('table')`; `relation` required on relation occurrences; `insideGrid(Element|null)`.
  - `activateTableField` taking `fieldName`; the dead `EditActivationCancelled` check; the `cell-interaction` origin and Shift+Tab fallbacks; `KeyboardSelection` repeating its caller's checks.
  - The redundant `undefined → null` conversion in grouped row matching, `source.href ?? ''`, `textContent ?? ''`, and the column-menu `isNavigableColumn` check.

Still to do: the remaining kind C behaviour (drag-and-drop pickup, preview and rollback, the smaller handlers) and the coverage threshold (step 5).

### Hints for QA

No user-visible change; verified by the unit suite and the coverage threshold. No manual QA needed.
