Merge origin/main: reconcile briefing checklist and brief visuals
Both branches added a case-briefing checklist independently. Reconcile onto
the upstream {id,text} BriefChecklistItem model and keep the upstream brief /
luggage-tag visual refinements (boxed CHECKLIST section, assignment/heading
split), while retaining this branch's full-stack backing (checklist table,
board read/write/clone, importer/exporter) and the player-tickable interaction
(persisted locally per level). Keep the brief-goals list alongside the new
checklist. Align the server wire shape, importer, and exporter to {id,text}.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -419,7 +419,7 @@ export function createLevelRepository(pool: Pool, editingEnabled: boolean, objec
|
||||
'SELECT document_exhibit_id,flag_key FROM osint.document_flag_requirements WHERE board_id=$1 ORDER BY document_exhibit_id,flag_key', [level.board_id]),
|
||||
pool.query<{ flag_key: string }>('SELECT flag_key FROM osint.level_flags WHERE level_id=$1 ORDER BY flag_key', [level.id]),
|
||||
pool.query<{ document_exhibit_id: string }>('SELECT document_exhibit_id FROM osint.level_seen_documents WHERE level_id=$1', [level.id]),
|
||||
pool.query<{ text: string }>('SELECT text FROM osint.level_brief_checklist_items WHERE board_id=$1 ORDER BY sort_order,id', [level.board_id]),
|
||||
pool.query<{ id: string; text: string }>('SELECT id,text FROM osint.level_brief_checklist_items WHERE board_id=$1 ORDER BY sort_order,id', [level.board_id]),
|
||||
])
|
||||
|
||||
const blocks = new Map<string, string[]>()
|
||||
@@ -481,7 +481,7 @@ export function createLevelRepository(pool: Pool, editingEnabled: boolean, objec
|
||||
tagPosition: row.tag_position_percent, tagOffset: row.tag_lateral_offset })),
|
||||
viewport: { x: level.viewport_x, y: level.viewport_y, zoom: level.viewport_zoom }, updatedAt: level.updated_at.toISOString(),
|
||||
views, revision: Number(level.revision),
|
||||
brief: { body: briefResult.rows[0]?.body || '', concepts, checklist: checklistResult.rows.map(row => row.text) }, goals, report, levelStatus: level.status, editingAllowed: editingEnabled && authorMode,
|
||||
brief: { body: briefResult.rows[0]?.body || '', concepts, checklist: checklistResult.rows.map(row => ({ id: row.id, text: row.text })) }, goals, report, levelStatus: level.status, editingAllowed: editingEnabled && authorMode,
|
||||
sourceTemplateVersionId: level.source_template_version_id || undefined }
|
||||
return filterLevelVisibility(fullState, flagsResult.rows.map(row => row.flag_key), seenResult.rows.map(row => row.document_exhibit_id), authorMode)
|
||||
}
|
||||
@@ -628,8 +628,7 @@ export function createLevelRepository(pool: Pool, editingEnabled: boolean, objec
|
||||
const tightness = Math.max(0, Math.min(100, Math.round(Number(connection.tightness ?? 65))))
|
||||
const tagStyle = connection.tagStyle === 'compact' ? 'compact' : 'luggage'
|
||||
const tagPosition = Math.max(0, Math.min(100, Math.round(Number(connection.tagPosition ?? 50))))
|
||||
const lateralLimit = Math.round(10 + (100 - tightness) * .6)
|
||||
const tagOffset = Math.max(-lateralLimit, Math.min(lateralLimit, Math.round(Number(connection.tagOffset ?? 0))))
|
||||
const tagOffset = 0
|
||||
await client.query(`INSERT INTO osint.exhibit_connections (id,board_id,connection_type_id,from_exhibit_id,to_exhibit_id,label,tightness,tag_style,tag_position_percent,tag_lateral_offset)
|
||||
VALUES ($1,$2,'thread',$3,$4,$5,$6,$7,$8,$9)`, [connection.id, level.board_id, connection.fromExhibitId, connection.toExhibitId, connection.label?.trim() || null, tightness, tagStyle, tagPosition, tagOffset])
|
||||
}
|
||||
@@ -659,8 +658,8 @@ export function createLevelRepository(pool: Pool, editingEnabled: boolean, objec
|
||||
}
|
||||
const brief = state.brief || { body: '', concepts: [] }
|
||||
await client.query('INSERT INTO osint.level_briefs (board_id,body) VALUES ($1,$2)', [level.board_id, brief.body || ''])
|
||||
for (const [sortOrder, item] of (brief.checklist || []).map(text => text.trim()).filter(Boolean).entries())
|
||||
await client.query('INSERT INTO osint.level_brief_checklist_items (id,board_id,sort_order,text) VALUES ($1,$2,$3,$4)', [randomUUID(), level.board_id, sortOrder, item])
|
||||
for (const [sortOrder, text] of (brief.checklist || []).map(item => item.text.trim()).filter(Boolean).entries())
|
||||
await client.query('INSERT INTO osint.level_brief_checklist_items (id,board_id,sort_order,text) VALUES ($1,$2,$3,$4)', [randomUUID(), level.board_id, sortOrder, text])
|
||||
for (const [sortOrder, concept] of brief.concepts.entries()) {
|
||||
requireUuid(concept.id, 'Brief concept id')
|
||||
if (concept.resolvedPartyExhibitId && !evidenceIds.has(concept.resolvedPartyExhibitId)) throw new Error('Concept resolution references an unknown party')
|
||||
|
||||
Reference in New Issue
Block a user