Skip to content

feat: Document artifact + rich text editor - #603

Merged
ilyabo merged 13 commits into
mainfrom
document-artifact
May 11, 2026
Merged

ilyabo merged 13 commits into
mainfrom
document-artifact

Conversation

@ilyabo

@ilyabo ilyabo commented May 10, 2026 •

Copy link
Copy Markdown
Collaborator
image

Summary by CodeRabbit

  • New Features
    • Added document artifacts with markdown editing and rich formatting tools
    • Integrated document synchronization across sessions for collaborative work
    • Built knowledge indexing system extracting wikilinks, hashtags, and metadata tags
    • Enhanced development builds with WebAssembly and async module support

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 10, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@ilyabo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 8 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71b3b8a1-a9a4-460d-92eb-f5f3927e0045

📥 Commits

Reviewing files that changed from the base of the PR and between 23cf1e4 and f50e0af.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • apps/sqlrooms-cli-ui/src/createDashboardCommands.ts
  • apps/sqlrooms-cli-ui/src/store-types.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • packages/ai-core/__tests__/AiSlice.modelSelection.test.ts
  • packages/ai-core/__tests__/ContextSelector.test.ts
  • packages/ai-core/src/components/ContextSelector.tsx
  • packages/ai/src/index.ts
  • packages/documents/__tests__/DocumentsSlice.test.ts
  • packages/documents/__tests__/documentsCrdt.test.ts
  • packages/documents/package.json
  • packages/documents/src/DocumentsSliceConfig.ts
  • packages/documents/src/MarkdownDocument.tsx
  • packages/documents/src/MarkdownDocumentEditor.tsx
  • packages/documents/src/crdt.ts
📝 Walkthrough

Walkthrough

This PR introduces document artifact support to SQLRooms by creating a new @sqlrooms/documents package with Markdown editing, document commands, CRDT sync, and knowledge indexing capabilities. It integrates this package into the CLI UI, enables CRDT synchronization via runtime configuration, and improves development script argument handling for target-specific forwarding.

Changes

Documents Package

Layer / File(s) Summary
Package Setup
packages/documents/package.json, tsconfig.json, jest.config.js, eslint.config.js, typedoc.js
ESM module configured with exports for root and ./crdt subpath; includes CodeMirror, Tiptap, and state management dependencies.
Data Models
packages/documents/src/DocumentsSliceConfig.ts, DocumentsSlice.ts
Zod schemas for document artifacts with id/markdown/updatedAt; Zustand slice with CRUD methods (ensureDocument, removeDocument, setMarkdown, getDocument).
UI Components
packages/documents/src/MarkdownDocument.tsx, MarkdownDocumentEditor.tsx
MarkdownDocument wrapper ensures backing storage exists; MarkdownDocumentEditor provides Tiptap-based rich editing with optional source/Markdown panel, formatting toolbar, and heading dropdown.
Document Commands
packages/documents/src/documentCommands.ts
Five commands: document.list, document.get, document.create, document.set-markdown, document.append-markdown, with input validation and AI discovery instructions.
CRDT Integration
packages/documents/src/crdt.ts
Loro mirror schema for documents and artifact metadata; select/apply functions synchronize markdown bodies while managing artifact order separately.
Knowledge Index
packages/documents/src/knowledgeIndex.ts
Builds in-memory index extracting wikilinks (with backlinks), body/frontmatter tags, and unresolved link metadata with reason (missing/ambiguous).
Public API
packages/documents/src/index.ts, useStoreWithDocuments.ts
useStoreWithDocuments hook and re-exports of all types, slices, commands, components, and utilities.
Documentation & Tests
packages/documents/README.md, __tests__/*
Package guide covering setup, components, commands, CRDT, knowledge index; tests for slice CRUD, command flows, CRDT mirror sync, knowledge extraction.

CLI UI Integration

Layer / File(s) Summary
Dependencies & Build
apps/sqlrooms-cli-ui/package.json, vite.config.ts
Adds @sqlrooms/crdt and @sqlrooms/documents workspace deps; registers vite-plugin-wasm and vite-plugin-top-level-await; sets build target to esnext.
Runtime Configuration
apps/sqlrooms-cli-ui/src/runtimeConfig.ts, store-types.ts
RuntimeConfig gains syncEnabled, crdtWsUrl, crdtRoomId fields; RoomState intersects DocumentsSliceState and CrdtSliceState.
Artifact Registry
apps/sqlrooms-cli-ui/src/artifactTypes.tsx
CLI_ARTIFACT_TYPES includes 'document'; ARTIFACT_TYPES.document entry wires icon (ScrollTextIcon), component (DocumentArtifact), and lifecycle handlers (ensureDocument/removeDocument).
Panel Component
apps/sqlrooms-cli-ui/src/workspace/DocumentArtifact.tsx
DocumentArtifact RoomPanelComponent resolves artifactId, ensures backing document via useEffect, renders MarkdownDocument or null.
Command Extension
apps/sqlrooms-cli-ui/src/createDashboardCommands.ts
artifact.select and createArtifactCommand branching now handles document type via state.documents methods.
Store Wiring
apps/sqlrooms-cli-ui/src/store.ts
Registers DocumentsSlice; creates conditional CRDT sync connector; registers/unregisters document commands in room lifecycle; appends DOCUMENT_AI_INSTRUCTIONS; includes documents in persistence schema.
Theme Export
packages/codemirror/src/index.ts
Exports createSqlroomsTheme for use in MarkdownDocumentEditor.

CRDT & Development

Layer / File(s) Summary
CRDT State
packages/crdt/src/createCrdtSlice.ts, sync/webSocketSyncConnector.ts
Snapshot loading calls checkoutToLatest after import; WebSocket sync introduces importAndCheckoutLatest helper applied consistently to all binary update paths.
Dev Scripts
python/sqlrooms-cli/package.json, scripts/dev.mjs, scripts/dev.mjs, sqlrooms/web/launcher.py
CLI dev script forwards args from env or CLI after -- separator; monorepo dev script routes arguments to CLI or Turbo, augments PATH, passes SQLROOMS_CLI_DEV_ARGS; launcher provides CRDT config (syncEnabled, crdtWsUrl, crdtRoomId).

🐰 Documents flow through the editor's keys,

CRDT keeps them synced with ease,

Wikilinks hop between the pages sweet,

Knowledge graphs grow complete! 📚✨

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • sqlrooms/sqlrooms#552: Both PRs modify artifact types and CLI UI store wiring, with this PR extending those foundations to add the document artifact type and full documents/CRDT integration.
  • sqlrooms/sqlrooms#489: Both PRs modify the same CLI UI store configuration (store.ts, vite.config.ts) and dependency wiring for CRDT and slice support.
  • sqlrooms/sqlrooms#492: Both PRs extend the CLI UI app surface by adding workspace dependencies and modifying runtimeConfig.ts and store.ts to wire new room slices with CRDT/sync capabilities.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Document artifact' directly describes the main feature addition visible across all changes—introducing a complete document artifact system with editor, state management, and CRDT sync.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch document-artifact

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ilyabo
ilyabo marked this pull request as ready for review May 11, 2026 13:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
packages/documents/src/MarkdownDocument.tsx (2)

24-29: 💤 Low value

Inline arrow function creates new reference on each render.

The onChange prop receives a new arrow function (markdown) => setMarkdown(artifactId, markdown) on every render. While this typically doesn't cause issues with Tiptap's internal memoization, you could wrap it in useCallback for consistency.

♻️ Optional: Memoize onChange callback
+ const handleChange = React.useCallback(
+   (markdown: string) => setMarkdown(artifactId, markdown),
+   [artifactId, setMarkdown],
+ );
+
  return (
    <MarkdownDocumentEditor
      value={document?.markdown ?? ''}
-     onChange={(markdown) => setMarkdown(artifactId, markdown)}
+     onChange={handleChange}
    />
  );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/documents/src/MarkdownDocument.tsx` around lines 24 - 29, The inline
arrow passed to MarkdownDocumentEditor’s onChange prop creates a new function
each render; wrap the handler in React.useCallback (e.g., create a memoized
handleChange that calls setMarkdown(artifactId, markdown)) and pass that
callback to onChange to stabilize the prop reference; ensure artifactId and
setMarkdown are included in the dependency array so the callback updates
correctly.

8-16: 💤 Low value

Consider combining selectors or using shallow comparison.

Three separate useStoreWithDocuments calls will cause this component to re-render whenever any of these three values change. While the functions (ensureDocument, setMarkdown) are likely stable references, combining related selectors can improve clarity and potentially performance.

♻️ Consider consolidating selectors
- const document = useStoreWithDocuments(
-   (state) => state.documents.config.artifacts[artifactId],
- );
- const ensureDocument = useStoreWithDocuments(
-   (state) => state.documents.ensureDocument,
- );
- const setMarkdown = useStoreWithDocuments(
-   (state) => state.documents.setMarkdown,
- );
+ const {document, ensureDocument, setMarkdown} = useStoreWithDocuments(
+   (state) => ({
+     document: state.documents.config.artifacts[artifactId],
+     ensureDocument: state.documents.ensureDocument,
+     setMarkdown: state.documents.setMarkdown,
+   }),
+ );

However, note that this creates a new object on each selector call, which will cause re-renders unless you use a shallow equality check. If the individual selectors are more stable, the current pattern may be preferable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/documents/src/MarkdownDocument.tsx` around lines 8 - 16, The
component currently calls useStoreWithDocuments three times for document,
ensureDocument, and setMarkdown causing extra re-renders; replace these with a
single useStoreWithDocuments selector that returns an object like { document:
state.documents.config.artifacts[artifactId], ensureDocument:
state.documents.ensureDocument, setMarkdown: state.documents.setMarkdown } and
pass a shallow equality comparator (e.g., zustand's shallow) as the second
argument to prevent re-renders from object identity changes; update references
to use the new object fields and keep original symbol names (document,
ensureDocument, setMarkdown) to minimize other changes.
packages/documents/src/MarkdownDocumentEditor.tsx (1)

274-285: 🏗️ Heavy lift

Consider a non-blocking link prompt UI.

Line 278 uses window.prompt(), which blocks the UI thread and can be jarring. For a better user experience, consider implementing a non-blocking dialog or inline input field for link insertion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/documents/src/MarkdownDocumentEditor.tsx` around lines 274 - 285,
The current command's run handler in MarkdownDocumentEditor.tsx uses
window.prompt (blocking) to get the link URL; replace it with a non-blocking UI
flow: create a small LinkInputDialog/InlineLinkInput component that opens via
state (or returns a Promise) when the run function is invoked, pre-fill it using
editor?.getAttributes('link').href, and then on confirm call
editor?.chain().focus().setLink({href}).run() or
editor?.chain().focus().unsetLink().run() on empty/cancel; update the run
implementation to toggle the dialog state (or await the component's promise)
instead of calling window.prompt so the UI remains responsive.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/documents/__tests__/documentsCrdt.test.ts`:
- Line 11: The test imports LoroDoc from "loro-crdt" but the `@sqlrooms/documents`
package manifest is missing that dependency; add "loro-crdt" to the
`@sqlrooms/documents` package.json dependencies (or devDependencies if it's only
used in tests) with an appropriate version, then run the package manager install
so knip/CI recognizes the dependency and the import in documentsCrdt.test.ts
succeeds.

In `@packages/documents/src/crdt.ts`:
- Around line 132-134: The filter that builds nonDocumentOrder currently treats
unknown IDs as non-documents because artifactsById[id]?.type !== 'document'
returns true for undefined; change the predicate to explicitly require the
artifact exists and is not a document—e.g. use artifactsById[id] != null &&
artifactsById[id].type !== 'document' (or
Object.prototype.hasOwnProperty.call(artifactsById, id) &&
artifactsById[id].type !== 'document') in the computation of nonDocumentOrder so
deleted/unknown IDs are excluded.
- Around line 125-139: incomingArtifactOrder can contain duplicates which leads
to duplicate IDs in artifactOrder; modify the creation of incomingDocumentOrder
to both filter for IDs present in documentArtifacts and remove duplicates while
preserving original order (e.g., use a seen Set to skip repeated IDs), then
rebuild knownDocumentOrder from that deduplicated incomingDocumentOrder before
computing missingDocumentOrder and artifactOrder so the final artifactOrder
contains each document ID only once.

In `@packages/documents/src/DocumentsSliceConfig.ts`:
- Around line 10-12: The DocumentsSliceConfig schema currently allows artifact
map keys to differ from DocumentArtifact.id; update the schema definition for
artifacts (DocumentsSliceConfig) to add a schema-level refinement that iterates
each record entry and fails validation if any record key !== artifact.id (use
z.record(...).superRefine or a top-level DocumentsSliceConfig.superRefine to
check artifacts). Ensure the error identifies which key/id mismatch occurred and
keep the value schema as DocumentArtifact so downstream code relying on
artifacts[artifactId] === {id: artifactId, ...} is guaranteed.

In `@python/sqlrooms-cli/scripts/dev.mjs`:
- Around line 11-13: The default DB path is hardcoded as '/tmp/sqlrooms-cli.db'
which is Unix-only; replace that literal (the '/tmp/sqlrooms-cli.db' entry in
the args array in dev.mjs) with a platform-neutral temp path built from Node's
os.tmpdir() and path.join (import os and path at top of the module and use
path.join(os.tmpdir(), 'sqlrooms-cli.db')) so Windows and Unix devs get a valid
temp file location.

---

Nitpick comments:
In `@packages/documents/src/MarkdownDocument.tsx`:
- Around line 24-29: The inline arrow passed to MarkdownDocumentEditor’s
onChange prop creates a new function each render; wrap the handler in
React.useCallback (e.g., create a memoized handleChange that calls
setMarkdown(artifactId, markdown)) and pass that callback to onChange to
stabilize the prop reference; ensure artifactId and setMarkdown are included in
the dependency array so the callback updates correctly.
- Around line 8-16: The component currently calls useStoreWithDocuments three
times for document, ensureDocument, and setMarkdown causing extra re-renders;
replace these with a single useStoreWithDocuments selector that returns an
object like { document: state.documents.config.artifacts[artifactId],
ensureDocument: state.documents.ensureDocument, setMarkdown:
state.documents.setMarkdown } and pass a shallow equality comparator (e.g.,
zustand's shallow) as the second argument to prevent re-renders from object
identity changes; update references to use the new object fields and keep
original symbol names (document, ensureDocument, setMarkdown) to minimize other
changes.

In `@packages/documents/src/MarkdownDocumentEditor.tsx`:
- Around line 274-285: The current command's run handler in
MarkdownDocumentEditor.tsx uses window.prompt (blocking) to get the link URL;
replace it with a non-blocking UI flow: create a small
LinkInputDialog/InlineLinkInput component that opens via state (or returns a
Promise) when the run function is invoked, pre-fill it using
editor?.getAttributes('link').href, and then on confirm call
editor?.chain().focus().setLink({href}).run() or
editor?.chain().focus().unsetLink().run() on empty/cancel; update the run
implementation to toggle the dialog state (or await the component's promise)
instead of calling window.prompt so the UI remains responsive.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 16d65985-3e05-475d-96f0-b45647f9cc33

📥 Commits

Reviewing files that changed from the base of the PR and between 4ffaf6c and 23cf1e4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (34)
  • apps/sqlrooms-cli-ui/package.json
  • apps/sqlrooms-cli-ui/src/artifactTypes.tsx
  • apps/sqlrooms-cli-ui/src/createDashboardCommands.ts
  • apps/sqlrooms-cli-ui/src/runtimeConfig.ts
  • apps/sqlrooms-cli-ui/src/store-types.ts
  • apps/sqlrooms-cli-ui/src/store.ts
  • apps/sqlrooms-cli-ui/src/workspace/DocumentArtifact.tsx
  • apps/sqlrooms-cli-ui/vite.config.ts
  • packages/codemirror/src/index.ts
  • packages/crdt/src/createCrdtSlice.ts
  • packages/crdt/src/sync/webSocketSyncConnector.ts
  • packages/documents/README.md
  • packages/documents/__tests__/DocumentsSlice.test.ts
  • packages/documents/__tests__/documentCommands.test.ts
  • packages/documents/__tests__/documentsCrdt.test.ts
  • packages/documents/__tests__/knowledgeIndex.test.ts
  • packages/documents/eslint.config.js
  • packages/documents/jest.config.js
  • packages/documents/package.json
  • packages/documents/src/DocumentsSlice.ts
  • packages/documents/src/DocumentsSliceConfig.ts
  • packages/documents/src/MarkdownDocument.tsx
  • packages/documents/src/MarkdownDocumentEditor.tsx
  • packages/documents/src/crdt.ts
  • packages/documents/src/documentCommands.ts
  • packages/documents/src/index.ts
  • packages/documents/src/knowledgeIndex.ts
  • packages/documents/src/useStoreWithDocuments.ts
  • packages/documents/tsconfig.json
  • packages/documents/typedoc.js
  • python/sqlrooms-cli/package.json
  • python/sqlrooms-cli/scripts/dev.mjs
  • python/sqlrooms-cli/sqlrooms/web/launcher.py
  • scripts/dev.mjs

Comment thread packages/documents/__tests__/documentsCrdt.test.ts
Comment thread packages/documents/src/crdt.ts Outdated
Comment thread packages/documents/src/crdt.ts Outdated
Comment thread packages/documents/src/DocumentsSliceConfig.ts
Comment on lines +11 to +13
'--db-path',
'/tmp/sqlrooms-cli.db',
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use a platform-neutral default DB path.

Line 12 hardcodes /tmp/sqlrooms-cli.db, which is Unix-specific and can fail on Windows dev machines.

💡 Suggested fix
 import {spawnSync} from 'node:child_process';
+import os from 'node:os';
+import path from 'node:path';
 
 const defaultArgs = [
   'run',
   'sqlrooms',
@@
   '--no-open-browser',
   '--db-path',
-  '/tmp/sqlrooms-cli.db',
+  path.join(os.tmpdir(), 'sqlrooms-cli.db'),
 ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'--db-path',
'/tmp/sqlrooms-cli.db',
];
import {spawnSync} from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
const defaultArgs = [
'run',
'sqlrooms',
'--no-open-browser',
'--db-path',
path.join(os.tmpdir(), 'sqlrooms-cli.db'),
];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/sqlrooms-cli/scripts/dev.mjs` around lines 11 - 13, The default DB
path is hardcoded as '/tmp/sqlrooms-cli.db' which is Unix-only; replace that
literal (the '/tmp/sqlrooms-cli.db' entry in the args array in dev.mjs) with a
platform-neutral temp path built from Node's os.tmpdir() and path.join (import
os and path at top of the module and use path.join(os.tmpdir(),
'sqlrooms-cli.db')) so Windows and Unix devs get a valid temp file location.

@ilyabo
ilyabo merged commit 14c96f9 into main May 11, 2026
4 checks passed
@ilyabo
ilyabo deleted the document-artifact branch May 11, 2026 14:54
@ilyabo ilyabo changed the title feat: Document artifact feat: Document artifact + rich text editor May 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant