Overview
@alggtta/editor is a commercial, core-first rich-text editor built on Tiptap 3 / ProseMirror. It ships as a single npm package with multiple distribution surfaces — a React component, a framework-agnostic mount() function, a Web Component, and a token-gated CDN/UMD build — and is licensed commercially (UNLICENSED), free to develop on localhost and token-verified in production.
@alggtta/editor is a commercial rich-text editor for product teams who want a Notion-grade editing surface without rebuilding ProseMirror from scratch. It is built on Tiptap 3.26 and ProseMirror, written in strict TypeScript, and styled with Tailwind v4 + DaisyUI. The design is core-first: the default bundle stays lean, and heavier capabilities (database tables, Mermaid, AI, export, collaboration) live behind explicit subpath imports so you only pay for what you use.
Who it is for
- React (18 or 19) product teams who want a drop-in editor with a friendly props API.
- Non-React stacks (Rails, Django, Laravel, plain HTML) that need to mount the editor into an existing page via mount() or a single <alggtta-editor> tag.
- Teams building a wiki or knowledge base — pair it with the private companion package @alggtta/wiki-store (provided to licensees on request) for backlinks, full-text search, and versions.
Dual distribution
One package, four ways to embed. The npm import is the primary path for bundled apps; the token-gated CDN/UMD build is for pages without a build step. All four share the same component and license flow.
| Surface | Entry point | Use case |
|---|---|---|
| React component | import { AlggttaEditor } from '@alggtta/editor' | React 18/19 apps (recommended — keeps license + feature gating active). |
| Framework-agnostic mount | import { mount } from '@alggtta/editor' | Any page: mount(elOrSelector, props) returns an imperative MountHandle. SSR-safe. |
| Web Component | defineAlggttaEditorElement() → <alggtta-editor> | Register a custom element and drop in a single tag with attributes. |
| UMD / CDN script | https://cdn.alggtta.com/editor.umd.js?token=… | No bundler — <script> + <link> on any HTML page (load react/react-dom UMD first; token-gated). |
The CDN / browser-script path is a first-class, supported surface as of v6.2.0: mount() and the <alggtta-editor> Web Component exist and the UMD/CDN build ships. Treat the npm import as the primary path and the CDN build as the no-build-step alternative.
Quick taste
import { AlggttaEditor } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
export function MyEditor() {
return (
<AlggttaEditor
token={process.env.ALGGTTA_TOKEN!} // required; localhost is free
preset="core" // 'core' (default) | 'full'
variant="document" // 'document' | 'embedded' | 'bare'
placeholder="Start writing…"
onUpdate={({ json, html, text }) => {
// persist json / html / text however you like
}}
/>
)
}<!-- No bundler needed. The deployed CDN bundle keeps React external,
so load the react / react-dom UMD globals first, then the editor. -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="https://cdn.alggtta.com/editor.css?token=YOUR_TOKEN" />
<script src="https://cdn.alggtta.com/editor.umd.js?token=YOUR_TOKEN"></script>
<alggtta-editor token="YOUR_TOKEN" preset="full" theme="light"></alggtta-editor>The <alggtta-editor> element observes the attributes token, theme, variant, preset, placeholder, locale, and content, and emits a 'change' CustomEvent (with detail.json) on edits. It mounts into a light-DOM child so global theme variables and typography apply — there is no Shadow DOM isolation yet.
Tech stack
| Layer | Technology | Notes |
|---|---|---|
| Runtime | React 18.2+ / 19 | react + react-dom are peer dependencies (^18.2.0 || ^19.0.0). |
| Language | TypeScript (strict) | Full type definitions ship with the package. |
| Editor engine | Tiptap 3.26 / ProseMirror | @tiptap/core, @tiptap/pm, @tiptap/react ^3.26.0 are required peers. |
| Styling | Tailwind v4 + DaisyUI 5 | Theme via the theme prop (a DaisyUI theme name). |
| Collaboration | Yjs | Realtime binding via the collaboration prop + /collab subpath. |
| Math | KaTeX | Optional peer (katex ^0.16.28). |
| Code highlight | lowlight / highlight.js | Bundled dependency for code blocks. |
Presets and variants at a glance
- preset 'core' (default): lean rich-text core — StarterKit plus code, image, table, and color.
- preset 'full': core plus advanced blocks (callout, toggle, columns, mention, wiki link, comments, find/replace, …) and their slash commands. Database / Mermaid / Math / AI remain opt-in via subpaths.
- variant 'document' (default) | 'embedded' | 'bare': controls the visual shell — a centered page, an inline panel, or no chrome at all.
Licensing
@alggtta/editor is commercial software (license: UNLICENSED, governed by a EULA) — it is not open source. The token prop is required. On dev origins (localhost / 127.0.0.1) no real token is checked, so local development is free. Production domains need a valid token issued by the CDN/admin system; the editor verifies it against the CDN gateway (verifyEndpoint, default https://cdn.alggtta.com). Without a valid token in production, the editor will not render.
Next.js / SSR: the editor is a client component. Add 'use client' to the file that imports AlggttaEditor (or mount), since rendering touches the DOM. mount() itself is SSR-safe — it does not touch the DOM until you call it.
Installation
Install @alggtta/editor with its React + Tiptap 3.26 peers, add per-feature optional peers, import the single stylesheet, and learn how the license token/verifyEndpoint and dev-origin bypass work.
Install @alggtta/editor from the npm registry. It is a commercial, source-available package (UNLICENSED / EULA) and ships React + Tiptap as peer dependencies, so you install those alongside it. The advanced blocks (database, mermaid, math, export, AI) declare their heavy libraries as optional peers — add them only for the features you actually use.
npm install
npm install @alggtta/editor react react-dom @tiptap/core @tiptap/pm @tiptap/reactThis single line covers the package plus every required peer. The companion server package @alggtta/wiki-store (v0.1.0, SQLite via node:sqlite + FTS5) is a private package — it is not on the public npm registry. Licensees receive it on request; it is only needed when you build a backlinked wiki backend (see Building a wiki).
Required peer dependencies
| Package | Version |
|---|---|
| react | ^18.2.0 || ^19.0.0 |
| react-dom | ^18.2.0 || ^19.0.0 |
| @tiptap/core | ^3.26.0 |
| @tiptap/pm | ^3.26.0 |
| @tiptap/react | ^3.26.0 |
Keep the whole @tiptap/* suite pinned to the same 3.26.x line. Mixing Tiptap minor versions across @tiptap/core, @tiptap/pm, and @tiptap/react causes ProseMirror schema mismatches at runtime.
Optional peer dependencies
Install these per feature. Each is declared optional in the package manifest, so npm will only warn if a feature subpath needs one that is missing — never on a base install.
| Feature | Package | Version |
|---|---|---|
| Database tables (/database) | @tanstack/react-table | ^8.21.3 |
| Mermaid diagrams (/mermaid) | mermaid | ^11.12.3 |
| PDF export (/export) | html2pdf.js | ^0.14.0 |
| DOCX import (/export) | mammoth | ^1.12.0 |
| Math / KaTeX | katex | ^0.16.28 |
| AI BYO-key Claude (/ai) | @anthropic-ai/sdk | >=0.70.0 <1.0.0 |
| AI BYO-key OpenAI (/ai) | openai | ^6.10.0 |
| AI BYO-key Gemini (/ai) | @google/generative-ai | ^0.24.1 |
| Collaboration persistence (/collab) | y-indexeddb | ^9.0.12 |
| Collaboration binding (/collab) | y-prosemirror | ^1.3.7 |
PDF/DOCX export needs both html2pdf.js and mammoth. The three AI provider SDKs are mutually independent — install only the one(s) whose models you wire into createBrowserLLMHandler from @alggtta/editor/ai.
Import the styles
The editor ships one stylesheet (Tailwind 4 + DaisyUI 5 layers). Import it once at your app entry, before rendering the editor:
import '@alggtta/editor/styles.css'Available subpaths
The root entry (@alggtta/editor) is the lean default. Opt-in feature surfaces live behind dedicated subpaths so the base bundle stays small — each maps to a real entry in the package's exports field.
- @alggtta/editor — AlggttaEditor (default), mount, defineAlggttaEditorElement, extension factories, getWikiLinks/getMentions
- @alggtta/editor/headless — useAlggttaEditor, useTableOfContents, useCheckpoints, useEditLock, useAutosave, MigrationRegistry
- @alggtta/editor/database — createDatabaseExtensions, databaseSlashCommands
- @alggtta/editor/mermaid — mermaid extension + slash command
- @alggtta/editor/ai — AI hooks/components incl. createBrowserLLMHandler
- @alggtta/editor/export — ReadOnlyViewer, ExportDropdown, exportSlashCommands, markdown conversion
- @alggtta/editor/license — license gate/context/hooks
- @alggtta/editor/legacy — UniversalEditor (full toolbar/comments/TOC/export) + domain blocks
- @alggtta/editor/comments — comments surface
- @alggtta/editor/collab — realtime collaboration (Yjs) surface
- @alggtta/editor/styles.css — the single stylesheet
Token + verifyEndpoint come from the CDN/admin system. The token prop is a license token issued by the admin dashboard, and verifyEndpoint points at the CDN gateway (defaults to https://cdn.alggtta.com). During development you do not need a real token: localhost and 127.0.0.1 are treated as dev origins and bypass verification. Production domains require a valid token whose allowed-domains list matches the serving origin.
In Next.js App Router and other RSC setups, render the editor from a Client Component ('use client'). The package already marks its React entry points with 'use client', but the component that renders <AlggttaEditor/> must run on the client.
Loading from the CDN (no bundler)
For Rails, Django, or plain HTML pages you can load the prebuilt UMD bundle and stylesheet directly and use the <alggtta-editor> custom element. The token is passed on the script URL and as the element's token attribute. The deployed CDN bundle keeps React external, so load the react and react-dom UMD globals before editor.umd.js.
<!-- The deployed CDN bundle keeps React external — load the UMD globals first. -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="https://cdn.alggtta.com/editor.css?token=YOUR_TOKEN" />
<script src="https://cdn.alggtta.com/editor.umd.js?token=YOUR_TOKEN"></script>
<alggtta-editor token="YOUR_TOKEN" preset="full" theme="light"></alggtta-editor>Every asset served from cdn.alggtta.com is token-gated, including the stylesheet. Put your token in the query string on both the <link> and <script> URLs (?token=…) — a <link>/<script> tag cannot send an Authorization header. Without a valid token the CDN responds 401 (no token) or 403 (token invalid, expired, or the serving domain is not in the token's allow-list); these are the expected responses, not an outage. Local development through npm needs no token; the hosted CDN always does.
Earlier docs labelled the CDN/browser-script path as "unsupported" — that note is stale. mount() and the <alggtta-editor> Web Component now ship from the package root, so script-tag embedding is fully supported. The element mounts into a light-DOM child (no Shadow DOM isolation yet), so your page's theme variables and typography apply. On each edit it dispatches a bubbling 'change' CustomEvent whose detail carries the document JSON, which non-React hosts can listen for.
Verify locally
pnpm typecheck
pnpm lint
pnpm test
pnpm buildReact (Vite) quickstart
Drop the AlggttaEditor component into a Vite + React app: install peers, import the named AlggttaEditor export and styles.css, pass token + verifyEndpoint from import.meta.env, and read updates from onUpdate({ json, html, text }).
This is the fastest way to render @alggtta/editor in a Vite + React project. You install the package alongside its required Tiptap and React peers, import the AlggttaEditor component (a named export of the package root) plus its stylesheet, and pass a license token. In development you can run against localhost without a real token; production domains require a valid token verified against the CDN gateway.
1. Install
Install the editor and its required peer dependencies. The Tiptap suite must stay on ^3.26.0 (3.26 or a newer 3.x release) and be bumped together, and React 18.2+ or 19 is supported.
npm install @alggtta/editor react react-dom @tiptap/core @tiptap/pm @tiptap/react| Peer dependency | Required version |
|---|---|
| @tiptap/core | ^3.26.0 |
| @tiptap/pm | ^3.26.0 |
| @tiptap/react | ^3.26.0 |
| react | ^18.2.0 || ^19.0.0 |
| react-dom | ^18.2.0 || ^19.0.0 |
@alggtta/editor is UNLICENSED (commercial / proprietary, governed by the EULA). The Tiptap and React peers are not bundled — they must be installed by your app so a single shared copy is used.
2. Configure environment variables
Vite exposes only variables prefixed with VITE_ to client code via import.meta.env. Put your license token and the CDN gateway URL there. Add the file to .gitignore — do not commit production tokens.
# .env.local
VITE_ALGGTTA_TOKEN=your-license-token
VITE_ALGGTTA_VERIFY_ENDPOINT=https://cdn.alggtta.comlocalhost and 127.0.0.1 are treated as development origins, so an empty/placeholder token still renders the editor while you build. A valid token issued by the CDN/admin system is required once you deploy to a production domain.
3. Render the editor
Import the AlggttaEditor named export and the stylesheet once (e.g. in your root or in this component). Pass the token and verifyEndpoint from import.meta.env, set initial content, mark it editable, supply a placeholder, and destructure { json, html, text } in onUpdate to persist changes. The editor variant defaults to 'document' and the extension preset defaults to 'core'.
// src/Editor.tsx
import { useState } from 'react'
import { AlggttaEditor } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
export default function Editor() {
const [doc, setDoc] = useState('<p>Hello from Alggtta 👋</p>')
return (
<AlggttaEditor
token={import.meta.env.VITE_ALGGTTA_TOKEN}
verifyEndpoint={import.meta.env.VITE_ALGGTTA_VERIFY_ENDPOINT}
content={doc}
editable
placeholder="Start writing…"
locale="en"
onUpdate={({ json, html, text }) => {
// json: Tiptap JSON (lossless — prefer for persistence)
// html: serialized HTML
// text: plain-text content
setDoc(html)
console.log({ json, html, text })
}}
/>
)
}For persistence prefer the json payload (lossless and compact); html is convenient for previews, and text is handy for search or word counts. All three are read from the same onUpdate call.
Core props
| Prop | Type | Description |
|---|---|---|
| token | string (required) | License token. Without a valid token the editor will not render on production origins. |
| verifyEndpoint | string | CDN gateway base URL for license verification. Defaults to https://cdn.alggtta.com. |
| content | string | JSON | Initial document as an HTML string or Tiptap JSON. |
| editable | boolean | Whether the document can be edited. Set false for read-only. |
| placeholder | string | Placeholder shown in the empty document. |
| onUpdate | ({ editor, json, html, text }) => void | Fires on every change; destructure json/html/text (lazy, computed on access). |
| variant | 'document' | 'embedded' | 'bare' | Visual shell preset. Default 'document'. |
| preset | 'core' | 'full' | Extension set. 'core' is the lean default; 'full' adds advanced blocks + slash commands. |
| locale | 'en' | 'ko' | Built-in UI label preset. Default 'en'. |
| theme | string | DaisyUI theme name applied via data-theme on the editor wrapper. |
If your code runs under a server component framework (e.g. Next.js App Router), AlggttaEditor is a client component ('use client') — keep it in a client boundary. For non-React pages, the package also ships a framework-agnostic mount() and an <alggtta-editor> custom element via defineAlggttaEditorElement(); those are covered in their own sections.
Next.js (App Router)
Render AlggttaEditor from a 'use client' wrapper in the Next.js App Router, and pass the license token and verify endpoint via NEXT_PUBLIC_ environment variables.
AlggttaEditor mounts a browser-side Tiptap editor and reads localStorage (the license guard caches the verified config for an offline grace window). It must therefore run on the client. In the App Router, wrap it in a 'use client' component and import that wrapper from a normal server page.tsx.
Do not import AlggttaEditor directly into a Server Component. Without the 'use client' boundary, Next.js tries to render it on the server where there is no DOM or localStorage, which throws at build/render time.
1. Install
npm install @alggtta/editor react react-dom @tiptap/core @tiptap/pm @tiptap/react2. Client wrapper (editor-client.tsx)
Mark the wrapper with 'use client', import the stylesheet once, and read configuration from NEXT_PUBLIC_-prefixed variables. The token prop is a required string, so coerce the env value (here with a non-null assertion).
// app/editor/editor-client.tsx
'use client'
import { AlggttaEditor } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
export function EditorClient() {
return (
<AlggttaEditor
token={process.env.NEXT_PUBLIC_CDN_TOKEN!}
verifyEndpoint={process.env.NEXT_PUBLIC_CDN_URL}
content=""
variant="document"
preset="core"
onUpdate={({ json, html, text }) => console.log({ json, html, text })}
/>
)
}3. Server page (page.tsx)
The page itself stays a Server Component. It just renders the client wrapper, so all of the browser-only work happens inside the 'use client' boundary.
// app/editor/page.tsx (Server Component)
import { EditorClient } from './editor-client'
export default function Page() {
return <EditorClient />
}4. Environment variables
# .env.local
NEXT_PUBLIC_CDN_TOKEN=tok_your_license_token
NEXT_PUBLIC_CDN_URL=https://cdn.alggtta.comimport.meta.env is Vite-only and is not defined in a Next.js bundle. In Next.js, only NEXT_PUBLIC_-prefixed variables are inlined into the browser bundle. This prefix is required here because the license token is read on the client to verify against the CDN gateway.
localhost and 127.0.0.1 are treated as dev origins: the license guard skips the remote /verify round-trip and unlocks the development feature set, so you can run the editor locally without a token issued by the CDN/admin system. A non-empty token string is still required (an empty token throws), but it does not have to be a real issued token. Production domains require a valid token; verifyEndpoint points at the CDN gateway (defaults to https://cdn.alggtta.com when omitted).
Because the NEXT_PUBLIC_ token is inlined into the client bundle and visible to end users, rely on the gateway's domain binding (Origin/Referer checks plus domain-bound decryption) for protection rather than treating the token as a secret.
Embed anywhere (vanilla JS, Web Component, CDN)
Drop @alggtta/editor into any page — not just a React tree — with the imperative mount() factory, the <alggtta-editor> custom element, or a single CDN script tag.
@alggtta/editor renders with React internally, but you do not need a React app to use it. The package root exports two framework-agnostic entry points — the imperative mount() factory and the defineAlggttaEditorElement() custom-element registrar — so you can embed the editor into Rails, Django, Laravel, or plain HTML pages. For pure browser pages with no bundler, the same build is served as a UMD script from the CDN.
Non-React embedding is fully supported as of v6.2.0: mount(), the <alggtta-editor> Web Component, and the CDN UMD script all exist. Use this section as the source of truth for embedding outside React.
1. mount() — imperative factory for any page
Import { mount } from the package root. Call mount(elementOrSelector, props) and it owns a React root for you, returning a MountHandle. The handle exposes update(partialProps) to re-render with merged props (swap content, theme, or token), unmount() to release the root, and ref — the imperative AlggttaEditorRef (insertContent / getJSON / focus / ...) once mounted. mount() is SSR-safe: nothing touches the DOM until you call it.
import { mount } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
// Selector or HTMLElement both work.
const handle = mount('#editor', {
token: 'tok_xxx', // required in production
preset: 'full', // 'core' (default) | 'full'
theme: 'light', // DaisyUI theme name
})
// Re-render with merged props.
handle.update({ theme: 'dark' })
// Reach the imperative editor ref.
const json = handle.ref?.getJSON()
// Tear down and release the React root.
handle.unmount()| MountHandle member | Signature | Purpose |
|---|---|---|
| update | (props: Partial<AlggttaEditorProps>) => void | Re-render with props merged over the current ones. |
| unmount | () => void | Unmount and release the React root. |
| ref | AlggttaEditorRef | null | Imperative handle (insertContent/getJSON/focus/...), null before mount. |
2. <alggtta-editor> Web Component
Import { defineAlggttaEditorElement } and call it once to register the custom element (default tag 'alggtta-editor'; pass a string to use a different tag). The call is a no-op outside the browser, when customElements is unavailable, or if the tag is already defined. Then place the tag anywhere and configure it via attributes. Observed attributes are: token, theme, variant, preset, placeholder, locale, content (content accepts a JSON string or plain text). The element mounts into a light-DOM child rather than a shadow root, so your global DaisyUI theme variables and typography styles apply directly — there is no Shadow DOM isolation yet.
import { defineAlggttaEditorElement } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
// Registers <alggtta-editor>. Pass a tag name to override the default.
defineAlggttaEditorElement()<alggtta-editor
token="tok_xxx"
preset="full"
theme="light"
variant="document"
locale="en"
placeholder="Start writing..."
></alggtta-editor>
<script>
// The element emits a 'change' CustomEvent on every edit.
// detail.json is the Tiptap document JSON.
document.querySelector('alggtta-editor')
.addEventListener('change', (e) => {
console.log(e.detail.json)
})
</script>The custom element surfaces edits as a 'change' CustomEvent (bubbles: true), whose detail.json is the document JSON — listen for 'change', not 'update'. Internally the React onUpdate callback is what fires it. Attribute changes after mount (e.g. swapping theme) re-render through the same MountHandle.update path.
3. CDN UMD script (no bundler)
For a plain HTML page with no bundler, load the obfuscated UMD bundle and stylesheet directly from the CDN. The token is read from the script URL's ?token= query parameter, so the editor and its dynamically imported chunks are authenticated automatically. Because the deployed CDN bundle keeps React external, load the react and react-dom UMD globals before editor.umd.js (as shown below). After the script registers the element, drop in the <alggtta-editor> tag exactly as above.
<!-- The deployed CDN bundle keeps React external — load the UMD globals first. -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="https://cdn.alggtta.com/editor.css?token=YOUR_TOKEN" />
<script src="https://cdn.alggtta.com/editor.umd.js?token=YOUR_TOKEN"></script>
<alggtta-editor token="YOUR_TOKEN" preset="full" theme="light"></alggtta-editor>The UMD filenames above (editor.umd.js and editor.css, served from cdn.alggtta.com) are the standard CDN build artifacts. If your deployment pins a versioned path (e.g. /v6.2.0/editor.umd.js), use those exact URLs — contact us if you are unsure which path your token is provisioned for. The token must be a valid token issued by the CDN/admin system and must match the production domain it was issued for.
Token & license behavior
- token is required. In all three paths (mount props, the token attribute, or the script ?token= query) the editor will not render without one in production.
- localhost and 127.0.0.1 are treated as development origins — no valid token is needed there, so you can prototype freely before wiring up issuance.
- Production domains need a valid token issued by the CDN/admin system; verifyEndpoint (an AlggttaEditorProps field) points at the CDN gateway and defaults to https://cdn.alggtta.com.
- preset:'full' registers the advanced blocks (advancedBlockExtensions) and advanced slash commands (advancedSlashCommands — database, mermaid, etc.); preset:'core' is the lean default. variant is 'document' (default) | 'embedded' | 'bare'.
Always import the stylesheet — '@alggtta/editor/styles.css' for the npm paths, or the editor.css <link> for CDN. Without it the editor renders unstyled. @alggtta/editor is UNLICENSED (commercial/proprietary, governed by its EULA); a token is part of the license flow, not just runtime config.
Components
The three render surfaces shipped by @alggtta/editor — AlggttaEditor (the recommended React component, with a full props table and the imperative AlggttaEditorRef), UniversalEditor from /legacy (batteries-included toolbar, comments, TOC, export), and ReadOnlyViewer from /export for static JSON rendering — plus the framework-agnostic mount() and <alggtta-editor> web component for non-React pages.
@alggtta/editor exposes three render surfaces. For almost every product use AlggttaEditor — it keeps license verification and feature gating active. UniversalEditor (from /legacy) is the batteries-included surface with a built-in toolbar, comments, table of contents and export. ReadOnlyViewer (from /export) renders saved Tiptap JSON as static, non-editable HTML.
AlggttaEditor (recommended)
The default export and primary entry point. It is a controlled React component that renders the full editing surface — drag handle, bubble menu, slash menu and mobile toolbar — behind a license gate. Import it from the package root and load the stylesheet once in your app.
import { AlggttaEditor } from '@alggtta/editor'
import type { AlggttaEditorRef } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
export function Page() {
return (
<AlggttaEditor
token={process.env.NEXT_PUBLIC_ALGGTTA_TOKEN!}
variant="document"
preset="core"
locale="en"
placeholder="Type '/' for commands..."
onUpdate={({ json }) => save(json)}
/>
)
}| Prop | Type | Description |
|---|---|---|
| token | string (required) | License token. Without a valid token the editor will not render. On localhost / 127.0.0.1 any value works in dev. |
| verifyEndpoint | string | CDN gateway base URL for license verification. Defaults to https://cdn.alggtta.com. |
| content | string | JSONContent | Initial document, as an HTML string or Tiptap JSON. Treated as the initial value; out-of-band updates are reconciled when the editor is not focused. |
| editable | boolean | Whether the document is editable. Defaults to true. Toggling after mount switches to read-only. |
| placeholder | string | Empty-document placeholder text. Defaults to "Type '/' for commands...". |
| theme | string | DaisyUI theme name applied to the wrapper via data-theme. |
| variant | 'document' | 'embedded' | 'bare' | Visual shell preset. Default 'document' (centered, padded, bordered card). 'embedded' is a tighter bordered surface; 'bare' drops all chrome. |
| preset | 'core' | 'full' | Which built-in extension set to mount. Default 'core' (lean rich text). 'full' adds advanced blocks (callout, toggle, columns, mention, wiki link, …) and their slash commands. |
| locale | 'en' | 'ko' | Loads a built-in UI label preset. Defaults to 'en'. Use the labels prop to override individual keys. |
| className | string | Extra classes merged onto the editor root element. |
| onUpdate | (payload: { editor, json, html, text }) => void | Fires on every document change. json/html/text are lazy getters — only the ones you read are serialized. |
| onImageUpload | (file: File) => Promise<string> | Resolve an uploaded image to a URL the editor inserts. Required for image insertion to persist remotely. |
| onFileUpload | (file: File) => Promise<string> | Same contract as onImageUpload, for generic file attachments. |
| extensionConfig | ExtensionConfig | Advanced per-feature config. Wire wiki-link search via extensionConfig.wikiLink.search and @-mention items via extensionConfig.mention.items. Memoize this object. |
In Next.js App Router, AlggttaEditor (and mount/UniversalEditor) are client components — the source already carries 'use client', but the file that imports them must run on the client. token is required in production: localhost and 127.0.0.1 are treated as dev origins and need no real token, but any other domain must supply a valid token issued by the CDN/admin system, with verifyEndpoint pointing at the gateway.
Imperative ref: AlggttaEditorRef
Attach a ref to drive the editor imperatively. The ref exposes the underlying Tiptap editor plus convenience methods for inserting content and reading the document.
import { useRef } from 'react'
import { AlggttaEditor } from '@alggtta/editor'
import type { AlggttaEditorRef } from '@alggtta/editor'
const ref = useRef<AlggttaEditorRef>(null)
// ref.current exposes:
// editor: Editor | null
// insertImage(url, attrs?)
// insertContent(content: string | JSONContent)
// getSelectedText(): string
// getJSON(): JSONContent
// getHTML(): string
// focus(position?)
<AlggttaEditor ref={ref} token={token} />
// later:
ref.current?.insertContent('<p>Hello</p>')
const doc = ref.current?.getJSON()Framework-agnostic: mount() and <alggtta-editor>
For Rails, Django, Laravel or plain HTML pages, mount(elementOrSelector, props) renders AlggttaEditor into any DOM node and returns a MountHandle with update(partialProps), unmount() and ref. It is SSR-safe: nothing touches the DOM until you call it.
import { mount } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
const handle = mount('#editor', { token: 'YOUR_TOKEN', preset: 'full' })
handle.update({ theme: 'dark' })
const json = handle.ref?.getJSON()
handle.unmount()Or register the custom element once and drop a single tag anywhere. defineAlggttaEditorElement(tag = 'alggtta-editor') observes the token, theme, variant, preset, placeholder, locale and content attributes, and emits a 'change' CustomEvent (detail.json) on edits. It mounts into a light-DOM child so global theme variables and typography apply (no Shadow DOM isolation yet).
<!-- The deployed CDN bundle keeps React external — load the UMD globals first. -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<link rel="stylesheet" href="https://cdn.alggtta.com/editor.css?token=YOUR_TOKEN" />
<script src="https://cdn.alggtta.com/editor.umd.js?token=YOUR_TOKEN"></script>
<alggtta-editor token="YOUR_TOKEN" preset="full" theme="light"></alggtta-editor>
<script>
// From an npm/bundler setup you would instead:
// import { defineAlggttaEditorElement } from '@alggtta/editor'
// defineAlggttaEditorElement()
document.querySelector('alggtta-editor')
.addEventListener('change', (e) => save(e.detail.json))
</script>UniversalEditor (/legacy)
UniversalEditor is the batteries-included surface imported from @alggtta/editor/legacy. It ships a built-in toolbar, comments sidebar, table of contents, character count and export UI — all toggled by boolean props — and drags in the heavier domain blocks. Prefer AlggttaEditor for new builds; reach for UniversalEditor when you want that chrome without assembling it yourself.
import { UniversalEditor } from '@alggtta/editor/legacy'
import '@alggtta/editor/styles.css'
<UniversalEditor
token={token}
showToolbar
showComments
showTableOfContents
showExport
currentUser={{ id: 'u1', name: 'Jane' }}
onUpdate={(editor) => save(editor.getJSON())}
/>| Prop | Type | Description |
|---|---|---|
| showToolbar | boolean | Render the built-in formatting toolbar. Defaults to true. |
| showComments | boolean | Enable the comments sidebar (pair with currentUser). Defaults to true. |
| showTableOfContents | boolean | Render an auto-generated table of contents from headings. Defaults to false. |
| showExport | boolean | Show the export dropdown (Markdown / PDF / DOCX). Defaults to true. |
| currentUser | User | Author identity attached to new comments. |
UniversalEditor uses a flat onUpdate(editor) callback — it receives the Tiptap editor instance directly. AlggttaEditor's onUpdate instead receives a { editor, json, html, text } payload, so the two signatures are not interchangeable.
ReadOnlyViewer (/export)
ReadOnlyViewer, imported from @alggtta/editor/export, renders saved Tiptap JSON as static HTML without mounting a full editor instance — ideal for published pages, feeds and previews. Its content prop accepts JSONContent | null (null renders empty). It is far lighter than booting a live editor.
import { ReadOnlyViewer } from '@alggtta/editor/export'
import '@alggtta/editor/styles.css'
import type { JSONContent } from '@tiptap/core'
export function PublishedDoc({ doc }: { doc: JSONContent | null }) {
return <ReadOnlyViewer content={doc} className="my-article" />
}Choosing a surface: use AlggttaEditor for editing (it keeps license verification and feature gating active); use mount() / <alggtta-editor> for non-React pages; use UniversalEditor when you want a ready-made toolbar/comments/TOC/export shell; use ReadOnlyViewer for displaying saved content with no editing. All four need '@alggtta/editor/styles.css' imported once.
Features
Bilingual Features section for @alggtta/editor v6.2.0: built-in rich text (slash/bubble/drag), core vs full presets, and a Feature | How to enable | Optional dep table covering AI (/ai BYO-key), collaboration (/collab Yjs), database (/database), Mermaid (/mermaid), comments (/comments), export-import (/export), math (KaTeX, /legacy), code highlight (lowlight), and the neuro theme family — every API, import, subpath, and version is verified against the published @alggtta/editor package.
`@alggtta/editor` ships a rich-text core that works out of the box, plus advanced blocks and integrations that you opt into through the `preset` prop or feature subpaths. The recommended entry point is the `AlggttaEditor` component, which keeps license verification and feature gating active. This page catalogues every feature, how to turn it on (root export vs. a subpath like `/ai` or `/database`), and the optional peer dependency each one requires.
Built-in rich text (always on)
The default `core` preset already includes the everyday writing surface: headings, lists, tables, task lists, links, marks (bold/italic/underline/strike/highlight/color), code blocks with syntax highlighting, images, and the three core interaction surfaces — the slash menu (`/`), the bubble (selection) menu, and the block drag handle. These are part of `createCoreExtensions` and need no extra packages.
In production (non-localhost domains) `AlggttaEditor` requires a valid `token` plus a `verifyEndpoint` pointing at your CDN gateway — without them the editor will not render. `localhost` and `127.0.0.1` are treated as dev origins and need no token. In Next.js App Router, render the editor from a `'use client'` wrapper.
Presets: core vs. full
The `preset` prop selects how many extensions register. `core` (default) is the lean writing surface. `full` additionally registers the advanced blocks (`advancedBlockExtensions`) and `advancedSlashCommands`, which expose blocks such as callouts, toggles, columns, embeds, mentions, wiki links, and comments through the slash menu. (Database, Mermaid, and Math stay opt-in via their own subpaths / `extensionConfig.customExtensions` — see the catalogue below.) The corresponding factories are `createCoreExtensions`, `createFullExtensions`, and the configurable `createExtensions(config)`, all root-exported.
import { AlggttaEditor } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
// Lean default — core preset, document shell
<AlggttaEditor token={token} verifyEndpoint={endpoint} />
// Advanced blocks (callouts, toggles, columns, mentions, comments…) via the slash menu
<AlggttaEditor token={token} verifyEndpoint={endpoint} preset="full" />Feature catalogue
| Feature | How to enable | Optional dep |
|---|---|---|
| Rich text, slash menu, bubble menu, drag handle | Built in — `AlggttaEditor` / `createCoreExtensions` (root) | none |
| Code blocks with syntax highlight | Built in (lowlight); core preset | none (bundled `lowlight`) |
| Advanced blocks bundle | `preset="full"` / `advancedBlockExtensions` + `advancedSlashCommands` (root) | none |
| AI writing (BYO-key) | `/ai` — `createBrowserLLMHandler` + `aiSlashCommands` | `@anthropic-ai/sdk` / `openai` ^6.10.0 / `@google/generative-ai` ^0.24.1 |
| Realtime collaboration | `/collab` — `createCollaborationExtensions` + `usePresence` | `y-prosemirror` ^1.3.7 (cursors), `y-indexeddb` ^9.0.12 (offline) |
| Database tables | `/database` — `createDatabaseExtensions()` + `databaseSlashCommands` | `@tanstack/react-table` ^8.21.3 (hard-required by this subpath) |
| Mermaid diagrams | `/mermaid` — `createMermaidExtensions` + `mermaidSlashCommands` | `mermaid` ^11.12.3 |
| Comments | `/comments` — `CommentMark` + `useComments` + `CommentSidebar` | none |
| Export / import | `/export` — `ExportDropdown` + `exportSlashCommands` + `ReadOnlyViewer` | PDF: `html2pdf.js` ^0.14.0; DOCX import: `mammoth` ^1.12.0 |
| Math (KaTeX) | `/legacy` — `MathExtension` / `InlineMathExtension` (add via `extensionConfig.customExtensions`; excluded from `preset="full"`) | `katex` ^0.16.28 |
Optional peers are declared `optional` in `peerDependenciesMeta`, so npm won't auto-install them. Install only the ones for the features you use, e.g. `npm install mermaid` before importing `@alggtta/editor/mermaid`, or `@tanstack/react-table` before `@alggtta/editor/database`. The `/database` subpath statically imports the table library, so it is a hard requirement (not lazy) for that path.
AI writing (bring your own key)
The `/ai` subpath connects the editor's AI surface directly to an LLM provider from the browser using a user-supplied key. `createBrowserLLMHandler(settings, options?)` returns an `OnAIRequestV2` streaming handler. Three providers are supported via the `LLMProvider` union `'anthropic' | 'openai' | 'gemini'` (Claude, OpenAI, Gemini). Each provider's SDK is an optional peer, installed only for the provider you actually use.
import { createBrowserLLMHandler } from '@alggtta/editor/ai'
// settings.provider: 'anthropic' | 'openai' | 'gemini'
const onAIRequest = createBrowserLLMHandler({
provider: 'anthropic',
apiKey: userKey, // user-supplied (BYO-key)
model: 'claude-opus-4-8',
})
// pass onAIRequest into the editor via onAIRequestV2 (extensionConfig / AlggttaEditor)Realtime collaboration
The `/collab` subpath turns the Yjs-ready core into a live collaborative editor. `createCollaborationExtensions` binds the document and remote cursors, `usePresence` exposes presence state, and `createIndexeddbPersistence` adds opt-in offline storage. It is provider-agnostic — pair it with y-websocket, Hocuspocus, or y-webrtc. Cursors require the `y-prosemirror` peer and offline persistence requires `y-indexeddb`.
Database, Mermaid, comments and export
Each integration lives on its own subpath so the lean core stays small. Database tables come from `/database` (`createDatabaseExtensions()` + `databaseSlashCommands`, requires `@tanstack/react-table`). Mermaid diagrams come from `/mermaid` (`createMermaidExtensions` + `mermaidSlashCommands`, requires `mermaid`). Inline comments come from `/comments` (`CommentMark`, `useComments`, `CommentSidebar`) — note that thread content lives in hook state, not the document, so persist and rehydrate it with `serializeCommentThreads` / `deserializeCommentThreads`. Export/import comes from `/export` (`ExportDropdown`, `exportSlashCommands`, `ReadOnlyViewer`); PDF export uses `html2pdf.js` and DOCX import uses `mammoth`.
// Each feature on its own subpath
import { createDatabaseExtensions, databaseSlashCommands } from '@alggtta/editor/database'
import { createMermaidExtensions, mermaidSlashCommands } from '@alggtta/editor/mermaid'
import { CommentMark, useComments, CommentSidebar } from '@alggtta/editor/comments'
import { ExportDropdown, exportSlashCommands, ReadOnlyViewer } from '@alggtta/editor/export'
import { createCollaborationExtensions, usePresence } from '@alggtta/editor/collab'Themes (neuro family)
Themes are applied with the `theme` prop (a DaisyUI theme name, set as `data-theme` on the editor wrapper). Alongside DaisyUI's `light` / `dark`, the editor ships six neuroscience-informed custom themes: `neuro-light` (warm cream, general bright environments), `neuro-dark` (warm-toned dark, halation-reduced), `neuro-sepia` (peach/sepia, dyslexia-friendly), `midnight` (high-contrast focus dark), `matcha` (low-saturation green for long writing), and `sunset` (warm coral/orange). The root-exported `ThemeToggle` / `SimpleThemeToggle` components let users switch between them.
import { AlggttaEditor, ThemeToggle } from '@alggtta/editor'
<AlggttaEditor token={token} verifyEndpoint={endpoint} theme="neuro-light" />
// Or expose a switcher:
<ThemeToggle />Browser/CDN script embedding is fully supported as of v6.2.0. A framework-agnostic `mount(elementOrSelector, props)` (returns a `MountHandle` with `update` / `unmount`) and a `defineAlggttaEditorElement()` Web Component (`<alggtta-editor>`) are root-exported, so the editor runs on plain HTML, Rails, or Django pages without React wiring. The npm React path remains the primary, fully supported integration.
Subpath APIs & extension config
Reference for the @alggtta/editor subpath entries, the createExtensions family for assembling your own Tiptap editor, and the public-API policy that keeps internal hooks out of the root barrel.
`@alggtta/editor` (v6.2.0) ships a lean root entry plus nine feature subpaths. Each subpath is independently importable, so optional peers (such as `@tanstack/react-table` or `mermaid`) are only pulled in when you import the matching subpath. The recommended product path is the root `AlggttaEditor` component; the subpaths and `createExtensions` family exist for advanced integrations that assemble their own Tiptap editor.
Public subpath entries
| Subpath | Provides | Optional peer |
|---|---|---|
| @alggtta/editor/headless | useAlggttaEditor, useEditorState, useToolbarState, useTouchDevice, useTableOfContents, useCheckpoints, useEditLock, useAutosave, getWikiLinks/getMentions, MigrationRegistry, types | — |
| @alggtta/editor/database | createDatabaseExtensions, databaseSlashCommands, DatabaseGrid + DB node types & migrations | @tanstack/react-table ^8.21.3 |
| @alggtta/editor/mermaid | createMermaidExtensions, MermaidExtension, MermaidNodeView, mermaidSlashCommands | mermaid ^11.12.3 |
| @alggtta/editor/ai | AI extensions/hooks/components, aiSlashCommands, createBrowserLLMHandler (BYO-key Claude/OpenAI/Gemini) | @anthropic-ai/sdk / openai ^6.10.0 / @google/generative-ai ^0.24.1 |
| @alggtta/editor/export | ReadOnlyViewer, ExportDropdown, ImportButton, exportSlashCommands, markdownToHtml/htmlToMarkdown, static renderers | html2pdf.js ^0.14.0 + mammoth ^1.12.0 (PDF/DOCX) |
| @alggtta/editor/license | LicenseProvider, useLicenseContext, useLicenseVerification, LicensedGate, DEFAULT_VERIFY_ENDPOINT | — |
| @alggtta/editor/legacy | UniversalEditor (full toolbar/comments/TOC/export), VersionHistory, PageMeta, TableOfContents, MathExtension, domain/guideflow blocks | katex ^0.16.28 (math) |
| @alggtta/editor/comments | CommentMark, useComments, CommentSidebar, serialize/deserializeCommentThreads | — |
| @alggtta/editor/collab | createCollaborationExtensions, createCollaborationCursor, usePresence, createIndexeddbPersistence (Yjs realtime) | y-prosemirror ^1.3.7 + y-indexeddb ^9.0.12 |
Styles live at a tenth subpath: import '@alggtta/editor/styles.css' once at your app root. Without it, the editor renders unstyled.
createExtensions / createCoreExtensions / createFullExtensions
For advanced integrations that drive a raw Tiptap editor, the root barrel exports three factory functions. `createCoreExtensions(config)` returns the lean core bundle; `createExtensions` is an alias for it. `createFullExtensions(config)` adds the advanced editing blocks (callout, toggle, columns, embed, file attachment, mention, wiki link, comments, find/replace, etc.) plus their slash commands — the same bundle that `preset:'full'` registers. Database, Mermaid, Math, and AI stay explicit opt-ins via their subpaths so 'full' never forces their optional peers on every consumer.
import { useEditor } from '@tiptap/react'
import { createExtensions } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
// createExtensions === createCoreExtensions (lean core bundle)
const extensions = createExtensions({
placeholder: 'Write here...',
characterCount: { limit: 10000 },
link: { autolink: true, openOnClick: false },
customSlashCommands: [],
})
const editor = useEditor({ extensions, content: '' })Compose optional features through `ExtensionConfig`. Pass node extensions via `customExtensions` and their slash commands via `customSlashCommands`. The same config surface works whether you call a factory directly or pass `extensionConfig` to `AlggttaEditor`:
import { createFullExtensions } from '@alggtta/editor'
import { createDatabaseExtensions, databaseSlashCommands } from '@alggtta/editor/database'
import { useEditor } from '@tiptap/react'
// Full bundle + the DB block composed via extensionConfig
const extensions = createFullExtensions({
placeholder: 'Write here...',
customExtensions: createDatabaseExtensions(),
customSlashCommands: databaseSlashCommands,
})
const editor = useEditor({ extensions, content: '' })Driving a raw Tiptap editor with these factories bypasses license verification and feature gating. For normal product use prefer the AlggttaEditor component, which keeps the license/token flow and feature gating active. Pass the same options through its extensionConfig prop instead.
Public API policy
Internal hooks, providers, and menu primitives are deliberately NOT exported from the root barrel — this prevents assembling a license-free editor by importing the raw building blocks. The boundary is enforced in CI by an automated export-contract test that asserts these names are `undefined` on the main entry. Examples of intentionally root-private symbols:
- `useEditor` (raw Tiptap), `EditorProvider`, `Toolbar`, `BubbleMenu` — editor assembly internals.
- `verifyLicense`, `initEditor`, `getDefaultExtensions`, `defaultExtensions` — license/bootstrap internals.
- Headless hooks (`useAlggttaEditor`, `useEditorState`, `useToolbarState`) are reachable only via `/headless`, not the root.
- Feature surfaces (`DatabaseGrid`, `MermaidExtension`, `AIPanel`, slash-command bundles) are reachable only via their dedicated subpaths.
Reaching for an internal symbol that the policy hides is a signal to use AlggttaEditor (or, for headless control, the /headless hooks) instead — that path keeps license verification and feature gating intact. This root-private export boundary is part of the published package contract, so these internal names may change without a major version bump.
Theming
How to theme @alggtta/editor: the neuroscience-based DaisyUI 5 theme family (neuro-light default, neuro-dark, neuro-sepia, midnight, matcha, sunset) plus built-in light/dark; setting a theme via the AlggttaEditor theme prop, the <alggtta-editor> theme attribute, or data-theme on a wrapper; the SimpleThemeToggle/ThemeToggle exports; required fonts; and the styles.css import.
@alggtta/editor ships a neuroscience-based theme family built on DaisyUI 5. Themes are pure CSS — selected by a single attribute, with no JavaScript runtime cost — so switching a theme recolors the entire editor (chrome, syntax highlighting, menus) at once. The design rationale (APCA polarity, blue-light suppression, OKLCH tokens) is available from us on request.
Available themes
Six custom themes plus DaisyUI's two built-ins are bundled. neuro-light is the default. neuro-dark also responds automatically to prefers-color-scheme: dark.
| Theme | Kind | Intended use |
|---|---|---|
| neuro-light | custom (light, default) | Daytime, general use, astigmatism — warm cream base, no pure white |
| neuro-dark | custom (dark, prefers-dark) | Night use — warm dark grey, blue-light suppressed, no pure black |
| neuro-sepia | custom (light) | Irlen syndrome / dyslexia — peach/sepia base |
| midnight | custom (dark) | High-contrast focus dark |
| matcha | custom (light) | Low-saturation green for long writing sessions |
| sunset | custom (light) | Warm-tone light / accent |
| light | DaisyUI built-in | Generic light |
| dark | DaisyUI built-in | Generic dark |
All themes use OKLCH tokens with a CVD-safe blue-orange palette and deliberately avoid pure white (#FFFFFF) and pure black (#000000) to reduce glare and halation. Syntax highlighting is reduced to five semantic groups whose colors are derived (via color-mix) from the active theme's primary/secondary/success/accent/warning tokens, so meaning stays consistent across every theme.
Setting a theme
There are three equivalent ways to apply a theme — they all resolve to a DaisyUI [data-theme="..."] selector. Pick the one that matches how you mounted the editor.
1. The theme prop on the React AlggttaEditor component (a string DaisyUI theme name). It is applied as data-theme on the editor's wrapper element, so it scopes the theme to just this editor instance:
import { AlggttaEditor } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
export function Page() {
return (
<AlggttaEditor
token={import.meta.env.VITE_ALGGTTA_TOKEN}
theme="neuro-dark"
placeholder="Write something…"
/>
)
}2. The theme attribute on the <alggtta-editor> custom element. theme is one of its observed attributes, so changing it at runtime re-applies the theme:
<script type="module">
import { defineAlggttaEditorElement } from '@alggtta/editor'
defineAlggttaEditorElement() // registers <alggtta-editor>
</script>
<alggtta-editor
token="YOUR_TOKEN"
theme="neuro-sepia"
variant="document"
></alggtta-editor>3. A data-theme attribute on any ancestor wrapper. DaisyUI applies the nearest [data-theme] in the DOM, so you can theme a whole page (or a region) by setting it on a parent element. This is also how the built-in toggles work — they set data-theme on <html>:
// Theme the whole document (what ThemeToggle does under the hood)
document.documentElement.setAttribute('data-theme', 'matcha')
localStorage.setItem('theme', 'matcha') // optional persistenceBuilt-in theme toggles
Two ready-made selector components are root-exported: SimpleThemeToggle (a compact <select>) and ThemeToggle (the same plus a transient confirmation toast). Both list all eight themes, set data-theme on document.documentElement, and persist the choice to localStorage under the 'theme' key.
import { SimpleThemeToggle, ThemeToggle } from '@alggtta/editor'
function Toolbar() {
// Either component sets <html data-theme> + persists to localStorage('theme')
return <SimpleThemeToggle />
}These toggles set the theme globally on <html>, while the AlggttaEditor theme prop / <alggtta-editor theme> scope to one instance. If you use both, the per-instance data-theme on the editor wrapper wins for that editor because it is the nearest [data-theme] ancestor. Pick one strategy to avoid confusion.
Fonts
The theme system pairs three typefaces: Atkinson Hyperlegible (Latin body — designed by the Braille Institute for low-vision legibility, disambiguating I/l and O/0), Pretendard (Korean body), and JetBrains Mono (code, with contextual ligatures for operators like => and !=). The package ships the CSS that references them via the --ue-font-sans and --ue-font-mono variables, but the font files themselves are loaded by your host page — add the Atkinson Hyperlegible + JetBrains Mono (Google Fonts) and Pretendard (jsDelivr) <link> tags shown below, otherwise the system font fallback chain is used.
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&family=JetBrains+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap"
rel="stylesheet"
/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/gh/orioncactus/[email protected]/dist/web/variable/pretendardvariable-dynamic-subset.min.css"
/>Theming is CSS-only and requires importing the stylesheet once: import '@alggtta/editor/styles.css'. Without it, theme tokens and typography are not applied. The same stylesheet carries all eight themes — there are no separate per-theme CSS files to import.
Building a wiki
Combine @alggtta/editor with the private companion @alggtta/wiki-store (SQLite, provided to licensees) to build a Notion/Obsidian-style wiki: the editor owns the page body plus the [[ and @ triggers and link enumeration, the store owns pages, the backlink index, full-text search and versions, and your app owns routing, the backlink panel and the graph.
@alggtta/editor ships the primitives a wiki needs — a rich page body, the [[ wiki-link and @ mention triggers, link enumeration, and an autosave hook — but it deliberately leaves storage to you. The private companion package @alggtta/wiki-store (v0.1.0, SQLite via node:sqlite + FTS5) — provided to licensees, not on the public npm registry — is the reference store: it owns pages, the backlink index, full-text search, tags, properties, versions and the graph. Your app wires them together with routing and a backlink panel. Request the full integration blueprint with your license.
Responsibility split
Each layer owns a clear slice. The editor never touches storage; the store never renders UI. This keeps the editor bundle lean and lets you swap the store (or skip it entirely) without forking the editor.
| Concern | @alggtta/editor | @alggtta/wiki-store | Your app |
|---|---|---|---|
| Page body editing + blocks | Owns | — | — |
| [[ / @ trigger UI + popup | Owns | — | — |
| Link enumeration (getWikiLinks / getMentions) | Owns | Consumes | — |
| Autosave timing (useAutosave) | Owns | Persists | — |
| Pages / backlink index / FTS / versions | — | Owns | — |
| Routing, backlink panel, graph, permissions | — | — | Owns |
Install
# Editor + required peers (public npm registry)
npm install @alggtta/editor react react-dom @tiptap/core @tiptap/pm @tiptap/react
# @alggtta/wiki-store is a PRIVATE companion package (Node 22+, node:sqlite + FTS5).
# It is NOT on the public npm registry — licensees receive it on request and
# install it from the tarball we provide, e.g.:
# npm install ./alggtta-wiki-store-0.1.0.tgz
# Request access: https://alggtta.comThe core loop
Create one store, then for each page render AlggttaEditor with preset="full" (this registers the WikiLink and Mention nodes). Wire the two suggestion sources through extensionConfig.wikiLink.search and extensionConfig.mention.items, and on every update derive outbound links with getWikiLinks(json) and persist them in a single store.savePage call.
'use client'
import { useMemo } from 'react'
import { AlggttaEditor, getWikiLinks } from '@alggtta/editor'
import {
createWikiStore,
createWikiSearchSource,
createMentionSource,
} from '@alggtta/wiki-store'
// One store per database file (create it once, outside React).
const store = createWikiStore({ filename: './wiki.db' })
function WikiPage({ pageId, token }: { pageId: string; token: string }) {
const page = useMemo(() => store.getPage(pageId), [pageId])
return (
<AlggttaEditor
token={token}
preset="full" // registers WikiLink + Mention nodes
content={page?.content}
extensionConfig={{
// [[ -> async page search; selecting inserts a wikiLink node
wikiLink: { search: createWikiSearchSource(store, { excludeId: pageId }) },
// @ -> people / pages
mention: { items: createMentionSource(store) },
}}
onUpdate={({ json }) => {
// Persist body + reindex outbound links + FTS in one call
store.savePage({
id: pageId,
title: page?.title ?? 'Untitled',
content: json,
links: getWikiLinks(json).map((l) => ({
targetId: l.pageId,
targetTitle: l.pageTitle,
kind: 'wiki' as const,
})),
})
}}
/>
)
}Debounced autosave
Saving on every keystroke is wasteful. Swap the inline onUpdate for the useAutosave hook from @alggtta/editor/headless — it debounces edits (default 800ms), flushes on blur and on unmount, and exposes a save status. Pair it with the store's createAutosaveHandler for a one-liner persist callback.
import { useAutosave } from '@alggtta/editor/headless'
import { createAutosaveHandler } from '@alggtta/wiki-store'
const save = createAutosaveHandler(store)
// `editor` is the Tiptap instance, e.g. from a ref or useAlggttaEditor
const { status, lastSavedAt, saveNow } = useAutosave({
editor,
delay: 800,
onSave: ({ json }) => save(pageId, json),
})
// status: 'idle' | 'pending' | 'saving' | 'saved' | 'error'Backlinks and graph
Because every save reindexes outbound links, the store can answer backlink, unlinked-mention and graph queries instantly. The editor's job ends at getWikiLinks(json); rendering the backlink panel and the graph canvas is your app's job.
// Pages that link here, and titles mentioned but not yet linked
const backlinks = store.getBacklinks(pageId)
const unlinked = store.getUnlinkedMentions(pageId)
// Whole-wiki graph -> feed to D3-force / Cytoscape / react-force-graph
const { nodes, edges } = store.getGraph()
// Cross-page full-text search (FTS5 prefix + snippets)
const results = store.search('graph view') // [{ id, title, snippet }]What stays your job
Routing, the page tree/sidebar, the graph canvas, permissions and redirect handling are app concerns — but every query they need (search, backlinks, graph, tags, properties, versions) is already in the store. The editor emits a wiki-link-click CustomEvent when a wiki link is clicked, so you can route on it.
editorEl.addEventListener('wiki-link-click', (e) => {
router.push(`/p/${e.detail.pageId}`)
})Versions: snapshot whenever you like with store.saveVersion({ pageId, authorName, summary }), then feed toEditorPageVersions(store, pageId) into VersionHistory from @alggtta/editor/legacy with onRestoreVersion={(v) => store.restoreVersion(v.id)}. The editor owns the timeline UI; the store owns version storage.
Next.js / React Server Components: AlggttaEditor is a client component, so mark the page that renders it with 'use client'. @alggtta/wiki-store uses node:sqlite, so it must run on the server (Node 22+) — never import it into a client bundle.
Token in production: token is required. On localhost / 127.0.0.1 the editor runs in dev mode without a real token; production domains need a valid token issued by the CDN/admin system, with verifyEndpoint pointing at your CDN gateway.
Validation & support
Validation and support: local checks (pnpm typecheck | lint | test | build), production tokens from the CDN/admin system (passed via the token prop, verified by the CDN gateway at verifyEndpoint), and contacting the team via https://alggtta.com for help or a custom build.
This section covers how to validate an `@alggtta/editor` integration locally, where to obtain a production license token, and how to reach the team for help or a custom build. The package is commercial (UNLICENSED, EULA) and is published to the npm registry as `@alggtta/editor` (currently v6.2.0).
Local validation
Run these four commands from the repository root to validate types, lint rules, unit tests, and the production build before shipping an integration:
pnpm typecheck # tsc --noEmit
pnpm lint # eslint .
pnpm test # vitest run
pnpm build # vite build (production build)`localhost` and `127.0.0.1` are treated as development origins by the license flow, so no token is required while developing locally. Production domains must supply a valid token via the `token` prop, with `verifyEndpoint` pointing at the CDN gateway (defaults to `https://cdn.alggtta.com`).
Getting a production token
Production tokens are issued and managed by the CDN/admin system, not generated in your own code. Each token is bound to a set of allowed domains and a plan, and the CDN gateway verifies it at runtime (Origin/Referer check plus a domain-bound encrypted boot config). Pass the issued token to the editor as the `token` prop:
import { AlggttaEditor } from '@alggtta/editor'
import '@alggtta/editor/styles.css'
<AlggttaEditor
token={process.env.NEXT_PUBLIC_CDN_TOKEN} // issued by CDN/admin
verifyEndpoint="https://cdn.alggtta.com" // CDN gateway base URL
content=""
/>| Item | Where |
|---|---|
| Token issuance / revocation | CDN admin dashboard (per-domain, per-plan tokens) |
| Runtime verification | CDN gateway at `verifyEndpoint` (default `https://cdn.alggtta.com`) |
| Token prop | `token` (required string) on `AlggttaEditor` / `mount` / `<alggtta-editor token>` |
Keep the token in an environment variable (e.g. `NEXT_PUBLIC_CDN_TOKEN` for Next.js, `VITE_CDN_TOKEN` for Vite). Because license verification happens client-side, the token is exposed to the browser bundle by design; the CDN gateway enforces domain binding so a leaked token cannot be reused on another origin.
Getting help / requesting a custom build
For licensing questions, token provisioning, bug reports, or a custom build (additional features, private extensions, enterprise terms), contact the team through the site at https://alggtta.com. Include your package version, a minimal reproduction, and the output of the failing validation command above to speed up triage.
- Package: `@alggtta/editor` v6.2.0 (UNLICENSED, commercial + EULA), published on npm.
- Companion (private): `@alggtta/wiki-store` v0.1.0 (SQLite via `node:sqlite` + FTS5) for wiki backlink/search/version storage — not on the public npm registry; provided to licensees on request.
- Installable version: check the public npm registry — `npm view @alggtta/editor version` (currently 6.2.0).
- Contact / custom builds: https://alggtta.com