Build log
What we've been building
A reverse-chronological log of meaningful steps in building Capuchino. Small fixes and routine polish are folded into nearby entries.
More honest results, and your pattern in words
A calibration run (150 words at fixed ranks, no adaptivity) showed that the test read a learner whose knowledge fades gradually about 1,600 words too high: the model only allowed fairly sharp drop-offs. It now allows slow fades too, and picks each word for what it tells us about your 80% point. Results are lower for many people, and ranges are wider and more honest, so the app now suggests stopping after about 25 answers. Your results also describe your pattern: solid up to one point, patchy to another, mostly new after that. The level now comes from the middle of your range again; your gap list still starts at the bottom. Earlier results stay as they were; take a new test to see yours under the new model.
Shaky no longer counts as half-known
A word that was clear only with the sentence now counts as not known in your estimate, because it isn’t one you know solidly. Simulations showed the old half credit read several hundred words high for people who use Shaky. Your gap list still marks those words as shaky.
More words on your gap list, a more careful level
Your gap list now starts at the bottom of your range instead of its middle, so the uncertain words show up rather than counting as known. The level comes from the bottom of your range too, and appears only once the range is narrow enough to mean something.
Private product analytics
We now count page visits and feature use with PostHog’s EU cloud, without cookies and without your name, email or account ID. The privacy notice says exactly what it sees.
A dashboard worth opening
/app shows your level, your 10,000 words at a glance, a word of the day, the next words to learn with one-click “I know it”, and your progress in each topic.
Analyze speaks in words
The slider now reads “Count the 5,000 most common words as known” instead of frequency bands. Words you rated yourself always keep your answer.
Thirty topic lists
Twelve more lists: shopping, the city, sports, music and film, books, free time, science, religion, geography, war, time and numbers.
How the assessment works, in detail
A new /methodology page: the word list, the model with its actual numbers, how each word is picked, when the test stops, the level guide and its limits.
Clearer profile and navigation
Your profile shows your range and level; the old “cliff” wording is gone everywhere. The logo always leads to the homepage; your dashboard is under Dashboard.
Admin tools (internal)
A private /admin section with a user list, for the two of us running Capuchino.
Topics open to everyone
/topics lists every topic with its word count, no account needed. Fifteen new lists went live, from animals, the home and travel to work, politics, law, health and the media.
Better Anki cards
Up to four meanings per word, related meanings, examples chosen for the topic, and the article on nouns (el/la). Re-importing updates your cards instead of duplicating them.
Export to Anki, your way
Pick any mix of topics, a frequency range and statuses, and download a file Anki imports directly.
An adaptive assessment
Rate 10 to 25 words, each shown alone first and then in a sentence if needed. Your range narrows with every answer, with a rough level. No account needed: keep your result by signing up afterwards.
New homepage and privacy notice
A rebuilt homepage with a sample result and what’s coming next, and a privacy notice covering the EU and California.
About pages reorganized
/about now leads to /about/specs and /about/log. The specs page opens with the services that see what you write.
Unused admin database client removed
A template had regenerated a server-side client that bypasses access rules. Nothing used it; it is gone, and a test keeps it out.
Moved to company accounts
Hosting, code, database, email and DNS moved to Umunhum Labs accounts. Inbound mail to hola@ bounced briefly and was restored.
Database functions locked down
Four internal functions were callable by any visitor after the move. Access is now limited to the database itself.
Database schema reconciled
The code repository now rebuilds the production database exactly, with every migration accounted for.
Clearer sign-in errors
Sign-up and sign-in no longer show raw database error text.
Pages now server-rendered
Moved from a browser-only app to TanStack Start, so public pages arrive complete for readers and search engines.
Admin email function closed to the public
A function that sends admin notifications could be called by anyone. It no longer can.
Profile dashboard: assessment recap + bucket breakdown
The /app/profile knowledge map turned into a real dashboard. Up top, a one-line recap of your latest assessment — the date, where your cliff sits in plain language (e.g., "the top ~6,000 words"), and the band number underneath for the precise read. Below that, three rows — Known, Shaky, Unknown — each with the total broken into confirmed (you rated it, or we showed it to you in the assessment) versus inferred (extrapolated from your cliff). Watching the inferred numbers shrink is the new long-arc progress signal.
Analyze on the marketing pages
The /app/analyze feature is live, but the public-facing pages were still describing it as roadmap. Fixed in two places: the /how-it-works bullet flipped from "on the roadmap" to live — with the honest caveat that URL fetching is still future, so the label drops "or URL". And the homepage gained a short teaser section between the topic preview and the founder note: eyebrow, problem framing, sample paragraph with above-the-cliff words highlighted, and a soft sign-up CTA. Public surfaces now match what the product actually does.
Knowledge map + confidence cues
Surfaced two long-overdue distinctions on the gap-list UI. (a) Each row now shows a small confidence icon — a filled check for confirmed statuses (assessed, overridden, mastered), an outline circle for inferred statuses (presumed from your cliff). (b) A new "Your knowledge map" section on /app/profile shows how many of the 10,000 lemmas you've personally confirmed vs. how many are still our best guess. Both surfaces auto-update as you take the assessment, override flags, or analyze new texts.
Text upload analysis (preview)
Shipped /app/analyze: paste a Spanish article (news, blog, lyrics — anything you'd actually want to read) and we'll show you which words sit above your cliff. Each text is saved to your private archive — open it again later to watch the gap shrink as your Spanish grows. Inline override controls let you correct our guesses on the fly; the "in gap" rule respects explicit "I know this" overrides so your judgment beats our heuristics. Honest-limitations note on every result: we're useful, not airtight.
Backfill: activity_events + exports tables
While building the text-upload feature, caught that two tables planned in the original user-side schema (migration 03) had silently been dropped during build: activity_events (telemetry) and exports (export audit log). Wrote them now — append-only, own-rows RLS — and wired the new analyze flow to use both from day one. Retrofit for the existing gap-list and topic-detail exports is on the backlog.
FAQ page
Added /faq — eight straight-talking questions covering audience scope (yes, English speakers learning Spanish; no, not the other way round for now), the 10,000 number's defensible oversimplifications, product mechanics (why self-rating, why feed Anki), and what's not in Capuchino yet. Plain scrollable list, no accordion. Linked from the top nav.
Tatoeba per-sentence attribution
Jeff Doozan (the upstream maintainer of our lexicon source) flagged that Tatoeba's terms of use require per-sentence author attribution. The data was already in our database but wasn't surfacing in the UI. Every example sentence in the assessment now shows its source — 'via Tatoeba — author1 & author2 (CC-BY 2.0 FR)' — with quiet hyperlinks to the platform and the license. Compliance closed.
Landing page
Replaced the placeholder hero on / with a proper landing page: problem-first headline ("Master the 10,000 words that stand between you and C1"), three-step product loop, topic teaser, and a build-in-public founder note with a Reid Hoffman quote. Tone leans founder-y — named what's thin (no audio yet, one frequency list, basic self-rating) rather than polishing in private. /about and CLAUDE.md updated to match the audience framing (past B1, working toward C1).
Topic-based sub-lists (preview)
Shipped the first topic-based sub-list — the human body. Two new tables (topics + lemma_topics with confidence and source columns), 528 lemmas tagged conversationally in a Claude Code session and ingested via the pipeline. /app/topics shows the eight-card catalog (one live, seven "coming soon"); /app/topics/human-body lists the lemmas with the same status filters and TSV/CSV export as the gap list. /how-it-works updated to point at the live preview.
Build log page
Added this /log page so progress on Capuchino is visible in one place. Reverse-chronological, lightly curated.
How it works: roadmap additions
Expanded the "What's not yet there" section on /how-it-works with two upcoming features: topic-based sub-lists, and analyzing a pasted text or URL to surface unknown top-10k words.
How it works page
Created /how-it-works with a plain-language walkthrough of the product loop — assessment, gaps, export. Translated to Spanish. Top nav reordered to About → How it works → Sign up → Sign in → language toggle.
Export to your SRS
Shipped clean export of the gap list as TSV or CSV — Anki-compatible by default; Quizlet, RemNote, and Mochi all take the same format. We don't reinvent SRS; we feed the one you already use. Filter at export time: only the unknown, shaky + unknown, or everything that isn't solid.
Gap list
Built the Gaps view: every word above your cliff plus anything you marked shaky or unknown, with manual overrides ("actually I know this", "actually I don't"). Filter by frequency band, status, or source; pick up where you left off across sessions.
Temperature-check assessment
Implemented the 50-item assessment with stratified sampling across the ten frequency bands. Self-rating in context (solid / shaky / unknown) on real example sentences. Result: a soft anchor showing where your knowledge drops off, not a verdict. Resumable mid-flight.
Lexicon import pipeline
Built the Python ingestion (in a separate repo) that loads doozan/spanish_data into Supabase: 10,000 lemmas across 10 frequency bands, 21,668 senses (Wiktionary glosses), 108,706 example sentences (Tatoeba, quality ≥ 5), and 577,731 form-to-lemma mappings. Imported data lives in read-only tables, separate from app-authored content — a clean license boundary for the ShareAlike obligation.
About page
Published the About page covering the project's mission, the libraries we lean on, and the data sources behind the lexicon — every upstream credited with its license.
Authenticated app shell
Added the signed-in app layout, profile page, and protected routes. Sign up, sign in, password reset, and email confirmation flows are all live.
Supabase backend
Wired up the backend: Postgres schema for the multi-list lexicon, Supabase Auth (with Resend as the SMTP provider for transactional email), and row-level security on every table from day one.
Internationalization (EN + ES)
Set up i18next with English and Spanish locales and a header language toggle. Interface is fully bilingual; word glosses remain in English for v1.
Project bootstrap
Spun up the project on React + Vite + Tailwind, wired the design system tokens, connected the capuchino.co domain, and shipped the landing page with the capuchin-monkey footer narrator and rotating advanced-vocabulary taglines.