sendou.ink/docs/dev/how-to.md
Kalle 42b44614f2
Some checks are pending
E2E Tests / e2e (push) Waiting to run
Tests and checks on push / run-checks-and-tests (push) Waiting to run
Updates translation progress / update-translation-progress-issue (push) Waiting to run
better-sqlite3 -> node:sqlite, get rid of ley (#3289)
2026-08-03 21:35:13 +03:00

3.5 KiB

How to...

Guides on how to do different things when developing sendou.ink

Fix style/lint errors (Biome)

Run the pnpm run biome:fix command. Also you might want to set up Biome as an extension to your IDE and run automatically when you save a file.

Add a new database migration

  1. Run pnpm run migrate:new "my cool feature". This creates migrations/<UTC timestamp>-my-cool-feature.ts e.g. migrations/20260803143000-my-cool-feature.ts. Migrations run in filename order, and the timestamp keeps two branches from claiming the same slot.
  2. Fill out the generated file, replacing the TODOs. Use the schema builder (trx.schema) rather than raw SQL:
import type { Kysely } from "kysely";

/** Gives users somewhere to put their pronouns */
export async function up(db: Kysely<any>): Promise<void> {
	// kysely does not wrap sqlite migrations in a transaction, so do it here
	await db.transaction().execute(async (trx) => {
		await trx.schema
			.alterTable("User")
			.addColumn("pronouns", "text")
			.execute();
	});
}

New tables need strict appended, which the builder has no method for:

import { type Kysely, sql } from "kysely";

export async function up(db: Kysely<any>): Promise<void> {
	await db.transaction().execute(async (trx) => {
		await trx.schema
			.createTable("UserPronoun")
			.addColumn("id", "integer", (col) => col.primaryKey())
			.addColumn("userId", "integer", (col) =>
				col.notNull().references("User.id").onDelete("cascade"),
			)
			.addColumn("createdAt", "integer", (col) =>
				col.notNull().defaultTo(sql`(strftime('%s', 'now'))`),
			)
			// every table in this schema is strict
			.modifyEnd(sql`strict`)
			.execute();

		await trx.schema
			.createIndex("user_pronoun_user_id")
			.on("UserPronoun")
			.column("userId")
			.execute();
	});
}

Notes:

  • No need to implement the "down" migration
  • Kysely does not wrap migrations in a transaction for SQLite, so wrap it yourself
  • Drop to raw sql`...` only for what the builder can't express: table rebuilds, pragma foreign_key_check, FTS5 virtual tables, generated columns. See the SQLite migration quirks.
  • migrations/20260803000000-initial.ts is the collapsed history of the 165 migrations that came before it. It only ever runs against an empty database, so leave it alone.
  1. Update the typings in app/db/tables.ts
  2. Run pnpm run migrate up to apply your migration (the unit test database db-test.sqlite3 is created and migrated automatically when unit tests run)

Add a new translation string

  1. Decide on where the translation should go. Either common.json which is available in every route by default or a feature specific one such as builds.json
  2. Add the translation string to the json with some descriptive key
  3. Access in code via the useTranslation hook
// common.json
{
  ...
  "my-cool.translation": "Translated"
  ...
}
// CoolComponent.tsx
export function CoolComponent() {
  const { t } = useTranslation(["common"]);

  return (
    <div>{t("common:my-cool.translation")}</div>
  )
}

When utilizing feature specific translations ensure the json is loaded. This is handled via the handle Remix function.

Sync

Use the pnpm run i18n:sync command to sync translation jsons with English (removing and adding keys for each language as needed). There is not currently a check in the pipeline that this was done but it should always be ran when a new translation string has been added or removed.