Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Base URL for the clapping API (no trailing slash)
# Used by the ClapButton component to GET counts and POST claps via sendBeacon
PUBLIC_API_URL="http://localhost:3000"
255 changes: 255 additions & 0 deletions src/components/ClapButton.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
---
export interface Props {
slug: string;
}

const { slug } = Astro.props;
---

<div class="clap-wrapper" data-slug={slug}>
<div class="clap-ring">
<button
class="clap-button"
aria-label="Clap for this content"
aria-pressed="false"
>
<span class="clap-icon" aria-hidden="true">👏</span>
<span class="clap-plus" aria-hidden="true">+1</span>
</button>
</div>

<span class="clap-count" aria-live="polite">…</span>
</div>

<script>
import { z } from "astro/zod";

const ClapsResponse = z.object({
count: z.number().int().nonnegative(),
max_claps: z.number().int().positive(),
user_claps: z.number().int().nonnegative(),
});

const MAX_CLAPS = 16 as const;
const API = import.meta.env.PUBLIC_API_URL;

document
.querySelectorAll<HTMLDivElement>(".clap-wrapper")
.forEach((wrapperElement) => {
const slug = wrapperElement.dataset.slug!;
const buttonElement =
wrapperElement.querySelector<HTMLButtonElement>(".clap-button")!;
const countElement =
wrapperElement.querySelector<HTMLElement>(".clap-count")!;

const local = { claps: 0, wasSent: false };
let remote: z.infer<typeof ClapsResponse> = {
count: 0,
max_claps: MAX_CLAPS,
user_claps: 0,
};

if (!API) {
return;
}

const clapsUrl = new URL("/claps", API);
clapsUrl.searchParams.set("slug", slug);

void fetch(clapsUrl)
.then((response) => response.json())
.then((data: unknown) => {
const parsed = ClapsResponse.safeParse(data);

if (!parsed.success) {
return;
}

remote = parsed.data;

render();
})
.catch(() => {});

function render() {
const localClaps = local.claps + remote.user_claps;

const total = String(remote.count);
const progress = String(localClaps / remote.max_claps);

const wasPressed = localClaps > 0;
const isDisabled = localClaps >= remote.max_claps;

countElement.textContent = total;
wrapperElement.style.setProperty("--clap-progress", progress);
buttonElement.setAttribute("aria-pressed", String(wasPressed));
buttonElement.toggleAttribute("disabled", isDisabled);
}

function animateClap() {
buttonElement.classList.remove("clap-pop");
// Reading offsetWidth forces a reflow, resetting the animation so it replays on every click.
void buttonElement.offsetWidth;
buttonElement.classList.add("clap-pop");
}

buttonElement.addEventListener("click", () => {
if (remote.user_claps + local.claps >= remote.max_claps) {
return;
}

local.claps++;
// Optimistic UI Update — keeps the displayed count in sync without a round-trip.
remote.count++;

animateClap();
render();
});

function sendClaps() {
const hasClapsToSend = local.claps > 0;
const canSend = hasClapsToSend && !local.wasSent;

if (!canSend) {
return;
}

local.wasSent = true;
window.removeEventListener("pagehide", sendClaps);

const body = JSON.stringify({ claps: local.claps });
navigator.sendBeacon(clapsUrl, body);
}

window.addEventListener("pagehide", sendClaps);
});
</script>

<style>
@layer starlight.core {
.clap-wrapper {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
}

.clap-ring {
width: 3.5rem;
height: 3.5rem;
border-radius: 50%;
padding: 3px;
background: conic-gradient(
var(--sl-color-text-accent) calc(var(--clap-progress, 0) * 360deg),
var(--sl-color-hairline) 0deg
);
transition: background 0.2s;
}

.clap-button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border-radius: 50%;
border: none;
background: var(--sl-color-bg);
cursor: pointer;
overflow: visible;
transition: background 0.15s;

&:hover:not(:disabled) {
background: color-mix(
in srgb,
var(--sl-color-bg),
var(--sl-color-text-accent) 8%
);
}

&:disabled {
cursor: not-allowed;
}

&:not(:disabled) .clap-icon {
filter: grayscale(0);
}

&.clap-pop .clap-icon {
animation: clap-pop 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97) forwards;
}

&.clap-pop .clap-plus {
animation: clap-plus-float 0.55s ease-out forwards;
}
}

.clap-icon {
font-size: 1.4rem;
line-height: 1;
display: block;
filter: grayscale(0.4);
transition: filter 0.2s;
}

.clap-plus {
position: absolute;
top: -0.25rem;
left: 50%;
transform: translateX(-50%);
font-size: 0.7rem;
font-weight: 700;
color: var(--sl-color-text-accent);
pointer-events: none;
opacity: 0;
white-space: nowrap;
}

.clap-count {
font-size: var(--sl-text-sm);
color: var(--sl-color-gray-3);
min-width: 2ch;
text-align: center;
font-variant-numeric: tabular-nums;
}

@keyframes clap-pop {
0% {
transform: scale(1) rotate(0deg);
}

25% {
transform: scale(1.35) rotate(-14deg);
}

55% {
transform: scale(0.9) rotate(5deg);
}

75% {
transform: scale(1.08) rotate(-3deg);
}

100% {
transform: scale(1) rotate(0deg);
}
}

@keyframes clap-plus-float {
0% {
opacity: 1;
transform: translateX(-50%) translateY(0);
}

20% {
opacity: 1;
}

100% {
opacity: 0;
transform: translateX(-50%) translateY(-1.75rem);
}
}
}
</style>
12 changes: 12 additions & 0 deletions src/content/docs/becoming-productive/closing-the-loop.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Closing the loop
description: How to make agents self-correct faster with autonomous feedback loops across tests, logs, CLIs, and manual QA automation.
---

import ClapButton from "../../../components/ClapButton.astro";

import ExternalLink from "../../../components/ExternalLink.astro";

Efficient agent workflows depend on a closed feedback loop. Agents should be able to gather signals from tests, logs, and runtime checks continuously, without waiting for manual input on every step.
Expand All @@ -14,12 +16,16 @@ The tips below focus on building that loop so agents can diagnose and fix issues
1. Make sure your agent writes tests for any regression it finds before attempting to fix actual code. If it doesn’t do this by itself, consider telling it so in your AGENTS.md.
2. Pay attention to how models assert the expected state. Many models tend to write leaky assertions, which only catch the exact issue they just reasoned about.

<ClapButton slug="becoming-productive/closing-the-loop/tests-tests-tests" />

## Backend logs and database access

1. Make your app tee logs to a `*.log` file. This will allow agents to observe runtime behavior. Models are also good at adding their own temporary logs while debugging.
2. Make it easy for an agent to connect to your database via `psql` or `sqlite3`. You can even use this interface in place of database GUIs.
3. <ExternalLink href="https://tidewave.ai/" />.

<ClapButton slug="becoming-productive/closing-the-loop/backend-logs-and-database-access" />

## Leverage CLIs

1. Models are trained _a lot_ on Bash. They breathe it and are very productive when they can process data through shell one-liners or quick Python scripts.
Expand All @@ -34,6 +40,8 @@ Also, take a look at these skills:
if you make/use interactive CLIs. Agents are pretty good at using GDB/LLDB via
tmux.

<ClapButton slug="becoming-productive/closing-the-loop/leverage-clis" />

## Automating web frontend QA

Current frontier models are surprisingly capable of browsing websites, clicking around, and observing what happens, provided they are given the right tools.
Expand All @@ -50,6 +58,8 @@ Also, take a look at these skills:

- <ExternalLink href="https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices" />

<ClapButton slug="becoming-productive/closing-the-loop/automating-web-frontend-qa" />

## Automating mobile app QA

You can also tell the agent to play with a phone simulator.
Expand All @@ -64,3 +74,5 @@ Also, have a look at these skills:

- <ExternalLink href="https://skills.sh/expo/skills" />
- <ExternalLink href="https://skills.sh/callstackincubator/agent-skills/react-native-best-practices" />

<ClapButton slug="becoming-productive/closing-the-loop/automating-mobile-app-qa" />
8 changes: 8 additions & 0 deletions src/content/docs/becoming-productive/going-10x.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Going 10x
description: Tactics for running multiple coding agents in parallel while managing conflicts, quality, and long-term maintainability.
---

import ClapButton from "../../../components/ClapButton.astro";

import ExternalLink from "../../../components/ExternalLink.astro";

{/* NOTE: "sometimes they are, for sure" is a pun, keep it verbatim */}
Expand All @@ -22,6 +24,8 @@ Agentic engineering is like multicore CPUs. Agents aren’t always faster than h
You can put this as a rule in your `AGENTS.md` file.
2. Avoid invoking tasks that would write to the same parts of the repository.

<ClapButton slug="becoming-productive/going-10x/conflict-management" />

## Help! My agents are writing spaghetti!!!

This is where vibe coding starts to diverge from agentic engineering. You, as a human, do serious work, and you must be held responsible for it. Models can make a mess because they are rewarded for task completion during training, not long-term architecture. This is (still) a human’s job.
Expand All @@ -42,6 +46,8 @@ Read more:
- <ExternalLink href="https://openai.com/index/harness-engineering/" />
- <ExternalLink href="https://bits.logic.inc/p/ai-is-forcing-us-to-write-good-code" />

<ClapButton slug="becoming-productive/going-10x/help-my-agents-are-writing-spaghetti" />

## Automated code reviews

Automated review can run in parallel with implementation and catch obvious issues before a human reviewer spends time on them.
Expand Down Expand Up @@ -72,3 +78,5 @@ PR review bots:
Use these tools to reduce toil, not to skip ownership: don’t ask teammates to review code you haven’t reviewed yourself.

- <ExternalLink href="https://simonwillison.net/guides/agentic-engineering-patterns/anti-patterns/" />

<ClapButton slug="becoming-productive/going-10x/automated-code-reviews" />
Loading