diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@
 -- cabal: default-extensions: DataKinds, TypeOperators, FlexibleContexts
 import DataFrame.Internal.DataFrame (DataFrame, fromNamedColumns)
 import DataFrame.Internal.Column (fromList)
-import DataFrame.Operators ((|>))
+import DataFrame.Expression.Operators ((|>))
 import qualified DataFrame.Functions as F
 import DataFrame.Typed.Types (TypedDataFrame)
 import DataFrame.Typed.Freeze (freeze)
diff --git a/dataframe-viz.cabal b/dataframe-viz.cabal
--- a/dataframe-viz.cabal
+++ b/dataframe-viz.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.4
 name:               dataframe-viz
-version:            1.3.1.0
+version:            1.3.2.0
 synopsis:           Visualisation/plotting helpers for the dataframe ecosystem.
 description:
     Display harness plus terminal and web plotters. Built on top of
@@ -37,7 +37,7 @@
     build-depends:      base >= 4 && < 5,
                         aeson >= 0.11.0.0 && < 3,
                         containers >= 0.6.7 && < 0.10,
-                        dataframe-core >= 2.4 && < 2.5,
+                        dataframe-core >= 2.5 && < 2.6,
                         directory >= 1.3.0.0 && < 2,
                         granite >= 0.6 && < 1,
                         process ^>= 1.6,
diff --git a/src/DataFrame/Display/Internal/Common.hs b/src/DataFrame/Display/Internal/Common.hs
--- a/src/DataFrame/Display/Internal/Common.hs
+++ b/src/DataFrame/Display/Internal/Common.hs
@@ -54,8 +54,8 @@
     materializeMerged,
     materializePacked,
  )
+import DataFrame.Internal.Column.Types
 import DataFrame.Internal.DataFrame (DataFrame (..), getColumn)
-import DataFrame.Internal.Types
 
 -- | Aggregation strategy for grouped/categorical plots.
 data Agg
diff --git a/src/DataFrame/Display/Internal/VegaLite.hs b/src/DataFrame/Display/Internal/VegaLite.hs
--- a/src/DataFrame/Display/Internal/VegaLite.hs
+++ b/src/DataFrame/Display/Internal/VegaLite.hs
@@ -54,10 +54,11 @@
 import System.IO (hPutStrLn, stderr)
 import Type.Reflection (TypeRep, tyConName, typeRep, typeRepTyCon, pattern App)
 
-import DataFrame.Internal.Column (Column, Columnable, unwrapTypedColumn)
-import DataFrame.Internal.DataFrame (DataFrame, getColumn)
+import Control.Exception (throw)
+import DataFrame.Internal.Column (Column, Columnable)
+import DataFrame.Internal.DataFrame (DataFrame, dataframeDimensions, getColumn)
 import DataFrame.Internal.Expression (Expr (Col))
-import DataFrame.Internal.Interpreter (interpret)
+import DataFrame.Internal.Interpreter (Ctx (..), eval, materialize)
 
 import DataFrame.Display.Internal.Common (columnToDoubles, columnToStrings)
 
@@ -250,11 +251,10 @@
         Nothing ->
             error $ "DataFrame.Display.Web: column not found: " <> T.unpack cname
 
-materialiseExpr :: (Columnable a) => DataFrame -> Expr a -> Column
-materialiseExpr df expr = case interpret df expr of
-    Right tc -> unwrapTypedColumn tc
-    Left err ->
-        error $ "DataFrame.Display.Web: could not evaluate expression: " <> show err
+materialiseExpr :: forall a. (Columnable a) => DataFrame -> Expr a -> Column
+materialiseExpr df expr = either throw id $ do
+    v <- eval (FlatCtx df) expr
+    pure $ materialize @a (fst (dataframeDimensions df)) v
 
 columnToValues :: FieldType -> Column -> [Value]
 columnToValues Quantitative col = map toJSON (columnToDoubles col)
diff --git a/src/DataFrame/Display/Web/Plot.hs b/src/DataFrame/Display/Web/Plot.hs
--- a/src/DataFrame/Display/Web/Plot.hs
+++ b/src/DataFrame/Display/Web/Plot.hs
@@ -66,15 +66,17 @@
 import qualified Data.List as L
 import qualified Data.Maybe
 import qualified Data.Text as T
-import qualified Data.Text.IO as T
+import qualified Data.Text.IO.Utf8 as T
 import GHC.Stack (HasCallStack)
 import Numeric (showFFloat)
 import System.Directory (getHomeDirectory)
 import System.Info (os)
 import System.Process (
+    CreateProcess,
     StdStream (NoStream),
     createProcess,
     proc,
+    shell,
     std_err,
     std_in,
     std_out,
@@ -458,6 +460,7 @@
 -- Browser launcher
 -- ---------------------------------------------------------------------------
 
+-- | Windows launches via a shell: 'start' is a cmd builtin.
 showInDefaultBrowser :: String -> IO ()
 showInDefaultBrowser p = do
     plotId <- generateChartId
@@ -471,15 +474,15 @@
     putStrLn fullPath
     T.writeFile fullPath (T.pack p)
     case os of
-        "mingw32" -> openFileSilently "start" fullPath
-        "darwin" -> openFileSilently "open" fullPath
-        _ -> openFileSilently "xdg-open" fullPath
+        "mingw32" -> launchSilently (shell ("start \"\" \"" <> fullPath <> "\""))
+        "darwin" -> launchSilently (proc "open" [fullPath])
+        _ -> launchSilently (proc "xdg-open" [fullPath])
 
-openFileSilently :: FilePath -> FilePath -> IO ()
-openFileSilently program path = do
+launchSilently :: CreateProcess -> IO ()
+launchSilently cp = do
     (_, _, _, ph) <-
         createProcess
-            (proc program [path])
+            cp
                 { std_in = NoStream
                 , std_out = NoStream
                 , std_err = NoStream
