INTERACTION MODEL
Protect the intent behind an activation
A user begins an interaction against the screen they can perceive. Before the browser commits the click or key activation, an interface update may move the target, replace its identity, or change what it means. sparsh records intent and a target snapshot, then compares the snapshot at activation time. A suspicious activation can be reported or stopped according to mode.
- Resolve: map the raw event target to the nearest interactive element.
- Snapshot: record its perceivable age, position, identity, and semantic fingerprint.
- Evaluate: run applicable policies and produce one decision.
- Respond: allow the activation or cancel it, then emit callbacks for application feedback.
MODES
Enforce by default; use report to tune
The default mode is enforce. In report mode, policies still evaluate
and callbacks still fire, but the activation is allowed through. You can set one global mode
or override modes by policy.
import { ActivationGuardProvider } from '@sparshlabs/react'
<ActivationGuardProvider
mode={{ age: 'report' }}
policies={['age', 'continuity', 'semantics', 'doubleFire']}
>
<Application />
</ActivationGuardProvider>
When a mode map is supplied, any omitted policy remains enforce. The first
applicable policy that returns a suspicious verdict determines the decision; a report-mode
verdict lets that activation through and ends evaluation for it. Consider policy order and
overlap when mixing modes.
| Decision field | Meaning |
|---|---|
allowed | The first suspicious policy verdict; false means a policy flagged the activation. |
enforced | True only when that verdict was in enforce mode and sparsh stopped the activation. |
policy | The policy that produced the verdict; absent when all applicable policies allow. |
reason | Human-readable explanation for a suspicious verdict, when provided. |
event | Input kind and activation phase. |
target | Identity summary, perceivable age, and optional stable guard key. |
POLICY REFERENCE
Four independent signals
| Policy | What it checks | Inputs | Default |
|---|---|---|---|
age | Target has been perceivable for at least cooldownMs. | Pointer, keyboard, virtual | Enforce; threshold 500 ms |
continuity | Target identity and rectangle stayed stable from pointer down to pointer up. | Pointer | Enforce; tolerance 4 px |
semantics | Meaningful target properties did not change during the interaction. | All kinds; needs a stored intent snapshot | Enforce; text comparison off |
doubleFire | A rapid residual press did not land on a new control at overlapping coordinates. | Pointer | Enforce; 300 ms window |
Age
Age measures time since an element became perceivable, not simply when its DOM node was
created. A target younger than cooldownMs is flagged. Unknown or pre-existing
targets are treated as old (infinite age), which fails open. Age also applies to keyboard and
virtual activations; if the threshold catches legitimate fast keyboard use, configure Age in
report mode or tune the cooldown.
Continuity
Continuity compares identity and position across the pointer press. It does not run for keyboard or virtual activations because they have no pointer-down snapshot.
Semantics
The default fingerprint checks disabled state, accessible label, role, link target or control
value, and data-guard-key. Text comparison is opt-in because counters, timestamps,
and live labels can change during an otherwise valid interaction. The current DOM host records
semantic intent on pointer down; without a stored intent snapshot, Semantics allows the
activation. Age still evaluates the target’s current age for keyboard and virtual input.
DoubleFire
This pointer-only policy looks for a different, newly visible target at overlapping coordinates shortly after an allowed pointer activation. A quick repeat on the same stable control is allowed; this policy is not a general double-click debounce.
DYNAMIC LISTS
Give reused rows a stable identity
A framework key helps the renderer reconcile nodes, but it is not automatically available to
the DOM host. Add data-guard-key when one physical row can represent different
entities after a reorder or update.
<ul>
{invoices.map((invoice) => (
<li key={invoice.id} data-guard-key={invoice.id}>
<button onClick={() => openInvoice(invoice.id)}>
Open {invoice.name}
</button>
</li>
))}
</ul> sparsh reads the nearest ancestor’s key. Without it, a recycled DOM element whose content changes in place may look like the same target, especially while text comparison is disabled.
INPUT AND ACCESSIBILITY
Keyboard and assistive technology remain part of the model
The DOM host observes Pointer Events and activation keys Enter and Space. It does not intercept Escape, Tab, focus changes, scrolling, or unrelated keys. Continuity and DoubleFire are pointer-only. Age evaluates keyboard and virtual activations; Semantics only compares meaning when it has a stored intent snapshot. Report mode is available if Age needs tuning for fast keyboard users.
A block should be explained. Use onDecision or a per-element hook to show a
status message, preserve focus, and invite the user to try again. onBlocked is
optional visual feedback, not a replacement for an accessible explanation.
KNOWN LIMITS
Signals are intentionally conservative
- Opacity-only reveals on an element that already exists are not detected by the current age tracking.
- An overlay can cover an unmoved target without changing its resolved identity; occlusion is not generally detected.
- Age treats unknown/pre-existing targets as old and allows them, favoring fewer false positives.
- In-place entity changes need
data-guard-keyfor reliable identity comparison. - Exceptions in policy evaluation fail open. Visual callback exceptions are ignored and do not affect cancellation.
For binding-specific APIs, see the React, Vue, and DOM references.