FRE-594: Implement revision tracking and version history system

Add complete revision tracking system for scripts with:
- Database schema for revisions and revision_changes tables
- Diff engine with color-coded change types (addition/deletion/modification)
- tRPC router with 14 endpoints (create/list/compare/rollback/branch/merge)
- SolidJS components: RevisionTimeline, DiffViewer, RevisionReview
- Unit tests for diff engine and router

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-24 05:54:06 -04:00
parent 8dc4827597
commit ccbf3039d9
12 changed files with 1751 additions and 3 deletions

View File

@@ -0,0 +1,77 @@
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
import { scripts } from "./scripts";
import { users } from "./users";
export const revisions = sqliteTable(
"revisions",
{
id: integer("id").primaryKey({ autoIncrement: true }),
scriptId: integer("script_id")
.notNull()
.references(() => scripts.id),
versionNumber: integer("version_number").notNull(),
branchName: text("branch_name").notNull().default("main"),
parentRevisionId: integer("parent_revision_id"),
title: text("title").notNull(),
summary: text("summary"),
content: text("content").notNull(),
authorId: integer("author_id")
.notNull()
.references(() => users.id),
status: text("status", {
enum: ["draft", "pending_review", "accepted", "rejected"],
})
.notNull()
.default("draft"),
reviewedById: integer("reviewed_by_id").references(() => users.id),
reviewedAt: integer("reviewed_at", { mode: "timestamp" }),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
},
(table) => ({
scriptVersionIdx: index("revisions_script_version_idx").on(
table.scriptId,
table.versionNumber
),
scriptBranchIdx: index("revisions_script_branch_idx").on(
table.scriptId,
table.branchName
),
authorIdx: index("revisions_author_idx").on(table.authorId),
})
);
export const revisionChanges = sqliteTable(
"revision_changes",
{
id: integer("id").primaryKey({ autoIncrement: true }),
revisionId: integer("revision_id")
.notNull()
.references(() => revisions.id),
changeType: text("change_type", {
enum: ["addition", "deletion", "modification"],
}).notNull(),
elementType: text("element_type"),
oldContent: text("old_content"),
newContent: text("new_content"),
sceneNumber: integer("scene_number"),
lineNumber: integer("line_number"),
pageNumber: integer("page_number"),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
},
(table) => ({
revisionIdx: index("revision_changes_revision_idx").on(table.revisionId),
changeTypeIdx: index("revision_changes_type_idx").on(table.changeType),
})
);
export type Revision = typeof revisions.$inferSelect;
export type NewRevision = typeof revisions.$inferInsert;
export type RevisionChange = typeof revisionChanges.$inferSelect;
export type NewRevisionChange = typeof revisionChanges.$inferInsert;