Show visual voting progress and submitted nominee
This commit is contained in:
@@ -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/<name>.db .venv/bin/alembic upgrade head`, then run the autogeneration command with that same `DATABASE_URL`.
|
- If Alembic autogeneration says the target database is not up to date, create a temporary database with `DATABASE_URL=sqlite:////tmp/<name>.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
|
## 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`).
|
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`).
|
||||||
|
|||||||
@@ -13,8 +13,8 @@
|
|||||||
- [x] Align the "Current Difficulty" and "Successes" boxes
|
- [x] Align the "Current Difficulty" and "Successes" boxes
|
||||||
- [x] Don't have dev mode toggling appear in the event log
|
- [x] Don't have dev mode toggling appear in the event log
|
||||||
- [x] Remove display of current deck size
|
- [x] Remove display of current deck size
|
||||||
- [ ] Voting status in the between-scenes phase should be visual, not text-based
|
- [x] Voting status in the between-scenes phase should be visual, not text-based
|
||||||
- [ ] Players should be able to see who they voted for
|
- [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
|
- [ ] 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
|
## Rules
|
||||||
|
|||||||
@@ -21,6 +21,12 @@
|
|||||||
try { return JSON.parse(value || '[]'); } catch { return []; }
|
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) {
|
function statusFor(p) {
|
||||||
const phase = state.game.phase;
|
const phase = state.game.phase;
|
||||||
if (phase === 'lobby') {
|
if (phase === 'lobby') {
|
||||||
@@ -44,13 +50,7 @@
|
|||||||
if (p.role === 'pirat') return `Pi-Rat · Rank ${p.rank}`;
|
if (p.role === 'pirat') return `Pi-Rat · Rank ${p.rank}`;
|
||||||
return 'Choosing a role…';
|
return 'Choosing a role…';
|
||||||
}
|
}
|
||||||
if (phase === 'between_scenes') {
|
if (phase === 'between_scenes') return `Rank ${p.rank}`;
|
||||||
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 === 'deep_upkeep') return `Rank ${p.rank} · ${p.is_ready ? 'Ready' : 'Finishing upkeep…'}`;
|
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 === 'recruit_creation') return p.needs_reroll ? 'Creating a new Pi-Rat' : `Rank ${p.rank} · Helping recruits`;
|
||||||
if (phase === 'ended') {
|
if (phase === 'ended') {
|
||||||
@@ -83,6 +83,9 @@
|
|||||||
title="View {p.name}'s character sheet"
|
title="View {p.name}'s character sheet"
|
||||||
on:click={() => openTargetId = p.id}
|
on:click={() => openTargetId = p.id}
|
||||||
>
|
>
|
||||||
|
{#if state.game.phase === 'between_scenes'}
|
||||||
|
<span class="vote-status" class:done={voteStatus(p).done} title={voteStatus(p).label} aria-label={voteStatus(p).label}>{voteStatus(p).icon}</span>
|
||||||
|
{/if}
|
||||||
<span class="bubble-icon">{iconFor(p)}</span>
|
<span class="bubble-icon">{iconFor(p)}</span>
|
||||||
<span class="bubble-name">{crewLabel(p, captainId)}</span>
|
<span class="bubble-name">{crewLabel(p, captainId)}</span>
|
||||||
<span class="bubble-meta">{statusFor(p)}</span>
|
<span class="bubble-meta">{statusFor(p)}</span>
|
||||||
@@ -95,3 +98,8 @@
|
|||||||
{#if openTarget}
|
{#if openTarget}
|
||||||
<CharacterSheet {state} target={openTarget} on:close={() => openTargetId = null} />
|
<CharacterSheet {state} target={openTarget} on:close={() => openTargetId = null} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.vote-status { position: absolute; top: 8px; right: 10px; display: grid; place-items: center; width: 1.5rem; height: 1.5rem; border: 1px solid currentColor; border-radius: 50%; color: var(--text-muted); font-size: 1rem; }
|
||||||
|
.vote-status.done { color: var(--success); }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -27,7 +27,9 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
$: myNominees = eligibleNomineesFor(state.player);
|
$: 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).
|
// With no eligible crewmates, the voter skips voting entirely (no deadlock).
|
||||||
$: mustVote = myNominees.length > 0;
|
$: mustVote = myNominees.length > 0;
|
||||||
|
|
||||||
@@ -214,7 +216,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else if hasVoted}
|
{:else if hasVoted}
|
||||||
<div class="voted-confirmation-box glass-panel">
|
<div class="voted-confirmation-box glass-panel">
|
||||||
<p class="success-text">✔️ Vote submitted! Waiting for other players to submit their nominations.</p>
|
<p class="success-text">✓ Your vote: <strong>{votedNominee ? displayName(votedNominee) : "Unavailable crewmate"}</strong></p>
|
||||||
</div>
|
</div>
|
||||||
{:else if !mustVote}
|
{:else if !mustVote}
|
||||||
<div class="voted-confirmation-box glass-panel">
|
<div class="voted-confirmation-box glass-panel">
|
||||||
|
|||||||
@@ -6,10 +6,11 @@
|
|||||||
// - Add a CHANGELOG entry only when a commit changes something players can
|
// - Add a CHANGELOG entry only when a commit changes something players can
|
||||||
// see. Skip refactors, tests, and tooling. Keep wording player-facing.
|
// 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, ...] }.
|
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
|
||||||
export const CHANGELOG = [
|
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: 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: 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.'] },
|
{ 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.'] },
|
||||||
|
|||||||
Reference in New Issue
Block a user