feat(figma): design-to-text mapping resolver (Phase C — T2F.5, T2F.6, T2F.7)#41
Open
canonical-muhammadbassiony wants to merge 4 commits into
Open
Conversation
added 2 commits
May 20, 2026 13:19
…persistence - T2F.5: internal/source/mapping resolver with Jaccard text matching + fallback - T2F.6: prompt engine GenerateChunksFromResolved, FigmaContextJSON, figma-context template - T2F.7: artifacts.WriteFigmaExtraction, WriteMappings, WriteFigmaComments
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new design-to-text mapping layer that enriches Google Docs suggestion groups with extracted Figma design context, then threads that enrichment into prompt rendering and artifact persistence for later inspection/debugging.
Changes:
- Introduces
internal/source/mappingwithResolvedChunktypes and a 4-strategy resolver (URL node ID → text similarity → frame-name overlap → fallback). - Extends the prompt engine to batch
ResolvedChunks intoPromptData, serialize design enrichment intoFigmaContextJSON, and conditionally render a newfigma-context.mdsection. - Adds artifact manager helpers to persist normalized Figma extraction, resolved mappings, and extracted comments under
extraction/.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/source/mapping/types.go | Adds resolved-chunk and mapping metadata types for Figma enrichment. |
| internal/source/mapping/resolver.go | Implements resolver strategies (URL/text/name/fallback) and attaches screenshots/comments. |
| internal/source/mapping/resolver_test.go | Adds tests for resolver behavior across matching strategies and attachments. |
| internal/prompt/engine.go | Adds Figma context fields, batching from resolved chunks, and conditional figma-context rendering. |
| internal/prompt/templates/figma-context.md | Adds a prompt section template for anchors/screenshots/designer comments. |
| internal/prompt/engine_test.go | Adds tests for batching and figma-context inclusion/exclusion during rendering. |
| internal/artifacts/manager.go | Adds persistence methods for Figma extraction, mappings, and comments. |
| internal/artifacts/manager_test.go | Adds tests verifying the new artifact outputs are written and parseable. |
| docs/implementation-log.md | Marks Phase C as done and documents the delivered changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+85
to
+86
| // matchByTextLayers uses weighted Jaccard similarity on token bags. | ||
| // Returns the best matching anchor and its confidence, or nil if no match meets threshold. |
Comment on lines
+73
to
+80
| // Strategy 4: fallback to first anchor (root node) | ||
| if len(design.Anchors) > 0 { | ||
| return []DesignAnchorRef{{ | ||
| FileKey: design.FileKey, | ||
| NodeID: design.Anchors[0].NodeID, | ||
| NodeName: design.Anchors[0].NodeName, | ||
| }}, MappingMetadata{Method: "fallback", Confidence: 0.50, Status: "unresolved"} | ||
| } |
Comment on lines
+115
to
+118
| if jacc >= 0.30 && conf > bestConf { | ||
| bestConf = conf | ||
| best = &anchors[i] | ||
| } |
Comment on lines
+148
to
+151
| if overlap >= 0.50 && conf > bestConf { | ||
| bestConf = conf | ||
| best = &anchors[i] | ||
| } |
Comment on lines
+144
to
+158
| // Append figma context section when design data is present | ||
| if data.FigmaContextJSON != "" { | ||
| var ctx figmaChunkContext | ||
| if err := json.Unmarshal([]byte(data.FigmaContextJSON), &ctx); err != nil { | ||
| return "", fmt.Errorf("parsing figma context JSON: %w", err) | ||
| } | ||
| tmpl, err := template.New("figma-context").Parse(figmaContextTemplate) | ||
| if err != nil { | ||
| return "", fmt.Errorf("parsing figma context template: %w", err) | ||
| } | ||
| buf.WriteString("\n\n---\n\n") | ||
| if err := tmpl.Execute(&buf, ctx); err != nil { | ||
| return "", fmt.Errorf("rendering figma context: %w", err) | ||
| } | ||
| } |
| }}, MappingMetadata{Method: "name", Confidence: conf, Status: "healthy"} | ||
| } | ||
|
|
||
| // Strategy 4: fallback to first anchor (root node), sorted for determinism |
Comment on lines
+91
to
+95
| // matchByTextLayers uses Jaccard similarity on token bags. | ||
| // Returns the best matching anchor and its confidence, or nil if no match meets threshold. | ||
| func matchByTextLayers(group gdocs.LocationGroupedSuggestions, anchors []figma.DesignAnchor) (*figma.DesignAnchor, float64) { | ||
| // Build token bag from gdocs suggestion group | ||
| gdocsTokens := tokenize(group.Location.ParentHeading + " " + group.Location.Section) |
Comment on lines
+256
to
+259
| suggestionsJSON, err := json.Marshal(locations) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("marshaling suggestions for chunk %d: %w", i+1, err) | ||
| } |
Comment on lines
+280
to
+297
| func buildFigmaContextJSON(batch []mapping.ResolvedChunk) (string, error) { | ||
| ctx := figmaChunkContext{} | ||
| for _, rc := range batch { | ||
| ctx.Anchors = append(ctx.Anchors, rc.DesignAnchors...) | ||
| ctx.Screenshots = append(ctx.Screenshots, rc.ScreenshotPaths...) | ||
| ctx.Comments = append(ctx.Comments, rc.Comments...) | ||
| } | ||
|
|
||
| if len(ctx.Anchors) == 0 && len(ctx.Screenshots) == 0 && len(ctx.Comments) == 0 { | ||
| return "", nil | ||
| } | ||
|
|
||
| b, err := json.Marshal(ctx) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| return string(b), nil | ||
| } |
Comment on lines
+201
to
+218
| // tokenize normalizes text into a lowercase token slice, removing stop words and short tokens. | ||
| func tokenize(text string) []string { | ||
| stop := map[string]bool{ | ||
| "the": true, "a": true, "an": true, "and": true, "or": true, | ||
| "in": true, "of": true, "to": true, "for": true, "is": true, "are": true, | ||
| "it": true, "at": true, "on": true, "by": true, "be": true, | ||
| } | ||
| words := strings.FieldsFunc(strings.ToLower(text), func(r rune) bool { | ||
| return !unicode.IsLetter(r) && !unicode.IsDigit(r) | ||
| }) | ||
| var result []string | ||
| for _, w := range words { | ||
| if len(w) >= 3 && !stop[w] { | ||
| result = append(result, w) | ||
| } | ||
| } | ||
| return result | ||
| } |
| @@ -0,0 +1,32 @@ | |||
| ## Design Context | |||
|
|
|||
| Design information has been extracted from Figma for the suggestions in this chunk. | |||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Introduces the mapping resolver that joins Google Docs suggestion groups with Figma design data, and integrates the result into prompts.
Tasks Implemented
internal/source/mapping— resolver with 4-strategy priority chain: (1) user-supplied node ID (confidence 1.0), (2) Jaccard text-layer similarity (threshold 0.30), (3) frame-name overlap (threshold 0.50), (4) fallback to first anchor (status "unresolved")FigmaContextJSONandFigmaURLfields inPromptData;GenerateChunksFromResolvedbatches resolved chunks into prompt data;RenderChunkconditionally renders figma-context templateWriteFigmaExtraction,WriteMappings,WriteFigmaCommentsmethods;figma-context.mdtemplate with anchors, screenshots, and comments sectionsFiles Changed
internal/source/mapping/types.go—ResolvedChunk,DesignAnchorRef,DesignCommentRef,MappingMetadatainternal/source/mapping/resolver.go—Resolver.Buildwith Jaccard matchinginternal/source/mapping/resolver_test.go— 9 test casesinternal/prompt/engine.go—GenerateChunksFromResolved,buildFigmaContextJSON, figma template renderinginternal/prompt/templates/figma-context.md— design context templateinternal/prompt/engine_test.go— 6 new test functionsinternal/artifacts/manager.go—WriteFigmaExtraction,WriteMappings,WriteFigmaCommentsinternal/artifacts/manager_test.go— tests for new methodsPart of the Bauer v2 stacked PR series (Branch 6 of 12).