diff --git a/README.md b/README.md
index e7260d6..b2ac4f0 100644
--- a/README.md
+++ b/README.md
@@ -4,17 +4,120 @@
This is in the early stages of development. Don't use this for anything important (or at all)!
-## Udo's C++ Entry Points
+## Overview
-The aim of this project is to make a PHP-like runtime that enables server-side "scripting" using C/C++. At the core is a multi-worker FastCGI server that can be talked to from Nginx or similar front-end servers. UCE has a shared-nothing isolated architecture to serve page requests. To minimize the potential for memory leaks, UCE uses a per-request memory arena. UCE also provides a PHP-like API to ease web development. UCE aims to use minimal dependencies (at the moment, the only dependency is the Clang compiler). UCE pages are automatically recompiled and dynamically reloaded as necessary.
+UCE is a PHP-inspired server-side runtime that lets you build web pages and handlers in C++ using a small `.uce` preprocessor plus a FastCGI application server.
+
+- `.uce` pages compile to shared objects on demand
+- normal HTTP pages expose `RENDER(Request& context)`
+- WebSocket pages can additionally expose `WS(Request& context)`
+- sub-rendering and components pass structured data through `context.call`
+- nginx can forward normal `.uce` requests and ordinary `.ws.uce` page loads to the FastCGI socket, while real WebSocket upgrade requests for `.ws.uce` endpoints go to the built-in HTTP/WebSocket listener
+- the nginx-published application tree now lives under `site/`
+- you can include C++ code as much as you want, but only .uce files called via API functions and entry points will be pre-processed
+- the preprocessor has two jobs:
+ - allow for inline HTML within C++ and the use of templating tags inside of that HTML
+ - convenience directive and macro parsing so UCE files don't need a lot of boiler plate
+
+The abolition of boilerplate was a major design factor, resulting in a page as small as this:
+
+```uce
+RENDER(Request& context)
+{
+ <>hello world>
+}
+```
+
+The runtime is still experimental.
+
+## Build
+
+Build the runtime with:
+
+```bash
+bash scripts/build_linux.sh
+```
+
+The current build expects:
+
+- `clang++`
+- `mysql_config`
+- standard Linux development headers for `dl`, `pthread`, sockets, and backtrace support
+
+The binary is written to:
+
+```bash
+bin/uce_fastcgi.linux.bin
+```
+
+## Runtime Model
+
+UCE pages now use explicit request handlers instead of implicit globals:
+
+- `RENDER(Request& context)` for normal HTTP rendering
+- `WS(Request& context)` for inbound WebSocket messages
+
+Useful related runtime patterns:
+
+- `render_file(String file_name)` or `render_file(String file_name, Request& context)` to invoke another page
+- `context.call` for invocation or message-local structured input
+- `context.connection` for broker-owned per-WebSocket-connection state shared across `WS(Request& context)` calls
+- `context.params`, `context.get`, `context.post`, `context.cookies`, `context.session`, and `context.header` for request/response state
+
+Named component-style render handlers are also supported:
+
+```cpp
+RENDER:BODY(Request& context)
+{
+ <>
+
= context.call["body"].to_string() ?>
+ >
+}
+```
+
+Those are intended for sub-rendering through helpers such as `component("components/card:BODY", props, context)` rather than direct page entry.
+
+## Template Output
+
+Inside `<> ... >` literal blocks, UCE supports three inline forms:
+
+- ` ... ?>` to emit raw C++ statements
+- `= expression ?>` to print HTML-escaped output
+- `` to print unescaped output
+
+Use `= ... ?>` by default for user-visible text. Use `` only for trusted markup or content that has already been escaped.
+
+## Components
+
+UCE includes a native component layer built on top of ordinary `.uce` files:
+
+- `component(name[, props[, context]])`
+- `render_component(name[, props[, context]])`
+- `component_exists(name)`
+- `component_resolve(name)`
+
+Component props are passed through `context.call`.
+
+Component names resolve:
+
+1. as the exact file name you supplied
+2. as that same name plus `.uce`
+3. as those same two forms under `components/`
+
+When you want returned component markup inside a literal block, prefer:
+
+```cpp
+<>
+
+>
+```
+
+because `= ... ?>` HTML-escapes the returned markup. For direct output from C++ code, use `render_component(...)`.
+
+Components may expose additional named handlers with `RENDER:NAME(Request& context)`. The default handler remains the ordinary `RENDER(Request& context)`.
## WebSockets
-WebSocket-enabled pages can now expose both:
-
-- `RENDER() { }` for normal HTTP rendering
-- `WS() { }` for inbound WebSocket messages on the same `.ws.uce` page
-
The runtime keeps the socket lifecycle in-process and exposes a low-boilerplate API to page code:
- `ws_message()`
@@ -24,1653 +127,318 @@ The runtime keeps the socket lifecycle in-process and exposes a low-boilerplate
- `ws_is_binary()`
- `ws_connections([scope])`
- `ws_connection_count([scope])`
-- `ws_send(message[, scope])`
-- `ws_broadcast(message[, scope])`
-- `ws_send_to(connection_id, message)`
+- `ws_send(message[, binary[, scope]])`
+- `ws_send_to(connection_id, message[, binary])`
- `ws_close([connection_id])`
-By default, the WebSocket scope is the current page file, so `ws_send()` broadcasts to other clients connected to that same `.ws.uce` endpoint.
+By default, the WebSocket scope is the current page file, so `ws_send()` queues a message for clients connected to that same `.ws.uce` endpoint.
+
+Each live WebSocket connection now owns a broker-side `DTree` exposed to page code as `context.connection`. Mutations to that tree persist for the life of the socket and are visible on later `WS(Request& context)` calls for the same client.
`ws_message()` may contain either text or binary payload data. Use `ws_opcode()` / `ws_is_binary()` to inspect the current inbound message type.
-The current `ws_send*` helpers queue text frames. Binary send helpers are not exposed yet.
+Set `binary = true` on `ws_send()` or `ws_send_to()` to queue a binary frame instead of a text frame.
-The runtime now accepts fragmented messages, validates reserved bits and UTF-8 for text payloads, and delivers both text and binary message frames into `WS()`.
+The runtime now accepts fragmented messages, validates reserved bits and UTF-8 for text payloads, and delivers both text and binary message frames into `WS(Request& context)`.
-## Service Setup
+## Error Reporting
-The repository now includes a systemd unit template and a management script for the FastCGI runtime:
+Unhandled exceptions and recovered fatal request signals now return a `500 Internal Server Error` response with a plain-text trace instead of simply dropping the upstream connection and leaving nginx to show a generic `502`.
+
+The demo page `site/test/error-reporting.uce` can be used to exercise:
+
+- uncaught exception handling
+- recovered `SIGABRT`
+- recovered `SIGSEGV`
+
+The current error page includes:
+
+- request URI
+- resolved script path
+- high-level error summary
+- signal number and name when applicable
+- a native backtrace
+
+This recovery path currently covers normal request handling. It is not yet the universal recovery path for every runtime subsystem.
+
+## Docs And Tests
+
+The most current user-facing reference lives under `site/doc/`, and the demo pages live under `site/test/`.
+
+Useful entry points:
+
+- repo files:
+ - `site/doc/index.uce`
+ - `site/doc/singlepage.uce`
+ - `site/test/index.uce`
+- published URLs:
+ - `/doc/index.uce`
+ - `/doc/singlepage.uce`
+ - `/test/index.uce`
+
+Representative test pages:
+
+- `site/test/components.uce`
+- `site/test/websockets.ws.uce`
+- `site/test/error-reporting.uce`
+- `site/test/post-multipart.uce`
+- `site/test/session.uce`
+
+## Deploy Behind Nginx
+
+The intended production shape is:
+
+- nginx serves static files directly
+- nginx forwards `.uce` requests and ordinary `.ws.uce` page loads to the UCE FastCGI Unix socket
+- nginx proxies actual WebSocket upgrade requests for `.ws.uce` endpoints to the runtime's built-in HTTP/WebSocket listener
+- systemd keeps the runtime built, started, and restarted on failure
+
+The repository ships the pieces used for this:
- `scripts/systemd/uce.service`
- `scripts/systemd/manage-uce-service.sh`
+- `etc/uce/settings.cfg`
-Typical usage on the deployment host:
+### 1. Install build and runtime dependencies
+
+On a Debian or Ubuntu host, start with the packages needed to build and run UCE behind nginx:
+
+```bash
+apt update
+apt install -y nginx clang mariadb-client libmariadb-dev build-essential
+```
+
+The exact package names may vary by distro. The important requirements are:
+
+- `nginx`
+- `clang++`
+- `mysql_config`
+- normal Linux development headers for threads, sockets, `dl`, and backtrace support
+
+### 2. Put the repo on the server
+
+This README assumes the repository lives at:
+
+```bash
+/Code/uce.openfu.com/uce
+```
+
+That is what the shipped `scripts/systemd/uce.service` file currently uses as its `WorkingDirectory` and build path. If you deploy somewhere else, update that unit file before enabling the service.
+
+### 3. Configure `/etc/uce/settings.cfg`
+
+The runtime reads its server settings from:
+
+```bash
+/etc/uce/settings.cfg
+```
+
+The shipped example contains the important filesystem and FastCGI settings:
+
+```ini
+BIN_DIRECTORY=/tmp/uce/work
+TMP_UPLOAD_PATH=/tmp/uce/uploads
+SESSION_PATH=/tmp/uce/sessions
+
+FCGI_SOCKET_PATH=/run/uce.sock
+FCGI_PORT=9993
+
+WORKER_COUNT=4
+MAX_MEMORY=16777216
+SESSION_TIME=2592000
+```
+
+For nginx deployments, the most important setting is:
+
+- `FCGI_SOCKET_PATH=/run/uce.sock`
+
+That is the Unix socket nginx should use for normal `.uce` requests.
+
+`FCGI_PORT` is optional if nginx is talking to the Unix socket. Leave it set if you also want a TCP FastCGI listener, or remove it if you want the socket to be the only FastCGI entry point.
+
+If you want WebSocket support through nginx, also make sure the built-in HTTP listener is available. The runtime currently defaults `HTTP_PORT` to `8080` even if it is not present in the config file, but it is clearer to set it explicitly:
+
+```ini
+HTTP_PORT=8080
+```
+
+Recommended deployment notes:
+
+- keep `HTTP_PORT` bound to localhost only at the firewall or by network policy; nginx should be the public entry point
+- keep `BIN_DIRECTORY`, `TMP_UPLOAD_PATH`, and `SESSION_PATH` on writable local storage
+- after editing `/etc/uce/settings.cfg`, restart `uce.service`
+
+### 4. Install and enable the systemd service
+
+As root, from the repository root:
```bash
scripts/systemd/manage-uce-service.sh setup
+```
+
+That script:
+
+- installs `scripts/systemd/uce.service` as `/etc/systemd/system/uce.service`
+- installs `etc/uce/settings.cfg` to `/etc/uce/settings.cfg` if it does not already exist
+- reloads systemd
+- enables the service at boot
+- starts the runtime immediately
+
+Useful follow-up commands:
+
+```bash
scripts/systemd/manage-uce-service.sh status
scripts/systemd/manage-uce-service.sh restart
scripts/systemd/manage-uce-service.sh logs 200
```
-The service runs the runtime from the repository root so `COMPILER_SYS_PATH` resolves correctly and nginx can forward `.uce` requests to the Unix socket defined in `/etc/uce/settings.cfg`.
+The unit currently:
+
+- creates `/tmp/uce/work`, `/tmp/uce/uploads`, and `/tmp/uce/sessions`
+- removes any stale `/run/uce.sock`
+- rebuilds the runtime before start
+- runs the binary from the repo root so `COMPILER_SYS_PATH` resolves correctly
+
+### 5. Configure nginx for `.uce` and `.ws.uce`
+
+You need two nginx paths for `.ws.uce` endpoints:
+
+- FastCGI for ordinary `.uce` requests and plain `.ws.uce` page renders
+- HTTP proxying only for actual WebSocket upgrade traffic on `.ws.uce` endpoints
+
+If you use WebSockets, add this `map` in the nginx `http` block:
+
+```nginx
+map $http_upgrade $connection_upgrade {
+ default upgrade;
+ '' close;
+}
+```
-For deployed WebSockets, nginx should proxy `.ws.uce` requests to the runtime's built-in HTTP listener instead of `fastcgi_pass`. The current OpenFU deployment already does this and keeps HTTP/WebSocket ownership on a single worker so page-scoped broadcasts stay coherent.
+Then use a server block along these lines:
-# API
+```nginx
+server {
+ listen 80;
+ server_name example.com;
+ root /Code/uce.openfu.com/uce/site;
+
+ index index.uce index.html;
+
+ location / {
+ try_files $uri $uri/ =404;
+ }
-Memcache Functions
-==================
+ location ~ \.uce$ {
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param DOCUMENT_ROOT $document_root;
+ fastcgi_param SCRIPT_NAME $fastcgi_script_name;
+ fastcgi_param DOCUMENT_URI $uri;
+ fastcgi_param REQUEST_URI $request_uri;
+ fastcgi_pass unix:/run/uce.sock;
+ }
-memcache\_command
------------------
+ location ~ \.ws\.uce$ {
+ error_page 418 = @uce_websocket;
+ if ($http_upgrade = "websocket") {
+ return 418;
+ }
-String memcache\_command(u64 connection, String command)
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ fastcgi_param DOCUMENT_ROOT $document_root;
+ fastcgi_param SCRIPT_NAME $fastcgi_script_name;
+ fastcgi_param DOCUMENT_URI $uri;
+ fastcgi_param REQUEST_URI $request_uri;
+ fastcgi_pass unix:/run/uce.sock;
+ }
-### Parameters
+ location @uce_websocket {
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection $connection_upgrade;
+ proxy_pass http://127.0.0.1:8080;
+ }
+}
+```
-**connection** : connection handle
+Important details:
-**command** : string containing the Memcache command
+- `.ws.uce` must be matched before the more general `.uce` rule
+- `fastcgi_pass` should point at the same socket path as `FCGI_SOCKET_PATH`
+- `proxy_pass` should point at the runtime's `HTTP_PORT`
+- ordinary `GET /page.ws.uce` page renders should stay on FastCGI
+- only upgrade requests for `/page.ws.uce` should go through the HTTP/WebSocket listener
+- `SCRIPT_FILENAME` should resolve to the actual `.uce` file on disk
+- `proxy_http_version 1.1` and the `Upgrade` / `Connection` headers are required for WebSockets
-**return value** : string containing the Memcache server's response
+The `location /` block above is intentionally conservative and only serves real files from `site/`. If your app uses a front-controller pattern such as routing everything through `/index.uce`, change that block accordingly.
-### Description
+### 6. Think about document root and private files
-Executes a command on an open memcache connection.
+Point nginx at `site/`, not the repository root. The repo still contains source, scripts, packaging files, and operational assets that are not meant to be public.
-memcache\_connect
------------------
+At minimum, explicitly block internal directories that should never be served directly. For example:
-u64 memcache\_connect(String host = "127.0.0.1", short port = 11211)
+```nginx
+location ~ ^/(src|scripts|etc|bin|work|dist|pkg)/ {
+ return 404;
+}
+```
-### Parameters
+If nginx is rooted at `site/`, most of those paths will not be reachable anyway, which is the preferred setup.
-**host** : optional host name of the memcache server, defaults to local address 127.0.0.1
+### 7. Reload nginx and verify the deployment
-**port** : optional memcache server's port, defaults to 11211
+After writing the nginx config:
-**return value** : the connection handle (or -1 if an error occurred)
+```bash
+nginx -t
+systemctl reload nginx
+```
-### Description
+Then verify:
-Connects to a memcache server instance.
+```bash
+systemctl status uce.service
+curl -i http://127.0.0.1/test/index.uce
+curl -i http://127.0.0.1/doc/index.uce
+```
-memcache\_delete
-----------------
+If WebSockets are enabled, also verify a `.ws.uce` endpoint through nginx rather than talking to the runtime directly.
-bool memcache\_delete(u64 connection, String key)
+### 8. Troubleshooting
-### Parameters
+Common failure modes:
-**connection** : connection handle
+- `502 Bad Gateway`
+ Usually means `uce.service` is down, the Unix socket path does not match, or the request crashed before sending a valid response.
+- WebSocket upgrade fails
+ Check that nginx is routing `.ws.uce` to `proxy_pass`, not `fastcgi_pass`, and that `HTTP_PORT` is reachable on localhost.
+- Requests compile but immediately crash
+ Check `journalctl -u uce.service` and clear stale generated artifacts under `BIN_DIRECTORY` if you have changed runtime ABI or entrypoint signatures.
+- nginx serves raw source or internal files
+ Tighten the server root and add explicit deny rules for non-public directories.
-**key** : key string
+## Repo Helpers
-**return value** : true if the operation was successful
+- `./codesearch [rg options...]`
-### Description
+This is a small repo-root wrapper around `rg` that searches from the project root and skips generated/build directories such as `.git/`, `bin/`, `dist/`, and `work/`.
-Deletes entry specified by the 'key'.
+## Reference Notes
-memcache\_get
--------------
+For up-to-date usage, prefer:
-String memcache\_get(u64 connection, String key, String default\_value = "")
+- the live docs under `site/doc/`
+- the declarations in `src/lib/compiler.h`, `src/lib/sys.h`, and `src/lib/functionlib.h`
+- the example pages under `site/test/`
-### Parameters
+## AI Disclosure
-**connection** : connection handle
-
-**key** : key string
-
-**default\_value** : optional default value
-
-**return value** : value that was returned by the Memcache server
-
-### Description
-
-Retrieves a value from an existing connection to a Memcache server.
-
-memcache\_get\_multiple
------------------------
-
-StringMap memcache\_get\_multiple(u64 connection, StringList keys)
-
-### Parameters
-
-**connection** : connection handle
-
-**keys** : a list of strings containing the keys to be retrieved
-
-**return value** : a StringMap with the retrieved entries
-
-### Description
-
-Retrieves a bunch of entries all at once.
-
-memcache\_set
--------------
-
-bool memcache\_set(u64 connection, String key, String value, u64 expires\_in = 60\*60)
-
-### Parameters
-
-**connection** : connection handle
-
-**key** : the entry's key
-
-**value** : the value to be set
-
-**expires\_in** : optional expiration timeout, defaults to one hour
-
-**return value** : true if the operation was successful
-
-### Description
-
-Stores a 'value' on the Memcache server.
-
-MySQL Functions
-===============
-
-mysql\_connect
---------------
-
-MySQL\* mysql\_connect(String host = "localhost", String username = "root", String password = "")
-
-### Parameters
-
-**host** : host name of the MySQL server
-
-**username** : user name
-
-**password** : password
-
-**return value** : pointer to the MySQL connection struct
-
-### Description
-
-Establishes a connection to a MySQL server.
-
-mysql\_disconnect
------------------
-
-void mysql\_disconnect(MySQL\* m)
-
-### Parameters
-
-**m** : pointer to an existing MySQL connection struct
-
-### Description
-
-Closes a connection to a MySQL server.
-
-mysql\_error
-------------
-
-String mysql\_error(MySQL\* m)
-
-### Parameters
-
-**m** : pointer to a MySQL connection struct
-
-**return value** : MySQL error message (if present, otherwise empty string)
-
-### Description
-
-Returns the last error message from a connection to a MySQL server.
-
-mysql\_escape
--------------
-
-String mysql\_escape(String raw, char quote\_char)
-
-### Parameters
-
-**raw** : the string to be escaped
-
-**quote\_char** : the character that should be used to wrap the string (pass NULL for no wrapping)
-
-**return value** : the safe version of the 'raw' string
-
-### Description
-
-Escapes a string such that it can be passed as a safe value into an SQL expression.
-
-mysql\_insert\_id
------------------
-
-u64 mysql\_insert\_id(MySQL\* m)
-
-### Parameters
-
-**m** : pointer to an active MySQL connection
-
-**return value** : the last used automatic row ID
-
-### Description
-
-This retrieves the last row ID that was used for a column with an AUTO\_INCREMENT row key.
-
-mysql\_query
-------------
-
-DTree mysql\_query(MySQL\* m, String q, StringMap params)
-
-### Parameters
-
-**m** : pointer to an active MySQL connection struct
-
-**q** : a string containing a MySQL query
-
-**params** : optional, a list of query parameter keys and values
-
-**return value** : a list of rows returned from executing the query
-
-### Description
-
-Executes a MySQL query and returns the resulting data (if any).
-
-### Examples
-
-(tbd)
-
-Noise/Hash Functions
-====================
-
-draw\_float
------------
-
-f64 draw\_float(f64 from, f64 to)
-
-### Parameters
-
-**from** : minimum value
-
-**to** : maximum value
-
-**return value** : a noise value between 'from' and 'to'
-
-### Description
-
-This function works exactly like generate\_float(), but context->random\_index is used for the 'index' value and context->random\_seed is used for the seed. After this function has been called, the context->random\_index is increased by one. At the start of every request, context->random\_seed is automatically populated with a new seed value.
-
-draw\_int
----------
-
-u64 draw\_int(u64 from, u64 to)
-
-### Parameters
-
-**from** : minimum value
-
-**to** : maximum value
-
-**return value** : a noise value between 'from' and 'to'
-
-### Description
-
-This function works exactly like generate\_int(), but context->random\_index is used for the 'index' value and context->random\_seed is used for the seed. After this function has been called, the context->random\_index is increased by one. At the start of every request, context->random\_seed is automatically populated with a new seed value.
-
-gen\_noise01
-------------
-
-u32 noise01(u64 index, u64 seed = 0)
-
-### Parameters
-
-**index** : index position
-
-**seed** : seed set (defaults to 0)
-
-**return value** : a noise value from 0 to 1
-
-### Description
-
-Generates a noise value in the range from 0 to 1 for the given 'index' and 'seed' values.
-
-gen\_noise32
-------------
-
-u32 noise32(u32 index, u32 seed = 0)
-
-### Parameters
-
-**index** : index position
-
-**seed** : seed set (defaults to 0)
-
-**return value** : a noise value given the 'index' and 'seed' values.
-
-### Description
-
-Generates a noise value for the given 'index' and 'seed' values.
-
-gen\_noise64
-------------
-
-u32 noise64(u64 index, u64 seed = 0)
-
-### Parameters
-
-**index** : index position
-
-**seed** : seed set (defaults to 0)
-
-**return value** : a noise value given the 'index' and 'seed' values.
-
-### Description
-
-Generates a noise value for the given 'index' and 'seed' values.
-
-gen\_float
-----------
-
-f64 generate\_float(f64 from, f64 to, u64 index, u64 seed = 0)
-
-### Parameters
-
-**from** : minimum result
-
-**to** : maximum result
-
-**index** : index position to generate number from
-
-**seed** : seed position to generate number from (defaults to 0)
-
-**return value** : noise value
-
-### Description
-
-Generates a noise value between 'from' and 'to', given the 'index' and 'seed' numbers.
-
-gen\_int
---------
-
-u64 generate\_int(u64 from, u64 to, u64 index, u64 seed = 0)
-
-### Parameters
-
-**from** : minimum result
-
-**to** : maximum result
-
-**index** : index position to generate number from
-
-**seed** : seed position to generate number from (defaults to 0)
-
-**return value** : noise value
-
-### Description
-
-Generates a noise value between 'from' and 'to', given the 'index' and 'seed' numbers.
-
-gen\_sha1
----------
-
-String sha1(String s, bool as\_binary = false)
-
-### Parameters
-
-**s** : data to be hashed
-
-**as\_binary** : when set to false, returns hash in hexadecimal notation (defaults to false)
-
-**return value** : the resulting hash value
-
-### Description
-
-Returns the sha1 hash of 's'.
-
-Output Buffer Functions
-=======================
-
-ob\_clear
----------
-
-void ob\_clear()
-
-### Parameters
-
-**(none)** :
-
-### Description
-
-Discard the current output buffer.
-
-ob\_get
--------
-
-String ob\_get()
-
-### Parameters
-
-**return value** : content of the current output buffer
-
-### Description
-
-Returns the contents of the current output buffer.
-
-ob\_get\_clear
---------------
-
-String ob\_get\_clear()
-
-### Parameters
-
-**return value** : content of the current output buffer
-
-### Description
-
-Returns the contents of the current output buffer and then discards the buffer.
-
-ob\_start
----------
-
-void ob\_start()
-
-### Parameters
-
-**(none)** :
-
-### Description
-
-Starts a new output buffer. All subsequent output will be directed into this buffer.
-
-Sessions
-========
-
-make\_session\_id
------------------
-
-String make\_session\_id()
-
-### Parameters
-
-**return value** : a new session ID
-
-### Description
-
-Creates a session ID
-
-session\_destroy
-----------------
-
-void session\_destroy(String session\_name)
-
-### Parameters
-
-**session\_name** : the name of the session
-
-### Description
-
-Deletes the cookie specified by 'session\_name' and clears the data stored under the session ID. This empties the 'context->session\_id' and 'context->session' variables.
-
-session\_start
---------------
-
-String session\_start(String session\_name)
-
-### Parameters
-
-**return value** : the session ID, defaults to "uce-session"
-
-### Description
-
-Starts session or connects to existing session. This function sets a cookie with the name contained in 'session\_name' if it does not exist and fills that cookie with a new unique session ID. It then loads the session data for that session ID. Afterwards, the following fields are populated in the 'context' variable:
-
-context->session\_id : the current session ID
-
-context->session\_name : the current session cookie name
-
-context->session : the current session data. The session data is automatically saved after a request completes.
-
-Socket Functions
-================
-
-socket\_close
--------------
-
-void socket\_close(u64 sockfd)
-
-### Parameters
-
-**sockfd** : socket handle
-
-### Description
-
-Closes an existing socket connection.
-
-socket\_connect
----------------
-
-u64 socket\_connect(String host, short port)
-
-### Parameters
-
-**host** : host name
-
-**port** : port number
-
-**return value** : the socket handle
-
-### Description
-
-Opens a socket connection to the given 'host' and 'port'.
-
-socket\_read
-------------
-
-String socket\_read(u64 sockfd, u32 max\_length = 1024\*128, u32 timeout = 1);
-
-### Parameters
-
-**sockfd** : socket handle
-
-**max\_length** : optional maximum data size, defaults to 128kBytes
-
-**timeout** : optional operation timeout, defaults to one second
-
-**return value** : string containing the data that was read
-
-### Description
-
-Reads data from a socket connection.
-
-socket\_write
--------------
-
-bool socket\_write(u64 sockfd, String data)
-
-### Parameters
-
-**sockfd** : socket handle
-
-**data** : a string containing the data to be written to the socket
-
-**return value** : true if the write operation was successful
-
-### Description
-
-Writes a string of 'data' to the given socket.
-
-String Functions
-================
-
-filter
-------
-
-StringList filter(StringList items, function f)
-
-vector filter(vector items, function f)
-
-### Parameters
-
-**items** : list of items to be filtered
-
-**f** : a function that decides which items should be in the new list
-
-**return value** : a new list
-
-### Description
-
-Returns a list containing the members of 'items' for which 'f' returned boolean true.
-
-first
------
-
-String first(String... args)
-
-### Parameters
-
-**args** : a variable number of String arguments
-
-**return value** : first of the 'args' that was not empty.
-
-### Description
-
-Given a variable number of String parameters, the first() function returns the first of these parameters that was not empty. Leading and trailing whitespace characters are not considered, resulting in a string that contains only whitespace characters being considered empty.
-
-join
-----
-
-String join(StringList l, String delim = "\\n")
-
-### Parameters
-
-**l** : list of strings to be joined
-
-**delim** : delimiter (defaults to newline character)
-
-**return value** : a string containing items joined by 'delim'
-
-### Description
-
-Joins the items contained in 'l' into a single String.
-
-nibble
-------
-
-String nibble(String& haystack, String delim)
-
-### Parameters
-
-**haystack** : string to be nibbled at
-
-**delim** : delimiter
-
-**return value** : string before first occurrence of 'delim'
-
-### Description
-
-Returns the part of 'haystack' before the first occurrence of 'delim', removing the corresponding part from 'haystack' (including 'delim'). If the substring 'delim' does not occurr in 'haystack', the entire string is returned and 'haystack' is set to an empty string.
-
-split
------
-
-StringList split(String str, String delim)
-
-### Parameters
-
-**str** : string to be split
-
-**delim** : delimiter
-
-**return value** : a list of strings
-
-### Description
-
-Splits 'str' into multiple strings based on the given delimiter 'delim'.
-
-split\_space
-------------
-
-StringList split\_space(String str)
-
-### Parameters
-
-**str** : string to be split
-
-**return value** : a list of strings
-
-### Description
-
-Splits 'str' into multiple strings along any whitespace characters (multiple whitespace characters count as one).
-
-split\_utf8
------------
-
-StringList split\_utf8(String str, bool compound\_characters = false)
-
-### Parameters
-
-**str** : string to be split
-
-**compound\_characters** : optional, if true tries to combine compound characters
-
-**return value** : a list of Unicode characters
-
-### Description
-
-Splits the string 'str' into its constituent Unicode code points.
-
-If 'compound\_characters' is true, split\_utf8 will attempt to combine compound characters based on very simple rules:
-
-* combine characters if they're connected by a Zero-Width Joiner (ZWJ) character
-
-* combine two characters if they're both a Regional Indicator Symbol Letter
-
-* if a character is a Variation Selector, append it to the previous character
-
-* in all other cases, characters remain on their own
-
-replace
--------
-
-String replace(String s, String search, String replace\_with)
-
-### Parameters
-
-**s** : the string where replacements should happen
-
-**search** : the string that should be searched for
-
-**replace\_with** : the string that should appear in places where 'search' occurs
-
-**return value** : a version of 's' where all instances of 'search' have been replaced with 'replace\_with'
-
-### Description
-
-Replace all occurrences of 'search' with the string defined in 'replace\_with'.
-
-to\_lower
----------
-
-String to\_lower(String s)
-
-### Parameters
-
-**s** : the string to be converted
-
-**return value** : returns a version of 's' where all upper case characters have been changed into lower case
-
-### Description
-
-Returns a lower case version of the input string 's'.
-
-Note: this function is not yet Unicode-aware.
-
-to\_upper
----------
-
-String to\_upper(String s)
-
-### Parameters
-
-**s** : the string to be converted
-
-**return value** : returns a version of 's' where all lower case characters have been changed into upper case
-
-### Description
-
-Returns a upper case version of the input string 's'.
-
-Note: this function is not yet Unicode-aware.
-
-trim
-----
-
-String trim(String raw)
-
-### Parameters
-
-**raw** : string to be trimmed
-
-**return value** : string with leading and trailing whitespace characters removed
-
-### Description
-
-Returns a string where leading an trailing whitespace characters have been trimmed off.
-
-File System Functions
-=====================
-
-basename
---------
-
-String basename(String fn)
-
-### Parameters
-
-**fn** : raw filename
-
-**return value** : the file's name
-
-### Description
-
-Isolates the file name component from a path/file name.
-
-dirname
--------
-
-String dirname(String fn)
-
-### Parameters
-
-**fn** : raw filename
-
-**return value** : the directory's name
-
-### Description
-
-Isolates the directory name component from a path/file name.
-
-expand\_path
-------------
-
-String expand\_path(String path, String relative\_to\_path = "")
-
-### Parameters
-
-**path** : a relative path
-
-**relative\_to\_path** : optional, expand relative to this path (if not given, the current path is used)
-
-**return value** : expanded version of the 'path'
-
-### Description
-
-Converts a relative path name into an absolute path, using the current working directory as a base.
-
-file\_append
-------------
-
-void file\_append(String file\_name, ...val)
-
-### Parameters
-
-**file\_name** : file name of file that should be written to
-
-**...val** : one or more values that should be written into the file
-
-### Description
-
-Opens or creates a given file and appends data to it.
-
-file\_exists
-------------
-
-bool file\_exists(String path)
-
-### Parameters
-
-**path** : the path name to be checked
-
-**return value** : true if the file exists
-
-### Description
-
-Checks whether the file or path specified by 'path' exists.
-
-file\_get\_contents
--------------------
-
-String file\_get\_contents(String file\_name)
-
-### Parameters
-
-**file\_name** : file name of file that should be read
-
-**return value** : String containing the file's contents
-
-### Description
-
-Reads the file identified by 'file\_name' and returns it as a String. If the file cannot be read, this function will return an empty string.
-
-file\_mtime
------------
-
-time\_t file\_mtime(String file\_name)
-
-### Parameters
-
-**file\_name** : name of the file
-
-**return value** : Unix time stamp of the file's last modification
-
-### Description
-
-Retrieves the last modification date of 'file\_name' as a Unix timestamp.
-
-file\_put\_contents
--------------------
-
-bool file\_put\_contents(String file\_name, String content)
-
-### Parameters
-
-**file\_name** : file name of file that should be written
-
-**content** : content that should be written
-
-**return value** : true if write was successful
-
-### Description
-
-Writes the String 'content' into a file identified by 'file\_name'. Any pre-existing content of the file will be overwritten.
-
-get\_cwd
---------
-
-String get\_cwd()
-
-### Parameters
-
-**return value** : the current working directory
-
-### Description
-
-Returns the current working directory.
-
-ls
---
-
-StringList ls(String path)
-
-### Parameters
-
-**path** : a filesystem path
-
-**return value** : list of directory entries
-
-### Description
-
-Returns a list of files and subdirectories within the given 'path'.
-
-mkdir
------
-
-bool mkdir(String path)
-
-### Parameters
-
-**path** : the path name to be created
-
-**return value** : returns true if the directory was successfully created
-
-### Description
-
-Creates a directory stated by 'path'
-
-set\_cwd
---------
-
-void set\_cwd(String path)
-
-### Parameters
-
-**path** : the new working directory
-
-### Description
-
-Sets a new working directory.
-
-shell\_escape
--------------
-
-String shell\_escape(String raw)
-
-### Parameters
-
-**raw** : string that should be escaped
-
-**return value** : escaped version of 'raw'
-
-### Description
-
-Escapes a parameter for shell\_exec
-
-shell\_exec
------------
-
-String shell\_exec(String cmd)
-
-### Parameters
-
-**cmd** : string that contains the shell command line to be executed
-
-**return value** : output of the command execution
-
-### Description
-
-Executes a Linux shell command and returns the generated output
-
-unlink
-------
-
-void unlink(String file\_name)
-
-### Parameters
-
-**file\_name** : name of the file
-
-### Description
-
-Deletes the file identified by 'file\_name'.
-
-Task API
-========
-
-kill
-----
-
-s64 kill(pid\_t pid, s64 sig)
-
-### Parameters
-
-**pid** : PID of the process
-
-**sig** : signal number
-
-**return value** : 0 if signal was sent, -1 otherwise
-
-### Description
-
-This is the standard POSIX kill() function, provided here for reference.
-
-Possible signal numbers are: SIGABND, SIGABRT, SIGALRM, SIGBUS, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGKILL, SIGPIPE, SIGPOLL, SIGPROF, SIGQUIT, SIGSEGV, SIGSYS, SIGTERM, SIGTRAP, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGXCPU, SIGXFSZ, SIGCHLD, SIGIO, SIGIOERR, SIGWINCH, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGCONT.
-
-task
-----
-
-pid\_t task(String key, std::function exec\_func)
-
-### Parameters
-
-**key** : string uniquely identifying the task
-
-**exec\_func** : function to execute
-
-**return value** : the process ID of the started (or still running) task
-
-### Description
-
-task() starts the 'exec\_func' in a new process and returns that process' ID. If a process with the same 'key' is already running, task will not start a new process but instead just return the PID of the process that is already running.
-
-task\_pid
----------
-
-pid\_t task\_pid(String key)
-
-### Parameters
-
-**key** : string uniquely identifying the task
-
-**return value** : the process ID of the task
-
-### Description
-
-Checks whether a process with the given 'key' is running and returns its PID if it is. Returns 0 otherwise.
-
-Time and Date Functions
-=======================
-
-microtime
----------
-
-f64 microtime()
-
-### Parameters
-
-**return value** : current Unix timestamp
-
-### Description
-
-Returns a 64 bit float containing the current Unix timestamp with millisecond accuracy or better.
-
-time
-----
-
-u64 time()
-
-### Parameters
-
-**return value** : second-accurate current Unix timestamp
-
-### Description
-
-Returns a 64 bit integer containing the current Unix timestamp.
-
-date
-----
-
-String date(String format = "", u64 timestamp = 0)
-
-### Parameters
-
-**format** : formatting string specifying the date format
-
-**timestamp** : optional timestamp value, defaults to current time
-
-**return value** : a formatted date
-
-### Description
-
-Returns a formatted date. This is based on the Linux date() command. The formatting string supports the following sequences:
-
- %% a literal %
-
- %a locale's abbreviated weekday name (e.g., Sun)
-
- %A locale's full weekday name (e.g., Sunday)
-
- %b locale's abbreviated month name (e.g., Jan)
-
- %B locale's full month name (e.g., January)
-
- %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)
-
- %C century; like %Y, except omit last two digits (e.g., 20)
-
- %d day of month (e.g., 01)
-
- %D date; same as %m/%d/%y
-
- %e day of month, space padded; same as %\_d
-
- %F full date; like %+4Y-%m-%d
-
- %g last two digits of year of ISO week number (see %G)
-
- %G year of ISO week number (see %V); normally useful only
-
- with %V
-
- %h same as %b
-
- %H hour (00..23)
-
- %I hour (01..12)
-
- %j day of year (001..366)
-
- %k hour, space padded ( 0..23); same as %\_H
-
- %l hour, space padded ( 1..12); same as %\_I
-
- %m month (01..12)
-
- %M minute (00..59)
-
- %n a newline
-
- %N nanoseconds (000000000..999999999)
-
- %p locale's equivalent of either AM or PM; blank if not known
-
- %P like %p, but lower case
-
- %q quarter of year (1..4)
-
- %r locale's 12-hour clock time (e.g., 11:11:04 PM)
-
- %R 24-hour hour and minute; same as %H:%M
-
- %s seconds since 1970-01-01 00:00:00 UTC
-
- %S second (00..60)
-
- %t a tab
-
- %T time; same as %H:%M:%S
-
- %u day of week (1..7); 1 is Monday
-
- %U week number of year, with Sunday as first day of week
-
- (00..53)
-
- %V ISO week number, with Monday as first day of week (01..53)
-
- %w day of week (0..6); 0 is Sunday
-
- %W week number of year, with Monday as first day of week
-
- (00..53)
-
- %x locale's date representation (e.g., 12/31/99)
-
- %X locale's time representation (e.g., 23:13:48)
-
- %y last two digits of year (00..99)
-
- %Y year
-
- %z +hhmm numeric time zone (e.g., -0400)
-
- %:z +hh:mm numeric time zone (e.g., -04:00)
-
- %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)
-
- %:::z numeric time zone with : to necessary precision (e.g.,
-
- -04, +05:30)
-
- %Z alphabetic time zone abbreviation (e.g., EDT)
-
- By default, date pads numeric fields with zeroes. The following
-
- optional flags may follow '%':
-
- - (hyphen) do not pad the field
-
- \_ (underscore) pad with spaces
-
- 0 (zero) pad with zeros
-
- + pad with zeros, and put '+' before future years with >4
-
- digits
-
- ^ use upper case if possible
-
- # use opposite case if possible
-
-gmdate
-------
-
-String gmdate(String format = "", u64 timestamp = 0)
-
-### Parameters
-
-**format** : formatting string specifying the date format
-
-**timestamp** : optional timestamp value, defaults to current time
-
-**return value** : a formatted date
-
-### Description
-
-Returns a formatted date in the GMT/UTC timezone. This is based on the Linux date() command. The formatting string supports the following sequences:
-
- %% a literal %
-
- %a locale's abbreviated weekday name (e.g., Sun)
-
- %A locale's full weekday name (e.g., Sunday)
-
- %b locale's abbreviated month name (e.g., Jan)
-
- %B locale's full month name (e.g., January)
-
- %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)
-
- %C century; like %Y, except omit last two digits (e.g., 20)
-
- %d day of month (e.g., 01)
-
- %D date; same as %m/%d/%y
-
- %e day of month, space padded; same as %\_d
-
- %F full date; like %+4Y-%m-%d
-
- %g last two digits of year of ISO week number (see %G)
-
- %G year of ISO week number (see %V); normally useful only
-
- with %V
-
- %h same as %b
-
- %H hour (00..23)
-
- %I hour (01..12)
-
- %j day of year (001..366)
-
- %k hour, space padded ( 0..23); same as %\_H
-
- %l hour, space padded ( 1..12); same as %\_I
-
- %m month (01..12)
-
- %M minute (00..59)
-
- %n a newline
-
- %N nanoseconds (000000000..999999999)
-
- %p locale's equivalent of either AM or PM; blank if not known
-
- %P like %p, but lower case
-
- %q quarter of year (1..4)
-
- %r locale's 12-hour clock time (e.g., 11:11:04 PM)
-
- %R 24-hour hour and minute; same as %H:%M
-
- %s seconds since 1970-01-01 00:00:00 UTC
-
- %S second (00..60)
-
- %t a tab
-
- %T time; same as %H:%M:%S
-
- %u day of week (1..7); 1 is Monday
-
- %U week number of year, with Sunday as first day of week
-
- (00..53)
-
- %V ISO week number, with Monday as first day of week (01..53)
-
- %w day of week (0..6); 0 is Sunday
-
- %W week number of year, with Monday as first day of week
-
- (00..53)
-
- %x locale's date representation (e.g., 12/31/99)
-
- %X locale's time representation (e.g., 23:13:48)
-
- %y last two digits of year (00..99)
-
- %Y year
-
- %z +hhmm numeric time zone (e.g., -0400)
-
- %:z +hh:mm numeric time zone (e.g., -04:00)
-
- %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)
-
- %:::z numeric time zone with : to necessary precision (e.g.,
-
- -04, +05:30)
-
- %Z alphabetic time zone abbreviation (e.g., EDT)
-
- By default, date pads numeric fields with zeroes. The following
-
- optional flags may follow '%':
-
- - (hyphen) do not pad the field
-
- \_ (underscore) pad with spaces
-
- 0 (zero) pad with zeros
-
- + pad with zeros, and put '+' before future years with >4
-
- digits
-
- ^ use upper case if possible
-
- # use opposite case if possible
-
-parse\_time
------------
-
-u64 parse\_time(String time\_string)
-
-### Parameters
-
-**time\_string** : a string containing a date and/or time in text form
-
-**return value** : the interpreted 'time\_string' as a Unix timestamp
-
-### Description
-
-Attempts to parse the given 'time\_string' into a Unix timestamp.
-
-URI Functions
-=============
-
-encode\_query
--------------
-
-String encode\_query(StringMap map)
-
-### Parameters
-
-**q** : StringMap containing URL parameters to be encoded
-
-**return value** : a string with the encoded parameters
-
-### Description
-
-Encodes a StringMap containing URL parameters into a single String.
-
-make\_session\_id
------------------
-
-String make\_session\_id()
-
-### Parameters
-
-**return value** : a new session ID
-
-### Description
-
-Creates a session ID
-
-parse\_query
-------------
-
-StringMap parse\_query(String q)
-
-### Parameters
-
-**q** : string containing URL parameters
-
-**return value** : a StringMap containing the parameters
-
-### Description
-
-Decodes a string of the format 'a=b&c=d' into a StringMap containing keyed entries.
-
-uri\_decode
------------
-
-String uri\_decode(String s)
-
-### Parameters
-
-**s** : string containing URI encoded data
-
-**return value** : a string that contains the decoded version of 's'
-
-### Description
-
-Decodes an URI-encoded string 's'.
-
-uri\_encode
------------
-
-String uri\_encode(String s)
-
-### Parameters
-
-**s** : string that should be encoded
-
-**return value** : an URI-encoded version of 's'
-
-### Description
-
-URI-encodes a string.
-
-Other Functions and Data Structures
-===================================
-
-context
--------
-
-Request\* context;
-
-### ServerState\* server
-
-Contains the current server state
-
-### StringMap params
-
-All FastCGI server parameters
-
-### StringMap get
-
-The current request's GET variables
-
-### StringMap post
-
-The current request's POST variables
-
-### StringMap cookies
-
-Cookies that have been transmitted by the browser
-
-### StringMap session
-
-The current session
-
-### String session\_id
-
-ID of the session cookie
-
-String session\_name
-
-Name of the session cookie
-
-### DTree var
-
-Variable user-defined data
-
-### std::vector uploaded\_files
-
-Files that have been uploaded in the current request
-
-### StringMap header
-
-Headers to be sent back to the browser
-
-### StringList set\_cookies;
-
-Cookies that should be sent back to the browser
-
-### u64 random\_seed
-
-The current request's "random" noise generator seed
-
-### u64 random\_index
-
-The current request's "random" noise generator index position
-
-### MemoryArena\* mem
-
-Contains the current request's memory arena
-
-### bool flags.log\_request
-
-Whether the request should be logged
-
-### Stats
-
-u32 stats.bytes\_written
-
-f64 stats.time\_init
-
-f64 stats.time\_start
-
-f64 stats.time\_end
-
-### invoke(String file\_name, \[DTree& call\_param\])
-
-Invokes the UCE file 'file\_name'
-
-#load
------
-
-#load "myfile.uce"
-
-### Parameters
-
-**file name** : name of an UCE file that should be included
-
-### Description
-
-Includes another UCE file
-
-float\_val
-----------
-
-f64 float\_val(String s)
-
-### Parameters
-
-**s** : string to be converted
-
-**return value** : a f64 containing the number (0 if no number could be identified).
-
-### Description
-
-Extracts a floating point number from a String.
-
-html\_escape
-------------
-
-String html\_escape(String s)
-
-### Parameters
-
-**s** : string to be escaped
-
-**return value** : an HTML-safe escaped version of 's'
-
-### Description
-
-Returns a version of the input string where the following characters have been replace by HTML entities:
-
-* & → &
-* < → lt;
-* \> → >
-* " → "
-
-int\_val
---------
-
-u64 int\_val(String s, u32 base = 10)
-
-### Parameters
-
-**s** : string to be converted
-
-**base** : number system base (default 10)
-
-**return value** : a u64 containing the number (0 if no number could be identified).
-
-### Description
-
-Extracts an integer from a String.
-
-json\_decode
-------------
-
-DTree json\_decode(String s)
-
-### Parameters
-
-**s** : string containing JSON data
-
-**return value** : a DTree object containing the deserialized JSON data
-
-### Description
-
-Deserializes 's' into a DTree structure.
-
-json\_encode
-------------
-
-String json\_encode(DTree t)
-
-### Parameters
-
-**t** : DTree object to be serialized
-
-**return value** : string containing the JSON result
-
-### Description
-
-Serializes a DTree structure 't' into a String in JSON notation.
-
-print
------
-
-void print(...val)
-
-### Parameters
-
-**...val** : one or more values that should be output
-
-### Description
-
-Appends data to the current request's output stream.
-
-var\_dump
----------
-
-String var\_dump(StringMap t, String prefix = "", String postfix = "\\n")
-
-String var\_dump(StringList t, String prefix = "", String postfix = "\\n")
-
-String var\_dump(DTree t, String prefix = "", String postfix = "\\n")
-
-### Parameters
-
-**t** : object to be dumped into a string
-
-**return value** : string containing a human-friendly representation of 't'
-
-### Description
-
-Returns a string representation of 't' intended for debugging.
+This project is largely human-made, with all the typical idiosyncracies of my projects clearly visible. However, OpenAI Codex was used for code review and documentation. Claude Opus was used for UI design work, and I used VS Code's git commit message generator.
diff --git a/codesearch b/codesearch
new file mode 100755
index 0000000..205bd4d
--- /dev/null
+++ b/codesearch
@@ -0,0 +1,58 @@
+#!/bin/bash
+set -euo pipefail
+
+cd "$(dirname "$0")"
+
+show_help() {
+ cat <<'EOF'
+Usage:
+ ./codesearch [rg options...]
+
+Examples:
+ ./codesearch "ws_send"
+ ./codesearch "RENDER\\(Request& context\\)"
+ ./codesearch -tcpp "compiler_invoke"
+ ./codesearch -g '*.uce' "context.call"
+
+Notes:
+ - This is a thin wrapper around ripgrep (`rg`).
+ - It searches from the repository root.
+ - Generated/build directories are excluded by default:
+ `.git/`, `bin/`, `dist/`, and `work/`.
+EOF
+}
+
+if [[ $# -eq 0 ]]; then
+ show_help
+ exit 1
+fi
+
+case "${1:-}" in
+ -h|--help|help)
+ show_help
+ exit 0
+ ;;
+esac
+
+if command -v rg >/dev/null 2>&1; then
+ exec rg \
+ --line-number \
+ --column \
+ --smart-case \
+ --glob '!.git/**' \
+ --glob '!bin/**' \
+ --glob '!dist/**' \
+ --glob '!work/**' \
+ "$@"
+fi
+
+pattern="$1"
+shift || true
+
+echo "ripgrep (rg) is not installed; falling back to grep." >&2
+exec grep -RIn \
+ --exclude-dir=.git \
+ --exclude-dir=bin \
+ --exclude-dir=dist \
+ --exclude-dir=work \
+ -- "$pattern" .
diff --git a/doc/pages/1_WS.txt b/doc/pages/1_WS.txt
deleted file mode 100644
index c9b14ee..0000000
--- a/doc/pages/1_WS.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-:sig
-WS()
-
-:desc
-Defines the WebSocket message handler for the current `.ws.uce` page.
-
-The same page may expose both `RENDER()` and `WS()`. `RENDER()` serves the initial HTTP response, while `WS()` is called whenever a complete WebSocket message arrives for that page.
-
-UCE reassembles fragmented messages before calling `WS()`. Text and binary frames are both delivered. Use `ws_opcode()` and `ws_is_binary()` to distinguish them.
-
-The `call` parameter passed into `WS()` contains:
-
-- `message`
-- `connection_id`
-- `scope`
-- `opcode`
-- `document_uri`
-
-:see
->websocket
diff --git a/doc/pages/draw_float.txt b/doc/pages/draw_float.txt
deleted file mode 100644
index 5a1b3fd..0000000
--- a/doc/pages/draw_float.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-:sig
-f64 draw_float(f64 from, f64 to)
-
-:params
-from : minimum value
-to : maximum value
-return value : a noise value between 'from' and 'to'
-
-:desc
-This function works exactly like generate_float(), but context->random_index is used for the 'index' value and context->random_seed is used for the seed. After this function has been called, the context->random_index is increased by one. At the start of every request, context->random_seed is automatically populated with a new seed value.
-
-:see
->noise
diff --git a/doc/pages/draw_int.txt b/doc/pages/draw_int.txt
deleted file mode 100644
index 236b744..0000000
--- a/doc/pages/draw_int.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-:sig
-u64 draw_int(u64 from, u64 to)
-
-:params
-from : minimum value
-to : maximum value
-return value : a noise value between 'from' and 'to'
-
-:desc
-This function works exactly like generate_int(), but context->random_index is used for the 'index' value and context->random_seed is used for the seed. After this function has been called, the context->random_index is increased by one. At the start of every request, context->random_seed is automatically populated with a new seed value.
-
-:see
->noise
diff --git a/doc/pages/render_file.txt b/doc/pages/render_file.txt
deleted file mode 100644
index ddb7b0f..0000000
--- a/doc/pages/render_file.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-:sig
-void render_file(String file_name, DTree& call_param = null)
-
-:params
-file_name : UCE file to load and execute
-call_param : optional, call parameter
-
-:desc
-Calls another UCE file and executes its RENDER() function.
-
-:Example
-// call a common page template
-render_file("page-template.uce");
-
-:see
->ob
diff --git a/doc/pages/ws_broadcast.txt b/doc/pages/ws_broadcast.txt
deleted file mode 100644
index 30dc7c7..0000000
--- a/doc/pages/ws_broadcast.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-:sig
-u64 ws_broadcast(String message, String scope = "")
-
-:params
-message : text message to send
-scope : optional scope identifier, defaults to the current WebSocket page scope
-return value : number of clients the message was queued for
-
-:desc
-Queues a text WebSocket message for every client connected to the given scope and returns the number of recipients.
-
-If `scope` is omitted, the current page scope is used.
-
-This helper currently sends text frames only.
-
-:see
->websocket
diff --git a/doc/pages/ws_send_to.txt b/doc/pages/ws_send_to.txt
deleted file mode 100644
index 971bf78..0000000
--- a/doc/pages/ws_send_to.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-:sig
-bool ws_send_to(String connection_id, String message)
-
-:params
-connection_id : ID of the target WebSocket client
-message : text message to send
-return value : true if the target connection exists and the message was queued
-
-:desc
-Queues a text WebSocket message for one specific connected client.
-
-This helper currently sends text frames only.
-
-:see
->websocket
diff --git a/examples/blog/LICENSE.txt b/examples/blog/LICENSE.txt
deleted file mode 100644
index 294e91d..0000000
--- a/examples/blog/LICENSE.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright (c) HTML5 Boilerplate
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/examples/blog/browserconfig.xml b/examples/blog/browserconfig.xml
deleted file mode 100644
index e5a529d..0000000
--- a/examples/blog/browserconfig.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/examples/blog/config/settings.uce b/examples/blog/config/settings.uce
deleted file mode 100644
index e69de29..0000000
diff --git a/examples/blog/css/main.css b/examples/blog/css/main.css
deleted file mode 100644
index 416a37e..0000000
--- a/examples/blog/css/main.css
+++ /dev/null
@@ -1,263 +0,0 @@
-/*! HTML5 Boilerplate v8.0.0 | MIT License | https://html5boilerplate.com/ */
-
-/* main.css 2.1.0 | MIT License | https://github.com/h5bp/main.css#readme */
-/*
- * What follows is the result of much research on cross-browser styling.
- * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
- * Kroc Camen, and the H5BP dev community and team.
- */
-
-/* ==========================================================================
- Base styles: opinionated defaults
- ========================================================================== */
-
-html {
- color: #222;
- font-size: 1em;
- line-height: 1.4;
-}
-
-/*
- * Remove text-shadow in selection highlight:
- * https://twitter.com/miketaylr/status/12228805301
- *
- * Vendor-prefixed and regular ::selection selectors cannot be combined:
- * https://stackoverflow.com/a/16982510/7133471
- *
- * Customize the background color to match your design.
- */
-
-::-moz-selection {
- background: #b3d4fc;
- text-shadow: none;
-}
-
-::selection {
- background: #b3d4fc;
- text-shadow: none;
-}
-
-/*
- * A better looking default horizontal rule
- */
-
-hr {
- display: block;
- height: 1px;
- border: 0;
- border-top: 1px solid #ccc;
- margin: 1em 0;
- padding: 0;
-}
-
-/*
- * Remove the gap between audio, canvas, iframes,
- * images, videos and the bottom of their containers:
- * https://github.com/h5bp/html5-boilerplate/issues/440
- */
-
-audio,
-canvas,
-iframe,
-img,
-svg,
-video {
- vertical-align: middle;
-}
-
-/*
- * Remove default fieldset styles.
- */
-
-fieldset {
- border: 0;
- margin: 0;
- padding: 0;
-}
-
-/*
- * Allow only vertical resizing of textareas.
- */
-
-textarea {
- resize: vertical;
-}
-
-/* ==========================================================================
- Author's custom styles
- ========================================================================== */
-
-/* ==========================================================================
- Helper classes
- ========================================================================== */
-
-/*
- * Hide visually and from screen readers
- */
-
-.hidden,
-[hidden] {
- display: none !important;
-}
-
-/*
- * Hide only visually, but have it available for screen readers:
- * https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
- *
- * 1. For long content, line feeds are not interpreted as spaces and small width
- * causes content to wrap 1 word per line:
- * https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe
- */
-
-.sr-only {
- border: 0;
- clip: rect(0, 0, 0, 0);
- height: 1px;
- margin: -1px;
- overflow: hidden;
- padding: 0;
- position: absolute;
- white-space: nowrap;
- width: 1px;
- /* 1 */
-}
-
-/*
- * Extends the .sr-only class to allow the element
- * to be focusable when navigated to via the keyboard:
- * https://www.drupal.org/node/897638
- */
-
-.sr-only.focusable:active,
-.sr-only.focusable:focus {
- clip: auto;
- height: auto;
- margin: 0;
- overflow: visible;
- position: static;
- white-space: inherit;
- width: auto;
-}
-
-/*
- * Hide visually and from screen readers, but maintain layout
- */
-
-.invisible {
- visibility: hidden;
-}
-
-/*
- * Clearfix: contain floats
- *
- * For modern browsers
- * 1. The space content is one way to avoid an Opera bug when the
- * `contenteditable` attribute is included anywhere else in the document.
- * Otherwise it causes space to appear at the top and bottom of elements
- * that receive the `clearfix` class.
- * 2. The use of `table` rather than `block` is only necessary if using
- * `:before` to contain the top-margins of child elements.
- */
-
-.clearfix::before,
-.clearfix::after {
- content: " ";
- display: table;
-}
-
-.clearfix::after {
- clear: both;
-}
-
-/* ==========================================================================
- EXAMPLE Media Queries for Responsive Design.
- These examples override the primary ('mobile first') styles.
- Modify as content requires.
- ========================================================================== */
-
-@media only screen and (min-width: 35em) {
- /* Style adjustments for viewports that meet the condition */
-}
-
-@media print,
- (-webkit-min-device-pixel-ratio: 1.25),
- (min-resolution: 1.25dppx),
- (min-resolution: 120dpi) {
- /* Style adjustments for high resolution devices */
-}
-
-/* ==========================================================================
- Print styles.
- Inlined to avoid the additional HTTP request:
- https://www.phpied.com/delay-loading-your-print-css/
- ========================================================================== */
-
-@media print {
- *,
- *::before,
- *::after {
- background: #fff !important;
- color: #000 !important;
- /* Black prints faster */
- box-shadow: none !important;
- text-shadow: none !important;
- }
-
- a,
- a:visited {
- text-decoration: underline;
- }
-
- a[href]::after {
- content: " (" attr(href) ")";
- }
-
- abbr[title]::after {
- content: " (" attr(title) ")";
- }
-
- /*
- * Don't show links that are fragment identifiers,
- * or use the `javascript:` pseudo protocol
- */
- a[href^="#"]::after,
- a[href^="javascript:"]::after {
- content: "";
- }
-
- pre {
- white-space: pre-wrap !important;
- }
-
- pre,
- blockquote {
- border: 1px solid #999;
- page-break-inside: avoid;
- }
-
- /*
- * Printing Tables:
- * https://web.archive.org/web/20180815150934/http://css-discuss.incutio.com/wiki/Printing_Tables
- */
- thead {
- display: table-header-group;
- }
-
- tr,
- img {
- page-break-inside: avoid;
- }
-
- p,
- h2,
- h3 {
- orphans: 3;
- widows: 3;
- }
-
- h2,
- h3 {
- page-break-after: avoid;
- }
-}
-
diff --git a/examples/blog/css/normalize.css b/examples/blog/css/normalize.css
deleted file mode 100644
index 192eb9c..0000000
--- a/examples/blog/css/normalize.css
+++ /dev/null
@@ -1,349 +0,0 @@
-/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
-
-/* Document
- ========================================================================== */
-
-/**
- * 1. Correct the line height in all browsers.
- * 2. Prevent adjustments of font size after orientation changes in iOS.
- */
-
-html {
- line-height: 1.15; /* 1 */
- -webkit-text-size-adjust: 100%; /* 2 */
-}
-
-/* Sections
- ========================================================================== */
-
-/**
- * Remove the margin in all browsers.
- */
-
-body {
- margin: 0;
-}
-
-/**
- * Render the `main` element consistently in IE.
- */
-
-main {
- display: block;
-}
-
-/**
- * Correct the font size and margin on `h1` elements within `section` and
- * `article` contexts in Chrome, Firefox, and Safari.
- */
-
-h1 {
- font-size: 2em;
- margin: 0.67em 0;
-}
-
-/* Grouping content
- ========================================================================== */
-
-/**
- * 1. Add the correct box sizing in Firefox.
- * 2. Show the overflow in Edge and IE.
- */
-
-hr {
- box-sizing: content-box; /* 1 */
- height: 0; /* 1 */
- overflow: visible; /* 2 */
-}
-
-/**
- * 1. Correct the inheritance and scaling of font size in all browsers.
- * 2. Correct the odd `em` font sizing in all browsers.
- */
-
-pre {
- font-family: monospace, monospace; /* 1 */
- font-size: 1em; /* 2 */
-}
-
-/* Text-level semantics
- ========================================================================== */
-
-/**
- * Remove the gray background on active links in IE 10.
- */
-
-a {
- background-color: transparent;
-}
-
-/**
- * 1. Remove the bottom border in Chrome 57-
- * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
- */
-
-abbr[title] {
- border-bottom: none; /* 1 */
- text-decoration: underline; /* 2 */
- text-decoration: underline dotted; /* 2 */
-}
-
-/**
- * Add the correct font weight in Chrome, Edge, and Safari.
- */
-
-b,
-strong {
- font-weight: bolder;
-}
-
-/**
- * 1. Correct the inheritance and scaling of font size in all browsers.
- * 2. Correct the odd `em` font sizing in all browsers.
- */
-
-code,
-kbd,
-samp {
- font-family: monospace, monospace; /* 1 */
- font-size: 1em; /* 2 */
-}
-
-/**
- * Add the correct font size in all browsers.
- */
-
-small {
- font-size: 80%;
-}
-
-/**
- * Prevent `sub` and `sup` elements from affecting the line height in
- * all browsers.
- */
-
-sub,
-sup {
- font-size: 75%;
- line-height: 0;
- position: relative;
- vertical-align: baseline;
-}
-
-sub {
- bottom: -0.25em;
-}
-
-sup {
- top: -0.5em;
-}
-
-/* Embedded content
- ========================================================================== */
-
-/**
- * Remove the border on images inside links in IE 10.
- */
-
-img {
- border-style: none;
-}
-
-/* Forms
- ========================================================================== */
-
-/**
- * 1. Change the font styles in all browsers.
- * 2. Remove the margin in Firefox and Safari.
- */
-
-button,
-input,
-optgroup,
-select,
-textarea {
- font-family: inherit; /* 1 */
- font-size: 100%; /* 1 */
- line-height: 1.15; /* 1 */
- margin: 0; /* 2 */
-}
-
-/**
- * Show the overflow in IE.
- * 1. Show the overflow in Edge.
- */
-
-button,
-input { /* 1 */
- overflow: visible;
-}
-
-/**
- * Remove the inheritance of text transform in Edge, Firefox, and IE.
- * 1. Remove the inheritance of text transform in Firefox.
- */
-
-button,
-select { /* 1 */
- text-transform: none;
-}
-
-/**
- * Correct the inability to style clickable types in iOS and Safari.
- */
-
-button,
-[type="button"],
-[type="reset"],
-[type="submit"] {
- -webkit-appearance: button;
-}
-
-/**
- * Remove the inner border and padding in Firefox.
- */
-
-button::-moz-focus-inner,
-[type="button"]::-moz-focus-inner,
-[type="reset"]::-moz-focus-inner,
-[type="submit"]::-moz-focus-inner {
- border-style: none;
- padding: 0;
-}
-
-/**
- * Restore the focus styles unset by the previous rule.
- */
-
-button:-moz-focusring,
-[type="button"]:-moz-focusring,
-[type="reset"]:-moz-focusring,
-[type="submit"]:-moz-focusring {
- outline: 1px dotted ButtonText;
-}
-
-/**
- * Correct the padding in Firefox.
- */
-
-fieldset {
- padding: 0.35em 0.75em 0.625em;
-}
-
-/**
- * 1. Correct the text wrapping in Edge and IE.
- * 2. Correct the color inheritance from `fieldset` elements in IE.
- * 3. Remove the padding so developers are not caught out when they zero out
- * `fieldset` elements in all browsers.
- */
-
-legend {
- box-sizing: border-box; /* 1 */
- color: inherit; /* 2 */
- display: table; /* 1 */
- max-width: 100%; /* 1 */
- padding: 0; /* 3 */
- white-space: normal; /* 1 */
-}
-
-/**
- * Add the correct vertical alignment in Chrome, Firefox, and Opera.
- */
-
-progress {
- vertical-align: baseline;
-}
-
-/**
- * Remove the default vertical scrollbar in IE 10+.
- */
-
-textarea {
- overflow: auto;
-}
-
-/**
- * 1. Add the correct box sizing in IE 10.
- * 2. Remove the padding in IE 10.
- */
-
-[type="checkbox"],
-[type="radio"] {
- box-sizing: border-box; /* 1 */
- padding: 0; /* 2 */
-}
-
-/**
- * Correct the cursor style of increment and decrement buttons in Chrome.
- */
-
-[type="number"]::-webkit-inner-spin-button,
-[type="number"]::-webkit-outer-spin-button {
- height: auto;
-}
-
-/**
- * 1. Correct the odd appearance in Chrome and Safari.
- * 2. Correct the outline style in Safari.
- */
-
-[type="search"] {
- -webkit-appearance: textfield; /* 1 */
- outline-offset: -2px; /* 2 */
-}
-
-/**
- * Remove the inner padding in Chrome and Safari on macOS.
- */
-
-[type="search"]::-webkit-search-decoration {
- -webkit-appearance: none;
-}
-
-/**
- * 1. Correct the inability to style clickable types in iOS and Safari.
- * 2. Change font properties to `inherit` in Safari.
- */
-
-::-webkit-file-upload-button {
- -webkit-appearance: button; /* 1 */
- font: inherit; /* 2 */
-}
-
-/* Interactive
- ========================================================================== */
-
-/*
- * Add the correct display in Edge, IE 10+, and Firefox.
- */
-
-details {
- display: block;
-}
-
-/*
- * Add the correct display in all browsers.
- */
-
-summary {
- display: list-item;
-}
-
-/* Misc
- ========================================================================== */
-
-/**
- * Add the correct display in IE 10+.
- */
-
-template {
- display: none;
-}
-
-/**
- * Add the correct display in IE 10.
- */
-
-[hidden] {
- display: none;
-}
diff --git a/examples/blog/favicon.ico b/examples/blog/favicon.ico
deleted file mode 100644
index be74abd..0000000
Binary files a/examples/blog/favicon.ico and /dev/null differ
diff --git a/examples/blog/humans.txt b/examples/blog/humans.txt
deleted file mode 100644
index 8d2330f..0000000
--- a/examples/blog/humans.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-# humanstxt.org/
-# The humans responsible & technology colophon
-
-# TEAM
-
- -- --
-
-# THANKS
-
-
-
-# TECHNOLOGY COLOPHON
-
- CSS3, HTML5
- Apache Server Configs, jQuery, Modernizr, Normalize.css
diff --git a/examples/blog/img/.gitignore b/examples/blog/img/.gitignore
deleted file mode 100644
index e69de29..0000000
diff --git a/examples/blog/index.uce b/examples/blog/index.uce
deleted file mode 100644
index 0acc4e8..0000000
--- a/examples/blog/index.uce
+++ /dev/null
@@ -1,7 +0,0 @@
-#load "config/settings.uce"
-#load "lib/site.uce"
-
-RENDER()
-{
- render_file("pages/page.html.uce");
-}
diff --git a/examples/blog/js/main.js b/examples/blog/js/main.js
deleted file mode 100644
index e69de29..0000000
diff --git a/examples/blog/js/plugins.js b/examples/blog/js/plugins.js
deleted file mode 100644
index feb7d19..0000000
--- a/examples/blog/js/plugins.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Avoid `console` errors in browsers that lack a console.
-(function() {
- var method;
- var noop = function () {};
- var methods = [
- 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
- 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
- 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
- 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
- ];
- var length = methods.length;
- var console = (window.console = window.console || {});
-
- while (length--) {
- method = methods[length];
-
- // Only stub undefined methods.
- if (!console[method]) {
- console[method] = noop;
- }
- }
-}());
-
-// Place any jQuery/helper plugins in here.
diff --git a/examples/blog/js/vendor/modernizr-3.11.2.min.js b/examples/blog/js/vendor/modernizr-3.11.2.min.js
deleted file mode 100644
index feada51..0000000
--- a/examples/blog/js/vendor/modernizr-3.11.2.min.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/*! modernizr 3.11.2 (Custom Build) | MIT *
- * https://modernizr.com/download/?-cssanimations-csscolumns-customelements-flexbox-history-picture-pointerevents-postmessage-sizes-srcset-webgl-websockets-webworkers-addtest-domprefixes-hasevent-mq-prefixedcssvalue-prefixes-setclasses-testallprops-testprop-teststyles !*/
-!function(e,t,n,r){function o(e,t){return typeof e===t}function i(e){var t=_.className,n=Modernizr._config.classPrefix||"";if(S&&(t=t.baseVal),Modernizr._config.enableJSClass){var r=new RegExp("(^|\\s)"+n+"no-js(\\s|$)");t=t.replace(r,"$1"+n+"js$2")}Modernizr._config.enableClasses&&(e.length>0&&(t+=" "+n+e.join(" "+n)),S?_.className.baseVal=t:_.className=t)}function s(e,t){if("object"==typeof e)for(var n in e)k(e,n)&&s(n,e[n]);else{e=e.toLowerCase();var r=e.split("."),o=Modernizr[r[0]];if(2===r.length&&(o=o[r[1]]),void 0!==o)return Modernizr;t="function"==typeof t?t():t,1===r.length?Modernizr[r[0]]=t:(!Modernizr[r[0]]||Modernizr[r[0]]instanceof Boolean||(Modernizr[r[0]]=new Boolean(Modernizr[r[0]])),Modernizr[r[0]][r[1]]=t),i([(t&&!1!==t?"":"no-")+r.join("-")]),Modernizr._trigger(e,t)}return Modernizr}function a(){return"function"!=typeof n.createElement?n.createElement(arguments[0]):S?n.createElementNS.call(n,"http://www.w3.org/2000/svg",arguments[0]):n.createElement.apply(n,arguments)}function l(){var e=n.body;return e||(e=a(S?"svg":"body"),e.fake=!0),e}function u(e,t,r,o){var i,s,u,f,c="modernizr",d=a("div"),p=l();if(parseInt(r,10))for(;r--;)u=a("div"),u.id=o?o[r]:c+(r+1),d.appendChild(u);return i=a("style"),i.type="text/css",i.id="s"+c,(p.fake?p:d).appendChild(i),p.appendChild(d),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(n.createTextNode(e)),d.id=c,p.fake&&(p.style.background="",p.style.overflow="hidden",f=_.style.overflow,_.style.overflow="hidden",_.appendChild(p)),s=t(d,e),p.fake?(p.parentNode.removeChild(p),_.style.overflow=f,_.offsetHeight):d.parentNode.removeChild(d),!!s}function f(e,n,r){var o;if("getComputedStyle"in t){o=getComputedStyle.call(t,e,n);var i=t.console;if(null!==o)r&&(o=o.getPropertyValue(r));else if(i){var s=i.error?"error":"log";i[s].call(i,"getComputedStyle returning null, its possible modernizr test results are inaccurate")}}else o=!n&&e.currentStyle&&e.currentStyle[r];return o}function c(e,t){return!!~(""+e).indexOf(t)}function d(e){return e.replace(/([A-Z])/g,function(e,t){return"-"+t.toLowerCase()}).replace(/^ms-/,"-ms-")}function p(e,n){var o=e.length;if("CSS"in t&&"supports"in t.CSS){for(;o--;)if(t.CSS.supports(d(e[o]),n))return!0;return!1}if("CSSSupportsRule"in t){for(var i=[];o--;)i.push("("+d(e[o])+":"+n+")");return i=i.join(" or "),u("@supports ("+i+") { #modernizr { position: absolute; } }",function(e){return"absolute"===f(e,null,"position")})}return r}function m(e){return e.replace(/([a-z])-([a-z])/g,function(e,t,n){return t+n.toUpperCase()}).replace(/^-/,"")}function h(e,t,n,i){function s(){u&&(delete N.style,delete N.modElem)}if(i=!o(i,"undefined")&&i,!o(n,"undefined")){var l=p(e,n);if(!o(l,"undefined"))return l}for(var u,f,d,h,A,v=["modernizr","tspan","samp"];!N.style&&v.length;)u=!0,N.modElem=a(v.shift()),N.style=N.modElem.style;for(d=e.length,f=0;f
- Hello
- >
- return(0);
-}
-
-RENDER()
-{
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- String view_name = safe_name(first(context->get["view"], "default")) + ".uce";
- if(file_exists(view_name))
- render_file(view_name);
- else
- print("view not found");
- ?>
-
-
-
- >
-}
diff --git a/examples/blog/robots.txt b/examples/blog/robots.txt
deleted file mode 100644
index d0e5f1b..0000000
--- a/examples/blog/robots.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# www.robotstxt.org/
-
-# Allow crawling of all content
-User-agent: *
-Disallow:
diff --git a/examples/blog/site.webmanifest b/examples/blog/site.webmanifest
deleted file mode 100644
index 222ae16..0000000
--- a/examples/blog/site.webmanifest
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "short_name": "",
- "name": "",
- "icons": [{
- "src": "icon.png",
- "type": "image/png",
- "sizes": "192x192"
- }],
- "start_url": "/?utm_source=homescreen",
- "background_color": "#fafafa",
- "theme_color": "#fafafa"
-}
diff --git a/examples/blog/tile-wide.png b/examples/blog/tile-wide.png
deleted file mode 100644
index ccd739c..0000000
Binary files a/examples/blog/tile-wide.png and /dev/null differ
diff --git a/examples/blog/tile.png b/examples/blog/tile.png
deleted file mode 100644
index f820f61..0000000
Binary files a/examples/blog/tile.png and /dev/null differ
diff --git a/scripts/setup.h.template b/scripts/setup.h.template
index efacf04..bf8e77d 100644
--- a/scripts/setup.h.template
+++ b/scripts/setup.h.template
@@ -1,8 +1,12 @@
+#ifndef UCE_SET_CURRENT_REQUEST_DEFINED
+#define UCE_SET_CURRENT_REQUEST_DEFINED
+
/*load_declarations*/
extern "C" void set_current_request(Request* _request)
{
context = _request;
- signal(SIGSEGV, on_segfault);
/*load_units*/
}
+
+#endif
diff --git a/site/doc/areas/markup.txt b/site/doc/areas/markup.txt
new file mode 100644
index 0000000..70bdb03
--- /dev/null
+++ b/site/doc/areas/markup.txt
@@ -0,0 +1,3 @@
+Markup Functions
+markdown_to_ast
+markdown_to_html
diff --git a/doc/areas/memcache.txt b/site/doc/areas/memcache.txt
similarity index 100%
rename from doc/areas/memcache.txt
rename to site/doc/areas/memcache.txt
diff --git a/doc/areas/mysql.txt b/site/doc/areas/mysql.txt
similarity index 100%
rename from doc/areas/mysql.txt
rename to site/doc/areas/mysql.txt
diff --git a/doc/areas/noise.txt b/site/doc/areas/noise.txt
similarity index 100%
rename from doc/areas/noise.txt
rename to site/doc/areas/noise.txt
diff --git a/doc/areas/ob.txt b/site/doc/areas/ob.txt
similarity index 58%
rename from doc/areas/ob.txt
rename to site/doc/areas/ob.txt
index fea6ddc..a394486 100644
--- a/doc/areas/ob.txt
+++ b/site/doc/areas/ob.txt
@@ -1,10 +1,15 @@
Output / Invocation Functions
+1_RENDER
call_file
+component
+component_exists
+component_resolve
load
ob_close
ob_get
ob_get_close
ob_start
print
+render_component
render_file
diff --git a/doc/areas/session.txt b/site/doc/areas/session.txt
similarity index 100%
rename from doc/areas/session.txt
rename to site/doc/areas/session.txt
diff --git a/doc/areas/socket.txt b/site/doc/areas/socket.txt
similarity index 100%
rename from doc/areas/socket.txt
rename to site/doc/areas/socket.txt
diff --git a/doc/areas/string.txt b/site/doc/areas/string.txt
similarity index 100%
rename from doc/areas/string.txt
rename to site/doc/areas/string.txt
diff --git a/doc/areas/sys.txt b/site/doc/areas/sys.txt
similarity index 100%
rename from doc/areas/sys.txt
rename to site/doc/areas/sys.txt
diff --git a/doc/areas/task.txt b/site/doc/areas/task.txt
similarity index 100%
rename from doc/areas/task.txt
rename to site/doc/areas/task.txt
diff --git a/doc/areas/time.txt b/site/doc/areas/time.txt
similarity index 100%
rename from doc/areas/time.txt
rename to site/doc/areas/time.txt
diff --git a/site/doc/areas/types.txt b/site/doc/areas/types.txt
new file mode 100644
index 0000000..854b9c7
--- /dev/null
+++ b/site/doc/areas/types.txt
@@ -0,0 +1,5 @@
+Types
+
+DTree
+String
+StringMap
diff --git a/doc/areas/uri.txt b/site/doc/areas/uri.txt
similarity index 100%
rename from doc/areas/uri.txt
rename to site/doc/areas/uri.txt
diff --git a/doc/areas/websocket.txt b/site/doc/areas/websocket.txt
similarity index 91%
rename from doc/areas/websocket.txt
rename to site/doc/areas/websocket.txt
index 3ad8b88..c7470ea 100644
--- a/doc/areas/websocket.txt
+++ b/site/doc/areas/websocket.txt
@@ -1,6 +1,5 @@
WebSocket Functions
-ws_broadcast
ws_close
ws_connection_count
ws_connection_id
diff --git a/doc/index.uce b/site/doc/index.uce
similarity index 97%
rename from doc/index.uce
rename to site/doc/index.uce
index 74d7152..3ff7c8a 100644
--- a/doc/index.uce
+++ b/site/doc/index.uce
@@ -19,10 +19,10 @@ void render_see_section(String name)
}
-RENDER()
+RENDER(Request& context)
{
- String page = first(context->get["p"], "index");
+ String page = first(context.get["p"], "index");
<>
diff --git a/doc/pages/0_context.txt b/site/doc/pages/0_context.txt
similarity index 71%
rename from doc/pages/0_context.txt
rename to site/doc/pages/0_context.txt
index b414d2c..da04738 100644
--- a/doc/pages/0_context.txt
+++ b/site/doc/pages/0_context.txt
@@ -1,5 +1,5 @@
:sig
-Request* context;
+Request& context;
:ServerState* server
Contains the current server state
@@ -22,12 +22,18 @@ The current session
:String session_id
ID of the session cookie
-String session_name
+:String session_name
Name of the session cookie
:DTree var
Variable user-defined data
+:DTree call
+Invocation or message-local structured data
+
+:DTree connection
+Broker-owned per-WebSocket-connection state. Inside `WS(Request& context)`, updates to this tree persist for the lifetime of that socket connection.
+
:std::vector uploaded_files
Files that have been uploaded in the current request
@@ -43,9 +49,6 @@ The current request's "random" noise generator seed
:u64 random_index
The current request's "random" noise generator index position
-:MemoryArena* mem
-Contains the current request's memory arena
-
:bool flags.log_request
Whether the request should be logged
@@ -55,9 +58,10 @@ Whether the request should be logged
f64 stats.time_start
f64 stats.time_end
-:invoke(String file_name, [DTree& call_param])
-Invokes the UCE file 'file_name'
-
-
+:render_file(String file_name, [Request& context])
+Invokes another UCE file using the current or supplied request context
+
+:see
+>types
diff --git a/site/doc/pages/1_RENDER.txt b/site/doc/pages/1_RENDER.txt
new file mode 100644
index 0000000..562c296
--- /dev/null
+++ b/site/doc/pages/1_RENDER.txt
@@ -0,0 +1,24 @@
+:sig
+RENDER(Request& context)
+
+:desc
+Defines the main HTTP render handler for the current `.uce` page.
+
+When a page is requested over HTTP, the runtime loads the target file and calls its `RENDER(Request& context)` function.
+
+Pages may also export additional named render handlers with `RENDER:NAME(Request& context)`.
+
+Named render handlers are not used for the page's direct HTTP entrypoint. They are intended for component-style sub-rendering through helpers such as `component("components/card:BODY", props, context)` or `render_component("components/card:BODY", props, context)`.
+
+The default page entrypoint is always the plain `RENDER(Request& context)` handler.
+
+The request environment is passed explicitly through `context`, including params, cookies, post data, session state, headers, uploaded files, and the current `context.call` tree.
+
+For a normal direct page request, `context.call` starts empty.
+
+If the page is invoked from another UCE file via `render_file(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)`. In that case `RENDER(Request& context)` serves the initial HTTP response and `WS(Request& context)` handles subsequent WebSocket messages.
+
+:see
+>ob
diff --git a/site/doc/pages/1_WS.txt b/site/doc/pages/1_WS.txt
new file mode 100644
index 0000000..ce39d39
--- /dev/null
+++ b/site/doc/pages/1_WS.txt
@@ -0,0 +1,26 @@
+:sig
+WS(Request& context)
+
+:desc
+Defines the WebSocket message handler for the current `.ws.uce` page.
+
+The same page may expose both `RENDER(Request& context)` and `WS(Request& context)`. `RENDER(Request& context)` serves the initial HTTP response, while `WS(Request& context)` is called whenever a complete WebSocket message arrives for that page.
+
+UCE reassembles fragmented messages before calling `WS(Request& context)`. Text and binary frames are both delivered. Use `context.call`, `context.connection`, `ws_opcode()`, and `ws_is_binary()` to inspect the current message.
+
+`context.connection` is a broker-owned `DTree` for the current socket. It starts empty for a new client and persists across later `WS(Request& context)` calls on that same connection.
+
+The current message data is available in `context.call`:
+
+context.call["message"] : current message payload
+
+context.call["connection_id"] : sender connection ID
+
+context.call["scope"] : current endpoint scope
+
+context.call["opcode"] : WebSocket opcode of the current message
+
+context.call["document_uri"] : request URI of the current endpoint
+
+:see
+>websocket
diff --git a/site/doc/pages/1_preprocessor.txt b/site/doc/pages/1_preprocessor.txt
new file mode 100644
index 0000000..b6ee0be
--- /dev/null
+++ b/site/doc/pages/1_preprocessor.txt
@@ -0,0 +1,95 @@
+:sig
+UCE source preprocessing
+
+:desc
+UCE runs a small custom source-to-source preprocessor before Clang sees a `.uce` or `.ws.uce` file.
+
+The implementation lives in `src/lib/compiler.cpp`. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands UCE literal blocks, inline code islands, `#load`, and `EXPORT` harvesting, then writes a generated `.cpp` file and compiles that file into a shared object.
+
+:Syntax
+- `<> ... >` enters literal-output mode.
+- Inside a literal block, ` ... ?>` emits raw C++.
+- Inside a literal block, `= expression ?>` emits `print(html_escape(expression));`.
+- Inside a literal block, `` emits `print(expression);` without HTML escaping.
+- `#load "other.uce"` injects another UCE unit at compile time.
+- `RENDER(Request& context)` and `WS(Request& context)` are normal C++ macros from `src/lib/compiler.h`.
+- `EXPORT` is also a normal C++ macro, but the custom pass additionally records exported declarations for metadata.
+
+:Pipeline
+- The generated file starts by including `COMPILER_SYS_PATH/src/lib/uce_lib.h`.
+- It then inlines the configured setup template from `SETUP_TEMPLATE` (by default `scripts/setup.h.template`), which defines `set_current_request(Request*)`.
+- It inserts `#line 1` before page code so compiler diagnostics point back to the original `.uce` file.
+- Each literal block is rewritten into one or more `print(R"( ... )");` calls.
+- ` ... ?>` temporarily breaks out of literal printing, emits the enclosed C++ unchanged, then resumes literal output.
+- `= ... ?>` becomes `print(html_escape(...));`. The runtime currently provides `html_escape()` overloads for `String`, `u64`, and `f64`.
+- `` becomes `print(...);` and is intended for trusted markup or already-escaped content.
+- `#load "file.uce"` is replaced with a generated C++ `#include` that points at the loaded unit's preprocessed `.cpp` file under `BIN_DIRECTORY`.
+- Lines beginning with `EXPORT` are scanned so their declarations can be written to a sibling `.exports.txt` file.
+- The final generated source is written to `BIN_DIRECTORY + src_path + "/" + source_file + ".cpp"`.
+- `scripts/compile` then compiles that generated `.cpp` into `source_file + ".so"` with `clang++ -shared -std=c++20 ...`.
+
+:GeneratedFiles
+- Source file: `/some/path/page.uce`
+- Generated C++: `BIN_DIRECTORY/some/path/page.uce.cpp`
+- Shared object: `BIN_DIRECTORY/some/path/page.uce.so`
+- Export list: `BIN_DIRECTORY/some/path/page.uce.exports.txt`
+
+:Example
+Example 1: literal output with escaped data
+`RENDER(Request& context)`
+`{`
+` <><h1><?= context.params["DOCUMENT_URI"] ?></h1></>`
+`}`
+
+Roughly becomes:
+`print(R"(<h1>)");`
+`print(html_escape(context.params["DOCUMENT_URI"]));`
+`print(R"(</h1>)");`
+
+Example 1b: literal output with trusted unescaped markup
+`RENDER(Request& context)`
+`{`
+` <><div class="panel"><?: component("components/card", context.call, context) ?></div></>`
+`}`
+
+Roughly becomes:
+`print(R"(<div class="panel">)");`
+`print(component("components/card", context.call, context));`
+`print(R"(</div>)");`
+
+Example 2: compile-time composition
+`#load "partials/nav.uce"`
+`RENDER(Request& context)`
+`{`
+` <><body>...</body></>`
+`}`
+
+The loaded file is resolved relative to the current source file unless the path is already absolute.
+
+:Rules
+- Literal mode starts only on the exact token `<>`.
+- Literal mode ends only on the exact token `>`.
+- `#load` is recognized only when the current line starts with `#load ` at column 1.
+- `EXPORT` harvesting likewise only triggers when the current line starts with `EXPORT` at column 1 and is followed by whitespace.
+- Relative `#load` paths are expanded against the including unit's source directory.
+- `render_file()` and `call_file()` are runtime APIs; `#load` is a compile-time include/composition feature.
+
+:Limitations
+- This pass is character-wise, not a full parser.
+- Outside literal blocks it only tracks double-quoted C++ strings while deciding whether `<>` should open literal mode.
+- It does not understand comments, raw string literals, templates, or general C++ token structure.
+- Inside literal blocks it tracks single and double quotes while scanning a ` ... ?>` island so quoted `?>` text does not close the island early.
+- Literal blocks are not nested.
+- Because literal output is emitted as a C++ raw string literal `R"( ... )"`, literal content must not contain the exact terminator sequence `)"` or the generated C++ will break.
+- `#load` depends on the target unit's generated `.cpp` existing and being compilable. If the target cannot be preprocessed or compiled correctly, the including file will fail to compile as well.
+
+:Debugging
+- When a page is compiled, inspect the generated file under `BIN_DIRECTORY` first. That file shows the exact C++ produced by the UCE preprocessor.
+- Compiler errors usually point back to the `.uce` source because the preprocessor inserts `#line 1`, but the generated `.cpp` is still the best place to inspect expansion problems.
+- If a `#load` include looks wrong, check the current file's directory, the configured `BIN_DIRECTORY`, and whether the loaded page already produced its own generated `.cpp`.
+
+:see
+load
+render_file
+call_file
+0_context
diff --git a/site/doc/pages/DTree.txt b/site/doc/pages/DTree.txt
new file mode 100644
index 0000000..84cb1bc
--- /dev/null
+++ b/site/doc/pages/DTree.txt
@@ -0,0 +1,27 @@
+:sig
+DTree
+
+:desc
+Dynamic tree/container type used throughout UCE for structured data.
+
+`DTree` can hold a `String`, `f64`, `bool`, pointer, or a nested map of child `DTree` values.
+
+Use `t["key"]` to access or create child entries. Use `push()` / `pop()` when treating it like an array-like container with numeric string keys.
+
+Common uses include:
+`json_decode()` / `json_encode()`
+`context.var`
+`context.call`
+`call_file()` return values
+
+Useful methods include:
+`to_string()`
+`to_json()`
+`get_type_name()`
+`set_bool()`
+`remove()`
+`clear()`
+`each()`
+
+:see
+>types
diff --git a/site/doc/pages/String.txt b/site/doc/pages/String.txt
new file mode 100644
index 0000000..73987a7
--- /dev/null
+++ b/site/doc/pages/String.txt
@@ -0,0 +1,16 @@
+:sig
+String
+
+:desc
+Primary string type used throughout UCE.
+
+`String` is an alias for `std::string`.
+
+It is used for request data, headers, cookie values, file contents, query strings, JSON text, and WebSocket payloads.
+
+Because it is backed by `std::string`, it is binary-safe and may also contain raw bytes.
+
+For UTF-8-aware splitting, use helpers such as `split_utf8()` instead of assuming one byte equals one character.
+
+:see
+>types
diff --git a/site/doc/pages/StringMap.txt b/site/doc/pages/StringMap.txt
new file mode 100644
index 0000000..ed72705
--- /dev/null
+++ b/site/doc/pages/StringMap.txt
@@ -0,0 +1,22 @@
+:sig
+StringMap
+
+:desc
+Associative container mapping `String` keys to `String` values.
+
+`StringMap` is an alias for `std::map`.
+
+It is commonly used for:
+`context.params`
+`context.get`
+`context.post`
+`context.cookies`
+`context.session`
+`context.header`
+
+Because it uses `std::map`, `map["key"]` will create an empty entry when that key does not already exist.
+
+Related helpers such as `parse_query()` and `encode_query()` convert between query strings and `StringMap` values.
+
+:see
+>types
diff --git a/doc/pages/basename.txt b/site/doc/pages/basename.txt
similarity index 100%
rename from doc/pages/basename.txt
rename to site/doc/pages/basename.txt
diff --git a/doc/pages/call_file.txt b/site/doc/pages/call_file.txt
similarity index 100%
rename from doc/pages/call_file.txt
rename to site/doc/pages/call_file.txt
diff --git a/site/doc/pages/component.txt b/site/doc/pages/component.txt
new file mode 100644
index 0000000..ff89191
--- /dev/null
+++ b/site/doc/pages/component.txt
@@ -0,0 +1,28 @@
+:sig
+String component(String name, [DTree props], [Request& context])
+
+:desc
+Renders another `.uce` file as a component and returns the captured output as a `String`.
+
+`component()` resolves the target file relative to the current page and also tries the `components/` prefix automatically, mirroring the shorthand used by the web app starter example project.
+
+Component props are passed in `context.call`.
+
+Because `= ... ?>` HTML-escapes its value, embed component markup with ``, `print(component(...))`, or use `render_component(...)` for direct output.
+
+When `name` contains a colon, such as `components/card:BODY`, the part after the colon selects a named render handler exported from the component file through `RENDER:BODY(Request& context)`.
+
+The default handler is `RENDER(Request& context)`.
+
+Resolution order is:
+exact file name
+exact file name with `.uce`
+the same two forms under `components/`
+
+Example:
+`DTree props;`
+`props["title"] = "Status";`
+`<><?: component("workspace/panel", props, context) ?></>`
+
+:see
+>ob
diff --git a/site/doc/pages/component_exists.txt b/site/doc/pages/component_exists.txt
new file mode 100644
index 0000000..677c34b
--- /dev/null
+++ b/site/doc/pages/component_exists.txt
@@ -0,0 +1,14 @@
+:sig
+bool component_exists(String name)
+
+:desc
+Checks whether a component file can be resolved from the current page context.
+
+Resolution tries the exact name first and then the `components/` shorthand form.
+
+If `name` contains a colon, only the file portion is used for existence checks.
+
+This is useful when a page wants to render an optional component if it is present without hard-failing when it is missing.
+
+:see
+>ob
diff --git a/site/doc/pages/component_resolve.txt b/site/doc/pages/component_resolve.txt
new file mode 100644
index 0000000..f8294fa
--- /dev/null
+++ b/site/doc/pages/component_resolve.txt
@@ -0,0 +1,14 @@
+:sig
+String component_resolve(String name)
+
+:desc
+Resolves a component name to the concrete `.uce` file path that will be loaded.
+
+Resolution tries the exact file name first, then the same name with `.uce` appended, and then the same two forms under the `components/` prefix.
+
+If `name` contains a colon, only the file portion is used for resolution.
+
+This is primarily a debugging helper so you can see which concrete file a shorthand component name maps to.
+
+:see
+>ob
diff --git a/doc/pages/concat.txt b/site/doc/pages/concat.txt
similarity index 100%
rename from doc/pages/concat.txt
rename to site/doc/pages/concat.txt
diff --git a/doc/pages/date.txt b/site/doc/pages/date.txt
similarity index 100%
rename from doc/pages/date.txt
rename to site/doc/pages/date.txt
diff --git a/doc/pages/dirname.txt b/site/doc/pages/dirname.txt
similarity index 100%
rename from doc/pages/dirname.txt
rename to site/doc/pages/dirname.txt
diff --git a/site/doc/pages/draw_float.txt b/site/doc/pages/draw_float.txt
new file mode 100644
index 0000000..036a8f9
--- /dev/null
+++ b/site/doc/pages/draw_float.txt
@@ -0,0 +1,13 @@
+:sig
+f64 draw_float(f64 from, f64 to)
+
+:params
+from : minimum value
+to : maximum value
+return value : a noise value between 'from' and 'to'
+
+:desc
+This function works exactly like generate_float(), but context.random_index is used for the 'index' value and context.random_seed is used for the seed. After this function has been called, the context.random_index is increased by one. At the start of every request, context.random_seed is automatically populated with a new seed value.
+
+:see
+>noise
diff --git a/site/doc/pages/draw_int.txt b/site/doc/pages/draw_int.txt
new file mode 100644
index 0000000..86dcea1
--- /dev/null
+++ b/site/doc/pages/draw_int.txt
@@ -0,0 +1,13 @@
+:sig
+u64 draw_int(u64 from, u64 to)
+
+:params
+from : minimum value
+to : maximum value
+return value : a noise value between 'from' and 'to'
+
+:desc
+This function works exactly like generate_int(), but context.random_index is used for the 'index' value and context.random_seed is used for the seed. After this function has been called, the context.random_index is increased by one. At the start of every request, context.random_seed is automatically populated with a new seed value.
+
+:see
+>noise
diff --git a/doc/pages/encode_query.txt b/site/doc/pages/encode_query.txt
similarity index 100%
rename from doc/pages/encode_query.txt
rename to site/doc/pages/encode_query.txt
diff --git a/doc/pages/expand_path.txt b/site/doc/pages/expand_path.txt
similarity index 100%
rename from doc/pages/expand_path.txt
rename to site/doc/pages/expand_path.txt
diff --git a/doc/pages/file_append.txt b/site/doc/pages/file_append.txt
similarity index 100%
rename from doc/pages/file_append.txt
rename to site/doc/pages/file_append.txt
diff --git a/doc/pages/file_exists.txt b/site/doc/pages/file_exists.txt
similarity index 100%
rename from doc/pages/file_exists.txt
rename to site/doc/pages/file_exists.txt
diff --git a/doc/pages/file_get_contents.txt b/site/doc/pages/file_get_contents.txt
similarity index 100%
rename from doc/pages/file_get_contents.txt
rename to site/doc/pages/file_get_contents.txt
diff --git a/doc/pages/file_mtime.txt b/site/doc/pages/file_mtime.txt
similarity index 100%
rename from doc/pages/file_mtime.txt
rename to site/doc/pages/file_mtime.txt
diff --git a/doc/pages/file_put_contents.txt b/site/doc/pages/file_put_contents.txt
similarity index 100%
rename from doc/pages/file_put_contents.txt
rename to site/doc/pages/file_put_contents.txt
diff --git a/doc/pages/filter.txt b/site/doc/pages/filter.txt
similarity index 100%
rename from doc/pages/filter.txt
rename to site/doc/pages/filter.txt
diff --git a/doc/pages/first.txt b/site/doc/pages/first.txt
similarity index 100%
rename from doc/pages/first.txt
rename to site/doc/pages/first.txt
diff --git a/doc/pages/float_val.txt b/site/doc/pages/float_val.txt
similarity index 100%
rename from doc/pages/float_val.txt
rename to site/doc/pages/float_val.txt
diff --git a/doc/pages/gen_float.txt b/site/doc/pages/gen_float.txt
similarity index 100%
rename from doc/pages/gen_float.txt
rename to site/doc/pages/gen_float.txt
diff --git a/doc/pages/gen_int.txt b/site/doc/pages/gen_int.txt
similarity index 100%
rename from doc/pages/gen_int.txt
rename to site/doc/pages/gen_int.txt
diff --git a/doc/pages/gen_noise01.txt b/site/doc/pages/gen_noise01.txt
similarity index 100%
rename from doc/pages/gen_noise01.txt
rename to site/doc/pages/gen_noise01.txt
diff --git a/doc/pages/gen_noise32.txt b/site/doc/pages/gen_noise32.txt
similarity index 100%
rename from doc/pages/gen_noise32.txt
rename to site/doc/pages/gen_noise32.txt
diff --git a/doc/pages/gen_noise64.txt b/site/doc/pages/gen_noise64.txt
similarity index 100%
rename from doc/pages/gen_noise64.txt
rename to site/doc/pages/gen_noise64.txt
diff --git a/doc/pages/gen_sha1.txt b/site/doc/pages/gen_sha1.txt
similarity index 100%
rename from doc/pages/gen_sha1.txt
rename to site/doc/pages/gen_sha1.txt
diff --git a/doc/pages/get_cwd.txt b/site/doc/pages/get_cwd.txt
similarity index 100%
rename from doc/pages/get_cwd.txt
rename to site/doc/pages/get_cwd.txt
diff --git a/doc/pages/gmdate.txt b/site/doc/pages/gmdate.txt
similarity index 100%
rename from doc/pages/gmdate.txt
rename to site/doc/pages/gmdate.txt
diff --git a/doc/pages/html_escape.txt b/site/doc/pages/html_escape.txt
similarity index 100%
rename from doc/pages/html_escape.txt
rename to site/doc/pages/html_escape.txt
diff --git a/doc/pages/int_val.txt b/site/doc/pages/int_val.txt
similarity index 100%
rename from doc/pages/int_val.txt
rename to site/doc/pages/int_val.txt
diff --git a/doc/pages/join.txt b/site/doc/pages/join.txt
similarity index 100%
rename from doc/pages/join.txt
rename to site/doc/pages/join.txt
diff --git a/doc/pages/json_decode.txt b/site/doc/pages/json_decode.txt
similarity index 100%
rename from doc/pages/json_decode.txt
rename to site/doc/pages/json_decode.txt
diff --git a/doc/pages/json_encode.txt b/site/doc/pages/json_encode.txt
similarity index 100%
rename from doc/pages/json_encode.txt
rename to site/doc/pages/json_encode.txt
diff --git a/doc/pages/kill.txt b/site/doc/pages/kill.txt
similarity index 100%
rename from doc/pages/kill.txt
rename to site/doc/pages/kill.txt
diff --git a/doc/pages/load.txt b/site/doc/pages/load.txt
similarity index 100%
rename from doc/pages/load.txt
rename to site/doc/pages/load.txt
diff --git a/doc/pages/ls.txt b/site/doc/pages/ls.txt
similarity index 100%
rename from doc/pages/ls.txt
rename to site/doc/pages/ls.txt
diff --git a/doc/pages/make_session_id.txt b/site/doc/pages/make_session_id.txt
similarity index 100%
rename from doc/pages/make_session_id.txt
rename to site/doc/pages/make_session_id.txt
diff --git a/site/doc/pages/markdown_to_ast.txt b/site/doc/pages/markdown_to_ast.txt
new file mode 100644
index 0000000..d772b54
--- /dev/null
+++ b/site/doc/pages/markdown_to_ast.txt
@@ -0,0 +1,77 @@
+:sig
+DTree markdown_to_ast(String src)
+DTree markdown_to_ast(String src, DTree options)
+
+:params
+src : markdown source text
+options : optional markdown options tree
+return value : a `DTree` document AST
+
+:desc
+Parses Markdown source into a structured `DTree` document tree.
+
+The parser targets a practical GitHub-flavored subset by default:
+- ATX headings (`#`)
+- setext headings
+- paragraphs
+- blockquotes
+- ordered and unordered lists
+- task list items
+- fenced code blocks
+- tables
+- horizontal rules
+- emphasis / strong / strikethrough
+- links, images, autolinks, and code spans
+- `:::` directive blocks for component-based extensions
+
+The returned AST uses `type` plus node-specific fields such as `level`, `text`, `lang`, `href`, `src`, `name`, `argument`, `attrs`, and `children`.
+
+Top-level documents use:
+`type = "document"`
+`children = [...]`
+
+Common block nodes:
+`heading`
+`paragraph`
+`blockquote`
+`list`
+`list_item`
+`code_block`
+`table`
+`directive`
+`hr`
+
+Common inline nodes:
+`text`
+`code`
+`strong`
+`em`
+`strike`
+`link`
+`image`
+`raw_html`
+
+:Example
+`DTree options = json_decode("{\"components\":{\":::warning\":\"components/markdown/warning\"}}");`
+`DTree ast = markdown_to_ast(file_get_contents("README.md"), options);`
+`print(json_encode(ast));`
+
+:Options
+`options["gfm"]`
+Enables GitHub-style extras such as tables, task lists, strikethrough, and bare-URL autolinks.
+Defaults to true.
+
+`options["allow_html"]`
+Allows raw HTML passthrough nodes to be captured and rendered.
+Defaults to false.
+
+`options["components"]`
+Component hook map used later by `markdown_to_html()`.
+The parser preserves directive data needed by those hooks.
+
+:see
+markdown_to_html
+component
+render_component
+json_encode
+DTree
diff --git a/site/doc/pages/markdown_to_html.txt b/site/doc/pages/markdown_to_html.txt
new file mode 100644
index 0000000..090b80c
--- /dev/null
+++ b/site/doc/pages/markdown_to_html.txt
@@ -0,0 +1,100 @@
+:sig
+String markdown_to_html(String src)
+String markdown_to_html(String src, DTree options)
+
+:params
+src : markdown source text
+options : optional markdown options tree
+return value : rendered HTML string
+
+:desc
+Renders Markdown source into HTML and returns the generated markup as a `String`.
+
+`markdown_to_html()` does not write to the output stream directly. This keeps it aligned with the UCE naming convention where `render_*` names are reserved for direct-output helpers.
+
+Because the return value is HTML markup, embed it with ``, `print(markdown_to_html(...))`, or pass it through a component.
+
+By default the function aims at a practical GitHub-flavored Markdown target, including tables, task lists, fenced code blocks, autolinks, and strikethrough.
+
+:Example
+`DTree options;`
+`options["components"][":::warning"] = "components/markdown/warning";`
+`options["components"]["node.code_block"] = "components/markdown/code_block";`
+`String html = markdown_to_html(file_get_contents("guide.md"), options);`
+`print(html);`
+
+:SupportedSyntax
+- headings with `#` or setext underlines
+- paragraphs
+- ordered and unordered lists
+- task lists
+- blockquotes
+- fenced code blocks
+- horizontal rules
+- tables
+- inline emphasis, strong, strikethrough, code spans
+- links, images, and bare `http://` / `https://` URLs
+- `:::name ... :::` directive blocks
+
+:Options
+`options["gfm"]`
+Defaults to true.
+Turns on GitHub-style extras such as tables, task lists, autolinks, and strikethrough.
+
+`options["allow_html"]`
+Defaults to false.
+When true, raw HTML blocks and inline tags may pass through as `raw_html` nodes instead of being escaped as plain text.
+
+`options["components"]`
+Declares renderer extension points using normal UCE components.
+
+Exact directive hooks:
+`options["components"][":::warning"] = "components/markdown/warning"`
+This hook is selected for `:::warning ... :::` blocks.
+
+Generic node hooks:
+`options["components"]["node.code_block"] = "components/markdown/code_block"`
+`options["components"]["node.table"] = "components/markdown/table"`
+`options["components"]["node.link"] = "components/markdown/link"`
+`options["components"]["node.directive"] = "components/markdown/directive"`
+
+If both an exact directive hook and a generic `node.directive` hook exist, the exact directive hook wins.
+
+:ComponentProps
+When a markdown hook component is called, its props arrive in `context.call`.
+
+Useful fields include:
+`context.call["hook"]` : matched hook key such as `:::warning` or `node.code_block`
+`context.call["target"]` : resolved component target name
+`context.call["default_html"]` : renderer output without the hook
+`context.call["children_html"]` : already-rendered child HTML
+`context.call["node"]` : full AST node
+`context.call["type"]` : node type
+`context.call["name"]` : directive name when applicable
+`context.call["argument"]` : directive remainder after the name
+`context.call["text"]` : source text for nodes such as `code_block`
+`context.call["lang"]` : fenced code language
+`context.call["href"]` / `context.call["src"]` / `context.call["title"]`
+`context.call["options"]` : full markdown options tree
+
+This lets a component either replace the HTML completely or wrap `default_html` / `children_html`.
+
+:DirectiveSchema
+Directive blocks use this form:
+`:::warning title="Heads up"`
+`Body markdown here`
+`:::`
+
+The parser stores:
+`node["name"] = "warning"`
+`node["argument"] = ...` for bare trailing text
+`node["attrs"] = ...` for parsed `key=value` pairs such as `title="Heads up"`
+
+This makes directive components a good fit for alerts, callouts, cards, embeds, and any richer page-level markdown extension.
+
+:see
+markdown_to_ast
+component
+render_component
+json_decode
+String
diff --git a/doc/pages/memcache_command.txt b/site/doc/pages/memcache_command.txt
similarity index 100%
rename from doc/pages/memcache_command.txt
rename to site/doc/pages/memcache_command.txt
diff --git a/doc/pages/memcache_connect.txt b/site/doc/pages/memcache_connect.txt
similarity index 100%
rename from doc/pages/memcache_connect.txt
rename to site/doc/pages/memcache_connect.txt
diff --git a/doc/pages/memcache_delete.txt b/site/doc/pages/memcache_delete.txt
similarity index 100%
rename from doc/pages/memcache_delete.txt
rename to site/doc/pages/memcache_delete.txt
diff --git a/doc/pages/memcache_get.txt b/site/doc/pages/memcache_get.txt
similarity index 100%
rename from doc/pages/memcache_get.txt
rename to site/doc/pages/memcache_get.txt
diff --git a/doc/pages/memcache_get_multiple.txt b/site/doc/pages/memcache_get_multiple.txt
similarity index 100%
rename from doc/pages/memcache_get_multiple.txt
rename to site/doc/pages/memcache_get_multiple.txt
diff --git a/doc/pages/memcache_set.txt b/site/doc/pages/memcache_set.txt
similarity index 100%
rename from doc/pages/memcache_set.txt
rename to site/doc/pages/memcache_set.txt
diff --git a/doc/pages/microtime.txt b/site/doc/pages/microtime.txt
similarity index 100%
rename from doc/pages/microtime.txt
rename to site/doc/pages/microtime.txt
diff --git a/doc/pages/mkdir.txt b/site/doc/pages/mkdir.txt
similarity index 100%
rename from doc/pages/mkdir.txt
rename to site/doc/pages/mkdir.txt
diff --git a/doc/pages/mysql_connect.txt b/site/doc/pages/mysql_connect.txt
similarity index 100%
rename from doc/pages/mysql_connect.txt
rename to site/doc/pages/mysql_connect.txt
diff --git a/doc/pages/mysql_disconnect.txt b/site/doc/pages/mysql_disconnect.txt
similarity index 100%
rename from doc/pages/mysql_disconnect.txt
rename to site/doc/pages/mysql_disconnect.txt
diff --git a/doc/pages/mysql_error.txt b/site/doc/pages/mysql_error.txt
similarity index 100%
rename from doc/pages/mysql_error.txt
rename to site/doc/pages/mysql_error.txt
diff --git a/doc/pages/mysql_escape.txt b/site/doc/pages/mysql_escape.txt
similarity index 100%
rename from doc/pages/mysql_escape.txt
rename to site/doc/pages/mysql_escape.txt
diff --git a/doc/pages/mysql_insert_id.txt b/site/doc/pages/mysql_insert_id.txt
similarity index 100%
rename from doc/pages/mysql_insert_id.txt
rename to site/doc/pages/mysql_insert_id.txt
diff --git a/doc/pages/mysql_query.txt b/site/doc/pages/mysql_query.txt
similarity index 100%
rename from doc/pages/mysql_query.txt
rename to site/doc/pages/mysql_query.txt
diff --git a/doc/pages/nibble.txt b/site/doc/pages/nibble.txt
similarity index 100%
rename from doc/pages/nibble.txt
rename to site/doc/pages/nibble.txt
diff --git a/doc/pages/ob_close.txt b/site/doc/pages/ob_close.txt
similarity index 100%
rename from doc/pages/ob_close.txt
rename to site/doc/pages/ob_close.txt
diff --git a/doc/pages/ob_get.txt b/site/doc/pages/ob_get.txt
similarity index 100%
rename from doc/pages/ob_get.txt
rename to site/doc/pages/ob_get.txt
diff --git a/doc/pages/ob_get_close.txt b/site/doc/pages/ob_get_close.txt
similarity index 100%
rename from doc/pages/ob_get_close.txt
rename to site/doc/pages/ob_get_close.txt
diff --git a/doc/pages/ob_start.txt b/site/doc/pages/ob_start.txt
similarity index 100%
rename from doc/pages/ob_start.txt
rename to site/doc/pages/ob_start.txt
diff --git a/doc/pages/parse_query.txt b/site/doc/pages/parse_query.txt
similarity index 100%
rename from doc/pages/parse_query.txt
rename to site/doc/pages/parse_query.txt
diff --git a/doc/pages/parse_time.txt b/site/doc/pages/parse_time.txt
similarity index 100%
rename from doc/pages/parse_time.txt
rename to site/doc/pages/parse_time.txt
diff --git a/doc/pages/print.txt b/site/doc/pages/print.txt
similarity index 100%
rename from doc/pages/print.txt
rename to site/doc/pages/print.txt
diff --git a/site/doc/pages/render_component.txt b/site/doc/pages/render_component.txt
new file mode 100644
index 0000000..34fde7d
--- /dev/null
+++ b/site/doc/pages/render_component.txt
@@ -0,0 +1,19 @@
+:sig
+void render_component(String name, [DTree props], [Request& context])
+
+:desc
+Renders another `.uce` file as a component and writes the result directly to the current output buffer.
+
+This is the direct-output counterpart to `component()`.
+
+Component props are passed through `context.call`, and `name:RENDERFUNC` may be used to select a named handler exported by `RENDER:RENDERFUNC(Request& context)`.
+
+Use `render_component()` when you want to write component output directly from C++ code instead of capturing it as a `String`.
+
+Example:
+`DTree props;`
+`props["body"] = "Hello";`
+`render_component("components/card:BODY", props, context);`
+
+:see
+>ob
diff --git a/site/doc/pages/render_file.txt b/site/doc/pages/render_file.txt
new file mode 100644
index 0000000..66c5fe7
--- /dev/null
+++ b/site/doc/pages/render_file.txt
@@ -0,0 +1,22 @@
+:sig
+void render_file(String file_name)
+void render_file(String file_name, Request& context)
+
+:params
+file_name : UCE file to load and execute
+context : optional request context to pass into the target page
+
+:desc
+Calls another UCE file and executes its `RENDER(Request& context)` function.
+
+If `context` is omitted, the current active request context is used.
+
+:Example
+// call a common page template
+render_file("page-template.uce");
+
+// explicitly pass a request context
+render_file("page-template.uce", context);
+
+:see
+>ob
diff --git a/doc/pages/replace.txt b/site/doc/pages/replace.txt
similarity index 100%
rename from doc/pages/replace.txt
rename to site/doc/pages/replace.txt
diff --git a/doc/pages/session_destroy.txt b/site/doc/pages/session_destroy.txt
similarity index 63%
rename from doc/pages/session_destroy.txt
rename to site/doc/pages/session_destroy.txt
index 20c7c32..386cb26 100644
--- a/doc/pages/session_destroy.txt
+++ b/site/doc/pages/session_destroy.txt
@@ -5,7 +5,7 @@ void session_destroy(String session_name)
session_name : the name of the session
:desc
-Deletes the cookie specified by 'session_name' and clears the data stored under the session ID. This empties the 'context->session_id' and 'context->session' variables.
+Deletes the cookie specified by 'session_name' and clears the data stored under the session ID. This empties the 'context.session_id' and 'context.session' variables.
:see
>session
diff --git a/doc/pages/session_start.txt b/site/doc/pages/session_start.txt
similarity index 71%
rename from doc/pages/session_start.txt
rename to site/doc/pages/session_start.txt
index da58921..c360f40 100644
--- a/doc/pages/session_start.txt
+++ b/site/doc/pages/session_start.txt
@@ -8,11 +8,11 @@ return value : the current session ID
:desc
Starts session or connects to existing session. This function sets a cookie with the name contained in 'session_name' if it does not exist and fills that cookie with a new unique session ID. It then loads the session data for that session ID. Afterwards, the following fields are populated in the 'context' variable:
-context->session_id : the current session ID
+context.session_id : the current session ID
-context->session_name : the current session cookie name
+context.session_name : the current session cookie name
-context->session : the current session data. The session data is automatically saved after a request completes.
+context.session : the current session data. The session data is automatically saved after a request completes.
:see
>session
diff --git a/doc/pages/set_cwd.txt b/site/doc/pages/set_cwd.txt
similarity index 100%
rename from doc/pages/set_cwd.txt
rename to site/doc/pages/set_cwd.txt
diff --git a/doc/pages/shell_escape.txt b/site/doc/pages/shell_escape.txt
similarity index 100%
rename from doc/pages/shell_escape.txt
rename to site/doc/pages/shell_escape.txt
diff --git a/doc/pages/shell_exec.txt b/site/doc/pages/shell_exec.txt
similarity index 100%
rename from doc/pages/shell_exec.txt
rename to site/doc/pages/shell_exec.txt
diff --git a/doc/pages/socket_close.txt b/site/doc/pages/socket_close.txt
similarity index 100%
rename from doc/pages/socket_close.txt
rename to site/doc/pages/socket_close.txt
diff --git a/doc/pages/socket_connect.txt b/site/doc/pages/socket_connect.txt
similarity index 100%
rename from doc/pages/socket_connect.txt
rename to site/doc/pages/socket_connect.txt
diff --git a/doc/pages/socket_read.txt b/site/doc/pages/socket_read.txt
similarity index 100%
rename from doc/pages/socket_read.txt
rename to site/doc/pages/socket_read.txt
diff --git a/doc/pages/socket_write.txt b/site/doc/pages/socket_write.txt
similarity index 100%
rename from doc/pages/socket_write.txt
rename to site/doc/pages/socket_write.txt
diff --git a/doc/pages/split.txt b/site/doc/pages/split.txt
similarity index 100%
rename from doc/pages/split.txt
rename to site/doc/pages/split.txt
diff --git a/doc/pages/split_space.txt b/site/doc/pages/split_space.txt
similarity index 100%
rename from doc/pages/split_space.txt
rename to site/doc/pages/split_space.txt
diff --git a/doc/pages/split_utf8.txt b/site/doc/pages/split_utf8.txt
similarity index 100%
rename from doc/pages/split_utf8.txt
rename to site/doc/pages/split_utf8.txt
diff --git a/doc/pages/task.txt b/site/doc/pages/task.txt
similarity index 100%
rename from doc/pages/task.txt
rename to site/doc/pages/task.txt
diff --git a/doc/pages/task_pid.txt b/site/doc/pages/task_pid.txt
similarity index 100%
rename from doc/pages/task_pid.txt
rename to site/doc/pages/task_pid.txt
diff --git a/doc/pages/task_repeat.txt b/site/doc/pages/task_repeat.txt
similarity index 100%
rename from doc/pages/task_repeat.txt
rename to site/doc/pages/task_repeat.txt
diff --git a/doc/pages/time.txt b/site/doc/pages/time.txt
similarity index 100%
rename from doc/pages/time.txt
rename to site/doc/pages/time.txt
diff --git a/doc/pages/to_lower.txt b/site/doc/pages/to_lower.txt
similarity index 100%
rename from doc/pages/to_lower.txt
rename to site/doc/pages/to_lower.txt
diff --git a/doc/pages/to_upper.txt b/site/doc/pages/to_upper.txt
similarity index 100%
rename from doc/pages/to_upper.txt
rename to site/doc/pages/to_upper.txt
diff --git a/doc/pages/trim.txt b/site/doc/pages/trim.txt
similarity index 100%
rename from doc/pages/trim.txt
rename to site/doc/pages/trim.txt
diff --git a/doc/pages/unlink.txt b/site/doc/pages/unlink.txt
similarity index 100%
rename from doc/pages/unlink.txt
rename to site/doc/pages/unlink.txt
diff --git a/doc/pages/uri_decode.txt b/site/doc/pages/uri_decode.txt
similarity index 100%
rename from doc/pages/uri_decode.txt
rename to site/doc/pages/uri_decode.txt
diff --git a/doc/pages/uri_encode.txt b/site/doc/pages/uri_encode.txt
similarity index 100%
rename from doc/pages/uri_encode.txt
rename to site/doc/pages/uri_encode.txt
diff --git a/doc/pages/var_dump.txt b/site/doc/pages/var_dump.txt
similarity index 100%
rename from doc/pages/var_dump.txt
rename to site/doc/pages/var_dump.txt
diff --git a/doc/pages/ws_close.txt b/site/doc/pages/ws_close.txt
similarity index 91%
rename from doc/pages/ws_close.txt
rename to site/doc/pages/ws_close.txt
index deb4b7d..3158ae9 100644
--- a/doc/pages/ws_close.txt
+++ b/site/doc/pages/ws_close.txt
@@ -8,7 +8,7 @@ return value : true if the target connection exists and was scheduled to close
:desc
Queues a WebSocket close frame and closes the targeted connection.
-If `connection_id` is omitted, the current connection handled by `WS()` is closed.
+If `connection_id` is omitted, the current connection handled by `WS(Request& context)` is closed.
:see
>websocket
diff --git a/doc/pages/ws_connection_count.txt b/site/doc/pages/ws_connection_count.txt
similarity index 100%
rename from doc/pages/ws_connection_count.txt
rename to site/doc/pages/ws_connection_count.txt
diff --git a/doc/pages/ws_connection_id.txt b/site/doc/pages/ws_connection_id.txt
similarity index 100%
rename from doc/pages/ws_connection_id.txt
rename to site/doc/pages/ws_connection_id.txt
diff --git a/doc/pages/ws_connections.txt b/site/doc/pages/ws_connections.txt
similarity index 100%
rename from doc/pages/ws_connections.txt
rename to site/doc/pages/ws_connections.txt
diff --git a/doc/pages/ws_is_binary.txt b/site/doc/pages/ws_is_binary.txt
similarity index 65%
rename from doc/pages/ws_is_binary.txt
rename to site/doc/pages/ws_is_binary.txt
index ca9637b..b48efad 100644
--- a/doc/pages/ws_is_binary.txt
+++ b/site/doc/pages/ws_is_binary.txt
@@ -5,7 +5,7 @@ bool ws_is_binary()
return value : true if the current WebSocket message is binary
:desc
-Returns whether the message currently being handled by `WS()` arrived as a binary frame.
+Returns whether the message currently being handled by `WS(Request& context)` arrived as a binary frame.
If this returns `false`, the current message was delivered as a text frame.
diff --git a/doc/pages/ws_message.txt b/site/doc/pages/ws_message.txt
similarity index 92%
rename from doc/pages/ws_message.txt
rename to site/doc/pages/ws_message.txt
index bdda571..aec50da 100644
--- a/doc/pages/ws_message.txt
+++ b/site/doc/pages/ws_message.txt
@@ -5,7 +5,7 @@ String ws_message()
return value : payload of the current WebSocket message
:desc
-Returns the payload of the current WebSocket message being handled by `WS()`.
+Returns the payload of the current WebSocket message being handled by `WS(Request& context)`.
For text frames this is the decoded text payload. For binary frames this String contains the raw message bytes.
diff --git a/doc/pages/ws_opcode.txt b/site/doc/pages/ws_opcode.txt
similarity index 92%
rename from doc/pages/ws_opcode.txt
rename to site/doc/pages/ws_opcode.txt
index 363d161..66d2b53 100644
--- a/doc/pages/ws_opcode.txt
+++ b/site/doc/pages/ws_opcode.txt
@@ -5,7 +5,7 @@ u8 ws_opcode()
return value : opcode of the current WebSocket message
:desc
-Returns the opcode of the message currently being handled by `WS()`.
+Returns the opcode of the message currently being handled by `WS(Request& context)`.
Common values are:
diff --git a/doc/pages/ws_scope.txt b/site/doc/pages/ws_scope.txt
similarity index 69%
rename from doc/pages/ws_scope.txt
rename to site/doc/pages/ws_scope.txt
index 4ae94cd..05676ab 100644
--- a/doc/pages/ws_scope.txt
+++ b/site/doc/pages/ws_scope.txt
@@ -7,7 +7,7 @@ return value : scope identifier of the current WebSocket endpoint
:desc
Returns the runtime's scope identifier for the current WebSocket endpoint.
-This is the same default scope used by `ws_send()`, `ws_broadcast()`, `ws_connections()`, and `ws_connection_count()` when no explicit scope is supplied.
+This is the same default scope used by `ws_send()`, `ws_connections()`, and `ws_connection_count()` when no explicit scope is supplied.
In the current runtime implementation this scope is the page's internal endpoint identifier, typically the absolute `SCRIPT_FILENAME` of the `.ws.uce` file.
diff --git a/doc/pages/ws_send.txt b/site/doc/pages/ws_send.txt
similarity index 50%
rename from doc/pages/ws_send.txt
rename to site/doc/pages/ws_send.txt
index 5b3ade9..4aaa641 100644
--- a/doc/pages/ws_send.txt
+++ b/site/doc/pages/ws_send.txt
@@ -1,17 +1,16 @@
:sig
-bool ws_send(String message, String scope = "")
+bool ws_send(String message, bool binary = false, String scope = "")
:params
-message : text message to send
+message : message payload to send
+binary : set to true to send a binary frame instead of a text frame
scope : optional scope identifier, defaults to the current WebSocket page scope
return value : true if the message was queued for at least one connected client
:desc
-Queues a text WebSocket message for every client connected to the given scope.
+Queues a WebSocket message for every client connected to the given scope.
If `scope` is omitted, the current page scope is used.
-This helper currently sends text frames only.
-
:see
>websocket
diff --git a/site/doc/pages/ws_send_to.txt b/site/doc/pages/ws_send_to.txt
new file mode 100644
index 0000000..ef0a35a
--- /dev/null
+++ b/site/doc/pages/ws_send_to.txt
@@ -0,0 +1,14 @@
+:sig
+bool ws_send_to(String connection_id, String message, bool binary = false)
+
+:params
+connection_id : ID of the target WebSocket client
+message : message payload to send
+binary : set to true to send a binary frame instead of a text frame
+return value : true if the target connection exists and the message was queued
+
+:desc
+Queues a WebSocket message for one specific connected client.
+
+:see
+>websocket
diff --git a/doc/singlepage.uce b/site/doc/singlepage.uce
similarity index 96%
rename from doc/singlepage.uce
rename to site/doc/singlepage.uce
index 6e7336e..b39ef47 100644
--- a/doc/singlepage.uce
+++ b/site/doc/singlepage.uce
@@ -104,12 +104,12 @@ void render_see_section(String name)
}
-RENDER()
+RENDER(Request& context)
{
already_shown_items = new StringMap();
- String page = first(context->get["p"], "index");
+ String page = first(context.get["p"], "index");
<>
diff --git a/doc/style.css b/site/doc/style.css
similarity index 100%
rename from doc/style.css
rename to site/doc/style.css
diff --git a/site/examples/uce-starter/README.md b/site/examples/uce-starter/README.md
new file mode 100644
index 0000000..2c9ab0e
--- /dev/null
+++ b/site/examples/uce-starter/README.md
@@ -0,0 +1,21 @@
+# UCE Starter
+
+`site/examples/uce-starter/` is a UCE-native port of the PHP `web-app-starter/`.
+
+It keeps the PHP starter around as reference while mirroring the same broad structure:
+
+- `index.uce` as the front controller
+- `views/` for routed page content
+- `components/` for reusable UI building blocks
+- `themes/`, `js/`, and `img/` for static assets
+
+This port intentionally leans on UCE's component layer rather than treating components as a compatibility shim. The page shell, nav/footer chrome, theme switcher, dashboard blocks, workspace primitives, and marketing sections are all rendered through `component()`.
+
+The example uses query-string routing in the same style as the PHP starter:
+
+- `index.uce`
+- `index.uce?page1`
+- `index.uce?themes&theme=portal-dark`
+- `index.uce?workspace/projects`
+
+The demo account pages use a small file-backed user store under `/tmp/uce-starter-data/` with session-based login state.
diff --git a/site/examples/uce-starter/components/auth/oauth-client.uce b/site/examples/uce-starter/components/auth/oauth-client.uce
new file mode 100644
index 0000000..2422967
--- /dev/null
+++ b/site/examples/uce-starter/components/auth/oauth-client.uce
@@ -0,0 +1,100 @@
+#load "../../lib/app.uce"
+
+RENDER(Request& context)
+{
+ String title = first(context.call["title"].to_string(), "Sign in with OAuth");
+ String subtitle = first(context.call["subtitle"].to_string(), "Choose your preferred authentication method");
+ String callback_url = first(context.call["callback_url"].to_string(), starter_link("auth/callback", context));
+
+ DTree services = context.call["services"];
+ if(services.get_type_name() != "array" || services["google"]["name"].to_string() == "")
+ {
+ services["google"]["name"] = "Google";
+ services["google"]["color"] = "#4285f4";
+ services["google"]["icon"] = "fab fa-google";
+ services["google"]["scope"] = "openid email profile";
+ services["google"]["auth_url"] = "https://accounts.google.com/o/oauth2/v2/auth";
+
+ services["github"]["name"] = "GitHub";
+ services["github"]["color"] = "#333333";
+ services["github"]["icon"] = "fab fa-github";
+ services["github"]["scope"] = "user:email";
+ services["github"]["auth_url"] = "https://github.com/login/oauth/authorize";
+
+ services["discord"]["name"] = "Discord";
+ services["discord"]["color"] = "#5865f2";
+ services["discord"]["icon"] = "fab fa-discord";
+ services["discord"]["scope"] = "identify email";
+ services["discord"]["auth_url"] = "https://discord.com/api/oauth2/authorize";
+
+ services["twitch"]["name"] = "Twitch";
+ services["twitch"]["color"] = "#9146ff";
+ services["twitch"]["icon"] = "fab fa-twitch";
+ services["twitch"]["scope"] = "user:read:email";
+ services["twitch"]["auth_url"] = "https://id.twitch.tv/oauth2/authorize";
+ }
+
+ <>
+
+ >
+}
diff --git a/site/examples/uce-starter/components/basic/cookie-consent.uce b/site/examples/uce-starter/components/basic/cookie-consent.uce
new file mode 100644
index 0000000..174f0ac
--- /dev/null
+++ b/site/examples/uce-starter/components/basic/cookie-consent.uce
@@ -0,0 +1,114 @@
+#load "../../lib/app.uce"
+
+RENDER(Request& context)
+{
+ starter_boot(context);
+
+ <>
+
+
+
+
+
+
Cookie Notice
+
+
+ We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic.
+ Learn more
+
+ Play a specific sprite by name from the sprite collection.
+
+
+
+
+
Sprite Configuration
+
+ Sprite Format: { name: [start_ms, duration_ms] }
+ • start_ms: Starting position in milliseconds
+ • duration_ms: Length of the sprite in milliseconds
+ • Sprites can be tightly packed or have gaps between them
+
+ addEffect(type, options) - Add audio effect to the sound
+ • Returns the Web Audio node for direct manipulation
+ • Types: 'lowpass', 'highpass', 'bandpass', 'notch', 'delay', 'reverb', 'gain', 'distortion'
+ • Options vary by type (frequency, Q, delay, gain, etc.)
+ effects - Getter for effects array (for direct manipulation)
+ updateEffectsChain() - Rebuild audio chain after effects manipulation
+
+ enableEQ(bands, callback, boost) - Enable real-time frequency analysis
+ • bands: Number of frequency bands (default: 16)
+ • callback: Function called with frequency data array
+ • boost: Gain multiplier for visualization (default: 4.0)
+ disableEQ() - Disable frequency analysis
+
+
+
+
+
+
+
+
3D Spatial Audio
+
+
+
+
+
+
+
+ 🟢 Green is you!
+ Drag the dots to move them around the 3D space!
+ 🏛️ Cathedral environment provides realistic reverb.
+
The charting logic and data-formatting ideas come directly from the more complex dashboard frontend, but the starter version is stripped down to generic building blocks.
This page exists in the UCE port so the starter menu resolves cleanly as a full route, while still reusing the same marketing components as the home page.
The gallery renders the same preview content in each theme family. This makes it easier to judge shell fit, typography, token balance, and embed behavior.
Try visiting " + html_escape(starter_link("workspace/projects", context)) + " or " + html_escape(starter_link("workspace/activity", context)) + " to see the nested route fallback in action.
";
+ main_body += component("../../components/workspace/primitives:SECTION", section_props, context);
+
+ if(section == "activity")
+ {
+ DTree empty_props;
+ empty_props["icon_class"] = "fas fa-clock-rotate-left";
+ empty_props["title"] = "No live stream wired yet";
+ empty_props["text"] = "The shell is generic. Add your own websocket, polling, or event-driven runtime behind it when a real product needs one.";
+ empty_props["action_html"] = "Open dashboard demo";
+ main_body += component("../../components/workspace/primitives:EMPTY_STATE", empty_props, context);
+ }
+
+ DTree panel_props;
+ panel_props["header_html"] = header;
+ panel_props["body_html"] = main_body;
+ String main = mobile_bar + component("../../components/workspace/primitives:PANEL", panel_props, context);
+
+ DTree app_props;
+ app_props["id"] = "workspace-demo-shell";
+ app_props["overlay_id"] = "workspace-demo-overlay";
+ app_props["sidebar_html"] = sidebar;
+ app_props["main_html"] = main;
+ print(component("../../components/workspace/primitives:APP_FRAME", app_props, context));
+ <>
+
+ >
+}
diff --git a/site/examples/web-app-starter/.ai/instructions.md b/site/examples/web-app-starter/.ai/instructions.md
new file mode 100755
index 0000000..facb2e0
--- /dev/null
+++ b/site/examples/web-app-starter/.ai/instructions.md
@@ -0,0 +1,134 @@
+# Web App Starter - AI Assistant Instructions
+
+## Project Architecture
+**Custom PHP framework** with server-side rendering focus, component-based UI, and clean URL routing.
+
+### Core Flow
+1. `index.php` → `URL::MakeRoute()` → `views/{page}.php` → theme template
+2. All requests flow through `index.php` (single entry point)
+3. Views render into `URL::$fragments['main']`, then wrapped by theme
+
+### Key Directories
+- `lib/` - Core classes (URL, DB, Log, Profiler, components)
+- `views/` - Page templates (maps to URL routes)
+- `components/` - Reusable UI components
+- `config/settings.php` - Main configuration
+- `themes/` - Page layout templates
+- `js/` - Client-side code (uquery.js, morphdom.js, etc.)
+
+## Core Classes & Functions
+
+### Configuration
+```php
+cfg('key/path') // Get config value
+cfg('url/pretty') // true=clean URLs, false=query strings
+```
+
+### URL Routing
+```php
+URL::MakeRoute() // Parse URL into URL::$route
+URL::Link($path, $params) // Generate URLs (respects pretty URL setting)
+URL::$route['page'] // Target view (default: 'index')
+URL::$route['l-path'] // Full URL path
+URL::$fragments['main'] // Main content area
+```
+
+### Components
+**Files return arrays with render functions:**
+```php
+ function($prop) { return '
...
'; },
+ 'about' => 'Component description'
+];
+```
+
+**Usage:**
+```php
+component('path/to/component', ['prop' => 'value'])
+component('path/to/component:method', $props) // Specific render method
+component_declare('name', $definition) // Inline declaration
+```
+
+### Database
+```php
+DB::Query($sql, $params) // Execute any SQL query
+DB::Get($sql, $params) // Get multiple rows as array
+DB::GetRowWithQuery($sql, $params) // Get single row
+DB::GetRow($table, $keyvalue) // Get row by primary key
+DB::GetRow($table, $id, $keyname) // Get row by specific key
+DB::GetRowsMatch($table, $criteria) // Get rows matching criteria
+DB::Insert($table, $data) // Insert new row, returns ID
+DB::Commit($table, $data) // Insert or update (REPLACE)
+DB::Update($table, $where, $data) // Update existing rows
+DB::RemoveRow($table, $keyvalue) // Delete row by key
+```
+
+### Logging
+```php
+Log::debug($module, $text) // log/debug.YYYY-mm.log
+Log::text($module, $text) // log/log.YYYY-mm.log
+Log::audit($module, $text) // System journal
+```
+
+### Profiler
+```php
+Profiler::log($text, $indent_level) // Runtime profiling
+Profiler::$log // Access logged data
+```
+
+## Coding Conventions
+
+### File Structure
+- Views: `views/{pagename}.php`
+- Components: `components/{category}/{name}.php`
+- Classes: `lib/{name}.class.php`
+- Utilities: `lib/{name}.php`
+
+### JS and PHP Coding Style
+- Use `=` for output, use escaping functions safe(), asafe(), and jsafe() as appropriate
+- snake_case for functions/methods
+- PascalCase for classes
+- kebab-case for file/directory names
+- Always include ` function($prop) {
+ return "
{$prop['content']}
";
+ },
+ 'render:variant' => function($prop) { /* alternative render */ },
+ 'about' => 'Component description'
+];
+```
+
+### URL Handling
+- Use `URL::Link()` for all internal links
+- Views auto-map to URLs (`views/users.php` → `/users`)
+
+## Built-in Libraries
+- **uquery.js** - jQuery-like DOM manipulation
+- **morphdom.js** - DOM diffing
+- **macrobars.js** - JavaScript templating
+
+## Common Patterns
+
+### New Page
+1. Create `views/{pagename}.php`
+2. Set URL::$page_type to 'json' or 'blank' if page is AJAX
+3. Content (auto-wrapped by theme page variant controlled by URL::$page_type)
+
+### New Component
+1. Create `components/{category}/{name}.php`
+2. Return array with `render` function
+3. Use `component('components/{category}/{name}', $props)`
+
+### Error Handling
+- Logging: Use `Log::debug()` for development, `Log::audit()` for security
+- Profiling: Use `Profiler::log()` for performance analysis
+
+## Development Notes
+- Server-side rendering preferred over SPA approach
+- Component system encourages reusability
+- Single entry point simplifies routing/middleware
diff --git a/site/examples/web-app-starter/.gitignore b/site/examples/web-app-starter/.gitignore
new file mode 100755
index 0000000..f499afe
--- /dev/null
+++ b/site/examples/web-app-starter/.gitignore
@@ -0,0 +1,66 @@
+# Logs
+logs
+*.log
+.fuse*
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+log/
+private/
+public/
+config/
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# next.js build output
+.next
diff --git a/site/examples/web-app-starter/README.md b/site/examples/web-app-starter/README.md
new file mode 100755
index 0000000..1eb29e7
--- /dev/null
+++ b/site/examples/web-app-starter/README.md
@@ -0,0 +1,208 @@
+# Web App Starter
+
+Simple web app starter package, expects to run in a domain's root directory but will run everywhere as long as cfg('url/pretty') is set to false.
+
+Don't be afraid of server-side rendering! But we have JS options too.
+
+## Nginx Configuration
+
+### Pretty URLs Support (Optional)
+For pretty URLs (`cfg('url/pretty') = true`), add this configuration to enable clean URLs like `/users/profile` instead of `/?users/profile`:
+
+```nginx
+location / {
+ try_files $uri $uri/ /index.php?$args;
+}
+
+# the rest of the owl
+location ~ ^/(config|lib|private|\.git)/ {
+ deny all;
+ return 404;
+}
+location ~ \.php$ {
+ fastcgi_pass unix:/var/run/php/php-fpm.sock;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ include fastcgi_params;
+}
+```
+
+**Note**: Without pretty URL configuration, set `cfg('url/pretty') = false` in your config/settings.php and URLs will use query string format: `/?page¶m=value`. You can generate URLs with `URL::Link()` which will automatically use the appropriate format if you want to keep your options open (as to whether to use pretty URLs or not).
+
+## Routing
+
+`URL::MakeRoute()` creates `URL::$route` data. For example, a request to `http://example.com/some/url/path`
+would look like this:
+
+```
+Array
+(
+ [page] => index
+ [l-path] => some/url/path
+)
+```
+
+The `page` key refers to the view should be rendered. By default, this is just `'index'`, leading to
+`'views/index.php'` getting invoked. If the URL's path element starts with a colon, the entire path
+is used as the `page` value.
+
+The `l-path` key refers to the actual URL path as given by the browser. For example, the `'views/index.php'`
+view uses this to invoke the correct sub-view.
+
+The starter also supports two additional patterns backported from downstream apps:
+
+- directory index routing: `/workspace` can resolve to `views/workspace/index.php`
+- parent-path fallback: `/workspace/activity` can resolve to `views/workspace/index.php` with `URL::$route['param'] === 'activity'`
+
+## Components
+
+Components are reusable PHP-backed view fragments. A component file returns an array with one or more render functions plus optional metadata.
+
+The default starter convention is to keep reusable components in `components/`. The loader supports explicit paths like `components/example/theme-switcher` and shorthand names like `example/theme-switcher`, which resolve under `components/`.
+
+```php
+ function($prop) {
+ return '
' . $prop['content'] . '
';
+ },
+
+ 'about' => 'Description of what this component does',
+
+];
+```
+
+### Using Components
+
+```php
+= component('components/example/theme-switcher') ?>
+```
+
+**Using a Specific Render Method Explicitly:**
+```php
+= component_call('components/workspace/panel', 'render', ['title' => 'Status']) ?>
+```
+
+The older `component-name:method` shorthand is still supported for backwards compatibility, but `component_call()` or an explicit `render_call` prop is the preferred API because it is clearer and easier to grep.
+
+## Dashboard Primitives
+
+The starter now includes a small set of dashboard-focused building blocks backported from a more complex production frontend:
+
+- `js/u-format.js` for human-readable byte, count, and duration formatting plus unit-aware parsing
+- `js/u-sortable-table.js` for lightweight sortable HTML tables with persisted sort state
+- `js/u-timeseries-chart.js` for multi-series canvas charts without pulling in a charting framework
+- `components/data/summary-metrics.php`, `components/data/sortable-table.php`, and `components/data/timeseries-chart.php`
+- `views/dashboard.php` as a working end-to-end example
+
+## Workspace Primitives
+
+The starter also includes a second backport slice from the `uh-ai` portal app:
+
+- `components/workspace/*` for app shells, sidebars, panel headers, status pills, empty states, and compact utility buttons
+- `themes/common/css/workspace.css` for the shared workspace shell styling
+- `js/u-workspace-shell.js` for simple responsive sidebar toggling
+- `views/workspace/index.php` as a nested-route demo for shell-style products
+
+## Theme Families
+
+The starter now includes named theme families derived from the `uh-ai` portal app and the `uh-llm2` admin shell:
+
+- `portal-light` for the B612-based corporate portal look
+- `portal-dark` for the glassy dark portal shell and the current default starter theme
+- `localfirst` for the cyan/orange llm2-style admin shell
+
+Theme choice is resolved server-side from `config/settings.php`, with a validated theme key stored in the `starter_theme` cookie. Theme metadata such as descriptions, footer text, and browser theme colors now live in the same config entry as the theme path, so the gallery, page shell, and docs all read from the same source of truth.
+
+To compare themes side by side, use `/?themes`. The gallery embeds a shared `/?theme-preview` route under every available theme so you can evaluate shell chrome, content density, and form styling with the same fixture.
+
+The page shell is now split between theme CSS/assets and shared helper functions in `lib/theme_helpers.php`, which keeps the four top-nav themes aligned while still allowing `localfirst` to provide its own admin-shell layout.
+
+The homepage demo form now includes proper `id` and `name` attributes so the starter does not emit the earlier label/field accessibility warnings on the landing page.
+
+## Gauge Primitives
+
+The gauges demo now contains two gauge families from `uh-llm2`:
+
+- `components/gauges/progressbar.php` and `components/gauges/needlegauge.php` for the original bar and needle gauges
+- `components/gauges/arcgauge.php` for the llm2-style SVG KPI arc gauges with optional watermark tracking
+
+Abstract gauge classes live in `themes/common/css/gauges.css`, so the components stay theme-aware through CSS variables instead of shipping a competing visual system.
+
+## Testing
+
+The starter now includes a no-dependency smoke suite under `tests/`.
+
+Run it with:
+
+```bash
+php tests/smoke.php
+```
+
+It covers route resolution, link generation, component lookup, shorthand component rendering, and centralized theme metadata.
+
+### Inline Components
+
+You can also declare components directly in code:
+
+```php
+component_declare('my-button', [
+ 'render' => function($prop) {
+ return "";
+ }
+]);
+```
+
+## Logging
+
+This package includes a very basic Log class.
+
+```php
+Log::debug($module, $text) /* writes to log/debug.[Y]-[m].log */
+Log::text($module, $text) /* writes to log/log.[Y]-[m].log */
+Log::audit($module, $text) /* writes to system journal */
+```
+
+`$module` should be a short string identifying the module or context of the log line.
+
+`$text` text to be logged.
+
+## Profiler
+
+A basic profiling fixture.
+
+```php
+Profiler::log($text, $indent_level = 0)
+```
+
+`$text` name/text describing the checkpoint to be profiled.
+
+Profiler logs are not committed to disk. Log data for the current request is stored in
+`Profiler::$log`.
+
+## Batteries included
+```
+- lib/ulib.php - convenience functions
+- lib/profiler.class.php - profiling
+- lib/log.class.php - logging
+- lib/url.class.php - URL routing
+- lib/components.php - component rendering
+- lib/odt.class.php - ODT document generator
+- lib/db.class.php - database access
+
+- js/morphdom.js - DOM diffing
+- js/uquery.js - DOM manipulation (my attempt at keeping the good parts of jQuery)
+- js/site.js - site-wide JS, as a starting point
+- js/macrobars.js - JS templating
+
+- js/ag-grid - ag-Grid
+```
+## License (MIT Open Source)
+
+Copyright 2018-2025 udo@openfu.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/site/examples/web-app-starter/codesearch b/site/examples/web-app-starter/codesearch
new file mode 100755
index 0000000..3d03d31
--- /dev/null
+++ b/site/examples/web-app-starter/codesearch
@@ -0,0 +1,66 @@
+#!/usr/bin/env bash
+
+csensitive=false
+patterns=()
+
+# Parse flags and patterns
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -c|--case-sensitive)
+ csensitive=true
+ shift
+ ;;
+ -h|--help)
+ echo "Usage: $0 [-c|--case-sensitive] [...]" >&2
+ exit 1
+ ;;
+ -* )
+ echo "Unknown option: $1" >&2
+ echo "Usage: $0 [-c|--case-sensitive] [...]" >&2
+ exit 1
+ ;;
+ * )
+ patterns+=("$1")
+ shift
+ ;;
+ esac
+done
+
+# Ensure at least one pattern is provided
+if (( ${#patterns[@]} == 0 )); then
+ echo "Usage: $0 [-c|--case-sensitive] [...]" >&2
+ exit 1
+fi
+
+# Build common grep options
+grep_opts=( -nR -H --color=tty )
+if ! $csensitive; then
+ grep_opts+=( -i )
+fi
+
+# Build -e arguments for each pattern
+pattern_opts=()
+for pat in "${patterns[@]}"; do
+ pattern_opts+=( -e "$pat" )
+done
+
+# Define search paths
+declare -a php_sea=(".")
+declare -a css_sea=(".")
+declare -a js_sea=(".")
+
+# Search PHP files
+for vs in "${php_sea[@]}"; do
+ grep "${grep_opts[@]}" --include="*.php" "${pattern_opts[@]}" "$vs"
+done
+
+# Search CSS files (exclude fontawesome)
+for vs in "${css_sea[@]}"; do
+ grep "${grep_opts[@]}" --include="*.css" "${pattern_opts[@]}" "$vs" \
+ | grep -iv "fontawesome"
+done
+
+# Search JS files
+for vs in "${js_sea[@]}"; do
+ grep "${grep_opts[@]}" --include="*.js" "${pattern_opts[@]}" "$vs"
+done
diff --git a/site/examples/web-app-starter/components/auth/oauth-client.php b/site/examples/web-app-starter/components/auth/oauth-client.php
new file mode 100644
index 0000000..a9e086b
--- /dev/null
+++ b/site/examples/web-app-starter/components/auth/oauth-client.php
@@ -0,0 +1,283 @@
+ function($prop) {
+ $services = $prop['services'] ?? [
+ 'google' => [
+ 'name' => 'Google',
+ 'color' => '#4285f4',
+ 'icon' => 'fab fa-google',
+ 'scope' => 'openid email profile',
+ 'auth_url' => 'https://accounts.google.com/oauth/authorize',
+ 'token_url' => 'https://oauth2.googleapis.com/token',
+ 'user_info_url' => 'https://www.googleapis.com/oauth2/v2/userinfo'
+ ],
+ 'github' => [
+ 'name' => 'GitHub',
+ 'color' => '#333333',
+ 'icon' => 'fab fa-github',
+ 'scope' => 'user:email',
+ 'auth_url' => 'https://github.com/login/oauth/authorize',
+ 'token_url' => 'https://github.com/login/oauth/access_token',
+ 'user_info_url' => 'https://api.github.com/user'
+ ],
+ 'discord' => [
+ 'name' => 'Discord',
+ 'color' => '#5865f2',
+ 'icon' => 'fab fa-discord',
+ 'scope' => 'identify email',
+ 'auth_url' => 'https://discord.com/api/oauth2/authorize',
+ 'token_url' => 'https://discord.com/api/oauth2/token',
+ 'user_info_url' => 'https://discord.com/api/users/@me'
+ ],
+ 'twitch' => [
+ 'name' => 'Twitch',
+ 'color' => '#9146ff',
+ 'icon' => 'fab fa-twitch',
+ 'scope' => 'user:read:email',
+ 'auth_url' => 'https://id.twitch.tv/oauth2/authorize',
+ 'token_url' => 'https://id.twitch.tv/oauth2/token',
+ 'user_info_url' => 'https://api.twitch.tv/helix/users'
+ ]
+ ];
+ $title = $prop['title'] ?? 'Sign in with OAuth';
+ $subtitle = $prop['subtitle'] ?? 'Choose your preferred authentication method';
+ $callback_url = $prop['callback_url'] ?? URL::Link('auth/callback');
+
+ ?>
+
+
+
+
Debug Information
+
+
+
+
+ 'OAuth authentication component with support for Google and other providers. Handles the complete OAuth flow including state verification and callback processing.'
+];
\ No newline at end of file
diff --git a/site/examples/web-app-starter/components/basic/cookie-consent.php b/site/examples/web-app-starter/components/basic/cookie-consent.php
new file mode 100755
index 0000000..b6794b0
--- /dev/null
+++ b/site/examples/web-app-starter/components/basic/cookie-consent.php
@@ -0,0 +1,245 @@
+ function($prop) {
+ ?>
+
+
+
+
+
+
+
Cookie Notice
+
+
+ = first($prop['text'] ?? false,
+ 'We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic.
+ By clicking "Accept All", you consent to our use of cookies.') ?>
+
+ Learn more
+
+
+ Play a specific sprite by name from the sprite collection.
+
+
+
+
+
Sprite Configuration
+
+ Sprite Format: { name: [start_ms, duration_ms] }
+ • start_ms: Starting position in milliseconds
+ • duration_ms: Length of the sprite in milliseconds
+ • Sprites can be tightly packed or have gaps between them
+
+ addEffect(type, options) - Add audio effect to the sound
+ • Returns the Web Audio node for direct manipulation
+ • Types: 'lowpass', 'highpass', 'bandpass', 'notch', 'delay', 'reverb', 'gain', 'distortion'
+ • Options vary by type (frequency, Q, delay, gain, etc.)
+ effects - Getter for effects array (for direct manipulation)
+ updateEffectsChain() - Rebuild audio chain after effects manipulation
+
+ enableEQ(bands, callback, boost) - Enable real-time frequency analysis
+ • bands: Number of frequency bands (default: 16)
+ • callback: Function called with frequency data array
+ • boost: Gain multiplier for visualization (default: 4.0)
+ disableEQ() - Disable frequency analysis
+
+
+
+
+
+
+
+
3D Spatial Audio
+
+
+
+
+
+
+
+ 🟢 Green is you!
+ Drag the dots to move them around the 3D space!
+ 🏛️ Cathedral environment provides realistic reverb.
+
Note: Only providers with valid client IDs will appear as login options.
+
+
+
+
3. Implement Backend Handler
+
Create views/account/callback.php to handle the OAuth callback and exchange the authorization code for tokens:
+
+
+
+
+
+
+ views/account/callback.php
+
+
<?php
+// Handle OAuth callback
+if ($_GET['code']) {
+ // Exchange code for access token
+ // Get user profile from provider
+ // Create/login user account
+ // Set session and redirect
+}
+?>
+
+
+
+
+
+
+
Built-in Provider Support
+
The OAuth component comes with built-in support for popular providers:
+ This page is the first backport slice from the LocalAI dashboard frontend. It keeps the parts that are generic enough for the starter itself:
+ metric cards, a canvas time-series chart, and a lightweight sortable table that remembers its last sort choice.
+
+ The charting logic and data-formatting ideas come directly from the more complex dashboard frontend on uh-llm2, but the starter version is stripped down to generic building blocks.
+ That keeps the repo useful as a baseline instead of baking in LocalAI-specific assumptions.
+
+
\ No newline at end of file
diff --git a/site/examples/web-app-starter/views/elements/error.php b/site/examples/web-app-starter/views/elements/error.php
new file mode 100755
index 0000000..50a91a5
--- /dev/null
+++ b/site/examples/web-app-starter/views/elements/error.php
@@ -0,0 +1,5 @@
+
+
diff --git a/site/examples/web-app-starter/views/page2-section1.php b/site/examples/web-app-starter/views/page2-section1.php
new file mode 100755
index 0000000..44e7e19
--- /dev/null
+++ b/site/examples/web-app-starter/views/page2-section1.php
@@ -0,0 +1,9 @@
+Can haz ODT? ');
+ print_r(ODT::check_requirements());
+ print('');
\ No newline at end of file
diff --git a/site/examples/web-app-starter/views/page2.php b/site/examples/web-app-starter/views/page2.php
new file mode 100755
index 0000000..ac490aa
--- /dev/null
+++ b/site/examples/web-app-starter/views/page2.php
@@ -0,0 +1,15 @@
+
Ajax Demo
+
+
+
This is the content of Page 2. You can add more information here.
+= nl2br('Sed at dolor leo. Morbi a tellus sed nisl dictum ultricies sit amet at purus. Nam mattis metus sed nunc egestas convallis. Fusce sagittis tellus convallis sem volutpat, a aliquam nulla posuere. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed malesuada, nunc at iaculis tempus, augue sem venenatis augue, nec hendrerit dolor arcu tempus tortor. Proin euismod nunc mi, ut ultrices elit porttitor et.
+
+Aliquam erat volutpat. Ut non purus sit amet orci pulvinar elementum. Curabitur pharetra mi eget viverra sagittis. Maecenas consequat metus vitae gravida commodo. Sed vitae neque sed massa ultricies convallis. Duis venenatis, lacus non volutpat aliquam, augue elit aliquam odio, vitae iaculis tortor urna rhoncus magna. Morbi lacus dui, egestas vitae turpis quis, volutpat aliquam purus. Fusce in libero sapien. Pellentesque quis neque eget mauris scelerisque tempus quis iaculis arcu. Duis sollicitudin sit amet magna non finibus. Donec hendrerit erat vel nunc scelerisque viverra. Donec et elementum augue, eget fringilla arcu.') ?>
+
+
+
+
+
+
This route renders the same neutral content inside each theme so downstream projects can compare layout, typography, color tokens, and chrome without switching between unrelated pages.
The gallery renders the same preview content in each theme family. This makes it easier to judge shell fit, typography, token balance, and embed behavior before carrying a theme into a downstream app.
Try visiting ' . safe(URL::link('workspace/projects')) . ' or ' . safe(URL::link('workspace/activity')) . ' to see the nested route fallback in action.
',
+ ]);
+
+ if ($section === 'activity') {
+ $mainBody .= component('components/workspace/empty-state', [
+ 'icon_class' => 'fas fa-clock-rotate-left',
+ 'title' => 'No live stream wired yet',
+ 'text' => 'The shell is generic. Add your own websocket, polling, or event-driven runtime behind it when a real product needs one.',
+ 'action_html' => 'Open dashboard demo',
+ ]);
+ }
+
+ $main = $mobileBar . component('components/workspace/panel', [
+ 'header_html' => $header,
+ 'body_html' => $mainBody,
+ ]);
+
+ echo component('components/workspace/app-frame', [
+ 'id' => 'workspace-demo-shell',
+ 'overlay_id' => 'workspace-demo-overlay',
+ 'sidebar_html' => $sidebar,
+ 'main_html' => $main,
+ ]);
+ ?>
+
\ No newline at end of file
diff --git a/test/call_file.uce b/site/test/call_file.uce
similarity index 65%
rename from test/call_file.uce
rename to site/test/call_file.uce
index 4987707..c74dd25 100644
--- a/test/call_file.uce
+++ b/site/test/call_file.uce
@@ -1,6 +1,6 @@
-RENDER()
+RENDER(Request& context)
{
<>
@@ -12,10 +12,9 @@ RENDER()
Display the date using any or all of the following elements:
@@ -173,7 +173,7 @@ Lifewire is part of the Dotdash publishing family.
}
-RENDER()
+RENDER(Request& context)
{
<>
@@ -182,8 +182,8 @@ RENDER()
UCE Test:
Index
- show_stuff(); ?>
-
= var_dump(context->params) ?>
+ show_stuff(context); ?>
+
= var_dump(context.params) ?>
>
}
diff --git a/test/inc-test.cpp b/site/test/inc-test.cpp
similarity index 100%
rename from test/inc-test.cpp
rename to site/test/inc-test.cpp
diff --git a/test/index.uce b/site/test/index.uce
similarity index 76%
rename from test/index.uce
rename to site/test/index.uce
index b1e9d8d..0d34f48 100644
--- a/test/index.uce
+++ b/site/test/index.uce
@@ -1,9 +1,9 @@
-RENDER()
+RENDER(Request& context)
{
DTree p;
- p.set(context->params);
+ p.set(context.params);
<>
@@ -35,20 +35,23 @@ RENDER()