diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # Revision history for asciidoc-hs
 
+## 0.1.0.3 -- 2026-06-02
+
+  * Open block delimiter is exactly two `--` (#12).
+    Fixes  a performance bug with `---` thematic breaks.
+
+  * Improve parsing of link descriptions (#6).
+
 ## 0.1.0.2 -- 2026-03-17
 
   * Allow fenced constructions to end with end-of-input (#9).
diff --git a/asciidoc.cabal b/asciidoc.cabal
--- a/asciidoc.cabal
+++ b/asciidoc.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.4
 name:               asciidoc
-version:            0.1.0.2
+version:            0.1.0.3
 synopsis:           AsciiDoc parser.
 description:        A parser for AsciiDoc syntax.
 license:            BSD-3-Clause
diff --git a/src/AsciiDoc/Parse.hs b/src/AsciiDoc/Parse.hs
--- a/src/AsciiDoc/Parse.hs
+++ b/src/AsciiDoc/Parse.hs
@@ -718,6 +718,13 @@
   withBlockContext (DelimitedContext c len) $
     catMaybes <$> manyTill pBlock endFence
 
+pDelimitedBlockExact :: Char -> Int -> P [Block]
+pDelimitedBlockExact c exactNumber = do
+  let fence = count exactNumber (vchar c) *> pBlankLine
+  fence
+  withBlockContext (DelimitedContext c exactNumber) $
+    catMaybes <$> manyTill pBlock fence
+
 pPassBlock :: Maybe BlockTitle -> Attr -> P Block
 pPassBlock mbtitle attr = do
   t <- T.unlines <$> pDelimitedLiteralBlock '+' 4
@@ -849,10 +856,9 @@
 
 pOpenBlock :: Maybe BlockTitle -> Attr -> P Block
 pOpenBlock mbtitle attr = Block attr mbtitle <$>
-  ((OpenBlock <$> pDelimitedBlock '-' 2)
+  ((OpenBlock <$> pDelimitedBlockExact '-' 2)
    <|>
-  (QuoteBlock Nothing <$>
-     (pDelimitedBlock '-' 2 <|> pDelimitedBlock '_' 4)))
+  (QuoteBlock Nothing <$> (pDelimitedBlock '_' 4)))
 
 parseAdmonitionType :: T.Text -> Maybe AdmonitionType
 parseAdmonitionType t =
@@ -1283,12 +1289,14 @@
  where
    pBareAttributeValue =
      T.strip <$> takeWhile (\c -> c /= ',' && c /= ']')
-   pQuotedAttr = do
-     vchar '"'
-     result <- many (satisfy (/='"') <|> (vchar '\\' *> satisfy (/='"')))
-     vchar '"'
-     pure $ T.pack result
 
+pQuotedAttr :: P Text
+pQuotedAttr = do
+   vchar '"'
+   result <- many (satisfy (/='"') <|> (vchar '\\' *> satisfy (/='"')))
+   vchar '"'
+   pure $ T.pack result
+
 pInlines' :: [Char] -> P [Inline]
 pInlines' cs = do
   (pLineComment *> pInlines' cs)
@@ -1623,8 +1631,7 @@
   c <- takeWhile1 isLetter
   guard $ let lc = T.length c in lc >= 2 && lc <= 5
   let email = a <> "@" <> b <> "." <> c
-  attr <- pAttributes <|> pure mempty
-  let (description, attr') = extractDescription attr
+  (description, attr') <- pLinkDescription <|> pure (mempty, mempty)
   Inline attr' . Link EmailLink (Target email)
            <$> if T.null description
                   then pure [Inline mempty (Str email)]
@@ -1654,12 +1661,29 @@
   url <- (scheme <>) . mconcat <$> some
           (urlChunk <|> (do Inline _ (Str t) <- pInMatched False '+' mempty (pure . Str)
                             pure t))
-  attr <- pAttributes <|> pure mempty
-  let (description, attr') = extractDescription attr
+  (description, attr') <- pLinkDescription <|> pure (mempty, mempty)
   Inline attr' . Link URLLink (Target url)
              <$> if T.null description
                     then pure [Inline mempty (Str url)]
                     else parseInlines description
+
+pLinkDescription :: P (Text, Attr)
+pLinkDescription = do
+  vchar '['
+  -- parse link description
+  let literalPart = takeWhile1 (\c -> c /= ']' && c /= ',' && c /= '=')
+  description <- option mempty $
+                    pQuotedAttr
+                <|> mconcat <$>
+                     many (literalPart
+                         <|> ("," <$ vchar ',' <* notFollowedBy pKeyValue))
+  -- parse (optional) attributes
+  kvs <- option []
+         (do unless (description == mempty) pComma
+             sepBy pKeyValue pComma <* option () pComma)
+  vchar ']'
+  pure (description, Attr mempty (M.fromList kvs))
+
 
 pBracedAutolink :: P Inline
 pBracedAutolink = vchar '<' *> pAutolink <* vchar '>'
diff --git a/test/regression/issue_6.test b/test/regression/issue_6.test
new file mode 100644
--- /dev/null
+++ b/test/regression/issue_6.test
@@ -0,0 +1,66 @@
+http://discuss.asciidoctor.org[asciidoctor.org,role="green"]
+
+http://discuss.asciidoctor.org["ascidoctor=cool"]
+
+http://example.com[A whole sentence, with comma and period.]
+
+http://example.com["with [brackets]",key=value]
+>>>
+Document
+  { docMeta =
+      Meta
+        { docTitle = []
+        , docTitleAttributes = Nothing
+        , docAuthors = []
+        , docRevision = Nothing
+        , docAttributes = fromList [ ( "sectids" , "" ) ]
+        }
+  , docBlocks =
+      [ Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "role" , "green" ) ] )
+                 (Link
+                    URLLink
+                    (Target "http://discuss.asciidoctor.org")
+                    [ Inline mempty (Str "asciidoctor.org") ])
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Link
+                    URLLink
+                    (Target "http://discuss.asciidoctor.org")
+                    [ Inline mempty (Str "ascidoctor=cool") ])
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 mempty
+                 (Link
+                    URLLink
+                    (Target "http://example.com")
+                    [ Inline mempty (Str "A whole sentence, with comma and period.") ])
+             ])
+      , Block
+          mempty
+          Nothing
+          (Paragraph
+             [ Inline
+                 Attr
+                 ( [] , fromList [ ( "key" , "value" ) ] )
+                 (Link
+                    URLLink
+                    (Target "http://example.com")
+                    [ Inline mempty (Str "with [brackets]") ])
+             ])
+      ]
+  }
