Reports are static. Exports are one-time. Dashboards are hard-coded. We needed something better: Viewsβfirst-class primitives that ask questions of the ledger declaratively, repeatably, and safely.
The Insight
Yesterday we defined financial truth. Today we asked: How do you ask questions of that truth?
Traditional finance apps give you canned reports. "Spending by Category". "Income vs Expenses". "Monthly Summary". These are features, not primitives. They can't be customized, combined, or shared.
We realized: reports, dashboards, and exports are all just Views. Different ways of asking the same underlying question: "Show me transactions that match these filters, grouped this way, sorted this way."
Why Views Are Primitives
A View is not code. It's a declarative description of a financial perspective:
{
name: "Spending by Category",
type: "report",
filters: [
{ field: "amount", operator: "lt", value: 0 },
{ field: "type", operator: "neq", value: "transfer" }
],
grouping: { field: "categoryId" },
sorting: [{ field: "amount", direction: "desc" }],
columns: ["category", "amount"]
}This is pure data. No callbacks. No code. No side effects. It describes what to query, not how to execute.
The View Primitive
Every View contains these fields:
Core Fields
name β Human-readable identifiertype β ledger | report | dashboard | customfilters β Array of field/operator/value conditionsgrouping β How to aggregate (category, account, month, type)sorting β Order of resultscolumns β What to displayThe QueryEngine
Views don't execute themselves. The QueryEngine takes a View definition and returns results:
1. Reads ONLY from LedgerService (no direct repo access)
2. Has NO side effects (pure function)
3. Returns DETERMINISTIC results (same input = same output)
4. Can be called at any time without changing data
This separation is crucial. Views describe intent. The QueryEngine executes intent against financial truth. Neither stores derived results.
Filter Operators
We defined a complete set of filter operators:
eq // equals neq // not equals gt, gte // greater than (or equal) lt, lte // less than (or equal) in, nin // in array / not in array between // between two values (inclusive) contains // string contains isNull // is null isNotNull// is not null
These cover every use case we could imagine:
-
date between [start, end]β Date range queries -
amount lt 0β Expenses only -
accountId in [a, b, c]β Multiple accounts -
reconciledAt isNullβ Unreconciled transactions
Built-in Views
Every workspace gets seeded with canonical Views:
All Transactions
Complete ledger sorted by date
Spending by Category
Expenses grouped by category
Income vs Expenses
Compare total inflow vs outflow
Account Balances
Dashboard of all accounts
Unreconciled Transactions
Transactions that need verification
Monthly Summary
Totals by month
These are NOT special cases. They're just View definitions that happen to be pre-created. They use the same QueryEngine as custom views. You can create identical views yourself.
Why This Enables Future Features
Because Views are pure data, they enable:
AI Integration
An AI can read a View definition, understand what it does, and suggest modifications. "Add a date filter for last 30 days" becomes a simple JSON update.
Shareable Links
A View can be serialized to a URL. Share "Spending by Category for December" as a link, not a file export.
Cross-Workspace Queries
Same View definition can run against different workspaces. Compare personal and business finances with the same query.
Template Library
Save and share View templates. "Best Views for Freelancers" becomes a collection of JSON objects.
What We Didn't Build
Constraints are features:
- No charts β Tables only. Charts come later.
- No mutations β Views are READ-ONLY. They don't change data.
- No stored results β Views don't cache. They always reflect current truth.
- No rules engine β Views query; they don't transform.
What We Built Today
- View Primitive β Fully typed schema with filters, grouping, sorting, columns
- Filter System β 12 operators covering all comparison types
- QueryEngine β Pure, read-only executor against LedgerService
- Built-in Views β 6 canonical views seeded for every workspace
- Views UI β List, select, execute, render with table output
- Create/Edit Views β Full CRUD with filter builder UI
Views are primitives, not features. They replace reports, dashboards, and exports with a single, composable abstraction. They describe financial questions declaratively. They execute deterministically against the ledger. They can be saved, shared, and re-run.
This is how you build for AI and sharing: make your queries pure data, not code.
β The Accelerate Finance Team