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

@syncropel/renderer

v0.5.0

Published

React renderer for Syncropel workspaces. Mount Syncropel workspace manifests in any React app — Next.js, Vite, Astro, Remix, Electron, Tauri, or your own.

Readme

@syncropel/renderer

React renderer for Syncropel workspaces. Mount core.workspace.v1 manifests in any React app — Next.js, Vite, Astro, Remix, Electron, Tauri, or your own.

Workspaces are portable: your app, your records, your renderer.

npm install @syncropel/renderer @tanstack/react-query

Quick start

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
  RendererProvider,
  WorkspaceRenderer,
  WorkspaceBootstrap,
} from "@syncropel/renderer";

const queryClient = new QueryClient;

export function App {
  return (
    <QueryClientProvider client={queryClient}>
      <RendererProvider
        fetcher={authedFetch}
        apiBase="https://api.example.com"
        catalogUrl="https://catalog.syncropel.com"
        onAction={(action, record) => console.log("action:", action, record)}
      >
        <WorkspaceBootstrap />
        <WorkspaceRenderer
          did="did:sync:syncropel-team/dashboard"
          fallback={<p>Workspace unavailable</p>}
        />
      </RendererProvider>
    </QueryClientProvider>
  );
}

<RendererProvider> is optional — every prop has a sensible default for browser hosts. Drop it entirely if you don't need authenticated requests, custom catalogs, or typed action handlers; the renderer falls back to plain fetch, window.location.origin, and a window.CustomEvent action dispatch.

That's it. The renderer:

  1. Resolves did:sync:syncropel-team/dashboard against the public catalog (https://catalog.syncropel.com).
  2. Falls back to your local Syncropel instance if the catalog misses.
  3. Validates the manifest.
  4. Mounts each declared component, fetching records via the registered projection (table, feed, metric, or srp).
  5. Renders your fallback if the manifest can't be resolved.

What this package is

A Syncropel workspace is a core.workspace.v1 record that declares an interactive UI — pages, views, projection names, action labels — over your Syncropel records. This package mounts that record as React.

Every workspace — published by Syncropel or by anyone else — is the same kind of artifact, rendered the same way. One renderer; mountable anywhere a React tree exists.

What it isn't

  • Not the data client. Use @syncropel/sdk to emit and query records.
  • Not the design system. Use @syncropel/react for tokens, atoms, and molecules. This package consumes CSS variables but ships no styling decisions of its own.
  • Not the schema authority. @syncropel/config ships the canonical JSON Schemas. This package re-uses the renderer-relevant types.
  • Not the iframe extension SDK. Use @syncropel/extensions for sandbox-isolated extensions. This package mounts in-process React components.

Boundary

This package is the portable React layer in a deliberate 3-layer cake:

Host (Next.js / Electron / Tauri / future browser-instance)
  ↓ wraps + configures via <RendererProvider>
Workspace Orchestrator (the future @syncropel/workspace)
  ↓ imports + composes
@syncropel/renderer   ← you are here

The renderer is identity-blind and orchestrator-blind (ADR-071 D8): it MUST NOT own connection state, consent enforcement, capability gating, extension lifecycle, federation policy, or thread subscription. Those are orchestrator/host concerns. The renderer's job is (manifest, providerConfig) → React.ReactNode.

The host injects 5 fields via <RendererProvider>: fetcher (authenticated), apiBase, catalogUrl, errorReporter, onAction. Not in the contract: instance, did, actor, auth_token — the renderer never sees user identity.

For the full normative boundary table, the rationale (portability thesis + per-instance origins + browser-instance horizon), and the accidental-coupling inventory in the canonical syncropel-web host, read syncropel-research/docs/thread-projection-convergence/02-renderer-host-boundary.md (ratified 2026-05-15) + ADR-071 (renderer capability contract) + ADR-072 (workspace orchestrator contract).


Concepts in 60 seconds

  • Workspace — a core.workspace.v1 record with name, publisher_did, lifecycle, and components[]. Identified by a DID of the form did:sync:<publisher>/<slug>.
  • Component — one entry in components[]. Has an id, a kind (view is fully supported in this release), and projection wiring.
  • Projection — a registered React component that takes records, options, and className, and renders UI. Four ship out of the box: table, feed, metric, and srp (renders a Syncropel Rendering Protocol document via @syncropel/react's <SRPRenderer>). Hosts can register more via ProjectionRegistry.register(...).
  • Manifest fetcher — resolves a DID by querying the catalog (POST /v1/records/query) and falling back to the local instance. Cached in an in-memory LRU (32 entries, 5-minute TTL).
  • Action — a row-level button declared in a manifest's projection_options. Clicks dispatch a typed WorkspaceActionEventDetail via window.dispatchEvent. (A future release adds a typed React context as the canonical mechanism.)

API tour

<WorkspaceRenderer>

<WorkspaceRenderer
  did="did:sync:syncropel-team/dashboard"
  fallback={<DashboardFallback />}
  catalogUrl="https://catalog.example.com"  // optional override
  hideHeader                                 // hide the workspace metadata header
  manifest={preResolvedManifest}             // skip the catalog hop entirely
  recordsByComponent={{ "live-feed": [...] }} // skip the server hop too (test mode)
/>

useWorkspaceManifest(did, options)

import { useWorkspaceManifest } from "@syncropel/renderer/hooks";

function Custom {
  const { manifest, isLoading, isError, error } = useWorkspaceManifest(
    "did:sync:alice/timetracker-pro",
  );
  // ...render manually
}

Registering a custom projection

import { ProjectionRegistry, type Projection } from "@syncropel/renderer";

const VibeGraph: Projection = {
  name: "cmv-vibe-graph",
  label: "Vibe graph",
  Component: ({ records, options, className }) => {
    /* your D3 / xyflow / canvas surface */
  },
};

ProjectionRegistry.register(VibeGraph);

A workspace manifest can now reference cmv-vibe-graph in projection_options.default and the renderer will mount it instead of one of the built-in projections.

Listening for workspace actions

The recommended path: pass an onAction callback to <RendererProvider>.

<RendererProvider
  onAction={(action, record) => {
    if (action === "edit") openEditModal(record);
    if (action === "delete") confirmDelete(record);
  }}
>
  {/* ... */}
</RendererProvider>

The legacy window.CustomEvent surface still works for back-compat:

import { WORKSPACE_ACTION_EVENT, type WorkspaceActionEventDetail } from "@syncropel/renderer";

useEffect( => {
  function onAction(ev: Event) {
    const { action, record } = (ev as CustomEvent<WorkspaceActionEventDetail>).detail;
    if (action === "edit") openEditModal(record);
    if (action === "delete") confirmDelete(record);
  }
  window.addEventListener(WORKSPACE_ACTION_EVENT, onAction);
  return  => window.removeEventListener(WORKSPACE_ACTION_EVENT, onAction);
}, []);

Both surfaces fire on every action — pick the one that fits your host. Inside a custom projection you've registered, use useWorkspaceActions to emit:

import { useWorkspaceActions } from "@syncropel/renderer";

function MyProjection({ records }) {
  const { emit } = useWorkspaceActions;
  return <button onClick={ => emit("custom-action", records[0])}>Do thing</button>;
}

Sub-path exports

// Main entry — everything
import { WorkspaceRenderer, RendererProvider, ProjectionRegistry } from "@syncropel/renderer";

// Just the projections
import { TableProjection, FeedProjection } from "@syncropel/renderer/projections";

// Just the manifest types and utilities (no React)
import { parseWorkspaceDid, validateManifest, fetchManifest } from "@syncropel/renderer/manifest";

// Just the React hooks
import { useWorkspaceManifest } from "@syncropel/renderer/hooks";

// Just the context layer (provider + hooks)
import { RendererProvider, useRendererContext, useWorkspaceActions } from "@syncropel/renderer/context";

Tree-shakers (esbuild, Rollup, Webpack 5) drop unused exports automatically.


Theming

The renderer is theme-agnostic. It uses CSS custom properties (var(--color-text-primary), var(--color-border-subtle), etc.) that the host defines. Two sane choices:

Option A — bring your own tokens. Define the variables on :root (or any ancestor of the renderer):

:root {
  --color-text-primary: #111;
  --color-text-secondary: #444;
  --color-text-muted: #888;
  --color-border-subtle: #ddd;
  --color-bg-secondary: #fafafa;
  --color-bg-hover: #f0f0f0;
  --color-act-intend: #f97316;
  --color-act-do: #38bdf8;
  --color-act-know: #34d399;
  --color-act-learn: #c084fc;
}

Option B — use the canonical Syncropel design system. Install @syncropel/react and import its tokens:

import "@syncropel/react/tokens.css"; // or styles.css for tokens + atom CSS

Either way, the renderer uses CSS variables and never bakes specific colors into its source. Customize at the variable level; no rebuild required.


SSR + prefetch (Next.js, Astro, Remix)

The renderer's useWorkspaceManifest hook is built on @tanstack/react-query, so server-side prefetching uses the standard Tanstack Query SSR pattern.

Next.js (App Router) — prefetch on the server, hydrate on the client:

// app/[...workspace]/page.tsx
import { fetchManifest } from "@syncropel/renderer/manifest";
import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query";

export default async function Page({ params }: { params: { workspace: string[] } }) {
  const manifestRef = params.workspace.join("/");
  const queryClient = new QueryClient;

  await queryClient.prefetchQuery({
    queryKey: ["workspace-manifest", manifestRef],
    queryFn:  => fetchManifest({ ref: manifestRef, apiBaseGetter:  => process.env.SPL_API_BASE! }),
  });

  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <WorkspaceRenderer manifestRef={manifestRef} />
    </HydrationBoundary>
  );
}

The renderer's hook reuses the prefetched query — no double fetch, no waterfalls. The cache TTL (5 min default) governs revalidation.

Edge case to know about: fetchManifest is async and uses the host's global fetch. In Edge runtimes (Cloudflare Workers, Vercel Edge), process.env.SPL_API_BASE may be undefined at build time but defined at request time — pass apiBaseGetter: => env.SPL_API_BASE from the request context, not from a module-level read.

For complete Next.js + Vite examples, see the examples/ directory.


Status: 0.2.1

Patch release on top of 0.2.0 — adopter-readiness polish surfaced by parallel-agent verification (sibling-conformance audit). Zero breaking changes.

  • CoreWorkspaceManifestV1 type alias — spec-canonical name for WorkspaceManifest. Both resolve to the same @syncropel/config CoreWorkspaceV1 type. Adopters reading code prefer this name; existing WorkspaceManifest retained for back-compat.
  • "sideEffects": false declared in package.json — enables tree-shaking in adopter bundlers.
  • examples/ directory with two minimal host integrations (Next.js + Vite).
  • SSR + prefetch section added to this README (above).

Carried over from 0.2.0:

  • <RendererProvider> typed React context — the canonical mechanism for app-wide configuration. Inject your authenticated fetch, server base URL, default catalog URL, error reporter, and action callback in one place. Optional — defaults work for standard browser hosts.
  • useWorkspaceActions hook — call emit(action, record) from inside a custom projection. Fires both the provider's onAction callback (when set) and the legacy window.CustomEvent (always, for back-compat).
  • /context sub-pathimport { RendererProvider, useRendererContext } from "@syncropel/renderer/context".

Carried over from 0.1.0:

  • The manifest schema defines five component kinds; this release renders one fully. view is fully supported. page, thread_view, workspace, and external render a placeholder pending the runtime extension layer (Phase 2 in the unified Renderer+Workspace arc).
  • Module-level host setters (_setHostFetch, _setApiBaseGetter, _setErrorReporter) still work as a fallback configuration mechanism for hosts that haven't adopted the provider.

Migrating from 0.2.0 requires no code changes. See CHANGELOG.md for the full roadmap.


Why this package

Workspaces should be portable in practice — mountable in your own app, not only on syncropel.com. That requires the renderer itself to be a public package. This is it.

Your workspace runs against your records, in your instance, in your app. End-to-end ownership.


Related packages

| Package | Role | |---|---| | @syncropel/sdk | Record client (emit, query, search, infer) | | @syncropel/projections | SRP v0.2 schema for declarative UI documents | | @syncropel/react | Design tokens + 21 React atoms/molecules | | @syncropel/extensions | Iframe extension SDK (SAP v0.2) | | @syncropel/config | Body-kind JSON Schemas | | @syncropel/workspace-templates | Starter workspace manifests |


License

Apache-2.0. Copyright Syncropic, Inc. See LICENSE.