Thank you for your interest in contributing to Brainfile!
- Node.js 18+
- npm 9+
- VS Code (for testing the extension)
# Clone and install
git clone https://github.com/brainfile/vscode.git
cd vscode
npm install
# Run tests
npm test
# Compile
npm run compile
# Package extension
npx vsce package- Make changes to source files in
src/ - Run
npm testto verify tests pass - Run
npm run compileto check TypeScript - Press F5 in VS Code to launch Extension Development Host
The extension follows a modular architecture with clear separation of concerns:
src/
├── extension.ts # Extension entry point
├── boardViewProvider.ts # Main webview provider (orchestrator)
└── board/ # Modular components
├── index.ts # Re-exports all modules
├── types.ts # Type definitions and guards
├── messages.ts # Message constants and validation
├── orchestrator.ts # File I/O coordination
├── html/ # HTML generation
│ ├── utils.ts # escapeHtml, generateNonce, etc.
│ ├── styles.ts # Priority CSS generation
│ ├── stats.ts # Stats panel HTML
│ └── error.ts # Error page HTML
├── agents/ # AI integration
│ └── promptBuilder.ts
├── data/ # Pure board operations
│ ├── archive.ts # Archive management
│ ├── taskId.ts # Task ID generation/lookup
│ └── boardOperations.ts # Immutable board mutations
└── handlers/ # Message handling
└── messageRouter.ts
- Pure Functions: Board operations in
data/are pure functions that return new objects instead of mutating state - VS Code Agnostic: Modules under
board/have no VS Code dependencies, making them testable - Type Safety: Extensive use of TypeScript type guards and discriminated unions
- Immutability: All board mutations return new board objects
Pure functions for board mutations:
updateTask()- Update task title/descriptiondeleteTask()- Remove task from columnmoveTask()- Move task between columnsaddTask()- Add new task to columntoggleSubtask()- Toggle subtask completion
Routes webview messages to appropriate handlers:
- Returns
{ type: "board-updated", board }for mutations - Returns
{ type: "external-action", action }for VS Code API calls - Returns
{ type: "no-op" }for unknown messages
Coordinates file I/O and workflows:
readBoardFromDisk()/writeBoardToDisk()processMessage()- Route and process messageslintBrainfile()- Validate content
Tests use Jest with ts-jest. Run with:
npm test # Run all tests
npm test -- --watch # Watch mode
npm test -- path/to/test # Run specific testTests live in __tests__/ directories alongside source:
src/board/
├── data/
│ ├── boardOperations.ts
│ └── __tests__/
│ └── boardOperations.test.ts
Example test:
import { updateTask } from "../boardOperations";
describe("updateTask", () => {
it("updates task title and description", () => {
const board = createTestBoard();
const result = updateTask(board, "todo", "task-1", "New Title", "New Desc");
expect(result.success).toBe(true);
expect(result.board?.columns[0].tasks[0].title).toBe("New Title");
});
it("does not mutate original board", () => {
const board = createTestBoard();
const original = JSON.stringify(board);
updateTask(board, "todo", "task-1", "New", "New");
expect(JSON.stringify(board)).toBe(original);
});
});The VS Code API is mocked in src/__tests__/__mocks__/vscode.ts. Jest automatically uses this mock.
- Run tests: Ensure
npm testpasses - Run compile: Ensure
npm run compilesucceeds - Add tests: New features should include tests
- Update changelog: Add entry to CHANGELOG.md
- Keep PRs focused: One feature/fix per PR
- TypeScript strict mode enabled
- Prefer
constoverlet - Use descriptive variable names
- Add JSDoc comments for public functions
- No
anytypes without justification
The webview uses a custom CSS kit for consistent styling. Files are in webview-ui/src/styles/:
styles/
├── vars.css # Semantic CSS variables
├── utilities.css # Utility classes
└── main.css # Component styles (imports vars + utilities)
Maps VS Code theme variables to semantic names:
/* Colors */
--c-text /* Primary text (--vscode-editor-foreground) */
--c-text-muted /* Secondary text (--vscode-descriptionForeground) */
--c-bg /* Background (--vscode-sideBar-background) */
--c-bg-elevated /* Section headers (--vscode-sideBarSectionHeader-background) */
--c-bg-hover /* Hover state (--vscode-list-hoverBackground) */
--c-border /* Borders (--vscode-panel-border) */
/* Typography */
--text-2xs: 10px /* Tiny (metadata, IDs) */
--text-xs: 11px /* Small (descriptions) */
--text-sm: 12px /* Base (body text) */
--text-base: 13px /* Medium (titles) */
--font-mono /* JetBrains Mono */
/* Spacing */
--space-1 through --space-10 /* 2px, 4px, 6px, 8px, 10px, 12px, 16px, 20px */
/* Other */
--radius, --radius-md, --radius-lg /* 4px, 6px, 8px */
--transition /* 0.15s */
--opacity-muted, --opacity-high /* 0.6, 0.9 */Common patterns as composable classes:
<!-- Layout -->
<div class="flex items-center gap-4">
<div class="flex-col gap-2">
<!-- Typography -->
<span class="text-xs text-muted font-mono">
<!-- Spacing -->
<div class="p-8 mt-4 ml-auto">
<!-- Components -->
<button class="icon-btn icon-btn-sm"> <!-- Transparent icon button -->
<header class="section-header"> <!-- Collapsible panel header -->
<span class="section-title"> <!-- Uppercase label -->
<span class="count"> <!-- Mono count badge -->
<li class="list-item hover-reveal"> <!-- Hoverable row -->
<div class="empty-state"> <!-- Empty state message -->
<span class="id-badge"> <!-- Task/rule ID -->
<button class="file-link"> <!-- Clickable file path -->
<!-- Reveal on hover -->
<div class="hover-reveal">
<button class="reveal-on-hover"> <!-- Hidden until parent hover -->
</div>- Prefer utilities in templates over scoped CSS
- Use kit variables (
var(--c-text)) not raw VS Code vars - Only add scoped CSS for component-specific styles
- Follow existing patterns - check
RulesPanel.vueandArchivePanel.vue
Example component:
<template>
<div>
<header class="section-header hover-reveal">
<span class="section-title">Items</span>
<span class="count">{{ items.length }}</span>
<button class="icon-btn ml-auto reveal-on-hover">
<Plus :size="14" />
</button>
</header>
<ul class="list-none">
<li v-for="item in items" class="list-item">
<span class="id-badge">#{{ item.id }}</span>
<span class="item-text">{{ item.text }}</span>
</li>
</ul>
<div v-if="!items.length" class="empty-state">No items</div>
</div>
</template>
<style scoped>
/* Only component-specific styles */
.item-text {
flex: 1;
font-size: var(--text-sm);
color: var(--c-text);
}
</style>When adding reusable patterns:
- Check if it exists in
utilities.cssfirst - If truly reusable (3+ uses), add to
utilities.css - Use kit naming conventions (
text-*,bg-*,gap-*) - Document in this file
- Open an issue for bugs or feature requests
- Start a discussion for questions