mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
Canon standing exit, chosen-card directive, and the ambition fixes
From Paul's approved UX and the eb686f36 session post-mortem: The standing exit: direction rounds carry a quiet, permanent "Play it straight" action (payload flag canon, reserved id) on the decision page and as the last structured-tool option. It is the user's door, never the model's: never recommended, never weighed against the roll, and choosing it swaps the bar rather than lowering it, two or three named reference products become the craft level, canon executed at full commitment. Safer/conventional steers resolve here, never to a stranger re-roll. Session fixes, each mechanical where possible: the ANSWER line now names the chosen card's hero and board and directs opening them before code (the session built from text alone after viewing a different world's card); generation scale joins the imagery rule (a library of centered 128px subjects foreclosed the atmospheric hero); DESIGN.md rules are checked against the world's native devices and never added to silence a hook finding (the session banned arcade lettering's own offset shadow and laundered 8px through the ramp); staging joins the FORM contract block (the axis was dropped silently at world-choice); the finishing reviewer audits the ceiling against the QUALITY BAR card after persistence (floor rigor was disguising unreached ambition); the icon-tile clause names hand-drawn icons as remedy, not target. Dist rebuild deferred: the release-gate campaign reads the pinned dist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
70fdc172b8
commit
e409bec7b5
@@ -30,6 +30,8 @@
|
||||
* }, ...
|
||||
* ],
|
||||
* "reroll": true, // adds a re-roll action (returns {"optionId":"reroll"})
|
||||
* "canon": true, // adds the quiet "Play it straight" standing exit
|
||||
* // (returns {"optionId":"canon"}); direction rounds only
|
||||
* "steer": true // adds a free-text steer field returned with any answer
|
||||
* }
|
||||
*
|
||||
@@ -93,6 +95,24 @@ if (wantsBrowser && !process.env.IMPECCABLE_QUESTION_FORCE) {
|
||||
}
|
||||
}
|
||||
|
||||
// Both answer channels (blocking stdout and --wait collection) print through
|
||||
// this: the ANSWER line, then a directive to open the chosen card's imagery
|
||||
// when it has any. The card viewing happens at the moment of choice, in the
|
||||
// working turn, because a build that never reopens the chosen world's board
|
||||
// and hero calibrates on nothing.
|
||||
function printAnswer(raw) {
|
||||
console.log(`ANSWER: ${raw}`);
|
||||
try {
|
||||
const a = JSON.parse(raw);
|
||||
if (a.hero || a.board) {
|
||||
console.log("CHOSEN CARD: open the chosen world's board and hero images now, before any code (download local copies first when your harness only reads files); they set the craft bar the build must reach.");
|
||||
}
|
||||
if (a.optionId === 'canon') {
|
||||
console.log('CANON CHOSEN: the user picked the category standard on purpose. Ask once for two or three products this should sit alongside; their craft level becomes the quality bar. Execute the canon at full commitment, conventions embraced without irony or smuggled quirk.');
|
||||
}
|
||||
} catch { /* raw answer */ }
|
||||
}
|
||||
|
||||
const payloadPath = arg('payload');
|
||||
const timeoutSec = Number(arg('timeout', '900'));
|
||||
const portArg = Number(arg('port', '0'));
|
||||
@@ -109,9 +129,10 @@ if (hasFlag('schema')) {
|
||||
{ id: 'challenger-teletext', label: 'Teletext Service', lineage: 'broadcast teletext magazines', body: 'Fused alternate.', hero: 'https://impeccable.style/worlds/cards/broadcast-programming-teletext-service-hero.webp' },
|
||||
],
|
||||
reroll: true,
|
||||
canon: true,
|
||||
steer: true,
|
||||
}, null, 2));
|
||||
console.log('\nOption ids return verbatim in ANSWER; "reroll" is reserved. hero/board accept URLs or local paths.');
|
||||
console.log('\nOption ids return verbatim in ANSWER; "reroll" and "canon" are reserved. hero/board accept URLs or local paths. canon adds a quiet standing "Play it straight" action for direction decisions: the user\'s explicit door to the category standard. Include it only for visual-direction rounds; never present canon as your own recommendation.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
@@ -144,7 +165,7 @@ if (hasFlag('wait')) {
|
||||
}
|
||||
if (!answered()) { console.log(`WAITING: no answer yet after ${pollSec}s; run --wait --key ${key} again`); process.exit(3); }
|
||||
const collected = fs.readFileSync(answerFile(key), 'utf8').trim();
|
||||
console.log(`ANSWER: ${collected}`);
|
||||
printAnswer(collected);
|
||||
// A re-roll keeps the table open: the server stays alive awaiting --update,
|
||||
// so only the answer file is consumed. Terminal choices clean up fully.
|
||||
let isRerollAnswer = false;
|
||||
@@ -354,6 +375,11 @@ function page() {
|
||||
#reroll { display: inline-flex; align-items: center; align-self: stretch; gap: 8px; padding: 0 16px; font-family: var(--ks-mono); font-size: .72rem; letter-spacing: .08em; text-transform: uppercase; color: var(--ks-kinpaku); background: transparent; border: 1px solid var(--ks-rule); border-radius: 6px; cursor: pointer; transition: border-color .2s ease, color .2s ease; }
|
||||
#reroll:hover { color: var(--ks-kinpaku-pale); border-color: var(--ks-kinpaku-deep); }
|
||||
#reroll svg { width: 15px; height: 15px; }
|
||||
/* The quiet exit: always available, never argued with, visually subordinate
|
||||
to the dealt cards and the re-roll so it reads as the user's own door,
|
||||
not a recommendation. */
|
||||
#canon { align-self: center; padding: 0 4px; font-family: var(--ks-mono); font-size: .66rem; letter-spacing: .08em; text-transform: uppercase; color: inherit; opacity: .45; background: transparent; border: none; border-bottom: 1px dotted currentColor; cursor: pointer; transition: opacity .2s ease; }
|
||||
#canon:hover { opacity: .85; }
|
||||
.card.skeleton .media { background: var(--ks-graphite); }
|
||||
.shimmer { width: 100%; height: 100%; background: linear-gradient(100deg, var(--ks-graphite) 35%, var(--ks-graphite-2) 50%, var(--ks-graphite) 65%); background-size: 220% 100%; animation: shimmer 1.4s linear infinite; }
|
||||
.card.skeleton .line { height: 11px; border-radius: 4px; background: linear-gradient(100deg, var(--ks-graphite) 35%, var(--ks-graphite-2) 50%, var(--ks-graphite) 65%); background-size: 220% 100%; animation: shimmer 1.4s linear infinite; }
|
||||
@@ -388,6 +414,7 @@ function page() {
|
||||
<footer>
|
||||
${payload.steer ? '<input id="steer" placeholder="Optional steer: what should be different or kept?">' : ''}
|
||||
${payload.reroll ? '<button id="reroll"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="4" fill="none" stroke="currentColor" stroke-width="1.6"/><circle cx="8.4" cy="8.4" r="1.5" fill="currentColor"/><circle cx="15.6" cy="8.4" r="1.5" fill="currentColor"/><circle cx="8.4" cy="15.6" r="1.5" fill="currentColor"/><circle cx="15.6" cy="15.6" r="1.5" fill="currentColor"/><circle cx="12" cy="12" r="1.5" fill="currentColor"/></svg><span>Re-roll</span></button>' : ''}
|
||||
${payload.canon ? '<button id="canon" title="Skip the roll: build the page this category ships, executed impeccably">Play it straight</button>' : ''}
|
||||
</footer>
|
||||
<script>
|
||||
const steer = () => document.getElementById('steer')?.value || '';
|
||||
@@ -458,6 +485,7 @@ function page() {
|
||||
const closeLightbox = () => { lightbox.classList.remove('open'); setTimeout(() => { lightbox.hidden = true; }, 250); };
|
||||
lightbox.addEventListener('click', closeLightbox);
|
||||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && !lightbox.hidden) closeLightbox(); });
|
||||
document.getElementById('canon')?.addEventListener('click', () => answer('canon'));
|
||||
document.getElementById('reroll')?.addEventListener('click', async () => {
|
||||
await fetch('/answer', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ optionId: 'reroll', steer: steer() }) });
|
||||
const grid = document.querySelector('.grid');
|
||||
@@ -535,13 +563,18 @@ const server = http.createServer((req, res) => {
|
||||
res.end('{"ok":true}');
|
||||
let parsed = {};
|
||||
try { parsed = JSON.parse(body); } catch { /* empty steer */ }
|
||||
const answer = JSON.stringify({ optionId: parsed.optionId ?? null, steer: parsed.steer ?? '' });
|
||||
const chosen = options.find((o) => o.id === parsed.optionId);
|
||||
const answer = JSON.stringify({
|
||||
optionId: parsed.optionId ?? null,
|
||||
steer: parsed.steer ?? '',
|
||||
...(chosen?.hero || chosen?.board ? { hero: chosen.hero ?? null, board: chosen.board ?? null } : {}),
|
||||
});
|
||||
const isReroll = parsed.optionId === 'reroll';
|
||||
if (detachedKey) {
|
||||
fs.mkdirSync(QUESTION_DIR, { recursive: true });
|
||||
fs.writeFileSync(answerFile(detachedKey), answer + '\n');
|
||||
} else {
|
||||
console.log(`ANSWER: ${answer}`);
|
||||
printAnswer(answer);
|
||||
}
|
||||
// A re-roll in detached mode keeps the table open: the client shows a
|
||||
// loading hand and reloads when --update delivers the next round.
|
||||
|
||||
Reference in New Issue
Block a user