Change proposals enable review and approval workflows in your application. Users and AI agents can propose changes in isolated versions that are reviewed before merging into the main version.
Lix uses "change proposal" instead of "pull request" because many non-technical users aren't familiar with Git terminology. "Change proposal" clearly describes proposing changes for review.
A change proposal tracks two versions:
The proposal can be accepted (merging changes and deleting the source version) or rejected (marking the proposal without merging).
Change proposals are simple - they just track the relationship between versions:
{
id: string; // Unique identifier
source_version_id: string; // Version with proposed changes
target_version_id: string; // Version to merge into
status: "open" | "accepted" | "rejected";
}Note: There are no built-in title, description, or comment fields. It's common to attach a conversation to a proposal for descriptions and comments, or store additional data as metadata in your own schema.
// Get the main version
const mainVersion = await lix.db
.selectFrom("version")
.where("name", "=", "main")
.select(["id"])
.executeTakeFirstOrThrow();
// Create a feature version for proposed changes (inherits from main)
const featureVersion = await createVersion({
lix,
name: "feature-typo-fixes",
inheritsFrom: mainVersion,
});
// Switch to the feature version and make edits before proposing
await switchVersion({ lix, to: featureVersion });
// ...perform edits here (e.g., update files/entities)...
// Create a change proposal
const proposal = await createChangeProposal({
lix,
source: { id: featureVersion.id },
target: { id: mainVersion.id },
});
console.log("Change proposal created:", proposal.id);
console.log("Status:", proposal.status); // "open"What happens:
// Get the main version
const mainVersion = await lix.db
.selectFrom("version")
.where("name", "=", "main")
.select(["id"])
.executeTakeFirstOrThrow();
// Create a feature version for proposed changes (inherits from main)
const featureVersion = await createVersion({
lix,
name: "feature-typo-fixes",
inheritsFrom: mainVersion,
});
// Switch to the feature version and make edits before proposing
await switchVersion({ lix, to: featureVersion });
// ...perform edits here (e.g., update files/entities)...
// Create a change proposal
const proposal = await createChangeProposal({
lix,
source: { id: featureVersion.id },
target: { id: mainVersion.id },
});
console.log("Change proposal created:", proposal.id);
console.log("Status:", proposal.status); // "open"// Query all open proposals
const openProposals = await lix.db
.selectFrom("change_proposal")
.where("status", "=", "open")
.selectAll()
.execute();
console.log("Open proposals:", openProposals.length);
// Query proposals for a specific target version
const mainProposals = await lix.db
.selectFrom("change_proposal")
.where("target_version_id", "=", mainVersion.id)
.selectAll()
.execute();
console.log("Proposals targeting main:", mainProposals.length);Query proposals by status, target version, or any other criteria using standard SQL.
// Get the main version
const mainVersion = await lix.db
.selectFrom("version")
.where("name", "=", "main")
.select(["id"])
.executeTakeFirstOrThrow();
// Create a feature version for proposed changes (inherits from main)
const featureVersion = await createVersion({
lix,
name: "feature-typo-fixes",
inheritsFrom: mainVersion,
});
// Switch to the feature version and make edits before proposing
await switchVersion({ lix, to: featureVersion });
// ...perform edits here (e.g., update files/entities)...
// Create a change proposal
const proposal = await createChangeProposal({
lix,
source: { id: featureVersion.id },
target: { id: mainVersion.id },
});
console.log("Change proposal created:", proposal.id);
console.log("Status:", proposal.status); // "open"
// Query all open proposals
const openProposals = await lix.db
.selectFrom("change_proposal")
.where("status", "=", "open")
.selectAll()
.execute();
console.log("Open proposals:", openProposals.length);
// Query proposals for a specific target version
const mainProposals = await lix.db
.selectFrom("change_proposal")
.where("target_version_id", "=", mainVersion.id)
.selectAll()
.execute();
console.log("Proposals targeting main:", mainProposals.length);// Switch back to main before accepting (so deleting the source version is safe)
await switchVersion({ lix, to: mainVersion });
// Accept the proposal - this merges changes and deletes the source version
await acceptChangeProposal({
lix,
proposal: { id: proposal.id },
});
console.log("Proposal accepted and merged");
// Verify the source version was deleted after acceptance
const acceptedSource = await lix.db
.selectFrom("version")
.where("id", "=", featureVersion.id)
.selectAll()
.executeTakeFirst();
console.log("Source version deleted:", !acceptedSource);What happens:
// Get the main version
const mainVersion = await lix.db
.selectFrom("version")
.where("name", "=", "main")
.select(["id"])
.executeTakeFirstOrThrow();
// Create a feature version for proposed changes (inherits from main)
const featureVersion = await createVersion({
lix,
name: "feature-typo-fixes",
inheritsFrom: mainVersion,
});
// Switch to the feature version and make edits before proposing
await switchVersion({ lix, to: featureVersion });
// ...perform edits here (e.g., update files/entities)...
// Create a change proposal
const proposal = await createChangeProposal({
lix,
source: { id: featureVersion.id },
target: { id: mainVersion.id },
});
console.log("Change proposal created:", proposal.id);
console.log("Status:", proposal.status); // "open"
// Query all open proposals
const openProposals = await lix.db
.selectFrom("change_proposal")
.where("status", "=", "open")
.selectAll()
.execute();
console.log("Open proposals:", openProposals.length);
// Query proposals for a specific target version
const mainProposals = await lix.db
.selectFrom("change_proposal")
.where("target_version_id", "=", mainVersion.id)
.selectAll()
.execute();
console.log("Proposals targeting main:", mainProposals.length);
// Switch back to main before accepting (so deleting the source version is safe)
await switchVersion({ lix, to: mainVersion });
// Accept the proposal - this merges changes and deletes the source version
await acceptChangeProposal({
lix,
proposal: { id: proposal.id },
});
console.log("Proposal accepted and merged");
// Verify the source version was deleted after acceptance
const acceptedSource = await lix.db
.selectFrom("version")
.where("id", "=", featureVersion.id)
.selectAll()
.executeTakeFirst();
console.log("Source version deleted:", !acceptedSource);// Create another proposal to demonstrate rejection
const featureVersion2 = await createVersion({
lix,
name: "feature-experimental",
inheritsFrom: mainVersion,
});
const proposal2 = await createChangeProposal({
lix,
source: { id: featureVersion2.id },
target: { id: mainVersion.id },
});
// Reject the proposal - marks as rejected, keeps source version
await rejectChangeProposal({
lix,
proposal: { id: proposal2.id },
});
console.log("Proposal rejected");
// Verify the rejection
const rejectedProposal = await lix.db
.selectFrom("change_proposal")
.where("id", "=", proposal2.id)
.selectAll()
.executeTakeFirst();
console.log("Proposal status:", rejectedProposal?.status); // "rejected"
// Source version still exists after rejection
const sourceExists = await lix.db
.selectFrom("version")
.where("id", "=", featureVersion2.id)
.selectAll()
.executeTakeFirst();
console.log("Source version still exists:", !!sourceExists);What happens:
User collaboration
AI agent safety
Approval workflows
To build a full review UI, you'll likely want to:
1. Add your own metadata:
// Store title, description, etc. in your own schema
await lix.db.insertInto("proposal_metadata").values({
proposal_id: proposal.id,
title: "Fix typos in documentation",
description: "Corrects spelling errors in user guide",
author_id: currentUser.id,
});2. Show diffs:
// Compare source and target versions to show changes
const diff = await lix.db
.selectFrom("file")
.where("lixcol_version_id", "=", proposal.source_version_id)
// ... compare with target version
.execute();3. Add comments/discussions:
4. Handle rejection reasons:
The @lix-js/agent-sdk provides higher-level abstractions:
See the agent SDK documentation for details.