Welcome to the Federated Learning Interoperability Platform (FLIP)! We're excited you're here and want to contribute. This documentation is intended for individuals and institutions interested in contributing to FLIP. FLIP is an open-source project and, as such, its success relies on its community of contributors willing to keep improving it. Your contribution will be a valued addition to the code base; we simply ask that you read this page and understand our contribution process, whether you are a seasoned open-source contributor or whether you are a first-time contributor.
We are happy to talk with you about your needs for FLIP and your ideas for contributing to the project. One way to do this is to create an issue discussing your thoughts. It might be that a very similar feature is under development or already exists, so an issue is a great starting point.
When creating issues, please use the appropriate issue template:
- Bug Report -- for reporting bugs and unexpected behaviour
- Feature Request -- for proposing new features or enhancements
- Task -- for general tasks that would not require any coding.
- Documentation -- for reporting documentation issues or proposing improvements to documentation.
FLIP is developed by the London AI Centre in collaboration with Guy's and St Thomas' NHS Foundation Trust and King's College London. It is an open-source platform for federated training and evaluation of medical imaging AI models across healthcare institutions, while ensuring data privacy and security.
The FLIP repository is a mono-repo: it consolidates the Central Hub API, Trust APIs, UI, Docker deployment, and
the federated learning code (base library, FL services, and tutorials) for both NVFLARE and Flower. Both backends
are also provisioned in-tree (gitignored) under fl-services/<backend>/provision/ (see
README.md#federated-learning-setup).
FLIP/
├── deploy/ # Docker deployment files
│ └── providers/
│ ├── AWS/ # Terraform for Central Hub + cloud trust (EC2)
│ ├── kubernetes/ # Helm chart for K8s trust deployment
│ └── local/ # Ansible for on-premises trust deployment
├── docs/ # Sphinx documentation
├── flip-api/ # Central Hub API service
├── flip-ui/ # UI service
├── trust/ # Services deployed in individual trust environments
│ ├── data-access-api/ # Data access API
│ ├── imaging-api/ # Imaging API
│ ├── observability/ # Observability stack (Grafana, Loki, Alloy)
│ ├── omop-db/ # Mocked OMOP database
│ ├── orthanc/ # Mocked PACS service (Orthanc)
│ ├── trust-api/ # Trust API
│ └── xnat/ # Mocked XNAT service
├── flip-utils/ # `flip` Python package — platform logic, NVFLARE components, Flower helpers
├── fl-services/ # Docker images for FL networks, per backend: fl-services/nvflare/{fl-server,fl-client,fl-api-base,fl-base}, fl-services/flower/{superlink,supernode,fl-api-flower,fl-base}
├── fl-apps/ # FL app templates per backend: fl-apps/nvflare/{standard,standard_client_api,evaluation,evaluation_client_api,diffusion_model,fed_opt}, fl-apps/flower/{standard,evaluation} (+ check_required_files.sh)
└── fl-tutorials/ # End-to-end tutorial examples per backend: fl-tutorials/nvflare/ (xray classification, spleen seg/eval, diffusion), fl-tutorials/flower/ (xray classification, 3D spleen seg, numpy)In addition to the deployment prerequisites, you'll need the following for development:
- Python 3.12+
- UV - Python environment management tool (
curl -LsSf https://astral.sh/uv/install.sh | sh) - act - Run GitHub Actions locally (install via Homebrew:
brew install act) - GHCR login —
make uppulls the repo-built service images from GitHub Container Registry by default, so authenticate once with a PAT that hasread:packages:Building everything locally instead (no GHCR access needed) isecho "$GHCR_PAT" | docker login ghcr.io -u <your-github-username> --password-stdin
make up BUILD=true— see Running the stack below.
The file recommended_extensions.vsix contains a bundle of recommended VS Code
extensions for FLIP development. Install with:
code --install-extension recommended_extensions.vsixKey extensions include:
ms-vscode-remote.vscode-remote-extensionpack— connect to Docker containers and remote servers via SSH for in-container development, avoiding the need to rebuild images on every change- Python linting/formatting (ruff, mypy)
- Docker tooling
Other useful tools:
FLIP is a monorepo of independent Python sub-projects, each with its own .venv and pyproject.toml. Open it via the
checked-in flip.code-workspace — File → Open Workspace from File… — rather than opening the
repo root as a plain folder. The multi-root workspace lets Pylance use each folder's own interpreter and Ruff its own
config; for example, nvflare imports resolve only when fl-services/nvflare/fl-api-base is using its own .venv.
Opening the repo root as a single folder applies one interpreter (flip-api) to every file, so cross-project imports such
as nvflare show up as unresolved.
After opening, confirm the per-folder interpreter with Python: Select Interpreter (it prompts for the folder first,
then the .venv), and run Developer: Reload Window if an import is still flagged.
FLIP uses UV for all Python services. Each service has a pyproject.toml and a
.python-version file in its root directory.
To install dependencies for a service:
uv syncTo add a new dependency:
uv add <package-name> # runtime dependency
uv add <package-name> --dev # development-only dependency
uv add <package-name> --group <group> # dependency in a named groupThe pyproject.toml file is the source of truth for dependencies. The Python version in .python-version must match
the version used in the service's Dockerfile.
Recent npm and PyPI supply-chain attacks follow a consistent pattern: a maintainer's credentials are compromised, a malicious release is published, the community detects it, and the package is yanked — usually within a few hours. To keep poisoned releases out of FLIP's CI, developer machines, and Trust-side containers, FLIP enforces a 72-hour cooldown on dependency installs:
No FLIP build, CI or local, may install a Python or JavaScript package whose release timestamp on its upstream registry (PyPI / npm) is less than 72 hours old. This applies to direct and transitive dependencies.
The policy is enforced through native package-manager configuration:
- uv (Python) — every
pyproject.tomlsetstool.uv.exclude-newer = "3 days", souv lockanduv addnever resolve a release younger than 72 hours (theuv.lockrecords this as a rollingexclude-newer-span). The Dependency Cooldown Check job insecret-scanning.ymlrunsuv lock --checkon every project, failing CI if a lockfile drifts from its manifest or was generated under a widerexclude-newerwindow than the committed manifest allows. - npm (JavaScript) —
flip-ui/.npmrcsetsmin-release-age=3, sonpm installrefuses to resolve a release younger than 72 hours. This key was introduced in npm 11.10, soflip-ui/Dockerfileand thetest_flip_ui.ymlworkflow use Node 24 LTS (which ships npm >= 11.10); Node 22 LTS bundles npm 10.x and silently ignores the key. CI installs usenpm ci, which fails on anypackage-lock.json/package.jsonmismatch. npm only enforcesmin-release-ageat lockfile-write time (npm install <pkg>), not when installing from a pinnedpackage-lock.json, so the npm cooldown rests on.npmrcrather than a CI gate.
There is no automated dependency-update bot wired into the repo today. Dependency bumps are hand-rolled PRs; the
two layers above (uv exclude-newer and npm min-release-age at install time, uv lock --check in CI) catch a
too-fresh package regardless of how it arrived in the lockfile.
The cooldown applies automatically when you run uv add <package> or npm install <package> — a release younger
than 72 hours is simply not selected. Run make lock to refresh every uv.lock after a dependency change.
For a genuine same-day patch of an active CVE, the cooldown can be bypassed for the single package that needs it:
- uv — add an
exclude-newer-packageentry under[tool.uv]for that package (for exampleexclude-newer-package = { "<package>" = "<recent-timestamp>" }) and re-runuv lock. The entry is committed, so the exception is visible in the pull request anduv lock --checkstill passes. - npm — run
npm install <package> --min-release-age=0, which overrides the.npmrcsetting for that one command.
Any override must be justified in the pull-request description. Use it only for security patches that genuinely cannot wait 72 hours.
Environment variables for local development are defined in .env.development.example. This file uses
dummy/safe credentials for local use and must not be used in production. It centrally configures all services.
To get started, copy the example file:
cp .env.development.example .env.developmentThen generate the internal service key (also generated automatically by make up):
make generate-internal-service-keyThis writes INTERNAL_SERVICE_KEY with INTERNAL_SERVICE_KEY_HASH into .env.development for
fl-server-to-hub authentication.
Trusts are registered on the running hub with make register-trusts (shipped dev roster) or
make register-trust KIT=<CODE> (one trust), which inserts each trust row (with its
api_key_hash), claims an FL kit slot, and fills that trust's kit file trust/.env.<CODE>.<env>
carrying TRUST_API_KEY and TRUST_INTERNAL_SERVICE_KEY. make up runs register-trusts
automatically once the hub is up.
Docker services receive these variables via the env_file directive in the
compose file — avoid hardcoding values in Dockerfiles or compose files directly.
Authentication environment variables:
TRUST_API_KEY_HEADER— HTTP header name for trust-to-hub authentication.TRUST_API_KEY— single per-trust plaintext API key. Lives only in that trust's kit file (trust/.env.<CODE>.<env>), written bymake register-trusts; never on the hub.INTERNAL_SERVICE_KEY_HEADER— HTTP header name for fl-server-to-hub authentication.INTERNAL_SERVICE_KEY— internal service key used by the fl-server on the Central Hub.INTERNAL_SERVICE_KEY_HASH— hub-side SHA-256 hash of the internal service key.TRUST_INTERNAL_SERVICE_KEY_HEADER— HTTP header name for trust-internal service auth (defaultX-Trust-Internal-Service-Key). Sent by every caller (trust-api, imaging-api, fl-client) on every call to imaging-api or data-access-api.TRUST_INTERNAL_SERVICE_KEY— single per-trust plaintext key, in that trust's kit file (trust/.env.<CODE>.<env>), minted byregister_trust. Read by every trust-internal container. The hub never sees it. Distinct fromINTERNAL_SERVICE_KEY*(which protects fl-server → flip-api on the Central Hub). SeeCLAUDE.mdfor the threat model.
FL clients (trust side) intentionally do not receive Central Hub API credentials. Only the fl-server (on the Central Hub) communicates with flip-api. FL clients relay metrics and exceptions to the fl-server, which forwards them.
FL-specific environment variables:
FL_PROVISIONED_DIR— path to the NVFLARE or Flower provisioned workspace, derived per-backend bydeploy/fl_backend.mkfromFL_BACKEND. The Makefile automatically converts this to an absolute path (Docker requires absolute paths for volume mounts). This directory contains certificates, keys,fed_client.json, and other files generated during provisioning for each network. Both are now provisioned in-tree (gitignored): NVFLARE atfl-services/nvflare/provision/workspace-dev, Flower atfl-services/flower/provision/creds.FL_API_PORT— port for FL API services (default:8000).
Some services (e.g. flip-api) interact with AWS via boto3. You will need AWS credentials configured locally.
Configure AWS SSO:
aws configure ssoFor headless/SSH environments, use the device authorization flow:
aws configure sso --use-device-codeLog in to AWS in a new terminal session:
aws sso login --profile <your-profile-name>To avoid specifying the profile name on every command:
export AWS_PROFILE=<your-profile-name>The CI/CD pipeline requires GitHub repository secrets to run tests and deployments. See .github/SECRETS.md for the complete list, how to generate them, and security best practices.
To debug failing CI jobs without pushing, use act (requires Docker):
make ciThis runs all jobs defined in .github/workflows/ locally.
Contributors work from a fork, and a fork's CI runs with the fork's own GITHUB_TOKEN
and without the upstream repository secrets. Workflows that publish or deploy therefore cannot run on a fork —
they would only ever fail trying to reach londonaicentre-owned resources — so each is guarded with
if: github.repository == 'londonaicentre/FLIP' and shows up as skipped (neutral, not a red failure) on fork
pushes. These are:
- Image publishing —
Build and Push NVFLARE/Flower FL Docker Images, and theorthanc,xnat-*,flip-api, andtrust-*GHCR build-and-push workflows. - Releases —
release.ymlandrelease-pypi.yml(git tags, GitHub releases, PyPI publishing).
Everything that validates your change still runs on your fork, and a red result there is a real failure to fix:
lint, type-checking, unit and integration tests, docs, Terraform validation, Helm tests, and secret scanning.
Coverage upload to Codecov is non-blocking (fail_ci_if_error: false), so a missing CODECOV_TOKEN on your fork
never fails an otherwise-green job.
In development (PROD unset), make up pulls the repo-built service images
(flip-api, trust-api, imaging-api, data-access-api, orthanc) from GHCR
instead of building them — each carries image: + pull_policy: always in the dev
compose, and your local src/ is bind-mounted on top, so editing a .py still
hot-reloads against the pulled image. Startup is fast and matches the published
:stag artifact's environment.
make up # pull GHCR images (default; requires `docker login ghcr.io`)
make up BUILD=true # rebuild the repo-built services from local source insteadUse BUILD=true when you've changed dependencies (uv.lock/pyproject.toml),
system packages, or a Dockerfile — those live in the image layer, so a plain
make up (which pulls) won't pick them up. flip-ui always builds locally (it has
no GHCR image). Stag/prod (PROD=stag|true) are unaffected: they run the prod
compose with baked images and no bind-mounts.
Fork the repository before making changes Learn how to fork. All contributions to the develop branch must be made via pull requests. This allows us to review your changes and ensure they meet our quality standards before merging them into the main codebase.
Pull request early, commit often. Don't wait until your changes are perfect before creating a pull request. Commit your changes in small, logical chunks with clear commit messages. This makes it easier for reviewers to understand your changes and provide feedback.
We encourage you to create pull requests early. It helps us track the contributions under development, whether they are ready to be merged or not. Create a draft pull request until it is ready for formal review.
To ensure code quality, FLIP relies on linting tools (ruff), static type analysis (mypy), as well as a set of unit and integration tests.
This section highlights all the necessary preparation steps required before sending a pull request. To collaborate efficiently, please read through this section and follow them. Make sure you configure your coding environment to follow the configurations in the pyproject.toml files so these are automatically enforced.
FLIP uses ruff for both linting and formatting. The ruff configuration is defined in the pyproject.toml file at the root of the repository and in each service directory.
The project-wide ruff rules are:
[tool.ruff]
line-length = 120
target-version = "py312"
[tool.ruff.lint]
preview = true
select = ['I', 'F', 'E', 'W', 'PT', 'UP006', 'UP007', 'UP035', 'UP042', 'UP045']We also use mypy for static type checking.
Before submitting a pull request, ensure all linting passes by running the following commands from the relevant service directory:
# Run linting with auto-fix
uv run ruff check . --fix
# Run type checking
uv run mypy .Most services have a Makefile with a test target that runs linting, type checking, and tests in sequence. For example, from a Python service directory:
make testThe flip-ui service uses make unit_test (Vitest) instead of make test.
Documentation follows the Google style guide for Python docstrings.
If your PR contains code inspired by other code bases, you MUST inform us in your PR so we can add proper references to the original code and evaluate whether it can be incorporated into our License framework.
If it's not tested, it's broken, so all new functionality should be accompanied by an appropriate set of tests. Existing tests throughout the services can serve as examples.
FLIP uses pytest for testing and coverage.py for measuring code coverage.
Tests are located within each service's directory (e.g. flip-api/tests/, trust/trust-api/tests/). Test file names follow the test_[module_name].py or [module_name]_test.py convention.
To run tests for a specific service, navigate to the service directory and run:
uv run pytest --tb=short --disable-warnings --cov=src/ --cov-report=html --cov-report=term-missingOr use the Makefile shorthand:
make testThis will run ruff, mypy, and pytest in sequence. The coverage report is generated in HTML format in the htmlcov directory.
To run unit tests across all services in the main repository from the root:
make unit_testmake tests is a narrower target that runs flip-ui unit and Cypress e2e tests followed by the full flip-api
test suite (ruff, mypy, and pytest).
For the FL base library in flip-utils/, unit tests can be run either directly with pytest or via the shipped
Makefile target:
cd flip-utils && uv run pytest tests/unit -s -vv
# or:
make -C flip-utils unit-test # ruff --fix + pytest with coverageSee flip-utils/README.md for the FL package's tests, and
fl-services/nvflare/README.md for provisioning FL networks.
Kubernetes chart testing: The K8s Helm chart at deploy/providers/kubernetes/ can be tested with:
# Lint + render + schema validation
make -C deploy/providers/kubernetes test
# Render all FL backend variants
make -C deploy/providers/kubernetes template-all-backends
# Validate rendered templates against K8s schema (requires kubeconform)
make -C deploy/providers/kubernetes validateThe chart has a check_status.py smoke test script and a register_k8s_trust.py registration script. See the K8s README for details.
Testing fixtures: For testing APIs and integration tests, we use pytest fixtures. Shared fixtures are defined in conftest.py files. In some cases, factory_boy is used to create test data following production data structures.
All new functionality should be accompanied by an appropriate set of tests. Existing tests throughout the services can serve as examples.
Add these sections to the service's pyproject.toml to configure pytest and coverage:
[tool.coverage.report]
exclude_lines = ["if __name__ == .__main__.:"]
omit = ["*.venv/*", "*/tests/*", "*/__init__.py"]
[tool.pytest.ini_options]
python_files = ["test_*.py", "*_test.py"]
addopts = []
filterwarnings = ["ignore::DeprecationWarning", "ignore::FutureWarning"]A test belongs in tests/integration/ if and only if it touches a real backing service. Examples of "real backing service":
- A real Postgres (via the
sessionfixture or Testcontainers) - A real AWS service (S3, Cognito, SES)
- A running sibling API (trust-api, data-access-api, etc.) reachable over HTTP
- A real Orthanc / XNAT / OMOP fixture
If your test mocks all external dependencies (database session, HTTP client, AWS clients, sibling APIs), it's a unit test — put it in tests/unit/, mirroring the source layout (e.g. tests for src/flip_api/user_services/set_user_roles.py go in tests/unit/user_services/test_set_user_roles.py).
FastAPI TestClient on its own does not make a test "integration" — what matters is whether the dependencies it transitively hits are real or mocked. A TestClient-based test that overrides every dependency (via app.dependency_overrides) and patches the DB session is a unit test; one that runs against an un-overridden real Postgres is an integration test.
This rule applies across all services: flip-api/tests/, trust/trust-api/tests/, trust/imaging-api/tests/, trust/data-access-api/tests/, etc.
flip-api/tests/integration/ boots a throwaway postgres:16-alpine container per pytest session via testcontainers-python (tests/integration/conftest.py). The fixture builds the schema by running the Alembic migrations (alembic upgrade head) — the same DDL dev/prod apply at boot — then seeds permissions / roles / role-permissions once, and truncates per-test tables between tests. Both the existing session fixture and FastAPI's Depends(get_session) are rewired at the throwaway DB, so a new test only needs to request session (raw SQL access) and/or client (TestClient against the same DB) — no per-test setup required.
CI runs these via make integration_test from flip-api/. Docker is preinstalled on ubuntu-latest, so no services: block is needed in the workflow. AWS-backed integration tests (Cognito, S3, SES) are out of scope for this fixture and are skip-marked at the file level until ticket B2 lands.
flip-api's PostgreSQL schema is owned by Alembic, not SQLModel.metadata.create_all. Any schema-affecting change to db/models/*.py must ship a migration revision in the same PR — run make migration MESSAGE="..." from flip-api/ (flip-db must be up), review the autogenerated file (native PG enums + the ARRAY(UUID) column), and apply it with make migrate. The integration suite builds its schema from these migrations, and tests/integration/test_migrations.py is a drift guard: a model change without a matching revision makes the suite fail. See flip-api/README.md for the full workflow and the enum gotchas.
FLIP enforces the Developer Certificate of Origin (DCO) on all pull requests. All commit messages should contain the Signed-off-by line with an email address.
Git has a -s (or --signoff) command-line option to append this automatically to your commit message:
git commit -s -m 'a new commit'The commit message will be:
a new commit
Signed-off-by: Your Name <yourname@example.org>All code changes to the develop branch must be done via pull requests. All PRs should be associated with an issue.
- Create a new ticket or take a known ticket from the issue list.
- Check if there's already a branch dedicated to the task.
- If the task has not been taken, create a new branch
named
[ticket_id]-[task_name]. For example, branch name19-ci-pipeline-setupcorresponds to issue #19. The new branch should be based on the latestdevelopbranch. - Make changes to the branch (use detailed commit messages if possible).
- Make sure that new tests cover the changes and the changed codebase passes all tests locally (see Unit testing).
- Run linting and type checking before pushing (see Checking the coding style).
- Create a new pull request from the task branch to the
developbranch, with a detailed description of the purpose of this pull request. - Check the CI/CD status of the pull request, make sure all CI/CD tests pass.
- Wait for reviews; if there are reviews, make point-to-point responses, make further code changes if needed.
- If there are conflicts between the pull request branch and the
developbranch, pull the changes fromdevelopand resolve the conflicts locally. - Reviewer and contributor may have discussions back and forth until all comments are addressed.
- Wait for the pull request to be merged.
Releases are cut from main. The version is set in the root pyproject.toml, and merging to main triggers .github/workflows/release.yml, which reads that version, creates a v<MAJOR.MINOR.PATCH> git tag, and publishes a GitHub Release with auto-generated notes. On the same merge, the per-service .github/workflows/docker_build_*.yml workflows rebuild every service and push the :prod image tag (alongside :<sha>) to GHCR. There is no separate release-publishing step beyond merging to main.
FLIP follows Semantic Versioning. The version in the root pyproject.toml is the FLIP release version — it is what release.yml reads to create the git tag.
Each service has its own version string:
flip-api/pyproject.tomlflip-ui/package.jsontrust/trust-api/pyproject.tomltrust/imaging-api/pyproject.tomltrust/data-access-api/pyproject.toml
These are independent. Bump a service's version only when that service has user-visible changes, applying SemVer to the service alone. Services are not aligned with the root version on every release — a release where only flip-ui changed bumps the root and flip-ui/package.json, and nothing else. Per-service versions are informational today (deployments select images by branch via :prod / :stag tags, not by version string), but keeping them honest makes them useful for audit and changelog scope.
Before opening the release PR from develop to main:
developis green in CI.- All PRs intended for this release are merged into
developand carry an appropriate label. The release-notes categories come from.github/release.yml:enhancement/feature,bug/fix,documentation/docs,ci/build,chore/dependencies. PRs labelledignore-for-releaseare excluded. - Bump the
versionin the rootpyproject.tomlto the new release version. Additionally bump theversionin any service file (flip-api/pyproject.toml,flip-ui/package.json,trust/*/pyproject.toml) whose code changed in this release, per the independent-SemVer rule above. Leave unchanged services alone. - Run
make unit_testandmake integration_testlocally.
- From a branch off
develop, commit the version bumps above and open a PR targetingdevelopwith titleRelease v<X.Y.Z>. - Once that merges and CI is green, open a PR from
developtomain. - On merge to
main:release.ymlcreates thev<X.Y.Z>git tag and publishes the GitHub Release with auto-generated notes.- Every
docker_build_*.ymlworkflow under.github/workflows/rebuilds its service and pushes the:prodand:<sha>tags to GHCR.
- Verify on the Releases page that the new release exists and the notes look right. Verify on GHCR that the
:prodtags onflip-api,trust-api,imaging-api, anddata-access-apiwere updated by the latest build.
There is no CHANGELOG.md — the GitHub Releases page is the changelog. Release notes are generated automatically from PR titles and labels via .github/release.yml. To curate the notes for a release, ensure each PR going into develop has the right label before it is merged.
Once the :prod images are in GHCR, deploy with:
cd deploy/providers/AWS
make full-deploy PROD=trueSee deploy/providers/AWS/README.md for full deployment instructions. For staging, no release tag is required: merging to develop publishes :stag images automatically, and make full-deploy PROD=stag rolls them out.
For an urgent fix on main without pulling in unrelated develop work:
- Branch from
main, apply the fix, bump the patch version in the rootpyproject.toml(and any affected service). - Open a PR targeting
main. On merge, the same automation kicks in —release.ymltagsv<X.Y.Z+1>anddocker_build_*.ymlrebuilds:prod. - Forward-port the fix to
developso the next regular release includes it.
Branch builds do not auto-publish to GHCR. To deploy a release-candidate branch for testing, manually trigger the relevant build workflows first:
gh workflow run docker_build_flip_api.yml --ref <branch-name>
gh workflow run docker_build_trust_trust_api.yml --ref <branch-name>
# ...one per service whose image you want to testWait for green completion, then point your .env file's DOCKER_TAG at the sanitized branch name (the per-service workflows publish a :<branch> tag on every push).
To extend the platform with a new service, add a definition to the appropriate Docker Compose file:
# deploy/compose.development.yml
services:
new-service:
build:
context: ../path/to/service
dockerfile: Dockerfile
ports:
- "8080:8080"
volumes:
- ../path/to/service:/app
depends_on:
- flip-db
env_file:
- ../.env.developmentCreate a directory following the standard service layout:
new-service/
├── src/
│ └── new_service/
├── tests/
├── Dockerfile
├── Makefile
├── pyproject.toml
└── .python-version
Optionally add Makefile shortcuts at the repository root:
new-service:
docker compose -f deploy/compose.development.yml up -d new-serviceTo create projects in various pipeline stages (unstaged, staged, approved) for manual testing:
make -C flip-api create_testing_projectsTo clean up the test data:
make -C flip-api delete_testing_projectsThese are also available as VS Code tasks via Terminal > Run Task — look for Create testing projects and
Delete testing projects.
The admin user-action GIFs under docs/source/assets/admin/ (referenced from
docs/source/sys-admin/admin-project-and-user-management.rst) are
auto-regenerated on every push to main by
.github/workflows/regenerate_docs_gifs.yml. The workflow records the demo
Cypress specs under flip-ui/test/cypress/docs/admin/ against a fully mocked
backend, converts the resulting videos to GIFs with ffmpeg, and opens a PR
against develop for human review.
To regenerate locally:
cd flip-ui
npm run docs:record # records videos under test/cypress/videos/docs/admin/
npm run docs:gifs # ffmpeg → docs/source/assets/admin/*.gif (requires ffmpeg on PATH)The demo specs reuse the functional suite's fixtures and globalIntercepts,
and layer a CSS cursor overlay (flip-ui/test/cypress/docs/support/demoCursor.ts)
on top so the recorded GIFs read as visible user actions. When adding a new
admin-area UI flow that should be documented, add one demo spec under
flip-ui/test/cypress/docs/admin/<gif-basename>.spec.ts — the filename maps
1:1 to the output GIF name.