authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-10 21:08:05-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-10 21:08:05-07:00
logad10b477304448e0310a90215fdfab07b4dd337f
tree5bfa141e286035a81bb9dc5693b8eb4b15b3babb
parentde01c779539ae34b40906112aa468ef3a52979fa
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

refactor: expose active-revision, page and parent accessors

Add Space::active, Document::{active, pages_in}, Revision::parents and RevisionIndex::{active, resolve_active}, replacing the hand-rolled default- context lookups, page filters and parent walks across the core, offline, notebook and SMB crates. Derive Clone and PartialEq for Layout, Format and Tag and read table-cell indents (0x1c001c12), matching the canvas branch. Validation: 298 workspace tests, Clippy, fmt and the fuzz workspace build. Assisted-by: claude-fable-5.1

22 files changed, 161 insertions(+), 269 deletions(-)

crates/onestore-notebook/src/lib.rs+2-14
...@@ -2,7 +2,7 @@...@@ -2,7 +2,7 @@
2#![doc = include_str!("../README.md")]2#![doc = include_str!("../README.md")]
33
4use onestore::{4use onestore::{
5 ExGuid, FileType, RevisionIndex, Store,5 FileType, RevisionIndex, Store,
6 document::{Document, Kind},6 document::{Document, Kind},
7};7};
8use serde::Serialize;8use serde::Serialize;
...@@ -251,19 +251,7 @@ fn scan(...@@ -251,19 +251,7 @@ fn scan(
251 let parsed = (|| {251 let parsed = (|| {
252 let index = RevisionIndex::parse(&store)?;252 let index = RevisionIndex::parse(&store)?;
253 let document = Document::parse(&index)?;253 let document = Document::parse(&index)?;
254 let revision = document254 let revision = document.active(document.root)?;
255 .spaces
256 .get(&document.root)
257 .and_then(|space| {
258 space
259 .contexts
260 .get(&ExGuid::default())
261 .and_then(|id| space.revisions.get(id))
262 })
263 .ok_or(onestore::Error {
264 offset: 0,
265 message: "Missing active notebook root revision",
266 })?;
267 if expected == FileType::Section {255 if expected == FileType::Section {
268 if revision256 if revision
269 .roots257 .roots
crates/onestore-offline/src/formatting.rs+1-2
...@@ -68,8 +68,7 @@ fn observe(...@@ -68,8 +68,7 @@ fn observe(
68 let store = Store::parse(source)?;68 let store = Store::parse(source)?;
69 let index = RevisionIndex::parse(&store)?;69 let index = RevisionIndex::parse(&store)?;
70 let document = Document::parse(&index)?;70 let document = Document::parse(&index)?;
71 let space = &document.spaces[&space];71 let revision = document.active(space)?;
72 let revision = &space.revisions[&space.contexts[&ExGuid::default()]];
73 let mut spans: Vec<Span> = Vec::new();72 let mut spans: Vec<Span> = Vec::new();
74 let mut start = 0;73 let mut start = 0;
75 for run in revision.text_runs(object)? {74 for run in revision.text_runs(object)? {
crates/onestore-offline/src/lib.rs+4-28
...@@ -305,29 +305,11 @@ fn active_paths(...@@ -305,29 +305,11 @@ fn active_paths(
305 pages: &[ExGuid],305 pages: &[ExGuid],
306 objects: &[ExGuid],306 objects: &[ExGuid],
307) -> Option<Vec<Vec<ExGuid>>> {307) -> Option<Vec<Vec<ExGuid>>> {
308 use std::collections::{BTreeMap, BTreeSet};308 let parents = view.parents(pages).ok()?;
309 let mut pending = pages.to_vec();
310 let mut seen = BTreeSet::new();
311 let mut parents = BTreeMap::<_, Vec<_>>::new();
312 while let Some(id) = pending.pop() {
313 if !seen.insert(id) {
314 continue;
315 }
316 let node = view.nodes.get(&id)?;
317 for child in node
318 .children
319 .iter()
320 .chain(&node.content)
321 .chain(&node.structure)
322 {
323 parents.entry(*child).or_default().push(id);
324 pending.push(*child);
325 }
326 }
327 objects309 objects
328 .iter()310 .iter()
329 .map(|object| {311 .map(|object| {
330 if !seen.contains(object) {312 if !parents.contains_key(object) && !pages.contains(object) {
331 return None;313 return None;
332 }314 }
333 let mut path = Vec::new();315 let mut path = Vec::new();
...@@ -352,14 +334,8 @@ fn paragraph(source: &[u8], space: ExGuid, object: ExGuid) -> Result<Option<Stri...@@ -352,14 +334,8 @@ fn paragraph(source: &[u8], space: ExGuid, object: ExGuid) -> Result<Option<Stri
352 let index = RevisionIndex::parse(&store)?;334 let index = RevisionIndex::parse(&store)?;
353 let document = Document::parse(&index)?;335 let document = Document::parse(&index)?;
354 let node = document336 let node = document
355 .spaces337 .active(space)
356 .get(&space)338 .ok()
357 .and_then(|space| {
358 space
359 .contexts
360 .get(&ExGuid::default())
361 .and_then(|revision| space.revisions.get(revision))
362 })
363 .and_then(|revision| revision.nodes.get(&object));339 .and_then(|revision| revision.nodes.get(&object));
364 Ok(match node.map(|node| &node.kind) {340 Ok(match node.map(|node| &node.kind) {
365 Some(Kind::RichText { text, .. }) => Some(text.clone()),341 Some(Kind::RichText { text, .. }) => Some(text.clone()),
crates/onestore-offline/src/outline.rs+2-13
...@@ -27,21 +27,10 @@ fn observe(...@@ -27,21 +27,10 @@ fn observe(
27 let store = Store::parse(source)?;27 let store = Store::parse(source)?;
28 let index = RevisionIndex::parse(&store)?;28 let index = RevisionIndex::parse(&store)?;
29 let document = Document::parse(&index)?;29 let document = Document::parse(&index)?;
30 let Some(space_view) = document.spaces.get(&space) else {30 let Ok(view) = document.active(space) else {
31 return Ok(None);31 return Ok(None);
32 };32 };
33 let Some(view) = space_view33 let pages = document.pages_in(space)?;
34 .contexts
35 .get(&ExGuid::default())
36 .and_then(|rid| space_view.revisions.get(rid))
37 else {
38 return Ok(None);
39 };
40 let pages: Vec<_> = document
41 .pages()?
42 .into_iter()
43 .filter_map(|(sid, page)| (sid == space).then_some(page))
44 .collect();
45 if pages.len() != 1 {34 if pages.len() != 1 {
46 return Ok(None);35 return Ok(None);
47 }36 }
crates/onestore-offline/src/pages.rs+1-2
...@@ -19,8 +19,7 @@ fn observe(source: &[u8]) -> Result<Option<(ExGuid, PageOrder)>> {...@@ -19,8 +19,7 @@ fn observe(source: &[u8]) -> Result<Option<(ExGuid, PageOrder)>> {
19 let document = Document::parse(&index)?;19 let document = Document::parse(&index)?;
20 let mut pages = Vec::new();20 let mut pages = Vec::new();
21 for (sid, _) in document.pages()? {21 for (sid, _) in document.pages()? {
22 let space = &document.spaces[&sid];22 let view = document.active(sid)?;
23 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
24 let Some(metadata) = view.roots.get(&2).and_then(|id| view.nodes.get(id)) else {23 let Some(metadata) = view.roots.get(&2).and_then(|id| view.nodes.get(id)) else {
25 return Ok(None);24 return Ok(None);
26 };25 };
crates/onestore-offline/src/paragraph.rs+2-13
...@@ -30,21 +30,10 @@ fn observe(source: &[u8], space: ExGuid, texts: &[ExGuid]) -> Result<Option<Vec<...@@ -30,21 +30,10 @@ fn observe(source: &[u8], space: ExGuid, texts: &[ExGuid]) -> Result<Option<Vec<
30 let store = Store::parse(source)?;30 let store = Store::parse(source)?;
31 let index = RevisionIndex::parse(&store)?;31 let index = RevisionIndex::parse(&store)?;
32 let document = Document::parse(&index)?;32 let document = Document::parse(&index)?;
33 let Some(section) = document.spaces.get(&space) else {33 let Ok(view) = document.active(space) else {
34 return Ok(None);34 return Ok(None);
35 };35 };
36 let Some(view) = section36 let pages = document.pages_in(space)?;
37 .contexts
38 .get(&ExGuid::default())
39 .and_then(|id| section.revisions.get(id))
40 else {
41 return Ok(None);
42 };
43 let pages: Vec<_> = document
44 .pages()?
45 .into_iter()
46 .filter_map(|(sid, page)| (sid == space).then_some(page))
47 .collect();
48 let Some(paths) = active_paths(view, &pages, texts) else {37 let Some(paths) = active_paths(view, &pages, texts) else {
49 return Ok(None);38 return Ok(None);
50 };39 };
crates/onestore-offline/src/sync.rs+3-3
...@@ -127,7 +127,7 @@ impl Replica {...@@ -127,7 +127,7 @@ impl Replica {
127 EditStatus::AwaitingConfirmation { revision },127 EditStatus::AwaitingConfirmation { revision },
128 )));128 )));
129 }129 }
130 revision = index.spaces[&intent.space].labels[&(ExGuid::default(), 1)];130 revision = index.active(intent.space)?;
131 }131 }
132 if let Err(error) = remote.confirm(&snapshot) {132 if let Err(error) = remote.confirm(&snapshot) {
133 if error.state == CommitState::Committed {133 if error.state == CommitState::Committed {
...@@ -182,7 +182,7 @@ impl Replica {...@@ -182,7 +182,7 @@ impl Replica {
182 if prepared.as_bytes() == snapshot {182 if prepared.as_bytes() == snapshot {
183 let store = Store::parse(&snapshot)?;183 let store = Store::parse(&snapshot)?;
184 let index = RevisionIndex::parse(&store)?;184 let index = RevisionIndex::parse(&store)?;
185 let revision = index.spaces[&intent.space].labels[&(ExGuid::default(), 1)];185 let revision = index.active(intent.space)?;
186 if let Err(error) = remote.confirm(&snapshot) {186 if let Err(error) = remote.confirm(&snapshot) {
187 if error.state == CommitState::Committed {187 if error.state == CommitState::Committed {
188 self.acknowledge(intent.id, revision, &snapshot)?;188 self.acknowledge(intent.id, revision, &snapshot)?;
...@@ -194,7 +194,7 @@ impl Replica {...@@ -194,7 +194,7 @@ impl Replica {
194 }194 }
195 let store = Store::parse(prepared.as_bytes())?;195 let store = Store::parse(prepared.as_bytes())?;
196 let index = RevisionIndex::parse(&store)?;196 let index = RevisionIndex::parse(&store)?;
197 let revision = index.spaces[&intent.space].labels[&(ExGuid::default(), 1)];197 let revision = index.active(intent.space)?;
198 let before_store = Store::parse(&snapshot)?;198 let before_store = Store::parse(&snapshot)?;
199 let before = RevisionIndex::parse(&before_store)?;199 let before = RevisionIndex::parse(&before_store)?;
200 let mut revisions: BTreeMap<_, _> = index200 let mut revisions: BTreeMap<_, _> = index
crates/onestore-offline/src/tree.rs+5-14
...@@ -168,18 +168,10 @@ fn observe(...@@ -168,18 +168,10 @@ fn observe(
168 let store = Store::parse(source)?;168 let store = Store::parse(source)?;
169 let index = RevisionIndex::parse(&store)?;169 let index = RevisionIndex::parse(&store)?;
170 let document = Document::parse(&index)?;170 let document = Document::parse(&index)?;
171 let Some(section) = document.spaces.get(&space) else {171 let Ok(view) = document.active(space) else {
172 return Ok(None);172 return Ok(None);
173 };173 };
174 let Some(rid) = section.contexts.get(&ExGuid::default()) else {174 let pages = document.pages_in(space)?;
175 return Ok(None);
176 };
177 let view = &section.revisions[rid];
178 let pages: Vec<_> = document
179 .pages()?
180 .into_iter()
181 .filter_map(|(sid, page)| (sid == space).then_some(page))
182 .collect();
183 if pages.len() != 1 {175 if pages.len() != 1 {
184 return Ok(None);176 return Ok(None);
185 }177 }
...@@ -217,7 +209,7 @@ fn observe(...@@ -217,7 +209,7 @@ fn observe(
217 .collect(),209 .collect(),
218 destination,210 destination,
219 content: if intent.destination().is_none() {211 content: if intent.destination().is_none() {
220 let sha256 = fingerprint(&store, &index.resolve(space, *rid)?, object, legacy)?;212 let sha256 = fingerprint(&store, &index.resolve_active(space)?, object, legacy)?;
221 Some(if legacy {213 Some(if legacy {
222 Content::Legacy(sha256)214 Content::Legacy(sha256)
223 } else {215 } else {
...@@ -232,7 +224,7 @@ fn observe(...@@ -232,7 +224,7 @@ fn observe(
232fn mutable_objects(source: &[u8], space: ExGuid) -> Result<BTreeSet<ExGuid>> {224fn mutable_objects(source: &[u8], space: ExGuid) -> Result<BTreeSet<ExGuid>> {
233 let store = Store::parse(source)?;225 let store = Store::parse(source)?;
234 let index = RevisionIndex::parse(&store)?;226 let index = RevisionIndex::parse(&store)?;
235 let raw = index.resolve(space, index.spaces[&space].labels[&(ExGuid::default(), 1)])?;227 let raw = index.resolve_active(space)?;
236 Ok(raw228 Ok(raw
237 .reachable()?229 .reachable()?
238 .into_iter()230 .into_iter()
...@@ -468,8 +460,7 @@ mod tests {...@@ -468,8 +460,7 @@ mod tests {
468 let index = RevisionIndex::parse(&store).unwrap();460 let index = RevisionIndex::parse(&store).unwrap();
469 let document = Document::parse(&index).unwrap();461 let document = Document::parse(&index).unwrap();
470 let (sid, _) = document.pages().unwrap()[0];462 let (sid, _) = document.pages().unwrap()[0];
471 let space = &document.spaces[&sid];463 let view = document.active(sid).unwrap();
472 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
473 let text = *view.nodes.iter().find(|(_, node)| matches!(&node.kind, Kind::RichText { text, .. } if text == "AlphaOmega")).unwrap().0;464 let text = *view.nodes.iter().find(|(_, node)| matches!(&node.kind, Kind::RichText { text, .. } if text == "AlphaOmega")).unwrap().0;
474 let directory = tempfile::tempdir().unwrap();465 let directory = tempfile::tempdir().unwrap();
475 let path = directory.path().join("legacy.sqlite");466 let path = directory.path().join("legacy.sqlite");
crates/onestore-smb/src/tests.rs+1-1
...@@ -137,7 +137,7 @@ fn text(bytes: &[u8]) -> (ExGuid, ExGuid, String) {...@@ -137,7 +137,7 @@ fn text(bytes: &[u8]) -> (ExGuid, ExGuid, String) {
137 doc.spaces137 doc.spaces
138 .iter()138 .iter()
139 .find_map(|(sid, space)| {139 .find_map(|(sid, space)| {
140 let revision = &space.revisions[&space.contexts[&ExGuid::default()]];140 let revision = space.active().unwrap();
141 revision141 revision
142 .nodes142 .nodes
143 .iter()143 .iter()
crates/onestore/src/document.rs+64-13
...@@ -30,6 +30,18 @@ pub struct Space<'a> {...@@ -30,6 +30,18 @@ pub struct Space<'a> {
30 pub revisions: BTreeMap<ExGuid, Revision<'a>>,30 pub revisions: BTreeMap<ExGuid, Revision<'a>>,
31}31}
3232
33impl<'a> Space<'a> {
34 /// The revision of the default context, which is the current content of the space.
35 pub fn active(&self) -> Option<&Revision<'a>> {
36 self.revisions.get(self.contexts.get(&ExGuid::default())?)
37 }
38
39 pub fn into_active(mut self) -> Option<Revision<'a>> {
40 self.revisions
41 .remove(self.contexts.get(&ExGuid::default())?)
42 }
43}
44
33#[derive(Debug, Serialize)]45#[derive(Debug, Serialize)]
34pub struct Revision<'a> {46pub struct Revision<'a> {
35 pub roots: BTreeMap<u32, ExGuid>,47 pub roots: BTreeMap<u32, ExGuid>,
...@@ -61,7 +73,7 @@ pub struct Element<'a> {...@@ -61,7 +73,7 @@ pub struct Element<'a> {
61 pub extra: Vec<Vec<Field<'a>>>,73 pub extra: Vec<Vec<Field<'a>>>,
62}74}
6375
64#[derive(Debug, Default, Serialize)]76#[derive(Debug, Default, Clone, PartialEq, Serialize)]
65pub struct Layout {77pub struct Layout {
66 pub x: Option<f32>,78 pub x: Option<f32>,
67 pub y: Option<f32>,79 pub y: Option<f32>,
...@@ -73,7 +85,7 @@ pub struct Layout {...@@ -73,7 +85,7 @@ pub struct Layout {
7385
74macro_rules! format_fields {86macro_rules! format_fields {
75 ($($field:ident: $value:ty),* $(,)?) => {87 ($($field:ident: $value:ty),* $(,)?) => {
76 #[derive(Debug, Default, Serialize)]88 #[derive(Debug, Default, Clone, PartialEq, Serialize)]
77 pub struct Format { $(pub $field: Option<$value>),* }89 pub struct Format { $(pub $field: Option<$value>),* }
7890
79 impl Format {91 impl Format {
...@@ -208,7 +220,36 @@ impl Revision<'_> {...@@ -208,7 +220,36 @@ impl Revision<'_> {
208 }220 }
209}221}
210222
211#[derive(Debug, Serialize)]223impl Revision<'_> {
224 /// Maps every element reachable from `roots` through children, content and structure
225 /// references to its referencing parents, in visiting order.
226 pub fn parents(&self, roots: &[ExGuid]) -> Result<BTreeMap<ExGuid, Vec<ExGuid>>> {
227 let mut pending: Vec<_> = roots.to_vec();
228 let mut seen = BTreeSet::new();
229 let mut parents = BTreeMap::<_, Vec<_>>::new();
230 while let Some(id) = pending.pop() {
231 if !seen.insert(id) {
232 continue;
233 }
234 let element = self
235 .nodes
236 .get(&id)
237 .ok_or_else(|| invalid("Page content is unavailable"))?;
238 for child in element
239 .children
240 .iter()
241 .chain(&element.content)
242 .chain(&element.structure)
243 {
244 parents.entry(*child).or_default().push(id);
245 pending.push(*child);
246 }
247 }
248 Ok(parents)
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize)]
212/// Tag dates count seconds since 1980-01-01 UTC; status retains the ActionItemStatus bits.253/// Tag dates count seconds since 1980-01-01 UTC; status retains the ActionItemStatus bits.
213pub struct Tag {254pub struct Tag {
214 pub definition: Option<ExGuid>,255 pub definition: Option<ExGuid>,
...@@ -308,6 +349,7 @@ pub enum Kind<'a> {...@@ -308,6 +349,7 @@ pub enum Kind<'a> {
308 Row,349 Row,
309 Cell {350 Cell {
310 shading: Option<u32>,351 shading: Option<u32>,
352 indents: Vec<f32>,
311 },353 },
312 Image {354 Image {
313 container: Option<ExGuid>,355 container: Option<ExGuid>,
...@@ -547,17 +589,25 @@ fn measurements(bytes: Option<&[u8]>, header: usize) -> Result<Vec<f32>> {...@@ -547,17 +589,25 @@ fn measurements(bytes: Option<&[u8]>, header: usize) -> Result<Vec<f32>> {
547impl<'a> Document<'a> {589impl<'a> Document<'a> {
548 /// Active (object space, page) identities in section order, excluding history and conflicts.590 /// Active (object space, page) identities in section order, excluding history and conflicts.
549 /// Encrypted sections return no visible pages; table-of-contents files return an error.591 /// Encrypted sections return no visible pages; table-of-contents files return an error.
592 /// The active revision of one object space.
593 pub fn active(&self, space: ExGuid) -> Result<&Revision<'a>> {
594 self.spaces
595 .get(&space)
596 .and_then(Space::active)
597 .ok_or_else(|| invalid("Object space has no active revision"))
598 }
599
600 /// Active page objects declared in one page space, in section order.
601 pub fn pages_in(&self, space: ExGuid) -> Result<Vec<ExGuid>> {
602 Ok(self
603 .pages()?
604 .into_iter()
605 .filter_map(|(sid, page)| (sid == space).then_some(page))
606 .collect())
607 }
608
550 pub fn pages(&self) -> Result<Vec<(ExGuid, ExGuid)>> {609 pub fn pages(&self) -> Result<Vec<(ExGuid, ExGuid)>> {
551 let active = |sid| {610 let active = |sid| self.active(sid);
552 self.spaces
553 .get(&sid)
554 .and_then(|s| {
555 s.contexts
556 .get(&ExGuid::default())
557 .and_then(|rid| s.revisions.get(rid))
558 })
559 .ok_or_else(|| invalid("Section page has no active revision"))
560 };
561 let root = active(self.root)?;611 let root = active(self.root)?;
562 let section = root612 let section = root
563 .roots613 .roots
...@@ -1084,6 +1134,7 @@ impl<'a> Element<'a> {...@@ -1084,6 +1134,7 @@ impl<'a> Element<'a> {
1084 0x60023 => Kind::Row,1134 0x60023 => Kind::Row,
1085 0x60024 => Kind::Cell {1135 0x60024 => Kind::Cell {
1086 shading: f.u32(0x14001e26)?,1136 shading: f.u32(0x14001e26)?,
1137 indents: measurements(f.bytes(0x1c001c12)?, 4)?,
1087 },1138 },
1088 0x60011 => Kind::Image {1139 0x60011 => Kind::Image {
1089 container: f.one(0x20001c3f)?,1140 container: f.one(0x20001c3f)?,
crates/onestore/src/edit.rs+15-50
...@@ -44,11 +44,7 @@ pub fn replace_text(...@@ -44,11 +44,7 @@ pub fn replace_text(
44 let store = Store::parse(source)?;44 let store = Store::parse(source)?;
45 let index = RevisionIndex::parse(&store)?;45 let index = RevisionIndex::parse(&store)?;
46 index.validate_current()?;46 index.validate_current()?;
47 let rid = *index47 let rid = index.active(space)?;
48 .spaces
49 .get(&space)
50 .and_then(|s| s.labels.get(&(ExGuid::default(), 1)))
51 .ok_or_else(|| invalid("Object space has no active default revision"))?;
52 let raw = index.resolve(space, rid)?;48 let raw = index.resolve(space, rid)?;
53 if !raw.reachable()?.contains(&object) {49 if !raw.reachable()?.contains(&object) {
54 return Err(invalid("Object is not reachable in the active revision"));50 return Err(invalid("Object is not reachable in the active revision"));
...@@ -59,11 +55,7 @@ pub fn replace_text(...@@ -59,11 +55,7 @@ pub fn replace_text(
59 .get(&space)55 .get(&space)
60 .and_then(|s| s.revisions.get(&rid))56 .and_then(|s| s.revisions.get(&rid))
61 .ok_or_else(|| invalid("The active document revision is unavailable"))?;57 .ok_or_else(|| invalid("The active document revision is unavailable"))?;
62 let pages: Vec<_> = document58 let pages = document.pages_in(space)?;
63 .pages()?
64 .into_iter()
65 .filter_map(|(sid, page)| (sid == space).then_some(page))
66 .collect();
67 let parents = editable_parents(revision, &pages, object)?;59 let parents = editable_parents(revision, &pages, object)?;
68 let node = revision60 let node = revision
69 .nodes61 .nodes
...@@ -317,37 +309,23 @@ pub(crate) fn editable_parents(...@@ -317,37 +309,23 @@ pub(crate) fn editable_parents(
317 object: ExGuid,309 object: ExGuid,
318) -> Result<std::collections::BTreeMap<ExGuid, Vec<ExGuid>>, Error> {310) -> Result<std::collections::BTreeMap<ExGuid, Vec<ExGuid>>, Error> {
319 let invalid = |message| Error { offset: 0, message };311 let invalid = |message| Error { offset: 0, message };
320 let mut pending: Vec<_> = pages.iter().map(|page| (*page, false)).collect();312 let parents = revision.parents(pages)?;
313 if !parents.contains_key(&object) && !pages.contains(&object) {
314 return Err(invalid("Select content on an active editable page"));
315 }
316 let mut pending = vec![object];
321 let mut seen = std::collections::BTreeSet::new();317 let mut seen = std::collections::BTreeSet::new();
322 let mut parents = std::collections::BTreeMap::<_, Vec<_>>::new();318 while let Some(id) = pending.pop() {
323 let mut editable = false;319 if !seen.insert(id) {
324 while let Some((id, read_only)) = pending.pop() {
325 if !seen.insert((id, read_only)) {
326 continue;320 continue;
327 }321 }
328 let element = revision322 if revision.nodes[&id].extra[0]
329 .nodes
330 .get(&id)
331 .ok_or_else(|| invalid("Page content is unavailable"))?;
332 let read_only = read_only || element.extra[0].iter().any(|field| field.id == 0x88001cde);
333 if id == object {
334 if read_only {
335 return Err(invalid("This page or its content is read-only"));
336 }
337 editable = true;
338 }
339 for child in element
340 .children
341 .iter()323 .iter()
342 .chain(&element.content)324 .any(|field| field.id == 0x88001cde)
343 .chain(&element.structure)
344 {325 {
345 parents.entry(*child).or_default().push(id);326 return Err(invalid("This page or its content is read-only"));
346 pending.push((*child, read_only));
347 }327 }
348 }328 pending.extend(parents.get(&id).into_iter().flatten().copied());
349 if !editable {
350 return Err(invalid("Select content on an active editable page"));
351 }329 }
352 Ok(parents)330 Ok(parents)
353}331}
...@@ -358,21 +336,8 @@ pub(crate) fn page_title(...@@ -358,21 +336,8 @@ pub(crate) fn page_title(
358 text_update: Option<(ExGuid, &str)>,336 text_update: Option<(ExGuid, &str)>,
359) -> Result<Option<(ExGuid, bool, String)>, Error> {337) -> Result<Option<(ExGuid, bool, String)>, Error> {
360 let invalid = |message| Error { offset: 0, message };338 let invalid = |message| Error { offset: 0, message };
361 let mut pending = pages.to_vec();339 let parents = revision.parents(pages)?;
362 let mut seen = std::collections::BTreeSet::new();340 let seen: std::collections::BTreeSet<_> = parents.keys().chain(pages).copied().collect();
363 while let Some(id) = pending.pop() {
364 if !seen.insert(id) {
365 continue;
366 }
367 let node = &revision.nodes[&id];
368 pending.extend(
369 node.children
370 .iter()
371 .chain(&node.content)
372 .chain(&node.structure)
373 .copied(),
374 );
375 }
376 let titles: Vec<_> = revision341 let titles: Vec<_> = revision
377 .nodes342 .nodes
378 .iter()343 .iter()
crates/onestore/src/formatting.rs+2-11
...@@ -96,17 +96,8 @@ pub(crate) fn format_text(...@@ -96,17 +96,8 @@ pub(crate) fn format_text(
96 let index = RevisionIndex::parse(&store)?;96 let index = RevisionIndex::parse(&store)?;
97 index.validate_current()?;97 index.validate_current()?;
98 let document = Document::parse(&index)?;98 let document = Document::parse(&index)?;
99 let semantic = document99 let view = document.active(space)?;
100 .spaces100 let pages = document.pages_in(space)?;
101 .get(&space)
102 .ok_or_else(|| invalid("The active page is unavailable"))?;
103 let rid = semantic.contexts[&ExGuid::default()];
104 let view = &semantic.revisions[&rid];
105 let pages: Vec<_> = document
106 .pages()?
107 .into_iter()
108 .filter_map(|(sid, page)| (sid == space).then_some(page))
109 .collect();
110 let parents = editable_parents(view, &pages, object)?;101 let parents = editable_parents(view, &pages, object)?;
111 let node = &view.nodes[&object];102 let node = &view.nodes[&object];
112 let Kind::RichText {103 let Kind::RichText {
crates/onestore/src/insertion.rs+4-13
...@@ -195,24 +195,15 @@ impl Insertion {...@@ -195,24 +195,15 @@ impl Insertion {
195 let index = RevisionIndex::parse(&store)?;195 let index = RevisionIndex::parse(&store)?;
196 index.validate_current()?;196 index.validate_current()?;
197 let mut document = Document::parse(&index)?;197 let mut document = Document::parse(&index)?;
198 let pages: Vec<_> = document198 let pages = document.pages_in(space)?;
199 .pages()?
200 .into_iter()
201 .filter_map(|(sid, id)| (sid == space).then_some(id))
202 .collect();
203 let [page] = pages.as_slice() else {199 let [page] = pages.as_slice() else {
204 return Err(invalid("Insertion requires a single active page"));200 return Err(invalid("Insertion requires a single active page"));
205 };201 };
206 let semantic_space = document202 let mut view = document
207 .spaces203 .spaces
208 .remove(&space)204 .remove(&space)
205 .and_then(crate::document::Space::into_active)
209 .ok_or_else(|| invalid("The active page is unavailable"))?;206 .ok_or_else(|| invalid("The active page is unavailable"))?;
210 let rid = semantic_space.contexts[&ExGuid::default()];
211 let mut view = semantic_space
212 .revisions
213 .into_iter()
214 .find_map(|(id, revision)| (id == rid).then_some(revision))
215 .unwrap();
216 let parents = editable_parents(&view, &pages, self.parent)?;207 let parents = editable_parents(&view, &pages, self.parent)?;
217 let parent = &view.nodes[&self.parent];208 let parent = &view.nodes[&self.parent];
218 let position = match self.placement {209 let position = match self.placement {
...@@ -372,7 +363,7 @@ impl Insertion {...@@ -372,7 +363,7 @@ impl Insertion {
372 },363 },
373 );364 );
374 }365 }
375 let raw = index.resolve(space, rid)?;366 let raw = index.resolve_active(space)?;
376 if new.keys().any(|id| raw.objects.contains_key(id)) {367 if new.keys().any(|id| raw.objects.contains_key(id)) {
377 return Err(invalid(368 return Err(invalid(
378 "An insertion identity is already present; reconcile the existing edit",369 "An insertion identity is already present; reconcile the existing edit",
crates/onestore/src/objects.rs+2-9
...@@ -200,15 +200,8 @@ impl<'a> RevisionIndex<'a> {...@@ -200,15 +200,8 @@ impl<'a> RevisionIndex<'a> {
200 message: "Object references its own object space",200 message: "Object references its own object space",
201 });201 });
202 }202 }
203 let rid = self203 let rid = self.active(target)?;
204 .spaces204 targets.insert((target, rid));
205 .get(&target)
206 .and_then(|space| space.labels.get(&(ExGuid::default(), 1)))
207 .ok_or(Error {
208 offset: 0,
209 message: "Object-space reference has no default revision",
210 })?;
211 targets.insert((target, *rid));
212 }205 }
213 for context in references.contexts {206 for context in references.contexts {
214 if !space.labels.contains_key(&(context, 1)) {207 if !space.labels.contains_key(&(context, 1)) {
crates/onestore/src/outline.rs+6-11
...@@ -58,18 +58,14 @@ impl OutlineEdit {...@@ -58,18 +58,14 @@ impl OutlineEdit {
58 let index = RevisionIndex::parse(&store)?;58 let index = RevisionIndex::parse(&store)?;
59 index.validate_current()?;59 index.validate_current()?;
60 let mut document = Document::parse(&index)?;60 let mut document = Document::parse(&index)?;
61 let pages: Vec<_> = document61 let pages = document.pages_in(space)?;
62 .pages()?
63 .into_iter()
64 .filter_map(|(sid, page)| (sid == space).then_some(page))
65 .collect();
66 let [page] = pages.as_slice() else {62 let [page] = pages.as_slice() else {
67 return Err(invalid("Outline editing requires a single active page"));63 return Err(invalid("Outline editing requires a single active page"));
68 };64 };
69 let mut semantic = document.spaces.remove(&space).unwrap();65 let view = document
70 let view = semantic66 .spaces
71 .revisions67 .remove(&space)
72 .remove(&semantic.contexts[&ExGuid::default()])68 .and_then(crate::document::Space::into_active)
73 .unwrap();69 .unwrap();
74 let parents = editable_parents(&view, &pages, object)?;70 let parents = editable_parents(&view, &pages, object)?;
75 let node = &view.nodes[&object];71 let node = &view.nodes[&object];
...@@ -151,8 +147,7 @@ mod tests {...@@ -151,8 +147,7 @@ mod tests {
151 let index = RevisionIndex::parse(&store).unwrap();147 let index = RevisionIndex::parse(&store).unwrap();
152 let document = Document::parse(&index).unwrap();148 let document = Document::parse(&index).unwrap();
153 let (sid, page) = document.pages().unwrap()[0];149 let (sid, page) = document.pages().unwrap()[0];
154 let space = &document.spaces[&sid];150 let view = document.active(sid).unwrap();
155 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
156 let outline = *view.nodes[&page]151 let outline = *view.nodes[&page]
157 .children152 .children
158 .iter()153 .iter()
crates/onestore/src/paragraph.rs+8-26
...@@ -84,24 +84,15 @@ impl ParagraphSplit {...@@ -84,24 +84,15 @@ impl ParagraphSplit {
84 let index = RevisionIndex::parse(&store)?;84 let index = RevisionIndex::parse(&store)?;
85 index.validate_current()?;85 index.validate_current()?;
86 let mut document = Document::parse(&index)?;86 let mut document = Document::parse(&index)?;
87 let pages: Vec<_> = document87 let pages = document.pages_in(space)?;
88 .pages()?
89 .into_iter()
90 .filter_map(|(sid, page)| (sid == space).then_some(page))
91 .collect();
92 let [_] = pages.as_slice() else {88 let [_] = pages.as_slice() else {
93 return Err(invalid("Splitting requires a single active page"));89 return Err(invalid("Splitting requires a single active page"));
94 };90 };
95 let semantic = document91 let view = document
96 .spaces92 .spaces
97 .remove(&space)93 .remove(&space)
94 .and_then(crate::document::Space::into_active)
98 .ok_or_else(|| invalid("The active page is unavailable"))?;95 .ok_or_else(|| invalid("The active page is unavailable"))?;
99 let rid = semantic.contexts[&ExGuid::default()];
100 let view = semantic
101 .revisions
102 .into_iter()
103 .find_map(|(id, view)| (id == rid).then_some(view))
104 .unwrap();
105 let parents = editable_parents(&view, &pages, self.text)?;96 let parents = editable_parents(&view, &pages, self.text)?;
106 let [paragraph] = parents97 let [paragraph] = parents
107 .get(&self.text)98 .get(&self.text)
...@@ -147,7 +138,7 @@ impl ParagraphSplit {...@@ -147,7 +138,7 @@ impl ParagraphSplit {
147 }138 }
148 pending.extend(parents.get(&id).into_iter().flatten().copied());139 pending.extend(parents.get(&id).into_iter().flatten().copied());
149 }140 }
150 let raw = index.resolve(space, rid)?;141 let raw = index.resolve_active(space)?;
151 let (text, runs) = ordinary_text(&view, &raw, self.text)?;142 let (text, runs) = ordinary_text(&view, &raw, self.text)?;
152 let node = &view.nodes[&self.text];143 let node = &view.nodes[&self.text];
153 let length = u32::try_from(text.encode_utf16().count())144 let length = u32::try_from(text.encode_utf16().count())
...@@ -419,24 +410,15 @@ impl ParagraphJoin {...@@ -419,24 +410,15 @@ impl ParagraphJoin {
419 let index = RevisionIndex::parse(&store)?;410 let index = RevisionIndex::parse(&store)?;
420 index.validate_current()?;411 index.validate_current()?;
421 let mut document = Document::parse(&index)?;412 let mut document = Document::parse(&index)?;
422 let pages: Vec<_> = document413 let pages = document.pages_in(space)?;
423 .pages()?
424 .into_iter()
425 .filter_map(|(sid, page)| (sid == space).then_some(page))
426 .collect();
427 if pages.len() != 1 {414 if pages.len() != 1 {
428 return Err(invalid("Joining requires a single active page"));415 return Err(invalid("Joining requires a single active page"));
429 }416 }
430 let semantic = document417 let view = document
431 .spaces418 .spaces
432 .remove(&space)419 .remove(&space)
420 .and_then(crate::document::Space::into_active)
433 .ok_or_else(|| invalid("The active page is unavailable"))?;421 .ok_or_else(|| invalid("The active page is unavailable"))?;
434 let rid = semantic.contexts[&ExGuid::default()];
435 let view = semantic
436 .revisions
437 .into_iter()
438 .find_map(|(id, view)| (id == rid).then_some(view))
439 .unwrap();
440 let parents = editable_parents(&view, &pages, self.left)?;422 let parents = editable_parents(&view, &pages, self.left)?;
441 editable_parents(&view, &pages, self.right)?;423 editable_parents(&view, &pages, self.right)?;
442 let parent = |id| -> Result<ExGuid, Error> {424 let parent = |id| -> Result<ExGuid, Error> {
...@@ -500,7 +482,7 @@ impl ParagraphJoin {...@@ -500,7 +482,7 @@ impl ParagraphJoin {
500 "Joining these child indentation levels requires a hierarchy edit",482 "Joining these child indentation levels requires a hierarchy edit",
501 ));483 ));
502 }484 }
503 let raw = index.resolve(space, rid)?;485 let raw = index.resolve_active(space)?;
504 let (a, a_runs) = ordinary_text(&view, &raw, self.left)?;486 let (a, a_runs) = ordinary_text(&view, &raw, self.left)?;
505 let (b, b_runs) = ordinary_text(&view, &raw, self.right)?;487 let (b, b_runs) = ordinary_text(&view, &raw, self.right)?;
506 let length = u32::try_from(a.encode_utf16().count())488 let length = u32::try_from(a.encode_utf16().count())
crates/onestore/src/revisions.rs+18
...@@ -396,3 +396,21 @@ impl<'a> RevisionIndex<'a> {...@@ -396,3 +396,21 @@ impl<'a> RevisionIndex<'a> {
396 })396 })
397 }397 }
398}398}
399
400impl<'a> RevisionIndex<'a> {
401 /// The revision labelled current in the default context of one object space.
402 pub fn active(&self, space: ExGuid) -> Result<ExGuid> {
403 self.spaces
404 .get(&space)
405 .and_then(|space| space.labels.get(&(ExGuid::default(), 1)))
406 .copied()
407 .ok_or(Error {
408 offset: 0,
409 message: "Object space has no active default revision",
410 })
411 }
412
413 pub fn resolve_active(&self, space: ExGuid) -> Result<crate::ResolvedRevision<'a>> {
414 self.resolve(space, self.active(space)?)
415 }
416}
crates/onestore/src/tree.rs+11-22
...@@ -100,20 +100,14 @@ impl TreeEdit {...@@ -100,20 +100,14 @@ impl TreeEdit {
100 let index = RevisionIndex::parse(&store)?;100 let index = RevisionIndex::parse(&store)?;
101 index.validate_current()?;101 index.validate_current()?;
102 let mut document = Document::parse(&index)?;102 let mut document = Document::parse(&index)?;
103 let pages: Vec<_> = document103 let pages = document.pages_in(space)?;
104 .pages()?
105 .into_iter()
106 .filter_map(|(sid, page)| (sid == space).then_some(page))
107 .collect();
108 let [page] = pages.as_slice() else {104 let [page] = pages.as_slice() else {
109 return Err(invalid("Tree editing requires a single active page"));105 return Err(invalid("Tree editing requires a single active page"));
110 };106 };
111 let semantic = document.spaces.remove(&space).unwrap();107 let mut view = document
112 let rid = semantic.contexts[&ExGuid::default()];108 .spaces
113 let mut view = semantic109 .remove(&space)
114 .revisions110 .and_then(crate::document::Space::into_active)
115 .into_iter()
116 .find_map(|(id, view)| (id == rid).then_some(view))
117 .unwrap();111 .unwrap();
118 let parents = editable_parents(&view, &pages, self.object)?;112 let parents = editable_parents(&view, &pages, self.object)?;
119 let path = checked_path(&view, &parents, *page, self.object)?;113 let path = checked_path(&view, &parents, *page, self.object)?;
...@@ -279,7 +273,7 @@ impl TreeEdit {...@@ -279,7 +273,7 @@ impl TreeEdit {
279 .copied(),273 .copied(),
280 );274 );
281 }275 }
282 let raw = index.resolve(space, rid)?;276 let raw = index.resolve_active(space)?;
283 let active_parents = editable_parents(&view, &pages, *page)?;277 let active_parents = editable_parents(&view, &pages, *page)?;
284 let modified = current_timestamps()?.0.to_le_bytes();278 let modified = current_timestamps()?.0.to_le_bytes();
285 let mut changed = BTreeMap::new();279 let mut changed = BTreeMap::new();
...@@ -431,8 +425,7 @@ mod tests {...@@ -431,8 +425,7 @@ mod tests {
431 let index = RevisionIndex::parse(&store).unwrap();425 let index = RevisionIndex::parse(&store).unwrap();
432 let document = Document::parse(&index).unwrap();426 let document = Document::parse(&index).unwrap();
433 let (sid, page) = document.pages().unwrap()[0];427 let (sid, page) = document.pages().unwrap()[0];
434 let space = &document.spaces[&sid];428 let view = document.active(sid).unwrap();
435 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
436 let outline = *view.nodes[&page]429 let outline = *view.nodes[&page]
437 .children430 .children
438 .iter()431 .iter()
...@@ -453,8 +446,7 @@ mod tests {...@@ -453,8 +446,7 @@ mod tests {
453 let store = Store::parse(repeated.as_bytes()).unwrap();446 let store = Store::parse(repeated.as_bytes()).unwrap();
454 let index = RevisionIndex::parse(&store).unwrap();447 let index = RevisionIndex::parse(&store).unwrap();
455 let document = Document::parse(&index).unwrap();448 let document = Document::parse(&index).unwrap();
456 let space = &document.spaces[&sid];449 let view = document.active(sid).unwrap();
457 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
458 assert_eq!(view.nodes[&outline].children, [second.object(), first]);450 assert_eq!(view.nodes[&outline].children, [second.object(), first]);
459 assert_eq!(451 assert_eq!(
460 view.nodes[&first].latest_author,452 view.nodes[&first].latest_author,
...@@ -472,8 +464,7 @@ mod tests {...@@ -472,8 +464,7 @@ mod tests {
472 let index = RevisionIndex::parse(&store).unwrap();464 let index = RevisionIndex::parse(&store).unwrap();
473 let document = Document::parse(&index).unwrap();465 let document = Document::parse(&index).unwrap();
474 let (sid, page) = document.pages().unwrap()[0];466 let (sid, page) = document.pages().unwrap()[0];
475 let space = &document.spaces[&sid];467 let view = document.active(sid).unwrap();
476 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
477 let outline = *view.nodes[&page]468 let outline = *view.nodes[&page]
478 .children469 .children
479 .iter()470 .iter()
...@@ -527,8 +518,7 @@ mod tests {...@@ -527,8 +518,7 @@ mod tests {
527 let index = RevisionIndex::parse(&store).unwrap();518 let index = RevisionIndex::parse(&store).unwrap();
528 index.validate_current().unwrap();519 index.validate_current().unwrap();
529 let document = Document::parse(&index).unwrap();520 let document = Document::parse(&index).unwrap();
530 let space = &document.spaces[&sid];521 let view = document.active(sid).unwrap();
531 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
532 assert_eq!(view.nodes[&outline].children, [a, second.object()]);522 assert_eq!(view.nodes[&outline].children, [a, second.object()]);
533 assert_eq!(view.nodes[&outline].child_level, Some(2));523 assert_eq!(view.nodes[&outline].child_level, Some(2));
534 assert_eq!(view.nodes[&a].child_level, Some(1));524 assert_eq!(view.nodes[&a].child_level, Some(1));
...@@ -557,8 +547,7 @@ mod tests {...@@ -557,8 +547,7 @@ mod tests {
557 let index = RevisionIndex::parse(&store).unwrap();547 let index = RevisionIndex::parse(&store).unwrap();
558 let document = Document::parse(&index).unwrap();548 let document = Document::parse(&index).unwrap();
559 let (sid, page) = document.pages().unwrap()[0];549 let (sid, page) = document.pages().unwrap()[0];
560 let space = &document.spaces[&sid];550 let view = document.active(sid).unwrap();
561 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
562 let outline = *view.nodes[&page]551 let outline = *view.nodes[&page]
563 .children552 .children
564 .iter()553 .iter()
crates/onestore/src/write.rs+2-16
...@@ -596,14 +596,7 @@ pub(crate) fn write_revision(...@@ -596,14 +596,7 @@ pub(crate) fn write_revision(
596 edit: impl FnOnce(&crate::ResolvedRevision<'_>) -> Result<BTreeMap<ExGuid, PropertyObject>>,596 edit: impl FnOnce(&crate::ResolvedRevision<'_>) -> Result<BTreeMap<ExGuid, PropertyObject>>,
597) -> Result<Vec<u8>> {597) -> Result<Vec<u8>> {
598 write_revisions(source, |index| {598 write_revisions(source, |index| {
599 let rid = *index599 let rid = index.active(space)?;
600 .spaces
601 .get(&space)
602 .and_then(|space| space.labels.get(&(ExGuid::default(), 1)))
603 .ok_or(Error {
604 offset: 0,
605 message: "Object space has no active default revision",
606 })?;
607 let revision = index.resolve(space, rid)?;600 let revision = index.resolve(space, rid)?;
608 Ok(BTreeMap::from([(601 Ok(BTreeMap::from([(
609 space,602 space,
...@@ -680,14 +673,7 @@ pub(crate) fn write_revisions(...@@ -680,14 +673,7 @@ pub(crate) fn write_revisions(
680 for (space, change) in changes {673 for (space, change) in changes {
681 let (rid, mut revision, mut replacements) = match change {674 let (rid, mut revision, mut replacements) = match change {
682 RevisionEdit::Update(objects) => {675 RevisionEdit::Update(objects) => {
683 let rid = *index676 let rid = index.active(space)?;
684 .spaces
685 .get(&space)
686 .and_then(|space| space.labels.get(&(ExGuid::default(), 1)))
687 .ok_or(Error {
688 offset: 0,
689 message: "Object space has no active default revision",
690 })?;
691 (Some(rid), index.resolve(space, rid)?, objects)677 (Some(rid), index.resolve(space, rid)?, objects)
692 }678 }
693 RevisionEdit::Create { roots, objects } => {679 RevisionEdit::Create { roots, objects } => {
crates/onestore/src/write/tests.rs+4-4
...@@ -144,7 +144,7 @@ fn document_insertions_and_formatting_respect_readonly_ancestors() {...@@ -144,7 +144,7 @@ fn document_insertions_and_formatting_respect_readonly_ancestors() {
144 .unwrap();144 .unwrap();
145 let document = crate::document::Document::parse(&index).unwrap();145 let document = crate::document::Document::parse(&index).unwrap();
146 let space = &document.spaces[&sid];146 let space = &document.spaces[&sid];
147 let view = &space.revisions[&space.contexts[&ExGuid::default()]];147 let view = space.active().unwrap();
148 let text = view.nodes[&paragraph].content[0];148 let text = view.nodes[&paragraph].content[0];
149 let join = crate::ParagraphJoin::new(text, right.text_object(), "Author").unwrap();149 let join = crate::ParagraphJoin::new(text, right.text_object(), "Author").unwrap();
150 assert!(PreparedEdit::join(&protected, sid, &join).is_err());150 assert!(PreparedEdit::join(&protected, sid, &join).is_err());
...@@ -861,7 +861,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() {...@@ -861,7 +861,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() {
861 let index = RevisionIndex::parse(&store).unwrap();861 let index = RevisionIndex::parse(&store).unwrap();
862 let document = Document::parse(&index).unwrap();862 let document = Document::parse(&index).unwrap();
863 let space = &document.spaces[&sid];863 let space = &document.spaces[&sid];
864 let view = &space.revisions[&space.contexts[&ExGuid::default()]];864 let view = space.active().unwrap();
865 let Kind::RichText { runs, .. } = &view.nodes[&text].kind else {865 let Kind::RichText { runs, .. } = &view.nodes[&text].kind else {
866 panic!()866 panic!()
867 };867 };
...@@ -888,7 +888,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() {...@@ -888,7 +888,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() {
888 let index = RevisionIndex::parse(&store).unwrap();888 let index = RevisionIndex::parse(&store).unwrap();
889 let doc = Document::parse(&index).unwrap();889 let doc = Document::parse(&index).unwrap();
890 let s = &doc.spaces[&sid];890 let s = &doc.spaces[&sid];
891 let view = &s.revisions[&s.contexts[&ExGuid::default()]];891 let view = s.active().unwrap();
892 let runs = view.text_runs(text).unwrap();892 let runs = view.text_runs(text).unwrap();
893 assert_eq!(runs.len(), 1);893 assert_eq!(runs.len(), 1);
894 assert_eq!(runs[0].format.font.as_deref(), Some("Georgia"));894 assert_eq!(runs[0].format.font.as_deref(), Some("Georgia"));
...@@ -909,7 +909,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() {...@@ -909,7 +909,7 @@ fn character_formatting_preserves_inheritance_and_associated_data() {
909 let previous = RevisionIndex::parse(&previous).unwrap();909 let previous = RevisionIndex::parse(&previous).unwrap();
910 let previous_doc = Document::parse(&previous).unwrap();910 let previous_doc = Document::parse(&previous).unwrap();
911 let s = &previous_doc.spaces[&sid];911 let s = &previous_doc.spaces[&sid];
912 let previous_view = &s.revisions[&s.contexts[&ExGuid::default()]];912 let previous_view = s.active().unwrap();
913 assert_eq!(913 assert_eq!(
914 format!("{:?}", view.nodes[&text].extra),914 format!("{:?}", view.nodes[&text].extra),
915 format!("{:?}", previous_view.nodes[&text].extra)915 format!("{:?}", previous_view.nodes[&text].extra)
crates/onestore/src/write/tests/multi.rs+3-3
...@@ -28,7 +28,7 @@ fn nest(source: &[u8]) -> Vec<u8> {...@@ -28,7 +28,7 @@ fn nest(source: &[u8]) -> Vec<u8> {
28 index.spaces[&index.root].labels[&(ExGuid::default(), 1)],28 index.spaces[&index.root].labels[&(ExGuid::default(), 1)],
29 )?;29 )?;
30 let view = &document.spaces[&index.root];30 let view = &document.spaces[&index.root];
31 let view = &view.revisions[&view.contexts[&ExGuid::default()]];31 let view = view.active().unwrap();
32 let root = section.roots[&1];32 let root = section.roots[&1];
33 let parent = *view.nodes[&root]33 let parent = *view.nodes[&root]
34 .children34 .children
...@@ -126,7 +126,7 @@ fn nesting_publishes_section_order_and_page_levels_in_one_transaction() {...@@ -126,7 +126,7 @@ fn nesting_publishes_section_order_and_page_levels_in_one_transaction() {
126 assert_eq!(changed_spaces, 3);126 assert_eq!(changed_spaces, 3);
127 for (ordinal, (sid, _)) in pages.iter().enumerate() {127 for (ordinal, (sid, _)) in pages.iter().enumerate() {
128 let view = &document.spaces[sid];128 let view = &document.spaces[sid];
129 let view = &view.revisions[&view.contexts[&ExGuid::default()]];129 let view = view.active().unwrap();
130 let Kind::Metadata { level, .. } = view.nodes[&view.roots[&2]].kind else {130 let Kind::Metadata { level, .. } = view.nodes[&view.roots[&2]].kind else {
131 panic!()131 panic!()
132 };132 };
...@@ -151,7 +151,7 @@ fn nesting_publishes_section_order_and_page_levels_in_one_transaction() {...@@ -151,7 +151,7 @@ fn nesting_publishes_section_order_and_page_levels_in_one_transaction() {
151 }151 }
152 }152 }
153 let root = &document.spaces[&document.root];153 let root = &document.spaces[&document.root];
154 let root = &root.revisions[&root.contexts[&ExGuid::default()]];154 let root = root.active().unwrap();
155 assert_eq!(155 assert_eq!(
156 root.nodes[&root.roots[&1]]156 root.nodes[&root.roots[&1]]
157 .children157 .children
crates/onestore/tests/document.rs+1-1
...@@ -17,7 +17,7 @@ fn documented_cell_shading_survives_native_2010_ignoring_its_display() {...@@ -17,7 +17,7 @@ fn documented_cell_shading_survives_native_2010_ignoring_its_display() {
17 .flat_map(|s| s.revisions.values())17 .flat_map(|s| s.revisions.values())
18 .flat_map(|r| r.nodes.values())18 .flat_map(|r| r.nodes.values())
19 .filter_map(|n| match n.kind {19 .filter_map(|n| match n.kind {
20 Kind::Cell { shading } => shading,20 Kind::Cell { shading, .. } => shading,
21 _ => None,21 _ => None,
22 })22 })
23 .collect();23 .collect();