Skip to content

Instantly share code, notes, and snippets.

@jalehman
Created January 31, 2026 23:10
Show Gist options
  • Select an option

  • Save jalehman/8deb1ed2c4ddfdc4707d147be1247a6b to your computer and use it in GitHub Desktop.

Select an option

Save jalehman/8deb1ed2c4ddfdc4707d147be1247a6b to your computer and use it in GitHub Desktop.
Martian Todos Workshop Demo - Feb 2026
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Martian Todos β€” Workshop Demo Repo</title>
<style>
:root {
--bg: #0d1117;
--bg-secondary: #161b22;
--border: #30363d;
--text: #c9d1d9;
--text-muted: #8b949e;
--accent: #58a6ff;
--green: #3fb950;
--red: #f85149;
--yellow: #d29922;
--purple: #a371f7;
--orange: #db6d28;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.6;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
h1, h2, h3 { color: #fff; margin-bottom: 1rem; }
h1 { font-size: 2rem; border-bottom: 1px solid var(--border); padding-bottom: 1rem; }
h2 { font-size: 1.5rem; margin-top: 2.5rem; color: var(--accent); }
h3 { font-size: 1.2rem; margin-top: 1.5rem; color: var(--purple); }
p { margin-bottom: 1rem; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 600;
margin-right: 0.5rem;
}
.badge-green { background: rgba(63, 185, 80, 0.2); color: var(--green); }
.badge-yellow { background: rgba(210, 153, 34, 0.2); color: var(--yellow); }
.badge-red { background: rgba(248, 81, 73, 0.2); color: var(--red); }
.badge-purple { background: rgba(163, 113, 247, 0.2); color: var(--purple); }
.card {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
}
.tree {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
font-size: 0.85rem;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
overflow-x: auto;
white-space: pre;
}
.tree .folder { color: var(--accent); }
.tree .file { color: var(--text); }
.tree .config { color: var(--yellow); }
.tree .ts { color: var(--green); }
.tree .md { color: var(--purple); }
pre {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
overflow-x: auto;
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
font-size: 0.8rem;
line-height: 1.5;
margin: 1rem 0;
}
code {
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
background: var(--bg-secondary);
padding: 0.15rem 0.4rem;
border-radius: 4px;
font-size: 0.85rem;
}
.highlight { background: rgba(248, 81, 73, 0.15); border-left: 3px solid var(--red); padding-left: 0.5rem; margin-left: -0.5rem; }
.comment { color: var(--text-muted); }
.keyword { color: var(--purple); }
.string { color: var(--green); }
.function { color: var(--accent); }
.type { color: var(--yellow); }
.api-table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
}
.api-table th, .api-table td {
text-align: left;
padding: 0.75rem;
border-bottom: 1px solid var(--border);
}
.api-table th { color: var(--text-muted); font-weight: 500; }
.method { font-weight: 600; font-family: monospace; }
.method-get { color: var(--green); }
.method-post { color: var(--accent); }
.method-patch { color: var(--yellow); }
.method-delete { color: var(--red); }
.imperfection {
background: rgba(248, 81, 73, 0.1);
border: 1px solid rgba(248, 81, 73, 0.3);
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
.imperfection h4 {
color: var(--red);
margin-bottom: 0.5rem;
font-size: 1rem;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin: 1rem 0;
}
.stat {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 8px;
padding: 1rem;
text-align: center;
}
.stat-value { font-size: 2rem; font-weight: 700; color: var(--accent); }
.stat-label { font-size: 0.8rem; color: var(--text-muted); }
footer {
margin-top: 3rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
color: var(--text-muted);
font-size: 0.85rem;
}
</style>
</head>
<body>
<h1>πŸš€ Martian Todos</h1>
<p>Demo todo app for <strong>AI engineering workshops</strong>. A production-ready pnpm TypeScript monorepo following Connected Play patterns.</p>
<p><span class="badge badge-green">Workshop Ready</span> <span class="badge badge-purple">Feb 10 2026</span> <span class="badge badge-yellow">Renew Home</span></p>
<div class="stats">
<div class="stat"><div class="stat-value">47</div><div class="stat-label">Files</div></div>
<div class="stat"><div class="stat-value">5</div><div class="stat-label">Workers Built</div></div>
<div class="stat"><div class="stat-value">3</div><div class="stat-label">Deliberate Bugs</div></div>
<div class="stat"><div class="stat-value">12</div><div class="stat-label">API Endpoints</div></div>
</div>
<h2>πŸ“ Directory Structure</h2>
<div class="tree"><span class="folder">martian-todos/</span>
β”œβ”€β”€ <span class="config">.env.example</span>
β”œβ”€β”€ <span class="config">.gitignore</span>
β”œβ”€β”€ <span class="md">CLAUDE.md</span>
β”œβ”€β”€ <span class="config">Makefile</span>
β”œβ”€β”€ <span class="md">README.md</span>
β”œβ”€β”€ <span class="md">WORKSHOP_FACILITATOR_NOTES.md</span> <span class="comment">← Deliberate imperfections documented here</span>
β”œβ”€β”€ <span class="folder">apps/</span>
β”‚ β”œβ”€β”€ <span class="folder">backend/</span>
β”‚ β”‚ β”œβ”€β”€ Dockerfile
β”‚ β”‚ β”œβ”€β”€ package.json
β”‚ β”‚ β”œβ”€β”€ <span class="folder">src/</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">config.ts</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="folder">db/</span>
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">database.ts</span>
β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">migrate.ts</span>
β”‚ β”‚ β”‚ β”‚ └── <span class="ts">schema.ts</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">index.ts</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="folder">middleware/</span>
β”‚ β”‚ β”‚ β”‚ └── <span class="ts">auth.ts</span>
β”‚ β”‚ β”‚ └── <span class="folder">routes/</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">auth.ts</span> <span class="comment">← JWT + bcrypt + refresh tokens</span>
β”‚ β”‚ β”‚ └── <span class="ts">todos.ts</span> <span class="comment">← CRUD + search + bulk ops (has bug!)</span>
β”‚ β”‚ └── tsconfig.json
β”‚ └── <span class="folder">frontend/</span>
β”‚ β”œβ”€β”€ Dockerfile
β”‚ β”œβ”€β”€ index.html
β”‚ β”œβ”€β”€ package.json
β”‚ β”œβ”€β”€ <span class="folder">src/</span>
β”‚ β”‚ β”œβ”€β”€ <span class="ts">App.tsx</span>
β”‚ β”‚ β”œβ”€β”€ <span class="folder">api/</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">auth.ts</span>
β”‚ β”‚ β”‚ └── <span class="ts">todos.ts</span>
β”‚ β”‚ β”œβ”€β”€ <span class="folder">components/</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">AddTodoForm.tsx</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">ErrorBoundary.tsx</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">FilterBar.tsx</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">KeyboardShortcuts.tsx</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">LoginForm.tsx</span>
β”‚ β”‚ β”‚ β”œβ”€β”€ <span class="ts">TodoItem.tsx</span>
β”‚ β”‚ β”‚ └── <span class="ts">TodoList.tsx</span> <span class="comment">← Has prop mutation bug!</span>
β”‚ β”‚ β”œβ”€β”€ <span class="folder">hooks/</span>
β”‚ β”‚ β”‚ └── <span class="ts">useAuth.ts</span>
β”‚ β”‚ β”œβ”€β”€ index.css
β”‚ β”‚ └── <span class="ts">main.tsx</span>
β”‚ β”œβ”€β”€ tsconfig.json
β”‚ β”œβ”€β”€ tsconfig.node.json
β”‚ └── vite.config.ts
β”œβ”€β”€ <span class="config">docker-compose.yml</span> <span class="comment">← Hot reload local dev</span>
β”œβ”€β”€ <span class="config">mise.toml</span>
β”œβ”€β”€ package.json
β”œβ”€β”€ <span class="folder">packages/</span>
β”‚ └── <span class="folder">shared/</span>
β”‚ β”œβ”€β”€ package.json
β”‚ β”œβ”€β”€ <span class="folder">src/</span>
β”‚ β”‚ β”œβ”€β”€ <span class="ts">index.ts</span>
β”‚ β”‚ └── <span class="ts">types.ts</span> <span class="comment">← Shared types</span>
β”‚ └── tsconfig.json
β”œβ”€β”€ <span class="folder">platform/</span>
β”‚ └── <span class="folder">terraform/</span>
β”‚ β”œβ”€β”€ <span class="md">README.md</span>
β”‚ β”œβ”€β”€ <span class="folder">environments/</span>
β”‚ β”‚ └── <span class="folder">dev/</span>
β”‚ β”‚ β”œβ”€β”€ main.tf
β”‚ β”‚ └── terraform.tfvars.example
β”‚ └── <span class="folder">modules/</span>
β”‚ β”œβ”€β”€ <span class="folder">ecs/</span> main.tf
β”‚ β”œβ”€β”€ <span class="folder">rds/</span> main.tf
β”‚ └── <span class="folder">vpc/</span> main.tf
β”œβ”€β”€ <span class="config">pnpm-workspace.yaml</span>
└── tsconfig.json</div>
<h2>πŸ”Œ API Endpoints</h2>
<h3>Auth</h3>
<table class="api-table">
<tr><th>Method</th><th>Endpoint</th><th>Description</th></tr>
<tr><td class="method method-post">POST</td><td><code>/auth/register</code></td><td>Create account</td></tr>
<tr><td class="method method-post">POST</td><td><code>/auth/login</code></td><td>Get JWT token</td></tr>
<tr><td class="method method-post">POST</td><td><code>/auth/refresh</code></td><td>Refresh access token</td></tr>
<tr><td class="method method-post">POST</td><td><code>/auth/logout</code></td><td>Revoke refresh token</td></tr>
</table>
<h3>Todos (authenticated)</h3>
<table class="api-table">
<tr><th>Method</th><th>Endpoint</th><th>Description</th></tr>
<tr><td class="method method-get">GET</td><td><code>/todos</code></td><td>List todos (pagination, filtering, search, sorting)</td></tr>
<tr><td class="method method-get">GET</td><td><code>/todos/:id</code></td><td>Get single todo</td></tr>
<tr><td class="method method-post">POST</td><td><code>/todos</code></td><td>Create todo</td></tr>
<tr><td class="method method-patch">PATCH</td><td><code>/todos/complete-all</code></td><td>Mark all as completed</td></tr>
<tr><td class="method method-patch">PATCH</td><td><code>/todos/:id</code></td><td>Update todo</td></tr>
<tr><td class="method method-delete">DELETE</td><td><code>/todos/completed</code></td><td>Delete completed todos</td></tr>
<tr><td class="method method-delete">DELETE</td><td><code>/todos/:id</code></td><td>Delete todo</td></tr>
</table>
<h2>πŸ› Deliberate Imperfections</h2>
<p>These bugs are <strong>intentionally inserted</strong> for workshop demonstrations. They're designed to be:</p>
<ul style="margin-left: 1.5rem; margin-bottom: 1rem;">
<li>Safe and non-destructive</li>
<li>Easy to find with AI code review</li>
<li>Educational examples of common mistakes</li>
</ul>
<div class="imperfection">
<h4>πŸ”΄ Bug #1: Prop Mutation in TodoList.tsx</h4>
<p><strong>File:</strong> <code>apps/frontend/src/components/TodoList.tsx</code></p>
<p><strong>Issue:</strong> Sorting mutates the props array in place, causing unstable ordering across renders.</p>
<pre><span class="keyword">const</span> priorityWeights: <span class="type">Record</span>&lt;<span class="type">string</span>, <span class="type">number</span>&gt; = {
high: <span class="string">0</span>,
medium: <span class="string">1</span>,
low: <span class="string">2</span>,
};
<span class="comment">// TODO: This mutates props and creates unstable ordering; copy before sorting.</span>
<div class="highlight"><span class="keyword">const</span> sortedTodos = todos.<span class="function">sort</span>(
(left, right) =&gt;
(priorityWeights[left.priority] ?? <span class="string">99</span>) -
(priorityWeights[right.priority] ?? <span class="string">99</span>)
);</div></pre>
<p><strong>Fix:</strong> Use <code>[...todos].sort(...)</code> or <code>useMemo</code> to avoid mutating props.</p>
</div>
<div class="imperfection">
<h4>πŸ”΄ Bug #2: Pagination Off-by-One in todos.ts</h4>
<p><strong>File:</strong> <code>apps/backend/src/routes/todos.ts</code></p>
<p><strong>Issue:</strong> Using <code>Math.floor</code> instead of <code>Math.ceil</code> undercounts the last page.</p>
<pre><span class="keyword">const</span> response: <span class="type">PaginatedResponse</span>&lt;<span class="type">Todo</span>&gt; = {
items: todos.<span class="function">map</span>(mapTodo),
total,
page,
pageSize,
<span class="comment">// TODO: Revisit pagination math to avoid off-by-one behavior.</span>
<div class="highlight"> totalPages: <span class="type">Math</span>.<span class="function">floor</span>(total / pageSize),</div>};</pre>
<p><strong>Fix:</strong> Use <code>Math.ceil(total / pageSize)</code> to include partial last pages.</p>
</div>
<div class="imperfection">
<h4>🟑 Code Smell: Recreating on Every Render</h4>
<p><strong>File:</strong> <code>apps/frontend/src/components/TodoList.tsx</code></p>
<p><strong>Issue:</strong> The <code>priorityWeights</code> object and sorting logic are recreated on every render.</p>
<p><strong>Fix:</strong> Extract to a module-level constant or use <code>useMemo</code>.</p>
</div>
<h2>πŸ’» Key Source Files</h2>
<h3>Auth Routes (JWT + Refresh Tokens)</h3>
<p><code>apps/backend/src/routes/auth.ts</code> β€” Full auth flow with bcrypt password hashing and secure refresh tokens.</p>
<pre><span class="comment">/**
* Generates a cryptographically secure refresh token.
*/</span>
<span class="keyword">function</span> <span class="function">generateRefreshToken</span>(): <span class="type">string</span> {
<span class="keyword">return</span> <span class="function">randomBytes</span>(REFRESH_TOKEN_BYTES).<span class="function">toString</span>(<span class="string">"hex"</span>);
}
<span class="comment">/**
* Hashes a refresh token for storage.
*/</span>
<span class="keyword">function</span> <span class="function">hashRefreshToken</span>(token: <span class="type">string</span>): <span class="type">string</span> {
<span class="keyword">return</span> <span class="function">createHash</span>(<span class="string">"sha256"</span>).<span class="function">update</span>(token).<span class="function">digest</span>(<span class="string">"hex"</span>);
}</pre>
<h3>Todos API (Search + Bulk Ops)</h3>
<p><code>apps/backend/src/routes/todos.ts</code> β€” Full CRUD with search, filtering, sorting, and bulk operations.</p>
<pre><span class="comment">// Supported query params:</span>
<span class="keyword">const</span> ListTodosSchema = z.<span class="function">object</span>({
page: z.<span class="function">coerce</span>.<span class="function">number</span>().<span class="function">int</span>().<span class="function">min</span>(<span class="string">1</span>).<span class="function">default</span>(<span class="string">1</span>),
pageSize: z.<span class="function">coerce</span>.<span class="function">number</span>().<span class="function">int</span>().<span class="function">min</span>(<span class="string">1</span>).<span class="function">max</span>(<span class="string">100</span>).<span class="function">default</span>(<span class="string">20</span>),
status: z.<span class="function">enum</span>([<span class="string">"pending"</span>, <span class="string">"in_progress"</span>, <span class="string">"completed"</span>]).<span class="function">optional</span>(),
priority: z.<span class="function">enum</span>([<span class="string">"low"</span>, <span class="string">"medium"</span>, <span class="string">"high"</span>]).<span class="function">optional</span>(),
search: z.<span class="function">string</span>().<span class="function">trim</span>().<span class="function">min</span>(<span class="string">1</span>).<span class="function">max</span>(<span class="string">200</span>).<span class="function">optional</span>(),
sortBy: z.<span class="function">enum</span>([<span class="string">"createdAt"</span>, <span class="string">"updatedAt"</span>, <span class="string">"dueDate"</span>, <span class="string">"priority"</span>, <span class="string">"status"</span>, <span class="string">"title"</span>])
.<span class="function">default</span>(<span class="string">"createdAt"</span>),
sortOrder: z.<span class="function">enum</span>([<span class="string">"asc"</span>, <span class="string">"desc"</span>]).<span class="function">default</span>(<span class="string">"desc"</span>),
});</pre>
<h2>🐳 Docker Compose</h2>
<p><code>docker-compose.yml</code> β€” Local dev with hot reload for both frontend and backend.</p>
<pre><span class="keyword">services:</span>
<span class="function">postgres:</span>
image: postgres:16-alpine
environment:
POSTGRES_USER: martian
POSTGRES_PASSWORD: martian
POSTGRES_DB: martian_todos
ports:
- <span class="string">"5432:5432"</span>
volumes:
- postgres_data:/var/lib/postgresql/data
<span class="function">backend:</span>
build: ./apps/backend
ports:
- <span class="string">"3001:3001"</span>
environment:
DATABASE_URL: postgres://martian:martian@postgres:5432/martian_todos
volumes:
- ./apps/backend/src:/app/src <span class="comment"># Hot reload</span>
depends_on:
- postgres
<span class="function">frontend:</span>
build: ./apps/frontend
ports:
- <span class="string">"5173:5173"</span>
volumes:
- ./apps/frontend/src:/app/src <span class="comment"># Hot reload</span></pre>
<h2>πŸ› οΈ Tech Stack</h2>
<div class="card">
<ul style="list-style: none;">
<li><span class="badge badge-green">Runtime</span> Node.js 22, pnpm workspaces</li>
<li><span class="badge badge-purple">Backend</span> Fastify, Kysely (PostgreSQL), JWT auth, bcrypt</li>
<li><span class="badge badge-yellow">Frontend</span> React 18, Vite, TypeScript</li>
<li><span class="badge badge-red">Infra</span> Terraform, AWS ECS Fargate, RDS PostgreSQL</li>
</ul>
</div>
<footer>
<p>Generated for <strong>Renew Home AI Workshop</strong> β€” Feb 10, 2026</p>
<p>Built by 5 parallel Codex workers via claude-team πŸ€–</p>
</footer>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment