docs: add AGENTS.md for repo, backend and frontend
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# Chitai frontend
|
||||
|
||||
SvelteKit web app for the eBook library. See the repo-root `AGENTS.md` for the overall picture and
|
||||
dev-environment setup.
|
||||
|
||||
**Stack:** SvelteKit 2 with `adapter-node` · Svelte 5 (runes) · Tailwind v4 · Zod v4 ·
|
||||
`epubjs` · `mode-watcher` (dark mode) · `svelte-sonner` (toasts) · pnpm.
|
||||
|
||||
Two experimental flags are on in `svelte.config.js` and the codebase depends on both:
|
||||
`kit.experimental.remoteFunctions` and `compilerOptions.experimental.async` (`await` in components).
|
||||
|
||||
Tailwind v4 has **no config file** — the theme, oklch colour tokens and `@custom-variant dark` all
|
||||
live in `src/app.css`.
|
||||
|
||||
## Talking to the backend
|
||||
|
||||
There are two mechanisms; pick deliberately.
|
||||
|
||||
**1. Remote functions — the default.** `src/lib/api/*.remote.ts` export `query` / `command` / `form`
|
||||
functions from `$app/server`. Each takes a Zod schema from `$lib/schema` as its validator and runs
|
||||
on the server, reaching the API through `locals.api` (the `ApiClient` in `src/lib/server/api.ts`):
|
||||
|
||||
```ts
|
||||
export const getBook = query(stringCoerce, async (id): Promise<Book> => {
|
||||
const { locals } = getRequestEvent();
|
||||
const response = await locals.api.get(`/books/${id}`);
|
||||
if (!response.ok) error(response.status === 404 ? 404 : 500, '…');
|
||||
return await response.json();
|
||||
});
|
||||
```
|
||||
|
||||
Conventions: build query strings with `createQueryParams` from `$lib/utils`; on a failed response
|
||||
throw SvelteKit's `error(status, message)`; multipart uploads go through `postMultipart` /
|
||||
`putMultipart`. Re-export new modules from `src/lib/api/index.ts`.
|
||||
|
||||
**2. The catch-all proxy** at `src/routes/api/[...path]/+server.ts` forwards GET/POST/PATCH/DELETE
|
||||
to the backend with the auth header attached. Use it only where the **browser itself** must fetch
|
||||
the backend — e.g. streaming a book file into the EPUB/PDF reader. It is not the general-purpose
|
||||
path.
|
||||
|
||||
## Auth
|
||||
|
||||
`src/hooks.server.ts` is a `sequence` of two handles: the first reads the `authToken` cookie,
|
||||
constructs an `ApiClient`, validates it with `GET /access/me` and fills `locals.user` /
|
||||
`locals.authToken` / `locals.api` (clearing the cookie if invalid); the second redirects any route
|
||||
outside `/login` to the login page when there is no user.
|
||||
|
||||
The cookie is set in `src/lib/api/auth.remote.ts` (`login`) — httpOnly, secure, sameSite strict, one
|
||||
week — and deleted by `logout`. The backend JWT never reaches client-side JS.
|
||||
|
||||
## Schemas
|
||||
|
||||
- `src/lib/schema/*.ts` — hand-written Zod schemas, used as remote-function input validators and as
|
||||
the source of the exported TS types. Mirror `backend/src/chitai/schemas/` when the API changes.
|
||||
- `src/lib/schema/common.ts` — shared building blocks: `stringCoerce` / `arrayCoerce` coercion
|
||||
helpers, `PaginatedResponse<T>`, and the pagination / search / order query schemas that most list
|
||||
endpoints compose from.
|
||||
- `src/lib/schema/openapi/schema.d.ts` — **generated** from the backend's OpenAPI document with
|
||||
`openapi-typescript`. Never hand-edit; regenerate after backend API changes.
|
||||
|
||||
## State
|
||||
|
||||
Client state lives in classes in `src/lib/state/*.svelte.ts` using `$state` / `$derived`, shared via
|
||||
Svelte context with a module-level `Symbol` key and a `setXState` / `getXState` pair:
|
||||
|
||||
```ts
|
||||
const LIBRARY_KEY = Symbol('LIBRARY');
|
||||
export function setLibraryState(libraries: Library[]) { return setContext(LIBRARY_KEY, new LibraryState(libraries)); }
|
||||
export function getLibraryState() { return getContext<ReturnType<typeof setLibraryState>>(LIBRARY_KEY); }
|
||||
```
|
||||
|
||||
Follow that pattern rather than introducing stores. `library.svelte.ts` is the reference — including
|
||||
its optimistic-delete-with-rollback and toast handling. `bookCollection` / `bookSelection` /
|
||||
`bookOperations` split list data, selection and mutations across three cooperating classes.
|
||||
|
||||
## Components
|
||||
|
||||
- `src/lib/components/ui/` — vendored shadcn-svelte (`components.json`) plus jsrepo blocks from
|
||||
`@ieedan/shadcn-svelte-extras` (`jsrepo.json`). Treat as generated: add components with the CLIs
|
||||
rather than hand-writing them, and prefer wrapping over editing.
|
||||
- App components live in `forms/`, `layout/`, `view/` (browser, grid/list/table, filters, sort) and
|
||||
`reader/` (epub reader + chapter sidebar).
|
||||
- `cn()` from `$lib/utils` merges Tailwind classes; the `WithElementRef` / `WithoutChild` helpers
|
||||
there are the shadcn prop-typing conventions.
|
||||
|
||||
## Routing
|
||||
|
||||
Route groups carry the layout structure:
|
||||
|
||||
- `(root)` — the authenticated shell (sidebar, header); `+layout.server.ts` loads the libraries.
|
||||
- `(root)/(library)` — library-scoped pages: `library/[libraryId]/view`, `book/[bookId]`, edit, and
|
||||
the readers at `book/[bookId]/read/{epub,pdf}/[fileId]`.
|
||||
- `+layout@.svelte` breakouts reset to the root layout for the login page and the full-screen reader.
|
||||
|
||||
## Conventions
|
||||
|
||||
Prettier (`.prettierrc`): tabs, single quotes, no trailing commas, 100 columns, with the Svelte and
|
||||
Tailwind plugins. Run `pnpm check` (svelte-check) and `pnpm lint` before considering work done.
|
||||
|
||||
## Known rough edges
|
||||
|
||||
Observed in the current tree — don't mistake these for intentional patterns to copy:
|
||||
|
||||
- `src/lib/schema/openapi/schema.d.ts` is **stale** — it predates the `BookProgress` rework and has
|
||||
no `BookProgressRead`, so `book.progress` types as `{}`. This is the source of most of the ~104
|
||||
errors `pnpm check` reports on a clean tree; regenerating it should clear them. Get a baseline
|
||||
before assuming an error is yours.
|
||||
- `src/routes/api/[...path]/+server.ts` — all four handlers are annotated `RequestHandler` while the
|
||||
import of that type is commented out at line 4.
|
||||
- `src/app.d.ts` — `App.Locals["user"]` is typed from `lucide-svelte`'s `User` *icon* component
|
||||
rather than the `User` interface in `$lib/server/auth`.
|
||||
- Uncommitted work in progress (as of 2026-08-10): library icons, spanning
|
||||
`components/ui/icon-picker/`, the newly vendored `components/ui/popover/`,
|
||||
`forms/library-create-form.svelte`, `layout/library-switcher.svelte` and `schema/library.ts`.
|
||||
Prefer not to refactor those files mid-flight.
|
||||
Reference in New Issue
Block a user