diff --git a/CHANGES b/CHANGES
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,13 @@
+* 0.2.3.1: 17 Aug 2022
+
+- Allow base-4.17 and test with GHC 9.4
+- Get rid of newtype-generics dependency
+
+* 0.2.3.0-r2: 11 Dec 2021
+
+- Allow base-4.16 and test with GHC 9.2
+- Allow semigroups-0.20
+
 * 0.2.3.0: 11 May 2021
 
 - Allow base-4.14, 4.15 and test with GHC 8.10, 9.0
diff --git a/dual-tree.cabal b/dual-tree.cabal
--- a/dual-tree.cabal
+++ b/dual-tree.cabal
@@ -1,12 +1,12 @@
 name:                dual-tree
-version:             0.2.3.0
+version:             0.2.3.1
 synopsis:            Rose trees with cached and accumulating monoidal annotations
 description:         Rose (n-ary) trees with both upwards- (/i.e./
                      cached) and downwards-traveling (/i.e./
                      accumulating) monoidal annotations.  This is used
                      as the core data structure underlying
                      the @diagrams@ framework
-                     (<http://projects.haskell.org/diagrams>), but
+                     (<https://diagrams.github.io>), but
                      potentially has other applications as well.
                      .
                      Abstractly, a DUALTree is a rose (n-ary) tree
@@ -31,7 +31,7 @@
 build-type:          Simple
 cabal-version:       >=1.10
 bug-reports:         https://github.com/diagrams/dual-tree/issues
-tested-with:         GHC == 8.4.4, GHC == 8.6.5, GHC == 8.8.4, GHC == 8.10.4, GHC == 9.0.1
+tested-with:         GHC ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.4 || ==9.4.1
 source-repository head
   type: git
   location: https://github.com/diagrams/dual-tree.git
@@ -40,9 +40,8 @@
   default-language:  Haskell2010
   exposed-modules:   Data.Tree.DUAL
                      Data.Tree.DUAL.Internal
-  build-depends:     base >= 4.3 && < 4.16,
-                     semigroups >= 0.8 && < 0.20,
-                     newtype-generics >= 0.6 && < 0.7,
+  build-depends:     base >= 4.3 && < 4.18,
+                     semigroups >= 0.8 && < 0.21,
                      monoid-extras >= 0.6 && < 0.7
   hs-source-dirs:    src
   other-extensions:  GeneralizedNewtypeDeriving,
diff --git a/src/Data/Tree/DUAL.hs b/src/Data/Tree/DUAL.hs
--- a/src/Data/Tree/DUAL.hs
+++ b/src/Data/Tree/DUAL.hs
@@ -9,7 +9,7 @@
 -- Rose (n-ary) trees with both upwards- (/i.e./ cached) and
 -- downwards-traveling (/i.e./ accumulating) monoidal annotations.
 -- This is used as the core data structure underlying the @diagrams@
--- framework (<http://projects.haskell.org/diagrams>), but potentially
+-- framework (<https://diagrams.github.io>), but potentially
 -- has other applications as well.
 --
 -- Abstractly, a DUALTree is a rose (n-ary) tree with data (of type
diff --git a/src/Data/Tree/DUAL/Internal.hs b/src/Data/Tree/DUAL/Internal.hs
--- a/src/Data/Tree/DUAL/Internal.hs
+++ b/src/Data/Tree/DUAL/Internal.hs
@@ -4,7 +4,6 @@
 {-# LANGUAGE FlexibleInstances          #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 {-# LANGUAGE MultiParamTypeClasses      #-}
-{-# LANGUAGE TypeOperators              #-}
 {-# LANGUAGE TypeFamilies               #-}
 
 -----------------------------------------------------------------------------
@@ -59,8 +58,6 @@
 import           Data.Semigroup
 import           Data.Typeable
 
-import           Control.Newtype.Generics
-
 ------------------------------------------------------------
 -- DUALTreeNE
 ------------------------------------------------------------
@@ -84,11 +81,6 @@
 
 newtype DAct d = DAct { unDAct :: d }
 
-instance Newtype (DAct d) where
-  type O (DAct d) = d
-  pack   = DAct
-  unpack = unDAct
-
 instance (Semigroup d, Semigroup u, Action d u)
     => Action (DAct d) (DUALTreeNE d u a l) where
   act (DAct d) (Act d' t) = Act (d <> d') t
@@ -103,22 +95,22 @@
 newtype DUALTreeU d u a l = DUALTreeU { unDUALTreeU :: (u, DUALTreeNE d u a l) }
   deriving (Functor, Semigroup, Typeable, Show, Eq)
 
-instance Newtype (DUALTreeU d u a l) where
-  type O (DUALTreeU d u a l) = (u, DUALTreeNE d u a l)
-  pack   = DUALTreeU
-  unpack = unDUALTreeU
+overDUALTreeU
+  :: ((u, DUALTreeNE d u a l) -> (u', DUALTreeNE d u' a l))
+  -> DUALTreeU d u a l -> DUALTreeU d u' a l
+overDUALTreeU f = DUALTreeU . f . unDUALTreeU
 
 instance (Semigroup d, Semigroup u, Action d u)
     => Action (DAct d) (DUALTreeU d u a l) where
-  act d = over DUALTreeU (act (unDAct d) *** act d)
+  act d = overDUALTreeU (act (unDAct d) *** act d)
 
 -- | \"Pull\" the root @u@ annotation out into a tuple.
 pullU :: (Semigroup u, Action d u) => DUALTreeNE d u a l -> DUALTreeU d u a l
-pullU t@(Leaf u _)                   = pack (u, t)
-pullU t@(LeafU u)                    = pack (u, t)
-pullU t@(Concat ts)                  = pack (sconcat . NEL.map (fst . unpack) $ ts, t)
-pullU t@(Act d (DUALTreeU (u,_)))    = pack (act d u, t)
-pullU t@(Annot _ (DUALTreeU (u, _))) = pack (u, t)
+pullU t@(Leaf u _)                   = DUALTreeU (u, t)
+pullU t@(LeafU u)                    = DUALTreeU (u, t)
+pullU t@(Concat ts)                  = DUALTreeU (sconcat . NEL.map (fst . unDUALTreeU) $ ts, t)
+pullU t@(Act d (DUALTreeU (u,_)))    = DUALTreeU (act d u, t)
+pullU t@(Annot _ (DUALTreeU (u, _))) = DUALTreeU (u, t)
 
 ------------------------------------------------------------
 -- DUALTree
@@ -152,10 +144,10 @@
 newtype DUALTree d u a l = DUALTree { unDUALTree :: Maybe (DUALTreeU d u a l) }
   deriving ( Functor, Semigroup, Typeable, Show, Eq )
 
-instance Newtype (DUALTree d u a l) where
-  type O (DUALTree d u a l) = Maybe (DUALTreeU d u a l)
-  pack   = DUALTree
-  unpack = unDUALTree
+overDUALTree
+  :: (Maybe (DUALTreeU d u a l) -> Maybe (DUALTreeU d u' a l))
+  -> DUALTree d u a l -> DUALTree d u' a l
+overDUALTree f = DUALTree . f . unDUALTree
 
 instance (Semigroup u, Action d u) => Monoid (DUALTree d u a l) where
   mempty  = DUALTree mempty
@@ -168,7 +160,7 @@
 --   operationally @act@ incurs only a constant amount of work.
 instance (Semigroup d, Semigroup u, Action d u)
     => Action (DAct d) (DUALTree d u a l) where
-  act = over DUALTree . fmap . act
+  act = overDUALTree . fmap . act
 
 ------------------------------------------------------------
 -- Convenience methods etc.
@@ -204,7 +196,7 @@
 --   only works on /non-empty/ trees; on empty trees this function is
 --   the identity.
 annot :: (Semigroup u, Action d u) => a -> DUALTree d u a l -> DUALTree d u a l
-annot a = (over DUALTree . fmap) (pullU . Annot a)
+annot a = (overDUALTree . fmap) (pullU . Annot a)
 
 -- | Apply a @d@ annotation at the root of a tree, transforming all
 --   @u@ annotations by the action of @d@.
@@ -216,7 +208,7 @@
 --   top-level cached @u@ annotation paired with a non-empty
 --   DUAL-tree.
 nonEmpty :: DUALTree d u a l -> Maybe (u, DUALTreeNE d u a l)
-nonEmpty = fmap unpack . unpack
+nonEmpty = fmap unDUALTreeU . unDUALTree
 
 -- | Get the @u@ annotation at the root, or @Nothing@ if the tree is
 --   empty.
@@ -244,7 +236,7 @@
 --   with the action of @d@) over all the @u@ annotations in a
 --   non-empty DUAL-tree paired with its cached @u@ value.
 mapUU :: (u -> u') -> DUALTreeU d u a l -> DUALTreeU d u' a l
-mapUU f = over DUALTreeU (f *** mapUNE f)
+mapUU f = overDUALTreeU (f *** mapUNE f)
 
 -- | Map a function over all the @u@ annotations in a DUAL-tree.  The
 --   function must be a monoid homomorphism, and must commute with the
@@ -258,7 +250,7 @@
 --     * @f (act d u) == act d (f u)@
 --
 mapU :: (u -> u') -> DUALTree d u a l -> DUALTree d u' a l
-mapU = over DUALTree . fmap . mapUU
+mapU = overDUALTree . fmap . mapUU
 
 ------------------------------------------------------------
 -- Folds
@@ -276,14 +268,14 @@
            -> DUALTreeNE d u a l -> r
 foldDUALNE  = foldDUALNE' Nothing
   where
-    foldDUALNE' dacc lf _   _   _    _   (Leaf _ l)  = lf (maybe mempty id dacc) l
+    foldDUALNE' dacc lf _   _   _    _   (Leaf _ l)  = lf (fromMaybe mempty dacc) l
     foldDUALNE' _    _  lfU _   _    _   (LeafU _)   = lfU
     foldDUALNE' dacc lf lfU con down ann (Concat ts)
-      = con (NEL.map (foldDUALNE' dacc lf lfU con down ann . snd . unpack) ts)
+      = con (NEL.map (foldDUALNE' dacc lf lfU con down ann . snd . unDUALTreeU) ts)
     foldDUALNE' dacc lf lfU con down ann (Act d t)
-      = down d (foldDUALNE' (dacc <> Just d) lf lfU con down ann . snd . unpack $ t)
+      = down d (foldDUALNE' (dacc <> Just d) lf lfU con down ann . snd . unDUALTreeU $ t)
     foldDUALNE' dacc lf lfU con down ann (Annot a t)
-      = ann a (foldDUALNE' dacc lf lfU con down ann . snd . unpack $ t)
+      = ann a (foldDUALNE' dacc lf lfU con down ann . snd . unDUALTreeU $ t)
 
 -- | Fold for DUAL-trees. It is given access to the internal and leaf
 --   data, internal @d@ values, and the accumulated @d@ values at each
@@ -323,5 +315,5 @@
             (\d l -> [(l, d)])
             []
             (concat . NEL.toList)
-            (flip const)
+            (\_ x -> x)
             (const id)
