Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/syhily/yufan.me/llms.txt

Use this file to discover all available pages before exploring further.

yufan.me has strict architecture conventions enforced by the layer model and documented in AGENTS.md files throughout the codebase. These rules exist to keep the import graph acyclic, bundles analyzable, and server-only code out of the browser. This page summarises every rule you must follow before opening a pull request.

Import layer rules

Five top-level layers under src/ form a one-way import graph. Violating this graph is caught at review time by the import/no-cycle lint rule and at build time by Vite’s module boundary checks.
LayerMay importMust NOT import
routes/*Any layer
server/*shared/*, other server/*client/*, ui/*
client/*, ui/*shared/*, ui/*, client/*server/*, any .server.* file
shared/*shared/* onlyEverything else
Route components must accept plain props. Loaders and actions live in route modules, not in server/* domain files.

File naming rules

The src/server/domains/ tree uses a locked vocabulary. Every domain folder may contain only these filenames:
FilenamePurpose
schema.tsDrizzle table definitions and column types
repo.tsDatabase queries (reads and writes)
service.tsBusiness logic that coordinates repo + cache calls
projection.tsRead-model projections (query-side shaping)
cache.tsRedis cache helpers scoped to this domain
Additional rules:
  • No barrel index.ts files anywhere in the project. The oxlint rule oxc/no-barrel-file is set to error. Import from the specific file directly.
  • Do not use the .server.ts suffix inside src/server/. The suffix is redundant there and misleading. It is only meaningful in src/routes/ and src/shared/ where a file must be excluded from the browser bundle.
  • Path alias: @/* maps to ./src/*. Always use the alias for cross-directory imports; never use relative ../ chains that cross a layer boundary.

What NOT to add

The following patterns were removed from the codebase deliberately. Do not reintroduce them:
  • Forbidden directories: src/actions, src/middleware, src/layouts, src/services, src/hooks, src/db, src/assets/scripts, src/content
  • Forbidden files/names: src/blog.config.ts, any export named DEFAULT_SETTINGS, BlogConstants, or a per-section “reset to defaults” action
  • Monolithic config context: no BlogConfigContext or <BlogConfigProvider>. Manage settings state with per-section hooks instead
  • Parallel lib directory: no src/lib/ alongside @/ui/lib. Shared utilities belong in src/shared/ or src/ui/lib/ as appropriate
  • Preserved routes: do NOT remove or rename public URLs, feed URLs, image endpoints, WordPress compatibility routes, or pagination routes unless explicitly asked to do so

Git commit format

Commits must follow semantic commit conventions and be written in English:
<type>: <imperative subject, lowercase, no trailing period>
Allowed types: feat, fix, docs, refactor, test, chore Good examples:
feat: add audit log archival to S3
fix: block tag deletion when posts reference it
refactor: extract slug pipeline into infra/slug.ts
docs: add layering diagram to AGENTS.md
Avoid:
Fixed the bug.          # past tense, trailing period
Added new feature       # past tense
Update stuff            # vague, no type prefix

TypeScript and tooling conventions

  • React 19 TSX/TS only. No .js or .jsx files.
  • React Router 7 framework mode with SSRappDirectory: 'src' in react-router.config.ts. Do not reach outside the framework abstractions.
  • Formatting is handled by Oxfmt (via vp check): 2-space indent, single quotes, no semicolons, trailing commas, LF line endings, print width 120.
  • Linting is handled by Oxlint with the configuration in oxlint.config.ts. Run vp lint to check. Notable enforced rules: oxc/no-barrel-file (error), import/no-cycle (warn), typescript/no-floating-promises (error).
  • Imports are sorted automatically by Oxfmt. The order is: type imports → built-ins and external → internal type → internal value → parent/sibling/index.

Adding an oRPC endpoint

Follow these three steps when adding a new API procedure:
1

Define the shared contract

Add or extend the Zod schema in src/shared/contracts/<domain>.ts. Include a compile-time parity assertion against the matching type in src/shared/types/ to ensure the DTO stays in sync with the database model.If the procedure is small and self-contained, you may define the schema inline next to the procedure definition instead.
2

Add a procedure to the controller

Add the procedure to the matching controller file in src/server/http/controllers/. Pick the correct base procedure for the required permission level:
Base procedureWho can call it
publicProcAny visitor, unauthenticated
authedProcAny signed-in user
authorProcUsers with the Author role
adminProcUsers with the Admin role
3

Register the controller in the router

If the controller is not already registered, add one line to src/server/http/api-router.ts:
// public and author-level procedures
export const apiRouter = router({
  myDomain: myDomainController,
  // ...
})

// admin-only procedures go under apiRouter.admin

Audit log rule

Every admin operation that mutates state (create, update, delete, publish, unpublish) must emit an audit event:
await recordAuditEventFromContext(ctx, {
  action: 'post.publish',
  targetId: post.id,
})
Read-only queries (list, get, search) must NOT emit audit events. This rule is not lint-enforced — it is a code review requirement.
Each subdirectory has its own AGENTS.md file with scope-specific conventions. Before writing code in an unfamiliar area of the codebase, open the AGENTS.md for that directory — for example, src/server/AGENTS.md before touching a domain service, or src/ui/AGENTS.md before adding a component.

Build docs developers (and LLMs) love