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-react

v0.36.0

Published

brain-kit chat-UI React components: chat surface, tool-call timeline, file browser, voice dictation, stores, and WebSocket transport

Readme

@schlessera/brain-ui-react

The brain-kit chat UI as a React component library: chat surface with streaming transcript and tool-call timeline, file browser, voice dictation, share flows, zustand stores, and the WebSocket transport speaking the @schlessera/brain-ui-sdk protocol.

The deployment shell owns the document: index.html, the mount point, the Vite/PWA build, the service worker, and the theme entry. This package ships prebuilt JS + .d.ts plus its styles in two forms.

ConnectionGate derives its message from the authenticated HTTP reachability probe and live WebSocket facts. After three consecutive handshakes fail before opening, it distinguishes a refused live connection from an unreachable server; close code 4008 is shown specifically as the server connection limit.

React compatibility

The react and react-dom peer range remains >=18: React 18 and the current React release are both covered by the packaging smoke test. It installs the packed package with matching @types/react and @types/react-dom, imports the entry point, and typechecks the emitted .d.ts against React 18's types.

Usage

import {
  configureBrainUi,
  ConnectionGate,
  AppShell,
  ChatPage,
  GraphPage,
  ActivityPage,
  useHashRoutes,
  useUIStore,
} from "@schlessera/brain-ui-react";

configureBrainUi({ appName: "Brain UI" }); // optional; defaults shown

export function App() {
  useHashRoutes();
  const activeView = useUIStore((state) => state.activeView);

  return (
    <ConnectionGate>
      <AppShell>
        {/* Keep chat mounted so an in-flight turn survives a view switch.
            `display: contents` rather than a plain wrapper: ChatPage is a
            flex child of AppShell and its `flex-1` needs to reach the shell's
            flex container, or the chat loses its viewport-filling layout and
            bounded scrolling. */}
        <div style={{ display: activeView === "chat" ? "contents" : "none" }}>
          <ChatPage />
        </div>
        {activeView === "graph" && <GraphPage />}
        {activeView === "activity" && <ActivityPage />}
      </AppShell>
    </ConnectionGate>
  );
}

Shell hooks

useHashRoutes() owns the generic client routes: #/files/<path> opens the file panel, #/graph and #/activity select their full-screen views, activity deep links keep their suffix, and store-driven view changes use history.replaceState rather than adding browser-history entries.

useServiceWorkerUpdates({ isBusy }) registers /service-worker.js and reloads after an update takes control. It never reloads for a first install, and an update takeover that happens while isBusy is true waits for the transition back to idle. Non-empty text fields are treated as busy by default; hasUnsentText can override that DOM probe. A Vite shell can preserve its development gate with enabled: import.meta.env.PROD.

Configuration

configureBrainUi() is this package's only configuration entry, and it must run before the first render. Nothing in here reads the ambient environment — no import.meta.env, no bundler globals — so the package works the same under Vite, webpack, Next.js or a plain bundle. The shell reads whatever it likes and passes values in.

| Field | Default | What it does | | --- | --- | --- | | appName | "Brain UI" | Product name on the login screen and connection status | | assistantName | "Brain" | Name the assistant speaks as in the transcript | | shareTitle | "Shared from Brain" | Default title for shared artifacts | | composerPlaceholder | "Ask your brain anything..." | Composer placeholder | | backendUrl | "" (same-origin) | Origin of the API/WebSocket backend, for a split topology | | devTools | false | Install the window.__chatStore / window.__graphStore fixture-injection handles |

Same-origin is the default topology: the server serves the built client and the API/WS from one origin, so nothing needs configuring. Set backendUrl only when the client and backend live on different origins — a Vite shell would pass import.meta.env.VITE_BACKEND_URL, but that read belongs in the shell, not here.

Because configuration arrives at boot rather than at import, the API base is a function: use apiBase(), not a constant.

Styles

Two ways to get the CSS, pick one:

  • Tailwind v4 build (recommended): import the source theme and let your build scan the package's components for the utilities they use:

    @import "tailwindcss";
    @import "@schlessera/brain-ui-react/theme.css";
    @source "../node_modules/@schlessera/brain-ui-react/src";

    theme.css imports the kit's tokens.css itself (not the kit's theme.css, which carries the kit's own @theme scales and would redefine p-2), so the --bk-* values the kit components inside this package read arrive with it. Importing @schlessera/brain-ui-kit/tokens.css a second time is harmless.

  • No Tailwind: import the precompiled stylesheet, which already includes the kit's tokens:

    import "@schlessera/brain-ui-react/styles.css";

Theme

The app is dark by default. Settings carries a three-way toggle (system / paper / dark), stored per root under brain-theme, and AppShell writes it to <html data-theme>; the kit's tokens switch on that attribute. Because the attribute is applied in an effect, a host that wants a stored non-default preference to paint correctly on the very first frame adds one inline line to its HTML before the stylesheet:

<script>document.documentElement.dataset.theme = localStorage.getItem("brain-theme") || "dark";</script>

The app types no colour of its own. Every utility colour theme.css declares (bg-surface, text-foreground, border-border, …) is one of the kit's --bk-* tokens, declared @theme inline, so the same attribute switches the shell and the kit components inside it. Accents come in two names, as the kit names them: primary, accent and destructive are the inks — for text, borders and rings, dark on paper — and primary-fill, accent-fill and destructive-fill are the fills, the same colour in both themes, for backgrounds solid or with an alpha, with primary-foreground as the ink that sits on a fill, and primary-mark / accent-mark / destructive-mark for a status dot, which the kit darkens on paper because a fill that small falls under 3:1. A host that styles its own elements with these utilities follows the same split; bg-primary is the ink and reads as brown on paper.

Multi-session state

useChatStore keeps a transcript buffer per session plus a draft buffer for a not-yet-named conversation; frames from background sessions accumulate in their own buffers while another session is in view. activeChat(state) selects the buffer in view; anyStreaming(state) is the "something is running" signal (used e.g. to defer service-worker update reloads).

UI roots

createBrainUiRoot({ config, storagePrefix, storage, request, api }) constructs independent stores, API access, renderer/ASR registries and a connection without opening a socket. Pass it to <BrainUiProvider root={root}>. Store selector hooks resolve the nearest provider; imperative code uses root.stores and root.connection. useBrainApi() and useBrainConfig() expose its services.

A provider without a root owns a new root and disposes it on unmount. An explicit root belongs to the caller, which must call root.dispose() when finished. useWebSocket() acquires a connection lease: multiple consumers of the same root share one socket until the last consumer unmounts.

Use a stable, distinct storagePrefix to restore an embedder's session, provider choice and frontmatter preference. Omission generates an ephemeral namespace; storage: null disables persistence. Outside a provider, hooks use the default application root and its existing storage keys. Hook statics such as useChatStore.getState() always address that default root; internal code must use explicit roots, enforced by check-root-stores.ts.

Component requests, media URLs and branding follow the provider's root. Browser resources remain shared: cookies, native credential/share UI, the share-target stash and service-worker push subscriptions. Native media loading uses backend URLs and browser credentials rather than the injected transport. Separate roots do not establish a browser security boundary. One root serves all agent backends on the same brain server; separate roots are only needed for distinct application instances.

Registries

Tool renderers and ASR clients register synchronously on first render into the current root's registries. Imports are inert. Repeated registration is safe; resetting one root's registry leaves other roots untouched. This is build-time composition, with no runtime plugin loading.

The chat UI's own tools render from their SDK contracts through bind(contract, Component): get_current_location as a map card, request_image_mask as a receipt, and show_block as one of the kit's answer blocks (comparison table, stat tiles, trend chart, data table, bar list, receipt, step list, timeline, schedule, quote card, contact card). A block is part of the answer, so the transcript draws it inline where the model called it rather than inside the tool timeline; a call whose result does not parse stays in the timeline with the result's own words.

The same BlockCard draws the blocks a host classifies out of the model's markdown (message_blocks, rev 4): MarkdownContent cuts a text part at each block's span and renders the kit block between the markdown pieces. A message without blocks renders exactly as before, and a span that does not fit the text is ignored.

Versioning

Versions in lockstep with all @schlessera/brain-* packages.

Quick capture recovery

The Add panel shows the saved path and whether indexing completed. If indexing failed, it keeps the saved confirmation visible and offers Retry indexing. That action updates the index without creating or appending the note again. Older servers that omit the indexing outcome are shown as unconfirmed.