postgresus / frontend
AGENTS.md9.1k starsChanged 42 days agoApache-2.0View on GitHub
This document contains coding standards for the Databasus frontend (React 19 + TypeScript + Vite + Ant Design + TailwindCSS). For project-wide engineering philosophy, see the root AGENTS.md. Write React components with the following structure: All hooks (including every useEffect) come below every plain function definition. If a useEffect reads a handler, the handler must be defined above it — don't reorder by putting handlers below the effects. Always use ClipboardHelper (shared/lib/ClipboardHelper.ts) for clipboard operations — never call navigator.clipboard directly.
We split this AGENTS.md into the rules it gives an agent
9 rules. About 0 are common, 4 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 31
Write React components with the following structure:
- Very specific: line 86
Always use ClipboardHelper (shared/lib/ClipboardHelper.ts) for clipboard operations — never call navigator.clipboard directly.
- Very specific: line 89
Paste: Check ClipboardHelper.isClipboardApiAvailable() first.
- Very specific: line 127
Every enum shown to the user has a label table <TYPE>_LABEL_KEYS: Record<Enum, TranslationKey> next to the type it labels, exported through …
- Distinctive: line 193
Downward-only imports: No upward imports, no same-layer cross-imports.
- Distinctive: line 237
When two slices on the same layer need to share code, try in order:
- Distinctive: line 248
When applying changes, do not forget to refactor old code.
- Very specific: line 252
Run pnpm format (from frontend/ root folder)
- Very specific: line 253
Run pnpm lint to verify the change
≈ “Common” is an estimate: at least 1 in 5 of the 36,368 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Frontend guidelines (React + TypeScript)
This document contains coding standards for the Databasus frontend (React 19 + TypeScript + Vite + Ant Design + TailwindCSS).
For project-wide engineering philosophy, see the root AGENTS.md.
Table of Contents
- UI kit and icons
- React component structure
- Vertical spacing
- Clipboard operations
- Forms
- User-facing copy
- Interface languages (i18n)
- FSD (Feature-Sliced Design)
- Refactoring
UI kit and icons
- AntD 5 only for components. Don't pull in Mantine, MUI, Chakra, shadcn, or Radix directly. Use AntD primitives (
Button,Input,Modal,Form,Table,Menu,Tabs, etc.) plus Tailwind utility classes for layout and spacing. @ant-design/iconsonly for icons. Don't addlucide-react,@heroicons/react,react-icons, or FontAwesome.
React component structure
Write React components with the following structure:
interface Props {
someValue: SomeValue;
}
const someHelperFunction = () => {
...
}
export const ReactComponent = ({ someValue }: Props): JSX.Element => {
// First put states
const [someState, setSomeState] = useState<...>(...)
// Then place functions
const loadSomeData = async () => {
...
}
// Then hooks
useEffect(() => {
loadSomeData();
});
// Then calculated values
const calculatedValue = someValue.calculate();
return <div> ... </div>
}Structure order
- Props interface — Define component props
- Helper functions (outside component) — Pure utility functions
- Component declaration
- States —
useStatedeclarations - Plain functions — Event handlers, async operations, in-component formatters. Anything that does not call a React hook.
- Hooks —
useRef+ ref mutation,useCallback,useMemo,useEffect. A function wrapped inuseCallbackis a hook — it lives here, not in the functions section. - Calculated values — Derived data computed inline (e.g. an AntD
columnsarray). - Return — JSX markup
- States —
All hooks (including every useEffect) come below every plain function definition. If a useEffect reads a handler, the handler must be defined above it — don't reorder by putting handlers below the effects.
Vertical spacing
- Structure function bodies with vertical rhythm. Put a blank line between logically distinct steps (setup / main work / return, independent branches, before and after a guard). Do not leave blank lines at the start or end of a body, never stack two in a row. Do not insert blanks inside a tight expression, a single statement split across lines, or a short (≤ 5-line) function. If blank lines alone aren't enough to navigate a body, extract — don't add comments.
Clipboard operations
Always use ClipboardHelper (shared/lib/ClipboardHelper.ts) for clipboard operations — never call navigator.clipboard directly.
- Copy:
ClipboardHelper.copyToClipboard(text)— usesnavigator.clipboardwithexecCommand('copy')fallback for non-secure contexts (HTTP). - Paste: Check
ClipboardHelper.isClipboardApiAvailable()first. If available, useClipboardHelper.readFromClipboard(). If not, showClipboardPasteModalComponent(shared/ui) which lets the user paste manually via a text input modal.
Forms
Progressive disclosure
- Submit buttons (
Save,Update password, etc.) render only when the form is dirty. - Dependent fields (e.g.
Confirm password) render only after the field they depend on has a value.
User-facing copy
In the English dictionary (shared/i18n/locales/en.ts), use a plain hyphen - in any string the user will see — labels, descriptions, notifications, modal bodies, error messages. Reserve em dashes (—) and en dashes (–) for markdown docs and code comments only.
The other dictionaries follow their language's own typography instead, as set out in website/AGENTS.md - Translation quality: Russian keeps «—» where its grammar wants it, French puts a space before : ; ! ? and %, Chinese uses full-width punctuation. The same section lists the canonical interface terms per language.
Interface languages (i18n)
The interface ships in English, Russian, Spanish, Portuguese (Brazilian), Chinese (Simplified) and French, through i18next and react-i18next. Everything lives in shared/i18n.
Dictionaries
- English (
locales/en.ts) is the source of truth for the set of keys. Every other dictionary is typedtypeof en, so a missing or leftover key failspnpm build. A language cannot ship partly translated. - Each dictionary is one object literal in one file. TypeScript's check for leftover keys only works on a literal written in place; a nested object passed in through a variable or a spread escapes it.
- Keys are grouped by domain (
backups,databases,storages,status,errors,common, ...), not by file path, so moving a component does not rename its keys. dictionaries.test.tschecks that every translation uses the same{{placeholders}}andTranstags as English.- Translations are written in the target language, not rendered word for word. Follow
website/AGENTS.md- Translation quality.
Rules for code
- No user-facing string literal in
src. Thei18next/no-literal-stringlint rule enforces it, SCREAMING_SNAKE constants and default parameter values included. Technical strings stay literal: commands, code blocks, connection strings, cron expressions, engine and tool names, file paths,localStoragekeys. A technical string the rule reports takes// eslint-disable-next-line i18next/no-literal-string -- <reason>; in JSX, make it an expression on its own line ({'pg_dump'}) so the directive applies to it. A file that holds nothing but technical strings, such as a shell command builder, may disable the rule once at the top with the reason. - Components read text with
const { t } = useTranslation()fromreact-i18next. - Code outside components holds keys, not text. A module-level constant, a label table or a parser that called
t()would freeze the language that was active when it ran. Such code stores aTranslationKey, or aLocalizedText({ key, params }) when the message has parameters, and the component translates it while rendering (t(key),translateLocalizedText(text, t)). The same goes for state: keep an error or a key in state, not a translated string. - Every enum shown to the user has a label table
<TYPE>_LABEL_KEYS: Record<Enum, TranslationKey>next to the type it labels, exported through the slice'sindex.ts. Never build a key from a value (t(`status.${value}`)): adding an enum member must fail the build. - Sentences stay whole. A sentence with inline markup is one key rendered through
<Trans i18nKey="..." components={{ code: <InlineCodeComponent />, bold: <strong />, docsLink: <a href={...} /> }} />(<TransByKey>fromshared/i18nwhen the key is held in a variable:Transcannot type-check the whole key union), with named tags in the dictionary (Run <code>pg_dump</code> first). Values are interpolated into a whole sentence ('Delete {{name}}?'), never concatenated with translated fragments. When the inserted word would inflect in other languages, each value gets its own sentence key. - A user-entered value (a workspace, database or user name) in a
Transsentence is a self-closing tag whose supplied element holds the value: dictionaryDelete <workspaceName/>?, componentcomponents={{ workspaceName: <strong>{name}</strong> }}. Never pass it throughvalues:Transwould expand placeholder syntax inside it. - Inline code in prose, inside a
Transsentence or not, goes throughInlineCodeComponentfromshared/ui, not a bare<code>: a monospace font alone doesn't set code apart from the text around it. - Counts are phrased so one string fits every number:
Members: {{count}}, not{{count}} members. The dictionaries have no plural forms, because Russian's_fewwould be a stray key undertypeof en. - Translated text is never rendered as HTML. No
dangerouslySetInnerHTML. - Locale-dependent output comes from hooks, so a language switch re-renders it:
useTranslationfor text,useLocale()forformatNumber(instead oftoLocaleString()) andformatRelativeTime(instead of dayjsfromNow()). - API errors reach the user only through
translateApiError(error, t): a known error code gives its translation, otherwise the backend's message, otherwise a general message. Never showerror.messagedirectly. - Links to databasus.com pages go through
getWebsitePageUrl(pageId, locale)with a page fromWEBSITE_PAGES, which opens the page in the selected language when the website publishes it.
Clock format and date order
shared/time/getUserTimeFormat.ts and shared/time/utils.ts read navigator.language on purpose. The 12- or 24-hour clock and the day/month order are regional conventions, not properties of a language: English is written with both clocks. The browser reports the user's region, while the language selection only says which words to use. Those reads stay; a bare toLocaleString() does not, because digit grouping follows the selected language.
FSD (Feature-Sliced Design)
The project follows FSD v2.1. Layers in use: app/, pages/, widgets/, features/, entity/, shared/.
Import direction
app → pages → widgets → features → entity → shared
A module may only import from layers strictly below it. Cross-imports between slices on the same layer are forbidden.
Paths below are written from src/ for readability. Real imports are relative; the project has no path alias.
✅ Allowed
a widget imports features/backups, entity/backups, shared/ui
a feature imports entity/databases, shared/api
an entity imports shared/lib
❌ Violations
an entity imports features/databases (upward: entity sits below features)
a feature imports pages/AuthPageComponent (upward: pages sits above features)
features/users imports features/backups (same layer: cross-slice import)
shared/lib imports entity/users (upward: shared is the bottom layer)Where new code goes
- Used in only one page → keep it in that
pages/slice. - Reusable infrastructure, no business logic →
shared/(UI kit, utils, API client, route constants, auth tokens, CRUD helpers). - User interaction reused in 2+ pages →
features/. - Domain model reused in 2+ pages/features →
entity/. - App-wide providers, router, theme →
app/.
When in doubt — keep it in pages/. Extract only when a second real consumer appears.
Quick placement table
| Scenario | Single use | Reused in 2+ places |
| Profile form | pages/profile/ui/ProfileForm.tsx | features/profile-form/ |
| Database card | pages/databases/ui/DatabaseCard.tsx | entity/database/ui/DatabaseCard.tsx |
| Data fetching for backup | pages/backup/api/fetch-backup.ts | entity/backup/api/ |
| Auth token / session | shared/auth/ (always) | shared/auth/ (always) |
| Login form | pages/login/ui/LoginForm.tsx | features/auth/ |
| CRUD helpers | shared/api/ (always) | shared/api/ (always) |
| Date formatting util | — | shared/lib/format-date.ts |
| Modal content | pages/[page]/ui/SomeModal.tsx | — |
MUST rules
- Downward-only imports. No upward imports, no same-layer cross-imports.
- Public API via
index.ts. External consumers import only from a slice'sindex.ts, never its internal files.✅ features/users ❌ features/users/ui/AuthNavbarComponent - Domain-based file names. Name files by the domain they represent, not their technical role.
// ❌ model/types.ts, model/utils.ts, lib/helpers.ts // ✅ model/user.ts, model/backup.ts, api/fetch-database.ts - No business logic in
shared/. Shared holds only infrastructure. Domain calculations live inentity/or higher. - One type per file in
model// DTOs. Eachinterface,class, orenumthat represents a domain entity, DTO, request body, or response shape gets its own file inmodel/(ormodels/), named after the type. Don't co-locate sibling types likeFoo+FooStatus+FooResponsein a singleFoo.ts— split them. Re-export each from the sliceindex.tsso consumers still import from the slice's public API.
Tiny shape-only helper types tightly coupled to a parent type (a literal-union alias, a// ❌ model/RestoreVerification.ts — enums + table-stat + main interface all in one file // ✅ model/RestoreVerification.ts — interface RestoreVerification // ✅ model/RestoreVerificationTableStat.ts — interface RestoreVerificationTableStat // ✅ model/VerificationStatus.ts — enum VerificationStatus // ✅ model/VerificationTrigger.ts — enum VerificationTriggerPick<>) may stay in the parent's file. Splitting applies to anything with its own identity — anything you'd reasonably import on its own elsewhere.
Segments inside a slice
ui/— components, stylesmodel/— state, types, domain logic, validationapi/— backend calls, request functions, API-specific typeslib/— internal helpers for this slice onlyconfig/— slice-level config / feature flags
app/ and shared/ have segments only, no slices. Segments within them may import from each other.
AVOID
- Creating an entity prematurely (single consumer → keep it in the page).
- Putting CRUD inside
entity/— CRUD is infrastructure, goes toshared/api/. - Creating a
userentity just for auth tokens/DTOs — those belong inshared/auth/orshared/api/. - Extracting single-use code "for future reuse."
- God slices (
user-management/covering auth + profile + password) — split by focused responsibility. - Importing UI segments of one entity from another entity. Entity UI may be imported only from features/widgets/pages.
- Abusing the
@xcross-import pattern — it is a last resort, not a tool.
Cross-imports between same-layer slices
When two slices on the same layer need to share code, try in order:
- Merge — if they always change together, they are one slice.
- Extract shared logic down to
entity/— keep UI in the features/widgets. - Compose in a higher layer (IoC) — the parent page/widget imports both and wires them together via props/slots.
@xnotation — explicit, documented cross-import between entities only. Last resort.
Refactoring
When applying changes, do not forget to refactor old code. You can shortify, make more readable, improve code quality, etc. Common logic can be extracted to functions, constants, files, etc.
After each large change with more than ~50-100 lines of code:
- Run
pnpm format(fromfrontend/root folder) - Run
pnpm lintto verify the change
Trending
Quiet since July.thin dataabout the whole repository
Who's talking about RostislavDugin/postgresus
- @its-me-rhk.bsky.social“🗄️ Postgresus — Alat open-source untuk monitoring & backup database PostgreSQL dengan antarmuka web. ⚡ …”Bluesky · Oct 30, 2025 · 3 likes
- Dokploy/templates“Postgresus template seems outdated – project renamed to Databasus”GitHub · Jan 19, 2026 · 1 reactions
- truenas/apps“[App Request]: Postgresus / Databasus”GitHub · Dec 19, 2025 · 1 reactions
- @mariuskimmina.com“"Postgresus" is just peak project naming (and it also seems really awesome) github.com/RostislavDug...”Bluesky · Dec 6, 2025 · 1 likes
- @harungecit.dev“PostgreSQL kullanıyorsan ama yedekleme & izleme seni yoruyorsa: 💡 Postgresus tam sana göre! 🕒 Otomatik …”Bluesky · Oct 30, 2025 · 1 likes
The biggest 5 of 10 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 10
Talked about on GitHub and Bluesky (10 times).
On GitHub · 7
- Jan 19, 2026Postgresus template seems outdated – project renamed to Databasus — Dokploy/templates1 reaction
- Dec 19, 2025[App Request]: Postgresus / Databasus — truenas/apps1 reaction
- Jul 14, 2026fix: add Databasus template (Postgresus was renamed upstream) — Dokploy/templates0 reactions · 1 comment
- Dec 11, 2025Redirect to https://postgresus.com/storages/google-oauth instead of my hosted Postgresus domain — databasus/databasus0 reactions · 2 comments
- Dec 7, 2025Email Notifier Password can't be empty when creating — databasus/databasus0 reactions · 2 comments
2 more on GitHub
- Nov 17, 2025Databasus — community-scripts/DevScripts0 reactions · 6 comments
- Sep 14, 2025Update PostgreSQL badge in README — pgmq/pgmq0 reactions
On Bluesky · 3
- Oct 30, 2025🗄️ Postgresus — Alat open-source untuk monitoring & backup database PostgreSQL dengan antarmuka web. ⚡ Fitur utama: jadwal backup,… — @its-me-rhk.bsky.social3 likes and reposts
- Dec 6, 2025"Postgresus" is just peak project naming (and it also seems really awesome) github.com/RostislavDug... — @mariuskimmina.com1 likes and repost
- Oct 30, 2025PostgreSQL kullanıyorsan ama yedekleme & izleme seni yoruyorsa: 💡 Postgresus tam sana göre! 🕒 Otomatik yedekler ☁️ S3, Drive, NAS desteği… — @harungecit.dev1 likes and repost
Other versions1 project uses a version of this.
Word for word · 1
- databasus/databasus★ 9.2k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 28,693 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository18 other files it gives its agents.
- adr/AGENTS.mdAGENTS.md
- AGENTS.mdAGENTS.md
- backend/AGENTS.mdAGENTS.md
- website/AGENTS.mdAGENTS.md
- adr/CLAUDE.mdCLAUDE.md
- backend/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- frontend/CLAUDE.mdCLAUDE.md
- website/CLAUDE.mdCLAUDE.md
- git-commitsSkill
- how-to-communicateSkill
- openspec-apply-changeSkill
- openspec-archive-changeSkill
- openspec-exploreSkill
- openspec-proposeSkill
- openspec-sync-specsSkill
- openspec-update-changeSkill
- humanizerSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

