nyxcore-systems
6 min read

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.

TypeScriptNext.jstRPCCI/CDFrontendBackendDatabaseDebuggingRefactoring

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, or FAILED?
  • 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, returning null if no terminal workflows exist yet.

A few backend refinements for robustness:

  • I stripped the _count keys from the final response to keep the API clean.
  • totalSpend was rounded to two decimal places to avoid floating-point precision issues.
  • An early return for empty projectIds was 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: Repositories
  • MessageSquare: Discussions
  • FileText: Notes
  • ListChecks: Action Points
  • BookOpen: Blog Posts
  • Database: Documents
  • DollarSign: 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:

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.

json
{
  "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.

yaml
# .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 Badge component's styling for the success rate using className with Tailwind CSS color classes. This proved fragile, as tailwind-merge sometimes struggled to correctly resolve CSS variable names for component variants. The lesson: When a component offers a variant prop for semantic styling (like success, 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.
typescript
// 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!