Demo
The demo in tests/test_app/ is the runnable application used by Interact's HTTP and browser tests. It combines the features documented in the guides and API pages.
Run the demo
Use PHP 8.5 with SQLite support, Node 24.11+ or Node 26+, and Docker.
composer install
npm ci
npm run build
composer previewOpen http://127.0.0.1:8765. Demo data seeds automatically. The session identity is for demonstration; applications supply their own authentication.
Realtime demo
composer preview includes realtime support. Open http://127.0.0.1:8765/realtime in two tabs. The launcher provisions an isolated FrankenPHP container with PHP 8.5, CakePHP, and its built-in Mercure 1.x hub on port 8765. The first run builds the image; later runs reuse Docker’s cache. The launcher removes the container on exit and retains the demo database. Stop any existing preview on port 8765 first.
Share the working demo with ngrok http 8765. Browser subscriptions use /.well-known/mercure on the current origin, so both the app and hub travel through the same tunnel. PHP publishes through the internal address. No public URL setting or second tunnel is needed.
The container invokes CakePHP's server --frankenphp command. The demo's ServerCommand enables --mercure and disables SSE compression. This runs ordinary PHP requests without worker mode. The image pins FrankenPHP 1.13.3 and installs CakePHP's required Intl extension. The same launcher starts isolated app/hub instances for the browser tests.
The chat room stores messages in SQLite and refreshes an independent feed in every subscribed tab. Type in one tab while sending from another: incoming messages arrive without disturbing your draft. Your own sends also refresh the feed through the normal response, so the chat remains usable without a live connection. Reloading restores the latest 50 messages. The room is shared by everyone visiting the demo; “Only my tabs” sends a private message scoped to the current browser session. Private history and subscriptions are authorized independently of the shared room.
“Ask demo assistant” streams a scripted reply into the same conversation, with progress events, text chunks, and an HTML completion marker. Other tabs in the same session receive it too. Assistant output is a temporary preview; saved messages remain available after reloading.
Tested source: composer island, feed island, template. Start with the two-tab notification walkthrough for a smaller example to build in your own application. The streaming walkthrough shows a report action without a realtime provider.
Try the interactions
Paths in the source column are relative to tests/test_app/. Matching templates live under templates/island/, with page mounts under templates/. Controllers use CakePHP routing attributes.
| Interaction | Source |
|---|---|
/Search articles, save one editor, preserve another draft, and choose dependent fields | Listing, dependent fields |
/formsBind article and comment drafts, render validation errors, and explicitly save | Form island, Table |
/renderlessSave drafts without replacing the view, with normal validation recovery | Draft editor |
/playgroundTry counters, conditional forms, tasks, polling, widgets, and failure recovery | Page mounts, widget wrapper |
/searchFilter 1,200 resources, sort, paginate, and restore URL state | Search island, Table finders |
/search/multiplePaginate Resources and Articles independently in one island | Overview island, Template |
/demo/url-stateShare nullable selections and nested filters; restore history without losing drafts | URL filters island, Template |
/compositionBind reusable child inputs and send reactive parent props | Parent, picker |
/uploadsStage documents, manage attachments, validate, and save | Upload island, application Form |
/loadingExplore loading modes, custom placeholders, and nested preservation | Page mounts |
/navigationNavigate ordinary CakePHP routes while preserving a layout notebook | Controller, page |
/outcomesSave and redirect, request an authorized download, and navigate explicitly | Actions, controller |
/streamingStream text, native elements, and progress during one action | Report island, template |
/pageUpdate a whole-page island while its layout stays in place | Controller |
Dynamic events
Open /events. Save article 1 to refresh the selected card, then save article 2 to refresh its own card. The unrelated card sends no request. Enter notes, change Watched article to article 2, and save article 2 again: both cards receive the same event. The selected card retains its identity and notes. Matching-event counters show which cards responded.
Tested source: listener island, parent island, template. See dynamic event names for syntax and supported props.
Debugging
Open /debugging from the demo navigation. Edit the message and try Dump draft, Stream three dumps, or Dump without rendering. Clear the message before Dump and validate to receive validation feedback alongside the dump. Dump then fail deliberately throws after dumping; Resume after failure restores normal requests.
The tested example uses PlaygroundIsland and its template. See debugging for viewer settings and DebugKit coexistence.
Navigation head and CSP examples
Open /navigation and switch between Overview and Details. Each page has its own external stylesheet; the previous page's sheet is removed. A shared inline style uses x-interact:asset="persist" with a stable id and remains when absent from Details.
Follow Try rotating CSP nonces to /navigation/nonces. Each GET generates distinct script/style nonces. Switch pages and use Back/Forward: the nonce-authorized heading color changes while enhanced navigation retains the original browser policy. Leaving this example changes the policy and uses a native visit.
Tested source: nonce page, demo security middleware.
Source and reset
The demo uses the TestApp namespace. Adapt it to your application's namespace when reusing its source. Props, drafts, authorization, validation, and saving remain explicit in each interaction.
Article saves persist in build/tmp/demo.sqlite. To reset the data, stop the server and tests, then delete that file. It is recreated on the next request. composer preview enables OPcache and checks PHP file timestamps on every request, so local edits appear immediately.
See Contributing for test commands and Troubleshooting for asset or update failures.