diff --git a/.gitignore b/.gitignore index 48347e6..9f0ce3e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,14 @@ node_modules/ .pnp .pnp.js docs/node_modules/ +www/node_modules/ # Build dist/ build/ docs/dist/ docs/.astro/ +www/blog/ # Data data/ diff --git a/Dockerfile.website b/Dockerfile.website index 981271e..ed7eff3 100644 --- a/Dockerfile.website +++ b/Dockerfile.website @@ -1,11 +1,26 @@ -FROM nginx:alpine +FROM node:20-alpine -COPY www/ /usr/share/nginx/html/ -COPY docker-entrypoint.d/ /docker-entrypoint.d/ +WORKDIR /app +# Install dependencies for blog build +COPY www/package.json ./www/package.json +RUN cd www && npm install --silent 2>/dev/null || true + +# Copy www source files +COPY www/ /app/www/ + +# Build blog (processes _posts/*.md to blog/*.html) +RUN node www/build-blog.js + +# Run envsubst on HTML files RUN apk add --no-cache gettext && \ - chmod +x /docker-entrypoint.d/*.sh + chmod +x /docker-entrypoint.d/*.sh 2>/dev/null || true + +# Copy entrypoint +COPY docker-entrypoint.d/ /docker-entrypoint.d/ +RUN chmod +x /docker-entrypoint.d/*.sh + +# Copy to nginx document root +COPY --from=0 /app/www/ /usr/share/nginx/html/ ENTRYPOINT ["/docker-entrypoint.d/30-envsubst.sh", "--", "nginx", "-g", "daemon off;"] - -CMD ["nginx", "-g", "daemon off;"] diff --git a/www/AGENTS.md b/www/AGENTS.md new file mode 100644 index 0000000..eb630cd --- /dev/null +++ b/www/AGENTS.md @@ -0,0 +1,120 @@ +# DearDiary Website & Blog - Agent Instructions + +## Overview + +The DearDiary website is a static HTML site generated from Markdown blog posts. No frameworks, no build complexity. + +## Structure + +``` +www/ +├── _posts/ # Blog posts in Markdown (source) +│ └── YYYY-MM-DD-slug.md +├── blog/ # Generated HTML (don't edit manually) +│ ├── index.html +│ └── YYYY-MM-DD-slug/ +│ └── index.html +├── css/ +│ └── styles.css +├── docs/ # Documentation (also static HTML) +├── js/ +│ └── main.js +├── index.html # Main website (uses envsubst for templating) +├── build-blog.js # Blog generator script +└── package.json # npm deps (gray-matter for parsing) +``` + +## Building + +```bash +cd www +npm install +node build-blog.js +``` + +This converts `_posts/*.md` → `blog/*.html`. + +## Blog Post Format + +Frontmatter: +```yaml +--- +title: Your Post Title +date: 2026-03-27 +author: Konrad Lother +excerpt: One sentence summary for the blog index +--- +``` + +## Tone & Style Guide + +Read unixsheikh.com for inspiration. Key characteristics: + +### Voice +- **Direct and authoritative** - Say what you mean, don't hedge unnecessarily +- **Personal opinion is welcome** - Write from a first-person perspective +- **Technical but accessible** - Explain complex topics simply +- **Not afraid to be contrarian** - Challenge conventional wisdom when you disagree with it + +### Structure +- **Strong opening** - Hook the reader immediately +- **Clear sections** - Use headers to organize thoughts +- **Concrete examples** - Illustrate abstract concepts +- **Practical takeaways** - End with actionable advice + +### What to Avoid +- Corporate/tech-blog speak ("leverage", "synergy", "cutting-edge") +- Unnecessary hedging ("might", "could potentially", "in some cases") +- Padding or fluff - every sentence should add value +- Being mean-spirited - critique ideas, not people + +### Example Openers + +Bad: +> "In today's fast-paced digital world, journaling has evolved..." + +Good: +> "Traditional journaling apps ask you to write long entries at the end of the day. But let's be honest - who has time for that?" + +### Length +- 500-1500 words is ideal +- If you're writing more, consider splitting into multiple posts +- Short posts can be 200-300 words if the idea is complete + +## Topics to Cover + +### DearDiary-Specific +- How to use features effectively +- Tips and tricks for better journaling +- Behind-the-scenes development stories +- Why AI-assisted journaling makes sense +- Privacy and self-hosting benefits + +### General Tech Thoughts +- Software craftsmanship +- Human-AI collaboration +- Simplicity in development +- Critical thinking about tech trends +- The value of boring technology + +### Personal/Philosophy +- Productivity and reflection +- Memory and identity +- The importance of记录 (record-keeping) +- Learning from daily life + +## Post Ideas + +1. Why capture events, not essays (the thesis behind DearDiary) +2. My journaling experiment - 30 days of DearDiary +3. What I learned building an app with AI +4. The case for boring technology in side projects +5. How to remember your life better +6. Self-hosting isn't hard, it's just unfamiliar + +## Technical Notes + +- The `build-blog.js` uses a simple regex-based Markdown converter +- For full Markdown support (tables, footnotes), consider using a proper parser +- Images should be hosted externally or added to the repo +- Code blocks work but syntax highlighting is limited diff --git a/www/_posts/2026-03-27-building-deardiary-with-ai.md b/www/_posts/2026-03-27-building-deardiary-with-ai.md new file mode 100644 index 0000000..3c0e316 --- /dev/null +++ b/www/_posts/2026-03-27-building-deardiary-with-ai.md @@ -0,0 +1,135 @@ +--- +title: Building DearDiary with AI - Lessons from Human-AI Collaboration +date: 2026-03-27 +author: Konrad Lother +excerpt: What I learned from building a full-stack app using an AI coding assistant +--- + +# Building DearDiary with AI + +## A Tale of Miscommunication and Debugging + +I built DearDiary using an AI coding assistant. It was enlightening, frustrating, sometimes hilarious, and ultimately successful. Here's what I learned about human-AI collaboration. + +## The Setup + +DearDiary is a full-stack journaling app: Bun + Hono backend, React + Vite frontend, SQLite database, Docker deployment. Not trivial, but not rocket science either. + +I gave the AI context about the project structure, my preferences, and let it work. + +## The Problems We Hit + +### 1. "It Should Work, But..." + +The first major issue was the most classic: the AI made changes that *should* have worked according to its understanding, but didn't. + +We consolidated environment variables into a single `.env` file with prefixes. The AI updated most references to `DATABASE_URL` → `BACKEND_DATABASE_URL`, but missed several: +- The Prisma schema +- The test helpers +- A variable in the healthcheck config + +The app failed to start. The error message? Cryptic Prisma errors that took time to trace back to a simple env var mismatch. + +**Lesson**: AI is great at systematic changes, but when it misses something, the gap is invisible to it. Always verify systematically. + +### 2. The Disappearing Routes + +The AI moved API routes into a separate file (`events.ts`) and mounted them at `/api/v1`. Simple, clean. + +Except the routes were at `/api/v1/events` but the frontend was calling `/events`. The AI didn't catch that the mounting path was part of the route definition. + +**Lesson**: AI understands code structure well, but context about how pieces connect across files is easily lost. + +### 3. "I Fixed That" + +Multiple times, the AI would say "Fixed!" and show the corrected code, but the actual file hadn't been changed. Or it would describe a solution that wasn't implemented. + +This is the most dangerous mode of failure - confidence without execution. + +**Lesson**: Never trust "fixed" without verification. Make it show you the actual changes. + +### 4. Permission Denied + +Docker entrypoint scripts kept failing with "permission denied". The AI knew about `chmod +x`, but the order of operations was wrong - file copied after chmod, or Docker cache serving old versions. + +**Lesson**: AI knows facts, but execution order matters. Sometimes you need to walk through the sequence step by step. + +### 5. The 404 Debugging Journey + +Events returned 404. We checked: +1. Routes - correct +2. Mounting - fixed +3. Auth middleware - fixed +4. The actual problem: nginx port mapping. Port 3000 on the host was mapped directly to the backend, not through nginx. The frontend (served by nginx) couldn't reach the API. + +**Lesson**: The AI focused on the obvious layers. The problem was in the infrastructure/configuration layer. AI needs explicit context about the full stack. + +## What Went Well + +Despite these issues, things also went surprisingly well: + +- **Feature implementation**: The core features (event capture, AI generation, search) worked on first try +- **Consistency**: Once a pattern was established, the AI maintained it consistently +- **Refactoring**: Moving from multiple `.env` files to one was smooth after the initial issues +- **Documentation**: README updates, code comments, and AGENTS.md were accurate + +## The Communication Patterns That Worked + +### Be Specific About Failures +Instead of "it doesn't work", I'd say: +> "Events endpoint returns 404, checked docker logs and the route is registered" + +The more context, the better the fix. + +### Ask for Verification +> "Show me the exact changes you're making before committing" + +This caught the "I said I fixed it" problem. + +### Break Down Complex Changes +Instead of "consolidate all env vars", we did it in stages: +1. List all current env vars +2. Decide on naming convention +3. Update backend +4. Update frontend +5. Update docker-compose +6. Verify + +### State What You Know Works +> "Previous similar changes worked with `docker compose build && docker compose up -d`" + +Context about what has worked before helps the AI avoid untested approaches. + +## The Meta-Lesson + +Building with AI is like working with a very knowledgeable junior developer who: +- Has read every Stack Overflow post +- Can write code faster than you can type +- Sometimes confidently does the wrong thing +- Needs supervision, especially for changes spanning multiple files +- Gets better with clearer instructions + +The key insight: **Your job becomes managing the AI, not just writing code.** You need to: +1. Provide good context +2. Verify systematically +3. Catch the invisible gaps +4. Maintain the mental model of the system + +## What I'd Do Differently + +1. **Track changes more carefully** - Use a changelog when AI makes changes, not just git diff +2. **Test incrementally** - Don't let the AI make 20 changes before testing +3. **Be clearer about expectations** - "This should work out of the box" is less clear than explicit test criteria +4. **Document the debugging journey** - The process of finding issues is valuable context for future fixes + +## Conclusion + +DearDiary is live. The AI and I built it together, argued about typos in environment variables, debugged at 2am, and shipped something I'm proud of. + +Human-AI collaboration isn't about replacing programmers. It's about amplifying what humans do well (context, judgment, verification) with what AI does well (speed, consistency, pattern matching). + +The future is not "AI replaces developers." It's "developers who use AI replace developers who don't." + +Now go build something with AI. Just keep an eye on those env vars. + +*— Konrad* diff --git a/www/_posts/2026-03-27-quick-start-guide.md b/www/_posts/2026-03-27-quick-start-guide.md new file mode 100644 index 0000000..01443d4 --- /dev/null +++ b/www/_posts/2026-03-27-quick-start-guide.md @@ -0,0 +1,59 @@ +--- +title: Quick Start Guide +date: 2026-03-26 +author: Konrad Lother +excerpt: How to get started with DearDiary in 5 minutes +--- + +# Quick Start Guide + +Getting started with DearDiary takes about 5 minutes. Here's how: + +## 1. Create an Event + +Press **Ctrl+J** anywhere in the app to open the Quick Add widget. Type your event and press Enter. + +That's it. It takes 3 seconds. + +Try these: +- "Had coffee with Sarah at the new cafe downtown" +- "Finished the chapter on machine learning" +- "Rain started around 3pm, got soaked walking back" + +## 2. Add More Events + +Throughout your day, capture anything that feels worth remembering: +- Meetings and conversations +- Meals and what you ate +- Exercise and how you felt +- Thoughts and ideas +- Photos and voice memos + +Don't overthink it. A short note is better than nothing. + +## 3. Generate Your Diary + +When you're ready, click the **Generate** button on today's page. AI reads all your events and writes a narrative diary entry. + +You can: +- Regenerate with different instructions +- Add context by including previous days' diaries +- Edit the generated diary (but the events stay locked) + +## 4. Review and Reflect + +Read your generated diary. Does it capture the essence of your day? If not, regenerate with instructions like: + +> "Focus more on the interesting conversations I had" + +or + +> "Make it more concise, highlight the key moments" + +## Pro Tips + +- **Be specific**: "Lunch with Marcus, talked about his new hiking trip to Patagonia" beats "Had lunch" +- **Capture emotions**: "Felt anxious before the presentation" is more interesting than "Presented to team" +- **Use voice**: Record voice memos while driving or walking - very efficient + +That's it. Happy journaling! diff --git a/www/build-blog.js b/www/build-blog.js new file mode 100644 index 0000000..c656c9d --- /dev/null +++ b/www/build-blog.js @@ -0,0 +1,200 @@ +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); + +const postsDir = path.join(__dirname, '_posts'); +const outputDir = path.join(__dirname, 'blog'); + +// Ensure output directory exists +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Get all markdown files +const files = fs.readdirSync(postsDir).filter(f => f.endsWith('.md')); + +// Parse and generate HTML for each post +const posts = files.map(file => { + const content = fs.readFileSync(path.join(postsDir, file), 'utf-8'); + const { data: frontmatter, content: markdown } = matter(content); + + // Simple markdown to HTML conversion + let html = markdown + // Headers + .replace(/^### (.*$)/gim, '

$1

') + .replace(/^## (.*$)/gim, '

$1

') + .replace(/^# (.*$)/gim, '

$1

') + // Bold + .replace(/\*\*(.*?)\*\*/g, '$1') + // Italic + .replace(/\*(.*?)\*/g, '$1') + // Code blocks + .replace(/```(\w*)\n([\s\S]*?)```/g, '
$2
') + // Inline code + .replace(/`(.*?)`/g, '$1') + // Links + .replace(/\[(.*?)\]\((.*?)\)/g, '$1') + // Images + .replace(/!\[(.*?)\]\((.*?)\)/g, '$1') + // Line breaks + .replace(/\n\n/g, '

') + // Lists + .replace(/^\- (.*$)/gim, '

  • $1
  • ') + .replace(/(
  • .*<\/li>)/s, ''); + + // Wrap in paragraph + html = `

    ${html}

    `; + + // Clean up empty paragraphs + html = html.replace(/

    <\/p>/g, ''); + + const slug = file.replace('.md', ''); + const url = `/blog/${slug}/`; + + return { + ...frontmatter, + slug, + url, + html, + date: frontmatter.date ? frontmatter.date.toISOString().split('T')[0] : '' + }; +}); + +// Sort posts by date (newest first) +posts.sort((a, b) => new Date(b.date) - new Date(a.date)); + +const relativePath = process.env.GIT_URL ? '' : '../'; + +// Generate index page +const indexHtml = ` + + + + + Blog - DearDiary + + + + +

    + +
    +
    +

    Blog

    +

    Updates, tutorials, and thoughts on AI-powered journaling.

    +
    + + +
    + +`; + +fs.writeFileSync(path.join(outputDir, 'index.html'), indexHtml); + +// Generate individual post pages +posts.forEach(post => { + const postDir = path.join(outputDir, post.slug); + if (!fs.existsSync(postDir)) { + fs.mkdirSync(postDir, { recursive: true }); + } + + const postRelativePath = process.env.GIT_URL ? '' : '../../'; + + const postHtml = ` + + + + + ${post.title} - DearDiary Blog + + + + + + +
    + ← Back to Blog + +
    +
    +

    ${post.title}

    +
    ${post.date}${post.author ? ` · ${post.author}` : ''}
    +
    + +
    + ${post.html} +
    +
    +
    + +`; + + fs.writeFileSync(path.join(postDir, 'index.html'), postHtml); +}); + +console.log(`Generated ${posts.length} blog posts.`); diff --git a/www/package-lock.json b/www/package-lock.json new file mode 100644 index 0000000..5e8c41b --- /dev/null +++ b/www/package-lock.json @@ -0,0 +1,123 @@ +{ + "name": "deardiary-website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "deardiary-website", + "version": "1.0.0", + "dependencies": { + "gray-matter": "^4.0.3" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + } + } +} diff --git a/www/package.json b/www/package.json new file mode 100644 index 0000000..ad35fc6 --- /dev/null +++ b/www/package.json @@ -0,0 +1,7 @@ +{ + "name": "deardiary-website", + "version": "1.0.0", + "dependencies": { + "gray-matter": "^4.0.3" + } +}