Skip to content

Commit bf6280b

Browse files
Marcosldclaude
andauthored
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

File tree

doc/llms.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ This file is the main entry point. All docs live at:
1515
If you cannot find a documentation file in `node_modules`, fetch the equivalent file from the GitHub
1616
repository at `https://github.com/Telefonica/mistica-web/blob/master/doc/<filename>`.
1717

18+
## Source code
19+
20+
Source is available at `node_modules/@telefonica/mistica/src/` (fallback:
21+
`https://github.com/Telefonica/mistica-web/tree/master/src`). Use `src/index.tsx` to map imports to files.
22+
23+
Read source only when you need to understand internal behavior that docs and types don't cover — for example
24+
debugging layout/CSS issues (`*.css.ts` files), understanding event handling, or authoring a custom skin (read
25+
a real skin in `src/skins/` to see canonical token patterns). Normally you shouldn't read source for
26+
greenfield UI work — the docs already cover that, and reading source tends to pull toward low-level APIs when
27+
a documented composite component would be simpler.
28+
1829
## Critical Rules
1930

2031
1. **NEVER hardcode colors in app/component UI code.** Always use `skinVars.colors.*` design tokens from

package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@
1414
"dist-es/**",
1515
"css/**",
1616
"doc/**",
17+
"src/**",
18+
"!src/generated/**",
19+
"!src/**/__tests__/**",
20+
"!src/**/__acceptance_tests__/**",
21+
"!src/**/__screenshot_tests__/**",
22+
"!src/**/__type_tests__/**",
23+
"!src/**/__stories__/**",
24+
"!src/**/__private_stories__/**",
25+
"!src/**/*-test.ts",
26+
"!src/**/*-test.tsx",
27+
"!src/**/*-story.ts",
28+
"!src/**/*-story.tsx",
1729
"community.d.ts",
1830
"community.js"
1931
],

0 commit comments

Comments
 (0)