Files
404-Layers/src/ui/native.ts
ncode 6889167fcf Fix lit pads not highlighting; stop swallowing haptics errors
A lit pad kept its grey text and only its border turned orange, so pads
looked unhighlighted next to every other key. Cause was cascade order,
not logic: .pad is a single-class selector like .lit, so equal
specificity meant whichever came last won, and .pad was declared after.
The state classes now sit at the bottom of the file with a comment
saying why, and a lit pad computes rgb(255,74,0) for text and border.

native.ts reported nothing when haptics failed because I wrote
catch(() => {}) and discarded the reason. It now reports once per
distinct failure - enough to read in logcat, quiet enough not to spam on
every key press.

That reporting shows the software path is fine: isNativePlatform true,
Haptics available and bridged, VIBRATE granted, and both impact and
vibrate resolve without rejecting. So the silence is below the bridge -
most likely the phone's own touch-vibration setting - and not something
this code can detect.

141 tests, typecheck clean, installed.

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

59 lines
2.0 KiB
TypeScript

/**
* The native bits Phase 1 did in native.js: haptics and the hardware back
* button. No-ops in a browser, so the dev server behaves normally.
*/
import { useEffect } from "react";
import { App as CapacitorApp } from "@capacitor/app";
import { Capacitor } from "@capacitor/core";
import { Haptics, ImpactStyle, NotificationType } from "@capacitor/haptics";
const isNative = (): boolean => Capacitor.isNativePlatform();
/**
* Swallowing these was a mistake: haptics did nothing on the device and the
* reason had been discarded. Report once per distinct failure — enough to read
* in logcat, quiet enough not to spam on every key press.
*/
const reported = new Set<string>();
function report(where: string, error: unknown): void {
const message = error instanceof Error ? error.message : String(error);
const key = where + ":" + message;
if (reported.has(key)) return;
reported.add(key);
console.error("haptics " + where + " failed:", message);
}
/** A light tick under every key press, as the reference's buzz() did. */
export function tapFeedback(): void {
if (!isNative()) return;
Haptics.impact({ style: ImpactStyle.Light }).catch((e) => report("impact", e));
}
/** Shaped feedback on a graded answer: right and wrong should feel different. */
export function gradeFeedback(correct: boolean): void {
if (!isNative()) return;
Haptics.notification({
type: correct ? NotificationType.Success : NotificationType.Error,
}).catch((e) => report("notification", e));
}
export function exitApp(): void {
if (!isNative()) return;
void CapacitorApp.exitApp();
}
/**
* Hardware back. Registering a listener suppresses Capacitor's default history
* navigation, so `onBack` owns the whole stack: sheet, then tab, then exit.
*/
export function useBackButton(onBack: () => void): void {
useEffect(() => {
if (!isNative()) return;
const handle = CapacitorApp.addListener("backButton", () => onBack());
return () => {
void handle.then((h) => h.remove());
};
}, [onBack]);
}