Maximize your time off. Forever PTO helps you strategically combine your PTO days with public holidays to get the most out of every one of them.
forever-pto.com · Documentation · Getting Started · Stack · Contributing
Given your country, region, year, and number of PTO days, Forever PTO suggests the optimal way to place your PTO days so you get the longest possible stretches of time off, automatically accounting for weekends and public holidays.
Strategies:
- Grouped: consolidate days into a few long vacations of about two weeks
- Optimized: maximize the total number of days off
- Balanced: a long weekend every few weeks, so you never work too long without rest
- Main vacation: one long trip of up to two weeks in the months you pick, then the best bridges for the rest
Beyond scheduling:
- Edit, delete, or add custom holidays
- See efficiency stats (efficiency, gain, long weekends, max work streak)
- PTO accrual calculator, PTO vs salary calculator, workday counter
- Charts and yearly summary
- Premium features for advanced analysis
The full wiki lives at docs.forever-pto.com: architecture, runtime flows (planner recalculation, holidays, premium, country detection…), the design system with live component demos, and the CI/CD guide, which starts at workflows and covers every job, the environments, the secrets and variables, the rulesets and the releases.
This repo is a workspace holding the app in apps/web/ (forever-pto-web) and the docs site in apps/docs/ (forever-pto-docs, Astro Starlight), which reuses the app's real components and tokens and deploys independently. Each versions itself: a commit is attributed to whichever package its paths fall under, so keep a pull request to one package.
pnpm --filter forever-pto-docs dev # docs dev server| Layer | Tech |
|---|---|
| Framework | Next.js (App Router) + React |
| Language | TypeScript |
| Styling | Tailwind CSS + shadcn/ui |
| State | Zustand |
| i18n | next-intl: en, es, ca, it, de, fr |
| Database | Turso (serverless SQLite) |
| Payments | Stripe |
| Resend | |
| Premium session | jose: a signed, HTTP-only JWT cookie; there are no accounts |
| Deployment | Cloudflare Workers via OpenNext (@opennextjs/cloudflare) |
| Monitoring | BetterStack |
| Testing | Vitest + Playwright |
| Linting | Biome |
Requirements: Node.js at the version .nvmrc declares (mirrored in engines.node) and pnpm, at the version packageManager declares; both are exact pins, so match them exactly. No version is repeated here: the manifests are the only copy Renovate keeps current
# Install dependencies
pnpm install
# Copy env file and fill in values
cp apps/web/.env.example apps/web/.env.development
# Start dev server
pnpm devpnpm dev # Dev server (Turbopack)
pnpm build # next build, the Next.js production build
pnpm cf:build # The Cloudflare Workers build through OpenNext, the one CI deploys
pnpm preview # Local Cloudflare Workers preview
pnpm test:ut # Unit tests
pnpm test:docs # Docs ⟷ code consistency (also runs inside test:ut)
pnpm test:e2e # E2E tests (Playwright)
pnpm lint:all:fix # Fix all lint issues
pnpm format:all # Format all filesThere is no deploy script. The app deploys from CI alone: a push to main builds, deploys, smoke-tests and
releases it, and a manual run of the CI workflow
redeploys production. A local opennextjs-cloudflare deploy without --env would put a laptop build on the
production Worker without its route or its environment variables; AGENTS.md says why.
Copy apps/web/.env.example to apps/web/.env.development for pnpm dev.
pnpm preview runs the real Workers runtime, which reads the server-side values from apps/web/.dev.vars
instead: same keys, gitignored.
| Variable | Deployed as | What it is |
|---|---|---|
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
GitHub environment variable, inlined at build | Stripe's publishable key; a production build refuses one that does not start with pk_ |
NEXT_PUBLIC_STORAGE_KEY |
GitHub environment variable, inlined at build | The key the stores obfuscate localStorage with; a production build refuses an empty one |
STRIPE_SECRET_KEY |
Worker secret | Stripe's secret key |
STRIPE_WEBHOOK_SECRET |
Worker secret | Verifies the Stripe webhook's signature |
RESEND_API_KEY |
Worker secret | Sends the contact email |
JWT_SECRET |
Worker secret | Signs the Premium session cookie |
TURSO_AUTH_TOKEN |
Worker secret | Turso's auth token |
TURSO_DATABASE_URL |
A wrangler.toml var per environment, not a secret |
The Turso database for that environment |
Every secret and variable CI reads, with where it lives and what breaks without it, is the secrets matrix.
| Document | What it answers |
|---|---|
AGENTS.md |
How the repository is put together, and what a change has to carry. Start here |
| The wiki's architecture overview | How the app is layered, measured from the imports, and where each request goes; there is no ARCHITECTURE.md |
| The CI/CD guide | Every workflow, deploy, check and release, the secrets matrix and the rulesets |
CODING_STANDARDS.md |
How code here is written: the rules a review holds a diff to |
apps/web/README.md |
The planner: what it does, how to run it, how it releases |
apps/docs/README.md |
The wiki: how to run it, and how to write a page |
GLOSSARY.md |
The domain glossary: one canonical name per concept |
docs/adr/ |
Why it is like this. One hard-to-reverse decision per file |
The architecture is domain-driven in its strategic half and deliberately not in its tactical half. The glossary rules the names, the bounded contexts and the layer boundaries are constraints, and the dependency graph is measured rather than drawn; aggregates, repositories, entities and an event bus are all absent on purpose. Which practices are taken, which are taken in part and which are rejected, with the reason for each and worked examples from this tree, is ADR 0014. Read it before proposing that anything here be "finished".
Selected folders under apps/web/src/ carry their own AGENTS.md with the detail for that folder: the layer roots plus their sub-folders, all listed in apps/web/AGENTS.md.
tests/docs-consistency.test.ts runs with the unit suite and fails the build
when the docs and the code disagree.
See CONTRIBUTING.md for setup, checks and conventions, and the Code of Conduct. Security issues follow the Security Policy, never a public issue.
Conventional commits required. Run pnpm prepare to set up git hooks.
AGPL-3.0-only © Ferran Buireu
Running a modified copy as a network service obliges you to offer its source to the people using it.