Source code for the SaaSy Land website, customer workspace, and admin console. The application combines a multilingual marketing site, documentation and blog, authentication, newsletter subscriptions, and Polar checkout and licence management in one TanStack Start application deployed to Cloudflare Workers.
This repository contains the website application. The SaaSy Land CLI and private generator templates are maintained separately and are not included here. See licensing for the distinction between the website source and the commercial product.
The landing page in September 2026. View the full page.
- Features and implementation status
- Technology
- Getting started
- Configuration and integrations
- Project structure and conventions
- Commands and verification
- Database and deployment
- Localization and content
- Performance
- Landing page videos
- Contributing
- Licensing
The application includes both connected workflows and UI demonstrations. A rendered admin screen does not necessarily have a persistent backend workflow.
| Area | Current implementation |
|---|---|
| Public website | Landing page, pricing presentation, localized legal pages, responsive layouts, theme support, and reduced-motion presentations. Public pages are prerendered during builds. |
| Documentation and blog | Localized MDX content, navigation, and documentation search through Fumadocs. Published content is maintained in Git. |
| Authentication | Email/password registration with required email verification, GitHub and Google sign-in, password recovery, TOTP with backup codes, and session management. |
| Customer workspace | Protected /app area and /app/license for the customer's licence key, machine activations, and activation deactivation. |
| Billing | Authenticated Polar checkout for the Core, Complete, and Agency tiers, with webhook handling for purchases, refunds, and licence-key benefits. Requires configured provider products and credentials. |
| Newsletter | Subscription, confirmation, unsubscribe, and subscription-preference server functions with localized email delivery. |
| Admin users and security | Database-backed user listing, password reset, ban/unban, and deletion. The settings security tab connects password changes, TOTP, and session revocation. |
| Products and categories | D1 schemas, permission-checked CRUD use cases, and database-backed lists. Product creation/publishing controls are not fully connected. |
| Demo and unfinished screens | Analytics, payment administration, blog administration, invitations, and role-management views include demo data. General settings, course creation, and the landing-page editor include unconnected controls. /premium is a placeholder. |
The admin blog editor does not publish the MDX blog, and the course and page-editor screens do not yet constitute complete authoring systems. User creation/edit/profile controls also remain incomplete. The built-in application roles are admin and customer; new accounts receive customer.
| Responsibility | Implementation |
|---|---|
| Application and routing | React 19, TypeScript, TanStack Start and Router |
| Toolchain | Bun, Vite+, formatting, linting, and type checking configured in vite.config.ts |
| Hosting and storage | Cloudflare Workers, D1 (SQLite), KV, and static assets |
| Database access | Drizzle ORM and checked-in SQLite migrations |
| Authentication and authorization | Better Auth with module-level permissions and server-function middleware |
| Data and forms | TanStack Query, Form, and Table; Zod validation |
| UI | Tailwind CSS 4, shadcn-styled components with React Aria and Base UI, Motion, and locally served fonts |
| Localization and content | use-intl, ICU messages, Fumadocs, and MDX |
| External services | Resend for email; Polar for checkout, billing, and licence keys |
| Verification | Vite+/Vitest, Testing Library, jsdom, and Playwright |
package.json and bun.lock define the application toolchain. The separate Remotion project uses its own npm lockfile.
- Bun 1.4.0, matching
packageManagerand CI. - Node.js 24.11 or newer within the 24.x release line, matching the locked Vite+ engine requirement and the Node 24 CI configuration. Node is also needed by the SQLite test harness.
- A Cloudflare account and provider credentials for development against real services. The isolated preview and automated tests below do not require those accounts.
From the repository root:
bun install --frozen-lockfileThe install lifecycle configures Vite+ and generates the class-name tables and Fumadocs content. If you install with --ignore-scripts, run bun run typegen afterward to generate Worker declarations, class-name tables, and content types.
Use the committed test environment to explore the application with local D1/KV storage, test accounts, and intercepted provider requests:
bun run db:migrate:test
bun run db:seed:test
bun run build:test
bun run start:testOpen http://127.0.0.1:3000. Sign in with either account below; both use the password WorkerTestPassword123! from e2e/data/accounts.ts.
| Role | Workspace | |
|---|---|---|
| Admin | admin@saasyland.example.test |
/admin |
| Customer | customer@saasyland.example.test |
/app |
This serves a built test application; rebuild it to see source changes. It uses dummy values from .env.test, the test environment in wrangler.jsonc, and isolated state under .wrangler/test. Provider interception is intended for local verification, not for validating real OAuth, email delivery, or payments. Keep this test configuration local.
The development server supports hot reload at http://localhost:3000. Its DB and CACHE bindings connect to remote preview resources. Consequently, development database writes and db:migrate:development affect that preview database.
-
Sign in to Cloudflare and copy the environment template:
bunx wrangler login cp .env.example .env.development openssl rand -base64 48
-
Set the generated value as
AUTH_SECRETin.env.development, then fill in the integration settings. UsePOLAR_SERVER=sandboxfor development. -
If using your own Cloudflare account, provision D1 and KV and replace the development and preview binding IDs before continuing. The checked-in IDs belong to the existing deployment.
-
Apply the migrations and start the server:
bun run db:migrate:development bun run dev
Email/password signup requires a functioning Resend configuration because accounts must verify their email before signing in. GitHub/Google sign-in and checkout require their respective provider configuration. Use the isolated preview when you only need to inspect the UI without those services.
There is no development admin seed. To bootstrap an installation, create and verify your account, then use D1 or Drizzle Studio to set that account's user.role to admin in the intended database. The test seed only populates the isolated test database.
Use .env.development, .env.preview, and .env.production for their corresponding environments. These files are ignored by Git; .env.example lists the configuration keys. .env.test contains committed dummy values for isolated tests.
Server integrations read secrets through env from cloudflare:workers. Do not give private credentials a VITE_ prefix, which makes values available to browser code. Deployment scripts upload the matching environment file with Wrangler's --secrets-file option.
| Variables | Purpose |
|---|---|
AUTH_SECRET |
Long random secret used by Better Auth. Generate a separate value for each deployment environment. |
AUTH_GITHUB_CLIENT_ID, AUTH_GITHUB_CLIENT_SECRET |
GitHub OAuth application credentials. |
AUTH_GOOGLE_CLIENT_ID, AUTH_GOOGLE_CLIENT_SECRET |
Google OAuth application credentials. |
RESEND_API_KEY, RESEND_EMAIL_FROM |
Email API credentials and the sender accepted by your Resend account. |
POLAR_SERVER |
Polar environment: sandbox for development or production for live billing. |
POLAR_ACCESS_TOKEN, POLAR_ORGANIZATION_ID |
Polar API access and the organization used for discounts and licence operations. |
POLAR_WEBHOOK_SECRET |
Secret used to verify Polar webhook deliveries. |
POLAR_PRODUCT_ID_CORE, POLAR_PRODUCT_ID_COMPLETE, POLAR_PRODUCT_ID_AGENCY |
Product IDs mapped to the three checkout tiers. Use IDs from the selected Polar environment. |
CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_DATABASE_ID, CLOUDFLARE_ACCESS_TOKEN |
Credentials for Drizzle Studio and introspection over the D1 HTTP API. Application queries use the DB binding instead. |
The development, preview, and production environments currently declare all of these keys in secrets.required, including the Cloudflare credentials used by Drizzle tooling. Supply the required secrets for the selected environment; see wrangler.jsonc for the authoritative list.
The public URL is configured in source: src/presentation/branding/index.ts defines APP_DOMAIN and derives APP_URL as https://${APP_DOMAIN}, together with the contact addresses and GitHub URL. VITE_APP_URL is not read by the application.
When adapting the application to your own domain, update these locations together:
| Location | What to configure |
|---|---|
src/presentation/branding/index.ts |
Application name, canonical domain, contact addresses, and GitHub identity. |
src/modules/_core/constants/api.ts |
appHostsForMode, the allowed authentication hosts per mode, derived from APP_DOMAIN. |
src/modules/newsletter-subscriber/newsletter-subscriber.server.ts |
The allowed Workers hostname suffix for newsletter request origins. Custom-domain checks derive from APP_URL. |
wrangler.jsonc |
Worker names, custom-domain routes, D1 databases, and KV namespaces for each environment. |
src/data/marketing.ts |
Displayed tiers and prices (TIERS, TIER_PRICES); keep them aligned with the configured Polar products. |
src/presentation/styles/ |
Theme, typography, fonts, and shared style definitions. |
Changing an environment file alone does not change the canonical URL. Rebuild after changing branding or deployment configuration.
- Configure GitHub and Google callback URLs at
https://<your-host>/api/auth/callback/githubandhttps://<your-host>/api/auth/callback/google. Use the appropriate development or deployed origin. - Configure Resend credentials and the sender address before testing verification, password recovery, or newsletter delivery. Templates live in
src/presentation/emails/. - Create the Polar products matching the three tier variables. Attach a licence-key benefit to each paid product and set its activation limit to match the advertised package.
- Configure Polar webhooks at
https://<your-host>/api/auth/polar/webhookswith the matching webhook secret. The handlers process paid/refunded orders and licence-benefit creation/revocation. - Regional checkout discounts require eligible percentage discounts without a discount code configured in Polar. The discount lookup uses the request country and the configured organization; synchronize provider discounts with the pricing rules.
The website associates purchases and issued keys with authenticated customers and exposes activation management. CLI installation and activation enforcement belong to the separately maintained CLI.
content/
blog/ Localized MDX posts
docs/ Localized MDX documentation and navigation metadata
messages/{locale}/ JSON catalogues split into dotted namespaces
e2e/ Playwright specs and local test-account fixtures
scripts/ Content checks, build checks, seeding, and tooling
remotion/ Video authoring source and isolated npm toolchain
src/
routes/ TanStack file routes, loaders, guards, and HTTP handlers
routes.ts Shared route constants
router.tsx Per-request QueryClient and SSR integration
server.ts Cloudflare Worker entry and locale middleware
integrations/{vendor}/ Vendor configuration and adapters ({vendor}.{concern}.ts)
modules/{table}/ One folder per database table: schema, zod, constants, types, use-cases/*.ts
modules/_core/ Shared error codes, catalogues, and helpers
presentation/
assets/motion/ Published landing-page videos and posters
branding/ Application identity and canonical URL
components/shadcn/ UI primitives (React Aria)
components/custom/ Shared components, plus app/, auth/, blog/, admin/{page}/, landing-page/
emails/ Email templates (markup and message namespace only)
styles/ Theme, typography, and local font declarations
providers/ Application-wide React providers
data/ Data shared by several files: navigation, marketing, admin demo rows
hooks/ Shared React hooks
lib/ Shared utilities (cn, cookies, SEO, GitHub stars, rate limits)
platform/testing/ Test Worker, mocks, RPC helpers, and render helpers
Routes coordinate loading and rendering. Business operations live in table modules and export native TanStack createServerFn functions with query or mutation options. Components consume those options; database and provider clients stay behind server boundaries.
A route file only composes its page: it defines the <Name>Page or <Name>Layout component, const NAMESPACE (page routes) or const NAMESPACES (layout routes), a loader that runs metadata, message and data loading in one Promise.all, and head: pageHead(ROUTES.X). Widgets, dialogs, forms, and table columns live in their own files under presentation/components/custom, one per file, and read their own translations and data instead of receiving labels as props. Forms follow the shadcn TanStack Form pattern with the module's Zod schema; tables use the generic DataTable with columns defined in an {entity}-columns.tsx file. Email templates contain markup only; the use case that sends an email loads its messages and renders the template.
Validate inputs at the server boundary and authorize protected functions themselves, in addition to page guards. Query and mutation keys are readonly tuples owned by the feature's *.constants.ts; include inputs that change the result in the query key. Reuse exported options for cache access, and invalidate only the data a mutation changes. Application documentation lives in content/docs/, including the architecture guides.
Authentication entry points are in src/integrations/better-auth/:
| File | Responsibility |
|---|---|
auth.server.ts |
Better Auth, providers, plugins, and allowed hosts. |
auth.access.ts |
Roles, permissions, and permission checks. |
auth.routes.ts |
Sign-in/admin guards and role-based redirects. |
auth.middleware.ts |
Server-function authorization, error handling, and rate limiting. |
auth.session.ts |
Request-scoped session lookup and UI query options. |
Authorization shares a fresh session lookup within one HTTP request; a new request checks session storage again. Route navigation rechecks sessions. Sign-in and sign-out clear private caches; route guards also clear cached data when the authenticated user changes. Rate limiting uses atomic D1 operations and rejects requests when its storage is unavailable. Expired counters are removed in bounded batches during rate-limit checks; there is no scheduled cleanup Worker.
Email-verification links enter /auth/verify-email and redirect to Better Auth's native endpoint. Newsletter confirmation and unsubscribe operations run in route loaders with preloading disabled. Successful processing replaces the token URL with a result notice; failed requests retain the token for retry.
Use bun run test to invoke the configured Vite+ test runner. Running bun test directly does not use this test setup.
| Command | Purpose |
|---|---|
bun run dev |
Generate content/class-name tables and start development with remote preview bindings. |
bun run typegen |
Generate Worker declarations, class-name tables, and Fumadocs content/types. |
bun run check |
Generate class-name tables; check localization/content, formatting, lint, and types. |
bun run check:fix |
Run checks and apply supported formatting/lint fixes. |
bun run check:i18n |
Validate message parity, scoped translation keys, ICU formats, and localized content. |
bun run test |
Run node, integration, and component test projects. |
bun run test:unit / bun run test:integration / bun run test:component |
Run one test project. |
bun run test:watch / bun run test:changed |
Watch tests or run tests affected by changes. |
bun run test:coverage |
Run tests with the configured coverage gate and reports. |
bun run test:e2e:install |
Install Chromium/WebKit and their Playwright dependencies. |
bun run test:e2e |
Run the browser suite in Chromium and WebKit. |
bun run test:e2e:smoke |
Run the Chromium smoke suite. |
bun run test:all |
Run the Vite+ tests followed by Chromium smoke tests. |
bun run build / bun run build:preview |
Build the preview Worker, prerender public pages, and verify/optimize output. |
bun run build:production |
Build and verify the production application. |
bun run build:test / bun run start:test |
Build or serve the isolated test application. |
bun run start |
Serve the most recently built application at 127.0.0.1:3000. |
Run the commands from the root package scripts; the Remotion commands run inside remotion/.
Unit and integration tests run in Node; component tests run in jsdom. Tests are colocated in src/**/__test__/. The test RPC helper exercises TanStack validation and middleware, and the D1 adapter applies the checked-in SQLite migrations. External provider operations are mocked. See the testing guide for fixtures and ownership conventions.
Coverage includes TypeScript application code under src, including routes, custom components, providers, and integrations. Exclusions cover tests/fixtures, test infrastructure, generated router/declaration files, and shadcn primitives. The configured gate requires 100% statements, branches, functions, and lines for the included code. This is a test requirement, not a claim that every product workflow is complete. Read measured results in coverage/index.html and coverage/coverage-summary.json; Playwright does not contribute to this report.
bun run test:e2e:install
bun run test:e2eLocally, Playwright migrates and seeds the test database, builds the test application, and starts its preview server unless an existing server already answers at the configured URL. Stop other servers on port 3000 before running the suite so it uses the intended test application. PLAYWRIGHT_BASE_URL overrides the default http://127.0.0.1:3000.
build:test selects env.test in the root wrangler.jsonc using CLOUDFLARE_ENV=test. It uses the test Worker entry, local bindings, .env.test, and .wrangler/test; no separate test Wrangler file or E2E=true flag is needed. In CI, migrations, seeding, and building run explicitly before Playwright starts the server.
- Main CI runs checks, coverage, and Chromium E2E on pull requests and pushes to
main. Successful eligible runs then deploy to Cloudflare. - Full E2E adds WebKit on pushes to
mainand runs Chromium and WebKit weekly and on manual dispatch. - Video CI separately checks and bundles affected Remotion source.
The deployment workflow records actual deployment results in GitHub's Preview and Production environments. Configure its credentials as described below before enabling deployments.
For a new Cloudflare account, create a preview database and KV namespace:
bunx wrangler login
bunx wrangler d1 create saasyland_com_preview
bunx wrangler kv namespace create CACHE --env previewReplace database_id, database_name, and the KV namespace id under both development and preview in wrangler.jsonc. Keep the binding names DB and CACHE, and preserve migrations_dir. These two environments share resources by default. Update Worker names and custom-domain routes for your account.
Create separate production resources with saasyland_com_production and bunx wrangler kv namespace create CACHE --env production, then update the production bindings. The deployment guard rejects placeholder IDs; it does not verify that the existing IDs belong to your account.
Module schemas are collected by drizzle.schemas.ts. Generate migrations with:
bun run db:generateReview and commit SQL files under src/integrations/drizzle-orm/migrations/ with their schema changes. Apply migrations before deploying code that depends on them.
| Command | Database affected |
|---|---|
bun run db:migrate:test |
Local test database in .wrangler/test. |
bun run db:migrate:local |
Local development database; the regular development server still uses remote bindings. |
bun run db:migrate:development |
Remote database configured for development, shared with preview by default. |
bun run db:migrate:preview |
Remote preview database. |
bun run db:migrate:production |
Remote production database. |
db:studio:development, db:studio:preview, and db:studio:production open Drizzle Studio using the corresponding environment file. The matching db:introspect:* scripts introspect D1 through its HTTP API. Set the three CLOUDFLARE_* credentials to the target account/database for these tools.
Existing data from a PostgreSQL installation needs a separate export and import into the SQLite/D1 schema; these migrations do not transfer it.
The CI workflow deploys only after formatting, lint, types, localization, coverage, and Chromium E2E pass:
| Event | Cloudflare target | GitHub environment | URL |
|---|---|---|---|
| Pull request opened, reopened, or updated from a branch in this repository | saasyland-preview (preview) |
Preview |
https://preview.saasyland.com |
Push to main, including merging a pull request |
saasyland (production) |
Production |
https://saasyland.com |
Preview is a shared environment: each successful PR deployment replaces the previous preview, using the same preview D1 database and KV namespace. It does not create a separate Worker or database for each PR. Fork and Dependabot PRs run validation without privileged deployments; move a reviewed change to a trusted repository branch to preview it.
In repository Settings → Environments, configure the existing Preview and Production environments with these secrets:
| Secret | Purpose |
|---|---|
CLOUDFLARE_API_TOKEN |
Wrangler authentication with permission to deploy the Worker, manage its configured bindings/routes, and apply D1 migrations. Scope the token to the intended account and zones. |
CLOUDFLARE_ACCOUNT_ID |
Cloudflare account containing the selected Worker, D1 database, and KV namespace. |
These are deployment credentials. CLOUDFLARE_API_TOKEN is separate from the CLOUDFLARE_ACCESS_TOKEN used by Drizzle Studio. Repository secrets with the same names can be used as a fallback; environment-specific secrets allow different credentials for preview and production. Follow Cloudflare's GitHub Actions authentication guide when creating the token.
The existing Workers must already have all runtime secrets required by wrangler.jsonc. CI builds with committed dummy credentials from .env.test because prerendering initializes provider clients, while still selecting the real preview/production configuration and Worker entry. It removes the generated local .dev.vars file before deployment. No real provider credentials are needed in GitHub, and no secrets file is uploaded: Wrangler inherits the selected Worker's existing secrets. A new Worker still needs the initial provisioning described in the manual deployment section.
Deployment jobs build and verify artifacts, apply the target D1 migrations, then deploy the tested revision. Workflow runs and deployments queue rather than cancel one another, with a separate deployment queue per environment. Schema changes must remain compatible with the currently running application because migrations run before the new Worker is deployed. WebKit's additional main-branch suite runs independently of the production deployment gate.
GitHub creates deployment records from the job's environment declaration and shows the job's real result. A successful Cloudflare deployment supersedes the old Vercel result for that environment; historical Vercel failures remain in deployment history. See GitHub's deployment tracking documentation.
When switching to this workflow, disconnect any remaining Vercel Git integration for this repository and disable duplicate automatic deploy triggers in Cloudflare Workers Builds. Use one deployment owner so a push does not deploy twice or bypass the CI gate. Production environment branch rules can restrict deployment to main; preview rules must allow PR merge refs. Required environment reviewers, if configured, pause deployment until approval.
After provisioning resources and configuring domains and secrets:
cp .env.example .env.preview
# Fill in preview secrets and sandbox provider settings before continuing.
bun run db:migrate:preview
bun run deploy:previewFor production:
cp .env.example .env.production
# Fill in production secrets and live provider settings before continuing.
bun run db:migrate:production
bun run deploy:productionEach deployment script verifies binding IDs, builds the selected environment, checks the generated artifacts, optimizes prerendered HTML, and deploys through Wrangler using the corresponding secrets file. The Cloudflare Vite plugin generates the deployment configuration consumed by Wrangler. Builds alone do not apply remote migrations or deploy resources.
Public pages are prerendered to dist/client; protected, authentication, newsletter-action, and API routes are excluded from prerendering. The build verifier checks the Worker deployment configuration, localized home/legal pages and SEO links, fonts, client JavaScript, and selected route exclusions. The application still requires a Worker for server-rendered routes, authentication, APIs, and locale handling.
The supported locales are en-US, de-DE, es-ES, fr-FR, it-IT, ja-JP, pl-PL, pt-BR, and uk-UA, defined in i18n.config.ts.
English uses unprefixed paths. Other languages use the full locale, such as /de-DE/docs or /pl-PL/blog; short aliases redirect to canonical paths. Switching language opens the current path in the chosen language; the query string and fragment are not carried over. The locale always comes from the URL path, so server functions that need it receive it as input, because their /_serverFn URLs carry no locale prefix.
- Store UI messages in
messages/{locale}/, using the same keys, ICU arguments, and rich-text tags across locales and each language's plural categories. - Store documentation and blog content as
slug.{locale}.mdxundercontent/docsandcontent/blog; localized navigation usesmeta.{locale}.json. - Translate frontmatter, visible component attributes, FAQ entries, and body text. Preserve slugs, MDX component names, code examples, and link destinations.
- Every page route owns one namespace (
const NAMESPACE = "pages.x", withmetadata.titleandmetadata.descriptionfor its head); layout routes preload the namespaces their subtree shares. Root namespaces (common,components.*,errors*) load for every page, and the translations provider wraps the whole document so error screens are translated too. - Use one
useTranslationsper component and name the translatort. A lateruseTranslationscall re-scopes every followingt()for i18n-ally, andbun run check:i18nfails on keys that do not resolve in their scope. - Link with
<Link to={ROUTES.X}>; the router adds the locale prefix. UselocalizePathnameonly for raw URLs such as authentication callbacks and email links. - Email messages live in
emails.*namespaces and are loaded by the sending use case, never by browser code. - Run
bun run check:i18nafter changes. Builds additionally verify localized home and legal-page output.
Fumadocs generates .source/; edit the source MDX and catalogues instead of generated output. The public legal routes are /privacy, /terms, /refunds, and /licence. Review their content when adapting the application to a different business.
PageSpeed Insights results for https://saasyland.com/, measured on 25 September 2026 with Lighthouse 13.5.0.
Mobile (emulated Moto G Power, Slow 4G throttling)
Desktop
| Metric | Mobile | Desktop |
|---|---|---|
| First Contentful Paint | 1.2 s | 0.4 s |
| Largest Contentful Paint | 2.3 s | 0.7 s |
| Total Blocking Time | 0 ms | 10 ms |
| Cumulative Layout Shift | 0 | 0 |
| Speed Index | 2.4 s | 0.6 s |
These are lab results. PageSpeed Insights runs on shared hardware, so scores vary between runs; compare several runs before judging a change. Run the analysis for current results.
The following decisions produce these results. Review them before changing the related code:
- Public pages are prerendered.
optimize-prerendered-html.tsremoves dependencymodulepreloadhints and keeps each page's entry hint at low priority, so the stylesheet and fonts load first. - Providers whose value changes after hydration, such as the theme and Motion providers, wrap only their consumers and never a route
<Outlet />. A change above a route's Suspense boundary forces that route to hydrate synchronously in one long task, or to render on the client and shift the layout. robots.txtandllms.txtare static files that bypass the Worker throughrun_worker_firstinwrangler.jsonc. Lighthouse fails the SEO and Agentic Browsing audits for these files when the requests time out.src/server.tsserves prerendered pages from static assets and imports the TanStack Start handler only for other requests, so page requests do not load the full server bundle.
remotion/ contains editable video compositions with a separate npm toolchain. Install it only when editing videos:
cd remotion
npm ci
npm run check
npm run devRemotion Studio runs on port 3001. Finished videos and full-size/responsive posters live in src/presentation/assets/motion/; Vite emits them with content hashes under /assets/. Keep the source and published assets in Git. Editor dependencies, bundles, caches, and temporary renders are ignored.
Normal application builds serve the checked-in assets without installing Remotion or rendering videos. Follow the rendering instructions when updating a composition, and regenerate its video and posters together.
Follow the architecture guides and use the existing theme and typography definitions. Keep tests beside their owner, and update localized content and documentation under content/docs/ when changing user-facing behavior.
Before opening a pull request, run the checks relevant to your change. The application verification sequence is:
bun run check
bun run test:coverage
bun run test:e2e:install
bun run test:e2e:smokeThe smoke command builds the local test application when starting its own server. Run the full browser suite for changes affecting multiple routes, authentication, or browser behavior. Use the isolated test build for provider-independent checks, and validate real provider integrations separately when changing their configuration.
Optional agent guidance can be restored with bun run skills:sync. It rebuilds .agents/skills from skills-lock.json, groups skills by vendor, and links them into .claude/skills. The command replaces those local skill directories; keep custom guidance elsewhere.
For documentation corrections, verify commands and links against the implementation. For feature changes, describe the resulting behavior, relevant validation, and any remaining demo or incomplete workflow in the pull request.
The website source code and associated technical documentation, including account, checkout, and licence-management code, are covered by the MIT licence and scope statements in LICENSE.md. A commercial purchase is not required to reuse the MIT-covered website code.
SaaSy Land's original logos and brand artwork are excluded from that grant, and no rights to the SaaSy Land name or trademarks are granted. Generic interface components remain covered; third-party assets retain their own licences. Reuse must not imply that a fork is the official service or has its endorsement. Previously granted MIT and other third-party permissions remain in effect.
The separately maintained CLI, private generator templates, and separately supplied paid learning/design material are proprietary. Their use is governed by the commercial product agreement, presented on /licence, rather than this repository's MIT grant. A purchase and valid licence key are required for the CLI; generated applications do not require a runtime licence key. The website's licence UI does not itself implement the private CLI's installation or activation enforcement.


