Design rules
This is the source of truth for what the application should look and feel like, and — more usefully — for what it should refuse to become. It's separate from AGENTS.md (how code gets written), PLANNING.md (how future work gets scoped), and PROJECT_MANAGEMENT.md (how issues get labeled and sized).
It applies equally to human contributors and AI agents — multiple agents work this repo at once, sometimes in parallel; this is the shared convention both are expected to follow, not one agent's local habit.
We are replacing a spreadsheet, not a dashboard
This is the one rule everything else follows from. Every audience described in the README is doing this work in Excel today, or not doing it at all — that's the incumbent to beat first, because it's the one they're actually using this morning. The enterprise platforms are competition too, and the plan is to eventually out-build them on substance, not carve out a smaller niche where we never have to face them. But nobody switches off a free spreadsheet for something merely more ambitious — they switch when it's faster at the same job. So: density, scan speed, filter latency, and export fidelity are the product's aesthetics. If a change makes the screen more attractive and shows fewer rows, it's a regression, no matter how sophisticated the feature behind it is.
Nobody has ever abandoned a spreadsheet because something else was prettier. That's still true even as the ambition grows — the Bloomberg Terminal, the reference point named in README's business-model section, is legendary for being dense and unglamorous, not for looking analytical. This rule doesn't get relaxed as the feature set grows toward enterprise parity; it gets more load-bearing, because density under a much bigger feature set is a harder problem than density under a small one.
Rugged is not permission to be ugly
The craft in this application goes into legibility under density, which is harder than decoration and much less obvious when it's done well. Concretely:
- Numbers align. Award amounts use tabular numerals and right-align, so magnitude is readable down a column without reading a single figure.
- Color is semantic or absent. Color means something specific — created, updated, removed, error — and is used nowhere else. A palette that spends its distinctiveness on decoration has nothing left when it needs to signal.
- Whitespace is earned. Generous spacing is a real tool for grouping, and a real cost in rows-per-screen. Spend it where it separates different kinds of thing, not uniformly.
- The empty state teaches. A list with no records should say what to do next, not just report emptiness.
- Keyboard before mouse. Spreadsheets are keyboard-driven, and so is anything that intends to replace one.
Cards are almost always wrong here
You can scan fifty table rows. You can scan about six cards. Our primary interaction is scanning a lot of records to find the few that matter, which makes the table the default and cards the exception requiring justification.
The same logic applies to charts. Federal award data invites dashboards — spend-by-agency donuts, award-count sparklines — and almost none of it answers a question our users actually have. A chart earns its place by answering a question a filtered table can't, not by looking analytical.
Where cards are legitimately right: narrative surfaces that aren't work surfaces. The roadmap page uses them correctly — it's read once, not scanned daily. The public status page planned for Open Beta is the other one, and it has a further constraint: most people arriving there are asking a single yes/no question under mild stress. It should answer that question above the fold, in one glance, before it shows anyone a number.
One view gets the design budget: the change timeline
The per-record change history is the only thing in this application the source systems cannot do at any price. It's the demo, it's the screenshot someone sends a colleague, and it's the reason the product exists. It's also the one view where the user is reading closely rather than scanning, which is exactly where careful design pays.
Make it genuinely beautiful. Diffs have looked the way they look for forty years because that shape works — before and after in parallel, the changed field emphasized and the unchanged context receding, monospace where values need to be compared character by character. Beautiful here means the change is instantly obvious, not that it's ornamented.
Everywhere else, earn your keep by being fast.
Show everything we received; let the user subtract
We do not curate what the user is allowed to see. Every field a source gave us is reachable in the interface, including the ones that didn't map cleanly onto a column and landed in extra, and including the raw payload exactly as the API returned it. Users hide what they don't want; we don't pre-hide it for them.
This follows directly from replacing a spreadsheet. Excel shows you every column you have, and you hide the ones you're not using today — it does not decide on your behalf that six of your twenty columns are the interesting ones. A tool that withholds fields is making an editorial judgment about someone else's work, and in this domain that judgment is usually wrong: the field a capture manager ignores completely is the one a contracts person opens the record for.
It also protects the thing we actually sell. We promise a complete history of what a record said. That promise is hollow if the interface quietly renders a subset of what we stored — a user who can't see a field has no way to know it changed.
"Findable" and "already on screen" are different guarantees
Keep these separate, because they have different lifespans:
Findable is permanent and absolute. Nothing we stored is unreachable from the interface — every normalized field, everything in extra, and the raw payload as the API returned it. This never gets traded away for cleanliness, and it isn't satisfied by "it's in the export" or "it's in the API." If it's in the database, a user can get to it in the UI.
On screen by default is a default, which means it's tunable and changes by phase. Right now it's maximal: every field is a column, the table scrolls wide, and there are no column controls at all. That's deliberate rather than unfinished — during dogfooding you cannot know which fields matter until you've lived with all of them, and a default that hides things is a default that stops you learning what you have.
The end state is a deliberately massive table plus saved filter and column preferences. Note that saved views, not a narrower default, are the mechanism for taming the width. We don't get there by deciding which eight columns everyone gets; we get there by letting each person save the arrangement they actually work in.
So, concretely:
- The record detail view shows every normalized field, plus the raw source payload. Nothing we stored is unreachable from the UI.
- The list shows every column until saved preferences exist to narrow it per user. Horizontal scroll is an acceptable cost; a hidden field is not.
- Export is complete by default. A CRM-shaped subset is a convenience option, not the only way out. Anyone should be able to get everything we have on the records they filtered.
- Opinionated means ordered, not filtered. Where we exercise judgment, it's in putting the most-used columns leftmost and the long free-text fields last — never in deciding someone doesn't get to see something.
- Identity columns stay pinned. In a table this wide, the row you're reading has to remain identifiable at any scroll position, so the watch toggle and title/ID freeze to the left edge.
The one thing we never do is drop a field on the way in. That rule lives in AGENTS.md because it's an engineering constraint, but it's the same principle: what a source told us is not ours to discard.
The tool adapts; it doesn't decide for the user
A capture manager scanning set-asides and NAICS codes and a grant writer scanning close dates are not looking at the same table, and choosing one default for both means being wrong for both. Configurability is a first-class feature here rather than a preference-panel afterthought: column selection, saved views, row density, and per-user default filters are all tracked as roadmap steps rather than as polish.
The corollary is that defaults still matter enormously, because most users never change them. Ship dense, ship opinionated, and let people who know what they want move it.
Things we are deliberately not
- Not a BI tool by accident. The ambition includes eventually covering the ground BI tools cover — analysis, charting, the works — but every chart still has to earn its place by answering a question a filtered table can't, built with the same density this document asks for everywhere else. Decoration that merely looks analytical is still decoration, no matter how far the roadmap has grown; it's the standard that's permanent here, not a scope limit on what the product is allowed to become.
- Not a notification firehose. An alerting product that cries wolf gets muted, and a muted product is a churned one. The pipeline already declines to log an event when nothing changed; every notification feature inherits that standard.
- Not a rebuild of the source sites. If the answer is "go look at SAM.gov," link to SAM.gov. Our value is the history and the cross-source list, not re-hosting a record detail page that already exists.