Files
History-client/EDITOR_LOCAL_STORAGE.md
2026-05-07 13:38:52 +07:00

12 KiB

Editor (/editor) - Local Store & Snapshot Conversion

Tài liệu này mô tả chi tiết các nơi lưu trữ state (store) ở phía FrontEndUser trong /editor/[id], ý nghĩa từng biến state, state nào là “single source of truth”, state nào chỉ là cache/UI, và cách chuyển đổi qua lại giữa:

  1. Local session state (React state trong phiên làm việc)
  2. Commit snapshot (commits.snapshot_json)
  3. Reload trang (mất state local, load lại từ commit snapshot)

Mục tiêu: dễ debug, nhất quán dữ liệu, tránh sai semantics "reference"/"binding".


0) 5 Dataset Quan Trọng Nhất (GEO/ENT/WIKI/ENT_WIKI/GEO_ENT)

Trong /editor, 5 nhóm dữ liệu quan trọng nhất tương ứng trực tiếp với snapshot:

  1. GEO: snapshot_json.geometries[] + snapshot_json.editor_feature_collection
  2. ENT: snapshot_json.entities[]
  3. WIKI: snapshot_json.wikis[]
  4. ENT_WIKI (entity ↔ wiki): snapshot_json.entity_wiki[]
  5. GEO_ENT (geometry ↔ entity): snapshot_json.geometry_entity[]

Điểm quan trọng về “store”:

  • ENT/WIKI/ENT_WIKI có store snapshot riêng trong React session:

    • snapshotEntities -> entities[]
    • snapshotWikis -> wikis[]
    • snapshotEntityWikiLinks -> entity_wiki[]
  • GEO/GEO_ENT không có store snapshot riêng theo kiểu snapshotGeometries / snapshotGeometryEntity.

    • Trong session, GEO sống ở editor.draft (GeoJSON FeatureCollection).
    • Khi commit, FE build ra:
      • geometries[] từ editor.draft + editor.changes + baselineSnapshot.geometries
      • geometry_entity[] từ editor.draft.features[].properties.entity_ids

Vì vậy, nếu bạn “tìm store của geo trong React state” thì bạn sẽ thấy nó nằm ở useEditorState() chứ không nằm trong useEditorSessionState().


1) Nguyên tắc chung

1.1 Single source of truth theo lớp

  • Geometry (map/editor): useEditorState(initialData) là state trung tâm cho draft/changes/undo.
  • Snapshot stores (phần sẽ đi vào commit snapshot):
    • snapshotEntities -> snapshot_json.entities
    • snapshotWikis -> snapshot_json.wikis
    • snapshotEntityWikiLinks -> snapshot_json.entity_wiki
  • Catalog/cache để tìm kiếm & hiển thị:
    • entityCatalog là danh sách entity “global” trong RAM (fetch + search merge). Không phải snapshot.

1.2 “reference” vs “binding”

  • "reference" (entities/wikis/geometries.operation) nghĩa là không sửa record trong commit đó.
  • "binding" (chỉ áp dụng cho entity_wiki.operation) nghĩa là link entity ↔ wiki đang tồn tại trong snapshot.
  • "delete" nghĩa là xóa record (entities/wikis/geometries) hoặc unlink (entity_wiki).

Khi mở 1 phiên editor mới từ commit, mọi operation local đều bị “reset về baseline”:

  • entities[].operationwikis[].operation trong session -> "reference"
  • entity_wiki[].operation trong session -> "binding" (nếu link còn active)

2) Local state: danh sách đầy đủ và ý nghĩa

Các state này được tạo từ useEditorSessionState()useEditorState() trong:

  • FrontEndUser/src/app/editor/[id]/page.tsx
  • FrontEndUser/src/uhm/lib/useEditorSessionState.ts
  • FrontEndUser/src/uhm/lib/useEditorState.ts

2.1 Geometry editor state (core)

Nguồn: const editor = useEditorState(initialData)

  • initialData: FeatureCollection

    • baseline của session hiện tại để render Map ban đầu.
    • Được set khi:
      • mở project (load snapshot head),
      • restore FE-only từ 1 commit,
      • hoặc import/replace dữ liệu session.
  • editor.draft: FeatureCollection

    • Single source of truth cho geometry đang hiển thị + chỉnh sửa.
    • Map render trực tiếp từ draft (hoặc bản “visibleDraft” đã filter theo timeline/binding).
    • Đây chính là store runtime của GEO trong session.
  • editor.changes: Map<id, Change>

    • Diff giữa draft và baseline map nội bộ (initialMapRef).
    • Dùng để tính pendingSaveCount và để build snapshot geometries/update/delete.
  • editor.undoStack

    • Danh sách thao tác gần nhất (create/update/properties/delete).
  • editor.changeCount

    • Số lượng changes (để chặn commit khi không đổi gì).
  • editor.hasPersistedFeature(id)

    • true nếu feature đã tồn tại trong baseline map nội bộ.
    • Dùng cho timeline filter: feature mới tạo trong session vẫn luôn visible.

2.2 Snapshot stores (persisted on commit)

Các state này là “source of truth” cho những phần non-geometry trong commit snapshot.

a) snapshotEntities: EntitySnapshot[]

  • Dùng để build snapshot_json.entities.
  • Bao gồm:
    • entity “pin” vào project (source:"ref", operation:"reference"),
    • entity tạo mới local (source:"inline", operation:"create"),
    • entity bị xóa (nếu có) (operation:"delete").

Lưu ý quan trọng:

  • snapshotEntities là nơi “giữ entity” qua các commit, kể cả entity tạo mới chưa bind geometry.
  • buildEditorSnapshot() có logic carry-forward inline entity từ previousSnapshot để tránh mất entity sau commit/reload.

b) snapshotWikis: WikiSnapshot[]

  • Dùng để build snapshot_json.wikis.
  • Wiki hiện lưu docstring (HTML) (Quill) hoặc null với ref wiki.
  • Tiptap JSON cũ: được normalize sang HTML để hiển thị.
  • Dùng để build snapshot_json.entity_wiki.
  • operation:
    • "binding": link đang tồn tại
    • "delete": unlink trong snapshot
    • (compat) "reference" từ snapshot cũ được normalize thành "binding" khi load.

2.3 Catalog/cache state (không persist)

entityCatalog: Entity[]

Đây là RAM cache để:

  • hiển thị tên/description/status của entity,
  • merge kết quả fetch + search,
  • giảm tình trạng UI “cùng 1 entity nhưng 2 object khác nhau”.

Không ghi thẳng vào snapshot. Snapshot vẫn lấy từ snapshotEntities.

Trong page, danh sách entities dùng cho UI được merge:

entities = mergeEntitySearchResults(entityCatalog, snapshotEntitiesAsEntities)

Nghĩa là: snapshot entities (local) luôn được ưu tiên hiển thị trong UI.

2.4 UI-only state (không persist)

Các state sau chỉ phục vụ UX, mất khi reload:

  • mode (idle/select/add-*)
  • selectedFeatureId
  • selectedGeometryEntityIds (list bind tạm thời cho UI, map patch sẽ sync vào feature properties)
  • geometryMetaForm
  • entityForm (tạo entity mới)
  • entityFormStatus (toast/status 3s)
  • searchKind, searchQuery
  • entitySearchResults, wikiSearchResults, geoSearchResults
  • timelineDraftYear, timelineFilterEnabled
  • panel widths (leftPanelWidth, rightPanelWidth)

2.5 LocalStorage (trên browser)

Hiện tại chỉ có 1 thứ persist sang LocalStorage:

  • backgroundVisibility (ẩn/hiện layer nền)

Các snapshot stores (snapshotEntities, snapshotWikis, snapshotEntityWikiLinks, draft) không lưu LocalStorage; chúng được persist qua commit snapshot (backend).


3) Chuyển đổi giữa local session ↔ snapshot

3.1 Load snapshot -> mở session

Luồng: openSectionEditor() -> normalizeEditorSnapshot() -> toEditorSessionSnapshot()

Khi mở session mới:

  1. baselineSnapshot = toEditorSessionSnapshot(snapshot)
  2. initialData = baselineSnapshot.editor_feature_collection || EMPTY_FEATURE_COLLECTION
  3. snapshotEntities = baselineSnapshot.entities || []
  4. snapshotWikis = baselineSnapshot.wikis || []
  5. snapshotEntityWikiLinks = baselineSnapshot.entity_wiki || []

Riêng về GEO/GEO_ENT khi load:

  • baselineSnapshot.editor_feature_collection là dữ liệu map gốc đưa vào initialData.
  • normalizeEditorSnapshot() sẽ rehydrate feature.properties.entity_ids/entity_id từ snapshot.geometry_entity[] (hoặc legacy link_scopes) để UI bind entity hoạt động.
    • Lưu ý: đây là rehydrate phục vụ editor UX, không phải dữ liệu persist chính thức trên feature.properties trong snapshot.

Điểm mấu chốt: toEditorSessionSnapshot() reset operation để snapshot trở thành “baseline state”:

  • entities/wikis -> "reference"
  • entity_wiki active -> "binding"

3.2 Commit session -> snapshot_json

Luồng: commitSection() -> buildEditorSnapshot({ draft, changes, snapshotEntities, snapshotWikis, snapshotEntityWikiLinks, previousSnapshot: baselineSnapshot })

buildEditorSnapshot() sẽ tạo:

  • editor_feature_collection (draft đã strip các field denormalized)
  • geometries[] (create/update/delete dựa trên changes + previousSnapshot)
  • geometry_entity[] (join table từ feature.properties.entity_ids)
  • entities[] (từ snapshotEntities + carry-forward inline + ensure entities referenced by joins)
  • wikis[] (từ snapshotWikis, tương tự)
  • entity_wiki[] (từ snapshotEntityWikiLinks, đã dedupe/sort)

Sau khi commit thành công:

  • baselineSnapshot cập nhật = toEditorSessionSnapshot(snapshot) của commit mới
  • snapshot stores cập nhật theo baseline mới (operation reset về "reference"/"binding")

3.3 Reload trang -> mất local state

Khi reload:

  • Toàn bộ React state reset
  • App sẽ load lại snapshot từ backend (head commit)
  • Các thứ bạn “tạo/sửa” chỉ còn lại nếu đã nằm trong commit snapshot

Vì vậy:

  • Entity/Wiki/Link/Geometry muốn “không mất” phải đi qua Commit.
  • Các state UI (selected geo, search results, form đang nhập) sẽ mất.

4) GEO Search (/geometries/entity) và tác động lên local store

Search GEO gọi:

GET /geometries/entity?name=<keyword>&limit=<n>

Khi bấm Import một geometry từ kết quả search:

  1. Tắt timelineFilterEnabled để geometry luôn nhìn thấy (không bị filter theo năm).
  2. Add entity tương ứng vào:
    • snapshotEntities (source:"ref", operation:"reference")
    • entityCatalog (để UI có name/description)
  3. Nếu geometry chưa có trong editor.draft:
    • tạo Feature mới với id = geometry.id
    • set properties.type từ geo_type (map qua geoTypeCodeToTypeKey)
    • set time_start/time_end/binding
    • set denormalized entity_id/entity_ids/entity_name/entity_names để UI/joins hoạt động
  4. editor.createFeature(feature) và auto select feature đó.

Lưu ý: Import geo tạo ra “create change” trong editor session, nên sẽ đi vào commit snapshot.


4.1 Nhìn nhanh “5 dataset nằm ở đâu” trong session

  • GEO:

    • Runtime store: editor.draft.features[]
    • Persisted on commit: snapshot_json.geometries[] (build khi commit)
  • ENT:

    • Runtime store (snapshot): snapshotEntities
    • Persisted on commit: snapshot_json.entities[]
  • WIKI:

    • Runtime store (snapshot): snapshotWikis
    • Persisted on commit: snapshot_json.wikis[]
  • ENT_WIKI:

    • Runtime store (snapshot): snapshotEntityWikiLinks
    • Persisted on commit: snapshot_json.entity_wiki[]
  • GEO_ENT:

    • Runtime store: denormalized tạm thời trên editor.draft.features[].properties.entity_ids (để UI chạy)
    • Persisted on commit: snapshot_json.geometry_entity[] (build khi commit)

5) Checklist khi debug “mất dữ liệu”

  1. Dữ liệu có nằm trong snapshotEntities/snapshotWikis/snapshotEntityWikiLinks/editor.draft không?
  2. Có bấm Commit chưa?
  3. pendingSaveCount có > 0 không (Commit button có enable không)?
  4. Khi reload, snapshot head commit load lên có chứa các rows đó không?
  5. Nếu entity tạo mới bị mất:
  • kiểm tra commit snapshot có entities[].source:"inline" không
  • nếu có mà reload vẫn mất, kiểm tra normalizeEditorSnapshot() có parse đúng không

6) File/entrypoints liên quan

  • Session stores:

    • FrontEndUser/src/uhm/lib/useEditorSessionState.ts
    • FrontEndUser/src/uhm/lib/editor/session/useEntitySessionState.ts
    • FrontEndUser/src/uhm/lib/editor/session/useWikiSessionState.ts
    • FrontEndUser/src/uhm/lib/editor/session/useSectionSessionState.ts
  • Geometry editor core:

    • FrontEndUser/src/uhm/lib/useEditorState.ts
  • Snapshot normalization + build snapshot:

    • FrontEndUser/src/uhm/lib/editor/snapshot/editorSnapshot.ts
  • Open/commit/restore commands:

    • FrontEndUser/src/uhm/lib/editor/section/useSectionCommands.ts
  • Page wiring / UI state:

    • FrontEndUser/src/app/editor/[id]/page.tsx