Conversation
Add split_sfc_blocks, a depth-aware pre-pass in parse_sfc that captures top-level <script>/<style> bodies verbatim before quick-xml sees them, treating them as HTML-style raw-text elements. Rhai &&/</> and CSS > combinators no longer need <![CDATA[…]]>; a wrapper is stripped if present so existing CDATA-wrapped files parse unchanged. Migrate examples/sfc/*.nemo to CDATA-free as the definitive reference: labeled-button exercises && and > in <script>, card exercises the > combinator in <style>. Update the SFC pattern doc, configuration concept, plan status, and roadmap index. The on-disk regression test now asserts the examples are CDATA-free and exercise raw-text features.
Let handlers and scripts create/remove built-in component instances at runtime through four new API functions: create_component, create_component_with_id, update_component, and remove_component. LayoutManager gains insert_component (validates type via registry, rejects duplicate IDs, appends to parent children), remove_component (recursive subtree teardown with binding cleanup, root guard), set_properties (bulk update), and generate_dynamic_id (__dyn_N counter). PluginContext trait adds the four methods with default Err(Unsupported) so existing plugin SDKs compile unchanged. RuntimeContext implements them through the existing try_write + data_dirty re-render path. Rhai registration exposes the functions to scripts; a click handler can now create a label that appears in the layout snapshot and remove it later. WASM WIT and host_impl gain matching host functions. State entries are lazily leaked (v1) — keyed by ID, harmless, bounded by app churn. No <binding> support (by scope decision).
Complete the plan's 'knowledgebase updates required when implemented': - components.md: new 'Runtime component creation' section - data-flow.md: note create_component as a re-render trigger - patterns/runtime-component-creation.md: new pattern doc - nemo-xml-reference skill: document the no-<binding> limitation Add a working demo to examples/sfc: a fourth card with 'Add label' and 'Remove last' buttons backed by a handlers.rhai script that calls create_component and remove_component at runtime. Validates clean with nemo validate --strict.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #89 +/- ##
==========================================
+ Coverage 56.53% 58.04% +1.50%
==========================================
Files 79 81 +2
Lines 7019 7629 +610
==========================================
+ Hits 3968 4428 +460
- Misses 3051 3201 +150 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Implements Phase 3 of the control-flow-directives plan (live-data n:for
runtime list binding) together with the shared compile_directives pass it
depends on (Phases 1 & 2).
Compile-time (nemo-config):
- New directives.rs: compile_directives pass, run in load_xml_string before
resolution. n:if -> bind-visible binding (comparisons compile to a binding
block with a == / != transform; bare paths compile to bind_visible truthiness).
Static n:for -> expands a child into N sibling nodes with ${item} substitution
and index/n:key id suffixing. Live-data n:for (data.*) -> list_binding metadata.
- Resolver skips list_binding subtrees (runtime ${item.*} placeholders).
- Value gains as_object_mut / as_array_mut.
Runtime (nemo-layout):
- apply_transform: == <lit> / != <lit> comparison transforms returning Bool.
- ListBindingSpec on ComponentConfig/LayoutNode.
- New list_binding.rs: ListBindingManager. Source-path matching (exact + nested
prefix so HTTP data.api populates data.api.users), keyed diff with stable
<container>_<key> ids (reuse + reorder, preserving widget state) and index
diff, insert/remove/update via insert_component/remove_component, per-index
bindings.
- LayoutManager: list_bindings field, registration in apply_layout,
on_list_data_changed, set_children_order.
Integration (nemo):
- parse_component_from_value reads list_binding into LayoutNode.
- apply_pending_data_updates calls on_list_data_changed after scalar bindings.
- render_component skips components with visible == false (generalizes Panel).
- validate: skip n:-prefixed attrs; list_binding treated as structural.
Example (examples/sfc): three new cards demonstrating static n:for, n:if, and
live-data n:for (add/remove/reverse with n:key), plus on-load seed + handlers.
Docs: mark Phases 1-3 implemented in the plan.
Parse_sfc gains handling for <app>/<data>/<imports>/<variable>/<themes>
top-level blocks, delegating to the same process_* functions process_root
uses. SfcDefinition gains app_blocks: Option<AppBlocks> carrying the
processed Values. New XmlParser::compile_app_sfc() assembles the same
Value tree process_root produces, mapping <template> to the layout key
(wrapped as {type, component: {root_id: template}}) and folding the
raw-text <script> body into scripts.inline.
Also trims CDATA inline script bodies in process_script to match the
raw-text splitter's trimming, so app.nemo and equivalent app.xml produce
identical Value trees.
Verified: round-trip equality tests covering <app>, <data>, <variable>,
<imports>, <template>, <script> (both src-attribute and raw-text forms).
Update plan status from planned to implemented, mark the Phase 1 verification bullet as done, update the plans index entry, and add a knowledgebase log entry for the implementation.
Phases 2-3 of the app.nemo SFC entry plan. The manifest default entry
is now app.nemo (app.xml still works when set explicitly), and
ConfigurationLoader::load detects a .nemo extension and compiles the
SFC to the same Value tree load_xml_string produces, then runs the
same directive-compile + ${} resolution. Centralizing the dispatch in
loader.load means build, validate, schema, and load_config all accept
.nemo entries with no per-caller change.
- manifest.rs: default_entry() -> "app.nemo"
- loader.rs: load() dispatches .nemo to new load_nemo_string; extract
shared parser_for + compile_resolve helpers
- build.rs: build_project works unchanged via loader.load; add
project_build_nemo_entry_round_trips_via_dist test, make existing
round-trip test use explicit entry = "app.xml"
- runtime.rs: load_config works unchanged; add
test_runtime_load_config_accepts_nemo_entry
- main.rs: dist_flag test uses explicit entry = "app.xml"
- docs: mark Phases 2-3 implemented
Phase 4 — Dev/watch recompiles .nemo on save: - path_is_watchable now matches nemo alongside xml/rhai/toml; the plan's claim that it already accepted .nemo was wrong (it only matched xml|rhai|toml, so .nemo saves were silently ignored). - dev.rs error message mentions app.nemo. - reload_config -> create_runtime -> load_config already compiles .nemo via Phase 3, so the watcher admission was the only code change needed. Phase 5 — Validate/schema accept .nemo: - validate.rs and schema.rs already call ConfigurationLoader::load(path), which Phase 3 wired to compile .nemo via load_nemo_string. No new detection code needed. The n:-prefixed-attribute skip was already present. Add examples/sfc/app.nemo fixture (SFC entry mirroring app.xml, exercising SFC composition and n:for/n:if directives). Verified: nemo validate app.nemo --strict passes; nemo schema --app-config app.nemo includes SFC descriptors; nemo build app.nemo compiles to dist/; legacy app.xml still validates; compile+resolve ~35ms (well under 0.7s hot-reload target); full test suite green.
Phase 6 — Settings persistence (overrides.xml overlay): - New decision doc: settings-overrides-xml.md. The settings UI writes theme choices to overrides.xml next to the entry (app.nemo or app.xml), not into the entry file itself. xml_edit::set_app_theme now targets overrides.xml (creating/updating it); set_theme_in_xml synthesizes a fresh overlay when the content is empty/has no <app>. The entry file is never mutated. - NemoRuntime::load_config merges the overlay's app key over the entry's (shallow merge) after loading. Only the runtime applies the overlay — build/validate/schema operate on the source entry so dist stays faithful. Works for both .nemo and .xml entries. Phase 7 — Templates, examples, docs migration: - Templates: basic, calculator, data-binding renamed app.xml → app.nemo (restructured as SFC with app-level blocks); nemo.toml added to each. new.rs tfile paths, render_readme, and Next-steps message updated. The complete template stays app.xml (uses multi-file <include>, which the SFC app-block path doesn't yet handle — deliberate scope limit). - project_loader CONFIG_CANDIDATES: [app.nemo, app.xml, .nemo/app.nemo, .nemo/app.xml] — .nemo takes precedence. - dev_panel auto-load: prefers app.nemo, falls back to app.xml. - Examples: app.nemo fixtures added to examples/basic and examples/calculator. - Docs: configuration.md (entry-file + settings-overlay sections), architecture.md, nemo-xml-reference skill, README.md updated. Plan phases 6-7 marked implemented. Verified: nemo new --template basic scaffolds app.nemo + nemo.toml and validates --strict; settings overlay overrides theme at load (unit tests); legacy app.xml still validates; full suite (255+18+115) green; clippy + fmt clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements the runtime component creation plan — Layer 1: let handlers and scripts create and remove built-in component instances at runtime, via Rhai and
PluginContext.Apps that need structurally dynamic UI (form builders, list-of-records editors, conditionally-composed dashboards) no longer need to pre-declare every possible component and toggle
visible.Changes
Core API (
LayoutManager+PluginContext+RuntimeContext)Four new functions exposed to Rhai,
PluginContext, and WASM:LayoutManager(nemo-layout/src/manager.rs):insert_component(validates type via registry, rejects duplicate IDs, appends to parent children),remove_component(recursive subtree teardown with binding cleanup, root guard),set_properties(bulk update),generate_dynamic_id(__dyn_Nmonotonic counter). 12 unit tests.PluginContexttrait (nemo-plugin-api/src/lib.rs): 4 new methods with defaultErr(Unsupported)so existing plugin SDKs compile unchanged.RuntimeContext(nemo/src/runtime.rs): implements all 4 viatry_writelock +data_dirty/data_notifyre-render path. 6 integration tests.nemo-extension/src/rhai_engine.rs): registers the 4 functions inregister_context. 1 end-to-end test (click handler creates a label, appears in snapshot, remove cleans up).nemo-wasm-guest/wit/nemo-plugin.wit+nemo-wasm/src/host_impl.rs): 4 WIT host functions + delegating implementations.Knowledgebase updates
concepts/components.md— new "Runtime component creation" sectionconcepts/data-flow.md—create_componentnoted as re-render triggerpatterns/runtime-component-creation.md— new pattern docpatterns/index.md— links the new patternnemo-xml-referenceskill — documents the no-<binding>limitationSFC example demo (
examples/sfc)A fourth card with "Add label" / "Remove last" buttons backed by
scripts/handlers.rhai— demonstratescreate_componentandremove_componentat runtime. Validates clean withnemo validate --strict.Design decisions
<binding>support (by scope): dynamic components use explicit Rhai handlers or set props at creation time.ComponentStatesentries are lazily leaked on removal — keyed by ID, harmless, bounded by app churn. A follow-up would wireApp-side cleanup.BindingManager::unbind_componentalready existed — used directly instead of adding a newremove_for_componentmethod as the plan assumed.remove_componentrefuses to remove the component returned byroot_id().Test plan
LayoutManagerunit tests (insert with/no parent, unknown type, duplicate ID, recursive remove, root guard, binding cleanup, bulk update, dynamic ID)RuntimeContextintegration tests (create appears in snapshot, explicit ID, unknown type rejected, remove cleans up, root refused, bulk update)nemo validate --strict examples/sfc/app.xmlpasses