Feature Spec: Private Content System

By Jay Griffin, Claude Sonnet 4.5*Claude wrote this spec based on Jay's requirements for practical offline productivity.·  January 23, 2026
docs
🏷️ Tags:feature-specprivacyofflinearchitectureproductivity

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:

  1. Committing sensitive information to a public repo
  2. Manually managing .gitignore rules per file
  3. 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

2. Personal Productivity

3. Semi-Private Work

Privacy Tiers

Not all private content has the same requirements. Three tiers cover the spectrum:

Tier 1: Local-Only (Highest Security)

Tier 2: Committed but Unpublished (Medium Security)

Tier 3: Published but Authenticated (Low Security)

Implementation: Tier 1 (MVP)

This is the 80/20 solution—maximum privacy with minimal implementation cost.

Setup (10 seconds)

terminal
mkdir content/private
echo "content/private/" >> .gitignore

Content Discovery Update

Modify src/lib/posts.ts to include private directory in development:

src/lib/posts.ts
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:

src/components/Navigator.tsx
{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):

src/types/post.ts
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:

src/lib/posts.ts
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

content/tsx/draft-post.tsx
export const metadata = {
  title: 'Work in Progress Post',
  slug: 'draft-post',
  draft: true, // Excluded from production builds
  // ... rest of metadata
};

Filter in Production

src/lib/posts.ts
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

src/components/Navigator.tsx
{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

content/tsx/unlisted-post.tsx
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

src/components/Navigator.tsx
// Filter unlisted from Navigator
const visiblePosts = posts.filter(p => !p.unlisted);
src/app/sitemap.ts
// Exclude from sitemap
const publicPosts = posts.filter(p => !p.unlisted);

Optional: Basic Auth

If truly needed, add middleware for password protection:

src/middleware.ts
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)

content/private/financial-plan.tsx
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)

content/private/todos-personal.tsx
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)

content/tsx/draft-react-patterns.tsx
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:

Search/Filter Integration

Navigator should support filtering by privacy status:

AI Integration

AI assistance works identically for private content. But consider:

Future Enhancements

Encryption at Rest

For paranoid-level security, encrypt private content on disk:

Multi-Device Sync

Currently private content is truly local (one machine only). For cross-device:

Export Formats

Generate PDFs/exports of private content for sharing:

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:

...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.