feat(w4): ship v1.5.0 timeline, character arc, and compliance

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-08 17:40:42 +08:00
parent c5c0b7329b
commit cb6b4c3731
51 changed files with 1528 additions and 42 deletions
+61
View File
@@ -0,0 +1,61 @@
import { randomUUID } from 'crypto'
import type { TimelineEntry } from '../../../shared/timeline'
import type { SqliteDb } from '../types'
function mapRow(row: Record<string, unknown>): TimelineEntry {
return {
id: row.id as string,
kind: 'manual',
title: row.title as string,
storyTime: (row.story_time as string) ?? null,
sortOrder: row.sort_order as number,
notes: (row.notes as string) ?? ''
}
}
export class TimelineRepository {
constructor(private db: SqliteDb) {}
listManual(): TimelineEntry[] {
return (
this.db
.prepare('SELECT * FROM timeline_events ORDER BY sort_order ASC, created_at ASC')
.all() as Record<string, unknown>[]
).map(mapRow)
}
upsert(input: {
id?: string
title: string
storyTime?: string | null
sortOrder?: number
notes?: string
}): TimelineEntry {
const id = input.id ?? randomUUID()
const existing = this.db.prepare('SELECT id FROM timeline_events WHERE id = ?').get(id)
if (existing) {
this.db
.prepare(
`UPDATE timeline_events SET title = ?, story_time = ?, sort_order = ?, notes = ? WHERE id = ?`
)
.run(
input.title,
input.storyTime ?? null,
input.sortOrder ?? 0,
input.notes ?? '',
id
)
} else {
this.db
.prepare(
`INSERT INTO timeline_events (id, title, story_time, sort_order, notes) VALUES (?, ?, ?, ?, ?)`
)
.run(id, input.title, input.storyTime ?? null, input.sortOrder ?? 0, input.notes ?? '')
}
return mapRow(this.db.prepare('SELECT * FROM timeline_events WHERE id = ?').get(id) as Record<string, unknown>)
}
delete(id: string): void {
this.db.prepare('DELETE FROM timeline_events WHERE id = ?').run(id)
}
}