From 0f9ac9c0d0ac0f683a1ffeb11a9a724fccd51a11 Mon Sep 17 00:00:00 2001 From: Adron Hall Date: Fri, 23 Oct 2026 16:17:07 -0700 Subject: [PATCH] Move help to the shared Hub help center; report build status to it The app's MkDocs site duplicated the Hub help center in substratalapps-applications-specs, which every Hub app links to. Its Android-specific content now lives there (CompositeCode/substratalapps-applications-specs branch docs/android-help), so this repo drops site/ and the docs workflow. - HelpLinks opens help/screens/.html in the help center. Screens this app proposes outside the design package (OPS-*) open the help home, since the spec's OPS-* IDs are different, web-only Console screens. - :core:model:exportHelp writes build/help/android.json, the help center's build-status format. help-status.yml opens or updates a PR with it in the specs repo on every merge to main, then sends app-docs-updated. - CI keeps the API coverage check against the pinned openapi/v1 spec. - App version moves to gradle.properties (substratal.appVersion), shared by the APK and the status file. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/android.yml | 15 +- .github/workflows/docs.yml | 85 ------- .github/workflows/help-status.yml | 69 ++++++ .gitignore | 8 - Makefile | 25 +- README.md | 16 +- app/build.gradle.kts | 12 +- .../hub/feature/auth/SignInScreen.kt | 2 +- .../com/substratalapps/hub/help/HelpLinks.kt | 32 +-- core/model/build.gradle.kts | 13 +- .../hub/model/ExportScreenCatalog.kt | 57 ++++- .../substratalapps/hub/model/ScreenSpec.kt | 7 +- docs/PREREQUISITES.md | 8 +- gradle.properties | 1 + scripts/sync_openapi.py | 4 +- site/docs/assets/app-icon.svg | 1 - site/docs/assets/mark-white.svg | 1 - site/docs/assets/substratal.css | 25 -- site/docs/changelog.md | 20 -- site/docs/developers/android.md | 56 ----- site/docs/developers/api/index.md | 60 ----- site/docs/developers/api/versions.md | 33 --- site/docs/developers/docs-workflow.md | 63 ----- site/docs/developers/help-links.md | 42 ---- site/docs/developers/index.md | 18 -- site/docs/faq.md | 36 --- site/docs/getting-started/index.md | 13 - site/docs/getting-started/install.md | 27 --- site/docs/getting-started/navigation.md | 46 ---- site/docs/getting-started/sign-in.md | 32 --- site/docs/guides/access-and-roles.md | 41 ---- site/docs/guides/account-security.md | 36 --- site/docs/guides/appearance.md | 17 -- site/docs/guides/applications.md | 48 ---- site/docs/guides/audit.md | 35 --- site/docs/guides/index.md | 16 -- site/docs/guides/operations.md | 28 --- site/docs/guides/users.md | 42 ---- site/docs/index.md | 62 ----- site/docs/reference/errors.md | 23 -- site/docs/reference/index.md | 12 - site/docs/reference/permissions.md | 43 ---- site/docs/reference/platforms.md | 19 -- site/docs/reference/statuses.md | 49 ---- site/docs/tutorials/check-on-the-api.md | 27 --- site/docs/tutorials/create-an-api-key.md | 48 ---- site/docs/tutorials/index.md | 32 --- site/docs/tutorials/invite-a-user.md | 39 --- .../docs/tutorials/register-an-application.md | 57 ----- site/docs/tutorials/turn-off-access.md | 46 ---- site/docs/tutorials/why-no-access.md | 56 ----- site/help/screens/acc-01.md | 13 - site/help/screens/apl-01.md | 9 - site/help/screens/auth-01.md | 7 - site/help/screens/auth-02.md | 8 - site/help/screens/glb-02.md | 9 - site/help/screens/ops-01.md | 10 - site/help/screens/set-01.md | 3 - site/help/screens/usr-01.md | 8 - site/mkdocs.yml | 142 ----------- site/requirements.txt | 7 - site/scripts/generate.py | 229 ------------------ 62 files changed, 176 insertions(+), 1872 deletions(-) delete mode 100644 .github/workflows/docs.yml create mode 100644 .github/workflows/help-status.yml delete mode 100644 site/docs/assets/app-icon.svg delete mode 100644 site/docs/assets/mark-white.svg delete mode 100644 site/docs/assets/substratal.css delete mode 100644 site/docs/changelog.md delete mode 100644 site/docs/developers/android.md delete mode 100644 site/docs/developers/api/index.md delete mode 100644 site/docs/developers/api/versions.md delete mode 100644 site/docs/developers/docs-workflow.md delete mode 100644 site/docs/developers/help-links.md delete mode 100644 site/docs/developers/index.md delete mode 100644 site/docs/faq.md delete mode 100644 site/docs/getting-started/index.md delete mode 100644 site/docs/getting-started/install.md delete mode 100644 site/docs/getting-started/navigation.md delete mode 100644 site/docs/getting-started/sign-in.md delete mode 100644 site/docs/guides/access-and-roles.md delete mode 100644 site/docs/guides/account-security.md delete mode 100644 site/docs/guides/appearance.md delete mode 100644 site/docs/guides/applications.md delete mode 100644 site/docs/guides/audit.md delete mode 100644 site/docs/guides/index.md delete mode 100644 site/docs/guides/operations.md delete mode 100644 site/docs/guides/users.md delete mode 100644 site/docs/index.md delete mode 100644 site/docs/reference/errors.md delete mode 100644 site/docs/reference/index.md delete mode 100644 site/docs/reference/permissions.md delete mode 100644 site/docs/reference/platforms.md delete mode 100644 site/docs/reference/statuses.md delete mode 100644 site/docs/tutorials/check-on-the-api.md delete mode 100644 site/docs/tutorials/create-an-api-key.md delete mode 100644 site/docs/tutorials/index.md delete mode 100644 site/docs/tutorials/invite-a-user.md delete mode 100644 site/docs/tutorials/register-an-application.md delete mode 100644 site/docs/tutorials/turn-off-access.md delete mode 100644 site/docs/tutorials/why-no-access.md delete mode 100644 site/help/screens/acc-01.md delete mode 100644 site/help/screens/apl-01.md delete mode 100644 site/help/screens/auth-01.md delete mode 100644 site/help/screens/auth-02.md delete mode 100644 site/help/screens/glb-02.md delete mode 100644 site/help/screens/ops-01.md delete mode 100644 site/help/screens/set-01.md delete mode 100644 site/help/screens/usr-01.md delete mode 100644 site/mkdocs.yml delete mode 100644 site/requirements.txt delete mode 100755 site/scripts/generate.py diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index e55eea6..3b61dc2 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,6 +1,6 @@ -# Build, test, and check that the docs still build, on every push and pull request. -# Code and docs ship together: a change that breaks the docs (a shipped screen without help, an -# API call missing from the pinned spec, a broken link) fails here. +# Build, test, lint, and check every API call the app's screens make against the pinned spec, +# on every push and pull request. Help pages live in the shared Hub help center +# (substratalapps-applications-specs); help-status.yml keeps this app's status there current. name: Android on: @@ -43,11 +43,6 @@ jobs: - uses: actions/setup-python@v7 with: python-version: "3.12" - cache: pip - cache-dependency-path: site/requirements.txt - - name: Docs toolchain - run: python -m venv site/.venv && site/.venv/bin/pip install -r site/requirements.txt - - - name: API coverage, docs generation, strict docs build - run: make docs + - name: Every API call in the screen catalog exists in the pinned spec + run: pip install pyyaml && make api-coverage diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index e7b83a2..0000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Publishes the documentation site to GitHub Pages (gh-pages branch) with mike. -# push to main -> "dev" version (what's being built) -# tag vX.Y.Z -> version "X.Y", and "latest" moves to it -# Every published version stays available, so an older app's help links keep working. -name: Docs - -on: - push: - branches: [main] - tags: ["v*"] - paths-ignore: - - "docs/PLAN-*.md" - workflow_dispatch: - inputs: - version: - description: Docs version to publish (e.g. 0.1, or dev) - required: true - default: dev - latest: - description: Also point "latest" at it - type: boolean - default: false - -permissions: - contents: write # mike commits to gh-pages - -concurrency: - group: docs-publish - cancel-in-progress: false - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - fetch-depth: 0 # mike needs gh-pages history - - - uses: actions/setup-java@v6 - with: - distribution: temurin - java-version: "21" - - - uses: gradle/actions/setup-gradle@v6 - - - uses: actions/setup-python@v7 - with: - python-version: "3.12" - cache: pip - cache-dependency-path: site/requirements.txt - - - name: Docs toolchain - run: python -m venv site/.venv && site/.venv/bin/pip install -r site/requirements.txt - - - name: Generate and check - run: make docs - - - name: Work out the version - id: version - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "name=${{ inputs.version }}" >> "$GITHUB_OUTPUT" - echo "latest=${{ inputs.latest }}" >> "$GITHUB_OUTPUT" - elif [[ "${GITHUB_REF}" == refs/tags/v* ]]; then - tag="${GITHUB_REF#refs/tags/v}" - echo "name=$(echo "$tag" | cut -d. -f1-2)" >> "$GITHUB_OUTPUT" - echo "latest=true" >> "$GITHUB_OUTPUT" - else - echo "name=dev" >> "$GITHUB_OUTPUT" - echo "latest=false" >> "$GITHUB_OUTPUT" - fi - - - name: Publish with mike - working-directory: site - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - if [ "${{ steps.version.outputs.latest }}" = "true" ]; then - .venv/bin/mike deploy --push --update-aliases "${{ steps.version.outputs.name }}" latest - .venv/bin/mike set-default --push latest - else - .venv/bin/mike deploy --push "${{ steps.version.outputs.name }}" - # Until the first release, the site root opens the dev docs. - if ! .venv/bin/mike list | grep -q latest; then .venv/bin/mike set-default --push dev; fi - fi diff --git a/.github/workflows/help-status.yml b/.github/workflows/help-status.yml new file mode 100644 index 0000000..be474fe --- /dev/null +++ b/.github/workflows/help-status.yml @@ -0,0 +1,69 @@ +# Keeps this app's entry in the Substratal Hub help center current. +# On every merge to main it regenerates help/apps/android.json (which screens the app has built) +# and, if it changed, opens or updates a pull request in substratalapps-applications-specs. Then it +# sends the help center an app-docs-updated dispatch so it rebuilds. +# +# Needs the secret SPECS_DOCS_TOKEN: a fine-grained token with Contents and Pull requests +# read/write on CompositeCode/substratalapps-applications-specs (see that repo's help/README.md). +name: Help status + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: help-status + cancel-in-progress: false + +env: + SPECS_REPO: CompositeCode/substratalapps-applications-specs + BRANCH: help-status/android + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: "21" + + - uses: gradle/actions/setup-gradle@v6 + + - name: Export build status + run: make help-status + + - name: Open or update the help center pull request + env: + GH_TOKEN: ${{ secrets.SPECS_DOCS_TOKEN }} + run: | + set -euo pipefail + gh repo clone "$SPECS_REPO" specs -- --depth 1 + cd specs + mkdir -p help/apps + if cmp -s ../build/help/android.json help/apps/android.json; then + echo "help/apps/android.json is already current." + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git switch -c "$BRANCH" + cp ../build/help/android.json help/apps/android.json + git add help/apps/android.json + git commit -m "Android build status from substratalapps-android@${GITHUB_SHA::7}" + git push --force origin "$BRANCH" + if ! gh pr view "$BRANCH" --repo "$SPECS_REPO" >/dev/null 2>&1; then + gh pr create --repo "$SPECS_REPO" --head "$BRANCH" --base main \ + --title "Android build status" \ + --body "Generated from CompositeCode/substratalapps-android@${GITHUB_SHA} by its help-status workflow. Updates which screens the Android app has built, shown on each screen page's **On your platform** table." + fi + + - name: Tell the help center to rebuild + env: + GH_TOKEN: ${{ secrets.SPECS_DOCS_TOKEN }} + run: | + gh api "repos/$SPECS_REPO/dispatches" -f event_type=app-docs-updated \ + -F 'client_payload[app]=android' -F "client_payload[sha]=$GITHUB_SHA" diff --git a/.gitignore b/.gitignore index 0447a9b..554981d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,13 +14,5 @@ captures/ !debug.keystore google-services.json -# Docs site -site/.venv/ -site/build/ -site/.generated/ -# Generated by site/scripts/generate.py from the app and openapi/ (never edit by hand) -site/docs/reference/screens/ -site/docs/developers/api/v*/ - # OS .DS_Store diff --git a/Makefile b/Makefile index 78b2e3e..f7b0a9d 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,7 @@ # Common tasks. The Android build itself is plain Gradle (./gradlew …). -PY ?= site/.venv/bin/python -MKDOCS ?= site/.venv/bin/mkdocs GRADLE ?= ./gradlew -.PHONY: build test check install install-local docs-deps catalog docs docs-serve api-coverage +.PHONY: build test check install install-local help-status api-coverage build: ## Debug APK $(GRADLE) assembleDebug @@ -17,21 +15,10 @@ install: ## Install on a connected device (production API) install-local: ## Install, pointed at the API devserver on the emulator's host $(GRADLE) installDebug -Psubstratal.apiEnvironment=LOCAL -docs-deps: ## Create the docs virtualenv - python3 -m venv site/.venv && site/.venv/bin/pip install -q -r site/requirements.txt +help-status: ## Write build/help/screens.json and build/help/android.json (help center status) + $(GRADLE) -q :core:model:exportHelp -catalog: ## Export the app's screen catalog for the docs - $(GRADLE) -q :core:model:exportScreenCatalog +api-coverage: help-status ## Every API call a screen makes must exist in the pinned spec (needs PyYAML) + python3 scripts/check_api_coverage.py build/help/screens.json openapi/v1/openapi.yaml -api-coverage: catalog ## Every API call a screen makes must exist in the pinned spec - $(PY) scripts/check_api_coverage.py site/.generated/screens.json openapi/v1/openapi.yaml - -docs: api-coverage ## Generate and build the docs site (strict) - $(PY) site/scripts/generate.py - cd site && .venv/bin/mkdocs build --strict - -docs-serve: api-coverage ## Generate and serve the docs at http://127.0.0.1:8000 - $(PY) site/scripts/generate.py - cd site && .venv/bin/mkdocs serve - -check: test docs ## Everything CI runs +check: test api-coverage ## Everything CI runs, apart from lint diff --git a/README.md b/README.md index f2a7c30..4967fcf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ The Android management app for the [Substratal Apps Platform API](https://compositecode.github.io/substratalapps.com/). It's the Android build of the Substratal Hub defined in [substratalapps-applications-specs](https://github.com/CompositeCode/substratalapps-applications-specs), built admin-first, plus a new Operations section for monitoring the API. -It also hosts **Substratal Help** (`site/`), the documentation site the management apps on every platform link to. +Help pages live in the **Substratal Hub help center**, shared by every Hub app: (source: `help/` in [substratalapps-applications-specs](https://github.com/CompositeCode/substratalapps-applications-specs)). Every screen's **?** button opens its page there. ## API versioning: no `/v1` in the URL @@ -31,7 +31,6 @@ core/network/ OkHttp/Retrofit client: version header, auth, single-flight core/designsystem/ Substratal themes (5 light, 5 dark), brand assets, status badges, components core/data/ Session + Keystore-encrypted token store, capabilities, preferences openapi/v1/ Pinned openapi.yaml + where it came from; CHANGELOG.md lists every sync -site/ Substratal Help (MkDocs Material + mike + Swagger UI) scripts/ API coverage check, spec sync docs/ Planning documents ``` @@ -55,15 +54,12 @@ make up && make migrate && make seed && make run && make quickstart Then sign in as `admin@example.com` with the password `local admin passphrase`. -## Docs +## Help and documentation -```bash -make docs-deps # once: Python venv with the pinned toolchain -make docs-serve # http://127.0.0.1:8000 -make docs # strict build, as CI runs it -``` - -What's generated, what CI enforces, and how publishing and API-spec syncing work are covered in [site/docs/developers/docs-workflow.md](site/docs/developers/docs-workflow.md). +- **Help links.** `HelpLinks` opens `screens/.html` in the help center for design-package screens. Screens this app adds on its own (the `OPS-*` preview) open the help home, because the spec's `OPS-*` IDs are different web-only Console screens. +- **Build status.** `make help-status` writes `build/help/android.json`, listing the screens this app has built. On every merge to `main`, `.github/workflows/help-status.yml` opens or updates a PR putting it at `help/apps/android.json` in the specs repo, then asks the help center to rebuild. Each screen's help page then shows "Android: Available in 0.1.0". It needs a `SPECS_DOCS_TOKEN` secret. +- **API contract.** `openapi/v1/` pins the spec this build is checked against. `make api-coverage` fails if a screen calls an operation that isn't in it, and `api-spec-sync.yml` opens a PR here when the API's spec changes. +- **Adding a screen.** Register it in `implementedScreens` and `ImplementationStatus`; unit tests fail if they disagree. Write its usage notes in the help center's `help/screens/.md`. ## Status diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7e7d3cc..495c3ec 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,12 +5,10 @@ plugins { alias(libs.plugins.hilt) } -// The docs site publishes one version per app minor version (mike), so in-app help links to the -// documentation that matches this build. Debug builds link to the "dev" docs. -val appVersionName = "0.1.0" -val docsVersion = appVersionName.split(".").take(2).joinToString(".") +val appVersionName = providers.gradleProperty("substratal.appVersion").get() +// In-app help opens the Substratal Hub help center that every Hub app shares. val helpBaseUrl = providers.gradleProperty("substratal.helpBaseUrl") - .orElse("https://compositecode.github.io/substratalapps-android/").get() + .orElse("https://compositecode.github.io/substratalapps-applications-specs/help/").get() val apiEnvironment = providers.gradleProperty("substratal.apiEnvironment").orElse("PRODUCTION").get() android { @@ -29,14 +27,10 @@ android { } buildTypes { - debug { - buildConfigField("String", "DOCS_VERSION", "\"dev\"") - } release { isMinifyEnabled = true isShrinkResources = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - buildConfigField("String", "DOCS_VERSION", "\"$docsVersion\"") } } diff --git a/app/src/main/kotlin/com/substratalapps/hub/feature/auth/SignInScreen.kt b/app/src/main/kotlin/com/substratalapps/hub/feature/auth/SignInScreen.kt index 3b4b57e..ac3c7b4 100644 --- a/app/src/main/kotlin/com/substratalapps/hub/feature/auth/SignInScreen.kt +++ b/app/src/main/kotlin/com/substratalapps/hub/feature/auth/SignInScreen.kt @@ -97,7 +97,7 @@ fun SignInScreen(viewModel: AuthViewModel = hiltViewModel()) { Button(onClick = { viewModel.signIn(email, password) }, enabled = canSubmit, modifier = Modifier.fillMaxWidth()) { if (ui.busy) CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp) else Text("Sign in") } - TextButton(onClick = { HelpLinks.open(context, HelpLinks.page("getting-started/sign-in/")) }) { + TextButton(onClick = { HelpLinks.open(context, HelpLinks.page("guides/sign-in-and-security.html")) }) { Text("Forgot password or need help signing in?") } } diff --git a/app/src/main/kotlin/com/substratalapps/hub/help/HelpLinks.kt b/app/src/main/kotlin/com/substratalapps/hub/help/HelpLinks.kt index bb50614..8521cbb 100644 --- a/app/src/main/kotlin/com/substratalapps/hub/help/HelpLinks.kt +++ b/app/src/main/kotlin/com/substratalapps/hub/help/HelpLinks.kt @@ -7,16 +7,18 @@ import com.substratalapps.hub.BuildConfig import com.substratalapps.hub.model.ScreenSpec /** - * Links into the Substratal documentation site. URLs are versioned like the site: - * `{base}{docsVersion}/{path}`, where a release build's docs version is its minor version and - * debug builds use `dev`. Every platform's app uses the same paths, so a screen id is a stable - * help address on all of them (`/reference/screens/usr-01/`). + * Links into the Substratal Hub help center, the documentation every Hub app shares + * (substratalapps-applications-specs, `help/`). A screen's page is `screens/.html` on every + * platform; see the help center's "Linking to help from an app". */ object HelpLinks { - private val root get() = "${BuildConfig.HELP_BASE_URL}${BuildConfig.DOCS_VERSION}/" + private val root get() = BuildConfig.HELP_BASE_URL fun home() = root - fun forScreen(screen: ScreenSpec) = root + screen.docsPath + + /** The screen's help page; screens this app adds outside the design package open the help home. */ + fun forScreen(screen: ScreenSpec) = screen.helpPath?.let { root + it } ?: home() + fun page(path: String) = root + path.trimStart('/') fun open(context: Context, url: String) { @@ -28,13 +30,13 @@ object HelpLinks { data class HelpTopic(val title: String, val summary: String, val path: String) val helpTopics = listOf( - HelpTopic("Getting started", "Install the app, sign in and find your way around.", "getting-started/"), - HelpTopic("Tutorials", "Step-by-step: invite a user, turn access off, register an app, create a key.", "tutorials/"), - HelpTopic("Managing users", "Search, invite, suspend and sign people out.", "guides/users/"), - HelpTopic("Access and roles", "Turn apps on and off for people, and decide what they can do.", "guides/access-and-roles/"), - HelpTopic("Applications and keys", "Register applications, manage API keys, webhooks and push.", "guides/applications/"), - HelpTopic("Operations", "Monitor the API: health, errors, usage, alerts and cost.", "guides/operations/"), - HelpTopic("Screen reference", "Every screen, what it does and the API calls behind it.", "reference/screens/"), - HelpTopic("API reference", "OpenAPI / Swagger reference for the Platform API, by version.", "developers/api/"), - HelpTopic("FAQ", "Answers to common questions.", "faq/"), + HelpTopic("Getting started", "What the Hub is, how it's organized, and where things are.", "guides/getting-started.html"), + HelpTopic("The Android app", "Layouts, links, offline use and security on Android.", "guides/android-app.html"), + HelpTopic("Your first hour as an administrator", "A guided tour of the tasks staff do first.", "tutorials/first-hour-as-an-administrator.html"), + HelpTopic("Users and access", "Find people, invite them, and turn app access on and off.", "guides/users-and-access.html"), + HelpTopic("Roles and permissions", "What each role can do, and how to change it.", "guides/roles-and-permissions.html"), + HelpTopic("Applications", "Register and manage applications, keys and webhooks.", "guides/applications.html"), + HelpTopic("Every screen", "Each screen, who can use it, and the API calls behind it.", "screens/index.html"), + HelpTopic("API reference", "Interactive OpenAPI reference for the Platform API, by version.", "api/index.html"), + HelpTopic("FAQ", "Answers to common questions.", "faq.html"), ) diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 57f5825..145ecc0 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -11,15 +11,16 @@ dependencies { testImplementation(libs.junit) } -// `./gradlew :core:model:exportScreenCatalog` writes the screen catalog as JSON for the docs site. +// `./gradlew :core:model:exportHelp` writes build/help/screens.json (for the API coverage check) and +// build/help/android.json (this app's build status for the Substratal Hub help center). application { mainClass.set("com.substratalapps.hub.model.ExportScreenCatalogKt") } -tasks.register("exportScreenCatalog") { +tasks.register("exportHelp") { group = "documentation" - description = "Writes the screen catalog to site/.generated/screens.json" + description = "Writes the screen catalog and the help center's build-status file to build/help/" classpath = sourceSets["main"].runtimeClasspath mainClass.set("com.substratalapps.hub.model.ExportScreenCatalogKt") - val out = rootProject.layout.projectDirectory.file("site/.generated/screens.json").asFile - args(out.absolutePath) - outputs.file(out) + val out = rootProject.layout.buildDirectory.dir("help").get().asFile + args(out.absolutePath, providers.gradleProperty("substratal.appVersion").get()) + outputs.dir(out) } diff --git a/core/model/src/main/kotlin/com/substratalapps/hub/model/ExportScreenCatalog.kt b/core/model/src/main/kotlin/com/substratalapps/hub/model/ExportScreenCatalog.kt index aaeaede..c95ec39 100644 --- a/core/model/src/main/kotlin/com/substratalapps/hub/model/ExportScreenCatalog.kt +++ b/core/model/src/main/kotlin/com/substratalapps/hub/model/ExportScreenCatalog.kt @@ -1,5 +1,6 @@ package com.substratalapps.hub.model +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import java.io.File @@ -12,22 +13,64 @@ private data class CatalogExport( ) @Serializable -private data class ScreenEntry(val spec: ScreenSpec, val buildState: BuildState, val navRoute: String, val docsPath: String) +private data class ScreenEntry(val spec: ScreenSpec, val buildState: BuildState, val navRoute: String) -/** Writes [ScreenCatalog] as JSON. The docs site renders its screen reference from this file. */ +/** + * This app's entry for the Substratal Hub help center (`help/apps/android.json` in + * substratalapps-applications-specs). Lists the design-package screens the app has built; any + * screen not listed is planned. Deterministic, so an unchanged app produces an identical file. + */ +@Serializable +private data class HelpStatus( + @SerialName("\$description") val description: String, + val platform: String, + val app: String, + @SerialName("app_version") val appVersion: String, + @SerialName("api_version") val apiVersion: String, + val screens: Map, +) + +/** + * Writes `/screens.json` (the full catalog, for the API coverage check) and + * `/android.json` (build status for the help center). Args: . + */ fun main(args: Array) { - val out = File(args.firstOrNull() ?: "screens.json") - out.parentFile?.mkdirs() + val dir = File(args.getOrElse(0) { "build/help" }).apply { mkdirs() } + val appVersion = args.getOrElse(1) { "0.0.0" } val json = Json { prettyPrint = true; encodeDefaults = true } - out.writeText( + + File(dir, "screens.json").writeText( json.encodeToString( CatalogExport.serializer(), CatalogExport( ApiVersioning.CURRENT, ApiVersioning.SPEC_VERSION, - ScreenCatalog.all.map { ScreenEntry(it, ImplementationStatus.of(it.id), it.navRoute, it.docsPath) }, + ScreenCatalog.all.map { ScreenEntry(it, ImplementationStatus.of(it.id), it.navRoute) }, ), ), ) - println("Wrote ${ScreenCatalog.all.size} screens to $out") + + // Only design-package screens: the help center's IDs. Screens this app proposes on its own + // (Phase.PROPOSED) aren't in the specs, so they have no help page to report on. + val built = ScreenCatalog.all + .filter { it.phase != Phase.PROPOSED } + .map { it.id to ImplementationStatus.of(it.id) } + .filter { (_, state) -> state != BuildState.PLANNED } + .sortedBy { it.first } + .associate { (id, state) -> id to state.name.lowercase() } + File(dir, "android.json").writeText( + json.encodeToString( + HelpStatus.serializer(), + HelpStatus( + description = "Screens the Android app has built. Generated by CompositeCode/substratalapps-android " + + "(./gradlew :core:model:exportHelp); don't edit by hand. Screens not listed are planned.", + platform = "android", + app = "CompositeCode/substratalapps-android", + appVersion = appVersion, + apiVersion = ApiVersioning.CURRENT, + screens = built, + ), + ) + "\n", + ) + println("Wrote ${ScreenCatalog.all.size} screens and ${built.size} built-screen statuses to $dir") } diff --git a/core/model/src/main/kotlin/com/substratalapps/hub/model/ScreenSpec.kt b/core/model/src/main/kotlin/com/substratalapps/hub/model/ScreenSpec.kt index 2c7f4fe..25bbc3c 100644 --- a/core/model/src/main/kotlin/com/substratalapps/hub/model/ScreenSpec.kt +++ b/core/model/src/main/kotlin/com/substratalapps/hub/model/ScreenSpec.kt @@ -66,8 +66,11 @@ data class ScreenSpec( val pathParameters: List get() = Regex(":(\\w+)").findAll(route).map { it.groupValues[1] }.toList() - /** Path of this screen's page on the documentation site, relative to the platform root. */ - val docsPath: String get() = "reference/screens/${id.lowercase()}/" + /** + * This screen's page in the Substratal Hub help center, relative to its `help/` root. Only + * design-package screens have one; [Phase.PROPOSED] screens return null. + */ + val helpPath: String? get() = if (phase == Phase.PROPOSED) null else "screens/${id.lowercase()}.html" } /** How the Platform API is versioned. The URL carries no version; a header does. */ diff --git a/docs/PREREQUISITES.md b/docs/PREREQUISITES.md index ccae278..68e5130 100644 --- a/docs/PREREQUISITES.md +++ b/docs/PREREQUISITES.md @@ -19,7 +19,7 @@ What was checked before building the Android app, and what's still outstanding. | Android SDK | Platforms 35, 36 (installed), 37.0 (installed: current AndroidX needs compileSdk 37); build-tools 36; platform-tools | OK | | Gradle | Wrapper 9.8.2; AGP 9.4.1 with built-in Kotlin 2.4.21 | Builds; 11 unit tests pass | | Emulator | AVD `il_pixel` (API 35) | Ran the app on phone and tablet sizes, light and dark | -| Docs toolchain | Python 3.10; MkDocs 1.6.1, Material 9.7.7, mike 2.2.0, swagger-ui-tag 0.8.1 (pinned) | `mkdocs build --strict` passes | +| Docs | Moved to the Hub help center in substratalapps-applications-specs (Node build, `HELP_STRICT=1`) | Builds with 0 problems, including Android's status file | | Docker / Go / jq | Docker 20.10, Go 1.24 (auto-fetches the 1.26 toolchain), jq 1.8 | OK for the local API | ## Already resolved in the API since the first plan @@ -40,13 +40,13 @@ These were tracked as `[api-gaps]` issues in `CompositeCode/substratalapps.com` | # | Item | Blocks | Owner | |---|---|---|---| -| 1 | **No GitHub remote for this repo.** CI, docs publishing and spec sync are written but can't run until the repo exists (`gh repo create CompositeCode/substratalapps-android`). GitHub Pages on a private repo needs a paid plan; otherwise make the repo public, or publish the built site to S3 + CloudFront | Docs site, CI | You | +| 1 | GitHub Actions can't start jobs on the CompositeCode account (billing / spending limit). The repo exists and is pushed. Help-status publishing also needs a `SPECS_DOCS_TOKEN` secret. The docs moved to the shared help center in substratalapps-applications-specs | CI, help status | You | | 2 | `dashboard.substratalapps.com` doesn't resolve. Verified App Links need `/.well-known/assetlinks.json` there; until then `https://dashboard…` links open in the browser. `substratal://` links work | Emailed links opening the app | Web app (`substratalapps.com-web`) | | 3 | A Play signing key and its SHA-256 fingerprint, for `assetlinks.json` and Play internal testing | App Links, distribution | You | | 4 | Firebase project for `app_hub` + FCM credential uploaded through the API (APL-07) | Push (milestone M3) | You / API issue #24 | | 5 | Register `app_hub` and Substratal's own applications | Push, Home | API issue #25 | | 6 | SES out of the sandbox | Invitations and resets reaching real people | API issue #22 | | 7 | Stripe set up | Plan & Billing screens | API issue #23 | -| 8 | Operations endpoints and permissions | OPS-01..10 live data | API issues #43–#48 | -| 9 | Design package still shows `/v1` paths and lacks the new operations | Spec consistency | API issue #40 (G3) | +| 8 | Operations endpoints and permissions | Live operations data (if Android keeps it; see 9) | API issues #43–#48 | +| 9 | **Operations on Android conflicts with the spec.** The specs (v1.8.0, PRODUCT-SPEC §2a, OPEN-QUESTIONS #27) now define web-only Console screens `OPS-01..07`, `SEC-01`, `CFG-01/02`, `RPT-01`, `INT-01`, which native apps skip. This app's own `OPS-01..10` preview reuses those IDs for different screens. Decide: drop the Android Operations section (matches the spec), or propose native adoption in the specs | Ops section, help links | You | | 10 | The production superadmin's credentials, to test against production | End-to-end checks in production | You | diff --git a/gradle.properties b/gradle.properties index 8ebed2a..16c99cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,3 +5,4 @@ org.gradle.parallel=true android.useAndroidX=true android.nonTransitiveRClass=true kotlin.code.style=official +substratal.appVersion=0.1.0 diff --git a/scripts/sync_openapi.py b/scripts/sync_openapi.py index 828a407..ec16fdc 100755 --- a/scripts/sync_openapi.py +++ b/scripts/sync_openapi.py @@ -4,7 +4,7 @@ Usage: sync_openapi.py Writes openapi/v1/openapi.yaml and SOURCE.yaml, and adds a row to openapi/CHANGELOG.md. If the new document serves a different API version, it stops: a new API version is a deliberate -change (openapi/v2/, ApiVersioning.CURRENT, the docs nav), not an automatic one. +change (openapi/v2/, ApiVersioning.CURRENT), not an automatic one. """ import datetime import pathlib @@ -77,7 +77,7 @@ def main(): if removed: print("Removed operations fail the API coverage check if a screen still calls them. Update " "`ScreenCatalog` before merging.\n") - print("After merging, the docs workflow republishes the API reference.") + print("The Hub help center publishes its API reference from the API's own openapi.yaml, so nothing else needs updating.") if __name__ == "__main__": diff --git a/site/docs/assets/app-icon.svg b/site/docs/assets/app-icon.svg deleted file mode 100644 index 40f1551..0000000 --- a/site/docs/assets/app-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/site/docs/assets/mark-white.svg b/site/docs/assets/mark-white.svg deleted file mode 100644 index 4fe68d0..0000000 --- a/site/docs/assets/mark-white.svg +++ /dev/null @@ -1 +0,0 @@ -Substratal \ No newline at end of file diff --git a/site/docs/assets/substratal.css b/site/docs/assets/substratal.css deleted file mode 100644 index 8e06e56..0000000 --- a/site/docs/assets/substratal.css +++ /dev/null @@ -1,25 +0,0 @@ -/* Moss Paper (light) and Forest Floor (dark), from the apps' default themes (THEMES.md). */ -:root, -[data-md-color-scheme="default"] { - --md-primary-fg-color: #4a7322; - --md-primary-fg-color--light: #6b9440; - --md-primary-fg-color--dark: #375719; - --md-accent-fg-color: #375719; - --md-default-bg-color: #f5f4ee; - --md-typeset-a-color: #4a7322; -} - -[data-md-color-scheme="slate"] { - /* Header and tabs on a dark surface; Forest Floor's lime primary for links and accents. */ - --md-primary-fg-color: #1c2417; - --md-primary-fg-color--light: #2a3422; - --md-primary-fg-color--dark: #141a10; - --md-primary-bg-color: #e4ead9; - --md-primary-bg-color--light: #c3cbb6; - --md-accent-fg-color: #b8e26a; - --md-default-bg-color: #10150f; - --md-typeset-a-color: #b8e26a; -} - -/* Screen IDs and API paths read as code everywhere. */ -.md-typeset table:not([class]) code { white-space: nowrap; } diff --git a/site/docs/changelog.md b/site/docs/changelog.md deleted file mode 100644 index 4fdea3a..0000000 --- a/site/docs/changelog.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -title: What's new -description: Changes in each release of the Substratal management app for Android. ---- - -# What's new - -## 0.1.0 (in development) - -The first build of the Android management app. - -- **Shell:** bottom bar on phones, rail on small tablets, full side menu on tablets and Chromebooks. All 85 screens have their own link and deep link. Screens that aren't built yet show what they'll do. -- **Sign in** with email, password and two-factor code or recovery code. -- **Users:** search, status filters and infinite scroll. -- **Applications:** every application for staff, with a review-queue filter; your own applications for developers. -- **Manage hub** with Operations, Administration and Developer. -- **Operations Overview** (preview, with sample data). -- **Account** and **Settings**, with the ten Substratal themes. -- **Help** on every screen, linked to this site. -- Uses API version **1** through the `Substratal-Version` header, and warns when that version is deprecated. diff --git a/site/docs/developers/android.md b/site/docs/developers/android.md deleted file mode 100644 index c634f82..0000000 --- a/site/docs/developers/android.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Android app architecture -description: How the Substratal Android app is built — modules, stack, and running it against a local API. ---- - -# Android app architecture - -## Stack - -| Concern | Choice | -|---|---| -| UI | Kotlin, Jetpack Compose, Material 3, `material3-adaptive-navigation-suite` | -| SDK | minSdk 26, targetSdk 36, compiled against 37; edge-to-edge; predictive back | -| Architecture | MVVM with unidirectional data flow (ViewModel + `StateFlow`), Hilt, Navigation Compose | -| Network | OkHttp 5 + Retrofit 3 + kotlinx.serialization | -| Session | Refresh token encrypted with an Android Keystore AES-GCM key, in DataStore; access token in memory | -| Build | Gradle 9.8, AGP 9.4 (built-in Kotlin), Kotlin 2.4, version catalog | - -## Modules - -``` -app/ Navigation, shell, screens (feature/*), Hilt wiring, help links -core/model/ Screen catalog (85 screens), API versioning constants, implementation status. Pure Kotlin -core/network/ API client: version header, auth, single-flight refresh, error decoding. Pure Kotlin -core/designsystem/ The ten Substratal themes, brand assets, status badges, shared components -core/data/ Session, secure token store, capabilities, preferences -``` - -**`ScreenCatalog`** in `core:model` is the source of truth for screens. Navigation registers a route and deep links for every screen in it, so all 85 are reachable. Screens without a built UI show their spec. The docs site's screen reference is generated from it too. - -## The version header - -`ApiHeadersInterceptor` sends `Substratal-Version` (from `ApiVersioning.CURRENT`) on every request. It records what the server echoes, and any `Deprecation` and `Sunset` headers. `ApiVersionMonitor` turns a deprecation into the *Update the app* banner. Base URLs carry no version. - -## Build and run - -```bash -./gradlew assembleDebug # build -./gradlew test # unit tests (catalog, API client, navigation registry) -./gradlew installDebug # install on a device or emulator (production API) - -# Against a local API (CompositeCode/substratalapps.com: make up && make migrate && make seed && make run) -./gradlew installDebug -Psubstratal.apiEnvironment=LOCAL -# sign in as admin@example.com / "local admin passphrase" -``` - -Debug builds allow plain HTTP to `10.0.2.2` (the emulator's host) only. - -## Adding a screen - -1. Build the composable under `app/.../feature//`. -2. Register it in `implementedScreens` (`HubNavHost.kt`). -3. Add its ID to `ImplementationStatus.available` (or `preview`). -4. Write `site/help/screens/.md`. - -The unit tests fail if steps 2 and 3 disagree, and the docs build fails if step 4 is missing. diff --git a/site/docs/developers/api/index.md b/site/docs/developers/api/index.md deleted file mode 100644 index 291f510..0000000 --- a/site/docs/developers/api/index.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: API overview -description: Base URL, version header, authentication and conventions for the Substratal Platform API. ---- - -# API overview - -The Platform API handles accounts, sign-in, roles, per-app access, organizations, settings, billing and push notifications for applications built on Substratal Apps. This page summarizes what an integration needs. The full contract is in the [API reference](v1/index.md), and the API's own documentation at [compositecode.github.io/substratalapps.com](https://compositecode.github.io/substratalapps.com/) is authoritative. - -## Base URL and version - -``` -https://api.substratalapps.com -``` - -The URL has **no version segment**. Each request names the API version it was written for in a header instead: - -```http -Substratal-Version: 1 -``` - -- **Required.** A missing header returns `400 api_version_required`, and an unknown version returns `400 unsupported_api_version`. Both list the supported versions in `details.supported`. There's no default. -- **Echoed.** Every response carries `Substratal-Version` with the version that served it. -- **Exempt:** `GET /health`, `POST /auth/oauth/token`, `/.well-known/*` and SCIM (`/scim/v2`). - -Paths like `/v1/users` were retired with the move to header versioning and now return `404`. See [API versions and changes](versions.md). - -## Authentication - -```bash -curl -s -X POST https://api.substratalapps.com/auth/login \ - -H "Substratal-Version: 1" -H "Content-Type: application/json" \ - -d '{"email": "you@example.com", "password": "…"}' -``` - -| Response | Next step | -|---|---| -| `{"access_token", "refresh_token", "expires_in": 900, …}` | Send `Authorization: Bearer ` | -| `{"mfa_required": true, "mfa_token", …}` | `POST /auth/mfa/verify` with `mfa_token` and `code` | - -- Access tokens last **15 minutes**. Renew them with `POST /auth/token/refresh`. -- Refresh tokens **rotate**: store the new one before using the new access token, and never refresh twice in parallel. Reusing a rotated token revokes the session. -- **API keys** (`satk_live_…`, `satk_test_…`) are for servers and agents. They're refused from browsers. - -## Conventions - -| Topic | Rule | -|---|---| -| IDs | Opaque strings with a type prefix: `usr_`, `app_`, `ent_`, `role_`, `key_`, `evt_`… Use `me` for yourself | -| Lists | `?limit=` and `?cursor=`. The response is `{"data": [...], "page": {"next_cursor", "has_more"}}` | -| Creates | Send `Idempotency-Key: `. It's required on grants and checkout. A retry with the same key returns the original result | -| Updates | Send `If-Match` with the `ETag` from your last read. A stale one returns `409 version_conflict` | -| Errors | `{"error": {"code", "message", "details"}}`. Branch on `code`, not `message`. Field errors are in `details.fields` | -| Rate limits | 100 requests a minute per key or session (more on hot paths, less on sign-in). `RateLimit-*` headers on every response; `Retry-After` on `429` | -| Wrong method | `405 method_not_allowed`, with an `Allow` header | - -## Getting started fast - -- Run the whole API locally: clone `CompositeCode/substratalapps.com`, then `make up && make migrate && make seed && make run`. It serves `http://localhost:8080`. -- Generate a client from [`openapi.yaml`](v1/openapi.yaml). It declares the version header, so generated clients send it. diff --git a/site/docs/developers/api/versions.md b/site/docs/developers/api/versions.md deleted file mode 100644 index b0f1a1b..0000000 --- a/site/docs/developers/api/versions.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: API versions and changes -description: How the Platform API is versioned, how deprecation works, and the spec updates this site has picked up. ---- - -# API versions and changes - -There are **two version numbers** to know about. - -| | What it is | Where you see it | -|---|---|---| -| **API version** | A whole number for the API's contract: `1` today. Breaking changes get a new number | The `Substratal-Version` request header; one reference per version under *Developers* | -| **Spec version** | `openapi.yaml`'s `info.version`, such as `1.0.0-draft.9`. Changes with every additive update within an API version | The header of each API reference page, and the table below | - -The docs site has its own versions too (the menu at the top of the page), which follow the app's releases. - -## How API versions change - -- **Additive changes ship within a version.** New endpoints, new optional fields and new enum values can appear at any time. Clients must ignore fields they don't know. The Android app does. -- **Breaking changes ship as the next number**, for example `Substratal-Version: 2`. The old version keeps working for **at least 6 months**. -- **Deprecation is announced in every response.** Responses to a deprecated version carry `Deprecation` and `Sunset` headers. The app shows an *Update the app* banner as soon as it sees them. -- **After the sunset date** the old version returns `400 unsupported_api_version`. -- **Webhook payloads** are versioned separately, by date (`api_version` on the subscription, such as `2026-10-05`). - -When API version 2 exists, this site will have an API reference for each version side by side, and each app release will say which version it sends. - -## Spec updates picked up - -Every time the pinned `openapi.yaml` is updated from the API repository, a row is added here. The sync is automated: see [How these docs stay current](../docs-workflow.md). - ---8<-- "openapi/CHANGELOG.md" - -The API's own changelog has the full detail: [compositecode.github.io/substratalapps.com/changelog](https://compositecode.github.io/substratalapps.com/changelog/). diff --git a/site/docs/developers/docs-workflow.md b/site/docs/developers/docs-workflow.md deleted file mode 100644 index f8bdf0f..0000000 --- a/site/docs/developers/docs-workflow.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: How these docs stay current -description: What's generated from the code, what CI checks, and how publishing works. ---- - -# How these docs stay current - -This site lives in the app's repository (`site/`) and is built and published by GitHub Actions. Code changes and doc changes ship in the same pull request, and anything that can be generated from the code is. - -## What's generated on every build - -| Page | Generated from | -|---|---| -| [Screen reference](../reference/screens/index.md): one page per screen | The app's `ScreenCatalog` and `ImplementationStatus`, exported by `./gradlew :core:model:exportScreenCatalog` | -| API reference, one per API version | `openapi/v{N}/openapi.yaml` and `SOURCE.yaml` | -| Spec update history | `openapi/CHANGELOG.md` | - -Hand-written help for each screen lives in `site/help/screens/.md` and is merged into the generated page. - -## What CI checks - -On every pull request and push (`.github/workflows/android.yml`): - -1. Unit tests, including that the navigation registry matches `ImplementationStatus`. -2. `scripts/check_api_coverage.py` confirms that every API call a screen makes exists in the pinned `openapi.yaml`. A spec change that removes or renames an endpoint fails the build until the app follows. -3. `site/scripts/generate.py` fails if a screen marked Available or Preview has no help page. -4. `mkdocs build --strict` fails on broken links and missing pages. - -## Publishing - -`.github/workflows/docs.yml` publishes with [mike](https://github.com/jimporter/mike) to the `gh-pages` branch: - -| Trigger | Publishes | -|---|---| -| Push to `main` touching the app, `site/` or `openapi/` | the `dev` version | -| Tag `v0.1.0` | version `0.1`, and moves `latest` to it | -| Manual run | either, on demand | - -Older versions stay published, so an older app's help links keep working. - -## Picking up API changes - -`.github/workflows/api-spec-sync.yml` runs daily, on demand, or when the API repository sends a `openapi-updated` dispatch. It: - -1. fetches `docs/openapi.yaml` from `CompositeCode/substratalapps.com` -2. if it changed, updates `openapi/v1/`, adds a row to `openapi/CHANGELOG.md`, and runs the coverage check -3. opens a pull request with the operations that were added or removed - -Merging that pull request publishes the new reference. - -To make API changes arrive immediately, the API repository's docs workflow can send the dispatch: - -```yaml -- run: gh api repos/CompositeCode/substratalapps-android/dispatches -f event_type=openapi-updated - env: { GH_TOKEN: "${{ secrets.ANDROID_DOCS_DISPATCH_TOKEN }}" } -``` - -## Working on the docs locally - -```bash -make docs-serve # export the catalog, generate, and serve at http://127.0.0.1:8000 -make docs # strict build into site/build -``` diff --git a/site/docs/developers/help-links.md b/site/docs/developers/help-links.md deleted file mode 100644 index 14ecb6e..0000000 --- a/site/docs/developers/help-links.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Linking to help from an app -description: The URL contract every Substratal app uses to open the right help page for its version. ---- - -# Linking to help from an app - -Every Substratal management app (Android, Web, iOS, macOS, Windows and Linux) opens help pages from this site using the same URL pattern. Keep to it, and help links keep working across platforms and releases. - -## The pattern - -``` -{base}{docs-version}/{path} -``` - -| Part | Value | -|---|---| -| `base` | `https://compositecode.github.io/substratalapps-android/` (configurable per build) | -| `docs-version` | The app's minor version (`0.1`) for release builds, `dev` for development builds, or `latest` | -| `path` | A page path ending in `/` | - -## Stable paths - -| Page | Path | -|---|---| -| A screen's help | `reference/screens/{screen-id-lowercase}/`, e.g. `reference/screens/usr-01/` | -| Help home | *(empty)* | -| Getting started | `getting-started/` | -| Sign-in help | `getting-started/sign-in/` | -| Tutorials | `tutorials/` | -| FAQ | `faq/` | -| API reference | `developers/api/v{N}/` | - -Screen IDs come from the shared design package (for example `USR-01`) and never change meaning, so a screen's help link is the same on every platform. - -## How the Android app does it - -`HelpLinks.forScreen(screen)` builds the link from `BuildConfig.HELP_BASE_URL`, `BuildConfig.DOCS_VERSION` and the screen's `docsPath`, and opens it in a Custom Tab. Every screen's top bar has a **?** that does this. Set a different base at build time with `-Psubstratal.helpBaseUrl=…`. - -## Versions that don't exist yet - -A release build links to its own minor version, which is published when that version is tagged. Builds made before their docs version is published should link to `latest`. diff --git a/site/docs/developers/index.md b/site/docs/developers/index.md deleted file mode 100644 index 0b56c04..0000000 --- a/site/docs/developers/index.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -title: Developers -description: For developers building against the Substratal Platform API, or working on the management apps. ---- - -# Developers - -**Building against the Platform API** - -- [API overview](api/index.md): base URL, versioning, authentication and conventions, in one page. -- [API reference (version 1)](api/v1/index.md): every endpoint, request and response, in Swagger UI, generated from the pinned `openapi.yaml`. -- [API versions and changes](api/versions.md): how versions work, deprecation, and every spec update this site has picked up. - -**Working on the management apps** - -- [Linking to help from an app](help-links.md): the URL contract every platform's app uses for in-app help. -- [Android app architecture](android.md): modules, build, running against a local API. -- [How these docs stay current](docs-workflow.md): what's generated, and what CI checks. diff --git a/site/docs/faq.md b/site/docs/faq.md deleted file mode 100644 index 420e091..0000000 --- a/site/docs/faq.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: FAQ -description: Answers to common questions about the Substratal management apps. ---- - -# FAQ - -??? question "I can't see Manage, Users or Applications. Why?" - The app only shows what your account can use. **Manage** appears once you have any administration, developer or operations rights. Ask an administrator for the role you need; see [Permissions and roles](reference/permissions.md). - -??? question "A screen says Planned. Is something broken?" - No. Planned screens aren't built in this app yet. They still open, show what they'll do, and link to their help page. See the [screen reference](reference/screens/index.md) for what's available. - -??? question "Why does Operations show sample numbers?" - The Overview's layout is ready, but the API doesn't have the monitoring endpoints it needs yet. The banner at the top says so. Only the API's own status is live. - -??? question "Can I use an API key to sign in to the app?" - No. The app uses your own account. API keys are for servers and agents, and the API refuses them from browsers. - -??? question "Does the app work offline?" - Partly. Screens you've opened keep showing what was last loaded, but every change needs a connection. That way, turning access off always takes effect immediately. - -??? question "Someone's access is off, but they're still in the app. Why?" - The application is told at once (`access.revoked`) and should end their session. App tokens already issued expire within 5 minutes. If they're still in after that, the application isn't handling the webhook. See [Applications, keys and webhooks](guides/applications.md). - -??? question "Why is there no /v1 in the API address any more?" - The API version moved from the URL into the `Substratal-Version` header, so addresses stay the same across versions. `/v1/...` paths now return `404`. See [API versions and changes](developers/api/versions.md). - -??? question "What happens when a new API version comes out?" - The app keeps working: the old version is supported for at least 6 months. When it's deprecated, the app shows an *Update the app* banner. Update before the date it gives. - -??? question "Which help version am I reading?" - The menu at the top shows it. Help opened from the app matches the app's version. **latest** is the newest release, and **dev** is what's being built now. - -??? question "How do I report a problem?" - Note the **request ID** from the error's details, if there is one, and contact your Substratal administrator or support. diff --git a/site/docs/getting-started/index.md b/site/docs/getting-started/index.md deleted file mode 100644 index c935ce5..0000000 --- a/site/docs/getting-started/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Getting started -description: Install the Substratal app, sign in, and find your way around. ---- - -# Getting started - -1. [Install the app](install.md) on your phone, tablet or Chromebook. -2. [Sign in](sign-in.md) with your Substratal account and your two-factor code. -3. [Find your way around](navigation.md): what's in the bottom bar, the Manage hub and the side menu on bigger screens. - -!!! tip "What you'll see depends on your role" - The app shows only what your account is allowed to use. A support agent sees Users and Access, a developer sees their own Applications and keys, and a superadmin sees everything, including Operations. See [Permissions and roles](../reference/permissions.md). diff --git a/site/docs/getting-started/install.md b/site/docs/getting-started/install.md deleted file mode 100644 index 6a9c8e3..0000000 --- a/site/docs/getting-started/install.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Install the app -description: Get the Substratal app on Android, and where the other platforms stand. ---- - -# Install the app - -=== "Android" - - The Android app runs on Android 8.0 (API 26) and later, on phones, tablets, foldables and Chromebooks. - - - **Staff and testers:** install the build your administrator shares through the Google Play internal testing track. - - **Everyone else:** the app will be listed on Google Play when it's released. - - The app needs an internet connection. It stores your session in the device's hardware-backed keystore, and nothing is included in device backups. - -=== "Web" - - The web app will run at `https://dashboard.substratalapps.com`. It's being built. - -=== "iOS, macOS, Windows, Linux" - - Being built. See [Platforms](../reference/platforms.md) for the state of each one. - -## Next - -[Sign in](sign-in.md). diff --git a/site/docs/getting-started/navigation.md b/site/docs/getting-started/navigation.md deleted file mode 100644 index ef44acd..0000000 --- a/site/docs/getting-started/navigation.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Find your way around -description: The bottom bar, the Manage hub, and the side menu on tablets and Chromebooks. ---- - -# Find your way around - -The app arranges the same sections differently depending on screen width. - -=== "Phones" - - A bar along the bottom has five destinations: - - | Destination | What's there | - |---|---| - | **Home** | The apps you can use | - | **Inbox** | Notifications from Substratal and your apps | - | **Orgs** | Organizations you belong to | - | **Manage** | Everything for running the platform: Operations, Administration and Developer. Only shown if you have rights in one of them | - | **Account** | Your profile and security, plus Plan & Billing, Settings, and Help & documentation | - -=== "Small tablets and foldables" - - The same five destinations, in a rail down the left side. - -=== "Tablets and Chromebooks" - - A menu down the left shows every section at once: - - - **Personal:** Home, Inbox, Organizations, Account, Plan & Billing, Settings - - **Operations:** Overview, Traffic & latency, Errors, Usage, Jobs & delivery, Alerts, Security events, Configuration, Integrations, Cost - - **Administration:** Users, Access, Roles, Audit Log, Send notification - - **Developer:** Applications, API Keys, Webhooks, Tenant - - **Help & documentation** - -## Help on every screen - -Every screen has a **?** button in its top bar. It opens that screen's page in this documentation, for the version of the app you're running. - -## Links that open the app - -Links to `https://dashboard.substratalapps.com/…`, for example in an invitation email, open in the app when it's installed. So do `substratal://` links, such as `substratal://admin/users`. Each screen's page in the [screen reference](../reference/screens/index.md) lists its link. - -## Screens that aren't built yet - -Some screens are marked **Planned**. You can still open them; they show what the screen will do and which API calls it uses. Screens marked **Preview** have their final layout but show sample data. diff --git a/site/docs/getting-started/sign-in.md b/site/docs/getting-started/sign-in.md deleted file mode 100644 index 6add33b..0000000 --- a/site/docs/getting-started/sign-in.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Sign in -description: Sign in with your email, password and two-factor code, and what to do when it doesn't work. ---- - -# Sign in - -1. Open the app and enter the **email** and **password** for your Substratal account. -2. If your account has two-factor authentication, enter the **6-digit code** from your authenticator app. If you've lost your device, choose **Use a recovery code** and enter one of the codes you saved when you turned two-factor on. -3. You land on **Home**. - -The app keeps you signed in. Your session refreshes in the background for up to 30 days of inactivity, and 90 days at most. - -!!! warning "Staff accounts: turn on two-factor" - Anyone with administration rights should turn on two-factor authentication straight away, in **Account › Security**. - -## If it doesn't work - -| You see | What to do | -|---|---| -| *That email and password don't match.* | Check the email, then the password. For security, the app never says which one is wrong. | -| *Too many attempts.* | After 5 failed attempts in 15 minutes the account is locked for 15 minutes. Wait, then try again. | -| *This account is suspended.* | An administrator has suspended your account. Contact them. | -| *That code didn't work.* | Codes change every 30 seconds. Check that your phone's clock is set automatically, then try the current code. | -| *This version of the app is no longer supported.* | Update the app. The API version it uses has been retired (see [API versions](../developers/api/versions.md)). | -| *You're offline.* | Connect to the internet and try again. | - -## Forgot your password - -Password reset happens on the web: choose **Forgot password or need help signing in?** and follow the link that's emailed to you. The in-app reset screens (AUTH-05, AUTH-06) are planned. - -See also: [Sign in screen](../reference/screens/auth-01.md) · [Two-factor screen](../reference/screens/auth-02.md) diff --git a/site/docs/guides/access-and-roles.md b/site/docs/guides/access-and-roles.md deleted file mode 100644 index 43ed672..0000000 --- a/site/docs/guides/access-and-roles.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: Access and roles -description: How Substratal decides whether a person can use an application, and what they can do in it. ---- - -# Access and roles - -## Is the app on for this person? - -Each person has an **entitlement status** for each application: - -| Status | Can they use it? | -|---|---| -| **Active** | Yes | -| **Scheduled** | Not yet. It starts later | -| **Turned off** (`disabled`) | No. Someone switched it off | -| **Expired** | No. A trial or subscription ended | -| **Revoked** | No. Ownership was removed | -| **None** | No. Nothing gives them this app | - -A person can have **several paths** to the same app at once: - -- **Managed by Substratal:** - - **free**, for free apps - - **subscription**, for Standard apps while they're on the Standard plan, personally or through an organization -- **Granted:** **admin grant**, **trial**, **purchase**, **organization seat** - -The app shows the combined answer first and lists the paths one tap away. Managed paths can only be turned off and back on, never edited or deleted. - -## What can they do? - -Once an app is on, **roles** decide what they can do in it. - -- **App roles** belong to one application, such as `admin`, `editor` and `viewer` for TimeTrack. They grant that app's permissions (`app.timetrack.export`). -- **Platform roles** are for running Substratal itself. See [Permissions and roles](../reference/permissions.md). - -Nobody can create or assign a role more powerful than their own. - -## Why can't someone use an app? - -Use the access check. See [Find out why someone can't use an app](../tutorials/why-no-access.md). diff --git a/site/docs/guides/account-security.md b/site/docs/guides/account-security.md deleted file mode 100644 index 7228fd7..0000000 --- a/site/docs/guides/account-security.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Your account and security -description: Two-factor authentication, sessions and signing out. ---- - -# Your account and security - -**Account** shows your email, ID and status, with links to **Security**, **Devices & notifications**, **Privacy**, **Plan & Billing**, **Settings** and **Help**. - -## Two-factor authentication - -Turn it on in **Account › Security**: - -1. Scan the code with an authenticator app. -2. Enter a code to confirm. -3. Save your **recovery codes** somewhere safe. Each one works once. - -Staff accounts should always have two-factor on. - -## Sessions - -**Security** lists where you're signed in. You can sign out of one device, or **sign out everywhere else** and stay signed in here. - -## Signing out - -**Account › Sign out**. Signing out: - -- ends the session on the server -- removes your session from this device -- stops push notifications to this device - -## How the app protects your session - -- Your session is stored encrypted with a key that lives in the device's secure hardware. It's never included in backups. -- Access tokens are kept in memory only and last 15 minutes. The app renews them automatically. -- If two refreshes ever happened at once, the API would treat it as theft and end the session. The app makes sure that never happens. diff --git a/site/docs/guides/appearance.md b/site/docs/guides/appearance.md deleted file mode 100644 index ac7798f..0000000 --- a/site/docs/guides/appearance.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: Appearance -description: Light and dark mode, and the ten Substratal themes. ---- - -# Appearance - -**Account › Settings** (SET-01) controls how the app looks. - -- **Mode:** **Light**, **Dark**, or **Match system** (the default). -- **Light theme:** Teal Ledger, Indigo Root, **Moss Paper** (default), Coral Clay, or Spore Pink. -- **Dark theme:** Night Soil, Deep Teal, Graphite Spore, **Forest Floor** (default), or Ember Clay. - -The app uses the light theme you picked in light mode and the dark one in dark mode. Wallpaper-based colours (Material You) are off, because the Substratal themes are the product palette. If you use the system's high-contrast setting, it takes over. - -!!! note - Your choice is saved on this device for now. It will sync across devices once the API can store it. diff --git a/site/docs/guides/applications.md b/site/docs/guides/applications.md deleted file mode 100644 index 90449c5..0000000 --- a/site/docs/guides/applications.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Applications, keys and webhooks -description: The developer side of Substratal: registering applications, their credentials, and how they hear about changes. ---- - -# Applications, keys and webhooks - -## Applications - -**Manage › Applications** lists the applications you can manage. Staff see every application and can switch to the **Review queue**, which lists submissions waiting for approval, oldest first. - -| Review status | Meaning | -|---|---| -| **Pending review** | Submitted by a developer; waiting for staff | -| **Approved** | Live. People can be given access | -| **Rejected** | Not approved. The reviewer's notes say why; the developer can edit and resubmit | -| **Suspended** | Paused by staff. No new access or sign-ins; existing access is untouched | - -Each application has: - -- **Overview** -- **Settings schema:** the typed settings it stores per person -- **Permissions & roles** -- **Push:** APNs, FCM and Web Push credentials -- its keys and webhooks - -Self-registered applications must use the hosted sign-in flow, not embedded sign-in. - -## API keys - -Keys let a server or agent call the API. See [Create an API key](../tutorials/create-an-api-key.md). Things to know: - -- A key's secret is shown **once**, when it's created or rotated. -- **Test** keys only see test data; **live** keys only see live data. -- **Agent** keys can't run destructive operations unless you allow it. -- An application's key is confined to that application. - -## Webhooks - -Webhooks tell an application's backend when something changes, for example `access.revoked` when someone's access is turned off. Every application must subscribe to `access.revoked` and `role.removed`. - -- **Deliveries** shows every attempt, its response and its status. **Redeliver** sends one again. -- Failed deliveries are retried for up to about 12 hours. -- **Rotate secret** issues a new signing secret, shown once. - -## Tenant - -Your **Tenant** is where your applications' data lives: the shared tier, or dedicated infrastructure for customers who need it. Tier changes are requested from **Tenant** and carried out by Substratal support. diff --git a/site/docs/guides/audit.md b/site/docs/guides/audit.md deleted file mode 100644 index 4e8bfdd..0000000 --- a/site/docs/guides/audit.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Audit log -description: Every change to users, access, roles, applications, keys and webhooks — who made it, and what changed. ---- - -# Audit log - -**Manage › Audit Log** lists every change made on the platform. You need `audit.view`. App owners see events for their own applications. - -Each event records: - -- the **actor**: a user, an API key, or the system -- the **action**, such as `entitlement.disabled` or `role.assigned` -- the **target** -- the **time** -- the **request ID** -- for changes, the values **before and after** - -## Filtering - -Filter by actor, actor type, target, action, application, and date range. The person page's **Activity** tab is the audit log filtered to that person. - -## How long events are kept - -- Events are searchable for **one year**. -- Older events are archived, without the before and after values, and kept indefinitely. Support can restore an archived event when it's needed for an investigation. Retrieval takes about 12 hours. - -## Exporting - -You can export a filtered view as NDJSON or CSV: - -- `POST /audit-exports` starts an export. -- `GET /audit-exports/{id}` returns a download link once it's ready, usually within a minute. - -You can run 20 exports a day. Files are deleted after 7 days. In-app export is planned. diff --git a/site/docs/guides/index.md b/site/docs/guides/index.md deleted file mode 100644 index bed21cf..0000000 --- a/site/docs/guides/index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Guides -description: How each part of the Substratal management app works. ---- - -# Guides - -Guides explain each area: what it's for, how it behaves, and the rules behind it. For step-by-step tasks, see [Tutorials](../tutorials/index.md). - -- [Users](users.md): find, invite, suspend and sign people out. -- [Access and roles](access-and-roles.md): the on/off switch for each app, and what people can do once it's on. -- [Applications, keys and webhooks](applications.md): the developer side. -- [Operations](operations.md): monitoring the API. -- [Audit log](audit.md): who changed what. -- [Your account and security](account-security.md) -- [Appearance](appearance.md): themes, and light and dark mode. diff --git a/site/docs/guides/operations.md b/site/docs/guides/operations.md deleted file mode 100644 index 120c1d2..0000000 --- a/site/docs/guides/operations.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Operations -description: Monitoring the Platform API — health, traffic, errors, background work, alerts, configuration and cost. ---- - -# Operations - -Operations is for whoever keeps the Platform API running. It's new in the management app; the shared design package doesn't have it yet. - -!!! note "What works today" - **Overview** is in preview: the layout is final, and it shows sample data apart from the API's own status. The other Operations screens are planned. They wait on the API's operations endpoints and permissions: issues [#43](https://github.com/CompositeCode/substratalapps.com/issues/43), [#44](https://github.com/CompositeCode/substratalapps.com/issues/44), [#45](https://github.com/CompositeCode/substratalapps.com/issues/45), [#46](https://github.com/CompositeCode/substratalapps.com/issues/46), [#47](https://github.com/CompositeCode/substratalapps.com/issues/47), [#48](https://github.com/CompositeCode/substratalapps.com/issues/48). - -| Screen | Shows | -|---|---| -| [Overview](../reference/screens/ops-01.md) | Component health, 24-hour traffic, error rate, latency, alerts, budget, deployed version | -| [Traffic & latency](../reference/screens/ops-02.md) | Requests and p50/p95/p99 by route over time, against the 150 ms target for hot paths | -| [Errors](../reference/screens/ops-03.md) | 4xx and 5xx by route and code, with request IDs | -| [Usage](../reference/screens/ops-04.md) | Requests by application, key and route; rate-limit hits | -| [Jobs & delivery](../reference/screens/ops-05.md) | Webhook and push delivery health, dead letters, scheduled jobs | -| [Alerts](../reference/screens/ops-06.md) | Firing and resolved alarms; acknowledging | -| [Security events](../reference/screens/ops-07.md) | Failed sign-ins, lockouts, refresh-token reuse, rate-limit hits | -| [Configuration](../reference/screens/ops-08.md) | Feature flags, rate limits, CORS origins, maintenance mode | -| [Integrations](../reference/screens/ops-09.md) | Where alerts go; MCP server and agent keys | -| [Cost](../reference/screens/ops-10.md) | AWS spend against the monthly budgets | - -## Who can see it - -Operations needs the proposed `operations.view` permission (and `operations.manage` to act). Until the API adds them, the app shows Operations to accounts that hold both `applications.manage` and `roles.manage`, which in practice means superadmins. diff --git a/site/docs/guides/users.md b/site/docs/guides/users.md deleted file mode 100644 index 518bd70..0000000 --- a/site/docs/guides/users.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -title: Users -description: Find, invite, suspend and sign out the people who use Substratal. ---- - -# Users - -**Manage › Users** lists every account. You need `users.list` to see it and `users.manage` to change anything. - -## Finding someone - -- **Search** by name, email or ID. Type at least 3 characters; results update as you type. -- **Filter** by status: **Active**, **Invited** (hasn't accepted yet) or **Suspended**. Deleted accounts are visible only to `users.manage`. -- Pull down to refresh. The list loads more as you scroll. - -## A person's page - -Opening someone shows tabs for: - -- **Access:** each app and whether it's on for them -- **Roles** -- **Sessions:** where they're signed in -- **Notifications** -- **Activity:** audit events about them - -## Suspending and signing out - -| Action | Effect | Undo | -|---|---|---| -| **Suspend** | Signs them out everywhere and blocks sign-in. Their access and roles are kept, so reactivating restores everything | **Reactivate** | -| **Sign out everywhere** | Ends every session without suspending them (`POST /users/{id}/sessions/revoke-all`) | They sign in again | -| **Revoke one session** | Ends one device's session | They sign in again | -| **Reset two-factor** | For someone who lost both their device and their recovery codes. Verify who they are first | They set up two-factor again | - -The platform owner's account is **protected**: it can't be suspended, deleted or demoted. - -## Privacy requests - -- **Export** gives everything Substratal holds about the person, as a file (5 per person per day). -- **Erasure** schedules the account for deletion. You can cancel it until it runs. - -See also: [Users screen](../reference/screens/usr-01.md) · [Invite a user](../tutorials/invite-a-user.md) diff --git a/site/docs/index.md b/site/docs/index.md deleted file mode 100644 index 7722d57..0000000 --- a/site/docs/index.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Substratal Help -description: Help and reference for the Substratal management apps and the Platform API behind them. ---- - -# Substratal Help - -Substratal's management apps let you run the Substratal Apps platform: - -- the people who use it -- what each person can reach -- the applications built on it -- the keys and webhooks those applications use -- the health of the API itself - -This site is the reference for every version of the app: Android today, then Web, iOS, macOS, Windows and Linux. All of them share the same screens, the same screen IDs and the same help pages. - -
- -- **New here?** - - --- - - Install the app, sign in and learn the layout. - - [Getting started](getting-started/index.md) - -- **Step by step** - - --- - - Invite a user, turn off access, register an application, create an API key. - - [Tutorials](tutorials/index.md) - -- **Every screen** - - --- - - What each screen does, who can use it and the API calls behind it. - - [Screen reference](reference/screens/index.md) - -- **Building on the API** - - --- - - The Platform API's OpenAPI reference, versioning and conventions. - - [Developers](developers/index.md) - -
- -## Which version am I reading? - -The version menu at the top of every page matches the app's version. **latest** is the most recent release, and **dev** follows what's being built. When you open help from inside the app, it opens the pages for your version. - -The **Platform API** has its own version, sent with every request in the `Substratal-Version` header. This version of the app uses API version **1**. See [API versions and changes](developers/api/versions.md). - -## Can't find it? - -Use search (press ++slash++ or ++s++), check the [FAQ](faq.md), or look a screen up by its ID in the [screen reference](reference/screens/index.md). Every screen in the app has a help button (**?**) that opens its page here. diff --git a/site/docs/reference/errors.md b/site/docs/reference/errors.md deleted file mode 100644 index 0ec962d..0000000 --- a/site/docs/reference/errors.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: Error messages -description: What the app's error messages mean, and what to do about them. ---- - -# Error messages - -| You see | Cause | What to do | -|---|---|---| -| *You're offline.* | No connection to the API | Reconnect, then **Try again**. Screens you've opened show what was last loaded | -| *Session ended* | Your session expired or was revoked, for example signed out from another device | Sign in again | -| *You don't have access* | Your role doesn't include this (`403`) | Ask an administrator. The app also rechecks your permissions | -| *Someone else changed this* | Another person saved a change first (`409 version_conflict`) | Choose **Reload** to see theirs, or **Re-apply** to save yours on top | -| *Too many requests. Try again in N seconds.* | Rate limit (`429`). The API allows 100 requests a minute per session | Wait for the countdown | -| *Substratal is temporarily unavailable* | The API is down or in maintenance (`503`) | Changes aren't retried automatically. Try again later | -| *This version of the app is no longer supported* | The API version this app uses has been retired (`400 unsupported_api_version`) | Update the app | -| *Update the app* banner | The API version this app uses is deprecated and has a retirement date | Update before that date | - -## Error details - -Where a message has **Details**, they include the error code and a **request ID**. Quote the request ID when you contact support; it identifies the exact request in the API's logs. - -For the full list of API error codes, see the [API reference](../developers/api/v1/index.md) and the Platform API's [Conventions → Errors](https://compositecode.github.io/substratalapps.com/api-reference/conventions/#errors). diff --git a/site/docs/reference/index.md b/site/docs/reference/index.md deleted file mode 100644 index 6f7bb3d..0000000 --- a/site/docs/reference/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Reference -description: Lookup tables for screens, permissions, statuses, error messages and platforms. ---- - -# Reference - -- [Screens](screens/index.md): every screen by ID, with its link, who can use it and its API calls. Generated from the app. -- [Permissions and roles](permissions.md): what each platform permission allows, and the built-in roles. -- [Statuses](statuses.md): what each status badge means. -- [Error messages](errors.md): what the app's error messages mean and what to do. -- [Platforms](platforms.md): which apps exist and how far along each one is. diff --git a/site/docs/reference/permissions.md b/site/docs/reference/permissions.md deleted file mode 100644 index b4cecb1..0000000 --- a/site/docs/reference/permissions.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Permissions and roles -description: Platform permissions, the built-in roles, and what each one shows in the app. ---- - -# Permissions and roles - -Platform permissions control what someone can do in Substratal itself. They never grant anything inside an application; that's what app roles are for. - -## Platform permissions - -| Permission | Allows | Shows in the app | -|---|---|---| -| `users.list` | See accounts and their roles | Users, Roles (read-only) | -| `users.manage` | Invite, suspend, reactivate, delete accounts; revoke sessions; reset two-factor | Users actions | -| `entitlements.manage` | Turn access on and off, and grant it, for any app | Access | -| `roles.manage` | Create, edit and assign roles | Roles | -| `applications.manage` | Create, approve, reject and suspend applications | Applications, review queue | -| `api_keys.manage` | Create and revoke platform API keys | API Keys | -| `webhooks.manage` | Manage any app's webhooks | Webhooks | -| `tenants.manage` | View and change Tenants | Tenant | -| `organizations.manage` | Manage any organization | Organizations | -| `billing.manage` | See and manage subscriptions | Plan & Billing (staff view) | -| `billing.refund` | Issue refunds | — | -| `audit.view` | Read the audit log | Audit Log | -| `notifications.send` | Send platform notifications | Send notification | -| `operations.view` *(proposed)* | See Operations | Operations | -| `operations.manage` *(proposed)* | Acknowledge alerts, change thresholds, redrive queues | Operations actions | - -## Built-in roles - -| Role | Permissions | -|---|---| -| `superadmin` | All of the above | -| `support` | `users.list`, `entitlements.manage`, `audit.view`, `tenants.manage`, `billing.manage` | -| `billing_admin` | `billing.manage`, `billing.refund`, `audit.view`, `users.list` | -| `member` | None. Everyone has it | - -Built-in roles can't be edited. You can create your own platform roles, but only with permissions you hold yourself. - -## Application owners - -Owning an application gives you its configuration, keys, webhooks, push credentials and roles, plus its users' access. You get this without any platform permission, but only for your own applications. diff --git a/site/docs/reference/platforms.md b/site/docs/reference/platforms.md deleted file mode 100644 index b62f588..0000000 --- a/site/docs/reference/platforms.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Platforms -description: The Substratal management apps on each platform, and how far along each one is. ---- - -# Platforms - -Every platform's app has the same screens, IDs, links and help pages. They differ only where the platform's own conventions differ. - -| Platform | Status | Push notifications | Repository | -|---|---|---|---| -| **Android** | Skeleton: navigation, sign-in, Users, Applications, Settings, Account; Operations Overview in preview | Firebase Cloud Messaging (planned) | `substratalapps-android` | -| **Web** | In development | Web Push | `substratalapps.com-web` | -| **iOS / iPadOS** | Planned | APNs | `substratalapps-ios` | -| **macOS** | Planned | APNs | `substratalapps-macos` | -| **Windows** | Planned | Local notifications, by polling | `substratalapps-windows` | -| **Linux** | Planned | Local notifications, by polling | `substratalapps-linux` | - -The [screen reference](screens/index.md) shows the state of each screen in the Android app. Other platforms will publish their status here as they ship. diff --git a/site/docs/reference/statuses.md b/site/docs/reference/statuses.md deleted file mode 100644 index 79ebcd7..0000000 --- a/site/docs/reference/statuses.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Statuses -description: What each status badge in the app means. ---- - -# Statuses - -Every status in the app is shown as an icon and a label, never colour alone. The same statuses are used on every platform. - -| Badge | Kind | Material icon | -|---|---|---| -| **Active** (`active`) | Good | `check_circle` | -| **Scheduled** (`scheduled`) | Information | `calendar_today` | -| **Turned off** (`disabled`) | Needs attention | `pause_circle` | -| **Expired** (`expired`) | Neutral | `schedule` | -| **Revoked** (`revoked`) | Problem | `cancel` | -| **No access** (`none`) | Neutral | `do_not_disturb_on` | -| **Invited** (`invited`) | Highlight | `mail` | -| **Suspended** (`suspended`) | Needs attention | `pause_circle` | -| **Deleted** (`deleted`) | Problem | `delete` | -| **Pending** (`pending`) | Information | `hourglass_empty` | -| **Important** (`important`) | Problem | `priority_high` | -| **Unread** (`unread`) | Highlight | `fiber_manual_record` | - -## Where each badge is used - -| Field | API value → badge | -|---|---| -| `entitlement.resolved_status` | `active` → Active, `scheduled` → Scheduled, `disabled` → Turned off, `expired` → Expired, `revoked` → Revoked, `none` → No access | -| `user.status` | `active` → Active, `invited` → Invited, `suspended` → Suspended, `deleted` → Deleted | -| `organization.status` | `active` → Active, `suspended` → Suspended | -| `membership_status` | `pending` → Invited, `active` → Active | -| `application.review_status` | `approved` → Active, `pending_review` → Pending, `rejected` → Revoked, `suspended` → Suspended | -| `webhook.status` | `healthy` → Active, `unhealthy` → Turned off, `disabled` → Expired | -| `webhook_delivery.status` | `pending` → Pending, `succeeded` → Active, `failed` → Revoked | -| `tenant.status` | `active` → Active, `migrating` → Scheduled, `suspended` → Suspended | -| `tier_change_request.status` | `pending` → Pending, `scheduled` → Scheduled, `in_progress` → Scheduled, `completed` → Active, `cancelled` → Expired | -| `erasure_request.status` | `scheduled` → Scheduled, `cancelled` → Expired, `completed` → Deleted | -| `subscription.status` | `active` → Active, `trialing` → Scheduled, `past_due` → Turned off, `canceled` → Expired, `unpaid` → Revoked, `incomplete` → Pending, `incomplete_expired` → Expired, `paused` → Suspended | -| `api_key` | `active` → Active, `expired (expires_at past)` → Expired, `revoked (revoked_at set)` → Revoked | -| `user.plan` | `free` → No access, `standard` → Active | -| `entitlement.source_labels` | `purchase` → Purchased, `trial` → Trial, `admin_grant` → Granted by admin, `org_seat` → Via {org}, `free` → Free (managed by Substratal), `subscription` → Standard (managed by Substratal) | -| `device.status` | `active` → Active, `inactive` → Expired | -| `notification.state` | `unread` → Unread, `read` → (no badge), `archived` → (no badge) | -| `notification.priority` | `high` → Important, `normal` → (no badge) | -| `notification.push.status` | `queued` → Pending, `sent` → Active, `failed` → Revoked, `skipped` → No access | -| `push_credential.status` | `active` → Active, `failed` → Turned off | - -Source: `status-semantics.json` in the shared design package. The Android app generates its badges from the same file. diff --git a/site/docs/tutorials/check-on-the-api.md b/site/docs/tutorials/check-on-the-api.md deleted file mode 100644 index 6ce0246..0000000 --- a/site/docs/tutorials/check-on-the-api.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Check on the API -description: See whether the Platform API is healthy, and what to look at when it isn't. ---- - -# Check on the API - -**Operations** shows how the Platform API is doing: its components, traffic, errors, background jobs, alerts and cost. It's visible to people with operations access. - -1. Go to **Manage › Overview** (on tablets: **Operations › Overview**). -2. The top of the screen shows each component's status: the API, MCP server, Stripe webhook, delivery worker, scheduled jobs and the database. -3. Below that are the last 24 hours of requests, the server error rate, and p95 latency against the 150 ms target. -4. **Alerts** lists anything firing. Tap one for details. - -!!! note "Preview" - The Overview's layout is in the app, but apart from the API's own status it shows **sample data** until the API's operations endpoints exist (issues [#43](https://github.com/CompositeCode/substratalapps.com/issues/43), [#44](https://github.com/CompositeCode/substratalapps.com/issues/44), [#47](https://github.com/CompositeCode/substratalapps.com/issues/47)). - -## Quick check without the app - -The health endpoint needs no token and no version header, so uptime monitors can call it as is: - -```bash -curl -s https://api.substratalapps.com/health -# {"status":"ok"} -``` - -Operational alarms (5xx rate, Lambda errors and throttles, database capacity, webhook dead letters) also go out by email from AWS CloudWatch. diff --git a/site/docs/tutorials/create-an-api-key.md b/site/docs/tutorials/create-an-api-key.md deleted file mode 100644 index a9e5297..0000000 --- a/site/docs/tutorials/create-an-api-key.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -title: Create an API key -description: Create a key for a backend service or an AI agent, and keep its secret safe. ---- - -# Create an API key - -API keys are for **servers and agents**, never for apps running on people's devices. The Substratal app itself never uses one; it uses your own sign-in. - -=== "In the app" - - 1. Go to **Manage › API Keys** and tap **Create key**. - 2. Choose: - - **Scope:** one application (most keys), or the whole platform (staff only). - - **Permissions:** only what the service needs. A platform key can't hold a permission you don't have, and an app key can never hold platform-administration permissions such as `users.manage`. - - **Mode:** **Live**, or **Test**, which only sees test data. - - **Use:** **Service**, or **Agent**. Agent keys can't run destructive operations unless you allow it. - - Optionally, an **expiry date**. - 3. Tap **Create**. The **secret is shown once**. Copy it into your password manager or secret store. The app clears it from the clipboard after 60 seconds. - - !!! info "Android status" - API key screens (KEY-01 to KEY-04) are planned. - -=== "With the API" - - ```bash - curl -s -X POST "$SUBSTRATAL_API/api-keys" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Substratal-Version: 1" \ - -H "Content-Type: application/json" \ - -d '{ - "name": "TimeTrack backend", - "scope": "app_timetrack", - "permissions": ["entitlements.manage", "users.list"], - "mode": "live", - "intended_use": "service" - }' - ``` - - The response includes `secret` (`satk_live_…`) **once**. Store it straight away. - -!!! danger "Keys don't work in browsers" - The API refuses an API key sent from a web page (`400 api_key_in_browser`), so a leaked key can't be used from one. Use keys only server-side. - -## Keeping keys healthy - -- **Rotate** a key from its detail screen (`POST /api-keys/{id}/rotate`). You get a new secret, shown once. -- **Revoke** keys you no longer use. `last_used_at` shows when each key was last used. diff --git a/site/docs/tutorials/index.md b/site/docs/tutorials/index.md deleted file mode 100644 index 641c22b..0000000 --- a/site/docs/tutorials/index.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Tutorials -description: Step-by-step walkthroughs of the most common administration tasks. ---- - -# Tutorials - -Each tutorial covers one task from start to finish. Where the Android screen for a step isn't built yet, the tutorial says so and gives the equivalent API call. You can make those calls with `curl` today. - -| Tutorial | You'll need | Time | -|---|---|---| -| [Invite a user](invite-a-user.md) | `users.manage` | 2 min | -| [Turn off someone's access](turn-off-access.md) | `entitlements.manage`, or own the app | 2 min | -| [Find out why someone can't use an app](why-no-access.md) | `users.list` or `entitlements.manage` | 3 min | -| [Register an application](register-an-application.md) | A verified email (staff: `applications.manage`) | 10 min | -| [Create an API key](create-an-api-key.md) | `api_keys.manage`, or own the app | 3 min | -| [Check on the API](check-on-the-api.md) | Operations access | 2 min | - -## Calling the API in these tutorials - -Every request needs two headers: your access token, and the API version. - -```bash -export SUBSTRATAL_API=https://api.substratalapps.com # no /v1: the version is a header -export TOKEN=… # an access token, from POST /auth/login - -curl -s "$SUBSTRATAL_API/users/me" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Substratal-Version: 1" -``` - -See the [API overview](../developers/api/index.md) for how to get a token. diff --git a/site/docs/tutorials/invite-a-user.md b/site/docs/tutorials/invite-a-user.md deleted file mode 100644 index c642f08..0000000 --- a/site/docs/tutorials/invite-a-user.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Invite a user -description: Create an account for someone and email them an invitation. ---- - -# Invite a user - -You'll need the `users.manage` permission (superadmins have it). - -=== "In the app" - - 1. Go to **Manage › Users**. On a tablet, **Users** is in the side menu. - 2. Tap **Invite user**. - 3. Enter their email and, optionally, a display name. - 4. Tap **Send invitation**. - - They get an email with a link. When they open it, they set a password and their account becomes **Active**. Until then it shows as **Invited**. - - !!! info "Android status" - The Users list is available. The **Invite user** form (USR-02) is planned; until it ships, use the API tab. - -=== "With the API" - - ```bash - curl -s -X POST "$SUBSTRATAL_API/users" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Substratal-Version: 1" \ - -H "Content-Type: application/json" \ - -H "Idempotency-Key: $(uuidgen)" \ - -d '{"email": "dana@example.com", "display_name": "Dana Ruiz"}' - ``` - - Returns `201` with the new User, `status: "invited"`. Sending the same `Idempotency-Key` again returns the same result instead of creating a second user. - -## Then - -- Give them access to an app: open the user, then **Access › Grant access**. -- Give them a staff role: open the user, then **Roles**. See [Permissions and roles](../reference/permissions.md). -- If the email didn't arrive: open the user and choose **Resend invitation** (`POST /users/{id}/invitation`). diff --git a/site/docs/tutorials/register-an-application.md b/site/docs/tutorials/register-an-application.md deleted file mode 100644 index 85857d2..0000000 --- a/site/docs/tutorials/register-an-application.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Register an application -description: Add an application to the platform so people can be given access to it. ---- - -# Register an application - -An **Application** is anything built on Substratal Apps that people sign in to. Registering it gives it an ID (`app_`), a catalog entry, and its own permissions and roles. - -- **Staff** (`applications.manage`) create applications that are approved straight away. -- **Developers** with a verified email can submit their own. A submission starts as **Pending review**, and staff approve or reject it from the review queue. - -=== "In the app" - - 1. Go to **Manage › Applications** and tap **Register application**. - 2. Enter: - - a **slug**: lowercase, permanent, becomes `app_` - - a **name** - - the **launch URL** - - any **redirect URIs** - 3. Choose the **access tier** (staff only): - - **Free:** everyone gets it. - - **Standard:** people on the $6/month Standard plan get it. - 4. Add the app's permissions (`app..`) and the role names it uses, such as `admin`, `editor` and `viewer`. - 5. Tap **Register**. - - !!! info "Android status" - The Applications list and its review-queue filter are available. The registration wizard (APL-02) is planned. - -=== "With the API" - - ```bash - curl -s -X POST "$SUBSTRATAL_API/applications" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Substratal-Version: 1" \ - -H "Content-Type: application/json" \ - -H "Idempotency-Key: $(uuidgen)" \ - -d '{ - "slug": "timetrack", - "name": "TimeTrack", - "launch_url": "https://timetrack.example.com", - "redirect_uris": ["https://timetrack.example.com/auth/callback"], - "access_tier": "standard", - "available_app_roles": ["admin", "member"], - "permissions": [{"key": "app.timetrack.export", "description": "Export timesheets"}] - }' - ``` - -## After registering - -1. [Create the app's API key](create-an-api-key.md) for its backend. -2. Subscribe its backend to `access.revoked` and `role.removed` webhooks. This is required, so the app ends sessions when access is turned off. -3. For push notifications, upload APNs, FCM or Web Push credentials on the app's **Push** tab. - -## Removing an application - -Today an application can be **suspended**, which blocks new access and new sign-ins but leaves existing access alone. Retiring an application completely is waiting on the API ([issue #52](https://github.com/CompositeCode/substratalapps.com/issues/52)). diff --git a/site/docs/tutorials/turn-off-access.md b/site/docs/tutorials/turn-off-access.md deleted file mode 100644 index b8448dd..0000000 --- a/site/docs/tutorials/turn-off-access.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -title: Turn off someone's access -description: Switch an application off for one person, immediately, with an undo window. ---- - -# Turn off someone's access - -Turning access off stops a person from using one application right away. It doesn't delete anything, and you can turn it back on. - -=== "In the app" - - 1. Open the person: **Manage › Users**, then search by name, email or `usr_` ID. - 2. Open the **Access** tab. Each application has a switch. - 3. Turn the switch off and enter a short reason, which is recorded in the audit log. - 4. A message with **Undo** appears for 10 seconds. - - Some access is **Managed by Substratal**: free apps, and apps covered by a Standard subscription. You can only turn that kind off and on again, never edit or delete it. - - !!! info "Android status" - The Access tab (ACX-01) and the switch (ACX-03) are planned. - -=== "With the API" - - Find the entitlement, then patch it with the `ETag` you got: - - ```bash - # 1. The person's entitlements (note the id and the ETag header) - curl -si "$SUBSTRATAL_API/users/usr_…/entitlements?application_id=app_timetrack" \ - -H "Authorization: Bearer $TOKEN" -H "Substratal-Version: 1" - - # 2. Turn it off - curl -s -X PATCH "$SUBSTRATAL_API/entitlements/ent_…" \ - -H "Authorization: Bearer $TOKEN" \ - -H "Substratal-Version: 1" \ - -H "Content-Type: application/json" \ - -H 'If-Match: "…etag…"' \ - -d '{"status": "disabled", "disabled_reason": "Left the team"}' - ``` - - If someone else changed it first you get `409 version_conflict`: fetch it again and retry. - -## What happens next - -- The application is told at once through its `access.revoked` webhook, and should end the person's session. -- App tokens already issued expire within 5 minutes. -- The change is in the [audit log](../guides/audit.md) with your name and reason. diff --git a/site/docs/tutorials/why-no-access.md b/site/docs/tutorials/why-no-access.md deleted file mode 100644 index 1b161db..0000000 --- a/site/docs/tutorials/why-no-access.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Find out why someone can't use an app -description: Use the access check to see exactly what decides whether a person can reach an application. ---- - -# Find out why someone can't use an app - -Whether someone can use an application comes down to three questions: - -1. Is their **account** active? Suspended accounts can't use anything. -2. Is the app **on for them**? This is their entitlement status, which can come from several places: free, their subscription, an organization seat, a trial or an admin grant. -3. If it's on, **what can they do** in it? That's decided by their roles. - -The access check answers all three at once. - -=== "In the app" - - 1. Open the person (**Manage › Users**), then the **Access** tab. - 2. Next to the application, tap **Why?** - 3. You'll see the resolved status, every path that gives them access (and which ones are off), and their roles and permissions in that app. - - !!! info "Android status" - The access check (ACX-04) is planned. - -=== "With the API" - - ```bash - curl -s "$SUBSTRATAL_API/users/usr_…/apps/app_timetrack/effective-permissions" \ - -H "Authorization: Bearer $TOKEN" -H "Substratal-Version: 1" - ``` - - ```json - { - "user_id": "usr_…", - "application_id": "app_timetrack", - "allowed": false, - "user_status": "active", - "entitlement_status": "disabled", - "access_paths": [ - { "source": "admin_grant", "entitlement_id": "ent_…", "status": "disabled" } - ], - "effective_permissions": [] - } - ``` - - Here, the only path is an admin grant that's been turned off. Turn it back on ([Turn off someone's access](turn-off-access.md) in reverse), or grant access another way. - -## Common answers - -| `entitlement_status` | Means | Fix | -|---|---|---| -| `none` | No path gives them this app | Grant access, or check the app's tier (Free or Standard) against their plan | -| `disabled` | Someone turned it off | Turn it on, if that's right | -| `expired` | A trial or subscription ended | Renew or grant again | -| `revoked` | Ownership was removed (refund, chargeback, terms) | Grant again only if appropriate | -| `scheduled` | Access starts later | Wait for `starts_at`, or change it | diff --git a/site/help/screens/acc-01.md b/site/help/screens/acc-01.md deleted file mode 100644 index 01ab7b2..0000000 --- a/site/help/screens/acc-01.md +++ /dev/null @@ -1,13 +0,0 @@ -## Using this screen - -The top card shows your email, your account ID (`usr_…`) and your status. Below are: - -- **Security:** two-factor and sessions -- **Devices & notifications** -- **Privacy:** export your data, or delete your account -- **Plan & Billing** -- **Settings:** appearance -- **Help & documentation:** this site -- **Sign out:** asks you to confirm, then ends your session and removes it from this device - -The footer shows the app version and the API version it's using. Include both when you report a problem. diff --git a/site/help/screens/apl-01.md b/site/help/screens/apl-01.md deleted file mode 100644 index be01fc2..0000000 --- a/site/help/screens/apl-01.md +++ /dev/null @@ -1,9 +0,0 @@ -## Using this screen - -Staff (`applications.manage`) see every application. Developers see the ones they own. - -- **Approved** shows live applications. -- **Review queue** shows submissions waiting for a decision, oldest first. -- **Register application** starts a new one. See [Register an application](../../tutorials/register-an-application.md). - -Each row shows the app's ID (`app_…`), its tier (Free or Standard) and its review status. diff --git a/site/help/screens/auth-01.md b/site/help/screens/auth-01.md deleted file mode 100644 index 53f3938..0000000 --- a/site/help/screens/auth-01.md +++ /dev/null @@ -1,7 +0,0 @@ -## Using this screen - -Enter the email and password for your Substratal account and tap **Sign in**. The eye button shows or hides the password. Your password manager can fill both fields. - -If your account has two-factor authentication, you'll be asked for a code next. See [Two-factor challenge](auth-02.md). - -Error messages never say whether an account exists. *That email and password don't match* covers both cases. After 5 failed attempts in 15 minutes, sign-in is locked for 15 minutes. More in [Sign in](../../getting-started/sign-in.md). diff --git a/site/help/screens/auth-02.md b/site/help/screens/auth-02.md deleted file mode 100644 index 59f98a2..0000000 --- a/site/help/screens/auth-02.md +++ /dev/null @@ -1,8 +0,0 @@ -## Using this screen - -Enter the 6-digit code from your authenticator app. The code is only valid briefly, so if you take too long you'll go back to sign-in. - -- **Use a recovery code** switches to a recovery code. Each one works once. -- **Back to sign in** cancels. - -If you've lost both your device and your recovery codes, an administrator with `users.manage` can reset two-factor for you after checking who you are. diff --git a/site/help/screens/glb-02.md b/site/help/screens/glb-02.md deleted file mode 100644 index d98465f..0000000 --- a/site/help/screens/glb-02.md +++ /dev/null @@ -1,9 +0,0 @@ -## Using this screen - -On phones and small tablets, **Manage** gathers everything for running the platform into one list: - -- **Operations:** monitoring the API -- **Administration:** users, access, roles, audit log, notifications -- **Developer:** applications, API keys, webhooks, tenant - -You only see the items your account can use. Items marked **Preview** or **Planned** aren't fully built yet. On tablets and Chromebooks these sections are in the side menu instead. diff --git a/site/help/screens/ops-01.md b/site/help/screens/ops-01.md deleted file mode 100644 index 39a7066..0000000 --- a/site/help/screens/ops-01.md +++ /dev/null @@ -1,10 +0,0 @@ -## Using this screen - -The Overview answers *is the API OK right now?* - -- **Components:** the API, MCP server, Stripe webhook, delivery worker, scheduled jobs and database. -- **Last 24 hours:** requests, server error rate, and p95 latency against the 150 ms target. -- **Alerts:** anything firing. Tap one to open Alerts. -- **Budget:** this month's AWS spend against the $75 warning budget. - -In this preview only the API's status is live; the rest is sample data, and the banner at the top says so. See [Operations](../../guides/operations.md). diff --git a/site/help/screens/set-01.md b/site/help/screens/set-01.md deleted file mode 100644 index 354da01..0000000 --- a/site/help/screens/set-01.md +++ /dev/null @@ -1,3 +0,0 @@ -## Using this screen - -Choose **Light**, **Dark** or **Match system**, then pick a light theme and a dark theme. The change applies straight away. See [Appearance](../../guides/appearance.md) for the ten themes. diff --git a/site/help/screens/usr-01.md b/site/help/screens/usr-01.md deleted file mode 100644 index e04684b..0000000 --- a/site/help/screens/usr-01.md +++ /dev/null @@ -1,8 +0,0 @@ -## Using this screen - -- **Search:** type at least 3 characters of a name or email, or a full `usr_` ID. -- **Filter:** tap **Active**, **Invited** or **Suspended**. Tap it again to clear it. -- **Open someone:** tap their row. -- **Invite user** is shown if you have `users.manage`. - -Pull down to refresh; scroll to load more. The newest accounts are listed first. See [Users](../../guides/users.md). diff --git a/site/mkdocs.yml b/site/mkdocs.yml deleted file mode 100644 index 1a56aca..0000000 --- a/site/mkdocs.yml +++ /dev/null @@ -1,142 +0,0 @@ -# Substratal Help — documentation for the Substratal management apps (Android first; Web, iOS, -# macOS, Windows and Linux share it). Built by `make docs`; published per app version with mike. -site_name: Substratal Help -site_description: Guides, tutorials, screen reference and API reference for the Substratal management apps. -site_url: https://compositecode.github.io/substratalapps-android/ -repo_url: https://github.com/CompositeCode/substratalapps-android -repo_name: substratalapps-android -edit_uri: edit/main/site/docs/ -copyright: Substratal Apps - -docs_dir: docs -site_dir: build - -theme: - name: material - language: en - logo: assets/mark-white.svg - favicon: assets/app-icon.svg - font: - text: Inter - code: JetBrains Mono - palette: - # Moss Paper / Forest Floor, the apps' default themes. - - media: "(prefers-color-scheme: light)" - scheme: default - primary: custom - accent: custom - toggle: { icon: material/weather-night, name: Switch to dark mode } - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: custom - accent: custom - toggle: { icon: material/weather-sunny, name: Switch to light mode } - features: - - navigation.tabs - - navigation.sections - - navigation.indexes - - navigation.top - - navigation.footer - - navigation.instant - - navigation.tracking - - search.suggest - - search.highlight - - search.share - - content.code.copy - - content.tabs.link - - content.action.edit - - toc.follow - -extra_css: - - assets/substratal.css - -extra: - # The version switcher reads versions.json, which mike writes at the site root. - version: - provider: mike - default: latest - alias: true - api_version: "1" - social: - - icon: fontawesome/brands/github - link: https://github.com/CompositeCode/substratalapps.com - name: Platform API repository - -plugins: - - search - - swagger-ui-tag - - mike: - canonical_version: latest - -markdown_extensions: - - abbr - - admonition - - attr_list - - def_list - - footnotes - - md_in_html - - tables - - toc: - permalink: true - - pymdownx.details - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.inlinehilite - - pymdownx.keys - - pymdownx.snippets: - base_path: [".."] # repository root, for openapi/CHANGELOG.md - check_paths: true - - pymdownx.superfences - - pymdownx.tabbed: - alternate_style: true - - pymdownx.tasklist: - custom_checkbox: true - -validation: - nav: - omitted_files: ignore - links: - absolute_links: relative_to_docs - unrecognized_links: warn - -nav: - - Home: index.md - - Getting started: - - getting-started/index.md - - Install the app: getting-started/install.md - - Sign in: getting-started/sign-in.md - - Find your way around: getting-started/navigation.md - - Tutorials: - - tutorials/index.md - - Invite a user: tutorials/invite-a-user.md - - Turn off someone's access: tutorials/turn-off-access.md - - Find out why someone can't use an app: tutorials/why-no-access.md - - Register an application: tutorials/register-an-application.md - - Create an API key: tutorials/create-an-api-key.md - - Check on the API: tutorials/check-on-the-api.md - - Guides: - - guides/index.md - - Users: guides/users.md - - Access and roles: guides/access-and-roles.md - - Applications, keys and webhooks: guides/applications.md - - Operations: guides/operations.md - - Audit log: guides/audit.md - - Your account and security: guides/account-security.md - - Appearance: guides/appearance.md - - Reference: - - reference/index.md - - Screens: reference/screens/index.md - - Permissions and roles: reference/permissions.md - - Statuses: reference/statuses.md - - Error messages: reference/errors.md - - Platforms: reference/platforms.md - - Developers: - - developers/index.md - - API overview: developers/api/index.md - - API reference (version 1): developers/api/v1/index.md - - API versions and changes: developers/api/versions.md - - Linking to help from an app: developers/help-links.md - - Android app architecture: developers/android.md - - How these docs stay current: developers/docs-workflow.md - - FAQ: faq.md - - What's new: changelog.md diff --git a/site/requirements.txt b/site/requirements.txt deleted file mode 100644 index 2ce0a86..0000000 --- a/site/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# Documentation site toolchain. Pinned so local builds match CI. -# Stay on MkDocs 1.x: 2.0 removes the plugin system this site uses (Material team, 2026-02-18). -mkdocs==1.6.1 -mkdocs-material==9.7.7 -mike==2.2.0 -mkdocs-swagger-ui-tag==0.8.1 -PyYAML==6.0.3 diff --git a/site/scripts/generate.py b/site/scripts/generate.py deleted file mode 100755 index 30c7e30..0000000 --- a/site/scripts/generate.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env python3 -"""Generates the parts of the documentation site that come from the code. - -Inputs (all in this repository, so docs can't drift from what's built): - site/.generated/screens.json ./gradlew :core:model:exportScreenCatalog (the app's screen catalog) - openapi/v/openapi.yaml the pinned API contract for each API version (Substratal-Version: N) - openapi/v/SOURCE.yaml where and when it was synced from the API repository - site/help/screens/.md hand-written help for a screen (required once a screen ships) - -Outputs (git-ignored, rebuilt on every docs build): - site/docs/reference/screens/index.md and one page per screen - site/docs/developers/api/v/openapi.yaml and index.md (Swagger UI for that version) - -Exits 1 when a screen the app has shipped (Available or Preview) has no hand-written help, so a -screen can't reach main undocumented. -""" -import json -import pathlib -import re -import shutil -import sys - -import yaml - -ROOT = pathlib.Path(__file__).resolve().parents[2] -SITE = ROOT / "site" -DOCS = SITE / "docs" -CATALOG = SITE / ".generated" / "screens.json" -HELP = SITE / "help" / "screens" -API_ISSUES = "https://github.com/CompositeCode/substratalapps.com/issues/" -DASHBOARD = "https://dashboard.substratalapps.com" - -SECTION_ORDER = ["OPERATIONS", "ADMINISTRATION", "DEVELOPER", "PERSONAL", "AUTH", "GLOBAL"] -SECTION_TITLES = { - "OPERATIONS": "Operations", "ADMINISTRATION": "Administration", "DEVELOPER": "Developer", - "PERSONAL": "Personal", "AUTH": "Sign-in", "GLOBAL": "Shell", -} -STATE_LABEL = {"AVAILABLE": "Available", "PREVIEW": "Preview", "PLANNED": "Planned"} -STATE_NOTE = { - "AVAILABLE": None, - "PREVIEW": '!!! note "Preview"\n The layout is in the app, but it shows sample data until the API endpoints it needs exist.', - "PLANNED": '!!! info "Planned"\n This screen isn\'t built in the Android app yet. You can still open it by its link; the app shows what it will do.', -} -PHASE_LABEL = {"MVP": "MVP", "PHASE_2": "Phase 2", "PHASE_3": "Phase 3", "PROPOSED": "Proposed"} - - -def norm(path): - return re.sub(r"\{[^}]+\}", "{}", path) - - -def api_versions(): - """Every pinned API version: openapi/v1, openapi/v2, … (the Substratal-Version header value).""" - out = [] - for d in sorted((ROOT / "openapi").glob("v*")): - spec = yaml.safe_load((d / "openapi.yaml").read_text()) - source = yaml.safe_load((d / "SOURCE.yaml").read_text()) if (d / "SOURCE.yaml").exists() else {} - out.append((d.name, spec, source)) - return out - - -def operation_index(spec): - index = {} - for path, item in spec["paths"].items(): - for method, op in item.items(): - if method in ("get", "post", "put", "patch", "delete"): - index[(method.upper(), norm(path))] = { - "id": op.get("operationId", ""), - "tag": (op.get("tags") or ["Other"])[0], - "phase": op.get("x-substratal-phase", ""), - } - return index - - -def screen_page(entry, ops_index, current_api): - s = entry["spec"] - sid, state = s["id"], entry["buildState"] - lines = [ - "---", - f"title: \"{s['id']} {s['title']}\"", - f"description: \"{s['purpose'].replace(chr(34), '')}\"", - "---", - "", - f"# {s['title']}", - "", - f"`{sid}` · **{STATE_LABEL[state]}** in the Android app · {PHASE_LABEL[s['phase']]} · {SECTION_TITLES[s['section']]}", - "", - ] - if s["purpose"]: - lines += [s["purpose"], ""] - if STATE_NOTE[state]: - lines += [STATE_NOTE[state], ""] - - help_file = HELP / f"{sid.lower()}.md" - if help_file.exists(): - lines += [help_file.read_text().strip(), ""] - - lines += ["## At a glance", "", "| | |", "|---|---|"] - lines.append(f"| Who can use it | {s['who'] or 'Everyone signed in'} |") - if s["route"].startswith("("): - lines.append(f"| Opens as | {s['route'].strip('()')} (no link of its own) |") - else: - lines.append(f"| Link | `{DASHBOARD}{s['route']}` |") - lines.append(f"| App link | `substratal://{entry['navRoute']}` |") - lines += [f"| Kind of screen | {s['archetype'].capitalize()} |", ""] - - if s["operations"]: - lines += [ - "## API calls", - "", - f"Sent with `Substratal-Version: {current_api}`. See the [API reference](../../developers/api/v{current_api}/index.md) for request and response details.", - "", - "| Call | Operation | Does |", - "|---|---|---|", - ] - for op in s["operations"]: - meta = ops_index.get((op["method"], norm(op["path"])), {}) - lines.append(f"| `{op['method']} {op['path']}` | `{meta.get('id', '—')}` | {op['summary']} |") - lines.append("") - - if s["apiGaps"]: - lines += ["## Waiting on the API", ""] - for ref in s["apiGaps"]: - lines.append(f"- [Issue {ref}]({API_ISSUES}{ref.lstrip('#')}) in CompositeCode/substratalapps.com") - lines.append("") - return "\n".join(lines) - - -def screen_index(entries): - counts = {k: sum(1 for e in entries if e["buildState"] == k) for k in STATE_LABEL} - lines = [ - "---", - "title: Screen reference", - "description: Every screen in the Substratal management apps, what it does, who can use it and the API calls behind it.", - "---", - "", - "# Screen reference", - "", - "Every screen in the Substratal management apps, by its stable ID. The IDs and links are the same on every platform, " - "so `/reference/screens/usr-01/` is the help page for the Users screen everywhere.", - "", - f"In the Android app: **{counts['AVAILABLE']} available**, **{counts['PREVIEW']} in preview**, " - f"**{counts['PLANNED']} planned**. This page is generated from the app's code on every build.", - "", - ] - for section in SECTION_ORDER: - rows = [e for e in entries if e["spec"]["section"] == section] - if not rows: - continue - lines += [f"## {SECTION_TITLES[section]}", "", "| ID | Screen | Android | Phase |", "|---|---|---|---|"] - for e in rows: - s = e["spec"] - lines.append( - f"| `{s['id']}` | [{s['title']}]({s['id'].lower()}.md) | {STATE_LABEL[e['buildState']]} | {PHASE_LABEL[s['phase']]} |" - ) - lines.append("") - return "\n".join(lines) - - -def api_reference_page(name, spec, source): - number = name.lstrip("v") - ops = sum(1 for item in spec["paths"].values() for m in item if m in ("get", "post", "put", "patch", "delete")) - commit = source.get("source_commit", "") - return f"""--- -title: API reference (version {number}) -description: OpenAPI / Swagger reference for the Substratal Platform API, version {number}. ---- - -# API reference — version {number} - -| | | -|---|---| -| Send | `Substratal-Version: {number}` on every request | -| Base URL | `{spec["servers"][0]["url"]}` (no version in the path) | -| Spec | `openapi.yaml` {spec["info"]["version"]}, OpenAPI {spec["openapi"]}, {ops} operations | -| Synced from | [{source.get("source_repo", "")}@{commit[:7]}](https://github.com/{source.get("source_repo", "")}/blob/{commit}/{source.get("source_path", "")}) on {str(source.get("synced_at", ""))[:10]} | -| Download | [openapi.yaml](openapi.yaml) | - -Authenticate with **Authorize** using a user access token. API keys are refused from browsers -(`400 api_key_in_browser`), and the production API only accepts browser calls from first-party -origins, so use **Try it out** against a local devserver, or copy the request as `curl`. - -The Platform API's own documentation is authoritative where the two differ: -[compositecode.github.io/substratalapps.com](https://compositecode.github.io/substratalapps.com/). - - -""" - - -def main(): - if not CATALOG.exists(): - sys.exit(f"{CATALOG} is missing. Run ./gradlew :core:model:exportScreenCatalog first (make docs does).") - catalog = json.loads(CATALOG.read_text()) - entries = catalog["screens"] - versions = api_versions() - if not versions: - sys.exit("No pinned API versions under openapi/.") - - # API reference: one folder per API version. - for name, _spec, _source in versions: - target = DOCS / "developers" / "api" / name - target.mkdir(parents=True, exist_ok=True) - shutil.copyfile(ROOT / "openapi" / name / "openapi.yaml", target / "openapi.yaml") - (target / "index.md").write_text(api_reference_page(name, _spec, _source)) - - current = catalog["apiVersion"] - ops_index = operation_index(next(spec for name, spec, _ in versions if name == f"v{current}")) - - out = DOCS / "reference" / "screens" - if out.exists(): - shutil.rmtree(out) - out.mkdir(parents=True) - (out / "index.md").write_text(screen_index(entries)) - for entry in entries: - (out / f"{entry['spec']['id'].lower()}.md").write_text(screen_page(entry, ops_index, current)) - - undocumented = [ - e["spec"]["id"] for e in entries - if e["buildState"] != "PLANNED" and not (HELP / f"{e['spec']['id'].lower()}.md").exists() - ] - print(f"Generated {len(entries)} screen pages and {len(versions)} API reference version(s).") - if undocumented: - print("These screens are in the app but have no help page in site/help/screens/:") - print(" " + ", ".join(undocumented)) - return 1 - return 0 - - -if __name__ == "__main__": - sys.exit(main())