feat(pitch-deck): full pitch versioning with git-style history (#4)
Some checks failed
Build pitch-deck / build-and-push (push) Failing after 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
CI / Deploy (push) Failing after 4s
Some checks failed
Build pitch-deck / build-and-push (push) Failing after 1m8s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 32s
CI / test-python-voice (push) Successful in 32s
CI / test-bqas (push) Successful in 32s
CI / Deploy (push) Failing after 4s
Full pitch versioning: 12 data tables versioned as JSONB snapshots, git-style parent chain (draft→commit→fork), per-investor assignment, side-by-side diff engine, version-aware /api/data + /api/financial-model. Bug fixes: FM editor [object Object] for JSONB arrays, admin scroll. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit was merged in pull request #4.
This commit is contained in:
102
pitch-deck/lib/version-diff.ts
Normal file
102
pitch-deck/lib/version-diff.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
export interface FieldDiff {
|
||||
key: string
|
||||
before: unknown
|
||||
after: unknown
|
||||
}
|
||||
|
||||
export interface RowDiff {
|
||||
status: 'added' | 'removed' | 'changed' | 'unchanged'
|
||||
id?: string | number
|
||||
fields: FieldDiff[]
|
||||
before?: Record<string, unknown>
|
||||
after?: Record<string, unknown>
|
||||
}
|
||||
|
||||
export interface TableDiff {
|
||||
tableName: string
|
||||
rows: RowDiff[]
|
||||
hasChanges: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff two arrays of row objects. Matches rows by `id` field if present,
|
||||
* otherwise by array position.
|
||||
*/
|
||||
export function diffTable(
|
||||
tableName: string,
|
||||
before: unknown[],
|
||||
after: unknown[],
|
||||
): TableDiff {
|
||||
const beforeArr = (before || []) as Record<string, unknown>[]
|
||||
const afterArr = (after || []) as Record<string, unknown>[]
|
||||
|
||||
const rows: RowDiff[] = []
|
||||
|
||||
// Build lookup by id if available
|
||||
const hasIds = beforeArr.length > 0 && 'id' in (beforeArr[0] || {})
|
||||
|
||||
if (hasIds) {
|
||||
const beforeMap = new Map(beforeArr.map(r => [String(r.id), r]))
|
||||
const afterMap = new Map(afterArr.map(r => [String(r.id), r]))
|
||||
const allIds = new Set([...beforeMap.keys(), ...afterMap.keys()])
|
||||
|
||||
for (const id of allIds) {
|
||||
const b = beforeMap.get(id)
|
||||
const a = afterMap.get(id)
|
||||
|
||||
if (!b && a) {
|
||||
rows.push({ status: 'added', id: a.id as string, fields: [], after: a })
|
||||
} else if (b && !a) {
|
||||
rows.push({ status: 'removed', id: b.id as string, fields: [], before: b })
|
||||
} else if (b && a) {
|
||||
const fields = diffFields(b, a)
|
||||
rows.push({
|
||||
status: fields.length > 0 ? 'changed' : 'unchanged',
|
||||
id: b.id as string,
|
||||
fields,
|
||||
before: b,
|
||||
after: a,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Positional comparison
|
||||
const maxLen = Math.max(beforeArr.length, afterArr.length)
|
||||
for (let i = 0; i < maxLen; i++) {
|
||||
const b = beforeArr[i]
|
||||
const a = afterArr[i]
|
||||
if (!b && a) {
|
||||
rows.push({ status: 'added', fields: [], after: a })
|
||||
} else if (b && !a) {
|
||||
rows.push({ status: 'removed', fields: [], before: b })
|
||||
} else if (b && a) {
|
||||
const fields = diffFields(b, a)
|
||||
rows.push({
|
||||
status: fields.length > 0 ? 'changed' : 'unchanged',
|
||||
fields,
|
||||
before: b,
|
||||
after: a,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
tableName,
|
||||
rows,
|
||||
hasChanges: rows.some(r => r.status !== 'unchanged'),
|
||||
}
|
||||
}
|
||||
|
||||
function diffFields(before: Record<string, unknown>, after: Record<string, unknown>): FieldDiff[] {
|
||||
const diffs: FieldDiff[] = []
|
||||
const allKeys = new Set([...Object.keys(before), ...Object.keys(after)])
|
||||
for (const key of allKeys) {
|
||||
const bVal = JSON.stringify(before[key] ?? null)
|
||||
const aVal = JSON.stringify(after[key] ?? null)
|
||||
if (bVal !== aVal) {
|
||||
diffs.push({ key, before: before[key], after: after[key] })
|
||||
}
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
Reference in New Issue
Block a user