From Protocol Papers to Production: A Day of Scaling, Streamlining, and System Evolution
Join me as I recount a marathon development session, tackling everything from a PhD-level protocol paper to a complete overhaul of our LLM-powered enrichment UX, all while navigating the hidden complexities of distributed limits and evolving UI frameworks.
Every now and then, you have one of those development sessions where the stars align, the coffee flows, and a mountain of work gets done. Yesterday was one of those days. What started as a "letter to myself" for a session handoff quickly became a testament to what a focused sprint can achieve, pushing three major deliverables across the finish line and into production.
We're talking about a deep dive into our core IPCHA protocol, significantly scaling our LLM-driven action point limits, and a complete reimagining of our note enrichment user experience – complete with auto-apply and a crucial undo mechanism. Let's break it down.
The Intellectual Deep Dive: Architecting IPCHA's Future
The first major deliverable was something a bit more... academic. I spent a significant chunk of time drafting the IPCHA Protocol Evolution Paper. This wasn't just a README update; we're talking a ~15,000-word, IEEE-style document detailing the phased implementation timeline, a deep dive into 14 pain points with root cause analysis, 5 major refactoring decisions, 15 proof-of-concept validations, and 3 specific case studies.
From evaluating Persona Eval v2 to understanding cross-language contamination and dissecting Axiom A/B/C test results, this paper lays the strategic groundwork for the protocol's next phase. It's a foundational piece, and while the initial draft suggested a "PhD+ Research Level" classification, it was ultimately refined for broader technical accessibility – a good reminder that even the most complex ideas need to be communicated effectively.
Scaling Wisdom: From 20 to 50 Action Points
Next up was a more immediate, user-facing enhancement: increasing the maximum number of action points our LLM could extract from a note from 20 to 50. This sounds trivial, right? A quick constant change and you're done. Oh, how naive that thought was.
Initially, I went straight for the most obvious culprit: src/server/services/group-prompt-builder.ts, where MAX_GROUP_SIZE dictates the overall context window for our LLM calls. I bumped it to 50 (and adjusted MAX_ESTIMATED_TOKENS for our 1M context window). "Done!" I thought, leaning back.
Lesson Learned: The Many Faces of a Limit
Deployment revealed the truth: the limit was still stubbornly stuck at 20 for enrichment. It turns out, the action point limit wasn't a single, centralized constant. It was enforced in four distinct places, completely independent of MAX_GROUP_SIZE:
src/server/services/group-prompt-builder.ts:MAX_GROUP_SIZE: 50(My initial, correct change for overall context).src/server/trpc/routers/projects.ts: A Zod schema validation,.max(50), on the incoming action points array. This is a crucial frontend/API boundary check.src/server/services/note-enrichment.ts: The LLM prompt itself explicitly stated "up to 20 action points." The model, being obedient, followed this.src/server/services/action-point-extraction.ts: A.slice(0, 50)operation after parsing, acting as a final safeguard.
This was a classic case of distributed enforcement, and a critical reminder to always trace limits through the entire system, from UI to database, including LLM prompts themselves. A quick hunt-and-fix across these four points finally unlocked the full 50 action points.
The UX Revolution: Auto-Apply, Undo, and a Smarter Workflow
This was arguably the most impactful change for the user experience. Previously, when you enriched a note, you'd get a list of suggested action points, and you'd have to manually cherry-pick and apply them. It was flexible, but cumbersome for power users. The goal was to streamline this into a "one-click magic" experience.
Merged Mutations and the Safety Net
The core of this change involved merging the LLM enrichment call with the action point application. Our enrich mutation now performs the LLM call, automatically applies all extracted action points to the note, and crucially, returns the necessary data for an immediate undo: originalContent, actionPointIds (of the newly created points), and actionPointCount.
To complete the safety net, I introduced a new undoEnrichment mutation. This mutation takes the undo data, restores the original note content, deletes the action points created during the enrichment, and resets the note's enrichmentStatus back to "none".
Handling Long-Running Operations: enrichmentStatus
Enrichment can take a few seconds, so a robust processing status was vital. When a user triggers enrichment, the note's enrichmentStatus is immediately set to "processing". This status is persisted to the database, meaning a page reload won't lose track of an ongoing operation. Our notesQuery now smartly polls every 3 seconds if any note is in a "processing" state, ensuring the UI updates automatically once the operation completes. Error recovery was also built in: if enrichment fails for any reason, enrichmentStatus attempts to revert to its previous value.
Lesson Learned: Evolving Your UI Toolkit
A key part of the UX was providing immediate feedback and the ability to undo. Our existing toast system, while functional, only supported (title, description?) signatures. It lacked the ability to add action buttons or custom durations.
This led to another mini-project: extending src/hooks/use-toast.ts and src/components/ui/toaster.tsx.
I introduced a new ToastAction interface:
interface ToastAction {
label: string;
onClick: () => void;
}
// ... and extended ToastData
interface ToastData {
title: string;
description?: string;
action?: ToastAction; // New!
duration?: number; // New!
// ... other properties
}
The toast() function was updated to accept a duration parameter, and toast.success() was overloaded to accept either a simple string or a full options object. Finally, the Toaster component was updated to render an action button (styled as an accent-bordered button) next to the close button when an action is provided.
This allowed us to simplify the main page.tsx significantly. The entire review/cherry-pick UI section was removed, replaced by a simple processing indicator (spinner + text). On success, a 30-second toast now appears, proudly proclaiming "Enrichment complete!" with a prominent "Undo" button.
A Small but Mighty Detail: $transaction for IDs
One final technical detail for the enrichment: when creating multiple action points, createMany() is efficient but doesn't return the IDs of the newly created records. For our undo mechanism, we absolutely needed those IDs. The solution was to use individual create() calls wrapped within a Prisma $transaction. This ensures atomicity while still allowing us to capture the id of each new action point for the undo operation.
Current State & Immediate Next Steps
All three deliverables – the IPCHA paper, the 50-action-point limit, and the auto-apply/undo enrichment – are complete, committed (739a2ce), pushed, and happily deployed to production. All containers are healthy, and no schema changes were required (the enrichmentStatus field was already flexible enough).
My immediate focus shifts to:
- Production Testing: Verify the enrichment flow on production with a known 36-action-point note.
- Undo Validation: Ensure the 30-second undo toast appears and functions correctly.
- Reload Resilience: Confirm that reloading the page during enrichment correctly shows the "Enriching with wisdom..." spinner and auto-updates upon completion.
- Undo Persistence (Consideration): Evaluate if undo data (original content, action point IDs) should be persisted server-side. Currently, undo is only available via the client-side toast closure, which disappears on reload. This might be a future enhancement for a more robust undo experience.
- BYOK Integration: Wire
userIdthrough toresolveProvider()calls to support personal API keys, a key feature in our pipeline.
It was a demanding but incredibly rewarding session. It's a great feeling to see such substantial features move from concept to production, especially when they involve both deep architectural thought and significant UX improvements. Onward to the next challenge!
{"thingsDone":["IPCHA Protocol Evolution Paper","Action Point Limit Increase (20 to 50)","Background Enrichment with Auto-Apply + Undo"],"pains":["Multiple enforcement points for limits (not just one constant)","Existing toast system lacked action buttons and custom duration","Prisma default string fields require explicit string value (not null)"],"successes":["Successful deployment of all features","Streamlined LLM enrichment UX","Robust undo mechanism implemented","Extended UI component capabilities (toast system)","Effective use of Prisma transactions"],"techStack":["TypeScript","Node.js","Prisma","tRPC","React","Next.js","LLM (likely OpenAI/Anthropic via a wrapper)","Docker","PostgreSQL","Redis"]}