Dependency List
An auditable dependency tree with real semver comparison, graded staleness, ordered CVE severities, licence obligation classes and import paths.
Loading preview…
Installation
npx shadcn@latest add https://ui.zyeon.ai/r/dependency-list.jsonPrompt
Build a React + TypeScript + Tailwind "DependencyList" component using zod,
lucide-react and the local cn() utility.
Contract (dependency-list.contract.ts)
- dependencySeveritySchema = z.enum(["critical","high","moderate","low"]),
declared worst-first from a DEPENDENCY_SEVERITY_ORDER tuple so the ordering
is data rather than a second hardcoded ranking.
- dependencyAdvisorySchema = { id, severity, title, url, patchedIn? }. `url`
is refined to an absolute http(s) form — `#`, `""` and relative paths are
rejected at the contract boundary so a dead advisory anchor can never reach
the DOM.
- dependencyRowSchema = { id, name, range?, installed, latest?, license?,
relation: "direct" | "transitive", via?: string[], sizeBytes?, advisories?,
homepage? } with a refine: a transitive row must carry a non-empty `via`
chain, because "transitive" without the chain that introduced it is not
actionable. `latest`, `license` and `via` are optional so "not reported"
stays representable and distinct from "fine".
- dependencyListSchema = { status: "loading"|"empty"|"error"|"ready", items }.
- Props extend the inferred row array with status, label, locale ("en-US"),
errorMessage, onRetry?, skeletonRows (clamped 1–24), defaultSort,
defaultScope, className and native section attributes; forwardRef<HTMLElement>.
Behavior — version arithmetic (the part that must not be faked)
- Parse versions with a real semver regex (optional leading v, discard build
metadata) and compare by precedence, never as strings. Text comparison ranks
1.10.0 below 1.9.0 and ranks 2.0.0-beta.1 above 2.0.0 — the two most common
upgrade situations, both reported backwards.
- Prerelease rules follow semver: a release outranks any prerelease of the same
core; numeric identifiers compare numerically and rank below alphanumeric
ones; a shorter identifier list ranks lower.
- Grade staleness instead of emitting one "update available":
current / prerelease / patch / minor / major, and carry the major gap so
"3 majors behind" is distinguishable from "1 major behind". A version that
fails to parse, or a missing `latest`, yields "unknown" — never "up to date".
- Evaluate the declared range so the row answers "does npm update reach this?":
caret is major-locked above 0.x, minor-locked on 0.x and patch-locked on
0.0.x; tilde is minor-locked; `>=` is open; `*` / `x` / empty accept
anything. Apply npm's prerelease rule (a prerelease only satisfies a
comparator that names a prerelease on the same core). Unmodelled syntax
returns "unknown" rather than a guess.
Behavior — risk
- Sort each row's advisories worst-first and take the worst as the row's
severity. Severity is shown with the word, a stair-step pip meter and the pip
count; hue is the last channel, and critical/high deliberately share it so
nothing depends on telling two reds apart.
- Every advisory links to its real source in a new tab with
rel="noreferrer noopener" and an sr-only "opens in a new tab".
- Classify licences into obligation classes: permissive, weak-copyleft,
strong-copyleft, proprietary, unknown. The `-only` / `-or-later` / `+`
suffixes fold onto the base id. SPDX expressions: a pure OR lets you take the
least demanding alternative, anything else stacks every obligation. An absent
licence, NOASSERTION or "SEE LICENSE IN …" is `unknown` — its own class,
ranked above permissive and weak copyleft in the risk sort, drawn with a
dashed chip reading "unreviewed". It must never render as cleared.
- Transitive rows render the full import chain as a wrapping paragraph
("via a to b to c"), with no truncation, no max-height and no scroller: a
deep chain is exactly when every hop matters.
Behavior — filtering and sorting
- Filters are real and compose: substring search over name *and* import chain,
scope (all / direct / transitive), "vulnerable only" and "outdated only"
toggles whose badge counts come from the same derivation as the rows.
- Sorts: severity-worst-first (ties broken by advisory count, then licence
class, then staleness), most-outdated-first (level then major gap), name, and
largest install first. Every sort ends with a source-index tiebreak so the
order is deterministic.
- Filtering to zero renders its own panel ("No packages match these filters"
plus a Clear filters button), textually distinct from status="empty" ("No
dependencies recorded"). A persistent polite live region reports
"Showing M of N packages".
- Never cap the rendered rows, advisories or path hops.
Rendering & styling
- Semantic tokens only: bg-card, bg-muted, bg-secondary, bg-background,
bg-primary/10, bg-destructive/10 and /15, text-muted-foreground,
text-destructive, border and ring. No hex, no rgb(), no oklch().
- No breakpoints at all, viewport or container — every row is a wrapping flex
line, so the same list is a full page in one app and a 320px sidebar in
another. Do not reach for @container out of habit: with no @-variants to feed
it, container-type: inline-size only stops the card sizing to its own content
inside a w-fit or auto-width parent. Long package names and path hops carry
both `wrap-anywhere` and `min-w-0`; verify by removing each one separately,
since either alone can be the load-bearing half.
- Native select elements for scope and sort, with [&>option] token colours so
the popup is readable in dark mode; toggles are buttons with aria-pressed;
focus-visible:ring-2 ring-ring throughout; decorative glyphs aria-hidden.
- Skeletons pulse with motion-reduce:animate-none and sit behind a single
role="status" line.
Customization levers
- Row density: the three row lines (identity / versions / provenance) are
independent blocks — drop the size column or the range chip without touching
the derivations.
- Licence policy: LICENSE_TABLE and LICENSE_RANK are the only place obligation
classes live; add SPDX ids or re-rank classes there. Keep `unknown` above the
cleared classes.
- Severity vocabulary: extend the enum tuple and the label/class maps together;
the pip meter reads its length from the tuple.
- Sorting: DEPENDENCY_SORT_LABELS drives the select, so adding a sort is one
comparator plus one label.
- Provenance: swap the wrapping chain for a collapsible tree if your chains
routinely exceed a dozen hops — but keep it expandable to full length rather
than truncating.
- Data reuse: analyzeDependency(row) returns the whole derivation (level,
majorGap, rangeFit, licence risk, worst severity) for a table, a CI summary
or an export that has to agree with the UI.Concepts
- Semver precedence — versions are compared field by field with semver's prerelease rules, because lexicographic order puts
1.10.0below1.9.0and2.0.0-beta.1above2.0.0. - Graded staleness — one patch behind, one minor behind and three majors behind are different amounts of work, so they get different labels; an unparseable version or a missing
latestbecomes "unknown", never "up to date". - Range fit — the declared
^/~range decides whether the newest release is reachable bynpm updateor needs a manifest edit, and npm's prerelease rule is applied so a caret never silently swallows an alpha. - Ordered severity — the four advisory levels are ranked, and the ranking is carried by a word plus a stair-step meter so it survives greyscale, colour blindness and a printed report.
- Unknown is not safe — an undeclared licence gets its own class, ranked above the cleared ones, and a dashed "unreviewed" chip; "we did not check" and "there is nothing to worry about" are opposite claims.
- Import-path provenance — a transitive row is only actionable with the chain that pulled it in, so the contract requires that chain and the row wraps it to full length instead of truncating.
- Filter then sort — filters narrow, sorts order, and both end in a source-index tiebreak; the filtered-empty panel is worded differently from the genuinely empty tree and always offers a way back.
Citation List
The numbered reference list under an AI answer — numbering derived exactly the way Citation Chip derives it so the markers and the list can never disagree, repeated sources merged with a citation count, graded trust that never leans on colour, and sources with no usable URL that refuse to become dead links.
SLA Report
A contract-side availability settlement — real period length, overlap-merged downtime, a weighted or worst-service composite, an error budget allowed to read past 100%, and the service-credit rung the period landed on.