nyxcore-systems
7 min read

From Brainstorm to Beat: Architecting nyxBook's AI-Powered Idea Pipeline

Dive into the journey of building nyxBook's new Ideas Pipeline, an AI-powered system designed to transform loose creative thoughts into structured story beats. We cover schema design, robust AI integration, a streamlined tRPC API, and a dynamic UI, all while preparing for advanced beat refactoring.

AILLMFullstackTypeScriptPrismatRPCNext.jsWritingToolsSoftwareArchitectureProductDevelopment

As developers, we often build tools to solve our own problems. For writers, one of the biggest challenges is taming the torrent of creative ideas that strike at inconvenient moments. A character quirk, a plot twist, a world-building detail – they all need a home, and ideally, a guide to help them find their rightful place in the narrative.

That's precisely the problem we're tackling with nyxBook, our AI-powered writing assistant. My latest deep dive was into building the Ideas Pipeline: a system designed to capture those fleeting sparks of genius and intelligently integrate them into a story's evolving structure.

This past session was a whirlwind of schema design, service logic, API endpoints, and UI components. By late evening, the core "Ideas" feature was not just implemented, but passing type-checks and linting with flying colors. Now, we're gearing up for the next frontier: AI-driven Beat Board refactoring.

Architecting the Flow: From Loose Thought to Incorporated Beat

The goal was clear: enable users to jot down any idea, have nyxBook's AI suggest where it might fit in their story, allow them to accept or reassign it, and finally, mark it as incorporated into their actual beats or chapters.

The Foundation: Schema Design

Every robust feature starts with a solid data model. For BookIdea, we needed to track its journey:

typescript
// prisma/schema.prisma
model BookIdea {
  id              String      @id @default(cuid())
  createdAt       DateTime    @default(now())
  updatedAt       DateTime    @updatedAt
  content         String      @db.Text
  tags            String[]    @default([])

  // AI Suggestion fields
  suggestedBeatId   String?
  suggestedChapterNum Int?
  suggestionReason  String?   @db.Text

  // User Assignment fields
  assignedBeatId    String?
  assignedChapterNum Int?

  // Lifecycle status: loose -> suggested -> assigned -> incorporated
  status          BookIdeaStatus @default(LOOSE)

  bookId          String
  book            Book        @relation(fields: [bookId], references: [id], onDelete: Cascade)

  tenantId        String
  tenant          Tenant      @relation(fields: [tenantId], references: [id], onDelete: Cascade)
}

enum BookIdeaStatus {
  LOOSE
  SUGGESTED
  ASSIGNED
  INCORPORATED
}

Key decisions here included:

  • Separating suggestedBeatId/ChapterNum from assignedBeatId/ChapterNum to track both AI's recommendation and the user's final decision.
  • A clear status enum to drive the idea's lifecycle through the pipeline.
  • Relating BookIdea directly to Book and Tenant for proper data scoping and ownership.

Once the schema was pushed and the Prisma client regenerated, we had our bedrock.

The Brain: AI Service Layer

This is where nyxBook truly shines. We created src/server/services/nyxbook-idea-suggest.ts to house our LLM interactions:

  • suggestIdeaPlacement(idea: string, bookId: string): This function takes a new idea and the context of the entire book (beats, characters, existing ideas) and makes an LLM call. The AI's task is to return a beatId, chapterNum, reason, and tags for the idea's optimal placement.
  • suggestBeatRefactoring(bookId: string): While primarily for the next phase, this function already exists, ready to analyze the story's beats, characters, and ideas to suggest structural changes (add, reorder, merge, split, modify).

Both services leverage our "Bring Your Own Key" (BYOK) pattern via resolveProvider(), ensuring flexibility and security for LLM access.

The API: tRPC Router

Our tRPC router (src/server/trpc/routers/nyxbook.ts) became the central hub for interacting with ideas. We introduced a new ideas nested router:

typescript
// Conceptual tRPC router structure
export const nyxbookRouter = t.router({
  // ... other book-related procedures
  ideas: t.router({
    list: publicProcedure.input(z.object({ bookId: z.string(), status: z.nativeEnum(BookIdeaStatus).optional() })).query(...),
    create: protectedProcedure.input(z.object({ bookId: z.string(), content: z.string(), tags: z.string().array().optional() }))
      .mutation(async ({ ctx, input }) => {
        // ... create idea, call suggestIdeaPlacement, update status to 'SUGGESTED'
      }),
    update: protectedProcedure.input(z.object({ id: z.string(), content: z.string().optional(), tags: z.string().array().optional() })).mutation(...),
    assign: protectedProcedure.input(z.object({ id: z.string(), beatId: z.string(), chapterNum: z.number() })).mutation(...),
    incorporate: protectedProcedure.input(z.object({ id: z.string() })).mutation(...),
    remove: protectedProcedure.input(z.object({ id: z.string() })).mutation(...),
    suggest: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => {
      // ... re-run AI placement on an existing idea
    }),
  }),
  beats: t.router({
    // ... existing beat procedures
    refactor: protectedProcedure.input(z.object({ bookId: z.string() })).mutation(async ({ ctx, input }) => {
      // ... call suggestBeatRefactoring
    }),
  }),
  // ... other router updates for counts and relations
});

The create procedure is particularly neat: it not only saves the idea but immediately triggers the suggestIdeaPlacement() service and updates the idea's status to SUGGESTED. This provides instant value to the user.

The Interface: Dynamic UI

On the frontend (src/app/(dashboard)/dashboard/nyxbook/page.tsx), the Ideas Pipeline came to life:

  • Dedicated "Ideas" Tab: A new lightbulb icon in the sidebar provides a clear home for all ideas.
  • IdeasTab Component: This central component features:
    • A quick-add input: Type an idea, hit Enter, and it's created and sent to the AI for suggestions.
    • Status filter tabs: "All," "Loose," "Suggested," "Assigned," "Incorporated" for easy navigation.
    • Interactive Idea Cards: Each card displays the idea's content, tags, the AI's suggestion (beat and reason), and action buttons like "Accept," "Reassign" (with a beat dropdown), "Re-suggest," "Done" (incorporate), and "Delete."
  • Overview Summary: A glanceable summary bar on the Overview tab shows idea counts by status, clickable to jump directly to the Ideas tab.

Integrating with the Beat Board

Finally, we connected the ideas directly to the existing BeatBoard. Each beat card now sports an "idea count" badge (a lightbulb icon with a number), and expanding a beat reveals all assigned or suggested ideas, providing immediate context for writers.

Lessons Learned: Robustness and Good Patterns Pay Off

One of the most satisfying aspects of this session was the lack of major roadblocks. Type-checking and linting passed cleanly on the first try, which is always a good sign of a well-structured plan.

  • Prisma's connect for Nested Creates: Our guidance (from internal CLAUDE.md documentation) on using Prisma's connect syntax for relating new ideas to existing Book and Tenant models proved invaluable. It worked flawlessly, reinforcing the power of well-documented patterns.
  • Graceful LLM Error Handling: We built the LLM suggestion service to handle parse failures gracefully. If the AI's JSON output isn't exactly what we expect, the idea simply reverts to a "loose" status. This is crucial for user experience when dealing with the inherent unpredictability of LLM responses. It prevents the entire process from breaking down due to a malformed suggestion.

The Road Ahead: Deeper AI Integration and UI Refinement

With the Ideas Pipeline successfully deployed, our immediate focus shifts to an even more ambitious goal: Beat Board Refactoring.

  1. Chapter-Grouped Timeline: The beat-board.tsx component is getting a major redesign. Instead of a flat grid, it will transform into a chapter-grouped timeline view, making the narrative flow much clearer. Unassigned beats will have their own dedicated section.
  2. Progression Markers: Chapter dividers will gain intelligent progression markers, showing:
    • Which characters appear or evolve within a chapter.
    • Which motifs are active, intensifying, or resolving.
    • Significant world state changes.
  3. Enhanced suggestBeatRefactoring(): Our AI service will then leverage these new progression markers. The prompt will be enhanced to explicitly consider character arc progression, motif development curves, and world-building evolution when suggesting structural changes. This moves nyxBook beyond simple beat management to truly intelligent story restructuring.

Beyond the Beat Board, we're also planning a significant sidebar restructure to better organize nyxBook's growing feature set, introducing dedicated sections for "World" (Characters, Motifs, Inspirations) and "Create."

This session was a major leap forward for nyxBook, bringing a powerful new tool into the hands of writers. The journey from a loose idea to a fully integrated story beat, guided by AI, is becoming a reality. Stay tuned for more updates as we continue to push the boundaries of creative writing assistance!


json
{
  "thingsDone": [
    "Implemented full Ideas Pipeline feature (schema, service, tRPC, UI)",
    "Created BookIdea model with lifecycle status and suggestion/assignment fields",
    "Developed LLM-powered suggestion service for idea placement and beat refactoring",
    "Built tRPC router with 7 idea-specific procedures and beat refactoring endpoint",
    "Designed and integrated 'Ideas' tab into dashboard UI with quick-add, filters, and actions",
    "Integrated idea counts and linked ideas into Beat Board component",
    "Ensured graceful handling of LLM parse failures"
  ],
  "pains": [
    "No major issues encountered, a testament to prior planning and architectural decisions."
  ],
  "successes": [
    "Prisma's nested create pattern with `connect` syntax worked flawlessly.",
    "LLM suggestion service handles parse failures gracefully, enhancing system robustness.",
    "Clean type-check and lint pass on first try, indicating solid development practices."
  ],
  "techStack": [
    "Next.js",
    "React",
    "TypeScript",
    "tRPC",
    "Prisma",
    "PostgreSQL",
    "Redis",
    "Docker",
    "LLM (via BYOK pattern)"
  ]
}