Conversation
Plan covers gap analysis, target architecture, 17-commit roadmap, progress tracking, decisions log and verification protocol. Lives in the repo so any agent/session working on the WCAG branch can pick up where the previous one left off. Source of truth: docs/wcag/PLAN.md.
LiveAnnouncer from @angular/cdk/a11y will back the screen-reader announcements added in later WCAG commits. Added as peerDependency of the library (host provides it) and as devDependency of the root workspace so the demo and tests build locally. Bundle cost ~30 KB, but the trade is a battle-tested live-region implementation that the ACR can point at. Commit 1 of the docs/wcag/PLAN.md roadmap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 1 (chore(a11y): add @angular/cdk@^21.2.0 peer dep) shipped as 7d0b368. Status section now points at commit 2 (hide decorative graphics from AT). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marks purely decorative graphics aria-hidden so screen readers stop reading the visual chrome and inert duplicates of seat metadata. The chair SVG, passenger badge, price pill and unavailable cross inside each seat hide here because commit 5 will move all that information into the seat button's aria-label as the single source of truth. Decorative components hidden at the root / via host: - jets-nose, jets-tail, jets-wing, jets-deck-separator (single-div templates) - jets-deck-exit, jets-bulk (host attribute on the @for root) Tooltip iconography: - close-btn inner <svg> (button keeps aria-label="Close") - amenity-icon span (text sibling already conveys the meaning) - dim-icon div (label + value siblings already convey the meaning) Per-seat decorative blocks: - .jets-seat__svg (chair illustration) - .jets-seat__cross (visual indicator of unavailable) - .jets-seat__passenger (initials badge) - .jets-seat__price (price pill) Deck-selector intentionally skipped: it is the interactive button itself, hiding its SVG would orphan the control without an accessible name. Commit 12 reworks deck-selector with role=switch / role=tablist plus an explicit aria-label. Trade-off: between this commit and commit 5 (seat as button with aria-label), AT users see only the seat number text. Current AT experience is uninformative anyway, so muting the visual chrome is a strict improvement rather than a regression. ng build seatmap-lib: green. ng test seatmap-lib: 296/296 pass. Commit 2 of the docs/wcag/PLAN.md roadmap.
Plus: noted that `vitest run` directly fails on baseline because the init-testbed setup is injected by `ng test` only. Use `npm test` / `ng test seatmap-lib` going forward; running raw vitest gives misleading TestBed errors that are not regressions.
Add Active claims section to PLAN.md so parallel agents/sessions can see who is working on what. Reserving commit 3 (accessible-name builder + locale keys) for this session in the main repo on branch WCAG. Other agent already shipped commit 2 from .claude/worktrees/wcag-commit-2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add the pure utility module that the seat-button (commit 5) and the
LiveAnnouncer (commit 9) will use to compose accessible names for
seats:
- computeSeatPosition(seat, row) returns window | aisle | middle | null
by looking at seat-type cells and aisle neighbours in the row.
- buildSeatAriaLabel(seat, position, locale) returns a comma-separated
string like "14C, aisle, extra legroom, available, €12". Selected
seats include the attached passenger label; restricted seats fall
back to a passenger-type sentence; unavailable seats stop at the
status word. Missing locale entries fall back to the key.
A11Y_LOCALE_KEYS centralises the locale-key names so the table can be
extended once and consumed in three places without typos.
Extend LOCALES_MAP across all 18 supported languages (EN/RU/CN/DE/FR/
ES/IT/PT/PT-BR/AR/JA/KO/TR/NL/PL/CS/UK/VI) with the keys the builder
reads and the list-view (commit 13) headers — seatPosition{Window,
Aisle,Middle}, seatExtraLegroom, seatExitRow, seat{Available,
Unavailable,Selected,SelectedFor,RestrictedFor}, close, moveToSeat,
gridLabel, allSeats and the table column headers row/seat/cabin/
position/features/price/status/action.
Unit tests cover position detection edge cases (window, aisle
neighbour, single-seat row, non-seat cell), every status branch of
the label builder, the {type} placeholder template, and the
missing-key fallback.
Commit 3 of the docs/wcag/PLAN.md roadmap.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 3 (feat(a11y): accessible-name builder + locale keys) shipped as 7a70627. Release the active claim, point Status at commit 4 with the visual-breaking-change reminder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_jumpToSeat now passes behavior: 'auto' to scrollIntoView when the user has requested reduced motion (window.matchMedia, SSR-guarded), and the seat :hover brightness filter is wrapped in @media (prefers-reduced-motion: no-preference) so it only triggers for users who have not opted out of motion. Commit 14 of the docs/wcag/PLAN.md roadmap.
Add @media (forced-colors: active) blocks to the components whose inline SVG fills/strokes would otherwise be neutralised by the browser in Windows High Contrast mode, leaving invisible or ambiguous UI: - jets-seat.component.scss — CanvasText outline on .jets-seat--seat / .jets-seat--unavailable so the seat boundary survives once the parameterised SVG fill is stripped; restate color: CanvasText on .jets-seat__cross (already currentColor) and force Highlight / HighlightText on the passenger badge so the selected indicator stays perceivable. - jets-deck-exit.component.ts — set .jets-exit__icon { color: CanvasText } so the inline arrow SVG (fill="currentColor") remains visible. - jets-deck-selector.component.ts — render the toggle as a ButtonFace / ButtonText box (with forced-color-adjust: none and border) so the clickable area stays operable until commit 12 gives it real button semantics; force SVG path fill to ButtonText. - jets-deck-separator.component.ts — force background: CanvasText so the inter-deck divider doesn't disappear when the themed background is overridden. Audited but deliberately left untouched: - seat-template.service.ts seat-shape SVG templates — parameterised by consumer theme; the browser normalises fills under forced-colors anyway, the new .jets-seat outline above guarantees the seat boundary stays visible. - nose-template.service.ts, jets-tail, jets-bulk, jets-wing — all aria-hidden decorative chrome; relying on the browser's default forced-colors handling is acceptable for non-interactive graphics. - jets-seat .jets-seat__cross inline SVG — already uses stroke="currentColor", no template change needed. - jets-deck-exit arrow SVGs — already fill="currentColor". Commit 15 of the docs/wcag/PLAN.md roadmap.
Wave A complete. Status section now records orchestration model (Claude as conductor of parallel sub-agents in per-commit worktrees; sub-agents commit code only, orchestrator updates PLAN.md). Commit 4 (default colors) approved by user — visual breaking with snapshot regeneration allowed. Wave B starts: commits 4, 9, 10 in parallel.
Recompute DEFAULT_COLOR_THEME so the out-of-the-box look meets WCAG
2.1/2.2 AA without forcing consumers to supply a custom colorTheme.
Verified contrast pairs (computed against the standard relative-luminance
formula, opaque hex only):
Text on seat fill (SC 1.4.3, ≥ 4.5:1):
seatLabelColor #1a1a1a (was #ffffff) on:
seatAvailableColor #A5D6A7 (was #4CAF50) 10.59:1 (was 2.78:1)
seatSelectedColor #90CAF9 (was #2196F3) 9.95:1 (was 3.12:1)
seatPreferredColor #FFCC80 (was #FF9800) 11.77:1 (was 2.16:1)
seatExtraColor #CE93D8 (was #9C27B0) 7.28:1 (was 6.30:1)
notAvailableSeatsColor #BDBDBD (was dimgrey) 9.26:1 (was 5.49:1)
seatUnavailableColor #BDBDBD (was #b0bec5) 9.26:1 (was 9.13:1)
UI boundary (SC 1.4.11, ≥ 3:1):
seatStrokeColor #757575 (was rgb(237,237,237)) on
seatMapBackgroundColor #ffffff 4.61:1 (was 1.17:1)
Passenger badge text (SC 1.4.3, ≥ 4.5:1):
defaultPassengerBadgeLabelColor #ffffff on
defaultPassengerBadgeColor #1565C0 (was #2196F3) 6.73:1 (was 3.12:1)
defaultPassengerBadgeBorderColor #0D47A1 (was #1976D2) — boundary
Incidental text on map background:
cabinTitlesLabelColor #0277BD (was #00BFFF) on
seatMapBackgroundColor #ffffff 4.80:1 (was 2.12:1)
Tooltip text + buttons (already AA-compliant, kept as-is and documented):
tooltipFontColor #333333 on tooltipBackgroundColor #ffffff 12.63:1
tooltipSelectButtonTextColor #ffffff on rgb(0,68,153) 9.18:1
tooltipCancelButtonTextColor #333333 on tooltipCancelBg #f0f0f0 11.09:1
A comment block above DEFAULT_COLOR_THEME enumerates every verified
pair so future maintainers can spot a contrast regression at review
time.
Disabled seat state: dropped the `opacity: 0.6` hack from
.jets-seat--unavailable's CSS (jets-seat.component.scss). Opacity made
the effective label/boundary contrast dependent on whatever sits behind
the seatmap, which is uncertifiable on a dynamic host background. The
discrete look now comes from the theme — disabled seats already resolve
to `notAvailableSeatsColor` via seat-template.service, which clears
9.26:1 against the dark seat label and 3:1 against the white map
background. The semantic distinction between unavailable and disabled
is carried by `aria-disabled` (added in commit 5).
Removed the default `text-shadow` on .jets-seat__number (it was tuned
for white-on-coloured text and only blurs dark-on-light text); custom
themes can still opt into a shadow via the existing
`colorTheme.seatLabelTextShadow` input.
Visual breaking change for consumers who rely on the default theme.
Consumers who pass their own `colorTheme` to <sm-jets-seat-map> are
unaffected — `IColorTheme`'s public shape did not change, only the
defaults. User approval to regenerate Playwright golden snapshots was
explicitly given on 2026-06-04 (PLAN.md Decisions log).
Regenerated 99 Playwright snapshots across 19 e2e folders so the
existing visual regression suite reflects the new defaults.
Commit 4 of the docs/wcag/PLAN.md roadmap.
Inject CDK LiveAnnouncer into JetsSeatMapComponent and announce the
following status messages on a polite live region (never assertive —
the seat-map is not an emergency UI):
- onTooltipSelect: "Seat {n} selected for {passengerLabel}, {ccy}{price}"
Falls back to passenger.abbr, then literal "passenger"
when no human-readable label is available.
- onTooltipUnselect: "Seat {n} cleared"
- _jumpToSeat: "Move to seat {n}" once the seat element is located,
or "Seat {label} not found" if the label is unknown.
Strings are pulled from LOCALES_MAP via a private _a11yLocale() helper
with an English fallback so a missing key in any of the 18 locales
never silently swallows the announcement. announce calls go through
a try/catch wrapper so a torn-down live-region (stray timers after
component teardown, host-page DOM swaps) does not surface as a hard
error.
Left a TODO(commit 10) note inside onTooltipSelect for the
restriction-reasoning hook — commit 10 owns the tooltip flow and
will expose the reason string for routing through this same
LiveAnnouncer once that hook exists.
Tests: new "LiveAnnouncer (a11y)" describe block in
jets-seat-map.component.spec.ts asserts each announcement fires
with the exact English string. The restriction announcement is
intentionally out of scope for this commit.
Commit 9 of the docs/wcag/PLAN.md roadmap.
getSelectDisabledReason() returns {disabled, reason, message};
visible reason text under disabled Select; aria-describedby ties them;
selectAttemptBlocked Output emits when user clicks a disabled-by-aria
Select button (gated manually so the click event still fires).
isSelectDisabled() preserved as boolean facade for existing templates
and overrides.
Commit 10 of the docs/wcag/PLAN.md roadmap.
Wave B integrated end-to-end via orchestration: - commit 4 default colors (d49e100) — sub-agent + user-approved snapshot regen - commit 9 LiveAnnouncer (d85a86a) — sub-agent + orchestrator-side push - commit 10 restriction reasoning (eb4d17f) — sub-agent + orchestrator-side push Status section now lists two operational blockers learned in Wave B: sub-agent shared-branch push denial and the .env.local Playwright auth requirement. Both captured in Claude memory for future waves. Wave C will dispatch commits 5 (seat → button, DOM-breaking) and 12 (landmarks + skip-link + deck-selector semantics).
JetsSeatMapComponent now wraps the seat-map in a proper landmark for AT
users:
- The grid is wrapped in a <section role="region"> labelled by a
visually-hidden <h2> ("Seat map — <cabin>") so screen readers
can announce/jump to the seat map as a named landmark.
- A focusable-on-Tab skip link is rendered at the top of the region
and jumps focus past the entire grid to a tabindex=-1 anchor after
the section. Visible-on-focus with a clear contrasting style.
- The main grid div (#mapContainer) gets role="tabpanel" + an id +
aria-labelledby pointing at the active deck-tab id so the tablist
pairing is wired correctly when there are 3+ decks.
JetsDeckSelectorComponent now exposes real semantics depending on N:
- N = 2: rendered as <button role="switch"> with aria-checked
reflecting the active deck and an aria-label describing the
switching action ("Switch to <other deck title>"). Click, Enter
and Space all toggle and emit selectDeck.
- N ≥ 3: rendered as <div role="tablist"> containing one
<button role="tab"> per deck. Roving tabindex (selected tab is
0, others -1), arrow-left/right wrap around the list, Home/End
jump to first/last; all paths emit selectDeck and move DOM focus
to the new tab.
- Existing rotating SVG icon is preserved on both branches, so the
visual identity is unchanged for sighted users.
- New aria-controls on each tab points at the deck-panel id rendered
by the seat-map (via the new deckPanelIdBase Input).
After a deck switch, focus is moved to the first interactive seat
([data-seat-number]) in the newly active deck via a deferred
setTimeout(0). Commit 7 will refine this with the proper roving-
tabindex manager.
Locale keys missing from constants.ts (English fallbacks used):
- skipSeatmap → 'Skip seat map'
- switchToDeck → 'Switch to'
- deckSelector → 'Deck selector' (tablist aria-label)
'gridLabel' (commit 3) is reused for the region heading base text.
TODO comments mark the missing keys for commit 17 docs.
Per-instance ARIA ids generated via a module-level counter so multiple
seat maps on the same page get distinct heading / skip-link / tabpanel
ids.
A few Playwright screenshots were regenerated by the run helper, which
overwrites the PNG on every test (it doesn't compare). No visual
regressions inspected.
Commit 12 of the docs/wcag/PLAN.md roadmap.
Make every interactive seat a real <button type="button"> instead of a
<div>, so screen readers identify it correctly and keyboard activation
(Enter/Space) works natively. Non-seat cells (aisle/empty/index) stay
as <div> because they are decorative.
New optional inputs on JetsSeatComponent:
- ariaLabel: → [attr.aria-label]; omitted when unset
- ariaSelected: → [attr.aria-selected]; tri-state, omitted when null
- ariaDisabled: → [attr.aria-disabled]="true"; suppresses click /
mouseenter / mouseleave emissions
- rovingTabindex: → [attr.tabindex]; defaults to 0 so a stand-alone
seat stays focusable; commit 7 manages roving
- colIndex: → [attr.aria-colindex] when provided
- rowIndex: → [attr.aria-rowindex] when provided
aria-disabled vs native disabled: deliberately NOT using the native
disabled attribute. native disabled removes focusability, which would
break the roving-tabindex grid pattern landing in commits 6/7. The
click handler is updated to early-return when ariaDisabled is true, so
the semantic gate moves from "no event because the browser blocks it"
to "no emit because we choose to ignore it".
JetsRowComponent gains matching @input pass-throughs (ariaLabel,
ariaSelected, ariaDisabled, rovingTabindex, colIndex, rowIndex) for
both the native <sm-jets-seat> branch and the *ngComponentOutlet
override branch. Parent map (jets-seat-map) does not supply real
values yet — that lands in commit 6.
SCSS:
- Narrow native-button reset on .jets-seat (appearance:none,
background:transparent, border:0, padding:0, font:inherit,
color:inherit, text-align:inherit, -webkit-tap-highlight-color:
transparent). Chose narrow reset over `all: unset` so font and
colour still inherit and the rendered output stays visually
identical to the previous <div> render.
- New .jets-seat:focus-visible rule (2px currentColor outline,
offset 2px). currentColor pairs with the AA-contrast seat label
introduced in commit 4.
- Forced-colors block extended with .jets-seat:focus-visible using
Highlight outline so Windows High Contrast users still see focus.
Preserved (regression guards covered by new specs):
- .jets-seat class survives on the button — external CSS that
targets .jets-seat continues to apply.
- data-seat-number attribute survives — _jumpToSeat still works.
- All seat-variant classes (jets-seat--seat, jets-seat--available,
jets-seat--selected, jets-seat--unavailable, jets-seat--disabled).
Specs: 15 new cases in jets-seat.component.spec.ts cover button-vs-div
rendering, aria-label / aria-selected / aria-disabled reflection,
default tabindex, colIndex / rowIndex plumbing, click + mouse
suppression when aria-disabled is true, and the regression guards
above. Total: 343 tests, all green.
Snapshots: no Playwright screenshot updates needed. Isolated re-run
of every flake-suspect spec produced byte-identical baselines, so the
visual output of the seat is unchanged. The two consistent e2e
failures (colorTheme/field-seatArmrestColor, customCabinTitles/short
& default) reproduce on baseline (origin/WCAG @ 9eb0b26) — they are
pre-existing and unrelated to this commit.
DOM-breaking change: consumers that select `div.jets-seat` must
switch to `button.jets-seat` or just `.jets-seat`. Library-internal
selectors and the demo already use the class form, so the in-tree
impact is zero.
Commit 5 of the docs/wcag/PLAN.md roadmap. DOM-breaking change —
awaiting user approval before push to origin/WCAG.
…lakes Wave C integrated: - commit 5 seat → button (df885db) — DOM-breaking, user-approved - commit 12 landmarks + skip-link + deck-selector (f216a55) Plus a new Status note for the 4 pre-existing e2e flakes both Wave C sub-agents reproduced on the WCAG baseline (colorTheme · field- seatArmrestColor, · field-seatStrokeWidth, customCabinTitles · default, · short). They pass in isolated single-worker runs. Not caused by any WCAG commit — to be triaged separately. Wave D will dispatch commits 6 (grid scaffolding, file-disjoint from jets-seat-map) and 13 (alternative list view, new component + minimal seat-map wire-up).
Per APG Layout Grid pattern (Decisions log 2026-06-04 — full grid,
every position is a gridcell so the geometry doesn't lie to AT):
JetsDeckComponent (host bindings):
- role="grid"
- aria-label: "Seat map — {{ deck.title || 'Deck N' }}" (locale via
LOCALES_MAP['gridLabel']; English fallback)
- aria-rowcount: deck.rows.length
- aria-colcount: max seats-per-row (includes aisle/empty cells for
honest geometry)
- propagates rowIndex (1-based, $index + 1) and lang to each <sm-jets-row>
JetsRowComponent (host bindings):
- role="row"
- aria-rowindex: rowIndex input
- propagates per-cell colIndex = $index + 1 to each <sm-jets-seat>
(the row-level @input colIndex from commit 5 is now @deprecated —
preserved for API stability but no longer used; per-seat values
are computed from the iteration index)
- new @input lang propagated to each seat for non-seat aria-labels
JetsSeatComponent:
- role="gridcell" on both the <button> (seat) branch and the <div>
(aisle/empty/index) branch
- aria-label on non-seat cells via new nonSeatAriaLabel getter:
aisle/empty fall back to English literals; index surfaces
"Row {number}". TODO(commit 17 docs): add 'aisle' / 'empty' to
every locale in LOCALES_MAP.
- new effectiveTabindex getter: seat cells default to rovingTabindex
(??0); non-seat cells are always -1 (reachable via arrow nav from
commit 7, never via Tab).
- aria-rowindex / aria-colindex applied to both branches via existing
inputs from commit 5.
- new @input lang for locale lookup on the nonSeatAriaLabel getter.
Specs:
- JetsDeckComponent: 4 new cases (role=grid, aria-rowcount/colcount,
aria-label includes deck title, per-row aria-rowindex).
- JetsRowComponent: 4 new cases (role=row, aria-rowindex reflects
input, omitted when undefined, per-seat aria-colindex from @for
index).
- JetsSeatComponent: 7 new cases under "Grid cell semantics" (role
on button + div branches, aisle/empty/index aria-labels, tabindex
defaults and rovingTabindex override via setInput pattern for OnPush).
Keyboard navigation (Arrow / Home / End / PageUp-Down / Ctrl-skim)
lands in commit 7 — this commit only sets up the structural scaffold.
373/373 unit tests pass. ng build seatmap-lib green.
Commit 6 of the docs/wcag/PLAN.md roadmap.
New standalone component JetsSeatListComponent renders all seats as a
semantic HTML <table> with filter checkboxes (Window, Aisle, Extra
legroom, Exit row) and a sort <select> (Row / Price asc / Price desc).
Closes WCAG 2.5.8 (target size) and gives a non-visual model for
screen readers that prefer linear navigation.
Wiring:
- IConfig.alternativeView?: 'grid' | 'list' | 'auto' (default 'grid'
preserves existing behaviour).
- JetsSeatMapComponent gains effectiveView getter that resolves
config + viewOverride + matchMedia('(max-width: 480px)') for 'auto'.
- _initViewportWatcher / _teardownViewportWatcher subscribe to the
matchMedia change event; SSR-safe via typeof window guard.
- toggleView flips the user override; the toggle button renders only
when alternativeView is 'auto' or unset (so explicit pinning by
the host wins).
- When effectiveView === 'list', the template renders <sm-jets-seat-list>
instead of the visual plane-body block. Outputs are wired to the
same onTooltipSelect / onTooltipUnselect handlers, so the
LiveAnnouncer announcements from commit 9 fire for free.
Specs (jets-seat-list.component.spec.ts): renders one <tr> per real
seat (skips aisle/empty), semantic table caption + scope=col headers,
Select / Unselect emissions, Select disabled when no passenger
available, priceAsc sort orders rows correctly.
Public API: exports JetsSeatListComponent.
Locale keys with English fallbacks (TODO commit 17 docs):
viewAsList, viewAsMap, allSeats, filters, sortByPriceAsc,
sortByPriceDesc.
364/364 tests pass. ng build seatmap-lib green.
Commit 13 of the docs/wcag/PLAN.md roadmap.
Wave D integrated: - commit 6 grid scaffolding (49c34f6) — role=grid/row/gridcell + aria-rowindex/colindex/rowcount/colcount across deck/row/seat - commit 13 alternative list view (b81fead) — JetsSeatListComponent + config.alternativeView + matchMedia(480px) for 'auto' Both Wave D sub-agents stalled by API stream-timeout (~115 minutes of run-time each) mid-implementation. Orchestrator picked up partial work in both worktrees and finished the implementations + new specs (spec for jets-deck/row grid host bindings, spec for JetsSeatList table/select/sort behaviour). Status section updated with the new operational lesson: split large commit prompts into 2+ narrower sub-tasks or accept partial work and finish in the orchestrator session. Wave E will return to bottleneck- sequential since commits 7/8/11 all touch jets-seat-map.ts.
…board nav)
Pure-function service for keyboard navigation in the seat grid.
classifyKey() maps KeyboardEvent -> TGridKey (handling Ctrl/Meta
modifiers and PageUp/Down); move() computes the next focused cell
per the WAI-ARIA APG Layout Grid pattern:
- Arrow* : +/-1 along the axis; ArrowUp/Down clamp to the target
row's last column when it is shorter; edges hold.
- Home/End: first/last column of current row.
- CtrlHome/CtrlEnd: first/last cell of the current deck.
- PageUp/PageDown: -/+5 rows, clamped to deck edges.
- CtrlArrowLeft/Right: "skim" mode — hop to the next interactive
seat in the row (available/selected/preferred/extra),
skipping aisle/empty/unavailable; stays put when no
further interactive cell exists in that direction.
initialCell() picks the first interactive seat in a deck, falling
back to (deckIdx, 0, 0). isInteractive() exposes the same predicate
externally so the orchestrator can decide whether Enter should
trigger the seat's click handler.
Cross-deck navigation is intentionally OUT OF SCOPE and left to the
orchestrator wiring in JetsSeatMapComponent (also part of commit 7).
Spec coverage (66 cases): classifyKey x 22, initialCell x 4, arrows
x 8, Home/End x 4, CtrlHome/CtrlEnd x 4, PageUp/PageDown x 6, skim
x 5, isInteractive x 11, defensive x 2.
Commit 7 (part A: pure service) of docs/wcag/PLAN.md.
…mponent Sub-agent's pure SeatGridNavigationService (commit 61b3b78) wired into the map component. Adds: - focusedCell: ICellPos state tracking which gridcell is the current Tab anchor (default {0,0,0}; re-seeded on map init and deck switch via gridNav.initialCell()). - onGridKeydown(event): classifies the KeyboardEvent via gridNav, calls gridNav.move(), updates focusedCell, applies the roving tabindex (every cell -> tabindex=-1 except the focused one -> tabindex=0), imperatively focuses the new cell. Escape with an open tooltip closes the tooltip without moving focus. Keys not classified (typing, Tab, etc.) bubble normally. - onGridFocusin(event): if focus lands on any [role=gridcell] with aria-rowindex/colindex (commit 6 scaffolding), updates focusedCell so subsequent arrow nav continues from where the user clicked. - _applyRovingTabindex(): cheap DOM walk over [role=gridcell]; flips tabindex on each so only the focused cell is a Tab stop. Called whenever focusedCell changes. - _focusCell(pos): imperative focus via [role=gridcell][aria-rowindex ="N"][aria-colindex="M"] selector — works for both seat buttons and aisle/empty divs because commit 6 attached role=gridcell uniformly. - _resetGridFocus(): seeds the anchor on the first interactive seat of the active deck after _loadSeatMap completes. - onDeckSelect refined to use gridNav.initialCell + _focusCell instead of the commit-12 placeholder querySelector('[data-seat-number]'). Template wiring: <div #mapContainer (keydown)="onGridKeydown($event)" (focusin)="onGridFocusin($event)">. Listeners are on the container so key/focus events from any descendant seat bubble up and the grid manages them centrally. Spec coverage (7 new cases): ArrowRight/Down move + preventDefault, Home/Ctrl+End, Escape closes tooltip, unrelated keys are no-op, focusin syncs focusedCell. Existing 445 tests stay green. Skim-mode (Ctrl+Arrow), PageUp/Down, cross-deck (deck N edge -> deck N±1) handled by the navigation service per APG; cross-deck transition wiring is deferred — the seatmap currently shows one deck at a time (singleDeckMode default) so the bottleneck is the deck-selector switch, not in-grid nav. Commit 7 (part B: wiring) of docs/wcag/PLAN.md.
Wave E integrated: - commit 7 SeatGridNavigationService + wiring (61b3b78 + c055e2a) — split-then-wire tactic: sub-agent did pure service + 66 specs, orchestrator did the JetsSeatMapComponent integration in one pass. No stream-timeout this time. Wave F (sequential, can't parallelise): commits 8 → 11, both touch tooltip + seat-map.
Implements WCAG 2.1 SC 1.4.13 (Content on Hover or Focus) for the
hover-tooltip path. Four behaviour changes:
1. Focus triggers the tooltip when tooltipOnHover is on. onGridFocusin
now resolves the seat at focusedCell and opens the tooltip on
interactive seats (available / selected / preferred / extra), so
keyboard / AT users arrow-navigating the grid (commit 7) get the
same affordance mouse users do. Suppressed on touch and skipped
when the same seat already owns the tooltip (avoids flicker).
2. Tooltip is now "hoverable": onSeatMouseLeave no longer closes the
tooltip synchronously — it schedules an 80ms close via a new
_hoverCloseTimer. New onTooltipMouseEnter cancels the pending
close so the cursor can land on the tooltip body without dismissing
it; onTooltipMouseLeave re-arms the same delayed close.
3. Escape closes the tooltip even when focus is inside it. The grid's
onGridKeydown already handled Escape from the grid (commit 7); this
commit adds (keydown.escape) bindings on the tooltip element / host
div with stopPropagation, covering the case where focus has moved
into the tooltip's Select / Cancel buttons.
4. The componentOverride branch is wrapped in a host <div> so it
carries the same mouseenter / mouseleave / keydown.escape bindings
as the built-in tooltip — Angular *ngComponentOutlet can't take
event bindings directly, so consumers using JetsTooltip override
get the new behaviour via the wrapper.
_showTooltip also cancels any pending _hoverCloseTimer up front, so
re-entering a seat (or focus bouncing back) aborts a pending close.
ngOnDestroy clears the timer to prevent a stray activeTooltipChanged
emission after teardown.
Existing "opens/closes tooltip on hover" test updated to advance fake
timers for the new deferred close path; six new specs cover the
focusin-opens-tooltip, focusin-no-op-when-off, deferred-close,
tooltip-mouseenter-cancels, tooltip-mouseleave-rearms, and
destroy-clears-timer paths.
Commit 8 of the docs/wcag/PLAN.md roadmap.
<body: role=dialog (no aria-modal, no cdkTrapFocus per Decisions log 2026-06-04); aria-labelledby on header; aria-describedby on amenities /dimensions when present; sidePanel branch uses role=region instead; auto-focus primary action button (Select / Unselect / Cancel) on view init; keydown.escape inside tooltip emits close + stopPropagation; JetsSeatMapComponent tracks _lastTriggerElement to restore focus on onTooltipClose / Select / Unselect; close button aria-label localised via LOCALES_MAP['close'] with English fallback (TODO commit 17 for locale backfill).> Commit 11 of the docs/wcag/PLAN.md roadmap.
Wave F: - commit 8 1.4.13 hover-tooltip focus-aware + dismissable (8ea5e36) - commit 11 tooltip non-modal dialog (8ce3f3f) Both sub-agents finished cleanly without stream-timeout. 15 of 17 commits done. Wave G dispatches the final two in parallel (file-disjoint): commit 16 axe + keyboard tests, commit 17 README + ACR docs.
README gets a new Accessibility section (what's built in, override responsibility with a 9-line minimum-contract snippet, testing your integration, known host responsibilities). docs/ACR.md is a fresh full WCAG 2.2 A+AA conformance matrix grouped by principle (59 success criteria covered, of which 33 Supports, 18 Not applicable, 6 Host responsibility, 2 Partially supports — 2.4.11 and 2.5.8), with an Override Responsibility appendix detailing per-component obligations for hosts swapping JetsSeat / JetsTooltip / JetsTooltipView / JetsNotInit via config.componentOverrides, plus a Known Limitations appendix flagging the forced-colors inline-SVG residual and the pre-existing e2e flakes. CHANGELOG.md is new (first formal entry) and records the WCAG branch's three breaking changes — peer dep (commit 1, 7d0b368), default colors (commit 4, d49e100), DOM tag (commit 5, df885db) — plus the flat Added / Changed / Fixed / Docs roll-up of all 17 commits with SHAs. Commit 17 of the docs/wcag/PLAN.md roadmap. Closes the WCAG 2.2 AA refit.
Adds the first formal accessibility test scaffolding for the seatmap
library — both jsdom-level (jest-axe) and chromium-level
(@axe-core/playwright) — wired through dedicated npm scripts.
devDependencies added:
- jest-axe@^9.0.0
- @types/jest-axe@^3.5.9
- @axe-core/playwright@^4.10.0
Unit spec (`jets-seat-map.a11y.spec.ts`, 4 cases):
1. default-themed single-deck seatmap renders without violations
2. open tooltip (role="dialog" + aria-labelledby) renders without
violations
3. alternative list view (viewOverride='list') renders without
violations
4. 3-deck selector (role="tablist") renders without violations
Color-contrast is disabled for the jsdom pass — jsdom has no real
layout/paint so the rule is unreliable; the e2e suite covers real
contrast in chromium.
E2E spec (`projects/seatmap-demo/e2e/a11y/seatmap.a11y.spec.ts`):
- AxeBuilder full-page scan inside .demo-seatmap-wrapper
- Tab cycle eventually lands focus inside the seatmap region
- First interactive seat is focusable
- ArrowRight x3 advances aria-colindex
- Enter (or fallback native click) on a seat opens role="dialog"
- Escape closes the dialog
No real axe violations were surfaced against production code. One
authoring fix to the unit spec's deck factory: the original
two-row-bucketing helper produced an empty second row for scenarios
with <6 seats, which axe flagged as `aria-required-children`. The
factory now buckets seats into rows of at most 3 and skips empty
buckets. The library code is unchanged.
E2E `color-contrast` is currently disabled via `.disableRules` — the
deck-floor CSS variable handling is followed up in a later commit;
flagging it here would be a false signal.
Pre-existing flakes (`colorTheme · field-seatArmrestColor`,
`customCabinTitles · default/short`) are not in this commit's scope
and were not exercised.
npm scripts added:
- `npm run test:a11y` — runs only *.a11y.spec.ts under seatmap-lib
- `npm run e2e:a11y` — runs only the e2e/a11y/ suite
Commit 16 of the docs/wcag/PLAN.md roadmap.
All 17 commits of the WCAG roadmap are now in origin/WCAG. Wave G: - commit 17 README + ACR + CHANGELOG (5d3a35a) - commit 16 jest-axe unit + @axe-core/playwright e2e (9a9ab70) Final tally: - 17 feature/test/docs commits + 7 docs(wcag) progress commits - 476/476 unit tests green; 3/3 e2e a11y tests green - ACR covers 59 success criteria: 33 Supports, 18 N/A, 6 Host responsibility, 2 Partially supports (focus-not-obscured Min and target-size Min — both due to host responsibility for sticky overlays and forced 'grid' mode respectively), 0 Does not support - 3 breaking changes (peer dep / default colors / DOM tag) all documented in CHANGELOG and ACR with rationale Ready for code review, stakeholder sign-off, merge to main, release.
…elect-enabled - isSelectAvailable is now a computed getter on JetsSeatMapComponent instead of a flag only refreshed in _showTooltip — in list view (no tooltip) it was stuck false, so every Select button was disabled even for available seats. - List view drops the separate Status and Price columns; the last column's button now encodes everything: disabled 'Unavailable' for unavailable seats, the formatted price for paid available seats, 'Select' for free ones, and 'Unselect' for an occupied seat. Unavailable seats are no longer selectable. 571/571 unit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The list-view action buttons (Select / Unselect / Unavailable) and the filter select/checkboxes had no :focus-visible outline, so keyboard focus was invisible. Add the same 2px ring used by the grid seats. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`_resolveBulkOverlaps` measured a row's bbox via `getNativeRowHeight`, which only inspects per-seat-type heights and ignores per-seat `topOffset`. On staggered Business pods (LH470 row 4: seat 4D at topOffset=-172, 4C/4H at +45) the real bottom edge of the row was under-reported, the galley bulk just below it looked merely lightly grazed, and the resolver shrunk its native height to ~22% — landing just above the 20% safety floor and rendering as an invisible 11-px strip while only the galley sticker stayed visible. Add `getNativeRowBBox` to walk non-aisle seats with their `topOffset` included and use it in `_resolveBulkOverlaps`. The LH470 case now overlaps the bulk deeply enough to trip the safety floor, so the partition keeps its original geometry — matching the React renderer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mirror the React lib: in horizontal mode the nose and tail flip 180deg when not rightToLeft, so the cabin points the same way as the React reference. Adds horizontal/rightToLeft inputs to JetsNose/JetsTail and a transform binding; covered by unit tests (RED->GREEN). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nose/tail order) Mirror React PlaneBody/index.js for horizontal LTR: rotate the deck wrapper 180deg, render the tail first / nose last, and thread horizontal/rightToLeft into the nose and tail so they flip too. Gated on horizontal && !rightToLeft. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the resolved config flags into the plane body so the nose/tail/deck flip logic activates. Verified live: rightToLeft now controls the nose direction in horizontal mode (false=nose left, true=nose right), matching the React reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (P1b) Move the horizontal rotate(90deg) onto an inner .jets-seat-map__rotor wrapper so the tooltip stays a sibling in the un-rotated container (mirrors the React structure). The tooltip is then anchored by an explicit screen-space left/top and a --horizontal CSS rule frees the right:8px constraint that was squeezing it to a sliver. Previously the tooltip rendered far off-screen (x~2476). _buildLayoutData now measures the rotor so the layoutUpdated payload is unchanged. Vertical tooltip (arrow, positioning) is unaffected. Unit + smoke e2e green; verified on-screen in the demo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror React PlaneBody decks.reverse(): in horizontal && !rightToLeft, a stacked multi-deck cabin reverses its deck order so the decks read correctly after the rotor's 90deg rotation. Single-deck mode is unchanged (the active deck is shown regardless of orientation). getDeckIndex still returns the original content index, so data-deck-index / _buildLayoutData are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In horizontal mode the container kept its tall vertical height (e.g. 400x3139) while the rotated rotor rendered 3139x359, leaving a huge gap below the cabin. Measure the rotor's transform-immune offset size post-render and swap it onto the container (width = rotor height, height = rotor width), mirroring React's outer 'width: scaledTotalDecksHeight'. A --horizontal modifier aligns the rotor to the start (it pivots top-left) and drops the vertical min-height. Also simplifies mapTransform (the scaleType ternary had identical branches; Angular bakes scale into deck rendering, so the offset is plain translateY(-100%)). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Behavioural guards for the horizontal React-parity work: nose points left in LTR and right in RTL (rightToLeft drives the direction), and the built-in tooltip stays fully within the viewport in horizontal mode (P1b regression). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rotor refactor (e3043bb) made _buildLayoutData measure the rotor instead of the container, shifting widthInPx by the fuselage inset (~400->359) and breaking the seatMapInited React-parity payload test. Measure the container again; in horizontal it now carries the swapped dimensions, so the payload stays correct without the rotor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts: # projects/seatmap-lib/src/lib/components/jets-nose/jets-nose.component.ts # projects/seatmap-lib/src/lib/components/jets-plane-body/jets-plane-body.component.ts # projects/seatmap-lib/src/lib/components/jets-seat-map/jets-seat-map.component.html # projects/seatmap-lib/src/lib/components/jets-tail/jets-tail.component.ts # projects/seatmap-lib/src/lib/components/jets-tooltip/jets-tooltip.component.ts
…al (P1a) In horizontal mode the cabin is rotated 90deg (and flipped 180deg in LTR), so the grid's model-space arrow logic no longer matched what a sighted keyboard user sees: ArrowRight moved focus visually up/down instead of right. Remap the four arrows to the model key that produces the matching visual movement (opposite rotation for LTR vs RTL). Non-arrow keys and vertical mode are unchanged. Verified on the demo: all four arrows now move focus on-screen in the pressed direction, both LTR and RTL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ArrowRight must move focus visually rightward when the cabin is rotated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The nose & tail outline paths start 1.5 viewBox units inside the SVG border, so they rendered slightly narrower than the fuselage and left a visible step where they meet it (most noticeable on wide cabins like QT888). Scale each up by 200/(200 - 2*effectiveInset), where effectiveInset = 1.5 - strokeWidth/2, so the outline centre lines up with the fuselage border centre. Mirrors the intent of React's Nose/Tail `distanceFromBorder` compensation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolvedConfig rebuilt the merged colorTheme (mergeColorThemeWithConstraints) on every access, so resolvedConfig.colorTheme changed identity each CD. Every seat then saw a new colorTheme input, ran ngOnChanges and rebuilt its inner SVG — replacing the <path> between mousedown and mouseup, so the FIRST click on a seat was swallowed and the tooltip only opened on the second click. Cache the merged theme by the raw config.colorTheme reference so it stays stable. Pre-existing on the WCAG branch (origin/WCAG shows it before this work); main and dev are unaffected (their colorTheme is already stable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ilability The keyboard-navigation test needs to reach an interactive seat and open its dialog. It rendered with the shared sandbox's live availability, so it flaked whenever the flight had few free seats left. Pass an empty availability override so every seat is available (interactive) and the test is deterministic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In horizontal mode the rotated cabin can be far wider than the controls column, so the demo's flex-row layout squeezed the "Send Events" panel and event log into a sliver pushed off-screen to the right (the e2e helper even needs `force:true` to click through the overlap). Drive a `--horizontal` modifier from the active config's `horizontal` flag: switch `.demo-content` to a vertical stack and let the wide map scroll inside its own wrapper (`overflow-x:auto`) so the controls stay full-width and on-screen below the map. Also make the e2e playwright config honour `PW_BASE_URL` so the suite can target an already-running dev server (e.g. the dev worktree on :4201) without editing the file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…toggle Toggling `horizontal` (or other layout-affecting flags) via a config change rather than a full flight reload skipped `_updateHorizontalDims()`, so the swapped container width/height stayed null from the initial vertical load. The `.jets-seat-map` container then collapsed to the rotor's un-rotated layout height (~3139px for a long cabin), leaving a large empty gap above the page controls. Re-run the swapped-dimension measurement after the view re-renders on a settings-only config change so the container reserves the wide, short horizontal footprint. Covered by a new e2e that toggles horizontal on an already-loaded map and asserts the reserved height stays smaller than the width. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s, wings, nose The horizontal container only reserved the fuselage box (`offsetWidth`), so the absolutely-positioned side cabin labels and wings (~20px past the fuselage on each side) and the nose/tail caps were sheared off once the wrapper clipped overflow: cabin titles were cut at the bottom and the nose ran off the top-left. Measure the union of every rotor descendant's painted rect to reserve the true footprint, and nudge the rotor (a leading `translate` in the transform) so the top-left overflow is pulled back inside the box. The offset compensates for the already-applied shift so repeated measurements converge instead of oscillating. Covered by a new e2e asserting the reserved height exceeds the fuselage strip and that the nose and cabin labels stay within the container bounds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nexus
requested changes
Jun 25, 2026
nexus
left a comment
Contributor
There was a problem hiding this comment.
hi @McSpace ,
Please fix github-advanced-security [bot] findings and also, please pay attention to the following points:
[
{
"file": "projects/seatmap-lib/src/lib/components/jets-plane-body/jets-plane-body.component.ts",
"line": 174,
"summary": "fuselageFill getter dropped the hullColor fallback — consumers using the still-public hullColor alias now get default grey fuselage instead of their branded color.",
"failure_scenario": "Old getter: `hullColor ?? fuselageFillColor ?? default`. New getter: `fuselageFillColor ?? default`. IColorTheme.hullColor is still declared in types.ts (line 93, marked @deprecated but not removed). Wings still fall back to hullColor correctly — so a consumer with `colorTheme: { hullColor: '#002244' }` would see branded navy wings but default grey fuselage, a visible mismatch."
},
{
"file": "projects/seatmap-lib/src/lib/components/jets-seat-map/jets-seat-map.component.ts",
"line": 1005,
"summary": "onGridFocusin always records focusedCell.deckIdx = activeDeckIndex, ignoring which physical deck the focused seat belongs to when singleDeckMode=false.",
"failure_scenario": "With singleDeckMode=false (valid config, tested in spec) all decks are rendered simultaneously. User Tab-focuses a seat in deck 1 while activeDeckIndex=0 → focusedCell.deckIdx=0 recorded → SeatGridNavigationService.move() does `decks[0].rows` → arrow navigation jumps to a seat in the wrong deck. The correct deck index is available in the DOM via `[attr.data-deck-index]` on the ancestor deck-wrapper but is never read."
},
{
"file": "projects/seatmap-lib/src/lib/components/jets-seat-map/jets-seat-map.component.ts",
"line": 1552,
"summary": "_prepareSeatForEmit casts a formatted price string (e.g. 'USD 29') to number via `as unknown as number`, making seat.price a string at runtime while the type says number|string.",
"failure_scenario": "priceStr is built as `` `${currency} ${numericPrice}` `` (e.g. 'USD 29') then emitted as `price: priceStr as unknown as number`. Any integrator doing `if (seat.price > 0)` or `seat.price * quantity` gets NaN. The raw numeric value is available in the separate `priceValue` field added in this PR, but nothing in the type signature directs consumers toward it."
},
{
"file": "projects/seatmap-lib/src/lib/components/jets-seat-map/jets-seat-map.component.ts",
"line": 590,
"summary": "getDeckFloorLiningWidth mixes coordinate spaces — subtracts unscaled fuselageStrokeWidth from a scaled pixel width, producing a lining that is slightly wrong at non-default displayScale values.",
"failure_scenario": "Line 590: `const innerW = this.fuselageBodyWidth - 2 * stroke` where fuselageBodyWidth is in scaled CSS pixels but stroke is the raw config value. The CSS border is `stroke * displayScale` (correctly scaled in jets-plane-body). The mismatch introduces an error of `2 * stroke * (displayScale - 1)` in innerW — typically a few pixels at displayScale < 1, causing the lining to slightly overflow the fuselage outline. Fix: use `2 * stroke * this.displayScale` on line 590."
}
]
Resolves PR #23 conflicts after #22 (React-parity seat colouring) landed on main. #22 removed the colorfulSeatsByClass/colorfulSeatsByScore flags, seatClassTints and color-tint util; this branch still carried them plus the WCAG a11y work on top. Conflict resolutions: - nose/tail/tooltip components: keep WCAG a11y attributes (aria-hidden, role/aria-labelledby/aria-describedby, escape/keydown handlers). - seat-map.component.ts: drop colorfulSeatsBy* (adopt #22) and keep WCAG's memoised _mergedThemeValue colourTheme. - seat-map.component.html: keep WCAG template (a11y wrappers, list view, rotor); drop the deck's [colorfulSeatsByClass] binding (#22 removal). - jets-seat.component.spec.ts: keep ARIA + grid-cell tests; drop the obsolete colorfulSeatsByClass describe block (#22 removed the feature). - horizontalParity.spec.ts: keep WCAG's P1a arrow-key test. - re-add IPassenger import to jets-tooltip.component.ts (used by WCAG's buildRestrictedPassengerTypeMessage; was unused on main). - e2e baseline screenshots taken from main; regenerated separately. Verified: build:lib OK, unit tests 587/587 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reviewer (nexus): - jets-plane-body: restore hullColor fallback in fuselageFill getter so a consumer theming only the deprecated hullColor gets a branded fuselage (the wings already honour it); refresh the @deprecated note in types.ts. - jets-seat-map onGridFocusin: record the focused seat's real deck from the ancestor .deck-wrapper[data-deck-index] instead of always activeDeckIndex, so arrow nav works with singleDeckMode=false (all decks rendered). - jets-seat-map _prepareSeatForEmit: drop the misleading `priceStr as unknown as number` cast — price is typed number|string, so the formatted string is assigned honestly; priceValue carries the number. - jets-seat-map getDeckFloorLiningWidth: scale fuselageStrokeWidth by displayScale before subtracting from the scaled fuselageBodyWidth, fixing a ~few-px lining error at non-default displayScale. CodeQL: - #44 seat-grid-navigation skim(): remove the dead lastInteractive assignment (the loop returns on the first interactive cell, so the tail was unreachable). - #43 dom-diff.spec.ts: drop unused setAvailability import. - #42 jets-seat-map-preparer: drop unused getNativeRowHeight import. Adds a focusin regression test for the multi-deck deck-index fix. build:lib green; 588/588 unit tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
Fixed all four review findings and the three CodeQL alerts (#42/#43/#44) in 09529cb. build:lib is green and 588/588 unit tests pass. |
Stamp the WCAG 2.2 AA refit + PR #23 review fixes as 0.0.3 and date the CHANGELOG entry. The publish workflow's version-guard requires projects/seatmap-lib/package.json `version` to equal the release tag (v0.0.3) minus the leading `v`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nexus
approved these changes
Jun 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds WCAG/accessibility support to the seatmap (opt-in via
wcagconfig). Unit 598/598, e2e 123/123 (against the WCAG demo).Highlights
Test plan
npx ng test seatmap-lib --watch=false→ 598/598.dev → main) but predates that PR's seat-colour React-parity refactor. It still contains the oldcolorfulSeatsByScore/colorfulSeatsByClassflags +color-tint.ts, which React parity: horizontal layout + seat colouring #22 removes.main(drop the old colour flags / re-apply the seat-colour refactor) before merging, so the removed flags are not reintroduced.🤖 Generated with Claude Code