Piles, not paths
Notes and formulas are collected in isolation. There is no ordering, no prerequisite, and no signal for what to study next.
The response
Tracks, prerequisites, curricula, and typed relations
Case study · Finance Archive
Three student failures led to one content model, one finance engine, and one read boundary. The result is a platform where every article follows the same method — and where a configuration bug once masqueraded as a broken database.
This is a working record, compiled from the repository and its live data. Counts are read at request time from the same boundary the application uses, and every benchmark below is asserted by the test suite. No adoption figures or projected outcomes have been invented.
01 · The problem
Most finance resources solve one of these and leave the other two untouched. The archive was designed around the connection between them.
Notes and formulas are collected in isolation. There is no ordering, no prerequisite, and no signal for what to study next.
The response
Tracks, prerequisites, curricula, and typed relations
A topic can be read without ever testing whether the idea survived contact with a question. Recognition gets mistaken for understanding.
The response
Practice items, self-checks, mastery bands, and exams
Even an understood formula is rarely applied to a decision, so judgement — the actual goal — never forms.
The response
A calculator, a worked case, and reflection in every article
The constraint that shaped everything else was coherence: a new topic should inherit the method automatically, and a number shown in two places should never be produced twice.
02 · One method
The method is not a page template. It is the shape of a note record, which is why every article supports the same three acts without bespoke wiring.
The idea, the formula, the assumptions, and the points that matter. Written to be read once and revisited later.
sections[]A live tool wired to the same record. Change an assumption and watch the result move, rather than trusting a screenshot.
calculate.kindA decision case and self-check questions. The reader has to choose, not merely recognise, and receives an explanation either way.
apply.caseStudy · apply.questions[]Why this matters for maintenance
An article becomes interactive by naming a calculator. The renderer reads the kind, loads the matching engine surface, and supplies the same record the reader is already seeing. There is no per-article screen to build, no parallel copy of the formula, and no place for the prose and the tool to drift apart.
Authoring path
The method survives because it is enforced in data, not remembered by whoever builds the next page.
03 · Architecture
Dependencies point one way. The interface asks for content, the application boundary answers, and the finance engine owns every calculation.
Dependency direction
UI → Feature → Application → Domain → DataUI
src/app · src/components
Server-rendered pages and focused client components. Renders records; never becomes the source of a number.
Feature
src/features
Practice, collections, collaboration, Lab workspaces. Owns interaction state and the shape of a workflow.
Application
src/lib
The read boundary. Pages ask for notes, formulas, and practice items; they never reach into data or MongoDB directly.
Domain
src/domain
A React-free finance engine with its own tests: statements, valuation, risk, and formula experiments.
Data
src/data · src/server
Typed seed records, the MongoDB repository, and the seed plan that keeps both representations in step.
Arithmetic lives in the finance engine. Charts, cards, and pages display its output, so a result cannot drift between two screens.
Every page reads through the same async functions. Switching from seed data to MongoDB changes the answer source, not the component.
Generated guidance is bounded to plain-language explanation. Figures always come from the deterministic engine.
Reads fall back to seed data; writes require a database and say so. A missing connection degrades the feature instead of faking success.
The read boundary in practice
A page calls getNotes() or getFormulas(). With a connection string the repository answers from MongoDB; without one it answers from the typed seed records. The component tree does not know which happened, so the demo, the test suite, and the deployed app run the same UI code.
04 · Content system
These are the current records at request time, not a launch-day claim. The same numbers feed the archive, the knowledge map, practice, and the seed plan.
Content can be authored in the browser studio or as a typed seed file. Both paths produce the same validated document, so seeding MongoDB does not create a second content model.
Concepts, typed relations, domains, and curricula give the archive memory. Mastery and recommendations read from that graph rather than inferring structure from page titles.
A second reading mode
The Ledger applies the same pipeline to finance essays — capital raising, hedging, the CFO role, the accounting profession — because an argument is read differently from a technique. It has its own record type, but shares the read boundary, repository, and seed plan.
05 · Design
One semantic token set controls the navy and gold palette, type, spacing, radii, and motion. Every page composes the same primitives instead of introducing a new visual vocabulary.
The header collapses to a menu, tables wrap instead of overflowing, charts scroll inside their own frame, and every interactive target is at least 44px.
Six top-level destinations keep the header readable. Dropdowns hold the deeper routes, while long reads gain a sticky contents rail and a two-column rhythm.
Reveals, lifts, and the floating ring motif are short and opt-in. All of it is disabled under prefers-reduced-motion, and content is visible before hydration.
Presentation rule
Interior pages should feel like they belong to the same building — not like a plain white room beside a designed entrance.
The shared gradient page hero, concentric rings, masked grid, and gold rule now open the Archive, Lab, Practice, Knowledge, and this case study. The design does not depend on a screenshot to explain itself.
06 · Debugging
The most instructive failure in the build looked like configuration. The app had the right connection string, the right database, and a healthy-looking read path — and still refused every write.
/api/health reported the database as configured, but the driver as missing. Seed and read routes still worked, so the site looked healthy while every write returned 503.
The read boundary did exactly what it promised: when the database was unavailable, it served typed seed data. That resilience hid the real fault. A healthy-looking page was not evidence of a healthy connection.
The driver specifier had been assembled at runtime to keep it out of the bundle. Turbopack could not analyse it, so it substituted a stub that threw “Cannot find module as expression is too dynamic.” The surrounding catch then folded that build-time problem into the same quiet fallback used for a missing database.
Before · unanalysable specifier
const driver = await import(
["mongo", "db"].join("")
);After · literal, externalised specifier
const driver = (await import("mongodb")) as
MongoDriverShape;What changed
mongodb is declared as a server external package.driverInstalled separately from connected, so the two failures cannot collapse into one.A graceful fallback is valuable — but only if it also tells you when it is being used.
07 · Results
The build is checked at three levels: static analysis, engine behaviour, and a live Atlas connection. These are the recorded results.
0
TypeScript errors
strict compile
0
Lint warnings
full codebase
82
Automated tests
across 12 files
303
Seed documents
across 13 collections
Hand-checked engine benchmarks
Discounted cash flows at the stated yield
Dividend growth model with stable assumptions
Risk-free rate plus beta times the premium
Weighted cost of debt and equity
Rate at which project NPV reaches zero
| Metric | Result | What it proves |
|---|---|---|
| Bond price | ৳871.65 | Discounted cash flows at the stated yield |
| Gordon value | ৳484.57 | Dividend growth model with stable assumptions |
| CAPM | 12.9% | Risk-free rate plus beta times the premium |
| WACC | 10.89% | Weighted cost of debt and equity |
| Project IRR | 24.92% | Rate at which project NPV reaches zero |
Benchmarks are recorded in VALIDATION.md and asserted in tests/formulas.test.mjs. The live connection reports configured: true, connected: true, and source: "database".
08 · Tradeoffs
A case study is less useful if it only lists strengths. These are the boundaries a reader should know before treating the platform as more than it is.
Progress, saved notes, and collections persist in the browser. That keeps the demo honest and frictionless, but it is not yet a multi-device identity.
Answers are checked in the client against the same typed questions. A production cohort would move results behind authenticated writes.
The archive teaches mechanics with sample figures. It is not a market feed, a valuation opinion, or financial advice.
Articles and Ledger essays deliberately use different records. Reading an argument is a different act from practising a technique, and forcing one shape would weaken both.
The demo-first decision is the largest tradeoff. It made the platform immediately usable and testable, but it also means a browser can look complete while persistence is local. The health endpoint and this page state the distinction plainly rather than hiding it behind an account wall.
09 · Lessons
These are engineering lessons that came from building the archive, not generic advice added afterwards.
A three-stage record makes every new article consistent without a per-article layout. The interface follows the content instead of memorising it.
A React-free engine is testable, reusable across the Archive and the Lab, and impossible to bypass with a convenience calculation in a component.
Seed fallback made the platform presentable and testable before a database existed. The same boundary then became the production read path.
Designing mobile and laptop deliberately beat chasing every breakpoint. The important layouts were proven where students actually read.
Written specs for the content model, finance engine, and data layer are what let a growing platform change without losing its shape.
The through-line
Every lesson points back to the same idea: make the right thing the easy thing. A consistent record shape, a single calculation owner, a single read boundary, and written documentation reduce the number of decisions the next change has to get right.
10 · What comes next
The front end and data layer are complete enough to demonstrate the whole product. The next phase is identity, persistence, and turning the knowledge graph into better discovery.
The current demo stores progress, saved notes, and collections in the browser. The API already exposes the write surfaces and refuses them cleanly without a database, so adding accounts is an extension of the architecture rather than a rewrite of the interface. The same is true of discussion persistence and concept-aware search.
About this build
Designs and builds the archive — the content model, the finance engine, and the interface. The goal was not to add more finance pages. It was to make the relationship between explanation, calculation, and judgement durable enough that new topics could be added without weakening the experience.
The full documentation set — architecture, content model, finance engine, design system, API, seeding, and validation — is indexed on the about page. The repository also carries docs/CASE-STUDY.md and VALIDATION.md.
Study the method
The archive is free to read, practise, and download. Start with the foundations, follow a track, or open the Lab and test an idea against a real set of statements.