Your Vedic (traditional Indian) astrology chart, free and computed in your own browser — no account, no data harvesting.
Live demo · Docs · Quickstart
Real output of the example below — the home screen make demo opens at http://localhost:4173 (the same app runs at almamesh.com).
- What it does — Like the birth-chart readings on paid astrology sites, but free and calculated on your own device. Enter a birth date, time, and place and it draws your Vedic chart (the Indian tradition, which measures against the fixed stars rather than the Western calendar zodiac): where each planet was, which sign was rising, and the planetary time periods astrologers use for timing. No AI touches your chart unless you switch it on.
- Who it's for — Someone curious about Vedic astrology who wants to see their own chart without typing their birth details into a site that demands an email and then sells a "full reading". Also for skeptics who want to check the math themselves, and practitioners who want a careful tool that costs nothing.
- What stays on your device / what leaves it — Stays: your name, birth date and time, every chart, and your saved chats. No server holds your birth data or charts, and the chart calculation makes no network calls. Leaves: the city name you type to find your birthplace (sent to Open-Meteo, a free place-lookup service; a built-in city list works offline); and — only if you turn on the optional AI — chart details with your name and birth date removed, the questions you ask, and any life story you explicitly ask it to organize, sent to the AI service you choose. Optional feedback sends a thumbs up or down, the page it is about, and whatever you type. The app tells your browser to refuse connections to any address not on a short fixed list. Full list of network requests.
- Runs on — Any modern browser on any operating system; checked in CI on the engines behind Chrome and Safari. Install it from the browser like an app; once the engine has downloaded, it keeps working with no internet. English, Spanish, and Portuguese.
- Not for — Certain predictions: it is an honest experiment, not a fortune-teller, and relationship scores are a traditional convention, never a verdict. Also not for syncing through an account — there isn't one; you move your data by exporting a single file and restoring it on another device.
- Status — Beta: v0.4.0 (pre-1.0), live at almamesh.com and deployed from
main; changes since v0.4.0 are listed under Unreleased. See CHANGELOG.
Fastest: open almamesh.com — nothing to install, no sign-up.
To run your own copy (needs uv and Bun):
git clone https://github.com/hseshadr/almamesh.git && cd almamesh && make demoThe first run takes longer than a minute: it installs packages, downloads the in-browser Python engine and the planet-position tables once, signs a local copy of them with a throwaway key, and builds the app. Then:
- Your browser opens http://localhost:4173 — the home screen above.
- Click Generate my chart — free, enter any name and birth date and time, and search for a city.
- Click Generate. First time only, the engine (about 38 MB) downloads and is saved in your browser.
- Your full chart appears on the dashboard, computed in your tab. Disconnect from the internet and it still works.
No browser? Get the same chart as data in your terminal
cd backend && uv sync --extra dev
uv run almamesh-chart "1990-01-15T12:00:00+00:00" 40.7128 -74.0060 # New York, 15 Jan 1990, 12:00 UTCExcerpt of the real output. lagna is the rising sign; a nakshatra is one of
the 27 star groups the Moon passes through, split into four pada quarters:
{
"ayanamsa_value": 23.7174,
"lagna": {
"longitude": 264.9083218673543,
"sign": "Sagittarius",
"sign_degrees": 24.90832186735429,
"sign_lord": "jupiter",
"nakshatra": "Purva Ashadha",
"nakshatra_pada": 4,
The full JSON (about 47 KB) also lists every planet, the houses, and the time
periods. It runs with no network and no account. Everything above depends only
on the three arguments. The current_maha, current_antar, and
current_pratyantar fields do not: this CLI has no reference-date option, so
they show the periods running on the day you run it.
More runnable examples: examples/.
The first time you generate a chart, your browser downloads a signed engine bundle (the real Python chart engine, its planet-position tables, and the packages it needs) and checks its signature and every file's hash before using it. The engine then runs inside your browser tab, in a background worker, using Pyodide (Python compiled to WebAssembly). Your birth details go straight from the form to that engine; the finished chart is saved in a database inside your browser. The only lookup that leaves the device during chart creation is the city name, and only while you search for your birthplace.
flowchart LR
A["You enter birth date, time, place"] --> B["Chart engine running in your browser tab"]
B --> C["Your chart, saved only in this browser"]
D["Signed engine download from almamesh.com, about 38 MB, then cached"] -.->|signature and hashes checked before use| B
A -.->|city name only| E["Open-Meteo place lookup"]
classDef blue fill:#e8f4f8,stroke:#7aa7b8,color:#171717
classDef green fill:#e8f8e8,stroke:#7ab87a,color:#171717
classDef orange fill:#f8f0e8,stroke:#b8987a,color:#171717
classDef purple fill:#f0e8f8,stroke:#9a7ab8,color:#171717
class A blue
class B,C green
class D orange
class E purple
Explore the interactive architecture map →
(Archify, generated from docs/architecture/runtime.architecture.json).
Deep dive: docs/ARCHITECTURE.md.
Most astrology apps ask you to trust a server with personal birth data. AlmaMesh does not have a chart-data server. The browser downloads a signed engine once, verifies it, and computes charts locally in a Web Worker.
The engine reads no clock when the app calls it. A chart is a pure function of four recorded inputs — birth instant, latitude, longitude, and the reference instant that selects the current Vimshottari period. The app stores that fourth input with the chart, so the same inputs produce the same bytes on CPython and in the browser. (The terminal CLI above is the exception: it passes no reference instant, so the engine falls back to today's date for the "current period" fields.)
Browser (the product) ─ installable PWA, offline after first load
│
├─ frontend/apps/web React + Vite + Tailwind UI
│ └─ birthplace search Open-Meteo first, bundled city fallback
│
├─ frontend/packages/browser the in-browser engine
│ ├─ edge-proc bundle sync ──▶ verifies ed25519 + sha256
│ │ OPFS primary; IndexedDB fallback + shared rollback floor
│ └─ Pyodide Web Worker ────▶ boots the UNCHANGED almamesh wheel, computes the chart
│ │ emits SiderealChart (TS mirror of the Python SiderealContext)
│ ▼
├─ frontend/packages/store pure adapters (reshape only, no astrology):
│ ├─ SiderealChart -> ChartData (the UI contract)
│ ├─ buildChartGeometry(SiderealChart) (N/S kundli geometry)
│ ├─ buildEnergyFrame(SiderealChart, t) (3D force-field frame)
│ ├─ profiles + members (named, password-less people; typed relationships)
│ ├─ portable SQLite state canonical user data in one OPFS database;
│ │ legacy IndexedDB migrates once, API keys stay out
│ └─ mesh (MeshEdgeContext per pair → the /mesh edge view)
├─ frontend/packages/llm optional interpretation + chat, NO AI by default;
│ opt-in, BYO OpenAI-compatible endpoint (one-click
│ OpenRouter preset or a local Ollama); save runs a
│ connectivity test; chart prompts PII-redacted, fail-closed
│ local_only; mesh narration is role-anonymized
│ (no names leave the device)
├─ frontend/packages/shared-types UI-facing TypeScript contracts
├─ frontend/packages/constants single design-token source
├─ frontend/packages/memory local semantic chat memory:
│ ├─ MiniLM Web Worker self-hosted, on-device embeddings
│ └─ SQLite vector Worker sqlite-vector exact search; OPFS persistence
External Lego: @edgeproc/browser signed-bundle sync plus SQLite/vector browser substrate
Build-time (Python, no server)
│
└─ backend/src/almamesh
├─ calculations.py sidereal astronomy (Skyfield + DE421; Lahiri default,
│ True-Chitra ayanamsa + True-node selectable)
├─ dasha/ yogas/ Vimshottari dasha + yoga detection
├─ transits/ strength/ predictive: Gochara/Sade Sati, Ashtakavarga + Shadbala, vargas
├─ mesh/ relationship engine: Ashtakoota Guna Milan + Mangal (cited
│ classical tables), chart overlay, daśā synchrony, significators
│ → a frozen, read-only MeshEdgeContext per pair
└─ edge/
├─ chart_runtime.py deterministic on-device chart runtime (also runs under Pyodide)
├─ bundle.py signed bundle publisher + consumer
├─ cli.py almamesh-chart (offline chart, no browser)
└─ publish_cli.py almamesh-bundle (keygen + sign + publish the bundle)
The Python entrypoint the browser calls (calculate_sidereal_context(..., reference_date=...)) is the same one the CLI calls. The fixed reference_date
pins the "current" dasha, which is what makes a chart reproducible byte-for-byte
across CPython and Pyodide.
See frontend/README.md for the monorepo layout and the
full set of dev/build/test commands.
- Draw degree-accurate North- and South-Indian charts, D1–D60 divisional charts, Vimshottari periods, transits, Shadbala, Ashtakavarga, and life-domain timing — architecture.
- Keep multiple local profiles and read the relationship between two finished charts — capability table.
- Rectify an uncertain birth time from dated life events. The accepted time becomes the chart authority; rejected candidates never feed transit houses or the report — spec.
- Download a PDF report. The report is available without AI: export the same chart twice and the two files are byte-for-byte identical. Long life histories become dated, categorized table rows with bounded text and controlled page breaks — spec.
- Use it in English, Spanish, or Portuguese, bundled for offline use.
- Keep everything in one portable, on-device SQLite database: canonical profiles, charts, life events, chat, interpretations, and language. Settings can export the real database file and restore it on another device; provider API keys and rebuildable caches are deliberately excluded — spec.
- Optionally turn on AI interpretation and chat. AI is off by default. If you enable it, requests go directly from your browser to the endpoint you configure. Asking AI to organize free-form life events sends that narrative as written only after the in-product disclosure — network table.
| Option | Better choice when | Where AlmaMesh differs |
|---|---|---|
| Paid astrology sites and apps | You want a human-written or curated reading, or an astrologer to talk to | Free, no account or email, and the birth data never reaches a server |
| Free online chart calculators | You only need a quick one-off chart and don't mind the site seeing your birth data | The calculation runs in your tab; charts are saved locally and work offline |
| Desktop astrology software (for example, Jagannatha Hora on Windows) | You are a practitioner who needs far more techniques and settings than AlmaMesh offers | Runs in any browser on any OS; the engine is open source and checked against an independent astronomy reference |
| Do nothing / ask a family astrologer | You trust a person more than software | AlmaMesh shows its math and states what it cannot prove |
The chart engine is zero-egress: chart computation stays on your device, and it is deterministic in the sense above — same four recorded inputs, same bytes. The complete browser network inventory is:
| Trigger | Destination | Data sent | Explicitly not sent |
|---|---|---|---|
| First load, signed engine sync, PWA update | almamesh.com |
Asset URLs, normal HTTP request metadata | Names, birth data, charts, life events |
| Birthplace search while online | Open-Meteo geocoding | City text you type plus ordinary HTTPS/request metadata visible to the provider | Name, birth date/time, chart; an offline city-list fallback is bundled |
| Optional interpretation or chat | Your configured OpenAI-compatible endpoint | PII-redacted chart facts and, for chat, the question you type | Profile name and birth date |
| Optional life-event organization | Your configured OpenAI-compatible endpoint | The narrative you submit, as written, after the disclosure | Birth details and chart data |
| OpenRouter model list or credit check in Settings | OpenRouter | API request plus the configured key; the credit response concerns that provider account | Chart, birth data, chat, life events |
| Opening feedback when Turnstile is configured | Cloudflare Turnstile | Normal anti-bot request metadata and a challenge token | Chart and birth data |
| Sending feedback | Same-origin /api/feedback |
Page identifier, thumbs sentiment, optional message as written, Turnstile token | Chart and birth data unless the user puts them in the optional message |
local_only fail-closes if a configured AI endpoint is not local. Production
diagnostics emit allowlisted codes only—never raw prompts, narratives, provider
errors, city text, chart data, or profile names.
The browser enforces that table. This inventory is not a promise you have to
take on trust: the deployed Content-Security-Policy (see
frontend/apps/web/public/_headers) restricts connect-src to a closed
allowlist—this origin, openrouter.ai, geocoding-api.open-meteo.com, and
loopback (localhost / 127.0.0.1, any port, for a local model). Any other
destination is refused by the browser itself, so code that tried to send your
data somewhere else could not, even if it wanted to. A consequence worth knowing:
a custom AI endpoint on a non-loopback address—a LAN machine like
http://192.168.1.10:11434, or a third-party proxy—is blocked by that policy;
use a loopback endpoint or OpenRouter.
- Verified: every engine bundle the browser syncs — the signed
latestpointer is checked against the ed25519 release key (public.key, served from the same origin), and the manifest and every content-addressed chunk against their SHA-256 hashes. Production bundles carry a signed, increasing sequence number, so an older genuinely-signed bundle cannot be replayed over a newer one. - Refuses rather than warns: a bad signature, a hash mismatch, or a rollback
to an older sequence stops the engine from booting from that bundle; the app
offers in-app recovery ("Reset & reload") instead of running unverified code.
local_onlyrefuses a non-local AI endpoint. The engine refuses out-of-range coordinates instead of silently computing a wrong chart. The browser refuses network destinations outside the allowlist above. - Not protected: a compromised device, browser, or browser extension; a
compromised
almamesh.comorigin (the verification key is delivered from that same origin); what the AI provider you opt into, or Open-Meteo, does with what you send it. Data inside the browser is not encrypted by AlmaMesh — anyone with access to your browser profile can read it (exports can be password-protected). - Verify a release: build and sign it yourself (
make demosigns a local bundle with a throwaway key), run the accuracy and parity checks in What this proves, and compare deployed bundles against the livehttps://almamesh.com/public.key— see Publish a signed bundle for why a local key never matches the production key.
See SECURITY.md for reporting a vulnerability.
Planet positions are accurate to under an arcsecond. You can check this yourself. It runs fully
offline: the engine uses the vendored backend/de421.bsp, and the independent
astropy + JPL Horizons oracle values are committed as fixtures (no live download
or API call at test time).
# Engine longitudes vs an independent astropy oracle (with a committed JPL
# Horizons cross-check), agreeing to sub-arcsecond — natal + transits:
cd backend && uv run pytest tests/validation/test_ground_truth.py tests/test_transit_reference.py -qYes, and you can watch it being checked. verify-browser-parity.mjs boots the
app in headless Chromium from a served origin, drives the real Pyodide Web
Worker, and asserts every fixture in the committed CPython golden comes back
byte-for-byte identical. It runs on every PR and every push to main.
cd frontend && bun install && bash apps/web/scripts/setup-dev-assets.sh && cd apps/web
bunx playwright install chromium # one time
VITE_API_URL= VITE_EXIT_GATE_HOOKS=1 ./node_modules/.bin/vite build --outDir dist-verify
VITE_API_URL= ./node_modules/.bin/vite preview --outDir dist-verify --port 4199 --strictPort &
node scripts/verify-browser-parity.mjs http://localhost:4199 \
--reference-date=2025-01-01T00:00:00+00:00The reference date is an argument, not a constant baked into the gate. That
matters: the date pins the "current" dasha, and a gate that hardcodes the one
value it is pinning cannot tell you whether the pin ever reached the engine. So
the gate also runs a sensitivity control — the same chart at a different
reference date must produce different bytes. If it doesn't, the gate fails,
because a parity result that ignores its own inputs proves nothing. Pass a
different --reference-date yourself and watch the comparison go red.
The PDF report is reproducible: two exports of one chart are the same file,
asserted by SHA-256 in e2e/report-pdf.e2e.spec.ts.
What this does not prove.
- The browser parity gate covers the natal chart: all seven fixtures in
backend/tests/fixtures/chart_golden_de421.json, and it fails if the golden gains a fixture the gate does not compute. The transit, predictive, and mesh goldens are enforced CPython-side by the backend test suite; their Pyodide-in-a-browser parity is not browser-gated yet. Saying so here rather than letting the badge imply more than it checks. - Accurate planet positions say nothing about whether astrology predicts anything. Strength percentages are calibrated structural measures, not empirically validated life outcomes; rectification confidence is a best-versus-runner-up fit margin, never the probability that a birth time is correct.
- The terminal CLI output is reproducible only in the fields that do not depend on today's date (see Try it in 60 seconds).
TL;DR: one clone and the locked package installs build everything. The
Python side resolves edge-proc from
PyPI. The browser consumes the public
@edgeproc/browser Lego at an
exact Git commit recorded in frontend/packages/browser/package.json and
frontend/bun.lock; no copied sync/storage implementation remains here. See
docs/edgeproc-browser.md for provenance and the
consumer boundary.
No sibling checkout, private access, or token is required: git clone, then
uv sync + bun install, then run. CI uses the same frozen locks.
Hosted: nothing to install — use almamesh.com, and install it from the browser as an app if you want it offline.
From source: requires Bun (version pinned in
frontend/.bun-version), uv, and Python 3.13.
Everything else resolves from public sources through committed locks and exact pins.
git clone https://github.com/hseshadr/almamesh.git && cd almamesh
# One command, from the repo root. Installs deps, builds the dev assets, then
# builds and opens the app at http://localhost:4173.
uv run poe demo # same as `make demo`The first run fetches the Pyodide dist and the DE421 ephemeris once (network required); after that the app is fully offline. Use
uv run poe demo-freshto force-rebuild the signed dev bundle.
What poe demo runs under the hood (the manual steps)
# 1. Install workspace deps
cd frontend
bun install
# 2. One-time: build the dev assets the in-browser engine needs.
# This fetches a self-hosted Pyodide dist and signs a dev edge-proc bundle
# (DE421 + wheels + meta) into apps/web/public/ — all gitignored.
# The script lives at frontend/apps/web/scripts/setup-dev-assets.sh.
cd apps/web
./scripts/setup-dev-assets.sh
# 3. Build and preview. IMPORTANT: the engine's module Workers only resolve in a
# production build, NOT `vite dev` — so build first, then preview.
bun run build
bun run preview # prints a local URL, e.g. http://localhost:4173Open the previewed URL, enter a birth date/time/place (birthplace search tries Open-Meteo first and falls back to the bundled city list), and generate a chart. After the location is resolved, chart calculation and rendering stay on-device; the app keeps working with the network disabled.
Dev-server caveat:
bun run dev(vite dev) is fine for editing UI, but the dev server's ESM module Workers fail to resolve thepyodideimport in worker scope, so the engine only runs in a real build (vite build+vite preview). The browser parity gate above drives exactly that build.
In the browser: onboard with a name, birth date/time, and city; the dashboard then shows the chart, periods, and timing. Profiles, people you add to your mesh, AI settings, language, and backup/restore live in Settings.
Terminal chart (almamesh-chart): the same engine as an offline CLI — no
browser, no server.
cd backend
uv sync --extra dev
uv run almamesh-chart "1990-01-15T12:00:00+00:00" 40.7128 -74.0060It prints the full sidereal chart as JSON — ascendant, the nine grahas with
sign/nakshatra/pada, whole-sign houses, and the dasha hierarchy — with no
network and no account. (examples/run_chart.sh wraps the same call.)
The engine's data and wheels are delivered to browsers as a signed, content-addressed bundle. A device verifies its ed25519 signature against the same-origin release key and fails closed on any mismatch. The service worker keeps a release-matched offline copy, while online loads revalidate the key so a key and its newly signed bundle can rotate together. Compute always stays local; the network is delivery-only.
cd backend
uv run almamesh-bundle keygen ./keys # raw ed25519 keypair (0o600 private key); prints its key_id
uv run almamesh-bundle bundle ./origin ./keys/private.key --version v1./origin is a static directory any web server or CDN can serve; public.key is
the release verification key delivered from that same origin. (setup-dev-assets.sh
runs this for you to produce the local dev bundle.)
One identity note for cold readers: the signing key is per-environment and never
committed. A local build uses a throwaway dev key that setup-dev-assets.sh
generates into frontend/apps/web/public/public.key (git-ignored), while the
production deploy injects the separate prod key from CI secrets — so the live
https://almamesh.com/public.key will not match your local copy, by design.
Verify live bundles against the live /public.key.
Nothing needs configuring to draw a chart. The knobs that exist:
- In the app (Settings): AI provider (off by default; OpenRouter preset or any
OpenAI-compatible loopback endpoint), model, and
local_onlymode; language; profiles and people; backup and restore. Your AI key is stored only in this browser and is excluded from backups. - Build-time (
VITE_env vars for the web app):VITE_BUNDLE_BASE_URL(overrides where the signed bundle is synced from; default is this origin),VITE_TURNSTILE_SITE_KEY(enables the feedback anti-bot check), and theVITE_LLM_*defaults for the optional AI provider.VITE_EXIT_GATE_HOOKS=1is for test builds only. - Dev assets:
PYODIDE_VERSION,PYODIDE_DIST(use a local Pyodide dist instead of downloading), andDEV_BUNDLE_SEQUENCEforfrontend/apps/web/scripts/setup-dev-assets.sh. - Engine (
backend, pydantic-settings, reads.env):EPHEMERIS_FILE(defaultde421.bsp, the ephemeris the publisher ships).
Signing keys are never committed: private.key files are git-ignored, and the
production key comes from CI secrets. Don't bake a real VITE_LLM_API_KEY into a
build you publish — build-time values end up in the shipped JavaScript.
Shipped (in v0.4.0 or listed under Unreleased in the CHANGELOG):
| Capability | What | State |
|---|---|---|
| Engine | Deterministic sidereal chart + dasha + yogas (Python); Lahiri default, True-Chitra + True-node selectable | ✅ shipped, tested |
| Engine validation | External golden-reference check: astropy (independent code path) + committed JPL Horizons cross-check, agreeing to sub-arcsecond; license-clean (no Swiss Ephemeris) | ✅ shipped, tested |
| Bundle publisher | Signed, content-addressed bundle publish/sync | ✅ shipped, tested |
| Offline CLI | almamesh-chart, almamesh-bundle |
✅ shipped, tested |
| In-browser engine | The Python wheel in Pyodide/WASM, off the UI thread | ✅ shipped (byte-parity gated in CI, in a real browser) |
| N/S Indian charts | Degree-accurate SVG kundli off a pure geometry adapter | ✅ shipped |
| 3D force-field | three.js hero, planets at real ecliptic longitude | ✅ shipped |
| D9 Navamsa | Engine computes the Navamsa; renders in both kundli styles + the print report | ✅ shipped |
| Divisional charts (D1–D60) | Full Shodasavarga set; D9 also rendered as a kundli, the rest as tables | ✅ shipped |
| Predictive layer ("Sky & Timing") | Transits/Gochara + Sade Sati, dasha depth (antar/pratyantar), Ashtakavarga + Shadbala, per-life-domain forecasts; /predictive route (incl. a Periods explorer + Road Ahead) + report sections VIII–XI |
✅ shipped |
| The mesh (relational astrology) | Per-pair relationship read of two whole charts: Ashtakoota Guna Milan + Mangal screening (cited classical tables, partner edges only), chart overlay, daśā synchrony, significators; role-anonymized AI narration, read-only by construction; /mesh constellation + /mesh/:memberId edge view |
✅ shipped |
| Members | People you add to your mesh, with typed relationships (spouse/partner/family/friend/…), each owning a full chart; persisted with a versioned migration; managed in Settings → People | ✅ shipped |
| AI interpretation + chat | Off by default (pure calculation); opt-in BYO OpenAI-compatible endpoint (one-click OpenRouter preset or a local Ollama). Natal reading and current timeline are separate, explicit actions with independent progress and storage; mount/reload/day rollover spend no tokens. Prompts are PII-redacted and life-event prose is disclosed separately; fail-closed | ✅ shipped |
| PDF export | Report available after a chart exists (cover + D1/D9 + daśā + deterministic predictive sections VIII–XI + Birth Time Authority §XII); stable natal AI prose is optional. Date-sensitive AI timeline prose stays on the dashboard, where its generated/as-of date remains visible. Byte-reproducible: two exports of one chart are the same file, asserted by SHA-256 in e2e/report-pdf.e2e.spec.ts |
✅ shipped |
| Birth-time rectification | Per-profile rectified time + confidence in Settings; recomputes the chart | ✅ shipped |
| Named profiles | Multiple password-less people per device, each owning its charts; rename + delete (chart cascade) | ✅ shipped |
| Birthplace search | Online-primary Open-Meteo lookup with a bundled offline fallback; the city query and ordinary network metadata leave the device, never chart/profile data | ✅ shipped |
| Internationalization | English / Spanish / Portuguese; react-i18next, offline bundled catalogs (zero-egress), persisted language + <html lang> sync, AI answers in-language; en authoritative, es/pt machine-translated |
✅ shipped |
| PWA delivery | Service worker + offline reboot + provenance footer | ✅ shipped |
The old SaaS backend (FastAPI, Postgres, Redis, Supabase auth) has been removed. AlmaMesh has no account or chart-data API; only the optional same-origin feedback function stores the disclosed anonymous feedback record.
Planned (not shipped):
- Browser (Pyodide) parity gates for the transit, predictive, and mesh outputs; today only the natal chart is browser-gated.
- A reference-date option for the
almamesh-chartCLI. - Exact dual-lagna production wiring and PDF stability markers (rigor spec, Stage 4 is partial).
- Custom AI endpoints on non-loopback addresses are blocked by the browser policy; there is no supported way to use one today.
Versioning: v0.4.0 is the application release tag. Backend and frontend workspace package versions describe independently versioned implementation layers and need not equal the app tag.
- GitHub Issues — Best for: bugs and concrete feature requests.
- In-app feedback — Best for: quick thumbs up/down on a screen (see the network table for what it sends).
- Email (private) — Best for: security reports; see SECURITY.md.
make gateThat runs backend uv run poe gate (ruff, mypy, xenon, pytest with coverage)
and frontend bun run gate (typecheck, lint, unit tests, build) — the same two
commands CI runs. The required native Dagger checks also generate the signed
browser assets and drive real onboarding, parity, offline, privacy/reset,
Chromium/WebKit, and report-PDF journeys; run them locally with dagger check.
See CONTRIBUTING.md.
MIT — see LICENSE.