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

uncial

v0.0.1

Published

Uncial is a backend-agnostic Svelte block editor built on Tiptap. Library consumers define a block once as a Svelte component, register it with `defineBlock(...)`, and reuse that same block definition in both the WYSIWYG editor and the renderer.

Readme

Uncial

Uncial is a backend-agnostic Svelte block editor built on Tiptap. Library consumers define a block once as a Svelte component, register it with defineBlock(...), and reuse that same block definition in both the WYSIWYG editor and the renderer.

Custom blocks can stay atomic or declare one default child content region for nested document flow.

What ships today

  • Shared block definitions for editor and renderer
  • Atomic blocks and container blocks with nested child content
  • Registry and schema helpers for block and mark allowlists
  • Typed attribute normalization for strings, numbers, booleans, and JSON-like fields
  • Document normalization with version stamping
  • Validation hooks for editor and renderer boundaries
  • SSR-safe renderer imports separated from browser-only editor behavior

Install

bun add uncial

Peer dependency:

  • svelte@^5

Quick start

<script lang="ts">
	import {
		Editor,
		Renderer,
		createBlockAttributesController,
		createBlockRegistry,
		createSchema,
		defineBlock
	} from 'uncial';
	import PromoCard from './PromoCard.svelte';

	const promoCard = defineBlock({
		id: 'promoCard',
		label: 'Promo Card',
		attributes: {
			title: '',
			featured: false,
			priority: 0,
			metadata: { default: { theme: 'sand' }, input: 'json' }
		},
		component: PromoCard
	});

	const blocks = createBlockRegistry([promoCard]);
	const schema = createSchema(blocks);
	const attributesController = createBlockAttributesController();

	let document = {
		type: 'doc',
		content: [{ type: 'paragraph' }]
	};
</script>

<Editor {blocks} {schema} {attributesController} bind:json={document} />
<Renderer content={document} {blocks} {schema} />

Styling and customization

The root Editor and Renderer exports ship with Uncial's starter shell and default component-scoped styling.

If you want to own the editor layout yourself, use bindEditor(...) on an element you control:

<script lang="ts">
	import type { Editor as TiptapEditor } from '@tiptap/core';
	import { bindEditor, Renderer } from 'uncial';

	let document = $state({
		type: 'doc',
		content: [{ type: 'paragraph' }]
	});
	let editor = $state<TiptapEditor | null>(null);
</script>

<div
	use:bindEditor={{
		blocks,
		schema,
		json: document,
		onChange: (nextDocument) => {
			document = nextDocument;
		},
		onEditor: (nextEditor) => {
			editor = nextEditor;
		}
	}}
/>
<Renderer content={document} {blocks} {schema} />

Block definition

Use component when the same Svelte component should render in both the editor and the frontend output.

const hero = defineBlock({
	id: 'hero',
	label: 'Hero',
	attributes: {
		title: '',
		subtitle: { default: '', input: 'textarea' },
		featured: false,
		priority: 1,
		settings: { default: { align: 'left' }, input: 'json' }
	},
	component: Hero
});

Attribute specs support:

  • default: required default value
  • required: require a value at validation time
  • validate: custom validation predicate
  • parse: custom coercion from editor or serialized input
  • serialize: custom serialization for HTML persistence
  • input: one of text, textarea, number, checkbox, or json
  • placeholder: optional editor placeholder

Blocks can also opt into child content:

const collapsible = defineBlock({
	id: 'collapsible',
	label: 'Collapsible',
	attributes: {
		title: ''
	},
	component: Collapsible,
	content: { kind: 'flow' }
});

Container block components receive:

  • attribute props as usual
  • content: normalized child PMNode[]
  • children: a built-in snippet for rendering or placing the child region

Example:

<script lang="ts">
	import type { Snippet } from 'svelte';

	interface Props {
		title?: string;
		children?: Snippet;
	}

	let { title = '', children }: Props = $props();
</script>

<details>
	<summary>{title}</summary>
	<div>
		{#if children}
			{@render children()}
		{/if}
	</div>
</details>

Content model

  • Documents are ProseMirror-compatible JSON objects
  • normalizeDocument(...) stamps the current document version and coerces known block attributes
  • Unknown custom block attributes are stripped during normalization
  • Atomic custom blocks drop accidental child content during normalization
  • Container custom blocks preserve validated child content
  • Disallowed marks are removed when a schema is supplied

Validation

Use validateDocument(...) directly or pass onIssue into Editor or Renderer to observe issues during normalization and render flows.

<Editor
	{blocks}
	{schema}
	bind:json={document}
	onIssue={(issue) => console.warn(issue.code, issue.path)}
/
>

Rendering and security

  • Renderer output is driven by the same block registry used by the editor
  • Built-in rich text rendering supports headings, lists, blockquotes, code blocks, inline code, strike, bold, italic, and links
  • Links are sanitized to allow only http, https, mailto, tel, relative paths, and hash links

Development

bun run check
bun run test:unit -- --run
bun run build

Additional suites:

  • bun run test:browser -- --run for browser-backed Svelte component tests
  • bun run test:e2e for Playwright end-to-end tests

Browser-backed tests require Playwright browsers to be installed:

bunx playwright install

Releasing

For user-facing library changes, run bun run changeset and commit the generated changeset with your PR. After regular PRs merge into main, Changesets opens or updates a version PR. Merging that version PR publishes uncial to npm with trusted publishing/provenance and creates the GitHub release.

Status

Uncial is currently a production-hardening library project rather than a finished CMS platform. The API is focused on typed custom blocks with editor/render parity, including container-style blocks with nested content.