I was at a conference recently and watched Joel Hooks talk about Effect. Effect homepage h1 advertises that it's the "Reliable TypeScript for the AI era". Joel explained that AI agents (Kiro, Claude Code, etc) can write way better TypeScript with Effect then he could ever do by himself. It made me start thinking, is this the future?
That raised a question I had not seriously considered before. Are we going to choose libraries based on what coding agents can write reliably, even when those libraries are harder for us to learn?
I made a video about that question using two libraries: Effect and StyleX.
Both libraries work really well with coding agents. Effect can more easily catch expected failures in the type system. StyleX puts styles behind a typed JavaScript API. Both help reduce errors during compilation.
That sounds great. However, it also creates a strange situation where the agent may understand the stack better then I could, even though I'm responsible to maintain it long term.
To help understand this more let's see how StyleX and Effect work, and where I think where this is all going.
Put expected failures in the type system with Effect
To understand Effect, you need to understand how it handles async functions. A normal asynchronous TypeScript function often tells you the success type and leaves the failure behavior in comments, thrown exceptions, or tribal knowledge.
Effect gives failures their own typed channel. In the demo, each expected problem has a tagged error:
import { Data, Effect } from 'effect'
export class NotFoundError extends Data.TaggedError('NotFoundError')<{
readonly handle: string
}> {}
export class RateLimitError extends Data.TaggedError('RateLimitError')<{
readonly retryAfterSeconds: number
}> {}
export class NetworkError extends Data.TaggedError('NetworkError')<{
readonly reason: string
}> {}
The profile program returns one of those errors based on the scenario:
export const loadProfile = (scenario: Scenario) =>
Effect.gen(function* () {
if (scenario === 'not-found') {
return yield* new NotFoundError({ handle: 'agent-editor' })
}
if (scenario === 'rate-limited') {
return yield* new RateLimitError({ retryAfterSeconds: 30 })
}
if (scenario === 'offline') {
return yield* new NetworkError({ reason: 'The demo API is offline.' })
}
return profile
})
I can infer the complete error union from the program instead of maintaining it separately:
export type LoadProfileError = Effect.Effect.Error<
ReturnType<typeof loadProfile>
>
The UI then handles each tag in one place:
function describeError(error: LoadProfileError) {
switch (error._tag) {
case 'NotFoundError':
return `No profile exists for @${error.handle}.`
case 'RateLimitError':
return `Try again in ${error.retryAfterSeconds} seconds.`
case 'NetworkError':
return error.reason
default:
return assertNever(error)
}
}
If a coding agent adds a MaintenanceError to the program and forgets to update the UI, the assertNever call can turn that omission into a type error. The agent gets a correction signal immediately.
Here is the feedback loop:
Coding agent changes the program
|
v
Effect updates the typed error channel
|
v
TypeScript checks every UI branch
|
missing case? fix it
|
v
Run tests and review behavior
The compiler is catching the errors first.
Effect introduces a functional programming model, its own vocabulary, and more abstraction than a plain async function.
This is great, but with this abstraction comes more complexity, and a steeper learning curve.
Let's take a look at how StyleX works next.
Put visual states behind a typed API with StyleX
StyleX takes a similar idea into styling. Instead of handing an agent a stylesheet with global selectors and arbitrary class names, you define styles through stylex.create:
const styles = stylex.create({
result: {
borderRadius: 14,
borderStyle: 'solid',
borderWidth: 1,
minHeight: 160,
},
resultSuccess: {
backgroundColor: colors.successSurface,
borderColor: colors.success,
},
resultWarning: {
backgroundColor: colors.warningSurface,
borderColor: colors.warning,
},
resultDanger: {
backgroundColor: colors.dangerSurface,
borderColor: colors.danger,
},
})
The component composes those states explicitly:
<div
{...stylex.props(
styles.result,
view.status === 'success' && styles.resultSuccess,
view.status === 'failure' && view.tone === 'warning' &&
styles.resultWarning,
view.status === 'failure' && view.tone === 'danger' &&
styles.resultDanger,
)}
>
{/* Result UI */}
</div>
This gives the agent named visual states and a type-checked API. It can still go wrong of course, but there are fewer ways to accidentally make up selectors, misspell properties, or create styles that never get attached to the component.
I'm not going to lie, I do not like the output to the page. Generated atomic class names look like random strings, and the CSS-in-JS syntax feels heavier than Tailwind.
Personally, I think StyleX may be a better interface for agents, but I'm not completely sold.
Decide who your stack is optimized for
I used to choose libraries based on the following:
- Does the team already know it?
- Is the documentation good?
- Can we debug it in production?
- Will it still be maintained in a few years?
With coding agents writing most of the code, we now need to decide if it should be the most important part.
In my opinion, that question is important, but it should not be the only reason you pick a library.
| Choice | Helpful for an agent | Cost for a developer |
|---|---|---|
| Effect | Typed failures, explicit composition, compiler feedback | New programming model and learning curve |
| StyleX | Typed styles, named states, constrained composition | Less familiar syntax and generated class names |
| Plain TypeScript | Familiar syntax and broad ecosystem | Expected failures are easier to leave implicit |
| Tailwind CSS | Familiar utilities and readable markup for many teams | More freedom for inconsistent generated combinations |
With all that said, there may be a middle ground. Libraries such as shadcn/ui work well with coding agents because the patterns are common and the component source stays in your project. A developer can still open the file and understand what was generated without first learning a new programming model or paradigm.
That is the balance I want. Give the agent enough structure to catch mistakes while keeping the code review useful for the person who owns the application.
Finale
I am not moving every TypeScript project to Effect or rewriting every Tailwind component with StyleX. For now, I am sticking with the tools I know well enough to review.
I am keeping an eye out for these new type libraries. I really think in the future that agent friendly libraries will be the new normal, and the human readability, while important, will not be as important.
However, we the developers are still responsible for architecture, behavior, and what reaches production. A compiler can catch an error, but us developers need to understand if the app is behaving the way it's supposed to.
Would you adopt a library because your coding agent writes it better, even if your team has to work harder to learn it?
Top comments (1)
What do you think? Do you choose libraries because how well your AI Agent works with them? Or not?