Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dds/tree/api-report/tree.alpha.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,7 @@ export interface RunTransaction {

// @alpha @input
export interface RunTransactionParams {
readonly label?: unknown;
readonly preconditions?: readonly TransactionConstraint[];
}

Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ export {
type GraphCommit,
CommitKind,
type CommitMetadata,
type CommitMetadataAlpha,
type RevisionTag,
RevisionTagSchema,
RevisionTagCodec,
Expand Down
1 change: 1 addition & 0 deletions packages/dds/tree/src/core/rebase/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export {
type GraphCommit,
CommitKind,
type CommitMetadata,
type CommitMetadataAlpha,
type RevisionTag,
RevisionTagSchema,
type EncodedRevisionTag,
Expand Down
12 changes: 12 additions & 0 deletions packages/dds/tree/src/core/rebase/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,18 @@ export interface CommitMetadata {
readonly isLocal: boolean;
}

/**
* Extended commit metadata that includes an optional user-provided label.
* @alpha
*/
export interface CommitMetadataAlpha extends CommitMetadata {
/**
* Optional label provided by the user when commit was created.
* This can be used by undo/redo to group or classify edits.
*/
label?: unknown;
}

/**
* Creates a new graph commit object. This is useful for creating copies of commits with different parentage.
* @param parent - the parent of the new commit
Expand Down
60 changes: 35 additions & 25 deletions packages/dds/tree/src/shared-tree/schematizingTreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,33 +296,43 @@ export class SchematizingSimpleTreeView<
addConstraintsToTransaction(this.checkout, constraintsOnRevert, constraints);
};

this.checkout.transaction.start();

// Validate preconditions before running the transaction callback.
addConstraints(false /* constraintsOnRevert */, params?.preconditions);
const transactionCallbackStatus = transaction();
const rollback = transactionCallbackStatus?.rollback;
const value = (
transactionCallbackStatus as TransactionCallbackStatus<TSuccessValue, TFailureValue>
)?.value;

if (rollback === true) {
this.checkout.transaction.abort();
return value !== undefined
? { success: false, value: value as TFailureValue }
: { success: false };
}
const executeTransaction = ():
| TransactionResultExt<TSuccessValue, TFailureValue>
| TransactionResult => {
this.checkout.transaction.start();

// Validate preconditions on revert after running the transaction callback and was successful.
addConstraints(
true /* constraintsOnRevert */,
transactionCallbackStatus?.preconditionsOnRevert,
);
// Validate preconditions before running the transaction callback.
addConstraints(false /* constraintsOnRevert */, params?.preconditions);
const transactionCallbackStatus = transaction();
const rollback = transactionCallbackStatus?.rollback;
const value = (
transactionCallbackStatus as TransactionCallbackStatus<TSuccessValue, TFailureValue>
)?.value;

if (rollback === true) {
this.checkout.transaction.abort();
return value !== undefined
? { success: false, value: value as TFailureValue }
: { success: false };
}

// Validate preconditions on revert after running the transaction callback and was successful.
addConstraints(
true /* constraintsOnRevert */,
transactionCallbackStatus?.preconditionsOnRevert,
);

this.checkout.transaction.commit();
return value !== undefined
? { success: true, value: value as TSuccessValue }
: { success: true };
this.checkout.transaction.commit();

return value !== undefined
? { success: true, value: value as TSuccessValue }
: { success: true };
};

if (params?.label !== undefined) {
return this.checkout.runWithTransactionLabel(params.label, () => executeTransaction());
}
return executeTransaction();
}

private ensureUndisposed(): void {
Expand Down
33 changes: 29 additions & 4 deletions packages/dds/tree/src/shared-tree/treeCheckout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
type AnchorSetRootEvents,
type ChangeFamily,
CommitKind,
type CommitMetadata,
type DeltaVisitor,
type DetachedFieldIndex,
type IEditableForest,
Expand Down Expand Up @@ -48,6 +47,7 @@ import {
type TreeNodeStoredSchema,
LeafNodeStoredSchema,
diffHistories,
type CommitMetadataAlpha,
} from "../core/index.js";
import {
type FieldBatchCodec,
Expand Down Expand Up @@ -122,7 +122,7 @@ export interface CheckoutEvents {
* @param getRevertible - a function provided that allows users to get a revertible for the change. If not provided,
* this change is not revertible.
*/
changed(data: CommitMetadata, getRevertible?: RevertibleAlphaFactory): void;
changed(data: CommitMetadataAlpha, getRevertible?: RevertibleAlphaFactory): void;

/**
* Fired when a new branch is created from this checkout.
Expand Down Expand Up @@ -370,6 +370,9 @@ export class TreeCheckout implements ITreeCheckoutFork {

private editLock: EditLock;

// User-defined label associated with the transaction whose commit is currently being produced for this checkout.
public transactionLabel?: unknown;

private readonly views = new Set<TreeView<ImplicitFieldSchema>>();

/**
Expand Down Expand Up @@ -421,6 +424,18 @@ export class TreeCheckout implements ITreeCheckoutFork {
this.registerForBranchEvents();
}

public runWithTransactionLabel<TLabel, TResult>(
label: TLabel,
fn: (label: TLabel) => TResult,
): TResult {
this.transactionLabel = label;
try {
return fn(label);
} finally {
this.transactionLabel = undefined;
}
}

private registerForBranchEvents(): void {
this.#transaction.branch.events.on("afterChange", this.onAfterBranchChange);
this.#transaction.activeBranchEvents.on("afterChange", this.onAfterChange);
Expand Down Expand Up @@ -532,12 +547,22 @@ export class TreeCheckout implements ITreeCheckoutFork {
};

let withinEventContext = true;
this.#events.emit("changed", { isLocal: true, kind }, getRevertible);
const metaData: CommitMetadataAlpha = {
isLocal: true,
kind,
label: this.transactionLabel,
};
this.#events.emit("changed", metaData, getRevertible);
withinEventContext = false;
}
} else if (this.isRemoteChangeEvent(event)) {
// TODO: figure out how to plumb through commit kind info for remote changes
this.#events.emit("changed", { isLocal: false, kind: CommitKind.Default });
const metaData: CommitMetadataAlpha = {
isLocal: false,
kind: CommitKind.Default,
label: this.transactionLabel,
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The transactionLabel should not be included for remote changes. Remote changes come from other clients and should not have the local checkout's transaction label. The label should be undefined for remote changes.

Suggested change
label: this.transactionLabel,
label: undefined,

Copilot uses AI. Check for mistakes.
};
this.#events.emit("changed", metaData);
}
};

Expand Down
8 changes: 8 additions & 0 deletions packages/dds/tree/src/simple-tree/api/transactionTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,12 @@ export interface RunTransactionParams {
* this client and ignored by all other clients.
*/
readonly preconditions?: readonly TransactionConstraint[];
/**
* An optional user-defined label for this transaction.
*
* This label is associated with the commit produced by this transaction, and is surfaced through {@link CommitMetadataAlpha.label},
* in the `commitApplied` event.
*
*/
readonly label?: unknown;
}
8 changes: 4 additions & 4 deletions packages/dds/tree/src/simple-tree/api/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type { IFluidLoadable, IDisposable, Listenable } from "@fluidframework/core-interfaces";

import type {
CommitMetadata,
CommitMetadataAlpha,
RevertibleAlphaFactory,
RevertibleFactory,
} from "../../core/index.js";
Expand Down Expand Up @@ -508,7 +508,7 @@ export interface TreeBranchEvents extends Omit<TreeViewEvents, "commitApplied">
* @param getRevertible - a function that allows users to get a revertible for the change. If not provided,
* this change is not revertible.
*/
changed(data: CommitMetadata, getRevertible?: RevertibleAlphaFactory): void;
changed(data: CommitMetadataAlpha, getRevertible?: RevertibleAlphaFactory): void;

/**
* Fired when:
Expand All @@ -527,7 +527,7 @@ export interface TreeBranchEvents extends Omit<TreeViewEvents, "commitApplied">
* @param getRevertible - a function provided that allows users to get a revertible for the commit that was applied. If not provided,
* this commit is not revertible.
*/
commitApplied(data: CommitMetadata, getRevertible?: RevertibleAlphaFactory): void;
commitApplied(data: CommitMetadataAlpha, getRevertible?: RevertibleAlphaFactory): void;
}

/**
Expand Down Expand Up @@ -571,7 +571,7 @@ export interface TreeViewEvents {
* @param getRevertible - a function provided that allows users to get a revertible for the commit that was applied. If not provided,
* this commit is not revertible.
*/
commitApplied(data: CommitMetadata, getRevertible?: RevertibleFactory): void;
commitApplied(data: CommitMetadataAlpha, getRevertible?: RevertibleFactory): void;
}

/**
Expand Down
Loading