Saturn — "the operating system for your freelance business." A Next.js app that unifies projects, requirements, proposals, milestones, timesheets, expenses, invoices, and client management for freelancers and agencies. Clients register on the same platform (no separate portal) and are scoped to what they're allowed to see.
Read `CONTEXT.md` for domain semantics (custom fields, invoice time units, settings cascades, permissions). It is the source of truth for business rules and is kept current — consult it before touching timesheets, invoices, or custom fields.
This codebase will outlive you. Every shortcut becomes someone else's burden; every hack compounds into debt that slows the whole team. The patterns you establish get copied — fight entropy and leave the code better than you found it.
- Match the surrounding code's style. Formatting is enforced by biome/ultracite (single quotes, no semicolons, sorted Tailwind classes via `cn`). `src/components/ui`
must never import
`
db
`
or call Drizzle directly.
**
Server components fetch data only through service objects.
`
src/app/api/<domain>/service.ts
`
, exported as a single object (e.g.
`
projectsService
`
,
`
teamService
`
,
`
timesheetService
`
). They own all read queries and shared write logic. Pages and actions call services.
**
is for infrastructure integrations only (
`
s3.service.ts
`
,
`
email.service.ts
`
,
`
analytics.service.ts
`
,
`
invoice-number.service.ts
`
), not domain data access.
`
actions.ts
`
, always built with
**
next-safe-action
**
.
src/lib/safe-action.ts
`
:
authedActionClient
//
schema lives in the route's common.ts
{
parsedInput
,
ctx
:
{
user
,
role
,
orgMember
}
}
)
=>
{
(
{
resource
:
[
'
create
'
]
}
)
.
success
)
{
'
You do not have permission to ...
'
)
`
user
`
,
`
session
`
,
`
orgMember
`
, and
`
role
`
. Authorize every mutation with
`
role.authorize(...)
`
and verify org ownership of inputs.
`
useHookFormAction
`
), never by calling the action as a bare function.
loading
`
prop that swaps children for a spinner. Use it:
isPending
}
>
Save
</
Button
>
{isPending ? <Spinner/> : 'Save'}
`
in children.
`
(project)/[org]/[project]
`
(project-scoped). Org/project context is resolved through cache helpers (e.g.
`
resolveOrgContext
`
in
`
(organization)/[org]/cache.ts
`
) — reuse them rather than re-querying. Per-route convention:
`
page.tsx
`
(server),
`
page.client.tsx
`
,
`
actions.ts
`
,
`
common.ts
`
(schemas),
`
types.ts
`
,
`
_components/
`
.
`
src/server/db/schema/
`
;
`
auth.ts
`
is generated by better-auth — edit the auth config, not that file.
`
src/server/auth/permissions.ts
`
.
`
content/
`
,
`
content-collections.ts
`
).
*
why
*
. Don't narrate what the code already says.
`
file defines its component and nothing else
**
— the component, its props/local subcomponents, that's it. Don't bolt unrelated helpers onto it.
`
src/lib/
`
only when it's genuinely shared and named for what it does. Don't scatter one-off utilities.