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.2.0

Published

React renderer for Syncropel workspaces. Mount `core.workspace.v1` 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 daemon if the catalog misses.
  3. Validates the manifest.
  4. Mounts each declared component, fetching records via the registered projection (table, feed, or metric).
  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.

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. Three ship out of the box: table, feed, metric. 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 daemon. 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 daemon 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.


Status: 0.2.0

The public API is stable and additive over 0.1.0. New in this release:

  • <RendererProvider> typed React context — the canonical mechanism for app-wide configuration. Inject your authenticated fetch, daemon 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" for hosts that want only the context layer.

Carried over from 0.1.0:

  • The manifest schema defines five component kinds; this release renders one. view is fully supported. page, thread_view, workspace, and external render a placeholder pending the runtime extension layer.
  • 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.1.0 requires no code changes. The provider is purely additive; existing code keeps working unchanged. 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 daemon, in your app. End-to-end ownership.


Related packages

| Package | Role | |---|---| | @syncropel/sdk | Record client (emit, query, search, infer) | | @syncropel/projections | SRP v0.1 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.