Phase 2: Explore's latch, haptics, back button, label overflow
Four things reported on the device, all real. Tapping a modifier did nothing. setLayer was only reachable from a search result or the layer sheet, so the reference's latch was simply never ported. It is now, with the semantics intact rather than reduced to a toggle: hold to peek and it lifts away, tap to lock and it stays, tap again to release, and reading a lit key while holding a modifier spends that modifier so lifting drops it. Knobs and pads never latch, which is what NOLATCH is for. held is now the source of truth and the layer key is derived from it, as in the reference. That needed per-control release events. Panel knew which control each pointer held and threw it away; it now reports onRelease before the gesture-wide callback, because the latch decides hold-versus-tap from how long one key was down. Haptics and the hardware back button were missing entirely - Phase 1 did both in native.js and the port had no equivalent. native.ts adds them, no-ops in a browser so the dev server is unaffected. Back owns the whole stack: sheet, then Explore, then exit. Grade haptics fire from the transition into answered, not from the four dispatch sites. Grading happens in the reducer, so deciding correctness again at each call site would be a second source of truth free to drift. PINGPONG overflowed its key. Lit keys show the entry's short label, which comes from the data and can be one unbreakable word; the soft hyphens only cover printed labels. Keys now break anywhere and clip. 141 tests, typecheck clean, installed and running on the device. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
43
src/ui/native.ts
Normal file
43
src/ui/native.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/** A light tick under every key press, as the reference's buzz() did. */
|
||||
export function tapFeedback(): void {
|
||||
if (!isNative()) return;
|
||||
void Haptics.impact({ style: ImpactStyle.Light }).catch(() => {});
|
||||
}
|
||||
|
||||
/** Shaped feedback on a graded answer: right and wrong should feel different. */
|
||||
export function gradeFeedback(correct: boolean): void {
|
||||
if (!isNative()) return;
|
||||
void Haptics.notification({
|
||||
type: correct ? NotificationType.Success : NotificationType.Error,
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
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]);
|
||||
}
|
||||
Reference in New Issue
Block a user