Sirv AI Studio

A workspace for merchants' product photos and product data, where almost every screen is a long, configurable list.

View as Markdown
Studio's products table in the dark theme over Fernhill Home, a fictional furniture catalog. A row of coloured saved views, Default view, Recents, Ready for Shopify, Missing photos, New this week and Sofas, sits above columns for product, SKU, status, source, assets, filled slots, readiness bars and brand, with thumbnails of chairs and desks.
Products in June, over Fernhill Home, a fictional catalog.
My role
Design Engineer
Period
20 February – 18 June 2026
Status
My part ended in June 2026, in the middle of the rework
Reading time
17 min

Stack

React, TypeScript, TanStack Start, TanStack Table, TanStack Virtual, Tailwind CSS, Sid-Kit, Data Grid, Storybook, React Compiler, Next.js, TanStack Router, TanStack Query, shadcn, Base UI, dnd-kit, XState, Zustand, React Flow, Playwright, Vitest, Sentry, PostHog

Models used

At a glance

On 9 June I decided that Studio's saved views, shipped two weeks earlier as tabs, shouldn't be tabs at all: tabs show content, and a view is something you press, drag and edit. A month earlier that would have meant a new mockup, a handover and a polite request to redo the work. In Studio it meant an evening. It was my first project as a developer and the first where I didn't design a screen in Figma. Studio is a workspace for merchants' photos and product data, so almost every screen is a long list with configurable columns, filters and views. I started with the foundation, a clean component kit and one grid for every list, and then reworked the screens built on it. Most of the decisions below were made the second time.

Problems and solutions

  1. Problem: Studio had tables in 35 files, five list virtualizers and hard-coded colours in 57 components, and every new screen copied one of them.

    Solution: Rebuild the foundation before polishing any screen
  2. Problem: Views were tabs that saved only filters, while columns and layout lived outside them, and a tab read as content rather than a control.

    Solution: Saved views are buttons in the page header, not tabs
  3. Problem: A shot list was a fixed list of pictures, while a catalog often needs any two of four angles rather than all four.

    Solution: A media template groups slots and says how many of each group must be filled
  4. Problem: Screens slid in from arbitrary directions, with JavaScript fades and a delay that lagged behind the route.

    Solution: A screen slides from where it sits in the sidebar, and only CSS animates it

A product made of lists starts with its foundation

Studio is lists all the way down, so the first thing to fix was what every list is made of. Igor Varyvoda, who created Studio, had grown it fast with agents, and by early March it had the code you get when a product outruns its kit. On 4 March it had 35 files with their own tables, five separate list virtualizers and hard-coded colours in 57 components. The products table and the asset gallery were different components that behaved differently under the same hand.

Turning Studio into a DAM/PIM was my idea: build on the customers and image CDN the company already had, and grow standalone AI image tools into a workspace for merchants’ assets, then for their product data and sales channels. A workspace like that lives or dies by its lists, and I didn’t want to polish screens that would be rebuilt anyway.

We started this project without shadcn and migrated later. I suspect a lot of it was migrated badly. My goal now is to align our UI kit with canonical shadcn where possible, and list every change so we can decide what to keep.

Me to Claude, 5 March at 23:53

The first attempt went wrong the next morning, and I threw it away and started again from a fresh branch, with a clean shadcn install kept beside the project as the reference. For a week components went back to canonical one by one: checkbox, dropdown, dialog, tabs, the sidebar’s tokens. On that base the kit grew into Sid-Kit, and by 14 May 79 of the 91 files in Studio’s ui folder were thin re-exports of it.

How the kit decides colours, depth and states is a story of its own, told in the Sid-Kit case.

Every list in Studio is one grid

The library, products, vendors and activity were the same problem, so they got the same component.

Let’s plan a nice grid component. It should support virtualization, customizable columns, grouping, sorting and so on. It will be used on the library and products mostly, also for vendors, and activity. Heck, we have many applications, actually.

Me to Claude, 4 March at 22:34

An hour later the library ran on grid primitives, with tests. Two days later a separate table and gallery for products appeared beside it; three days after that they were gone, folded into the same grid. By May the grid was a package, and Studio’s screens switched to it on 1 May.

Products before the grid, on its first day and in June.

A grid is hard in a way a mockup doesn’t show. The one time I opened Figma for Studio, it was to cut cells out of paper, in effect: which line belongs to which cell, which border covers which, what a row is when its cells are pinned to different edges. After that the design happened in code.

Most of what makes a list feel right is small rules. Double-clicking a column’s edge resets its width and a second double-click lets it fill the free space. Pinned columns stay put while the rest slide underneath, with a shadow only where something is hidden. The keyboard and selection follow a file manager, and when the spec I’d written for that was wrong, I said so: Escape should leave a clean slate from where the cursor is.

Moving a column was the hardest of these. The first version, on 5 March, used a drag-and-drop library. It couldn’t move pinned columns or show the whole column in flight, so on 3 April it was replaced with plain pointer events and one rule: a column moves when its leading edge crosses the centre of its neighbour. On 8 April a full-column ghost and an animated landing came in, and on 9 April I reverted them: too much motion for something the hand does dozens of times a day. The ghost came back on its own on 28 April, without the choreography.

The products table while the Brand column is dragged left: its header floats as a small label with a grip under the pointer, the Brand cells and the Readiness column slide past each other mid-move, and a strip of the dark canvas under the table shows where the columns have not settled yet.

A column in flight.

Sorting came last. On 7 June Igor’s review blocked my secondary sort because it existed only in the interface. So sorting by several fields became part of the grid’s contract, which each screen’s data source implements, and its levels became a list in the view sheet with directions in words, “A–Z” or “Newest first”, rather than arrows.

The surprise was width. A table in Studio never knows how wide it will be: the sidebar can be dragged wider or folded away with an animation, a details panel or the view sheet can open beside it, and every column can be fitted, fixed or set to fill. Each of those changes the width of every column at once, and early on each one made the table stutter. Making it keep up took more work than any feature here. Underneath it is one geometry for the table: column widths, pins and fills worked out together and cached, so a view that comes back unchanged recomputes nothing. Fitting a column to its content then needed to know how wide text really is, which no library measured well enough, and middle truncation, which keeps both ends of a file name or an ID, needed the same measurer. Both became packages of their own.

The grid’s internals, the geometry cache, measuring and truncation, selection and performance, are in the Data Grid case.

Loading keeps the shape of the table

A list that is loading should already look like the list. In April Studio’s grid flickered between rows, empty space and a spinner whenever it scrolled fast or refetched.

On 23 April I asked for a ghost grid that was more than cell borders, with skeleton chunks where the values would be. The first version painted them with a repeating CSS gradient, and I asked whether that could round the chunks’ corners. It couldn’t.

Nah. Try SVG. You get animation for free there too.

Me to Codex, 23 April at 23:01

The result is what we called the carpet. Its geometry comes from the live grid: column widths, flexible columns, pinned edges, the header and the row height. Each column draws one SVG strip of chunks under every row at once, and real rows simply cover it as they mount, so there’s nothing to swap and nothing to time. Ten minutes later I asked for random widths for the text chunks, so the carpet read as text rather than as a pattern. A week later the gallery got its own carpet from the same numbers: gaps, card sizes and how cards stretch.

The products table held in its loading state: under the real header, every column shows rounded grey chunks of different widths in place of names, SKUs, statuses, sources, counts, readiness bars and brands, with a square where each thumbnail will be.

The carpet, held in place from the bug button.

Loading itself stopped blocking. There’s no overlay; the scrollbar knows the server’s full total from the start, a jump far down loads that range directly, and the end of a list fades into the background with a spinner instead of ending in a hard edge.

A bug button puts the app in any state

A loading state that lasts 200 ms can’t be designed by refreshing the page and hoping to catch it. The tool for that started as something else. In April lazy loading took weeks of experiments with scroll speed, overscan and when to preload the next page, and the numbers had to be watched on a live list. So on 20 April the grid got a corner of plain text on development builds: rows loaded, rows mounted above and below the viewport, the visible range and the preload threshold.

Two days later the numbers turned out to be the smaller part.

Let’s widen the role of the virtualizer panel and make it a debug tool. I want to drag it around the screen by hand. And I want a switch for the page’s loading states, to look at the table’s skeleton and poke at it with the inspector.

Me to Codex, 22 April at 12:46

By the evening it was a small window floating over the app, which folds into a round button with a bug on it and can be dragged anywhere on the screen. From that button any list screen can be held in one state while the real page stays mounted behind it: the route’s first shell, the carpet alone, the overlay that covers the list while its layout catches up, a refetch over the body or over the whole table, the indicator for a range still loading, or ready. The carpet chapter above was designed this way, in each state side by side with the real rows, until rhythm and optical weight matched.

The inspector also reads out what the grid is doing. It shows which rows are mounted, and whether the column geometry was measured again or taken from the cache, with the keys that decided it. It switches off React’s StrictMode, Sentry, session replay and tracing one by one, names the commands for a local production build and for a dev server without hot reload, and turns on routes still in development. Those switches came out of late April, when part of what we’d been fixing turned out to be the dev build itself; the geometry readout is how the column-width cache was checked in June.

The bug button, folded and opened.

The handles are the same for a script as for my hand, so an agent could put a screen into a state and photograph it. The pictures of loading states in this case were captured the same way.

A saved view is a button, not a tab

A view is a way of looking at the same list, so it has to be cheap to make, obvious to change and impossible to lose. Igor built the first saved views in late May: saved filters shown as tabs above products and assets, which could be pinned and reordered. Column layout was left for later.

On 5 June I took them over with one idea: one sheet configures the whole view, filters, sort, grouping and columns, and the grid behind it changes as you go. Simple filters come first, laid out like an online store’s, with a query builder for the rest; switching to the builder loses nothing, and switching back warns when it would.

Four days in, the views themselves had to change.

And these shouldn’t be tabs at all. Tabs aren’t an interactive component. This is something button-like.

Me to Codex, 9 June at 04:39

A view is a whole way of looking at the list, not a saved filter: its filters, sort, columns, their order and widths travel with it, so switching from Ready for Shopify to Missing photos changes what the table shows as well as which rows. Views moved into a second row of the page header as small buttons, plain for the others and flat for the current one, with actions on right-click instead of a menu on every button. Each custom view takes its colour from its own key, so the same view always has the same colour. I tried tinted shadows and coloured dots first and threw them out.

Tabs in May, then two views in June with columns of their own.

The hard part was state. A view you’ve changed but not saved keeps a dot, even after you leave it for another one. Changes apply live, so Done just closes the sheet and Cancel reverts. A changed view compares itself with what’s applied for its dot and with what’s saved for Update. Reset returns to defaults and Revert to what was saved, and they’re different buttons because they’re different promises. Whether a list is a table or a gallery lives in the address, not in the view: it’s the reader’s choice, not the view’s. Column widths, fixed, fitted or filling, joined the view in the last week.

One sheet for the whole view, applied as you go.

  1. The view sheet for Ready for Shopify on its Filters tab in Basic mode: name, SKU, status set to Active, source, brand, product type, category, tags, supplier, images, channel set to the Shopify web shop and readiness set to Ready to publish.

    FiltersSimple filters first, a query builder a tab away.

  2. The Sort tab: name as the primary sort, modified as secondary and created as tertiary, each with its direction in words, A to Z or Newest, above the fields still available.

    SortLevels in order, directions in words.

  3. The Table tab: a list of columns to show, drag to reorder, and set to fit their content, fill the free space or pin, with the products table switched to a table behind it.

    TableColumns to show, fit, fill or pin.

  4. The Gallery tab: card sizes from 2XS to 2XL with LG selected and the fields to show on a card, with the products switched to a gallery of large cards behind it.

    GalleryCard size and what a card shows.

The query builder was the other half of the Filters tab. Igor’s first version in May was a fixed OR of AND groups that opened on a list of starter presets. I gave every group and the whole query an explicit ALL or ANY, so single conditions and groups can sit side by side, put each condition on two rows, the field and operator above and the value below, and removed the presets: the builder opens empty, with only the buttons to add a condition or a group. Conditions and groups drag between each other. Each field brings its own operators and its own picker, so a category is chosen from the tree and a date from a range with presets such as the last 30 days, and on the server each condition becomes a piece of SQL. Basic mode and the builder edit the same rules, so a view built in one opens in the other.

The query builder in the view sheet. Conditions are joined by ALL: Status equals Active; a condition group set to ANY with Category equals Seating and Tags equals Bestseller or Gift idea; and the start of a second group set to ALL with Channel equals a value. Each condition has a drag handle, field and operator on one row and the value below.

Active products in Seating or tagged as gifts, checked for Shopify.

About 100 of my commits in June were views, filters and sort.

Templates describe the shoot and its file names

A product page needs particular pictures: a front view, a back, a detail, maybe a video. Studio’s first answer was Igor’s slot templates, where each template holds an ordered list of slots with a role, a label, an example picture and whether it’s required, and vendors’ uploads land in slots by the SKU in their file names. By March they had grown into a long settings page for vendor shot lists.

In mid-March I started two prototypes side by side, in a new Templates section: naming conventions and media templates.

Photos arrive from studios and suppliers named by someone else’s rules, and Studio has to know which product and which shot each file is. On 13 and 14 March I built naming conventions: a pattern such as {product_id}[-{row}]-{frame}.{ext}, where braces mark a variable and square brackets an optional part. While you type, the pattern is parsed and each variable is drawn as a chip coloured by its type: digits, a fixed set of values, a sequence or free text. Typing a brace opens a list of the variables you can use. The same pattern compiles into a matcher, so the field that defines a mask also shows how it reads a real file name: which part became which variable, what it was normalized to, and whether a whole list of pasted names matches. Conventions for Amazon, Walmart, Home Depot, GS1 and Sirv’s own 360 spins come built in.

The Naming Conventions list: Amazon Standard, Generic SKU, Grainger 360 Spin, GS1 Product Image, Home Depot, Lowe's 360 Spin, Shopify SEO, Sirv 360 Spin, Walmart and others, each pattern drawn with coloured variables such as {gtin}, {sequence}, {row}, {frame} and {ext} between plain separators.

Patterns drawn as chips.

A naming convention, tab by tab.

  1. A convention named Fernhill studio spins on its Summary tab: name, description, the filename pattern {product_id}[-{row}]-{frame}.{ext} in coloured chips, the path pattern {product_id}/, and short lists of validation and normalization per variable.

    SummaryThe whole convention at a glance.

  2. The General tab: name and description fields, the filename pattern typed as text with chips for each variable below it, and a path pattern switched on.

    GeneralThe pattern, typed and parsed.

  3. The Normalization tab: a switch to normalize separators, and per variable the casing and aliases, such as jpeg to jpg and tiff to tif for {ext}.

    NormalizationCasing, aliases and separators.

  4. The Validation tab: allowed extensions for {ext}, zero-padding and ranges for {frame} and {row}, and length limits for {product_id}, each overridable.

    ValidationWhat each variable may hold.

  5. The Test tab: the file name ABC123-01-01.jpg matches, and a table splits it into {product_id} ABC123, {row} 01, {frame} 01 and {ext} jpg with their separators, each with a check.

    TestOne name, split into its parts.

  6. Bulk test results for six pasted file names: four match with three or four variables each, and two, FH-CHR-01134_side.jpg and FH-ARM-01659-2-1.png, show no match.

    Bulk testFour of six names match.

On 18 March I built the second prototype, Media Templates, without touching the vendor portal. Its list looked like the other template lists in Studio and its editor opened in a sheet. Within an hour I cut it down.

Remove the tabs. There isn’t enough content to justify this layout. Just the template’s name, a description and the slot configurator.

Me to Claude, 18 March at 18:02

What made it different was groups. A slot has a media type, image, video, 3D, spin or document, its own specs and reference photos. Slots can sit in a group with a rule, “At least 1”, “At least 2” or “All 3 required”, because a catalog often needs any two of four angles rather than all four. Slots drag to reorder and between groups. I wanted no visual noise for a simple rearrange, so the target group was tinted only when a slot crossed into it; a day later I removed the tint altogether. Escape cancels the drag rather than closing the sheet. In the list, each template shows a strip with every slot, groups drawn as dashed outlines.

A shot list, then media templates.

  1. A shot list editor on 17 March in the vendor portal settings: a long page with a template brief, a naming convention with a filename pattern and placeholders, and side cards for template pulse counts and a vendor preview of file names.

    Shot lists, 17 MarchIgor's editor in the vendor portal.

  2. The Media Templates list: Lighting, Marketplace listing, Seating product page and Tables & storage, each with a strip of slot thumbnails where grouped slots sit inside dashed outlines, a description and a date.

    Media templatesEvery slot of every template in one strip.

  3. The Media Templates editor as a sheet: a name and description, then 8 slots in 2 groups. A required Hero slot, an Angles group set to At least 2 required with three-quarter, side and back slots, and the top of a Details group set to Not required, each slot with a drag handle and a reference thumbnail.

    Template editorSlots in groups, with a rule per group.

Five days later the same prototype gained image specs: checks such as resolution, background or colour space with a warning value and a passing value, as a general idea for every check that has a threshold. My note to the agent was that if it worked, Igor would use it in the vendor portal.

Then I set both aside for more pressing work, the grid and the lists. Data storage and the vendor side were Igor’s at the time, and he took parts of the prototypes into his shot lists.

A screen slides from where it sits

Navigation is easier to follow when the motion says where you went. In March I moved Studio to a standard sidebar layout with settings and AI tools sliding in as sub-panels, and Igor added the first page transitions. In May, with collections arriving as a tree under assets, the motion needed rules.

The first rule was about the means.

Drop our workarounds. If this navigation can’t be animated with CSS, we don’t animate it at all.

Me to Codex, 11 May at 21:17

The second was about direction. A section above the current one in the sidebar slides down into place, one below slides up. Opening a panel, settings, vendors or collections, slides right, and its title button slides back left. Tabs inside a panel follow their own order. Then, after one in the morning, I asked where that model came from.

How do we build the model of which navbar button sends the transition where? Dynamically, from the taxonomy? What if someone reorders the navbar by drag and drop?

Me to Codex, 12 May at 01:34

So vertical direction comes from the order the sidebar actually renders, including pinned collections, not from a list written next to it. The next evening I asked whether this was a module or smeared across the app, and it became one registry with four rules. A panel slides in 240 ms on one curve. Every row in a tree keeps a chevron slot, even without children, so parents and leaves keep the same rhythm.

Folded, the sidebar keeps only its icons, and nothing becomes unreachable. Pointing at a section opens its contents as a menu beside it: the screens of Assets or Products, the grouped pages of the supplier portal, or anything else a section holds, down to a tree. The width is free as well: the sidebar can be dragged wider or narrower, and folds and unfolds with an animation while the table beside it keeps up.

The folded sidebar opens each section as a menu.

The sidebar from March to June.

  1. Studio on 4 March: the library beside a sidebar of ten top-level items from Get Started to Trash, a Collections list with names cut off such as Autumn 20… and Marketpla…, then AI Tools and Settings; folders sit in the same grid as photos.

    4 MarchTen sections, collections cut off.

  2. Studio on 10 March in the standard sidebar layout: Get Started, Products, Assets, Activity, Review, Analytics, Workflows, AI Tools, Trash, Vendors and Settings, with Collections folded below, beside the library's folders and photos.

    10 MarchA standard sidebar with sliding panels.

  3. Studio in June: Overview, Assets with All assets, Favorites, Collections and Trash, Products with All products, Categories, Attributes and Channels, then Review, AI Tools, Activity, Analytics, Supplier Portal and Settings, beside the assets gallery with its saved views.

    JuneSections with their own screens.

  4. The Collections panel slid in over the sidebar, with a back arrow, add and search buttons and five collections with thumbnails and counts, beside the assets gallery.

    CollectionsA panel slides in from the right.

Some controls didn’t exist yet

A few controls Studio needed weren’t in shadcn or any library in the form it needed. Product types form a tree, and choosing one in a filter, a channel’s settings or a product group meant choosing from a tree: so Sid-Kit’s option list learned to render trees with search, nesting and selection that work the same as its flat lists. Sort levels became an option list with a direction toggle in each row. The query builder’s conditions read as two rows, the field and operator above the value, instead of one long line.

A category picker open inside a query builder condition: a search field over a tree of categories, with Furniture expanded, Seating checked and its children Armchairs and Benches checked with it, each with its path such as Furniture > Seating > Armchairs, above Clear, Cancel and Apply selection.

Choosing a category from the tree.

Storybook replaced Figma

For years my Figma files looked like a design system’s: a page per component, its states laid out in a grid, a table of variables and tokens. My prototypes used everything Figma could offer and lagged for it; in screencasts they passed for a real app. In Studio I stopped. Figma is a way to build something and show it to others when you can’t build it in code yet, and turning its pictures into code is the hard part that never quite works. I already knew how HTML and CSS are put together and what makes a design implementable, and with agents the code was no longer the slow part.

My design system in Figma, before Studio.

  1. A page of the SirvUI Figma file: primitive colour palettes from zinc to pink in ten steps each, on a light and a dark board, with text, colour and effect styles listed on the right.

    ThemePrimitive palettes, light and dark.

  2. Figma Variables for the SirvUI file: a $variant collection of 227 variables with modes default, faded and bordered, showing Button border width, focus outlines and neutral background colours for disabled, idle, hover and open states.

    Variables227 variables for variants alone.

  3. The Button page in Figma: the Button component set with its states, icon buttons, reusable buttons and a showcase of OK, Save changes, Apply, Proceed and download buttons in every state.

    ButtonA component, its states, a showcase.

  4. A Figma board of button showcases in the light theme: one panel per size and variant, 2xs, xs and sm by bordered, flat, light, solid and vibrant, each with the same buttons in every state.

    ShowcaseEvery size, variant and state laid out.

How the components were used, in Figma.

  1. The FormControl page in Figma: labels outside and inside fields, descriptions and error messages, and an Addon section with dropdown add-ons at the start and end of a field.

    Form controlLabels, hints and add-ons.

  2. The PasswordInput page in Figma: md and sm sizes, with the label outside or inside, each in every state from empty and filled to focused, disabled and with add-ons.

    Password inputEvery state, at every size.

  3. A Figma board of real dialogs in the dark theme: two-factor setup steps with a QR code, recovery codes, changing an email, a name and a password, leaving an account, creating and renaming a sub-account, and managing auto-fetch files, each in several states.

    DialogsReal dialogs, state by state.

  4. The same Figma file zoomed out: a long board titled DialogBody content with hundreds of dialog frames in a row, too small to read.

    All dialogsThe whole board, zoomed out.

Storybook does the same jobs live. A component’s story lays out its main abilities and states, and they can be clicked and changed. The styling section isn’t drawn by hand: it parses the real CSS and shows each token’s name, how its parametric definition unfolds step by step, and the swatch it ends up as. A Figma table of variables is right until the code changes; this one can’t be out of date. Studio’s style pages appeared on 13 May, and Sid-Kit’s token browser on 17 June.

The same jobs in Storybook, June 2026.

Without a handover between a picture and the code, changing my mind cost what the change cost, and that is why most decisions here were made twice.

An agent has to see the screen

Spring 2026 models wrote interfaces fluently and couldn’t see them. The harnesses of the time had no browser of their own, so an agent would report a fix it had never looked at, and most of my corrections were about things on screen. In March I had Claude drive my browser through an extension and kept repeating the same request.

Can you please use the browser for debugging like this? Remember it somehow.

Me to Claude, 14 March at 00:30

In April the grid got numbers: a baseline for switching between the table and the gallery, 1.3 s one way and 1.8 s the other, a profiler for the grid’s interactions and the bug button. Numbers weren’t enough, because they measured something other than what I saw.

Do you understand that you need a way to see the same thing I see, for tests and gates?

Me to Codex, 30 April at 03:45

So the agent learned to look. In May it moved to an isolated browser of its own, with my browser as the last resort. In June a final screenshot became mandatory for any interface change, and empty frames and skeletons became something tests detect instead of something I report.

For everything else I recorded the screen, as I would for a person: what I do, what I expect, what’s wrong, said aloud in Russian with English UI terms. On 17 June I turned that into a skill. It extracts the audio with ffmpeg and transcribes it with Whisper running locally, with a prompt that keeps interface terms in English, into a timeline with timestamps. The agent reads the timeline first, finds where I describe the problem, and only then pulls dense frame sheets around those moments, not the whole video. Its rules are short: never look at frames without the transcript when there is speech, never guess what I meant before reading it, and write a regression test before the fix. By the next day it had processed 30 recordings.

How it was built

Studio was my first project as a developer. I’d designed interfaces for years and knew how they are built; I hadn’t shipped them myself. The practices of development came to me as answers to problems I already had. When regressions kept coming back, tests were the answer someone had already worked out; when three people worked in one repository, Git was. For about a month the stream of new things was intense. By April I noticed the load had become normal.

Interface work still needs more of my attention than architecture does. An architecture can be designed once and left with an agent for hours. An interface has to be looked at after every change, because models still produce plausible screens that are wrong in ways only a person notices. From April to June I sent Codex about 5,500 messages in Studio, after some 2,600 prompts to Claude in February to April.

Studio was built by a team of three: Igor Varyvoda, Veniamin Krachun and me. I’m grateful to Igor. He was always reachable, and when I was out of my depth he answered to the point without spoon-feeding. He brought me into development, and into development with agents in particular.

Studio’s grid and kit outgrew it: read the Data Grid case and the Sid-Kit case. Igor’s own account of Studio is at varyvoda.com.