Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,46 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {

### Tailwind over inline styles

Always write Tailwind classes for styling wherever possible.
Always reach for Tailwind utility classes first. The codebase uses Tailwind v4
with CSS variables from Radix Themes (e.g. `--gray-12`, `--space-3`,
`--radius-2`); use Tailwind v4's CSS-var shorthand to bridge them — `text-(--gray-12)`,
`bg-(--gray-2)`, `rounded-(--radius-2)`, `border-(--gray-5)`. Use arbitrary values
(`text-[13px]`, `pl-[18px]`) when the design token doesn't have a named match.

Inline `style={{}}` is acceptable in three cases only:

1. **Genuinely dynamic values** computed at runtime that can't be a class —
e.g. `style={{ width: `${pxFromHook}px` }}`, `style={{ transform: `translateY(${y}px)` }}`,
pixel positions from measurement, data-driven colors that don't fit a fixed palette.
2. **Library configuration** passed to non-React libraries (CodeMirror's
`EditorView.theme(...)`, xterm.js options, etc.).
3. **CSS variables set from JS** that downstream classes consume —
`style={{ "--row-color": item.color }}` paired with `className="bg-(--row-color)"`.

Do NOT use inline `style` for:

- Color tokens (use `text-(--gray-12)`, `bg-(--gray-2)`, `border-(--gray-5)`)
- Spacing (use `p-3`, `mt-2`, `pl-4`, `gap-2`) — Radix `--space-N` matches Tailwind's
spacing scale 1:1 for `--space-1`..`--space-4`; `--space-5` = `6`, `--space-6` = `8`, etc.
- Layout primitives (`shrink-0`, `min-w-0`, `flex-1`, `overflow-y-auto`, `w-full`, `h-full`)
- Borders (`border border-(--gray-5)`), radii (`rounded-(--radius-2)` or `rounded-full`)
- Cursors (`cursor-pointer`, `cursor-col-resize`)
- Opacity (`opacity-50`), text-align, text-transform (`uppercase`), white-space, word-break
- Position (`absolute`, `relative`, `fixed`), z-index (`z-10`, `z-[201]`), inset (`inset-0`)
- Animations that map to a Tailwind utility (`animate-spin`)
- Conditional values that can be `className={cond ? "x" : "y"}` or
`className={\`base-classes ${cond ? "active-classes" : "inactive-classes"}\`}`

Default line-heights have been tightened (`text-sm` ships with etc.)
in [apps/code/src/renderer/styles/globals.css](./apps/code/src/renderer/styles/globals.css).
Don't add a `leading-*` class for body text unless you specifically want a non-default
line-height. For arbitrary sizes (`text-[13px]`), pair with `leading-snug` for body
text or `leading-tight` for titles.

When writing a custom React component that wraps a styled element, accept BOTH
`className?: string` and `style?: React.CSSProperties` props and merge the
`className` into the inner element's classes (e.g. ``className={`base-classes ${className ?? ""}`}``).
This lets call sites override styling via Tailwind without forcing inline `style`.

### Store / Service Boundary

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
18 changes: 9 additions & 9 deletions apps/code/src/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ function buildAppMenu(): MenuItemConstructorOptions {
},
},
{ type: "separator" },
{
label: "Settings...",
accelerator: "CmdOrCtrl+,",
click: () => {
container.get<UIService>(MAIN_TOKENS.UIService).openSettings();
},
},
{ type: "separator" },
...(!isDevBuild()
? [
{
Expand All @@ -80,21 +88,13 @@ function buildAppMenu(): MenuItemConstructorOptions {
.triggerMenuCheck();
},
},
{ type: "separator" as const },
]
: []),
{ type: "separator" },
{ role: "hide" as const },
{ role: "hideOthers" as const },
{ role: "unhide" as const },
{ type: "separator" as const },
{
label: "Settings...",
accelerator: "CmdOrCtrl+,",
click: () => {
container.get<UIService>(MAIN_TOKENS.UIService).openSettings();
},
},
{ type: "separator" as const },
{ role: "quit" as const },
],
};
Expand Down
52 changes: 9 additions & 43 deletions apps/code/src/renderer/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,11 @@ interface CodeBlockProps {
size?: CodeBlockSize;
}

const sizeStyles: Record<
CodeBlockSize,
{ fontSize: string; lineHeight: string }
> = {
"1": {
fontSize: "var(--font-size-1)",
lineHeight: "var(--line-height-1)",
},
"1.5": {
fontSize: "var(--font-size-1-5)",
lineHeight: "var(--line-height-1-5)",
},
"2": {
fontSize: "var(--font-size-2)",
lineHeight: "var(--line-height-2)",
},
"3": {
fontSize: "var(--font-size-3)",
lineHeight: "var(--line-height-3)",
},
const SIZE_TO_CLASS: Record<CodeBlockSize, string> = {
"1": "text-[13px]",
"1.5": "text-[13.5px]",
"2": "text-sm",
"3": "text-base",
};

function extractText(children: ReactNode): string {
Expand All @@ -46,7 +31,7 @@ function extractText(children: ReactNode): string {
}

export function CodeBlock({ children, size = "1" }: CodeBlockProps) {
const styles = sizeStyles[size];
const sizeClass = SIZE_TO_CLASS[size];
const [copied, setCopied] = useState(false);

const handleCopy = useCallback(() => {
Expand All @@ -57,23 +42,9 @@ export function CodeBlock({ children, size = "1" }: CodeBlockProps) {
}, [children]);

return (
<div className="group" style={{ position: "relative" }}>
<div className="group relative">
<pre
style={{
margin: 0,
marginBottom: "var(--space-3)",
padding: "var(--space-3)",
paddingRight: "var(--space-7)",
backgroundColor: "var(--gray-2)",
borderRadius: "var(--radius-2)",
border: "1px solid var(--gray-4)",
fontFamily: "var(--code-font-family)",
fontSize: styles.fontSize,
lineHeight: styles.lineHeight,
color: "var(--gray-12)",
overflowX: "auto",
whiteSpace: "pre",
}}
className={`m-0 mb-3 overflow-x-auto whitespace-pre rounded-(--radius-2) border border-(--gray-4) bg-(--gray-2) p-3 pr-10 font-[var(--code-font-family)] text-(--gray-12) ${sizeClass}`}
>
{children}
</pre>
Expand All @@ -83,14 +54,9 @@ export function CodeBlock({ children, size = "1" }: CodeBlockProps) {
color="gray"
onClick={handleCopy}
style={{
position: "absolute",
top: "var(--space-1)",
right: "var(--space-1)",
opacity: 0,
transition: "opacity 0.15s",
cursor: "pointer",
}}
className="group-hover:!opacity-100 [&]:hover:!opacity-100"
className="group-hover:!opacity-100 [&]:hover:!opacity-100 absolute top-1 right-1 cursor-pointer opacity-0"
aria-label="Copy code"
>
{copied ? <Check size={14} /> : <Copy size={14} />}
Expand Down
2 changes: 1 addition & 1 deletion apps/code/src/renderer/components/ConnectivityPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function ConnectivityPrompt({
<Dialog.Title className="mb-0">No internet connection</Dialog.Title>
</Flex>
<Dialog.Description>
<Text size="2" color="gray">
<Text color="gray" className="text-sm">
PostHog Code requires an internet connection to use AI features.
Check your connection and try again.
</Text>
Expand Down
17 changes: 5 additions & 12 deletions apps/code/src/renderer/components/Divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,16 @@ interface DividerProps {
size?: DividerSize;
}

const sizeStyles: Record<DividerSize, { marginY: string }> = {
"1": { marginY: "var(--space-2)" },
"2": { marginY: "var(--space-3)" },
"3": { marginY: "var(--space-4)" },
const SIZE_TO_MARGIN: Record<DividerSize, string> = {
"1": "my-2",
"2": "my-3",
"3": "my-4",
};

export function Divider({ size = "2" }: DividerProps) {
const styles = sizeStyles[size];

return (
<hr
style={{
border: "none",
borderTop: "1px solid var(--gray-6)",
marginTop: styles.marginY,
marginBottom: styles.marginY,
}}
className={`border-0 border-t border-t-(--gray-6) ${SIZE_TO_MARGIN[size]}`}
/>
);
}
14 changes: 6 additions & 8 deletions apps/code/src/renderer/components/DotPatternBackground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,25 @@ import { useId } from "react";
const DOT_FILL = "var(--gray-6)";

interface DotPatternBackgroundProps {
className?: string;
style?: React.CSSProperties;
}

export function DotPatternBackground({ style }: DotPatternBackgroundProps) {
export function DotPatternBackground({
className,
style,
}: DotPatternBackgroundProps) {
const patternId = useId();

return (
<svg
aria-hidden="true"
style={{
position: "absolute",
bottom: 0,
left: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
opacity: 0.4,
maskImage: "linear-gradient(to top, black 0%, transparent 100%)",
WebkitMaskImage: "linear-gradient(to top, black 0%, transparent 100%)",
...style,
}}
className={`pointer-events-none absolute bottom-0 left-0 h-full w-full opacity-40 ${className ?? ""}`}
>
<defs>
<pattern
Expand Down
5 changes: 1 addition & 4 deletions apps/code/src/renderer/components/DotsCircleSpinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,10 @@ export function DotsCircleSpinner({

return (
<span
className={className}
className={`inline-flex items-center justify-center ${className}`}
style={{
display: "inline-flex",
width: size,
height: size,
alignItems: "center",
justifyContent: "center",
fontSize: size,
lineHeight: 1,
}}
Expand Down
10 changes: 2 additions & 8 deletions apps/code/src/renderer/components/DraggableTitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,9 @@ import { HEADER_HEIGHT } from "./HeaderRow";
export function DraggableTitleBar() {
return (
<Box
className="drag"
className="drag absolute top-0 right-0 left-0 z-10 w-full"
style={{
height: HEADER_HEIGHT, // Same as the more complex HeaderRow used in the main app
width: "100%",
position: "absolute",
top: 0,
left: 0,
right: 0,
zIndex: 10,
height: HEADER_HEIGHT,
}}
/>
);
Expand Down
4 changes: 2 additions & 2 deletions apps/code/src/renderer/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export class ErrorBoundary extends Component<Props, State> {
</Callout.Icon>
<Callout.Text>
<Flex direction="column" gap="2">
<Text weight="medium">Something went wrong</Text>
<Text size="1" className="text-gray-11">
<Text className="font-medium">Something went wrong</Text>
<Text className="text-[13px] text-gray-11">
{this.state.error?.message || "An unexpected error occurred"}
</Text>
<Flex gap="2" mt="2">
Expand Down
38 changes: 7 additions & 31 deletions apps/code/src/renderer/components/FullScreenLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,60 +31,36 @@ export function FullScreenLayout({
<Flex
direction="column"
height="100vh"
style={{ position: "relative", overflow: "hidden" }}
className="relative overflow-hidden"
>
<DraggableTitleBar />

<div
style={{
position: "absolute",
inset: 0,
backgroundColor: "var(--color-background)",
}}
/>
<div className="absolute inset-0 bg-(--color-background)" />
<DotPatternBackground />

<Flex
direction="column"
flexGrow="1"
style={{
position: "relative",
zIndex: 1,
minHeight: 0,
width: "100%",
}}
className="relative z-[1] min-h-0 w-full"
>
<img
src={isDarkMode ? phWordmarkWhite : phWordmark}
alt="PostHog"
style={{
height: "40px",
objectFit: "contain",
alignSelf: "flex-start",
marginLeft: 32,
marginTop: "clamp(24px, 6vh, 80px)",
flexShrink: 0,
}}
className="mt-[clamp(24px,6vh,80px)] ml-8 h-10 shrink-0 self-start object-contain"
/>

<Flex
direction="column"
flexGrow="1"
overflow="hidden"
style={{ minHeight: 0 }}
className="min-h-0"
>
{children}
</Flex>

<Flex
justify="between"
style={{
position: "absolute",
bottom: 20,
left: 32,
right: 32,
zIndex: 2,
}}
className="absolute right-[32px] bottom-[20px] left-[32px] z-[2]"
>
{footerLeft ?? (
<Button
Expand All @@ -96,7 +72,7 @@ export function FullScreenLayout({
url: EXTERNAL_LINKS.discord,
})
}
style={{ opacity: 0.5 }}
className="opacity-50"
>
<Lifebuoy size={14} />
Get support
Expand Down
Loading
Loading