fix: post mutation

This commit is contained in:
Wayne Sutton
2025-12-30 15:26:59 -08:00
parent ac0dfab784
commit e81c814bc2
6 changed files with 215 additions and 3 deletions

View File

@@ -383,6 +383,7 @@ export const syncPosts = internalMutation({
blogFeatured: v.optional(v.boolean()),
newsletter: v.optional(v.boolean()),
contactForm: v.optional(v.boolean()),
unlisted: v.optional(v.boolean()),
}),
),
},
@@ -433,6 +434,7 @@ export const syncPosts = internalMutation({
blogFeatured: post.blogFeatured,
newsletter: post.newsletter,
contactForm: post.contactForm,
unlisted: post.unlisted,
lastSyncedAt: now,
});
updated++;
@@ -487,6 +489,7 @@ export const syncPostsPublic = mutation({
blogFeatured: v.optional(v.boolean()),
newsletter: v.optional(v.boolean()),
contactForm: v.optional(v.boolean()),
unlisted: v.optional(v.boolean()),
}),
),
},
@@ -537,6 +540,7 @@ export const syncPostsPublic = mutation({
blogFeatured: post.blogFeatured,
newsletter: post.newsletter,
contactForm: post.contactForm,
unlisted: post.unlisted,
lastSyncedAt: now,
});
updated++;

View File

@@ -27,14 +27,26 @@ Released December 30, 2025
- Automated CLAUDE.md updates via sync-discovery-files.ts
- CLAUDE.md status comment updated during `npm run sync:discovery`
- Includes current site name, post count, page count, and last updated timestamp
- Unlisted posts feature
- New `unlisted` frontmatter field for blog posts
- Set `unlisted: true` to hide posts from listings while keeping them accessible via direct link
- Unlisted posts are excluded from: blog listings, featured sections, tag pages, search results, and related posts
- Posts remain accessible via direct URL (e.g., `/blog/post-slug`)
- Useful for draft posts, private content, or posts you want to share via direct link only
**Technical details:**
- New file: `CLAUDE.md` in project root
- New directory: `.claude/skills/` with three markdown files
- Updated: `scripts/sync-discovery-files.ts` to update CLAUDE.md alongside AGENTS.md and llms.txt
- Updated: `convex/schema.ts` - Added `unlisted` optional boolean field to posts table
- Updated: `convex/posts.ts` - All listing queries filter out unlisted posts
- Updated: `convex/search.ts` - Search excludes unlisted posts from results
- Updated: `scripts/sync-posts.ts` - Added `unlisted` to interfaces and parsing logic
- Updated: `src/pages/Write.tsx` - Added `unlisted` to POST_FIELDS frontmatter reference
- Updated documentation: `.claude/skills/frontmatter.md`, `content/pages/docs.md`, `content/blog/setup-guide.md`, `files.md`
Updated files: `CLAUDE.md`, `.claude/skills/frontmatter.md`, `.claude/skills/convex.md`, `.claude/skills/sync.md`, `scripts/sync-discovery-files.ts`, `files.md`, `changelog.md`, `TASK.md`
Updated files: `CLAUDE.md`, `.claude/skills/frontmatter.md`, `.claude/skills/convex.md`, `.claude/skills/sync.md`, `scripts/sync-discovery-files.ts`, `convex/schema.ts`, `convex/posts.ts`, `convex/search.ts`, `scripts/sync-posts.ts`, `src/pages/Write.tsx`, `files.md`, `changelog.md`, `content/pages/changelog-page.md`
## v2.0.0

View File

@@ -125,6 +125,7 @@ Content here...
| `blogFeatured` | No | Show as featured on blog page (first becomes hero, rest in 2-column row) |
| `newsletter` | No | Override newsletter signup display (`true` to show, `false` to hide) |
| `contactForm` | No | Enable contact form on this post |
| `unlisted` | No | Hide from listings but allow direct access via slug. Set `true` to hide from blog listings, featured sections, tag pages, search results, and related posts. Post remains accessible via direct link. |
| `showImageAtTop` | No | Set `true` to display the `image` field at the top of the post above the header (default: `false`) |
### Static pages
@@ -169,6 +170,8 @@ Content here...
**Hide pages from navigation:** Set `showInNav: false` to keep a page published and accessible via direct URL, but hidden from the navigation menu. Pages with `showInNav: false` remain searchable and available via API endpoints. Useful for pages you want to link directly but not show in the main nav.
**Unlisted posts:** Set `unlisted: true` to hide a blog post from all listings while keeping it accessible via direct link. Unlisted posts are excluded from: blog listings (`/blog` page), featured sections (homepage), tag pages (`/tags/[tag]`), search results (Command+K), and related posts. The post remains accessible via direct URL (e.g., `/blog/post-slug`). Useful for draft posts, private content, or posts you want to share via direct link only. Note: `unlisted` only works for blog posts, not pages.
**Show image at top:** Add `showImageAtTop: true` to display the `image` field at the top of the post/page above the header. Default behavior: if `showImageAtTop` is not set or `false`, image only used for Open Graph previews and featured card thumbnails.
**Image lightbox:** Images in blog posts and pages automatically open in a full-screen lightbox when clicked (if enabled in `siteConfig.imageLightbox.enabled`). This allows readers to view images at full size. The lightbox can be closed by clicking outside the image, pressing Escape, or clicking the close button.

View File

@@ -0,0 +1,190 @@
# How I added WorkOS to my Convex app with Cursor
> A timeline of adding WorkOS AuthKit authentication to my markdown blog dashboard using Cursor, prompt engineering, and vibe coding. From PRD import to published feature.
---
Type: post
Date: 2025-12-30
Reading time: 8 min read
Tags: cursor, workos, convex, prompt-engineering, ai-coding
---
# How I added WorkOS to my Convex app with Cursor
I added WorkOS AuthKit authentication to my markdown blog dashboard using Cursor, prompt engineering, and what I call vibe coding. Here's the timeline from start to published.
## The goal
Add optional WorkOS authentication to the `/dashboard` page. The dashboard should work with or without WorkOS configured. When authentication is enabled, users log in before accessing the dashboard.
## Timeline: start to published
### Step 1: Updated docs and cursor rules
I started by updating `content/pages/docs.md` with a dashboard section explaining how it works. Then I updated my Cursor rules to include WorkOS documentation patterns and Convex AuthKit integration guidelines.
The docs update gave me a clear picture of what I wanted to build. The cursor rules helped Cursor understand the patterns I use for authentication in Convex apps.
### Step 2: Imported PRD from Claude
I had a conversation with Claude about adding WorkOS to a Convex app. Claude generated a step-by-step PRD that covered:
- WorkOS account setup
- Environment variable configuration
- Convex auth configuration
- React component structure
- Route handling
I imported this PRD into my project at `prds/workos-authkit-dashboard-guide.md`. It became the blueprint for the entire feature.
### Step 3: Created a plan in Cursor
I opened Cursor and asked it to create a plan based on the PRD. Cursor analyzed the PRD and generated a structured plan file at `.cursor/plans/workos_setup_9603c983.plan.md`.
The plan broke down the work into specific tasks:
- Create Callback.tsx
- Add callback route to App.tsx
- Update Dashboard.tsx with auth protection
- Test the authentication flow
Each task had a clear status and description. This gave me a roadmap I could follow step by step.
### Step 4: Updated environment variables
I added WorkOS environment variables to `.env.local`:
```env
VITE_WORKOS_CLIENT_ID=client_01XXXXXXXXXXXXXXXXX
VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback
```
I also added `WORKOS_CLIENT_ID` to my Convex environment variables through the Convex dashboard. These variables connect the frontend and backend to WorkOS.
### Step 5: Cursor and Opus built the app
I started implementing the plan with Cursor. I asked it to create the Callback component first. Cursor generated `src/pages/Callback.tsx` with proper WorkOS auth handling.
Then I asked Cursor to update `src/App.tsx` to add the callback route. It added the route correctly, matching the existing route patterns.
For the Dashboard component, I asked Cursor to add authentication protection. It wrapped the dashboard content with `Authenticated`, `Unauthenticated`, and `AuthLoading` components from Convex React.
### Step 6: Debugged routes for dashboard page
The dashboard needed to work in two modes:
1. With WorkOS configured and `requireAuth: true` - requires login
2. Without WorkOS or with `requireAuth: false` - open access
I asked Cursor to implement conditional authentication. It created a utility function `isWorkOSConfigured()` that checks if WorkOS environment variables are set.
The Dashboard component now checks:
- If dashboard is disabled, show disabled message
- If auth is required but WorkOS isn't configured, show setup instructions
- If WorkOS isn't configured and auth isn't required, show dashboard directly
- If WorkOS is configured, use the auth flow
This conditional logic ensures the dashboard works whether WorkOS is set up or not.
### Step 7: Frontmatter integration
I wanted the dashboard authentication to be configurable via `siteConfig.ts`. I added a `dashboard` configuration object:
```typescript
dashboard: {
enabled: true,
requireAuth: false, // Set to true to require WorkOS authentication
},
```
Cursor helped me integrate this configuration into the Dashboard component. The component reads `siteConfig.dashboard.enabled` and `siteConfig.dashboard.requireAuth` to determine behavior.
### Step 8: Published and documented
After testing locally, I:
1. Deployed the changes to production
2. Updated `changelog.md` with the new feature
3. Updated `content/pages/changelog-page.md` with release notes
4. Created a blog post: "How to setup WorkOS"
5. Updated `files.md` with new file descriptions
The feature went live on December 29, 2025 as part of v1.45.0.
## What I learned about prompt engineering
### Start with documentation
Updating docs first helped me clarify what I wanted to build. When I asked Cursor to implement features, it had context from the docs to understand the patterns.
### Use PRDs as blueprints
The PRD from Claude became the single source of truth. Every implementation step referenced the PRD. This kept the work focused and prevented scope creep.
### Break work into small tasks
The plan file broke the work into specific, actionable tasks. Each task was small enough that Cursor could complete it in one go. This made progress visible and debugging easier.
### Iterate on prompts
When Cursor didn't get something right, I refined my prompts. Instead of "add authentication," I said "add WorkOS authentication that works with or without WorkOS configured, checking siteConfig.dashboard.requireAuth."
Specific prompts led to better results.
### Trust but verify
Cursor generated working code, but I tested each change. The conditional authentication logic needed manual verification to ensure it handled all cases correctly.
## The vibe coding experience
Vibe coding means working with AI tools in a flow state. You describe what you want, the AI generates code, you test it, and you iterate.
With Cursor, this felt natural:
1. I described the feature
2. Cursor generated the code
3. I tested it locally
4. I asked for refinements
5. We repeated until it worked
The back-and-forth felt like pair programming with a very fast partner who never gets tired.
## Key files created
- `src/pages/Callback.tsx` - Handles OAuth callback
- `src/utils/workos.ts` - WorkOS configuration utility
- `convex/auth.config.ts` - Convex auth configuration
- `prds/workos-authkit-dashboard-guide.md` - Step-by-step PRD
- `.cursor/plans/workos_setup_9603c983.plan.md` - Implementation plan
## Key files modified
- `src/main.tsx` - Added conditional WorkOS providers
- `src/App.tsx` - Added callback route handling
- `src/pages/Dashboard.tsx` - Added optional authentication
- `src/config/siteConfig.ts` - Added dashboard configuration
- `content/pages/docs.md` - Added dashboard documentation
## Result
The dashboard now supports optional WorkOS authentication. Users can:
- Use the dashboard without WorkOS (open access)
- Enable WorkOS authentication via `siteConfig.dashboard.requireAuth`
- See setup instructions if auth is required but WorkOS isn't configured
The implementation is clean, type-safe, and follows Convex best practices. It works whether WorkOS is configured or not.
## Takeaways
1. **Documentation first** - Writing docs clarifies requirements
2. **PRDs as blueprints** - Import PRDs to guide implementation
3. **Small tasks** - Break work into specific, actionable items
4. **Specific prompts** - Detailed prompts produce better code
5. **Test everything** - Verify AI-generated code works correctly
6. **Iterate quickly** - Use AI tools to move fast and refine
Adding WorkOS to my Convex app took a few hours from start to published. Most of that time was testing and refining. The actual coding was fast thanks to Cursor and good prompt engineering.
The [How to setup WorkOS](https://www.markdown.fast/how-to-setup-workos) is the setup guide covers everything you need to get started

View File

@@ -2,8 +2,10 @@
This is the homepage index of all published content.
## Blog Posts (17)
## Blog Posts (18)
- **[How I added WorkOS to my Convex app with Cursor](/raw/how-i-added-workos-with-cursor.md)** - A timeline of adding WorkOS AuthKit authentication to my markdown blog dashboard using Cursor, prompt engineering, and vibe coding. From PRD import to published feature.
- Date: 2025-12-30 | Reading time: 8 min read | Tags: cursor, workos, convex, prompt-engineering, ai-coding
- **[How to setup WorkOS with Markdown Sync](/raw/how-to-setup-workos.md)** - Step-by-step guide to configure WorkOS AuthKit authentication for your markdown blog dashboard. WorkOS is optional and can be enabled in siteConfig.ts.
- Date: 2025-12-29 | Reading time: 10 min read | Tags: workos, authentication, tutorial, dashboard
- **[How to use the Markdown sync dashboard](/raw/how-to-use-the-markdown-sync-dashboard.md)** - Learn how to use the dashboard at /dashboard to manage content, configure your site, and sync markdown files without leaving your browser.
@@ -51,6 +53,6 @@ This is the homepage index of all published content.
---
**Total Content:** 17 posts, 7 pages
**Total Content:** 18 posts, 7 pages
All content is available as raw markdown files at `/raw/{slug}.md`

View File

@@ -345,6 +345,7 @@ Your markdown content here...
| `authorName` | No | Author display name shown next to date |
| `authorImage` | No | Round author avatar image URL |
| `rightSidebar` | No | Enable right sidebar with CopyPageDropdown (opt-in, requires explicit `true`) |
| `unlisted` | No | Hide from listings but allow direct access via slug. Set `true` to hide from blog listings, featured sections, tag pages, search results, and related posts. Post remains accessible via direct link. |
### How Frontmatter Works