diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,178 +1,147 @@
 # clerk
 
-`clerk` is a library for declarative spreadsheet generation using a Haskell eDSL.
-
-It extends upon the [work](https://youtu.be/1xGoa-zEOrQ) of Kudasov by making the tables' layout more flexible.
+`clerk` provides a Haskell eDSL in a library for declarative spreadsheet generation. `clerk` is built on top of the [xlsx](https://hackage.haskell.org/package/xlsx) package and extends upon the [work](https://youtu.be/1xGoa-zEOrQ) of Nickolay Kudasov by making the tables' layout more flexible.
 
 ## Features
 
-`clerk` produces a styled spreadsheet with some data and formulas on it. These formulas will be calculated by the target spreadsheet system.
+`clerk` can be used to produce a styled spreadsheet with some data and formulas on it. These formulas are evaluated when the document is loaded into a target spreadsheet system.
 
-The library supports
+The library supports:
 
-- typed cell references - `Cell Double`
-- type-safe arithmetic operations - `(a :: Cell Double) + (b :: Cell Double)`
-- range references - `a |:| b` -> `A1:B1`
-- formulas - `(e :: Expr Double) = "SUM" |$| [(a |:| b)]` -> `SUM(A1:B1)`
-- conditional styles, formatting, column widths
+- Typed cell references. Example: `CellRef Double`.
+- Type-safe arithmetic operations with them. Example: `(a :: CellRef Double) + (b :: CellRef Double)` produces a `CellRef Double`.
+- Constructing expressions with given types. Example: `(e :: Expr Double) = "SUM" |$| [a |:| b]`, `e` translates to `SUM(A1:B1)` (actual value depends on the values of `a` and `b`).
+- Conditional styles, formatting, column widths.
 
-The example below demonstrates some of these features.
+The example below demonstrates most of these features.
 
 ## Example
 
-This is a demo program that uses `clerk` to produce an `xlsx` file that looks as follows:
+The goal: describe and generate a spreadsheet that calculates the pressure data given some volume data and constants.
 
+The source code for this example is available in the [example](./example) directory.
+The program produces an `xlsx` file that looks as follows:
+
 <img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/demoValues.png" width = "80%">
 
 Alternatively, with formulas enabled:
 
 <img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/demoFormulas.png" width = "80%">
 
-This file has a sheet with several tables. These are tables for
-constants' header, a table per a constant's value (three of them), volume & pressure header, volume & pressure values.
-Let's see how we can construct such a sheet.
+The below sections describe how such a spreadsheet can be constructed.
 
+### Extensions
+
+We'll need several language extensions.
+<!-- FOURMOLU_DISABLE -->
+
+```haskell
+{-# LANGUAGE OverloadedRecordDot #-} -- access the fields of records like a.b
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE InstanceSigs #-}
+```
+
+<!-- FOURMOLU_ENABLE -->
+
 ### Imports
 
-First, we import the necessary stuff.
+And import the necessary stuff.
 
 ```haskell
 module Main (main) where
+
 import Clerk
 import Codec.Xlsx qualified as X
 import Codec.Xlsx.Formatted qualified as X
 import Control.Lens ((%~), (&), (?~))
+import Control.Monad (void)
 import Data.ByteString.Lazy qualified as L
 import Data.Text qualified as T
 import Data.Time.Clock.POSIX (getPOSIXTime)
-import Control.Monad (void)
 ```
 
-### Inputs
+### Tables
 
-Following that, we declare a number of data types that we'll use to store the input values.
+The tables that we'd like to construct are:
 
-A type for constants' headers.
+- A table per a constant's value (three of them)
+- Volume & pressure table
+- Constants' header
+- Volume & pressure header
 
-```haskell
-data ConstantsHeader = ConstantsHeader
-    { hConstant :: String
-    , hSymbol :: String
-    , hValue :: String
-    , hUnits :: String
-    }
+#### Constants' values
 
-constantsHeader :: ConstantsHeader
-constantsHeader =
-    ConstantsHeader
-        { hConstant = "constant"
-        , hSymbol = "symbol"
-        , hValue = "value"
-        , hUnits = "units"
-        }
-```
+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constants.png" width = "50%">
 
-A type for constants' data.
+In our case, each constant has the same type of the numeric value - `Double`.
+However, it might be the case that in another set of constants, they'll have different types.
+That's why, in our case, we'll construct a table with a single row per a constant and later stack the constants' tables together.
+We can keep a constant's data in a record.
 
 ```haskell
-data ConstantsData a = ConstantsData
-    { name :: String
-    , symbol :: String
-    , value :: a
-    , units :: String
-    }
+data ConstantData a = ConstantData
+  { constantName :: String
+  , constantSymbol :: String
+  , constantValue :: a
+  , constantUnits :: String
+  }
 ```
 
-Additionally, we declare a helper type that will store all constants together.
+Next, we can group the constants.
 
 ```haskell
-data ConstantsInput = ConstantsInput
-    { gas :: ConstantsData Double
-    , nMoles :: ConstantsData Double
-    , temperature :: ConstantsData Double
-    }
+data Constants f = Constants
+  { gasConstant :: f Double
+  , numberOfMoles :: f Double
+  , temperature :: f Double
+  }
 
+type ConstantsInput = Constants ConstantData
+
 constants :: ConstantsInput
 constants =
-    ConstantsInput
-        { gas = ConstantsData "GAS CONSTANT" "R" 0.08206 "L.atm/mol.K"
-        , nMoles = ConstantsData "NUMBER OF MOLES" "n" 1 "moles"
-        , temperature = ConstantsData "TEMPERATURE(K)" "T" 273.2 "K"
-        }
-```
-
-A type for the Volume & Pressure header.
-
-```haskell
-data ValuesHeader = ValuesHeader
-    { hVolume :: String
-    , hPressure :: String
+  Constants
+    { gasConstant = ConstantData "GAS CONSTANT" "R" 0.08206 "L.atm/mol.K"
+    , numberOfMoles = ConstantData "NUMBER OF MOLES" "n" 1 "moles"
+    , temperature = ConstantData "TEMPERATURE(K)" "T" 273.2 "K"
     }
-
-valuesHeader :: ValuesHeader
-valuesHeader =
-    ValuesHeader
-        { hVolume = "VOLUME (L)"
-        , hPressure = "PRESSURE (atm)"
-        }
 ```
 
-The last type is for volume inputs. We just generate them
-
-```haskell
-newtype Volume = Volume
-    { volume :: Double
-    }
-
-volumeData :: [Volume]
-volumeData = take 10 $ Volume <$> [1 ..]
-```
-
-### Styles
-
-Following the headers and data types, we define the styles. Let's start with colors.
-We select several color codes and store them into `colors`
+Furthermore, we'd like to style the constants' tables, so let's prepare the styles. We'll reuse these styles in other tables.
 
 ```haskell
-data Colors = Colors
-    { lightBlue :: T.Text
-    , lightGreen :: T.Text
-    , blue :: T.Text
-    , green :: T.Text
-    }
-
-colors :: Colors
-colors =
-    Colors
-        { lightGreen = "90CCFFCC"
-        , lightBlue = "90CCFFFF"
-        , blue = "FF99CCFF"
-        , green = "FF00FF00"
-        }
-```
-
-Next, we convert them to `FormatCell` function
+data Colors = LightBlue | LightGreen | Blue | Green
+instance Show Colors where
+  show :: Colors -> String
+  show = \case
+    LightBlue -> "90CCFFFF"
+    LightGreen -> "90CCFFCC"
+    Blue -> "FF99CCFF"
+    Green -> "FF00FF00"
 
-```haskell
-colorBlue :: FormatCell
-colorBlue = mkColorStyle colors.blue
+blue :: FormatCell
+blue = mkColorStyle Blue
 
-colorLightBlue :: FormatCell
-colorLightBlue = mkColorStyle colors.lightBlue
+lightBlue :: FormatCell
+lightBlue = mkColorStyle LightBlue
 
-colorGreen :: FormatCell
-colorGreen = mkColorStyle colors.green
+green :: FormatCell
+green = mkColorStyle Green
 
-colorMixed :: FormatCell
-colorMixed coords idx = mkColorStyle (if even idx then colors.lightGreen else colors.lightBlue) coords idx
+mixed :: FormatCell
+mixed coords idx = mkColorStyle (if even idx then LightGreen else LightBlue) coords idx
 ```
 
-Additionally, we compose a transform for the number format
+Additionally, we compose a transformation of a `FormatCell` for the number format
 
 ```haskell
--- | allow 2 decimal digits
-nf2decimal :: FCTransform
-nf2decimal fc = fc & X.formattedFormat %~ (\ff -> ff & X.formatNumberFormat ?~ X.StdNumberFormat X.Nf2Decimal)
+use2decimalDigits :: FCTransform
+use2decimalDigits fcTransform =
+  fcTransform & X.formattedFormat %~ (\format -> format & X.formatNumberFormat ?~ X.StdNumberFormat X.Nf2Decimal)
 ```
 
 And a transform for centering the cell contents
@@ -182,101 +151,118 @@
 alignCenter = horizontalAlignment X.CellHorizontalAlignmentCenter
 ```
 
-### `Builder`s
+Now, we can make a `RowBuilder` for a constant.
+We'll later use this builder for each constant separately.
 
-Now, we are able to compose the `Builder`s for tables.
+We get a pair of outputs:
 
-A builder for the constants header.
+- Top left cell of a constant's table. That is, the cell with that constant's name.
+- The value of the constant.
 
-<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constantsHeader.png" width = "50%">
+Later, the outputs of this and other `RowBuilder`s will be used to relate the positions of tables on a sheet.
 
 ```haskell
-constantsHeaderBuilder :: Builder ConstantsHeader CellData (Coords, Coords)
-constantsHeaderBuilder = do
-    tl <- columnWidth 20 (alignCenter <| colorBlue) hConstant
-    columnWidth_ 8 (alignCenter <| colorBlue) hSymbol
-    column_ (alignCenter <| colorBlue) hValue
-    tr <- columnWidth 13 (alignCenter <| colorBlue) hUnits
-    return (unCell tl, unCell tr)
+constantBuilder :: ToCellData a => RowBuilder (ConstantData a) CellData (Coords, CellRef a)
+constantBuilder = do
+  refTopLeft <- column lightBlue constantName
+  column_ lightBlue constantSymbol
+  refValue <- column (lightBlue +> use2decimalDigits) constantValue
+  column_ lightBlue constantUnits
+  return (unCell refTopLeft, refValue)
 ```
 
-A builder for a constant. We'll use this builder for each constant separately
-as each constant produces cells of a specific type.
+#### Volume & Pressure values
 
-<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constants.png" width = "50%">
+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesFormulas.png" width = "50%">
 
+To fill this table, we'll take the some data and combine it with the constants.
+
 ```haskell
-constantBuilder :: forall a. ToCellData a => Builder (ConstantsData a) CellData (Coords, Cell a)
-constantBuilder = do
-    topLeft <- column colorLightBlue name
-    column_ colorLightBlue symbol
-    value <- column (nf2decimal <| colorLightBlue) value
-    column_ colorLightBlue units
-    return (unCell topLeft, value)
+newtype Volume = Volume {volume :: Double}
+
+volumeData :: [Volume]
+volumeData = take 10 $ Volume <$> [1 ..]
 ```
 
-A builder for values' header.
+To pass the constants' references in a structured way, we make a helper type.
 
-<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesHeader.png" width = "50%">
+```haskell
+data ConstantsRefs = ConstantsRefs
+  { refGas :: CellRef Double
+  , refNumberOfMoles :: CellRef Double
+  , refTemperature :: CellRef Double
+  }
+```
 
+Next, we define a function to produce a builder for volume and pressure.
+
 ```haskell
-valuesHeaderBuilder :: Builder ValuesHeader CellData Coords
-valuesHeaderBuilder = do
-    tl <- columnWidth 12 colorGreen hVolume
-    columnWidth_ 16 colorGreen hPressure
-    return (unCell tl)
+valuesBuilder :: ConstantsRefs -> RowBuilder Volume CellData ()
+valuesBuilder ConstantsRefs{..} = do
+  refVolume <- column mixed volume
+  let pressure' = refGas |*| refNumberOfMoles |*| refTemperature |/| refVolume
+  column_ (mixed +> use2decimalDigits) (const pressure')
 ```
 
-A builder for volume & pressure (formulas enabled)
+#### Constants' header
 
-<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesFormulas.png" width = "50%">
+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constantsHeader.png" width = "50%">
 
-To pass values in a structured way, we make a helper type.
+We won't use records here. Instead, we'll put the names of the columns straight into the `RowBuilder`.
 
+The outputs will be the coordinates of the top left cell and the top right cell of this table.
+
 ```haskell
-data ConstantsValues = ConstantsValues
-    { gas :: Cell Double
-    , nMoles :: Cell Double
-    , temperature :: Cell Double
-    }
+constantsHeaderBuilder :: RowBuilder () CellData (Coords, Coords)
+constantsHeaderBuilder = do
+  refTopLeft <- columnWidth 20 (blue +> alignCenter) (const "constant")
+  columnWidth_ 8 (blue +> alignCenter) (const "symbol")
+  column_ (blue +> alignCenter) (const "value")
+  refTopRight <- columnWidth 13 (blue +> alignCenter) (const "units")
+  return (unCell refTopLeft, unCell refTopRight)
 ```
 
-Next, we define a function to produce a builder.
+#### Volume & Pressure header
 
+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesHeader.png" width = "50%">
+
+For this header, we'll also put the names of columns straight inside the builder.
+
 ```haskell
-valuesBuilder :: ConstantsValues -> Builder Volume CellData ()
-valuesBuilder cv = do
-    volume' <- column colorMixed volume
-    let pressure' = ex cv.gas |*| ex cv.nMoles |*| ex cv.temperature |/| ex volume'
-    column_ (nf2decimal <| colorMixed) (const pressure')
+valuesHeaderBuilder :: RowBuilder () CellData Coords
+valuesHeaderBuilder = do
+  tl <- columnWidth 12 green (const "VOLUME (L)")
+  columnWidth_ 16 green (const "PRESSURE (atm)")
+  return (unCell tl)
 ```
 
-### `SheetBuilder`
+### Sheet builder
 
-The `SheetBuilder` is used to place builders onto a sheet and glue them together
+The `SheetBuilder` is used to place `RowBuilder`s onto a sheet and glue them together.
+Inside `SheetBuilder`, when a `RowBuilder` is placed onto a sheet, we can use the
+references that it produces in the subsequent expressions.
 
 ```haskell
 full :: SheetBuilder ()
 full = do
-    (constantsHeaderTL, constantsHeaderTR) <- placeInput (Coords 2 2) constantsHeader constantsHeaderBuilder
-    (gasTL, gas) <- placeInput (overRow (+ 2) constantsHeaderTL) constants.gas constantBuilder
-    (nMolesTL, nMoles) <- placeInput (overRow (+ 1) gasTL) constants.nMoles constantBuilder
-    temperature <- snd <$> placeInput (overRow (+ 1) nMolesTL) constants.temperature constantBuilder
-    valuesHeaderTL <- placeInput (overCol (+ 2) constantsHeaderTR) valuesHeader valuesHeaderBuilder
-    placeInputs_ (overRow (+ 2) valuesHeaderTL) volumeData (valuesBuilder $ ConstantsValues{..})
+  (constantsHeaderTL, constantsHeaderTR) <- placeInput (Coords 2 2) () constantsHeaderBuilder
+  (gasTL, gas) <- placeInput (overRow (+ 2) constantsHeaderTL) constants.gasConstant constantBuilder
+  (nMolesTL, nMoles) <- placeInput (overRow (+ 1) gasTL) constants.numberOfMoles constantBuilder
+  temperature <- snd <$> placeInput (overRow (+ 1) nMolesTL) constants.temperature constantBuilder
+  valuesHeaderTL <- placeInput (overCol (+ 2) constantsHeaderTR) () valuesHeaderBuilder
+  placeInputs_ (overRow (+ 2) valuesHeaderTL) volumeData (valuesBuilder $ ConstantsRefs gas nMoles temperature)
 ```
 
 ### Result
 
-Now, we can write the result and get the spreadsheet images that you've seen at the top of this tutorial.
+Finally, we can write the result and get the spreadsheet like the one that you've seen at the top of this tutorial.
 
 ```haskell
 writeWorksheet :: SheetBuilder a -> String -> IO ()
 writeWorksheet tb name = do
-    ct <- getPOSIXTime
-    let
-        xlsx = composeXlsx [("List 1", void tb)]
-    L.writeFile ("example-" <> name <> ".xlsx") $ X.fromXlsx ct xlsx
+  ct <- getPOSIXTime
+  let xlsx = composeXlsx [(T.pack "List 1", void tb)]
+  L.writeFile ("example-" <> name <> ".xlsx") $ X.fromXlsx ct xlsx
 
 writeEx :: IO ()
 writeEx = writeWorksheet full "1"
@@ -301,20 +287,34 @@
 
 ## Contribute
 
-### Prerequisites
+This project provides a dev environment via a `Nix` flake.
 
-As this project uses `Nix` for dev environment, study the following prerequisites to set up the project
+1. With [flakes enabled](https://nixos.wiki/wiki/Flakes#Enable_flakes), run:
 
-- [Prerequisites](https://github.com/deemp/flakes#prerequisites)
-- `Haskell` project [template](https://github.com/deemp/flakes/tree/main/templates/codium/haskell#readme)
-- [Haskell](https://github.com/deemp/flakes/blob/main/README/Haskell.md)
+    ```console
+    nix develop
+    cabal build
+    ```
 
-Next, run
+1. This `README.md` is generated from several files. If you edit them, re-generate it.
 
-```sh
-nix develop
-write-settings-json
-codium .
-```
+    ```console
+    cabal test docs
+    ```
 
-and open a `Haskell` file. `HLS` should soon start giving you hints.
+1. (Optionally) Start `VSCodium` with `Haskell` extensions.
+
+    1. Write settings and run `VSCodium`.
+
+        ```console
+        nix run .#writeSettings
+        nix run .#codium .
+        ```
+
+    1. Open a `Haskell` file. `Haskell Language Server` should soon start giving you hints.
+
+1. Study these links if you'd like to learn more about the tools used in this flake:
+
+    - [Prerequisites](https://github.com/deemp/flakes#prerequisites)
+    - `Haskell` project [template](https://github.com/deemp/flakes/tree/main/templates/codium/haskell#readme)
+    - [Haskell](https://github.com/deemp/flakes/blob/main/README/Haskell.md)
diff --git a/clerk.cabal b/clerk.cabal
--- a/clerk.cabal
+++ b/clerk.cabal
@@ -1,13 +1,16 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.35.0.
+-- This file has been generated from package.yaml by hpack version 0.35.1.
 --
 -- see: https://github.com/sol/hpack
 
 name:           clerk
-version:        0.1.0.2
-synopsis:       Declaratively describe spreadsheets and generate xlsx
-description:    Please see the README on GitHub at <https://github.com/deemp/clerk#readme>
+version:        0.1.0.3
+synopsis:       Declaratively describe spreadsheets
+description:    `clerk` provides a Haskell eDSL and a library for declaratively describing the spreadsheets. 
+                `clerk` is built on top of the [xlsx](https://hackage.haskell.org/package/xlsx) package 
+                and extends upon the [work](https://youtu.be/1xGoa-zEOrQ) of Nickolay Kudasov.
+                See the [README](https://github.com/deemp/clerk#readme) for an example of `clerk` usage and further info.
 category:       spreadsheet
 homepage:       https://github.com/deemp/clerk#readme
 bug-reports:    https://github.com/deemp/clerk/issues
@@ -32,31 +35,38 @@
       Paths_clerk
   hs-source-dirs:
       src
-  default-extensions:
-      DataKinds
-      DeriveFunctor
-      FlexibleContexts
-      FlexibleInstances
-      OverloadedStrings
-      GeneralizedNewtypeDeriving
-      RankNTypes
-      InstanceSigs
-      NamedFieldPuns
-      RecordWildCards
-      TupleSections
-      MonoLocalBinds
-      TypeSynonymInstances
-      DuplicateRecordFields
   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
   build-depends:
-      base >=4.9.0.0 && <5.0
-    , bytestring >=0.10.8.0
-    , containers >=0.5.0.0
-    , data-default >=0.7.1.1
-    , lens >=3.8 && <5.3
-    , mtl >=2.1
-    , text >=0.11.3.1
-    , time >=1.4.0.1
-    , transformers >=0.3.0.0
-    , xlsx >=1.1.0.1
+      base >=4.9 && <5.0
+    , bytestring
+    , containers
+    , data-default
+    , lens
+    , mtl
+    , text
+    , time
+    , transformers
+    , xlsx
+  default-language: Haskell2010
+
+test-suite docs
+  type: exitcode-stdio-1.0
+  main-is: test/Docs.hs
+  other-modules:
+      Paths_clerk
+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
+  build-tool-depends:
+      lima:lima ==0.1.*
+  build-depends:
+      base
+    , bytestring
+    , containers
+    , data-default
+    , lens
+    , mtl
+    , text
+    , time
+    , transformers
+    , typed-process
+    , xlsx
   default-language: Haskell2010
diff --git a/src/Clerk.hs b/src/Clerk.hs
--- a/src/Clerk.hs
+++ b/src/Clerk.hs
@@ -1,28 +1,86 @@
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE DeriveFunctor #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE ImportQualifiedPost #-}
+{-# LANGUAGE InstanceSigs #-}
+{-# LANGUAGE MonoLocalBinds #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
 {-# OPTIONS_GHC -Wno-redundant-constraints #-}
 {-# OPTIONS_GHC -Wno-unused-top-binds #-}
 
+-- | @Clerk@ library
+
 module Clerk (
-  Builder,
-  Cell (unCell),
-  CellData,
-  Coords (Coords),
-  FCTransform,
+  -- * Coords
+  -- $Coords
+  Coords (..),
+
+  -- * Cell references
+  -- $CellRef
+  CellRef (..),
+  getCol,
+  getRow,
+  overCol,
+  overRow,
+  unsafeChangeCellRefType,
+
+  -- * Cell formatting
+  -- $Formatting
+  InputIndex,
   FormatCell,
-  ToCellData,
-  SheetBuilder (..),
-  column,
+  CellTemplate,
+  FormattedMap,
+  FMTransform,
+  WSTransform,
+  Transform,
+  FCTransform,
+  horizontalAlignment,
+  mkColorStyle,
+
+  -- * Templates
+  -- $Templates
+  RowBuilder (..),
+  Template (..),
+  -- runBuilder,
+  -- evalBuilder,
+  -- execBuilder,
+  -- RenderTemplate,
+  -- RenderBuilderInputs,
+  -- RenderBuilderInput,
+  -- renderBuilderInputs,
+  -- renderTemplate,
+
+  -- * Columns
+  -- $Columns
+  ColumnsProperties (..),
+  columnWidthCell,
   columnWidth,
   columnWidth_,
+  column,
   column_,
-  composeXlsx,
-  ex,
-  ex',
-  horizontalAlignment,
-  mkColorStyle,
-  overCol,
-  overRow,
-  placeInput,
+
+  -- * Sheet builder
+  -- $SheetBuilder
+  SheetBuilder (..),
+  placeInputs,
   placeInputs_,
+  placeInput,
+  placeInput_,
+
+  -- * Expressions
+  -- $Expressions
+  Expr (..),
+  ToExpr (..),
+  ArithmeticOperator,
   (|+|),
   (|-|),
   (|*|),
@@ -30,12 +88,20 @@
   (|:|),
   (|^|),
   (|$|),
-  (<|),
-  Expr (..),
+  (+>),
+
+  -- * Cells
+  -- $Cells
+  CellData,
+  ToCellData (..),
+
+  -- * Produce xlsx
+  -- $Xlsx
+  composeXlsx,
 ) where
 
-import qualified Codec.Xlsx as X
-import qualified Codec.Xlsx.Formatted as X
+import Codec.Xlsx qualified as X
+import Codec.Xlsx.Formatted qualified as X
 import Control.Lens (Identity (runIdentity), (%~), (&), (?~))
 import Control.Lens.Operators ((.~))
 import Control.Monad.State (
@@ -53,36 +119,26 @@
 import Data.Default (Default (..))
 import Data.Foldable (Foldable (..))
 import Data.List (intercalate)
-import qualified Data.Map.Strict as Map (Map, insert)
+import Data.Map.Strict qualified as Map (Map, insert)
 import Data.Maybe (isJust, maybeToList)
-import qualified Data.Text as T
-
--- Coords
+import Data.Text qualified as T
 
 -- TODO Allow sheet addresses
 
+-- TODO Make formulas aware of the current sheet
+-- Or, make just formula printers aware so that they don't print the full address
+-- when referring to data on the same sheet
+
+{- FOURMOLU_DISABLE -}
+-- $Coords
+{- FOURMOLU_ENABLE -}
+
 -- | Coords of a cell
 data Coords = Coords {row :: Int, col :: Int}
 
 instance Show Coords where
   show :: Coords -> String
-  show (Coords{..}) = toLetters col <> show row
-
-alphabet :: [String]
-alphabet = (: "") <$> ['A' .. 'Z']
-
-toLetters :: Int -> String
-toLetters x = f "" (x - 1)
- where
-  new :: Int -> String -> String
-  new cur acc = alphabet !! (cur `mod` 26) <> acc
-  f :: String -> Int -> String
-  f acc cur = if cur `div` 26 > 0 then f (new cur acc) (cur `div` 26 - 1) else new cur acc
-
-{-
->>>toLetters <$> [1, 26, 27, 52, 78]
-["A","Z","AA","AZ","BZ"]
--}
+  show (Coords{..}) = toAlphaNumeric col <> show row
 
 instance Num Coords where
   (+) :: Coords -> Coords -> Coords
@@ -98,28 +154,97 @@
   fromInteger :: Integer -> Coords
   fromInteger x = Coords (fromIntegral (abs x)) (fromIntegral (abs x))
 
--- Cell
+-- | Letters that can be used in column indices
+alphabet :: [Char]
+alphabet = ['A' .. 'Z']
 
+-- | Translate a number into an alphanumeric representation. Relevant for columns
+toAlphaNumeric :: Int -> String
+toAlphaNumeric x = f "" (x - 1)
+ where
+  new :: Int -> String -> String
+  new cur acc = [alphabet !! (cur `mod` 26)] <> acc
+  f :: String -> Int -> String
+  f acc cur = if cur `div` 26 > 0 then f (new cur acc) (cur `div` 26 - 1) else new cur acc
+
+{-
+>>>toLetters <$> [1, 26, 27, 52, 78]
+["A","Z","AA","AZ","BZ"]
+-}
+
+-- {- FOURMOLU_DISABLE -}
+
+-- $CellRef
+-- {\- FOURMOLU_ENABLE -\}
+
+-- | A typed reference to a cell.
+--
+-- The user is responsible for setting the necessary cell type.
+--
+-- The type prevents operations between cell references with incompatible types.
+--
+-- >>>str = CellRef (Coords 1 1) :: CellRef String
+-- >>> str |+| str
+-- No instance for (Num String) arising from a use of ‘|+|’
+--
+-- When necessary, the user may change the cell reference type via 'unsafeChangeCellRefType'
+--
+-- >>>int = CellRef (Coords 1 1) :: CellRef Int
+-- >>>double = CellRef (Coords 2 5) :: CellRef Double
+-- >>>unsafeChangeCellRefType int |+| double
+-- A1+E2
+newtype CellRef a = CellRef {unCell :: Coords}
+  deriving newtype (Num)
+
+-- | Get a column number from a 'CellRef'
+getCol :: CellRef a -> Int
+getCol (CellRef c) = c & col
+
+-- | Get a row number from a 'CellRef'
+getRow :: CellRef a -> Int
+getRow (CellRef c) = c & row
+
+-- | Apply a function over a column of a coordinate
+overCol :: (Int -> Int) -> Coords -> Coords
+overCol f (Coords row col) = Coords row (f col)
+
+-- | Apply a function over a row of a coordinate
+overRow :: (Int -> Int) -> Coords -> Coords
+overRow f (Coords row col) = Coords (f row) col
+
+-- | Change the type of a cell reference. Use with caution!
+--
+-- The type variables in the @forall@ clause are swapped for the conveniece of type applications
+unsafeChangeCellRefType :: forall b a. CellRef a -> CellRef b
+unsafeChangeCellRefType (CellRef c) = CellRef c
+
+{- FOURMOLU_DISABLE -}
+-- $Formatting
+{- FOURMOLU_ENABLE -}
+
 -- | Index of an input
-type Index = Int
+type InputIndex = Int
 
 -- | Format a single cell depending on its coordinates, index, and data
-type FormatCell = Coords -> Index -> CellData -> X.FormattedCell
+type FormatCell = Coords -> InputIndex -> CellData -> X.FormattedCell
 
--- | Cell with contents, style, column props
+-- | Template of a cell with contents, style, column properties
 data CellTemplate input output = CellTemplate
   { mkOutput :: input -> output
-  , format :: FormatCell
+  , fmtCell :: FormatCell
   , columnsProperties :: Maybe X.ColumnsProperties
   }
 
--- Transforms
-
+-- | Map of coordinates to cell formatting
 type FormattedMap = Map.Map (X.RowIndex, X.ColumnIndex) X.FormattedCell
+
+-- | Transform of a map that maps coordinates to cell formatting
 type FMTransform = FormattedMap -> FormattedMap
+
+-- | Transform of a worksheet
 type WSTransform = X.Worksheet -> X.Worksheet
 
--- | A transform of the map of formats and a transform of a worksheet
+-- | Combined: a transform of a map of formats and a transform of a worksheet
 data Transform = Transform {fmTransform :: FMTransform, wsTransform :: WSTransform}
 
 instance Semigroup Transform where
@@ -134,36 +259,77 @@
   def :: Transform
   def = mempty
 
--- Template
+-- | Make a 'FormatCell' for a single color
+--
+-- @show@ on the input should translate into an @ARGB@ color. See 'XS.Color'
+mkColorStyle :: Show a => a -> FormatCell
+mkColorStyle color _ _ cd =
+  X.def
+    & X.formattedCell .~ dataCell cd
+    & X.formattedFormat
+      .~ ( X.def
+            & X.formatFill
+              ?~ ( X.def
+                    & X.fillPattern
+                      ?~ ( X.def
+                            & ( X.fillPatternFgColor
+                                  ?~ (X.def & X.colorARGB ?~ T.pack (show color))
+                              )
+                            & ( X.fillPatternType
+                                  ?~ X.PatternTypeSolid
+                              )
+                         )
+                 )
+         )
 
+-- | Transform of a formatted cell
+type FCTransform = X.FormattedCell -> X.FormattedCell
+
+-- | Apply 'FCTransform' to a 'FormatCell' to get a new 'FormatCell'
+(+>) :: FormatCell -> FCTransform -> FormatCell
+fc +> ft = \coords idx cd -> ft $ fc coords idx cd
+
+infixl 5 +>
+
+-- | Get a 'FCTransform' with a given horizontal alignment in a cell
+horizontalAlignment :: X.CellHorizontalAlignment -> FCTransform
+horizontalAlignment alignment fc =
+  fc
+    & X.formattedFormat
+      %~ ( \ff ->
+            ff
+              & X.formatAlignment
+                ?~ ( X.def & X.alignmentHorizontal ?~ alignment
+                   )
+         )
+
+{- FOURMOLU_DISABLE -}
+-- $Templates
+{- FOURMOLU_ENABLE -}
+
 -- | Template for multiple cells
 newtype Template input output = Template [CellTemplate input output]
   deriving (Semigroup, Monoid)
 
--- Builder
-
--- | A builder
-newtype Builder input output a = Builder {unBuilder :: StateT Coords (Writer (Template input output)) a}
+-- | Allows to describe how to build a template for a row
+newtype RowBuilder input output a = RowBuilder {unBuilder :: StateT Coords (Writer (Template input output)) a}
   deriving (Functor, Applicative, Monad, MonadState Coords, MonadWriter (Template input output))
 
 -- | Run builder on given coordinates. Get a result and a template
-runBuilder :: Builder input output a -> Coords -> (a, Template input output)
+runBuilder :: RowBuilder input output a -> Coords -> (a, Template input output)
 runBuilder builder coord = runWriter (evalStateT (unBuilder builder) coord)
 
 -- | Run builder on given coordinates. Get a template
-evalBuilder :: Builder input output a -> Coords -> Template input output
+evalBuilder :: RowBuilder input output a -> Coords -> Template input output
 evalBuilder builder coord = snd $ runBuilder builder coord
 
 -- | Run builder on given coordinates. Get a result
-execBuilder :: Builder input output a -> Coords -> a
+execBuilder :: RowBuilder input output a -> Coords -> a
 execBuilder builder coord = fst $ runBuilder builder coord
 
-type RenderTemplate m input output = (Monad m, ToCellData output) => Coords -> Index -> input -> Template input output -> m Transform
-type RenderBuilderInputs m input output a = (Monad m, ToCellData output) => Builder input output a -> [input] -> m (Transform, a)
-type RenderBuilderInput m input output a = (Monad m, ToCellData output) => Builder input output a -> input -> m (Transform, a)
-
--- Render
--- meaning produce a transform
+type RenderTemplate m input output = (Monad m, ToCellData output) => Coords -> InputIndex -> input -> Template input output -> m Transform
+type RenderBuilderInputs m input output a = (Monad m, ToCellData output) => RowBuilder input output a -> [input] -> m (Transform, a)
+type RenderBuilderInput m input output a = (Monad m, ToCellData output) => RowBuilder input output a -> input -> m (Transform, a)
 
 -- | Render a builder with given coords and inputs. Return the result calculated using the topmost row
 renderBuilderInputs :: (Monad m, ToCellData output) => Coords -> RenderTemplate m input output -> RenderBuilderInputs m input output a
@@ -202,7 +368,7 @@
             cd' = toCellData (mkOutput input)
             col' = (col + columnIdx)
             coords' = Coords row col'
-            c = format coords' inputIdx cd'
+            c = fmtCell coords' inputIdx cd'
             fmTransform = Map.insert (fromIntegral row, fromIntegral col') c
             wsTransform
               -- add column width only once
@@ -214,8 +380,11 @@
       [0 ..]
       columns
 
--- Columns
+{- FOURMOLU_DISABLE -}
+-- $Columns
+{- FOURMOLU_ENABLE -}
 
+-- | Properties of a column
 newtype ColumnsProperties = ColumnsProperties {unColumnsProperties :: X.ColumnsProperties}
 
 instance Default ColumnsProperties where
@@ -232,12 +401,9 @@
         , cpBestFit = False
         }
 
--- TODO fix doesn't work for non-first row
--- need to filter the final list
-
--- | Produce a column with a given style and width and get a cell
-columnWidthCell :: forall a input output. Maybe Double -> FormatCell -> (input -> output) -> Builder input output (Cell a)
-columnWidthCell width format mkOutput = do
+-- | A column with a possibly given width and cell format. Returns a cell reference
+columnWidthCell :: forall a input output. Maybe Double -> FormatCell -> (input -> output) -> RowBuilder input output (CellRef a)
+columnWidthCell width fmtCell mkOutput = do
   coords <- get
   let columnsProperties =
         Just $
@@ -246,100 +412,91 @@
             , X.cpMax = coords & col
             , X.cpWidth = width
             }
-  tell (Template [CellTemplate{format, mkOutput, columnsProperties}])
-  cell <- gets Cell
+  tell (Template [CellTemplate{fmtCell, mkOutput, columnsProperties}])
+  cell <- gets CellRef
   modify (\x -> x{col = (x & col) + 1})
   return cell
 
-columnWidth :: ToCellData output => Double -> FormatCell -> (input -> output) -> Builder input CellData (Cell a)
+-- | A column with a given width and cell format. Returns a cell reference
+columnWidth :: ToCellData output => Double -> FormatCell -> (input -> output) -> RowBuilder input CellData (CellRef a)
 columnWidth width fmtCell mkOutput = columnWidthCell (Just width) fmtCell (toCellData . mkOutput)
 
-columnWidth_ :: ToCellData output => Double -> FormatCell -> (input -> output) -> Builder input CellData ()
+-- | A column with a given width and cell format
+columnWidth_ :: ToCellData output => Double -> FormatCell -> (input -> output) -> RowBuilder input CellData ()
 columnWidth_ width fmtCell mkOutput = void (columnWidth width fmtCell mkOutput)
 
--- | Produce a column with a given style and get a cell
-column :: ToCellData output => FormatCell -> (input -> output) -> Builder input CellData (Cell a)
+-- | A column with a given cell format. Returns a cell reference
+column :: ToCellData output => FormatCell -> (input -> output) -> RowBuilder input CellData (CellRef a)
 column fmtCell mkOutput = columnWidthCell Nothing fmtCell (toCellData . mkOutput)
 
--- | Produce a column with a given style
-column_ :: ToCellData output => FormatCell -> (input -> output) -> Builder input CellData ()
+-- | A column with a given cell format
+column_ :: ToCellData output => FormatCell -> (input -> output) -> RowBuilder input CellData ()
 column_ fmtCell mkOutput = void (column fmtCell mkOutput)
 
--- | Produce a transform and a result from inputs and a builder
-composeTransformResult :: forall a input output. ToCellData output => RenderTemplate Identity input output -> Coords -> [input] -> Builder input output a -> (Transform, a)
-composeTransformResult renderTemplate' offset input builder = runIdentity $ renderBuilderInputs offset renderTemplate' builder input
+-- | Produce a transform and a result from a template renderer, inputs, and a builder
+composeTransformAndResult :: forall a input output. ToCellData output => RenderTemplate Identity input output -> Coords -> [input] -> RowBuilder input output a -> (Transform, a)
+composeTransformAndResult renderTemplate' offset input builder = runIdentity $ renderBuilderInputs offset renderTemplate' builder input
 
--- | Produce a result
-defaultTransformResult :: ToCellData output => Coords -> [input] -> Builder input output a -> (Transform, a)
-defaultTransformResult = composeTransformResult renderTemplate
+-- | Produce a result from a default template renderer, inputs, and a builder
+defaultComposeTransformAndResult :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> (Transform, a)
+defaultComposeTransformAndResult = composeTransformAndResult renderTemplate
 
--- TODO
--- Store current sheet info for formulas
+{- FOURMOLU_DISABLE -}
+-- $SheetBuilder
+{- FOURMOLU_ENABLE -}
 
--- | Top monad to compose the results of Builders
+-- | A builder to compose the results of 'RowBuilder's
 newtype SheetBuilder a = SheetBuilder {unSheetBuilder :: Writer Transform a}
   deriving (Functor, Applicative, Monad, MonadWriter Transform)
 
-class Functor a => Discardable a where
-  discard :: a b -> a ()
-
-placeInputs :: ToCellData output => Coords -> [input] -> Builder input output a -> SheetBuilder a
+-- | Starting at given coordinates, place rows of data made from a list of inputs according to a row builder. Return the result of the row builder.
+placeInputs :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> SheetBuilder a
 placeInputs offset inputs b = do
-  let transformResult = defaultTransformResult offset inputs b
+  let transformResult = defaultComposeTransformAndResult offset inputs b
   tell (fst transformResult)
   return (snd transformResult)
 
-placeInput :: ToCellData output => Coords -> input -> Builder input output a -> SheetBuilder a
+-- | Starting at given coordinates, place a row of data made from a single input according to a row builder. Return the result of the row builder.
+placeInput :: ToCellData output => Coords -> input -> RowBuilder input output a -> SheetBuilder a
 placeInput coords input = placeInputs coords [input]
 
-placeInputs_ :: ToCellData output => Coords -> [input] -> Builder input output a -> SheetBuilder ()
+-- | Starting at given coordinates, place rows of data made from a list of inputs according to a row builder.
+placeInputs_ :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> SheetBuilder ()
 placeInputs_ coords inputs b = void (placeInputs coords inputs b)
 
-placeInput_ :: ToCellData output => Coords -> input -> Builder input output a -> SheetBuilder ()
+-- | Starting at given coordinates, place a row of data made from a single input according to a row builder.
+placeInput_ :: ToCellData output => Coords -> input -> RowBuilder input output a -> SheetBuilder ()
 placeInput_ coords input = placeInputs_ coords [input]
 
-composeXlsx :: [(T.Text, SheetBuilder ())] -> X.Xlsx
-composeXlsx sheetBuilders = workBook'
- where
-  getTransform x = execWriter $ unSheetBuilder x
-  workBook = X.formatWorkbook ((\(name, tf') -> (name, (getTransform tf' & fmTransform) X.def)) <$> sheetBuilders) X.def
-  filterWidths ws = ws & X.wsColumnsProperties %~ filter (isJust . X.cpWidth)
-  workBook' =
-    workBook
-      & X.xlSheets
-        %~ \sheets -> zipWith (\x (name, ws) -> (name, (getTransform x & wsTransform) ws & filterWidths)) (snd <$> sheetBuilders) sheets
-
-{- Lib. Formulas -}
+{- FOURMOLU_DISABLE -}
+-- $Expressions
+{- FOURMOLU_ENABLE -}
 
--- | Formula expressions
+-- | Expression syntax
 data Expr t
   = Add (Expr t) (Expr t)
   | Sub (Expr t) (Expr t)
   | Mul (Expr t) (Expr t)
   | Div (Expr t) (Expr t)
+  | Power (Expr t) (Expr t)
   | Function String [Expr t]
   | Range (Expr t) (Expr t)
-  | ExprCell (Cell t)
-  deriving (Functor)
-
--- | Change phantom type of an Expr
-ex' :: forall b a. Expr a -> Expr b
-ex' = toExpr
+  | ExprCell (CellRef t)
 
 -- | Something that can be turned into an expression
 class ToExpr v where
   toExpr :: v -> Expr t
 
-instance ToExpr (Cell a) where
-  toExpr :: Cell a -> Expr t
-  toExpr (Cell c) = ExprCell (Cell c)
+instance ToExpr (CellRef a) where
+  toExpr :: CellRef a -> Expr t
+  toExpr (CellRef c) = ExprCell (CellRef c)
 
 instance ToExpr Coords where
   toExpr :: Coords -> Expr t
-  toExpr c = ExprCell (Cell c)
+  toExpr c = ExprCell (CellRef c)
 
-toExprCell :: Cell a -> Coords
-toExprCell (Cell c1) = c1
+toExprCell :: CellRef a -> Coords
+toExprCell (CellRef c1) = c1
 
 instance ToExpr (Expr a) where
   toExpr :: Expr a -> Expr b
@@ -347,9 +504,10 @@
   toExpr (Sub l r) = Sub (toExpr l) (toExpr r)
   toExpr (Mul l r) = Mul (toExpr l) (toExpr r)
   toExpr (Div l r) = Div (toExpr l) (toExpr r)
+  toExpr (Power b p) = Power (toExpr b) (toExpr p)
   toExpr (Function name args) = Function name (toExpr <$> args)
   toExpr (Range l r) = Range (toExpr l) (toExpr r)
-  toExpr (ExprCell (Cell c)) = ExprCell (Cell c)
+  toExpr (ExprCell (CellRef c)) = ExprCell (CellRef c)
 
 showOp2 :: (Show a, Show b) => String -> a -> b -> String
 showOp2 operator c1 c2 = show c1 <> operator <> show c2
@@ -361,38 +519,41 @@
 mkNumOp2 = mkOp2
 
 -- | Assemble a range expression
-(|:|) :: Cell a -> Cell b -> Expr c
+(|:|) :: CellRef a -> CellRef b -> Expr c
 (|:|) = mkOp2 Range
 
 infixr 5 |:|
 
+-- | A type for arithmetic operators
+type ArithmeticOperator a b c = (Num a, ToExpr (b a), ToExpr (c a)) => b a -> c a -> Expr a
+
 -- | Assemble an addition expression
-(|+|) :: Num a => Expr a -> Expr a -> Expr a
+(|+|) :: ArithmeticOperator a b c
 (|+|) = mkNumOp2 Add
 
 infixl 6 |+|
 
 -- | Assemble a subtraction expression
-(|-|) :: Num a => Expr a -> Expr a -> Expr a
+(|-|) :: ArithmeticOperator a b c
 (|-|) = mkNumOp2 Sub
 
 infixl 6 |-|
 
 -- | Assemble a division expression
-(|/|) :: Num a => Expr a -> Expr a -> Expr a
+(|/|) :: ArithmeticOperator a b c
 (|/|) = mkNumOp2 Div
 
 infixl 7 |/|
 
 -- | Assemble a multiplication expression
-(|*|) :: Num a => Expr a -> Expr a -> Expr a
+(|*|) :: ArithmeticOperator a b c
 (|*|) = mkNumOp2 Mul
 
 infixl 6 |*|
 
 -- | Assemble a multiplication expression
-(|^|) :: Num a => Expr a -> Expr a -> Expr a
-(|^|) = mkNumOp2 Mul
+(|^|) :: ArithmeticOperator a b c
+(|^|) = mkNumOp2 Power
 
 infixr 8 |^|
 
@@ -408,100 +569,22 @@
   show (Sub c1 c2) = showOp2 "-" c1 c2
   show (Mul c1 c2) = showOp2 "*" c1 c2
   show (Div c1 c2) = showOp2 "/" c1 c2
+  show (Power c1 c2) = showOp2 "^" c1 c2
   show (Range c1 c2) = showOp2 ":" c1 c2
-  show (ExprCell (Cell e)) = show e
+  show (ExprCell (CellRef e)) = show e
   show (Function n as) = n <> "(" <> intercalate "," (show <$> as) <> ")"
 
--- | Coordinates of a cell with a given phantom type
-newtype Cell a = Cell {unCell :: Coords} deriving (Functor)
-
-cellCol :: Cell a -> Int
-cellCol (Cell c) = c & col
-
-cellRow :: Cell a -> Int
-cellRow (Cell c) = c & row
-
-overCol :: (Int -> Int) -> Coords -> Coords
-overCol f (Coords row col) = Coords row (f col)
-
-overRow :: (Int -> Int) -> Coords -> Coords
-overRow f (Coords row col) = Coords (f row) col
-
-instance Num (Cell a) where
-  (+) :: Cell a -> Cell a -> Cell a
-  (+) (Cell c1) (Cell c2) = Cell (c1 + c2)
-  (*) :: Cell a -> Cell a -> Cell a
-  (*) (Cell c1) (Cell c2) = Cell (c1 * c2)
-  (-) :: Cell a -> Cell a -> Cell a
-  (-) (Cell c1) (Cell c2) = Cell (c1 - c2)
-  abs :: Cell a -> Cell a
-  abs (Cell c1) = Cell (abs c1)
-  signum :: Cell a -> Cell a
-  signum (Cell c1) = Cell (signum c1)
-  fromInteger :: Integer -> Cell a
-  fromInteger x = Cell (fromInteger x)
-
--- | Convert a typed cell to an expression
-ex :: Cell a -> Expr a
-ex = toExpr
-
-{- Lib.Example.Typechecks
->>>str = ex (Cell (Coords 1 1)) :: Expr String
->>> str |+| str
-No instance for (Num String) arising from a use of `|+|'
-In the expression: str |+| str
-NOW In an equation for `it_a1TViD': it_a1TViD = str |+| str
-
->>>int = ex (Cell (Coords 1 1)) :: Expr Int
->>>double = ex (Cell (Coords 2 5)) :: Expr Double
->>>ex' int |+| double
-A1+E2
--}
-
-mkColorStyle :: T.Text -> FormatCell
-mkColorStyle color _ _ cd =
-  X.def
-    & X.formattedCell .~ dataCell cd
-    & X.formattedFormat
-      .~ ( X.def
-            & X.formatFill
-              ?~ ( X.def
-                    & X.fillPattern
-                      ?~ ( X.def
-                            & ( X.fillPatternFgColor
-                                  ?~ (X.def & X.colorARGB ?~ color)
-                              )
-                            & ( X.fillPatternType
-                                  ?~ X.PatternTypeSolid
-                              )
-                         )
-                 )
-         )
-
-type FCTransform = X.FormattedCell -> X.FormattedCell
-
-infixl 5 <|
-(<|) :: FCTransform -> FormatCell -> FormatCell
-f <| fc = \coords idx cd -> f $ fc coords idx cd
-
-horizontalAlignment :: X.CellHorizontalAlignment -> FCTransform
-horizontalAlignment alignment fc =
-  fc
-    & X.formattedFormat
-      %~ ( \ff ->
-            ff
-              & X.formatAlignment
-                ?~ ( X.def & X.alignmentHorizontal ?~ alignment
-                   )
-         )
+{- FOURMOLU_DISABLE -}
+-- $Cells
+{- FOURMOLU_ENABLE -}
 
--- | A union of some Cell components
+-- | A union of what can be inside a cell
 data CellData
   = CellFormula X.CellFormula
   | CellValue X.CellValue
   | CellComment X.Comment
 
--- | Convert some Cell component into a cell
+-- | Convert some CellRef component into a cell
 dataCell :: CellData -> X.Cell
 dataCell cd =
   X.def
@@ -510,6 +593,7 @@
       CellFormula d -> X.cellFormula ?~ d
       CellComment d -> X.cellComment ?~ d
 
+-- | Something that can be turned into 'CellData'
 class ToCellData a where
   toCellData :: a -> CellData
 
@@ -542,3 +626,19 @@
         , X._cellfCalculate = True
         , X._cellfExpression = X.NormalFormula $ X.Formula $ T.pack $ show e
         }
+
+{- FOURMOLU_DISABLE -}
+-- $Xlsx
+{- FOURMOLU_ENABLE -}
+
+-- | Compose an @xlsx@ from a list of sheet names and builders
+composeXlsx :: [(T.Text, SheetBuilder ())] -> X.Xlsx
+composeXlsx sheetBuilders = workBook'
+ where
+  getTransform x = execWriter $ unSheetBuilder x
+  workBook = X.formatWorkbook ((\(name, tf') -> (name, (getTransform tf' & fmTransform) X.def)) <$> sheetBuilders) X.def
+  filterWidths ws = ws & X.wsColumnsProperties %~ filter (isJust . X.cpWidth)
+  workBook' =
+    workBook
+      & X.xlSheets
+        %~ \sheets -> zipWith (\x (name, ws) -> (name, (getTransform x & wsTransform) ws & filterWidths)) (snd <$> sheetBuilders) sheets
diff --git a/test/Docs.hs b/test/Docs.hs
new file mode 100644
--- /dev/null
+++ b/test/Docs.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# OPTIONS_GHC -Wno-unused-top-binds #-}
+
+{- FOURMOLU_ENABLE -}
+
+module Main (main) where
+
+import Control.Monad (when)
+import GHC.IO.Exception (ExitCode (..))
+import System.Exit (exitFailure, exitSuccess)
+import System.Process.Typed (runProcess, shell)
+
+main :: IO ()
+main = do
+  print "Converting README"
+  s1 <-
+    runProcess $
+      shell $
+        unlines
+          [ "lima hs2md -f example/app/Main.hs"
+          , "touch README.tmp"
+          , "cat README/Intro.md >> README.tmp"
+          , "printf \"\n\" >> README.tmp"
+          , "cat example/app/Main.hs.md >> README.tmp && rm example/app/Main.hs.md"
+          , "printf \"\n\" >> README.tmp"
+          , "cat README/Outro.md >> README.tmp"
+          , "mv README.tmp README.md"
+          ]
+  when (s1 /= ExitSuccess) (print "Failed to generate README.md. Exiting ..." >> exitFailure)
+  exitSuccess
