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
12 changes: 8 additions & 4 deletions packages/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FlowCanvas } from './components/FlowCanvas'
import { DataInspectionPanel, BookmarkTab, type DockSide } from './components/DataInspectionPanel'
import { FlowEditorModal } from './components/FlowEditorModal'
import { buildStarterYaml } from './lib/starter-yaml'
import { isMobileViewport } from './lib/mobile'
import { useFlowList, useFlowData } from './hooks/useFlowPolling'
import { useFlowMutations } from './hooks/useFlowMutations'
import type { FlowNode, FlowStep, FlowData, Flow } from './types'
Expand Down Expand Up @@ -222,13 +223,16 @@ function App() {
}
}, [])

// Inspector panel state
const [inspectorOpen, setInspectorOpen] = useState(true)
// Inspector panel state. Starts collapsed on mobile-sized viewports
// (Tailwind `md` breakpoint, 768px) so the canvas gets full width on
// phones; on desktop it stays open as before.
const [inspectorOpen, setInspectorOpen] = useState(() => !isMobileViewport())
const [inspectorSide, setInspectorSide] = useState<DockSide>('right')
const [inspectorSize, setInspectorSize] = useState(320)
// Sidebar (flow tree) collapsed/expanded state. Bookmark tab on the
// left edge of the canvas toggles it.
const [sidebarOpen, setSidebarOpen] = useState(true)
// left edge of the canvas toggles it. Same mobile-default rule as
// the inspector.
const [sidebarOpen, setSidebarOpen] = useState(() => !isMobileViewport())

// Reset inspected step when flow changes
useEffect(() => {
Expand Down
7 changes: 5 additions & 2 deletions packages/web/src/AppFragment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Sidebar } from './components/Sidebar'
import { buildStarterYaml } from './lib/starter-yaml'
import { buildShareUrl, decodeFragment, encodeFragment } from './lib/share-url'
import { EXAMPLE_FLOWS } from './lib/example-flows'
import { isMobileViewport } from './lib/mobile'
import type { FlowListItem } from './hooks/useFlowPolling'
import type { FlowNode, FlowStep, FlowData, Flow } from './types'

Expand Down Expand Up @@ -204,10 +205,12 @@ export default function AppFragment() {
}
}, [])

const [inspectorOpen, setInspectorOpen] = useState(true)
// Both side panels start collapsed on narrow viewports so the canvas
// gets full width on phones; on desktop they stay open as before.
const [inspectorOpen, setInspectorOpen] = useState(() => !isMobileViewport())
const [inspectorSide, setInspectorSide] = useState<DockSide>('right')
const [inspectorSize, setInspectorSize] = useState(320)
const [sidebarOpen, setSidebarOpen] = useState(true)
const [sidebarOpen, setSidebarOpen] = useState(() => !isMobileViewport())

// Match the current hash against each example's encoded form so the
// sidebar can highlight the active example. Stable across re-renders
Expand Down
30 changes: 25 additions & 5 deletions packages/web/src/components/FlowCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,22 +180,38 @@
}
}, [layoutKey, fitToPane])

// Auto-zoom guard: tracks the last focus key so the same step doesn't
// re-issue setCenter mid-animation (which stutters).
const lastFocusKeyRef = useRef<string>('')

// Bumped by the ResizeObserver below so the auto-zoom effect re-runs
// after the pane changes width (e.g. user toggles FLOWS / INSPECT).
// Without this, fitToPane() snaps to overview on resize and the
// auto-zoom effect skips re-applying the active-step focus because
// its focusKey hasn't changed — playback would visibly drop back to
// overview until the next step advance.
const [paneResizeTick, setPaneResizeTick] = useState(0)

// Re-fit when the canvas pane resizes (e.g. user toggles sidebar /
// inspector via the bookmark tabs). Without this, the diagram visibly
// shifts left when the sidebar collapses because the viewport keeps the
// same world coords against a now-wider canvas.
useEffect(() => {
const pane = containerRef.current?.querySelector('.react-flow') as HTMLElement | null
if (!pane || typeof ResizeObserver === 'undefined') return
const observer = new ResizeObserver(() => fitToPane())
const observer = new ResizeObserver(() => {
fitToPane()
// Force the auto-zoom effect to re-apply: clear the focus guard
// and bump the tick that the effect depends on. Together they
// make resize-triggered fitToPane() not the last word during
// playback.
lastFocusKeyRef.current = ''
setPaneResizeTick((t) => t + 1)
})
observer.observe(pane)
return () => observer.disconnect()
}, [fitToPane])

// Auto-zoom guard: tracks the last focus key so the same step doesn't
// re-issue setCenter mid-animation (which stutters).
const lastFocusKeyRef = useRef<string>('')

const pairEdgeMap = useMemo(() => {
const map = new Map<string, Edge>()
for (const edge of baseEdges) {
Expand Down Expand Up @@ -378,6 +394,10 @@
animState.activeToIds,
baseNodes,
reactFlow,
// paneResizeTick: re-runs this effect after a sidebar/inspector
// toggle so the camera re-locks onto the active step instead of
// staying at the overview fitToPane() applied.
paneResizeTick,
])

// Report step changes to parent
Expand Down Expand Up @@ -629,7 +649,7 @@
},
}
})
}, [

Check warning on line 652 in packages/web/src/components/FlowCanvas.tsx

View workflow job for this annotation

GitHub Actions / build & test (node 20)

React Hook useMemo has a missing dependency: 'nodeOutgoingSteps'. Either include it or remove the dependency array

Check warning on line 652 in packages/web/src/components/FlowCanvas.tsx

View workflow job for this annotation

GitHub Actions / build & test (node 22)

React Hook useMemo has a missing dependency: 'nodeOutgoingSteps'. Either include it or remove the dependency array
baseNodes,
animState.activeFromIds,
animState.activeToIds,
Expand Down
13 changes: 13 additions & 0 deletions packages/web/src/lib/mobile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Mobile-viewport detection for collapse-by-default chrome.
*
* Tailwind's `md` breakpoint is 768px, so we treat anything below
* that as "mobile" — narrow enough that the FLOWS sidebar + INSPECT
* panel can't sit alongside the canvas without crushing it. Used
* once at mount, via `useState(() => !isMobileViewport())`, to set
* the initial open/closed state of both bookmark-tabbed panels.
*/
export function isMobileViewport(): boolean {
if (typeof window === 'undefined') return false
return window.matchMedia('(max-width: 767px)').matches
}
Loading