npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@schlessera/brain-ui-kit

v0.43.1

Published

brain-kit design kit: presentational React components, design tokens, and the Storybook that documents them

Readme

@schlessera/brain-ui-kit

The brain-kit design kit: presentational React components, the design tokens they are built from, and the Storybook that documents them.

Everything here is prop-driven. Props in, callbacks out — no stores, no fetch, no ambient configuration, no browser globals. That is not a style preference: kit components can render in a story, a test or another embedder without depending on application state. @schlessera/brain-ui-react owns that state in isolated roots created by createBrainUiRoot and selected through BrainUiProvider; hooks outside a provider use the default application root (UI roots). Kit components need neither a root nor a provider, and scripts/check-kit-purity.ts enforces that separation.

Status

The kit covers primitives, rows, evidence blocks, question and decision cards, conversation states, agent views, screen chrome and desktop navigation. Assembled Storybook screens exercise Morning Digest, Chat Answer, Weekly Review, Run Detail, Actions Triage, File Viewer and First Run. Browser interaction/accessibility checks and curated visual baselines run in CI.

@schlessera/brain-ui-react consumes the kit: its chat, files, settings, graph and desktop surfaces are assembled from these components. Why the kit is shaped the way it is — the component API, the tokens, the light theme, the accessibility gate — is in docs/decisions/design-kit.md, and the measured design divergences several components ship on purpose are in docs/decisions/design-feedback.md.

Overlays

Overlay adds one chrome primitive with sheet, dialog, fullscreen and panel variants. Modal variants use native dialogs and the top layer; destination panels keep marked navigation live. The caller owns open and receives close reasons. surfaceRef lets app adapters reset destination scroll and heading focus. Initial focus prefers body controls before the header close button. Focus returns after the React commit; onAfterClose runs only when the caller sets open false. Acts that focus background content belong in that callback. CommandPalette no longer carries dialog semantics; wrap it in Overlay. Load the mandatory kit stylesheet for geometry, scrims, scroll lock and motion. z and LAYERS name the six document layers.

Loading

Loading is blurred ghost text in the replaced content's type role, with one spectrum band per frame and a 600ms cross-fade when that item's data arrives. Glyphs stay static; only the band's transforms move. The band spans the text block; broad edge fades scale with the frame, so the text lights up gradually across its width. Frames, icons and status-dot slots keep their final geometry. Loading states are not ambient motion: they end when the data does. Reduced motion shows plain blurred base text and makes the handoff instant; print hides ghosts.

Queue and search index props remain accepted, but no longer stagger the sweep. The owning frame shares one sweep across all of its text slots. See D53 in docs/decisions/design-kit.md and the States/GhostSweep Storybook review gallery.

Search results

SearchResultCard accepts segments for all matched and unmatched snippet parts, overriding before / highlight / after. Optional title, type and controlled active preserve result identity and keyboard selection in a search list. Pass score="" when retrieval supplies no score; it is a retrieval metric rather than a calibrated probability. Opening remains the caller's onClick, and long snippet tokens wrap within the card.

Grouped questions

AskUserGroupCard presents two to four questions as one exchange, with one header and action row. The caller supplies each section's options, current answer, controlled Other text and callbacks. The kit marks unanswered sections and moves focus when Go to unanswered is activated; Submit returns all answers together. Answered and dismissed exchanges keep one row per question. Use AskUserCard for a single question, including its composer-typed state.

Ranked questions

AskUserRankCard orders two to fifteen stable item ids in one exchange. The caller supplies the question, labels and optional details, links and cutoff; Submit returns every id in order and whether the original order was kept. Users can drag a handle, tap an item then its destination, or use the keyboard. Handle gestures capture the pointer while the rest of the row scrolls the transcript. Answered cards retain the order; dismissed cards can be asked again.

Conditional questions

AskUserFormCard presents single, multi, scale, rank and short text nodes in one exchange. The caller supplies question and a flat nodes list; showIf: { node, anyOf } reveals a child when an earlier choice matches. Children occupy branch slots below the parent's whole control, without accumulating indentation. Breadcrumbs identify the path and shorten beyond three levels. The form has one sticky header and one outer action row.

Changing a choice sets hidden branch answers aside locally, with an Undo receipt; revisiting the branch restores them. onSubmit receives { answers, visibleNodes } and omits hidden answers. Nodes default to required; an incomplete submission flags missing required answers and focuses the first flagged node. Revealing a branch announces the change and keeps focus on the current control.

Use state="answered" with answers to replay a compact summary of the visible paths, skipped optional questions, scale groups and ranked items. Longer records can expand. state="dismissed" accepts lapsedNote and onAskAgain, as the standalone cards do.

ScaleList and RankList export the same controls used by the standalone AskUserListCard and AskUserRankCard. Their embedded prop defaults to true, omitting another header and action row. Both expose onChange; ScaleList also exposes flagged and onComplete, while RankList exposes onMoveChange so an outer form can guard submission during a move.

Attachments

AttachmentRow requires kind and label. Pass duration, metadata, extracts and provenance explicitly; absent fields draw nothing. meta accepts one string or several lines, and names and metadata wrap. An audio waveform needs an explicit finite seconds value. Sample values belong in stories.

For a static sent-file record, pass actionIcon="" and omit onClick. The row then has no role or tab stop. The chat consumer uses this form for validated tracks; its image thumbnails and zoom controls remain separate.

Streaming status

StreamingAnswer draws only supplied phase, target, elapsed, answer and cost facts; prototype samples belong in stories. pulse={false} makes its status dot static. Only the phase word is announced; targets wrap and elapsed ticks stay outside the live region. For a waiting status without another answer or Stop, omit text and set stoppable={false}. bars={false} omits ghosts.

Imported tracks

TrackMap draws file-provided lines with start/end shapes, a scale and a full summary and waypoint list. Supply one path per usable section, the complete track envelope through fitPoints, canonical metrics and evidence, and an original file name/path. Optional background paths/land carry their attribution. Without background geometry it labels the drawing Track only. A projectionReason replaces the drawing while retaining all text and the original. The component performs no parsing, measurements or requests. ui-react resolves staged originals and optional geography before both chat display and static export.

Link policy

@schlessera/brain-ui-kit/links exports classifyLink, the one decision about what an address a model chose may do: absolute http(s) only, no credentials, no invisible or bidi characters, and no hostname label that mixes scripts (UTS #39 Highly Restrictive). It is pure and imports no React, so a server can use it: @schlessera/brain-ui-sdk's show_block handler rejects a refused link with it. LinkPreviewCard calls it on its own url prop, so the host a card shows and the address it opens always come from the same parse. The reasoning is D48 in docs/decisions/design-kit.md.

Logo

The brain-kit logo ships here once, and everything that shows it takes it from this package rather than drawing its own (the accepted design is #1423).

BrandMark renders it inline: variant="mark" (the default) or "lockup" with the outlined brain-kit wordmark, at size px tall. Both halves paint with the theme's tokens, so light and dark need no prop, and below 24px the mark switches to the small-size master. It is decorative unless given a label, which makes it an image with that name. Nothing about the artwork is configurable.

The master SVGs ship as files, for anything that cannot render React (a favicon link, a README, a static site):

import { BRAND_ASSET_SPECIFIER, BRAND_MASTERS } from "@schlessera/brain-ui-kit/brand";
// "@schlessera/brain-ui-kit/brand/lockup-on-paper.svg", resolvable by a bundler
const lockup = `${BRAND_ASSET_SPECIFIER}lockup-on-paper.svg`;

-on-dark and -on-paper name the flat ground each file's colours are for.

BRAND_RASTERS are the raster files. bun run brand:generate (tools/brand/generate.ts) renders favicon.ico (16, 32 and 48px), apple-touch-icon.png (180px, opaque), icon-192.png, icon-512.png and icon-maskable-512.png (mark inside the central 80% circle) from those masters. The 1200x630 social-card.png and its 2400x1260 [email protected] are reviewed raster masters of their own (#1428), which the generator never touches; a GitHub social preview takes the 1x file. A host copies the brand files it serves and declares them from the same entry, rather than typing the values:

import { HTML_ICON_LINKS, WEB_APP_COLORS, WEB_APP_MANIFEST_ICONS } from "@schlessera/brain-ui-kit/brand";
const manifest = { name: "brain", icons: WEB_APP_MANIFEST_ICONS, ...WEB_APP_COLORS };

src and href are bare file names, relative to wherever the host serves the brand files.

Styles

Two forms, both generated from src/styles.css:

// A consumer with its own Tailwind v4 build:
//   @import "@schlessera/brain-ui-kit/theme.css";
//   @source "../node_modules/@schlessera/brain-ui-kit/src";

// A consumer without one:
import "@schlessera/brain-ui-kit/styles.css";

src/tokens.css is the token set, reproduced from the design catalog's Foundations section; src/theme.css imports it and adds the kit's own Tailwind scales as @theme static. A consumer with its OWN Tailwind theme imports @schlessera/brain-ui-kit/tokens.css instead of theme.css, or the kit's --spacing-2: 2px silently redefines that consumer's p-2 (this is what ui-react does). Two things in it are easy to get wrong and are asserted by tests/theme-tokens.test.ts: the spacing scale is deliberately irregular (it is not a 4px grid), and three radius steps are ranges whose ends both ship.

tokens.css also delivers the shared scale, rank and conditional-form layout and interaction rules. All three stylesheet entries include these rules; components need no separate form stylesheet.

Themes

Every colour token is one declaration, --bk-x: light-dark(<paper>, <dark>), and color-scheme picks the half. The stylesheet sets color-scheme: dark on :root, so a consumer who does nothing gets the dark kit. To switch, put data-theme on <html> — or on any element, for one subtree:

<html data-theme="system">  <!-- follows prefers-color-scheme, no script -->
<html data-theme="light">   <!-- the paper theme -->
<html data-theme="dark">

No theme context, no provider, no component knows which theme it is in. A persisted user choice should be written to the attribute by an inline script before first paint. light-dark() is Baseline since May 2024; in an older browser the tokens are invalid at computed-value time and the page renders without colour — the same loud failure as a missing stylesheet, on purpose.

The light values come from the design's paper contract plus one derivation rule per token family, generated by tools/theme/derive-light.ts and pinned by tests/light-theme.test.ts; edit the generator, not the values. Every story runs on paper under the same a11y gate as on dark.

Storybook

Run from this directory:

bun run storybook          # dev server on :6006, MCP endpoint at /mcp
bun run build-storybook    # static build
bun run test-storybook:ci  # every story's render + play function, in Chromium, dark
bunx vitest run --project=storybook-light   # the same, on paper

The story tests run under Vitest browser mode against real Chromium, which is a separate runner from the repo's bun test. Both are expected to pass; they do not interfere.

Stories are CSF Next (preview.meta / meta.story), the format that becomes Storybook's default in 11. They import the preview through the #* subpath map in package.json, so no story carries a ../../.storybook/preview path.

Accessibility

parameters.a11y.test is 'error': axe violations fail the browser suite in CI. The gate was verified with a seeded violation. Known palette contrast gaps have explicit, story-scoped exceptions recorded in docs/decisions/design-feedback.md; a green suite does not mean those design issues are resolved. Keyboard groups use roving tab stops, and reduced-motion behavior is checked in the browser.

The following interaction rules are also covered by stories.

Interaction states are gated on a handler. A component that was given no onClick gets no role, no tab stop, no focus ring and no hover — so a static row never pretends to be pressable. That is an API contract, not styling.

Button reads its default rest paint and per-tone hover palette through the kit stylesheet. Its style prop is still merged last onto the root: caller background, border and colour overrides remain effective during hover as well as at rest. Other paint channels retain their tone's hover treatment. A caller using border: "none" keeps a border-free target, including the rank footer's small controls and separate inset hover hairline.

Roving groups take the arrows, Home and End. FilterRow, TabBar, SideRail, CommandPalette, a ChoiceOption radiogroup and a stack of FileRows are one tab stop each; the arrows move inside, Home / End reach the edges. A SideRail given acts adds a second group, a vertical Acts toolbar with its own stop, and onOpenPalette adds the All commands button as a third; the arrows never cross from one group into another. Single-letter shortcuts (a / d / s, j / k) are the app's and are focus-scoped there; the kit only prints them on the controls.

Hit targets are not visual size. Small controls stay small and extend their target with a transparent pseudo-element: Toggle (38x22 drawn), FeedbackRow (30x26) and InlineToast's undo (text-sized). The constraint that makes it safe is that expansion per side must be no more than half the distance to the nearest interactive neighbour on that axis — get it wrong and a neighbour's invisible target steals the click, which is how FeedbackRow once recorded thumbs-down for a thumbs-up. Each one is asserted with elementFromPoint at the target's EDGES in its own story. Note the measurement: inset on an absolutely positioned pseudo-element resolves against the containing block's PADDING box, so a border reduces the reach. Historically, borders left FeedbackRow at 46x42 and the undo at 43.65px tall, both below the 44px floor. D34 corrected this: FeedbackRow uses an inset box-shadow for its hairline and reaches 46x44; the undo uses text-decoration for its underline and extends 16px above and below the text, clearing 44px. Their HitTargets and UndoHitTarget stories assert the reach and zero border widths, preserving the correction alongside the history in docs/decisions/design-feedback.md.

SideRail destinations, acts and the All commands button keep their 36px mouse density. When any available pointer is coarse, their rectangular targets are at least 44×44, including the corners of the rounded paint. The collapsed 60px rail accounts for its 1px border in its insets. The rail's middle (the destinations through the acts) scrolls within short rails while the wordmark and All commands stay pinned; targets retain the 3px separation. Dedicated Chromium cases cover fine-only, coarse-only and mixed pointers in both themes.

DiscButton paints a 32px disc inside a 44px button. The default end anchor reaches 12px left and 6px up and down, and not right, so the box never covers a scrollbar; center reaches 6px on every side. A labelled disc opens leftward into a pill on hover and keyboard focus, and its DiscRow grows with it, so a neighbouring disc is pushed rather than covered. Interactive SuggestionChips are at least 44px tall when any available pointer is coarse. A disabled chip prints its why and keeps its cost. Dedicated Chromium cases cover fine-only, coarse-only and mixed pointers in both themes.

TabBar uses the design's other sanctioned method — padding cancelled by an equal negative margin — and that one is exact: padding is not measured against anything, so a border costs it nothing. The neighbour constraint still applies and here it has a number. With justify-content: space-around the clear gap between two slots is the bar's free space divided by the slot count, so for the five default slots it reaches the required 28px at a bar width of 276px. Below that a five-slot tab bar steals its own clicks, and NarrowBarStealsTheClick reproduces it at 240px.

Container-requiring roles get their container. A tab needs a tablist, an option needs a listbox, a radio needs a radiogroup. Where the container element belongs to the component it is rendered — FilterRow, TabBar and SideRail render their own tablist, AskUserCard its radiogroup, CommandPalette its listbox and one group per result kind. Where it belongs to the caller (FileRow's tree) the stories show the wrapper it is owed.

A control that writes says so in its accessible name. CommandPalette puts the effect chip into the row's aria-label — "Re-index knowledge/, reindex" — so the warning is not shown only to people who can see it.

Live feedback uses polite announcements. StreamingAnswer's phase line and InlineToast carry aria-live="polite", because both change without the user doing anything to make them change and are otherwise silent to a screen reader. polite rather than assertive in both cases: neither should cut across whatever is being read.

Pass announce={false} to InlineToast when a containing live region combines the receipt with other feedback. AskUserFormCard uses this for one announcement of branch reveals, hidden nodes, answers set aside and the remaining count.

Dictating composer state

Composer accepts state="dictating" to preserve a read-only draft and make Send and Attach unavailable while capture is active. Its mic is labelled “Stop dictation” and still calls onMic; the consumer owns capture, draining and review. Other composer states keep their existing behavior.

CodeBlock renders only supplied content: pass code or pre-rendered children, lang when a language tag is wanted, and an action for a real head control. It has no sample command, default language or decorative copy control. Clipboard access and highlighting belong to the host. wrap={false} retains native keyboard-accessible horizontal scrolling.

LaneChart hosts supply the shared percentage time axis and explicit lanes, ticks and legend. HATCH_GLYPH is exported from the package root for the waiting-on-you legend; use it only for a documented human-wait interval. Lane names and legends wrap for narrow containers.

AgentOrbit groups recorded agents by state: needs you, running and ended. Pass agents with stable id, functional name and required state; an empty or missing array draws nothing. Progress orbit and authored angle props are removed in the approved pre-1.0 minor migration. RunState adds neutral, non-pulsing stopped for denied/cancelled work. Pass coreMeta explicitly. The kit measures wrapping pills, limits each ring to collision-free targets and represents the rest as +N; the host's full list remains the record. Pass compact below a 480px container width, onOpen(id) for labelled pills and onOverflow to reach the list. The count legend defaults on; no radius represents completion, and no ring moves without a real state change.

Local recording rows

RecordingRow shows a recording's time, duration and explicit state word. Interrupted rows name the committed boundary and say the end may be missing. Its optional playback/discard callbacks and children let a root-scoped view provide local actions and transcript editing. The row does not transcribe or send anything; dark and light stories cover every recording state.

For real pending tool permissions, ApprovalCard accepts children for the existing renderer's full input/diff, wrapHeader for long tool names and targets, and onAlwaysAllow only when a remembered grant is eligible. Pass empty diff/risk when no such data exists to clear demo defaults. The optional Always allow control names its write_policy effect.

Hygiene review presentation

HygieneCard composes ActionCard, PathRef, EffectPreview, Callout and DispositionBar for finding evidence, repair controls and recovery states. The caller supplies the server projection and callbacks. HygieneEnd presents review counts and deferred findings; HygieneBlocker presents configuration recovery. Their Odysseus stories and visual baselines cover dark and paper at 320px and 1280px. The kit performs no selection, validation or effect dispatch.