W7 done done

This commit is contained in:
root
2026-06-15 11:04:16 +00:00
parent 04745f39a8
commit 1743c51e46
33 changed files with 110 additions and 547 deletions
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
-147
View File
@@ -1,147 +0,0 @@
# React Developer Affordances Todo
## Objective
Add practical value for developers coming from React frameworks while preserving UCE's server-first C++ model. Defer component syntax/children work, avoid global head/assets/islands in the runtime, and focus on function-library data helpers, diagnostics, docs, examples, demos, and a starter-local router with starter-local asset/island components.
## Success Criteria
- [x] Function library has useful collection/data-shaping helpers with docs and tests.
- [x] Compile/runtime diagnostics are more helpful, especially for generated-code and common preprocessor mistakes.
- [x] Docs include a concise React/Next/Remix orientation guide.
- [x] Starter example uses a centralized hierarchical/file-based router in `index.uce` efficiently.
- [x] Starter-local asset/island affordances live as component handlers in the starter, not global runtime APIs.
- [x] Network tests and relevant build checks pass on `k-uce`.
## Current State
- Status: complete
- Last updated: 2026-05-28
- Source of truth: `/root/mount_ssh/k-uce-root-htdocs-uce`
- Runtime/live target: `k-uce:/Code/uce.openfu.com/uce`; rebuilt and restarted `uce.service` on `k-uce`.
## Goal Tree
Legend: `[ ]` not started, `[~]` in progress, `[x]` done, `[!]` blocked, `[-]` superseded
- [x] G1: Add collection/data helpers to function library
- Why: React-framework developers routinely shape arrays/objects near render code.
- Done when: helpers are declared, implemented, documented, and covered by tests.
- Verify: build plus focused site/network tests.
- [x] G1.1: Identify current `StringList`/`DValue` idioms and choose helper surface.
- [x] G1.2: Implement minimal high-value helpers without broad template complexity.
- [x] G1.3: Add docs and examples for helpers.
- [x] G1.4: Add/extend tests.
- [x] G2: Improve developer diagnostics
- Why: React frameworks win by making failures easy to act on.
- Done when: compile/runtime error output includes actionable context and docs mention debugging flow.
- Verify: intentional broken page surfaces improved message.
- [x] G2.1: Inspect current compiler/runtime error rendering path.
- [x] G2.2: Add source excerpt / generated path / common-hint text where appropriate.
- [x] G2.3: Document diagnostics.
- [x] G3: Add React/Next/Remix orientation docs
- Why: mapping familiar concepts reduces onboarding cost without adding syntax.
- Done when: docs page exists and is linked from docs/README/demo surfaces.
- Verify: docs page renders live.
- [x] G4: Starter-local router and starter affordances
- Why: User specifically wants hierarchical/file-based routing beautifully in starter `index.uce`.
- Done when: starter routes go through a central router in `index.uce`, and starter-local asset/island component handlers exist and are used where sensible.
- Verify: key starter routes render 200.
- [x] G4.1: Inspect current starter routing.
- [x] G4.2: Refactor to clear route table / hierarchical file resolution in `index.uce`.
- [x] G4.3: Add starter-local `COMPONENT:asset` / `COMPONENT:island` style handlers in one unit.
- [x] G4.4: Use them efficiently in starter pages/layout.
- [x] G5: Demos and examples
- Why: Affordances must be visible to developers, not hidden in APIs.
- Done when: docs/demo/tests expose examples.
- Verify: demo URLs return 200 and tests pass.
- [x] G6: Verification and project docs
- Done when: build/test commands are run on `k-uce`, project notes updated, and adversarial review completed.
## Execution Queue
Complete.
## Decisions
- 2026-05-28: Defer component children/slots and JSX-like preprocessor syntax.
- 2026-05-28: Do not add global runtime head/assets/islands APIs; implement asset/island as starter-local components.
- 2026-05-28: Do not add a generic runtime file-based-routing system; demonstrate hierarchical/file routing inside starter `index.uce`.
- 2026-05-28: Keep collection helpers explicit (`list_*`, `dv_*`) instead of overloading generic names such as `map`/`sort`.
## Assumptions
- Current source-of-truth mount is live-editable; runtime validation requires SSH to `k-uce`.
- Existing site tests are the right place for function-library coverage.
## Blockers and Risks
- No current blockers.
- Future risk: if UCE grows a full parser or component-tag syntax, keep this pass's explicit component/router APIs as a stable lower-level fallback.
## Evidence and Verification Log
- 2026-05-28: Created plan after reviewing README, preprocessor docs, and function library headers.
- 2026-05-28: `ssh k-uce 'cd /Code/uce.openfu.com/uce && bash scripts/build_linux.sh'` succeeded.
- 2026-05-28: Restarted `uce.service` on `k-uce`.
- 2026-05-28: `tests/run_network_tests.py --match core` passed.
- 2026-05-28: Manual checks returned `200` for `/examples/uce-starter/index.uce`, `?dashboard`, `?workspace/projects`, `?themes`, `/demo/collections.uce`, `/doc/index.uce?p=coming_from_react`, `/doc/index.uce?p=list_map`, and `/doc/index.uce?p=dv_group_by`.
- 2026-05-28: Full internal network suite passed, 25/25.
- 2026-05-28: Temporary broken `/tests/diagnostic-probe.uce` returned `500` with formatted `UCE compile error` diagnostics; source and cache artifacts were removed afterward.
## Change Log
- 2026-05-28: Created initial goal tree.
- 2026-05-28: Implemented helpers, diagnostics, docs, demo, starter router, starter-local web affordance components, tests, and validation.
## Follow-up Cleanup 2026-05-29
- Removed duplicate route cleanup from `starter_router_candidates()` because `app_make_route()` is the single normalization point for `l_path`.
- Weeded out nearby duplicate/obsolete starter code:
- `starter_router_add_candidate(...)` now owns repeated candidate tree construction.
- Removed unused `app_resolve_view()` / `starter_resolve_view()` after moving routing into starter `index.uce`.
- Removed unused legacy registered asset rendering functions from `lib/app.uce`; registered assets now render through `components/theme/web_affordances.uce`.
- `app_init()` now reuses `app_base_url(context)` instead of repeating base URL derivation.
- `web_affordances.uce` now uses one `starter_render_asset_group(...)` loop for CSS and JS.
- Verification: rebuilt on `k-uce`, restarted `uce.service`, checked key starter routes, and ran `tests/run_network_tests.py --match core` successfully.
## Follow-up Routed Views 2026-05-29
- Changed starter route dispatch from `unit_render(...)` to `component(...)`.
- Converted all routed `site/examples/uce-starter/views/*.uce` files to `COMPONENT(Request& context)` rather than `RENDER(Request& context)` because they are central-router-only views.
- Updated the starter README and verified key starter routes. No service restart was required because only `.uce`/docs changed.
## Follow-up Canonical Starter URLs 2026-05-29
- Canonicalized starter self-links from `/examples/uce-starter/index.uce?...` to `/examples/uce-starter/?...` with `app_canonical_script_url(...)`.
- Updated the starter README to show canonical directory URLs.
- Touched the starter front controller to force the `#load`ed helper change into the cached generated unit.
- Verified canonical/direct starter routes and checked generated self-links. No service restart was required.
## Follow-up Not Found Component 2026-05-29
- Moved `starter_router_render_not_found` markup into `components/basic/notfound.uce`.
- Router now delegates 404 body rendering through `component("components/basic/notfound", props, context)`.
- Verified missing routes return `404` and normal dashboard route returns `200`. No service restart required.
## Follow-up Page Shell Component 2026-05-29
- Moved app page rendering into `themes/page.uce` as a component.
- Removed `app_render_page`, `app_theme_page_component`, and `starter_render_page` from `lib/app.uce`.
- Page template resolution now follows `context.call["app"]["page_type"]`: current theme first, common fallback second.
- Verified representative HTML routes and the JSON page-type fallback. No service restart required.
## Follow-up Deep Starter Context Cleanup 2026-05-29
- Removed repeated `starter_boot(context)` calls; root `index.uce` is the boot point.
- Removed `context.call["starter"]` duplicated state and JSON side-channel state.
- JSON routes now use only `context.call["app"]["page_type"]` plus normal captured output.
- Simplified `themes/page.uce` and `themes/common/page.json.uce` accordingly.
- Replaced `starter_*` alias helper usage with direct `app_*` helpers and removed alias wrappers except `StarterUser`.
- Verified key routes and core tests. No service restart required.
## Follow-up Route Context Flattening 2026-05-29
- Flattened `context.call["app"]["route"]` to `context.call["route"]`.
- Moved former `context.call["app"]["router"]` metadata into `context.call["route"]`.
- Verified representative starter HTML, 404, and JSON routes. No service restart required.
@@ -1,104 +0,0 @@
[
{
"backend": "native",
"target": "template-heavy-doc",
"url": "/doc/singlepage.uce",
"ok": true,
"status": 200,
"samples_ms": [
318.72209906578064,
340.9293442964554,
319.705568253994,
324.0259513258934,
350.93583166599274,
313.20811808109283,
317.0944079756737,
310.312956571579,
307.89367109537125,
308.1864267587662,
311.681292951107,
313.39504569768906,
314.3857270479202,
312.5988021492958,
313.25943768024445,
312.9996135830879,
334.4864323735237,
314.2518773674965,
309.7490146756172,
317.4726217985153
],
"median_ms": 313.8234615325928,
"mean_ms": 318.2647120207548,
"min_ms": 307.89367109537125,
"max_ms": 350.93583166599274,
"note": ""
},
{
"backend": "native",
"target": "sqlite-page",
"url": "/demo/sqlite.uce",
"ok": true,
"status": 200,
"samples_ms": [
3.4144148230552673,
6.088584661483765,
3.463640809059143,
3.498159348964691,
3.5023540258407593,
6.214611232280731,
3.563329577445984,
3.3720433712005615,
3.330707550048828,
3.3808723092079163,
3.322914242744446,
3.3655911684036255,
3.374151885509491,
3.358304500579834,
3.3307820558547974,
3.268897533416748,
3.360658884048462,
3.3061057329177856,
6.233863532543182,
3.432638943195343
],
"median_ms": 3.3775120973587036,
"mean_ms": 3.809131309390068,
"min_ms": 3.268897533416748,
"max_ms": 6.233863532543182,
"note": ""
},
{
"backend": "native",
"target": "component-heavy-starter",
"url": "/examples/uce-starter/?dashboard",
"ok": true,
"status": 200,
"samples_ms": [
74.01428371667862,
40.9795418381691,
40.995217859745026,
41.04568809270859,
40.724173188209534,
41.10313206911087,
40.59913754463196,
41.28593951463699,
41.10439121723175,
40.649913251399994,
42.06441342830658,
42.074643075466156,
43.475523591041565,
41.8255478143692,
41.068583726882935,
41.116394102573395,
41.09777510166168,
42.58237034082413,
41.312023997306824,
70.98117470741272
],
"median_ms": 41.11039265990257,
"mean_ms": 44.50499340891838,
"min_ms": 40.59913754463196,
"max_ms": 74.01428371667862,
"note": ""
}
]
@@ -1,19 +0,0 @@
# Phase 5 native baseline — 2026-06-12
Host: `k-uce` / `uce.openfu.com` via localhost with Host header.
This is a durable informational snapshot. The Phase 5 gate recomputes native medians during paired native/WASM runs; these numbers are not hard-coded budgets.
- Warmup suite: 82/82 passed; excludes `site tests tasks` to avoid perturbing task lifecycle state.
- Measured full network suite: 83/83 passed.
- Measured starter subset: 14/14 passed.
- Static audit default scan: 50 code findings, documentation prose excluded by default.
| target | median ms | mean ms | samples |
|---|---:|---:|---:|
| template-heavy-doc | 313.8 | 318.3 | 20 |
| sqlite-page | 3.4 | 3.8 | 20 |
| component-heavy-starter | 41.1 | 44.5 | 20 |
Raw benchmark JSON: `native-baseline-2026-06-12.json`.
Static audit snapshot: `site-static-audit-2026-06-12.md`.
@@ -1,56 +0,0 @@
# Phase 5 site static-state audit
Findings: 50 code, 0 documentation prose.
| file | line | severity | kind | code | note |
|---|---:|---|---|---|---|
| site/demo/index.uce | 76 | code | background task | `<? if(allow_server_demos) { render_card("task_repeat.uce", "Task Repeat", "Recurring task scheduling"); } ?>` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/once-init.uce | 1 | code | static local/global | `static s64 demo_worker_init_count = 0;` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/demo/once-init.uce | 2 | code | static local/global | `static s64 demo_component_hits = 0;` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/demo/once-init.uce | 4 | code | init hook | `INIT(Request& context)` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/demo/once-init.uce | 10 | code | once hook | `ONCE(Request& context)` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/demo/once-init.uce | 44 | code | once hook | `ONCE() and INIT()` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/demo/once-init.uce | 44 | code | init hook | `ONCE() and INIT()` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/demo/once-init.uce | 47 | code | once hook | `This page calls the same named component twice. `ONCE()` should only run once for the request, while `INIT()` should stay stable for the currently loaded worker copy.` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/demo/once-init.uce | 47 | code | init hook | `This page calls the same named component twice. `ONCE()` should only run once for the request, while `INIT()` should stay stable for the currently loaded worker copy.` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/demo/task-status.uce | 11 | code | background task | `String task_name = first(context.get["task-name"], "example-task");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task-status.uce | 13 | code | background task | `print("Task Name: ", task_name, "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task-status.uce | 14 | code | background task | `print("Task ID: ", task_pid(task_name), "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task-status.uce | 15 | code | background task | `print("Task Running: ", task_pid(task_name) == 0 ? "no" : "yes", "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 13 | code | background task | `String task_name = first(context.get["task-name"], "example-task");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 37 | code | background task | `<input type="text" name="task-name" value="<?= task_name ?>"/>` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 46 | code | background task | `load(document.getElementById('task-status'), 'task-status.uce?task-name=<?= uri_encode(task_name) ?>');` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 56 | code | background task | `print("Task Name: ", task_name, "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 57 | code | background task | `print("Task ID: ", task_pid(task_name), "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 58 | code | background task | `print("Task Running: ", task_pid(task_name) == 0 ? "no" : "yes", "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task.uce | 71 | code | background task | `print("New Task ID: ", task(task_name, []() {` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 13 | code | background task | `String task_name = first(context.get["task-name"], "example-task");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 37 | code | background task | `<input type="text" name="task-name" value="<?= task_name ?>"/>` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 46 | code | background task | `load(document.getElementById('task-status'), 'task-status.uce?task-name=<?= uri_encode(task_name) ?>');` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 56 | code | background task | `print("Task Name: ", task_name, "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 57 | code | background task | `print("Task ID: ", task_pid(task_name), "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 58 | code | background task | `print("Task Running: ", task_pid(task_name) == 0 ? "no" : "yes", "\n");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/demo/task_repeat.uce | 71 | code | background task | `print("New Task ID: ", task_repeat(task_name, 5, []() {` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/examples/uce-starter/components/data/widgets.uce | 3 | code | once hook | `ONCE(Request& context)` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/examples/uce-starter/components/workspace/primitives.uce | 3 | code | once hook | `ONCE(Request& context)` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/examples/uce-starter/lib/user.class.h | 13 | code | static local/global | `static String session_key()` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/examples/uce-starter/lib/user.class.h | 18 | code | static local/global | `static String normalize_email(String email)` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/examples/uce-starter/lib/user.class.h | 23 | code | static local/global | `static String hash_id(String raw)` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/examples/uce-starter/lib/user.class.h | 45 | code | static local/global | `static String password_hash(String password, String salt)` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/examples/uce-starter/lib/user.class.h | 53 | code | static local/global | `static DValue read_json_file(String file_name)` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/examples/uce-starter/lib/user.class.h | 64 | code | static local/global | `static bool write_json_file(String file_name, DValue data)` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/examples/uce-starter/views/dashboard.uce | 3 | code | once hook | `ONCE(Request& context)` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/info/index.uce | 269 | code | static local/global | `<li>nginx serves static files directly from `site/`</li>` | Check whether state is request-local, immutable, or intentionally persistent; unit statics reset per wasm workspace. |
| site/tests/preprocessor.uce | 3 | code | once hook | `ONCE(Request& context)` | Audit behavior under per-request wasm workspaces; ONCE/INIT may need host-side cache semantics if used for cross-request state. |
| site/tests/tasks.uce | 25 | code | background task | `pid_t repeat_existing = task_pid("site-tests-repeat");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 27 | code | background task | `task_kill(repeat_existing, 15);` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 37 | code | background task | `repeat_pid = task_repeat("site-tests-repeat", 1.0, []() {` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 55 | code | background task | `pid_t seen_short_pid = task_pid("site-tests-short");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 56 | code | background task | `pid_t seen_repeat_pid = task_pid("site-tests-repeat");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 57 | code | background task | `pid_t seen_timeout_pid = task_pid("site-tests-timeout");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 58 | code | background task | `pid_t seen_unsafe_key_pid = task_pid("site-tests/../unsafe key");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 59 | code | background task | `int short_alive = seen_short_pid == 0 ? -1 : task_kill(seen_short_pid, 0);` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 60 | code | background task | `int repeat_alive = seen_repeat_pid == 0 ? -1 : task_kill(seen_repeat_pid, 0);` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 72 | code | background task | `check("task_pid() + task_kill(pid, 0)", short_alive == 0, "kill(0) result=" + std::to_string(short_alive));` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 73 | code | background task | `check("task_repeat()", repeat_pid != 0 && seen_repeat_pid != 0, "started=" + std::to_string(repeat_pid) + " seen=" + std::to_string(seen_repeat_pid));` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
| site/tests/tasks.uce | 77 | code | background task | `check("task_kill() rejects negative pid", task_kill(-1, 0) == -1, "kill(-1, 0) rejected");` | Task APIs cross request lifetimes; verify they are host handles, not guest statics. |
-197
View File
@@ -1,197 +0,0 @@
# Phase 0 findings — toolchain & runtime spike
- **Status: EXIT CRITERION PASSED** (2026-06-12, on k-uce)
- **Runtime selected: Wasmtime** (v45.0.1, C API). WAMR rejected — evidence below.
- Exceptions decision (§11.1, error codes / `-fno-exceptions`) **confirmed viable**:
both the stubs and two real generated units compile with `-fno-exceptions`,
no try/catch blocker anywhere.
The exit criterion ran end-to-end: a core stub (libc/libc++ statically linked,
owns memory/allocator) and a unit stub (PIC, `dylink.0`) were linked **at
runtime** by `loader.cpp` and produced:
```
hello from unit; unit-data-segment-ok; counter=7; mapsum=3; core-string+unit[cb:42][got-func-ok][fn:42]
core_counter (in linear memory): before=7 after=8
PHASE0 EXIT CRITERION: PASS
```
which validates, in one render call: unit data-segment relocation
(`__memory_base`), GOT.mem read **and write** of a core global, `std::string`
/`std::map` in unit code on the core's heap, a heap C++ object created in core
and mutated/read by the unit, function pointers crossing unit→core→unit
through the shared table, GOT.func resolution, and a `std::function` lambda
allocated in the unit and invoked by core. That is the §3.4 contract
("DValue inside the workspace: no serialization, ever") demonstrated at the
ABI level.
## Toolchain pins
| What | Version | Where on k-uce |
|---|---|---|
| wasi-sdk | 33 (clang 22.1.0-wasi-sdk) | `/opt/wasi-sdk` |
| target triple | `wasm32-wasip1` (`wasm32-wasi` is deprecated) | — |
| Wasmtime C API | v45.0.1 (prebuilt x86_64-linux release) | `/opt/wasmtime` |
| WAMR (rejected) | WAMR-2.4.4, built from source | `/opt/wamr` |
| cmake / ninja | 3.31.6 / 1.12.1 (apt) | — |
## Runtime selection: why not WAMR
WAMR was tried first per §9 ("preferred ... use Wasmtime only if blocked").
We are blocked, on the load-bearing requirement itself:
1. **WAMR's wasm-c-api ignores imported memories and tables.** At unit
instantiation it logs `"doesn't support import memories and tables for
now, ignore them"` (`wasm_c_api.c`) and gives the instance its own
memory/table — which silently destroys the shared-workspace model.
2. **Host-side `wasm_table_grow` / `wasm_memory_grow` are explicitly
unsupported** ("Only allow growing a table via the opcode table.grow").
3. Its build banner lists *Import/Export of Mutable Globals* as unsupported —
the dylink ABI imports `__stack_pointer` and every `GOT.*` entry as a
mutable global.
Its multi-module feature is name-based auto-resolution, not host-orchestrated
dylink (no host-computed `__memory_base`/`__table_base`, no GOT). Making WAMR
fit means implementing import binding through the c-api layer and runtime
internals — a runtime-development project, not a patch.
**Wasmtime v45.0.1 passed everything on the first run** through the standard
`wasm.h` C API: host-created funcref table shared by both instances, exported
memory imported by the unit, host-created (mutable) globals, cross-instance
export→import wiring. The remaining §9 criteria also favor it: AOT artifacts
(`.cwasm` precompilation) for the unit cache, epoch interruption for CPU
limits, and built-in copy-on-write memory-image instantiation for the §6 core
snapshot (machinery we'd have had to build ourselves on WAMR).
Trade-off accepted (was already in §10): Rust codebase, heavier to
vendor/patch. Pin the release artifact (lib + headers, checksummed) the way
sqlite is vendored; building from source stays possible but is not the
default path. The C API .so is ~27 MB.
## Module build recipe (what `build_modules.sh` settled on)
Core (non-PIC reactor, owns libc/libc++/allocator):
```
clang++ --target=wasm32-wasip1 -mexec-model=reactor -O1 -fno-exceptions \
core.cpp -o core.wasm \
-Wl,--export-all -Wl,--import-table \
-Wl,--export=__stack_pointer -Wl,--export=__heap_base \
-Wl,--undefined=_ZTVN10__cxxabiv117__class_type_infoE
```
Unit (PIC side module):
```
clang++ --target=wasm32-wasip1 -fPIC -fvisibility=default \
-fvisibility-inlines-hidden -O1 -fno-exceptions -c unit.cpp
wasm-ld -shared --experimental-pic --unresolved-symbols=import-dynamic \
--Bsymbolic unit.o -o unit.wasm --export=<entry>
```
Hard-won flag findings:
1. **`--unresolved-symbols=import-dynamic`** is required for the side-module
link; undefined symbols then become `env.*` function imports and `GOT.*`
globals exactly per the Emscripten dylink ABI.
2. **`-fvisibility-inlines-hidden` is mandatory.** Without it one libc++
vague-linkage lambda (`std::map` tree-emplace internals, missing libc++'s
usual hide-from-ABI attribute) is emitted as *both* an export and an
import of the unit — a self-import the loader cannot satisfy at
instantiation time without lazy-binding trampolines. `--Bsymbolic` alone
did **not** bind it.
3. **Core symbol closure**: `--export-all` only exports what got *linked*.
The unit needed `__cxxabiv1::__class_type_info`'s vtable (RTTI machinery
behind `std::function`), which the core never references — forced in with
`--undefined=`. The production core needs a closure strategy:
`--whole-archive` for libc/libc++/libc++abi, or a curated keep-list. The
loader also implements the complementary fallback (resolve `GOT.mem` of
weak data from the unit's *own* exports post-instantiation, patching the
provisional GOT global).
4. **`--import-table` on the core** + a **host-created table** is the right
shape (see loader notes); `--export-table`/`--growable-table` was the
first attempt and died on WAMR's host-grow limitation, but host-created
stays the better design under Wasmtime too: the loader picks table size
(core's declared minimum + headroom) before any instantiation.
5. `wasm32-wasi` triple is deprecated in wasi-sdk 33 → use `wasm32-wasip1`.
## Loader notes (`loader.cpp`, ~450 lines, standard wasm-c-api)
Sequence proven: instantiate core (45 WASI imports satisfied with named trap
stubs — none was ever called) → parse `dylink.0` (`mem_info`: memsize/align,
tablesize) → `__memory_base` = call core's exported `malloc``__table_base`
= bump pointer starting at core's table-import minimum → build the unit's
import vector (memory/table/`__stack_pointer` shared from core; `env.*`
functions from core exports; `GOT.mem.*` as host mutable i32 globals holding
addresses read from core's exported data-symbol globals) → instantiate →
patch deferred GOT entries → `__wasm_apply_data_relocs``__wasm_call_ctors`
→ call the entry export.
- **Erratum (found in Phase 3): self-resolved `GOT.mem` values must add
`__memory_base`.** A PIC module's exported data symbols are i32 globals
holding offsets *relative to its `__memory_base`*, not absolute addresses —
the linker adds the base when patching deferred GOT entries (there is no
`__wasm_apply_global_relocs` export to do it). Copying the export verbatim
reads/writes core memory at low addresses and renders silently wrong values;
the Phase 3 fixture's `self-got`/`callback` markers exist to catch exactly
this. `GOT.mem` entries resolved from the *core's* exports are absolute
already (the core is non-PIC) and need no adjustment.
- **GOT.func is resolved guest-side**: the core exports a helper returning
`(intptr_t)&func` — taking the address forces a link-time elem entry and a
wasm function pointer *is* its table index. No host-side funcref injection
is needed at all (it was WAMR-unsupported; under Wasmtime it would work but
the guest-side registry is simpler and runtime-agnostic). The production
core should carry a name→funcptr registry (dlsym-shaped) for its API
surface.
- **wasi-libc gotcha**: `_initialize` has a double-init guard ending in
`__builtin_trap()`. WAMR runs `_initialize` automatically at instantiation
(so calling it again traps "unreachable"); Wasmtime does not (so you must
call it). Cost one debugging round; recorded here so it never costs another.
- Export-name `wasm_name_t` may include the trailing NUL in `size` (WAMR
did); trim when indexing exports by name.
## Real generated units (delegated grind — full log in `realunit-report.md`)
`site/demo/collections.uce.cpp` and `hello.uce.cpp` (taken verbatim from the
live unit cache at `/tmp/uce/work/...`) both compile and link as PIC side
modules with `dylink.0`, **no allocator definitions**, with only shim-level
intervention. `collections.wasm`: 42 KB, 52 imports — including exactly the
predicted `GOT.mem.context` for the global `Request*`. Friction points that
become Phase 2 work items:
1. **`types.h` defines global `operator new/delete` in every unit** — must be
gated (`#ifdef`) out of side-module builds; allocator ownership belongs to
the core (§3.2: "the one fatal misconfiguration").
2. **`sys.h` includes `<signal.h>`** → wasi needs `-D_WASI_EMULATED_SIGNAL`
(+ `-lwasi-emulated-signal` in the core) or an `#ifdef __wasm__` carve-out;
signals/fork/exec/sockets in `sys.h` have no wasi equivalent and move
behind hostcalls anyway (§5.1).
3. **Generated units include `uce_lib.h` by absolute path** — the
preprocessor should emit a logical include so the wasm build can supply
its own include order.
4. **Header-inline connector wrappers (MySQL etc.) get pulled into every
unit** regardless of use; the §3.3 membrane split (thin wasm-side shim,
host-side implementation) resolves this and shrinks unit import lists.
5. The real-unit compile used the pre-`-fvisibility-inlines-hidden` flag set
(parallel work); the final unit flag set above should be used from
Phase 2 on.
## Implications for the next phases
- **Phase 1 (DValue C ABI, native)**: unaffected by any of this; proceed as
written.
- **Phase 2 (core module + membrane)**: add the closure strategy
(whole-archive), the GOT.func name→funcptr registry, the `types.h`
allocator gate, the signal emulation define, and the preprocessor include
change. Compile `uce_lib` with the core recipe above.
- **Phase 3 (loader)**: `loader.cpp` here is the skeleton — dylink parsing,
base allocation, GOT resolution, and init sequencing are all proven; what
remains is the registry/dispatch layer, ABI stamping, and multi-unit
placement.
- **Phase 4**: use Wasmtime's epoch interruption for CPU limits and its
memory-image/CoW instantiation for the core snapshot rather than building
either by hand.
## Artifacts (on k-uce, not in git)
- `/tmp/uce/wasm-phase0/{core.wasm,unit.wasm,loader}` — exit-criterion run
- `/tmp/uce/wasm-phase0/realunit/` — real-unit compiles + shim tree + inspector dumps
- `/opt/wasi-sdk`, `/opt/wasmtime`, `/opt/wamr` — toolchains/runtimes
+1 -1
View File
@@ -32,7 +32,7 @@ For a normal direct page request, `context.props` starts empty.
If the page is invoked from another UCE file via `unit_render(file_name, context)`, the callee receives that same `context`.
Pages intended to serve WebSocket traffic may expose both `RENDER(Request& context)` and `WS(Request& context)`. Files may also define `COMPONENT()` handlers when they intentionally need both page and component behavior in one unit.
Pages that serve WebSocket traffic may expose both `RENDER(Request& context)` and `WS(Request& context)`. Files may also define `COMPONENT()` handlers when one unit needs both page and component behavior.
In that case:
+30
View File
@@ -0,0 +1,30 @@
:sig
String base64_decode(String raw, bool& ok)
:params
raw : Base64 encoded string
ok : set to `true` when decoding succeeds; set to `false` for invalid input
return value : decoded binary-safe string, or an empty string when decoding fails
:see
>string
base64_encode
:content
Decodes a Base64 string.
Pass a `bool` variable for `ok` so callers can distinguish invalid input from a valid empty decoded value.
Example:
```uce
bool ok = false;
String decoded = base64_decode("aGVsbG8=", ok);
// ok == true
// decoded == "hello"
```
Related:
- PHP: `base64_decode($value, true)`
- JavaScript / Node.js: `Buffer.from(value, "base64")`
+27
View File
@@ -0,0 +1,27 @@
:sig
String base64_encode(String raw)
:params
raw : binary-safe source string
return value : Base64 encoded string
:see
>string
base64_decode
:content
Encodes a string with Base64.
UCE strings can contain binary data, so `raw` may include NUL bytes and non-text bytes.
Example:
```uce
String encoded = base64_encode("hello");
// encoded == "aGVsbG8="
```
Related:
- PHP: `base64_encode()`
- JavaScript / Node.js: `Buffer.from(value).toString("base64")`
+4 -4
View File
@@ -27,7 +27,7 @@ UCE is server-first C++ with a small template preprocessor. It does not try to b
- `ONCE(Request& context)` is per-request setup for a unit before its first render/component entry.
- `INIT(Request& context)` is worker-local setup when a unit is loaded.
- `<?= expression ?>` is escaped interpolation; prefer it for user-visible text.
- `<?: expression ?>` is trusted raw markup output, closer to a deliberate `dangerouslySetInnerHTML` decision.
- `<?: expression ?>` writes trusted raw markup, similar to `dangerouslySetInnerHTML` in React.
- `unit_render()` renders another page unit; `component()` returns component HTML as a string.
## Routes and Layouts
@@ -51,11 +51,11 @@ DValue app_items = dv_filter(menu, [](DValue item, String key) { return(item["se
DValue by_section = dv_group_by(menu, [](DValue item, String key) { return(item["section"].to_string()); });
```
Use these when the transformation communicates intent. Prefer explicit loops when side effects or multi-step validation are the main concern.
Use these when a short transformation is clearer than a loop. Prefer explicit loops for side effects or multi-step validation.
## Assets and Islands
Global runtime APIs for assets and islands are intentionally not part of UCE core. The starter emits CSS and JavaScript from the owning unit's `ONCE(Request& context)` hook, with a few shared sibling asset components when multiple components need the same files. The only starter web-affordance helper left is `COMPONENT:island` in `components/theme/web_affordances.uce` for small progressive-enhancement modules. This keeps app policy in the app without an asset registry layer.
UCE core does not provide a global asset or island registry. The starter emits CSS and JavaScript from the owning unit's `ONCE(Request& context)` hook, with a few shared sibling asset components when multiple components need the same files. The starter's `COMPONENT:island` helper in `components/theme/web_affordances.uce` covers small progressive-enhancement modules while keeping app policy in the app.
## Debugging
@@ -66,4 +66,4 @@ When a unit fails to compile, UCE reports the source path, generated C++ path, c
- No client-side virtual DOM is built into UCE.
- No global file-router is imposed by the runtime.
- No JSX-like component tags are required for this workflow.
- Component children/slot syntax is intentionally deferred; use explicit props and component calls for now.
- Component children/slot syntax is not part of UCE yet; use explicit props and component calls for now.
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Keeps children for which f returns true. List-like input stays list-like.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
DValue visible = dv_filter(items, [](DValue item, String key) { return(item["hidden"].to_bool() == false); });
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Groups children into list-like buckets by the string returned from f.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
DValue by_section = dv_group_by(menu, [](DValue item, String key) { return(item["section"].to_string()); });
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns map keys from a DValue. Scalar values produce an empty list.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
StringList keys = dv_keys(context.cfg["menu"]);
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Transforms each child. List-like input stays list-like; map input keeps keys.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
DValue titles = dv_map(items, [](DValue item, String key) { DValue out; out = item["title"].to_string(); return(out); });
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Copies a DValue map except for selected keys.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
DValue safe_user = dv_omit(user, {"password_hash"});
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Copies only selected keys from a DValue map.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
DValue public_user = dv_pick(user, {"name", "avatar"});
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns child values as a list-like DValue.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
DValue menu_items = dv_values(context.cfg["menu"]);
+1 -1
View File
@@ -4,7 +4,7 @@ vector<T> filter(vector<T> items, function<bool (T)> f)
:params
items : list of items to be filtered
f : a function that decides which items should be in the new list
f : predicate function; items are kept when this returns `true`
return value : a new list
:see
+1 -1
View File
@@ -27,7 +27,7 @@ items["custom"] = "x";
// items.is_list() == false, items.is_array() == true
```
`dv_map()` and `dv_filter()` use this distinction to decide whether results re-index from zero or keep their original keys.
`dv_map()` and `dv_filter()` use this distinction: list inputs re-index from zero, while map inputs keep their original keys.
## Related Concepts
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns true when every item matches.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
bool all_named = list_every(routes, [](String s) { return(s != ""); });
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns the first matching item or fallback.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
String route = list_find(routes, [](String s) { return(str_starts_with(s, "dashboard")); }, "index");
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns true when any item matches.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
bool has_dashboard = list_some(routes, [](String s) { return(s == "dashboard"); });
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns a sorted copy of the list.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
auto sorted = list_sort(tags);
+1 -1
View File
@@ -12,7 +12,7 @@ filter
:content
Returns the first occurrence of each string, preserving input order.
These helpers are intentionally small data-shaping conveniences for render code, routers, and configuration trees. They are useful when porting habits from React/Next/Remix code where lists of routes, navigation items, cards, or records are transformed close to the rendering boundary.
These helpers keep common data-shaping code close to render code, routers, and configuration trees. They are useful for route lists, navigation items, cards, and records that need simple transformations before rendering.
```cpp
auto tags = list_unique({"uce", "docs", "uce"});
+29
View File
@@ -0,0 +1,29 @@
:sig
StringMap split_kv(String s, char separator = '=', bool trim_whitespace = true, bool uppercase_keys = false)
:params
s : input containing one key/value pair per line
separator : character separating each key from its value
trim_whitespace : trim keys and values when true
uppercase_keys : uppercase keys when true
return value : map of parsed keys and values
:see
>string
split
split_http_headers
:content
Parses simple line-based key/value text into a `StringMap`.
Each non-empty line is split on the first `separator`. Lines without the separator are kept with an empty value.
Example:
```uce
StringMap cfg = split_kv("host = localhost\nport = 8080");
// cfg["host"] == "localhost"
// cfg["port"] == "8080"
```
This is useful for small config files, metadata blocks, and tests that need predictable key/value parsing.
+1 -1
View File
@@ -20,7 +20,7 @@ If a process with the same `key` is already running anywhere in the runtime inst
Task keys may contain ordinary user-facing text. UCE hashes the key before using it as an internal lock/status filename so slashes and other path-like characters cannot escape the task state directory.
`timeout` is enforced in the child process with an alarm. The default is ten minutes. Pass `0` only for tasks that are intentionally unbounded and have their own shutdown path.
`timeout` is enforced in the child process with an alarm. The default is ten minutes. Pass `0` only for tasks that have their own shutdown path.
Related:
+1 -1
View File
@@ -21,7 +21,7 @@ Starts a repeating background worker process.
If a process with the same `key` is already running anywhere in the runtime instance, `task_repeat()` does not start a second worker and instead returns the PID of the existing one. Coordination is through the same shared task state used by `task()`.
`timeout` bounds the lifetime of the repeating worker. The default is ten minutes. Pass `0` only for intentionally unbounded workers with another shutdown path.
`timeout` bounds the lifetime of the repeating worker. The default is ten minutes. Pass `0` only for workers that have another shutdown path.
Related:
+1 -1
View File
@@ -12,7 +12,7 @@ Returns the payload of the current WebSocket message being handled by `WS(Reques
For text frames this is the decoded text payload. For binary frames this `String` contains the raw message bytes.
Use `ws_is_binary()` or `ws_opcode()` to decide how the payload should be interpreted.
Use `ws_is_binary()` or `ws_opcode()` to choose how to parse the payload.
Related:
+1 -1
View File
@@ -15,7 +15,7 @@ String
:content
Parses a simple XML document into a structured `DValue`.
`xml_decode()` is intentionally small. It does not validate schemas, DTDs, namespaces, or document types. It parses the first root element and returns the same structural element shape accepted by `xml_encode()`.
`xml_decode()` parses a compact XML subset. It does not validate schemas, DTDs, namespaces, or document types. It parses the first root element and returns the same structural element shape accepted by `xml_encode()`.
Try the live example in the [XML demo](../demo/xml.uce).
+1 -1
View File
@@ -15,7 +15,7 @@ xml_decode
:content
Parses a practical YAML subset into a `DValue`.
`yaml_decode()` is designed for concise UCE config files. It intentionally avoids full YAML schema behavior and does not support anchors, aliases, tags, directives, or complex inline collection syntax.
`yaml_decode()` is designed for concise UCE config files. It supports a practical YAML subset and does not implement anchors, aliases, tags, directives, or complex inline collection syntax.
Try the live example in the [YAML demo](../demo/yaml.uce).