diff --git "a/wcfs/internal/xbtree/\316\264btail.go" "b/wcfs/internal/xbtree/\316\264btail.go"
index 5c497855f80fa9fcab823fb52eb9e60966f90197..db6eca7f27e368c958e98d99ae1b318cd2de338f 100644
--- "a/wcfs/internal/xbtree/\316\264btail.go"
+++ "b/wcfs/internal/xbtree/\316\264btail.go"
@@ -38,13 +38,27 @@ type Key    = int64
 type Value  = zodb.Oid // XXX assumes key points to IPersistent
                        // XXX how to represent deletion? InvalidOid?
 
-// XXX
+// ΔB represents a change in BTrees space.
+//
+// XXX place
+type ΔB struct {
+	Rev	zodb.Tid
+	Change	map[*Tree]map[Key]Value // {} root -> {}(key, value)
+}
+
+// ΔTail represents tail of revisional changes to BTrees.
+//
+// It semantically consists of
+//
+//	[]δB			; rev ∈ (tail, head
+//
+// where δB represents a change in BTrees space
 //
 //	δB:
 //		.rev↑
 //		{} root -> {}(key, value)
-
-// ΔTail represents tail of revisional changes to BTrees.
+//
+//	XXX + atTail
 //
 // It covers changes to keys from tracked subset of BTrees parts and
 // semantically consists of
@@ -67,7 +81,7 @@ type Value  = zodb.Oid // XXX assumes key points to IPersistent
 //   - Track(path)		- start tracking tree nodes and keys; root=path[0], keys=path[-1].keys
 //   - ForgetPast(revCut)	- forget changes past revCut
 //   - Get(root, key, at)	- get root[key] @at assuming root[key] ∈ tracked
-//   - Update(δZ)		- update BTree δ tail given raw ZODB changes
+//   - Update(δZ) -> δB		- update BTree δ tail given raw ZODB changes
 //
 // An example for tracked set is a set of visited BTree paths.
 // There is no requirement that tracked set belongs to only one single BTree.
@@ -192,7 +206,7 @@ func (δb *ΔTail) Track(path []Node) {	// XXX Tree|Bucket; path[0] = root
 // only those keys, that correspond to tracked subset of δZ.
 //
 // XXX returned [](root, []key) -> move to separate SliceByRev to get diff?
-func (btail *ΔTail) Update(δZ *zodb.EventCommit) []ΔTree {
+func (btail *ΔTail) Update(δZ *zodb.EventCommit) ΔB {
 	btail.ΔZtail.Append(δZ.Tid, δZ.Changev)
 
 	// {} root -> []oid changed under that root
@@ -212,9 +226,14 @@ func (btail *ΔTail) Update(δZ *zodb.EventCommit) []ΔTree {
 	}
 
 	// XXX stub to get file.size invalidation working
-	var δB []ΔTree
+	δB := ΔB{Rev: δZ.Tid, Change: make(map[*Tree]map[Key]Value)}
 	for root := range δZByRoot {
-		δB = append(δB, ΔTree{δZ.Tid, root, nil})
+		δt, ok := δB.Change[root]
+		if !ok {
+			δt = make(map[Key]Value)
+			δB.Change[root] = δt
+		}
+		// TODO update δt
 	}
 	return δB
 
diff --git "a/wcfs/\316\264ftail.go" "b/wcfs/\316\264ftail.go"
index b51a78b8ed4b04a701a902446bed6464d9069339..8d260b079d30d820f2004b2b5d72ff21b949f2fa 100644
--- "a/wcfs/\316\264ftail.go"
+++ "b/wcfs/\316\264ftail.go"
@@ -89,7 +89,7 @@ func (δf *ΔFTail) Track(file *BigFile, path []btree.LONode) {
 func (δFtail *ΔFTail) Update(δZ *zodb.EventCommit) ΔF {
 	δB := δFtail.ΔTail.Update(δZ)
 	δF := ΔF{Rev: δB.Rev, Change: make(map[*BigFile]SetI64)}
-	for root, δt := range δB {
+	for root, δt := range δB.Change {
 		files := δFtail.fileIdx[root]
 		if len(files) == 0 {
 			panicf("ΔFTail: root<%s> -> ø file", root.POid())