Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Aston2710/mc-modeler/llms.txt

Use this file to discover all available pages before exploring further.

All domain types live in a single file: /src/domain/types.ts. They are pure TypeScript — no runtime dependencies, no React imports, no bpmn-js references. Every other layer imports from this file; nothing in types.ts imports from any other layer.
These types define what is stored and what flows through the application. They are not Zod schemas — runtime validation happens at the persistence boundary where needed. The types here are the source of truth for what every store, hook, and component agrees on.

Diagram

The primary persisted entity. Every BPMN file the user works with is a Diagram.
export interface Diagram {
  id: string                      // UUID v4 — immutable once created
  name: string                    // User-editable label, max 100 chars
  xml: string                     // Full BPMN 2.0 XML (OMG namespace)
  thumbnail: string | null        // Data URL (PNG) — auto-generated, ~200×150px
  folderId: string | null         // Local folder grouping (null = root)
  projectId: string | null        // Parent project (null = standalone diagram)
  elementCount: number            // Count of BPMN elements — computed on save
  schemaVersion: number           // Data model migration version, starts at 1
  createdAt: string               // ISO 8601 timestamp
  updatedAt: string               // ISO 8601 timestamp — server-authoritative in cloud mode
  parentDiagramId: string | null  // Set when this diagram is a sub-process
  subProcessElementId: string | null  // ID of the sub-process element in the parent
  /** Soft delete: non-null means the diagram is in the trash. */
  deletedAt?: string | null
}

Field notes

FieldDetails
idGenerated by generateDiagramId() in /src/utils/idGenerator.ts. Never mutated after creation.
xmlThe full BPMN 2.0 XML string using the bpmn: prefix and the OMG namespace (http://www.omg.org/spec/BPMN/20100524/MODEL). Compatible with Bizagi Modeler and Camunda.
thumbnailNot stored inline in Supabase — the thumbnail field is always null in the list response and fetched separately via getThumbnail(id). In LocalRepository, it is stored in a dedicated localforage instance.
updatedAtIn SupabaseRepository, this is set by a BEFORE UPDATE database trigger (diagrams_set_updated_at), making it server-authoritative. The client adopts the returned value as its CAS token for the next save.
parentDiagramIdLinks a sub-process diagram to its parent. Sub-process diagrams are regular Diagram records — they are not a separate entity type.
subProcessElementIdThe id of the bpmn:SubProcess element inside the parent diagram that this diagram expands. Used to re-link after import.
deletedAtSoft delete timestamp. When set, the diagram is in the trash and excluded from getAll(). Call restore() to clear it or purge() to permanently delete.
In SupabaseRepository.getAll(), the xml field is intentionally left empty in list responses. The XML can be hundreds of kilobytes per diagram, and loading it for every diagram card would be unacceptably slow. Call diagramStore.ensureXml(id) to hydrate the XML on demand when a diagram is opened.

Project

A named group of diagrams. Projects enable collaboration at the group level — sharing a project gives access to all diagrams within it.
export interface Project {
  id: string          // UUID v4
  name: string        // User-editable project name
  ownerId: string     // auth.users.id of the creator
  createdAt: string   // ISO 8601
  updatedAt: string   // ISO 8601
  /** Soft delete: non-null means the project is in the trash. */
  deletedAt?: string | null
}
Deleting a project soft-deletes both the project record and all its diagrams together. Restoring a project restores its diagrams as well.

Folder

A local organizational grouping for diagrams. Folders have no permission semantics — they are purely cosmetic, like file-system folders.
export interface Folder {
  id: string        // UUID v4
  name: string      // User-editable, max 60 chars
  createdAt: string // ISO 8601
}
Folders belong to individual users. In SupabaseRepository, each folder row has an owner_id column enforced by RLS. Collaborators on a project see the project’s diagrams regardless of which folder those diagrams are in.

LibraryImage

Images are first-class entities in MC Modeler, not embedded data URLs. BPMN elements link to images by id via the flujo:linkedImages attribute — they never embed bytes directly in the XML.
/**
 * Image in the library: a first-class entity. BPMN elements link to it
 * by id (flujo:linkedImages attribute), never embedding the bytes.
 * `ref` resolves the bytes on demand:
 *   `storage://diagram-images/...`  — cloud (Supabase Storage)
 *   `local://<id>`                  — offline (IndexedDB)
 */
export interface LibraryImage {
  id: string              // UUID v4
  name: string            // Display name
  projectId: string | null  // Owning project (null = personal)
  folderId: string | null   // Image folder (null = root)
  mime: string            // MIME type, e.g. "image/webp", "image/png"
  size: number            // File size in bytes
  ref: string             // Storage reference — see format below
  createdAt: string       // ISO 8601
  updatedAt: string       // ISO 8601
}

The ref field

The ref field is an opaque reference string resolved by the image repository at access time:
FormatBackendResolution
storage://diagram-images/<path>Supabase StorageFetched from the diagram-images bucket
local://<id>IndexedDBFetched from LocalImageRepository by the image id
This indirection means the XML stored in Diagram.xml remains lightweight — it contains image IDs, not base64-encoded binary data.

ImageFolder

Organizes images in the library. Parallel to the Folder type for diagrams, but scoped to the image library.
export interface ImageFolder {
  id: string              // UUID v4
  name: string
  projectId: string | null  // Scoped to a project, or personal (null)
  createdAt: string
}

UserPreferences

User-specific application settings, persisted per device. In SupabaseRepository, preferences are always stored locally (delegated to LocalRepository) — they are not synced to the cloud.
export interface UserPreferences {
  language: 'es' | 'en'                       // UI language; default 'es'
  theme: 'light' | 'dark' | 'system'          // Color theme
  gridEnabled: boolean                         // Show canvas grid
  gridSize: 5 | 10 | 20                        // Grid cell size in px
  snapToGrid: boolean                          // Snap elements to grid
  autoSaveInterval: number                     // Seconds between saves; default 30
  lastOpenedDiagramId: string | null           // Reopened on next app launch
  paletteMode: 'grid' | 'dropdown' | 'bizagi' // Element palette layout
  showComments: boolean                        // Toggle comment overlays
  diagramSort: DiagramSort                     // Active sort for diagram list
}

DiagramSort

export type DiagramSortKey = 'updated' | 'created' | 'name' | 'elements'

export interface DiagramSort {
  key: DiagramSortKey
  dir: 'asc' | 'desc'
}

ValidationResult

Produced by the validation engine (/src/domain/validation.ts) when the user triggers a BPMN check. Results are held in uiStore.validationResults.
export interface ValidationResult {
  id: string               // Unique result ID
  elementId: string | null // null = diagram-level error (e.g., no end event)
  elementName: string | null
  severity: 'error' | 'warning'
  code: string             // Machine-readable code, e.g. 'MISSING_END_EVENT'
  message: string          // Pre-translated to the active UI language
}
Clicking a result in the validation panel calls useBpmnModeler.scrollToElement(elementId), which selects and scrolls to the offending element in the canvas.

Toast

In-app non-blocking notifications. Managed by uiStore. Toasts auto-dismiss after duration milliseconds; a duration of 0 makes a toast persistent until manually dismissed.
export interface ToastAction {
  label: string
  onClick: () => void
}

export interface Toast {
  id: string
  type: 'success' | 'error' | 'warning' | 'info'
  title: string
  message?: string
  /** Milliseconds until auto-dismiss. 0 = persistent. */
  duration?: number
  /** Action buttons; clicking any one dismisses the toast. */
  actions?: ToastAction[]
}

Collaboration Types

CollaboratorRole

export type CollaboratorRole = 'owner' | 'editor' | 'viewer'
RolePermissions
ownerFull control: edit, share, delete, manage collaborators
editorCan edit the diagram or project content
viewerRead-only access; can view and comment, cannot modify

Collaborator

Represents a user’s membership in a diagram or project’s collaboration roster.
export interface Collaborator {
  userId: string
  email: string | null
  displayName: string | null
  avatarUrl: string | null
  role: CollaboratorRole
}

DiagramConflictError

DiagramConflictError is defined in /src/persistence/IDiagramRepository.ts, not in types.ts. It is documented here because it is part of the public domain contract — every caller of IDiagramRepository.save() must handle it.
A typed error class thrown by IDiagramRepository.save() when an optimistic concurrency check (CAS — Compare-And-Swap) fails. This happens in SupabaseRepository when expectedUpdatedAt is provided but the record in the database has a different updated_at — meaning another writer saved in between.
export class DiagramConflictError extends Error {
  constructor(public readonly diagramId: string) {
    super(
      `Conflicto de guardado: el diagrama ${diagramId} fue modificado por otro usuario`
    )
    this.name = 'DiagramConflictError'
  }
}
diagramStore.saveDiagram() catches this error and implements a two-retry resolution strategy:
1

First conflict

Re-fetch the current server version. If the server already has this exact XML (another writer saved the same state), adopt the server’s updatedAt and stop.
2

Retry with fresh version

If the content differs, retry save() using the freshly fetched updatedAt as the new CAS token.
3

Second conflict

Re-fetch again and check content. If content is the same, adopt. If content genuinely diverges and the conflict persists, emit a flujo:save-conflict custom DOM event so the UI can offer the user a choice: reload the server version or save a local copy as a duplicate.

UI State Types

The following types are used by uiStore to manage transient, non-persisted application state.

ActiveModal

Union type of all modal identifiers. null means no modal is open.
export type ActiveModal =
  | 'export'
  | 'import'
  | 'shortcuts'
  | 'validation'
  | 'newDiagram'
  | 'imageUpload'
  | 'share'
  | 'newProject'
  | 'shareProject'
  | 'linkDiagram'
  | 'imageGallery'
  | 'imagePicker'
  | 'imageViewer'
  | null

DiagramTab

Represents an open editor tab. The dirty flag drives the unsaved-changes indicator in the tab bar.
export interface DiagramTab {
  id: string     // Matches a Diagram.id
  name: string   // Tab label (mirrors Diagram.name)
  dirty: boolean // True if there are unsaved changes in this tab
}

DiagramListFilter

export type DiagramListFilter = 'all' | 'recent' | 'own' | 'shared'

Full types.ts Reference

When adding a new domain concept, add it to types.ts first. Define the interface before writing any store, hook, or component. This ensures the shape is agreed upon before any implementation begins.
The complete file at a glance:
// /src/domain/types.ts — summary of all exports

export interface Diagram { ... }
export interface Folder { ... }
export interface ImageFolder { ... }
export interface LibraryImage { ... }
export interface Project { ... }

export type DiagramListFilter = 'all' | 'recent' | 'own' | 'shared'
export type DiagramSortKey = 'updated' | 'created' | 'name' | 'elements'
export interface DiagramSort { key: DiagramSortKey; dir: 'asc' | 'desc' }

export interface UserPreferences { ... }
export interface ValidationResult { ... }

export interface ToastAction { label: string; onClick: () => void }
export interface Toast { ... }

export type ActiveModal = 'export' | 'import' | ... | null

export type CollaboratorRole = 'owner' | 'editor' | 'viewer'
export interface Collaborator { ... }

export interface UIState { ... }
export interface DiagramTab { ... }

Build docs developers (and LLMs) love