---
url: https://docs.nemo12.com/ops/mm.md
description: >-
  mm.nemo12.com (SRC-1006): webapp gõ Markdown ra sơ đồ tư duy markmap, bản nháp
  lưu trong localStorage, nói tiếng Anh.
---

# mm.nemo12.com

SRC-1006 (chủ dự án, 23.09.2026): một webapp nhỏ, gõ Markdown bên trái, nhận sơ đồ tư duy
(markmap) bên phải. Bản nháp đang gõ nằm trong `localStorage` của máy người dùng, dùng được khi
chưa đăng nhập. Editor ẩn/hiện được, và trang nhớ lựa chọn ấy.

SRC-1014 (chủ dự án, cùng ngày): đăng nhập Google rồi lưu tối đa **10 markmap** mỗi người vào D1,
chọn để mở lại và sửa.

* Đăng nhập dùng lại đường chung `POST /v1/auth/google`; cookie phiên đặt cho cả `*.nemo12.com`,
  nên ai đã đăng nhập ở learn thì vào mm là có sẵn phiên.
* Bảng `markmaps` (migration 0287), API `/v1/markmaps` (liệt kê, đọc, tạo, sửa), mọi route
  đòi phiên và lọc theo `user_id` của phiên: bản của người khác trả 404 như bản không tồn tại.
* Trần 10 giữ ở SERVER, trong chính câu `INSERT ... WHERE (SELECT COUNT(*)) < 10`, nên hai lượt
  lưu cùng lúc không vượt được. Quá trần trả 409.
* Google chỉ cho nút đăng nhập chạy trên origin đã khai trong OAuth client, nên
  `https://mm.nemo12.com` phải có trong *Authorized JavaScript origins* của client dùng chung.

SRC-1017 (chủ dự án, 24.09.2026), hai chỉ đạo:

* **Toàn bộ giao diện mm nói tiếng Anh**, kể cả nút Google và câu lỗi API của `/v1/markmaps`.
  Đây là ngoại lệ thứ ba của luật "UI tiếng Việt" trong CLAUDE.md; đừng dịch ngược.
* **Không có tính năng xoá.** Không nút, không route `DELETE`. Đủ 10 bản thì mở một bản cũ và lưu
  đè lên; test `routes.test.ts` giữ cho route xoá không quay lại.

SRC-1041 (chủ dự án, 25.09.2026): **share public** và trang **Explore**.

* Chủ markmap bấm "Share public" trên một bản đã lưu; bấm lại là thôi share. Link công khai là
  `mm.nemo12.com/m/<id>`: ai cũng xem được, không cần đăng nhập, kéo, phóng to, thu/mở nhánh
  được, và có nút "Make a copy" chép nội dung sang editor của người xem.
* `mm.nemo12.com/explore` liệt kê mọi bản đang share, mới share trước, tìm được theo tiêu đề.
* Link công khai hiện bản ĐÃ LƯU gần nhất, không phải bản đang gõ dở.
* API: `PUT /v1/markmaps/{id}/visibility` (đòi phiên, chỉ chủ), `GET /v1/public/markmaps` và
  `GET /v1/public/markmaps/{id}` (công khai, khai trong bảng PUBLIC của cổng phủ auth). Hai đường
  công khai **không trả gì về người viết**: không user_id, không tên, không email, vì người dùng
  có thể là trẻ em. Thôi share là 404 ngay, giống hệt id không tồn tại. Cột `is_public`,
  `shared_at` thêm ở migration 0295.

SRC-1042 (chủ dự án, 25.09.2026): nút **Share on Facebook** trên trang xem công khai và cạnh
"Copy link" trong editor, mở hộp chia sẻ `facebook.com/sharer`.

Facebook không chạy JavaScript khi đọc link, nên SPA tĩnh sẽ cho mọi markmap cùng một tiêu đề.
`apps/mm/worker.ts` chạy trước asset CHỈ cho `/m/*` (`run_worker_first`), lấy tiêu đề từ API công
khai rồi thay `<title>`, `og:title`, `og:description` (các nhánh cấp 2), `og:url` và canonical.
API hỏng hay bản không còn share thì trả trang gốc. Facebook giữ bản xem trước trong cache; đổi
tiêu đề rồi muốn Facebook thấy ngay thì dán link vào Sharing Debugger và bấm Scrape Again.

| Việc | Chỗ |
| --- | --- |
| Mã nguồn | `apps/mm` (Vite + React + Tailwind v4 + `motion`, token DS-001) |
| API + bảng | `workers/api/src/modules/markmaps`, `migrations/0287_markmaps.sql` |
| Vẽ sơ đồ | `markmap-lib/no-plugins` + `markmap-view` |
| Deploy | Worker `nemo12-mm` (static assets), route `mm.nemo12.com`, qua `ci.yml` |

Dùng bản `no-plugins` của `markmap-lib` là cố ý: bản đủ plugin kéo katex và highlight.js,
nặng thêm khoảng 380 kB mà trang này không dùng.
