Features Page
One page that outlines every user-facing surface in the app: each feature's name, a one-line description, its route, and a link to its long-form doc, grouped into seven categories and filtered by a single search box.
- Route:
/features - Nav: the global dock's Settings menu → All Features (the first item)
- Panel:
apps/debate-ai.com/lib/ui/features/FeaturesPanel.tsx
Why this exists
Nothing in the app listed everything it does:
- The global dock exposes four destinations (Videos, Shared, Debate, Docs) plus a Settings menu that is a flat, unexplained list of forty-odd items.
/researchand/coacheach tab across the panels of one package.
So a debater arriving at the app had no way to learn what it does short of opening the Settings menu and clicking through every entry. This page is that map.
What it shows
| Element | Source |
|---|---|
| Header count line | buildFeatureCatalogSummaryText — e.g. "50 features across 7 categories: Core Workspaces (8), …" |
| Search box | searchFeatures, matching title, description, route, and hidden tags |
| Jump-to-category row | One pill per non-empty section, anchored to its id |
| Section heading + blurb | FEATURE_CATEGORY_LABELS / FEATURE_CATEGORY_DESCRIPTIONS |
| Feature card | Title (links to the route), description, the route itself, and a Docs link when the feature has one |
The count line always describes the whole catalog, not the current filter, so it stays a stable "how big is this app" answer while someone types.
Categories
| Category | What it groups |
|---|---|
| Core Workspaces | Video archive, card search, the flow spreadsheet, Reason docs/editor, the research/coach hubs, the community hub |
| Evidence & Research | Evidence and argument libraries, contributions feed, card scoring, peer review, revision incentives, coverage, progress, task inbox |
| Team Collaboration | Brainstorm boards, collaboration mode, prep room, prep notes, notifications, coaching programs, coach materials |
| Flowing & Round Analysis | Argument tree outline, transcript summaries, flow annotations, response-outcome charts, speech documents, word-count speeches |
| Pre-Round Intelligence | Opponent and judge profiles, briefings, scout-to-strategy, paradigm picker, AI judge decision |
| Practice & Coaching | Practice round simulator, versus-AI rounds, opponent personas, AI coach mode, drills |
| Recognition & Progress | Leaderboard, awards, daily best card, quests, streaks, unlocks, group challenges, team rankings |
How it works
packages/debate-feature-catalog/src/feature-catalog.tsholds the pure data and helpers:APP_FEATURES,buildFeatureSections,searchFeatures,featureDocUrl, andbuildFeatureCatalogSummaryText. Likedebate-community's narrower community-hub directory it has no store — every entry links to a surface that already persists (or doesn't need to persist) its own state. It is a dependency-free leaf package (the same shape asdebate-data-sync) that the app anddebate-communityboth import from directly — see "One shared catalog" below for why this needed its own package rather than living in either.apps/debate-ai.com/app/features/page.tsxmountsapps/debate-ai.com/lib/ui/features/FeaturesPanel.tsx, which renders the catalog (an aurora hero, counted-up totals, a marquee, and scroll-reveal spotlight cards) holding only the search string in local state. It links each card's doc to the app's own same-origin docs site viafeatureDocUrl's default options.
Per-card hover colour
The grid at rest is painted in the one app accent, but each card hovers in a colour of its own: the cursor spotlight, the rotating border beam and the card title all come up in a hue no neighbouring card shares.
cardHueShift(index) in effects.tsx turns a card's position in the catalog
into a rotation off --accent-hue, stepping by the golden angle (137.508°) so
consecutive cards land far apart on the wheel rather than in the
near-duplicates a plain index * 40 would give. SpotlightCard writes that
angle to the card as --da-card-hue and tags it da-card-tint;
globals.css turns the angle into the --da-card-accent /
--da-card-accent-3 pair, which the hover visuals read with a fallback to the
page-wide --da-accent.
Three details are load-bearing:
- The angle is unitless. It is added to the bare
--accent-hueinside onecalc(), andcalc(192 + 138deg)is invalid — adegsuffix would silently drop the colour to its fallback. - The rotation is resolved in CSS, not inline. A custom property inherits
the value it was substituted with, not the expression, so a card that only
overrode
--da-card-huewould never recompute an--da-accentdeclared on an ancestor. The card has to declare its own colour tokens — and doing it in the stylesheet keeps the light/dark lightness split the rest of the accent tokens use. - The hue comes from the full catalog, not the filtered grid. A feature keeps its colour while someone types, instead of every card changing hue on each keystroke.
Only the hover visuals read the per-card tokens. The resting page — the Docs
links, the section icons, the aurora, the shimmering headline — stays on
--da-accent, so the grid still reads as one surface until a card is pointed
at.
One shared catalog
APP_FEATURES used to exist in three hand-synced places — this app's own
copy, the old shared UI kit's seed copy, and
packages/debate-contributor-progress's News Stream copy — because none of
the packages involved shared a dependency edge that would let one import
another's copy. The UI kit was deliberately not a runtime dependency of the
app or of debate-community, and it has since been dissolved into a
per-package ui/ folder (see Packages).
That's now resolved by promoting the catalog into its own dependency-free
leaf package, debate-feature-catalog (the same shape as debate-data-sync:
a package.json with no exports/main, consumers importing its one
source file by deep path). All three sites depend on it directly
("debate-feature-catalog": "workspace:*" in each of their package.jsons)
and import the same APP_FEATURES, buildFeatureSections, searchFeatures,
and buildFeatureCatalogSummaryText:
| Consumer | Renders via |
|---|---|
apps/debate-ai.com (/features, live) | apps/debate-ai.com/lib/ui/features/FeaturesPanel.tsx |
packages/debate-contributor-progress (News Stream "Tool spotlight" posts) | src/lib/news-stream.ts's buildAutoFeatureNews() |
Where a feature's doc link points is a parameter rather than a forked file:
featureDocUrl(entry, options) defaults to the live app's same-origin,
extensionless docs route (/docs/features/task-inbox), and a renderer with
no docs site of its own can pass
{ baseUrl: GITHUB_DOCS_BASE_URL, stripExtension: false } to link to the
doc's source on GitHub instead. buildAutoFeatureNews doesn't call
featureDocUrl at all, so it's unaffected either way.
Titles and descriptions are copied from each route's own metadata export
(or, where a route has none, from its feature doc's opening lines) so a card
reads the same as the page a reader lands on after clicking it.
Each entry may also carry tags: search terms that don't appear in the
visible copy — package names, synonyms, and the jargon a debater would
actually type. "elo" finds Team Rankings, "rfd" finds AI Judge Decision, and
"verbatim" finds both the Reason Editor and Speech Documents, none of which
mention those words on screen.
debate-feature-catalog is its own package, rather than living inside any of
its three consumers, because the catalog spans every package: it names
surfaces from debate-round, debate-research-evidence, debate-videos,
debate-speech-writer, and reason-editor, so it can't live inside any one
of them without inverting the dependency graph, and putting it in the app
itself would have put it outside the root Vitest projects, which only cover
packages/*.
Tests
packages/debate-feature-catalog/test/feature-catalog.test.ts covers the
catalog's own invariants (unique ids and routes, every entry categorized,
every category used, docs pointing at .md files), section grouping and
ordering, search across all four matched fields, featureDocUrl in both its
default and GitHub-blob-URL forms, and summary-line pluralization.
apps/debate-ai.com/lib/ui/__tests__/features-panel.test.tsx renders the
panel and asserts every catalogued route, the category headings, the summary
line, a docs link, the jump nav, and that a one-entry catalog renders without
the jump nav. It also covers the per-card hover colour: that every card
renders a --da-card-hue, that the angle follows the card's catalog position
rather than its place in a filtered grid, and that cardHueShift stays inside
one turn of the wheel while keeping consecutive cards more than 80° apart.
apps/debate-ai.com/lib/__tests__/tool-catalog-consistency.test.ts
cross-checks this catalog's routes against /tools' ALL_TOOLS and the
Reason Editor's WORKSPACE_LINKS, and separately walks every static route
under apps/debate-ai.com/app/ to assert each appears in at least one of
the three.
Known gaps
- Fixed: the catalog is a hand-maintained registry, so a new route used
to have to be added to it by hand with nothing checking that it was — the
tests only asserted the catalog's internal consistency, not that it
covered every file under
apps/debate-ai.com/app/, since the app is outside the packages Vitest runs over.tool-catalog-consistency.test.tsnow also walksapps/debate-ai.com/app/for every static route with its ownpage.tsx(skipping dynamic-segment detail routes, which don't get their own catalog entry) and asserts each appears in at least one ofALL_TOOLS/APP_FEATURES/WORKSPACE_LINKS, unless it's in that test's ownROUTES_WITHOUT_A_CATALOG_ENTRY(the homepage,/admin, an auth-flow step, a legal page,/login, or the editor's own settings pages — none of which are "a tool" to list). This is a test-only relative import into the app's own route tree vianode:fs, so it adds no runtime dependency; a new route added toapp/and to none of the three catalogs now fails this test instead of silently reaching none of them. - Fixed: the catalog used to exist as three hand-synced copies (see "One
shared catalog" above), and they drifted once —
/contactswas added to the app's copy but never carried over to the other two, so News Stream's auto-generated "Tool spotlight" post silently never fired for it, and nothing caught it because each copy's own test suite only checked its own internal consistency. Promoting the catalog into the shareddebate-feature-catalogpackage removes the drift risk entirely — there's only one file to edit now, so this class of bug can no longer recur. /loginis intentionally absent: it's a step on the way to a feature rather than a feature, and it's already reachable from the Settings menu.- The live
/featurespage's Docs links stay same-origin; a renderer with no docs site of its own would have to passGITHUB_DOCS_BASE_URLtofeatureDocUrlto point at the feature docs on GitHub'smasterbranch instead.
