eCourt-Doc-Edit/docs/plans/version-pdf.md
Suphonchai Phoonsawat 39d610a781 Phase 11: version PDF (FR-15)
- เลข version ใหม่: เว็บ-generate = v0 ฉบับตั้งต้น (ไม่มี PDF), add-in save (Start Versioning + Save & Version) = v1+ มี PDF เสมอ
- PDF ผลิตจาก Word จริงผ่าน task pane เท่านั้น (getFileAsync Pdf + runtime guard, manifest ไม่เพิ่ม requirement); capture/upload เป็น best-effort — docx ผ่านแม้ PDF ล้ม + MessageBar เตือนตาม hasPdf
- Backend: DocumentVersion.PdfObjectKey (nullable, migration AddVersionPdf) + sibling .pdf ใน bucket docx-versions · multipart part pdf (ผิด type → 400) · /download + /download-link รับ ?format=pdf (ไม่มี → 404) · presigned ฝังชื่อไฟล์ไทย + _vN (RFC 5987)
- Web: เปิดไฟล์แทน blob-download — PDF แท็บใหม่, docx ผ่าน ms-word:ofe|u| พร้อม heuristic fallback ลิงก์สำรอง (useVersionOpen, versionDownload) · ปุ่ม "เปิดใน Word" ที่หน้า success ของฟอร์มสร้างเอกสาร
- Spec v7 + CONTEXT.md + ADR 0001 + แผน docs/plans/version-pdf.md
- Automated QA: backend 68 / add-in 63 / web 55 ผ่าน · manual acceptance ผ่านบน Word for Mac (2026-09-08) รวม fidelity + track changes — ค้างเฉพาะ Windows / Word on the web

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-08 14:01:13 +07:00

27 KiB
Raw Blame History

Version PDF + เลข version v0/v1+ + เปิดไฟล์จากเว็บ — FR-15 / AC-18 / Spec v7

Status: เสร็จสมบูรณ์ — implementation + automated QA + manual acceptance ผ่านบน Word for Mac (2026-09-08) · ค้างเฉพาะ Word for Windows / Word on the web ตามรายการใน acceptance-review; spec v7

Context

ระบบเก็บทุก version เป็น .docx ใน MinIO แต่การนำเอกสารไปใช้จริง โดยเฉพาะสำเนาเชิงกฎหมาย/ยื่นศาล ต้องการ PDF ที่ฟอร์แมตเที่ยงเท่ากับที่ Word เอง export — ตัวแปลงกลาง (LibreOffice/Gotenberg, Aspose, Microsoft Graph, Word Interop บน server) ล้วนเป็น layout engine คนละตัวหรือมีต้นทุน/topology ที่ไม่เข้ากัน จึงตัดทิ้งทั้งหมด

กลไกที่เลือก (ยืนยันจาก MS Learn): Office.context.document.getFileAsync(Office.FileType.Pdf) — PdfFile requirement set รองรับ Word Windows/Mac/web/iPad · add-in ที่รันใน Word สั่ง export ได้โดยตรง และ fileService มี slice loop + sliceDataToBytes() รองรับ slice หลายรูปแบบอยู่แล้ว (กติกาข้อ 13) · known issue PDF ฝัง JS เป็นของ Excel/PPT เท่านั้น

พร้อมกันนี้ผู้ใช้ขอปรับสองเรื่อง: (1) ระบบเลข version — เว็บ-generate เป็น v0 ฉบับตั้งต้น (ไม่มี PDF), ทุกการ save จาก add-in เริ่ม v1 และมี PDF เสมอ → เลข version บอกที่มา (0 = ฟอร์ม, ≥1 = ผ่าน Word) · (2) คลิกลิงก์บนหน้าเว็บให้เปิดไฟล์ด้วยแอปของไฟล์นั้น — PDF เปิดแท็บใหม่ (ตัวดู PDF ของเบราว์เซอร์), docx เปิด Word จริงผ่าน ms-word:ofe|u|<url> (นิ่งบน Windows, Mac อาจต้องยืนยัน/พังบ้าง — มี fallback ดาวน์โหลดเสมอ)

ข้อตกลง fixed (ยืนยันครบใน grill session)

  1. ผู้ผลิต PDF เดียวในระบบ = Word จริง ผ่าน add-in — ไม่มี server-side converter ไม่ต้องเพิ่ม infra
  2. เลข version: generate เว็บ → v0 ฉบับตั้งต้น ไม่มี PDF · add-in save (ทั้ง Start Versioning และ Save & Version) → v1, v2, … มี PDF เสมอ · ไม่ backfill · ยังไม่มี production → ไม่ต้อง migration ข้อมูล dev เดิม (คงเลขเดิมหรือล้าง DB ก็ได้)
  3. Best-effort: PDF capture/upload ล้ม → version ของ docx ผ่านเสมอ (PdfObjectKey = null) + MessageBar เตือนใน task pane "บันทึกแล้ว แต่สร้าง PDF ไม่สำเร็จ" — กด Save & Version ใหม่ได้ version ใหม่พร้อม PDF
  4. ที่เก็บ: bucket docx-versions เดิม, sibling key {documentId}/v{N:D4}_{clientVersionId}.pdf (stem เดียวกับ docx)
  5. UI: ปุ่ม/ลิงก์ PDF เฉพาะ version ที่มี PDF จริง · add-in: presigned + openBrowserWindow (กติกาข้อ 12) · เว็บ: PDF แท็บใหม่ / docx ผ่าน ms-word:ofe|u| + heuristic ตรวจว่าเปิดจริง (visibilitychange/blur ~1.5s) ไม่เปิด → ลิงก์ presigned ดาวน์โหลดสำรอง
  6. pdf part ที่ส่งมาไม่ใช่ .pdf → 400 (บั๊ก client ควรเห็นชัด) — ต่างจากความล้มสิ่งแวดล้อม (MinIO) ที่ best-effort
  7. manifest ไม่ declare PdfFile เป็น requirement (ทำให้ host ไม่รองรับโหลด add-in ไม่ได้ทั้งอัน ขัด best-effort) — runtime-guard ด้วย Office.context.requirements.isSetSupported("PdfFile", "1.1") เท่านั้น

ขั้นที่ 0 — บันทึกแผน + แก้เอกสาร requirement ก่อน (กติกา repo)

  1. บันทึกแผนนี้ที่ docs/plans/version-pdf.md (ธรรมเนียมเดิม) — ทำแล้ว
  2. specs/spec-officejs.md → v7: changelog บรรทัดบนสุด (FR-15 + เลข v0/v1 + เปิดไฟล์จากเว็บ) · แก้ของเดิม: FR-5 (response + pdf part + เลขเริ่ม v1 สำหรับ upload / v0 generate), FR-7 (download/download-link ?format=pdf + presigned ฝังชื่อไฟล์ Content-Disposition), FR-10 ("version 1" → "version 0 ฉบับตั้งต้น"), AC 12/15 ที่อ้าง v1 ของ generate → v0 · ### FR-15: Version PDF — สำเนา PDF ที่ Word ผลิต (เพิ่มเติม v7) ต่อท้ายหมวด 6: ผลิตจาก Word จริงเท่านั้นใน task pane (Save & Version + Start Versioning) · multipart part pdf ทางเลือก · sibling key .pdf · best-effort + คำเตือน · download ?format=pdf (proxy + presigned 404 เมื่อไม่มี) · เว็บเปิด docx ผ่าน ms-word:ofe|u| และ PDF แท็บใหม่ · v0 ไม่มี PDF เสมอ · หมวด 7: DocumentVersion.PdfObjectKey + ปรับคำอธิบาย VersionNumber · หมวด 10: AC-18 (v7) · หมวด 11: task items (v7) ทั้ง 3 list
  3. docs/plans/officejs-implementation-plan.md: ## Phase 11: Version PDF + เลข version v0/v1+ — FR-15 (เพิ่มทีหลัง, v7) + checklist + แถว | 11 (ทีหลัง, v7) | FR-15 (+แก้ FR-5/FR-7/FR-10) | AC-18 |
  4. docs/acceptance-review.md: AC-18 (verdict 🔶 รอ implement) + หมายเหตุว่า AC เดิมที่อ้าง "generate = v1" อ่านใหม่ตาม v7
  5. สร้าง CONTEXT.md (glossary เท่านั้น ห้ามใส่ implementation): Version PDF = สำเนา PDF ที่ Word ผลิตขณะเปิดเอกสาร; ขาดได้แบบ best-effort · ฉบับตั้งต้น (v0) = version ที่เว็บสร้างจากฟอร์ม ยังไม่เคยผ่าน Word · เลข version = จำนวนรอบที่ผ่าน Word · ต้นฉบับ (docx) = ตัว version จริง; PDF เป็น derived artifact
  6. สร้าง docs/adr/0001-version-pdf-from-word-only.md: Decision (Word เท่านั้น + v0 ไม่มี PDF + ไม่ backfill) · rejected: Gotenberg/LibreOffice, Aspose, Graph cloud, Word Interop server · consequences (v0/version เก่าไม่มี PDF, request โตขึ้น, ms-word พึ่ง protocol handler ของเครื่องผู้ใช้)

ขั้นที่ 1 — Backend (backend/WordVersioning.Api)

  1. เลข version ใหม่: DocumentsController.cs Generate — VersionNumber = 0, ObjectKey v0000_{Guid}.docx (สังเกต: GenerateDocumentResponse.VersionNumber คืน 0) · UploadVersion ใช้ max+1 เดิมได้ทันที (0→1 และ ไม่มี→1 ได้ v1 ทั้งคู่) · add-in เดาเลข latest+1 ใช้ได้กับ v0 ด้วย
  2. Models/DocumentVersion.cs: public string? PdfObjectKey { get; set; } (+ doc comment FR-15: null = version นี้ไม่มี PDF — v0 หรือ capture/upload ไม่สำเร็จ)
  3. Data/AppDbContext.cs: entity.Property(x => x.PdfObjectKey).HasMaxLength(1024); · migration dotnet ef migrations add AddVersionPdf (tests ใช้ MigrateAsync อยู่แล้ว)
  4. สร้าง Services/PdfUploadValidation.cs มิเรอร์ DocxUploadValidation: นามสกุล .pdf + content-type (application/pdf/octet-stream/ว่าง) — ความลึกเท่าของเดิม ไม่ต้อง magic-byte
  5. Models/VersionDtos.cs: UploadVersionRequest.Pdf (IFormFile? — bind part pdf) · UploadVersionResponse เพิ่ม bool HasPdf ท้าย · VersionListItem เพิ่ม bool HasPdf ท้าย · GenerateDocumentResponse ไม่แตะ (v0 ไม่มี PDF — asymmetry ตั้งใจ)
  6. UploadVersion: validate part pdf หลัง message check (ไม่ใช่ .pdf/ว่าง → 400, ใหญ่เกิน → 413) · buffer เป็น MemoryStream (มิเรอร์ path docx) · ใน retry loop: PdfObjectKey = มี pdf ? $"{documentId}/v{nextNumber:D4}_{request.ClientVersionId}.pdf" : null (คำนวณใน loop ให้ตรงเลขที่จองได้) · หลัง docx PutAsync (โค้ดเดิมไม่แตะ): put PDF ด้วย application/pdf — AmazonServiceException → log warning + version.PdfObjectKey = null; SaveChangesAsync() (ultra-rare fail → row ชี้ object ไม่มี = 404 ตอนโหลด ยอมรับได้) + 201 ต่อ · response UploadVersionResponse(..., PdfObjectKey is not null) · matrix: docx put ล้ม → 502 rollback เหมือนเดิม / pdf put ล้ม → 201 hasPdf:false
  7. GetVersions: projection เพิ่ม PdfObjectKey → HasPdf ท้าย VersionListItem
  8. Download ทั้งสอง endpoint เพิ่ม string? format: "pdf" (case-insensitive; อื่น/null = docx — lenient ตามสไตล์ Clamp) → เลือก key จาก PdfObjectKey; ไม่มี → 404 "version นี้ไม่มีสำเนา PDF" · DownloadVersion: filename .pdf (Path.ChangeExtension) + content-type application/pdf · GetVersionDownloadLink: presign key ที่เลือก + ResponseHeaderOverrides.ContentDisposition ชื่อสวย (inline; filename*=UTF-8''{base}_v{N}.pdf / attachment; filename*=UTF-8''{base}_v{N}.docx — RFC 5987 สำหรับชื่อไทย, compose จาก Document.FileName + VersionNumber ฝั่ง server; add-in เจ้าเดิมก็ได้ชื่อสวยขึ้นไปด้วย) · update Swagger XML comments

ขั้นที่ 2 — Add-in (addin)

  1. fileService.ts: parametrize capture เป็น private readFileAsBlob(fileType, contentType) (slice loop + sliceDataToBytes + closeAsync ใน finally เดิม) · getCurrentDocumentFile() public API เดิม · เพิ่ม getCurrentDocumentPdf() — guard Office.context.requirements.isSetSupported("PdfFile", "1.1") (throw ถ้าไม่รองรับ), คืน Blob type application/pdf · export PDF_CONTENT_TYPE · จับทีละ File object ตามลำดับ (Word เปิดพร้อมกันได้ 2 ตัว)
  2. versioningService.ts: uploadVersion(..., pdf?: Blob) ท้าย signature → if (pdf) form.append("pdf", pdf, \${documentId}.pdf`)·buildDownloadUrl/getDownloadLink/downloadVersionเพิ่มformat?: "docx" | "pdf"→?format=pdf`
  3. models/versionRecord.ts: UploadVersionResult.hasPdf: boolean · VersionRecord.hasPdf?: boolean (optional กัน payload เก่า)
  4. versionFlow.ts — ทั้งสอง flow (v1+ มี PDF ทุกตัว): แก้ uploadCurrentFile ให้ทำเองทั้ง capture: docx → progress "กำลังสร้างสำเนา PDF…" → capture PDF ใน try/catch (พัง = ข้าม + console.warn — กติกาข้อ 10, ห้ามบล็อกการบันทึก) + guard pdf.size === 0 (build เก่าคืนไฟล์เปล่า — office-js#5950) → uploadVersion(..., pdf) ครั้งเดียว · SaveAndVersionResult/StartVersioningResult พา hasPdf จาก response ตรงๆ (backend echo คือ source เดียวของ warning — ไม่ต้องมี flag ท้องถิ่น) · step แก้เลข XML part หลัง upload เดิมทั้งหมด
  5. App.tsx: state flowWarning (เคลียร์พร้อม flowSuccess/flowError) — หลัง saveAndVersion/startVersioning สำเร็จ: if (!result.hasPdf) setFlowWarning("บันทึกแล้ว แต่สร้าง PDF ไม่สำเร็จ — version นี้ไม่มีสำเนา PDF (กด Save & Version อีกครั้งเพื่อสร้าง version ใหม่ที่มี PDF)") → render <MessageBar intent="warning"> ใต้ success (Fluent UI v9) · handleDownload(version, format) + state downloadingPdfVersionId มิเรอร์ของเดิม (ส่ง format ให้ getDownloadLink/downloadVersion, ข้อความ/ชื่อไฟล์ fallback ตาม format)
  6. VersionHistoryPanel.tsx: props เพิ่ม downloadingPdfVersionId: string | null + onDownloadPdf: (version) => void · แถวที่ version.hasPdf → Button DocumentPdf24Regular label "PDF" (size="small" appearance="subtle", disabled ระหว่างโหลดตัวใดตัวหนึ่ง, wrap flex column ให้อยู่รอด task pane แคบ)
  7. manifest.xml ไม่แก้ (ข้อตกลง #7)

ขั้นที่ 3 — Web (web)

  1. types/api.ts: VersionListItem.hasPdf: boolean · GenerateDocumentResponse เดิม
  2. สร้าง web/app/utils/versionDownload.ts (pure — ทดสอบได้ตาม pattern vitest ที่มี): versionFileName(fileName, versionNumber, format) (strip .docx + _v{N} + .pdf/.docx) · versionDownloadPath(documentId, versionId, format) (?format=pdf เมื่อ pdf) · wordOpenUrl(presignedUrl) → ms-word:ofe|u|${url} (ตรวจระดับ encode กับ MS-DOCSTRURI ตอน implement)
  3. documents/[documentId].vue — เปลี่ยนพฤติกรรมจาก blob-download เป็นเปิดไฟล์: openVersion(v, format) = GET download-link (Bearer) → PDF: window.open(url, "_blank") · DOCX: window.open(wordOpenUrl(url)) + heuristic visibilitychange/blur ใน ~1.5s — ไม่เกิด = AlertBox พร้อมลิงก์ presigned ดาวน์โหลดสำรอง · state openingVersionId/downloadError แทน downloadingVersionId (proxy-blob flow ของหน้านี้เลิกใช้ — endpoint proxy ยังอยู่เพราะ add-in/spec ใช้) · ปุ่ม history (ตาราง + การ์ดมือถือ): "เปิด" ทุก version + "PDF" v-if="v.hasPdf" · Badge แสดง v0 ได้ตามข้อมูลจริง
  4. documents/create/[templateId].vue: ปุ่ม success เปลี่ยนเป็น "เปิดใน Word" (ms-word กับ v0 — next step ธรรมชาติของ flow: เปิด → แก้ → Save & Version เป็น v1) + ลิงก์ดาวน์โหลดสำรอง

ขั้นที่ 4 — Tests

Backend — ไฟล์ใหม่ VersionPdfTests.cs (pattern VersionMessageTests) + แก้ test เดิมที่ assert เลข version ของ generate (GenerateTests ฯลฯ คาด v1 → v0, key v0000_...):

  1. ขยาย helper ก่อน: TestDtos (+HasPdf ทั้ง upload/history, DownloadLinkResult) · ApiExtensions.VersionForm เพิ่ม byte[]? pdf = null (part pdf + content-type) · TestDocx.BuildPdf() (%PDF-1.7 + payload)
  2. Upload_WithPdf_PersistsSiblingKey_AndEchoesHasPdf (key {documentId}/v0001_{clientVersionId}.pdf)
  3. Upload_WithoutPdf_StoresNullKey_HasPdfFalse
  4. Upload_InvalidPdfPart_Returns400_NoSideEffect (ชื่อไฟล์ไม่ใช่ .pdf)
  5. DownloadPdf_ReturnsExactBytes_PdfContentType_PdfFilename
  6. DownloadPdf_WhenNoPdf_Returns404 · DownloadLink_FormatPdf_NoPdf_Returns404
  7. DownloadLink_FormatPdf_PresignedUrlWithFilenameDisposition
  8. Generate_CreatesVersionZero_NoPdf (VersionNumber == 0, PdfObjectKey null, v0000_ key) · UploadAfterGenerate_StartsAtVersionOne_WithPdf
  9. Regression: default (ไม่ส่ง format) ยังเป็น docx — ครอบโดย test download เดิม

Add-in (jest): ขยาย installOfficeFileMock (fileService.test) / installOfficeDocumentMock (versionFlow.test) เพิ่ม FileType.Pdf + ตัวเลือก fail · fileService.test.ts: getCurrentDocumentPdf คืน Blob type ใช่/เรียก FileType.Pdf/slice-shape อีกรูปแบบหนึ่ง/isSetSupported false → reject/closeAsync ถูกเรียกครั้งเดียวแม้พัง · versionFlow.test.ts: ทั้ง saveAndVersion และ startVersioning แนบ part pdf ชื่อ .pdf + requestedFileTypes เรียง compressed → pdf · failPdf → ยัง upload สำเร็จ ไม่มี part pdf + hasPdf:false · test เดิม 5 ตัวไม่ต้องแก้ (assert เฉพาะ docx) · สร้าง versioningService.test.ts: getDownloadLink(id, vid, "pdf") ชน ?format=pdf · uploadVersion แนบ pdf เมื่อได้รับ

Web (vitest): web/test/versionDownload.spec.ts — ชื่อไฟล์ v0/v1 + format, path ?format=pdf, wordOpenUrl encode ถูก · component เป็น manual verify (ไม่มี component-test infra — ไม่แกล้งทดสอบ)

ขั้นที่ 5 — ปิดงาน

  1. รันชุดเต็ม: dotnet test (backend) · npm test (addin) · npm test + npm run build (web)
  2. CLAUDE.md: สถานะ Phase 0–11 · เพิ่มย่อหน้า "Version PDF (FR-15)" ในหมวดสถาปัตยกรรม + ปรับประโยค "เห็น v1" เป็นระบบ v0/v1+ · อัปเดต AGENTS.md ให้ตรงกัน
  3. docs/acceptance-review.md: verdict AC-18 + หลักฐาน test + รายการยืนยันมือที่เหลือ

ยืนยันมือที่ทำแทนไม่ได้ (ไว้ตรวจตอนปิด phase)

  1. Word Windows: generate จากเว็บ (ได้ v0 ไม่มี PDF) → "เปิดใน Word" จากหน้า success (ทดสอบ ms-word จริง) → แก้ → Save & Version → v1 + PDF ไม่มี warning + ปุ่ม PDF ขึ้นเฉพาะ v1
  2. Start Versioning เอกสารนำเข้า → v1 มี PDF ทันที
  3. Fidelity: เทียบ PDF กับ File → Save As PDF ของ Word — ฟอนต์ไทย (TH Sarabun New) render + embed (Acrobat → Properties → Fonts), หัว-ท้ายกระดาษตรง
  4. Tracked changes: เปิด Track Changes → แก้ → Save & Version → PDF มี markup ปนไหม (ไม่มี API คุม — ถ้ามี จดใน AC-18 + spec แนะนำ accept/reject ก่อนบันทึกสำเนาศาล)
  5. Word Mac + Word on the web: ซ้ำข้อ 1 (web = path ใหม่ของ PdfFile; slice shape ต่าง host)
  6. Warning path: บังคับ PDF capture ล้ม → version ผ่าน + MessageBar เตือน + ไม่มีปุ่ม PDF ทั้งสอง UI + บันทึกใหม่ได้ PDF
  7. เว็บเปิดไฟล์: PDF → แท็บใหม่เรนเดอร์ทันที · docx → Word เปิดจริง (Windows) / เครื่องไม่มี handler → เห็นลิงก์สำรอง · ชื่อไฟล์สวยทั้งสองกรณี
  8. Regression: flow docx เดิม (AC-7/8/13) + history ทุกหน้าเรียง/แสดงเลขถูกตามระบบใหม่

ความเสี่ยงที่รับไว้ (ตัดสินแล้ว)

  • 100MB request cap ครอบ docx+pdf รวมกัน — คงตาม spec เดิม, เจอจริงค่อยปรับ
  • ms-word: พึ่ง protocol handler ของเครื่องผู้ใช้ — Windows นิ่ง, Mac อาจต้องยืนยัน/พัง → heuristic fallback เสมอ
  • apiClient ไม่มี timeout (เป็นมาก่อนกับ docx) — ไม่ขยายงานนี้
  • Dev data เก่าใช้เลขเดิม (generate=v1) — ยอมรับได้เพราะยังไม่มี production หรือล้าง DB dev

Implementation record — 2026-09-08

  • ขั้น 0: spec v7 + Phase 11 + glossary + ADR
  • Backend: PdfObjectKey migration, optional PDF validation/storage/download, hasPdf, generate v0
  • Add-in: PDF ทั้งสอง flow + warnings + history PDF; ไม่มี blob fallback ใน task pane
  • Web: shared useVersionOpen, เปิด Word/PDF + fallback + pending-tab cleanup
  • Automated checks: backend 68 / add-in 63 / web 55; builds ผ่าน, add-in lint ผ่าน
  • QA รอบยืนยัน — PASS
  • Manual acceptance บน Word for Mac ผ่าน (2026-09-08) — ผลละเอียดในหมวด Manual acceptance ด้านล่าง; Windows / Word on the web คงค้างตามรายการเดิม

ข้อปรับรายละเอียดจากแผน: ใช้ DocumentPdf24Regular ที่ package มีจริง; คง signed URL โดยไม่ encode ทั้งก้อนตาม Microsoft Office URI Schemes; reserve PDF tab ก่อน await เพื่อรองรับ popup policy; เพิ่ม composable lifecycle tests โดยไม่เพิ่ม component-test framework.

QA

Dev database follow-up — 2026-09-08

พบ 42703: column "PdfObjectKey" does not exist ตอน Generate: migration ถูกสร้างและผ่าน Testcontainers แต่ยังไม่ได้ apply กับฐานข้อมูล dev wordversioning. ตรวจซ้ำด้วย SELECT "PdfObjectKey" FROM "DocumentVersions" LIMIT 0 แล้วได้ error เดียวกัน; migration history มีถึง AddTemplateFieldEditor เท่านั้น และ config ชี้ฐานข้อมูลเดียวกัน. ลง 20260908053250_AddVersionPdf ใน dev แล้วสำเร็จ. ก่อนทดสอบกับฐานข้อมูลเดิมหลังเพิ่ม migration ต้องรัน dotnet ef database update ตาม README; automated tests ที่สร้าง DB ใหม่ไม่ยืนยันสถานะ migration ของ dev DB.

2026-09-08 · fresh-eyes reviewer qa_fr15 · 2 rounds · พบ 3 ข้อ แก้ครบ 3 ไม่มี rejected findings.

  1. Build: test ใช้ .at(-1) เกิน TS target → indexed access; build ผ่าน
  2. Lifecycle: pending PDF tab ค้างเมื่อออกจากหน้า → ปิดทันทีตอน reset/unmount; test ครอบ cleanup/late response
  3. Copy: PDF inline แต่ข้อความบอกดู Downloads → ข้อความระบุเปิด PDF ใน browser

Final verdict: PASS (implementation/automated QA + manual acceptance บน Word for Mac). Phase 11 ปิดแล้ว.

Manual acceptance — 2026-09-08 (Word for Mac)

ผู้ใช้ยืนยันผ่านครบตามรายการ "ยืนยันมือที่ทำแทนไม่ได้" บน Mac (ข้อ 1–4, 6–8):

  • ข้อ 1–2 flow: เว็บ generate → v0 ไม่มี PDF → เปิดใน Word → Save & Version → v1 + PDF ไม่มี warning, ปุ่ม PDF ขึ้นเฉพาะ v1 · Start Versioning เอกสารนำเข้า → v1 มี PDF ทันที
  • ข้อ 3 fidelity: เทียบ PDF จาก add-in กับ File → Save As PDF ของ Word — ฟอนต์ไทย (TH Sarabun New) render + embed ครบ, หัว-ท้ายกระดาษตรง
  • ข้อ 4 Track Changes: เปิด Track Changes ทิ้งไว้ → แก้ → Save & Version → PDF ไม่มี markup ของ revision ปน — ไม่มีข้อจำกัดต้องบันทึกเพิ่มใน AC-18/spec
  • ข้อ 6 warning path · ข้อ 7 เว็บเปิดไฟล์ (PDF แท็บใหม่, docx ผ่าน ms-word พร้อม fallback, ชื่อไฟล์สวย) · ข้อ 8 regression flow docx เดิม — ผ่าน
  • ค้างตามเดิม (ข้อ 5): Word for Windows + Word on the web — ต้องมีเครื่อง/บัญชีทดสอบ เป็นข้อค้างเดิมตั้งแต่ Phase 8 (AC-1 ฯลฯ) ไม่ใช่สิ่งที่ Phase 11 เพิ่มเข้ามา