Compare commits

..

29 Commits

Author SHA1 Message Date
Eric Wagoner
37c513adb4 Add image render hook to show alt text as tooltips
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 22:44:27 -05:00
Eric Wagoner
1633f977e8 Add weeknote for January 24-30, 2026
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 22:08:03 -05:00
Eric Wagoner
7835af3d00 Date corrections 2026-01-28 09:39:04 -05:00
Eric Wagoner
caf7e826ca Add weeknote for January 17-23, 2026
Week of Inuhele convention in Atlanta, swim meet, international food
exploration in Duluth, 3D printed cormorant pendant, and meeting
MeduSirena the fire-eating mermaid.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-28 01:35:00 -05:00
Eric Wagoner
3dd816e6a6 Fix image file extensions to match actual filenames
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 21:12:51 -05:00
Eric Wagoner
2f3ee7f3de Add weeknote for January 11-17, 2026
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 20:51:14 -05:00
Eric Wagoner
fc1f1c0c67 Wordsmithing and tweaking scripts 2026-01-12 15:31:05 -05:00
Eric Wagoner
1a5d864d9b Add weeknote for January 4-10, 2026 and fix list template excerpts
- New weeknote covering the week's shipped work, reading, gaming, cooking, and observations
- Fixed list templates to show description excerpts consistently across main index and weeknotes page
- Updated layouts/_default/list.html to check .Params.description before .Description
- Updated layouts/weeknotes/single.html to display full excerpts with reading time

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 14:42:55 -05:00
Eric Wagoner
ca05e826f2 Show full article in RSS 2026-01-08 16:26:45 -05:00
Eric Wagoner
44a49088fe Fixed date 2026-01-08 15:08:34 -05:00
Eric Wagoner
9af5c735e4 Publish post 2026-01-08 15:07:02 -05:00
Eric Wagoner
1963ca966d Fixed link to ii blog 2026-01-06 09:37:33 -05:00
Eric Wagoner
7c3f9213b4 Formatting and date tweaks 2026-01-06 09:24:39 -05:00
Eric Wagoner
c52dc603e1 Update resume with expanded role history and tech details
- Add detailed progression through Infinity Interactive roles (2016-present)
- Add Elasticsearch to database skills
- Update infrastructure certifications (AWS, Atlassian)
- Update cloud platforms (GitHub, Google Cloud)
- Refine job titles for clarity

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-05 21:49:03 -05:00
Eric Wagoner
87707aa61c Add resume page and publish "I Thought I Had 15 Minutes" post
- Add standalone resume at /resume with professional two-column layout
- Publish blog post about automating standup prep
- Update post draft status to false

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-05 19:43:12 -05:00
Eric Wagoner
047905c51e Add weeknotes for December 28, 2025–January 3, 2026
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-04 00:37:36 -05:00
Eric Wagoner
f4ba98acb5 Fix post date to January 1, 2026
Update the cake post date from December 30, 2025 to January 1, 2026
since it was written and published today. Also renamed the folder to
match the correct date.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 14:25:19 -05:00
Eric Wagoner
fb2803ea69 Update cake post to use figure shortcodes with centered captions
Convert all images in the Yule log post to use Hugo's figure shortcode,
which provides centered, italicized captions below each image. The
existing CSS styling already handles the visual presentation with
drop shadows and proper spacing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 14:19:23 -05:00
Eric Wagoner
98607f936d Add new blog post: Either Way, There'll Be Cake
A story about making a gluten-free Yule log cake for second Christmas
with the family, complete with sugared cranberries, meringue mushrooms,
and a cake that split into sections but came together anyway.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-01 13:58:41 -05:00
Eric Wagoner
70847b14fb Add custom 404 error page
- Create custom 404 page matching blog layout and Eric's voice
- Includes helpful navigation links to home, tags, weeknotes, search
- References blog migrations since 2001 in a characteristically honest way
- Server-side nginx config added to blog.kestrelsnest.social.d/custom_errors.conf

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 11:36:02 -05:00
Eric Wagoner
84e54b08de Add weeknotes system with first entry
- Add weeknotes landing page with auto-listing of all entries
- Create first weeknote: December 20–27, 2025
- Add Weeknotes to secondary menu alongside Now/Then/Future
- Create custom layout to display all weeknotes chronologically
- Update weeknotes command to use date ranges and current time
- Credit Genehack for weeknotes inspiration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-27 11:06:20 -05:00
Eric Wagoner
2b98d186ef Add weeknotes system with /weeknotes command
- Create weeknotes archetype for manual post creation
- Add /weeknotes slash command that prompts through each section
  interactively (shipped, read, played, cooked, noticed, thinking, next)
- Add /weeknotes landing page explaining the concept
- Update CLAUDE.md with weeknotes documentation and voice guidelines

Weeknotes are brief weekly reflections—lighter than full posts, more
personal journal than polished article.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 14:11:55 -05:00
Eric Wagoner
77d818a7f2 Add temporal pages maintenance reminder to CLAUDE.md
Adds a section reminding to check /now, /then, and /upcoming pages
for needed updates when doing other blog work. These three pages
form a temporal view that should stay in sync with blog content.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 14:01:42 -05:00
Eric Wagoner
c4db5f6ce8 Update now/then/upcoming pages for December 2025
- /now: Add games (Outer Worlds 2, AC Valhalla, Gloomhaven), writing updates,
  Random Recipe Project resumption, upcoming events (Inuhele, CONpossible)
- /then: Add LocallyGrown migration, Random Recipe Project (50+ episodes),
  Kestrel's Nest federation, recent books from BookWyrm, events attended
- /upcoming: Add Inuhele and CONpossible with dates/venues, Loops for YunoHost,
  tiny apps, creative projects, n8n/Claude Code exploration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 13:48:45 -05:00
Eric Wagoner
b727f92496 Add pantry inventory post with styled images
- Add "Where Did I Put the Mochi Flour?" post about pantry organization app
- Add img shortcode for figure/caption support across themes
- Add CSS styling for figures: rounded corners, shadows, centered captions
- Update standup post date to 2026-01-06

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-24 16:29:55 -05:00
Eric Wagoner
3b30bd0323 Add two new blog posts and support preview frontmatter
- Add "I Thought I Had 15 Minutes" post about standup automation (draft)
- Add "Why I Self-Host My Social Media" post about YunoHost setup
- Update list.html to use preview param for homepage listings, falling back to description

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-20 20:56:21 -05:00
Eric Wagoner
2c5632651a Add new blog post: Why My Payment Agent Is Named George
Personal essay on naming AI sub-agents after inspiring humans as a practice
for staying intentional about who the work serves.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:24:41 -05:00
Eric Wagoner
1a432fd8c2 Add new blog post: Forty Years of Code, Still Can't Type
Personal reflection on impostor syndrome and neurodivergent coding experience. Discusses 40+ years of self-taught development while struggling with mechanical typing precision, the unsustainable "code frenzies" coping mechanism, and how AI-assisted development (Claude Code) eliminated the transcription bottleneck.

Also updates CLAUDE.md with comprehensive voice and style guide for future writing assistance.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-17 20:06:28 -05:00
Eric Wagoner
744235b47f Add LocallyGrown series part 6 and implement series navigation
- Add final part of LocallyGrown.net series: "From Survival to Sustainability"
- Implement Hugo series taxonomy for better content organization
- Add series navigation partial showing all 6 parts with prev/next links
- Update all LocallyGrown posts with series metadata and ordering
- Display series indicators on index page for connected content
- Style series navigation with responsive design and dark mode support
- Fix series links across all posts to use correct slugs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-29 01:12:22 -04:00
76 changed files with 2401 additions and 1051 deletions

View File

@@ -0,0 +1,108 @@
---
description: Interactive weeknotes blog post generator - prompts through each section and creates the post
---
You are helping Eric create his weekly weeknotes blog post. Weeknotes are personal reflections on the week—what happened, what was made, what was noticed. They should feel like a genuine personal update, not a status report.
## Reference Material
Before starting, read these resources to understand Eric's current context:
- `/content/now.md` - what Eric is currently working on
- Recent posts in `/content/posts/` - for voice and recent activity context
## Gathering Information
Use the AskUserQuestion tool to prompt Eric for each section. Ask these one at a time, allowing him to skip any with "skip" or "-":
1. **Shipped**: "What did you ship, build, fix, or finish this week? (Projects, features, code, creative work, household accomplishments)"
2. **Read**: "What did you read this week? (Books, articles, documentation, interesting threads)"
3. **Played**: "What did you play this week? (Video games, board games, experiments, fun side projects)"
4. **Cooked**: "What did you cook this week? (New recipes, Random Recipe Project experiments, notable meals)"
5. **Noticed**: "What did you notice this week? (In the garden, neighborhood, weather, cats, life in general)"
6. **Thinking About**: "What's on your mind? (Ideas brewing, problems you're chewing on, topics you keep returning to)"
7. **What's Next**: "What's coming up? (Plans for next week, upcoming events, things you're looking forward to)"
8. **Vibe Check**: "In one sentence, what was the overall feel of this week?"
## Generating the Post
After gathering responses, create the weeknotes post:
1. **Calculate the filename**: Use today's date in format `YYYY-MM-DD-weeknotes.md`
2. **Generate frontmatter**:
```yaml
---
title: "Weeknotes: [Start date][End date], [Year]" (e.g., "December 2027, 2025")
date: [ISO 8601 datetime with -05:00 timezone, set to current time to avoid future-date issues]
draft: false
tags:
- weeknotes
---
```
3. **Write the opening**: Take the "vibe check" response and craft it into an italicized opening tagline that captures the week's essence.
4. **Write each section**: Only include sections where Eric provided content (skip empty ones). For each:
- Use `## Section Name` heading
- Transform bullet points into brief, punchy prose or keep as bullets depending on content
- Add relevant links where appropriate
- Keep Eric's voice: concrete, specific, honest
5. **Voice guidelines** (from CLAUDE.md):
- Concrete over abstract—real project names, actual numbers
- Brief sentences work well for weeknotes
- Honest about setbacks alongside wins
- Forward momentum at the end
## Creating the File
Save the post to `/Users/ericwagoner/Sites/blog/content/posts/[date]-weeknotes.md`
## After Creation
Show Eric the generated post content and ask:
- "Want me to start the Hugo dev server so you can preview it?"
- "Any sections you'd like me to revise?"
If he wants to preview, run `hugo server -D` in the blog directory.
## Example Output
```markdown
---
title: "Weeknotes: December 2027, 2025"
date: 2025-12-27T10:45:00-05:00
draft: false
tags:
- weeknotes
---
_A week of small wins and steady momentum._
## Shipped
Built a pantry inventory app in two hours with Claude Code. Simple PHP/HTML, deployed to YunoHost. Already used it at the grocery store.
## Read
Finished the first few chapters of *Service Model* by Adrian Tchaikovsky. The robot narrator's voice is delightful.
## Played
More Outer Worlds 2. The corporate dystopia hits different when you're on vacation.
## Noticed
The cats have claimed the heating vent by my desk. Territorial negotiations ongoing.
## What's Next
Inuhele prep is ramping up. Need to finalize my schedule and pack the tiki shirts.
```

View File

@@ -6,9 +6,18 @@
"Bash(cat:*)", "Bash(cat:*)",
"Read(//Users/ericwagoner/Downloads/**)", "Read(//Users/ericwagoner/Downloads/**)",
"Bash(mysql:*)", "Bash(mysql:*)",
"Read(//Users/ericwagoner/Sites/ericwagoner.com/**)" "Read(//Users/ericwagoner/Sites/ericwagoner.com/**)",
"Bash(curl:*)",
"Bash(ssh:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(./deploy)",
"Bash(mkdir:*)",
"Bash(chmod:*)",
"Bash(ls:*)",
"Bash(pkill:*)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []
} }
} }

View File

@@ -1 +1 @@
{} {"content":{"posts":{"2025-12-30-either-way-therell-be-cake":{}}}}

1
.vscode/ltex.dictionary.en-US.txt vendored Normal file
View File

@@ -0,0 +1 @@
standup

View File

@@ -11,5 +11,6 @@
"other": "off" "other": "off"
}, },
"editor.minimap.enabled": false "editor.minimap.enabled": false
} },
"frontMatter.panel.openOnSupportedFile": true
} }

176
CLAUDE.md
View File

@@ -52,12 +52,34 @@ hugo new content-name.md
The blog uses the "m10c" theme but has two additional themes available (henry, kestrel). Theme switching is done via `config.toml`. The blog uses the "m10c" theme but has two additional themes available (henry, kestrel). Theme switching is done via `config.toml`.
### Content Organization ### Content Organization
- `/content/posts/` - Blog posts
- `/content/posts/` - Blog posts (including weeknotes)
- `/content/now.md` - "Now" page showing current activities - `/content/now.md` - "Now" page showing current activities
- `/content/then.md` - "Past" page with historical content - `/content/then.md` - "Past" page with historical content
- `/content/upcoming.md` - "Future" page with upcoming events - `/content/upcoming.md` - "Future" page with upcoming events
- `/content/weeknotes.md` - Weeknotes landing page
- `/content/mytweets.md` - Local tweet archive page - `/content/mytweets.md` - Local tweet archive page
### Weeknotes
Weeknotes are short weekly reflections on what Eric shipped, read, played, cooked, and noticed. They use a lighter voice than full blog posts—more personal journal than polished article.
**To create a weeknote**: Run `/weeknotes` to be prompted through each section interactively.
**Manual creation**: `hugo new --kind weeknotes posts/YYYY-MM-DD-weeknotes.md`
**Sections** (all optional—skip any that don't apply):
- Shipped — projects, features, fixes, accomplishments
- Read — books, articles, interesting finds
- Played — games, experiments, fun projects
- Cooked — recipes, Random Recipe Project work
- Noticed — observations from garden, neighborhood, life
- Thinking About — ideas brewing, problems being chewed on
- What's Next — upcoming plans and goals
**Voice for weeknotes**: Brief, punchy, concrete. Less polished than regular posts. Okay to be fragmentary. The goal is marking time and noticing patterns, not crafting essays.
### Menu Structure ### Menu Structure
The site has four menu groups configured in `config.toml`: The site has four menu groups configured in `config.toml`:
- **main**: Home and Tags navigation - **main**: Home and Tags navigation
@@ -75,4 +97,154 @@ The site has four menu groups configured in `config.toml`:
- The Hugo server automatically watches for changes in `/archetypes`, `/assets`, `/content`, `/data`, `/layouts`, `/static`, and `/themes` - The Hugo server automatically watches for changes in `/archetypes`, `/assets`, `/content`, `/data`, `/layouts`, `/static`, and `/themes`
- Posts are created with draft status by default (controlled by archetype) - Posts are created with draft status by default (controlled by archetype)
- The site integrates with multiple social platforms and services in the Kestrel's Nest ecosystem - The site integrates with multiple social platforms and services in the Kestrel's Nest ecosystem
### Temporal Pages Maintenance
When doing any work on the blog, check if the following pages need updates:
- **`/content/now.md`** - Current activities, projects, reading, playing, upcoming events
- **`/content/then.md`** - Completed projects, finished books/games, past events
- **`/content/upcoming.md`** - Future plans, events, projects in the pipeline
These three pages form a temporal view of Eric's life. New blog posts often signal changes that should be reflected here:
- A new project post might mean something moved from `/upcoming` to `/now`
- A project completion post means moving from `/now` to `/then`
- Event announcements should appear in `/upcoming` with dates/venues
- After events pass, move them to `/then`
Each page includes a "last updated" date in the opening paragraph—update this when making changes.
## Eric's Writing Voice & Style Guide
When writing or editing blog posts, maintain Eric's distinctive voice and structural patterns:
### Structural Patterns
1. **Opening Elements**
- Start with an italicized tagline that captures the core insight (e.g., "_Three thousand passing tests don't mean your system works_")
- Include a TL;DR section early in technical posts, using callout boxes for key summaries
- Use horizontal rules (`---`) to separate major sections
2. **Callout Boxes** (use Hugo shortcode syntax)
- Extensively use callout boxes for warnings, examples, quotes, and key insights
- Types: `info`, `warning`, `success`, `danger`, `note`, `quote`, `example`
- These are structural signposts, not decoration—they highlight critical information
- Example: `{{< callout type="info" title="Why This Matters" >}}...{{< /callout >}}`
3. **Section Structure**
- Use numbered breakdowns for complex topics (Bug Category #1, #2, etc.)
- End sections with concrete metrics or results
- Include "What's Next" or forward-looking sections in series posts
### Tone & Voice Characteristics
1. **Brutal Honesty with Technical Confidence**
- Be unflinchingly honest about failures and mistakes
- Never self-pitying—own mistakes but frame as learning
- Mix emotional vulnerability with technical precision
- Examples: "The Most Embarrassing Bug," "My morale was at an all-time low" paired with detailed technical analysis
2. **Concrete Over Abstract**
- Always ground statements in specific numbers, metrics, and examples
- Replace abstract lists with detailed scenarios showing actual time breakdowns
- Use real project names (LocallyGrown.net) and specific technologies (Rails, SvelteKit, Claude Code)
- Before/after comparisons with actual measurements: "8-12 commits per week" → "40-50 commits per week"
3. **Show the Work**
- Include code snippets (even wrong code) with explanations
- Provide specific numbers: "314 commits in 18 days," "$1.3 million in annual sales"
- Show actual dialogue or process breakdowns
- Time breakdowns: "Total: 25 minutes. The thinking took 2. The transcription took 23."
4. **Characteristic Phrases**
- "Here's what matters:"
- "The numbers tell the story:"
- "The real X was always Y"
- "This wasn't about X. It was about Y."
- Rhetorical questions followed by answers
- Extensive parenthetical asides: "(after comparing Prisma, TypeORM, and others)"
5. **Human Impact Focus**
- Always circle back to impact on real people—users, customers, communities
- Frame personal victories as community victories
- End with forward-looking perspective, not just resolution
- Connect technical decisions to human consequences
6. **Technical Communication**
- Never assume reader is technical—explain even when showing code
- Balance technical specifics with human impact
- Use technical details to prove points, not to show off
- Include both the code AND the why
### Content Patterns
1. **Examples Over Explanation**
- Transform abstract concepts into concrete scenarios
- Show specific workflows with timing breakdowns
- Include real-world examples from actual projects
- Use before/after comparisons with measurable differences
2. **Data-Driven Narratives**
- Support claims with specific metrics
- Track and report actual measurements
- Use Git logs, analytics, user counts as evidence
- Quantify time savings, performance improvements, impact
3. **Acknowledgment & Gratitude**
- Credit others extensively (market managers, designers, contributors)
- Acknowledge tools and their creators
- Recognize community contributions
- Human names for AI agents (Ray, Agatha, Ada) to maintain human-centered perspective
4. **Balanced Disclaimers**
- Use callout boxes for important context or disclaimers
- Acknowledge different approaches work for different people
- "What This Isn't" sections to clarify boundaries
- Respectful of different working styles and tools
### Series & Long-Form Posts
For multi-part series (like the LocallyGrown series):
- Include series navigation at the end
- Link to related posts with clear context
- Use consistent callout styles across the series
- Build narrative arc across posts
- Reference earlier posts when relevant
### Example Transformations
**Abstract → Concrete:**
Before: "Most of my time was spent on mechanical tasks"
After: "I tracked it once: 4 hours of actual coding work produced 3.5 hours of fixing typos, hunting syntax, and triple-checking. The ratio was backwards."
**General → Specific:**
Before: "The platform performs well"
After: "Response times are consistently under 2 seconds, even during peak Sunday night ordering when 200+ customers are browsing simultaneously."
**Personal → Community:**
Before: "I finally solved the problem"
After: "The fix went live Tuesday morning. By Wednesday, three market managers reported their volunteers could finally access the tools they needed. Physical workflow restored."
### Voice Checklist for Posts
- [ ] Opens with italicized tagline
- [ ] Includes TL;DR with metrics (for technical posts)
- [ ] Uses 2-4 callout boxes for key insights
- [ ] Contains specific numbers and measurements
- [ ] Shows concrete examples, not just abstractions
- [ ] Balances vulnerability with technical confidence
- [ ] Includes impact on real people/communities
- [ ] Ends with forward-looking perspective
- [ ] Credits others and acknowledges contributions
- [ ] Uses characteristic phrases ("Here's what matters," "The numbers tell the story")
This voice reflects someone who is deeply technical, brutally honest about challenges, focused on real-world impact, and committed to serving communities—not just building software.

37
archetypes/weeknotes.md Normal file
View File

@@ -0,0 +1,37 @@
---
title: "Weeknotes: {{ dateFormat "January 2, 2006" .Date }}"
date: {{ .Date }}
draft: true
tags:
- weeknotes
---
_[One sentence capturing the week's vibe]_
## Shipped
-
## Read
-
## Played
-
## Cooked
-
## Noticed
-
## Thinking About
-
## What's Next
-

View File

@@ -16,4 +16,221 @@
} }
// Import callout styles // Import callout styles
@import 'callouts'; @import 'callouts';
// Series indicator on list pages
.series-indicator {
display: inline-block;
padding: 0.25rem 0.75rem;
margin-left: 0.5rem;
background: var(--series-bg, #e3f2fd);
color: var(--series-text, #1976d2);
border-radius: 4px;
font-size: 0.85rem;
font-weight: 500;
vertical-align: middle;
svg {
width: 14px;
height: 14px;
margin-right: 0.25rem;
vertical-align: -2px;
}
}
@media (prefers-color-scheme: dark) {
.series-indicator {
background: rgba(33, 150, 243, 0.15);
color: rgba(144, 202, 249, 1);
}
}
// Series navigation styling
.series-box {
background: var(--card-bg, #f5f5f5);
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 8px;
padding: 1.5rem;
margin: 2rem 0;
.series-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
font-size: 1.1rem;
.series-part {
color: var(--secondary-text, #666);
font-size: 0.9rem;
}
}
.series-nav {
margin: 1rem 0;
.series-list {
list-style: none;
padding-left: 0;
margin: 0;
li {
padding: 0.5rem 0;
border-left: 3px solid transparent;
padding-left: 1rem;
&.current {
border-left-color: var(--primary-color, #007bff);
background: var(--highlight-bg, rgba(0, 123, 255, 0.05));
margin-left: -1rem;
padding-left: calc(1rem + 1rem);
margin-right: -1rem;
padding-right: 1rem;
}
a {
color: var(--link-color, #007bff);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
}
.series-navigation {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--border-color, #e0e0e0);
.series-nav-links {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
.series-prev,
.series-next {
flex: 1;
padding: 0.5rem 1rem;
border-radius: 4px;
text-decoration: none;
transition: background-color 0.2s;
}
.series-prev {
text-align: left;
background: var(--button-bg, #f0f0f0);
color: var(--link-color, #007bff);
&:hover:not(.disabled) {
background: var(--button-hover-bg, #e0e0e0);
}
&.disabled {
color: var(--disabled-text, #999);
cursor: not-allowed;
}
}
.series-next {
text-align: right;
background: var(--button-bg, #f0f0f0);
color: var(--link-color, #007bff);
&:hover:not(.disabled) {
background: var(--button-hover-bg, #e0e0e0);
}
&.disabled {
color: var(--disabled-text, #999);
cursor: not-allowed;
}
}
}
}
}
// Image and figure styling
figure {
margin: 2rem 0;
img {
display: block;
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
figcaption {
margin-top: 0.75rem;
font-size: 0.9rem;
color: var(--secondary-text, #666);
text-align: center;
font-style: italic;
}
}
// Also style standalone images in posts
.post-content img:not(figure img) {
display: block;
max-width: 100%;
height: auto;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
margin: 2rem auto;
}
@media (prefers-color-scheme: dark) {
figure {
img {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
figcaption {
color: rgba(255, 255, 255, 0.7);
}
}
.post-content img:not(figure img) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
}
// Dark mode support
@media (prefers-color-scheme: dark) {
.series-box {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
.series-header .series-part {
color: rgba(255, 255, 255, 0.7);
}
.series-nav .series-list li.current {
background: rgba(255, 255, 255, 0.05);
}
.series-navigation {
border-top-color: rgba(255, 255, 255, 0.1);
.series-nav-links {
.series-prev,
.series-next {
background: rgba(255, 255, 255, 0.05);
&:hover:not(.disabled) {
background: rgba(255, 255, 255, 0.1);
}
&.disabled {
color: rgba(255, 255, 255, 0.3);
}
}
}
}
}
}

View File

@@ -2,6 +2,11 @@ baseURL = 'https://blog.kestrelsnest.social'
languageCode = 'en-us' languageCode = 'en-us'
title = "Kestrel's Nest" title = "Kestrel's Nest"
theme = "m10c" theme = "m10c"
[taxonomies]
tag = "tags"
category = "categories"
series = "series"
[params] [params]
author = "Eric Wagoner" author = "Eric Wagoner"
avatar = "face.jpg" avatar = "face.jpg"
@@ -92,6 +97,11 @@ theme = "m10c"
name = "Future" name = "Future"
url = "/upcoming/" url = "/upcoming/"
weight = 3 weight = 3
[[menu.secondary]]
identifier = "weeknotes"
name = "Weeknotes"
url = "/weeknotes/"
weight = 4
[[menu.tertiary]] [[menu.tertiary]]
identifier = "mytweets" identifier = "mytweets"
name = "Local Tweet Archive" name = "Local Tweet Archive"

View File

@@ -10,13 +10,11 @@ title: Eric in the Present
[blog]: https://blog.kestrelsnest.social [blog]: https://blog.kestrelsnest.social
[mastodon]: https://toots.kestrelsnest.social/@eric [mastodon]: https://toots.kestrelsnest.social/@eric
[pics]: https://pix.kestrelsnest.social/@eric [pics]: https://pix.kestrelsnest.social/@eric
[ericsayshi]: https://podcasts.kestrelsnest.social/@EricSaysHi
[grimmlunch]: https://grimmlunch.org
[locallygrown]: /posts/locallygrown-origin-story/ [locallygrown]: /posts/locallygrown-origin-story/
[randomrecipe]: https://www.youtube.com/@RandomRecipeProject [randomrecipe]: https://www.youtube.com/@RandomRecipeProject
[conpossible]: https://www.conpossible.com [conpossible]: https://www.conpossible.com
This page is all about what I am doing *now*. It was last updated on September 23, 2025, and will be edited as things change. This page is all about what I am doing *now*. It was last updated on December 25, 2025, and will be edited as things change.
## Where I am now ## Where I am now
@@ -32,30 +30,38 @@ We [lost Charlie](/posts/2023-07-24-goodbye,-charlie/) but our two remaining cat
I'm the Vice President of Technology at [Infinity Interactive][infinity]. Our whole industry is in turmoil, so work is hard. We're doing a wide range of consulting—from technical infrastructure and software development to workflows and solving both simple and complex problems. Basically, all things having to do with technology and helping organizations organize what they have. I'm the Vice President of Technology at [Infinity Interactive][infinity]. Our whole industry is in turmoil, so work is hard. We're doing a wide range of consulting—from technical infrastructure and software development to workflows and solving both simple and complex problems. Basically, all things having to do with technology and helping organizations organize what they have.
Just completed a massive six-month migration of [LocallyGrown.net][locallygrown] from dying Rails 3 infrastructure to modern SvelteKit. The migration to new infrastructure is complete, but now the real journey is just beginning as we can take advantage of all the things the modern platform has to offer. Time to write new features, grow the service, and take it to the next couple of decades. [LocallyGrown.net][locallygrown] has settled into a rhythm of steady improvements after the massive six-month migration from Rails 3 to SvelteKit. The infrastructure work is behind us; now it's about new features, better tools for market managers, and growing the platform that serves 70+ farmers markets.
## What I am reading now ## What I am reading now
Just finished a new short story by Dan Moren set in his Galactic Cold War series—it was wonderful. Currently a few pages into *Service Model* by Adrian Tchaikovsky. Currently a few chapters into *Service Model* by Adrian Tchaikovsky.
## What I am playing now
- **The Outer Worlds 2** — A whimsically dark RPG from Obsidian in the tradition of Fallout. Delightfully cynical corporate dystopia.
- **Assassin's Creed: Valhalla** — Viking exploration on the Xbox.
- **Gloomhaven** — Finally playing with a regular group after the game sat on my shelf for years.
## What else? ## What else?
### Emerging from the LocallyGrown Marathon ### Writing Again
The LocallyGrown work consumed 100% of my free time over the last five months. Everything got dropped. I'm slowly emerging from that intensity and starting to pick things back up. Kind of in recovery mode, just slowly coming back out and getting to be myself again.
### Creative Projects Resuming After months of silence, I'm writing regularly again—both here and at the [Infinity Interactive blog](https://iinteractive.com/resources/blog). The words are flowing.
- Working on a corset vest sewing project that I'm super excited about
- Eager to get back to my YouTube cooking series, the [Random Recipe Project][randomrecipe] ### Creative Projects
- Haven't released podcast episodes for [Eric Says Hi][ericsayshi] or [Brothers Grimm Lunch][grimmlunch] in quite a while, but want to start again—I just need to do it
- Eyeing some leather working projects with supplies sitting ready for this fall - Recording, editing, and posting cooking videos for the [Random Recipe Project][randomrecipe] again
- Still have that corset vest sewing project waiting—fabric ready, pattern chosen
- Leather working supplies sitting ready for when I find the time
### Upcoming Events ### Upcoming Events
- **Wild Rumpus** (end of October) - Athens' big outdoor Halloween parade festival. Need to decide on and create my costume soon!
- **[CONpossible][conpossible]** (2026) - I'm the Costuming Track Director for this annual convention that started as steampunk but has expanded to embrace all of the "punks." This year's theme is "Through the Faerie Ring"—magic mixed with technology should be a lot of fun - **[Inuhele](https://inuhele.com)** (January 2325, 2026) — Atlanta's annual Tiki Weekend. I'm on staff.
- **[CONpossible][conpossible]** (February 68, 2026) — I'm the Costuming Track Director. This year's theme is "Through the Faerie Ring"—magic mixed with technology.
## Where my head is ## Where my head is
Really just trying to emerge from the LocallyGrown marathon. I knew it was going to take a lot out of me, but I wasn't quite ready for how much effort it took and how all-consuming it was. Slowly rediscovering equilibrium and reconnecting with the creative projects that feed my soul. The LocallyGrown marathon is behind me. Three months out from the migration, I've found my rhythm again. Writing is happening. Videos are being made. Games are being played. The creative projects that feed my soul are no longer waiting—they're in motion.
--- ---

View File

@@ -10,6 +10,8 @@ categories:
- locallygrown - locallygrown
lastmod: 2025-09-22T20:54:42.989Z lastmod: 2025-09-22T20:54:42.989Z
slug: locallygrown-origin-story slug: locallygrown-origin-story
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 1
--- ---
_How a failed wholesale experiment in Athens, Georgia became the foundation for nationwide local food systems_ _How a failed wholesale experiment in Athens, Georgia became the foundation for nationwide local food systems_
@@ -251,4 +253,4 @@ _This is part one of a series documenting the creation, evolution, and rescue of
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/)
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -12,6 +12,8 @@ lastmod: 2025-09-22T20:54:49.481Z
keywords: keywords:
- locallygrown - locallygrown
slug: locallygrown-rescue-mission slug: locallygrown-rescue-mission
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 2
--- ---
_How a platform serving thousands of farmers and customers nearly died from its own success—and the impossible choice I had to make to save it_ _How a platform serving thousands of farmers and customers nearly died from its own success—and the impossible choice I had to make to save it_
@@ -253,4 +255,4 @@ _This is part two of a series documenting the rescue and modernization of Locall
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/)
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -17,6 +17,8 @@ keywords:
- svelte - svelte
- rails - rails
slug: locallygrown-rails-svelte-migration slug: locallygrown-rails-svelte-migration
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 3
--- ---
_How I migrated decades of complex ActiveRecord relationships, subdomain multitenancy, and business logic to a modern stack without losing a single feature_ _How I migrated decades of complex ActiveRecord relationships, subdomain multitenancy, and business logic to a modern stack without losing a single feature_
@@ -985,4 +987,4 @@ Part 4 is the messy part: real users, real money, and the bugs that only show up
3. **The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit**_You are here_ 3. **The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit**_You are here_
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -17,6 +17,8 @@ keywords:
- svelte - svelte
- rails - rails
slug: locallygrown-reality-of-production slug: locallygrown-reality-of-production
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 4
--- ---
_Three thousand passing tests don't mean your system works_ _Three thousand passing tests don't mean your system works_
@@ -351,4 +353,4 @@ _This is part four of a series documenting the rescue and modernization of Local
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/)
4. **The Reality of Production: When Hope Meets Live Users**_You are here_ 4. **The Reality of Production: When Hope Meets Live Users**_You are here_
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/) 5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -22,6 +22,8 @@ keywords:
- rails - rails
- claude - claude
slug: locallygrown-lessons slug: locallygrown-lessons
series: ["LocallyGrown.net: The 23-Year Journey"]
series_order: 5
--- ---
_Advanced Developer Tools, Real Tradeoffs_ _Advanced Developer Tools, Real Tradeoffs_
@@ -1177,4 +1179,4 @@ _This is part five of a series documenting the rescue and modernization of Local
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/) 3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-architecture-challenge/)
4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/) 4. [The Reality of Production: When Hope Meets Live Users](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. **Lessons from the Solo Developer Using Modern Tools**_You are here_ 5. **Lessons from the Solo Developer Using Modern Tools**_You are here_
6. The Future: Building on Modern Foundations 6. [From Survival to Sustainability: The Next 20 Years for LocallyGrown.net](https://blog.kestrelsnest.social/posts/locallygrown-survival-to-sustainability/)

View File

@@ -0,0 +1,241 @@
---
title: "From Survival to Sustainability: The Next 20 Years for LocallyGrown.net"
description: Six weeks after the rebuild, the crisis is over. Heres the focused roadmap—EBT access, delivery tools, and quiet reliability—for community-run, multi-grower markets.
date: 2025-09-29T04:24:13.598Z
preview: ""
draft: false
tags:
- locallygrown
categories:
- locallygrown
lastmod: 2025-10-03T16:29:08.958Z
keywords:
- locallygrown
slug: locallygrown-survival-to-sustainability
series:
- "LocallyGrown.net: The 23-Year Journey"
series_order: 6
---
_Why saving a 23-year-old farmers market platform matters more now than ever—and how modern architecture enables the next chapter of local food systems_
---
## TL;DR
{{< callout type="warning" title="The Path Forward" >}}
Six weeks post-launch, I'm still squashing bugs daily, but the existential crisis is over. The platform that was one hardware failure away from extinction now has a solid foundation. With a clear niche (community-run, multi-grower cooperative markets), I'm focused on thoughtful improvements: automation that removes friction, EBT support for equitable access, and delivery tools for the last mile. This is about preserving vital community infrastructure that hundreds of growers and thousands of customers rely on.
{{< /callout >}}
---
Over the last five posts, I've walked you through a 23-year journey. I started with an accidental discovery that became pioneering infrastructure for a movement that barely existed. I sat with you in the crushing weight of a 14-year technical stagnation that brought the platform to the brink of extinction. I dissected the six-month, part-time rescue mission to rebuild everything from scratch, the architectural choices that made it possible, and the modern tools that made it feasible for a solo developer. And I lived through the brutal, humbling reality of the post-launch "bug apocalypse".
Six weeks after launch, after hundreds of commits and countless fixes, the platform has stabilized. I'm still fixing bugs (two just today) and keeping an eagle eye on Sentry metrics while refining performance. But the constant, gnawing anxiety of imminent server failure that I lived with for years is gone. The old Rails app that could die at any moment has been replaced by a modern system that, while still needing attention, is fundamentally sound. For the first time in a long time, I can think beyond just keeping the lights on. I can think about the future.
But the world LocallyGrown.net has been reborn into is not the one it left behind. The local food movement I helped nurture is now a vibrant, competitive industry. So, for this final post in the series, I want to talk about what's next. It's time to look at the crowded landscape I now find myself in, define my place in it, and chart a course for the future.
## Finding My Place in a Crowded Field
When I started this in 2002, the idea of an online farmers market was a novelty. Today, it's a standard. Dozens of companies, many of them well-funded, now offer sophisticated platforms for selling local food. To understand where I go from here, I first have to understand where I fit.
The competitive landscape is diverse, with platforms specializing in different parts of the local food ecosystem:
- **Platforms for Individual Farms:** Solutions like GrazeCart are built "by farmers for farmers," offering an all-in-one system for a single farm to manage its e-commerce and in-person POS sales, with a strong focus on features like selling meat by weight.
- **Comprehensive Food Hub Software:** Competitors like Local Food Marketplace provide powerful, all-in-one SaaS solutions for established food hubs, farms, and CSAs that need to manage multiple sales channels, from direct-to-consumer to wholesale.
- **The Modern SaaS Powerhouse:** Then there's a competitor like Local Line, a feature-rich "farm-to-fork commerce platform" that serves both suppliers and wholesale buyers. With sophisticated tools for subscriptions, custom price lists for different customer types (retail vs. wholesale), and advanced inventory management, it's a powerful option for farms ready to scale their operations.
Seeing this, it would be easy to feel outmatched. But that analysis is missing the point. It's also missing my history. These platforms aren't competitors so much as fellow travelers, each serving different needs in the growing local food ecosystem. The diversity of solutions reflects the diversity of communities we all serve. Were not competing to replace each other so much as completing the ecosystem from different angles.
LocallyGrown.net was never just a generic e-commerce platform. It was built to solve a specific problem for a specific type of organization: the community-run, multi-grower, cooperative online market. My core innovation wasn't just a shopping cart; it was the "Predictable Harvesting" model that the software enabled. Farmers harvest only what's been pre-ordered, eliminating waste while giving customers the full selection of a farmers market with the convenience of online shopping. I helped de-risk small-scale farming by eliminating waste and gave customers unprecedented choice and convenience, combining the best of farmers markets, CSAs, and buying clubs.
That is still my niche. That is still my strength. I am not trying to build the best platform for a single farm selling meat, nor am I building a sourcing platform for national grocery chains. I am, and will remain, focused on building the best platform for a community that wants to build its own online market together. My 23 years of experience are focused on that single, powerful goal.
## The Path Forward: From Stability to Service
The rescue was about survival. The future is about service. Having a stable, modern platform means I can finally stop patching holes and start building bridges. I can add features that make a real difference in the day-to-day lives of the market managers and growers who depend on this system. I can't match the big competitors feature for feature, but I can be smarter and more focused. My roadmap is about adding thoughtful improvements that align with the core mission of the platform.
### 1. Small Automations, Big Impact
One of the most consistent pieces of feedback I get revolves around the manual work of running a market cycle. A simple but powerful improvement is to automate the market schedule. Market managers should be able to set their ordering windows (when the market opens for shopping and when it closes) on a recurring schedule and then forget about it. No more late-night logins to manually open the market. This is a perfect example of a subtle automation that removes a recurring point of friction and frees up a manager's time for more important work.
### 2. Expanding Access with Online EBT
A core principle of local food is that it should be for everyone. A major step toward that goal is accepting SNAP/EBT payments online. This is not a simple feature to add. It involves a rigorous authorization process with the USDA's Food and Nutrition Service (FNS) to become an approved online SNAP retailer. An e-commerce platform must meet specific technical requirements for secure, encrypted PIN entry and submit a letter of intent to the FNS to even begin the process. While SNAP benefits can only be used for eligible food items and not for delivery or service fees, offering this payment option is a critical quality-of-life improvement that makes local food more accessible to more members of the community. It's a complex challenge, but it's one that goes to the very heart of what LocallyGrown.net is about.
{{< callout type="info" title="Why this matters" >}}
Nationally, SNAP participation skews rural, and rural broadband can be spotty. Enabling secure online EBT reduces travel burdens, expands choice, and keeps dollars circulating locally.
{{< /callout >}}
### 3. Solving the Last Mile: Better Delivery Support
As more markets explore delivery, the logistical challenges become clear. It's one thing to have customers pick up from a central point; it's another to efficiently route deliveries to dozens of individual homes. While I have no intention of building a full-blown transportation management system like those used by large-scale haulers, there is a clear need for better delivery tools. This means features like delivery route management and optimization, which are common in CSA-focused software. The goal is to give market managers the tools they need to plan efficient routes, manage drivers, and provide customers with accurate delivery information, making home delivery a viable and scalable option.
## A Renewed Commitment
Knowing who I am is the first step. The next is committing to a path that honors my past while ensuring a sustainable future. The rescue mission was a success, but it came with hard-won lessons. The existential risk is no longer technical failure. It's ensuring that local food remains accessible to all communities, not just those with privilege. The next challenge is about equity, reach, and sustainability in the truest sense.
Moving forward, my commitment is threefold:
**First, a commitment to stability and trust.** Six weeks post-launch, I'm still fixing bugs and watching metrics like a hawk. The "bug apocalypse" taught me that my code has real-world consequences for people running their businesses. My first priority will always be quiet reliability and responding quickly to the needs of my users.
**Second, a commitment to my story.** I can't outspend or out-develop the competition, and I won't try. My strength is my authenticity: 23 years of being built from within the local food movement, by a grower who lived the problems he was solving. I will continue to build a platform that reflects that unique experience.
**Finally, a commitment to never again.** The 14-year stagnation was a crisis born from perpetually deferred maintenance. That will not happen again. A portion of my development time is now permanently allocated to the unglamorous but essential work of keeping the platform healthy. I will never again be one hardware failure away from extinction.
## The Current State: By the Numbers
As of September 2025, LocallyGrown.net serves:
- 25+ active markets across North America
- 500+ growers and food producers
- Thousands of customers weekly
- Over $1.3 million in annual sales flowing directly to small farms
These aren't Silicon Valley numbers, and they never will be. But they represent real communities, real farms, and real families making a living from the land.
## The Technical Foundation for the Future
The modern SvelteKit architecture I've built isn't just about escaping Rails. It's about enabling capabilities that were impossible before. The new, decoupled service layer means I can integrate complex third-party APIs for things like EBT and delivery logistics without risking the stability of the core platform—a task that would have been impossible on the old Rails monolith. Every integration point is now isolated, testable, and replaceable. A failed Stripe webhook can't bring down the shopping experience. A slow USDA API call for EBT verification won't freeze the entire application. This architecture makes the ambitious roadmap above not just possible, but achievable for a solo developer.
### Mobile-First by Default
```svelte
<!-- Every interface component now adapts naturally -->
<div class="product-grid">
{#each products as product}
<ProductCard {product} {market} />
{/each}
</div>
```
The platform is now truly mobile-responsive, something the old Rails app never achieved.
### Real-Time Inventory Management
```typescript
// API endpoint farmers can call from their phones in the field
export const POST: RequestHandler = async ({ request, locals }) => {
const { productId, quantity } = await request.json();
// Verify the farmer owns this product
if (!canEditProduct(locals.user, productId)) {
throw error(403, 'Unauthorized');
}
// Update through our service layer
const updated = await ProductService.updateQuantity(productId, quantity);
return json({ success: true, product: updated });
}
```
Farmers can now update availability from their phones in the field, and customers see the changes on their next page load.
### Performance at Scale
With Redis caching and database optimizations, the platform handles traffic spikes gracefully:
```typescript
// Market data cached in Redis for fast retrieval
export async function getMarketProducts(marketId: number) {
const cacheKey = `market:${marketId}:products`;
// Check Redis cache first
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Fall back to database with optimized query
const products = await ProductService.getByMarket(marketId);
// Cache for next request
await redis.setex(cacheKey, 300, JSON.stringify(products));
return products;
}
```
Response times are consistently fast, even during peak Sunday night ordering.
## The 12-Month Roadmap
### Q4 2025: Foundation Solidification
*Building the bedrock of trust that markets need to depend on this platform for another 20 years*
-**Rails migration complete**
- Continue bug fixes and performance optimization
- Achieve 90% test coverage
- Automated market scheduling
- Enhanced monitoring and alerting (building on Sentry)
### Q1 2026: Access and Equity
*Fulfilling the promise that local food is for everyone, not just those who can afford it*
- SNAP/EBT online payment integration
- Multi-language support (Spanish first)
- ADA compliance audit and fixes (WCAG 2.2 AA)
- Low-bandwidth mode for rural areas
### Q2 2026: Community Features
*Strengthening the human connections that transform a transaction into a relationship*
- Farmer storytelling tools
- Recipe sharing based on available products
- Bulk buying coordination
- Community-supported agriculture (CSA) management
### Q3 2026: Delivery and Logistics
*Solving the last-mile problem to bring the harvest to the doorsteps of those who can't make it to market*
- Route optimization tools
- Driver mobile app
- Customer delivery tracking
- Integration with existing delivery services
{{< callout type="info" title="Market Managers" >}}
If you'd like to pilot scheduling automation, EBT, or delivery tools, [reach out](mailto:support@locallygrown.net). I'm prioritizing real-world pilots over lab demos.
{{< /callout >}}
## The Business Model: Built for Communities
LocallyGrown.net has always operated on a simple, sustainable pricing model: 3% of sales, with nothing upfront. Markets pay on their own schedule. This approach means the platform scales naturally as markets grow, and payment is never an impediment to getting started or continuing to operate. It's pricing that reflects the community-first philosophy of local food itself.
## Future Extensibility
While I'd love to extract and open source components where it makes sense, I'm still identifying what those pieces might be. One area I'm exploring is the design system. I'm planning to expose the theming framework to make it easier for designers to create safe, resilient themes. This would revive the marketplace for LocallyGrown themes that existed in the early days, bringing back the customization options that markets loved while maintaining the stability and security of the modern platform.
## Closing: The Real Work Begins Again
This entire journey, from a failed cooperative in 2002 to a high-stakes rescue mission in 2025, was never just about saving a Rails application. It was about preserving vital community infrastructure that hundreds of growers and thousands of customers rely on. It was about ensuring that the accidental innovation that transformed so many local food economies would have a chance to serve the next generation.
The new foundation is solid. The code is modern, the servers are resilient, and the path forward is clear.
My deepest gratitude flows to the market managers who kept faith during the rocky transition, the growers who depend on this platform for their livelihoods, and the customers who choose local food week after week. Your patience and feedback have shaped every line of code.
The real work of growing begins again.
---
_This is the final part of a series documenting the rescue and modernization of LocallyGrown.net. Thank you for joining me on my retelling of how I managed to make the impossible merely difficult._
### The Complete Series
If you've come in at the end, here's the whole journey in order:
1. [From Accidental Discovery to Agricultural Infrastructure (2002-2011)](https://blog.kestrelsnest.social/posts/locallygrown-origin-story/)
2. [The 23-Year Rescue Mission: Saving Agricultural Innovation from Technical Extinction](https://blog.kestrelsnest.social/posts/locallygrown-rescue-mission/)
3. [The Architecture Challenge: Translating 19 Years of Rails Logic to Modern SvelteKit](https://blog.kestrelsnest.social/posts/locallygrown-rails-svelte-migration/)
4. [Crisis Response: When Launch Day Goes Wrong](https://blog.kestrelsnest.social/posts/locallygrown-reality-of-production/)
5. [Lessons from the Solo Developer Using Modern Tools](https://blog.kestrelsnest.social/posts/locallygrown-lessons/)
6. **From Survival to Sustainability: The Next 20 Years**_You are here_
---
**Want to follow the journey?** Subscribe to this blog or follow me on [Bluesky](https://bsky.app/profile/kestrelsnest.social) or [Mastodon](https://toots.kestrelsnest.social/@eric) for real-time development stories.
**Want to use LocallyGrown?** Visit [locallygrown.net](https://locallygrown.net) to find a market near you or start your own.
**Want to help?** I'm always looking for feedback from markets, growers, and customers. Reach out at <support@locallygrown.net>.

View File

@@ -0,0 +1,163 @@
---
title: "Forty Years of Code, Still Can't Type: A Developer's Confession"
description: After four decades of writing code, I still feel like an impostor. Not because I can't solve problems, but because the mechanical act of typing them has always been excruciating. Until now.
date: 2025-11-17T23:46:08.582Z
preview: ""
draft: false
tags:
- programming
- ai
- impostor-syndrome
- development
categories: []
lastmod: 2025-11-18T01:05:54.604Z
---
_After forty years of writing code, I finally realized the thing that made me feel like an impostor had nothing to do with my ability to code—and everything to do with my fingers._
---
## TL;DR
{{< callout type="info" title="The Bottleneck" >}}
After 40 years of coding while terrible at typing and spelling, I discovered the bottleneck wasn't my brain. It was my fingers. Using AI-assisted development tools didn't replace my expertise; it eliminated the mechanical transcription that was draining most of my energy. The typing was never the "real work." The thinking always was.
**The difference:** I get more done in the same hours. Features ship faster. Bugs get fixed quicker. And I still feel energized when I'm done coding, not drained from forced mechanical precision.
{{< /callout >}}
---
I've been writing code for over forty years. I've never taken a single computer class. And despite building countless projects that work, that solve real problems, that create value, I've spent most of those four decades feeling like a fraud.
Here's my confession: I'm terrible at the mechanical act of coding.
**This isn't a story about replacing developers with AI.** It's about removing an unnecessary handicap so I could finally code the way my brain was wired to.
## The Love and the Struggle
I love creating digital things from thin air. The moment when you imagine something that could exist, work through the logic in your head, and then, solely by thinking, make it real. That part is magic, and that part I'm good at.
{{< callout type="example" title="How I Learned to Code" >}}
I started young, typing code out of magazines on an Apple ][+ in the early 1980s. I'd read guides from The Beagle Bros. about PEEK and POKE, try things out, study source code on shareware programs from hand-me-down floppies. I learned by doing, by playing, by emulating what I found.
I'm neurodivergent, and this learning style (experiential, hands-on, pattern-based) has always been how my brain works best. I could see the logic, understand the systems, grasp the patterns. The thinking part came naturally.
The typing part never did.
{{< /callout >}}
But there's a gap between my brain and the computer. A chasm, really. And crossing it has always felt like walking through quicksand.
I literally cannot touch type. I've tried to learn, so many times, but my fingers don't work that way. Ironically, when typing natural language (like this sentence), my index and middle fingers on both hands race across the keys faster than you'd guess. The words flow from thought to screen (though often not spelled as they should).
But code? Code demands perfection: the right words, the right spelling, the right syntax. Every brace, every semicolon, every character in exactly the right place. One hundred percent accuracy, no exceptions. And I'm a terrible speller on top of it all.
My brain excels at problem-solving. It's great at seeing patterns, understanding systems, architecting solutions. But it struggles with mechanical precision. And for forty years, I've been trying to force a problem-solving brain to do transcription work that requires a level of exactness my fingers have never mastered.
## The Reality of My Days
Here's what implementing a simple authentication check actually looked like for me:
**The Thinking:**
"Need to verify user has admin role OR owns this resource." It was clear, simple, immediate.
**The Transcription:**
1. Look up the syntax for our authorization library
2. Type the condition slowly, fixing typos as I go: `if (user.hasRole('admin') || resource.ownerId == user.id)`
3. Catch the typo myself: should be `===` not `==`
4. Remember to add error handling; look up try/catch syntax again
5. Add the try/catch block
6. Run the linter, fix the typos it catches
7. Re-read everything one more time to make sure
The thinking was quick. The transcription felt endless. The ratio was backwards.
By the end of a coding session, I'd be exhausted. Not the good exhaustion of solving hard problems, but the draining fatigue of transcription work. Like an introvert after hours in a loud room, depleted from the wrong kind of effort.
And I kept thinking: _Real developers don't struggle like this. Real developers don't make these basic mistakes. Real developers can just... type._
**The typing was necessary work. I was just really, really bad at it.** So bad that I developed an unsustainable coping mechanism over the decades: code frenzies. I'd psych myself up, marshal all my energy and focus, and have these huge bursts of productivity where I'd push through the exhaustion and transcribe as much as I could. Then I'd crash. For days afterward, I'd have no energy for coding. I could research, plan, think through problems, but actually writing code? I had to wait until I'd recovered enough to force another frenzy.
For me, this is what "being a developer" meant. This was my normal.
To bosses, clients, and teammates, it probably looked fine. The work got done. I hit deadlines. But I was working myself to mental exhaustion just to produce what other developers seemed to do effortlessly.
## The Turning Point
Then I started using Claude Code. Not as a replacement for my thinking (I'm still the one solving the problems, making the architectural decisions, determining what the code needs to do) but as a partner that handles the mechanical transcription while I maintain creative control.
Something unexpected happened.
{{< callout type="success" title="I Could Suddenly Fly" >}}
**The same authentication check, with Claude Code:**
Me: "I need to add authorization logic to the resource edit endpoint in the API routes. Here's the requirement: a user should be able to edit a resource if they have the admin role OR if they own that specific resource. The user object already has a hasRole method we use elsewhere, so use that for the admin check. For ownership, compare resource.ownerId to user.id, and make sure you use strict equality not loose equality. The authorization check needs to happen before any data modifications. If it fails, throw a 403 Forbidden with a message like 'You do not have permission to edit this resource.' Wrap the whole thing in try/catch so we handle any errors gracefully. And make sure all the TypeScript types are explicit."
Claude Code: [Generates the function with correct syntax, proper comparison operators, error handling, and TypeScript types]
Me: [Reviews the implementation, verifies the logic matches my requirements, checks edge cases, approves, commits]
**What just happened:** I spent my energy on the thinking and the explaining. Claude Code spent its energy on the transcription. No typos. No syntax lookup. No exhaustion.
{{< /callout >}}
The bottleneck between my brain and the computer was gone. I could think through a problem and see it implemented correctly, immediately. No typos, no syntax errors, no hunting down the same function signature for the dozenth time. And best of all, no exhaustion from forced mechanical precision.
I was getting more done, feeling better at the end of the day, and most importantly, I was still writing code, still creating, still solving problems.
I just wasn't spending most of my energy on transcription anymore.
## What Changed
With Claude Code, the frenzies stopped. I could work at a steady pace. Think through a problem, explain it in detail, review the implementation, move on to the next problem. No psyching myself up. No crashes. No cycles of burst and recovery.
For forty years, I'd been measuring myself against the wrong metric. "Good developers" were people who could type code quickly and accurately. But that was confusing the medium with the message.
I was always a great developer. I was just programming on hard mode for four decades.
## What This Isn't
{{< callout type="note" title="Important Context" >}}
This isn't an argument that everyone should use AI tools. Some developers genuinely enjoy the mechanical aspects of coding. Some find the typing meditative. Some think at the speed of their fingers. That's wonderful—code however your brain works best.
This also isn't suggesting that formal training isn't valuable, or that AI will replace programmers, or that the craft of software development doesn't matter.
If anything, it's the opposite. By removing my personal bottleneck, I can finally focus more fully on the craft itself—on writing better software, not just writing software better.
{{< /callout >}}
## Working at the Speed of Thought
These days, I work differently. When I need to implement something, I think through the problem, explain what needs to happen, and Claude Code handles the transcription. I review it, validate it, adjust it. The code is still mine—I'm still the one creating it, making the decisions, taking responsibility for it.
I'm just not exhausting myself typing it anymore.
**A Recent Example:**
Recently, I rebuilt the search functionality for my LocallyGrown.net farmers market platform. The old Rails implementation was slow and inflexible. I needed to add filters, improve performance, and support multiple market contexts.
With Claude Code:
- Designed the new architecture (my decisions about caching strategy, query optimization, API structure)
- Explained the requirements and constraints
- Reviewed the generated TypeScript services and Svelte components
- Caught edge cases Claude missed (markets with no active products, search terms with special characters)
- Adjusted and shipped
The work that used to take days of exhausting transcription now takes hours of focused problem-solving. Not because the thinking is easier, but because the mechanical bottleneck is gone.
The projects I'm building now ship faster, work better, and reach more people. The code is not fundamentally different, and I'm not draining my energy on transcription anymore.
---
Will the impostor syndrome ever fully fade? Probably not. There will always be ways, real or imagined, to measure myself against my peers. That's not something a tool can fix.
But now I have one more coping mechanism stripped away, one more mask that was hiding my neurodivergence that I can remove. The code frenzies, the exhaustion, the forced mechanical precision—I don't need those anymore to keep up.
After forty years of trying to force my brain to work like everyone else's, I can finally apply my actual strengths: the thinking, the problem-solving, the architecture, the creation of digital things from thin air.
The typing was never going to be my strength. And that's okay now.
{{< callout type="quote" title="If You've Ever Felt Like an Impostor" >}}
If you've felt like an impostor because the mechanical parts of your craft don't come naturally, here's my takeaway: **the tools you use don't determine your legitimacy. The problems you solve and the things you create do.**
Find the tools that let your brain work the way it actually works. Strip away the masks. Apply your strengths.
{{< /callout >}}

View File

@@ -0,0 +1,116 @@
---
title: Why My Payment Agent Is Named George, Not stripe-agent
description: I name my AI sub-agents after people who inspire me—not whimsy, but a practice for remembering who the work is actually for.
date: 2025-12-14T03:12:58.910Z
preview: ""
draft: false
tags:
- ai
- programming
- development
- philosophy
categories: []
lastmod: 2025-12-14T03:22:45.477Z
---
_Most developers name their AI sub-agents things like stripe-agent or security-checker. I named mine George, Agatha, Ray, and Helen. This isn't whimsy; it's how I remember who the work is actually for._
---
When I need to focus on payment integrations, I don't invoke `stripe-agent` or `payment-flow-optimizer`. I invoke George.
George Washington Carver spent his life transforming overlooked resources into sustainable value. He found hundreds of uses for the peanut, the sweet potato, the soybean (crops that others dismissed). He turned simple inputs into systems that supported entire communities.
That's what a good payment system does. It takes the simple act of exchanging money for goods and turns it into something that sustains the people who depend on it. When I invoke George while designing webhook handlers and subscription flows, I'm thinking about Carver's legacy: patient, resourceful, turning small things into something that lasts.
This is how I've come to work with AI-assisted development tools. I name my sub-agents after the people who inspire me, not because the tools have personalities, but because I need to stay intentional about who I'm serving. These aren't chatbots with personalities; they're specialized configurations I invoke by name to focus my intent.
## The Problem With Abstraction
The tech industry loves to abstract away the human. Users become "MAUs." Problems become "pain points." Customers become "conversions." I've spent over forty years writing software, and I can't afford to think that way.
Almost all of my work has served real humans with names and lives. Biologists creating DNA from scratch who needed to manage their labs. Astronomers who needed searchable sky atlases built from radio telescope data. Legal professionals who needed portals into dense law journals. Rural electric engineers who were ready to leave the paper and pencil era behind. Regular people who just wanted to file their taxes without drowning in forms.
And for over twenty years, farmers. Market managers. Neighbors trying to feed their families something better than what the grocery store offers. That's the work I've written about most recently, but it's one thread in a much longer career of building tools for people I'd never meet but whose problems I came to understand.
Naming my tools after people who inspire me is a small ritual that keeps me from forgetting who I'm building for.
## Where It Started
The first agent I named was Ray, after Ray Eames.
I was working on visual design, trying to get an interface right, and the functional name (something like `design-system-agent`) felt hollow. It reduced the work to a skill to be optimized rather than a craft in service of people.
So I named her Ray instead. And something clicked.
Every time I invoked her, I thought about Ray Eames. About the design revolution she helped create. About her belief that design should serve human needs, not just look clever. About chairs built for sitting, not for winning awards.
The name changed how I approached the work. It wasn't just "run the design agent." It was "think about this the way Eames would want me to think about it." Form serving function. Function serving humans. Touch targets sized for hands that have been working all day. Interfaces that work in bright sunlight or a dim office.
That's when I realized the naming wasn't a quirk. It was a practice.
## How It Actually Works
I don't chat with Ray directly. Claude's sub-agents are specialized configurations that the main agent can invoke for particular tasks. They're always there in the background, ready to be called on.
But I can suggest them by name. When I know we're working on a task where I'd want to apply the principles embodied by their real-life human counterparts, I can ask Claude to do the same.
Agatha is a good example. I named her after Agatha Christie, and her focus is security audits. Christie built a career on finding what everyone else missed: the detail that doesn't fit, the clue hiding in plain sight, the pattern that only becomes clear when you look from the right angle.
When I say "have Agatha review this before we ship," I'm not asking for a generic security scan. I'm saying that I need to look for what I missed. I need to find the secret traveling farther than it should, the data leaking where it shouldn't, the assumption I made that an attacker won't make. I need to be paranoid on behalf of the users whose data and trust I'm protecting.
The names aren't just labels. They're invocations. They shape my intent before the work even starts.
## The Chorus
Over time, I've built a small roster of these named agents. Beyond George, Ray, and Agatha, there's Ada for performance, Erma for user-facing text, Maya for marketing, Diderot for documentation, and Helen for accessibility. Each is named for someone whose work I admire in that space.
Something interesting happens when I apply multiple agents to the same work. When I'm preparing a feature for launch, I might invoke several of them in sequence, each bringing a different lens. It's not a literal conversation between them, but their perspectives compound.
I'm applying a chorus of values: Christie's attention to the telling detail, Eames's insistence on function serving humans, Lovelace's vision of what a system could become, Bombeck's warmth in the everyday, Keller's understanding of barriers and how to remove them.
On one recent feature, invoking Helen after Ada meant slowing a transition animation that technically passed performance benchmarks but broke screen reader timing. Ada's lens said it was fast enough. Helen's lens said it wasn't accessible enough. The human need won.
These are people who cared about craft and cared about the humans their craft served. I can't live up to their standards, not really. But I can try, and invoking their names is a way of keeping their values present in the work.
## Helen's Story
I added Helen to my roster just this week.
I'd been writing a blog post for my company about Blue Beanie Day, the annual celebration of web standards and accessibility. The core argument was simple: accessibility should be built in from day one, not retrofitted. When you try to add it after the fact, you're fighting against architectural decisions that assumed mouse interaction, fixing color choices that were never tested for contrast, spending ten times the effort you would have spent doing it right initially.
After I finished writing, I realized I wasn't fully practicing what I'd preached.
Ray had accessibility as part of her purview. Her configuration mentions WCAG compliance, color contrast, and keyboard navigation. But accessibility wasn't her focus; it was one concern among many.
Helen Keller didn't just understand accessibility. She lived it, advocated for it, made it her life's work. If I wanted accessibility to get that same dedicated attention in my own work, I needed someone whose entire purpose was watching for it.
So I created Helen. I tuned her configuration with WCAG criteria, screen reader compatibility, keyboard navigation patterns, and the whole audit framework. Then I added her to my roster and ran a full audit.
The audit found things, issues I'd missed even with Ray's broader design lens. Focus states that weren't quite visible enough. A few interactive elements that weren't fully keyboard accessible. Color contrast that passed in most contexts but failed in a few edge cases.
The practice works. The name made me create the agent. The agent's lens caught real problems. The problems got fixed. The software is better for everyone now, not just users with disabilities, but everyone who benefits from clear focus states and logical tab order and sufficient contrast.
## Software as Medium
I don't think of myself as someone who writes software. I think of myself as someone who solves human problems, and my medium happens to be telling computers what to do.
A carpenter doesn't love wood for its own sake; they love what wood becomes in service of the people who'll use it. A chef doesn't cook to impress other chefs; they cook to nourish and delight the people who'll eat. The material and the technique matter, but they're not the point.
When I write code, I'm not trying to impress other programmers with elegance or cleverness. I'm trying to make something that works for the biologist tracking samples, the astronomer searching the sky, the lawyer chasing a precedent, the engineer planning a grid, the taxpayer trying to get through April, the farmer checking their order list.
Naming my tools after humans keeps that truth at the center. The code serves people. The tools serve the code. The names remind me which direction the service flows.
## A Small Ritual
I don't know if this practice would help anyone else. It might be too personal, too idiosyncratic, too tied to the specific way my brain works.
But I offer it anyway, in case it resonates: consider naming your tools after the people whose work you admire. Not as a gimmick, but as a practice. A small ritual that asks you, every time you invoke them, _what would they think of what I'm building? Who am I building it for? Am I doing work they'd be proud to be associated with?_
The tools don't care what you call them. But you might find that you care, and that caring might make you better at the work.
---
George, Agatha, Ray, Helen, and the rest of my chorus. They're people who made things that mattered. I can't match what they accomplished, but every time I invoke them, I remember what this is all for.
Software is a means, not an end. The code serves the people. The names help me remember.

View File

@@ -0,0 +1,74 @@
---
title: I Thought I Had 15 Minutes
date: 2026-01-06T09:00:00-05:00
draft: false
tags:
- Programming
- Neurodivergence
- Automation
- n8n
lastmod: 2026-01-06T14:37:21.515Z
description: I automated the five minutes before standup, and accidentally learned something about how my brain works.
---
*The five minutes before standup cost more than the meeting itself—and I didn't realize it until I stopped paying.*
---
I'm three lines away from fixing a bug that's been nagging me all morning. The solution finally clicked, I'm typing it out, and—
Slack ping. "You coming to standup?"
I thought I had fifteen minutes. I'm already a minute late.
So now I'm joining the call with my brain still wrapped around a database query while trying to remember what I even worked on yesterday. Or it's Monday and I'm scrolling through my commit history on mute, hoping someone else goes first so I can piece together where I was on Friday.
This isn't the meeting's fault. I actually find standups valuable. The rapid-fire "what I did, what I'm doing, my blockers" surfaces connections early, keeps work from drifting into isolation. On the kinds of projects I work on, touching multiple codebases and solving bits of different problems throughout the week, that situational awareness genuinely helps.
But there's a thing about standups nobody talks about: even when the meeting is on my calendar, the preparation costs more than it should.
---
Here's the thing about my brain: I thrive on deliberate context-switching. Rotating through multiple problems, solving bits of each in turn, building them up in parallel. A payment integration in the morning, a data pipeline after lunch, a frontend redesign the next day. That rhythm keeps me engaged in a way that single-project focus never has.
But there's a critical word in that sentence: *deliberate*.
An unexpected ping, a meeting I lost track of, someone pulling my attention while I'm holding a complex system in my head. That can be devastating. An hour's worth of thought, collapsed in an instant. The structure I was building just... gone. Sometimes I never fully reconstruct it.
The difference is control. Choosing to rotate through problems is energizing. Having context switches imposed on me is exhausting. And the cost isn't the interruption itself—it's the *reconstruction time* afterward.
Standup prep felt like a chosen context switch. It was on my calendar. I knew when it was coming. But it still carried that reconstruction cost. I had to stop holding whatever I was currently working on, shift into a different mental mode entirely, and piece together a narrative of my week from scattered artifacts. Commits in GitHub. Tickets in Jira. Memory of Friday half-erased by the weekend. Five minutes of clicking through systems trying to assemble a coherent answer to "what did you do?"
That's when I realized the meeting wasn't costing me. The preparation was.
---
I'd had n8n installed on a server for a while, an automation tool I hadn't found the right use for yet.
Then I watched [Leon van Zyl's tutorial on automated standups](https://www.youtube.com/watch?v=CJMaqmYhKqU) and something clicked. Not just "I could build that" but a broader realization about where tools like n8n actually fit. They're not for the big obvious workflows that justify dedicated engineering time. They're for the quiet friction points you've learned to live with. The stuff that's annoying but not annoying *enough* to fix properly.
Standup prep was exactly that kind of problem.
So I built a workflow. Every morning at 8 AM it gathers my activity from the past 24 hours: commits from GitHub, tickets from Jira. It merges them together and sends them to Claude with a prompt asking for a conversational summary. Something I could read aloud without sounding like a robot.
The result lands in my inbox before I've finished my coffee. A few paragraphs telling me what I worked on, what's in progress, what's still open.
Natural and accurate, *no archaeology required*.
---
I expected the time savings. I didn't expect how it would feel.
There's something quietly satisfying about getting that email every morning. A small summary of what I accomplished, delivered without any effort on my part. Even on days when it feels like I didn't get much done, seeing the actual commit history usually proves otherwise.
This matters more than it might seem. My brain thrives on rotating through problems, but that rotation makes it harder to maintain a coherent narrative of what I've actually *done*. When I'm deep in one problem, I'm not thinking about the three other problems I solved earlier in the week. The work feels fragmented because my attention is fragmented—by design, because that's how I work best.
Having a system that automatically reconstructs that narrative turned out to be surprisingly meaningful. Beyond saving time, it's a small daily reminder that *scattered doesn't mean unproductive*.
---
Since building this, I've noticed more of those quiet friction points. Minor annoyances I'd normalized because fixing them felt like overkill. Triaging support emails. Generating SQL queries from natural language. Even auto-inking my kids' pencil drawings. None of them big enough to justify a real engineering project, but real enough to be worth an hour of wiring things together.
The standup automation taught me to look for them. And it taught me something about my own brain in the process—about the hidden costs I was paying without realizing it, and the value of systems that do reconstruction work so I don't have to.
If you want the technical details (the workflow JSON, the setup, the quirks I discovered), I wrote that up for [Infinity Interactive's blog](https://iinteractive.com/resources/blog/how-i-automated-my-morning-standup-with-n8n-and-got-an-unexpected-morale-boost). This piece is the human context around it. Why a simple automation mattered more than it should have, and what it accidentally revealed about how I work.

View File

@@ -0,0 +1,65 @@
---
title: Why I Self-Host My Social Media (and You Could Too)
description: I started with a $12 droplet and YunoHost. Four years later, I run my entire social media presence on it.
preview: In 2021, I gave myself ketamine treatments for my 50th birthday. One of the first signs they were working was that I wanted to build again. I started with a $12 droplet and YunoHost. Four years later, I run my entire social media presence on it.
date: 2025-12-20T12:00:00-05:00
draft: false
tags:
- Self-Hosting
- Fediverse
- YunoHost
- Social Media
lastmod: 2025-12-21T01:53:59.581Z
---
*I started with a $12 droplet and YunoHost. Four years later, I run my entire social media presence on it.*
---
In November 2021, I spun up a $12-a-month DigitalOcean droplet and installed YunoHost on it. I just wanted to play around, see what self-hosting felt like these days. Four years and two server upgrades later, that experiment runs my entire social media presence: Mastodon, Pixelfed, BookWyrm, Castopod, Gitea, and this blog. Soon it'll run Loops too, a brand new federated short-video platform that's just coming online.
I didn't plan any of this. Like most decisions that end up mattering, it started somewhere else entirely.
---
Two months before I set up that server, I turned fifty. To mark the occasion, I gave myself a gift: ketamine treatments for lifelong depression. The kind that doesn't respond to the usual interventions. The kind you learn to route around rather than actually fix. The kind that becomes so baseline you forget you're carrying it until something finally lifts it and you realize, *oh, this is what people feel like normally.*
The treatments worked. The fog lifted. Colors came back. And one of the first tangible signs that something had changed was that I wanted to *build* again.
Not code for clients. Not features for deadlines. I wanted to tinker and play and create a digital home that belonged to me. Something I could point to and say: this is mine, I made this, nobody can take it away.
So I installed [YunoHost](https://yunohost.org) on a small server and started adding apps.
---
YunoHost makes self-hosting approachable. It's a Debian-based system with a friendly web interface for managing applications, users, domains, and backups. Install an app, point a subdomain at it, and YunoHost handles the certificates and reverse proxy. All the fiddly bits that usually make self-hosting a headache just... work.
Could I manage all this myself? Sure. I've been a full-stack developer for over forty years. I could wrangle nginx configs and Let's Encrypt renewals and database backups manually. But the combination of a semi-managed VPS at DigitalOcean and a maintained app management system like YunoHost hits a sweet spot. I get ownership and control without spending my weekends on sysadmin busywork. As my usage grew, I bumped up the server twice and added automated backups. The costs crept up a bit, but the ease stayed the same.
It's also open source and community-driven. People package apps, maintain integrations, help each other in the forums. I ended up contributing too—I wrote the BookWyrm integration that's now used by plenty of other YunoHost deployments. When Loops stabilizes enough, I'll probably package that one myself.
---
Twitter's long decline had already started my migration. I'd been on the platform since its earliest days, watching it gradually curdle. When ownership changed in late 2022, I already had somewhere to go.
My Mastodon instance at toots.kestrelsnest.social was running. My Pixelfed at pix.kestrelsnest.social was ready for photos. I could leave without losing connections entirely—the fediverse meant I could still follow and be followed by people scattered across other servers.
Then Facebook and Instagram started their own collapse, and I was glad I'd gotten ahead of it. My data lived on my server, in my database, in my storage. The platforms could implode and I'd still have everything.
---
The sidebar on this blog lists where you can find me online. Almost every link points to a subdomain of kestrelsnest.social. Mastodon for microblogging, Pixelfed for photos, BookWyrm for tracking what I'm reading, Castopod for podcasting whenever I get around to it, Gitea for my code repositories.
I grew up on the old web, where you could have a home on the internet that actually belonged to you. I ran this blog on my own server through the first decade of the 2000s. Then I got lazy, let Twitter and Facebook become my default, and watched years of my words disappear into timelines I didn't control.
Self-hosting is just going back to what the web was supposed to be.
---
Could you do this yourself? Maybe. YunoHost really does lower the barrier. A basic VPS, a domain name, and a weekend can get you running. Their forums are helpful when you get stuck. And you can always find me at any of my socials if you want to ask questions. You don't need decades of experience, just patience and a willingness to learn.
The real question is whether you care enough about owning your data to invest the time. The corporate platforms will always be more convenient if all you want is to post and scroll. That convenience has costs, though. You just won't know the full price until it's too late.
I'm glad I started when I did. The infrastructure was already in place when the platforms started crumbling. Every time I add a new app—BookWyrm last year, Loops coming soon—I'm building out a space that can weather whatever collapse comes next.
The ketamine treatments were the gift I gave myself for turning fifty. The server might be the gift that lasts longest.

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 KiB

View File

@@ -0,0 +1,47 @@
---
title: Where Did I Put the Mochi Flour?
description: I found three opened bags of rice flour this week. So I built a pantry inventory app.
date: 2025-12-24T12:00:00-05:00
draft: false
tags:
- Cooking
- Projects
- Claude Code
lastmod: 2025-12-24T21:27:08.386Z
---
I found three opened bags of rice flour this week. Also two of tapioca starch. And a potato starch I'd forgotten existed entirely.
Gluten-free baking has always been my organizational weak spot. The flours all look similar, they come in bags that don't stack well, and I'm forever buying "just in case" because I can't remember if I have any left. We have shelves full of labelled containers of dry goods, but with jars in front of jars, bags everywhere, and two adventurous cooks in the kitchen, things just piled up to the point where we didn't know if we had something specific and even if we did who knew where it might be.
{{< img src="flip-top-jars.jpg" alt="Two wooden shelves of glass flip-top jars with printed labels containing various dry goods" caption="Glass flip-top jars full of dry goods, each with a printed label" >}}
The spice situation used to be just as bad. Multiple half-empty jars of cumin, duplicates of things I'd bought because I couldn't find the original. Last year I solved that with a wall of magnetic hex jars from [Gneiss Spice](https://gneissspice.com). Now we have over 100 herbs and spices, all visible at once. No more duplicates because I can see everything. And when I'm standing in the spice aisle at the grocery store, I can visualize the wall in my head and generally know whether I need more smoked paprika.
{{< img src="spice-wall.jpg" alt="A wall covered with approximately 100 magnetic hexagonal glass spice jars arranged in a honeycomb pattern" caption="The spice wall: over 100 magnetic hex jars in a honeycomb arrangement" >}}
Physical organization is great for storage, but it doesn't help you find anything. The spice wall works because it's small enough to scan visually. The pantry shelves are not.
{{< img src="flour-shelf.jpg" alt="Wire shelving unit with OXO pop-top containers holding various flours" caption="The flour shelf: OXO containers, pasta, and baking supplies" >}}
After consolidating the flours and refilling containers and adding labels, I stood back and felt mostly satisfied. The shelves looked good, or at least well enough to begin an actual deep reorganization effort. But I still couldn't answer the question I actually needed answered: do I have mochi flour, and if so, what kind of container is it in?
So the next evening, I built an app.
---
The core idea is simple: type part of a name, see what container to look for. That's it. Everything else—the stock status, the shopping list export, the category filters—grew naturally from that starting point. You can try it yourself and see what we have in the kitchen at [pantry.kestrelsnest.social](https://pantry.kestrelsnest.social).
This is unapologetically a household-scale tool—built to solve one specific annoyance in one specific kitchen.
The implementation is deliberately minimal, designed to fit YunoHost's "My Webapp" feature: one HTML file, one PHP file, a couple of JSON files for configuration. No database, no build step, no framework. My Webapp gives me a simple PHP server pointed at a subdomain, so deployment is just rsyncing the files up. The source is on [my Gitea](https://git.kestrelsnest.social/eric/pantry) if you want to run your own.
I built it with Claude Code over maybe two hours. This is the kind of thing where Claude Code and I really shine: I can type every character of code if I wanted to, but here I can rapidly prototype, try things out, and sculpt in real time. The code itself is very simple, straightforward CRUD stuff, but typing out all the boilerplate for forms and API endpoints and CSS is tedious, and Claude is good at tedious. I described what I wanted, Claude typed it out, and I nudged it into shape. It's the kind of collaboration that makes evening projects actually viable for someone with limited evening energy.
---
There's a QR code feature so I can print a little card and stick it on the pantry shelf. Anyone in the household can scan it and search. Viewing is open; editing requires a PIN. I'm not building a multi-tenant SaaS, so I went with simple access control for a simple problem.
The real test will be the next time I'm at the store wondering if I need tapioca starch. I'll pull up the app, search "tapioca," and see it's in a medium flip-top jar, in stock. Or maybe out of stock, and I'll know to grab some. I already did that today for a fresh bag of AP flour, white sugar, dried chives, and ground white pepper.
Here's to no more three-bag situations!

Binary file not shown.

After

Width:  |  Height:  |  Size: 419 KiB

View File

@@ -0,0 +1,43 @@
---
title: "Weeknotes: December 1926, 2025"
date: 2025-12-27T10:45:00-05:00
description: Slow, like the world was on pause. Everyone else off celebrating while I hung out in the quiet of home.
draft: false
tags:
- weeknotes
lastmod: 2026-01-06T04:04:15.067Z
---
_Slow, like the world was on pause. Everyone else off celebrating while I hung out in the quiet of home._
## Shipped
Built a pantry inventory app to track dry goods, herbs, and spices. It already proved useful when it reminded me about a bag of self-rising flour I needed to use up.
Also finished a five-video series of fairy-themed foods for CONpossible (this year's theme: "Through the Fairy Ring"). Each one runs about a minute, short promotional pieces that'll also go up on the Random Recipe Project channel.
## Read
Honestly, not much. I've been writing more than reading lately, and I'm making peace with that. Didn't come close to my already modest 2025 reading goal, but the words have been flowing in the other direction.
## Played
Had a great Gloomhaven session on Sunday. We're all nearing retirement for our third round of mercenaries and deep into the main storyline. The end is in sight, which makes every session feel weightier.
## Cooked
The only standalone butcher shop in town closed for good on Christmas Eve. I bought two full racks of spareribs from them as a send-off and smoked them for six hours on Wednesday. First time I'd fired up the smoker in two years. I ordered replacement parts so I can get back to using it regularly. Made a big southern squash casserole to go alongside.
That pantry app earned its keep when it surfaced a bag of self-rising flour, so I made buttermilk biscuits from scratch with sausage gravy. Also picked up some alcoholic eggnog for our morning coffees this week. A little somethin' somethin'.
## Noticed
Last week was bitter cold. This week bounced to nearly 80°F on Christmas Day. Georgia.
## Thinking About
Just starting a new project at work, a simple web app on a limited budget. The kind of thing I could do in my sleep. Times are slow and lean right now, though, and the temptation is to throw everyone and everything at it just to keep people busy. I'm pushing back. We need to know how to do small, lean projects repeatedly and in parallel. That seems to be where the industry is headed.
## What's Next
My kids were both gone this week, and my partner and I don't really celebrate Christmas, so it was genuinely quiet. Next week they'll both be here, including my eldest who's about to start their final semester of college. We'll do what we call "Second Christmas," running through the traditions on time delay. Works better for us.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

View File

@@ -0,0 +1,52 @@
---
title: Either Way, There'll Be Cake
date: 2026-01-01T12:00:00-05:00
draft: false
description: A gluten-free Yule log for second Christmas, held together by willpower and ganache.
preview: ""
tags:
- cooking
- family
- locallygrown
lastmod: 2026-01-01T19:12:22.889Z
---
My kids were home together this week for what we call "second Christmas"—the post-holiday stretch when everyone's schedules finally align. While planning the dinner menu, they made a request: a Yule log cake.
We used to get one every year from Linda Johnson of [Sylvan Falls Mill](https://northeastgeorgia.locallygrown.net/growers/1646), a miller and baker who sold at my farmers market. She made a gluten-free version, which mattered because my eldest is celiac. It was always a special treat, the kind of thing that becomes part of your family's holiday vocabulary without you noticing until it's gone.
With less than two days before dinner and a full menu to prepare, I told them it seemed really intimidating. I'd never made one. But I said I'd try.
I've baked their birthday cakes every single year, all their lives. They describe what they want and I try to make it happen. Some years are triumphs. Some years are laughable catastrophes that we still talk about. But every year there's cake. This felt like the same deal.
{{< figure src="cranberries.jpg" alt="Fresh cranberries and rosemary sprigs coated in sugar, drying on parchment paper." caption="Sugared cranberries and rosemary, made the night before." >}}
I started with a box of King Arthur's gluten-free chocolate cake mix—no shame in a good shortcut—but I needed to turn it into something that could roll without shattering. The technique I borrowed from [Sally's Baking Addiction](https://sallysbakingaddiction.com/buche-de-noel-yule-log/) calls for separating eggs, whipping the whites to stiff peaks, and folding everything together for an airy sponge. I added two extra eggs beyond what the box called for and crossed my fingers.
The cake looked great coming out of the oven. I rolled it while hot in a cocoa-dusted towel, let it cool for three hours, and felt cautiously optimistic.
Then I unrolled it.
The cake had split into sections a few inches wide. It had decided to become a kit.
{{< figure src="disaster.jpg" alt="Unrolled chocolate cake split into several vertical sections, with whipped cream filling visible in the cracks." caption="The kit." >}}
I stood there for a moment, weighing my options. Then I decided to act like nothing was wrong. I spread the whipped cream filling over the pieces, rolled the whole thing back up as tightly as I could manage, wrapped the towel around the outside, and put it in the fridge overnight to think about what it had done.
{{< figure src="mushrooms.jpg" alt="Small meringue mushrooms with piped stems and caps, dusted with cocoa powder, standing on a silicone baking mat." caption="Meringue mushrooms, dusted with cocoa." >}}
The next day, when it came time to cut the diagonal branch piece and assemble it on the board for ganache, the log barely held its shape. I worked fast. The ganache went on, I dragged a fork through it for bark texture, and suddenly it looked like an actual Yule log instead of a chocolate-flavored anxiety dream.
{{< figure src="bark.jpg" alt="Chocolate yule log covered in ganache with fork-dragged bark texture, before decorations, showing the spiral of cake and cream at the cut end." caption="Bark texture achieved. Now it just needs a forest." >}}
The garnishes helped. I'd made sugared cranberries and rosemary the night before—just a simple syrup soak and a tumble in sugar, but they sparkle like little frozen gems. The meringue mushrooms took longer than expected (piping stems and caps separately, baking low and slow, gluing them together with melted chocolate) but they're the detail that makes the whole thing feel like a forest floor instead of just a frosted cake.
{{< figure src="forest-floor.jpg" alt="Close-up of decorated yule log with meringue mushrooms, sugared cranberries, and rosemary sprigs on chocolate ganache bark." caption="The forest floor." >}}
When I brought it to the table, my kids lit up. It held together when sliced—somehow—and they said it reminded them of the Sylvan Falls version while being its own thing. The almond flour base from the market version was different, but this worked.
Linda still sells at another market these days, up in Northeast Georgia. Still using my LocallyGrown software, which makes me unreasonably happy. The web of connections from that market keeps surprising me—people I haven't seen in years, still linked by the tools we built together.
I'd make this again. Maybe it'll be a new tradition. Next time might be a perfect specimen, or I might have to pivot to calling it "a decaying log on the forest floor." Either way, there'll be cake.
{{< figure src="finished.jpg" alt="Overhead view of completed yule log cake decorated with meringue mushrooms, sugared cranberries, and sugared rosemary on a wooden cutting board." caption="Second Christmas, 2025." >}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

View File

@@ -0,0 +1,51 @@
---
title: "Weeknotes: December 27January 2, 2026"
date: 2026-01-03T12:00:00-05:00
description: Our time-shifted Christmas week was really peaceful. A couple more days before going back to work on Monday, and I'm trying to make the most of it.
draft: false
tags:
- weeknotes
lastmod: 2026-01-06T04:04:40.233Z
---
_Our time-shifted Christmas week was really peaceful. A couple more days before going back to work on Monday, and I'm trying to make the most of it._
## Shipped
Took most of the week off for the holidays with both kids here. Still managed to nearly complete a new client project we'd budgeted the entire month of January for, in about ten hours of work. Our first status call isn't until Tuesday and I've already got something over 90% of the way there. Sometimes things just come together in a near-perfect way, and I live for those moments.
On the other side, I spun down a SaaS product that never really came to fruition. There were many reasons why it didn't succeed, but it was still sad to turn off those lights.
## Read
Nothing this week, but something jumped right to the top of my to-read pile: _Automatic Noodle_ by Annalee Newitz. Deactivated robots come back online in an abandoned ghost kitchen and decide to make their own way doing what they know: hand-pulled noodles for the humans of San Francisco, who are recovering from a devastating war. Robots, food, post-apocalyptic city life? Right up my alley. I might shelve _Service Model_ temporarily so I can devour this one.
## Played
My Gloomhaven group won't be able to meet for a while, but the digital edition was on sale on Xbox for $20, complete edition. I put almost twenty hours into it this week and have only beaten two scenarios in all that time.
I know how to play the game really well, but the UI of the digital edition is beyond frustrating. So many things are undocumented, and I'm still figuring out how to do simple things like see the map while planning my round. My biggest gripe is that there's no undo function, and it's trivially easy to ruin a thought-out plan with one wrong button press. The closest they have is "restart round," which can mean losing half an hour of progress.
## Cooked
We had our Christmas meal this week. The kids chose ham, so I grabbed a big bone-in one on an after-Christmas sale (one of the benefits of Second Christmas) and used the included cherry vanilla cola glaze instead of my usual approach. Not terrible. We're still eating it several days later, but it's mostly gone. I also made baked potatoes, a big fruit salad, and a garlic-ginger stir-fried broccoli that turned out fantastic as a baked potato topper. Oh, and gluten-free cornbread.
The showstopper was the gluten-free yule log cake I largely winged and [documented on the blog](https://blog.kestrelsnest.social/posts/2026-01-01-either-way-therell-be-cake/).
The following night I set up an omelette station for dinner, which reminded me of one of my many college jobs in the campus cafeteria, where I often got to run the omelette station.
## Noticed
Cold and a little rainy, which made for good cozy time inside gaming and cooking.
## Thinking About
I'm on staff for two conventions in the next six weeks. The first is a cakewalk: sole technical troubleshooter for presenters all weekend at a tiki culture convention. I can do that work in my sleep or, as is the case here, with a different rum-based drink in each hand.
The other one is a steampunk-adjacent convention where I serve as costuming track director, and I'm really feeling the crunch on that one.
The five fairy food videos I made for CONpossible have been going live every other day and have been well received. YouTube is inscrutable as ever, but they've netted me several thousand views and a bunch of new subscribers. The con's social media team will be posting their versions in the coming weeks.
## What's Next
My youngest starts back to school Tuesday, beginning the second semester of sophomore year with a new slate of classes. She turns 16 in a month, so I need to start thinking about that. My eldest returns to college this weekend, so there's lots of coordinating to manage over the next few days.

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

View File

@@ -0,0 +1,60 @@
---
title: "Weeknotes: January 309, 2026"
date: 2026-01-10T09:00:00-05:00
description: The chair arrived. Buddhist monks walked through town. Fried chicken achieved maximum cronch.
draft: false
tags:
- weeknotes
- cooking
- conventions
---
The chair arrived.
![A reclining chair with gray upholstery and a bentwood frame, paired with a dark desk surface with pegboard holes for accessories](chair.jpg)
I backed a Kickstarter back in April, maybe, from a Ukrainian woodworker for a reclining chair and desk combo. A reward for finishing the LocallyGrown conversion, I told myself. I work almost exclusively at a standing desk these days, but sometimes you need to sit for reading, ketamine treatments, or experimenting with multiple giant monitors in VR. The chair finally showed up this week, assembled without drama, and it's exactly as comfortable as I'd hoped. Quality time was had.
---
## Shipped
It was back to work this week after the holidays. Things are uncomfortably slow in general, but my role has me touching nearly every project and potential project we're engaged with. There's been lots of context switching, lots of architecture decisions, and lots of opportunities to put new dev tools through their paces. The context switching suits my brain, honestly. It's the *unexpected* interruptions that wreck me, not the deliberate pivots.
## Read
I started *Automatic Noodle* this week. The real world is on fire and this was exactly the respite I needed, a few delightful minutes at a time.
## Played
I didn't do much gaming this week. I did pick up [A Gentle Rain](https://dryad-games.com/shop/a-gentle-rain-bloom-edition/), a meditative tile-placement game where you arrange lotus blooms on a pond. It takes about fifteen minutes, there's no competition, and it's pure pattern-making. It should be a good mental reset between tasks.
## Cooked
It was mostly a week of working through holiday leftovers, but I made one big production: a giant pile of fried chicken using Babish's "Ultimate Fried Chicken" recipe. The process involves a dry brine, a tempura-ish batter, and a double fry. He specifies a particular flour blend for maximum crunch, but I needed these gluten-free, so I improvised my own mix.
![Close-up of golden fried chicken pieces with a craggy, textured crust resting on a wire rack](fried-chicken.jpg)
I achieved plenty of cronch. I'd make these again when I'm feeling ambitious. They're more involved than my usual southern style, but worth it.
## Noticed
It was hot and muggy this week, which feels wrong for January. That'll flip hard next week; the highs won't reach this week's lows. Our youngest cat has some Maine Coon in him and his winter coat has fully arrived. He's more fur than flesh at this point, which will serve him well come the cold snap.
The bigger thing I noticed this week: Buddhist monks from the Walk for Peace pilgrimage passed through Lexington, Georgia, on their 2,300-mile journey from Fort Worth to Washington, D.C.
![Monks in saffron and maroon robes walk down a rural road, led by a monk with a tall walking staff. Aloka the rescue Peace Dog trots alongside. Community members line both sides of the road, many with palms pressed together in greeting, as a police vehicle with blue lights escorts the procession.](peace-walk.jpg)
My own philosophies lean more Taoist than Buddhist, but there's considerable overlap. It was a wonderful opportunity to be mindful and let them lead by practical example. I'm glad my youngest daughter and I could share a meal with these monks as they walked through our part of the country.
## Thinking About
I've written quite a bit lately, here and on the company blog, about using AI-powered dev tools with thought and intentionality. For various reasons I've felt the need to articulate the philosophy and ethics behind how I use them, not just demonstrate the workflows. Having it down in black and white creates accountability. It's harder to quietly drift when you've already said where you stand.
## What's Next
Inuhele, the tiki convention I help with, is in two weeks. CONpossible, the steampunk convention I help with, is in four. I have so much to do before then.
---
The vibe this week: I need the coming weeks to run well above my usual productivity, and my usual has been solid. I have a suspicion this is only going to ramp up from there. It's a good thing I have a comfortable chair for the occasional sit-down.

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

View File

@@ -0,0 +1,76 @@
---
title: "AI as Tool, Not Creator: How I Learned to Stop Worrying and Front-Load the Thinking"
date: 2026-01-09T00:00:00-05:00
description: The craft is in the decisions. The tool just handles the transcription.
draft: false
tags:
- AI
- Writing
- Technology
- Philosophy
lastmod: 2026-01-12T20:30:45.342Z
---
_The craft is in the decisions. The tool just handles the transcription._
I use AI tools almost every day. I also have serious concerns about AI as an industry. These positions might seem contradictory, but I don't think they are.
I want to be clear up front: this is a personal statement about where I've landed after a couple of years of thinking about this while actually using the tools. Other people have landed elsewhere, and I respect that. The ethics here are genuinely unsettled, and anyone who tells you they have it all figured out is selling something.
---
The loudest voices in the AI conversation tend toward extremes. On one side: breathless enthusiasm, AI will write all our code and our novels and our grocery lists, resistance is futile and also why would you resist? On the other side: principled rejection, the entire enterprise is built on theft, using these tools makes you complicit, real creators don't need them.
I find myself in neither camp, which is uncomfortable. Camps are comfortable. You know who your people are. You know what to think when a new development hits the news.
But neither position survives contact with the details, at least for me.
---
The theft question is real and I won't pretend otherwise.
AI models learn by consuming enormous amounts of human-created work. So do humans—I learned to write by reading thousands of books, learned to code by studying other people's code. But human learning mostly happens inside systems of payment, consent, and attribution. I (or someone on my behalf) paid for those books. The authors got a cut. AI companies trained on the same material and in many cases paid nothing. Some of them scraped content that was never intended to be freely available. It's all been documented by the scrapers themselves.
Anthropic, whose tools I use almost exclusively, admitted to using pirated source material in their training data. To their credit, they've acknowledged it and are paying a settlement to affected authors. (Yes, I know the lawyers say they're not admitting guilt and they had the right to do what they did, but the legal system demands that. What's important is they stopped fighting and started paying.) Other companies are still fighting in court to avoid facing consequences for doing the same thing. That matters to me. It's not the only thing that matters, but it's not nothing.
The even more egregious form of theft is when models reproduce training material verbatim. No synthesis, no transformation, just copying. When an image generator spits out something that's clearly a specific artist's work with the signature smudged out, that's infringement. When a code assistant regurgitates a chunk of GPL-licensed code into your proprietary project, that's a problem. There's no learning happening there, just memorization and reproduction.
I haven't encountered that with Claude, though I want to be honest: that might be because of how I use it rather than because it never happens. I don't ask it to generate from whole cloth. I don't say "write me a short story in the style of Ursula K. Le Guin" or "create an authentication system." My usage pattern might simply avoid the places where the cracks show.
---
Here's what my actual workflow looks like.
This post you're reading started as a conversation with Claude. But look at what that conversation actually contained: I described what I wanted to write about. I explained my position on the ethics in my own words, working through the nuances as I typed. I gave detailed direction about voice, structure, what to include and what to leave out. I answered questions that pushed me to clarify my thinking. Sometimes I spoke aloud and dictated my thoughts. What you see printed here is already long, but the words I put down were many times longer still, eventually distilled, reorganized, and drafted into a cohesive essay.
By the time Claude produced a draft I felt was ready for editing, the creative work was done. The ideas, the positions, the voice—all mine, drawn from all my notes on this topic I put down over the last few days and then referenced with years of other writing I'd fed into the project to keep Claude from interjecting its training material into my words. What remained was assembly: taking the cloud of my words and editing them into a coherent shape.
That's what I mean when I say I use AI as a tool rather than a creator. My line: if I haven't already decided what to say, the AI doesn't get to decide for me.
My coding workflow follows the same pattern. I don't hand over big problems and wait for solutions. I think through the architecture myself. I work out the exact behavior I want. I break the work into pieces small enough that implementing each one is mechanical. Then I describe those small pieces in enough detail that all Claude Code has to do is transcribe my description into working syntax.
I've tried to structure my workflow so there's little opportunity to plagiarize, because the decisions that matter, the creative decisions, have already been made before the tool gets involved.
---
I still feel like a writer when I work this way. That surprised me at first, because I expected to feel like I was cheating somehow. But the feeling never came, and eventually I understood why.
The physical act of putting words on a page is necessary, but it's not where the writing happens. For me, writing happens in my head, in the false starts and reconsidered angles and sudden moments of clarity that I chase through the fog of a draft. All of that still happens. I'm doing it right now, choosing this word instead of that one, cutting a tangent that doesn't serve the piece, or noticing when a sentence lands wrong and figuring out why. Claude can help me move faster through the mechanical parts, but it can't do that noticing for me.
The same is true when I'm writing code. The craft is understanding the problem, designing a solution that will hold up over time, making tradeoffs that serve the humans who will use and maintain what you build. Typing the syntax is just how the decisions become real.
---
I've chosen to work almost exclusively with Anthropic's tools, and that's a deliberate choice rather than a default. Some of it is how the tools behave. Claude feels more like an extension of my thoughts than a magic box (and I understand how much what I wrote sounds like it is indeed a magic box), which matches how I want to work. Some of it is that Anthropic has been more thoughtful, or at least more transparent, about the ethical tangles than other players in this space. The settlement I mentioned earlier is an example. Acknowledging harm and making it right isn't nothing, even if it doesn't resolve every concern.
I'm not naive enough to think my choice of vendor solves the larger problems. The training data issues are industry-wide. The questions about what these tools will do to creative labor markets are real and unresolved. I've just decided that using the tools carefully and intentionally is a more honest position, for me, than either pretending there are no problems or refusing to engage at all. I've also decided not to passively hold this position. By being vocal about my choices—here, on social media, in conversations with other developers—maybe I can help lead others to make thoughtful choices of their own and collectively be the "market forces" that drive the technology in a beneficial direction instead of where megalomaniacal billionaires want it to go.
---
So that's where I've landed. AI as tool, not creator. Front-load the thinking, hand off the transcription. Stay alert to where the ethical lines are, even when they're blurry.
New tools have always created disruption, this I know. But if the balance shifts in ways I haven't anticipated, if my careful approach turns out to violate ethical lines I thought I was respecting, I'll have to reconsider. I'm not attached to being right about this. I'm attached to doing less harm than I would by ignoring the question entirely.
Other people will draw the lines differently, and I'm not here to tell them they're wrong. The technology is genuinely new, the implications are genuinely uncertain, and reasonable people can look at the same situation and come to different conclusions.
This is just where I am, for now, trying to thread a needle that keeps moving.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -0,0 +1,57 @@
---
title: "Weeknotes: January 1016, 2026"
date: 2026-01-17T10:00:00-05:00
draft: false
description: Everything around me seems to be in motion this week. Kids moving through milestones, colleagues moving on to new things, seasons shifting. Even the birds are starting to come back.
tags:
- weeknotes
- cooking
- family
lastmod: 2026-01-18T01:46:29.567Z
---
Everything around me seems to be in motion this week. Kids moving through milestones, colleagues moving on to new things, seasons shifting. Even the birds are starting to come back.
## Shipped
Saturday I attended a UGA swim meet with my youngest, who's on her high school team. She's lucky enough to practice at the UGA natatorium, a genuinely world-class facility, and it was fun to watch the collegiate teams compete, including several nationally ranked swimmers.
![The UGA Gabrielsen Natatorium, showing the diving well, competition pool, and championship banners under the massive steel truss ceiling](natatorium.JPG)
There's something satisfying about watching people who are really, really good at something do that thing at full speed.
Sunday we took the eldest back to college for their final semester. They're packed with senior-level classes to finish out a degree in computer game design come May. On the way we explored Little Five Points, one of Atlanta's iconic neighborhoods. It felt appropriate: a neighborhood that's survived by constantly reinventing itself, visited on the cusp of a big transition.
Tuesday we went out for dinner, an extreme rarity for us. Half-price oysters and creole enchiladas. Both delicious.
Wednesday was our monthly virtual happy hour at work, where we bid farewell to an amazing co-worker who found much greener pastures elsewhere. He came to us right out of boot camp several years ago, and it's been a joy to watch him grow into a developer as capable as any we have. Bittersweet, but the right kind. The kind where you're genuinely happy for someone even as you'll miss them.
## Read
I found time for a few more chapters of *Automatic Noodles*, which remains a fun and interesting read. Also had to dive into documentation for Shibboleth, a single sign-on authentication system I'll need to integrate soon. The name is appropriately intimidating for an auth system. A word you have to pronounce correctly or be identified as an outsider.
## Played
Not much play this week, other than with the cats. I did start cleaning the craft area, something I should have done months ago, so I can get a few things done for the rapidly approaching steampunk convention. "Start" is doing a lot of work in that sentence.
## Cooked
On a friend's strong recommendation I picked up the River Cottage *Much More Veg* book and made the recipe she'd been raving about: a red cabbage biryani.
![Red cabbage biryani in the pan, showing the braised purple cabbage ribbons and turmeric-stained cashews](biryani-cooking.JPG)
I've never made a biryani before, much less a vegan one, and it was fantastic. I still have some cabbage left and will absolutely make it again. Tonight, even.
![The finished biryani plated with fresh cilantro and sliced red pepper](biryani-plated.JPG)
## Noticed
The songbirds are starting to return, which means I need to disinfect and refill the bird feeders and bath. They have cameras in them, and I love getting little video postcards throughout day. One of those small technological pleasures that actually delivers on its promise.
## Thinking About
Trying not to panic about all the stuff I wanted to do for the steampunk con that I haven't done. I wanted to have more done by now, but here we are.
## What's Next
It's a short work week for me so I can go help with Inuhele, Atlanta's tiki weekend. I can't wait. After a week of watching other people's transitions, I'm ready for three days of escapism and terrible puns about rum.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

View File

@@ -0,0 +1,68 @@
---
title: "Weeknotes: January 1723, 2026"
date: 2026-01-24T09:00:00-05:00
draft: false
description: The week started with chlorine and ended with rum. In between, international grocery exploration, storm anxiety, and a fire-eating mermaid who made me want to do more with my life.
tags:
- weeknotes
- Inuhele
- CONpossible
- 3d-printing
- family
---
The week started with chlorine and ended with rum. In between: international grocery exploration, storm anxiety, and a fire-eating mermaid who made me want to do more with my life.
---
Saturday I spent at UGA's competition pool watching Juniper swim her way through another big high school meet. She's a sophomore, solidly JV, but this was a personal-records kind of day—first place heat finishes, times dropping, the whole arc of improvement visible in a single afternoon. The older kids will graduate. She'll be ready.
Sunday took me to Duluth for the last in-person CONpossible staff meeting before the convention. But the real adventure was the food. Before the meeting I wandered through a Middle Eastern grocery store down the street from the hotel. Afterward I crossed to an enormous Vietnamese shopping center, which culminated in a truly excellent bánh mì eaten in the parking lot before the drive home. Duluth is a treasure trove of international markets and I've made it a habit to explore a new one each visit. I may never run out.
Monday was MLK Day—a work holiday. Some years I join one of the local service projects, but this year I put the 3D printer to work on items for CONpossible and one beautiful cormorant pendant for Inuhele.
![Close-up of a 3D printed cormorant pendant, hand-painted in black and silver with blue and gold accents, hanging on a black cord against a red shirt](cormorant-pendant.jpg)
The cormorant isn't Inuhele's mascot, but it's a tropical, oceanic bird—adjacent to the traditional tiki imagery but my own. I wanted something that felt both handmade and a little fancy. A few hours of printing, a few more of careful painting, and I had something I was genuinely proud to wear.
Tuesday and Wednesday were my only work days this week, and by Wednesday evening the weather forecast had solidified into something alarming: a potentially catastrophic ice storm arriving over the weekend, right when we'd be in Atlanta at Inuhele. I split my attention between actual work and storm preparation—weatherproofing the house, arranging extra care for the cats, packing for what might become a longer stay than planned. Tuesday night I managed dinner and drinks with a dear friend I hadn't seen in far too long. She's been traveling internationally and in-person sightings have been rare. It was good medicine before the anxious Wednesday that followed.
---
Thursday morning I finished freeze-proofing the house, then we drove to Atlanta for [Inuhele](https://inuhele.com) setup day. This is one of my favorite parts of being on staff: the transformation. You arrive at a generic hotel conference space and leave behind a tropical, kitschy paradise.
![Person standing next to a large Creature from the Black Lagoon statue in a hotel hallway](creature-greeting.jpg)
![Golden skeleton wearing a hat and lei, seated on a wooden barrel surrounded by ropes and nautical decor](skeleton-on-barrel.jpg)
![Floral arch with tropical flowers at the entrance to the convention space](floral-arch-entrance.jpg)
![Main Inuhele stage with thatched roof, tapa cloth backdrop, and peacock chairs under string lights](main-stage-setup.jpg)
By Thursday night the space had become something else entirely.
---
Friday was the first full day of the convention. I'm the A/V and general tech support person for Inuhele, but my workload this year was genuinely light. Light enough that I could do it with a drink—or two—always in hand.
![Evening scene at Inuhele with string lights, colorful lanterns, and attendees in Hawaiian shirts mingling](evening-atmosphere.jpg)
![Mirror selfie showing Eric in tiki toga party attire: blue embroidered fez, purple sash draped as a toga over a tropical shirt, and the cormorant pendant](friday-outfit.jpg)
![Two people in matching silver space suits at a mobile bar cart](space-couple-bar.jpg)
![Hand holding a cocktail garnished with a lime wheel, cherry, ti leaf, and small hibiscus pick in an Inuhele-branded cup](inuhele-cocktail.jpg)
I love this convention and the people who come to it. The creativity and silliness seem boundless. People show up in elaborate costumes, build themed room parties, treat the whole weekend as collaborative performance art.
My favorite new person was [MeduSirena](https://www.instagram.com/fireeatingmermaid.medusirena/), billed as "the fire-eating mermaid." Her approach to art and performance mirrors my own sensibilities so closely—but she actually goes out and *does* it, full time. I only dabble. Watching her made me want to do more. Not necessarily fire-eating or mermaid-ing, but *something*. She's the kind of inspiration that sticks with you after the convention ends.
---
**Shipped:** One hand-painted cormorant pendant. The CONpossible prints are still on the printer, waiting for paint.
**Noticed:** Duluth's international food scene keeps rewarding exploration. And MeduSirena reminded me that "dabbling" is a choice, not a constraint.
**What's Next:** An ice storm was bearing down on Georgia as Friday ended. We were safely in Atlanta with the cats cared for at home, but the forecast looked bad. More on that next week. CONpossible prep continues—those prints need painting.
**Vibe Check:** A week that built from routine through anxiety and released into something joyful.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 MiB

View File

@@ -0,0 +1,63 @@
---
title: "Weeknotes: January 2430, 2026"
date: 2026-01-31T09:00:00-05:00
draft: false
description: One day you're watching fire dancers in a hotel ballroom transformed into a Polynesian paradise. Three days later you're on back-to-back video calls while ice encases everything outside your window.
tags:
- weeknotes
- Inuhele
- swimming
- ice-storm
- work
lastmod: 2026-02-04T03:01:17.216Z
---
One day you're watching fire dancers in a hotel ballroom transformed into a Polynesian paradise. Three days later you're on back-to-back video calls while ice encases everything outside your window. Convention weeks do that to you.
---
Saturday was the last full day of Inuhele, and it delivered. Panels all day, then a spectacular evening show where MeduSirena and her husband once again demonstrated why they're worth traveling to see: wit, creativity, and genuine mastery over their art form. After that, the room parties. If you've never been to a tiki convention, you might expect grass skirts and plastic leis. You'd be wrong. One room transported us to Victorian-era Coney Island. The attention to detail in these transformations is staggering.
!["Let Them Drink Rum." Tiki Marie Antoinette watches carnival jugglers in the atrium. Just another Saturday night.](atrium-saturday-night.jpg)
The reverie continued well into the early morning hours.
Sunday is usually a half day, but the incoming ice storm had other plans. Most attendees fled before noon, which meant those of us on crew got an early start on breakdown. Returning an immersive tropical paradise back into boring hotel conference space is its own kind of work: slower than setup, and a little melancholy. A dedicated crew stayed until after dark, loading everything onto trucks and depositing it safely back in the warehouse. A few of us closed out the weekend with dinner at the hotel bar, tired and satisfied.
![Monday morning, from the hotel coffee shop. We slept in and waited for things to warm up before making our escape.](ice-storm-monday.jpg)
The ice arrived overnight, though not as badly as feared. Northern Alabama got crippled; large swaths north and west of Atlanta were hit hard. We got a late checkout and made our careful escape back to Athens in the afternoon. The roads were fine even as the ice on the trees grew thicker the closer we got to home. Good thing we didn't wait much longer. Everything froze over again at sunset and stayed that way for two days.
---
One downside to being in charge of so many things: when I take time away, the work just waits. Tuesday was a full day of meetings, catching up on everything that had queued while I was hauling tiki bars and dodging ice. The town was still frozen over, but our power held, and I powered through.
Wednesday and Thursday I wrapped up a project I'd been sprinting on for five weeks: a community engagement portal for a university client. The final push involved rebuilding several major components after discovering, three days before deadline, that we'd been working from outdated brand guidelines. We still shipped on time. I wrote up the story for the [Infinity blog](https://iinteractive.com/resources/blog/why-we-build-the-runway-before-we-need-it). The short version is good infrastructure, centralized design tokens, and a tireless AI collaborator named Ray turned what could have been a crisis into just a hard few days.
But the technical save wasn't the satisfying part. It was the client's reaction when she saw her years-long vision finally realized. That kind of joy is why I do this work.
---
Friday was the final swim meet of the season: a local championship at the UGA pool with several area high schools competing. Juniper set more personal bests, continuing her trend of steady improvement all year. Swim season is brutal for everyone involved: near-daily evening practices stacked on top of a heavy class load. It was certainly a challenge this year. Good to have it behind us and settle into a more relaxed spring schedule.
---
**Shipped:** The university portal, after a four-week solo sprint and a three-day scramble at the end. One of the more satisfying deliveries I've had in a while.
**Read:** Honestly? Nothing. Convention weeks don't leave much room for reading.
**Played:** Also nothing. See above.
**Cooked:** Survival mode. Whatever was in the fridge that hadn't gone bad while we were gone.
**Noticed:** The strange beauty of ice-coated trees against gray skies. The whiplash of going from tropical escapism to frozen reality in 48 hours. And once the thaw finally came, Piglet and Wil'em reclaimed the sunny windowsill like nothing had ever happened.
![They do not care about your deadlines or your ice storms. They found the sun.](piglet-sunbathing.jpg)
![They do not care about your deadlines or your ice storms. They found the sun.](wilem-sunbathing.jpg)
**Thinking about:** How conventions create these intense temporary communities, then disperse. How the breakdown is part of the ritual—you can't just leave the magic standing. Also thinking about what happens after you ship something hard: the scramble, then the relief, then the quiet satisfaction once it's done.
**What's Next:** CONpossible is at the end of next week. Am I ready? Not even a little bit.
**Vibe Check:** Coming down from the high, digging back into the work,

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 MiB

View File

@@ -2,25 +2,47 @@
title: Eric in the Past title: Eric in the Past
--- ---
This page is all about what I have done. As things roll off the [now page](/now), they'll show up here. It was last updated on January 7, 2023. This page is all about what I have done. As things roll off the [now page](/now), they'll show up here. It was last updated on December 25, 2025.
## What I finished building
- **LocallyGrown.net Migration** (2025) — A massive six-month project migrating a farmers market platform from dying Rails 3 infrastructure to modern SvelteKit. 70+ farmers markets, $1.3 million in annual sales, and a complete rewrite that touched every part of the system. [The whole story is in my blog series](/posts/locallygrown-origin-story/).
- **[Random Recipe Project](https://www.youtube.com/@RandomRecipeProject)** — A YouTube cooking series with 50+ episodes exploring recipes from everywhere: video game tie-in cookbooks (Skyrim, Dragon Age, Fallout, Star Trek), vintage classics (Betty Crocker, Julia Child, Galloping Gourmet), modern favorites (Kenji Lopez-Alt, Moosewood), and even 600-year-old medieval recipes.
- **Kestrel's Nest Federation** — Built out my self-hosted social media presence on YunoHost: Mastodon, Pixelfed, BookWyrm, Castopod, Gitea, and this blog. All running on a DigitalOcean droplet I've upgraded twice since 2021.
- **BookWyrm for YunoHost** — Contributed the BookWyrm integration package now used by other YunoHost deployments.
## What I finished reading ## What I finished reading
- The first six stories (three novels and three shorts) of [Dan Moren's](https://dmoren.com/) [Galactic Cold War](https://dmoren.com/writing/galactic-cold-war/) series. These are spy thrillers set across multiple star systems colonized by humans. Both aspects are very well done and each story has been better than the last. Recent reads (see my full [BookWyrm shelf](https://books.kestrelsnest.social/user/eric/books/read) for the complete list):
- [Paper Girls](https://imagecomics.com/comics/series/paper-girls). I had the final two installments sitting on my nightstand for months and finally sat down to read them. My inaction is not reflective of the quality, as I really did like the whole series. It's a time travel adventure that fits in almost all of the standard time travel tropes, but it blends them together in really weird ways that tread new ground. And the artwoek is top-notch, and that really helped. The images of kaiju tardigrades battling it out over the city will stick with me for a long time.
- All of Martha Wells' [Murderbot Diaries](https://www.marthawells.com/murderbot.htm). These are all told in the first person by a sentient security robot who has hacked himself to be free of harsh programmed restrictions and has to live in human society. I can't stress enough how much I loved these, perhaps aided by how my own coming to terms with being an undiagnosed autistic adult makes me [relate to the main character](https://www.tor.com/2022/06/21/murderbot-an-autistic-coded-robot-done-right/). - *I'm Starting to Worry about This Black Box of Doom* by Jason Pargin (2024)
- *The Armageddon Protocol* by Dan Moren (2024) — completing the Galactic Cold War series
- *All Souls Lost* by Dan Moren (2023)
- *Bookshops & Bonedust* by Travis Baldree (2023)
- *Antimatter Blues* by Edward Ashton (2023)
- *Mickey7* by Edward Ashton (2022)
- *The Kaiju Preservation Society* by John Scalzi (2022)
- *Sea of Tranquility* by Emily St. John Mandel (2022)
- The complete [Galactic Cold War](https://dmoren.com/writing/galactic-cold-war/) series by Dan Moren — spy thrillers across multiple star systems. Each story better than the last.
- [Paper Girls](https://imagecomics.com/comics/series/paper-girls) — time travel adventure with kaiju tardigrades. The artwork will stick with me.
- All of Martha Wells' [Murderbot Diaries](https://www.marthawells.com/murderbot.htm) — a sentient security robot navigating human society. [Deeply relatable](https://www.tor.com/2022/06/21/murderbot-an-autistic-coded-robot-done-right/) as an autistic adult.
## What I finished playing ## What I finished playing
- [The Outer Worlds][outer worlds] from Obsidian. Decididly anti-corporate darkly humorous science fiction from some of the same people who created the [Fallout games](https://en.wikipedia.org/wiki/Fallout_(series)). Short gameplay, but I loved every minute of it. - [The Outer Worlds][outer worlds] from Obsidian — darkly humorous anti-corporate science fiction. Short but loved every minute. (Now playing the sequel!)
- [The Dragon Age series](https://dragonage.fandom.com/wiki/Dragon_Age_Wiki) from BioWare. I played them all as they originally came out, but recently did a replay straight through. In terms of world building and story telling, this is my favorite game series (*perhaps* tied with the Elder Scroll and Fallout games) - [The Dragon Age series](https://dragonage.fandom.com/wiki/Dragon_Age_Wiki) from BioWare — replayed the entire series straight through. My favorite game series for world building and storytelling.
- [Elder Scrolls Online](https://www.elderscrollsonline.com). New content comes out all the time so I'll be back, but for now I'm caught up. I did find a good guild to socialize with, but I've played everything I could by myself. Surprisingly, that was most of the content and 100% of the story. - [Elder Scrolls Online](https://www.elderscrollsonline.com) — caught up on all solo content. Found a good guild to socialize with.
## What I finished watching ## What I finished watching
- Season Three of [Jack Ryan](https://www.amazon.com/gp/video/detail/B0B8KWN7N2/ref=atv_dp_season_select_s3) on Amazon. I was a big fan of the original books (to the point where I considered applying to the CIA in my late 20s) and I liked the first two seasons of the show well enough. This one? Not so much. I mean, I watched the whole thing to see it out, but I wasn't happy about it. The character works when he's placed in a realistic world and the events could plausibly happen. This season might have well been set in an alternate history earth, where the geopolitics are similar yet very different from our own in key ways, the technology borders on fantastical, and things like travel time and logistics coordination is miraculous. In this fantasy setting, Jack Ryan became a comic book character in a superhero story. And I don't want or enjoy that version of Ryan. - All episodes of [Taskmaster][taskmaster] [on YouTube][taskmaster youtube]. Nothing has lifted my spirits quite like this show.
- All the espisodes of [Taskmaster][taskmaster] [on YouTube][taskmaster youtube] (and some that fell off the back of a truck). Nothing I've watched recently has lifted my spirits and made me giggle like this show.
[taskmaster]:https://taskmaster.fandom.com/wiki/Taskmaster_Wiki ## Events attended
- **Wild Rumpus 2025** — Athens' big outdoor Halloween parade festival
- **Inuhele** and **CONpossible** — annual Atlanta conventions where I'm on staff
- **The B-52's Farewell Concert** (January 2023) — in Athens, where it all started
[taskmaster]: https://taskmaster.fandom.com/wiki/Taskmaster_Wiki
[taskmaster youtube]: https://www.youtube.com/channel/UCT5C7yaO3RVuOgwP8JVAujQ [taskmaster youtube]: https://www.youtube.com/channel/UCT5C7yaO3RVuOgwP8JVAujQ
[outer worlds]: https://outerworlds.obsidian.net/en/enter [outer worlds]: https://outerworlds.obsidian.net/en/enter

View File

@@ -2,25 +2,37 @@
title: Eric Yet To Come title: Eric Yet To Come
--- ---
This page is all about what I am planning on doing in the not too distant future. As I get to them, they'll leave here and appear on my [now page](/now). It was last updated on December 21, 2022. This page is all about what I am planning on doing in the not too distant future. As I get to them, they'll leave here and appear on my [now page](/now). It was last updated on December 25, 2025.
## Where I will be ## Where I will be
- Athens, early January 2023, for the B-52's final concert of their farewell tour - **[Inuhele](https://inuhele.com)** (January 2325, 2026) — Atlanta's annual Tiki Weekend at the Omni Atlanta Hotel at Centennial Park. I'm on staff and it's always a blast.
- Las Vegas, mid-January, for [my employer](https://iinteractive.com)'s annual all-hands gathering - **[CONpossible](https://www.conpossible.org)** (February 68, 2026) — I'm the Costuming Track Director for this annual convention at the Sonesta Gwinnett Place Atlanta. This year's theme is "Through the Faerie Ring"—magic mixed with technology should be a lot of fun.
- Atlanta, late January, for [Inuhele](https://inuhele.com), Atlanta's annual Tiki Weekend. I'm on the staff there and it's loads of fun
- Atlanta, early February, for the [Atlanta Steampunk Exposition](https://www.atlantasteampunkexpo.com). I love this crowd. The theme this year is "steampunks in space" and I'm really looking forward to it. ## What I will be building
- **Loops for YunoHost** — I'm planning on packaging [Loops](https://loops.video), an alternative TikTok-style video platform, for the YunoHost self-hosting ecosystem. Another piece in the Kestrel's Nest federation.
- **LocallyGrown.net features** — Now that the massive Rails-to-SvelteKit migration is complete, the real work is ongoing: new features, better tools for market managers, and growing the platform that serves 70+ farmers markets.
- **More tiny apps** — The pantry inventory app reminded me how satisfying it is to build small, focused tools that solve specific household problems. More of those are coming.
## What I will be making
- **Corset vest** — A sewing project I'm genuinely excited about. The fabric is ready, the pattern is chosen, I just need to make time.
- **Leather projects** — Supplies have been sitting ready since fall. Time to actually use them.
- **Random Recipe Project episodes** — My YouTube cooking series has been on hiatus during the LocallyGrown marathon. Ready to get back to the kitchen camera.
- **Podcast episodes** — Both Eric Says Hi and Brothers Grimm Lunch have been quiet too long.
## What I will be reading ## What I will be reading
- The rest of [Dan Moren's](https://dmoren.com/) [Galactic Cold War](https://dmoren.com/writing/galactic-cold-war/) series. After I finish my current book, I'll have one short story and one novel to go. - Currently a few chapters into *Service Model* by Adrian Tchaikovsky. After that, the stack awaits.
## What I will be watching ## What I will be exploring
- [The Rings of Power](https://www.amazon.com/Lord-Rings-Power-Season/dp/B09QH98YG1). As much of a Tolkien nerd as I am, it's shocking I didn't watch these as they came out, but my plate was full. This has risen near the top now. - **n8n automations** — The standup prep workflow opened my eyes to what's possible. I keep noticing small friction points that could be automated away.
- **Claude Code workflows** — Finding new ways to collaborate with AI on rapid prototyping. The two-hour pantry app was an example of what's possible when you're sculpting code instead of typing it.
## What I will be playing ## Where my head is going
- [Gloomhaven](https://cephalofair.com/collections/board-games/products/gloomhaven). My copy arrived shortly after the pandemic started and it's just been sitting here. I really want to play it so I just need to decide if I'm going solo or if it's time to find a party to play with me. The LocallyGrown marathon consumed everything for six months. Now I'm slowly emerging, rediscovering equilibrium, and reconnecting with the creative projects that feed my soul. The self-hosting work continues to expand Kestrel's Nest. The making and creating is resuming. The writing is happening again.
- [Etherfields](https://etherfieldsapp.awakenrealms.com). I kickstarted this game shortly before the pandemic and it only recently arrived. I have friends in mind I want to play it with.
- [Sleeping Gods](https://www.redravengames.com/sleeping-gods/). Another pandemic arrival that I need to either just play solo or find a group to play with. Forward motion, one project at a time.

10
content/weeknotes.md Normal file
View File

@@ -0,0 +1,10 @@
---
title: Weeknotes
type: weeknotes
aliases:
- /weeknotes/
---
Weeknotes are short, personal reflections on the week—what I shipped, read, played, cooked, noticed, and thought about. They're a way to mark time and notice patterns.
The format comes from the [weeknotes movement](https://indieweb.org/weeknotes), which grew out of the UK digital government community and spread through personal blogs. [Giles Turnbull's guide](https://doingweeknotes.com/) and [Tracy Durnell's reflections](https://tracydurnell.com/2024/07/30/using-personal-weeknotes-as-a-tool-for-attention/) shaped my approach. My friend [Genehack](https://genehack.blog/archives/) introduced me to weeknotes and inspired me to start my own after he'd been doing them for well over a year.

4
deploy
View File

@@ -3,6 +3,10 @@ USER=admin
HOST=social HOST=social
DIR=../../var/www/blog/www # the directory where your web site files should go DIR=../../var/www/blog/www # the directory where your web site files should go
# Clean build directories to force full regeneration
rm -rf public resources
# Build site and index, then deploy
hugo && pagefind --site public && rsync -avz --no-t --no-p --delete public/ ${HOST}:~/${DIR} # this will delete everything on the server that's not in the local public folder hugo && pagefind --site public && rsync -avz --no-t --no-p --delete public/ ${HOST}:~/${DIR} # this will delete everything on the server that's not in the local public folder
exit 0 exit 0

View File

@@ -62,5 +62,8 @@
"frontMatter.taxonomy.categories": [], "frontMatter.taxonomy.categories": [],
"frontMatter.content.autoUpdateDate": true, "frontMatter.content.autoUpdateDate": true,
"frontMatter.dashboard.openOnStart": true, "frontMatter.dashboard.openOnStart": true,
"frontMatter.preview.host": "http://localhost:1313/" "frontMatter.preview.host": "http://localhost:1313/",
"frontMatter.website.host": "https://blog.kestrelsnest.social",
"frontMatter.framework.startCommand": "hugo server -D -F",
"frontMatter.git.enabled": true
} }

24
layouts/404.html Normal file
View File

@@ -0,0 +1,24 @@
{{ define "main" }}
<article class="post">
<header class="post-header">
<h1 class="post-title">404: Page Not Found</h1>
</header>
<div class="post-content">
<p>Well, this is awkward.</p>
<p>The page you're looking for isn't here. Maybe it never was, or maybe it got lost in one of the many migrations this blog has been through since 2001.</p>
<p>Here are some places you might want to try:</p>
<ul>
<li><a href="/">Home</a> — Recent posts</li>
<li><a href="/tags/">Tags</a> — Browse by topic</li>
<li><a href="/weeknotes/">Weeknotes</a> — Weekly updates</li>
<li><a href="/search/">Search</a> — Find what you're looking for</li>
</ul>
<p>Or just <a href="mailto:eric@ericwagoner.com?subject=Broken link on blog">let me know</a> if you think something should be here.</p>
</div>
</article>
{{ end }}

View File

@@ -0,0 +1 @@
<img src="{{ .Destination | safeURL }}" alt="{{ .Text }}"{{ with .Title }} title="{{ . }}"{{ else }} title="{{ $.Text }}"{{ end }} />

View File

@@ -11,7 +11,7 @@
{{- $pages = $pages | first $limit -}} {{- $pages = $pages | first $limit -}}
{{- end -}} {{- end -}}
{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }} {{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel> <channel>
<title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title> <title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
<link>{{ .Permalink }}</link> <link>{{ .Permalink }}</link>
@@ -32,17 +32,8 @@
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
{{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}} {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
<guid>{{ .Permalink }}</guid> <guid>{{ .Permalink }}</guid>
<description> <description>{{ .Summary | html }}</description>
{{- $content := .Content -}} <content:encoded>{{ printf "<![CDATA[%s]]>" .Content | safeHTML }}</content:encoded>
{{- if .Params.summary -}}
{{- $content = .Params.summary -}}
{{- else if .Truncated -}}
{{- $content = .Description -}}
{{- else -}}
{{- $content = .Plain | truncate 200 -}}
{{- end -}}
{{ $content | html }}
</description>
</item> </item>
{{ end }} {{ end }}
</channel> </channel>

View File

@@ -5,9 +5,21 @@
{{ range where .Paginator.Pages "Type" "!=" "page" }} {{ range where .Paginator.Pages "Type" "!=" "page" }}
<li class="posts-list-item"> <li class="posts-list-item">
<a class="posts-list-item-title" href="{{ .Permalink }}">{{ .Title }}</a> <a class="posts-list-item-title" href="{{ .Permalink }}">{{ .Title }}</a>
{{ if .Params.series }}
{{ $series := index .Params.series 0 }}
{{ $order := .Params.series_order }}
<span class="series-indicator">
{{ partial "icon.html" (dict "ctx" $ "name" "book") }}
Part {{ $order }} of {{ $series }}
</span>
{{ end }}
<span class="posts-list-item-description"> <span class="posts-list-item-description">
<div> <div>
{{ if .Description }} {{ if .Params.preview }}
<br />{{ .Params.preview }}<br />(more inside)<br /><br />
{{ else if .Params.description }}
<br />{{ .Params.description }}<br />(more inside)<br /><br />
{{ else if .Description }}
<br />{{ .Description }}<br />(more inside)<br /><br /> <br />{{ .Description }}<br />(more inside)<br /><br />
{{ else }} {{ else }}
{{ .Content }} {{ .Content }}

View File

@@ -0,0 +1,40 @@
{{ define "main" }}
<article class="post">
<header class="post-header">
<h1 class ="post-title">{{ .Title }}</h1>
{{- if ne .Type "page" }}
<div class="post-meta">
<div>
{{ partial "icon.html" (dict "ctx" $ "name" "calendar") }}
{{ .PublishDate.Format "Jan 2, 2006" }}
</div>
<div>
{{ partial "icon.html" (dict "ctx" $ "name" "clock") }}
{{ .ReadingTime }} min read
</div>
{{- with .Params.tags }}
<div>
{{ partial "icon.html" (dict "ctx" $ "name" "tag") }}
{{- range . -}}
{{ with $.Site.GetPage (printf "/%s/%s" "tags" . ) }}
<a class="tag" href="{{ .RelPermalink }}">{{ .Title }}</a>
{{- end }}
{{- end }}
</div>
{{- end }}
</div>
{{- end }}
</header>
<div class="post-content">
{{ .Content }}
</div>
{{/* Series navigation at the bottom of the post */}}
{{ partial "series.html" . }}
<div class="post-footer">
{{ template "_internal/disqus.html" . }}
</div>
</article>
{{ end }}

View File

@@ -0,0 +1,66 @@
{{ with .Params.series }}
{{ $currentPage := $ }}
{{ $series := index . 0 }}
{{ $currentOrder := $.Params.series_order }}
{{/* Get all posts in this series */}}
{{ $seriesPosts := where $.Site.RegularPages "Params.series" "intersect" (slice $series) }}
{{ $seriesPosts = sort $seriesPosts "Params.series_order" }}
{{ $totalParts := len $seriesPosts }}
{{ if gt $totalParts 1 }}
<div class="series-box">
<div class="series-header">
<strong>{{ $series }}</strong>
<span class="series-part">Part {{ $currentOrder }} of {{ $totalParts }}</span>
</div>
<nav class="series-nav">
<ol class="series-list">
{{ range $seriesPosts }}
<li class="{{ if eq .RelPermalink $currentPage.RelPermalink }}current{{ end }}">
{{ if eq .RelPermalink $currentPage.RelPermalink }}
<strong>{{ .Title }}</strong>
{{ else }}
<a href="{{ .RelPermalink }}">{{ .Title }}</a>
{{ end }}
</li>
{{ end }}
</ol>
</nav>
<div class="series-navigation">
{{/* Previous post */}}
{{ $prevPost := "" }}
{{ $nextPost := "" }}
{{ $foundCurrent := false }}
{{ range $seriesPosts }}
{{ if $foundCurrent }}
{{ if not $nextPost }}
{{ $nextPost = . }}
{{ end }}
{{ else if eq .RelPermalink $currentPage.RelPermalink }}
{{ $foundCurrent = true }}
{{ else }}
{{ $prevPost = . }}
{{ end }}
{{ end }}
<div class="series-nav-links">
{{ with $prevPost }}
<a href="{{ .RelPermalink }}" class="series-prev">← Previous: {{ .Title }}</a>
{{ else }}
<span class="series-prev disabled">← Previous</span>
{{ end }}
{{ with $nextPost }}
<a href="{{ .RelPermalink }}" class="series-next">Next: {{ .Title }} →</a>
{{ else }}
<span class="series-next disabled">Next →</span>
{{ end }}
</div>
</div>
</div>
{{ end }}
{{ end }}

View File

@@ -0,0 +1,41 @@
<figure {{ with .Get "class" }}class="{{.}}"{{ end }}>
{{ with .Get "align" }}<div align="{{.}}">{{ end }}
{{ with .Get "href" }}<a href="{{.}}">{{ end }}
<img src="{{ or (.Get "src") (.Get 0) }}"
{{ with .Get "class" }}class="{{.}}"{{ end }}
{{ if or (.Get "alt") (.Get "caption") }}
alt="{{ with .Get "alt"}}{{.}}{{else}}{{ .Get "caption" }}{{ end }}"
{{ end }}
{{ with .Get "width" }}width="{{.}}"{{ end }}
{{ with .Get "height" }}height="{{.}}"{{ end }}
/>
{{ if or (or (.Get "title") (.Get "caption")) (.Get "attr")}}
<figcaption>
{{ if isset .Params "title" }}<h4>{{ .Get "title" }}</h4>{{ end }}
{{ if or (.Get "caption") (.Get "attr")}}
{{ .Get "caption" }}
{{ if isset .Params "attrlink"}}
{{ if isset .Params "align"}}
{{/* we need the class here to remove margin-left for center alignment */}}
<a class="{{.Get "align"}}" href="{{.Get "attrlink"}}">
{{ else }}
<a href="{{.Get "attrlink"}}">
{{ end }}
{{ end }}
{{ .Get "attr" }}
{{ if isset .Params "attrlink"}}</a>{{ end }}
{{ end }}
</figcaption>
{{ end }}
{{ if isset .Params "align" }}</div>{{ end }}
{{ if isset .Params "href" }}</a>{{ end }}
</figure>

View File

@@ -0,0 +1,40 @@
{{ define "main" }}
<article class="post">
<header class="post-header">
<h1 class="post-title">{{ .Title }}</h1>
</header>
<div class="post-content">
{{ .Content }}
</div>
<h2>All Weeknotes</h2>
<ul class="posts-list">
{{ range (where .Site.RegularPages "Params.tags" "intersect" (slice "weeknotes")).ByDate.Reverse }}
<li class="posts-list-item">
<a class="posts-list-item-title" href="{{ .Permalink }}">{{ .Title }}</a>
<span class="posts-list-item-description">
<div>
{{ if .Params.preview }}
<br />{{ .Params.preview }}<br />(more inside)<br /><br />
{{ else if .Params.description }}
<br />{{ .Params.description }}<br />(more inside)<br /><br />
{{ else if .Description }}
<br />{{ .Description }}<br />(more inside)<br /><br />
{{ else }}
{{ .Summary }}
{{ end }}
</div>
</span>
<span class="posts-list-item-description">
{{ partial "icon.html" (dict "ctx" $ "name" "calendar") }}
{{ .PublishDate.Format "Jan 2, 2006" }}
<span class="posts-list-item-separator">-</span>
{{ partial "icon.html" (dict "ctx" $ "name" "clock") }}
{{ .ReadingTime }} min read
</span>
</li>
{{ end }}
</ul>
</article>
{{ end }}

View File

@@ -1 +0,0 @@
*{box-sizing:border-box}html{line-height:1.6}body{margin:0;font-family:sans-serif;background:#353b43;color:#afbac4}h1,h2,h3,h4,h5,h6{color:#fff}a{color:#57cc8a;transition:color .35s;text-decoration:none}a:hover{color:#fff}code{font-family:monospace,monospace;font-size:1em;color:rgba(175,186,196,.8)}pre{font-size:1rem;line-height:1.2em;margin:0;overflow:auto}pre code{font-size:.8em}::selection{background:rgba(175,186,196,.25)}::-moz-selection{background:rgba(175,186,196,.25)}.app-header{padding:2.5em;background:#242930;text-align:center}.app-header-avatar{width:15rem;height:15rem;border-radius:100%;border:.5rem solid #57cc8a}.app-container{padding:2.5rem}.app-header-social{display:flex;align-items:center;justify-content:center;font-size:2em;color:#fff}.app-header-social a:not(:last-child){margin-right:.4em}.app-header-title{color:#fff;display:block;font-size:2em;margin:.67em 0;font-weight:700}@media(min-width:940px){.app-header{position:fixed;top:0;left:0;width:20rem;min-height:100vh}.app-container{max-width:65rem;margin-left:20rem}}.error-404{text-align:center}.error-404-title{text-transform:uppercase}.icon{display:inline-block;width:1em;height:1em;margin-top:-.125em}.pagination{display:block;list-style:none;padding:0;font-size:.8em;text-align:center;margin:3em 0}.page-item{display:inline-block}.page-item .page-link{display:flex;align-items:center;justify-content:center;width:1.8rem;height:1.8rem}.page-item.active .page-link{color:#fff;border-radius:2em;background:#57cc8a}.post-title{color:#fff}.post-meta>div{display:flex;align-items:center;font-size:.8em}.post-meta>div>.icon{margin-right:.4em}.post-content>pre,.post-content .highlight{margin:1em 0}.post-content>pre,.post-content .highlight>pre,.post-content .highlight>div{border-left:.4em solid rgba(87,204,138,.8);padding:1em}.post-content blockquote{border-left:.4em solid rgba(87,204,138,.8);margin:1em 0;padding:.5em 1em;background:#242930}.post-content blockquote p{margin:.5em 0}.post-content img{max-width:100%}.posts-list{padding:0}.posts-list-item{list-style:none;padding:.4em 0}.posts-list-item:not(:last-child){border-bottom:1px dashed rgba(255,255,255,.3)}.posts-list-item-description{display:flex;align-items:center;font-size:.8em}.posts-list-item-description>.icon{margin-right:.4em}.posts-list-item-separator{margin:0 .4em}.tag{display:inline-block;margin-right:.2em;padding:0 .6em;font-size:.9em;border-radius:.2em;white-space:nowrap;background:rgba(255,255,255,.1);transition:color .35s,background .35s}.tag:hover{transition:color .25s,background .05s;background:rgba(255,255,255,.3)}.tags-list{padding:0}.tags-list-item{display:flex;align-items:center;list-style:none;padding:.4em 0}.tags-list-item>.icon{margin-right:.4em}.tags-list-item:not(:last-child){border-bottom:1px dashed rgba(255,255,255,.3)}@media(min-width:450px){.tags-list{display:flex;flex-wrap:wrap}.tags-list-item{width:calc(50% - 1em)}.tags-list-item:nth-child(even){margin-left:1em}.tags-list-item:nth-last-child(2){border:none}}.app-header-social a:not(:last-child){margin-right:.2em}

View File

@@ -1 +0,0 @@
{"Target":"css/main.min.094178a756796576f886abd7963ac38503256532ad258acaed956b7f683180f4.css","MediaType":"text/css","Data":{"Integrity":"sha256-CUF4p1Z5ZXb4hqvXljrDhQMlZTKtJYrK7ZVrf2gxgPQ="}}

View File

@@ -1,984 +0,0 @@
*,
*::before,
*::after {
box-sizing: border-box; }
* {
margin: 0;
padding: 0; }
html {
font-size: 100%; }
body {
line-height: 1.5; }
html,
body {
height: 100%; }
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
margin: 1rem 0; }
input,
button,
textarea,
select {
font: inherit; }
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word; }
.highlight .hll {
background-color: #49483e; }
.highlight,
pre {
background: #272822;
color: #f8f8f2; }
.highlight .c {
color: #75715e; }
/* Comment */
.highlight .err {
color: #960050;
background-color: #1e0010; }
/* Error */
.highlight .k {
color: #66d9ef; }
/* Keyword */
.highlight .l {
color: #ae81ff; }
/* Literal */
.highlight .n {
color: #f8f8f2; }
/* Name */
.highlight .o {
color: #f92672; }
/* Operator */
.highlight .p {
color: #f8f8f2; }
/* Punctuation */
.highlight .ch {
color: #75715e; }
/* Comment.Hashbang */
.highlight .cm {
color: #75715e; }
/* Comment.Multiline */
.highlight .cp {
color: #75715e; }
/* Comment.Preproc */
.highlight .cpf {
color: #75715e; }
/* Comment.PreprocFile */
.highlight .c1 {
color: #75715e; }
/* Comment.Single */
.highlight .cs {
color: #75715e; }
/* Comment.Special */
.highlight .gd {
color: #f92672; }
/* Generic.Deleted */
.highlight .ge {
font-style: italic; }
/* Generic.Emph */
.highlight .gi {
color: #a6e22e; }
/* Generic.Inserted */
.highlight .gs {
font-weight: bold; }
/* Generic.Strong */
.highlight .gu {
color: #75715e; }
/* Generic.Subheading */
.highlight .kc {
color: #66d9ef; }
/* Keyword.Constant */
.highlight .kd {
color: #66d9ef; }
/* Keyword.Declaration */
.highlight .kn {
color: #f92672; }
/* Keyword.Namespace */
.highlight .kp {
color: #66d9ef; }
/* Keyword.Pseudo */
.highlight .kr {
color: #66d9ef; }
/* Keyword.Reserved */
.highlight .kt {
color: #66d9ef; }
/* Keyword.Type */
.highlight .ld {
color: #e6db74; }
/* Literal.Date */
.highlight .m {
color: #ae81ff; }
/* Literal.Number */
.highlight .s {
color: #e6db74; }
/* Literal.String */
.highlight .na {
color: #a6e22e; }
/* Name.Attribute */
.highlight .nb {
color: #f8f8f2; }
/* Name.Builtin */
.highlight .nc {
color: #a6e22e; }
/* Name.Class */
.highlight .no {
color: #66d9ef; }
/* Name.Constant */
.highlight .nd {
color: #a6e22e; }
/* Name.Decorator */
.highlight .ni {
color: #f8f8f2; }
/* Name.Entity */
.highlight .ne {
color: #a6e22e; }
/* Name.Exception */
.highlight .nf {
color: #a6e22e; }
/* Name.Function */
.highlight .nl {
color: #f8f8f2; }
/* Name.Label */
.highlight .nn {
color: #f8f8f2; }
/* Name.Namespace */
.highlight .nx {
color: #a6e22e; }
/* Name.Other */
.highlight .py {
color: #f8f8f2; }
/* Name.Property */
.highlight .nt {
color: #f92672; }
/* Name.Tag */
.highlight .nv {
color: #f8f8f2; }
/* Name.Variable */
.highlight .ow {
color: #f92672; }
/* Operator.Word */
.highlight .w {
color: #f8f8f2; }
/* Text.Whitespace */
.highlight .mb {
color: #ae81ff; }
/* Literal.Number.Bin */
.highlight .mf {
color: #ae81ff; }
/* Literal.Number.Float */
.highlight .mh {
color: #ae81ff; }
/* Literal.Number.Hex */
.highlight .mi {
color: #ae81ff; }
/* Literal.Number.Integer */
.highlight .mo {
color: #ae81ff; }
/* Literal.Number.Oct */
.highlight .sa {
color: #e6db74; }
/* Literal.String.Affix */
.highlight .sb {
color: #e6db74; }
/* Literal.String.Backtick */
.highlight .sc {
color: #e6db74; }
/* Literal.String.Char */
.highlight .dl {
color: #e6db74; }
/* Literal.String.Delimiter */
.highlight .sd {
color: #e6db74; }
/* Literal.String.Doc */
.highlight .s2 {
color: #e6db74; }
/* Literal.String.Double */
.highlight .se {
color: #ae81ff; }
/* Literal.String.Escape */
.highlight .sh {
color: #e6db74; }
/* Literal.String.Heredoc */
.highlight .si {
color: #e6db74; }
/* Literal.String.Interpol */
.highlight .sx {
color: #e6db74; }
/* Literal.String.Other */
.highlight .sr {
color: #e6db74; }
/* Literal.String.Regex */
.highlight .s1 {
color: #e6db74; }
/* Literal.String.Single */
.highlight .ss {
color: #e6db74; }
/* Literal.String.Symbol */
.highlight .bp {
color: #f8f8f2; }
/* Name.Builtin.Pseudo */
.highlight .fm {
color: #a6e22e; }
/* Name.Function.Magic */
.highlight .vc {
color: #f8f8f2; }
/* Name.Variable.Class */
.highlight .vg {
color: #f8f8f2; }
/* Name.Variable.Global */
.highlight .vi {
color: #f8f8f2; }
/* Name.Variable.Instance */
.highlight .vm {
color: #f8f8f2; }
/* Name.Variable.Magic */
.highlight .il {
color: #ae81ff; }
/* Literal.Number.Integer.Long */
/*
* Gist + Github styles
* Adapted from https://github.com/lonekorean/gist-syntax-themes
*/
body .gist .gist-meta,
body .gist .highlight,
body .gist .gist-file,
body .gist .gist-file .gist-data {
background: #272822;
border: none; }
body .gist .blob-num,
body .gist .blob-code-inner,
body .gist .highlight,
body .gist .pl-enm,
body .gist .pl-ko,
body .gist .pl-mo,
body .gist .pl-mp1 .pl-sf,
body .gist .pl-ms,
body .gist .pl-pdc1,
body .gist .pl-scp,
body .gist .pl-smc,
body .gist .pl-som,
body .gist .pl-va,
body .gist .pl-vpf,
body .gist .pl-vpu,
body .gist .pl-mdr {
color: #aab1bf;
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace; }
body .gist .pl-mb,
body .gist .pl-pdb {
font-weight: 700; }
body .gist .pl-c,
body .gist .pl-c span,
body .gist .pl-pdc {
color: #5b6270;
font-style: italic; }
body .gist .pl-sr .pl-cce {
color: #56b5c2;
font-weight: 400; }
body .gist .pl-ef,
body .gist .pl-en,
body .gist .pl-enf,
body .gist .pl-eoai,
body .gist .pl-kos,
body .gist .pl-mh .pl-pdh,
body .gist .pl-mr {
color: #61afef; }
body .gist .pl-ens,
body .gist .pl-vi {
color: #be5046; }
body .gist .pl-enti,
body .gist .pl-mai .pl-sf,
body .gist .pl-ml,
body .gist .pl-sf,
body .gist .pl-sr,
body .gist .pl-sr .pl-sra,
body .gist .pl-src,
body .gist .pl-st,
body .gist .pl-vo {
color: #56b5c2; }
body .gist .pl-eoi,
body .gist .pl-mri,
body .gist .pl-pds,
body .gist .pl-pse .pl-s1,
body .gist .pl-s,
body .gist .pl-s1 {
color: #97c279; }
body .gist .pl-k,
body .gist .pl-kolp,
body .gist .pl-mc,
body .gist .pl-pde {
color: #c578dd; }
body .gist .pl-mi,
body .gist .pl-pdi {
color: #c578dd;
font-style: italic; }
body .gist .pl-mp,
body .gist .pl-stp {
color: #818896; }
body .gist .pl-mdh,
body .gist .pl-mdi,
body .gist .pl-mdr {
font-weight: 400; }
body .gist .pl-mdht,
body .gist .pl-mi1 {
color: #97c279;
background: #020; }
body .gist .pl-md,
body .gist .pl-mdhf {
color: #df6b75;
background: #200; }
body .gist .pl-corl {
color: #df6b75;
text-decoration: underline; }
body .gist .pl-ib {
background: #df6b75; }
body .gist .pl-ii {
background: #e0c184;
color: #fff; }
body .gist .pl-iu {
background: #e05151; }
body .gist .pl-ms1 {
color: #aab1bf;
background: #373b41; }
body .gist .pl-c1,
body .gist .pl-cn,
body .gist .pl-e,
body .gist .pl-eoa,
body .gist .pl-eoac,
body .gist .pl-eoac .pl-pde,
body .gist .pl-kou,
body .gist .pl-mm,
body .gist .pl-mp .pl-s3,
body .gist .pl-mq,
body .gist .pl-s3,
body .gist .pl-sok,
body .gist .pl-sv,
body .gist .pl-mb {
color: #d19965; }
body .gist .pl-enc,
body .gist .pl-entc,
body .gist .pl-pse .pl-s2,
body .gist .pl-s2,
body .gist .pl-sc,
body .gist .pl-smp,
body .gist .pl-sr .pl-sre,
body .gist .pl-stj,
body .gist .pl-v,
body .gist .pl-pdb {
color: #e4bf7a; }
body .gist .pl-ent,
body .gist .pl-entl,
body .gist .pl-entm,
body .gist .pl-mh,
body .gist .pl-pdv,
body .gist .pl-smi,
body .gist .pl-sol,
body .gist .pl-mdh,
body .gist .pl-mdi {
color: #df6b75; }
body {
font-family: "ibm-plex-serif", "IBM Plex Serif", ui-serif, Georgia, "Times New Roman", serif;
font-size: 20px;
color: #bbbbbb;
background-color: #2b343e;
-webkit-text-size-adjust: 100%; }
li > ul,
li > ol {
margin-bottom: 0; }
h1, h1 a,
h2, h2 a,
h3, h3 a,
h4, h4 a,
h5, h5 a,
h6, h6 a {
color: #c5cdd6;
font-weight: bold; }
figure {
margin-bottom: 24px; }
a {
color: #bbbbbb; }
a:hover {
text-decoration: #797979 underline; }
.icon > svg {
display: inline-block;
width: 16px;
height: 16px;
vertical-align: middle; }
.icon > svg path {
fill: #797979; }
blockquote {
color: #797979;
border-color: #797979;
border-left: 4px solid #797979;
padding-left: 18px;
padding-bottom: 8px;
margin-top: 12px;
margin-bottom: 24px;
font-style: italic; }
blockquote code {
font-size: 0.8rem; }
blockquote p {
margin: 0.5rem 0; }
blockquote > :last-child {
margin-bottom: 0; }
blockquote ul {
margin-top: 6px; }
.center {
text-align: center; }
.end {
text-align: end; }
.start {
text-align: start; }
#site-footer,
.post-index,
figure.full-bleed,
.post-header,
.post-content,
.post-meta,
#site-footer,
.post-index {
min-width: 375px;
display: grid;
grid-template-columns: 1fr min(65ch, calc(100% - 2 * 24px)) 1fr; }
#site-footer > *,
.post-index > *,
figure.full-bleed > *,
.post-header > *,
.post-content > *,
.post-meta > *,
#site-footer > *,
.post-index > * {
grid-column: 2; }
figure.full-bleed {
grid-template-columns: 0fr 100% 0fr; }
@media only screen and (min-width: 601px) {
figure.full-bleed {
grid-column: 1 / 4; }
figure.full-bleed img {
width: 100%;
max-height: 100vh; } }
@media only screen and (max-width: 600px) {
figure.full-bleed {
margin-left: -24px;
grid-template-columns: 0fr calc(100% + 24px) 0fr; } }
.goto {
display: none;
filter: alpha(opacity=0);
-moz-opacity: 0;
opacity: 0;
text-align: center;
position: fixed;
width: 35px;
right: 12px;
border-radius: 50%;
border: 2px solid #797979;
background-color: #2b343e;
-webkit-transition: opacity 1s ease-in;
-moz-transition: opacity 1s ease-in;
-ms-transition: opacity 1s ease-in;
-o-transition: opacity 1s ease-in;
transition: opacity 1s ease-in; }
.goto a {
text-decoration: none;
color: #bbbbbb;
width: 100%;
display: block; }
.goto.top {
top: 12px; }
.goto.bottom {
bottom: 12px; }
.post-index h2.post-list-header {
color: #404d5c;
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace;
font-size: 25px;
font-weight: normal;
margin-bottom: 24px; }
.post-index h2.post-list-header + ul {
margin: 0 0 24px 24px; }
.post-index ul.post-list-content {
float: left;
list-style: none;
margin: 0;
padding: 0; }
.post-index ul.post-list-content .post-link {
font-family: "ibm-plex-sans", "IBM Plex Sans", ui-sans-serif, sans-serif;
font-size: 22px;
display: block;
float: left;
clear: left;
margin: 8px 0; }
@media only screen and (max-width: 600px) {
.post-index ul.post-list-content .post-link {
font-size: 19.5px; } }
@media only screen and (max-width: 600px) {
.post-index ul.post-list-content .post-link {
margin: 6px 0; } }
.post-index ul.post-list-content .post-link .post-descriptor {
float: left;
width: 12px;
margin-left: -22px; }
.post-index ul.post-list-content .post-link .post-descriptor :first-child {
color: #797979;
padding-right: 6px;
text-decoration: none; }
.post-index ul.post-list-content .post-link .post-descriptor :first-child:hover {
color: #bbbbbb; }
.post-index ul.post-list-content .post-link .post-descriptor .linklog {
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace;
width: 100%;
display: block;
color: #797979; }
.post-index ul.post-list-content .post-link .post-descriptor .linklog:hover {
color: #d7675d; }
.post-index ul.post-list-content .post-link .post-link-date {
color: #656565;
margin: 0;
clear: left;
white-space: nowrap;
font-size: 17.5px; }
@media only screen and (max-width: 600px) {
.post-index ul.post-list-content .post-link .post-link-date {
font-size: 15px; } }
.post-index ul.post-list-content .post-link a.post-link-url {
color: whitesmoke;
text-decoration: none;
padding-right: 6px; }
.post-index ul.post-list-content .post-link a.post-link-url:hover {
color: #d7675d; }
.post {
padding-top: 12px;
font-size: 20px; }
@media only screen and (max-width: 600px) {
.post {
font-size: 17.5px; } }
@media only screen and (max-width: 600px) {
.post {
margin: 0 auto; } }
.post .post-header {
margin-top: 96px; }
@media only screen and (max-width: 600px) {
.post .post-header {
margin-top: 36px;
float: none; } }
.post .post-header .post-author {
color: #797979;
font-weight: normal;
font-style: italic; }
.post .post-header .post-title {
color: whitesmoke;
margin: 24px 0 3px 0;
font-weight: bold;
font-family: "ibm-plex-serif", "IBM Plex Serif", "ff-tisa-web-pro", Georgia, "Times New Roman", serif;
line-height: 1.2;
font-size: 40px; }
@media only screen and (max-width: 600px) {
.post .post-header .post-title {
font-size: 30px; } }
.post .post-header .post-title .article-link {
color: whitesmoke;
text-decoration: none; }
.post .post-header .post-title .article-link:hover {
text-decoration: underline; }
.post .post-header .post-title .article-link .linklog {
color: #d7675d; }
@media only screen and (max-width: 600px) {
.post .post-header .post-title {
margin-top: 0; } }
.post .post-content {
padding-top: 48px;
clear: left;
line-height: 1.5;
font-size: 20px; }
@media only screen and (max-width: 600px) {
.post .post-content {
padding-top: 24px; } }
@media only screen and (max-width: 600px) {
.post .post-content {
font-size: 17.5px; } }
.post .post-content > p, .post .post-content > ul, .post .post-content > ol {
margin-bottom: 18px; }
@media only screen and (max-width: 600px) {
.post .post-content > p, .post .post-content > ul, .post .post-content > ol {
margin-bottom: 12px; } }
.post .post-content ol, .post .post-content ul {
padding-left: 30px;
padding-right: 30px; }
@media only screen and (max-width: 600px) {
.post .post-content ol, .post .post-content ul {
padding-left: 15px;
padding-right: 15px; } }
.post .post-content a {
text-decoration-color: #797979;
text-underline-offset: 4px;
text-decoration-thickness: 2px; }
.post .post-content a:hover {
text-decoration: none; }
.post .post-content h1 {
font-size: 40px;
margin: 1.5rem 0 1rem 0rem; }
@media only screen and (max-width: 600px) {
.post .post-content h1 {
font-size: 30px; } }
.post .post-content > h1:first-child {
margin-top: 1rem; }
.post .post-content h2 {
font-size: 30px;
margin: 1rem 0 1rem 0; }
@media only screen and (max-width: 600px) {
.post .post-content h2 {
font-size: 28px; } }
.post .post-content h3 {
font-size: 1rem;
font-style: italic;
font-weight: normal;
font-size: 20px;
margin-bottom: 0.5rem; }
@media only screen and (max-width: 600px) {
.post .post-content h3 {
font-size: 17.5px; } }
.post .post-content img {
display: block; }
.post .post-content img.full-bleed {
border-left: none;
border-right: none; }
.post .post-content img :not(.full-bleed) {
display: block;
max-width: 100%; }
.post .post-content .highlight {
margin-bottom: 24px; }
.post .post-content hr {
display: none; }
.post .post-content hr + p:first-letter {
float: left;
font-family: "ibm-plex-serif", "IBM Plex Serif", "ff-tisa-web-pro", Georgia, "Times New Roman", serif;
line-height: 30px;
padding-right: 6px; }
@media only screen and (max-width: 600px) {
.post .post-content hr + p:first-letter {
font-size: 50px;
padding-top: 12px; } }
@media only screen and (min-width: 601px) {
.post .post-content hr + p:first-letter {
font-size: 56px;
padding-top: 17px; } }
.post .post-content figure figcaption {
font-size: 17.5px;
margin-top: 6px; }
.post .post-content figure:not(.full-bleed) a:not(.center) {
margin-left: 24px; }
.post .post-content figure.full-bleed figcaption {
text-align: center; }
.post .post-content .footnotes {
font-size: 17.5px;
padding: 24px 0 0 0; }
@media only screen and (max-width: 600px) {
.post .post-content .footnotes {
margin-bottom: 0; } }
.post .post-content .footnotes ol {
margin-left: 0; }
.post .post-meta {
text-transform: none;
padding-top: 48px; }
@media only screen and (max-width: 600px) {
.post .post-meta {
padding-top: 24px; } }
.post .post-meta .post-date {
color: #bbbbbb;
font-size: 20px; }
@media only screen and (max-width: 600px) {
.post .post-meta .post-date {
font-size: 17.5px; } }
.post .post-meta .post-permalink {
/* border-top: 1px solid $color-text; */
width: 20%;
margin-bottom: 0; }
.post .post-meta .post-permalink a {
font-size: 17.5px;
color: whitesmoke;
text-decoration: none; }
@media only screen and (max-width: 600px) {
.post .post-meta .post-permalink a {
font-size: 15px; } }
.post .post-meta .post-permalink a:hover {
color: #d7675d; }
.post .post-meta .post-taxonomies {
margin: 1rem 0 1.5rem 0;
display: flex;
justify-content: flex-start; }
.post .post-meta .post-related {
border: 3px solid #797979;
padding: 1rem;
margin-top: 24px; }
.post .post-meta .post-related:hover {
color: whitesmoke;
border-color: whitesmoke; }
.post .post-meta .post-related a {
color: inherit;
text-decoration: none; }
.post .post-meta .post-related a:hover {
color: #d7675d; }
.post .post-meta .post-related ol, .post .post-meta .post-related ul {
margin-left: 24px; }
.post .post-meta .post-taxonomy {
border: 2px solid #797979;
margin-right: 1rem;
text-align: center;
font-size: 1.2rem;
font-weight: 500; }
.post .post-meta .post-taxonomy:hover {
color: #d7675d;
border-color: #d7675d; }
.post .post-meta .post-taxonomy a {
display: block;
padding: 6px 12px;
color: inherit;
text-decoration: none; }
.post .post-meta .post-taxonomy a:hover {
color: inherit; }
.draft {
background-color: #d7675d;
border-radius: 12px;
border: 2px solid #000;
color: black;
font-size: 13.35px;
font-weight: bold;
text-transform: uppercase;
vertical-align: middle;
padding: 0 6px;
margin-right: 6px;
margin-top: 6px; }
.callout:not(figure) {
border: 2px solid #797979;
padding: 24px;
margin-bottom: 24px; }
ul.callout {
margin-left: 0;
padding-left: 30px; }
img.callout {
margin-bottom: 0; }
.sidenote {
padding-left: 12px;
margin-bottom: 1.5rem;
border-left: 1px solid #797979;
color: #797979;
font-style: italic;
font-size: 0.8rem; }
.sidenote-hover {
border-color: #d7675d;
color: #d7675d; }
code {
font-family: ui-monospace, "SF Mono", SFMono-Regular, "ibm-plex-mono", "IBM Plex Mono", "Consolas", monospace; }
article.post-content pre {
font-size: 0.75rem;
padding: 1rem;
overflow: auto; }
article.post-content ol code,
article.post-content ul code,
article.post-content p > code {
font-size: 0.98rem;
color: #e3d1b9; }
#site-footer {
padding: 24px 0; }
@media only screen and (min-width: 601px) {
#site-footer {
margin: 2rem; } }
#site-footer .newsletter {
grid-column: 2;
display: grid;
grid-gap: 6px;
margin-bottom: 48px; }
@media only screen and (max-width: 600px) {
#site-footer .newsletter {
padding: 0 12px; } }
@media only screen and (min-width: 601px) {
#site-footer .newsletter {
grid-template-columns: 48% 1fr 48%; } }
#site-footer .newsletter input {
text-align: center;
padding: 0.5rem; }
#site-footer .newsletter input[type="text"] {
border: 1px solid #797979; }
@media only screen and (min-width: 601px) {
#site-footer .newsletter input[type="text"] {
grid-column-start: 1; } }
#site-footer .newsletter input[type="submit"] {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: lightgray;
color: black; }
@media only screen and (min-width: 601px) {
#site-footer .newsletter input[type="submit"] {
grid-column-start: 3; } }
.site-nav ol, .site-nav ul {
display: grid;
row-gap: 12px;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
list-style-type: none;
margin-left: 0;
text-align: center; }
@media only screen and (max-width: 600px) {
.site-nav ol, .site-nav ul {
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr)); } }
.site-nav ol .nav-link, .site-nav ul .nav-link {
display: block;
padding-top: 6px;
padding-bottom: 6px;
text-decoration: none;
font-size: 20px;
border-top: 2px solid transparent;
border-bottom: 2px solid transparent; }
@media only screen and (max-width: 600px) {
.site-nav ol .nav-link, .site-nav ul .nav-link {
font-size: 15px; } }
.site-nav ol .nav-link:hover, .site-nav ul .nav-link:hover {
border-top: 2px solid #d7675d;
border-bottom: 2px solid #d7675d; }
.powered-by {
font-size: 0.75rem;
text-align: center;
margin: 36px 24px; }
.powered-by > a {
text-decoration: none;
color: whitesmoke; }
.powered-by > a:hover {
color: #d7675d; }
/*# sourceMappingURL=style.css.map */

View File

@@ -1 +0,0 @@
{"Target":"css/style.css","MediaType":"text/css","Data":{}}

BIN
static/eggs.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 MiB

324
static/resume/index.html Normal file
View File

@@ -0,0 +1,324 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Eric Wagoner - Resume</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #333;
background: #f5f5f5;
}
.page {
width: 8.5in;
min-height: 11in;
margin: 0 auto;
background: white;
display: grid;
grid-template-columns: 2.4in 1fr;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
}
.sidebar {
background: #1a365d;
color: white;
padding: 0.5in 0.3in;
}
.sidebar h1 {
font-size: 24pt;
font-weight: 600;
margin-bottom: 0.1in;
line-height: 1.2;
}
.sidebar .tagline {
font-size: 9pt;
opacity: 0.85;
margin-bottom: 0.35in;
line-height: 1.4;
}
.sidebar h2 {
font-size: 10pt;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.12in;
padding-bottom: 0.06in;
border-bottom: 1px solid rgba(255,255,255,0.3);
}
.sidebar section {
margin-bottom: 0.3in;
}
.sidebar p, .sidebar li {
font-size: 9pt;
opacity: 0.9;
}
.sidebar ul {
list-style: none;
}
.sidebar li {
margin-bottom: 0.04in;
}
.contact-item {
margin-bottom: 0.08in;
font-size: 9pt;
}
.main {
padding: 0.4in 0.4in 0.4in 0.35in;
}
.main h2 {
font-size: 12pt;
text-transform: uppercase;
letter-spacing: 1px;
color: #1a365d;
margin-bottom: 0.12in;
padding-bottom: 0.06in;
border-bottom: 2px solid #1a365d;
}
.main section {
margin-bottom: 0.25in;
}
.summary {
font-size: 10pt;
line-height: 1.5;
color: #444;
}
.job {
margin-bottom: 0.2in;
}
.job-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 0.04in;
}
.job-title {
font-weight: 600;
font-size: 11pt;
}
.job-dates {
font-size: 9pt;
color: #666;
}
.job-company {
font-size: 10pt;
color: #555;
margin-bottom: 0.08in;
}
.job ul {
margin-left: 0.15in;
font-size: 9.5pt;
}
.job li {
margin-bottom: 0.04in;
}
.early-exp {
font-size: 9.5pt;
color: #444;
}
.early-exp p {
margin-bottom: 0.06in;
}
.recognition li, .education p {
font-size: 9.5pt;
margin-bottom: 0.04in;
}
.ai-statement {
font-size: 9.5pt;
line-height: 1.5;
}
.ai-statement p {
margin-bottom: 0.1in;
}
@media print {
body { background: white; }
.page { box-shadow: none; }
}
</style>
</head>
<body>
<div class="page">
<aside class="sidebar">
<h1>Eric Wagoner</h1>
<p class="tagline">VP of Technology<br>Full-Stack Developer<br>Agricultural Technology Innovator</p>
<section>
<h2>Contact</h2>
<div class="contact-item">Athens, GA</div>
<div class="contact-item">eric@ericwagoner.com</div>
<div class="contact-item">linkedin.com/in/wagonereric</div>
<div class="contact-item">ericwagoner.com</div>
</section>
<section>
<h2>Languages & Frameworks</h2>
<ul>
<li>JavaScript/TypeScript</li>
<li>Node.js, Python, Java</li>
<li>Ruby, React, SvelteKit</li>
</ul>
</section>
<section>
<h2>Databases</h2>
<ul>
<li>MySQL, PostgreSQL</li>
<li>Elasticsearch</li>
<li>Database architecture</li>
<li>Migration strategies</li>
<li>Query optimization</li>
</ul>
</section>
<section>
<h2>Infrastructure</h2>
<ul>
<li>AWS, Atlassian (certified)</li>
<li>Docker, Linux</li>
<li>CI/CD pipelines</li>
<li>Github, Google Cloud</li>
</ul>
</section>
<section>
<h2>Practices</h2>
<ul>
<li>Test-driven development</li>
<li>Automated testing</li>
<li>Agile methodologies</li>
<li>System architecture</li>
<li>Legacy modernization</li>
</ul>
</section>
<section>
<h2>Education</h2>
<p>B.S. Astrophysics with Honors</p>
<p style="font-size: 8pt; opacity: 0.8;">New Mexico Tech, 1994</p>
<p style="margin-top: 0.1in; font-size: 8pt;">Sigma Pi Sigma Honor Society</p>
</section>
</aside>
<main class="main">
<section>
<h2>Professional Summary</h2>
<p class="summary">Technology leader with 30+ years building mission-critical software systems for biotech and research environments. Currently VP of Technology at Infinity Interactive, where I've designed web platforms for LIMS supporting genome engineering workflows, built software for liquid handling robotics used in NGS, PCR, and nucleic acid extraction, and served as senior technical advisor to biotech engineering teams. Scientific background (BS in Astrophysics, early career processing astronomical data at NRAO) provides foundation for working in research-driven, data-intensive environments. Equally skilled at designing greenfield applications and modernizing legacy systems; most recently completed a solo migration of a 19-year production platform to modern architecture with zero data loss.</p>
</section>
<section>
<h2>Professional Experience</h2>
<div class="job">
<div class="job-header">
<span class="job-title">VP of Technology</span>
<span class="job-dates">Jun 2023 Present</span>
</div>
<div class="job-header">
<span class="job-title">Manager of Software Delivery</span>
<span class="job-dates">Aug 2021 Jun 2023</span>
</div>
<div class="job-header">
<span class="job-title">Team Lead</span>
<span class="job-dates">May 2017 Aug 2021</span>
</div>
<div class="job-header">
<span class="job-title">Sr. Developer</span>
<span class="job-dates">Jan 2016 May 2017</span>
</div>
<div class="job-company">Infinity Interactive, Remote</div>
<ul>
<li>Lead technical strategy and delivery for a distributed team serving clients in biotech, healthcare, finance, and legal sectors</li>
<li>Designed and built web platforms for laboratory information management systems (LIMS) supporting genome engineering workflows: sample tracking interfaces, protocol management, and research data pipelines</li>
<li>Built software interfaces for liquid handling robotics platforms used in NGS library prep, PCR setup, and nucleic acid extraction: touchscreen UIs, protocol execution engines, and instrument integration layers</li>
<li>Developed data-intensive applications requiring high accuracy: tax filing systems, legal research portals, document management platforms</li>
<li>Selected and deployed optimal technology stacks for each engagement, balancing performance, team capabilities, and maintainability</li>
</ul>
</div>
<div class="job">
<div class="job-header">
<span class="job-title">Founder & Solo Developer</span>
<span class="job-dates">Jan 2005 Present</span>
</div>
<div class="job-company">LocallyGrown.net, Athens, GA</div>
<ul>
<li>Built and maintain a production platform serving hundreds of markets; processed over $16 million in transactions requiring financial accuracy and data integrity</li>
<li>Designed multi-tenant architecture with shared database supporting cross-market inventory synchronization</li>
<li>Completed a 6-month solo migration from legacy Rails to modern SvelteKit: 23 data models, comprehensive test coverage, zero data loss</li>
<li>Manage full lifecycle as sole developer: architecture, development, database administration, operations, support</li>
</ul>
</div>
<div class="job">
<div class="job-header">
<span class="job-title">Head of Testing / "Utility Infielder" Senior Dev</span>
<span class="job-dates">Jul 1997 Oct 2015</span>
</div>
<div class="job-company">Partner Software, Athens, GA</div>
<ul>
<li>Joined months after founding; grew with the company through every phase of the startup lifecycle</li>
<li>Developed web-based system configuration tools and workflow management systems for enterprise networks</li>
<li>Built an extensible data-to-PDF reporting system using XSL/FOP and later iText</li>
</ul>
</div>
</section>
<section>
<h2>Earlier Experience</h2>
<div class="early-exp">
<p><strong>Socorro Electric Cooperative</strong>, Engineering Technician (19941997): Managed GIS systems, oversaw enterprise software conversions.</p>
<p><strong>National Radio Astronomy Observatory</strong>, Student Employee (19921994): Developed scientific data processing tools in UNIX/Solaris; converted raw radio telescope data from India into a searchable graphical sky map with spatial indexing.</p>
</div>
</section>
<section>
<h2>Recognition</h2>
<ul class="recognition">
<li><strong>Barbara Petit Pollinator Award</strong> (Georgia Organics, 2015) Recognized for creating "a national model for connecting growers to consumers."</li>
<li><strong>Alec Little Environmental Award</strong> (UGA Odum School of Ecology, 2012) Honored for environmental responsibility and sustainable agriculture work.</li>
</ul>
</section>
<section>
<h2>Statement on the Use of AI</h2>
<div class="ai-statement">
<p>I use AI-assisted development tools extensively, and I've been deliberate about how.</p>
<p>The hype around AI makes it trivial to abdicate responsibility. Developers generate prodigious amounts of unmaintainable code that technically works for the happy path, call it productivity, and move on. I find that approach corrosive to the craft.</p>
<p>What I embrace is assistive AI: tools that amplify the skills I've spent forty years developing. They remove mechanical friction (transcription, syntax lookup, boilerplate) so I can focus on architecture, problem-solving, and building software that serves real humans. The thinking is mine. The decisions are mine. The responsibility is mine.</p>
<p>I name my AI coding agents after people who inspire me: George Washington Carver, Ray Eames, Ada Lovelace. The tools don't have personalities, but the names remind me who the work is for. They're lenses that focus intent, not collaborators with agency.</p>
<p>I've written extensively about this philosophy and regularly teach other developers how a more thoughtful approach can work, whether they've given in to abdication or been revulsed by it.</p>
</div>
</section>
</main>
</div>
</body>
</html>