Skip to content

Commit 7bf5829

Browse files
authored
feat: add consolidated loro skill (#923)
1 parent 1e986f3 commit 7bf5829

File tree

10 files changed

+593
-0
lines changed

10 files changed

+593
-0
lines changed

skills/loro/SKILL.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
---
2+
name: loro
3+
description: "Comprehensive guide for using Loro across document modeling, synchronization, versioning, rich text editors, app-state mirroring, performance tradeoffs, and wasm bindings. Use when Codex needs to work with `loro-crdt`, `loro`, `loro-prosemirror`, `loro-mirror`, or `crates/loro-wasm` for: (1) Choosing CRDT container types and document structure, (2) Designing sync, persistence, checkout, or history workflows, (3) Integrating rich-text editors and stable selections, (4) Mirroring app state with schemas and React, (5) Reasoning about versions, events, import status, or Inspector output, or (6) Maintaining the WASM binding layer."
4+
---
5+
6+
# Loro
7+
8+
Use this skill as the single entry point for all Loro work. Load one primary chapter first. Load a second chapter only when the task clearly crosses domains.
9+
10+
## Select A Chapter
11+
12+
- Read [references/topic-map.md](references/topic-map.md) if the task is broad and you need to route it.
13+
- Read [references/fit-and-architecture.md](references/fit-and-architecture.md) for CRDT fit, local-first framing, setup, and high-level architecture.
14+
- Read [references/containers-and-encoding.md](references/containers-and-encoding.md) for container choice, composition, encoding, persistence, shallow snapshots, and redaction.
15+
- Read [references/sync-versioning-and-events.md](references/sync-versioning-and-events.md) for sync flows, frontiers, version vectors, checkout, import status, timestamps, event timing, and Inspector.
16+
- Read [references/richtext-and-editors.md](references/richtext-and-editors.md) for `LoroText`, cursors, `applyDelta`, `updateByLine`, `loro-prosemirror`, Tiptap, and CodeMirror.
17+
- Read [references/mirror-and-react.md](references/mirror-and-react.md) for `loro-mirror`, `$cid`, `idSelector`, validation, selectors, and React integration.
18+
- Read [references/wasm-maintenance.md](references/wasm-maintenance.md) for `crates/loro-wasm`, `#[wasm_bindgen]`, pending-event flushing, wrapper decoration, and tests.
19+
- Read [references/performance-and-research.md](references/performance-and-research.md) for benchmarks, Eg-Walker tradeoffs, movable-tree context, rich-text design context, and project history.
20+
21+
## Route Common Tasks
22+
23+
- “Build a collaborative document model / choose data types / persist history”
24+
- Start with `containers-and-encoding.md`
25+
- Add `sync-versioning-and-events.md` if version/history behavior matters
26+
- “Debug checkout / detached mode / missing imports / event timing”
27+
- Start with `sync-versioning-and-events.md`
28+
- “Integrate ProseMirror, Tiptap, CodeMirror, or custom rich text”
29+
- Start with `richtext-and-editors.md`
30+
- Add `sync-versioning-and-events.md` if undo/version/event behavior matters
31+
- “Model app state with loro-mirror or loro-mirror-react”
32+
- Start with `mirror-and-react.md`
33+
- Add `containers-and-encoding.md` if schema semantics depend on container choice
34+
- “Change wasm bindings or debug pending event flushing”
35+
- Start with `wasm-maintenance.md`
36+
- “Decide whether Loro is even the right tool / explain tradeoffs”
37+
- Start with `fit-and-architecture.md`
38+
- Add `performance-and-research.md` if benchmark or research context matters
39+
40+
## Execute The Task
41+
42+
1. Classify the task before reading everything.
43+
2. Load one primary chapter.
44+
3. Load at most one secondary chapter for cross-domain work.
45+
4. Keep solutions grounded in Loro semantics:
46+
- choose data types by merge behavior,
47+
- distinguish state version from history version,
48+
- keep ephemeral state out of persisted CRDT data,
49+
- respect the binding/runtime invariants in `crates/loro-wasm`.
50+
51+
## Keep Guardrails
52+
53+
- Do not assume CRDTs are the right fit for hard invariants, exclusivity, or authorization-at-write-time problems.
54+
- Do not model editable text as plain strings when user intent requires merged edits.
55+
- Do not reuse peer IDs across concurrent sessions.
56+
- Do not confuse detached documents with detached containers.
57+
- Do not change wasm-exposed mutators without checking pending-event flushing behavior.

skills/loro/agents/openai.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
interface:
2+
display_name: "Loro"
3+
short_description: "Complete Loro guide for modeling, sync, editors, and wasm"
4+
default_prompt: "Use $loro to solve a Loro modeling, sync, editor, mirror, versioning, or wasm-maintenance task."
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Containers And Encoding
2+
3+
## Container Choice
4+
5+
- `LoroMap`: LWW key-value storage. Good for object-like state, coordinates, metadata, URLs, and fields where overwriting is preferable to merging.
6+
- `LoroList`: ordered sequence with insert/delete semantics.
7+
- `LoroMovableList`: ordered sequence with native move and set semantics. Prefer for drag-and-drop or reorder-heavy UIs.
8+
- `LoroTree`: hierarchical moves plus node-local data maps. Prefer for outlines, layers, nested blocks, or parent-child structures.
9+
- `LoroCounter`: additive numeric CRDT. Use when concurrent increments/decrements must accumulate.
10+
- `LoroText`: collaborative text. Use `updateByLine(...)` when line-oriented reconciliation is preferable to raw whole-string replacement.
11+
12+
## Behavioral Notes
13+
14+
- `LoroMap` compares concurrent writes by logical time and retains the winning value.
15+
- Setting a map entry to the same value is a no-op, so it does not create history.
16+
- `LoroList` and `LoroMovableList` both support stable cursors; use them when positions must survive concurrent edits.
17+
18+
## Choice Pitfalls
19+
20+
- Do not store editable prose as a plain string in a map unless LWW semantics are desired.
21+
- Do not model coordinates as `[x, y]` in a list. Concurrent delete+insert can create invalid arrays. Use a map.
22+
- Do not model arbitrary graphs directly. Loro documents compose as trees, not DAGs with shared children.
23+
24+
## Container States And Composition
25+
26+
- Detached containers come from constructors like `new LoroMap()`.
27+
- Attached containers belong to a document and have stable `ContainerID`s.
28+
- Adding a detached container to a document returns an attached version; the original object remains detached.
29+
- Root containers come from `doc.getMap(...)`, `doc.getText(...)`, `doc.getList(...)`, `doc.getTree(...)`, and so on.
30+
- Use `setContainer(...)` and `insertContainer(...)` for nesting, not plain `set(...)` or `insert(...)`.
31+
32+
## Container ID And Overwrite Hazards
33+
34+
- Root container IDs derive from root name plus type.
35+
- Child container IDs derive from the operation that created them.
36+
- Avoid concurrent creation of different child containers under the same map key. Container IDs differ, so one branch can appear overwritten.
37+
38+
## Tree Specifics
39+
40+
- `LoroTree` gives each node an associated `Map` container via `node.data`.
41+
- Fractional indices order siblings. Use them when child ordering matters.
42+
- When many peers insert at the same sibling position, peer ID acts as a tiebreaker for equal fractional indices.
43+
- Fractional index jitter reduces collisions at some encoding-size cost.
44+
- `getNodes(...)` and node JSON views are useful when a flat inspection of the forest is easier than recursive traversal.
45+
46+
## List Vs Movable List
47+
48+
- Use `LoroList` when delete+insert is acceptable and native move identity is unnecessary.
49+
- Use `LoroMovableList` for kanban columns, cards, playlist reordering, or any UX where “move this item” is semantically distinct from delete+insert.
50+
51+
## Counter
52+
53+
- `Counter` aggregates applied values from all peers.
54+
- It is the right choice for additive metrics, not for values with hard invariants.
55+
56+
## Export Modes
57+
58+
- `update`: sync delta payloads.
59+
- `updates-in-range`: bounded history export.
60+
- `snapshot`: full state checkpoint.
61+
- `shallow-snapshot`: current state plus truncated history.
62+
63+
## Import Choices
64+
65+
- `import(...)`: one payload at a time.
66+
- `importBatch(...)`: preferred for multiple updates or mixed snapshot/update payloads because diffing and event emission are coalesced.
67+
- Imports can succeed partially when dependencies are missing. Pending ranges mean the document knows about those operations but cannot apply them yet.
68+
69+
## Persistence Pattern
70+
71+
1. Periodically save a snapshot.
72+
2. Frequently persist updates, often after each local edit or on a debounce.
73+
3. On load, import the snapshot and outstanding updates.
74+
4. Recompact by exporting a fresh snapshot and deleting replayed updates.
75+
76+
## Shallow Snapshots And Redaction
77+
78+
- Use shallow snapshots when old history can be archived or discarded.
79+
- They are often much smaller than full snapshots.
80+
- Peers can only sync if they have versions after the shallow start point.
81+
- Archive full history before trimming if old history may still matter.
82+
- Redaction replaces sensitive payloads while preserving enough structure for future synchronization.
83+
- If old peers still hold unsanitized history, sensitive data still exists there.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Fit And Architecture
2+
3+
## When Loro Fits
4+
5+
- Collaborative text and structured documents.
6+
- Offline-first applications that later synchronize.
7+
- Multi-device sync where eventual consistency is acceptable.
8+
- Apps that benefit from complete history, time travel, or version checkpoints.
9+
10+
## When Loro Does Not Fit By Itself
11+
12+
- Financial or accounting invariants.
13+
- Exclusive ownership or booking/locking semantics.
14+
- Authorization decisions that must be enforced at write time.
15+
- Arbitrary graph-shaped or non-JSON-like data without an adaptation layer.
16+
17+
## Conceptual Model
18+
19+
- Loro is a CRDT framework for local-first apps.
20+
- It trades strict coordination for strong eventual consistency.
21+
- Under the hood it mixes Fugue-style correctness with Eg-Walker-inspired replay and simple local indexing.
22+
23+
## Document Shape Constraints
24+
25+
- Loro documents are JSON-like.
26+
- Map keys are strings.
27+
- The composed document structure is tree-shaped, not a general graph with shared children.
28+
29+
## Entry Points
30+
31+
- JS/TS: `loro-crdt`
32+
- Rust: `loro`
33+
- Swift: `loro-swift`
34+
- Python: `loro-py`
35+
- Ecosystem integrations include editor bindings, state-mirroring layers, and Inspector.
36+
37+
## Setup Notes
38+
39+
- In JS frontends, install `loro-crdt`.
40+
- In Vite-based apps, WASM support and top-level-await handling must be configured correctly.
41+
- Inspector is the quickest interactive tool for browsing state and history during development.
42+
43+
## Read Order Inside This Skill
44+
45+
1. Start here for fit and high-level tradeoffs.
46+
2. Switch to `containers-and-encoding.md` for concrete data types and storage choices.
47+
3. Switch to `sync-versioning-and-events.md` for history, events, and sync state.
48+
4. Switch to `richtext-and-editors.md` or `mirror-and-react.md` for integrations.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Mirror And React
2+
3+
## Mirror Intent
4+
5+
- `loro-mirror` keeps an immutable app-state view in sync with a `LoroDoc`.
6+
- Local `setState(...)` edits become granular CRDT operations.
7+
- Remote CRDT events patch the mirrored app state back in.
8+
- The common mental model is still “immutable app state plus typed actions”, not direct mutation of raw CRDT containers everywhere.
9+
10+
## Root Schema
11+
12+
- Build the root with `schema({...})`.
13+
- Keep root fields as Loro containers: `LoroMap`, `LoroList`, `LoroMovableList`, `LoroText`.
14+
- Put primitives inside those containers, not at the root.
15+
16+
## Field Types
17+
18+
- `schema.String`, `schema.Number`, `schema.Boolean`: concrete primitive fields.
19+
- `schema.Any`: dynamic JSON-like payload when the shape is truly unknown.
20+
- `schema.Ignore`: local-only or derived fields that should not sync to Loro.
21+
22+
## Maps, Lists, And `$cid`
23+
24+
- `schema.LoroMap({...})`: nested object container.
25+
- `schema.LoroMap(...).catchall(valueSchema)`: add dynamic keys while preserving known keys.
26+
- `schema.LoroMapRecord(valueSchema)`: homogeneous record-like map.
27+
- Every mirrored `LoroMap` includes a read-only `$cid`.
28+
- `$cid` matches the underlying Loro container ID.
29+
- Use `$cid` as a stable key or default `idSelector`, especially for list items.
30+
- Never try to persist or mutate `$cid` from app code.
31+
- Do not invent a `withCid`-style option; mirrored map values already receive `$cid` automatically.
32+
33+
## Lists
34+
35+
- `LoroList(itemSchema, idSelector?)`: ordered collection. Add `idSelector` for stable add/remove/update/move diffs.
36+
- `LoroMovableList(itemSchema, idSelector)`: native move semantics, ideal for drag-and-drop.
37+
- If a list item is a map, `(item) => item.$cid` is often the right `idSelector`.
38+
39+
## Defaults And Validation
40+
41+
- Explicit `defaultValue` wins.
42+
- Required fields without explicit defaults fall back to built-in defaults.
43+
- `validateUpdates` is enabled by default in `Mirror`. Keep it on unless a measured hot path proves otherwise.
44+
- Use `validateSchema(...)` when you need an explicit validation pass during migration or debugging.
45+
46+
## React Patterns
47+
48+
- Create a stable `LoroDoc` instance.
49+
- Create helpers through `createLoroContext(schema)` or `useLoroStore(...)`.
50+
- Wrap the tree in `LoroProvider` when using the context pattern.
51+
- Use the narrowest hook that solves the task:
52+
- `useLoroState` for the full mirrored state
53+
- `useLoroSelector` for focused subscriptions
54+
- `useLoroAction` for write paths
55+
- Keep the provider `doc` stable across renders.
56+
- Prefer `useLoroSelector` over pulling the full state into every component.
57+
- Use mirrored list item `$cid` values as React keys.
58+
59+
## Practical Mental Model
60+
61+
- Subscribe to order at collection boundaries.
62+
- Subscribe to content at item boundaries.
63+
- Pass `$cid`s downward and reselect locally instead of passing large mirrored objects through the whole tree.
64+
- Keep view-local state outside the mirror unless collaborators must share it.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Performance And Research
2+
3+
## Performance Interpretation
4+
5+
- Treat benchmarks as tradeoff indicators, not universal rankings.
6+
- Compare:
7+
- encode/decode cost
8+
- parse time
9+
- document/update size
10+
- memory footprint
11+
- conflict-heavy vs low-conflict workloads
12+
13+
## Main Performance Topics
14+
15+
- Benchmark framing and methodology.
16+
- Encoded document size comparisons and shallow snapshot savings.
17+
- Native Rust benchmark context.
18+
19+
## Main Concept Topic
20+
21+
- Event-graph replay explains why Loro can keep local operations simple while still merging remote history efficiently.
22+
23+
## Stored Knowledge Threads
24+
25+
- Stable encoding and import/export speedups.
26+
- Movable tree algorithm, unsafe moves, and fractional index tradeoffs.
27+
- Rich text design, style anchors, overlap, and expansion behavior.
28+
- Peritext/Fugue background for rich text intent preservation.
29+
- Mirror motivation, complexity model, and state-to-CRDT mapping.
30+
- Local-first product vision and project history.
31+
32+
## Practical Rule
33+
34+
- Reach for these topics when the user asks “why does Loro work this way?” or “what tradeoff is Loro making?”.
35+
- Do not dump benchmark tables into product recommendations without first matching them to the workload.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Rich Text And Editors
2+
3+
## Rich Text Rules
4+
5+
- Use `LoroText` for collaboratively edited text.
6+
- Keep rich text style config in sync across peers:
7+
- `after`
8+
- `before`
9+
- `none`
10+
- `both`
11+
- If two peers use different `configTextStyle(...)`, they can interpret the same boundary insert differently.
12+
13+
## Text API Notes
14+
15+
- `update(...)` rewrites based on a target text snapshot.
16+
- `updateByLine(...)` is useful when line-oriented reconciliation is more appropriate than raw whole-string replacement.
17+
- `toDelta()` preserves marks and annotations.
18+
- `toJSON()` / `toString()` return plain text only.
19+
20+
## `applyDelta(...)` Caveat
21+
22+
- `applyDelta(...)` is ideal for editor bindings because it matches event diffs.
23+
- Delta inserts must include the full attribute set of the inserted range.
24+
- If CRDT inheritance would add marks but the delta omits them, `applyDelta(...)` removes those attributes.
25+
- Out-of-range formatting can cause Loro to materialize newlines to satisfy editor assumptions.
26+
27+
## Stable Selections
28+
29+
- Use `text.getCursor(...)` to create stable positions.
30+
- Use two cursors for selections: anchor and head.
31+
- Resolve live offsets with `doc.getCursorPos(...)`.
32+
- Persist updated cursors returned from resolution to reduce replay cost.
33+
- WASM text offsets are UTF-16 indices.
34+
35+
## ProseMirror And Tiptap
36+
37+
- Start with `loro-prosemirror` for ProseMirror and Tiptap.
38+
- It already covers document sync, collaborative undo and redo, and cursor presence.
39+
- The current baseline is `CursorEphemeralStore` plus `LoroEphemeralCursorPlugin`.
40+
- Give each editor instance its own `containerId` when several editors share one `LoroDoc`.
41+
- Reuse the same container only if several views intentionally co-edit the exact same content.
42+
43+
## CodeMirror
44+
45+
- Use the official CodeMirror integration when the editor is CM6-based.
46+
- Keep awareness/presence, undo, and document sync at the Loro layer rather than inventing a separate editor-level protocol.
47+
48+
## Rich Text Internals
49+
50+
- Loro rich text uses style anchors to represent mark boundaries.
51+
- Overlap and expansion behavior are first-class concerns.
52+
- Overlappable styles are often best modeled by unique keys with a shared prefix pattern such as `comment:alice` and `comment:bob`.
53+
54+
## Guardrails
55+
56+
- Do not model editable prose as `string` in a `LoroMap`.
57+
- Do not store collaborative selections as plain indices.
58+
- Do not mix old and new ProseMirror cursor APIs accidentally; inspect the package version and current codebase first.

0 commit comments

Comments
 (0)