Skip to content

Commit faba5cc

Browse files
oOclaude
andcommitted
feat: Text Management Tool with style fixes and duplicate removal (v0.29.0)
- Add manage_text tool for text operations and style management - Remove text creation from create_node tool to eliminate duplication - Fix style deletion trailing comma issues in Figma API operations - Fix font loading in all text operations to prevent "unloaded font" errors - Remove convert_to_vectors operation from manage_text (use boolean_operations instead) - Add version tracking to all get_plugin_status responses Designed with ❤️ by oO. Coded with ✨ by Claude Sonnet 4 Co-authored-by: Claude.AI <[email protected]>
1 parent e663de7 commit faba5cc

File tree

16 files changed

+1631
-502
lines changed

16 files changed

+1631
-502
lines changed

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.29.0] - 2025-06-24
9+
10+
### Added
11+
- **Text Management Tool**: New `manage_text` tool for text operations in Figma
12+
- Create text nodes with font loading and fallback system
13+
- Update existing text content and formatting
14+
- Character-level styling with mixed formatting support
15+
- Apply and create text styles for design system consistency
16+
- Hyperlink support for interactive text elements
17+
- Replaces previous typography functionality with streamlined interface
18+
19+
### Removed
20+
- **Text Creation from create_node**: Text creation removed from `create_node` tool to eliminate duplication
21+
- Users should use `manage_text` tool for all text operations
22+
- `create_node` now supports: rectangle, ellipse, frame (no longer text)
23+
- Removed text-related parameters: content, fontSize, fontFamily, fontStyle, textAlignHorizontal
24+
25+
### Fixed
26+
- **Style Management Reliability**: Fixed trailing comma issues in Figma API style operations
27+
- Style deletion now properly handles Figma's trailing comma style IDs
28+
- Text style application uses correct style ID format for setTextStyleIdAsync
29+
- Style response formatting removes trailing commas for consistent parameter usage
30+
- **Font Loading in Text Operations**: Added comprehensive font loading to prevent "unloaded font" errors
31+
- All text operations now load fonts before applying changes
32+
- Font loading applied to create, update, character styling, and style operations
33+
- **Plugin Status Versioning**: All get_plugin_status responses now include version information
34+
835
## [0.28.2] - 2025-06-21
936

1037
### Fixed

CLAUDE.md

Lines changed: 114 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,85 @@ Co-authored-by: Claude.AI <[email protected]>
5858
- Use descriptive variable and function names
5959
- Follow existing patterns for consistency
6060

61+
### Figma API Patterns
62+
**CRITICAL**: Always follow the correct Figma API patterns and handle known API quirks:
63+
64+
**Style ID Trailing Comma Issue:**
65+
```typescript
66+
// ✅ CRITICAL FIX: Figma's getLocalTextStyles() and similar methods return style IDs with trailing commas
67+
// Always clean style IDs before using them for lookups or operations
68+
const cleanStyleId = styleId.replace(/,$/, '');
69+
70+
// Apply this fix in:
71+
// - Style deletion: allStyles.find(s => s.id.replace(/,$/, '') === params.styleId)
72+
// - Text style application: await text.setTextStyleIdAsync(cleanStyleId)
73+
// - Style retrieval: style = allStyles.find(s => s.id.replace(/,$/, '') === params.styleId)
74+
```
75+
76+
**Style Deletion Pattern:**
77+
```typescript
78+
// ✅ CORRECT: Find style from local collections, then remove
79+
const paintStyles = figma.getLocalPaintStyles();
80+
const textStyles = figma.getLocalTextStyles();
81+
const allStyles = [...paintStyles, ...textStyles, ...effectStyles, ...gridStyles];
82+
const style = allStyles.find(s => s.id.replace(/,$/, '') === params.styleId);
83+
style.remove();
84+
85+
// ❌ INCORRECT: figma.getStyleByIdAsync() doesn't exist in Plugin API
86+
// ❌ INCORRECT: Cannot delete styles directly by ID
87+
```
88+
89+
**Text Style Application Pattern:**
90+
```typescript
91+
// ✅ CORRECT: Clean style ID before application
92+
const cleanStyleId = params.textStyleId.replace(/,$/, '');
93+
await text.setTextStyleIdAsync(cleanStyleId);
94+
95+
// ❌ INCORRECT: Using style ID with trailing comma will fail silently
96+
// await text.setTextStyleIdAsync(params.textStyleId); // Will clear textStyleId to empty string
97+
```
98+
99+
**Key Principles:**
100+
- **ALWAYS** remove trailing commas from style IDs before any operations
101+
- Style operations require searching through local style collections, not direct API calls
102+
- Text style application must use cleaned style IDs to work properly
103+
- Handle style not found errors properly in try/catch blocks
104+
61105
### Implementation Pattern Guidelines
62106
**CRITICAL**: Before implementing any new feature, always examine existing implementations first to match established patterns.
63107

108+
#### MCP Tool Parameter Design Pattern
109+
**ALWAYS use flat parameter structures** - never nest objects for tool parameters:
110+
111+
**✅ CORRECT (Flat):**
112+
```json
113+
{
114+
"operation": "create_text_style",
115+
"nodeId": "123:456",
116+
"styleName": "Heading Large",
117+
"styleDescription": "Main heading style"
118+
}
119+
```
120+
121+
**❌ INCORRECT (Nested):**
122+
```json
123+
{
124+
"operation": "create_text_style",
125+
"nodeId": "123:456",
126+
"createTextStyle": {
127+
"name": "Heading Large",
128+
"description": "Main heading style"
129+
}
130+
}
131+
```
132+
133+
**Design Principles:**
134+
- All tool parameters must be at the root level of the parameter object
135+
- Use descriptive flat parameter names (e.g., `styleName`, `styleDescription`)
136+
- Never create nested configuration objects in tool interfaces
137+
- This ensures consistency across all MCP tools and simplifies parameter handling
138+
- Follow the pattern established by `manage_styles`, `manage_components`, and all existing tools
139+
64140
#### Handler Implementation Pattern
65141
When adding new handlers to the Figma plugin:
66142
1. **First**: Read existing handlers (e.g., `style-handler.ts`, `node-handler.ts`) to understand the pattern
@@ -86,29 +162,59 @@ export class NewHandler extends BaseHandler {
86162
```
87163
3. **Do NOT use**: `async handle(type: string, payload: any)` patterns
88164
4. **Always match**: The exact interface contracts used by existing handlers
165+
5. **Always use flat parameters**: Convert flat parameters to internal objects if needed for helper functions
89166

90167
### Documentation Guidelines
91168

92-
**IMPORTANT**: During the pre-release phase, documentation should be factual and concise about the current state of the tool, not changes or future plans.
169+
**CRITICAL PRE-RELEASE RULE**: During pre-release phase (major version < 1.0.0), ALL documentation must reflect the current state only. Do NOT document changes, history, or what was added/removed - that's only for CHANGELOG.md.
170+
171+
#### Pre-Release Documentation Rules
172+
- **Current State Only**: All documentation (README, DEVELOPMENT, EXAMPLES) describes what the tool does NOW
173+
- **No Change Documentation**: Never mention "new", "added", "updated", "removed", "replaced" in documentation files
174+
- **No Version History**: Don't reference previous versions or what changed between versions
175+
- **No Migration Notes**: Don't include upgrade instructions or breaking change notices
176+
- **Exception**: CHANGELOG.md is the ONLY file that documents changes commit-to-commit
93177

94178
#### Writing Style
95179
- **Avoid superlatives**: Do not use words like "comprehensive", "complete", "robust", "extensive", "advanced", "cutting-edge", "powerful", "seamless", etc.
96180
- **No test counting**: Do not mention specific test numbers in documentation as they change frequently and add no value to users
97181
- **Be direct**: Use simple, clear language without marketing fluff
98182
- **Focus on functionality**: Describe what tools do, not how amazing they are
183+
- **Present tense only**: Use present tense to describe current capabilities
99184

100-
#### Documentation Files
101-
- **README.md**: Main project documentation - should reflect current capabilities and setup instructions
102-
- **DEVELOPMENT.md**: Technical documentation for contributors - architecture, development workflow, testing
103-
- **EXAMPLES.md**: Usage examples and workflows - should demonstrate actual current functionality
104-
- **CHANGELOG.md**: Exception to the rule - tracks changes from one commit to the next, documents incremental changes within sessions
185+
#### Documentation Files (Pre-Release < 1.0.0)
186+
- **README.md**: Current project state - capabilities and setup instructions as they exist now
187+
- **DEVELOPMENT.md**: Current technical architecture - how the system works now for contributors
188+
- **EXAMPLES.md**: Current usage patterns - how to use the tools that exist now
189+
- **CHANGELOG.md**: ONLY exception - tracks changes from one commit to the next
105190

106191
#### Documentation Principles
107192
- Stay factual and concise about current state
108193
- Avoid hardcoded numbers that require frequent maintenance updates
109-
- Focus on "what it does now" not "what it will do"
110-
- Only CHANGELOG.md should document changes and progression
194+
- Focus on "what it does now" not "what it will do" or "what it used to do"
195+
- ONLY CHANGELOG.md documents changes and progression
111196
- Use clear, direct language without superlatives
197+
- Write as if this is the first version anyone has ever seen
198+
199+
#### Documentation Scope for Commits
200+
**CRITICAL**: Documentation should only cover changes since the last commit, not intermediate development steps.
201+
202+
**Process for documenting changes:**
203+
1. **Check actual changes**: Use `git status --porcelain` and `git diff --name-status HEAD` to see what files were modified, added, or deleted since the last commit
204+
2. **Document only commit-level changes**: Do not document intermediate development steps or work-in-progress changes
205+
3. **Use git diff to understand scope**: Review actual code changes to understand what functionality was added, removed, or modified
206+
4. **Examples of what to document**:
207+
- New tools added (complete functionality, not development iterations)
208+
- Tools removed or deprecated
209+
- Breaking changes to existing tool interfaces
210+
- New major features or architectural changes
211+
5. **Examples of what NOT to document**:
212+
- Intermediate refactoring steps during development
213+
- Temporary fixes that were later replaced
214+
- Development debugging or testing iterations
215+
- Changes that were made and then reverted in the same session
216+
217+
**For this project**: Compare the current state against the last git commit to determine what actually changed, then document only those changes in CHANGELOG.md.
112218

113219
## Project Architecture
114220

figma-plugin/src/handlers/style-handler.ts

Lines changed: 58 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,8 @@ export class StyleHandler extends BaseHandler {
266266
const effectStyles = figma.getLocalEffectStyles();
267267
const gridStyles = figma.getLocalGridStyles();
268268
const allStyles = [...paintStyles, ...textStyles, ...effectStyles, ...gridStyles] as any[];
269-
style = allStyles.find(s => s.id === params.styleId);
269+
// Fix: Remove trailing comma from style IDs (Figma API bug)
270+
style = allStyles.find(s => s.id.replace(/,$/, '') === params.styleId);
270271
} else if (params.styleName) {
271272
const paintStyles = figma.getLocalPaintStyles();
272273
const textStyles = figma.getLocalTextStyles();
@@ -314,55 +315,93 @@ export class StyleHandler extends BaseHandler {
314315
}
315316

316317
private async deleteStyle(params: StyleParams): Promise<any> {
317-
let style: PaintStyle | TextStyle | EffectStyle | GridStyle | undefined;
318+
this.validateParams(params, ['styleId']);
319+
320+
// Debug: Log the requested style ID
321+
console.log('DELETE STYLE DEBUG - Requested ID:', params.styleId);
318322

323+
// Find the style by searching through all local styles
319324
const paintStyles = figma.getLocalPaintStyles();
320325
const textStyles = figma.getLocalTextStyles();
321326
const effectStyles = figma.getLocalEffectStyles();
322327
const gridStyles = figma.getLocalGridStyles();
323-
const allStyles = [...paintStyles, ...textStyles, ...effectStyles, ...gridStyles] as any[];
328+
const allStyles = [...paintStyles, ...textStyles, ...effectStyles, ...gridStyles];
324329

325-
if (params.styleId) {
326-
style = allStyles.find(s => s.id === params.styleId);
327-
} else if (params.styleName) {
328-
style = allStyles.find(s => s.name === params.styleName);
329-
}
330+
// Debug: Log all available style IDs
331+
console.log('DELETE STYLE DEBUG - Available style IDs:');
332+
allStyles.forEach(s => {
333+
console.log(` - ${s.name}: ${s.id} (type: ${s.type})`);
334+
});
335+
336+
// Fix: Remove trailing comma from style IDs (Figma API bug)
337+
const style = allStyles.find(s => s.id.replace(/,$/, '') === params.styleId);
338+
339+
console.log('DELETE STYLE DEBUG - Fixed comparison result:', !!style);
330340

331341
if (!style) {
332-
throw new Error('Style not found');
342+
// Enhanced error with more debugging info
343+
throw new Error(`Style not found with ID: ${params.styleId}. Found ${allStyles.length} total styles in file. Check console for available IDs.`);
333344
}
334345

335-
const styleInfo = {
346+
console.log('DELETE STYLE DEBUG - Found style to delete:', {
336347
id: style.id,
337348
name: style.name,
338349
type: style.type
350+
});
351+
352+
// Capture style info BEFORE deletion - create minimal response to avoid issues
353+
const styleInfo: any = {
354+
id: style.id.replace(/,$/, ''), // Clean ID for response
355+
name: style.name,
356+
type: style.type
339357
};
340358

359+
// Safely capture description
360+
try {
361+
styleInfo.description = style.description || '';
362+
} catch (e) {
363+
styleInfo.description = '';
364+
}
365+
366+
// Safely add type-specific properties before deletion
367+
if (style.type === 'TEXT') {
368+
try {
369+
const textStyle = style as TextStyle;
370+
styleInfo.fontName = textStyle.fontName;
371+
styleInfo.fontSize = textStyle.fontSize;
372+
styleInfo.letterSpacing = textStyle.letterSpacing;
373+
styleInfo.lineHeight = textStyle.lineHeight;
374+
} catch (e) {
375+
console.warn('Could not capture text style properties:', e);
376+
}
377+
}
378+
379+
// Delete the style using the correct API pattern
341380
style.remove();
342381

382+
console.log('DELETE STYLE DEBUG - Style removed successfully');
383+
343384
return {
344385
deletedStyle: styleInfo,
345-
message: `Deleted ${style.type.toLowerCase()} style "${style.name}"`
386+
message: `Successfully deleted ${styleInfo.type.toLowerCase()} style "${styleInfo.name}"`
346387
};
347388
}
348389

349390
private async getStyle(params: StyleParams): Promise<any> {
350-
let style: PaintStyle | TextStyle | EffectStyle | GridStyle | undefined;
391+
this.validateParams(params, ['styleId']);
351392

393+
// Find the style by searching through all local styles
352394
const paintStyles = figma.getLocalPaintStyles();
353395
const textStyles = figma.getLocalTextStyles();
354396
const effectStyles = figma.getLocalEffectStyles();
355397
const gridStyles = figma.getLocalGridStyles();
356-
const allStyles = [...paintStyles, ...textStyles, ...effectStyles, ...gridStyles] as any[];
398+
const allStyles = [...paintStyles, ...textStyles, ...effectStyles, ...gridStyles];
357399

358-
if (params.styleId) {
359-
style = allStyles.find(s => s.id === params.styleId);
360-
} else if (params.styleName) {
361-
style = allStyles.find(s => s.name === params.styleName);
362-
}
400+
// Fix: Remove trailing comma from style IDs (Figma API bug)
401+
const style = allStyles.find(s => s.id.replace(/,$/, '') === params.styleId);
363402

364403
if (!style) {
365-
throw new Error('Style not found');
404+
throw new Error(`Style not found with ID: ${params.styleId}`);
366405
}
367406

368407
return formatStyleResponse(style);

0 commit comments

Comments
 (0)