diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/library/pro-abstract-prelude/Containers.hs b/library/pro-abstract-prelude/Containers.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract-prelude/Containers.hs
@@ -0,0 +1,87 @@
+module Containers where
+
+import BasePrelude (Applicative, Bool, Foldable, Maybe (..), Ord, Semigroup, maybe, pure, (.), (<*>), (<>))
+import Data.Foldable (fold, toList)
+import Data.Function (fix, id)
+import Optics.Core (Prism', prism')
+
+import Data.Sequence (Seq (..))
+import qualified Data.Sequence as Seq
+
+import Data.Map.Strict (Map)
+import qualified Data.Map.Strict as Map
+
+import Data.Set (Set)
+import qualified Data.Set as Set
+
+import qualified Data.Text as Text
+
+mapLookup :: Ord k => k -> Map k a -> Maybe a
+mapLookup = Map.lookup
+
+mapDelete :: Ord k => k -> Map k a -> Map k a
+mapDelete = Map.delete
+
+mapInsert :: Ord k => k -> a -> Map k a -> Map k a
+mapInsert = Map.insert
+
+setMember :: Ord a => a -> Set a -> Bool
+setMember = Set.member
+
+setInsert :: Ord a => a -> Set a -> Set a
+setInsert = Set.insert
+
+setDelete :: Ord a => a -> Set a -> Set a
+setDelete = Set.delete
+
+seqWither :: Applicative f => (a -> f (Maybe b)) -> Seq a -> f (Seq b)
+seqWither f = fix \r -> seqReduceR (pure Seq.Empty) \x xs -> pure seqConsMaybe <*> f x <*> r xs
+
+seqReduceR :: b -> (a -> Seq a -> b) -> Seq a -> b
+seqReduceR z f = \case{ Seq.Empty -> z; (Seq.:<|) x xs -> f x xs }
+
+seqConsMaybe :: Maybe a -> Seq a -> Seq a
+seqConsMaybe = maybe id (Seq.:<|)
+
+seqFromMaybe :: Maybe a -> Seq a
+seqFromMaybe = maybe Seq.empty Seq.singleton
+
+seqSingleton :: Prism' (Seq a) a
+seqSingleton = prism' Seq.singleton f
+  where
+    f = \case
+        (x Seq.:<| Seq.Empty) -> Just x
+        _ -> Nothing
+
+seqAtMostOne :: Prism' (Seq a) (Maybe a)
+seqAtMostOne = prism' (maybe Seq.empty Seq.singleton) f
+  where
+    f = \case
+        Seq.Empty -> Just Nothing
+        (x Seq.:<| Seq.Empty) -> Just (Just x)
+        _ -> Nothing
+
+seqToList :: Seq a -> [a]
+seqToList = toList
+
+seqConcat :: Seq (Seq a) -> Seq a
+seqConcat = fold
+
+textEmpty :: Text.Text
+textEmpty = Text.empty
+
+textConcat :: Foldable t => t Text.Text -> Text.Text
+textConcat = Text.concat . toList
+
+seqAppendAndConcat :: Semigroup a => Seq a -> Seq a -> Seq a
+seqAppendAndConcat x Empty = x
+seqAppendAndConcat Empty x = x
+seqAppendAndConcat (xs :|> x) (y :<| ys) = xs <> Seq.singleton (x <> y) <> ys
+
+seqAppendAndConcatWith :: (a -> a -> Maybe a) -> Seq a -> Seq a -> Seq a
+seqAppendAndConcatWith _ x Empty = x
+seqAppendAndConcatWith _ Empty x = x
+seqAppendAndConcatWith f a@(xs :|> x) b@(y :<| ys) =
+    case f x y of
+        Nothing -> a <> b
+        Just z -> xs <> Seq.singleton z <> ys
diff --git a/library/pro-abstract-prelude/Prelude.hs b/library/pro-abstract-prelude/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract-prelude/Prelude.hs
@@ -0,0 +1,21 @@
+module Prelude (module X) where
+
+import Control.DeepSeq as X (NFData)
+import Data.Functor as X ((<&>))
+import Data.Functor.Identity as X (Identity (Identity), runIdentity)
+import Data.Hashable as X (Hashable)
+import Data.Kind as X (Type)
+import Data.Map as X (Map)
+import Data.Maybe as X (fromMaybe, mapMaybe)
+import Data.Sequence as X (Seq)
+import Data.Set as X (Set)
+import Data.Text as X (Text)
+import GHC.Exts as X (IsList (..))
+import GHC.Generics as X (Generic)
+
+import Containers as X
+
+import BasePrelude as X (Applicative, Bool, Either (..), Eq, Foldable, Functor, Maybe (..), Monad, Monoid (mempty), Ord, Semigroup (..), Show, Traversable, const, either, fmap, maybe, not, pure, traverse, ($), (.), (<$>), (<*>), (=<<), (>>=))
+
+import Optics.Core as X (A_Fold, A_Getter, A_Lens, A_Traversal, AffineFold, AffineTraversal, AffineTraversal', An_AffineFold, An_AffineTraversal, An_Iso, Is, Iso, Iso', JoinKinds, Lens, Lens', NoIx, Optic, Optic', OpticKind, Prism', Traversal, Traversal', _Just, _Left, _Right, adjoin, afailing, afolding, atraversal, castOptic, coerced, equality, lens, matching, over, preview, prism', re, review, set, simple, to, traversalVL, traverseOf, traversed, view, (%), JoinKinds)
+import Optics.Empty.Core as X (_Empty, pattern Empty)
diff --git a/library/pro-abstract/ProAbstract.hs b/library/pro-abstract/ProAbstract.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract.hs
@@ -0,0 +1,253 @@
+{- |
+Copyright: © 2020 James Alexander Feldman-Crough
+           © 2022 Mission Valley Software LLC
+License: MPL-2.0
+
+
+=== Document
+
+/Document/ is the root of the tree.
+
+- 'Document' = ('metadata' : 'Metadata') × ('content' : 'Blocks')
+
+
+=== Tagged nodes
+
+/Tagged/ nodes are branch points within a document.
+
+- 'Tag' = ('name' : 'Text') × ('metadata' : 'Metadata') × ('annotation' : 'Annotation')
+
+- 'Tagged' x = ('tag' : 'Tag') × ('content' : x)
+
+'Tagged' is a data family, where the type parameter corresponds to the type of content under the tag. There are three instances of this family:
+
+- 'Tagged' 'Blocks'
+- 'Tagged' 'PlainBlock'
+- 'Tagged' 'Lines'
+
+Optics:
+
+- 'tag' targets the tag at a node; it is either a prism or an affine traversal, depending on the node type.
+- The 'allTags' traversal targets every tag at and below a node.
+- The 'allBlockTags' and 'allInlineTags' also target tags at or below a node, but are limited to tags at the block or inline level, respectively.
+- Since a tag has metadata, all of the optics for targeting parts of metadata (e.g. 'atSetting' and 'hasProperty') are also available on 'Tag' and on 'Tagged' nodes.
+
+Operations for altering tags and/or removing tagged nodes in bulk:
+
+  - Pure: 'mapMaybeTags', 'mapMaybeBlockTags', 'mapMaybeInlineTags'
+  - Monadic: 'witherTags', 'witherBlockTags', 'witherInlineTags'
+
+
+=== The Block level
+
+/Block-level/ content is everything that appears below the document level and above the paragraph level.
+
+- 'Block' = ('fork' : 'Tagged' 'Blocks') ∪ ('plain' : 'Tagged' 'PlainBlock') ∪ ('paragraph' : 'Paragraph')
+
+There are three kinds of block:
+
+- /Fork/ blocks contain more blocks.
+
+    * 'Tagged' 'Blocks' = ('tag' : 'Tag') × ('contents' : 'Block')
+
+- /Plain/ blocks contain text.
+
+    * 'Tagged' 'PlainBlock' = ('tag' : 'Tag') × ('content' : 'PlainBlock')
+    * 'PlainBlock' = ('contents' : 'Fragment') × ('annotation' : 'Annotation')
+
+- /Paragraphs/ contain inline content.
+
+    * 'Paragraph' = ('content' : 'Lines') × ('annotation' : 'Annotation')
+
+Unenforced guideline: The 'Lines' of a 'Paragraph' should contain at least one 'Line'.
+
+
+=== BlockTag
+
+A /BlockTag/ is a non-paragraph Block.
+
+- 'BlockTag' = ('fork' : 'Tagged' 'Blocks') ∪ ('plain' : 'Tagged' 'PlainBlock')
+
+The Block type can be described in terms of BlockTag as:
+
+- 'Block' = ('blockTag' : 'BlockTag') ∪ ('paragraph' : 'Paragraph')
+
+Example: @'blockTag' % 'Optics.Core.filtered' (\x -> 'Optics.Core.view' 'name' x == "h1")@ is an affine fold that targets blocks with a tag name of "h1".
+
+
+=== The Inline level
+
+/Inline-level/ content is everything below the paragraph level.
+
+Inline content is grouped into lines; we specify no particular semantics of line breaks but suggest that a typical consumer of 'Lines' will fold them together with a single space character interspersed between the lines.
+
+- 'Inline' = ('fork' : 'Tagged' 'Lines') ∪ ('plain' : 'Fragment')
+
+There are two kinds of inline:
+
+- /Fork/ inlines contain more inlines.
+
+    * 'Tagged' 'Lines' = ('tag' : 'Tag') × ('content' : 'Lines')
+    * 'Lines' = ('contents' : 'Line')
+    * 'Line' = ('content' : 'Seq' 'Inline') × ('annotation' : 'Annotation')
+
+- /Plain/ inlines contain text.
+
+Unenforced guideline: Each 'Line' should contain at least one 'Inline'.
+
+
+=== Fragments
+
+/Fragments/ are all the little scraps of text that serve as the leaves of the document tree.
+
+- 'Fragment' = ('content' : 'Text') × ('annotation' : 'Annotation')
+
+A fragment may appear at the inline level as a plain 'Inline', or at the block level as a line of a 'PlainBlock'.
+
+Unenforced guidelines: Fragment text should be non-empty, and it should not contain any line break characters.
+
+
+=== Metadata
+
+Documents and tags both have /metadata/.
+
+- 'Metadata' = ('properties' : 'Set' 'Text') × ('settings' : 'Map' 'Text' 'Text')
+
+Optics:
+
+- 'metadata' targets the metadata of a node; it is either a prism or an affine traversal, depending on the node type.
+- 'atSetting' and 'hasProperty' target specific parts of metadata by name.
+- The 'allMetadata' traversal targets every metadata at and under a node.
+
+
+=== Content
+
+The /content/ of a node is whatever is nested directly under it in the document tree. The 'content' lens is overloaded via the 'Content' type family and 'HasContent' class.
+
+The /contents/ of a node is the type of sequence element most directly nested under it. The 'contents' lens is via the 'Contents' type family and 'HasContents' class.
+
+List of nodes and their content types:
+
++-----------------------+-----------------------+-----------------------+
+| __x__                 | __Content x__         | __Contents x__        |
++-----------------------+-----------------------+-----------------------+
+| 'Document'            | 'Blocks'              | 'Block'               |
++-----------------------+-----------------------+-----------------------+
+| 'Blocks'              | —                     | 'Block'               |
++-----------------------+-----------------------+-----------------------+
+| 'Paragraph'           | 'Lines'               | 'Line'                |
++-----------------------+-----------------------+-----------------------+
+| 'Lines'               | —                     | 'Line'                |
++-----------------------+-----------------------+-----------------------+
+| 'Line'                | —                     | 'Inline'              |
++-----------------------+-----------------------+-----------------------+
+| 'PlainBlock'          | —                     | 'Fragment'            |
++-----------------------+-----------------------+-----------------------+
+| 'Tagged' y            | y                     | 'Contents' y          |
++-----------------------+-----------------------+-----------------------+
+| 'Fragment'            | 'Text'                | —                     |
++-----------------------+-----------------------+-----------------------+
+
+'HasContent' and 'HasContents' have type aliases 'HasContents'' and 'HasContents'' respectively for types that only support simple optics.
+
+
+=== Fork and plain
+
+At both the block and inline level, a node may be a /fork/ that contains a sequence of more content at the same level, and a node may be /plain/ node that just contains text.
+
+The 'fork' prism is overloaded via the 'Fork' type family and 'CanFork' class. List of nodes and their fork types:
+
++-----------------------+-----------------------+
+| __x__                 | __Fork x__            |
++-----------------------+-----------------------+
+| 'Block'               | 'Tagged' 'Blocks'     |
++-----------------------+-----------------------+
+| 'Inline'              | 'Tagged' 'Lines'      |
++-----------------------+-----------------------+
+
+The 'plain' prism is overloaded via the 'Plain' type family and 'CanBePlain' class. List of nodes and their plain types:
+
++-----------------------+-----------------------+
+| __x__                 | __Plain x__           |
++-----------------------+-----------------------+
+| 'Block'               | 'Tagged' 'PlainBlock' |
++-----------------------+-----------------------+
+| 'Inline'              | 'Fragment'            |
++-----------------------+-----------------------+
+
+('Block' also has a third prism, 'paragraph', which is neither fork nor plain.)
+
+
+=== Annotation
+
+An /annotation/ is attached to every 'Tag', 'PlainBlock', 'Paragraph', 'Line', and 'Fragment'. The 'Annotation' type may be anything, but its original purpose was to map each component of a document to the location where it appeared in a text file.
+
+Optics:
+
+- The 'annotation' lens targets a single annotation at a node
+- The 'allAnnotations' traversal targets every annotation at and under a node.
+
+
+=== Modules
+
+The ProAbstract module provides everything in the pro-abstract package. There are also some smaller collections of reëxports.
+
+Organized by topic:
+
+- "ProAbstract.Annotation"
+- "ProAbstract.Content"
+- "ProAbstract.Metadata"
+- "ProAbstract.Structure"
+- "ProAbstract.Tag"
+- "ProAbstract.Tagless"
+
+Organized by kind of thing:
+
+- "ProAbstract.Types" — The essential types, exported abstractly. Includes type families and data families.
+- "ProAbstract.Optics" — Lenses, prisms, traversals, etc. Many of these are class methods, but are exported here without their classes.
+- "ProAbstract.Datatypes" — Datatypes including with their constructors and record fields
+- "ProAbstract.Families" — Type families and data families.
+- "ProAbstract.Classes" — Typeclasses, mostly for the polymorphic optics.
+
+-}
+module ProAbstract
+    (
+    {- * Document -} Document (..),
+
+    {- * Blocks -} Block (..), Blocks (..),
+    {- ** Paragraphs -} Paragraph (..), HasManyParagraphs (..),
+    {- ** Tag blocks -} BlockTag (..), blockTag,
+
+    {- * Lines -} Inline (..), Line (..), Lines (..),
+    {- ** Plain text -} Fragment (..), PlainBlock (..),
+            HasManyPlainInlines (..), HasManyPlainBlocks (..),
+
+    {- * Tags -} Tag (..), Tagged (..), name, HasTag (..),
+    {- ** Traversal -} HasManyTags (..), HasManyBlockTags (..),
+    {- ** Withering -} HasWitherableTags (..),
+            HasWitherableInlineTags (..), HasWitherableBlockTags (..),
+
+    {- * Prisms -}
+    {- ** Plain -} Plain, CanBePlain (..),
+    {- ** Fork -} Fork, CanFork (..),
+    {- ** Paragraph -} paragraph,
+
+    {- * Metadata -} Metadata (..), HasMetadata (..), HasManyMetadata (..),
+    {- ** Properties -} properties, hasProperty,
+    {- ** Settings -} settings, atSetting,
+
+    {- * Content -} Content, HasContent (..), HasContent',
+    {- ** Contents -} Contents, HasContents (..), HasContents',
+
+    {- * Annotation -} Annotation, HasAnnotation (..), HasAnnotation',
+            HasManyAnnotations (..),
+
+    {- * Tagless content -} CanBeTagless (..), CanHaveTaglessContent (..),
+            KindOfText (..)
+
+    ) where
+
+import ProAbstract.Classes
+import ProAbstract.Datatypes
+import ProAbstract.Optics
+import ProAbstract.Types
diff --git a/library/pro-abstract/ProAbstract/Annotation.hs b/library/pro-abstract/ProAbstract/Annotation.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Annotation.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Annotation
+    ( Annotation
+    , HasAnnotation (..), HasAnnotation'
+    , HasManyAnnotations (..)
+    ) where
+
+import ProAbstract.Annotation.AnnotationFamily
+import ProAbstract.Annotation.HasAnnotation
+import ProAbstract.Annotation.HasManyAnnotations
diff --git a/library/pro-abstract/ProAbstract/Annotation/AnnotationFamily.hs b/library/pro-abstract/ProAbstract/Annotation/AnnotationFamily.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Annotation/AnnotationFamily.hs
@@ -0,0 +1,5 @@
+module ProAbstract.Annotation.AnnotationFamily
+    ( Annotation
+    ) where
+
+type family Annotation x
diff --git a/library/pro-abstract/ProAbstract/Annotation/HasAnnotation.hs b/library/pro-abstract/ProAbstract/Annotation/HasAnnotation.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Annotation/HasAnnotation.hs
@@ -0,0 +1,11 @@
+module ProAbstract.Annotation.HasAnnotation
+    ( HasAnnotation (..)
+    , HasAnnotation'
+    ) where
+
+import ProAbstract.Annotation.AnnotationFamily
+
+class HasAnnotation x x' where
+    annotation :: Lens x x' (Annotation x) (Annotation x')
+
+type HasAnnotation' x = HasAnnotation x x
diff --git a/library/pro-abstract/ProAbstract/Annotation/HasManyAnnotations.hs b/library/pro-abstract/ProAbstract/Annotation/HasManyAnnotations.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Annotation/HasManyAnnotations.hs
@@ -0,0 +1,8 @@
+module ProAbstract.Annotation.HasManyAnnotations
+    ( HasManyAnnotations (..)
+    ) where
+
+import ProAbstract.Annotation.AnnotationFamily
+
+class HasManyAnnotations x x' where
+    allAnnotations :: Traversal x x' (Annotation x) (Annotation x')
diff --git a/library/pro-abstract/ProAbstract/Classes.hs b/library/pro-abstract/ProAbstract/Classes.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Classes.hs
@@ -0,0 +1,40 @@
+module ProAbstract.Classes
+    (
+    -- * Block and inline prisms
+      CanBePlain (..), CanFork (..)
+
+    -- * Plaintext traversals
+    , HasManyPlainBlocks (..), HasManyPlainInlines (..)
+
+    -- * Annotation
+    , HasAnnotation (..), HasAnnotation', HasManyAnnotations (..)
+
+    -- * Content
+    , HasContent (..), HasContent', HasContents (..), HasContents'
+
+    -- * Metadata
+    , HasMetadata (..), HasManyMetadata (..)
+
+    -- * Tags
+    , HasTag (..)
+
+    -- ** Traversal
+    , HasManyTags (..), HasManyBlockTags (..)
+
+    -- ** Withering
+    , HasWitherableTags (..), HasWitherableInlineTags (..), HasWitherableBlockTags (..)
+
+    -- * Paragraphs
+    , HasManyParagraphs (..)
+
+    -- * Getting text from tagless content
+    , CanBeTagless (..), CanHaveTaglessContent (..), KindOfText (..)
+
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Metadata
+import ProAbstract.Structure
+import ProAbstract.Tag
+import ProAbstract.Tagless
diff --git a/library/pro-abstract/ProAbstract/Content.hs b/library/pro-abstract/ProAbstract/Content.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Content.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Content
+    ( Content, HasContent (..), HasContent'
+    , Contents, HasContents (..), HasContents'
+    ) where
+
+import ProAbstract.Content.ContentFamily
+import ProAbstract.Content.ContentsFamily
+import ProAbstract.Content.HasContent
+import ProAbstract.Content.HasContents
diff --git a/library/pro-abstract/ProAbstract/Content/ContentFamily.hs b/library/pro-abstract/ProAbstract/Content/ContentFamily.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Content/ContentFamily.hs
@@ -0,0 +1,5 @@
+module ProAbstract.Content.ContentFamily
+    ( Content
+    ) where
+
+type family Content a
diff --git a/library/pro-abstract/ProAbstract/Content/ContentsFamily.hs b/library/pro-abstract/ProAbstract/Content/ContentsFamily.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Content/ContentsFamily.hs
@@ -0,0 +1,5 @@
+module ProAbstract.Content.ContentsFamily
+    ( Contents
+    ) where
+
+type family Contents a
diff --git a/library/pro-abstract/ProAbstract/Content/HasContent.hs b/library/pro-abstract/ProAbstract/Content/HasContent.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Content/HasContent.hs
@@ -0,0 +1,10 @@
+module ProAbstract.Content.HasContent
+    ( HasContent (..), HasContent'
+    ) where
+
+import ProAbstract.Content.ContentFamily
+
+class HasContent x x' where
+    content :: Lens x x' (Content x) (Content x')
+
+type HasContent' x = HasContent x x
diff --git a/library/pro-abstract/ProAbstract/Content/HasContents.hs b/library/pro-abstract/ProAbstract/Content/HasContents.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Content/HasContents.hs
@@ -0,0 +1,10 @@
+module ProAbstract.Content.HasContents
+    ( HasContents (..), HasContents'
+    ) where
+
+import ProAbstract.Content.ContentsFamily
+
+class HasContents x x' where
+    contents :: Lens x x' (Seq (Contents x)) (Seq (Contents x'))
+
+type HasContents' x = HasContents x x
diff --git a/library/pro-abstract/ProAbstract/Datatypes.hs b/library/pro-abstract/ProAbstract/Datatypes.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Datatypes.hs
@@ -0,0 +1,28 @@
+module ProAbstract.Datatypes
+    (
+    -- * Document
+      Document (..)
+
+    -- * Blocks
+    , Block (..), Blocks (..)
+
+    -- * Lines
+    , Inline (..), Line (..), Lines (..)
+
+    -- * Tags and tagged things
+    , Tag (..), Tagged (..), BlockTag (..)
+
+    -- * Paragraph
+    , Paragraph (..)
+
+    -- * Plain text
+    , Fragment (..), PlainBlock (..)
+
+    -- * Metadata
+    , Metadata (..)
+
+    ) where
+
+import ProAbstract.Metadata
+import ProAbstract.Structure
+import ProAbstract.Tag
diff --git a/library/pro-abstract/ProAbstract/Families.hs b/library/pro-abstract/ProAbstract/Families.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Families.hs
@@ -0,0 +1,12 @@
+module ProAbstract.Families
+    ( Annotation
+    , Content
+    , Contents
+    , Fork
+    , Plain
+    , Tagged
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Structure
diff --git a/library/pro-abstract/ProAbstract/Metadata.hs b/library/pro-abstract/ProAbstract/Metadata.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Metadata.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Metadata
+  ( Metadata (..), HasMetadata (..), HasManyMetadata (..)
+  , properties, settings, hasProperty, atSetting
+  ) where
+
+import ProAbstract.Metadata.HasManyMetadata
+import ProAbstract.Metadata.HasMetadata
+import ProAbstract.Metadata.HasMetadataOptics
+import ProAbstract.Metadata.MetadataType
diff --git a/library/pro-abstract/ProAbstract/Metadata/HasManyMetadata.hs b/library/pro-abstract/ProAbstract/Metadata/HasManyMetadata.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Metadata/HasManyMetadata.hs
@@ -0,0 +1,8 @@
+module ProAbstract.Metadata.HasManyMetadata
+    ( HasManyMetadata (..)
+    ) where
+
+import ProAbstract.Metadata.MetadataType
+
+class HasManyMetadata x where
+    allMetadata :: Traversal' x Metadata
diff --git a/library/pro-abstract/ProAbstract/Metadata/HasMetadata.hs b/library/pro-abstract/ProAbstract/Metadata/HasMetadata.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Metadata/HasMetadata.hs
@@ -0,0 +1,13 @@
+module ProAbstract.Metadata.HasMetadata
+    ( HasMetadata (..), Metadata
+    ) where
+
+import ProAbstract.Metadata.MetadataType (Metadata)
+
+class HasMetadata x where
+    type MetadataOpticKind x
+    metadata :: Optic' (MetadataOpticKind x) NoIx x Metadata
+
+instance HasMetadata Metadata where
+    type MetadataOpticKind Metadata = An_Iso
+    metadata = castOptic simple
diff --git a/library/pro-abstract/ProAbstract/Metadata/HasMetadataOptics.hs b/library/pro-abstract/ProAbstract/Metadata/HasMetadataOptics.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Metadata/HasMetadataOptics.hs
@@ -0,0 +1,23 @@
+module ProAbstract.Metadata.HasMetadataOptics
+    ( properties, settings, hasProperty, atSetting
+    ) where
+
+import ProAbstract.Metadata.HasMetadata
+
+import qualified ProAbstract.Metadata.MetadataOptics as Meta
+
+-- | Fetch all properties from items which contain metadata.
+properties :: (HasMetadata m, JoinKinds (MetadataOpticKind m) A_Lens k) => Optic' k NoIx m (Set Text)
+properties = metadata % Meta.properties
+
+-- | Fetch all settings defined on items which contain metadata.
+settings :: (HasMetadata m, JoinKinds (MetadataOpticKind m) A_Lens k) => Optic' k NoIx m (Map Text Text)
+settings = metadata % Meta.settings
+
+-- | Check if a property is attached to an item with metadata. Using this optic as a setter will add a property if set to 'True' and remove the property when set to 'False'.
+hasProperty :: (HasMetadata m, JoinKinds (MetadataOpticKind m) A_Lens k) => Text -> Optic' k NoIx m Bool
+hasProperty k = metadata % Meta.hasProperty k
+
+-- | Select a setting from an item attached to metadata. Returns 'Nothing' if no value is set.
+atSetting :: (HasMetadata m, JoinKinds (MetadataOpticKind m) A_Lens k) => Text -> Optic' k NoIx m (Maybe Text)
+atSetting k = metadata % Meta.atSetting k
diff --git a/library/pro-abstract/ProAbstract/Metadata/MetadataOptics.hs b/library/pro-abstract/ProAbstract/Metadata/MetadataOptics.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Metadata/MetadataOptics.hs
@@ -0,0 +1,21 @@
+module ProAbstract.Metadata.MetadataOptics
+    ( properties, settings, hasProperty, atSetting
+    ) where
+
+import ProAbstract.Metadata.MetadataType
+
+-- | Targets all properties from metadata.
+properties :: Lens' Metadata (Set Text)
+properties = lens metadataProperties (\m p -> m { metadataProperties = p })
+
+-- | Targets all settings from metadata.
+settings :: Lens' Metadata (Map Text Text)
+settings = lens metadataSettings (\m s -> m { metadataSettings = s })
+
+-- | Check if metadata includes a property. Using this optic as a setter will add a property if set to 'True' and remove the property when set to 'False'.
+hasProperty :: Text -> Lens' Metadata Bool
+hasProperty k = properties % lens (setMember k) (\s b -> (if b then setInsert else setDelete) k s)
+
+-- | Targets a setting from metadata. Returns 'Nothing' if no value is set.
+atSetting :: Text -> Lens' Metadata (Maybe Text)
+atSetting k = settings % lens (mapLookup k) (\m x -> maybe (mapDelete k) (mapInsert k) x m)
diff --git a/library/pro-abstract/ProAbstract/Metadata/MetadataType.hs b/library/pro-abstract/ProAbstract/Metadata/MetadataType.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Metadata/MetadataType.hs
@@ -0,0 +1,20 @@
+module ProAbstract.Metadata.MetadataType
+    ( Metadata (..)
+    ) where
+
+{- | A set of properties and settings, associated with a document or tag.
+
+The namespaces of properties and settings are distinct; a property can share a name with a setting without conflict.
+-}
+data Metadata = Metadata
+    { metadataProperties :: Set Text      -- ^ 'ProAbstract.properties'
+    , metadataSettings   :: Map Text Text -- ^ 'ProAbstract.settings'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (NFData, Hashable)
+
+instance Monoid Metadata where
+    mempty = Metadata mempty mempty
+
+instance Semigroup Metadata where
+    Metadata p1 s1 <> Metadata p2 s2 = Metadata (p1 <> p2) (s1 <> s2)
diff --git a/library/pro-abstract/ProAbstract/Optics.hs b/library/pro-abstract/ProAbstract/Optics.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Optics.hs
@@ -0,0 +1,37 @@
+module ProAbstract.Optics
+    (
+    -- * Lenses
+      content, contents, tag, name
+
+    -- ** Annotation
+    , annotation
+
+    -- ** Metadata
+    , metadata, atSetting, hasProperty, properties, settings
+
+    -- * Prisms
+    , fork, plain, paragraph, blockTag
+
+    -- * Traversals
+    , allParagraphs, allPlainBlocks, allPlainInlines
+
+    -- ** Annotation
+    , allAnnotations
+
+    -- ** Tags
+    , allTags, allInlineTags, allBlockTags
+
+    -- ** Metadata
+    , allMetadata
+
+    -- * Affine folds
+    , tagless, taglessContent, KindOfText (..)
+
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Metadata
+import ProAbstract.Structure
+import ProAbstract.Tag
+import ProAbstract.Tagless
diff --git a/library/pro-abstract/ProAbstract/Structure.hs b/library/pro-abstract/ProAbstract/Structure.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure.hs
@@ -0,0 +1,43 @@
+module ProAbstract.Structure
+    (
+    -- * Document
+      Document (..)
+
+    -- * Blocks
+    , Block (..), paragraph, Blocks (..), BlockTag (..), blockTag
+
+    -- * Paragraph
+    , Paragraph (..)
+
+    -- * Inlines
+    , Inline (..), Line (..), Lines (..)
+
+    -- * Tagged
+    , Tagged (..)
+
+    -- * Text
+    , PlainBlock (..), Fragment (..)
+
+    -- * Plain and fork
+    , CanBePlain (..), Plain, CanFork (..), Fork
+
+    -- * Traversals
+    , HasManyParagraphs (..), HasManyPlainBlocks (..), HasManyPlainInlines (..)
+
+    ) where
+
+
+import ProAbstract.Structure.Block
+import ProAbstract.Structure.BlockTag
+import ProAbstract.Structure.CanBePlain
+import ProAbstract.Structure.CanFork
+import ProAbstract.Structure.Document
+import ProAbstract.Structure.Fork
+import ProAbstract.Structure.Fragment
+import ProAbstract.Structure.HasManyParagraphs
+import ProAbstract.Structure.HasManyPlainBlocks
+import ProAbstract.Structure.HasManyPlainInlines
+import ProAbstract.Structure.Inline
+import ProAbstract.Structure.Paragraph
+import ProAbstract.Structure.Plain
+import ProAbstract.Structure.PlainBlock
diff --git a/library/pro-abstract/ProAbstract/Structure/Block.hs b/library/pro-abstract/ProAbstract/Structure/Block.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Block.hs
@@ -0,0 +1,237 @@
+module ProAbstract.Structure.Block
+    ( Block (..), paragraph, Blocks (..), Tagged (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Structure.Fork
+import ProAbstract.Metadata
+import ProAbstract.Structure.CanBePlain
+import ProAbstract.Structure.CanFork
+import ProAbstract.Structure.HasManyParagraphs
+import ProAbstract.Structure.HasManyPlainBlocks
+import ProAbstract.Structure.HasManyPlainInlines
+import ProAbstract.Structure.Paragraph
+import ProAbstract.Structure.Plain
+import ProAbstract.Structure.PlainBlock
+import ProAbstract.Tag
+
+
+-- ⭐ Block
+
+data Block ann =
+    BlockPlain     (Tagged (PlainBlock ann)) -- ^ 'ProAbstract.plain'
+  | BlockParagraph (Paragraph ann)           -- ^ 'ProAbstract.paragraph'
+  | BlockFork      (Tagged (Blocks ann))     -- ^ 'ProAbstract.fork'
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Block ann) = ann
+
+type instance Plain (Block ann) = Tagged (PlainBlock ann)
+
+type instance Fork (Block ann) = Tagged (Blocks ann)
+
+instance HasMetadata (Block ann) where
+    type MetadataOpticKind (Block ann) = An_AffineTraversal
+    metadata = tag % metadata
+
+instance HasManyAnnotations (Block ann) (Block ann') where
+    allAnnotations = traversalVL \f -> \case
+        BlockPlain     x -> BlockPlain     <$> traverseOf allAnnotations f x
+        BlockParagraph x -> BlockParagraph <$> traverseOf allAnnotations f x
+        BlockFork      x -> BlockFork      <$> traverseOf allAnnotations f x
+
+instance HasAnnotation (Block ann) (Block ann) where
+    annotation = lens f g
+      where
+        f = \case
+            BlockPlain     x -> view annotation x
+            BlockParagraph x -> view annotation x
+            BlockFork      x -> view annotation x
+        g = \case
+            BlockPlain     x -> \a -> BlockPlain     (set annotation a x)
+            BlockParagraph x -> \a -> BlockParagraph (set annotation a x)
+            BlockFork      x -> \a -> BlockFork      (set annotation a x)
+
+instance HasManyPlainInlines (Block ann) where
+    allPlainInlines = allParagraphs % allPlainInlines
+
+instance HasManyParagraphs (Block ann) where
+    allParagraphs = paragraph `adjoin` (fork % allParagraphs)
+
+instance HasManyPlainBlocks (Block ann) where
+    allPlainBlocks = plain `adjoin` (fork % allPlainBlocks)
+
+instance HasTag (Block ann) where
+    type TagOpticKind (Block ann) = An_AffineTraversal
+    tag = atraversal f g
+      where
+        f = \case
+            BlockPlain x -> Right (view tag x)
+            BlockFork  x -> Right (view tag x)
+            x -> Left x
+        g = \case
+            BlockPlain x -> \a -> BlockPlain (set tag a x)
+            BlockFork  x -> \a -> BlockFork  (set tag a x)
+            x -> \_ -> x
+
+instance HasManyTags (Block ann) where
+    allTags = (fork % allTags) `adjoin` (paragraph % allTags) `adjoin` (plain % tag)
+    allInlineTags = allParagraphs % allInlineTags
+
+instance HasManyBlockTags (Block ann) where
+    allBlockTags = (fork % allBlockTags) `adjoin` (plain % tag)
+
+instance HasWitherableInlineTags (Block ann) where
+    witherInlineTags f = traverseOf allParagraphs (witherInlineTags f)
+
+instance HasManyMetadata (Block ann) where
+    allMetadata = allTags % metadata
+
+instance CanFork (Block ann) where
+    fork = prism'
+        BlockFork
+        \case{ BlockFork t -> Just t; _ -> Nothing }
+
+instance CanBePlain (Block ann) where
+    plain = prism'
+        BlockPlain
+        \case{ BlockPlain t -> Just t; _ -> Nothing }
+
+paragraph :: Prism' (Block ann) (Paragraph ann)
+paragraph = prism'
+    BlockParagraph
+    \case{ BlockParagraph p -> Just p; _ -> Nothing }
+
+
+-- ⭐ Blocks
+
+newtype Blocks ann =
+    Blocks
+      (Seq (Block ann)) -- ^ 'ProAbstract.content'
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+  deriving newtype (Semigroup, Monoid)
+
+type instance Contents (Blocks ann) = Block ann
+
+instance HasContents (Blocks ann) (Blocks ann') where
+    contents = castOptic coerced
+
+instance IsList (Blocks ann) where
+    type Item (Blocks ann) = Block ann
+    toList (Blocks xs) = toList xs
+    fromList xs = Blocks (fromList xs)
+
+type instance Annotation (Blocks ann) = ann
+
+instance HasManyAnnotations (Blocks ann) (Blocks ann') where
+    allAnnotations = contents % traversed % allAnnotations
+
+instance HasManyPlainInlines (Blocks ann) where
+    allPlainInlines = contents % traversed % allPlainInlines
+
+instance HasManyPlainBlocks (Blocks ann) where
+    allPlainBlocks = contents % traversed % allPlainBlocks
+
+instance HasManyTags (Blocks ann) where
+    allTags = contents % traversed % allTags
+    allInlineTags = contents % traversed % allInlineTags
+
+instance HasManyBlockTags (Blocks ann) where
+    allBlockTags = contents % traversed % allBlockTags
+
+instance HasManyMetadata (Blocks ann) where
+    allMetadata = allTags % metadata
+
+instance HasManyParagraphs (Blocks ann) where
+    allParagraphs = contents % traversed % allParagraphs
+
+instance HasWitherableTags (Blocks ann) where
+    witherTags f = traverseOf contents $ seqWither \case
+        BlockParagraph x ->
+            Just . BlockParagraph <$> traverseOf content (witherTags f) x
+        BlockPlain x ->
+            f (view tag x) >>= \case
+                Nothing -> pure Nothing
+                Just t -> pure . Just . BlockPlain . set tag t $ x
+        BlockFork x ->
+            f (view tag x) >>= \case
+                Nothing -> pure Nothing
+                Just t -> Just . BlockFork . set tag t <$> traverseOf content (witherTags f) x
+
+instance HasWitherableBlockTags (Blocks ann) where
+    witherBlockTags f = traverseOf contents $ seqWither \case
+        BlockParagraph x -> pure $ Just $ BlockParagraph x
+        BlockPlain x ->
+            f (view tag x) >>= \case
+                Nothing -> pure Nothing
+                Just t -> pure . Just . BlockPlain . set tag t $ x
+        BlockFork x ->
+            f (view tag x) >>= \case
+                Nothing -> pure Nothing
+                Just t -> Just . BlockFork . set tag t <$> traverseOf content (witherBlockTags f) x
+
+instance HasWitherableInlineTags (Blocks ann) where
+    witherInlineTags f = traverseOf (contents % traversed) (witherInlineTags f)
+
+
+-- ⭐ Tagged Blocks
+
+data instance Tagged (Blocks ann) =
+  TaggedBlocks
+    { blocksTag :: Tag ann -- ^ 'ProAbstract.tag'
+    , taggedBlocks :: Blocks ann -- ^ 'ProAbstract.content'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Tagged (Blocks ann)) = ann
+
+instance HasTag (Tagged (Blocks ann)) where
+    type TagOpticKind (Tagged (Blocks ann)) = A_Lens
+    tag = lens blocksTag \x a -> x{ blocksTag = a }
+
+type instance Content (Tagged (Blocks ann)) = Blocks ann
+
+type instance Contents (Tagged (Blocks ann)) = Block ann
+
+instance HasManyAnnotations (Tagged (Blocks ann)) (Tagged (Blocks ann')) where
+    allAnnotations = traversalVL \f (TaggedBlocks t b) -> TaggedBlocks
+        <$> traverseOf annotation f t <*> traverseOf allAnnotations f b
+
+instance HasAnnotation (Tagged (Blocks ann)) (Tagged (Blocks ann)) where
+     annotation = tag % annotation
+
+instance HasContent (Tagged (Blocks ann)) (Tagged (Blocks ann)) where
+    content = lens taggedBlocks \x c -> x{ taggedBlocks = c }
+
+instance HasContents (Tagged (Blocks ann)) (Tagged (Blocks ann)) where
+    contents = content % contents
+
+instance HasMetadata (Tagged (Blocks ann)) where
+    type MetadataOpticKind (Tagged (Blocks ann)) = A_Lens
+    metadata = tag % metadata
+
+instance HasManyPlainInlines (Tagged (Blocks ann)) where
+    allPlainInlines = content % allPlainInlines
+
+instance HasManyTags (Tagged (Blocks ann)) where
+    allTags = tag `adjoin` (content % allTags)
+    allInlineTags = allParagraphs % allInlineTags
+
+instance HasManyBlockTags (Tagged (Blocks ann)) where
+    allBlockTags = tag `adjoin` (content % allBlockTags)
+
+instance HasWitherableInlineTags (Tagged (Blocks ann)) where
+    witherInlineTags f = traverseOf allParagraphs (witherInlineTags f)
+
+instance HasManyMetadata (Tagged (Blocks ann)) where
+    allMetadata = allTags % metadata
+
+instance HasManyParagraphs (Tagged (Blocks ann)) where
+    allParagraphs = content % allParagraphs
+
+instance HasManyPlainBlocks (Tagged (Blocks ann)) where
+    allPlainBlocks = content % allPlainBlocks
diff --git a/library/pro-abstract/ProAbstract/Structure/BlockTag.hs b/library/pro-abstract/ProAbstract/Structure/BlockTag.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/BlockTag.hs
@@ -0,0 +1,89 @@
+module ProAbstract.Structure.BlockTag
+    ( BlockTag (..), blockTag
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Metadata
+import ProAbstract.Structure.Block
+import ProAbstract.Structure.CanBePlain
+import ProAbstract.Structure.CanFork
+import ProAbstract.Structure.Fork
+import ProAbstract.Structure.HasManyParagraphs
+import ProAbstract.Structure.HasManyPlainBlocks
+import ProAbstract.Structure.HasManyPlainInlines
+import ProAbstract.Structure.Plain
+import ProAbstract.Structure.PlainBlock
+import ProAbstract.Tag
+
+data BlockTag ann =
+    BlockTagFork  (Tagged (Blocks ann))     -- ^ 'ProAbstract.fork'
+  | BlockTagPlain (Tagged (PlainBlock ann)) -- ^ 'ProAbstract.plain'
+
+type instance Annotation (BlockTag ann) = ann
+
+type instance Plain (BlockTag ann) = Tagged (PlainBlock ann)
+
+type instance Fork (BlockTag ann) = Tagged (Blocks ann)
+
+instance CanFork (BlockTag ann) where
+    fork = prism' BlockTagFork \case{ BlockTagFork x -> Just x; _ -> Nothing }
+
+instance CanBePlain (BlockTag ann) where
+    plain = prism' BlockTagPlain \case{ BlockTagPlain x -> Just x; _ -> Nothing }
+
+instance HasAnnotation (BlockTag ann) (BlockTag ann) where
+    annotation = lens f g
+      where
+        f = \case
+            BlockTagFork x -> view annotation x
+            BlockTagPlain x -> view annotation x
+        g = \case
+            BlockTagFork x -> \a -> BlockTagFork $ set annotation a x
+            BlockTagPlain x -> \a -> BlockTagPlain $ set annotation a x
+
+instance HasManyAnnotations (BlockTag ann) (BlockTag ann') where
+    allAnnotations = traversalVL \f -> \case
+        BlockTagFork x -> BlockTagFork <$> traverseOf allAnnotations f x
+        BlockTagPlain x -> BlockTagPlain <$> traverseOf allAnnotations f x
+
+instance HasManyMetadata (BlockTag ann) where
+    allMetadata = (fork % allMetadata) `adjoin` (plain % metadata)
+
+instance HasManyParagraphs (BlockTag ann) where
+    allParagraphs = fork % allParagraphs
+
+instance HasManyPlainBlocks (BlockTag ann) where
+    allPlainBlocks = (fork % allPlainBlocks) `adjoin` plain
+
+instance HasManyPlainInlines (BlockTag ann) where
+    allPlainInlines = fork % allPlainInlines
+
+instance HasManyTags (BlockTag ann) where
+    allTags = (fork % allTags) `adjoin` (plain % tag)
+    allInlineTags = fork % allInlineTags
+
+instance HasTag (BlockTag ann) where
+    type TagOpticKind (BlockTag ann) = A_Lens
+    tag = lens f g
+      where
+        f = \case
+            BlockTagFork x -> view tag x
+            BlockTagPlain x -> view tag x
+        g = \case
+            BlockTagFork x -> \a -> BlockTagFork (set tag a x)
+            BlockTagPlain x -> \a -> BlockTagPlain (set tag a x)
+
+instance HasMetadata (BlockTag ann) where
+    type MetadataOpticKind (BlockTag ann) = A_Lens
+    metadata = tag % metadata
+
+blockTag :: Prism' (Block ann) (BlockTag ann)
+blockTag = prism' f g
+  where
+    f = \case
+      BlockTagFork x -> BlockFork x
+      BlockTagPlain x -> BlockPlain x
+    g = \case
+      BlockFork x -> Just (BlockTagFork x)
+      BlockPlain x -> Just (BlockTagPlain x)
+      _ -> Nothing
diff --git a/library/pro-abstract/ProAbstract/Structure/CanBePlain.hs b/library/pro-abstract/ProAbstract/Structure/CanBePlain.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/CanBePlain.hs
@@ -0,0 +1,8 @@
+module ProAbstract.Structure.CanBePlain
+    ( CanBePlain (..)
+    ) where
+
+import ProAbstract.Structure.Plain
+
+class CanBePlain x where
+    plain :: Prism' x (Plain x)
diff --git a/library/pro-abstract/ProAbstract/Structure/CanFork.hs b/library/pro-abstract/ProAbstract/Structure/CanFork.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/CanFork.hs
@@ -0,0 +1,8 @@
+module ProAbstract.Structure.CanFork
+    ( CanFork (..)
+    ) where
+
+import ProAbstract.Structure.Fork
+
+class CanFork x where
+    fork :: Prism' x (Fork x)
diff --git a/library/pro-abstract/ProAbstract/Structure/Document.hs b/library/pro-abstract/ProAbstract/Structure/Document.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Document.hs
@@ -0,0 +1,66 @@
+module ProAbstract.Structure.Document
+    ( Document (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Metadata
+import ProAbstract.Structure.Block
+import ProAbstract.Structure.HasManyParagraphs
+import ProAbstract.Structure.HasManyPlainBlocks
+import ProAbstract.Structure.HasManyPlainInlines
+import ProAbstract.Tag
+
+data Document ann = Document
+    { documentMetadata :: Metadata -- ^ 'ProAbstract.metadata'
+    , documentContent :: Blocks ann -- ^ 'ProAbstract.content'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Document ann) = ann
+
+type instance Content (Document ann) = Blocks ann
+
+type instance Contents (Document ann) = Block ann
+
+instance HasContent (Document ann) (Document ann') where
+    content = lens documentContent (\d c -> d { documentContent = c })
+
+instance HasContents (Document ann) (Document ann') where
+    contents = content % contents
+
+instance HasMetadata (Document ann) where
+    type MetadataOpticKind (Document ann) = A_Lens
+    metadata = lens documentMetadata (\d m -> d { documentMetadata = m })
+
+instance HasManyPlainInlines (Document ann) where
+    allPlainInlines = content % allPlainInlines
+
+instance HasManyPlainBlocks (Document ann) where
+    allPlainBlocks = content % allPlainBlocks
+
+instance HasManyAnnotations (Document ann) (Document ann') where
+    allAnnotations = content % allAnnotations
+
+instance HasManyMetadata (Document ann) where
+    allMetadata = metadata `adjoin` (content % allMetadata)
+
+instance HasManyParagraphs (Document ann) where
+    allParagraphs = content % allParagraphs
+
+instance HasManyTags (Document ann) where
+    allTags = content % allTags
+    allInlineTags = content % allInlineTags
+
+instance HasWitherableTags (Document ann) where
+    witherTags f = traverseOf content (witherTags f)
+
+instance HasManyBlockTags (Document ann) where
+    allBlockTags = content % allBlockTags
+
+instance HasWitherableBlockTags (Document ann) where
+    witherBlockTags f = traverseOf content (witherBlockTags f)
+
+instance HasWitherableInlineTags (Document ann) where
+    witherInlineTags f = traverseOf content (witherInlineTags f)
diff --git a/library/pro-abstract/ProAbstract/Structure/Fork.hs b/library/pro-abstract/ProAbstract/Structure/Fork.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Fork.hs
@@ -0,0 +1,5 @@
+module ProAbstract.Structure.Fork
+    ( Fork
+    ) where
+
+type family Fork a
diff --git a/library/pro-abstract/ProAbstract/Structure/Fragment.hs b/library/pro-abstract/ProAbstract/Structure/Fragment.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Fragment.hs
@@ -0,0 +1,23 @@
+module ProAbstract.Structure.Fragment
+    ( Fragment (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+
+data Fragment ann = Fragment
+  { fragmentText :: Text -- ^ 'ProAbstract.content'
+  , fragmentAnnotation :: ann -- ^ 'ProAbstract.annotation'
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Fragment ann) = ann
+
+instance HasAnnotation (Fragment ann) (Fragment ann') where
+    annotation = lens fragmentAnnotation \x a -> x { fragmentAnnotation = a }
+
+type instance Content (Fragment ann) = Text
+
+instance HasContent (Fragment ann) (Fragment ann) where
+    content = lens fragmentText (\f t -> f { fragmentText = t })
diff --git a/library/pro-abstract/ProAbstract/Structure/HasManyParagraphs.hs b/library/pro-abstract/ProAbstract/Structure/HasManyParagraphs.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/HasManyParagraphs.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Structure.HasManyParagraphs
+    ( HasManyParagraphs (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Structure.Paragraph
+
+class HasManyParagraphs x where
+    allParagraphs :: Traversal' x (Paragraph (Annotation x))
diff --git a/library/pro-abstract/ProAbstract/Structure/HasManyPlainBlocks.hs b/library/pro-abstract/ProAbstract/Structure/HasManyPlainBlocks.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/HasManyPlainBlocks.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Structure.HasManyPlainBlocks
+    ( HasManyPlainBlocks (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Structure.PlainBlock
+
+class HasManyPlainBlocks x where
+    allPlainBlocks :: Traversal' x (Tagged (PlainBlock (Annotation x)))
diff --git a/library/pro-abstract/ProAbstract/Structure/HasManyPlainInlines.hs b/library/pro-abstract/ProAbstract/Structure/HasManyPlainInlines.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/HasManyPlainInlines.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Structure.HasManyPlainInlines
+    ( HasManyPlainInlines (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Structure.Fragment
+
+class HasManyPlainInlines x where
+    allPlainInlines :: Traversal' x (Fragment (Annotation x))
diff --git a/library/pro-abstract/ProAbstract/Structure/Inline.hs b/library/pro-abstract/ProAbstract/Structure/Inline.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Inline.hs
@@ -0,0 +1,217 @@
+module ProAbstract.Structure.Inline
+    ( Inline (..), Line (..), Lines (..), Tagged (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Metadata
+import ProAbstract.Structure.CanBePlain
+import ProAbstract.Structure.CanFork
+import ProAbstract.Structure.Fork
+import ProAbstract.Structure.Fragment
+import ProAbstract.Structure.HasManyPlainInlines
+import ProAbstract.Structure.Plain
+import ProAbstract.Tag
+
+
+-- ⭐ Inline
+
+data Inline ann =
+    InlineFork (Tagged (Lines ann)) -- ^ 'ProAbstract.fork'
+  | InlinePlain (Fragment ann) -- ^ 'ProAbstract.plain'
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Inline ann) = ann
+
+type instance Fork (Inline ann) = Tagged (Lines ann)
+
+type instance Plain (Inline ann) = Fragment ann
+
+instance HasMetadata (Inline ann) where
+    type MetadataOpticKind (Inline ann) = An_AffineTraversal
+    metadata = tag % metadata
+
+instance HasManyAnnotations (Inline ann) (Inline ann') where
+    allAnnotations = traversalVL \f -> \case
+        InlinePlain x -> InlinePlain <$> traverseOf annotation f x
+        InlineFork  x -> InlineFork  <$> traverseOf allAnnotations f x
+
+instance HasAnnotation (Inline ann) (Inline ann) where
+    annotation = lens f g
+      where
+        f = \case
+            InlineFork  x -> view annotation x
+            InlinePlain x -> view annotation x
+        g = \case
+            InlineFork  x -> \a -> InlineFork  (set annotation a x)
+            InlinePlain x -> \a -> InlinePlain (set annotation a x)
+
+instance HasManyPlainInlines (Inline ann) where
+    allPlainInlines = plain `adjoin` (fork % allPlainInlines)
+
+instance HasTag (Inline ann) where
+    type TagOpticKind (Inline ann) = An_AffineTraversal
+    tag = atraversal f g
+      where
+        f = \case
+            InlineFork x -> Right (view tag x)
+            x -> Left x
+        g = \case
+            InlineFork x -> \a -> InlineFork (set tag a x)
+            x -> \_ -> x
+
+instance HasManyTags (Inline ann) where
+    allTags = fork % allTags
+    allInlineTags = allTags
+
+instance HasManyMetadata (Inline ann) where
+    allMetadata = fork % allMetadata
+
+instance CanFork (Inline ann) where
+    fork = prism' InlineFork \case{ InlineFork t -> Just t; _ -> Nothing }
+
+instance CanBePlain (Inline ann) where
+    plain = prism' InlinePlain \case{ InlinePlain t -> Just t; _ -> Nothing }
+
+
+-- ⭐ Line
+
+data Line ann = Line
+    { lineInlines :: Seq (Inline ann) -- ^ 'ProAbstract.contentsSeq'
+    , lineAnnotation :: ann -- ^ 'ProAbstract.annotation'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+instance IsList (Line ()) where
+    type Item (Line ()) = Inline ()
+    toList (Line xs ()) = toList xs
+    fromList xs = Line (fromList xs) ()
+
+type instance Contents (Line ann) = Inline ann
+
+instance HasContents (Line ann) (Line ann) where
+    contents = lens lineInlines \x a -> x{ lineInlines = a }
+
+type instance Annotation (Line ann) = ann
+
+instance HasAnnotation (Line ann) (Line ann) where
+    annotation = lens lineAnnotation \x a -> x{ lineAnnotation = a }
+
+instance HasManyPlainInlines (Line ann) where
+    allPlainInlines = contents % traversed % allPlainInlines
+
+instance HasManyAnnotations (Line ann) (Line ann') where
+    allAnnotations = traversalVL \f (Line xs a) ->
+        Line <$> traverseOf (traversed % allAnnotations) f xs <*> f a
+
+instance HasManyMetadata (Line ann) where
+    allMetadata = contents % traversed % allMetadata
+
+instance HasManyTags (Line ann) where
+    allTags = contents % traversed % allTags
+    allInlineTags = allTags
+
+instance HasWitherableInlineTags (Line ann) where
+    witherInlineTags = witherTags
+
+instance HasWitherableTags (Line ann) where
+    witherTags f = traverseOf contents $
+        seqWither \case
+            InlinePlain x -> pure . Just . InlinePlain $ x
+            InlineFork x -> f (view tag x) >>= \case
+                Nothing -> pure Nothing
+                Just t -> Just . InlineFork . set tag t <$> traverseOf content (witherTags f) x
+
+
+-- ⭐ Lines
+
+newtype Lines ann =
+  Lines
+    (Seq (Line ann)) -- ^ 'ProAbstract.contentsSeq'
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+  deriving newtype (Semigroup, Monoid)
+
+instance IsList (Lines ann) where
+    type Item (Lines ann) = Line ann
+    toList (Lines xs) = toList xs
+    fromList xs = Lines (fromList xs)
+
+type instance Contents (Lines ann) = Line ann
+
+instance HasContents (Lines ann) (Lines ann') where
+    contents = castOptic coerced
+
+type instance Annotation (Lines ann) = ann
+
+instance HasManyPlainInlines (Lines ann) where
+    allPlainInlines = contents % traversed % allPlainInlines
+
+instance HasManyAnnotations (Lines ann) (Lines ann') where
+    allAnnotations = contents % traversed % allAnnotations
+
+instance HasManyMetadata (Lines ann) where
+    allMetadata = contents % traversed % allMetadata
+
+instance HasManyTags (Lines ann) where
+    allTags = contents % traversed % allTags
+    allInlineTags = allTags
+
+instance HasWitherableInlineTags (Lines ann) where
+    witherInlineTags = witherTags
+
+instance HasWitherableTags (Lines ann) where
+    witherTags f = traverseOf (contents % traversed) (witherTags f)
+
+
+-- ⭐ Tagged Lines
+
+data instance Tagged (Lines ann) =
+  TaggedLines
+    { linesTag :: Tag ann -- ^ 'ProAbstract.tag'
+    , taggedLines :: Lines ann -- ^ 'ProAbstract.content'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Tagged (Lines ann)) = ann
+
+instance HasTag (Tagged (Lines ann)) where
+    type TagOpticKind (Tagged (Lines ann)) = A_Lens
+    tag = lens linesTag \x a -> x{ linesTag = a }
+
+type instance Content (Tagged (Lines ann)) = Lines ann
+
+type instance Contents (Tagged (Lines ann)) = Line ann
+
+instance HasManyAnnotations (Tagged (Lines ann)) (Tagged (Lines ann')) where
+    allAnnotations = traversalVL \f (TaggedLines t b) -> TaggedLines
+        <$> traverseOf annotation f t <*> traverseOf allAnnotations f b
+
+instance HasAnnotation (Tagged (Lines ann)) (Tagged (Lines ann)) where
+     annotation = tag % annotation
+
+instance HasContent (Tagged (Lines ann)) (Tagged (Lines ann)) where
+    content = lens taggedLines \x c -> x{ taggedLines = c }
+
+instance HasContents (Tagged (Lines ann)) (Tagged (Lines ann)) where
+    contents = content % contents
+
+instance HasMetadata (Tagged (Lines ann)) where
+    type MetadataOpticKind (Tagged (Lines ann)) = A_Lens
+    metadata = tag % metadata
+
+instance HasManyPlainInlines (Tagged (Lines ann)) where
+    allPlainInlines = content % allPlainInlines
+
+instance HasManyTags (Tagged (Lines ann)) where
+    allTags = tag `adjoin` (content % allTags)
+    allInlineTags = allTags
+
+instance HasWitherableInlineTags (Tagged (Lines ann)) where
+    witherInlineTags f = traverseOf content (witherInlineTags f)
+
+instance HasManyMetadata (Tagged (Lines ann)) where
+   allMetadata = allTags % metadata
diff --git a/library/pro-abstract/ProAbstract/Structure/Paragraph.hs b/library/pro-abstract/ProAbstract/Structure/Paragraph.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Paragraph.hs
@@ -0,0 +1,50 @@
+module ProAbstract.Structure.Paragraph
+    ( Paragraph (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Metadata
+import ProAbstract.Structure.HasManyPlainInlines
+import ProAbstract.Structure.Inline
+import ProAbstract.Tag
+
+-- | A collection of 'Lines'. A 'Paragraph' represents the border between block and inline contexts. All ancestors of a paragraph are block items or a document, and all children are inline items.
+data Paragraph ann = Paragraph
+    { paragraphContent :: Lines ann -- ^ 'ProAbstract.content'
+    , paragraphAnnotation :: ann -- ^ 'ProAbstract.annotation'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Content (Paragraph ann) = Lines ann
+
+type instance Contents (Paragraph ann) = Line ann
+
+type instance Annotation (Paragraph ann) = ann
+
+instance HasManyAnnotations (Paragraph ann) (Paragraph ann') where
+    allAnnotations = traversalVL \f (Paragraph c a) ->
+        Paragraph <$> traverseOf allAnnotations f c <*> f a
+
+instance HasAnnotation (Paragraph ann) (Paragraph ann) where
+    annotation = lens paragraphAnnotation \x a -> x{ paragraphAnnotation = a }
+
+instance HasContent (Paragraph ann) (Paragraph ann) where
+    content = lens paragraphContent (\t c -> t { paragraphContent = c })
+
+instance HasContents (Paragraph ann) (Paragraph ann) where
+    contents = content % contents
+
+instance HasManyPlainInlines (Paragraph ann) where
+    allPlainInlines = content % allPlainInlines
+
+instance HasManyMetadata (Paragraph ann) where
+    allMetadata = content % allMetadata
+
+instance HasManyTags (Paragraph ann) where
+    allTags = content % allTags
+    allInlineTags = content % allInlineTags
+
+instance HasWitherableInlineTags (Paragraph ann) where
+    witherInlineTags f = traverseOf content (witherInlineTags f)
diff --git a/library/pro-abstract/ProAbstract/Structure/Plain.hs b/library/pro-abstract/ProAbstract/Structure/Plain.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/Plain.hs
@@ -0,0 +1,5 @@
+module ProAbstract.Structure.Plain
+    ( Plain
+    ) where
+
+type family Plain a
diff --git a/library/pro-abstract/ProAbstract/Structure/PlainBlock.hs b/library/pro-abstract/ProAbstract/Structure/PlainBlock.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Structure/PlainBlock.hs
@@ -0,0 +1,71 @@
+module ProAbstract.Structure.PlainBlock
+    ( PlainBlock (..), Tagged (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Content
+import ProAbstract.Metadata
+import ProAbstract.Structure.Fragment
+import ProAbstract.Tag
+
+
+-- ⭐ PlainBlock
+
+data PlainBlock ann = PlainBlock
+  { plainBlockLines      :: Seq (Fragment ann) -- ^ 'ProAbstract.contentsSeq'
+  , plainBlockAnnotation :: ann                -- ^ 'ProAbstract.annotation'
+  }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Contents (PlainBlock ann) = Fragment ann
+
+instance HasContents (PlainBlock ann) (PlainBlock ann) where
+    contents = lens plainBlockLines \x a -> x{ plainBlockLines = a }
+
+type instance Annotation (PlainBlock ann) = ann
+
+instance HasAnnotation (PlainBlock ann) (PlainBlock ann) where
+    annotation = lens plainBlockAnnotation \x a -> x { plainBlockAnnotation = a }
+
+instance HasManyAnnotations (PlainBlock ann) (PlainBlock ann') where
+    allAnnotations = traversalVL \f (PlainBlock t a) ->
+        PlainBlock <$> traverseOf (traversed % annotation) f t <*> f a
+
+
+-- ⭐ Tagged PlainBlock
+
+data instance Tagged (PlainBlock ann) =
+  TaggedPlainBlock
+    { plaintextTag :: Tag ann -- ^ 'ProAbstract.Tag'
+    , taggedPlaintext :: PlainBlock ann -- ^ 'ProAbstract.content'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Tagged (PlainBlock ann)) = ann
+
+instance HasTag (Tagged (PlainBlock ann)) where
+    type TagOpticKind (Tagged (PlainBlock ann)) = A_Lens
+    tag = lens plaintextTag \x a -> x{ plaintextTag = a }
+
+type instance Content (Tagged (PlainBlock ann)) = PlainBlock ann
+
+type instance Contents (Tagged (PlainBlock ann)) = Fragment ann
+
+instance HasContents (Tagged (PlainBlock ann)) (Tagged (PlainBlock ann)) where
+    contents = content % contents
+
+instance HasManyAnnotations (Tagged (PlainBlock ann)) (Tagged (PlainBlock ann')) where
+    allAnnotations = traversalVL \f (TaggedPlainBlock t b) -> TaggedPlainBlock
+        <$> traverseOf annotation f t <*> traverseOf allAnnotations f b
+
+instance HasAnnotation (Tagged (PlainBlock ann)) (Tagged (PlainBlock ann)) where
+     annotation = tag % annotation
+
+instance HasContent (Tagged (PlainBlock ann)) (Tagged (PlainBlock ann)) where
+    content = lens taggedPlaintext \x c -> x{ taggedPlaintext = c }
+
+instance HasMetadata (Tagged (PlainBlock ann)) where
+    type MetadataOpticKind (Tagged (PlainBlock ann)) = A_Lens
+    metadata = tag % metadata
diff --git a/library/pro-abstract/ProAbstract/Tag.hs b/library/pro-abstract/ProAbstract/Tag.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag.hs
@@ -0,0 +1,14 @@
+module ProAbstract.Tag
+    ( Tag (..), name
+    , Tagged
+    , HasTag (..)
+    , HasManyTags (..), HasWitherableTags (..)
+    , HasManyBlockTags (..), HasWitherableBlockTags (..)
+    , HasWitherableInlineTags (..)
+    ) where
+
+import ProAbstract.Tag.HasManyTags
+import ProAbstract.Tag.HasTag
+import ProAbstract.Tag.HasTagOptics
+import ProAbstract.Tag.TagType
+import ProAbstract.Tag.TaggedFamily
diff --git a/library/pro-abstract/ProAbstract/Tag/HasManyTags.hs b/library/pro-abstract/ProAbstract/Tag/HasManyTags.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag/HasManyTags.hs
@@ -0,0 +1,33 @@
+module ProAbstract.Tag.HasManyTags
+    ( HasManyTags (..), HasWitherableTags (..)
+    , HasManyBlockTags (..), HasWitherableBlockTags (..)
+    , HasWitherableInlineTags (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Tag.TagType
+
+class HasManyTags x where
+    allTags :: Traversal' x (Tag (Annotation x))
+    allInlineTags :: Traversal' x (Tag (Annotation x))
+
+class HasWitherableTags x where
+    witherTags :: Monad f => (Tag (Annotation x) -> f (Maybe (Tag (Annotation x)))) -> x -> f x
+
+    mapMaybeTags :: (Tag (Annotation x) -> Maybe (Tag (Annotation x))) -> x -> x
+    mapMaybeTags f = runIdentity . witherTags (Identity . f)
+
+class HasManyTags x => HasManyBlockTags x where
+    allBlockTags :: Traversal' x (Tag (Annotation x))
+
+class HasManyBlockTags x => HasWitherableBlockTags x where
+    witherBlockTags :: Monad f => (Tag (Annotation x) -> f (Maybe (Tag (Annotation x)))) -> x -> f x
+
+    mapMaybeBlockTags :: (Tag (Annotation x) -> Maybe (Tag (Annotation x))) -> x -> x
+    mapMaybeBlockTags f = runIdentity . witherBlockTags (Identity . f)
+
+class HasManyTags x => HasWitherableInlineTags x where
+    witherInlineTags :: Monad f => (Tag (Annotation x) -> f (Maybe (Tag (Annotation x)))) -> x -> f x
+
+    mapMaybeInlineTags :: (Tag (Annotation x) -> Maybe (Tag (Annotation x))) -> x -> x
+    mapMaybeInlineTags f = runIdentity . witherInlineTags (Identity . f)
diff --git a/library/pro-abstract/ProAbstract/Tag/HasTag.hs b/library/pro-abstract/ProAbstract/Tag/HasTag.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag/HasTag.hs
@@ -0,0 +1,14 @@
+module ProAbstract.Tag.HasTag
+    ( HasTag (..)
+    ) where
+
+import ProAbstract.Annotation
+import ProAbstract.Tag.TagType
+
+class HasTag x where
+    type TagOpticKind x :: OpticKind
+    tag :: Optic' (TagOpticKind x) NoIx x (Tag (Annotation x))
+
+instance HasTag (Tag ann) where
+    type TagOpticKind (Tag ann) = A_Lens
+    tag = castOptic simple
diff --git a/library/pro-abstract/ProAbstract/Tag/HasTagOptics.hs b/library/pro-abstract/ProAbstract/Tag/HasTagOptics.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag/HasTagOptics.hs
@@ -0,0 +1,10 @@
+module ProAbstract.Tag.HasTagOptics
+    ( name
+    ) where
+
+import ProAbstract.Tag.HasTag
+
+import qualified ProAbstract.Tag.TagOptics as Tag
+
+name :: (HasTag x, JoinKinds (TagOpticKind x) A_Lens k) => Optic' k NoIx x Text
+name = tag % Tag.name
diff --git a/library/pro-abstract/ProAbstract/Tag/TagOptics.hs b/library/pro-abstract/ProAbstract/Tag/TagOptics.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag/TagOptics.hs
@@ -0,0 +1,6 @@
+module ProAbstract.Tag.TagOptics where
+
+import ProAbstract.Tag.TagType
+
+name :: Lens' (Tag ann) Text
+name = lens tagName (\t n -> t { tagName = n })
diff --git a/library/pro-abstract/ProAbstract/Tag/TagType.hs b/library/pro-abstract/ProAbstract/Tag/TagType.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag/TagType.hs
@@ -0,0 +1,24 @@
+module ProAbstract.Tag.TagType
+    ( Tag (..)
+    )
+    where
+
+import ProAbstract.Annotation
+import ProAbstract.Metadata
+
+data Tag ann = Tag
+    { tagName       :: Text     -- ^ 'ProAbstract.name'
+    , tagMetadata   :: Metadata -- ^ 'ProAbstract.metadata'
+    , tagAnnotation :: ann      -- ^ 'ProAbstract.annotation'
+    }
+  deriving stock (Eq, Show, Generic)
+  deriving anyclass (Hashable, NFData)
+
+type instance Annotation (Tag ann) = ann
+
+instance HasAnnotation (Tag ann) (Tag ann') where
+    annotation = lens tagAnnotation \x a -> x{ tagAnnotation = a }
+
+instance HasMetadata (Tag ann) where
+    type MetadataOpticKind (Tag ann) = A_Lens
+    metadata = lens tagMetadata (\d m -> d { tagMetadata = m })
diff --git a/library/pro-abstract/ProAbstract/Tag/TaggedFamily.hs b/library/pro-abstract/ProAbstract/Tag/TaggedFamily.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tag/TaggedFamily.hs
@@ -0,0 +1,5 @@
+module ProAbstract.Tag.TaggedFamily
+    ( Tagged
+    ) where
+
+data family Tagged con
diff --git a/library/pro-abstract/ProAbstract/Tagless.hs b/library/pro-abstract/ProAbstract/Tagless.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tagless.hs
@@ -0,0 +1,8 @@
+module ProAbstract.Tagless
+    ( CanBeTagless (..), CanHaveTaglessContent (..)
+    , KindOfText (..)
+    ) where
+
+import ProAbstract.Tagless.CanBeTagless
+import ProAbstract.Tagless.CanHaveTaglessContent
+import ProAbstract.Tagless.KindOfText
diff --git a/library/pro-abstract/ProAbstract/Tagless/CanBeTagless.hs b/library/pro-abstract/ProAbstract/Tagless/CanBeTagless.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tagless/CanBeTagless.hs
@@ -0,0 +1,56 @@
+module ProAbstract.Tagless.CanBeTagless
+    ( CanBeTagless (..)
+    ) where
+
+import ProAbstract.Content
+import ProAbstract.Tagless.KindOfText
+import ProAbstract.Structure
+
+class CanBeTagless a where
+    tagless :: KindOfText txt -> AffineFold a txt
+
+instance CanBeTagless (Fragment ann) where
+    tagless = liftLine $ castOptic content
+
+instance CanBeTagless (Line ann) where
+    tagless = liftLine $ contents % all (tagless TextLine) % to textConcat
+
+instance CanBeTagless (Lines ann) where
+    tagless = linesAre $ tagless TextLine
+
+instance CanBeTagless (PlainBlock ann) where
+    tagless = linesAre $ tagless TextLine
+
+instance CanBeTagless (Paragraph ann) where
+    tagless = linesAre $ tagless TextLine
+
+-- | Only a 'plain' inline is considered to be plain text, because 'fork' inlines have a tags.
+instance CanBeTagless (Inline ann) where
+    tagless s = plain % tagless s
+
+-- | Only a 'paragraph' inline is considered to be plain text, because 'fork' and 'plain' blocks have tags.
+instance CanBeTagless (Block ann) where
+    tagless s = paragraph % tagless s
+
+instance CanBeTagless (Blocks ann) where
+    tagless = \case
+        TextParagraphs -> contents % all (tagless TextParagraphs) % to seqConcat
+        s -> contents
+            % seqAtMostOne -- For lines and stanza return types, there is not allowed to be more than one block
+            % all @Maybe (tagless s) -- If there is a block, it has to be convertable to the desired text kind
+            % to (fromMaybe (noText s)) -- If there are no blocks, return an empty line or stanza
+
+noText :: KindOfText txt -> txt
+noText = \case{ TextLine -> textEmpty; TextStanza -> Empty; TextParagraphs -> Empty }
+
+all :: Traversable t => AffineFold a b -> AffineFold (t a) (t b)
+all = afolding . traverse . preview
+
+liftLine :: AffineFold a Text -> KindOfText txt -> AffineFold a txt
+liftLine o = liftStanza o (o % re seqSingleton)
+
+liftStanza :: AffineFold a Text -> AffineFold a (Seq Text) -> KindOfText txt -> AffineFold a txt
+liftStanza o1 o2 = \case{ TextLine -> o1; TextStanza -> o2; TextParagraphs -> o2 % re seqSingleton }
+
+linesAre :: HasContents' a => AffineFold (Contents a) Text -> KindOfText txt -> AffineFold a txt
+linesAre o = liftStanza (contents % seqAtMostOne % all o % to (fromMaybe textEmpty)) (contents % all o)
diff --git a/library/pro-abstract/ProAbstract/Tagless/CanHaveTaglessContent.hs b/library/pro-abstract/ProAbstract/Tagless/CanHaveTaglessContent.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tagless/CanHaveTaglessContent.hs
@@ -0,0 +1,49 @@
+module ProAbstract.Tagless.CanHaveTaglessContent
+    ( CanHaveTaglessContent (..)
+    ) where
+
+import ProAbstract.Content
+import ProAbstract.Structure
+import ProAbstract.Tagless.CanBeTagless
+import ProAbstract.Tagless.KindOfText
+
+class CanHaveTaglessContent a where
+    taglessContent :: KindOfText t -> AffineFold a t
+
+instance CanHaveTaglessContent (Block ann) where
+    taglessContent s =
+        (plain % taglessContent @(Tagged (PlainBlock ann)) s) `afailing`
+        (fork % taglessContent @(Tagged (Blocks ann)) s) `afailing`
+        (paragraph % tagless @(Paragraph ann) s)
+
+instance CanHaveTaglessContent (BlockTag ann) where
+    taglessContent s =
+        (plain % taglessContent @(Tagged (PlainBlock ann)) s) `afailing`
+        (fork % taglessContent @(Tagged (Blocks ann)) s)
+
+instance CanHaveTaglessContent (Inline ann) where
+    taglessContent s = (plain % tagless s) `afailing` (fork % taglessContent s)
+
+instance CanHaveTaglessContent (Lines ann) where
+    taglessContent = tagless
+
+instance CanHaveTaglessContent (Blocks ann) where
+    taglessContent = tagless
+
+instance CanHaveTaglessContent (Line ann) where
+    taglessContent = tagless
+
+instance CanHaveTaglessContent (Fragment ann) where
+    taglessContent = tagless
+
+instance CanHaveTaglessContent (Tagged (Blocks ann)) where
+    taglessContent s = content % tagless s
+
+instance CanHaveTaglessContent (Tagged (PlainBlock ann)) where
+    taglessContent s = content % tagless s
+
+instance CanHaveTaglessContent (Tagged (Lines ann)) where
+    taglessContent s = content % tagless s
+
+instance CanHaveTaglessContent (Document ann) where
+    taglessContent s = content % tagless s
diff --git a/library/pro-abstract/ProAbstract/Tagless/KindOfText.hs b/library/pro-abstract/ProAbstract/Tagless/KindOfText.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Tagless/KindOfText.hs
@@ -0,0 +1,9 @@
+module ProAbstract.Tagless.KindOfText
+    ( KindOfText (..)
+    ) where
+
+data KindOfText txt where
+    TextLine       :: KindOfText           Text
+    TextStanza     :: KindOfText      (Seq Text)
+    TextParagraphs :: KindOfText (Seq (Seq Text))
+
diff --git a/library/pro-abstract/ProAbstract/Types.hs b/library/pro-abstract/ProAbstract/Types.hs
new file mode 100644
--- /dev/null
+++ b/library/pro-abstract/ProAbstract/Types.hs
@@ -0,0 +1,27 @@
+module ProAbstract.Types
+    ( Annotation
+    , Block
+    , Blocks
+    , BlockTag
+    , Content
+    , Contents
+    , Document
+    , Fork
+    , Fragment
+    , Inline
+    , Line
+    , Lines
+    , Map
+    , Metadata
+    , Paragraph
+    , Plain
+    , PlainBlock
+    , Seq
+    , Set
+    , Tag
+    , Tagged
+    , Text
+    ) where
+
+import ProAbstract.Datatypes
+import ProAbstract.Families
diff --git a/pro-abstract.cabal b/pro-abstract.cabal
new file mode 100644
--- /dev/null
+++ b/pro-abstract.cabal
@@ -0,0 +1,144 @@
+cabal-version: 3.0
+
+name: pro-abstract
+version: 0.1.0.0
+
+category: Language
+synopsis: Abstract syntax for writing documents
+description:
+    This package defines the abstract syntax only
+    for a language for writing documents.
+
+    Forked from part of the
+    <https://hackage.haskell.org/package/prosidy prosidy>
+    package.
+
+license: MPL-2.0
+license-file: LICENSE
+maintainer: hello@typeclasses.com
+author: Chris Martin, James Alexander Feldman-Crough
+copyright:
+    © 2020 James Alexander Feldman-Crough,
+    © 2022 Mission Valley Software LLC
+
+source-repository head
+    type: git
+    location: git://github.com/typeclasses/pro-abstract.git
+
+common base
+    default-language: Haskell2010
+    ghc-options: -Wall
+    default-extensions:
+        BlockArguments
+        ConstraintKinds
+        DataKinds
+        DeriveAnyClass
+        DeriveGeneric
+        DerivingStrategies
+        FlexibleContexts
+        FlexibleInstances
+        GADTs
+        GeneralizedNewtypeDeriving
+        LambdaCase
+        MultiParamTypeClasses
+        PatternSynonyms
+        ScopedTypeVariables
+        TypeApplications
+        TypeFamilies
+    build-depends:
+        base ^>= 4.13 || ^>= 4.14 || ^>= 4.15 || ^>= 4.16
+      , containers ^>= 0.6
+      , deepseq ^>= 1.4.4
+      , hashable ^>= 1.3.4.0 || ^>= 1.4
+      , optics-core ^>= 0.4
+      , text ^>= 1.2.4
+
+library
+    import: base
+    mixins: base hiding (Prelude)
+    build-depends: pro-abstract-prelude
+    hs-source-dirs: library/pro-abstract
+
+    exposed-modules:
+        ProAbstract
+      , ProAbstract.Annotation
+      , ProAbstract.Classes
+      , ProAbstract.Content
+      , ProAbstract.Datatypes
+      , ProAbstract.Families
+      , ProAbstract.Metadata
+      , ProAbstract.Optics
+      , ProAbstract.Structure
+      , ProAbstract.Tag
+      , ProAbstract.Tagless
+      , ProAbstract.Types
+
+    other-modules:
+      , ProAbstract.Annotation.AnnotationFamily
+      , ProAbstract.Annotation.HasAnnotation
+      , ProAbstract.Annotation.HasManyAnnotations
+      , ProAbstract.Content.ContentFamily
+      , ProAbstract.Content.ContentsFamily
+      , ProAbstract.Content.HasContent
+      , ProAbstract.Content.HasContents
+      , ProAbstract.Metadata.HasManyMetadata
+      , ProAbstract.Metadata.HasMetadata
+      , ProAbstract.Metadata.HasMetadataOptics
+      , ProAbstract.Metadata.MetadataOptics
+      , ProAbstract.Metadata.MetadataType
+      , ProAbstract.Structure.Block
+      , ProAbstract.Structure.BlockTag
+      , ProAbstract.Structure.CanBePlain
+      , ProAbstract.Structure.CanFork
+      , ProAbstract.Structure.Document
+      , ProAbstract.Structure.Fork
+      , ProAbstract.Structure.Fragment
+      , ProAbstract.Structure.HasManyParagraphs
+      , ProAbstract.Structure.HasManyPlainBlocks
+      , ProAbstract.Structure.HasManyPlainInlines
+      , ProAbstract.Structure.Inline
+      , ProAbstract.Structure.Paragraph
+      , ProAbstract.Structure.Plain
+      , ProAbstract.Structure.PlainBlock
+      , ProAbstract.Tag.HasManyTags
+      , ProAbstract.Tag.HasTag
+      , ProAbstract.Tag.HasTagOptics
+      , ProAbstract.Tag.TaggedFamily
+      , ProAbstract.Tag.TagOptics
+      , ProAbstract.Tag.TagType
+      , ProAbstract.Tagless.CanBeTagless
+      , ProAbstract.Tagless.CanHaveTaglessContent
+      , ProAbstract.Tagless.KindOfText
+
+library pro-abstract-prelude
+    import: base
+    hs-source-dirs: library/pro-abstract-prelude
+
+    default-extensions:
+        NoImplicitPrelude
+        NoMonomorphismRestriction
+
+    exposed-modules:
+        Containers
+      , Prelude
+
+    mixins:
+        base (Prelude as BasePrelude)
+      , base hiding (Prelude)
+
+test-suite test-pro-abstract
+    import: base
+    type: exitcode-stdio-1.0
+    hs-source-dirs: test-suite/test-pro-abstract
+    main-is: Main.hs
+
+    default-extensions:
+        OverloadedLists
+        OverloadedStrings
+        QuasiQuotes
+        TemplateHaskell
+
+    build-depends:
+        pro-abstract
+      , neat-interpolation ^>= 0.5
+      , hedgehog ^>= 1.0 || ^>= 1.1
diff --git a/test-suite/test-pro-abstract/Main.hs b/test-suite/test-pro-abstract/Main.hs
new file mode 100644
--- /dev/null
+++ b/test-suite/test-pro-abstract/Main.hs
@@ -0,0 +1,96 @@
+module Main (main) where
+
+import ProAbstract
+
+import Optics.Core
+import Prelude hiding (break)
+
+import Control.Monad (when)
+import Data.Text (Text)
+import Hedgehog (Property, checkParallel, discover, property, withTests, (===))
+import System.Exit (exitFailure)
+
+main :: IO ()
+main = checkParallel $$discover >>= \ok -> when (not ok) exitFailure
+
+frag :: Text -> Fragment ()
+frag x = Fragment{ fragmentText = x, fragmentAnnotation = () }
+
+inlinePlain :: Text -> Inline ()
+inlinePlain = InlinePlain . frag
+
+inlineFork :: Lines () -> Inline ()
+inlineFork x = InlineFork $ TaggedLines{ linesTag = Tag{ tagName = "x", tagMetadata = mempty, tagAnnotation = () }, taggedLines = x }
+
+para :: Lines () -> Block ()
+para x = BlockParagraph Paragraph{ paragraphAnnotation = (), paragraphContent = x }
+
+btag :: Blocks () -> Block ()
+btag x = BlockFork TaggedBlocks{ blocksTag = Tag{ tagName = "x", tagMetadata = mempty, tagAnnotation = () }, taggedBlocks = x }
+
+tbtag :: Blocks () -> BlockTag ()
+tbtag x = BlockTagFork TaggedBlocks{ blocksTag = Tag{ tagName = "x", tagMetadata = mempty, tagAnnotation = () }, taggedBlocks = x }
+
+prop_ex1 :: Property
+prop_ex1 = withTests 1 $ property $ do
+    x <- pure $ inlinePlain "abc"
+    preview (tagless TextStanza) x === Just ["abc"]
+
+prop_ex2 :: Property
+prop_ex2 = withTests 1 $ property $ do
+    x <- pure $ InlineFork $
+        TaggedLines
+            { linesTag = Tag{ tagName = "x", tagMetadata = mempty, tagAnnotation = () }
+            , taggedLines = [[inlinePlain "abc", inlinePlain "def"]]
+            }
+    preview (fork % content % (tagless TextStanza)) x === Just ["abcdef"]
+
+prop_ex3 :: Property
+prop_ex3 = withTests 1 $ property $ do
+    x <- pure $ inlineFork [[inlinePlain "abc", inlinePlain "def"], [inlinePlain "ghi"]]
+    preview (taglessContent TextStanza) x === Just ["abcdef", "ghi"]
+
+prop_ex4 :: Property
+prop_ex4 = withTests 1 $ property $ do
+    x <- pure $ inlineFork [[inlinePlain "abc"]]
+    preview (taglessContent TextStanza) x === Just ["abc"]
+
+prop_ex5 :: Property
+prop_ex5 = withTests 1 $ property $ do
+    x <- pure $ inlineFork [[inlinePlain "abc", inlineFork []]]
+    preview (taglessContent TextStanza) x === Nothing
+
+prop_ex6 :: Property
+prop_ex6 = withTests 1 $ property $ do
+    x <- pure $ para [[inlinePlain "abc", inlineFork []]]
+    preview (tagless TextStanza) x === Nothing
+
+prop_ex7 :: Property
+prop_ex7 = withTests 1 $ property $ do
+    x <- pure $ para [[inlinePlain "abc", inlinePlain "def"]]
+    preview (tagless TextStanza) x === Just ["abcdef"]
+
+prop_ex8 :: Property
+prop_ex8 = withTests 1 $ property $ do
+    x <- pure $ btag [para [[inlinePlain "abc", inlinePlain "def"]]]
+    preview (taglessContent TextStanza) x === Just ["abcdef"]
+
+prop_ex9 :: Property
+prop_ex9 = withTests 1 $ property $ do
+    x <- pure $ ([para [[inlinePlain "abc", inlinePlain "def"]]] :: Blocks ())
+    preview (tagless TextStanza) x === Just ["abcdef"]
+
+prop_ex10 :: Property
+prop_ex10 = withTests 1 $ property $ do
+    x <- pure $ tbtag $ [para [[inlinePlain "abc"]]]
+    preview (taglessContent TextStanza) x === Just ["abc"]
+
+prop_ex11 :: Property
+prop_ex11 = withTests 1 $ property $ do
+    x <- pure $ tbtag $ [para [[inlinePlain "abc", inlinePlain "def"]]]
+    preview (fork % content % (tagless TextStanza)) x === Just ["abcdef"]
+
+prop_ex12 :: Property
+prop_ex12 = withTests 1 $ property $ do
+    x <- pure $ tbtag $ [para [[inlinePlain "abc", inlinePlain "def"], [inlinePlain "ghi"]]]
+    preview (fork % content % (tagless TextStanza)) x === Just ["abcdef", "ghi"]
