703 lines
92 KiB
Markdown
703 lines
92 KiB
Markdown
# Chatwoot Parity Development Plan
|
|
|
|
Updated: 2026-06-05
|
|
|
|
## Goal
|
|
|
|
Build GoChat as a Go backend that can directly reuse the frontend from `reference/chatwoot`. The backend API, data contracts, side effects, permissions, and runtime behavior must match the local `reference/chatwoot` repository first. Existing docs are secondary when they conflict with the reference implementation.
|
|
|
|
## Confirmed Decisions
|
|
|
|
- Frontend: reuse Chatwoot frontend directly. Backend compatibility is mandatory.
|
|
- Baseline: `reference/chatwoot` is the source of truth for routes, controllers, models, serializers, jobs, and service behavior.
|
|
- Immediate order: keep `go test ./...` green first, then deepen Chatwoot behavior parity.
|
|
- Search: final implementation must use Meilisearch. DB/LIKE search is not acceptable as the final engine.
|
|
- Enterprise scope: exclude SSO/SAML/LDAP/OIDC. Include the remaining paid features already present in planning and code: SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation, macros, assignment policies, and related limits/workflows.
|
|
|
|
## Current Baseline
|
|
|
|
- Latest implementation checkpoint: `c2766fe feat(crm): email contact export completions`.
|
|
- Worktree status at this planning checkpoint: clean.
|
|
- `go test ./...` passes.
|
|
- Route dump succeeds with `TOTAL: 820` after adding the Chatwoot-compatible `PUT` contact note update route.
|
|
- Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`.
|
|
- Tracked frontend-critical route audit covers 262 Chatwoot routes: 262 exact, 0 method-compatible, 0 parameter-compatible, 0 missing.
|
|
- `/api/v1/widget` stubs are burned down and public inbox/contact/conversation/message core flows are backed by real handlers.
|
|
- Handler test stability fixes are committed into the baseline before feature parity work continues.
|
|
- `.codegraph/` is generated indexing output and is not part of tracked product code.
|
|
|
|
## Execution Snapshot
|
|
|
|
| Phase | Name | Status | Blocking gaps |
|
|
| --- | --- | --- | --- |
|
|
| Phase 0 | Test and route baseline | Done | none |
|
|
| Phase 1 | Meilisearch search engine | Review | live Meilisearch integration and document-shape parity still need reference verification |
|
|
| Phase 2 | Route and controller parity audit | Doing | Ruby/Bundler unavailable, so Chatwoot route extraction currently uses static `routes.rb` fallback |
|
|
| Phase 3 | Data and serializer parity | Doing | JSON fixture coverage is partial and still endpoint-family based |
|
|
| Phase 4 | Enterprise feature completion | Planned | excluded SSO family must stay out of scope; all other enterprise features remain included |
|
|
| Phase 5 | Background jobs and integrations | Planned | durable worker choice and job parity are open |
|
|
| Phase 6 | Core placeholder burn-down | Doing | account/contact/conversation/message/inbox placeholder groups remain broad |
|
|
| Phase 7 | Verification harness | Planned | route/JSON/frontend smoke harness not complete |
|
|
|
|
## Tracking Artifacts
|
|
|
|
| Artifact | Purpose | Update rule |
|
|
| --- | --- | --- |
|
|
| `docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md` | Master execution plan and status ledger. | Update in every parity commit. |
|
|
| `docs/parity/gochat_routes.txt` | Generated Go route inventory. | Regenerate after every route change. |
|
|
| `docs/parity/route_parity.md` | Generated tracked route comparison against `reference/chatwoot/config/routes.rb`. | Regenerate after every route-tracking or route-registration change. |
|
|
| `cmd/route_parity` | Static route parity generator. | Extend whenever a new Chatwoot route group enters the tracked critical set. |
|
|
| `.hermes/plans/2025-05-24-global-search-meilisearch.md` | Original Meilisearch implementation plan. | Mine for context only; this document is now the active tracker. |
|
|
| `.hermes/plans/2026-05-24-automation-macro-csat.md` | Original automation, macro, and CSAT implementation plan. | Mine for context only; this document is now the active tracker. |
|
|
| `docs/requirements/*.md` | Reference notes extracted from Chatwoot modules. | Use as helper material after checking `reference/chatwoot` directly. |
|
|
|
|
## Commit Ledger
|
|
|
|
This ledger records the committed parity checkpoints that future slices should build on. Each implementation commit must update this document again before it is considered closed.
|
|
|
|
| Commit | Scope | Verification summary | Follow-up state |
|
|
| --- | --- | --- | --- |
|
|
| `6aa62c6 docs: consolidate chatwoot parity roadmap` | Promoted Hermes-era plans into this master tracker; locked user decisions; added milestone, slice, enterprise, and webhook provider tracking. | Documentation-only checkpoint. | B1/P6.7 selected as next implementation slice. |
|
|
| `9e3f561 feat(webhook): align chatwoot ingress routes` | Added Chatwoot public webhook paths for Twitter, Telegram, LINE, SMS/Twilio, WhatsApp, Instagram, TikTok, and Shopify; removed generic fallback success masking. | `go test ./...`; route dump regenerated with `TOTAL: 801`; route parity stayed `251 exact, 0 missing`. | Provider-specific lookup and verification moved to review. |
|
|
| `bc7da9e feat(webhook): implement instagram and shopify ingress` | Implemented Instagram verify/event handling, Shopify HMAC/redact/event forwarding, and WhatsApp verify-token/app-secret signature corrections. | Focused webhook tests, `go test ./...`, route dump `TOTAL: 801`, route parity `251 exact, 0 missing`, `git diff --check`. | Remaining P6.7 work is durable incoming-message persistence and provider dispatch parity. |
|
|
| `5eb726b feat(webhook): persist incoming provider messages` | Added provider incoming-message persistence boundary and wired Telegram, LINE, SMS/Twilio, WhatsApp, Facebook/Instagram, and TikTok parsed incoming messages into ContactInbox, Conversation, and Message storage. | Focused webhook/channel tests passed; full `go test ./...` passed. | Continue P6.7 review with delivery/read receipt status updates and async dispatch/events. |
|
|
| `66ecabb feat(webhook): persist provider receipt statuses` | Added delivery/read/failed status persistence for Twilio, WhatsApp, Facebook/Instagram, and TikTok receipt events. | Focused webhook/channel tests passed; full `go test ./...` passed. | Continue P6.7 review with async dispatch/events and broader provider fixture assertions. |
|
|
| `06b999b feat(webhook): dispatch persisted provider events` | Wired webhook incoming persistence and status updates into the existing `channel.Dispatcher` fan-out boundary. | Focused webhook tests passed; full `go test ./...` passed. | Continue P6.7 review with broader provider fixture assertions. |
|
|
| `55295dd test(webhook): cover provider ingress persistence fixtures` | Added provider-specific webhook persistence fixture assertions for LINE, Twilio SMS, WhatsApp, Instagram, and TikTok, extending the existing Telegram fixture. | Focused webhook tests passed; full `go test ./...` passed. | Continue P6.7 review with signature edge fixtures and final provider Done/Review classification. |
|
|
| `0ea7a08 feat(webhook): verify tiktok ingress signatures` | Added Chatwoot-style TikTok `Tiktok-Signature` HMAC verification using `TIKTOK_APP_SECRET`, timestamp freshness, and invalid-signature rejection coverage. | Focused webhook tests passed; full `go test ./...` passed. | Continue P6.7 review with LINE/WhatsApp missing-signature edge fixtures and final provider classification. |
|
|
| `0439f3b feat(webhook): require line ingress signatures` | Tightened LINE webhook signature parity so configured `channel_secret` requires a present and valid `X-Line-Signature`, with missing-signature rejection coverage. | Focused webhook tests passed; full `go test ./...` passed. | Continue P6.7 review with WhatsApp missing-signature edge fixtures and Shopify/Twitter final classification. |
|
|
| `1c30933 test(webhook): cover whatsapp ingress verification` | Added WhatsApp route-level verification coverage for verify-token challenge echo, signed Cloud API POST persistence, and missing Meta signature rejection without persistence. | Focused webhook tests passed; full `go test ./...` passed. | Continue P6.7 review with Shopify/Twitter final classification. |
|
|
| `ebd8f08 feat(webhook): finalize provider ingress parity` | Finalized P6.7 provider classification with Twitter CRC prefix parity, Twitter route-level webhook tests, Instagram missing-signature rejection coverage, and Shopify real-handler classification. | Focused webhook/API tests passed; full `go test ./...` passed. | Move next to serializer/API parity queue. |
|
|
| `56a8c5b feat(profile): align chatwoot user serializer` | Aligned `/api/v1/profile`, profile update, avatar delete/update, availability, auto-offline, and reset-access-token responses to Chatwoot's raw `api/v1/models/_user.json.jbuilder` shape. Added user/account/account_user fields and personal access-token persistence needed by the serializer. | Focused profile/API tests passed; sandboxed `go test ./...` hit socket restrictions; escalated full `go test ./...` passed; `git diff --check` passed. | Continue B2 with `/auth/sign_in`, `/auth/validate_token`, auth headers/cookie contract, and current-user payload fixtures. |
|
|
| `e59f9f3 feat(auth): add chatwoot session routes` | Added root `/auth/sign_in`, `/auth/validate_token`, `/auth/sign_out`, and `/auth/password` routes consumed by the reused Chatwoot frontend. Sign-in now emits DeviseTokenAuth-compatible `access-token`, `token-type`, `client`, `expiry`, and `uid` headers and returns raw `{ data: user }`; validate-token returns `{ payload: { success, data } }`. | Focused auth/profile tests passed; route dump regenerated with `TOTAL: 805`; route parity remained `251 exact, 0 missing`; escalated full `go test ./...` passed; `git diff --check` passed. | Continue B2 with profile settings update parity: `display_name`, `message_signature`, `ui_settings`, password update, and multipart avatar handling. |
|
|
| `7aa3362 feat(profile): support chatwoot settings updates` | Completed profile settings update parity for `display_name`, `message_signature`, `ui_settings`, `phone_number` custom attributes, password changes with current-password verification, and multipart `profile[...]` FormData including avatar file input. | Focused profile tests passed; escalated full `go test ./...` passed; `git diff --check` passed. | B2 core auth/profile fixtures are complete enough to move next to conversations/messages serializer parity. |
|
|
| `9f89cbf feat(conversations): align chatwoot message serializers` | Added Chatwoot conversation/message serializer boundary for dashboard list/show/filter, message index, message create/update/retry, status toggle payloads, display-id route resolution, outgoing/private message defaults, `echo_id`, `content_attributes`, and conversation/message parity migration fields. | Focused conversation/message handler tests passed; full `go test ./...` passed; `git diff --check` passed. | Continue B3 with delete/update status parity, multipart attachment create, team assignment response parity, and deeper message finder before moving to contacts/companies. |
|
|
| `a465bbe feat(conversations): finish message mutation parity` | Finished the B3 mutation gap set: message delete now returns the Chatwoot deleted-message serializer and clears attachments; message status update supports `status`/`external_error` with API-inbox-only enforcement; `MessageFinder` now supports latest, `before`, `after`, and between windows; multipart `attachments[]` create attachment rows and serialize them in message payloads; team assignment returns the raw team payload. | Focused handler/service/repository tests passed; full `go test ./...` passed; `git diff --check` passed. | B3 core dashboard message flows move to review; continue with B4 contacts/companies while tracking deeper delivery/storage side effects. |
|
|
| `3481597 feat(crm): align contact company payloads` | Started B4 contacts/companies parity: added Chatwoot-shaped `{ meta, payload }` and `{ payload }` CRM serializers, strict empty-query `422` handling, contact create `{ contact, contact_inbox }` envelope, selected custom-attribute deletion, companies nested `company` params, company contacts on `contacts.company_id`, relation search/add routes, and Rails-compatible PATCH routes. | Focused contacts/companies tests passed; handler/service/repository tests passed; route dump regenerated with `TOTAL: 809`; route parity remained `251 exact, 0 missing`; sandboxed full `go test ./...` failed on socket restrictions, escalated full `go test ./...` passed; `git diff --check` passed. | Continue B4 with merge/import/export/labels/avatar/company destroy-custom-attributes, contact/company notes payload depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
|
|
| `7a033e2 feat(crm): complete contact label avatar gaps` | Completed the next B4 CRM route/behavior slice: added `contact_labels` persistence, Chatwoot `{ payload: labels }` contact label list/update endpoints, contact list/search label filtering via contact labels, contact/company avatar delete responses, company `destroy_custom_attributes`, and tracked trailing-slash nested route aliases. | Focused CRM tests passed; handler/service/repository/router tests passed; route dump regenerated with `TOTAL: 817`; route parity is `261 exact, 0 missing`; sandboxed full `go test ./...` failed on local socket restrictions, escalated full `go test ./...` passed; `git diff --check` passed. | Continue B4 with contact merge, import/export job/data-import behavior, contact/company notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
|
|
| `1e3bf47 feat(crm): align contact merge action` | Completed B4 contact merge parity for the reused dashboard action: added the exact `POST /api/v1/accounts/:account_id/actions/contact_merge` route, changed merge responses to the raw Chatwoot contact model serializer, moved conversations/contact inboxes/notes/messages from mergee to base, preserved base attributes while filling blanks from mergee, and covered legacy lowercase contact message senders. | Focused merge/CRM tests passed; handler/service/repository/router tests passed; route dump regenerated with `TOTAL: 818`; route parity is `262 exact, 0 missing`; full `go test ./...` passed; `git diff --check` passed. | Continue B4 with import/export job/data-import behavior, contact/company notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
|
|
| `8eed2f1 feat(crm): persist contact data imports` | Advanced B4 import/data-import parity: added Chatwoot-shaped `data_imports` fields and migration, changed contacts import missing-file errors to `422`, successful imports to empty `200 OK`, persisted import lifecycle states/counts/errors, and made CSV import merge/update existing contacts by identifier/email/phone with labels/custom attributes. | Focused import tests passed; handler/service/repository/router package tests passed; full `go test ./...` passed; `git diff --check` passed. | Continue B4.6 with contacts export async artifact/download/notification parity, then notes serializer depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
|
|
| `47c2b80 feat(crm): persist contact export artifacts` | Advanced B4 export parity: `POST /contacts/export` now creates a persisted `contact_exports` artifact, stores Chatwoot default/requested CSV columns with UTF-8 BOM, supports label/filter export inputs, exposes a local download URL, and creates a user notification with export metadata. | Focused export tests passed; handler/service/repository/router package tests passed; regenerated route dump `TOTAL: 819`; route parity remains `262 exact, 0 missing`; full `go test ./...` passed; `git diff --check` passed. | Continue B4 with real email delivery for export completion if required by mailer infrastructure, contact/company notes payload depth, Meilisearch-backed CRM search, and frontend smoke fixtures. |
|
|
| `af57482 feat(crm): align contact note payloads` | Advanced B4 notes parity: contact notes now return raw Chatwoot note arrays/objects with nested agent payloads, flat and nested `note.content` requests are accepted, create/update/show avoid the Go API envelope, delete returns empty `200 OK`, note deletion hard-deletes like Chatwoot, company note payloads include user metadata, and the `PUT` note update route is registered. | Focused notes/API tests passed; full `go test ./...` passed; route dump regenerated with `TOTAL: 820`; tracked route parity remains `262 exact, 0 missing`; `git diff --check` passed. | Continue B4 with nested contact/company conversations payload depth, CRM Meilisearch search shape, frontend smoke fixtures, and deferred export email delivery. |
|
|
| `fb775b7 feat(crm): align nested conversation payloads` | Advanced B4 nested CRM conversation parity: contact and company conversations now render Chatwoot's `{ payload: [conversation partial] }` shape with nested `meta.sender`, `messages`, `last_non_activity_message`, account/inbox IDs, timestamps, priority, labels, and unread fields from the shared conversation serializer. Company conversations now follow the enterprise controller's latest-20, `last_activity_at DESC` behavior instead of paginated raw models. | Focused nested conversation handler tests passed; focused handler/service/repository/router tests passed; escalated full `go test ./...` passed after sandbox socket limits blocked local-only auth/middleware tests; `git diff --check` passed. No route changes; route dump remains `TOTAL: 820`. | Continue B4 with CRM Meilisearch search shape, stricter import label validation, frontend smoke fixtures, and deferred export email delivery. |
|
|
| `202da19 feat(crm): route crm search through meilisearch` | Advanced B4 CRM search parity: contact and company service search now use the configured search service for non-empty queries, preserving Chatwoot CRM `{ meta, payload }` serializers by resolving Meilisearch result IDs back through account-scoped repositories. Bootstrap wires the Meilisearch-backed search service into contact/company services, and DB search remains only the no-reader/test fallback. Global search DB fallback now includes companies so the company document shape has a repository equivalent. | Focused search/service/repository/API tests passed; full `go test ./...` passed; `git diff --check` passed. No route changes; route dump remains `TOTAL: 820`. | Continue B4 with stricter import label validation, frontend smoke fixtures, and deferred export email delivery. |
|
|
| `653db81 feat(crm): validate imported contact labels` | Completed the remaining B4 import validation gap: contact CSV import now validates `labels` against existing account labels before creating/updating contacts, rejects rows with unknown labels, records failed row counts, preserves canonical existing label names, and no longer creates arbitrary account tags from import input. | Focused import/service/API tests passed; full `go test ./...` passed; `git diff --check` passed. No route changes; route dump remains `TOTAL: 820`. | Continue B4 with frontend CRM smoke fixtures and deferred export email delivery. |
|
|
| `c2766fe feat(crm): email contact export completions` | Completed B4 contact export email delivery: the completed export artifact now triggers a Chatwoot-style `contact_export_complete` mailer boundary for the requesting user's email, using the same subject and download URL semantics while preserving the existing persisted notification. The default mailer reads Chatwoot-compatible SMTP env vars and no-ops when SMTP is not configured. | Focused export/service/API tests passed; full `go test ./...` passed; `git diff --check` passed. No route changes; route dump remains `TOTAL: 820`. | Continue B4 with frontend CRM smoke fixtures. |
|
|
|
|
## Next Slice Contract
|
|
|
|
Completed implementation slice: B4 contact merge, contact import, contact export, and notes checkpoints now cover contact/company CRUD payloads, company-contact relations, contact labels, label filtering, avatar deletion, selected custom-attribute deletion, company destroy-custom-attributes, dashboard contact merge behavior, persisted contact import lifecycle behavior, persisted contact export artifacts, and Chatwoot-shaped contact/company notes for the CRM flows.
|
|
|
|
Next implementation slice: continue B4 with frontend CRM smoke coverage. Keep B3 in review for delivery/storage side-effect parity.
|
|
|
|
| Step | Required result | Reference source | Verification |
|
|
| --- | --- | --- | --- |
|
|
| N1 | Finish remaining contact/company behavior gaps with frontend smoke validation. | Chatwoot contacts and enterprise companies controllers/Jbuilder views/frontend API clients. | Handler/service tests assert status codes, persistence, and exact `{ payload }` or `{ meta, payload }` shape. |
|
|
| N2 | Harden CRM Meilisearch behavior by extending indexed contact/company fields as new frontend cases are found. | Chatwoot search usage and local Meilisearch decision. | Search tests run against mocked Meilisearch engine and preserve CRM payload contracts. |
|
|
| N3 | Preserve completed auth/profile/conversation/message fixtures while expanding the CRM suite. | Existing focused tests and Chatwoot frontend clients. | Existing auth/profile/conversation/message focused tests remain green. |
|
|
| N4 | Regenerate route artifacts after route changes; current route dump is `TOTAL: 820` and tracked route parity is `262 exact, 0 missing`. | `cmd/dump_routes`, `cmd/route_parity`. | Route commands run when applicable. |
|
|
| N5 | Update this tracker after every implementation checkpoint. | This document. | `git diff --check`; `go test ./...` for Go changes. |
|
|
|
|
Current B2 profile checkpoint:
|
|
|
|
- Added a `ProfileUserResponse` matching Chatwoot's `api/v1/models/_user.json.jbuilder`, including `access_token`, `available_name`, `message_signature`, `pubsub_token`, `ui_settings`, and nested account membership fields.
|
|
- Profile handlers now return raw Chatwoot user JSON instead of the Go API `{success,data}` envelope for frontend-consumed profile endpoints.
|
|
- Availability and auto-offline endpoints now return the full current-user serializer, matching frontend store expectations.
|
|
- User creation now assigns a `pubsub_token` like Chatwoot's `Pubsubable` concern, avoiding duplicate empty token rows and enabling ActionCable-compatible profile payloads.
|
|
- Added migration `000017_add_profile_serializer_fields` for profile serializer fields on users, accounts, and account_users.
|
|
- Added Chatwoot root auth/session routes. `/auth/sign_in` now accepts normalized email/password login, emits the auth headers stored by frontend `cw_d_session_info`, and returns the profile serializer under `data`. `/auth/validate_token` validates the same `access-token` header and returns the frontend-expected `payload.data` shape. `/auth/sign_out` revokes the refresh session.
|
|
- Added profile settings update support for JSON and multipart form payloads produced by `dashboard/api/auth.js`: `profile[display_name]`, `profile[message_signature]`, `profile[avatar]`, JSON `profile.ui_settings`, `profile.phone_number`, and password update fields now persist and return the raw Chatwoot user serializer.
|
|
|
|
Current B3 conversation/message checkpoint:
|
|
|
|
- Conversation list now returns Chatwoot's `index.json.jbuilder` shape: `{ data: { meta, payload } }`, with `payload` entries built from `api/v1/conversations/partials/_conversation.json.jbuilder` fields such as `meta.sender`, `messages`, `labels`, timestamps, status, priority, and unread count.
|
|
- Conversation show/create/update/priority/labels responses now return raw conversation payloads instead of the Go `{success,data}` envelope for frontend-consumed dashboard endpoints.
|
|
- Conversation route IDs now resolve by account-scoped `display_id` first, with legacy primary-key fallback, and new conversations receive account-scoped display IDs.
|
|
- Message list now returns Chatwoot's `{ meta, payload }` shape with contact/assignee metadata and serialized messages matching `api/v1/models/_message.json.jbuilder` field names.
|
|
- Message create accepts the reused frontend payload from `dashboard/api/inbox/message.js`, including omitted `message_type`, `private`, `echo_id`, JSON or multipart `content_attributes`, and defaults to outgoing text messages like Chatwoot's dashboard composer.
|
|
- Message create/update/retry return raw Chatwoot message payloads with numeric `message_type` values expected by `shared/constants/messages.js`.
|
|
- Message delete now follows `MessagesController#destroy`: it keeps the message row, changes content to `This message was deleted`, sets `content_attributes.deleted`, deletes message attachments, and returns the raw message serializer instead of `204`.
|
|
- Message status updates now follow `Messages::StatusUpdateService` for `sent/delivered/read/failed`, preserve the read-to-delivered guard, record `external_error` only on failed messages, and reject status updates for non-API inboxes with `403`.
|
|
- Message list now follows `MessageFinder` windows for latest, `before`, `after`, and between ranges, including `filter_internal_messages` handling.
|
|
- Multipart `attachments[]` from Chatwoot's dashboard composer now create attachment rows and serialize Chatwoot-style attachment event data in message payloads.
|
|
- Team assignment now follows `AssignmentsController#set_team` and renders the raw team payload when assigning by `team_id`.
|
|
- Added migration `000018_add_conversation_message_parity_fields` for display-id routing and message serializer fields such as `echo_id`, `status`, and JSON attributes.
|
|
- Remaining B3 review gaps: attachment persistence currently records metadata/URLs but does not yet provide ActiveStorage-equivalent durable binary storage; outbound delivery jobs and provider-specific attachment send behavior still need deeper Chatwoot side-effect parity; message realtime event payloads need frontend smoke validation.
|
|
|
|
Current B4 contacts/companies checkpoint:
|
|
|
|
- Contact list, active/filter/search, show, update, selected custom-attribute deletion, and create now return Chatwoot-style `meta/payload` envelopes instead of legacy Go `contacts` or raw model JSON.
|
|
- Contact create returns `{ payload: { contact, contact_inbox } }` and accepts existing frontend fields while preserving contact inbox shell output.
|
|
- Empty contact/company search queries now return `422` with `Specify search string with parameter q`, matching Chatwoot controllers.
|
|
- Company list/search/show/create/update now return enterprise Chatwoot `payload` and `meta.total_count/page` shapes; create/update accept nested `{ company: ... }` params while retaining flat compatibility inside the service boundary.
|
|
- Company contact relations now use `contacts.company_id` instead of the older local many-to-many join table, matching Chatwoot enterprise company membership semantics.
|
|
- Added Chatwoot-compatible `PATCH /contacts/:contact_id`, `PATCH /companies/:company_id`, `GET /companies/:company_id/contacts/search`, and `POST /companies/:company_id/contacts` body-based contact assignment routes.
|
|
- Added `contact_labels` persistence and Chatwoot-compatible `GET/POST /contacts/:contact_id/labels` responses as `{ payload: [...] }`; contact list/search/filter label params now filter contact label lists instead of conversation labels.
|
|
- Added `DELETE /contacts/:contact_id/avatar`, `DELETE /companies/:company_id/avatar`, and `POST /companies/:company_id/destroy_custom_attributes` with Chatwoot-shaped `{ payload }` responses and `422 { error: "custom_attributes must be an array" }` validation.
|
|
- Added trailing-slash aliases for Chatwoot nested label/contact-inbox collection routes.
|
|
- Added exact `POST /api/v1/accounts/:account_id/actions/contact_merge` and raw contact-model response shape for dashboard merge actions; merge moves conversations, contact inboxes, notes, and contact-sender messages to the base contact while preserving base attributes over mergee attributes.
|
|
- Added Chatwoot-shaped `data_imports` persistence for contact CSV imports. Missing `import_file` now returns `422 { error: "failed to import contacts" }`, successful imports return empty `200 OK`, import state/count/error fields are recorded, and CSV rows merge/update existing contacts by identifier/email/phone while preserving labels and unknown/custom columns in contact attributes.
|
|
- Added persisted contact export artifacts for `POST /contacts/export`: default/requested column handling, label/filter export inputs, UTF-8 BOM CSV content, local download URL, row counts, and a user notification carrying file metadata.
|
|
- Added Chatwoot note serializer parity for contact notes: raw array/object responses, nested agent payloads, flat and nested note request bodies, empty `200 OK` destroy, hard-delete semantics, and `PUT`/`PATCH` update routes. Company note payloads now include nested user metadata.
|
|
- Added nested contact/company conversation serializer parity: both endpoints now return `{ payload: [...] }` arrays built from the shared Chatwoot conversation partial, including nested sender metadata, latest message payloads, `last_non_activity_message`, labels, timestamps, priority, and unread fields. Company conversations are limited to the latest 20 by `last_activity_at DESC` like the enterprise controller.
|
|
- Routed CRM contact/company non-empty search through the configured Meilisearch-backed search service while preserving existing Chatwoot CRM list serializers by resolving hit IDs through account-scoped repositories. Service tests now prove contact/company search reader usage, and global search has a company repository fallback for document-shape parity in local DB mode.
|
|
- Added Chatwoot-style contact import label validation: CSV rows with labels not already present on the account are rejected, failed row counts are recorded on `data_imports`, valid labels use existing canonical tag names, and imports no longer create unknown labels from user-provided CSV values.
|
|
- Added contact export completion email delivery: after the persisted CSV artifact is generated, a Chatwoot-style mailer boundary sends `Your contact's export file is available to download.` to the requesting user when SMTP is configured, while keeping the in-app notification metadata.
|
|
- Route dump is now `TOTAL: 820` and tracked route parity is `262 exact, 0 missing`.
|
|
- Remaining B4 gap: frontend smoke validation.
|
|
|
|
Active B4 task board:
|
|
|
|
| ID | Task | Reference source | Status | Exit gate |
|
|
| --- | --- | --- | --- | --- |
|
|
| B4.1 | Contact/company CRUD/search serializers and empty-search errors. | Contacts and enterprise companies controllers/views. | Done | `3481597`; CRM handler tests. |
|
|
| B4.2 | Company-contact relation semantics on `contacts.company_id`. | Enterprise company contacts controller. | Done | `3481597`; relation add/list/search tests. |
|
|
| B4.3 | Contact label list/update and label-filtered contact search. | `contacts/labels_controller.rb`, `LabelConcern`, contact frontend API. | Done | `7a033e2`; label update/list/filter tests. |
|
|
| B4.4 | Contact/company avatar delete and company destroy-custom-attributes. | Contacts controller `avatar`; enterprise companies controller `avatar` and `destroy_custom_attributes`. | Done | `7a033e2`; avatar/custom-attribute tests. |
|
|
| B4.5 | Contact merge behavior and response shape. | `actions/contact_merges_controller.rb`, `ContactMergeAction`, contact merge Jbuilder view. | Done | `1e3bf47`; merge route/handler/service tests. |
|
|
| B4.6a | Contact import/data-import persistence. | Contacts import controller, data import model/job, `DataImport::ContactManager`. | Done | `8eed2f1`; import handler/service tests cover status codes, lifecycle persistence, merge/update matching, labels, and custom attributes. |
|
|
| B4.6b | Contacts export artifact/download behavior. | Contacts export controller and `Account::ContactsExportJob`. | Done | `47c2b80`; export request persists CSV artifact, download URL, label/filter data, row count, and notification metadata. |
|
|
| B4.6c | Contacts export email delivery. | `AdministratorNotifications::AccountNotificationMailer#contact_export_complete`. | Done | `c2766fe`; export completion calls a mailer boundary with the Chatwoot subject, requesting user's email, and export download URL. SMTP env mailer no-ops when not configured. |
|
|
| B4.6d | Contact import label validation. | `DataImportJob#build_contact_from_row` approved-label validation. | Done | `653db81`; import rejects unknown labels, keeps valid rows, records failed counts, and avoids creating arbitrary tags. |
|
|
| B4.7a | Contact/company notes payload depth. | Contact notes controller/Jbuilder, company notes frontend consumer. | Done | `af57482`; raw contact note array/object fixtures, nested user payloads, empty destroy response, and company note user metadata tests. |
|
|
| B4.7b | Contact/company conversations payload depth. | Nested conversations controllers and Jbuilder views. | Done | `fb775b7`; nested CRM conversation payload fixtures cover shared conversation partial fields, latest messages, sender metadata, and company latest-20 ordering. |
|
|
| B4.8 | CRM search through Meilisearch document shape. | Chatwoot frontend search usage and local Meilisearch engine. | Done | `202da19`; contact/company CRM search services use the configured search reader for non-empty queries, keep CRM serializers by resolving result IDs, and tests cover mocked search-reader usage plus company search fallback shape. |
|
|
| B4.9 | Reused frontend CRM smoke. | `reference/chatwoot` dashboard contacts/companies screens. | Todo | Contact list/search/show/edit/labels/company relation flows run without frontend adapters. |
|
|
|
|
## Immediate Execution Queue
|
|
|
|
This is the ordered queue for the next implementation slices. Do not skip the route and test gates even when working on deeper business behavior.
|
|
|
|
| Order | Work item | Primary phase | Done when | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| Q1 | Expand tracked route parity to Captain/Copilot, assignment policies, widget, public inbox/contact/conversation, public CSAT, v2 reports, summary reports, and live reports. | Phase 2 | `cmd/route_parity` tracks these groups and `docs/parity/route_parity.md` lists every missing/mismatched route. | Done |
|
|
| Q2 | Patch route aliases discovered by Q1, especially Chatwoot widget/public paths such as `/api/v1/widget/...` versus existing `/widget/...`. | Phase 2 | Missing tracked routes return to zero or are explicitly documented with implementation tasks. | Done |
|
|
| Q3 | Add route boot regression coverage for wildcard conflict groups before expanding more Rails-style resources. | Phase 2 | Router tests cover nested dynamic resources that previously risked Gin conflicts. | Done |
|
|
| Q4 | Start serializer parity fixtures for auth/session, conversations/messages, contacts/companies, inboxes, notifications, and search. | Phase 3 | Each area has at least one reference fixture and Go response test. | Doing |
|
|
| Q5 | Review Meilisearch document shape and endpoint payloads against Chatwoot frontend consumers. | Phase 1 and Phase 3 | Search remains Meilisearch-first and payload mismatches are fixed or tracked. | Todo |
|
|
| Q6 | Implement provider-specific webhook ingress for Chatwoot public webhook paths. | Phase 6 | Generic webhook placeholder no longer masks provider gaps; Telegram, LINE, SMS/Twilio, WhatsApp, Instagram/Twitter/TikTok routes resolve and verify like Chatwoot where supported. | Done |
|
|
| Q7 | Burn down enterprise gaps in this order: SLA, assignment policy and capacity, CSAT, automation/macros, Audit, CustomRole, InboxLimit, Captain/Copilot. | Phase 4 and Phase 5 | Each feature passes route, persistence, auth, side-effect, response, and test checks. | Todo |
|
|
| Q8 | Add frontend smoke harness using the reused Chatwoot frontend once core API flows boot end-to-end. | Phase 7 | Login, inbox list, conversation list/detail, message send, contact view, and widget init run without frontend adapters. | Todo |
|
|
|
|
## Current Decision Ledger
|
|
|
|
No new user confirmation is required before continuing the next implementation slice.
|
|
|
|
| Topic | Locked decision | Consequence |
|
|
| --- | --- | --- |
|
|
| Frontend | Reuse `reference/chatwoot` frontend directly. | Backend URLs, request payloads, response serializers, auth behavior, async side effects, and error envelopes must match Chatwoot. |
|
|
| Reference | Local `reference/chatwoot` wins over older docs. | Every task must cite or inspect matching Rails route/controller/model/job behavior before being marked done. |
|
|
| Test order | Keep `go test ./...` green before deepening behavior. | Route/handler fixes and regression tests come before broad enterprise implementation. |
|
|
| Search | Meilisearch is mandatory. | DB search can stay only as explicit development fallback; all final search payload and indexing work targets Meilisearch. |
|
|
| Enterprise scope | Exclude SSO/SAML/LDAP/OIDC; include all other paid features. | Do not spend roadmap capacity on SSO family except safe disablement. SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation, macros, and assignment policies remain in scope. |
|
|
|
|
## End-to-End Milestone Map
|
|
|
|
These milestones are the tracking spine for the remaining Chatwoot frontend reuse work. A milestone is complete only after its verification gates pass and this document records the commit/result.
|
|
|
|
| Milestone | Scope | Exit gate | Status |
|
|
| --- | --- | --- | --- |
|
|
| M0 | Test, route, and documentation baseline. | Clean worktree, `go test ./...`, route dump/parity artifacts current. | Done |
|
|
| M1 | Meilisearch-first search foundation. | Config, engine, indexing hooks, reindex command, mocked tests, and live-shape review tracked. | Review |
|
|
| M2 | Route parity expansion for frontend-critical routes. | Tracked route set has zero missing routes and every new route group has router boot coverage. | Doing |
|
|
| M3 | Serializer parity for frontend API families. | Fixture tests cover auth/profile, accounts/users, inboxes, conversations/messages, contacts/companies, notifications, reports, widget/public, and search. | Doing |
|
|
| M4 | Core handler placeholder burn-down. | Account/contact/conversation/message/inbox/webhook handlers are repository-backed and no frontend-critical route returns placeholder JSON. | Doing |
|
|
| M5 | Paid feature parity excluding SSO family. | SLA, Audit, CustomRole, AgentCapacity, Captain/Copilot, CSAT, InboxLimit, automation/macros, and assignment policies pass route, persistence, auth, side-effect, serializer, and tests. | Todo |
|
|
| M6 | Durable jobs and external integrations. | Search indexing, CSAT send, automation actions, notifications, webhooks, and external deliveries are queued, retryable, logged, and idempotent. | Todo |
|
|
| M7 | Reused Chatwoot frontend smoke validation. | Chatwoot frontend boots against GoChat for login, inbox list, conversation detail, message send, contact view, widget init/message, public CSAT, and key enterprise screens without adapters. | Todo |
|
|
|
|
## Slice Backlog
|
|
|
|
Work proceeds top-down unless a failing test or frontend blocker forces a narrower fix.
|
|
|
|
| Slice | Work | Reference source | Verification | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| B1 | Webhook ingress route and handler parity. | `reference/chatwoot/config/routes.rb:614-624`, `reference/chatwoot/app/controllers/webhooks/*`, `reference/chatwoot/app/controllers/api/v1/webhooks_controller.rb` | Provider lookup tests, router route dump, `go test ./...`. | Done |
|
|
| B2 | Auth/profile serializer fixtures. | `reference/chatwoot/app/controllers/api/v1/profile*`, frontend auth client. | Fixture tests for login/current user/profile/availability/settings. | Done |
|
|
| B3 | Conversation/message serializer and behavior fixtures. | Chatwoot conversation/message controllers, entities, jobs. | Fixture tests for list/show/create/update/private notes/attachments/status/assignment. | Review |
|
|
| B4 | Contact/company behavior fixtures. | Chatwoot contact/company controllers, merge/import/export/notes/labels. | Fixture tests for CRUD/search/merge/relation/import-export shells. | Doing |
|
|
| B5 | Inbox/channel behavior fixtures. | Chatwoot inbox/channel controllers and channel models. | Fixture tests for inbox CRUD, settings, business hours, members, avatar, channel config. | Todo |
|
|
| B6 | Meilisearch live-shape review. | Chatwoot frontend search usage and search controllers. | Meilisearch-backed response fixtures plus optional live integration gate. | Todo |
|
|
| B7 | SLA and assignment capacity. | Chatwoot enterprise SLA and assignment policy behavior. | Unit/integration tests for SLA state, breach, assignment capacity, policy selection. | Todo |
|
|
| B8 | CSAT account-side completion. | Chatwoot CSAT survey responses, reports, downloads, listeners. | Metrics/list/review/download/send idempotency tests. | Todo |
|
|
| B9 | Automation/macros durable side effects. | Chatwoot automation/macro services and jobs. | Action execution, logs, webhook/email transcript retry tests. | Todo |
|
|
| B10 | Audit, CustomRole, InboxLimit. | Chatwoot enterprise admin behavior and policies. | Authorization, audit emission, limits enforcement, admin payload fixtures. | Todo |
|
|
| B11 | Captain/Copilot deep behavior. | Chatwoot Captain/Copilot controllers, services, frontend clients. | Assistant/tool/document/scenario/copilot thread/task tests and feature gates. | Todo |
|
|
| B12 | Frontend smoke harness. | `reference/chatwoot` frontend. | Repeatable smoke command and checked gap report. | Todo |
|
|
|
|
## Phase 0: Test And Route Baseline
|
|
|
|
Status: done.
|
|
|
|
Checklist:
|
|
|
|
- [x] Fix `internal/handler/api/v1` route param mismatches around `:account_id`, `:id`, and resource IDs.
|
|
- [x] Fix malformed test routes that conflict with Gin wildcard rules.
|
|
- [x] Add defensive handling for zero-value services in handler edge tests.
|
|
- [x] Align handler error responses where tests encode the expected Chatwoot-compatible status category.
|
|
- [x] Keep platform routes bootable and dumpable.
|
|
- [x] Verify `go test ./...`.
|
|
- [x] Verify `go run ./cmd/dump_routes`.
|
|
|
|
Verification commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes
|
|
```
|
|
|
|
## Phase 1: Meilisearch Search Engine
|
|
|
|
Status: review.
|
|
|
|
Source material:
|
|
|
|
- `.hermes/plans/2025-05-24-global-search-meilisearch.md`
|
|
- `reference/chatwoot` search controllers, models, and indexing behavior
|
|
- Current local packages under `internal/search`, `internal/handler/api/v1/search_handler.go`, and entity repositories
|
|
|
|
Checklist:
|
|
|
|
- [x] Define a stable `SearchEngine` interface for Meilisearch-backed search and indexing.
|
|
- [x] Add search config for engine, host, API key, and index prefix.
|
|
- [x] Implement Meilisearch client wrapper with index bootstrapping and settings.
|
|
- [x] Define per-entity documents for conversations, messages, contacts, companies, articles, and help-center content as required by Chatwoot frontend behavior.
|
|
- [x] Wire create/update/delete hooks from services into indexing.
|
|
- [x] Add batch reindex command for existing data.
|
|
- [x] Keep DB search only as explicit development fallback, not as final production mode.
|
|
- [x] Add tests with a mocked search engine and integration hooks that can run without a live Meilisearch instance.
|
|
- [x] Document required Meilisearch environment variables and local startup flow.
|
|
|
|
Acceptance:
|
|
|
|
- Global search endpoint and entity search endpoints return Chatwoot-compatible payloads.
|
|
- Search results are account-scoped.
|
|
- Search indexing survives entity updates and deletes.
|
|
- `go test ./...` stays green.
|
|
|
|
Tracking table:
|
|
|
|
| ID | Task | Target files | Reference source | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| P1.1 | Add `SearchConfig` with engine, host, API key, index prefix, env binding, and defaults. | `internal/config/config.go`, `configs/config.yaml`, config tests | `.hermes/plans/2025-05-24-global-search-meilisearch.md` | Done |
|
|
| P1.2 | Add stable engine contract for search, indexing, deletes, batch indexing, and close. | `internal/search/engine.go` | Chatwoot global/entity search behavior | Done |
|
|
| P1.3 | Implement Meilisearch engine wrapper, index naming, bootstrap, sortable/filterable/searchable settings. | `internal/search/engine_meili.go` | Chatwoot search models/services | Review |
|
|
| P1.4 | Keep existing DB search as explicit dev fallback only. Production config must prefer Meilisearch. | `internal/search/engine_db.go`, `internal/search/search_service.go` | User decision on Meilisearch | Done |
|
|
| P1.5 | Define documents and serializers for conversations, messages, contacts, companies, articles, and help-center content. | `internal/search/engine.go` | `reference/chatwoot` models/serializers | Review |
|
|
| P1.6 | Wire create/update/delete hooks from entity services into async or synchronous indexing boundary. | `internal/service/*`, `internal/search/search_service.go`, `internal/app/bootstrap.go` | Chatwoot callbacks/jobs | Done |
|
|
| P1.7 | Add batch reindex command and account/entity filters. | `cmd/reindex_search` | Chatwoot reindex/search tasks | Done |
|
|
| P1.8 | Add mocked engine tests and service integration tests without requiring live Meilisearch. | `internal/search/engine_test.go`, `internal/config/config_test.go` | Existing test style | Done |
|
|
| P1.9 | Document Meilisearch env vars and local startup flow. | this doc, ops docs if needed | Hermes plan | Done |
|
|
|
|
Meilisearch local flow:
|
|
|
|
```bash
|
|
docker run --rm -p 7700:7700 -e MEILI_MASTER_KEY=gochat_dev getmeili/meilisearch:latest
|
|
GOCHAT_SEARCH_ENGINE=meilisearch GOCHAT_SEARCH_HOST=http://localhost:7700 GOCHAT_SEARCH_API_KEY=gochat_dev go run ./cmd/reindex_search -types all
|
|
```
|
|
|
|
Search environment variables:
|
|
|
|
| Variable | Default | Notes |
|
|
| --- | --- | --- |
|
|
| `GOCHAT_SEARCH_ENGINE` | `meilisearch` | Use `db` only for explicit local fallback. |
|
|
| `GOCHAT_SEARCH_HOST` | `http://localhost:7700` | Meilisearch endpoint. |
|
|
| `GOCHAT_SEARCH_API_KEY` | empty | Set to Meilisearch master/search key when enabled. |
|
|
| `GOCHAT_SEARCH_INDEX_PREFIX` | `gochat_` | Prefixes indexes such as `gochat_conversations`. |
|
|
| `GOCHAT_SEARCH_TIMEOUT_SECONDS` | `5` | HTTP timeout for search/index requests. |
|
|
|
|
## Phase 2: Route And Controller Parity Audit
|
|
|
|
Status: doing.
|
|
|
|
Checklist:
|
|
|
|
- [x] Dump Chatwoot route source declarations from `reference/chatwoot/config/routes.rb`.
|
|
- [x] Dump GoChat routes with `cmd/dump_routes`.
|
|
- [x] Build a first tracked route parity table covering method, path, controller, source, and route status.
|
|
- [x] Prioritize first frontend-critical API v1 account routes used by the Chatwoot web app.
|
|
- [ ] Extend parity table with auth scope, request params, response serializer, and handler implementation status.
|
|
- [x] Patch first-batch route names and wildcard params where Gin constraints require different internal names, while preserving external URLs.
|
|
- [x] Add regression tests for the contact conversations relation route added in this slice.
|
|
- [x] Expand route boot regression coverage for tracked Gin wildcard/param conflict groups.
|
|
|
|
Acceptance:
|
|
|
|
- Route gap report is generated and checked in.
|
|
- All currently implemented routes boot without panic.
|
|
- Missing frontend-critical routes have tickets or implementation tasks.
|
|
|
|
Tracking table:
|
|
|
|
| ID | Task | Artifact | Status |
|
|
| --- | --- | --- | --- |
|
|
| P2.1 | Generate Chatwoot route dump from `reference/chatwoot`. | `docs/parity/chatwoot_routes_static.md` | Review |
|
|
| P2.2 | Generate GoChat route dump with `cmd/dump_routes`. | `docs/parity/gochat_routes.txt` | Done |
|
|
| P2.3 | Produce route parity table: method, path, controller/handler, auth, request params, serializer, status. | `docs/parity/route_parity.md` | Review |
|
|
| P2.4 | Mark frontend-critical gaps from Chatwoot web app route usage. | `docs/parity/route_parity.md` | Done |
|
|
| P2.5 | Convert existing placeholders/stubs into tracked feature tasks instead of hidden debt. | this doc and gap report | Doing |
|
|
| P2.6 | Add route boot regression tests for Gin wildcard/param conflicts. | `internal/router/router_test.go` | Done |
|
|
| P2.7 | Track Captain/Copilot route group from `routes.rb:62-89`. | `cmd/route_parity`, `docs/parity/route_parity.md` | Done |
|
|
| P2.8 | Track assignment policies and inbox assignment policy routes from `routes.rb:306-313`. | `cmd/route_parity`, router | Done |
|
|
| P2.9 | Track widget API routes from `routes.rb:442-472`; keep `/api/v1/widget` as the Chatwoot-compatible surface. | `cmd/route_parity`, router widget mount | Done |
|
|
| P2.10 | Track public API routes from `routes.rb:569-585`, including public inbox contacts, conversations, messages, and CSAT survey. | `cmd/route_parity`, public handlers | Done |
|
|
| P2.11 | Track v2 reports, summary reports, and live reports from `routes.rb:479-513`. | `cmd/route_parity`, reports handlers | Done |
|
|
|
|
Current Phase 2 route findings:
|
|
|
|
| Type | Count | Required action |
|
|
| --- | --- | --- |
|
|
| Exact tracked critical routes | 262 | Keep covered while expanding audit scope. |
|
|
| Method-compatible update routes | 0 | First tracked batch now has exact Rails-compatible method coverage. |
|
|
| Missing tracked critical routes | 0 | Current tracked frontend-critical route set has no route-level gaps. |
|
|
|
|
Expanded tracked groups now covered by route parity:
|
|
|
|
| Area | Coverage |
|
|
| --- | --- |
|
|
| Agents and assignable agents | CRUD/bulk-create route coverage for `agents`, plus `assignable_agents#index`. |
|
|
| Canned responses | index/create/update/destroy account routes. |
|
|
| Custom attributes and custom filters | index/show/create/update/destroy account routes. |
|
|
| Labels and team membership | labels CRUD plus teams and team_members collection actions. |
|
|
| Notifications | account-scoped notifications, notification_settings, unread_count, read_all, destroy_all, snooze, unread. |
|
|
| Inbox members | Chatwoot account-scoped `inbox_members` create/show/update/destroy routes. |
|
|
| Inbox member actions | agent_bot, set_agent_bot, sync_templates, health, register_webhook, reset_secret, avatar. |
|
|
| Captain/Copilot | Assistants, assistant inboxes/scenarios, assistant responses, bulk actions, copilot threads/messages, custom tools, documents, preferences, and tasks. |
|
|
| Assignment policies | Account assignment policies, nested inbox bindings, and inbox assignment policy routes. |
|
|
| Widget API | Chatwoot `/api/v1/widget/*` route surface plus legacy `/widget/*` compatibility. |
|
|
| Public API | Public inbox contact/conversation/message routes and public CSAT survey route. |
|
|
| Reports v2 | `/api/v2/accounts/:account_id` summary reports, reports, and live reports. |
|
|
| CRM nested routes | Contact active/search/filter/export/import, contactable inboxes, contact labels/contact inboxes, company avatar/custom-attribute/contact search routes. |
|
|
|
|
New route groups tracked in this slice:
|
|
|
|
| Area | Chatwoot source | Current Go surface observed | Expected action |
|
|
| --- | --- | --- | --- |
|
|
| Captain/Copilot | `reference/chatwoot/config/routes.rb:62-89` | Many `/api/v1/accounts/:account_id/captain/...` routes already existed. | Added tracked critical set; route parity is exact. |
|
|
| Assignment policies | `reference/chatwoot/config/routes.rb:306-313` | `/assignment_policies` and local `/assignment_policies_v2` routes exist. | Added tracked route set and aligned singular inbox assignment delete route. |
|
|
| Widget API | `reference/chatwoot/config/routes.rb:442-472` | GoChat exposed many routes only under `/widget`. | Added `/api/v1/widget` aliases; behavior parity remains tracked under Phase 3/6. |
|
|
| Public API | `reference/chatwoot/config/routes.rb:569-585` | GoChat had public CSAT conversation routes. | Added public inbox/contact/conversation/message route surface and Chatwoot public CSAT path. |
|
|
| Reports v2 | `reference/chatwoot/config/routes.rb:479-513` | GoChat had report-style routes under `/api/v1/accounts`. | Added exact v2 `/api/v2/accounts/:account_id/...` report paths. |
|
|
|
|
Closed tracked critical route gaps in this slice:
|
|
|
|
| Method | Path | Chatwoot controller | Implementation note |
|
|
| --- | --- | --- | --- |
|
|
| GET | `/api/v1/accounts/:account_id/contacts/:contact_id/conversations` | `api/v1/accounts/contacts/conversations#index` | Added contact conversation relation endpoint returning Chatwoot `payload` shape, optional `inbox_id`, and latest-20 ordering. |
|
|
| POST | `/api/v1/accounts/:account_id/contacts/export` | `api/v1/accounts/contacts#export` | Added Chatwoot async request path returning `200 OK`; existing CSV download route remains for local compatibility. |
|
|
| POST | `/api/v1/accounts/:account_id/conversations/:conversation_id/toggle_priority` | `api/v1/accounts/conversations#toggle_priority` | Added Chatwoot action path returning `200 OK`. |
|
|
|
|
Closed Rails update method gaps:
|
|
|
|
| Expected | Existing GoChat route | Implementation note |
|
|
| --- | --- | --- |
|
|
| `PUT /api/v1/accounts/:account_id/conversations/:conversation_id` | `PATCH /api/v1/accounts/:account_id/conversations/:conversation_id` | Added PUT alias to the same update handler. |
|
|
| `PUT /api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id` | `PATCH /api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id` | Added PUT alias to the same update handler. |
|
|
|
|
Phase 2 commands:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes > docs/parity/gochat_routes.txt
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity
|
|
```
|
|
|
|
## Phase 3: Data And Serializer Parity
|
|
|
|
Status: doing.
|
|
|
|
Checklist:
|
|
|
|
- [ ] Compare key Chatwoot serializers/entities with Go response payloads.
|
|
- [ ] Align account, user, inbox, conversation, message, contact, company, team, label, canned response, campaign, help center, and notification payload shapes.
|
|
- [ ] Verify timestamps, IDs, enum strings, nested objects, pagination metadata, and error envelopes.
|
|
- [ ] Add fixture-driven tests for payload compatibility.
|
|
|
|
Acceptance:
|
|
|
|
- Chatwoot frontend can consume the payloads without adapter code.
|
|
- Serializer deviations are documented only where GoChat intentionally differs.
|
|
|
|
Frontend-critical API groups to audit first:
|
|
|
|
| ID | Area | Scope | Status |
|
|
| --- | --- | --- | --- |
|
|
| P3.1 | Auth/session/profile | Login, logout, current user, profile, availability, notification settings. | Done |
|
|
| P3.2 | Accounts/users/teams | Account settings, users, agents, teams, invitations, roles, permissions. | Todo |
|
|
| P3.3 | Inboxes/channels | Inbox CRUD, assignable agents, avatars, channel config, business hours, widget config. | Todo |
|
|
| P3.4 | Conversations/messages | List filters, status changes, assignment, labels, private notes, attachments, drafts, typing/read events. | Review |
|
|
| P3.5 | Contacts/companies | CRUD, merge, labels, notes, custom attributes, import/export, conversations relation. | Doing |
|
|
| P3.6 | Labels/custom attributes/custom filters | Create/update/list behavior and exact response shapes. | Todo |
|
|
| P3.7 | Notifications/reports/help center/campaigns | Frontend-visible payloads and pagination/error envelopes. | Todo |
|
|
| P3.8 | Widget/public APIs | Widget init, campaigns, config, contact, conversations, messages, direct uploads, public inbox flow, public CSAT. | Doing |
|
|
| P3.9 | Search payloads | Global search and entity search documents backed by Meilisearch. | Review |
|
|
|
|
Serializer parity work plan:
|
|
|
|
| Order | Endpoint family | Reference sources | Verification artifact | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| S1 | Auth and profile | `reference/chatwoot/app/controllers/api/v1/profile*`, frontend auth API usage | fixture tests for current user/profile payloads | Done |
|
|
| S2 | Conversations and messages | `reference/chatwoot/app/controllers/api/v1/accounts/conversations*`, serializers/entities | fixture tests for index/show/message create/update | Review |
|
|
| S3 | Contacts and companies | `reference/chatwoot/app/controllers/api/v1/accounts/contacts*`, `companies*` | fixture tests for list/show/search/merge/relation payloads | Doing |
|
|
| S4 | Inboxes and channels | `reference/chatwoot/app/controllers/api/v1/accounts/inboxes*`, channel controllers | fixture tests for inbox CRUD, channel settings, widget config | Todo |
|
|
| S5 | Notifications and settings | `reference/chatwoot/app/controllers/api/v1/accounts/notifications*` | fixture tests for notification list/actions/settings | Todo |
|
|
| S6 | Reports and CSAT | `reference/chatwoot/app/controllers/api/v1/accounts/reports*`, `csat_survey_responses*` | fixture tests for report filters and CSAT metrics/list | Todo |
|
|
| S7 | Widget/public | `reference/chatwoot/app/controllers/api/v1/widget*`, `public/api/v1*` | widget smoke fixtures and public flow tests | Doing |
|
|
| S8 | Search | `reference/chatwoot` search controllers plus frontend search client | Meilisearch-backed search response fixtures | Todo |
|
|
|
|
Serializer comparison rules:
|
|
|
|
- Compare against `reference/chatwoot` serializers/entities before changing Go responses.
|
|
- Prefer fixture-driven tests for exact JSON shape, enum strings, pagination metadata, and error envelopes.
|
|
- Preserve Chatwoot field names even if Go internal naming differs.
|
|
|
|
## Phase 4: Enterprise Feature Completion
|
|
|
|
Status: doing.
|
|
|
|
Excluded:
|
|
|
|
- SSO
|
|
- SAML
|
|
- LDAP
|
|
- OIDC
|
|
|
|
Included checklist:
|
|
|
|
- [ ] SLA policies and SLA event tracking.
|
|
- [ ] Audit logs and admin-readable audit endpoints.
|
|
- [ ] Custom roles and permission checks.
|
|
- [ ] Agent capacity and assignment limits.
|
|
- [ ] Assignment policies and auto-assignment compatibility.
|
|
- [ ] Captain/Copilot assistant, custom tools, scenarios, documents, responses, and inbox bindings.
|
|
- [ ] CSAT survey response flow, metrics, filters, and review notes.
|
|
- [ ] Inbox limits and account/inbox usage enforcement.
|
|
- [ ] Automation rules, macros, execution logs, and action side effects.
|
|
|
|
Acceptance:
|
|
|
|
- Each included feature has routes, persistence, authorization, tests, and Chatwoot-compatible response behavior.
|
|
- Unsupported SSO family features are explicitly disabled or omitted without breaking frontend navigation for enabled features.
|
|
|
|
Enterprise tracking table:
|
|
|
|
| ID | Feature | Existing Go surface | Required next work | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| P4.1 | SLA policies/events | `internal/model/sla_policy.go`, `internal/model/sla_event.go`, `internal/service/sla_policy_service.go`, `internal/service/sla_event_service.go`, `internal/handler/api/v1/sla_policy_handler.go` | Compare with Chatwoot SLA behavior; wire lifecycle events and breach tracking; add frontend payload tests. | Todo |
|
|
| P4.2 | Audit logs | `internal/model/audit.go`, `internal/service/audit_service.go`, `internal/repository/audit_repo.go`, `internal/handler/api/v1/audit_handler.go` | Ensure every mutating enterprise/core action emits audit events and filters match Chatwoot. | Todo |
|
|
| P4.3 | Custom roles/permissions | `internal/model/custom_role.go`, `internal/service/custom_role_service.go`, `internal/middleware/role_check.go`, `internal/handler/api/v1/custom_role_handler.go` | Align permission keys, inherited roles, authorization failures, and admin UX payloads. | Todo |
|
|
| P4.4 | Agent capacity | `internal/model/agent_capacity_policy.go`, `internal/service/agent_capacity_policy_service.go`, `internal/handler/api/v1/agent_capacity_handler.go`, `internal/autoassignment/*` | Enforce capacity in assignment path and auto-assignment; add limit tests. | Todo |
|
|
| P4.5 | Inbox limits | `internal/model/inbox_limit.go`, `internal/service/inbox_limit_service.go`, `internal/repository/inbox_limit_repo.go`, `internal/handler/api/v1/inbox_limit_handler.go` | Enforce account/inbox usage limits and frontend-compatible responses. | Todo |
|
|
| P4.6 | Captain/Copilot | `internal/model/captain_models.go`, `internal/model/copilot_models.go`, `internal/service/captain_*`, `internal/service/copilot_*`, `internal/handler/api/v1/captain_*`, `internal/handler/api/v1/copilot_handler.go` | Complete assistant, tools, scenarios, documents, responses, inbox bindings, suggestions, and streaming compatibility. | Todo |
|
|
| P4.7 | CSAT | `internal/csat/*`, `internal/automation/csat_survey_*`, `internal/handler/api/v1/csat_*`, `internal/service/csat_metrics_service.go` | Wire resolve-triggered survey send, public update flow, metrics, review notes, filters. | Todo |
|
|
| P4.8 | Automation and macros | `internal/automation/*`, `internal/handler/api/v1/automation_rule_handler.go`, `internal/handler/api/v1/macro_handler.go` | Finish action side effects, execution logs, webhook/email transcript delivery, and rule trigger coverage. | Todo |
|
|
| P4.9 | Assignment policies | `internal/autoassignment/*`, `internal/automation/agent_bot_rule_listener.go` | Match Chatwoot assignment policy behavior and availability/capacity rules. | Todo |
|
|
|
|
Enterprise work package breakdown:
|
|
|
|
| Package | Subtasks | Must verify | Status |
|
|
| --- | --- | --- | --- |
|
|
| SLA | Policy CRUD parity, conversation SLA assignment, first-response/next-response/resolution timers, business-hours handling, breach events, notifications. | Field/unit parity, timer state transitions, breach idempotency, report payloads. | Todo |
|
|
| Assignment and capacity | Assignment policy CRUD, inbox policy binding, round-robin/availability/capacity selection, manual assignment limits, fallback behavior. | Manual and automatic assignment respect policy, availability, team/inbox membership, and limits. | Todo |
|
|
| CSAT account side | Survey send on resolve, response list, metrics, filters, downloads, review notes, resend/idempotency, public lock already implemented. | Account API payload fixtures, metrics math, 14-day lock, one response per CSAT message. | Todo |
|
|
| Automation rules | Condition/action parity, event listener coverage, delayed actions, execution logs, stop-on-match behavior, webhook and transcript actions. | Rule trigger tests for conversation/contact/message events and durable retry for external actions. | Todo |
|
|
| Macros | Macro CRUD, availability by account/user, execute side effects, validation, audit/log output. | Execute changes conversation labels/status/assignee/team/notes/custom attributes exactly as frontend expects. | Todo |
|
|
| Audit | Audit model parity, mutating action coverage, request metadata, filters/pagination, admin endpoint payloads. | Representative mutations across core and enterprise features emit audit records. | Todo |
|
|
| Custom roles | Permission-key parity, account-user role resolution, policy middleware, create/update/delete behavior. | Permission matrix tests and frontend admin payload fixtures. | Todo |
|
|
| Inbox limits | Account/inbox limit models, create/update enforcement, UI-readable limit responses, admin overrides. | Inbox/channel create paths reject or allow consistently with configured limits. | Todo |
|
|
| Captain/Copilot | Assistants, inbox bindings, scenarios, responses, documents, tools, copilot threads/messages, tasks, streaming/tool-call behavior. | Route fixtures, persistence tests, feature gates for external LLM dependencies, frontend smoke screens. | Todo |
|
|
|
|
Enterprise acceptance gates:
|
|
|
|
| Feature | Required gates before `Done` | Reference notes |
|
|
| --- | --- | --- |
|
|
| SLA | Field names and units match Chatwoot; `only_during_business_hours` exists; applied SLA state machine is wired; breach events create notifications; delete/update statuses match Rails behavior. | `docs/verification/SLA_ASSIGNMENT_POLICY_V2_COMPATIBILITY_REPORT.md`, `docs/requirements/M11-enterprise-features.md` |
|
|
| Audit | Mutating account resources emit audit records with actor, IP, request UUID, auditable type/id, associated account, and changes; admin list pagination matches Chatwoot. | `docs/requirements/M11-enterprise-features.md` |
|
|
| CustomRole | Permission keys match Chatwoot; `AccountUser` permission resolution honors custom roles; deleting a role nullifies users; admin-only policy is enforced. | `docs/requirements/M1-accounts-and-users.md`, `docs/requirements/M11-enterprise-features.md` |
|
|
| AgentCapacity and InboxLimit | Assignment and auto-assignment respect per-inbox conversation limits; account/inbox limits are enforced in create paths and surfaced to frontend. | `docs/requirements/M1-accounts-and-users.md`, `docs/requirements/M5-team-and-assignment.md` |
|
|
| Assignment policies | V2 route and payload shape match Chatwoot; policy selection is applied during manual and automatic assignment; availability and capacity interact correctly. | `reference/chatwoot/config/routes.rb:306-313` |
|
|
| CSAT | Resolve event sends survey once; widget/WhatsApp/Twilio paths are modeled where supported; public submit/update honors lock window; metrics and download filters match Chatwoot. | `.hermes/plans/2026-05-24-automation-macro-csat.md`, `docs/requirements/M7-reporting-and-csat.md` |
|
|
| Automation and macros | Conditions/actions match Chatwoot; macro execute side effects are real; execution logs and async webhook/email transcript actions are durable and retryable. | `.hermes/plans/2026-05-24-automation-macro-csat.md`, `docs/requirements/M6-automation-and-templates.md` |
|
|
| Captain/Copilot | Assistant, tools, documents, responses, scenarios, inbox bindings, copilot threads/messages, tasks, streaming, and LLM/tool-call behavior are implemented or explicitly feature-gated. | `docs/requirements/M10-captain-and-copilot.md` |
|
|
|
|
Excluded tracking table:
|
|
|
|
| Feature | Decision | Required handling |
|
|
| --- | --- | --- |
|
|
| SSO | Excluded | Do not prioritize implementation. Existing surfaces should not block core frontend use. |
|
|
| SAML | Excluded | Do not expand beyond already present code unless needed to disable safely. |
|
|
| LDAP | Excluded | Omit from parity scope. |
|
|
| OIDC | Excluded | Omit from parity scope. |
|
|
|
|
## Phase 5: Background Jobs And Integrations
|
|
|
|
Status: planned.
|
|
|
|
Known hotspots:
|
|
|
|
- `internal/worker/worker.go` is still mostly placeholder.
|
|
- `internal/automation/action_service.go` has pending webhook/email transcript work.
|
|
- `internal/automation/csat_survey_listener.go` has pending CSAT enable/send behavior.
|
|
- `internal/auth/webhook_registry.go` has pending signature verification for Facebook/WhatsApp.
|
|
- `internal/service/analytics_service.go` has placeholder analytics paths.
|
|
|
|
Checklist:
|
|
|
|
- [ ] Map Chatwoot jobs/listeners to Go worker responsibilities.
|
|
- [ ] Implement durable job dispatch for automation, CSAT, notifications, webhooks, and search indexing.
|
|
- [ ] Add retry and failure logging for external calls.
|
|
- [ ] Add tests for job enqueueing and idempotency.
|
|
|
|
Acceptance:
|
|
|
|
- User-visible side effects do not depend on synchronous handler-only execution.
|
|
- Failed background work is observable and retryable.
|
|
|
|
Tracking table:
|
|
|
|
| ID | Task | Current hotspot | Status |
|
|
| --- | --- | --- | --- |
|
|
| P5.1 | Choose and wire durable job runner compatible with current Go stack. | `internal/worker/worker.go`, `internal/channel/dispatcher.go` | Todo |
|
|
| P5.2 | Move search indexing into retryable jobs where Chatwoot uses callbacks/jobs. | search services and worker | Todo |
|
|
| P5.3 | Implement automation webhook delivery with retry, timeout, and logs. | `internal/automation/action_service.go` | Todo |
|
|
| P5.4 | Implement email transcript delivery once email infrastructure is available. | `internal/automation/action_service.go` | Todo |
|
|
| P5.5 | Complete CSAT survey send listener and idempotency. | `internal/automation/csat_survey_listener.go`, `internal/csat/listener.go` | Todo |
|
|
| P5.6 | Complete webhook signature verification for Facebook/WhatsApp/Twilio/provider paths. | `internal/auth/webhook_registry.go`, channel providers | Todo |
|
|
| P5.7 | Replace placeholder analytics with real report builders/queries. | `internal/service/analytics_service.go`, reporting services | Todo |
|
|
|
|
## Phase 6: Core Product Placeholder Burn-down
|
|
|
|
Status: doing.
|
|
|
|
Purpose: route parity is not enough; existing handlers that return placeholder JSON must be converted into real Chatwoot-compatible behavior before the frontend can be reused directly.
|
|
|
|
Tracking table:
|
|
|
|
| ID | Area | Known references | Required next work | Status |
|
|
| --- | --- | --- | --- | --- |
|
|
| P6.1 | Account APIs | `docs/ROUTE_GAP_ANALYSIS.md`, account handlers | Replace placeholder responses with repository-backed behavior and serializer tests. | Todo |
|
|
| P6.2 | Contact APIs | `docs/ROUTE_GAP_ANALYSIS.md`, contact handlers/services | Finish merge, import/export/data-import, notes serializer depth, and Meilisearch-backed CRM search. | Doing |
|
|
| P6.3 | Conversation APIs | `docs/ROUTE_GAP_ANALYSIS.md`, conversation handlers/services | Implement frontend-critical filters, assignment, status, snooze, merge, bulk actions. | Todo |
|
|
| P6.4 | Message APIs | `docs/ROUTE_GAP_ANALYSIS.md`, message handlers/services | Implement create/list/delete, private notes, attachments, source attribution, events. | Todo |
|
|
| P6.5 | Inbox APIs | `docs/ROUTE_GAP_ANALYSIS.md`, inbox handlers/services | Implement CRUD, assignable agents, avatar, campaigns, channel settings, reset secret. | Todo |
|
|
| P6.6 | Widget/public APIs | `docs/ROUTE_GAP_ANALYSIS.md`, widget/channel provider code, `chatwootParityStub` routes | Widget/public frontend-critical route behavior is handler-backed, including public inbox flow, direct uploads/attachments, and public CSAT survey submission. | Done |
|
|
| P6.7 | Webhook ingress | `internal/router/router.go`, `internal/handler/webhook/*`, channel providers | Replace generic placeholder with provider-specific verified ingestion and dispatch. | Done |
|
|
|
|
Webhook ingress subtracking:
|
|
|
|
| ID | Provider/path | Chatwoot reference | Current Go gap | Done when | Status |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| P6.7a | Twitter `GET/POST /webhooks/twitter` | `api/v1/webhooks#twitter_crc`, `#twitter_events` | Go route exposed only `/webhooks/twitter/webhook`. | CRC and event routes exist at Chatwoot paths and use the existing Twitter handlers/tests. | Done |
|
|
| P6.7b | LINE `POST /webhooks/line/:line_channel_id` | `webhooks/line#process_payload` | Router param and handler lookup were mismatched; handler read an inbox-style param instead of line channel ID. | Handler resolves `ChannelLINE` by `channel_id`, verifies `X-Line-Signature`, and persists/dispatches like Chatwoot. | Done |
|
|
| P6.7c | Telegram `POST /webhooks/telegram/:bot_token` | `webhooks/telegram#process_payload` | Handler lookup was a placeholder and did not resolve the real inbox by bot token. | Handler resolves `ChannelTelegram` by `bot_token`, loads inbox, processes update, persists incoming messages, and returns provider-safe `200 OK`. | Done |
|
|
| P6.7d | SMS/Twilio `POST /webhooks/sms/:phone_number` | `webhooks/sms#process_payload` | Go path was `/webhooks/twilio/sms/:phone_number`; handler read an inbox-style param. | Chatwoot path is registered, phone number resolves `ChannelTwilioSMS`, message/status events persist and dispatch, and Twilio-safe XML ack behavior is covered. | Done |
|
|
| P6.7e | WhatsApp `GET/POST /webhooks/whatsapp/:phone_number` | `webhooks/whatsapp#verify`, `#process_payload` | Verify-token lookup scanned only account `0`, and Cloud signature verification used access token as a placeholder secret. | Verify challenge and POST event ingestion match Chatwoot path, token, response, and inbox resolution behavior. | Done |
|
|
| P6.7f | Instagram `GET/POST /webhooks/instagram` | `webhooks/instagram#verify`, `#events` | Chatwoot no-param route was registered but still returned parity stub responses. | Verify/event routes exist at Chatwoot paths, validate Meta signatures, resolve account/inbox from payload/subscription data, and persist/dispatch messages. | Done |
|
|
| P6.7g | TikTok `POST /webhooks/tiktok` | `webhooks/tiktok#events` | Go route expected `:business_id`; Chatwoot route has no path param and should derive identity from payload. | Handler accepts Chatwoot path, verifies `Tiktok-Signature`, resolves business/inbox from payload, and persists/dispatches provider events. | Done |
|
|
| P6.7h | Shopify `POST /webhooks/shopify` | `webhooks/shopify#events` | Chatwoot route existed but returned parity stub responses. | Route has a real verified handler with Shopify HMAC validation, `shop/redact` cleanup, and supported topic forwarding. | Done |
|
|
| P6.7i | Generic fallback and auth middleware | Go `WebhookAuth`, `webhookStub` | Generic middleware reads `:channel_type/:identifier`, which breaks provider-specific routes; fallback returned placeholder success. | Provider routes perform provider-specific verification; fallback no longer masks missing providers with success JSON. | Done |
|
|
|
|
P6.7 implementation notes:
|
|
|
|
- Public webhook routes should not depend on generic `WebhookAuth` params that do not exist on Chatwoot provider paths.
|
|
- Provider handlers may still return `200 OK` on invalid external payloads when Chatwoot does so to avoid provider retries, but the reason must be covered by tests.
|
|
- Route dump and route parity artifacts must be regenerated if public webhook paths are added to the tracked route set.
|
|
- Mark P6.7 `Done` only after provider lookup, verification, dispatch boundary, route registration, and regression tests exist for the provider set above.
|
|
|
|
Widget/public subtracking:
|
|
|
|
| ID | Slice | Reference behavior | Status |
|
|
| --- | --- | --- | --- |
|
|
| P6.6a | `/api/v1/widget` config, message send/list, contact, conversations, cable token | `reference/chatwoot/app/controllers/api/v1/widget/*` and widget SDK clients | Done |
|
|
| P6.6b | `/api/v1/widget` campaigns, events, inbox members, labels | Chatwoot widget campaigns/events/labels controllers and serializers | Done |
|
|
| P6.6c | `/api/v1/widget` message update, transcript, `contact/set_user`, Dyte participant | Chatwoot widget message/contact/transcript/integration behavior | Done |
|
|
| P6.6d | `/public/api/v1/inboxes` contact/conversation/message core flow | `reference/chatwoot/app/controllers/public/api/v1/inboxes/*` and matching jbuilder views | Done |
|
|
| P6.6e | Widget direct uploads and attachments | Chatwoot active storage/direct upload and attachment payloads | Done |
|
|
| P6.6f | Public CSAT deep behavior | Chatwoot CSAT survey controller/listener and message locking rules | Done |
|
|
|
|
## Phase 7: Verification Harness
|
|
|
|
Status: planned.
|
|
|
|
Checklist:
|
|
|
|
- [ ] Add repeatable command to compare Chatwoot and GoChat route dumps.
|
|
- [ ] Add fixture-based JSON parity tests for major frontend endpoints.
|
|
- [ ] Add Meilisearch mock tests and optional integration test mode gated by env vars.
|
|
- [ ] Add frontend smoke path using reused Chatwoot frontend once backend boot flow is ready.
|
|
- [ ] Keep a generated gap report in `docs/parity/` with date, reference commit, Go commit, and pass/fail summary.
|
|
|
|
Required verification before each feature commit:
|
|
|
|
```bash
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...
|
|
env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes
|
|
```
|
|
|
|
Verification milestone gates:
|
|
|
|
| Gate | Command or artifact | Required before |
|
|
| --- | --- | --- |
|
|
| V1 | `env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go test ./...` | Every commit that changes Go code. |
|
|
| V2 | `env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/dump_routes > docs/parity/gochat_routes.txt` | Every route/router/handler registration change. |
|
|
| V3 | `env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/route_parity` | Every tracked route or route generator change. |
|
|
| V4 | JSON fixture parity tests for touched endpoint family. | Every serializer or handler payload change. |
|
|
| V5 | Optional live Meilisearch integration test gated by env var. | Search behavior changes that claim Meilisearch compatibility. |
|
|
| V6 | Reused Chatwoot frontend smoke flow. | Before declaring frontend reuse viable. |
|
|
|
|
## Task Status Legend
|
|
|
|
- `Todo`: not started or only skeleton exists.
|
|
- `Doing`: implementation is underway in the current working tree.
|
|
- `Review`: code is implemented and needs parity/test verification.
|
|
- `Done`: committed, tested, and linked to the matching `reference/chatwoot` behavior.
|
|
- `Blocked`: needs a decision, missing dependency, or reference behavior cannot yet be reproduced.
|
|
|
|
## Ongoing Tracking Rules
|
|
|
|
- Every parity task should name the matching file or behavior in `reference/chatwoot`.
|
|
- Every route change should keep `cmd/dump_routes` passing.
|
|
- Every phase must keep `go test ./...` green before moving on.
|
|
- Test-only route fixes should be limited to malformed tests. Production route changes must preserve external Chatwoot-compatible URLs.
|
|
- New search work must target Meilisearch first.
|
|
- Update this document in the same commit as each completed parity slice: change task status, add verification command output summary, and link the touched `reference/chatwoot` behavior.
|
|
- Do not count a feature as done because a model/handler exists; it is done only when route behavior, persistence, authorization, side effects, response shape, and tests are covered.
|
|
- Keep `.codegraph/` and other generated local analysis artifacts out of product commits unless explicitly requested.
|
|
|
|
## Progress Log
|
|
|
|
- 2026-06-04: Baseline stabilized and committed as `42cdab8 chore: stabilize chatwoot parity baseline`; `go test ./...` passed and route dump reported `TOTAL: 704`.
|
|
- 2026-06-04: Phase 1 search foundation added: Meilisearch config/env defaults, `SearchEngine` contract, Meilisearch HTTP wrapper with bootstrap/settings, DB fallback adapter, document builders, reindex command, and no-live-Meilisearch tests. Verified `go test ./...` in unsandboxed mode because miniredis/httptest need local sockets; route dump still reports `TOTAL: 704`.
|
|
- 2026-06-04: Phase 1 indexing hooks wired for conversations, messages, contacts, companies, and articles. Create/update/delete paths now call the service-layer `SearchIndexer` boundary, bootstrap injects the Meilisearch-backed search service, and unit tests cover each entity hook path.
|
|
- 2026-06-04: Phase 2 route parity tracking added. Ruby/Bundler are unavailable in this workspace, so `cmd/route_parity` records static Chatwoot route DSL declarations from `reference/chatwoot/config/routes.rb`, consumes `cmd/dump_routes` output, and writes `docs/parity/route_parity.md`. First tracked critical route summary: 76 exact, 2 method-compatible, 0 parameter-compatible, 3 missing out of 81; GoChat route dump remains `TOTAL: 704`.
|
|
- 2026-06-04: First tracked Phase 2 route gaps closed. Added Chatwoot-compatible contact conversations, contact export POST, conversation `toggle_priority`, and PUT aliases for conversation/message updates; fixed contact handlers to accept `:account_id` as well as legacy `:id`. Regenerated parity report: 81 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 81; route dump now reports `TOTAL: 709`.
|
|
- 2026-06-04: Expanded Phase 2 route parity from 81 to 138 tracked frontend-critical account routes. Added account-scoped notification routes, `notification_settings` PUT alias, and Chatwoot account-level `inbox_members` create/show/update/destroy handlers. Regenerated parity report: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 138; route dump now reports `TOTAL: 723`.
|
|
- 2026-06-04: Expanded Phase 2 route parity from 138 to 251 tracked frontend-critical routes. Added tracking and route-level coverage for Captain/Copilot, assignment policies, `/api/v1/widget`, public inbox/contact/conversation/message APIs, public CSAT survey, and `/api/v2` reports. Regenerated parity report: 251 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 251; route dump now reports `TOTAL: 791`. Added router boot regression coverage for Captain static/dynamic routes, widget collection routes, public nested message routes, and v2 reports.
|
|
- 2026-06-04: Started Phase 3/6 widget behavior parity for the reused Chatwoot frontend. `/api/v1/widget/config` now returns Chatwoot-style `website_channel_config`, contact pubsub token, and global config; `/api/v1/widget/messages` accepts `X-Auth-Token` and nested `message.content`, returns Chatwoot message shape, and exposes latest messages as `{payload, meta}`; `/api/v1/widget/contact` and core conversation actions now route to real handlers instead of parity stubs. Legacy `/widget/*` response compatibility is preserved. Focused widget/service/router tests pass.
|
|
- 2026-06-04: Continued Phase 3/6 widget behavior parity. Replaced more `/api/v1/widget` stubs with handlers for `campaigns`, `events`, `inbox_members`, `labels`, and label removal. Inbox member payload now follows Chatwoot `{payload: [...]}` shape; campaigns return enabled inbox campaigns with trigger rules; events validate website/contact token context and return `204`; labels mutate the latest widget conversation only when the label exists in the account. Added focused widget handler coverage for available agents, events, and label add/remove. Remaining widget stubs: message update, transcript, `contact/set_user`, and Dyte participant integration; public inbox/contact/conversation/message routes are still placeholder-backed.
|
|
- 2026-06-04: Completed the remaining `/api/v1/widget` stub burn-down. Message update now persists submitted email/form values and identifies the contact; `contact/set_user` validates identifier HMAC, supports verified contact identification, and returns `widget_auth_token` when the contact context changes; conversation transcript returns Chatwoot-compatible status behavior around missing conversations; Dyte participant endpoint validates integration messages and returns a meeting token payload. Added model/repository support for contact inbox HMAC verification and identifier lookup. Public inbox/contact/conversation/message routes remain the next P6.6 placeholder group.
|
|
- 2026-06-04: Replaced `/public/api/v1/inboxes` contact/conversation/message placeholders with real Chatwoot public API handlers. API inboxes now resolve through `Channel::Api` identifiers, public contacts create/update by `source_id` with optional identifier HMAC verification, public conversations enforce the same verified-contact visibility split, and public messages support create/list/update submitted values. Added focused public API handler flow coverage. Verified `go test ./...`, regenerated `docs/parity/gochat_routes.txt` (`TOTAL: 791`), and regenerated `docs/parity/route_parity.md` (`251 exact, 0 missing`). Remaining P6.6 work is direct uploads/attachments and deeper public CSAT behavior.
|
|
- 2026-06-04: Completed P6.6e widget direct upload/attachment parity for the reused Chatwoot widget frontend. `/api/v1/widget/direct_uploads` now accepts ActiveStorage metadata with `website_token` + `X-Auth-Token`, returns the raw `signed_id/direct_upload` blob shape expected by `DirectUpload`, supports the follow-up PUT body upload, and attaches `message[attachments][]` signed IDs to incoming widget messages. Message create/list payloads now include Chatwoot-style attachment fields (`data_url`, `thumb_url`, `file_type`, extension, size). Added focused handler coverage for ActiveStorage create/PUT and multipart attachment-only message send/list. Verified focused package tests and regenerated route artifacts; route dump now reports `TOTAL: 793`, while tracked parity remains `251 exact, 0 missing`. Remaining P6.6 work is deeper public CSAT behavior.
|
|
- 2026-06-04: Completed P6.6f public CSAT deep behavior. `/public/api/v1/csat_survey/:id` now resolves the conversation UUID to the `input_csat` message and returns the Chatwoot public survey payload (`csat_survey_response`, display type, inbox avatar/name, locale, conversation/message IDs). Public CSAT submit now accepts nested `message.submitted_values`, updates the survey message content attributes, upserts a message-linked CSAT response, and enforces Chatwoot's 14-day lock with `422`. Public inbox message update now applies the same lock/response-builder path for `input_csat` messages. Added handler coverage for public CSAT show/update/lock and public inbox CSAT message update/lock. Focused package tests passed.
|
|
- 2026-06-04: Consolidated the Hermes-era planning into this master tracker. Added the locked decision ledger, end-to-end milestone map, ordered slice backlog, enterprise work package breakdown, and detailed P6.7 provider webhook ingress checklist. Current next implementation slice is B1/P6.7 webhook ingress.
|
|
- 2026-06-04: Started P6.7 webhook ingress parity. Added Chatwoot public webhook paths for Twitter, Telegram, LINE, SMS/Twilio, WhatsApp, Instagram, TikTok, and Shopify; removed the generic success fallback so unsupported providers no longer return placeholder success. Telegram, LINE, Twilio SMS, and TikTok handlers now resolve inboxes through provider channel records instead of inbox-id placeholders; TikTok model column naming now matches existing repository queries. Added provider lookup tests and router boot coverage. Regenerated route dump: `TOTAL: 801`; tracked route parity remains `251 exact, 0 missing`.
|
|
- 2026-06-04: Continued P6.7 webhook ingress parity. Instagram `/webhooks/instagram` now performs Chatwoot-style global verify-token challenge handling, verifies Meta signatures against env/channel app secrets, resolves Instagram inboxes from webhook sender/recipient IDs, and dispatches parsed DM/comment events through the existing Meta pipeline boundary. Shopify `/webhooks/shopify` now verifies `X-Shopify-Hmac-SHA256` with `SHOPIFY_CLIENT_SECRET`, handles `shop/redact` by deleting matching Shopify integration hooks, and forwards supported topics to the existing Shopify event processor. WhatsApp verification now queries channels by verify token directly, and Cloud API signature verification uses app secrets from provider config/env instead of access tokens. Added focused webhook tests; route dump remains `TOTAL: 801` and tracked route parity remains `251 exact, 0 missing`.
|
|
- 2026-06-04: Added the P6.7 incoming persistence boundary. Provider webhook handlers now persist normalized incoming messages into `contacts`, `contact_inboxes`, open `conversations`, and incoming `messages` instead of only parsing/logging them. Telegram, LINE, Twilio SMS, WhatsApp, Facebook/Instagram, and TikTok are wired through the shared persister; duplicates are skipped by `inbox_id + source_id`. Added direct persister coverage and Telegram webhook persistence coverage. Focused webhook/channel tests and full `go test ./...` passed.
|
|
- 2026-06-04: Continued P6.7 dispatch parity by persisting provider receipt/status events. Existing messages are updated from Twilio delivery callbacks, WhatsApp sent/delivered/read/failed statuses, Facebook/Instagram delivery/read receipts, and TikTok read receipts. Added tests for direct status update and Twilio delivery callback update. Focused webhook/channel tests and full `go test ./...` passed.
|
|
- 2026-06-05: Wired P6.7 incoming persistence into the existing dispatcher fan-out boundary. Newly persisted webhook contacts, conversations, messages, and message status updates now emit `ChannelEvent`s through `DispatchAsync`'s current sync fallback, keeping automation, bot rule, CSAT, and notification listeners reachable from provider webhooks. Added listener-based regression coverage for incoming and status events. Focused webhook tests and full `go test ./...` passed.
|
|
- 2026-06-05: Broadened P6.7 provider webhook persistence fixtures. LINE, Twilio SMS, WhatsApp, Instagram, and TikTok webhook tests now assert persisted incoming `messages` by provider source ID, matching the existing Telegram persistence fixture and reducing the remaining provider-review surface to signature edge cases and final unsupported-provider classification. Focused webhook tests and full `go test ./...` passed.
|
|
- 2026-06-05: Added TikTok webhook signature parity. `/webhooks/tiktok` now rejects missing, invalid, or stale `Tiktok-Signature` values using the same timestamp-plus-body HMAC shape as the Chatwoot reference controller, while valid signed payloads still resolve the inbox from `biz_id` and persist incoming messages. Focused webhook tests and full `go test ./...` passed.
|
|
- 2026-06-05: Tightened LINE webhook signature parity. `/webhooks/line/:line_channel_id` now rejects missing signatures when a `channel_secret` is configured and only persists signed payloads whose `X-Line-Signature` matches the raw request body. Focused webhook tests and full `go test ./...` passed.
|
|
- 2026-06-05: Completed WhatsApp route-level webhook verification coverage. `/webhooks/whatsapp/:phone_number` now has tests for verify-token challenge echo, signed Cloud API POST persistence, and missing `X-Hub-Signature-256` rejection without message persistence. Focused webhook tests and full `go test ./...` passed.
|
|
- 2026-06-05: Finalized P6.7 provider classification. Twitter CRC now returns `sha256=<base64-hmac>` like Chatwoot, Twitter route-level CRC/event tests exist, Instagram rejects unsigned signed-required event payloads without persistence, and Telegram, SMS/Twilio, Instagram, Shopify, and the already completed providers are marked Done in the webhook ingress tracker. Focused webhook/API tests and full `go test ./...` passed.
|
|
- 2026-06-05: Started B3 dashboard conversation/message serializer parity. Conversation list/filter/show/create/update and message list/create/update/retry now return Chatwoot frontend payload shapes instead of the Go response envelope; message creation accepts frontend composer JSON/multipart fields and preserves `echo_id`/`content_attributes`; account-scoped conversation `display_id` routing is now generated and resolved. Verified focused conversation/message handler tests, full `go test ./...`, and `git diff --check`.
|
|
- 2026-06-05: B4 contacts/companies first checkpoint committed as `3481597 feat(crm): align contact company payloads`; focused CRM tests, handler/service/repository tests, escalated full `go test ./...`, route dump `TOTAL: 809`, route parity `251 exact, 0 missing`, and `git diff --check` passed.
|
|
- 2026-06-05: B4 contacts/companies second checkpoint committed as `7a033e2 feat(crm): complete contact label avatar gaps`; contact labels now persist through `contact_labels`, label list/update returns Chatwoot `{ payload }`, CRM contact list/search/filter labels use contact labels, contact/company avatar delete and company destroy-custom-attributes match Chatwoot response envelopes, route dump is `TOTAL: 817`, route parity is `261 exact, 0 missing`, escalated full `go test ./...` passed, and `git diff --check` passed.
|
|
- 2026-06-05: B4 contact merge checkpoint committed as `1e3bf47 feat(crm): align contact merge action`; `/api/v1/accounts/:account_id/actions/contact_merge` now matches Chatwoot route usage, merge returns the raw contact model serializer, moves mergee conversations/contact inboxes/notes/messages to the base contact, preserves base attributes while filling blanks from mergee, route dump is `TOTAL: 818`, route parity is `262 exact, 0 missing`, full `go test ./...` passed, and `git diff --check` passed.
|
|
- 2026-06-05: B4 contact import/data-import checkpoint committed as `8eed2f1 feat(crm): persist contact data imports`; contact imports now persist Chatwoot-shaped `data_imports`, missing `import_file` returns `422`, successful imports return empty `200 OK`, lifecycle counts/errors are recorded, and CSV rows merge/update existing contacts by identifier/email/phone while applying labels and custom attributes. Focused import tests, handler/service/repository/router package tests, full `go test ./...`, and `git diff --check` passed.
|
|
- 2026-06-05: B4 contact export artifact checkpoint committed as `47c2b80 feat(crm): persist contact export artifacts`; `POST /contacts/export` now persists `contact_exports`, generates Chatwoot default/requested CSV columns with UTF-8 BOM, supports label/filter export inputs, stores a download URL and row count, and creates a user notification with file metadata. Focused export tests, handler/service/repository/router package tests, regenerated route dump `TOTAL: 819`, route parity `262 exact, 0 missing`, full `go test ./...`, and `git diff --check` passed.
|
|
- 2026-06-05: B4 contact/company notes checkpoint committed as `af57482 feat(crm): align contact note payloads`; contact note index/create/show/update now returns Chatwoot raw note JSON with nested agent payloads, accepts flat and nested note params, destroy returns empty `200 OK` and hard-deletes notes, company notes include nested user metadata, and the Rails-compatible `PUT` note update route is registered. Focused notes/API tests, full `go test ./...`, regenerated route dump `TOTAL: 820`, route parity `262 exact, 0 missing`, and `git diff --check` passed.
|
|
- 2026-06-05: B4 nested contact/company conversations checkpoint committed as `fb775b7 feat(crm): align nested conversation payloads`; contact and company nested conversation endpoints now return Chatwoot `{ payload: [...] }` conversation partials with nested sender metadata, latest message payloads, `last_non_activity_message`, labels, timestamps, priority, account/inbox fields, and company latest-20 ordering by `last_activity_at DESC`. Focused nested conversation tests, focused handler/service/repository/router tests, escalated full `go test ./...`, and `git diff --check` passed. Route dump unchanged at `TOTAL: 820`.
|
|
- 2026-06-05: B4 CRM Meilisearch checkpoint committed as `202da19 feat(crm): route crm search through meilisearch`; contact and company CRM search now routes non-empty queries through the configured search service, resolves Meilisearch result IDs back through account-scoped repositories to preserve Chatwoot CRM list payloads, wires the search reader in app bootstrap, and adds company document fallback coverage to global DB search. Focused search/service/repository/API tests, full `go test ./...`, and `git diff --check` passed. Route dump unchanged at `TOTAL: 820`.
|
|
- 2026-06-05: B4 contact import label validation checkpoint committed as `653db81 feat(crm): validate imported contact labels`; CSV import now matches Chatwoot's approved-label guard by rejecting rows whose `labels` column contains labels absent from the account, preserving valid rows, recording failed row counts on `data_imports`, and preventing arbitrary tag creation from CSV input. Focused import/service/API tests, full `go test ./...`, and `git diff --check` passed. Route dump unchanged at `TOTAL: 820`.
|
|
- 2026-06-05: B4 contact export email checkpoint committed as `c2766fe feat(crm): email contact export completions`; completed contact exports now invoke a Chatwoot-style `contact_export_complete` mailer boundary for the requesting user's email, using the export download URL and subject `Your contact's export file is available to download.`, while retaining the persisted notification. Focused export/service/API tests, full `go test ./...`, and `git diff --check` passed. Route dump unchanged at `TOTAL: 820`.
|