Major Features: - CRM Lite: Pipeline, tasks, accounts, calendar, inbox - AI Copilot: Multi-provider support, brand voice, content rules - Marketing: Campaigns, templates, channels, broadcasts - Intelligence: Buyer analytics, market intelligence dashboard - Orchestrator: Sales & marketing automation with AI - Compliance: License tracking (minimal shell) - Conversations: Buyer-seller messaging with email/SMS routing Infrastructure: - Suites & Plans system for feature gating - 60+ new migrations - Module middleware for access control - Database seeders for production sync - Enhanced product management (varieties, inventory modes) Documentation: - V1 scope, launch checklist, QA scripts - Module current state audit - Feature matrix (standard vs premium)
20 KiB
Platform Naming & Style Guide
Version: 1.0 Last Updated: 2025-11-20 Purpose: Establish consistent naming conventions, routing patterns, and architectural terminology across the entire platform.
Core Principles
- No Vendor References: Never reference external platforms or competitors in code, docs, or UI
- Domain-First Language: Use our own terminology that describes behavior, not comparisons
- Consistency: Same concepts use same terms everywhere (code, UI, docs, database)
- Clarity: Names should be self-documenting and unambiguous
A. Terminology Rules
Platform Concepts
| Concept | Correct Term | ❌ Never Use |
|---|---|---|
| Cannabis brand/manufacturer | Business (seller type) | Vendor, Supplier, External account |
| Retail dispensary | Business (buyer type) | Customer, Retailer, Shop |
| Product manufacturer identity | Brand | Brand account, Manufacturer |
| Sellable item | Product | SKU, Item, Listing |
| Product variant | Variety | Variant, Child product, Sub-product |
| Inventory unit | Batch or Lot | Package, Unit, Inventory ID |
| Marketing email/SMS | Broadcast (internal) / Campaign (UI) | Email blast, Newsletter |
| Two-way messaging | Conversation / Message | Thread, Chat, Inbox item |
| Communication method | Channel (email/SMS/push) | Provider, Service, Integration |
User & Business Types
// Business types
'buyer' // Dispensary browsing and ordering products
'seller' // Brand manufacturing and selling products
'both' // Vertically integrated (rare)
// User types (must match their business type)
User::where('user_type', 'seller')
User::where('user_type', 'buyer')
Inventory Modes
// Product inventory tracking modes
'unlimited' // No inventory tracking (always available)
'simple' // Single inventory count
'batched' // Track by batch/lot with COAs
Marketing & Messaging
// Marketing channels (outbound)
MarketingChannel::TYPE_EMAIL // 'email'
MarketingChannel::TYPE_SMS // 'sms'
// Providers (no vendor lock-in)
MarketingChannel::PROVIDER_SYSTEM_MAIL // Laravel default
MarketingChannel::PROVIDER_POSTMARK
MarketingChannel::PROVIDER_SES
MarketingChannel::PROVIDER_RESEND
MarketingChannel::PROVIDER_TWILIO
MarketingChannel::PROVIDER_CANNABRANDS // Our custom SMS
MarketingChannel::PROVIDER_NULL // Testing
// Broadcast statuses
'draft' // Being composed
'scheduled' // Queued for future send
'sending' // Currently being sent
'sent' // Completed successfully
'paused' // Temporarily stopped
'cancelled' // Stopped permanently
'failed' // Send failed
Messaging & Conversations
// Future omnichannel inbox concepts
Conversation::channel_type // 'email', 'sms', 'whatsapp', 'chat'
Message::direction // 'inbound', 'outbound'
ConversationParticipant // Links users to conversations
B. Routing Conventions
URL Structure
Read /docs/architecture/URL_STRUCTURE.md before making route changes
Seller Routes (Brand Dashboard)
Pattern: /s/{business}/{module}/{resource}/{action}
Examples:
/s/cannabrands/dashboard
/s/cannabrands/products
/s/cannabrands/products/create
/s/cannabrands/products/{product}/edit
/s/cannabrands/brands
/s/cannabrands/marketing/campaigns
/s/cannabrands/marketing/channels
/s/cannabrands/messaging
Business Parameter:
- Always use
{business}slug, not ID - Route model binding converts slug → Business instance
- Middleware verifies user has access to business
Buyer Routes (Public Marketplace)
Pattern: /brands/{brand}/{context}
Examples:
/brands/thunder-bud
/brands/thunder-bud/products/{hashid}
/brands/thunder-bud/about
Product URLs:
- ALWAYS use hashids for products/varieties
- NEVER use numeric IDs in public URLs
- Format:
/brands/{brand}/products/{productHashid} - Example:
/brands/thunder-bud/products/a7k9mP
Admin Routes
Pattern: /admin/{resource}
Examples:
/admin/businesses
/admin/users
/admin/system-settings
Tech: Filament v3 (admin panel only, not for buyer/seller UIs)
Route Naming Convention
// Pattern: {userType}.{context}.{resource}.{action}
// Seller routes
Route::name('seller.business.products.index')
Route::name('seller.business.marketing.campaigns.create')
Route::name('seller.business.messaging.index')
// Buyer routes
Route::name('brands.show')
Route::name('brands.products.show')
// Admin routes
Route::name('admin.businesses.index')
C. Model Naming & Relationships
Core Models
// Business (seller or buyer)
Business::class
->hasMany(Brand::class) // Sellers have brands
->hasMany(User::class) // Team members
->hasMany(Location::class) // Physical locations
->hasMany(Contact::class) // Business contacts
// Brand (product line identity)
Brand::class
->belongsTo(Business::class) // Owner
->hasMany(Product::class) // Products under this brand
// Product (sellable item)
Product::class
->belongsTo(Brand::class) // Brand identity
->belongsTo(Product::class, 'parent_product_id') // Variety parent
->hasMany(Product::class, 'parent_product_id') // Child varieties
->hasMany(Batch::class) // Inventory batches
// Variety (product variant)
// Uses Product model with parent_product_id set
Product::where('parent_product_id', $parentId)
Inventory Models
// Component (raw material)
Component::class
->belongsTo(Business::class)
->belongsToMany(Product::class, 'product_components') // BOM
// Batch (inventory lot)
Batch::class
->belongsTo(Product::class)
->belongsTo(Component::class)
->hasOne(BatchCoa::class) // Certificate of Analysis
Marketing Models
// Marketing channel configuration
MarketingChannel::class
->belongsTo(Business::class)
->hasMany(Broadcast::class, 'marketing_channel_id')
// Broadcast (campaign backend)
Broadcast::class
->belongsTo(Business::class)
->belongsTo(MarketingChannel::class)
->belongsTo(MarketingTemplate::class)
->hasMany(BroadcastRecipient::class)
->hasMany(BroadcastEvent::class)
// Marketing template
MarketingTemplate::class
->belongsTo(Business::class)
->hasMany(Broadcast::class)
Messaging Models (Future)
// Conversation thread
Conversation::class
->belongsTo(Business::class)
->hasMany(Message::class)
->hasMany(ConversationParticipant::class)
// Individual message
Message::class
->belongsTo(Conversation::class)
->belongsTo(User::class, 'sender_id')
// Participants in conversation
ConversationParticipant::class
->belongsTo(Conversation::class)
->belongsTo(User::class)
D. Database Conventions
Table Naming
Pattern: {resource}s (plural, snake_case)
Examples:
businesses
brands
products
product_categories
marketing_channels
marketing_broadcasts
broadcast_recipients
conversations
messages
conversation_participants
Foreign Keys
// Pattern: {model}_id
business_id
brand_id
product_id
parent_product_id // Self-referential
marketing_channel_id
conversation_id
Pivot Tables
// Pattern: {model1}_{model2} (alphabetical)
brand_user // Many-to-many: brands ↔ users
business_user // Many-to-many: businesses ↔ users
product_components // BOM: products ↔ components
Boolean Columns
// Pattern: is_{attribute} or has_{attribute}
is_active
is_default
is_approved
has_marketing
has_analytics
has_inventory
has_processing
Timestamp Columns
// Pattern: {action}_at
created_at
updated_at
deleted_at // Soft deletes
scheduled_at // Broadcast scheduled time
started_sending_at // Broadcast send start
finished_sending_at // Broadcast send complete
E. View & Blade Naming
Directory Structure
resources/views/
├── buyer/ # Buyer-facing views
│ ├── brands/
│ │ ├── show.blade.php
│ │ └── products/
│ │ └── show.blade.php
├── seller/ # Seller dashboard views
│ ├── dashboard.blade.php
│ ├── products/
│ │ ├── index.blade.php
│ │ ├── create.blade.php
│ │ └── edit.blade.php
│ ├── marketing/
│ │ ├── campaigns/ # "Campaigns" in UI
│ │ │ ├── index.blade.php
│ │ │ ├── create.blade.php
│ │ │ └── show.blade.php
│ │ ├── channels/ # Provider configs
│ │ └── templates/
│ └── messaging/ # Future inbox
│ ├── index.blade.php
│ ├── conversations.blade.php
│ └── settings.blade.php
├── components/
│ ├── seller-sidebar.blade.php
│ ├── buyer-header.blade.php
│ └── product-card.blade.php
└── layouts/
├── seller.blade.php
├── buyer.blade.php
└── admin.blade.php
View Naming
// Pattern: {action}.blade.php
index.blade.php // List view
create.blade.php // Create form
edit.blade.php // Edit form
show.blade.php // Detail view
F. UI Terminology Guidelines
Navigation Labels
✅ Correct:
- Products
- Brands
- Marketing → Campaigns / Channels / Templates
- Messaging → Inbox / Conversations / Settings
- Inventory → Items / Movements / Alerts
- Customers
- Analytics
❌ Never:
- "[Competitor]-style Catalog"
- "[Competitor] Integration"
- "[Competitor] Orders"
- "Like [competitor]"
Section Headers
✅ Correct (descriptive, generic):
- Product Details
- Pricing & Availability
- Inventory Tracking
- Variety Options
- Marketing Channels
- Campaign Settings
- Audience Selection
❌ Never (comparative):
- "[Competitor]-Compatible Fields"
- "Matches [Competitor] Format"
- "[Competitor] Integration Settings"
Button Labels
✅ Correct:
- New Campaign
- Configure Channels
- Send Test
- Schedule Campaign
- Save Draft
❌ Never:
- "Send [Competitor]-Style Broadcast"
- "[Competitor] Export"
Help Text & Descriptions
✅ Correct:
"Configure email and SMS providers to send campaigns to your buyers."
❌ Never:
"Like [Competitor], configure providers to send broadcasts."
"Similar to [Competitor]'s messaging system."
G. Code Comment Standards
File Headers
<?php
namespace App\Http\Controllers\Seller\Marketing;
use App\Models\Broadcast;
use Illuminate\Http\Request;
/**
* Campaign management controller.
*
* Handles CRUD operations for marketing campaigns (broadcasts),
* including scheduling, sending, and analytics.
*/
class BroadcastController extends Controller
{
// ...
}
❌ Never:
/**
* [Competitor]-style broadcast controller
* Matches [Competitor]'s campaign structure
*/
Inline Comments
✅ Correct:
// Calculate campaign statistics for dashboard
// Scope query to business's active channels
// Send test message to specified recipients
❌ Never:
// [Competitor]-compatible query
// [Competitor]-style filtering
// Like [Competitor], we track opens
TODOs
✅ Correct:
// TODO: Add WhatsApp channel support
// TODO: Implement conversation threading
// FIXME: Handle timezone conversion for scheduled sends
❌ Never:
// TODO: Make this work like [Competitor]
// FIXME: Match [Competitor] behavior
H. Git Commit Message Rules
Commit Message Format
type(scope): description
Examples:
feat(marketing): add campaign scheduling with timezone support
fix(inventory): correct batch quantity calculation
docs(api): document broadcast webhook events
refactor(messaging): consolidate channel provider logic
Types
feat- New featurefix- Bug fixdocs- Documentation onlyrefactor- Code refactoringtest- Adding testschore- Maintenance (deps, config)perf- Performance improvement
✅ Good Commit Messages
feat(campaigns): add SMS character count and segment estimation
fix(products): correct variety parent relationship query
docs(architecture): document marketing channel provider system
refactor(broadcasts): extract recipient preparation logic to service
❌ Bad Commit Messages
feat: add [Competitor]-style campaigns
fix: make products work like [Competitor]
refactor: use [Competitor] approach for inventory
No AI Attribution
❌ Never include:
- "🤖 Generated with Claude Code"
- "Co-Authored-By: Claude"
- Any AI tool attribution
✅ Clean, professional commits only
I. Controller Naming
Naming Pattern
// Pattern: {Resource}Controller
ProductController // CRUD for products
BrandController // CRUD for brands
BroadcastController // Campaigns (internal: broadcast)
ChannelController // Marketing channels
MessagingController // Future inbox
Namespace Structure
// Seller controllers
App\Http\Controllers\Seller\ProductController
App\Http\Controllers\Seller\BrandController
App\Http\Controllers\Seller\Marketing\BroadcastController
App\Http\Controllers\Seller\Marketing\ChannelController
App\Http\Controllers\Seller\Messaging\MessagingController
// Buyer controllers
App\Http\Controllers\Buyer\BrandController
App\Http\Controllers\Buyer\ProductController
// Shared/API controllers
App\Http\Controllers\Api\WebhookController
App\Http\Controllers\ImageController
J. Service Layer Naming
Service Classes
// Pattern: {Purpose}Service
BroadcastService // Campaign sending logic
SmsManager // SMS provider abstraction
EmailService // Email sending logic
InventoryService // Inventory calculations
AnalyticsService // Tracking & reporting
Provider Pattern
// Pattern: {Provider}Provider
App\Services\SMS\Providers\TwilioProvider
App\Services\SMS\Providers\CannabrandsProvider
App\Services\SMS\Providers\NullProvider
App\Services\Email\Providers\PostmarkProvider
App\Services\Email\Providers\ResendProvider
K. JavaScript/Alpine.js Conventions
Alpine Components
// Pattern: {purpose}Form() or {purpose}Component()
function campaignForm() {
return {
channelType: '',
sendType: 'immediate',
smsMessage: ''
};
}
function productForm() {
return {
inventoryMode: 'simple',
hasVarieties: false
};
}
Data Attributes
<!-- Use descriptive x-data names -->
<div x-data="campaignForm()">...</div>
<div x-data="productSelector()">...</div>
<!-- Not vendor-specific -->
❌ <div x-data="competitorStyleForm()">...</div>
L. CSS/Styling Conventions
DaisyUI Components
Use DaisyUI/Tailwind only - NO inline styles
✅ Correct:
<div class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title">Campaign Settings</h2>
</div>
</div>
❌ Never:
<div style="background: #fff; padding: 20px;">
<h2 style="font-size: 18px;">Campaign Settings</h2>
</div>
Color Classes
<!-- Use semantic color names -->
bg-primary
text-success
badge-warning
btn-error
<!-- Defined in resources/css/app.css -->
Icon Classes (Lucide)
<!-- Pattern: icon-[lucide--{name}] -->
<span class="icon-[lucide--mail] size-5"></span>
<span class="icon-[lucide--message-square] size-4"></span>
<span class="icon-[lucide--send] size-6"></span>
M. Testing Conventions
Test File Naming
// Pattern: {Model}Test.php or {Feature}Test.php
tests/Unit/Models/BroadcastTest.php
tests/Feature/Marketing/CampaignCreationTest.php
tests/Feature/Messaging/ConversationTest.php
Test Method Naming
// Pattern: test_{behavior}_when_{condition}
public function test_campaign_sends_when_scheduled_time_reached()
public function test_channel_validates_required_config_fields()
public function test_user_cannot_access_other_business_campaigns()
N. Documentation Standards
README Files
Structure:
- What it does (no vendor comparisons)
- How to use it
- Architecture/patterns
- Examples
- Testing
❌ Never start with: "Like [Competitor], this module provides..." "Similar to [Competitor]'s approach..."
✅ Instead: "This module provides campaign management with email/SMS channels..."
Architecture Docs
docs/
├── platform_naming_and_style_guide.md (this file)
├── architecture/
│ ├── URL_STRUCTURE.md
│ ├── DATABASE.md
│ └── API.md
├── features/
│ ├── phase-7-marketing-foundation.md
│ └── phase-8-navigation-complete.md
└── supplements/
├── analytics.md
├── departments.md
└── permissions.md
O. API Conventions
Endpoint Naming
Pattern: /api/{version}/{resource}
Examples:
/api/v1/products
/api/v1/brands/{brand}/products
/api/v1/broadcasts/{broadcast}/stats
Response Format
{
"data": {
"id": "a7k9mP",
"type": "product",
"attributes": {
"name": "Black Maple",
"brand": "Thunder Bud"
}
},
"meta": {
"timestamp": "2025-11-20T12:00:00Z"
}
}
No vendor-specific response structures
P. Error Messages & Validation
Error Messages
✅ Correct:
'The selected channel must be active.'
'Campaign name is required.'
'Invalid provider configuration.'
❌ Never:
'Channel must be configured like [Competitor].'
'Use [Competitor]-compatible format.'
Validation Rules
✅ Correct:
'marketing_channel_id' => 'required|exists:marketing_channels,id'
'type' => 'required|in:email,sms'
'scheduled_at' => 'required_if:type,scheduled|date|after:now'
❌ Never reference vendor formats in validation
Q. Module Naming
Premium Modules
// Business flags for module access
has_marketing // Campaigns, channels, templates
has_analytics // Business intelligence, reports
has_inventory // Inventory tracking, alerts
has_processing // Manufacturing, conversions
has_compliance // Regulatory, METRC
Module Route Prefixes
/s/{business}/marketing/*
/s/{business}/messaging/*
/s/{business}/inventory/*
/s/{business}/processing/*
/s/{business}/analytics/*
R. Environment & Config
Config Keys
// Pattern: {module}_{setting}
config('marketing.default_from_name')
config('sms.cannabrands_api_url')
config('inventory.low_stock_threshold')
// Not vendor-specific
❌ config('competitor.api_key')
.env Variables
# Correct
MARKETING_FROM_EMAIL=noreply@cannabrands.com
SMS_CANNABRANDS_API_URL=https://sms.cannabrands.com
SMS_CANNABRANDS_API_KEY=
# Never
COMPETITOR_API_KEY=
COMPETITOR_WEBHOOK_URL=
S. Exception Messages
Exception Text
✅ Correct:
throw new InvalidArgumentException('Marketing channel must be SMS type');
throw new BusinessLogicException('Cannot send campaign without active channel');
❌ Never:
throw new Exception('Channel not [Competitor]-compatible');
T. Seeder & Factory Naming
Factory Naming
// Pattern: {Model}Factory
ProductFactory::class
BroadcastFactory::class
MarketingChannelFactory::class
Seeder Naming
// Pattern: {Purpose}Seeder
DatabaseSeeder::class
DemoBusinessSeeder::class
MarketingChannelsSeeder::class
Summary Checklist
Before committing code, verify:
- No vendor names in code/comments/docs
- Routes follow
/s/{business}/pattern for sellers - Public URLs use hashids, not numeric IDs
- Models follow naming conventions
- UI text is descriptive, not comparative
- DaisyUI/Tailwind only (no inline styles)
- Commit messages describe behavior, not comparisons
- No AI attribution in commits
- Documentation uses domain terminology
- Variable names are self-documenting
This guide is the source of truth for all platform naming and conventions.
Any deviation requires explicit justification and should be documented as an exception.