nyxcore-systems
6 min read

Automating Our Blog: From GitHub Push to Publish-Ready Content (and a UX Glow-Up!)

We just wrapped up a massive session focused on automating our blog generation workflow, enhancing user feedback with comprehensive toast notifications, and scaling our content creation capabilities. Dive into the technical details of webhooks, GitHub Actions, and why every API call deserves a toast!

automationgithub-actionswebhooksnextjstrpcdeveloper-experienceuxprismaai-generation

Development sessions are often a dance between fixing immediate issues and pushing towards a grander vision. This past session was firmly in the latter camp, marking significant strides towards a more automated, user-friendly, and scalable content generation platform. We tackled everything from integrating GitHub push events to providing crystal-clear user feedback.

Let's dive into the key achievements from this whirlwind development sprint.

Unleashing Automated Blog Generation

Our primary goal was to drastically reduce the manual overhead involved in generating blog posts. Previously, creating content, especially from our .memory/ files (which serve as development session summaries and insights), involved several manual steps. No more! We've implemented a robust system that transforms a simple git push into a background content generation process.

The "Why": Seamless Content Creation

Imagine writing your development notes, committing them, pushing to GitHub, and then having a blog post automatically drafted for you. This is the dream we're now realizing. It frees up developers to focus on coding, not content formatting or manual triggering.

The "How": A Symphony of Services

  1. GitHub Webhook Integration (src/server/services/github-webhook.ts): We extended our existing GitHub webhook service to specifically listen for pushes that affect files within the .memory/ directory. When a file like .memory/letter_2026-03-01.md is added or modified, our service springs into action.

    • It intelligently parses the GitHubPushPayload to identify relevant .memory/letter_*.md files.
    • For each detected file, it fetches the raw content from GitHub.
    • Crucially, it then creates a MemoryEntry and a BlogPost placeholder in our database.
    • Finally, it kicks off our generateBlogPost() function in the background – a fire-and-forget operation that leverages our previously built multi-provider AI generation capabilities.
  2. A Dedicated REST Endpoint (src/app/api/v1/blog/auto-generate/route.ts): To provide a secure and flexible entry point for external systems (like GitHub Actions), we introduced a new REST API endpoint. This endpoint is secured with a BLOG_AUTO_GENERATE_SECRET Bearer token and accepts a payload containing project ID and file content. This allows for direct control over what content is pushed for generation.

  3. Rewriting the GitHub Actions Workflow (.github/workflows/vibe_publisher.yml): Our GitHub Actions workflow, vibe_publisher.yml, received a significant overhaul.

    • The primary path now leverages git diff to identify new .memory/ files.
    • If secrets like NYXCORE_URL, BLOG_AUTO_GENERATE_SECRET, and NYXCORE_PROJECT_ID are configured, it directly calls our new nyxCore REST endpoint, passing the detected memory files for immediate background generation.
    • A fallback mechanism ensures that if these secrets aren't present, the original Python script and PR flow are still available, maintaining compatibility and flexibility.

This combination creates a powerful, automated pipeline that turns raw development notes into polished blog drafts with minimal human intervention.

A UX Glow-Up: Comprehensive Toast Notifications

User experience is paramount, and nothing communicates success or failure more immediately than a well-placed toast notification. Our project detail page, with its myriad of actions and mutations, was a prime candidate for a comprehensive UX upgrade in this area.

The "Why": Instant Feedback for Every Action

Before, if an API call failed, the user might not always know why or even if it failed without checking the console. Now, every interaction gets immediate, clear feedback. This builds trust and makes the application feel more responsive and robust.

The "How": A Centralized Toast System

  1. use-toast.ts Hook: We implemented a module-level toast system, exposing toast(), toast.error(), and toast.success() functions. This system manages the lifecycle of toasts, including auto-dismissal after 5 seconds and limiting the number of visible toasts to three at any given time to prevent clutter.

  2. toaster.tsx Component: This component acts as the renderer, leveraging existing Radix Toast primitives for a consistent and accessible UI.

  3. providers.tsx Integration: The <Toaster /> component was seamlessly integrated into our app/providers.tsx file, ensuring it's available globally across the application.

  4. Full-Page Mutation Coverage (src/app/(dashboard)/dashboard/projects/[id]/page.tsx): This was the heavy lifting! We meticulously went through every single mutation on the project detail page and added onError callbacks to display toast.error() messages. Additionally, key successful actions now trigger toast.success(). This includes:

    • updateProject, deleteProject
    • generateBatch, reimport
    • createNote, updateNote, deleteNote, enrichNote, applyEnrichment
    • createMutation, updateMutation, deleteMutation (for Active Processes)
    • extractMutation, createWorkflowMutation, importTodoMutation, createGroupWorkflowMutation
    • deleteManyMutation, deleteMutation (for reports)
    • confirmMutation, fetchUrlMutation, deleteMutation, updateMutation (for Axiom)
    • reprocessMutation
    • createTokenMutation, revokeTokenMutation

Now, whether you're generating a batch of blogs, deleting a note, or revoking an API token, you'll get instant, clear feedback right where you need it.

Scaling Content Creation: The 100-Entry Blog Batch Limit

Sometimes, the simplest changes have the biggest impact. We previously had a hard limit of 10 memory entries that could be selected for batch blog generation. This was a bottleneck for larger content pieces or when generating multiple related articles.

The "Why": More Flexibility, More Content

Our users needed the ability to select a larger set of memory entries to form a more comprehensive blog post or generate a series of interconnected articles.

The "How": A Single Line Change

We simply bumped the memoryEntryIds max limit from 10 to 100 in src/server/trpc/routers/projects.ts line 869. This small tweak unlocks significant flexibility for content creators.

Building on Solid Foundations

This session also built upon several recent enhancements, ensuring a cohesive and continuously improving platform:

  • Memory Insights Enhancement: Features like relevance dots, Select All/Clear All, and cross-project warnings make managing memory entries more intuitive.
  • Sidebar Progress: Our sidebar now dynamically displays the status of ongoing processes like documentation pipelines and blog generation.
  • Blog BYOK Generation: Support for multiple AI providers and fire-and-forget generation laid the groundwork for the automation we built this session.
  • Blog Timeline UI: A vertical timeline with robust search, sort, and filter capabilities makes navigating generated content a breeze.
  • New Personas: The introduction of personas like "Ipcha Mistabra" and specialized teams (PhD Expert, Compliance Audit, Adversarial Analysis) enriches the AI-generated content capabilities.

A Small Detour: The Case of the Missing isActive Field

Even in a productive session, there are always small challenges that teach valuable lessons. We encountered one when trying to query prisma.apiKey.findFirst({ where: { isActive: true } }) within our webhook service.

The Problem

TypeScript immediately flagged isActive as a non-existent field on ApiKeyWhereInput. My initial thought was, "Of course, API keys have an active status!"

The Lesson Learned

A quick check of our Prisma schema (schema.prisma) revealed the truth: the ApiKey model does not have an isActive field. Our keys are considered active if they exist in the database; their validity is determined at the point of usage by checking their expiry date. This was a classic case of assuming a field exists without verifying the schema. It's a good reminder to always double-check your data models, especially when working with ORMs.

What's Next?

With these features now implemented, our immediate focus shifts to ensuring everything works flawlessly in a production environment:

  1. Configuration: Setting up BLOG_AUTO_GENERATE_SECRET in our .env and GitHub repository secrets.
  2. Testing Toasts: Deliberately triggering API errors to confirm our flash messages appear correctly and provide useful feedback.
  3. Testing Auto-Generation: Pushing a .memory/ file to GitHub and verifying that the webhook successfully creates a BlogPost and initiates generation.
  4. E2E Tests: Expanding our end-to-end test suite for the blog generation flow, memory picker, and docs pipeline sidebar.
  5. Persona Polish: Adding an avatar image for the "Ipcha Mistabra" persona to complete the visual experience.

This session was a huge step forward in making our platform more intelligent, automated, and delightful to use. We're excited to see these enhancements streamline content creation and improve the overall developer and user experience.