Files
hub/docs/platform_naming_and_style_guide.md
kelly 3905f86d6a feat: V1 Release - Complete platform with all premium modules
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)
2025-12-01 09:48:40 -07:00

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

  1. No Vendor References: Never reference external platforms or competitors in code, docs, or UI
  2. Domain-First Language: Use our own terminology that describes behavior, not comparisons
  3. Consistency: Same concepts use same terms everywhere (code, UI, docs, database)
  4. 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 feature
  • fix - Bug fix
  • docs - Documentation only
  • refactor - Code refactoring
  • test - Adding tests
  • chore - 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:

  1. What it does (no vendor comparisons)
  2. How to use it
  3. Architecture/patterns
  4. Examples
  5. 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.