Skip to content

feat: add set_solid_fills for multi-node paint updates - #46

Open
julioservan wants to merge 1 commit into
gethopp:mainfrom
julioservan:pr1-set-solid-fills
Open

feat: add set_solid_fills for multi-node paint updates#46
julioservan wants to merge 1 commit into
gethopp:mainfrom
julioservan:pr1-set-solid-fills

Conversation

@julioservan

@julioservan julioservan commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

What

Adds set_solid_fills, which applies solid fills or strokes to many nodes in a single round-trip.

Why

Theming a component's variants costs one WebSocket round-trip per node today. That is fine for three layers and unworkable for the hundred a real theming pass touches — the latency is entirely in the round-trips, not the local work.

Shape

It follows the repo's existing multi-target idiom rather than inventing one: an items array, same as set_node_visibility and save_screenshots. Each item mirrors set_solid_fill field for field, including the fillHex/fillOpacity aliases added in #39 — so a caller can lift a single-node call straight into items without renaming anything.

Items are independent. A bad nodeId fails only its own entry and lands in that entry's error; the rest still apply. The response is { results: [...] }, matching set_node_visibility.

Notes

  • The loop is sequential on purpose — these all mutate the document, and the round-trip being saved is the WebSocket one.
  • set_solid_fills is registered in EDIT_REQUEST_TYPES, so it returns the usual clear error in Dev Mode.
  • README's tool table is updated.

Testing

tsc --noEmit is clean on the server. The plugin's 5 pre-existing type errors are unchanged (they reproduce on main without this patch). Both packages build.


This is the first of three stacked PRs adding a design-system write surface; the other two (variables/modes/bindings, and styles) build on this one and I'll open them once this lands — or sooner if you'd rather review them together.

Summary by CodeRabbit

  • New Features
    • Added a batch tool for applying solid fills to multiple design elements.
    • Supports updating fills or strokes with color and opacity values.
    • Allows optional file targeting and accepts alternative fill property names.
    • Reports individual successes and errors without stopping the entire batch.
  • Documentation
    • Documented the new tool in the Available Tools section.
    • Improved spacing in the “What You Can Build” section.

Theming a component's variants currently costs one WebSocket round-trip per
node, which does not scale past a handful of layers.

set_solid_fills takes an `items` array — the same field name and per-item
shape as set_node_visibility, including the fillHex/fillOpacity aliases from
gethopp#39 — and applies them all in a single round-trip. Items are independent: a
failing node is reported in its own result entry and does not abort the rest,
so a partially-stale node list still does useful work.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f443827-669c-48d2-8046-3145ebbc4bc4

📥 Commits

Reviewing files that changed from the base of the PR and between 1218122 and 61a86ad.

📒 Files selected for processing (4)
  • README.md
  • plugin/src/main/code.ts
  • server/src/schema.ts
  • server/src/tools.ts

📝 Walkthrough

Walkthrough

Changes

Batch solid-fill updates

Layer / File(s) Summary
Batch input contract
server/src/schema.ts
Adds setSolidFillsInput with item validation, fill aliases, opacity aliases, and optional fileKey.
Server forwarding
server/src/tools.ts, server/src/schema.ts
Registers set_solid_fills and forwards the batch request through RPC mapping.
Sequential fill application
plugin/src/main/code.ts, README.md
Validates and applies fill or stroke updates sequentially, returns per-item results, and documents the tool.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 61a86

The change adds a localized batch paint-update capability with no actionable merge-blocking risk remaining after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MCPTool
  participant Plugin
  participant FigmaNodes
  Client->>MCPTool: submit set_solid_fills items
  MCPTool->>Plugin: forward validated batch
  Plugin->>FigmaNodes: apply fill or stroke per item
  FigmaNodes-->>Plugin: return item result
  Plugin-->>Client: return batch results
Loading

Possibly related PRs

Suggested reviewers: konsalex

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding set_solid_fills for multi-node paint updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@konsalex

Copy link
Copy Markdown
Contributor

Hey @julioservan .

I am now reading the PR and the batch shape itself is right. items plus per item results matches the set_node_visibility field for field, which is nice. One thing I'd want changed before merge, one design question, and a bit of cleanup.

1/ The alias handling belongs in the schema, not the plugin

set_solid_fill resolves fillHex and fillOpacity in the schema with a .transform and the comment above it gives the reason: the shape is derived from the advertised one "so the two cannot drift". leader.ts then forwards the schema output instead of the caller's raw object, with a comment saying the plugin only understands the canonical spelling. That's why the single node handler only ever reads params.hex and params.opacity.

This PR uses .refine instead, so fillHex and fillOpacity arrive at the plugin untouched and the plugin resolves them a second time:

const hex = typeof item.hex === "string" ? item.hex : item.fillHex;
const rawOpacity =
  typeof item.opacity === "number" ? item.opacity : item.fillOpacity;

The alias rule now exists twice, in two layers (server + plugin), and the two sibling handlers disagree about how many spellings they accept. Whenever the next alias gets added to set_solid_fill, and you mention two more PRs are coming, the batch tool will quietly not pick it up.

Switching the item schema to a .transform fixes that and lets the plugin handler shrink to the same three lines as set_solid_fill. It also gives better errors with ctx.addIssue({ path: ["hex"] }) the caller gets items.3.hex rather than one message for the whole item, which matters when the payload has a hundred entries in it.

const createSolidFillItemSchema = () =>
  z
    .object({ /* same fields as today */ })
    .transform(({ fillHex, fillOpacity, ...rest }, ctx) => {
      const hex = rest.hex ?? fillHex;
      if (hex === undefined) {
        ctx.addIssue({
          code: z.ZodIssueCode.custom,
          path: ["hex"],
          message: "hex is required (fillHex is accepted as an alias)",
        });
        return z.NEVER;
      }
      return { ...rest, hex, opacity: rest.opacity ?? fillOpacity };
    });

2/ set_solid_fill vs set_solid_fills

Two tools one letter apart, both described as "Replace the fill (or stroke)" will lead to models picking the wrong one fairly often, and both mistakes are silent ones. Reach for the singular in a loop and you get exactly the latency this PR exists to remove, with nothing in the response to hint at it.

This is the first of three PRs setting the pattern (as you mentioned), so I'd rather decide it now than inherit it. Two options worth weighing: let set_solid_fill accept either nodeId or items, since the schema already has the machinery to widen an input shape without breaking existing callers and it keeps the tool list shorter for the model, or keep them separate but name the batch one so it can't be misread, for example set_solid_fills_batch. No strong preference from me, I just don't want it settled by default across the stack.

3/ Cleanup

The patch has quite a few random blank lines in it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants