From d06abe5829b1af30e48eaf8474121ceb3670ffc0 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 9 Mar 2026 04:36:37 -0400 Subject: [PATCH] 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 --- CLAUDE.md | 6 ++ frontend/Dockerfile | 4 +- frontend/src/components/layout/AppLayout.tsx | 2 +- frontend/src/index.css | 78 +++++++++++--------- frontend/src/instrument.ts | 2 +- frontend/src/pages/TreeEditorPage.tsx | 4 +- 6 files changed, 55 insertions(+), 41 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 341b070f..6c68b1da 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 675bcfd4..4fbdf2b1 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -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 diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index d56ddcfb..a5942fe5 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -183,7 +183,7 @@ export function AppLayout() { )} {/* Main Content */} -
+
diff --git a/frontend/src/index.css b/frontend/src/index.css index bdd0bbe7..8f0ddb51 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -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 ────────────────────── */ diff --git a/frontend/src/instrument.ts b/frontend/src/instrument.ts index d59877dc..823a6d4b 100644 --- a/frontend/src/instrument.ts +++ b/frontend/src/instrument.ts @@ -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: [ diff --git a/frontend/src/pages/TreeEditorPage.tsx b/frontend/src/pages/TreeEditorPage.tsx index 2b47b0ee..f8206f21 100644 --- a/frontend/src/pages/TreeEditorPage.tsx +++ b/frontend/src/pages/TreeEditorPage.tsx @@ -517,7 +517,7 @@ export function TreeEditorPage() { } return ( -
+
{/* Main content column */}
@@ -805,7 +805,7 @@ export function TreeEditorPage() { )} {/* Main Editor */} -
+