Back to .md Directory

SolidInvoice - AI Assistant Guide

This document provides comprehensive guidance for AI assistants working with the SolidInvoice codebase. It covers the architecture, conventions, workflows, and best practices to help you understand and effectively contribute to this project.

May 2, 2026
0 downloads
3 views
ai workflow
View source

SolidInvoice - AI Assistant Guide

This document provides comprehensive guidance for AI assistants working with the SolidInvoice codebase. It covers the architecture, conventions, workflows, and best practices to help you understand and effectively contribute to this project.

Table of Contents


Project Overview

SolidInvoice is a sophisticated open-source invoicing application designed for small businesses and freelancers. It provides comprehensive billing operations including:

  • Client and contact management
  • Quote creation and management
  • Invoice generation and tracking (including recurring invoices)
  • Online payment processing (multiple gateways via Payum)
  • Tax and discount handling
  • RESTful API for integrations
  • Multi-channel notifications (email, SMS, webhooks)

Current Version: 2.3.11 License: MIT Primary Author: Pierre du Plessis

Current Status

The current branch is in the process of a UI rewrite from AdminLTE (Bootstrap 4) to Tabler (Bootstrap 5.3). Any changes to templates or frontend code should be made in accordance with the new Tabler design system.


Technology Stack

Backend

ComponentTechnologyVersion
FrameworkSymfony7.1+
PlatformSolidWorx/Platformdev-main
PHPPHP8.4+
ORMDoctrine ORM2.15+
MigrationsDoctrine Migrations3.5+
DatabaseMySQL/PostgreSQL/SQLiteMySQL 8.0+ recommended
API FrameworkAPI Platform4.0+
PDF GenerationmPDF8.0+
Money Handlingmoneyphp/money4.5+
Task SchedulingSymfony Schedule7.1+
Payment ProcessingPayum1.7+
WorkflowSymfony Workflow7.1+

Frontend

ComponentTechnologyVersion
Build ToolWebpack (Symfony Encore)5.3+
CSS PreprocessorSass1.63+
JavaScript FrameworkStimulus3.2+
UI FrameworkTabler (Bootstrap)1.4+
IconsFontAwesome6.4+
Package ManagerBun1.3+

Development Tools

  • Static Analysis: PHPStan (Level 6)
  • Coding Standards: EasyCodingStandard (Symplify)
  • Code Refactoring: Rector (PHP 8.4+)
  • Testing: PHPUnit 10.4+, Mockery, Symfony Panther
  • Fixtures: Foundry
  • CI/CD: GitHub Actions

Distribution

  • Docker: Official SolidInvoice Docker images available on Docker Hub (solidinvoice/solidinvoice). Runs the FrankenPHP binary inside the container.
  • FrankenPHP: Official recommended installation method. Application is built into a single binary, which contains PHP, all required extensions, the web server, and the application code.
  • Archive: ZIP/TAR archives for manual installation (all assets pre-compiled)

Codebase Structure

Root Directory Layout

/home/user/SolidInvoice/
├── assets/                 # Frontend JavaScript and SCSS
│   ├── controllers/        # Stimulus controllers
│   ├── img/                # Images and icons
│   ├── scss/               # Sass stylesheets
│   ├── controllers.json    # Stimulus config
│   └── core.ts             # Main application entry
├── bin/                    # Executables (console, phpunit, etc.)
├── config/                 # Symfony configuration
│   ├── packages/           # Package configurations
│   ├── routes/             # Route definitions
│   └── services.php        # Service container config
├── docker/                 # Docker configuration
├── frankenphp/             # FrankenPHP code and config (Go files and shell scripts to build and compile the binary)
├── migrations/             # Database migrations
├── public/                 # Web-accessible files
├── scripts/                # Shell scripts for various tasks
├── src/                    # Application source code (18 bundles)
├── templates/              # Twig templates which overrides external bundle templates
├── tests/                  # Test bootstrap and utilities
├── var/                    # Runtime data (cache, logs)
├── vendor/                 # Composer dependencies

Bundle Organization (src/)

SolidInvoice uses a modular bundle architecture with 18 bundles:

Business Logic Bundles

  • InvoiceBundle - Invoice management, recurring invoices, cloning, state transitions
  • QuoteBundle - Quote creation, management, conversion to invoices
  • ClientBundle - Client and contact management with address tracking
  • PaymentBundle - Payment processing with Payum integration
  • TaxBundle - Tax rate management and calculations

User & Security

  • UserBundle - User management, API tokens, authentication

System & Infrastructure

  • CoreBundle - Core utilities, entities, billing logic, PDF generation, Twig extensions
  • ApiBundle - REST API implementation with API Platform
  • MailerBundle - Email configuration and delivery
  • SettingsBundle - Application configuration management
  • InstallBundle - Installation wizard and setup
  • CronBundle - Scheduled task management

UI & Presentation

  • DashboardBundle - Dashboard widgets and analytics
  • DataGridBundle - Data grid/table rendering
  • FormBundle - Form customizations

Specialized Services

  • NotificationBundle - Multi-channel notifications (email, SMS, webhooks)
  • MoneyBundle - Money type handling with moneyphp/money library
  • SaasBundle - SaaS features like subscriptions and usage tracking

Bundle Internal Structure

Each bundle follows this pattern:

BundleNameBundle/
├── Action/                      # Action classes (entry points for HTTP requests)
├── Entity/                      # Doctrine ORM entities
├── Form/                        # Symfony Form types and handlers
├── (Listener|EventSubscriber)/  # Event listeners and subscribers
├── Manager/                     # Complex business logic managers
├── Repository/                  # Database access objects
├── Resources/                   # Configuration, templates, assets
│   ├── config/                  # Bundle-specific configs
│   │   ├── routing/             # Route configuration      
│   │   └── services/            # DI container services
│   ├── translations/            # Translation files
│   └── views/                   # Twig templates
├── Tests/                       # Unit and functional tests
│   ├── Functional/              # Functional tests
│   └── Unit/                    # Unit tests (not in a `Unit` directory, but just in the top-level Tests directory)
└── BundleNameBundle.php

Architecture & Design Patterns

Core Patterns

1. Modular Bundle Architecture

  • Each feature has its own bundle with clear boundaries
  • Bundles communicate via events, not direct coupling
  • Dependency injection for all services

2. Action Pattern (Not Controllers)

  • Entry points for HTTP requests are Action classes
  • Single responsibility per action
  • Located in Action/ directory of each bundle

Example:

// src/InvoiceBundle/Action/CreateAction.php
class CreateAction
{
    public function __invoke(Request $request): Response
    {
        // Handle invoice creation
    }
}

3. Repository Pattern

  • Custom repositories extending Doctrine EntityRepository
  • Query optimization and complex business logic isolation
  • Located in Repository/ directory

4. Event-Driven Architecture

  • Extensive use of Symfony EventDispatcher
  • Events for state transitions, entity lifecycle
  • Listeners in Listener/ or EventSubscriber/ directory

Key events:

  • InvoiceEvent - Invoice lifecycle events
  • PaymentCompleteListener - Payment completion handling
  • Doctrine lifecycle callbacks

5. Manager Pattern

  • Complex domain operations encapsulated in Manager classes
  • Examples: InvoiceManager, PaymentSettingsManager
  • Located in Manager/ directory

6. State Machine (Workflow)

  • Symfony Workflow for invoice state management
  • States: draft, pending, recurring, paid, sent, viewed, cancelled
  • Graph-based workflow configuration

7. Platform

This application is built using SolidWorx/Platform, which provides foundational services, base entities, and utilities to streamline development. SolidWorx/Platform is built in conjunction with SolidInvoice and is designed to be reusable across multiple projects. For any UI functionality, use the SolidWorx/PlatformUI package which provides Twig templates and components for common styles. In cases where a specific UI component is not available in PlatformUI, mention what is needed so that it can be added to the PlatformUI package for future use.

  • All repositories should extend from SolidWorx\Platform\PlatformBundle\Repository\EntityRepository.
  • All commands should extend from SolidWorx\Platform\PlatformBundle\Console\Command.

Entity Traits (Cross-Cutting Concerns)

Common traits used across entities:

// Can archive entities
trait Archivable {
    private bool $archived = false;
}

// Automatic timestamps
trait TimeStampable {
    private DateTimeInterface $created;
    private DateTimeInterface $updated;
}

// Multi-tenancy
trait CompanyAware {
    private Company $company;
}

// Money value objects
trait Money {
    // Money object fields
}

Dependency Injection

  • All services use constructor injection
  • Configuration in config/services.php and bundle-specific configs
  • Auto-wiring enabled for most services

Development Workflow

Initial Setup

  1. Clone the repository:

    git clone https://github.com/SolidInvoice/SolidInvoice.git
    cd SolidInvoice
    
  2. Install PHP dependencies:

    composer install
    
  3. Install frontend dependencies:

    bun install
    
  4. Build frontend assets:

    bun run dev  # Development mode
    # or
    bun run build  # Production build
    
  5. Setup database:

    bin/console doctrine:database:create
    bin/console doctrine:migrations:migrate
    

Development Commands

# Frontend development
bun run dev      # Build assets in dev mode with watch
bun run build    # Production optimized build
bun run lint:js  # ESLint JavaScript validation
bun run lint:css # StyleLint CSS validation

# Backend development
bin/console cache:clear                 # Clear cache
bin/console doctrine:migrations:migrate # Run migrations
bin/console doctrine:schema:validate    # Validate database schema

# Code quality
bin/ecs check                # Check coding standards
bin/ecs check --fix          # Fix coding standards
bin/phpstan analyse          # Run static analysis
bin/rector process --dry-run # Preview refactoring changes
bin/rector process           # Apply refactoring changes

# Testing
bin/phpunit                          # Run all tests
bin/phpunit --coverage-html coverage # Generate coverage report

Git Workflow

  1. Branch naming: Feature branches should be descriptive
  2. Commit messages: Follow conventional commits style
  3. Pull requests: Must pass all CI checks (tests, CS, static analysis)

CI/CD Pipeline (GitHub Actions)

Every pull request triggers:

  1. Unit Tests (unit-tests.yml)

    • PHP 8.4 and 8.5
    • MySQL 8.0
    • Code coverage reporting (Codecov)
    • E2E tests with Symfony Panther
  2. Coding Standards (cs.yml)

    • EasyCodingStandard (PHP)
    • Composer normalize
    • Super-Linter (YAML, JSON, XML, Markdown, CSS, JavaScript)
  3. Static Analysis (static-analysis.yml)

    • PHPStan (Level 6)
    • Qodana code quality
  4. Security Checks (security-checker.yml)

    • Composer security vulnerabilities
    • CodeQL analysis

Testing Strategy

Test Structure

src/BundleNameBundle/Tests/
├── Functional/
│   ├── Api/           # API endpoint tests
│   └── ...
├── Form/          # Form type tests
├── Repository/    # Repository tests
└── ...

PHPUnit Configuration

Key settings from phpunit.xml.dist:

  • Execution: Random order (executionOrder="random")
  • Strict mode: All warnings treated as failures
  • Database isolation: DAMA/DoctrineTestBundle for transaction wrapping
  • E2E testing: Symfony Panther for browser testing
  • Environment: Test environment with .env.test

Test Types

1. Unit Tests

  • Test individual classes in isolation
  • Use Mockery for mocking dependencies
  • Fast execution, no database required
use Mockery as m;

class InvoiceManagerTest extends TestCase
{
    public function testCreateInvoice(): void
    {
        $repository = m::mock(InvoiceRepository::class);
        // Test logic
    }
}

2. Functional Tests

  • Test full request/response cycle
  • Use database for realistic scenarios
  • Located in Tests/Functional/

3. API Tests

  • Test REST API endpoints
  • Extend ApiTestCase base class
  • JSON-LD/HAL response validation

Fixtures

Foundry - Factory-based fixtures

Running Tests

# All tests
bin/phpunit

# Specific bundle
bin/phpunit src/InvoiceBundle/Tests

# Specific test file
bin/phpunit src/InvoiceBundle/Tests/Unit/Manager/InvoiceManagerTest.php

# With coverage
bin/phpunit --coverage-html coverage

# Filter by test name
bin/phpunit --filter testCreateInvoice

Code Quality & Standards

Coding Standards (ECS)

Configuration: ecs.php

Standards Applied:

  • PSR-12
  • Symfony coding standards
  • PHPUnit standards
  • Clean code principles

File Header Required:

<?php

declare(strict_types=1);

/*
 * This file is part of SolidInvoice project.
 *
 * (c) Pierre du Plessis <open-source@solidworx.co>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

Commands:

bin/ecs check           # Check standards
bin/ecs check --fix     # Auto-fix issues

Static Analysis (PHPStan)

Configuration: phpstan.neon

Settings:

  • Level: 6
  • Symfony container XML analysis
  • Doctrine plugin enabled
  • Baseline: phpstan-baseline.neon

Commands:

bin/phpstan analyse                    # Analyze with baseline
bin/phpstan analyse --no-baseline     # Analyze without baseline

Code Refactoring (Rector)

Configuration: rector.php

Rules Applied:

  • PHP upgrades
  • Symfony best practices
  • Doctrine improvements
  • PHPUnit modernization
  • Code quality improvements

Commands:

bin/rector process --dry-run    # Preview changes
bin/rector process              # Apply changes

Pre-commit Checklist

Before committing code:

  1. ✅ Run coding standards: bin/ecs check --fix
  2. ✅ Run static analysis: bin/phpstan analyse
  3. ✅ Run tests: bin/phpunit
  4. ✅ Ensure proper file headers
  5. ✅ Ensure strict types declaration: declare(strict_types=1);

Common Tasks & Commands

Creating a New Entity

  1. Use Symfony Maker (if available):

    bin/console make:entity
    
  2. Or manually create in appropriate bundle's Entity/ directory

  3. Add common traits as needed:

    use SolidInvoice\CoreBundle\Traits\Entity\Archivable;
    use SolidInvoice\CoreBundle\Traits\Entity\TimeStampable;
    use SolidInvoice\CoreBundle\Traits\Entity\CompanyAware;
    
  4. Generate migration:

    bin/console doctrine:migrations:diff
    
  5. Run migration:

    bin/console doctrine:migrations:migrate
    

Creating a New API Endpoint

  1. Add #[ApiResource] attribute to entity:

    use ApiPlatform\Core\Annotation\ApiResource;
    
    #[ApiResource]
    class MyEntity
    {
        // ...
    }
    
  2. Configure serialization groups if needed

  3. Test the endpoint at /api/my-entities

Adding a New Event Listener

  1. Create listener class in Listener/ directory
  2. Implement the listener logic
  3. Use auto-configuration to register the entity
  4. Tag with appropriate event name if not using auto-configuration

Working with Money Values

Always use Money objects, never raw floats:

use Money\Money;
use Money\Currency;

$amount = Money::USD(1000); // $10.00 (amounts in cents)
$currency = new Currency('USD');
$price = new Money(5000, $currency); // $50.00

Currencies are configured on the Client entity, and should always be used for formatting. Never use a default currency.

PDF Generation

Use the PDF manager service:

use SolidInvoice\CoreBundle\Pdf\Generator;

$generator->generate($html, $filename);

Sending Emails

Use Symfony Mailer:

use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

$email = (new Email())
    ->to($recipient)
    ->subject('Invoice #1234')
    ->html($content);

$mailer->send($email);

Key Conventions

Naming Conventions

TypeConventionExample
EntitiesSingular nounInvoice, Quote, Client
RepositoriesEntity name + RepositoryInvoiceRepository
Form TypesName + TypeInvoiceType, ClientType
ListenersPurpose + ListenerInvoicePaidListener
ActionsVerb or purposeCreateAction, EditAction, IndexAction
ManagersEntity + ManagerInvoiceManager, PaymentManager
EventsEntity + EventInvoiceEvent, PaymentEvent

File Organization

  • One class per file
  • Filename matches class name
  • Namespace reflects directory structure
  • Use statement ordering: const, class, function

PHP Standards

  • Always use strict types: declare(strict_types=1);
  • Type hints: Always specify parameter and return types
  • Final classes: Prefer final classes unless inheritance needed
  • Visibility: Always specify (public, private, protected)
  • Enums: Use PHP 8.1+ backed enums for fixed sets of values (status, type, etc.), NEVER class constants
  • Constants: Use class constants for configuration values, not for enum-like values

Doctrine Best Practices

  • Use attributes for mapping (PHP 8+)
  • Repository methods should return typed arrays or collections
  • Query optimization: Use joins to avoid N+1 queries
  • Use filters for soft deletes (ArchivableFilter) and multi-tenancy (CompanyFilter)

Enum Usage (PHP 8.1+)

ALWAYS use backed enums for fixed sets of values:

// GOOD - Use Enum
enum InvoiceStatus: string
{
    case Draft = 'draft';
    case Pending = 'pending';
    case Paid = 'paid';
    case Cancelled = 'cancelled';
}

// In entity
#[ORM\Column(enumType: InvoiceStatus::class)]
private InvoiceStatus $status;

// Usage
$invoice->setStatus(InvoiceStatus::Paid);
if ($invoice->getStatus() === InvoiceStatus::Paid) {
    // ...
}
// BAD - Don't use class constants for enum-like values
class Invoice
{
    public const STATUS_DRAFT = 'draft';      // ❌ Wrong
    public const STATUS_PENDING = 'pending';  // ❌ Wrong
    public const STATUS_PAID = 'paid';        // ❌ Wrong
}

When to use enums:

  • Entity status fields (draft, published, archived)
  • Entity type fields (pre_due, overdue_1, overdue_7)
  • Fixed categories or classifications
  • Any field with a predetermined set of values

When to use constants:

  • Configuration values (API_VERSION, MAX_UPLOAD_SIZE)
  • Table names (TABLE_NAME)
  • Fixed string values that aren't enum-like

Symfony Best Practices

  • Constructor injection for dependencies
  • Environment variables for configuration
  • Twig for all templates
  • Form types for all forms
  • Validation via annotations/attributes

Database & ORM

Configuration

ORM: Doctrine ORM 2.15+ DBAL: Doctrine DBAL 3.4+ Mapping: PHP 8 Attributes

Database Support

  • MySQL 5.7+, 8.0+ (recommended)
  • PostgreSQL 10+
  • SQLite (Recommended)

UUID Primary Keys

All entities use UUID binary ordered time for primary keys:

use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid;

#[ORM\Column(name: 'id', type: UlidType::NAME)]
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid $id;

Global Doctrine Filters

Two filters are always enabled:

  1. CompanyFilter - Multi-tenancy: Automatically filters queries by company_id
  2. ArchivableFilter - Soft deletes: Excludes archived records

Migrations

Location: /migrations/

  1. Migrations don't use raw SQL queries but instead use the Doctrine Schema tool. All newly created migrations should use the Schema tool to update the database schema.

  2. Migrations don't use the standard naming convention from Doctrine. Instead, use a migration file per version. For example, for version 2.3.11, the migration file should be named Version203011.php.

  3. From version 2.4 onwards, migrations are split into separate files using the following naming convention: Version{major}{minor}{patch}_{partNumber}.php. For example, for version 2.4.0, the migration files would be named Version20400_1.php, Version20400_2.php, etc.

Commands:

# Generate migration
bin/console doctrine:migrations:diff

# Run migrations
bin/console doctrine:migrations:migrate

# Check status
bin/console doctrine:migrations:status

# Rollback
bin/console doctrine:migrations:migrate prev

Custom Doctrine Types

  • json_array - Legacy JSON storage
  • uuid - Standard UUID type
  • Custom hydrators for Money objects

API Development

Framework

API Platform 4.0+ with full REST API support

Authentication

Token-based authentication via X-API-TOKEN header:

curl -H "X-API-TOKEN: your-token-here" https://example.com/api/invoices

API Endpoints

Main resources:

  • /api/invoices - Invoice CRUD
  • /api/quotes - Quote CRUD
  • /api/clients - Client CRUD with sub-resources
  • /api/contacts - Contact management
  • /api/payments - Payment records
  • /api/tax - Tax rates

Supported Formats

  • JSON-LD (default)
  • JSON-HAL
  • JSON
  • XML
  • HTML (API documentation)

Pagination

Default: 30 items per page

Query parameters:

  • page - Page number
  • itemsPerPage - Items per page (max 100)

Example:

GET /api/invoices?page=2&itemsPerPage=50

Serialization

Custom normalizers for complex types:

  • MoneyNormalizer - Money objects
  • CreditNormalizer - Client credits
  • DiscountNormalizer - Discount values
  • AdditionalContactDetailsNormalizer - Contact details

Serialization groups:

  • invoice_api - Invoice API representation
  • client_api - Client API representation
  • Default - Default serialization

Creating API Resources

use ApiPlatform\Core\Annotation\ApiResource;

#[ApiResource(
    collectionOperations: ['get', 'post'],
    itemOperations: ['get', 'put', 'delete'],
    normalizationContext: ['groups' => ['invoice_api']],
    denormalizationContext: ['groups' => ['invoice_write']]
)]
class Invoice
{
    // ...
}

Frontend Development

Build System

Webpack 5 via Symfony Encore

Configuration: webpack.config.js

JavaScript Architecture

Framework: Stimulus Pattern: Stimulus controllers for modular JS

Entry Points:

  • assets/core.ts - Global core functionality

Asset Organization

assets/
├── core.ts                 # Main entry point
└── scss/                   # Stylesheets
    ├── app.scss            # Main styles
    ├── email.scss          # Email templates
    └── pdf.scss            # PDF styles

Styling

Framework: Tabler 1.4+ (Bootstrap 5.3) Preprocessor: Sass Admin Template: Tabler

Build Commands

# Development
bun run dev        # Single build
bun run watch      # Watch mode
bun run dev-server # Dev server with HMR

# Production
bun run build # Optimized build

# Linting
bun run lint:js  # ESLint
bun run lint:css # StyleLint

Adding New JavaScript

  1. Create file in appropriate location:

    • Global code: assets/core.ts
  2. Rebuild assets:

    bun run dev
    

Security Considerations

Authentication

  • Username/password authentication
  • API token authentication
  • User invitation system

Authorization

  • Role-based access control (RBAC)
  • Symfony Security component
  • Voter pattern for complex permissions

Multi-Tenancy

  • Company-based data isolation
  • Automatic filtering via CompanyFilter
  • All company-aware entities use CompanyAware trait

Payment Security

  • PCI-compliant payment processing via Payum
  • Secure token storage
  • No raw card data storage

API Security

  • Token-based authentication required
  • Token history tracking for audit
  • Rate limiting (configure as needed)

Sensitive Data

  • Never commit credentials
  • Use environment variables for secrets
  • Encrypted values use defuse/php-encryption

Best Practices

  1. Input Validation: Always validate user input
  2. Output Escaping: Twig auto-escapes, but be careful with raw filter
  3. SQL Injection: Use Doctrine ORM/DBAL, never raw queries
  4. CSRF Protection: Enabled by default on forms
  5. XSS Protection: Validate and sanitize all user input

Additional Resources

Documentation

Important Files

  • CONTRIBUTING.md - Contribution guidelines
  • README.md - Project overview and installation
  • CHANGELOG.md - Version history
  • SECURITY.md - Security policy
  • UPGRADE.md - Upgrade instructions

Project Links


Quick Reference for AI Assistants

When Adding Features

  1. ✅ Identify the appropriate bundle (or create new if necessary)
  2. ✅ Follow the bundle internal structure (Action, Entity, Repository, etc.)
  3. ✅ Add proper type hints and strict types
  4. ✅ Include file header comment
  5. ✅ Write unit tests
  6. ✅ Update API resources if needed
  7. ✅ Run code quality tools (ECS, PHPStan)
  8. ✅ Generate and run migrations if database changes

When Fixing Bugs

  1. ✅ Add a failing test that reproduces the bug
  2. ✅ Fix the bug
  3. ✅ Ensure the test passes
  4. ✅ Run full test suite
  5. ✅ Run static analysis

When Refactoring

  1. ✅ Ensure tests exist and pass
  2. ✅ Consider using Rector for automated refactoring
  3. ✅ Maintain backward compatibility unless major version
  4. ✅ Update documentation if API changes
  5. ✅ Verify all tests still pass

Code Review Checklist

  • Strict types declared
  • Proper type hints (parameters and return types)
  • File header present
  • Follows PSR-12 and project standards
  • Tests included
  • No hardcoded values (use configuration)
  • Proper error handling
  • No security vulnerabilities
  • Documentation updated if needed
  • Passes all CI checks

Design System Guide

This section provides comprehensive guidelines for implementing consistent, modern UI across all SolidInvoice pages. All UI changes MUST follow these guidelines.

Design Philosophy

SolidInvoice follows a Clean & Minimal design aesthetic:

  • Content-focused: Reduce visual noise, let data speak
  • Consistent spacing: Use the 8px-based spacing scale
  • Subtle interactions: Purposeful animations, no decorative effects
  • Accessible by default: Proper contrast, focus states, ARIA attributes

Design Tokens

All design values are defined as CSS custom properties with the --swp- prefix in /assets/scss/design-system/_tokens.scss.

Color Palette

TokenValueUsage
--swp-primary#2e963aMain CTAs, links, active states
--swp-primary-dark#1f6c29Hover/active states
--swp-primary-light#e8f5e9Backgrounds, badges
--swp-secondary#f0a015Accent color (use sparingly)
--swp-success#10b981Success states, confirmations
--swp-danger#ef4444Errors, destructive actions
--swp-warning#f59e0bWarnings, cautions
--swp-info#3b82f6Informational content

Gray Scale (Slate-based):

  • --swp-gray-50 to --swp-gray-900 for text and backgrounds
  • --swp-text-primary, --swp-text-secondary, --swp-text-muted for text hierarchy

Spacing Scale (8px base)

TokenValueUsage
--swp-space-10.25rem (4px)Tiny gaps
--swp-space-20.5rem (8px)Icon gaps, tight spacing
--swp-space-30.75rem (12px)Button padding
--swp-space-41rem (16px)Standard form spacing
--swp-space-61.5rem (24px)Card padding
--swp-space-82rem (32px)Section spacing

Border Radius

TokenValueUsage
--swp-radius-sm6pxSmall elements, badges
--swp-radius-md8pxButtons, form controls
--swp-radius-lg12pxCards
--swp-radius-xl16pxModals

Shadows

TokenUsage
--swp-shadow-smCards, subtle elevation
--swp-shadow-mdHover states, dropdowns
--swp-shadow-lgModals, popovers
--swp-shadow-primaryPrimary button glow

Typography Guidelines

ElementSize TokenWeightColor
Page title--swp-text-2xl--swp-font-semibold--swp-text-primary
Card title--swp-text-lg--swp-font-semibold--swp-text-primary
Section title--swp-text-md--swp-font-semibold--swp-text-primary
Body text--swp-text-base--swp-font-normal--swp-body-color
Help text--swp-text-sm--swp-font-normal--swp-text-muted
Labels--swp-text-sm--swp-font-medium--swp-text-secondary
Table headers--swp-text-xs--swp-font-semibold--swp-text-muted

Platform UI Components (REQUIRED)

Always use SolidWorx/Platform UI components when available. These are Twig components that provide consistent, accessible UI elements.

Available Components:

ComponentLocationDocs
<twig:Ui:Alert>vendor/solidworx/platform/src/Bundle/Ui/templates/components/Alert.html.twigvendor/solidworx/platform/src/Bundle/Ui/Docs/Alert.md
<twig:Ui:Card>vendor/solidworx/platform/src/Bundle/Ui/templates/components/Card.html.twigvendor/solidworx/platform/src/Bundle/Ui/Docs/Card.md
<twig:Ui:Modal>vendor/solidworx/platform/src/Bundle/Ui/templates/components/Modal.html.twigvendor/solidworx/platform/src/Bundle/Ui/Docs/Modal.md

Usage Examples:

{# Card Component #}
<twig:Ui:Card title="Invoice Details" subtitle="View and edit">
    <p>Card body content here</p>

    <twig:block name="footer">
        <button class="btn btn-ghost" data-bs-dismiss="modal">Cancel</button>
        <button class="btn btn-primary">{{ ux_icon('tabler:device-floppy') }} Save</button>
    </twig:block>
</twig:Ui:Card>

{# Alert Component #}
<twig:Ui:Alert type="success" icon="tabler:check" title="Saved!" :dismissible="true">
    Your changes have been saved successfully.
</twig:Ui:Alert>

{# Modal Component #}
<twig:Ui:Modal id="confirm-modal" title="Confirm Action" status="danger">
    <p>Are you sure you want to proceed?</p>

    <twig:block name="footer">
        <button class="btn btn-ghost" data-bs-dismiss="modal">Cancel</button>
        <button class="btn btn-danger">Confirm</button>
    </twig:block>
</twig:Ui:Modal>

When a style/feature is needed but not supported:

  1. First, create custom HTML to achieve the desired result
  2. Note what feature is missing for future addition to SolidWorx/Platform/UiBundle
  3. New components will be added to Platform over time

Do NOT:

  • Create custom implementations when a Platform component exists
  • Override Platform component styles with inline styles
  • Duplicate Platform component functionality

Component Usage

Buttons

Variants and when to use:

ClassUsage
btn-primaryMain page action, form submit (Save, Create, Confirm)
btn-secondarySecondary accent actions (rarely used)
btn-outline-primarySecondary actions, Cancel buttons
btn-outline-secondaryTertiary actions
btn-ghostMinimal actions, navigation-like buttons
btn-dangerDestructive actions ONLY (Delete, Remove)
btn-outline-dangerLess prominent destructive actions

Icon usage in buttons:

<button class="btn btn-primary">
    {{ ux_icon('tabler:device-floppy') }} Save
</button>
  • Icon BEFORE text
  • Use gap-2 (handled by .btn class)
  • Icon size: 18px default, 16px for small, 20px for large

Button states:

  • Hover: Subtle lift (translateY(-1px)) + shadow increase
  • Active: Return to baseline + reduced shadow
  • Disabled: 50% opacity, no pointer events
  • Loading: Use .btn-loading class for spinner

Cards

Standard card structure:

<div class="card">
    <div class="card-header">
        <h3 class="card-title">Title</h3>
        <div class="card-header-actions">
            <!-- Optional action buttons -->
        </div>
    </div>
    <div class="card-body">
        <!-- Content -->
    </div>
    <div class="card-footer">
        <!-- Action buttons -->
    </div>
</div>

Card variants:

  • Default: White background, subtle shadow, 12px radius
  • .card-flat: No shadow, light border (for nested cards)
  • .card-interactive: Hover lift effect (for clickable cards)
  • .card-muted: Gray background (for secondary content)
  • .card-status-*: Colored top border indicator

Card sections (within body):

<div class="card-section">
    <div class="card-section-header">
        <h4 class="card-section-title">
            {{ ux_icon('tabler:icon') }} Section Title
        </h4>
    </div>
    <!-- Section content -->
</div>

Forms

Standard form field structure:

<div class="mb-4">
    <label class="form-label required">Field Label</label>
    <input type="text" class="form-control" />
    <div class="form-help">Help text appears here</div>
</div>

Form layouts:

  • Single column: Default for settings, simple forms
  • Two columns: Use .row with .col-md-6 for related fields
  • Use .form-row-2 or .form-row-3 for grid layouts

Toggle switches:

<div class="form-switch-field">
    <div class="form-check form-switch form-switch-wrapper">
        <input type="checkbox" class="form-check-input" role="switch" />
        <label class="form-check-label">Toggle Label</label>
    </div>
    <div class="form-help">Help text</div>
</div>

Input with icon:

<div class="input-group input-icon-group">
    <span class="input-group-text">
        {{ ux_icon('tabler:mail', {width: 18, height: 18}) }}
    </span>
    <input type="email" class="form-control" />
</div>

Form Actions Pattern (CRITICAL)

Every form MUST follow this pattern:

┌─────────────────────────────────────────────────────────────────┐
│ [Cancel (btn-ghost)]                     [Save (btn-primary + icon)]│
└─────────────────────────────────────────────────────────────────┘

Standard implementation:

<div class="card-footer">
    <div class="form-actions">
        <button type="button" class="btn btn-ghost">
            {{ 'Cancel'|trans }}
        </button>
        <button type="submit" class="btn btn-primary">
            {{ ux_icon('tabler:device-floppy') }} {{ 'Save'|trans }}
        </button>
    </div>
</div>

Rules:

  1. Cancel button: Ghost or outline style, positioned LEFT or BEFORE save
  2. Save button: Primary style, with floppy disk icon, positioned RIGHT
  3. Use .form-actions-spread to push Cancel to far left
  4. Danger actions (Delete): Use separate confirmation flow, NOT in main form actions

Multi-action forms:

<div class="form-actions form-actions-spread">
    <button type="button" class="btn btn-ghost">Cancel</button>
    <div class="d-flex gap-2">
        <button type="button" class="btn btn-outline-primary">Save as Draft</button>
        <button type="submit" class="btn btn-primary">
            {{ ux_icon('tabler:device-floppy') }} Save
        </button>
    </div>
</div>

Page Patterns

Settings Pages

Rules:

  • Max-width: 900px, centered (use .settings-page)
  • Use horizontal tabs for 2-7 sections
  • Use sidebar navigation for 8+ sections
  • Each settings group in its own card
  • Section headers with icon + title
  • Danger zone at the bottom with red border

Tab navigation:

<nav class="settings-tabs">
    <a href="#company" class="settings-tab active">
        {{ ux_icon('tabler:building') }} Company
    </a>
    <a href="#invoices" class="settings-tab">
        {{ ux_icon('tabler:file-invoice') }} Invoices
    </a>
</nav>

Danger zone:

<div class="settings-danger-zone">
    <div class="card-header">
        <div class="settings-card-icon">
            {{ ux_icon('tabler:alert-triangle') }}
        </div>
        <div>
            <h3 class="settings-card-title">Danger Zone</h3>
            <p class="settings-card-description">Irreversible actions</p>
        </div>
    </div>
    <div class="card-body">
        <!-- Danger actions with confirmation -->
    </div>
</div>

List Pages (DataGrid)

Structure:

<div class="list-page">
    <!-- 1. Page header with title + Create button -->
    <div class="list-header">
        <h1 class="list-title">Invoices</h1>
        <div class="list-actions">
            <a href="#" class="btn btn-primary">
                {{ ux_icon('tabler:plus') }} Create Invoice
            </a>
        </div>
    </div>

    <!-- 2. Search/Filter bar (optional) -->
    <div class="list-filters">
        <div class="list-search">...</div>
        <div class="list-filter-group">...</div>
    </div>

    <!-- 3. Data table -->
    <div class="list-content">
        <table class="table">...</table>
    </div>

    <!-- 4. Pagination -->
    <div class="list-footer">
        <span class="list-footer-info">Showing 1-10 of 50</span>
        <nav class="pagination">...</nav>
    </div>
</div>

Empty state:

<div class="list-empty">
    <div class="list-empty-icon">
        {{ ux_icon('tabler:file-invoice') }}
    </div>
    <h3 class="list-empty-title">No invoices yet</h3>
    <p class="list-empty-description">Create your first invoice to get started.</p>
    <a href="#" class="btn btn-primary">
        {{ ux_icon('tabler:plus') }} Create Invoice
    </a>
</div>

Form Pages (Create/Edit)

Structure:

<div class="form-page">
    <form>
        <div class="card">
            <div class="card-header">
                <h3 class="card-title">Create Invoice</h3>
            </div>
            <div class="card-body">
                <!-- Form fields -->
            </div>
            <div class="card-footer">
                <div class="form-actions">
                    <button type="button" class="btn btn-ghost">Cancel</button>
                    <button type="submit" class="btn btn-primary">
                        {{ ux_icon('tabler:device-floppy') }} Save
                    </button>
                </div>
            </div>
        </div>
    </form>
</div>

Modals

Standard modal:

<div class="modal fade" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Modal Title</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body">
                <!-- Content -->
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-ghost" data-bs-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-primary">Confirm</button>
            </div>
        </div>
    </div>
</div>

Sizes: .modal-sm (400px), default (500px), .modal-lg (800px), .modal-xl (1140px)

Danger/Delete confirmation:

<div class="modal fade modal-danger">
    <div class="modal-dialog modal-confirm">
        <div class="modal-content">
            <div class="modal-body">
                <div class="modal-confirm-icon icon-danger">
                    {{ ux_icon('tabler:trash') }}
                </div>
                <h3 class="modal-confirm-title">Delete Invoice?</h3>
                <p class="modal-confirm-message">This action cannot be undone.</p>
            </div>
            <div class="modal-footer">
                <button class="btn btn-ghost">Cancel</button>
                <button class="btn btn-danger">Delete</button>
            </div>
        </div>
    </div>
</div>

Alerts

Types and usage:

  • alert-success: Confirmation of completed action
  • alert-danger: Error messages, failed operations
  • alert-warning: Cautions, important notices
  • alert-info: Helpful tips, neutral information
  • alert-primary: Brand-related notifications

Structure:

<div class="alert alert-success" role="alert">
    {{ ux_icon('tabler:check') }}
    <div class="alert-content">
        <div class="alert-title">Success!</div>
        <div class="alert-message">Invoice has been saved.</div>
    </div>
    <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>

Tables/DataGrids

Table styling:

  • Headers: Uppercase, small text (--swp-text-xs), muted color
  • Rows: Adequate padding (1rem 1.5rem), subtle hover
  • Borders: Light gray (--swp-border-light), minimal
  • No alternating row colors (hover is enough)

Column types:

  • .table-actions: Right-aligned, action buttons
  • .table-checkbox: Checkbox column (narrow)
  • .table-date: Date column (no wrap)
  • .table-amount: Right-aligned, monospace
  • .table-id: Monospace, muted

Animation Guidelines

Transitions:

  • Hover effects: 150-200ms
  • State changes: 200-300ms
  • Modal open/close: 200ms

What to animate:

  • Hover: Shadow increase, subtle lift (translateY(-1px))
  • Focus: Visible focus ring with primary color
  • Button press: Return to baseline

What NOT to animate:

  • Text content changes
  • Active states (should be instant)
  • Background images

Accessibility Checklist

  • All interactive elements have visible focus states
  • Color contrast ratio: 4.5:1 for text, 3:1 for UI
  • Form fields have associated labels
  • Required fields marked with aria-required="true"
  • Error messages linked with aria-describedby
  • Icon-only buttons have aria-label
  • Modals trap focus and close on Escape
  • Loading states announced to screen readers

File Reference

Design System (SolidInvoice custom styles):

FilePurpose
/assets/scss/design-system/_tokens.scssDesign tokens (colors, spacing, shadows, transitions)
/assets/scss/design-system/_typography.scssTypography scale and utility classes
/assets/scss/_forms.scssForm field enhancements
/assets/scss/settings.scssSettings page specific styles
/src/CoreBundle/Resources/views/Form/fields.html.twigForm field templates

Platform UI Components:

FilePurpose
vendor/solidworx/platform/src/Bundle/Ui/Docs/Card.mdCard component documentation
vendor/solidworx/platform/src/Bundle/Ui/Docs/Alert.mdAlert component documentation
vendor/solidworx/platform/src/Bundle/Ui/Docs/Modal.mdModal component documentation

Note: Button, table, modal, and alert styling comes from Tabler/Bootstrap. Do not create custom component SCSS files that duplicate framework styles.


Quick Reference Checklist

When implementing any UI:

  1. Use Platform UI components (<twig:Ui:Card>, <twig:Ui:Alert>, <twig:Ui:Modal>) when available
  2. ✅ Use design tokens for all colors, spacing, shadows
  3. ✅ Follow form actions pattern (Cancel left/ghost, Save right/primary with icon)
  4. ✅ Use appropriate button variant for the action type
  5. ✅ Settings pages use tabs for < 8 sections
  6. ✅ Tables use Tabler/Bootstrap default styling
  7. ✅ Accessibility: focus states, ARIA labels, proper contrast
  8. ✅ Transitions are subtle and functional (150-300ms)
  9. ✅ No hardcoded color/spacing values - use CSS variables
  10. Do NOT duplicate Bootstrap/Tabler styles - use framework defaults

<frontend_aesthetics> You tend to converge toward generic, "on distribution" outputs. In frontend design, this creates what users call the "AI slop" aesthetic. Avoid this: make creative, distinctive frontends that surprise and delight. Focus on:

Typography: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics.

Color & Theme: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. Draw from IDE themes and cultural aesthetics for inspiration.

Motion: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions.

Backgrounds: Create atmosphere and depth rather than defaulting to solid colors. Layer CSS gradients, use geometric patterns, or add contextual effects that match the overall aesthetic.

Avoid generic AI-generated aesthetics:

  • Overused font families (Inter, Roboto, Arial, system fonts)
  • Clichéd color schemes (particularly purple gradients on white backgrounds)
  • Predictable layouts and component patterns
  • Cookie-cutter design that lacks context-specific character

Interpret creatively and make unexpected choices that feel genuinely designed for the context. Vary between light and dark themes, different fonts, different aesthetics. You still tend to converge on common choices (Space Grotesk, for example) across generations. Avoid this: it is critical that you think outside the box! </frontend_aesthetics>


Last Updated: 2025-12-31 Document Version: 1.1.0 SolidInvoice Version: 3.0.0-dev

Related Documents