@@ -54,6 +54,8 @@ pub enum Fragment {
5454 ForLoop (WebUIFragmentFor ),
5555 Signal (WebUIFragmentSignal ),
5656 IfCond (WebUIFragmentIf ),
57+ Attribute (WebUIFragmentAttribute ),
58+ Plugin (WebUIFragmentPlugin ),
5759}
5860```
5961### Fragment Types
@@ -123,6 +125,16 @@ pub struct WebUIFragmentAttribute {
123125}
124126```
125127
128+ #### Plugin Fragment
129+ Plugin fragments carry opaque data from parser plugins to handler plugins. WebUI does
130+ not interpret this data — each parser/handler plugin pair defines its own binary contract.
131+ ``` rust
132+ pub struct WebUIFragmentPlugin {
133+ /// Opaque plugin-specific binary data.
134+ pub data : Vec <u8 >,
135+ }
136+ ```
137+
126138** Attribute types:**
127139- ** Simple dynamic:** ` href="{{url}}" ` → ` { name: "href", value: "url" } `
128140- ** Boolean (` ? ` prefix):** ` ?disabled={{isDisabled}} ` → ` { name: "disabled", condition_tree: identifier("isDisabled") } ` — rendered only if condition is truthy; silently dropped if value is not a pure handlebars expression.
@@ -239,21 +251,70 @@ pub enum ExpressionError {
239251```
240252
241253## Handler Implementation (webui-handler)
242- ### Core Function
254+ ### Core API
243255``` rust
244- pub fn handler (
245- protocol : & WebUIProtocol ,
246- state : & Value ,
247- writer : impl Writer
248- ) -> Result <(), HandlerError >
256+ pub struct WebUIHandler {
257+ plugin : Option <Box <dyn HandlerPlugin >>,
258+ }
259+
260+ impl WebUIHandler {
261+ pub fn new () -> Self ;
262+ pub fn with_plugin (plugin : Box <dyn HandlerPlugin >) -> Self ;
263+
264+ pub fn handle (
265+ & mut self ,
266+ protocol : & WebUIProtocol ,
267+ state : & Value ,
268+ writer : & mut dyn ResponseWriter ,
269+ ) -> Result <()>;
270+ }
249271```
250272### Writer Interface
251273``` rust
252- pub trait Writer {
253- fn write (& mut self , content : & str ) -> Result <(), io :: Error >;
254- fn end (& mut self ) -> Result <(), io :: Error >;
274+ pub trait ResponseWriter {
275+ /// Write content to the output
276+ fn write (& mut self , content : & str ) -> Result <()>;
277+ /// Finalize the output
278+ fn end (& mut self ) -> Result <()>;
255279}
256280```
281+
282+ ### Handler Plugin System
283+ The handler supports a framework-agnostic plugin system. Plugins receive lifecycle
284+ callbacks during rendering and can inject arbitrary content. WebUI does not interpret
285+ what plugins write — each framework defines its own marker format.
286+
287+ ``` rust
288+ pub trait HandlerPlugin {
289+ fn push_scope (& mut self );
290+ fn pop_scope (& mut self );
291+ fn on_binding_start (& mut self , name : & str , writer : & mut dyn ResponseWriter ) -> Result <()>;
292+ fn on_binding_end (& mut self , name : & str , writer : & mut dyn ResponseWriter ) -> Result <()>;
293+ fn on_repeat_item_start (& mut self , index : usize , writer : & mut dyn ResponseWriter ) -> Result <()>;
294+ fn on_repeat_item_end (& mut self , index : usize , writer : & mut dyn ResponseWriter ) -> Result <()>;
295+ fn on_plugin_data (& mut self , data : & [u8 ], writer : & mut dyn ResponseWriter ) -> Result <()>;
296+ }
297+ ```
298+
299+ ** Hook invocation points:**
300+ - ** Signal** : ` on_binding_start ` before, ` on_binding_end ` after (same scope)
301+ - ** For loop** : ` on_binding_start/end ` around entire loop; ` on_repeat_item_start/end ` + ` push_scope/pop_scope ` per item
302+ - ** If condition** : ` on_binding_start/end ` around condition; ` push_scope/pop_scope ` if condition is true
303+ - ** Component** : ` push_scope/pop_scope ` around component body
304+ - ** Plugin fragment** : ` on_plugin_data ` with opaque bytes from protocol
305+
306+ ** Built-in plugin: ` FastHydrationPlugin ` **
307+ Injects FAST-HTML hydration comment markers for client-side re-hydration:
308+ - Binding: ` <!--fe-b$$start$$INDEX$$NAME$$fe-b--> ` / ` <!--fe-b$$end$$INDEX$$NAME$$fe-b--> `
309+ - Repeat: ` <!--fe-repeat$$start$$INDEX$$fe-repeat--> ` / ` <!--fe-repeat$$end$$INDEX$$fe-repeat--> `
310+ - Attribute (single): ` data-fe-b-INDEX `
311+ - Attribute (multi): ` data-fe-c-INDEX-COUNT `
312+
313+ ** Usage:**
314+ ``` rust
315+ let mut handler = WebUIHandler :: with_plugin (Box :: new (FastHydrationPlugin :: new ()));
316+ handler . handle (& protocol , & state , & mut writer )? ;
317+ ```
257318### Fragment Processing
258319- ** Raw fragments:** Write value directly to output
259320- ** Signal fragments:**
@@ -280,6 +341,7 @@ pub trait Writer {
280341 the fields of the current item being looped over and the global state. The ` Component ` fragment doesn't need to use
281342 the ` For ` fragment item moniker and can access the fields without the qualification. If the ` Component ` fragment is
282343 nested in multiple ` For ` fragments only the closest enclosing ` For ` fragment item's state is accessible to it.
344+ - ** Plugin fragments:** Pass opaque ` data ` bytes to the handler plugin's ` on_plugin_data ` hook. Skipped silently when no plugin is configured.
283345
284346### State Management
285347- Global state refers to the global application state that is available to all fragments at all times.
@@ -357,6 +419,44 @@ Set via `parser.set_css_strategy(CssStrategy::Inline)`.
357419pub fn parse (& mut self , fragment_id : & str , html_content : & str ) -> Result <(), ParserError >
358420pub fn into_fragment_records (self ) -> WebUIFragmentRecords
359421```
422+
423+ ### Parser Plugin System
424+ The parser supports a framework - agnostic plugin system . Plugins customize parsing
425+ behavior for framework - specific needs (component discovery , attribute filtering ,
426+ hydration data emission ) without WebUI knowing framework internals .
427+
428+ ```rust
429+ pub trait ParserPlugin {
430+ fn on_parse_component (& mut self , tag_name : & str , component : & Component ) -> Result <()>;
431+ fn should_skip_attribute (& self , attr_name : & str ) -> bool ;
432+ fn on_body_end (& mut self ) -> Option <String >;
433+ fn on_element_parsed (& mut self , binding_attribute_count : u32 ) -> Option <Vec <u8 >>;
434+ }
435+ ```
436+
437+ ** Hook invocation points:**
438+ - ** Attribute loop** : ` should_skip_attribute ` called per attribute; skipped attrs are not parsed
439+ - ** Element completion** : ` on_element_parsed ` called with binding count after all attrs processed; returned bytes emitted as ` Plugin ` fragment
440+ - ** Component encounter** : ` on_parse_component ` called when a custom element is found
441+ - ** Body end** : ` on_body_end ` called before ` body_end ` signal; returned HTML injected as raw fragment
442+
443+ ** Built-in plugin: ` FastParserPlugin ` **
444+ - Skips FAST-specific runtime attributes (` @click ` , ` f-ref ` , ` f-slotted ` , ` f-children ` )
445+ - Emits ` Plugin ` fragments with u32 LE attribute binding counts
446+ - Tracks components and injects ` <f-template> ` wrappers at body end
447+ - Converts BTR syntax to FAST syntax: ` <if> ` →` <f-when> ` , ` <for> ` →` <f-repeat> ` , ` {{expr}} ` →` {expr} ` in ` :attr ` values
448+
449+ ** Usage:**
450+ ``` rust
451+ let mut parser = HtmlParser :: with_plugin (Box :: new (FastParserPlugin :: new ()));
452+ parser . parse (" index.html" , & html )? ;
453+ ```
454+
455+ ** CLI integration:**
456+ ``` bash
457+ webui build ./templates --out ./dist --plugin=fast
458+ webui start ./templates --state ./data/state.json --plugin=fast
459+ ```
360460#### Content Processing
361461
362462##### Raw Content
@@ -496,6 +596,27 @@ webui/
496596- Error handling guidelines
497597- Examples for all major features
498598
599+ ## FFI Bindings (webui-ffi)
600+
601+ The FFI crate exposes WebUI to host languages via a C-compatible ABI. The generated
602+ header is at ` crates/webui-ffi/include/webui_ffi.h ` .
603+
604+ ### Functions
605+
606+ | Function | Description |
607+ | ----------| -------------|
608+ | ` webui_render(html, data_json) ` | Parse + render in one call. Returns heap-allocated string (caller frees with ` webui_free ` ). |
609+ | ` webui_handler_create() ` | Create a reusable handler (no plugin). |
610+ | ` webui_handler_create_with_plugin(plugin_id) ` | Create a handler with a named plugin (e.g. ` "fast" ` ). Returns ` NULL ` on error. |
611+ | ` webui_handler_render(handler, data, len, json) ` | Render a pre-compiled protocol. Returns heap-allocated string. |
612+ | ` webui_handler_destroy(handler) ` | Destroy a handler. ` NULL ` is a safe no-op. |
613+ | ` webui_free(ptr) ` | Free a string returned by any render function. ` NULL ` is a safe no-op. |
614+ | ` webui_last_error() ` | Return per-thread error message. Caller must ** not** free. |
615+
616+ ### Error Model
617+ Thread-local error storage following the POSIX ` dlerror() ` pattern. After any
618+ function returns ` NULL ` , call ` webui_last_error() ` for a human-readable diagnostic.
619+
499620## CLI Tool (webui-cli)
500621
501622The CLI specification and usage details are maintained in [ crates/webui-cli/README.md] ( crates/webui-cli/README.md ) .
0 commit comments