DEV Community

rocketsquirreldev
rocketsquirreldev

Posted on

I built a desk setup cable planner with Flutter Web — here's what I learned

I hate planning desk setups.

Every time I buy a new monitor or dock, I spend 20 minutes Googling "does USB-C carry DisplayPort signal" or "can I daisy-chain these two monitors." So I built a tool to do it for me.

DeskFlow — a browser-based diagram tool where you drag equipment onto a canvas, click ports to connect them, and instantly see whether the cable is physically possible + what to buy.


What it does

  • Drag equipment from a library onto a canvas
  • Click two ports to connect them — compatibility is checked automatically
  • Incompatible connections are rejected (e.g. HDMI out → DP in via passive adapter is blocked, two bidirectional power ports can't connect to each other)
  • Shopping list panel auto-generates with cable names and prices from a DB

Stack

  • Flutter Web — single codebase deployed as a web app without learning a JS framework
  • Supabase — equipment DB (33 devices, 11 cable types). anon key only, all reads. No auth needed.
  • Vercel — static hosting with a vercel.json rewrite rule for SPA routing

The interesting problems

1. Gesture arena conflicts in Flutter Web

The canvas uses a GestureDetector with HitTestBehavior.opaque for panning. Port chips sit on top of the canvas as overlaid widgets.

Problem: tapping a port chip would get stolen by the canvas gesture detector before the port's tap handler fired.

Fix: replaced GestureDetector with raw Listener (onPointerDown/Move/Up). Listeners don't participate in the gesture arena — they always fire.

// Before: lost to gesture arena
GestureDetector(
  onPanUpdate: (d) => _handleResize(d.delta),
  child: divider,
)

// After: always fires
Listener(
  onPointerMove: (e) => _handleResize(e.delta),
  child: divider,
)
Enter fullscreen mode Exit fullscreen mode

2. Port compatibility model

Ports have a type (hdmi, dp, usbC, thunderbolt4, usbA, power, ethernet, sdcard) and a direction (input, output, bidirectional).

  • TB4 and USB-C share the same physical connector → treated as compatible
  • USB-C ↔ HDMI/DP/USB-A → allowed (adapters exist)
  • DP(output) → HDMI(input) → allowed (passive adapter)
  • HDMI(output) → DP(input) → blocked (needs active converter)
  • Two bidirectional power ports → blocked (two chargers can't charge each other)
  • SD card ↔ SD card only → no cross-type adapters

TB4 ports fall back to usbC when looking up the cable catalog:

static String _normalizeCableType(String type) =>
    type == 'thunderbolt4' ? 'usbC' : type;
Enter fullscreen mode Exit fullscreen mode

3. SPA routing on Vercel

Flutter Web produces a single index.html. Direct URL access returns 404 without this in vercel.json:

{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
Enter fullscreen mode Exit fullscreen mode

What's next

  • Setup templates (home office, streaming, video editing)
  • Mobile/touch support
  • Shopping cart total / budget summary panel

Happy to answer questions about any of the Flutter Web specifics — it's a surprisingly capable target that doesn't get enough coverage.

Top comments (0)