Form field descriptions were visually rendered but not connected
to inputs via aria-describedby, making them invisible to screen
readers.
Added a descriptionSlot prop to FieldLabel that renders the
description as a React Aria <Text slot="description"> element,
letting React Aria automatically wire up aria-describedby on
the associated input.
Applied to TextField, PasswordField, SearchField, Select,
RadioGroup, and CheckboxGroup. Slider already handled this
manually and is unchanged.
Also added a missing WithDescription story for RadioGroup.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Replace `humanizeEntityRef(parseEntityRef(owner), { defaultKind: 'group' })`
with `entityPresentationSnapshot(ref, { defaultKind: 'group' }).primaryTitle`
in the OwnershipCard's `useGetEntities` hook. This was the last remaining
usage of the deprecated function in the codebase.
Signed-off-by: Marat Dyatko <maratd@spotify.com>
Made-with: Cursor
Signed-off-by: Marat Dyatko <maratd@spotify.com>
Made-with: Cursor
Signed-off-by: Marat Dyatko <maratd@spotify.com>
Made-with: Cursor
- MySQL UPDATE returns 0 affected rows when values are unchanged, so
detect existence via SELECT within the transaction instead.
- Add integration tests for PUT /locations/:id in createRouter.test.ts.
- Add changeset for catalog-react catalogApiMock update.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
The existing tests never exercised `getQueryParams` because the mock
catalog API always returned empty items. These new tests verify that the
generated query parameters include correctly humanized owner entity refs,
handle multiple owners, and group entities by kind and type.
Made-with: Cursor
Signed-off-by: Marat Dyatko <maratd@spotify.com>
Made-with: Cursor
Made href resolution always-on in useDefinition instead of requiring
each component to opt in via resolveHref: true. The hook now always
calls useInRouterContext/useHref and only applies the resolved value
when an href prop is actually provided, preventing href from leaking
into components that don't accept it.
Removed the resolveHref field from ComponentConfig, the
ResolveHrefConstraint type, and resolveHref: true from all 14
component definitions. Also removed a now-unnecessary type cast
in the Text component.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Replace direct entityPresentationApiRef usage with the new
entityPresentationSnapshot helper from #33576. This fixes the type
safety concern where CompoundEntityRef was passed to forEntity()
which only accepts Entity | string.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
The PID file was written to the database directory before
`pg.initialise()`, which prevented initialization from succeeding.
Moved the PID file write to just after initialization but before
starting the database.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
* feat(ui): export TableBodySkeleton as public API
Export the TableBodySkeleton component so it can be used independently
of the built-in Table component. Relax the column type constraint from
ColumnConfig<T> to { id: string } for compatibility with custom column
types.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
* fix(ui): use direct index instead of parsing skeleton item ID
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
* Update .changeset/export-table-body-skeleton.md
Co-authored-by: Johan Persson <johanopersson@gmail.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
* docs(ui): add TableBodySkeleton to table primitives documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
---------
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Johan Persson <johanopersson@gmail.com>
* feat(ui): widen activeTabId type to accept null
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* feat(ui): add automatic active tab detection to HeaderNav
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* feat(ui): update Header stories to demonstrate auto-detection and explicit activeTabId
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* refactor(ui): remove manual useActiveTabId from PluginHeaderAndHeader recipe
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* docs(ui): update Header docs for activeTabId auto-detection
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* chore(ui): add API report and changeset for activeTabId auto-detection
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* fix(ui): resolve relative hrefs in HeaderNav tabs
Add resolveHref to HeaderNavItemDefinition so tab links with relative
hrefs are resolved against the router context before rendering.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
* chore(ui): add breaking change changeset for HeaderNav resolveHref
Signed-off-by: Johan Persson <johanopersson@gmail.com>
---------
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Replace Material UI components with Backstage UI (BUI) equivalents
across the InspectEntityDialog and all its tab pages (Overview,
Ancestry, Colocated, JSON, YAML).
- Dialog shell uses BUI Dialog, DialogHeader, DialogBody, Tabs
- Horizontal tab bar replaces vertical MUI tabs
- Card sections use BUI Card, CardHeader, CardBody
- Key-value pairs rendered as semantic dl/dt/dd elements
- Copy buttons use BUI ButtonIcon with remixicon icons
- Help links use BUI ButtonLink
- Alerts use BUI Alert
- Tags use BUI TagGroup/Tag
- Accessible live region for copy confirmation
- Proper heading hierarchy (h2 for tab pages, h3 for cards, h4 for sections)
- Added OverviewPage tests for identity rendering, link detection, and tags
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Use UPDATE...RETURNING for Postgres/SQLite and UPDATE+count-check+SELECT
(in a transaction) for MySQL. Removes the old read-before-write approach.
Also adds test coverage for updateLocation across all database engines.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Replace humanizeEntityRef with entityPresentationApiRef in CatalogGraphCard
and CatalogGraphPage for consistent entity display via the Catalog
Presentation API. Contributes to #20955.
Signed-off-by: Matt Van Horn <matt@osc.dev>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
- Add `entityRef` field to all Location API responses, carrying the stable
entity ref (e.g. `location:default/generated-<sha1hex>`) that was
already persisted to the `location_entity_ref` column.
- Make `entityRef` filterable via `POST /locations/by-query`.
- Add `PUT /locations/:id` endpoint that updates the `type`/`target` of
an existing location and issues the corresponding delta mutation so the
catalog entity is updated in-place without changing its entity ref.
- Wire `updateLocation` through `CatalogApi`, `CatalogService`,
`CatalogClient`, `LocationService`, `LocationStore`, and their
implementations and mocks.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
When the JWT id_token lacks a sub claim, the cache key would be
auth0-profile:undefined, causing all users without a sub to share
the same cached profile. Now skips caching entirely when sub is
missing and fetches the profile directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
Replace manual base64 JWT decoding with jose's decodeJwt for
correctness and consistency with other auth modules. Add jose
as a dependency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
Auth0 rotates refresh tokens on each use, causing cache misses every
time. Use the user's sub claim from the ID token as the cache key
instead, which is stable per user.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jonathan Roebuck <jroebuck@spotify.com>
Every page refresh triggers the auth0 authenticator to fetch the user
profile from Auth0's /userinfo API. Auth0 enforces strict rate limits on
this endpoint, causing failures at scale.
Add a createAuth0Authenticator factory that accepts an optional
CacheService to cache profile responses with a 1-minute TTL. The module
now uses the cached variant by default. The existing auth0Authenticator
export remains available for use without caching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>