-
Notifications
You must be signed in to change notification settings - Fork 537
Description
Overview
The Content Admin (/admin) provides content management for the Hyprnote website. It consists of two main sections:
- Articles (
/admin/collections) — Create, edit, and publish blog posts - 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:
- User enters a slug (e.g.
testing) - System creates a new branch:
blog/testing - System commits the actual MDX file (
testing.mdx) on that branch — the file only exists on the branch, not onmain - The file is created with default frontmatter:
--- meta_title: "" display_title: "" meta_description: "" author: "John Jeong" featured: false category: "" date: "2026-02-12" ---
- 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 theblog/{slug}branch. It does NOT exist onmainuntil 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:
- If there are unsaved changes (within the 10s debounce window), they are immediately committed to the branch first
- A PR is created from the draft branch to
main, ready to be merged - The PR is assigned to
harshikaalagh-netizenfor review - A banner appears in the editor linking to the PR
- The
blog-grammar-check.ymlGitHub 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
mainunderapps/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:
- On first auto-save (10s debounce), a new branch is created:
blog/{slug} - The current content from
mainis committed to that branch with the user's changes - 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:
- Browse/search available images from the full media library
- Select an image (for inline content or cover image)
- 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)— Returnsblog/{slug}createBranch(branchName)— Creates branch frommaincreateContentFileOnBranch(folder, filename, content, branch)— Commits the new MDX file directly on the branch (file does not exist onmain)updateContentFileOnBranch(filePath, content, branch)— Commits updated content to branch (used by auto-save)savePublishedArticleToBranch(filePath, content, metadata)— Creates branch + commits for published article editspublishArticle(filePath, branch, metadata)— Creates PR tomainand assigns reviewercreatePullRequest(head, base, title, body)— Creates or reuses existing PRlistBlogBranches()— Lists allblog/*branches (for draft discovery)getFileContentFromBranch(filePath, branch)— Reads file content from a specific branchfindExistingEditPR(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:
- Extract base64 images from markdown content
- Upload to Supabase storage under
articles/{slug}/ - Replace base64 data URIs with public Supabase URLs in the content
- 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.ymlto 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-draftscorrectly discovers allblog/*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:
MediaSelectorModalshould provide a smooth experience for picking images from the library while editing
Metadata
Metadata
Assignees
Type
Projects
Status