Skip to main content
Stavros Apostolou
[ Case Study 01 ]

Oikion

A unified MLS, CRM, and team-intelligence platform for Greek real-estate agencies. Designed in Greek, from the entity model up.

[ oikion.com ] (opens in new tab)
[ Senior Product Designer · Design Engineer ][ Feb 2026 – Present · ~5 mos (active) ][ Figma · Design Tokens · Next.js · Tailwind · Claude Code ][ ~30 min read ]
Oikion · short version

Impact

3 1
Improvised tools → one platform
4
First-class entities, nothing nested
4
Themes on a single token layer
Greek
Designed Greek-first, English verified

Oikion replaced the patchwork of spreadsheets and WhatsApp groups that small Greek agencies, five to fifteen agents, had stitched together just to get through the week. When it went into pilot use, I stopped watching conversion numbers. The moment that mattered was quieter: an agent reaching for Oikion instead of the old spreadsheet, without really thinking about it, because for once the software worked the way their head already did.

Signup completion: what the research shows
Research on form length (Baymard Institute) traces roughly a fifth of all form abandonment to one thing: a flow that asks for too much, too soon. Oikion asks for three fields to open an account. The incumbent mega-form asks for about eighteen. The teardown below makes that gap easy to feel.
Showing everything, with the deep architecture diagrams collapsed. Pick a lens and the case study expands what matters to you. This view itself never hides anything.
// read this your way

How would you like to read this?

Same story, tailored depth. Choose the hat you are wearing and the case study expands what matters most to you.

Showing everything, with the deep architecture diagrams collapsed. Pick a lens and the case study expands what matters to you. This view itself never hides anything.

// 01 · Origin

The problem I couldn’t stop thinking about

Oikion (pronounced ee-KEE-on, Greek for “dwelling”) started as a problem I couldn’t put down. Every industry has its software graveyard: tools built for a world that has since moved on, quietly broken, kept alive because replacing them feels scarier than limping along with them. Greek real-estate agencies were living in one of the worst I had ever seen.

I spent weeks embedded with agencies of five to fifteen agents across Athens and Thessaloniki, sitting beside people while they worked. What I found wasn’t a technology gap. It was a quieter mismatch, between the way the software pictured the world and the way agents actually moved through it. Their tools came in three flavours: generic international CRMs built for B2B sales pipelines, with no idea what a property transaction even is; legacy Greek desktop software from the late 1990s, installed on a single machine and visually frozen somewhere around Windows XP; and, most often, a homemade stack of spreadsheets, WhatsApp groups, and paper ledgers they had cobbled together themselves because nothing else fit.

None of them was the right tool. And yet the spreadsheet, of all things, was the honest one: it never pretended to be more than it was. That honesty is exactly why it had been so hard to replace.

The question that started it
The question I started with wasn’t “how do we digitize this?” It was: if we designed this from scratch, starting with how Greek agents actually think, what would it become?

That question became Oikion. Over roughly five months it grew from a cleaner CRM into four hard systems I had to design the human experience for: an engine that swallows the messy spreadsheets agencies already keep, a network that lets rival offices share without fear, a legal signing flow, and an encryption layer that makes the most sensitive data unreadable even to us. The rest of this study is really about those four, and about the one job the interface had in every case: to make something genuinely complex feel calm, safe, and obvious.

// 02 · Competitive

The field wasn’t empty. It was incoherent

Greek agencies were not short of software. They were short of software that agreed with itself. The serious incumbents are mature, capable products, and pretending otherwise would have been the fastest way to lose the argument.

ilistCRM, the CRM tied to the Spitogatos network, has run for about eighteen years and sits inside 850-plus agencies across Greece and Cyprus. RealStatus, out of Thessaloniki, has run nearly as long and serves 724. Both are ISO 27001 certified, both syndicate to two dozen-plus property portals, and both already cross-match properties against client requests. Below them sit the portals themselves (Spitogatos and xe.gr) for search, and the spreadsheet-plus-WhatsApp-plus-ledger stack that most small offices actually run the day on.

What I was actually up against
These are not weak tools. Eighteen years each, hundreds of agencies, deep integrations. I was never going to out-feature them in a year, so the bet was the model and the craft: a coherent demand-aware data layer, Greek as the design substrate, and an interface agents actually find easier. That last one is the thing eighteen years of accretion tends to make harder, not easier.

The shared tell is older than any of them: the word mandate. Treated as a listing the agency holds, it models the business supply-first. Greek agents mean the opposite: a brief a buyer hands you, a demand signal. The incumbents bolt request-matching onto a property-centric core. Oikion makes the Request a first-class, demand-side entity from the data model up. Same word, opposite centre of gravity.

Criteria
ilistCRM (Fortunet)~18 yrs · 850+ agencies~24 portals (Spitogatos network)Property-centric + request matchingAccreted over 18 years
RealStatus (iarts)~18 yrs · 724 agencies28 portals + internationalProperty / request handlingFeature-rich, accreted
OikionPre-launch challengerNetwork-first; few portals yetDemand-aware: Requests first-classDesigned from the model up

So the incumbents were the competition, but the humble spreadsheet was the real benchmark, and it set the bar in four places the old tools kept missing. Agencies’ real data lived in messy bilingual spreadsheets that took days to hand-split and retype. A buyer with no match, or a listing with no buyer, had no safe way to travel across rival offices. Contracts were printed, signed by hand, scanned, and emailed, with sensitive party data along for the ride. And the most private financial and tax details were guarded by nothing sturdier than a promise. Each of those gaps became a system I had to design, not just a feature: an import engine, an inter-agency network called Polis, a self-hosted signing flow, and an end-to-end encryption layer. The rest of this study meets them one at a time.

// 03 · Onboarding

The fastest way to lose a user is to ask for everything at once

Every legacy tool I audited opened with the same wall: one page, every field a Greek agency could ever need, all demanded before the product had earned a single keystroke of trust. Company name, VAT number, tax office, professional licence, two addresses, a phone for every occasion. It is the most honest expression of software that serves itself.

So I built both, side by side. Scroll the competitor wall on the left; step through Oikion’s real flow on the right.

Competitor signup18 fields · one screen

// representative composite · scroll the wall, then submit

Step 0 of 5

Choose your language

Choose your preferred interface language

🇬🇷Ελληνικά
🇬🇧English
jump:
OnboardingSteps.tsxtsx
// Steps: 0=Language, 1=Welcome, 2=Theme, 3=Username,//        4=Notifications, 5=Security&Privacy, 6=Reviewconst TOTAL_STEPS = 7; // Calculate progress (excluding language selection step from progress display)const progressSteps = TOTAL_STEPS - 1;const displayStep = currentStep > 0 ? currentStep - 1 : 0;const progress = currentStep <= 1  ? 0  : ((displayStep) / (progressSteps - 1)) * 100; // Progress bar shows only from step 2 onward, because// language + welcome are zero-friction entry, not work.{currentStep >= 2 && <Progress value={progress} />}

Oikion asks for three things to make an account (email, password, confirm), then gets out of the way. Everything else became a seven-step progressive flow, and the progress bar deliberately stays hidden until step three: choosing a language and saying hello are explicitly not work, and the interface refuses to dress them up as if they were.

What the split bought
Splitting the old all-in-one form into a minimal account step and a separate, progressive onboarding is the change that moved the needle most. Modeled against form-usability benchmarks, the switch to this dual signup-and-onboarding flow projects to roughly a 28% lift in onboarding completion.
What revising looks like
That flow shipped at nine steps. Watching real first-runs, two steps were ceremony, so it shipped again at seven. The number that matters in onboarding is the one you remove.
Estimate
Estimated: collapsing an eighteen-field wall to three required fields removes the largest single pre-activation drop-off. The direction is well established in form-usability research (Baymard Institute, Nielsen Norman Group), though the magnitude here is modeled, not measured on Oikion.
// 04 · System

“A system, not a skin”

Before any token, three words decided every visual argument. They gave me something to hold a decision against instead of debating taste, and each carried a behavioural rule, not just a mood.

Calm
The interface absorbs complexity so agents don’t feel it. One meaningful motion per action. A saved listing simply arrives; it doesn’t celebrate.
Trustworthy
Precision builds confidence. Seven semantic status states, numbers right-aligned, euro amounts in correct Greek locale. Never reach for red unless something is actually wrong.
Efficient
Nothing earns a pixel unless it speeds a task. An agent with 200 listings needs to scan 200 rows in 30 seconds, so density here is a feature, not a flaw.

Oikion was designed in Greek first and verified in English, because Greek runs about a third longer and every compact element has to survive the harder language. Underneath sits a token system: HSL semantic colours, a four-point spacing scale, fifteen hue families each with five variants, and OKLCH badge tokens where perceptual evenness matters.

Estate Light · bg
oklch(95% 0.01 85)
Estate Light · fg
oklch(29% 0.02 148)
Estate Dark · bg
oklch(24% 0.02 149)
Twilight · accent
oklch(61% 0.19 300)
Pearl Sand · bg (planned)
oklch(92% 0.01 78)
Badge success · token
oklch(94% 0.07 145)

The type scale is a fixed six-step ramp, set in Manrope (the primary interface face, with the legacy Inter being phased out) and chosen so hierarchy reads from weight and size alone, not from colour.

Aah1 · 3rem · 700
Aah2 · 2.25rem · 600
Aah3 · 1.875rem · 600
Aah4 · 1.5rem · 600
The quick brown foxbody · 1rem · 400
The quick brown foxcaption · 0.875rem · 400
Manrope
Aa Bb Cc · Αα Ββ Γγ · Ακίνητα · 0123
Primary UI face
Inter
Aa Bb Cc · Αα Ββ Γγ · Ακίνητα · 0123
Legacy face, being phased out

Light and dark are explicitly not one inverted into the other; they were designed as peers from the token layer up. Four themes ship today (Estate Light, Estate Dark, Twilight Lavender, and a System mode), with Pearl Sand designed and queued behind its token block.

The hardest decision in this layer was visibility. A property carries one of four states, on a single drag track from Hidden to Public.

Private·your organisation

Your organisation only; participates in intra-agency matchmaking.

Downgrading is consequential, so the guarantee lives where it cannot be skipped: in the database. Dropping a property below Secure deletes its cross-organisation match records atomically, inside a single Prisma transaction, and the UI updates optimistically and rolls back if the server refuses. A named are-you-sure confirmation is designed and queued next; today, Oikion is explicitly not relying on a confirmation dialog for that guarantee, in favour of the atomic transaction itself.

Shipped vs next
Shipped: atomic cross-org match cleanup on downgrade, plus optimistic update with rollback. Next: an in-UI confirmation that names exactly what a downgrade will remove before it happens.

That slider is really the front door to Polis, the inter-agency network, and Polis turned out to be less a matching feature than a trust problem. Fiercely private, competing offices will only share data if they stay in control of it, so I gave sharing two dimensions instead of one. The slider decides whether a record is shared at all. A second, separate control, the one below, decides how much of it others see: Anonymised shows bare specifications, Agency-identified adds the office name and logo, and Full adds the listing agent and their contact. An agency can join the network revealing almost nothing, then open up to the partners it comes to trust.

Polis Settings
Network Membership
Share with Network
Privacy Levels
Properties
Agency shown·Specs + agency name
Requests
Anonymized·Specs only
Bilateral Partners
Search agencies…
Kolonaki EstatesAthens, AtticaInvited
Thermi PropertiesThessalonikiInvite
Full network settingsSave

The privacy tiers are resolved when a record is read, not baked in when it is written, so a change of heart takes effect the instant an agent makes it, with nothing to recompute and no stale copy left behind. The source agency’s current setting always wins, and a viewing office can never widen it. Matching was the reward. Control was the product.

The Sharing Hub
All of it converges on one screen that answers a single anxious question: “where is my data going?” Three groups (On my showcase, In the Polis network, Shared with connections) each carry a plain badge: a green globe for public, a shield for secure, a lock for personal. Oikion is explicitly not curing a trust barrier with a promise, in favour of a dashboard you can read at a glance.

Matching isn’t a filter, it’s a weighted score: nineteen criteria, budget weighted highest, with three factors that only make sense in Greece (Golden Visa eligibility, inside-the-city-plan status, and an energy class from A-plus to H) and weights an agency can retune to its own market.

Criteria
Budget20The deal-breaker; graduated, not binary
Property type / Location12 eachWrong type or area ends the conversation
Bedrooms / Size8 / 7Liveability, scored on a curve
Greek-specific (Golden Visa, city-plan, energy)2–3 eachReal constraints no foreign CRM models

Invisible security: the whole thing behind one PIN

The most demanding design problem in Oikion is one you never see. Agencies hold financial positions, tax numbers, and private negotiations, and I wanted to offer a real guarantee instead of a comforting phrase: that Oikion itself cannot read the most sensitive collaborative data, full stop. The trouble is that true end-to-end encryption is famously cruel to users. Keys, lost devices, and recovery normally leak straight into the interface.

My brief for this one was a single sentence: make a real “we cannot read your data” guarantee feel as calm as a password field. The answer was one PIN. A single four-to-eight digit PIN, set once during onboarding, unlocks a person’s entire encrypted world across every organisation they belong to: comments on clients and properties, direct messages, group channels, and file attachments. Underneath, each record behaves like its own encrypted channel and access is a key shared to your identity, but none of that vocabulary ever reaches the user. They see a lock icon and a PIN.

The real work lived in the edge states. Recording recovery codes, the most user-hostile moment in any encrypted product, became a guided step during onboarding rather than a wall of warnings. During a PIN reset, while the system quietly re-shares access to the new key, the affected records read “Restoring access to encrypted comments…” while everything else stays usable, a degraded state I designed on purpose rather than leaving to a spinner. And I kept the one truth that cannot be designed away: if an organisation’s only admin loses both their PIN and every recovery code, the data is genuinely gone. The interface says so, plainly, at the moment it matters. An honest message beats a false reassurance.

What makes it trustworthy
Beneath the calm surface is a hand-rolled cryptographic core in TypeScript over WebCrypto, using Signal-family protocols chosen per job: X3DH to set up sessions, the Double Ratchet for one-to-one messages, and Megolm for group channels and the entity-as-channel comment model. It was hardened through audited passes, and its session-backup layer was reviewed and revised seven times, benchmarked openly against Signal and WhatsApp, and candid about its own limits. The invisible-security experience is the story. The depth underneath is what makes the promise believable.
// 05 · Challenges

Where the product fought back

Good design work isn’t a series of correct decisions. It’s decisions, mistakes, and recoveries, and the recoveries are where the durable insights live. Three of them mattered most.

1. The most expensive decision was delaying the right one

The original model treated buyer briefs as supply-side constructs attached to properties, the same error the legacy tools made. When testing proved it wrong, every one of the fifty-plus files built on it was wrong in the same way: API routes, server actions, schema, forms, validation, translations, search filters, permission guards. Correcting it meant a phased migration, with Mandates becoming Requests (demand-side constructs held by buyers) across multiple schema changes and deprecated endpoints.

The lesson
The entity model is the substrate every other decision sits on. A wrong model in the data layer produces wrong interactions at the UI layer, because users are asked to interact with constructs that don’t match how they think. Validating a data model is a design activity, and it belongs on the designer’s desk first.

2. The bug that wasn’t a bug

In the seven-step property wizard, Select values vanished when users navigated back, then reappeared going forward; occasionally a number typed into “size” surfaced in “bedrooms.” No JavaScript error. The form store held the values correctly. It only reproduced after repeated back-and-forth navigation. The cause spanned four systems that look like one until they disagree: React reused DOM nodes for same-shaped steps, the browser’s native number-input state bled across, react-hook-form’s registry silently dropped fields, and Zod’s cross-field .refine() ran during per-field validation against data that didn’t exist yet.

The fix, once diagnosed, was a single line: force a fresh mount per step so native DOM state can’t bleed, while the form store keeps the values:

// shouldUnregister:false keeps values in the form store,
// so entries survive the unmount, but the DOM is fresh,
// so browser-native state never bleeds between steps.
<CardContent key={currentStep}>
  {renderStep(currentStep)}
</CardContent>
The lesson
React reconciliation, the form library’s registry, the validator’s execution, and the browser’s native DOM state are four distinct systems. I’d designed the form assuming they were one.

3. Three voices speaking at once

At one point three toast implementations coexisted: sixty files importing Sonner directly with hardcoded strings, fifteen using the intended useAppToast hook backed by translation keys, and twenty-five on a deprecated Radix component with a different API. Different durations, different treatments, different languages, so Greek agents would occasionally see English feedback. Nobody added a third system maliciously; each developer picked the path of least resistance in the moment. The fix standardised on useAppToast (errors 6s, warnings 5s, success and info 4s, all routed through translation keys) and added ESLint rules, so the constraint is enforced at authoring time, not caught in review.

The lesson
Systems-level incoherence is solved by making the right path the easy path. The ESLint rule did more for consistency than any amount of code review could.

The same instinct resolved the quieter ones: 3,089 TypeScript errors that traced to a single dangling messages.d.ts reference; sub-44px touch targets fixed with pseudo-element hit areas; seventeen fixed-width panels that clipped on the 1366×768 Windows laptops Greek offices actually use; and a navigation includes() check that wasn’t the same as “is active.” Each looked like distributed breakage and turned out to be a single upstream cause.

The battles a layer down

The three problems above were fought at the interface. The next three were fought underneath it, where the screen sat on top of a hard distributed-systems or cryptography problem, and the design job was to keep every bit of that difficulty off the user’s desk.

4. Designing with the platform’s real limits: the Import Engine

The Import Engine takes one messy bilingual spreadsheet and turns it into clean, linked, encrypted records. A single row might hold a property, its owner’s phone, and a buyer’s budget all at once, so the engine has to work out what each row contains and create everything in the right order, atomically, so a failed import never leaves half-built data behind. That atomicity kept colliding with the production database’s real ceilings: it rejected the fast raw-SQL patterns and capped how long a transaction could run, right below what the bulk insert needed. The shape went back and forth several times before it settled into a two-pass algorithm: count the unique entities, generate all their IDs in one batched call, then build and insert in ordered phases inside a single transaction. It exists because of those limits, not in spite of them, and designing with a platform’s real ceilings instead of an imagined ideal is one of the lines between mid-level and senior work.

The design on top of the constraint
All of that is invisible. What the agency sees is a six-step wizard: Upload, Mapping, Validation, Review, Importing, Complete. The mapping step began life as tabs and failed in testing, so it became a flat list with a per-row entity selector. When the auto-mapper isn’t sure about a column, it clears its own guess and sends the column to Unassigned rather than guess wrong. And before you commit, a skip-cascade warning tells you the consequence in plain words: skip this contact and its linked property still imports, just without the link. Surfacing uncertainty and consequence beats hiding them.

5. Two systems that can’t share a transaction: the signing flow

Document signing had to make a legally binding, multi-party, asynchronous process feel like one calm tab, across a boundary where the usual safety net does not exist. Oikion’s database and the external signing engine are two separate systems, and two separate systems cannot share a transaction. If the engine accepted a contract but the database then failed to record it, I would be left with a ghost envelope. So I ordered the steps on purpose: the external call happens first, before any database write. If the engine fails, there is no record and nothing to undo. If it succeeds but the database write then fails, the system fires a best-effort cancellation so no orphaned envelope lingers on the other side. The engine is self-hosted, a choice driven entirely by data sovereignty, because the alternative was shipping GDPR-sensitive party data to a processor overseas. The party data never leaves, and Oikion stays the single source of truth.

The interface hides all of it. An agent lines up signers in a three-step modal, with drag-to-reorder that announces itself to screen readers, sends the envelope, and watches its status climb through a view that is quietly polling the engine every fifteen seconds, because there is no push channel back to the browser. When the last person signs, a new document tagged Signed appears on its own, inheriting every link the original had. The distributed-systems complexity is real. The experience is a calm tab.

6. The edge case that could lock users out forever: E2EE session backup

The hardest single problem in the encryption system was a quiet one: what happens when a user clears their browser storage, switches to a new device, or opens a private window? In an end-to-end encrypted system, the keys that decrypt your history live only in your browser. Lose that local storage with no recovery design and the encrypted comments and messages are gone for good, and it also meant encryption could not follow a user across their own two devices at all.

So I designed encrypted session backup, and designed it in two layers. An inner layer encrypts the session data to the user’s own identity key, so the server can never read it. An outer layer wraps that again with a per-organisation key for encryption at rest. A server breach without the user’s key yields only opaque blobs; a stolen device without the server yields only wrapped data. I benchmarked the design openly against how Signal and WhatsApp handle encrypted backups, and the spec went through review and was revised seven separate times, each revision a documented trade-off: scoping backups per-organisation because a user’s keys differ across orgs; a best-effort save on unload that accepts the risk of losing a few seconds of state on a hard crash; and deliberately leaving one-time keys out of the backup, because carrying them across devices would let them be reused and break a core guarantee.

Why this is the flagship
The surface is calm: access is simply restored, or, in the unrecoverable case, the interface tells the truth. Underneath is a design that was stress-tested, reviewed, revised, benchmarked against the best systems in the world, and honest about its own limits. Designing the experience and the system it rests on, and staying candid about the trade-offs of both, is the altitude I want this work to show.
// 06 · Process

How the thing actually got built

A case study that only shows the polished surface is hiding the part that matters. Oikion was built as a loop (research, write, audit, revise, repeat), and the loop left evidence.

Process
[ Field ]
Research
Embedded with five-to-fifteen-agent offices; mapped the work before the data model.
[ Mental-model map ][ The mandate inversion ]

The Mandates-to-Requests rename is the clearest example. Getting the central noun wrong meant fifty files were wrong in the same way; correcting it meant a scripted migration and a rule I now keep: the four i18n registration points are one atomic unit, touched together or not at all.

The discipline
Audits and reviews are explicitly not a phase after building, in favour of being part of it from the start. Auth, external API, and database queries each get a standing review pass, because the cheapest place to catch a class of failure is the moment it becomes possible.

There are no wireframes in this project, and that was deliberate. AI design-and-build tools have collapsed the distance between an idea and a working, themed screen, so the wireframe, whose whole value was being cheap to throw away, had lost most of its reason to exist.

But the bigger reason was the user. I was testing with working real-estate agents, not designers. A wireframe asks a non-technical person to imagine the product from grey boxes, and it reliably produced question marks; a finished screen produced opinions. Skipping the abstraction did not just save time. It improved the feedback, because people were reacting to the thing instead of a diagram of the thing.

What the testers taught me
A finished screen produced opinions. A wireframe produced question marks.
// 07 · Outcome

What Oikion became

Oikion is a product built twice: once to understand what it should be, and once to become it. What emerged puts three modules on a single screen: the MLS listing inventory, the CRM for contacts and deal pipeline, and Oikosync, a real-time team activity feed carried over Ably that keeps a team live without polling.

Beneath those three modules sit the four hard systems the earlier chapters followed: the Import Engine that swallows an agency’s old spreadsheets, the Polis network that lets rival offices share on their own terms, the self-hosted signing flow, and the end-to-end encryption that keeps the most sensitive data unreadable even to us. None of them announces itself. That was the point.

The design system underneath isn’t a style guide. It’s a set of enforced constraints: ESLint catches hardcoded colours at authoring time, TypeScript enforces translation-key correctness at compile time, and components ship WCAG-compliant defaults so the accessible choice is the easy one. The data layer is multi-tenant by design: every query scoped to an organisation, sensitive fields encrypted per-org with a wrapped-key architecture, and a four-role system (Owner, Lead, Member, Viewer) enforced at the server-action layer before any data is touched.

What the pilot actually showed
Five working agents trialled it, small and informal, with no formal scoring and no staged quotes. The read was consistent: Oikion was quick to learn, and felt more polished and easier to use than the incumbent they came from (ilistCRM). I am explicitly not presenting this as a metric; it is a signal, and that is exactly how I am treating it.

Set against the incumbents I actually have to win against, the honest picture is mixed, and saying so is the whole point. They are ahead where eighteen years buys reach and features; Oikion is ahead where a coherent model and design discipline buy fit.

Criteria
ilistCRM (Fortunet)18 yrs, 850+ agencies, ~24 portalsAI integratingMatching on a property coreNative, accreted
RealStatus (iarts)18 yrs, 724 agencies, 28 portalsML, native apps, voice AIProperty + request handlingNative, feature-rich
OikionPre-launch challengerFocused core, no ML yetRequests, first-classDesigned Greek-first, 3-field entry

I’m proud of what Oikion is. I’m prouder of the decisions it required, especially the ones that hurt to make.

// what’s next

Building something that deserves this kind of care?

This is how I work: from the entity model to the last pixel, with the honest parts left in. If that is the sort of partner your team is looking for, I would love to hear from you.