# Lemonnade V3 (L3) design system > React + TypeScript components and design tokens for Lemonn, CS Pro and Kuber — mobile-first trading apps. Build UI only from these components and `--l3-*` tokens; read the rules first. Human docs: https://kirtanyak-pe.github.io/lemonnade-v3-docs/ · Figma: https://www.figma.com/design/lxQ6QIXGOv5mmx0khh5sJn/ · Everything in one file: llms-full.txt ## Rules - [Generate & redesign screens in Figma (L3 kit)](agent-docs/generate.md): Read first for any screen / flow / redesign task in Figma: the algorithm, the L3 JSX language and the rules the kit applies for you. - [Why the L3 kit: token & time audit](agent-docs/algorithm.md): Measured on 16 sessions: where tokens and time went, the principles, the pipeline and what it saves. - [Instructions for AI agents](agent-docs/agents.md): Read first. Workflow, non-negotiable rules and how to verify a screen. - [Figma → code map](agent-docs/figma-code-map.md): How every Figma component and property maps to a React component and prop. - [Design system rules](agent-docs/design-system.md): Spacing, layout, tokens, typography, components, states, accessibility, theming, Do/Don't. - [Design language (fintech)](agent-docs/design-language.md): How L3 screens look and read: principles, numbers, colour meaning, archetypes, the quality bar every generated screen is scored on. - [Learnings](agent-docs/learnings.md): Dated lessons from real runs (what was slow or wrong, the number, the fix). Read before repeating a task. - [Playbook: migrate, audit, extend, generate](agent-docs/playbook.md): Algorithms + tools: find a component, audit code/Figma, migrate old designs, swap components without moving layout, create components, generate screens. ## Foundations - [Colors](agent-docs/colors.md): How Lemonnade color works: semantic tokens by role, the 13 accent families, component tokens and the base palette — for all 10 themes (5 brands × modes, plus ♿ Accessible). - [Typography](agent-docs/typography.md): Manrope in three roles — Heading (750), Label (650) and Description (500) — 20 text styles, each a single font token. - [Spacing & radius](agent-docs/spacing.md): Spacing, radius, size and icon-size tokens from the Figma "🌌 Number" and "Icon size" collections. - [Layout](agent-docs/layout.md): How a screen is spaced: 16 page padding, sections 24 apart (32 for a bigger break), 16 from a section heading to its card, and 12 inside every card. - [Layering](agent-docs/layering.md): How surfaces stack, from the screen up to a second bottom sheet: what lives on each layer, its surface and its shadow. - [Icons](agent-docs/icons.md): The full Material Symbols set — Rounded, weight 400, grade 0, optical size 24dp, fill off (with the filled variant). Search, copy as SVG or download any icon. - [Brand logo](agent-docs/brand-logo.md): The Lemonn, Zing and Coinswitch logos: full (mark + wordmark) or just the mark, at 24–48px high. - [Color tokens](agent-docs/color-tokens.md): All semantic color tokens with values per theme. - [Typography tokens](agent-docs/typography-tokens.md): Every text style: Heading (750), Label (650) and Description (500). - [Spacing, radius & size tokens](agent-docs/spacing-tokens.md): Spacing, radius, size and icon-size values. ## Components · Patterns - [Selection](agent-docs/selection.md): How every component shows a choice: the shared cues, strong vs subtle, single and multi-select, which component for which job, and accessibility. ## Components · Action - [Button](agent-docs/button.md): Buttons let people take an action, confirm a choice or move forward in a flow. - [Button dock](agent-docs/button-group.md): A bar at the bottom of a screen or sheet that holds its main actions — stacked full-width, or side by side. ## Components · Input & control - [Checkbox & radio](agent-docs/checkbox.md): Checkboxes pick any number of options; radios pick exactly one from a group. - [Input field & text box](agent-docs/text-field.md): Fields let people enter text — a single line (input field) or several lines with a character counter (text box). - [OTP input](agent-docs/otp-input.md): OTP input takes a one-time code — 4 or 6 digits, one per box — for logins, verifying a number or bank account, and confirming risky actions. - [Toggle switch](agent-docs/switch.md): Switches turn a single setting on or off, and take effect immediately. - [Stepper](agent-docs/stepper.md): A number with − and + buttons, for quantity and lots in the order pad. - [Select](agent-docs/select.md): An inline trigger — a label and an icon — that opens a sheet of options. - [Date picker](agent-docs/date-picker.md): A month calendar for picking one day or a date range. ## Components · Navigation - [Tabs](agent-docs/tabs.md): Tabs switch between related views on the same screen. Underline tabs split a page into sections, pill tabs filter what's shown, and a pill group switches how content is shown. - [Actionbar](agent-docs/actionbar.md): The bar at the top of a screen: back, the screen title with an optional description, and up to a couple of actions — or a search field. - [Bottom navbar](agent-docs/bottom-navbar.md): The tab bar at the bottom of the app for moving between its main sections. Mutual Fund and F&O have their own sub-navs with a Home item back to the main bar. ## Components · Surfaces - [Card](agent-docs/card.md): A surface that groups related content. Clickable cards are one tap target; static cards just show information. - [Bottom sheet](agent-docs/bottom-sheet.md): A panel that slides over the screen to show a focused task — confirm an order, pick an option, see a result — without leaving the page. - [Overlay](agent-docs/overlay.md): The dimmed backdrop behind a bottom sheet or any other modal. ## Components · Feedback & status - [Tooltip & coach mark](agent-docs/tooltip.md): A tooltip is a short dark label that explains an icon or a number. A coach mark is a light card that points out a new feature once. - [Aerobar & toast](agent-docs/aerobar.md): A short status message with an optional action — inline as a full-width bar, or floating as a toast after something happens. - [Empty state](agent-docs/empty-state.md): What to show when a search or list has nothing in it: an illustration, a short heading and hint, and a way forward. - [Skeleton](agent-docs/skeleton.md): A grey placeholder that mirrors the layout while content loads. - [Loader](agent-docs/loader.md): A spinning ring for a step that is running and has nothing to draw yet — verifying a code, closing positions. - [Steps](agent-docs/steps.md): A vertical progress list for a process of a few steps — what’s done, where you are, what’s next. - [Progress bar](agent-docs/progress-bar.md): Shows how much of something is used or done, or where a value sits in a range. ## Components · Data display - [Badge](agent-docs/badge.md): A badge is a small count or dot on an icon, tab or row — unread notifications, open orders, something new. - [Avatar](agent-docs/avatar.md): An avatar shows a stock or company logo, a fund, or a person — a picture, initials or an icon — at the start of a row, card or header. - [List cell](agent-docs/list-cell.md): A row in a list: an icon, a label with an optional description, and something on the right — a chevron, a switch, a tag or a value. - [Key-value list](agent-docs/key-value-list.md): A key-value list shows facts as label → value: order details, fees and charges, account info, receipts — with an optional total. - [Tag](agent-docs/tag.md): Tags label, categorise or show the status of something in a compact form. - [Section header](agent-docs/section-header.md): The heading row of a section: a title, with an optional tag, info icon, short description and one action — View all or a switcher. - [Price change](agent-docs/price-change.md): A signed change — price, P&L or returns — in green when up and red when down. - [Chart](agent-docs/chart.md): Price charts — candles, line or area — and a small sparkline for lists. # Generate or redesign screens with the L3 kit **Agents: for any "design screens / a flow / redesign old screens in Figma" task, read only this file.** It replaces reading DESIGN_SYSTEM.md, the USAGE files, PLAYBOOK and LEARNINGS for that job — the kit applies those rules for you. Open another doc only for a question this file doesn't answer, and then only that section (`grep -n`). Why it works (docs/agent/ALGORITHM.md has the numbers): you write short **L3 JSX** (the React component names and props), a local compiler checks it against the design rules and fixes what it can, and **one `use_figma` call builds the whole flow** from real ✅ Lemonnade V3 instances, then checks it and returns snapshots. --- ## 1. The algorithm — follow it in order | # | Step | Tool | Budget | |---|---|---|---| | 0 | **Set up** — repo + this file. `git -C pull`; `npm ci` once if `node_modules` is missing. | Bash | 0 Figma calls | | 1 | **Intake.** New flow: list the screens (`"01 Positions"`, `"02 Settings"`…), what each shows, how they connect (push · bottom sheet · toast). Redesign: `npm run kit -- outline ` → paste the file it writes as **one** `use_figma` call → a text outline per old screen (§6). | — / 1 call | ≤ 1 call | | 2 | **Write `flow.jsx`** — every screen of the task in one file, in a work folder **outside the repo** (scratchpad). Content first: real-looking names, ₹ amounts, dates. | Write | 1 write | | 3 | **Compile** — `npm run kit -- build flow.jsx --parent ` (`--parent` overrides ``; one is enough). Fix every ✖ (edit the JSX), read the ⚠, note the ✓ (already applied). | Bash | until 0 ✖ | | 4 | **Build** — paste each `flow*.figma.js` it wrote, **whole and unchanged**, as the `code` of one `use_figma` call (in order). | use_figma | 1 call per file (≈ 12–15 screens) | | 5 | **Review** — the result lists every screen (`id`, `size`, `issues`) plus a 0.5× snapshot of each. Fix by **editing `flow.jsx`**, then rebuild only those screens: `--only "03 Confirm,04 Done"` (rebuilt frames replace the old ones in place). | Edit + 3–4 | ≤ 2 rounds | | 6 | **Report** — what was built (screen names + ids), auto-fixes worth knowing, open questions (missing components, `PENDING` rules). | — | — | **Stop and ask** instead of improvising when: something you need isn't in §3 (don't fake a component), the outline shows a pattern with no L3 equivalent, or you're past the budgets above (something is wrong — say what). ## 2. Cost rules — why the budgets matter - **Every API call re-reads your whole context.** Cost ≈ context size × number of calls (in our sessions 73% of all tokens were these re-reads). So: few calls, small context. One call that builds 12 screens beats 12 calls. - **Don't explore.** Component keys, variants, slots, text styles, colour/spacing tokens and 190 icons are already in the kit. Never `search_design_system` for components, never `get_design_context` / `get_metadata` on whole frames, never screenshot old screens (use the outline), never read the kit's source. - **Images only from the build result** (already 0.5×). Need to look closer? `get_screenshot` one node, `maxDimension` ≤ 800. - **Long outputs land in files** (Bash > ~30 kB is persisted and must be re-read) — don't print big things. - **One task per session.** The repo's `.claude/settings.json` compacts at ~250k tokens; start a fresh session for a new flow. - **Tiny tweak** (one text, one toggle) on a kit-built screen? Still edit `flow.jsx` and rebuild that one screen — the JSX stays the source of truth and the kit keeps every rule. ## 3. L3 JSX — the language The **component elements and props are the React components'** (`src/components`, `docs/figma-code-map.md`). Props take `"text"`, `{number}`, `{true}`, `{[…]}`, `{{…}}` or `{}`; a bare prop is `true`. Comments: `{/* … */}`. Layout helpers (`Flow Screen Section Stack Row List Text Icon KeyValue Stats Divider Placeholder`) exist only in the kit. ### Structure | Element | Props | Notes | |---|---|---| | `` | `name` · `parent` · `width` · `height` · `theme` · `gap` | Optional wrapper: makes / reuses a Section named `name` on the page `parent` and lays screens out in a row. | | `` | `name` (unique) · `width` 360 · `height` 800 · `base` · `theme` · `gap` 24 · `clip` | Top: `Actionbar`. Bottom: `ButtonGroup` **or** `BottomNavbar` **or** `Keyboard`. Everything else is the body (16 top, 24 apart). Taller content grows the frame. `base="02 Settings"` starts from that built screen (for sheets over it). `theme="LM Dark"` / `"CS PRO Dark"` / `"Kuber Light"`. | | `
` | `title` · `description` · `tag` · `action` (`"View all"` or `{{ type: "switcher", label: "1D" }}`) · `info` · `gap` 16 | A `SectionHeader` + its content, 16 apart. Use for every titled block. | | `` / `` | `gap` 8 · `pad` (`16` · `"12 16"` · `"page"`) · `align` · `justify` (`between`…) · `wrap` · `bg` · `radius` · `border` | Auto-layout frames bound to spacing tokens. Children of a `Row` hug; give one `grow` to take the rest. | | `` | `variant` (`card`) · `density` (`compact` · `breathable`) · `gap` | Optional: consecutive `ListCell`s are grouped for you. Asset rows (a `PriceChange`/`Sparkline` trailing) become Breathable. | | `` | `style` (`Heading/10–36` · `Label/10–18` · `Description/10–18`) · `color` · `align` · `lines` | Default `Description/14`, primary. Numbers are `Label` (or `Heading` when prominent ≥ 18), never `Description`. | | `` | `label` · `value` · `valueStyle` · `change` (number or `{{ value, unit, percent, size }}`) · `align` | Label over value (+ PriceChange). | | `` | `items={[["Invested","₹44,220.50"],…]}` · `columns` 2 · `card` (`false` = no grey card) | Grid of KeyValues in a filled card. | | `` + `` | list: `variant` plain · card · `size` sm · `total={{ label, value }}` — row: `label` · `value` · `caption` · `info` · `copy` · `layout` stacked · `tone` up · down | L3: Key-value list / row. Facts people read (order details, charges, account info, receipts). Stacked for long values (UPI ID, address). Card = static card, no shadow. | | `` | `name` · `size` 24 · `color` | Material name from the Icons library (`npm run kit -- icons `), or a custom icon: `kill_switch` · `auto_tp_sl` · `pnl_protect` · `watchlist_add` · `watchlist_added` (only for that feature). | | `` | `initials` · `icon` · `size` 24 · 32 · 40 (default) · 48 · `shape` circle · rounded · `src` | L3: Avatar — stock / company logo, fund or person. Type follows the content (icon → Icon, initials → Initials, else the grey Image layer: set the logo as its fill). In rows: `}>`; in a card header: first child of a `Row`. | | `` | `content` (or children) · `placement` top · bottom · `arrow` start · center · end · `close` | L3: Tooltip — a one-line dark hint (≤ ~40 chars) pointing at an icon or number. `placement="bottom"` = the bubble sits below its target (arrow up). Put it right after the row it points at; `arrow` lines it up (end = under a right-hand icon). `close` shows ✕. | | `` | `heading` · `body` · `step` ("1 of 3") · `skip` · `nextLabel` · `skipLabel` · `placement` · `arrow` | L3: Tooltip Type=Coach mark — points out a new feature once. Same placement rules as Tooltip. | | `` / `` | `inset` / `label` · `height` | Placeholder = grey box for real artwork you don't have (reported). | Colours (`color`, `bg`, `border`): `primary` `secondary` `tertiary` `disabled` `inverted` `up`/`profit` `down`/`loss` `success` `error` `warning` `discover` `brand` `zing`, or a token path (`surface/secondary`, `border/light`). Common per-element props: `name` (layer name), `grow` / `fill` (in a Row), `width`, `height`, `hug`, `bleed`. ### Components (L3 library) | Element | Key props (defaults first) | The kit also… | |---|---|---| | `Actionbar` | `title` · `back` · `description` · `actions={["search", {icon:"settings",label:"F&O settings",variant:"ghost"}]}` (≤ 2; or `` children) · `bottom={}` · `titleSelect="NIFTY 50"` | L1 (no `back`) → Heading/18 "L1 page heading"; L2 → Heading/14. Actions: Tertiary Small boxed (`variant:"ghost"` only if asked). Underline Tabs placed right under it move into `bottom`. | | `Button` | children (label) · `variant` primary · secondary · tertiary · ghost · brand · buy · sell · `size` lg/md/sm · `icon`/`iconLeft` · `iconRight` · `disabled` · `loading` · `label` (icon-only name) · `full` | Hugs outside docks (`full` to stretch); size md inside cards/sheets/margins, lg elsewhere; placeholder icons hidden. | | `ButtonGroup` | `direction` vertical · horizontal · `note` (text above the dock) | The dock. Buttons → lg; strong button (primary/buy/sell/brand) on top / on the right. One per screen. | | `Tabs` | `items={["A","B"]}` or `[{label, subLabel, icon}]` · `appearance` pill · underline · pill-group · ribbon (special — only when the brief names it; never pick it yourself) · `value` (index or label) · `emphasis` · `size` sm · `behavior` | Pill rows run edge to edge with their own 16 inset — never doubled inside a margin. Only rows that act as tabs fill the width (equal widths if they fit, hug + scroll if not); filters and toggles hug. `behavior` tabs · filter · toggle — defaults: underline = tabs, pill = filter, pill-group = toggle, ribbon = tabs that hug (folder tabs on a dark baseline); set `behavior="tabs"` on a chip row that switches sections. A chip row first in the body moves into the Actionbar (12 top/bottom); inside the content it has 0. | | `ListCell` | `label` (one line ≤ ~32) · `description` · `size` md/sm · `variant` plain · card · `iconLeft` (icon name or element) · `iconRight` (chevron) · `trailing` (`"switch:on"` · `"switch"` · `"radio:on"` · `"check"` · `"value text"` · `` · `[…]`) · `labelSuffix` (side: "Long") · `labelSuffixTone` up · down · neutral · `caption` (second line under `trailing`) · `selected` · `tappable` · `dotLeft`/`dotRight` · `density` | Navigation rows get `chevron_right` (the library default is ⌄). Plain rows inside cards / sheets / margins drop their own 16 inset. | | `Card` | `variant` static · clickable · flat · filled (or `clickable`) · `padding` (`"none"`) · `selected` · `footer={[]}` · `footerFilled` · `gap` 8 · `direction` (`row`) | Footer buttons Tertiary Small, 12 from the edge. Content 12 from the edge. | | `SectionHeader` | `title` · `description` · `tag` · `info` · `action` | Prefer `
`. | | `Tag` | children · `color` neutral · profit · loss · success · error · warning · discover · processing · zing · indigo · teal · purple · `variant` secondary · primary · tertiary · `size` sm/md/lg · `icon` · `hideLabel` | Never tappable. No green/red/yellow/orange. | | `PriceChange` | `value` (signed number) · `unit` percent · currency · number · `percent` · `size` sm/md/lg · `arrow` · `decimals` · `text` | Sign + colour from the value; ₹ with Indian grouping. Never colour a change by hand. | | `TextField` | `label` · `hideLabel` (label stays its name, not drawn — an amount field inside a row) · `placeholder` · `value` · `helperText` · `status` error · success · `disabled` · `required` · `multiline` · `iconLeft`/`iconRight` | | | `Select` | children · `size` sm/md/lg · `subtle` · `icon` swap · chevron | Inline trigger (opens a sheet of Radio rows). No boxed dropdown exists. | | `Stepper` | `value` · `size` sm/lg · `sublabel` · `min` · `max` · `label` | | | `Switch` · `Checkbox` · `Radio` | `checked` · `size` (Switch) · `indeterminate` · `disabled` | In rows use `ListCell trailing="switch:on"`. | | `Aerobar` | `type` discover · primary · success · warning · danger · `emphasis` · `heading` · `paragraph` · `icon` (name or `false`) · `action` · `tappable` (whole bar/card opens more: chevron on the right, no `action` with it) · `floating` · `placement` bottom · inline | Full-width bar → always placed at the bottom above the dock / navbar (auto-moved). `floating` = toast above the dock/bar with shadow; `floating placement="inline"` = flat card inside the content (a full-width bar written inside content is auto-converted to this). Pick by use case: result / failed+retry → toast; heads-up, action required, system status → full-width bar. | | `EmptyState` | `title` · `description` · `action` | Only for *nothing to show* — its illustration is always the L3 default empty-state illustration (built in; never pick or draw another). Never for a success / result. | | `BottomSheet` | `heading` · `description` · `size` sm · lg · `icon` (lg) · `back` · `trailing` · `bottom` · `footer={…}` · `utility` · children (or `header={}`) | Inside a `Screen`: scrim + sheet over it. ≤ 2 per screen; the 2nd gets a back button. No ✕, no handle. Confirmations are sheets (no dialogs). | | `ProgressBar` | `value` · `type` progress · range · `status` · `size` | | | `Chart` / `Sparkline` | `type` candle · line · area · `trend` up · down · `ranges={["1D","1W","1M"]}` · `showVolume`… | Charts run edge to edge. | | `Skeleton` · `SkeletonListRow` · `SkeletonCard` | `shape` | Loading states. | | `BottomNavbar` | `value` stocks · market · portfolio · mf · fno (· `fno-positions`…) | | | `BrandLogo` · `Keyboard` · `SystemStatusbar` · `DatePicker` | `brand`, `variant` · `numeric` · `dark` · `mode`, `month` | The Actionbar already includes the status bar. | **Not in L3** (stop and ask): sliders (dropped — never build one), dialogs, boxed dropdowns, OTP boxes (`OtpInput` is in code; the kit can't place it until the Figma library is published — use `TextField` for now), stock-logo pictures (`` places the grey Image layer — the real logo is set in Figma), illustrations other than EmptyState's. ## 4. Choosing — the judgment the kit can't make | Need | Use | |---|---| | Screen title + back / actions / top tabs | `Actionbar` (`back` on every non-top-level screen) | | A titled block of content | `
` (never a big `Text`) | | Main action(s) of a screen or sheet | `ButtonGroup` — **one primary per screen**; a Cancel next to it is `secondary` | | Standalone action ("View all", "Add another") | `Button variant="tertiary"` (in a Section: `action="View all"`) | | A tappable item that opens something | `ListCell` with `iconRight` (or a clickable `Card` for rich content) | | On/off setting · pick one · pick many | `ListCell trailing="switch:on"` · `trailing="radio:on"` rows (card rows for options) · `trailing="check:on"` | | Choose between views (filters, segments) | `Tabs appearance="pill"` (top-of-screen sections: `underline` in the Actionbar). Only rows that switch sections fill the width (`behavior="tabs"`); filters and toggles hug | | Grouped facts / numbers | `KeyValueList` (label → value rows, optional total); a 2-column number grid → `Stats` | | Any signed change, P&L, returns | `PriceChange` | | Status: heads-up · action required · system status → full-width `Aerobar` (bottom); result of an action → `Aerobar floating` (toast); note inside content → `Aerobar floating placement="inline"` | | Result of a flow (added, placed, submitted, paused…) | `` over the last screen — never `EmptyState` (failure: `icon="error"` `iconColor="error"`) | | A form | `Stack gap={16}` of `TextField`s (`value` filled = typed state, `helperText` for hints / verified names), the submit `Button` in the `ButtonGroup` dock | | Facts in a confirm / result sheet | `KeyValueList` (or plain `ListCell label trailing="value text"` rows, no `iconRight`) | | Confirm before acting | `BottomSheet` over the screen: question heading, `ListCell` facts, horizontal `ButtonGroup` (secondary Cancel + strong confirm) | | Nothing to show / loading | `EmptyState` / `SkeletonListRow` | **Status messages (Aerobar) — placement, shadow, leaving, by use case:** - Full-width bar: always at the bottom, just above the dock / navbar (or the screen edge) — never at the top (the kit moves it). Floating at the bottom has a shadow; floating inside the content or near the top is flat (`placement="inline"`). - Toasts leave on their own (4 s, 6 s with an action). Full-width bars stay until ✕, a button (OK / Dismiss), doing the task, or the state changing. - Result of an action (Order placed, Copied) → toast, solid success/primary. Failed, can retry → toast, solid danger + Retry (blocking failure → result sheet). Heads-up, time-bound (Market closes in 15 min) → full-width + Dismiss. Action required (KYC pending, add bank, margin shortfall) → full-width, the fix as `action`, no ✕. System status (offline, maintenance) → full-width while true. Note inside content → floating inline. - One full-width bar (action required → system status → heads-up) and one toast per screen. **Rows of the same kind:** order / trade history → `ListCell size="sm"` with `labelSuffix` (Long / Buy green, Short / Sell `labelSuffixTone="down"`), a status `Tag` as `trailing` with the time as `caption`; amounts as `trailing` with their type as `caption`. Content: one-line labels; sentence case; numbers as `₹1,23,456.78`, `+2.40%`; dates `14 Oct 2026`, `3:30 PM`. Write the copy a real user would see — no lorem ipsum, no "Label". ## 5. Example — paste-ready, compiles clean ```jsx
} /> } />
``` More, from real product screens: Lemonn Portfolio → Orders (cards, chip tabs, navbar), the Tata Motors stock page (chart, holding, SIPs, Sell / Buy dock) and Kuber Orders & History with its order-details sheet and cancel toast — `scripts/figma/kit/examples/real-screens.jsx`. ## 6. Redesigning old screens 1. `npm run kit -- outline ` → paste `outline.figma.js` as one `use_figma` call (read-only). Each screen comes back as text: `⧉ instance props`, `"text"`, `▢ frame ↓gap p t/r/b/l`, repeats `×N`. Old D2 parts keep their names. 2. Map what each block **does** to §4 (not how it looks): a grey stat block → `Stats`; a row of chips → `Tabs pill`; a bordered row with a chevron → `ListCell variant="card" iconRight`; an old CTA bar → `ButtonGroup`. 3. Keep the copy and the order; drop decoration the system doesn't have; note anything with no L3 equivalent. 4. Build the redesign next to the old section (``), never over it. In-place migration that must keep the old layout (swap components, rebind tokens) is a different job: PLAYBOOK §4. ## 7. When something goes wrong | You see | Do | |---|---| | `✖ isn't an L3 element` | Use §3 / §4. Missing for real → stop and ask. | | `✖ icon "x" isn't in docs/agent/icons.json` | `npm run kit -- icons ` for the right name. Not there → one `search_design_system` in "👁️ Lemonnade V3 → Icons", add the key to `icons.json`. | | `issues: truncated: "…"` | Shorten the label; move detail to `description`. | | `issues: off-screen` / `placeholder` | Too much in a `Row` (give one child `grow`, drop one) / a prop you left empty. | | `⚠ X` note drawn in a screen, or `errors` in the result | The kit failed on that element — the rest was built. Fix the JSX; if it's the kit, log it in docs/LEARNINGS.md. | | `time budget — not built: …` | Re-run with `--only` for those screens. | | `base "…" not found` | Build the base screen first (same file is fine) or check its name. | After the task: add one line to `docs/LEARNINGS.md` if something cost you time, and fix the kit (`scripts/figma/kit/`) or this file so the next agent doesn't pay for it again — then `npm run skill` so the shareable skill (`plugin/`) gets the fix too. --- # The L3 generation algorithm — why it's built this way The operating manual is `docs/agent/GENERATE.md`. This file is the reasoning and the evidence, for whoever maintains the kit (people or agents). Built 2026-10-10/11 from an audit of every Claude Code session in this project. ## 1. What we measured 16 sessions (Sep 25 – Oct 10, 2026), 8,348 model calls, read with `scripts/token-audit.py`. | Where the cost went | Tokens | Share of cost* | |---|---|---| | Re-reading the conversation on every call (cache reads) | 3,825 M | **73 %** | | Writing new context into the cache | 50 M | 19 % | | Model output (thinking, text, tool calls) | 8.2 M | 8 % | \* weighted by price: cache read 0.1×, cache write 1.25–2×, output 5× an input token. **Cost ≈ context size × number of calls.** Sessions ran with a 1M-token window and compacted only near the limit, so the average call re-read 250k–510k tokens. Everything below follows from that one fact. What the two sessions that generated screens from this repo did (Kill Switch, 25 frames · Account setup, 28 screens): | Phase | What happened | Cost | |---|---|---| | Bootstrap | "Read all the rules": AGENTS, DESIGN_SYSTEM, PLAYBOOK, LEARNINGS, USAGE files, code map, recipes… | context 70k → 170k in the first minute, carried by every later call | | Exploration | registry, component internals, `search_design_system` for icons (12k-token results), screenshots of old screens (40 images) | 20–40 calls before the first build | | Reinventing | each session hand-wrote its own 10–43 kB helper library (`I()`, `P()`, `cell()`, `sheetOver()`…) and re-sent it in every build call | ~15k characters per call | | Fix loops | component quirks found one at a time: a FILL text centring itself (7 calls), Tabs min-width 328 (4 calls), buttons stretching, default icons showing | 10–25 small calls, each re-reading 300k+ | | Result | 68 calls, peak 385k · 106 calls, peak 477k | | ## 2. Principles 1. **Few calls, small context** — the bill is context × calls; a call that builds 12 screens beats 12 calls. 2. **Knowledge as code, not as reading** — every rule a machine can apply lives in the compiler or the runtime, not in prose an agent must load (L1/L2 titles, dock order, hug vs fill, density, insets, chevron rows, icon visibility…). 3. **Precompute, never explore** — component keys, variants, slots, styles, tokens and 190 icon keys ship with the kit. 4. **Spec, not imperative code** — the agent writes ~120 tokens of L3 JSX per screen; the runtime does the Plugin API work. 5. **Validate before you spend** — the local compiler (zero tokens) rejects what would be wrong; the build call checks what it built and returns only problems + small snapshots. 6. **Exceptions only** — results list issues, not inventories; outlines replace screenshots for reading old screens. 7. **Fix the system, not the instance** — a wrong result becomes a compiler rule, a runtime fix or a selftest case. 8. **Judgment stays with the agent** — choosing components and writing copy is the only thinking left, so it still works at medium effort. ## 3. The pipeline ``` brief / old screens ──► intake ──► flow.jsx ──► npm run kit -- build ──► flow.figma.js ──► use_figma (1 call) (GENERATE.md) outline ~120 tok parse · lint · fix runtime + build · place · (1 call) per screen tree-shake · check plan, < 49 kB check · snapshot │ ◄──────────────────────── edit flow.jsx, rebuild --only the changed screens ◄──── issues ``` | Piece | File | Runs | Does | |---|---|---|---| | Brief | `docs/agent/GENERATE.md` | read once | The only doc an agent reads for the job: steps, budgets, language, choices | | Compiler | `scripts/figma/kit/compile.js` | Node, 0 tokens | Static JSX parser, design-rule lint (errors · warnings · auto-fixes), row grouping + density, plan | | CLI | `scripts/figma/kit.ts` (`npm run kit`) | Node | `build` (bundles only the runtime regions a flow uses + the keys it needs, splits at 49 kB, syntax-checks), `lint`, `outline`, `icons`, `jsx`, `selftest` | | Runtime | `scripts/figma/kit/runtime.js` | inside `use_figma` | Preload (parallel imports, fonts, tokens) → screens from L3 instances → placement rules → check → snapshots. No eval | | Outline | `scripts/figma/kit/outline.js` | inside `use_figma` | Old screen → compact text (≈ 1k tokens/screen) instead of screenshots / metadata | | Icons | `docs/agent/icons.json` | data | 190 icon keys from "👁️ Lemonnade V3 → Icons", mined from past search results | | Regression | `scripts/figma/kit/examples/*.jsx` + `selftest` | Node | Every element documented + rendered; every doc example compiles clean | ## 4. Results | | Before (Kill Switch session, 25 frames) | After (kit benchmark, 6 screens) | |---|---|---| | Docs loaded to start | ~100k tokens | GENERATE.md ≈ 7k tokens | | Exploration before building | ~25 calls | 0 | | What the agent writes per screen | ~1–2k tokens of Plugin API code | ~120 tokens of JSX (6 screens = 2.4 kB) | | Build calls | 6 (2–5 frames each) + 10–25 fix calls | 1 per ≤ ~15 screens; fixes = JSX edits + `--only` rebuild | | Rules applied | by the agent, from prose (missed: one-line labels, chevron rows, hug…) | by the compiler/runtime, every time | | Build time | ~2 min per call | **6 screens in 8.2 s** (5.2 s preload + 0.1–0.7 s per screen); was 55 s before the text-style fix | ## 5. Constraints found on the way (and what we did) - **No resident code.** Storing the runtime inside the Figma file and `eval`-ing it (it works technically: shared plugin data, 100 kB per entry) was blocked by Claude Code's auto-mode classifier as a remote-code-execution surface — fair: code persisted in a shared file could be swapped. So every build call carries its runtime; the CLI tree-shakes it to what the flow uses (~25–35 kB) and the budget is spent on more screens per call instead. - **Subagents can't reach the claude.ai Figma connector** in the desktop app (they see the plugin server, which needs its own auth). A cheap "runner" subagent that pastes the build file would keep it out of the main context; it works only where subagents have Figma access. - **`use_figma` code is capped at 50,000 characters** → the CLI splits flows into several files. - **The first `setTextStyleIdAsync` of every call stalls ~29 s**; the synchronous `textStyleId` setter takes ~2 ms. Fixed in the kit and in `lib/core.js` (`applyTextStyle`) — this was the unexplained "~60 s per build" in LEARNINGS. - **A FILL child silently turns a hugging auto-layout parent into a fixed one** (the body must only grow after the screen is measured), and **`findAllWithCriteria({types:['TEXT']})` can return instances** inside slots (filter by type). ## 6. Distribution `npm run skill` packages the whole method as a standalone skill — `plugin/skills/lemonnade-l3-figma/` (SKILL.md from this brief, the kit CLI as plain ESM, compiler, runtime, outline, library keys, icons, examples; ~140 kB, zero dependencies) plus `plugin/lemonnade-l3-figma.zip` and a plugin marketplace (`.claude-plugin/marketplace.json`). The build runs the skill's own selftest from its folder, so a broken package never ships. ## 7. Session hygiene - `.claude/settings.json` sets `autoCompactWindow: 250000`: compaction near 250k instead of ~1M — the average call re-reads roughly a third of what it did. Sessions started outside the repo can set the same in `~/.claude/settings.json` (or `CLAUDE_CODE_AUTO_COMPACT_WINDOW=250000`). - One task per session; a new flow starts fresh with GENERATE.md. - Big tool outputs (> ~30 kB) are persisted to files and must be re-read: print summaries, not dumps. ## 8. Keep it improving 1. After each run: one line in `docs/LEARNINGS.md` (what cost time, the number, the fix). 2. Turn it into a rule: compiler lint/fix (`compile.js`), runtime behaviour (`runtime.js`), or a brief line (GENERATE.md). 3. `npm run kit -- selftest` must pass; add an example under `scripts/figma/kit/examples/` for anything new. 4. Re-measure with `python3 scripts/token-audit.py ~/.claude/projects/` — calls per screen, peak context. Next candidates: render the same JSX to React for code prototypes (layout helpers as tiny token-only wrappers); per-call versions of `swap` / `migrate-tokens` through the same tree-shaking bundler; a Figma-side "L3 Title" element. --- # Instructions for AI agents This repo is the **Lemonnade V3 (L3) design system**: React + TypeScript + Vite components in `src/components`, design tokens in `src/tokens`, and a docs site in `src/docs`. When you build UI here — a screen, a flow, a new component, or a Figma design — follow this file. > **Designing screens, a flow or a redesign in Figma?** Read only [`docs/agent/GENERATE.md`](docs/agent/GENERATE.md) and > follow it. You write short L3 JSX, `npm run kit` checks it against the rules below and fixes what it can, and one > `use_figma` call builds the whole flow from real L3 instances. Don't load the other docs for that job (why: > `docs/agent/ALGORITHM.md`). ## 1. Read before you build 1. **`DESIGN_SYSTEM.md`** — spacing scale, layout primitives, token rules, card/actionbar rules, states, a11y, theming, icons, responsive rules, Do/Don't. 2. **`src/components//USAGE.md`** for every component you use — which variant/size to pick, labels, placement, states. (Written so far: `Button`, `ButtonGroup`, `BottomSheet`, `Card`, `ListCell`, `Loader`, `Steps`. For others, use the component's docs page / playground in `src/docs`.) 3. The component's props (`src/components//.tsx`). TypeScript enforces several rules — if a combination doesn't compile, it's not allowed; don't cast around it. **Outside this repo** (Claude Design, ChatGPT, web agents): the same knowledge is published as plain text — `llms.txt` (index) and `llms-full.txt` (everything) on the docs site, generated by `npm run agent-docs` from the docs data, AGENTS.md, DESIGN_SYSTEM.md and the USAGE files. Edit those sources, never the generated files. Sections marked `PENDING` are **undecided**. Do not invent rules for them. If your task depends on one, stop and ask, or list it under "Open questions" in your answer. ## 2. Non-negotiable rules - **Components first.** Use an existing component whenever one exists; never hand-roll a button, tab, card, sheet, toast, field, etc. Don't restyle a component's internals — use its props. - **Tokens only.** No hex/rgb, raw px spacing, font sizes or shadows in styles. Only `--l3-*` variables (the one accepted literal is `1px` for hairlines). Missing value → add it to `src/tokens/source/local.*.json`, run `npm run tokens`, and say so. - **Screen layout:** page padding 16 left/right; sections 24 apart (32 for a bigger break); section heading → its card 16; card padding 12 on every side. Gaps are the container's auto-layout gap, bound to spacing tokens. Section titles are always `SectionHeader` (title required; tag / info / description ≤ 2 lines / one action — View all or a switcher — optional). Pill tabs: first chip at the 16 page margin — never container 16 + the tabs' own 16 inset. - **Selection:** one surface cue (border-dark or fill) + the control's mark; strong (primary pill) for filters, subtle (border-dark) for options; single → radio/tabs, multi → checkbox rows (DESIGN_SYSTEM 7.1b). - **Layering:** screen → inset (filled) → content (static) → raised (clickable, shadow low) → bars → toast → scrim → sheet → second sheet (max 2). Higher = heavier shadow; only tappable things are lifted (DESIGN_SYSTEM 6.1). - **Mobile first.** Build at 360px wide (mockups 360×800), then check 392 and 412. Touch areas ≥ 48 × 48 and invisible — never add padding or height for them (ghost buttons and Select hug their content). Hover only inside `@media (hover: hover)`. - **Both themes.** Check light and dark (and ♿ Accessible, `data-contrast="accessible"`); only semantic tokens (`surface`, `content`, `border`, `component`). Overlays/backdrops use `surface/overlay`. - **Accessible.** Every control has a name; headings in order; status via `Aerobar`. - **Don't invent patterns or components.** If something you need doesn't exist (e.g. a slider or a carousel), say so and ask — don't build a one-off lookalike inside a screen. Select, Stepper, DatePicker, OtpInput, Avatar, Badge, KeyValueList, Tooltip / CoachMark, Skeleton, Loader, Steps, ProgressBar, Chart / Sparkline, PriceChange and Overlay exist now. Key component rules (details in the USAGE files / DESIGN_SYSTEM.md): - **Button:** one `primary` per screen; `secondary` only next to a stronger button (primary/buy/sell/brand), usually in a dock; standalone actions like "View all" are `tertiary`; buttons in a dock / `ButtonGroup` are always `lg`; `buy`/`sell` only for trades; an icon button has no label, exactly one icon and an `aria-label`; `ghost` hugs its content in every size (no padding, no fixed height) and relies on its 48 × 48 touch area. - **ButtonGroup (Figma "L3: Button Dock"):** the main action(s) of a screen or sheet, one per screen, always `lg`; at least one strong button (primary/buy/sell/brand); horizontal → strong on the right, vertical → strong on top; needs `aria-label`. - **Actionbar:** flat tabs at the top go in its `bottom` slot; at most 2 actions — Tertiary Small boxed by default (`ActionbarAction`), Ghost only when the design asks. Title: Heading/18 on an L1 screen (no back / ✕), Heading/14 on an L2 screen (back or ✕) — automatic from `onBack`. The screen's content container always fills the screen. - **BottomSheet:** no drag handle and no ✕ — it closes by tapping the backdrop or dragging the sheet down (the component also handles Esc and a hidden screen-reader Close button). Tabs or search at the top go in `BottomSheetHeader`'s `bottom` slot; the `footer` is a `ButtonGroup`. **Confirmations are bottom sheets — there is no dialog** (review: question header, ListCell facts, secondary Cancel + strong confirm; result: `lg` header). At most 2 sheets stacked: the first sheet has no back button, only a second sheet on top of it has one. Never open a third. - **Accent groups** (DESIGN_SYSTEM.md 3.1): Brand · Market indicators (profit/loss) · Status (success, warning, error, discover, orange) · Sub-brands (us-stock, zing) · Miscellaneous (purple, indigo, teal — exceptional cases only). Choose by meaning, never by look. - **Tag colors:** `profit` / `loss` for price moves, P&L and buy/sell side; `success` / `error` for outcomes (placed, failed); `processing` for in-progress (open, pending); `warning`, `discover`, `zing` as named. Don't use the old names `green` / `red` / `yellow` / `orange` (deprecated). Tags are never tappable. - **EmptyState:** always its default illustration (Figma "L3: Empty state default illustration", 5599:7131) — never other artwork; `illustration={null}` only to hide it. - **Ribbon tabs** (`appearance="ribbon"`) are a special case: only when the design or brief names them. Never pick them yourself in generated UI; sections use `underline`. - **Icon-only Tag / Tab:** `hideLabel` + exactly one icon; the label text is still required (it's the accessible name). `subLabel` is for chip (pill) tabs only. - **Containers:** large blocks are `surface-default` + `border-light`. Grey (`surface-secondary` / `tertiary`) is for small elements inside a card — tags, chips, small highlight boxes. A large grey area (section, full-width strip, big panel) only when it's intentional and high-emphasis — never as a default background. - **Card:** clickable → `surface-primary` + `border-light` + `elevation-low` + press scale; a card with one action is a clickable card (no buttons in its content — quick actions go in an action footer, `footer` — no fill by default, a lone button is Tertiary; `footerFilled` grey strip only for intentional emphasis); static (rounded + border) → `surface-default`; flat → transparent, no border/shadow; filled → grey inset panel (`surface-secondary`, no border/shadow) for grouped details. Selected (chosen option in a list of choices, clickable only, `selected`): bordered → `border-dark` instead of `border-light`; flat → `surface-secondary` (flat is otherwise unfilled). Cards sit inside a margin; padding 12 — a clickable/static card may use padding="none" only when its sections bring their own (content still 12 from the edge); filled always padded; flat runs edge to edge. **ListCell** follows the Card rules: plain = flat (no fill, edge to edge); card row not tappable = static (no fill + border-light); tappable = clickable (surface-primary + border-light + elevation-low, press 0.98); `selected` → plain `surface-secondary`, card `border-dark`. ## 3. Workflow for UI tasks 1. **Plan first.** List the screens, what each contains, the transitions (push, bottom sheet, toast), the components you'll use, and anything missing or `PENDING`. For more than one screen, show this plan before building. 2. **Build.** Screens as React components (docs demos live in `src/docs/demos.tsx` and are registered in `src/docs/pages.tsx`). Use real-looking content and cover empty / loading / error states. 3. **Verify** before saying you're done: - `npx tsc -b --noEmit` and `npm run build` pass. - No raw colors in your CSS: `grep -nE '#[0-9a-fA-F]{3,8}\b|rgba?\(' ` returns nothing (except `rgb(from var(--l3-…))`). - Look at it at 360 / 392 / 412 in light and dark (`npm run dev`, then the docs page or demo). 4. **Report** what you built, what you verified, and any rule you had to bend or any `PENDING` decision you hit. ## 4. Figma work - **Figma → code:** `docs/figma-code-map.md` maps every Figma component and property to the React component and prop (e.g. Button `Type=◻️ Primary, Size=Large` → `