diff --git a/docs/framework/angular/changeset.md b/docs/framework/angular/changeset.md
new file mode 100644
index 0000000000000000000000000000000000000000..7307a7d414cac92ad441906d8c890ab119e8a6f9
--- /dev/null
+++ b/docs/framework/angular/changeset.md
@@ -0,0 +1,44 @@
+# AngularJS: Changesets
+
+!!! caution "Work in progress"
+
+    This documentation is still a work in progress.
+
+The [Quick Start](quickstart.md) and [Loader](loader.md) provide examples of
+creating *new* screens.  But what if you need to alter an *existing* screen?
+CiviCRM allows third-parties to define *changesets* which programmatically
+manipulate Angular content before sending it to the client.
+
+## Background
+
+Most AngularJS tutorials focus on idealized projects where a single
+developer or product-owner exercises full authority over their application.
+But CiviCRM is an _ecosystem_ with a range of stakeholders, including many
+developers (authoring indpendent extensions) and administrators (managing
+independent deployments with independent configurations).
+
+...
+
+## tldr
+
+```php
+function mailwords_civicrm_alterAngular(\Civi\Angular\Manager $angular) {
+  $changeSet = \Civi\Angular\ChangeSet::create('inject_mailwords')
+    // ->requires('crmMailing', 'mailwords')
+    ->alterHtml('~/crmMailing/BlockSummary.html',
+      function (phpQueryObject $doc) {
+        $doc->find('.crm-group')->append('
+        <div crm-ui-field="{name: \'subform.mailwords\', title: ts(\'Keywords\')}">
+          <input crm-ui-id="subform.mailwords" class="crm-form-text" name="mailwords" ng-model="mailing.template_options.keywords">
+        </div>
+      ');
+      });
+  $angular->add($changeSet);
+}
+```
+
+```
+cv ang:html:list
+cv ang:html:show <file>
+cv ang:html:show <file> --diff
+```
diff --git a/docs/framework/angular/files.md b/docs/framework/angular/files.md
new file mode 100644
index 0000000000000000000000000000000000000000..f7acd243556de7842b5461503ebbf375e6a6e239
--- /dev/null
+++ b/docs/framework/angular/files.md
@@ -0,0 +1,73 @@
+# AngularJS: File names
+
+As a developer working with CiviCRM-Angular, you write *Angular modules* --
+these modules are composed of various JS/CSS/HTML files which define the
+*services*, *directives*, *controllers*, and *routes*.
+
+For sake of predictability, these files are placed in the `ang/` folder, and
+they follow a naming convention.
+
+!!! note "How does this work with `civix`?"
+    When you generate Angular code via `civix`, the files are
+    named according to convention.
+
+    One file, `ang/{mymodule}.ang.php`, provides instructions for the
+    file-loader. It lists any files which match the naming
+    convention.
+
+!!! note "What if I don't use `civix`? What if my code doesn't follow the naming convention?"
+    The file-loader needs some information about the name and location of
+    your AngularJS code, but you don't need to follow the convention.  You
+    can configure it via hook.  See: [AngularJS: Loading](/framework/angular/loader.md).
+
+## Abridged convention
+
+The abridged convention applies to small Angular modules with a narrow
+purpose -- such as defining a singular `service` or `directive`.  These
+modules only have 2 or 3 files.
+
+   * `ang/{mymodule}.ang.php` - General metadata about the module (per [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md)).
+   * `ang/{mymodule}.js` - All Javascript for the module.
+   * `ang/{mymodule}.css` - All CSS for the module (if applicable).
+   * `ang/{mymodule}.md` - Developer documentation about the module (if applicable).
+
+## Full convention
+
+The full convention applies to bigger Angular modules which serve a broader
+purpose -- such as defining a new screen with a series of related
+`directive`s, `controller`s, and `service`s. Each of these elements may
+have multiple aspects (JS/HTML/CSS).
+
+__Module Files__
+
+   * `ang/{mymodule}.ang.php` - General metadata about the module (per [hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md)).
+   * `ang/{mymodule}.js` - General metadata about the module.
+   * `ang/{mymodule}.css` - General CSS that applies throughout the module (if applicable).
+   * `ang/{mymodule}.md` - Developer documentation about the module (if applicable).
+
+__Directive Files__
+
+   * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a directive named `mymoduleFooBar` or `<div mymodule-foo-bar>`.
+   * `ang/{mymodule}/{FooBar}.html` - The main/default template for the directive (if applicable).
+   * `ang/{mymodule}/{FooBar}/{Extra}.html` - If you have multiple templates used by the same directive (e.g. via `ng-include` or conditional logic), then put them in a subdir.
+   * `ang/{mymodule}/{FooBar}.css` - Any CSS specifically intended for `mymoduleFooBar` (if applicable).
+   * `ang/{mymodule}/{FooBar}.md` - Developer documentation about the directive (if applicable).
+
+__Controller Files__ (These follow the same convention as directives, but they have the suffix `Ctrl`.)
+
+   * `ang/{mymodule}/{FooBar}Ctrl.js` - The declaration and logic for a controller named `MymoduleFooBarCtrl`.
+   * `ang/{mymodule}/{FooBar}Ctrl.html` - The main/default template for the controller (if applicable).
+   * `ang/{mymodule}/{FooBar}Ctrl/{Extra}.html` - If you have multiple templates used with the same controller (e.g. via `ng-include` or conditional logic), then put them in a subdir.
+   * `ang/{mymodule}/{FooBar}Ctrl.css` - Any CSS specifically intended for `MymoduleFooBarCtrl` (if applicable).
+   * `ang/{mymodule}/{FooBar}Ctrl.md` - Developer documentation about the controller (if applicable).
+
+__Service Files__
+
+   * `ang/{mymodule}/{FooBar}.js` - The declaration and logic for a service named `mymoduleFooBar`.
+   * `ang/{mymodule}/{FooBar}.md` - Developer documentation about the service (if applicable).
+
+!!! tip "Tip: Use tilde (`~`) to load HTML templates"
+    When writing code for Angular, you might use an expression like
+    `{templateUrl: 'https://example.org/FooBar.html'}`.  However,
+    constructing a full URL that works in every Civi deployment would be
+    complex.  Instead, use the tilde prefix.  For example, `{templateUrl: '~/mymodule/FooBar.html'}`.
diff --git a/docs/framework/angular/index.md b/docs/framework/angular/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..d20fb0515d453488c3aa9ae3806105b47318a056
--- /dev/null
+++ b/docs/framework/angular/index.md
@@ -0,0 +1,101 @@
+# AngularJS: Overview
+
+AngularJS is a client-side framework for development of rich web
+applications.  The core CiviCRM application uses AngularJS for several
+administrative screens, and extensions increasingly use AngularJS for
+"leaps" that add or replace major parts of the application.
+
+This documentation aims to explain how AngularJS works within a CiviCRM
+context.
+
+## Two cultures
+
+CiviCRM is an extensible PHP application (similar to Drupal, Joomla, or
+WordPress).  In this culture, the common expectation is that an
+*administrator* installs the main application.  To customize it, they
+download, evaluate, and configure a set of business-oriented modules.  The
+administrator's workflow is dominated by web-based config screens and CLI
+commands.
+
+AngularJS is a frontend, Javascript development framework.  In this culture,
+the expectation is that a *developer* creates a new application.  To
+customize it, they download, evaluate, and configure a set of
+function-oriented libraries.  The developer's workflow is dominated by CLI's
+and code.
+
+The CiviCRM-AngularJS integration must balance the expectations of these
+two cultures.  The balance works as follows:
+
+ * __Build/Activation__: The process of building or activating modules
+   should meet administrators' expectations.  It should be managed by the
+   PHP application.  (This means that you won't see `gulp` or `grunt`
+   orchestrating the final build -- because PHP logic fills that role.)
+ * __Frontend Code uses Angular (JS+HTML)__: The general structure of the
+   Javascript and HTML files should meet the frontend developers'
+   expectations.  These files should be grounded in the same notations and
+   concepts as the upstream AngularJS framework.  (This means that AngularJS
+   is not abstracted, wrapped, or mapped by an intermediary like
+   HTML_QuickForm, Symfony Forms or Drupal Form API.)
+ * __Backend Code uses Civi API (PHP)__: The general structure of
+   web-services should meet the backend developers' expectations.  These are
+   implemented in PHP (typically with CiviCRM APIv3).
+
+## Basics
+
+AngularJS is a client-side Javascript framework, and it interacts with
+CiviCRM in two major ways.  To see this, let's consider an example AngularJS
+page -- it's an HTML document that looks a lot like this:
+
+```html
+<!-- URL: https://example.org/civicrm/a -->
+ 1: <html>
+ 2: <head>
+ 3:   <link rel="stylesheet" href="**all the CSS files**" />
+ 4:   <script type="text/javascript" src="**all the Javascript files**"></script>
+ 5:   <script type="text/javascript">var CRM = {**prefetched settings/data**};</script>
+ 6: </head>
+ 7: <body>
+ 8:   <div>...site wide header...</div>
+ 9:   <div ng-app="crmApp"></div>
+10:   <div>...site wide footer...</div>
+11: </body>
+12: </html>
+```
+
+The first interaction comes when CiviCRM generates the initial HTML page:
+
+ * CiviCRM listens for requests to the path `civicrm/a`. (It does this in a
+   way which is compatible with multiple CMSs -- Drupal, Joomla, WordPress, etc.)
+ * CiviCRM builds the list of CSS/JS/JSON resources in lines 3-5.  (It does this in a
+   way which allows extensions to add new CSS/JS/JSON. See also:
+   [Resource Reference](https://wiki.civicrm.org/confluence/display/CRMDOC/Resource+Reference).)
+ * CiviCRM ensures that the page includes the site-wide elements, such as
+   lines 8 and 10. (It does this in a way which is compatible with multiple CMSs.)
+
+Once the page is loaded, it works just like any AngularJS 1.x application.
+It uses concepts like `ng-app`, "module", "directive", "service", "component", and
+"partial".
+
+!!! seealso "Read more about AngularJS 1.x"
+    A good resource for understanding AngularJS concepts is [the
+    official AngularJS tutorial](https://code.angularjs.org/1.5.11/docs/tutorial).
+
+The second interaction comes when the AngularJS application loads or stores
+data.  This uses the CiviCRM API.  Key concepts in CiviCRM API include
+"entity", "action", "params", the "API Explorer", and the bindings for PHP/Javascript/CLI.
+
+!!! seealso "Read more about CiviCRM API"
+    A good resource for understanding CiviCRM API concepts is the [APIv3:
+    Intro](/api/index.md).
+
+In the remainder of this document, we'll try to avoid in-depth discussion
+about the internals of AngularJS 1.x or APIv3.  You should be able to follow
+the discussion if you have a beginner-level understanding of both.
+
+
+## Additional pages
+
+ * [Quick Start](quickstart.md): How to create a new screen in the CiviCRM-Angular UI
+ * [File Names](files.md): How AngularJS files are named in `civicrm-core` and `civix`
+ * [Loader](loader.md): How to find and load JS/CSS files for CiviCRM-Angular
+ * [Changesets](changeset.md): How a third-party can modify the content of a CiviCRM-Angular UI
diff --git a/docs/framework/angular/loader.md b/docs/framework/angular/loader.md
new file mode 100644
index 0000000000000000000000000000000000000000..3ba2473c86740cbeebe5193060224a1d80ab0d15
--- /dev/null
+++ b/docs/framework/angular/loader.md
@@ -0,0 +1,186 @@
+# AngularJS: Loader
+
+What happens when a user visits a CiviCRM-Angular page? For example, let's
+consider this URL:
+
+ * `https://example.org/civicrm/a/#/caseType`
+
+Broadly speaking, two things happen:
+
+ 1. (Server-side) CiviCRM processes the request for `civicrm/a`. It
+    displays a web-page with all your Angular modules -- such as
+    `ngRoute`, `crmAttachment`, `crmCaseType`, `crmUi`, and so on.
+ 2. (Client-side) AngularJS processes the HTML/JS/CSS.  It finds that the
+    module `crmCaseType` includes a route for `#/caseType` and loads the
+    appropriate HTML template.
+
+The client-side behavior is well-defined by Angular
+[ngRoute](https://docs.angularjs.org/api/ngRoute).  We'll explore the
+server-side in greater depth because that is unique to the CiviCRM-Angular
+integration.
+
+!!! caution "Caution: Discusses new/experimental interfaces"
+
+    Some elements of this document have been around since CiviCRM v4.6
+    and should remain well-supported. Other elements are new circa v4.7.21
+    and are still flagged experimental.
+
+## Library of modules
+
+The CiviCRM-Angular loader needs a list of available AngularJS modules.
+This list depends on your system configuration (e.g.  which CiviCRM
+extensions are enabled).  To view the current list, run `cv`:
+
+```
+$ cv ang:module:list
+For a full list, try passing --user=[username].
++-------------------+-------------+------------------------------------------------------------------------------------+
+| name              | basePages   | requires                                                                           |
++-------------------+-------------+------------------------------------------------------------------------------------+
+| angularFileUpload | civicrm/a   |                                                                                    |
+| bw.paging         | (as-needed) |                                                                                    |
+| civicase          | (as-needed) | crmUi, crmUtil, ngRoute, angularFileUpload, bw.paging, crmRouteBinder, crmResource |
+| crmApp            | civicrm/a   |                                                                                    |
+| crmAttachment     | civicrm/a   | angularFileUpload, crmResource                                                     |
+| crmAutosave       | civicrm/a   | crmUtil                                                                            |
+| crmCaseType       | civicrm/a   | ngRoute, ui.utils, crmUi, unsavedChanges, crmUtil, crmResource                     |
+...snip...
+```
+
+!!! tip "Tip: More options for `cv ang:module:list`"
+    Use `--columns` to specify which columns to display. Ex:
+
+    ```
+    $ cv ang:module:list --columns=name,ext,extDir
+    ```
+
+    Use `--out` to specify an output format. Ex:
+
+    ```
+    $ cv ang:module:list --out=json-pretty
+    ```
+
+    Use `--user` to specify a login user. This may reveal permission-dependent modules. Ex:
+
+    ```
+    $ cv ang:module:list --user=admin
+    ```
+
+Under-the-hood, this library of modules is built via
+[hook_civicrm_angularModules](/hooks/hook_civicrm_angularModules.md), e.g.
+
+```php
+/**
+ * Implements hook_civicrm_angularModules.
+ */
+function foobar_civicrm_angularModules(&$angularModules) {
+  $angularModules['myBigAngularModule'] = array(
+    'ext' => 'org.example.foobar',
+    'basePages' => array('civicrm/a'),
+    'requires' => array('crmUi', 'crmUtils', 'ngRoute'),
+    'js' => array('ang/myBigangularModule/*.js'),
+    'css' => array('ang/myBigangularModule/*.css'),
+    'partials' => array('ang/myBigangularModule'),
+  );
+}
+```
+
+!!! tip "Tip: Generating skeletal code with `civix`"
+    In practice, one usually doesn't need to implement this hook directly.
+    Instead, generate skeletal code with `civix`.  For details, see
+    [AngularJS: Quick Start](/framework/angular/quickstart.md).
+
+## Default base-page
+
+CiviCRM includes a "base-page" named `civicrm/a`.  By default, this page
+includes the core AngularJS files as well as all the modules in the library.
+
+The page is generated with a PHP class, `AngularLoader`, using logic like this:
+
+```php
+$loader = new \Civi\Angular\AngularLoader();
+$loader->setModules(array('crmApp'));
+$loader->setPageName('civicrm/a');
+$loader->load();
+```
+
+The `load()` function determines the necessary JS/CSS/HTML/JSON resources
+and loads them on the page. This will include:
+
+ * Any AngularJS modules explicitly listed in `setModules(...)`. (Ex: `crmApp`)
+ * Any AngularJS modules with matching `basePages`. (Ex: The value `civicrm/a`
+   is specified by both `setPageName(...)` [above] and `myBigAngularModule` [above].)
+ * Any AngularJS modules transitively required by the above.
+
+!!! note "What makes `civicrm/a` special?"
+    When declaring a module, the property `basePages` will default to
+    `array('civicrm/a')`.  In other words, if you don't specify otherwise,
+    all modules are loaded on `civicrm/a`.
+
+!!! note "How does `load()` output the `<script>` tag(s)?"
+    `load()` uses [CRM_Core_Resources](https://wiki.civicrm.org/confluence/display/CRMDOC/Resource+Reference)
+    to register JS/CSS files.
+
+## Other base-pages
+
+Loading all Angular modules on one page poses a trade-off.  On one hand, it
+warms up the caches and enables quick transitions between screens.  On the
+other hand, the page could become bloated with modules that aren't actually
+used.
+
+If this is a concern, then you can create new base-pages which are tuned to
+a more specific use-case.  For example, suppose we want to create a page
+which *only* has the CiviCase administrative UI.
+
+First, create a skeletal CiviCRM page:
+
+```
+$ civix generate:page CaseAdmin civicrm/caseadmin
+```
+
+Second, edit the new PHP file and update the `run()` function.  Create an
+`AngularLoader` to load all the JS/CSS files:
+
+```php
+public function run() {
+  $loader = new \Civi\Angular\AngularLoader();
+  $loader->setModules(array('crmCaseType'));
+  $loader->setPageName('civicrm/caseadmin');
+  $loader->load();
+  parent::run();
+}
+```
+
+Third, initialize AngularJS on the client-side.  You might put this
+in the Smarty template:
+
+```html
+<div ng-app="crmCaseType">
+  <div ng-view=""></div>
+</div>
+```
+
+Finally, flush the cache and visit the new page.
+
+```
+# Flush the cache
+$ cv flush
+
+# Lookup the URL (Drupal 7 example)
+$ cv url 'civicrm/caseadmin/#/caseType'
+"http://dmaster.l/civicrm/caseadmin/#/caseType"
+```
+
+!!! seealso "See Also: AngularJS Bootstrap Guide"
+
+    There are a few ways to boot Angular, such as `ng-app="..."` or
+    `angular.bootstrap(...);`.  These techniques are discussed more in the
+    [AngularJS Bootstrap Guide](https://code.angularjs.org/1.5.11/docs/guide/bootstrap).
+
+!!! caution "Embedding Angular in other contexts"
+
+    In the example, we created a new, standalone page.  But you can use
+    `AngularLoader` in other ways -- eg, you might listen for
+    `hook_civicrm_pageRun` and embed Angular onto a pre-existing,
+    non-Angular page.  Some extensions do this -- though it remains to be
+    seen whether this is *wise*.
diff --git a/docs/framework/angular/quickstart.md b/docs/framework/angular/quickstart.md
new file mode 100644
index 0000000000000000000000000000000000000000..9a52eb498442a7d516939a21f519f93c45096623
--- /dev/null
+++ b/docs/framework/angular/quickstart.md
@@ -0,0 +1,121 @@
+# AngularJS: Quick Start
+
+Let's create a new CiviCRM extension with an AngularJS page. It will present
+a small "About Me" screen.
+
+## Create a CiviCRM extension
+
+First, we'll need a skeletal CiviCRM extension:
+
+```
+$ civix generate:module org.example.aboutme
+Initalize module org.example.aboutme
+Write org.example.aboutme/info.xml
+Write org.example.aboutme/aboutme.php
+Write org.example.aboutme/aboutme.civix.php
+Write org.example.aboutme/LICENSE.txt
+```
+
+## Create an Angular module
+
+Of course, AngularJS also has its own module system -- any Angular routes,
+directives, or services need to live within an Angular `module`.  Let's
+create one.
+
+```
+$ cd org.example.aboutme
+$ civix generate:angular-module
+Initialize Angular module "aboutme"
+Write ang/aboutme.ang.php
+Write ang/aboutme.js
+Write ang/aboutme.css
+```
+
+!!! tip "Tip: Angular module names"
+    By default, `civix` assumes that your Angular module name matches your
+    extension name.  In this case, both are named `aboutme`.  However, this
+    is not required -- the option `--am` can specify a different name.  This
+    can be useful if you want to organize your code into multiple modules.
+
+!!! note "Note: `ang/` folder"
+    By convention, AngularJS source code is stored in the `ang/` folder, and
+    each item is named after its module.  The convention is discussed in
+    more detail in [AngularJS: File Names](/framework/angular/files.md)
+
+The first file, `ang/aboutme.ang.php`, provides metadata for the PHP-based
+file-loader, e.g.
+
+```php
+return array(
+  'requires' => array('ngRoute', 'crmUi', 'crmUtil'),
+  'js' => array('ang/aboutme.js', 'ang/aboutme/*.js', 'ang/aboutme/*/*.js'),
+  'css' => array('ang/aboutme.css'),
+  'partials' => array('ang/aboutme'),
+  'settings' => array(),
+);
+```
+
+The second file, `ang/aboutme.js`, provides metadata for the JS-based
+Angular runtime, e.g.
+
+```js
+  angular.module('aboutme', [
+    'ngRoute', 'crmUi', 'crmUtil'
+  ]);
+```
+
+!!! tip "Tip: angular.module() and CRM.angRequires()"
+    The list of dependencies is declared once in PHP and once in JS.  To
+    remove this duplication, call `CRM.angRequires(...)`, as in:
+
+    ```js
+    angular.module('aboutme', CRM.angRequires('aboutme'));
+    ```
+
+
+## Add an Angular-based page
+
+Now let's add a new Angular-based page.  This page will require a `route`
+with a `controller` and an HTML template.  The command
+`civix generate:angular-page` will create each of these:
+
+```
+$ civix generate:angular-page EditCtrl about/me
+Initialize Angular page "AboutmeEditCtrl" (civicrm/a/#/about/me)
+Write ang/aboutme/EditCtrl.js
+Write ang/aboutme/EditCtrl.html
+```
+
+If you inspect the code, you'll find a basic AngularJS app which uses
+`$routeProvider`, `angular.module(...).controller(...)`, and so on.
+
+The generated code will display a small "About Me" screen with the current
+user's first-name and last-name.
+
+!!! tip "Tip: Flush caches _or_ enable debug mode"
+    By default, CiviCRM aggregates AngularJS files and caches them.  You can
+    flush this cache manually (`cv flush`).  However, it may be easier to
+    disable some of the aggregation/caching features by [enabling debug
+    mode](/tools/debugging.md).
+
+## Open the page
+
+Finally, we'd like to take a look at this page in the web-browser.
+
+By default, CiviCRM combines all Angular modules into one page, `civicrm/a`.
+The URL of this page depends on your system configuration.  Here are a few
+examples:
+
+```
+# Example: Lookup the URL on Drupal 7
+$ cv url 'civicrm/a/#/about/me'
+"http://dmaster.l/civicrm/a/#/about/me"
+
+# Example: Lookup the URL on WordPress
+$ cv url 'civicrm/a/#/about/me'
+"http://wpmaster.l/wp-admin/admin.php?page=CiviCRM&q=civicrm/a/#/about/me"
+```
+
+!!! tip "Tip: Open the browser from the command-line"
+    If you're developing locally on a Linux/OSX workstation, pass the
+    option `--open` to automatically open the page in a web browser.
diff --git a/docs/framework/asset-builder.md b/docs/framework/asset-builder.md
new file mode 100644
index 0000000000000000000000000000000000000000..b77e46fbc27aae6dbc4f51080dc5a66571d933fc
--- /dev/null
+++ b/docs/framework/asset-builder.md
@@ -0,0 +1,143 @@
+# Asset Builder
+
+The `AssetBuilder` manages lazily-generated assets, such as aggregated
+JS/CSS files.  The first time you request a lazy asset, the `AssetBuilder`
+fires a hook which builds the content.  The content is stored in a cache
+file, and subsequent requests use the cache file.
+
+Lazy assets are simultaneously dynamic and static:
+
+ * __Dynamic__: They vary depending on the current system configuration.
+   You cannot lock-in a singular version of the asset because each
+   deployment may need a slightly different version.
+ * __Static__: Within a given deployment, the asset is not likely to change.
+   It can even be served directly by the web-server (without the overhead
+   of PHP/CMS/Civi bootstrap)
+
+!!! note "Example: Batch loading Angular HTML"
+    When visiting the AngularJS base page `civicrm/a`, one needs to load a
+    mix of many small HTML templates.  It's ideal to aggregate them into one
+    bigger file and reduce the number of round-trip HTTP requests.
+
+    This asset is _dynamic_ because extensions can add or modify HTML
+    templates.  Two different deployments would have different HTML
+    templates (depending on the mix of extensions).  Yet, within a
+    particular deployment, the content is _static_ because the HTML doesn't
+    actually change at runtime.
+
+!!! tip "Tip: Caching and development"
+    If you are developing or patching assets, then the caching behavior may
+    get distracting. To bypass the cache, navigate to
+    __Administer > System Settings > Debugging__ and enable debugging.
+
+
+## Usage: Simple asset
+
+There are generally two aspects to using `AssetBuilder` -- creating a URL
+for the asset, and defining the content of the asset.
+
+For example, suppose we wanted to define a static file named
+`api-fields.json` which lists all the fields of all the API entities.
+
+```php
+// Get the URL to `api-fields.json`.
+$url = \Civi::service('asset_builder')->getUrl('api-fields.json');
+
+...
+
+// Define the content of `api-fields.json` using `hook_civicrm_buildAsset`.
+function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
+  if ($asset !== 'api-fields.json') return;
+
+  $entities = civicrm_api3('Entity', 'get', array());
+  $fields = array();
+  foreach ($entities['values'] as $entity) {
+    $fields[$entity] = civicrm_api3($entity, 'getfields');
+  }
+
+  $mimeType = 'application/json';
+  $content = json_encode($fields);
+}
+```
+
+!!! note "What does `getUrl(...)` do?"
+
+    In normal/production mode, `getUrl(...)` checks to see if the asset
+    already exists.  If necessary, it fires the hook and saves the asset to
+    disk.  Finally, it returns the direct URL for the asset -- which allows
+    the user to fetch it quickly (without extra PHP/CMS/Civi bootstrapping).
+
+    In debug mode, `getUrl(...)` returns the URL of a PHP script.  The PHP
+    script will build the asset every time it's requested.
+
+## Usage: Parameterized asset
+
+What should you do if you need to create a series of similar assets, based on slightly
+different permutations or configurations? Add parameters (aka `$params`).
+
+For example, we might want a copy of `api-fields.json` which only includes a
+handful of chosen entities.  Simply pass the chosen entities into
+`getUrl()`, then update the definition to use `$params['entities']`, as in:
+
+```php
+// Get the URL to `api-fields.json`. This variant only includes
+// a few contact-related entities.
+$contactEntitiesUrl = \Civi::service('asset_builder')
+  ->getUrl('api-fields.json', array(
+    'entities' => array('Contact', 'Phone', 'Email', 'Address'),
+  )
+);
+
+// Get the URL to `api-fields.json`. This variant only includes
+// a few case-related entities.
+$caseEntitiesUrl = \Civi::service('asset_builder')
+  ->getUrl('api-fields.json', array(
+    'entities' => array('Case', 'Activity', 'Relationship'),
+  )
+);
+
+...
+
+// Define the content of `api-fields.json` using `hook_civicrm_buildAsset`.
+function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
+  if ($asset !== 'api-fields.json') return;
+
+  $fields = array();
+  foreach ($params['entities'] as $entity) {
+    $fields[$entity] = civicrm_api3($entity, 'getfields');
+  }
+
+  $mimeType = 'application/json';
+  $content = json_encode($fields);
+}
+```
+
+!!! note "Note: Parmaters and caching"
+    Each combination of (`$asset`,`$params`) will be cached separately.
+
+!!! tip "Tip: Economize on parameters"
+    In debug mode, all parameters are passed as part of the URL.
+    `AssetBuilder` will try to compress them, but it can only do so much.
+    Fundamentally, long `$params` will produce long URLs.
+
+## Other considerations
+
+!!! note "Compare: How does AssetBuilder differ from [Assetic](https://github.com/kriswallsmith/assetic)?"
+    Both are written in PHP, but they address differet parts of the process:
+
+     * `AssetBuilder` provides URL-routing, caching, and parameterization.
+       Its strength is defining a *lazy lifecycle* for the assets.
+     * `Assetic` provides a library of generators and filters.  Its strength
+       is defining the *content* of an asset.
+
+    You could use them together -- e.g.  in `hook_civicrm_buildAsset`,
+    declare a new asset and use `Assetic` to build its content.
+
+!!! caution "Caution: Confidentiality and lazy assets"
+    The current implementation does not take aggressive measures to keep
+    assets confidential. For example, an asset built from public JS files
+    is fine, but an asset built from permissioned data (contact-records
+    or activity-records) could be problematic.
+
+    It may be possible to fix this by computing URL digests differently, but
+    (at time of writing) we don't have a need/use-case.
diff --git a/docs/hooks/hook_civicrm_angularModules.md b/docs/hooks/hook_civicrm_angularModules.md
index 21dd0901557e4cafd18886de074eff54f25a5241..0ce3c933ba614c9a61f398d08411ebaf51bf8483 100644
--- a/docs/hooks/hook_civicrm_angularModules.md
+++ b/docs/hooks/hook_civicrm_angularModules.md
@@ -16,12 +16,23 @@ available in CiviCRM 4.6+.
 
 ## Parameters
 
--   &$angularModules - an array containing a list of all Angular
-    modules.
+ * `&$angularModules` - an array containing a list of all Angular modules. Each item is keyed by the Angular module name.
+
+Each item `angularModules`  may include these properties:
+
+ * `ext` (`string`): The name of the CiviCRM extension which has the source-code.
+ * `js` (`array`): List of Javascript files. May use the wildcard (`*`). Relative to the extension.
+ * `css` (`array`): List of CSS files. May use the wildcard (`*`). Relative to the extension.
+ * `partials` (`array`): List of HTML folders. Relative to the extension.
+ * `requires` (`array`): List of AngularJS modules required by this module. Default: `array()`. (`v4.7.21+`)
+ * `basePages` (`array`): Uncondtionally load this module onto the given Angular pages. (`v4.7.21+`)
+   * If omitted, the default is `array('civicrm/a')`. This provides backward compatibility with behavior since `v4.6+`.
+   * For a utility that should only be loaded on-demand, use `array()`.
+   * For a utility that should be loaded in all pages use, `array('*')`.
 
 ## Returns
 
--   null
+ * `null`
 
 ## Example
 
@@ -32,6 +43,8 @@ available in CiviCRM 4.6+.
       );
       $angularModules['myBigAngularModule'] = array(
         'ext' => 'org.example.mymod',
+        'requires' => array('ngRoute', 'crmUi'),
+        'basePages' => array('civicrm/a'),
         'js' => array('js/part1.js', 'js/part2.js'),
         'css' => array('css/myAngularModule.css'),
         'partials' => array('partials/myBigAngularModule'),
diff --git a/docs/hooks/hook_civicrm_buildAsset.md b/docs/hooks/hook_civicrm_buildAsset.md
new file mode 100644
index 0000000000000000000000000000000000000000..18c5ffa026d889a3098d71ddc2a95f2694aaab67
--- /dev/null
+++ b/docs/hooks/hook_civicrm_buildAsset.md
@@ -0,0 +1,32 @@
+# hook_civicrm_buildAsset
+
+## Description
+
+This hook fires whenever the system builds a semi-dynamic asset.  For more
+discussion, see [AssetBuilder](/framework/asset-builder.md).
+
+## Definition
+
+    hook_civicrm_buildAsset($asset, $params, &$mimeType, &$content)
+
+## Parameters
+
+ * `$asset` (string): the logical file name of an asset (ex: `hello-world.json`)
+ * `$params` (array): an optional set of parameters describing how to build the asset
+ * `$mimeType` (string, output): the MIME type of the asset (ex: `application/json`)
+ * `$content` (string, output): the full content of the asset
+
+## Returns
+
+ * null
+
+## Example
+
+```php
+function mymodule_civicrm_buildAsset($asset, $params, &$mimeType, &$content) {
+  if ($asset === 'hello-world.json') {
+    $mimeType = 'application/json';
+    $content = json_encode(array('hello', 'world'));
+  }
+}
+```
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index d378847ca21080e4168cbd7df3c46871bce4b6b3..1553482c7cabaf8823b5183be150bc454adc988c 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -43,6 +43,13 @@ pages:
   - Troubleshooting: extensions/troubleshooting.md
   - Advanced patterns: extensions/advanced.md
 - Framework Reference:
+  - AngularJS:
+    - "AngularJS: Intro": framework/angular/index.md
+    - "AngularJS: Quick Start": framework/angular/quickstart.md
+    - "AngularJS: File Names": framework/angular/files.md
+    - "AngularJS: Loader": framework/angular/loader.md
+    - "AngularJS: Changesets":  framework/angular/changeset.md
+  - Asset Builder: framework/asset-builder.md
   # Bootstrap: /framework/bootstrap.md
   # Cache: /framework/cache.md
   # Components: /framework/components.md
@@ -168,6 +175,7 @@ pages:
     - hook_civicrm_alterSettingsMetaData: hooks/hook_civicrm_alterSettingsMetaData.md
     - hook_civicrm_angularModules: hooks/hook_civicrm_angularModules.md
     - hook_civicrm_apiWrappers: hooks/hook_civicrm_apiWrappers.md
+    - hook_civicrm_buildAsset: hooks/hook_civicrm_buildAsset.md
     - hook_civicrm_buildStateProvinceForCountry: hooks/hook_civicrm_buildStateProvinceForCountry.md
     - hook_civicrm_check: hooks/hook_civicrm_check.md
     - hook_civicrm_config: hooks/hook_civicrm_config.md