Why I built it
Most developer tools I use don't fail because a feature is missing. They fail because getting to that feature costs three tab switches, a copy-paste, and a re-read of context I already had in my head thirty seconds ago. The tooling is fine. The path through it is fragmented.
Codistant is what I built for that. A multi-language web workspace for coding assistance and developer productivity flows. I was not chasing more capability. I wanted a shorter path from what I want to the thing being done.
Reducing the steps between intent and execution is a feature, even when it adds no new capability.
The framing that drove every decision
I was solving context switching, not capability. Once I saw it that way, a UI-centric product was the obvious shape. If the friction lives in the interaction, the interaction is where the engineering has to go.
The problem
Context switching and fragmented workflows are expensive in aggregate even when each step looks cheap. Codistant folds the common coding-assistance interactions into one surface, so the loop from query to action never asks you to leave.
Fragmented workflow
The cost is not the tool, it is the boundary crossing that drops your held context.
The most expensive step, and the one that produces nothing.
Codistant
Input, interaction, and result render inside the same workspace shell.
Same destination, fewer boundaries crossed. The removed steps are context switches, not capabilities.
Architecture
UI-first, on purpose. A frontend-centric app shell owns layout and navigation. Interaction modules own the assistance flows and their state transitions. A flow can change without touching the shell. The shell can change without re-testing every flow.
Surface
The persistent workspace. Moving between assistance flows never means leaving it.
Interaction modules
Where intent enters the workspace - the first step of the compressed loop.
Each flow owns its own transitions rather than pushing state up into the shell.
Results land in the same surface the query came from, so no context switch closes the loop.
Language composition
A product judged on interaction feel should have styling as its heaviest layer.
Where iteration speed still matters more than a stable contract.
Modules whose interfaces stopped moving and earned annotation cost.
- Shell
- Interaction module
- Codebase composition
UI-first by design: the shell stays thin so interaction flows can change independently. Hover a block for the reasoning.
Frontend-centric app shell
The persistent surface. Owns layout and navigation so moving between flows never means leaving the workspace.
Interaction modules
Each assistance flow is its own module with its own event flow and state transitions. Keeps the shell thin and the flows independently changeable.
Traceable decisions
Spec-driven development against my own written specs, backed by an append-only
session knowledge base (claude/context.md) and a teaching journal
(claude/learnings.md).
Maintainable mixed languages
JavaScript and TypeScript coexist on purpose. The rule is directional: modules get typed as their interfaces settle, never the reverse.
- TypeScript
- JavaScript
- HTML
- CSS
- Git
The tech stack, and why
The language mix is what a UI-heavy product with real interactive logic looks like when you are honest about where the work lives.
| Layer | Choice | Why it earned its place |
|---|---|---|
| Styling | CSS (30.3%) | The largest single slice, which is what a product judged on interaction feel should look like |
| Client logic | JavaScript (27.0%) | Interaction and runtime behavior, where iteration speed mattered more than ceremony |
| Typed logic | TypeScript (24.8%) | Safety for the parts whose complexity was growing and whose interfaces had stabilized |
| Markup | HTML (17.9%) | UI structure |
Type hardening as a migration, not a rewrite
The JS/TS split is not an accident I am apologizing for. It is a policy.
Shapes change daily here. Annotating them would mean paying full cost on interfaces about to be thrown away.
The signal to promote is behavioural: I have stopped redesigning it and started merely using it.
The type system now protects a contract worth protecting, which is the only thing it is actually good at.
Modules move left to right as their interfaces settle. Nothing moves back.
New and volatile code starts in JavaScript, where changing a shape costs nothing. Once a module's interface stops moving (once I stop redesigning it and start merely using it), it gets hardened into TypeScript. Types protect contracts that are worth protecting. Everything else can wait.
What this costs
Type strictness is inconsistent across the codebase at any given moment. That is a real cost when reading unfamiliar files. I took it knowingly. Typing everything up front would have meant paying full annotation cost on interfaces I was about to throw away.
Working solo, with a paper trail
No teammates, no reviewers, no assigned tickets. The failure mode is not slow delivery. It is undocumented decisions. Six weeks later you cannot reconstruct why a flow works the way it does, and you end up fighting the same argument again.
Write the spec first
Development runs against specs I wrote before building, so the target exists outside my own short-term memory.
Append, never overwrite
claude/context.mdis an append-only session knowledge base. Overwriting it would destroy exactly the history that makes it useful.Keep a teaching journal
claude/learnings.mdcaptures what I actually learned, separately from what I decided. Mixing those two records loses both.
Design rationale
How do you balance speed and maintainability in a mixed JS/TS codebase? I shipped quickly in JavaScript where iteration mattered, then hardened modules with TypeScript as their interfaces stabilized. Early velocity without giving up long-term maintainability. The typed surface grows to cover the parts that have earned permanence, and nowhere else.
Why is abstraction the risk to watch here? Workflow compression works by hiding steps, and a hidden step is a step the user can no longer control. Every simplification trades away some lower-level access. The call is which controls a developer wants in the moment versus which ones they only reach for because the tool put them in the way.
Why UI-first for a developer tool? The problem I set out to solve lives in the interaction, not the capability. Fast feedback loops and clear interaction design were the guiding principles because those are the axes the product is judged on.
Where it stands
Live and open source, grounded in the code as of the development branch, and
built end to end by one person. The repository
is the source of truth for the current feature surface.



