mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 13:46:32 +03:00
Fix: use system fonts and the light theme in the picker (#751)
Replace external fonts with system stacks and an outlined SVG logo. Adopt the light paper theme, refine Kinpaku accents, and preserve picker interactions with regression coverage. AI assistance: Codex, under maintainer direction.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
---
|
||||
version: 1
|
||||
slug: "crates-context-src-question-page-rs"
|
||||
primary_target: "crates/context/src/question_page.rs"
|
||||
related_targets: ["crates/context/src/serve_question.rs"]
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
Operate mode: the existing local question picker. Help builders compare the
|
||||
current hand, choose, steer, or re-roll. Preserve payloads, copy, choices,
|
||||
confirmation, loading, error handling, and responsive deck behavior.
|
||||
|
||||
## Direction contract
|
||||
|
||||
THESIS: Quiet picker chrome lets the offered directions carry the character.
|
||||
OWN-WORLD: Inherit impeccable-site's paper-and-instruments proposal: neutral paper,
|
||||
ink type, patina state text, small gold marks, tactile controls. Use system fonts
|
||||
and the existing outlined SVG logo; no font files or external font requests.
|
||||
STORY: Compare the options, inspect details, choose or request another hand.
|
||||
FIRST VIEWPORT: Small brand at top; readable heading and build-path switch above
|
||||
the existing card deck; steering and re-roll actions remain reachable below.
|
||||
FORM: Existing picker structure, user-pinned site theme; no concept roll required.
|
||||
FINISH: unreviewed and undocumented is unfinished; this build ends with the finish review, the verdict, DESIGN.md, and every shipping raster carrying its provenance
|
||||
|
||||
## Authority
|
||||
|
||||
The user confirmed impeccable-site #34 (head eb5348e48fcd94d0d74895488b985988a401a2e2)
|
||||
as the light-theme authority, with system fonts for the picker. This is a scoped adaptation,
|
||||
not a rewrite of the repository's global DESIGN.md. No new raster assets.
|
||||
|
||||
## Built surface
|
||||
|
||||
Recorded from `crates/context/src/question_page.rs`, rendered
|
||||
`.impeccable/review/after.html`, and the desktop/mobile captures alongside it.
|
||||
Finish reviewer disposition: **ship**, with no requested fixes.
|
||||
|
||||
- **Paper and ink:** light-only chrome uses neutral paper (`oklch(97.8% 0 0)`),
|
||||
raised paper (`oklch(99.5% 0 0)`), ink headings (`oklch(13% 0 0)`), and body
|
||||
text (`oklch(22% 0 0)`). Dark instrument fills carry primary actions and image
|
||||
controls; gold appears in the logo, lead-card border, and active switch dot.
|
||||
- **Local palette adaptations:** patina is the site's text-safe deep value
|
||||
(`oklch(49% 0.11 190)`); faint metadata shares muted ink (`oklch(46% 0 0)`).
|
||||
Dividers and the build-path switch boundary use ink at 12% opacity; other
|
||||
control boundaries use 45%. The lead-card outline (including hover) and
|
||||
active switch dot use default Kinpaku (`oklch(84% 0.19 80.46)`), not deep gold.
|
||||
- **Explicit font override:** headings, body, and controls use `system-ui,
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`; metadata uses
|
||||
`ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace`. Body is 15px/1.55;
|
||||
headings are 2rem at weight 600, reduced to 1.5rem in portrait; card titles
|
||||
are 1.125rem at weight 600. The existing wordmark is inline SVG path outlines
|
||||
with a tight viewBox, displayed at 164 × 30px. No font files are required.
|
||||
- **Comparison layout:** a 90rem maximum content width shares its inset with
|
||||
the deck and footer. Landscape uses a horizontal snapping deck; portrait
|
||||
uses a vertical deck capped at `min(68dvh, 44rem)` with Back/More controls.
|
||||
The build-path switch moves below the heading in portrait. Footer controls
|
||||
are sticky in landscape and remain in document flow in portrait.
|
||||
- **Tactile controls:** cards and dialogs use 8px corners and layered soft
|
||||
shadows. Actions and the steering field use 6px corners. The build-path
|
||||
switch has a recessed gray track, a raised active cap, and a gold dot;
|
||||
re-roll controls share the cap shadow and inset pressed state. Keyboard
|
||||
focus uses a 2px patina outline. Image zoom, card flips, loading placeholders,
|
||||
confirmation, and completion/error surfaces retain their existing roles.
|
||||
|
||||
These are picker-local facts, not a replacement global design system. Existing
|
||||
card badges and dense uppercase metadata are preserved content treatments,
|
||||
not new typography rules for other surfaces. The user-authorized system fonts
|
||||
are an intentional local override. No raster assets were created or added.
|
||||
@@ -106,6 +106,16 @@ fn detached_posts_require_key_and_loopback_host_origin() {
|
||||
assert!(page.contains("/heartbeat' + keyQ"));
|
||||
assert!(page.contains("/build-path' + keyQ"));
|
||||
|
||||
// The picker is self-contained: system fonts and an outlined SVG
|
||||
// wordmark, with no bundled font routes or external stylesheet.
|
||||
assert!(!page.contains("@font-face"));
|
||||
assert!(page.contains("--ks-font: system-ui,"));
|
||||
assert!(page.contains("aria-label=\"Impeccable\""));
|
||||
for path in ["/fonts/albert-sans.woff2", "/fonts/alumni-sans.ttf", "/fonts/%2e%2e/q.json"] {
|
||||
let (st, _) = raw_request(port, "GET", path, &[], None);
|
||||
assert_eq!(st, 404, "no font asset routes");
|
||||
}
|
||||
|
||||
// The build-path flip takes the same gate as /answer.
|
||||
let flip = r#"{"value":"comp"}"#;
|
||||
let (st, _) = raw_request(port, "POST", "/build-path", &[json], Some(flip));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1264,6 +1264,18 @@ impl ServerState {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn question_page_uses_system_fonts_and_inline_branding() {
|
||||
assert!(!PAGE.contains("fonts.googleapis.com"));
|
||||
assert!(!PAGE.contains("fonts.gstatic.com"));
|
||||
assert!(!PAGE.contains("@font-face"));
|
||||
assert!(!PAGE.contains("/fonts/"));
|
||||
assert!(PAGE.contains("--ks-font: system-ui,"));
|
||||
assert!(PAGE.contains("aria-label=\"Impeccable\""));
|
||||
assert!(PAGE.contains("color-scheme: light"));
|
||||
assert!(!PAGE.contains("color-scheme: dark"));
|
||||
}
|
||||
|
||||
// JS scenarios: tests/serve-question.test.mjs (public repo main,
|
||||
// eaaecbd1 + 2e075dc5 + 7982002d).
|
||||
|
||||
|
||||
@@ -129,6 +129,76 @@ function spawnSyncGen(prompt, out, size = null) {
|
||||
// serve-question interactive cycles
|
||||
// --------------------------------------------------------------------------
|
||||
describe('new-work-e2e: serve-question decision page', () => {
|
||||
it('renders a light system-font picker without font or external requests', async () => {
|
||||
const cwd = makeWorkspace();
|
||||
const key = 'system-fonts';
|
||||
const context = await browser.newContext({ colorScheme: 'dark' });
|
||||
const externalRequests = [];
|
||||
const fontResponses = [];
|
||||
try {
|
||||
const { url } = await startDaemon(cwd, {
|
||||
title: 'Choose the visual world',
|
||||
options: [{ id: 'assigned', label: 'Local typography' }],
|
||||
buildPath: { value: 'code', toggle: true },
|
||||
}, key);
|
||||
const origin = new URL(url).origin;
|
||||
await context.route('**/*', (route) => {
|
||||
if (new URL(route.request().url()).origin !== origin) {
|
||||
externalRequests.push(route.request().url());
|
||||
return route.abort();
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
const page = await context.newPage();
|
||||
page.on('response', (response) => {
|
||||
if (response.request().resourceType() === 'font') {
|
||||
fontResponses.push({ url: response.url(), status: response.status(), type: response.headers()['content-type'] });
|
||||
}
|
||||
});
|
||||
await page.goto(url);
|
||||
const appearance = await page.evaluate(async () => {
|
||||
await document.fonts.ready;
|
||||
const tokenColor = (token) => {
|
||||
const probe = document.createElement('span');
|
||||
probe.style.color = `var(${token})`;
|
||||
document.body.append(probe);
|
||||
const color = getComputedStyle(probe).color;
|
||||
probe.remove();
|
||||
return color;
|
||||
};
|
||||
return {
|
||||
fonts: document.fonts.size,
|
||||
family: getComputedStyle(document.body).fontFamily,
|
||||
scheme: getComputedStyle(document.documentElement).colorScheme,
|
||||
logoPaths: document.querySelectorAll('.brand svg path').length,
|
||||
logoText: document.querySelectorAll('.brand svg text').length,
|
||||
kinpaku: tokenColor('--ks-kinpaku'),
|
||||
rule: tokenColor('--ks-rule'),
|
||||
leadBorder: getComputedStyle(document.querySelector('.face.lead')).borderTopColor,
|
||||
switchBorder: getComputedStyle(document.querySelector('.bp-switch')).borderTopColor,
|
||||
activeDot: getComputedStyle(document.querySelector('.bp-opt.active'), '::before').backgroundColor,
|
||||
};
|
||||
});
|
||||
assert.equal(appearance.fonts, 0, 'no custom font faces');
|
||||
assert.match(appearance.family, /^system-ui,/);
|
||||
assert.equal(appearance.scheme, 'light', 'picker stays light even with a dark OS preference');
|
||||
assert.ok(appearance.logoPaths > 2, 'the brand mark and wordmark are vector outlines');
|
||||
assert.equal(appearance.logoText, 0, 'the logo does not depend on a font');
|
||||
assert.equal(appearance.leadBorder, appearance.kinpaku, 'lead outline uses default Kinpaku');
|
||||
assert.equal(appearance.activeDot, appearance.kinpaku, 'active switch dot uses default Kinpaku');
|
||||
assert.equal(appearance.switchBorder, appearance.rule, 'switch track has a quiet border');
|
||||
await page.locator('.card').first().hover();
|
||||
assert.equal(await page.locator('.face.lead').evaluate((el) => getComputedStyle(el).borderTopColor), appearance.kinpaku, 'hover preserves the default Kinpaku outline');
|
||||
await page.getByRole('img', { name: 'Impeccable', exact: true }).waitFor();
|
||||
assert.deepEqual(externalRequests, [], 'dialog must not request third-party resources');
|
||||
assert.deepEqual(fontResponses, [], 'no bundled or remote font downloads');
|
||||
} finally {
|
||||
await context.close();
|
||||
await stopDaemon(cwd, key);
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('(a) pick assigned returns the option, hero/board fields, and the CHOSEN CARD directive', async () => {
|
||||
const cwd = makeWorkspace();
|
||||
const key = 'pick';
|
||||
|
||||
Reference in New Issue
Block a user