pensieve-patterns
Pensieve Patterns
Reusable patterns from the Pensieve AI chat application.
1. GitHub Contents API for Note Search
Fetch markdown files from a private GitHub repo without cloning:
// lib/github/api.ts
const GITHUB_API_BASE = "https://api.github.com";
async function fetchWithAuth(url: string): Promise<Response> {
const token = process.env.GITHUB_TOKEN;
const headers: HeadersInit = {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
};
if (token) headers.Authorization = `Bearer ${token}`;
return fetch(url, { headers, cache: "no-store" });
}
async function listDirectoryContents(owner: string, repo: string, path = "", branch = "main") {
const encodedPath = path ? `/${encodeURIComponent(path)}` : "";
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents${encodedPath}?ref=${branch}`;
const response = await fetchWithAuth(url);
if (!response.ok) throw new Error(`GitHub API error: ${response.status}`);
return response.json();
}
// Recursively fetch all .md files
export async function getMarkdownFiles(): Promise<{ path: string; title: string }[]> {
const repoPath = process.env.GITHUB_REPO; // "owner/repo"
const [owner, repo] = repoPath!.split("/");
return getAllMarkdownFilesRecursive(owner, repo);
}
2. @ Mention Popup with Fuzzy Search
Custom popup positioned above the @ symbol:
// Key features:
// - Positioned above @ using caret coordinates calculation
// - Fuzzy search with fuse.js
// - Keyboard navigation (arrows, enter, escape)
// - Backspace on empty closes popup and removes @
interface NoteMentionPopupProps {
notes: Note[];
onSelect: (note: Note) => void;
onClose: () => void;
onBackspaceEmpty: () => void; // Remove @ and close
inputRef: RefObject<HTMLTextAreaElement>;
text: string;
cursorPosition: number;
}
// Position calculation
const getCaretCoordinates = (element: HTMLTextAreaElement, position: number) => {
// Create mirror div, copy styles, measure span position
// Returns { top, left } relative to textarea
};
3. Shimmer Loading Indicator
Replace spinners with animated shimmer text:
// components/ai-elements/thinking-indicator.tsx
import { Shimmer } from "./shimmer";
import { BrainIcon } from "lucide-react";
export function ThinkingIndicator({ message = "Thinking..." }) {
return (
<div className="flex items-center gap-2 text-muted-foreground text-sm py-2">
<BrainIcon className="size-4 animate-pulse" />
<Shimmer duration={1.5}>{message}</Shimmer>
</div>
);
}
// Usage in chat
{status === "submitted" && <ThinkingIndicator />}
4. Dark Mode Only Setup
Force dark mode in Next.js:
// app/layout.tsx
<html lang="en" className="dark">
// globals.css - define .dark selector with CSS variables
.dark {
--background: oklch(0.2478 0 0);
--foreground: oklch(0.9851 0 0);
// ... other variables
}
5. PWA Icons from Single Source
Generate all icons from one source image:
# Using macOS sips
sips -z 512 512 logo.png --out icon-512.png
sips -z 192 192 logo.png --out icon-192.png
sips -z 180 180 logo.png --out apple-touch-icon.png
# Favicon with ImageMagick
sips -z 32 32 logo.png --out favicon-32.png
sips -z 16 16 logo.png --out favicon-16.png
magick favicon-16.png favicon-32.png favicon.ico
Place in:
/public/- icon-192.png, icon-512.png, apple-touch-icon.png, logo.png/src/app/- favicon.ico (Next.js App Router convention)
6. Vercel Env Vars from .env.local
Push local env to Vercel production:
# Link project
vercel link
# Add each variable
source .env.local
echo "$SESSION_SECRET" | vercel env add SESSION_SECRET production
echo "$GITHUB_TOKEN" | vercel env add GITHUB_TOKEN production
# ... repeat for each var
# Update existing
vercel env rm VAR_NAME production -y
echo "$NEW_VALUE" | vercel env add VAR_NAME production
# Verify
vercel env ls
7. AI SDK 6 Chat with Tools
// app/api/chat/route.ts
import { streamText, tool } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
const vaultTools = {
search: tool({
description: "Search vault for content",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => searchVault(query),
}),
};
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
messages: await convertToModelMessages(messages),
tools: vaultTools,
stopWhen: stepCountIs(5),
});
return result.toUIMessageStreamResponse({ sendReasoning: true });
}
8. Dexie.js for Client-Side Sessions
// lib/db/dexie.ts
const db = new Dexie("Pensieve");
db.version(1).stores({
sessions: "id, createdAt, updatedAt",
messages: "id, sessionId, createdAt",
});
// lib/db/hooks.ts
export function useSessions() {
return useLiveQuery(() => db.sessions.orderBy("updatedAt").reverse().toArray());
}
export async function saveMessage(sessionId: string, message: Message) {
await db.messages.add({ ...message, id: nanoid(), sessionId, createdAt: new Date() });
await db.sessions.update(sessionId, { updatedAt: new Date() });
}
More from magnusrodseth/dotfiles
playwright-cli
Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.
10agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
10skill-creator
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
9github-cli
GitHub CLI workflows for issues, labels, and PRs with proper linking, base branch handling, and structured descriptions
9product-marketing-context
When the user wants to create or update their product marketing context document. Also use when the user mentions 'product context,' 'marketing context,' 'set up context,' 'positioning,' or wants to avoid repeating foundational information across marketing tasks. Creates `.claude/product-marketing-context.md` that other marketing skills reference.
9vercel-react-best-practices
React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
9