diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,19 @@
 
 ### Bug fixes
 
+## 0.1.1 -- 2026-06-21
+
+### New features
+
+* Export `DoxygenKey` from `Doxygen.Parser.Types`. It is still available from
+  `Doxygen.Parser` as before.
+
+### Minor changes
+
+* Build `Doxygen.Parser.Types` and `Doxygen.Parser.Warning` as public library
+  modules so they get their own Haddock pages. In 0.1.0 they were re-exported
+  from a private sub-library, which left them without module pages on Hackage.
+
 ## 0.1.0 -- 2026-06-18
 
 * First version. Extracted from
diff --git a/doxygen-parser.cabal b/doxygen-parser.cabal
--- a/doxygen-parser.cabal
+++ b/doxygen-parser.cabal
@@ -1,6 +1,6 @@
 cabal-version:   3.0
 name:            doxygen-parser
-version:         0.1.0
+version:         0.1.1
 license:         BSD-3-Clause
 license-file:    LICENSE
 copyright:       2024-2026 Well-Typed LLP and Anduril Industries Inc.
@@ -56,15 +56,16 @@
     OverloadedRecordDot
     OverloadedStrings
 
-library internal
+library
   import:          lang
-  visibility:      private
-  hs-source-dirs:  src-internal
+  hs-source-dirs:  src src-internal
   exposed-modules:
-    Doxygen.Parser.Internal
+    Doxygen.Parser
     Doxygen.Parser.Types
     Doxygen.Parser.Warning
 
+  -- Implementation module, kept off the public Hackage surface.
+  other-modules:   Doxygen.Parser.Internal
   build-depends:
     , containers   >=0.6.5.1 && <0.9
     , directory    >=1.3.6.2 && <1.4
@@ -74,15 +75,23 @@
     , text         >=1.2     && <2.2
     , xml-conduit  >=1.9     && <1.11
 
-library
-  import:             lang
-  hs-source-dirs:     src
-  exposed-modules:    Doxygen.Parser
-  reexported-modules:
-    , Doxygen.Parser.Types
-    , Doxygen.Parser.Warning
-
-  build-depends:      doxygen-parser:internal
+-- Private sub-library that re-exposes the implementation module to the test
+-- suite. It depends on the main library so 'Doxygen.Parser.Types' (and the
+-- 'DoxygenKey' it now houses) stay a single shared copy across both.
+library internal
+  import:          lang
+  visibility:      private
+  hs-source-dirs:  src-internal
+  exposed-modules: Doxygen.Parser.Internal
+  build-depends:
+    , containers      >=0.6.5.1 && <0.9
+    , directory       >=1.3.6.2 && <1.4
+    , doxygen-parser
+    , filepath        >=1.4     && <1.6
+    , process         >=1.6     && <1.7
+    , temporary       >=1.3     && <1.4
+    , text            >=1.2     && <2.2
+    , xml-conduit     >=1.9     && <1.11
 
 test-suite test-doxygen-parser
   import:         lang
diff --git a/src-internal/Doxygen/Parser/Internal.hs b/src-internal/Doxygen/Parser/Internal.hs
--- a/src-internal/Doxygen/Parser/Internal.hs
+++ b/src-internal/Doxygen/Parser/Internal.hs
@@ -2,7 +2,7 @@
 
 {-# OPTIONS_HADDOCK hide #-}
 
--- | Doxygen XML parser — internal implementation.
+-- | Doxygen XML parser, internal implementation.
 --
 -- This module contains the full implementation of the Doxygen XML parser.
 -- The public API is re-exported by "Doxygen.Parser"; this module additionally
@@ -20,7 +20,7 @@
 --
 -- [@\<file\>_8h.xml@]
 --   One per input file (e.g. @myheader_8h.xml@).  Lists all declarations
---   with their @refid@s — we don't parse comments from these directly.
+--   with their @refid@s, we don't parse comments from these directly.
 --
 -- [@group__\<name\>.xml@]
 --   One per @\@defgroup@\/@\@addtogroup@ group.  Contains the group title,
@@ -45,7 +45,6 @@
   , Doxygen(..)
   , emptyDoxygen
     -- * Lookup keys
-  , DoxygenKey(..)
   , lookupComment
     -- * Group sections
   , lookupGroupMembership
@@ -126,26 +125,6 @@
   }
 
 {-------------------------------------------------------------------------------
-  Lookup keys
--------------------------------------------------------------------------------}
-
--- | Key for looking up a comment in the 'Doxygen' state
---
--- Unifies the four separate map lookups (declarations, structs, fields,
--- enum values) into a single 'Map' keyed by this type.
---
-data DoxygenKey
-  = KeyDecl { name :: Text }
-    -- ^ Function, typedef, variable, or enum (keyed by C name)
-  | KeyStruct { name :: Text }
-    -- ^ Struct\/union compound (keyed by C type name)
-  | KeyField { structName :: Text, fieldName :: Text }
-    -- ^ Struct\/union field
-  | KeyEnumValue { enumName :: Text, valueName :: Text }
-    -- ^ Enum value
-  deriving stock (Eq, Ord, Show)
-
-{-------------------------------------------------------------------------------
   State
 -------------------------------------------------------------------------------}
 
@@ -315,7 +294,7 @@
     boolOption False = "NO"
 
 {-------------------------------------------------------------------------------
-  XML parsing — assembles Doxygen directly from XML files
+  XML parsing, assembles Doxygen directly from XML files
 -------------------------------------------------------------------------------}
 
 -- | Parse the XML output directory into a 'Result'
@@ -368,12 +347,12 @@
 data XMLFileResult = XMLFileResult {
     comments       :: Map DoxygenKey (Comment DoxyRef)
   , groupTitles    :: [(Text, Text)]
-    -- ^ (group name, title) — only populated for group XML files
+    -- ^ (group name, title), only populated for group XML files
   , groupChildren  :: [(Text, Text)]
-    -- ^ (child group name, parent group name) — derived from
+    -- ^ (child group name, parent group name), derived from
     -- @\<innergroup\>@ elements in group XML files
   , groupMembers   :: [(Text, Text)]
-    -- ^ (declaration name, group name) — derived from
+    -- ^ (declaration name, group name), derived from
     -- @\<innerclass\>@ elements in group XML files (structs\/unions)
   , warnings       :: [Warning]
   }
@@ -583,7 +562,7 @@
     -------------------------------------------------------------------}
 
     -- Top-level declarations (functions, typedefs, enums).
-    -- Struct fields have a qualifiedname containing "::" — exclude those.
+    -- Struct fields have a qualifiedname containing "::", exclude those.
     toDeclMap :: [MemberInfo] -> [(DoxygenKey, Comment DoxyRef)]
     toDeclMap ms =
       [ (KeyDecl mi.miName, mi.miComment)
@@ -660,7 +639,7 @@
                (evp.warnings ++ commentWarns, [])
 
     {-------------------------------------------------------------------
-      Child classifiers — one fold per XML element type
+      Child classifiers, one fold per XML element type
 
       Each classifier iterates children exactly once, dispatching on
       element name.  Known-but-ignored elements are listed explicitly
@@ -797,7 +776,7 @@
 
 -- | What to do with a child element
 data ChildAction a
-  = Skip      -- ^ Known child, not needed here — skip silently
+  = Skip      -- ^ Known child, not needed here, skip silently
   | Yield a   -- ^ Known child, include in results
 
 -- | Process element children, dispatching on element name.
@@ -805,9 +784,9 @@
 -- For each element child, calls the handler with the element name and
 -- cursor.  The handler returns:
 --
---   * @Just (Yield x)@ — include @x@ in the result list
---   * @Just Skip@ — known child, skip silently
---   * @Nothing@ — unknown child, emit a 'StructureLevel' warning
+--   * @Just (Yield x)@, include @x@ in the result list
+--   * @Just Skip@, known child, skip silently
+--   * @Nothing@, unknown child, emit a 'StructureLevel' warning
 --
 -- Text and other non-element nodes are silently skipped.
 forChildren
@@ -947,7 +926,7 @@
     let children = Cursor.child cursor
         titles = [extractText c | c <- children, nodeElementName c == Just "title"]
         (kindWarns, kind) = parseSimpleSectKind cursor (listToMaybe titles)
-        -- Filter <title> — already consumed by parseSimpleSectKind for SSPar
+        -- Filter <title>, already consumed by parseSimpleSectKind for SSPar
         contentChildren = filter (not . isTitle) children
         (contentWarns, content) = unzipBlocks contentChildren
     in  (kindWarns ++ contentWarns, [SimpleSect kind content])
@@ -1232,7 +1211,7 @@
 --
 -- This is a deny-list: it excludes known non-entity files.  If future doxygen
 -- versions add new auxiliary XML files, they would pass through and be parsed
--- as entities — harmlessly yielding an empty result since they won't contain
+-- as entities, harmlessly yielding an empty result since they won't contain
 -- a recognised @\<compounddef\>@.
 --
 -- Excludes schema files (@.xsd@), XSLT files, @index.xml@, @Doxyfile.xml@,
diff --git a/src-internal/Doxygen/Parser/Types.hs b/src-internal/Doxygen/Parser/Types.hs
deleted file mode 100644
--- a/src-internal/Doxygen/Parser/Types.hs
+++ /dev/null
@@ -1,208 +0,0 @@
--- | Doxygen comment types parameterized by cross-reference type
---
--- These types represent structured Doxygen comments parsed from XML output.
--- They are parameterized by @ref@ so that cross-references can be threaded
--- through consumer-specific pipelines and resolved later.
---
--- The structure closely mirrors Doxygen's XML schema:
---
--- * 'Comment' splits brief and detailed descriptions
--- * 'Block' represents block-level content (paragraphs, lists, code, etc.)
--- * 'Inline' represents inline formatting and cross-references
--- * 'Param' represents documented parameters with direction
---
--- Intended for qualified import:
---
--- @
--- import Doxygen.Parser.Types qualified as Doxy
--- @
---
-module Doxygen.Parser.Types (
-    -- * Comment types
-    Comment(..)
-  , Block(..)
-  , Inline(..)
-  , Param(..)
-    -- * Cross-reference types
-  , DoxyRef(..)
-  , RefKind(..)
-    -- * Enumerations
-  , ParamListKind(..)
-  , ParamDirection(..)
-  , SimpleSectKind(..)
-  ) where
-
-import Data.Text (Text)
-import GHC.Generics (Generic)
-
-{-------------------------------------------------------------------------------
-  Comment types
--------------------------------------------------------------------------------}
-
--- | A Doxygen comment with brief and detailed sections
---
--- The @ref@ parameter is the cross-reference type.  The parser produces
--- @Comment DoxyRef@, where each 'DoxyRef' carries the raw C name and the
--- optional @kindref@.  Consumers can 'fmap' or 'traverse' to resolve these
--- to their own identifier types.
---
--- Corresponds to the @\<briefdescription\>@ and @\<detaileddescription\>@
--- elements in Doxygen XML output.
---
-data Comment ref = Comment {
-    brief    :: [Inline ref]
-    -- ^ Brief one-line summary, from the @\<briefdescription\>@ element.
-  , detailed :: [Block ref]
-    -- ^ Detailed description blocks, from @\<detaileddescription\>@.
-  }
-  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
-
--- | Block-level content in a Doxygen comment
---
-data Block ref
-  = Paragraph [Inline ref]
-    -- ^ A paragraph: @\<para\>@ element
-  | ParamList ParamListKind [Param ref]
-    -- ^ Parameter or return value list: @\<parameterlist\>@
-  | SimpleSect SimpleSectKind [Block ref]
-    -- ^ Special section: @\<simplesect kind=\"...\"\>@
-  | CodeBlock [Text]
-    -- ^ Code block: @\<programlisting\>@
-  | ItemizedList [[Block ref]]
-    -- ^ Bullet list: @\<itemizedlist\>@. Each element is one list item.
-  | OrderedList [[Block ref]]
-    -- ^ Numbered list: @\<orderedlist\>@. Each element is one list item.
-  | XRefSect Text [Block ref]
-    -- ^ Cross-reference section: @\<xrefsect\>@
-    --
-    -- Used for @\@deprecated@ and similar. First field is the title
-    -- (e.g., \"Deprecated\"), second is the description.
-  | Tag Text [Block ref]
-    -- ^ Unsupported or unrecognised XML\/HTML tag.
-    --
-    -- Used for tags that do not have a dedicated constructor (e.g., HTML
-    -- @\<table\>@).  The 'Text' is the element name; the @[Block ref]@
-    -- are the recursively-parsed children, so that content inside
-    -- unsupported tags is still preserved.
-  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
-
--- | Inline content in a Doxygen comment
-data Inline ref
-  = Text Text
-    -- ^ Plain text
-  | Bold [Inline ref]
-    -- ^ Bold text: @\<bold\>@ element (from @\@b@ or @\<b\>@)
-  | Emph [Inline ref]
-    -- ^ Emphasized text: @\<emphasis\>@ element (from @\@e@, @\@a@, or @\<i\>@)
-  | Mono [Inline ref]
-    -- ^ Monospace text: @\<computeroutput\>@ element (from @\@c@, @\@p@, or @\<code\>@)
-  | Ref ref Text
-    -- ^ Cross-reference: @\<ref\>@ element
-    --
-    -- First field is the reference (consumer-specific type).
-    -- Second field is the display text (the C name as shown in the comment).
-  | Anchor Text
-    -- ^ Anchor: @\<anchor\>@ element
-  | Link [Inline ref] Text
-    -- ^ Hyperlink: @\<ulink\>@ element. First field is the label, second is the URL.
-  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
-
--- | A documented parameter
---
-data Param ref = Param {
-    paramName      :: Text
-    -- ^ Parameter name, from the @\<parametername\>@ element.
-  , paramDirection :: Maybe ParamDirection
-    -- ^ Direction annotation (@in@, @out@, @inout@), when present.
-  , paramDesc      :: [Block ref]
-    -- ^ Parameter description blocks.
-  }
-  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
-
-{-------------------------------------------------------------------------------
-  Enumerations
--------------------------------------------------------------------------------}
-
--- | Kind of parameter list
---
-data ParamListKind
-  = ParamListParam
-    -- ^ @\@param@ documentation
-  | ParamListRetVal
-    -- ^ @\@retval@ documentation
-  deriving stock (Show, Eq, Generic)
-
--- | Parameter passing direction
---
-data ParamDirection
-  = DirIn
-    -- ^ Input parameter: @\@param[in]@
-  | DirOut
-    -- ^ Output parameter: @\@param[out]@
-  | DirInOut
-    -- ^ Input/output parameter: @\@param[in,out]@
-  deriving stock (Show, Eq, Generic)
-
--- | Kind of simple section
---
-data SimpleSectKind
-  = SSReturn
-    -- ^ @\@return@ / @\@returns@ / @\@result@
-  | SSWarning
-    -- ^ @\@warning@
-  | SSNote
-    -- ^ @\@note@
-  | SSSee
-    -- ^ @\@see@ / @\@sa@
-  | SSSince
-    -- ^ @\@since@
-  | SSVersion
-    -- ^ @\@version@
-  | SSPre
-    -- ^ @\@pre@
-  | SSPost
-    -- ^ @\@post@
-  | SSPar Text
-    -- ^ @\@par Title:@ (the 'Text' is the paragraph title)
-  | SSDeprecated
-    -- ^ @\@deprecated@
-  | SSRemark
-    -- ^ @\@remark@ / @\@remarks@
-  | SSAttention
-    -- ^ @\@attention@
-  | SSTodo
-    -- ^ @\@todo@
-  | SSInvariant
-    -- ^ @\@invariant@
-  | SSAuthor
-    -- ^ @\@author@
-  | SSDate
-    -- ^ @\@date@
-  deriving stock (Show, Eq, Generic)
-
-{-------------------------------------------------------------------------------
-  Cross-reference types
--------------------------------------------------------------------------------}
-
--- | Kind of a Doxygen cross-reference target
---
--- Corresponds to the @kindref@ attribute on @\<ref\>@ elements in Doxygen XML.
-data RefKind
-  = RefCompound
-    -- ^ Compound type (struct, union, class, namespace, etc.)
-  | RefMember
-    -- ^ Member (function, variable, typedef, macro, enum value, etc.)
-  deriving stock (Show, Eq, Generic)
-
--- | Doxygen cross-reference with optional kind information
---
--- The parser produces @'DoxyRef'@ values at cross-reference positions
--- (@\<ref\>@ elements in Doxygen XML), preserving the @kindref@ attribute
--- when present.
-data DoxyRef = DoxyRef {
-    doxyRefName :: Text
-    -- ^ The referenced C name, as shown in the comment.
-  , doxyRefKind :: Maybe RefKind
-    -- ^ The @kindref@ attribute, when Doxygen records it.
-  }
-  deriving stock (Show, Eq, Generic)
diff --git a/src-internal/Doxygen/Parser/Warning.hs b/src-internal/Doxygen/Parser/Warning.hs
deleted file mode 100644
--- a/src-internal/Doxygen/Parser/Warning.hs
+++ /dev/null
@@ -1,52 +0,0 @@
--- | Structured warnings for unsupported or degraded Doxygen content
---
--- When the parser encounters Doxygen XML elements it cannot fully represent
--- in the typed AST, it emits a 'Warning' describing what happened and how
--- the content was degraded.
---
-module Doxygen.Parser.Warning (
-    Warning(..)
-  , Context(..)
-  , Degradation(..)
-  ) where
-
-import Data.Text (Text)
-import GHC.Generics (Generic)
-
--- | A structured warning about unsupported or degraded content
-data Warning = Warning {
-    element     :: Text
-    -- ^ The XML element or feature that triggered the warning
-  , context     :: Context
-    -- ^ Where in the AST the element appeared
-  , degradation :: Degradation
-    -- ^ How the content was degraded
-  , explanation :: Text
-    -- ^ Human-readable description of what happened
-  }
-  deriving stock (Show, Eq, Generic)
-
--- | Where in the AST hierarchy the unsupported element appeared
-data Context
-  = BlockLevel
-    -- ^ Inside a block-level context (e.g., child of @\<para\>@ or
-    -- @\<detaileddescription\>@)
-  | InlineLevel
-    -- ^ Inside an inline context (e.g., child of @\<bold\>@ or inline
-    -- content within @\<para\>@)
-  | UnknownSectKind
-    -- ^ An unrecognised @kind@ attribute on @\<simplesect\>@
-  | StructureLevel Text
-    -- ^ Inside a structural element.  The 'Text' identifies the parent
-    -- (e.g. @\"compounddef\"@, @\"sectiondef\"@, @\"memberdef\"@).
-  deriving stock (Show, Eq, Generic)
-
--- | How the unsupported content was handled
-data Degradation
-  = Omitted
-    -- ^ Content was entirely dropped from the AST
-  | DegradedToText
-    -- ^ Formatting was lost; content preserved as plain text
-  | DefaultedTo Text
-    -- ^ An unknown value was replaced with a default
-  deriving stock (Show, Eq, Generic)
diff --git a/src/Doxygen/Parser/Types.hs b/src/Doxygen/Parser/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Doxygen/Parser/Types.hs
@@ -0,0 +1,232 @@
+-- | Doxygen comment types parameterized by cross-reference type
+--
+-- These types represent structured Doxygen comments parsed from XML output.
+-- They are parameterized by @ref@ so that cross-references can be threaded
+-- through consumer-specific pipelines and resolved later.
+--
+-- The structure closely mirrors Doxygen's XML schema:
+--
+-- * 'Comment' splits brief and detailed descriptions
+-- * 'Block' represents block-level content (paragraphs, lists, code, etc.)
+-- * 'Inline' represents inline formatting and cross-references
+-- * 'Param' represents documented parameters with direction
+--
+-- It also provides 'DoxygenKey', the key used to index parsed comments.
+--
+-- Intended for qualified import:
+--
+-- @
+-- import Doxygen.Parser.Types qualified as Doxy
+-- @
+--
+module Doxygen.Parser.Types (
+    -- * Comment types
+    Comment(..)
+  , Block(..)
+  , Inline(..)
+  , Param(..)
+    -- * Cross-reference types
+  , DoxyRef(..)
+  , RefKind(..)
+    -- * Enumerations
+  , ParamListKind(..)
+  , ParamDirection(..)
+  , SimpleSectKind(..)
+    -- * Lookup keys
+  , DoxygenKey(..)
+  ) where
+
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+{-------------------------------------------------------------------------------
+  Comment types
+-------------------------------------------------------------------------------}
+
+-- | A Doxygen comment with brief and detailed sections
+--
+-- The @ref@ parameter is the cross-reference type.  The parser produces
+-- @Comment DoxyRef@, where each 'DoxyRef' carries the raw C name and the
+-- optional @kindref@.  Consumers can 'fmap' or 'traverse' to resolve these
+-- to their own identifier types.
+--
+-- Corresponds to the @\<briefdescription\>@ and @\<detaileddescription\>@
+-- elements in Doxygen XML output.
+--
+data Comment ref = Comment {
+    brief    :: [Inline ref]
+    -- ^ Brief one-line summary, from the @\<briefdescription\>@ element.
+  , detailed :: [Block ref]
+    -- ^ Detailed description blocks, from @\<detaileddescription\>@.
+  }
+  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
+
+-- | Block-level content in a Doxygen comment
+--
+data Block ref
+  = Paragraph [Inline ref]
+    -- ^ A paragraph: @\<para\>@ element
+  | ParamList ParamListKind [Param ref]
+    -- ^ Parameter or return value list: @\<parameterlist\>@
+  | SimpleSect SimpleSectKind [Block ref]
+    -- ^ Special section: @\<simplesect kind=\"...\"\>@
+  | CodeBlock [Text]
+    -- ^ Code block: @\<programlisting\>@
+  | ItemizedList [[Block ref]]
+    -- ^ Bullet list: @\<itemizedlist\>@. Each element is one list item.
+  | OrderedList [[Block ref]]
+    -- ^ Numbered list: @\<orderedlist\>@. Each element is one list item.
+  | XRefSect Text [Block ref]
+    -- ^ Cross-reference section: @\<xrefsect\>@
+    --
+    -- Used for @\@deprecated@ and similar. First field is the title
+    -- (e.g., \"Deprecated\"), second is the description.
+  | Tag Text [Block ref]
+    -- ^ Unsupported or unrecognised XML\/HTML tag.
+    --
+    -- Used for tags that do not have a dedicated constructor (e.g., HTML
+    -- @\<table\>@).  The 'Text' is the element name; the @[Block ref]@
+    -- are the recursively-parsed children, so that content inside
+    -- unsupported tags is still preserved.
+  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
+
+-- | Inline content in a Doxygen comment
+data Inline ref
+  = Text Text
+    -- ^ Plain text
+  | Bold [Inline ref]
+    -- ^ Bold text: @\<bold\>@ element (from @\@b@ or @\<b\>@)
+  | Emph [Inline ref]
+    -- ^ Emphasized text: @\<emphasis\>@ element (from @\@e@, @\@a@, or @\<i\>@)
+  | Mono [Inline ref]
+    -- ^ Monospace text: @\<computeroutput\>@ element (from @\@c@, @\@p@, or @\<code\>@)
+  | Ref ref Text
+    -- ^ Cross-reference: @\<ref\>@ element
+    --
+    -- First field is the reference (consumer-specific type).
+    -- Second field is the display text (the C name as shown in the comment).
+  | Anchor Text
+    -- ^ Anchor: @\<anchor\>@ element
+  | Link [Inline ref] Text
+    -- ^ Hyperlink: @\<ulink\>@ element. First field is the label, second is the URL.
+  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
+
+-- | A documented parameter
+--
+data Param ref = Param {
+    paramName      :: Text
+    -- ^ Parameter name, from the @\<parametername\>@ element.
+  , paramDirection :: Maybe ParamDirection
+    -- ^ Direction annotation (@in@, @out@, @inout@), when present.
+  , paramDesc      :: [Block ref]
+    -- ^ Parameter description blocks.
+  }
+  deriving stock (Functor, Foldable, Traversable, Show, Eq, Generic)
+
+{-------------------------------------------------------------------------------
+  Enumerations
+-------------------------------------------------------------------------------}
+
+-- | Kind of parameter list
+--
+data ParamListKind
+  = ParamListParam
+    -- ^ @\@param@ documentation
+  | ParamListRetVal
+    -- ^ @\@retval@ documentation
+  deriving stock (Show, Eq, Generic)
+
+-- | Parameter passing direction
+--
+data ParamDirection
+  = DirIn
+    -- ^ Input parameter: @\@param[in]@
+  | DirOut
+    -- ^ Output parameter: @\@param[out]@
+  | DirInOut
+    -- ^ Input/output parameter: @\@param[in,out]@
+  deriving stock (Show, Eq, Generic)
+
+-- | Kind of simple section
+--
+data SimpleSectKind
+  = SSReturn
+    -- ^ @\@return@ / @\@returns@ / @\@result@
+  | SSWarning
+    -- ^ @\@warning@
+  | SSNote
+    -- ^ @\@note@
+  | SSSee
+    -- ^ @\@see@ / @\@sa@
+  | SSSince
+    -- ^ @\@since@
+  | SSVersion
+    -- ^ @\@version@
+  | SSPre
+    -- ^ @\@pre@
+  | SSPost
+    -- ^ @\@post@
+  | SSPar Text
+    -- ^ @\@par Title:@ (the 'Text' is the paragraph title)
+  | SSDeprecated
+    -- ^ @\@deprecated@
+  | SSRemark
+    -- ^ @\@remark@ / @\@remarks@
+  | SSAttention
+    -- ^ @\@attention@
+  | SSTodo
+    -- ^ @\@todo@
+  | SSInvariant
+    -- ^ @\@invariant@
+  | SSAuthor
+    -- ^ @\@author@
+  | SSDate
+    -- ^ @\@date@
+  deriving stock (Show, Eq, Generic)
+
+{-------------------------------------------------------------------------------
+  Cross-reference types
+-------------------------------------------------------------------------------}
+
+-- | Kind of a Doxygen cross-reference target
+--
+-- Corresponds to the @kindref@ attribute on @\<ref\>@ elements in Doxygen XML.
+data RefKind
+  = RefCompound
+    -- ^ Compound type (struct, union, class, namespace, etc.)
+  | RefMember
+    -- ^ Member (function, variable, typedef, macro, enum value, etc.)
+  deriving stock (Show, Eq, Generic)
+
+-- | Doxygen cross-reference with optional kind information
+--
+-- The parser produces @'DoxyRef'@ values at cross-reference positions
+-- (@\<ref\>@ elements in Doxygen XML), preserving the @kindref@ attribute
+-- when present.
+data DoxyRef = DoxyRef {
+    doxyRefName :: Text
+    -- ^ The referenced C name, as shown in the comment.
+  , doxyRefKind :: Maybe RefKind
+    -- ^ The @kindref@ attribute, when Doxygen records it.
+  }
+  deriving stock (Show, Eq, Generic)
+
+{-------------------------------------------------------------------------------
+  Lookup keys
+-------------------------------------------------------------------------------}
+
+-- | Key for looking up a comment in the parsed @Doxygen@ state.
+--
+-- Unifies the four separate map lookups (declarations, structs, fields,
+-- enum values) into a single @Map@ keyed by this type.
+--
+data DoxygenKey
+  = KeyDecl { name :: Text }
+    -- ^ Function, typedef, variable, or enum (keyed by C name)
+  | KeyStruct { name :: Text }
+    -- ^ Struct\/union compound (keyed by C type name)
+  | KeyField { structName :: Text, fieldName :: Text }
+    -- ^ Struct\/union field
+  | KeyEnumValue { enumName :: Text, valueName :: Text }
+    -- ^ Enum value
+  deriving stock (Eq, Ord, Show)
diff --git a/src/Doxygen/Parser/Warning.hs b/src/Doxygen/Parser/Warning.hs
new file mode 100644
--- /dev/null
+++ b/src/Doxygen/Parser/Warning.hs
@@ -0,0 +1,52 @@
+-- | Structured warnings for unsupported or degraded Doxygen content
+--
+-- When the parser encounters Doxygen XML elements it cannot fully represent
+-- in the typed AST, it emits a 'Warning' describing what happened and how
+-- the content was degraded.
+--
+module Doxygen.Parser.Warning (
+    Warning(..)
+  , Context(..)
+  , Degradation(..)
+  ) where
+
+import Data.Text (Text)
+import GHC.Generics (Generic)
+
+-- | A structured warning about unsupported or degraded content
+data Warning = Warning {
+    element     :: Text
+    -- ^ The XML element or feature that triggered the warning
+  , context     :: Context
+    -- ^ Where in the AST the element appeared
+  , degradation :: Degradation
+    -- ^ How the content was degraded
+  , explanation :: Text
+    -- ^ Human-readable description of what happened
+  }
+  deriving stock (Show, Eq, Generic)
+
+-- | Where in the AST hierarchy the unsupported element appeared
+data Context
+  = BlockLevel
+    -- ^ Inside a block-level context (e.g., child of @\<para\>@ or
+    -- @\<detaileddescription\>@)
+  | InlineLevel
+    -- ^ Inside an inline context (e.g., child of @\<bold\>@ or inline
+    -- content within @\<para\>@)
+  | UnknownSectKind
+    -- ^ An unrecognised @kind@ attribute on @\<simplesect\>@
+  | StructureLevel Text
+    -- ^ Inside a structural element.  The 'Text' identifies the parent
+    -- (e.g. @\"compounddef\"@, @\"sectiondef\"@, @\"memberdef\"@).
+  deriving stock (Show, Eq, Generic)
+
+-- | How the unsupported content was handled
+data Degradation
+  = Omitted
+    -- ^ Content was entirely dropped from the AST
+  | DegradedToText
+    -- ^ Formatting was lost; content preserved as plain text
+  | DefaultedTo Text
+    -- ^ An unknown value was replaced with a default
+  deriving stock (Show, Eq, Generic)
