diff --git a/AGENTS.md b/AGENTS.md index b1bb3fb..1f627c7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,8 @@ When you run into a repeatable problem during testing (e.g. port assignment coll - If Alembic autogeneration says the target database is not up to date, create a temporary database with `DATABASE_URL=sqlite:////tmp/.db .venv/bin/alembic upgrade head`, then run the autogeneration command with that same `DATABASE_URL`. +- Before browser testing, check whether the default Vite port belongs to this project. If it is occupied by another app, start this frontend with `npm --prefix frontend run dev -- --host 127.0.0.1 --port 5174 --strictPort` and use that URL. Local server binds may require sandbox escalation. + ## Versioning & changelog The app shows its version number and a player-facing changelog in the ☰ menu → About. Both come from `frontend/src/lib/changelog.js` (rendered by `frontend/src/components/AboutModal.svelte`). diff --git a/TODO.md b/TODO.md index 0294070..09ce290 100644 --- a/TODO.md +++ b/TODO.md @@ -13,8 +13,8 @@ - [x] Align the "Current Difficulty" and "Successes" boxes - [x] Don't have dev mode toggling appear in the event log - [x] Remove display of current deck size -- [ ] Voting status in the between-scenes phase should be visual, not text-based -- [ ] Players should be able to see who they voted for +- [x] Voting status in the between-scenes phase should be visual, not text-based +- [x] Players should be able to see who they voted for - [ ] The "ready" button between scenes is redundant. Allow players to vote, and allow them to change their vote, but once all players have voted, display the result and move on ## Rules diff --git a/frontend/src/components/CrewSidebar.svelte b/frontend/src/components/CrewSidebar.svelte index 9b961ea..5618dd6 100644 --- a/frontend/src/components/CrewSidebar.svelte +++ b/frontend/src/components/CrewSidebar.svelte @@ -21,6 +21,12 @@ try { return JSON.parse(value || '[]'); } catch { return []; } } + function voteStatus(p) { + if ((state.votes || []).some(v => v.voter_player_id === p.id)) return { icon: '✓', label: 'Voted', done: true }; + const canVote = state.players.some(candidate => candidate.id !== p.id && candidate.role !== 'deep' && !candidate.is_dead && !candidate.needs_reroll); + return canVote ? { icon: '○', label: 'Choosing a nominee', done: false } : { icon: '—', label: 'No eligible nominee', done: true }; + } + function statusFor(p) { const phase = state.game.phase; if (phase === 'lobby') { @@ -44,13 +50,7 @@ if (p.role === 'pirat') return `Pi-Rat · Rank ${p.rank}`; return 'Choosing a role…'; } - if (phase === 'between_scenes') { - const voted = (state.votes || []).some(v => v.voter_player_id === p.id); - const canVote = state.players.some(candidate => - candidate.id !== p.id && candidate.role !== 'deep' && !candidate.is_dead && !candidate.needs_reroll - ); - return `Rank ${p.rank} · ${voted ? 'Nomination done' : canVote ? 'Nominating…' : 'No vote'}`; - } + if (phase === 'between_scenes') return `Rank ${p.rank}`; if (phase === 'deep_upkeep') return `Rank ${p.rank} · ${p.is_ready ? 'Ready' : 'Finishing upkeep…'}`; if (phase === 'recruit_creation') return p.needs_reroll ? 'Creating a new Pi-Rat' : `Rank ${p.rank} · Helping recruits`; if (phase === 'ended') { @@ -83,6 +83,9 @@ title="View {p.name}'s character sheet" on:click={() => openTargetId = p.id} > + {#if state.game.phase === 'between_scenes'} + {voteStatus(p).icon} + {/if} {iconFor(p)} {crewLabel(p, captainId)} {statusFor(p)} @@ -95,3 +98,8 @@ {#if openTarget} openTargetId = null} /> {/if} + + diff --git a/frontend/src/components/UpkeepPhase.svelte b/frontend/src/components/UpkeepPhase.svelte index 76fc8d9..0d24c5a 100644 --- a/frontend/src/components/UpkeepPhase.svelte +++ b/frontend/src/components/UpkeepPhase.svelte @@ -27,7 +27,9 @@ ); } $: myNominees = eligibleNomineesFor(state.player); - $: hasVoted = !!state.votes.find(v => v.voter_player_id === state.player.id); + $: myVote = state.votes.find(v => v.voter_player_id === state.player.id); + $: hasVoted = !!myVote; + $: votedNominee = myVote ? state.players.find(p => p.id === myVote.nominated_player_id) : null; // With no eligible crewmates, the voter skips voting entirely (no deadlock). $: mustVote = myNominees.length > 0; @@ -214,7 +216,7 @@ {:else if hasVoted}
-

✔️ Vote submitted! Waiting for other players to submit their nominations.

+

✓ Your vote: {votedNominee ? displayName(votedNominee) : "Unavailable crewmate"}

{:else if !mustVote}
diff --git a/frontend/src/lib/changelog.js b/frontend/src/lib/changelog.js index f29bdd8..c0352eb 100644 --- a/frontend/src/lib/changelog.js +++ b/frontend/src/lib/changelog.js @@ -6,10 +6,11 @@ // - Add a CHANGELOG entry only when a commit changes something players can // see. Skip refactors, tests, and tooling. Keep wording player-facing. -export const VERSION = 29; +export const VERSION = 30; // Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }. export const CHANGELOG = [ + { version: 30, date: '2026-09-04', changes: ['The crew roster shows voting progress with icons.', 'Your submitted vote now shows the crewmate you nominated.'] }, { version: 29, date: '2026-09-04', changes: ['Removed the remaining deck count from the table.'] }, { version: 28, date: '2026-09-04', changes: ['The Event Log toggle keeps the same size when opened or closed.', 'Dev Mode changes no longer clutter the Event Log.'] }, { version: 27, date: '2026-09-04', changes: ['Shorter labels make the table easier to scan.', 'Difficulty and Successes now line up side by side, including on small screens.'] },