diff --git a/.eslintrc.js b/.eslintrc.js
index 11e409fdd..05cb957b3 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -250,7 +250,7 @@ module.exports = {
         "no-unneeded-ternary": "off",
         "no-unused-expressions": "off",
         "no-unused-vars": "off",
-        "@typescript-eslint/no-unused-vars": ["error"],
+        "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
         "no-useless-backreference": "error",
         "no-useless-call": "off",
         "no-useless-computed-key": "error",
diff --git a/rollup.config.js b/rollup.config.js
index ef40aedfd..e4c07cdb5 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -19,7 +19,7 @@ function commonPlugins() {
         },
       ],
     }),
-    typescript(),
+    typescript({ noEmitOnError: false }),
   ]
 }
 
diff --git a/src/js/editor/post/post-inserter.js b/src/js/editor/post/post-inserter.ts
similarity index 71%
rename from src/js/editor/post/post-inserter.js
rename to src/js/editor/post/post-inserter.ts
index 46bce5489..0b7e41e2a 100644
--- a/src/js/editor/post/post-inserter.js
+++ b/src/js/editor/post/post-inserter.ts
@@ -1,20 +1,32 @@
-import assert from 'mobiledoc-kit/utils/assert'
-import {
-  MARKUP_SECTION_TYPE,
-  LIST_SECTION_TYPE,
-  POST_TYPE,
-  CARD_TYPE,
-  IMAGE_SECTION_TYPE,
-  LIST_ITEM_TYPE,
-} from 'mobiledoc-kit/models/types'
-
-const MARKERABLE = 'markerable',
-  NESTED_MARKERABLE = 'nested_markerable',
-  NON_MARKERABLE = 'non_markerable'
+import assert from '../../utils/assert'
+import { Option } from '../../utils/types'
+import { Type } from '../../models/types'
+import Post from '../../models/post'
+import Editor from '../editor'
+import PostNodeBuilder from '../../models/post-node-builder'
+import { Position } from '../../utils/cursor'
+import Section, { WithParent } from '../../models/_section'
+import MarkupSection from '../../models/markup-section'
+import ListSection from '../../models/list-section'
+import ListItem from '../../models/list-item'
+import Card from '../../models/card'
+import Image from '../../models/image'
+import Markerable from '../../models/_markerable'
+import { Cloneable } from '../../models/_cloneable'
+import HasChildSections, { hasChildSections } from '../../models/_has-child-sections'
+
+const MARKERABLE = 'markerable'
+const NESTED_MARKERABLE = 'nested_markerable'
+const NON_MARKERABLE = 'non_markerable'
 
 class Visitor {
-  constructor(inserter, cursorPosition) {
-    let { postEditor, post } = inserter
+  postEditor: Editor
+  builder: PostNodeBuilder
+  _post: Post
+  _hasInsertedFirstLeafSection: boolean
+  _cursorPosition!: Position
+
+  constructor({ postEditor, post }: Inserter, cursorPosition: Position) {
     this.postEditor = postEditor
     this._post = post
     this.cursorPosition = cursorPosition
@@ -32,7 +44,7 @@ class Visitor {
     this.postEditor.setRange(position)
   }
 
-  visit(node) {
+  visit(node: Post | Section) {
     let method = node.type
     assert(`Cannot visit node of type ${node.type}`, !!this[method])
     this[method](node)
@@ -51,7 +63,7 @@ class Visitor {
   }
 
   get cursorSection() {
-    return this.cursorPosition.section
+    return this.cursorPosition.section!
   }
 
   get cursorOffset() {
@@ -62,21 +74,22 @@ class Visitor {
     return this.cursorSection.isNested
   }
 
-  [POST_TYPE](node) {
-    if (this.cursorSection.isBlank && !this._isNested) {
+  [Type.POST](node: Post) {
+    let { cursorSection } = this
+    if (cursorSection.isBlank && !cursorSection.isNested) {
       // replace blank section with entire post
       let newSections = node.sections.map(s => s.clone())
-      this._replaceSection(this.cursorSection, newSections)
+      this._replaceSection(cursorSection as Section & WithParent<HasChildSections>, newSections)
     } else {
       node.sections.forEach(section => this.visit(section))
     }
   }
 
-  [MARKUP_SECTION_TYPE](node) {
+  [Type.MARKUP_SECTION](node: MarkupSection) {
     this[MARKERABLE](node)
   }
 
-  [LIST_SECTION_TYPE](node) {
+  [Type.LIST_SECTION](node: ListSection) {
     let hasNext = !!node.next
     node.items.forEach(item => this.visit(item))
 
@@ -85,19 +98,19 @@ class Visitor {
     }
   }
 
-  [LIST_ITEM_TYPE](node) {
+  [Type.LIST_ITEM](node: ListItem) {
     this[NESTED_MARKERABLE](node)
   }
 
-  [CARD_TYPE](node) {
+  [Type.CARD](node: Card) {
     this[NON_MARKERABLE](node)
   }
 
-  [IMAGE_SECTION_TYPE](node) {
+  [Type.IMAGE_SECTION](node: Image) {
     this[NON_MARKERABLE](node)
   }
 
-  [NON_MARKERABLE](section) {
+  [NON_MARKERABLE](section: Cloneable<Section>) {
     if (this._isNested) {
       this._breakNestedAtCursor()
     } else if (!this.cursorSection.isBlank) {
@@ -107,7 +120,7 @@ class Visitor {
     this._insertLeafSection(section)
   }
 
-  [MARKERABLE](section) {
+  [MARKERABLE](section: Markerable) {
     if (this._canMergeSection(section)) {
       this._mergeSection(section)
     } else if (this._isNested && this._isMarkerable) {
@@ -116,7 +129,7 @@ class Visitor {
       this._breakAtCursor()
 
       // Advance the cursor to the head of the blank list item
-      let nextPosition = this.cursorSection.next.headPosition()
+      let nextPosition = this.cursorSection.next!.headPosition()
       this.cursorPosition = nextPosition
 
       // Merge this section onto the list item
@@ -127,22 +140,22 @@ class Visitor {
     }
   }
 
-  [NESTED_MARKERABLE](section) {
+  [NESTED_MARKERABLE](section: Markerable) {
     if (this._canMergeSection(section)) {
       this._mergeSection(section)
       return
     }
 
-    section = this._isNested ? section : this._wrapNestedSection(section)
+    let insertedSection = this._isNested ? section : this._wrapNestedSection(section as ListItem)
     this._breakAtCursor()
-    this._insertLeafSection(section)
+    this._insertLeafSection(insertedSection)
   }
 
   // break out of a nested cursor position
   _breakNestedAtCursor() {
     assert('Cannot call _breakNestedAtCursor if not nested', this._isNested)
 
-    let parent = this.cursorSection.parent
+    let parent = this.cursorSection.parent!
     let cursorAtEndOfList = this.cursorPosition.isEqual(parent.tailPosition())
 
     if (cursorAtEndOfList) {
@@ -160,6 +173,7 @@ class Visitor {
     let list = this.cursorSection.parent,
       position = this.cursorPosition,
       blank = this.builder.createMarkupSection()
+
     let [pre, post] = this.postEditor._splitListAtPosition(list, position)
 
     let collection = this._post.sections,
@@ -168,14 +182,14 @@ class Visitor {
     return [pre, blank, post]
   }
 
-  _wrapNestedSection(section) {
-    let tagName = section.parent.tagName
+  _wrapNestedSection(section: ListItem) {
+    let tagName = section.parent!.tagName
     let parent = this.builder.createListSection(tagName)
     parent.items.append(section.clone())
     return parent
   }
 
-  _mergeSection(section) {
+  _mergeSection(section: Markerable) {
     assert('Can only merge markerable sections', this._isMarkerable && section.isMarkerable)
     this._hasInsertedFirstLeafSection = true
 
@@ -213,8 +227,8 @@ class Visitor {
     this.cursorPosition = pre.tailPosition()
   }
 
-  _replaceSection(section, newSections) {
-    assert('Cannot replace section that does not have parent.sections', section.parent && section.parent.sections)
+  _replaceSection(section: Section, newSections: Section[]) {
+    assert('Cannot replace section that does not have parent.sections', hasChildSections(section.parent!))
     assert('Must pass enumerable to _replaceSection', !!newSections.forEach)
 
     let collection = section.parent.sections
@@ -228,7 +242,11 @@ class Visitor {
     this.cursorPosition = lastSection.tailPosition()
   }
 
-  _insertSectionBefore(section, reference) {
+  _insertSectionBefore(section: Section, reference?: Option<Section>) {
+    assert(
+      'Cannot insert into section that does not have parent.sections',
+      hasChildSections(this.cursorSection.parent!)
+    )
     let collection = this.cursorSection.parent.sections
     this.postEditor.insertSectionBefore(collection, section, reference)
 
@@ -237,7 +255,7 @@ class Visitor {
 
   // Insert a section after the parent section.
   // E.g., add a markup section after a list section
-  _insertSectionAfter(section, parent) {
+  _insertSectionAfter(section: Section, parent: Section) {
     assert('Cannot _insertSectionAfter nested section', !parent.isNested)
     let reference = parent.next
     let collection = this._post.sections
@@ -245,7 +263,7 @@ class Visitor {
     this.cursorPosition = section.tailPosition()
   }
 
-  _insertLeafSection(section) {
+  _insertLeafSection(section: Cloneable<Section>) {
     assert('Can only _insertLeafSection when cursor is at end of section', this.cursorPosition.isTail())
 
     this._hasInsertedFirstLeafSection = true
@@ -267,12 +285,15 @@ class Visitor {
 }
 
 export default class Inserter {
-  constructor(postEditor, post) {
+  postEditor: Editor
+  post: Post
+
+  constructor(postEditor: Editor, post: Post) {
     this.postEditor = postEditor
     this.post = post
   }
 
-  insert(cursorPosition, newPost) {
+  insert(cursorPosition: Position, newPost: Post) {
     let visitor = new Visitor(this, cursorPosition)
     if (!newPost.isBlank) {
       visitor.visit(newPost)
diff --git a/src/js/models/_cloneable.ts b/src/js/models/_cloneable.ts
new file mode 100644
index 000000000..43dd18fa9
--- /dev/null
+++ b/src/js/models/_cloneable.ts
@@ -0,0 +1,13 @@
+import Section from './_section'
+
+export type Cloneable<T> = T & {
+  clone(): Cloneable<T>
+}
+
+export function expectCloneable<T extends Section>(section: T): Cloneable<T> {
+  if (!('clone' in section)) {
+    throw new Error('Expected section to be cloneable')
+  }
+
+  return section as Cloneable<T>
+}
diff --git a/src/js/models/_markerable.ts b/src/js/models/_markerable.ts
index 101812e66..6f52e9025 100644
--- a/src/js/models/_markerable.ts
+++ b/src/js/models/_markerable.ts
@@ -8,14 +8,16 @@ import Section from './_section'
 import Marker from './marker'
 import { tagNameable } from './_tag-nameable'
 import { Type } from './types'
+import { Cloneable } from './_cloneable'
+import Markuperable from '../utils/markuperable'
 
 type MarkerableType = Type.LIST_ITEM | Type.MARKUP_SECTION
 
-export default abstract class Markerable extends tagNameable(Section) {
+export default abstract class Markerable extends tagNameable(Section) implements Cloneable<Markerable> {
   type: MarkerableType
-  markers: LinkedList<Marker>
+  markers: LinkedList<Markuperable>
 
-  constructor(type: MarkerableType, tagName: string, markers: Marker[] = []) {
+  constructor(type: MarkerableType, tagName: string, markers: Markuperable[] = []) {
     super(type)
     this.type = type
     this.isMarkerable = true
@@ -70,7 +72,7 @@ export default abstract class Markerable extends tagNameable(Section) {
    *
    * @return {Number} The offset relative to the start of this section
    */
-  offsetOfMarker(marker: Marker, markerOffset = 0) {
+  offsetOfMarker(marker: Markuperable, markerOffset = 0) {
     assert(`Cannot get offsetOfMarker for marker that is not child of this`, marker.section === this)
 
     // FIXME it is possible, when we get a cursor position before having finished reparsing,
@@ -112,7 +114,7 @@ export default abstract class Markerable extends tagNameable(Section) {
     return [beforeSection, afterSection]
   }
 
-  abstract splitAtMarker(marker: Marker, offset: number): [Section, Section]
+  abstract splitAtMarker(marker: Markuperable, offset: number): [Section, Section]
 
   /**
    * Split this section's marker (if any) at the given offset, so that
@@ -128,7 +130,7 @@ export default abstract class Markerable extends tagNameable(Section) {
     let markerOffset
     let len = 0
     let currentMarker = this.markers.head
-    let edit: { added: Marker[]; removed: Marker[] } = { added: [], removed: [] }
+    let edit: { added: Markuperable[]; removed: Markuperable[] } = { added: [], removed: [] }
 
     if (!currentMarker) {
       let blankMarker = this.builder.createMarker()
@@ -181,7 +183,7 @@ export default abstract class Markerable extends tagNameable(Section) {
 
   markerPositionAtOffset(offset: number) {
     let currentOffset = 0
-    let currentMarker: Marker | null = null
+    let currentMarker: Markuperable | null = null
     let remaining = offset
     this.markers.detect(marker => {
       currentOffset = Math.min(remaining, marker.length)
@@ -211,7 +213,7 @@ export default abstract class Markerable extends tagNameable(Section) {
   markersFor(headOffset: number, tailOffset: number) {
     const range = Range.create(this, headOffset, this, tailOffset)
 
-    let markers: Marker[] = []
+    let markers: Markuperable[] = []
     this._markersInRange(range, (marker, { markerHead, markerTail, isContained }) => {
       const cloned = marker.clone()
       if (!isContained) {
@@ -237,7 +239,7 @@ export default abstract class Markerable extends tagNameable(Section) {
   // for each marker that is wholly or partially contained in the range.
   _markersInRange(
     range: Range,
-    callback: (marker: Marker, info: { markerHead: number; markerTail: number; isContained: boolean }) => void
+    callback: (marker: Markuperable, info: { markerHead: number; markerTail: number; isContained: boolean }) => void
   ) {
     const { head, tail } = range
     assert(
@@ -274,7 +276,7 @@ export default abstract class Markerable extends tagNameable(Section) {
   // mutates this by appending the other section's (cloned) markers to it
   join(otherSection: Markerable) {
     let beforeMarker = this.markers.tail
-    let afterMarker: Marker | null = null
+    let afterMarker: Markuperable | null = null
 
     otherSection.markers.forEach(m => {
       if (!m.isBlank) {
diff --git a/src/js/models/_section.ts b/src/js/models/_section.ts
index accc29576..0bde1a7d5 100644
--- a/src/js/models/_section.ts
+++ b/src/js/models/_section.ts
@@ -1,13 +1,18 @@
 import LinkedItem from '../utils/linked-item'
 import assert from '../utils/assert'
+import { Option } from '../utils/types'
 import Position from '../utils/cursor/position'
 import Range from '../utils/cursor/range'
-import Marker from './marker'
 import RenderNode from './render-node'
 import Post from './post'
 import { isListSection } from './is-list-section'
 import PostNodeBuilder from './post-node-builder'
 import { Type } from './types'
+import Markuperable from '../utils/markuperable'
+
+export interface WithParent<T> {
+  parent: Option<T>
+}
 
 export default class Section extends LinkedItem {
   type: Type
@@ -20,10 +25,10 @@ export default class Section extends LinkedItem {
   isLeafSection = true
   isCardSection = false
 
-  post?: Post | null
-  renderNode: RenderNode | null = null
+  post?: Option<Post>
+  renderNode!: RenderNode
 
-  parent: Section | null = null
+  parent: Option<Section> = null
   builder!: PostNodeBuilder
 
   constructor(type: Type) {
@@ -81,7 +86,7 @@ export default class Section extends LinkedItem {
    */
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   splitMarkerAtOffset(_offset: number) {
-    let blankEdit: { added: Marker[]; removed: Marker[] } = { added: [], removed: [] }
+    let blankEdit: { added: Markuperable[]; removed: Markuperable[] } = { added: [], removed: [] }
     return blankEdit
   }
 
diff --git a/src/js/models/_tag-nameable.ts b/src/js/models/_tag-nameable.ts
index d6d153f9a..a5ce323f3 100644
--- a/src/js/models/_tag-nameable.ts
+++ b/src/js/models/_tag-nameable.ts
@@ -28,3 +28,7 @@ export function tagNameable(Base: Constructor<Section>) {
 
   return TagNameable
 }
+
+export function isTagNameable(section: Section): section is Section & TagNameable {
+  return 'tagName' in section
+}
diff --git a/src/js/models/atom-node.ts b/src/js/models/atom-node.ts
index 62ac3c5d7..68c8902c0 100644
--- a/src/js/models/atom-node.ts
+++ b/src/js/models/atom-node.ts
@@ -55,7 +55,7 @@ export default class AtomNode {
     return {
       name: this.atom.name,
       onTeardown: (callback: TeardownCallback) => (this._teardownCallback = callback),
-      save: (value: unknown, payload = {}) => {
+      save: (value: string, payload = {}) => {
         this.model.value = value
         this.model.payload = payload
 
diff --git a/src/js/models/atom.ts b/src/js/models/atom.ts
index 41101a26f..b3d226952 100644
--- a/src/js/models/atom.ts
+++ b/src/js/models/atom.ts
@@ -1,8 +1,8 @@
 import { Type } from './types'
 import Markuperable from '../utils/markuperable'
 import assert from '../utils/assert'
-import Marker from './marker'
 import Markup from './markup'
+import PostNodeBuilder, { PostNode } from './post-node-builder'
 
 const ATOM_LENGTH = 1
 
@@ -11,14 +11,14 @@ export default class Atom extends Markuperable {
   isAtom = true
 
   name: string
-  value: unknown
+  value: string
   text: string
   payload: {}
 
   markups: Markup[]
-  builder: any
+  builder!: PostNodeBuilder
 
-  constructor(name: string, value: unknown, payload: {}, markups: Markup[] = []) {
+  constructor(name: string, value: string, payload: {}, markups: Markup[] = []) {
     super()
     this.name = name
     this.value = value
@@ -55,7 +55,7 @@ export default class Atom extends Markuperable {
   }
 
   split(offset = 0, endOffset = offset) {
-    let markers: Marker[] = []
+    let markers: Markuperable[] = []
 
     if (endOffset === 0) {
       markers.push(this.builder.createMarker('', this.markups.slice()))
@@ -70,13 +70,13 @@ export default class Atom extends Markuperable {
     return markers
   }
 
-  splitAtOffset(offset: number) {
+  splitAtOffset(offset: number): [Markuperable, Markuperable] {
     assert('Cannot split a marker at an offset > its length', offset <= this.length)
 
     let { builder } = this
     let clone = this.clone()
     let blankMarker = builder.createMarker('')
-    let pre: Marker, post: Marker
+    let pre: Markuperable, post: Markuperable
 
     if (offset === 0) {
       ;[pre, post] = [blankMarker, clone]
@@ -93,3 +93,7 @@ export default class Atom extends Markuperable {
     return [pre, post]
   }
 }
+
+export function isAtom(postNode: PostNode): postNode is Atom {
+  return postNode.type === Type.ATOM
+}
diff --git a/src/js/models/image.ts b/src/js/models/image.ts
index 3911ce66c..b54963522 100644
--- a/src/js/models/image.ts
+++ b/src/js/models/image.ts
@@ -9,6 +9,10 @@ export default class Image extends Section {
     super(Type.IMAGE_SECTION)
   }
 
+  clone() {
+    return this.builder.createImageSection(this.src)
+  }
+
   canJoin() {
     return false
   }
diff --git a/src/js/models/list-item.ts b/src/js/models/list-item.ts
index bfd1674a0..32da2043b 100644
--- a/src/js/models/list-item.ts
+++ b/src/js/models/list-item.ts
@@ -4,7 +4,10 @@ import { normalizeTagName } from '../utils/dom-utils'
 import { contains } from '../utils/array-utils'
 import Section from './_section'
 import { expect } from '../utils/assert'
+import { Option } from '../utils/types'
 import Marker from './marker'
+import ListSection from './list-section'
+import Markuperable from '../utils/markuperable'
 
 export const VALID_LIST_ITEM_TAGNAMES = ['li'].map(normalizeTagName)
 
@@ -12,8 +15,9 @@ export default class ListItem extends Markerable {
   isListItem = true
   isNested = true
   section: Section | null = null
+  parent!: Option<ListSection>
 
-  constructor(tagName: string, markers: Marker[] = []) {
+  constructor(tagName: string, markers: Markuperable[] = []) {
     super(Type.LIST_ITEM, tagName, markers)
   }
 
diff --git a/src/js/models/list-section.ts b/src/js/models/list-section.ts
index b05667cd3..78d9c5d49 100644
--- a/src/js/models/list-section.ts
+++ b/src/js/models/list-section.ts
@@ -21,7 +21,7 @@ export default class ListSection extends attributable(tagNameable(Section)) impl
   items: LinkedList<ListItem>
   sections: LinkedList<ListItem>
 
-  constructor(tagName = DEFAULT_TAG_NAME, items = [], attributes = {}) {
+  constructor(tagName = DEFAULT_TAG_NAME, items: ListItem[] = [], attributes = {}) {
     super(LIST_SECTION_TYPE)
     this.tagName = tagName
 
@@ -43,7 +43,7 @@ export default class ListSection extends attributable(tagNameable(Section)) impl
     return false
   }
 
-  isValidTagName(normalizedTagName) {
+  isValidTagName(normalizedTagName: string) {
     return contains(VALID_LIST_SECTION_TAGNAMES, normalizedTagName)
   }
 
diff --git a/src/js/models/marker.ts b/src/js/models/marker.ts
index 6b626248f..c771b9a61 100644
--- a/src/js/models/marker.ts
+++ b/src/js/models/marker.ts
@@ -3,8 +3,8 @@ import Markuperable from '../utils/markuperable'
 import assert from '../utils/assert'
 import { Type } from './types'
 import Markup from './markup'
-import Markerable from './_markerable'
 import RenderNode from './render-node'
+import PostNodeBuilder, { PostNode } from './post-node-builder'
 
 // Unicode uses a pair of "surrogate" characters" (a high- and low-surrogate)
 // to encode characters outside the basic multilingual plane (like emoji and
@@ -22,10 +22,8 @@ export default class Marker extends Markuperable {
 
   value: string
 
-  builder: any
+  builder!: PostNodeBuilder
   markups: Markup[] = []
-  section: Markerable | null = null
-  parent: Markerable | null = null
   renderNode: RenderNode | null = null
 
   constructor(value = '', markups: Markup[] = []) {
@@ -48,10 +46,6 @@ export default class Marker extends Markuperable {
     return this.length === 0
   }
 
-  charAt(offset: number) {
-    return this.value.slice(offset, offset + 1)
-  }
-
   /**
    * A marker's text is equal to its value.
    * Compare with an Atom which distinguishes between text and value
@@ -93,7 +87,7 @@ export default class Marker extends Markuperable {
   }
 
   split(offset = 0, endOffset = this.length) {
-    let markers = [
+    let markers: [Marker, Marker, Marker] = [
       this.builder.createMarker(this.value.substring(0, offset)),
       this.builder.createMarker(this.value.substring(offset, endOffset)),
       this.builder.createMarker(this.value.substring(endOffset)),
@@ -106,7 +100,7 @@ export default class Marker extends Markuperable {
   /**
    * @return {Array} 2 markers either or both of which could be blank
    */
-  splitAtOffset(offset: number) {
+  splitAtOffset(offset: number): [Marker, Marker] {
     assert('Cannot split a marker at an offset > its length', offset <= this.length)
     let { value, builder } = this
 
@@ -121,3 +115,7 @@ export default class Marker extends Markuperable {
     return [pre, post]
   }
 }
+
+export function isMarker(postNode: PostNode): postNode is Marker {
+  return postNode.type === Type.MARKER
+}
diff --git a/src/js/models/markup-section.ts b/src/js/models/markup-section.ts
index 832084ff1..ec00fb806 100644
--- a/src/js/models/markup-section.ts
+++ b/src/js/models/markup-section.ts
@@ -5,6 +5,8 @@ import { normalizeTagName } from '../utils/dom-utils'
 import { contains } from '../utils/array-utils'
 import { entries } from '../utils/object-utils'
 import Marker from './marker'
+import Markuperable from '../utils/markuperable'
+import Section from './_section'
 
 // valid values of `tagName` for a MarkupSection
 export const VALID_MARKUP_SECTION_TAGNAMES = ['aside', 'blockquote', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].map(
@@ -23,7 +25,9 @@ export default class MarkupSection extends attributable(Markerable) {
   isMarkupSection = true
   isGenerated = false
 
-  constructor(tagName = DEFAULT_TAG_NAME, markers: Marker[] = [], attributes = {}) {
+  _inferredTagName: boolean = false
+
+  constructor(tagName = DEFAULT_TAG_NAME, markers: Markuperable[] = [], attributes = {}) {
     super(MARKUP_SECTION_TYPE, tagName, markers)
     entries(attributes).forEach(([k, v]) => this.setAttribute(k, v))
   }
@@ -41,3 +45,11 @@ export default class MarkupSection extends attributable(Markerable) {
     return this._redistributeMarkers(beforeSection, afterSection, marker, offset)
   }
 }
+
+export function isMarkupSection(section: Section): section is MarkupSection {
+  return (section as MarkupSection).isMarkupSection
+}
+
+export function hasInferredTagName(section: Section): section is MarkupSection {
+  return isMarkupSection(section) && section._inferredTagName
+}
diff --git a/src/js/models/post-node-builder.ts b/src/js/models/post-node-builder.ts
index f3e39c210..6dc81d46a 100644
--- a/src/js/models/post-node-builder.ts
+++ b/src/js/models/post-node-builder.ts
@@ -15,6 +15,8 @@ import { DEFAULT_TAG_NAME as DEFAULT_LIST_SECTION_TAG_NAME } from './list-sectio
 import { normalizeTagName } from '../utils/dom-utils'
 import { objectToSortedKVArray } from '../utils/array-utils'
 import assert from '../utils/assert'
+import Markuperable from '../utils/markuperable'
+import Section from './_section'
 
 function cacheKey(tagName, attributes) {
   return `${normalizeTagName(tagName)}-${objectToSortedKVArray(attributes).join('-')}`
@@ -52,14 +54,14 @@ export default class PostNodeBuilder {
     return post
   }
 
-  createMarkerableSection(type: Type.LIST_ITEM, tagName: string, markers: Marker[]): ListItem
-  createMarkerableSection(type: Type.MARKUP_SECTION, tagName: string, markers: Marker[]): MarkupSection
+  createMarkerableSection(type: Type.LIST_ITEM, tagName: string, markers: Markuperable[]): ListItem
+  createMarkerableSection(type: Type.MARKUP_SECTION, tagName: string, markers: Markuperable[]): MarkupSection
   createMarkerableSection(
     type: Exclude<Type, Type.LIST_ITEM & Type.MARKUP_SECTION>,
     tagName: string,
-    markers: Marker[]
+    markers: Markuperable[]
   ): never
-  createMarkerableSection(type: Type, tagName: string, markers: Marker[] = []) {
+  createMarkerableSection(type: Type, tagName: string, markers: Markuperable[] = []) {
     switch (type) {
       case LIST_ITEM_TYPE:
         return this.createListItem(markers)
@@ -77,7 +79,7 @@ export default class PostNodeBuilder {
    */
   createMarkupSection(
     tagName: string = DEFAULT_MARKUP_SECTION_TAG_NAME,
-    markers: Marker[] = [],
+    markers: Markuperable[] = [],
     isGenerated = false,
     attributes = {}
   ): MarkupSection {
@@ -90,14 +92,14 @@ export default class PostNodeBuilder {
     return section
   }
 
-  createListSection(tagName = DEFAULT_LIST_SECTION_TAG_NAME, items = [], attributes = {}) {
+  createListSection(tagName = DEFAULT_LIST_SECTION_TAG_NAME, items: ListItem[] = [], attributes = {}) {
     tagName = normalizeTagName(tagName)
     const section = new ListSection(tagName, items, attributes)
     section.builder = this
     return section
   }
 
-  createListItem(markers: Marker[] = []) {
+  createListItem(markers: Markuperable[] = []) {
     const tagName = normalizeTagName('li')
     const item = new ListItem(tagName, markers)
     item.builder = this
@@ -109,6 +111,7 @@ export default class PostNodeBuilder {
     if (url) {
       section.src = url
     }
+    section.builder = this
     return section
   }
 
@@ -165,3 +168,5 @@ export default class PostNodeBuilder {
     return markup
   }
 }
+
+export type PostNode = Section | Markuperable | Marker
diff --git a/src/js/models/post.ts b/src/js/models/post.ts
index ccfe31c28..2099fc506 100644
--- a/src/js/models/post.ts
+++ b/src/js/models/post.ts
@@ -5,7 +5,6 @@ import Set from '../utils/set'
 import Position from '../utils/cursor/position'
 import Range from '../utils/cursor/range'
 import assert from '../utils/assert'
-import Marker from './marker'
 import Markerable, { isMarkerable } from './_markerable'
 import Section from './_section'
 import PostNodeBuilder from './post-node-builder'
@@ -14,6 +13,8 @@ import ListItem, { isListItem } from './list-item'
 import MarkupSection from './markup-section'
 import RenderNode from './render-node'
 import HasChildSections from './_has-child-sections'
+import { expectCloneable, Cloneable } from './_cloneable'
+import Markuperable from '../utils/markuperable'
 
 type SectionCallback = (section: Section, index: number) => void
 
@@ -25,10 +26,10 @@ type SectionCallback = (section: Section, index: number) => void
  * When persisting a post, it must first be serialized (loss-lessly) into
  * mobiledoc using {@link Editor#serialize}.
  */
-export default class Post implements HasChildSections {
+export default class Post implements HasChildSections<Cloneable<Section>> {
   type = Type.POST
   builder!: PostNodeBuilder
-  sections: LinkedList<Section>
+  sections: LinkedList<Cloneable<Section>>
   renderNode!: RenderNode
 
   constructor() {
@@ -94,7 +95,7 @@ export default class Post implements HasChildSections {
    * @return {Array} markers that are completely contained by the range
    */
   markersContainedByRange(range: Range): Array<any> {
-    const markers: Marker[] = []
+    const markers: Markuperable[] = []
 
     this.walkMarkerableSections(range, (section: Markerable) => {
       section._markersInRange(range.trimTo(section), (m, { isContained }) => {
@@ -212,7 +213,7 @@ export default class Post implements HasChildSections {
       listParent: ListSection | null = null
 
     this.walkLeafSections(range, section => {
-      let newSection: Section
+      let newSection: ListItem | MarkupSection | Cloneable<Section>
       if (isMarkerable(section)) {
         if (isListItem(section)) {
           if (listParent) {
@@ -250,15 +251,3 @@ export default class Post implements HasChildSections {
     return post
   }
 }
-
-interface Cloneable<T> {
-  clone(): T
-}
-
-function expectCloneable<T extends Section>(section: T): T & Cloneable<T> {
-  if (!('clone' in section)) {
-    throw new Error('Expected section to be cloneable')
-  }
-
-  return section as T & Cloneable<T>
-}
diff --git a/src/js/models/render-node.ts b/src/js/models/render-node.ts
index d856513fc..18b974f27 100644
--- a/src/js/models/render-node.ts
+++ b/src/js/models/render-node.ts
@@ -8,8 +8,7 @@ import CardNode from './card-node'
 import AtomNode from './atom-node'
 import Section from './_section'
 import Markuperable from '../utils/markuperable'
-
-export type PostNode = Section | Markuperable
+import { PostNode } from './post-node-builder'
 
 export default class RenderNode<T extends Node = Node> extends LinkedItem {
   parent: Option<RenderNode> = null
diff --git a/src/js/models/render-tree.ts b/src/js/models/render-tree.ts
index dcc575eaa..cd652ce25 100644
--- a/src/js/models/render-tree.ts
+++ b/src/js/models/render-tree.ts
@@ -1,6 +1,7 @@
-import RenderNode, { PostNode } from '../models/render-node'
+import RenderNode from '../models/render-node'
 import ElementMap from '../utils/element-map'
 import Section from './_section'
+import { PostNode } from './post-node-builder'
 
 export default class RenderTree {
   _rootNode: RenderNode
diff --git a/src/js/parsers/dom.js b/src/js/parsers/dom.ts
similarity index 62%
rename from src/js/parsers/dom.js
rename to src/js/parsers/dom.ts
index a37a56401..eb5523be5 100644
--- a/src/js/parsers/dom.js
+++ b/src/js/parsers/dom.ts
@@ -1,43 +1,56 @@
 import { NO_BREAK_SPACE, TAB_CHARACTER, ATOM_CLASS_NAME } from '../renderers/editor-dom'
 import { MARKUP_SECTION_TYPE, LIST_SECTION_TYPE, LIST_ITEM_TYPE } from '../models/types'
-import { isTextNode, isCommentNode, isElementNode, getAttributes, normalizeTagName } from '../utils/dom-utils'
-import { any, detect, forEach } from '../utils/array-utils'
-import { TAB } from 'mobiledoc-kit/utils/characters'
-import { ZWNJ } from 'mobiledoc-kit/renderers/editor-dom'
-
-import SectionParser from 'mobiledoc-kit/parsers/section'
-import Markup from 'mobiledoc-kit/models/markup'
+import { isTextNode, isElementNode, getAttributes, normalizeTagName } from '../utils/dom-utils'
+import { any, detect, forEach, Indexable, ForEachable } from '../utils/array-utils'
+import { TAB } from '../utils/characters'
+import { ZWNJ } from '../renderers/editor-dom'
+import SectionParser from '../parsers/section'
+import Markup from '../models/markup'
+import Markerable, { isMarkerable } from '../models/_markerable'
+import PostNodeBuilder from '../models/post-node-builder'
+import { Dict } from '../utils/types'
+import Section from '../models/_section'
+import Post from '../models/post'
+import { Cloneable } from '../models/_cloneable'
+import MarkupSection, { hasInferredTagName } from '../models/markup-section'
+import RenderTree from '../models/render-tree'
+import { isMarker } from '../models/marker'
+import { isAtom } from '../models/atom'
+import RenderNode from '../models/render-node'
+import Markuperable from '../utils/markuperable'
+import ListItem from '../models/list-item'
+import ListSection from '../models/list-section'
 
 const GOOGLE_DOCS_CONTAINER_ID_REGEX = /^docs-internal-guid/
 
 const NO_BREAK_SPACE_REGEX = new RegExp(NO_BREAK_SPACE, 'g')
 const TAB_CHARACTER_REGEX = new RegExp(TAB_CHARACTER, 'g')
-export function transformHTMLText(textContent) {
+
+export function transformHTMLText(textContent: string) {
   let text = textContent
   text = text.replace(NO_BREAK_SPACE_REGEX, ' ')
   text = text.replace(TAB_CHARACTER_REGEX, TAB)
   return text
 }
 
-export function trimSectionText(section) {
-  if (section.isMarkerable && section.markers.length) {
+export function trimSectionText(section: Section) {
+  if (isMarkerable(section) && section.markers.length) {
     let { head, tail } = section.markers
-    head.value = head.value.replace(/^\s+/, '')
-    tail.value = tail.value.replace(/\s+$/, '')
+    head!.value = head!.value.replace(/^\s+/, '')
+    tail!.value = tail!.value.replace(/\s+$/, '')
   }
 }
 
-function isGoogleDocsContainer(element) {
+function isGoogleDocsContainer(element: Node) {
   return (
-    !isTextNode(element) &&
-    !isCommentNode(element) &&
+    isElementNode(element) &&
     normalizeTagName(element.tagName) === normalizeTagName('b') &&
     GOOGLE_DOCS_CONTAINER_ID_REGEX.test(element.id)
   )
 }
 
-function detectRootElement(element) {
-  let childNodes = element.childNodes || []
+function detectRootElement(element: HTMLElement) {
+  let childNodes: Indexable<Node> = element.childNodes || []
   let googleDocsContainer = detect(childNodes, isGoogleDocsContainer)
 
   if (googleDocsContainer) {
@@ -47,23 +60,23 @@ function detectRootElement(element) {
   }
 }
 
-const TAG_REMAPPING = {
+const TAG_REMAPPING: Dict<string> = {
   b: 'strong',
   i: 'em',
 }
 
-function remapTagName(tagName) {
+function remapTagName(tagName: string) {
   let normalized = normalizeTagName(tagName)
   let remapped = TAG_REMAPPING[normalized]
   return remapped || normalized
 }
 
-function trim(str) {
+function trim(str: string) {
   return str.replace(/^\s+/, '').replace(/\s+$/, '')
 }
 
-function walkMarkerableNodes(parent, callback) {
-  let currentNode = parent
+function walkMarkerableNodes(parent: Node, callback: (node: Node) => void) {
+  let currentNode: Node | null = parent
 
   if (isTextNode(currentNode) || (isElementNode(currentNode) && currentNode.classList.contains(ATOM_CLASS_NAME))) {
     callback(currentNode)
@@ -80,18 +93,21 @@ function walkMarkerableNodes(parent, callback) {
  * Parses DOM element -> Post
  * @private
  */
-class DOMParser {
-  constructor(builder, options = {}) {
+export default class DOMParser {
+  builder: PostNodeBuilder
+  sectionParser: SectionParser
+
+  constructor(builder: PostNodeBuilder, options = {}) {
     this.builder = builder
     this.sectionParser = new SectionParser(this.builder, options)
   }
 
-  parse(element) {
+  parse(element: HTMLElement) {
     const post = this.builder.createPost()
     let rootElement = detectRootElement(element)
 
     this._eachChildNode(rootElement, child => {
-      let sections = this.parseSections(child)
+      let sections = this.parseSections(child as HTMLElement)
       this.appendSections(post, sections)
     })
 
@@ -102,14 +118,14 @@ class DOMParser {
     return post
   }
 
-  appendSections(post, sections) {
+  appendSections(post: Post, sections: ForEachable<Cloneable<Section>>) {
     forEach(sections, section => this.appendSection(post, section))
   }
 
-  appendSection(post, section) {
+  appendSection(post: Post, section: Cloneable<Section>) {
     if (
       section.isBlank ||
-      (section.isMarkerable && trim(section.text) === '' && !any(section.markers, marker => marker.isAtom))
+      (isMarkerable(section) && trim(section.text) === '' && !any(section.markers, marker => marker.isAtom))
     ) {
       return
     }
@@ -117,8 +133,8 @@ class DOMParser {
     let lastSection = post.sections.tail
     if (
       lastSection &&
-      lastSection._inferredTagName &&
-      section._inferredTagName &&
+      hasInferredTagName(lastSection) &&
+      hasInferredTagName(section) &&
       lastSection.tagName === section.tagName
     ) {
       lastSection.join(section)
@@ -127,19 +143,19 @@ class DOMParser {
     }
   }
 
-  _eachChildNode(element, callback) {
+  _eachChildNode(element: Node, callback: (element: Node) => void) {
     let nodes = isTextNode(element) ? [element] : element.childNodes
     forEach(nodes, node => callback(node))
   }
 
-  parseSections(element) {
+  parseSections(element: HTMLElement) {
     return this.sectionParser.parse(element)
   }
 
   // walk up from the textNode until the rootNode, converting each
   // parentNode into a markup
-  collectMarkups(textNode, rootNode) {
-    let markups = []
+  collectMarkups(textNode: Text, rootNode: Node) {
+    let markups: Markup[] = []
     let currentNode = textNode.parentNode
     while (currentNode && currentNode !== rootNode) {
       let markup = this.markupFromNode(currentNode)
@@ -153,8 +169,8 @@ class DOMParser {
   }
 
   // Turn an element node into a markup
-  markupFromNode(node) {
-    if (Markup.isValidElement(node)) {
+  markupFromNode(node: Node) {
+    if (isElementNode(node) && Markup.isValidElement(node)) {
       let tagName = remapTagName(node.tagName)
       let attributes = getAttributes(node)
       return this.builder.createMarkup(tagName, attributes)
@@ -163,55 +179,55 @@ class DOMParser {
 
   // FIXME should move to the section parser?
   // FIXME the `collectMarkups` logic could simplify the section parser?
-  reparseSection(section, renderTree) {
+  reparseSection(section: Section, renderTree: RenderTree) {
     switch (section.type) {
       case LIST_SECTION_TYPE:
-        return this.reparseListSection(section, renderTree)
+        return this.reparseListSection(section as ListSection, renderTree)
       case LIST_ITEM_TYPE:
-        return this.reparseListItem(section, renderTree)
+        return this.reparseListItem(section as ListItem, renderTree)
       case MARKUP_SECTION_TYPE:
-        return this.reparseMarkupSection(section, renderTree)
+        return this.reparseMarkupSection(section as MarkupSection, renderTree)
       default:
         return // can only parse the above types
     }
   }
 
-  reparseMarkupSection(section, renderTree) {
+  reparseMarkupSection(section: MarkupSection, renderTree: RenderTree) {
     return this._reparseSectionContainingMarkers(section, renderTree)
   }
 
-  reparseListItem(listItem, renderTree) {
+  reparseListItem(listItem: ListItem, renderTree: RenderTree) {
     return this._reparseSectionContainingMarkers(listItem, renderTree)
   }
 
-  reparseListSection(listSection, renderTree) {
+  reparseListSection(listSection: ListSection, renderTree: RenderTree) {
     listSection.items.forEach(li => this.reparseListItem(li, renderTree))
   }
 
-  _reparseSectionContainingMarkers(section, renderTree) {
-    let element = section.renderNode.element
-    let seenRenderNodes = []
-    let previousMarker
+  _reparseSectionContainingMarkers(section: Markerable, renderTree: RenderTree) {
+    let element = section.renderNode.element!
+    let seenRenderNodes: RenderNode[] = []
+    let previousMarker: Markuperable
 
     walkMarkerableNodes(element, node => {
-      let marker
+      let marker!: Markuperable
       let renderNode = renderTree.getElementRenderNode(node)
       if (renderNode) {
-        if (renderNode.postNode.isMarker) {
-          let text = transformHTMLText(node.textContent)
-          let markups = this.collectMarkups(node, element)
+        if (isMarker(renderNode.postNode!)) {
+          let text = transformHTMLText(node.textContent || '')
+          let markups = this.collectMarkups(node as Text, element)
           if (text.length) {
-            marker = renderNode.postNode
+            marker = renderNode.postNode!
             marker.value = text
             marker.markups = markups
           } else {
             renderNode.scheduleForRemoval()
           }
-        } else if (renderNode.postNode.isAtom) {
+        } else if (isAtom(renderNode.postNode!)) {
           let { headTextNode, tailTextNode } = renderNode
-          if (headTextNode.textContent !== ZWNJ) {
-            let value = headTextNode.textContent.replace(new RegExp(ZWNJ, 'g'), '')
-            headTextNode.textContent = ZWNJ
+          if (headTextNode!.textContent !== ZWNJ) {
+            let value = headTextNode!.textContent!.replace(new RegExp(ZWNJ, 'g'), '')
+            headTextNode!.textContent = ZWNJ
             if (previousMarker && previousMarker.isMarker) {
               previousMarker.value += value
               if (previousMarker.renderNode) {
@@ -231,16 +247,16 @@ class DOMParser {
               section.renderNode.childNodes.insertBefore(newPreviousRenderNode, renderNode)
             }
           }
-          if (tailTextNode.textContent !== ZWNJ) {
-            let value = tailTextNode.textContent.replace(new RegExp(ZWNJ, 'g'), '')
-            tailTextNode.textContent = ZWNJ
+          if (tailTextNode!.textContent !== ZWNJ) {
+            let value = tailTextNode!.textContent!.replace(new RegExp(ZWNJ, 'g'), '')
+            tailTextNode!.textContent = ZWNJ
 
             if (renderNode.postNode.next && renderNode.postNode.next.isMarker) {
               let nextMarker = renderNode.postNode.next
 
               if (nextMarker.renderNode) {
-                let nextValue = nextMarker.renderNode.element.textContent
-                nextMarker.renderNode.element.textContent = value + nextValue
+                let nextValue = nextMarker.renderNode.element!.textContent
+                nextMarker.renderNode.element!.textContent = value + nextValue
               } else {
                 let nextValue = value + nextMarker.value
                 nextMarker.value = nextValue
@@ -266,7 +282,7 @@ class DOMParser {
           }
         }
       } else if (isTextNode(node)) {
-        let text = transformHTMLText(node.textContent)
+        let text = transformHTMLText(node.textContent!)
         let markups = this.collectMarkups(node, element)
         marker = this.builder.createMarker(text, markups)
 
@@ -295,5 +311,3 @@ class DOMParser {
     }
   }
 }
-
-export default DOMParser
diff --git a/src/js/parsers/html.js b/src/js/parsers/html.ts
similarity index 68%
rename from src/js/parsers/html.js
rename to src/js/parsers/html.ts
index 20cb92b43..2a77cd1d6 100644
--- a/src/js/parsers/html.js
+++ b/src/js/parsers/html.ts
@@ -1,9 +1,14 @@
 import { parseHTML } from '../utils/dom-utils'
 import assert from '../utils/assert'
 import DOMParser from './dom'
+import PostNodeBuilder from '../models/post-node-builder'
+import Post from '../models/post'
 
 export default class HTMLParser {
-  constructor(builder, options = {}) {
+  builder: PostNodeBuilder
+  options: {}
+
+  constructor(builder: PostNodeBuilder, options = {}) {
     assert('Must pass builder to HTMLParser', builder)
     this.builder = builder
     this.options = options
@@ -13,7 +18,7 @@ export default class HTMLParser {
    * @param {String} html to parse
    * @return {Post} A post abstract
    */
-  parse(html) {
+  parse(html: string): Post {
     let dom = parseHTML(html)
     let parser = new DOMParser(this.builder, this.options)
     return parser.parse(dom)
diff --git a/src/js/parsers/mobiledoc/0-2.js b/src/js/parsers/mobiledoc/0-2.ts
similarity index 55%
rename from src/js/parsers/mobiledoc/0-2.js
rename to src/js/parsers/mobiledoc/0-2.ts
index 3410c7f00..4a8620dfb 100644
--- a/src/js/parsers/mobiledoc/0-2.js
+++ b/src/js/parsers/mobiledoc/0-2.ts
@@ -1,17 +1,31 @@
 import {
-  MOBILEDOC_MARKUP_SECTION_TYPE,
-  MOBILEDOC_IMAGE_SECTION_TYPE,
-  MOBILEDOC_LIST_SECTION_TYPE,
-  MOBILEDOC_CARD_SECTION_TYPE,
-} from 'mobiledoc-kit/renderers/mobiledoc/0-2'
-import { kvArrayToObject, filter } from '../../utils/array-utils'
-import assert from 'mobiledoc-kit/utils/assert'
+  MobiledocMarkerType,
+  MobiledocV0_2,
+  MobiledocSection,
+  MobiledocMarker,
+  MobiledocCardSection,
+  MobiledocImageSection,
+  MobiledocMarkupSection,
+  MobiledocListSection,
+} from '../../renderers/mobiledoc/0-2'
+import { MobiledocSectionKind } from '../../renderers/mobiledoc/constants'
+import { kvArrayToObject, filter, ForEachable } from '../../utils/array-utils'
+import assert from '../../utils/assert'
+import PostNodeBuilder from '../../models/post-node-builder'
+import Post from '../../models/post'
+import Markup from '../../models/markup'
+import ListSection from '../../models/list-section'
+import Markerable from '../../models/_markerable'
 
 /*
  * Parses from mobiledoc -> post
  */
 export default class MobiledocParser {
-  constructor(builder) {
+  builder: PostNodeBuilder
+  markups!: Markup[]
+  markerTypes!: Markup[]
+
+  constructor(builder: PostNodeBuilder) {
     this.builder = builder
   }
 
@@ -19,7 +33,7 @@ export default class MobiledocParser {
    * @param {Mobiledoc}
    * @return {Post}
    */
-  parse({ sections: sectionData }) {
+  parse({ sections: sectionData }: MobiledocV0_2): Post {
     try {
       const markerTypes = sectionData[0]
       const sections = sectionData[1]
@@ -36,50 +50,49 @@ export default class MobiledocParser {
     }
   }
 
-  parseMarkerTypes(markerTypes) {
+  parseMarkerTypes(markerTypes: MobiledocMarkerType[]) {
     return markerTypes.map(markerType => this.parseMarkerType(markerType))
   }
 
-  parseMarkerType([tagName, attributesArray]) {
+  parseMarkerType([tagName, attributesArray]: MobiledocMarkerType) {
     const attributesObject = kvArrayToObject(attributesArray || [])
     return this.builder.createMarkup(tagName, attributesObject)
   }
 
-  parseSections(sections, post) {
+  parseSections(sections: ForEachable<MobiledocSection>, post: Post) {
     sections.forEach(section => this.parseSection(section, post))
   }
 
-  parseSection(section, post) {
-    let [type] = section
-    switch (type) {
-      case MOBILEDOC_MARKUP_SECTION_TYPE:
+  parseSection(section: MobiledocSection, post: Post) {
+    switch (section[0]) {
+      case MobiledocSectionKind.MARKUP:
         this.parseMarkupSection(section, post)
         break
-      case MOBILEDOC_IMAGE_SECTION_TYPE:
+      case MobiledocSectionKind.IMAGE:
         this.parseImageSection(section, post)
         break
-      case MOBILEDOC_CARD_SECTION_TYPE:
+      case MobiledocSectionKind.CARD:
         this.parseCardSection(section, post)
         break
-      case MOBILEDOC_LIST_SECTION_TYPE:
+      case MobiledocSectionKind.LIST:
         this.parseListSection(section, post)
         break
       default:
-        assert(`Unexpected section type ${type}`, false)
+        assert(`Unexpected section type ${section[0]}`, false)
     }
   }
 
-  parseCardSection([, name, payload], post) {
+  parseCardSection([, name, payload]: MobiledocCardSection, post: Post) {
     const section = this.builder.createCardSection(name, payload)
     post.sections.append(section)
   }
 
-  parseImageSection([, src], post) {
+  parseImageSection([, src]: MobiledocImageSection, post: Post) {
     const section = this.builder.createImageSection(src)
     post.sections.append(section)
   }
 
-  parseMarkupSection([, tagName, markers], post) {
+  parseMarkupSection([, tagName, markers]: MobiledocMarkupSection, post: Post) {
     const section = this.builder.createMarkupSection(tagName.toLowerCase() === 'pull-quote' ? 'aside' : tagName)
     post.sections.append(section)
     this.parseMarkers(markers, section)
@@ -90,27 +103,27 @@ export default class MobiledocParser {
     })
   }
 
-  parseListSection([, tagName, items], post) {
+  parseListSection([, tagName, items]: MobiledocListSection, post: Post) {
     const section = this.builder.createListSection(tagName)
     post.sections.append(section)
     this.parseListItems(items, section)
   }
 
-  parseListItems(items, section) {
+  parseListItems(items: MobiledocMarker[][], section: ListSection) {
     items.forEach(i => this.parseListItem(i, section))
   }
 
-  parseListItem(markers, section) {
+  parseListItem(markers: MobiledocMarker[], section: ListSection) {
     const item = this.builder.createListItem()
     this.parseMarkers(markers, item)
     section.items.append(item)
   }
 
-  parseMarkers(markers, parent) {
+  parseMarkers(markers: MobiledocMarker[], parent: Markerable) {
     markers.forEach(m => this.parseMarker(m, parent))
   }
 
-  parseMarker([markerTypeIndexes, closeCount, value], parent) {
+  parseMarker([markerTypeIndexes, closeCount, value]: [number[], number, string], parent: Markerable) {
     markerTypeIndexes.forEach(index => {
       this.markups.push(this.markerTypes[index])
     })
diff --git a/src/js/parsers/mobiledoc/0-3-1.js b/src/js/parsers/mobiledoc/0-3-1.ts
similarity index 55%
rename from src/js/parsers/mobiledoc/0-3-1.js
rename to src/js/parsers/mobiledoc/0-3-1.ts
index 80c810767..197327267 100644
--- a/src/js/parsers/mobiledoc/0-3-1.js
+++ b/src/js/parsers/mobiledoc/0-3-1.ts
@@ -1,19 +1,35 @@
 import {
-  MOBILEDOC_MARKUP_SECTION_TYPE,
-  MOBILEDOC_IMAGE_SECTION_TYPE,
-  MOBILEDOC_LIST_SECTION_TYPE,
-  MOBILEDOC_CARD_SECTION_TYPE,
-  MOBILEDOC_MARKUP_MARKER_TYPE,
-  MOBILEDOC_ATOM_MARKER_TYPE,
-} from 'mobiledoc-kit/renderers/mobiledoc/0-3-1'
-import { kvArrayToObject, filter } from '../../utils/array-utils'
-import assert from 'mobiledoc-kit/utils/assert'
+  MobiledocMarkerType,
+  MobiledocMarkupSection,
+  MobiledocSection,
+  MobiledocCard,
+  MobiledocAtom,
+  MobiledocCardSection,
+  MobiledocImageSection,
+  MobiledocListSection,
+  MobiledocMarker,
+} from '../../renderers/mobiledoc/0-3'
+import { kvArrayToObject, filter, ForEachable } from '../../utils/array-utils'
+import assert from '../../utils/assert'
+import { MobiledocMarkerKind, MobiledocSectionKind } from '../../renderers/mobiledoc/constants'
+import PostNodeBuilder from '../../models/post-node-builder'
+import { MobiledocV0_3_1 } from '../../renderers/mobiledoc/0-3-1'
+import Markup from '../../models/markup'
+import Post from '../../models/post'
+import ListSection from '../../models/list-section'
+import Markerable from '../../models/_markerable'
 
 /*
  * Parses from mobiledoc -> post
  */
 export default class MobiledocParser {
-  constructor(builder) {
+  builder: PostNodeBuilder
+  markups!: Markup[]
+  markerTypes!: Markup[]
+  cardTypes!: MobiledocCard[]
+  atomTypes!: MobiledocAtom[]
+
+  constructor(builder: PostNodeBuilder) {
     this.builder = builder
   }
 
@@ -21,7 +37,7 @@ export default class MobiledocParser {
    * @param {Mobiledoc}
    * @return {Post}
    */
-  parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }) {
+  parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }: MobiledocV0_3_1): Post {
     try {
       const post = this.builder.createPost()
 
@@ -37,79 +53,78 @@ export default class MobiledocParser {
     }
   }
 
-  parseMarkerTypes(markerTypes) {
+  parseMarkerTypes(markerTypes: MobiledocMarkerType[]) {
     return markerTypes.map(markerType => this.parseMarkerType(markerType))
   }
 
-  parseMarkerType([tagName, attributesArray]) {
+  parseMarkerType([tagName, attributesArray]: MobiledocMarkerType) {
     const attributesObject = kvArrayToObject(attributesArray || [])
     return this.builder.createMarkup(tagName, attributesObject)
   }
 
-  parseCardTypes(cardTypes) {
+  parseCardTypes(cardTypes: MobiledocCard[]) {
     return cardTypes.map(cardType => this.parseCardType(cardType))
   }
 
-  parseCardType([cardName, cardPayload]) {
+  parseCardType([cardName, cardPayload]: MobiledocCard): MobiledocCard {
     return [cardName, cardPayload]
   }
 
-  parseAtomTypes(atomTypes) {
+  parseAtomTypes(atomTypes: MobiledocAtom[]) {
     return atomTypes.map(atomType => this.parseAtomType(atomType))
   }
 
-  parseAtomType([atomName, atomValue, atomPayload]) {
+  parseAtomType([atomName, atomValue, atomPayload]: MobiledocAtom): MobiledocAtom {
     return [atomName, atomValue, atomPayload]
   }
 
-  parseSections(sections, post) {
+  parseSections(sections: ForEachable<MobiledocSection>, post: Post) {
     sections.forEach(section => this.parseSection(section, post))
   }
 
-  parseSection(section, post) {
-    let [type] = section
-    switch (type) {
-      case MOBILEDOC_MARKUP_SECTION_TYPE:
+  parseSection(section: MobiledocSection, post: Post) {
+    switch (section[0]) {
+      case MobiledocSectionKind.MARKUP:
         this.parseMarkupSection(section, post)
         break
-      case MOBILEDOC_IMAGE_SECTION_TYPE:
+      case MobiledocSectionKind.IMAGE:
         this.parseImageSection(section, post)
         break
-      case MOBILEDOC_CARD_SECTION_TYPE:
+      case MobiledocSectionKind.CARD:
         this.parseCardSection(section, post)
         break
-      case MOBILEDOC_LIST_SECTION_TYPE:
+      case MobiledocSectionKind.LIST:
         this.parseListSection(section, post)
         break
       default:
-        assert('Unexpected section type ${type}', false)
+        assert(`Unexpected section type ${section[0]}`, false)
     }
   }
 
-  getAtomTypeFromIndex(index) {
+  getAtomTypeFromIndex(index: number) {
     const atomType = this.atomTypes[index]
     assert(`No atom definition found at index ${index}`, !!atomType)
     return atomType
   }
 
-  getCardTypeFromIndex(index) {
+  getCardTypeFromIndex(index: number) {
     const cardType = this.cardTypes[index]
     assert(`No card definition found at index ${index}`, !!cardType)
     return cardType
   }
 
-  parseCardSection([, cardIndex], post) {
+  parseCardSection([, cardIndex]: MobiledocCardSection, post: Post) {
     const [name, payload] = this.getCardTypeFromIndex(cardIndex)
     const section = this.builder.createCardSection(name, payload)
     post.sections.append(section)
   }
 
-  parseImageSection([, src], post) {
+  parseImageSection([, src]: MobiledocImageSection, post: Post) {
     const section = this.builder.createImageSection(src)
     post.sections.append(section)
   }
 
-  parseMarkupSection([, tagName, markers], post) {
+  parseMarkupSection([, tagName, markers]: MobiledocMarkupSection, post: Post) {
     const section = this.builder.createMarkupSection(tagName)
     post.sections.append(section)
     this.parseMarkers(markers, section)
@@ -120,27 +135,27 @@ export default class MobiledocParser {
     })
   }
 
-  parseListSection([, tagName, items], post) {
+  parseListSection([, tagName, items]: MobiledocListSection, post: Post) {
     const section = this.builder.createListSection(tagName)
     post.sections.append(section)
     this.parseListItems(items, section)
   }
 
-  parseListItems(items, section) {
+  parseListItems(items: MobiledocMarker[][], section: ListSection) {
     items.forEach(i => this.parseListItem(i, section))
   }
 
-  parseListItem(markers, section) {
+  parseListItem(markers: MobiledocMarker[], section: ListSection) {
     const item = this.builder.createListItem()
     this.parseMarkers(markers, item)
     section.items.append(item)
   }
 
-  parseMarkers(markers, parent) {
+  parseMarkers(markers: MobiledocMarker[], parent: Markerable) {
     markers.forEach(m => this.parseMarker(m, parent))
   }
 
-  parseMarker([type, markerTypeIndexes, closeCount, value], parent) {
+  parseMarker([type, markerTypeIndexes, closeCount, value]: MobiledocMarker, parent: Markerable) {
     markerTypeIndexes.forEach(index => {
       this.markups.push(this.markerTypes[index])
     })
@@ -151,12 +166,12 @@ export default class MobiledocParser {
     this.markups = this.markups.slice(0, this.markups.length - closeCount)
   }
 
-  buildMarkerType(type, value) {
+  buildMarkerType(type: MobiledocMarkerKind, value: string | number) {
     switch (type) {
-      case MOBILEDOC_MARKUP_MARKER_TYPE:
-        return this.builder.createMarker(value, this.markups.slice())
-      case MOBILEDOC_ATOM_MARKER_TYPE: {
-        const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value)
+      case MobiledocMarkerKind.MARKUP:
+        return this.builder.createMarker(value as string, this.markups.slice())
+      case MobiledocMarkerKind.ATOM: {
+        const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value as number)
         return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice())
       }
       default:
diff --git a/src/js/parsers/mobiledoc/0-3-2.js b/src/js/parsers/mobiledoc/0-3-2.ts
similarity index 57%
rename from src/js/parsers/mobiledoc/0-3-2.js
rename to src/js/parsers/mobiledoc/0-3-2.ts
index 1b5d6e74d..3518b8a43 100644
--- a/src/js/parsers/mobiledoc/0-3-2.js
+++ b/src/js/parsers/mobiledoc/0-3-2.ts
@@ -1,21 +1,41 @@
 import {
-  MOBILEDOC_MARKUP_SECTION_TYPE,
-  MOBILEDOC_IMAGE_SECTION_TYPE,
-  MOBILEDOC_LIST_SECTION_TYPE,
-  MOBILEDOC_CARD_SECTION_TYPE,
-  MOBILEDOC_MARKUP_MARKER_TYPE,
-  MOBILEDOC_ATOM_MARKER_TYPE,
-} from '../../renderers/mobiledoc/0-3-2'
-
-import { kvArrayToObject, filter } from '../../utils/array-utils'
+  MobiledocMarkerType,
+  MobiledocCard,
+  MobiledocAtom,
+  MobiledocMarker,
+  MobiledocCardSection,
+  MobiledocImageSection,
+  MobiledocMarkupSection,
+  MobiledocListSection,
+} from '../../renderers/mobiledoc/0-3'
+
+import { kvArrayToObject, filter, ForEachable } from '../../utils/array-utils'
 import assert from '../../utils/assert'
 import { entries } from '../../utils/object-utils'
+import Markup from '../../models/markup'
+import PostNodeBuilder from '../../models/post-node-builder'
+import {
+  MobiledocV0_3_2,
+  MobiledocAttributedMarkupSection,
+  MobiledocAttributedListSection,
+  MobiledocAttributedSection,
+} from '../../renderers/mobiledoc/0-3-2'
+import Post from '../../models/post'
+import { MobiledocSectionKind, MobiledocMarkerKind } from '../../renderers/mobiledoc/constants'
+import ListSection from '../../models/list-section'
+import Markerable from '../../models/_markerable'
 
 /*
  * Parses from mobiledoc -> post
  */
 export default class MobiledocParser {
-  constructor(builder) {
+  builder: PostNodeBuilder
+  markups!: Markup[]
+  markerTypes!: Markup[]
+  cardTypes!: MobiledocCard[]
+  atomTypes!: MobiledocAtom[]
+
+  constructor(builder: PostNodeBuilder) {
     this.builder = builder
   }
 
@@ -23,7 +43,7 @@ export default class MobiledocParser {
    * @param {Mobiledoc}
    * @return {Post}
    */
-  parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }) {
+  parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }: MobiledocV0_3_2): Post {
     try {
       const post = this.builder.createPost()
 
@@ -39,79 +59,81 @@ export default class MobiledocParser {
     }
   }
 
-  parseMarkerTypes(markerTypes) {
+  parseMarkerTypes(markerTypes: MobiledocMarkerType[]) {
     return markerTypes.map(markerType => this.parseMarkerType(markerType))
   }
 
-  parseMarkerType([tagName, attributesArray]) {
+  parseMarkerType([tagName, attributesArray]: MobiledocMarkerType) {
     const attributesObject = kvArrayToObject(attributesArray || [])
     return this.builder.createMarkup(tagName, attributesObject)
   }
 
-  parseCardTypes(cardTypes) {
+  parseCardTypes(cardTypes: MobiledocCard[]) {
     return cardTypes.map(cardType => this.parseCardType(cardType))
   }
 
-  parseCardType([cardName, cardPayload]) {
+  parseCardType([cardName, cardPayload]: MobiledocCard): MobiledocCard {
     return [cardName, cardPayload]
   }
 
-  parseAtomTypes(atomTypes) {
+  parseAtomTypes(atomTypes: MobiledocAtom[]) {
     return atomTypes.map(atomType => this.parseAtomType(atomType))
   }
 
-  parseAtomType([atomName, atomValue, atomPayload]) {
+  parseAtomType([atomName, atomValue, atomPayload]: MobiledocAtom): MobiledocAtom {
     return [atomName, atomValue, atomPayload]
   }
 
-  parseSections(sections, post) {
+  parseSections(sections: ForEachable<MobiledocAttributedSection>, post: Post) {
     sections.forEach(section => this.parseSection(section, post))
   }
 
-  parseSection(section, post) {
-    let [type] = section
-    switch (type) {
-      case MOBILEDOC_MARKUP_SECTION_TYPE:
+  parseSection(section: MobiledocAttributedSection, post: Post) {
+    switch (section[0]) {
+      case MobiledocSectionKind.MARKUP:
         this.parseMarkupSection(section, post)
         break
-      case MOBILEDOC_IMAGE_SECTION_TYPE:
+      case MobiledocSectionKind.IMAGE:
         this.parseImageSection(section, post)
         break
-      case MOBILEDOC_CARD_SECTION_TYPE:
+      case MobiledocSectionKind.CARD:
         this.parseCardSection(section, post)
         break
-      case MOBILEDOC_LIST_SECTION_TYPE:
+      case MobiledocSectionKind.LIST:
         this.parseListSection(section, post)
         break
       default:
-        assert('Unexpected section type ${type}', false)
+        assert(`Unexpected section type ${section[0]}`, false)
     }
   }
 
-  getAtomTypeFromIndex(index) {
+  getAtomTypeFromIndex(index: number) {
     const atomType = this.atomTypes[index]
     assert(`No atom definition found at index ${index}`, !!atomType)
     return atomType
   }
 
-  getCardTypeFromIndex(index) {
+  getCardTypeFromIndex(index: number) {
     const cardType = this.cardTypes[index]
     assert(`No card definition found at index ${index}`, !!cardType)
     return cardType
   }
 
-  parseCardSection([, cardIndex], post) {
+  parseCardSection([, cardIndex]: MobiledocCardSection, post: Post) {
     const [name, payload] = this.getCardTypeFromIndex(cardIndex)
     const section = this.builder.createCardSection(name, payload)
     post.sections.append(section)
   }
 
-  parseImageSection([, src], post) {
+  parseImageSection([, src]: MobiledocImageSection, post: Post) {
     const section = this.builder.createImageSection(src)
     post.sections.append(section)
   }
 
-  parseMarkupSection([, tagName, markers, attributesArray], post) {
+  parseMarkupSection(
+    [, tagName, markers, attributesArray]: MobiledocMarkupSection | MobiledocAttributedMarkupSection,
+    post: Post
+  ) {
     const section = this.builder.createMarkupSection(tagName)
     post.sections.append(section)
     if (attributesArray) {
@@ -127,7 +149,10 @@ export default class MobiledocParser {
     })
   }
 
-  parseListSection([, tagName, items, attributesArray], post) {
+  parseListSection(
+    [, tagName, items, attributesArray]: MobiledocListSection | MobiledocAttributedListSection,
+    post: Post
+  ) {
     const section = this.builder.createListSection(tagName)
     post.sections.append(section)
     if (attributesArray) {
@@ -138,21 +163,21 @@ export default class MobiledocParser {
     this.parseListItems(items, section)
   }
 
-  parseListItems(items, section) {
+  parseListItems(items: MobiledocMarker[][], section: ListSection) {
     items.forEach(i => this.parseListItem(i, section))
   }
 
-  parseListItem(markers, section) {
+  parseListItem(markers: MobiledocMarker[], section: ListSection) {
     const item = this.builder.createListItem()
     this.parseMarkers(markers, item)
     section.items.append(item)
   }
 
-  parseMarkers(markers, parent) {
+  parseMarkers(markers: MobiledocMarker[], parent: Markerable) {
     markers.forEach(m => this.parseMarker(m, parent))
   }
 
-  parseMarker([type, markerTypeIndexes, closeCount, value], parent) {
+  parseMarker([type, markerTypeIndexes, closeCount, value]: MobiledocMarker, parent: Markerable) {
     markerTypeIndexes.forEach(index => {
       this.markups.push(this.markerTypes[index])
     })
@@ -163,12 +188,12 @@ export default class MobiledocParser {
     this.markups = this.markups.slice(0, this.markups.length - closeCount)
   }
 
-  buildMarkerType(type, value) {
+  buildMarkerType(type: MobiledocMarkerKind, value: string | number) {
     switch (type) {
-      case MOBILEDOC_MARKUP_MARKER_TYPE:
-        return this.builder.createMarker(value, this.markups.slice())
-      case MOBILEDOC_ATOM_MARKER_TYPE: {
-        const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value)
+      case MobiledocMarkerKind.MARKUP:
+        return this.builder.createMarker(value as string, this.markups.slice())
+      case MobiledocMarkerKind.ATOM: {
+        const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value as number)
         return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice())
       }
       default:
diff --git a/src/js/parsers/mobiledoc/0-3.js b/src/js/parsers/mobiledoc/0-3.ts
similarity index 55%
rename from src/js/parsers/mobiledoc/0-3.js
rename to src/js/parsers/mobiledoc/0-3.ts
index 03b6f00c1..3f29a17d6 100644
--- a/src/js/parsers/mobiledoc/0-3.js
+++ b/src/js/parsers/mobiledoc/0-3.ts
@@ -1,19 +1,35 @@
 import {
-  MOBILEDOC_MARKUP_SECTION_TYPE,
-  MOBILEDOC_IMAGE_SECTION_TYPE,
-  MOBILEDOC_LIST_SECTION_TYPE,
-  MOBILEDOC_CARD_SECTION_TYPE,
-  MOBILEDOC_MARKUP_MARKER_TYPE,
-  MOBILEDOC_ATOM_MARKER_TYPE,
-} from 'mobiledoc-kit/renderers/mobiledoc/0-3'
-import { kvArrayToObject, filter } from '../../utils/array-utils'
-import assert from 'mobiledoc-kit/utils/assert'
+  MobiledocMarkerType,
+  MobiledocMarkupSection,
+  MobiledocSection,
+  MobiledocCard,
+  MobiledocAtom,
+  MobiledocV0_3,
+  MobiledocMarker,
+  MobiledocCardSection,
+  MobiledocImageSection,
+  MobiledocListSection,
+} from '../../renderers/mobiledoc/0-3'
+import { kvArrayToObject, filter, ForEachable } from '../../utils/array-utils'
+import assert from '../../utils/assert'
+import PostNodeBuilder from '../../models/post-node-builder'
+import Markup from '../../models/markup'
+import Post from '../../models/post'
+import { MobiledocSectionKind, MobiledocMarkerKind } from '../../renderers/mobiledoc/constants'
+import ListSection from '../../models/list-section'
+import Markerable from '../../models/_markerable'
 
 /*
  * Parses from mobiledoc -> post
  */
 export default class MobiledocParser {
-  constructor(builder) {
+  builder: PostNodeBuilder
+  markups!: Markup[]
+  markerTypes!: Markup[]
+  cardTypes!: MobiledocCard[]
+  atomTypes!: MobiledocAtom[]
+
+  constructor(builder: PostNodeBuilder) {
     this.builder = builder
   }
 
@@ -21,7 +37,7 @@ export default class MobiledocParser {
    * @param {Mobiledoc}
    * @return {Post}
    */
-  parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }) {
+  parse({ sections, markups: markerTypes, cards: cardTypes, atoms: atomTypes }: MobiledocV0_3): Post {
     try {
       const post = this.builder.createPost()
 
@@ -37,79 +53,78 @@ export default class MobiledocParser {
     }
   }
 
-  parseMarkerTypes(markerTypes) {
+  parseMarkerTypes(markerTypes: MobiledocMarkerType[]) {
     return markerTypes.map(markerType => this.parseMarkerType(markerType))
   }
 
-  parseMarkerType([tagName, attributesArray]) {
+  parseMarkerType([tagName, attributesArray]: MobiledocMarkerType) {
     const attributesObject = kvArrayToObject(attributesArray || [])
     return this.builder.createMarkup(tagName, attributesObject)
   }
 
-  parseCardTypes(cardTypes) {
+  parseCardTypes(cardTypes: MobiledocCard[]) {
     return cardTypes.map(cardType => this.parseCardType(cardType))
   }
 
-  parseCardType([cardName, cardPayload]) {
+  parseCardType([cardName, cardPayload]: MobiledocCard): MobiledocCard {
     return [cardName, cardPayload]
   }
 
-  parseAtomTypes(atomTypes) {
+  parseAtomTypes(atomTypes: MobiledocAtom[]) {
     return atomTypes.map(atomType => this.parseAtomType(atomType))
   }
 
-  parseAtomType([atomName, atomValue, atomPayload]) {
+  parseAtomType([atomName, atomValue, atomPayload]: MobiledocAtom): MobiledocAtom {
     return [atomName, atomValue, atomPayload]
   }
 
-  parseSections(sections, post) {
+  parseSections(sections: ForEachable<MobiledocSection>, post: Post) {
     sections.forEach(section => this.parseSection(section, post))
   }
 
-  parseSection(section, post) {
-    let [type] = section
-    switch (type) {
-      case MOBILEDOC_MARKUP_SECTION_TYPE:
+  parseSection(section: MobiledocSection, post: Post) {
+    switch (section[0]) {
+      case MobiledocSectionKind.MARKUP:
         this.parseMarkupSection(section, post)
         break
-      case MOBILEDOC_IMAGE_SECTION_TYPE:
+      case MobiledocSectionKind.IMAGE:
         this.parseImageSection(section, post)
         break
-      case MOBILEDOC_CARD_SECTION_TYPE:
+      case MobiledocSectionKind.CARD:
         this.parseCardSection(section, post)
         break
-      case MOBILEDOC_LIST_SECTION_TYPE:
+      case MobiledocSectionKind.LIST:
         this.parseListSection(section, post)
         break
       default:
-        assert('Unexpected section type ${type}', false)
+        assert(`Unexpected section type ${section[0]}`, false)
     }
   }
 
-  getAtomTypeFromIndex(index) {
+  getAtomTypeFromIndex(index: number) {
     const atomType = this.atomTypes[index]
     assert(`No atom definition found at index ${index}`, !!atomType)
     return atomType
   }
 
-  getCardTypeFromIndex(index) {
+  getCardTypeFromIndex(index: number) {
     const cardType = this.cardTypes[index]
     assert(`No card definition found at index ${index}`, !!cardType)
     return cardType
   }
 
-  parseCardSection([, cardIndex], post) {
+  parseCardSection([, cardIndex]: MobiledocCardSection, post: Post) {
     const [name, payload] = this.getCardTypeFromIndex(cardIndex)
     const section = this.builder.createCardSection(name, payload)
     post.sections.append(section)
   }
 
-  parseImageSection([, src], post) {
+  parseImageSection([, src]: MobiledocImageSection, post: Post) {
     const section = this.builder.createImageSection(src)
     post.sections.append(section)
   }
 
-  parseMarkupSection([, tagName, markers], post) {
+  parseMarkupSection([, tagName, markers]: MobiledocMarkupSection, post: Post) {
     const section = this.builder.createMarkupSection(tagName.toLowerCase() === 'pull-quote' ? 'aside' : tagName)
     post.sections.append(section)
     this.parseMarkers(markers, section)
@@ -120,27 +135,27 @@ export default class MobiledocParser {
     })
   }
 
-  parseListSection([, tagName, items], post) {
+  parseListSection([, tagName, items]: MobiledocListSection, post: Post) {
     const section = this.builder.createListSection(tagName)
     post.sections.append(section)
     this.parseListItems(items, section)
   }
 
-  parseListItems(items, section) {
+  parseListItems(items: MobiledocMarker[][], section: ListSection) {
     items.forEach(i => this.parseListItem(i, section))
   }
 
-  parseListItem(markers, section) {
+  parseListItem(markers: MobiledocMarker[], section: ListSection) {
     const item = this.builder.createListItem()
     this.parseMarkers(markers, item)
     section.items.append(item)
   }
 
-  parseMarkers(markers, parent) {
+  parseMarkers(markers: MobiledocMarker[], parent: Markerable) {
     markers.forEach(m => this.parseMarker(m, parent))
   }
 
-  parseMarker([type, markerTypeIndexes, closeCount, value], parent) {
+  parseMarker([type, markerTypeIndexes, closeCount, value]: MobiledocMarker, parent: Markerable) {
     markerTypeIndexes.forEach(index => {
       this.markups.push(this.markerTypes[index])
     })
@@ -151,12 +166,12 @@ export default class MobiledocParser {
     this.markups = this.markups.slice(0, this.markups.length - closeCount)
   }
 
-  buildMarkerType(type, value) {
+  buildMarkerType(type: MobiledocMarkerKind, value: string | number) {
     switch (type) {
-      case MOBILEDOC_MARKUP_MARKER_TYPE:
-        return this.builder.createMarker(value, this.markups.slice())
-      case MOBILEDOC_ATOM_MARKER_TYPE: {
-        const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value)
+      case MobiledocMarkerKind.MARKUP:
+        return this.builder.createMarker(value as string, this.markups.slice())
+      case MobiledocMarkerKind.ATOM: {
+        const [atomName, atomValue, atomPayload] = this.getAtomTypeFromIndex(value as number)
         return this.builder.createAtom(atomName, atomValue, atomPayload, this.markups.slice())
       }
       default:
diff --git a/src/js/parsers/mobiledoc/index.js b/src/js/parsers/mobiledoc/index.js
deleted file mode 100644
index 50194df92..000000000
--- a/src/js/parsers/mobiledoc/index.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import MobiledocParser_0_2 from './0-2'
-import MobiledocParser_0_3 from './0-3'
-import MobiledocParser_0_3_1 from './0-3-1'
-import MobiledocParser_0_3_2 from './0-3-2'
-
-import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2 } from 'mobiledoc-kit/renderers/mobiledoc/0-2'
-import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3 } from 'mobiledoc-kit/renderers/mobiledoc/0-3'
-import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1 } from 'mobiledoc-kit/renderers/mobiledoc/0-3-1'
-import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2 } from 'mobiledoc-kit/renderers/mobiledoc/0-3-2'
-import assert from 'mobiledoc-kit/utils/assert'
-
-function parseVersion(mobiledoc) {
-  return mobiledoc.version
-}
-
-export default {
-  parse(builder, mobiledoc) {
-    let version = parseVersion(mobiledoc)
-    switch (version) {
-      case MOBILEDOC_VERSION_0_2:
-        return new MobiledocParser_0_2(builder).parse(mobiledoc)
-      case MOBILEDOC_VERSION_0_3:
-        return new MobiledocParser_0_3(builder).parse(mobiledoc)
-      case MOBILEDOC_VERSION_0_3_1:
-        return new MobiledocParser_0_3_1(builder).parse(mobiledoc)
-      case MOBILEDOC_VERSION_0_3_2:
-        return new MobiledocParser_0_3_2(builder).parse(mobiledoc)
-      default:
-        assert(`Unknown version of mobiledoc parser requested: ${version}`, false)
-    }
-  },
-}
diff --git a/src/js/parsers/mobiledoc/index.ts b/src/js/parsers/mobiledoc/index.ts
new file mode 100644
index 000000000..f81d302b2
--- /dev/null
+++ b/src/js/parsers/mobiledoc/index.ts
@@ -0,0 +1,31 @@
+import MobiledocParser_0_2 from './0-2'
+import MobiledocParser_0_3 from './0-3'
+import MobiledocParser_0_3_1 from './0-3-1'
+import MobiledocParser_0_3_2 from './0-3-2'
+
+import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_2, MobiledocV0_2 } from '../../renderers/mobiledoc/0-2'
+import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3, MobiledocV0_3 } from '../../renderers/mobiledoc/0-3'
+import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_1, MobiledocV0_3_1 } from '../../renderers/mobiledoc/0-3-1'
+import { MOBILEDOC_VERSION as MOBILEDOC_VERSION_0_3_2, MobiledocV0_3_2 } from '../../renderers/mobiledoc/0-3-2'
+import assert from '../../utils/assert'
+import PostNodeBuilder from '../../models/post-node-builder'
+import Post from '../../models/post'
+
+type Mobiledoc = MobiledocV0_2 | MobiledocV0_3 | MobiledocV0_3_1 | MobiledocV0_3_2
+
+export default {
+  parse(builder: PostNodeBuilder, mobiledoc: Mobiledoc): Post {
+    switch (mobiledoc.version) {
+      case MOBILEDOC_VERSION_0_2:
+        return new MobiledocParser_0_2(builder).parse(mobiledoc)
+      case MOBILEDOC_VERSION_0_3:
+        return new MobiledocParser_0_3(builder).parse(mobiledoc)
+      case MOBILEDOC_VERSION_0_3_1:
+        return new MobiledocParser_0_3_1(builder).parse(mobiledoc)
+      case MOBILEDOC_VERSION_0_3_2:
+        return new MobiledocParser_0_3_2(builder).parse(mobiledoc)
+      default:
+        assert(`Unknown version of mobiledoc parser requested: ${(mobiledoc as any).version}`, false)
+    }
+  },
+}
diff --git a/src/js/parsers/section.js b/src/js/parsers/section.ts
similarity index 68%
rename from src/js/parsers/section.js
rename to src/js/parsers/section.ts
index b606d9c62..cb271d564 100644
--- a/src/js/parsers/section.js
+++ b/src/js/parsers/section.ts
@@ -1,17 +1,33 @@
-import { DEFAULT_TAG_NAME, VALID_MARKUP_SECTION_TAGNAMES } from 'mobiledoc-kit/models/markup-section'
-import { VALID_LIST_SECTION_TAGNAMES } from 'mobiledoc-kit/models/list-section'
-import { VALID_LIST_ITEM_TAGNAMES } from 'mobiledoc-kit/models/list-item'
-import { LIST_SECTION_TYPE, LIST_ITEM_TYPE, MARKUP_SECTION_TYPE } from 'mobiledoc-kit/models/types'
-import { VALID_MARKUP_TAGNAMES } from 'mobiledoc-kit/models/markup'
-import { getAttributes, normalizeTagName, isTextNode, isCommentNode, NODE_TYPES } from '../utils/dom-utils'
-import { any, forEach, contains } from 'mobiledoc-kit/utils/array-utils'
+import MarkupSection, {
+  DEFAULT_TAG_NAME,
+  VALID_MARKUP_SECTION_TAGNAMES,
+  isMarkupSection as sectionIsMarkupSection,
+} from '../models/markup-section'
+import { VALID_LIST_SECTION_TAGNAMES, isListSection as sectionIsListSection } from '../models/list-section'
+import { VALID_LIST_ITEM_TAGNAMES, isListItem as sectionIsListItem } from '../models/list-item'
+import { LIST_SECTION_TYPE, LIST_ITEM_TYPE, MARKUP_SECTION_TYPE } from '../models/types'
+import Markup, { VALID_MARKUP_TAGNAMES } from '../models/markup'
+import {
+  getAttributes,
+  normalizeTagName,
+  isTextNode,
+  isCommentNode,
+  NODE_TYPES,
+  isElementNode,
+} from '../utils/dom-utils'
+import { any, forEach, contains } from '../utils/array-utils'
 import { transformHTMLText, trimSectionText } from '../parsers/dom'
-import assert from '../utils/assert'
+import assert, { assertType, expect } from '../utils/assert'
+import PostNodeBuilder from '../models/post-node-builder'
+import Section from '../models/_section'
+import Marker from '../models/marker'
+import Markerable, { isMarkerable } from '../models/_markerable'
+import { Cloneable } from '../models/_cloneable'
 
 const SKIPPABLE_ELEMENT_TAG_NAMES = ['style', 'head', 'title', 'meta'].map(normalizeTagName)
 
 const NEWLINES = /\s*\n\s*/g
-function sanitize(text) {
+function sanitize(text: string) {
   return text.replace(NEWLINES, ' ')
 }
 
@@ -20,13 +36,40 @@ function sanitize(text) {
  * elements contained within
  * @private
  */
-class SectionParser {
-  constructor(builder, options = {}) {
+
+interface SectionParserOptions {
+  plugins?: SectionParserPlugin[]
+}
+
+interface SectionParserState {
+  section?: Cloneable<Section> | null
+  text?: string
+  markups?: Markup[]
+}
+
+interface SectionParseEnv {
+  addSection: (section: Cloneable<Section>) => void
+  addMarkerable: (marker: Marker) => void
+  nodeFinished(): void
+}
+
+export type SectionParserPlugin = (node: Node, builder: PostNodeBuilder, env: SectionParseEnv) => void
+
+type SectionParserNode = HTMLElement | Text | Comment
+
+export default class SectionParser {
+  builder: PostNodeBuilder
+  plugins: SectionParserPlugin[]
+
+  sections!: Cloneable<Section>[]
+  state!: SectionParserState
+
+  constructor(builder: PostNodeBuilder, options: SectionParserOptions = {}) {
     this.builder = builder
     this.plugins = options.plugins || []
   }
 
-  parse(element) {
+  parse(element: HTMLElement) {
     if (this._isSkippable(element)) {
       return []
     }
@@ -47,7 +90,7 @@ class SectionParser {
       let childNodes = isTextNode(element) ? [element] : element.childNodes
 
       forEach(childNodes, el => {
-        this.parseNode(el)
+        this.parseNode(el as SectionParserNode)
       })
     }
 
@@ -56,20 +99,20 @@ class SectionParser {
     return this.sections
   }
 
-  runPlugins(node) {
+  runPlugins(node: Node) {
     let isNodeFinished = false
     let env = {
-      addSection: section => {
+      addSection: (section: Cloneable<Section>) => {
         // avoid creating empty paragraphs due to wrapper elements around
         // parser-plugin-handled elements
-        if (this.state.section && this.state.section.isMarkerable && !this.state.section.text && !this.state.text) {
+        if (this.state.section && isMarkerable(this.state.section) && !this.state.section.text && !this.state.text) {
           this.state.section = null
         } else {
           this._closeCurrentSection()
         }
         this.sections.push(section)
       },
-      addMarkerable: marker => {
+      addMarkerable: (marker: Marker) => {
         let { state } = this
         let { section } = state
         // if the first element doesn't create it's own state and it's plugin
@@ -79,8 +122,9 @@ class SectionParser {
           state.section = this.builder.createMarkupSection(normalizeTagName('p'))
           section = state.section
         }
-        assert(
+        assertType<Markerable>(
           'Markerables can only be appended to markup sections and list item sections',
+          section,
           section && section.isMarkerable
         )
         if (state.text) {
@@ -103,7 +147,7 @@ class SectionParser {
   }
 
   /* eslint-disable complexity */
-  parseNode(node) {
+  parseNode(node: SectionParserNode) {
     if (!this.state.section) {
       this._updateStateFromElement(node)
     }
@@ -115,7 +159,7 @@ class SectionParser {
 
     // handle closing the current section and starting a new one if we hit a
     // new-section-creating element.
-    if (this.state.section && !isTextNode(node) && node.tagName) {
+    if (this.state.section && isElementNode(node) && node.tagName) {
       let tagName = normalizeTagName(node.tagName)
       let isListSection = contains(VALID_LIST_SECTION_TAGNAMES, tagName)
       let isListItem = contains(VALID_LIST_ITEM_TAGNAMES, tagName)
@@ -125,16 +169,16 @@ class SectionParser {
 
       // lists can continue after breaking out for a markup section,
       // in that situation, start a new list using the same list type
-      if (isListItem && this.state.section.isMarkupSection) {
+      if (isListItem && sectionIsMarkupSection(this.state.section)) {
         this._closeCurrentSection()
-        this._updateStateFromElement(node.parentElement)
+        this._updateStateFromElement(node.parentElement!)
       }
 
       // we can hit a list item after parsing a nested list, when that happens
       // and the lists are of different types we need to make sure we switch
       // the list type back
-      if (isListItem && lastSection && lastSection.isListSection) {
-        let parentElement = node.parentElement
+      if (isListItem && lastSection && sectionIsListSection(lastSection)) {
+        let parentElement = expect(node.parentElement, 'expected node to have parent element')
         let parentElementTagName = normalizeTagName(parentElement.tagName)
         if (parentElementTagName !== lastSection.tagName) {
           this._closeCurrentSection()
@@ -152,14 +196,14 @@ class SectionParser {
         !lastSection.isListSection
       ) {
         this._closeCurrentSection()
-        this._updateStateFromElement(node.parentElement)
+        this._updateStateFromElement(node.parentElement!)
       }
 
       // if we have consecutive list sections of different types (ul, ol) then
       // ensure we close the current section and start a new one
       let isNewListSection =
         lastSection &&
-        lastSection.isListSection &&
+        sectionIsListSection(lastSection) &&
         this.state.section.isListItem &&
         isListSection &&
         tagName !== lastSection.tagName
@@ -171,7 +215,10 @@ class SectionParser {
           this.state.section.isListItem &&
           tagName === 'p' &&
           !node.nextSibling &&
-          contains(VALID_LIST_ITEM_TAGNAMES, normalizeTagName(node.parentElement.tagName))
+          contains(
+            VALID_LIST_ITEM_TAGNAMES,
+            normalizeTagName(expect(node.parentElement, 'expected node to have parent element').tagName)
+          )
         ) {
           this.parseElementNode(node)
           return
@@ -179,7 +226,7 @@ class SectionParser {
 
         // avoid creating empty paragraphs due to wrapper elements around
         // section-creating elements
-        if (this.state.section.isMarkerable && !this.state.text && this.state.section.markers.length === 0) {
+        if (isMarkerable(this.state.section) && !this.state.text && this.state.section.markers.length === 0) {
           this.state.section = null
         } else {
           this._closeCurrentSection()
@@ -188,13 +235,13 @@ class SectionParser {
         this._updateStateFromElement(node)
       }
 
-      if (this.state.section.isListSection) {
+      if (this.state.section && this.state.section.isListSection) {
         // ensure the list section is closed and added to the sections list.
         // _closeCurrentSection handles pushing list items onto the list section
         this._closeCurrentSection()
 
         forEach(node.childNodes, node => {
-          this.parseNode(node)
+          this.parseNode(node as SectionParserNode)
         })
         return
       }
@@ -202,28 +249,29 @@ class SectionParser {
 
     switch (node.nodeType) {
       case NODE_TYPES.TEXT:
-        this.parseTextNode(node)
+        this.parseTextNode(node as Text)
         break
       case NODE_TYPES.ELEMENT:
-        this.parseElementNode(node)
+        this.parseElementNode(node as HTMLElement)
         break
     }
   }
 
-  parseElementNode(element) {
+  parseElementNode(element: HTMLElement) {
     let { state } = this
+    assert('expected markups to be non-null', state.markups)
 
     const markups = this._markupsFromElement(element)
-    if (markups.length && state.text.length && state.section.isMarkerable) {
+    if (markups.length && state.text!.length && isMarkerable(state.section!)) {
       this._createMarker()
     }
     state.markups.push(...markups)
 
     forEach(element.childNodes, node => {
-      this.parseNode(node)
+      this.parseNode(node as SectionParserNode)
     })
 
-    if (markups.length && state.text.length && state.section.isMarkerable) {
+    if (markups.length && state.text!.length && state.section!.isMarkerable) {
       // create the marker started for this node
       this._createMarker()
     }
@@ -232,12 +280,12 @@ class SectionParser {
     state.markups.splice(-markups.length, markups.length)
   }
 
-  parseTextNode(textNode) {
+  parseTextNode(textNode: Text) {
     let { state } = this
-    state.text += sanitize(textNode.textContent)
+    state.text += sanitize(textNode.textContent!)
   }
 
-  _updateStateFromElement(element) {
+  _updateStateFromElement(element: SectionParserNode) {
     if (isCommentNode(element)) {
       return
     }
@@ -257,18 +305,18 @@ class SectionParser {
     }
 
     // close a trailing text node if it exists
-    if (state.text.length && state.section.isMarkerable) {
+    if (state.text!.length && state.section.isMarkerable) {
       this._createMarker()
     }
 
     // push listItems onto the listSection or add a new section
-    if (state.section.isListItem && lastSection && lastSection.isListSection) {
+    if (sectionIsListItem(state.section) && lastSection && sectionIsListSection(lastSection)) {
       trimSectionText(state.section)
       lastSection.items.append(state.section)
     } else {
       // avoid creating empty markup sections, especially useful for indented source
       if (
-        state.section.isMarkerable &&
+        isMarkerable(state.section) &&
         !state.section.text.trim() &&
         !any(state.section.markers, marker => marker.isAtom)
       ) {
@@ -278,7 +326,7 @@ class SectionParser {
       }
 
       // remove empty list sections before creating a new section
-      if (lastSection && lastSection.isListSection && lastSection.items.length === 0) {
+      if (lastSection && sectionIsListSection(lastSection) && lastSection.items.length === 0) {
         sections.pop()
       }
 
@@ -289,9 +337,9 @@ class SectionParser {
     state.text = ''
   }
 
-  _markupsFromElement(element) {
+  _markupsFromElement(element: HTMLElement | Text) {
     let { builder } = this
-    let markups = []
+    let markups: Markup[] = []
     if (isTextNode(element)) {
       return markups
     }
@@ -306,7 +354,7 @@ class SectionParser {
     return markups
   }
 
-  _isValidMarkupForElement(tagName, element) {
+  _isValidMarkupForElement(tagName: string, element: HTMLElement) {
     if (VALID_MARKUP_TAGNAMES.indexOf(tagName) === -1) {
       return false
     } else if (tagName === 'b') {
@@ -317,9 +365,9 @@ class SectionParser {
     return true
   }
 
-  _markupsFromElementStyle(element) {
+  _markupsFromElementStyle(element: HTMLElement) {
     let { builder } = this
-    let markups = []
+    let markups: Markup[] = []
     let { fontStyle, fontWeight } = element.style
     if (fontStyle === 'italic') {
       markups.push(builder.createMarkup('em'))
@@ -332,15 +380,16 @@ class SectionParser {
 
   _createMarker() {
     let { state } = this
-    let text = transformHTMLText(state.text)
+    let text = transformHTMLText(state.text!)
     let marker = this.builder.createMarker(text, state.markups)
+    assertType<Markerable>('expected section to be markerable', state.section, isMarkerable(state.section!))
     state.section.markers.append(marker)
     state.text = ''
   }
 
-  _getSectionDetails(element) {
-    let sectionType,
-      tagName,
+  _getSectionDetails(element: HTMLElement | Text) {
+    let sectionType: string,
+      tagName: string,
       inferredTagName = false
 
     if (isTextNode(element)) {
@@ -376,13 +425,13 @@ class SectionParser {
     return { sectionType, tagName, inferredTagName }
   }
 
-  _createSectionFromElement(element) {
+  _createSectionFromElement(element: Comment | HTMLElement) {
     if (isCommentNode(element)) {
       return
     }
 
     let { builder } = this
-    let section
+    let section: Cloneable<Section>
     let { tagName, sectionType, inferredTagName } = this._getSectionDetails(element)
 
     switch (sectionType) {
@@ -394,7 +443,7 @@ class SectionParser {
         break
       case MARKUP_SECTION_TYPE:
         section = builder.createMarkupSection(tagName)
-        section._inferredTagName = inferredTagName
+        ;(section as MarkupSection)._inferredTagName = inferredTagName
         break
       default:
         assert('Cannot parse section from element', false)
@@ -403,12 +452,7 @@ class SectionParser {
     return section
   }
 
-  _isSkippable(element) {
-    return (
-      element.nodeType === NODE_TYPES.ELEMENT &&
-      contains(SKIPPABLE_ELEMENT_TAG_NAMES, normalizeTagName(element.tagName))
-    )
+  _isSkippable(element: Node) {
+    return isElementNode(element) && contains(SKIPPABLE_ELEMENT_TAG_NAMES, normalizeTagName(element.tagName))
   }
 }
-
-export default SectionParser
diff --git a/src/js/parsers/text.js b/src/js/parsers/text.ts
similarity index 61%
rename from src/js/parsers/text.js
rename to src/js/parsers/text.ts
index 65cbe72ad..60d05a9fb 100644
--- a/src/js/parsers/text.js
+++ b/src/js/parsers/text.ts
@@ -1,6 +1,12 @@
-import assert from 'mobiledoc-kit/utils/assert'
-import { MARKUP_SECTION_TYPE, LIST_SECTION_TYPE } from 'mobiledoc-kit/models/types'
-import { DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME } from 'mobiledoc-kit/models/markup-section'
+import assert from '../utils/assert'
+import { MARKUP_SECTION_TYPE, LIST_SECTION_TYPE } from '../models/types'
+import { DEFAULT_TAG_NAME as DEFAULT_MARKUP_SECTION_TAG_NAME } from '../models/markup-section'
+import PostNodeBuilder from '../models/post-node-builder'
+import Post from '../models/post'
+import Section from '../models/_section'
+import { Option } from '../utils/types'
+import ListSection, { isListSection } from '../models/list-section'
+import { Cloneable } from '../models/_cloneable'
 
 const UL_LI_REGEX = /^\* (.*)$/
 const OL_LI_REGEX = /^\d\.? (.*)$/
@@ -15,8 +21,16 @@ function normalizeLineEndings(text) {
   return text.replace(CR_LF_REGEX, LF).replace(CR_REGEX, LF)
 }
 
+export interface TextParserOptions {}
+
 export default class TextParser {
-  constructor(builder, options) {
+  builder: PostNodeBuilder
+  options: TextParserOptions
+  post: Post
+
+  prevSection: Option<Cloneable<Section>>
+
+  constructor(builder: PostNodeBuilder, options: TextParserOptions) {
     this.builder = builder
     this.options = options
 
@@ -28,7 +42,7 @@ export default class TextParser {
    * @param {String} text to parse
    * @return {Post} a post abstract
    */
-  parse(text) {
+  parse(text: string): Post {
     text = normalizeLineEndings(text)
     text.split(SECTION_BREAK).forEach(text => {
       let section = this._parseSection(text)
@@ -38,7 +52,7 @@ export default class TextParser {
     return this.post
   }
 
-  _parseSection(text) {
+  _parseSection(text: string) {
     let tagName = DEFAULT_MARKUP_SECTION_TAG_NAME,
       type = MARKUP_SECTION_TYPE,
       section
@@ -46,11 +60,11 @@ export default class TextParser {
     if (UL_LI_REGEX.test(text)) {
       tagName = 'ul'
       type = LIST_SECTION_TYPE
-      text = text.match(UL_LI_REGEX)[1]
+      text = text.match(UL_LI_REGEX)![1]
     } else if (OL_LI_REGEX.test(text)) {
       tagName = 'ol'
       type = LIST_SECTION_TYPE
-      text = text.match(OL_LI_REGEX)[1]
+      text = text.match(OL_LI_REGEX)![1]
     }
 
     let markers = [this.builder.createMarker(text)]
@@ -72,19 +86,19 @@ export default class TextParser {
     return section
   }
 
-  _appendSection(section) {
+  _appendSection(section: Cloneable<Section>) {
     let isSameListSection =
-      section.isListSection &&
+      isListSection(section) &&
       this.prevSection &&
-      this.prevSection.isListSection &&
+      isListSection(this.prevSection) &&
       this.prevSection.tagName === section.tagName
 
     if (isSameListSection) {
-      section.items.forEach(item => {
-        this.prevSection.items.append(item.clone())
+      ;(section as ListSection).items.forEach(item => {
+        ;(this.prevSection as ListSection).items.append(item.clone())
       })
     } else {
-      this.post.sections.insertAfter(section, this.prevSection)
+      this.post.sections.insertAfter(section, this.prevSection!)
       this.prevSection = section
     }
   }
diff --git a/src/js/renderers/editor-dom.ts b/src/js/renderers/editor-dom.ts
index 84758d595..0cb9e70d1 100644
--- a/src/js/renderers/editor-dom.ts
+++ b/src/js/renderers/editor-dom.ts
@@ -13,7 +13,7 @@ import Section from '../models/_section'
 import { Attributable } from '../models/_attributable'
 import { TagNameable } from '../models/_tag-nameable'
 import ListSection from '../models/list-section'
-import RenderNode, { PostNode } from '../models/render-node'
+import RenderNode from '../models/render-node'
 import { Option, Maybe } from '../utils/types'
 import Atom from '../models/atom'
 import Editor from '../editor/editor'
@@ -23,6 +23,7 @@ import ListItem from '../models/list-item'
 import Image from '../models/image'
 import Card from '../models/card'
 import RenderTree from '../models/render-tree'
+import { PostNode } from '../models/post-node-builder'
 
 export const CARD_ELEMENT_CLASS_NAME = '__mobiledoc-card'
 export const NO_BREAK_SPACE = '\u00A0'
diff --git a/src/js/renderers/mobiledoc/0-2.ts b/src/js/renderers/mobiledoc/0-2.ts
index aa450fbb4..7d2b096d0 100644
--- a/src/js/renderers/mobiledoc/0-2.ts
+++ b/src/js/renderers/mobiledoc/0-2.ts
@@ -9,12 +9,9 @@ import Image from '../../models/image'
 import Card from '../../models/card'
 import Marker from '../../models/marker'
 import Markup from '../../models/markup'
+import { MobiledocSectionKind } from './constants'
 
 export const MOBILEDOC_VERSION = '0.2.0'
-export const MOBILEDOC_MARKUP_SECTION_TYPE = 1
-export const MOBILEDOC_IMAGE_SECTION_TYPE = 2
-export const MOBILEDOC_LIST_SECTION_TYPE = 3
-export const MOBILEDOC_CARD_SECTION_TYPE = 10
 
 const visitor = {
   [Type.POST](node: Post, opcodes: Opcodes) {
@@ -48,38 +45,43 @@ const visitor = {
   },
 }
 
-type OpcodeCompilerMarker = [number[], number, unknown]
-type OpcodeCompilerSection =
-  | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[]]
-  | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][]]
-  | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string]
-  | [typeof MOBILEDOC_CARD_SECTION_TYPE, string, {}]
+export type MobiledocMarker = [number[], number, string]
+export type MobiledocMarkerType = [string, string[]?]
 
-type PostOpcodeCompilerMarkerType = [string, string[]?]
+export type MobiledocMarkupSection = [MobiledocSectionKind.MARKUP, string, MobiledocMarker[]]
+export type MobiledocListSection = [MobiledocSectionKind.LIST, string, MobiledocMarker[][]]
+export type MobiledocImageSection = [MobiledocSectionKind.IMAGE, string]
+export type MobiledocCardSection = [MobiledocSectionKind.CARD, string, {}]
+
+export type MobiledocSection =
+  | MobiledocMarkupSection
+  | MobiledocListSection
+  | MobiledocImageSection
+  | MobiledocCardSection
 
 class PostOpcodeCompiler {
   markupMarkerIds!: number[]
-  markers!: OpcodeCompilerMarker[]
-  sections!: OpcodeCompilerSection[]
-  items!: OpcodeCompilerMarker[][]
-  markerTypes!: PostOpcodeCompilerMarkerType[]
+  markers!: MobiledocMarker[]
+  sections!: MobiledocSection[]
+  items!: MobiledocMarker[][]
+  markerTypes!: MobiledocMarkerType[]
   result!: MobiledocV0_2
 
   _markerTypeCache!: { [key: string]: number }
 
-  openMarker(closeCount: number, value: unknown) {
+  openMarker(closeCount: number, value: string) {
     this.markupMarkerIds = []
     this.markers.push([this.markupMarkerIds, closeCount, value || ''])
   }
 
   openMarkupSection(tagName: string) {
     this.markers = []
-    this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers])
+    this.sections.push([MobiledocSectionKind.MARKUP, tagName, this.markers])
   }
 
   openListSection(tagName: string) {
     this.items = []
-    this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items])
+    this.sections.push([MobiledocSectionKind.LIST, tagName, this.items])
   }
 
   openListItem() {
@@ -88,11 +90,11 @@ class PostOpcodeCompiler {
   }
 
   openImageSection(url: string) {
-    this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url])
+    this.sections.push([MobiledocSectionKind.IMAGE, url])
   }
 
   openCardSection(name: string, payload: {}) {
-    this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, name, payload])
+    this.sections.push([MobiledocSectionKind.CARD, name, payload])
   }
 
   openPost() {
@@ -117,7 +119,7 @@ class PostOpcodeCompiler {
 
     let index = this._markerTypeCache[key]
     if (index === undefined) {
-      let markerType: PostOpcodeCompilerMarkerType = [tagName]
+      let markerType: MobiledocMarkerType = [tagName]
       if (attributesArray.length) {
         markerType.push(attributesArray)
       }
@@ -133,7 +135,7 @@ class PostOpcodeCompiler {
 
 export interface MobiledocV0_2 {
   version: typeof MOBILEDOC_VERSION
-  sections: [PostOpcodeCompilerMarkerType[], OpcodeCompilerSection[]]
+  sections: [MobiledocMarkerType[], MobiledocSection[]]
 }
 
 /**
diff --git a/src/js/renderers/mobiledoc/0-3-1.ts b/src/js/renderers/mobiledoc/0-3-1.ts
index 42f7b3297..be31307f0 100644
--- a/src/js/renderers/mobiledoc/0-3-1.ts
+++ b/src/js/renderers/mobiledoc/0-3-1.ts
@@ -11,15 +11,10 @@ import Marker from '../../models/marker'
 import Markup from '../../models/markup'
 import Atom from '../../models/atom'
 import { Dict } from '../../utils/types'
+import { MobiledocSectionKind, MobiledocMarkerKind } from './constants'
+import { MobiledocMarker, MobiledocSection, MobiledocMarkerType, MobiledocAtom, MobiledocCard } from './0-3'
 
 export const MOBILEDOC_VERSION = '0.3.1'
-export const MOBILEDOC_MARKUP_SECTION_TYPE = 1
-export const MOBILEDOC_IMAGE_SECTION_TYPE = 2
-export const MOBILEDOC_LIST_SECTION_TYPE = 3
-export const MOBILEDOC_CARD_SECTION_TYPE = 10
-
-export const MOBILEDOC_MARKUP_MARKER_TYPE = 0
-export const MOBILEDOC_ATOM_MARKER_TYPE = 1
 
 const visitor = {
   [Type.POST](node: Post, opcodes: Opcodes) {
@@ -57,42 +52,37 @@ const visitor = {
   },
 }
 
-type OpcodeCompilerMarker = [number, number[], number, unknown]
-type OpcodeCompilerSection =
-  | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[]]
-  | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][]]
-  | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string]
-  | [typeof MOBILEDOC_CARD_SECTION_TYPE, number]
-
-type OpcodeCompilerAtom = [string, unknown, {}]
-type OpcodeCompilerCard = [string, {}]
-type OpcodeCompilerMarkerType = [string, string[]?]
-
 class PostOpcodeCompiler {
   markupMarkerIds!: number[]
-  markers!: OpcodeCompilerMarker[]
-  sections!: OpcodeCompilerSection[]
-  items!: OpcodeCompilerMarker[][]
-  markerTypes!: OpcodeCompilerMarkerType[]
-  atomTypes!: OpcodeCompilerAtom[]
-  cardTypes!: OpcodeCompilerCard[]
+  markers!: MobiledocMarker[]
+  sections!: MobiledocSection[]
+  items!: MobiledocMarker[][]
+  markerTypes!: MobiledocMarkerType[]
+  atomTypes!: MobiledocAtom[]
+  cardTypes!: MobiledocCard[]
   result!: MobiledocV0_3_1
 
   _markerTypeCache!: Dict<number>
 
-  openMarker(closeCount: number, value: unknown) {
+  openMarker(closeCount: number, value: string) {
     this.markupMarkerIds = []
-    this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || ''])
+    this.markers.push([MobiledocMarkerKind.MARKUP, this.markupMarkerIds, closeCount, value || ''])
+  }
+
+  openAtom(closeCount: number, name: string, value: string, payload: {}) {
+    const index = this._addAtomTypeIndex(name, value, payload)
+    this.markupMarkerIds = []
+    this.markers.push([MobiledocMarkerKind.ATOM, this.markupMarkerIds, closeCount, index])
   }
 
   openMarkupSection(tagName: string) {
     this.markers = []
-    this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers])
+    this.sections.push([MobiledocSectionKind.MARKUP, tagName, this.markers])
   }
 
   openListSection(tagName: string) {
     this.items = []
-    this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items])
+    this.sections.push([MobiledocSectionKind.LIST, tagName, this.items])
   }
 
   openListItem() {
@@ -101,18 +91,12 @@ class PostOpcodeCompiler {
   }
 
   openImageSection(url: string) {
-    this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url])
+    this.sections.push([MobiledocSectionKind.IMAGE, url])
   }
 
   openCardSection(name: string, payload: {}) {
     const index = this._addCardTypeIndex(name, payload)
-    this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index])
-  }
-
-  openAtom(closeCount: number, name: string, value: unknown, payload: {}) {
-    const index = this._addAtomTypeIndex(name, value, payload)
-    this.markupMarkerIds = []
-    this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index])
+    this.sections.push([MobiledocSectionKind.CARD, index])
   }
 
   openPost() {
@@ -135,13 +119,13 @@ class PostOpcodeCompiler {
   }
 
   _addCardTypeIndex(cardName: string, payload: {}) {
-    let cardType: OpcodeCompilerCard = [cardName, payload]
+    let cardType: MobiledocCard = [cardName, payload]
     this.cardTypes.push(cardType)
     return this.cardTypes.length - 1
   }
 
-  _addAtomTypeIndex(atomName: string, atomValue: unknown, payload: {}) {
-    let atomType: OpcodeCompilerAtom = [atomName, atomValue, payload]
+  _addAtomTypeIndex(atomName: string, atomValue: string, payload: {}) {
+    let atomType: MobiledocAtom = [atomName, atomValue, payload]
     this.atomTypes.push(atomType)
     return this.atomTypes.length - 1
   }
@@ -154,7 +138,7 @@ class PostOpcodeCompiler {
 
     let index = this._markerTypeCache[key]
     if (index === undefined) {
-      let markerType: OpcodeCompilerMarkerType = [tagName]
+      let markerType: MobiledocMarkerType = [tagName]
       if (attributesArray.length) {
         markerType.push(attributesArray)
       }
@@ -170,10 +154,10 @@ class PostOpcodeCompiler {
 
 export interface MobiledocV0_3_1 {
   version: typeof MOBILEDOC_VERSION
-  atoms: OpcodeCompilerAtom[]
-  cards: OpcodeCompilerCard[]
-  markups: OpcodeCompilerMarkerType[]
-  sections: OpcodeCompilerSection[]
+  atoms: MobiledocAtom[]
+  cards: MobiledocCard[]
+  markups: MobiledocMarkerType[]
+  sections: MobiledocSection[]
 }
 
 /**
diff --git a/src/js/renderers/mobiledoc/0-3-2.ts b/src/js/renderers/mobiledoc/0-3-2.ts
index 882736b05..94cd9425f 100644
--- a/src/js/renderers/mobiledoc/0-3-2.ts
+++ b/src/js/renderers/mobiledoc/0-3-2.ts
@@ -11,15 +11,18 @@ import Marker from '../../models/marker'
 import Markup from '../../models/markup'
 import Atom from '../../models/atom'
 import { Dict } from '../../utils/types'
+import { MobiledocSectionKind, MobiledocMarkerKind } from './constants'
+import { MobiledocCard, MobiledocAtom, MobiledocMarker, MobiledocSection, MobiledocMarkerType } from './0-3'
 
 export const MOBILEDOC_VERSION = '0.3.2'
-export const MOBILEDOC_MARKUP_SECTION_TYPE = 1
-export const MOBILEDOC_IMAGE_SECTION_TYPE = 2
-export const MOBILEDOC_LIST_SECTION_TYPE = 3
-export const MOBILEDOC_CARD_SECTION_TYPE = 10
 
-export const MOBILEDOC_MARKUP_MARKER_TYPE = 0
-export const MOBILEDOC_ATOM_MARKER_TYPE = 1
+export type MobiledocAttributedMarkupSection = [MobiledocSectionKind.MARKUP, string, MobiledocMarker[], string[]]
+export type MobiledocAttributedListSection = [MobiledocSectionKind.LIST, string, MobiledocMarker[][], string[]]
+
+export type MobiledocAttributedSection =
+  | MobiledocSection
+  | MobiledocAttributedMarkupSection
+  | MobiledocAttributedListSection
 
 const visitor = {
   [Type.POST](node: Post, opcodes: Opcodes) {
@@ -56,50 +59,44 @@ const visitor = {
     visitArray(visitor, node.openedMarkups, opcodes)
   },
 }
-
-type OpcodeCompilerMarker = [number, number[], number, unknown]
-type OpcodeCompilerSection =
-  | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[], string[]?]
-  | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][], string[]?]
-  | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string]
-  | [typeof MOBILEDOC_CARD_SECTION_TYPE, number]
-
-type OpcodeCompilerAtom = [string, unknown, {}]
-type OpcodeCompilerCard = [string, {}]
-type OpcodeCompilerMarkerType = [string, string[]?]
-
 class PostOpcodeCompiler {
   markupMarkerIds!: number[]
-  markers!: OpcodeCompilerMarker[]
-  sections!: OpcodeCompilerSection[]
-  items!: OpcodeCompilerMarker[][]
-  markerTypes!: OpcodeCompilerMarkerType[]
-  atomTypes!: OpcodeCompilerAtom[]
-  cardTypes!: OpcodeCompilerCard[]
+  markers!: MobiledocMarker[]
+  sections!: MobiledocAttributedSection[]
+  items!: MobiledocMarker[][]
+  markerTypes!: MobiledocMarkerType[]
+  atomTypes!: MobiledocAtom[]
+  cardTypes!: MobiledocCard[]
   result!: MobiledocV0_3_2
 
   _markerTypeCache!: Dict<number>
 
-  openMarker(closeCount: number, value: unknown) {
+  openMarker(closeCount: number, value: string) {
+    this.markupMarkerIds = []
+    this.markers.push([MobiledocMarkerKind.MARKUP, this.markupMarkerIds, closeCount, value || ''])
+  }
+
+  openAtom(closeCount: number, name: string, value: string, payload: {}) {
+    const index = this._addAtomTypeIndex(name, value, payload)
     this.markupMarkerIds = []
-    this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || ''])
+    this.markers.push([MobiledocMarkerKind.ATOM, this.markupMarkerIds, closeCount, index])
   }
 
   openMarkupSection(tagName: string, attributes: string[]) {
     this.markers = []
     if (attributes && attributes.length !== 0) {
-      this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers, attributes])
+      this.sections.push([MobiledocSectionKind.MARKUP, tagName, this.markers, attributes])
     } else {
-      this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers])
+      this.sections.push([MobiledocSectionKind.MARKUP, tagName, this.markers])
     }
   }
 
   openListSection(tagName: string, attributes: string[]) {
     this.items = []
     if (attributes && attributes.length !== 0) {
-      this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items, attributes])
+      this.sections.push([MobiledocSectionKind.LIST, tagName, this.items, attributes])
     } else {
-      this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items])
+      this.sections.push([MobiledocSectionKind.LIST, tagName, this.items])
     }
   }
 
@@ -109,18 +106,12 @@ class PostOpcodeCompiler {
   }
 
   openImageSection(url: string) {
-    this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url])
+    this.sections.push([MobiledocSectionKind.IMAGE, url])
   }
 
   openCardSection(name: string, payload: {}) {
     const index = this._addCardTypeIndex(name, payload)
-    this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index])
-  }
-
-  openAtom(closeCount: number, name: string, value: unknown, payload: {}) {
-    const index = this._addAtomTypeIndex(name, value, payload)
-    this.markupMarkerIds = []
-    this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index])
+    this.sections.push([MobiledocSectionKind.CARD, index])
   }
 
   openPost() {
@@ -143,13 +134,13 @@ class PostOpcodeCompiler {
   }
 
   _addCardTypeIndex(cardName: string, payload: {}) {
-    let cardType: OpcodeCompilerCard = [cardName, payload]
+    let cardType: MobiledocCard = [cardName, payload]
     this.cardTypes.push(cardType)
     return this.cardTypes.length - 1
   }
 
-  _addAtomTypeIndex(atomName: string, atomValue: unknown, payload: {}) {
-    let atomType: OpcodeCompilerAtom = [atomName, atomValue, payload]
+  _addAtomTypeIndex(atomName: string, atomValue: string, payload: {}) {
+    let atomType: MobiledocAtom = [atomName, atomValue, payload]
     this.atomTypes.push(atomType)
     return this.atomTypes.length - 1
   }
@@ -162,7 +153,7 @@ class PostOpcodeCompiler {
 
     let index = this._markerTypeCache[key]
     if (index === undefined) {
-      let markerType: OpcodeCompilerMarkerType = [tagName]
+      let markerType: MobiledocMarkerType = [tagName]
       if (attributesArray.length) {
         markerType.push(attributesArray)
       }
@@ -178,10 +169,10 @@ class PostOpcodeCompiler {
 
 export interface MobiledocV0_3_2 {
   version: typeof MOBILEDOC_VERSION
-  atoms: OpcodeCompilerAtom[]
-  cards: OpcodeCompilerCard[]
-  markups: OpcodeCompilerMarkerType[]
-  sections: OpcodeCompilerSection[]
+  atoms: MobiledocAtom[]
+  cards: MobiledocCard[]
+  markups: MobiledocMarkerType[]
+  sections: MobiledocAttributedSection[]
 }
 
 /**
diff --git a/src/js/renderers/mobiledoc/0-3.ts b/src/js/renderers/mobiledoc/0-3.ts
index 20bb4fef1..8b38e8747 100644
--- a/src/js/renderers/mobiledoc/0-3.ts
+++ b/src/js/renderers/mobiledoc/0-3.ts
@@ -11,15 +11,9 @@ import Marker from '../../models/marker'
 import Markup from '../../models/markup'
 import Atom from '../../models/atom'
 import { Dict } from '../../utils/types'
+import { MobiledocSectionKind, MobiledocMarkerKind } from './constants'
 
 export const MOBILEDOC_VERSION = '0.3.0'
-export const MOBILEDOC_MARKUP_SECTION_TYPE = 1
-export const MOBILEDOC_IMAGE_SECTION_TYPE = 2
-export const MOBILEDOC_LIST_SECTION_TYPE = 3
-export const MOBILEDOC_CARD_SECTION_TYPE = 10
-
-export const MOBILEDOC_MARKUP_MARKER_TYPE = 0
-export const MOBILEDOC_ATOM_MARKER_TYPE = 1
 
 const visitor = {
   [Type.POST](node: Post, opcodes: Opcodes) {
@@ -57,42 +51,57 @@ const visitor = {
   },
 }
 
-type OpcodeCompilerMarker = [number, number[], number, unknown]
-type OpcodeCompilerSection =
-  | [typeof MOBILEDOC_MARKUP_SECTION_TYPE, string, OpcodeCompilerMarker[]]
-  | [typeof MOBILEDOC_LIST_SECTION_TYPE, string, OpcodeCompilerMarker[][]]
-  | [typeof MOBILEDOC_IMAGE_SECTION_TYPE, string]
-  | [typeof MOBILEDOC_CARD_SECTION_TYPE, number]
+export type MobiledocMarkupMarker = [MobiledocMarkerKind.MARKUP, number[], number, string]
+export type MobiledocAtomMarker = [MobiledocMarkerKind.ATOM, number[], number, number]
+
+export type MobiledocMarker = MobiledocMarkupMarker | MobiledocAtomMarker
+
+export type MobiledocMarkupSection = [MobiledocSectionKind.MARKUP, string, MobiledocMarker[]]
+export type MobiledocListSection = [MobiledocSectionKind.LIST, string, MobiledocMarker[][]]
+export type MobiledocImageSection = [MobiledocSectionKind.IMAGE, string]
+export type MobiledocCardSection = [MobiledocSectionKind.CARD, number]
 
-type OpcodeCompilerAtom = [string, unknown, {}]
-type OpcodeCompilerCard = [string, {}]
-type OpcodeCompilerMarkerType = [string, string[]?]
+export type MobiledocSection =
+  | MobiledocMarkupSection
+  | MobiledocListSection
+  | MobiledocImageSection
+  | MobiledocCardSection
+
+export type MobiledocAtom = [string, string, {}]
+export type MobiledocCard = [string, {}]
+export type MobiledocMarkerType = [string, string[]?]
 
 class PostOpcodeCompiler {
   markupMarkerIds!: number[]
-  markers!: OpcodeCompilerMarker[]
-  sections!: OpcodeCompilerSection[]
-  items!: OpcodeCompilerMarker[][]
-  markerTypes!: OpcodeCompilerMarkerType[]
-  atomTypes!: OpcodeCompilerAtom[]
-  cardTypes!: OpcodeCompilerCard[]
+  markers!: MobiledocMarker[]
+  sections!: MobiledocSection[]
+  items!: MobiledocMarker[][]
+  markerTypes!: MobiledocMarkerType[]
+  atomTypes!: MobiledocAtom[]
+  cardTypes!: MobiledocCard[]
   result!: MobiledocV0_3
 
   _markerTypeCache!: Dict<number>
 
-  openMarker(closeCount: number, value: unknown) {
+  openMarker(closeCount: number, value: string) {
+    this.markupMarkerIds = []
+    this.markers.push([MobiledocMarkerKind.MARKUP, this.markupMarkerIds, closeCount, value || ''])
+  }
+
+  openAtom(closeCount: number, name: string, value: string, payload: {}) {
+    const index = this._addAtomTypeIndex(name, value, payload)
     this.markupMarkerIds = []
-    this.markers.push([MOBILEDOC_MARKUP_MARKER_TYPE, this.markupMarkerIds, closeCount, value || ''])
+    this.markers.push([MobiledocMarkerKind.ATOM, this.markupMarkerIds, closeCount, index])
   }
 
   openMarkupSection(tagName: string) {
     this.markers = []
-    this.sections.push([MOBILEDOC_MARKUP_SECTION_TYPE, tagName, this.markers])
+    this.sections.push([MobiledocSectionKind.MARKUP, tagName, this.markers])
   }
 
   openListSection(tagName: string) {
     this.items = []
-    this.sections.push([MOBILEDOC_LIST_SECTION_TYPE, tagName, this.items])
+    this.sections.push([MobiledocSectionKind.LIST, tagName, this.items])
   }
 
   openListItem() {
@@ -101,18 +110,12 @@ class PostOpcodeCompiler {
   }
 
   openImageSection(url: string) {
-    this.sections.push([MOBILEDOC_IMAGE_SECTION_TYPE, url])
+    this.sections.push([MobiledocSectionKind.IMAGE, url])
   }
 
   openCardSection(name: string, payload: {}) {
     const index = this._addCardTypeIndex(name, payload)
-    this.sections.push([MOBILEDOC_CARD_SECTION_TYPE, index])
-  }
-
-  openAtom(closeCount: number, name: string, value: unknown, payload: {}) {
-    const index = this._addAtomTypeIndex(name, value, payload)
-    this.markupMarkerIds = []
-    this.markers.push([MOBILEDOC_ATOM_MARKER_TYPE, this.markupMarkerIds, closeCount, index])
+    this.sections.push([MobiledocSectionKind.CARD, index])
   }
 
   openPost() {
@@ -135,13 +138,13 @@ class PostOpcodeCompiler {
   }
 
   _addCardTypeIndex(cardName: string, payload: {}) {
-    let cardType: OpcodeCompilerCard = [cardName, payload]
+    let cardType: MobiledocCard = [cardName, payload]
     this.cardTypes.push(cardType)
     return this.cardTypes.length - 1
   }
 
-  _addAtomTypeIndex(atomName: string, atomValue: unknown, payload: {}) {
-    let atomType: OpcodeCompilerAtom = [atomName, atomValue, payload]
+  _addAtomTypeIndex(atomName: string, atomValue: string, payload: {}) {
+    let atomType: MobiledocAtom = [atomName, atomValue, payload]
     this.atomTypes.push(atomType)
     return this.atomTypes.length - 1
   }
@@ -154,7 +157,7 @@ class PostOpcodeCompiler {
 
     let index = this._markerTypeCache[key]
     if (index === undefined) {
-      let markerType: OpcodeCompilerMarkerType = [tagName]
+      let markerType: MobiledocMarkerType = [tagName]
       if (attributesArray.length) {
         markerType.push(attributesArray)
       }
@@ -170,10 +173,10 @@ class PostOpcodeCompiler {
 
 export interface MobiledocV0_3 {
   version: typeof MOBILEDOC_VERSION
-  atoms: OpcodeCompilerAtom[]
-  cards: OpcodeCompilerCard[]
-  markups: OpcodeCompilerMarkerType[]
-  sections: OpcodeCompilerSection[]
+  atoms: MobiledocAtom[]
+  cards: MobiledocCard[]
+  markups: MobiledocMarkerType[]
+  sections: MobiledocSection[]
 }
 
 /**
diff --git a/src/js/renderers/mobiledoc/constants.ts b/src/js/renderers/mobiledoc/constants.ts
new file mode 100644
index 000000000..5868b1a7e
--- /dev/null
+++ b/src/js/renderers/mobiledoc/constants.ts
@@ -0,0 +1,11 @@
+export const enum MobiledocSectionKind {
+  MARKUP = 1,
+  IMAGE = 2,
+  LIST = 3,
+  CARD = 10,
+}
+
+export const enum MobiledocMarkerKind {
+  MARKUP = 0,
+  ATOM = 1,
+}
diff --git a/src/js/utils/assert.ts b/src/js/utils/assert.ts
index 96e0b6683..7bd0ef66c 100644
--- a/src/js/utils/assert.ts
+++ b/src/js/utils/assert.ts
@@ -1,6 +1,6 @@
 import MobiledocError from './mobiledoc-error'
 
-export default function (message: string, conditional: unknown): asserts conditional {
+export default function assert(message: string, conditional: unknown): asserts conditional {
   if (!conditional) {
     throw new MobiledocError(message)
   }
@@ -12,6 +12,10 @@ export function assertNotNull<T>(message: string, value: T | null): asserts valu
   }
 }
 
+export function assertType<T>(message: string, _value: any, conditional: boolean): asserts _value is T {
+  assert(message, conditional)
+}
+
 export function expect<T>(value: T | null | undefined, message: string): T {
   if (value === null || value === undefined) {
     throw new MobiledocError(message)
diff --git a/src/js/utils/cursor.ts b/src/js/utils/cursor.ts
index 08220178e..8faa4939a 100644
--- a/src/js/utils/cursor.ts
+++ b/src/js/utils/cursor.ts
@@ -10,6 +10,7 @@ import Post from '../models/post'
 import { unwrap, assertNotNull, expect } from './assert'
 import { isCardSection } from '../models/card'
 import Section from '../models/_section'
+import { Type } from '../models/types'
 
 export { Position, Range }
 
@@ -95,7 +96,7 @@ class Cursor {
       } else {
         node = section.renderNode.element!.lastChild
       }
-    } else if (section.isBlank) {
+    } else if (section.isBlank || section.type === Type.IMAGE_SECTION) {
       node = section.renderNode.cursorElement
       offset = 0
     } else {
diff --git a/src/js/utils/cursor/position.ts b/src/js/utils/cursor/position.ts
index 2c6c6f0ad..fbf4360a2 100644
--- a/src/js/utils/cursor/position.ts
+++ b/src/js/utils/cursor/position.ts
@@ -3,7 +3,7 @@ import RenderTree from '../../models/render-tree'
 import { isTextNode, containsNode, isElementNode } from '../dom-utils'
 import { findOffsetInNode } from '../selection-utils'
 import { DIRECTION } from '../key'
-import assert from '../assert'
+import assert, { assertType } from '../assert'
 import Range from './range'
 import Markerable from '../../models/_markerable'
 import Section from '../../models/_section'
@@ -72,10 +72,6 @@ function assertIsCard(section: any): asserts section is Card {
   assert('findOffsetInSection must be called with markerable or card section', section && section.isCardSection)
 }
 
-function assertType<T>(message: string, _value: any, conditional: boolean): asserts _value is T {
-  assert(message, conditional)
-}
-
 function isMarkerable(section: Section): section is Markerable {
   return section.isMarkerable
 }
@@ -159,7 +155,7 @@ export default class Position {
    * (i.e., the marker to the left of the cursor if the cursor is on a marker boundary and text is left-to-right)
    * @return {Marker|undefined}
    */
-  get marker(): Marker | null {
+  get marker(): Markuperable | null {
     return (this.isMarkerable && this.markerPosition.marker) || null
   }
 
diff --git a/src/js/utils/cursor/range.ts b/src/js/utils/cursor/range.ts
index 7b9e61673..ec5047365 100644
--- a/src/js/utils/cursor/range.ts
+++ b/src/js/utils/cursor/range.ts
@@ -2,9 +2,9 @@ import Position from './position'
 import { DIRECTION } from '../key'
 import assert, { assertNotNull, unwrap } from '../assert'
 import Markerable from '../../models/_markerable'
-import Marker from '../../models/marker'
 import MobiledocError from '../mobiledoc-error'
 import Section from '../../models/_section'
+import Markuperable from '../markuperable'
 
 export type Direction = DIRECTION | null
 /**
@@ -139,7 +139,7 @@ export default class Range {
    *
    * @public
    */
-  expandByMarker(detectMarker: (marker: Marker) => boolean) {
+  expandByMarker(detectMarker: (marker: Markuperable) => boolean) {
     let { head, tail, direction } = this
     let { section: headSection } = head
 
@@ -152,11 +152,15 @@ export default class Range {
       )
     }
 
-    let firstNotMatchingDetect = (i: Marker) => {
+    let firstNotMatchingDetect = (i: Markuperable) => {
       return !detectMarker(i)
     }
 
-    let headMarker: Marker | null | undefined = headSection.markers.detect(firstNotMatchingDetect, head.marker, true)
+    let headMarker: Markuperable | null | undefined = headSection.markers.detect(
+      firstNotMatchingDetect,
+      head.marker,
+      true
+    )
     if (!headMarker && detectMarker(headSection.markers.head!)) {
       headMarker = headSection.markers.head
     } else {
diff --git a/src/js/utils/dom-utils.ts b/src/js/utils/dom-utils.ts
index 347fae0a2..e7877fb5f 100644
--- a/src/js/utils/dom-utils.ts
+++ b/src/js/utils/dom-utils.ts
@@ -10,7 +10,7 @@ export function isTextNode(node: Node): node is Text {
   return node.nodeType === NODE_TYPES.TEXT
 }
 
-export function isCommentNode(node: Node) {
+export function isCommentNode(node: Node): node is Comment {
   return node.nodeType === NODE_TYPES.COMMENT
 }
 
diff --git a/src/js/utils/element-utils.ts b/src/js/utils/element-utils.ts
index 45d2be18f..e999e4b8d 100644
--- a/src/js/utils/element-utils.ts
+++ b/src/js/utils/element-utils.ts
@@ -69,7 +69,11 @@ export function setData(element: HTMLElement, name: string, value: string) {
   }
 }
 
-export function whenElementIsNotInDOM(element: HTMLElement, callback: () => void) {
+export interface Cancelable {
+  cancel(): void
+}
+
+export function whenElementIsNotInDOM(element: HTMLElement, callback: () => void): Cancelable {
   let isCanceled = false
   const observerFn = () => {
     if (isCanceled) {
diff --git a/src/js/utils/log-manager.ts b/src/js/utils/log-manager.ts
index fec68a029..9a99707af 100644
--- a/src/js/utils/log-manager.ts
+++ b/src/js/utils/log-manager.ts
@@ -1,4 +1,4 @@
-class Logger {
+export class Logger {
   type: string
   manager: LogManager
 
diff --git a/src/js/utils/markuperable.ts b/src/js/utils/markuperable.ts
index c88200ffb..a9935d2b3 100644
--- a/src/js/utils/markuperable.ts
+++ b/src/js/utils/markuperable.ts
@@ -1,8 +1,10 @@
 import { normalizeTagName } from './dom-utils'
 import { detect, commonItemLength, forEach, filter } from './array-utils'
+import { Option } from './types'
 import Markup from '../models/markup'
 import RenderNode from '../models/render-node'
 import { Type } from '../models/types'
+import Markerable from '../models/_markerable'
 
 type MarkupCallback = (markup: Markup) => boolean
 type MarkupOrMarkupCallback = Markup | MarkupCallback
@@ -16,10 +18,24 @@ export default abstract class Markuperable {
   isAtom = false
   isMarker = false
 
+  section: Option<Markerable> = null
+  parent: Option<Markerable> = null
+
   renderNode: RenderNode | null = null
 
+  abstract text: string
+  abstract value: string
   abstract type: Type
   abstract length: number
+  abstract clone(): Markuperable
+  abstract isBlank: boolean
+  abstract canJoin(other: Markuperable): boolean
+  abstract textUntil(offset: number): string
+  abstract splitAtOffset(offset: number): [Markuperable, Markuperable]
+
+  charAt(offset: number) {
+    return this.value.slice(offset, offset + 1)
+  }
 
   clearMarkups() {
     this.markups = []
diff --git a/src/js/utils/object-utils.ts b/src/js/utils/object-utils.ts
index 7636e54c0..7acd93ecc 100644
--- a/src/js/utils/object-utils.ts
+++ b/src/js/utils/object-utils.ts
@@ -1,7 +1,7 @@
-export function entries<T extends { [key: string]: unknown }, K extends keyof T>(obj: T): [keyof T, T[K]][] {
+export function entries<T extends { [key: string]: unknown }, K extends Extract<keyof T, string>>(obj: T): [K, T[K]][] {
   const ownProps = Object.keys(obj) as K[]
   let i = ownProps.length
-  const resArray = new Array<[keyof T, T[K]]>(i)
+  const resArray = new Array<[K, T[K]]>(i)
 
   while (i--) {
     resArray[i] = [ownProps[i], obj[ownProps[i]]]
diff --git a/src/js/utils/parse-utils.js b/src/js/utils/parse-utils.ts
similarity index 71%
rename from src/js/utils/parse-utils.js
rename to src/js/utils/parse-utils.ts
index dde21b1df..ba1456356 100644
--- a/src/js/utils/parse-utils.js
+++ b/src/js/utils/parse-utils.ts
@@ -1,7 +1,12 @@
-/* global JSON */
 import mobiledocParsers from '../parsers/mobiledoc'
 import HTMLParser from '../parsers/html'
 import TextParser from '../parsers/text'
+import PostNodeBuilder from '../models/post-node-builder'
+import { SectionParserPlugin } from '../parsers/section'
+import Post from '../models/post'
+import { Logger } from './log-manager'
+import Editor from '../editor/editor'
+import { Maybe } from './types'
 
 export const MIME_TEXT_PLAIN = 'text/plain'
 export const MIME_TEXT_HTML = 'text/html'
@@ -13,11 +18,11 @@ const MOBILEDOC_REGEX = new RegExp(/data-mobiledoc='(.*?)'>/)
  * @return {Post}
  * @private
  */
-function parsePostFromHTML(html, builder, plugins) {
-  let post
+function parsePostFromHTML(html: string, builder: PostNodeBuilder, plugins: SectionParserPlugin[]): Post {
+  let post: Post
 
   if (MOBILEDOC_REGEX.test(html)) {
-    let mobiledocString = html.match(MOBILEDOC_REGEX)[1]
+    let mobiledocString = html.match(MOBILEDOC_REGEX)![1]
     let mobiledoc = JSON.parse(mobiledocString)
     post = mobiledocParsers.parse(builder, mobiledoc)
   } else {
@@ -31,17 +36,22 @@ function parsePostFromHTML(html, builder, plugins) {
  * @return {Post}
  * @private
  */
-function parsePostFromText(text, builder, plugins) {
+function parsePostFromText(text: string, builder: PostNodeBuilder, plugins: SectionParserPlugin[]): Post {
   let parser = new TextParser(builder, { plugins })
   let post = parser.parse(text)
   return post
 }
 
+// Extend TypeScript's Window interface to include clipboardData from events
+interface Window {
+  readonly clipboardData: DataTransfer | null
+}
+
 /**
  * @return {{html: String, text: String}}
  * @private
  */
-export function getContentFromPasteEvent(event, window) {
+export function getContentFromPasteEvent(event: ClipboardEvent, window: Window) {
   let html = '',
     text = ''
 
@@ -65,13 +75,13 @@ export function getContentFromPasteEvent(event, window) {
  * @return {{html: String, text: String}}
  * @private
  */
-function getContentFromDropEvent(event, logger) {
+function getContentFromDropEvent(event: DragEvent, logger?: Logger): { html: string; text: string } {
   let html = '',
     text = ''
 
   try {
-    html = event.dataTransfer.getData(MIME_TEXT_HTML)
-    text = event.dataTransfer.getData(MIME_TEXT_PLAIN)
+    html = event.dataTransfer!.getData(MIME_TEXT_HTML)
+    text = event.dataTransfer!.getData(MIME_TEXT_PLAIN)
   } catch (e) {
     // FIXME IE11 does not include any data in the 'text/html' or 'text/plain'
     // mimetypes. It throws an error 'Invalid argument' when attempting to read
@@ -90,7 +100,7 @@ function getContentFromDropEvent(event, logger) {
  * @param {Window}
  * @private
  */
-export function setClipboardData(event, { mobiledoc, html, text }, window) {
+export function setClipboardData(event: ClipboardEvent, { mobiledoc, html, text }: Editor, window: Window) {
   if (mobiledoc && html) {
     html = `<div data-mobiledoc='${JSON.stringify(mobiledoc)}'>${html}</div>`
   }
@@ -116,11 +126,11 @@ export function setClipboardData(event, { mobiledoc, html, text }, window) {
  * @private
  */
 export function parsePostFromPaste(
-  pasteEvent,
-  { builder, _parserPlugins: plugins },
+  pasteEvent: ClipboardEvent,
+  { builder, _parserPlugins: plugins }: Editor,
   { targetFormat } = { targetFormat: 'html' }
-) {
-  let { html, text } = getContentFromPasteEvent(pasteEvent, window)
+): Maybe<Post> {
+  let { html, text } = getContentFromPasteEvent(pasteEvent, (window as unknown) as ClipboardEvent)
 
   if (targetFormat === 'html' && html && html.length) {
     return parsePostFromHTML(html, builder, plugins)
@@ -136,7 +146,11 @@ export function parsePostFromPaste(
  * @return {Post}
  * @private
  */
-export function parsePostFromDrop(dropEvent, editor, { logger } = {}) {
+export function parsePostFromDrop(
+  dropEvent: DragEvent,
+  editor: Editor,
+  { logger }: { logger?: Logger } = {}
+): Maybe<Post> {
   let { builder, _parserPlugins: plugins } = editor
   let { html, text } = getContentFromDropEvent(dropEvent, logger)
 
diff --git a/src/js/views/tooltip.js b/src/js/views/tooltip.ts
similarity index 71%
rename from src/js/views/tooltip.js
rename to src/js/views/tooltip.ts
index a1d77c509..4e311009b 100644
--- a/src/js/views/tooltip.js
+++ b/src/js/views/tooltip.ts
@@ -1,14 +1,34 @@
 import View from './view'
-import { positionElementCenteredBelow, getEventTargetMatchingTag, whenElementIsNotInDOM } from '../utils/element-utils'
+import {
+  positionElementCenteredBelow,
+  getEventTargetMatchingTag,
+  whenElementIsNotInDOM,
+  Cancelable,
+} from '../utils/element-utils'
 import { editLink } from '../editor/ui'
 
 const SHOW_DELAY = 200
 const HIDE_DELAY = 600
 
+type Editor = any
+
+interface TooltipOptions {
+  rootElement: HTMLElement
+  editor: Editor
+  showForTag: string
+}
+
+interface AddListenerOptions {
+  showForTag: string
+}
+
 export default class Tooltip extends View {
-  constructor(options) {
-    options.classNames = ['__mobiledoc-tooltip']
-    super(options)
+  rootElement: HTMLElement
+  editor: any
+  elementObserver: Cancelable | null = null
+
+  constructor(options: TooltipOptions) {
+    super({ ...options, classNames: ['__mobiledoc-tooltip'] })
 
     this.rootElement = options.rootElement
     this.editor = options.editor
@@ -16,7 +36,7 @@ export default class Tooltip extends View {
     this.addListeners(options)
   }
 
-  showLink(linkEl) {
+  showLink(linkEl: HTMLElement) {
     const { editor, element: tooltipEl } = this
     const { tooltipPlugin } = editor
 
@@ -33,9 +53,9 @@ export default class Tooltip extends View {
     this.elementObserver = whenElementIsNotInDOM(linkEl, () => this.hide())
   }
 
-  addListeners(options) {
+  addListeners(options: AddListenerOptions) {
     const { rootElement, element: tooltipElement } = this
-    let showTimeout, hideTimeout
+    let showTimeout: number, hideTimeout: number
 
     const scheduleHide = () => {
       clearTimeout(hideTimeout)
@@ -53,12 +73,12 @@ export default class Tooltip extends View {
     })
 
     this.addEventListener(rootElement, 'mouseover', event => {
-      let target = getEventTargetMatchingTag(options.showForTag, event.target, rootElement)
+      let target = getEventTargetMatchingTag(options.showForTag, event.target as HTMLElement, rootElement)
 
       if (target && target.isContentEditable) {
         clearTimeout(hideTimeout)
         showTimeout = setTimeout(() => {
-          this.showLink(target)
+          target && this.showLink(target)
         }, SHOW_DELAY)
       }
     })
@@ -74,7 +94,7 @@ export default class Tooltip extends View {
 }
 
 export const DEFAULT_TOOLTIP_PLUGIN = {
-  renderLink(tooltipEl, linkEl, { editLink }) {
+  renderLink(tooltipEl: Element, linkEl: HTMLLinkElement, { editLink }) {
     const { href } = linkEl
     tooltipEl.innerHTML = `<a href="${href}" target="_blank">${href}</a>`
     const button = document.createElement('button')
diff --git a/src/js/views/view.js b/src/js/views/view.ts
similarity index 70%
rename from src/js/views/view.js
rename to src/js/views/view.ts
index 1e85a613d..534a6356f 100644
--- a/src/js/views/view.js
+++ b/src/js/views/view.ts
@@ -1,20 +1,35 @@
 import { addClassName } from '../utils/dom-utils'
 
+interface ViewOptions {
+  tagName: string
+  container: HTMLElement
+  classNames: string[]
+}
+
+type EventType = keyof HTMLElementEventMap
+
 class View {
-  constructor(options = {}) {
+  element: HTMLElement
+  container: HTMLElement
+
+  isShowing: boolean = false
+  isDestroyed: boolean = false
+
+  _eventListeners: [HTMLElement, EventType, EventListener][]
+
+  constructor(options: Partial<ViewOptions> = {}) {
     options.tagName = options.tagName || 'div'
     options.container = options.container || document.body
 
     this.element = document.createElement(options.tagName)
     this.container = options.container
-    this.isShowing = false
 
     let classNames = options.classNames || []
     classNames.forEach(name => addClassName(this.element, name))
     this._eventListeners = []
   }
 
-  addEventListener(element, type, listener) {
+  addEventListener(element: HTMLElement, type: EventType, listener: EventListener) {
     element.addEventListener(type, listener)
     this._eventListeners.push([element, type, listener])
   }
diff --git a/tests/acceptance/editor-copy-paste-test.js b/tests/acceptance/editor-copy-paste-test.js
index bfd0785c3..2562e00ca 100644
--- a/tests/acceptance/editor-copy-paste-test.js
+++ b/tests/acceptance/editor-copy-paste-test.js
@@ -132,7 +132,6 @@ test('willCopy callback called before copy', (assert) => {
   editor.addCallback('willCopy', data => {
     assert.deepEqual(data.mobiledoc, mobiledoc);
     data.mobiledoc.sections[0][1] = 'blockquote';
-    console.log({ data })
   });
   editor.render(editorElement);
 
diff --git a/tests/helpers/post-abstract.js b/tests/helpers/post-abstract.js
index 7e0eca7d9..73c3a9932 100644
--- a/tests/helpers/post-abstract.js
+++ b/tests/helpers/post-abstract.js
@@ -29,6 +29,7 @@ function build(treeFn) {
 }
 
 let cardRegex = /\[(.*)\]/;
+let imageSectionRegex = /^\{(.*)\}/;
 let markupRegex = /\*/g;
 let listStartRegex = /^\* /;
 let cursorRegex = /<|>|\|/g;
@@ -177,6 +178,8 @@ function parseSingleText(text, builder) {
 
   if (cardRegex.test(text)) {
     section = builder.cardSection(cardRegex.exec(text)[1]);
+  } else if (imageSectionRegex.test(text)) {
+    section = builder.imageSection(imageSectionRegex.exec(text)[1]);
   } else {
     let type = 'p';
     if (listStartRegex.test(text)) {
@@ -224,6 +227,7 @@ function parseSingleText(text, builder) {
  * buildFromText(["abc","def"]) -> { post } with 2 markups sections ("p") with texts "abc" and "def"
  * buildFromText("abc|def") -> { post, range } where range is collapsed at offset 3 (after the "c")
  * buildFromText(["abcdef","[some-card]","def"]) -> { post } with [MarkupSection, Card, MarkupSection] sections
+ * buildFromText(["abc", "{def}", "def"]) -> { post } with [MarkupSection, ImageSection, MarkupSection] sections
  * buildFromText(["* item 1", "* item 2"]) -> { post } with a ListSection with 2 ListItems
  * buildFromText(["<abc", "def", "ghi>"]) -> { post, range } where range is the entire post (before the "a" to after the "i")
  */
diff --git a/tests/unit/editor/post/insert-post-test.js b/tests/unit/editor/post/insert-post-test.js
index 8b474cddf..1bdcabc75 100644
--- a/tests/unit/editor/post/insert-post-test.js
+++ b/tests/unit/editor/post/insert-post-test.js
@@ -26,6 +26,9 @@ let blankSectionExpecations = [
   ['*abc*'], // section with markup
   ['[my-card]'], // single card
   ['[my-card]', '[my-other-card]'], // multiple cards
+  // Image section test is failing only in Safari due to selection ranges not
+  // accepting img elements
+  // ['{my-image}'], // single image section
   ['abc','* 123','* 456','[my-card]'], // mixed
 ];
 blankSectionExpecations.forEach(dsl => {
diff --git a/tsconfig.json b/tsconfig.json
index 0722840b2..21ad7a8ad 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -42,12 +42,12 @@
     // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
 
     /* Module Resolution Options */
-    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+    "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
     // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
     // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
     // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
     // "typeRoots": [],                       /* List of folders to include type definitions from. */
-    // "types": [],                           /* Type declaration files to be included in compilation. */
+    "types": [],                              /* Type declaration files to be included in compilation. */
     // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
     "esModuleInterop": true,                  /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
     // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */