เพิ่ม editor ต่อ template field — ผู้ดูแลเลือกรูปแบบช่องกรอกในฟอร์มสร้างเอกสารได้: text → textbox/textarea, number/date → control ตาม type (presentation metadata ล้วน — validation ยังยึด Type, ค่า field เป็น string ทั้ง flow, ไม่แตะ generate/sync/add-in) - Backend: TemplateField.Editor (nullable, null = default ของ type) + migration AddTemplateFieldEditor + validate ตาม type ใน PUT templates (ค่าไม่รู้จัก/ไม่เข้ากับ type → 400, normalize-on-write, API คืน resolved เสมอ) - Web: util fieldEditor.ts + editor select หน้าแก้ template (desktop+mobile, ซ่อนเมื่อ type มีทางเดียว) + ฟอร์มสร้างเอกสาร render ตาม editor + แก้ prefill re-generate ของ field date จาก ISO timestamp เต็ม (input[type=date] รับเฉพาะ yyyy-MM-dd เดิมแสดงว่าง) - Docs: spec v6 (FR-14/AC-17) + plan Phase 10 + sub-plan field-editor.md + acceptance-review AC-17 + README/CLAUDE.md - Tests: backend 60/60 (FieldEditorTests ใหม่ 8 กรณี) · web 46/46 + build ผ่าน Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
โปรเจกต์นี้คืออะไร
ระบบ Word Versioning (ชื่อที่แสดงใน UI: e-Court Documents Versioning): จัดการ version history ของไฟล์ .docx — สร้างเอกสารจาก template ผ่านเว็บ, ตรวจจับ tracked document ใน Word, บันทึก version ผ่านปุ่ม Save & Version และ sync ค่า fields กลับ DB ประกอบด้วย 3 ส่วน:
| ส่วน | Stack | หน้าที่ |
|---|---|---|
/addin |
Office.js + TypeScript + React + Fluent UI (yo office) |
Task pane ใน Word Desktop/Online — detect, Save & Version, history, field extraction |
/backend |
ASP.NET Core 8 + EF Core + PostgreSQL + MinIO (AWSSDK.S3) + OpenXML | Version API, template system, docx generation, JWT validation |
/web |
Nuxt.js + TailwindCSS (light/dark theme) | จัดการ template + กรอก form สร้างเอกสารเป็น version 1 อัตโนมัติ |
Infra ทั้งหมดรันผ่าน docker-compose.yml: PostgreSQL + MinIO + Keycloak (OIDC, 3 public clients พร้อม PKCE: word-addin-officejs, word-versioning-web, word-addin-vsto)
สถานะปัจจุบัน: Phase 0–10 เสร็จ (implement ครบ + tests ครบชุด: addin 50 / web 46 / backend integration 60 — รันจริงบน PostgreSQL+MinIO+Keycloak ผ่าน Testcontainers + ไล่ AC 1–17 ครบที่ docs/acceptance-review.md) — เหลือยืนยันด้วยมือเฉพาะงานที่ทำแทนไม่ได้: Word Online/Windows, click-through บน browser จริง, silent refresh จริง (ดูรายการท้าย acceptance-review.md)
เอกสารหลัก (อ่านก่อนเริ่มงานใดๆ)
specs/spec-officejs.md— source of truth ของ requirements (FR-1–14, acceptance criteria 1–17, data models, platform constraints)docs/plans/officejs-implementation-plan.md— implementation plan แบ่ง 11 phases (0–10) พร้อม checklist, DoD, dependency ระหว่าง phase และตารา mapping Phase↔FR↔ACdocs/acceptance-review.md— ผลตรวจ AC 1–17 พร้อมหลักฐาน (test ไหนคุ้มข้อไหน) + รายการที่เหลือต้องยืนยันด้วยมือ
เมื่อ requirement เปลี่ยน: แก้ spec ก่อน แล้ว sync ไปยัง plan
กฎที่ห้ามละเมิด (ข้อจำกัดแพลตฟอร์มจริง — สรุปจาก spec ส่วน 2 และ 9)
- ห้ามออกแบบ flow ที่ดัก native save (Ctrl+S / File>Save) — Office.js ไม่มี before-save event ที่ cancel ได้ ใช้ปุ่มใน task pane เท่านั้น
- Custom Document Properties = primary flag ตรวจจับ tracked document; Custom XML Part = secondary เท่านั้น (ต้อง delete แล้ว add ใหม่ทุกครั้ง, มี known bug บน Mac บาง build — ต้อง catch แบบไม่ throw)
- ห้าม redirect iframe ของ task pane ไป Keycloak ตรงๆ — ต้องใช้ Office Dialog API (
displayDialogAsync+messageParent); ส่วน/webเป็น top-level page ใช้ OIDC redirect ปกติ - Token เก็บใน memory เท่านั้น — ห้าม localStorage · ของ
/webที่ persist ได้มีเฉพาะ theme preference (localStorage) และ refresh token + PKCE verifier (sessionStorage อายุสั้น — จำเป็นเพื่อข้าม redirect/reload ตามที่แผนอนุญาต) - Backend คือ source of truth ของ version history และ field values
- Field key = content control (SDT) tag ใช้ค่าเดียวกันทั้ง generate (backend) / extract (add-in) / sync (DB)
- Docx generation + metadata stamping ทำฝั่ง backend เท่านั้น (OpenXML, แก้ที่ run level ใน SDT เพื่อรักษา formatting)
- Ports: add-in =
https://localhost:3000, web =http://localhost:5173(ห้ามชนกัน), backend CORS ต้องอนุญาตทั้งคู่ - Config (DB/MinIO/Keycloak) แยกเป็น env/appsettings เท่านั้น — ห้าม hardcode;
userIdเอาจาก JWT claims เท่านั้น ห้ามรับจาก client - ทุก Office.js call ต้อง catch — ห้ามปล่อย unhandled promise rejection
- Dev: ทุก service ที่ task pane (หน้า https) เรียกต้องเป็น HTTPS ด้วย cert ของ
office-addin-dev-certs— Keycloak =https://localhost:8443(Office Dialog API ปฏิเสธ http:// โดยสมบูรณ์), backend =https://localhost:7232+http://localhost:5075(WKWebView บล็อก mixed content → "Load failed") — รันnpx office-addin-dev-certs installก่อนdocker compose up - ดาวน์โหลดไฟล์จาก add-in ใช้ presigned URL +
Office.context.ui.openBrowserWindowเท่านั้น — webview ของ Word ทิ้ง blob download (a[download]) แบบเงียบๆ ไฟล์ไม่ถูกเขียนที่ไหนเลย; และห้ามcpทับ manifest ใน wef folder (npm startใช้ symlink — ทับแล้วครั้งหน้า EEXIST, ให้แก้ที่addin/manifest.xmlต้นทาง) slice.dataจากgetSliceAsyncมีหลายรูปแบบตาม host (ArrayBuffer / view / base64 string /number[]) — ต้องผ่านfileService.sliceDataToBytes()เสมอ ห้าม assume รูปเดียว@nuxtjs/tailwindcssบน Nuxt 4 ต้องระบุcssPath: '~/assets/css/tailwind.css'ใน nuxt.config.ts เสมอ — default แบบ relative resolve ต่อ rootDir แล้ว fallback ไปไฟล์ default ของ tailwindcss package แบบเงียบ (token/component classes ทั้งหมดหายจาก build โดยไม่มี error) · แก้tailwind.config.tsแล้วต้อง restart dev server · สีทั้งหมดเป็น CSS variables RGB triplet ใน tailwind.css — ห้ามใส่ hex (break/opacityutilities เงียบๆ)
สถาปัตยกรรม — flow หลักที่ต้องเข้าใจ
Create → v1 (FR-10): web form → POST /api/documents/generate → OpenXML แทนที่ค่าใน SDT + stamp SavedByAddIn/DocumentTrackingId ลงไฟล์ → สร้าง Document + DocumentFieldValue + upload v1 → ผู้ใช้โหลดไฟล์ไปเปิดใน Word → add-in ตรวจพบ tracked doc ทันทีเห็น v1
Save & Version (FR-2): ปุ่มใน task pane → dialog ยืนยันพร้อมช่อง commit message ทางเลือก (FR-13) → stamp properties → อัปเดต XML part (delete-then-add, fail ได้แบบ silent) → document.save() → getFileAsync → POST version พร้อม fields map จาก content controls (FR-11) + message → backend update DocumentFieldValue (latest) + FieldsSnapshotJson (ต่อ version)
Version message (FR-13): commit message ทางเลือกต่อ version (≤2048, ว่าง/whitespace → null + trim) รับจากทุก flow ที่สร้าง version (upload multipart + generate JSON) — เก็บที่ DocumentVersion.Message เท่านั้น ไม่ฝังลงไฟล์ docx (backend คือ source of truth) · แสดงใน history ทุกหน้า (web ตาราง+การ์ดมือถือ, add-in panel) · เกิน 2048 → 400 ตรวจก่อนแตะ MinIO
Field sync policy (FR-11): field ขาด (ผู้ใช้ลบ control) → เก็บค่าเดิม + mark missing ไม่ fail request; ส่งมาเกินนิยาม → ignore
เอกสารของ template (FR-12): GET /api/templates/{id}/documents (รายการต่อ template) + GET /api/documents/{id} (detail + ค่า fields ล่าสุด — merge นิยาม × ค่าแบบรองรับ orphan สองทาง) · re-generate ทำฝั่งเว็บ: ฟอร์มสร้างเอกสาร prefilled จากค่าปัจจุบัน (?from={documentId}) แล้วผ่าน endpoint generate เดิม → เอกสารใหม่ v1 เอกสารต้นทางไม่ถูกแตะ
MinIO: bucket docx-versions (snapshot ต่อ version, key {documentId}/v{N:D4}_{clientVersionId}.docx) + docx-templates (ไฟล์ template) — private ทั้งคู่, เข้าถึงผ่าน backend เท่านั้น (proxy stream หรือ presigned URL อายุสั้นจาก download-link — ซึ่ง add-in ใช้เปิดในเบราว์เซอร์ของเครื่อง)
Template: template.docx author ด้วย Word (ใส่ content controls) → upload → backend parse SDT tags → admin ตั้ง label/type/editor/required → delete = deactivate (soft) เพราะ document เก่ายังอ้างอยู่
Field editor (FR-14): TemplateField.Editor (nullable, null = default ของ type: text→textbox, number→number, date→date; text เลือก textarea ได้) — presentation metadata ล้วน: คุมแค่ control ที่ฟอร์มสร้างเอกสาร render (web/app/utils/fieldEditor.ts), validation ของค่ายังยึด Type, ค่า field เป็น string ทั้ง flow ไม่แตะ generate/sync/docx/add-in · ค่า = default → DB เก็บ null แต่ API คืน resolved เสมอ
คำสั่ง (ใช้ได้หลัง scaffold ใน Phase 0)
docker compose up -d # infra: PostgreSQL + MinIO + Keycloak(https:8443)
python3 scripts/dev-token.py # token PKCE จริงสำหรับทดสอบ backend/Swagger (admin-cli ไม่มี aud ใช้ไม่ได้)
# backend (http://localhost:5075 + https://localhost:7232 — dev เปิด https อัตโนมัติด้วย dev cert)
cd backend/WordVersioning.Api
dotnet run # API + swagger (ผ่าน launchSettings — เปิด browser ให้)
ASPNETCORE_ENVIRONMENT=Development ASPNETCORE_URLS=http://localhost:5075 dotnet run --no-launch-profile # แบบไม่เปิด browser — ต้องใส่ env เอง ไม่งั้นไปอ่าน appsettings.json (DB password ว่าง) → 500
dotnet ef migrations add <Name>
# backend tests (integration — ต้องมี Docker รันอยู่: Testcontainers ยก Postgres+MinIO+Keycloak เองทุก run ~1 นาที)
cd backend
dotnet test # slnx ครอบทั้ง API + tests (60 tests)
dotnet test --filter "FullyQualifiedName~GenerateTests" # รัน test เดี่ยว
# add-in (https://localhost:3000)
cd addin
npx office-addin-dev-certs install # ครั้งแรกครั้งเดียว — ทำก่อน docker compose up
npm run build # webpack build
npm start # dev server — sideload ผ่าน manifest (symlink)
npm test
npx jest src/services/metadataService.test.ts # รัน test เดี่ยว
# web portal (http://localhost:5173 — ห้ามใช้ 3000)
cd web
npm run dev
npm test
npx vitest run test/useTheme.spec.ts # รัน test เดี่ยว