# paper-doc A tool that supports the paper-writing process: it manages the materials, sections, figures, and references that go into a paper. The name reflects the document-centric core of the workflow — a paper is composed from structured documents rather than written in one pass. The first feature to land is the **template configuration**: a reusable library of section fields plus named templates built from them, so that a paper is written by filling in a known structure instead of deciding one. ## Tech Stack **Backend** - FastAPI (Python) — REST API - SQLAlchemy + Alembic — ORM layer and schema migrations - TiDB v8.5.0 — MySQL-compatible distributed SQL database, deployed in k3s - Dependencies managed with `venv` + `requirements.txt` **Frontend** - Vue 3 + Vite — single-page application - `axios` — HTTP client - `vue-router` — client-side routing - `pinia` + `pinia-plugin-persistedstate` — state management with persistence - Element Plus — UI component library - Dependencies managed with `pnpm` ## Architecture A decoupled SPA + REST API layout: - The Vue SPA runs in the browser and talks to the backend exclusively over HTTP/JSON via `axios`. It holds no direct database access. - The FastAPI backend owns all persistence. It connects to TiDB through SQLAlchemy sessions and owns the schema through Alembic migrations. - TiDB runs inside the existing k3s cluster. The backend reaches it either through the in-cluster Service (`tidb-tidb.tidb:4000`) or, during local development, through the NodePort `192.168.1.88:32738`. Development runs both parts locally: Vite serves the SPA with a dev proxy that forwards `/api` to the FastAPI process, so the browser sees a single origin and no CORS configuration is required. ``` Browser ──HTTP/JSON──▶ FastAPI ──SQLAlchemy──▶ TiDB (k3s) │ ▲ └── Vite dev server ───┘ (proxy /api) ``` ## Domain Model Three tables, and one rule that everything else follows from. ``` section_field the reusable heading library id, name, level, font_size, font_color paper_template a named outline id, name, abstract template_field the join, and the only home of display order id, template_id → paper_template, field_id → section_field, sort ``` ### The field library is flat, not a tree `section_field` has no `parent_id`. Hierarchy is expressed only by `level` (1 for `1.`, 2 for `1.1`), which is a *rendering hint*: it drives indentation and numbering semantics in the UI and nothing else. The reason is reuse. With a parent pointer a level-2 heading would belong to exactly one level-1 heading, so "Background" could not sit under both `1. Introduction` and `2. Related Work`. Flat fields are attached to templates, not to each other, so one field can appear in any number of templates, in a different position in each. The numbering a reader sees is part of `name` and is written by the user (`"1. Introduction"`, `"0 Abstract"`). Nothing derives or rewrites it. ### Display order lives in the join table `template_field.sort` is a plain ascending integer and is the **only** thing that decides what order a template's fields appear in. The order the user clicked fields in is never stored, so selecting `字段1, 字段2.1, 字段2, 字段1.1` and setting sorts `1, 4, 3, 2` renders as `字段1, 字段1.1, 字段2, 字段2.1`. Two consequences are deliberate: - **There is no unique constraint on `(template_id, field_id)`.** Placing the same field twice in one template is a legitimate layout — the same level-2 heading under two different level-1 headings. The UI warns about a repeat; it does not forbid one. - **Ties are legal.** Equal `sort` values are broken by insertion order, so the ordering is always total and stable. The UI warns about ties too, and offers a button that renumbers the selection `1..N`. ### Templates hold references, not copies `template_field` points at a library field; it does not snapshot its name or typography. Renaming or restyling a field therefore updates every template that places it, which is what makes "fix the template and the section names follow" work. ### TiDB does not enforce foreign keys TiDB parses `FOREIGN KEY` for compatibility and then ignores it. The constraints are declared to document the relationships, and the integrity they would provide is enforced in the application layer instead: - deleting a field still placed in a template is refused with `409`, naming the field and how many templates use it; - creating a template that references a missing field is refused with `400`; - `PaperTemplate.items` uses `cascade="all, delete-orphan"`, so deleting a template removes its rows from the join table. ## API All routes are mounted under `/api`. Interactive docs at `/docs`. | Method | Path | Notes | |---|---|---| | `GET` | `/health` | liveness plus a TiDB probe | | `GET` | `/section-fields` | `keyword`, `level`, `page`, `page_size` | | `POST` | `/section-fields` | create | | `GET` `PATCH` `DELETE` | `/section-fields/{id}` | read / partial update / delete | | `POST` | `/section-fields/batch-delete` | body `{ "ids": [...] }` | | `GET` | `/templates` | `keyword` matches name **or** abstract | | `POST` | `/templates` | name + abstract + ordered `fields` | | `GET` `PATCH` `DELETE` | `/templates/{id}` | `PATCH` with `fields` replaces the selection | | `POST` | `/templates/batch-delete` | body `{ "ids": [...] }` | Conventions worth knowing: - `font_color` is stored and returned as canonical `#RRGGBB`. The API also accepts `rgb(r, g, b)`, `#rgb` and bare `aabbcc`, and normalises them on write. - `font_size` is a JSON number, not a string — a client should not have to parse it before using it in a CSS rule. - List endpoints return `{ items, total, page, page_size, pages }`. - A template read returns `fields` already ordered by `sort`; clients never sort. ## Frontend ### Shell ``` ┌──────────────────────────────────────────────────────────┐ │ [book mark] paper-doc 论文 模板 [mark] │ header ├──────────────────────────────────────┬───────────────────┤ │ │ 模板配置 │ second-level │ │ 模板列表 │ menu, right │ │ 字段管理 │ └──────────────────────────────────────┴───────────────────┘ ``` The mark appears at both ends of the header. The second-level menu sits on the **right** and is driven entirely by `route.meta.section`, so a route declares which menu it belongs to and deep links render correctly on first paint. Routes without a `section` (the welcome page) show no menu. ### Routes | Path | View | Section | |---|---|---| | `/` | welcome | — | | `/papers` | 论文 (content TBD) | `papers` | | `/templates` | redirects to `/templates/list` | — | | `/templates/list` | template table + CRUD | `templates` | | `/templates/fields` | field library + CRUD | `templates` | ## Directory Structure ``` paper-doc/ ├── backend/ # FastAPI application │ ├── app/ │ │ ├── api/routes/ # route handlers (health, section_fields, templates) │ │ ├── core/ # settings and configuration │ │ ├── crud/ # data-access helpers │ │ ├── db/ # engine, session, declarative base │ │ ├── models/ # SQLAlchemy models │ │ └── schemas/ # Pydantic request/response models │ ├── alembic/ # migration environment and revisions │ ├── scripts/seed.py # idempotent seed for the field library + templates │ ├── alembic.ini │ ├── requirements.txt │ └── .env.example ├── frontend/ # Vue 3 SPA │ ├── src/ │ │ ├── api/ # axios instance and endpoint modules │ │ ├── components/ # shell, field and template components │ │ ├── router/ # vue-router configuration │ │ ├── stores/ # pinia stores (persisted) │ │ ├── styles/ # global reset and the shell viewport contract │ │ ├── utils/ # formatting helpers │ │ └── views/ # route-level components │ ├── package.json │ └── vite.config.ts └── docs/ # project documentation └── OVERVIEW.md ``` ## Getting Started **Prerequisites** - Python 3.10+ with `venv` - Node.js 18+ with `pnpm` - Network access to the TiDB instance (local development uses the NodePort) **Backend** ```bash cd backend python3 -m venv .venv && source .venv/bin/activate pip install -r requirements.txt cp .env.example .env # then fill in the database credentials uvicorn app.main:app --reload --port 8000 ``` API docs are then served at . **Frontend** ```bash cd frontend pnpm install pnpm dev ``` The SPA is served at and proxies `/api` to the backend on port 8000. **Database** The `paper_doc` database is created once, out of band, against TiDB. Schema changes are applied through Alembic: ```bash cd backend alembic upgrade head ``` **Seed data** `scripts/seed.py` fills the field library with a standard academic outline (20 fields, from `0 Abstract` to `7 References`) and creates three starter templates. It matches fields and templates by name, so running it twice adds nothing: ```bash cd backend .venv/bin/python scripts/seed.py # add anything missing .venv/bin/python scripts/seed.py --reset # empty the tables first ```