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

@granular-software/react

v0.1.0

Published

Customer-facing React components and hooks for Granular browser sessions and frontend actions

Downloads

150

Readme

@granular-software/react

React bindings for delegated Granular browser sessions, frontend actions, and embedded agent UI.

This package is designed for customer-facing product surfaces:

  • your backend authenticates the current end user
  • one small server route exchanges that stable user ID for a short-lived Granular token
  • GranularProvider refreshes that token through the route when needed
  • the browser opens a subject-scoped Granular session
  • frontend actions are published from that browser session only

GranularProvider accepts either a trusted exact environmentId or an environmentName (defaulting to prod). For a name-only exchange, the gateway first verifies the server's scoped Embedded UI key, reconciles the subject, checks its sandbox assignment, and resolves the named environment that your backend or the Granular console already provisioned. The browser token is then bound to the resulting exact environment ID; an environment name is never used as an authorization boundary.

Install

bun add @granular-software/react @granular-software/sdk

Core Split

Customer backend

  • owns user authentication
  • owns the /api/granular/session route
  • owns manifest generation, ontology setup, and backend action execution
  • may also own the agentEndpoint

Customer frontend

  • mounts GranularProvider or GranularWidget
  • publishes frontend actions for the current browser surface
  • renders the canonical feed and its prompt/artifact/file/resource controls
  • may inspect tasks, timeline, and jobs through diagnostic hooks, but never merges those collections into customer-visible chronology

Customer-facing copy

The shared components own connection, recovery, and generic failure copy. Your app owns the product vocabulary and deliberate business explanations, such as a policy result or a field that needs changing. Do not render raw backend errors in product UI. For configuration failures, pass the stable platform code to GranularAgentDockErrorState and let the Dock render its own error state.

Provide clear business labels and descriptions for your actions, prompts, fields, results, and feedback. The Dock deliberately falls back to neutral language instead of guessing what an internal identifier or effect name means.

See Customer-Facing Copy And Ownership for the complete contract.

Main Exports

Provider and hooks

  • GranularProvider
  • useGranularSession
  • useGranularAgent
  • useGranularSubject
  • useGranularEnvironment
  • usePublishFrontendActions
  • useGranularDocument
  • useGranularFeed
  • useGranularHeap
  • useGranularTimeline
  • useGranularTasks
  • useGranularPrompts
  • useGranularJobs
  • useGranularSessionTimeline
  • useGranularSessionJobs
  • useGranularSessionJob
  • useGranularSessionHeapEntries
  • useGranularSessionHeapEntry
  • useGranularSessionHeapLists
  • useGranularSessionHeapList
  • useGranularSessionTranscript
  • useGranularHarness
  • useGranularEffects
  • useGranularHarnessState
  • useGranularRecordImportSummary
  • useGranularRecordImports
  • useGranularRecordImport
  • useGranularSessionStats
  • useGranularJob

UI components

  • GranularWidget
  • GranularAgentPanel
  • GranularMessenger
  • GranularAgentDock
  • GranularActionBar

Minimal Setup

import {
  GranularMessenger,
  GranularProvider,
  usePublishFrontendActions,
} from "@granular-software/react";
import type { ToolWithHandler } from "@granular-software/sdk";

const openRefundDrawer: ToolWithHandler = {
  name: "open_refund_drawer",
  description:
    "Open the in-product refund review drawer for the selected order.",
  parameters: {
    type: "object",
    properties: {
      orderId: { type: "string" },
    },
    required: ["orderId"],
  },
  async handler(args) {
    // Customer-authored UI logic goes here.
    console.log("Open refund drawer for", args.orderId);
    return { ok: true };
  },
};

function FrontendActions() {
  usePublishFrontendActions([openRefundDrawer]);
  return null;
}

export function SupportDesk() {
  return (
    <GranularProvider
      apiUrl={process.env.NEXT_PUBLIC_GRANULAR_API_URL!}
      ontologyId="support-ops"
      agentEndpoint="/granular/agent"
      sessionKey="current-authenticated-user"
    >
      <FrontendActions />
      <GranularMessenger />
    </GranularProvider>
  );
}

Every custom agentEndpoint receives the stable operationId, userMessageId, and user-message metadata after the Provider durably appends that user occurrence. It also receives ordered conversation history projected from the complete canonical session feed; browser-local optimistic state is never used as history. The backend must durably author or reuse every non-empty assistant reply in the same Granular session and return:

const published = await environmentSession.publishAssistantReply({
  id: assistantMessageId,
  operationId: assistantOperationId,
  text: reply,
});

return Response.json({
  reply,
  meta: {
    durableMessagePersisted: true,
    durableMessageId: published.feedItemId,
    durableMessageTimestamp: published.timestamp,
  },
});

An unmarked reply fails closed so browser code cannot forge assistant history. publishAssistantReply() requires a server API-key EnvironmentSession; delegated browsers cannot call it. A custom endpoint returns one JSON result; it has no assistant or progress stream. Chronological components must be published to the canonical session feed and become visible through the feed subscription. Product result fields such as patch, recommendation, artifacts, target, and meta remain available to onAgentResult.

Omit agentEndpoint to use the Provider's atomic Harness path.

Frontend actions still need two things:

  • declaration in the ontology manifest
  • live publication from the browser surface

You can publish them either:

  • statically with the frontendActions prop on GranularProvider
  • dynamically from a component lifecycle with usePublishFrontendActions(...)

Reference

The full API reference lives in REFERENCE.md.

It covers:

  • delegated subject authentication
  • session creation and session listing
  • environment APIs such as GraphQL, record ingestion, and record-import tracking
  • live CRDT hooks for heap, tasks, prompts, timeline, jobs, and harness state
  • job feedback and execution-state subscriptions
  • built-in UI components

Agent Dock

GranularAgentDock renders the compact bottom command surface used for workspace chat, object-scoped follow-ups, active-session switching, and operator attention prompts.

For the shortest Next.js integration path, see NEXTJS.md.

<GranularAgentDock />

The dock ships with the canonical visual contract by default: a responsive single-row launcher, conversation depth, focused artifact workbench, session history, factual activity, inline attention prompts, unread state, object-aware context receipts, and wrapping starter suggestions. Add props only for product context:

// Shell/layout:
<GranularAgentDock
  agentContext={() => ({ selectedOrder })}
  objectDisplay={{
    order: {
      label: "Order",
      href: (object) => `/orders/${object.id}`,
      subtitle: (object) => object.description,
    },
  }}
/>;

// Object page:
useGranularTarget("order", selectedOrder.id, selectedOrder.orderNumber);

If the shell already owns the current object, pageTarget is also available:

<GranularAgentDock pageTarget={currentPageObject} />

The conversation primitives are not injectable. GranularAgentDock always owns and renders its canonical shadcn Base/Nova Bubble, Message, Marker, MessageScroller, and Attachment components. Product integrations provide context and behavior; they cannot replace the dock's message or artifact UI.

theme and root-level layout style remain available for unusual embedding constraints, but neither changes the component implementation.

Deterministic action autonomy

Prepared session artifacts can declare impact metadata in policy (or the legacy metadata.autonomy field). The dock applies a fixed policy:

  • declared read-only and UI-only work can run directly
  • single internal mutations require a visible review
  • bulk, external, financial, permission, destructive, and high-risk work requires impact review followed by explicit confirmation
  • missing impact metadata uses the conservative review path; the dock never asks a model to guess the risk
const artifact = {
  // ...SessionArtifactRecord fields
  policy: {
    sideEffect: "write",
    scope: "financial",
    risk: "high",
    targetCount: 4,
    reversible: false,
  },
};

Conversation activity renders from canonical feedback occurrences. The Dock does not derive a second timeline from response actions or generated reasoning prose. Drafts, attachments, prompt inputs, selected artifacts, and scroll position are restored per session when the user switches conversations.

Delegated Auth Setup

Customer backend

  • creates an Embedded UI key and stores it as GRANULAR_EMBEDDED_UI_API_KEY
  • exposes POST /api/granular/session
  • must derive the current end user from the customer’s own trusted session
  • passes only that stable user ID to Granular from the server
  • must keep the Embedded UI key on the server only
  • should shut down or rotate the embedded Granular session when the host user logs out or switches account

Use the server helper so request validation, safe errors, and no-store headers stay consistent:

import { createGranularSessionRoute } from "@granular-software/sdk/delegated-auth";

export const POST = createGranularSessionRoute({
  getSubject: async (request) => (await readYourSession(request))?.userId,
});

Typical backend config:

  • GRANULAR_EMBEDDED_UI_API_KEY
  • GRANULAR_API_KEY only when the backend also performs trusted setup, imports, or administration
  • your own existing customer session-cookie / auth middleware config

Granular platform

  • validates that the key has only the browser-session creation permission
  • resolves the tenant from the key and the subject from the stable user ID
  • resolves the user's current assignment and permission profile
  • returns a token scoped to one exact environment for at most five minutes

Customer frontend

  • mounts GranularProvider
  • passes ontologyId; /api/granular/session and the hosted gateway are defaults
  • may pass agentEndpoint
  • may pass sessionKey when host-user switches should force a reconnect
  • may publish frontend actions with either frontendActions or usePublishFrontendActions
  • does not need a cookie private key or signing secret in the browser

Typical frontend env/config:

  • NEXT_PUBLIC_GRANULAR_API_URL
  • NEXT_PUBLIC_GRANULAR_ONTOLOGY_ID
  • optional NEXT_PUBLIC_GRANULAR_AGENT_ENDPOINT

Notes:

  • clientId is optional; the package generates one when omitted
  • there is no library-mandated cookie name
  • there is no frontend-managed API key, refresh token, signing key, or cookie secret
  • there is no required cookie key name or cookie encryption secret specific to Granular

Recommended Hook Shape

For most apps, the cleanest mental model is:

  • useGranularSubject(): delegated identity plus session catalog/creation
  • useGranularEnvironment(): environment APIs, record ingestion, and frontend-action publication
  • useGranularSession(): full low-level provider state and direct access to the active session object
  • useGranularJob(job): one live execution with logs, tool calls, prompts, and feedback

The lower-level slice hooks such as useGranularHeap(), useGranularTimeline(), and useGranularTasks() still exist because the live document is CRDT-backed and they are the best React shape for focused UI surfaces.

For persisted session history and saved artifacts, use the durable hooks:

  • useGranularFeed()
  • useGranularSessionTranscript()
  • useGranularSessionTimeline()
  • useGranularSessionJobs()
  • useGranularSessionHeapEntries() / useGranularSessionHeapEntry()
  • useGranularSessionHeapLists() / useGranularSessionHeapList()

useGranularFeed() is the sole customer-visible chronology. Timeline and job hooks are diagnostics and must not be merged into the display feed.

Important Runtime Model

Manifest generation, ontology builds, and backend actions stay in your backend stack.

The frontend package is responsible for:

  • delegated authentication in the browser
  • opening the live browser session
  • publishing frontend actions for that session
  • rendering live session state with React
  • answering prompts and showing agent progress in-product