Introduce the narrative layer as a directed story-flow graph: an authored campaign a player walks node by node, replacing the interim slot/chapter model. Schema (migrations 015-021): - mysteries, global NPC templates + named poses, per-user playthroughs - story_nodes, terminals, utterances (the flow graph and dialogue trees) - clean cutover: retire slot cutscenes/chapters/seen_dialogue Runtime: - New Game creates a playthrough bound to the JWT identity (dev test-user fallback) - advance() walks the graph cutscene -> dialogue -> level -> ..., auto-skipping gates - branching dialogue: player choices route out through node terminals Admin authoring: - NPC editor: upload named poses to the gupi MinIO bucket - mystery graph editor: vertical node canvas, wiring, entrypoint, delete-by-click - dialogue crafter: utterance tree, Tab to add child, 1/2 speaker, undo Content authored via the manifest importer / admin panel and seeded for Glass Harbour. MinIO added to the dev stack; dev container runs in development mode. Also includes a folder-widget simplification (removes open/close) and a resolveUserId auth helper. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
12 KiB
GUPI OSINT Board roadmap
This is the ordered implementation roadmap following the accepted exhibit model. Work generally proceeds from top to bottom. It is not a substitute for migrations or implementation issues.
The narrative layer — campaigns, NPC cutscenes, the admin authoring panel, and the LLM cognitive shim that keeps players oriented through complex real-world scam cases — is tracked separately in narrative-todo.md. It depends on Milestone 5 (Case Report / Claims) for the assistant's read-only view of the player's reasoning.
Working agreement
- Spend roughly 60% of development time on features and model work, 25% on tests and bug fixing, and 15% on repository and deployment hygiene.
- Stop feature work for interaction-breaking, data-loss, migration, or deployment bugs. Batch cosmetic defects separately.
- A new exhibit behavior is complete only when its PostgreSQL representation, API behavior, frontend widget, persistence, and focused tests agree.
- Mystery content must be authored as template data through supported application operations, never hard-coded into React components.
Milestone 0: trustworthy baseline
- Track
.gitignore,.dockerignore, and.env.example; confirm that secrets and generated artifacts cannot enter Git accidentally. - Commit the current working POC and tag the checkpoint
poc-pre-exhibit-model. - Confirm
mainis pushed to the Ramanujan-hostedoriginrepository. - Keep
npm run buildgreen and makenpm testrun real tests rather than an empty suite.
Milestone 1: focused safety net
- Test screen/board coordinate conversion across pan and zoom levels.
- Test timeline date-to-pixel projection and recomputation after viewport resizing.
- Test the interaction boundary between exhibit dragging, hand-tool panning, desktop wheel zoom, and mobile-only pinch zoom.
- Test folder open/close behavior, retained file positions, and containment-band state.
- Test document upload, metadata persistence, board save/reload, and reset.
- Run migrations and API integration tests against disposable PostgreSQL, not SQLite or mocked persistence.
- Add one browser smoke test: open an isolated level, drag an exhibit, pan, board-zoom, open a folder, move a file, reload, and verify persistence.
Milestone 2: exhibit-schema cutover
- Add boards, exhibits, exhibit types, subtype tables, immutable template versions, and mutable levels.
- Cut over the explicitly disposable POC database directly; no transitional data existed to backfill or compare.
- Implement template instantiation, version selection, reset, and “save level as template” as transactional clone operations.
- Introduce a server-side repository/service boundary so SQL and cloning transactions do not live in Express route handlers.
- Move exhibit and document rendering to a typed frontend widget registry backed by the normalized API.
- Remove transitional
widgets,widget_relations, andplaythrough_*tables in the canonical cutover migration.
Milestone 3: events and parties
Events and narrative
- Implement Event exhibits with occurrence time and investigator-authored narrative text.
- Implement normalized Event-to-Evidence links and their distinct board visualization.
- Present chronologically ordered events as the emerging investigation story.
Party exhibits
- Add a Party exhibit supertype representing an investigation participant.
- Add a Person subtype with display name, normalized aliases, and extensible structured identity fields.
- Add an Organization subtype covering businesses, public bodies, associations, and informal groups.
- Add normalized many-to-many Party-to-Evidence associations with an optional explanatory note.
- Add typed Party-to-Party relationships such as employment, ownership, membership, control, and representation.
- Build distinct Person and Organization dossier presentations that reveal associated evidence without using folder ownership semantics.
- Include brief concepts, parties, aliases, evidence associations, and party relationships in template/level cloning.
Milestone 4: first playable mystery
- Design a small mystery that exercises documents, folders, notes, events, people, organizations, connections, and the timeline.
- Create it as an immutable level-template version using the same supported operations available to an author.
- Instantiate and solve a cloned level without modifying the template or relying on hard-coded case behavior.
- Turn the successful solve path into an end-to-end acceptance test.
- Perform a manual playability and visual-polish pass before production deployment.
Milestone 5: claim-driven case report
The purpose of this milestone is the gameplay loop, not a knowledge graph: the player connects two exhibits, explains that one specific thread with a luggage-tag Claim, and later discovers that their accumulated explanations have become a nearly complete case report.
5.1 Lock the gameplay and temporal rules
- Define a Claim as an entity owned by exactly one investigative thread; a thread has zero or one Claim. (The text associated with a claim can prove multiple points, handed by the text.
- Keep untagged threads as ordinary connections that do not appear in the report.
- Use the same Claim text on the luggage tag and in the report. Editing either presentation updates the same database value.
- Derive the Claim date from the earliest non-null temporal date of its two endpoint exhibits; never use
created_ator the current time. - Use Event
occurred_atand a Document's primary timeline date as direct endpoint dates. For a Folder, use the earliest dated contained Document. Leave a Claim undated when neither endpoint supplies a date. - Place undated Claims after dated Claims in the initial report order while keeping them fully editable and reorderable.
- Treat the derived date as the initial chronological suggestion only. Manual report ordering must not rewrite exhibit or Claim dates.
5.2 Add normalized persistence
- Add a
claimstable with a unique foreign key toexhibit_connections, text, tag style, position percentage, lateral offset, and timestamps. - Move luggage-tag-specific text and placement fields out of
exhibit_connections; retain curve tightness and endpoints on the connection. - Add one level-owned
case_reportand normalizedcase_report_claimsrows with explicitsort_order. - Assign stable, level-local display numbers to cite exhibits as
Exhibit 3independently of board position, z-index, or report order. - Enforce same-board ownership for the Claim's connection, both endpoint exhibits, report, and report membership.
- Delete a Claim and its report membership transactionally when its luggage tag is removed, while retaining the now-untagged thread.
- Delete both the Claim and connection when the thread itself is removed.
- Clone board-owned Claims with fresh IDs during template creation and instantiation; rebuild level report membership against the cloned Claim IDs.
- Make reset discard player-created Claims and restore exactly the Claims present in the source template version.
- Keep uploaded binary evidence in MinIO. Reports and Claims reference Document exhibits and asset metadata; they never duplicate or embed asset bytes.
5.3 Expose a focused API contract
- Add a typed Claim DTO to an investigative connection instead of exposing a free-form connection
label. - Return each Claim's derived date and its two stable exhibit citations in the report response.
- Add granular operations to create, edit, and remove a Claim without replacing the complete board state.
- Add a report endpoint that returns ordered Claim rows and an atomic reorder operation.
- Reject empty Claim text, invalid connection ownership, duplicate Claims on one connection, and report orders containing foreign or duplicate Claim IDs.
- Protect Claim and report writes with the board revision so concurrent saves cannot silently overwrite reasoning.
5.4 Turn luggage tags into Claim widgets
- Change the thread prompt from “Add tag” to “Explain this connection,” with a secondary option to leave the thread untagged.
- Create the Claim and its luggage-tag presentation in one interaction, preserving the current tightening animation.
- Keep both
LUGGAGEandCOMPACTas visual presentations of the same Claim entity while playtesting them. - Preserve draggable percentage and tension-constrained lateral offset as Claim presentation state.
- Rename tag-oriented frontend types and commands to Claim terminology without changing the established visual design.
- Give a newly created Claim a subtle “added to report” ink animation or badge without interrupting board work.
5.5 Build the typewriter case report
The end goal is that a case report is prepopulated by the claims the player made during the investigation so that a skeleon of the case solution is present. The player must simply edit the case report and submit it.
- Add Case Report as a primary menu item and implement it as a persistent board view, separate from exhibits and the timeline.
- Provide an empty state that explains that explaining red threads will create the report, without revealing a solution or forcing a tutorial.
- Initially arrange Claim rows chronologically by derived date, using the order parameters on the claim as tie-breaker and undated Claims last, below a horizontal rule that says (missing date)
- Render each row as a typewritten Claim with its date and endpoint citations, for example:
14.10.1987 — Maria Voss redirected the shipment. Exhibits 4 and 7. - It needs to be possible to add free text before and after the claims. Coloured inline text (use span elements) have a specific class and id can be edited
- -Make Claim text editable inline. Persist through the Claim API so the luggage tag updates immediately.
- Support pointer and keyboard reordering of Claim rows and persist the resulting explicit report order into order column of the claim.
- Clicking a Claim must minimize the report as appropriate, center its thread, and briefly illuminate the curve and luggage tag.
- Clicking an exhibit citation must locate and highlight that exhibit using the existing tray/board locator treatment.
- Ensure the report is legible and operable on portrait mobile layouts as well as desktop.
- Add restrained typewriter, paper, and ink feedback while respecting reduced-motion preferences.
5.6 Test the reasoning loop
- Unit-test endpoint date resolution for Event, Document, Folder, partially dated, fully undated, and invalid-date cases.
- Migration-test the one-Claim-per-thread constraint, cascading behavior, same-board enforcement, and report ordering constraints.
- Integration-test create/edit/remove Claim, two-way text synchronization, report reorder, reload persistence, reset, and template cloning.
- Verify that Claims citing uploaded Document exhibits survive cloning while their immutable assets remain shared through MinIO.
- Add a browser test that creates several connections out of chronological order, explains them, opens the report, edits and reorders the Claims, then returns to each highlighted thread.
- Play the Glass Harbor mystery using Claims as the primary reasoning mechanism and record whether the generated report makes the conclusion emerge naturally.
- Revise prompts, animation, initial ordering, and report typography based on that playtest before adding automated evaluation.
5.7 Deliberately deferred
- Design a hidden, template-versioned solution rubric only after the deterministic Claim/report loop is enjoyable and dependable.
- Add LLM report evaluation as a later milestone with structured citations, calibrated uncertainty, and reproducible evaluator output.
- Do not add general-purpose semantic edge roles, Claim hubs, Claim-to-Claim links, or a knowledge-graph ontology as part of this milestone.
Milestone 5 definition of done
A player can connect exhibits, explain each connection with a luggage-tag Claim, discover those exact words in a chronological typewriter report, improve and reorder the Claims, follow every citation back to the board, reload without loss, and reset safely to the template. No LLM is required for this experience to work.