diff --git a/.cursor/plans/mcp_server_implementation_012bbe93.plan.md b/.cursor/plans/mcp_server_implementation_012bbe93.plan.md
new file mode 100644
index 0000000..a050e9c
--- /dev/null
+++ b/.cursor/plans/mcp_server_implementation_012bbe93.plan.md
@@ -0,0 +1,318 @@
+---
+name: MCP Server Implementation
+overview: Add Model Context Protocol (MCP) server capability to enable AI tools like Cursor to read blog posts, pages, and homepage content from the Convex database. The server will be read-only, secure, and expose tools that wrap existing Convex queries.
+todos:
+ - id: add-mcp-dependency
+ content: Add @modelcontextprotocol/sdk to package.json dependencies and add mcp script
+ status: pending
+ - id: create-mcp-server
+ content: Create scripts/mcp-server.ts with MCP protocol implementation, stdio transport, and 7 tools (list_posts, get_post, list_pages, get_page, get_homepage, search_content, export_all)
+ status: pending
+ dependencies:
+ - add-mcp-dependency
+ - id: create-blog-post
+ content: Create content/blog/how-to-use-mcp-server.md with complete usage instructions, Cursor config example, and troubleshooting
+ status: pending
+ - id: update-docs
+ content: Add MCP Server section to content/pages/docs.md with overview, quick start, and tool reference
+ status: pending
+ dependencies:
+ - create-mcp-server
+ - id: update-setup-guide
+ content: Add MCP server mention to content/blog/setup-guide.md with link to detailed guide
+ status: pending
+ dependencies:
+ - create-blog-post
+ - id: update-siteconfig
+ content: Review and optionally add minimal MCP config to src/config/siteConfig.ts (if needed for future extensibility)
+ status: pending
+ - id: update-files-md
+ content: Add mcp-server.ts entry to files.md under Scripts section
+ status: pending
+ dependencies:
+ - create-mcp-server
+ - id: update-changelogs
+ content: Add MCP feature entry to changelog.md and content/pages/changelog-page.md
+ status: pending
+ dependencies:
+ - create-mcp-server
+ - create-blog-post
+ - id: update-readme
+ content: Add MCP Server section to README.md with brief description and quick start
+ status: pending
+ dependencies:
+ - create-mcp-server
+---
+
+# MCP Server Implementation Plan
+
+## Overview
+
+Add MCP (Model Context Protocol) server capability to enable AI tools like Cursor to access blog content. The server runs locally, connects to Convex via HTTP client, and exposes read-only tools for accessing posts, pages, homepage data, and search functionality.
+
+## Architecture
+
+```mermaid
+flowchart LR
+ subgraph local[Local Machine]
+ Cursor[Cursor/AI Client]
+ MCPServer[MCP Server
scripts/mcp-server.ts]
+ end
+
+ subgraph cloud[Convex Cloud]
+ ConvexDB[(Convex Database)]
+ Queries[Convex Queries
posts.ts, pages.ts, search.ts]
+ end
+
+ Cursor -->|"MCP Protocol
(stdio)"| MCPServer
+ MCPServer -->|"ConvexHttpClient
(HTTP)"| Queries
+ Queries --> ConvexDB
+```
+
+## Implementation Steps
+
+### 1. Add MCP SDK Dependency
+
+**File:** `package.json`
+
+- Add `@modelcontextprotocol/sdk` to dependencies
+- Version: Use latest stable version (check npm registry)
+- Add `mcp` script: `"mcp": "tsx scripts/mcp-server.ts"`
+
+### 2. Create MCP Server Script
+
+**New File:** `scripts/mcp-server.ts`
+
+**Key Components:**
+
+- Import MCP SDK: `@modelcontextprotocol/sdk`
+- Use `ConvexHttpClient` pattern from `sync-posts.ts`
+- Load environment: Read `VITE_CONVEX_URL` from `.env.local` (development) or `.env.production.local` (production)
+- Implement stdio transport for MCP protocol
+- Use `Server` class from MCP SDK
+- Log to stderr (not stdout) to avoid corrupting JSON-RPC messages
+
+**MCP Tools to Implement:**
+
+1. **`list_posts`** - Get all published posts
+ - Calls: `api.posts.getAllPosts`
+ - Returns: Array of post metadata (no content)
+
+2. **`get_post`** - Get single post by slug with full content
+ - Args: `slug: string`
+ - Calls: `api.posts.getPostBySlug`
+ - Returns: Full post object with content
+
+3. **`list_pages`** - Get all published pages
+ - Calls: `api.pages.getAllPages`
+ - Returns: Array of page metadata (no content)
+
+4. **`get_page`** - Get single page by slug with full content
+ - Args: `slug: string`
+ - Calls: `api.pages.getPageBySlug`
+ - Returns: Full page object with content
+
+5. **`get_homepage`** - Get homepage structure and featured content
+ - Calls: `api.posts.getFeaturedPosts`, `api.pages.getFeaturedPages`, `api.posts.getAllPosts` (limited)
+ - Returns: Combined homepage data structure
+
+6. **`search_content`** - Full text search across posts and pages
+ - Args: `query: string`
+ - Calls: `api.search.search`
+ - Returns: Search results with snippets
+
+7. **`export_all`** - Batch export all posts and pages with full content
+ - Calls: Multiple queries to get all content
+ - Returns: Complete content export
+
+**Error Handling:**
+
+- Validate Convex URL is set
+- Handle Convex query errors gracefully
+- Return user-friendly error messages
+- Never expose internal errors to MCP clients
+
+**Security:**
+
+- Read-only access (no mutations)
+- Uses existing public Convex queries
+- No authentication needed (public content)
+- Each user runs their own server locally
+
+### 3. Create Blog Post with Usage Instructions
+
+**New File:** `content/blog/how-to-use-mcp-server.md`
+
+**Content Sections:**
+
+- What is MCP and why use it
+- Prerequisites (Node.js, Convex deployment)
+- Installation steps
+- Starting the MCP server
+- Cursor configuration example
+- Available tools reference
+- Troubleshooting common issues
+- Security considerations
+
+**Frontmatter:**
+
+```yaml
+title: "How to Use the MCP Server"
+description: "Guide to setting up and using the Model Context Protocol server with Cursor and other AI tools"
+date: "2025-12-28"
+slug: "how-to-use-mcp-server"
+published: true
+tags: ["mcp", "cursor", "ai", "tutorial"]
+```
+
+### 4. Update Documentation Files
+
+**File:** `content/pages/docs.md`
+
+Add new section "MCP Server" after "API Endpoints" section:
+
+- Overview of MCP server capability
+- Quick start: installation and running
+- Available tools list with descriptions
+- Cursor configuration example
+- Link to detailed blog post
+
+**File:** `content/blog/setup-guide.md`
+
+Add subsection under "Next Steps" or create new section:
+
+- Brief mention of MCP server capability
+- Link to detailed guide
+- Quick setup command reference
+
+**File:** `src/config/siteConfig.ts`
+
+Add optional MCP configuration (if needed for future features):
+
+- Consider adding `mcpServer` config object for future extensibility
+- For now, keep it simple - server reads from environment variables
+
+### 5. Update Reference Files
+
+**File:** `files.md`
+
+Add entry under "Scripts (`scripts/`)" section:
+
+```markdown
+| `mcp-server.ts` | MCP server for AI tool integration (Cursor, etc.). Exposes read-only tools for accessing blog posts, pages, and search. Uses ConvexHttpClient to connect to Convex deployment. |
+```
+
+**File:** `changelog.md`
+
+Add entry at top:
+
+```markdown
+## [Unreleased]
+
+### Added
+
+- MCP (Model Context Protocol) server for AI tool integration
+- Read-only access to blog posts, pages, homepage, and search
+- Local server connects to Convex via HTTP client
+- Exposes 7 tools: list_posts, get_post, list_pages, get_page, get_homepage, search_content, export_all
+- Cursor configuration support
+- Blog post: "How to Use the MCP Server"
+```
+
+**File:** `content/pages/changelog-page.md`
+
+Add same entry at top (before v1.38.0)
+
+### 6. Update README.md
+
+**File:** `README.md`
+
+Add section "MCP Server" after "Fork Configuration" or in appropriate location:
+
+- Brief description
+- Quick start command
+- Link to detailed documentation
+- Note about read-only access
+
+## Technical Details
+
+### Environment Variables
+
+The MCP server reads `VITE_CONVEX_URL` from:
+
+- `.env.local` (development)
+- `.env.production.local` (production)
+
+Users can specify via environment variable or config file.
+
+### MCP Server Pattern
+
+Follow the TypeScript MCP server pattern from official docs:
+
+- Use `Server` class from `@modelcontextprotocol/sdk`
+- Implement stdio transport
+- Define tools with proper schemas
+- Handle errors gracefully
+- Log to stderr only
+
+### Convex Query Usage
+
+All tools use existing public queries:
+
+- `api.posts.getAllPosts`
+- `api.posts.getPostBySlug`
+- `api.posts.getFeaturedPosts`
+- `api.pages.getAllPages`
+- `api.pages.getPageBySlug`
+- `api.pages.getFeaturedPages`
+- `api.search.search`
+
+No new Convex functions needed - leverages existing API.
+
+## Security Considerations
+
+1. **Read-only access**: Only queries, no mutations exposed
+2. **Public content only**: Uses same queries as public website
+3. **Local execution**: Server runs on user's machine
+4. **No authentication**: Blog content is public by design
+5. **Error handling**: Never expose internal errors or stack traces
+
+## Testing Checklist
+
+- [ ] MCP server starts without errors
+- [ ] All 7 tools respond correctly
+- [ ] Convex connection works (dev and prod)
+- [ ] Error handling works for missing Convex URL
+- [ ] Error handling works for invalid queries
+- [ ] Cursor can connect and use tools
+- [ ] Documentation is clear and complete
+- [ ] Blog post provides step-by-step instructions
+
+## Files to Create
+
+1. `scripts/mcp-server.ts` - Main MCP server implementation
+2. `content/blog/how-to-use-mcp-server.md` - Usage guide blog post
+
+## Files to Update
+
+1. `package.json` - Add dependency and script
+2. `content/pages/docs.md` - Add MCP Server section
+3. `content/blog/setup-guide.md` - Add MCP mention
+4. `src/config/siteConfig.ts` - Optional future config (minimal changes)
+5. `files.md` - Add script description
+6. `changelog.md` - Add feature entry
+7. `content/pages/changelog-page.md` - Add feature entry
+8. `README.md` - Add MCP Server section
+
+## Dependencies
+
+- `@modelcontextprotocol/sdk` - MCP SDK for TypeScript
+- Existing: `convex`, `dotenv`, `tsx` (already in package.json)
+
+## Notes
+
+- No newsletter or email features exposed (as requested)
+- Server is read-only for security
+- Uses existing Convex queries (no backend changes needed)
+- Follows same pattern as `sync-posts.ts` for Convex connection
+- Compatible with Cursor and other MCP clients
diff --git a/FORK_CONFIG.md b/FORK_CONFIG.md
index 0634fa7..327eee5 100644
--- a/FORK_CONFIG.md
+++ b/FORK_CONFIG.md
@@ -76,19 +76,19 @@ Edit each file individually following the guide below.
### Files to Update
-| File | What to Update |
-| ----------------------------------- | -------------------------------------------- |
+| File | What to Update |
+| ----------------------------------- | ------------------------------------------------------------ |
| `src/config/siteConfig.ts` | Site name, bio, GitHub username, gitHubRepo config, features |
-| `src/pages/Home.tsx` | Intro paragraph, footer links |
-| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME` constants |
-| `convex/http.ts` | `SITE_URL`, `SITE_NAME` constants |
-| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` |
-| `index.html` | Meta tags, JSON-LD, page title |
-| `public/llms.txt` | Site info, GitHub link |
-| `public/robots.txt` | Sitemap URL |
-| `public/openapi.yaml` | Server URL, site name |
-| `public/.well-known/ai-plugin.json` | Plugin metadata |
-| `src/context/ThemeContext.tsx` | Default theme |
+| `src/pages/Home.tsx` | Intro paragraph, footer links |
+| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME` constants |
+| `convex/http.ts` | `SITE_URL`, `SITE_NAME` constants |
+| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` |
+| `index.html` | Meta tags, JSON-LD, page title |
+| `public/llms.txt` | Site info, GitHub link |
+| `public/robots.txt` | Sitemap URL |
+| `public/openapi.yaml` | Server URL, site name |
+| `public/.well-known/ai-plugin.json` | Plugin metadata |
+| `src/context/ThemeContext.tsx` | Default theme |
---
@@ -170,10 +170,10 @@ export const siteConfig: SiteConfig = {
// GitHub repository config (for AI service links)
// Used by ChatGPT, Claude, Perplexity "Open in AI" buttons
gitHubRepo: {
- owner: "YOURUSERNAME", // GitHub username or organization
- repo: "YOUR-REPO-NAME", // Repository name
- branch: "main", // Default branch
- contentPath: "public/raw", // Path to raw markdown files
+ owner: "YOURUSERNAME", // GitHub username or organization
+ repo: "YOUR-REPO-NAME", // Repository name
+ branch: "main", // Default branch
+ contentPath: "public/raw", // Path to raw markdown files
},
};
```
@@ -453,6 +453,7 @@ homepage: {
### Examples
**Use a static page as homepage:**
+
```typescript
homepage: {
type: "page",
@@ -462,6 +463,7 @@ homepage: {
```
**Use a blog post as homepage:**
+
```typescript
homepage: {
type: "post",
@@ -471,6 +473,7 @@ homepage: {
```
**Switch back to default homepage:**
+
```typescript
homepage: {
type: "default",
@@ -489,10 +492,10 @@ The newsletter feature integrates with AgentMail for email subscriptions and sen
Set these in the Convex dashboard:
-| Variable | Description |
-| -------- | ----------- |
-| `AGENTMAIL_API_KEY` | Your AgentMail API key |
-| `AGENTMAIL_INBOX` | Your inbox address (e.g., `newsletter@mail.agentmail.to`) |
+| Variable | Description |
+| ------------------- | --------------------------------------------------------- |
+| `AGENTMAIL_API_KEY` | Your AgentMail API key |
+| `AGENTMAIL_INBOX` | Your inbox address (e.g., `newsletter@mail.agentmail.to`) |
### In fork-config.json
@@ -567,7 +570,7 @@ Hide or show newsletter signup on specific posts using frontmatter:
```yaml
---
title: My Post
-newsletter: false # Hide newsletter signup on this post
+newsletter: false # Hide newsletter signup on this post
---
```
@@ -576,7 +579,7 @@ Or force show it even if posts default is disabled:
```yaml
---
title: Special Offer Post
-newsletter: true # Show newsletter signup on this post
+newsletter: true # Show newsletter signup on this post
---
```
@@ -598,6 +601,73 @@ npx convex run newsletter:sendPostNewsletter '{"postSlug":"setup-guide","siteUrl
View subscriber count on the `/stats` page. Subscribers are stored in the `newsletterSubscribers` table in Convex.
+### Newsletter Admin
+
+The Newsletter Admin UI at `/newsletter-admin` provides a management interface for subscribers and sending newsletters.
+
+**Configuration:**
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+newsletterAdmin: {
+ enabled: true, // Enable /newsletter-admin route
+ showInNav: false, // Hide from navigation (access via direct URL)
+},
+```
+
+**Features:**
+
+- View and search all subscribers
+- Filter by status (all, active, unsubscribed)
+- Delete subscribers
+- Send blog posts as newsletters
+- Write and send custom emails with markdown support
+- View recent newsletter sends
+- Email statistics dashboard
+
+**CLI Commands:**
+
+```bash
+# Send a blog post to all subscribers
+npm run newsletter:send
+
+# Send weekly stats summary
+npm run newsletter:send:stats
+```
+
+### Newsletter Notifications
+
+Configure developer notifications for subscriber events:
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+newsletterNotifications: {
+ enabled: true, // Global toggle for notifications
+ newSubscriberAlert: true, // Send email when new subscriber signs up
+ weeklyStatsSummary: true, // Send weekly stats summary email
+},
+```
+
+Uses `AGENTMAIL_CONTACT_EMAIL` or `AGENTMAIL_INBOX` as recipient.
+
+### Weekly Digest
+
+Automated weekly email with posts from the past 7 days:
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+weeklyDigest: {
+ enabled: true, // Global toggle for weekly digest
+ dayOfWeek: 0, // 0 = Sunday, 6 = Saturday
+ subject: "Weekly Digest", // Email subject prefix
+},
+```
+
+Runs automatically via cron job every Sunday at 9:00 AM UTC.
+
---
## Contact Form Configuration
@@ -608,10 +678,10 @@ Enable contact forms on any page or post via frontmatter. Messages are sent via
Set these in the Convex dashboard:
-| Variable | Description |
-| -------- | ----------- |
-| `AGENTMAIL_API_KEY` | Your AgentMail API key |
-| `AGENTMAIL_INBOX` | Your inbox address for sending (e.g., `newsletter@mail.agentmail.to`) |
+| Variable | Description |
+| ------------------------- | --------------------------------------------------------------------------- |
+| `AGENTMAIL_API_KEY` | Your AgentMail API key |
+| `AGENTMAIL_INBOX` | Your inbox address for sending (e.g., `newsletter@mail.agentmail.to`) |
| `AGENTMAIL_CONTACT_EMAIL` | Optional: recipient for contact form messages (defaults to AGENTMAIL_INBOX) |
### Site Config
@@ -644,6 +714,243 @@ The form includes name, email, and message fields. Submissions are stored in Con
---
+## Footer Configuration
+
+The footer component displays markdown content and can be configured globally or per-page.
+
+### In fork-config.json
+
+```json
+{
+ "footer": {
+ "enabled": true,
+ "showOnHomepage": true,
+ "showOnPosts": true,
+ "showOnPages": true,
+ "showOnBlogPage": true,
+ "defaultContent": "Built with [Convex](https://convex.dev) for real-time sync."
+ }
+}
+```
+
+### Manual Configuration
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+footer: {
+ enabled: true, // Global toggle for footer
+ showOnHomepage: true, // Show footer on homepage
+ showOnPosts: true, // Default: show footer on blog posts
+ showOnPages: true, // Default: show footer on static pages
+ showOnBlogPage: true, // Show footer on /blog page
+ defaultContent: "...", // Default markdown content
+},
+```
+
+**Frontmatter Override:**
+
+Set `showFooter: false` in post/page frontmatter to hide footer on specific pages. Set `footer: "..."` to provide custom markdown content.
+
+---
+
+## Social Footer Configuration
+
+Display social icons and copyright information below the main footer.
+
+### In fork-config.json
+
+```json
+{
+ "socialFooter": {
+ "enabled": true,
+ "showOnHomepage": true,
+ "showOnPosts": true,
+ "showOnPages": true,
+ "showOnBlogPage": true,
+ "socialLinks": [
+ {
+ "platform": "github",
+ "url": "https://github.com/yourusername/your-repo-name"
+ },
+ {
+ "platform": "twitter",
+ "url": "https://x.com/yourhandle"
+ }
+ ],
+ "copyright": {
+ "siteName": "Your Site Name",
+ "showYear": true
+ }
+ }
+}
+```
+
+### Manual Configuration
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+socialFooter: {
+ enabled: true,
+ showOnHomepage: true,
+ showOnPosts: true,
+ showOnPages: true,
+ showOnBlogPage: true,
+ socialLinks: [
+ { platform: "github", url: "https://github.com/username" },
+ { platform: "twitter", url: "https://x.com/handle" },
+ { platform: "linkedin", url: "https://linkedin.com/in/profile" },
+ ],
+ copyright: {
+ siteName: "Your Site Name",
+ showYear: true, // Auto-updates to current year
+ },
+},
+```
+
+**Supported Platforms:** github, twitter, linkedin, instagram, youtube, tiktok, discord, website
+
+**Frontmatter Override:**
+
+Set `showSocialFooter: false` in post/page frontmatter to hide social footer on specific pages.
+
+---
+
+## Right Sidebar Configuration
+
+Enable a right sidebar on posts and pages that displays CopyPageDropdown at wide viewport widths.
+
+### In fork-config.json
+
+```json
+{
+ "rightSidebar": {
+ "enabled": true,
+ "minWidth": 1135
+ }
+}
+```
+
+### Manual Configuration
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+rightSidebar: {
+ enabled: true, // Set to false to disable globally
+ minWidth: 1135, // Minimum viewport width to show sidebar
+},
+```
+
+**Frontmatter Usage:**
+
+Enable right sidebar on specific posts/pages:
+
+```yaml
+---
+title: My Post
+rightSidebar: true
+---
+```
+
+**Features:**
+
+- Right sidebar appears at 1135px+ viewport width
+- Contains CopyPageDropdown with sharing options
+- Three-column layout: left sidebar (TOC), main content, right sidebar
+- Hidden below 1135px, CopyPageDropdown returns to nav
+
+---
+
+## AI Chat Configuration
+
+Configure the AI writing assistant powered by Anthropic Claude.
+
+### In fork-config.json
+
+```json
+{
+ "aiChat": {
+ "enabledOnWritePage": false,
+ "enabledOnContent": false
+ }
+}
+```
+
+### Manual Configuration
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+aiChat: {
+ enabledOnWritePage: true, // Show AI chat toggle on /write page
+ enabledOnContent: true, // Allow AI chat on posts/pages via frontmatter
+},
+```
+
+**Environment Variables (Convex):**
+
+- `ANTHROPIC_API_KEY` (required): Your Anthropic API key
+- `CLAUDE_PROMPT_STYLE`, `CLAUDE_PROMPT_COMMUNITY`, `CLAUDE_PROMPT_RULES` (optional): Split system prompts
+- `CLAUDE_SYSTEM_PROMPT` (optional): Single system prompt fallback
+
+**Frontmatter Usage:**
+
+Enable AI chat on posts/pages:
+
+```yaml
+---
+title: My Post
+rightSidebar: true
+aiChat: true
+---
+```
+
+Requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`.
+
+---
+
+## Posts Display Configuration
+
+Control where posts appear and limit homepage display.
+
+### In fork-config.json
+
+```json
+{
+ "postsDisplay": {
+ "showOnHome": true,
+ "showOnBlogPage": true,
+ "homePostsLimit": 5,
+ "homePostsReadMore": {
+ "enabled": true,
+ "text": "Read more blog posts",
+ "link": "/blog"
+ }
+ }
+}
+```
+
+### Manual Configuration
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+postsDisplay: {
+ showOnHome: true, // Show post list on homepage
+ showOnBlogPage: true, // Show post list on /blog page
+ homePostsLimit: 5, // Limit posts on homepage (undefined = show all)
+ homePostsReadMore: {
+ enabled: true, // Show "read more" link when limited
+ text: "Read more blog posts",
+ link: "/blog",
+ },
+},
+```
+
+---
+
## AI Agent Prompt
Copy this prompt to have an AI agent apply all changes:
@@ -701,12 +1008,12 @@ Discovery files (`AGENTS.md` and `public/llms.txt`) can be automatically updated
### Commands
-| Command | Description |
-| ------- | ----------- |
-| `npm run sync:discovery` | Update discovery files with local Convex data |
-| `npm run sync:discovery:prod` | Update discovery files with production Convex data |
-| `npm run sync:all` | Sync content + discovery files together (development) |
-| `npm run sync:all:prod` | Sync content + discovery files together (production) |
+| Command | Description |
+| ----------------------------- | ----------------------------------------------------- |
+| `npm run sync:discovery` | Update discovery files with local Convex data |
+| `npm run sync:discovery:prod` | Update discovery files with production Convex data |
+| `npm run sync:all` | Sync content + discovery files together (development) |
+| `npm run sync:all:prod` | Sync content + discovery files together (production) |
### When to run
@@ -716,10 +1023,10 @@ Discovery files (`AGENTS.md` and `public/llms.txt`) can be automatically updated
### What gets updated
-| File | Updated Content |
-| ---- | --------------- |
-| `AGENTS.md` | Project overview, current status (site name, URL, post/page counts) |
-| `public/llms.txt` | Site info, total posts, latest post date, GitHub URL |
+| File | Updated Content |
+| ----------------- | ------------------------------------------------------------------- |
+| `AGENTS.md` | Project overview, current status (site name, URL, post/page counts) |
+| `public/llms.txt` | Site info, total posts, latest post date, GitHub URL |
The script reads from `siteConfig.ts` and queries Convex for live content statistics.
diff --git a/README.md b/README.md
index d415d49..41744da 100644
--- a/README.md
+++ b/README.md
@@ -74,15 +74,25 @@ Follow the step-by-step guide in `FORK_CONFIG.md` to update each file manually.
- Four theme options: Dark, Light, Tan (default), Cloud
- Real-time data with Convex
- Fully responsive design
-- Real-time analytics at `/stats`
+- Real-time analytics at `/stats` with visitor map
- Full text search with Command+K shortcut
- Featured section with list/card view toggle
-- Logo gallery with continuous marquee scroll
+- Logo gallery with continuous marquee scroll or static grid
- GitHub contributions graph with year navigation
- Static raw markdown files at `/raw/{slug}.md`
-- Dedicated blog page with configurable navigation order
+- Dedicated blog page with configurable navigation order and featured layout
- Markdown writing page at `/write` with frontmatter reference
- AI Agent chat (powered by Anthropic Claude) on Write page and optionally in right sidebar
+- Tag pages at `/tags/[tag]` with view mode toggle
+- Related posts based on shared tags
+- Footer component with markdown support and images
+- Social footer with customizable social links and copyright
+- Right sidebar for CopyPageDropdown and AI chat
+- Contact forms on any page or post
+- Newsletter subscriptions and admin UI
+- Homepage post limit with optional "read more" link
+- Blog page featured layout with hero post
+- Show image at top of posts/pages
### SEO and Discovery
@@ -116,10 +126,12 @@ The framework includes AgentMail integration for newsletter subscriptions and co
- Newsletter subscriptions and sending
- Contact forms on any post or page
-- Automated weekly digests
-- Developer notifications
-- Admin UI for subscriber management
+- Automated weekly digests (Sundays 9am UTC)
+- Developer notifications (new subscriber alerts, weekly stats summaries)
+- Admin UI for subscriber management at `/newsletter-admin`
- CLI tools for sending newsletters and stats
+- Custom email composition with markdown support
+- Email statistics dashboard
See the [AgentMail setup guide](https://www.markdown.fast/blog/how-to-use-agentmail) for configuration instructions.
@@ -474,6 +486,15 @@ A public markdown writing page at `/write` (not linked in navigation).
Access directly at `yourdomain.com/write`. Content is stored in localStorage only (not synced to database). Use it to draft posts, then copy the content to a markdown file in `content/blog/` or `content/pages/` and run `npm run sync`.
+**Features:**
+- Three-column Cursor docs-style layout
+- Content type selector (Blog Post or Page) with dynamic frontmatter templates
+- Frontmatter field reference with individual copy buttons
+- Font switcher (Serif/Sans-serif/Monospace)
+- Theme toggle matching site themes
+- Word, line, and character counts
+- localStorage persistence for content, type, and font preference
+
**AI Agent mode:** When `siteConfig.aiChat.enabledOnWritePage` is enabled, a toggle button appears in the Actions section. Clicking it replaces the textarea with the AI Agent chat interface. The page title changes to "Agent" when in chat mode. Requires `ANTHROPIC_API_KEY` environment variable in Convex.
## AI Agent Chat
@@ -494,11 +515,13 @@ Set these in [Convex Dashboard](https://dashboard.convex.dev) > Settings > Envir
**Features:**
-- Per-page chat history stored in Convex
+- Per-page chat history stored in Convex (per-session, per-context)
- Page content can be provided as context for AI responses
-- Markdown rendering for AI responses
+- Markdown rendering for AI responses with copy functionality
- User-friendly error messages when API key is not configured
- Anonymous session authentication using localStorage
+- Chat history limited to last 20 messages for efficiency
+- System prompt configurable via environment variables (split or single prompt)
## Source
diff --git a/content/blog/fork-configuration-guide.md b/content/blog/fork-configuration-guide.md
index 1f358df..4a04fe8 100644
--- a/content/blog/fork-configuration-guide.md
+++ b/content/blog/fork-configuration-guide.md
@@ -8,7 +8,7 @@ tags: ["configuration", "setup", "fork", "tutorial"]
readTime: "4 min read"
featured: true
layout: "sidebar"
-featuredOrder: 3
+featuredOrder: 2
authorName: "Markdown"
authorImage: "/images/authors/markdown.png"
image: "/images/forkconfig.png"
@@ -97,19 +97,19 @@ If you prefer to update files manually, follow the guide in `FORK_CONFIG.md`. It
The configuration script updates these files:
-| File | What changes |
-| ----------------------------------- | ----------------------------------------- |
-| `src/config/siteConfig.ts` | Site name, bio, GitHub username, features |
-| `src/pages/Home.tsx` | Intro paragraph, footer links |
-| `src/pages/Post.tsx` | SITE_URL, SITE_NAME constants |
-| `convex/http.ts` | SITE_URL, SITE_NAME constants |
-| `convex/rss.ts` | SITE_URL, SITE_TITLE, SITE_DESCRIPTION |
-| `index.html` | Meta tags, JSON-LD, page title |
-| `public/llms.txt` | Site info, GitHub link |
-| `public/robots.txt` | Sitemap URL |
-| `public/openapi.yaml` | Server URL, site name |
-| `public/.well-known/ai-plugin.json` | Plugin metadata |
-| `src/context/ThemeContext.tsx` | Default theme |
+| File | What changes |
+| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
+| `src/config/siteConfig.ts` | Site name, bio, GitHub username, features, footer, social footer, newsletter, contact form, AI chat, right sidebar |
+| `src/pages/Home.tsx` | Intro paragraph, footer links |
+| `src/pages/Post.tsx` | SITE_URL, SITE_NAME constants |
+| `convex/http.ts` | SITE_URL, SITE_NAME constants |
+| `convex/rss.ts` | SITE_URL, SITE_TITLE, SITE_DESCRIPTION |
+| `index.html` | Meta tags, JSON-LD, page title |
+| `public/llms.txt` | Site info, GitHub link |
+| `public/robots.txt` | Sitemap URL |
+| `public/openapi.yaml` | Server URL, site name |
+| `public/.well-known/ai-plugin.json` | Plugin metadata |
+| `src/context/ThemeContext.tsx` | Default theme |
## Optional settings
@@ -142,15 +142,65 @@ The JSON config file supports additional options:
},
"postsDisplay": {
"showOnHome": true,
- "showOnBlogPage": true
+ "showOnBlogPage": true,
+ "homePostsLimit": 5,
+ "homePostsReadMore": {
+ "enabled": true,
+ "text": "Read more blog posts",
+ "link": "/blog"
+ }
},
"featuredViewMode": "cards",
"showViewToggle": true,
- "theme": "tan"
+ "theme": "tan",
+ "fontFamily": "serif",
+ "rightSidebar": {
+ "enabled": true,
+ "minWidth": 1135
+ },
+ "footer": {
+ "enabled": true,
+ "showOnHomepage": true,
+ "showOnPosts": true,
+ "showOnPages": true,
+ "showOnBlogPage": true
+ },
+ "socialFooter": {
+ "enabled": true,
+ "showOnHomepage": true,
+ "showOnPosts": true,
+ "showOnPages": true,
+ "showOnBlogPage": true,
+ "socialLinks": [
+ {
+ "platform": "github",
+ "url": "https://github.com/yourusername/your-repo-name"
+ }
+ ],
+ "copyright": {
+ "siteName": "Your Site Name",
+ "showYear": true
+ }
+ },
+ "aiChat": {
+ "enabledOnWritePage": false,
+ "enabledOnContent": false
+ },
+ "newsletter": {
+ "enabled": false,
+ "signup": {
+ "home": { "enabled": false },
+ "blogPage": { "enabled": false },
+ "posts": { "enabled": false }
+ }
+ },
+ "contactForm": {
+ "enabled": false
+ }
}
```
-These are optional. If you omit them, the script uses sensible defaults.
+These are optional. If you omit them, the script uses sensible defaults. See `fork-config.json.example` for the complete schema with all available options.
## After configuring
diff --git a/content/blog/happy-holidays-2025.md b/content/blog/happy-holidays-2025.md
index 1f8e98b..c9e637c 100644
--- a/content/blog/happy-holidays-2025.md
+++ b/content/blog/happy-holidays-2025.md
@@ -6,10 +6,10 @@ slug: "happy-holidays-2025"
published: true
tags: ["updates", "community", "ai"]
readTime: "2 min read"
-featured: true
+featured: false
featuredOrder: 0
-blogFeatured: true
-aiChat: true
+blogFeatured: false
+aiChat: false
image: /images/1225-changelog.png
excerpt: "Thank you for the stars, forks, and feedback. More AI-first publishing features are coming."
authorName: "Wayne Sutton"
diff --git a/content/blog/how-to-use-agentmail.md b/content/blog/how-to-use-agentmail.md
index 3f01125..5c60f7c 100644
--- a/content/blog/how-to-use-agentmail.md
+++ b/content/blog/how-to-use-agentmail.md
@@ -4,6 +4,8 @@ description: "Complete guide to setting up AgentMail for newsletters and contact
date: "2025-12-27"
slug: "how-to-use-agentmail"
published: true
+featured: true
+featuredOrder: 3
image: /images/agentmail-blog.png
tags: ["agentmail", "newsletter", "email", "setup"]
---
diff --git a/content/blog/setup-guide.md b/content/blog/setup-guide.md
index 8789dbf..381482e 100644
--- a/content/blog/setup-guide.md
+++ b/content/blog/setup-guide.md
@@ -7,7 +7,7 @@ published: true
tags: ["convex", "netlify", "tutorial", "deployment"]
readTime: "8 min read"
featured: true
-featuredOrder: 6
+featuredOrder: 1
newsletter: true
layout: "sidebar"
image: "/images/setupguide.png"
@@ -75,6 +75,7 @@ This guide walks you through forking [this markdown framework](https://github.co
- [Using Search](#using-search)
- [How It Works](#how-it-works)
- [Real-time Stats](#real-time-stats)
+ - [Newsletter Admin](#newsletter-admin)
- [Mobile Navigation](#mobile-navigation)
- [Copy Page Dropdown](#copy-page-dropdown)
- [API Endpoints](#api-endpoints)
@@ -883,6 +884,37 @@ Cards display post thumbnails (from `image` frontmatter field), titles, excerpts
**View preference:** User's view mode choice is saved to localStorage and persists across page visits.
+**Blog page featured layout:**
+
+Posts can be marked as featured on the blog page using the `blogFeatured` frontmatter field:
+
+```yaml
+---
+title: "My Featured Post"
+blogFeatured: true
+---
+```
+
+The first `blogFeatured` post displays as a hero card with landscape image, tags, date, title, excerpt, author info, and read more link. Remaining `blogFeatured` posts display in a 2-column featured row with excerpts. Regular (non-featured) posts display in a 3-column grid without excerpts.
+
+### Homepage Post Limit
+
+Limit the number of posts shown on the homepage:
+
+```typescript
+postsDisplay: {
+ showOnHome: true,
+ homePostsLimit: 5, // Limit to 5 most recent posts (undefined = show all)
+ homePostsReadMore: {
+ enabled: true,
+ text: "Read more blog posts",
+ link: "/blog",
+ },
+},
+```
+
+When posts are limited, an optional "read more" link appears below the list. Only shows when there are more posts than the limit.
+
### Hardcoded Navigation Items
Add React route pages (like `/stats`, `/write`) to the navigation menu via `siteConfig.ts`. These pages are React components, not markdown files.
@@ -1050,6 +1082,14 @@ Pages appear automatically in the navigation when published.
**Right sidebar:** When enabled in `siteConfig.rightSidebar.enabled`, posts and pages can display a right sidebar containing the CopyPageDropdown at 1135px+ viewport width. Add `rightSidebar: true` to frontmatter to enable. Without this field, pages render normally with CopyPageDropdown in the nav bar. When enabled, CopyPageDropdown moves from the navigation bar to the right sidebar on wide screens. The right sidebar is hidden below 1135px, and CopyPageDropdown returns to the nav bar automatically.
+**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.
+
+**Footer:** Footer content can be set in frontmatter (`footer` field) or use `siteConfig.footer.defaultContent`. Control visibility globally via `siteConfig.footer.enabled` and per-page via `showFooter: true/false` frontmatter.
+
+**Social footer:** Display social icons and copyright below the main footer. Configure via `siteConfig.socialFooter`. Control visibility per-page via `showSocialFooter: true/false` frontmatter.
+
+**Contact form:** Enable contact forms on any page or post via `contactForm: true` frontmatter. Requires `AGENTMAIL_API_KEY` and `AGENTMAIL_INBOX` environment variables in Convex. See the [Contact Form section](#contact-form-configuration) below.
+
**AI Agent chat:** The site includes an AI writing assistant (Agent) powered by Anthropic Claude API. Enable Agent on the Write page via `siteConfig.aiChat.enabledOnWritePage` or in the right sidebar on posts/pages using `aiChat: true` frontmatter (requires `rightSidebar: true`). Requires `ANTHROPIC_API_KEY` environment variable in Convex. See the [AI Agent chat section](#ai-agent-chat) below for setup instructions.
### Update SEO Meta Tags
@@ -1065,6 +1105,14 @@ Edit `index.html` to update:
Edit `public/llms.txt` and `public/robots.txt` with your site information.
+## Tag Pages and Related Posts
+
+Tag pages are available at `/tags/[tag]` for each tag used in your posts. They display all posts with that tag in a list or card view.
+
+**Related posts:** Individual blog posts show up to 3 related posts in the footer based on shared tags. Posts are sorted by relevance (number of shared tags) then by date. Only appears on blog posts (not static pages).
+
+**Tag links:** Tags in post footers link to their respective tag archive pages.
+
## Search
Your blog includes full text search with Command+K keyboard shortcut.
@@ -1107,6 +1155,137 @@ How it works:
- A cron job cleans up stale sessions every 5 minutes
- No personal data is stored (only anonymous UUIDs)
+## Footer Configuration
+
+The footer component displays markdown content and can be configured globally or per-page.
+
+**Global configuration:**
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+footer: {
+ enabled: true, // Global toggle for footer
+ showOnHomepage: true, // Show footer on homepage
+ showOnPosts: true, // Default: show footer on blog posts
+ showOnPages: true, // Default: show footer on static pages
+ showOnBlogPage: true, // Show footer on /blog page
+ defaultContent: "...", // Default markdown content
+},
+```
+
+**Frontmatter override:**
+
+Set `showFooter: false` in post/page frontmatter to hide footer on specific pages. Set `footer: "..."` to provide custom markdown content.
+
+**Footer images:** Footer markdown supports images with size control via HTML attributes (`width`, `height`, `style`, `class`).
+
+## Social Footer Configuration
+
+Display social icons and copyright information below the main footer.
+
+**Configuration:**
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+socialFooter: {
+ enabled: true,
+ showOnHomepage: true,
+ showOnPosts: true,
+ showOnPages: true,
+ showOnBlogPage: true,
+ socialLinks: [
+ { platform: "github", url: "https://github.com/username" },
+ { platform: "twitter", url: "https://x.com/handle" },
+ { platform: "linkedin", url: "https://linkedin.com/in/profile" },
+ ],
+ copyright: {
+ siteName: "Your Site Name",
+ showYear: true, // Auto-updates to current year
+ },
+},
+```
+
+**Supported platforms:** github, twitter, linkedin, instagram, youtube, tiktok, discord, website
+
+**Frontmatter override:**
+
+Set `showSocialFooter: false` in post/page frontmatter to hide social footer on specific pages.
+
+## Right Sidebar Configuration
+
+Enable a right sidebar on posts and pages that displays CopyPageDropdown at wide viewport widths.
+
+**Configuration:**
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+rightSidebar: {
+ enabled: true, // Set to false to disable globally
+ minWidth: 1135, // Minimum viewport width to show sidebar
+},
+```
+
+**Frontmatter usage:**
+
+Enable right sidebar on specific posts/pages:
+
+```yaml
+---
+title: My Post
+rightSidebar: true
+---
+```
+
+**Features:**
+
+- Right sidebar appears at 1135px+ viewport width
+- Contains CopyPageDropdown with sharing options
+- Three-column layout: left sidebar (TOC), main content, right sidebar
+- Hidden below 1135px, CopyPageDropdown returns to nav
+
+## Contact Form Configuration
+
+Enable contact forms on any page or post via frontmatter. Messages are sent via AgentMail.
+
+**Environment Variables:**
+
+Set these in the Convex dashboard:
+
+| Variable | Description |
+| ------------------------- | --------------------------------------------------------------------------- |
+| `AGENTMAIL_API_KEY` | Your AgentMail API key |
+| `AGENTMAIL_INBOX` | Your inbox address for sending |
+| `AGENTMAIL_CONTACT_EMAIL` | Optional: recipient for contact form messages (defaults to AGENTMAIL_INBOX) |
+
+**Site Config:**
+
+In `src/config/siteConfig.ts`:
+
+```typescript
+contactForm: {
+ enabled: true, // Global toggle for contact form feature
+ title: "Get in Touch",
+ description: "Send us a message and we'll get back to you.",
+},
+```
+
+**Frontmatter Usage:**
+
+Enable contact form on any page or post:
+
+```yaml
+---
+title: Contact Us
+slug: contact
+contactForm: true
+---
+```
+
+The form includes name, email, and message fields. Submissions are stored in Convex and sent via AgentMail to the configured recipient.
+
## Newsletter Admin
A newsletter management interface is available at `/newsletter-admin`. Use it to view subscribers, send newsletters, and compose custom emails.
diff --git a/content/pages/docs.md b/content/pages/docs.md
index abacd88..45765c7 100644
--- a/content/pages/docs.md
+++ b/content/pages/docs.md
@@ -127,6 +127,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 |
+| `showImageAtTop` | No | Set `true` to display the `image` field at the top of the post above the header (default: `false`) |
### Static pages
@@ -165,9 +166,12 @@ Content here...
| `aiChat` | No | Enable AI chat in right sidebar. Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. |
| `newsletter` | No | Override newsletter signup display (`true` to show, `false` to hide) |
| `contactForm` | No | Enable contact form on this page |
+| `showImageAtTop` | No | Set `true` to display the `image` field at the top of the page above the header (default: `false`) |
**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.
+**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.
+
### Sidebar layout
Posts and pages can use a docs-style layout with a table of contents sidebar. Add `layout: "sidebar"` to the frontmatter:
@@ -756,6 +760,14 @@ The `npm run sync` command only syncs markdown text content. Images are deployed
- **Homepage logo:** Configured via `logo` in `siteConfig.ts`. Set to `null` to hide.
- **Inner page logo:** Configured via `innerPageLogo` in `siteConfig.ts`. Shows on blog page, posts, and static pages. Desktop: top left corner. Mobile: top right corner (smaller). Set `enabled: false` to hide on inner pages while keeping homepage logo.
+## Tag Pages and Related Posts
+
+Tag pages are available at `/tags/[tag]` for each tag used in your posts. They display all posts with that tag in a list or card view with localStorage persistence for view mode preference.
+
+**Related posts:** Individual blog posts show up to 3 related posts in the footer based on shared tags. Posts are sorted by relevance (number of shared tags) then by date. Only appears on blog posts (not static pages).
+
+**Tag links:** Tags in post footers link to their respective tag archive pages.
+
## Search
Press `Command+K` (Mac) or `Ctrl+K` (Windows/Linux) to open the search modal. Click the search icon in the nav or use the keyboard shortcut.
@@ -806,6 +818,68 @@ Each post and page includes a share dropdown with options:
**Download as SKILL.md:** Downloads the content formatted as an Anthropic Agent Skills file with metadata, triggers, and instructions sections.
+## Homepage Post Limit
+
+Limit the number of posts shown on the homepage:
+
+```typescript
+postsDisplay: {
+ showOnHome: true,
+ homePostsLimit: 5, // Limit to 5 most recent posts (undefined = show all)
+ homePostsReadMore: {
+ enabled: true,
+ text: "Read more blog posts",
+ link: "/blog",
+ },
+},
+```
+
+When posts are limited, an optional "read more" link appears below the list. Only shows when there are more posts than the limit.
+
+## Blog Page Featured Layout
+
+Posts can be marked as featured on the blog page using the `blogFeatured` frontmatter field:
+
+```yaml
+---
+title: "My Featured Post"
+blogFeatured: true
+---
+```
+
+The first `blogFeatured` post displays as a hero card with landscape image, tags, date, title, excerpt, author info, and read more link. Remaining `blogFeatured` posts display in a 2-column featured row with excerpts. Regular (non-featured) posts display in a 3-column grid without excerpts.
+
+## Homepage Post Limit
+
+Limit the number of posts shown on the homepage:
+
+```typescript
+postsDisplay: {
+ showOnHome: true,
+ homePostsLimit: 5, // Limit to 5 most recent posts (undefined = show all)
+ homePostsReadMore: {
+ enabled: true,
+ text: "Read more blog posts",
+ link: "/blog",
+ },
+},
+```
+
+When posts are limited, an optional "read more" link appears below the list. Only shows when there are more posts than the limit.
+
+## Blog Page Featured Layout
+
+Posts can be marked as featured on the blog page using the `blogFeatured` frontmatter field:
+
+```yaml
+---
+title: "My Featured Post"
+blogFeatured: true
+---
+```
+
+The first `blogFeatured` post displays as a hero card with landscape image, tags, date, title, excerpt, author info, and read more link. Remaining `blogFeatured` posts display in a 2-column featured row with excerpts. Regular (non-featured) posts display in a 3-column grid without excerpts.
+
## Real-time stats
The `/stats` page displays real-time analytics:
diff --git a/files.md b/files.md
index 32dc4ab..90b7e99 100644
--- a/files.md
+++ b/files.md
@@ -33,7 +33,7 @@ A brief description of each file in the codebase.
| File | Description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration, footer configuration, social footer configuration, homepage configuration, AI chat configuration, newsletter configuration, contact form configuration) |
+| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display with homepage post limit and read more link, GitHub contributions, nav order, inner page logo settings, hardcoded navigation items for React routes, GitHub repository config for AI service raw URLs, font family configuration, right sidebar configuration, footer configuration with markdown support, social footer configuration, homepage configuration, AI chat configuration, newsletter configuration with admin and notifications, contact form configuration, weekly digest configuration) |
### Pages (`src/pages/`)
@@ -41,7 +41,7 @@ A brief description of each file in the codebase.
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Home.tsx` | Landing page with featured content and optional post list. Supports configurable post limit (homePostsLimit) and optional "read more" link (homePostsReadMore) via siteConfig.postsDisplay |
| `Blog.tsx` | Dedicated blog page with featured layout: hero post (first blogFeatured), featured row (remaining blogFeatured in 2 columns with excerpts), and regular posts (3 columns without excerpts). Supports list/card view toggle. Includes back button in navigation |
-| `Post.tsx` | Individual blog post or page view with optional left sidebar (TOC) and right sidebar (CopyPageDropdown). Includes back button (hidden when used as homepage), tag links, and related posts section in footer for blog posts. Supports 3-column layout at 1135px+. Can be used as custom homepage via siteConfig.homepage (update SITE_URL/SITE_NAME when forking) |
+| `Post.tsx` | Individual blog post or page view with optional left sidebar (TOC) and right sidebar (CopyPageDropdown). Includes back button (hidden when used as homepage), tag links, related posts section in footer for blog posts, footer component with markdown support, and social footer. Supports 3-column layout at 1135px+. Can display image at top when showImageAtTop: true. Can be used as custom homepage via siteConfig.homepage (update SITE_URL/SITE_NAME when forking) |
| `Stats.tsx` | Real-time analytics dashboard with visitor stats and GitHub stars |
| `TagPage.tsx` | Tag archive page displaying posts filtered by a specific tag. Includes view mode toggle (list/cards) with localStorage persistence |
| `Write.tsx` | Three-column markdown writing page with Cursor docs-style UI, frontmatter reference with copy buttons, theme toggle, font switcher (serif/sans/monospace), localStorage persistence, and optional AI Agent mode (toggleable via siteConfig.aiChat.enabledOnWritePage). When enabled, Agent replaces the textarea with AIChatView component. Includes scroll prevention when switching to Agent mode to prevent page jump. Title changes to "Agent" when in AI chat mode. |
@@ -69,8 +69,8 @@ A brief description of each file in the codebase.
| `RightSidebar.tsx` | Right sidebar component that displays CopyPageDropdown or AI chat on posts/pages at 1135px+ viewport width, controlled by siteConfig.rightSidebar.enabled and frontmatter rightSidebar/aiChat fields |
| `AIChatView.tsx` | AI chat interface component (Agent) using Anthropic Claude API. Supports per-page chat history, page content context, markdown rendering, and copy functionality. Used in Write page (replaces textarea when enabled) and optionally in RightSidebar. Requires ANTHROPIC_API_KEY environment variable in Convex. System prompt configurable via CLAUDE_PROMPT_STYLE, CLAUDE_PROMPT_COMMUNITY, CLAUDE_PROMPT_RULES, or CLAUDE_SYSTEM_PROMPT environment variables. Includes error handling for missing API keys. |
| `NewsletterSignup.tsx` | Newsletter signup form component for email-only subscriptions. Displays configurable title/description, validates email, and submits to Convex. Shows on home, blog page, and posts based on siteConfig.newsletter settings. Supports frontmatter override via newsletter: true/false. |
-| `ContactForm.tsx` | Contact form component with name, email, and message fields. Displays when contactForm: true in frontmatter. Submits to Convex which sends email via AgentMail to configured recipient. |
-| `SocialFooter.tsx` | Social footer component with social icons on left (GitHub, Twitter/X, LinkedIn, etc.) and copyright on right. Configurable via siteConfig.socialFooter. Shows below main footer on homepage, blog posts, and pages. Supports frontmatter override via showSocialFooter: true/false. |
+| `ContactForm.tsx` | Contact form component with name, email, and message fields. Displays when contactForm: true in frontmatter. Submits to Convex which sends email via AgentMail to configured recipient. Requires AGENTMAIL_API_KEY and AGENTMAIL_INBOX environment variables. |
+| `SocialFooter.tsx` | Social footer component with social icons on left (GitHub, Twitter/X, LinkedIn, Instagram, YouTube, TikTok, Discord, Website) and copyright on right. Configurable via siteConfig.socialFooter. Shows below main footer on homepage, blog posts, and pages. Supports frontmatter override via showSocialFooter: true/false. Auto-updates copyright year. |
### Context (`src/context/`)
@@ -102,18 +102,18 @@ A brief description of each file in the codebase.
| File | Description |
| ------------------ | ------------------------------------------------------------------------------------------------------------------ |
-| `schema.ts` | Database schema (posts, pages, viewCounts, pageViews, activeSessions, aiChats, newsletterSubscribers, newsletterSentPosts, contactMessages) with indexes for tag and AI queries. Posts and pages include showSocialFooter field for frontmatter control. |
-| `posts.ts` | Queries and mutations for blog posts, view counts, getAllTags, getPostsByTag, and getRelatedPosts |
+| `schema.ts` | Database schema (posts, pages, viewCounts, pageViews, activeSessions, aiChats, newsletterSubscribers, newsletterSentPosts, contactMessages) with indexes for tag queries (by_tags), AI queries, and blog featured posts (by_blogFeatured). Posts and pages include showSocialFooter, showImageAtTop, blogFeatured, and contactForm fields for frontmatter control. |
+| `posts.ts` | Queries and mutations for blog posts, view counts, getAllTags, getPostsByTag, getRelatedPosts, and getBlogFeaturedPosts. Includes tag-based queries for tag pages and related posts functionality. |
| `pages.ts` | Queries and mutations for static pages |
| `search.ts` | Full text search queries across posts and pages |
| `stats.ts` | Real-time stats with aggregate component for O(log n) counts, page view recording, session heartbeat |
-| `crons.ts` | Cron jobs for stale session cleanup, weekly newsletter digest (Sundays 9am UTC), and weekly stats summary (Mondays 9am UTC) |
-| `http.ts` | HTTP endpoints: sitemap, API (update SITE_URL/SITE_NAME when forking, uses www.markdown.fast) |
+| `crons.ts` | Cron jobs for stale session cleanup (every 5 minutes), weekly newsletter digest (Sundays 9am UTC), and weekly stats summary (Mondays 9am UTC). Uses environment variables SITE_URL and SITE_NAME for email content. |
+| `http.ts` | HTTP endpoints: sitemap (includes tag pages), API (update SITE_URL/SITE_NAME when forking, uses www.markdown.fast), Open Graph HTML generation for social crawlers |
| `rss.ts` | RSS feed generation (update SITE_URL/SITE_TITLE when forking, uses www.markdown.fast) |
| `aiChats.ts` | Queries and mutations for AI chat history (per-session, per-context storage). Handles anonymous session IDs, per-page chat contexts, and message history management. Supports page content as context for AI responses. |
| `aiChatActions.ts` | Anthropic Claude API integration action for AI chat responses. Requires ANTHROPIC_API_KEY environment variable in Convex. Uses claude-sonnet-3-5-20240620 model. System prompt configurable via environment variables (CLAUDE_PROMPT_STYLE, CLAUDE_PROMPT_COMMUNITY, CLAUDE_PROMPT_RULES, or CLAUDE_SYSTEM_PROMPT). Includes error handling for missing API keys with user-friendly error messages. Supports page content context and chat history (last 20 messages). |
-| `newsletter.ts` | Newsletter mutations and queries: subscribe, unsubscribe, getSubscriberCount, getActiveSubscribers, getAllSubscribers (admin), deleteSubscriber (admin), getNewsletterStats, getPostsForNewsletter, wasPostSent, recordPostSent. |
-| `newsletterActions.ts` | Newsletter actions (Node.js runtime): sendPostNewsletter, sendWeeklyDigest, notifyNewSubscriber, sendWeeklyStatsSummary. Uses AgentMail SDK for email delivery. |
+| `newsletter.ts` | Newsletter mutations and queries: subscribe, unsubscribe, getSubscriberCount, getActiveSubscribers, getAllSubscribers (admin), deleteSubscriber (admin), getNewsletterStats, getPostsForNewsletter, wasPostSent, recordPostSent, scheduleSendPostNewsletter, scheduleSendCustomNewsletter, scheduleSendStatsSummary, getStatsForSummary. |
+| `newsletterActions.ts` | Newsletter actions (Node.js runtime): sendPostNewsletter, sendCustomNewsletter, sendWeeklyDigest, notifyNewSubscriber, sendWeeklyStatsSummary. Uses AgentMail SDK for email delivery. Includes markdown-to-HTML conversion for custom emails. |
| `contact.ts` | Contact form mutations and actions: submitContact, sendContactEmail (AgentMail API), markEmailSent. |
| `convex.config.ts` | Convex app configuration with aggregate component registrations (pageViewsByPath, totalPageViews, uniqueVisitors) |
| `tsconfig.json` | Convex TypeScript configuration |
@@ -148,11 +148,11 @@ Markdown files with frontmatter for blog posts. Each file becomes a blog post.
| `tags` | Array of topic tags |
| `readTime` | Estimated reading time |
| `image` | Header/Open Graph image URL (optional) |
-| `showImageAtTop` | Display image at top of post above header (optional, default: false) |
+| `showImageAtTop` | Display image at top of post above header (optional, default: false). When true, image displays full-width with rounded corners above post header. |
| `excerpt` | Short excerpt for card view (optional) |
| `featured` | Show in featured section (optional) |
| `featuredOrder` | Order in featured section (optional) |
-| `blogFeatured` | Show as featured on blog page (optional, first becomes hero, rest in 2-col row) |
+| `blogFeatured` | Show as featured on blog page (optional, first becomes hero card with landscape image, rest in 2-column featured row with excerpts) |
| `authorName` | Author display name (optional) |
| `authorImage` | Round author avatar image URL (optional) |
| `rightSidebar` | Enable right sidebar with CopyPageDropdown (optional) |
@@ -162,7 +162,7 @@ Markdown files with frontmatter for blog posts. Each file becomes a blog post.
| `aiChat` | Enable AI Agent chat in right sidebar (optional). Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. |
| `blogFeatured` | Show as featured on blog page (optional, first becomes hero, rest in 2-column row) |
| `newsletter` | Override newsletter signup display (optional, true/false) |
-| `contactForm` | Enable contact form on this post (optional) |
+| `contactForm` | Enable contact form on this post (optional). Requires siteConfig.contactForm.enabled: true and AGENTMAIL_API_KEY/AGENTMAIL_INBOX environment variables. |
## Static Pages (`content/pages/`)
@@ -177,7 +177,7 @@ Markdown files for static pages like About, Projects, Contact, Changelog.
| `showInNav` | Show in navigation menu (default: true) |
| `excerpt` | Short excerpt for card view (optional) |
| `image` | Thumbnail/OG image URL (optional) |
-| `showImageAtTop` | Display image at top of page above header (optional, default: false) |
+| `showImageAtTop` | Display image at top of page above header (optional, default: false). When true, image displays full-width with rounded corners above page header. |
| `featured` | Show in featured section (optional) |
| `featuredOrder` | Order in featured section (optional) |
| `authorName` | Author display name (optional) |
@@ -188,7 +188,7 @@ Markdown files for static pages like About, Projects, Contact, Changelog.
| `showSocialFooter` | Show social footer on this page (optional, overrides siteConfig default) |
| `aiChat` | Enable AI Agent chat in right sidebar (optional). Set `true` to enable (requires `rightSidebar: true` and `siteConfig.aiChat.enabledOnContent: true`). Set `false` to explicitly hide even if global config is enabled. |
| `newsletter` | Override newsletter signup display (optional, true/false) |
-| `contactForm` | Enable contact form on this page (optional) |
+| `contactForm` | Enable contact form on this page (optional). Requires siteConfig.contactForm.enabled: true and AGENTMAIL_API_KEY/AGENTMAIL_INBOX environment variables. |
## Scripts (`scripts/`)
@@ -198,8 +198,8 @@ Markdown files for static pages like About, Projects, Contact, Changelog.
| `sync-discovery-files.ts` | Updates AGENTS.md and llms.txt with current app data |
| `import-url.ts` | Imports external URLs as markdown posts (Firecrawl) |
| `configure-fork.ts` | Automated fork configuration (reads fork-config.json) |
-| `send-newsletter.ts` | CLI tool for sending newsletter posts (npm run newsletter:send ) |
-| `send-newsletter-stats.ts` | CLI tool for sending weekly stats summary (npm run newsletter:send:stats) |
+| `send-newsletter.ts` | CLI tool for sending newsletter posts (npm run newsletter:send ). Calls scheduleSendPostNewsletter mutation directly. |
+| `send-newsletter-stats.ts` | CLI tool for sending weekly stats summary (npm run newsletter:send:stats). Calls scheduleSendStatsSummary mutation directly. |
### Sync Commands
diff --git a/fork-config.json.example b/fork-config.json.example
index 92a3ece..e60361e 100644
--- a/fork-config.json.example
+++ b/fork-config.json.example
@@ -45,7 +45,13 @@
},
"postsDisplay": {
"showOnHome": true,
- "showOnBlogPage": true
+ "showOnBlogPage": true,
+ "homePostsLimit": 5,
+ "homePostsReadMore": {
+ "enabled": true,
+ "text": "Read more blog posts",
+ "link": "/blog"
+ }
},
"featuredViewMode": "cards",
"showViewToggle": true,
@@ -56,6 +62,47 @@
"slug": null,
"originalHomeRoute": "/home"
},
+ "rightSidebar": {
+ "enabled": true,
+ "minWidth": 1135
+ },
+ "footer": {
+ "enabled": true,
+ "showOnHomepage": true,
+ "showOnPosts": true,
+ "showOnPages": true,
+ "showOnBlogPage": true,
+ "defaultContent": "Built with [Convex](https://convex.dev) for real-time sync and deployed on [Netlify](https://netlify.com)."
+ },
+ "socialFooter": {
+ "enabled": true,
+ "showOnHomepage": true,
+ "showOnPosts": true,
+ "showOnPages": true,
+ "showOnBlogPage": true,
+ "socialLinks": [
+ {
+ "platform": "github",
+ "url": "https://github.com/yourusername/your-repo-name"
+ },
+ {
+ "platform": "twitter",
+ "url": "https://x.com/yourhandle"
+ },
+ {
+ "platform": "linkedin",
+ "url": "https://www.linkedin.com/in/yourprofile/"
+ }
+ ],
+ "copyright": {
+ "siteName": "Your Site Name",
+ "showYear": true
+ }
+ },
+ "aiChat": {
+ "enabledOnWritePage": false,
+ "enabledOnContent": false
+ },
"newsletter": {
"enabled": false,
"agentmail": {
@@ -81,6 +128,25 @@
"description": "Subscribe for more updates."
}
}
+ },
+ "contactForm": {
+ "enabled": false,
+ "title": "Get in Touch",
+ "description": "Send us a message and we'll get back to you."
+ },
+ "newsletterAdmin": {
+ "enabled": false,
+ "showInNav": false
+ },
+ "newsletterNotifications": {
+ "enabled": false,
+ "newSubscriberAlert": false,
+ "weeklyStatsSummary": false
+ },
+ "weeklyDigest": {
+ "enabled": false,
+ "dayOfWeek": 0,
+ "subject": "Weekly Digest"
}
}
diff --git a/public/images/setupguide.png b/public/images/setupguide.png
index d76fcb3..fd2327a 100644
Binary files a/public/images/setupguide.png and b/public/images/setupguide.png differ
diff --git a/public/raw/setup-guide.md b/public/raw/setup-guide.md
index 0261676..bff46c3 100644
--- a/public/raw/setup-guide.md
+++ b/public/raw/setup-guide.md
@@ -68,6 +68,7 @@ This guide walks you through forking [this markdown framework](https://github.co
- [Using Search](#using-search)
- [How It Works](#how-it-works)
- [Real-time Stats](#real-time-stats)
+ - [Newsletter Admin](#newsletter-admin)
- [Mobile Navigation](#mobile-navigation)
- [Copy Page Dropdown](#copy-page-dropdown)
- [API Endpoints](#api-endpoints)