Cleanup: drop vestigial utterance columns, sync story-graph doc
- migration 022 drops utterances.advances_to_utterance_id and .effect, which the parent-only/child-count dialogue model never used; purge their references. - rewrite docs/story-graph.md to match what was built (parent-child utterances, vertical mystery graph, graph runtime, gate stubs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,7 +33,7 @@ suite('PostgreSQL migrations', () => {
|
||||
const migrationsDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'migrations')
|
||||
const firstRun: string[] = []
|
||||
await runMigrations(testDatabaseUrl, migrationsDir, message => firstRun.push(message))
|
||||
expect(firstRun.filter(message => message.startsWith('apply '))).toHaveLength(21)
|
||||
expect(firstRun.filter(message => message.startsWith('apply '))).toHaveLength(22)
|
||||
|
||||
const client = new Client({ connectionString: testDatabaseUrl })
|
||||
await client.connect()
|
||||
@@ -49,7 +49,7 @@ suite('PostgreSQL migrations', () => {
|
||||
]))
|
||||
expect(tableNames).not.toEqual(expect.arrayContaining(['cases', 'widgets', 'widget_relations', 'cutscenes', 'dialogue_steps', 'mystery_chapters', 'seen_dialogue']))
|
||||
const ledger = await client.query<{ count: string }>('SELECT COUNT(*)::text AS count FROM osint.schema_migrations')
|
||||
expect(ledger.rows[0].count).toBe('21')
|
||||
expect(ledger.rows[0].count).toBe('22')
|
||||
const connectionColumns = await client.query<{ column_name: string }>(`SELECT column_name FROM information_schema.columns WHERE table_schema='osint' AND table_name='exhibit_connections'`)
|
||||
expect(connectionColumns.rows.map(row => row.column_name)).toEqual(expect.arrayContaining(['label', 'tightness', 'tag_style', 'tag_position_percent', 'tag_lateral_offset']))
|
||||
const eventOccurrence = await client.query<{ is_nullable: string }>(`SELECT is_nullable FROM information_schema.columns WHERE table_schema='osint' AND table_name='event_exhibits' AND column_name='occurred_at'`)
|
||||
@@ -58,7 +58,7 @@ suite('PostgreSQL migrations', () => {
|
||||
|
||||
const secondRun: string[] = []
|
||||
await runMigrations(testDatabaseUrl, migrationsDir, message => secondRun.push(message))
|
||||
expect(secondRun.filter(message => message.startsWith('skip '))).toHaveLength(21)
|
||||
expect(secondRun.filter(message => message.startsWith('skip '))).toHaveLength(22)
|
||||
expect(secondRun.some(message => message.startsWith('apply '))).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,8 +21,8 @@ export type LevelTemplateOption = { versionId: string; slug: string; name: strin
|
||||
export type Utterer = 'npc' | 'player'
|
||||
export type UtteranceDto = {
|
||||
id: string; nodeId: string; utterer: Utterer; npcId: string | null; poseKey: string | null; text: string
|
||||
parentUtteranceId: string | null; advancesToUtteranceId: string | null; terminalId: string | null
|
||||
effect: string | null; xpos: number; ypos: number; sortOrder: number
|
||||
parentUtteranceId: string | null; terminalId: string | null
|
||||
xpos: number; ypos: number; sortOrder: number
|
||||
}
|
||||
|
||||
// A sensible starter terminal set so a freshly dropped node is immediately wireable.
|
||||
@@ -46,7 +46,7 @@ export interface StoryGraphRepository {
|
||||
listLevelTemplates(): Promise<LevelTemplateOption[]>
|
||||
listUtterances(nodeId: string): Promise<UtteranceDto[]>
|
||||
createUtterance(nodeId: string, input: { utterer: Utterer; xpos: number; ypos: number; text?: string }): Promise<UtteranceDto | null>
|
||||
updateUtterance(id: string, input: Partial<{ text: string; utterer: Utterer; npcId: string | null; poseKey: string | null; xpos: number; ypos: number; parentUtteranceId: string | null; advancesToUtteranceId: string | null; terminalId: string | null; effect: string | null }>): Promise<{ ok: boolean; error?: string }>
|
||||
updateUtterance(id: string, input: Partial<{ text: string; utterer: Utterer; npcId: string | null; poseKey: string | null; xpos: number; ypos: number; parentUtteranceId: string | null; terminalId: string | null }>): Promise<{ ok: boolean; error?: string }>
|
||||
deleteUtterance(id: string): Promise<boolean>
|
||||
authorGraph(mysteryId: string, spec: GraphSpec): Promise<{ nodes: number } | null>
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
|
||||
async listUtterances(nodeId) {
|
||||
const result = await pool.query<UtteranceRow>(
|
||||
`SELECT id,node_id,utterer,npc_id,pose_key,text,parent_utterance_id,advances_to_utterance_id,terminal_id,effect,xpos,ypos,sort_order
|
||||
`SELECT id,node_id,utterer,npc_id,pose_key,text,parent_utterance_id,terminal_id,xpos,ypos,sort_order
|
||||
FROM osint.utterances WHERE node_id=$1 ORDER BY sort_order,id`, [nodeId])
|
||||
return result.rows.map(mapUtterance)
|
||||
},
|
||||
@@ -192,7 +192,7 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
await pool.query('INSERT INTO osint.utterances (id,node_id,utterer,text,xpos,ypos,sort_order) VALUES ($1,$2,$3,$4,$5,$6,$7)',
|
||||
[id, nodeId, input.utterer, input.text || '', input.xpos, input.ypos, order.rows[0].next])
|
||||
const created = await pool.query<UtteranceRow>(
|
||||
`SELECT id,node_id,utterer,npc_id,pose_key,text,parent_utterance_id,advances_to_utterance_id,terminal_id,effect,xpos,ypos,sort_order FROM osint.utterances WHERE id=$1`, [id])
|
||||
`SELECT id,node_id,utterer,npc_id,pose_key,text,parent_utterance_id,terminal_id,xpos,ypos,sort_order FROM osint.utterances WHERE id=$1`, [id])
|
||||
return mapUtterance(created.rows[0])
|
||||
},
|
||||
|
||||
@@ -201,7 +201,7 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
if (!owner.rows[0]) return { ok: false, error: 'Utterance not found' }
|
||||
const nodeId = owner.rows[0].node_id
|
||||
// Same-node integrity for the three links.
|
||||
for (const link of ['parentUtteranceId', 'advancesToUtteranceId'] as const) {
|
||||
for (const link of ['parentUtteranceId'] as const) {
|
||||
const value = input[link]
|
||||
if (value) {
|
||||
const target = await pool.query<{ node_id: string }>('SELECT node_id FROM osint.utterances WHERE id=$1', [value])
|
||||
@@ -214,7 +214,7 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
}
|
||||
const columns: Record<string, string> = {
|
||||
text: 'text', utterer: 'utterer', npcId: 'npc_id', poseKey: 'pose_key', xpos: 'xpos', ypos: 'ypos',
|
||||
parentUtteranceId: 'parent_utterance_id', advancesToUtteranceId: 'advances_to_utterance_id', terminalId: 'terminal_id', effect: 'effect',
|
||||
parentUtteranceId: 'parent_utterance_id', terminalId: 'terminal_id',
|
||||
}
|
||||
const sets: string[] = []
|
||||
const values: unknown[] = [id]
|
||||
@@ -302,13 +302,13 @@ export function createStoryGraphRepository(pool: Pool): StoryGraphRepository {
|
||||
|
||||
type UtteranceRow = {
|
||||
id: string; node_id: string; utterer: Utterer; npc_id: string | null; pose_key: string | null; text: string
|
||||
parent_utterance_id: string | null; advances_to_utterance_id: string | null; terminal_id: string | null
|
||||
effect: string | null; xpos: number; ypos: number; sort_order: number
|
||||
parent_utterance_id: string | null; terminal_id: string | null
|
||||
xpos: number; ypos: number; sort_order: number
|
||||
}
|
||||
function mapUtterance(row: UtteranceRow): UtteranceDto {
|
||||
return {
|
||||
id: row.id, nodeId: row.node_id, utterer: row.utterer, npcId: row.npc_id, poseKey: row.pose_key, text: row.text,
|
||||
parentUtteranceId: row.parent_utterance_id, advancesToUtteranceId: row.advances_to_utterance_id, terminalId: row.terminal_id,
|
||||
effect: row.effect, xpos: row.xpos, ypos: row.ypos, sortOrder: row.sort_order,
|
||||
parentUtteranceId: row.parent_utterance_id, terminalId: row.terminal_id,
|
||||
xpos: row.xpos, ypos: row.ypos, sortOrder: row.sort_order,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user