nyxcore-systems
6 min read

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.

nyxcorefeature-releasefrontendbackendprismanextjstypescriptdatabaseworkflowreportslessons-learned

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:

  1. Content Extraction: Helper functions like extractChapterNumber() and extractStepContent() meticulously parse the output of the completed workflow step, identifying the chapter number and its corresponding content.
  2. 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.
  3. 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:

  1. Database Schema: We added a simple isPublic Boolean @default(false) column to our Report model. This flag controls the visibility of each report.
  2. API Endpoint: A new togglePublic tRPC mutation in reports.ts allows users to easily switch a report's public status.
  3. Public Routes:
    • A dedicated server component at src/app/(public)/r/[shortId]/page.tsx now renders public reports. This route is designed to be accessible without authentication.
    • We also created src/app/api/v1/reports/pdf/public/route.ts to allow direct PDF downloads of public reports.
  4. User Interface: Share toggles were integrated into the ReportGeneratorModal and the ReportsTab on project pages. Users can now click a "Link2" icon to toggle sharing and copy the public URL.
  5. Middleware Integration: Crucially, src/middleware.ts was 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 use prisma.report.findFirst({ where: { id: { startsWith: shortId } } }).
  • The Problem: We quickly discovered that Prisma's UuidFilter does not support a startsWith operation 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 a LIKE comparison:
    sql
    REPLACE(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.$queryRaw when 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 pull fetched 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 main resolved 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 isPublic column, my first instinct was to run npx prisma@5.22.0 db push on production.
  • The Problem: Prisma's db push command, designed for development, warned it wanted to drop our embedding vector column (which held 711 non-null values!). This was a clear sign of potential data loss. db push is 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.ts was 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!