Athena, feature modules
How features are organized in athena/src/features/
Athena's src/features/ directory holds the per-feature code. Each feature is roughly: a Next.js page, the components that make up the page, and the actions or API calls.
The 12 modules
| Module | Page | Responsibility |
|---|---|---|
analytics | /analytics | Dashboard widgets, PKCE, MFA, login attempts |
auth | /login, /logout, /callback | Athena's own OAuth2 session against IAM Hera |
identities | /identities, /identities/[id] | Identity CRUD UI |
m2m-clients | /m2m-clients | M2M OAuth2 client management |
messages | /messages | Kratos courier message log |
oauth2-auth | (background) | OAuth2 flow into Athena |
oauth2-clients | /oauth2-clients, /oauth2-clients/[id] | User-facing OAuth2 client UI |
oauth2-tokens | /oauth2-tokens | Token introspection tool |
schemas | /schemas | Identity schema editor + live reload |
security | /security | Security audit log viewer |
sessions | /sessions | Active sessions list + revoke |
settings | /settings | Settings vault editor |
Per-module shape
A typical feature module:
src/features/<module>/
├── page.tsx # Next.js page (the actual route)
├── components/ # Feature-specific React
│ ├── identity-form.tsx
│ ├── identity-list.tsx
│ └── ...
├── actions.ts # Server actions / API client calls
├── types.ts # Module-specific types
└── tests/ # Vitest testsPages import only from their own components/ and from app-wide lib/, services/, @olympusoss/canvas.
Cross-module sharing happens through lib/ (utilities), not via inter-module imports.
Why this structure
Three goals:
- Discoverability, "I want to edit the locked-accounts page" →
src/features/security/. - Encapsulation, changes to identity UI don't touch sessions UI.
- Testability, each module's tests live with the module.
Page routing
Next.js's App Router uses filesystem-based routing:
src/app/identities/page.tsx→/identitiessrc/app/identities/[id]/page.tsx→/identities/<uuid>
The pages in src/app/ are typically thin re-exports of feature module pages:
// src/app/identities/page.tsx
export { default } from "@/features/identities/page";This keeps the App Router happy while feature code lives in the structured features/ tree.
Server actions
Athena uses Next.js server actions where appropriate:
// src/features/identities/actions.ts
"use server";
import { listIdentities } from "@/services/kratos/identities";
export async function getIdentities(domain: "ciam" | "iam") {
return await listIdentities(domain, { pageSize: 50 });
}Server actions run on the server, can call services directly, and surface results to client components via standard React patterns.
Where business logic lives
services/, Ory adapters.lib/, cross-cutting utilities.features/<x>/actions.ts, feature-specific server logic.features/<x>/components/, feature-specific UI.
Business logic that's tightly coupled to one feature stays in that feature. Business logic that's used by multiple features goes to lib/.
Tests
features/identities/
├── actions.test.ts # tests server actions, mocks services/kratos/identities
└── components/
└── identity-form.test.tsxTest naming convention: <file>.test.ts next to the code it tests.
Anti-patterns
- Importing across feature boundaries (
features/identitiesimporting fromfeatures/sessions). If you need to do this, the shared code belongs inlib/. - Putting routes in
app/without a feature module. Every page should have a corresponding feature module home. - Mixing client components into
app/page files. Use feature module re-exports.