Doc

A document in suggesting mode. People write in place; an agent's edits show up on the blocks they would change.

Installation

npx shadcn@latest add https://ui.usecube.io/r/doc.json

It brings the Sheet with it, since a doc can show one. Import both stylesheets, the Doc's after the Sheet's:

app/globals.css
@import "tailwindcss";
@import "../components/sheet/sheet.css";
@import "../components/doc/doc.css";

@custom-variant dark (&:where(.dark, .dark *));

Usage

import { Doc } from '@/components/doc/Doc'
import { docStore } from '@/components/doc/store'

const store = docStore(
  {
    blocks: {
      b1: { type: 'text', order: 1, md: '# Launch notes' },
      b2: { type: 'text', order: 2, md: 'We ship on Thursday. The beta list gets the first invites.' },
    },
  },
  { name: 'notes' },
)

export function Notes() {
  return <Doc store={store} />
}

Data

A doc is blocks: one per paragraph, heading, list, quote, code block or table, each holding its own markdown. Every block has a key that never moves, so a model writes /blocks/b3/md, never /blocks/2. Order is a field, sorted when the Doc draws.

interface DocState {
  blocks: Record<string, TextBlock | SheetBlock>
}

interface TextBlock {
  type: 'text'
  order: number
  md: string // one paragraph, heading, list, quote, code block or table
}

interface SheetBlock {
  type: 'sheet'
  order: number
  ref: string // where the sheet lives in the same store, like '/sheets/pipeline'
}

Agent

const tools = store.tools({ by: 'Claude' }) // notes_read, notes_propose

To rewrite a paragraph, the model replaces that block's markdown:

{
  "why": "Move the launch to Friday",
  "ops": [{ "op": "replace", "path": "/blocks/b2/md", "value": "We ship on Friday. The beta list gets the first invites." }]
}

The Doc shows the old words struck through and the new ones beside them, in the block, until a person decides.

A sheet inside

A block of type sheet shows a live Sheet from the same store, and one proposal can change a cell and the paragraph that talks about it. Linking shows how.

API Reference

Doc

PropTypeDescription
storeDocStoreThe data, from docStore, or a workspace store holding the doc.
atstringWhere the doc lives in the store, like /docs/plan. Leave it out when the store is the doc.

docStore

docStore(initial: DocState, options?: { name?: string; about?: string }): DocStore

name names the tools (${name}_read, ${name}_propose), about tells the agent what the doc is. Proposals that would break the doc are refused with the reason.