DEV Community

Cover image for How I Built a Behavioral Activation Tracker in 80 Lines of Vanilla JavaScript — No Framework, No Backend, No Therapy App
CBT Tools
CBT Tools

Posted on

How I Built a Behavioral Activation Tracker in 80 Lines of Vanilla JavaScript — No Framework, No Backend, No Therapy App

Depression tells you nothing will feel good. The data says otherwise.

Behavioral activation is the single most effective CBT technique for depression. The protocol is simple: schedule an activity, predict how rewarding it will feel, do it anyway, then rate how it actually felt. The gap between prediction and reality is the intervention — depression systematically under-predicts pleasure, and seeing the gap corrects the belief.

Most apps that do this are heavy, require signup, cost money, or send your mental health data to a server. I built one in 80 lines of vanilla JavaScript with zero dependencies, zero backend, and zero signup. Your data stays in localStorage.

The algorithm

1. User schedules an activity (e.g., "walk in the park")
2. User predicts: pleasure 0-10, mastery 0-10
3. User does the activity
4. User rates actual: pleasure 0-10, mastery 0-10
5. Compute gap = actual - predicted
6. After ≥2 completed activities, compute average gap
7. If avg gap > 0.5 → "depression underestimates reward" (BA working)
8. If avg gap < -0.5 → "try smaller activities or add social contact"
Enter fullscreen mode Exit fullscreen mode

The prediction-error correction IS the therapy. You're not just tracking mood — you're building an evidence base that contradicts the depressive belief "nothing will feel good."

The data structure

var baKey = 'cbt_activity_scheduler';
var baEntries = [];
try { baEntries = JSON.parse(localStorage.getItem(baKey) || '[]'); } catch (e) {}

function baSave() {
  try { localStorage.setItem(baKey, JSON.stringify(baEntries)); } catch (e) {}
}
Enter fullscreen mode Exit fullscreen mode

Each entry stores both predictions and actuals:

baEntries.unshift({
  id: Date.now(),
  activity: act,
  type: type,
  predP: predP,    // predicted pleasure 0-10
  predM: predM,    // predicted mastery 0-10
  actP: null,      // actual pleasure (filled later)
  actM: null,      // actual mastery (filled later)
  done: false,
  date: new Date().toISOString()
});
Enter fullscreen mode Exit fullscreen mode

The gap calculation

When the user rates the actual experience, we compute the prediction error:

var gapP = (e.actP !== null) ? (e.actP - e.predP) : null;
var gapM = (e.actM !== null) ? (e.actM - e.predM) : null;
Enter fullscreen mode Exit fullscreen mode

If gapP > 0, it felt better than predicted. We show this inline:

var gapParts = [];
if (gapP !== null) gapParts.push('pleasure ' + (gapP >= 0 ? '+' : '') + gapP);
if (gapM !== null) gapParts.push('mastery ' + (gapM >= 0 ? '+' : '') + gapM);
var positive = (gapP !== null && gapP > 0) || (gapM !== null && gapM > 0);
// "Actual vs predicted: pleasure +3, mastery +2 — it felt better than you expected."
Enter fullscreen mode Exit fullscreen mode

The insight engine

This is the part that makes it more than a tracker. After ≥2 completed activities, we aggregate the gaps:

var done = baEntries.filter(function(e) { return e.done; });
if (done.length >= 2) {
  var sumGap = 0, n = 0;
  for (var j = 0; j < done.length; j++) {
    if (done[j].actP !== null) { sumGap += (done[j].actP - done[j].predP); n++; }
    if (done[j].actM !== null) { sumGap += (done[j].actM - done[j].predM); n++; }
  }
  var avg = n ? (sumGap / n) : 0;
Enter fullscreen mode Exit fullscreen mode

Then two branches:

  if (avg > 0.5) {
    // "Across N activities, actual reward averaged X points higher than predicted.
    //  This is behavioral activation working — depression systematically
    //  underestimates how rewarding activity actually is."
  } else if (avg < -0.5) {
    // "Activities are feeling less rewarding than predicted.
    //  Try smaller activities, or pair them with social contact."
  }
}
Enter fullscreen mode Exit fullscreen mode

The 0.5 threshold avoids noise from single-point fluctuations. The two-branch design mirrors what a CBT therapist does: reinforce progress when it's working, adjust the protocol when it's not.

Why vanilla JavaScript, not an ML model

You could train a model to predict how rewarding an activity will be for a given user. That would be worse, for four reasons:

  1. The prediction is the intervention. If an ML model predicts "you'll enjoy this 7/10," the user never forms their own (depressed) prediction, never experiences the prediction error, and never updates their belief. The user's OWN prediction being wrong is what makes behavioral activation work.

  2. Determinism. A therapist showing you the same evidence gives the same insight every time. An ML model with stochastic output would undermine the evidence-building process.

  3. Privacy. Mental health data should not leave your device. localStorage never sends anything anywhere.

  4. Zero cost, zero latency. No API calls, no model loading, no subscription. The tool works offline on a phone with no signal.

What I left out (and why)

  • No authentication. Your data is in your browser. If you want to move it, there's an export button. No accounts means no data breaches.
  • No "AI insights." The insight engine is a threshold comparison. A 40-line LLM prompt would add latency, cost, hallucination risk, and privacy concerns to tell you the same thing a sumGap / n > 0.5 check already says.
  • No charts. The gap is shown inline per activity. A chart would be nice but adds 200KB of Chart.js to show 5 numbers.

The full tool

Live demo: CBT for Depression — Activity Scheduler

The full toolkit (22 free mental health tools, all vanilla JS, all no-signup): cbt-toolkit on GitHub

The activity scheduler is one of 22 tools in the toolkit. Each follows the same pattern: a specific CBT technique, implemented in under 100 lines of vanilla JavaScript, with the algorithm IS the therapeutic mechanism — not a wrapper around an API.

If you found this useful, the toolkit is free and open-source. Star it on GitHub if you want to see more tools built this way.

Top comments (0)