Linking

One store drives a doc and the sheets in it, so one proposal can change both.

Real work rarely stays in one component. A plan has a table in it; a report sums up the numbers beside it. When the agent changes a number, the sentence about it should change with it, and the person should see both before accepting either.

One store

workspaceStore holds sheets at /sheets/{id} and docs at /docs/{id}, each checked the way it is checked alone.

import { workspaceStore } from '@/components/doc/store'

const store = workspaceStore({
  sheets: {
    hiring: {
      columns: {
        name: { name: 'Candidate', type: 'text', order: 1 },
        stage: { name: 'Stage', type: 'enum', order: 2, enum: ['Offer', 'Hired'] },
      },
      rows: { r1: { order: 1, cells: { name: 'Maya Chen', stage: 'Offer' } } },
    },
  },
  docs: {
    plan: {
      blocks: {
        b1: { type: 'text', order: 1, md: '# Q4 hiring plan' },
        b2: { type: 'text', order: 2, md: 'Two offers are out.' },
        b3: { type: 'sheet', order: 3, ref: '/sheets/hiring' },
      },
    },
  },
})

Render each part

Every component takes the same store and an at: where its part lives.

<Doc store={store} at="/docs/plan" />
<Sheet store={store} at="/sheets/hiring" />

A sheet in a doc

A doc block { type: 'sheet', ref: '/sheets/hiring' } shows that sheet, live, inside the doc. It is the same sheet you can render on its own: an edit in one shows in the other.

One proposal, two places

The agent gets workspace_read and workspace_propose, and their description tells it to put a cell and the paragraph about it in one proposal:

{
  "why": "Maya accepted the offer",
  "ops": [
    { "op": "replace", "path": "/sheets/hiring/rows/r1/cells/stage", "value": "Hired" },
    { "op": "replace", "path": "/docs/plan/blocks/b2/md", "value": "Maya accepted. One offer is still out." }
  ]
}

The Sheet draws the cell's change and the Doc draws the paragraph's, each where it acts.

Review per place

Accepting the proposal takes both. To take one part, accept only that place; the other stays pending, under the same reason.

store.accept(id) // the cell and the paragraph
store.accept(id, { only: ['/sheets/hiring/rows/r1/cells/stage'] }) // just the cell
store.reject(id, { only: ['/docs/plan/blocks/b2/md'] }) // drop the paragraph

If a person edits either place first, only that place goes stale. See Concepts.