Pantrix UI

Teaching Pantrix to an assistant

The documents written so that coding assistants (Claude, Copilot, Cursor…) learn Pantrix from the library's own sources rather than by guessing.

Everything said here was seen in tests and builds run in this repository. Section 9 says what was seen where. The server and its documents are published with the library, in its version, as com.pantrixui:mcp (section 4).

1. The problem

An assistant that has not read the library's sources:

A document written by hand solves part of this, but goes stale as the library changes. A parameter is renamed, the document keeps the old name, and the assistant writes the old name.

2. What is written

FileWhatWhat for
llms.txtThe index, in the format of llmstxt.org. What the library is, its artifacts, the tools published with it (the detekt rules and this server), the smallest app and the rules code that uses Pantrix keeps to; a line for each page of the demo under its group (its link, the first sentence of its summary, the component's name); a line for each guide (its link, the first sentence of its summary); the modules' API references; the skill's link. About 33 KB, some 8,500 tokens.The file an assistant reads first: it says where everything is.
llms-full.txtAll of it in one file: the same opening, the demo's 118 pages in the order of its menu, the 24 guides, then the API no page documents, by module. It holds no links. About 4.7 MB, some 1.3 million tokens.A model with a very wide context, or loading into a document index (RAG).
pages/<key>.mdA page of the demo: its summary, its features, the component's short examples, the compiled examples (with their imports and the code of the helpers they use), and the API the page documents. The key is the page's address on the web: date-picker.Giving an assistant the component of the one screen it is writing.
api/<module>.mdAll of a module's public API, by package: signatures, descriptions, parameters, enum entries, members.Looking at the whole of a declaration.
guides/<key>.mdA guide for apps, in English: how a part of the library is used across an app, the problem every Compose Multiplatform app has there, the library's answer, its pitfalls and how it is tested. The key is the guide's name without _GUIDE, in small letters: docs/guide/date-time/DATE_TIME_GUIDE.en.md is guides/date-time.md. Its links lead to the other guides among the documents, and to the repository's other files on GitHub.Giving an assistant how a whole part of an app is done: its tests, its layout by the window, dates, the clipboard.
pantrix-ui.jsonThe library as data: its modules, its pages and their examples, its components, every public declaration and its parameters. About 5 MB.Tools: the MCP server, a site, a code generator.
pantrix-ui.schema.jsonThe JSON Schema (2020-12) of pantrix-ui.json. Written by hand: ai/schema/pantrix-ui.schema.json.Telling a tool that reads the JSON what it can rely on.
CHANGELOG.mdThe repository's changelog, as it is.What changed in a version.
tr/pages/<key>.md, tr/guides/<key>.md, tr/CHANGELOG.mdThe Turkish of the demo's pages, of the guides for apps and of the changelog, in the same form. A page is as the demo writes it, its examples' code too; in its API the library's KDoc is in Turkish where it has a Turkish, and in English where it has none. What comes from the components' annotations, their descriptions and their short examples' titles, stays English. A guide's links lead to the Turkish of the other guides. The index (llms.txt) and the MCP server name the English alone.A place that reads in Turkish, such as the site's Turkish pages.
skills/pantrix-ui/The skill. SKILL.md: where to look things up, the library's rules ("Core rules"), Pantrix's counterparts of Material's names, the steps of writing a screen and of reviewing code, the list of the references. references/: setup, theming, layout, forms, overlays, charts, languages and platform services, accessibility, tests, Material 3. Written by hand (ai/skills/pantrix-ui/); only references/components.md is written from the catalogue at every build: every component with its import, what it is for, its keywords, its tags and its page, and the icons.The skill an assistant installs (section 4). llms.txt and the prompts read the rules from it; the MCP server gives the skill itself as well (get_rules, get_documentation_page, pantrix://skill).
files.txtThe list of every document, a path a line.A reader that cannot list a jar's resources: the server's commands docs and skill.

3. Making them

./gradlew :ai:mcp:generateAiDocs

For a site

Where the documents are to be served from a site, its address is given, and the links are then absolute:

./gradlew :ai:mcp:generateAiDocs -Ppantrix.ai.baseUrl=https://site-address

4. Giving them to an assistant

The MCP server

An assistant reads the documents best from Pantrix's MCP server: it chooses what to ask, and takes only what it needs. The server is one jar that runs by itself, mcp-<version>-all.jar, and it is taken from one of three places:

Where fromHow
A releaseA file attached to each release of the package repository, which asks for no token: https://github.com/develappsancho/pantrix-ui-cmp-package/releases/download/v<version>/mcp-<version>-all.jar.
GitHub PackagesPublished with the library, as com.pantrixui:mcp with the classifier all; reading it asks for a token that may read packages (read:packages) (the publishing guide, in Turkish).
This repository./gradlew :ai:mcp:allJar writes it to ai/mcp/build/libs/mcp-<version>-all.jar; after ./gradlew publishToMavenLocal it is in ~/.m2/repository/com/pantrixui/mcp/<version>/.

An assistant's settings name the command that runs it. In Claude Code:

claude mcp add pantrix-ui -- java -jar /path/mcp-<version>-all.jar

In a client that reads its settings from an mcpServers field (Claude Desktop, Cursor…):

{
  "mcpServers": {
    "pantrix-ui": {
      "command": "java",
      "args": ["-jar", "/path/mcp-<version>-all.jar"]
    }
  }
}

Over HTTP

A site serves the server too, at an address of its own (such as https://pantrixui.com/mcp). An assistant is then given the address alone, and the user's machine needs neither Java nor the jar:

claude mcp add --transport http pantrix-ui https://site-address/mcp

In a .mcp.json, or an mcpServers field, the entry takes "type": "http" and a "url".

On the site, PantrixMcp.http() gives the server: MCP's Streamable HTTP transport, bound to no web framework. The site's own server, Spring Boot, Ktor or a servlet, hands each request made to the address to answer(method, headers, body) and writes back the Response it gets (its status, headers and body) as it is. In Spring Boot (WebFlux, with kotlinx-coroutines-reactor):

@RestController
class McpEndpoint {
    private val mcp = PantrixMcp.http(origins = setOf("site-address"))

    @RequestMapping("/mcp")
    suspend fun answer(
        request: ServerHttpRequest,
        @RequestBody(required = false) body: String?
    ): ResponseEntity<String> {
        val answer = mcp.answer(request.method.name(), request.headers.toSingleValueMap(), body.orEmpty())
        val response = ResponseEntity.status(answer.status)
            .headers { headers -> answer.headers.forEach { (name, value) -> headers.set(name, value) } }
        return if (answer.body.isEmpty()) response.build() else response.body(answer.body)
    }
}

The Claude code plugin

The package repository is also a marketplace of Claude Code's plugins. Its one plugin, pantrix-ui, brings the release's skill; once a site serves the MCP server, the plugin carries its address too (MCP_URL in release.sh):

claude plugin marketplace add develappsancho/pantrix-ui-cmp-package
claude plugin install pantrix-ui@pantrix-ui

The skill

The skill teaches an assistant how to work with Pantrix: its rules, the steps of writing a screen, and how the parts of the library fit together. It is in the Agent Skills format: a folder, with SKILL.md and its references in it. The assistant learns from the description in SKILL.md when to use it, and reads the references as it needs them.

java -jar mcp-<version>-all.jar skill <folder>

In another project, from start to end

An app's repository that uses Pantrix takes the skill and the server so. The first two steps are taken on each machine, the others in the app's repository. In Claude Code the plugin is the shortest way (above); this recipe puts the skill into the repository, and works with any assistant.

  1. The library: the app takes the packages from the local Maven repository (mavenLocal(), after ./gradlew clean publishToMavenLocal in Pantrix's repository) or from GitHub Packages (with a token); both repositories' lines and the dependency lines are in the skill's references/setup.md, and the publishing in detail in the publishing guide (in Turkish).

  2. The jar: the server's jar is downloaded from the release, or stands in the local Maven repository after the local publishing. Each machine keeps where it is in an environment variable of its own, in the shell's profile for example:

        # From a release
        curl -fL --create-dirs -o ~/.pantrix/mcp-<version>-all.jar https://github.com/develappsancho/pantrix-ui-cmp-package/releases/download/v<version>/mcp-<version>-all.jar
        export PANTRIX_MCP_JAR=$HOME/.pantrix/mcp-<version>-all.jar
        # Or after publishToMavenLocal in Pantrix's repository
        export PANTRIX_MCP_JAR=$HOME/.m2/repository/com/pantrixui/mcp/<version>/mcp-<version>-all.jar
  3. The skill: java -jar "$PANTRIX_MCP_JAR" skill .claude/skills at the app's root. .claude/skills/pantrix-ui/ goes into git; the team takes the skill with the repository.

  4. The server: a .mcp.json at the app's root:

        {
          "mcpServers": {
            "pantrix-ui": {
              "command": "java",
              "args": ["-jar", "${PANTRIX_MCP_JAR}"]
            }
          }
        }

    Claude Code expands ${PANTRIX_MCP_JAR} with each machine's own variable: the file goes into git, the jar's path does not. An interactive session asks for approval of the repository's server the first time it opens; claude -p loads it without asking. Where a site serves the server, the entry carries its address alone ("type": "http", "url"), and step 2 is not needed.

  5. Trying it: claude is opened in the app's folder. /skills lists pantrix-ui, and /mcp lists pantrix-ui as connected. Asked for something like "write a sign-up screen", the assistant loads the skill, calls the server's tools and writes with Pantrix's components (the trial in section 9).

  6. Keeping it current: when Pantrix is updated, steps 1–3 are taken again: the library, the jar and the skill come from one version. The skill command writes the folder anew; the change is a commit in the app's repository.

  7. Other assistants: clients that read their settings from a mcpServers field take the same entry in their own settings file. An assistant that reads skills reads the folder pantrix-ui from its own folder of skills. An assistant that reads neither is given llms.txt (below).

Giving the files

WhenWhat to give
The assistant is to write a screen or a feature in Pantrixllms.txt. The assistant reads the page or the module's reference it needs.
The work is with one componentThat component's page: pages/data-table.md. The page has the examples' code with its imports, and every parameter of the component with its description.
The assistant is to know everything at oncellms-full.txt. It is large: for a model with a wide context, or for a document index.
A tool is to read Pantrixpantrix-ui.json and its schema (section 7).

5. Where the documents come from

Every line of the documents comes from a source the build already holds:

SourceWhat goes into the documents
The library's KDoc (English)The description of every public declaration, parameter, enum entry and member. detekt's rule UndocumentedParameter stops the build on a parameter without a description (the detekt guide).
@PantrixComponent and @PantrixExampleThe component's name, its group, its one-sentence description, its keywords and its short examples.
The demo's pages (@DemoPage, @DemoExample)The page's summary and features, the examples' titles, descriptions and code.
The demo's English (app/shared/src/commonMain/translations/en)The English of the pages and the examples, the texts in the examples' code among them.
The READMEIts first paragraph is the library's summary, its first Kotlin example the smallest app. The build reads the example against the library too (section 6).
ai/skills/pantrix-ui/The skill, as it is written by hand. The section "Core rules" of SKILL.md is the library's rules: what the skill, llms.txt, get_rules and the prompts say, the one place the rules are written.
The guides' English (docs/guide/<key>/X_GUIDE.en.md)The guides for apps (guides/<key>.md). The build reads each against its Turkish first (section 6).
The guides' Turkish (docs/guide/<key>/X_GUIDE.md) and CHANGELOG.tr.mdThe documents in Turkish (tr/guides/<key>.md, tr/CHANGELOG.md): only the guides that have an English too.
The demo's own texts and the KDoc's Turkish (app/shared/src/commonMain/translations/tr)The pages' Turkish (tr/pages/<key>.md).

6. What stops the build

Where the documents would tell an assistant something false, or nothing at all, the task stops the build; so does the server's jar, where its notice of what it bundles is not what it bundles. The message says what to fix where:

WhatMessageFixing it
A component no page documentsui/…/Gauge.kt:198: no page of the sample app documents PantrixGauge, …The component is named in the api of its page's @DemoPage.
A named argument in a @PantrixExample that no form of the library hasPantrixButton, "Primary action": tag is given to PantrixButton, and no form of PantrixButton has a parameter of that nameThe example's code is written with the component's signature as it is today.
A text of the demo without its EnglishThese texts of the sample app have no English, … with the list of the missing texts./gradlew :app:shared:generateDemoCatalog is run, and the files under app/shared/build/reports/demoCatalog/translations/en are filled and copied to translations/en (the demo catalogue).
Two declarations that would get the same id in the documents… would share the id … in the documents for coding assistantsOne of the declarations is renamed.
A component whose keywords are empty, not written in lower case, or give a word twiceui/…/Button.kt:144: the @PantrixComponent of Button gives no keywords. …The annotation's keywords are given the words someone looking for the component would use that its name does not say: what other design systems and platforms call it, and what it is used for (toast and undo for a snackbar). Three to eight, in English and in lower case.
The skill, or the README's first example, names what the library does not have (PantrixSlider), or an entry or a member a type does not have (ButtonVariant.Primary)ai/skills/pantrix-ui/SKILL.md:73: ButtonVariant has no Primary The name is written as the library has it today.
A block of Kotlin imports what the library does not have, uses a name of the library it does not import, or gives an argument by a name no form has…/references/overlays.md:55: PantrixDialog is the library's, and the code does not import it: import com.pantrixui.components.overlay.dialog.PantrixDialog The block is written with its imports and the signature's names as they are today.
An artifact that is not published, or a version other than the library's…/references/setup.md:36: com.pantrixui:components:0.2.0 is not the version the library is published in, 0.1.0-SNAPSHOTThe version is the one in version.properties: when it is raised, the skill's are raised with it.
The skill's form: a name that is not its folder's, a description longer than 1,024 characters, a SKILL.md longer than 500 lines; a reference no link of SKILL.md leads to, a link that leads to no file of the skill; a reference longer than 100 lines without a Contents: line at its top that lists its sections…/references/layout.md:1: a reference of 193 lines lists its sections before the first: Contents: … What the message says is written.
A references/components.md written by hand… the generator writes this reference; it is not written by handThe file is removed: the generator writes it.
A guide outside the folder of its key (docs/guide/<key>/); a guide for apps without its English, or whose English has parted from its Turkish: a heading, a table's line, a block of code, a list item or an image the other does not have; a third line that does not link the other language; a link that leads to no file or heading, or from the English to the Turkish of a guide that has its Englishdocs/guide/date-time/DATE_TIME_GUIDE.en.md:120: a bullet, where the Turkish has a numbered item, docs/guide/date-time/DATE_TIME_GUIDE.md:118The English is written as the Turkish stands, and a part is added to both or taken from both. A guide about the repository's own building, which stays Turkish, is named among REPOSITORY_GUIDES in AiDocsPlugin.
A module the server's jar bundles that the notice ai/mcp/licenses/THIRD-PARTY.md does not list, a module it lists that the jar does not bundle, or a licence's text it names that the jar does not holdai/mcp/licenses/THIRD-PARTY.md: io.ktor:ktor-sse-jvm is bundled into the jar, and no list names it: …The library's section of the notice says its licence and lists the module; a text that no bundled jar holds is put beside the notice, under the path it takes after META-INF/licenses/.

7. The format

The schema (ai/schema/pantrix-ui.schema.json) describes every field of pantrix-ui.json. What a tool needs to know:

8. Checking them

./gradlew :ai:mcp:test

The generator's own tests run with ./gradlew :build-logic:test: the types' kinds, the checks of the short examples, of the skill, of the guides' two languages and of the jar's notice, the JSON writer, the reader and its guards, and the Markdown, llms.txt, the guides' documents and the index of the components exactly as expected.

Trying them in a real assistant

That the skill and the server work in an assistant is seen by giving one task in two folders. Both folders hold the skeleton of a small app that depends on Pantrix (settings.gradle.kts, composeApp/build.gradle.kts with the dependency, an App.kt that opens PantrixTheme). One is given the skill and the server as the recipe says (section 4), the other neither. Claude Code's headless mode runs the task once and writes every tool it calls:

claude -p "$(cat task.txt)" --mcp-config .mcp.json --strict-mcp-config \
  --setting-sources project,local --permission-mode acceptEdits --permission-prompts none \
  --allowedTools "mcp__pantrix-ui Skill" --disallowedTools "Bash WebFetch WebSearch" \
  --max-budget-usd 3 --no-session-persistence --output-format stream-json --verbose > session.jsonl

9. What was verified and what was not

Verified

Not verified