Files
404-Layers/src/domain/format.ts
ncode 563caf0658 Phase 2: the panel
Panel.tsx is presentational - told what is lit, reports presses, holds no
state. The arrangement lives in panelLayout.ts as data, so the component
stays readable and the layout can be tested.

panelLayout.test.ts asserts allPanelControls covers all 54 labelled
controls exactly once; a typo'd id would otherwise quietly vanish from
the panel. It also pins the bank row at six keys ending in SHIFT, because
Panel.module.css widens that key with :last-child:nth-child(6), a
coupling that stops matching silently if the row changes length.

Geometry is the measured one: 8 + 36 + 8 + 124 + 10 + 134 + 10 + 187 + 8
= 525. The screen is the hardware's arrangement - a 124px circle behind a
168x84 LCD, showing only as caps above and below, with no ring around the
display. Key labels are 7px with no tracking, which is what stops
RESAMPLE overflowing its 35.9px button.

cx() absorbs CSS-module lookups typing as string | undefined under
noUncheckedIndexedAccess, rather than turning that flag off - it has
already caught real bugs.

Two fixed on the way through: panelLabel used PL without importing it,
which broke typecheck and the layout test at runtime; and ten className
type errors from the same CSS-module typing, in the places that needed a
definite string rather than React's optional one.

112 tests, build clean, www/ untouched so the Phase 1 APK still installs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 01:12:09 +03:00

40 lines
1.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Turning ids into the words printed on the panel and in combo lines. */
import { CTX, GNAME, GROUPS, L, PL } from "./labels";
import type { ContextCode, ControlId, Entry, GroupId, RawEntry } from "./types";
const GROUP_IDS: ReadonlySet<string> = new Set(Object.keys(GROUPS));
/** Name of a single control: `"shift"` becomes `"SHIFT"`. */
export function controlName(id: ControlId): string {
return L[id] ?? id;
}
/** Label printed on the key itself, shortened where the full name won't fit. */
export function panelLabel(id: ControlId): string {
return PL[id] ?? controlName(id);
}
/**
* Name of whatever an entry targets: one control, a group ("PAD 1–16"), or an
* explicit list joined with a slash.
*/
export function targetName(target: RawEntry[1]): string {
if (Array.isArray(target)) return target.map(controlName).join(" / ");
if (GROUP_IDS.has(target)) return GNAME[target as GroupId];
return controlName(target);
}
export function contextNames(ctx: readonly ContextCode[]): string[] {
return ctx.map((c) => CTX[c]);
}
/** The combo as its parts: modifiers first, then the target. */
export function comboParts(entry: Entry): string[] {
return [...entry.mods.map(controlName), targetName(entry.target)];
}
/** The combo as one line: `"SHIFT + PAD 1"`. */
export function comboText(entry: Entry): string {
return comboParts(entry).join(" + ");
}