BasaltExpo phone preview, visual edit, and Figma merge for VS Code and Cursor. Basalt is a standalone extension — no separate IDE required. Open your Expo app, preview it in a device frame, edit copy in place, and merge Figma designs with Cursor AI. Features
Screen picker (universal)Basalt discovers screens from your project automatically:
Use the Screens dropdown in the preview header after Metro starts. Custom manifest (
|
| Command | Action |
|---|---|
Basalt: Open Phone Preview |
Open the preview sidebar |
Basalt: Start Preview |
Start Metro and load the app |
Basalt: Stop Preview |
Stop Metro |
Basalt: Open Figma Tab |
Open preview and switch to Figma |
Basalt: Show Preview Log |
Open the output log |
Basalt: Enable Source & Bound-Edit Injection |
Enable dev-only precise source and runtime item provenance |
Settings
| Setting | Default | Description |
|---|---|---|
basalt.expoProjectPath |
"" |
Fallback Expo root if not in workspace |
basalt.autoStartPreview |
false |
Auto-open preview when Expo is detected |
basalt.itemIdProperty |
"id" |
Stable object-key property used to edit state-selected list items (for example uuid) |
Edit mode tips
- Switch to Edit, double-click text, type, press Enter (or click outside) to save.
- ⌘Z / Ctrl+Z undoes preview + file edits.
- Success toasts only appear when the file actually saved. If Basalt cannot resolve a binding, it says so — it will not pretend.
Supported text-edit patterns (honest scope)
Basalt edits source, not the running app’s memory. It follows language shapes that show up in many Expo/RN apps — not your file names, folder layout, or product domain.
Works today
| Pattern | What Basalt does |
|---|---|
| Literal JSX / props | <Text>Hello</Text>, title="…", config strings |
| List row binding | FlatList / SectionList data={ARR} + {item.field} / `${item.field}` → import of ARR → that object (by React list key when available) |
Detail via .find() |
const row = ARR.find(…) + {row.field} → import of ARR → unique object whose field matches the displayed value |
| State-selected detail/modal | setSelected(item) from .map / renderItem, then {selected.field} → dev-only runtime identity stamp → the exact source object |
| Open in Source | Uses injected source + TypeScript when possible |
Does not reliably work yet
| Pattern | What happens |
|---|---|
| Redux / Zustand / React Query / context-only data | Usually cannot land on a literal — Basalt should refuse with an honest error |
| Values that exist only on a server (no literal in the repo) | Cannot “save to source” |
| Deep prop drilling / renamed bindings TS can’t follow | May fail; use Cmd+click and edit manually |
| Ambiguous matches without runtime item identity | Refuses rather than guessing the wrong row |
State-selected edits require Basalt's dev-only source injection. Existing configured projects are refreshed automatically before Metro starts; the generated code is disabled in production builds. For collections keyed by something other than id, set basalt.itemIdProperty and restart Preview.
What we will not do
- Hardcode paths or app names (
cars.ts, a specific screen file, one customer’s repo) - Grep the whole project for
name:/price:until something sticks - Show “Saved to source” when nothing was written
As coverage grows, new fixes target more of these shapes (same in every app), not tuning for one project.
Figma merge tips
- Use a figma.com URL, not a local
.figfile (MCP reads cloud designs). - Run merge from your Expo app workspace, not the extension repo.
- Enable Figma MCP in Cursor.
- In chat:
@basalt /fig_mergeor use Merge to RN from the Figma tab.
Development
npm install
npm run compile
npm run package # creates basalt-preview-X.X.X.vsix
Press F5 in this repo to launch an Extension Development Host. Disable any installed copy of Basalt while debugging.
License
MIT