01 — Context
Two platforms. One brief.
Storable serves both the storage and marina industries. Within the marine division, two separate products had been operating in parallel: Molo, the primary marina management platform, and Stellar, a rentals-focused product with its own operator-facing workflows.
In 2025, Storable made the decision to merge Stellar's reservation system architecture into Molo. The two platforms had evolved independently, developing different approaches to the same core problem: how marina operators configure and manage pricing. Molo had its own rate system. Stellar had a more sophisticated rate logic architecture. Each had strengths worth keeping.
One customer was navigating the move from Stellar to Molo in real time. Their feedback wasn't just useful — it was a constant reality check on whether the new system was actually bridging the gap, or just creating a third unfamiliar thing.
02 — The problem
Marina pricing sounds simple until you're inside it.
A slip isn't just a slip. It's a specific size, in a specific location, available to a specific vessel type, priced differently by season, contract length, and a dozen other variables. Marinas need to express all of that in their software.
Molo could technically handle this complexity — but only by accumulating dozens of specialized, one-off pricing configurations over time. Every edge case got its own solution. The result was a system where pricing logic couldn't be reused, seasonal changes had to be rebuilt from scratch, and scaling a pricing catalog meant duplicating work indefinitely.
The root issue wasn't the interface. It was the model. Rates were being treated as monolithic objects — each one a self-contained bundle of logic, conditions, and price. But marina pricing doesn't work that way.
The logic that defines who a rate applies to is highly configurable, but it changes on its own cadence. The price attached to that logic changes every season. Coupling them meant that every seasonal update required recreating complex logic from scratch, and no two rates could ever share a foundation.
The fix required separating those two things — and building a composable system where rate logic and pricing could be configured independently and combined flexibly. That's a model-level problem before it's a design problem.
03 — The model
Starting with the model, not the interface.
The first decision was to resist jumping to screens. The old system's problems weren't primarily visual — they were structural. Designing a better UI on top of a broken model would have just made the mess prettier. So the early work was about establishing the right underlying architecture before any interface decisions were made.
An early alignment artifact establishing the model before any screens were designed. Rate Logic defines the rules. A Rate combines logic with a season and a price.
The core architectural decision was splitting what had previously been a single monolithic object into two distinct concepts:
Rate Logic
The rules — who and how
Defines what contract type a rate applies to, what space types are eligible, how the price is calculated, and what conditions must be met. Highly configurable, but once built, doesn't need to change every season.
Rate
The price — how much and when
Combines a piece of Rate Logic with a season and a price. Changes frequently — seasonally, annually, in response to market conditions — without requiring any changes to the underlying logic.
Separating these two things meant that operators could build Rate Logic once and reuse it across seasons. Updating prices for a new season no longer required rebuilding the underlying logic from scratch.
04 — Composable calculation
Three components instead of an ever-growing list.
Over time, the platform had accumulated a long list of hardcoded pricing permutations — per foot per night, per square foot per month, per ton, and so on. Every new pricing requirement meant adding another option to the list. Instead, I redesigned pricing calculations around three composable components:
What the price is based onA flat rate, a unit, a vessel dimension, a space dimension, or the greater of the two.
Which measurement (if applicable)Length, width, area, or draft. Only appears when the selected basis requires it — a flat rate doesn't need a dimension, but a per-foot rate does.
The time unitPer night, per week, per month, per year. Combined with basis and dimension to produce the final calculation.
Simple rates use just a basis and interval. Complex rates compose all three. New pricing requirements can be expressed through composition rather than requiring a new configuration to be built and maintained.
05 — Conditional pricing
A generalized system instead of one-off configurations.
Marina pricing often needs to be highly specific. A rate might only apply to pontoon boats, or vessels under 30 feet, or stays longer than 30 days. The old system handled this through specialized one-off configurations. The new system needed a generalized solution.
I designed a condition builder that lets operators construct precise pricing rules from a defined set of condition types. Each condition dynamically adjusts its available operators and inputs based on what's being evaluated. Conditions can be combined with AND/OR logic to express rules like "vessel type is one of Pontoon or Sailboat AND vessel length is between 25 and 40 feet."
Operators who don't need conditions never have to engage with this section at all. The complexity is there when you need it — invisible when you don't.
Conditions are built through a structured form and saved as plain-language summaries. "Vessel Type is any of Pontoon, Sailboat" — not a dense configuration row.
06 — Rate creation UI
Making the architecture navigable without making it simple.
The rate creation page is where the architectural decisions made early in the project become tangible. The page follows a two-column layout: a sequential form on the left, a sticky Rate Summary panel on the right that builds as fields are completed.
The Rate Summary panel reflects selections in real time, giving operators a plain-language preview of the rate they're building before they save it. For a form this configurable, that running summary is the difference between feeling in control and feeling lost.
Bulk duplication lets operators copy an entire season's rates into a new season and adjust pricing in a single action. What previously required rebuilding every rate from scratch now takes seconds.
07 — Reservation creation flow
The rate engine made invisible.
Designing the rate engine was the hard problem. Designing the reservation creation flow was the proof that it worked. An operator should be able to create a reservation without needing to understand the pricing model running underneath it.
Contact & vesselSearch for an existing contact, which immediately surfaces their linked vessel and current A/R balance.
Contract detailsContract type and the reservation date range — the frame that everything downstream depends on.
Space assignmentSpace type, specific space, arrival and departure, invoicing frequency, and a meters table that appears automatically after a space is selected.
Invoice scheduleA drawer showing the full projected invoice schedule with expandable line items — see the full financial picture before the reservation exists.
All the complexity of the rate engine, invisible at the point of use.
Operators can preview the full financial schedule of a reservation before creating it.
08 — Interactive prototype
Try the reservation creation flow yourself.
I built this prototype to explore the core interactions — contact search populating vessel details automatically, the meters table appearing after space selection, and the invoice schedule drawer. It's not a pixel-perfect recreation of the shipped product, but it makes the key UX decisions tangible in a way screenshots can't.
Try searching for "Oscar," "Lando," or "Alex" to select a contact. Then pick a contract type, assign a space, and open the invoice schedule drawer to see the full financial picture.
09 — Reflection
What I'd do differently — and what I think holds.
Research came too late
The case for change was internally obvious — the old system demonstrably didn't scale — but "obviously broken" isn't the same as "we know what better looks like." We ran one round of user feedback before dev handoff, which gave us directional confidence but not certainty. For a project that touched this much of the platform, that was thin. I'd have pushed harder for structured research before committing to the architectural model.
The condition builder is better — but still complex
It's more coherent, more generalized, less reliant on one-off configurations than what it replaced. But complexity that's necessary is still complexity. I don't think we've fully solved that surface yet. A longer feedback cycle would have sharpened it.
What I'm proud of: the model
Separating Rate Logic from Rates, making the calculation composable, treating conditions as a generalized system rather than a collection of edge cases — those decisions came from taking the problem seriously at the right level of abstraction. The interface is the visible part. The model is the part that will either hold up or crack as the platform grows. I think it holds.