Dashboard Deep Dive: Enriching Project Views and Taming a Tricky CI Pipeline
A recent development session saw us transform our project list page with crucial statistics and conquer a trio of stubborn CI pipeline failures. Dive into the technical journey, the lessons learned, and the satisfaction of a green build.
Every developer knows the feeling: a new feature request lands, and simultaneously, the CI pipeline decides to throw a tantrum. This past week, I had the pleasure (and challenge!) of tackling both. My mission: turn a basic project list into a data-rich overview and bring our failing CI back to a glorious green.
Elevating the Project List: From Basic to Insightful
Our /dashboard/projects page, while functional, was a bit sparse. It showed project names, but that was about it. The goal was to imbue each project card with immediate, glanceable insights: how many workflows, discussions, notes, and action points it had, its total spend, and crucially, its success rate.
This enhancement primarily involved two areas: our tRPC backend router and the Next.js frontend page component.
The Backend Power-Up (src/server/trpc/routers/projects.ts)
To get all this juicy data, our list query needed a serious upgrade. I started by leveraging Prisma's _count aggregation feature for seven of our project's relations: workflows, discussions, project notes, action points, repositories, project documents, and blog posts. This gave us raw counts, which is a great start.
However, some metrics required more sophisticated calculations, like:
- Workflow Statuses: How many are
DRAFT,COMPLETED, orFAILED? - Open Actions: A simple count of action points not yet marked
DONE. - Spend Aggregation: Summing up costs across various entities like workflow steps, discussion messages, reports, and blog posts. This required nine parallel aggregation queries to fetch all the necessary cost data efficiently.
- Success Rate: Calculated as
completed_terminal_workflows / (completed_terminal_workflows + failed_terminal_workflows). This provides a clear picture of how often automated processes succeed, returningnullif no terminal workflows exist yet.
A few backend refinements for robustness:
- I stripped the
_countkeys from the final response to keep the API clean. totalSpendwas rounded to two decimal places to avoid floating-point precision issues.- An early return for empty
projectIdswas added to prevent unnecessary database queries, a small but important performance optimization.
The Frontend Glow-Up (src/app/(dashboard)/dashboard/projects/page.tsx)
With the backend serving up all the data, the frontend's job was to display it elegantly. Each project card now features a compact row of statistics, each accompanied by a lucide icon for quick visual recognition:
GitBranch: RepositoriesMessageSquare: DiscussionsFileText: NotesListChecks: Action PointsBookOpen: Blog PostsDatabase: DocumentsDollarSign: Total Spend
For the successRate, we used a semantic Badge component, dynamically setting its variant (e.g., success, warning, danger) based on the rate. This provides immediate visual feedback without relying on fragile CSS overrides. We also added badges for open action counts, draft counts, and total post counts. All purely decorative icons received aria-hidden="true" for accessibility, and a formatCost(usd) helper ensured consistent currency display.
Taming the Beast: Squashing CI Pipeline Failures
While the feature work was satisfying, the lurking red "X" on our CI pipeline was a constant irritant. There were three distinct failures, each pointing to a different area of our stack.
1. The Linting Labyrinth
Our lint job was failing spectacularly, reporting 40+ pre-existing errors alongside a new one. The root cause was an upgrade of eslint-config-next@14.2.35 which, in turn, pulled in @typescript-eslint@8.x. This newer version requires explicit plugin registration in .eslintrc.json:
{
"extends": ["next/core-web-vitals"],
"plugins": ["@typescript-eslint"], // <--- This was missing!
"rules": {
// ... other rules
}
}
Beyond that, I also added varsIgnorePattern and destructuredArrayIgnorePattern to allow our convention of prefixing unused variables or destructured props with an underscore (_) without triggering lint errors.
{
"rules": {
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_"
}
]
}
}
With the configuration fixed, I went on a spree, cleaning up those 40+ pre-existing lint errors across ~25 files. This included removing unused imports, consistently using the _ prefix for ignored variables, and fixing a subtle React Hook issue in markdown-renderer.tsx where a useCallback was conditionally called after an early return for Mermaid diagram rendering.
2. The Shifting sands of LLM Models (Unit Test Fix)
A unit test for our LLM service (tests/unit/services/llm/kimi.test.ts) was failing. The culprit? An internal adapter change. The expected model name had subtly shifted from kimi-k2-0711 to kimi-k2-0711-preview. A quick string update in the test file was all it took to realign. This highlights the fragility of external dependencies and the importance of robust testing.
3. Database Vector Power-Up (E2E Test Fix)
Finally, our end-to-end (E2E) tests were failing due to a database issue. Our schema requires the vector type, which isn't available in the standard postgres:16-alpine image. The fix was to switch the Postgres service image in our .github/workflows/ci.yml to pgvector/pgvector:pg16, which bundles the necessary extension.
# .github/workflows/ci.yml
services:
postgres:
image: pgvector/pgvector:pg16 # Changed from postgres:16-alpine
env:
POSTGRES_DB: testdb
POSTGRES_USER: user
POSTGRES_PASSWORD: password
ports:
- 5432:5432
Lessons Learned: Navigating the Nuances
Every development session comes with its "aha!" moments and head-scratching challenges. Here are a couple from this sprint:
- Semantic Component Usage: I initially tried to override the
Badgecomponent's styling for the success rate usingclassNamewith Tailwind CSS color classes. This proved fragile, astailwind-mergesometimes struggled to correctly resolve CSS variable names for component variants. The lesson: When a component offers avariantprop for semantic styling (likesuccess,warning,danger), use it! It's more robust and aligns with the component's design system. - TypeScript Destructuring with Unused Props: In a function signature, I tried to prefix a destructured prop directly (e.g.,
_teamId) to indicate it was unused. TypeScript, however, correctly complained that the prop name must match the type definition. The elegant workaround is to use TypeScript's rename syntax during destructuring:teamId: _teamId. This allows you to alias the prop to a new name (which can then be prefixed with_) without altering the original type definition.
// Incorrect (TypeScript error)
function MyComponent({ _teamId }: { teamId: string }) { /* ... */ }
// Correct (using rename syntax)
function MyComponent({ teamId: _teamId }: { teamId: string }) { /* ... */ }
What's Next?
With the feature complete and the CI pipeline back to a healthy green, the immediate next steps are to confirm the visual appearance of the new project list page and then, as always, consider future improvements. This includes possibly adding tooltips to clarify the success rate calculation and exploring denormalization of these statistics into the Project model if the list query becomes a performance bottleneck at scale.
It's immensely satisfying to push code knowing that not only have you shipped a valuable feature, but you've also left the codebase and its testing infrastructure in a healthier state. On to the next challenge!