From Dev Session to Live Features: NyxCore's Latest Enhancements
A deep dive into NyxCore's recent feature deployment, covering seamless chapter auto-save and robust public report sharing, alongside the technical challenges we navigated.
Shipping new features is always a thrill, especially when they significantly enhance the user experience and expand the platform's capabilities. This past week at NyxCore, we pushed two major updates that I'm excited to share: automatic chapter saving for nyxBook workflows and public sharing for generated reports. Both are now live in production, and this post takes you behind the scenes of their development, the technical decisions made, and the valuable lessons learned along the way.
Empowering Writers: nyxBook Auto-Save Chapters
For anyone crafting narratives or structured documents with nyxBook, the fear of losing progress is real. Our primary goal with this feature was to eliminate that anxiety, ensuring that every completed workflow step is automatically saved as a chapter. This creates a seamless, worry-free writing experience.
How We Built It: A Glimpse Under the Hood
The core of this feature lives within our workflow-engine.ts. When a user completes a step in their nyxBook workflow, a few things happen:
- Content Extraction: Helper functions like
extractChapterNumber()andextractStepContent()meticulously parse the output of the completed workflow step, identifying the chapter number and its corresponding content. - Persistent Storage: The extracted data is then passed to
persistChapterFromWorkflow(). This function intelligently upserts the chapter into our database. "Upsert" is key here – it either creates a new chapter or updates an existing one if the user revisits and modifies an earlier step. - User Feedback: To keep the user informed, we emit a Server-Sent Event (SSE) displaying a message like "Chapter N saved (narrative + aktenlage)," providing immediate confirmation of their progress being securely stored.
We ensured robustness with a comprehensive suite of 9 unit tests in tests/unit/persist-chapter.test.ts, covering various scenarios to guarantee the reliability of the saving mechanism. The entire design was laid out in our docs/plans/2026-03-08-save-to-chapter-design.md, guiding our implementation.
Broadening Reach: Public Report Sharing
NyxCore generates powerful, data-rich reports. Until now, sharing these insights was limited to internal team members. Our second major feature unlocks the ability to share reports publicly via a unique, short URL, significantly expanding the utility of our platform.
Making Reports Shareable
Implementing public sharing involved changes across our stack:
- Database Schema: We added a simple
isPublic Boolean @default(false)column to ourReportmodel. This flag controls the visibility of each report. - API Endpoint: A new
togglePublictRPC mutation inreports.tsallows users to easily switch a report's public status. - Public Routes:
- A dedicated server component at
src/app/(public)/r/[shortId]/page.tsxnow renders public reports. This route is designed to be accessible without authentication. - We also created
src/app/api/v1/reports/pdf/public/route.tsto allow direct PDF downloads of public reports.
- A dedicated server component at
- User Interface: Share toggles were integrated into the
ReportGeneratorModaland theReportsTabon project pages. Users can now click a "Link2" icon to toggle sharing and copy the public URL. - Middleware Integration: Crucially,
src/middleware.tswas updated to recognize/r/as a public route, preventing unauthenticated users from being redirected to the login page when trying to access a shared report.
As with the auto-save feature, a detailed plan was documented in docs/plans/2026-03-08-public-report-sharing-design.md.
Navigating the Rapids: Challenges & Lessons Learned
Development isn't always smooth sailing. Here are some of the key challenges we encountered during this session and the valuable lessons we took away:
1. Prisma's UUID startsWith Limitation
- The Challenge: For our public report sharing, we wanted to use a short, 8-character prefix of the report's UUID (e.g.,
/r/9CFD8B12) for easy, human-readable URLs. Our initial thought was to useprisma.report.findFirst({ where: { id: { startsWith: shortId } } }). - The Problem: We quickly discovered that Prisma's
UuidFilterdoes not support astartsWithoperation on UUID columns directly. - The Workaround: We had to drop down to raw SQL using
prisma.$queryRaw. The solution involved casting the UUID to text, removing hyphens, and then performing aLIKEcomparison:sqlREPLACE(id::text, '-', '') LIKE ${prefix + "%"} - Lesson Learned: While ORMs like Prisma are incredibly powerful and convenient, there are always edge cases where direct SQL access is necessary. Don't be afraid to leverage
prisma.$queryRawwhen ORM abstractions hit their limits, especially for database-specific string manipulations or optimizations.
2. The Case of the Missing Commits
- The Challenge: During deployment, I attempted to pull the latest changes on the production server, but
git pullfetched nothing. - The Problem: The commits only existed locally on my machine; I had forgotten to push them to the remote repository.
- The Workaround: A quick
git push origin mainresolved the issue, making the commits available for the server to pull. - Lesson Learned: This was a basic but critical reminder: always ensure your local changes are pushed to the remote repository before initiating a server deployment. It's easy to overlook in the heat of development!
3. Prisma db push and Production Schema Changes
- The Challenge: When adding the
isPubliccolumn, my first instinct was to runnpx prisma@5.22.0 db pushon production. - The Problem: Prisma's
db pushcommand, designed for development, warned it wanted to drop ourembeddingvector column (which held 711 non-null values!). This was a clear sign of potential data loss.db pushis not designed for production migrations. - The Workaround: We opted for a direct SQL command to safely add the column without risking existing data:
sql
ALTER TABLE reports ADD COLUMN IF NOT EXISTS "isPublic" BOOLEAN NOT NULL DEFAULT false; - Lesson Learned: Be extremely cautious with ORM migration tools on production databases. For simple, additive schema changes, direct SQL is often the safest and most explicit path, especially when dealing with production data. Always understand what a migration command will do before executing it in a live environment.
4. Middleware Route Mismatch
- The Challenge: After deploying, accessing a public report URL (
/r/someShortId) unexpectedly redirected to the login page (HTTP 307). - The Problem: Our
src/middleware.tswas correctly identifying other public routes (like/b/for books) but had not been updated to include the new/r/prefix. - The Fix: We updated the middleware to include
isReportRoute = nextUrl.pathname.startsWith("/r/")in our public route checks. - Lesson Learned: When introducing new public-facing routes, always remember to update any authentication or authorization middleware to explicitly allow access. Overlooking this can lead to frustrating redirection loops.
Current Status and Next Steps
Both features are deployed to production (commit 3fbe1ac) and verified working. You can see a test report live at https://nyxcore.cloud/r/9CFD8B12. The isPublic column has been successfully added to the reports table.
Our immediate next steps include:
- Thoroughly testing the share toggle within the UI (project page, Link2 icon).
- Verifying PDF download functionality from public report pages.
- Considering the implementation of rate limiting for public report and PDF routes to prevent abuse.
- Adding Open Graph (OG) meta tags to public report pages for improved social media sharing.
It was a productive session, resulting in two powerful new features and a handful of valuable lessons that will undoubtedly make our development process smoother in the future. We're excited for you to try them out!