A small nonprofit supports women through the loss of a parent — community events, grief support groups, subsidized therapy, and the anniversaries that come around every year. It had no product at all. We built both halves: the public site that has to earn an application, and the members' app and back office the whole organization now runs on.
A public face and an operating system for the organization behind it — because a site that converts strangers into applicants is worth very little if nobody has anywhere to review the applications.
Members arrive grieving, often distressed, frequently on a phone, sometimes not especially technical. Nobody came here wanting to use software. So the arrival flow asks one group of questions at a time, leaving a memorial wish offers preset words before it offers a blank box, and nothing important is ever a single unguarded click.
A Yahrtzeit — the anniversary of a death — is recorded as a Hebrew date, so its date in the civil calendar moves every year and has to be recomputed. In a leap year, an anniversary in plain Adar is observed in Adar II. That is domain logic, not a formatting detail, and it is handled natively rather than approximated.
Her record is hers. Staff who need to see the member app on live data impersonate a dedicated test account and nothing else, behind three separate guards. Closing an account is a request rather than a button. Erasing the personal details is a different, deliberate action — and it leaves the memorial standing.
Warmth and dignity, without slipping into either sterility or sentimentality.
The design system names what it is not as carefully as what it is: not clinical, not corporate, not greeting-card, not funereal. Two registers meet at the login — a warm public voice that has to persuade someone to apply, and a calmer interior built for repeat use — sharing one hue doing two jobs, so the accent colour is spent only on the action that ends a form. Crossing the login is a change of register, not a change of brand.
Every one of these started as a sentence about a person — what she should never accidentally do, what should survive her leaving, what a staff member needs to see without being able to read someone's private record.
Anniversaries are found in two steps: narrow by day of month in the database, then confirm each candidate by converting it into the target year's Hebrew calendar — including the leap-year rule that moves a plain-Adar date into Adar II. It uses the calendar extension already in the language rather than a dependency, and the logic was extracted once a second command needed it.
Admins can view the member experience on live data, but only as a dedicated test account — guarded three ways, and the only place in the codebase that logs a user in directly, so that account can hold a password nobody knows. Every entry and exit is written to the audit log.
A member asks to close her account; an admin erases the personal details later, and only on an already-closed account. The erasure anonymizes her without tearing down what she left behind — the parent she commemorated and the wishes written for her stay. The audit entry records her name and email as they go, because that becomes the last place she is named at all.
Bulk email is split into categories a member can decline and one she cannot, because transactional mail is not marketing. Unsubscribing asks for confirmation and doesn't promise an instant effect it can't deliver. Every send records who it reached and what the mail provider did with it.
The cluster scales to zero, and waking it holds it awake for a full sleep timeout — so a check every fifteen minutes bills around the clock. Reminders run hourly instead, and the command is written to be idempotent and to catch up, so a longer interval can delay a reminder but never skip one.
The processor changed late, which meant reworking refunds into voids of unsettled payments and simplifying the donation types. Recurring billing runs in the application rather than at the processor, because each cycle needs its own record to produce a receipt and an annual statement — and it runs without overlapping, since two concurrent passes could charge the same donor twice.
A small nonprofit has no engineering staff and no appetite for surprises. Everything that could be made a rule the machine enforces was made one, so the software doesn't need supervision to stay correct.
Four gates, all of which must pass before anything merges: coverage at exactly 100% by line and by type, three formatters with zero diff, and static analysis with a single documented exception. Alongside the feature tests are architectural ones — conventions the codebase must keep obeying, including a test that the design system's own typography stays as specified.
Coverage as an equality rather than a minimum is a deliberately awkward rule. It is also the only version that doesn't quietly erode.
The platform described the community as "peer-led" until we looked at what that sentence was doing. It described how the organization is run rather than what a visitor gets — and it undersold the programme, because support groups and subsidized therapy are not peer-led. It came out of every page.
The client's own rewrite of the public copy was then taken wholesale. They know how to say this to the person arriving; our job was to make sure the software didn't contradict them.
This is web application development with UI and UX design done as one job, on the timeline of an MVP build.
Tell us who arrives at your product and what state they arrive in. That answer changes more of the build than the feature list does — and it is the part most software gets wrong before a line is written.