chore(overlay): scaffold custom/ overlay tree and add microsoft_shared? predicate

Foundation for LeadChat customizations using Chatwoot's built-in custom/
extension mechanism (ChatwootApp.extensions). Adds:

- LEADCHAT.md: index of every customization touchpoint, updated as work lands
- custom/: overlay tree where Custom::Leadchat::* modules live, autoloaded
  alongside enterprise/ via a parallel block in config/application.rb
- Channel::Email#microsoft_shared? predicate as the first overlay (used by
  the upcoming Microsoft 365 Shared Inbox provider)

OSS-file additions wrapped in '# === LeadChat: ===' markers so future
upstream-merge conflicts are mechanical.
This commit is contained in:
netlas
2026-04-27 12:08:08 +03:00
parent 2d54224d04
commit 800f2b2654
4 changed files with 75 additions and 0 deletions

49
LEADCHAT.md Normal file
View File

@@ -0,0 +1,49 @@
# LeadChat customizations
This file is the index of every LeadChat-specific customization on top of upstream Chatwoot. Consult it during upstream merges to know what's customized and where conflicts may surface.
LeadChat is a white-label Chatwoot fork maintained on the `leadchat` branch. Customizations are layered using Chatwoot's built-in `custom/` extension mechanism (see [`lib/chatwoot_app.rb`](lib/chatwoot_app.rb) — `ChatwootApp.extensions` includes `'custom'` automatically when a `custom/` directory exists at repo root).
## Layering strategy
1. **Overlay modules under `custom/`** — preferred for Ruby modifications. New code lives in `custom/app/...` under the `Custom::Leadchat::*` namespace; OSS files only get a one-line `include_mod_with` / `prepend_mod_with` hook that Chatwoot's extension loader resolves automatically.
2. **Direct edits to OSS** — only where no overlay mechanism exists (routes, schedule, Vue components). Each block is wrapped in `# === LeadChat: <feature> (start/end) ===` markers so future merge conflicts are mechanical.
3. **Brand script** — separate process that overwrites brand strings in upstream files even after they're updated. Listed in "Brand script targets" below so we can extend its rules as new strings surface.
## Customization categories
### A. Overlay modules (Custom::Leadchat::*)
| Hook in OSS file | Overlay file | Adds |
|---|---|---|
| `app/models/channel/email.rb` (bottom) | `custom/app/models/custom/leadchat/channel/email_extension.rb` | `Channel::Email#microsoft_shared?` predicate |
### B. Direct edits to OSS files
| File | Change | Marker |
|---|---|---|
| `config/application.rb` | Mirror enterprise/ autoload setup for `custom/` so `Custom::*` modules are autoloaded and `custom/config/initializers/**/*.rb` are required | `# === LeadChat: custom/ overlay autoload ===` |
| `app/models/channel/email.rb` (bottom) | One-line `include_mod_with` hook for the email_extension overlay | `# === LeadChat: microsoft_shared ===` |
### C. New top-level files (no upstream conflict potential)
| Path | Purpose |
|---|---|
| `LEADCHAT.md` | This file |
| `custom/` | Root of LeadChat overlay tree, picked up by `ChatwootApp.custom?` and the Phase-0 autoload setup |
### D. Brand script targets
Strings/files that the brand script rewrites away from "Chatwoot". Extend this list whenever a new "Chatwoot" mention surfaces in product UI.
- `app/javascript/dashboard/routes/dashboard/settings/inbox/components/SenderNameExamplePreview.vue` lines 33, 43 — hardcoded `businessName: 'Chatwoot'` in sender-name preview tiles. (Discovered 2026-04-27 while building the Microsoft Shared Inbox feature; not yet handled by the brand script.)
## Active feature work
- **Microsoft Shared Inbox provider** (in progress) — new `microsoft_shared` Email Channel type backed entirely by Microsoft Graph API, supports multiple shared mailboxes per OAuth via separate channel-add flows, bypasses Security Defaults / SMTP AUTH retirement. Plan: `~/.claude/plans/starry-mapping-snowflake.md`.
## Conventions
- Conventional Commits (`type(scope): subject`)
- No Claude attribution in commits or PR bodies
- Update this file as each customization lands, not as a final pass

View File

@@ -78,3 +78,7 @@ class Channel::Email < ApplicationRecord
self.forward_to_email ||= "#{SecureRandom.hex}@#{account.inbound_email_domain}"
end
end
# === LeadChat: microsoft_shared (start) ===
Channel::Email.include_mod_with('Leadchat::Channel::EmailExtension')
# === LeadChat: microsoft_shared (end) ===

View File

@@ -51,6 +51,21 @@ module Chatwoot
enterprise_initializers = Rails.root.join('enterprise/config/initializers')
Dir[enterprise_initializers.join('**/*.rb')].each { |f| require f } if enterprise_initializers.exist?
# === LeadChat: custom/ overlay autoload (start) ===
# Mirrors the enterprise/ setup so Custom::* modules under custom/app/ are autoloaded
# and registered with ChatwootApp.extensions (lib/chatwoot_app.rb#custom?).
if Rails.root.join('custom').exist?
config.eager_load_paths << Rails.root.join('custom/lib') if Rails.root.join('custom/lib').exist?
# rubocop:disable Rails/FilePath
config.eager_load_paths += Dir["#{Rails.root}/custom/app/**"]
# rubocop:enable Rails/FilePath
config.paths['app/views'].unshift('custom/app/views') if Rails.root.join('custom/app/views').exist?
custom_initializers = Rails.root.join('custom/config/initializers')
Dir[custom_initializers.join('**/*.rb')].each { |f| require f } if custom_initializers.exist?
end
# === LeadChat: custom/ overlay autoload (end) ===
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading

View File

@@ -0,0 +1,7 @@
module Custom::Leadchat::Channel::EmailExtension
extend ActiveSupport::Concern
def microsoft_shared?
provider == 'microsoft_shared'
end
end