Transforming Development with Mermaid Diagrams and GitHub Copilot Part 1

August 13, 2025

From Zero to Full-Stack Application

The Challenge

Have you ever wondered what it’s like to build a complete, production-ready application using AI-powered development tools?

In this blog series, I’ll take you through my journey of recreating a fully functional Trello clone using GitHub Copilot (using the VSCode plugin), SvelteKit, and Mermaid diagrams (built using Mermaid Chart) for planning and documentation.

What started as a simple recreation project after losing my original workspace turned into a fascinating exploration of modern AI-assisted development, complete with real challenges, debugging sessions, and the kind of iterative problem-solving that mirrors actual production development.

Note: Yes, I initially had Copilot write this blog post as well, but it was heavily edited as I read through it, since it certainly did not sound like me, but also there was a lot of duplication of information and odd placement of different sections

The site at the end of this session

TL;DR;

This was fun, and worked much better than I originally thought it would. It was also a good exercise for me to see that no, AI will NOT be taking over software development jobs. They will be pairing with developers as another developer.

Because AI-assisted development isn’t about replacing developer skills, Its amplifying what the developer is already doing with better communication tools. The conversations with Copilot, enhanced by visual diagrams, can lead to better solutions, catch potential issues early, and help explore implementation options that might not have been considered otherwise.

The Mermaid + Copilot combination was transformative. Visual diagrams eliminated ambiguity in requirements, caught design issues early, and enabled Copilot to provide much more precise and contextual suggestions. This approach reduced development time while actually improving code quality—a rare combination.

Overall, AI assistance was invaluable for rapid prototyping, debugging complex issues, and implementing comprehensive testing strategies. However, the project also highlighted the importance of understanding fundamental concepts and making thoughtful architectural decisions.

The Setup: Tech stack

I already knew I wanted to use Svelte/SvelteKit for the web framework, and creating the skeleton app provided options for installing other development tools (I used npx sv create for the skeleton)

  • SvelteKit with Svelte 5 (using the new runes mode)
  • TypeScript for type safety
  • Tailwind CSS for styling
  • Vitest for testing
  • Playwright for end-to-end testing
  • GitHub Copilot as my AI pair programming partner

Tools and Workflow

As mentioned earlier, I used the Copilot chat window within VSCode that is provided by the Copilot extension, which allowed copilot to analyze and update code as we talked, but also so I did not have to do any copy/paste from the website tool.

I used Mermaid Chart for creating and refining diagrams, then copied the Mermaid markdown directly into my conversations with Copilot. This visual-first approach reduced the back-and-forth needed to clarify requirements and led to more accurate implementations on the first try.

The Conversation-Driven Development Approach

One of the most interesting aspects of this project was how it evolved through natural conversation with Copilot. Instead of starting with detailed specifications, I described what I wanted to build and let the conversation guide the architecture:

Me: "I want to recreate a Trello clone project using SvelteKit, Copilot, and Mermaid diagrams"

Copilot: "I'll help you create a comprehensive Trello clone! Let's start by setting up the project structure and defining our data models..."

This conversational approach led to some surprising insights about how AI can help with architectural decisions.


MermaidJs: Visual Communication with AI

One of the more powerful techniques I discovered was using Mermaid diagrams as a communication tool with GitHub Copilot. Rather than describing a complex system in prose, I found that providing visual diagrams (in a markdown syntax) dramatically improved the quality and accuracy of Copilot’s suggestions.

Instead of writing lengthy descriptions, I provided Copilot with the following diagrams: a flowchart showing the process of actions that a user can take, as well as a UML Class Diagram showing the base entities needed

---
config:
  theme: redux
---
flowchart LR
 subgraph FE["Frontend (Web App)"]
  Dash["Dashboard"]
  Board["Board"]      
 end

 subgraph subGraph0["Identity Operations"]
  Login["Login"]
  UserExists["Does user exist"]
  Register["Register"]
  CreateAccount["Create account flow"]
 end

 subgraph subGraph1["Kanban Board Operations"]
  BE["Backend API"]
 end

 User["User"] --> UserExists
 UserExists -- No --> Register
 UserExists -- Yes --> Login
 Register --> CreateAccount
 CreateAccount <--> DB
 Login --> Dash
 Dash -- select board --> Board
 FE -- API Requests --> BE
 BE -- Auth/User Data --> DB["Database"]
 Dash -- Create Board --> BE
 Board -- Update board name --> BE
 Board -- Create/edit List/Card --> BE
 Board -- Move Card --> BE
 Board -- Update/Delete Card --> BE
 Board -- Assign User --> BE
 BE -- CRUD Kanban Data --> DB

 UserExists@{ shape: diam}
 CreateAccount@{ shape: subproc}
 DB@{ shape: cyl}

Data Model Class Diagram

For the data architecture, I created a UML class diagram that Copilot could instantly understand:

classDiagram
 class User {
  +number id
  +string username
  +string email
 }

 class Board {
  +number id
  +string title
  +List[] lists
  +User[] members
 }

 class List {
  +number id
  +string name
  +number position
  +Card[] cards
 }

 class Card {
  +number id
  +string title
  +string description
  +User createdBy
  +Date createdAt
  +Date? dueDate
  +number position
  +Comment[] comments
  +Label[] labels
  +User? assignedTo
 }

 class Comment {
  +number id
  +string text
  +Date createdAt
  +User createdBy
 }

 class Label {
  +number id
  +string name
  +string color
 }

 Board ||--o{ List : contains
 List ||--o{ Card : contains
 Card ||--o{ Comment : has
 Card }o--o{ Label : tagged_with
 Card }o--|| User : assigned_to
 Card ||--|| User : created_by
 Comment ||--|| User : created_by
 Board }o--o{ User : has_members

Why Mermaid Diagrams Transformed Development

1. Precision in Communication
When I told Copilot “implement drag and drop,” it gave generic suggestions. When I showed the sequence diagram above, it immediately understood:

  • The need for data serialization/de-serialization
  • The component hierarchy and event flow
  • The state management requirements
  • The persistence layer interaction

2. Architectural Validation
Copilot could spot potential issues in the diagrams:

Me: "Here's my component architecture diagram..."

Copilot: "I notice in your sequence diagram that you're passing Date objects through drag events. This will fail because drag data transfer only supports strings. You'll need to serialize dates to ISO strings and reconstruct them on drop."

3. Implementation Guidance
The class diagram directly informed the TypeScript interfaces:

// Generated directly from the class diagram
export interface Card {
  id: number;
  title: string;
  description: string;
  createdBy: User;        // Relationship from diagram
  createdAt: Date;
  dueDate?: Date;         // Optional from diagram
  position: number;
  comments: Comment[];    // One-to-many from diagram
  labels: Label[];        // Many-to-many from diagram
  assignedTo?: User;      // Optional relationship
}

4. Feature Planning
The flowchart helped prioritize development phases:

  • Phase 1: Core data models and basic CRUD
  • Phase 2: Board and list management
  • Phase 3: Drag-and-drop functionality
  • Phase 4: User management and assignment
  • Phase 5: Advanced features (comments, labels, due dates)

Conversation Example: Mermaid-Driven Development

Here’s an actual conversation that shows the power of this approach:

Me: "I need to implement the card editing modal. Here's the user interaction flow:"

[Provides Mermaid diagram]

Copilot: "Based on your diagram, I can see you need:
1. A reusable Modal component with backdrop/escape handling
2. Form state management for the card fields
3. Date input handling for due dates
4. User selection dropdown for assignment
5. Label checkbox management
6. Comment system with real-time updates

Let me implement each piece..."

The diagram provided context that would have taken paragraphs to explain in text.

Phase 1: Design and Architecture

Defining the Core Models

The first step was establishing our data structure. Using the UML class diagram I created with Mermaid (shown above), I provided this visual specification to Copilot, which then generated the corresponding TypeScript interfaces that would form the backbone of our application:

export interface User {
  id: number;
  username: string;
  email: string;
}

...

Key Decision: Using full User objects instead of just IDs for references. This choice, influenced by the class diagram’s relationship notation, would later impact our testing strategy and data serialization approach.

How Mermaid Helped: The class diagram’s relationship lines (||--o{, }o--||) clearly showed which relationships were one-to-many vs many-to-many, guiding Copilot to suggest appropriate TypeScript array types and optional fields.

Component Design Patterns

We established several key patterns:

  1. Props with Bindable State: Using $bindable() for two-way data flow
  2. Event-Driven Communication: Components communicate through callback props
  3. Immutable Updates: All state changes create new objects
  4. TypeScript First: Full type safety throughout the application

Data Flow Architecture

[Dashboard] → [Board View] → [List Components] → [Card Components]
     ↓              ↓              ↓               ↓
[Mock Service] ← [State Updates] ← [User Actions] ← [Modal Editing]

Performance Considerations

  • Minimal Re-renders: Careful state management to avoid unnecessary updates
  • Efficient Drag Operations: Proper event handling and state batching
  • Memory Management: Clean component cleanup and event listener removal

Mock Service Layer

Rather than jumping straight into a backend, we created a comprehensive mock service that simulated a real API:

class MockService {
  private boards: Board[] = [/* sample data */];

  getBoards(): Board[] { return this.boards; }
  getBoard(id: number): Board | undefined { /* implementation */ }
  updateBoard(board: Board): void { /* implementation */ }
  getUsers(): User[] { /* implementation */ }
  getLabels(): Label[] { /* implementation */ }
}

This approach proved invaluable for rapid prototyping and testing.

Building Reusable Components

We created three core components that would power our application:

  • Modal: A flexible, accessible modal with backdrop clicking and keyboard navigation
  • Card: A complex component handling card display, editing, and drag operations
  • List: Managing card containers with drag-and-drop zones

Implementing Card Editing

The card editing modal became quite sophisticated, handling:

  • Form Management: Title, description, due dates, user assignment
  • Comments System: Add and display comments with timestamps
  • Label Management: Toggle labels with color-coded display
  • Validation: Ensure required fields are filled
<Modal bind:isOpen={isEditModalOpen} onClose={() => isEditModalOpen = false}>
  <h2>Edit Card</h2>
  <form onsubmit={handleSave}>
    <input bind:value={editedCard.title} placeholder="Card title" required />
    <textarea bind:value={editedCard.description} placeholder="Card description"></textarea>

    <!-- Due date selection -->
    <input type="datetime-local" bind:value={dueDateInput} />

    <!-- User assignment -->
    <select bind:value={selectedUserId}>
      <option value="">Unassigned</option>
      {#each users as user}
        <option value={user.id}>{user.username}</option>
      {/each}
    </select>

    <!-- Label management -->
    {#each availableLabels as label}
      <label>
        <input 
          type="checkbox" 
          checked={editedCard.labels.some(l => l.id === label.id)}
          onchange={(e) => toggleLabel(label, e.target.checked)}
        />
        <span class="label" style="background-color: {label.color}">{label.name}</span>
      </label>
    {/each}
  </form>
</Modal>

Overdue Card Detection

We implemented visual indicators for overdue cards using css classes and the class provided by Svelte

function isOverdue(dueDate: Date | undefined): boolean {
  if (!dueDate) return false;
  return dueDate < new Date();
}
<div 
  class="card"
  class:overdue={isOverdue(card.dueDate)}
  data-testid="card"
>
  <!-- Card content -->
</div>

.card.overdue {
  border-left: 4px solid #e74c3c;
  background-color: #fdf2f2;
}

Comprehensive Test Coverage

One of the most valuable aspects of this project was implementing a robust testing strategy. From the very beginning I asked Copilot to write tests at each step so that we did not miss anything. This also helped us spot any issues as all tests were run to validate we did not just break something.
The AI generated unit tests for things like the data models, integration tests for things like the drag & drop interaction, and even End to End tests with Playwright.

At the end we achieved 34 passing tests


Phase 2: Challenges

Challenge 1: The Runes Mode Challenge

One of our first major challenges was properly implementing Svelte 5’s new runes mode. The syntax and patterns were significantly different from Svelte 4, and AI would use some odd syntax that did not match either 4 or 5, causing compilation errors.

Solution: The main solution was to switch the LLM model from GPT-4.1 to Claude Sonnet 4, which seems to do a little better with Svelte 5 syntax, and coding structure in general

<script lang="ts">
  // Old Svelte 4 way
  export let card;
  let isOpen = false;

  // New Svelte 5 runes way
  let { card = $bindable(), onCardUpdate } = $props<{
    card: Card;
    onCardUpdate: (card: Card) => void;
  }>();

  let isOpen = $state(false);
</script>

Challenge 2: Runes Mode Continues

One of the trickiest aspects was learning how Svelte 5’s reactivity works differently from previous versions:

Problem: Binding to array items in #each blocks doesn’t work the same way:

<!-- This doesn't work in Svelte 5 runes mode -->
{#each board.lists as list}
  <ListComponent bind:list />
{/each}

Solution: We had to manage state updates explicitly:

{#each board.lists as list}
  <ListComponent 
    {list}
    onCardUpdate={handleCardUpdate}
    onCardMove={handleCardMove}
  />
{/each}

Challenge 3: The Drag-and-Drop Implementation Challenge

Our first implementation of drag-and-drop seemed straightforward:

function handleDragStart(event) {
  event.dataTransfer.setData('application/json', JSON.stringify(card));
}

Problem: This failed spectacularly when cards had Date objects, because JSON.stringify() converts dates to strings, and JSON.parse() doesn’t convert them back to Date objects.

The conversation with Copilot during debugging was particularly revealing:

Me: "The drag and drop isn't working between lists. Cards seem to lose their date information."

Copilot: "The issue is likely with Date serialization in the drag data transfer. Let me help you implement proper Date handling..."

Solution: We implemented a two-step serialization process to ensure proper date serialization

Key Insight: Working with Copilot helped identify that the HTML5 drag-and-drop API only supports string data transfer, requiring explicit serialization strategies for complex objects.

Challenge 4: Complex Component Communication

Problem: Initially unclear how events should flow between Card, List, and Board components
Solution: Created a detailed sequence diagram showing the exact event flow
Mermaid Impact: The sequence diagram became the implementation blueprint:

sequenceDiagram
    participant Card
    participant List  
    participant Board
    participant Service

    Card->>List: dragstart (serialized data)
    List->>List: show drop zone
    Card->>List: drop event
    List->>Board: onCardMove(card, targetId, position)
    Board->>Board: update state immutably
    Board->>Service: persist changes
    Service->>Board: confirm update
    Board->>List: re-render with new state

This diagram eliminated guesswork and provided a clear implementation path.

Challenge 5: State Management with Immutable Updates

Problem: Reactive updates not triggering properly
Solution: Implemented immutable update patterns with proper state propagation

Challenge 6: Testing Svelte 5 Components

Problem: Testing libraries not fully compatible with Svelte 5
Solution: Focused on integration tests and simplified component tests

Challenge 7: E2E Test Reliability

Problem: Browser tests flaky due to timing issues
Solution: Added proper test IDs and wait conditions

Phase 3: Lessons Learned

1. AI-Assisted Development is Conversational

Working with Copilot in this conversational manner felt more like pair programming than using a traditional tool. The back-and-forth led to better architectural decisions and helped identify issues early.

2. Visual Communication Amplifies AI Effectiveness

Mermaid diagrams transformed the quality of AI assistance. Instead of lengthy text descriptions that could be misinterpreted, visual diagrams provided unambiguous context. A single sequence diagram was worth hundreds of words of explanation.

Before Mermaid: “Implement drag and drop between lists”
Result: Generic drag-and-drop code that missed edge cases

After Mermaid: [Sequence diagram showing exact component interactions]
Result: Precise implementation that handled data serialization, state updates, and persistence correctly on the first try

3. Modern Frameworks Require Learning Investment

Even with AI assistance, understanding Svelte 5’s new patterns required dedicated learning time. The AI helped explain concepts but couldn’t replace fundamental understanding.

4. Diagrams Catch Design Issues Early

Multiple times, creating Mermaid diagrams revealed potential problems before coding:

  • The class diagram showed that using full User objects (vs IDs) would complicate serialization
  • The sequence diagram revealed the Date serialization issue
  • The flowchart identified missing error handling paths

5. Testing Pays Dividends Immediately

Implementing comprehensive tests early caught numerous edge cases and made refactoring much safer. The drag-and-drop functionality would have been much harder to debug without proper test coverage.

6. Incremental Development Works Well with AI

Building features incrementally allowed for course correction and let the AI provide more targeted assistance at each step.

What’s Next

In the upcoming posts in this series, we’ll dive deeper into:

  • Part 2: Adding persistence and identity management layers
  • Part 3: Advanced UI formatting, drag-and-drop patterns and animations
  • Part 4: Adding real-time collaboration features
  • Part 5: Performance optimization and scaling considerations
  • Part 6: Deployment strategies and production considerations

.


Have questions about any of the implementation details? Want to see specific aspects covered in more depth? Leave a comment below or reach out on [social media]. The next post will dive deep into adding persistence layers and identity management, guided by comprehensive system architecture diagrams.

Leave a Reply

Discover more from Ramblings in time and measure

Subscribe now to keep reading and get access to the full archive.

Continue reading