Insights

Custom mental health web apps whose answers never leave the device

Custom mental health web apps whose answers never leave the device

The whole scoring engine of the substance use screener in my clinical tools suite is one expression: document.querySelectorAll('.sud-check:checked'), and then its length. There is no submit button, because there is nowhere to submit to. I re-read all nine live tool pages on 4 September 2026 looking for the place where an answer leaves the browser, and there is not one. Nothing in the tools' own code fetches, opens an XMLHttpRequest, beacons, or even submits a form. The absence is the product. The page those tools sit on is a different story, and I get to it further down.

What are the nine tools, and who are they for?

They are the interactive version of the worksheets a therapy session already runs on, split between the two places those worksheets go.

The suite is a somatic sensation tracker, a bilateral stimulation pacer, an Internal Family Systems parts mapper, a breath pacer, a CBT thought log, a state journal, a DSM-5 criteria checklist, a toolkit page with two standard screeners on it, and a psychoeducation video library. Some are made for the room: things you run while a person is sitting with you, where the tool is a shared object rather than a private one. The rest are for the week between sessions, which is where most of the actual work of therapy happens and where a paper handout usually goes to die in a coat pocket.

The CUTI Labs home page in a browser window: a navigation bar listing Somatic, BLS, IFS, Breath, CBT, Journal, SUD Test, Videos and Tools, a headline reading Therapeutic Innovation, and a line saying I build specialized digital tools to bridge the gap between clinical theory and personal nervous system regulation.
Nine tools, one route each. The navigation is the whole product surface, which is the first sign of what kind of thing this is.

I have a decade in mental health behind the code: residential addiction treatment, then a private practice in trauma and serious mental illness that I ran from 2019 until I closed it in July 2026, and I still carry a CADC III alongside the counseling license. The substance use criteria are the instrument I have sat with most. That is the reason this suite exists at all, and it is also the reason it does less than you might expect. There is no record system in here, no client roster, no export, no login. The whole thing is documented as a case study for practices that want their own version, and the shortest description of what a practice is buying is a tool that holds nothing.

Where do a screener's answers actually live?

In exactly one of three places, and you choose which before you design a single screen.

A three column comparison of where a screener's answers can live. Tier one, in the tab: the DSM-5 checklist, GAD-7 sheet, bilateral pacer, breath pacer and video library, whose state is the checkboxes themselves and does not survive a reload. Tier two, on the device: the state journal, parts mapper, thought log and somatic map, stored under four local storage keys in that browser only. Tier three, on a server: none of the nine, with the note that maintaining protected health information for a practice makes the developer a business associate under 45 CFR 160.103.
The third column is the one nobody notices they chose. It is also the one that brings every obligation with it.

Five of the nine tools sit in tier one. The state of the screener is the checkboxes: no variable holds a copy, no object is assembled, nothing is written down. Reload the page and it is not that the data was deleted, it is that there never was a record. The reset button exists for the case where you do not want to reload, and closing the tab does the same job.

Four tools sit in tier two, because a journal you cannot come back to is not a journal. Those write to local storage in that one browser, under four keys, one per tool, plus a fifth that only remembers a dismissed hint. This is the tier people describe loosely, so here is the precise version: local storage is not encryption, and it is not a secret. It is a drawer in the browser that anyone holding the unlocked device can open. Not transmitted and not readable are two different promises, and only the first one is true here. On iOS the same tension shows up as a privacy label, which I wrote about in the three small apps and their download sizes.

Nobody is in tier three. That is not laziness. A row on a server is a durable, identified, backed-up statement about somebody's mental health, and once one exists, the questions start: who can read it, how long does it stay, who gets told when it leaks, and what happens when a court asks for it. Those are good questions. The cheapest way to answer them well is to never create the row.

What does a counselor know here that a developer usually does not?

That the number is the least interesting part of an instrument, and that most of the instrument is the conditions attached to the number.

Here is the difference in one line of JavaScript. The GAD-7 sheet computes its total like this:

let score = 0, answered = 0;
for (let i = 0; i < 7; i++) {
  document.getElementsByName(`g${i}`).forEach(r => {
    if (r.checked) { score += parseInt(r.value); answered++; }
  });
}
if (answered < 7) return;

A developer's instinct is a live running total, because responsive is good and feedback is good. A partial GAD-7 total is not a low score, it is not a score. The bands only mean anything across all seven items: the paper that introduced the scale says "a score of 10 or greater on the GAD-7 represents a reasonable cut point for identifying cases of GAD" and that "Cut points of 5, 10, and 15 might be interpreted as representing mild, moderate, and severe levels of anxiety". Three items in, the honest display is nothing at all, so that guard clause is the clinical judgment, expressed as code.

The other panel on that same page shows what happens when the conditions are not read as carefully, and I found it on 4 September 2026 while writing this post. The toolkit carries a CAGE-AID panel beside the GAD-7 sheet, and on 4 September 2026 the live code read if(score >= 2) { res.textContent = "Clinically Significant"; }, which leaves a score of 1 sitting in green under "Low Indication". The published scoring line for the instrument, citing Brown and Rounds (1995), is to regard one or more positive responses as a positive screen; in the same validation table one or more yes answers gives a sensitivity of 0.79 and two or more gives 0.70, so a cut point of 2 under-flags exactly the people a conjoint screener exists to catch. Moving the threshold to 1 and relabeling the result is the next commit to the lab, and finding it this way is the argument of this whole section: a threshold is a clinical claim living in a JavaScript file, and somebody has to read that file with the literature open.

The same instinct applies to what surrounds a threshold. The DSM-5 count carries a window: in the National Institute on Alcohol Abuse and Alcoholism's summary, "anyone meeting any 2 of the 11 criteria during the same 12-month period would receive a diagnosis of AUD". A checkbox list quietly drops that clause, because checkboxes have no tense. The version I would build for a practice puts the window in the instrument's own heading, next to the count, rather than in the notes underneath.

The evidence sheet of the DSM-5 screener, opened over the tool: an overview section, a clinical evidence section citing four papers, a reference list including the DSM-5 itself and Hasin and colleagues 2013, and a technical implementation note reading pure client-side scoring with zero data transmission.
Every tool opens a sheet like this from its own header: what it implements, the literature behind it, the references, and what it does with your data.

That sheet is where the tool says, in its own words, that it is a screening tool and not a clinical diagnosis. Building it was the right call and I would do it again. Reading it back for this post, the thing I would change is where the sentence lives: it is one tap away, and the number is on the face. The number is what gets read out loud. A qualifier should travel with the thing it qualifies. The same goes for the larger qualifier, which I will put here in plain words: none of the nine is treatment. They are adjuncts to treatment, things to run alongside the work a person and a clinician do together, and not a substitute for any part of it.

The last one is smaller and it comes straight from the room. Every tool has a visible reset, one tap, with a confirmation. Not because users mistype, but because in a session a screen gets turned around and handed across a desk, and the person who touches it next should not be reading the person who touched it before. A shared laptop in a practice is normal. Designing as though every device has exactly one owner is a developer assumption that the room disproves in the first week, and it is the same instinct behind building for one thumb before anything else. Where the rest of this comes from is the ten clinical years.

Where is the line between a website and a covered entity's system?

For the builder, the line is a verb list. You cross it when you create, receive, maintain or transmit health information on a practice's behalf.

The definition at 45 CFR 160.103 is specific about it. A business associate is a person who, on behalf of a covered entity,

creates, receives, maintains, or transmits protected health information for a function or activity regulated by this subchapter, including claims processing or administration, data analysis, processing or administration, utilization review, quality assurance, patient safety activities listed at 42 CFR 3.20, billing, benefit management, practice management, and repricing ...

That is prong (i), and the quote stops where the definition does not. Prong (ii) of the same section reaches a person who provides "legal, actuarial, accounting, consulting, data aggregation ..., management, administrative, accreditation, or financial services" to a covered entity "where the provision of the service involves the disclosure of protected health information" to that person, which is a separate question, and the one a builder with access to a practice's devices, screens or staff has to answer honestly about the engagement rather than about the code.

Four verbs in prong (i), and a tool with no server performs none of them. That is the actual argument for this architecture. The tool does not have a good answer to the compliance question. It has no question to answer, and the agreement, the breach plan, the retention schedule and the awkward call in three years all go with it.

The DSM-5 criteria checklist in a browser window, in its empty state: eleven criteria as unchecked circles, a score badge reading Score 0 with a reset icon beside it, and a result card reading No Criteria Met with the classification set to none.
The empty state is the honest screenshot. Every visit starts here, because there is nothing for the page to remember.

The practice is on the other side of that line and stays there. A therapist using this in session is a covered entity the whole time, and what they write in the record afterwards is the record. The Office for Civil Rights makes the same point about apps a practice offers to its clients: information collected by a regulated entity's mobile app "generally is PHI", and passing it to a tracking vendor is a disclosure of it. That is precisely the burden a tool can lift off a practice by holding nothing, and it is why "we do not sell your data" is a much weaker sentence than "there is no request".

Being outside HIPAA is not the same as being unregulated, which is the part builders miss. The Federal Trade Commission's Health Breach Notification Rule at 16 CFR part 318 covers the gap by design: it "does not apply to HIPAA-covered entities, or to any other entity to the extent that it engages in activities as a business associate of a HIPAA-covered entity", and its definitions reach "any online service such as a website, mobile application, or internet-connected device that provides mechanisms to track diseases, health conditions, diagnoses or diagnostic testing, treatment, medications, vital signs, symptoms, bodily functions, fitness, fertility, sexual health, sleep, mental health". A mental health web tool arguably reaches that sentence. The duty then attaches one step further on, to a vendor of personal health records, and 16 CFR 318.2 defines a personal health record as one "that has the technical capacity to draw information from multiple sources and that is managed, shared, and controlled by or primarily for the individual", which is a test to run against your own build rather than to assume either way. Whichever side of the HIPAA line you land on, somebody is holding the other end.

I am a builder quoting regulators, not a lawyer, and this ground moves: a Texas court vacated part of the OCR tracking guidance in June 2024, which is a reason to read the current page rather than a blog post about it. hhs.gov returns a 403 to my fetcher, so I read that bulletin through the Internet Archive on 4 September 2026, and the eCFR text above came from the API on the same day.

What does the page around the tool have to do?

Less than the tool does, and on the lab pages it currently does more.

The tool's architecture settles what happens to an answer. It says nothing about the page the tool is mounted on, and my own lab is the example. Every one of those pages loads eight third-party script tags from five hosts: Google Analytics through googletagmanager.com, a CSS framework from a CDN, an animation library in three parts, an icon library, a smooth scroll library and a confetti library. Every one of them also loads a stylesheet from fonts.googleapis.com and preconnects to fonts.gstatic.com, and thirteen YouTube embeds sit on the video library page, one per video. Ordinary furniture, and worth saying plainly. None of it can see an answer, because the answers never leave the DOM and there is no request of mine to piggyback on. Google's analytics script does report the page view, using navigator.sendBeacon inside Google's own code, which is the exact call my code never makes. So the honest version of the claim is the narrow one: the answers do not leave the device, while the visit does. Google Analytics sees that a browser loaded a page named for a screener, and on the video library YouTube sees which videos the page framed.

That distinction is exactly where the regulators have been looking, and the Health Breach Notification Rule counts more than a burglary: a breach of security "includes an unauthorized acquisition of unsecured PHR identifiable health information in a personal health record that occurs as a result of a data breach or an unauthorized disclosure". A disclosure. So the rule for a page I hand to a practice is narrower than the rule for my own lab bench: a page carrying a screener serves its own scripts and its own fonts, and loads no analytics tag and no embeds. Self-hosting five libraries and a type family is a small job on a new build. It is a conversation on an old one, which is why it belongs in the first sketch rather than the security review.

What would I hand a practice tomorrow?

One page per tool, a whitelist in front of it, no database, no login, and the qualifier printed next to the number.

The shape is deliberately unfashionable. Each tool is one route and one view. The router accepts a page name only if it is in an explicit list of allowed pages, which is the two-line version of not letting a URL choose which file gets included. There is no schema, so there are no migrations. There is no account, so there is no password reset, no session, no email, no recovery flow, and no table of people. Most of what makes a small web app take three months is the machinery around identity, and a tool that identifies nobody skips all of it. That is the honest reason these ship in days, and it is the same reason a clinician subscription can sit at ninety-nine dollars a month without a support burden underneath it. If a practice wants the tool findable as well as private, that is a different job, and it starts with why the practice site is not showing up in the first place.

One thing is still owed. The cut points live in JavaScript and the citations live in the evidence sheet, and nothing connects them: I could change a threshold tomorrow and the reference list would go on saying what it says. The next version puts the source of a number in the same file as the number, so that moving one without the other is impossible. A threshold with no provenance is asking a clinician to trust me instead of the literature, and that is the wrong way round.

Common questions

Do these tools store what a person enters?

Five of the nine keep nothing at all: the answers are held by the form controls on the page and are gone on reload. The other four, the state journal, parts mapper, thought log and somatic map, save to local storage in that one browser, under four keys, and each has a one-tap flush. Nothing a person enters is transmitted either way: across all nine live pages, checked on 4 September 2026, the tools' own code contains no fetch, no XMLHttpRequest, no sendBeacon and no form element. Page views are a separate matter. The lab pages load Google Analytics and Google Fonts, and the video library page carries thirteen YouTube embeds, so the visit is visible to Google and YouTube even though the answers are not.

Is a clinical web tool like this covered by HIPAA?

HIPAA regulates covered entities and their business associates. Under prong (i) of 45 CFR 160.103 a developer becomes a business associate by creating, receiving, maintaining or transmitting protected health information on a practice's behalf, and a tool with no server does none of those four, so no answer is held by anyone but the person using it. Prong (ii) of the same definition is a separate question, reaching consulting, management and administrative services where the service involves disclosure of protected health information, and it is about the engagement rather than the code. The practice is still a covered entity throughout, and the note written after the session is still the record. I am a builder quoting the regulation, not a lawyer.

Is a screener the same thing as a diagnosis?

No, and the tool says so itself: its evidence sheet describes it as a screening tool and not a clinical diagnosis. These tools are adjuncts to treatment, not treatment. The DSM-5 threshold also carries a condition a checkbox list cannot show. In the National Institute on Alcohol Abuse and Alcoholism's summary, it takes any 2 of the 11 criteria "during the same 12-month period". A count is something to bring to a conversation with a clinician, not a result.

What would a version for my practice look like?

One page per tool, a router that only serves a page name from an explicit allowed list, no database, no accounts, and every third-party script self-hosted so the page loads nothing from anywhere else. Because there is no identity layer, there are no migrations, sessions or password resets to build, which is why these ship in days rather than months. The case study on the tools has the detail.

Related

← All insights