diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,28 @@
 # Changelog for commonmark-pandoc
 
+## 0.3.0.1
+
+  * keep task-list checkbox attached under source positions.
+    With sourcepos enabled, an item's first Plain/Para has already been
+    wrapped in a Div (wrapper="1") by addBlockAttrs before taskList runs,
+    so toTaskListItem's Plain/Para patterns never matched and the
+    checkbox was emitted as a separate Plain block preceding the item
+    contents. Recognize the wrapper Div and insert the checkbox into
+    the Plain/Para inside it.
+
+  * Use B.text for resolved entities. B.str put whitespace inside Str
+    nodes for entities like &#32; and &#10;, violating pandoc AST
+    conventions; B.text produces proper Space/SoftBreak elements.
+
+  * Remove redundant illegalCodePoint check.
+
+  * Don't duplicate classes and keys in `addToPandocAttr`.
+    Matches the HTML backend's incorporateAttribute, which never
+    produces duplicate classes or attribute keys.
+
+  * Make the wrapper `data-pos` suppression robust. Don't rely on the
+    wrapper marker being the first entry in the key-value list.
+
 ## 0.3
 
   * Define Applicative instances of IsBlock, IsInline etc.
diff --git a/commonmark-pandoc.cabal b/commonmark-pandoc.cabal
--- a/commonmark-pandoc.cabal
+++ b/commonmark-pandoc.cabal
@@ -1,5 +1,5 @@
 name:           commonmark-pandoc
-version:        0.3
+version:        0.3.0.1
 synopsis:       Bridge between commonmark and pandoc AST.
 description:    This library provides typeclasses for rendering
                 commonmark to Pandoc types.
diff --git a/src/Commonmark/Pandoc.hs b/src/Commonmark/Pandoc.hs
--- a/src/Commonmark/Pandoc.hs
+++ b/src/Commonmark/Pandoc.hs
@@ -15,7 +15,6 @@
 
 import Data.Maybe (fromMaybe)
 import qualified Data.Text as T
-import qualified Data.Text.Read as TR
 import Text.Pandoc.Definition
 import Text.Pandoc.Walk
 import qualified Text.Pandoc.Builder as B
@@ -47,9 +46,7 @@
   lineBreak = Cm B.linebreak
   softBreak = Cm B.softbreak
   str t = Cm $ B.text t
-  entity t
-    | illegalCodePoint t = Cm $ B.str "\xFFFD"
-    | otherwise = Cm $ B.str $ fromMaybe t $ lookupEntity (T.drop 1 t)
+  entity t = Cm $ B.text $ fromMaybe t $ lookupEntity (T.drop 1 t)
   escapedChar c = Cm $ B.str $ T.singleton c
   emph ils = B.emph <$> ils
   strong ils = B.strong <$> ils
@@ -172,8 +169,15 @@
   case B.toList $ coerce item of
     (Plain ils : rest) -> Plain (checkbox : Space : ils) : rest
     (Para  ils : rest) -> Para  (checkbox : Space : ils) : rest
+    -- with source positions, the item's first block has been wrapped
+    -- in a Div by addBlockAttrs; insert the checkbox inside it:
+    (Div attr (Plain ils : ds) : rest) | isWrapperAttr attr
+      -> Div attr (Plain (checkbox : Space : ils) : ds) : rest
+    (Div attr (Para ils : ds) : rest) | isWrapperAttr attr
+      -> Div attr (Para (checkbox : Space : ils) : ds) : rest
     bs                 -> Plain [checkbox] : bs
     where checkbox = Str (if checked then "\9746" else "\9744")
+          isWrapperAttr (_,_,kvs) = lookup "wrapper" kvs == Just "1"
 
 instance Rangeable (Cm a B.Blocks)
   => HasDiv (Cm a B.Blocks) where
@@ -224,34 +228,23 @@
 
 addToPandocAttr :: Attributes -> Attr -> Attr
 -- avoid adding spurious extra data-pos to wrapper div (#159):
-addToPandocAttr [("data-pos", _)] attrs@(_,_,("wrapper","1"):_)  = attrs
+addToPandocAttr [("data-pos", _)] attrs@(_, _, kvs)
+  | lookup "wrapper" kvs == Just "1" = attrs
 addToPandocAttr attrs attr = foldr go attr attrs
  where
   go ("id", v) (_, cls, kvs) = (v, cls, kvs)
-  go ("class", v) (ident, cls, kvs) = (ident, v:cls, kvs)
-  go (k, v) (ident, cls, kvs) = (ident, cls, (k,v):kvs)
+  go ("class", v) (ident, cls, kvs)
+    | v `elem` cls           = (ident, cls, kvs)
+    | otherwise              = (ident, v:cls, kvs)
+  go (k, v) (ident, cls, kvs)
+    | k `elem` map fst kvs   = (ident, cls, kvs)
+    | otherwise              = (ident, cls, (k,v):kvs)
 
 instance (Rangeable (Cm a B.Inlines), Rangeable (Cm a B.Blocks))
      => HasFootnote (Cm a B.Inlines) (Cm a B.Blocks) where
   footnote _num _lab _x = mempty
   footnoteList _xs = mempty
   footnoteRef _num _lab contents = B.note . walk deNote <$> contents
-
-illegalCodePoint :: T.Text -> Bool
-illegalCodePoint t =
-  "&#" `T.isPrefixOf` t &&
-  let t' = T.drop 2 $ T.filter (/=';') t
-      badvalue (n, r) = not (T.null r) ||
-                        n < 1 ||
-                        n > (0x10FFFF :: Integer)
-  in
-  case T.uncons t' of
-       Nothing -> True
-       Just (x, rest)
-         | x == 'x' || x == 'X'
-           -> either (const True) badvalue (TR.hexadecimal rest)
-         | otherwise
-           -> either (const True) badvalue (TR.decimal t')
 
 stringify :: Walkable Inline a => a -> T.Text
 stringify = query go . walk (deNote . deQuote)
