# Henry Chen — Full Site Content

> Agentic engineer · Fullstack developer · Photographer, based in Sydney, New South Wales, Australia. Everything below is generated from the live site, so it never lags what the pages themselves say. Article bodies are included in full.

## Profile

- **Name:** Henry Chen
- **Location:** Sydney, New South Wales, Australia
- **Timezone:** Australia/Sydney
- **Role:** Agentic engineer · Fullstack developer · Photographer
- **Alma maters:** The University of Western Australia; The University of Sydney
- **Website:** https://misoto22.com

## Skills by layer

- **frontend:** React, Tailwind CSS, HTML5, CSS3, Figma, TypeScript, Next.js, JavaScript
- **backend:** Python, Node.js, Java, C
- **data:** PostgreSQL, SQL Server
- **devops:** Docker, GitHub Actions, AWS, Vercel, CI/CD, Linux, Terraform
- **tooling:** Git, VS Code, Agile, Jira, Lightroom, Claude Code, Codex

## Work history

- **Software Engineer**, Inovit Pty Ltd (Jun 2025 – Present, Sydney, NSW) — React, Django, SQL Server, Git, Agile
- **IT and Data Intern**, Path of Hope Foundation (Jul 2024 - Oct 2024, Perth, WA) — ETL, Data Visualization, Web Development, IT Strategy, Data Management
- **Senior Math Tutor**, HD Education (Jul 2019 - Jan 2023, Sydney, NSW) — Curriculum Development, Student Assessment, Educational Technology, Communication, Problem Solving

## Education

- **Master of Information Technology**, The University of Western Australia (2023 - 2024)
- **Bachelor of Computing**, The University of Sydney (2020 - 2022)

## Projects (8)

- [INOVIT Dealer Portal](https://misoto22.com/projects/dealer-portal): A B2B e-commerce platform replacing a legacy .NET ERP — 49 API endpoints, 4-region database routing, cross-repo OpenAPI type generation, and zero-downtime migration on a shared production SQL Server. — Stack: React 19, TypeScript, Django 5, DRF, SQL Server, Ant Design 6
- [EFFICIENT System](https://misoto22.com/projects/efficient-system): A multi-tenant wheels and tyres ERP and commerce platform — one Django backend, a shared React UI kit, five portals, a storefront, a Rust CLI with an MCP server. My layer is the one underneath: an engineering contract of 160 rules that no repository is allowed to copy, and the agent workflows that hold fifteen repositories to it. — Stack: Agent Skills, Claude Code, Python, Django, React
- [SlateCourt - Badminton Venues & Games](https://misoto22.com/projects/slatecourt): A mobile-first PWA for Sydney badminton: a 54-venue directory with live court availability aggregated from the venues' own public booking pages, a game ledger with bill-splitting and a gear log, plus friends, groups and notifications. A personal, non-commercial demo — bilingual, self-hosted, registration by invite code only. — Stack: Next.js 16, TypeScript, FastAPI, Python, PostgreSQL, Redis
- [Personal Website](https://misoto22.com/projects/personal-website): A bilingual portfolio with an AI assistant grounded in the site's own content, privacy-first analytics, blog, photography gallery, and WCAG AAA accessibility — 17 pages, 17 API routes, self-hosted on my own Docker stack, all solo-built. — Stack: Next.js 16, React 19, TypeScript, Tailwind CSS, PostgreSQL + pgvector, Docker
- [Lumia Crystal - E-commerce Platform](https://misoto22.com/projects/lumia-crystal): A modern headless e-commerce platform for crystal jewelry brand. Built with Next.js 15 and Shopify Storefront API, featuring real-time product catalog, shopping cart, search with filtering, and responsive design with OKLCH color system. — Stack: Next.js, TypeScript, Tailwind CSS, Shopify API, React
- **Smart Vision Hat**: An IoT-based wearable device designed to assist visually impaired individuals. Built with Raspberry Pi and a YOLOv8 object detection model, it provides real-time audio feedback and emergency alerts. — Stack: Raspberry Pi, Python, Flask, Firebase, YOLOv8, OpenCV
- **Parallel Fish School Search**: A high-performance computing project simulating fish school behavior using C, OpenMP, and MPI. Achieved massive speedups on the Setonix supercomputer through thread/process-level optimization. — Stack: C, OpenMP, MPI, Bash
- **Australia EOI Points Calculator**: A modern web application for calculating points for Australian Expression of Interest (EOI) for skilled migration visas. Features real-time calculation, bilingual support, responsive design, and dark/light mode. — Stack: Next.js, TypeScript, Tailwind CSS, i18next, Framer Motion

## Blog posts (8)

### Where Should My Data Live?

https://misoto22.com/blog/where-should-my-data-live · 2026 · 12 min read · Dev

> What limits how well we use AI stopped being model capability a while ago — it's that the data about me isn't mine. Any product can be swapped out; the only thing keeping you in place is the context that has settled inside it. I built a personal database to fix that, and building it showed me it's only a mirror.

> What limits how well we use AI stopped being model capability a while ago. It's that the data about me isn't mine.

#### The AI Doesn't Know Me

I use LLMs every day, and none of them know who I am.

A model can help me write code, analyze an architecture, rework my résumé — and once it has tools, carry out real work on my behalf. But every time I open a new provider, I have to explain myself from scratch: which languages I use, what I do for a living, which projects I run, how I like code organized, which technical constraints can't be waved away.

At first I treated this as an annoyance. Then I realized it was the ceiling.

#### The Bottleneck Has Moved Many Times — Never on My Side

On the July 28th episode of [Invest Like the Best](https://colossus.com/episode/how-to-make-an-abundant-future/), Patrick O'Shaughnessy and Sam Altman spent close to an hour on where AI actually gets stuck. One stretch is about exactly that (22:27): over the past few years the binding constraint kept rotating — sometimes research ideas, sometimes compute, sometimes data, then back to compute again.

That's the bottleneck as seen by the people building the models.

The bottleneck on my side has never rotated once.

Models jump a tier every few months, inference costs fall by an order of magnitude, the capability frontier keeps expanding. And every time I open a new conversation, I still start by explaining who I am. Progress on the model side is exponential. My side restarts at zero, every time.

So the real question isn't "is the AI smart enough." It's: how many facts about me can a sufficiently smart system actually reach?

The answer is: very few, and none of them are in my custody.

#### Every Product Keeps Its Own Copy of Me

Providers usually offer somewhere to put this, something like `things about me`. Write it in and the problem looks solved. But once I started maintaining it seriously, I realized I was only building one more copy of the data about me.

My personal site stores my work history, skills, projects and bio. The app that tailors my résumé needs the same data, so it imports another copy. The LLM provider keeps its own profile, and the next product will ask for a profile of its own.

These systems aren't holding different data. They're holding different projections of the same person. The problem is that each projection gradually starts acting like the source.

The moment data gets copied, drift follows. I update a piece of work history — which one do I edit first? A project changes its stack; which system holds the correct version now? And when a preference the provider memorized goes stale, I may not even know an old fact is still shaping its answers.

At first I read this as a context problem. Maybe I needed a better system prompt, or a JSON file I could import into different models. Later I understood that this was just another way of copying.

Context is the shape data takes when some consumer uses it — it is not the data itself. A prompt, a RAG document, a résumé page and a site API can all be ways of consuming it, but none of them should own a separate source of truth.

#### What You Can't Walk Away From Isn't the Product

The same conversation gets to a sharper question: once intelligence itself is a pure commodity, which moats are left?

Altman's ranking runs roughly like this. Compute fleet scale is the most durable. The stickiness that comes from workflows and integrations is next. Brand and distribution sit in the middle — he calls ChatGPT's bundling advantage "very very tiny." And **the product itself is the weakest of them**: if someone builds something better, people will move.

I sat with that for a long time, because it says from the other side exactly what I'd been circling:

> If a product can be swapped out at any time, the only thing keeping you in place is the context that has settled inside it.

Altman is saying it from the company's side: the product will get replaced, so the moat has to live somewhere else. From my side it's the same fact under a different name — that isn't a moat, it's a lock.

The more context I accumulate inside a platform, the deeper its moat and the less able I am to leave. And that context was my life to begin with: the projects I've built, the notes I've written, the résumés I've reworked, the technical preferences I've explained a hundred times.

So the question isn't "can the AI remember me." It's: **who owns the thing that remembers me?**

Context is the output; ownership is the precondition. Whoever holds the data decides who may use that context, whether it can be carried out, and whether it still exists on the day you replace the product.

#### The Data Boundary Runs the Wrong Way

Large platforms today usually organize data like this:

> one domain × many people

A health platform collects health data from many people, a music platform collects listening history from many people, a hiring platform collects careers from many people. Their domain boundaries are perfectly clear, but one person's data is sliced vertically and left inside separate companies.

What personal AI needs is the opposite:

> one person × many domains

Career, knowledge, photos, music, health, travel and browsing history belong to one person. They may arrive from different products, but the product shouldn't remain the final boundary forever.

The direction sounds obvious, and it's easy to underestimate how hard it is. I tried it.

#### I Built One, and Building It Showed Me It's Only a Mirror

I wrote myself a personal database called Kioku: one PostgreSQL holding career, knowledge, photos, music, health and browsing history, read by my site over an API and by LLM clients over MCP. It does solve the copy-and-drift problem — there's exactly one source now, and everything else is derived.

But once it was working I could see plainly what it is: **right now it's only a mirror and a sync of other platforms' data.**

The data still originates in someone else's system. Listening history comes from a streaming service, health data from a phone and a watch, code activity from a Git host, reading and viewing history from their respective apps. What Kioku does is pull it in on a schedule, reconcile it, and store it. What I own is a reliable copy, not the origin.

That has very practical consequences. When a platform changes its export interface, my side breaks. When a platform offers no incremental interface, all I get is a one-off archive. And what a platform never recorded in the first place will never appear on my side at all. Ownership moved to me on paper; the end that produces the data did not.

I don't regret building it. Building that layer is exactly what showed me where the problem actually sits: **storing was never the hard part. The origin is.**

#### There Is Still No Eye

Pulling scattered data back only solves half of it.

Almost everything I can reclaim is a digital footprint: what I clicked, what I bought, what I listened to, what I committed. That's the version of me a platform wrote down — a second-hand projection.

What I did in the physical world, nothing is recording. Who I met today, what we talked about, what I saw, what I was thinking at the time — that layer explains me better than any of the rest, and it is entirely missing. No device sees the real world the way my eyes do and keeps what it takes in.

In the same conversation, Altman spends real time on robotics (35:33): he expects a ChatGPT moment for robotics in the next two or three years, and argues it would be very bad if the remaining role for humans were to act as actuators for cloud-side AI. That's about giving cloud intelligence a pair of hands.

But personal AI isn't missing hands. It's missing eyes.

That gap will get filled, and most likely by a device. What I care about isn't when it arrives — it's who the data belongs to by default when it does. If the most intimate, most explanatory layer of a person's life sits in somebody else's database from the first second it is recorded, then every argument about data ownership up to that point was tidying a smaller room.

Which is why this isn't a "later" problem. It also isn't a "record more" problem — the gap isn't that too little gets captured, it's **whose hands the intake is in**. The boundary has to be drawn before the data exists, not negotiated back after it's already sitting somewhere else.

#### People Have Walked This Road Before

"A database for one person" is not a new idea.

Vannevar Bush proposed the Memex in 1945, imagining an external memory that could store and link a person's own material. In 2001, Gordon Bell, Jim Gemmell and Roger Lueder started [MyLifeBits](https://www.microsoft.com/en-us/research/project/mylifebits/) at Microsoft Research: a SQL-based "personal database for everything," holding documents, mail, photos, web pages, recordings and sensor-captured life logs, and exploring full-text search, annotation, linking and similarity.

Later, Personal Data Stores and Solid approached the same tension from another direction: data shouldn't be bound inside applications; applications should access storage the person chooses, once authorized. As of 2026, the W3C is still pushing [Linked Web Storage](https://www.w3.org/TR/lws10-core/) forward, trying to standardize secure, authorized application access to external storage.

There is also a more pragmatic engineering line. [HPI](https://github.com/karlicoss/HPI) doesn't wait for the whole internet to agree on one protocol first; it uses export files and adapters to turn chat, music, browsing, location and health data into interfaces a personal program can query.

These projects aren't one continuous lineage, but they keep answering the same question: when a person's digital life spans many applications, can the data be reorganized around that person?

#### Why It Never Became the Internet's Default Architecture

The idea keeps reappearing, and it keeps stopping at research projects, open-source tools and a handful of self-hosting users. The obstacle was never disk space.

First, platforms have little incentive to hand over continuous, complete, machine-readable data. Even where an export exists, what you get is often a one-off archive rather than a stable incremental interface. Being able to retrieve your data doesn't mean you can reliably sync it — which is exactly the wall I ran into.

Second, data from different sources is hard to unify automatically. The same place, contact or activity may share no identifier across systems; timestamps carry different timezone assumptions; two devices may record the same workout twice. Putting the files in one directory is easy. Making them one database in any semantic sense is much harder.

Third, centralization widens the blast radius. Scattered data is inconvenient to use, but aggregated health, location, career and browsing history becomes a concentrated loss the moment one authorization goes wrong. Data sovereignty can't only mean "everything is on my side" — it has to include least privilege, auditing, backups and recoverability.

Finally, an ordinary person shouldn't have to become a database administrator in order to own their own data. For a developer, one PostgreSQL per person is acceptable. If this model is going to serve more people, the realistic shape is a hosted personal space or a logically isolated data account — not a server everyone has to maintain.

#### AI Turns This From Worthwhile Into Urgent

For most of its history, a cross-domain personal database lacked a strong enough everyday consumer. Charting your health or searching old messages is useful, but not necessarily enough to make an entire ecosystem move its data boundaries.

Personal AI changed that. An agent that helps me plan work, rework a résumé, review a project or make sense of how I've been living can't know only one domain. It has to reach across career, knowledge, preferences and history — while each task gets only the slice that task requires. It's the first consumer that naturally needs all of it, and the first time data ownership stops being a matter of principle and starts setting the ceiling on what the system can do.

The external conditions are shifting too. The EU [Data Act](https://digital-strategy.ec.europa.eu/en/factpages/data-act-explained) already pushes connected products to give users the data they generate along with the metadata needed to interpret it, and the DMA is pushing large platforms toward timelier portability interfaces. Those rules won't create a personal database on their own, but they are turning "getting my data back" from a reverse-engineering exercise into a capability you can actually demand.

Still, AI only makes the demand stronger. It hasn't solved data governance for us.

An LLM cannot be the single source of truth. A vector index doesn't preserve exact quantities, and a relationship the model infers is not a fact. An agent may suggest that two records could be related, but it shouldn't rewrite my work history because of one generation. Natural language makes data easier to use; provenance, permission and certainty still have to be expressed explicitly by the system.

#### Intelligence Will Commoditize. Context Won't.

If intelligence really does become a pure commodity, then the one thing that can't be commoditized is the context about me. It decides what the same model can do in my hands, and it decides what I get to carry out when I leave.

What I hold today is a mirror. It beats being scattered across a dozen platforms, but it isn't the destination — the origin still sits with someone else, and the physical-world layer has no intake at all.

So this isn't a solution. It's a statement of where the problem actually is: what limits how far we can push AI isn't the model, it's who owns the data about us.

Models will keep getting stronger. This question won't resolve itself.

### From a Website Assistant to a Personal Knowledge Interface

https://misoto22.com/blog/building-a-rag-chatbot · 2026 · 11 min read · AI

> What began as a cited Q&A assistant for this site became two interfaces over Kioku: a public RAG path for visitors and a private MCP tool surface for my AI, sharing one source of truth without sharing permissions.

The first version of the assistant on this site had a narrow job: let a visitor ask a question about my work and get an answer grounded in something I had actually published. That sounds like a chatbot feature. It eventually forced a more useful question: if an AI is going to work with my data, where should the intelligence around that data live?

My answer is now Kioku, the personal data system behind this site. The public assistant is still there, but it has become one client of a larger interface. My own AI clients use a separate MCP connection to search private knowledge, query structured records, capture new material, and start controlled workflows. Both paths meet the same source of truth. They do not share the same permissions.

That distinction matters more to me than any particular model or vector database. Models will change. The boundary around personal data should survive them.

#### It started as a website Q&A assistant

A language model knows a great deal about public subjects, but it does not automatically know what I built last month, why I made an architectural choice, or which details on my résumé are current. Asking it to answer from memory produces fluent guesses. Pasting every page into every prompt is expensive, slow, and soon becomes impossible as the site grows.

Retrieval-augmented generation, usually shortened to RAG, is a practical way around that problem. Before asking a model to answer, the system searches a collection I control and supplies a small set of relevant passages. The model is not being retrained on my site. It receives better context for this particular question.

Embeddings make that search less literal. They turn text into coordinates that roughly preserve meaning, so a question about “career changes” can find a passage that says “moved from one role to another” even when the wording differs. PostgreSQL remains the main database, while pgvector adds the similarity search needed to compare those coordinates. The model then writes an answer from the retrieved passages and attaches links back to the source pages.

The citation is part of the product, not decoration. A visitor should be able to move from an answer to the page that supports it. When the system cannot find enough public evidence, a restrained “I don’t know” is better than an impressive invention.

The visible interaction stays simple. A question goes to `/ask`; the answer appears progressively; relevant sources arrive with it. Server-Sent Events, or SSE, carry that stream to the browser so the user does not wait for a complete response before seeing anything. Complexity exists behind the screen, but the contract with the visitor is just a grounded conversation with inspectable sources.

#### Why RAG moved into Kioku

The earliest design treated retrieval as a feature of the website. That was reasonable while the website was the only place using it. It became awkward as soon as Kioku began holding more of the underlying material: articles, projects, saved knowledge, and structured parts of my personal history.

If the site owns the index, every new client has to depend on the site or reproduce its retrieval rules. Changes to visibility must be copied into more than one place. A background import can update Kioku while the website still searches yesterday’s view. The component presenting the data gradually becomes responsible for understanding all of it.

I moved the corpus, indexing, retrieval, and answer orchestration into Kioku instead. The site now owns what is specific to the site: checking that traffic came through the expected edge, limiting bursts from a visitor, and passing the response stream to the browser. Kioku validates the question, searches public material, runs the bounded agent loop, calls the model through a replaceable LLM gateway, and shapes the citations. The website is a client, not a second knowledge backend.

The recent backend refactor made that separation explicit. HTTP endpoints, MCP, and command-line jobs are sibling adapters. They handle their own authentication and response formats, then call the same protocol-neutral application use cases. MCP does not secretly call the REST API, and the REST API does not contain a private copy of the business logic. This is a conventional application boundary, but it has an important effect: the rules around the data can be reused without tying them to one transport.

```diagram
{
  "caption": "One source of truth, reached through two enforceable access paths.",
  "direction": "column",
  "nodes": [
    {
      "id": "callers",
      "label": "Two callers, two paths",
      "note": "same data, different permissions",
      "direction": "column",
      "children": [
        {
          "id": "v",
          "label": "Visitor",
          "note": "no account",
          "direction": "row",
          "children": [
            {
              "id": "v1",
              "label": "misoto22.com /ask"
            },
            {
              "id": "v2",
              "label": "Public retrieval",
              "note": "public-only view"
            },
            {
              "id": "v3",
              "label": "Cited answer"
            }
          ]
        },
        {
          "id": "o",
          "label": "Owner's AI",
          "note": "authenticated",
          "direction": "row",
          "children": [
            {
              "id": "o1",
              "label": "Kioku MCP"
            },
            {
              "id": "o2",
              "label": "Private search",
              "note": "separate DB role"
            },
            {
              "id": "o3",
              "label": "Structured tools"
            }
          ]
        }
      ]
    },
    {
      "id": "core",
      "label": "Shared Kioku services and data",
      "note": "one source of truth",
      "accent": true,
      "footnote": "no include_private switch — the boundary is below the conversation"
    }
  ],
  "edges": [
    {
      "from": "v1",
      "to": "v2"
    },
    {
      "from": "v2",
      "to": "v3"
    },
    {
      "from": "o1",
      "to": "o2"
    },
    {
      "from": "o2",
      "to": "o3"
    },
    {
      "from": "callers",
      "to": "core",
      "label": "both read"
    }
  ]
}
```

#### One data source, two read boundaries

“The public assistant only asks for public results” is not a security model. Prompts can be misunderstood, application flags can be passed incorrectly, and future code can accidentally broaden a query. I wanted the public/private distinction to be enforced below the conversational layer.

Kioku therefore has two retrieval paths. Public `/ask` reads a database view and role that can see only public chunks. The owner-facing knowledge search reads a different private index through a separate database capability. The public function does not accept an `include_private` switch. There is no magic value that turns a visitor query into an owner query.

The two paths still share useful machinery: text is divided into stable passages, embeddings are generated consistently, and the same PostgreSQL installation stores the indexes. Sharing infrastructure is not the same as sharing authority. The database decides which material is reachable before the model sees any context.

This makes the architecture easier to reason about. The public model can be prompted to ignore private information, but it does not receive private information in the first place. If someone tries to induce it to reveal a private note, retrieval has nothing private to return. Kioku’s bilingual quality checks include these privacy-inducement cases alongside ordinary questions, citation checks, and no-answer behavior. Evaluation does not replace access control, but it catches regressions in the experience built on top of it.

The same principle applies to the outer layers. The site can reject abusive traffic before it spends embedding or generation quota. Kioku applies its own global admission and cost limits because it is the component that actually talks to those providers. Each layer owns the limit it can enforce accurately.

This separation also keeps future clients honest. A mobile app, another website, or a local tool could use Kioku later, but it would still need an explicit identity and capability. “Uses the same data” no longer implies “may see the same data.”

#### MCP lets AI do more than search

RAG is useful when the answer lives in prose. It is less suitable for questions that have an exact, structured answer. “What did I write about database ownership?” is a search problem. “How many flights did I record last year?” or “Which projects use Rust?” should be answered from records and relationships, not by asking a model to estimate from a handful of semantically similar passages.

That is where MCP changed the shape of the project. The Model Context Protocol gives an AI client a described set of tools rather than one large prompt containing my data. Through Kioku’s private MCP endpoint, my AI can search knowledge, read authoritative records, capture a new note, connect related entries, or start a workflow. The useful point is not the number of tools. It is that each operation has a specific contract and a specific permission.

Search itself combines two different strengths. Semantic search finds related meaning even when the words differ. Literal search remains better for an exact name, identifier, or phrase. Kioku fuses both rather than forcing the model to guess which search mode will work before it has seen the answer. For numerical or categorical questions, a structured tool can bypass embeddings entirely and query the relevant domain.

This creates a healthier role for the model. It chooses and combines capabilities, but it does not become the system of record. A database query establishes an exact fact. Retrieval supplies passages for explanation. The model turns those results into a useful response. Each part does the job it is suited to do.

MCP also makes Kioku useful during work, not only after publication. If I discover a useful article while discussing a problem with an AI, the conversation can capture it into the knowledge base instead of leaving it trapped in chat history. It can later suggest related entries, while creating the actual link remains a deliberate write. Similar patterns apply to personal records and longer-running jobs.

The private client is powerful, but it is not treated as an unrestricted owner session. It has its own revocable credential and database role. Tool definitions declare whether an operation reads, writes, starts work, or has an external effect. Calls are checked against capabilities, potentially dangerous actions can require approval, and writes are auditable. The model is an agent acting for me; it is not me.

#### Keeping the data fresh and private

A knowledge interface becomes unreliable if its search index is a forgotten snapshot. Rebuilding every embedding after every edit would keep it correct, but it would waste time and provider quota. Waiting for the next deployment would make newly captured material feel broken.

Kioku tracks a content hash for each indexed source. When text or visibility changes, only the affected passages are regenerated. New captures schedule follow-up indexing so they can become searchable without a full release. A complete rebuild still exists as a repair and verification path, not as the normal way every small change reaches search.

Visibility is part of the indexed identity. Changing a note from private to public is a meaningful content change even if not one word of the note changed. Renaming an addressable page also removes the old identity before the new one is promoted, preventing stale public results from lingering behind a former URL.

Freshness is only half of trust. Capture defaults to private. The MCP agent cannot publish content merely because it can create it. Public retrieval is served by the restricted path described above. Operations with effects can be gated by confirmation, and audit records make writes reviewable later. These are deliberately ordinary controls around an AI-shaped interface.

The model providers are kept outside the ownership boundary. Kioku sends the minimum context needed for an embedding or answer, through gateways that can be replaced as models and prices change. The durable copy, visibility rules, links, revisions, and structured records remain in my database. Switching a model should be an integration change, not a data migration.

There are still trade-offs. Semantic indexes can miss the right passage. Streaming generation can fail partway through. A useful personal system will always contain information that should never be public. The goal is not to make those risks disappear behind an “AI” label; it is to give each one a visible boundary, a fallback, and a place to test it.

#### What I built was not merely a chatbot

The `/ask` assistant is still the most visible part of this work, and it remains intentionally small: ask about my public work, receive a sourced answer, follow the links if you want the full context. But its backend is no longer designed around a chat box.

Kioku now exposes two views of the same personal source of truth. The public view is narrow, cited, and unable to reach private material. The owner view can search more broadly, use exact domain tools, capture knowledge, and coordinate controlled work. Their shared application layer prevents duplicated behavior; their separate database capabilities prevent shared authority.

That is the roadmap I care about. Not recording everything for its own sake, and not adding tools simply because a model can call them. The aim is a durable personal knowledge interface: data I can keep, rules I can inspect, and multiple AI clients that remain replaceable.

The implementation will continue to evolve in [Kioku](https://github.com/Misoto22/kioku), while the public client lives in the [misoto22-site repository](https://github.com/Misoto22/misoto22-site). The lasting design decision is simpler than either codebase: put intelligence close to the data, and put permissions closer still.

### Ghostty + Tmux: One Tab Per Project, One Pane for Claude, One for the App

https://misoto22.com/blog/my-ghostty-setup · 2026 · 7 min read · Dev

> How I use Ghostty tabs as project slots and tmux as the split manager — why tabs stay visible, why splits live in tmux, and the config choices that make eight projects open at once stop feeling like eight projects open at once.

The best terminal isn't the one with the most features. It's the one that lets you open eight projects at once without your brain leaking out of your ears.

What I landed on is boring on purpose: Ghostty hosts the window, macOS tabs slot projects, and every project lives inside a tmux session where splits hold the two things that matter — Claude Code on one side, the app running on the other. No fancy Ghostty splits, no workspace manager, no tmuxinator config file I stop understanding after three months.

This post is a walkthrough of that setup and the config choices behind it. The full `~/.config/ghostty/config` lives at [github.com/Misoto22/ghostty-config](https://github.com/Misoto22/ghostty-config) if you want to grab it as-is.

---

#### The Mental Model

Three rules drive every decision:

| Rule | What it means |
|---|---|
| **Tab = Project** | Every Ghostty tab is pinned to one project. Switching project means `⌘+1/2/3`, not hunting a window. |
| **Tmux = Role** | Inside a tab, tmux splits hold fixed roles: left pane runs Claude Code, right pane runs the app / tests / logs. |
| **Ghostty is just the host** | Ghostty does fonts, colors, tabs, keybinds. It does not do splits or session persistence. That is tmux's job. |

Why splits in tmux instead of Ghostty's native splits? **Detach, reattach, survive an SSH session.** Reboot the machine, `tmux attach` brings everything back. Claude Code on the left picks up with `claude --continue`, the app on the right still has its history. Ghostty's native splits can't do that.

---

#### Shortcut Cheat Sheet

**Ghostty — windows and tabs only:**

| Key | Action |
|---|---|
| `⌘+T` | New tab (a new project slot) |
| `⌘+W` | Close current surface |
| `⌘+1~9` | Jump to Nth tab |
| `⌘+Shift+Enter` | Toggle fullscreen |
| `⌘+K` | Clear screen |
| `⌘+` `` ` `` | Quick Terminal (global hotkey) |
| `⌘+D` / `⌘+Shift+D` | Native split (fallback when I haven't started tmux) |

**Tmux — everything inside a project.** I use `Ctrl+a` as prefix (personal preference; stock `Ctrl+b` works fine):

| Key | Action |
|---|---|
| `prefix + \|` | Split left/right |
| `prefix + -` | Split top/bottom |
| `prefix + h/j/k/l` | Move between panes |
| `prefix + z` | Zoom current pane |
| `prefix + d` | Detach (session keeps running) |
| `tmux a -t <name>` | Re-attach |

---

#### The Config, Section by Section

##### Font: JetBrains Mono Nerd Font

```text
font-family = JetBrainsMono Nerd Font
font-size = 14
font-thicken = false
```

Why not Maple Mono? I rarely need CJK monospace, and I prefer JetBrains Mono's proportions and ligatures. The Nerd Font build ships with all the icons tmux's status bar and starship prompt need — one font, no fallback chain.

`font-thicken = false` because I don't want to compensate for macOS's rendering. The native, slightly thinner weight reads cleaner against a high-opacity dark background.

##### Theme: Cobalt Next Dark, One and Done

```text
theme = Cobalt Next Dark
```

No `light:…,dark:…` auto-switching. Reason: I'm almost never coding in a light terminal. For the six-to-eight hours I'm in it, the room lighting is constant. Switching themes only disturbs the color associations I've built up in the tmux status bar. One fixed scheme, one less thing to track.

Cobalt Next Dark has a bit more contrast than Catppuccin Mocha — diff reds pop harder, which matters more to me than café aesthetics.

##### Window: Keep the Tabs Visible

```text
background-opacity = 0.96
macos-titlebar-style = tabs
window-padding-x = 12
window-padding-y = 12
```

Two disagreements with the popular "disappearing terminal" setup:

- **`background-opacity = 0.96`** — not 0.88. I have a tmux status bar at the bottom. Too much transparency and the wallpaper bleeds through the status bar text. 0.96 leaves a hint of depth without sacrificing readability. Function first, aesthetics second.
- **`macos-titlebar-style = tabs`** — not `hidden`. Tabs *are* my project switcher. I need to see them. `⌘+1/2/3` is muscle memory, but visually confirming "I'm in the right project" happens every few minutes. The pixels I'd save by hiding the title bar aren't worth paying for that confirmation.

##### Cursor and Clipboard

```text
cursor-style = block
cursor-style-blink = false
copy-on-select = clipboard
scrollback-limit = 100000
```

Block cursor isn't nostalgia — I spend a lot of time in tmux + vim, and a block cursor matches vim's normal-mode cursor visually. One less cognitive switch.

`scrollback-limit = 100000` — two orders of magnitude smaller than some guides suggest. Enough to scroll through one build log. If I actually need to study output later, I pipe to `| tee build.log`. Scrollback shouldn't pretend to be a logging system.

##### macOS Integration

```text
macos-option-as-alt = true
quit-after-last-window-closed = true
confirm-close-surface = false
```

`macos-option-as-alt = true` is non-negotiable. Shell life needs `Option+←/→` for word-jump and `Option+Backspace` for word-delete. Without it, half of readline is dead.

##### Quick Terminal: The Escape Hatch

```text
keybind = global:cmd+grave_accent=toggle_quick_terminal
quick-terminal-position = top
quick-terminal-animation-duration = 0.1
```

One purpose: I'm in VS Code / a browser / Notion, and I suddenly need `git status` or `pbpaste | wc -l`. `⌘+` `` ` `` drops a terminal from the top, I type, I hit it again to hide. **No new tab, no polluted project context.**

##### Native Splits: Kept, Rarely Used

```text
keybind = cmd+d=new_split:right
keybind = cmd+shift+d=new_split:down
keybind = cmd+opt+left/right/up/down=goto_split:...
```

These stay for the case where I open a throwaway tab and don't want to start a tmux session just to split it. Day-to-day, entering a project tab means `tmux a` or `tmux new -s <project>`, and from there splits are tmux's job.

---

#### A Project's Daily Flow

**Opening one:**

```bash
⌘+T                             # new tab
cd ~/code/efision
tmux new -s efision             # or `tmux a -t efision`
```

**The standard layout inside tmux:**

```diagram
{
  "caption": "One window, two panes — the agent on the left, whatever it is driving on the right.",
  "nodes": [
    {
      "id": "window",
      "label": "Ghostty",
      "note": "fullscreen",
      "footnote": "one tmux window",
      "children": [
        {
          "label": "Claude Code",
          "note": "claude"
        },
        {
          "label": "cargo run",
          "note": "or pytest, or tail -f"
        }
      ]
    }
  ]
}
```

Claude Code on the left. On the right, whatever the project's long-running companion is:

- Rust project: `cargo watch -x run`
- Django: `uv run manage.py runserver`
- Frontend: `pnpm dev`

If I need a third pane for an ad-hoc `git diff` or `psql`, `prefix + -` cuts one below.

**Switching projects:** `⌘+2` → a different tab → that project's tmux session is right where I left it.

**Shutdown / Ghostty crash:** shrug. The tmux server is still running (unless the machine rebooted). Reopen Ghostty, `tmux a -t <name>` in each tab, and **every pane comes back, Claude Code included**. Then `claude --continue` to resume the conversation.

---

#### Scenario Cheat Sheet

| Scenario | Action |
|---|---|
| Switch project | `⌘+1/2/3` |
| Add a pane to tail a log | `prefix + -` |
| Claude's output overflows | `prefix + z` to zoom the pane |
| Run a quick command without polluting the project | `⌘+` `` ` `` Quick Terminal |
| Resume after a reboot | `tmux a` + `claude --continue` |
| One-off experiment | `⌘+T` and skip tmux entirely |

---

#### Why This Beats Native Splits

| Dimension | Ghostty native splits | Tmux splits |
|---|---|---|
| Persistence | Lost on restart | `tmux a` brings them back |
| SSH consistency | Can't use remotely | Same muscle memory local and remote |
| Scripted layouts | Manual drag | `tmuxinator` / shell script for one-command setup |
| Naming / switching | None | `prefix + $` renames the window |

The cost is one prefix key and one layer of abstraction. Worth it for me.

---

#### Related

- **[My Ghostty config on GitHub](https://github.com/Misoto22/ghostty-config)** — full `config` file, copy-paste ready
- [Ghostty](https://ghostty.org)
- [tmux](https://github.com/tmux/tmux)
- [JetBrains Mono Nerd Font](https://www.nerdfonts.com)
- Cobalt Next theme — ships with Ghostty; `ghostty +list-themes | grep -i cobalt`

### Three Ways to Run Claude Code in Parallel

https://misoto22.com/blog/claude-code-parallel-development · 2026 · 7 min read · AI

> Worktrees, agent teams, and in-conversation subagents — when to reach for each, how they actually work under the hood, and the tradeoffs I've hit using them day-to-day.

The more I lean on Claude Code, the more the bottleneck shifts from "can it write this" to "how many of these can it write at once." A backend module, a frontend feature, and a docs pass are usually independent enough to run in parallel — the trick is keeping the file systems, branches, and contexts from stepping on each other.

Claude Code gives you three ways to do this, and they aren't interchangeable. This post is a tour of all three, what they actually do under the hood, and the rules I use to pick between them.

---

#### 1. `claude --worktree` — multiple terminals, one repo

The simplest and most predictable. Each terminal launches its own Claude session against its own git worktree.

```bash
# Terminal 1
claude --worktree feat-dashboard

# Terminal 2
claude -w feat-customers   # -w is the short form

# Terminal 3
claude -w feat-assets
```

What actually happens: Claude creates a worktree at `.claude/worktrees/<name>/` and checks out a new branch called `worktree-<name>`. The base is whatever your local `origin/HEAD` points at — **not your current branch**. If your repo's default branch changed on GitHub and you haven't re-synced, you'll branch from a stale ref. Fix it with:

```bash
git remote set-head origin -a
```

Add `.claude/worktrees/` to your `.gitignore` so the worktree directories don't show up as untracked files in your main checkout.

**Copying ignored files.** A fresh worktree has no `.env` or local config, because those are gitignored. Create a `.worktreeinclude` at the repo root with gitignore-style patterns, and Claude copies matching files into every new worktree:

```
.env
.env.local
config/secrets.json
```

Only files that are both matched and gitignored get copied, so tracked files are never duplicated.

**Cleanup.** When you exit a session with no changes, the worktree and its branch are removed automatically. If there are commits or dirty files, Claude prompts you to keep or discard. For anything created outside a session, use `git worktree list` and `git worktree remove` directly.

I reach for this 80% of the time. Each session stays fully isolated, I can jump between terminals the way I'd tab between editors, and there's zero coordination overhead.

---

#### 2. Agent Teams — multiple Claudes that talk to each other

Agent teams are the newer, more ambitious mode. One session is the **lead**; it spawns teammates that each get their own context window and can message each other directly, not just report back to the lead. Requires **Claude Code v2.1.32 or later**, and it's experimental — opt in by adding this to `settings.json` or your shell:

```json
{
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1"
  }
}
```

Then just describe the team in natural language:

```
Create an agent team to build the dealer portal. Spawn three teammates:
- one for the dashboard backend (apps/dashboard/)
- one for the customers module (apps/customers/)
- one for the assets module (apps/assets/)
Have them share findings on shared types before touching overlapping files.
```

**Display modes.** There are two:

- **In-process** — all teammates run inside the lead's terminal. `Shift+Down` cycles through them; `Enter` drops you into a teammate's session; `Esc` interrupts their current turn; `Ctrl+T` toggles the shared task list. Works in any terminal.
- **Split panes** — each teammate in its own pane. Requires either **tmux** or **iTerm2 with the `it2` CLI** (and the Python API enabled in iTerm2 settings). `tmux -CC` inside iTerm2 is the recommended combo on macOS.

Default is `"auto"` — split panes if you're already inside a tmux session, otherwise in-process. Override globally via `teammateMode` in `~/.claude.json`, or per-session with `claude --teammate-mode in-process`.

**What's actually shared.** A task list with dependency tracking (blocked tasks auto-unblock when their parents finish), and a mailbox so teammates can `message` one peer or `broadcast` to all. Teammates load the same project context as a regular session — `CLAUDE.md`, MCP servers, skills — but **not** the lead's conversation history. Put task-specific context in the spawn prompt.

**Known sharp edges.** Session resumption doesn't restore in-process teammates. Task status sometimes lags (teammates forget to mark things complete, blocking dependents). Only one team per lead, no nested teams, and the lead is fixed for the team's lifetime. Split-pane mode doesn't work in VS Code's integrated terminal, Windows Terminal, or Ghostty.

Where it earns its keep is **research and adversarial review** — parallel investigators trying to disprove each other's hypotheses converge on root causes faster than a single agent that anchors on the first plausible explanation. For implementation work where teammates need to chat, it's genuinely powerful; for tasks that are really just "do these three things," worktrees are cheaper.

---

#### 3. In-conversation subagents

The lightest option. Inside a single conversation, tell Claude to spawn parallel subagents:

```
Build these three modules in parallel:

Module A — Dashboard: implement the KPI stats API…
Module B — Customer profile: GET/PUT /api/customers/profile/…
Module C — Assets: tag filtering and detail view…
```

Claude spawns each subagent with its own context window, and they report results back to the main conversation. They don't talk to each other. If you want them in isolated worktrees too, add `isolation: worktree` to the subagent's frontmatter — each gets its own worktree, auto-cleaned when the subagent finishes with no changes.

Subagents are cheap because only summaries come back to the main context. They're also the most fragile — if a subagent stalls or goes off the rails, you have less leverage to redirect than you do with a teammate you can `Shift+Down` into.

---

#### The comparison that actually matters

|                | `--worktree` | Agent Teams | In-conversation subagents |
| -------------- | ------------ | ----------- | ------------------------- |
| Communication  | None — fully independent | Teammates message each other + lead | Only report to main session |
| Coordination   | Manual (you switch terminals) | Shared task list, auto-dependency resolution | Lead session delegates manually |
| Token cost     | Medium (independent sessions) | **High** (each teammate is a full instance) | Low (summaries only) |
| Setup          | None | `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`, v2.1.32+ | None |
| Best for       | Independent modules, long-running work | Adversarial review, cross-layer features, hypothesis debugging | Quick parallel lookups or focused builds |
| Biggest risk   | You forget which terminal is which | Teammates go idle silently, task status lags | Subagent stalls with no recovery |

---

#### Rules I actually follow

**Parallelize when:**
- Modules live in separate directories (`apps/dashboard/`, `apps/customers/`, `apps/assets/`).
- Work spans frontend + backend and each half has its own repo or package.
- You're doing a parallel code review with distinct lenses — security, performance, test coverage.

**Don't parallelize when:**
- More than one agent will touch the same shared file (`shared/types/`, top-level `urls.py`, a monolithic schema).
- There's a real dependency chain — B needs A's API shape to exist first.
- A database migration is involved. Let one session own it; worktrees share the same database, and two agents racing migrations is pain you don't need.

**Merge strategy** when the parallel branches come back:

1. Merge the foundational branch first (the one others implicitly depend on).
2. Merge the rest one at a time, running tests between each.
3. Resolve conflicts by hand, or drop back into Claude Code with both branches in context and let it do a three-way read.

---

#### A concrete example

The dealer portal rewrite I did recently, end-to-end:

```bash
# Terminal 1 — dashboard backend
claude -w feat-dashboard
> Under apps/dashboard/, implement KPI aggregates: recent orders, total
> revenue, pending count. Follow the repository pattern in apps/orders/.

# Terminal 2 — customer backend
claude -w feat-customers
> Under apps/customers/, implement sub-user CRUD: list, create, update, delete.
> Auth via existing JWT middleware.

# Terminal 3 — assets backend
claude -w feat-assets
> Under apps/assets/, add tag filtering and the detail-with-items endpoint.
> Match the serializer style in apps/orders/serializers.py.
```

Three terminals, three worktrees, three branches. When each one finished I reviewed its diff, ran the tests, and merged in order. Total wall-clock for what would have been a week of sequential work: an afternoon.

---

#### Which one should you actually use?

If you're starting out: **just use `--worktree`**. It's the one you won't outgrow.

Reach for agent teams when your problem genuinely benefits from teammates talking to each other — debugging a gnarly bug with competing hypotheses, or a PR review where security, performance, and tests each deserve their own pass. The token bill is real, and so is the coordination overhead; don't use it for tasks where a single focused session would finish faster.

Reach for in-conversation subagents when you want results summarised back into a session you're already driving, and you're comfortable not being able to steer them mid-flight.

All three are the same underlying trick — more contexts, working in isolation, against the same repo. The differences are all about how much they need to talk, and how much you're willing to pay for that conversation.

### My Claude Code IDE: Zed for Review, Ghostty and tmux for the Agent

https://misoto22.com/blog/my-claude-code-terminal · 2026 · 11 min read · AI

> Why I split the editor and the agent across two apps, how tmux keeps Claude Code sessions alive when the window closes, and the small configuration quirks that make it all feel native.

When I started using Claude Code seriously, I expected to live inside VS Code with its Claude Code extension. I tried that setup for a few weeks and it worked — but it always felt like two tools wrestling for the same real estate. The editor wanted to show me code. The agent wanted my full attention. Both wanted the bottom half of the window.

The configuration I've landed on splits the roles across two apps: a terminal for the agent, an editor for reviewing what it did. That separation turned out to be the thing that made the whole workflow click. This post is a walkthrough of that setup — Ghostty and tmux for the agent side, Zed for the review side — plus the small configuration knots you actually need to untie to make it feel native.

---

#### Why Two Apps Instead of One

A plugin-based setup (VS Code + Claude Code extension) puts both the agent and the editor into a single window. It's convenient, but two things bothered me:

1. **The agent pane is always small.** The editor takes the majority of the screen by default, and when I'm actually reviewing a long diff I want the editor to be large, not the agent. A resizable pane doesn't fix that — it just makes every rearrangement a judgment call.
2. **The session is tied to the window.** Close VS Code, lose the conversation. Reboot, lose everything. There's resume support, but it's not the same as having the session genuinely still running.

Separating the two apps fixes both. The agent runs full-screen in Ghostty (often split across tmux panes when I want two agents working in parallel). The editor opens separately, showing me only code. The agent keeps running when the editor is closed, and the editor reloads itself when the agent changes a file.

The mental model is simple: **Claude Code is a process, not a panel.** Once you start treating it that way, hosting it in a terminal multiplexer is the natural choice.

---

#### The Setup at a Glance

```diagram
{
  "caption": "Ghostty hosts tmux; tmux hosts the session. Zed opens beside them to read the diff.",
  "nodes": [
    {
      "id": "ghostty",
      "label": "Ghostty",
      "note": "fullscreen",
      "footnote": "persisted by tmux + resurrect",
      "children": [
        {
          "label": "tmux session: misoto22",
          "direction": "row",
          "children": [
            {
              "label": "claude",
              "note": "the agent"
            },
            {
              "label": "pnpm dev",
              "note": "or a REPL"
            }
          ]
        }
      ]
    },
    {
      "id": "zed",
      "label": "Zed",
      "note": "project diff · git panel",
      "footnote": "opens when I want to review changes",
      "children": [
        {
          "label": "Inline diff gutters",
          "note": "reads what Claude wrote"
        }
      ]
    }
  ]
}
```

Ghostty hosts tmux. tmux hosts the Claude Code session alongside a dev server or a REPL. Zed opens on the side to review what Claude changes. Closing Ghostty doesn't kill the Claude session — tmux detaches it, and tmux-resurrect brings it back later.

The rest of this post walks through each piece.

---

#### Zed — The Review Side

I used VS Code for years. Zed replaced it as my read-mostly editor for Claude Code work for two reasons: it's fast enough that I don't notice opening it, and its git-diff integration is good enough that reviewing agent-generated changes is the primary thing I do in it.

**Git integration** is what I actually use. Zed gives you three views of a change:

- **Colored gutter bars** next to each line (added / modified / deleted)
- **Inline blame** showing the commit for the current line, which is useful for distinguishing what Claude just wrote from what was already there
- **A Project Diff view** that lists every changed file in the working tree as an editable multibuffer — you can stage or unstage hunks from it directly with `cmd-y` / `alt-y`

The Git panel and Project Diff both refresh instantly when Claude writes to disk, so I can watch changes appear as the agent makes them. There's one caveat worth knowing: **buffers you've already opened don't auto-reload**. If Claude rewrites a file that's open in a Zed tab, Zed notices the file changed on disk but leaves the buffer showing the old content until you run `editor::ReloadFile`. The community has asked for automatic reload of unmodified buffers but it's not shipped yet.

My habit: use the Git panel and Project Diff to *find* what Claude changed, then open the file fresh (not from an existing tab) to actually read it. That sidesteps the stale-buffer issue entirely.

**Split views** — useful when I want a diff on one side and the current file on the other, or two related files side by side. Zed's splitting is lightweight enough that I use it reflexively instead of tab-hopping.

What I don't use Zed for: writing code directly. Claude writes almost all of the code now. Zed is where I read it, approve it, and occasionally fix a one-liner that's faster than explaining.

---

#### Ghostty — The Terminal

Ghostty is a GPU-accelerated terminal emulator with a zero-configuration philosophy. It does what iTerm2 and Alacritty do, but the defaults are good enough that I haven't had to touch its config beyond a font size.

What makes it worth choosing for Claude Code specifically:

- **`Shift+Enter` works natively** for multi-line input in Claude Code. Most terminals need a keybinding tweak; Ghostty, iTerm2, WezTerm, and Kitty are the four the docs list as working out of the box.
- **Desktop notifications work** without config. When Claude finishes a long task and fires a notification hook, Ghostty pops it up like any other macOS notification.
- **GPU rendering** means scrollback and long outputs don't judder. This matters more than you'd expect when Claude is dumping thousands of tokens into the terminal.

The thing to understand about Ghostty: it's intentionally boring. No built-in tab UI beyond the basics, no plugin system, no scripting layer. If you want sessions and splits, you reach for tmux. That's the point.

---

#### tmux — Sessions, Windows, Panes

tmux is a terminal multiplexer. If you haven't used one, the three concepts to understand are:

- **Pane** — a rectangle of terminal, running one shell or one program.
- **Window** — a tab, containing one or more panes.
- **Session** — a collection of windows. This is the unit that survives across terminal crashes, reboots, and SSH disconnects.

The magic verb is **detach**. When you detach from a session, everything in it keeps running — the Claude Code conversation, the dev server, the REPL, a long-running build. You can come back later (`tmux attach -t <name>`) and find it exactly where you left it. Close Ghostty entirely and reopen it — still running. SSH disconnects in the middle of a deploy — still running.

For Claude Code specifically, this solves the single most annoying thing about terminal-hosted agents: losing the conversation because you closed a window.

My default layout for a project session is two panes: Claude Code on the left, a dev server or test watcher on the right. When I'm running two agents in parallel on different branches, it becomes two horizontal panes stacked in one window, one session per project.

---

#### Session Persistence — Surviving Reboots

Detach-and-reattach handles closed windows and SSH drops. It doesn't handle reboots — tmux sessions live in memory, so a restart clears them. For that you need two plugins, and for Claude Code specifically, a third.

**tmux-resurrect** saves the environment: session list, window layout, pane arrangement, working directories, and in many cases the running programs themselves. Trigger with `prefix + Ctrl-s` to save, `prefix + Ctrl-r` to restore.

**tmux-continuum** sits on top. It auto-saves every 15 minutes in the background, and with one setting it auto-restores the last save whenever tmux starts. The line that makes it work:

```text
set -g @continuum-restore 'on'
```

After that, `tmux` on a fresh boot comes up already attached to where you left off the last time.

The gap: plain resurrect captures *that* Claude Code was running in a pane, but not *which conversation* it was attached to. A restored pane opens a fresh Claude session, not your old one. That's fine for a shell or a dev server, but useless if you wanted to pick up a long conversation.

**tmux-assistant-resurrect** closes that gap. It's a companion plugin that hooks into CC's `SessionStart` event to record each live session's UUID, then hooks tmux-resurrect's save/restore lifecycle to re-run `claude --resume <id>` in the right pane when tmux comes back. The command it reconstructs keeps your CLI flags, your environment variables, and your session history intact. Install via TPM alongside the other two:

```text
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @plugin 'timvw/tmux-assistant-resurrect'
```

The practical result: I reboot, Ghostty opens, tmux attaches, and three Claude Code windows come back in exactly the state they were in — same conversations, same working directories, same dev servers alongside them.

---

#### The Configuration Knots That Actually Matter

Most of `~/.tmux.conf` is personal preference. A few lines, though, are load-bearing for Claude Code specifically.

**Shift+Enter for newlines.** tmux strips extended keys by default, which means `Shift+Enter` submits your message instead of inserting a newline inside Claude Code. Two lines fix this:

```text
set -s extended-keys on
set -as terminal-features 'xterm*:extkeys'
```

The first tells tmux to honour extended key reporting when Claude Code requests it. The second tells tmux that the outer terminal (Ghostty) can pass those sequences through.

**Desktop notification passthrough.** Claude Code's notification hook emits an escape sequence the outer terminal converts into a native notification. tmux intercepts that sequence by default, so notifications never reach Ghostty. One line opens the channel:

```text
set -g allow-passthrough on
```

The same setting also lets Claude Code's terminal progress bar reach the outer terminal.

**Sensible defaults.** I use the standard `tmux-sensible` plugin so I don't have to re-learn every obscure default. The only things I change on top are a higher history limit (for when Claude spews a long trace), and `set -g mouse on` so I can click between panes instead of reaching for the prefix.

Everything else — status bar, colours, key remaps — is taste, and nothing about it is Claude-specific.

---

#### A Typical Session

The flow I fall into without thinking:

1. `tmux attach` (or just `tmux` if continuum already restored) → I'm immediately back where I left off.
2. Switch to the pane running Claude Code. The conversation is still there.
3. Describe what I want. Claude edits files, runs tests, commits.
4. When I need to review, I `cmd-tab` to Zed. Project Diff shows everything that changed. Read, approve, or send corrections back to Claude.
5. If I need to check something the terminal isn't showing well — a long log, a diff too big for the pane — I `cmd-tab` to Zed for that too.
6. Close my laptop lid at the end of the day. Reopen next morning. Same session, same Claude conversation, same dev server still running.

Two apps, clear roles, no context switching penalty. It stops feeling like *setup* and starts feeling like a single environment.

---

#### What I'd Set Up First

If you're moving from VS Code + Claude Code extension to a terminal-hosted setup, in order:

1. **Install Ghostty or another terminal where `Shift+Enter` works natively.** iTerm2, WezTerm, and Kitty are the other three that work out of the box.
2. **Install tmux.** Even before you think about plugins, the detach-and-reattach behaviour alone is worth it.
3. **Install `tmux-resurrect`, `tmux-continuum`, and `tmux-assistant-resurrect`.** All three via TPM in fifteen minutes. This is the piece that makes the setup actually resilient.
4. **Add the three must-have tmux config lines** (`extended-keys`, `terminal-features`, `allow-passthrough`).
5. **Pick an editor for review.** Zed is what I use; VS Code without the Claude extension works too. The point is that the editor is a read-mostly companion now, not the agent's host.
6. **Stop resizing panes and start trusting `cmd-tab`.** Two full windows will feel wrong at first. Give it a week.

Most of the friction in an AI-driven workflow comes from fighting your tools for attention. Splitting the agent and the editor into two apps ends that fight. Everything else — session persistence, keybindings, diff views — is polish on top of that one decision.

---

#### References

Every link below is a page I consulted while writing this post. The notes are what you'll actually get from each.

1. **[Optimize your terminal setup](https://code.claude.com/docs/en/terminal-config)** — Claude Code's official terminal guide. The canonical reference for `Shift+Enter` configuration, tmux extended-keys requirements, and notification passthrough. Read this before touching `~/.tmux.conf`.
2. **[Zed Git documentation](https://zed.dev/docs/git)** — Git panel, Project Diff, inline blame, and the two diff-view modes. Also the source for the gutter and word-level highlighting behaviour.
3. **[Ghostty documentation](https://ghostty.org/docs)** — Ghostty's design philosophy and supported platforms. Short by design; the whole point is that you don't need to read much.
4. **[tmux-resurrect](https://github.com/tmux-plugins/tmux-resurrect)** and **[tmux-continuum](https://github.com/tmux-plugins/tmux-continuum)** — The two standard plugins for tmux session persistence. Read both: resurrect explains what's saved, continuum explains how to make the whole thing automatic.
5. **[tmux-assistant-resurrect](https://github.com/timvw/tmux-assistant-resurrect)** — The Claude-Code-specific glue that turns "tmux came back" into "my Claude conversations came back". The README walks through how it hooks `SessionStart` to track session IDs and re-runs `claude --resume` on restore.

### My 2026 Mac App Stack

https://misoto22.com/blog/my-2026-mac-apps · 2026 · 11 min read · Dev

> Thirteen Mac apps I use every day as a developer — from terminals and editors to voice input, remote access, and credential management. Each one solves a specific problem.

I've been developing on Mac for a few years now, and the toolset has gone through many iterations. What I've settled on in 2026 is the stack I actually use every day — no filler, no "top 100" list. Just thirteen apps: some help me write and review code, some connect my machines, and one keeps the credentials behind all of it under control. Each has a clear reason to be here.

---

#### Ghostty — Terminal

[Ghostty](https://ghostty.org/) is a terminal emulator written in Zig by Mitchell Hashimoto (founder of HashiCorp). It's fast — GPU-accelerated rendering, near-instant cold start. Configuration is dead simple: one `~/.config/ghostty/config` file, no YAML/TOML/JSON debates.

I switched from iTerm2 and never looked back. iTerm2 is feature-rich but slow to launch and complex to configure. Ghostty takes the opposite approach — it does less, but everything it does is polished. Native macOS rendering, perfect font fallback, decent splits.

A terminal should get out of your way. Ghostty does exactly that.

---

#### Zed — Code Editor

[Zed](https://zed.dev/) is the editor built by the original Atom team, rewritten in Rust. It opens instantly and handles large files without stuttering — that alone is a win.

I use Zed for most lightweight editing: quick config changes, reading logs, writing Markdown. VS Code is still around for its plugin ecosystem (Remote SSH, language-specific debuggers). But for the "open a file, change a few lines" workflow, Zed is significantly faster.

Real-time collaborative editing is a core Zed feature, though I haven't used it much. The AI integration is solid. Built-in terminal, file tree, and search all work well. It's a modern editor without the bloat — everything you need, nothing you don't.

---

#### Warp — AI Terminal

Wait — why another terminal when Ghostty exists?

Because they solve different problems. Ghostty is a pure terminal emulator — fast and stable. [Warp](https://www.warp.dev/) is a terminal-IDE hybrid. Its input area is a real editor with free cursor movement, multi-line editing, and command history search. Each command's output is an independent block that you can collapse, copy, or scroll through without it bleeding into surrounding context.

I use Ghostty for long-running processes (dev servers, docker compose) and Warp for interactive work (git, debugging, ad-hoc scripts). They complement each other well.

Warp also has AI-powered command explanation and generation. I don't use it often (Claude Code handles that), but it's handy when I forget some obscure `ffmpeg` flag.

---

#### Typeless — Voice Input for Coding Agents

[Typeless](https://www.typeless.com/) is system-wide AI dictation. I speak naturally, including pauses and corrections, and it turns that into clean text in whichever app has focus instead of preserving every filler word and repeated phrase.

The place it has changed my workflow most is agentic coding. A useful request for Codex or Claude Code is rarely a one-line command: I need to explain the result I want, the current context, the constraints, and how I will judge the finished work. Saying that brief aloud is faster and more natural than typing a long prompt, especially for the first task in a session, review feedback, or a follow-up after reading an agent's plan.

I still type code, shell commands, paths, and credentials. Voice is for intent; the keyboard is for exact syntax. That boundary makes Typeless genuinely useful instead of turning it into a novelty input method.

---

#### DataGrip — Database IDE

[DataGrip](https://www.jetbrains.com/datagrip/) is JetBrains' database IDE. At work I connect to SQL Server 2025, personal projects use Supabase (PostgreSQL) — DataGrip handles both in one tool.

SQL autocompletion, schema visualization, query result export, simultaneous multi-source connections — these features sound standard, but DataGrip's completion quality is the best I've used across any database tool. It genuinely understands your schema, not just keyword matching.

The only downside is the classic JetBrains problem: memory consumption. But for database work, there's no better alternative. DBeaver is free but noticeably rougher.

---

#### Typora — Markdown Editor

[Typora](https://typora.io/) is a WYSIWYG Markdown editor, and it's been my go-to for long-form writing.

Many people say "VS Code / Zed can do Markdown too," and they can — but the experience is completely different. Typora's live preview isn't a split pane; it renders inline right where you type. You never get pulled out of your writing flow by syntax symbols.

I use Typora for blog drafts, project docs, and meeting notes, then copy the content into MDX files. Maybe it's an "extra step," but for writing, how a tool feels matters more than raw efficiency. Typora feels the best.

Drag-and-drop images, table editing, theme customization, PDF export — it has everything and remains rock-solid stable.

---

#### PDF Expert — PDF Reading & Annotation

[PDF Expert](https://pdfexpert.com/) is the best PDF tool on macOS, period.

Preview.app handles basic viewing, but the moment you need annotations, signatures, text editing, or file merging, it falls short. PDF Expert launches fast, renders accurately, and has a complete annotation toolkit. I use it daily for technical docs and papers — the highlight + annotate workflow is seamless.

Form filling and signing are also frequent use cases. Much lighter than Adobe Acrobat, and reasonably priced (one-time purchase).

---

#### Docker Desktop — Containers

[Docker Desktop](https://www.docker.com/products/docker-desktop/) needs no introduction. When local development requires databases, message queues, or various services, Docker Compose handles it in one command.

My main uses:
- Running PostgreSQL, Redis locally
- Testing multi-service architectures
- Reproducing production environment issues

Docker Desktop's performance on macOS has long been criticized (it runs a Linux VM under the hood), but recent versions have improved significantly. VirtioFS makes bind mount speeds acceptable.

OrbStack is a lighter alternative, but Docker Desktop works well enough for me, and its VS Code Dev Containers integration is more mature.

---

#### Tailscale — A Private Link Between My Laptops

[Tailscale](https://tailscale.com/) connects my two laptops even when they are not on the same local network. Each machine gets a stable private address and MagicDNS name, so the laptop at home remains reachable from the other one without router port-forwarding or exposing a service to the public internet.

I use that private connection for SSH, development servers, dashboards, and the occasional file transfer. The service still has to be running on the destination machine — Tailscale supplies the network path, not the service itself — but once both laptops are in the same tailnet, changing Wi-Fi networks stops changing how I reach them.

Most of the time the connection is direct. When the surrounding networks make that impossible, Tailscale can relay it automatically. I do not have to care which path it took; I use the same private hostname either way.

---

#### 1Password — Accounts and Project Secrets

[1Password](https://1password.com/) holds the personal side of my AI workflow: the ChatGPT and Claude logins, passkeys, recovery codes, and related account details. They do not live in browser notes, shell history, or a text file that eventually gets copied into the wrong place.

It also became the source of truth for this project's credentials. The repository can commit a value-free reference such as:

```dotenv
LLM_API_KEY=op://.../LLM_API_KEY
```

The actual value stays in 1Password. For local development, `op run` resolves the reference and exposes it only to the child process. Production uses the same idea immediately before deployment, so the runtime receives the credential without turning GitHub or the repository into the canonical secret store.

This matters more now that Codex and Claude Code can run commands on my behalf. I do not give an agent broad access to a vault; I inject only the variables needed by the command it is running. 1Password is not just where I save passwords — it is the boundary between an agent that can do useful work and an agent that can see every credential I own.

---

#### Raycast — Spotlight Replacement

[Raycast](https://www.raycast.com/) is the first app I set up on any new Mac. Once installed, I never open Spotlight again.

##### Why Replace Spotlight

Spotlight's problem isn't that it's broken — it's slow and imprecise. App search rankings are often wrong, and file search is even worse. Raycast's fuzzy matching is fast and accurate — two or three characters and you're at the app you want. But Raycast isn't just a launcher — it's a programmable command palette that replaces a whole collection of standalone utilities.

I bind Raycast to `Cmd+Space`, completely replacing the system Spotlight.

##### Snippets — My Most-Used Feature

Snippets is the Raycast feature I use the most. The concept is simple: define a keyword that auto-expands into full content when typed.

My commonly used Snippets:

| Keyword | Expands To | Use Case |
|---------|------------|----------|
| `;phone` | My phone number | Forms, account signups |
| `;email` | Primary email address | Logins, contact forms |
| `;addr` | Home address | Deliveries, registrations |
| `;waddr` | Office address | Work-related forms |
| `;card` | Bank card last 4 digits (never full number) | Reminding myself which card to use |
| `;sig` | Email signature | Writing emails |
| `;zoom` | Zoom meeting link | Scheduling meetings |

I type this information multiple times daily. Before Snippets, it was either manual typing or copy-pasting from Notes. Now `;phone` expands instantly, and it works in any application.

You can also use it for code templates (e.g., `;log` expanding to `console.log()`), but I personally leave code completion to my editor and Claude Code. Snippets are for personal info and frequently used text.

##### Clipboard History — Never Lose Copied Content Again

macOS's native clipboard only keeps the last item. Copy some code, then copy a URL, and the code is gone.

Raycast's Clipboard History solves this:
- **Unlimited history**: Everything you've ever copied is retained — text, images, files
- **Search**: `Cmd+Shift+V` opens the history panel, search by keyword instantly
- **Pin**: Pin frequently used items for quick access anytime

This feature sounds simple, but once you've used it, there's no going back. Especially during development when you're constantly copying between multiple code snippets — history means no more switching back and forth between windows.

##### Other Useful Features

- **Window management**: Hotkeys to snap windows left/right/fullscreen — no need for Magnet or Rectangle
- **Calculator**: Quick math right in the search bar, with unit conversion and exchange rates
- **Plugin ecosystem**: Jira, GitHub, Linear, Notion all have quality community plugins

The free tier covers 90% of use cases. The Pro AI features are nice but not essential.

---

#### Shottr — Screenshots

[Shottr](https://shottr.cc/) is a lightweight screenshot tool.

macOS's built-in screenshot (`Cmd+Shift+4`) works, but Shottr adds several key features:
- **Scrolling capture**: Full-page screenshots of long webpages or conversations
- **Annotations**: Arrows, boxes, text, blur — annotate immediately after capture
- **OCR**: Extract text from screenshots instantly
- **Measurement**: Pixel distances, color picking — great for UI development
- **Pin to screen**: Pin a screenshot on top of everything for easy reference

Free, lightweight, native macOS feel. CleanShot X has more features but costs money. Shottr hits the sweet spot for me.

---

#### Bob — Translation

[Bob](https://bobtranslate.com/) is a macOS translation tool that supports text selection translation, screenshot translation, and manual input translation.

As a developer who constantly switches between Chinese and English, Bob is essential. When reading English docs and hitting an unfamiliar term, just select the text and the translation appears — no need to switch to a browser and open Google Translate.

Bob's strength is its multi-source support: it can display results from DeepL, OpenAI, and Google Translate simultaneously, letting you compare which translation is most accurate. I currently use a DeepL + OpenAI combination.

Screenshot translation is also practical — when encountering text in images (UI screenshots, scanned PDFs), just capture the area and it translates directly, no manual typing needed.

---

#### Summary

These thirteen apps cover the parts of my daily development workflow where the choice of tool genuinely changes the experience:

| Use Case | Tool |
|----------|------|
| Terminal | Ghostty (pure) + Warp (interactive) |
| Code editing | Zed (lightweight) + VS Code (heavy-duty) |
| Database | DataGrip |
| Writing | Typora |
| PDF | PDF Expert |
| Containers | Docker Desktop |
| Launcher | Raycast |
| Screenshots | Shottr |
| Translation | Bob |
| Voice input | Typeless |
| Remote connection | Tailscale |
| Passwords & project secrets | 1Password |

My principle for choosing tools remains simple: **solve one problem, and solve it completely.** I do not care whether an app is the biggest or the newest. What stays installed is what I reach for repeatedly and can explain in one sentence. These thirteen now form the practical layer around my Mac workflow: express the intent, let the agents work, connect the machines, protect the credentials, and handle everything around the code.

### How I Set Up Claude Code as My Daily Development Companion

https://misoto22.com/blog/my-claude-code-setup · 2026 · 18 min read · AI

> A walkthrough of Claude Code from the ground up — what it is, how configuration scopes work, and the rules, plugins, hooks, auto memory, and CLI glue that make it click for day-to-day engineering work.

Claude Code is the tool I spend more time in than any IDE — Rust one hour, Python the next, TypeScript the hour after that. Hundreds of messages a day, across four or five projects. Over the past few months the configuration around it has turned into something much more than autocomplete: it enforces conventions, remembers context between sessions, and drives real infrastructure on my behalf. This post walks through that setup from the ground up. If you've never opened Claude Code before, the first half is for you. If you have, skip to the configuration sections.

---

#### What Claude Code Actually Is

Claude Code is Anthropic's official CLI. You run `claude` in a terminal and it opens an interactive agent that can read files, edit code, execute shell commands, search the web, talk to [MCP servers](https://code.claude.com/docs/en/mcp), and delegate work to specialized subagents — all rooted in the directory you launched it from.

Under the hood it's the same Claude model that powers the API. What sets it apart from a chat window is everything around the model: tools, permissions, hooks, memory, plugins. The model is fixed; the shell around it is what you tune.

The mental model I use: **Claude Code is a shell for an agent.** The agent is smart. The shell is what you configure.

---

#### Where Configuration Lives

Four scopes, higher-priority locations winning on conflict:

| Scope | Location | Affects | Shared? |
|-------|----------|---------|---------|
| Managed | OS-level policy directory | All users on the machine | IT deploys |
| User | `~/.claude/` | You, across every project | No |
| Project | `.claude/` + `CLAUDE.md` at repo root | Everyone in the repo | Yes (git) |
| Local | `.claude/settings.local.json` | You, in this repo only | No (gitignored) |

Everything below fits somewhere on this ladder. A rule that applies to every project goes in user scope. A convention that only makes sense in this repo goes in project scope. A machine-specific override — a local path, a personal API key — goes in local scope. Managed is how organisations deploy company-wide policies that individuals can't override.

The separation matters more than it sounds. User-scope rules are how you stop repeating yourself. Project-scope rules are how you teach Claude what's non-obvious about this codebase without bloating every other session with it.

---

#### The Global Layout

My user-scope directory looks like this:

```
~/.claude/
├── CLAUDE.md              # Global instructions, loaded every session
├── settings.json          # Permissions, hooks, plugins, status line
├── rules/                 # Topic-scoped instruction files
├── skills/                # Custom workflows triggered by /commands
├── projects/              # Per-project auto memory (managed by Claude)
│   └── <project>/memory/
│       ├── MEMORY.md
│       └── ...
├── statusline-command.sh  # Custom status bar script
├── update-pricing.sh      # Daily Anthropic pricing fetcher
└── cache/
    └── model-pricing.env
```

The rest of the post walks through each of these in turn, roughly in the order you'd set them up yourself.

---

#### CLAUDE.md — Instructions

This is the file Claude reads at the start of every session. Think of it as a standing briefing: who you are, what you're working on, which conventions to respect.

You can have one at `~/.claude/CLAUDE.md` (applies everywhere), one at `./CLAUDE.md` or `./.claude/CLAUDE.md` at the root of each repo (applies only there), and a personal `./CLAUDE.local.md` that stays out of git. All discovered files are concatenated into context — they don't override each other. If you're starting a new project, `/init` inside Claude Code generates a first draft by reading your codebase.

Mine keeps the global version short. It's an index, not a manual:

- **Who I am** — full-stack developer, Rust / C# / .NET / Python / TypeScript.
- **How I work** — macOS host plus a Windows VM through Parallels for the .NET Framework 4.8 projects.
- **Behaviour** — explore before editing, ask before assuming, match the existing style, never force-push `main`.
- **Pointers to rules** — each domain has its own file under `rules/`.

The reason for keeping it short is that wall-of-text system prompts get ignored, both by the model and by future me when I go to edit it. The docs themselves recommend under 200 lines per `CLAUDE.md`. An index tells Claude where to look and keeps the actual content close to where it's relevant.

You can also pull in other files with `@path` imports — useful for referencing a `README`, a `package.json`, or a shared rules file from your home directory. Imports resolve recursively, so a project `CLAUDE.md` can pull in a personal preferences file without duplicating it.

Project-level `CLAUDE.md` files carry the things that are specific to a given codebase — the package manager, the content model, import aliases, testing commands. My personal website's looks something like:

- Use `pnpm` (not `npm`).
- Blog posts are MDX files under `content/blog/`.
- Imports use `@/i18n/navigation` (not `next/link`).
- API responses follow the global RFC 9457 rules.

`cd` into a different repo and the project-scope `CLAUDE.md` loads automatically, layered on top of the global one. Claude never needs to be re-briefed.

---

#### Rules — Scoped, Reloadable Standards

Claude Code has a dedicated directory for modular instructions: `.claude/rules/` at the project level and `~/.claude/rules/` at the user level. Every `.md` file in there is discovered recursively and loaded alongside `CLAUDE.md`. It's a built-in feature, not a convention you have to bolt on.

The thing that makes rules genuinely useful over just extending `CLAUDE.md`: you can scope a rule to specific files via frontmatter. A rule with `paths: ["src/api/**/*.ts"]` only enters context when Claude is actually working on an API file. The rest of the time, it stays out of the way.

My user-level rule set is ten files, each focused on a single concern:

| File | What it covers |
|------|----------------|
| `coding-style.md` | One concept per file, functions under 50 lines, "why" comments |
| `git-workflow.md` | Imperative commits, feature branches, linter-before-commit |
| `security.md` | No secrets in code, parameterised SQL, HTTPS only |
| `testing.md` | Happy path plus edge and error cases, per-language frameworks |
| `rust.md` | `anyhow` / `thiserror`, no `unwrap()`, the `Service<R: Repository>` pattern |
| `python-django.md` | Django 5 + DRF, `mssql-django`, `managed = False`, `uv` over pip |
| `api-design.md` | RFC 9457 errors, cursor pagination, rate-limit headers on all paths |
| `patterns.md` | Repository pattern, per-layer error-handling strategy |
| `performance.md` | Model selection, context budget, compaction strategy |
| `agents.md` | When to delegate to subagents, parallel execution, plugin-to-task mapping |

Two things make this worth the setup effort:

1. **Rules survive compaction.** Anything Claude picks up mid-conversation is at risk when the context window is summarised. Project-root `CLAUDE.md` and `.claude/rules/` files are re-injected from disk after compaction.
2. **They're diffable.** When I change a standard, I change one file — not four prompts scattered across my shell history.

The payoff shows up in small ways every day. Claude runs `cargo clippy` before Rust commits, reaches for `uv` instead of `pip`, formats API errors in RFC 9457 shape — without me saying a word. Write the rule once, stop repeating yourself.

---

#### settings.json — Permissions, Hooks, Plugins, Status Line

`settings.json` is the other half of the global config. It controls what Claude is *allowed* to do and what happens *around* its actions. Four things live here, each big enough to earn its own section.

---

#### Permissions — Defense in Depth

Permissions split cleanly into an allow-list (auto-approved, no prompt) and a deny-list (hard-blocked, no override).

**Allowed** — what I want Claude to do without asking:

- File operations: `Read`, `Edit`, `Write`, `Glob`, `Grep`
- Shell: `Bash`, scoped by the deny-list
- Web: `WebFetch`, `WebSearch`
- Every tool from Playwright and Context7

**Denied** — things that should never run, even if the model thinks they should:

- Destructive: `rm -rf /`, `git push --force`, `git reset --hard`, `git clean -f`
- Dangerous: `dd`, `mkfs`, `shutdown`, `reboot`, `chmod 777`
- Supply chain: `npm publish`, `cargo publish`, `curl | bash`
- Secrets: `cat ~/.ssh/*`, `cat *id_rsa*`

The deny-list is the single highest-leverage thing you can configure. A model that occasionally hallucinates dangerous commands is fine if those commands can never actually run. Set this up before you need it, not after.

For finer control, permissions accept *argument-shape* patterns — `Bash(gh pr *)` instead of opening `Bash` wholesale for `gh`. I use this when I want a CLI available but not trusted with arbitrary subcommands.

---

#### Hooks — Automated Guardrails

Hooks run shell commands around Claude's tool use. The docs list more than twenty events — `PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `SessionStart`, `Stop`, `PreCompact`, `PostCompact`, `SubagentStart`, `FileChanged`, and more. Each receives a JSON payload on stdin and can return structured output to block, modify, or just observe the call.

I use three of them.

A `PreToolUse` hook that nudges me before any `Bash` call:

```json
{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo '⚠️  Confirm branch and remote before pushing'"
      }]
    }]
  }
}
```

The `matcher` field is a tool name, a `|`-separated list, or a regex. `PreToolUse` can also block a call by returning `{"hookSpecificOutput": {"permissionDecision": "deny"}}`, which is how organisations enforce policy that goes beyond the static deny-list.

A `PostToolUse` hook that reminds Claude to lint after every edit. The payload arrives on stdin, so I extract the file path with `jq`:

```bash
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
  *.rs) echo "Reminder: run cargo clippy" ;;
  *.cs) echo "Reminder: run dotnet build" ;;
  *.py) echo "Reminder: run ruff check && ruff format" ;;
esac
```

Neither blocks the call — they just tap Claude on the shoulder. But they catch more than you'd think, because the reminder fires every time, not just when Claude remembers to ask.

A `SessionStart` hook gives me a moment to dump custom context — git status, branch staleness, a reminder about any in-flight experiment — into the very first message.

---

#### Plugins and Marketplaces

Plugins package skills, agents, hooks, MCP servers, and slash commands into a single installable unit. Claude Code ships with the official Anthropic marketplace (`claude-plugins-official`) already active, and you can add third-party marketplaces from GitHub repos, Git URLs, or local paths.

Installing a plugin is one command:

```shell
/plugin install github@claude-plugins-official
/plugin install supabase@claude-plugins-official
/plugin install vercel@claude-plugins-official
```

Plugin skills are namespaced by the plugin name (`/github:create-pr`), so two plugins can have skills with the same short name without colliding.

I run about a dozen. The official ones:

- **commit-commands** — `/commit`, `/commit-push-pr`
- **github** — issues, PRs, releases via the GitHub MCP
- **vercel** — deployments, observability, Next.js and AI SDK guidance
- **supabase** — project introspection and SQL
- **code-review** — PR review with confidence-based filtering
- **rust-analyzer-lsp**, **typescript-lsp** — real-time diagnostics from Language Server Protocol integrations

Plus third-party:

- **[context7](https://github.com/upstash/context7)** — live library docs, because training data goes stale fast
- **[interface-design](https://github.com/Dammyjay93/interface-design)** — design system auditing
- **[ecc](https://github.com/affaan-m/everything-claude-code)** — a large community collection
- **playwright** — browser automation

Adding a third-party marketplace automatically happens through `/plugin marketplace add`, but you can also declare it in `settings.json` so teammates who trust the repo get prompted to install it:

```json
{
  "extraKnownMarketplaces": {
    "interface-design": {
      "source": {
        "source": "github",
        "repo": "Dammyjay93/interface-design"
      }
    }
  }
}
```

I keep a plugin-to-task mapping in `rules/agents.md` so Claude reaches for the right tool without being asked:

| Task | Plugin |
|------|--------|
| Plan a feature | `/feature-dev` or Plan mode |
| Review code or PR | `/code-review` |
| Simplify code | `/simplify` |
| Git commit | `/commit-commands:commit` |
| Commit + push + PR | `/commit-commands:commit-push-pr` |
| Rust diagnostics | `rust-analyzer-lsp` |
| Browser testing | `playwright` |

---

#### Platform CLIs

Several of those plugins (`github`, `vercel`, `supabase`, `context7`, `playwright`) are really MCP servers in a wrapper — they query APIs and hand back structured data, which is good for reading state. For actually doing things — shipping a deploy, running a migration, cutting a release — I let Claude call the platform's native CLI through `Bash`.

The ones I use most:

- **`gh`** — creating PRs, polling CI, merging. `/ship` is essentially a scripted sequence of `gh` calls.
- **Supabase CLI** — `db diff`, migrations, edge function deploys. The Supabase MCP handles introspection; the CLI handles changes.
- **Vercel CLI** — `vercel deploy`, `env pull`, log tailing. The plugin tells Claude what's going on; the CLI lets it act.
- **AWS CLI** — bucket listings, log tails, EC2 describes. IAM stays in charge of what's actually allowed.
- **gcloud / kubectl / docker** — same story in their respective worlds.

No extra wrapping involved. The commands go through `Bash`, so the deny-list already blocks `--force` and the other destructive flags, and the `PostToolUse` hook still fires before anything mutating runs. When I want tighter control I allow-list a specific pattern like `Bash(gh pr *)` instead of opening up `gh` across the board.

The split I've settled on: MCP for reading, CLIs for writing, plugins for workflows that need both. Once that's written into the rules, Claude stops second-guessing which tool to reach for.

---

#### Status Line — Real-Time Dashboard

My favourite customisation. One line at the bottom of the terminal, showing everything that matters:

```
[Opus 4.6] misoto22-site | main | 45k 12k | 32% (57k/200k) | $0.45 | 12m30s | 3f +42 -8
```

Left to right: model, project directory, Git branch, tokens in and out, context usage with a colour code (green under 50%, yellow 50–75%, red above 75%), session cost in USD, session duration, Git diff stats.

Cost comes from a cached copy of Anthropic's pricing, refreshed daily by a small script that scrapes the public pricing page and writes per-model rates to `~/.claude/cache/`:

```bash
if [ ! -f "$PRICING_CACHE" ] || \
   [ "$(( $(date +%s) - $(stat -f %m "$PRICING_CACHE") ))" -gt 86400 ]; then
    bash "$PRICING_SCRIPT" &>/dev/null &
fi
```

No API key involved. The colour-coded context bar is what I actually watch — it tells me when to `/compact` before the window fills up.

---

#### Auto Memory — Cross-Session Learning

Rules and `CLAUDE.md` are static: you write them, Claude reads them. Auto memory is dynamic, and it's a built-in feature as of Claude Code v2.1.59. Claude takes its own notes during a session — build commands it figured out, corrections you made, architecture patterns it learned — and saves them to `~/.claude/projects/<project>/memory/` as plain markdown files.

The directory contains a `MEMORY.md` entrypoint plus any number of topic files Claude creates as needed:

```
~/.claude/projects/<project>/memory/
├── MEMORY.md           # Index, loaded into every session
├── user-profile.md     # Role, preferences, expertise
├── feedback.md         # Corrections and confirmed approaches
├── architecture.md     # Decisions worth remembering
└── references.md       # Pointers to external systems
```

At session start, the first 200 lines (or 25 KB) of `MEMORY.md` get loaded automatically. Everything else loads on demand when Claude decides it needs the detail. The whole thing is plain markdown — you can read, edit, or delete entries directly, or open the directory via `/memory` inside a session.

My additions on top of the built-in behaviour:

1. **A conventional entry format.** Every memory file has YAML frontmatter (`name`, `description`, `type`) so the `MEMORY.md` index stays scannable and future Claude knows what each file is for.
2. **Four categories.** `user` (identity, preferences), `feedback` (corrections), `project` (architecture decisions), `reference` (pointers to external systems). This is just a convention encoded in my global `CLAUDE.md`, but it keeps the directory organised instead of turning into a pile.
3. **Explicit permission to update.** A rule in `CLAUDE.md` that says "when you learn something worth keeping, write it to auto memory" — because the default behaviour is conservative.

In practice this means Claude already knows I'm a native Chinese speaker who prefers terse replies, that my Efision project uses a 5-layer Clean Architecture with `Service<R>` generics, and that running `npm` inside a `pnpm` project is a mistake I've corrected before. These details compound. Over months they change how Claude approaches every task.

The one bit of discipline that makes memory work: don't try to front-load it. Let it build from real corrections. A memory written because the model actually got something wrong is load-bearing; one written preemptively is noise.

---

#### Custom Skills — Repeatable Workflows

Skills are Claude Code's unit for reusable workflows. Each one is a directory under `~/.claude/skills/` (or a project's `.claude/skills/`) containing a `SKILL.md` file and any supporting assets. The frontmatter tells Claude when to use it, and the markdown body tells it how.

A minimal skill:

```markdown
---
name: deploy
description: Deploy the application to production
disable-model-invocation: true
allowed-tools: Bash(gh *) Bash(vercel *)
---

Deploy $ARGUMENTS to production:

1. Run the test suite
2. Build the application
3. Push to the deployment target
4. Verify the deployment succeeded
```

A few frontmatter fields I use often:

- **`disable-model-invocation: true`** — "only the user can call this". Use for workflows with side effects, so Claude never auto-triggers a deploy because the code looks done.
- **`allowed-tools`** — grants the skill permission to run specific tools without prompting while it's active.
- **`context: fork`** with `agent: Explore` — runs the skill in a subagent's isolated context, good for research tasks that shouldn't pollute the main conversation.

The one I use most is `/ship`. One command, working tree to merged PR:

1. **Test** — run the project's test suite; auto-fix up to two times if tests fail.
2. **Commit** — stage, generate a conventional commit message from the diff.
3. **PR** — create branch, push, open PR with summary and test plan.
4. **CI** — poll `gh pr checks` every 30 seconds (10-minute timeout); on failure, read logs, fix, push, retry up to two times.
5. **Merge** — squash merge, delete branch, report the PR URL.

No scripting. Claude just follows the markdown and stops to ask if something breaks. It replaces what used to be five to ten manual commands.

I also have a `deep-research` skill — eight steps that turn a vague question into a structured report, with sources tiered by authority and intermediate artifacts saved to `~/Downloads/research/<topic>/`. It's the skill equivalent of a lab notebook.

Plugin skills live inside plugins and are invoked with the namespaced form: `/interface-design:audit`, `/commit-commands:commit-push-pr`. Your own skills under `~/.claude/skills/` use the plain form: `/ship`, `/deep-research`.

---

#### A Typical Session

With everything above in place, a day looks like this:

1. I open Claude Code in a project. The status line immediately shows model, branch, cost, context usage.
2. Global `CLAUDE.md`, project `CLAUDE.md`, rules, auto memory, and plugin skills all load automatically.
3. I describe what I need. Claude reads the relevant code first (that's a rule), asks if something's unclear, then builds.
4. Hooks fire on every edit — linter reminders, push confirmations.
5. When the context bar turns yellow, I `/compact` at a natural breakpoint. Root-level rules and `CLAUDE.md` re-inject from disk automatically.
6. Bigger features get Plan mode or parallel subagents.
7. Anything worth carrying into the next session gets written to auto memory.

Nothing about this feels like operating an AI. It feels like pair programming with someone who has already read the code, already knows my preferences, and has opinions about which tool to use.

---

#### What I'd Set Up First

If you're starting from nothing and want the biggest return per hour of setup, in order:

1. **Write the deny-list.** Block `rm -rf`, `--force`, `publish`, `curl | bash` before you ever need them blocked.
2. **Write a global `CLAUDE.md`.** Keep it short — under 200 lines. Identity, working style, a few behavioural rules.
3. **Externalise your conventions into `.claude/rules/`.** Anything you've typed into a chat twice deserves a rule file. Use `paths` frontmatter to scope rules that only apply to certain parts of the tree.
4. **Add a `PostToolUse` linter reminder.** One-liner, catches real issues.
5. **Customise the status line.** Once you can see cost and context in real time, you work differently — you compact earlier, you notice runaway sessions.
6. **Install the plugins your infrastructure actually uses.** `/plugin install github@claude-plugins-official` alone pays for itself in a week.
7. **Let auto memory build itself.** Don't front-load. Correct Claude when it's wrong, and those corrections stick.
8. **Layer your config.** Global for universal standards, project for local conventions, local for machine-specific quirks.

Most of my configuration came from real mistakes — a force-push that shouldn't have happened, an `npm install` in a `pnpm` project, a session that ran out of context mid-implementation. Every rule in the file exists because I once needed it to.

---

#### References

Every link below is a page I consulted while writing this post. The notes are what you'll actually get from each — not just the page title.

1. **[Settings](https://code.claude.com/docs/en/settings)** — The canonical reference for the four configuration scopes and every field in `settings.json`. Start here if you're setting things up from scratch.
2. **[Memory](https://code.claude.com/docs/en/memory)** — Covers both halves of Claude Code's memory story: `CLAUDE.md` loading order, `.claude/rules/` with `paths` frontmatter, and how the built-in auto memory directory is stored and re-loaded.
3. **[Hooks](https://code.claude.com/docs/en/hooks)** — Full event catalogue (more than twenty), matcher regex syntax, and the JSON I/O contract for `command` / `http` / `prompt` / `agent` handler types. Essential if you want hooks that *do* something rather than just print reminders.
4. **[Skills](https://code.claude.com/docs/en/skills)** — Frontmatter fields, `$ARGUMENTS` substitution, inline shell-exec blocks, and `context: fork` for running a skill inside a subagent. Read this before writing anything beyond a trivial `/command`.
5. **[Plugins](https://code.claude.com/docs/en/plugins)** and **[Plugin marketplaces](https://code.claude.com/docs/en/discover-plugins)** — Plugin structure, manifest schema, and the `/plugin install <name>@<marketplace>` flow. The second link is the one that actually shows you how `extraKnownMarketplaces` works.
6. **[Subagents](https://code.claude.com/docs/en/sub-agents)** and **[MCP](https://code.claude.com/docs/en/mcp)** — When you're ready to think about Claude Code as an orchestrator rather than a single agent: subagent delegation, preloaded skills, and connecting external tools through Model Context Protocol.

### Next.js Rendering: CSR, SSR, SSG, ISR Explained

https://misoto22.com/blog/nextjs-rendering-methods · 2025 · 5 min read · Dev

> A practical breakdown of CSR, SSR, SSG and ISR in Next.js App Router — with code examples, data flow diagrams, and real use cases.

Building with Next.js means choosing how and where your pages get rendered. Each strategy trades off between performance, SEO, data freshness, and server cost — and picking the right one for each page makes a real difference.

Before we dive in, one important thing: **Next.js App Router defaults to React Server Components (RSC)**. Every component renders on the server and ships zero JavaScript to the client unless you explicitly add `'use client'`. This is fundamentally different from the old Pages Router — and it shapes how all four rendering strategies work today.

---

#### 1. Client-Side Rendering (CSR)

In App Router, pure CSR is actually something you opt into. You mark a component with `'use client'`, and it hydrates on the client after the initial server render.

```tsx
'use client'

import { useState, useEffect } from 'react'

export default function Dashboard() {
  const [stats, setStats] = useState(null)

  useEffect(() => {
    fetch('/api/stats').then(res => res.json()).then(setStats)
  }, [])

  if (!stats) return <div>Loading...</div>
  return <div>{stats.totalUsers} users</div>
}
```

**Data flow:**

Client → Server (sends pre-rendered HTML + JS bundle) → Client (hydrates) → API → Client (re-renders with data)

Note that even `'use client'` components get server-rendered HTML on first load — the browser doesn't see a blank page. But the data fetching still happens on the client after hydration.

**Best for:** Interactive widgets, dashboards, authenticated areas — anywhere you need browser APIs or real-time state.

---

#### 2. Server-Side Rendering (SSR)

In App Router, SSR happens when a Server Component fetches data dynamically. By default, `fetch()` requests are cached — to opt into per-request rendering, you use `cache: 'no-store'` or mark the page as dynamic.

```tsx
// app/news/page.tsx — renders fresh on every request
export const dynamic = 'force-dynamic'

export default async function NewsPage() {
  const res = await fetch('https://api.example.com/news', {
    cache: 'no-store',
  })
  const articles = await res.json()

  return (
    <ul>
      {articles.map(a => <li key={a.id}>{a.title}</li>)}
    </ul>
  )
}
```

**Data flow:**

Client → Server → Database/API → Server (builds HTML) → Client (displays immediately)

The user sees a complete page right away, and search engines get fully rendered content. The trade-off is that every request hits the server.

**Best for:** Pages where content changes on every request — news feeds, user profiles, search results.

---

#### 3. Static Site Generation (SSG)

SSG pre-renders pages at build time. In App Router, this is the default behavior — if your page doesn't fetch dynamic data, it's automatically static. For dynamic routes, you use `generateStaticParams()` to tell Next.js which paths to pre-build.

```tsx
// app/blog/[slug]/page.tsx — pre-rendered at build time
export async function generateStaticParams() {
  const posts = await getAllPostSlugs()
  return posts.map(slug => ({ slug }))
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  return <article>{post.content}</article>
}
```

**Data flow:**

Build time: Server → API/Database → Static HTML generated
Request time: Client → CDN (serves pre-built page instantly)

This is as fast as it gets. The content is frozen at build time — if your data changes, users see stale content until the next deploy. This very site uses SSG for blog posts — the content lives in MDX files and only changes when I push a new commit.

**Best for:** Blog posts, documentation, marketing pages — content that changes infrequently.

---

#### 4. Incremental Static Regeneration (ISR)

ISR combines the speed of SSG with the ability to update content without a full redeploy. You export a `revalidate` interval, and Next.js serves the cached page until it expires. When it does, the next request triggers a background regeneration.

```tsx
// app/products/[id]/page.tsx — static but refreshes every 60 seconds
export const revalidate = 60

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await fetch(`https://api.example.com/products/${params.id}`, {
    next: { revalidate: 60 },
  }).then(res => res.json())

  return <div>{product.name} — ${product.price}</div>
}
```

**Data flow:**

1st request: Client → CDN (serves cached HTML instantly)
Background: Server → API/Database → New HTML generated and cached
Next request: Client → CDN (serves the updated page)

The slight catch is that one user always gets the stale version that triggers the rebuild. But for most use cases, a few seconds of staleness is a worthwhile trade for near-instant load times.

**Best for:** Product pages, listings, blog indexes — content that updates regularly but doesn't need to be real-time.

---

#### Bonus: Partial Prerendering (PPR)

Starting from Next.js 15, there's a fifth option that blurs the line between static and dynamic. **Partial Prerendering** lets a single page be partially static and partially dynamic — the static shell is served instantly from the CDN, while dynamic parts stream in as they become ready.

```tsx
// app/page.tsx — static shell + dynamic content
import { Suspense } from 'react'

export default function HomePage() {
  return (
    <div>
      <h1>Welcome</h1>              {/* Static — served from CDN */}
      <Suspense fallback={<p>Loading...</p>}>
        <RecommendedItems />         {/* Dynamic — streams in */}
      </Suspense>
    </div>
  )
}
```

PPR is the natural evolution of these four strategies — instead of choosing one mode for the entire page, you get granular control at the component level. It's still relatively new, but it's where Next.js rendering is heading.

---

#### Comparison

| Method | First Load | SEO  | Data Freshness      | Next.js Config                        |
| ------ | ---------- | ---- | ------------------- | ------------------------------------- |
| CSR    | Medium     | OK*  | Live (client-side)  | `'use client'` + `useEffect`          |
| SSR    | Medium     | Good | Live (per-request)  | `dynamic = 'force-dynamic'`           |
| SSG    | Fast       | Good | Fixed at build time | Default / `generateStaticParams()`    |
| ISR    | Fast       | Good | Revalidated         | `export const revalidate = N`         |
| PPR    | Fast       | Good | Mixed               | `Suspense` boundaries + dynamic data  |

*\*CSR components still get server-rendered HTML on first load in App Router, so SEO is better than traditional SPA.*

---

#### My Rule of Thumb

- **Default to SSG or ISR** — they're fast, cheap, and cover most pages.
- **Reach for SSR** when content genuinely differs per request or per user.
- **Reserve CSR** for interactive widgets or pages behind authentication.
- **Consider PPR** when a page has both static and dynamic sections — don't make the whole page dynamic just because one part needs fresh data.

> Static where possible, dynamic where necessary. Let the framework handle the rendering complexity so you can focus on what actually matters — delivering value to users.

## Photography

91 photographs of Australian landscapes, urban architecture and street scenes, each with EXIF metadata (camera, lens, aperture, shutter speed, ISO) and, where recorded, the place it was taken.

- [Photography](https://misoto22.com/photography)

## Agent Skills

- [email](https://misoto22.com/skills/email): Draft, reply to, forward, format, send, or verify outbound email under a policy — drafting is the default, and sending stays blocked until a narrow local scope authorizes that exact message. Use when recipients, external disclosure, attachments, authorization, HTML bodies, or Sent-folder confirmation matter, and on requests such as write an email to, reply to this thread, draft a note to the client, forward this with a cover note, send it and confirm it arrived, 写封邮件给, 回一下这个, 帮我发出去, 这封邮件再改改. Not for triaging or summarising a mailbox you are not answering, chat and internal notes, or rewriting how a message sounds without sending it.
- [tempering](https://misoto22.com/skills/tempering): Rewrites blunt, sarcastic, or impatient workplace messages into professional ones that retain the underlying request, offering three registers from collegial to formally documented. Use when a draft is addressed to a colleague, manager, client, vendor, or cross-team counterpart and carries visible frustration — sarcasm, blame, exasperation, ultimatums, or lines such as "are you serious", "脑子有问题", "到底做不做", "有没有一个准信". Trigger on requests to make a message professional, soften it, tone it down, check whether it is too harsh, or work out how to say something without damaging the relationship, including 润色一下, 帮我改得客气点, 这样发出去会不会太冲, 怎么说才不得罪人. Also handles the reverse direction — plain-language interpretation of corporate phrasing when asked what a message actually means, 说人话, or decode this. Not for marketing copy, resumes, blog posts, or grammar cleanup without interpersonal stakes.
- [personal-blog](https://misoto22.com/skills/personal-blog): Use when researching, outlining, drafting, revising, or polishing a personal blog article—an explainer, idea essay, personal essay, cultural review, or technical post—or when asked to preserve voice in a personal blog draft, turn notes into a personal blog post, 写博客, or 写一篇博客. Not for newsletters, magazine profiles, manuscript editing, email, chat, repository documentation, marketing copy, fiction, academic papers, or generic grammar cleanup.
- [repo-polish](https://misoto22.com/skills/repo-polish): Build or restore a repository's public face — README, hero banner, LICENSE, SECURITY.md, CONTRIBUTING.md, the forge's one-line About text, and its GitHub or GitLab topics — written from what the repository's own files say. Every pass runs unless flags narrow it. Use when asked to 装修一下仓库, 仓库装修, 美化仓库, 把开源文件补齐, 写个 readme, 加个 license, 补个安全策略, 设置仓库 topics, 仓库描述写一下, polish this repo, set the repo up properly, add a security policy, fill in the repository description, or tag a repository on GitHub. Not for API reference docs, changelogs, release notes, marketing landing pages, or the source code itself.
- [sync](https://misoto22.com/skills/sync): Bring the local repository in line with its remote — fetch, prune, fast-forward the base branch, and report what diverged. It never rebases a feature branch, never resolves a conflict, and never discards a local commit. Use when asked to sync, pull latest, update from main, get up to date, catch up with remote, 同步一下, 拉一下最新的, 更新到最新, 跟 main 对齐, 更新一下代码. Not for shipping changes, deleting merged branches, or resolving a merge conflict.
- [ship](https://misoto22.com/skills/ship): Ship the current changes as a merged pull request — branch off, run the project's tests, commit, open the PR, wait for CI, merge, and clean up the worktree. Runs a preflight first that marks each step RUN or SKIP, so a clean tree on the base branch exits without doing anything. Use when asked to ship it, ship this, land it, get this merged, open a PR and merge it, push this up and merge, 发出去, 合掉, 开个 PR 合了, 把这些改动提上去, 推上去合并. Not for tagging a release, publishing a package, deploying, or writing a commit message without pushing it.
- [cleanup](https://misoto22.com/skills/cleanup): Remove what shipping left behind — local and remote branches whose pull request merged, worktrees for those branches, and ignored residue a move stranded, such as a __pycache__ that git mv could not see. Every deletion is verified against the forge first, and anything unmerged or unexplained is reported rather than removed. Use when asked to clean up, tidy the repo, delete merged branches, remove stale branches, prune the remote branches, clear out old worktrees, 清理一下, 清掉合并过的分支, 删掉没用的分支, 清理远程分支, 收拾一下仓库. Not for discarding uncommitted work, resetting a branch, or removing untracked files you have not been shown.
- [retitle](https://misoto22.com/skills/retitle): Normalize agent conversation titles onto a dated `MMDD｜TYPE｜subject` scheme — English by default, Chinese with `--lang=zh` — across Codex, Claude Code, and any client that exposes its session list. The date comes from creation time, the middle field from a closed set of nine types, and every rename is proposed as a two-column table before a single title is written. Use when asked to 规范对话名称, 整理会话标题, 统一对话命名, 批量重命名会话, 会话名太乱了, clean up my conversation titles, rename my chat sessions, or make my session names consistent. Not for renaming projects, folders, git branches, worktrees, or files; not for editing, archiving, pinning, or deleting the conversations themselves.
- [steward](https://misoto22.com/skills/steward): Sweep every repository your agent sessions have touched, found from the agent's own session records and any roots you name, and run the dev loop's housekeeping across all of them at once — fast-forward each base branch, remove what already merged, keep conversation titles in scheme, and report which branches are ready to merge, which pull requests are blocked, and which worktrees a live session still occupies. Built to run unattended on a schedule, so a question a pass would have stopped to ask lands in the report instead. Use when asked to 巡一遍所有项目, 大内总管, 看看哪些分支该合并了, 定时清理本地环境, 扫一下所有 worktree, sweep all my repos, check on every project, what needs merging across my projects, run the housekeeping, keep my local environment clean. Not for syncing or cleaning the one repository you are standing in, for shipping or merging a branch, or for renaming a single conversation.
- [reunite](https://misoto22.com/skills/reunite): Make every signed-in account see every conversation in the desktop app's sidebar. The app keeps one conversation index per account, so signing in as a second account hides the first account's history — this unions the indexes and brings a shared conversation's diverged titles back onto one name, adding entries and never removing one. Use when asked why sessions disappeared after switching accounts, where my old conversations went, share sessions between two accounts, merge the session lists, 换账号以后 session 都不见了, 会话历史没了, 两个账号共享会话, 把 session 列表合起来, 找回以前的对话. Not for deleting conversations, renaming them (that is retitle), or moving history between machines.
- [synastry](https://misoto22.com/skills/synastry): Use when two people's birth details need an uncertainty-aware JSON v2 synastry calculation, including 合盘, 星盘配对, exact times, bounded time windows, or date-only records. Not for interpreting an existing v2 artifact, legacy TXT, one-person natal charts, transits, forecasts, predictions, or compatibility scores.
- [synastry-reading](https://misoto22.com/skills/synastry-reading): Use when $synastry hands off a JSON v2 artifact, or when someone supplies a synastry-chart schema 2.0 JSON path or object and asks for 合盘解读, interpretation, relationship dynamics, or an evidence-linked Markdown report. Not for legacy TXT, raw birth details, one-person natal charts, recalculation, transits, forecasts, predictions, or compatibility scores.
- [natal-chart](https://misoto22.com/skills/natal-chart): Compute one person's natal chart from an exact birth date, minute, and place — planet positions with sign, house, dignity and retrograde state, the four angles, intra-chart aspects, sect, and the classical lots, written as canonical JSON plus data-only Markdown. Use for 本命盘, 星盘, 出生星图, natal chart, birth chart, my rising sign, my chart's aspects, or where a planet sits. Not for two-person 合盘, interpreting a chart that already exists, transits, progressions, returns, or any dated prediction.
- [natal-reading](https://misoto22.com/skills/natal-reading): Explain a natal chart artifact someone already has — what the rising sign shapes, where the sect light sits, which aspects are tight enough to lean on, and what the angles and lots add. Writes a reader report plus an auditable evidence file, and a shareable ink-wash page on request. Use for 解读星盘, 看本命盘, 我的上升是什么意思, or asking what a placement or aspect pattern means. Not for computing a chart from raw birth details, 合盘 between two people, transits, progressions, returns, or predicting a dated event.
- [bazi-chart](https://misoto22.com/skills/bazi-chart): Calculate one reusable BaZi chart from a named person's exact birth date, minute, and birthplace, write canonical JSON plus data-only Markdown, then start the natal reading automatically. Use for 八字排盘, 生辰八字, four pillars, or informal single-person birth details. Not for two-person compatibility, existing-chart interpretation, Da Yun, annual luck, or event forecasts.
- [bazi-reading](https://misoto22.com/skills/bazi-reading): Interpret a completed single-person BaZi JSON chart or equivalent verified four-pillar data as an evidence-linked static natal report. Use when a calculator hands off its artifact or someone asks to 解读八字, 看命局, explain day-master strength, structure, or favorable elements from an existing chart. Not for raw birth details, relationship matching, luck cycles, dated predictions, or incomplete source data.
- [bazi-compatibility](https://misoto22.com/skills/bazi-compatibility): Compare two people from reusable BaZi chart JSON files, two complete birth records, or one of each; write auditable interaction data and transparent general or relationship-specific scores before automatic interpretation. Use for 八字合婚, 合八字, two-person compatibility, 配不配, or whether two charts work together. Not for one-person natal work, reading an existing comparison, forecasting, or missing birth minutes.
- [bazi-compatibility-reading](https://misoto22.com/skills/bazi-compatibility-reading): Interpret a completed BaZi compatibility JSON artifact or equivalent verified comparison as a balanced, directional, evidence-linked relationship report. Use after the compatibility calculator or when asked to explain general, romance, marriage, friendship, family, or work scores already computed for two charts. Not for raw birth records, one-person readings, recalculation, predictions, or a binary destiny verdict.
- [ziwei-chart](https://misoto22.com/skills/ziwei-chart): Place one twelve-palace Zi Wei Dou Shu 命盘 from someone's stated birth moment, birthplace, and gender, recording palaces, stars, 生年四化, and 大限 windows as reusable placement data. Use for 紫微斗数, 紫微排盘, 排紫微, 紫微命盘, 十二宫, or purple star astrology. Not for 八字 four pillars, matching two people, comparing two systems against each other, 流年 or monthly transformations, or a 命盘 that has already been placed.
- [ziwei-reading](https://misoto22.com/skills/ziwei-reading): Interpret an already-placed Zi Wei 命盘 artifact, or equivalent verified twelve-palace data, writing a reader report plus a separate audit artifact and, when asked, an ink-wash HTML poster. Use for 解读紫微, 看命盘, 看十二宫, or explaining the stars sitting in 命宫, the 生年四化, and what each 大限 window emphasizes. Not for unplaced birth records, 八字 four pillars, comparing two systems against each other, matching two people, 流年, or dated predictions.
- [bazi-ziwei-cross](https://misoto22.com/skills/bazi-ziwei-cross): Read one person's finished 八字 artifact and finished 紫微 artifact against each other, recording every place the two systems agree, complement, or flatly contradict, without merging them into a single number. Use for 八字紫微综合, 双系统印证, 两盘合参, or asking whether the two systems say the same thing about one person. Not for placing either 命盘, reading one system by itself, matching two people, or forecasting.
- [logo-banner](https://misoto22.com/skills/logo-banner): Create cohesive raster logo, app-icon, favicon, and repository-hero systems through ChatGPT Image when a user asks to design or refresh visual identity, a logo, a banner, branding assets, or light and dark brand variants; stop clearly when the host lacks that generator rather than substituting another tool; not for code-drawn SVG icons, general website layout, or ordinary photo retouching.
- [photo-abstract-editorial-native](https://misoto22.com/skills/photo-abstract-editorial-native): Assemble a source-faithful photograph with a supplied editorial abstraction panel as a sharp comparison board, preserving orientation, aspect ratio, source provenance, and lower-panel scale. Use for original-versus-abstract photography diptychs, photo comparison cards, or repairing blurred and stretched editorial boards; not for generating lower artwork or replacing a separately licensed art-direction skill.

## Contact

- Email: henrycxw@gmail.com
- GitHub: https://github.com/Misoto22
- LinkedIn: https://linkedin.com/in/henry-misoto22
- X: https://x.com/misoto222
- Instagram: https://instagram.com/hry.photography
- Unsplash: https://unsplash.com/@misoto22

## Machine-readable resources

- [Full site content for LLMs](https://misoto22.com/llms-full.txt): this document, expanded, with article bodies
- [Agent skills catalogue](https://misoto22.com/skills.md): every skill with its install line, as Markdown
- [Atom feed](https://misoto22.com/feed.xml): English blog posts
- [Sitemap](https://misoto22.com/sitemap.xml): every canonical URL, both locales
- [OpenAPI description](https://misoto22.com/openapi.yaml): the public read API
- [API catalog](https://misoto22.com/.well-known/api-catalog): RFC 9727 linkset
