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.
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:
// 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/ChapterNumfromassignedBeatId/ChapterNumto track both AI's recommendation and the user's final decision. - A clear
statusenum to drive the idea's lifecycle through the pipeline. - Relating
BookIdeadirectly toBookandTenantfor 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 abeatId,chapterNum,reason, andtagsfor 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:
// 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.
IdeasTabComponent: 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
Overviewtab shows idea counts by status, clickable to jump directly to theIdeastab.
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
connectfor Nested Creates: Our guidance (from internalCLAUDE.mddocumentation) on using Prisma'sconnectsyntax for relating new ideas to existingBookandTenantmodels 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.
- Chapter-Grouped Timeline: The
beat-board.tsxcomponent 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. - 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.
- 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 movesnyxBookbeyond 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!
{
"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)"
]
}