From 2c32c4460715419d737d296efb2a38df5411a3b2 Mon Sep 17 00:00:00 2001 From: Kenneth Hough Date: Mon, 6 Jul 2026 01:56:45 -0500 Subject: [PATCH] feat(342): per-model Column Projection toggle in model Settings tab Adds a "Column Projection" switch to the model detail Settings tab that reads/writes the allow_projection flag inside the model's controller_config JSON column (kyte-php v4.16.0). Mirrors the existing Sensitive toggle: inline auto-save via a partial DataModel PUT (no `name`, so the server skips the table-rename path) with revert-on-failure. Merges into controller_config rather than clobbering so future flags are preserved. - app/model/index.html: second Settings-tab card + switch markup - kyte-shipyard-model-details.js (source): parse/merge/PUT + prefill + revert - i18n en/es/ja/ko: projection_* keys - CHANGELOG: 2.4.0 entry Requires kyte-php v4.16.0+ (controller_config column + projection opt-in). Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 10 ++++++ app/model/index.html | 11 ++++++ assets/i18n/en.json | 6 ++++ assets/i18n/es.json | 6 ++++ assets/i18n/ja.json | 6 ++++ assets/i18n/ko.json | 6 ++++ .../js/source/kyte-shipyard-model-details.js | 35 +++++++++++++++++++ 7 files changed, 80 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9da67bf..e599b2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 2.4.0 + +### Feature: per-model "Column Projection" toggle (KYTE-#342) + +The model **Settings** tab gains a **Column Projection** switch. When enabled, the model's list and read endpoints honor the client's `X-Kyte-Fields` header and return only the requested columns instead of the full row — lighter, faster tables for models with large `text`/`longtext` columns. Off by default (full rows — unchanged behavior). + +The switch reads and writes the `allow_projection` flag inside the model's `controller_config` JSON column (new in kyte-php v4.16.0). It **merges** rather than clobbers, so any other controller-config flags are preserved. Saves inline with the same auto-save + revert-on-failure UX as the Sensitive toggle (a partial PUT that omits `name`, so `DataModelController` skips the table-rename path). + +**Requires kyte-php v4.16.0+** (the `controller_config` column + server-side projection opt-in). Against an older server the toggle persists its value but has no effect. + ## 2.3.0 ### Fix: password reset works in JWT mode (KYTE-#268) diff --git a/app/model/index.html b/app/model/index.html index 639191a..eae04ef 100644 --- a/app/model/index.html +++ b/app/model/index.html @@ -565,6 +565,17 @@
Sensitiv
+
+
+
Column Projection
+

When enabled, list and read endpoints honor the client's requested field list (the X-Kyte-Fields header) and return only those columns instead of the full row — lighter, faster tables for models with large columns. Leave off for controllers whose logic needs the full record.

+
+ + +
+
+
+
diff --git a/assets/i18n/en.json b/assets/i18n/en.json index d1c91aa..60d08cd 100644 --- a/assets/i18n/en.json +++ b/assets/i18n/en.json @@ -431,6 +431,12 @@ "ui.model_detail.sensitive_saving": "Saving...", "ui.model_detail.sensitive_saved": "Saved.", "ui.model_detail.sensitive_save_failed": "Save failed. Reverting.", + "ui.model_detail.projection_card_title": "Column Projection", + "ui.model_detail.projection_description": "When enabled, list and read endpoints honor the client's requested field list (the X-Kyte-Fields header) and return only those columns instead of the full row — lighter, faster tables for models with large columns. Leave off for controllers whose logic needs the full record.", + "ui.model_detail.projection_label": "Allow client-requested column projection", + "ui.model_detail.projection_saving": "Saving...", + "ui.model_detail.projection_saved": "Saved.", + "ui.model_detail.projection_save_failed": "Save failed. Reverting.", "ui.site_detail.back_to_sites": "Back to Sites", "ui.site_detail.sidebar.content": "Content", diff --git a/assets/i18n/es.json b/assets/i18n/es.json index b454d52..def536d 100644 --- a/assets/i18n/es.json +++ b/assets/i18n/es.json @@ -431,6 +431,12 @@ "ui.model_detail.sensitive_saving": "Guardando...", "ui.model_detail.sensitive_saved": "Guardado.", "ui.model_detail.sensitive_save_failed": "Error al guardar. Revirtiendo.", + "ui.model_detail.projection_card_title": "Proyección de columnas", + "ui.model_detail.projection_description": "Cuando está habilitado, los endpoints de lista y lectura respetan la lista de campos solicitada por el cliente (el encabezado X-Kyte-Fields) y devuelven solo esas columnas en lugar de la fila completa, lo que hace que las tablas sean más ligeras y rápidas para modelos con columnas grandes. Déjelo desactivado para controladores cuya lógica necesita el registro completo.", + "ui.model_detail.projection_label": "Permitir proyección de columnas solicitada por el cliente", + "ui.model_detail.projection_saving": "Guardando...", + "ui.model_detail.projection_saved": "Guardado.", + "ui.model_detail.projection_save_failed": "Error al guardar. Revirtiendo.", "ui.site_detail.back_to_sites": "Volver a Sitios", "ui.site_detail.sidebar.content": "Contenido", diff --git a/assets/i18n/ja.json b/assets/i18n/ja.json index 6397399..3a83e2a 100644 --- a/assets/i18n/ja.json +++ b/assets/i18n/ja.json @@ -436,6 +436,12 @@ "ui.model_detail.sensitive_saving": "保存中...", "ui.model_detail.sensitive_saved": "保存しました。", "ui.model_detail.sensitive_save_failed": "保存に失敗しました。元に戻します。", + "ui.model_detail.projection_card_title": "列プロジェクション", + "ui.model_detail.projection_description": "有効にすると、一覧および読み取りエンドポイントはクライアントが要求したフィールド一覧(X-Kyte-Fields ヘッダー)を尊重し、行全体ではなく該当する列のみを返します。大きな列を持つモデルの一覧が軽く高速になります。完全なレコードを必要とするコントローラーではオフのままにしてください。", + "ui.model_detail.projection_label": "クライアントが要求する列プロジェクションを許可", + "ui.model_detail.projection_saving": "保存中...", + "ui.model_detail.projection_saved": "保存しました。", + "ui.model_detail.projection_save_failed": "保存に失敗しました。元に戻します。", "ui.site_detail.back_to_sites": "サイト一覧に戻る", "ui.site_detail.sidebar.content": "コンテンツ", diff --git a/assets/i18n/ko.json b/assets/i18n/ko.json index 07d969a..db7e594 100644 --- a/assets/i18n/ko.json +++ b/assets/i18n/ko.json @@ -431,6 +431,12 @@ "ui.model_detail.sensitive_saving": "저장 중...", "ui.model_detail.sensitive_saved": "저장됨.", "ui.model_detail.sensitive_save_failed": "저장 실패. 되돌리는 중.", + "ui.model_detail.projection_card_title": "열 프로젝션", + "ui.model_detail.projection_description": "활성화하면 목록 및 읽기 엔드포인트가 클라이언트가 요청한 필드 목록(X-Kyte-Fields 헤더)을 따르며 전체 행 대신 해당 열만 반환합니다. 큰 열이 있는 모델의 테이블이 더 가볍고 빨라집니다. 전체 레코드가 필요한 컨트롤러에서는 꺼 두세요.", + "ui.model_detail.projection_label": "클라이언트 요청 열 프로젝션 허용", + "ui.model_detail.projection_saving": "저장 중...", + "ui.model_detail.projection_saved": "저장됨.", + "ui.model_detail.projection_save_failed": "저장 실패. 되돌리는 중.", "ui.site_detail.back_to_sites": "사이트 목록으로 돌아가기", "ui.site_detail.sidebar.content": "콘텐츠", diff --git a/assets/js/source/kyte-shipyard-model-details.js b/assets/js/source/kyte-shipyard-model-details.js index f43d0a0..b26e23b 100644 --- a/assets/js/source/kyte-shipyard-model-details.js +++ b/assets/js/source/kyte-shipyard-model-details.js @@ -1029,6 +1029,41 @@ document.addEventListener('KyteInitialized', function(e) { }); }); + // Settings tab (KYTE-#342): allow_projection lives inside the + // controller_config JSON column. Parse it once, then toggle only + // the allow_projection key so any other controller_config flags + // are preserved, and PUT the merged JSON (partial update — no + // `name`, so DataModelController skips the table-rename path). + let controllerConfig = {}; + try { + const raw = r.data[0].controller_config; + if (raw) { + const parsed = (typeof raw === "string") ? JSON.parse(raw) : raw; + if (parsed && typeof parsed === "object") controllerConfig = parsed; + } + } catch (e) { + controllerConfig = {}; + } + $("#model-setting-allow-projection").prop("checked", controllerConfig.allow_projection === true); + $("#model-setting-allow-projection").off("change.projection").on("change.projection", function() { + const newVal = this.checked; + const $status = $("#model-projection-save-status"); + const savingMsg = (window.kyteI18n && window.kyteI18n.t('ui.model_detail.projection_saving')) || 'Saving...'; + const savedMsg = (window.kyteI18n && window.kyteI18n.t('ui.model_detail.projection_saved')) || 'Saved.'; + const failMsg = (window.kyteI18n && window.kyteI18n.t('ui.model_detail.projection_save_failed')) || 'Save failed. Reverting.'; + $status.removeClass("text-success text-danger").addClass("text-muted").html('' + savingMsg); + // Merge into a copy so a failed save reverts cleanly. + const merged = Object.assign({}, controllerConfig, { allow_projection: newVal }); + _ks.put("DataModel", "id", modelIdx, { controller_config: JSON.stringify(merged) }, null, [], function() { + controllerConfig = merged; // commit only on success + $status.removeClass("text-muted text-danger").addClass("text-success").html('' + savedMsg); + setTimeout(() => $status.html(""), 2500); + }, function() { + $("#model-setting-allow-projection").prop("checked", !newVal); + $status.removeClass("text-muted text-success").addClass("text-danger").html('' + failMsg); + }); + }); + // Get i18n instance for translations const i18n = window.kyteI18n; const t = (key, fallback) => i18n ? i18n.t(key) : fallback;