From a90debdc878e7011eb4e4e81771cb182ccde677e Mon Sep 17 00:00:00 2001 From: Shahid Raza <50213579+shahid-io@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:55:37 +0530 Subject: [PATCH 1/6] feat(threads): add two TypeScript 7 threads (#55) Covers the Go rewrite (why shared-memory concurrency was the missing capability, why not Rust) and the migration reality (removed options, changed defaults, the missing programmatic API). Co-authored-by: Claude Opus 4.8 --- .../threads/upgrading-to-typescript-7.mdx | 127 ++++++++++++++ .../why-typescript-7-is-written-in-go.mdx | 156 ++++++++++++++++++ src/lib/thread-covers.ts | 4 + 3 files changed, 287 insertions(+) create mode 100644 src/content/threads/upgrading-to-typescript-7.mdx create mode 100644 src/content/threads/why-typescript-7-is-written-in-go.mdx diff --git a/src/content/threads/upgrading-to-typescript-7.mdx b/src/content/threads/upgrading-to-typescript-7.mdx new file mode 100644 index 0000000..e6f7e99 --- /dev/null +++ b/src/content/threads/upgrading-to-typescript-7.mdx @@ -0,0 +1,127 @@ +--- +title: "Upgrading to TypeScript 7: what actually breaks" +description: "A field guide to the TypeScript 7 migration: the compiler options that are now hard errors, the missing programmatic API, and why a Next.js + MDX site like this one still needs TypeScript 6 in the editor." +date: 2026-07-12 +tags: ["typescript", "tooling", "migration"] +--- + +[TypeScript 7 is 8-12x faster](/threads/why-typescript-7-is-written-in-go), which makes the +upgrade tempting to do on a Friday afternoon. Don't. The speed is real, but 7.0 is the +release where the team cashed in every deprecation they've been sitting on *and* shipped +without a programmatic API. Whether you can move depends less on your code than on your +toolchain. + +Here's the honest checklist, in the order the breakage actually hits you. + +## 1. Options that are now hard errors + +TypeScript 6 made these warnings. TypeScript 7 makes them errors, so `tsc` refuses to start +rather than silently doing something else. Removed outright: + +| Removed | What to do instead | +| --- | --- | +| `target: es5` | Target a modern runtime; downlevel in your bundler if you truly must | +| `downlevelIteration` | Gone with ES5 | +| `moduleResolution: node` / `node10` | `nodenext` (Node) or `bundler` (Vite, webpack, Next) | +| `module: amd` / `umd` / `systemjs` / `none` | `esnext` | +| `baseUrl` | `paths`, resolved relative to the project root | +| `esModuleInterop: false`, `allowSyntheticDefaultImports: false` | Cannot be disabled; delete them | + +If you're on a modern setup, most of this is a no-op. If you're carrying a `tsconfig.json` +that has been copy-pasted since 2019, this is where your afternoon goes. + +## 2. Defaults that changed under you + +More dangerous than the removals, because these don't error, they just change what your +build means. + +- **`strict: true` is the default.** If you were implicitly relying on `strict` being off, + you now have every error `strict` catches, all at once. Set `"strict": false` explicitly + if you're not ready — but note you're now opting *out*, in writing, which is the point. +- **`module: esnext` is the default.** +- **`noUncheckedSideEffectImports: true`** — a bare `import "./thing"` that resolves to + nothing is now an error. This catches real bugs and a few false ones (side-effect imports + of CSS or assets need the right ambient declarations). +- **`stableTypeOrdering: true`, and it cannot be disabled.** The checker is parallel now, so + deterministic type ordering has to be enforced rather than assumed. +- **`rootDir` now defaults to `./`.** If your `tsconfig.json` lives above your sources, your + output layout just changed. Say what you mean: + + ```json + { + "compilerOptions": { + "rootDir": "./src" + } + } + ``` + +- **`types` defaults to `[]`** instead of auto-discovering everything in + `node_modules/@types`. This is a genuinely good change (auto-discovery was a common source + of "why is `describe` a global in my production build") and a genuinely annoying one, + because you must now list what you use: + + ```json + { + "compilerOptions": { + "types": ["node", "vitest/globals"] + } + } + ``` + + Missing globals after upgrading — `process`, `describe`, `expect` — is almost always this. + +## 3. Checked JavaScript got stricter + +If you run `checkJs` over a JS codebase with JSDoc types, several patterns are gone: values +can no longer stand in for types (use `typeof x`), `@enum` is no longer recognized (use +`@typedef`), the postfix `!` non-null operator is out, and Closure-style function syntax +isn't supported. Small surface, but if you're a big JSDoc shop it's the whole migration. + +## 4. The one that actually decides your timeline: no API + +TypeScript 7.0 ships with **no programmatic API**. Anything that imports `typescript` and +walks the AST or asks the checker questions does not work on 7 yet. That includes: + +- typescript-eslint (so: most people's lint setup) +- Vue, Svelte, Astro, **MDX** — anything that embeds or templates TypeScript +- Angular's template type-checking +- ts-morph, most codemods, most custom transformers + +The supported path is to run both compilers side by side: TypeScript 7 for the fast CLI +builds, TypeScript 6 via `@typescript/typescript6` (an npm alias) for the tools that still +need the API. A real API is expected in 7.1. + +So the practical question is not "does my code compile under 7" but "which of my tools reach +into the compiler". Most teams will find the answer is *at least one*. + +## What this means for a site like this one + +This site is Next.js App Router plus MDX (that's how these threads are written). MDX needs +the TypeScript API to type-check inside `.mdx`, so per the release notes, MDX projects stay +on TypeScript 6 for now. The realistic setup is split: + +- **CLI type-check and CI:** TypeScript 7. `npm run typecheck` is the single slowest thing + in our pipeline, and it's the thing every push waits on. +- **Editor and lint:** TypeScript 6 until 7.1 lands the API, because typescript-eslint and + the MDX toolchain need it. + +That's not a clean win, and it's worth being clear-eyed about it rather than performing an +upgrade for the changelog entry. A codebase this size type-checks in a couple of seconds +either way. The 8-12x is transformative for a 130-second build and a rounding error for a +2-second one, so we'll take the CI half now and move the editor when the API is real. + +## A migration order that works + +1. Upgrade to the **latest TypeScript 6** first, and fix every deprecation warning. This is + the actual work, and you can do it incrementally while your tooling still functions. +2. Modernize `tsconfig.json`: `moduleResolution` to `bundler` or `nodenext`, drop `baseUrl`, + drop `esModuleInterop: false`, make `rootDir` and `types` explicit. +3. Turn `strict` on, or explicitly turn it off, so nothing changes silently under you. +4. Inventory what touches the compiler API: lint, test transforms, codemods, framework + plugins. That list is your gate. +5. Run TypeScript 7 for CLI builds and CI, keep TypeScript 6 wherever the API is required. +6. Revisit when 7.1 ships an API and the ecosystem catches up. + +Steps 1-3 are worth doing even if you never adopt 7. That's usually the sign of a +well-designed breaking release: the migration path improves your codebase whether or not you +arrive at the destination. diff --git a/src/content/threads/why-typescript-7-is-written-in-go.mdx b/src/content/threads/why-typescript-7-is-written-in-go.mdx new file mode 100644 index 0000000..b823ee1 --- /dev/null +++ b/src/content/threads/why-typescript-7-is-written-in-go.mdx @@ -0,0 +1,156 @@ +--- +title: "Why TypeScript 7 is written in Go" +description: "TypeScript 7 shipped on July 8, 2026 with an 8-12x faster compiler. The speedup isn't a clever optimization, it's the consequence of leaving a runtime that couldn't express the thing the checker wanted to do all along." +date: 2026-07-12 +tags: ["typescript", "compilers", "go", "concurrency"] +--- + +TypeScript 7.0 [shipped on July 8, 2026](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/). +The headline is the number: vscode's build went from 125.7s to 10.6s, sentry from 139.8s +to 15.7s, playwright from 12.8s to 1.47s. Roughly 8-12x, plus 6-26% less memory. Editors +got the bigger win in practice — opening a file with errors in VS Code fell from 17.5 +seconds to 1.3. + +The interesting part isn't the number. It's that the number came from a *port*, not a +redesign. Same architecture, same algorithms, same type system, largely the same code +structure, moved from TypeScript to Go. When a straight port makes something an order of +magnitude faster, the old implementation wasn't slow because it was badly written. It was +slow because the runtime under it couldn't express what the program actually wanted to do. + +## Where the time was going + +`tsc` was a TypeScript program compiled to JavaScript, running on Node. That gives you +three costs that no amount of micro-optimization removes. + +**You pay for the JIT, every time.** A compiler run is short and cold. V8 has to parse, +interpret, profile and eventually optimize hot functions — but batch `tsc` often exits +before the JIT has finished paying itself back. The checker is a mess of megamorphic call +sites (a `Type` can be one of dozens of shapes), which is exactly the pattern inline caches +handle worst. + +**Every node is a heap object.** ASTs, symbols, types: millions of small objects, each a +V8 object with a hidden class and a pointer chase to reach any field. Type checking is +pointer-chasing all the way down, so cache behavior *is* the performance story, and +JavaScript gives you no control over layout. + +**And the hard one: you cannot share memory.** Node's concurrency story is worker threads +with structured-cloned messages or `SharedArrayBuffer` (raw bytes, no objects). The type +checker's core structure is a giant cyclic graph — symbol tables pointing at declarations, +declarations at AST nodes, AST nodes back at symbols, types recursing into themselves. To +check two files in parallel, both workers need to *read that same graph*. Serializing it +per worker costs more than the checking does. So `tsc` stayed single-threaded, on machines +where 8 or 16 idle cores were watching it work. + +That last constraint is the whole story. The checker is embarrassingly parallel by nature — +files are mostly independent, and the shared graph is read-mostly once it's built. The +algorithm wanted threads. The runtime had none to give. + +## Why Go, and not Rust + +This is where most of the noise was, and the reasoning is more interesting than the tribal +answer. + +Go's memory model is the one the compiler already assumed: a garbage-collected heap where +any goroutine can hold a pointer to any object, and goroutines share one address space. You +can spawn workers that all read the same symbol tables with no copying, no `Arc`, no +serialization. That is precisely the capability JavaScript couldn't offer, and it's where +the 10x lives. + +Rust would have fought the data model. The compiler's graph is *deliberately* cyclic: a +node points at its parent and its children; a symbol points at the declarations that create +it, which point back at the symbol; recursive types loop by construction. That's ownership's +worst case. You'd end up with `Rc>` or arena indices everywhere — either +paying the cost you were trying to avoid, or rewriting the type checker's data flow from +scratch. And a rewrite is a different, much riskier project than a port: you have to +re-derive twenty years of accumulated behavior that only exists as code. + +The port mattered for a second reason. The existing compiler is written in a function-heavy, +closure-and-data style rather than a deeply object-oriented one, which maps onto Go almost +one-to-one. That kept the port mostly mechanical, and mechanical means the type system's +thousand undocumented edge cases survive the move. + +Go isn't "faster than Rust" here. It's the language whose defaults match the shape of this +specific program, at a level of effort that made the project finishable. + +## What native + threads actually buys + +Three separate wins, worth keeping apart: + +1. **Native code.** No JIT warmup, no deopt cliffs. Structs are laid out flat, so walking + an AST touches fewer cache lines. Call this a solid constant factor. +2. **Shared-memory parallelism.** Parsing, checking and emitting run concurrently across + goroutines over one graph. This is the multiplier, and it's the one that was previously + impossible rather than merely slow. +3. **Control over allocation.** A compiler allocates in bulk and frees in bulk. In Go you + can shape that; in JavaScript you get whatever V8 decides. + +The parallelism is exposed, not hidden. TypeScript 7 adds `--checkers` (default 4) for +type-checking workers, `--builders` for parallelizing project references, and +`--singleThreaded` when you need deterministic behavior for debugging: + +```bash +# More checkers: faster on big projects, more memory. +tsc --checkers 8 + +# Reproducible ordering while chasing a compiler bug. +tsc --singleThreaded +``` + +The team reports up to 16.7x with `--checkers 8` on large projects, trading memory for it. +That knob existing at all is the tell: concurrency is now a first-class part of the +compiler's design, not an afterthought bolted onto a single-threaded core. + +There's a subtle consequence of going parallel, and TypeScript 7 handles it rather than +ignoring it. If workers check files in nondeterministic order, the order that types get +*created* in can vary, and anything that prints types — declaration emit, error messages, +union ordering — could shift between runs. So `stableTypeOrdering` is on and cannot be +turned off. Determinism is now a property the compiler has to maintain deliberately, because +the machine underneath it no longer provides it for free. + +## The editor was always the real workload + +Batch builds are the number people quote, but the language service is where developers +actually feel a compiler. It's a long-lived process holding the same graph, answering +latency-sensitive questions: hover, completions, go-to-definition, find-all-references. + +TypeScript 7 rebuilt this on the Language Server Protocol with multithreading, and the +numbers that matter aren't speed at all: an 80% reduction in failing language server +commands and 60% fewer server crashes. A single-threaded server that blocks for 17 seconds +on a big file isn't slow, it's *broken* — it drops requests, times out, and gets killed. A +lot of what everyone experienced as TypeScript being flaky in large repos was a +concurrency-starved process failing under load. + +They also rewrote the file watcher, porting Parcel's C++ watcher to Go. Watching is one of +those problems that looks trivial and is not: it's where "my editor didn't notice the file +changed" bugs are born. + +## What it cost + +TypeScript 7.0 ships **without a programmatic API**. Not a smaller one — none. Everything +that imports `typescript` and pokes at the AST (typescript-eslint, ts-morph, Vue, Svelte, +Astro, MDX, Angular's template checker, most codemods) cannot run on TypeScript 7 today. The +compatibility answer is to keep TypeScript 6 side by side via `@typescript/typescript6`, +with an API promised in 7.1. + +That's a big bill, and it's honest about the tradeoff being made. A JavaScript API handing +out object references into a graph is a very different thing to design when that graph is +being read by a pool of goroutines and owned by a Go garbage collector. They shipped the +compiler and deferred the boundary rather than shipping a bad boundary and living with it +for a decade. Worth respecting, and worth knowing before you plan an upgrade — we wrote up +[what the migration actually involves](/threads/upgrading-to-typescript-7) separately. + +## The part worth stealing + +Nobody found a magic algorithm. They took a program whose *natural* structure was a +shared, read-mostly graph traversed in parallel, and moved it to a runtime that can +represent that structure. The 10x wasn't created; it was already there, sitting behind an +abstraction that couldn't reach it. + +Which is the useful question to carry back to your own systems: not "how do I make this +loop faster", but "what is this program obviously trying to do, and what is stopping it?" +Sometimes the answer is a better data structure. Sometimes the honest answer is that the +platform you chose cannot express your problem, and every optimization from here is +interest payments on that decision. + +The TypeScript team spent a year answering that question with a full port. The rest of us +usually get to answer it for the price of a weekend spike. diff --git a/src/lib/thread-covers.ts b/src/lib/thread-covers.ts index 6695532..ed88463 100644 --- a/src/lib/thread-covers.ts +++ b/src/lib/thread-covers.ts @@ -3,6 +3,8 @@ import notesWindow from "@/images/notes-window.jpg"; import wireframes from "@/images/wireframes.jpg"; import tornPaper from "@/images/torn-paper.jpg"; import forceNote from "@/images/the-force-note.jpg"; +import colorStacks from "@/images/color-stacks.jpg"; +import planningNotes from "@/images/planning-notes.jpg"; /** * Maps a thread slug to its cover photo. Threads without an entry simply render @@ -14,4 +16,6 @@ export const threadCovers: Record = { "how-semantic-search-works": wireframes, "an-mcp-server-for-your-notes": tornPaper, "what-local-first-buys-you": forceNote, + "why-typescript-7-is-written-in-go": colorStacks, + "upgrading-to-typescript-7": planningNotes, }; From a44b7e1aa5b9eb3fd8d683df9d509f7fba380e01 Mon Sep 17 00:00:00 2001 From: Shahid Raza <50213579+shahid-io@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:55:24 +0530 Subject: [PATCH 2/6] feat(threads): add thread on Claude routines and cron (#57) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New /threads post: "Cron was easy because the job was dumb" — how a Claude routine is a cron job whose payload is an agent, and which of cron's guarantees (determinism, safe retries, overlap) that breaks. Co-authored-by: Claude Opus 4.8 --- .../threads/claude-routines-and-cron.mdx | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 src/content/threads/claude-routines-and-cron.mdx diff --git a/src/content/threads/claude-routines-and-cron.mdx b/src/content/threads/claude-routines-and-cron.mdx new file mode 100644 index 0000000..94e3a1a --- /dev/null +++ b/src/content/threads/claude-routines-and-cron.mdx @@ -0,0 +1,145 @@ +--- +title: "Cron was easy because the job was dumb" +description: "Claude routines are cron jobs where the payload is an agent instead of a script. That one swap breaks most of the assumptions cron quietly relied on — determinism, idempotency, cheap retries — and it changes how you should design the thing you schedule." +date: 2026-07-17 +tags: ["ai", "agents", "cron", "distributed-systems"] +--- + +Cron is one of the most durable pieces of software ever written. You give it five fields and +a command, and for forty years it has run that command on time and gotten out of the way. It +works because it makes almost no promises. It fires a process; it does not care what the +process does, whether it succeeded, or whether the last one is still running. + +Claude *routines* — scheduled agents that run on a cron expression — look like the same idea +with a smarter payload. Point one at "every morning at 8, review yesterday's failed CI runs +and open an issue for anything that looks like a real regression," pick a schedule, and it +runs on its own. Underneath, the scheduling primitive really is just cron. + +But the payload is no longer a script. It's a nondeterministic agent that reads, decides, and +writes to the outside world. That single swap invalidates most of the assumptions cron got to +be simple by ignoring. This is a note about which assumptions break, and how to design a +routine so they don't hurt. + +## A routine is a cold start every time + +The first thing to internalize: a scheduled run does **not** inherit the conversation you were +in when you created it. It starts cold. No memory of "the above," no working directory you had +open, no half-finished reasoning. The prompt you save is the entire context the next run gets. + +This is the right design — a job that only works because of state trapped in one person's +terminal session is not a job, it's a demo — but it flips how you write the instruction. You're +not talking to an assistant that already knows what you meant. You're writing a spec for a +stranger who will execute it once, alone, months from now: + +```text +Bad: "do that check we talked about and let me know" +Good: "Fetch the last 24h of runs from the `deploy-production` workflow in + BiSemaphore/binarysemaphore. For each failure, read the logs. If the + failure is a real regression (not a flaky timeout or a cancelled run), + open a GitHub issue titled `CI regression: ` with the failing step + and a link. If everything passed, do nothing and post no message." +``` + +Everything the run needs — repo, tool, definition of "real regression," what to do on the happy +path — is in the text, because there is nothing else. The discipline this forces is the same +discipline behind a good `README` or a good on-call runbook: assume the reader has none of your +context, because they don't. + +## Cron's promises, and which ones the agent revokes + +Cron's simplicity came from a short list of things it refused to guarantee. Each one was fine +when the payload was `backup.sh`. Each one is now yours to think about. + +| Cron quietly assumes | Fine for a script because | Now that the payload is an agent | +| --- | --- | --- | +| The job is deterministic | Same input, same output | Same prompt can produce different actions each run | +| Retrying is safe | Re-running `rsync` is idempotent | A retry might file the issue *again* | +| Overlap is your problem | Two `backup.sh` rarely race | Two runs can both act on the same event | +| Exit code 0 means success | The script knows if it failed | "Success" is a judgement the model is making | +| Missed runs just… don't happen | You'll notice a stale backup | A skipped morning review is silent | + +None of these are reasons not to use routines. They're the checklist you'd apply to any +unattended distributed job, surfaced by the fact that the payload can now think. The good news +is the fixes are old and well understood. + +### Make the action idempotent, not the agent + +You cannot make the model deterministic, and you shouldn't try. What you *can* do is make the +side effect safe to repeat. This is the same move you make with any at-least-once system: push +idempotency into the write, not the caller. + +Instead of "open an issue," the instruction becomes "open an issue **if one with this title +doesn't already exist**." Instead of "send the summary," it's "send the summary for *this +date*, and check the channel for one already posted today first." The routine is allowed to run +twice — because eventually it will — and the second run is a no-op. A cron job for a dumb script +gets this for free. A routine gets it because you designed the target of the action to tolerate +a duplicate. + +### Decide what overlap means before it happens + +If your review job takes eleven minutes and you schedule it every five, cron will happily start +a second one while the first is still going. For `backup.sh` that's an annoyance. For an agent +with write access it's two runs reasoning about the same world and possibly both acting on it. + +Pick a schedule with generous headroom over the worst-case runtime, and lean on the idempotent +writes above as the real backstop, since headroom is a guess and idempotency is a guarantee. + +## The five fields still bite + +The agent is the interesting part, but the boring part — the cron expression itself — is still +where a lot of routines quietly go wrong. Three classics survive intact into the AI era: + +- **Time zones.** `0 8 * * *` is 8am in *some* zone. If you don't know which, you don't know + when your routine runs. State the zone explicitly; don't let a server's `UTC` default decide + your "every morning" is happening at midnight local. +- **Daylight saving.** Twice a year a wall-clock time either doesn't exist or happens twice. A + job at `2:30` on the spring-forward night can be skipped entirely; on the fall-back night it + can fire twice. If the run matters, don't schedule it in the 1am–3am window, and — again — + make it idempotent so the double-fire is harmless. +- **The day-of-month / day-of-week trap.** When you set *both* fields five, + cron treats them as **OR**, not AND. `0 9 13 * 5` is not "the 13th if it's a Friday." It's + "the 13th, *and also* every Friday." This one has been surprising people since the 1970s and + it will surprise you too. + +The routine wrapper doesn't rescue you from any of this. Cron semantics are cron semantics; the +agent just does something more expensive than usual when the expression is wrong. + +## Routines vs. loops: schedule vs. session + +There's a neighbouring feature worth drawing a line against, because they solve different +problems. A **loop** re-runs a prompt on an interval *inside the current session* — it keeps +your context and is meant for "watch this thing until it's done": babysit a deploy, poll a build, +iterate on a task while you're around. A **routine** is a scheduled *cloud* run with no session +and no you: it wakes up cold, does one self-contained job, and exits. + +The rule of thumb: if the work needs the conversation you're in right now, it's a loop. If the +work should happen whether or not you're at your machine, it's a routine. Reaching for a routine +to babysit something you're actively watching gives you a cold agent fumbling for context it +would have had for free in-session. Reaching for a loop to run a nightly job means it only runs +on the nights you happened to leave the session open. + +## Design a routine like an unattended job, because it is one + +Strip away the novelty and a routine is a cron-scheduled batch job whose worker happens to be a +language model. The engineering that makes it trustworthy is the same engineering that makes any +unattended job trustworthy, and none of it is new: + +1. **Keep the scope narrow.** One routine, one job. "Review CI failures" is a routine. "Manage + the repo" is a wish. A tight objective is easier to write, cheaper to run, and far easier to + trust when nobody's watching. +2. **Make success and failure observable.** A silent routine is indistinguishable from a broken + one. Have it leave a trace — an issue, a message, a line in a log — even on the "nothing to + do" path, at least until you trust it. Cron mailed you the job's stdout for exactly this + reason. +3. **Make the writes idempotent.** Covered above, and it's the single highest-leverage habit. + Assume at-least-once, design for it, stop worrying about double-fires. +4. **Bound the blast radius.** The same instinct as [starting an MCP server + read-only](/threads/an-mcp-server-for-your-notes): give the routine exactly the reach its job + needs and nothing more. An unattended agent with broad write access is a standing risk that + fires on a timer. + +Cron got to be simple because it never had to understand its payload. Routines hand the payload +a mind, which is genuinely useful — a nightly job that can *read logs and judge* is worth a +lot — but the price is that all the guarantees cron waved off are now yours to provide. Provide +them, and a routine is a tireless teammate that shows up on schedule. Skip them, and it's a very +articulate way to do the wrong thing at 8am every day. From 7ec2667c062ea587dc170d26ddf2e64d87bee943 Mon Sep 17 00:00:00 2001 From: Shahid Raza <50213579+shahid-io@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:43:23 +0530 Subject: [PATCH 3/6] feat(threads): deepen Claude routines thread, add reading enhancements (#59) Thread: - Much deeper, more practical: routine anatomy, a gallery of real routines (CI triage, dependency digest, stale sweep, on-call handoff, docs freshness) with schedules and prompts, idempotency patterns, a cron syntax refresher, and an observability section. - Adds the companion YouTube link and a `claude` tag (tags are derived from frontmatter, so it becomes a filter automatically). Reusable components (all wired into MDX): - `annotate.tsx`: hand-drawn pen marks (Underline/Circle/Box/Strike/Highlight) that draw themselves on scroll, gated behind prefers-reduced-motion. Soft candy colours (blue/violet/lime/sun), no red. - `table-of-contents.tsx`: sticky "On this page" right rail with scroll-spy. - `cron-diagram.tsx`: styled cron field breakdown, replacing brittle ASCII art. - `code-block.tsx`: copy-to-clipboard button on thread code blocks. Co-authored-by: Claude Opus 4.8 --- src/app/threads/[slug]/page.tsx | 9 + src/components/annotate.tsx | 189 ++++++++++++++ src/components/code-block.tsx | 46 ++++ src/components/cron-diagram.tsx | 64 +++++ src/components/table-of-contents.tsx | 96 +++++++ .../threads/claude-routines-and-cron.mdx | 241 ++++++++++++++---- src/mdx-components.tsx | 13 + 7 files changed, 602 insertions(+), 56 deletions(-) create mode 100644 src/components/annotate.tsx create mode 100644 src/components/code-block.tsx create mode 100644 src/components/cron-diagram.tsx create mode 100644 src/components/table-of-contents.tsx diff --git a/src/app/threads/[slug]/page.tsx b/src/app/threads/[slug]/page.tsx index d635851..8d61496 100644 --- a/src/app/threads/[slug]/page.tsx +++ b/src/app/threads/[slug]/page.tsx @@ -11,6 +11,7 @@ import { threadCovers } from "@/lib/thread-covers"; import { Photo } from "@/components/photo"; import { Header } from "@/components/header"; import { Footer } from "@/components/footer"; +import { TableOfContents } from "@/components/table-of-contents"; type Params = { slug: string }; @@ -67,6 +68,7 @@ export default async function ThreadPage({
+
+ + +