Minerva is an AI-assisted flashcard and active-recall application built with React and Vite. It shares Janus API accounts, encrypted provider credentials, and provider/model selection with Aether and Nyx. See Overall architecture below for how the four services fit together.
- Ask Minerva a question or request a flashcard in natural language.
- Review and edit the structured front, back, and suggested tags.
- Add the card to the rotation explicitly; AI responses are never saved automatically.
- Review due cards and rate recall as Again, Hard, Good, or Easy.
- Search, filter, edit, or delete the complete card library.
Minerva uses Node.js 24 LTS. With nvm installed:
nvm install
nvm use
npm ci
npm run devCopy .env.example to .env when running Janus API locally. Otherwise Minerva uses the deployed Janus API.
The full-resolution generated icon is retained in artwork/. Regenerate the optimized favicon, Apple touch icon, and UI icon with npm run icons.
The development server also includes a browser-only demo account with seeded Hindi, computing, and history cards:
Email: demo@minerva.local
Password: minerva-demo
Use the Use demo account button, or sign in with those credentials. The demo is not included in production builds, calls no AI provider, stores no API keys, and keeps its cards only in browser storage.
Minerva depends on this Janus API functionality, already implemented and deployed:
POST /api/minerva/respond- Flashcard create/list/update/delete endpoints
- The due-card endpoint and review scheduler
- User-scoped Firestore
flashcardsandflashcard_reviewscollections - Account-deletion cleanup for both collections
Provider keys remain encrypted by Janus API and never reach the browser after submission.
npm run check
npm run buildThe frontend tests cover confirmation-only card creation and the due-card review flow. Janus API contains API, isolation, scheduling, idempotency, and account-deletion tests. The production build also verifies that development-only demo fixtures are absent and that public icon assets stay within their size budgets.
The production container builds the Vite app and serves it through nginx on port 8080, including SPA fallback and /health.
gcloud run deploy minerva \
--source . \
--project donal-geraghty-home \
--region europe-west1 \
--allow-unauthenticated \
--port 8080The GitHub Actions workflow expects an Artifact Registry repository named minerva. Configure GCP_WORKLOAD_IDENTITY_PROVIDER (and optionally GCP_SERVICE_ACCOUNT), or provide the temporary GCP_SA_KEY fallback, before the first automated deployment.
The workflow at .github/workflows/deploy-gcp.yml follows the Aether Cloud Run pattern: it checks every pull request and builds, pushes, deploys, then health-checks a publicly accessible Minerva service for pushes to main or master.
Before the first push, complete these one-time Google Cloud and GitHub steps:
- Create an Artifact Registry Docker repository named
minervaineurope-west1in thedonal-geraghty-homeproject. - Create
minerva-github-deployer@donal-geraghty-home.iam.gserviceaccount.com(or choose another deployer identity) with permissions to deploy Cloud Run revisions, write to that Artifact Registry repository, and act as the Cloud Run runtime service account when one is configured. The usual roles areroles/run.admin,roles/artifactregistry.writer, androles/iam.serviceAccountUser. - Configure GitHub OpenID Connect / Workload Identity Federation for this repository and grant that identity access to the deployer service account.
- In the GitHub repository, add the Actions variable
GCP_WORKLOAD_IDENTITY_PROVIDERwith the full workload-identity-provider resource name. AddGCP_SERVICE_ACCOUNTonly if you chose a name other than the default Minerva deployer account. - Optionally add
VITE_JANUS_API_URLas a GitHub Actions variable if Minerva should use a different Janus API URL. The deployed Janus API URL is the default. - If you are not using Workload Identity Federation, instead add a
GCP_SA_KEYActions secret containing a deployer service-account JSON key. This is a fallback; workload identity is preferred.
Then push to main (or run Deploy Minerva to Cloud Run manually from the Actions tab). The workflow publishes the resulting Cloud Run URL in its run summary.
- Janus API — shared Flask backend for authentication, encrypted AI-provider credentials, and workout/nutrition/flashcard storage
- Aether — sibling frontend for workout tracking
- Nyx — sibling frontend for nutrition tracking
Minerva is one of three independently deployed React/Vite frontends built around a single shared backend, Janus API. All four services run as separate Cloud Run services in the same Google Cloud project (donal-geraghty-home, region europe-west1).
Aether (React/Vite, Cloud Run) ─┐
Minerva (React/Vite, Cloud Run) ─┼─▶ Janus API (Flask, Cloud Run) ─┬─▶ Firestore
Nyx (React/Vite, Cloud Run) ─┘ │ (users, credentials, nutrition,
│ workouts, flashcards)
├─▶ Cloud KMS
│ (encrypts each user's provider key)
├─▶ OpenAI / Mistral AI / Anthropic
│ (called with the user's own key)
└─▶ Cloud Scheduler
(Web Push reminders, every 5 minutes)
- All three frontends build the same way: a Node build stage produces a Vite bundle, served by an
nginx:alpinecontainer on port8080with SPA fallback and immutable asset caching. Each has its own Artifact Registry repository and its owndeploy-gcp.ymlworkflow that builds, pushes, and runsgcloud run deploy, preferring Workload Identity Federation with aGCP_SA_KEYsecret as a fallback — the pattern described above under "First GitHub Actions deployment." - Janus API deploys differently: it builds directly from source with
gcloud run deploy --source ., so it has no Artifact Registry step and authenticates with a staticGCP_SA_KEYsecret rather than Workload Identity Federation. - Minerva points at Janus API via the
VITE_JANUS_API_URLbuild argument (see "Local development" above). Because Aether and Nyx point at the same Janus API deployment and Firestore project, a single account's login session, encrypted AI-provider keys, and selected provider/model are shared across all three apps — only each app's own flashcard, workout, or nutrition data stays separate. - Minerva calls Janus API's
/api/minerva/*and/api/flashcards/*endpoints, plus the shared/api/auth/*and/api/user/*endpoints for account and AI-credential management. Aether and Nyx call their own equivalent endpoints on the same backend.