- เลข 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>
27 KiB
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)
- ผู้ผลิต PDF เดียวในระบบ = Word จริง ผ่าน add-in — ไม่มี server-side converter ไม่ต้องเพิ่ม infra
- เลข version: generate เว็บ → v0 ฉบับตั้งต้น ไม่มี PDF · add-in save (ทั้ง Start Versioning และ Save & Version) → v1, v2, … มี PDF เสมอ · ไม่ backfill · ยังไม่มี production → ไม่ต้อง migration ข้อมูล dev เดิม (คงเลขเดิมหรือล้าง DB ก็ได้)
- Best-effort: PDF capture/upload ล้ม → version ของ docx ผ่านเสมอ (
PdfObjectKey= null) + MessageBar เตือนใน task pane "บันทึกแล้ว แต่สร้าง PDF ไม่สำเร็จ" — กด Save & Version ใหม่ได้ version ใหม่พร้อม PDF - ที่เก็บ: bucket
docx-versionsเดิม, sibling key{documentId}/v{N:D4}_{clientVersionId}.pdf(stem เดียวกับ docx) - UI: ปุ่ม/ลิงก์ PDF เฉพาะ version ที่มี PDF จริง · add-in: presigned +
openBrowserWindow(กติกาข้อ 12) · เว็บ: PDF แท็บใหม่ / docx ผ่านms-word:ofe|u|+ heuristic ตรวจว่าเปิดจริง (visibilitychange/blur ~1.5s) ไม่เปิด → ลิงก์ presigned ดาวน์โหลดสำรอง pdfpart ที่ส่งมาไม่ใช่ .pdf → 400 (บั๊ก client ควรเห็นชัด) — ต่างจากความล้มสิ่งแวดล้อม (MinIO) ที่ best-effort- manifest ไม่ declare
PdfFileเป็น requirement (ทำให้ host ไม่รองรับโหลด add-in ไม่ได้ทั้งอัน ขัด best-effort) — runtime-guard ด้วยOffice.context.requirements.isSetSupported("PdfFile", "1.1")เท่านั้น
ขั้นที่ 0 — บันทึกแผน + แก้เอกสาร requirement ก่อน (กติกา repo)
- บันทึกแผนนี้ที่
docs/plans/version-pdf.md(ธรรมเนียมเดิม) — ทำแล้ว - 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 partpdfทางเลือก · 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 - 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 | - docs/acceptance-review.md: AC-18 (verdict 🔶 รอ implement) + หมายเหตุว่า AC เดิมที่อ้าง "generate = v1" อ่านใหม่ตาม v7
- สร้าง
CONTEXT.md(glossary เท่านั้น ห้ามใส่ implementation): Version PDF = สำเนา PDF ที่ Word ผลิตขณะเปิดเอกสาร; ขาดได้แบบ best-effort · ฉบับตั้งต้น (v0) = version ที่เว็บสร้างจากฟอร์ม ยังไม่เคยผ่าน Word · เลข version = จำนวนรอบที่ผ่าน Word · ต้นฉบับ (docx) = ตัว version จริง; PDF เป็น derived artifact - สร้าง
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)
- เลข version ใหม่: DocumentsController.cs
Generate—VersionNumber = 0, ObjectKeyv0000_{Guid}.docx(สังเกต:GenerateDocumentResponse.VersionNumberคืน 0) ·UploadVersionใช้max+1เดิมได้ทันที (0→1 และ ไม่มี→1 ได้ v1 ทั้งคู่) · add-in เดาเลขlatest+1ใช้ได้กับ v0 ด้วย - Models/DocumentVersion.cs:
public string? PdfObjectKey { get; set; }(+ doc comment FR-15: null = version นี้ไม่มี PDF — v0 หรือ capture/upload ไม่สำเร็จ) - Data/AppDbContext.cs:
entity.Property(x => x.PdfObjectKey).HasMaxLength(1024);· migrationdotnet ef migrations add AddVersionPdf(tests ใช้ MigrateAsync อยู่แล้ว) - สร้าง
Services/PdfUploadValidation.csมิเรอร์DocxUploadValidation: นามสกุล.pdf+ content-type (application/pdf/octet-stream/ว่าง) — ความลึกเท่าของเดิม ไม่ต้อง magic-byte - Models/VersionDtos.cs:
UploadVersionRequest.Pdf(IFormFile?— bind partpdf) ·UploadVersionResponseเพิ่มbool HasPdfท้าย ·VersionListItemเพิ่มbool HasPdfท้าย ·GenerateDocumentResponseไม่แตะ (v0 ไม่มี PDF — asymmetry ตั้งใจ) UploadVersion: validate partpdfหลัง message check (ไม่ใช่ .pdf/ว่าง → 400, ใหญ่เกิน → 413) · buffer เป็น MemoryStream (มิเรอร์ path docx) · ใน retry loop:PdfObjectKey = มี pdf ? $"{documentId}/v{nextNumber:D4}_{request.ClientVersionId}.pdf" : null(คำนวณใน loop ให้ตรงเลขที่จองได้) · หลัง docxPutAsync(โค้ดเดิมไม่แตะ): put PDF ด้วยapplication/pdf—AmazonServiceException→ log warning +version.PdfObjectKey = null; SaveChangesAsync()(ultra-rare fail → row ชี้ object ไม่มี = 404 ตอนโหลด ยอมรับได้) + 201 ต่อ · responseUploadVersionResponse(..., PdfObjectKey is not null)· matrix: docx put ล้ม → 502 rollback เหมือนเดิม / pdf put ล้ม → 201 hasPdf:falseGetVersions: projection เพิ่มPdfObjectKey→HasPdfท้าย VersionListItem- Download ทั้งสอง endpoint เพิ่ม
string? format:"pdf"(case-insensitive; อื่น/null = docx — lenient ตามสไตล์ Clamp) → เลือก key จากPdfObjectKey; ไม่มี → 404 "version นี้ไม่มีสำเนา PDF" ·DownloadVersion: filename.pdf(Path.ChangeExtension) + content-typeapplication/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)
- fileService.ts: parametrize capture เป็น private
readFileAsBlob(fileType, contentType)(slice loop +sliceDataToBytes+closeAsyncใน finally เดิม) ·getCurrentDocumentFile()public API เดิม · เพิ่มgetCurrentDocumentPdf()— guardOffice.context.requirements.isSetSupported("PdfFile", "1.1")(throw ถ้าไม่รองรับ), คืน Blob typeapplication/pdf· exportPDF_CONTENT_TYPE· จับทีละ File object ตามลำดับ (Word เปิดพร้อมกันได้ 2 ตัว) - versioningService.ts:
uploadVersion(..., pdf?: Blob)ท้าย signature →if (pdf) form.append("pdf", pdf, \${documentId}.pdf`)·buildDownloadUrl/getDownloadLink/downloadVersionเพิ่มformat?: "docx" | "pdf"→?format=pdf` - models/versionRecord.ts:
UploadVersionResult.hasPdf: boolean·VersionRecord.hasPdf?: boolean(optional กัน payload เก่า) - versionFlow.ts — ทั้งสอง flow (v1+ มี PDF ทุกตัว): แก้
uploadCurrentFileให้ทำเองทั้ง capture: docx → progress "กำลังสร้างสำเนา PDF…" → capture PDF ใน try/catch (พัง = ข้าม +console.warn— กติกาข้อ 10, ห้ามบล็อกการบันทึก) + guardpdf.size === 0(build เก่าคืนไฟล์เปล่า — office-js#5950) →uploadVersion(..., pdf)ครั้งเดียว ·SaveAndVersionResult/StartVersioningResultพาhasPdfจาก response ตรงๆ (backend echo คือ source เดียวของ warning — ไม่ต้องมี flag ท้องถิ่น) · step แก้เลข XML part หลัง upload เดิมทั้งหมด - 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)+ statedownloadingPdfVersionIdมิเรอร์ของเดิม (ส่ง format ให้getDownloadLink/downloadVersion, ข้อความ/ชื่อไฟล์ fallback ตาม format) - VersionHistoryPanel.tsx: props เพิ่ม
downloadingPdfVersionId: string | null+onDownloadPdf: (version) => void· แถวที่version.hasPdf→ ButtonDocumentPdf24Regularlabel "PDF" (size="small" appearance="subtle", disabled ระหว่างโหลดตัวใดตัวหนึ่ง, wrap flex column ให้อยู่รอด task pane แคบ) - manifest.xml ไม่แก้ (ข้อตกลง #7)
ขั้นที่ 3 — Web (web)
- types/api.ts:
VersionListItem.hasPdf: boolean·GenerateDocumentResponseเดิม - สร้าง
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) - documents/[documentId].vue — เปลี่ยนพฤติกรรมจาก blob-download เป็นเปิดไฟล์:
openVersion(v, format)=GET download-link(Bearer) → PDF:window.open(url, "_blank")· DOCX:window.open(wordOpenUrl(url))+ heuristicvisibilitychange/blurใน ~1.5s — ไม่เกิด =AlertBoxพร้อมลิงก์ presigned ดาวน์โหลดสำรอง · stateopeningVersionId/downloadErrorแทนdownloadingVersionId(proxy-blob flow ของหน้านี้เลิกใช้ — endpoint proxy ยังอยู่เพราะ add-in/spec ใช้) · ปุ่ม history (ตาราง + การ์ดมือถือ): "เปิด" ทุก version + "PDF"v-if="v.hasPdf"· Badge แสดง v0 ได้ตามข้อมูลจริง - 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_...):
- ขยาย helper ก่อน:
TestDtos(+HasPdfทั้ง upload/history,DownloadLinkResult) ·ApiExtensions.VersionFormเพิ่มbyte[]? pdf = null(partpdf+ content-type) ·TestDocx.BuildPdf()(%PDF-1.7 + payload) Upload_WithPdf_PersistsSiblingKey_AndEchoesHasPdf(key{documentId}/v0001_{clientVersionId}.pdf)Upload_WithoutPdf_StoresNullKey_HasPdfFalseUpload_InvalidPdfPart_Returns400_NoSideEffect(ชื่อไฟล์ไม่ใช่ .pdf)DownloadPdf_ReturnsExactBytes_PdfContentType_PdfFilenameDownloadPdf_WhenNoPdf_Returns404·DownloadLink_FormatPdf_NoPdf_Returns404DownloadLink_FormatPdf_PresignedUrlWithFilenameDispositionGenerate_CreatesVersionZero_NoPdf(VersionNumber == 0, PdfObjectKey null,v0000_key) ·UploadAfterGenerate_StartsAtVersionOne_WithPdf- 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 — ปิดงาน
- รันชุดเต็ม:
dotnet test(backend) ·npm test(addin) ·npm test+npm run build(web) - CLAUDE.md: สถานะ Phase 0–11 · เพิ่มย่อหน้า "Version PDF (FR-15)" ในหมวดสถาปัตยกรรม + ปรับประโยค "เห็น v1" เป็นระบบ v0/v1+ · อัปเดต AGENTS.md ให้ตรงกัน
- docs/acceptance-review.md: verdict AC-18 + หลักฐาน test + รายการยืนยันมือที่เหลือ
ยืนยันมือที่ทำแทนไม่ได้ (ไว้ตรวจตอนปิด phase)
- Word Windows: generate จากเว็บ (ได้ v0 ไม่มี PDF) → "เปิดใน Word" จากหน้า success (ทดสอบ
ms-wordจริง) → แก้ → Save & Version → v1 + PDF ไม่มี warning + ปุ่ม PDF ขึ้นเฉพาะ v1 - Start Versioning เอกสารนำเข้า → v1 มี PDF ทันที
- Fidelity: เทียบ PDF กับ File → Save As PDF ของ Word — ฟอนต์ไทย (TH Sarabun New) render + embed (Acrobat → Properties → Fonts), หัว-ท้ายกระดาษตรง
- Tracked changes: เปิด Track Changes → แก้ → Save & Version → PDF มี markup ปนไหม (ไม่มี API คุม — ถ้ามี จดใน AC-18 + spec แนะนำ accept/reject ก่อนบันทึกสำเนาศาล)
- Word Mac + Word on the web: ซ้ำข้อ 1 (web = path ใหม่ของ PdfFile; slice shape ต่าง host)
- Warning path: บังคับ PDF capture ล้ม → version ผ่าน + MessageBar เตือน + ไม่มีปุ่ม PDF ทั้งสอง UI + บันทึกใหม่ได้ PDF
- เว็บเปิดไฟล์: PDF → แท็บใหม่เรนเดอร์ทันที · docx → Word เปิดจริง (Windows) / เครื่องไม่มี handler → เห็นลิงก์สำรอง · ชื่อไฟล์สวยทั้งสองกรณี
- 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.
- Build: test ใช้
.at(-1)เกิน TS target → indexed access; build ผ่าน - Lifecycle: pending PDF tab ค้างเมื่อออกจากหน้า → ปิดทันทีตอน reset/unmount; test ครอบ cleanup/late response
- 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 เพิ่มเข้ามา