Feature Spec: Private Content System
Comprehensive feature spec for private/offline content creation. Enables sensitive documents, personal todos, and local-only work within the same system used for public content.
Problem Statement
The system currently serves one purpose: public content creation and publishing. But the same components, metadata schema, and AI-assisted workflow would be equally valuable for private personal work—todos, grocery lists, sensitive documents, work notes, financial planning.
Right now there's no way to use this system for private content without either:
- Committing sensitive information to a public repo
- Manually managing
.gitignorerules per file - Using a separate tool entirely (defeating the "personal OS" consolidation)
Goal: Enable the same content creation experience (components, AI assistance, metadata, localhost preview) for content that should never be committed or deployed.
Use Cases
1. Sensitive Documents
- Work notes: Internal company info, meeting notes with names, salary discussions
- Financial planning: Budget spreadsheets, investment strategies, account numbers
- Medical records: Health history, doctor notes, prescriptions
- Legal documents: Contracts, agreements, personal legal matters
2. Personal Productivity
- Todo lists: Personal tasks, job application tracking, project planning
- Grocery/shopping lists: Weekly groceries, gift ideas, wishlists
- Journal entries: Daily reflections, therapy notes, personal thoughts
- Dream log: Recurring dreams, analysis, patterns
3. Semi-Private Work
- Draft blog posts: Work-in-progress content not ready for public view
- Experimental features: Half-baked specs, brainstorming sessions
- Interview prep: Company research, questions, salary negotiation notes
Privacy Tiers
Not all private content has the same requirements. Three tiers cover the spectrum:
Tier 1: Local-Only (Highest Security)
- Never committed: Lives in
content/private/, gitignored by default - Never deployed: Only visible on localhost, impossible to accidentally publish
- Full feature parity: Same components, metadata, AI assistance as public content
- Use cases: Sensitive docs, medical records, financial planning, truly private journals
Tier 2: Committed but Unpublished (Medium Security)
- Version controlled: Lives in regular
content/dirs, committed to private repo - Environment-gated: Only loads in development, excluded from production build
- Use cases: Draft blog posts, work-in-progress specs, experimental content
- Benefit: Git history for tracking changes, synced across devices via private repo
Tier 3: Published but Authenticated (Low Security)
- Public URL: Deployed to production, accessible via direct link
- Not indexed: Hidden from Navigator, sitemap, search engines
- Optional auth: Basic password protection or OAuth if needed
- Use cases: Shareable docs for small groups, unlisted content, private portfolio pieces
Implementation: Tier 1 (MVP)
This is the 80/20 solution—maximum privacy with minimal implementation cost.
Setup (10 seconds)
mkdir content/private
echo "content/private/" >> .gitignoreContent Discovery Update
Modify src/lib/posts.ts to include private directory in development:
const contentDirs = [
path.join(process.cwd(), 'content/tsx'),
path.join(process.cwd(), 'content/md'),
// Only load private content in development
...(process.env.NODE_ENV === 'development'
? [path.join(process.cwd(), 'content/private')]
: []
),
];Visual Indicator
Tag private content in Navigator and ContentHeader so it's obvious what won't deploy:
{posts.map(post => (
<div key={post.slug}>
<Link href={post.route}>{post.title}</Link>
{post.isPrivate && <span style={{ color: 'orange' }}> 🔒 Local Only</span>}
</div>
))}Metadata Extension
Add isPrivate flag to PostMeta (auto-detected from path):
export interface PostMeta {
// ... existing fields
isPrivate?: boolean; // Auto-set if file in content/private/
}Safety Check
Add build-time assertion that private content never makes it to production:
export async function getAllPosts() {
const posts = await loadAllContent();
// Safety: Fail build if private content leaked
if (process.env.NODE_ENV === 'production') {
const privateContent = posts.filter(p => p.isPrivate);
if (privateContent.length > 0) {
throw new Error(
`Private content leaked to production: ${privateContent.map(p => p.slug).join(', ')}`
);
}
}
return posts;
}Implementation: Tier 2 (Committed but Unpublished)
For content you want version controlled but not deployed.
Metadata Flag
export const metadata = {
title: 'Work in Progress Post',
slug: 'draft-post',
draft: true, // Excluded from production builds
// ... rest of metadata
};Filter in Production
export async function getAllPosts() {
const posts = await loadAllContent();
// Filter drafts in production
if (process.env.NODE_ENV === 'production') {
return posts.filter(p => !p.draft);
}
return posts;
}Visual Indicator
{post.draft && <span style={{ color: 'yellow' }}> 📝 Draft</span>}Implementation: Tier 3 (Authenticated)
For shareable-but-unlisted content. This is lower priority—most use cases covered by Tiers 1 & 2.
Metadata Flag
export const metadata = {
title: 'Unlisted Post',
slug: 'secret-project-notes',
unlisted: true, // Accessible via direct URL, hidden from Navigator/sitemap
// ... rest of metadata
};Hide from Discovery
// Filter unlisted from Navigator
const visiblePosts = posts.filter(p => !p.unlisted);// Exclude from sitemap
const publicPosts = posts.filter(p => !p.unlisted);Optional: Basic Auth
If truly needed, add middleware for password protection:
export function middleware(request: NextRequest) {
const url = request.nextUrl;
// Check if accessing unlisted content
if (isUnlistedRoute(url.pathname)) {
const auth = request.headers.get('authorization');
if (!isValidAuth(auth)) {
return new Response('Auth required', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic' },
});
}
}
return NextResponse.next();
}Practical Workflow Examples
Sensitive Document (Tier 1: Local-Only)
export const metadata = {
title: 'Personal Financial Plan 2026',
slug: 'financial-plan-2026',
date: '2026-01-23',
author: 'Jay Griffin',
type: 'doc' as const,
tags: ['personal', 'financial', 'sensitive'],
};
export default function FinancialPlan() {
return (
<>
<Heading level={2}>Budget Overview</Heading>
<Paragraph>
Monthly income: $X, expenses: $Y, savings goals: $Z...
</Paragraph>
{/* Full plan with personal details, never commits */}
</>
);
}Export to PDF: Print from localhost:3000 for records. Content never touches git or production server.
Personal Todos (Tier 1: Local-Only)
export const metadata = {
title: 'Personal Todos - Jan 2026',
slug: 'todos-personal-jan-2026',
type: 'doc' as const,
tags: ['todo', 'personal'],
};
export default function PersonalTodos() {
return (
<>
<Heading level={2}>Job Applications</Heading>
<List>
<ListItem>Apply to Company X - deadline Feb 1</ListItem>
<ListItem>Research Company Y salaries on Glassdoor</ListItem>
<ListItem>Prepare portfolio walkthrough demo</ListItem>
</List>
<Heading level={2}>Groceries This Week</Heading>
<List>
<ListItem>Milk, eggs, bread</ListItem>
<ListItem>Coffee beans (out on Thursday)</ListItem>
<ListItem>Vegetables for stir-fry</ListItem>
</List>
</>
);
}Draft Blog Post (Tier 2: Committed but Unpublished)
export const metadata = {
title: 'Advanced React Patterns',
slug: 'advanced-react-patterns',
draft: true, // Won't deploy until set to false
date: '2026-01-20',
author: 'Jay Griffin',
type: 'post' as const,
tags: ['react', 'patterns', 'architecture'],
};
export default function AdvancedReactPatterns() {
return (
<>
<Heading level={2}>Work in Progress</Heading>
<Paragraph>
Need to flesh out examples, add code samples...
</Paragraph>
</>
);
}Workflow: Commit drafts to track progress. Ship when ready by changing draft: false.
Integration Points
Metadata Editor Integration
When the metadata editor exists, it should:
- Respect privacy tiers: Warn before bulk-editing private content
- Support tier migration: "Move this draft to private" button
- Filter by privacy: "Show only local-only content" toggle
Search/Filter Integration
Navigator should support filtering by privacy status:
- "Show Private Only" - useful for finding sensitive docs quickly
- "Hide Drafts" - clean view of published content
- "Show All" - default in dev, see everything
AI Integration
AI assistance works identically for private content. But consider:
- Data privacy: Using Claude/Copilot on sensitive content sends data to AI providers
- Mitigation: Use AI for structure/formatting, write sensitive details manually
- Or: Use local-only AI models for truly sensitive work (requires separate setup)
Future Enhancements
Encryption at Rest
For paranoid-level security, encrypt private content on disk:
- Password-protected encryption key
- Decrypt on dev server start
- Encrypted files in
content/private/unreadable without key
Multi-Device Sync
Currently private content is truly local (one machine only). For cross-device:
- Private git repo: Push to GitHub private repo, pull on other machines
- Encrypted cloud sync: Dropbox/iCloud for
content/private/ - Self-hosted sync: Syncthing or similar for full control
Export Formats
Generate PDFs/exports of private content for sharing:
- PDF generation: Server-side rendering to PDF (puppeteer or similar)
- Markdown export: Convert TSX → MD for portability
- Print styling: CSS optimized for print (no nav, clean headers)
Implementation Priority
Now (10 seconds): Create content/private/ and add to .gitignore. Immediately usable for sensitive documents.
Soon (1 hour): Update posts.ts to load private content in dev only. Add visual indicators. Add safety checks.
Later (2-3 hours): Implement draft flag for Tier 2. Add filtering to Navigator.
Eventually (if needed): Tier 3 unlisted content, basic auth, encryption, multi-device sync.
Why This Matters
This isn't about adding features—it's about making the system indispensable for all work, not just public content.
If you can use the same components, metadata, AI assistance, and workflow for:
- Public blog posts
- Work documentation (at new job)
- Personal todos and shopping lists
- Sensitive personal documents
- Draft ideas and half-baked specs
...then this truly becomes your personal operating system for knowledge work. Not a portfolio site you occasionally update—a tool you live in daily.
That's when the productivity multiplier becomes undeniable.