pushword/conversation
Pushword Conversation is a Laravel package for adding chat and messaging features to your app. It provides Eloquent-friendly models and APIs for conversations, participants, and messages, making it easy to build threaded discussions, inboxes, or chat histories.
Install into a site (or work in the monorepo).
composer require pushword/conversation
In the monorepo, work from packages/conversation/ and run commands from the repo root (composer …) or packages/dev-app/ (bin/console …). Symfony Flex should register the bundle; verify in config/bundles.php.
Look in this order — bounded searches only.
packages/conversation/src/ and packages/conversation/tests/ first; widen scope only if the answer isn't there.packages/docs/content/extension/ for the bundle's feature page, and packages/docs/content/architecture.md for repository-wide wiring.packages/core/src/Event/PushwordEvents.php for the event names you can hook.rg --glob '*.php' for PHP and rg --max-columns 240 when generated/minified files may match.Boot it.
composer console list pw # confirm the bundle's commands appear
composer console doctrine:schema:update --force # NO migrations — this is the schema path
composer console cache:clear
SQLite is the default; PostgreSQL and MariaDB work through Doctrine but you own the schema sync.
First use case: attach a conversation to a Page and render it.
Persist/associate the conversation entity against Page (see Page.php), then render from a public template using the bundle's Twig function/block. Public templates follow packages/core/DesignGuidelines.md (Tailwind). Verify in the admin by opening the conversation CRUD, then on the front end by loading the page — assert on rendered HTML, not just a 200.
Page; query by page id/slug in the controller or Twig extension rather than loading all threads and filtering in the template. This keeps the fragment cache honest (see below).{% block page_actions %} — in @EasyAdmin/page/content.html.twig a wrongly named block is silently dropped. Assert on rendered HTML.PushwordEvents.php (core and admin events) to react to page create/update/delete: e.g. close or archive a thread when its page is unpublished, or emit a notification when a message arrives. Don't patch the bundle for site-specific behaviour.extensions.md is the supported seam. Avoid form-theme overrides for editor wiring; use the Twig-function merge path if the conversation editor touches EditorJS.Pushword\Core\Command\AgentOutputTrait with a --format option (auto|agent|text) and gate human output behind if (! $this->agentMode). Tests asserting human text must pass '--format' => 'text'; update packages/docs/content/agent-output.md.composer console cache:clear
composer test-filter ConversationTest
Then `composer stan` and `composer rector` before committing. Never call `vendor/bin/phpunit` directly, and never skip tests.
- **Translations.** Add keys to `packages/<package>/translations/messages.<locale>.yaml`, camelCase, alphabetical.
## Gotchas and Tips
- **No migrations.** `doctrine:schema:update --force` is the only schema path and it will drop/alter columns without warning. Back up a SQLite file before touching the entity; on Postgres/MariaDB diff the generated SQL (`--dump-sql`) and apply manually.
- **The Markdown fragment pool outlives `cache:clear`.** `var/cache/pushword-pools` is deliberately kept across deploys. Any change to rendered conversation output must bump `MarkdownParser::CACHE_VERSION` (TOC: `SplitContent::TOC_CACHE_VERSION`), or you'll ship stale threads.
- **Renders must be byte-deterministic.** No `random()`, no timestamps, no unordered collection iteration in a render path. A content-hash cache that never hits is the symptom of entropy leaking in — and it silently defeats the static build's skip-unchanged.
- **Conversation counts and "unread" badges belong in one query.** N+1 queries in the admin list are the usual first performance regression; use a `COUNT`/`GROUP BY` in the CRUD query builder.
- **Admin template blocks fail silently.** If a conversation action button is missing, it's almost always a wrong block name, not a permissions bug. Check the rendered HTML.
- **EditorJS tools are built, not registered at runtime.** A tool lives in `packages/admin-block-editor/src/assets/tools/<Name>/`, is imported into `editorjsTools` in `editor.ts`, and ships as the committed `dist` after `yarn build`. A bundle activates one via an `EditorJsToolProviderInterface` tagged `pushword.editorjs_tool_provider`. Build order matters: `packages/admin`'s Vite build empties `src/Resources/public/`, so build `admin-monaco-editor` after it.
- **Monaco is on demand.** Only textareas with `data-editor` get it on CRUD pages; keep that attribute off hidden (`x-show`) textareas or they mount into a 0×0 box.
- **Thin docs for this package specifically.** The supplied sources are largely monorepo-wide agent guidance; the conversation bundle's own feature page under `packages/docs/content/extension/` and its `src/` are the authoritative surface. Treat the low star count and zero dependents as a signal to pin the version and read the upgrade note (`packages/docs/content/upgrade/`) before bumping — an upgrade that changes behaviour under an unchanged call will be documented there, nowhere else.
- **Shared working tree.** If you're in the monorepo with parallel agents, commit scoped: `git commit --only -m "feat(conversation): …" -- <paths>`, staging new files individually first. Never `git add -A`, never bare `git commit`.
- **Downstream sites** should read `vendor/pushword/docs/AGENTS.md`, which is the shipped copy of `packages/docs/AGENTS.md`.
How can I help you explore Laravel packages today?