diff --git a/src/content/docs/hosted-app/install.mdx b/src/content/docs/hosted-app/install.mdx
new file mode 100644
index 0000000..73e4401
--- /dev/null
+++ b/src/content/docs/hosted-app/install.mdx
@@ -0,0 +1,184 @@
+---
+title: Install the app
+description: Install the Sluiceway GitHub App, merge the onboarding pull request it opens, add your secrets, and choose whether ticks can go through the app.
+headerPicture:
+ picture: first-run
+ alt: No stacks yet, Penny stands beside an empty channel
+---
+
+import Callout from "../../../components/Callout.astro";
+import Steps from "../../../components/Steps.astro";
+import { page } from "../../../lib/routes";
+
+
+ The Sluiceway app is a GitHub App that installs beside the action. It shows every stack of
+ every repo it covers in one place, and opens one pull request per repo to set Sluiceway up.
+
+
+The app does not replace the action. Your workflow keeps running in your runners, with your
+credentials, and the dashboard issue in each repo keeps working as it does without the app.
+
+
+
+## Install
+
+Open the Sluiceway app on GitHub, choose
+**Install**, pick the org or account, and choose all repos or only some. You can change the
+repos later in GitHub's settings for the installation, and the app's Settings tab links there.
+
+Then sign in at app.sluiceway.dev with GitHub. You see
+the installations of the orgs and accounts you belong to, and nothing else.
+
+Installing the app on repos that have no dashboard costs nothing: only repos with a dashboard
+count toward your plan. See Plans and adopted repos.
+
+### What GitHub asks you to accept
+
+These are the permissions the app asks for, and what each one is used for.
+
+| Permission | Access | What for |
+| --- | --- | --- |
+| Metadata | Read | Required by GitHub for every app. |
+| Issues | Read and write | Reading the dashboard and its edit history. Writing the dashboard when a tick goes through the app, and the comment when a tick is refused. |
+| Deployments | Read and write | Reading the deployment records. Opening a record for a tick that goes through the app. |
+| Actions | Read and write | Reading workflow runs. Starting your Sluiceway workflow for a tick or a rescan that goes through the app. |
+| Contents | Read and write | Reading the repo for the onboarding pull request, and `sluiceway.yaml` at a commit. Writing one branch with one commit for a pull request the app opens. |
+| Pull requests | Read and write | Opening the onboarding pull request and the pull requests of edit mode. Reading when a merged pull request was merged, for Insights. |
+| Members (organization) | Read | Checking that a person is an admin of the org before they change the plan. |
+
+The app does not ask for Secrets. It never reads your secrets, their names or their values.
+
+## The onboarding pull request
+
+When the app is installed on a repo with no dashboard, it opens one pull request there, on the
+branch `sluiceway/onboarding`. It is written by the action's own
+init and check, run over a copy of the repo's default branch,
+so it says exactly what adding the action by hand would give you.
+
+It holds:
+
+- **The workflow** at `.github/workflows/deploy-dashboard.yml`, which runs `sluiceway/sluiceway@v0`.
+- **A first `sluiceway.yaml`**, only when init writes one, and the export script for an env file
+ of secret references, only when init found one.
+- **The `recordWriters` line**, which lets ticks go through the app. See
+ [below](#the-recordwriters-line).
+
+Its description lists:
+
+- the stacks the check found, what an `ignore` entry leaves out, and what root module discovery
+ left out and why;
+- the dashboard the repo would get, folded. Every stack is drawn in sync there, because none has
+ been previewed yet, and the first scan replaces it;
+- per stack, the credentials it needs and the ways to provide them, with links to the repo's
+ Actions secrets settings, and what the workflow does not provide yet;
+- what init says is still for a person to do, and the whole summary of the check, folded.
+
+It names secrets and never holds a value.
+
+A person merges it. The app never merges anything, and never pushes to your default branch.
+
+### When there is no pull request
+
+A repo that has a dashboard already gets no pull request, and neither does one where the app's
+onboarding pull request is still open: the org view links that one.
+
+When the app cannot open the pull request, the org view says why in one sentence under the repo.
+The reasons are ones only you can change: the repo is empty, it is too large to read, a workflow
+in it runs Sluiceway already, init found no stack, `sluiceway.yaml` is not valid, discovery could
+not work out the stacks, or GitHub did not answer. The app does not try again on its own. Fix
+the repo and press the button on the org view or the repo's page to ask again.
+
+A repo where init found no stack is listed on the org view in one quiet line, "No stacks found
+in", and you can ask for the pull request from its page once you add stacks.
+
+## The secrets step stays yours
+
+The app never holds, asks for or supplies a cloud credential. Before you merge:
+
+1. **Add the secrets the pull request names** in the repo's Actions secrets settings, or provide
+ them another way from Credentials and your own tooling.
+2. **Point the workflow at a self-hosted runner** when your infrastructure cannot be reached from
+ GitHub's runners, such as a cluster on a private network. See
+ Self-hosted runners.
+
+Then merge. The first scan previews every stack, creates the dashboard issue, and the repo moves
+from Not set up yet to the stacks on the org view.
+
+### When the first scan fails
+
+The app explains a failed first scan in one sentence with one link to the page that fixes it.
+
+On the repo's page, for each stack whose preview failed and that never deployed, it says one of:
+
+- a credential the stack needs is not provided by the workflow;
+- the stack needs a cluster, and the scan ran on a GitHub-hosted runner that cannot see it;
+- a credential may be missing, where the check cannot see into a step that might provide it;
+- or that the facts do not explain it, and the reason is in the summary of the run.
+
+On the org view, for a repo whose first scan failed before it wrote a dashboard, it says where
+the run stopped: in a step before Sluiceway's, where a secret the pull request names is likely
+not set yet; in Sluiceway's own step, with what to check on a self-hosted or a hosted runner; or
+before the run started, when an Actions policy does not allow the action.
+
+The app cannot read the names of your secrets, so it says "likely" where it cannot know.
+
+## The recordWriters line
+
+The pull request adds the app to `recordWriters` in `sluiceway.yaml`, in the file init writes or
+in the one the repo has, with a comment that says what it allows:
+
+```yaml title="sluiceway.yaml"
+# The Sluiceway app may open deployment records that this repo's workflow
+# deploys, after judging the tick with the same rule the action uses.
+recordWriters:
+ - sluiceway[bot]
+```
+
+Every other key, comment and writer in the file stays as it was. A file that names the app
+already is not changed, and the description says so.
+
+`recordWriters` lists the logins whose
+deployment records your workflow deploys. With `sluiceway[bot]` on it:
+
+- **You can tick a box and ask for a rescan from the app.** The app judges the tick with the
+ action's own tick rule and your permission on the repo, opens the deployment record, and
+ starts your workflow. See Tick from the app.
+- **A tick on the dashboard issue gets its row written by the app**, usually before your workflow
+ has started. Your workflow's run then previews the stack again and deploys only when the fresh
+ preview gives the same diff hash and value fingerprint, as for any tick.
+
+Ticking and Rescan from the app work in a repo whose `sluiceway.yaml` names `sluiceway[bot]` in
+`recordWriters`, and in any other repo they open the dashboard on GitHub.
+
+It allows nothing else. The app cannot deploy a stack nobody ticked, and a stack that deletes
+or replaces resources still waits for a person's tick.
+
+Without the line, the app judges no tick on that repo and every tick is left to your workflow, as
+it is without the app. The org view says so under the repo in one sentence. You can add the line
+later from the app's Settings tab or from edit mode, each of which opens a one-line pull request,
+and take it out the same way.
+
+
+ The tick rule cannot judge a bot, which has no access level of its own. That is why an app has
+ to be named in `sluiceway.yaml`, where a change goes through review, rather than allowed by a
+ setting in the app.
+
diff --git a/src/content/docs/hosted-app/plans.mdx b/src/content/docs/hosted-app/plans.mdx
new file mode 100644
index 0000000..f2002e5
--- /dev/null
+++ b/src/content/docs/hosted-app/plans.mdx
@@ -0,0 +1,112 @@
+---
+title: Plans and adopted repos
+description: What counts toward a plan, what happens to the repos past it, choosing active repos in Settings, and billing and cancelling through Stripe.
+headerPicture:
+ picture: pending-5
+ alt: 5 stacks are pending, five crates wait upstream of Penny
+---
+
+import Callout from "../../../components/Callout.astro";
+import { page } from "../../../lib/routes";
+
+
+ A plan decides how many adopted repos the app shows whole, how far back the audit log goes,
+ and whether a second approver can hold a deploy. Every other feature is on every plan.
+
+
+| Plan | Adopted repos | History |
+| --- | --- | --- |
+| Free | 5 | 30 days |
+| Team | 25 | 13 months |
+| Team Plus | 100 | 3 years |
+
+A required second approver is on Team and Team Plus: you add Sluiceway as a custom deployment
+protection rule on a GitHub environment, and GitHub holds each deploy job there until someone
+other than the ticker approves it in the app. GitHub offers custom protection rules on GitHub
+Enterprise and on public repos only. On Free the app approves such a job at once, so no deploy
+waits.
+
+The prices are on the pricing page.
+
+A plan belongs to an installation: one org, or one personal account, on GitHub.
+
+## What counts
+
+An **adopted repo** is a repo the app is installed on that has a dashboard: the action has run a
+scan there. Only adopted repos count.
+
+A repo without a dashboard never counts, so you can install the app on a whole org at no cost.
+It is listed under Not set up yet, with its onboarding pull request, and counts from the moment
+its first scan writes the dashboard.
+
+Edit mode does not count either: it shows one repo at a time.
+
+## Active repos, and the ones past the line
+
+Your plan's number of adopted repos are **active**: the app shows them whole, on the org view,
+their own pages, Insights and the audit log.
+
+Which ones is your choice. On the org's **Settings** tab, under Active repos, pick up to your
+plan's number. The places you leave open go to the busiest repos: the most stacks not in sync,
+then the most stacks, then by name. With no choice, the busiest are active. Anyone who can see
+the installation can choose, because the choice changes what the app shows and nothing else.
+Clear the choice to go back to the busiest.
+
+The adopted repos past the line are listed greyed out, in a group of their own called Past the
+free tier, or past your plan's name:
+
+- **The org view** lists each by name with its state counts, and no stacks or rows.
+- **Their own pages** are not shown, so Rescan for them opens the dashboard on GitHub.
+- **The audit log** says how many lines each one has, and not the lines. The CSV holds what the
+ page shows.
+- **Insights** name each one with its deploys, and do not count them.
+
+
+ A repo past the line keeps its dashboard on GitHub, and it works as always: scans, ticks and
+ deploys go on exactly as they did. The app keeps its facts, and shows them again as soon as
+ the repo is active.
+
+
+## History
+
+The audit log shows the deploys inside your plan's history, and its last sentence says how far
+back that is and on which plan. Nothing is deleted at the line: when your plan grows, the older
+lines are shown again.
+
+Insights always cover the last 12 weeks against the 12 before, on every plan.
+
+## Upgrade
+
+An admin of the org on GitHub, or the person whose personal account it is, can change the plan.
+Everyone else who can see the installation sees the plan, its numbers and its price, and one
+sentence that says who can change it. The app checks with GitHub every time someone changes the
+plan.
+
+**Upgrade** is on the Settings tab, and in place of the pricing link beside the repos past the
+line. Choose a plan, monthly or yearly, and pay on Stripe's Checkout page. There is no trial.
+
+- **VAT**: Stripe works out the tax from your billing address. A business in the EU can enter
+ its VAT id.
+- **A promotion code** goes in on the Checkout page. A code that brings the price to 0 needs no
+ card, and the Plan section names the code.
+
+You come back to Settings, which says Stripe is confirming the payment until it has.
+
+## Change or cancel
+
+**Manage billing** on the Settings tab opens Stripe's customer portal. Change between Team and
+Team Plus or monthly and yearly, update your card, read your invoices, or cancel there.
+
+When you cancel, or a payment fails, your plan holds until the end of the period you paid for,
+and the installation falls back to Free after it. Nothing is deleted: the app shows what Free
+shows, and shows the rest again if a plan starts again. Settings says when your plan renews or
+ends, and what then happens.
+
+To pay by invoice, write to hello@sluiceway.dev. An
+invoice plan is changed the same way.
+
+## See also
+
+- Settings, in Using the app.
+- What the app keeps and never keeps, including
+ what Stripe keeps and the app does not.
diff --git a/src/content/docs/hosted-app/using-the-app.mdx b/src/content/docs/hosted-app/using-the-app.mdx
new file mode 100644
index 0000000..c1387ef
--- /dev/null
+++ b/src/content/docs/hosted-app/using-the-app.mdx
@@ -0,0 +1,190 @@
+---
+title: Using the app
+description: The org view, a repo's dashboard, ticking from the app with Undo, Rescan, Insights, the audit log, and edit mode with its templates.
+headerPicture:
+ picture: deploying-3
+ alt: A stack is deploying and 3 are pending, the gate is open and one crate goes through it
+---
+
+import { glossary, page, ROUTES } from "../../../lib/routes";
+
+
+ The app draws what your dashboards say, across every repo it covers, in the dashboard's own
+ words. Every row it draws leads back to its repo and its dashboard on GitHub.
+
+
+Sign in at app.sluiceway.dev with GitHub. The app reads
+which installations you may see once, when you sign in, and keeps no token of yours. A session
+lasts eight hours, so a change of your membership on GitHub shows at your next sign-in.
+
+The top bar shows where you are, an org or one of its repos, and switches between them. An org
+has the tabs Overview, Insights, Audit and Settings. A repo has Dashboard, Insights and Audit.
+
+## The org view
+
+The org's Overview tab is every stack of every repo the app covers, at `app.sluiceway.dev/`.
+
+- **The counts** of the five states, pending, deploying, drifted, preview failed and in sync,
+ and when the last scan ran. Queued stacks count as deploying, and their rows still say queued.
+- **One banner** naming every pending stack that deletes or replaces resources.
+- **Chips** that filter the rows by state.
+- **The groups**: Needs you (pending, drifted, preview failed), In flight (deploying) and In
+ sync, folded. Or By repo, one panel per repo.
+
+A row is one link to its repo. It says what the dashboard's row says, without the changes: the
+state, how many resources it deletes or replaces, drift outside the code, and the stack's last
+deploy with who ticked it or merged it, the commit and the run.
+A deploy the dashboard's trail lists as an outside deploy
+is marked "deployed outside the dashboard".
+
+Under the stacks:
+
+- **Not set up yet**: repos with no dashboard, each with its onboarding pull request or the button
+ to ask for one, and why a first scan failed when it did. See
+ Install the app.
+- **No stacks found**: one quiet line for the repos where init found no stack.
+- **Past the free tier**, or past your plan: adopted repos beyond your plan's number, greyed out.
+ See Plans and adopted repos.
+
+## A repo's dashboard
+
+The Dashboard tab is the repo's dashboard issue drawn again, in the same order, with the same
+headings and sentences, Penny's header included. The app draws
+it with the action's own renderer, at the version these docs describe, from the facts it keeps.
+
+The app keeps no diff, so a pending or drifted row is drawn the way a
+redacted row is: its counts and no change lines. The changes
+themselves are one click away on the dashboard on GitHub. A failure line points at the run,
+since the app keeps no reason.
+
+## Tick from the app
+
+On a repo whose `sluiceway.yaml` names `sluiceway[bot]` in
+`recordWriters`, a box on the
+Dashboard tab is a real box. Tick it, and:
+
+1. **The row holds for five seconds**, dimmed, and a toast says "Asked GitHub to deploy" with the
+ stack id and an **Undo** button. Undo takes the tick back, and nothing was asked of GitHub.
+2. **The app judges the tick** with the action's own tick rule
+ and your permission on the repo, read live from GitHub, exactly as your workflow judges a tick
+ on the issue.
+3. **An allowed tick** starts your workflow and opens the stack's
+ deployment record with you as the ticker. The row
+ says "asking GitHub to start the run…", then waiting to start. Undo still works until the
+ record is open.
+4. **Your runner deploys.** The run previews the stack again and deploys only when the fresh
+ preview gives the same diff hash, as for any tick. The row moves as the dashboard's row does.
+
+A refused tick writes nothing on the issue. The refusal, in the action's own words, comes back
+to you on the page.
+
+The app never says a deploy went out: it says what it asked GitHub for. GitHub and your runner
+decide the rest, and the row shows it.
+
+Some boxes stay on GitHub, drawn as the same square but linking to the dashboard there:
+
+- the bulk, confirm and merge boxes, and the rescan box of the issue;
+- a drift repair, and a stack with a dependency, a phase, a deploy window or deploy on merge, or
+ one an `ignore` entry leaves out;
+- every box on a repo that does not name the app in `recordWriters`. The tab then shows one line
+ with a button that opens the pull request to add it.
+
+A read-only dashboard has no boxes, in the app or
+on GitHub.
+
+Ticking and Rescan from the app work in a repo whose `sluiceway.yaml` names `sluiceway[bot]` in
+`recordWriters`, and in any other repo they open the dashboard on GitHub.
+
+## Rescan
+
+**Rescan** in the top bar asks for a full scan: of the repo
+in view, or from the org, of one repo or every repo the app scans. It follows the rule of the
+rescan box: you need write access to the repo.
+
+The app starts your workflow, which runs a full scan, and the rescan box on the issue stays
+unticked. A rescan deploys nothing, so it is asked at once, with no Undo. The toast says "Asked
+GitHub to scan" and the repo's name. Asking again while that scan runs starts nothing new.
+
+On a repo that does not name the app in `recordWriters`, and on one past your plan, Rescan opens
+the dashboard on GitHub, where the rescan box is.
+
+## Insights
+
+Insights, for an org or for one repo, cover the last 12 weeks, from Monday in UTC, each number
+against the 12 weeks before.
+
+- **Lead time**: from the merge of a pull request to the deploy of its merge commit, the median.
+ A deploy of a commit with no merged pull request, such as a direct push, is not measured.
+- **Deploys per week**, from the dashboard and outside it.
+- **Change failure rate**: failed deploys from the dashboard over all deploys from the dashboard.
+- **Deployed outside the dashboard**: the outside deploys a full scan found.
+
+Four charts show lead time per week, deploys per week, stacks drifted per week, and where the
+time goes between a merge and its deploy: to the scan, to the preview, waiting for a tick, and
+the deploy itself. The org's Insights end with one row per repo.
+
+A number the facts cannot give says "Not measured yet" in one sentence, with no number.
+
+## The audit log
+
+The Audit tab lists one line per deploy, newest first: when, the repo, the stack, who ticked or
+who merged, the commit, and the result in the trail's words. A deploy outside the dashboard is
+flagged in red, "outside the dashboard", and names nobody, because the tool's history never says
+who.
+
+Filter by repo, by stack or to the flagged lines only. Each filter is a plain link, so a filtered
+log has an address you can paste into a ticket. **CSV** downloads what the page shows, with the
+same filters, the time in UTC and the whole commit.
+
+The log goes back as far as your plan's history, and its last sentence says how far.
+
+## Edit mode
+
+**Customise** on the Dashboard tab turns the dashboard into its own editor: the same render, with
+the settings of the block you select beside it. Every change redraws the dashboard and the diff of
+`sluiceway.yaml`.
+
+Every setting is a documented key of `sluiceway.yaml` that the action reads without the app. What
+you build keeps working exactly the same if you stop using the app.
+
+- **The dashboard**: `dashboard.title`, `personality` (Penny's header), `redact`, `showValues`,
+ `recentlyDeployed`, `timeZone` and `readOnly`, and `drift.enabled`.
+- **Who may tick**: `tickers`, and `recordWriters` as the app on the list or off it.
+- **Stacks**: per stack, `deploy` and `tickers`, and the `ignore` list with its reasons.
+
+A setting at its default takes its key out of the file. See
+Configuration for what each key does.
+
+### Templates
+
+A template is a named set of those keys, applied in one click over the settings you have, and
+adjusted after. It touches no key it does not name.
+
+- **Minimal**: `personality` off, rows redacted, and the last 3 deploys in the trail.
+- **Classic**: every dashboard key and drift at the action's defaults.
+- **Ops desk**: the last 30 deploys in the trail and drift on. Set your time zone yourself.
+
+### Save opens a pull request
+
+**Save** opens one pull request on the branch `sluiceway/dashboard`, from the commit the dashboard
+was scanned at. It changes `sluiceway.yaml` and nothing else, keeps every other key and every
+comment as they were, and its description names each change and shows the dashboard as it will
+look. A person merges it, and the next scan draws the dashboard that way.
+
+The app never changes the file behind anyone's back. While a pull request from edit mode is open,
+the app names it rather than open a second one. When the file moved on the default branch since
+the scan, the pull request shows the conflict for you to resolve.
+
+On a repo with no dashboard yet, edit mode starts from the action's example dashboard.
+
+## Settings
+
+The org's Settings tab holds what the app decides, and nothing that belongs in `sluiceway.yaml`:
+
+- **Plan**: your plan, its repos and history, and billing. See
+ Plans and adopted repos.
+- **Active repos**: which adopted repos your plan shows.
+- **The installation**: the repos the app is installed on, with a link to GitHub's page to change
+ them.
+- **Access per repo**: whether each adopted repo names the app in `recordWriters`, with a button
+ that opens the one-line pull request to add it or take it out.
diff --git a/src/content/docs/hosted-app/what-the-app-keeps.mdx b/src/content/docs/hosted-app/what-the-app-keeps.mdx
new file mode 100644
index 0000000..625bcaf
--- /dev/null
+++ b/src/content/docs/hosted-app/what-the-app-keeps.mdx
@@ -0,0 +1,94 @@
+---
+title: What the app keeps and never keeps
+description: The app keeps stack-level facts only, never a resource name, diff, value or cloud credential, never deploys on its own, and GitHub stays the source.
+sidebar:
+ label: What the app keeps
+headerPicture:
+ picture: in-sync
+ alt: Every stack is in sync, and Penny rests on a calm quay
+---
+
+import { glossary, page } from "../../../lib/routes";
+
+
+ The app keeps a timeline, not a map of your infrastructure. It holds no cloud credential, it
+ cannot deploy on its own, and GitHub stays the source of truth.
+
+
+## What it keeps
+
+Facts at the level of a stack, read from what the action publishes: the dashboard's
+markers, the deployment records
+and the workflow runs. See What Sluiceway writes
+for that shape.
+
+- **Per stack**: its stack id, its row state, how many resources it deletes or replaces, drift
+ and resources gone outside the code, and whether its box is ticked.
+- **Per deploy**: the stack, who ticked or who merged, which commit, which run, how it ended, when,
+ and whether it was an outside deploy.
+- **Per tick in the app**: who, which stack, when, and how it was judged, in one word.
+- **Per scan**: when it started, its commit, when its dashboard was written, and how many stacks
+ it drew drifted.
+- **Per merge**, for Insights: the merge commit and when it was merged.
+- **Per rescan asked in the app**: who asked, when, and the run it started.
+- **Per repo**: why its onboarding pull request could not be opened, and why its first scan
+ failed, each as one word from the app's fixed list.
+
+## What it never keeps
+
+- **No resource name, property path, diff or value.** Not in its database, not in its pages, not
+ in the pull requests it opens. A row in the app shows counts, and the changes are on GitHub.
+- **No text of a row, an issue title or a status description**, and of a merged pull request
+ nothing but its commit and time: no number, title, description, branch or author.
+- **None of your repo's files.** For the onboarding pull request and to explain a failed first
+ scan, the app reads a copy of the repo into a temporary directory and removes it when it is
+ done, whether it worked or failed. The app refuses a copy larger than 100 MB.
+- **No token of yours.** Signing in reads who you are and which installations you may see, and
+ the token is dropped. The app keeps no token of yours, so it cannot act as you.
+- **No card, email, address, business name or tax id.** Stripe keeps those. The app keeps your
+ plan, what Stripe says about the subscription, when the period ends, and Stripe's ids.
+
+## No cloud credential, ever
+
+The app never holds, asks for or supplies a cloud or infrastructure credential. Your credentials
+stay in your runners, set up as Credentials and your own tooling
+describes. The app does not ask GitHub for permission to read your secrets, and the onboarding
+pull request names the secrets a stack needs without ever seeing one.
+
+## It never causes a deploy on its own
+
+The app cannot deploy anything. Your runner does, as it does without the app.
+
+- **A deploy starts from a person's tick**, on the dashboard or in the app, judged with the
+ action's own tick rule, the same code your workflow uses.
+- **Only on a repo that names the app** in `recordWriters`,
+ a line of `sluiceway.yaml` that goes through review.
+- **The app opens a record and starts your workflow.** Your runner then previews the stack again
+ and deploys only when the fresh preview gives the diff hash the person approved. A change that
+ moved in between is refused.
+- **A stack that deletes or replaces resources** still waits for a person's tick.
+
+Nothing else the app does starts a deploy. A rescan runs a scan. Edit mode and the onboarding
+pull request are pull requests, and a person merges them.
+
+## GitHub is the source of truth
+
+The app is a view of what GitHub holds, and catches up with it.
+
+- **Your workflow keeps working without the app.** Installing the app changes nothing in it and
+ turns nothing off. When you tick on the dashboard, your workflow judges and deploys the tick
+ itself, whatever the app does. When both act on the same tick, whoever opens the deployment
+ record first has it, and the other finds the record open and does nothing more.
+- **When the app is down, nothing is lost.** On every start it reads each repo again since the
+ last point it saw: the dashboard, the deployment records and the workflow runs. GitHub sends
+ the webhooks it missed again too. A downtime delays what the app shows and never loses it.
+- **What the app shows follows GitHub.** A dashboard written again, a record closed, a repo
+ removed from the installation: the app brings its facts up to date. A repo the installation no
+ longer covers keeps its lines in the audit log.
+
+## If you stop using the app
+
+Uninstall it from GitHub's settings for the installation. Your dashboards, your workflow and
+your deploys go on as before. Everything edit mode wrote is a documented key of
+`sluiceway.yaml` that the action reads on its own, so the dashboard looks the same. Take
+`sluiceway[bot]` out of `recordWriters` when you no longer want the app to open records.
diff --git a/src/lib/pages.ts b/src/lib/pages.ts
index 5151670..b03d3bd 100644
--- a/src/lib/pages.ts
+++ b/src/lib/pages.ts
@@ -78,6 +78,17 @@ export const SLICE_3 = {
theHeader: "the-header",
};
+/**
+ * The pages about the hosted app, in sidebar order. They are written in src/content/docs: the
+ * action has no Markdown about the app.
+ */
+export const HOSTED_APP = {
+ install: "hosted-app/install",
+ using: "hosted-app/using-the-app",
+ keeps: "hosted-app/what-the-app-keeps",
+ plans: "hosted-app/plans",
+} as const;
+
/**
* Whether one of slice 3's pages is in src/content/docs yet. Until it is, the sidebar leaves it
* out and links to it go to GitHub, so no link points at a page that is not built.
@@ -1004,6 +1015,10 @@ export function sidebar(): SidebarItem[] {
{ slug: "guides/security" },
],
},
+ {
+ label: "Hosted app",
+ items: Object.values(HOSTED_APP).map((slug) => ({ slug })),
+ },
{
label: "Reference",
items: [
diff --git a/src/lib/routes.ts b/src/lib/routes.ts
index 5877868..484f8b2 100644
--- a/src/lib/routes.ts
+++ b/src/lib/routes.ts
@@ -2,7 +2,7 @@
// id from src/lib/pages.ts and goes through its `urlFor`, so the base is added the same way
// everywhere. A page id that pages.ts does not define stops the build.
-import { githubSlug, pages, SLICE_3, urlFor } from "./pages";
+import { githubSlug, HOSTED_APP, pages, SLICE_3, urlFor } from "./pages";
import { REPO_URL } from "./source";
const BASE = import.meta.env.BASE_URL.replace(/\/$/, "");
@@ -17,6 +17,7 @@ export function page(id: string): string {
SLICE_3.overview,
SLICE_3.whatItLooksLike,
SLICE_3.theHeader,
+ ...Object.values(HOSTED_APP),
]);
const path = id.split("#")[0] ?? "";
if (!known.has(path)) {