Skip to content

k3sava/chalk

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chalk

Most web pages go through this:

Writer → Google Doc → Designer → Figma → 2-3 revision rounds → Dev → more revision rounds

Three people. Four tools. Every transition is a manual content transcription: doc to Figma text layers to JSX strings. A single copy edit means touching three tools and three people. The writer can't see it rendered. The designer reviews a mockup, not the real page. The developer gets messy input and spends days cleaning it up before they can publish.

Chalk fixes the input:

Staging page → Writer edits inline → Designer comments on the real page → AI applies → Dev imports and ships

The staging page replaces both the Google Doc and the Figma. The writer edits real copy in real layout. The designer reviews the actual rendered page, not a mockup. By the time dev gets the content, it's final: reviewed, refined, and structured as clean JSON they can import directly. Hours to publish, not days.

Chalk is a React component library that turns any staging page into this surface. Store your content as JSON with TextElement { id, text } fields. Chalk adds inline editing, anchored comments, and AI review on top.

Not a CMS. Not a page builder. A thin overlay that gives the writer and designer a shared surface, and gives the developer clean input.

Install

npm install chalk-edit

Quick start

1. Store content as JSON with TextElement fields

{
  "pageId": "landing",
  "hero": {
    "h1": { "id": "landing.hero.h1", "text": "Ship faster with fewer meetings" },
    "body": { "id": "landing.hero.body", "text": "Stop transcribing copy between tools." }
  }
}

Every text field is a TextElement { id, text }. That's the only contract.

2. Wrap your page with ChalkProvider

import { ChalkProvider, ChalkToolbar, CommentPanel, ReviewPanel, ChalkText } from "chalk-edit/react";

export default function LandingPage({ content }) {
  return (
    <ChalkProvider pageId="landing">
      <ChalkText element={content.hero.h1} tag="h1" className="text-4xl font-bold" />
      <ChalkText element={content.hero.body} tag="p" className="text-lg" />
      <ChalkToolbar />
      <CommentPanel />
      <ReviewPanel />
    </ChalkProvider>
  );
}

3. Add API routes (Next.js)

// src/app/api/chalk/content/route.ts
import { createContentHandler } from "chalk-edit/next";
const { GET, PUT } = createContentHandler({
  contentDir: "src/content",
  pageIdToSlug: (pageId) => pageId,
});
export { GET, PUT };
// src/app/api/chalk/comments/route.ts
import { createCommentsHandler } from "chalk-edit/next";
const { GET, POST, PATCH, DELETE } = createCommentsHandler({
  chalkDir: "src/content/.chalk",
});
export { GET, POST, PATCH, DELETE };
// src/app/api/chalk/review/route.ts
import { createReviewHandler } from "chalk-edit/next";
const { GET, POST } = createReviewHandler({
  contentDir: "src/content",
  chalkDir: "src/content/.chalk",
});
export { GET, POST };

Done. Your staging page is now an editing surface.

How it works

Three modes, toggled from a floating toolbar at the bottom of the page.

View -- Normal page. No editing UI. What the visitor sees.

Edit -- Every ChalkText becomes contentEditable. Click, type, save. The writer works on the real rendered page, not a doc.

Comment -- Click any text to leave a comment anchored to that specific element. The designer comments on real rendered copy in real layout, not a Figma approximation. Comments appear in a right sidebar with timestamps, resolve/reopen, and delete.

AI review

When the designer leaves comments, one button sends them all for AI review. Chalk does not call any AI API itself. Instead:

  1. Chalk saves a review request to disk (open comments + content snapshot).
  2. An external tool (Claude Code, a CI script, whatever) reads the request, reasons about all comments together, and writes edits back.
  3. Chalk polls for the edits and shows them inline with accept/reject per edit.

The AI sees every comment in context and produces coherent edits across the page. No more one-at-a-time revision rounds. The prompt is yours to build. Chalk gives you collectText() to flatten content into a map, and the comment data to work with.

Why this exists

The workflow problem isn't speed. It's the number of translations. Content gets written once and transcribed four times: doc, Figma layers, JSX, then back through the loop for revisions. Each translation introduces drift. The designer sees a mockup that doesn't match the build. The writer sees a doc that doesn't match the design. The developer gets ambiguous input and spends time reconciling what the writer meant, what the designer mocked up, and what's actually buildable.

Chalk eliminates the translations before dev. Every edit happens on the actual page. The JSON content file is the single source of truth. When the writer changes a headline, it's rendered immediately in the real layout. When the designer comments on spacing, they're looking at real CSS, not a Figma approximation. By the time the developer receives the content, it's been edited and reviewed on the real page. They import structured JSON, review it in their own environment, refine, and publish.

Core API

Pure functions. No React, no Node, no side effects.

import { findElement, updateElement, collectText, getAllElementIds } from "chalk-edit/core";
import { elementId, commentId, te, sectionFromId, pageFromId } from "chalk-edit/core";
import { TextElementSchema, CommentSchema, ReviewEditSchema } from "chalk-edit/core";
Function What it does
findElement(obj, id) Find a TextElement by ID anywhere in a nested object
updateElement(obj, id, text) Update a TextElement's text by ID. Mutates in place.
collectText(obj) Collect all TextElements into { id: text } map
getAllElementIds(obj) Get all TextElement IDs from a nested object
elementId(pageId, ...path) Generate a deterministic element ID
commentId() Generate a random comment ID
te(pageId, path, text) Shorthand to create a TextElement

React components

import {
  ChalkProvider,    // Context provider. Wraps your page.
  ChalkText,        // Smart renderer: editable in edit mode, plain otherwise.
  ChalkToolbar,     // Floating toolbar: mode toggle, save, review.
  CommentPanel,     // Right sidebar for comments.
  ReviewPanel,      // AI edit suggestions panel.
  EditableText,     // Low-level contentEditable wrapper.
  useChalk,         // Hook to access Chalk state and actions.
} from "chalk-edit/react";

Theming

Chalk uses CSS custom properties. Set them on any parent element:

:root {
  --chalk-accent: #2563eb;
  --chalk-accent-hover: #1d4ed8;
  --chalk-text: #1D2939;
}

Next.js API handlers

Factory functions that return Next.js route handlers.

Factory Returns Options
createContentHandler(opts) { GET, PUT } contentDir, pageIdToSlug?, devOnly?
createCommentsHandler(opts) { GET, POST, PATCH, DELETE } chalkDir
createReviewHandler(opts) { GET, POST } contentDir, chalkDir, pageIdToSlug?

Content writes are dev-only by default. Set devOnly: false to allow in production.

Keyboard shortcuts

Key Action
V View mode
E Edit mode
C Comment mode (toggles panel)
S Save (edit mode, when dirty)
R Request AI review (comment mode, with open comments)

Shortcuts appear as tooltips when you hover any toolbar button.

Relationship to Marker

Marker solves the same workflow problem for slide decks. Chalk solves it for web pages. Both share the same primitives (TextElement, Comment, ReviewEdit) and the pattern of element-level comments processed by AI in a single pass.

Marker is a CLI that owns the full rendering pipeline. Chalk is a component library that layers onto your existing pages.

License

MIT

About

Writer edits inline. Designer comments on the real page. AI applies. No more doc-to-Figma-to-code transcription.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors