Field notebook: capture NPC lines, tear pages onto the board
A dialogue line gains a "✎ Note this" capture that saves it to a per-playthrough notebook (migration 035 + notebook endpoints). The inventory notebook lists captured pages in a handwriting font (Google "Reenie Beanie") and "✂ Tear to board" drops a page onto the current board as a note exhibit (reusing addNote), then removes the page. Board notes render handwritten too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,9 @@ export interface NarrativeRepository {
|
||||
listAchievements(playthroughId: string): Promise<string[] | null>
|
||||
awardAchievement(playthroughId: string, flagKey: string, nodeId?: string | null): Promise<{ ok: boolean; earned: boolean; error?: string }>
|
||||
reachUtterance(playthroughId: string, utteranceId: string): Promise<{ ok: boolean; earned?: boolean }>
|
||||
notebookPages(playthroughId: string): Promise<{ id: string; text: string; createdAt: string }[]>
|
||||
addNotebookPage(playthroughId: string, text: string, sourceUtteranceId?: string | null): Promise<{ id: string; text: string } | null>
|
||||
removeNotebookPage(playthroughId: string, pageId: string): Promise<boolean>
|
||||
phoneDirectory(playthroughId: string): Promise<{ available: boolean; numbers: { number: string; name: string }[] }>
|
||||
dial(playthroughId: string, number: string): Promise<{ outcome: 'connect' | 'voicemail' | 'unknown'; name?: string; state?: PlaythroughState }>
|
||||
gotoNode(userId: string, playthroughId: string, nodeId: string): Promise<{ ok: boolean; state?: PlaythroughState; error?: string }>
|
||||
@@ -282,6 +285,26 @@ export function createNarrativeRepository(pool: Pool, objectStorage: ObjectStora
|
||||
return { ok: true, earned: (result.rowCount || 0) > 0 }
|
||||
},
|
||||
|
||||
// Field notebook: lines the player captured from NPCs during this playthrough.
|
||||
async notebookPages(playthroughId) {
|
||||
const rows = (await pool.query<{ id: string; text: string; created_at: Date }>(
|
||||
'SELECT id,text,created_at FROM osint.notebook_pages WHERE playthrough_id=$1 ORDER BY created_at', [playthroughId])).rows
|
||||
return rows.map(row => ({ id: row.id, text: row.text, createdAt: row.created_at.toISOString() }))
|
||||
},
|
||||
async addNotebookPage(playthroughId, text, sourceUtteranceId) {
|
||||
const clean = text.trim()
|
||||
if (!clean) return null
|
||||
if (!(await pool.query('SELECT 1 FROM osint.playthroughs WHERE id=$1', [playthroughId])).rowCount) return null
|
||||
const row = (await pool.query<{ id: string }>(
|
||||
'INSERT INTO osint.notebook_pages (playthrough_id,text,source_utterance_id) VALUES ($1,$2,$3) RETURNING id',
|
||||
[playthroughId, clean, sourceUtteranceId || null])).rows[0]
|
||||
return { id: row.id, text: clean }
|
||||
},
|
||||
async removeNotebookPage(playthroughId, pageId) {
|
||||
const result = await pool.query('DELETE FROM osint.notebook_pages WHERE id=$1 AND playthrough_id=$2', [pageId, playthroughId])
|
||||
return (result.rowCount || 0) > 0
|
||||
},
|
||||
|
||||
// The phone directory available on the player's current node: the terminals of a
|
||||
// phone node the current node is wired to. No connected phone node => nobody's listed.
|
||||
async phoneDirectory(playthroughId) {
|
||||
|
||||
Reference in New Issue
Block a user