An agile retrospective application. | Read-only mirror of https://github.com/republique-et-canton-de-geneve/RetroGemini — République et canton de Genève. Issues & pull requests at the source. https://www.opensource.admin.ch/en/softwares/pfrqpp
  • TypeScript 86.6%
  • JavaScript 13%
  • Shell 0.2%
  • CSS 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dependabot[bot] d79e3f0eb2
npm(deps-dev): bump the development-dependencies group with 5 updates (#517)
Bumps the development-dependencies group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.71.1` | `8.71.2` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.71.1` | `8.71.2` |
| [@vitest/coverage-v8](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8) | `5.0.3` | `5.0.4` |
| [@vitest/ui](https://github.com/vitest-dev/vitest/tree/HEAD/packages/ui) | `5.0.3` | `5.0.4` |
| [postcss](https://github.com/postcss/postcss) | `8.5.29` | `8.5.30` |


Updates `@typescript-eslint/eslint-plugin` from 8.71.1 to 8.71.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.2/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.71.1 to 8.71.2
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.71.2/packages/parser)

Updates `@vitest/coverage-v8` from 5.0.3 to 5.0.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.4/packages/coverage-v8)

Updates `@vitest/ui` from 5.0.3 to 5.0.4
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v5.0.4/packages/ui)

Updates `postcss` from 8.5.29 to 8.5.30
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.29...8.5.30)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.71.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.71.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@vitest/coverage-v8"
  dependency-version: 5.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: "@vitest/ui"
  dependency-version: 5.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
- dependency-name: postcss
  dependency-version: 8.5.30
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: development-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-16 06:12:45 +00:00
.claude fix: Address the seven Codex findings on PR #417, and record D14-D16 2026-08-06 07:47:02 +00:00
.github Merge pull request #512 from republique-et-canton-de-geneve/dependabot/github_actions/actions/setup-node-7.1.0 2026-10-13 11:10:17 +02:00
.husky
__tests__ refactor: Retire dataService.updateTeam and the cached-feedback getters 2026-10-12 19:37:18 +00:00
components docs: Correct the Dashboard comment on template deletion 2026-10-12 19:37:18 +00:00
e2e fix: Measure the session headers with a sync chip shown, and let it step aside on phones 2026-10-12 19:24:29 +00:00
e2e-prod fix: Tie every log line to the request that produced it (H44) 2026-08-26 15:45:16 +00:00
i18n fix: Hold own work only while sync is stalled, and keep health-check proposals 2026-10-12 17:49:52 +00:00
k8s feat: Warn when changes stop being saved (35.0) 2026-10-12 14:55:32 +00:00
loadtest fix: Leave only the named session, and never emit on a downed socket 2026-07-30 06:54:46 +00:00
public docs: add retrospective board screenshot to README 2026-10-12 15:11:40 +02:00
scripts feat: Give health checks the retrospective's timer, shared not copied 2026-10-08 15:25:51 +00:00
server fix: Do not restore facilitator fields from a baseline older than the write 2026-10-12 19:37:18 +00:00
services refactor: Retire dataService.updateTeam and the cached-feedback getters 2026-10-12 19:37:18 +00:00
utils fix: Build the retro's timer expiry on the session it is applied to 2026-10-08 16:10:38 +00:00
.dockerignore feat: Serve the What's New release notes in French to French readers 2026-10-06 16:36:31 +00:00
.env.example fix: Tie every log line to the request that produced it (H44) 2026-08-26 15:45:16 +00:00
.gitattributes
.gitignore
.trivyignore
ACCESSIBILITY.md fix: Build the retro's timer expiry on the session it is applied to 2026-10-08 16:10:38 +00:00
AGENTS.md docs: Describe the add-only template merge and the cache and baseline rules 2026-10-12 19:39:53 +00:00
App.tsx improve: Health checks share the retro's participants panel; fix stuck status and feedback images 2026-10-07 07:55:05 +00:00
AUDIT_REPORT.md docs: Align repo documentation with current reality 2026-07-09 11:57:14 +00:00
CHANGELOG.fr.md feat: Warn when changes stop being saved (35.0) 2026-10-12 14:55:32 +00:00
CHANGELOG.md feat: Warn when changes stop being saved (35.0) 2026-10-12 14:55:32 +00:00
CLAUDE.md docs: add gstack tooling and unify AGENTS.md/CLAUDE.md as single source 2026-06-30 11:40:39 +00:00
CONTRIBUTING.md docs: add gstack tooling and unify AGENTS.md/CLAUDE.md as single source 2026-06-30 11:40:39 +00:00
docker-compose.yml
docker-entrypoint.sh
Dockerfile feat: Serve the What's New release notes in French to French readers 2026-10-06 16:36:31 +00:00
Dockerfile.dev
eslint.config.js test: Measure the accessibility baseline, and close out the pass (H42, H38) 2026-08-24 09:32:24 +00:00
HARDENING_STATUS.md docs: Document the template routes and the two follow-up decisions 2026-10-12 18:12:23 +00:00
index.css fix: Stop a pointer-opened dialog leaving a focus ring on its opener (H51) 2026-08-25 16:00:36 +00:00
index.html
index.tsx feat: Add i18n core, bilingual retro templates and language-aware emails 2026-10-06 06:51:13 +00:00
LICENSE
MAINTENANCE.md refactor: Type the session CAS stamp and unify socketAdapter (audit L1) 2026-07-28 11:48:20 +00:00
nginx.conf
nixpacks.toml
package-lock.json npm(deps-dev): bump the development-dependencies group with 5 updates (#517) 2026-10-16 06:12:45 +00:00
package.json npm(deps-dev): bump the development-dependencies group with 5 updates (#517) 2026-10-16 06:12:45 +00:00
playwright.config.ts fix: Keep the console's exit button on a phone, and stop a stray comment opener from erasing releases 2026-10-06 17:22:23 +00:00
playwright.prod.config.ts feat: Document French support, bump to 33.0 and test the language plumbing 2026-10-06 07:02:34 +00:00
postcss.config.js
publiccode.yml chore: Bump to 35.5 for the second review cycle fixes 2026-10-12 19:46:42 +00:00
QUICK_START_MAINTENANCE.md
railway.toml
README.md docs: add retrospective board screenshot to README 2026-10-12 15:11:40 +02:00
render.yaml docs: prepare RetroGemini for public discovery (#385) 2026-07-24 15:12:34 +02:00
retrogeminihardeningaudit.md audit result gstack & claude 2026-07-07 09:09:31 +02:00
SECURITY.md security: Give privileged actions a durable, append-only trace (H45) 2026-08-27 12:51:45 +00:00
server.js fix: Own the team lists through granular routes and carry renames into the live session 2026-10-12 14:39:45 +00:00
tsconfig.json
types.ts feat: Give health checks the retrospective's timer, shared not copied 2026-10-08 15:25:51 +00:00
VERSION chore: Bump to 35.5 for the second review cycle fixes 2026-10-12 19:46:42 +00:00
vite.config.ts
vitest.config.ts feat: Document French support, bump to 33.0 and test the language plumbing 2026-10-06 07:02:34 +00:00
vitest.setup.ts fix: make Web Storage tests pass on Node >= 26 in CI matrix 2026-07-22 15:15:15 +00:00

RetroGemini

CI Docker Pulls License: Unlicense

Deploy to Render

Private-by-design, real-time Scrum retrospectives and team health checks for organizations that need to keep their data on their own infrastructure.

RetroGemini was created at the State of Geneva and is already used by around 30 internal teams. Because candid retrospective feedback can be sensitive, our public administration cannot send it to external cloud services. RetroGemini keeps the complete application and its data under the organization's control.

This makes it a strong fit for public-sector, healthcare, finance, regulated, privacy-conscious, and air-gapped environments — or simply for teams that prefer to own their tools and data.

No external service is required at runtime. SMTP and OpenAI-compatible AI features are optional and disabled unless you configure them.

If RetroGemini solves a problem for your organization, please star the repository so more self-hosting and Agile teams can find it.

Start, Stop, Continue retrospective board with six fictional demo cards

Demo board with fictional data.

Try it in 60 seconds

You only need Docker:

docker run --pull=always -d \
  --name retrogemini \
  -p 8080:8080 \
  -v retro-data:/data \
  jpfroud/retrogemini:latest

Open http://localhost:8080, create a team, and start a retrospective.

The named Docker volume keeps your test data across container restarts. For a production deployment, also set a stable SESSION_TOKEN_SECRET, use HTTPS through your reverse proxy, and review the configuration below.

One-click temporary demo

The Deploy to Render button above creates a free personal evaluation instance from the latest published Docker image.

Render's free web services sleep after 15 minutes without traffic and use an ephemeral filesystem. The demo's SQLite data is therefore cleared whenever the service sleeps, restarts, or redeploys. Use this path to explore the product, not for a real team deployment.

What teams get

Area Capabilities
Retrospectives Start/Stop/Continue, 4Ls, Mad/Sad/Glad, Sailboat, KALM, DAKI, Starfish, Rose/Thorn/Bud, Hot Air Balloon, Speed Car, Lean Coffee, Three Little Pigs, and custom templates — each available in English or French, chosen per retrospective
Languages English and French interface for facilitators, guests and the administration console, detected from the browser and switchable at any time; "What's New" reads in your language and invitation emails follow the sender's language
Guided facilitation Icebreaker, Brainstorm, Group, Vote, Discuss, Review, and Close phases, with contextual tips and timeboxes, and the same shared countdown timer in retrospectives and health checks
Live collaboration Real-time WebSocket sync, participant presence, typing activity, anonymous brainstorming, comments, grouping, and voting
Continuous improvement Action proposals, assignees, carry-over between sessions, ROTI follow-up, reports, and team health trends
Team administration Password-protected workspaces, member management, invitations, favorites, search, feedback hub, backup, and restore
Optional AI OpenAI-compatible ticket grouping, retrospective summaries, and cross-retrospective release analysis
Enterprise deployment SQLite or PostgreSQL, multi-pod Socket.IO adapters, Redis support, rolling-update recovery, proxies, custom CAs, Kubernetes, and OpenShift
Privacy Self-hosted assets, no CDN dependency, offline and air-gapped operation, non-root container, and organization-controlled storage

Why RetroGemini

  • Your retrospective data stays with you. The core application makes no external service calls and can run on an isolated network.
  • It is designed for real facilitation. Sessions follow a clear workflow rather than presenting an unstructured sticky-note board.
  • It supports the work after the retro. Actions, decisions, health checks, reports, and follow-up remain visible over time.
  • It starts small and scales. Use one Docker container with SQLite, or PostgreSQL plus Redis/PostgreSQL adapters for multiple pods.
  • It has real organizational usage. The product is shaped by feedback from approximately 30 teams at the State of Geneva.
  • It is open source with a permissive public-domain license. Use, adapt, and redistribute it without vendor lock-in.

Docker Compose

git clone https://github.com/republique-et-canton-de-geneve/RetroGemini.git
cd RetroGemini
docker compose up -d app

The application is available at http://localhost:8080, with data persisted in the retro-data volume.

Production example

Generate and keep a stable secret in your secret manager, then run:

docker run --pull=always -d \
  --name retrogemini \
  --restart unless-stopped \
  -p 8080:8080 \
  -v /path/to/retrogemini-data:/data \
  -e SESSION_TOKEN_SECRET='replace-with-a-long-random-secret' \
  -e SUPER_ADMIN_PASSWORD='replace-with-a-strong-admin-password' \
  jpfroud/retrogemini:latest

Put RetroGemini behind an HTTPS reverse proxy before exposing it outside a trusted network.

Deployment options

Docker

Build the image yourself if you do not want to pull the published image:

docker build -t retrogemini .
docker run -d \
  --name retrogemini \
  -p 8080:8080 \
  -v /path/to/data:/data \
  retrogemini

Kubernetes / OpenShift

See the Kubernetes and OpenShift guide. The container runs as a non-root user and exposes /health and /ready probes.

Railway

  1. Fork this repository.
  2. Create a Railway project from your fork.
  3. Add a persistent volume mounted at /data.
  4. Deploy using the included Dockerfile and railway.toml.

Without a persistent volume, SQLite data is ephemeral and will be lost during redeployments.

Configuration

All configuration is provided through environment variables. See .env.example for the complete list.

Variable Description Default
PORT Server port 3000 (8080 in Docker)
DATABASE_URL PostgreSQL connection URL; PostgreSQL is used instead of SQLite when set SQLite
POSTGRES_HOST PostgreSQL host — the discrete alternative to DATABASE_URL, used when it is unset (what the Kubernetes manifests supply from the PostgreSQL Secret) None
POSTGRES_PORT PostgreSQL port 5432
POSTGRES_USER PostgreSQL user None
POSTGRES_PASSWORD PostgreSQL password None
POSTGRES_DB PostgreSQL database name None
DATA_STORE_PATH SQLite database path /data/data.sqlite
REDIS_URL Redis connection for the multi-pod Socket.IO adapter Disabled
REDIS_HOST / REDIS_PORT / REDIS_PASSWORD Redis connection as discrete values, when REDIS_URL is not used Disabled
SMTP_HOST SMTP server for invitations and notifications Disabled
SMTP_PORT SMTP server port 587
SMTP_SECURE Use TLS for SMTP false
SMTP_USER SMTP username None
SMTP_PASS SMTP password None
FROM_EMAIL Sender email address SMTP_USER
SUPER_ADMIN_PASSWORD Enable the super-admin panel Disabled
SESSION_TOKEN_SECRET Stable HMAC secret for sessions, invitation credentials and the WebSocket join handshake; use the same value on every pod (without it, a restart drops live participants out of their session) Random per process
BACKUP_ENABLED Enable automatic server-side backups true
BACKUP_INTERVAL_HOURS Hours between automatic backups 24
BACKUP_MAX_COUNT Maximum automatic backups to keep 7
BACKUP_ON_STARTUP Create a backup when the server starts true
RESTORE_MAX_BODY_MB Maximum compressed restore upload size 128
RESTORE_MAX_DECOMPRESSED_MB Maximum decompressed restore size 512
AUTH_RATE_LIMIT_MAX Rejected team-create / restore-session credentials per IP per 15 minutes. Only 401 responses count, so a legitimate user is never blocked however often they reload. Counted per pod (no shared store, so a multi-pod deployment allows up to replicas × this value) 5
PUBLIC_BASE_URL Canonical public URL used to build mailed links. Required to send password-reset email — that mail carries a live token and its caller is anonymous, so without a configured origin the route answers 501 public_base_url_not_configured rather than trusting the request's Host. Invitations are authenticated and fall back to the request origin when it is unset. May include a sub-path None (invitations fall back to the request origin)
CORS_ORIGIN Restrict Socket.IO CORS to specific origin(s) *
TRUST_PROXY Express trust-proxy setting 1 in production
LOG_FORMAT json writes one line of JSON per log record — timestamp, level, source, message and the request/socket correlation id — which a cluster log aggregator can query; text keeps the human-readable console output. Secrets are redacted in both json in production, text otherwise
WIFI_SSID Wi-Fi name for an optional offline-network QR code Disabled
WIFI_PASSWORD Wi-Fi password for the optional QR code Disabled
PG_POOL_MAX PostgreSQL connections per pod; keep replicas × PG_POOL_MAX under the database's max_connections 10
SESSION_CACHE_MAX Live sessions cached in memory per pod (bounds memory only — session state is always recoverable from the database) 500
SOCKET_MAX_BUFFER_SIZE Maximum Socket.IO message size in bytes (caps one session update) 1000000
SOCKET_UPDATE_RATE Sustained session updates per second allowed per socket. Off by default and off in the shipped manifest — enable it only if the deployment is reachable beyond your internal network, or if a runaway client is actually observed. Start at 20 (timer sync is ~1/s per client). A throttled write is healed with the authoritative state and re-sent, never dropped 0 (disabled)
SOCKET_UPDATE_BURST Short update burst allowed above the sustained rate 2 × rate
LAST_CONNECTION_DEBOUNCE_MS Minimum interval between refreshes of a team's last-connection date on join (avoids a write storm when a whole session reconnects) 300000
ROSTER_BROADCAST_DEBOUNCE_MS Debounce window for coalescing roster rebroadcasts (avoids an O(N²) reconnect stampede); 0 broadcasts synchronously 250

Data persistence

RetroGemini supports two database backends:

  • SQLite for a simple single-container or single-pod deployment.
  • PostgreSQL for production and multi-pod deployments.

For SQLite, the server tries these locations in order:

  1. DATA_STORE_PATH
  2. /data/data.sqlite
  3. /tmp/data.sqlite — ephemeral
  4. ./data.sqlite

A warning is logged when ephemeral storage is used.

For multiple pods, Socket.IO uses Redis when REDIS_URL or REDIS_HOST is configured. Otherwise it uses the PostgreSQL adapter when PostgreSQL is the data store.

Corporate proxy and custom CA

export HTTP_PROXY=http://proxy.example.com:8080
export HTTPS_PROXY=http://proxy.example.com:8080
export NO_PROXY=localhost,127.0.0.1
export NODE_EXTRA_CA_CERTS=/path/to/corporate-ca.crt

The same variables can be configured in Docker Compose, Kubernetes, or OpenShift.

Architecture

  • Frontend: React 19, TypeScript, Vite, Tailwind CSS
  • Backend: Express 5, Socket.IO 4
  • Storage: SQLite with WAL mode or PostgreSQL
  • Scale-out: Redis or PostgreSQL Socket.IO adapter
  • Container: Node 26 Alpine, non-root user

The application is designed to survive rolling updates: session state is persisted, WebSocket clients reconnect automatically, and participants rejoin their active session.

Quality and security

Every pull request runs linting, TypeScript checks, unit and integration tests, a production build, dependency review, CodeQL analysis, and container vulnerability scanning.

Useful local checks:

npm run lint
npm run type-check
npm test
npm run build
npm run test:e2e

See MAINTENANCE.md, HARDENING_STATUS.md, SECURITY.md, and ACCESSIBILITY.md for details.

Accessibility is gated on every pull request — an axe-core run over six screens and eslint-plugin-jsx-a11y inside the lint budget. Both are ratchets: the number they allow may fall, never rise. ACCESSIBILITY.md states what is tested, what was fixed, and what is still missing.

Development

Requirements: Node.js 22+ and npm.

npm install
npm run start

In another terminal:

npm run dev

The backend runs on port 3000 and the Vite development server on port 5173.

Built with AI assistance, not dependent on AI

RetroGemini was developed with extensive assistance from Gemini, Claude, and Codex. That describes the development process, not a runtime dependency: the application works without an internet connection or an AI service.

Administrators can optionally connect an OpenAI-compatible model for grouping suggestions and summaries. Those features remain off until explicitly configured.

Contributing

Bug reports, feature requests, documentation improvements, translations, and code contributions are welcome. Read CONTRIBUTING.md before opening a pull request.

If you deploy RetroGemini, consider opening a discussion or issue to share your environment and feedback. Real-world deployment notes help other organizations adopt it.

License

RetroGemini is released into the public domain under The Unlicense. You may use, copy, modify, and distribute it for any purpose without conditions.