juspay / juspay/blend-rescript

Generated CodeConnectRegistry: dispatch by figmaComponentName so consumers don't hand-maintain a component mapping

Open Beginner friendly
#130 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
ReScript
Stars
1
Forks
0
Avg merge
2h 48m
Merged PRs (30d)
4

Description

Follow-up to #128 (additive — not a blocker for that PR, which is green and reviewed).

## Problem

#128 gives every verified component a `toProps` / `fromFigmaProps`. That removed the need to hand-copy *property* mapping tables into a consumer. But a consumer still hand-maintains a **component** mapping — a switch from Figma component name to the right module:

```rescript
// juspay-portal apps/code-connect/src/entry.res, today
switch updatedName {
| "Button" => (JuspayRescriptBlend.ButtonCodeConnect.toProps(props), "Blend.Button", "")->...
| "Tags" => (JuspayRescriptBlend.TagCodeConnect.toProps(props), "Blend.Tag", "")->...
| "Popover" => ...
}
```

Every new verified component means another arm in another repo. That's the same duplication #128 set out to remove, one level up — and the data needed to generate it **already exists here**. Every `figma/componentMaps/*.mjs` carries both halves:

```js
figmaComponentName: 'Button', // Figma side
codeComponent: 'Button', // ReScript side
```

`scripts/generate-figma-code-connect.mjs` already loads all of them to emit the per-component modules, so it can emit one more file essentially for free.

## Proposal

Generate `src/Figma/CodeConnectRegistry.res`, shipped in the package like the rest:

```rescript
// GENERATED by scripts/generate-figma-code-connect.mjs
let resolve = (
figmaComponentName: string,
props: CodeConnectUtils.figmaProps,
): option<(array>, string)> =>
switch figmaComponentName {
| "Button" => Some((ButtonCodeConnect.toProps(props), "Button"))
| "Tag" => Some((TagCodeConnect.toProps(props), "Tag"))
| "Popover" => Some((PopoverCodeConnect.toProps(props), "Popover"))
// ... one arm per map with figmaComponentName set
| _ => None
}
```

Same rule as the codegen already applies: only maps with a non-null `figmaComponentName` get an arm, so unverified scaffolds never leak in.

The consumer then has **no per-component mapping at all**, and new verified components light up on a version bump with zero downstream edits:

```rescript
switch JuspayRescriptBlend.CodeConnectRegistry.resolve(componentName, props) {
| Some((props, component)) =>
(props, "Blend." ++ component, "")->convertPropsNodeStateVariable("")
| None => // consumer's own handling for what Blend doesn't cover
}
```

### Why return `codeComponent` bare rather than a rendered tag

Returning `"Button"` (not `""`) keeps prefix policy on the consumer side. juspay-portal renders Blend through a `Blend.` facade module (`packages/common/src/Blend.res`, 1694 ``. Both halves compile, so this is cosmetic and explicitly **not** part of this request — noting it only because a `~modulePrefix` knob, if ever wanted, would belong in the same generated layer.

## Scope / non-goals

- **Prop-mapped components only.** Anything that needs to walk children (a consumer's Modal/Table/Snackbar/Calendar-style converters take the node and a recursive callback) can't be expressed as `figmaProps => props`, and should keep falling through to `None`. This covers the simple majority, not everything.
- **`instanceSwap` props are unchanged** — `leadingIcon`/`trailingIcon` still aren't statically derivable and still need call-site splicing.
- **Resolving the Figma component name is the consumer's problem, not this package's.** Worth recording since it's the main integration friction: a Figma plugin sees an *instance* whose layer name is only incidentally the component name (Figma suffixes duplicates, hence juspay-portal's existing `^Button \d+$` normalization). Doing it properly means `getMainComponentAsync()` — mandatory rather than the sync `mainComponent` getter under `documentAccess: "dynamic-page"` — plus walking to the `COMPONENT_SET` parent, since a variant's own name is `"Size=md, Type=primary"`. That's async, which infects an otherwise-sync recursive node walker. None of that belongs here; `resolve` should just take the string.

## Why here and not in the consumer

The mapping data is already synced from blend-design-system into `figma/componentMaps/*.mjs` and kept honest by `figma:check-drift`. Re-encoding it downstream means a second copy that drifts silently, which is exactly what #128's "blend-design-system is the sole source of truth" posture exists to prevent.

Estimated effort: ~30 lines in `scripts/generate-figma-code-connect.mjs` plus a generated file.

Contributor guide

Open the contributing guide

Research direction

Start with scripts/generate-figma-code-connect.mjs and the existing figma/componentMaps/*.mjs inputs to follow how component modules are emitted. Add the generated src/Figma/CodeConnectRegistry.res so only maps with non-null figmaComponentName produce resolve arms, then confirm the generated file is shipped with the package.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
build-system, tooling
Issue type
Feature
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.