diff --git a/src/docs/developer/rendering_html.diviner b/src/docs/developer/rendering_html.diviner
new file mode 100644
index 0000000000..185c43caf9
--- /dev/null
+++ b/src/docs/developer/rendering_html.diviner
@@ -0,0 +1,178 @@
+@title Rendering HTML
+@group developer
+
+Rendering HTML in the Phabricator environment.
+
+= Overview =
+
+Phabricator attempts to prevent XSS by treating strings as default-unsafe when
+rendering. This means that if you try to build HTML through string
+concatenation, it won't work: the string will be escaped by the rendering
+pipeline, and the browser will treat it as plain text, not HTML.
+
+This document describes the right way to build HTML components so they are safe
+from XSS and render correctly. Broadly:
+
+ - Use @{function@libphutil:phutil_tag} (and @{function:javelin_tag}) to build
+ tags.
+ - Use @{function@libphutil:hsprintf} where @{function@libphutil:phutil_tag}
+ is awkward.
+ - Combine elements with arrays, not string concatenation.
+ - @{class:AphrontView} subclasses should return a
+ @{class@libphutil:PhutilSafeHTML} object from their `render()` method.
+ - @{function:pht} has some special rules.
+ - There are some other things that you should be aware of.
+ - Do not use @{function:phutil_render_tag} or @{function:javelin_render_tag},
+ they are deprecated and unsafe.
+
+See below for discussion.
+
+= Building Tags: phutil_tag() =
+
+Build HTML tags with @{function@libphutil:phutil_tag}. For example:
+
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'some-class',
+ ),
+ $content);
+
+@{function@libphutil:phutil_tag} will properly escape the content and all the
+attributes, and return a @{class@libphutil:PhutilSafeHTML} object. The rendering
+pipeline knows that this object represents a properly escaped HTML tag. This
+allows @{function@libphutil:phutil_tag} to render tags with other tags as
+content correctly (without double-escaping):
+
+ phutil_tag(
+ 'div',
+ array(),
+ phutil_tag(
+ 'strong',
+ array(),
+ $content));
+
+In Phabricator, the @{function:javelin_tag} function is similar to
+@{function@libphutil:phutil_tag}, but provides special handling for the
+`sigil` and `meta` attributes.
+
+= Building Blocks: hsprintf() =
+
+Sometimes, @{function@libphutil:phutil_tag} can be particularly awkward to
+use. You can use @{function@libphutil:hsprintf} to build larger and more
+complex blocks of HTML, when @{function@libphutil:phutil_tag} is a poor fit.
+@{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML:
+
+ // Safely build fragments or unwieldy blocks.
+ hsprintf(
+ '
',
+ $div_id);
+
+@{function:hsprintf} can be especially useful when:
+
+ - You need to build a block with a lot of tags, like a table with rows and
+ cells.
+ - You need to build part of a tag (usually you should avoid this, but if you
+ do need to, @{function@libphutil:phutil_tag} can not do it).
+
+Note that it is unsafe to provide any user-controlled data to the first
+parameter of @{function@libphutil:hsprintf} (the `sprintf()`-style pattern).
+
+Like @{function@libphutil:phutil_tag}, this function returns a
+@{class@libphutil:PhutilSafeHTML} object.
+
+= Composing Tags =
+
+When you are building a view which combines smaller components, like a section
+with a header and a body:
+
+ $header = phutil_tag('h1', ...);
+ $body = phutil_tag('p', ...);
+
+...you should NOT use string concatenation:
+
+ COUNTEREXAMPLE
+ // Not dangerous, but does the wrong thing.
+ phutil_tag('div', array(), $header.$body);
+
+Instead, use an array:
+
+ // Render a tag containing other tags safely.
+ phutil_tag('div', array(), array($header, $body));
+
+If you concatenate @{class@libphutil:PhutilSafeHTML} objects, they revert to
+normal strings and are no longer marked as properly escaped tags.
+
+(In the future, these objects may stop converting to strings, but for now they
+must to maintain backward compatibility.)
+
+If you need to build a list of items with some element in between each of them
+(like a middot, comma, or vertical bar) you can use
+@{function:array_interleave}:
+
+ // Render links with commas between them.
+ phutil_tag(
+ 'div',
+ array(),
+ array_interleave(', ', $list_of_links));
+
+= AphrontView Classes =
+
+Subclasses of @{class:AphrontView} in Phabricator should return a
+@{class@libphutil:PhutilSafeHTML} object. The easiest way to do this is to
+return `phutil_tag()` or `javelin_tag()`:
+
+ return phutil_tag('div', ...);
+
+@{class:AphrontView} subclasses can use `renderHTMLChildren()` and
+`renderHTMLView()` to build @{class@libphutil:PhutilSafeHTML} objects from
+children or arbitrary lists of components.
+
+@{class:AphrontView} subclasses should avoid `renderChildren()` and
+`renderSingleView()` and transition callers to the `HTML` varieties. These older
+methods do not return @{class@libphutil:PhutilSafeHTML} objects.
+
+= Internationalization: pht() =
+
+The @{function:pht} function has some special rules. If any input to
+@{function:pht} is a @{class@libphutil:PhutilSafeHTML} object, @{function:pht}
+returns a @{class@libphutil:PhutilSafeHTML} object itself. Otherwise, it returns
+normal text.
+
+This is generally safe because translations are not permitted to have more tags
+than the original text did (so if the original text had no tags, translations
+can not add any).
+
+Normally, this just means that @{function:pht} does the right thing and behaves
+like you would expect, but it is worth being aware of.
+
+= Special Cases =
+
+NOTE: This section describes dangerous methods which can bypass XSS protections.
+If possible, do not use them.
+
+You can build @{class@libphutil:PhutilSafeHTML} out of a string explicitly by
+calling @{function:phutil_safe_html} on it. This is **dangerous**, because if
+you are wrong and the string is not actually safe, you have introduced an XSS
+vulnerability. Consequently, you should avoid calling this if possible.
+
+You can use @{function@libphutil:phutil_escape_html} to explicitly escape an
+HTML string. You should not normally need to use it.
+
+You can use @{function@libphutil:phutil_escape_html_newlines} to escape HTML
+while converting newlines to `
`.
+
+If you need to extract the content of a @{class@libphutil:PhutilSafeHTML}
+object, you should call `getHTMLContent()`, not cast it to a string. Eventually,
+we would like to remove the string cast entirely.
+
+= Deprecated Functions =
+
+The functions @{function@libphutil:phutil_render_tag} and
+@{function:javelin_render_tag} are unsafe versions of
+@{function@libphutil:phutil_tag} and @{function:javelin_tag}, are deprecated,
+and will be removed in the future. You should avoid their use and convert code
+to use @{function@libphutil:phutil_tag} and @{function:javelin_tag}.
+
+The function @{function:phabricator_render_form} is also deprecated, in favor of
+@{function:phabricator_form}.