diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,25 @@
+# Changelog for `gongju-text`
+
+All notable changes to the `gongju-text` package will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to the Haskell Package Versioning Policy (PVP).
+
+## [0.1.0.0] — 2026-10-24
+
+### Added
+- **`Gongju.Text.NonEmpty`**: Non-empty text refinements and safe splitting utilities using `mono-traversable`'s `NonNull`:
+  - `toText`: Convert non-null text to strict text.
+  - `fromText`: Safe constructor returning `Maybe (NonNull Text)`.
+  - `wordsNE`: Split text into non-empty words as `Maybe (NonEmpty Text)`.
+  - `linesNE`: Split text into non-empty lines as `Maybe (NonEmpty Text)`.
+- **`Gongju.Text.Stanza`**: Stanza splitting and parsing over polymorphic containers:
+  - `Stanza`: Newtype wrapper around polymorphic text containers.
+  - `stanzas`: Split strict text into stanzas of non-blank lines as standard lists (`[Stanza []]`).
+  - `stanzasSeq`: Split strict text into stanzas collecting lines into `Data.Sequence.Seq`.
+- **`Gongju.Text.Layout`**: Lightweight indentation measurement and hierarchical block-nesting analysis:
+  - `IndentedLine`: Line representation with measured leading whitespace indentation depth.
+  - `Block`: Nested block structure with line content and recursive child sub-blocks.
+  - `parseIndentedLines`: Measure space indentation depths across line lists.
+  - `parseBlocks`: Build nested hierarchical block trees from indented lines.
+- Full test suite covering non-empty splits, stanza chunking, and hierarchical indentation parsing.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,189 @@
+EUROPEAN UNION PUBLIC LICENCE v. 1.2
+EUPL © the European Union 2007, 2016
+
+This European Union Public Licence (the ‘EUPL’) applies to the Work (as defined below) which is provided under the
+terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such
+use is covered by a right of the copyright holder of the Work).
+The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following
+notice immediately following the copyright notice for the Work:
+                          Licensed under the EUPL
+or has expressed by any other means his willingness to license under the EUPL.
+
+1.Definitions
+In this Licence, the following terms have the following meaning:
+— ‘The Licence’:this Licence.
+— ‘The Original Work’:the work or software distributed or communicated by the Licensor under this Licence, available
+as Source Code and also as Executable Code as the case may be.
+— ‘Derivative Works’:the works or software that could be created by the Licensee, based upon the Original Work or
+modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work
+required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in
+the country mentioned in Article 15.
+— ‘The Work’:the Original Work or its Derivative Works.
+— ‘The Source Code’:the human-readable form of the Work which is the most convenient for people to study and
+modify.
+— ‘The Executable Code’:any code which has generally been compiled and which is meant to be interpreted by
+a computer as a program.
+— ‘The Licensor’:the natural or legal person that distributes or communicates the Work under the Licence.
+— ‘Contributor(s)’:any natural or legal person who modifies the Work under the Licence, or otherwise contributes to
+the creation of a Derivative Work.
+— ‘The Licensee’ or ‘You’:any natural or legal person who makes any usage of the Work under the terms of the
+Licence.
+— ‘Distribution’ or ‘Communication’:any act of selling, giving, lending, renting, distributing, communicating,
+transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential
+functionalities at the disposal of any other natural or legal person.
+
+2.Scope of the rights granted by the Licence
+The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for
+the duration of copyright vested in the Original Work:
+— use the Work in any circumstance and for all usage,
+— reproduce the Work,
+— modify the Work, and make Derivative Works based upon the Work,
+— communicate to the public, including the right to make available or display the Work or copies thereof to the public
+and perform publicly, as the case may be, the Work,
+— distribute the Work or copies thereof,
+— lend and rent the Work or copies thereof,
+— sublicense rights in the Work or copies thereof.
+Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the
+applicable law permits so.
+In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed
+by law in order to make effective the licence of the economic rights here above listed.
+The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the
+extent necessary to make use of the rights granted on the Work under this Licence.
+
+3.Communication of the Source Code
+The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as
+Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with
+each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to
+the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to
+distribute or communicate the Work.
+
+4.Limitations on copyright
+Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the
+exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations
+thereto.
+
+5.Obligations of the Licensee
+The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those
+obligations are the following:
+
+Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to
+the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the
+Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work
+to carry prominent notices stating that the Work has been modified and the date of modification.
+
+Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this
+Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless
+the Original Work is expressly distributed only under this version of the Licence — for example by communicating
+‘EUPL v. 1.2 only’. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the
+Work or Derivative Work that alter or restrict the terms of the Licence.
+
+Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both
+the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done
+under the terms of this Compatible Licence. For the sake of this clause, ‘Compatible Licence’ refers to the licences listed
+in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with
+his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.
+
+Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide
+a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available
+for as long as the Licensee continues to distribute or communicate the Work.
+Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names
+of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and
+reproducing the content of the copyright notice.
+
+6.Chain of Authorship
+The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or
+licensed to him/her and that he/she has the power and authority to grant the Licence.
+Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or
+licensed to him/her and that he/she has the power and authority to grant the Licence.
+Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions
+to the Work, under the terms of this Licence.
+
+7.Disclaimer of Warranty
+The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work
+and may therefore contain defects or ‘bugs’ inherent to this type of development.
+For the above reason, the Work is provided under the Licence on an ‘as is’ basis and without warranties of any kind
+concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or
+errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this
+Licence.
+This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.
+
+8.Disclaimer of Liability
+Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be
+liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the
+Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss
+of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However,
+the Licensor will be liable under statutory product liability laws as far such laws apply to the Work.
+
+9.Additional agreements
+While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services
+consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole
+responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by
+the fact You have accepted any warranty or additional liability.
+
+10.Acceptance of the Licence
+The provisions of this Licence can be accepted by clicking on an icon ‘I agree’ placed under the bottom of a window
+displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of
+applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms
+and conditions.
+Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You
+by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution
+or Communication by You of the Work or copies thereof.
+
+11.Information to the public
+In case of any Distribution or Communication of the Work by means of electronic communication by You (for example,
+by offering to download the Work from a remote location) the distribution channel or media (for example, a website)
+must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence
+and the way it may be accessible, concluded, stored and reproduced by the Licensee.
+
+12.Termination of the Licence
+The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms
+of the Licence.
+Such a termination will not terminate the licences of any person who has received the Work from the Licensee under
+the Licence, provided such persons remain in full compliance with the Licence.
+
+13.Miscellaneous
+Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the
+Work.
+If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or
+enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid
+and enforceable.
+The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of
+the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence.
+New versions of the Licence will be published with a unique version number.
+All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take
+advantage of the linguistic version of their choice.
+
+14.Jurisdiction
+Without prejudice to specific agreement between parties,
+— any litigation resulting from the interpretation of this License, arising between the European Union institutions,
+bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice
+of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,
+— any litigation arising between other parties and resulting from the interpretation of this License, will be subject to
+the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.
+
+15.Applicable Law
+Without prejudice to specific agreement between parties,
+— this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat,
+resides or has his registered office,
+— this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside
+a European Union Member State.
+
+Appendix
+
+‘Compatible Licences’ according to Article 5 EUPL are:
+— GNU General Public License (GPL) v. 2, v. 3
+— GNU Affero General Public License (AGPL) v. 3
+— Open Software License (OSL) v. 2.1, v. 3.0
+— Eclipse Public License (EPL) v. 1.0
+— CeCILL v. 2.0, v. 2.1
+— Mozilla Public Licence (MPL) v. 2
+— GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
+— Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software
+— European Union Public Licence (EUPL) v. 1.1, v. 1.2
+— Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+).
+
+The European Commission may update this Appendix to later versions of the above licences without producing
+a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the
+covered Source Code from exclusive appropriation.
+All other changes or additions to this Appendix require the production of a new EUPL version.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,56 @@
+# gongju-text
+
+**Structured text utilities, non-empty text collections, and layout parsing for Haskell.**
+
+`gongju-text` provides lightweight, robust text processing utilities leveraging non-empty refinements, polymorphic container targets, and layout-aware block and stanza parsing.
+
+---
+
+## Features
+
+1. **Non-Empty Text Refinements (`Gongju.Text.NonEmpty`):**
+   - Safe construction and unwrap via `NonNull Text` (`fromText`, `toText`).
+   - Non-empty word splitting (`wordsNE :: Text -> Maybe (NonEmpty Text)`).
+   - Non-empty line splitting (`linesNE :: Text -> Maybe (NonEmpty Text)`).
+
+2. **Polymorphic Stanza Chunking (`Gongju.Text.Stanza`):**
+   - Stanza grouping of lines separated by one or more blank or whitespace lines.
+   - Container-polymorphic wrapper `Stanza container`.
+   - List-based (`stanzas :: Text -> [Stanza []]`) and sequence-based (`stanzasSeq :: Text -> Seq (Stanza Seq)`) chunking.
+
+3. **Indentation and Block Parsing (`Gongju.Text.Layout`):**
+   - Space-indentation measuring (`parseIndentedLines :: [Text] -> [IndentedLine]`).
+   - Hierarchical indentation block tree construction (`parseBlocks :: [Text] -> [Block]`).
+   - Zero-dependency layout analysis without full Markdown or YAML parser overhead.
+
+---
+
+## Quick Example
+
+```haskell
+{-# LANGUAGE OverloadedStrings #-}
+
+import qualified Data.Text as T
+import Gongju.Text.NonEmpty (wordsNE)
+import Gongju.Text.Stanza (stanzas, unStanza)
+import Gongju.Text.Layout (parseBlocks, Block(..))
+
+main :: IO ()
+main = do
+  -- 1. Non-empty word splitting
+  print (wordsNE "hello   world")
+
+  -- 2. Stanza grouping
+  let doc = "First stanza line 1\nLine 2\n\nSecond stanza line 1\nLine 2"
+  mapM_ (print . unStanza) (stanzas doc)
+
+  -- 3. Indented block hierarchy
+  let lines' = ["root", "  child 1", "  child 2", "    grandchild"]
+  print (parseBlocks lines')
+```
+
+---
+
+## License
+
+Licensed under the European Union Public Licence (EUPL-1.2) or Apache License 2.0.
diff --git a/gongju-text.cabal b/gongju-text.cabal
new file mode 100644
--- /dev/null
+++ b/gongju-text.cabal
@@ -0,0 +1,67 @@
+cabal-version:      3.0
+name:               gongju-text
+version:            0.1.0.0
+synopsis:           Structured text utilities, non-empty text collections, and layout parsing
+description:
+    Gongju-Text provides robust text processing utilities leveraging non-empty
+    refinements, polymorphic container targets, and layout-aware block/stanza parsing.
+license:            EUPL-1.2 OR Apache-2.0
+license-files:      LICENSE
+author:             Nadia Yvette Chambers
+maintainer:         nadia.yvette.chambers@ik.me
+homepage:           https://github.com/NadiaYvette/gongju
+bug-reports:        https://github.com/NadiaYvette/gongju/issues
+category:           Text, Parsing, Utilities
+build-type:         Simple
+extra-doc-files:
+    README.md
+  , CHANGELOG.md
+  , LICENSE
+
+tested-with:
+    GHC == 9.6.7
+  , GHC == 9.8.4
+  , GHC == 9.10.3
+  , GHC == 9.12.4
+  , GHC == 9.14.1
+
+source-repository head
+    type:     git
+    location: https://github.com/NadiaYvette/gongju.git
+    subdir:   packages/gongju-text
+
+common commons
+    default-language:   Haskell2010
+    default-extensions:
+        DerivingStrategies
+        OverloadedStrings
+        ScopedTypeVariables
+        StandaloneDeriving
+        UndecidableInstances
+    ghc-options:        -Wall -Wcompat -Wredundant-constraints
+
+library
+    import:             commons
+    hs-source-dirs:     src
+    exposed-modules:
+        Gongju.Text.NonEmpty
+        Gongju.Text.Stanza
+        Gongju.Text.Layout
+    build-depends:
+        base              >= 4.18 && < 5
+      , text              >= 2     && < 3
+      , mono-traversable  >= 1     && < 2
+      , containers        >= 0.6   && < 1
+      , vector            >= 0.12  && < 1
+
+test-suite gongju-text-test
+    import:             commons
+    type:               exitcode-stdio-1.0
+    hs-source-dirs:     test
+    main-is:            Main.hs
+    build-depends:
+        base              >= 4.18 && < 5
+      , text              >= 2     && < 3
+      , gongju-text
+      , hspec             >= 2.11  && < 3
+      , containers        >= 0.6   && < 1
diff --git a/src/Gongju/Text/Layout.hs b/src/Gongju/Text/Layout.hs
new file mode 100644
--- /dev/null
+++ b/src/Gongju/Text/Layout.hs
@@ -0,0 +1,52 @@
+{- |
+Module      : Gongju.Text.Layout
+Description : Lightweight layout, indentation, and block-nesting analysis
+Copyright   : (c) Nadia Yvette Chambers, 2026
+License     : EUPL-1.2 OR Apache-2.0
+
+Provides indentation measuring and hierarchical block structure resolution
+without relying on full specification codecs like Markdown.
+-}
+module Gongju.Text.Layout
+    ( IndentedLine (..)
+    , Block (..)
+    , parseIndentedLines
+    , parseBlocks
+    ) where
+
+import qualified Data.Text as T
+
+-- | A line with measured leading whitespace indentation depth.
+data IndentedLine = IndentedLine
+    { indentDepth :: !Int
+    , lineContent :: !T.Text
+    } deriving stock (Show, Eq)
+
+-- | A nested block of content based on indentation level.
+data Block = Block
+    { blockIndent :: !Int
+    , blockLines  :: ![T.Text]
+    , subBlocks   :: ![Block]
+    } deriving stock (Show, Eq)
+
+-- | Convert raw lines into 't:IndentedLine' records counting space depth.
+parseIndentedLines :: [T.Text] -> [IndentedLine]
+parseIndentedLines = map parseLine
+  where
+    parseLine t =
+        let (spaces, rest) = T.span (== ' ') t
+        in IndentedLine (T.length spaces) rest
+
+-- | Group indented lines into nested hierarchical blocks.
+parseBlocks :: [T.Text] -> [Block]
+parseBlocks lines' = buildBlocks (parseIndentedLines $ filter (not . T.null . T.strip) lines')
+
+buildBlocks :: [IndentedLine] -> [Block]
+buildBlocks [] = []
+buildBlocks (IndentedLine d content : rest) =
+    let (sameOrDeeper, shallower) = span (\l -> indentDepth l >= d) rest
+        (direct, deeper) = span (\l -> indentDepth l == d) sameOrDeeper
+        currLines = content : map lineContent direct
+        nested = buildBlocks deeper
+        currBlock = Block d currLines nested
+    in currBlock : buildBlocks shallower
diff --git a/src/Gongju/Text/NonEmpty.hs b/src/Gongju/Text/NonEmpty.hs
new file mode 100644
--- /dev/null
+++ b/src/Gongju/Text/NonEmpty.hs
@@ -0,0 +1,45 @@
+{- |
+Module      : Gongju.Text.NonEmpty
+Description : Non-empty text refinements and safe splitting utilities
+Copyright   : (c) Nadia Yvette Chambers, 2026
+License     : EUPL-1.2 OR Apache-2.0
+
+Provides safe wrappers and collection operations over non-empty text values
+utilizing 'mono-traversable' 'NonNull' types, ensuring operations like word
+splitting and regex-style matching yield meaningful non-empty components.
+-}
+module Gongju.Text.NonEmpty
+    ( NonNull
+    , toText
+    , fromText
+    , wordsNE
+    , linesNE
+    ) where
+
+import qualified Data.Text as T
+import Data.List.NonEmpty (NonEmpty (..))
+import Data.NonNull (NonNull, fromNullable, toNullable)
+
+-- | Convert a NonNull Text back to a strict Text.
+toText :: NonNull T.Text -> T.Text
+toText = toNullable
+
+-- | Attempt to create a NonNull Text from a strict Text.
+fromText :: T.Text -> Maybe (NonNull T.Text)
+fromText = fromNullable
+
+-- | Split text into non-empty words as a NonEmpty collection.
+wordsNE :: T.Text -> Maybe (NonEmpty T.Text)
+wordsNE t =
+    let ws = filter (not . T.null) (T.words t)
+    in case ws of
+         []     -> Nothing
+         (w:ws') -> Just (w :| ws')
+
+-- | Split text into non-empty lines as a NonEmpty collection.
+linesNE :: T.Text -> Maybe (NonEmpty T.Text)
+linesNE t =
+    let ls = filter (not . T.null) (T.lines t)
+    in case ls of
+         []     -> Nothing
+         (l:ls') -> Just (l :| ls')
diff --git a/src/Gongju/Text/Stanza.hs b/src/Gongju/Text/Stanza.hs
new file mode 100644
--- /dev/null
+++ b/src/Gongju/Text/Stanza.hs
@@ -0,0 +1,43 @@
+{- |
+Module      : Gongju.Text.Stanza
+Description : Stanza splitting and parsing over polymorphic containers
+Copyright   : (c) Nadia Yvette Chambers, 2026
+License     : EUPL-1.2 OR Apache-2.0
+
+Handles grouping of lines into stanzas separated by one or more blank lines,
+with support for polymorphic container targets (Lists, Sequences, Vectors).
+-}
+module Gongju.Text.Stanza
+    ( Stanza (..)
+    , stanzas
+    , stanzasSeq
+    ) where
+
+import qualified Data.Text as T
+import qualified Data.Sequence as Seq
+import Data.Sequence (Seq)
+
+-- | A stanza represents a block of non-blank lines.
+newtype Stanza container = Stanza { unStanza :: container T.Text }
+
+deriving instance Show (container T.Text) => Show (Stanza container)
+deriving instance Eq (container T.Text) => Eq (Stanza container)
+
+-- | Split text into stanzas separated by blank lines, returning standard lists.
+stanzas :: T.Text -> [Stanza []]
+stanzas = fmap (Stanza . filter (not . T.null)) . splitStanzas . T.lines
+  where
+    splitStanzas [] = []
+    splitStanzas ls =
+        let (block, rest) = break isBlank ls
+        in case block of
+             []   -> splitStanzas (dropWhile isBlank rest)
+             b    -> b : splitStanzas (dropWhile isBlank rest)
+
+    isBlank = T.null . T.strip
+
+-- | Split text into stanzas, collecting lines into 'Seq.Seq'.
+stanzasSeq :: T.Text -> Seq (Stanza Seq)
+stanzasSeq = Seq.fromList . fmap toSeqStanza . stanzas
+  where
+    toSeqStanza (Stanza xs) = Stanza (Seq.fromList xs)
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,39 @@
+module Main (main) where
+
+import Test.Hspec
+import qualified Data.Text as T
+import qualified Data.Sequence as Seq
+import Data.List.NonEmpty (NonEmpty (..))
+import Gongju.Text.NonEmpty
+import Gongju.Text.Stanza
+import Gongju.Text.Layout
+
+main :: IO ()
+main = hspec $ do
+  describe "Gongju.Text.NonEmpty" $ do
+    it "fromText returns Nothing for empty text" $
+      fromText T.empty `shouldBe` Nothing
+
+    it "wordsNE splits words into NonEmpty" $
+      wordsNE "hello world" `shouldBe` Just (T.pack "hello" :| [T.pack "world"])
+
+  describe "Gongju.Text.Stanza" $ do
+    it "parses stanzas separated by blank lines" $ do
+      let sample = "Line 1\nLine 2\n\nLine 3\n"
+          res = stanzas sample
+      res `shouldBe` [Stanza [T.pack "Line 1", T.pack "Line 2"], Stanza [T.pack "Line 3"]]
+
+    it "parses stanzas into Seq containers" $ do
+      let sample = "A\n\nB\n"
+          res = stanzasSeq sample
+      res `shouldBe` Seq.fromList [Stanza (Seq.singleton (T.pack "A")), Stanza (Seq.singleton (T.pack "B"))]
+
+  describe "Gongju.Text.Layout" $ do
+    it "computes indentation depths" $ do
+      let raw = [T.pack "  indented"]
+      parseIndentedLines raw `shouldBe` [IndentedLine 2 (T.pack "indented")]
+
+    it "constructs nested blocks" $ do
+      let raw = [T.pack "root", T.pack "  child"]
+          res = parseBlocks raw
+      res `shouldBe` [Block 0 [T.pack "root"] [Block 2 [T.pack "child"] []]]
