The native macOS client for the Substratal Apps Platform API: the Hub that staff and app developers use to run the platform (users, access, roles, Applications, API keys, webhooks, tenants and the audit log), plus every person's own apps, inbox, account and plan.
- UI spec: substratalapps-applications-specs → macOS (75 screens, the source of truth for screens, copy and behavior)
- Help: the shared Substratal Hub help center, the one documentation reference for every Hub app. Its source is
help/in the specs repo; the macOS pages are Using the Hub on macOS and Troubleshooting. - Plans: docs/PLAN-management-app.md · docs/PLAN-api-gaps.md · docs/PREREQUISITES.md
Skeleton. Every screen in the spec is reachable, and most show a placeholder linking to their spec and help page. Built so far:
| Shell | Sidebar (Personal · Administration · Developer) gated by Capabilities, Go menu shortcuts, Help menu, multiple windows, deep links (substratal://…) |
| AUTH-01/02 | Sign in, two-factor (TOTP or recovery code), session in the Keychain with single-flight token refresh, resume on relaunch |
| USR-01 | User directory against the live API: search, status filter, paging |
| SET-01 | Appearance (5 light + 5 dark themes) and API connection (production or local devserver) |
| GLB-03 | API version banners: Update available (Deprecation/Sunset) and Update required (unsupported_api_version) |
Verified end to end against the API's local devserver: sign-in, Capabilities, the user directory, and session resume.
The Operations console (health, traffic, logs, alarms, cost) is web-only by spec decision (PRODUCT-SPEC §2a, OPEN-QUESTIONS #27), so it isn't part of this app.
The Platform API is versioned by header, not URL. Every request goes to the host root and sends Substratal-Version: 1:
GET https://api.substratalapps.com/users/me ← no /v1
Substratal-Version: 1
/v1/… paths return 404. The version is the compile-time constant APIVersion.current in SubstratalKit, and VersionMiddleware adds the header to every request, so no call can forget it. Moving to a new API version is a code change, never configuration. See API versioning in the help center.
.
├── App/ The macOS app target: views and view models only
│ ├── SubstratalHubApp.swift Scenes: main window, route windows, Settings
│ ├── AppModel.swift Session, Capabilities, API environment, version status
│ ├── Navigation/ Split view, sidebar, menus/shortcuts, per-window routing
│ ├── Screens/ One folder per area; ScreenHost maps routes to screens
│ └── Resources/ Assets (from the spec's brand-assets), entitlements
├── AppUITests/ UI smoke tests
├── Packages/SubstratalKit/ Shared core (macOS now, iOS later)
│ ├── Sources/SubstratalAPI/ openapi.yaml → generated client (swift-openapi-generator, at build time)
│ └── Sources/SubstratalKit/ API middleware and versioning, sessions + Keychain, Capabilities,
│ screen catalog + routes, help links, theme, domain services
├── scripts/ sync-openapi.sh, import-screens.rb, help-check.rb
├── docs/ Plans and the prerequisites check
├── project.yml XcodeGen definition (the .xcodeproj is generated, not committed)
└── .github/workflows/ ci.yml, upstream-sync.yml, docs-dispatch.yml
Requirements: Xcode 26 or later, macOS 15 or later, XcodeGen (brew install xcodegen), Ruby for the scripts, and Node.js to build the help center for make help-check.
make project # generate SubstratalHub.xcodeproj
make build # or open SubstratalHub.xcodeproj and run the SubstratalHub scheme
scripts/run-local.sh # build and launch against the production API (or pass http://localhost:8080)
make run # build and launch against production
make test # SubstratalKit unit tests
make help-check # check the app's Help links against ../substratalapps-applications-specs
make check # everything CI runsThe first build takes a few minutes: the API client is generated from the full spec (124 operations).
Run the API repository's devserver (make up && make migrate && make seed && make run there), then:
make run API=http://localhost:8080or choose Settings → Connection → Local devserver in the app. Each environment keeps its own session. Sign in with the seeded ADMIN_EMAIL / ADMIN_PASSWORD from that repo's .env.
scripts/build-pkg.sh # → build/pkg/SubstratalHub-<version>.pkg (+ .sha256)
VERSION=0.2.0 BUILD_NUMBER=7 scripts/build-pkg.shIt archives the Release configuration and packages the app to install into /Applications (macOS 15 or later). With APP_IDENTITY and INSTALLER_IDENTITY (Developer ID) set, it signs both. With an App Store Connect key (NOTARY_KEY_PATH, NOTARY_KEY_ID, NOTARY_ISSUER_ID) it also notarizes and staples. Without them it builds an unsigned test package.
Releases on GitHub: push a tag (git tag v0.2.0 && git push origin v0.2.0), or run the Release workflow by hand. release.yml builds the .pkg and attaches it and its checksum to a GitHub Release. Without the Developer ID secrets the release is an unsigned pre-release; the secrets are listed at the top of the workflow.
Debug builds sign with your Apple Development certificate (team set in project.yml). Without a provisioning profile, the session token is stored in the login keychain instead of the data-protection Keychain. Release builds need the registered App ID and a Developer ID certificate; see docs/PREREQUISITES.md.
Two upstreams feed this repo, and both are vendored by script, never edited by hand:
| File | From | Refresh |
|---|---|---|
Packages/SubstratalKit/Sources/SubstratalAPI/openapi.yaml |
API repo docs/openapi.yaml on main |
make sync-api |
Packages/SubstratalKit/Sources/SubstratalKit/Resources/screens.json |
Specs repo macos/SCREENS.md |
make sync-screens |
The screen catalog drives the sidebar, menus, routes and deep links. The help center generates its screen pages from the same spec, and CI checks that every screen and Help menu link the app opens exists there.
The app's documentation is the shared Hub help center, in the specs repo, not here:
- Writing docs: edit
help/in substratalapps-applications-specs (guides, tutorials, developer pages, FAQ, optional per-screen notes). See itshelp/README.md. Per-screen pages, the versioned API reference and What's new are generated there on every build. - When this app changes:
docs-dispatch.ymlsendsapp-docs-updatedto the specs repo on every merge tomain, which rebuilds and republishes the help center. It needs theSPECS_DOCS_TOKENsecret; without it, the help center rebuilds daily. - When the API changes: the help center rebuilds its API reference from the live
openapi.yamldaily and onapi-contract-updated. This repo'supstream-sync.ymlopens a PR that updates the app's generated client. - Links can't break: CI builds the help center and runs
scripts/help-check.rb, so a screen without a help page, or a Help menu item pointing nowhere, fails the build.
Update CHANGELOG.md under Unreleased with every change people will notice.
- Sign-in shows in the main window; the spec's separate fixed-size sign-in
Windowis still to do. - Feature flags are built-in defaults until the API serves them (API issue #48).
- Push (APNs), Universal Links and Touch ID re-open lock need the provisioning prerequisites.
- No command palette (⌘K) or inspector yet.
MIT.