TODAY I LEARNED
by lubosmato
by lubosmato
SSH into a fresh box from Ghostty (or Kitty, WezTerm, foot...) and half your TUIs explode with unknown terminal type. The remote just doesn't have your terminal's terminfo entry, and these custom ones aren't in any distro's ncurses package — they ship with the terminal itself.
The fix: dump terminfo entry with infocmp and pipe it into tic on the remote. It compiles into ~/.terminfo for your user.
I was building an MCP server with Mastra and Zod, exposing tools to Claude Code. Out of nowhere, Claude Code started telling me the MCP server was not authorized and showed zero tools — even ones that worked fine a minute ago.
The culprit: I had added one new tool whose outputSchema was a z.discriminatedUnion(...) at the root. One bad tool took down discovery for the whole server.
MCP transports tool schemas as JSON Schema. Clients (including Claude Code) validate that every tool's input and output schema has type: "object" at the root. A union serializes as { anyOf: [...] } with no root object type, fails that check, and the client rejects the entire tools/list response. The misleading part is the error: Claude Code surfaces it as a generic auth failure, not a schema problem.
Wrap the union in an outer z.object({ ... }). You keep the discriminated union semantics — only the root shape changes from a union to an object with one field that contains the union.
If an MCP server suddenly shows no tools or an auth error after you added or edited one tool, suspect the schema before suspecting auth. Check every new tool's inputSchema and outputSchema — if either starts with a union, wrap it.
PostgreSQL ships with createdb — a CLI wrapper around CREATE DATABASE. No need to open a psql session or remember the SQL syntax. It uses your system user by default, same as psql.
PayloadCMS stores rich text in Lexical format, but the search plugin needs plaintext for indexing. The @payloadcms/richtext-lexical package exports a convertLexicalToPlaintext utility that handles this conversion.
Use it in the beforeSync hook of the search plugin to transform rich text fields before they're indexed.
When using custom CSS variables in React inline styles, TypeScript complains because CSSProperties doesn't know about custom properties.
Use TypeScript module augmentation to extend React's CSSProperties interface:
Mastra's RequestContext lets you customize agent behavior on a per-request basis. By extending the base class with your own type parameters, you get type-safe access to context values like user sessions or feature flags. This is more flexible than creating separate agents - you maintain one agent definition while dynamically adjusting its capabilities based on who’s calling it and what they’re allowed to do.
The satisfies keyword is perfect here because it validates the object shape against Record<BlockSlug, ComponentType<any>> without widening the type. This means TypeScript will error at compile time if you add a new block type in Payload but forget to add the corresponding component. Unlike a type annotation (: Record<...>), satisfies preserves the narrow literal types of the keys, giving you autocomplete and strict key checking while still enforcing the constraint.
React doesn't recognize custom element tag names in JSX, so TypeScript complains. You can fix this by extending React's IntrinsicElements with your component's props alongside DetailedHTMLProps to keep standard React props like children, className, ref, and key working.
You can automatically pick black or white color based on a background color using a only CSS.
The trick abuses relative color syntax in oklch. It extracts the perceptual lightness (l) of the background, subtracts it from a threshold (0.6), and multiplies by infinity. The result is either +infinity (clamped to white) or -infinity (clamped to black). Zero chroma and hue ensure a pure achromatic result.
Perfect for dynamic badges, tags, or any element where the background color is unpredictable. Works in all modern browsers today.
No JS needed for parallax anymore. The animation-timeline: scroll() property lets you drive a CSS animation by scroll position instead of time. Define a regular @keyframes animation with translate3d, set animation-timeline: scroll(root), and each layer moves at its own speed based on a CSS custom property. Stack multiple layers with different --parallax-y values and you've got depth.
infocmp -x xterm-ghostty | ssh user@host -- tic -x -createdb my_nice_db
# instead of:
# psql -c "CREATE DATABASE my_nice_db;"import { z } from "zod"
import { createTool } from "@mastra/core"
// Reusable union — fine on its own
const resultUnion = z.discriminatedUnion("type", [
z.object({ type: z.literal("ok"), pageId: z.string() }),
z.object({ type: z.literal("error"), message: z.string() }),
])
// BAD — top-level union breaks MCP tool discovery.
// Claude Code reports "not authorized" and hides ALL tools on the server.
const brokenTool = createTool({
id: "do-thing",
description: "Does a thing",
inputSchema: z.object({ pageId: z.string() }),
outputSchema: resultUnion, // <-- root is { anyOf: [...] }, no type:"object"
execute: async () => ({ type: "ok" as const, pageId: "x" }),
})
// GOOD — wrap the union in an outer z.object.
// Discriminator semantics are preserved; the root is now a proper object.
const workingTool = createTool({
id: "do-thing",
description: "Does a thing",
inputSchema: z.object({ pageId: z.string() }),
outputSchema: z.object({ result: resultUnion }),
execute: async () => ({ result: { type: "ok" as const, pageId: "x" } }),
})
// Same rule applies to inputSchema — arguably even more strictly,
// since clients use it to render tool params to the model.import { searchPlugin } from "@payloadcms/plugin-search"
import { convertLexicalToPlaintext } from "@payloadcms/richtext-lexical/plaintext"
export const search = searchPlugin({
collections: ["posts"],
beforeSync: async ({ originalDoc, searchDoc }) => {
return {
...searchDoc,
// Convert Lexical JSON to searchable plaintext
description: convertLexicalToPlaintext({
data: originalDoc.description, // Lexical rich text field
}),
}
},
})import { RequestContext as _RequestContext } from "@mastra/core/request-context";
import { Agent } from "@mastra/core/agent"
class RequestContext extends _RequestContext<{
enableCmsTools?: boolean;
}> {}
// In agent config:
export const agent = new Agent({
id: "my-agent",
name: "My Agent",
// ...
tools: ({ requestContext }) => {
const context = requestContext as RequestContext;
const tools = {
searchWebpages,
fetchPage,
// Conditionally add CMS tools:
...(context.get("enableCmsTools")
? {
createPage,
updatePage,
}
: {}),
}
return tools;
},
});
function Component() {
return (
<div
style={{
"--parallax-y": "-420px", // OK
"--other-var": "42px", // Error
}}
/>
)
}
declare module "react" {
interface CSSProperties {
[`--parallax-y`]?: `${number}px`
}
}
import type { Page } from "@/payload-types"
import type { BlockSlug } from "payload"
import type { ComponentType } from "react"
export function RenderBlocks({ blocks }: { blocks: Page["content"][0][] }) {
return blocks.map((block, index) => {
if (!(block.blockType in blockComponents)) {
return null
}
const Block = blockComponents[block.blockType]
return (
<div key={index}>
{/* @ts-expect-error block props should be correct at this point */}
<Block {...block} />
</div>
)
})
}
const blockComponents = {
pageTitle: PageTitleBlock,
sectionHeader: SectionHeaderBlock,
anchorNavigation: AnchorNavigationBlock,
textImage: TextImageBlock,
productCardGrid: ProductCardGridBlock,
topicTeaser: TopicTeaserBlock,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} satisfies Record<BlockSlug, ComponentType<any>>import { useRef, type DetailedHTMLProps, type HTMLAttributes } from "react"
declare module "react/jsx-runtime" {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace JSX {
interface IntrinsicElements {
"my-button": DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement> & {
/** The variant of the button, either "primary" or "secondary". */
variant: "primary" | "secondary"
/** Whether the button is disabled. @default false */
disabled?: boolean
}
}
}
}
export function Page() {
const btnRef = useRef<HTMLButtonElement>(null)
return (
<my-button variant="primary" key="some-key" ref={btnRef}>
Submit
</my-button>
)
}<Badge
style={{
backgroundColor: tag.color,
color: `oklch(from ${tag.color} calc((0.6 - l) * infinity) 0 0)`,
}}
>
{tag.name}
</Badge>
@keyframes parallax {
from {
transform: translate3d(0, 0, 0);
}
to {
transform: translate3d(0, var(--parallax-y), 0);
}
}
.scene {
position: relative;
}
.parallax-layer {
--parallax-y: -20vh;
position: absolute;
inset: 0;
animation: parallax linear both;
animation-timeline: scroll(root);
}
/* Different speeds per layer via custom property */
.bg-layer { --parallax-y: -5vh; }
.fg-layer { --parallax-y: -10vh; }
/* Usage:
<div class="scene">
<div class="parallax-layer bg-layer"></div>
<div class="parallax-layer fg-layer"></div>
<div class="content">Your content here</div>
</div>
*/