Skip to content

Commit fdf7410

Browse files
committed
Feat: New Callouts Component
Signed-off-by: Lee Calcote <lee.calcote@layer5.io>
1 parent cf7477c commit fdf7410

File tree

4 files changed

+176
-27
lines changed

4 files changed

+176
-27
lines changed

src/collections/blog/2026/03-09-why-claude-code-cant-find-your-tools/index.mdx

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ resource: true
1919

2020
import { BlogWrapper } from "../../Blog.style.js";
2121
import { Link } from "gatsby";
22-
import Blockquote from "../../../../reusecore/Blockquote";
23-
import KanvasCTA from "../../../../sections/Kanvas/kanvas-cta";
22+
import Callout from "../../../../reusecore/Callout";
2423

2524
<BlogWrapper>
2625

@@ -33,7 +32,7 @@ import KanvasCTA from "../../../../sections/Kanvas/kanvas-cta";
3332
<h2>The Surprise</h2>
3433

3534
<p>
36-
AI coding assistants like Claude Code, Cline, Aider, and similar tools spawn child shell processes to run commands on your behalf. From where you sit, the terminal looks identical to the one you use every day. But the shell those tools launch is fundamentally different from the one you interact with — and that difference determines which startup files are read, which means it determines what is on your PATH.
35+
AI coding assistants like Claude Code, Gemini, Copilot, Cline, Aider, and similar tools spawn child shell processes to run commands on your behalf. From where you sit, the terminal looks identical to the one you use every day. But the shell those tools launch is fundamentally different from the one you interact with — and that difference determines which startup files are read, which means it determines what is on your PATH.
3736
</p>
3837

3938
<p>
@@ -46,7 +45,7 @@ import KanvasCTA from "../../../../sections/Kanvas/kanvas-cta";
4645
Zsh loads different configuration files depending on how it was launched. There are four main files, and they are read in this order:
4746
</p>
4847

49-
<table>
48+
<table className="table-2">
5049
<thead>
5150
<tr>
5251
<th>File</th>
@@ -102,9 +101,9 @@ import KanvasCTA from "../../../../sections/Kanvas/kanvas-cta";
102101
</li>
103102
</ul>
104103

105-
<Blockquote
106-
quote="A non-interactive, non-login shell sources only ~/.zshenv. Everything in ~/.zshrc is invisible to it — including every PATH export most developers have ever written."
107-
/>
104+
<Callout type="note">
105+
<p>A non-interactive, non-login shell sources only <code>{"~/.zshenv"}</code>. Everything in <code>{"~/.zshrc"}</code> is invisible to it — including every PATH export most developers have ever written.</p>
106+
</Callout>
108107

109108
<h2>What PATH a Non-Interactive Shell Actually Sees</h2>
110109

@@ -163,11 +162,9 @@ zsh -i -c 'which node'
163162
The <code>-i</code> flag forces an interactive shell, which sources <code>{"~/.zshrc"}</code>. If <code>zsh -c 'which gh'</code> prints <code>gh not found</code> but <code>zsh -i -c 'which gh'</code> prints the correct path, your PATH export is in <code>{"~/.zshrc"}</code> and you have confirmed the root cause.
164163
</p>
165164

166-
<div className="note">
167-
<p>
168-
<strong>Quick diagnosis:</strong> Run <code>zsh -c 'which gh'</code> (no <code>-i</code> flag). If this fails but <code>which gh</code> in your normal terminal works, your PATH is only set in <code>{"~/.zshrc"}</code>. Move the relevant exports to <code>{"~/.zshenv"}</code> to fix it.
169-
</p>
170-
</div>
165+
<Callout type="note" title="Quick diagnosis">
166+
<p>Run <code>zsh -c 'which gh'</code> (no <code>-i</code> flag). If this fails but <code>which gh</code> in your normal terminal works, your PATH is only set in <code>{"~/.zshrc"}</code>. Move the relevant exports to <code>{"~/.zshenv"}</code> to fix it.</p>
167+
</Callout>
171168

172169
<h2>The Fix: Move PATH Exports to <code>{"~/.zshenv"}</code></h2>
173170

@@ -233,10 +230,8 @@ zsh -c 'which node'
233230
Moving everything to <code>{"~/.zshenv"}</code> is not the right answer. Some configuration should stay in <code>{"~/.zshrc"}</code> because it only makes sense in interactive contexts or because it has side effects that slow down non-interactive shells unnecessarily.
234231
</p>
235232

236-
<div className="tip">
237-
<p>
238-
<strong>Guiding principle:</strong> If it is an environment variable that a program needs to find another program, it belongs in <code>{"~/.zshenv"}</code>. If it is a user-facing customization for your interactive terminal experience, it belongs in <code>{"~/.zshrc"}</code>.
239-
</p>
233+
<Callout type="tip" title="Guiding principle">
234+
<p>If it is an environment variable that a program needs to find another program, it belongs in <code>{"~/.zshenv"}</code>. If it is a user-facing customization for your interactive terminal experience, it belongs in <code>{"~/.zshrc"}</code>.</p>
240235

241236
<p><strong>Keep in <code>{"~/.zshenv"}</code>:</strong></p>
242237
<ul>
@@ -256,7 +251,7 @@ zsh -c 'which node'
256251
<li><code>zsh</code> plugins and plugin managers</li>
257252
<li>Anything that prints output (welcome messages, <code>neofetch</code>, etc.)</li>
258253
</ul>
259-
</div>
254+
</Callout>
260255

261256
<h2>The Broader Pattern: Any Tool That Spawns Subshells</h2>
262257

@@ -277,11 +272,9 @@ zsh -c 'which node'
277272
If you have ever fixed a <em>works on my machine</em> problem by adding <code>export PATH=...</code> to a CI configuration or a Dockerfile, you have already solved the same class of problem. The <code>{"~/.zshenv"}</code> fix is just the developer workstation equivalent.
278273
</p>
279274

280-
<Blockquote
281-
quote="If a command works in your terminal but fails in a script, a CI job, or an AI agent, the first question to ask is: which startup files does this shell read?"
282-
/>
283-
284-
<KanvasCTA />
275+
<Callout type="tip">
276+
<p>If a command works in your terminal but fails in a script, a CI job, or an AI agent, the first question to ask is: which startup files does this shell read?</p>
277+
</Callout>
285278

286279
<h2>Putting It Together: A Minimal <code>{"~/.zshenv"}</code> Template</h2>
287280

src/collections/blog/Blog.style.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ export const BlogWrapper = styled.div`
5050
table,
5151
td {
5252
text-align: center;
53-
border-top: 1px dotted #3c494f;
53+
border-top: 1px dotted ${(props) => props.theme.secondaryLightColorTwo};
5454
th {
55-
background-color: #3c494f;
55+
background-color: ${(props) => props.theme.secondaryLightColorTwo};
5656
color: #eee;
5757
padding: 0rem 0.5rem;
5858
}
@@ -107,10 +107,10 @@ export const BlogWrapper = styled.div`
107107
,
108108
td {
109109
text-align: center;
110-
border-top: 1px dotted #3c494f;
110+
border-top: 1px dotted ${(props) => props.theme.secondaryLightColorTwo};
111111
th {
112-
background-color: #3c494f;
113-
color: #eee;
112+
background-color: ${(props) => props.theme.secondaryLightColorTwo};
113+
color: ${(props) => props.theme.grey232323ToGreyEEEEEE};
114114
padding: 0rem 0.5rem;
115115
}
116116
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import styled from "styled-components";
2+
3+
// Per-type color palette aligned with Sistent design tokens
4+
const BORDER_COLORS = {
5+
note: "#477DFF", // Sistent info blue
6+
tip: "#00D3A9", // keppelColor / Sistent teal
7+
warning: "#EBC017", // highlightColor / saffron
8+
caution: "#F97316", // Sistent orange
9+
important: "#B32700", // Sistent error red
10+
};
11+
12+
// Title text colors — darker shades for light mode (contrast), full saturation for dark mode
13+
const TITLE_COLORS_LIGHT = {
14+
note: "#2458CC",
15+
tip: "#007A6B",
16+
warning: "#7A5700",
17+
caution: "#C75800",
18+
important: "#8C1A00",
19+
};
20+
21+
const TITLE_COLORS_DARK = {
22+
note: "#7EB3FF",
23+
tip: "#00D3A9",
24+
warning: "#EBC017",
25+
caution: "#F97316",
26+
important: "#FF6B4A",
27+
};
28+
29+
const CalloutStyle = styled.aside`
30+
margin: 1.75rem 0;
31+
border-radius: 0.375rem;
32+
border-left: 4px solid
33+
${({ type }) => BORDER_COLORS[type] || BORDER_COLORS.note};
34+
overflow: hidden;
35+
font-size: 0.95rem;
36+
37+
.callout-header {
38+
display: flex;
39+
align-items: center;
40+
gap: 0.5rem;
41+
padding: 0.55rem 1rem;
42+
background: ${({ type, theme }) => {
43+
const c = BORDER_COLORS[type] || BORDER_COLORS.note;
44+
return theme.DarkTheme ? `${c}33` : `${c}26`;
45+
}};
46+
47+
.callout-icon {
48+
font-size: 1rem;
49+
line-height: 1;
50+
flex-shrink: 0;
51+
}
52+
53+
.callout-title {
54+
font-weight: 700;
55+
font-size: 0.9rem;
56+
line-height: 1;
57+
letter-spacing: 0.01em;
58+
color: ${({ type, theme }) =>
59+
theme.DarkTheme
60+
? TITLE_COLORS_DARK[type] || TITLE_COLORS_DARK.note
61+
: TITLE_COLORS_LIGHT[type] || TITLE_COLORS_LIGHT.note};
62+
}
63+
}
64+
65+
.callout-body {
66+
padding: 0.75rem 1rem;
67+
background: ${({ type, theme }) => {
68+
const c = BORDER_COLORS[type] || BORDER_COLORS.note;
69+
return theme.DarkTheme ? `${c}1a` : `${c}0f`;
70+
}};
71+
72+
p,
73+
li,
74+
code {
75+
color: ${(props) => props.theme.primaryColor};
76+
}
77+
78+
p {
79+
margin: 0 0 0.5rem 0;
80+
line-height: 1.65;
81+
82+
&:last-child {
83+
margin-bottom: 0;
84+
}
85+
}
86+
87+
ul,
88+
ol {
89+
margin: 0 0 0.5rem 0;
90+
padding-left: 1.4rem;
91+
92+
&:last-child {
93+
margin-bottom: 0;
94+
}
95+
}
96+
97+
li {
98+
line-height: 1.65;
99+
margin-bottom: 0.25rem;
100+
}
101+
102+
code {
103+
font-size: 0.875em;
104+
padding: 0.1em 0.3em;
105+
border-radius: 3px;
106+
background: ${({ type, theme }) => {
107+
const c = BORDER_COLORS[type] || BORDER_COLORS.note;
108+
return theme.DarkTheme ? `${c}26` : `${c}1a`;
109+
}};
110+
}
111+
112+
strong {
113+
color: ${(props) => props.theme.primaryColor};
114+
}
115+
}
116+
`;
117+
118+
export default CalloutStyle;

src/reusecore/Callout/index.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React from "react";
2+
import PropTypes from "prop-types";
3+
import CalloutStyle from "./callout.style.js";
4+
5+
const TYPE_CONFIG = {
6+
note: { label: "Note", icon: "ℹ" },
7+
tip: { label: "Tip", icon: "💡" },
8+
warning: { label: "Warning", icon: "⚠" },
9+
caution: { label: "Caution", icon: "⚠" },
10+
important: { label: "Important", icon: "❗" },
11+
};
12+
13+
const Callout = ({ type, title, children }) => {
14+
const config = TYPE_CONFIG[type] || TYPE_CONFIG.note;
15+
return (
16+
<CalloutStyle type={type}>
17+
<div className="callout-header">
18+
<span className="callout-icon" aria-hidden="true">
19+
{config.icon}
20+
</span>
21+
<span className="callout-title">{title || config.label}</span>
22+
</div>
23+
<div className="callout-body">{children}</div>
24+
</CalloutStyle>
25+
);
26+
};
27+
28+
Callout.propTypes = {
29+
type: PropTypes.oneOf(["note", "tip", "warning", "caution", "important"]),
30+
title: PropTypes.string,
31+
children: PropTypes.node.isRequired,
32+
};
33+
34+
Callout.defaultProps = {
35+
type: "note",
36+
};
37+
38+
export default Callout;

0 commit comments

Comments
 (0)