This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Lexical-based rich editor ecosystem. Modular packages: core editor, UI primitives, content renderers, plugins, extensions, and integration bundles.
Build: Vite 7 + Vanilla Extract CSS-in-TS. ESM only (.mjs). TypeScript 5.9. Lexical 0.42. pnpm 10. Node >= 20.
pnpm dev # demo/ dev playground (all features)
pnpm --filter @haklex/rich-editor dev:build # watch mode for core
pnpm build # turbo build (all)
pnpm build:packages # workspace packages under packages/
pnpm --filter @haklex/rich-editor build # single package
pnpm lint # turbo lint (all)
npx eslint path/to/file.ts # lint single file
npx prettier --write path/to/file.ts # format single file
npx vitest run packages/rich-renderer-katex/tests/ # run specific tests
pnpm release:rich # bump + build + publish @haklex/*- ESLint:
eslint-config-hyoban. Key rule:react/no-use-context: 'error'— must use React 19use(Context)notuseContext(). - Versioning: All
@haklex/*share one version (frompackages/rich-editor/package.json), managed bybumpp. - Pre-commit:
simple-git-hooks+lint-staged(prettier + eslint on staged files). - Build: Shared Vite config in
vite.shared.ts(createViteConfig()). Auto-externalizes deps/peerDeps. Output: ESM.mjs+.d.ts. Vanilla Extract for CSS-in-TS (zero runtime).
The ecosystem splits nodes and renderers into static (read-only) and edit (editor-only) variants for tree-shaking.
- Static Node/Renderer: Display-only. No
@haklex/rich-editor-uiorlucide-reactimports. Used byRichRenderer. - Edit Node/Renderer: Extends static variant. Adds heavy deps (Popover, DropdownMenu, NestedComposer). Used by
RichEditor.
Split criteria: Only split when the edit path introduces heavy imports absent from the static path.
Registration:
@haklex/rich-editorsrc/config.ts→ static nodes →RichRenderer@haklex/rich-editorsrc/config-edit.ts→ edit nodes →RichEditor@haklex/rich-rendererssrc/config.ts→enhancedRendererConfig(static renderers)@haklex/rich-renderers-editsrc/config.ts→enhancedEditRendererConfig(edit renderers)
Adding a new node with edit UI: Create FooNode with RendererWrapper in decorate(), then FooEditNode extends FooNode overriding decorate() with edit decorator. Register base in config.ts, edit in config-edit.ts. Create FooRenderer (static) + FooEditRenderer (edit). Add to respective aggregator configs.
@haklex/rich-kit-shiro (integration bundle — ShiroEditor / ShiroRenderer)
├── @haklex/rich-editor (core: nodes + plugins + styles + contexts)
│ ├── @haklex/rich-editor-ui (Dialog, Dropdown, Popover via @base-ui/react)
│ ├── @haklex/rich-headless (server-side node registry, zero React)
│ └── @haklex/rich-style-token (theme tokens, CSS variables, variant presets)
├── @haklex/rich-static-renderer (SSR engine, RichRenderer component)
├── @haklex/rich-renderers (static renderer aggregator)
├── @haklex/rich-renderers-edit (edit renderer aggregator)
├── @haklex/rich-ext-{code-snippet,embed,excalidraw,gallery} (heavy extensions)
├── @haklex/rich-plugin-{block-handle,floating-toolbar,link-edit,mention,slash-menu,table,toolbar}
└── @haklex/rich-renderer-katex (KaTeX edit nodes)
@haklex/rich-diff (standalone diff viewer)
@haklex/rich-editor-demo (dev playground, workspace at demo/)
- Context Provider value stability: Never pass object literals as context
value. Primitives OK directly; objects must beuseMemo'd. - Neutral colors: Use
#737373/#a3a3a3(neutral-500/400) for muted text/borders. No tinted grays (slate, zinc). - Styling: Vanilla Extract (
*.css.ts) throughout. No Tailwind in editor packages. - Variant system:
article(sans-serif, 16px),note(CJK serif, 16px),comment(sans-serif, 14px). Controlled byvariantprop +ColorSchemeContext.
Shiroi (Next.js frontend, ../Shiroi)
└── @haklex/rich-kit-shiro (npm, native React)
admin-vue3 (Vue 3 dashboard, ../admin-vue3)
└── @haklex/{rich-editor,rich-editor-ui,rich-kit-shiro,rich-plugin-toolbar,rich-style-token,rich-diff,rich-ext-nested-doc}
React-in-Vue bridge: createRoot() inside Vue defineComponent (src/components/editor/rich/RichEditor.tsx)
mx-core (NestJS backend, ../mx-core)
└── @haklex/rich-headless
Server-side: createHeadlessEditor() + allHeadlessNodes + $toMarkdown() for Lexical JSON → Markdown
Release flow: pnpm release:rich → bump + build + publish to npm. Then update pinned versions in admin-vue3/package.json and mx-core/apps/core/package.json.