Skip to content

Demo lives in a separate showcase project, not in the package

Context

The package shipped a "demo trio": the chatbot:demo command, DemoController, the GET /chatbot/demo route, a demo.blade.php view, a chatbot.demo config block (CHATBOT_DEMO env), and a branch in the service provider that bound LLMClient to a FakeClient with canned replies when chatbot.demo.enabled was true. Its job was to let someone confirm the widget renders end-to-end without spending tokens — it was the post-install verification step in the installation guide.

That convenience carried ongoing cost. The demo bound a fake driver and exposed a public route, so every surface that touched it (installation, security, configuration docs) had to repeat a "NEVER enable in production" warning, and the provider's client-resolution path carried a demo branch that had nothing to do with real operation. Meanwhile two things that look adjacent are not demo and earn their place: FakeClient / Chatbot::fake() is the public, documented testing API for consumers, and the Playwright fixture (chatbot.playwright_fixture) is the package's own browser e2e infrastructure.

A separate showcase project — one that exercises every package feature — serves people evaluating the package before they install it. That is a different audience from someone verifying the install inside their own app, and conflating the two is what kept demo code in the library.

Decision

The demo trio is removed from the package. Demos and feature showcases live in a separate, standalone project.

  • Removed: chatbot:demo, DemoController, routes/chatbot-demo.php, resources/views/demo.blade.php, the chatbot.demo config block / CHATBOT_DEMO env, the provider's demo FakeClient binding branch, and the DemoCommandTest / DemoRouteTest tests.
  • Kept: FakeClient and Chatbot::fake() (public testing API), and the entire Playwright fixture. Neither is demo code.
  • Post-install verification moves to a short Chatbot::fake() recipe in the docs — binding the fake in a throwaway route or test — which closes the "confirm it works without spending tokens" gap using an API that already exists and is already documented.
  • Clean break. No deprecation cycle. The removal ships as a breaking release with an upgrading.md entry; a stale CHATBOT_DEMO=true simply becomes inert and /chatbot/demo returns 404. A deprecation period was not worth it for a flag that was never meant to run in production.

Considered alternatives

  • Keep the demo in the package. Rejected. It is permanent surface and a recurring "never in production" caveat for a feature that only matters in dev, and it duplicates what a dedicated showcase project does better.
  • Deprecate over one release (warn + no-op, then remove). Rejected. It keeps demo code alive longer, which is the opposite of the goal, for negligible safety on a dev-only flag.
  • Clean break but fail loud (throw if chatbot.demo.enabled is still truthy). Rejected as unnecessary ceremony — the upgrade note covers the dev who had it on, and an inert key harms nothing.

Consequences

  • This is a breaking change: /chatbot/demo 404s, the chatbot.demo config key is inert, and a dev relying on CHATBOT_DEMO=true for a fake driver now falls through to the real client (possibly with an empty API key). The upgrading.md entry documents the migration to Chatbot::fake().
  • ADR 0009 referenced GET /chatbot/demo as a web-wrapped route and, with the fixture, as a page that "models the real integration." After this change the Playwright fixture stands alone as that integration model; 0009's body is left intact as a historical record and carries a pointer to this ADR.
  • Until the separate showcase project exists, there is no in-package "see everything at once" experience — only the per-feature Chatbot::fake() recipe. That gap is accepted deliberately.

Released under the MIT License.