Skip to content

chore: security hardening + npm trusted publishing#17

Merged
McSpace merged 36 commits into
mainfrom
dev
Jun 10, 2026
Merged

chore: security hardening + npm trusted publishing#17
McSpace merged 36 commits into
mainfrom
dev

Conversation

@McSpace

@McSpace McSpace commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Re-opens #16 from the new dev branch (workflow change: all work flows through dev).

This PR has accumulated everything on dev since the previous merge to main — primarily two distinct workstreams (OpenSSF Scorecard hardening and npm Trusted Publishing) on top of routine React-parity / locale / e2e fixes already reviewed at commit time. Headline scope below.

Security hardening — OpenSSF Scorecard (4.4 → ~7.0, matches the React analog at 7.5/10)

  • Top-level permissions: read-all in ci.yml and codeql.yml (Token-Permissions 0 → 10)
  • All GitHub Actions pinned to commit SHA across ci.yml, codeql.yml, scorecard.yml; Dependabot already tracks github-actions and will bump pins automatically (Pinned-Dependencies 4 → 8+)
  • package.json overrides extended with hono >=4.12.21 and qs >=6.15.2 — closes 5 GHSA advisories surfaced via the Vite toolchain (Vulnerabilities 5 → 9+)
  • SECURITY.md with private disclosure channels (GitHub Security Advisories + email) and response SLA (Security-Policy 0 → 10)
  • CONTRIBUTING.md with the PR workflow, test commands and Conventional Commits style already used in the repo
  • scripts/setup-branch-protection.sh — one-shot, idempotent gh api call enabling required PR review + status checks (Unit tests, Build library and demo, CodeQL) and blocking force-push/deletion on main (Branch-Protection 0 → ~8 after running it)

Maintained recovers automatically once the repo crosses 90 days.

npm Trusted Publishing — closes the remaining Packaging / Signed-Releases gap

  • provenance: true added to projects/seatmap-lib/package.jsonpublishConfig, so npm publish attaches a Sigstore-signed attestation (Signed-Releases N/A → 10 after first OIDC release)
  • New workflow .github/workflows/publish.yml: triggered by a published GitHub Release, runs in a manual-approval npm-publish GitHub Environment, upgrades npm to ≥ 11.5.1, verifies projects/seatmap-lib/package.json version against the release tag, guards dist/seatmap-lib/ against SEATMAP_API_* / APP_ID / APP_KEY / env-config.js leaking into the published artifact, and npm publish --provenance --access public
  • NPM_TOKEN deliberately not referenced — workflow uses short-lived OIDC; old token is to be revoked after the first successful release
  • Admin checklist: INSTRUCTIONS_FOR_ADMIN.md at the repo root walks an admin through the one-time setup (GitHub Environment + npm Trusted Publisher) and the per-release flow

Test plan

  • npm audit → 0 vulnerabilities (was 5)
  • npm test → 315 unit tests pass
  • npm run build:lib → Angular package builds cleanly
  • .github/workflows/publish.yml YAML validates with js-yaml
  • Secret-leak guard regex does not false-positive against the current dist/seatmap-lib/ (README/LICENSE excluded)
  • CI green on the previous PR (Unit tests, Build, e2e, CodeQL — see #16)
  • CI green on this PR
  • After merge: run ./scripts/setup-branch-protection.sh from a repo-admin workstation
  • After merge: gh workflow run "Scorecard supply-chain security" and verify new score at https://api.scorecard.dev/projects/github.com/Kwiket/jets-seatmap-angular-lib
  • After merge: follow INSTRUCTIONS_FOR_ADMIN.md for the first OIDC release of @seatmaps.com/angular-lib

Andrey Vilchinskiy and others added 30 commits June 8, 2026 15:48
Close Scorecard checks failing on the Angular lib so it matches the
React analog (7.5/10):

- Add top-level `permissions: read-all` to ci.yml and codeql.yml so
  jobs run with least-privilege GITHUB_TOKEN (Token-Permissions 0 -> 10)
- Pin all GitHub Actions to commit SHA across ci.yml, codeql.yml and
  scorecard.yml; Dependabot already tracks github-actions and will
  bump pins automatically (Pinned-Dependencies 4 -> 8+)
- Extend package.json `overrides` with hono >=4.12.21 and qs >=6.15.2
  to close five GHSA advisories surfaced by `npm audit` via the Vite
  toolchain (Vulnerabilities 5 -> 9+)
- Add SECURITY.md describing private disclosure channels (GitHub
  Security Advisories + email) and response SLA (Security-Policy 0 -> 10)
- Add CONTRIBUTING.md with the PR workflow, test commands and
  Conventional Commits style used throughout the repo
- Add scripts/setup-branch-protection.sh: one-shot, idempotent
  `gh api` call that enables required PR review + status checks
  (Unit tests, Build library and demo, CodeQL) and blocks force-push
  and deletion on `main` (Branch-Protection 0 -> ~8 after running it)

Maintained will recover automatically once the repo crosses 90 days;
Packaging and Signed-Releases stay N/A until the first npm publish
workflow is added with `--provenance`.

Verification: `npm audit` reports 0 vulnerabilities, all 315 unit
tests pass, `ng build seatmap-lib` succeeds, and all three workflow
YAMLs parse cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 'n'

Reviewer found two leftover divergences after the previous parity pass:

- passengerTypes was still double-wrapped — service.ts:126 wrapped the
  already-array onlyForPassengerType into another array, producing
  [["ADT","CHD","INF"]]. React (service.js:100-103) assigns the value
  through verbatim, so we now do the same and correct the public type
  IAvailabilityItem.onlyForPassengerType to string[] (the single-string
  shape was a relic — every real caller passes an array).

- rotation was being dropped from the emitted payload. The previous "drop
  the field" choice matched the wrong React seat (an unrotated 70E whose
  rotation also happened to be absent in that snapshot). React's Seat
  fixtures and consumer code expect the field present with 'n' (north /
  no-rotation) as the default. _mapRotation now passes through any known
  direction and normalises unknown/empty input to 'n'; _prepareSeatForEmit
  emits the rotation alongside other public fields.

TSeatRotation gains 'n' alongside the legacy ''. The empty-string variant
is still allowed for backward compat with already-prepared seats, but the
public emit path always normalises '' → 'n'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ity proof

Reflects the follow-up fix in 83fc194:
  - passengerTypes must be a flat string[] (guards against the legacy
    [["ADT","CHD","INF"]] nesting from the availability merge bug)
  - rotation is now expected to be present with 'n' as the default
    (was previously asserted absent — that earlier check matched the
    wrong React seat and missed the actual contract)

Overlay panel now prints both checks; screenshots regenerated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The leading docblock still claimed `rotation` was no longer emitted —
out of sync with the actual contract (rotation present, defaults to 'n').
Comment-only fix, no behaviour change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ct parity)

setAvailabilityHandler used to ignore additionalProps, the tooltip's
amenities getter only read seat.features, and prepareSeatAdditionalProps
was dead code with no production caller — so integrator-defined extra
rows (e.g. "Priority boarding", "Free Wi-Fi") silently disappeared.
React stitches them after the API amenities, capped at 12, and emits
the same list on tooltipRequested.

Changes:
- setAvailabilityHandler concatenates entry+wildcard additionalProps
  (entry first, matching service.js:104-119) and runs them through
  prepareSeatAdditionalProps before attaching to the seat.
- Tooltip's amenities getter appends additionalProps after the API
  features and slices at 12 (DEFAULT_FEATURES_RENDER_LIMIT). The
  hiddenSeatFeatures filter applies only to API features, mirroring
  React's TooltipGlobal.js#finalListOfFeatures.
- prepareSeatAdditionalProps now sets title='' (not null) so the
  tooltip's negative-amenity strikethrough doesn't fire on integrator
  rows, and assigns a fresh uniqId for @for ... track stability.
- Tooltip template's title fallback flips from `??` to `||`, so an
  empty-string title still falls back to value. Negative amenities
  (title === null) keep their existing styling.
- Types: TSeatAvailability.additionalProps now declares the real
  {label, icon?, cssClass?} shape (was the stale {type, cssClass});
  ISeatFeature gains an optional cssClass.

Tests: 4 new preparer cases, 4 new setAvailabilityHandler cases,
4 new tooltip-amenities cases (combined list, no negative styling on
additionalProps, 12-cap, hiddenSeatFeatures scope). New e2e spec at
projects/seatmap-demo/e2e/additionalProps asserts the tooltip text,
the tooltipRequested.seat.additionalProps payload shape, and a
parity-proof screenshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lyForPassengerType

The previous fix (9dee1e8) flattened how `source.onlyForPassengerType`
is assigned to `seat.passengerTypes`, but the only regression coverage
lived in an e2e parity-proof that exercises the *emitted* payload (where
the default `['ADT','CHD','INF']` short-circuits the bug). The actual
disable path — `isSelectDisabled` reads the internal seat record's
`passengerTypes` — had no unit guard against the
`[["ADT","CHD","INF"]]` nesting.

This regressed in a reproducible way: with the user's availability
(seat `20E` carrying `onlyForPassengerType: ['ADT','CHD','INF']`) and an
ADT next-passenger, the Select button greyed out because
`passengerTypes.includes('ADT')` was checking a nested array.

Adds two unit tests on `setAvailabilityHandler`:
  - flat shape after a single pass
  - flat shape after a second, idempotent pass (mirrors the real ngOnChanges
    sequence where availability lands twice during demo init)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…eact parity)

Block built-in passenger management from removing or re-seating a passenger
marked `readOnly: true`:

- IPassenger gains an optional `readOnly?: boolean` field; the existing inline
  cast on the click path is dropped.
- Tooltip's "Unselect" button renders with `[disabled]` when the occupant is
  readOnly, mirroring React's TooltipGlobal.view.js:133.
- onTooltipUnselect early-returns on a readOnly occupant — defence-in-depth
  for viewOverride consumers and the side-panel delegation path that
  bypass the DOM disabled attribute.
- getNextPassenger skips readOnly entries so a seatless readOnly passenger
  never bids for the next free seat, matching React's service.js:198.

Coverage:
- unit: tooltip disables Unselect and swallows the click; seat-map's
  onTooltipUnselect is a no-op on readOnly; service.getNextPassenger
  skips readOnly seatless passengers.
- e2e: parity-proof spec in projects/seatmap-demo/e2e/readOnly/ with a
  close-up of the disabled vs enabled button block for visual diffing.

README's IPassenger interface example now lists `readOnly?: boolean` so the
documented type matches the published one.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tip (React parity)

When a seat is reserved for a subset of the default passenger types
(ADT/CHD/INF), the React tooltip shows a line like "The seat is only
for: adults" under the header (TooltipGlobal.js:147-154, shared slot
with `passengerLabel`). The Angular built-in tooltip rendered the
`passengerLabel` slot but never showed the restriction text — so seats
with `availability.onlyForPassengerType: ['ADT']` looked identical to
unrestricted seats.

Reuse the existing `jets-tooltip--header-passenger` slot: when no
passenger occupies the seat, render the localized restriction line
derived from `seat.passengerTypes`. Filtering, the "only if narrower
than default" guard, and unknown-type drop mirror React exactly.
Locale keys (`ADT`/`CHD`/`INF`/`seatRestrictions`) already existed
in `constants.ts` for every supported language.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s (React parity)

The built-in tooltip wrapped every amenity glyph in a 1-px box derived
from `--tooltip-icon-border` (default `#4f6f8f`) and `--tooltip-icon-bg`.
React only applies those CSS vars to the dimensions block
(`.jets-tooltip--measurement` — Pitch/Width/Recline), never to the
feature list (`.jets-tooltip--feature`), which renders SVGs as bare
glyphs (TooltipGlobal.view.js:73-91 vs :97-119).

Drop `border` and `background` from `.jets-tooltip--amenity-icon`. Size,
flex layout, and the negative-amenity color override are unchanged.
Dimensions box styling is unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed (React parity)

`onSeatMapInited` payload was emitting `availabilityData = this.availability`
(the integrator-supplied Input that controls per-seat status/colour). React
emits something different there: the read-only `{ availableSeats: [...] }`
block that the Quicket API itself returns as a sibling element of the
response array (api.js:101-104). The two shapes are unrelated — integrators
relying on the React contract were getting Angular's per-seat override
array under the same key.

Changes:
- jets-seat-map-api.service.ts: after array merging, look for an element
  with `id === 'availabilityData'` and copy the rest of its fields onto
  `response.availabilityData` (marker `id` stripped, matching React).
- jets-seat-map.service.ts: forward `apiResponse.availabilityData` through
  `getSeatMapData`'s return shape.
- jets-seat-map.component.ts: replace `availabilityData: this.availability`
  with `result.availabilityData` in the seatMapInited payload; comment
  explains the distinction so the Input/payload conflation doesn't
  resurface.
- types.ts: add `ISeat` + `IAvailableSeatsData` (mirroring React's public
  types); change `IInitialLayoutData.availabilityData` from
  `TSeatAvailability` to `IAvailableSeatsData`; add the field to
  `IApiSeatmapResponse` so the service layer is typed end-to-end.
- spec updates: api.service.spec.ts gains two cases (marker present /
  absent); component.spec.ts rewrites the misleading "mirroring the
  Input" assertion to prove payload sources `availabilityData` from
  the API result and that the Input does not leak through.
- .gitignore: `.env.local*` so per-task credential overrides (used to
  hit sandbox v1.1 for this fix's e2e parity proof) stay out of git.
- Refreshed `seatMapInitedParity-overlay.png` capturing the real
  sandbox payload with `availabilityData.availableSeats` populated.

Verification: 341 unit tests pass (was 315 — 2 new in api.service spec,
1 rewritten in component spec); ng build seatmap-lib clean;
`seatMapInitedParity` e2e green against sandbox v1.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…TY pass (React parity)

`setAvailabilityHandler` only assigned `passengerTypes` when the seat
didn't already have one — so after the first SET AVAILABILITY, repeat
calls silently kept the stale list. A second pass tightening the rule
from `['ADT','CHD','INF']` to `['ADT','CHD']` (or relaxing it back)
left the tooltip restriction line empty and `isSeatSelectDisabled`
gated against the wrong whitelist. Manifested as "works only once."

React (jets-seatmap-react-lib-pub/src/components/SeatMap/service.js:100-103)
overwrites on every pass: entry → wildcard → `DEFAULT_SEAT_PASSENGER_TYPES`.
Mirror that exactly. Add three regression specs: replace-on-rerun,
default-when-omitted, wildcard-fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous run baked the screenshots against a stale `ng serve` process
that was holding a pre-9dee1e8 bundle in memory (the commit that restored
the `rotation: 'n'` default to the seat-event payload). With the dev
server re-launched and `dist/seatmap-lib` rebuilt from scratch the spec
passes and the captured payload now correctly shows `"rotation": "n"`
alongside the flat `passengerTypes: ['ADT','CHD','INF']` it always did.

No library code changes — this is purely a baseline refresh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… parity)

When the seatmap API call fails, the lib now still fires `seatMapInited`
with an `error` field and `undefined` layout fields — mirroring React's
`onSeatMapInited` payload shape. Previously only `loadError` was emitted,
so consumers wiring only `onSeatMapInited` (the React-equivalent contract)
silently lost errors.

The error string is formatted as `postData: {status} - {message}` (React
parity), preferring the HTTP body's `message` over the generic transport
message so callers see the actual backend validation failure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…(React parity)

The Quicket API returns HTTP 200 with `[{id, error: "…"}, …]` for some
flight-level failures (e.g. invalid departure IATA returns "schedule is
not found for the flight"). Previously _postSeatmap silently picked
`rawResponse[0]` (the matching error object — no decks, no seatDetails)
and the component's catch never fired, so the seatMapInited payload
shipped with `error: undefined`. The companion fix in 62a4e4c surfaces
catch-path errors via seatMapInited, but only HTTP transport failures
ever reached that catch — soft errors still slipped through.

Mirrors React `api.js:80-83` which detects the item with `id === flight.id`
and `throw new Error(item.error)`. The thrown Error now propagates into
the component's catch and lands in `seatMapInited({error})` like any
other failure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-sourced

Hits the Quicket sandbox on EK 2 LHR→DXB (the flight whose response is
known to carry the `{ id: 'availabilityData', availableSeats: [...] }`
marker block) and feeds the lib's `availability` Input a deliberately
unfakeable value (`currency: '__INPUT__'`, `price: -42`,
`label: '__INPUT_ONLY_LABEL__'`, `color: '#deadbe'`). The captured
`seatMapInited.availabilityData` payload then has to:

- be shaped as `{ availableSeats: [...] }` (API contract), not as the
  Input's `Array<{ label, price, currency, ... }>` shape;
- carry no seat whose currency/price/label/color matches any Input
  marker (no merge);
- not deep-equal the Input array as a whole.

Locks down the contract pointed out in the task spec ("availabilityData
is read-only, should contain server response data, nothing should be
merged there") so a future refactor cannot silently route the Input back
into the payload.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setting `currentDeckIndex` to a value outside [0, content.length) collapsed
`visibleDecks` to an empty array, leaving just the fuselage outline on screen.
Guard the assignment in `ngOnChanges` so an invalid integrator value is
silently ignored and the previously active deck stays rendered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…act parity)

Tooltip rendered `amenity.title || amenity.value`, so the localized
`audioVideo`/`wifi`/`power` label always won over the backend `summary`
field. React's TooltipGlobal.view.js:89 renders `{value}` directly,
where the cabin-level summary lands via `_mergeCabinFeatures`
(data-preparer.js:87-107). New helper `amenityText()` returns `value`
when it's a non-empty string/number, falling back to `title` only for
the `value=true` boolean-flag case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tangle

JetsWingComponent drew the wing as a plain rectangle with a 6 px CSS
clip-path triangle pasted at the top to fake a leading edge. At the
typical 1:45 wing aspect ratio that notch was nearly invisible and the
wing had no trailing edge at all — leading to a "wrong shape" report
from a custom-theme consumer.

Replace the rectangle + clip-path overlay with a single SVG path that
draws the actual swept-back parallelogram silhouette (sloped leading
AND trailing edges, slope ≈ tan(30°) · wingWidth, capped at 40 % of
wing height so the wingtip stays visible). Tie the viewBox height to
wingHeight so 1 viewBox unit ≈ 1 rendered pixel — without this, any
sweep offset specified in viewBox units would be vertically stretched
by the path's aspect ratio.

Refresh the visibleWings / colorTheme Playwright baseline screenshots
to reflect the new silhouette.
…cales

Angular's LOCALIZATION_* maps diverged from the React lib's verified
strings (e.g. DE pitch was 'Sitzabstand', React uses 'Abstand'; RU
cancel was 'Отмена', React uses 'Закрыть'). Shared keys across EN/RU/
CN/DE/FR/ES/IT/PT/PT-BR/AR/JA/KO/TR/NL/PL/CS/UK/VI now mirror React's
i18n.languages.js verbatim, including the `(%s)` format markers that
React ships for power/audio in PL/ES/DE.

Angular-only keys (legend, gallery, status labels, deprecated power/USB
synonyms, *_legroom snake_case) are preserved. `pitchShort`/`widthShort`/
`reclineShort` are dropped — React's `pitch`/`width`/`recline` are
already the short form, so the preparer's locale fallback is simplified
to a single key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The spec was anchored to flight EK 2 LHR→DXB (id "1111") because the
Quicket sandbox used to surface the `{ id: 'availabilityData',
availableSeats: [...] }` sibling element for that flight. The sandbox no
longer emits it, so the assertion `payload.availabilityData != null`
started failing on CI — not a lib regression, the upstream contract
moved.

Intercept the seatmap POST with page.route() and append a fixed
`availabilityData` block to whatever the API returns. The mocked
seats use plain USD/EUR currencies and positive prices so they can
never collide with the integrator's INPUT_MARKER values that the
leak-detection assertion looks for.

Also refresh the eventPayloadParity baseline screenshots — incidental
diffs from the new wing silhouette landed in 6993fa8.
`onSeatSelected/onSeatUnselected` returned `passenger.seat = { price: <raw
number>, seatLabel }` and silently dropped both `currency` and `priceValue`.
React's contract (service.js:44-63) ships the formatted price string
(`"USD 33"`), the numeric `priceValue`, and `currency` alongside `seatLabel`,
because its `setAvailabilityHandler` had already rewritten the seat record.

Angular keeps the internal `seat.price` numeric and never sets `priceValue`,
so the parity is produced at the moment of selection: a small helper
promotes `seat.{price,currency}` into the public payload, mirroring the
same formatting `_prepareSeatForEmit` uses for `tooltipRequested.seat`.
Fields with no source value are omitted instead of leaking `undefined`.

`IPassenger.seat` widens accordingly: `price` becomes `string | number`
(string for lib output, number tolerated for integrator-provided fixtures),
`currency?: string` and `priceValue?: number` join the shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… body

`getDeckCabinLabelGap` returned 0 for any full-width deck, so the cabin
label was pinned at the deck-floor edge regardless of the consumer's
`colorTheme.cabinTitlesWidth`. Bumping the value from 80 → 200 moved
nothing on screen; this regressed React parity where the same knob
clearly opens up the gap between the label and the body.

Sum a `cabinTitlesWidth*scale - LABEL_WIDTH - OUTER_INSET` push on top
of the existing narrow-deck adjustment. The clamp at zero means a small
`cabinTitlesWidth` (or one dominated by a larger `wingsWidth`) still
hugs the body — only growth past the label's own width opens the gap.

Refresh the colorTheme / visibleCabinTitles baseline screenshots that
re-rendered with the now-shifted label position.
React parity (jets-seatmap-react-lib-pub PR #171, commit 7e0e30f):
register `'FR-CA'` in `LOCALES_MAP`, add `'FR-CA'` to the `TLang` union,
and ship `LOCALIZATION_FR_CA` with React's verbatim `LOCALE_FR_CA`
strings for the shared keys (e.g. `pitch`/`recline` → `'Inclinaison'`,
`bassinet` → `'Moïse disponible'`, `nearGalley` → `'Près de l’office'`).

Angular-only keys (status labels, `legend`/`gallery`/`loading`, the
snake_case `restricted_legroom`/`extra_legroom` synonyms) reuse the
closest LOCALIZATION_FR string so the dict matches the shape of every
other Angular locale and the runtime fallback chain stays identical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r/icon/label

`prepareSeatAdditionalProps` already carries the integrator-defined
`availability[].additionalProps[].cssClass` onto each `ISeatFeature`, but
the tooltip template never bound it to the DOM, so the documented contract
("container, icon and label classes from a single `cssClass`") was dead.
Wire `cssClass`, `cssClass-icon`, `cssClass-label` onto the three nodes so
host stylesheets can target them as the README promises.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The swept-trapezoid path from 6993fa8 sloped BOTH the leading and the
trailing edge by `sweep` units, producing a true parallelogram. The
React reference only slants the top (leading) edge; the trailing edge
runs flat across the wing chord.

Drop the `-s` offset on the inner-bottom vertex in both wing paths:

  LEFT  M60,0 L0,s L0,h L60,h  Z   (was L60,h-s)
  RIGHT M0,0  L60,s L60,h L0,h  Z   (was L0,h-s)

Refresh affected colorTheme / visibleWings baseline screenshots.
…dimension tile

Previously these two color-theme fields styled only the small inner glyph
box of each measurement (.jets-tooltip--dim-icon). React parity:
TooltipGlobal.view.js:99-101 applies them to the whole .jets-tooltip--measurement
tile, with tooltipIconColor remaining the SVG line color. Swap the CSS-var
targets so the outer .jets-tooltip--dimension consumes --tooltip-icon-border /
--tooltip-icon-bg, and drop them from the inner block.

Side effect (also React parity): tooltipBackgroundColor no longer bleeds
into dimension tiles; tooltipFontColor no longer recolors tile borders.
Affected baselines regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Production rule: console.log and console.error are stripped. Library
keeps the 401-retry console.warn (diagnostic, not in the deletion list).
Demo event handlers continue to surface activity via the in-app EVENT
LOG panel (addLog), so the console duplicates are pure noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Rename to @seatmaps.com/angular-lib (mirrors React lib scope).
- Add description, keywords, repository, author, license (Apache-2.0),
  bugs, homepage, publishConfig.access=public.
- Include LICENSE in tarball via ng-package assets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ng-packagr forbids assets paths outside project root, so revert the
ng-package assets entry and copy LICENSE into dist/seatmap-lib after
ng build via a cross-platform node copyFileSync step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
11fdef7 stripped console.* across the repo, but the production policy
("to be deleted in production: console.log, console.error") applies
strictly to the published library, not to the dev demo. Restore the
demo's debug output so a developer running `npm start` sees the same
event firehose in DevTools and in the in-app EVENT LOG panel. The
library stays unchanged (only the 401 retry `console.warn` lives there).
Andrey Vilchinskiy and others added 6 commits June 9, 2026 22:25
…onent

Verifies that 4 independent JetsSeatMapComponent instances can coexist on
a single page without bleed between `providedIn: 'root'` services. Adds a
fixture component (2x2 grid, 2 flights x 2 cabin classes, distinct themes
and one horizontal variant), wired via `?multiInstance=1` query-param so
the main demo UX is untouched. Playwright spec asserts independent inited
payloads and produces a full-page screenshot under
`e2e/multiInstance/screenshots/four-instances.png`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t (React parity)

Previously, fields from `config.apiMetadata` were spread onto the top
level of the seatmap request body, so a config like
`apiMetadata: { PROPRIETARY_KEY: 'PROPRIETARY_VALUE' }` produced a
payload with `PROPRIETARY_KEY` at the root. React (api.js:64) instead
sends `metadata: this._apiMetadata ?? undefined`, keeping proprietary
fields under their own `metadata` object and omitting the key entirely
when no metadata is configured.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…blishing

npm Trusted Publishing requires `provenance: true` so `npm publish`
generates a Sigstore-signed attestation that ties the published tarball
to the GitHub Actions run that built it. Setting it under publishConfig
keeps the contract with the package rather than relying on CLI flags;
the CI workflow still passes `--provenance` explicitly as defence in
depth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a release-triggered workflow that publishes @seatmaps.com/angular-lib
to npm using OIDC trusted publishing instead of a long-lived NPM_TOKEN.
The job runs in a protected `npm-publish` environment (manual approval),
verifies that projects/seatmap-lib/package.json version matches the
release tag, upgrades npm to >= 11.5.1 (required for trusted publishing),
runs a guard against SEATMAP_API_*/APP_ID/APP_KEY/env-config.js leaking
into the built artifact, and publishes from dist/seatmap-lib with
--provenance --access public.

NPM_TOKEN is intentionally not referenced; it stays in repo secrets as a
manual fallback until the first successful OIDC release and will be
revoked afterwards.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent coordinate systems were colliding in the renderer: rows
flow with a CSS margin-top derived from `(row.topOffset - prev) * scale -
prevRowHeight`, while bulks are absolutely positioned at `bulk.topOffset
* scale + topAdjust`. When the API places a row whose native height
exceeds the slot between its topOffset and the neighbouring bulk's
topOffset (e.g. UA953 ORD->MUC row 5 over the fish lavatory, row 22/30
over the cabin divider), the seat visually bites into the partition.

The preparer now post-processes deck.extras.bulks via
`_resolveBulkOverlaps`: for every bulk whose native bbox actually
overlaps a row above or below, push the top down / trim the bottom by
the overlap plus a small configurable gap. Adjustments that would
collapse a bulk below 8 native units or 20% of its original height
are silently skipped. The pass is a strict no-op when no overlap is
detected, so already-correct layouts pass through unchanged. New
`IConfig.partitionGap` (default 4 native units, ~2-3 px at typical
deck scale) is the public knob; pass 0 to opt out entirely and use
raw API coordinates verbatim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
INSTRUCTIONS_FOR_ADMIN.md walks a repo/npm admin through the one-time
setup (npm-publish GitHub Environment, npm Trusted Publisher config),
the per-release flow (create GitHub Release, approve workflow gate,
verify), and the post-release NPM_TOKEN revoke. Kept at the repo root
so it surfaces in `gh repo view` and PR file lists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@McSpace McSpace changed the title chore(security): improve OpenSSF Scorecard score (4.4 -> ~7.0) chore: security hardening + npm trusted publishing Jun 10, 2026
@nexus nexus self-assigned this Jun 10, 2026
@nexus nexus self-requested a review June 10, 2026 12:15
@McSpace McSpace merged commit 030b80d into main Jun 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants