Roadmap¶
What's queued up, roughly in priority order. Things move between sections as priorities shift, so treat this as a snapshot rather than a contract.
How to read this page
Each section groups related work. Within a section, the things at the top are usually the next ones to do.
Some items have a π link to a GitHub issue β those are scoped tightly and ready for someone to pick up. The rest live here only β half-baked ideas, things we're still thinking about, items waiting on something else to land first.
Where else to look
- What's already live lives in
status.mdat the repo root: shipped features, deferred work, infrastructure things like the Postgres migration and storage move. - What we shipped when lives in
maud/CHANGELOG.mdβ dated entries per merged PR. - The original Phase 1 plan lives in
phase_1_slices.mdif you want the strategic backstory.
Quick wins¶
Small things that won't take long, queued for when we want a low-effort win.
- Make the DVLA lookup forgiving of weird input. If someone pastes a registration plate from an email and it includes a smart-quote or other non-standard character, the lookup crashes. Should reject it cleanly with a friendly message instead. About half an hour of work. Defer until it happens to a real user.
- Better document search. Today, searching for a document does a plain word-match across title, provider, and document type. Fine for under a few hundred documents per household; gets slow and dumb above that. Upgrading to SQLite's full-text-search engine adds relevance ranking, stemming ("plumbing" matches "plumber"), and phrase matching. About half a day. Worth doing before the first household with 500+ documents, or as part of the eventual move to Postgres.
Add more document types to the vocabulary.β shipped as part of the taxonomy work (#96): the catalog now spans 104 doctypes across all nine topics; adding one is a singletaxonomy.yamlentry + agenerate_doctype_fingerprintsrun.
Making Triage smarter¶
These items together form the next wave of work on Triage (the workflow that finds documents in your inbox and asks you to keep or dismiss them). They share a foundation: a single file that defines every document type Maud knows about, with three columns per type β examples for the AI, fingerprint patterns for the heuristic, and tag suggestions. Add a new document type once, and three different parts of the system pick it up.
Recommended order (each one is shippable on its own, but the order keeps the work manageable):
Detect duplicates against documents already filedβ shippedBuild the taxonomy fileβ shipped (#96)Stop re-asking about the same junk you've already dismissedβ shipped (#93)Refactor the heuristic + AI gate + tag suggestions using the taxonomyβ shipped (#96 sub-slices 2cβ2e)- Polish the first-scan experience
The items¶
π #96 β One file for every document type Maud knows about.β shipped. All four sub-slices landed: the fingerprint-drafting command, the per-doctype heuristic, the 9-topic classifier gate (Triage is no longer property-only), and tag priors. Original scope for reference: Today, definitions of document types are spread across three places: examples the AI sees, keyword lists the heuristic uses, and tag suggestion logic. Consolidating into one file means adding a doctype is one edit, not three. The scaffolding for this file already exists; remaining work is to fill it in and wire it up properly. Four sub-pieces (each shippable on its own):- Auto-generate fingerprints β write a command that uses Claude itself to draft per-doctype keyword + sender patterns. You review and accept.
- Refactor the heuristic β make scan-time scoring use the new per-doctype fingerprints instead of a generic keyword list.
- Wider AI gate β today the classifier asks "is this property-relevant?" which is why Triage only ever surfaces property docs. Replace with "which of the 9 topics does this belong to, or none?" β opens up medical, finance, family IDs, etc.
-
Better tag suggestions β have the AI prefer tags you've already used (over inventing free-form ones) and skip tag suggestions that duplicate things you can see anyway (property name, person name, year).
-
π #93 β Stop bothering me with the same junk: learned dismissal suppression.β shipped. Two dismissals of the same fingerprint suppress it pre-classifier; rules are revocable at/triage/suppression/. Original scope: When you dismiss something (a marketing newsletter, a flight booking confirmation), Maud should remember and skip it next scan rather than ask Claude to look at it again. Without this, the ongoing-tier price model doesn't work β costs grow with email volume. With it, costs drop over time as the system learns each household's idiosyncratic junk. -
Treat the very first scan differently. Same logic, same AI, but the experience should reflect that the first scan is project-like ("here are 50 documents we found, let's sort them all in one sweep") rather than notification-like ("you have 3 new findings"). Bulk-accept and bulk-dismiss should be more prominent. Every dismissal should aggressively seed the learned-suppression rules above β the first scan is where the system learns most about what you don't care about.
-
π #95 β Make the AI smarter using what you've already registered (no parallel context file).β shipped. Heuristic boosts and the classifier's household-context block both derive from registered houses/vehicles/people at scan time. Original scope: When you register a house, vehicle, or person, the system should automatically use those names, addresses, and registration plates to recognise emails about them β without you ever maintaining a separate "here's what I own" list. Pairs naturally with the first-scan item above: registering more things β the next scan gets smarter. -
π #97 β Size limits on file uploads + verify Postmark is really Postmark.β shipped. 25 MB per-document cap on every create path; HMAC signature verification on the Postmark webhook (enable by settingPOSTMARK_WEBHOOK_SIGNING_KEY). Original scope: Two defence-in-depth bits from the security audit. Today there's no limit on how big an attached file can be β a malicious user could fill our disk. And the inbound-email webhook only checks a username/password, not Postmark's cryptographic signature. Both fixes are small. -
π #98 β Invite links that can be cancelled and only used once (Medium severity audit finding).β shipped. Server-side single-use revocable invites minted from/household/invites/; the pre-baked dashboard link is gone. Original scope: Today's invite links are like bearer tokens β anyone holding the URL can join your household for 14 days. They're pre-printed onto every dashboard, so they leak by screen-share. Fix: each invite is a one-time row in the database, revocable from a settings page, only generated when you click "Invite someone". -
Tighten cross-household isolation on Person and house/vehicle records. Today these tables use Django's default access pattern, which means a future PR that forgets to add a household filter would silently leak data across households. There's no live problem β every current query filters explicitly β but switching to the safer default is a tedious mechanical pass through ~20 production sites + ~170 tests. Not urgent; valuable defence-in-depth.
-
"Did you mean?" picker when accepting Gmail findings. When the AI says "this looks like a document about 16 High St", and you've already registered "16 High Street", show that as a "did you mean?" option instead of creating a duplicate property. Prevents the typo-driven dupes you currently have to merge manually after the fact. Adds an "aliases" field on each property/vehicle so the system remembers old names you've merged in.
Older Triage ideas we still like¶
Independent ideas from the original spike that the bigger Triage cluster above doesn't replace. Each is small-to-medium and shippable on its own.
- Show drafts more quietly than final versions. A signed contract is more important than the draft of the same contract sent five days earlier. Today they look the same in the journal. Drafts should sit in a muted "watching" state; when the final arrives, it auto-replaces the draft. Stops the system pestering you to review obsolete versions.
- Quotes and other "is it noise?" docs make sense if they belong to a project. A standalone plumbing quote is forgettable noise. The same quote as part of a "kitchen renovation 2026" project is valuable. The system should recognise project membership as a reason to keep something.
- Let a document belong to more than one topic. An email about a property matter from your work might be relevant under both Property and Family-IDs. Today docs have one primary topic. Should support secondary topics as a soft tag.
- One contract beats twelve bills. When you have a year of monthly electricity bills, what matters is the contract / tariff (which is the journal-grade record). Individual monthly bills should be rolled up quietly β perhaps as a single "energy spend, year by year" summary β rather than 12 separate Documents. Needs a product call on the right behaviour.
- Be careful with silent dismissal. Today, if the AI is confident something is a superseded draft, it hides it. But "this is a draft and I haven't found the final" is a different situation β should be surfaced with a question ("Did you sign this elsewhere?") rather than buried.
- Make dismissed items findable with reasoning visible. Anything the AI hid should be findable later, with its reasoning shown, plus a one-click "actually, keep this" rescue. Today they vanish silently, which is unsettling.
Help me notice things I haven't told the system about¶
- Propose properties and vehicles you haven't registered yet, based on what shows up in Gmail. When the scan finds documents mentioning a property at "Chatel, France" but you haven't registered that as a house, a friendly banner on the Triage page: "We saw documents that look like they're for a Chatel property β add it and we'll file these correctly?" One click adds the property and re-runs the AI on the affected findings, moving them out of the "unclear" bucket and into the right place. Works for any registered-thing kind (houses, vehicles, future kinds).
House setup¶
π #94 β Make the property wizard work for non-UK houses.β shipped. The wizard now opens with "Where is it?" (UK / France / Other); France gets its own checklist (taxe fonciΓ¨re, DPE, diagnostic immobilierβ¦) and type options, Other gets a generic checklist, and the classifier learns each house's country.
Editing people, houses, and vehicles¶
π #99 β Let people be renamed and deleted (same pattern as houses and vehicles).β shipped. People get the same kebab rename/delete as houses and vehicles; deleting a person re-files their documents under the household's "self" person, and the account holder / sign-in-capable members are protected. The PersonβUserβMembership reconciliation (#108) landed alongside: joining via invite now creates a linked Person.
People and accounts¶
The Person model is fully wired underneath (#99, #108) but nearly invisible in the UI: nothing links to the per-person pages, every document silently attaches to the account holder, and there's no account page at all. These three slices fix that. The organising principle: if documents attach to it, it's reached from the dashboard; if it's about your login or app behaviour, it's reached from the account chip.
π #112 β Surface People on the dashboard.β shipped. Original scope: A People section with the same progressive disclosure as empty topics: a quiet strip while it's just "You", graduating to white tiles (name + document count) once a second person exists or person-split documents arrive. Plus the missing "+ Add a person" affordance. Makes the #99 rename/delete menu reachable and gives invited partners (#108) a visible landing.π #113 β Person picker on confirm + triage review.β shipped. Original scope:Document.personexists but no UI shows or edits it β everything piles onto the account holder. Add a "Who is this about?" picker to the confirm form and triage review, pre-selected by matching the classifier'sperson_subjectguess against registered People (the same mechanism named items use). This is what makes #112's tiles fill up.-
π #114 β Account settings page.β shipped. Original scope: Login things (display name, email, password, connected Google) plus the hub for the orphan settings pages (#98 invites, #93 suppression). Account dropdown slims to Account settings + Sign out. Person-level facts (DOB, NI numberβ¦) stay out β they belong on a futurePersonProfileper #108. -
π #116 β PersonProfile: facts about a person on their page.β shipped. Original scope: DOB, NI number, passport number + expiry β the facts you hunt for at 11pm, on the person's page. Lives on Person (not the account page) per the #108 boundary, so managed people without logins get them too. Mirrors the PropertyProfile/VehicleProfile sidecar pattern. π #117 β One-click "Add Savannah" from the AI's person hint.β shipped. Original scope: When the review screen says a document is about someone you haven't registered, the hint should be actionable in place: one click creates the person, pre-selected in the picker. The People sibling of the "propose properties you haven't registered" idea.
Sign-in and account security¶
Maud holds families' identity, financial, health, and property documents β the data where account takeover hurts most β so the product plan commits to MFA from launch, with passkeys as the direction of travel. Neither is shipped yet. The valuable project isn't passkeys themselves; it's consolidating auth onto a maintained library (django-allauth) and retiring the hand-rolled Google-OAuth backend. Once that foundation lands, MFA and passkeys are config-and-template changes rather than builds. So this is a three-step arc, each blocked by the previous β do them in order.
- π #142 β Auth foundation: migrate to django-allauth. Step 1, and the only one that must happen before anything else. Consolidates password login, Google sign-in and signup onto allauth, retiring the custom
apps/auth_googlebackend and data-migratingGoogleIdentityβ allauth'sSocialAccount. Most of the effort and nearly all the risk lives here (thesubβuidmapping, keeping django-axes first in the backend order, and moving the household-bootstrap onto allauth'suser_signed_upsignal so both signup paths still create a household). Roughly 1.5β2 weeks β migration risk, not passkey complexity. Explicitly reverses the earlier "custom OAuth backend over allauth" decision, which was right for one provider and no MFA but stops being right the moment we want MFA + passkeys. - π #143 β TOTP MFA + recovery codes via allauth.mfa. Step 2, blocked by #142. Delivers the "MFA from launch" commitment: opt-in authenticator-app (TOTP) enrolment on the account page, plus recovery codes generated once with a regenerate path. Recovery codes land here, before passkeys, on purpose β they're the fallback that stops passkey-only login from ever becoming a lockout with no way back in. A few days on top of Step 1.
- π #144 β Passkeys (WebAuthn) via allauth.mfa. Step 3, blocked by #142 and #143. Mostly a switch-on once the foundation and recovery codes exist β passkey registration/management on the account page, second-factor first with passwordless as an explicit fast-follow. Two things to lock before anyone enrols: the recovery policy (require at least one of {recovery codes, password, second passkey}, and block removing your last credential) and the Relying-Party domain β passkeys bind permanently to it, and since we've settled on
heymaud.comthe RP ID should be that canonical domain from day one so no credential ever gets stranded by a domain change.
Dashboard and navigation polish¶
- Dashboard alert overlays (per-area "needs attention"). The redesigned area tiles reserve a coral alert state β border + disc tint go coral, the completion % turns coral, and a small flag row appears (e.g. β "MOT due in 3 weeks", "3 documents unfiled", "missing a critical document"). The tile markup already supports this; what's missing is the rule for when an area is in alert. Candidate signals: vehicle MOT expired / expiring within N weeks and road-tax status (both already stored from the DVLA/DVSA lookup); documents sitting unfiled against an entity; a critical checklist item still missing. Deliberately deferred so the alert stays scarce and meaningful rather than firing on weak signals β needs a product decision on thresholds before wiring it up.
- "Get documents in" panel should graduate. Right now that big accent panel on the dashboard dominates the page regardless of how set up you are. It's onboarding scaffolding, not steady-state UI. Should collapse to a small reminder once you've got Gmail connected, an email-in address configured, or at least three documents filed. The persistent + Add button in the nav (desktop + mobile, both shipped) is the home for ingestion from then on. (Superseded in part by the dashboard redesign β the empty-state peppermint hero now carries the welcome + Connect-email CTA, and steady-state relies on + Add. This bullet stays for the graduation nuance.)
- Sandbox accounts for testing. No code change β just a note that we create
sandbox-<name>users and use thewipe_householdcommand to reset between test runs. Captured here so the practice isn't lost. - Deeper search and filter polish. Beyond the tag filtering that already shipped: date ranges, person filters, smarter multi-tag combinations. Speculative β not asked for yet, captured for when someone asks.
Bigger / structural¶
Items that are non-trivial. Most need a clear product call or a chunk of focused engineering time.
- Move the rest of the heavy work onto the durable task queue. PR #82 set up the queue, PR #89 moved the inbound-email webhook onto it, and the ongoing-Gmail-scan PR retired the daemon thread for Gmail scans too. Remaining: parallelise classifier calls within a single scan (~5Γ concurrency would turn a 15-minute scan into ~3 minutes) and split out a separate worker machine when Postgres lands.
- Data-protection impact assessment outline. Needed before any cross-household aggregate learning (planned for Phase 3) can switch on. Useful to sketch early so Phase 1 consent architecture is built with it in mind.
- Visual data model diagram. Current data model is described in text only. Drawing it as a Mermaid diagram makes it much easier to review changes.
- Production prompt library. Version-controlled
.mdfiles for all the prompts we send to Claude (classification prompt, wizard query templates, etc.). Mostly subsumed by the taxonomy file work above, but the actual prompt strings still need a coherent home in the repo. - Switch the classifier from Claude Sonnet to Claude Haiku. Validated against ground-truth labels: Haiku matches Sonnet on precision and recall at ~3Γ lower cost per document scanned. Production still uses Sonnet. Switching is mostly a few config changes β the prompt and taxonomy work has already mostly landed. Also: bake in a retry mechanism for transient API errors, so a connection blip ⅔ of the way through a big first scan doesn't cost us 30% of the run.
- Wireframes / UI sketches for the key flows. Onboarding, the seeded dashboard, document confirmation, House Sale Wizard. A Figma or Excalidraw job β text descriptions of UI flows have limits.
Getting ready to charge for it¶
Product strategy + go-live items. Most need positioning, legal, or finance decisions before any code can start.
- Pricing tiers shaped around what each tier costs us. Today: the per-user variable cost is mostly Claude API calls. Rough estimates: ~$0.50β$1 for a first-time Gmail scan, ~$0.05β$0.15 per ongoing weekly scan once the "remembered junk" rules from #93 have kicked in. The proposed shape: Free = upload + email-in only, no Gmail Triage. One-off (Β£X paid once) = first-time Triage + 30 days of follow-up scans, then auto-downgrade. Ongoing (Β£Y/year) = continuous weekly scans + remembered junk. The "Ongoing" price only works because the remembered-junk rules compress classifier costs over time β so we ship #93 first, then this.
- Pick a payment processor. Stripe is the default; GoCardless for direct-debit subscriptions. Deferred until paid tiers are real.
- Legal entity and trading arrangement. Who owns the product? Who holds the user data contractually? Drives privacy policy, T&Cs, payment flow, GDPR posture, ICO registration.
- A separate marketing/sales website. Currently the only "site" outside the app is the developer docs. Needs landing page, value prop, tier pricing, trust signals on security and privacy, signup CTA. Subdomain choice (
heymaud.comvslifefile.appvs a separate marketing domain) is part of this. - Does the property wizard work on the free tier? Open product question β current plan says "to be decided." Decide before launching tiers.
- Go-live readiness checklist. Tracks all the prerequisites scattered across documents and memory: Google's CASA security audit (gates Gmail past 100 users), commercial hosting (separate from the personal Fly account), payment integration, legal/privacy/T&Cs/data-protection agreements, email deliverability for the inbound domain, branch protection on main, on-call/observability, backup + disaster recovery. Not work itself β a tracking page that other slices feed into.
Things we should investigate before deciding¶
- How does Vercel + Neon compare to Fly.io on cost at projected Phase 1 load? Useful before we lock in hosting longer-term.
- Model the Claude API cost per active user per month at realistic scan + classification volumes. Spike data gives us per-document cost; multiply by expected volumes. Feeds the pricing-tiers item above.
- Streaming-findings UX experiment. Today: scan runs in the background, findings appear at the end. Alternative: stream findings as the AI finishes each one. Affects both perceived speed and the back-end architecture. Worth prototyping.
- Do we need OCR fallback? Claude vision handles most things, but the spike sample will tell us whether there's a long tail of documents where we'd need Google Document AI or AWS Textract.
Open product questions¶
- Should users be able to add their own document types and categories? And under what guardrails β prevent fragmentation while letting a household add "school uniform receipts" if they want? Becomes more concrete once the taxonomy file lands.
Editing this page¶
This page lives at maud/docs-site/docs/roadmap.md in the repo. Edit it like any other docs page β PR + deploy lands it on the docs site (docs.heymaud.com). The Cloudflare Access gate keeps it behind your email allowlist.
When an item here matures into something you'd ask a contributor to ship, open a GitHub issue using the patterns from #93β#99 (plain-English: what / why / example / what we'd build, with technical notes at the bottom), then link the issue back from the bullet here.