Both @braintree/sanitize-url (this branch) and @internationalized/date
(master) were added to packages/ui dependencies; keeping both.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
- Replace custom UNSAFE_HREF_RE with @braintree/sanitize-url for robust XSS prevention
- Shorten renderInlineMarkdown JSDoc
- Single-user with href: collapse two adjacent links into one wrapping avatar + name
- Multi-user list: use href ?? index:name as key to avoid collisions on duplicate names
- Status dot: replace role="img"/aria-label with aria-hidden (text label is sufficient)
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
- Tags: replace div+Fragment with ul/li; move circle divider to CSS ::before pseudo-element, removing it from the DOM
- Metadata: replace div wrapper with dl/dt/dd for proper key-value semantics; reset dl and dd browser margins
- HeaderMetadataUsers: render multi-user stack as ul/li; simplify single-user branch into one ternary with a fragment
- HeaderMetadataStatus: add role="img" and aria-label to the status dot so screen readers announce its meaning
- Restore Fragment import from react
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Uses marked's Lexer.lexInline() instead of a hand-rolled regex to parse
inline links in the Header description. marked ships CommonJS, has zero
dependencies, and is already used in the monorepo. This gives us a proper
token model that handles edge cases the regex could not.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
The following patches were included in the v1.50.2 patch release and
no longer need to be tracked:
- pr-33908 (TechDocs sidebar positioning)
- pr-33952 (zod v4 bump)
- pr-33975 (React Aria dependency clamping)
- pr-33984 (tab indicator opacity fix)
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
- Replace hardcoded gap: 20px with var(--bui-space-5) in metadata row
- Trim leading whitespace from href before unsafe-scheme check to prevent bypass
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Reverting the full-width change as it is too disruptive at this time
and will be handled in a separate PR.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
- Guard against unsafe URL schemes (javascript:/vbscript:/data:) in description links
- Use index-based keys for tags and metadata to avoid duplicate-key warnings
- Render ReactNode metadata values directly instead of wrapping in Text to avoid invalid span>div nesting; only wrap plain strings in Text
- Replace empty-string Avatar src fallback with data:, to prevent spurious page requests
- Fix JSDoc in HeaderMetadataUsers to accurately describe the row layout
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
react-markdown v8+ is ESM-only and breaks Jest in Node-role packages that
transitively import @backstage/ui via core-app-api. Since the Header
description only needs inline link support, a small regex-based parser
is sufficient and avoids the ESM dependency entirely.
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
Add `isPending` prop to Alert, Button, ButtonIcon, Table, and
TableRoot, aligning with React Aria naming conventions. The
`loading` prop is deprecated but remains functional as an alias.
CSS selectors now target `data-ispending` instead of `data-loading`
for pending state styling. The `data-loading` attribute is still
emitted for backward compatibility.
Internal Table hooks (`PaginationResult`, `UsePageCacheResult`)
renamed `loading` to `isPending`. The `useTable` hook returns both
`isPending` and `loading` on `tableProps` to preserve backward
compatibility.
Updated docs-ui documentation and stories accordingly.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
The NOT IN guard against null final_entity rows was preventing the
query planner from using parallel workers and (with a future covering
index) index-only scans. Without the covering index it caused a 2.6x
regression on the no-filter path (7s -> 18.4s).
The filtered path already excludes unstitched entities via the
whereNotNull('final_entities.final_entity') in the inner
entityIdSubquery, so no guard is needed there.
The no-filter path now matches 1.49.x behavior. A followup migration
adding a covering index can re-introduce the guard efficiently.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
When the app was served under a non-root base path, BUI link
components rewrote absolute `href` values as in-app paths — e.g.
`https://example.com` became `/basename/https:/example.com` —
because every href was passed through react-router's `useHref`,
which treats all strings as relative paths.
External URLs (`http://`, `https://`, `//`, `mailto:`, `tel:`)
now bypass href resolution. Internal hrefs are normalized to
their canonical pre-basename form in `useDefinition`, so
downstream resolution by react-router's `useHref` (for
rendering) and `navigate` (for click-navigation) adds the
basename exactly once.
Signed-off-by: Johan Persson <johanopersson@gmail.com>
Remove `export` from five internal helper functions that are not part of
the published API and are only used within their own files. The `sleep`
function in GitLabIntegration is kept exported for test access but marked
as `@internal`.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
The `otherGraph` variable in `listChangedPackages` was incorrectly
created from `thisLockfile` instead of `otherLockfile`, making the
merged dependency graph a duplicate of the current one. This meant
that dependencies only present in the old lockfile were never added
to the graph, so transitive removals could not be detected.
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
Made-with: Cursor
Instead of checking IS NOT NULL against ~520k rows (which adds a
2.6x regression on the no-filter path), exclude the tiny set of
entities where final_entity IS NULL (~3,700 rows). The anti-join
is nearly free and allows the guard to be applied unconditionally,
so facets results consistently exclude not-yet-stitched (or future
tombstoned) entities regardless of whether filters are present.
Also addresses review feedback:
- Add regression test for unstitched entity exclusion
- Extract setupFacetsCatalog helper to reduce test boilerplate
- Tighten assertions: use exact arrays instead of arrayContaining
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor
Benchmarking on a production-like database shows that always applying
the final_entities subquery (even without filters) causes a 2.6x
regression on the no-filter path (5.2s -> 13.5s) due to ~530k
memoized index lookups against final_entities. The FK cascade from
search -> final_entities already guarantees search rows only exist
for entities with a final_entities row, so the constraint is only
needed when filters route through final_entities.
Signed-off-by: Fredrik Adelöw <freben@spotify.com>
Made-with: Cursor