Skip to content
Portal Control Protocol

Parameter Targeting

Formatting capabilities in the TEXT_INPUT category (text.format, text.replace, text.insert) accept an optional targeting object that tells the SI layer exactly where an operation applies. The targeting object has four fields:

{
"text": "full document text for context",
"element": "element_42",
"start": 10,
"end": 25
}

text carries the full document content. The SI layer includes this so the capability handler has surrounding context when making formatting decisions. The handler does not modify this field; it reads from it.

element identifies a specific UI element by its element tree ID. When present, the operation targets that element regardless of focus state. This matters when the SI layer needs to format text in a background element or an element that lost focus between the SI’s analysis and the capability invocation.

start and end are character offsets within the element’s text. They define a range for operations like text.replace and text.format. A text.insert operation uses only start and ignores end. When both offsets are omitted, the capability operates on the element’s current selection. When the selection is empty and no offsets are provided, the capability operates on the entire element text.

When the targeting object is omitted entirely, the capability operates on the currently focused element with its current selection state. This fallback keeps simple invocations concise: text.format(element_id, {format: "bold"}) without a targeting object applies bold to whatever the user has selected in the focused element.

The targeting parameters are advisory inputs to the capability handler, not commands that the compositor enforces. The handler reads them to determine scope. The compositor validates that the element ID exists and that the caller has permission to operate on it, but does not validate offset ranges against document length. That responsibility belongs to the capability handler.

Tier 1 capabilities operate on the application’s data model in Rust. This is not a suggestion; it is a requirement. The StateSource::Rust variant is the default for all Tier 1 capabilities, and the compositor enforces it at registration time.

The data model in Rust owns the authoritative state of every document, every element tree, and every application setting. When text.format applies bold formatting, it modifies the data model’s representation of that text. The renderer reads the updated data model and re-renders the affected region. The renderer never owns formatting state that the data model does not also reflect.

StateSource::Renderer exists as an escape hatch for capabilities that genuinely cannot fulfill their purpose from the data model. screenshot.capture is the canonical example: pixel data exists only in the rendered output, not in any data model. A capability that reads GPU buffer contents or captures a visual snapshot must use StateSource::Renderer because there is no Rust-side equivalent to read from.

The distinction matters for reliability and testability. A capability operating on the data model can run in a headless environment with no renderer, can be tested by inspecting the data model directly, and produces deterministic results. A capability operating on the renderer depends on frame timing, GPU state, and compositing behavior, none of which are deterministic.

Capabilities that were previously backed by UI automation (injecting keystrokes, simulating clicks, running JavaScript in a web view) must migrate to data-model operations before reaching Tier 1 compliance. The migration path replaces each UI-automation call with the equivalent data-model mutation. The capability handler calls into the application’s Rust API instead of dispatching events through the UI layer.

The UI layer and the PCP layer serve different purposes, and both can operate on the same application simultaneously without conflict.

The UI layer encompasses toolbar buttons, keyboard shortcuts, context menus, and any direct user interaction with the application interface. When a user presses Ctrl+B or clicks a “Bold” button, the application handles that input through its own UI code path. This path can use document.execCommand, direct DOM manipulation, or any other API the application chooses. The compositor does not inspect or restrict these UI-internal operations.

The PCP layer encompasses all capability invocations from the SI layer. When the SI invokes text.format(element_id, {format: "bold"}), the compositor routes that to the capability handler, which modifies the application’s data model in Rust. The application then re-renders from the updated data model.

Both paths converge on the same final state because the data model is the single source of truth. A bold formatting change applied through the UI path writes its result back to the data model. A bold formatting change applied through the PCP path writes directly to the data model. The renderer reads from the data model in both cases, so the visual output is identical.

The key difference is latency and auditability. The UI path is faster for interactive use because it skips compositor routing and goes straight from input event to visual update. The PCP path adds compositor-mediated routing, permission checks, and audit logging, which introduces latency appropriate for SI-initiated actions but unnecessary for direct user input.

There is a hard boundary between what each layer can call. Tier 1 capability handlers must not call evaluate_script(), execute_script(), or document.execCommand(). These APIs belong to the renderer’s JavaScript context, and calling them from a PCP handler would bypass the data model, breaking the single-source-of-truth guarantee. The compositor does not enforce this boundary at runtime (it cannot intercept arbitrary function calls inside a handler), but capability registration for Tier 1 requires a declaration of StateSource::Rust, and compliance verification checks handler implementations against this rule.

Application UI code is free to use whatever APIs it needs. The restriction applies only to PCP capability handlers. An application’s Bold button can call document.execCommand("bold") today and migrate to a data-model API tomorrow without affecting PCP. The coexistence model treats the two layers as independent code paths with a shared data model at their convergence point.

Last updated: