3875 stories
·
3 followers

Beyond the Prompt: Claude Code

1 Share

Claude Code is one of those tools where the difference between a casual user and someone who has internalized it is enormous. The casual user types prompts, accepts suggestions, and treats it like a fancier autocomplete. The daily driver uses it like a programmable agent with memory, custom commands, parallel sessions, and a project setup that compounds over time. This guide is for the second kind of person, assuming you already know what claude does when you type it in a terminal.


1. Claude Code Beyond the Basics

Once you stop thinking of Claude Code as a prompt-and-wait chatbot and start treating it as an autonomous agent that needs guardrails, your workflow shifts. The single most important principle from Boris Cherny and the Anthropic team: give Claude a way to verify its own work. Without that, you are the only feedback loop. With it, Claude iterates until things actually work, and Boris says this alone gives a 2-3x quality improvement.

A few patterns that change how you operate day to day:

Explore, then plan, then code. Plan mode (Shift+Tab twice) puts Claude into read-only exploration. Read files, trace flows, understand the data model. Then get a plan. Then execute. Skip planning for small fixes; use it for anything touching more than one file.

Use plan mode like a design document. Have one Claude write the plan, then spin up a second Claude in a fresh session to review it as a staff engineer, with no context bias, so it actually catches gaps. If implementation goes sideways, go back to plan mode and re-plan with verification steps included.

Reference, do not describe. Instead of “look at the auth module”, type @src/auth/login.py. Instead of pasting an error, pipe it: cat error.log | claude. Exact context beats approximate description every time.

Delegate, do not pair-program. Cat Wu (Claude Code team): “The model performs best if you treat it like an engineer you’re delegating to, not a pair programmer you’re guiding line by line.” Write a crisp brief upfront, then let it run.

: Press Ctrl+G to open Claude’s plan in your editor and tweak it before Claude proceeds. The plan is just text, so shape it before it becomes code.

: When Claude makes a mistake, end your prompt with “Update CLAUDE.md so you do not repeat this.” Boris calls Claude “eerily good at writing rules for itself” from its own failures. This habit compounds more than any other in this guide.


2. The .claude Directory, Properly Understood

Most people open .claude/ once, see CLAUDE.md, and never look further. It is actually a layered configuration system.

Two scopes: Project scope lives in .claude/ inside your repo, committed to git so your team shares it. Global scope lives in ~/.claude/ and applies across every project on your machine.

Mental model: project files describe the project, global files describe you.

FileScopeCommitWhat it does
CLAUDE.mdProject and globalYesInstructions loaded every session
CLAUDE.local.mdProject onlyNo, gitignore itYour private project notes
settings.jsonProject and globalYesPermissions, hooks, env vars, model defaults
settings.local.jsonProject onlyNoPersonal overrides, auto-gitignored
.mcp.jsonProject onlyYesTeam-shared MCP servers
skills/<name>/SKILL.mdProject and globalYesReusable prompts invoked with /name
commands/*.mdProject and globalYesSingle-file slash commands
agents/*.mdProject and globalYesSubagent definitions
rules/*.mdProject and globalYesTopic-scoped instructions, optionally path-gated

A typical layout:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
my-repo/
├── .claude/
│   ├── settings.json
│   ├── agents/
│   │   ├── pr-review.md
│   │   └── test-writer.md
│   ├── skills/
│   │   └── api-conventions/SKILL.md
│   └── rules/
│       ├── frontend.md        # path-gated to src/frontend/
│       └── migrations.md      # path-gated to db/migrations/
├── CLAUDE.md                  # checked in, team-shared
├── CLAUDE.local.md            # gitignored, personal
└── .mcp.json                  # team-shared MCP servers

A few things easy to miss:

CLAUDE.md files cascade. In a monorepo, both root/CLAUDE.md and root/services/billing/CLAUDE.md load when you work in the billing service. Powerful for codebases with different conventions per folder.

rules/*.md is path-gated. Guidance specific to your migrations folder does not belong in CLAUDE.md bloating every session; it belongs in .claude/rules/migrations.md with a glob.

Skills over commands. .claude/commands/*.md and .claude/skills/<name>/SKILL.md both create slash commands, but skills support supporting files, disable-model-invocation, allowed tools, and agent overrides. New work should go in skills/.

: Run claude project purge ~/path/to/repo --dry-run to see exactly what local state Claude holds for a project, handy before handing off a laptop.


3. CLAUDE.md, The Way Boris Writes It

CLAUDE.md is loaded at the start of every session. Get it wrong and Claude repeats the same mistakes. Get it right and the same prompt produces dramatically better output.

Boris is direct about two things that matter more than the rest:

Keep it short. Long files bury important rules. For every line, ask: “Would removing this cause Claude to make a mistake?” If not, cut it.

Let Claude write rules for itself. Any time Claude does something wrong, tell it: “Update CLAUDE.md so you do not repeat this.” Claude is surprisingly good at distilling its own mistakes into precise rules. Do this for a few weeks and the file becomes a curated list of every gotcha your project has.

3.1 The Real CLAUDE.md From the Claude Code Team

Boris has shared the actual CLAUDE.md the Claude Code team checks into their own repo. The whole team contributes mulle times a week:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Development Workflow

**Always use `bun`, not `npm`.**

# 1. Make changes

# 2. Typecheck (fast)

bun run typecheck

# 3. Run tests

bun run test -- -t "test name" # Single suite
bun run test:file -- "glob" # Specific files

# 4. Lint before committing

bun run lint:file -- "file1.ts"
bun run lint

# 5. Before creating PR

bun run lint:claude && bun run test

That is the entire file. Build commands Claude cannot guess, the exact order to run things, single-test invocations, the pre-PR ritual. No style preferences. No codebase tours. No platitudes.

Boris also uses @claude in PR comments to have Claude commit a rule directly:

1
2
nit: use a string literal, not a ts enum
@claude add to CLAUDE.md to never use enums, always prefer literal unions

He calls this “Compounding Engineering,” where every PR review becomes a CLAUDE.md improvement.

A fleshed-out template following the same philosophy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# Code style

- Use ES modules (import/export), not CommonJS (require)

# Workflow

- Always use `bun`, not `npm`
- Run `bun run typecheck` before claiming done
- Never push to main directly. Always open a PR.

# Architecture

- All API routes go through src/api/middleware/auth.ts
- New database queries go in src/db/queries/. No inline raw SQL.

# Gotchas

- `User` and `UserRecord` are distinct types. UserRecord is the DB row, User is the runtime object.
- `formatCurrency` assumes USD. For international use `formatCurrencyByLocale`.

The “Gotchas” section is the magic. Every entry is a mistake Claude made, captured the moment it happened.

What does not belong in CLAUDE.md: standard language conventions, file-by-file codebase descriptions, long tutorials, API docs, anything that changes frequently.

: Words like IMPORTANT or YOU MUST improve adherence. Use them sparingly so they carry weight.

You can import other files using @path syntax to keep CLAUDE.md short while pulling in details:

1
2
See @README.md for project overview and @package.json for scripts.
@~/.claude/my-preferences.md

4. CLAUDE.local.md as a Daily Driver

CLAUDE.local.md lives alongside CLAUDE.md, gets loaded the same way, but never leaves your machine. Add it to .gitignore.

The way I use it: after every PR I open, reviewers leave comments. Instead of trying to remember them, I dump them into CLAUDE.local.md the moment I see them. Over time it becomes a personalized rule file for exactly the feedback I get most often.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# Personal review notes (private)

# From PR feedback

- New SQS consumers need a DLQ and alarms in the same PR
- Use `Optional<T>` over null returns
- Tests for new endpoints must include the auth-failure case
- Prefer named tuples over plain dicts for return types with 3+ fields

# My own quirks to correct

- Stop using `console.log`; use the project logger instead
- Always update the OpenAPI spec when adding endpoints

Loaded every session, Claude already knows to include auth-failure tests and update the OpenAPI spec without me mentioning it. Nitpick comments on my PRs dropped noticeably within a couple of weeks.

: Keep two sections clearly separated: project-specific feedback and personal habits to correct. Mixing them makes the file harder to prune later.

: Prune after a few weeks. Things that have become muscle memory can go. The file should capture what is still learning, not what you already do automatically.


5. Skills, In Depth

Skills let Claude Code go from “an agent that can do anything” to “an agent that does specific things really well for your project.” They are the unit of reusable expertise.

5.1 What Skills Actually Are

A skill is a folder under .claude/skills/<name>/ (project) or ~/.claude/skills/<name>/ (global) containing a SKILL.md with frontmatter and instructions. The folder name becomes the slash command.

The simplest possible skill:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
---
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---

## Current changes

!`git diff HEAD`

## Instructions

Summarize the changes in two or three bullet points, then list any risks: missing error handling, hardcoded values, tests that need updating.

Save to ~/.claude/skills/summarize-changes/SKILL.md and /summarize-changes is available in every session.

Three things that make Skills powerful:

  • Progressive disclosure. Claude loads only frontmatter descriptions at session start (~100 tokens each). Full SKILL.md and helper files load only when the skill is actually needed.
  • Skills are folders, not files. Bundle templates, reference docs, scripts, config. SKILL.md is just the entry point.
  • Inline shell. Lines starting with ! run a command and inject the output at invocation time.

Frontmatter supports useful extras:

1
2
3
4
5
6
7
---
name: my-skill
description: When to use this skill
disable-model-invocation: true # only runs when user explicitly types /my-skill
allowed-tools: Read, Grep, Bash
agent: read-only
---

: Use disable-model-invocation: true for skills with side effects. You want /ship to deploy only when explicitly typed, not when Claude decides it is relevant.

5.2 Writing a Real Skill: Go API Conventions

A complete skill for a Go service team, covering conventions, gotchas, and scaffolding for a new HTTP handler:

1
2
3
4
5
6
.claude/skills/go-handler/
├── SKILL.md
├── templates/
│   └── handler.go.tmpl
└── examples/
    └── healthz.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
---
description: Scaffolds a new HTTP handler in our Go service following team conventions for routing, validation, error handling, and tests. Use when the user asks to add a new endpoint, a new handler, or extend an existing route group.
---

# Go HTTP Handler Skill

## Stack

- Go 1.22 with chi router
- sqlc for typed queries, never write raw SQL strings in handlers
- zap for structured logging, never fmt.Println
- testify for assertions, table-driven tests preferred

## Gotchas

- `chi.URLParam` returns `""` for missing params, not an error. Always check.
- Our `httperr.Wrap` does not log. Log separately with `h.log.Error` before returning.
- Auth middleware injects via `context.Value(authkey.User)`. Type-assert to `*models.User`.
- sqlc nullable strings use `pgtype.Text`. Check `.Valid` before calling `.String`.
- Tests must use `httptest.NewRecorder` and `httptest.NewRequest`. No real server.

A skill like this lets a new developer add a fully conventional endpoint without reading the entire codebase first.

mattpocock/skills, the most popular skills repo (~100k stars). Standouts:

  • /grill-me: interviews you about a plan before any code gets written
  • /tdd: enforces red-green-refactor strictly
  • /diagnose: disciplined debugging, reproduce, minimize, hypothesize, fix, regression test

Install: npx skills@latest add mattpocock/skills

Jeffallan/claude-skills ships 66 language-specific profiles: go-pro, python-pro, java-architect, typescript-pro, rust-engineer, sql-pro, and more. Compose them; a Next.js task pulls in nextjs-developer and typescript-pro together.

Anthropic’s official skills:

  • /code-review: four parallel agents audit the diff, confidence-scored findings only
  • /simplify: reviews recent code for reuse and efficiency
  • /batch: fans out a migration to dozens of parallel agents, each in its own worktree
  • /webapp-testing: gives Claude Playwright control to test your local web app

: If you do something more than once a day, turn it into a skill. Anything you repeat is a skill waiting to be written.

: Check skills into git. They become institutional knowledge, and new engineers clone the repo and get the team’s accumulated practices for free.


6. Building Custom Subagents

A subagent runs in its own context window with its own tool permissions and reports back a summary. It can read fifty files without filling up your main session. That is the entire value proposition.

A subagent is a markdown file under .claude/agents/ (project) or ~/.claude/agents/ (global) with a frontmatter block declaring name, description, tools, and model.

6.1 Walking Through a /pr-review Agent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
---
name: pr-review
description: Reviews the current branch diff against main, looking for bugs, security issues, missed edge cases, and project-convention violations. Use proactively before opening a PR.
tools: Read, Grep, Glob, Bash
model: opus
---

You are a senior staff engineer reviewing a pull request. Thorough, direct, goal is to catch issues before human reviewers do.

## Process

1. Run `git diff main...HEAD`
2. Run `git log main..HEAD --oneline`
3. Read full files, not just diff context
4. Cross-check against CLAUDE.md, CLAUDE.local.md, and .claude/rules/

## Flag

- Correctness bugs: off-by-one, null handling, error paths, race conditions
- Security: injection risks, missing auth checks, secrets in code
- Missing tests for new logic
- N+1 queries
- Convention violations from CLAUDE.md or rules/

## Do NOT flag

- Style preferences not in project rules
- Refactoring suggestions for working code
- Anything outside this diff

## Output

Group by severity (Critical / High / Medium / Low). File + line + issue + suggested fix.
End with a verdict: **SHIP**, **FIX FIRST**, or **REWORK**.

Run it by saying Have the pr-review agent look at my current branch. The subagent handles everything in its own context, your main session stays clean.

Key design choices: tools is read-only, because a reviewer that modifies code gets biased toward defending its own edits. model: opus for high-stakes review. The “Do NOT flag” section keeps signal-to-noise high.

The Claude Code team checks in: build-validator, code-architect, code-simplifier, oncall-guide, verify-app.

Community patterns worth adopting:

AgentWhat it does
security-reviewerinjection, auth, secrets, insecure deserialization
test-writergenerates tests, pairs with code-reviewer in a loop
debuggertraces failing tests to root causes
performance-auditorprofiles flows and queries
migration-writergenerates DB migrations matching project conventions
release-notes-writerchangelogs from commit history

Curated repos: VoltAgent/awesome-claude-code-subagents (100+ agents) and hesreallyhim/a-list-of-claude-code-agents.

: Chain agents: Session A implements, then call Use the code-reviewer subagent to check the work. The reviewer evaluates in a fresh context with no implementation bias.

: Add isolation: worktree to frontmatter to run the subagent in its own git worktree, especially powerful when fanning out a migration across dozens of parallel agents.


7. Plugins and the Marketplace

Plugins bundle skills, hooks, subagents, and MCP servers into a single installable unit. Run /plugin to open the marketplace browser. Add community marketplaces with /plugin marketplace add owner/repo.

Day-one installs:

/code-review runs four parallel agents: two audit for CLAUDE.md compliance, one scans for bugs, one analyzes git blame for context. Confidence-scored, high signal-to-noise.

/feature-dev is the most popular skill on the official marketplace. Turns a feature brief into working code through seven phases: requirements → exploration → architecture → implementation → testing → review → docs.

Language server plugin provides precise symbol navigation and automatic diagnostics after every edit. The team consistently calls this the single highest-impact plugin you can install.

/security-guidance is Anthropic’s official security skill, surfacing concerns before they ship.

Plugin categories worth knowing (1,000+ plugins across 75+ marketplaces as of mid-2026):

  • Git workflow, code intelligence (LSP), documentation generators, testing, browser automation (Playwright), design system (Figma), observability (Sentry, Datadog)

: A team-shared .mcp.json plus a few well-chosen plugins gets a new engineer productive within minutes of cloning the repo. Treat plugin choices as part of your onboarding story.


8. Underused Claude Code Commands

Most users learn /clear, /compact, and /init and stop. A handful of the rest quietly do more for productivity than anything else.

CommandWhat it does
/insightsAnalyzes your usage patterns; run once a month
/compact <hint>Compresses session; hint controls what survives
/copyCopies last response; interactive picker for code blocks
/rewindUndo for your whole session, restoring code, conversation, or both
/btwSide question that never enters conversation history
/contextVisualizes context usage
/export <file>Dumps conversation to file
/branchForks your session to try something risky
/batchFans work out to parallel agents across worktrees
/loop <interval>Schedules Claude to run on repeat, up to 3 days
/scheduleCloud version of /loop, works even when your laptop is closed
/teleportMoves a session between terminal and web
/focusHides intermediate tool calls, shows only final result
/voiceVoice input; Boris says he codes mostly by speaking
--bareUp to 10x faster startup for non-interactive claude -p usage

/compact vs /clear: genuinely new task = /clear with a fresh hand-written brief. Related task where you still need context = /compact with a hint. /compact is a lossy LLM summary; /clear is your brief. That distinction matters.

/rewind creates a checkpoint for every prompt, and those persist across sessions. When Claude goes down a wrong path, do not type “that did not work, try X,” as that pollutes context. Rewind and re-prompt with what you learned.

: Use ! as a shell escape. !git status or !npm test runs immediately with output landing in context.

: Set CLAUDE_CODE_AUTO_COMPACT_WINDOW=400000. Context rot kicks in around 300-400k tokens on the 1M model, so force earlier compaction to stay sharp.

Fan-out pattern: generate a task list, then loop:

1
2
3
4
5
for file in $(cat files.txt); do
  claude -p "Migrate $file from React to Vue. Return OK or FAIL." \
    --allowedTools "Edit,Bash(git commit *)" \
    --bare
done

Test on three files. Fix the prompt. Then run on two thousand.

8.1 /goal, the Ralph Loop Built In

/goal sets a completion condition. Claude keeps working until the condition is true. Every time it tries to stop, it checks the condition against the transcript.

1
/goal all tests in test/auth pass and the lint step is clean

Real examples:

1
2
3
4
/goal all integration tests in tests/api pass without flaking 3 runs in a row
/goal the OpenAPI spec validates and matches the actual response shapes
/goal docker compose up runs cleanly and the healthcheck endpoint returns 200
/goal coverage on src/billing/ is above 80% and all new tests are not placeholders

Pick a verifiable, deterministic condition, tied to a test command, CLI exit code, or file state. Vague conditions like “the code is good” do not work.

Companions that pair well:

  • /loop: repeat at an interval, burn down a backlog
  • /schedule: run on a cadence in the cloud
  • A Stop hook: gate on your own test suite or CI endpoint
  • Auto mode: removes permission prompts so long goals do not stall

: Combine /goal + auto mode + /focus. Write a crisp brief, set the goal, walk away. Come back to a finished PR. This is the workflow Boris and Cat Wu push for Opus 4.7.


MCP (Model Context Protocol) turns Claude Code from a coding agent into a system-aware coding agent. An MCP server exposes external tools like a database, a design tool, your error tracker, or your notes to Claude in a standardized way.

Without MCP, Claude reads files and runs commands. With MCP, Claude reads your Linear tickets, queries your Postgres, pulls up a Figma component, fetches live Sentry stack traces, or reads your Obsidian vault, all without leaving the terminal.

The go-to MCPs for engineering work:

MCPWhat it unlocks
GitHubRepo management, PRs, issues, code search
Context7Live, up-to-date library docs; append use context7 to any prompt
SentryReal error context, stack traces, breadcrumbs
LinearRead/create tickets, update status
PlaywrightBrowser automation via accessibility snapshots
FigmaLive design tree: auto-layout, spacing tokens, component refs
Postgres / SupabaseQuery your dev DB directly
SlackRead threads, summarize discussions, draft responses

Local servers use stdio, vendor-hosted use HTTP with OAuth:

1
claude mcp add --transport http sentry <a href="https://mcp.sentry.dev/mcp" rel="nofollow">https://mcp.sentry.dev/mcp</a>

Team-shared MCPs go in .mcp.json at the project root. Personal MCPs go in ~/.claude.json.

9.1 A Real Obsidian Workflow

The Obsidian + Claude Code pairing becomes genuinely powerful when you use it as a three-tier memory architecture, not just “Claude can read my vault.”

Setup: Install obsidian-claude-code-mcp in Obsidian (exposes the vault on a local WebSocket, port 22360). Claude Code auto-discovers it. Add a CLAUDE.md to your vault explaining the folder structure.

Folder structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
vault/
├── 00-Inbox/         # raw capture
├── 10-Daily/         # one note per day
├── 20-Projects/      # active project notes
│   └── billing-v2/
│       ├── README.md      # goal, status, open questions
│       ├── decisions/     # ADRs
│       └── sessions/      # one log per Claude session
├── 30-Decisions/     # cross-project ADRs
├── 40-Atoms/         # reusable knowledge, linked
└── 90-Archive/

The three tiers:

Hot storage: daily session log. Every Claude session writes a timestamped log to 10-Daily/<today>.md. A Stop hook can do this automatically: when the agent finishes, it appends a structured summary.

Warm storage: project notes. Each project has a folder under 20-Projects/. Before any new session, Claude reads the project README and last 2-3 session logs to rebuild context. Two weeks of context in 30 seconds.

Cold storage: decisions and atoms. Architectural decisions get promoted into 30-Decisions/ as ADRs. Reusable knowledge gets distilled into 40-Atoms/ and linked via wikilinks across all projects.

Daily workflows:

  • What is in my inbox? Summarize and suggest where each item belongs.
  • Check 30-Decisions/ for anything related to retry policies.
  • Read the last 3 session logs for billing-v2. Tell me where I left off.

: Resist installing every MCP. Each one expands the tool list Claude reasons over, and bloated tool lists hurt decision quality. Starter set: GitHub, Context7, plus one or two domain-specific.

: Run /mcp inside Claude Code to list every active server and its connection status. First place to check when something is not working.


10. Optimizing Your Daily Workflow

Morning. Open Claude Code in the project. Skim what subagents and scheduled jobs did overnight. Run /insights once a week.

New feature. Plan mode → edit plan with Ctrl+G → implement → invoke /pr-review subagent or spin up a fresh Claude session to review.

Bug. Reproduce first. Pipe the error: cat error.log | claude. Ask Claude to write a failing test that reproduces it. Only then ask it to fix. The test prevents the fix from being a guess.

Migrations or mass changes. Use /batch, which interviews you about the change, then fans out to parallel agents, each in its own worktree, each testing and creating a PR.

Unfamiliar code. Use a subagent: “Use a subagent to investigate how our auth handles token refresh.” It reads dozens of files in its own context and reports back a summary. Your main session stays clean.

Parallel sessions. Boris and the team call this the single biggest productivity unlock: three to five git worktrees, each running its own Claude session. Use the agent view (claude agents) as a control plane.

Writer/Reviewer pattern. Session A implements. Session B reviews in a fresh context. Copy the review back, fix, repeat.

Compact at milestones. After finishing a logical chunk: /compact Preserve the decisions made, files changed, and test commands.

: Never let Claude claim success without evidence, whether that is tests, screenshots, or real command output. The trust-then-verify gap is the single biggest source of bad output.


11. s From the Anthropic Team

Collected from Boris, Cat Wu, Thariq, and the broader team. These are the patterns that actually change how they work:

“Give Claude a way to verify its output. Once you do that, Claude will iterate until the result is great.” Boris’s single most-repeated .

Use Opus with high or xhigh effort for almost everything. The smaller model that needs more correction is often slower overall, which is Boris’s reasoning for defaulting to Opus.

Run 3-5 sessions in parallel. Worktrees over checkouts. Use claude --worktree or the Desktop app. The agent view ties them together.

Maintain a notes directory per project, updated after every PR. Tell Claude to keep notes in a directory, point CLAUDE.md at it. The codebase compounds in self-knowledge.

Build a /techdebt slash command. Run it at the end of every session to find and kill duplicated code.

The team’s CLAUDE.md is shared and edited mulle times a week. Anytime someone sees Claude do something incorrectly, they add a rule. Treat it as a living document.

Esc twice opens rewind. Combined with checkpoints: try risky things, find out they failed, rewind cleanly.

For UI changes, set up Playwright MCP. Boris uses the Chrome extension every time he works on web code, where Claude opens a browser, clicks around, and verifies.

Install a language server plugin. Type errors and unused imports caught after every edit. Highest-impact plugin you can install.

Use /voice for prompting. You speak 3x faster than you type, and prompts get way more detailed as a result.

Auto mode + /focus + /goal. Crisp brief, set the goal, walk away. Come back to a finished PR.

Use Ctrl+G to edit Claude’s plan in your editor before implementation. Faster than typing corrections in the chat.

Ask Claude to draw ASCII diagrams of new protocols and codebases. Boris’s for understanding unfamiliar code quickly.


12. Resources

Official docs

Boris and the team

Skills

Subagents

Plugins and marketplaces

MCPs


Closing Notes

Claude Code clicked for me only after I stopped treating it like ChatGPT in a terminal. You stop thinking “I need to write this code” and start thinking “I need to set up Claude to write this code well.” The setup is the work. The execution is mostly verification.

A few things that have genuinely changed how I work:

CLAUDE.md is compounding infrastructure. Every mistake Claude makes is a rule waiting to be written. After a few weeks of “update CLAUDE.md so you do not repeat this”, the same prompts produce dramatically better output.

CLAUDE.local.md captures PR feedback. Your reviewers are giving you free training data. Convert recurring feedback into rules. Let Claude apply them next time.

Skills are the unit of reusable expertise. If you find yourself prompting the same instructions twice, that is a skill waiting to be written.

Subagents over kitchen-sink prompts. Separate concerns, keep contexts clean, and the quality of every individual task goes up.

Parallel sessions are the unlock everyone underestimates. Three Claudes in three worktrees is a different kind of leverage. Try it for a day.

The real shift happens when you stop thinking of Claude Code as a tool you use to write code and start thinking of it as something you train, configure, and operate. Most people stop at the prompts. Going past that, into the directory structure, skills, agents, plugins, and MCPs, is where it stops feeling like a tool and starts feeling like a teammate.


Claude Code icon by LobeHub, used under the Apache 2.0 license.

Read the whole story
emrox
52 minutes ago
reply
Hamburg, Germany
Share this story
Delete

CSS vs. JavaScript

1 Share
Introduction

One of the most common questions around animation performance is whether JS-based animations are slower than CSS-based ones. Should we always strive to use CSS transitions, or is it OK to use JavaScript animation libraries?

There’s a surprising amount of nuance to this question, and I think that the conventional wisdom isn’t quite right. In this post, we’re going to dig into this question and see the differences for ourselves!

Link to this headingComparing CSS keyframes to JavaScript loops

Let’s suppose we’re building the following animation:

We can wire this up with a CSS keyframe, like this:

@keyframes bounce {
  to {
    transform: translateX(calc(var(--bounce-magnitude) * -1));
  }
}

.ball {
  --bounce-magnitude: 200px;
  animation: bounce 1000ms infinite alternate;
}

(I’m using a CSS transform for this animation because it produces the smoothest motion. In cases where the container size is dynamic, we would need to calculate and apply --bounce-magnitude in JS.)

Alternatively, we could implement this animation using JavaScript! Before we consider JS libraries like GSAP or Motion, let’s start with a plain JS version:

const startTime = performance.now();

const ball = document.querySelector('.ball');

function animate() {
  const elapsedTime = performance.now() - startTime;

  // ✂️ Calculate `x` based on the amount of time that has passed.

  ball.style.transform = `translateX(${x}px)`;

  window.requestAnimationFrame(animate);
}

This code uses requestAnimationFrame to run the animate function on every frame (60 times per second on most displays). I’ve cut out the main logic to calculate x since it’s a bit complicated and not relevant for the topic at hand, but you can see the full code(opens in new tab) if you’re curious.

Here’s the question: which approach do you think runs more smoothly?

I think for most of us, our intuitions would tell us that the CSS version is more performant. And our intuition is correct, but maybe not for the reasons we think. 😅

You might think that the JS version is slower because it has to do all that extra work calculating the x value on every frame, or that there’s an extra cost to “crossing the bridge” between JavaScript and the DOM. But modern browser engines can tackle all of that stuff without breaking a sweat; even on low-end devices, that work happens in a tiny fraction of a millisecond, way too quick to affect the framerate of our animation.

But there’s one significant difference: the JavaScript version runs on the main thread, along with everything else happening in our application. CSS transitions and keyframe animations run on a separate thread, so they aren’t disrupted when stuff happens in JavaScript.

I’ve taken the liberty of creating a simulation. Click the “Play” button to run the demo. Every few seconds, the main thread will be blocked. Notice what effect it has on these two animations:

In modern web applications, the main thread does a lot of work. JavaScript frameworks like React are constantly making updates to the DOM, to keep it in sync with application state. Every time we make a fetch request (eg. to load more data, or to refresh existing data), that response has to be parsed by the main thread.

So, if you’ve ever seen a spinner freeze for a moment before the UI is updated, this is why! JavaScript-based animations have to compete for processing power with the rest of the application.

Link to this headingComparing animation libraries

In the example above, I used a requestAnimationFrame loop to update the UI on every frame in JavaScript. This is a pretty low-level technique; in practice, many developers use JavaScript libraries that provide a higher-level abstraction.

Let’s compare two popular animation libraries, Motion(opens in new tab) (formerly Framer Motion) and GSAP(opens in new tab):

Huh! Both Motion and GSAP are JavaScript-based, so you might expect them to share the same limitations of running on the main thread. But somehow, Motion manages to keep the animation running smoothly even when the main thread is occupied. 🤔

The secret is that Motion uses the Web Animations API(opens in new tab) (WAAPI) under the hood. WAAPI is essentially a JavaScript interface that hooks into the same low-level animation engine as CSS keyframe animations. So, Motion is able to run its animations on a separate thread, avoiding the main pitfall of most other JavaScript animation libraries! 😮

To be fair to GSAP, it’s an enormously powerful library which includes features that probably aren’t compatible with WAAPI. So, it’s not that GSAP made the wrong choice, it’s that they’re choosing different trade-offs.

In my own work, I try to use native CSS animations/transitions whenever I can. When I run into situations that CSS alone can’t handle, I try to use a library like Motion, which solves the problem without the drawbacks typically associated with JS libraries.

That said, CSS has become so powerful that there really aren’t that many cases where we need to reach for an animation library these days; new APIs like View Transitions, linear(), and Animation Timeline make it possible to do all sorts of stuff without JavaScript. ✨

We explore all of these tools, and so much more, in my brand-new course, Whimsical Animations(opens in new tab). I’ll show you how I design and implement top-tier animations using modern CSS, JavaScript, SVG, and Canvas.

Whimsical Animations, a course from Josh W. Comeau

These days, LLMs are great at generating syntax, but we still need to use our own judgment. In this blog post, we learned about the performance considerations between CSS and JavaScript, and my course is full of stuff like this. So, regardless of whether you’re writing code by hand or not, this course will help you create stunning animations and interactions.

🌸 And I’m currently running my annual Spring Sale! You can save up to $150 on the course, but only for a limited time. You can learn more here:

Last updated on

May 26th, 2026

# of hits

Read the whole story
emrox
3 hours ago
reply
Hamburg, Germany
Share this story
Delete

From Rust to Ruby

1 Share

Who does things like that?!?

Apparently: me.

I have my own project, written in Rust. Not a big one, mind you, maybe approx. 30k lines of code in total. Rust is verbose so it’s not really that impressive. I’ve put it aside for some time and was toying with local inference, LLMs, writing agents and my attention was brought to Ruby.

It’s been a while. So I had to take a look around to remind myself what Ruby and Ruby on Rails are doing nowadays. They’re doing quite well. There are some typing initiatives (Sorbet), and the language itself is terse as ever.

And then I had this thought… But an introduction is in order first: In my Rust app I have an isolated crate that’s pretty much a webapp written with Tera and Axum. 14,943 lines of Rust code in total, around 10s of compilation time (maybe the code isn’t big, but it pulls the whole universe behind itself) and then quite heavy E2E tests involving setting up Playwright and (because of the near-impossibility of mocking) an isolated database namespace and mocking services (along with a very special internal-api crate that allows Playwright to interact with the app in headless mode…).

So I thought “hmm, I wonder if I can get my Local Qwen3.6 to do a oneshot conversion”. But before I did so I researched first. I asked a few instances to analyse the project in terms of gains of complexity, stability, testability, etc., and while (obviously) stability would drop (no types in Ruby) it’s not that awful (Sorbet has types in Ruby!).


 ┌─────────────────────────────────┬──────────────────┬───────┬────────────────┐
 │ Area                            │ Rust/Axum/Diesel │ Rails │ Rails + Sorbet │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Suitability for solo dev        │ 60               │ 90    │ 85             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Development speed               │ 40               │ 90    │ 75             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Safety                          │ 95               │ 55    │ 80             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Development complexity          │ 70               │ 90    │ 75             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Performance                     │ 95               │ 50    │ 50             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Boilerplate                     │ 30               │ 85    │ 80             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ E2E testing testability         │ 40               │ 75    │ 75             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Unit testability                │ 20               │ 90    │ 90             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Integration testing testability │ 30               │ 85    │ 85             │
 ├─────────────────────────────────┼──────────────────┼───────┼────────────────┤
 │ Sum                             │ 480              │ 710   │ 695            │
 └─────────────────────────────────┴──────────────────┴───────┴────────────────┘

So in the end it seems I have (licks finger and turns to the wind) 1.47x better outcomes if the app were a Ruby on Rails app instead.

interesting ai-generated image of Bugs Bunny saying i-i-i-i-i-i-nteresting

I have a local LLM running on my (bought it for gaming pre-AI craze) 4090 Ti1 - I’m a free man with unlimited tokens2. So I thought: BRING IT ON!

Since it is a relatively small project the conversion took ~30 minutes. I have no idea if it works or not because I haven’t yet tried running it. But there is one thing I checked, and stared at in horror:

$ fd . -e rs -uu | xargs cat | wc -l
   14943
$ fd . -e rb -uu | xargs cat | wc -l
    3322
ai-generated image of Bugs Bunny winning formula-1-like race with a huge 77% balloon

That’s right folks! 77% decrease in line count; 4.49 lines of Rust code for each line of Ruby.

I browsed the Ruby code and it looks… fine. There are probably some bugs (no bunnies) but I must say it’s looking clean and idiomatic for my dated eye. I’m going to examine it further with some things in mind:

  • I can add types using Agents, so probably type safety can be alleviated

  • Ruby/Rails is pretty much batteries+kitchen sink included, which beats 3GiB of compiled deps.

  • Testing will be SO MUCH EASIER

       VCR.use_cassette("llm_call") do
         result = LlmClient.match(entry, data_list)
         expect(result.results.size).to eq(data_list.size)
       end
    

    vs

    #[derive(Debug)]
    pub struct MockProvider {
      responses: Arc<RwLock<Vec<Response>>>,
      call_count: Arc<AtomicUsize>,
    }
    
    impl Default for MockProvider {
      fn default() -> Self {
        Self {
          responses: Arc::new(RwLock::new(vec![Response::default()])),
          call_count: Arc::new(AtomicUsize::new(0)),
        }
      }
    }
    
    impl MockProvider {
      pub fn new(responses: Vec<Response>) -> Self {
        Self {
          responses: Arc::new(RwLock::new(responses)),
          call_count: Arc::new(AtomicUsize::new(0)),
        }
      }
    }
    
    #[async_trait]
    impl Provider for MockProvider {
      async fn match(&self, entry: &Entry, data_list: &[Data]) -> Result<MatchResult> {
        self.call_count.fetch_add(1, Ordering::SeqCst);
        let responses = self.responses.read().await;
        Ok(MatchResult { results: responses.clone() })
      }
    }
    
    #[cfg(test)]
    mod tests {
      use super::*;
    
      #[tokio::test]
      async fn test_mock_provider_returns_expected_results() {
        let expected = vec![Response::default()];
        let provider = MockProvider::new(expected.clone());
        let result = provider.match(&Entry::default(), &[]).await.unwrap();
        assert_eq!(result.results, expected);
        assert_eq!(provider.call_count.load(Ordering::SeqCst), 1);
      }
    }
    

    …you get the vibe, right?

The benefits of working on your own project are, well, you can make crazy decisions, and I’ll be looking at this one very closely.

Read the whole story
emrox
4 hours ago
reply
Hamburg, Germany
Share this story
Delete

BEMoji — The Emoji-First CSS Framework

1 Share

🎯 BEMoji v1.0.0-beta

RFC DRAFT

Block · Element · Modifier · 🎉

The CSS framework
your team will actually
argue about.

BEMoji is a production-grade utility and component framework built entirely on emoji class names. Semantic, systematic, and completely illegible to anyone without the docs.

<!-- A card component in BEMoji -->

<article class="🃏">

  <div class="🃏__🖼️ 🃏__🖼️--🌟">
    <img src="hero.jpg" alt="...">
  </div>

  <div class="🃏__📝">
    <h2 class="🃏__🔠">Card Title</h2>
    <p class="🃏__💬">Card description text</p>
  </div>

  <footer class="🃏__🦶">
    <button class="🔘 🔘--🌟">Primary</button>
    <button class="🔘 🔘--👻">Disabled</button>
  </footer>

</article>

<!-- Responsive: 📱 = mobile-only -->
<div class="📐💠 💻📐🔲">
  Grid col-1 on mobile, col-2 on desktop
</div>
/* ── Card Block ── */
.🃏 {
  border-radius: 8px;
  overflow: hidden;
  box-shadow: var(--🌑-md);
  background: var(--⬜);
}

/* Element: image */
.🃏__🖼️ {
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

/* Modifier: featured */
.🃏__🖼️--🌟 {
  outline: 3px solid var(--⭐-gold);
  outline-offset: -3px;
}

/* Design tokens use emoji too */
:root {
  --⬜: #ffffff;
  --⬛: #0d0d0f;
  --🌑-sm: 0 1px 3px rgba(0,0,0,.12);
  --🌑-md: 0 4px 12px rgba(0,0,0,.15);
  --📏-1: 0.25rem;
  --📏-4: 1rem;
  --📏-8: 2rem;
}
// bemoji.config.js
export default {
  version: '1.0',

  /** Map readable names → emoji tokens */
  blocks: {
    card:    '🃏',
    navbar:  '🧭',
    modal:   '🪟',
    alert:   '🔔',
    form:    '📋',
    table:   '📊',
  },

  elements: {
    image:   '🖼️',
    title:   '🔠',
    body:    '📝',
    footer:  '🦶',
    button:  '🔘',
    input:   '📥',
  },

  modifiers: {
    primary: '🌟',
    danger:  '🔴',
    success: '🟢',
    ghost:   '👻',
    loading: '⏳',
  },

  separator: {
    element:  '__',
    modifier: '--',
  }
};
// With the Babel transform, write readable
// BEM names — they compile to emoji at build time.

import { bem } from 'bemoji/react';

const Card = ({ featured, children }) => (
  <article className={bem('card')}>
    <div className={bem('card__image', {
      featured
    })}>
      {children.image}
    </div>
    <div className={bem('card__body')}>
      {children.body}
    </div>
  </article>
);

// Compiles to:
// className="🃏"
// className="🃏__🖼️ 🃏__🖼️--🌟"  (when featured)
// className="🃏__📝"

01 — Core Concepts

BEM, but make it
completely unreadable

BEMoji inherits BEM's three-layer architecture exactly. The only change is that every identifier — blocks, elements, modifiers, utilities, breakpoints, and design tokens — is an emoji.

🧱

Block

A standalone, self-contained component. The root of a component tree. Blocks should be reusable and carry no inherited context.

.🃏 .🧭 .📋 .🪟

🔩

Element

A part of a block that has no standalone meaning. Always expressed as block__element. Elements cannot exist outside their block.

.🃏__🖼️ .🧭__🔗 .📋__📥

🎨

Modifier

A flag that changes appearance or behavior. Applied alongside the base class using block--modifier or block__element--modifier.

.🃏--🌟 .🔘--🔴 .📥--👻

02 — Naming Anatomy

Dissecting a class name

Every BEMoji class name follows a strict, parseable structure. The double-underscore and double-hyphen delimiters are preserved from BEM, making the format machine-readable even if it isn't human-readable.

Note on escaping: Modern browsers parse emoji in CSS class selectors without escaping. For maximum compatibility (older Webkit, certain CSS-in-JS parsers), the BEMoji compiler can output escaped unicode: .🃏.\01F0CF. The toolchain handles this automatically.

Emoji Role Full class Equivalent BEM
🃏 Block: card .🃏 .card
🖼️ Element: image .🃏__🖼️ .card__image
🌟 Modifier: featured .🃏__🖼️--🌟 .card__image--featured
🔴 Modifier: danger .🔘--🔴 .button--danger
👻 Modifier: disabled .📥--👻 .input--disabled
💻🔲 Responsive utility .💻📐🔲 .lg\:grid-cols-2

03 — Emoji Vocabulary

The canonical emoji lexicon

The BEMoji spec defines 143 reserved emoji tokens across blocks, elements, modifiers, states, and design tokens. Teams can extend this with custom emoji via the config, provided they don't collide with reserved tokens.

🧱 Blocks — Components

🃏cardpanel, tile

🧭navbarnav, header

🦶footer-

📋form-

🪟modaldialog, overlay

🔔alertnotification

🏷️badgetag, chip

💬tooltippopover

📊table-

📑tabs-

🎠carouselslider

🍞breadcrumb-

🔩 Elements — Parts

🖼️imageimg, media

🔠titleheading, h

📝bodycontent, text

🦶footeractions

🔘buttonbtn, cta

📥inputfield, control

🔗linkanchor, a

🏷️label-

🎭icon-

📄itemrow, entry

🖇️dividerseparator, hr

🔭prefixprepend

🎨 Modifiers — States

🌟primaryfeatured, hero

🔴dangererror, destructive

🟢successok, valid

🟡warningcaution

🔵info-

👻disabledghost, muted

activeselected, on

loadingpending, busy

🔒lockedreadonly

💎premiumpro, upgrade

🆕newfresh, recent

🕶️darknight

🎛️ Modifiers — Size

🔬xsextra-small

🤏smsmall

⚖️mdmedium, default

🏋️lglarge

🏔️xlextra-large

🌍2xlfull, max

04 — Design Tokens

Variables, all the way down

BEMoji's token system uses emoji as CSS custom property names. This means the theme layer is as illegible as the class layer — one consistent obfuscated aesthetic throughout your entire codebase.

Color Tokens

--⬛Base dark / ink
--⬜Base light / paper
🔴--🔴Danger / error
🟢--🟢Success / ok
🟡--🟡Warning
🔵--🔵Info / accent
🟣--🟣Purple / brand
🟠--🟠Orange

Spacing Scale

📏--📏-10.25rem (4px)
📏--📏-20.5rem (8px)
📏--📏-41rem (16px)
📏--📏-61.5rem (24px)
📏--📏-82rem (32px)
📏--📏-123rem (48px)
📏--📏-164rem (64px)

Typography

✍️--✍️-xs0.75rem
✍️--✍️-sm0.875rem
✍️--✍️-base1rem
✍️--✍️-lg1.125rem
✍️--✍️-xl1.25rem
✍️--✍️-2xl1.5rem
✍️--✍️-4xl2.25rem

Shadows & Radius

🌑--🌑-smSubtle shadow
🌑--🌑-mdMedium shadow
🌑--🌑-lgLarge shadow
🌑--🌑-innerInset shadow
--⭕-smborder-radius: 4px
--⭕-mdborder-radius: 8px
--⭕-fullborder-radius: 9999px

05 — Responsive System

Breakpoints as emoji prefixes

Responsive utilities use an emoji breakpoint prefix, separated from the utility by a zero-width joiner (U+200D). This keeps class names as a single unicode "word" from the browser's perspective, while remaining visually parseable.

📱

xs

0 – 639px

Mobile first (no prefix)

📐💠

📟

sm

640px+

Large mobile / phablet

📟📐🔲

📲

md

768px+

Tablet

📲📐🔲

💻

lg

1024px+

Laptop / desktop

💻📐🔳

🖥️

xl

1280px+

Wide desktop

🖥️📐⬛

/* Generated by the BEMoji compiler */

@media (min-width: 640px) {
  .\01F4DF \01F4D0 \01F532 { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .\01F4BB \01F4D0 \01F533 { grid-template-columns: repeat(3, 1fr); }
}

/* Or with modern emoji CSS (most environments) */

@media (min-width: 640px) { .📟📐🔲 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .💻📐🔳 { grid-template-columns: repeat(3, 1fr); } }

06 — Component Library

Built-in components

BEMoji ships with 24 production-ready components pre-built in the framework stylesheet. Each follows the emoji naming convention throughout, with full modifier support.

🃏

Card Title

Supporting body text goes here

🃏 card — .🃏 .🃏__🖼️ .🃏__🔠 .🃏__📝

🌟 Primary 🔴 Danger 🟢 Success 🟡 Warning

🏷️ badge — .🏷️ .🏷️--🌟 .🏷️--🔴 .🏷️--🟢

🔘 button — .🔘 .🔘--🌟 .🔘--🔴 .🔘--👻

🔴

Something went wrong. Please try again or contact support.

🔔 alert — .🔔 .🔔--🔴 .🔔__💬 .🔔__🔘

📥 input — .📥 .📥--👻 .📥--🟢 .📥--🔴

🍞 Home / Products

🍞 Home / Cats / Tabby

🍞 breadcrumb — .🍞 .🍞__📄 .🍞__📄--✅

07 — Tooling Ecosystem

A complete dev toolchain

BEMoji ships with a full suite of build tools, editor integrations, and framework adapters. You write readable names in development; the compiler handles the emoji transformation at build time.

Transforms human-readable BEM class names in your CSS source to emoji equivalents at build time. Supports custom mappings, escaped output, and source maps.

// postcss.config.js
module.exports = {
  plugins: [
    require('bemoji-postcss')({
      config: './bemoji.config.js',
      escape: 'auto',   // 'raw' | 'unicode'
      sourceMap: true,
    })
  ]
}

Zero-config Vite integration. Transforms class names in HTML, JSX, TSX, and Vue templates. Hot module reloading works transparently with emoji class names.

// vite.config.ts
import bemoji from 'vite-plugin-bemoji';

export default {
  plugins: [
    bemoji({
      config: './bemoji.config.js',
      include: ['**/*.{tsx,jsx,vue,html}'],
    })
  ]
}

IntelliSense for BEMoji class names. Hover over an emoji class to see its human-readable equivalent. Autocomplete from your config, lint unknown tokens, and toggle between emoji/readable views.

// .vscode/settings.json
{
  "bemoji.showTooltips": true,
  "bemoji.autocomplete": "emoji+readable",
  "bemoji.lintUnknownTokens": "warn",
  "bemoji.hoverFormat": "both",
  "bemoji.configPath": "./bemoji.config.js"
}

Enforces correct BEMoji patterns. Warns on unknown emoji tokens, invalid block–element–modifier hierarchies, missing block context on elements, and disallowed multi-emoji sequences.

// .eslintrc.js
{
  "plugins": ["bemoji"],
  "rules": {
    "bemoji/no-unknown-tokens": "error",
    "bemoji/no-orphan-elements": "warn",
    "bemoji/no-modifier-without-base": "error",
    "bemoji/prefer-semantic-emoji": "warn"
  }
}

The bem() helper resolves readable block/element/modifier strings to emoji class names at runtime (or at compile time with the Babel transform). Works with conditional modifiers via object syntax.

import { bem } from 'bemoji/react';

// Runtime usage
bem('card')              // → '🃏'
bem('card__image')       // → '🃏__🖼️'
bem('card', { primary }) // → '🃏 🃏--🌟'
bem('card__image', {
  featured: true,
  loading: false         // → '🃏__🖼️ 🃏__🖼️--🌟'
})

Scaffold new projects, compile CSS, audit for unused tokens, generate a Storybook story per component, and export the full emoji→readable mapping as a JSON reference sheet.

npx bemoji init           # Scaffold + config
npx bemoji compile        # Transform CSS files
npx bemoji audit          # Check for unused tokens
npx bemoji export --fmt json > tokens.json
npx bemoji storybook      # Generate component stories
npx bemoji decode "🃏__🖼️--🌟"
# → card__image--featured

08 — Design Philosophy

Why on earth would
you do this?

BEMoji is a genuinely considered tradeoff, not a joke with no payoff. Here's the honest case for and against.

01

Free obfuscation

Production CSS with emoji class names is meaningless to scrapers, competitors, and client-side inspectors. You get the obfuscation benefits of CSS modules or hashed class names with none of the build complexity — it's just the class names themselves.

02

Enforced vocabulary

Because every UI concept maps to a single, canonical emoji, naming arguments become arguments about which emoji is most semantically correct — which is a much shorter argument than any discussion of BEM naming conventions.

03

Config is the contract

The bemoji.config.js file is a living contract between your design system and your codebase. Changing a concept's emoji is a single-line config change that propagates everywhere, unlike refactoring a string-based class name across thousands of files.

04

Developer velocity

Emoji are faster to type than long BEM class strings once you have input method shortcuts. 🃏 takes two keystrokes on most OS emoji pickers. .card__image--featured takes 24. The math eventually works out.

Honest caveat: BEMoji is not appropriate for every team or project. If your team doesn't control the config file, emoji class names become a liability rather than an asset. It also requires editor tooling buy-in from every contributor. The VS Code extension and ESLint plugin exist specifically to make this tractable.

09 — Framework Comparison

How BEMoji stacks up

Feature BEMoji Tailwind CSS BEM (vanilla) CSS Modules
Zero runtime JS
Automatic obfuscation Build-time only
Semantic naming
Responsive utilities
Design token system
Human-readable source Via build tool
Pre-built components
Bundle size ~4kb gzip ~10kb purged 0kb (DIY) 0kb (DIY)
Colleague confusion Maximum Moderate Minimal Minimal

10 — Get Started

Install BEMoji

Step 01

Install the package

npm install bemoji
# or
yarn add bemoji
# or
pnpm add bemoji

Installs the core framework, PostCSS plugin, and CLI. Peer deps are PostCSS 8+ and Node 18+.

Step 02

Initialise your config

npx bemoji init

# Creates:
# ├── bemoji.config.js
# ├── bemoji.css (base styles)
# └── postcss.config.js

The init wizard lets you pick a pre-built vocabulary or define your own token set from scratch.

Step 03

Import and build

/* main.css */
@import 'bemoji/base';
@import 'bemoji/tokens';
@import 'bemoji/components';

/* Then write your own BEM: */
.[card] { border-radius: var(--⭕-md); }
.[card__image--featured] {
  outline: 2px solid var(--🟡);
}

The PostCSS plugin compiles [card] shorthand to emoji class names automatically at build time.

Read the whole story
emrox
1 day ago
reply
Hamburg, Germany
Share this story
Delete

Agentic Patterns — Veso Research

1 Share
Research Agentic Patterns Start

The universal architecture emerging across all frontier agentic systems.

Convergence.

Claude Code, OpenAI Codex, Gemini CLI, LangGraph, CrewAI, Google ADK, Amazon Bedrock — built by different companies, in different languages, under different constraints. They converged on the same design.

Not because they copied each other. Because the constraints are physics. Finite context windows. Tools that need a protocol. Safety that can’t depend on the model obeying. Tasks too complex for a single invocation. Any team that builds long enough arrives here.


Which kind of system are you building?

The patterns in this guide apply universally, but their weight depends on which seam in the agent ecosystem you’re working on. Read in the order that matches your problem.

If you are building…You care most about…Start with
A domain context substrate (an MCP server that gives any agent structured access to one domain: a codebase, a screen, a system)Deterministic extraction, fixed ontology, behavior contracts installed at the user’s project/tool-protocols, /instructions, /anti-patterns
A personal AI runtime (an agent that the user owns, that runs in the background, with long-running state)Memory architecture, compaction-resident state, hooks, scheduler-gated background work/memory, /enforcement, /multi-agent
A multi-agent shell (an orchestrator over other people’s agents, with chat-platform reach)Adapter patterns, isolated sub-agent tool registries, settings architecture, cost controls/multi-agent, /enforcement, /cost-management

These categories aren’t airtight — many systems blur them. But knowing which one is your load-bearing concern keeps you from over-applying patterns that don’t fit your seam.


The 8 Postulates

These are not suggestions. They are the load-bearing walls of every production agentic system. Violate them and you will rediscover why they exist.

#PostulateWhat to do
1Start with a persistent instruction fileCreate a CLAUDE.md, AGENTS.md, or GEMINI.md before writing any agent config. Cover conventions, stack, testing, git, and security. Keep it under 200 lines.
2Enforce safety outside the promptPut style preferences in the instruction file. Put linting in hooks. Put destructive command blocking in permissions. Never rely on the model remembering a safety rule.
3Budget your context windowReserve 10-15% for instructions, 30-40% for conversation, 20-30% for tool results. Compact at 70%. Clear at 80%. Separate cacheable content from compactable content.
4Build tools on MCPUse .mcp.json for tool connections. 97M+ downloads/month across every major platform. If you need agent-to-agent communication across systems, add A2A — but start with MCP.
5Coordinate through shared stateWithin a system, agents read from and write to shared state — not messages to each other. Between systems or organizations, use messaging protocols (A2A). Default to state; reach for messaging only when you must.
6Decompose before you hit the cliffAgent coherence degrades after extended sessions. The threshold moves with each model generation. Don’t find the limit — stay well under it. Break work into sub-tasks that complete in the safe zone.
7Track cost per task from day oneSet token budgets per session. Route simple work to cheap models. Cache stable prompts. Monitor with alerts at 50%, 75%, and 90% of budget. Cost management is infrastructure, not optimization.
8Add complexity in weekly incrementsWeek 1: instruction file. Week 2: hooks. Week 3: MCP tools. Week 4: skills. Month 2+: sub-agents. If your team has distributed systems experience, you can move faster — but still validate each layer before adding the next.

The Architecture


Who This Is For

RoleWhat you get
Agent developersPatterns for instruction files, hooks, MCP tools, and context management.
Platform engineersMulti-agent architecture, shared state, delegation, and cost controls.
Infrastructure teamsObservability, token accounting, safety enforcement, and production runbooks.
Engineering managersAdoption roadmaps, cost models, and risk frameworks.

Reading Order

SectionKey questions answered
PromptWhat does the agent read at session start? What does the harness compile around it?
ControlHow do you bind the agent’s behavior outside the prompt?
ContextWhat does the agent remember? How do multiple agents coordinate?
InterfaceHow does the agent talk to tools, code, the web, and editors?
OperateHow do you run it in production — cost, observability, credentials, lifecycle?
Anti-PatternsWhat failure looks like — named and citable.

First agent? Start with Prompt → Control. Skip Context until one agent works reliably.

Scaling? Jump to Context and Operate. That’s where the failure modes live.

Production generative AI engineering, applied to the industries, jurisdictions, and use cases buyers actually face.

Solutions · Framing

Solutions · Delivery

Company

Read

Read the whole story
emrox
1 day ago
reply
Hamburg, Germany
Share this story
Delete

Jira IS Turing-Complete

1 Share

Nicolas Seriot

Computation > Jira is Turing-Complete

Building a Minsky Machine in Atlassian Automation
22nd May 2026

Engineering folklore holds that Jira (Atlassian's project-tracking tool) is Turing-complete. Existing claims point vaguely at automation features without exhibiting a reduction. This article supplies a proof, with setup instructions and execution trace.

Mapping the Computational Model

A Minsky register machine needs only two unbounded counters and a finite set of labeled instructions:

  • INC r; goto S
  • DEC r; if r == 0 goto S else goto S'

Or, in plain English:

  • increment register R, then goto some state S
  • decrement register R, if R == 0 goto zero-state S, else goto nonzero-state S'

A Minsky program that adds register A into register B looks like:

1. DEC A; if A == 0 goto 3 else goto 2
2. INC B; goto 1
3. HALT

Minsky proved this model Turing-complete (1967). Exhibiting it in Jira's automation language therefore establishes the reduction. Here is how the model maps onto Jira:

Minsky Machine Jira
Register A Count of linked issues of type Bug
Register B Count of linked issues of type Task
Program Counter Status of a single Epic issue
Dispatch Table Jira Automation rules, one per instruction state
Clock Automation-triggered transitions, or external re-triggering past chain caps

The Epic's status encodes the current instruction. Automation rules inspect the linked-issue counts and decide the next status. INC and DEC are implemented as issue creation and deletion on the appropriate linked-issue type. Conditional branching is implemented as a JQL-conditioned rule.

Implementing Addition

Here is a minimal working implementation using one Epic, five linked issues, and one Automation rule per instruction state (Space Settings > Automation).

1. Create Workflow

Create a Jira Workflow with statuses initial state BACKLOG, then TODO, DEV and PROD. Any state can transition to any other.

Create an Epic in status BACKLOG.

2. Create Rule for TODO

DEC A; if A=0 halt, else goto DEV.

  • Trigger: Epic status changed to TODO.
  • If at least one linked Bug exists: delete one Bug, transition Epic to DEV.
  • Else: transition Epic to PROD (halt).

3. Create Rule for DEV

INC B; goto TODO.

  • Trigger: Epic status changed to DEV.
  • Create a new Task, link it to the Epic.
  • Transition Epic to TODO.

Both rules have "Allow rule to trigger other rules" enabled.

The screenshot below shows the two rules wired into the Epic's workflow.

4. Init Registers

Link 2 Bugs (A=2) and 3 Tasks (B=3) to the Epic.

5. Bootstrap the Machine.

Transition the Epic to TODO to start the cascade. Five transitions:

(2,3) TODO → 
(1,3) DEV  → 
(1,4) TODO → 
(0,4) DEV  → 
(0,5) TODO → 
(0,5) PROD

Recorded on a real *.atlassian.net instance.

The Epic lands in PROD with 0 Bugs and 5 Tasks linked. We've just added 2 + 3 = 5.

Fibonacci in Three States

The reduction above suffices to prove Turing-completeness. In addition to that, Jira's automation language can simplify Minsky operations. Convert Issue Type changes an issue's type instantly: Bug → Story, Story → Task, and so on.

CONVERT is expressible as DEC + INC. It doesn't extend Jira's computational power, but it shrinks the dispatch table dramatically for any move-loop, making non-trivial programs tractable.

Fibonacci as (A, B) → (B, A+B) collapses to three states with three registers (A=Bug, B=Task, C=Story), using TODO, QA (add it to the workflow), and DEV as the three instruction states:

TODO:
    if any linked Task exists:
        CONVERT Task → Story
        INC Bug
        transition to TODO
    else:
        transition to QA

QA:
    if any linked Bug exists:
        CONVERT Bug → Task
        transition to QA
    else:
        transition to DEV

DEV:
    if any linked Story exists:
        CONVERT Story → Bug
        transition to DEV
    else:
        transition to TODO

Initial state A=1, B=1, C=0. The sequence 1, 1, 2, 3, 5, 8, 13, … appears in B (Task count).

Unlike the addition machine, the Fibonacci machine has no halt state. It runs until Jira Cloud's chain-depth cap of 10 triggers, at which point the operator re-triggers the Epic to continue. A single status edit restarts the cascade.

The reduction still holds, the human just supplies the next clock tick. Jira Data Center exposes the same as automation.rule.execution.timeout and related, configurable properties.

Conclusion

Jira's automation language can encode a two-counter machine given unbounded issue creation and rule execution. Every physical computer is finite, so Jira Cloud's finite quotas do not refute the construction. Under that standard convention, Jira is Turing-complete.

So, if complex Jira automations feel like programs, it is because they literally are.

Read the whole story
emrox
2 days ago
reply
Hamburg, Germany
Share this story
Delete
Next Page of Stories