Coding standards
This page lists the rules the repository enforces with tools or tests, plus the rules its agent
skills and AGENTS.md document. When a rule has a check, the check wins over this page.
Formatting
Section titled “Formatting”oxfmt formats css, js, json, jsonc, jsx, md, ts, tsx, yaml
and yml files. Configuration is in .oxfmtrc.json: 2-space indent, 100-column print width,
double quotes, trailing commas, semicolons. Generated validators and protocol schema files are
ignored.
npm run format # writenpm run format:check # check, as CI doesoxlint runs with .oxlintrc.json. The correctness, suspicious and perf categories are
errors, plus explicit rules. The ones you are most likely to hit:
| Rule | Effect |
|---|---|
typescript/no-explicit-any |
No any |
typescript/consistent-type-definitions |
interface over type for object shapes |
typescript/no-unnecessary-type-assertion |
No redundant as casts |
no-nested-ternary, no-unneeded-ternary |
No nested or pointless ternaries |
complexity (20), max-depth (4), max-nested-callbacks (3) |
Keep functions shallow |
no-shadow |
No shadowed variables |
react/jsx-max-depth (6) |
Limit JSX nesting |
react/no-array-index-key |
Stable keys, not array indexes |
react-hooks/exhaustive-deps |
Complete hook dependency arrays |
react-perf/jsx-no-new-{object,function,array}-as-prop |
No fresh literals as props (arrays allowed in apps/ui/src) |
import/no-unassigned-import |
Side-effect imports only from an allowlist |
In apps/ui/src, no-restricted-imports bans:
TextInputfromreact-native. UseEditingTextInputfrom@/components/ui/text-input.useQuery,useInfiniteQueryanduseQueriesfrom@tanstack/react-query. UseuseReplicaQueryoruseFetchQueryfrom@/data/query.useUnistylesfromreact-native-unistyles.
The config lists grandfathered files for the last two bans. Files may leave those lists; do not add files to them.
npm run lintnpm run lint:fixTypeScript
Section titled “TypeScript”- Workspace tsconfigs enable
strict, directly or throughtsconfig.base.json. - Most workspaces typecheck with
tsgo --noEmit(@typescript/native-preview); builds usetsc. - Run
npm run typecheckbefore merging. See Development setup for build-order failures. npm run knipreports unused files, dependencies and exports. CI does not run it.
File size
Section titled “File size”No tool limits file length. The maintainers’ guideline is one responsibility per file: aim for under 200 implementation lines, accept up to 500 for unified complex logic, and split or justify anything longer. Split by responsibility, not by pulling out single functions.
Shell scripts
Section titled “Shell scripts”Tracked Bash scripts start with #!/usr/bin/env bash. Worktree lifecycle commands in frogg.json
must also run under PowerShell on Windows, so put portable logic in a Node script
(node ./scripts/<area>/<name>.mjs), as scripts/dev/seed-worktree-dev-state.mjs does.
Licensing
Section titled “Licensing”frogg is Apache-2.0 and a fork of Paseo.
- Keep
LICENSEandNOTICEintact, including the Paseo attribution inNOTICE. - Preserve any existing license or copyright header when you edit or move a file.
- Keep upstream names only in attribution and licensing, never in configuration, environment variables or runtime paths.
No hard-coded product names
Section titled “No hard-coded product names”Product identity comes from the selected brand, so a fork can rebuild without code changes.
scripts/ci/branding-contribution.mjs enforces this in branding.yml. It compares your branch
with origin/main (override with BRANDING_BASE_REF) and fails when:
- a new string literal in
apps/orpackages/source matchesfrogg,Froggorfrogg.appand is not inscripts/ci/branding-literals.json. Tests,generated/paths andpackages/branding/are skipped. Literals that already existed in the file are allowed. - any
.generated/,packages/branding/src/generated/or.branding-input/file is tracked. brands/frogg/changes without thebranding:officialpull request label.
Instead of a literal:
import { brand } from "@frogg/branding";
const title = `${brand.name} settings`;In UI copy, use the interpolation variables that apps/ui/src/i18n/i18next.ts injects:
{{brandName}}, {{brandFullName}} and {{cliName}}.
Add an entry to branding-literals.json only for an exact compatibility or attribution string,
with a reason. Run the check locally with node scripts/ci/branding-contribution.mjs.
UI copy and i18n
Section titled “UI copy and i18n”The frogg-i18n skill (skills/frogg-i18n/SKILL.md) is the full procedure. The rules:
- Client copy lives in
apps/ui/src/i18n/resources/:en.tsplusar,es,fr,ja,ko,pt-BR,ruandzh-CN. Keys are nested by product surface. - Every locale is typed as
TranslationResources, derived fromen.ts. Add a key to all nine locales in the same change, translated. Do not paste English. - Translate client-owned copy only. Agent output, daemon errors, paths, provider, model and command names, and user text stay as values interpolated into a translated wrapper.
- Components and hooks use
useTranslation(). Non-React modules calli18n.t(...). - Keys that differ by forge use i18next context: a base key plus an
_mrsibling. Indeclinable tokens such asPR/MRare interpolated instead. plugin-settings.tshas its own per-locale blocks; update all nine.
apps/ui/src/i18n/resources.test.ts enforces key parity, matching {{placeholder}} names,
under 25% of strings identical to English per locale, and a blocklist of hard-coded English
phrases. It also pins some existing strings, so rewording one means updating its assertion.
npx vitest run apps/ui/src/i18n/resources.test.ts --bail=1npm run typecheck --workspace=@frogg/appProtocol and RPC changes
Section titled “Protocol and RPC changes”The frogg-rpc skill (skills/frogg-rpc/SKILL.md) lists the ordered file changes for a new
session RPC. The rules:
- Names are dotted with the direction last and a verb operation:
checkout.forge.set_auto_merge.requestand.response. No new flat snake_case names. - Requests are flat with
requestIdat the top level. Responses put everything, includingrequestIdand a nullableerror, underpayload. - The response type is the request type with
.requestreplaced by.response. - Stay compatible with older clients: new fields are
.optional(). Never make a field required, remove it or narrow its type. - Wire schemas are pure. No
.transform(),.catch()or.preprocess(). Usez.discriminatedUnionwhen branches share a literal tag. - Add both an inbound and an outbound row in
packages/server/src/server/authorization/operation-permissions.ts. The outbound record is exhaustive;rpc_erroris the onlynullrow. - Gate app features on a
server_info.featuresflag, checked in one place. Advertise a flag only when the running daemon can perform the action. - Tag every compatibility shim with
// COMPAT(<name>): added in vX.Y.Z, remove after <date>.
Verify with:
npm run build --workspace=@frogg/protocolnpm run typecheck:servernpx vitest run packages/server/src/server/authorization/index.test.ts --bail=1A TS2353 error on your new key in operation-permissions.ts usually means a stale protocol
build. Run npm run build:client before you change the entry.
frogg namespace
Section titled “frogg namespace”frogg uses its own names on the wire, in the environment, in storage and in deep links. Do not reintroduce upstream names.
- Environment variables use the brand’s
envPrefix:FROGG_HOME,FROGG_LISTEN, and so on. Read them throughbrandEnvfrom@frogg/branding/identityin code that must work for other brands. - The deep-link scheme is the brand
scheme(frogg://for the official brand). - The state directory is the brand
homeDir(~/.frogg), and project config isfrogg.jsonin every brand. - Wire, environment and storage names changed in the 0.6 namespace migration. A breaking change to any of them needs coordinated daemon and client upgrades.
See Environment variables for the full list.