Family Dashboard v3.30: Writing My Own Weather One-Liners

TL;DR: The Family Dashboard v3.30 replaced 156 comedian-style weather quips — borrowed material that never quite felt like mine — with 56 clean, original one-liners. The modular weather-narrative-engine.js built during the v3.26 refactor made the swap a single-file change. Fewer lines, owned voice, better fit for a dashboard you look at a dozen times a day.

The Dashboard, Briefly

The Family Dashboard is a wall-mounted display I run on an older iPad. It shows today's weather, CalDAV calendar events from up to three accounts, a weekend preview, and a rotating one-liner tied to the current weather conditions. It refreshes every 30 minutes and has been running 24/7 since I first deployed it.

If you want the full origin story — the CalDAV migration, the timezone bug hunts, the 2,000-line code purge — the v3.7 to v3.26 evolution post covers all of that. This post picks up where that one left off.

The Problem with Borrowed Material

The weather commentary system has been in the dashboard since early versions. The idea is simple: after the temperature and forecast description, a one-liner appears — something light, a bit playful, appropriate to whether the weather is good or bad. The dashboard becomes slightly less utilitarian and slightly more alive.

For a long time, the commentary was sourced from comedian-style quips — the kind of jokes you find in lists of "funny weather quotes." Some of them were good. A lot of them were fine. But none of them were mine. And on a display I look at a dozen times a day, the difference between writing that sounds like you and writing that sounds like a random internet list turns out to matter.

The earlier implementation had accumulated 156 total entries across four weather categories: good weather, poor weather, extreme heat, and extreme cold. A few of the lines were genuinely sharp. Many were the kind of weatherman-humor that wears out quickly. Several were clearly paraphrased from comedian material — technically original enough, but carrying the debt of borrowed tone.

The v3.30 decision was to cut the entire set and start over.

What the Narrative Engine Looks Like

The v3.26 modular refactor extracted the weather commentary system into its own file: weather-narrative-engine.js. Before that refactor, commentary logic was scattered through the main dashboard module — arrays defined inline, selection logic mixed with rendering code, no clear boundary between "data" and "how we display data."

After the refactor, weather-narrative-engine.js owns all of it:

// weather-narrative-engine.js
const narratives = {
  goodWeather: [
    /* entries */
  ],
  poorWeather: [
    /* entries */
  ],
  extremeHeat: [
    /* entries */
  ],
  extremeCold: [
    /* entries */
  ]
};

export function getWeatherNarrative(conditions) {
  const { tempF, precipitation, isExtremeHeat, isExtremeCold } = conditions;

  let pool;
  if (isExtremeCold) pool = narratives.extremeCold;
  else if (isExtremeHeat) pool = narratives.extremeHeat;
  else if (tempF >= 60 && !precipitation?.expected) pool = narratives.goodWeather;
  else pool = narratives.poorWeather;

  return pool[Math.floor(Math.random() * pool.length)];
}

The interface is deliberately narrow: pass in a conditions object, get back a string. The rest of the dashboard doesn't know or care how the string was chosen. It just renders it.

This separation is what made the v3.30 overhaul painless. The commentary rewrite was a single-file change. The calling code didn't move. The rendering didn't move. I opened weather-narrative-engine.js, deleted the arrays, and wrote new ones.

Writing 56 Original Lines

Cutting from 156 to 56 was intentional. The prior set had accumulated entries over time without any real curation — a line would get added because it was okay, and it would stay because nothing forced a review. The result was a pool that was large but uneven: a handful of genuinely good lines diluted by a lot of filler.

Starting from zero with a target of 56 forced actual decisions. Each of the four categories gets 14 lines:

  • Good weather (14) — for days above 60°F with no precipitation expected
  • Poor weather (14) — for cold, rainy, or generally discouraging days
  • Extreme heat (14) — triggered when the temperature crosses the heat threshold
  • Extreme cold (14) — triggered when it drops below the cold threshold

Fourteen per category means you see a repeat roughly every two weeks if you check the dashboard once a day. That felt like the right cadence — familiar enough to become part of the rhythm without becoming invisible.

What "Original" Actually Meant

The bar I set for "original" wasn't novelty for its own sake. It was: does this line sound like something I'd actually say? The test was reading each line aloud and asking whether it felt natural or performed.

The comedian-style lines had a particular problem: they were written to land in isolation, to get a reaction from a stranger reading a list. A dashboard one-liner has a different job. It's read by the same person, repeatedly, in the context of their actual day. It needs to fit the moment without demanding attention.

Some examples of what the new lines look like in practice:

Good weather:

  • "Take the long way home tonight."
  • "The forecast recommends leaving your desk at some point."
  • "Your future self will thank you for going outside."

Poor weather:

  • "A good day to clear the backlog."
  • "The weather is doing its best. Probably."
  • "Inside is underrated."

Extreme heat:

  • "Drink water. No, more than that."
  • "The sun is very committed today."
  • "Good conditions for staying extremely hydrated."

Extreme cold:

  • "Layer up. Then layer again."
  • "The cold builds character. Or at least a good excuse."
  • "Your car would like a few extra minutes this morning."

None of these are trying to be jokes. They're observations — small, low-pressure, appropriate to a utility you interact with before you've fully woken up. The tone is more dry than comedic, more matter-of-fact than punchline-seeking.

The Architecture Lesson

The smoothness of the v3.30 overhaul is a direct consequence of the v3.26 modular refactor. Before that refactor, a change like this would have required touching multiple files, tracking down inline commentary arrays, and carefully avoiding breakage to the rendering logic sitting right next to them in the same function body.

After the refactor, it was: open one file, rewrite the data, close the file. Done.

The v3.26 refactor created five new modules beyond weather-narrative-engine.js:

  • logger.js — centralized logging with configurable verbosity levels
  • error-handler.js — consistent error display and recovery patterns
  • date-utils.js — timezone-aware date arithmetic extracted from the main module
  • caldav-client.js — CalDAV request and XML parsing logic
  • api-client.js — weather API calls and response normalization

Each module has a narrow interface. caldav-client.js doesn't know anything about how the dashboard renders events. weather-narrative-engine.js doesn't know what temperature threshold triggers "extreme heat" in the UI layer — it receives a pre-evaluated conditions object. The coupling is minimal by design.

This is the reward for doing the modular work earlier: future changes become local changes. The cost of the v3.26 refactor was paid upfront. v3.30 spent that investment.

What Else Changed in v3.30

The commentary overhaul was the primary change, but v3.30 included a few smaller improvements alongside it:

  • Removed emoji overuse — the older commentary set used emoji heavily as punctuation. The new set uses them sparingly, only where they add something rather than just adding color.
  • Tightened category thresholds — the temperature cutoffs for "extreme heat" and "extreme cold" were adjusted to better reflect the region's actual climate. What triggers "extreme cold" commentary in Florida is different from what would in Minnesota.
  • Simplified the selection function — the previous implementation had branching logic that handled some edge cases with fallbacks. The new conditions object structure made the branching cleaner, and a few edge cases turned out to be unreachable in practice.

Running It

If you want to run the Family Dashboard yourself, the setup is straightforward for a self-hosted or GitHub Pages deployment. The CalDAV integration requires a Vercel serverless function to proxy requests — the README covers the full setup including generating App Passwords from Google.

# Clone and open locally
git clone https://github.com/josefresco/family-dash.git
cd family-dash

# Open setup.html first to configure your API keys and CalDAV credentials
# Then open dashboard.html — no build step required

# For CalDAV support, deploy the api/calendar.js function to Vercel
# and set CALDAV_PROXY_URL in config.js to your function URL

The dashboard is vanilla JavaScript with no build tooling. You can open dashboard.html directly in a browser and it will work for everything except the CalDAV integration, which requires the serverless proxy. There's a setup.html wizard that walks through configuration for first-time setup.

Personality in Utility Software

The reason this overhaul was worth doing at all comes down to a question about what kind of software the Family Dashboard is trying to be. It's a utility — it displays information you need to start your day. But it's also something you look at constantly, every day, in the same physical space as your family.

That context shapes what "personality" should mean. A Twitter bot can have an exaggerated voice; the stakes of getting it wrong are low and the audience is transient. A wall display in your kitchen has a different relationship with its audience. It needs to fit the room without demanding to be noticed. It should add something small without subtracting focus.

The comedian-style quips failed this test not because they were unfunny — some of them were — but because they were performing. They asked you to appreciate them. The new lines try not to ask for anything. They're just there, briefly, and then your attention moves on to the calendar.

That's the right job for this kind of software personality: low friction, appropriate to the context, and genuinely yours.

What's Next

A few things on the roadmap for future versions:

  • Configurable commentary — exposing the narrative engine's data as a JSON config that users can override without touching JavaScript. The module boundary makes this straightforward to add.
  • Timezone configurability — the dashboard currently hardcodes Eastern Time. Abstracting this into config.js is a one-afternoon change now that date-utils.js owns the timezone logic.
  • Visual diff for event changes — showing which calendar events have changed since the last refresh, rather than silently re-rendering the full event list.
  • Multi-account color coding improvements — the per-account colors work well for three accounts but need refinement when a fourth account gets added.

Conclusion

v3.30 is a small release that fixed something I'd been quietly bothered by for a while. The borrowed commentary wasn't broken — it was just not quite right. Replacing it with something smaller and more genuinely mine made the dashboard feel more settled.

The architectural lesson is the more transferable one: the work you do to separate concerns pays forward. The v3.26 modular refactor was uncomfortable when it happened — more files, more indirection, more decisions to make about where things belong. v3.30 made clear why it was worth it. A change that would have been an afternoon of careful surgery became twenty minutes of focused writing.

The full source is on GitHub. Forks welcome — and if you end up writing your own set of weather one-liners, I'd be curious what voice you end up landing on.

Need a Custom Dashboard Built?

I build personal and business dashboards that pull from multiple data sources into clean, always-on interfaces. From family calendars to operational metrics, let's build something useful together.