chore: release v0.4.0 - #19
Merged
Merged
Conversation
Pinch and double-tap zoom broke the fixed layout on phones, so the viewport meta now pins the scale. iOS ignores user-scalable=no, so body also opts into touch-action: manipulation. bump version to 0.2.4 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…imits - add usePacedReveal hook and paragraphs helper to split assistant replies by sentence, holding back the trailing unfinished one while streaming - raise question limits for 3-5 person cases (30 -> 45) with a new product cap - sort characters deterministically for public introductions - fetch and cache sound effects during deployment - add direction mockups for desktop review Co-Authored-By: Claude <noreply@anthropic.com>
Adds mocks/effects.html cataloging the ten UI motions with replay controls, wires it into mocks/storyboard.html as a new section, and includes a reference screenshot. Co-Authored-By: Claude <noreply@anthropic.com>
…line events Add timeline_events to scenario_truths and supports to evidences so the compiled scenario carries a structured timeline the alibi chart can read without touching the existing narrative timeline. Server computes per- character alibi segments from discovered revelations/evidence and returns them from the session endpoint; segments end at the next known event for that person rather than leaking undiscovered timing. Add a NewFactBand component and wire interrogation/result screens to surface newly revealed facts. Reorganize the effects mock catalog into mocks/effects/* per-effect pages and refresh desktop/mobile mocks to match. Co-Authored-By: Claude <noreply@anthropic.com>
…tions drive the alibi table Explain that `kind` on facts decides solid vs. dashed lines, that `participants`/`location` on timeline events are required for a row to appear, and that `supports`/`relatedFacts` are what make discovered clues draw new segments. Co-Authored-By: Claude <noreply@anthropic.com>
…chema Introduce victim cause-of-death, findings, and found location fields, and derive an investigable flag so the client can decide whether the body can be examined without consulting the truth table. Co-Authored-By: Claude <noreply@anthropic.com>
Add a victim examination flow: compiled scenarios remap victim
findings' evidence/revelation requirements to their scenario uuids so
discovery gates actually resolve, a new server-side sheet builder
exposes only unlocked findings without leaking that hidden ones exist,
and the interrogation screen adds the victim as a special subject
("所見") alongside characters when the case allows examining the body.
Co-Authored-By: Claude <noreply@anthropic.com>
…ation Pass the character (or victim) picked on the overview screen through the route search param so interrogation resumes with that target instead of always starting from the top of the character list. Co-Authored-By: Claude <noreply@anthropic.com>
… examinations
Examining the victim previously reused the interview question-generator, which
produced lines addressed to the deceased ("涼子さん、〜はありますか?"). Add
composeExaminationFocus/EXAMINATION_INTENT_RULES so examinations get a
self-directed "what to check next" line instead of a question, and route
sessions.ts to it when the target is under examination.
Co-Authored-By: Claude <noreply@anthropic.com>
Add a `pickable` flag to AlibiChart so the victim's column header only becomes clickable when the scenario allows examining them, and wire the interrogation screen's victim role label and target-switching through it. Co-Authored-By: Claude <noreply@anthropic.com>
Carries the target picked from the character list back into the interrogation screen when resuming, and consumes the `first` search param once so navigating away and back doesn't snap the conversation back to it. Co-Authored-By: Claude <noreply@anthropic.com>
Wrap AlibiChart in a scrollable container across all four screens that render it, so long time windows scroll within the frame at their real 1min/10px scale instead of shrinking the tick spacing or overflowing the viewport. Co-Authored-By: Claude <noreply@anthropic.com>
… for all scenarios Add victim.foundAt/foundIn/causeOfDeath/findings and victim-sourced motive clues to every scenario, backed by migration 0011 and a new test enforcing the current-authoring guide. Co-Authored-By: Claude <noreply@anthropic.com>
Let name lists and interrogation screens treat an investigable victim as a selectable target labeled "調べる" instead of a silent, disabled "被害者" row. Adds a mobile target-swap control since the phone layout has no column headers, and switches the mobile chat background to opaque so overlapping text stays legible on narrow screens. Co-Authored-By: Claude <noreply@anthropic.com>
Add an optional `who` param to `Mock.play` so callers can pin the current speaker; without it, repeated calls (e.g. device switching) could silently reassign the previously chosen target. Co-Authored-By: Claude <noreply@anthropic.com>
Timeline location may reference floorPlan.rooms[].id instead of a place name when the case has a floor plan. Resolve it to the room's label so the compiled table doesn't show a raw room ID; fall back to the raw value when it isn't a known room id. Co-Authored-By: Claude <noreply@anthropic.com>
…tion Co-Authored-By: Claude <noreply@anthropic.com>
Deriving a UUIDv5 from the filename and threading it through compile so re-seeding deletes the old row by id. Deleting by title left duplicate rows behind when a scenario's title changed between runs. Co-Authored-By: Claude <noreply@anthropic.com>
…ions guidance Bumps judgeRubricKey to v2 so cached rubrics don't serve stale instructions for up to an hour after deploy, and tightens suggestedQuestions prompt guidance to phrase suggestions as player questions rather than instructions or disclosure-condition paraphrases. Co-Authored-By: Claude <noreply@anthropic.com>
The tsukimisou scenario combined Kiryu passing Mizuki in the corridor with Mizuki entering the study into one event, which made the alibi table place Kiryu in the study too. Split into two events by actual location and update the authoring guide to require this split, plus adjust the timeline length test. Co-Authored-By: Claude <noreply@anthropic.com>
Note that all 43 scenarios now populate location, and describe the compile-time room-id-to-name resolution for scenarios with floor plans. Co-Authored-By: Claude <noreply@anthropic.com>
- drop legacy fields (tags, secret, role, about, reveal.mode, requiredFacts, quality) from the authoring format - add migrations 0012-0018 for evidence detail, death time, clash materials, and required records - require estimatedDeathAt for time-deception cases and record backing for physical-evidence timeline events - add authoringWarnings to flag evidence contradictions with no timeline anchor - retitle scenarios, dropping duplicate and legacy titles - update interrogation UI, alibi chart, and mobile safe-area mocks Co-Authored-By: Claude <noreply@anthropic.com>
…of hardcoded px Store clash participants/time in C.clash and resolve column indices at render time so the line and end pins track cast/column changes instead of drifting when layout shifts. Add pin-lift-x for the alibi table's vertical time axis, mirroring pin-lift's horizontal counterpart, and sync the direction doc and interrogation mock accordingly. Co-Authored-By: Claude <noreply@anthropic.com>
Track who told each lie and which character's evidence broke it, so clashOf can return the pair the dashed line connects instead of just a timestamp. AlibiChart now derives the line's column endpoints and width from actual column layout instead of hardcoded pixels. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…scenario Co-Authored-By: Claude <noreply@anthropic.com>
The clashOf logic in src/server/game/alibi.ts draws a clash line between two people's columns, so it also needs the evidence that breaks a lie to come from a character other than the liar. authoringWarnings only checked timeline/facts linkage, so scenarios with clash-eligible lies but same-source evidence silently produced no clash line. Update the warning condition and docs accordingly. Co-Authored-By: Claude <noreply@anthropic.com>
The catalogue drew motion three on a horizontal rail with pin-lift, but the alibi chart runs vertically, so its pins are horizontal bars that stretch along their own length via pin-lift-x. The card no longer showed what the app actually does, which defeats the catalogue's purpose as a comparison surface. Redraw it as two columns with the line drawn between them and both end pins lifting once the line settles, matching AlibiChart. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Screenshots belong in screenshots/, which .gitignore excludes, so this one should never have been tracked at the root. The file stays on disk for local reference; only the tracked copy goes away. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The two conditions for the clash mark are easy to satisfy on paper and still produce nothing on screen, so authors need to see what they are aiming for. Track the screenshot under docs/images/ and reference it from the lies section, where both conditions are explained. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The catalogue pages still drew motion three on a horizontal rail with pin-lift, while the screen mock, AlibiChart and the Storybook catalogue all show the vertical table where a pin is a horizontal bar stretched by pin-lift-x. Anyone looking up the motion got a different picture depending on where they looked, and the rail form hid why the vocabulary needs both orientations. Redraw both catalogue pages as two columns with the line drawn between them and the end pins lifting once it settles. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…nterrogation Introduces a separate "places" group in case-overview so scenes/bodies can be examined distinctly from cast members, unifies the row-rendering logic for cast/victim/place entries, and updates interrogation.html to reflect examine-mode copy and headline text when a place or the victim is selected. Co-Authored-By: Claude <noreply@anthropic.com>
…har count Switch the interrogation log auto-scroll from tracking accumulated text length to a ResizeObserver on the log content element, since text length changes before paced-reveal actually renders new lines, leaving the view short of the bottom. Also fix the hint element's [hidden] override in the phone mock CSS to scope the display:none to .hint instead of all elements. Co-Authored-By: Claude <noreply@anthropic.com>
Redrawing motion three in the vertical form left pin-lift with no user: the alibi chart is vertical, so only pin-lift-x is ever drawn. The pair is worth keeping because it shows that a pin's orientation follows its time axis, but an unused rule sitting silently is the very thing that made the motion vocabulary drift out of step with the app in the first place. Note the reason at each of the four copies. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Went through all eight screens against mocks/ and closed the gaps the comparison turned up. Two were structural rather than cosmetic: the case overview had no "where to investigate" section at all, and the result screen was missing the whole accused-time comparison on mobile. The rest is line work — rules, spacing, leading, and the pigment assignments. Shared parts moved too: the block button takes the mincho face and wider tracking the mocks use for it, the accusation outline goes to full shu, the select arrow becomes a filled triangle, the textarea border moves to keisen, and avatar discs go neutral so only the letter carries the character's colour. Interrogation also gained the examine state the mocks already described: places sit alongside people, and the wording turns from asking to examining. Co-Authored-By: Claude <noreply@anthropic.com>
develop already carries the pinch-zoom fix as #11, squashed. This branch still has the original commit for it, so the same change arrived twice and index.css conflicted. Kept this branch's body block, which is a superset — it has the touch-action line develop brought plus the font smoothing and safe-area padding added since. Co-Authored-By: Claude <noreply@anthropic.com>
…scenario schema, and the mock pass (#13) A month of unpushed work: PWA viewport lock, sentence-paced chat log, alibi segments and clash lines, the examination step, the scenario definition schema rework, the mock catalogue, and finally the pass that matched all eight screens to their mocks.
…erver Extract the 500-char topic limit into a single MAX_TOPIC_CHARS constant in src/shared/turns.ts, reduce it to 140, and use it from both the input field and the server-side schema so the two can't drift apart. Co-Authored-By: Claude <noreply@anthropic.com>
…ve's question Ports the two pieces of #4 that develop never picked up. The rest of that branch — the interviewer, the topic flow, the DO's topic persistence — had already landed here by another route, which is why the branch would not merge. Yielded topics: the session DO now keeps a list of which rounds produced evidence or a revelation, the history reports it, and the chat log lifts those topic lines to the main text colour. No new pigment — the palette belongs to the characters, and a topic is nobody's. Reading back a case, the lines that mattered are the ones that stand out. Streamed questions: the interviewer now streams, so the detective's question appears as it is written rather than arriving whole after a silence. The question-start event is only sent once the first chunk lands, so a round where the model returns nothing leaves no empty line behind. Examination keeps its one-shot phrasing — that line is short enough that streaming it shows nothing. Not ported: RevealAnnounce. It announces new clues in the centre of the screen, which NewFactBand already does here, and the band is what the mocks specify. Co-Authored-By: Claude <noreply@anthropic.com>
…ve's question (#14) Ports the two pieces of #4 that develop never picked up: the shared topic length constant, the yielded-topic mark through DO/history/restore/log, and the streamed interviewer question. RevealAnnounce is deliberately left out — NewFactBand already covers it and matches the mocks.
The board asserted a death estimate the player could not yet know. In the Tsukimisou case the record says the body was found around 20:30, but the chart drew 20:15 "death estimate" from the first turn, before anyone had examined anything. The data already separated foundAt from estimatedDeathAt; only the drawing collapsed them. So the deadline becomes a window rather than a line. The discovery time is public and always drawn solid. The estimate is drawn by how it was come by: 1. fixed solid, the time as it is 2. range a span mark with end caps, both ends solid 3. unknown dotted across the whole known window, with ? instead of a time 4. claimed dotted, ? beside the time, and whose word it rests on, in their ink Splitting 1 from 4 is the point. The same 18:50 means something different when the detective examined the body than when a bystander says so, and if that bystander is the culprit, the two will not agree once the examination lands. Nothing is filled — the window is a line with returns at each end. A shaded band would compete with the suspects' own bars. Desktop only for now; the phone chart still draws the old single line. Reasoning is written up in docs/design/deadline-window.md. Co-Authored-By: Claude <noreply@anthropic.com>
Switching #death= one at a time shows each state but never lets you compare them, and comparing is the whole point: the difference between a time the detective established and the same time a bystander asserted is only legible next to each other. The marks come from Mock.deathMarks, the same call the alibi table makes, so the sheet cannot drift from what the screens actually draw. The stage keeps the table's scale for the same reason — a comparison at a different scale is a comparison of something else. Co-Authored-By: Claude <noreply@anthropic.com>
The phone lists places in its swap nav; the desk had no way in at all, because switching there is done by the alibi table's column headings and places hold no column. The mock said as much, with a "still" in it — a note left for later rather than a decision. The consequence was that the same case played differently by device: on a phone you step from the counter to the back room where you stand, on a desk you go back to the prep screen first. So the desk gets its own way in, beside the nameplate rather than in the table. Grey, no pigment: places do not answer, and the colours belong to those who do. The one you are already looking at is left out — an entry that does nothing when pressed is worse than no entry. Co-Authored-By: Claude <noreply@anthropic.com>
Asking and examining are two different moves, each costing a turn. Listing them together let you slip from a conversation into a search from the corner of the screen, which blurs where one act ends and the next begins. So the switcher now offers only what matches what you are already doing: people while questioning, the body and the places while examining. Crossing between the two goes back through the prep roster, which is where you chose in the first place. On the desk this means the place list appears only while examining; people were never in that corner anyway, since the table's column headings have always been the way to them, and they stay reachable throughout. Co-Authored-By: Claude <noreply@anthropic.com>
- Introduce places as a first-class target for `ask`, alongside characters and the victim: schema, migration, compile-scenario split into public/truth findings, and location-typed evidence sources now resolve against places too. - Add a deadline window derived from victim discovery/estimated-death times, revealed only after the body has been examined, and render it across AlibiChart, CaseOverviewScreen, InterrogationScreen, and ResultScreen. - Update scenario YAMLs, fixtures, stories, and docs to cover the new places and deadline data. Co-Authored-By: Claude <noreply@anthropic.com>
The schema, the server and the screens could all handle places, but nothing joined them: only Storybook ever passed any, so in the running app the section never appeared no matter how far the case progressed. The two routes now pass what the API already returns. The nameplate takes `situation` rather than `introduction`. The latter is the roster blurb — "the ground floor, the till and the ledger" — which describes the place rather than what is in front of you. What belongs under the name while you are standing there is the other line: the clearing-up stopped halfway. The authoring guide gains a places section: the shape, why findings are mandatory (a place on the roster is always worth examining), the ID rules and why they exist, matching room IDs when there is a floor plan, and the line between `situation` and `findings` — the first is public and keyword-checked, the second is earned. Also a reminder that a place with nothing sourced from it is a turn spent on nothing. Co-Authored-By: Claude <noreply@anthropic.com>
…tcher In easy mode every character carries a remaining count in the alibi table's column heading, and every room on the floor plan carries one too. Places fall between: they hold no column, and a place that is not also a drawn room appears on no plan either. The count the server computes for them reached nothing. So it goes beside the name in the switcher, which is the only surface a place has. People and the body deliberately do not get one there — their number already stands in the column heading, and the same figure in two places reads as one of them being stale. Co-Authored-By: Claude <noreply@anthropic.com>
The desk drew the window; the phone was still drawing one line. Same vocabulary now, turned ninety degrees onto the rail. Two things had to change rather than shrink. Labels cannot sit beside their mark at 390px — discovery stands at 92% of the axis, so its label would fall off the edge; they go on a row beneath instead, a point-mark's label right-aligned to it and a window's centred on its span. And the accusation's shu tick, which defaults to 18:50, was covering the deadline mark outright; it now stops at the row above, so solid and dotted are told apart below it. The enlarged rail also loses the 18:50 tick it used to print in the middle. That was the board naming the estimate before anyone had earned it — the same fault this whole change exists to remove. Co-Authored-By: Claude <noreply@anthropic.com>
The estimate used to appear once the body had been examined. That was a placeholder standing in for the real rule, and it closed the other door: a case with a doctor in it should let you reach the same time by asking them. Evidence now carries `revealsDeathTime`. Hold one such piece and the board's dotted window resolves to a solid line; hold none and it stays unknown to the end. The mark sits on evidence alone rather than also on findings, because the server can tell whether a piece of evidence has been discovered — a finding only records whether it may be shown, so a mark there could never be read back. The server decides. Sending the client a table of which evidence opens the estimate would hand the board a key to its own answer, so it computes the state and ships the resolved time or null. While here: `read/scenarios.ts` was putting `estimatedDeathAt` into the case detail. The board drew "unknown" while the browser already held 20:15 — the whole point of hiding it, undone one layer down. Removed. Tsukimisou gets two ways in, the examination and the doctor's account, so that choosing one route does not lock the deadline away for the rest of the case. Co-Authored-By: Claude <noreply@anthropic.com>
…re that opens them (#15) The board no longer asserts a death estimate the player has not earned: the discovery time is public and solid, the estimate stays a dotted window until a piece of evidence marked revealsDeathTime is found. Places become a third kind of subject alongside people and the body, reaching the screens for the first time. The switcher offers only the kind you are already working with.
Lets an evidence entry mark that it discloses the time of death, feeding the interrogation deadline disclosure logic. Co-Authored-By: Claude <noreply@anthropic.com>
…mate Add hasEstimatedDeathAt so the board can tell "not yet found" apart from "this case has no death estimate at all", instead of showing the searching-dashes-and-? mark for cases that never had one. Co-Authored-By: Claude <noreply@anthropic.com>
Two cases the guide did not cover, both easy to get wrong and neither caught by the validator. Leaving `estimatedDeathAt` out is now a decision, not an omission: the board draws no estimate mark at all. Dotted-and-question-mark means "there is something here to find", so showing it where nothing was ever written asks the player to search for what does not exist. And writing the time without marking any evidence with `revealsDeathTime` is the worst of the three: the answer exists, no route reaches it, and the board keeps inviting a search that cannot end. Both halves validate fine on their own, so nothing will tell the author but a playthrough. Co-Authored-By: Claude <noreply@anthropic.com>
…ship the missing migration (#16) The 0020 migration was left untracked when #15 was staged, so develop described a column nothing created. And deadlineOf folded "no estimate in the scenario" into "not discovered yet", drawing a dotted window with a question mark for 33 cases where no time was ever written — an invitation to search for nothing.
…e death estimate (#17) Scenario data for the machinery #15 and #16 shipped. Places go from 2 cases to 17, and every case that names a death estimate now has evidence marked to disclose it — the nine that had a time written and no route to it are gone. Two migrations carry it to databases already seeded. The second rewrites only the public half of each place entry, leaving the private findings alone: what the roster and the nameplate show is public, what examining turns up is earned.
master sits at 0.3.0 because the dependency update went straight there without passing through develop, so develop's 0.2.4 is behind the released version while carrying a month more work. 0.4.0 clears both.
The dependency update went to master directly, so develop never received it and the release PR would not merge. This brings it back: the held-back AI SDK pins, the Biome 2.5 config, and the devcontainer Node bump. The auto-merge resolved package.json to master's 0.3.0, which would have released the version that is already out. Set back to 0.4.0. Co-Authored-By: Claude <noreply@anthropic.com>
5 tasks
The dependency update went to master directly and never came back, so the release PR would not merge. Git's auto-resolution also took master's 0.3.0 for package.json, which would have released the version already out; restored to 0.4.0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
developtomaster. Merging deploys to production and applies migrations0019–0022to the production D1.What ships
The board stops knowing things the player has not earned. The alibi table used to draw
a death estimate from the first turn, before anyone had examined the body or asked the
doctor. Now the discovery time — public, since the case record states it — is drawn solid
from the start, and the estimate stays a dotted window with a question mark until evidence
marked
revealsDeathTimeis found. Cases that never name an estimate draw no mark at all,rather than inviting a search for something nobody wrote.
Three separate leaks of the same time were closed along the way: the chart drawing it, the
scenario detail API shipping it to the browser regardless, and the phone's enlarged rail
printing it as an axis label.
Places can be investigated. A third kind of subject beside people and the body — the
counter, the back room — reachable from both the roster and the interrogation screen, with
their own findings feeding the same evidence path. 17 of 43 cases carry them.
Topics, not questions. The player names a topic and the interviewer model composes the
actual questions, streaming them as they are written. Topics that yield evidence are marked
in the log so a case can be read back.
The switcher offers only what matches what you are doing — people while questioning,
the body and places while examining. Asking and examining each cost a turn; sliding between
them from the corner of the screen blurred where one act ended.
Also: a month of previously unpushed work (PWA viewport lock, sentence-paced chat log,
alibi clash lines, the scenario definition rework), the dependency update held back from
the AI SDK majors, and every screen matched against its mock.
Migrations
0019places columns ·0020the disclosure flag ·0021place and disclosure data forseeded rows ·
0022spoiler-neutral public copy for place entries. All additive orin-place updates; none drop or delete.
After merge
The local seed will be applied to the production D1 by hand. This deletes and re-inserts
all 43 scenarios, which cascades to
play_sessions— 7 sessions and 49 messages currentlyin production will be lost. Confirmed as acceptable.
Test plan
develop(build, code check, typecheck, test, commitlint)bun run test— 721 pass / 0 failseed.sqlleaves it unchanged0019–0022applied to production D1🤖 Generated with Claude Code