You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Comark data binding is read-only. :prop="dot.path" resolves against the render context; {{ }} and ::if read the same context. There is no write channel.
Add ::prop="dot.path" in comark core so any component or native form element can write back to a small ComarkModel protocol. Native HTML form elements become the input surface — no first-party Inputs library.
Volume: :input{type="range" min="0" max="100" ::value="data.volume"}
The volume is **{{ data.volume }}%**.
::if{:value="data.volume" :gte="90"}
That's loud.
::
::prop is attribute-position only. It does not collide with block ::name, inline :name, one-way :prop, or {{ }}. The props parser currently drops ::value, so the namespace is unclaimed.
This belongs in core, not @comark/binding. Two-way binding extends the attribute-resolution contract in resolveAttributes() / NodeRenderData, consumed by all renderers.
flowchart LR
MD["Markdown<br/><b>::value=#quot;data.volume#quot;</b>"]
Core["comark core<br/>parse · resolveAttributes"]
Model["ComarkModel<br/>get / set / subscribe"]
UI["Renderers<br/>input · Binding · If"]
MD -->|parse| Core
Core -->|"X + onUpdate:X"| UI
UI -->|set| Model
Model -->|subscribe| UI
classDef src fill:#ecfdf5,stroke:#059669,color:#065f46
classDef core fill:#eff6ff,stroke:#2563eb,color:#1e3a8a
classDef model fill:#fff7ed,stroke:#ea580c,color:#9a3412
classDef ui fill:#f0f9ff,stroke:#0284c7,color:#0c4a6e
class MD src
class Core core
class Model model
class UI ui
linkStyle 0,1 stroke:#2563eb,stroke-width:2px
linkStyle 2 stroke:#ea580c,stroke-width:2px
linkStyle 3 stroke:#0284c7,stroke-width:2px
Loading
Motivation
Interactive Markdown today needs either host-owned wrappers per control or an Inputs-style catalog. Both bypass the render context and do not work for plain HTML. A write at the attribute layer updates every reader of that path ({{ }}, ::if, other :prop bindings) without a component catalog.
Proposed solution
Syntax — Widen the props-key regex to accept ::. Reject a :: key with no value.
ComarkModel (comark/model) — Dependency-free protocol: get / set / subscribe (optional batch). Ship createModelStore(). Hosts can pass their own implementation (Vue reactive(), Zustand, …). Default store is per render root.
Attribute resolution — For ::X="path" with a model: emit X (current value) + an update callback (onUpdate:X / onUpdateX). Filtered expressions, non-writable paths, and missing model degrade to one-way (warn in prod / throw in dev where appropriate). Reuse existing URL/HTML-sink guards for ::href / ::src / innerHTML. set() rejects prototype-pollution segments and paths outside writable (default: ['data']).
Native form elements — Shared helper wires input / select / textarea (value / checked / files) with type coercion. Custom components receive X + update callback and write themselves.
Renderers — React / Vue / Svelte / Angular bridge via subscriptions. HTML SSR emits inert data-comark-model* attrs; optional @comark/html/runtime for progressive enhancement. ANSI resolves ::X as :X (one dev warning). Markdown gains model / onModelChange; default creates a store from data when no model is passed.
::form — Optional aggregate that collects child binding paths into one object (same layering as Binding / If).
Description
Comark data binding is read-only.
:prop="dot.path"resolves against the render context;{{ }}and::ifread the same context. There is no write channel.Add
::prop="dot.path"in comark core so any component or native form element can write back to a smallComarkModelprotocol. Native HTML form elements become the input surface — no first-party Inputs library.::propis attribute-position only. It does not collide with block::name, inline:name, one-way:prop, or{{ }}. The props parser currently drops::value, so the namespace is unclaimed.This belongs in core, not
@comark/binding. Two-way binding extends the attribute-resolution contract inresolveAttributes()/NodeRenderData, consumed by all renderers.Motivation
Interactive Markdown today needs either host-owned wrappers per control or an Inputs-style catalog. Both bypass the render context and do not work for plain HTML. A write at the attribute layer updates every reader of that path (
{{ }},::if, other:propbindings) without a component catalog.Proposed solution
Syntax — Widen the props-key regex to accept
::. Reject a::key with no value.ComarkModel(comark/model) — Dependency-free protocol:get/set/subscribe(optionalbatch). ShipcreateModelStore(). Hosts can pass their own implementation (Vuereactive(), Zustand, …). Default store is per render root.Attribute resolution — For
::X="path"with a model: emitX(current value) + an update callback (onUpdate:X/onUpdateX). Filtered expressions, non-writable paths, and missing model degrade to one-way (warn in prod / throw in dev where appropriate). Reuse existing URL/HTML-sink guards for::href/::src/innerHTML.set()rejects prototype-pollution segments and paths outsidewritable(default:['data']).Native form elements — Shared helper wires
input/select/textarea(value/checked/files) with type coercion. Custom components receiveX+ update callback and write themselves.Renderers — React / Vue / Svelte / Angular bridge via subscriptions. HTML SSR emits inert
data-comark-model*attrs; optional@comark/html/runtimefor progressive enhancement. ANSI resolves::Xas:X(one dev warning).Markdowngainsmodel/onModelChange; default creates a store fromdatawhen no model is passed.::form— Optional aggregate that collects child binding paths into one object (same layering asBinding/If).Out of scope
:input{type=… ::value=…}).number/.lazy/.trim) — leave.in keys unclaimedalien-signalsetc. stay optional / separate)Constraints
::propis inline-attribute-only until YAML block props resolve bindings::for, settle index-aware paths (item.done→data.items[i].done) jointlyAdditional context