Project XXIII Studio
A creative suite sold as a licensed package
My role Product design and engineering, solo
Architecture, UX, interface, policy, build
When June 2026 – present
Scope Four engines, twenty-one modes
~130,000 lines across 800 files
Platform Web · React + TypeScript, Next.js
Installable npm package with a Vite library build
Status Live in one host product · licensed, not publicly distributed

My Role

XXIII Studio is a generative creative suite — image, video, voice and likeness training — that I designed and built end to end, and that ships as an installable package rather than as a screen inside one app. It runs today inside a live adult social platform, and it is written so the same codebase can be licensed to a property developer or a design studio without a fork.

The work is unusual for a product designer in one specific way: the interface was the smaller half. What made this shippable was deciding where authority lives — which side of the wire owns a decision, what a client is allowed to infer, and what has to be impossible rather than merely discouraged.

  • Product architecture: Defined the four engines and the mode taxonomy, and the rule that a mode is a configuration entry rather than a screen — which is why twenty-one of them do not read as twenty-one products.
  • UX & interaction design: Designed the workspace: a four-slot panel system, one adaptive composer, a readiness checklist that says what is missing instead of disabling a button, and the mode picker the whole product opens onto.
  • System design: Wrote the lifecycle contract, the orchestration spine every image mode runs on, the billing invariant that keeps client and server quoting the same price, and the failure taxonomy that makes an error name its owner.
  • Content policy: Designed the generation gate — what it screens, in what order, and why the order is the safety property — and the consolidation that collapses eleven scattered policy locations into one.
  • Engineering: Built it. React, TypeScript, Zustand, React Query, a Vite library build, and a provider layer that keeps the package free of any network, auth or brand code at all.
  • Packaging: Defined the licensing model, the entitlement contract the backend enforces, and the onboarding path a new client's engineers follow.

Motion, Scenes. Generated in the Studio, in the mode it advertises, and shipped as that tile’s cover.

The problem

Generative tools fail in a predictable way once they grow past one trick. Every new capability arrives as a new screen with its own prompt box, its own upload rules, its own idea of what "ready" means and its own copy of the safety logic. Six months later the product is a folder of half-shared surfaces, and the same bug has to be fixed in four places because it exists in four places.

The brief was not "add video to the image tool". It was to build a creative suite that could keep absorbing capabilities — and be sold to a second and third company — without the interface, the billing or the safety policy drifting apart. Three constraints shaped everything:

  • Generation is slow, expensive and refuses. A job runs for minutes across third-party providers that fail differently, moderate differently and sometimes just stop answering. Every one of those minutes is money already held.
  • The product is adult, and the bank is watching. Consensual adult work is the point of the host platform. A narrow set of categories must nonetheless never reach a provider — and "the provider will probably catch it" is not an architecture.
  • The same codebase has to be several products. The next client wants identical mechanics and completely different imagery, a different plan, a different brand and their own backend.

One door into everything

Studio boots inside somebody else's application. The name on the splash, the artwork behind it and the modes that appear a second later all arrive from the host's brand and licence — the package ships none of them, and the boot is deliberately fail-closed on the plan rather than optimistic about it.

And it opens over the host rather than replacing it. Studio is a modal — expandable, closable — so the application it lives inside never loses its place. That is also what makes it installable into a product that already owns its own navigation, its own routes and its own idea of where the user is.

Behind the splash is Origin: every mode the licence permits, as a row of art-directed covers grouped by engine. It is the mode picker, but it is also the answer to the question a suite this wide always gets asked — what can this actually do? — and it answers with the product's own output rather than with icons.

Every cover is generated in the Studio itself, in the mode it advertises, through the real pipeline. There is deliberately no bespoke generator in the authoring tool: reimplementing submit through to billing settle would mean a second, divergent client for the one path that has to be exactly right.

Dream and Motion above; Whisper and Echo follow below them. Covers are art per vertical — a property developer, a design studio and the current host get the same twenty-one modes and completely different imagery, because a vertical swaps art and nothing else.

Four slots, twenty-one modes

Underneath, there is one workspace. Panels are assigned to slots by the active flow and never speak to each other through props — they read and write a shared context stack, so any component can swap any slot at any time. A mode is a set of slot assignments, which is what keeps the product from becoming twenty-one screens.

In use it reads as one composer beside one library. The narrow panel is the controls; the large panel is the work. Flows with nothing to configure simply do not fill the narrow slot, and the layout closes around it.

Every screen here is the running build, captured in the modal against a development backend with no session — which is why the library is empty.

One box, every mode

Dream, Motion and Whisper each shipped their own prompt box — roughly 4,500 lines of panel and 2,650 lines of submit orchestration across three engines, with the same three concepts stored three different ways. A shared component already existed and two of its three consumers bypassed it through escape hatches, which is the tell that the abstraction had leaked rather than that it was missing.

The fix was not to extract a component again. It was to make the shell declarative so nothing needs an escape hatch: the mode becomes a setting inside the box rather than a destination in the navigation, and the uploads, the controls, the readiness checklist and the price are derived from the resolved mode and provider.

The checklist is the part I care most about. A disabled submit button that will not say why is the most common cruelty in tooling like this. Here the requirements are always visible, and the unmet one is struck through — so the state of the box is legible before you touch it.

Motion, Tour. An ordered set of room images becomes one walkthrough — the client submits a single job and tracks a single lifecycle; the backend does the multi-clip work.

The spine every mode runs on

Eight image modes look like eight features and are one orchestration. Only the anchor image and a single pipeline method differ; everything else — the watchdog, the retry budget, the per-slot failure capture, the terminal event — is inherited by plugging in.

Two decisions in here are the ones I would defend hardest. The first is that a refund is proportional, not binary: a batch that produces five of eight images refunds three, rather than charging for eight or nothing. The second is that the client and the server must quote the same price through the same clamp — under-quote and the job dies mid-flight after the first image has already succeeded; over-quote and users are asked to top up for work they can already afford.

The cost of the spine is that it is easy to plug in badly. A mode that skips one row of the wiring checklist does not crash — it ships a button that does nothing. That is why the checklist is written down as binding, and why adding a mode is documented as a walk through it rather than as a folder to copy.

The gate

This is the part of the product that has to be right, and the part where a designer's instinct to be helpful is actively dangerous. Adult work is allowed and is the point. A narrow set of categories must never reach a provider under any circumstance, in any language, on any surface.

What makes it hold is not the rule list. It is the order: screening runs on the user's literal words, before any rewriting, normalisation or provider mapping; the verdict is terminal; and the one rescuable substitution we allow is reachable only on an allow, so nothing ambiguous can ever be rewritten into something permitted.

The uncomfortable finding is on the right of that diagram. Run against the gate as originally built, the same word list over-blocked legitimate adult English and let the identical prohibited request through in two other languages. Both failures have one cause — a list matches tokens and the question is about meaning — which is the argument for inverting the architecture rather than tuning it. The vendor gate the bank requires stays switched on throughout, and is deliberately never load-bearing.

The rules themselves are not shown here, and are not going to be. The point of the diagram is the evaluation order.

Nothing may be assumed

The interface rule that removed the most bugs is also the least visible one: the client is not allowed to decide that anything finished. No watchdog, no timeout, no heuristic, no optimistic card. A job renders as running only against a real job id or a confirmed event from the backend, and terminal states are immutable once reached.

The corollary matters just as much for the interface: a failure before any network call costs nothing and leaves no record to reconcile, while a failure after one belongs to somebody — compliance, the user, the engine, the safety service or the provider. Naming the owner is what lets an error message tell the truth instead of apologising in general.

Echo, and the choice nobody could make

Echo trains a likeness model from someone's own photographs. It used to ship two named pipelines and ask the user to choose between them — a decision nobody outside the system was equipped to make, and one that let two submit surfaces silently overwrite each other.

Removing the picker was not a simplification pass. The expansion decision is now derived from the dataset: the system reads coverage rather than count, generates frames only for what is genuinely missing, and always keeps the person's real photographs, because they are the strongest identity evidence in the run.

A character is visible anywhere in the product only once a completed model and a valid artefact exist. Broken records are repaired by a reconciler or marked failed — never patched over by the interface.

The same modes, pointed at buildings

Everything above runs inside an adult social platform, which makes the licensing claim easy to assert and hard to believe. The test I could actually run was to point the existing engines at the vertical the package is being sold into next — architecture and property — and change nothing but the art direction and the reference slots.

No new engine, no new panel, no new pipeline. Same mode picker, same composer, same readiness checklist, same submit-quote-settle path. What follows is Dream and Motion running unmodified against an architecture brief.

Untextured white massing study of a curved residential tower in a city block
Massing study
Photoreal dusk render of the same tower, lit, with planted terraces and traffic below
Photoreal, dusk
Massing → photoreal An untextured volume study taken to a finished dusk render. The tower keeps its silhouette, its podium and its neighbours across the two, because the second is anchored on the first rather than described to the model a second time.
Wireframe analysis sheet for a tower: site plan, south elevation, transverse section, typical floor plan and a corner construction detail
Wireframe analysis
Annotated presentation board of the same tower carrying material callouts, podium heights and gross floor area figures
Annotated board
The drawing set Two document types a practice would actually hand over — a wireframe sheet carrying site plan, elevation, section, typical floor and a corner detail, and the same building again as a presentation board. The sheet furniture arrives with the drawing: callouts, leader lines, scale notes and a title block, rather than laid over it afterwards.
Dimensioned 2D floor plan of a four-bedroom single-storey house with room names and square footage
Dimensioned plan
The same floor plan rendered in 3D, furnished and textured, with every room label and dimension still legible
Furnished, rendered
Plan → volume A dimensioned CAD plan rendered furnished. Every room name, every square-footage figure and the fifty-foot overall run survive intact — the labels are the first thing tools like this normally destroy, and they are the only reason the drawing is worth anything to the person holding it.
Hand-drawn construction sketch of a two-storey house with dimension lines and margin notes
Concept sketch
Motion · 5s
Photographed basswood scale model of the same house on a workbench, lit from within
Scale model
One drawing, three outputs A construction sketch; the same sketch resolving into a finished render across five seconds; and the same house again as a scale model photographed on the bench. Two engines and three modes, all reading the one drawing.

The interesting part is what did not have to change. A vertical is a bundle of covers, prompt scaffolding and reference-slot labels — art and copy, resolved at boot from the licence. The floor plan above enters through the same anchor slot a portrait does, and the five-second render leaves through the same settle path, refunded proportionally if half of it lands.

Capability tests, not client work — there is no property developer's building in here. They exist to prove the mechanics survive a vertical swap, which is the thing the licence is actually selling.

Built to be somebody else's product

The commercial idea is that the Studio is not a feature of one app — it is a package another company installs. Making that true meant a hard rule: the package contains no network code at all. No fetch, no socket, no auth, no payment provider, no brand. Every one of those is a small TypeScript contract the host fills in with the client it already has.

Two consequences are worth naming. Entitlements are fail-closed: a mode whose key the backend does not return is not disabled with a tooltip, it does not render — so a licence can never be widened by a stale client. And the licence travels with every call rather than only the one that draws the interface, because the moment an entitlement has to authorise something rather than decide what to paint, a request that arrives without it silently takes the wrong branch. That one cost me a real bug.

The Decision

Twenty-one modes across four engines is a lot of surface for one person. The decision that made it survivable was to give the backend total authority over lifecycle, and to give the client none.

The path I took

The backend owns every terminal state

Progress renders only against a confirmed job id or a socket event. Terminal states are immutable. There is no client watchdog, no timeout, no phase inferred from the act of pressing submit — and a preflight refusal happens before any request record exists, so it costs nothing and leaves nothing to clean up.

The option I killed

A client-side watchdog

Start a timer on submit, show a progress card immediately, and fail the job locally if nothing comes back in five minutes. It is what almost every generative interface does, it makes the product feel instantly responsive, and it needs no backend cooperation at all.

What choosing it cost

Perceived speed, and a genuinely worse moment when the connection drops. A user who submits and loses their socket sees nothing move, because the one thing the client is forbidden to do is guess. I bought some of that back with confirmed queue states and honest progress copy, but not all of it — and the cost is real, not theoretical. What it bought is that the two failure modes I refuse to ship became unrepresentable rather than merely fixed: a job that shows complete and is not, and a job charged twice because two surfaces each believed they owned it. On a product where a minute of generation is money already held, I will take a slower-feeling truth over a fast-feeling guess.

How I Knew It Held

This runs in one live product with real users and real money, but I do not own its analytics and I am not going to dress up numbers I cannot show. What I can show is what was made structurally impossible, and what is instrumented rather than hoped for.

A block costs nothing
The gate runs before the credit hold and before the request row, so a refusal has zero provider calls and zero financial footprint. If the terminal payload guard ever fires, a surface forgot to preflight — and the log names it.
Partial work is paid for partially
Settle refunds the difference between the units quoted and the outputs that actually landed. A pipeline that throws or times out refunds everything, including the anchors.
Failures explain themselves
Every failed slot carries its own reason back to the container instead of vanishing into logs, and every preflight failure carries an owner: compliance, user, engine, safety service or provider.
The type system holds the taxonomy
Mode tiles are keyed off the mode enums, so a new mode cannot ship without a tile and a retired one cannot leave a stale tile behind. The compiler enforces what a checklist would otherwise have to.

What was never measured

Everything about whether people find it. I have no data on how many users open Origin and understand what twenty-one modes are for, how far the readiness checklist actually reduces failed submits, or how many people abandon a mode after one attempt. Those are the four numbers I would instrument first, and the honest position is that the architecture is validated and the interface is not.

What I would do differently

I would have consolidated the prompt box before adding the twelfth mode instead of after — the three-surface split was visible early and I let it run because each new mode was faster to ship inside the surface that already existed. I would also have written the policy as one package on day one rather than discovering it in eleven places later; that consolidation is specified and only partly built, and I would rather report it done. And the panel system carries a real scar: the state name for the large panel rendered into the CSS class for the small one for months, which nothing surfaced until a drag-and-drop handler bound to the panel that reads like the big one never saw a single drop. Decoupled systems fail quietly, and I did not have a test that would have caught it.

Conclusion

Impact: XXIII Studio turns a pile of AI capabilities into a product that can keep growing and can be sold twice. One workspace absorbs twenty-one modes; one orchestration serves nine of them; one policy core replaces eleven; and the whole thing installs into somebody else's application without a line of network code of its own. It is the clearest statement of how I work now — the interface, the system underneath it and the rules it must never break, all held by the same person, and shipped as a product rather than a prototype.