Skip to content

Latest commit

 

History

History
345 lines (300 loc) · 20.9 KB

uxor.md

File metadata and controls

345 lines (300 loc) · 20.9 KB

uxor development and tests

This optional profile targets the actual xormania/uxor repository. For Claude cloud sessions, read the session quick start before starting a stack or importing another worktree's dependencies.

The canonical runner is examples/uxor/run.sh, with UXOR_PROJECT for an explicit stack name. Default Compose names, relay keys and local stack state use uxor identities. The authored uxor-dev family becomes available only after normal image publication and accepted release evidence; source definitions do not establish public availability. Historical releases remain in git history and their original public Release assets.

The acceptance fixture pins commit 8fff45d: Symfony 8.1, UX 3.5.1, PHP 8.5/FrankenPHP workers, Node 22, Playwright 1.58.2 and Tailwind v4.3.3. Both PHP 8.4 and 8.5 profiles exercise this baseline, including the PHPUnit suite. Choose 8.4 for the minimum-version checks and 8.5 for the browser CI runtime. This pin includes the upstream calendar fix for three existing WebKit behavior failures: preserving unchanged day text avoids losing a click during focus rendering. Its stronger upstream Turbo navigation helper also removes the need for the former fixture overlay. The separate browser release parity fixture keeps its own reviewed consumer pin.

Part Supplied by
PHP extensions, Composer, Symfony CLI, FrankenPHP/Caddy, APCu php-frankenphp/8.4-trixie or 8.5-trixie
Node 22, npm and checksum-verified Tailwind v4.3.3 CLI uxor-dev/8.4-trixie or 8.5-trixie, derived from the exact FrankenPHP parent
Caddy/Mercure, worker mode, PHP ini, app entrypoint and source layout The current uxor checkout's demo/ configuration
Application and Playwright packages composer.lock and package-lock.json using Composer install and npm ci
Browser binaries and fonts matching the locked Playwright version Latest accepted, version-matched xorder playwright-browser companion (currently 1.0.2); official mcr.microsoft.com/playwright:v<locked-version>-noble fallback

Definitions alone are not pullable releases. Select an available uxor-dev digest from the catalog after publication, or build the root and derivative locally for validation:

python3 scripts/build.py php-frankenphp/8.5-trixie image-library-check:dev --children

Select the cached profile for either runtime from the verified catalog:

python3 scripts/library.py select tests/requirements/uxor.json
python3 scripts/library.py select tests/requirements/uxor-8.4.json

From a clone of this library, with an existing uxor checkout:

export IMAGE='<verified uxor-dev digest_reference>'
export WORKSPACE='/absolute/path/to/uxor'
bash examples/uxor/run.sh up
bash examples/uxor/run.sh status
bash examples/uxor/run.sh test tests/e2e/lab.calendar.spec.ts
bash examples/uxor/run.sh test --shard=2/3
bash examples/uxor/run.sh phpunit
bash examples/uxor/run.sh php-tests all
PHPUNIT_XDEBUG_MODE=coverage bash examples/uxor/run.sh phpunit --coverage-clover var/coverage.xml
bash examples/uxor/run.sh exec bash -c 'cd demo && bin/console tailwind:build'
bash examples/uxor/run.sh down

up syncs recipes before Composer setup, seeds the matching cached Tailwind binary, compiles the project's Tailwind CSS before FrankenPHP starts, starts the demo and browser, runs npm ci, warms Symfony's test cache and checks a worker-served application page. The same application page is used by the healthcheck; Caddy metrics alone do not establish app readiness. Set APP_READY_PATH=/your-public-page if / is not the appropriate readiness route. The profile preserves the repository's application entrypoint, database wait and runtime behavior. Playwright's config builds stale Tailwind CSS before tests. There is no host Node dependency. The runner needs Python 3, Docker/Compose and registry/dependency access. Project names default to the workspace basename plus a stable path hash. Each workspace also gets stable loopback HTTP/HTTPS ports in 20000–29999 (the HTTPS port is reused for HTTP3/UDP). A hash collision or occupied host port is reported before startup; select an explicit slot to resolve it.

bash examples/uxor/run.sh --slot 2 up      # HTTP 20004, HTTPS/HTTP3 20005
bash examples/uxor/run.sh --slot 2 status
bash examples/uxor/run.sh --slot 2 down

Explicit slots 0–14999 select ports in 20000–49999; the smaller default range avoids the usual Linux ephemeral port range. Use the same slot for every command. UXOR_PROJECT, HTTP_PORT, HTTPS_PORT and HTTP3_PORT override derived values individually. The default HTTP3 port follows the selected HTTPS port. Port probes apply to a local Docker engine; Compose reports final binding conflicts, including races and remote engine conflicts. An explicit project already attached to another checkout is rejected. Existing unlabeled profiles are recognized by their /app workspace bind mount. To retain the previous runner's fixed defaults, explicitly set UXOR_PROJECT=uxor-library HTTP_PORT=8084 HTTPS_PORT=8444 HTTP3_PORT=8444. up always asks Compose to reconcile configuration and bring stopped services back. It verifies retained Composer/npm metadata, then reuses expensive setup only when source inputs, effective Compose configuration, installed dependency state and container identities match a previous successful run. Source hashing uses the checkout's Git ignore rules, so generated recipes/assets do not trigger a recreation loop. Changes to source/configuration, CA bytes, image, mounts or proxies cause setup to be reconsidered. Readiness is checked again each time. The setup receipt is a local cache, not an authority over changed inputs. status checks Docker, service state/health and a live application request without starting containers, reading package locks or requiring IMAGE. It exits 0 only when both app/browser services are running and healthy and the page responds; otherwise it exits 1. Starting Docker itself remains a host responsibility.

down preserves caches; down --volumes deliberately removes this profile's disposable Caddy/home/demo-var volumes.

Images with verified php-testing and pcov capabilities also offer php-tests coverage, php-tests phpstan, and php-tests all. The aggregate command runs the demo's lock-owned PHPUnit with PCOV, then the kit's PHPStan configuration from the repository root. It explicitly selects the isolated, image-prepared PHPStan tool lock, supplies the demo autoloader, and finds recipe PHP directories from their manifests. Environment overrides described in PHP test tasks select other configurations, paths, or drivers. Earlier exact image pins retain the phpunit command and require a deliberate verified image upgrade before using these additional commands.

Xdebug is installed and stays off for the server and ordinary PHPUnit runs. PHPUNIT_XDEBUG_MODE=coverage enables it only in the PHPUnit exec process, including when the containers were started with Xdebug off. Reports live under demo/var in the named cache volume; retrieve one with run.sh exec cat demo/var/coverage.xml. PHPUnit comes from the demo lockfile. The runner sets APP_ENV=test, APP_DEBUG=1 and CREATE_SNAPSHOTS=false for this process, independently of the development server's environment.

For browser fault catalogs, source plants and copy checks describe project-owned named edits, served-content confirmation, recovery, and sync --check. The latter reports stale ignored demo copies using UXor's own sync rules. Named plants use the normal test budget and receipt; source and copies are restored after the run, including an expected test failure.

The image stores a checksum-verified Tailwind v4.3.3 binary at /opt/tailwind. After Composer is ready, up reads the demo's resolved tailwind-bundle config and copies the binary to demo/var/tailwind/v4.3.3/tailwindcss-linux-x64. Fresh worktrees and cache volumes reuse those image bytes. A different pinned version, explicit binary path or platform keeps the bundle's normal behavior. The project configuration remains authoritative. Both authored uxor 1.0.0 runtime lines include this cache helper; select their verified digest after publication and catalog acceptance.

For a linked Git worktree, export WORKTREE_GIT=1 before all runner commands:

export WORKSPACE='/absolute/path/to/uxor-worktree'
export WORKTREE_GIT=1
bash examples/uxor/run.sh up
bash examples/uxor/run.sh exec git status

This option requires host Git and mounts the common repository metadata, including the selected worktree's private metadata, read-only at its host path. It overlays /app/.git with an absolute pointer stored in the host user's $XDG_CACHE_HOME/xorder/worktrees (default $HOME/.cache). The checkout's .git file is unchanged, including relative pointers. The cached pointer survives container restarts. Git reads work inside the container; run Git writes such as add/commit on the host. Ordinary checkouts need no option.

The browser shares the app container's network namespace. Its localhost port 3000 is reachable by the existing Playwright configuration without publishing that port on the host. The runner uses DEMO_URL=https://localhost and PHP_BINARY=php, so PHP helpers execute directly in the same app container. PW_BROWSER_SERVER=/bin/false tells Playwright to reuse the already healthy server and fail if it disappears. Neither container has a mounted Docker socket. The version is read from package-lock.json and checked against package.json; the official companion and its server package use that same exact version.

The profile overlays the repository's existing Compose files and mounts its Caddyfile plus 10-app.ini/20-app.dev.ini. That preserves worker execution, Mercure directives and the current upload limits (2M per file, 8M per request). It supplies a prebuilt development image instead of rebuilding the demo's PHP toolchain. Exact CI artifact parity requires using the same selected library digest in project CI; matching a PHP minor alone is insufficient. Keep the repository's production image build separate from this development profile.

The profile verifies installed Composer package names, versions, source/dist references, package directories, generated runtime metadata and a usable autoloader against demo/composer.lock, including dev requirements. Composer also validates the lock against composer.json. A nonempty vendor/ alone is insufficient. An imported matching vendor tree regenerates autoloads and runs the project's post-install-cmd hooks once; unchanged verified setup can be reused. A changed lock or incomplete vendor tree goes through Composer install. Project hooks remain authoritative and may still require network access (for example importmap assets). This is metadata validation, not a checksum audit of every vendor file.

Importmap asset readiness is checked independently on every up, even when Composer setup is reused. The profile uses Symfony's own importmap reader and storage paths to check the requested versions, package files and additional files such as CSS fonts. Complete installations need no importmap download; incomplete ones run the project's importmap:install command before readiness. The runner keeps the project's other Composer hooks enabled.

After a complete installation, the runner records the original installed.php bytes and hashes of its required assets in demo/var/xorder/importmap-state.json (the persistent demo-var volume). Before Composer hooks run, a missing manifest can be restored from that receipt only when the importmap, locked AssetMapper version/reference, and every captured asset still match. Dependency mappings, package aliases and extra-file metadata are preserved exactly. Changed or missing assets, changed inputs, and corrupt receipts use normal Symfony installation; a filename-only seed cannot establish the missing metadata. Preserve the whole assets/vendor/ directory, including installed.php, when bringing in a prepared installation. A first installation without that metadata or a matching receipt requires network access through the configured proxy.

The defaults are IMPORTMAP_FILE=importmap.php and IMPORTMAP_VENDOR_DIR=assets/vendor, relative to /app/demo. If the project configures other paths in Symfony, set these variables to those same paths before up. An application without the selected importmap file skips this asset step. No package versions or generated dependency mappings are invented.

Composer installation is dist-only. up rejects a source preference before starting Docker work, checks that source fallback is disabled in Composer 2.10+ and that locked packages have supported dist artifacts, and refuses to update an incomplete source-installed tree. A blocked archive download stops setup; it does not retry with git clones. Complete source-installed packages can still be verified and reused without another install.

For a writable existing host Composer download cache:

export COMPOSER_CACHE_DIR='/absolute/path/to/composer-cache'
bash examples/uxor/run.sh up

Reuse existing dependencies without rewriting them

To populate a new checkout from a trusted prepared worktree, use python3 scripts/xr.py NEW_WORKTREE seed --from PREPARED_WORKTREE. Composer/npm manifests and locks, optional Symfony recipe lock and importmap inputs must match. Dependency destinations must be absent. The default creates private reflinks/copies; --hardlink requires stopped stacks and installs a sharing guard in both worktrees. See the cloud seeding workflow.

bash examples/uxor/run.sh up --reuse-only

Alternatively set XORDER_REUSE_ONLY=1 for a session wrapper. This mode ignores an inherited source preference because it performs no installation. Before starting or recreating services, a one-off container checks Composer manifest/ lock agreement, package metadata, generated autoloads, npm manifest/lock/package agreement and Symfony importmap metadata/assets. Incomplete inputs fail with the missing setup reported. No packages, autoloads, importmap files or readiness markers are repaired; cache snapshots are neither restored nor stored, recipe sync is skipped, and Composer setup hooks and npm ci do not run.

demo/vendor, node_modules and the configured importmap vendor directory are mounted read-only in PHP. The dependency paths must already exist inside the workspace; the importmap defaults are the paths below in this guide. This protects hardlinked package files from application/bootstrap writes too. For manual reuse without a sharing receipt, use the flag on every up; later exec commands use the retained container and its read-only mounts. Ordinary up restores writable mounts and normal setup behavior for these manually prepared trees. Worktrees seeded with --hardlink instead retain the automatic sharing guard: ordinary up also verifies read-only reuse, and both donor and receiver mounts remain protected. The receiver's project-owned sync command copies recipes into private demo destinations before the dependency check.

A missing or changed Composer/npm readiness marker is reported as an unrecorded setup step. Complete dependency metadata can be adopted without pretending that project hooks ran or creating a marker. Application health must still pass before Ready is printed. Symfony runtime/test caches, compiled Tailwind CSS and the host runner receipt remain writable and local to the stack; this mode protects dependency trees rather than making the whole application read-only. Prepare missing hooks, recipes or assets in a private writable tree before reuse.

The runner creates a separate xorder/<workspace-hash> cache under that existing directory and mounts only that child. Writable dependency archives are not shared between workspaces. The cache bind is outside HOME and its ownership is never changed. Ensure the child is writable by PUID:PGID. Without a host cache, Composer uses the persistent named home volume. Installation remains dist-only. When a restricted gateway blocks archive downloads, seed from a trusted prepared checkout or use a verified dependency bundle and read-only reuse.

HTTP_PROXY, HTTPS_PROXY, their lowercase forms and ALL_PROXY/all_proxy are forwarded. In automatic mode, loopback endpoints (localhost, 127.0.0.0/8, IPv6 ::1, including mapped IPv4) get a TCP relay bound only to the local Docker bridge gateway. Container proxy URLs use that numeric address, so reaching the proxy needs no container DNS. The upstream proxy still handles authentication, CONNECT and destination policy; TLS verification remains enabled. Host commands retain their original proxy settings. Tools must honor proxy configuration; this does not provide general DNS or transparently proxy every protocol.

Each workspace/project owns its relay. It survives up, is reused by later commands, restores the same ports after an interrupted relay, and is stopped by that project's successful down. Concurrent worktrees do not share relay ownership. Changed upstream endpoints require up. Private state under the host temporary directory contains endpoints/ports and an authenticated local control token, never proxy credentials. Keep the checkout available while using the runner. After host reboot, run up again before using retained containers.

Before opening an upstream connection, the relay checks the client's source IP against Docker's live container addresses and the runner's dev.xorder.proxy-owner label. Only this workspace/project's PHP container (including the browser sharing its network namespace) can use its relay. Unrelated containers and failed Docker lookups are rejected. Addresses are checked on every connection so a removed container's IP is not kept authorized. This requires continued host-side Docker access; host networking is unsupported. Docker daemon administrators remain trusted, as they can create labeled containers or join another container's network namespace.

Automatic forwarding requires a Docker bridge address bindable from the sandbox where the upstream proxy listens. A remote daemon, Docker Desktop VM, rootless engine, or isolated Docker socket may need a separately configured reachable proxy; the runner reports an unavailable route instead of dropping the proxy. Set PROXY_PASSTHROUGH=0 to disable container proxies, or PROXY_PASSTHROUGH=1 to forward URLs unchanged deliberately. Upper/lowercase NO_PROXY settings retain their entries and add localhost, loopback and the profile's service names. Readiness requests explicitly bypass proxies. Proxy values are not printed in diagnostics.

The app uses your UID/GID by default. PUID=0 PGID=0 supports a root-owned sandbox checkout. Only the named home, Caddy and demo-var cache volumes have their ownership adjusted. The checkout supplies writable vendor/node_modules directories for the chosen user. Set CA_CERTIFICATE=/absolute/proxy-ca.pem for the shared CA trust option; the Node browser companion gets the mounted certificate as an extra trusted CA as well.

The readiness recipe tests persistent workers and executed-line Xdebug coverage plus the shared PHP/native/TLS/user contract. It then starts the pinned real consuming repository as a linked Git worktree, checks Git access, builds Tailwind from the seeded binary, runs PHPUnit with a nonempty Clover coverage report, and runs calendar, dropzone, both data-table and both editor lab specs. PHP 8.5 executes these 45 behavior cases in Chromium, Firefox and WebKit using each engine's desktop device profile, for 135 cases; PHP 8.4 retains 45 Chromium cases. The three-engine suite has a ten-minute total budget, a 30-second per-test limit, and zero retries; its receipt must contain all 135 executed passes. Both inspect the actual default 4 GiB browser container limit. The 8.5 fixture also checks a real child OOM in an isolated browser-service container, including the original failed command exit and current-window cgroup evidence. This probe still runs when a consumer assertion fails, and the fixture preserves that original failure. Receipts, logs and failing traces survive cleanup under out/uxor-runtime/. It never updates screenshot baselines. A source update to tests/fixtures/uxor/consumer.json deliberately updates that consumer baseline; ordinary users run their current checkout.

References: