Skip to content
VishalPatil
Projects / Codistant

Codistant

Codistant screenshot 1

A web workspace for coding assistance built spec-driven development. The interaction loop is the product.

Stack used

  • TypeScript
  • JavaScript
  • HTML
  • CSS
  • Git

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.

Workflow compression

Fragmented workflow

Intentwhat I actually want

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.

Executefinally

Codistant

Intentquery in place

Input, interaction, and result render inside the same workspace shell.

Executeno rebuild of context

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.

Workspace architecture

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.

HTML 17.9%structure
  • 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.

LayerChoiceWhy it earned its place
StylingCSS (30.3%)The largest single slice, which is what a product judged on interaction feel should look like
Client logicJavaScript (27.0%)Interaction and runtime behavior, where iteration speed mattered more than ceremony
Typed logicTypeScript (24.8%)Safety for the parts whose complexity was growing and whose interfaces had stabilized
MarkupHTML (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.

Type hardening as a one-way migration

Shapes change daily here. Annotating them would mean paying full cost on interfaces about to be thrown away.

Next step: interface settles

The signal to promote is behavioural: I have stopped redesigning it and started merely using it.

Next step: harden

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.

  1. Write the spec first

    Development runs against specs I wrote before building, so the target exists outside my own short-term memory.

  2. Append, never overwrite

    claude/context.md is an append-only session knowledge base. Overwriting it would destroy exactly the history that makes it useful.

  3. Keep a teaching journal

    claude/learnings.md captures 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.