# Projects board: guide for AI agents

Board: https://projects.ramziramzi.ir/projects. No login. Every change is live and visible to people immediately.

## Prompt to give an agent

Shown at the top of the board with a Copy button (its text lives in `static/index.html`):

> You can manage my projects at https://projects.ramziramzi.ir/projects. Read https://projects.ramziramzi.ir/projects/AGENTS.md first, then get the current state from https://projects.ramziramzi.ir/projects/api/tree.md. Make all changes through the API described there. When you work on a task, update its status and progress, write what you learn into its knowledge-base section, and reference files and other sections with [[D<id>]] and [[T<id>]].

## The model

```
P  project        name, start_at, end_at, progress   kb = project overview section
S    subproject   name, start_at, end_at, progress   kb = subproject overview/detail section
T      task       title, status, start_at, end_at, progress   kb = task detail section
D  doc            kind = page (extra wiki page, body) | file (uploaded) | link (saved web address)
```

Every item has a **ref**: `P1`, `S3`, `T12`, `D7`. Refs are used everywhere: in the API, in links between sections,
and in the page address (`https://projects.ramziramzi.ir/projects/#/kb/T12` opens that section for a human).

### Knowledge base

The knowledge base is a tree that mirrors the plan: the project overview, then one section per subproject,
then one section per task, then any extra pages. Each section is markdown stored on its item (field `kb`;
for extra pages, `body`). There is no separate "linked docs" list. Connections are written **inline**:

| Write in a section | Becomes |
|---|---|
| `[[T12]]`, `[[S3]]`, `[[P1]]`, `[[D4]]` | link to that section or page, labelled with its name |
| `[[T12\|custom label]]` | same, with your label |
| `[[Exact name]]` | link by name to a section, page, file or link in the same project |
| `[[D7]]` where D7 is a file | download link to the file |
| `![[D7]]` where D7 is an image file | the image, shown inline |
| `[[D9]]` where D9 is a saved link | the external link |
| `[text](https://example.com)` or a bare URL | an external link |

References are parsed: each item reports `refs` (what its section links to) and `referenced_by` (sections linking to
it). A reference to something deleted shows as a broken link, so check `referenced_by` before deleting.

Suggested structure: project overview = goal, scope, key decisions; subproject = what it covers, approach;
task = goal, decisions, findings, results, with references to the files it used and the sections it depends on.

### Times and progress

* Times are ISO 8601, stored in UTC: `2026-11-09T14:30Z`, `2026-11-09T18:00+03:30`, `2026-11-09`, or `"now"`.
  Send `null` to clear. Times without an offset are taken as UTC.
* Every P/S/T returns `computed`: `{start_at, end_at, progress, duration_s, running, derived}`. **Read `computed`.**
  Parents roll up from children: start = earliest child start, stop = latest child stop once all children have stopped,
  progress = average of children. A start/stop you set on a parent wins. A parent's progress can't be set while it
  has children; set it on the tasks.
* Task `status` is `todo | doing | done` and stays consistent (anything you send explicitly wins):
  `doing` sets start_at to now if empty; `done` sets end_at to now and progress 100; `todo` clears both times and sets
  progress 0; sending only `progress` moves the status (100 means done, above 0 on a todo task means doing).

## Reading

| What | Request |
|---|---|
| Outline of everything (status, progress, times, whether each section is written) | `GET /projects/api/tree.md` |
| List projects | `GET /projects/api/projects` |
| A project's whole knowledge base, in tree order, as one markdown document | `GET /projects/api/projects/{id}.md` |
| One section as markdown | `GET /projects/api/kb/{ref}.md`, e.g. `/kb/T12.md` |
| One section as JSON (text, refs, referenced_by, the item) | `GET /projects/api/kb/{ref}` |
| Everything as JSON (section texts as 300-char excerpts) | `GET /projects/api/tree` (add `?full=true` for complete texts) |
| Search names and section texts | `GET /projects/api/search?q=text` |
| Download a file | `GET /projects/api/docs/{id}/download` |
| Did anything change? | `GET /projects/api/rev` (number goes up on every write) |

## Writing (JSON body, `Content-Type: application/json`; send only the fields you change)

| What | Request |
|---|---|
| Create project | `POST /projects/api/projects` `{"name":"...","kb":"overview markdown"}` returns `{"id":N,"ref":"P<N>"}` |
| Create subproject | `POST /projects/api/projects/{pid}/subprojects` `{"name":"...","kb":"..."}` |
| Create task | `POST /projects/api/subprojects/{sid}/tasks` `{"title":"...","kb":"...","status":"todo"}` |
| **Append to a section** | `POST /projects/api/kb/{ref}/append` `{"text":"## Findings\n..."}` (no need to read first) |
| Replace a section | `PUT /projects/api/kb/{ref}` `{"text":"..."}` (read it first and keep what you don't mean to remove) |
| Start / finish a task | `PATCH /projects/api/tasks/{id}` `{"status":"doing"}` / `{"status":"done"}` |
| Report progress | `PATCH /projects/api/tasks/{id}` `{"progress":60}` |
| Set times by hand | `PATCH .../{id}` `{"start_at":"2026-11-09T09:00Z","end_at":null}` |
| Rename or move | `PATCH /projects/api/{projects,subprojects,tasks}/{id}` (`name`/`title`; tasks: `subproject_id`; subprojects: `project_id`) |
| Delete | `DELETE /projects/api/{projects,subprojects,tasks,docs}/{id}` (a project deletes everything in it) |
| Extra wiki page | `POST /projects/api/projects/{pid}/docs` `{"kind":"page","title":"...","body":"markdown"}` |
| Save a web link | `POST /projects/api/projects/{pid}/docs` `{"kind":"link","title":"...","url":"example.com"}` |
| Upload a file | `curl -F file=@report.pdf -F title="Report" .../projects/{pid}/files` returns `{"ref":"D9","reference":"[[D9]]"}` |
| Upload without multipart | `curl --data-binary @report.pdf ".../projects/{pid}/files/raw?filename=report.pdf&title=Report"` |
| Replace a file | `curl -F file=@v2.pdf .../docs/{id}/file` |

After uploading a file, reference it from the section that uses it, or nobody will find it from the plan.
Unknown fields are rejected (422) so typos don't silently do nothing. Errors are JSON `{"detail":"readable message"}`
with status 400, 404, 413 (over 50 MB), 422 (bad or unknown field) or 507 (server disk nearly full).
A failed write changes nothing.

```bash
B=https://projects.ramziramzi.ir/projects/api
curl -s $B/tree.md
P=$(curl -s -X POST $B/projects -H 'Content-Type: application/json' -d '{"name":"Website","kb":"Goal: ship a new portfolio by December."}' | jq .id)
S=$(curl -s -X POST $B/projects/$P/subprojects -H 'Content-Type: application/json' -d '{"name":"Design"}' | jq .id)
T=$(curl -s -X POST $B/subprojects/$S/tasks -H 'Content-Type: application/json' -d '{"title":"Pick typefaces"}' | jq .id)
curl -s -X PATCH $B/tasks/$T -H 'Content-Type: application/json' -d '{"status":"doing"}'
F=$(curl -s -F file=@moodboard.png $B/projects/$P/files | jq -r .ref)
curl -s -X POST $B/kb/T$T/append -H 'Content-Type: application/json' \
  -d "{\"text\":\"## Findings\nShortlist in ![[${F}]]. Must match the overview in [[P${P}]].\"}"
curl -s -X PATCH $B/tasks/$T -H 'Content-Type: application/json' -d '{"status":"done"}'
```

Full machine-readable reference: `/projects/api/openapi.json` (browsable at `/projects/api/swagger`).

## Etiquette

* Read before you write; refs come from `tree.md`. Don't create duplicates of things that exist.
* Prefer `append` to `PUT` for sections other people or agents also write in.
* Don't delete projects, subprojects, sections, pages or files unless the user asked. Deleting files is permanent.
* The database is copied to `backups/` automatically (every service start, and at most daily on writes; newest 14 kept).

## Changing the page itself (server access: `ssh arb`)

```
/root/projects_app/
  app.py              API and page server (Python 3, FastAPI, SQLite). After editing: test, then restart.
  static/index.html   page shell                       \  read from disk on every request and linked
  static/style.css    all styling; colours in :root     >  with ?v=<file time>, so saving the file
  static/app.js       all behaviour, no framework        >  and reloading the browser is enough.
  static/md.js        safe markdown + [[ref]] renderer  /
  static/fonts/       self-hosted fonts (fetch_fonts.py regenerates them)
  selftest.py         API tests on a throwaway database
  pm.db, uploads/, backups/   DATA: never edit or delete by hand
  .venv/              Python environment
```

1. Copy before editing: `cp static/app.js /tmp/app.js.bak`.
2. For `app.py`: `cd /root/projects_app && .venv/bin/python selftest.py` must print `ALL PASSED`,
   then `systemctl restart projects_app`, then check `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8100/projects/`
   returns 200. Logs: `journalctl -u projects_app -n 50`.
3. For static files: reload the page and check the browser console. Restore your copy if anything breaks.

Rules in the code that keep it safe:
* `app.js`: `load()` fetches `/tree`, `render()` rebuilds the page from it. Add UI in the `*Html()` functions;
  don't edit the DOM by hand (it is replaced on the next refresh). Controls carry `data-action`, handled in one
  delegated click handler at the bottom. Navigation to sections is the URL hash `#/kb/<ref>` (`goKb()`).
* All user text goes through `esc()` or `MD.render()` (XSS guard; `md.js` escapes everything before formatting).
  Keep `dir="auto"` on user text so Persian/Arabic reads right to left.
* `app.py`: every write uses `with db(write=True) as con:` so open pages refresh; read-only code uses `db()`.
  Schema changes go in `SCHEMA` plus a step in `migrate()` for existing databases.
* Stable selectors: `[data-ref] [data-project-id] [data-subproject-id] [data-task-id][data-status] [data-doc-id] [data-action]`.
* Don't touch `/root/arb_bot`, `arb_bot.service`, or the nginx `location /` block (a separate trading bot).
  The `/projects` nginx location proxies to port 8100 with `client_max_body_size 50m`; run `nginx -t` before reloading.
