Commit bf6280b
feat(llms): Include source code in mistica package (#1531)
## Summary
Adds source code to Mistica published package. To make up the
instructions for the LLMs we conducted a series of experiments that are
detailed below. The conclusions were:
1. **Docs first** — LLMs tend to work best with documentation and
examples. Less tokens consumption, faster responses, etc.
2. **`.d.ts` for prop questions** — Reading .d.ts files is 10-17%
cheaper than reading `.tsx` source, and LLMs understand these types.
Also JSDoc is preserved in .d.ts files and taken in account by LLMs.
3. **`.tsx` / `.css.ts` source can help LLMs when asked specifically
about library internals or debugging errors caused by Mistica lib
itself. When source files are not present LLMs read the compiled files
reaching essentially the same conclusions, but reading the compiled code
is more expensive in tokens/time.
4. **UI shows no improvements** when having the source code available.
5. **Source reading sometimes regresses output quality.** Seeing
low-level APIs (render props, internal escape hatches) can tempt the LLM
to use them instead of simpler declarative alternatives.
Following these, we concluded adding the source code can be helpful, but
we tailored the instructions so LLMs just read it when needed and don't
default to it.
---
### Iteration 1 — Beta v16.59.0-beta.1 (with source code) vs Stable
v16.58.0 (without source code)
**Setup**: Four greenfield UI evals (baseline vs "imperative read
source")
Beta has the "Source code location" section; stable v16.58.0's llms.md
doesn't ship it.
| Eval | Beta tokens/duration | Stable tokens/duration |
|---|---|---|
| Settings | 55.0k / 234s | 56.6k / 302s |
| Checkout | 54.8k / 291s | 58.0k / 341s |
| Dashboard | 60.1k / 316s | 56.5k / 260s |
| Netflix | 79.2k / 420s | ~same / 524s* |
*Stable Netflix hit API error after completing — screenshot/code
succeeded.
#### Settings page
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/a8f2fe61-fcb7-4d36-8942-dda156766b2d"
width="400"><br><b>Beta v16.59.0-beta.1</b></td>
<td><img
src="https://github.com/user-attachments/assets/2af5e7d2-44e1-4e9b-8a24-9adb183a2f9b"
width="400"><br><b>Stable v16.58.0</b></td>
</tr>
</table>
Stable used `HeaderLayout + Header` and `Callout` — more polished
structure. Both used `Row switch` and `RowList` idiomatically.
#### Checkout form
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/6e0afa46-878e-480b-ab65-37b1f12db8ec"
width="400"><br><b>Beta v16.59.0-beta.1</b></td>
<td><img
src="https://github.com/user-attachments/assets/2569b541-d390-45b8-a8e1-87cf4236601c"
width="400"><br><b>Stable v16.58.0</b></td>
</tr>
</table>
Stable added a `Stepper` component (Cart/Shipping/Payment/Confirmation)
— a composite the beta version didn't reach for. Ignore the
misalignment, the stable version didn't include last changes from:
#1526
#### Dashboard
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/449636dd-a57a-4681-9c1c-6c0628ffdcfc"
width="400"><br><b>Beta v16.59.0-beta.1</b></td>
<td><img
src="https://github.com/user-attachments/assets/19432ce3-4d72-4613-88af-4b04787717ca"
width="400"><br><b>Stable v16.58.0</b></td>
</tr>
</table>
Stable used `GridLayout template="8+4"` for side-by-side composition of
data usage and connected devices. Beta used simpler vertical stack and
MainNavigationBar.
#### Netflix page
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/818fc048-1472-47a8-9d75-a5a7659d8949"
width="400"><br><b>Beta v16.59.0-beta.1</b></td>
<td><img
src="https://github.com/user-attachments/assets/fa2d22ce-c6ab-4f0e-b772-a137c1620679"
width="400"><br><b>Stable v16.58.0</b></td>
</tr>
</table>
Both visually striking with custom dark skins. Roughly tied.
| Eval | Winner | Why |
|---|---|---|
| Settings | **Stable (slight)** | Richer page structure with
HeaderLayout and Callout |
| Checkout | **Stable (clear)** | Used Stepper composite that beta
missed |
| Dashboard | Tied | Both missed some components |
| Netflix | Tied | Both striking with custom skins |
> **Conclusion**: Stable v16.58.0 (without source location section)
produced consistently equal-or-better outputs. The beta's "Source code
location" section likely distracted agents from documented composites.
---
### Iteration 1 — Baseline vs "imperative read source" (same beta
version)
Added a "Step 3" to llms.md **forcing** agents to read component source
before coding.
#### Settings page
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/c086af24-ed74-4394-a5e6-cc3f2172f9e5"
width="400"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/127915d1-c9c7-4e76-8730-533de7b056b9"
width="400"><br><b>Modified (imperative read source)</b></td>
</tr>
</table>
#### Checkout form
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/054c2bcd-9463-4970-9ce5-9ab2f775e1b0"
width="400"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/ef7da6a8-b674-444f-ac98-aae10dfbeb31"
width="400"><br><b>Modified (imperative read source)</b></td>
</tr>
</table>
#### Dashboard
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/53d01167-768c-4c15-866d-fd14a67d95ba"
width="400"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/2189ae24-2d98-435f-ba78-6440bbcf6ce8"
width="400"><br><b>Modified (imperative read source)</b></td>
</tr>
</table>
#### Netflix page
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/db0f0de5-4a33-45e6-a6ae-4c619531f9de"
width="400"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/44e4f031-d7d1-4385-95a8-acc01a862e72"
width="400"><br><b>Modified (imperative read source)</b></td>
</tr>
</table>
| Eval | Baseline | Modified | Winner |
|---|---|---|---|
| Settings | Row `switch`, NegativeBox, StartIcon | Legacy skin,
standalone Switch | **Baseline** |
| Checkout | Wrong validate type | Correct types, CC field names from
source | **Modified** |
| Dashboard | Manual Grid; missed DataCard/Carousel | Carousel +
DataCard | **Modified** |
| Netflix | 3 card types, best footer | Single card type, weaker skin |
**Baseline** |
While the results are inconsistent, token usage and time consumption was
much higher for the modified version. Avg tokens: Baseline 67.6k,
Modified 74.5k (+10%). **Mixed results** — source reading helped
discover composites but regressed idiomatic patterns.
---
### Iteration 1b — Three-way: baseline vs imperative vs "why-driven"
rationale
#### Settings page
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/c086af24-ed74-4394-a5e6-cc3f2172f9e5"
width="270"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/127915d1-c9c7-4e76-8730-533de7b056b9"
width="270"><br><b>V1 (imperative)</b></td>
<td><img
src="https://github.com/user-attachments/assets/715af4b8-5629-45b2-9b0a-3080409e7fb5"
width="270"><br><b>V2 (why-driven)</b></td>
</tr>
</table>
#### Checkout form
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/054c2bcd-9463-4970-9ce5-9ab2f775e1b0"
width="270"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/ef7da6a8-b674-444f-ac98-aae10dfbeb31"
width="270"><br><b>V1 (imperative)</b></td>
<td><img
src="https://github.com/user-attachments/assets/0eaa273d-c891-4f10-ad46-751ea89276c1"
width="270"><br><b>V2 (why-driven)</b></td>
</tr>
</table>
#### Dashboard
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/53d01167-768c-4c15-866d-fd14a67d95ba"
width="270"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/2189ae24-2d98-435f-ba78-6440bbcf6ce8"
width="270"><br><b>V1 (imperative)</b></td>
<td><img
src="https://github.com/user-attachments/assets/a4e77652-d0c7-4bd6-98e8-8cec13254842"
width="270"><br><b>V2 (why-driven)</b></td>
</tr>
</table>
#### Netflix page
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/db0f0de5-4a33-45e6-a6ae-4c619531f9de"
width="270"><br><b>Baseline</b></td>
<td><img
src="https://github.com/user-attachments/assets/44e4f031-d7d1-4385-95a8-acc01a862e72"
width="270"><br><b>V1 (imperative)</b></td>
<td><img
src="https://github.com/user-attachments/assets/2aabbe20-0e2b-4dc1-a900-c503bc53d8b6"
width="270"><br><b>V2 (why-driven)</b></td>
</tr>
</table>
Produced code has essentially the same quality.
> **Conclusion**: Explaining "why" to read source did NOT consistently
improve over imperative. V2 took 24% more tokens but split wins evenly.
The why-preamble may have displaced attention from actually executing
the source reading thoroughly (V2's Netflix skin was visibly more
incomplete).
---
### Iterations 2–3 — Image upload: with/without Source location section
Here we don't include screenshots because they were essentially the
same.
**Prompt**: "Generate a wireframe with mistica where the user can attach
images to upload..."
- **Iteration 2**: Neither variant used Mistica's `FileUpload` component
— both built custom drop zones. Source location hint didn't help with
component *discovery*.
- **Iteration 3** (explicitly asked to use `FileUpload`): WITHOUT-source
used the cleaner declarative API
(`renderButton`/`renderFiles`/`withDropZone`). WITH-source reached for
the low-level `render` prop.
> **Conclusion**: Source access tempts agents toward verbose low-level
APIs when simpler declarative alternatives exist.
---
### Iteration 4 — beta (source code) vs v16.58.0 (no source code) +
FileUpload
<table>
<tr>
<td><img
src="https://github.com/user-attachments/assets/660d83cf-4dc8-4ba6-a60d-a34f663eb7c2"
width="400"><br><b>WITH source (beta v16.59.0-beta.1)</b></td>
<td><img
src="https://github.com/user-attachments/assets/9b7dbd1a-f108-4b1e-931e-5f6bbd35279f"
width="400"><br><b>WITHOUT source (stable v16.58.0)</b></td>
</tr>
</table>
| | WITH source (beta) | WITHOUT source (v16.58.0) |
|---|---|---|
| Tokens | 77.3k | 59.7k (**−23%**) |
| Duration | 295s | 213s (**−28%**) |
| Mistica-native components | HeaderLayout, Grid, Image, Align, Circle |
Raw CSS grid, raw `<img>` |
| FileUpload API | Low-level `render` prop | Declarative props |
> **Conclusion**: Source section drove deeper reading (+30% tokens) and
broader component use. The WITHOUT-source version was more
cost-efficient and picked the simpler API.
---
### Iteration 5 — Fix DataCard accessibility
(`segregateTouchableContent`)
#### Prompt
Fix the accessibility of the DataCard in example-fix-data-card/card.tsx
so that all sections within the card (headline, title, subtitle,
description, slot) are read one by one by screen readers.
#### Code
```typescript
<DataCard
asset={
<Circle backgroundColor={skinVars.colors.brandLow} size={40}>
<IconShopRegular color={skinVars.colors.brand} />
</Circle>
}
headline={<Tag type="promo">Headline</Tag>}
title="Title"
subtitle="Subtitle"
description="Description"
slot={<Placeholder />}
onPress={() => {
console.log("hola");
}}
/>
```
Both found the same correct answer: `segregateTouchableContent` prop.
| | WITH source (beta) | WITHOUT source (v16.58.0) |
|---|---|---|
| Tokens | 58.5k | 48.6k (**−17%**) |
| Discovery path | Read `card-internal.tsx` | Read `card-internal.d.ts`
|
> **Conclusion**: For prop discovery, `.d.ts` files are sufficient.
JSDoc comments preserved there. Same answer, 17% cheaper.
---
### Iteration 6 — Card with custom "hola" aria-label
#### Prompt
Generate a DataCard whose elements (headline, title, subtitle,
description, slot) are read one by one by screen readers, AND whose
clickable/touchable accessibility label is the word 'hola' (instead of
the title text).
Both found the same compound solution: `segregateTouchableContent` +
`touchableAriaLabel="hola"`.
| | WITH source | WITHOUT source |
|---|---|---|
| Tokens | 63.1k | 54.0k (**−14%**) |
| Duration | 268s | 174s (**−35%**) |
---
### Iteration 7 — Debug "aria-label ignored, title announced"
#### Prompt
I have a DataCard at example-fix-data-card/card.tsx. I added
aria-label='hola' because I want screen readers to announce the
clickable card as 'hola'. I also added segregateTouchableContent so each
section is read individually. But when I test it with my screen reader,
it announces the title 'Title' as the button label instead of 'hola'.
The aria-label seems to be ignored. Why?
Both found the root cause: `aria-label` labels the outer container, not
the touchable button. Fix: `touchableAriaLabel`.
| | WITH source | WITHOUT source |
|---|---|---|
| Tokens | 51.2k | 46.2k (**−10%**) |
| Explanation | Cited `card-internal.tsx` line 1359 + named
`hasTouchableInContent` variable | Paraphrased JSDoc from `.d.ts` |
> WITH-source gave deeper mechanistic explanation, but same correct fix.
Useful for learning, not required for the fix.
---
### Iteration 8 — Debug "search field not expanding in
MainNavigationBar"
Here we asked the llm to reason about a ticket Vivo opened recently. It
would need to inspect the source code in order to know why components
weren't working as expected.
#### Prompt
I have a page with a MainNavigationBar at
example-fix-data-card/page-example.tsx. The right prop contains an
<Inline space='between' fullWidth> with a <SearchField fullWidth /> and
a <NavigationBarAction> (Avatar + 'Entrar' text). The right portion of
the navigation bar is NOT being stretched to occupy the full width of
the available area. The SearchField does not expand horizontally to fill
space — it just renders at its natural minimum width. I want the
SearchField to grow and fill the available horizontal space inside the
navbar's right slot, while the 'Entrar' button stays at its natural size
on the right edge. Why is this happening, and how do I fix it?
#### Code
```typescript
<ResponsiveLayout>
<MainNavigationBar
withBorder={false}
burgerMenuExtra={true}
large
wide
logo={<Logo type="imagotype" size={48} />}
sections={[
{
title: "Baixar o App Vivo",
onPress: () => setIndex(0),
},
{
title: "Produtos e Serviços",
onPress: () => setIndex(1),
},
{
title: "Ajuda",
onPress: () => setIndex(2),
},
{
title: "Por que Vivo",
onPress: () => setIndex(3),
},
{
title: "Melhores Ofertas",
onPress: () => setIndex(4),
},
]}
selectedIndex={index}
right={
<Inline space="between" fullWidth>
<SearchField name="search" label="Search" fullWidth />
<NavigationBarAction onPress={() => {}} aria-label="Entrar">
<Avatar size={32} />
<Text wordBreak={false} hyphens="manual">
Entrar
</Text>
</NavigationBarAction>
</Inline>
}
/>
<Box paddingY={80}>
<Slideshow
autoplay
withBullets
items={Array.from({ length: 3 }, (_, idx) => (
<CoverCard
aspectRatio="16:9"
slot={
<Box paddingY={16} paddingBottom={48}>
<ButtonGroup
primaryButton={
<ButtonPrimary onPress={() => {}}>
Pós com Vivo Fibra e Wi-Fi 6
</ButtonPrimary>
}
/>
</Box>
}
pretitle="Pós + Fibra"
title="Pós com Vivo Fibra e Wi-Fi 6"
description="Planos a partir de R$ 150/mês"
imageSrc="https://cdn-assets-eu.frontify.com/s3/frontify-enterprise-files-eu/eyJwYXRoIjoidGVsZWZvbmljYVwvZmlsZVwvdUNqUU42c0Y3ZW9Db1NvV1h4eHguanBnIn0:telefonica:sFFXnOJrAlsAdyFQRwphLVSonDf7X4lYfZzy8cfWwM0?width=2400"
/>
))}
/>
</Box>
</ResponsiveLayout>
```
Both found the same 4-layer root cause and fixed with `flex: 1` wrapper.
| | WITH source | WITHOUT source |
|---|---|---|
| Tokens | 48.0k | 58.2k (+21%) |
| Duration | 214s | **390s (+82%)** |
| Tool uses | 38 | **68 (+79%)** |
> **First iteration where WITHOUT-source was MORE expensive.**
Layout/CSS behavior requires understanding DOM structure — something
`.d.ts` can't describe. The WITHOUT-source agent had to reverse-engineer
from compiled `.js`.
---
## Key findings
- **JSDoc in `.d.ts` is an underrated discovery channel** — for prop
questions, same answer at a fraction of the cost
- **Source code location > forcing to read source code > explaning why
has to read source code**
- **Source reading sometimes regresses greenfield output** — low-level
APIs (render props, manual wrappers) tempt agents away from simpler
documented composites (probably because their flexibility).
- **Source cost premium is real but task-dependent** — +10% to +30%
tokens on prop tasks; pays for itself on layout/CSS/skin tasks
- **Reading source can sometimes help** when creating a skin from zero.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent ec1e35c commit bf6280b
2 files changed
Lines changed: 23 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
18 | 29 | | |
19 | 30 | | |
20 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
17 | 29 | | |
18 | 30 | | |
19 | 31 | | |
| |||
0 commit comments