# UHM Editor - developer guide thực dụng Tài liệu này dành cho người sửa editor hiện tại, không phải mô tả kiến trúc lý tưởng. ## 1. Entry points quan trọng - `src/app/editor/[id]/page.tsx` - orchestration chính của project editor - `src/uhm/components/Map.tsx` - container cho map và các hook map - `src/uhm/lib/editor/state/useEditorState.ts` - draft geometry + diff + undo - `src/uhm/lib/editor/state/useEditorSessionState.ts` - session/UI/project/wiki/entity state - `src/uhm/lib/editor/snapshot/editorSnapshot.ts` - normalize snapshot từ backend và build snapshot gửi ngược lại backend Nếu chưa đọc 5 file này, chưa nên sửa behavior lớn của editor. Docs nên đọc trước khi sửa editor: - `src/uhm/doc/editor_operations.md` - `src/uhm/doc/editor_data_roles.md` - `src/uhm/doc/editor_snapshot_contract.md` - `src/uhm/doc/editor_manual_test_checklist.md` - `src/uhm/doc/editor_replay_actions.md` ## 2. Cấu trúc thư mục nên ưu tiên hiểu - `src/uhm/components/editor/` - panel UI bên trái/phải - `src/uhm/components/wiki/` - wiki editor và wiki viewer/sidebar - `src/uhm/components/map/` - hooks tích hợp MapLibre - `src/uhm/lib/map/engines/` - logic interaction theo mode - `src/uhm/lib/editor/session/` - các nhóm session state - `src/uhm/lib/editor/draft/` - draft diff và undo - `src/uhm/lib/editor/snapshot/` - schema conversion / snapshot semantics ## 3. Cách editor thật sự vận hành Editor có 3 tầng dữ liệu: 1. `baselineSnapshot` - snapshot gốc của session 2. `baselineFeatureCollection` - `FeatureCollection` rehydrate từ snapshot đó - seed/reset cho `useEditorState()` 3. `mainDraft` - working copy để user sửa trên map Map không render trực tiếp `mainDraft` mọi lúc. Page tạo `mapRenderDraft` từ `mainDraft`/`replayDraft`/preview draft sau khi áp timeline/replay filter, rồi truyền xuống `Map` dưới prop `renderDraft`. `labelContextDraft` chỉ dùng để lookup label, không được dùng để quyết định geometry nào hiện trên map. Khi commit: - geometry đi từ `mainDraft` - entity/wiki/link đi từ snapshot collections - `buildEditorSnapshot()` quyết định operation nào là `reference`, `binding`, `update`, `delete` Đừng tự build payload ở component nếu chưa hiểu file `editorSnapshot.ts`. ## 4. Khi thêm mode/tool mới Checklist an toàn: 1. Thêm mode vào `sessionTypes.ts`. 2. Thêm button vào `ToolsPanel.tsx`. 3. Nếu mode cần preview source/layer mới, thêm vào `setupMapLayers()`. 4. Nối mode với engine trong `useMapInteraction.ts`. 5. Nếu tool tạo geometry mới, chọn default: - `type` - `geometry_preset` - `entity_ids` - `bound_with` 6. Kiểm tra interaction cleanup khi chuyển mode. Nếu mode chưa được cleanup đúng, map rất dễ giữ preview cũ hoặc event listener cũ. ## 5. Khi thêm geotype mới Checklist ngắn: 1. Cập nhật `geoTypeMap` nếu cần mapping backend code <-> key. 2. Cập nhật `geometryTypeOptions.ts`. 3. Tạo style file trong `styles/geotypes/`. 4. Register ở `geotypeLayers.ts`. 5. Kiểm tra point icon hoặc label pipeline nếu type mới là point/route/polygon label. Nếu chỉ sửa `geometryTypeOptions.ts` mà quên style registry, UI sẽ cho chọn type nhưng map không render đúng. ## 6. Khi sửa snapshot semantics File quan trọng nhất là `editorSnapshot.ts`. Ở đó đang có hai hướng xử lý khác nhau: - `normalizeEditorSnapshot(raw)` - đọc payload từ backend - rehydrate fields UI như `entity_ids`, `entity_name`, `bound_with`, `time_start`, `time_end` - `buildEditorSnapshot(options)` - strip các field generate-only khỏi `editor_feature_collection` - build `geometry_entity[]` và `entity_wiki[]` - tính operation phù hợp Nguyên tắc: - feature trong editor có thể mang field denormalized để UI dễ dùng - payload gửi backend thì không nên mang những field denormalized đó ## 7. Khi sửa wiki editor Wiki project editor hiện là Quill, không phải Tiptap. Các file nên đọc trước: - `WikiSidebarPanel.tsx` - `PublicWikiSidebar.tsx` Các điểm dễ làm hỏng: - sanitize link của Quill - compatibility với doc dạng HTML/plain text - slug links nội bộ - sentinel `__missing__` Nếu thay storage format, phải sửa cả editor lẫn viewer compatibility path. ## 8. Những key localStorage thật sự đang dùng - `uhm.backgroundLayerVisibility.v1` - `uhm:mapProjection` Hiện không có local draft autosave toàn editor. Đừng dựa vào doc cũ hoặc giả định rằng F5 sẽ hồi lại draft geometry/wiki/entity. ## 9. Restore commit hiện là FE-only `CommitHistoryPanel -> Restore`: - load snapshot từ commit cũ - reset editor state ở frontend - không đổi head commit trên backend Nếu muốn restore server-side thật, cần dùng endpoint backend riêng và sửa cả UI wording. ## 10. Pending submission lock là rule thật `openSectionEditor()` chủ động chặn project có `PENDING` submission. Nghĩa là: - không nên "lách" UI để cho sửa tiếp - nếu đổi behavior này, phải thống nhất với backend contract ## 11. Performance và state hygiene Một số nguyên tắc nên giữ: - dùng `renderDraftRef`/refs trong map engines để tránh rebind handler vô ích - giữ component panel càng dumb càng tốt, logic patch state đặt ở page/hooks - khi cần undo cho entity/wiki/link, đi qua `editor.setSnapshot*()` để undo stack biết - khi cần undo cho replay script, đi qua `editor.mutateActiveReplay()` hoặc replay collection helper hiện có - hạn chế thêm `JSON.stringify` compare ở chỗ nóng nếu chưa đo hiệu năng ## 12. Chỗ dễ gây hiểu nhầm khi debug ### Geometry biến mất Có thể do: - timeline filter - geometry visibility theo type - bound_with filter Không phải lúc nào cũng là bug render layer. ### Commit count lạ `Commit (N)` là `pendingSaveCount`, không phải số mutation backend. ### Selection mất Selection hiện bám theo `editor.draft`, không theo `mapRenderDraft`. Vì vậy geometry đang chọn có thể bị timeline filter ẩn khỏi map nhưng panel metadata vẫn đọc được draft gốc. ## 13. Nên test gì sau khi sửa Ít nhất nên test thủ công: 1. mở project có commit cũ 2. tạo geometry mới bằng mode liên quan 3. sửa metadata geometry 4. bind entity và geometry 5. tạo/sửa wiki 6. link entity-wiki 7. commit 8. restore từ commit cũ 9. mở project có pending submission nếu đang debug flow đó