diff --git a/.planning/phases/19-chat-and-chatchannels/19-CONTEXT.md b/.planning/phases/19-chat-and-chatchannels/19-CONTEXT.md
new file mode 100644
index 0000000..d0852af
--- /dev/null
+++ b/.planning/phases/19-chat-and-chatchannels/19-CONTEXT.md
@@ -0,0 +1,183 @@
+# Phase 19: Chat and ChatChannels - Context
+
+**Gathered:** 2026-10-09
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+`Golem15.Chat` and `Golem15.ChatChannels` run on SummerCMS as the shared plugins `sm-chat-plugin` (`golem15.chat`) and `sm-chatchannels-plugin` (`golem15.chatchannels`), with realtime delivery through `lighthouse`. What they provide:
+
+- The schema, models, context adapters, jobs and services from PHP.
+- A typed Go service API.
+- A generic headless JSON API (`/_chat/api/v1`, `/_chatchannels/api/v1`).
+- Private attachments and message search.
+
+The point is to have them ready for the later Wavepath port.
+
+Wavepath's own `/_wave/api/v1/community/...` routes, the satellites (chatbots, chatvideo, forum, herald, userfriends; Phase 19.1), the Vue frontend and the Wavepath app port itself are out of scope.
+
+**Repos touched:** `sm-chat-plugin` and `sm-chatchannels-plugin` (new, under `git.golem15.com/golem15/`), plus additive changes to `sm-user-plugin` (D-15, D-17) and `summercms.go` only if `lighthouse`, `attach` or `beachcomber` lack a feature. The ROADMAP Phase 19 "Repos" line and success criterion 3 need updating to match D-06 to D-09 and D-15 (do it in the first planning commit).
+
+
+
+
+## Implementation Decisions
+
+### Source and legacy
+- **D-01:** **The frozen port contract is Wavepath's tree:** chat at `943b712` and chatchannels at `b92fb42`, read from `/media/nvme/dev/jz/wavepath.org/plugins/golem15/{chat,chatchannels}`.
+ - The starter's chat (`4db7484`) is a strict ancestor, 97 commits behind, with nothing Wavepath lacks. Do not use it.
+ - The P15/P18 frozen-source rule applies. Binding: table and column names, migration-equivalent schema, realtime event names, channels and payloads, serializer output (`toApiArray` and friends), console command names, config keys and lang keys.
+ - **Reversibility:** costly. Names are copied into the DBs and clients of every app.
+- **D-02:** **No QuestStream trace in the Go plugins.** Not ported:
+ - the `family_id` column and its index;
+ - `Conversation::LEGACY_FAMILY_CONTEXT`, `scopeForFamily`, `findOrCreateDm($family)` and `findOrCreateFamilyGroup`;
+ - `ChatService::getFamilyGroupChat` and `addUserToFamilyGroupChat`;
+ - the `migrate_family_to_context` data migration;
+ - the `parent_oversight` lang string;
+ - the QuestStream `ApiController` parent class.
+
+ No identifier, string, comment or README names QuestStream, family, parent or child. The user does not plan a QuestStream port soon.
+- **D-03:** **`canActAs` stays in the context contract, fully generic.** It means "this actor may act on behalf of another member within this context", with a neutral Go name chosen by Claude (for example `CanActFor(ctx, actorID, memberID)`). Adapters that don't need it return false. The PHP session-based "pick the member to act as" flow from the `ChatWindow` component is not ported.
+- **D-04:** **Migrations are one squashed, idempotent baseline per plugin,** equal to the final PHP schema minus `family_id`: chat v1.3.0 (conversations, participants, messages with `root_id`/`edited_at`/soft deletes, thread_summaries) and chatchannels v1.6.0 (channels, channel_categories, personal_categories, channel_user_prefs, channel_members, channel_mentions, channel_pins, user_context_prefs). The baseline adopts an existing Wavepath database unchanged, as in Phase 5 and P18.1 D-04. The researcher diffs it against a real Wavepath DB.
+ - **Reversibility:** one-way. Migration IDs and table shapes become the shared contract.
+- **D-05:** **`family_id` on adopted databases is ignored.** Fresh databases never get the column. On an adopted Wavepath DB it stays, nullable and untouched; Go never reads, writes or drops it.
+
+### HTTP surface
+- **D-06:** **Both plugins ship a typed Go service API plus a generic headless JSON API.**
+ - **Service API:** a ChatService/ChannelService successor that mirrors the PHP services. App routes (the future Wavepath `/_wave` port) call it.
+ - **Generic API:** `/_chat/api/v1` (conversations, messages, DMs, replies and threads, read, typing, mute, attachments) and `/_chatchannels/api/v1` (channels, categories, personal categories, prefs, members, pins, mentions, message search). Each plugin owns its prefix (the `/_x/api/v1` convention, P15 D-14, P18 D-06), so an app that imports only chat gets no channel routes.
+ - **Reversibility:** one-way. It's a published API that apps will consume.
+- **D-07:** **The generic API mirrors Wavepath's community routes.** It has the same operations, parameters and JSON payloads as `/_wave/api/v1/community/...` (`wavepath/routes.php` lines 488-526, 558-590, 673-721, and `threads/{rootId}/mute`), re-rooted at context-neutral paths. Both use one serializer set, so the Wavepath port can make its routes thin aliases or move the SPA onto `/_chat`.
+- **D-08:** **Contexts are addressed by morph alias and id,** for example `/_chat/api/v1/contexts/{type}/{id}/conversation`. `{type}` is the alias registered at build time (D-11), and access is decided by the context adapter (`hasMember`, `canOverseeChats`, `isChatEnabled`). Conversation- and message-level routes use their own ids, and participation is checked on every call.
+- **D-09:** **Auth is `jwt.auth`, with named rate-limit buckets.** The `chat-dm` bucket (10 per minute per user, PHP's `throttle:chat-dm`) guards DM start, and a general chat API bucket covers the rest. Mounting follows the `lighthouse.Mount` surfaces pattern, so the app picks the guard.
+- **D-10:** **The legacy `/api/chat/dm` and `/api/chat/attachments/{messageId}/{fileId}` routes are dropped,** along with the `ChatWindow` and `ChatBadge` components and their `chat.js`. Both routes fatal on PHP today, and no client calls them (Wavepath's `dmApi.ts` avoids the attachment path explicitly).
+- **Parity proof (replaces SC3's route wording):** golden tests show that the serializers (`Message.toApiArray` with attachments, thread summaries, channel, category, pref, pin and mention arrays) and every realtime event (name, channel and payload) match PHP output captured from Wavepath. Route-level parity of `/_wave` is checked at the Wavepath port.
+
+### Extension seams
+- **D-11:** **Contexts plug in through a build-time morph registry,** the successor of the `chat.context.resolver` singleton (`registerAdapter`, `registerProvider`).
+ - At Boot, an app or plugin registers a morph alias with a loader that returns a Go chat-context interface: members, `hasMember`, `canOverseeChats`, `isChatEnabled`, group chat name, the D-03 act-for hook, and a broadcast channel of 2 or 3 segments.
+ - This mirrors P18.1 D-08. An unknown `context_type` read from the DB fails loudly and is never silently skipped.
+- **D-12:** **Existing PHP class names in `context_type` (and `golem15_chatchannels_channels.context_type`) are rewritten to aliases on adoption.**
+ - A Go migration or adoption command rewrites the stored class strings to the registered aliases once. The class → alias map comes from the registrations (each registration may declare the legacy PHP class it replaces).
+ - After the rewrite, PHP can no longer resolve these rows, so it's a cutover point of no return, like P18 D-11's v2 rows. The app port takes a DB backup first.
+ - The researcher checks the other morph columns that store PHP class names, especially `system_files.attachment_type` for chat attachments (`Golem15\Chat\Models\Message`). The attachment owner morph name must keep resolving adopted files.
+ - **Reversibility:** one-way. Stored rows in every adopting app's DB change.
+- **D-13:** **Cross-plugin hooks are typed festival events.**
+ - chat fires typed events for message create, update and delete inside the write transaction. chatchannels listens to them for mention accrual, reconcile and remove, and for search index and deindex.
+ - User lifecycle (the `beforeDelete` GDPR cascade and handle generation on create) uses `sm-user-plugin` events. Any that don't exist yet are added there, additively.
+ - Plugins never patch each other's models (the successor of `Message::extend` and `User::extend`).
+- **D-14:** **ChannelAccessResolver** (`canAccessContext`, `canManageContext`) **is deny-all by default, with a setter.** The app installs its own resolver at Boot, and Boot logs a Warn when chatchannels runs with the default.
+- **D-15:** **`users.handle` moves into `sm-user-plugin`.**
+ - The user plugin owns the column (unique, added idempotently if missing so Wavepath's DB adopts unchanged), handle auto-generation (the HandleGenerator port) and the backfill.
+ - chatchannels uses it for `@mentions` (MentionParser regex `@([\w.-]{2,255})`).
+ - This is an additive change to a core plugin contract.
+ - **Reversibility:** costly. It adds a column and behavior on the shared `users` table across every app that mounts the user plugin.
+- **D-16:** **The WebSockets `$require` is replaced by `lighthouse`.** Broadcasts go through the lighthouse service: model broadcasts through a `lighthouse.Bind` binding, explicit events through `Emit`/`Publish`. chat registers the `chat` namespace authorizer (`chat:conversation:{id}`, participants only, capabilities `["prs"]`).
+- **D-17:** **`sm-user-plugin` registers the lighthouse `user` namespace authorizer** (`user:{id}` allowed only to that user) if it doesn't already. chat (`chat:message`, `chat:unread`) and chatchannels (`mentions.changed`) publish there, and Phase 19.1's herald and forum will too.
+
+### Attachments and search
+- **D-18:** **Attachments are private `lagoon/attach` files.**
+ - They are `system_files` rows with `is_public=false`, stored through `attach.Store` with ChatAttachmentPolicy's limits: 100 MB (`MAX_KB` 102400), at most 5 files (`MAX_ATTACHMENTS`), an extension allowlist, and SVG, HTML and XML blocked.
+ - Downloads go only through a `/_chat/api/v1` route that checks participation and streams the blob. They're never reachable through the public static handler.
+ - The attachment `kind`, `width` and `height` serialization stays as in PHP.
+- **D-19:** **`chat:migrate-attachments` is not ported.** Run it on PHP before cutover.
+- **D-20:** **Message search ports on `beachcomber`.**
+ - The read-only `MessageIndex` becomes a `beachcomber.Searchable` registration from chatchannels.
+ - It uses the same Typesense collection name (the scout prefix plus `messages`) and the same document fields.
+ - Indexing fails open, as in PHP.
+ - Search is exposed through `/_chatchannels/api/v1`.
+
+### Proof and tests
+- **D-21:** **A test-only host in each plugin repo,** as in P18 D-03: testcontainers Postgres, the lighthouse memory driver, beachcomber with a fake engine, a neutral fixture context (for example `acme` teams, as chatchannels' PHP tests use `Acme\Teams\Team`), and `sm-user-plugin`. Every PHP test method (59 in chat, about 130 in chatchannels) maps to a Go case or to an explicit "not applicable" note (QuestStream-only cases). Golden payloads and events are captured from Wavepath. The mapping is completed in the last plan.
+
+### Claude's Discretion
+- Go package, type and identifier names, including the context interface, the registry, the act-for hook, the service APIs and the event types.
+- Exact endpoint paths of the generic API, within D-07 and D-08.
+- Queue name and job shape for `MaintainThreadSummaryJob` on conga (PHP uses the `broadcasts` queue; it's dispatched after message create and delete when `root_id` is set, off-request).
+- Config keys: `use_ui_avatars` (env `CHAT_USE_UI_AVATARS`) carries over under the plugin namespace.
+- Lang strings port to phrasebook in EN and PL. The plugin display name becomes neutral (for example "Chat" rather than "Friends Chat"), and QuestStream strings are dropped per D-02.
+- Whether the adoption rewrite (D-12) is a migration, a console command, or both.
+- Plan count and split, subject to the plan-count checkpoint and "unit tests are the last plan". Expect chat first, then chatchannels, then the user plugin additions where they're needed.
+- Run the security-review agent. The phase adds a public authenticated API, private file downloads, channel authorization and a GDPR delete cascade.
+
+
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### Port sources (frozen, D-01)
+- `/media/nvme/dev/jz/wavepath.org/plugins/golem15/chat` at `943b712`: models, `contracts/ChatContextInterface.php`, `classes/{ContextResolver,AbstractContextAdapter,ChatService,ChatChannelAuthorizer,ChatAttachmentPolicy}.php`, `traits/ChatBroadcasting.php`, `jobs/MaintainThreadSummaryJob.php`, `updates/`, `tests/` (59 methods)
+- `/media/nvme/dev/jz/wavepath.org/plugins/golem15/chatchannels` at `b92fb42`: `classes/ChannelService.php` (1381 lines), `MentionParser`, `HandleGenerator`, `SearchIndexer`, `contracts/ChannelAccessResolver`, `models/MessageIndex`, `updates/`, `tests/` (about 130 methods)
+- `/media/nvme/dev/jz/wavepath.org/plugins/golem15/wavepath/routes.php` lines 171, 488-526, 558-590, 673-721: the community routes that the generic API mirrors (D-07)
+- `/media/nvme/dev/jz/wavepath.org/plugins/golem15/wavepath/Plugin.php` lines 163-190 (context adapter registration) and 209 (access resolver rebinding): how a host wires the seams
+- `/media/nvme/dev/jz/wavepath.org/plugins/golem15/userfriends/Plugin.php` lines 41-52: the other context adapter and provider registration
+- `/media/nvme/dev/jz/wavepath.org/vue-wavepath-app/src/`: the realtime events the client listens to (`created.chat.message`, `typing.chat.message`, `mentions.changed`, `chat:bot`) and the API usage in `api/dmApi.ts`
+
+### Project decisions
+- `.planning/notes/core-plugin-port-inventory.md`: why websockets isn't ported (lighthouse covers it), chat and chatchannels sizes, port order
+- `.planning/notes/core-plugins-own-repos.md`: the `sm-*-plugin` repo, module, submodule and commit workflow; core plugin contracts stay non-breaking
+- `.planning/notes/plugin-layout-winter-directories.md` §"Evidence (jz golem15/chat)": the classes/models edges in chat (ChatAttachmentPolicy, the ChatBroadcasting hook, the context resolver)
+- `.planning/phases/18.1-payment-gateway-subscriptions/18.1-CONTEXT.md` D-04, D-08, D-09: squashed baseline, build-time morph registry, and shared-users-column precedents
+- `.planning/phases/18-payment-gateway/18-CONTEXT.md` D-03, D-06: the test-only host and the `/_x/api/v1` customer API precedents
+
+### Framework modules
+- `modules/lighthouse/README.md`: publisher, `Bind`, `Emit`, Registry/Authorizer, `Mount` surfaces, memory driver
+- `modules/lagoon/README.md` §Attachments: `attach.Store`, `attach.Limits`, `is_public`, `attach.Owner` morph name, two-phase delete
+- `modules/beachcomber/README.md`: `Searchable`, Typesense engine, kill-switch
+- `modules/festival/README.md`: typed synchronous events
+- `modules/conga`: River jobs, for the thread summary job
+
+
+
+
+## Existing Code Insights
+
+### Reusable Assets
+- `lighthouse`: replaces the whole WebSockets dependency. `Bind` covers the `created.chat.message` model broadcast; `Emit` and `Publish` cover the typing, read, unread, `chat:message` and `mentions.changed` events; `Registry.Register("chat", …)` covers ChatChannelAuthorizer; `MemoryDriver` serves the golden event tests.
+- `lagoon/attach`: protected `system_files` storage with limits and an extension and MIME allowlist, matching ChatAttachmentPolicy.
+- `beachcomber` with its `typesense` driver: the Scout successor for MessageIndex.
+- `conga`: River jobs for `MaintainThreadSummaryJob`.
+- `festival`: the typed event bus for the D-13 hooks.
+- `sm-user-plugin`: the `jwt.auth` guard, the user model and lifecycle; it gains `handle` (D-15) and the `user` authorizer (D-17).
+
+### Established Patterns
+- A squashed idempotent baseline that adopts the PHP DB (Phase 5, P18.1 D-04).
+- A build-time registry instead of PHP singletons and config pushes (P18 D-15, P18.1 D-08).
+- A headless `/_x/api/v1` API per plugin (P15 D-14, P18 D-06, P18.1 D-13).
+- Plugin READMEs never name a consuming app (CLAUDE.md); the fixtures use `acme`.
+
+### Integration Points
+- The app (later Wavepath) registers context aliases (D-11), installs the ChannelAccessResolver (D-14), and mounts both APIs (D-09).
+- chat → chatchannels only through festival events (D-13); chatchannels requires chat.
+- Message broadcasts are enqueued in the write transaction through lighthouse callbacks, so nothing is published for a write that rolls back.
+
+
+
+
+## Specific Ideas
+
+- "No trace of QuestStream / Family / Child at this stage". This applies to code, strings, comments and READMEs.
+- The generic API should let the Wavepath port reuse it rather than re-implement it: identical payloads and serializers.
+
+
+
+
+## Deferred Ideas
+
+- A `vue-chat-layer` with chat window and badge components: once a client wants the generic API.
+- QuestStream chat features (family contexts, parent oversight): with a future QuestStream port, in its app plugin.
+- `chat:migrate-attachments`: run on PHP before the Wavepath cutover, not ported (D-19).
+- Wavepath's `/_wave/api/v1/community` routes: with the Wavepath app port.
+
+### Reviewed Todos (not folded)
+- The todo matcher returned only keyword noise (journal author fields, menu/faq/sitemap ports, docs and scaffold todos). None concern chat, so none were folded.
+
+
+
+---
+
+*Phase: 19-chat-and-chatchannels*
+*Context gathered: 2026-10-09*
diff --git a/.planning/phases/19-chat-and-chatchannels/19-DISCUSSION-LOG.md b/.planning/phases/19-chat-and-chatchannels/19-DISCUSSION-LOG.md
new file mode 100644
index 0000000..59fc6a5
--- /dev/null
+++ b/.planning/phases/19-chat-and-chatchannels/19-DISCUSSION-LOG.md
@@ -0,0 +1,61 @@
+# Phase 19: Chat and ChatChannels - Discussion Log
+
+> **Audit trail only.** Do not use as input to planning, research, or execution agents.
+> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
+
+**Date:** 2026-10-09
+**Phase:** 19-chat-and-chatchannels
+**Areas discussed:** Source and QuestStream, HTTP surface, Extension seams, Attachments and search
+
+---
+
+## Source and QuestStream
+
+| Question | Options | Selected |
+|---|---|---|
+| Frozen source | Wavepath HEADs (943b712 / b92fb42); latest develop at plan time | Wavepath HEADs |
+| QuestStream leftovers | Schema only; port everything 1:1; drop column too | Drop column too ("won't be migrating QuestStream anytime soon") |
+| canActAs | Keep in contract; drop it | Keep, "as generic as possible, no trace of QuestStream / Family / Child" |
+| Migrations | Squashed baseline; port each version | Squashed baseline |
+| family_id on adopted DBs | Drop if exists; ignore if present | Ignore if present |
+
+## HTTP surface
+
+| Question | Options | Selected |
+|---|---|---|
+| Plugin HTTP surface | Service API + serializers; plus generic /_chat/api/v1; move /_wave community routes in | Plus generic /_chat/api/v1 |
+| Legacy /api/chat routes | Drop both; port as-is | Drop both |
+| SC3 parity proof | Golden payloads + events; test-host routes | Golden payloads + events |
+| Frontend | No layer now; vue-chat-layer | No layer now |
+| API shape | Mirror /_wave community; fresh REST design | Mirror /_wave community |
+| Channel endpoints | /_chatchannels/api/v1; all under /_chat | /_chatchannels/api/v1 |
+| Context in URL | Morph alias + id; conversation id only | Morph alias + id |
+| Auth | jwt.auth + named buckets; app mounts with its guard | jwt.auth + named buckets |
+
+## Extension seams
+
+| Question | Options | Selected |
+|---|---|---|
+| Context plug-in | Build-time morph registry; keep PHP class names | Build-time morph registry |
+| Morph values in existing rows | Alias + legacy class map; rewrite on adoption | Rewrite on adoption |
+| Message/User hooks | Typed festival events; GORM callbacks | Typed festival events |
+| users.handle | chatchannels migration; move into sm-user-plugin | Move into sm-user-plugin |
+| ChannelAccessResolver default | Deny-all + setter; derive from chat context | Deny-all + setter |
+| user:{id} authorizer | sm-user-plugin; chat plugin | sm-user-plugin |
+
+## Attachments and search
+
+| Question | Options | Selected |
+|---|---|---|
+| Storage | attach private + service route; signed URLs | attach private + service route |
+| chat:migrate-attachments | Port it; skip | Skip |
+| MessageIndex search | Port on beachcomber; defer | Port on beachcomber |
+| Tests | Map PHP tests in test host; fresh Go tests | Map PHP tests |
+
+## Claude's Discretion
+
+Go names, exact generic API paths, the thread summary job's queue, config key namespacing, lang neutral naming, whether the adoption rewrite is a migration or a command, plan split.
+
+## Deferred Ideas
+
+vue-chat-layer; QuestStream chat features; chat:migrate-attachments (run on PHP); /_wave community routes (Wavepath port).