I recently discovered a useful pattern when writing web applications.
When making a new web component,
put just the component and some mock data on a page that is only served during development.
I use /visual
as the base route for these pages.
This is massively helpful for programming agents like Claude Code, which will take instructions like
use playwright mcp to visit
http://localhost:3000/visual/user-list
to see the component as you make changes
Making live functional components using mock data accessible this way is a big speed up when working with LLM agents, because you don’t have to prompt them into logging in and putting the application in the state that shows the component you are modifying. With the right prompting, the agent will iterate on its own until the component is right.
(This is a speed-up that doesn’t require disabling permission checks!)
It also improves things for the human in the loop for the same reason. Whether I’m making the changes myself or verifying what the LLM has done, having a single no-bullshit page I can load is a lot faster.
You can set up multiple pages with the component showing different mocked state, like showing normal and error states, or states that might require fiddling with a lot of app controls to get to manually.
I do this for big components like my user dashboard, and for tiny components like a user account search box with incremental search.
To make this work, you have to design your application state so it can be mocked in the first place. There are two ways I do this:
-
The simplest approach is telling them to accept mock data and making their local data public. Here’s an example I added to a Lit component in my app a couple of weeks ago:
@customElement("user-lookup") export class UserLookupElement extends LitElement { @property({ type: Boolean, attribute: "enable-mock" }) enableMock = false; @property({ type: Object }) selectedUser: UserListItem | null = null; // ... snip ... }
When the component is instantiated as
<user-lookup enable-mock></user-lookup>
, a script on the parent page can do something likedocument.querySelector("user-lookup").selectedUser = someUser;
and this component will display the mock data without talking to the backend at all. -
For components that rely on global state, you’ll have to have a mock state object. I won’t bore you with details about my boutique state management system (it’s unique and special just like me), but this ought to be possible with any normal state management library.
If your state management library doesn’t have this capability, consider adding it — it’ll help you with unit tests too.