From e81c814bc23eea6ef59bb3d40edb4ecf317ce8b0 Mon Sep 17 00:00:00 2001 From: Wayne Sutton Date: Tue, 30 Dec 2025 15:26:59 -0800 Subject: [PATCH] fix: post mutation --- convex/posts.ts | 4 + public/raw/changelog.md | 14 +- public/raw/docs.md | 3 + public/raw/how-i-added-workos-with-cursor.md | 190 +++++++++++++++++++ public/raw/index.md | 6 +- public/raw/setup-guide.md | 1 + 6 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 public/raw/how-i-added-workos-with-cursor.md diff --git a/convex/posts.ts b/convex/posts.ts index 70228e0..af47204 100644 --- a/convex/posts.ts +++ b/convex/posts.ts @@ -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++; diff --git a/public/raw/changelog.md b/public/raw/changelog.md index 41bdff0..2f0ddc2 100644 --- a/public/raw/changelog.md +++ b/public/raw/changelog.md @@ -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 diff --git a/public/raw/docs.md b/public/raw/docs.md index f33c7c2..2c39ea5 100644 --- a/public/raw/docs.md +++ b/public/raw/docs.md @@ -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. diff --git a/public/raw/how-i-added-workos-with-cursor.md b/public/raw/how-i-added-workos-with-cursor.md new file mode 100644 index 0000000..e2c1987 --- /dev/null +++ b/public/raw/how-i-added-workos-with-cursor.md @@ -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 \ No newline at end of file diff --git a/public/raw/index.md b/public/raw/index.md index 9bc0a6e..2d9f2a2 100644 --- a/public/raw/index.md +++ b/public/raw/index.md @@ -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` diff --git a/public/raw/setup-guide.md b/public/raw/setup-guide.md index 38b58f0..3d38a8d 100644 --- a/public/raw/setup-guide.md +++ b/public/raw/setup-guide.md @@ -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