pub struct Card { /* private fields */ }Expand description
A single card-yaml block (root or composable). body is the content
(Content) form of the prose after the closing fence: the empty content
when none follows; check card.body().is_blank(). Markdown is a projection:
Card::body_markdown re-emits it.
Implementations§
Source§impl Card
impl Card
Sourcepub fn new(kind: impl Into<String>) -> Result<Self, EditError>
pub fn new(kind: impl Into<String>) -> Result<Self, EditError>
Create a composable card with the given kind, no fields, and an empty body.
Sourcepub fn store_field(
&mut self,
name: &str,
value: impl Into<QuillValue>,
) -> Result<(), EditError>
pub fn store_field( &mut self, name: &str, value: impl Into<QuillValue>, ) -> Result<(), EditError>
Store a payload field verbatim, clearing any !must_fill marker on that
key, the opaque store (store = verbatim, coercion deferred to render;
contrast the typed TypedWriter::set). Scalars
convert in place (store_field("qty", 3)); see the From impls on
QuillValue.
Returns EditError::InvalidFieldName when name does not match
[A-Za-z_][A-Za-z0-9_]*.
Sourcepub fn store_fill(
&mut self,
name: &str,
value: impl Into<QuillValue>,
) -> Result<(), EditError>
pub fn store_fill( &mut self, name: &str, value: impl Into<QuillValue>, ) -> Result<(), EditError>
Store a payload field verbatim and mark it as a !must_fill placeholder.
Null emits as key: !must_fill; scalars/sequences as key: !must_fill <value>.
The opaque store’s fill variant (quill-free, verbatim); same validation as
Card::store_field.
Sourcepub fn store_fields<K, V, I>(
&mut self,
fields: I,
) -> Result<(), Vec<(String, EditError)>>
pub fn store_fields<K, V, I>( &mut self, fields: I, ) -> Result<(), Vec<(String, EditError)>>
Store several payload fields verbatim and atomically, clearing any
!must_fill marker on each key, the opaque store’s batch (contrast the
typed TypedWriter::set_all). The whole
batch is validated first: on any violation nothing is applied and every
offending field is reported as a (name, error) pair, so a caller feeding
externally-sourced names (database columns, form keys) sees all violations
in one pass instead of fix-rerun-repeat. Per-field rules are those of
Card::store_field; insertion order follows the iterator, and a
repeated name behaves like repeated store_field calls (last value
wins, first position kept).
Sourcepub fn remove_field(
&mut self,
name: &str,
) -> Result<Option<QuillValue>, EditError>
pub fn remove_field( &mut self, name: &str, ) -> Result<Option<QuillValue>, EditError>
Remove a payload field; returns Ok(None) if the name is absent.
Removal has no lane: the one verb serves every write path. Same
validation as Card::store_field.
Sourcepub fn store_ext(&mut self, value: Map<String, Value>) -> Result<(), EditError>
pub fn store_ext(&mut self, value: Map<String, Value>) -> Result<(), EditError>
Replace the card’s opaque $ext map wholesale, inserting it at the
canonical position (after $quill/$kind, before user fields)
when none existed. Passing an empty map records an explicit $ext: {}.
$ext carries out-of-band consumer state (editor renames, agent
annotations, …) and is stripped from Document::to_plate_json, so a
write here can never affect a render. Any nested comments attached to a
replaced $ext are dropped.
Returns EditError::ValueTooDeep when the map nests past the §8
depth limit: $ext never reaches the plate JSON, but it does flow
through the recursive emit and DTO paths, so it carries the same
depth bound as user fields.
Quill-free and never coerced: an opaque store_* verb by the vocabulary
rule, not a typed set.
Sourcepub fn remove_ext(&mut self) -> Option<Map<String, Value>>
pub fn remove_ext(&mut self) -> Option<Map<String, Value>>
Remove the card’s $ext map entirely, returning the previous map if
present. This is a blunt escape hatch: it discards every namespace
($ext.editor, $ext.agent, …) at once. To clear consumer
state, prefer Card::remove_ext_namespace, which drops only your
own slot and leaves sibling consumers’ state intact.
Sourcepub fn store_ext_namespace(
&mut self,
namespace: impl Into<String>,
value: Value,
) -> Result<(), EditError>
pub fn store_ext_namespace( &mut self, namespace: impl Into<String>, value: Value, ) -> Result<(), EditError>
Merge value into the card’s $ext map under namespace, creating
the map when absent and replacing any existing value at that key.
This is the recommended way to write $ext: it preserves sibling
namespaces, so independent consumers keying on their own slot
($ext.editor, $ext.agent, …) don’t clobber each other.
Returns EditError::ValueTooDeep when the merged map nests past
the §8 depth limit (see Card::store_ext); the card’s $ext is
unchanged on error. Quill-free and never coerced: an opaque store_*
verb.
Sourcepub fn remove_ext_namespace(&mut self, namespace: &str) -> Option<Value>
pub fn remove_ext_namespace(&mut self, namespace: &str) -> Option<Value>
Remove namespace from the card’s $ext map, returning the value
that was stored there (or None when the map or the key was absent).
This is the recommended way to clear $ext state: it is the
namespace-scoped inverse of Card::store_ext_namespace and preserves
sibling namespaces, where Card::remove_ext would wipe them all.
When removing the last namespace empties the map, the $ext entry is
dropped entirely (not left as $ext: {}), so
store_ext_namespace(ns, v) followed by remove_ext_namespace(ns)
restores a card that had no $ext to its original state.
Sourcepub fn seed(&self) -> Option<&Map<String, Value>>
pub fn seed(&self) -> Option<&Map<String, Value>>
The raw $seed map (keyed by card-kind), or None. For a parsed,
per-kind overlay, index this map by kind and pass the entry to
crate::SeedOverlay::from_json. Only the main card carries $seed.
Sourcepub fn store_seed_overlay(
&mut self,
card_kind: impl Into<String>,
value: Value,
) -> Result<(), EditError>
pub fn store_seed_overlay( &mut self, card_kind: impl Into<String>, value: Value, ) -> Result<(), EditError>
Merge a card-kind’s seed overlay value into the card’s $seed map
under card_kind, creating the map when absent and replacing any
existing overlay for that kind. Sibling kinds are preserved: this is
the per-kind-safe writer, the seed analogue of
Card::store_ext_namespace. card_kind must be a valid, non-reserved
composable kind (EditError::InvalidKindName / EditError::ReservedKind
otherwise): $seed is keyed by composable card-kind, unlike the
free-form namespaces of $ext. Returns EditError::ValueTooDeep when
the merged map nests past the §8 depth limit; the card is unchanged on
error. Quill-free and never coerced: an opaque store_* verb.
Sourcepub fn remove_seed_overlay(&mut self, card_kind: &str) -> Option<Value>
pub fn remove_seed_overlay(&mut self, card_kind: &str) -> Option<Value>
Remove card_kind from the card’s $seed map, returning the overlay
stored there (or None). When removing the last kind empties the map,
the $seed entry is dropped entirely (not left as $seed: {}).
The seed analogue of Card::remove_ext_namespace.
Sourcepub fn overwrite_body(&mut self, content: Content)
pub fn overwrite_body(&mut self, content: Content)
Overwrite the body with a pre-built Content: value semantics, the
native content writer, and the bottom rung of the content lane’s ladder
by anchor fate: overwrite destroys, revise_body
rebases, apply_body_change preserves. A
content is valid by construction, so this is infallible: no markdown
import, no diff, no schema check; the identity anchors of the previous
body are gone (write-this-exact-value, so a to_markdown → overwrite
round-trip cannot resurrect them). Use it when the caller already holds a
content (a decoded canonical-JSON body, another field’s value, an
editor’s serialized state). Cold-importing markdown is spelled
overwrite_body(import_body(md)?) at the call site, where the anchor
loss is visible.
Sourcepub fn overwrite_field(
&mut self,
name: &str,
content: Content,
) -> Result<(), EditError>
pub fn overwrite_field( &mut self, name: &str, content: Content, ) -> Result<(), EditError>
Overwrite a content field’s value with a pre-built Content: the
field-level twin of overwrite_body. Value
semantics: stores the canonical content JSON verbatim (identity marks and
content-only marks such as underline intact), no diff, no schema check
(schema-blind, like apply_field_change;
commit_field is the typed door). The previous
value’s anchors are gone; the incoming content’s ride along exactly.
Returns EditError::InvalidFieldName for a malformed name.
Richtext codec. A plaintext field rests as its literal string, so an
object landed here is off that field’s resting form: a repairable
departure the next bound load (Quill::conform)
converges. Write one through the typed door.
Sourcepub fn revise_body(
&mut self,
body: impl Into<String>,
) -> Result<Delta, EditError>
pub fn revise_body( &mut self, body: impl Into<String>, ) -> Result<Delta, EditError>
Revise the body from an authored markdown string: edit semantics,
the whole-document (stale-text / LLM / MCP) writer, and the receipt-
returning default write path. Imports the markdown, diffs it against the
current body, and rebases surviving identity anchors onto the new text
(cold import + diff_import), then returns the text Delta from the
old body to the new one: the change an editor bridge maps its own
positions through across a whole-document replace (Delta::map_pos).
Surviving identity anchors rebase; formatting marks are re-derived by the
fresh import. A pathologically over-nested input (> MAX_NESTING_DEPTH)
returns EditError::Import rather than silently degrading to the
empty content. Discard the receipt with let _ = card.revise_body(md)?;
when caret stability is not needed.
Sourcepub fn revise_field(
&mut self,
name: &str,
body: impl Into<String>,
) -> Result<Delta, EditError>
pub fn revise_field( &mut self, name: &str, body: impl Into<String>, ) -> Result<Delta, EditError>
Revise a richtext field from an authored markdown string: the
field-level twin of revise_body, and the
field-level diff_import. The other field-content writers are the cold
commit_field and the splice
apply_field_change, so this
is the anchor-preserving path for rewriting a richtext field’s markdown
wholesale. Decodes the field’s current content as the diff base (an absent
field cold-imports from empty), rebases surviving anchors onto the new
text, re-stores the canonical content, and returns the text Delta.
Schema-blind by design: the content-writer stratum splices without the
quill (like apply_field_change);
commit_field is the typed door that enforces
richtext(inline), and a violation otherwise surfaces at validate/render.
Richtext only, and the exclusion bites: this decodes the current value
as markdown, which eats a plaintext field’s escapes (a \*b\* commits
back as a *b*) and leaves a content object where that field’s rest is a
string. The typed
TypedWriter::revise_field resolves
the codec from the schema and is the plaintext-safe door.
Returns EditError::InvalidFieldName for a malformed name,
EditError::FieldDecode when the field is present but is not a
richtext content (a scalar a store_field wrote), and
EditError::Import on an over-nested markdown input.
Sourcepub fn apply_body_change(
&mut self,
bundle: &ChangeBundle,
) -> Result<(), EditError>
pub fn apply_body_change( &mut self, bundle: &ChangeBundle, ) -> Result<(), EditError>
Apply a committed field-change bundle to the body content: the native
form-editor writer. Order is text delta → island ops → line ops → mark
ops, then one terminal normalization (Content::apply_field_change);
mark ranges are in final-text coordinates. Returns
EditError::ContentApply when an op is out of bounds; the apply is
all-or-nothing (Content::apply_field_change), so the body is
unchanged on error: apply the bundle against the body the delta was
computed from.
Sourcepub fn apply_field_change(
&mut self,
name: &str,
bundle: &ChangeBundle,
) -> Result<(), EditError>
pub fn apply_field_change( &mut self, name: &str, bundle: &ChangeBundle, ) -> Result<(), EditError>
Splice a content field-change bundle into a field’s stored content: the
field-path twin of apply_body_change, and
what lets identity marks (anchors, island ids) persist on field content
across incremental edits. Decodes the field’s canonical content, applies
the text delta plus any island/line/mark ops in the same all-or-nothing
bundle, and re-stores the canonical result.
An absent field splices against the empty content, as
revise_field diffs against it. A bundle that
expected content still fails: its text delta declares the base length it
was computed against, so only a zero-base bundle lands, and a zero-base
bundle is one its producer computed against an empty field.
Returns EditError::InvalidFieldName for a malformed name,
EditError::FieldDecode when the stored value is not a content (the
caller addresses a field it knows is content-typed, exactly as when
writing it), and EditError::ContentApply when the bundle applies out
of bounds.
Richtext codec: schema-blind like revise_field,
so a plaintext field’s stored string decodes here as markdown and a
splice lands it off its resting form. The next bound load converges it.
Source§impl Card
impl Card
pub fn quill(&self) -> Option<&QuillReference>
pub fn kind(&self) -> Option<&str>
Sourcepub fn ext(&self) -> Option<&Map<String, Value>>
pub fn ext(&self) -> Option<&Map<String, Value>>
Opaque $ext map for out-of-band extension data (UI editor state,
agent annotations, …). Carried through Markdown and storage DTO
round-trips; never emitted into the plate JSON consumed by
backends.
Sourcepub fn payload(&self) -> &Payload
pub fn payload(&self) -> &Payload
The card’s card-yaml storage as a read view. Writes go through the
enforcing verbs (store_field,
TypedWriter), not through here.
Sourcepub fn body(&self) -> &Content
pub fn body(&self) -> &Content
The card body as a Content content: the canonical content model.
For the markdown projection use Card::body_markdown.
Sourcepub fn body_markdown(&self) -> String
pub fn body_markdown(&self) -> String
The card body rendered back to its markdown projection. This is a
derived view (export ∘ body), not stored state; a Document round-trip
therefore canonicalizes the body (e.g. __b__ → **b**).