refactor(spec): one candidate producer over one target-eligibility rule

Both hosts routed verbs themselves and both policies enumerated their own
actions, and all four drifted. Web sent `swipes` to scrollable containers only,
so swipe-to-dismiss on a list row was reachable on native and unreachable on
web; the model policy folded gestures its own way and could not reach what the
seeded picker drew.

A host now reports facts about every element and never decides which verb may
act on it: targets.ts acceptsTarget owns that for both. pick.ts builtinCandidates
is the single enumeration, and the model policy reads it through
__sanderlingEnumerateBuiltin__ instead of reimplementing it in Go.

Gesture verbs change with it: scrolls stay vertical over scrollable containers,
swipes go free-form in all four directions from any element with real bounds.

Claude-Session: https://claude.ai/code/session_01Fj4wJUikdABuMQEETwW55J
This commit is contained in:
pj committed 2026-08-12 16:48:27 +05:30
1 parent ef04a6de9d
commit b5f64bf665
23 files changed
+1494 -594

No files matched your search

+8 -4
View File
@@ -15,10 +15,14 @@ import type {
function installRuntime(initialState: State): void {
const state = { current: initialState };
const runtime: SanderlingRuntime = {
extract: <T>(getter: (s: State) => T): Extracted<T> => ({
current: getter(state.current),
previous: undefined,
}),
extract: <T>(getter: (s: State) => T): Extracted<T> => {
const handle: Extracted<T> = {
current: getter(state.current),
previous: undefined,
named: () => handle,
};
return handle;
},
always: () => ({ __sanderlingFormula: true } as Formula),
now: () => ({ __sanderlingFormula: true } as Formula),
next: () => ({ __sanderlingFormula: true } as Formula),