+
+ e-Court Documents Versioning
+ {phase !== "signed-out" && phase !== "checking" && username ? (
+ /* จอแคบ: username ย้ายไป title/aria-label — ไม่กินพื้นที่แถบหัวจนตัวชื่อแอป wrap 3-4 บรรทัด */
+
+ ) : null}
+
+
+ {/* FR-17 — banner auto-save error ยกไว้เหนือ toggle (เดิมซ่อนอยู่ใน SaveControls ของแท็บ
+ versioning): auto-save รันทุกแท็บ การเขียนค่าทดสอบของแท็บ template ก็ทำให้ dirty ได้
+ จึงต้องเห็น error แม้อยู่อีกแท็บ */}
+ {(phase === "untracked" || phase === "tracking") && autoSaveError ? (
+
{autoSaveError}
+ ) : null}
+
+ {/* toggle แท็บ — แสดงเฉพาะ phase ที่เข้าถึงเอกสารแล้ว (checking/signed-out ยังไม่มีอะไรให้จัดการ)
+ ปิดทั้งสองปุ่มระหว่าง flow ที่เขียนเอกสาร: saving/starting ของ versioning (กัน progress หาย
+ กลาง flow — handler อยู่ที่ component นี้ flow ยังรันจนจบ แค่หน้าตา), saveBusy ของบันทึก
+ local/ปิดเอกสาร และ templateBusy ที่ panel รายงานผ่าน onBusyChange — กันเครื่องมือ template
+ แข่งแก้เอกสารกลาง stamp/capture ของ versionFlow หรือกลับกัน (panel unmount แล้ว promise
+ ของ service ก็ยังเขียนเอกสารต่อ ต้องปิดทางสลับตั้งแต่ต้น) */}
+ {(phase === "untracked" || phase === "tracking") && (
+
+
+
+
+ )}
+
+ {phase === "checking" ? (
+
+
+ {bootError ? (
+ void detect()}
+ >
+ ลองใหม่
+
+ }
+ >
+ {bootError}
+
+ ) : null}
+
+ ) : null}
+
+ {phase === "signed-out" ? (
+
+
เข้าสู่ระบบเพื่อเริ่มใช้งาน versioning
+
+ ใช้บัญชีของ realm word-versioning — หน้าต่าง login จะเปิดแยกต่างหาก
+
+
+ {loginError ?
{loginError} : null}
+
+ ) : null}
+
+ {phase === "untracked" && activeTab === "versioning" ? (
+
+
เอกสารนี้ยังไม่ได้ติดตาม version
+
+ กดปุ่มด้านล่างเพื่อเริ่ม track — ระบบจะ stamp metadata ลงไฟล์และอัปโหลดเป็น version 1
+ (ไฟล์ที่สร้างจากเว็บ portal จะถูกตรวจพบและข้ามขั้นตอนนี้อัตโนมัติ)
+
+ {documentTemplateId ? (
+
+ ไฟล์นี้เป็นไฟล์ร่างจาก template — กดเริ่มติดตามแล้วระบบจะผูกเอกสารกับ template ต้นทาง
+ และ sync ค่า fields ให้อัตโนมัติ
+
+ ) : null}
+
+
void handleSaveOnly()}
+ onSaveAndClose={() => void handleSaveAndClose()}
+ />
+ {starting && progressMessage ? : null}
+ {flowError ? {flowError} : null}
+
+ ) : null}
+
+ {phase === "tracking" && activeTab === "versioning" ? (
+ /* ห้ามใส่ items-start — ลูกทุกตัวจะหดตามเนื้อหา (MessageBar/history list ไม่เต็มความกว้าง pane) */
+
+ {/* กลุ่มข้อความสถานะ */}
+ {hasUncommitted ? (
+
+ มีการแก้ไขที่ยังไม่ถูกบันทึกเป็นเวอร์ชันใหม่ — กด Save & Version เพื่อบันทึกเป็น version ใหม่
+
+ ) : null}
+
+ {flowWarning ?
{flowWarning} : null}
+ {flowSuccess ?
{flowSuccess} : null}
+ {flowError ?
{flowError} : null}
+
+ {/* กลุ่ม action บันทึก — ปุ่มหลักเต็มความกว้างแถบ (action เด่นที่สุดของทั้งแอป) + progress + local save
+ icon ใช้ commit ไม่ใช่ floppy — แยกจากปุ่ม "บันทึก" (local save) ที่ความหมายต่างกัน */}
+
+
+ {saving && progressMessage ? : null}
+ void handleSaveOnly()}
+ onSaveAndClose={() => void handleSaveAndClose()}
+ />
+
+
+ {/* ink-soft (ไม่ใช่ ink-faint 2.6:1) — tracking id คือ identifier ที่ผู้ใช้อาจต้องอ่าน/คัดลอกตอนแจ้งปัญหา */}
+
tracking id: {documentId?.slice(0, 8)}…
+
+
void handleDownload(version, "pdf")}
+ onDownload={(version) => void handleDownload(version)}
+ onRetry={() => documentId && void refreshHistory(documentId)}
+ />
+
+ ) : null}
+
+ {/* แท็บเครื่องมือ Template — state ทั้งหมดอยู่ใน panel เอง (snapshot ชั่วคราวของเอกสาร
+ สลับแท็บแล้ว unmount = ทิ้งค่าทิ้งโดยตั้งใจ) */}
+ {(phase === "untracked" || phase === "tracking") && activeTab === "template" ? (
+
+ ) : null}
+
+ {/* dialog ทั้งสองเป็นของ flow versioning — render เฉพาะแท็บ versioning (ตอน dialog เปิด
+ overlay fixed inset-0 + Tab trap อยู่แล้ว ปุ่ม toggle แตะไม่ได้ จึงไม่มี path ที่ dialog
+ เปิดค้างขณะสลับแท็บ) */}
+ {(phase === "untracked" || phase === "tracking") && activeTab === "versioning" ? (
+ <>
+ {/* กดยืนยันแล้วปิด dialog ทันที — progress/ผลลัพธ์แสดงที่ pane (dialog ค้าง = บังผลลัพธ์
+ + เสี่ยงกดยืนยันซ้ำสร้าง version ซ้ำ; busy ใน dialog เหลือเป็น defense ชั่วขณะก่อน re-render) */}
+
{
+ setConfirmOpen(false);
+ void handleSaveAndVersion(message);
+ }}
+ {...(closeSupported
+ ? {
+ onConfirmClose: (message: string) => {
+ setConfirmOpen(false);
+ void handleSaveAndVersion(message, true);
+ },
+ }
+ : {})}
+ onDismiss={() => setConfirmOpen(false)}
+ />
+
+ {/* FR-13 — Start Versioning ใช้ dialog เดียวกัน: ยืนยัน + พิมพ์ commit message ของ v1 ก่อนเริ่ม track */}
+ {
+ setStartConfirmOpen(false);
+ void handleStartVersioning(message);
+ }}
+ onDismiss={() => setStartConfirmOpen(false)}
+ />
+ >
+ ) : null}
+
+ );
+};
+
+export default TaskpaneApp;
diff --git a/components/wordaddin/TemplateToolsPanel.tsx b/components/wordaddin/TemplateToolsPanel.tsx
new file mode 100644
index 0000000..3267edf
--- /dev/null
+++ b/components/wordaddin/TemplateToolsPanel.tsx
@@ -0,0 +1,411 @@
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+import { CornerDownLeft, Crosshair, FilePlus, LoaderCircle, PenLine, ScanSearch, WrapText } from "lucide-react";
+
+import {
+ applyTestValues,
+ insertBreakAtSelection,
+ insertParagraphAtSelection,
+ listContentControls,
+ selectContentControl,
+ supportsAllContentControlTypes,
+ type ScannedField,
+} from "@/wordaddin/services/templateService";
+import { getErrorMessage } from "@/wordaddin/services/errors";
+
+import { MessageBar } from "./MessageBar";
+
+/**
+ * แท็บ "เครื่องมือ Template" (แท็บที่สองของ taskpane) — เครื่องมือช่วยทำ template
+ * สำหรับคนออกแบบเอกสาร ใช้ได้ทั้ง phase untracked และ tracking เพราะไม่แตะ backend/tracking:
+ * 1. สแกน Content Controls ทั้งเอกสาร + เตือน tag ว่าง/ซ้ำ + กระโดดไปเลือกช่อง
+ * 2. กรอกค่าทดสอบทุกช่องแล้วเขียนกลับเข้าเอกสารจริง (ดูผลจัดหน้า/ตัดบรรทัดทันที
+ * โดยไม่ต้อง render ผ่าน /api/template/render)
+ * 3. แทรกย่อหน้า/ตัวแบ่งบรรทัด/ตัวแบ่งหน้าที่ cursor ตอนจัดโครงสร้าง
+ *
+ * state ทั้งหมดถือภายใน — เป็น snapshot ชั่วคราวของเอกสาร: สลับแท็บแล้ว unmount = ทิ้ง
+ * ค่าทิ้งโดยตั้งใจ (เก็บไว้ก็ stale เสียเองเมื่อผู้ใช้แก้เอกสารระหว่างนั้น)
+ */
+
+const BTN_PRIMARY =
+ "focus-ring inline-flex items-center gap-1.5 rounded-md bg-primary px-4 py-2.5 text-sm font-medium text-white transition hover:-translate-y-px hover:bg-primary-hover hover:shadow-btn-glow disabled:pointer-events-none disabled:opacity-50";
+const BTN_SECONDARY =
+ "focus-ring inline-flex items-center gap-1.5 rounded-md border border-line bg-white px-3 py-1.5 text-sm text-ink transition hover:border-primary hover:text-primary disabled:pointer-events-none disabled:opacity-50";
+/** px-2 py-1.5 + text-xs ≈ สูง 30px — พ้นเกณฑ์ touch target 24px (WCAG 2.5.8) เหมือน VersionHistoryPanel */
+const BTN_ACTION =
+ "focus-ring inline-flex items-center gap-1 rounded-md px-2 py-1.5 text-xs text-ink-soft transition hover:bg-primary/[0.06] hover:text-primary disabled:pointer-events-none disabled:opacity-50";
+
+/** ปุ่มแทรกองค์ประกอบ (ย่อหน้า/ตัวแบ่ง) — icon บน label ล่าง 3 ปุ่มต่อแถว */
+const BTN_INSERT =
+ "focus-ring flex flex-col items-center gap-1 rounded-md border border-line bg-white px-1 py-2 text-xs text-ink transition hover:border-primary hover:text-primary disabled:pointer-events-none disabled:opacity-50";
+
+function TemplateToolsPanel({ onBusyChange }: { onBusyChange?: (busy: boolean) => void }) {
+ /** null = ยังไม่เคยสแกน (empty state ต่างจาก [] = สแกนแล้วไม่มีช่อง) */
+ const [fields, setFields] = useState