Skip to content

Latest commit

 

History

History
379 lines (305 loc) · 11.8 KB

File metadata and controls

379 lines (305 loc) · 11.8 KB

CALM VSCode Extension - AI Assistant Guide

This guide helps AI assistants work efficiently with the CALM VSCode extension codebase.

Tech Stack

  • Language: TypeScript 5.8+
  • Framework: VSCode Extension API 1.88+
  • State Management: Zustand (Redux-like store)
  • Build Tool: tsup (esbuild-based)
  • Test Framework: Vitest
  • Architecture Pattern: MVVM + Hexagonal + Mediator
  • UI Components: Native VSCode API (TreeView, Webview, Commands)

Key Commands

IMPORTANT: Always run npm commands from the repository root using workspaces, not from within this package directory.

# Development (from repository root)
npm run build --workspace calm-plugins/vscode          # Build extension
npm run watch --workspace calm-plugins/vscode          # Watch mode (no auto-reload in VSCode)
npm test --workspace calm-plugins/vscode               # Run Vitest tests
npm run lint --workspace calm-plugins/vscode           # ESLint check
npm run lint-fix --workspace calm-plugins/vscode       # Auto-fix linting issues
npm run package --workspace calm-plugins/vscode        # Create .vsix package for distribution

# Testing Extension in VSCode
# 1. Open the repository root in VSCode (File → Open Folder)
# 2. Use the "calm-plugin: watch" task or run: npm run watch --workspace calm-plugins/vscode
# 3. Press F5 (or Run → Start Debugging) to launch Extension Development Host
# 4. In the new Extension Development Host window, open a CALM JSON file to activate extension

Architecture Overview

🏗️ Three-Layer Architecture

MVVM (Model-View-ViewModel)

View (VSCode UI) <--> ViewModel (Framework-free) <--> Model (Zustand Store)

Hexagonal Architecture (Ports & Adapters)

src/core/
├── ports/           # Interfaces (dependency inversion)
├── services/        # Core business logic
└── mediators/       # Cross-cutting orchestration

Mediator Pattern

  • Coordinates between services without tight coupling
  • Examples: RefreshService, SelectionService, WatchService

Directory Structure

src/
├── extension.ts                    # VSCode entry point (activate/deactivate)
├── calm-extension-controller.ts    # Main orchestrator (wires dependencies)
├── application-store.ts            # Zustand global state
│
├── core/                           # Framework-free business logic
│   ├── ports/                      # Interfaces for dependency inversion
│   ├── services/                   # Core services (refresh, selection, watch, navigation)
│   ├── mediators/                  # Cross-cutting coordinators
│   └── emitter.ts                 # Event system (framework-free)
│
├── features/                       # Feature modules
│   ├── tree-view/                 # Sidebar tree navigation
│   │   └── view-model/           # MVVM presentation logic
│   ├── editor/                    # Editor integration (hover, CodeLens)
│   └── preview/                   # Webview preview panel
│       ├── docify-tab/           # Documentation generation
│       ├── model-tab/            # Model data display
│       └── template-tab/         # Template processing & live mode
│
├── commands/                       # VSCode command handlers
├── models/                         # CALM model parsing & indexing
└── cli/                           # CLI integration (deprecated, being replaced)

Key Design Principles

  1. Framework Isolation: ViewModels have NO vscode imports
  2. Dependency Inversion: Core depends on ports, not VSCode
  3. Single Store: All state in application-store.ts
  4. Mediator Coordination: Services don't call each other directly

Key Concepts

State Management (Zustand)

Store Location: src/application-store.ts

interface ApplicationStore {
    calmModel: CalmModel | null;
    selectedNode: string | null;
    isLoading: boolean;
    // ... other state
    
    // Actions
    setCalmModel: (model: CalmModel) => void;
    setSelectedNode: (nodeId: string | null) => void;
    // ... other actions
}

Usage:

import { useApplicationStore } from './application-store';

// In ViewModels or components
const model = useApplicationStore(state => state.calmModel);
const setModel = useApplicationStore(state => state.setCalmModel);

MVVM Pattern

ViewModel Example:

// src/features/tree-view/view-model/tree-view-model.ts
export class TreeViewModel {
    // NO vscode imports!
    constructor(
        private store: ApplicationStore,
        private emitter: Emitter
    ) {}
    
    getTreeData(): TreeNode[] {
        const model = this.store.getState().calmModel;
        return this.transformToTree(model);
    }
}

View (VSCode Specific):

// src/features/tree-view/tree-data-provider.ts
export class CalmTreeDataProvider implements vscode.TreeDataProvider {
    constructor(private viewModel: TreeViewModel) {}
    
    getChildren(element?: TreeItem): TreeItem[] {
        return this.viewModel.getTreeData().map(toTreeItem);
    }
}

Mediator Pattern

Mediators coordinate between services:

// src/core/mediators/store-reaction-mediator.ts
export class StoreReactionMediator {
    constructor(
        private store: ApplicationStore,
        private refreshService: RefreshService,
        private selectionService: SelectionService
    ) {
        // React to store changes
        this.store.subscribe(
            state => state.calmModel,
            model => this.refreshService.refreshAll()
        );
    }
}

Navigation Service

  • Purpose: Handles navigation between CALM documents via detailed-architecture references.
  • Key Logic: Uses DocumentLoader from @finos/calm-shared to resolve URLs/relative paths to local files based on calm.urlMapping.
  • Integration: Called by SelectionService when a node with details is clicked.

Features

Tree View

  • Purpose: Sidebar navigation of CALM model structure
  • Location: src/features/tree-view/
  • Key Files:
    • tree-data-provider.ts - VSCode TreeDataProvider
    • view-model/tree-view-model.ts - Business logic (framework-free)

Webview Preview

  • Purpose: Multi-tab preview (Model, Docify, Template)
  • Location: src/features/preview/
  • Tabs:
    • Model Tab: Display CALM JSON in formatted view
    • Docify Tab: Generate documentation websites
    • Template Tab: Live template processing with Handlebars

Editor Integration

  • Hover Providers: Show info on hover
  • CodeLens: Inline commands in editor
  • Location: src/features/editor/

Testing

Test Structure

  • *.spec.ts - Unit tests alongside source
  • test-architectures/ - Sample CALM files for testing

Running Tests

# From repository root (preferred)
npm test --workspace calm-plugins/vscode              # All tests
npm test --workspace calm-plugins/vscode -- --watch   # Watch mode
npm test --workspace calm-plugins/vscode -- <file>    # Specific test file

Testing ViewModels

ViewModels are framework-free, so they're easy to unit test:

import { TreeViewModel } from './tree-view-model';

describe('TreeViewModel', () => {
    it('transforms model to tree', () => {
        const store = createMockStore();
        const vm = new TreeViewModel(store, mockEmitter);
        
        const tree = vm.getTreeData();
        expect(tree).toHaveLength(3);
    });
});

Common Tasks

Adding a New Command

  1. Register in package.json:
{
    "contributes": {
        "commands": [{
            "command": "calm.myCommand",
            "title": "CALM: My Command"
        }]
    }
}
  1. Create handler in src/commands/:
export function registerMyCommand(context: vscode.ExtensionContext) {
    context.subscriptions.push(
        vscode.commands.registerCommand('calm.myCommand', () => {
            // Implementation
        })
    );
}
  1. Register in src/extension.ts:
import { registerMyCommand } from './commands/my-command';

export function activate(context: vscode.ExtensionContext) {
    registerMyCommand(context);
}

Adding State to Store

  1. Update src/application-store.ts:
interface ApplicationStore {
    myNewState: string;
    setMyNewState: (value: string) => void;
}

export const useApplicationStore = create<ApplicationStore>((set) => ({
    myNewState: '',
    setMyNewState: (value) => set({ myNewState: value }),
}));

Creating a New ViewModel

  1. Create in appropriate feature folder (framework-free!):
// src/features/my-feature/view-model/my-view-model.ts
export class MyViewModel {
    constructor(
        private store: ApplicationStore,
        private emitter: Emitter
    ) {}
    
    // Methods that work with store, NO vscode imports
}
  1. Create VSCode View:
// src/features/my-feature/my-view.ts
import * as vscode from 'vscode';
import { MyViewModel } from './view-model/my-view-model';

export class MyView {
    constructor(private viewModel: MyViewModel) {}
    
    // VSCode-specific implementation
}

Adding a Webview Tab

  1. Create tab component in src/features/preview/my-tab/
  2. Update src/features/preview/preview-panel.ts to include new tab
  3. Add HTML template if needed

Dependencies on Other Packages

vscode-plugin depends on:
  ├── calm-models (via ../../calm-models)
  ├── calm-widgets (via ../../calm-widgets)
  └── shared (via ../../shared)

Important: Build dependencies first:

# From repository root (always use workspaces)
npm run build:shared    # Builds models, widgets, shared
# Or build individual packages:
npm run build --workspace calm-models
npm run build --workspace calm-widgets
npm run build --workspace shared

Common Pitfalls

  1. Importing vscode in ViewModels: ViewModels must be framework-free!
  2. Direct Service Calls: Use mediators for cross-cutting concerns
  3. Store Mutations: Always use store actions, never mutate directly
  4. Extension Not Activating: Check activationEvents in package.json
  5. Webview Not Updating: Remember to postMessage from webview to extension
  6. toCanonicalSchema adds undefined values: When using toCanonicalSchema() from calm-models, ALL optional properties are added with undefined values. Code checking for property existence must check for truthy values, not just key existence. See calm-widgets/AGENTS.md for details.
  7. URL Mapping: To test multi-document navigation, you likely need to configure calm.urlMapping in .vscode/settings.json to point to a mapping file (e.g. calm-mapping.json) in the workspace root.

Debugging

Debug Extension

  1. Open this folder in VSCode
  2. Set breakpoints in TypeScript source
  3. Press F5 (or Run → Start Debugging)
  4. Extension Development Host window opens
  5. Open a CALM file to trigger activation

Debug Webview

  1. In Extension Development Host: Ctrl+Shift+P
  2. Run: "Developer: Open Webview Developer Tools"
  3. Use browser devtools to debug webview

Configuration Files

  • package.json - Extension manifest, commands, views
  • tsconfig.json - TypeScript compiler options
  • tsup.config.ts - Build configuration
  • vitest.config.mts - Test configuration
  • eslint.config.mjs - Linting rules

Publishing

# From repository root
npm run package --workspace calm-plugins/vscode        # Creates .vsix file
# Then publish to VS Code Marketplace via GitHub Actions

Useful Links