House Call: how it came together.
How it came together.
- 01
Three traditions, one synthesis layer
Each tradition has its own ontology (houses, palaces, pillars), and flattening them into one synthesis on top would lose what makes each useful. The synthesis layer holds all three in parallel and lets the reader weave between them in prose, without collapsing them into a single chart.
Product Strategy - 02
Daily transits as a separate cron
Transit forecasts run against the user's natal chart every twenty-four hours using
Backend Engineeringastronomy-engine, not at request time, because the calculation is expensive and the result is identical for every viewer of that user's day. Caching by user-day collapses the per-request cost to a database lookup and lets the forecast feed surface instantly when the user opens the app. - 03
Falsifiable predictions, logged
Predictions in the reading get tagged with a time window and an outcome category. When the window passes, the user marks the prediction hit, missed, or partial. The tracker holds the prediction reader accountable to their own forecasts, which is both the trust mechanic and the dataset that improves the next reading.
Product Strategy - 04
Per-user auth + Turso libSQL
Per-user auth wasn't optional here. Built on Next.js with jose-signed JWTs against Turso libSQL via Prisma; the database edge-replicates so transit calculations near the user stay fast. Migrations run through a custom script because the Prisma CLI doesn't speak Turso.
Backend Engineering
Decisions on the record.
The few calls worth defending. Each one is a fork; the other branch would have been a different project.
Obsidian to author, Turso to serve
Reading markdown is authored in an Obsidian vault; the Obsidian app is the only editor, and the web app never writes back to it. But the app doesn't read the vault directly: a change is pushed into Turso by a sync script and served from there, so production reads never depend on a second repo's uptime or a live filesystem read on every request. A CMS would still mean a second editor surface; the vault avoids that while the sync step avoids what a live vault read would have cost.
Why per-user access control from day one
Natal readings are intimate, so per-user access is a precondition, not a later feature. Each
natal_userreads only their own reading viagetAccessibleSlugs(user), built in from the first commit rather than retrofitted after a leak.Why AI drafts, the practitioner ships
Every reading is AI-drafted from a curated corpus and human-reviewed before it reaches the user; the DB schema enforces the queue: questions queue, the practitioner answers, the user sees the answer. A pure-AI pipeline would scale faster, but it wouldn't be a practice.
The design system.
Tokens and treatments as they exist in the repo - values verbatim, nothing aspirational.
Named Gilt: gold on midnight, run as a real token system rather than a palette. A full opacity ramp off one gold value, an editorial serif for reading and a system sans for chrome, and the whole thing reachable through user-controlled accessibility overrides instead of a single fixed look.
--gold#C8A96E- Brand anchor: headings, active states, the orbital glyph, focus rings.
--midnight#07060D- Base background across the natal shell and marketing pages.
--cream#F3ECDD- Primary reading text on midnight; autofill fill colour.
--gold-faint / -glass / -soft / -border / -medium / -strong / -bold0.06 / 0.12 / 0.16 / 0.18 / 0.25 / 0.30 / 0.45- A seven-step opacity ramp off the one gold value, replacing ~94 hardcoded rgba(200,169,110,x) literals that had drifted across admin/settings/forms. Recounted 2026-09-11: a seventh step, --gold-soft, sits between -glass and -border and hadn't been reflected here before.
--rose-soft / --lilac-soft#E6B7A3 / #C5B6E6- Prediction/tarot/Ask verdict pastels for "false" and "mixed/uncertain", pulled out as tokens so PredictionsList, TarotForm, and AskForm can't drift apart.
--accent#3B82F6- Public marketing/auth shell only (login, signup, privacy); the natal reading itself never uses blue.
- Reading prose
AaBb Handgloves 0123
"Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serifEditorial serif for the actual reading content inside .natal-shell; drop-caps the first paragraph after H1 unless it opens on a quote/strong/em, where the drop-cap would compete with the callout.
- UI chrome
AaBb Handgloves 0123
system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serifEverything outside the reading itself: nav, forms, buttons, admin.
Plain CSS custom properties in app/tokens.css, loaded once at :root before both app/globals.css (marketing/auth shell) and app/natal/natal.css's .natal-shell (the reading surface). The two stylesheets used to carry their own copies of the same tokens, kept in sync by convention; consolidating into one file and adding a lint rule that fails the build on a duplicate :root declaration removed that drift risk. No Tailwind: the repo shipped unbuilt @tailwind at-rules for months with no tailwind.config or postcss plugin to expand them, so the utility classes were dead code. Removed 2026-07-25 along with the tailwindcss/clsx/tailwind-merge dependencies once that was caught.
- Drop-cap on the reading's opening paragraph
The first paragraph after an H1 gets a large serif drop-cap (::first-letter), sized down responsively at narrow viewports.
Suppressed when the paragraph opens on a quote, <strong>, or <em>, since those already read as a callout, and a drop-cap on top of one competes for the same visual emphasis instead of adding to it.
- Radius scale
Four fixed steps: 4px small controls, 8px cards/panels/buttons, 12px large surfaces, a pill for chips/badges, one shared source instead of per-component values.
- Gold-on-midnight scrollbar
A gold gradient thumb (cream at the top fading to bronze) with a soft glow that intensifies on hover/active, covered on both Firefox (scrollbar-color) and WebKit (::-webkit-scrollbar-*). The reading view's thumb runs 12px instead of 10px so it reads as part of the editorial chrome, not browser furniture.
- "Continue with Google" button
Restyled to the gold-on-midnight register (pill shape, gold border/text) while leaving Google's official 4-colour G mark untouched.
Google's brand guidelines require the mark itself to stay unmodified even when everything around it is reskinned.
- Notification pill/chip (admin header)
Gold-tinted uppercase pill for the idle/enable state, a quieter outlined chip once granted, muted text for denied/unsupported: three states mapped to three visual weights instead of one button that just changes its label.
Killing Tailwind instead of finally configuring it
The fix wasn't adding the missing tailwind.config. The repo had already been style-complete for months on plain CSS custom properties, Tailwind or not. Configuring it now would mean re-deriving styles that already exist as tokens into utilities. Deleting the dead dependency was less work, and one less build step.
An opacity scale instead of ad hoc rgba() literals
The gold accent needed a dozen-odd translucency levels (hover glows, borders, chip fills) and they'd been hand-typed as rgba(200,169,110, <n>) at each call site, ~94 times over, with no guarantee two "the same" usages actually matched. Naming six fixed steps (faint/glass/border/medium/strong/bold) off the one gold value turns every one of those into a lookup, and turns a future rebrand into a one-line change.
Accessibility as data attributes, not a second theme
Text size, motion, and contrast preferences are read from the signed-in user's saved settings and applied as data-text-size / data-motion / data-contrast attributes on <html>, driving CSS custom-property overrides: --text-scale, a brightened --text-dim, a thickened --border. Scaling one root variable propagates through every rem/em measurement in the site. The alternative, a parallel high-contrast stylesheet, would drift from the primary one within a release or two.
