Fission 0.11.0 replaces TextInput::on_change with one universal
TextInput::on_input contract. The action an application binds is never
rewritten by the text runtime. The complete edited text, widget ID, caret, and
selection anchor arrive separately through ReducerContext::input.
This is deliberately a breaking release. The old API was convenient for
SetText(String) but made the action payload mean two incompatible things: the
application's stable context before dispatch and the current text after
dispatch. That failed for dynamic fields whose reducer needed both a field ID
and its new value. One contract is simpler to reason about and works for simple
fields and dynamic forms alike.
Bind an action that contains only stable application context:
TextInput {
value: draft.metadata.get(&field).cloned().unwrap_or_default(),
on_input: Some(ctx.bind_local(
UpdateIntegrationField(field.clone()),
draft.clone(),
reduce!(update_integration_field),
)),
..Default::default()
}
Read the transient edit in the reducer:
#[fission_reducer(UpdateIntegrationField)]
fn update_integration_field(
draft: &mut IntegrationDraft,
field: FieldId,
ctx: &mut ReducerContext<IntegrationDraft>,
) {
let Some(change) = ctx.input.text_change() else {
return;
};
draft.metadata.insert(field, change.new_text.clone());
}
text_change() returns an UpdateTextInput with node_id, new_text,
new_caret, and new_anchor. Offsets are UTF-8 byte offsets. Fission does not
inspect application JSON, replace the last string in a tuple, or synthesize a
different action at dispatch time.
UpdateTextInput is runtime event data and no longer implements Action.
Applications bind their own unit or contextual action to on_input and read
the edit from the reducer context; they do not dispatch UpdateTextInput
directly.
A field that only updates one string uses the same contract. Bind a unit action
and read change.new_text; there is no legacy payload-replacement mode to opt
into.
Declarative binding also now installs one reducer handler per effective action
ID during a build. Repeated fields can bind the same action type with different
payloads without dispatching the same reducer once per mounted field. Put
field-specific context in the action value, as above. Explicit register(...)
continues to run every registered handler and is the API for intentional
multicast dispatch.
NumberInput::on_input forwards the same text-edit event. Parse in the reducer
so the application owns product decisions around empty input, a leading minus
sign, decimal separators, invalid text, clamping, and formatting:
#[fission_reducer(SetQuantity)]
fn set_quantity(state: &mut OrderState, ctx: &mut ReducerContext<OrderState>) {
let Some(change) = ctx.input.text_change() else {
return;
};
if let Ok(quantity) = change.new_text.parse::<f32>() {
state.quantity = quantity.clamp(1.0, 100.0);
}
}
Combobox and Editable also rename their typed-edit hook to on_input and
forward this contract from their inner TextInput.
One event across interactive shells
Core keyboard and input-method edits, winit accessibility value changes, native
desktop and mobile input, Web input, and interactive SSR browser islands all
dispatch ActionInput::TextChanged(UpdateTextInput). Core IR identifies the
binding with ActionTrigger::TextChanged. Browser islands retain the runtime
and local widget state across edits rather than reconstructing the action from
DOM payloads.
Static site output and ordinary SSR HTML are intentionally inert: they render
native controls but do not execute per-keystroke Rust reducers. Use an
interactive browser island when an SSR page needs this reducer contract in the
browser. Browser islands are not yet a general client-side effects runtime;
input reducers are supported, while queued effects and callbacks are rejected.
Reducer action-deserialization failures now emit sanitized diagnostics and
return errors instead of looking like silently lost input. Diagnostics identify
the action and target without logging serialized form payloads or raw errors
that may contain credentials.
Breaking surface
Applications must make these migrations:
rename TextInput::on_change to TextInput::on_input;
replace direct UpdateTextInput action dispatch with an application action
bound to on_input;
update text reducers to read ctx.input.text_change() instead of an action
string or number;
rename Combobox, Editable, and NumberInput typed-edit hooks to
on_input;
parse NumberInput text in the reducer;
move per-widget context captured by distinct reducer closures into the bound
action payload when several widgets use the same action type;
add TextChanged arms to exhaustive matches over public ActionInput and
ActionTrigger enums;
and add an ActionDispatchFailed arm to exhaustive matches over the public
DiagEventKind enum.
Update the Fission dependency used by the application:
fission = { version = "0.11.0", default-features = false, features = ["desktop"] }
The clean break is the reason this release is 0.11.0 rather than a patch to
the previous payload-replacement behavior.