Skip to content

Content Admin: Editorial Workflow & Technical Architecture #3910

@devin-ai-integration

Description

@devin-ai-integration

Overview

The Content Admin (/admin) provides content management for the Hyprnote website. It consists of two main sections:

  1. Articles (/admin/collections) — Create, edit, and publish blog posts
  2. Media Library (/admin/media) — Manage visual assets (images, blobs) used in blog content

Having both tightly integrated is critical: when writing in the editor, the user (Harshika, our marketer) needs to select images from the media library directly via the editor toolbar and embed them inline. The goal is a smooth, friction-free editorial experience.

Current state: The system exists but is broken in several areas. This issue documents the correct intended workflow and the technical implementation backing it.


Editorial Workflow

1. Creating a New Post

When a user creates a new post in the Articles section:

  1. User enters a slug (e.g. testing)
  2. System creates a new branch: blog/testing
  3. System commits the actual MDX file (testing.mdx) on that branch — the file only exists on the branch, not on main
  4. The file is created with default frontmatter:
    ---
    meta_title: ""
    display_title: ""
    meta_description: ""
    author: "John Jeong"
    featured: false
    category: ""
    date: "2026-02-12"
    ---
  5. The post now appears as a draft in the sidebar (discovered via blog/* branch listing)

Important: The MDX file ({slug}.mdx) is created directly on the blog/{slug} branch. It does NOT exist on main until the PR is merged. This is how drafts work — they live entirely on their branch.

2. Editing (Debounced Auto-Save)

While editing a draft post:

  • Changes to both the content editor and metadata (frontmatter) are tracked
  • Auto-save uses a 10-second debounce: if the user stops editing for 10 seconds, changes are automatically committed to the draft branch
  • If the user keeps editing continuously, the save is deferred until they pause for 10 seconds
  • An auto-save indicator is shown in the UI (e.g. "Saving...", "Saved", "Unsaved changes") — there is no explicit Save button
  • The branch accumulates commits over time as the user works, but only after natural pauses in editing

3. External Edits (Branch Sync)

Since the article lives on a git branch, anyone can pull that branch and make commits externally (e.g., from their IDE, CLI, or another tool). The editor must handle this:

  • The editor should periodically poll the branch for new commits (or check on focus/tab switch)
  • If the branch has been updated externally (remote is ahead), the editor should automatically fetch and apply the latest content
  • This keeps the editor in sync so the user always sees the most up-to-date version, regardless of where edits were made

4. Publishing (Explicit Publish Button)

Instead of a Save button, there is only a Publish button. When the user clicks Publish:

  1. If there are unsaved changes (within the 10s debounce window), they are immediately committed to the branch first
  2. A PR is created from the draft branch to main, ready to be merged
  3. The PR is assigned to harshikaalagh-netizen for review
  4. A banner appears in the editor linking to the PR
  5. The blog-grammar-check.yml GitHub Action triggers on the PR

Key distinction: Save = auto (debounced, no button). Publish = explicit (button click, creates PR).

5. Grammar Check (Inline PR Review Comments)

When a PR is created/updated, the grammar check CI should post feedback as inline PR review comments on the specific lines of the MDX file — similar to how Devin Review works:

  • Each grammar/style suggestion should be an inline comment on the relevant line in the PR diff
  • Suggestions should include a code suggestion block so the author can apply fixes with one click
  • This is much more actionable than a single summary comment — the author sees exactly where each issue is and can fix them in context

The current blog-grammar-check.yml posts a single summary comment. It should be updated to use the GitHub PR review API to leave inline comments instead.

6. Merging = Going Live

  • Once CI passes and the reviewer approves, the PR is merged and the article goes live on the website
  • Any MDX file present in main under apps/web/content/articles/ is considered published

7. Editing a Published Article

When a user opens a published article (already on main) and starts editing, the flow is identical to creating a new post:

  1. On first auto-save (10s debounce), a new branch is created: blog/{slug}
  2. The current content from main is committed to that branch with the user's changes
  3. From this point on, the normal auto-save / publish flow applies

This applies regardless of how the article was previously published — whether the old branch/PR was merged and deleted, or the PR was closed. There is no attempt to reuse or reopen old branches or PRs. Every editing session on a published article starts fresh with a new branch.

9. Rebasing / Main Branch Drift

While a draft branch is open, main may have moved forward with other changes. This needs consideration:

  • The draft branch may fall behind main
  • Before creating or merging a PR, the branch may need to be rebased or merged with the latest main
  • This is an open question — the exact strategy (auto-rebase, merge commit, manual) is TBD and left for implementation to decide

Media Library

The Media Library (/admin/media) is a wrapper around Supabase Storage, functioning as a visual asset manager (like Google Drive for images).

Target Users

  • Marketer (Harshika): Organizes assets, ensures posts have the right images, manages cover images
  • Designer: Uploads and organizes visual assets in the media library

Core Features

All of these need to work reliably (some are currently broken):

  • Folder management: Create folders, navigate with breadcrumbs (currently broken — creating folders does not work)
  • File upload: Drag-and-drop file upload into current folder
  • Drag-and-drop move: Move files into folders by dragging them
  • Multi-select: Hold Shift or Cmd to select multiple items, then batch move, delete, or download
  • Context menu: Right-click on individual files for rename, copy path, download, delete
  • Search and filtering: Search by filename, filter by file type
  • Folder navigation: Tree view sidebar with expandable folder hierarchy

Integration with Editor

The key integration point: the editor toolbar has an image picker that opens the media library, allowing the user to:

  1. Browse/search available images from the full media library
  2. Select an image (for inline content or cover image)
  3. Insert it directly into the editor content

This is implemented via the MediaSelectorModal component. The experience should be seamless — the user should not have to leave the editor to find and insert images.


Technical Architecture

Key Files

Component Path
Articles UI apps/web/src/routes/admin/collections/index.tsx
Media Library UI apps/web/src/routes/admin/media/index.tsx
Admin Layout apps/web/src/routes/admin/route.tsx
GitHub Content Functions apps/web/src/functions/github-content.ts
Save API apps/web/src/routes/api/admin/content/save.ts
Publish API apps/web/src/routes/api/admin/content/publish.ts
List Drafts API apps/web/src/routes/api/admin/content/list-drafts.ts
Grammar Check Workflow .github/workflows/blog-grammar-check.yml
Media Selector Modal apps/web/src/components/admin/media-selector-modal.tsx

Branch & Git Operations (via GitHub API)

All git operations are performed via the GitHub REST API (not local git). Key functions in github-content.ts:

  • generateBranchName(slug) — Returns blog/{slug}
  • createBranch(branchName) — Creates branch from main
  • createContentFileOnBranch(folder, filename, content, branch) — Commits the new MDX file directly on the branch (file does not exist on main)
  • updateContentFileOnBranch(filePath, content, branch) — Commits updated content to branch (used by auto-save)
  • savePublishedArticleToBranch(filePath, content, metadata) — Creates branch + commits for published article edits
  • publishArticle(filePath, branch, metadata) — Creates PR to main and assigns reviewer
  • createPullRequest(head, base, title, body) — Creates or reuses existing PR
  • listBlogBranches() — Lists all blog/* branches (for draft discovery)
  • getFileContentFromBranch(filePath, branch) — Reads file content from a specific branch
  • findExistingEditPR(slug) — Checks if an open PR already exists for this article

Auto-Save Flow (10s Debounce)

Editor changes → reset 10s debounce timer
  → User stops editing for 10s → POST /api/admin/content/save
    → updateContentFileOnBranch() — commit to existing draft branch
  → UI shows auto-save indicator: "Saving..." → "Saved"

Branch Sync Flow (External Edits)

On editor focus / periodic poll (e.g. every 30s):
  → GET /api/admin/content/get-branch-file?branch=blog/{slug}
  → Compare remote content SHA with last-known SHA
  → If different: update editor content with latest from branch
  → This ensures external commits (from IDE, CLI, etc.) are reflected in the editor

Publish Flow

User clicks Publish →
  1. If unsaved changes exist: immediately commit to branch (flush debounce)
  2. POST /api/admin/content/publish
    → Create PR via publishArticle()
    → Assign reviewer (harshikaalagh-netizen)
    → Return PR URL to display in editor banner

New Post Creation Flow

User enters slug → POST /api/admin/content/create
  → createContentFileOnBranch(folder, filename, defaultContent, branchName)
    → createBranch("blog/{slug}") from main
    → Commit {slug}.mdx with default frontmatter ON THE BRANCH (not main)
  → File only exists on blog/{slug} branch
  → Discovered as draft via listBlogBranches()

Editing a Published Article (Same as New Post)

User opens a published article (on main) and starts editing →
  → On first auto-save trigger (10s debounce):
    → Create new branch blog/{slug}
    → Commit content with user's changes to the new branch
    → From here, identical to the normal draft auto-save / publish flow
  → No reuse of old branches or PRs — always a fresh branch

Grammar Check Flow (Inline Review)

PR created/updated on blog/* branch →
  blog-grammar-check.yml triggers →
    → Parse changed MDX files
    → Run AI grammar/style analysis per line
    → Post inline PR review comments on specific lines using GitHub PR Review API
    → Each suggestion includes a code suggestion block for one-click apply

Reference: Devin Review inline comments example

Image Handling

When saving content that contains pasted/base64 images:

  1. Extract base64 images from markdown content
  2. Upload to Supabase storage under articles/{slug}/
  3. Replace base64 data URIs with public Supabase URLs in the content
  4. Then commit the cleaned content

UI Changes

  • Remove Save button — replace with auto-save indicator (e.g. "Saving...", "Saved", "Unsaved changes")
  • Add Publish button — only action button; flushes pending changes then creates PR

What Needs Fixing

Articles / Editor

  • New post creation: Verify branch creation + MDX file commit on branch works end-to-end (file should only exist on the branch, not on main)
  • Auto-save: Change from 60s interval to 10-second debounce (save after 10s of inactivity); remove Save button and show auto-save indicator instead
  • Branch sync: Add polling/focus-check to detect external commits on the branch and update the editor content automatically
  • Editing published articles: When a user edits a published article, create a new branch on first auto-save (identical to new post flow — no reuse of old branches/PRs)
  • Rebasing / main drift: TBD — decide strategy for keeping draft branches up-to-date with main
  • Publish flow: Add Publish button that flushes pending changes + creates a non-draft PR ready to merge
  • Grammar check: Update blog-grammar-check.yml to post inline PR review comments on specific lines (like Devin Review) instead of a single summary comment
  • Editor ↔ Media Library integration: Ensure the image picker in the editor toolbar works and can pull from the media library
  • Draft listing: Verify list-drafts correctly discovers all blog/* branches and shows them in the sidebar

Media Library

  • Folder creation: Fix — currently broken, cannot create folders
  • Drag-and-drop move: Files should be draggable into folders
  • Multi-select: Shift/Cmd click to select multiple items, then batch move/delete/download
  • Seamless editor integration: MediaSelectorModal should provide a smooth experience for picking images from the library while editing

Metadata

Metadata

Assignees

Projects

Status

Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions