diff --git a/.gitignore b/.gitignore
index ab5ea70..25120aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
node_modules/
lib/
+lib-commonjs/
dist/
+release/
+jest-output/
temp/
*.sppkg
.heft/
diff --git a/docs/CT-PORT.md b/docs/CT-PORT.md
new file mode 100644
index 0000000..927354a
--- /dev/null
+++ b/docs/CT-PORT.md
@@ -0,0 +1,174 @@
+# CT — перенос в ct-forms-portal (Фаза 3)
+
+Порядок (утверждён): **Этап 1 перенос 1:1 → Этап 2 декомпозиция под структуру KPS → Этап 3 перекраска на ui-kit.**
+Правило: сохранить 100% поведения; build после каждого этапа. Эталон структуры — `forms/kps/`.
+Связано: `docs/REPAINT-MAP.md` (перекраска), `docs/BEHAVIORS.md`.
+
+Источник: `ct-create-form/src/webparts/ctCreateForm/`. CT = 6953 стр, god-компоненты.
+
+## Этап 1 — перенос 1:1 — ✅ ЗАВЕРШЁН (heft build зелёный)
+1. ✅ Скопированы `components/` и `services/` → `forms/ct/{components,services}/` (17 файлов, 1:1).
+2. ✅ Правки для сборки в портале (минимум, поведение не менялось):
+ - `CtService`: конструктор `(context, siteUrl?)`, `baseUrl = siteUrl ?? absoluteUrl` (как KPS).
+ `kpFolder` тоже параметризован через `siteUrl` (было жёстко на `context.pageContext.web.serverRelativeUrl`).
+ - `ICtCreateFormProps` + `siteUrl?`; `CtCreateForm` → `new CtService(context, siteUrl)`.
+ - Web-part-специфичных зависимостей (loc strings) в components/services не было — ничего убирать не пришлось.
+3. ✅ Обвязка портала:
+ - `ctDocumentConfig.ts`: `inPortal: true` + `buildInPortalParams: (row) => ({ [row.status==='Черновик'?'ctId':'ctid']: row.id })`
+ (сверено с `useCtForm.ts` — читает именно `ctId`/`ctid`, совпало 1:1). siteUrl уже `/sites/sed`.
+ - `CtFormsPortal.tsx`: ветка `form==='ct'` → `` + «← К реестру» (по образцу KPS-ветки).
+4. ✅ `heft build` зелёный.
+
+**Открытие при переносе:** CT уже частично зависел от `@tnsu/ui-kit@^0.4.0` — 3 файла
+(`Step1PositionSelection.tsx`, `Step2CommercialOffers.tsx`, `CurrencyRatesForm.tsx`) уже используют
+`Button`/`Alert` из кита. API совпадает 1:1 с текущим `0.11.0` (variant="secondary"/"accent", Alert
+variant="err") — конфликтов не было, работает как есть. Ускорит Этап 3.
+
+## Этап 2 — декомпозиция (карта резки god-компонентов)
+
+**Статус:** [x] Step2CommercialOffers ✅ | [x] Step4Approval ✅ | [~] Step3ComparisonTable (в работе)
+
+### [~] Step3ComparisonTable (2217) — В РАБОТЕ, по чекпоинтам
+- ✅ **CP1 согласование** (build ок): `useStep3Approval.ts` (голосование/делегат, 1:1) + `InlineApprovalChain.tsx`
+ (вью цепочки + диалоги решение/делегат + баннер notifyWarning; хелперы APPROVAL_STATUS_*). Контейнер 2217→1918.
+- ✅ **CP2 AI/PDF** (build ок): `useAiComment.ts` (aiComment/isAiLoading/aiError/isPdfLoading/showAiPromptDialog +
+ handleAiComment/handleGeneratePdfReport/handleDeleteAiComment + loadCtComment-effect + decodeHtmlEntities,
+ buildPayload передаётся аргументом из контейнера — матричные данные не копируются в хук) + `AiCommentBlock.tsx`
+ (только блок отображения, диалог-промпт остался в контейнере — как у KPS). PDF-ошибка идёт через колбэк
+ `onPdfError` в общий `saveError` (сохранили родной баннер).
+- ✅ **CP3 хелперы/типы** (build ок): типы (ExtraSupplier/ExtraPosition/PositionRow/CellEdit/EditableOffers) →
+ `matrixTypes.ts`; константы (SUPPLIER_STATUS_OPTIONS/DELIVERY_TYPE_OPTIONS) + чистые ф-ции
+ (formatKzt/Num/Qty/round2Str/getSubCols/computeOffer/buildOfferKey) → `matrixHelpers.ts`.
+ `extraIdCounter` (мутабельный) оставлен в контейнере — уедет вместе с CP4.
+- **Контейнер после CP1-3: 2217 → 1735 строк (−22%).**
+- ✅ **CP4 ядро матрицы** (build ок): весь стейт/рефы(~15)/эффекты(loadCriteria/loadOffers/refs-sync/beforeunload/
+ unmount-persist)/мемо(suppliersWithOffer/allPositions/supplierCurrencies/computedOffers/minTotalByPosition/
+ recommendedSupplierId)/все update*/add*/delete*-хендлеры/handleSave → `useStep3Matrix.ts` (856, перенос
+ ВЕРБАТИМ). В контейнере — **деструктуризация** результата, поэтому render (984) НЕ менялся. `CriteriaEntry`
+ переехал в matrixTypes (общий). approval+ai хуки остались в контейнере (берут значения из деструктуризации).
+- **Контейнер: 2217 → 984 строки (−56%). Логика матрицы вся в хуке. Этап 2 для CT ЗАВЕРШЁН.**
+- ✅ **CP4 happy-path проверен в workbench**: полный проход по всем 4 шагам (создание СТ → загрузка КП → матрица →
+ согласование) прошёл без ошибок. Основной риск (грубый регресс/краш) снят.
+ ⚠️ Не проверены детально: bundle-строки (комплект), перевод ед. изм. (тумблер), доп.расходы/логистика,
+ F5 (beforeunload-persist), критерии/AI-комментарий, делегат/замена в согласовании. Смотреть при случае,
+ не блокирует дальнейшую работу.
+- ⏭️ **CP4 матрица-ядро** (САМЫЙ РИСК): ~15 рефов + эффекты (loadCriteria/loadOffers/beforeunload/unmount-persist) +
+ мемо (suppliersWithOffer/allPositions/supplierCurrencies/computedOffers/minTotalByPosition/recommendedSupplierId) +
+ все update*/add*/delete*-хендлеры + handleSave → `useStep3Matrix.ts` (перенос ВЕРБАТИМ, не переписывать!).
+ ⚠️ Клубок рефов; компилятор не ловит регресс поведения. ЖЕЛАТЕЛЬНО делать при возможности workbench-теста
+ (после провижининга списков). Рендер таблицы (thead/tbody/итоги/критерии) дробить на ComparisonMatrix/
+ MatrixRowView/OfferCells/CriteriaTable/AddExtraRow — на Этапе 3 вместе с перекраской.
+Метод: чисто механический перенос блоков (логику не трогать), Fluent оставляем (перекраска — Этап 3),
+build после каждого god-компонента.
+**Уточнённая граница Этап2/Этап3:** Этап 2 = вынос ЛОГИКИ (хуки/persist), «раз-god-ивание». Финальное
+дробление оставшейся ВЬЮХИ на под-компоненты (диалоги/списки) + размеры <200 — на Этапе 3 вместе с
+перекраской (диалоги всё равно → ui-kit Dialog), чтобы не трогать файлы дважды.
+
+### ✅ Step4Approval (886) — СДЕЛАНО (build ок)
+886 → `useStep4Approval.ts` (вся логика: chain-ops, add/delete/replace, handleActionConfirm, send/incore/restart,
+производные флаги + удобные методы selectAddSuggestion/cancelAddRow/openReplace/…) + view `Step4Approval.tsx`
+(~370, только разметка + helpers STATUS_LABELS/getStatusClass/formatDate; Fluent). Вьюху добьём на Этапе 3.
+
+### ✅ Step2CommercialOffers (597) — СДЕЛАНО (build ок, 2 чекпоинта)
+597 → контейнер `Step2CommercialOffers.tsx` (~155) + `kpTypes.ts` + `useKpProcessing.ts` (конвейер Syntex/Flow)
++ `step2Persist.ts` (handleSave→функция, возвращает patch) + вьюхи `SuppliersWithoutKp.tsx` (владеет add-стейтом)
+/ `UploadedKpList.tsx`. Одно семантич. уточнение: в persist `formDataRef.current`→`formData` (в момент save совпадают).
+
+
+### Step3ComparisonTable.tsx (2217) → по образцу `Step3BuildCt/*` + отдать approval в Step4
+| Секции CT (строки) | Куда (аналог KPS) |
+|---|---|
+| 25–179 типы/хелперы/формат | `types/domain.ts`, `utils/format.ts`, `useStep3Matrix.ts` |
+| 196–435 стейт матрицы | `useStep3Matrix.ts` |
+| 285–394 approval-хендлеры | → Step4 `useStep4Approval.ts` |
+| 396–405, 947–1017 AI/PDF | `useAiComment.ts` + `AiCommentBlock.tsx` |
+| 439–604 useEffect загрузка/persist | `step3Persist.ts` + `useStep3Matrix.ts` |
+| 606–706 useMemo производные | `useStep3Matrix.ts` |
+| 708–789 редактируемые ячейки | `OfferCells.tsx` |
+| 791–945 хендлеры строк | `AddExtraRow.tsx` + `useStep3Matrix.ts` |
+| 1019–1193 handleSave | `step3Persist.ts` |
+| 1197–1239 тулбар + тумблер ед.изм. | `Step3BuildCt.tsx` (+ доменное `unitConversion`) |
+| 1241–1337 thead | `ComparisonMatrix.tsx` |
+| 1339–1806 tbody + итоги | `MatrixRowView.tsx` + `OfferCells.tsx` + `ComparisonMatrix.tsx` |
+| 1807–1864 инлайн-добавление | `AddExtraRow.tsx` |
+| 1865–2029 критерии + AI + PDF | `CriteriaTable.tsx` + `AiCommentBlock.tsx` |
+| 2030–2114 инлайн-цепочка (Teams) | → Step4 `InlineApprovalChain.tsx` |
+| 2115–2211 диалоги (делегирование/решение) | ui-kit `Dialog` в `Step4Approval.tsx` |
+
+### Step4Approval.tsx (886) → `useStep4Approval.ts` + `Step4Approval.tsx` + `InlineApprovalChain.tsx`
+(стейт+манипуляции цепочкой 94–352, handleActionConfirm/Send/Incore/Restart 239–474 → хук; render 476–763; диалоги 764–885 → ui-kit Dialog)
+
+### Step2CommercialOffers.tsx (597) → `useKpProcessing.ts`/`kpPipeline.ts`/`kpOffers.ts`/`step2Persist.ts` + view (`Step2LoadKp`,`KpFileList`,`CurrencyRatesForm`,`SuppliersWithoutKp`,`UploadedKpList`)
+
+### CtService.ts (1532) — ввести `siteUrl?` (крит.) + при желании обобщённые CRUD-примитивы как в KPS. Создать `common/*` (StepShell/FormHeader/Pill) + единый `StepProps`.
+
+## Доменные отличия CT (НЕ терять при декомпозиции!)
+- Шаг 1 — из **SP-списка** (`fetchRequests(days)`), не из TNSU API (KPS).
+- Колонки позиций: **disciplineCode (марка), smrType (SMR), agskCode**, uniqueCode, organization.
+- `CT.number` — строковый, `generateCtNumber(projectName)` (per-project).
+- `utils/unitConversion.ts` — пересчёт в ед. изм. заявки (тумблер) — нет в KPS.
+- `SUPPLIER_STATUS_OPTIONS` (Завод/Дистрибьютор…) — нет в KPS.
+- CurrencyRate/SupplierOffer — иные поля (rateToKzt; deliveryCostKzt/additionalCostsKzt/totalWithExtraKzt/pricePerUnitKzt).
+- `getCurrentUserCompany()==='astel-k'` → `isRestrictedView` (скрытие цен).
+
+## Этап 3 — перекраска (карта A/B/C из REPAINT-MAP)
+Fluent в CT: PrimaryButton/DefaultButton→Button, TextField→TextInput/TextArea, MessageBar→Alert,
+IconButton/Icon, Spinner→Spinner, Dialog→ui-kit Dialog, Checkbox→Checkbox, статусы→Pill/Badge.
+Особые: **Toggle** (пересчёт ед.изм.) и **NormalPeoplePicker** (делегирование) — прямых аналогов в ките нет:
+Toggle → ui-kit Toggle (есть!); people picker → TextInput + searchUsers()+suggestions (как KPS Step4).
+
+**✅✅ Этап 3 ЗАВЕРШЁН (механическая перекраска) — Fluent полностью удалён из CT (0 импортов @fluentui
+во всей `forms/ct/`), heft build ожидается пользователем.**
+- Все 12 файлов с Fluent переведены на `@tnsu/ui-kit`: RequestsTable/FileUploadArea/UploadedKpList
+ (Checkbox/IconButton), SuppliersWithoutKp/CurrencyRatesForm/Step1PositionSelection/
+ Step2CommercialOffers/AiCommentBlock (TextInput/Spinner/Alert/IconButton/FormField),
+ useStep3Approval/InlineApprovalChain/Step4Approval (делегат/замена — NormalPeoplePicker заменён
+ на паттерн KPS: query+debounce(300ms)+suggestions+select, `useStep4Approval`/`useStep3Approval`
+ получили `handleAddQueryChange(val)`/`setDelegateQuery(val)` вместо Fluent-сигнатуры (event, val)),
+ и главный контейнер `Step3ComparisonTable.tsx` (984 строки, вся матрица/критерии/футер/AI-диалог).
+- Правило замен применено: `TextField`→`TextInput`/`TextArea` (multiline→TextArea rows=1, автовысоты
+ нет — принято как в KPS `CriteriaTable`); без `className`→обёрнуто в `
`; `Toggle` без `label`→
+ обёрнут в `