Claude Code Plugins

Community-maintained marketplace

Feedback
5
0

Use when working with Miro SDK v2 operations, board manipulation, creating shapes, text, frames, or syncing data between Miro and database. Helps implement features for master board, project rows, and client reports.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name miro-sdk-integration
description Use when working with Miro SDK v2 operations, board manipulation, creating shapes, text, frames, or syncing data between Miro and database. Helps implement features for master board, project rows, and client reports.
allowed-tools Read, Grep, Glob, Edit, Bash, mcp__plugin_context7_context7__resolve-library-id, mcp__plugin_context7_context7__query-docs

Miro SDK v2 Integration Skill

Overview

This skill guides implementation of Miro SDK v2 features for the project management system. It ensures proper use of existing services, adherence to board architecture patterns, and safe database synchronization.


Critical Services (Always Read First)

Location: /src/features/boards/services/

Core Services

  1. miroSdkService.ts (106KB - Primary Workhorse)

    • 50+ methods for board operations
    • Shape creation, text manipulation, frame organization
    • Safe operations that return null on error instead of throwing
    • Methods: createFrame(), createStickyNote(), createText(), createShape(), etc.
  2. masterBoardService.ts

    • Manages "Master Board" - consolidated view of all clients with projects
    • Creates vertical frames with mini-kanbans
    • Updates when projects change status
    • Coordinates with projectRowService
  3. projectRowService.ts

    • Manages individual project timeline rows
    • Status columns for deliverables (pending, wip, review, approved)
    • Real-time updates from database changes
    • Tracks Miro item IDs in miroItemRegistry
  4. projectSyncOrchestrator.ts

    • Orchestrates bidirectional sync: Database ↔ Miro
    • Source of truth is always Supabase
    • Handles create, update, delete operations
    • Prevents infinite sync loops
  5. brandWorkspaceService.ts

    • Creates project workspace frames
    • Sections: Brief, Process, Deliverables
    • Project-specific collaboration area
  6. miroReportService.ts & miroClientReportService.ts

    • Generate visual reports on Miro boards
    • Charts, metrics, status summaries
    • Client-facing report generation
  7. miroItemRegistry.ts

    • Tracks Miro item IDs ↔ Database entity relationships
    • Critical for identifying what items represent
    • Prevents duplicate creation

Safe SDK Access Pattern

ALWAYS use miroAdapter from @shared/lib/miroAdapter:

import { miroAdapter } from '@shared/lib/miroAdapter';

// ✅ Check availability before operations
if (miroAdapter.isAvailable()) {
  await miroAdapter.showInfo('Message to user');
}

// ✅ Safe operations return null instead of throwing
const boardInfo = await miroAdapter.getBoardInfo();
if (!boardInfo) {
  logger.warn('Not in Miro context');
  return;
}

// ✅ Get board ID safely
const boardId = await miroAdapter.getBoardId();

// ❌ NEVER access window.miro directly
// window.miro.board.get() // DON'T DO THIS

Board Architecture

Master Board Layout

┌─────────────────────────────────────────┐
│          MASTER BOARD                    │
├─────────────────────────────────────────┤
│  Client A         Client B               │
│  ┌─────────┐     ┌─────────┐            │
│  │ Project │     │ Project │            │
│  │  Mini   │     │  Mini   │            │
│  │ Kanban  │     │ Kanban  │            │
│  └─────────┘     └─────────┘            │
└─────────────────────────────────────────┘

Managed by: masterBoardService.ts

Project Row Layout

┌───────────────────────────────────────────────┐
│  PROJECT: "Brand Identity for Client X"       │
├──────────┬──────────┬──────────┬─────────────┤
│ Pending  │   WIP    │  Review  │  Approved   │
├──────────┼──────────┼──────────┼─────────────┤
│ [Logo]   │ [Colors] │ [Fonts]  │ [Guidelines]│
└──────────┴──────────┴──────────┴─────────────┘

Managed by: projectRowService.ts


Constants & Configuration

Location: /src/features/boards/services/constants/

// Layout constants
import { LAYOUT } from '@shared/config/boardConfig';

LAYOUT.frameStartX      // Starting X position for frames
LAYOUT.frameStartY      // Starting Y position for frames
LAYOUT.frameWidth       // Standard frame width
LAYOUT.frameHeight      // Standard frame height
LAYOUT.stickyNoteSize   // Standard sticky note dimensions

// Colors (Use explicit hex - CSS vars don't work in Miro iframe!)
import { COLORS } from '@shared/config/boardConfig';

COLORS.primary          // #050038
COLORS.accent           // #2563EB
COLORS.success          // #10B981
COLORS.warning          // #F59E0B
COLORS.error            // #EF4444

// Briefing templates
import { BRIEFING_TEMPLATE } from '@shared/config/boardConfig';

Database Synchronization Pattern

Critical Rule: Database is Source of Truth

// ✅ CORRECT: Update database first, then sync to Miro
async function updateProjectStatus(projectId: string, status: ProjectStatus) {
  // 1. Update Supabase
  const { data, error } = await supabase
    .from('projects')
    .update({ status })
    .eq('id', projectId)
    .select()
    .single();

  if (error) throw error;

  // 2. Sync to Miro board
  await projectSyncOrchestrator.syncProjectToBoard(data);
}

// ❌ WRONG: Don't update Miro first
async function updateProjectStatusWrong(projectId: string, status: ProjectStatus) {
  // DON'T DO THIS
  await miroSdkService.updateFrameTitle(frameId, newTitle);
  await supabase.from('projects').update({ status }); // Out of sync!
}

Sync Orchestrator Usage

import { projectSyncOrchestrator } from '@features/boards/services/projectSyncOrchestrator';

// Sync single project
await projectSyncOrchestrator.syncProjectToBoard(project);

// Sync all projects on board
await projectSyncOrchestrator.syncAllProjectsToBoard(boardId);

// Handle deletions
await projectSyncOrchestrator.removeProjectFromBoard(projectId);

Common Implementation Patterns

Pattern 1: Creating Board Objects

import { miroSdkService } from '@features/boards/services/miroSdkService';
import { LAYOUT, COLORS } from '@shared/config/boardConfig';

// Create frame
const frameId = await miroSdkService.createFrame({
  title: 'Project Workspace',
  x: LAYOUT.frameStartX,
  y: LAYOUT.frameStartY,
  width: LAYOUT.frameWidth,
  height: LAYOUT.frameHeight,
  style: {
    fillColor: COLORS.primary, // Use hex, not CSS variable!
  }
});

// Create sticky note
const stickyId = await miroSdkService.createStickyNote({
  content: 'Logo design',
  x: 100,
  y: 200,
  style: {
    fillColor: COLORS.warning,
  }
});

// Create text
const textId = await miroSdkService.createText({
  content: 'Project Brief',
  x: 300,
  y: 100,
  style: {
    fontSize: 24,
    color: COLORS.primary,
  }
});

Pattern 2: Updating Board Objects

// Update frame title
await miroSdkService.updateFrame(frameId, {
  title: 'Updated Title',
});

// Move sticky note
await miroSdkService.updateStickyNote(stickyId, {
  x: 150,
  y: 250,
});

// Update text content
await miroSdkService.updateText(textId, {
  content: 'Updated Brief',
});

Pattern 3: Querying Board Objects

// Get all frames on board
const frames = await miroSdkService.getAllFrames();

// Get specific item by ID
const item = await miroSdkService.getItemById(itemId);

// Get items by type
const stickies = await miroSdkService.getItemsByType('sticky_note');

Pattern 4: Using Miro Item Registry

import { miroItemRegistry } from '@features/boards/services/miroItemRegistry';

// Track new Miro item
miroItemRegistry.register({
  miroItemId: frameId,
  entityType: 'project',
  entityId: projectId,
});

// Lookup database entity from Miro item
const entity = miroItemRegistry.getEntity(miroItemId);
if (entity?.entityType === 'project') {
  const projectId = entity.entityId;
}

// Remove tracking
miroItemRegistry.unregister(miroItemId);

Handling Board Events

Webhook Processing

Miro board changes trigger webhooks processed by Edge Function:

File: supabase/functions/miro-webhook/index.ts

// Webhook receives events: item:created, item:updated, item:deleted
// Process events and sync back to database if needed

// Example event types:
{
  type: 'item:created',
  data: {
    id: 'miro-item-id',
    type: 'sticky_note',
    // ... other properties
  }
}

Important: Prevent infinite loops by checking if change originated from sync operation.


CSS Styling for Miro Context

Critical: CSS Variables Don't Work in Miro Iframe!

// ❌ WRONG: CSS variables won't resolve
style: {
  fillColor: 'var(--color-primary)', // Won't work!
}

// ✅ CORRECT: Use explicit hex colors
style: {
  fillColor: '#050038', // Works!
}

// ✅ BETTER: Use constants
import { COLORS } from '@shared/config/boardConfig';
style: {
  fillColor: COLORS.primary, // Resolves to '#050038'
}

Entry Points & HTML Files

The app has 4 HTML entry points (configured in vite.config.ts):

  1. index.html - Main panel entry (sidebar panel)
  2. app.html - Full app interface (modal or standalone)
  3. board-modal.html - Modal dialogs opened from board UI
  4. admin-modal.html - Admin-specific modal dialogs

When adding Miro features, ensure proper entry point is configured.


Rate Limiting & Performance

Miro API Limits

  • Max 25 API calls per second
  • Use batching for bulk operations
  • Implement retry logic with exponential backoff
// ✅ Batch operations
const items = await Promise.all(
  itemIds.map(id => miroSdkService.getItemById(id))
);

// ❌ Sequential operations in loop (slow + may hit rate limit)
for (const id of itemIds) {
  const item = await miroSdkService.getItemById(id); // DON'T
}

Testing in Miro Context

Availability Checks

import { miroAdapter } from '@shared/lib/miroAdapter';

// Gate Miro-specific code
if (!miroAdapter.isAvailable()) {
  logger.warn('Not in Miro context, skipping board operation');
  return null;
}

// Proceed with Miro operations
const board = await miroAdapter.board.get();

Testing Entry Points

Test all 4 HTML entry points:

  • Panel view (index.html)
  • Full app (app.html)
  • Board modal (board-modal.html)
  • Admin modal (admin-modal.html)

Browser Console

Check for SDK errors in browser console:

  • Miro SDK initialization errors
  • API rate limit warnings
  • Item not found errors

Common Pitfalls

❌ Don't Trust Miro Item Timestamps

Miro doesn't guarantee accurate timestamps. Use database timestamps as source of truth.

❌ Don't Assume Items Exist

Always validate before manipulating:

const item = await miroSdkService.getItemById(itemId);
if (!item) {
  logger.error('Item not found', { itemId });
  return;
}
// Now safe to update
await miroSdkService.updateItem(item.id, { ... });

❌ Don't Create Duplicate Items

Check miroItemRegistry before creating:

const existing = miroItemRegistry.getByEntity('project', projectId);
if (existing) {
  // Update existing item
  await miroSdkService.updateFrame(existing.miroItemId, { ... });
} else {
  // Create new item
  const frameId = await miroSdkService.createFrame({ ... });
  miroItemRegistry.register({
    miroItemId: frameId,
    entityType: 'project',
    entityId: projectId,
  });
}

Role-Based Access

RLS policies enforce access control:

  • Admin: Full access to all boards
  • Designer: Access to assigned projects only
  • Client: Access to own projects only

Always verify permissions before Miro operations that expose sensitive data.


Before Making Changes Checklist

  • Read relevant service in /src/features/boards/services/
  • Check if similar functionality already exists
  • Understand board layout from constants
  • Verify role-based access requirements
  • Plan database-first sync strategy
  • Use explicit hex colors, not CSS variables
  • Check miroItemRegistry for item tracking
  • Test with miroAdapter.isAvailable() guards
  • Test all 4 HTML entry points
  • Verify browser console for errors

Useful Context7 Queries

When implementing Miro features, query Context7 for:

"Miro SDK v2 create frame with custom style"
"Miro SDK v2 sticky note positioning best practices"
"Miro SDK v2 webhook event handling"
"Miro SDK v2 batch operations rate limits"
"Miro SDK v2 board selection and viewport"

Quick Reference

// Services
import { miroSdkService } from '@features/boards/services/miroSdkService';
import { masterBoardService } from '@features/boards/services/masterBoardService';
import { projectRowService } from '@features/boards/services/projectRowService';
import { projectSyncOrchestrator } from '@features/boards/services/projectSyncOrchestrator';
import { miroItemRegistry } from '@features/boards/services/miroItemRegistry';

// Safe SDK access
import { miroAdapter } from '@shared/lib/miroAdapter';

// Constants
import { LAYOUT, COLORS, BRIEFING_TEMPLATE } from '@shared/config/boardConfig';

// Logger
import { createLogger } from '@shared/lib/logger';
const logger = createLogger('MiroFeature');

Remember: Database is source of truth. Miro is a visual representation. Always sync from database to Miro, never the reverse.