Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,160 @@
|
||||
# CRM Decision Report
|
||||
|
||||
## Decision
|
||||
|
||||
Decision: `extract-plugin`.
|
||||
|
||||
CRM is an optional CRM vertical/plugin candidate, not boilerplate Core and not an immediate-removal target. The retained core should provide auth, tenancy, admin/app shell, rows/entities runtime, transactional email, and stable extension points; CRM should own its product opinion around companies, contacts, opportunities, submissions, marketing subscriber state, CRM-specific routes, icons, commands, translations, and any Email Marketing coupling that depends on contacts as campaign recipients.
|
||||
|
||||
This slice does not change runtime code. It records the canonical decision and execution order for a later extraction/removal slice.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope for this decision:
|
||||
|
||||
- Classify CRM as an optional plugin/vertical with action `extract-plugin`.
|
||||
- Identify current CRM surfaces and couplings that a future extraction/removal task must audit.
|
||||
- Preserve retained CRM code, data, and docs surfaces until a dedicated implementation slice moves or removes CRM. Default core route registration disables CRM route trees.
|
||||
- Define a sequential, atomic execution plan and verification plan for later work.
|
||||
|
||||
Out of scope for this decision:
|
||||
|
||||
- Deleting CRM routes, modules, entity templates, translations, or command entries in this slice.
|
||||
- Refactoring Email Marketing, Rows/Entities, user sync, or sidebar code in this slice.
|
||||
- Deciding final packaging mechanics for a future plugin runtime beyond the extraction order and required checks.
|
||||
|
||||
Human-review notes that should not block execution:
|
||||
|
||||
- Confirm whether Email Marketing remains a plugin that depends on CRM contacts, or whether it should introduce a separate audience/list abstraction before CRM extraction.
|
||||
- Confirm whether existing installations with CRM entities should receive a migration path, export instructions, or compatibility shim before CRM is disabled by default.
|
||||
- Confirm whether `submission` remains part of CRM or is split into a generic contact-form/audience plugin.
|
||||
|
||||
## Current Code Surfaces
|
||||
|
||||
The current known CRM surfaces are retained plugin-candidate code surfaces. Route paths listed here are not active default core route registrations:
|
||||
|
||||
| Surface | Current role | Future handling |
|
||||
| --- | --- | --- |
|
||||
| `apps/web/app/modules/crm` | CRM module root; currently includes `apps/web/app/modules/crm/services/CrmService.ts`. | Move to plugin package/module or delete only after dependents are removed. |
|
||||
| `apps/web/app/modules/crm/services/CrmService.ts` | Validates CRM entity presence and reads/writes company/contact/opportunity/submission rows; handles contact form, newsletter fallback, ConvertKit fallback, user sync helpers, and CRM summaries. | Plugin-owned service; extraction must replace core callers with an interface, feature flag, or no-op path. |
|
||||
| `apps/web/app/routes/admin/crm.tsx` | Admin CRM layout route; validates CRM entities and builds CRM icon navigation including Overview, entity routes, and Sync. | Plugin admin route shell. |
|
||||
| `apps/web/app/routes/admin/crm/*` | Admin CRM nested routes for overview/entity/sync flows. The current filesystem contains `apps/web/app/routes/admin/crm` as the route directory. | Move/delete atomically with admin CRM layout and route registration. |
|
||||
| `apps/web/app/routes/app.$tenant/crm.tsx` | Tenant CRM layout route; validates tenant CRM entities and builds tenant CRM icon navigation. | Plugin tenant route shell. |
|
||||
| `apps/web/app/routes/app.$tenant/crm/*` | Tenant CRM nested routes for overview/entity flows. The current filesystem contains `apps/web/app/routes/app.$tenant/crm` as the route directory. | Move/delete atomically with tenant CRM layout and route registration. |
|
||||
| `apps/web/app/modules/templates/defaultEntityTemplates.ts` | Defines `CRM_ENTITIES_TEMPLATE` for `company`, `opportunity`, `contact`, and `submission` plus relationships. | Decide whether the template is plugin-owned, installable as optional seed data, or removed from default template loading. |
|
||||
| `apps/web/app/utils/api/.server/EntitiesApi.ts` | Provides CRM-specific entity route generation for admin CRM and tenant CRM paths. | Remove CRM branches from core or make route generation plugin-aware. |
|
||||
| `apps/web/app/services/command-registry/commandRegistry.ts` | Owns command palette actions. CRM command entries are currently absent from the generic registry. | Keep CRM commands out of core or register them through plugin command contribution. |
|
||||
| `apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts` | Imports `CrmService` and `ContactDto`; campaign previews/sends require contact rows, `marketingSubscriber`, and contact variable replacement. | Must be audited before CRM extraction; Email Marketing either depends on CRM plugin or receives its own audience abstraction. |
|
||||
| `apps/web/app/utils/db/users.db.server.ts` | Imports `CrmService` and `ContactStatus`; user creation/update paths can create CRM contacts and log CRM sync. | Move CRM sync to plugin event subscriber or guard behind optional plugin availability. |
|
||||
| `apps/web/public/locales/en/translations.json` | Contains `crm.title` and `crm.settings.*` strings. | Move/remove CRM translations with plugin UI. |
|
||||
| `apps/web/public/locales/de/translations.json` | Contains `crm.title` and `crm.settings.*` strings. | Move/remove CRM translations with plugin UI. |
|
||||
| `docs/feature-manifests/plugin-candidates.json` | Lists CRM as plugin candidate with code areas `apps/web/app/modules/crm`, `apps/web/app/routes/admin/crm*`, and `apps/web/app/routes/app.$tenant/crm*`; pruning action is `extract-plugin`. | Keep consistent with this decision report and later split into a CRM-specific plugin manifest if extraction begins. |
|
||||
|
||||
Additional evidence from current inventory:
|
||||
|
||||
- `docs/feature-inventory.md` classifies CRM as Optional/Plugin because it is a strong product opinion coupled to default entities and Email Marketing.
|
||||
- The filesystem still retains `apps/web/app/routes/admin/crm`, `apps/web/app/routes/admin/crm.tsx`, `apps/web/app/routes/app.$tenant/crm`, and `apps/web/app/routes/app.$tenant/crm.tsx`, while default core route discovery excludes those plugin trees.
|
||||
- `apps/web/app/modules/crm/services/CrmService.ts` depends on Rows/Entities APIs, row relationships, row values, notifications, cache helpers, and default entity names.
|
||||
|
||||
## Couplings
|
||||
|
||||
### Rows and default entities
|
||||
|
||||
CRM is coupled to the generic rows/entities runtime through entity names and slugs rather than isolated CRM tables. `CrmService.validate()` requires `company`, `opportunity`, and `contact`, plus `submission` for admin/global CRM. The service uses rows APIs and row relationship APIs for contact, company, opportunity, and submission behavior. A future extraction must preserve rows/entities as core infrastructure while ensuring CRM entity templates are optional plugin seed data.
|
||||
|
||||
### Email Marketing
|
||||
|
||||
`apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts` directly imports `CrmService` and `ContactDto`. It uses CRM contacts as campaign recipients, blocks sends to contacts without `marketingSubscriber`, replaces email variables from contact fields, and loads contacts from contact entity views. This is the most important downstream dependency: CRM cannot be removed from core unless Email Marketing is either extracted together with CRM, made dependent on the CRM plugin, or refactored to a separate audience/contact-list abstraction.
|
||||
|
||||
### User lifecycle sync
|
||||
|
||||
`apps/web/app/utils/db/users.db.server.ts` imports `CrmService` and `ContactStatus` and can create CRM contact rows for users. The future extraction must move this behavior into plugin-owned lifecycle hooks or guard it so core user persistence no longer imports CRM.
|
||||
|
||||
### Route and entity routing helpers
|
||||
|
||||
`apps/web/app/utils/api/.server/EntitiesApi.ts` contains route-generation branches for admin CRM and tenant CRM paths. These branches make CRM route paths part of shared entity routing. A future extraction must either remove those branches or replace them with plugin-provided route builders.
|
||||
|
||||
### Navigation and commands
|
||||
|
||||
`apps/web/app/services/command-registry/commandRegistry.ts` is the current command-registration surface. CRM command entries are not registered in the generic registry; the admin/app CRM layout routes still build CRM-specific local navigation with CRM icons. A future extraction must keep command registration plugin-owned and remove route/sidebar visibility unless the CRM plugin is installed.
|
||||
|
||||
### Translations and icons
|
||||
|
||||
CRM strings live in shared locale files under `crm.*`. CRM route layouts import CRM-specific icon components from `~/components/ui/icons/crm/*`. A future extraction must move translations and icon usage with the plugin or delete them when CRM is removed.
|
||||
|
||||
### Manifest and documentation
|
||||
|
||||
`docs/feature-manifests/plugin-candidates.json` already classifies CRM as a plugin candidate. This report becomes the human-readable canonical decision; T02 should verify that report references remain resolvable and consistent with the manifest.
|
||||
|
||||
## Schema and Template Impact
|
||||
|
||||
CRM does not appear as a dedicated Prisma model surface in the reviewed task inputs. Its data model is represented through the rows/entities runtime and `CRM_ENTITIES_TEMPLATE` in `apps/web/app/modules/templates/defaultEntityTemplates.ts`:
|
||||
|
||||
- `company` with slug `companies`, type `AdminOnly`, properties such as `name`, `address`, `phone`, `source`, and `logo`.
|
||||
- `opportunity` with slug `opportunities`, type `AdminOnly`, properties such as `name`, `value`, and `expectedCloseDate`.
|
||||
- `contact` with slug `contacts`, type `AdminOnly`, properties such as `firstName`, `lastName`, `email`, `jobTitle`, `status`, and `marketingSubscriber`.
|
||||
- `submission` with slug `submissions`, type `AdminOnly`, properties such as `users` and `message`.
|
||||
- Relationships: `company` to `contact`, `company` to `opportunity`, and `contact` to `submission`.
|
||||
|
||||
Future extraction decision for templates must be explicit and atomic:
|
||||
|
||||
1. If CRM becomes an installable plugin, move `CRM_ENTITIES_TEMPLATE` into plugin seed/install code and keep core template loading independent from CRM.
|
||||
2. If CRM is removed for minimal boilerplate builds, remove default availability of company/contact/opportunity/submission templates and document migration/export for existing installations.
|
||||
3. If Email Marketing still needs contacts, do not remove `contact`/`marketingSubscriber` until Email Marketing owns or imports an audience template.
|
||||
|
||||
## Sidebar Command Docs Impact
|
||||
|
||||
Sidebar and route navigation impact:
|
||||
|
||||
- Admin CRM route code is retained at `apps/web/app/routes/admin/crm.tsx` and nested `apps/web/app/routes/admin/crm/*` routes, but is disabled in default core route registration.
|
||||
- Tenant CRM route code is retained at `apps/web/app/routes/app.$tenant/crm.tsx` and nested `apps/web/app/routes/app.$tenant/crm/*` routes, but is disabled in default core route registration.
|
||||
- CRM layout routes build their own icon subnavigation for Overview, opportunities, companies, contacts, submissions, and, in admin, Sync.
|
||||
- Any global AdminSidebar/AppSidebar references discovered in a future implementation slice must be removed or made plugin-aware. Current sidebar tests already assert some optional admin routes, including canonical admin-host CRM paths, are not present in the admin sidebar serialization.
|
||||
|
||||
Command impact:
|
||||
|
||||
- `apps/web/app/services/command-registry/commandRegistry.ts` is the command registry surface and should remain free of CRM commands unless a CRM plugin contributes them.
|
||||
- Future extraction must remove this core command or move it to plugin command registration.
|
||||
|
||||
Docs and i18n impact:
|
||||
|
||||
- `apps/web/public/locales/en/translations.json` and `apps/web/public/locales/de/translations.json` contain CRM translations and should move with plugin UI.
|
||||
- `docs/feature-manifests/plugin-candidates.json` must remain aligned with this report; if CRM receives a dedicated plugin manifest later, `plugin-candidates.json` should point to it or retain a consistent summary entry.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
The future CRM extraction/removal work must run in this sequence, with each step atomic and independently verifiable:
|
||||
|
||||
1. Decision/report: land this `extract-plugin` decision report and the T02 verifier before runtime changes.
|
||||
2. Email Marketing dependency check: inspect `apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts`, Email Marketing routes, campaign recipient storage, and tests to choose one of three paths: extract Email Marketing together with CRM, declare Email Marketing dependent on CRM plugin, or introduce a non-CRM audience abstraction.
|
||||
3. Route/module extraction or deletion: move or remove `apps/web/app/modules/crm`, `apps/web/app/routes/admin/crm.tsx`, `apps/web/app/routes/admin/crm/*`, `apps/web/app/routes/app.$tenant/crm.tsx`, and `apps/web/app/routes/app.$tenant/crm/*` in one implementation slice after dependencies are guarded.
|
||||
4. Default entity template decision: move `CRM_ENTITIES_TEMPLATE` from `apps/web/app/modules/templates/defaultEntityTemplates.ts` into plugin install/seed code, or remove it from default loading only after Email Marketing/user sync no longer requires it.
|
||||
5. Command/sidebar/i18n update: remove or plugin-register CRM command palette entries, CRM route/sidebar links, CRM route helpers in `apps/web/app/utils/api/.server/EntitiesApi.ts`, CRM locale keys in `apps/web/public/locales/en/translations.json` and `apps/web/public/locales/de/translations.json`, and CRM docs/manifest entries.
|
||||
6. Tests/static scans: add or update static scans and focused tests so core no longer imports `~/modules/crm`, CRM routes are absent when the plugin is disabled, Email Marketing behavior is explicit, and docs/manifests stay consistent.
|
||||
|
||||
## Verification Plan
|
||||
|
||||
T02 must add a local static verifier, expected at `scripts/verify-crm-decision-report.mjs`, that fails when this decision contract drifts. The verifier must check:
|
||||
|
||||
- Required report sections exist: Decision, Scope, Current Code Surfaces, Couplings, Schema and Template Impact, Sidebar Command Docs Impact, Execution Plan, Verification Plan, Risks.
|
||||
- The report contains the exact decision token `extract-plugin`.
|
||||
- No TODO, TBD, or unresolved placeholder text exists in the report.
|
||||
- Required referenced paths are present in the report and resolvable where they are concrete current paths: `apps/web/app/modules/crm`, `apps/web/app/routes/admin/crm.tsx`, `apps/web/app/routes/admin/crm/*`, `apps/web/app/routes/app.$tenant/crm.tsx`, `apps/web/app/routes/app.$tenant/crm/*`, `apps/web/app/modules/templates/defaultEntityTemplates.ts`, `apps/web/app/utils/api/.server/EntitiesApi.ts`, `apps/web/app/services/command-registry/commandRegistry.ts`, `apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts`, `apps/web/app/utils/db/users.db.server.ts`, `apps/web/public/locales/en/translations.json`, `apps/web/public/locales/de/translations.json`, `docs/feature-manifests/plugin-candidates.json`.
|
||||
- Glob-style references such as `apps/web/app/routes/admin/crm/*` and `apps/web/app/routes/app.$tenant/crm/*` resolve to existing route directories or files.
|
||||
- `docs/feature-manifests/plugin-candidates.json` still contains a CRM component and `pruning.action` remains `extract-plugin` unless a later migration intentionally updates both manifest and report.
|
||||
|
||||
Manual verification for a future runtime extraction should cover:
|
||||
|
||||
- Core app and admin shells start without CRM installed.
|
||||
- Email Marketing either works with its declared CRM dependency or fails with a clear disabled-plugin message.
|
||||
- User creation/update no longer imports or calls CRM from core when CRM is disabled.
|
||||
- Existing CRM installations have a documented export/migration path if runtime deletion is chosen instead of plugin packaging.
|
||||
|
||||
## Risks
|
||||
|
||||
- Incomplete surface map can cause later removal to miss imports; this report therefore names all known coupling files from the slice research and the task inputs.
|
||||
- Email Marketing has direct CRM service and contact DTO coupling, so removing CRM first would break campaign preview/send flows.
|
||||
- User lifecycle CRM sync creates hidden coupling outside CRM routes and must be moved or guarded before extraction.
|
||||
- CRM entities are generic rows/entities templates, so deleting only route/module code can leave orphaned templates or data.
|
||||
- Translation, command, and route-helper leftovers can keep CRM visible or navigable after the module is removed.
|
||||
- Existing tenants may rely on CRM rows and relationships; plugin extraction should include compatibility and migration notes rather than silent data loss.
|
||||
@@ -0,0 +1,186 @@
|
||||
# Email Marketing Decision Report
|
||||
|
||||
## Decision
|
||||
|
||||
Decision: `extract-plugin`.
|
||||
|
||||
Email Marketing and Newsletter are optional plugin surfaces. They are not boilerplate Core because campaign management, subscriber/audience selection, sender configuration, tracking, and marketing consent rules are product-specific. They are also not immediate-removal targets because the current implementation has real campaign, sender, outbound-email, route, schema, translation, and CRM-contact dependencies that must be unwound atomically.
|
||||
|
||||
The canonical strategy is to keep Email Marketing as a plugin candidate that currently depends on CRM contacts. A future extraction must either bundle Email Marketing with the CRM plugin, declare an explicit dependency on the CRM plugin, or introduce a plugin-owned audience/list abstraction before CRM contacts or CRM entities are removed from Core.
|
||||
|
||||
This decision is deliberately narrower than the Email Sequences and marketing-outbound foundations. Email Marketing means the CRM-contact campaign surface under `apps/web/app/modules/emailMarketing` and the `email-marketing` admin/tenant routes. Email Sequences are documented in `docs/features/email-sequences.md` as foundation/admin-preview work with production follow-ups. The generic marketing-outbound handler is documented in `docs/features/marketing-funnel.md` as an unbootstrapped general handler foundation.
|
||||
|
||||
This report is documentation-only. It records the decision and safe execution order for later extraction/removal work; it does not change runtime behavior.
|
||||
|
||||
## Scope
|
||||
|
||||
In scope for this decision:
|
||||
|
||||
- Classify Email Marketing and Newsletter as optional plugin surfaces with action `extract-plugin`.
|
||||
- Keep Email Marketing scoped to CRM-contact campaign management, not the Email Sequences foundation/admin-preview surfaces.
|
||||
- Preserve transactional mail as Core and keep marketing mail outside that Core boundary.
|
||||
- Document current Email Marketing routes, modules, database/schema surfaces, command/i18n references, and manifest/docs dependencies.
|
||||
- Record the current CRM contact/audience dependency and the required extraction choices before CRM removal.
|
||||
- Define an atomic execution plan and a static verification plan for later tasks.
|
||||
|
||||
Out of scope for this decision:
|
||||
|
||||
- Removing `apps/web/app/modules/emailMarketing`, routes, Prisma models, translations, command entries, or manifests in this slice.
|
||||
- Migrating CRM, replacing contacts with a new audience model, or changing campaign send behavior in this slice.
|
||||
- Moving provider secrets or adding runtime plugin packaging mechanics in this slice.
|
||||
- Changing transactional email behavior in `apps/web/app/modules/emails`, `apps/web/app/application/emails`, `apps/web/app/utils/services/emailService.ts`, or `packages/mail`.
|
||||
- Reclassifying Email Sequences as an Email Marketing plugin candidate or claiming production delivery before seed data, stats aggregation, E2E, Mailjet delivery evidence/reconciliation, and scheduler/deploy evidence follow-ups land.
|
||||
- Bootstrapping the generic `marketing-outbound` subscriber or treating `MARKETING_WEBHOOKS_ENABLED` as evidence that Slack, Discord, Mailchimp, HubSpot, Brevo, or custom webhook delivery is active.
|
||||
|
||||
## Current Code Surfaces
|
||||
|
||||
The current known Email Marketing surfaces are:
|
||||
|
||||
| Surface | Current role | Future handling |
|
||||
| --- | --- | --- |
|
||||
| `apps/web/app/modules/emailMarketing` | Email Marketing module root for campaign, sender, outbound-email, and send services. | Move into an Email Marketing plugin or remove after routes, schema, translations, and CRM dependency are handled. |
|
||||
| `apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts` | Main campaign service. Imports `CrmService` and `ContactDto`, loads contact views, validates marketing subscribers, performs contact variable replacement, sends tests and campaigns, and writes outbound status. | Plugin-owned service. Must not remain in Core while importing CRM unless Email Marketing and CRM are bundled or dependency-gated. |
|
||||
| `apps/web/app/modules/emailMarketing/db/emailSender.ts` | Email sender persistence surface. Stores provider, stream, API key, from/reply-to fields, and optional tenant ownership through `EmailSender`. | Plugin-owned settings/storage surface; extraction must preserve secret ownership rules and migration. |
|
||||
| `apps/web/app/modules/emailMarketing/db/campaigns.db.server.ts` | Campaign persistence and campaign-recipient composition. | Plugin-owned persistence surface tied to `Campaign` and `OutboundEmail` schema. |
|
||||
| `apps/web/app/modules/emailMarketing/db/outboundEmails.db.server.ts` | Outbound email tracking persistence for previews, sends, delivery, opens, clicks, bounces, spam complaints, and unsubscribes. | Plugin-owned persistence and analytics surface. |
|
||||
| `apps/web/app/routes/admin/email-marketing.tsx` | Admin Email Marketing layout route. Validates CRM entities through `CrmService.validate(null)` and exposes Overview, Campaigns, Activity, and Senders navigation. | Plugin admin route shell; CRM validation must be replaced by declared plugin dependency or audience validation. |
|
||||
| `apps/web/app/routes/admin/email-marketing/*` | Admin nested Email Marketing routes for overview/campaign/activity/sender workflows. | Move/delete atomically with admin layout and module services. |
|
||||
| `apps/web/app/routes/app.$tenant/email-marketing.tsx` | Tenant Email Marketing layout route. Requires `tenantEmailMarketing` feature flag, validates CRM through `CrmService.validate(tenantId)`, and exposes tenant navigation. | Plugin tenant route shell; must be disabled clearly when plugin or CRM/audience dependency is unavailable. |
|
||||
| `apps/web/app/routes/app.$tenant/email-marketing/*` | Tenant nested Email Marketing routes for tenant campaign/activity/sender workflows. | Move/delete atomically with tenant layout and feature-gate behavior. |
|
||||
| `prisma/schema/email.prisma` | Defines inbound email models plus Email Marketing models `EmailSender`, `Campaign`, `OutboundEmail`, `OutboundEmailOpen`, and `OutboundEmailClick`. `OutboundEmail.contactRowId` references CRM contact rows through generic `Row`. | Split or migrate plugin-owned schema. Preserve Core transactional mail boundary and avoid deleting inbound/system mail accidentally. |
|
||||
| `apps/web/app/services/command-registry/commandRegistry.ts` | Shared command registry named by the slice contract as a possible Email Marketing/CRM command visibility surface. | Future verifier/extraction must confirm whether Email Marketing commands exist and move/remove them through plugin command registration if present. |
|
||||
| `apps/web/public/locales/en/translations.json` | Contains Email Marketing strings such as `emailMarketing.title` and sender labels used by routes. | Move plugin strings with plugin UI or remove only when UI is removed. |
|
||||
| `apps/web/public/locales/de/translations.json` | German Email Marketing translation counterpart. | Move plugin strings with plugin UI or remove only when UI is removed. |
|
||||
| `docs/feature-manifests/plugin-candidates.json` | Lists `email-marketing` as a plugin candidate with module/routes/emails code areas and broader plugin extraction rules. | Keep consistent with this report and later split into a dedicated plugin manifest if extraction begins. |
|
||||
| `docs/feature-manifests/transactional-mail.json` | Defines transactional mail as Core and explicitly says newsletter/marketing abstractions may move to plugins. | Must remain the boundary source for Core mail. This report must not reclassify marketing mail as transactional Core. |
|
||||
| `docs/feature-decisions/crm-decision-report.md` | Canonical CRM decision. It identifies Email Marketing as the most important downstream CRM dependency. | Email Marketing extraction/removal order must follow this CRM dependency contract. |
|
||||
|
||||
Additional inventory evidence:
|
||||
|
||||
- `docs/feature-inventory.md` classifies Email Marketing/Newsletter as Optional/Plugin and Core mail as Transactional Email only.
|
||||
- `docs/feature-manifests/plugin-candidates.json` includes `apps/web/app/modules/emailMarketing`, `apps/web/app/routes/admin/email-marketing*`, and `apps/web/app/routes/app.$tenant/email-marketing*` under the `email-marketing` component.
|
||||
- `docs/feature-manifests/transactional-mail.json` keeps password reset, invitation, verification, and system email in Core while allowing Email Marketing and Newsletter abstractions to move to plugins.
|
||||
- `docs/features/email-sequences.md` keeps Email Sequences separate as foundation/admin-preview work gated by `MARKETING_SEQUENCES_ENABLED`, with production delivery blocked on explicit follow-ups.
|
||||
- `docs/features/marketing-funnel.md` keeps the general `marketing-outbound` handler separate as a shipped foundation that is not bootstrapped from `apps/worker/src/index.ts`.
|
||||
|
||||
## Couplings
|
||||
|
||||
### CRM contacts and audience selection
|
||||
|
||||
`apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts` directly imports `CrmService` and `ContactDto` from `~/modules/crm/services/CrmService`. Campaign drafts call `EntityViewsApi.get()` for entity name `contact`, load contact row IDs, call `CrmService.getContactsInRowIds()`, and reject campaigns when contacts are missing or not marked as `marketingSubscriber`. Contact previews and campaign sends call `CrmService.getContact()` and use contact fields for variable replacement.
|
||||
|
||||
This makes CRM the current audience provider for Email Marketing. CRM cannot be removed from Core first unless Email Marketing is also extracted with CRM, is disabled behind a clear CRM-plugin dependency, or receives a plugin-owned audience/list abstraction that replaces CRM contacts and the `marketingSubscriber` contact field.
|
||||
|
||||
### Rows/entities runtime
|
||||
|
||||
Campaign recipients use `OutboundEmail.contactRowId` in `prisma/schema/email.prisma`, and that field relates to generic `Row`. This ties marketing recipients to the rows/entities runtime and, in practice, to CRM contact rows. Future extraction may keep rows/entities as Core infrastructure, but Email Marketing must own or declare the contact/audience semantics that sit on top of rows.
|
||||
|
||||
### Routes and feature gating
|
||||
|
||||
`apps/web/app/routes/admin/email-marketing.tsx` and `apps/web/app/routes/app.$tenant/email-marketing.tsx` are layout routes with nested route directories. Both validate CRM before rendering. The tenant route also checks `appConfiguration.app.features.tenantEmailMarketing` and redirects when tenant Email Marketing is disabled. Future plugin packaging must preserve clear disabled-state behavior and avoid leaving routable pages that fail because CRM entities are missing.
|
||||
|
||||
### Sender, provider, and outbound services
|
||||
|
||||
Email Marketing owns sender configuration, campaign composition, outbound email records, tracking records, and broadcast send calls. This is a marketing-send surface, not a Core transactional mail surface. Future extraction must move these pieces together so Core password reset/invitation/system email remains independent from marketing campaign providers and tracking.
|
||||
|
||||
### Commands, navigation, and translations
|
||||
|
||||
Routes use `emailMarketing.title` and `emailMarketing.senders.plural` translation keys and email/crm icon navigation. `apps/web/app/services/command-registry/commandRegistry.ts` is the shared command-registration surface that must be audited for Email Marketing or CRM-dependent entries during extraction. Translation keys in `apps/web/public/locales/en/translations.json` and `apps/web/public/locales/de/translations.json` should move with plugin UI or be removed only when all plugin UI is removed.
|
||||
|
||||
### Documentation and manifests
|
||||
|
||||
`docs/feature-inventory.md`, `docs/feature-manifests/plugin-candidates.json`, `docs/feature-manifests/transactional-mail.json`, and `docs/feature-decisions/crm-decision-report.md` together define the boundary: Email Marketing is a plugin candidate, CRM is a plugin candidate, transactional mail remains Core, and CRM contacts currently provide the Email Marketing audience. Future implementation and verification must keep those sources consistent.
|
||||
|
||||
`docs/feature-manifests/plugin-candidates.json` must not use the `email-sequences` admin-preview foundation or the unbootstrapped `marketing-outbound` handler as evidence that Email Marketing has already been extracted. Email Sequences and marketing-outbound may interact with marketing queues, but they are separate status surfaces: Email Sequences are foundation/admin-preview with production followups, while marketing-outbound is a general handler foundation until a worker bootstrap wires its subscriber.
|
||||
|
||||
## Sender Secrets and Provider Boundary
|
||||
|
||||
Email Marketing sender credentials are plugin-owned marketing credentials, not Core transactional-mail credentials. `prisma/schema/email.prisma` defines `EmailSender.apiKey`, `provider`, `stream`, from-address, reply-to, tenant, campaign, and outbound-email relations. `docs/feature-manifests/plugin-candidates.json` states that plugin provider secrets must be admin/platform-owned unless explicitly tenant-owned, while `docs/feature-manifests/transactional-mail.json` states transactional mail provider API keys are platform/admin-owned and never tenant-owned.
|
||||
|
||||
Future extraction must therefore make ownership explicit:
|
||||
|
||||
1. Marketing sender API keys must move with the Email Marketing plugin and be encrypted/stored according to the platform's secret-storage standard.
|
||||
2. Tenant-scoped senders may exist because `EmailSender.tenantId` is optional, but tenant ownership does not imply arbitrary tenant access to raw provider secrets.
|
||||
3. Transactional mail settings for password reset, invitation, verification, and system email remain Core and must not be reused implicitly for bulk marketing campaigns.
|
||||
4. Marketing sends, campaign tracking, unsubscribe handling, opens, clicks, bounces, and spam complaints are plugin responsibilities.
|
||||
5. A future verifier must fail if the report or manifests blur transactional mail and marketing mail boundaries.
|
||||
|
||||
## Schema and Data Impact
|
||||
|
||||
`prisma/schema/email.prisma` contains both inbound/general email models and marketing campaign models. The marketing-owned schema impact is:
|
||||
|
||||
- `EmailSender` stores sender/provider configuration, API key, optional tenant ownership, and relations to campaigns/outbound emails.
|
||||
- `Campaign` stores campaign metadata, content, status, sender relation, optional tenant ownership, and recipient relation.
|
||||
- `OutboundEmail` stores each recipient/send record, optional campaign relation, `contactRowId`, email address, sender, preview flag, errors, sent/delivery/bounce/spam/unsubscribe timestamps, and open/click relations.
|
||||
- `OutboundEmailOpen` and `OutboundEmailClick` store tracking events.
|
||||
- `OutboundEmail.contactRowId` links to `Row`, which currently represents CRM contacts selected through contact entity views.
|
||||
|
||||
Future extraction must choose one data strategy before removing CRM contact rows:
|
||||
|
||||
1. Bundle with CRM: keep `contactRowId` and contact variables because CRM remains installed with Email Marketing.
|
||||
2. Depend on CRM plugin: declare the dependency and fail clearly when CRM is not enabled.
|
||||
3. Own audience/list data: introduce plugin-owned audience, subscriber, list, consent, and merge-field tables or row templates, then migrate campaigns/outbound records away from direct CRM contact assumptions.
|
||||
|
||||
Data migration must preserve campaign history, outbound delivery/tracking history, unsubscribe state, and sender configuration. Existing rows should not be silently deleted when a plugin is disabled.
|
||||
|
||||
## CRM Audience Decision
|
||||
|
||||
The current audience decision is explicit: Email Marketing depends on CRM contacts today. `EmailMarketingService.ts` uses CRM contacts as campaign recipients, enforces `marketingSubscriber`, replaces variables from CRM contact/company fields, and rejects non-contact campaign recipients. The CRM decision report also says CRM extraction must audit Email Marketing first.
|
||||
|
||||
Future extraction must pick exactly one path before CRM removal:
|
||||
|
||||
1. **Bundle path:** extract Email Marketing and CRM together as a vertical plugin where CRM contact rows remain the canonical audience.
|
||||
2. **Dependency path:** extract Email Marketing separately but declare CRM as a required plugin dependency; disabled or missing CRM must produce a clear unavailable-plugin status instead of route/runtime errors.
|
||||
3. **Audience abstraction path:** create an Email Marketing-owned audience/list/subscriber abstraction with consent fields, import/sync hooks, and merge-field support; migrate campaign creation and send flows away from `CrmService` and `contactRowId` before CRM is removed.
|
||||
|
||||
Until one path is implemented, Email Marketing must not be treated as standalone Core and CRM must not be deleted ahead of Email Marketing.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
Future Email Marketing extraction/removal work must run in this atomic sequence:
|
||||
|
||||
1. Decision/report: land this `extract-plugin` report and the static verifier before runtime changes.
|
||||
2. Manifest/docs alignment: update `docs/feature-inventory.md`, `docs/feature-manifests/plugin-candidates.json`, and any dedicated Email Marketing manifest so they point to this canonical report and agree with `docs/feature-manifests/transactional-mail.json`.
|
||||
3. CRM audience decision: choose bundle, dependency, or audience abstraction. Do not remove CRM contacts, `CrmService`, or CRM entity templates before this choice is implemented.
|
||||
4. Route/module packaging: move or remove `apps/web/app/modules/emailMarketing`, `apps/web/app/routes/admin/email-marketing.tsx`, `apps/web/app/routes/admin/email-marketing/*`, `apps/web/app/routes/app.$tenant/email-marketing.tsx`, and `apps/web/app/routes/app.$tenant/email-marketing/*` in one implementation slice.
|
||||
5. Schema migration: move or migrate plugin-owned `EmailSender`, `Campaign`, `OutboundEmail`, `OutboundEmailOpen`, and `OutboundEmailClick` ownership while preserving transactional/inbound mail models needed by Core.
|
||||
6. Sender secret boundary: migrate marketing sender provider secrets to plugin-owned settings and ensure Core transactional mail credentials remain separate.
|
||||
7. Command/navigation/i18n cleanup: remove or plugin-register Email Marketing command entries in `apps/web/app/services/command-registry/commandRegistry.ts`, route/sidebar visibility, icons, and translation keys in `apps/web/public/locales/en/translations.json` and `apps/web/public/locales/de/translations.json`.
|
||||
8. Tests/static scans: add focused tests and static checks so Core no longer imports Email Marketing when the plugin is disabled, transactional mail remains available, CRM dependency behavior is explicit, and docs/manifests stay consistent.
|
||||
|
||||
## Verification Plan
|
||||
|
||||
T03 must add a local static verifier, expected at `scripts/verify-email-marketing-decision-report.mjs`, and a focused test file, expected at `scripts/verify-email-marketing-decision-report.test.mjs`. The verifier must fail on the negative cases named in this task plan:
|
||||
|
||||
- Missing required heading: Decision, Scope, Current Code Surfaces, Couplings, Sender Secrets and Provider Boundary, Schema and Data Impact, CRM Audience Decision, Execution Plan, Verification Plan, Risks.
|
||||
- Missing exact decision token `Decision: `extract-plugin``.
|
||||
- Placeholder drift such as unresolved TODO/TBD placeholders outside explicit verifier-requirement wording.
|
||||
- Unresolved required path references for concrete current paths and route directories.
|
||||
- Manifest drift between this report, `docs/feature-inventory.md`, `docs/feature-manifests/plugin-candidates.json`, `docs/feature-manifests/transactional-mail.json`, and `docs/feature-decisions/crm-decision-report.md`.
|
||||
- Missing CRM coupling statements for `CrmService`, `ContactDto`, contact views, `marketingSubscriber`, and `OutboundEmail.contactRowId`.
|
||||
- Boundary drift that mixes transactional mail with marketing mail or omits sender API-key ownership.
|
||||
|
||||
The verifier should specifically check that this report references the current required surfaces: `apps/web/app/modules/emailMarketing`, `apps/web/app/routes/admin/email-marketing.tsx`, `apps/web/app/routes/admin/email-marketing/*`, `apps/web/app/routes/app.$tenant/email-marketing.tsx`, `apps/web/app/routes/app.$tenant/email-marketing/*`, `apps/web/app/modules/emailMarketing/services/EmailMarketingService.ts`, `apps/web/app/modules/emailMarketing/db/emailSender.ts`, `apps/web/app/modules/emailMarketing/db/campaigns.db.server.ts`, `apps/web/app/modules/emailMarketing/db/outboundEmails.db.server.ts`, `prisma/schema/email.prisma`, `apps/web/app/services/command-registry/commandRegistry.ts`, `apps/web/public/locales/en/translations.json`, `apps/web/public/locales/de/translations.json`, `docs/feature-manifests/plugin-candidates.json`, `docs/feature-manifests/transactional-mail.json`, and `docs/feature-decisions/crm-decision-report.md`.
|
||||
|
||||
Task-level smoke verification for this report is:
|
||||
|
||||
- `test -s docs/feature-decisions/email-marketing-decision-report.md`
|
||||
- `grep -q "Decision: \`extract-plugin\`" docs/feature-decisions/email-marketing-decision-report.md`
|
||||
|
||||
Manual verification for a future runtime extraction should cover:
|
||||
|
||||
- Password reset, invitation, verification, and system emails still work without Email Marketing installed.
|
||||
- Email Marketing routes are absent or show a clear disabled-plugin state when the plugin is disabled.
|
||||
- Campaign creation and sending either work with the declared CRM dependency or use the new audience/list abstraction.
|
||||
- Sender credentials are not exposed to tenants beyond intended administration workflows.
|
||||
- Existing campaigns, outbound history, tracking events, unsubscribes, and sender settings remain migratable.
|
||||
|
||||
## Risks
|
||||
|
||||
- Missing the CRM contact coupling would make later CRM extraction break campaign preview/send flows.
|
||||
- Mixing transactional mail with marketing mail would risk breaking password reset, invitation, verification, or system email while removing campaign features.
|
||||
- Omitting sender API-key ownership would make plugin extraction unsafe because marketing credentials may be tenant-scoped in data but still require platform-grade secret handling.
|
||||
- Removing Email Marketing routes without schema/data migration can leave campaigns, outbound records, tracking data, and sender secrets orphaned.
|
||||
- Removing CRM contacts before an audience decision can leave campaigns without recipients and variable replacement without data.
|
||||
- Translation, command, sidebar, and route-helper leftovers can keep a removed plugin visible or navigable.
|
||||
- Existing tenants may rely on campaign history, unsubscribes, and sender configuration; plugin extraction should include compatibility, export, or migration notes rather than silent data loss.
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
# Knowledge Base, Widgets, Workflow Engine, and Public Marketing Decision Report
|
||||
|
||||
## Decision
|
||||
|
||||
Decision: `extract-plugin` for Knowledge Base, Widgets, and Workflow Engine.
|
||||
|
||||
Decision: `remove-from-core` for Public Marketing remnants, including Public Marketing, Blog, Changelog, and marketing-chatbot surfaces.
|
||||
|
||||
D009 classifies Knowledge Base, Widgets, and Workflow Engine as optional plugin candidates, not Boilerplate Core and not immediate deletion targets. D009 also classifies remaining Public Marketing/Blog/Changelog/marketing-chatbot surfaces as removal candidates because marketing content and unauthenticated marketing automation conflict with the target minimal boilerplate core. This report is the canonical Feature Decision/S05 lifecycle source for R009 while supporting R001, R002, and R003 through a deterministic, sequential decision pass.
|
||||
|
||||
No runtime routes, schema, package dependencies, API handlers, sidebar registrations, workers, queues, or data records are changed in this slice. This slice records the decision contract only so later implementation slices can extract plugins or remove marketing remnants atomically after their route, schema, API, dependency, sidebar, public-data, and worker boundaries are audited.
|
||||
|
||||
## Scope
|
||||
|
||||
The scoped feature groups are Knowledge Base, Widgets, Workflow Engine, and Public Marketing remnants. The canonical action tokens are:
|
||||
|
||||
- Knowledge Base: `extract-plugin`.
|
||||
- Widgets: `extract-plugin`.
|
||||
- Workflow Engine: `extract-plugin`.
|
||||
- Public Marketing remnants: `remove-from-core`.
|
||||
|
||||
The scoped documentation and manifest sources are `docs/feature-inventory.md`, `docs/feature-manifests/plugin-candidates.json`, and `docs/feature-manifests/remove-candidates.json`. They must keep Knowledge Base, Widgets, and Workflow Engine in the plugin-candidate boundary and keep Public Marketing/Blog/Changelog/marketing-chatbot in the removal-candidate boundary. This report does not reclassify unrelated features and does not authorize runtime pruning during Feature Decision/S05.
|
||||
|
||||
## Current Code Surfaces
|
||||
|
||||
Concrete surfaces that later work must audit before changing runtime behavior:
|
||||
|
||||
- Knowledge Base module: `apps/web/app/modules/knowledgeBase`.
|
||||
- Knowledge Base admin routes: `apps/web/app/routes/admin/knowledge-base*`.
|
||||
- Knowledge Base public docs routes: `apps/web/app/routes/docs/*`.
|
||||
- Knowledge Base plugin-disabled help route tree: `apps/web/app/routes/help/*`.
|
||||
- Knowledge Base schema: `prisma/schema/knowledgeBase.prisma`.
|
||||
- Widgets module: `apps/web/app/modules/widgets`.
|
||||
- Widgets tenant routes: `apps/web/app/routes/app.$tenant/__widgets/*`.
|
||||
- Widgets API surface: `apps/api/src/app.ts`.
|
||||
- Widgets package DB helper: `packages/db/src/widgets.server.ts`.
|
||||
- Workflow schema: `prisma/schema/workflows.prisma`.
|
||||
- Workflow Engine module: `apps/web/app/modules/workflowEngine`.
|
||||
- Workflow admin routes: `apps/web/app/routes/admin/__workflows/*`.
|
||||
- Workflow tenant routes: `apps/web/app/routes/app.$tenant/__workflows/*`.
|
||||
- Workflow API routes: `apps/web/app/routes/internal/api/__workflows/*`.
|
||||
- Blog, changelog, and brand route surfaces were removed during pruning and remain historical evidence only.
|
||||
- Public entity route: `apps/web/app/routes/public/$entity.$id.tsx`.
|
||||
- Marketing chatbot route and tool surfaces were removed during pruning and remain historical evidence only.
|
||||
|
||||
## Knowledge Base Boundary
|
||||
|
||||
Knowledge Base is a support/docs product surface and is classified by D009 as `extract-plugin`. It is useful for products that need public help centers, localized articles, and support content, but it is not required for the minimal boilerplate core.
|
||||
|
||||
Later extraction must account for `apps/web/app/modules/knowledgeBase`, `apps/web/app/routes/docs/*`, `apps/web/app/routes/admin/knowledge-base*`, `apps/web/app/routes/help/*`, and `prisma/schema/knowledgeBase.prisma` together. The public docs routes create a public-data boundary: article content may be intentionally public, but draft state, tenant ownership, author metadata, internal support content, localized variants, and any user-generated or operational metadata must not become publicly exposed during extraction.
|
||||
|
||||
Knowledge Base must remain a plugin candidate, not a Public Marketing removal candidate, even if some seed or fake article content is later removed as source-product copy. Public help content and admin knowledge-base management need their own plugin-owned route, schema, sidebar, and data-retention plan before code moves or schema is pruned.
|
||||
|
||||
## Widgets Boundary
|
||||
|
||||
Widgets are classified by D009 as `extract-plugin`. Embedded widgets are product-specific because they expose tenant-configured embed behavior, public or semi-public API access, and integration expectations that vary by product.
|
||||
|
||||
Later extraction must audit `apps/web/app/modules/widgets`, `apps/web/app/routes/app.$tenant/__widgets/*`, `apps/api/src/app.ts`, and `packages/db/src/widgets.server.ts` together. The API and package DB surfaces mean Widgets are not only a UI module: API handlers, route parameters, widget identifiers, tenant ownership, and ORM-safe data access must remain aligned.
|
||||
|
||||
Widgets include a public/API boundary because embedded widget IDs and API reads may be exposed outside the authenticated tenant shell. Later work must validate and normalize widget identifiers, keep tenant ownership checks explicit, preserve parameterized/ORM-safe filters, and avoid exposing tenant-private configuration, PII, or internal widget records through public embed endpoints.
|
||||
|
||||
## Workflow Engine Boundary
|
||||
|
||||
Workflow Engine is classified by D009 as `extract-plugin`. It is a powerful automation surface, but its dependency, schema, API, and possible worker/queue behavior make it too complex and product-specific for the minimal boilerplate core.
|
||||
|
||||
Later extraction must audit `apps/web/app/modules/workflowEngine`, `apps/web/app/routes/admin/__workflows/*`, `apps/web/app/routes/app.$tenant/__workflows/*`, `apps/web/app/routes/internal/api/__workflows/*`, and `prisma/schema/workflows.prisma` together. Workflow route ownership spans admin, tenant app, and API surfaces, so partial extraction would leave broken automation entry points or orphaned schema.
|
||||
|
||||
Workflow execution now has an explicit worker/outbox boundary in the current codebase: workflow event producers publish through the outbox, the `worker-general` group can route `outbox.workflow`, and the worker-native trigger runner owns idempotent execution startup. Later extraction must preserve or replace that boundary and define whether workflow definitions, runs, triggers, logs, retries, external event subscriptions, and package dependencies move into a worker-backed plugin, remain disabled unless the plugin is installed, or are removed only after data retention and audit requirements are satisfied.
|
||||
|
||||
## Public Marketing Removal Boundary
|
||||
|
||||
Public Marketing remnants are classified by D009 as `remove-from-core`. Blog, brand, playground/debug, marketing-chatbot, and public row sharing at `apps/web/app/routes/public/$entity.$id.tsx` have been pruned and remain historical evidence only. Future scans should treat any reintroduced unauthenticated marketing/public row surface as a regression.
|
||||
|
||||
These surfaces are removal candidates, not plugin candidates, because marketing sites, blog/changelog content, public entity marketing pages, and unauthenticated marketing chatbot behavior belong outside the default boilerplate. Later pruning must preserve only neutral Core public pages that are required for auth, legal, billing, or app shell behavior.
|
||||
|
||||
Marketing chatbot removal must be handled carefully because it touches AI/chatbot routes and tools. The `remove-from-core` action applies only to the marketing chatbot API route and marketing-specific tool implementations named in this report; it does not reclassify the broader AI, chatbot, prompt builder, or MCP plugin-candidate surfaces.
|
||||
|
||||
## Schema and Data Impact
|
||||
|
||||
No schema or data changes occur in Feature Decision/S05. Later Knowledge Base extraction must account for `prisma/schema/knowledgeBase.prisma`, public help article visibility, localization, draft/published state, tenant ownership, authoring metadata, and any fake/source-product article content that should be removed separately from plugin extraction.
|
||||
|
||||
Later Widgets extraction must preserve the data contract used by `packages/db/src/widgets.server.ts`, tenant-owned widget configuration, API-visible widget identifiers, public embed access, and any PII or tenant-private fields that must not leak through API handlers. Widget data should be migrated, exported, retained, or disabled through a plugin-owned lifecycle instead of silently deleted.
|
||||
|
||||
Later Workflow Engine extraction must include a schema/data plan for `prisma/schema/workflows.prisma`, including definitions, triggers, execution state, logs, retries, external subscriptions, and tenant ownership. Later Public Marketing cleanup must keep blog/changelog content, public entity pages, marketing-chatbot prompts/tools, source-product copy, and related fake data outside Core without affecting retained Core data.
|
||||
|
||||
## Sidebar API and Package Dependency Impact
|
||||
|
||||
No sidebar entries, API handlers, or package dependencies are changed in this slice. Later Knowledge Base extraction must update admin/help navigation only through an explicit plugin enablement or routing boundary, while keeping Core navigation free of support-docs product assumptions.
|
||||
|
||||
Later Widgets extraction must handle tenant sidebar visibility, `apps/api/src/app.ts` widget endpoints, `packages/db/src/widgets.server.ts`, widget route parameters, and package ownership together. Removing or moving only the UI routes would leave API/package surfaces behind; removing only API support would break embedded widgets still reachable from tenant routes.
|
||||
|
||||
Later Workflow Engine extraction must audit package dependencies such as workflow graph/automation libraries together with `apps/web/app/modules/workflowEngine`, workflow schema, admin/tenant/API routes, and any worker/queue runtime. Later Public Marketing removal may allow marketing-only chatbot, blog, changelog, visual editor, or marketing asset dependencies to be pruned, but only after consumers are proven gone.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
1. Land this D009 report and align `docs/feature-inventory.md`, `docs/feature-manifests/plugin-candidates.json`, and `docs/feature-manifests/remove-candidates.json` so human-readable and machine-readable sources point to the same canonical decision.
|
||||
2. Add a static verifier that checks required headings, exact action tokens, D009/R001/R002/R003/R009 citations, required path references, manifest alignment, placeholder-free content, and absence of runtime-change instructions in this documentation slice.
|
||||
3. Audit Knowledge Base routes, module code, schema, admin/sidebar exposure, public help visibility, localization, source-product/fake content, and public-data boundaries before extraction.
|
||||
4. Audit Widgets tenant routes, API handlers, `packages/db/src/widgets.server.ts`, identifier validation, tenant ownership, public embed behavior, package dependencies, and sidebar visibility before extraction.
|
||||
5. Audit Workflow Engine admin/tenant/API routes, schema, dependency graph, trigger/run/log data, worker/queue expectations, retries, and API behavior before extraction.
|
||||
6. Keep `apps/web/app/routes/public/$entity.$id.tsx` and removed Blog/Brand/debug/playground/chatbot paths as historical D009 evidence rather than active code areas; any reintroduction needs an explicit plugin or product decision.
|
||||
7. Implement runtime extraction or removal only in later implementation slices after the verifier and audits prove route, schema, API, package dependency, sidebar, public-data, and worker boundaries can change atomically.
|
||||
|
||||
## Verification Plan
|
||||
|
||||
The Feature Decision/S05 verifier must fail with explicit diagnostics when required sections are missing, exact action tokens are absent, D009 or R001/R002/R003/R009 citations are missing, required paths drift, placeholders appear, manifest classification no longer matches the report, inventory references drift, Public Marketing/Blog/Changelog/marketing-chatbot are incorrectly moved to plugin candidates, or the report suggests runtime code changes in this slice.
|
||||
|
||||
Task-level verification for this report is static: the file must be non-empty, contain no unresolved placeholder markers, cite D009, and contain the exact action tokens `extract-plugin` and `remove-from-core`. Slice-level verification later runs `scripts/verify-kb-widgets-workflow-marketing-decision-report.mjs` and focused Vitest drift tests without browser, Playwright, Cypress, or other E2E execution.
|
||||
|
||||
## Risks
|
||||
|
||||
The main risk is partial implementation after this decision pass: extracting Knowledge Base without public help/data boundaries, moving Widgets UI without API and package DB ownership, changing Workflow Engine routes without schema and worker/queue planning, or removing marketing chatbot code while leaving unauthenticated public behavior reachable elsewhere.
|
||||
|
||||
A second risk is classification drift. Knowledge Base, Widgets, and Workflow Engine must remain `extract-plugin` candidates unless D009 is superseded, while Public Marketing/Blog/Changelog/marketing-chatbot must remain `remove-from-core` candidates rather than being packaged as default plugins. Downstream agents must treat this report as the decision boundary and verify documentation/manifest alignment before any runtime code changes.
|
||||
@@ -0,0 +1,79 @@
|
||||
# Portals/Sites Decision Report
|
||||
|
||||
## Decision
|
||||
|
||||
Decision: `extract-plugin`
|
||||
|
||||
Portals/Sites are classified by D007 as an optional plugin candidate, not as Boilerplate Core and not as an immediate removal target. This report is the canonical Portals/Sites lifecycle source for Feature Decision/S03 and carries forward the D006 deferral that kept Portals/Sites persistence in module-owned DB files until the Feature Decision feature decision was made. The current code surfaces are retained as plugin-candidate inventory, while default core route registration disables the portal/site route trees. Later implementation work must preserve or explicitly remove the retained schema, Stripe billing, analytics, domains, sidebar, docs, environment, and verification boundaries atomically.
|
||||
|
||||
No runtime code is changed in this slice. This slice only records the decision report and prepares the later static verifier/execution plan so downstream agents do not blindly alter retained Portals/Sites surfaces or mistake disabled plugin routes for active core routes.
|
||||
|
||||
## Scope
|
||||
|
||||
The scoped feature group covers customer portal and site-builder functionality across tenant routes, admin routes, module services, Prisma schema, billing provider behavior, analytics filters, domains configuration, sidebar command docs, environment examples, and feature inventory/manifests.
|
||||
|
||||
The canonical inventory and manifest references are `docs/feature-inventory.md` and `docs/feature-manifests/plugin-candidates.json`. They must continue to classify Portals/Sites as a plugin candidate and should reference this report once inventory alignment lands. The decision does not reclassify Portals/Sites as Core, and it does not instruct executors to remove or change runtime code during Feature Decision/S03.
|
||||
|
||||
## Current Code Surfaces
|
||||
|
||||
Concrete Portals/Sites surfaces that later work must audit before any runtime change. Route paths listed here are retained plugin-candidate code surfaces, not active default core route registrations:
|
||||
|
||||
- Modules: `apps/web/app/modules/portals` and `apps/web/app/modules/sites`.
|
||||
- Admin route: `apps/web/app/routes/admin/portals.tsx`.
|
||||
- Tenant portal routes: `apps/web/app/routes/app.$tenant/__portals/*`.
|
||||
- Tenant site routes: `apps/web/app/routes/app.$tenant/sites/*`.
|
||||
- Portal Stripe services: `apps/web/app/modules/portals/services/PortalStripe.server.ts` and `apps/web/app/modules/portals/services/StripeConnect.server.ts`.
|
||||
- Module-owned persistence: `apps/web/app/modules/portals/db/portals.db.server.ts` and `apps/web/app/modules/sites/db/sites.db.server.ts`.
|
||||
- Site configuration types: `apps/web/app/modules/sites/site-types.ts`.
|
||||
- Analytics coupling: `apps/web/app/routes/admin/analytics/page-views.tsx` and `prisma/schema/analytics.prisma`.
|
||||
- Domains coupling: `apps/web/app/modules/domains/services/Domains.server.ts`.
|
||||
- Sidebar coverage: `apps/web/app/application/sidebar/AppSidebar.test.tsx`.
|
||||
- Prisma schema surfaces: `prisma/schema/portals.prisma` and `prisma/schema/sites.prisma`.
|
||||
- Environment documentation: `.env.example`, `.env.production.example`, and `.env.fly.example`.
|
||||
- Documentation and manifest contracts: `docs/feature-inventory.md` and `docs/feature-manifests/plugin-candidates.json`.
|
||||
|
||||
## Couplings
|
||||
|
||||
Portals/Sites are coupled to tenant navigation, admin management, analytics reporting, domain resolution, Stripe billing flows, Prisma schema, and environment configuration. The retained route code spans both hidden portal paths under `apps/web/app/routes/app.$tenant/__portals/*` and visible site-builder paths under `apps/web/app/routes/app.$tenant/sites/*`, while default core route discovery excludes those plugin trees. Extraction must preserve route ownership and user-visible navigation behavior if the plugin is enabled, or remove the retained surfaces atomically.
|
||||
|
||||
Analytics portal filters and site page-view behavior must be explicitly preserved, redesigned, or removed during later implementation. Domains behavior in `apps/web/app/modules/domains/services/Domains.server.ts` must also be treated as part of the feature boundary because public site routing and domain lookup can become broken if the module is moved without corresponding domain configuration.
|
||||
|
||||
## Stripe Billing and Provider Boundary
|
||||
|
||||
Portals/Sites include Stripe-specific behavior through `apps/web/app/modules/portals/services/PortalStripe.server.ts` and `apps/web/app/modules/portals/services/StripeConnect.server.ts`. Later implementation must preserve or explicitly decide Stripe Connect behavior, customer billing portal behavior, provider initialization, webhook expectations, environment variables, and failure handling before moving or removing any portal billing route.
|
||||
|
||||
This report keeps Billing/Subscriptions Core separate from the Portals/Sites plugin candidate. Core Stripe subscription flows may remain in the boilerplate, while portal-specific Stripe Connect and customer portal behavior must be isolated behind a plugin/provider boundary if Portals/Sites are extracted.
|
||||
|
||||
## Schema and Data Impact
|
||||
|
||||
D006 deferred Portals/Sites DB ownership and accepted temporary module-owned DB files at `apps/web/app/modules/portals/db/portals.db.server.ts` and `apps/web/app/modules/sites/db/sites.db.server.ts`. D007 resolves the lifecycle classification but does not move data access in this slice. Later implementation must preserve or decide the module-owned DB boundary before changing persistence.
|
||||
|
||||
The schema surfaces `prisma/schema/portals.prisma`, `prisma/schema/sites.prisma`, and `prisma/schema/analytics.prisma` must be audited together. Extraction or removal must include an explicit schema/data migration plan for portal records, site records, analytics relations, foreign keys, seeds or fixtures, and rollback behavior.
|
||||
|
||||
## Sidebar Command Docs Impact
|
||||
|
||||
The app sidebar and its tests must stay aligned with the feature classification. `apps/web/app/application/sidebar/AppSidebar.test.tsx` should continue to prove that retained navigation does not expose removed plugin surfaces and that optional plugin entries are gated or documented consistently.
|
||||
|
||||
Docs and command/playbook surfaces must point executors to this report before runtime work. Inventory and manifest alignment must update `docs/feature-inventory.md` and `docs/feature-manifests/plugin-candidates.json` so future static checks can detect drift between human-readable documentation and machine-readable plugin classification.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
1. Land this report and a static verifier for required sections, exact decision token, D007 reference, D006 deferral, path references, manifest alignment, unresolved placeholders, and no runtime-change instructions in this slice.
|
||||
2. Audit dependencies across routes, modules, Stripe services, analytics, domains, environment examples, sidebar tests, Prisma schema, and docs/manifests.
|
||||
3. Implement route/module extraction or removal only after the dependency audit proves a safe order for `apps/web/app/modules/portals`, `apps/web/app/modules/sites`, `apps/web/app/routes/admin/portals.tsx`, `apps/web/app/routes/app.$tenant/__portals/*`, and `apps/web/app/routes/app.$tenant/sites/*`.
|
||||
4. Create the schema/data migration plan for `prisma/schema/portals.prisma`, `prisma/schema/sites.prisma`, `prisma/schema/analytics.prisma`, and the module-owned DB files before moving persistence or deleting data models.
|
||||
5. Define the Stripe/env boundary for portal-specific Stripe Connect, customer billing portal behavior, dynamic site API keys, and `.env.example`, `.env.production.example`, `.env.fly.example` updates.
|
||||
6. Align analytics, domains, sidebar, docs, inventory, and plugin manifest references so retained Core behavior remains coherent after extraction.
|
||||
7. Add or update focused tests and static scans that protect route coverage, manifest classification, schema/data expectations, provider boundaries, and placeholder-free documentation without browser or E2E execution in this documentation slice.
|
||||
|
||||
## Verification Plan
|
||||
|
||||
The Feature Decision/S03 verifier must fail with explicit diagnostics when required sections are missing, the exact decision token is absent, D007 or the D006 deferral is not cited, required path references drift, placeholders appear, manifest classification no longer matches `extract-plugin`, the inventory does not reference the canonical report, or the report suggests runtime code changes in this slice.
|
||||
|
||||
Task-level verification for this report is static: the file must be non-empty and must contain `Decision: `extract-plugin``. Slice-level verification later runs `scripts/verify-portals-sites-decision-report.mjs` without browser, Playwright, Cypress, or other E2E execution.
|
||||
|
||||
## Risks
|
||||
|
||||
Missing any route, schema, provider, analytics, domains, environment, sidebar, inventory, or manifest surface would let a later implementation handle Portals/Sites incompletely. Reclassifying Portals/Sites as Core would conflict with D007, while immediate removal would conflict with the `extract-plugin` action and the D006 ownership deferral.
|
||||
|
||||
The highest implementation risk is partial extraction: moving routes without Stripe/customer portal decisions, deleting schema without data migration, or changing analytics/domains behavior without preserving public site expectations. Downstream work must therefore treat this report as the decision boundary and use static verification before runtime code changes.
|
||||
Reference in new issue
Block a user