fix: restore tree editor visibility after Tailwind v4 upgrade and add Sentry DSN build arg

- Add missing `flex` class on TreeEditorPage editor wrapper (collapsed canvas to 0 height)
- Rewrite React Flow CSS overrides to use --xy-* custom properties (v12 compat with TW4)
- Move React Flow CSS import from component to index.css (CSS layer ordering)
- Add VITE_SENTRY_DSN build arg to Dockerfile for Railway builds
- Use env var for Sentry DSN in instrument.ts with hardcoded fallback
- Add lessons learned #53-55 to CLAUDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-09 04:36:37 -04:00
parent a900408c3b
commit d06abe5829
6 changed files with 55 additions and 41 deletions

View File

@@ -337,6 +337,12 @@ navigate(`/trees/${newTree.id}/edit`)
**52. Mobile scroll-to-top — use `scrollIntoView`, not `window.scrollTo`:** Mobile browsers (iOS Safari, Firefox Android) often ignore `window.scrollTo()`. Use a ref at the top of the page and call `ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' })` instead. Trigger via `useEffect` on the state change (not inline with `setState`) so the DOM has committed before scrolling.
**53. Flex height chain — every ancestor must be a flex container for `flex-1` to work:** If a child uses `flex-1` to fill its parent, the parent MUST have `display: flex` (the `flex` class). A missing `flex` on any wrapper div breaks the entire height chain, causing React Flow (and any `h-full` descendant) to collapse to 0 height. Debug with: `let el = document.querySelector('.react-flow'); while(el) { console.log(el.getBoundingClientRect().height, el.className); el = el.parentElement; }`. The break is where height drops to 0. Common symptom: React Flow error `"parent container needs a width and a height"`.
**54. React Flow CSS in Tailwind v4 — import in `index.css`, not component JS:** With `@tailwindcss/vite`, importing `@xyflow/react/dist/style.css` inside a component file causes the plugin to process/wrap it in a cascade layer, lowering specificity. Import it in `index.css` instead: `@import '@xyflow/react/dist/style.css';` after `@import 'tailwindcss';`. Override dark theme using `--xy-*` CSS custom properties (e.g., `--xy-edge-stroke-default`) on `.react-flow.dark`, NOT old-style direct property selectors like `.react-flow__edge-path { stroke: ... }`.
**55. App shell height chain for full-height pages (tree editor, procedural editor):** The CSS Grid app shell (`app-shell`) → `.main-content` → page component chain must preserve height. `.main-content` is a grid cell with implicit height from `1fr`. Pages using React Flow or other full-height layouts need every wrapper div between `.main-content` and the canvas to either use `flex` + `flex-1` + `min-h-0` or explicit `h-full`. Adding ANY wrapper div (e.g., for animations, transitions) without proper height classes will collapse the canvas to 0.
---
## RBAC & Permissions

View File

@@ -12,9 +12,11 @@ RUN npm ci
# Copy source code
COPY . .
# Build argument for API URL (set at build time)
# Build arguments (set at build time)
ARG VITE_API_URL
ARG VITE_SENTRY_DSN
ENV VITE_API_URL=$VITE_API_URL
ENV VITE_SENTRY_DSN=$VITE_SENTRY_DSN
# Build the application
RUN npm run build

View File

@@ -183,7 +183,7 @@ export function AppLayout() {
)}
{/* Main Content */}
<main className="main-content overflow-y-auto">
<main className="main-content">
<EmailVerificationBanner />
<Outlet />
</main>

View File

@@ -2,6 +2,11 @@
@custom-variant dark (&:where(.dark, .dark *));
/* React Flow — imported here (not in component JS) so @tailwindcss/vite
doesn't wrap it in a cascade layer, which would lower its specificity
below Tailwind's own styles and hide nodes/edges. */
@import '@xyflow/react/dist/style.css';
@theme {
/* ── Brand tokens ─────────────────────────────────── */
--color-brand-gradient-from: oklch(0.65 0.13 195); /* #06b6d4 cyan-500 */
@@ -364,58 +369,59 @@
}
/* ── React Flow dark theme overrides ─────────────────── */
.react-flow__background {
background-color: transparent !important;
/* React Flow v12 uses --xy-* CSS custom properties for theming.
Override the defaults to match our Slate & Ice design system. */
.react-flow.dark {
--xy-background-color-default: transparent;
--xy-edge-stroke-default: var(--color-border);
--xy-edge-stroke-selected-default: var(--color-primary);
--xy-edge-label-color-default: var(--color-muted-foreground);
--xy-edge-label-background-color-default: var(--color-card);
--xy-node-background-color-default: var(--color-card);
--xy-node-color-default: var(--color-foreground);
--xy-node-border-default: 1px solid var(--color-border);
--xy-handle-background-color-default: var(--color-border);
--xy-handle-border-color-default: var(--color-card);
--xy-minimap-background-color-default: var(--color-card);
--xy-minimap-mask-background-color-default: oklch(0.22 0.008 264 / 0.6);
--xy-controls-button-background-color-default: var(--color-card);
--xy-controls-button-background-color-hover-default: var(--color-accent);
--xy-controls-button-color-default: var(--color-muted-foreground);
--xy-controls-button-color-hover-default: var(--color-foreground);
--xy-controls-button-border-color-default: var(--color-border);
}
.react-flow__controls {
background-color: var(--color-card) !important;
border: 1px solid var(--color-border) !important;
border-radius: 0.75rem !important;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3) !important;
overflow: hidden;
}
.react-flow__controls-button {
background-color: var(--color-card) !important;
border-color: var(--color-border) !important;
fill: var(--color-muted-foreground) !important;
color: var(--color-muted-foreground) !important;
}
.react-flow__controls-button:hover {
background-color: var(--color-accent) !important;
fill: var(--color-foreground) !important;
}
.react-flow__controls-button svg {
fill: inherit !important;
}
.react-flow__minimap {
background-color: var(--color-card) !important;
border: 1px solid var(--color-border) !important;
border-radius: 0.75rem !important;
}
.react-flow__edge-path {
stroke: var(--color-border);
}
.react-flow__edge-text {
fill: var(--color-muted-foreground);
}
.react-flow__edge-textbg {
fill: var(--color-card);
}
.react-flow__attribution {
display: none;
}
.react-flow__handle {
background-color: var(--color-border);
/* ── Glow edge animations ────────────────────────────── */
@keyframes glow-flow-downstream {
from { stroke-dashoffset: 40; }
to { stroke-dashoffset: 0; }
}
@keyframes glow-flow-upstream {
from { stroke-dashoffset: 0; }
to { stroke-dashoffset: 40; }
}
.glow-edge-flow-downstream {
animation: glow-flow-downstream 1s linear infinite;
}
.glow-edge-flow-upstream {
animation: glow-flow-upstream 1s linear infinite;
}
/* ── Accessibility: Reduce motion ────────────────────── */

View File

@@ -1,7 +1,7 @@
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: "https://23937b8c0cea2484f6a9d5b97d0b7d4b@o4511005918887936.ingest.us.sentry.io/4511005926883328",
dsn: import.meta.env.VITE_SENTRY_DSN || "https://23937b8c0cea2484f6a9d5b97d0b7d4b@o4511005918887936.ingest.us.sentry.io/4511005926883328",
environment: import.meta.env.MODE,
integrations: [

View File

@@ -517,7 +517,7 @@ export function TreeEditorPage() {
}
return (
<div className="flex h-full overflow-hidden">
<div className="flex h-[calc(100vh-56px)] overflow-hidden">
{/* Main content column */}
<div className="flex min-w-0 flex-1 flex-col overflow-hidden">
@@ -805,7 +805,7 @@ export function TreeEditorPage() {
)}
{/* Main Editor */}
<div className="min-h-0 flex-1 overflow-hidden">
<div className="flex min-h-0 flex-1 overflow-hidden">
<TreeEditorLayout
isMobile={isMobile}
isMetadataOpen={isMetadataOpen}