Phase 2: the info card, selection, and what 35 lit keys taught

InfoCard fills the box that was empty: the layer hint and instruction
when nothing is picked, or the selected combo as chips with its name,
description and manual page. App carries the selection, and pressing a
control that holds several functions cycles through them, as the
reference does.

Lit is now an outline rather than a fill, and solid orange means
selected. This deviates from the mockups on purpose. They only ever
showed three lit keys; the SHIFT layer lights 35 of 54 controls, and
solid orange across all of them is a wall instead of a map. I checked
that by rendering the SHIFT layer rather than assuming, which meant
temporarily defaulting to it, capturing, and restoring the source.

That capture also caught a defect nothing else would have: SHIFT + VALUE
is MARK FN inside a 30px circle, and border-radius alone does not clip,
so the label sat outside the knob. Round keys now hide overflow and
shrink when lit.

Verified in light, dark, and on the SHIFT layer at 360x735. Typecheck
clean, 112 tests, www/ untouched so the Phase 1 APK still installs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 01:22:45 +03:00
parent 4961500fa7
commit c7c827ecf8
6 changed files with 228 additions and 29 deletions

View File

@@ -20,31 +20,43 @@ export interface PanelProps {
/** Controls carrying a function on the current layer, and which entries. */
readonly lit: ReadonlyMap<ControlId, Entry[]>;
readonly held: readonly ControlId[];
readonly selected?: ControlId | null;
readonly screen: ScreenText;
readonly onPress?: (id: ControlId) => void;
readonly onPress?: ((id: ControlId) => void) | undefined;
}
interface KeyProps extends Pick<PanelProps, "lit" | "held" | "onPress"> {
interface KeyProps {
readonly id: ControlId;
/** Comes from a CSS module, so possibly undefined. See cx. */
readonly className: string | undefined;
readonly lit: ReadonlyMap<ControlId, Entry[]>;
readonly held: readonly ControlId[];
readonly selected: ControlId | null;
readonly onPress?: ((id: ControlId) => void) | undefined;
}
/** Pads print their number, not "PAD 7" — until a layer lights them up. */
const padNumber = (id: ControlId): string | undefined => /^p(\d+)$/.exec(id)?.[1];
function Key({ id, className, lit, held, onPress }: KeyProps) {
function Key({ id, className, lit, held, selected, onPress }: KeyProps) {
const entries = lit.get(id);
const isHeld = held.includes(id);
const isLit = entries !== undefined && entries.length > 0;
const isSel = isLit && id === selected;
const number = padNumber(id);
return (
<button
type="button"
className={cx(className, isHeld && styles.held, !isHeld && isLit && styles.lit)}
className={cx(
className,
isHeld && styles.held,
!isHeld && isSel && styles.sel,
!isHeld && isLit && !isSel && styles.lit,
)}
data-control={id}
aria-label={panelLabel(id)}
aria-pressed={isHeld || isSel}
onClick={onPress ? () => onPress(id) : undefined}
>
{isLit ? (
@@ -61,8 +73,8 @@ function Key({ id, className, lit, held, onPress }: KeyProps) {
);
}
export function Panel({ lit, held, screen, onPress }: PanelProps) {
const key = { lit, held, ...(onPress ? { onPress } : {}) };
export function Panel({ lit, held, selected, screen, onPress }: PanelProps) {
const key = { lit, held, selected: selected ?? null, onPress };
return (
<div className={styles.panel}>