Repository: yorkie-team/yorkie
Pull request #549 (2023-06-14 08:09:51)

Implement Tree.Style for setting attributes to nodes
<!--  Thanks for sending a pull request! -->

**What this PR does / why we need it**:

Implement `Tree.Style` for setting attributes to nodes.

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:
<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->
```release-note

```

**Additional documentation**:

<!--
This section can be blank if this pull request does not require a release note.

Please use the following format for linking documentation:
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

**Checklist**:
- [x] Added relevant tests or not required
- [x] Didn't break anything


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) Report
> Merging [#549](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (990dff3) into [main](https://app.codecov.io/gh/yorkie-team/yorkie/commit/0ce364b62d487aa1a6cc26f059f3eb6cfefb19eb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) (0ce364b) will **decrease** coverage by `0.11%`.
> The diff coverage is `46.87%`.

```diff
@@            Coverage Diff             @@
##             main     #549      +/-   ##
==========================================
- Coverage   50.53%   50.43%   -0.11%     
==========================================
  Files          67       67              
  Lines        6678     6763      +85     
==========================================
+ Hits         3375     3411      +36     
- Misses       2876     2923      +47     
- Partials      427      429       +2     
```


| [Impacted Files](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team) | Coverage Δ | |
|---|---|---|
| [api/converter/to\_pb.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-YXBpL2NvbnZlcnRlci90b19wYi5nbw==) | `57.27% <0.00%> (-1.98%)` | :arrow_down: |
| [pkg/index/tree.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2luZGV4L3RyZWUuZ28=) | `53.18% <0.00%> (ø)` | |
| [api/converter/from\_pb.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-YXBpL2NvbnZlcnRlci9mcm9tX3BiLmdv) | `49.23% <7.40%> (-2.30%)` | :arrow_down: |
| [api/converter/to\_bytes.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-YXBpL2NvbnZlcnRlci90b19ieXRlcy5nbw==) | `72.29% <64.28%> (-2.18%)` | :arrow_down: |
| [pkg/document/crdt/tree.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvdHJlZS5nbw==) | `72.29% <70.96%> (-0.12%)` | :arrow_down: |
| [pkg/document/crdt/rht.go](https://app.codecov.io/gh/yorkie-team/yorkie/pull/549?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=yorkie-team#diff-cGtnL2RvY3VtZW50L2NyZHQvcmh0Lmdv) | `68.25% <100.00%> (+7.46%)` | :arrow_up: |



Patch:
diff --git a/api/converter/from_pb.go b/api/converter/from_pb.go
index 35f351c1..651a373c 100644
--- a/api/converter/from_pb.go
+++ b/api/converter/from_pb.go
@@ -309,6 +309,8 @@ func FromOperations(pbOps []*api.Operation) ([]operations.Operation, error) {
 			op, err = fromIncrease(decoded.Increase)
 		case *api.Operation_TreeEdit_:
 			op, err = fromTreeEdit(decoded.TreeEdit)
+		case *api.Operation_TreeStyle_:
+			op, err = fromTreeStyle(decoded.TreeStyle)
 		default:
 			return nil, ErrUnsupportedOperation
 		}
@@ -553,6 +555,36 @@ func fromTreeEdit(pbTreeEdit *api.Operation_TreeEdit) (*operations.TreeEdit, err
 	), nil
 }
 
+func fromTreeStyle(pbTreeStyle *api.Operation_TreeStyle) (*operations.TreeStyle, error) {
+	parentCreatedAt, err := fromTimeTicket(pbTreeStyle.ParentCreatedAt)
+	if err != nil {
+		return nil, err
+	}
+
+	executedAt, err := fromTimeTicket(pbTreeStyle.ExecutedAt)
+	if err != nil {
+		return nil, err
+	}
+
+	from, err := fromTreePos(pbTreeStyle.From)
+	if err != nil {
+		return nil, err
+	}
+
+	to, err := fromTreePos(pbTreeStyle.To)
+	if err != nil {
+		return nil, err
+	}
+
+	return operations.NewTreeStyle(
+		parentCreatedAt,
+		from,
+		to,
+		pbTreeStyle.Attributes,
+		executedAt,
+	), nil
+}
+
 func fromCreatedAtMapByActor(
 	pbCreatedAtMapByActor map[string]*api.TimeTicket,
 ) (map[string]*time.Ticket, error) {
@@ -619,9 +651,19 @@ func fromTreeNode(pbNode *api.TreeNode) (*crdt.TreeNode, error) {
 		return nil, err
 	}
 
+	attrs := crdt.NewRHT()
+	for k, pbAttr := range pbNode.Attributes {
+		updatedAt, err := fromTimeTicket(pbAttr.UpdatedAt)
+		if err != nil {
+			return nil, err
+		}
+		attrs.Set(k, pbAttr.Value, updatedAt)
+	}
+
 	return crdt.NewTreeNode(
 		pos,
 		pbNode.Type,
+		attrs,
 		pbNode.Value,
 	), nil
 }
diff --git a/api/converter/to_bytes.go b/api/converter/to_bytes.go
index 926fdeeb..f6b5b08e 100644
--- a/api/converter/to_bytes.go
+++ b/api/converter/to_bytes.go
@@ -196,9 +196,9 @@ func toTextNodes(textNodes []*crdt.RGATreeSplitNode[*crdt.TextValue]) []*api.Tex
 	for _, textNode := range textNodes {
 		value := textNode.Value()
 
-		attrs := make(map[string]*api.TextNodeAttr)
+		attrs := make(map[string]*api.NodeAttr)
 		for _, node := range value.Attrs().Nodes() {
-			attrs[node.Key()] = &api.TextNodeAttr{
+			attrs[node.Key()] = &api.NodeAttr{
 				Value:     node.Value(),
 				UpdatedAt: ToTimeTicket(node.UpdatedAt()),
 			}
@@ -241,12 +241,24 @@ func ToTreeNodes(treeNode *crdt.TreeNode) []*api.TreeNode {
 }
 
 func toTreeNode(treeNode *crdt.TreeNode, depth int) *api.TreeNode {
+	var attrs map[string]*api.NodeAttr
+	if treeNode.Attrs != nil {
+		attrs = make(map[string]*api.NodeAttr)
+		for _, node := range treeNode.Attrs.Nodes() {
+			attrs[node.Key()] = &api.NodeAttr{
+				Value:     node.Value(),
+				UpdatedAt: ToTimeTicket(node.UpdatedAt()),
+			}
+		}
+	}
+
 	pbNode := &api.TreeNode{
-		Pos:       toTreePos(treeNode.Pos),
-		Type:      treeNode.Type(),
-		Value:     treeNode.Value,
-		RemovedAt: ToTimeTicket(treeNode.RemovedAt),
-		Depth:     int32(depth),
+		Pos:        toTreePos(treeNode.Pos),
+		Type:       treeNode.Type(),
+		Value:      treeNode.Value,
+		RemovedAt:  ToTimeTicket(treeNode.RemovedAt),
+		Depth:      int32(depth),
+		Attributes: attrs,
 	}
 
 	if treeNode.InsPrev != nil {
diff --git a/api/converter/to_pb.go b/api/converter/to_pb.go
index 1f3dbce0..350f3ed8 100644
--- a/api/converter/to_pb.go
+++ b/api/converter/to_pb.go
@@ -237,6 +237,8 @@ func ToOperations(ops []operations.Operation) ([]*api.Operation, error) {
 			pbOperation.Body, err = toIncrease(op)
 		case *operations.TreeEdit:
 			pbOperation.Body, err = toTreeEdit(op)
+		case *operations.TreeStyle:
+			pbOperation.Body, err = toTreeStyle(op)
 		default:
 			return nil, ErrUnsupportedOperation
 		}
@@ -399,6 +401,18 @@ func toTreeEdit(e *operations.TreeEdit) (*api.Operation_TreeEdit_, error) {
 	}, nil
 }
 
+func toTreeStyle(style *operations.TreeStyle) (*api.Operation_TreeStyle_, error) {
+	return &api.Operation_TreeStyle_{
+		TreeStyle: &api.Operation_TreeStyle{
+			ParentCreatedAt: ToTimeTicket(style.ParentCreatedAt()),
+			From:            toTreePos(style.FromPos()),
+			To:              toTreePos(style.ToPos()),
+			Attributes:      style.Attributes(),
+			ExecutedAt:      ToTimeTicket(style.ExecutedAt()),
+		},
+	}, nil
+}
+
 func toJSONElementSimple(elem crdt.Element) (*api.JSONElementSimple, error) {
 	switch elem := elem.(type) {
 	case *crdt.Object:
diff --git a/api/yorkie/v1/resources.pb.go b/api/yorkie/v1/resources.pb.go
index 053eeb60..177adde8 100644
--- a/api/yorkie/v1/resources.pb.go
+++ b/api/yorkie/v1/resources.pb.go
@@ -350,6 +350,7 @@ type Operation struct {
 	//	*Operation_Style_
 	//	*Operation_Increase_
 	//	*Operation_TreeEdit_
+	//	*Operation_TreeStyle_
 	Body                 isOperation_Body `protobuf_oneof:"body"`
 	XXX_NoUnkeyedLiteral struct{}         `json:"-"`
 	XXX_unrecognized     []byte           `json:"-"`
@@ -422,16 +423,20 @@ type Operation_Increase_ struct {
 type Operation_TreeEdit_ struct {
 	TreeEdit *Operation_TreeEdit `protobuf:"bytes,9,opt,name=tree_edit,json=treeEdit,proto3,oneof" json:"tree_edit,omitempty"`
 }
+type Operation_TreeStyle_ struct {
+	TreeStyle *Operation_TreeStyle `protobuf:"bytes,10,opt,name=tree_style,json=treeStyle,proto3,oneof" json:"tree_style,omitempty"`
+}
 
-func (*Operation_Set_) isOperation_Body()      {}
-func (*Operation_Add_) isOperation_Body()      {}
-func (*Operation_Move_) isOperation_Body()     {}
-func (*Operation_Remove_) isOperation_Body()   {}
-func (*Operation_Edit_) isOperation_Body()     {}
-func (*Operation_Select_) isOperation_Body()   {}
-func (*Operation_Style_) isOperation_Body()    {}
-func (*Operation_Increase_) isOperation_Body() {}
-func (*Operation_TreeEdit_) isOperation_Body() {}
+func (*Operation_Set_) isOperation_Body()       {}
+func (*Operation_Add_) isOperation_Body()       {}
+func (*Operation_Move_) isOperation_Body()      {}
+func (*Operation_Remove_) isOperation_Body()    {}
+func (*Operation_Edit_) isOperation_Body()      {}
+func (*Operation_Select_) isOperation_Body()    {}
+func (*Operation_Style_) isOperation_Body()     {}
+func (*Operation_Increase_) isOperation_Body()  {}
+func (*Operation_TreeEdit_) isOperation_Body()  {}
+func (*Operation_TreeStyle_) isOperation_Body() {}
 
 func (m *Operation) GetBody() isOperation_Body {
 	if m != nil {
@@ -503,6 +508,13 @@ func (m *Operation) GetTreeEdit() *Operation_TreeEdit {
 	return nil
 }
 
+func (m *Operation) GetTreeStyle() *Operation_TreeStyle {
+	if x, ok := m.GetBody().(*Operation_TreeStyle_); ok {
+		return x.TreeStyle
+	}
+	return nil
+}
+
 // XXX_OneofWrappers is for the internal use of the proto package.
 func (*Operation) XXX_OneofWrappers() []interface{} {
 	return []interface{}{
@@ -515,6 +527,7 @@ func (*Operation) XXX_OneofWrappers() []interface{} {
 		(*Operation_Style_)(nil),
 		(*Operation_Increase_)(nil),
 		(*Operation_TreeEdit_)(nil),
+		(*Operation_TreeStyle_)(nil),
 	}
 }
 
@@ -1181,6 +1194,85 @@ func (m *Operation_TreeEdit) GetExecutedAt() *TimeTicket {
 	return nil
 }
 
+type Operation_TreeStyle struct {
+	ParentCreatedAt      *TimeTicket       `protobuf:"bytes,1,opt,name=parent_created_at,json=parentCreatedAt,proto3" json:"parent_created_at,omitempty"`
+	From                 *TreePos          `protobuf:"bytes,2,opt,name=from,proto3" json:"from,omitempty"`
+	To                   *TreePos          `protobuf:"bytes,3,opt,name=to,proto3" json:"to,omitempty"`
+	Attributes           map[string]string `protobuf:"bytes,4,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	ExecutedAt           *TimeTicket       `protobuf:"bytes,5,opt,name=executed_at,json=executedAt,proto3" json:"executed_at,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}          `json:"-"`
+	XXX_unrecognized     []byte            `json:"-"`
+	XXX_sizecache        int32             `json:"-"`
+}
+
+func (m *Operation_TreeStyle) Reset()         { *m = Operation_TreeStyle{} }
+func (m *Operation_TreeStyle) String() string { return proto.CompactTextString(m) }
+func (*Operation_TreeStyle) ProtoMessage()    {}
+func (*Operation_TreeStyle) Descriptor() ([]byte, []int) {
+	return fileDescriptor_36361b2f5d0f0896, []int{3, 9}
+}
+func (m *Operation_TreeStyle) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Operation_TreeStyle) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Operation_TreeStyle.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Operation_TreeStyle) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Operation_TreeStyle.Merge(m, src)
+}
+func (m *Operation_TreeStyle) XXX_Size() int {
+	return m.Size()
+}
+func (m *Operation_TreeStyle) XXX_DiscardUnknown() {
+	xxx_messageInfo_Operation_TreeStyle.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Operation_TreeStyle proto.InternalMessageInfo
+
+func (m *Operation_TreeStyle) GetParentCreatedAt() *TimeTicket {
+	if m != nil {
+		return m.ParentCreatedAt
+	}
+	return nil
+}
+
+func (m *Operation_TreeStyle) GetFrom() *TreePos {
+	if m != nil {
+		return m.From
+	}
+	return nil
+}
+
+func (m *Operation_TreeStyle) GetTo() *TreePos {
+	if m != nil {
+		return m.To
+	}
+	return nil
+}
+
+func (m *Operation_TreeStyle) GetAttributes() map[string]string {
+	if m != nil {
+		return m.Attributes
+	}
+	return nil
+}
+
+func (m *Operation_TreeStyle) GetExecutedAt() *TimeTicket {
+	if m != nil {
+		return m.ExecutedAt
+	}
+	return nil
+}
+
 type JSONElementSimple struct {
 	CreatedAt            *TimeTicket `protobuf:"bytes,1,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
 	MovedAt              *TimeTicket `protobuf:"bytes,2,opt,name=moved_at,json=movedAt,proto3" json:"moved_at,omitempty"`
@@ -1953,7 +2045,7 @@ func (m *RGANode) GetElement() *JSONElement {
 	return nil
 }
 
-type TextNodeAttr struct {
+type NodeAttr struct {
 	Value                string      `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
 	UpdatedAt            *TimeTicket `protobuf:"bytes,2,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"`
 	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
@@ -1961,18 +2053,18 @@ type TextNodeAttr struct {
 	XXX_sizecache        int32       `json:"-"`
 }
 
-func (m *TextNodeAttr) Reset()         { *m = TextNodeAttr{} }
-func (m *TextNodeAttr) String() string { return proto.CompactTextString(m) }
-func (*TextNodeAttr) ProtoMessage()    {}
-func (*TextNodeAttr) Descriptor() ([]byte, []int) {
+func (m *NodeAttr) Reset()         { *m = NodeAttr{} }
+func (m *NodeAttr) String() string { return proto.CompactTextString(m) }
+func (*NodeAttr) ProtoMessage()    {}
+func (*NodeAttr) Descriptor() ([]byte, []int) {
 	return fileDescriptor_36361b2f5d0f0896, []int{8}
 }
-func (m *TextNodeAttr) XXX_Unmarshal(b []byte) error {
+func (m *NodeAttr) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
 }
-func (m *TextNodeAttr) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+func (m *NodeAttr) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
 	if deterministic {
-		return xxx_messageInfo_TextNodeAttr.Marshal(b, m, deterministic)
+		return xxx_messageInfo_NodeAttr.Marshal(b, m, deterministic)
 	} else {
 		b = b[:cap(b)]
 		n, err := m.MarshalToSizedBuffer(b)
@@ -1982,26 +2074,26 @@ func (m *TextNodeAttr) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)
 		return b[:n], nil
 	}
 }
-func (m *TextNodeAttr) XXX_Merge(src proto.Message) {
-	xxx_messageInfo_TextNodeAttr.Merge(m, src)
+func (m *NodeAttr) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_NodeAttr.Merge(m, src)
 }
-func (m *TextNodeAttr) XXX_Size() int {
+func (m *NodeAttr) XXX_Size() int {
 	return m.Size()
 }
-func (m *TextNodeAttr) XXX_DiscardUnknown() {
-	xxx_messageInfo_TextNodeAttr.DiscardUnknown(m)
+func (m *NodeAttr) XXX_DiscardUnknown() {
+	xxx_messageInfo_NodeAttr.DiscardUnknown(m)
 }
 
-var xxx_messageInfo_TextNodeAttr proto.InternalMessageInfo
+var xxx_messageInfo_NodeAttr proto.InternalMessageInfo
 
-func (m *TextNodeAttr) GetValue() string {
+func (m *NodeAttr) GetValue() string {
 	if m != nil {
 		return m.Value
 	}
 	return ""
 }
 
-func (m *TextNodeAttr) GetUpdatedAt() *TimeTicket {
+func (m *NodeAttr) GetUpdatedAt() *TimeTicket {
 	if m != nil {
 		return m.UpdatedAt
 	}
@@ -2009,14 +2101,14 @@ func (m *TextNodeAttr) GetUpdatedAt() *TimeTicket {
 }
 
 type TextNode struct {
-	Id                   *TextNodeID              `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
-	Value                string                   `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
-	RemovedAt            *TimeTicket              `protobuf:"bytes,3,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"`
-	InsPrevId            *TextNodeID              `protobuf:"bytes,4,opt,name=ins_prev_id,json=insPrevId,proto3" json:"ins_prev_id,omitempty"`
-	Attributes           map[string]*TextNodeAttr `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
-	XXX_NoUnkeyedLiteral struct{}                 `json:"-"`
-	XXX_unrecognized     []byte                   `json:"-"`
-	XXX_sizecache        int32                    `json:"-"`
+	Id                   *TextNodeID          `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	Value                string               `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+	RemovedAt            *TimeTicket          `protobuf:"bytes,3,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"`
+	InsPrevId            *TextNodeID          `protobuf:"bytes,4,opt,name=ins_prev_id,json=insPrevId,proto3" json:"ins_prev_id,omitempty"`
+	Attributes           map[string]*NodeAttr `protobuf:"bytes,5,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	XXX_NoUnkeyedLiteral struct{}             `json:"-"`
+	XXX_unrecognized     []byte               `json:"-"`
+	XXX_sizecache        int32                `json:"-"`
 }
 
 func (m *TextNode) Reset()         { *m = TextNode{} }
@@ -2080,7 +2172,7 @@ func (m *TextNode) GetInsPrevId() *TextNodeID {
 	return nil
 }
 
-func (m *TextNode) GetAttributes() map[string]*TextNodeAttr {
+func (m *TextNode) GetAttributes() map[string]*NodeAttr {
 	if m != nil {
 		return m.Attributes
 	}
@@ -2143,15 +2235,16 @@ func (m *TextNodeID) GetOffset() int32 {
 }
 
 type TreeNode struct {
-	Pos                  *TreePos    `protobuf:"bytes,1,opt,name=pos,proto3" json:"pos,omitempty"`
-	Type                 string      `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
-	Value                string      `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
-	RemovedAt            *TimeTicket `protobuf:"bytes,4,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"`
-	InsPrevPos           *TreePos    `protobuf:"bytes,5,opt,name=ins_prev_pos,json=insPrevPos,proto3" json:"ins_prev_pos,omitempty"`
-	Depth                int32       `protobuf:"varint,6,opt,name=depth,proto3" json:"depth,omitempty"`
-	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
-	XXX_unrecognized     []byte      `json:"-"`
-	XXX_sizecache        int32       `json:"-"`
+	Pos                  *TreePos             `protobuf:"bytes,1,opt,name=pos,proto3" json:"pos,omitempty"`
+	Type                 string               `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
+	Value                string               `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"`
+	RemovedAt            *TimeTicket          `protobuf:"bytes,4,opt,name=removed_at,json=removedAt,proto3" json:"removed_at,omitempty"`
+	InsPrevPos           *TreePos             `protobuf:"bytes,5,opt,name=ins_prev_pos,json=insPrevPos,proto3" json:"ins_prev_pos,omitempty"`
+	Depth                int32                `protobuf:"varint,6,opt,name=depth,proto3" json:"depth,omitempty"`
+	Attributes           map[string]*NodeAttr `protobuf:"bytes,7,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+	XXX_NoUnkeyedLiteral struct{}             `json:"-"`
+	XXX_unrecognized     []byte               `json:"-"`
+	XXX_sizecache        int32                `json:"-"`
 }
 
 func (m *TreeNode) Reset()         { *m = TreeNode{} }
@@ -2229,6 +2322,13 @@ func (m *TreeNode) GetDepth() int32 {
 	return 0
 }
 
+func (m *TreeNode) GetAttributes() map[string]*NodeAttr {
+	if m != nil {
+		return m.Attributes
+	}
+	return nil
+}
+
 type TreePos struct {
 	CreatedAt            *TimeTicket `protobuf:"bytes,1,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
 	Offset               int32       `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"`
@@ -3040,6 +3140,8 @@ func init() {
 	proto.RegisterMapType((map[string]string)(nil), "yorkie.v1.Operation.Style.AttributesEntry")
 	proto.RegisterType((*Operation_Increase)(nil), "yorkie.v1.Operation.Increase")
 	proto.RegisterType((*Operation_TreeEdit)(nil), "yorkie.v1.Operation.TreeEdit")
+	proto.RegisterType((*Operation_TreeStyle)(nil), "yorkie.v1.Operation.TreeStyle")
+	proto.RegisterMapType((map[string]string)(nil), "yorkie.v1.Operation.TreeStyle.AttributesEntry")
 	proto.RegisterType((*JSONElementSimple)(nil), "yorkie.v1.JSONElementSimple")
 	proto.RegisterType((*JSONElement)(nil), "yorkie.v1.JSONElement")
 	proto.RegisterType((*JSONElement_JSONObject)(nil), "yorkie.v1.JSONElement.JSONObject")
@@ -3050,11 +3152,12 @@ func init() {
 	proto.RegisterType((*JSONElement_Tree)(nil), "yorkie.v1.JSONElement.Tree")
 	proto.RegisterType((*RHTNode)(nil), "yorkie.v1.RHTNode")
 	proto.RegisterType((*RGANode)(nil), "yorkie.v1.RGANode")
-	proto.RegisterType((*TextNodeAttr)(nil), "yorkie.v1.TextNodeAttr")
+	proto.RegisterType((*NodeAttr)(nil), "yorkie.v1.NodeAttr")
 	proto.RegisterType((*TextNode)(nil), "yorkie.v1.TextNode")
-	proto.RegisterMapType((map[string]*TextNodeAttr)(nil), "yorkie.v1.TextNode.AttributesEntry")
+	proto.RegisterMapType((map[string]*NodeAttr)(nil), "yorkie.v1.TextNode.AttributesEntry")
 	proto.RegisterType((*TextNodeID)(nil), "yorkie.v1.TextNodeID")
 	proto.RegisterType((*TreeNode)(nil), "yorkie.v1.TreeNode")
+	proto.RegisterMapType((map[string]*NodeAttr)(nil), "yorkie.v1.TreeNode.AttributesEntry")
 	proto.RegisterType((*TreePos)(nil), "yorkie.v1.TreePos")
 	proto.RegisterType((*User)(nil), "yorkie.v1.User")
 	proto.RegisterType((*Project)(nil), "yorkie.v1.Project")
@@ -3073,157 +3176,159 @@ func init() {
 func init() { proto.RegisterFile("yorkie/v1/resources.proto", fileDescriptor_36361b2f5d0f0896) }
 
 var fileDescriptor_36361b2f5d0f0896 = []byte{
-	// 2390 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5a, 0xcf, 0x8f, 0x1b, 0x49,
-	0xf5, 0x77, 0xfb, 0x77, 0x3f, 0x4f, 0x32, 0x4e, 0x4d, 0x7e, 0x38, 0xde, 0xcd, 0x64, 0xe2, 0xec,
-	0x37, 0xdf, 0xd9, 0x84, 0xf5, 0x24, 0x43, 0xc2, 0x8f, 0x0d, 0x8b, 0xf0, 0xd8, 0xbd, 0xf1, 0x64,
-	0x27, 0x9e, 0x51, 0xdb, 0x93, 0x90, 0x08, 0xd4, 0xea, 0xe9, 0xae, 0xc4, 0xbd, 0x63, 0x77, 0xf7,
-	0x76, 0x97, 0xbd, 0xf1, 0x01, 0x09, 0x21, 0x90, 0x40, 0xe2, 0xc2, 0x8d, 0x3f, 0x00, 0x09, 0xf1,
-	0x27, 0xec, 0x95, 0x03, 0xe2, 0x06, 0x88, 0x95, 0xb8, 0x70, 0x80, 0x70, 0x40, 0x1c, 0x11, 0x12,
-	0x37, 0x24, 0x54, 0x55, 0xdd, 0xed, 0x76, 0xbb, 0xed, 0x38, 0x66, 0x84, 0x12, 0x71, 0x73, 0x55,
-	0x7d, 0x5e, 0xd5, 0xfb, 0x55, 0xef, 0xbd, 0xea, 0x67, 0xb8, 0x38, 0xb2, 0x9c, 0x63, 0x03, 0x6f,
-	0x0d, 0x6f, 0x6d, 0x39, 0xd8, 0xb5, 0x06, 0x8e, 0x86, 0xdd, 0xaa, 0xed, 0x58, 0xc4, 0x42, 0x22,
-	0x5f, 0xaa, 0x0e, 0x6f, 0x95, 0x2f, 0x3f, 0xb3, 0xac, 0x67, 0x3d, 0xbc, 0xc5, 0x16, 0x8e, 0x06,
-	0x4f, 0xb7, 0x88, 0xd1, 0xc7, 0x2e, 0x51, 0xfb, 0x36, 0xc7, 0x96, 0xd7, 0xa3, 0x80, 0x4f, 0x1d,
-	0xd5, 0xb6, 0xb1, 0xe3, 0xed, 0x55, 0xf9, 0x49, 0x12, 0xa0, 0xde, 0x55, 0xcd, 0x67, 0xf8, 0x40,
-	0xd5, 0x8e, 0xd1, 0x15, 0x58, 0xd1, 0x2d, 0x6d, 0xd0, 0xc7, 0x26, 0x51, 0x8e, 0xf1, 0xa8, 0x24,
-	0x6c, 0x08, 0x9b, 0xa2, 0x5c, 0xf0, 0xe7, 0x3e, 0xc2, 0x23, 0x74, 0x07, 0x40, 0xeb, 0x62, 0xed,
-	0xd8, 0xb6, 0x0c, 0x93, 0x94, 0x92, 0x1b, 0xc2, 0x66, 0x61, 0xfb, 0x5c, 0x35, 0x60, 0xa9, 0x5a,
-	0x0f, 0x16, 0xe5, 0x10, 0x10, 0x95, 0x21, 0xef, 0x9a, 0xaa, 0xed, 0x76, 0x2d, 0x52, 0x4a, 0x6d,
-	0x08, 0x9b, 0x2b, 0x72, 0x30, 0x46, 0x37, 0x20, 0xa7, 0x31, 0x1e, 0xdc, 0x52, 0x7a, 0x23, 0xb5,
-	0x59, 0xd8, 0x3e, 0x33, 0xb1, 0x1f, 0x5d, 0x91, 0x7d, 0x04, 0xaa, 0xc1, 0x99, 0xbe, 0x61, 0x2a,
-	0xee, 0xc8, 0xd4, 0xb0, 0xae, 0x10, 0x43, 0x3b, 0xc6, 0xa4, 0x94, 0x99, 0x62, 0xa3, 0x63, 0xf4,
-	0x71, 0x87, 0x2d, 0xca, 0xab, 0x7d, 0xc3, 0x6c, 0x33, 0x38, 0x9f, 0x40, 0x97, 0x00, 0x0c, 0x57,
-	0x71, 0x70, 0xdf, 0x1a, 0x62, 0xbd, 0x94, 0xdd, 0x10, 0x36, 0xf3, 0xb2, 0x68, 0xb8, 0x32, 0x9f,
-	0xa8, 0x7c, 0x07, 0xb2, 0xfc, 0x50, 0x74, 0x15, 0x92, 0x86, 0xce, 0x94, 0x50, 0xd8, 0x5e, 0x9b,
-	0xe2, 0x69, 0xb7, 0x21, 0x27, 0x0d, 0x1d, 0x95, 0x20, 0xd7, 0xc7, 0xae, 0xab, 0x3e, 0xc3, 0x4c,
-	0x1b, 0xa2, 0xec, 0x0f, 0xd1, 0x6d, 0x00, 0xcb, 0xc6, 0x8e, 0x4a, 0x0c, 0xcb, 0x74, 0x4b, 0x29,
-	0x26, 0xda, 0xd9, 0xd0, 0x36, 0xfb, 0xfe, 0xa2, 0x1c, 0xc2, 0x55, 0x7e, 0x20, 0x40, 0xde, 0x3f,
-	0x80, 0xb2, 0xaa, 0xf5, 0x0c, 0x6a, 0x0e, 0x17, 0x7f, 0xc2, 0x38, 0x39, 0x25, 0x8b, 0x7c, 0xa6,
-	0x8d, 0x3f, 0x41, 0x57, 0x00, 0x5c, 0xec, 0x0c, 0xb1, 0xc3, 0x96, 0xe9, 0xf1, 0xa9, 0x9d, 0xe4,
-	0x4d, 0x41, 0x16, 0xf9, 0x2c, 0x85, 0xbc, 0x0d, 0xb9, 0x9e, 0xda, 0xb7, 0x2d, 0x87, 0xeb, 0x9d,
-	0xaf, 0xfb, 0x53, 0xe8, 0x22, 0xe4, 0x55, 0x8d, 0x58, 0x8e, 0x62, 0xe8, 0xa5, 0x34, 0x33, 0x4b,
-	0x8e, 0x8d, 0x77, 0xf5, 0xca, 0xcf, 0x2e, 0x80, 0x18, 0x70, 0x88, 0xbe, 0x00, 0x29, 0x17, 0x13,
-	0x4f, 0x17, 0xa5, 0x38, 0x21, 0xaa, 0x6d, 0x4c, 0x9a, 0x09, 0x99, 0xc2, 0x28, 0x5a, 0xd5, 0x75,
-	0xcf, 0x3b, 0xe2, 0xd1, 0x35, 0x5d, 0xa7, 0x68, 0x55, 0xd7, 0xd1, 0x16, 0xa4, 0xa9, 0xe6, 0x19,
-	0x7f, 0x85, 0xed, 0x8b, 0xb1, 0xf0, 0x07, 0xd6, 0x10, 0x37, 0x13, 0x32, 0x03, 0xa2, 0x3b, 0x90,
-	0xe5, 0xd6, 0x63, 0x3c, 0x17, 0xb6, 0xdf, 0x8a, 0x25, 0xe1, 0xf6, 0x6c, 0x26, 0x64, 0x0f, 0x4c,
-	0xcf, 0xc1, 0xba, 0xe1, 0x7b, 0x4b, 0xfc, 0x39, 0x92, 0x6e, 0x50, 0x29, 0x18, 0x90, 0x9e, 0xe3,
-	0xe2, 0x1e, 0xd6, 0x08, 0x73, 0x92, 0x59, 0xe7, 0xb4, 0x19, 0x84, 0x9e, 0xc3, 0xc1, 0x68, 0x1b,
-	0x32, 0x2e, 0x19, 0xf5, 0x70, 0x29, 0xc7, 0xa8, 0xca, 0xf1, 0x54, 0x14, 0xd1, 0x4c, 0xc8, 0x1c,
-	0x8a, 0xee, 0x42, 0xde, 0x30, 0x35, 0x07, 0xab, 0x2e, 0x2e, 0xe5, 0x19, 0xd9, 0xa5, 0x58, 0xb2,
-	0x5d, 0x0f, 0xd4, 0x4c, 0xc8, 0x01, 0x01, 0xfa, 0x1a, 0x88, 0xc4, 0xc1, 0x58, 0x61, 0xd2, 0x89,
-	0x73, 0xa8, 0x3b, 0x0e, 0xc6, 0x9e, 0x84, 0x79, 0xe2, 0xfd, 0x2e, 0xff, 0x4a, 0x80, 0x54, 0x1b,
-	0x13, 0x7a, 0xb3, 0x6c, 0xd5, 0xa1, 0xbe, 0x46, 0xb7, 0x25, 0x58, 0x57, 0x54, 0xdf, 0xe0, 0xb3,
-	0x6e, 0x16, 0xc7, 0xd7, 0x39, 0xbc, 0x46, 0x50, 0x11, 0x52, 0x34, 0x6c, 0xf0, 0x7b, 0x40, 0x7f,
-	0x52, 0x5d, 0x0c, 0xd5, 0xde, 0xc0, 0x37, 0xee, 0xdb, 0xa1, 0x8d, 0xee, 0xb7, 0xf7, 0x5b, 0x52,
-	0x0f, 0xd3, 0xc0, 0xd2, 0x36, 0xfa, 0x76, 0x0f, 0xcb, 0x1c, 0x8a, 0xbe, 0x04, 0x05, 0xfc, 0x1c,
-	0x6b, 0x03, 0x8f, 0x85, 0xf4, 0x3c, 0x16, 0xc0, 0x47, 0xd6, 0x48, 0xf9, 0x1f, 0x02, 0xa4, 0x6a,
-	0xba, 0x7e, 0x12, 0x82, 0x7c, 0x00, 0xab, 0xb6, 0x83, 0x87, 0xe1, 0x0d, 0x92, 0xf3, 0x36, 0x38,
-	0x45, 0xd1, 0x63, 0xf2, 0xff, 0xa6, 0xd4, 0xff, 0x14, 0x20, 0x4d, 0x6f, 0xc7, 0x6b, 0x20, 0xf6,
-	0x6d, 0x80, 0x10, 0x65, 0x6a, 0x1e, 0xa5, 0xa8, 0x05, 0x54, 0xcb, 0x0a, 0xfe, 0x99, 0x00, 0x59,
-	0x7e, 0xc7, 0x4f, 0x42, 0xf4, 0x49, 0xde, 0x93, 0xcb, 0xf1, 0x9e, 0x5a, 0x94, 0xf7, 0x5f, 0xa6,
-	0x21, 0x4d, 0x2f, 0xdf, 0x49, 0x70, 0x7e, 0x1d, 0xd2, 0x4f, 0x1d, 0xab, 0xef, 0xf1, 0x7c, 0x3e,
-	0x4c, 0x85, 0x9f, 0x93, 0x96, 0xa5, 0xe3, 0x03, 0xcb, 0x95, 0x19, 0x06, 0x5d, 0x83, 0x24, 0xb1,
-	0x3c, 0x36, 0x67, 0x21, 0x93, 0xc4, 0x42, 0x5d, 0xb8, 0x30, 0xe6, 0x47, 0xe9, 0xab, 0xb6, 0x72,
-	0x34, 0x52, 0x58, 0x66, 0xf0, 0x52, 0xf4, 0xf6, 0xcc, 0xe8, 0x59, 0x0d, 0x38, 0x7b, 0xa0, 0xda,
-	0x3b, 0xa3, 0x1a, 0x25, 0x92, 0x4c, 0xe2, 0x8c, 0xe4, 0x35, 0x6d, 0x7a, 0x85, 0xa6, 0x4f, 0xcd,
-	0x32, 0x09, 0x36, 0x79, 0x5c, 0x16, 0x65, 0x7f, 0x18, 0xd5, 0x6d, 0x76, 0x41, 0xdd, 0xa2, 0x5d,
-	0x00, 0x95, 0x10, 0xc7, 0x38, 0x1a, 0x10, 0xec, 0x96, 0x72, 0x8c, 0xdd, 0x77, 0x67, 0xb3, 0x5b,
-	0x0b, 0xb0, 0x9c, 0xcb, 0x10, 0x71, 0xf9, 0xdb, 0x50, 0x9a, 0x25, 0x8d, 0x1f, 0xeb, 0x84, 0x71,
-	0xac, 0xbb, 0xe1, 0xdf, 0xfa, 0xb9, 0xde, 0xc3, 0x31, 0xef, 0x27, 0xbf, 0x22, 0x94, 0x3f, 0x80,
-	0xd5, 0xc8, 0xe9, 0x31, 0xbb, 0x9e, 0x0d, 0xef, 0x2a, 0x86, 0xc9, 0xff, 0x20, 0x40, 0x96, 0x27,
-	0x9f, 0xd7, 0xd5, 0x8d, 0x96, 0xbd, 0xda, 0x7f, 0x4e, 0x42, 0x86, 0x25, 0xc8, 0xd7, 0x55, 0xb0,
-	0xfb, 0x13, 0x3e, 0xc6, 0xaf, 0xc4, 0xf5, 0xd9, 0x79, 0x7e, 0x9e, 0x93, 0x45, 0x95, 0x94, 0x59,
-	0x54, 0x49, 0xff, 0xa1, 0xf7, 0x7c, 0x26, 0x40, 0xde, 0xaf, 0x26, 0x4e, 0x42, 0xcd, 0xdb, 0x93,
-	0xde, 0xbf, 0x4c, 0xce, 0x5b, 0x38, 0x7c, 0x7e, 0x37, 0x09, 0x79, 0xbf, 0x96, 0x39, 0x09, 0xde,
-	0xaf, 0x4d, 0xb8, 0x08, 0x0a, 0x53, 0x39, 0x38, 0xe4, 0x1e, 0x95, 0x90, 0x7b, 0xc4, 0xa1, 0xa8,
-	0x6b, 0xbc, 0x37, 0x0e, 0x68, 0xdc, 0x2f, 0xd6, 0x22, 0x40, 0xea, 0x47, 0x33, 0xa3, 0xdc, 0xa2,
-	0xd6, 0xdf, 0xc9, 0x42, 0xfa, 0xc8, 0xd2, 0x47, 0x95, 0xbf, 0x0b, 0x70, 0x66, 0x4a, 0xbf, 0x91,
-	0x6c, 0x26, 0x2c, 0x98, 0xcd, 0x6e, 0x42, 0x9e, 0x3d, 0x81, 0x5e, 0x9a, 0x01, 0x73, 0x0c, 0xc6,
-	0xb3, 0xa6, 0xf7, 0x8e, 0x7a, 0x79, 0xc6, 0xf7, 0x80, 0x35, 0x82, 0x36, 0x21, 0x4d, 0x46, 0x36,
-	0xaf, 0xde, 0x4f, 0x4f, 0x3c, 0x89, 0x1e, 0x52, 0xb7, 0xe8, 0x8c, 0x6c, 0x2c, 0x33, 0xc4, 0xd8,
-	0x7d, 0x33, 0xec, 0x71, 0xc2, 0x07, 0x95, 0x5f, 0x9c, 0x82, 0x42, 0x48, 0x66, 0xd4, 0x80, 0xc2,
-	0xc7, 0xae, 0x65, 0x2a, 0xd6, 0xd1, 0xc7, 0xb4, 0x58, 0xe7, 0xe2, 0x5e, 0x89, 0x77, 0x40, 0xf6,
-	0x7b, 0x9f, 0x01, 0x9b, 0x09, 0x19, 0x28, 0x1d, 0x1f, 0xa1, 0x1a, 0xb0, 0x91, 0xa2, 0x3a, 0x8e,
-	0x3a, 0xf2, 0xe4, 0xdf, 0x98, 0xb3, 0x49, 0x8d, 0xe2, 0x9a, 0x09, 0x59, 0xa4, 0x54, 0x6c, 0x80,
-	0xbe, 0x01, 0xa2, 0xed, 0x18, 0x7d, 0x83, 0x18, 0xc1, 0x73, 0x66, 0xd6, 0x0e, 0x07, 0x3e, 0x8e,
-	0xee, 0x10, 0x10, 0xa1, 0x5b, 0x90, 0x26, 0xf8, 0xb9, 0xef, 0x07, 0x6f, 0xcd, 0x20, 0xa6, 0xe1,
-	0x88, 0xbe, 0x52, 0x28, 0x14, 0xbd, 0x4f, 0x1d, 0x6e, 0x60, 0x12, 0xec, 0x78, 0x39, 0x72, 0x7d,
-	0x06, 0x55, 0x9d, 0xa3, 0x9a, 0x09, 0xd9, 0x27, 0x60, 0xc7, 0x39, 0xd8, 0x7f, 0xa9, 0xcc, 0x3c,
-	0xce, 0xc1, 0xec, 0xf1, 0x45, 0xa1, 0xe5, 0xcf, 0x05, 0x80, 0xb1, 0x0e, 0xd1, 0x26, 0x64, 0x4c,
-	0x4b, 0xc7, 0x6e, 0x49, 0x60, 0xce, 0x1e, 0xbe, 0x15, 0x72, 0xb3, 0xc3, 0x7c, 0x9d, 0x03, 0x96,
-	0xac, 0xb0, 0xc2, 0x3e, 0x99, 0x5a, 0xc2, 0x27, 0xd3, 0x8b, 0xf9, 0x64, 0xf9, 0xf7, 0x02, 0x88,
-	0x81, 0x55, 0xe7, 0x4a, 0x75, 0xaf, 0xf6, 0xe6, 0x48, 0xf5, 0x37, 0x01, 0xc4, 0xc0, 0xd3, 0x82,
-	0x7b, 0x27, 0x2c, 0x7e, 0xef, 0x92, 0xa1, 0x7b, 0xb7, 0x64, 0x7d, 0x1f, 0x96, 0x35, 0xbd, 0x84,
-	0xac, 0x99, 0x05, 0x65, 0xfd, 0x8d, 0x00, 0x69, 0x7a, 0x31, 0xd0, 0xbb, 0x93, 0xc6, 0x5b, 0x8b,
-	0xc9, 0xe3, 0x6f, 0x86, 0xf5, 0xfe, 0x2a, 0x40, 0xce, 0xbb, 0xb4, 0xff, 0x0b, 0xb6, 0x73, 0x30,
-	0x9e, 0x6b, 0x3b, 0x3f, 0x77, 0xbe, 0x09, 0xb6, 0x0b, 0xf2, 0xf3, 0x03, 0xc8, 0x79, 0x71, 0x30,
-	0xa6, 0x3a, 0xbb, 0x09, 0x39, 0xcc, 0x63, 0x6c, 0x4c, 0x75, 0x1a, 0x8a, 0xc0, 0xb2, 0x0f, 0xab,
-	0x68, 0x90, 0xf3, 0x02, 0x10, 0x2d, 0x5a, 0x4c, 0x9a, 0x2a, 0x84, 0xa9, 0x72, 0xc4, 0x0f, 0x51,
-	0x6c, 0x7d, 0x89, 0x43, 0x9e, 0xc0, 0x8a, 0x7f, 0x51, 0x68, 0x85, 0x39, 0xf6, 0x28, 0x21, 0x54,
-	0x44, 0x52, 0xbd, 0x0c, 0x6c, 0x7d, 0x31, 0xfd, 0x7b, 0xc0, 0x1a, 0xa9, 0xfc, 0x8e, 0x96, 0x6e,
-	0xde, 0xe6, 0xe8, 0xff, 0x42, 0x1f, 0x58, 0xcf, 0xc5, 0x5c, 0x53, 0xef, 0x13, 0x6b, 0x6c, 0x11,
-	0xbb, 0x64, 0xed, 0x71, 0x07, 0x0a, 0x86, 0xe9, 0x2a, 0xec, 0x33, 0x87, 0xf7, 0xd1, 0x73, 0xe6,
-	0xd9, 0xa2, 0x61, 0xba, 0x07, 0x0e, 0x1e, 0xee, 0xea, 0xa8, 0x3e, 0x51, 0xf0, 0x67, 0x98, 0x73,
-	0x5e, 0x8d, 0xa1, 0x9a, 0xfb, 0x9c, 0x7c, 0xb8, 0x48, 0xc5, 0xfe, 0xde, 0x64, 0x1d, 0x7d, 0x21,
-	0xe6, 0x10, 0xba, 0x49, 0xa8, 0x94, 0xaf, 0x3c, 0x01, 0x18, 0x73, 0xbd, 0x64, 0xed, 0x77, 0x1e,
-	0xb2, 0xd6, 0xd3, 0xa7, 0x2e, 0xe6, 0x96, 0xcc, 0xc8, 0xde, 0xa8, 0xf2, 0x47, 0x81, 0x97, 0xda,
-	0xcc, 0x5e, 0xef, 0x40, 0xca, 0xb6, 0xdc, 0x18, 0x8f, 0xf3, 0x0b, 0x60, 0xba, 0x8c, 0x90, 0x17,
-	0xaa, 0xb8, 0xb5, 0x22, 0x41, 0x29, 0x35, 0xdb, 0x84, 0x0b, 0x5e, 0x2d, 0x74, 0x1b, 0x56, 0x02,
-	0x13, 0x52, 0x76, 0x32, 0x33, 0xd9, 0x01, 0xcf, 0x80, 0x07, 0x96, 0x4b, 0x39, 0xd0, 0xb1, 0x4d,
-	0xba, 0xac, 0x48, 0xca, 0xc8, 0x7c, 0x50, 0x79, 0x04, 0x39, 0x0f, 0x7c, 0xc2, 0x7a, 0xeb, 0x43,
-	0xfa, 0xd0, 0xc5, 0x0e, 0x3a, 0x1d, 0xb8, 0xb8, 0xc8, 0x7c, 0xb9, 0x0c, 0xf9, 0x81, 0x8b, 0x1d,
-	0x53, 0xed, 0xfb, 0x0a, 0x0a, 0xc6, 0xe8, 0xab, 0x31, 0x31, 0xba, 0x5c, 0xe5, 0x2d, 0x9c, 0xaa,
-	0xdf, 0xc2, 0x61, 0x7c, 0xb0, 0x1e, 0x4f, 0x88, 0x8d, 0xca, 0xbf, 0x92, 0x90, 0x3b, 0x70, 0x2c,
-	0x56, 0x92, 0x45, 0x8f, 0x44, 0x90, 0x0e, 0x1d, 0xc7, 0x7e, 0xa3, 0x4b, 0x00, 0xf6, 0xe0, 0xa8,
-	0x67, 0x68, 0xac, 0xcf, 0xc3, 0x8d, 0x22, 0xf2, 0x99, 0x8f, 0xf0, 0x88, 0x2e, 0xbb, 0x58, 0x73,
-	0x30, 0x6f, 0x03, 0xa5, 0xf9, 0x32, 0x9f, 0xa1, 0xcb, 0x9b, 0x50, 0x54, 0x07, 0xa4, 0xab, 0x7c,
-	0x8a, 0x8f, 0xba, 0x96, 0x75, 0xac, 0x0c, 0x9c, 0x9e, 0xf7, 0xf5, 0xe6, 0x34, 0x9d, 0x7f, 0xc4,
-	0xa7, 0x0f, 0x9d, 0x1e, 0xba, 0x09, 0x67, 0x27, 0x90, 0x7d, 0x4c, 0xba, 0x96, 0xee, 0x96, 0xb2,
-	0x1b, 0xa9, 0x4d, 0x51, 0x46, 0x21, 0xf4, 0x03, 0xbe, 0x82, 0xbe, 0x0e, 0x6f, 0x79, 0x2d, 0x0f,
-	0x1d, 0xab, 0x1a, 0x31, 0x86, 0x2a, 0xc1, 0x0a, 0xe9, 0x3a, 0xd8, 0xed, 0x5a, 0x3d, 0x9d, 0x55,
-	0xaa, 0xa2, 0x7c, 0x91, 0x43, 0x1a, 0x01, 0xa2, 0xe3, 0x03, 0x22, 0x4a, 0xcc, 0xbf, 0x82, 0x12,
-	0x29, 0x69, 0x28, 0xa2, 0x89, 0x2f, 0x27, 0x1d, 0x87, 0xb5, 0x1f, 0xa6, 0xe0, 0xfc, 0x21, 0x1d,
-	0xa9, 0x47, 0x3d, 0xec, 0x19, 0xe2, 0x43, 0x03, 0xf7, 0x74, 0x17, 0xdd, 0xf4, 0xd4, 0x2f, 0x78,
-	0xef, 0xe2, 0xe8, 0x7e, 0x6d, 0xe2, 0x18, 0xe6, 0x33, 0x96, 0xc5, 0x3d, 0xe3, 0x7c, 0x18, 0xa3,
-	0xde, 0xe4, 0x02, 0xd4, 0x51, 0xe5, 0x3f, 0x9d, 0xa1, 0x7c, 0xee, 0x59, 0xb7, 0x43, 0xbe, 0x1d,
-	0xcf, 0x7a, 0xb5, 0x36, 0x65, 0x9e, 0x58, 0x93, 0x7d, 0x6b, 0xbe, 0xc9, 0xd2, 0x0b, 0xb0, 0x3e,
-	0xdb, 0xa0, 0xe5, 0x2a, 0xa0, 0x69, 0x3e, 0x78, 0xdb, 0x8d, 0x8b, 0x23, 0x30, 0x5f, 0xf2, 0x87,
-	0x95, 0xef, 0x25, 0x61, 0xb5, 0xe1, 0x75, 0x2c, 0xdb, 0x83, 0x7e, 0x5f, 0x75, 0x46, 0x53, 0x57,
-	0x62, 0xba, 0x51, 0x11, 0x6d, 0x50, 0x8a, 0xa1, 0x06, 0xe5, 0xa4, 0x4b, 0xa5, 0x5f, 0xc5, 0xa5,
-	0xee, 0x42, 0x41, 0xd5, 0x34, 0xec, 0xba, 0xe1, 0x7a, 0x68, 0x1e, 0x2d, 0xf8, 0xf0, 0x29, 0x7f,
-	0xcc, 0xbe, 0x8a, 0x3f, 0xfe, 0x58, 0x80, 0xfc, 0x81, 0x83, 0x5d, 0x6c, 0x6a, 0x2c, 0xf8, 0x6a,
-	0x3d, 0x4b, 0x3b, 0x66, 0x0a, 0xc8, 0xc8, 0x7c, 0x40, 0xdf, 0x7e, 0xd4, 0xe8, 0xa5, 0x24, 0x4b,
-	0x66, 0xe1, 0x86, 0x91, 0x4f, 0x58, 0x6d, 0xa8, 0x44, 0xe5, 0x69, 0x8c, 0x41, 0xcb, 0x5f, 0x06,
-	0x31, 0x98, 0x7a, 0x95, 0x8f, 0x4d, 0x95, 0x5d, 0xc8, 0xd6, 0x99, 0x81, 0x43, 0x96, 0x58, 0x61,
-	0x96, 0xd8, 0x82, 0xbc, 0xed, 0x1d, 0xe7, 0xf9, 0xf8, 0x5a, 0x0c, 0x27, 0x72, 0x00, 0xaa, 0xb4,
-	0x00, 0xc6, 0x3d, 0xe6, 0x48, 0x07, 0x54, 0x88, 0xeb, 0x80, 0x4e, 0xf6, 0x50, 0x93, 0x91, 0x1e,
-	0x6a, 0xe5, 0xfb, 0x02, 0x14, 0x42, 0x9f, 0xf7, 0x4e, 0x36, 0x0d, 0xa0, 0xff, 0x87, 0x55, 0x07,
-	0xf7, 0x54, 0xfa, 0xfc, 0x52, 0x3c, 0x40, 0x8a, 0x01, 0x4e, 0xfb, 0xd3, 0xfb, 0x3c, 0x5f, 0x68,
-	0x00, 0xe3, 0x9d, 0xc3, 0x5d, 0x5b, 0x61, 0xba, 0x6b, 0xfb, 0x36, 0x88, 0x3a, 0xee, 0xd1, 0x57,
-	0x1d, 0x76, 0x7c, 0x81, 0x82, 0x89, 0x89, 0x9e, 0x6e, 0x6a, 0xb2, 0xa7, 0xfb, 0x23, 0x01, 0xf2,
-	0x0d, 0x4b, 0x93, 0x86, 0xd4, 0x12, 0x37, 0x26, 0x5e, 0x14, 0xe1, 0x3a, 0xc3, 0x87, 0x84, 0x1e,
-	0x15, 0x5b, 0xc0, 0xb3, 0x83, 0xdb, 0xf5, 0x8e, 0x8c, 0x74, 0xe9, 0x99, 0x3a, 0xe5, 0x31, 0x06,
-	0x5d, 0x86, 0xe0, 0x6f, 0x03, 0x3e, 0x23, 0xa2, 0x0c, 0xfe, 0xd4, 0xae, 0x7e, 0xfd, 0xf3, 0x24,
-	0x88, 0xc1, 0xd3, 0x05, 0xad, 0xc1, 0xea, 0xc3, 0xda, 0xde, 0xa1, 0xa4, 0x74, 0x1e, 0x1f, 0x48,
-	0x4a, 0xeb, 0x70, 0x6f, 0xaf, 0x98, 0x40, 0xe7, 0x01, 0x85, 0x26, 0x77, 0xf6, 0xf7, 0xf7, 0xa4,
-	0x5a, 0xab, 0x28, 0x44, 0xe6, 0x77, 0x5b, 0x1d, 0xe9, 0x9e, 0x24, 0x17, 0x93, 0x91, 0x4d, 0xf6,
-	0xf6, 0x5b, 0xf7, 0x8a, 0x29, 0x74, 0x0e, 0xce, 0x84, 0x26, 0x1b, 0xfb, 0x87, 0x3b, 0x7b, 0x52,
-	0x31, 0x1d, 0x99, 0x6e, 0x77, 0xe4, 0xdd, 0xd6, 0xbd, 0x62, 0x06, 0x9d, 0x85, 0x62, 0xf8, 0xc8,
-	0xc7, 0x1d, 0xa9, 0x5d, 0xcc, 0x46, 0x36, 0x6e, 0xd4, 0x3a, 0x52, 0x31, 0x87, 0xca, 0x70, 0x3e,
-	0x34, 0x49, 0x0b, 0x69, 0x65, 0x7f, 0xe7, 0xbe, 0x54, 0xef, 0x14, 0xf3, 0xe8, 0x22, 0x9c, 0x8b,
-	0xae, 0xd5, 0x64, 0xb9, 0xf6, 0xb8, 0x28, 0x46, 0xf6, 0xea, 0x48, 0xdf, 0xec, 0x14, 0x21, 0xb2,
-	0x97, 0x27, 0x91, 0x52, 0x6f, 0x75, 0x8a, 0x05, 0x74, 0x01, 0xd6, 0x22, 0x52, 0xb1, 0x85, 0x95,
-	0xe8, 0x4e, 0xb2, 0x24, 0x15, 0x4f, 0x5d, 0xff, 0xb9, 0x00, 0x2b, 0x61, 0xfb, 0xa1, 0x77, 0x60,
-	0xa3, 0xb1, 0x5f, 0x57, 0xa4, 0x87, 0x52, 0xab, 0xe3, 0xeb, 0xa0, 0x7e, 0xf8, 0x40, 0x6a, 0x75,
-	0xda, 0x4a, 0xbd, 0x59, 0x6b, 0xdd, 0x93, 0x1a, 0xc5, 0xc4, 0x5c, 0xd4, 0xa3, 0x5a, 0xa7, 0xde,
-	0x94, 0x1a, 0x45, 0x01, 0x5d, 0x83, 0xca, 0x4c, 0xd4, 0x61, 0xcb, 0xc7, 0x25, 0xd1, 0x55, 0xb8,
-	0x1c, 0xc1, 0x1d, 0xc8, 0x52, 0x5b, 0x6a, 0xd5, 0xa5, 0xe0, 0xc8, 0xd4, 0xce, 0x8d, 0x5f, 0xbf,
-	0x58, 0x17, 0x7e, 0xfb, 0x62, 0x5d, 0xf8, 0xd3, 0x8b, 0x75, 0xe1, 0xa7, 0x7f, 0x59, 0x4f, 0xc0,
-	0x19, 0x1d, 0x0f, 0x7d, 0xa7, 0x52, 0x6d, 0xa3, 0x3a, 0xbc, 0x75, 0x20, 0x3c, 0x49, 0x57, 0xef,
-	0x0e, 0x6f, 0x1d, 0x65, 0x59, 0xb8, 0xfb, 0xe2, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x77,
-	0xdc, 0x0c, 0x18, 0x23, 0x00, 0x00,
+	// 2432 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5a, 0xcd, 0x8f, 0x1b, 0x49,
+	0x15, 0x9f, 0xb6, 0xdb, 0x1f, 0xfd, 0x3c, 0xc9, 0x38, 0x35, 0x49, 0xd6, 0xf1, 0x6e, 0x26, 0x13,
+	0x67, 0x09, 0x93, 0x04, 0x3c, 0xc9, 0x90, 0xf0, 0xb1, 0x61, 0x01, 0x8f, 0xdd, 0x9b, 0x99, 0xec,
+	0xc4, 0x33, 0x6a, 0x7b, 0x12, 0xb2, 0x02, 0xb5, 0x7a, 0xba, 0x2b, 0x71, 0xef, 0xd8, 0xdd, 0xbd,
+	0xdd, 0x65, 0x6f, 0x7c, 0x40, 0x42, 0x08, 0x24, 0x90, 0xb8, 0x70, 0xe3, 0x3f, 0x40, 0xfc, 0x09,
+	0x7b, 0xe5, 0x80, 0xe0, 0x80, 0x40, 0x62, 0x25, 0xae, 0x10, 0x0e, 0x88, 0x23, 0x42, 0x70, 0x43,
+	0x42, 0x55, 0xd5, 0xdd, 0x6e, 0xb7, 0xdb, 0x8e, 0x63, 0x46, 0xab, 0x44, 0xdc, 0x5c, 0x55, 0xbf,
+	0x57, 0xf5, 0xbe, 0xeb, 0xbd, 0x2e, 0xc3, 0x85, 0xa1, 0xed, 0x1e, 0x9b, 0x78, 0x73, 0x70, 0x6b,
+	0xd3, 0xc5, 0x9e, 0xdd, 0x77, 0x75, 0xec, 0x55, 0x1d, 0xd7, 0x26, 0x36, 0x92, 0xf8, 0x52, 0x75,
+	0x70, 0xab, 0x7c, 0xe9, 0xa9, 0x6d, 0x3f, 0xed, 0xe2, 0x4d, 0xb6, 0x70, 0xd4, 0x7f, 0xb2, 0x49,
+	0xcc, 0x1e, 0xf6, 0x88, 0xd6, 0x73, 0x38, 0xb6, 0xbc, 0x16, 0x07, 0x7c, 0xec, 0x6a, 0x8e, 0x83,
+	0x5d, 0x7f, 0xaf, 0xca, 0xcf, 0x52, 0x00, 0xf5, 0x8e, 0x66, 0x3d, 0xc5, 0x07, 0x9a, 0x7e, 0x8c,
+	0x2e, 0xc3, 0xb2, 0x61, 0xeb, 0xfd, 0x1e, 0xb6, 0x88, 0x7a, 0x8c, 0x87, 0x25, 0x61, 0x5d, 0xd8,
+	0x90, 0x94, 0x42, 0x30, 0xf7, 0x3e, 0x1e, 0xa2, 0x3b, 0x00, 0x7a, 0x07, 0xeb, 0xc7, 0x8e, 0x6d,
+	0x5a, 0xa4, 0x94, 0x5a, 0x17, 0x36, 0x0a, 0x5b, 0xe7, 0xaa, 0x21, 0x4b, 0xd5, 0x7a, 0xb8, 0xa8,
+	0x44, 0x80, 0xa8, 0x0c, 0x79, 0xcf, 0xd2, 0x1c, 0xaf, 0x63, 0x93, 0x52, 0x7a, 0x5d, 0xd8, 0x58,
+	0x56, 0xc2, 0x31, 0xba, 0x01, 0x39, 0x9d, 0xf1, 0xe0, 0x95, 0xc4, 0xf5, 0xf4, 0x46, 0x61, 0xeb,
+	0xcc, 0xd8, 0x7e, 0x74, 0x45, 0x09, 0x10, 0xa8, 0x06, 0x67, 0x7a, 0xa6, 0xa5, 0x7a, 0x43, 0x4b,
+	0xc7, 0x86, 0x4a, 0x4c, 0xfd, 0x18, 0x93, 0x52, 0x66, 0x82, 0x8d, 0xb6, 0xd9, 0xc3, 0x6d, 0xb6,
+	0xa8, 0xac, 0xf4, 0x4c, 0xab, 0xc5, 0xe0, 0x7c, 0x02, 0x5d, 0x04, 0x30, 0x3d, 0xd5, 0xc5, 0x3d,
+	0x7b, 0x80, 0x8d, 0x52, 0x76, 0x5d, 0xd8, 0xc8, 0x2b, 0x92, 0xe9, 0x29, 0x7c, 0xa2, 0xf2, 0x3d,
+	0xc8, 0xf2, 0x43, 0xd1, 0x15, 0x48, 0x99, 0x06, 0x53, 0x42, 0x61, 0x6b, 0x75, 0x82, 0xa7, 0xdd,
+	0x86, 0x92, 0x32, 0x0d, 0x54, 0x82, 0x5c, 0x0f, 0x7b, 0x9e, 0xf6, 0x14, 0x33, 0x6d, 0x48, 0x4a,
+	0x30, 0x44, 0xb7, 0x01, 0x6c, 0x07, 0xbb, 0x1a, 0x31, 0x6d, 0xcb, 0x2b, 0xa5, 0x99, 0x68, 0x67,
+	0x23, 0xdb, 0xec, 0x07, 0x8b, 0x4a, 0x04, 0x57, 0xf9, 0x91, 0x00, 0xf9, 0xe0, 0x00, 0xca, 0xaa,
+	0xde, 0x35, 0xa9, 0x39, 0x3c, 0xfc, 0x11, 0xe3, 0xe4, 0x94, 0x22, 0xf1, 0x99, 0x16, 0xfe, 0x08,
+	0x5d, 0x06, 0xf0, 0xb0, 0x3b, 0xc0, 0x2e, 0x5b, 0xa6, 0xc7, 0xa7, 0xb7, 0x53, 0x37, 0x05, 0x45,
+	0xe2, 0xb3, 0x14, 0xf2, 0x16, 0xe4, 0xba, 0x5a, 0xcf, 0xb1, 0x5d, 0xae, 0x77, 0xbe, 0x1e, 0x4c,
+	0xa1, 0x0b, 0x90, 0xd7, 0x74, 0x62, 0xbb, 0xaa, 0x69, 0x94, 0x44, 0x66, 0x96, 0x1c, 0x1b, 0xef,
+	0x1a, 0x95, 0xdf, 0x96, 0x41, 0x0a, 0x39, 0x44, 0x5f, 0x80, 0xb4, 0x87, 0x89, 0xaf, 0x8b, 0x52,
+	0x92, 0x10, 0xd5, 0x16, 0x26, 0x3b, 0x4b, 0x0a, 0x85, 0x51, 0xb4, 0x66, 0x18, 0xbe, 0x77, 0x24,
+	0xa3, 0x6b, 0x86, 0x41, 0xd1, 0x9a, 0x61, 0xa0, 0x4d, 0x10, 0xa9, 0xe6, 0x19, 0x7f, 0x85, 0xad,
+	0x0b, 0x89, 0xf0, 0x07, 0xf6, 0x00, 0xef, 0x2c, 0x29, 0x0c, 0x88, 0xee, 0x40, 0x96, 0x5b, 0x8f,
+	0xf1, 0x5c, 0xd8, 0x7a, 0x33, 0x91, 0x84, 0xdb, 0x73, 0x67, 0x49, 0xf1, 0xc1, 0xf4, 0x1c, 0x6c,
+	0x98, 0x81, 0xb7, 0x24, 0x9f, 0x23, 0x1b, 0x26, 0x95, 0x82, 0x01, 0xe9, 0x39, 0x1e, 0xee, 0x62,
+	0x9d, 0x30, 0x27, 0x99, 0x76, 0x4e, 0x8b, 0x41, 0xe8, 0x39, 0x1c, 0x8c, 0xb6, 0x20, 0xe3, 0x91,
+	0x61, 0x17, 0x97, 0x72, 0x8c, 0xaa, 0x9c, 0x4c, 0x45, 0x11, 0x3b, 0x4b, 0x0a, 0x87, 0xa2, 0xbb,
+	0x90, 0x37, 0x2d, 0xdd, 0xc5, 0x9a, 0x87, 0x4b, 0x79, 0x46, 0x76, 0x31, 0x91, 0x6c, 0xd7, 0x07,
+	0xed, 0x2c, 0x29, 0x21, 0x01, 0xfa, 0x3a, 0x48, 0xc4, 0xc5, 0x58, 0x65, 0xd2, 0x49, 0x33, 0xa8,
+	0xdb, 0x2e, 0xc6, 0xbe, 0x84, 0x79, 0xe2, 0xff, 0x46, 0xdf, 0x04, 0x60, 0xd4, 0x9c, 0x67, 0x60,
+	0xe4, 0x6b, 0x53, 0xc9, 0x03, 0xbe, 0xd9, 0x89, 0x6c, 0x50, 0xfe, 0xb5, 0x00, 0xe9, 0x16, 0x26,
+	0x34, 0x34, 0x1d, 0xcd, 0xa5, 0xce, 0x4a, 0xf9, 0x22, 0xd8, 0x50, 0xb5, 0xc0, 0x63, 0xa6, 0x85,
+	0x26, 0xc7, 0xd7, 0x39, 0xbc, 0x46, 0x50, 0x11, 0xd2, 0x34, 0xef, 0xf0, 0x40, 0xa2, 0x3f, 0xa9,
+	0x32, 0x07, 0x5a, 0xb7, 0x1f, 0x78, 0xc7, 0x5b, 0x91, 0x8d, 0xee, 0xb7, 0xf6, 0x9b, 0x72, 0x17,
+	0xd3, 0xcc, 0xd4, 0x32, 0x7b, 0x4e, 0x17, 0x2b, 0x1c, 0x8a, 0xbe, 0x0c, 0x05, 0xfc, 0x0c, 0xeb,
+	0x7d, 0x9f, 0x05, 0x71, 0x16, 0x0b, 0x10, 0x20, 0x6b, 0xa4, 0xfc, 0x4f, 0x01, 0xd2, 0x35, 0xc3,
+	0x38, 0x09, 0x41, 0xde, 0x85, 0x15, 0xc7, 0xc5, 0x83, 0xe8, 0x06, 0xa9, 0x59, 0x1b, 0x9c, 0xa2,
+	0xe8, 0x11, 0xf9, 0x67, 0x29, 0xf5, 0xbf, 0x05, 0x10, 0x69, 0x78, 0xbd, 0x02, 0x62, 0xdf, 0x06,
+	0x88, 0x50, 0xa6, 0x67, 0x51, 0x4a, 0x7a, 0x48, 0xb5, 0xa8, 0xe0, 0x9f, 0x08, 0x90, 0xe5, 0x49,
+	0xe2, 0x24, 0x44, 0x1f, 0xe7, 0x3d, 0xb5, 0x18, 0xef, 0xe9, 0x79, 0x79, 0xff, 0x95, 0x08, 0x22,
+	0x8b, 0xde, 0x13, 0xe0, 0xfc, 0x3a, 0x88, 0x4f, 0x5c, 0xbb, 0xe7, 0xf3, 0x7c, 0x3e, 0x4a, 0x85,
+	0x9f, 0x91, 0xa6, 0x6d, 0xe0, 0x03, 0xdb, 0x53, 0x18, 0x06, 0x5d, 0x85, 0x14, 0xb1, 0x7d, 0x36,
+	0xa7, 0x21, 0x53, 0xc4, 0x46, 0x1d, 0x78, 0x63, 0xc4, 0x8f, 0xda, 0xd3, 0x1c, 0xf5, 0x68, 0xa8,
+	0xb2, 0xab, 0xc5, 0xbf, 0xe3, 0xb7, 0xa6, 0xa6, 0xdf, 0x6a, 0xc8, 0xd9, 0x03, 0xcd, 0xd9, 0x1e,
+	0xd6, 0x28, 0x91, 0x6c, 0x11, 0x77, 0xa8, 0xac, 0xea, 0x93, 0x2b, 0xf4, 0xfe, 0xd5, 0x6d, 0x8b,
+	0x60, 0x8b, 0x27, 0x76, 0x49, 0x09, 0x86, 0x71, 0xdd, 0x66, 0xe7, 0xd4, 0x2d, 0xda, 0x05, 0xd0,
+	0x08, 0x71, 0xcd, 0xa3, 0x3e, 0xc1, 0x5e, 0x29, 0xc7, 0xd8, 0xbd, 0x36, 0x9d, 0xdd, 0x5a, 0x88,
+	0xe5, 0x5c, 0x46, 0x88, 0xcb, 0xdf, 0x85, 0xd2, 0x34, 0x69, 0x82, 0x5c, 0x27, 0x8c, 0x72, 0xdd,
+	0x8d, 0x20, 0xea, 0x67, 0x7a, 0x0f, 0xc7, 0xbc, 0x93, 0xfa, 0xaa, 0x50, 0x7e, 0x17, 0x56, 0x62,
+	0xa7, 0x27, 0xec, 0x7a, 0x36, 0xba, 0xab, 0x14, 0x25, 0xff, 0x93, 0x00, 0x59, 0x7e, 0x7b, 0xbd,
+	0xaa, 0x6e, 0xb4, 0x68, 0x68, 0xff, 0x25, 0x05, 0x19, 0x76, 0x39, 0xbd, 0xaa, 0x82, 0xdd, 0x1f,
+	0xf3, 0x31, 0x1e, 0x12, 0xd7, 0xa7, 0x17, 0x0a, 0xb3, 0x9c, 0x2c, 0xae, 0xa4, 0xcc, 0xbc, 0x4a,
+	0xfa, 0x1f, 0xbd, 0xe7, 0x13, 0x01, 0xf2, 0x41, 0x39, 0x72, 0x12, 0x6a, 0xde, 0x1a, 0xf7, 0xfe,
+	0x45, 0xee, 0xbc, 0xb9, 0xd3, 0xe7, 0xf7, 0x53, 0x90, 0x0f, 0x8a, 0xa1, 0x93, 0xe0, 0xfd, 0xea,
+	0x98, 0x8b, 0xa0, 0x28, 0x95, 0x8b, 0x23, 0xee, 0x51, 0x89, 0xb8, 0x47, 0x12, 0x8a, 0xba, 0xc6,
+	0x17, 0x47, 0x09, 0x8d, 0xfb, 0xc5, 0x6a, 0x0c, 0x48, 0xfd, 0x68, 0x6a, 0x96, 0xcb, 0xbc, 0x44,
+	0x88, 0x48, 0x61, 0x41, 0xf7, 0xaa, 0xe9, 0xa0, 0x99, 0x10, 0x1e, 0xd5, 0xd9, 0x35, 0xe9, 0x2b,
+	0x18, 0x22, 0xdb, 0x59, 0x10, 0x8f, 0x6c, 0x63, 0x58, 0xf9, 0x87, 0x00, 0x67, 0x26, 0x7c, 0x38,
+	0x56, 0x31, 0x08, 0x73, 0x56, 0x0c, 0x37, 0x21, 0xcf, 0xfa, 0xd4, 0x17, 0x56, 0x19, 0x39, 0x06,
+	0xe3, 0x95, 0x89, 0xdf, 0xec, 0xbe, 0xb8, 0xaa, 0xf2, 0x81, 0x35, 0x82, 0x36, 0x40, 0x24, 0x43,
+	0x87, 0xb7, 0x58, 0xa7, 0xc7, 0xfa, 0xd6, 0x87, 0x54, 0xbe, 0xf6, 0xd0, 0xc1, 0x0a, 0x43, 0x8c,
+	0xe4, 0xcf, 0xb0, 0x0e, 0x92, 0x0f, 0x2a, 0xbf, 0x3c, 0x05, 0x85, 0x88, 0xcc, 0xa8, 0x01, 0x85,
+	0x0f, 0x3d, 0xdb, 0x52, 0xed, 0xa3, 0x0f, 0x69, 0x47, 0xc5, 0xc5, 0xbd, 0x9c, 0x1c, 0xe4, 0xec,
+	0xf7, 0x3e, 0x03, 0xee, 0x2c, 0x29, 0x40, 0xe9, 0xf8, 0x08, 0xd5, 0x80, 0x8d, 0x54, 0xcd, 0x75,
+	0xb5, 0xa1, 0x2f, 0xff, 0xfa, 0x8c, 0x4d, 0x6a, 0x14, 0x47, 0xdb, 0x15, 0x4a, 0xc5, 0x06, 0xe8,
+	0x5b, 0x20, 0x39, 0xae, 0xd9, 0x33, 0x89, 0x19, 0xf6, 0x9c, 0xd3, 0x76, 0x38, 0x08, 0x70, 0x74,
+	0x87, 0x90, 0x08, 0xdd, 0x02, 0x91, 0xe0, 0x67, 0x81, 0x1b, 0xbd, 0x39, 0x85, 0x98, 0xa6, 0x7c,
+	0xda, 0x4a, 0x52, 0x28, 0x7a, 0x87, 0x06, 0x75, 0xdf, 0x22, 0xd8, 0xf5, 0xeb, 0x90, 0xb5, 0x29,
+	0x54, 0x75, 0x8e, 0xda, 0x59, 0x52, 0x02, 0x02, 0x76, 0x9c, 0x8b, 0x83, 0x76, 0x72, 0xea, 0x71,
+	0x2e, 0x66, 0x1d, 0x32, 0x85, 0x96, 0x3f, 0x15, 0x00, 0x46, 0x3a, 0x44, 0x1b, 0x90, 0xb1, 0x6c,
+	0x03, 0x7b, 0x25, 0x81, 0x45, 0x52, 0x34, 0xea, 0x94, 0x9d, 0x36, 0xcb, 0x27, 0x1c, 0xb0, 0x60,
+	0x15, 0x1b, 0xf5, 0xc9, 0xf4, 0x02, 0x3e, 0x29, 0xce, 0xe7, 0x93, 0xe5, 0x3f, 0x0a, 0x20, 0x85,
+	0x56, 0x9d, 0x29, 0xd5, 0xbd, 0xda, 0xeb, 0x23, 0xd5, 0xdf, 0x05, 0x90, 0x42, 0x4f, 0x0b, 0xe3,
+	0x4e, 0x98, 0x3f, 0xee, 0x52, 0x91, 0xb8, 0x5b, 0xb0, 0x87, 0x8a, 0xca, 0x2a, 0x2e, 0x20, 0x6b,
+	0x66, 0x4e, 0x59, 0x7f, 0x2f, 0x80, 0x48, 0x03, 0x03, 0x5d, 0x1b, 0x37, 0xde, 0x6a, 0x42, 0xad,
+	0xf4, 0x7a, 0x58, 0xef, 0x6f, 0x02, 0xe4, 0xfc, 0xa0, 0xfd, 0x7f, 0xb0, 0x9d, 0x8b, 0xf1, 0x4c,
+	0xdb, 0x05, 0xf5, 0xc9, 0xeb, 0x60, 0xbb, 0xf0, 0x7e, 0x7e, 0x00, 0x39, 0x3f, 0x0f, 0x26, 0x5c,
+	0xef, 0x37, 0x21, 0x87, 0x79, 0x8e, 0x4d, 0xe8, 0x00, 0x22, 0x19, 0x58, 0x09, 0x60, 0x15, 0x1d,
+	0x72, 0x7e, 0x02, 0xa2, 0x45, 0x91, 0x45, 0xaf, 0x0a, 0x61, 0xa2, 0xdc, 0x09, 0x52, 0x14, 0x5b,
+	0x5f, 0xe0, 0x90, 0x87, 0x90, 0xa7, 0xf4, 0xb4, 0x3c, 0x19, 0x79, 0x93, 0x10, 0xa9, 0x40, 0xa8,
+	0x4e, 0xfa, 0x8e, 0x31, 0x9f, 0xee, 0x7d, 0x60, 0x8d, 0x54, 0x7e, 0x47, 0x4b, 0x63, 0x3f, 0x02,
+	0xd1, 0xe7, 0x22, 0x5f, 0xc0, 0xcf, 0x25, 0x84, 0xa8, 0xff, 0x0d, 0x3c, 0xb1, 0x02, 0x5a, 0xb0,
+	0xee, 0xb8, 0x03, 0x05, 0xd3, 0xf2, 0x54, 0xf6, 0x19, 0xc9, 0xff, 0x2a, 0x3d, 0xf5, 0x6c, 0xc9,
+	0xb4, 0xbc, 0x03, 0x17, 0x0f, 0x76, 0x0d, 0x54, 0x1f, 0xab, 0x18, 0x33, 0xcc, 0x31, 0xaf, 0x24,
+	0x50, 0xcd, 0x6c, 0xd7, 0x95, 0x79, 0xca, 0xbd, 0x6b, 0xe3, 0x7d, 0x4a, 0xd4, 0xfb, 0x03, 0x83,
+	0x44, 0x6a, 0xc0, 0xca, 0x07, 0x00, 0x23, 0x8e, 0x17, 0xac, 0xf9, 0xce, 0x43, 0xd6, 0x7e, 0xf2,
+	0xc4, 0xc3, 0xdc, 0x8a, 0x19, 0xc5, 0x1f, 0x55, 0xfe, 0xe5, 0xb7, 0x31, 0xcc, 0x56, 0x6f, 0x43,
+	0xda, 0xb1, 0xbd, 0x04, 0x4f, 0x0b, 0x0a, 0x6b, 0xba, 0x8c, 0x90, 0x9f, 0xa2, 0xb8, 0xa5, 0x62,
+	0xc9, 0x28, 0x3d, 0xdd, 0x7c, 0x73, 0x86, 0x14, 0xba, 0x0d, 0xcb, 0xa1, 0xf9, 0x28, 0x3b, 0x99,
+	0xa9, 0xec, 0x80, 0x6f, 0xbc, 0x03, 0xdb, 0xa3, 0x1c, 0x18, 0xd8, 0x21, 0x1d, 0x56, 0x1c, 0x65,
+	0x14, 0x3e, 0x88, 0xd9, 0x34, 0x37, 0x69, 0x53, 0x5f, 0xf4, 0xcf, 0xdc, 0xa6, 0x8f, 0x20, 0xe7,
+	0x4b, 0x71, 0xc2, 0x06, 0xed, 0x81, 0x78, 0xe8, 0x61, 0x17, 0x9d, 0x0e, 0xe3, 0x4e, 0x62, 0x01,
+	0x56, 0x86, 0x7c, 0xdf, 0xc3, 0xae, 0xa5, 0xf5, 0x02, 0xcb, 0x85, 0x63, 0xf4, 0xb5, 0x84, 0x4b,
+	0xa3, 0x5c, 0xe5, 0x0f, 0x7f, 0xd5, 0xe0, 0xe1, 0x8f, 0xf1, 0xc1, 0x5e, 0x06, 0x23, 0x6c, 0x54,
+	0xfe, 0x93, 0x82, 0xdc, 0x81, 0x6b, 0xb3, 0x1a, 0x31, 0x7e, 0x24, 0x02, 0x31, 0x72, 0x1c, 0xfb,
+	0x8d, 0x2e, 0x02, 0x38, 0xfd, 0xa3, 0xae, 0xa9, 0xb3, 0xd7, 0x41, 0xee, 0x2d, 0x12, 0x9f, 0x79,
+	0x1f, 0x0f, 0xe9, 0xb2, 0x87, 0x75, 0x17, 0xf3, 0xc7, 0x43, 0x91, 0x2f, 0xf3, 0x19, 0xba, 0xbc,
+	0x01, 0x45, 0xad, 0x4f, 0x3a, 0xea, 0xc7, 0xf8, 0xa8, 0x63, 0xdb, 0xc7, 0x6a, 0xdf, 0xed, 0xfa,
+	0x9f, 0xec, 0x4e, 0xd3, 0xf9, 0x47, 0x7c, 0xfa, 0xd0, 0xed, 0xa2, 0x9b, 0x70, 0x76, 0x0c, 0xd9,
+	0xc3, 0xa4, 0x63, 0x1b, 0x5e, 0x29, 0xbb, 0x9e, 0xde, 0x90, 0x14, 0x14, 0x41, 0x3f, 0xe0, 0x2b,
+	0xe8, 0x1b, 0xf0, 0xa6, 0xff, 0x50, 0x66, 0x60, 0x4d, 0x27, 0xe6, 0x40, 0x23, 0x58, 0x25, 0x1d,
+	0x17, 0x7b, 0x1d, 0xbb, 0x6b, 0xb0, 0xd2, 0x59, 0x52, 0x2e, 0x70, 0x48, 0x23, 0x44, 0xb4, 0x03,
+	0x40, 0x4c, 0x89, 0xf9, 0x97, 0x50, 0x22, 0x25, 0x8d, 0xa4, 0x59, 0xe9, 0xc5, 0xa4, 0xa3, 0x5c,
+	0xfb, 0xe3, 0x34, 0x9c, 0x3f, 0xa4, 0x23, 0xed, 0xa8, 0x8b, 0x7d, 0x43, 0xbc, 0x67, 0xe2, 0xae,
+	0xe1, 0xa1, 0x9b, 0xbe, 0xfa, 0x05, 0xff, 0x63, 0x48, 0x7c, 0xbf, 0x16, 0x71, 0x4d, 0xeb, 0x29,
+	0x2b, 0x2b, 0x7c, 0xe3, 0xbc, 0x97, 0xa0, 0xde, 0xd4, 0x1c, 0xd4, 0x71, 0xe5, 0x3f, 0x99, 0xa2,
+	0x7c, 0xee, 0x59, 0xb7, 0x23, 0xbe, 0x9d, 0xcc, 0x7a, 0xb5, 0x36, 0x61, 0x9e, 0x44, 0x93, 0x7d,
+	0x67, 0xb6, 0xc9, 0xc4, 0x39, 0x58, 0x9f, 0x6e, 0xd0, 0x72, 0x15, 0xd0, 0x24, 0x1f, 0xfc, 0xb1,
+	0x96, 0x8b, 0x23, 0x30, 0x5f, 0x0a, 0x86, 0x95, 0x1f, 0xa4, 0x60, 0xa5, 0xe1, 0xbf, 0x73, 0xb7,
+	0xfa, 0xbd, 0x9e, 0xe6, 0x0e, 0x27, 0x42, 0x62, 0xf2, 0x75, 0x2a, 0xfe, 0xac, 0x2d, 0x45, 0x9e,
+	0xb5, 0xc7, 0x5d, 0x4a, 0x7c, 0x19, 0x97, 0xba, 0x0b, 0x05, 0x4d, 0xd7, 0xb1, 0xe7, 0x45, 0x0b,
+	0xb4, 0x59, 0xb4, 0x10, 0xc0, 0x27, 0xfc, 0x31, 0xfb, 0x32, 0xfe, 0xf8, 0x53, 0x01, 0xf2, 0x07,
+	0x2e, 0xf6, 0xb0, 0xa5, 0xb3, 0x5b, 0x41, 0xef, 0xda, 0xfa, 0x31, 0x53, 0x40, 0x46, 0xe1, 0x03,
+	0xda, 0x8c, 0x52, 0xa3, 0x97, 0x52, 0x2c, 0x1b, 0x47, 0x9f, 0x19, 0x03, 0xc2, 0x6a, 0x43, 0x23,
+	0x1a, 0xcf, 0xc3, 0x0c, 0x5a, 0xfe, 0x0a, 0x48, 0xe1, 0xd4, 0xcb, 0x7c, 0x3e, 0xa9, 0xec, 0x42,
+	0xb6, 0xce, 0x0c, 0x1c, 0xb1, 0xc4, 0x32, 0xb3, 0xc4, 0x26, 0xe4, 0x1d, 0xff, 0xb8, 0x84, 0x94,
+	0x1d, 0x70, 0xa2, 0x84, 0xa0, 0x4a, 0x13, 0x60, 0xf4, 0xcf, 0x84, 0xd8, 0xbb, 0xb9, 0x90, 0xf4,
+	0x6e, 0x3e, 0xfe, 0xf2, 0x9e, 0x8a, 0xbd, 0xbc, 0x57, 0x7e, 0x28, 0x40, 0x21, 0xf2, 0x4d, 0xf7,
+	0x64, 0xaf, 0x01, 0xf4, 0x79, 0x58, 0x71, 0x71, 0x57, 0xa3, 0xfd, 0xa0, 0xea, 0x03, 0xd2, 0x0c,
+	0x70, 0x3a, 0x98, 0xde, 0xe7, 0xf7, 0x85, 0x0e, 0x30, 0xda, 0x39, 0xfa, 0xd6, 0x2f, 0x4c, 0xbe,
+	0xf5, 0xbf, 0x05, 0x92, 0x81, 0xbb, 0xb4, 0xcd, 0xc4, 0x6e, 0x20, 0x50, 0x38, 0x31, 0xf6, 0x4f,
+	0x80, 0xf4, 0xf8, 0x3f, 0x01, 0x7e, 0x22, 0x40, 0xbe, 0x61, 0xeb, 0xf2, 0x80, 0x5a, 0xe2, 0xc6,
+	0x58, 0x8b, 0xf3, 0x46, 0x44, 0xc4, 0x00, 0x12, 0xe9, 0x72, 0x36, 0x81, 0xdf, 0x0e, 0x5e, 0xc7,
+	0x3f, 0x32, 0xf6, 0xdf, 0x0e, 0xa6, 0x4e, 0x65, 0x84, 0x41, 0x97, 0x20, 0xfc, 0xb3, 0x49, 0xc0,
+	0x88, 0xa4, 0x40, 0x30, 0xb5, 0x6b, 0x5c, 0xff, 0x34, 0x05, 0x52, 0xd8, 0x4b, 0xa1, 0x55, 0x58,
+	0x79, 0x58, 0xdb, 0x3b, 0x94, 0xd5, 0xf6, 0xe3, 0x03, 0x59, 0x6d, 0x1e, 0xee, 0xed, 0x15, 0x97,
+	0xd0, 0x79, 0x40, 0x91, 0xc9, 0xed, 0xfd, 0xfd, 0x3d, 0xb9, 0xd6, 0x2c, 0x0a, 0xb1, 0xf9, 0xdd,
+	0x66, 0x5b, 0xbe, 0x27, 0x2b, 0xc5, 0x54, 0x6c, 0x93, 0xbd, 0xfd, 0xe6, 0xbd, 0x62, 0x1a, 0x9d,
+	0x83, 0x33, 0x91, 0xc9, 0xc6, 0xfe, 0xe1, 0xf6, 0x9e, 0x5c, 0x14, 0x63, 0xd3, 0xad, 0xb6, 0xb2,
+	0xdb, 0xbc, 0x57, 0xcc, 0xa0, 0xb3, 0x50, 0x8c, 0x1e, 0xf9, 0xb8, 0x2d, 0xb7, 0x8a, 0xd9, 0xd8,
+	0xc6, 0x8d, 0x5a, 0x5b, 0x2e, 0xe6, 0x50, 0x19, 0xce, 0x47, 0x26, 0x69, 0x65, 0xaf, 0xee, 0x6f,
+	0xdf, 0x97, 0xeb, 0xed, 0x62, 0x1e, 0x5d, 0x80, 0x73, 0xf1, 0xb5, 0x9a, 0xa2, 0xd4, 0x1e, 0x17,
+	0xa5, 0xd8, 0x5e, 0x6d, 0xf9, 0xdb, 0xed, 0x22, 0xc4, 0xf6, 0xf2, 0x25, 0x52, 0xeb, 0xcd, 0x76,
+	0xb1, 0x80, 0xde, 0x80, 0xd5, 0x98, 0x54, 0x6c, 0x61, 0x39, 0xbe, 0x93, 0x22, 0xcb, 0xc5, 0x53,
+	0xd7, 0x7f, 0x21, 0xc0, 0x72, 0xd4, 0x7e, 0xe8, 0x6d, 0x58, 0x6f, 0xec, 0xd7, 0x55, 0xf9, 0xa1,
+	0xdc, 0x6c, 0x07, 0x3a, 0xa8, 0x1f, 0x3e, 0x90, 0x9b, 0xed, 0x96, 0x5a, 0xdf, 0xa9, 0x35, 0xef,
+	0xc9, 0x8d, 0xe2, 0xd2, 0x4c, 0xd4, 0xa3, 0x5a, 0xbb, 0xbe, 0x23, 0x37, 0x8a, 0x02, 0xba, 0x0a,
+	0x95, 0xa9, 0xa8, 0xc3, 0x66, 0x80, 0x4b, 0xa1, 0x2b, 0x70, 0x29, 0x86, 0x3b, 0x50, 0xe4, 0x96,
+	0xdc, 0xac, 0xcb, 0xe1, 0x91, 0xe9, 0xed, 0x1b, 0xbf, 0x79, 0xbe, 0x26, 0xfc, 0xe1, 0xf9, 0x9a,
+	0xf0, 0xe7, 0xe7, 0x6b, 0xc2, 0xcf, 0xff, 0xba, 0xb6, 0x04, 0x67, 0x0c, 0x3c, 0x08, 0x9c, 0x4a,
+	0x73, 0xcc, 0xea, 0xe0, 0xd6, 0x81, 0xf0, 0x81, 0x58, 0xbd, 0x3b, 0xb8, 0x75, 0x94, 0x65, 0xe9,
+	0xee, 0x4b, 0xff, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xe3, 0x01, 0xec, 0x60, 0x4e, 0x25, 0x00, 0x00,
 }
 
 func (m *ChangePack) Marshal() (dAtA []byte, err error) {
@@ -3649,6 +3754,27 @@ func (m *Operation_TreeEdit_) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	}
 	return len(dAtA) - i, nil
 }
+func (m *Operation_TreeStyle_) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Operation_TreeStyle_) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	if m.TreeStyle != nil {
+		{
+			size, err := m.TreeStyle.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResources(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x52
+	}
+	return len(dAtA) - i, nil
+}
 func (m *Operation_Set) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -4380,6 +4506,100 @@ func (m *Operation_TreeEdit) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *Operation_TreeStyle) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Operation_TreeStyle) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Operation_TreeStyle) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.ExecutedAt != nil {
+		{
+			size, err := m.ExecutedAt.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResources(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x2a
+	}
+	if len(m.Attributes) > 0 {
+		for k := range m.Attributes {
+			v := m.Attributes[k]
+			baseI := i
+			i -= len(v)
+			copy(dAtA[i:], v)
+			i = encodeVarintResources(dAtA, i, uint64(len(v)))
+			i--
+			dAtA[i] = 0x12
+			i -= len(k)
+			copy(dAtA[i:], k)
+			i = encodeVarintResources(dAtA, i, uint64(len(k)))
+			i--
+			dAtA[i] = 0xa
+			i = encodeVarintResources(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x22
+		}
+	}
+	if m.To != nil {
+		{
+			size, err := m.To.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResources(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x1a
+	}
+	if m.From != nil {
+		{
+			size, err := m.From.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResources(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x12
+	}
+	if m.ParentCreatedAt != nil {
+		{
+			size, err := m.ParentCreatedAt.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintResources(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
 func (m *JSONElementSimple) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -5172,7 +5392,7 @@ func (m *RGANode) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
-func (m *TextNodeAttr) Marshal() (dAtA []byte, err error) {
+func (m *NodeAttr) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
 	n, err := m.MarshalToSizedBuffer(dAtA[:size])
@@ -5182,12 +5402,12 @@ func (m *TextNodeAttr) Marshal() (dAtA []byte, err error) {
 	return dAtA[:n], nil
 }
 
-func (m *TextNodeAttr) MarshalTo(dAtA []byte) (int, error) {
+func (m *NodeAttr) MarshalTo(dAtA []byte) (int, error) {
 	size := m.Size()
 	return m.MarshalToSizedBuffer(dAtA[:size])
 }
 
-func (m *TextNodeAttr) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+func (m *NodeAttr) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	i := len(dAtA)
 	_ = i
 	var l int
@@ -5382,6 +5602,32 @@ func (m *TreeNode) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 		i -= len(m.XXX_unrecognized)
 		copy(dAtA[i:], m.XXX_unrecognized)
 	}
+	if len(m.Attributes) > 0 {
+		for k := range m.Attributes {
+			v := m.Attributes[k]
+			baseI := i
+			if v != nil {
+				{
+					size, err := v.MarshalToSizedBuffer(dAtA[:i])
+					if err != nil {
+						return 0, err
+					}
+					i -= size
+					i = encodeVarintResources(dAtA, i, uint64(size))
+				}
+				i--
+				dAtA[i] = 0x12
+			}
+			i -= len(k)
+			copy(dAtA[i:], k)
+			i = encodeVarintResources(dAtA, i, uint64(len(k)))
+			i--
+			dAtA[i] = 0xa
+			i = encodeVarintResources(dAtA, i, uint64(baseI-i))
+			i--
+			dAtA[i] = 0x3a
+		}
+	}
 	if m.Depth != 0 {
 		i = encodeVarintResources(dAtA, i, uint64(m.Depth))
 		i--
@@ -6334,6 +6580,18 @@ func (m *Operation_TreeEdit_) Size() (n int) {
 	}
 	return n
 }
+func (m *Operation_TreeStyle_) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.TreeStyle != nil {
+		l = m.TreeStyle.Size()
+		n += 1 + l + sovResources(uint64(l))
+	}
+	return n
+}
 func (m *Operation_Set) Size() (n int) {
 	if m == nil {
 		return 0
@@ -6617,6 +6875,42 @@ func (m *Operation_TreeEdit) Size() (n int) {
 	return n
 }
 
+func (m *Operation_TreeStyle) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.ParentCreatedAt != nil {
+		l = m.ParentCreatedAt.Size()
+		n += 1 + l + sovResources(uint64(l))
+	}
+	if m.From != nil {
+		l = m.From.Size()
+		n += 1 + l + sovResources(uint64(l))
+	}
+	if m.To != nil {
+		l = m.To.Size()
+		n += 1 + l + sovResources(uint64(l))
+	}
+	if len(m.Attributes) > 0 {
+		for k, v := range m.Attributes {
+			_ = k
+			_ = v
+			mapEntrySize := 1 + len(k) + sovResources(uint64(len(k))) + 1 + len(v) + sovResources(uint64(len(v)))
+			n += mapEntrySize + 1 + sovResources(uint64(mapEntrySize))
+		}
+	}
+	if m.ExecutedAt != nil {
+		l = m.ExecutedAt.Size()
+		n += 1 + l + sovResources(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func (m *JSONElementSimple) Size() (n int) {
 	if m == nil {
 		return 0
@@ -6957,7 +7251,7 @@ func (m *RGANode) Size() (n int) {
 	return n
 }
 
-func (m *TextNodeAttr) Size() (n int) {
+func (m *NodeAttr) Size() (n int) {
 	if m == nil {
 		return 0
 	}
@@ -7066,13 +7360,26 @@ func (m *TreeNode) Size() (n int) {
 	if m.Depth != 0 {
 		n += 1 + sovResources(uint64(m.Depth))
 	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
-	}
-	return n
-}
-
-func (m *TreePos) Size() (n int) {
+	if len(m.Attributes) > 0 {
+		for k, v := range m.Attributes {
+			_ = k
+			_ = v
+			l = 0
+			if v != nil {
+				l = v.Size()
+				l += 1 + sovResources(uint64(l))
+			}
+			mapEntrySize := 1 + len(k) + sovResources(uint64(len(k))) + l
+			n += mapEntrySize + 1 + sovResources(uint64(mapEntrySize))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *TreePos) Size() (n int) {
 	if m == nil {
 		return 0
 	}
@@ -8263,6 +8570,41 @@ func (m *Operation) Unmarshal(dAtA []byte) error {
 			}
 			m.Body = &Operation_TreeEdit_{v}
 			iNdEx = postIndex
+		case 10:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field TreeStyle", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			v := &Operation_TreeStyle{}
+			if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			m.Body = &Operation_TreeStyle_{v}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipResources(dAtA[iNdEx:])
@@ -10413,6 +10755,328 @@ func (m *Operation_TreeEdit) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *Operation_TreeStyle) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowResources
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: TreeStyle: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: TreeStyle: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ParentCreatedAt", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.ParentCreatedAt == nil {
+				m.ParentCreatedAt = &TimeTicket{}
+			}
+			if err := m.ParentCreatedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field From", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.From == nil {
+				m.From = &TreePos{}
+			}
+			if err := m.From.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field To", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.To == nil {
+				m.To = &TreePos{}
+			}
+			if err := m.To.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Attributes == nil {
+				m.Attributes = make(map[string]string)
+			}
+			var mapkey string
+			var mapvalue string
+			for iNdEx < postIndex {
+				entryPreIndex := iNdEx
+				var wire uint64
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return ErrIntOverflowResources
+					}
+					if iNdEx >= l {
+						return io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					wire |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				fieldNum := int32(wire >> 3)
+				if fieldNum == 1 {
+					var stringLenmapkey uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowResources
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapkey |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapkey := int(stringLenmapkey)
+					if intStringLenmapkey < 0 {
+						return ErrInvalidLengthResources
+					}
+					postStringIndexmapkey := iNdEx + intStringLenmapkey
+					if postStringIndexmapkey < 0 {
+						return ErrInvalidLengthResources
+					}
+					if postStringIndexmapkey > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
+					iNdEx = postStringIndexmapkey
+				} else if fieldNum == 2 {
+					var stringLenmapvalue uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowResources
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapvalue |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapvalue := int(stringLenmapvalue)
+					if intStringLenmapvalue < 0 {
+						return ErrInvalidLengthResources
+					}
+					postStringIndexmapvalue := iNdEx + intStringLenmapvalue
+					if postStringIndexmapvalue < 0 {
+						return ErrInvalidLengthResources
+					}
+					if postStringIndexmapvalue > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
+					iNdEx = postStringIndexmapvalue
+				} else {
+					iNdEx = entryPreIndex
+					skippy, err := skipResources(dAtA[iNdEx:])
+					if err != nil {
+						return err
+					}
+					if (skippy < 0) || (iNdEx+skippy) < 0 {
+						return ErrInvalidLengthResources
+					}
+					if (iNdEx + skippy) > postIndex {
+						return io.ErrUnexpectedEOF
+					}
+					iNdEx += skippy
+				}
+			}
+			m.Attributes[mapkey] = mapvalue
+			iNdEx = postIndex
+		case 5:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ExecutedAt", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.ExecutedAt == nil {
+				m.ExecutedAt = &TimeTicket{}
+			}
+			if err := m.ExecutedAt.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipResources(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthResources
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *JSONElementSimple) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -12324,7 +12988,7 @@ func (m *RGANode) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
-func (m *TextNodeAttr) Unmarshal(dAtA []byte) error {
+func (m *NodeAttr) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
 	for iNdEx < l {
@@ -12347,10 +13011,10 @@ func (m *TextNodeAttr) Unmarshal(dAtA []byte) error {
 		fieldNum := int32(wire >> 3)
 		wireType := int(wire & 0x7)
 		if wireType == 4 {
-			return fmt.Errorf("proto: TextNodeAttr: wiretype end group for non-group")
+			return fmt.Errorf("proto: NodeAttr: wiretype end group for non-group")
 		}
 		if fieldNum <= 0 {
-			return fmt.Errorf("proto: TextNodeAttr: illegal tag %d (wire type %d)", fieldNum, wire)
+			return fmt.Errorf("proto: NodeAttr: illegal tag %d (wire type %d)", fieldNum, wire)
 		}
 		switch fieldNum {
 		case 1:
@@ -12642,10 +13306,10 @@ func (m *TextNode) Unmarshal(dAtA []byte) error {
 				return io.ErrUnexpectedEOF
 			}
 			if m.Attributes == nil {
-				m.Attributes = make(map[string]*TextNodeAttr)
+				m.Attributes = make(map[string]*NodeAttr)
 			}
 			var mapkey string
-			var mapvalue *TextNodeAttr
+			var mapvalue *NodeAttr
 			for iNdEx < postIndex {
 				entryPreIndex := iNdEx
 				var wire uint64
@@ -12719,7 +13383,7 @@ func (m *TextNode) Unmarshal(dAtA []byte) error {
 					if postmsgIndex > l {
 						return io.ErrUnexpectedEOF
 					}
-					mapvalue = &TextNodeAttr{}
+					mapvalue = &NodeAttr{}
 					if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil {
 						return err
 					}
@@ -13089,6 +13753,135 @@ func (m *TreeNode) Unmarshal(dAtA []byte) error {
 					break
 				}
 			}
+		case 7:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Attributes", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowResources
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthResources
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthResources
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Attributes == nil {
+				m.Attributes = make(map[string]*NodeAttr)
+			}
+			var mapkey string
+			var mapvalue *NodeAttr
+			for iNdEx < postIndex {
+				entryPreIndex := iNdEx
+				var wire uint64
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return ErrIntOverflowResources
+					}
+					if iNdEx >= l {
+						return io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					wire |= uint64(b&0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				fieldNum := int32(wire >> 3)
+				if fieldNum == 1 {
+					var stringLenmapkey uint64
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowResources
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						stringLenmapkey |= uint64(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					intStringLenmapkey := int(stringLenmapkey)
+					if intStringLenmapkey < 0 {
+						return ErrInvalidLengthResources
+					}
+					postStringIndexmapkey := iNdEx + intStringLenmapkey
+					if postStringIndexmapkey < 0 {
+						return ErrInvalidLengthResources
+					}
+					if postStringIndexmapkey > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapkey = string(dAtA[iNdEx:postStringIndexmapkey])
+					iNdEx = postStringIndexmapkey
+				} else if fieldNum == 2 {
+					var mapmsglen int
+					for shift := uint(0); ; shift += 7 {
+						if shift >= 64 {
+							return ErrIntOverflowResources
+						}
+						if iNdEx >= l {
+							return io.ErrUnexpectedEOF
+						}
+						b := dAtA[iNdEx]
+						iNdEx++
+						mapmsglen |= int(b&0x7F) << shift
+						if b < 0x80 {
+							break
+						}
+					}
+					if mapmsglen < 0 {
+						return ErrInvalidLengthResources
+					}
+					postmsgIndex := iNdEx + mapmsglen
+					if postmsgIndex < 0 {
+						return ErrInvalidLengthResources
+					}
+					if postmsgIndex > l {
+						return io.ErrUnexpectedEOF
+					}
+					mapvalue = &NodeAttr{}
+					if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil {
+						return err
+					}
+					iNdEx = postmsgIndex
+				} else {
+					iNdEx = entryPreIndex
+					skippy, err := skipResources(dAtA[iNdEx:])
+					if err != nil {
+						return err
+					}
+					if (skippy < 0) || (iNdEx+skippy) < 0 {
+						return ErrInvalidLengthResources
+					}
+					if (iNdEx + skippy) > postIndex {
+						return io.ErrUnexpectedEOF
+					}
+					iNdEx += skippy
+				}
+			}
+			m.Attributes[mapkey] = mapvalue
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipResources(dAtA[iNdEx:])
diff --git a/api/yorkie/v1/resources.proto b/api/yorkie/v1/resources.proto
index 0eab4ad2..0955ea0d 100644
--- a/api/yorkie/v1/resources.proto
+++ b/api/yorkie/v1/resources.proto
@@ -111,6 +111,13 @@ message Operation {
     repeated TreeNode content = 4;
     TimeTicket executed_at = 5;
   }
+  message TreeStyle {
+    TimeTicket parent_created_at = 1;
+    TreePos from = 2;
+    TreePos to = 3;
+    map<string, string> attributes = 4;
+    TimeTicket executed_at = 5;
+  }
 
   oneof body {
     Set set = 1;
@@ -122,6 +129,7 @@ message Operation {
     Style style = 7;
     Increase increase = 8;
     TreeEdit tree_edit = 9;
+    TreeStyle tree_style = 10;
   }
 }
 
@@ -197,7 +205,7 @@ message RGANode {
   JSONElement element = 2;
 }
 
-message TextNodeAttr {
+message NodeAttr {
   string value = 1;
   TimeTicket updated_at = 2;
 }
@@ -207,7 +215,7 @@ message TextNode {
   string value = 2;
   TimeTicket removed_at = 3;
   TextNodeID ins_prev_id = 4;
-  map<string, TextNodeAttr> attributes = 5;
+  map<string, NodeAttr> attributes = 5;
 }
 
 message TextNodeID {
@@ -222,6 +230,7 @@ message TreeNode {
   TimeTicket removed_at = 4;
   TreePos ins_prev_pos = 5;
   int32 depth = 6;
+  map<string, NodeAttr> attributes = 7;
 }
 
 message TreePos {
diff --git a/pkg/document/crdt/rht.go b/pkg/document/crdt/rht.go
index 806b2205..b7784020 100644
--- a/pkg/document/crdt/rht.go
+++ b/pkg/document/crdt/rht.go
@@ -147,6 +147,11 @@ func (rht *RHT) Nodes() []*RHTNode {
 	return nodes
 }
 
+// Len returns the number of elements.
+func (rht *RHT) Len() int {
+	return len(rht.nodeMapByKey)
+}
+
 // DeepCopy copies itself deeply.
 func (rht *RHT) DeepCopy() *RHT {
 	instance := NewRHT()
@@ -183,3 +188,28 @@ func (rht *RHT) Marshal() string {
 
 	return sb.String()
 }
+
+// ToXML returns the XML representation of this hashtable.
+func (rht *RHT) ToXML() string {
+	members := rht.Elements()
+
+	size := len(members)
+
+	// Extract and sort the keys
+	keys := make([]string, 0, size)
+	for k := range members {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+
+	sb := strings.Builder{}
+	for idx, k := range keys {
+		if idx > 0 {
+			sb.WriteString(" ")
+		}
+		value := members[k]
+		sb.WriteString(fmt.Sprintf(`%s="%s"`, k, EscapeString(value)))
+	}
+
+	return sb.String()
+}
diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go
index 5a439ee5..5a76232e 100644
--- a/pkg/document/crdt/tree.go
+++ b/pkg/document/crdt/tree.go
@@ -61,6 +61,7 @@ type TreeNode struct {
 	InsPrev *TreeNode
 
 	Value string
+	Attrs *RHT
 }
 
 // TreePos represents the position of Tree.
@@ -93,7 +94,7 @@ func (t *TreePos) Compare(other llrb.Key) int {
 }
 
 // NewTreeNode creates a new instance of TreeNode.
-func NewTreeNode(pos *TreePos, nodeType string, value ...string) *TreeNode {
+func NewTreeNode(pos *TreePos, nodeType string, attributes *RHT, value ...string) *TreeNode {
 	node := &TreeNode{
 		Pos: pos,
 	}
@@ -101,6 +102,8 @@ func NewTreeNode(pos *TreePos, nodeType string, value ...string) *TreeNode {
 	if len(value) > 0 {
 		node.Value = value[0]
 	}
+	node.Attrs = attributes
+
 	node.IndexTreeNode = index.NewNode(nodeType, node)
 
 	return node
@@ -132,11 +135,20 @@ func (n *TreeNode) Length() int {
 	return len(encoded)
 }
 
-// String returns the string representation of this node.
+// String returns the string representation of this node's value.
 func (n *TreeNode) String() string {
 	return n.Value
 }
 
+// Attributes returns the string representation of this node's attributes.
+func (n *TreeNode) Attributes() string {
+	if n.Attrs == nil || n.Attrs.Len() == 0 {
+		return ""
+	}
+
+	return " " + n.Attrs.ToXML()
+}
+
 // Append appends the given node to the end of the children.
 func (n *TreeNode) Append(newNodes ...*TreeNode) {
 	indexNodes := make([]*index.Node[*TreeNode], len(newNodes))
@@ -187,7 +199,7 @@ func (n *TreeNode) SplitText(offset int) *TreeNode {
 	rightNode := NewTreeNode(&TreePos{
 		CreatedAt: n.Pos.CreatedAt,
 		Offset:    offset,
-	}, n.Type(), string(rightRune))
+	}, n.Type(), nil, string(rightRune))
 	n.IndexTreeNode.Parent.InsertAfterInternal(rightNode.IndexTreeNode, n.IndexTreeNode)
 
 	return rightNode
@@ -212,7 +224,12 @@ func (n *TreeNode) InsertAt(newNode *TreeNode, offset int) {
 
 // DeepCopy copies itself deeply.
 func (n *TreeNode) DeepCopy() *TreeNode {
-	clone := NewTreeNode(n.Pos, n.Type(), n.Value)
+	var clone *TreeNode
+	if n.Attrs != nil {
+		clone = NewTreeNode(n.Pos, n.Type(), n.Attrs.DeepCopy(), n.Value)
+	} else {
+		clone = NewTreeNode(n.Pos, n.Type(), nil, n.Value)
+	}
 	clone.RemovedAt = n.RemovedAt
 
 	if n.IsText() {
@@ -243,7 +260,7 @@ type Tree struct {
 // NewTree creates a new instance of Tree.
 func NewTree(root *TreeNode, createdAt *time.Ticket) *Tree {
 	tree := &Tree{
-		DummyHead:    NewTreeNode(DummyTreePos, DummyHeadType),
+		DummyHead:    NewTreeNode(DummyTreePos, DummyHeadType, nil),
 		IndexTree:    index.NewTree[*TreeNode](root.IndexTreeNode),
 		NodeMapByPos: llrb.NewTree[*TreePos, *TreeNode](),
 		createdAt:    createdAt,
@@ -445,6 +462,59 @@ func (t *Tree) Edit(from, to *TreePos, content *TreeNode, editedAt *time.Ticket)
 	}
 }
 
+// StyleByIndex applies the given attributes of the given range.
+// This method uses indexes instead of a pair of TreePos for testing.
+func (t *Tree) StyleByIndex(start, end int, attributes map[string]string, editedAt *time.Ticket) {
+	fromPos := t.FindPos(start)
+	toPos := t.FindPos(end)
+
+	t.Style(fromPos, toPos, attributes, editedAt)
+}
+
+// Style applies the given attributes of the given range.
+func (t *Tree) Style(from, to *TreePos, attributes map[string]string, editedAt *time.Ticket) {
+	_, toRight := t.findTreePos(to, editedAt)
+	_, fromRight := t.findTreePos(from, editedAt)
+
+	// 02. style the nodes.
+	t.nodesBetween(fromRight, toRight, func(node *TreeNode) {
+		if node.IsText() {
+			return
+		}
+
+		for key, value := range attributes {
+			if node.Attrs == nil {
+				node.Attrs = NewRHT()
+			}
+			node.Attrs.Set(key, value, editedAt)
+		}
+	})
+}
+
+// findTreePos returns TreePos and the right node of the given index in postorder.
+func (t *Tree) findTreePos(pos *TreePos, editedAt *time.Ticket) (*index.TreePos[*TreeNode], *TreeNode) {
+	treePos := t.toTreePos(pos)
+	if treePos == nil {
+		panic(fmt.Errorf("cannot find node at %p", pos))
+	}
+
+	// Find the appropriate position. This logic is similar to the logical to
+	// handle the same position insertion of RGA.
+	current := treePos
+	for current.Node.Value.Next != nil && current.Node.Value.Next.Pos.CreatedAt.After(editedAt) &&
+		current.Node.Value.IndexTreeNode.Parent == current.Node.Value.Next.IndexTreeNode.Parent {
+
+		current = &index.TreePos[*TreeNode]{
+			Node:   current.Node.Value.Next.IndexTreeNode,
+			Offset: current.Node.Value.Next.Len(),
+		}
+	}
+
+	// TODO(hackerwins): Consider to use current instead of treePos.
+	right := t.IndexTree.FindPostorderRight(treePos)
+	return current, right
+}
+
 // findTreePosWithSplitText finds the right node of the given index in postorder.
 func (t *Tree) findTreePosWithSplitText(pos *TreePos, editedAt *time.Ticket) (*index.TreePos[*TreeNode], *TreeNode) {
 	treePos := t.toTreePos(pos)
diff --git a/pkg/document/json/tree.go b/pkg/document/json/tree.go
index d81831ae..082b17d1 100644
--- a/pkg/document/json/tree.go
+++ b/pkg/document/json/tree.go
@@ -42,6 +42,9 @@ type TreeNode struct {
 	// Value is the value of text node. If this node is an element node, it is
 	// empty string.
 	Value string
+
+	// Attributes is the attributes of this node.
+	Attributes map[string]string
 }
 
 // Tree is a CRDT-based tree structure that is used to represent the document
@@ -64,10 +67,18 @@ func (t *Tree) Edit(fromIdx, toIdx int, content *TreeNode) bool {
 	if fromIdx > toIdx {
 		panic("from should be less than or equal to to")
 	}
+	ticket := t.context.IssueTimeTicket()
 
 	var node *crdt.TreeNode
 	if content != nil {
-		node = crdt.NewTreeNode(crdt.NewTreePos(t.context.IssueTimeTicket(), 0), content.Type, content.Value)
+		var attributes *crdt.RHT
+		if content.Attributes != nil {
+			attributes = crdt.NewRHT()
+			for key, val := range content.Attributes {
+				attributes.Set(key, val, ticket)
+			}
+		}
+		node = crdt.NewTreeNode(crdt.NewTreePos(ticket, 0), content.Type, attributes, content.Value)
 		for _, child := range content.Children {
 			buildDescendants(t.context, child, node)
 		}
@@ -80,7 +91,7 @@ func (t *Tree) Edit(fromIdx, toIdx int, content *TreeNode) bool {
 		clone = node.DeepCopy()
 	}
 
-	ticket := t.context.LastTimeTicket()
+	ticket = t.context.LastTimeTicket()
 	t.Tree.Edit(fromPos, toPos, clone, ticket)
 
 	t.context.Push(operations.NewTreeEdit(
@@ -101,9 +112,18 @@ func (t *Tree) Len() int {
 
 // EditByPath edits this tree with the given path and node.
 func (t *Tree) EditByPath(fromPath []int, toPath []int, content *TreeNode) bool {
+	ticket := t.context.IssueTimeTicket()
+
 	var node *crdt.TreeNode
 	if content != nil {
-		node = crdt.NewTreeNode(crdt.NewTreePos(t.context.IssueTimeTicket(), 0), content.Type, content.Value)
+		var attributes *crdt.RHT
+		if content.Attributes != nil {
+			attributes = crdt.NewRHT()
+			for key, val := range content.Attributes {
+				attributes.Set(key, val, ticket)
+			}
+		}
+		node = crdt.NewTreeNode(crdt.NewTreePos(ticket, 0), content.Type, attributes, content.Value)
 		for _, child := range content.Children {
 			buildDescendants(t.context, child, node)
 		}
@@ -116,7 +136,7 @@ func (t *Tree) EditByPath(fromPath []int, toPath []int, content *TreeNode) bool
 		clone = node.DeepCopy()
 	}
 
-	ticket := t.context.LastTimeTicket()
+	ticket = t.context.LastTimeTicket()
 	t.Tree.Edit(fromPos, toPos, clone, ticket)
 
 	t.context.Push(operations.NewTreeEdit(
@@ -130,14 +150,37 @@ func (t *Tree) EditByPath(fromPath []int, toPath []int, content *TreeNode) bool
 	return true
 }
 
+// Style sets the attributes to the elements of the given range.
+func (t *Tree) Style(fromIdx, toIdx int, attributes map[string]string) bool {
+	if fromIdx > toIdx {
+		panic("from should be less than or equal to to")
+	}
+
+	fromPos := t.Tree.FindPos(fromIdx)
+	toPos := t.Tree.FindPos(toIdx)
+
+	ticket := t.context.IssueTimeTicket()
+	t.Tree.Style(fromPos, toPos, attributes, ticket)
+
+	t.context.Push(operations.NewTreeStyle(
+		t.CreatedAt(),
+		fromPos,
+		toPos,
+		attributes,
+		ticket,
+	))
+
+	return true
+}
+
 // buildRoot converts the given node to a CRDT-based tree node. If the given
 // node is nil, it creates a default root node.
 func buildRoot(ctx *change.Context, node *TreeNode, createdAt *time.Ticket) *crdt.TreeNode {
 	if node == nil {
-		return crdt.NewTreeNode(crdt.NewTreePos(createdAt, 0), DefaultRootNodeType)
+		return crdt.NewTreeNode(crdt.NewTreePos(createdAt, 0), DefaultRootNodeType, nil)
 	}
 
-	root := crdt.NewTreeNode(crdt.NewTreePos(createdAt, 0), node.Type)
+	root := crdt.NewTreeNode(crdt.NewTreePos(createdAt, 0), node.Type, nil)
 	for _, child := range node.Children {
 		buildDescendants(ctx, child, root)
 	}
@@ -148,12 +191,22 @@ func buildRoot(ctx *change.Context, node *TreeNode, createdAt *time.Ticket) *crd
 // buildDescendants converts the given node to a CRDT-based tree node.
 func buildDescendants(ctx *change.Context, n TreeNode, parent *crdt.TreeNode) {
 	if n.Type == index.DefaultTextType {
-		treeNode := crdt.NewTreeNode(crdt.NewTreePos(ctx.IssueTimeTicket(), 0), n.Type, n.Value)
+		treeNode := crdt.NewTreeNode(crdt.NewTreePos(ctx.IssueTimeTicket(), 0), n.Type, nil, n.Value)
 		parent.Append(treeNode)
 		return
 	}
 
-	treeNode := crdt.NewTreeNode(crdt.NewTreePos(ctx.IssueTimeTicket(), 0), n.Type)
+	ticket := ctx.IssueTimeTicket()
+
+	var attributes *crdt.RHT
+	if n.Attributes != nil {
+		attributes = crdt.NewRHT()
+		for key, val := range n.Attributes {
+			attributes.Set(key, val, ticket)
+		}
+	}
+
+	treeNode := crdt.NewTreeNode(crdt.NewTreePos(ticket, 0), n.Type, attributes)
 	parent.Append(treeNode)
 
 	for _, child := range n.Children {
diff --git a/pkg/document/operations/tree_style.go b/pkg/document/operations/tree_style.go
new file mode 100644
index 00000000..f0ed76d8
--- /dev/null
+++ b/pkg/document/operations/tree_style.go
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023 The Yorkie Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package operations
+
+import (
+	"github.com/yorkie-team/yorkie/pkg/document/crdt"
+	"github.com/yorkie-team/yorkie/pkg/document/time"
+)
+
+// TreeStyle represents the style operation of the tree.
+type TreeStyle struct {
+	// parentCreatedAt is the creation time of the Text that executes Style.
+	parentCreatedAt *time.Ticket
+
+	// fromPos represents the start point of the editing range.
+	from *crdt.TreePos
+
+	// toPos represents the end point of the editing range.
+	to *crdt.TreePos
+
+	// attributes represents the tree style.
+	attributes map[string]string
+
+	// executedAt is the time the operation was executed.
+	executedAt *time.Ticket
+}
+
+// NewTreeStyle creates a new instance of TreeStyle.
+func NewTreeStyle(
+	parentCreatedAt *time.Ticket,
+	from *crdt.TreePos,
+	to *crdt.TreePos,
+	attributes map[string]string,
+	executedAt *time.Ticket,
+) *TreeStyle {
+	return &TreeStyle{
+		parentCreatedAt: parentCreatedAt,
+		from:            from,
+		to:              to,
+		attributes:      attributes,
+		executedAt:      executedAt,
+	}
+}
+
+// Execute executes this operation on the given `CRDTRoot`.
+func (e *TreeStyle) Execute(root *crdt.Root) error {
+	parent := root.FindByCreatedAt(e.parentCreatedAt)
+	obj, ok := parent.(*crdt.Tree)
+	if !ok {
+		return ErrNotApplicableDataType
+	}
+
+	obj.Style(e.from, e.to, e.attributes, e.executedAt)
+	return nil
+}
+
+// FromPos returns the start point of the editing range.
+func (e *TreeStyle) FromPos() *crdt.TreePos {
+	return e.from
+}
+
+// ToPos returns the end point of the editing range.
+func (e *TreeStyle) ToPos() *crdt.TreePos {
+	return e.to
+}
+
+// ExecutedAt returns execution time of this operation.
+func (e *TreeStyle) ExecutedAt() *time.Ticket {
+	return e.executedAt
+}
+
+// SetActor sets the given actor to this operation.
+func (e *TreeStyle) SetActor(actorID *time.ActorID) {
+	e.executedAt = e.executedAt.SetActorID(actorID)
+}
+
+// ParentCreatedAt returns the creation time of the Text.
+func (e *TreeStyle) ParentCreatedAt() *time.Ticket {
+	return e.parentCreatedAt
+}
+
+// Attributes returns the content of Style.
+func (e *TreeStyle) Attributes() map[string]string {
+	return e.attributes
+}
diff --git a/pkg/index/tree.go b/pkg/index/tree.go
index 04ef1e71..05fb012b 100644
--- a/pkg/index/tree.go
+++ b/pkg/index/tree.go
@@ -146,7 +146,7 @@ func ToXML[V Value](node *Node[V]) string {
 	}
 
 	builder := strings.Builder{}
-	builder.WriteString("<" + string(node.Type) + ">")
+	builder.WriteString("<" + string(node.Type) + node.Value.Attributes() + ">")
 	for _, child := range node.Children() {
 		builder.WriteString(ToXML(child))
 	}
@@ -165,6 +165,7 @@ type Value interface {
 	IsRemoved() bool
 	Length() int
 	String() string
+	Attributes() string
 }
 
 // Node is a node of Tree.


Test patch:
diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go
index efbbacae..8360766d 100644
--- a/pkg/document/crdt/tree_test.go
+++ b/pkg/document/crdt/tree_test.go
@@ -28,7 +28,7 @@ import (
 
 func TestTreeNode(t *testing.T) {
 	t.Run("text node test", func(t *testing.T) {
-		node := crdt.NewTreeNode(crdt.DummyTreePos, "text", "hello")
+		node := crdt.NewTreeNode(crdt.DummyTreePos, "text", nil, "hello")
 		assert.Equal(t, crdt.DummyTreePos, node.Pos)
 		assert.Equal(t, "text", node.Type())
 		assert.Equal(t, "hello", node.Value)
@@ -38,8 +38,8 @@ func TestTreeNode(t *testing.T) {
 	})
 
 	t.Run("element node test", func(t *testing.T) {
-		para := crdt.NewTreeNode(crdt.DummyTreePos, "p")
-		para.Append(crdt.NewTreeNode(crdt.DummyTreePos, "text", "helloyorkie"))
+		para := crdt.NewTreeNode(crdt.DummyTreePos, "p", nil)
+		para.Append(crdt.NewTreeNode(crdt.DummyTreePos, "text", nil, "helloyorkie"))
 		assert.Equal(t, "<p>helloyorkie</p>", crdt.ToXML(para))
 		assert.Equal(t, 11, para.Len())
 		assert.Equal(t, false, para.IsText())
@@ -55,6 +55,14 @@ func TestTreeNode(t *testing.T) {
 		assert.Equal(t, &crdt.TreePos{CreatedAt: time.InitialTicket, Offset: 5}, right.Pos)
 	})
 
+	t.Run("element node with attributes test", func(t *testing.T) {
+		attrs := crdt.NewRHT()
+		attrs.Set("font-weight", "bold", time.InitialTicket)
+		node := crdt.NewTreeNode(crdt.DummyTreePos, "span", attrs)
+		node.Append(crdt.NewTreeNode(crdt.DummyTreePos, "text", nil, "helloyorkie"))
+		assert.Equal(t, `<span font-weight="bold">helloyorkie</span>`, crdt.ToXML(node))
+	})
+
 	t.Run("UTF-16 code unit test", func(t *testing.T) {
 		tests := []struct {
 			length int
@@ -67,8 +75,8 @@ func TestTreeNode(t *testing.T) {
 			{12, "🌷🎁💩😜👍🏳"},
 		}
 		for _, test := range tests {
-			para := crdt.NewTreeNode(crdt.DummyTreePos, "p")
-			para.Append(crdt.NewTreeNode(crdt.DummyTreePos, "text", test.value))
+			para := crdt.NewTreeNode(crdt.DummyTreePos, "p", nil)
+			para.Append(crdt.NewTreeNode(crdt.DummyTreePos, "text", nil, test.value))
 
 			left := para.Child(0)
 			assert.Equal(t, test.length, left.Len())
@@ -84,14 +92,14 @@ func TestTree(t *testing.T) {
 		ctx := helper.TextChangeContext(root)
 		//       0
 		// <root> </root>
-		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "r"), helper.IssueTime(ctx))
+		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "r", nil), helper.IssueTime(ctx))
 		assert.Equal(t, 0, tree.Root().Len())
 		assert.Equal(t, "<r></r>", tree.ToXML())
 		helper.ListEqual(t, tree, []string{"r"})
 
 		//           1
 		// <root> <p> </p> </root>
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
 		assert.Equal(t, "<r><p></p></r>", tree.ToXML())
 		helper.ListEqual(t, tree, []string{"p", "r"})
 		assert.Equal(t, 2, tree.Root().Len())
@@ -100,7 +108,7 @@ func TestTree(t *testing.T) {
 		// <root> <p> h e l l o </p> </root>
 		tree.EditByIndex(
 			1, 1,
-			crdt.NewTreeNode(helper.IssuePos(ctx), "text", "hello"),
+			crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "hello"),
 			helper.IssueTime(ctx),
 		)
 		assert.Equal(t, "<r><p>hello</p></r>", tree.ToXML())
@@ -109,8 +117,8 @@ func TestTree(t *testing.T) {
 
 		//       0   1 2 3 4 5 6    7   8 9  10 11 12 13    14
 		// <root> <p> h e l l o </p> <p> w  o  r  l  d  </p>  </root>
-		p := crdt.NewTreeNode(helper.IssuePos(ctx), "p")
-		p.InsertAt(crdt.NewTreeNode(helper.IssuePos(ctx), "text", "world"), 0)
+		p := crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil)
+		p.InsertAt(crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "world"), 0)
 		tree.EditByIndex(7, 7, p, helper.IssueTime(ctx))
 		assert.Equal(t, "<r><p>hello</p><p>world</p></r>", tree.ToXML())
 		helper.ListEqual(t, tree, []string{"text.hello", "p", "text.world", "p", "r"})
@@ -120,7 +128,7 @@ func TestTree(t *testing.T) {
 		// <root> <p> h e l l o ! </p> <p> w  o  r  l  d  </p>  </root>
 		tree.EditByIndex(
 			6, 6,
-			crdt.NewTreeNode(helper.IssuePos(ctx), "text", "!"),
+			crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "!"),
 			helper.IssueTime(ctx),
 		)
 		assert.Equal(t, "<r><p>hello!</p><p>world</p></r>", tree.ToXML())
@@ -154,7 +162,7 @@ func TestTree(t *testing.T) {
 		// <root> <p> h e l l o ~ ! </p> <p>  w  o  r  l  d  </p>  </root>
 		tree.EditByIndex(
 			6, 6,
-			crdt.NewTreeNode(helper.IssuePos(ctx), "text", "~"),
+			crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "~"),
 			helper.IssueTime(ctx),
 		)
 		assert.Equal(t, "<r><p>hello~!</p><p>world</p></r>", tree.ToXML())
@@ -167,11 +175,11 @@ func TestTree(t *testing.T) {
 		// <root> <p> a b </p> <p> c d </p> </root>
 
 		ctx := helper.TextChangeContext(helper.TestRoot())
-		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
-		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "cd"), helper.IssueTime(ctx))
+		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
+		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "cd"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>ab</p><p>cd</p></root>", tree.ToXML())
 		helper.ListEqual(t, tree, []string{"text.ab", "p", "text.cd", "p", "root"})
 
@@ -199,11 +207,11 @@ func TestTree(t *testing.T) {
 		// <root> <p> a b </p> <p> c d </p> </root>
 
 		ctx := helper.TextChangeContext(helper.TestRoot())
-		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
-		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "cd"), helper.IssueTime(ctx))
+		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
+		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "cd"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>ab</p><p>cd</p></root>", tree.ToXML())
 		helper.ListEqual(t, tree, []string{"text.ab", "p", "text.cd", "p", "root"})
 
@@ -221,7 +229,7 @@ func TestTree(t *testing.T) {
 		assert.Equal(t, 1, structure.Children[0].Children[1].Size)
 
 		// 03. insert a new text node at the start of the first paragraph.
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "@"), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "@"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>@ad</p></root>", tree.ToXML())
 	})
 
@@ -230,76 +238,98 @@ func TestTree(t *testing.T) {
 		//       0   1   2   3 4 5    6    7    8
 		// <root> <p> <b> <i> a b </i> </b> </p> </root>
 		ctx := helper.TextChangeContext(helper.TestRoot())
-		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i"), helper.IssueTime(ctx))
-		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
+		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b><i>ab</i></b></p></root>", tree.ToXML())
 		tree.EditByIndex(5, 6, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b>ab</b></p></root>", tree.ToXML())
 
 		// 02. Edit between two element nodes in same hierarchy.
-		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i"), helper.IssueTime(ctx))
-		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
+		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b><i>ab</i></b></p></root>", tree.ToXML())
 		tree.EditByIndex(6, 7, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><i>ab</i></p></root>", tree.ToXML())
 
 		// 03. Edit between text and element node in same hierarchy.
-		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i"), helper.IssueTime(ctx))
-		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
+		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b><i>ab</i></b></p></root>", tree.ToXML())
 		tree.EditByIndex(4, 6, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b>a</b></p></root>", tree.ToXML())
 
 		// 04. Edit between text and element node in same hierarchy.
-		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i"), helper.IssueTime(ctx))
-		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
+		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b><i>ab</i></b></p></root>", tree.ToXML())
 		tree.EditByIndex(5, 7, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>ab</p></root>", tree.ToXML())
 
 		// 05. Edit between text and element node in same hierarchy.
-		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i"), helper.IssueTime(ctx))
-		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
+		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b><i>ab</i></b></p></root>", tree.ToXML())
 		tree.EditByIndex(4, 7, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>a</p></root>", tree.ToXML())
 
 		// 06. Edit between text and element node in same hierarchy.
-		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i"), helper.IssueTime(ctx))
-		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
+		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(2, 2, crdt.NewTreeNode(helper.IssuePos(ctx), "i", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(3, 3, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p><b><i>ab</i></b></p></root>", tree.ToXML())
 		tree.EditByIndex(3, 7, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p></p></root>", tree.ToXML())
 
 		// 07. Edit between text and element node in same hierarchy.
-		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root"), helper.IssueTime(ctx))
-		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ab"), helper.IssueTime(ctx))
-		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "b"), helper.IssueTime(ctx))
-		tree.EditByIndex(6, 6, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "cd"), helper.IssueTime(ctx))
-		tree.EditByIndex(10, 10, crdt.NewTreeNode(helper.IssuePos(ctx), "p"), helper.IssueTime(ctx))
-		tree.EditByIndex(11, 11, crdt.NewTreeNode(helper.IssuePos(ctx), "text", "ef"), helper.IssueTime(ctx))
+		tree = crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
+		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "b", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(6, 6, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "cd"), helper.IssueTime(ctx))
+		tree.EditByIndex(10, 10, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(11, 11, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ef"), helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>ab</p><p><b>cd</b></p><p>ef</p></root>", tree.ToXML())
 		tree.EditByIndex(9, 10, nil, helper.IssueTime(ctx))
 		assert.Equal(t, "<root><p>ab</p><b>cd</b><p>ef</p></root>", tree.ToXML())
 	})
+
+	t.Run("style node with attributes test", func(t *testing.T) {
+		// 01. style attributes to an element node.
+		ctx := helper.TextChangeContext(helper.TestRoot())
+		tree := crdt.NewTree(crdt.NewTreeNode(helper.IssuePos(ctx), "root", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(0, 0, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(1, 1, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "ab"), helper.IssueTime(ctx))
+		tree.EditByIndex(4, 4, crdt.NewTreeNode(helper.IssuePos(ctx), "p", nil), helper.IssueTime(ctx))
+		tree.EditByIndex(5, 5, crdt.NewTreeNode(helper.IssuePos(ctx), "text", nil, "cd"), helper.IssueTime(ctx))
+		assert.Equal(t, "<root><p>ab</p><p>cd</p></root>", tree.ToXML())
+
+		tree.StyleByIndex(3, 4, map[string]string{"weight": "bold"}, helper.IssueTime(ctx))
+		assert.Equal(t, `<root><p weight="bold">ab</p><p>cd</p></root>`, tree.ToXML())
+
+		// 02. style attributes to elements.
+		tree.StyleByIndex(3, 8, map[string]string{"style": "italic"}, helper.IssueTime(ctx))
+		assert.Equal(t, `<root><p style="italic" weight="bold">ab</p><p style="italic">cd</p></root>`, tree.ToXML())
+
+		// 03. style attributes to text nodes.
+		tree.StyleByIndex(1, 3, map[string]string{"style": "italic"}, helper.IssueTime(ctx))
+		assert.Equal(t, `<root><p style="italic" weight="bold">ab</p><p style="italic">cd</p></root>`, tree.ToXML())
+	})
 }
diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go
index d02094d6..8fc7fea6 100644
--- a/test/integration/tree_test.go
+++ b/test/integration/tree_test.go
@@ -117,6 +117,26 @@ func TestTree(t *testing.T) {
 		assert.NoError(t, err)
 	})
 
+	t.Run("created from JSON with attributes test", func(t *testing.T) {
+		doc := document.New(helper.TestDocKey(t))
+		err := doc.Update(func(root *json.Object) error {
+			root.SetNewTree("t", &json.TreeNode{
+				Type: "doc",
+				Children: []json.TreeNode{{
+					Type: "p",
+					Children: []json.TreeNode{{
+						Type:       "span",
+						Attributes: map[string]string{"bold": "true"},
+						Children:   []json.TreeNode{{Type: "text", Value: "hello"}},
+					}},
+				}},
+			})
+			assert.Equal(t, `<doc><p><span bold="true">hello</span></p></doc>`, root.GetTree("t").ToXML())
+			return nil
+		})
+		assert.NoError(t, err)
+	})
+
 	t.Run("edit its content test", func(t *testing.T) {
 		doc := document.New(helper.TestDocKey(t))
 		err := doc.Update(func(root *json.Object) error {
@@ -219,6 +239,35 @@ func TestTree(t *testing.T) {
 		assert.NoError(t, err)
 	})
 
+	t.Run("edit its content with attributes test", func(t *testing.T) {
+		doc := document.New(helper.TestDocKey(t))
+		err := doc.Update(func(root *json.Object) error {
+			root.SetNewTree("t", &json.TreeNode{Type: "doc"})
+			assert.Equal(t, "<doc></doc>", root.GetTree("t").ToXML())
+
+			root.GetTree("t").Edit(0, 0, &json.TreeNode{
+				Type:       "p",
+				Attributes: map[string]string{"bold": "true"},
+				Children:   []json.TreeNode{{Type: "text", Value: "ab"}},
+			})
+			assert.Equal(t, `<doc><p bold="true">ab</p></doc>`, root.GetTree("t").ToXML())
+
+			root.GetTree("t").Edit(4, 4, &json.TreeNode{
+				Type:       "p",
+				Attributes: map[string]string{"italic": "true"},
+				Children:   []json.TreeNode{{Type: "text", Value: "cd"}},
+			})
+			assert.Equal(t, `<doc><p bold="true">ab</p><p italic="true">cd</p></doc>`, root.GetTree("t").ToXML())
+
+			root.GetTree("t").Edit(2, 6, nil)
+			assert.Equal(t, `<doc><p italic="true">ad</p></doc>`, root.GetTree("t").ToXML())
+
+			return nil
+		})
+		assert.NoError(t, err)
+		assert.Equal(t, `<doc><p italic="true">ad</p></doc>`, doc.Root().GetTree("t").ToXML())
+	})
+
 	t.Run("sync with other clients test", func(t *testing.T) {
 		ctx := context.Background()
 		d1 := document.New(helper.TestDocKey(t))
@@ -244,6 +293,37 @@ func TestTree(t *testing.T) {
 		assert.Equal(t, "<root><p>Hello</p></root>", d2.Root().GetTree("t").ToXML())
 	})
 
+	t.Run("set attributes test", func(t *testing.T) {
+		ctx := context.Background()
+		d1 := document.New(helper.TestDocKey(t))
+		assert.NoError(t, c1.Attach(ctx, d1))
+
+		assert.NoError(t, d1.Update(func(root *json.Object) error {
+			root.SetNewTree("t", &json.TreeNode{
+				Type: "root",
+				Children: []json.TreeNode{
+					{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "ab"}}},
+					{Type: "p", Attributes: map[string]string{"italic": "true"}, Children: []json.TreeNode{{Type: "text", Value: "cd"}}},
+				},
+			})
+			return nil
+		}))
+		assert.NoError(t, c1.Sync(ctx))
+		assert.Equal(t, `<root><p>ab</p><p italic="true">cd</p></root>`, d1.Root().GetTree("t").ToXML())
+
+		assert.NoError(t, d1.Update(func(root *json.Object) error {
+			root.GetTree("t").Style(3, 4, map[string]string{"bold": "true"})
+			return nil
+		}))
+
+		assert.NoError(t, c1.Sync(ctx))
+		d2 := document.New(helper.TestDocKey(t))
+		assert.NoError(t, c2.Attach(ctx, d2))
+
+		assert.Equal(t, `<root><p bold="true">ab</p><p italic="true">cd</p></root>`, d1.Root().GetTree("t").ToXML())
+		assert.Equal(t, `<root><p bold="true">ab</p><p italic="true">cd</p></root>`, d2.Root().GetTree("t").ToXML())
+	})
+
 	t.Run("insert inline content to the same position(left) concurrently test", func(t *testing.T) {
 		ctx := context.Background()
 		d1 := document.New(helper.TestDocKey(t))