Skip to content
ChatfuelSDK
Concepts

Vendoring, not depending

The design system, the API client and the proxy arrive as source you own — the trade that makes, and the two rules that keep the copies working.

Nothing in your package.json points back at our repository. That is not an accident of packaging; it is the decision the rest of this page explains.

Four trees are copied into your project as source:

In this repositoryIn your appReached as
content/ui/srcsrc/vendor/ui~ui
content/api-client/srcsrc/vendor/api~api
content/schemasrc/vendor/schemaread from disk by codegen
content/vite-plugin-proxy/srcvendor/chatfuel-proxyrelative paths

A fifth is the generator itself: content/codegen lands at scripts/codegen, beside the client it produces. It sits under scripts/ rather than src/ because it reads the disk — nothing in it is part of the bundle, and the app's main tsconfig never sees it.

The proxy is copied recursively, with no file list, so a file added beside the others travels without anyone remembering to name it. Four marked blocks are then rewritten to point at the copies: the proxy import in vite.config.ts, server/entry.ts and api/chatfuel.ts, and the two stylesheet imports in src/index.css.

What source you own is good for

A component you disagree with is a file you can edit. There is no upstream package to fight, no !important to win an argument with, and no wrapper to write around a prop we did not expose.

The visual identity is CSS custom properties in vendor/ui/styles/tokens.css — colors, radii, shadows, durations, the type ramp — so a rebrand is a token edit that every component follows, rather than a search through class names. And deleting is as available as editing: a module you do not want is a directory you remove, and the import boundary keeps that deletion local.

The other half of the trade is that nothing we publish can reach you. There is no version of the design system that can be pushed under your app and change how it renders on a Tuesday.

Two rules keep the copies portable

A tree that is going to be moved onto somebody else's disk has to survive the move byte for byte. Two rules make that true:

  1. Every internal import is relative. A path alias that resolves in this repository does not resolve after the copy lands somewhere else.
  2. ~ui may import nothing but react and react-dom. Those are the design system's only peer dependencies; anything else would be a package your app never agreed to install.

Both hold in the code, and neither is checked by anything. The repository's own gates read content/ui/src for tokens, barrels and class names, never for import specifiers, and the one import-boundary pass is scoped to module subtrees. They are conventions kept by hand — break one and you find out when the tree lands in somebody's app and does not resolve.

The API client's contract is the same shape with a different list: its source imports graphql, graphql-ws and @graphql-typed-document-node/core, so a scaffolded app declares exactly those three as dependencies. The proxy adds ws, undici and https-proxy-agent, and every relative import inside it carries an explicit .js extension because Vercel deploys those files as they are.

So "vendored" does not mean dependency-free. It means the dependency list is short, visible in your own package.json, and yours.

What does not travel

Our unit tests for the copied trees stay behind — the copy filter drops every *.test.ts and *.test.tsx on the way out, because a test of ours is a failing vitest run in an app nobody wrote it for.

The shell is the exception and travels whole, tests included. That suite is the app's own: it is what catches a module that renders nothing, and it is yours to extend.

Neither does the manifest tooling travel: module manifests are read while the wizard runs and have no presence in the app afterwards. The same line runs through the whole repository — a package's src/ is what a user gets, and the configs, README and build scripts beside it stay here.

The cost, stated plainly

An upgrade is never npm update. A fix we make after your run does not reach you until you go and get it.

What makes going and getting it bearable is that every file the wizard wrote is recorded, with the digest it had when it landed, so chatfuel-wizard update can tell a file you changed from a file that only moved upstream — see upgrading an app. That is the price of the guarantee above, and it is the same price either way: you cannot have code that we cannot break and also code that we can fix.

For the tree this leaves on disk, see what the wizard writes.

On this page