diff --git a/cmd/route_parity/main.go b/cmd/route_parity/main.go index 82dfdda8..45f19919 100644 --- a/cmd/route_parity/main.go +++ b/cmd/route_parity/main.go @@ -23,6 +23,9 @@ type sourceDecl struct { } var criticalRoutes = []route{ + {Method: "GET", Path: "/app", Controller: "dashboard#index", Source: "routes.rb:19"}, + {Method: "GET", Path: "/app/*params", Controller: "dashboard#index", Source: "routes.rb:20"}, + {Method: "POST", Path: "/api/v1/accounts/", Controller: "api/v1/accounts#create", Source: "routes.rb:47"}, {Method: "GET", Path: "/api/v1/accounts/:account_id", Controller: "api/v1/accounts#show", Source: "routes.rb:47"}, {Method: "PUT", Path: "/api/v1/accounts/:account_id", Controller: "api/v1/accounts#update", Source: "routes.rb:47"}, diff --git a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md index 8f22e41a..43a5fe9c 100644 --- a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md +++ b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md @@ -49,14 +49,14 @@ Hermes task landing checklist: ## Current Baseline -- Current tracking checkpoint: 2026-06-06 after docs checkpoint, prepared as `docs: land full parity tracking plan`. -- Latest implementation checkpoint: `efceb22 feat(help-center): expose public sitemap`. -- Latest documentation/tooling checkpoint: this checkpoint, prepared as `docs: land full parity tracking plan`; this document is now the active follow-up plan and supersedes `.hermes/plans/*`. -- Worktree status at this implementation checkpoint: P3.8/P6.6l public help-center sitemap drift is now implemented for Chatwoot `Public::Api::V1::PortalsController#sitemap`, `sitemap.xml.erb`, and `routes.rb:591`. GoChat registers `/hc/:slug/sitemap.xml`, resolves only non-archived portals by slug, emits sitemap XML for published portal articles only, uses the portal custom domain when configured, prefixes `https://` when the base URL has no protocol, and emits public article links plus ISO date `lastmod` entries. P6.6g public article lists, P6.6h public categories, P6.6i public portal show, P6.6j public portal search, P6.6k public article show/markdown/tracking, P3.5 company avatar form/pagination drift, P3.11 account integrations apps/hooks API, P3.10 account webhook API, P3.9 account agent-bot API, P6.8 contact outbound voice call, P3.7d enterprise help-center article translation, P6.5 inbox agent-bot member actions, P6 message retry parity, P6 conversation custom-attribute response parity, and P3.7a-P3.7c help-center payload parity remain in Review. The fresh placeholder audit found remaining `chatwootParityStub` routes only in webhook handler-not-injected fallbacks, not in the reused dashboard account/contact/conversation/message/inbox critical path. P3.6 custom filters/custom attribute definitions and label CRUD are already in Review. B11.1a-B11.3e Captain/Copilot slices remain in Review; B12.1/B12.2/B12.3 smoke harnesses remain in Review; P5 durable job work through P5.13b is in Review. Next active implementation slice is another Phase 6 placeholder burn-down item or a new Phase 2/3 drift slice from fresh reference/smoke evidence. +- Current tracking checkpoint: 2026-06-06 after this implementation checkpoint, prepared as `feat(dashboard): expose app shell routes`. +- Latest implementation checkpoint: this checkpoint, prepared as `feat(dashboard): expose app shell routes`. +- Latest documentation/tooling checkpoint: `docs: land full parity tracking plan`; this document is now the active follow-up plan and supersedes `.hermes/plans/*`. +- Worktree status at this implementation checkpoint: P2/P3 dashboard deep-link route drift is now implemented for Chatwoot `DashboardController#index`, `routes.rb:19-20`, and the mailer/push helper paths that rely on `/app/*params`. GoChat registers `/app` and `/app/*params`, serves a Chatwoot-style dashboard HTML shell for browser requests, and returns Chatwoot's `406 { error }` response for JSON dashboard requests. P3.8/P6.6l public help-center sitemap drift is in Review. P6.6g public article lists, P6.6h public categories, P6.6i public portal show, P6.6j public portal search, P6.6k public article show/markdown/tracking, P3.5 company avatar form/pagination drift, P3.11 account integrations apps/hooks API, P3.10 account webhook API, P3.9 account agent-bot API, P6.8 contact outbound voice call, P3.7d enterprise help-center article translation, P6.5 inbox agent-bot member actions, P6 message retry parity, P6 conversation custom-attribute response parity, and P3.7a-P3.7c help-center payload parity remain in Review. The fresh placeholder audit found remaining `chatwootParityStub` routes only in webhook handler-not-injected fallbacks, not in the reused dashboard account/contact/conversation/message/inbox critical path. P3.6 custom filters/custom attribute definitions and label CRUD are already in Review. B11.1a-B11.3e Captain/Copilot slices remain in Review; B12.1/B12.2/B12.3 smoke harnesses remain in Review; P5 durable job work through P5.13b is in Review. Next active implementation slice is another Phase 6 placeholder burn-down item or a new Phase 2/3 drift slice from fresh reference/smoke evidence. - `go test ./...` passes. -- Route dump succeeds with `TOTAL: 881` after adding the public help-center sitemap route. +- Route dump succeeds with `TOTAL: 883` after adding the dashboard `/app` shell routes. - Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`. -- Tracked frontend-critical route audit covers 342 Chatwoot routes: 333 exact, 0 method-compatible, 9 parameter-compatible, 0 missing. The 9 parameter-compatible routes are Gin-internal parameter-name differences for nested AgentCapacityPolicy users/inbox limits plus the public article `.md`/`.png` suffixes served through the same external article route dispatcher. +- Tracked frontend-critical route audit covers 344 Chatwoot routes: 335 exact, 0 method-compatible, 9 parameter-compatible, 0 missing. The 9 parameter-compatible routes are Gin-internal parameter-name differences for nested AgentCapacityPolicy users/inbox limits plus the public article `.md`/`.png` suffixes served through the same external article route dispatcher. - `/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. @@ -138,7 +138,7 @@ This table is the shortest authoritative handoff view. If an older lower section | Priority | Workstream | Current state | Next checkpoint | Commit close rule | | --- | --- | --- | --- | --- | -| 1 | Phase 2/3 drift | Tracked route parity is 0 missing for the current 342-route critical set; help-center portal/category/article routes from `routes.rb:385-404`, public help-center portal/sitemap/article/category/search/article-detail routes from `routes.rb:590-601`, enterprise contact outbound voice call from `routes.rb:216`, account agent-bot routes from `routes.rb:94-97`, account webhook routes from `routes.rb:342`, and account integration app/hook routes from `routes.rb:345-348` are now explicitly tracked. Notification list/action serializers, user notification-settings raw payloads, campaigns raw payload/display-id routes, Devise password reset/confirmation payloads, CRM shared attachment payloads plus fixed 100-row attachment pagination, account/settings payloads, assignable-agent payloads, agent index full-list behavior, agent create/update/delete defaults/errors/scope, account agent-bot route/payload/mutation behavior, account webhook payload/mutation behavior, integration app/hook payload behavior, label CRUD payloads, custom filters, custom attribute definitions, contact outbound voice calls, help-center portal/category/article payloads, public widget popular-article lists, public help-center category list/show payloads, public portal show/default-locale payloads, public portal search payloads, public article show/markdown/tracking routes, and public sitemap XML now match the inspected Chatwoot contract. | Run the next evidence-backed route/controller/serializer drift audit. | Regenerate parity artifacts when routes change and add endpoint-family fixture tests. | +| 1 | Phase 2/3 drift | Tracked route parity is 0 missing for the current 344-route critical set; dashboard `/app` shell routes from `routes.rb:19-20`, help-center portal/category/article routes from `routes.rb:385-404`, public help-center portal/sitemap/article/category/search/article-detail routes from `routes.rb:590-601`, enterprise contact outbound voice call from `routes.rb:216`, account agent-bot routes from `routes.rb:94-97`, account webhook routes from `routes.rb:342`, and account integration app/hook routes from `routes.rb:345-348` are now explicitly tracked. Notification list/action serializers, user notification-settings raw payloads, campaigns raw payload/display-id routes, Devise password reset/confirmation payloads, CRM shared attachment payloads plus fixed 100-row attachment pagination, account/settings payloads, assignable-agent payloads, agent index full-list behavior, agent create/update/delete defaults/errors/scope, account agent-bot route/payload/mutation behavior, account webhook payload/mutation behavior, integration app/hook payload behavior, label CRUD payloads, custom filters, custom attribute definitions, contact outbound voice calls, help-center portal/category/article payloads, dashboard app shell route behavior, public widget popular-article lists, public help-center category list/show payloads, public portal show/default-locale payloads, public portal search payloads, public article show/markdown/tracking routes, and public sitemap XML now match the inspected Chatwoot contract. | Run the next evidence-backed route/controller/serializer drift audit. | Regenerate parity artifacts when routes change and add endpoint-family fixture tests. | | 2 | Phase 6 placeholder audit | Widget/public/webhook critical placeholders are burned down; inbox WhatsApp health/register-webhook and sync-template drift are closed; fresh `rg` audit shows only webhook nil-handler fallbacks still call `chatwootParityStub`; dashboard conversation transcript/custom-attribute response drift and message retry status drift are closed. | Continue targeted account/contact/conversation/message/inbox drift from reference/smoke evidence. | `rg` placeholder audit is recorded and no reused-frontend blocker is ownerless. | | 3 | P3.9 account agent-bot API | Implemented for the reused dashboard AgentBots settings route with no-trailing-slash routes, PATCH update, raw Jbuilder-style payloads, account mutation scope, system-bot show/list visibility, empty `200 OK` delete, and full reset/avatar action payloads. | Keep in Review; reopen only if live settings smoke exposes avatar upload storage or administrator-secret gating drift. | Focused AgentBot handler tests, service/router focused tests, route dump/parity regeneration, full `go test ./...`, and `git diff --check` passed. | | 4 | P3.10 account webhooks API | Implemented for the reused dashboard Webhooks settings route with PATCH update, Chatwoot `{ payload }` list/mutation serializers, nested `{ webhook: ... }` bodies, generated secret, account-scoped mutations, URL/subscription validation, optional inbox serialization, and empty `200 OK` delete. | Keep in Review; reopen only if live settings smoke exposes audit writer or delivery-signature drift beyond the existing delivery service boundary. | Focused webhook handler/service/router tests, migration test, route dump/parity regeneration, full `go test ./...`, and `git diff --check` passed. | @@ -207,6 +207,7 @@ This ledger records the committed parity checkpoints that future slices should b | Commit | Scope | Verification summary | Follow-up state | | --- | --- | --- | --- | +| `feat(dashboard): expose app shell routes` | Advances Phase 2/3 dashboard route parity by matching Chatwoot `DashboardController#index`, `routes.rb:19-20`, `layouts/vueapp.html.erb`, and mailer/push helper paths that resolve to `/app/accounts/:account_id/conversations/:display_id`. GoChat now registers `/app` and `/app/*params`, returns a Chatwoot-style HTML dashboard shell with `window.chatwootConfig`, `window.globalConfig`, and the `#app` mount for browser requests, and returns Chatwoot's `406 { error: "Please use API routes instead of dashboard routes for JSON requests" }` for JSON dashboard requests. | `go test ./internal/router -run 'DashboardIndex\|RegisterRoutes' -count=1`; `go test ./cmd/route_parity -count=1`; `go run ./cmd/dump_routes > docs/parity/gochat_routes.txt`; `go run ./cmd/route_parity`; `go test ./...`; `git diff --check`. Route dump is `TOTAL: 883`; tracked route parity is `335 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 344`. | Dashboard deep-link routes move to Review for current mailer/push evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `docs: land full parity tracking plan` | Documentation-only checkpoint requested before continuing implementation. It confirms the worktree was already clean at `efceb22`, makes the Hermes search and automation/macro/CSAT task families explicit in the master tracker, and records the current implementation baseline plus next executable checkpoints in one place. | `git diff --check`. No Go code changed and no route artifacts changed. | Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke from this tracker; `.hermes/plans/*` are source notes only. | | `feat(help-center): expose public sitemap` | Advances P3.8/P6.6 public help-center parity by matching `Public::Api::V1::PortalsController#sitemap`, `sitemap.xml.erb`, and `routes.rb:591`. GoChat now registers `/hc/:slug/sitemap.xml`, resolves only non-archived portals by slug, emits sitemap XML for published portal articles only, uses custom domain URLs with Chatwoot-style `https://` prefixing when no protocol is present, and writes public article links plus ISO date `lastmod` values. | `go test ./internal/handler/api/v1 -run PortalHandler -count=1`; `go test ./internal/router -run Router -count=1`; `go test ./cmd/route_parity -count=1`; `go run ./cmd/dump_routes > docs/parity/gochat_routes.txt`; `go run ./cmd/route_parity`; `go test ./...`; `git diff --check`. Route dump is `TOTAL: 881`; tracked route parity is `333 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 342`. | P3.8/P6.6l moves to Review for current public sitemap evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(help-center): expose public article show` | Advances P3.8/P6.6 public help-center parity by matching `Public::Api::V1::Portals::ArticlesController#show/#show_markdown/#tracking_pixel`, the public article Jbuilder payload, and `routes.rb:598-601`. GoChat now registers the public article route, dispatches `.md` and `.png` suffixes through the same Gin-compatible article slug route, resolves only non-archived portals by slug, returns public article payloads by slug, serves raw markdown for published articles, rejects markdown for drafts, and increments published article views when serving the 1x1 tracking pixel. | `go test ./internal/handler/api/v1 -run ArticleHandler -count=1`; `go test ./internal/router -run Router -count=1`; `go test ./cmd/route_parity -count=1`; `go run ./cmd/dump_routes > docs/parity/gochat_routes.txt`; `go run ./cmd/route_parity`; `go test ./...`; `git diff --check`. Route dump is `TOTAL: 880`; tracked route parity is `332 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 341`. | P3.8/P6.6k moves to Review for current public article show/markdown/tracking evidence; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | @@ -2345,3 +2346,4 @@ Verification milestone gates: - 2026-06-06: P3.8/P6.6k public article checkpoint prepared as `feat(help-center): expose public article show`; audited Chatwoot public articles show/markdown/tracking controller actions, public article Jbuilder, and routes `598-601`. GoChat now exposes `/hc/:slug/articles/:article_slug`, handles `.md` and `.png` suffixes through a Gin-compatible dispatcher, returns public article payloads by slug, serves raw markdown for published articles, rejects draft markdown, and increments published article views when serving the tracking pixel. Focused article/router/route-parity tests, route dump/parity regeneration, full `go test ./...`, and `git diff --check` passed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: P3.8/P6.6l public sitemap checkpoint prepared as `feat(help-center): expose public sitemap`; audited Chatwoot public portal sitemap action, `sitemap.xml.erb`, and route `591`. GoChat now exposes `/hc/:slug/sitemap.xml`, rejects archived portals, returns sitemap XML for published articles only, uses portal custom domains with `https://` prefixing when no protocol is present, and emits public article links plus ISO `lastmod` dates. Focused portal/router/route-parity tests, route dump/parity regeneration, full `go test ./...`, and `git diff --check` passed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. - 2026-06-06: Documentation checkpoint prepared as `docs: land full parity tracking plan`; worktree was clean at `efceb22 feat(help-center): expose public sitemap`, so this docs-only checkpoint lands the remaining Hermes task-family checklist directly into the master tracker. The plan now maps search, indexing, automation, macros, CSAT, enterprise paid features, and B12 reused-frontend smoke to concrete owner sections, current states, and reopen triggers. Verification for this docs-only checkpoint: `git diff --check`; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. +- 2026-06-06: Dashboard app-shell checkpoint prepared as `feat(dashboard): expose app shell routes`; audited Chatwoot `DashboardController#index`, `routes.rb:19-20`, `layouts/vueapp.html.erb`, and mailer/push helper paths that point to `/app/accounts/:account_id/conversations/:display_id`. GoChat now exposes `/app` and `/app/*params`, serves a dashboard HTML shell with `window.chatwootConfig`, `window.globalConfig`, and the `#app` mount, rejects JSON dashboard requests with Chatwoot's `406` error, and tracks the two app shell routes in route parity. Focused router tests, route-parity tests, route dump/parity regeneration, full `go test ./...`, and `git diff --check` passed. Continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. diff --git a/docs/parity/gochat_routes.txt b/docs/parity/gochat_routes.txt index bad4b376..4850285a 100644 --- a/docs/parity/gochat_routes.txt +++ b/docs/parity/gochat_routes.txt @@ -412,6 +412,8 @@ GET /api/v2/accounts/:account_id/summary_reports/channel GET /api/v2/accounts/:account_id/summary_reports/inbox GET /api/v2/accounts/:account_id/summary_reports/label GET /api/v2/accounts/:account_id/summary_reports/team +GET /app +GET /app/*params GET /auth/validate_token GET /cable GET /hc/:slug @@ -879,4 +881,4 @@ PUT /public/api/v1/csat_survey/:id PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id PUT /widget/direct_uploads/:upload_uuid -TOTAL: 881 +TOTAL: 883 diff --git a/docs/parity/route_parity.md b/docs/parity/route_parity.md index 6fd60c5d..aaa5a515 100644 --- a/docs/parity/route_parity.md +++ b/docs/parity/route_parity.md @@ -7,7 +7,7 @@ Generated from: This report covers tracked frontend-critical Chatwoot routes from `reference/chatwoot/config/routes.rb`, including API v1 account routes, Captain/Copilot, assignment policies, widget/public APIs, and API v2 reports. Ruby is not installed in the workspace, so Chatwoot routes are sourced from static route declarations instead of `bin/rails routes`. -Summary: 333 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 342 tracked critical routes. +Summary: 335 exact, 0 method-compatible, 9 parameter-compatible, 0 missing out of 344 tracked critical routes. ## Missing Critical Routes @@ -209,6 +209,8 @@ These routes exist with equivalent method and path shape but different parameter | GET | `/api/v2/accounts/:account_id/summary_reports/inbox` | `/api/v2/accounts/:account_id/summary_reports/inbox` | `api/v2/accounts/summary_reports#inbox` | `routes.rb:483` | exact | | GET | `/api/v2/accounts/:account_id/summary_reports/label` | `/api/v2/accounts/:account_id/summary_reports/label` | `api/v2/accounts/summary_reports#label` | `routes.rb:484` | exact | | GET | `/api/v2/accounts/:account_id/summary_reports/team` | `/api/v2/accounts/:account_id/summary_reports/team` | `api/v2/accounts/summary_reports#team` | `routes.rb:482` | exact | +| GET | `/app` | `/app` | `dashboard#index` | `routes.rb:19` | exact | +| GET | `/app/*params` | `/app/*params` | `dashboard#index` | `routes.rb:20` | exact | | GET | `/hc/:slug` | `/hc/:slug` | `public/api/v1/portals#show` | `routes.rb:590` | exact | | GET | `/hc/:slug/:locale` | `/hc/:slug/:locale` | `public/api/v1/portals#show` | `routes.rb:592` | exact | | GET | `/hc/:slug/:locale/articles` | `/hc/:slug/:locale/articles` | `public/api/v1/portals/articles#index` | `routes.rb:594` | exact | diff --git a/internal/router/router.go b/internal/router/router.go index ef8b7efc..ab2f38e2 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -1,6 +1,11 @@ package router import ( + "encoding/json" + "html" + "net/http" + "os" + "strings" "time" "github.com/gin-gonic/gin" @@ -189,6 +194,11 @@ func RegisterRoutes( // Swagger UI — interactive API documentation engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + // Dashboard shell routes used by Chatwoot mailer and push deep links. + // Reference: Chatwoot routes.rb `get '/app'`, `get '/app/*params'` -> DashboardController#index. + engine.GET("/app", dashboardIndex) + engine.GET("/app/*params", dashboardIndex) + // Auth routes — PUBLIC, no AuthRequired middleware v1.RegisterAuthRoutes(engine.Group("/api/v1"), handlers.Auth) v1.RegisterChatwootAuthRoutes(engine.Group("/auth"), handlers.Auth) @@ -1963,3 +1973,63 @@ func healthCheck(c *gin.Context) { "uptimeSeconds": uint64(uptime.Seconds()), }) } + +func dashboardIndex(c *gin.Context) { + if dashboardWantsJSON(c) { + c.JSON(http.StatusNotAcceptable, gin.H{"error": "Please use API routes instead of dashboard routes for JSON requests"}) + return + } + + installationName := strings.TrimSpace(os.Getenv("INSTALLATION_NAME")) + if installationName == "" { + installationName = "GoChat" + } + frontendURL := strings.TrimRight(os.Getenv("FRONTEND_URL"), "/") + helpCenterURL := strings.TrimRight(os.Getenv("HELPCENTER_URL"), "/") + + c.Data(http.StatusOK, "text/html; charset=utf-8", []byte(dashboardHTML(installationName, frontendURL, helpCenterURL))) +} + +func dashboardWantsJSON(c *gin.Context) bool { + if strings.HasSuffix(c.Request.URL.Path, ".json") { + return true + } + accept := strings.ToLower(c.GetHeader("Accept")) + return strings.Contains(accept, "application/json") && !strings.Contains(accept, "text/html") +} + +func dashboardHTML(installationName string, frontendURL string, helpCenterURL string) string { + name := html.EscapeString(installationName) + chatwootConfig := dashboardJSON(map[string]any{ + "hostURL": frontendURL, + "helpCenterURL": helpCenterURL, + "allowedLoginMethods": []string{"email"}, + "signupEnabled": "false", + "isEnterprise": "true", + "selectedLocale": "en", + }) + globalConfig := dashboardJSON(map[string]any{"INSTALLATION_NAME": installationName}) + return ` + +
+