session 1 commit 3/4: CrdtOp moved to pmacs-protocol

CrdtOp { peer_id: u64, bytes: Vec<u8> } moves from src/rope.rs to
pmacs-protocol::crdt. The type is unconditional (not #[cfg]-gated),
matching the original's 'always present to avoid feature-flag
proliferation through every Edit consumer' decision: the parent
pmacs crate's 'crdt' feature gates loro and op application, not
wire shape.

Removed the unused 'crdt' feature stub I'd added to
pmacs-protocol/Cargo.toml at session start; nothing in pmacs-protocol
needs it.

src/rope.rs adds 'pub use pmacs_protocol::CrdtOp;' so existing
crate::rope::CrdtOp imports keep resolving.

Lib gate: still 1314 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-05-20 09:32:09 -04:00
parent 2c04102aad
commit 5ffc47aa33
4 changed files with 57 additions and 38 deletions

View File

@ -32,13 +32,11 @@ cast_precision_loss = "allow"
similar_names = "allow" similar_names = "allow"
multiple_crate_versions = "allow" multiple_crate_versions = "allow"
[features] # No features — the wire type set is unconditional. `CrdtOp` is
# Mirrors the `crdt` feature on the parent `pmacs` crate. When enabled, # always compiled (matching the original `pmacs::rope::CrdtOp`'s
# the `InstanceMessage::CrdtOp` / `FrontendEvent::CrdtOp` variants exist # "not `#[cfg]`-gated to avoid feature-flag proliferation through
# and the wire-level `CrdtOp` struct is in scope. When disabled, those # every Edit consumer" decision); the parent `pmacs` crate's `crdt`
# variants are compiled out so v0.1-era binaries built without `crdt` # feature gates loro and op-application, not wire shape.
# match the pre-v1.0 wire surface byte-for-byte.
crdt = []
[dependencies] [dependencies]
serde = { workspace = true } serde = { workspace = true }

View File

@ -0,0 +1,44 @@
//! `CrdtOp` — moved from `pmacs::rope` in session 1 of the `pmacs-gpu`
//! arc. Carried on the `InstanceMessage::CrdtOp` /
//! `FrontendEvent::CrdtOp` wire variants when an attached session
//! negotiated `crdt_replica: true`.
//!
//! The type is unconditional (not `#[cfg]`-gated) — the comment on
//! the original `pmacs::rope::CrdtOp` explained why: "Always present
//! (not `#[cfg]`-gated) to avoid feature-flag proliferation through
//! every Edit consumer." Keeping the same shape here. The `crdt`
//! feature on the parent `pmacs` crate gates loro and the actual
//! application of CRDT ops; the wire-type definition stays compiled
//! unconditionally so consumers (`pmacs-gpu`, debug tools) don't have
//! to mirror the feature flag to handle a wire-level variant they
//! may never see.
/// T M10.2 Day 3: CRDT-op metadata carried by `Edit` in CRDT mode.
///
/// Two fields:
///
/// * `peer_id` — the producing-frontend identity. M10.4's per-frontend
/// undo reads this as the "is this op mine?" filter; saves the
/// consumer from parsing the op bytes to extract identity.
/// * `bytes` — wire-format serialization of the CRDT ops produced by
/// the originating edit, as returned by loro's
/// `ExportMode::updates_owned(pre_version)`. M10.5+ sends these
/// over the wire; receiving frontends import them via loro's
/// `import` to apply on their local CRDT.
///
/// Constructed by `Buffer::apply_edit` (and `undo` / `redo`) in CRDT
/// mode; rope's edit constructors set `Edit::crdt_op` to `None` and
/// the Buffer wraps after the rope returns.
///
/// T M10.5: serde derives added so this type can be the payload of
/// `InstanceMessage::CrdtOp` and `FrontendEvent::CrdtOp` on the wire.
/// `bytes` is opaque to the protocol layer — it's loro's incremental-
/// update format; the receiving end's `CrdtState::import_updates`
/// decodes it.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CrdtOp {
/// Producing frontend's identity (loro `PeerID`).
pub peer_id: u64,
/// Wire-format op bytes (loro `ExportMode::updates_owned` output).
pub bytes: Vec<u8>,
}

View File

@ -35,12 +35,14 @@
//! directly. //! directly.
pub mod cell; pub mod cell;
pub mod crdt;
pub mod ids; pub mod ids;
pub use cell::{ pub use cell::{
Attachment, Cell, CellCoord, CellSize, Color, DiffSpan, Glyph, Style, UnderlineStyle, Attachment, Cell, CellCoord, CellSize, Color, DiffSpan, Glyph, Style, UnderlineStyle,
}; };
pub use crdt::CrdtOp;
pub use ids::{BufferId, ByteRange, FrontendId, Position}; pub use ids::{BufferId, ByteRange, FrontendId, Position};
// The `SemanticFrame` family, top-level message envelopes, and the // The `SemanticFrame` family and the top-level message envelopes
// feature-gated `CrdtOp` follow in subsequent commits within this PR. // follow in the final commit within this PR.

View File

@ -330,35 +330,10 @@ pub struct Edit {
pub crdt_op: Option<Box<CrdtOp>>, pub crdt_op: Option<Box<CrdtOp>>,
} }
/// T M10.2 Day 3: CRDT-op metadata carried by [`Edit`] in CRDT mode. // `CrdtOp` moved to `pmacs-protocol::crdt` (session 1 of the
/// // `pmacs-gpu` arc — see `docs/pmacs-gpu-design.md`). Re-exported here
/// Two fields: // so existing `crate::rope::CrdtOp` import paths continue to resolve.
/// pub use pmacs_protocol::CrdtOp;
/// * `peer_id` — the producing-frontend identity. M10.4's per-frontend
/// undo reads this as the "is this op mine?" filter; saves the
/// consumer from parsing the op bytes to extract identity.
/// * `bytes` — wire-format serialization of the CRDT ops produced by
/// the originating edit, as returned by loro's
/// `ExportMode::updates_owned(pre_version)`. M10.5+ sends these
/// over the wire; receiving frontends import them via loro's
/// `import` to apply on their local CRDT.
///
/// Constructed by `Buffer::apply_edit` (and `undo` / `redo`) in CRDT
/// mode; rope's edit constructors set `Edit::crdt_op` to `None` and
/// the Buffer wraps after the rope returns.
///
/// T M10.5: serde derives added so this type can be the payload of
/// `InstanceMessage::CrdtOp` and `FrontendEvent::CrdtOp` on the wire.
/// `bytes` is opaque to the protocol layer — it's loro's incremental-
/// update format; the receiving end's `CrdtState::import_updates`
/// decodes it.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CrdtOp {
/// Producing frontend's identity (loro `PeerID`).
pub peer_id: u64,
/// Wire-format op bytes (loro `ExportMode::updates_owned` output).
pub bytes: Vec<u8>,
}
/// A half-open byte range `[start, end)` into a rope. /// A half-open byte range `[start, end)` into a rope.
/// ///