Five things from the second playtest. A status line at the top of every screen: the game, the pack, your current title, and the version plus the commit it came from. `tools/stamp.js` writes the commit into src/build-info.js before serving and before building, so a distributed dist/theladder.html states exactly which commit produced it. An uncommitted working tree gets a `+` suffix — "4cab9fc+" is honest in a way a bare SHA would not be. The whole game is now playable on Enter. Every screen marks one control and the app focuses it after each render: the first *available* option while choosing, the next-turn button once the result is in, "keep going" on the retrospective. Tab and Shift+Tab reach the other options. Locked options needed no special handling — the browser's tab order already skips disabled controls, so they stay visible without being in the way. "What is notable? under What changed?" — two fixes. `notable` is the retrospective's own list of moments; a display rule can now say `inLedger: false` and it is left out, because "Notable — changed" tells nobody anything. Flags are the opposite and stay, but as sentences the pack supplies rather than as booleans: "The envelope is in your locker." instead of "the envelope: now true". Exported logs live in logs/, gitignored, and `npm run analyze` reads the newest one there when given no argument. A log records what a real person did turn by turn, including whatever they typed into the feedback box — committing one once would put it in the history for good. Version set to 0.3.0 on the reasoning that the engine was 0.1 and the interface 0.2; say if you want a different scheme and I will renumber. 119 tests. Reasoning in docs/DECISIONS.md §25-28. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VMSFHyVPitUoosW5wyEADj
45 lines
1.7 KiB
JavaScript
45 lines
1.7 KiB
JavaScript
// Write the current commit into src/build-info.js.
|
|
//
|
|
// Runs before serving and before building, so what is on screen always names
|
|
// the commit it came from. The file is tracked rather than generated-and-
|
|
// ignored so that a fresh clone works without running anything first; it
|
|
// changes at most once per commit, and only when the value actually differs.
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { resolve, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
const target = resolve(root, 'src/build-info.js');
|
|
|
|
function commit() {
|
|
try {
|
|
const sha = execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: root, stdio: 'pipe' })
|
|
.toString().trim();
|
|
const dirty = execFileSync('git', ['status', '--porcelain'], { cwd: root, stdio: 'pipe' })
|
|
.toString().trim().length > 0;
|
|
return dirty ? `${sha}+` : sha;
|
|
} catch {
|
|
return 'dev';
|
|
}
|
|
}
|
|
|
|
const next = `// Stamped by tools/stamp.js, which runs before \`npm run serve\` and
|
|
// \`npm run build\`. The commit is whatever HEAD was at stamp time, so a dist
|
|
// file says exactly which commit produced it.
|
|
export const BUILD = {
|
|
commit: '${commit()}',
|
|
builtAt: '${new Date().toISOString().slice(0, 10)}',
|
|
};
|
|
`;
|
|
|
|
const current = readFileSync(target, 'utf8');
|
|
// Only the date differs on a same-commit restamp; leave the file alone then,
|
|
// so a working tree does not churn every time the server starts.
|
|
const strip = (text) => text.replace(/builtAt: '[^']*'/, '');
|
|
if (strip(current) !== strip(next)) {
|
|
writeFileSync(target, next, 'utf8');
|
|
console.log(`stamped build ${commit()}`);
|
|
}
|