# basecamp/fizzy architecture

> Architecture diagram of the GitHub repository basecamp/fizzy, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-26). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/basecamp/fizzy
- Repository: https://github.com/basecamp/fizzy

## Overview

1. **Overall shape**
   - This is a Ruby on Rails monolith for a Kanban-style product called Fizzy.
   - The repo is split into a **core application** at the root and a **SaaS extension/engine** under `saas/`, which overlays billing, storage limits, native push devices, admin stats, and related deployment/runtime differences.

2. **Primary request path**
   - Most user interactions follow the standard Rails flow: **controller → model/service logic → view**.
   - The app uses **Hotwire/Turbo**, **Stimulus**, **Action Text**, and **Importmap** rather than a separate SPA frontend, so server-rendered HTML and incremental updates are the main UI pattern.
   - Many actions also emit **Turbo Streams** for live updates in place.

3. **Domain model boundary**
   - The core business domain is organized around **accounts, users, identities/sessions, boards, columns, cards, comments, tags, reactions, watches, pins, notifications, events, and webhooks**.
   - The app is explicitly **multi-tenant**: most models and controllers are scoped through account/board/card concerns, and there are dedicated multi-tenant helpers and initializers.
   - The board/card domain is the center of the product; almost every other subsystem feeds into it or derives from it.

4. **Board/card workflow subsystem**
   - Boards contain columns and cards, with specialized states such as streams, not-nows, and closures.
   - Card operations are decomposed into many small controllers and model concerns: assignments, self-assignments, closures, triage, notes/comments, steps, pins, watches, goldness, tagging, and publishing.
   - This is where the main product data flow happens: users manipulate cards, which update board state, activity history, and notifications.

5. **Activity, events, and timelines**
   - The app has a first-class **event** layer, separate from direct card/board records.
   - Events drive activity feeds, day timelines, and user-centric views, and they are also used as input for notifications and webhooks.
   - This means “what happened” is modeled independently from “current board state,” which is important for diagrams showing both operational state and audit/activity history.

6. **Search and filters**
   - Search is its own subsystem with query parsing, stemming, highlighting, results, and backend-specific record implementations.
   - There is also a structured **filter** model with fields/resources/params/summaries, used to refine boards, cards, people, tags, and activity views.
   - Search appears to be indexed in background jobs and supported by database-specific full-text/search record tables.

7. **Notifications and messaging**
   - Notifications are multi-channel: **in-app trays**, **email**, **web push**, and in the SaaS layer also **native push devices**.
   - Notification delivery is asynchronous and organized through bundling, push targets, and notifier classes.
   - Mailers cover sign-in, exports/imports, account events, and bundled notifications; there are also unsubscribe flows and notification settings.

8. **Authentication and account access**
   - Authentication supports **magic links** and **passkeys/WebAuthn**, with dedicated controllers, mailers, and a custom `lib/action_pack/passkey` implementation.
   - The app also supports identities, access tokens, join codes, user transfers, and role/verification workflows.
   - Access control is centralized through controller concerns and tenant/account scoping rather than being embedded in individual views.

9. **Files, rich content, and storage**
   - The app relies on **Active Storage** and **Action Text** for attachments and rich text content.
   - Storage is treated as a real domain concern: there are storage totals, tracked entries, materialization/reconciliation jobs, and purge-on-last-attachment behavior.
   - The SaaS layer adds storage enforcement and exceptions, so storage is both an infrastructure concern and a product boundary.

10. **Asynchronous processing**
   - Background jobs are a major part of the architecture.
   - Jobs handle imports/exports, notification delivery, webhook dispatch, search reindexing, storage reconciliation, tag cleanup, and activity-spike detection.
   - This separates user-facing requests from slower side effects and external integrations.

11. **External integrations**
   - The main external integrations visible in the tree are **email delivery**, **web push notifications**, **WebAuthn/passkeys**, **webhooks**, and **object storage**.
   - There is also support for PWA assets, service worker, and mobile-facing client configuration endpoints, which suggests the app is designed to work well on browsers and installed clients.

12. **Public vs authenticated surfaces**
   - There are distinct controller/view areas for **public access**, **authenticated app usage**, **admin**, **account settings**, and **my/ personal settings**.
   - Public board/card endpoints expose read-only or shareable views, while internal routes manage editing, notifications, preferences, and administrative operations.
   - This separation is important for diagramming trust boundaries.

13. **SaaS overlay**
   - The `saas/` tree is not just configuration; it is a parallel engine with its own models, controllers, views, jobs, tests, and migrations.
   - It introduces billing/subscription concepts, device registration, storage-limit handling, admin auditing/stats, and native push notification support.
   - In architecture terms, think of it as a product-variant layer that extends the same core domain with paid-plan and operational controls.

14. **Deployment/runtime**
   - The repo supports Docker and Kamal deployments, with separate app and SaaS deployment configs.
   - The app is built as a conventional Rails server with background jobs and a cable layer, plus database, cache, queue, and file storage support.
   - Tests are broad and layered: controller, model, job, integration, system, mailer, and library tests, mirroring the subsystem boundaries above.

Main entry points: [`app/models/board.rb`](https://github.com/basecamp/fizzy/blob/main/app/models/board.rb) (Boards & cards), [`app/jobs/application_job.rb`](https://github.com/basecamp/fizzy/blob/main/app/jobs/application_job.rb) (Jobs & mailers), [`saas/app/models/account/storage_limited.rb`](https://github.com/basecamp/fizzy/blob/main/saas/app/models/account/storage_limited.rb) (Limits & devices), [`app/models/notification.rb`](https://github.com/basecamp/fizzy/blob/main/app/models/notification.rb) (Notifications)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_core["Core app"]
end

subgraph group_ui["UI layer"]
  node_application_controller["Application controller<br/>Rails base"]
  node_boards_cards_controllers["Board actions<br/>Controllers"]
  node_specialized_card_controllers["Card workflows<br/>Controllers"]
  node_boards_columns_controllers["Column actions<br/>Controllers"]
  node_sessions_auth_controllers["Auth flow<br/>Controllers"]
  node_account_user_controllers["Account area<br/>Controllers"]
  node_notifications_search_controllers["Search & inbox<br/>Controllers"]
  node_public_controllers["Public surface<br/>Controllers"]
  node_client_ui_runtime["Hotwire client<br/>Frontend runtime<br/>[application.js]"]
end

subgraph group_domain["Domain model"]
  node_board_card_models["Boards & cards<br/>Models<br/>[board.rb]"]
  node_user_identity_models["Users & identity<br/>Models<br/>[user.rb]"]
  node_event_timeline_models["Events & timelines<br/>Models<br/>[event.rb]"]
  node_search_filter_models["Search & filters<br/>Models<br/>[search.rb]"]
  node_notification_models["Notifications<br/>Models<br/>[notification.rb]"]
  node_storage_models["Storage ledger<br/>Models<br/>[storage.rb]"]
end

subgraph group_async["Async & integrations"]
  node_webhook_models["Webhooks<br/>Models<br/>[webhook.rb]"]
  node_jobs_and_mailers["Jobs & mailers<br/>Async<br/>[application_job.rb]"]
end

subgraph group_saas["SaaS overlay"]
  node_saas_engine["SaaS engine<br/>[engine.rb]"]
  node_saas_limits_devices["Limits & devices<br/>SaaS models/controllers<br/>[storage_limited.rb]"]
  node_saas_admin_stats["Admin stats<br/>SaaS controllers"]
end

subgraph group_runtime["Runtime"]
  node_rails_extensions_lib["Rails extensions<br/>Runtime lib<br/>[passkey.rb]"]
  node_core_initializers["Core config<br/>Initializers<br/>[multi_tenant.rb]"]
end

node_application_controller -->|"authenticates"| node_sessions_auth_controllers
node_application_controller -->|"scopes"| node_board_card_models
node_boards_cards_controllers -->|"mutates"| node_board_card_models
node_specialized_card_controllers -->|"updates"| node_board_card_models
node_boards_columns_controllers -->|"reorders"| node_board_card_models
node_public_controllers -->|"reads"| node_board_card_models
node_account_user_controllers -->|"manages"| node_user_identity_models
node_sessions_auth_controllers -->|"verifies"| node_user_identity_models
node_notifications_search_controllers -->|"queries"| node_search_filter_models
node_notifications_search_controllers -->|"views"| node_event_timeline_models
node_board_card_models -->|"emits"| node_event_timeline_models
node_board_card_models -->|"notifies"| node_notification_models
node_event_timeline_models -->|"feeds"| node_notification_models
node_board_card_models -->|"triggers"| node_webhook_models
node_jobs_and_mailers -->|"delivers"| node_notification_models
node_jobs_and_mailers -->|"dispatches"| node_webhook_models
node_jobs_and_mailers -->|"reindexes"| node_search_filter_models
node_jobs_and_mailers -->|"reconciles"| node_storage_models
node_client_ui_runtime -->|"requests"| node_application_controller
node_client_ui_runtime -->|"updates live"| node_board_card_models
node_rails_extensions_lib -->|"supports"| node_sessions_auth_controllers
node_core_initializers -->|"configures"| node_board_card_models
node_core_initializers -->|"loads"| node_rails_extensions_lib
node_saas_engine -->|"adds"| node_saas_limits_devices
node_saas_engine -->|"adds"| node_saas_admin_stats
node_saas_limits_devices -->|"constrains"| node_storage_models
node_saas_limits_devices -->|"extends"| node_notification_models
node_saas_limits_devices -->|"guards"| node_board_card_models
node_saas_admin_stats -->|"observes"| node_jobs_and_mailers

click node_application_controller "https://github.com/basecamp/fizzy/blob/main/app/controllers/application_controller.rb"
click node_boards_cards_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/cards_controller.rb"
click node_specialized_card_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/cards/assignments_controller.rb"
click node_boards_columns_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/boards/columns_controller.rb"
click node_sessions_auth_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/sessions_controller.rb"
click node_account_user_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/account/settings_controller.rb"
click node_notifications_search_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/searches_controller.rb"
click node_public_controllers "https://github.com/basecamp/fizzy/blob/main/app/controllers/public/boards_controller.rb"
click node_board_card_models "https://github.com/basecamp/fizzy/blob/main/app/models/board.rb"
click node_user_identity_models "https://github.com/basecamp/fizzy/blob/main/app/models/user.rb"
click node_event_timeline_models "https://github.com/basecamp/fizzy/blob/main/app/models/event.rb"
click node_search_filter_models "https://github.com/basecamp/fizzy/blob/main/app/models/search.rb"
click node_notification_models "https://github.com/basecamp/fizzy/blob/main/app/models/notification.rb"
click node_storage_models "https://github.com/basecamp/fizzy/blob/main/app/models/storage.rb"
click node_webhook_models "https://github.com/basecamp/fizzy/blob/main/app/models/webhook.rb"
click node_jobs_and_mailers "https://github.com/basecamp/fizzy/blob/main/app/jobs/application_job.rb"
click node_client_ui_runtime "https://github.com/basecamp/fizzy/blob/main/app/javascript/application.js"
click node_rails_extensions_lib "https://github.com/basecamp/fizzy/blob/main/lib/action_pack/passkey.rb"
click node_core_initializers "https://github.com/basecamp/fizzy/blob/main/config/initializers/multi_tenant.rb"
click node_saas_engine "https://github.com/basecamp/fizzy/blob/main/saas/lib/fizzy/saas/engine.rb"
click node_saas_limits_devices "https://github.com/basecamp/fizzy/blob/main/saas/app/models/account/storage_limited.rb"
click node_saas_admin_stats "https://github.com/basecamp/fizzy/blob/main/saas/app/controllers/admin/stats_controller.rb"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_application_controller,node_boards_cards_controllers,node_specialized_card_controllers,node_boards_columns_controllers,node_sessions_auth_controllers,node_account_user_controllers,node_notifications_search_controllers,node_public_controllers,node_client_ui_runtime toneAmber
class node_board_card_models,node_user_identity_models,node_event_timeline_models,node_search_filter_models,node_notification_models,node_storage_models toneMint
class node_webhook_models,node_jobs_and_mailers toneRose
class node_saas_engine,node_saas_limits_devices,node_saas_admin_stats toneIndigo
class node_rails_extensions_lib,node_core_initializers toneTeal
```

## Components

### UI layer

Hotwire, controllers, views, and client-side Stimulus behavior.

- [Application controller](https://github.com/basecamp/fizzy/blob/main/app/controllers/application_controller.rb): Rails base · Top-level request entry with auth, tenant, and UI concerns. · `app/controllers/application_controller.rb`
- [Board actions](https://github.com/basecamp/fizzy/blob/main/app/controllers/cards_controller.rb): Controllers · Board and card workflow endpoints for editing, moving, and updating work items. · `app/controllers/cards_controller.rb`
- [Card workflows](https://github.com/basecamp/fizzy/blob/main/app/controllers/cards/assignments_controller.rb): Controllers · Assignments, closures, triage, comments, pins, watches, tags, steps, reactions, and drafts. · `app/controllers/cards/assignments_controller.rb`
- [Column actions](https://github.com/basecamp/fizzy/blob/main/app/controllers/boards/columns_controller.rb): Controllers · Board columns plus card drops into columns and special states. · `app/controllers/boards/columns_controller.rb`
- [Auth flow](https://github.com/basecamp/fizzy/blob/main/app/controllers/sessions_controller.rb): Controllers · Sessions, magic links, passkeys, signups, transfers, and join codes. · `app/controllers/sessions_controller.rb`
- [Account area](https://github.com/basecamp/fizzy/blob/main/app/controllers/account/settings_controller.rb): Controllers · Account settings, exports/imports, identities, roles, verifications, avatars, and personal settings. · `app/controllers/account/settings_controller.rb`
- [Search & inbox](https://github.com/basecamp/fizzy/blob/main/app/controllers/searches_controller.rb): Controllers · Search, filters, activity timelines, notification trays/settings, and read states. · `app/controllers/searches_controller.rb`
- [Public surface](https://github.com/basecamp/fizzy/blob/main/app/controllers/public/boards_controller.rb): Controllers · Public board and card views for shareable or read-only access. · `app/controllers/public/boards_controller.rb`
- [Hotwire client](https://github.com/basecamp/fizzy/blob/main/app/javascript/application.js): Frontend runtime · Stimulus controllers, importmap application, Turbo updates, Action Text, and PWA/client config. · `app/javascript/application.js`

### Domain model

Board/card collaboration, activity history, identity, search, notifications, and storage.

- [Boards & cards](https://github.com/basecamp/fizzy/blob/main/app/models/board.rb): Models · Core tenant-scoped board, column, card, comment, tag, pin, watch, reaction, and step records. · `app/models/board.rb`
- [Users & identity](https://github.com/basecamp/fizzy/blob/main/app/models/user.rb): Models · Users, identities, sessions, access tokens, magic links, passkeys, joins, roles, and verification. · `app/models/user.rb`
- [Events & timelines](https://github.com/basecamp/fizzy/blob/main/app/models/event.rb): Models · Event layer, day timelines, activity summaries, and promptable descriptions. · `app/models/event.rb`
- [Search & filters](https://github.com/basecamp/fizzy/blob/main/app/models/search.rb): Models · Query parsing, stemming, highlights, results, and structured filter resources. · `app/models/search.rb`
- [Notifications](https://github.com/basecamp/fizzy/blob/main/app/models/notification.rb): Models · Notification bundles, push targets, notifiers, readings, and user notification settings. · `app/models/notification.rb`
- [Storage ledger](https://github.com/basecamp/fizzy/blob/main/app/models/storage.rb): Models · Attachments, storage totals, tracked entries, and account storage accounting. · `app/models/storage.rb`

### Async & integrations

Jobs, mailers, webhooks, push, and external delivery paths.

- [Webhooks](https://github.com/basecamp/fizzy/blob/main/app/models/webhook.rb): Models · Webhook definitions, deliveries, delinquency tracking, and trigger logic. · `app/models/webhook.rb`
- [Jobs & mailers](https://github.com/basecamp/fizzy/blob/main/app/jobs/application_job.rb): Async · Background delivery for imports, exports, reindexing, notifications, webhooks, and account cleanup. · `app/jobs/application_job.rb`

### SaaS overlay

Parallel engine that adds billing-like limits, admin stats, and native push support.

- [SaaS engine](https://github.com/basecamp/fizzy/blob/main/saas/lib/fizzy/saas/engine.rb): Engine · Parallel extension that adds subscriptions, storage limits, native push devices, and admin tooling. · `saas/lib/fizzy/saas/engine.rb`
- [Limits & devices](https://github.com/basecamp/fizzy/blob/main/saas/app/models/account/storage_limited.rb): SaaS models/controllers · Storage-limited card paths, native push devices, subscriptions, and storage exceptions. · `saas/app/models/account/storage_limited.rb`
- [Admin stats](https://github.com/basecamp/fizzy/blob/main/saas/app/controllers/admin/stats_controller.rb): SaaS controllers · Admin auditing and usage statistics for the SaaS variant. · `saas/app/controllers/admin/stats_controller.rb`

### Runtime

Deployment, initialization, and shared library/runtime extensions.

- [Rails extensions](https://github.com/basecamp/fizzy/blob/main/lib/action_pack/passkey.rb): Runtime lib · Custom Action Pack passkeys/WebAuthn code, Rails extensions, deployment helpers, and web push utilities. · `lib/action_pack/passkey.rb`
- [Core config](https://github.com/basecamp/fizzy/blob/main/config/initializers/multi_tenant.rb): Initializers · Multi-tenant, passkey, push, storage, CSP, and environment initialization. · `config/initializers/multi_tenant.rb`

## Connections

- Application controller → Auth flow: authenticates · Shared controller concerns route users through session and identity flows.
- Application controller → Boards & cards: scopes · Controllers operate inside tenant/account and board/card boundaries.
- Board actions → Boards & cards: mutates · Core card CRUD and board updates persist into domain records.
- Card workflows → Boards & cards: updates · Assignments, comments, pins, watches, tags, steps, and reactions all touch the card model.
- Column actions → Boards & cards: reorders · Column actions move cards across board state and special lanes.
- Public surface → Boards & cards: reads · Public views render board and card data without full edit access.
- Account area → Users & identity: manages · Account settings and personal settings mutate identity and user records.
- Auth flow → Users & identity: verifies · Magic links, passkeys, join codes, and transfers establish identity and session state.
- Search & inbox → Search & filters: queries · Search and filters build structured queries and refine results.
- Search & inbox → Events & timelines: views · Activity and day-timeline endpoints present event history.
- Boards & cards → Events & timelines: emits · Board/card changes generate first-class events and timelines.
- Boards & cards → Notifications: notifies · Card activity produces in-app, email, and push notifications.
- Events & timelines → Notifications: feeds · Event payloads are used for notification content and routing.
- Boards & cards → Webhooks: triggers · Publishing and eventing dispatch webhook activity.
- Jobs & mailers → Notifications: delivers · Bundled and push notifications are sent asynchronously.
- Jobs & mailers → Webhooks: dispatches · Webhook delivery jobs handle external callbacks.
- Jobs & mailers → Search & filters: reindexes · Search indexing and refresh work runs in background jobs.
- Jobs & mailers → Storage ledger: reconciles · Storage materialization and reconciliation happen out of band.
- Hotwire client → Application controller: requests · Turbo/Hotwire client interactions hit Rails controllers.
- Hotwire client → Boards & cards: updates live · Turbo streams and Stimulus interactions keep board/card state fresh.
- Rails extensions → Auth flow: supports · Custom passkey/WebAuthn plumbing backs authentication.
- Core config → Boards & cards: configures · Tenanting and storage/search/runtime initializers shape model behavior.
- Core config → Rails extensions: loads · Boot-time config enables custom Rails and security extensions.
- SaaS engine → Limits & devices: adds · Engine layer introduces paid-plan limits and native push device handling.
- SaaS engine → Admin stats: adds · SaaS variant includes admin dashboards and audits.
- Limits & devices → Storage ledger: constrains · SaaS storage limits overlay the core storage ledger.
- Limits & devices → Notifications: extends · Native push becomes another notification target.
- Limits & devices → Boards & cards: guards · Storage-limit concerns block or alter card creation and publishing paths.
- Admin stats → Jobs & mailers: observes · Admin tooling reflects asynchronous billing and usage activity.
