When an experiment starts behaving like a product

The last 24 hours only touched a few repos, but the activity was dense:

  • 2026-06-09 21:44 - Update Reveal It builder and scratch experience
  • 2026-06-09 22:15 - Create Loop It Synth project
  • 2026-06-09 22:22 - Improve loop quality controls
  • 2026-06-09 22:41 - Make loop download explicit
  • 2026-06-09 22:45 - Add MP3 loop export
  • 2026-06-09 22:48 - Add Gitea Pages workflow
  • 2026-06-09 22:59 - Deploy Loop It Synth with Cloudflare Pages
  • 2026-06-09 23:16 - Turn Loop It Synth into a track composer
  • 2026-06-10 10:04 - Prioritize tracks in Loop It Synth layout

The quick reading is: a scratch-card app got UX improvements and a music app came together quickly.

The correct reading is more useful: both stopped behaving like isolated demos and started asking for product rules.

The signal was not code volume

Reveal It had a large diff: builder, scratch card, sounds, i18n, checkout, Worker and smoke tests.

Loop It Synth had a shorter but intense sequence: project creation, loop quality controls, MP3 export, Pages deployment, track composition, and then a layout correction to give the sequencer priority.

They are not the same product. They are not even the same kind of interface.

But they triggered the same chain:

flowchart LR tweak["Visible tweak"] --> state["State and model"] state --> tests["Automated verification"] tests --> deploy["Real deployment"] deploy --> product["Product behavior"]

That is the important part: maturity is not measured by line count. It shows up when a visible improvement can no longer live on its own.

Reveal It: fixing the preview forced a contract change

The Reveal It conversation started with something that looked visual:

the home preview had to match configure, the revealed text could no longer sit inside the image, and it needed sound with mute.

That sounds like CSS. It was not.

As soon as the home page moved toward the same visual structure as the configurator, deeper decisions appeared:

Layer What had to be decided Why it mattered
UI move the revealed text outside the image frame and add a mute button the home demo stops being decoration and starts promising the real experience
State persist soundLibraryId alongside generated audio otherwise you cannot distinguish "no sound" from "old card with no field"
Publishing make the published card preserve that selection if behavior changes after publish, the demo was lying
Tests scope smoke tests because the hidden home already had another .scratch-card reusing visual structure also changes what tests think they are looking at
Operations stop relying on fixed ports in smoke an old server on 5189 had already polluted earlier results

The useful part of that chat was this: every time an experience improvement crossed a boundary, an implicit assumption had to become an explicit contract.

It was not enough to "add music". We had to decide what silence means, where mute lives, how it survives in the published clone, and how to prove we are not reading from an old server.

Loop It Synth: the MVP asked for mixing, export and visual hierarchy within hours

Loop It Synth compressed a small product roadmap into less than a day:

  1. The project was created.
  2. Loop controls were tightened so the output sounded more consistent.
  3. Download became explicit.
  4. MP3 export was added.
  5. Gitea Pages was replaced with Cloudflare Pages.
  6. The sequencer stopped being a simple generator and became a track composer.
  7. The next morning the layout had to be rearranged because the monitor was pushing the sequencer too far down.

The final conversation said it precisely: on medium viewports the monitor was still taking too much first-screen attention, so the layout had to prioritize tracks before settings.

That is a product decision, not visual polish.

Once a music app moves from "generate something" to "compose, export and deploy", the screen hierarchy changes:

  • the sequencer stops being a detail and becomes the primary action,
  • export stops being a bonus and becomes a promise,
  • and deployment stops being administrative because there is now a public URL that must return 200.

The shared lesson: a demo tolerates ambiguity, a product charges for it

In both cases a boundary became visible:

Case Ambiguity a demo can tolerate Ambiguity a product cannot tolerate
Reveal It the home page looks "roughly like" the configurator the published experience loses the promised audio, mute or layout behavior
Loop It Synth the generator makes something useful even if the flow is linear composing, exporting and using it across viewports has no clear primary action

What changes between those phases is not just the interface.

The required precision around everything else changes too:

  • state naming,
  • persistence,
  • test selectors,
  • port policy,
  • deployment workflow,
  • and what gets first-viewport priority.

Why this phase matters

There is a common mistake in small projects: thinking product means "make it prettier once it works".

These 24 hours show the opposite.

Product appears when the system forces you to answer questions a demo could avoid:

  • if the user mutes, where does that truth live,
  • if the layout competes for attention, which task wins,
  • if publishing changes behavior, which version is lying,
  • and if a test fails, are you validating new code or an old process still listening on a fixed port.

What I am taking from this stretch

  • A serious visual tweak usually uncovers contract debt.
  • Once a demo reuses real pieces, tests can no longer stay naive.
  • Exporting and deploying turn an experiment into an external promise; from that point on, operations are part of the product.
  • Viewport hierarchy is architecture too: it decides which action dominates and which becomes secondary.

These apps did not just grow in features. They grew in rigor.

That is a healthy signal: when an interface improvement forces state, smoke tests and deployment to move together, you are no longer polishing a demo. You are entering product territory.


Part of my product and platform notes. Follow the blog or contact me.