diff --git a/modules/comment/assets/js/comment.admin.js b/modules/comment/assets/js/comment.admin.js index 04bde3cb..abb0d2cc 100644 --- a/modules/comment/assets/js/comment.admin.js +++ b/modules/comment/assets/js/comment.admin.js @@ -35,7 +35,7 @@ var status = document.getElementById('comment-status'); grid = tigerDataTable('#comment-grid', { - service: { module: 'comment', service: 'comment', method: 'datatable' }, + service: { module: 'comment', service: 'comment', action: 'datatable' }, extraData: function () { return { status: status ? status.value : 'pending' }; }, order: [[3, 'desc']], columns: [ diff --git a/modules/comment/controllers/AdminController.php b/modules/comment/controllers/AdminController.php index d8e50629..1688afdd 100644 --- a/modules/comment/controllers/AdminController.php +++ b/modules/comment/controllers/AdminController.php @@ -22,8 +22,9 @@ public function indexAction() throw new Zend_Controller_Action_Exception('Comments are disabled', 404); } - $this->view->title = 'Comments — Tiger Admin'; - $this->view->statuses = Tiger_Model_Comment::STATUSES; + $this->view->title = 'Comments — Tiger Admin'; + $this->view->statuses = Tiger_Model_Comment::STATUSES; + $this->view->useDataTables = true; // the layout loads jQuery + DataTables when set // The AI spam control only APPEARS when there is a live agent to run it — an always-visible // toggle that silently does nothing is worse than no toggle. diff --git a/modules/comment/services/Comment.php b/modules/comment/services/Comment.php index c38bedab..e97b3b6a 100644 --- a/modules/comment/services/Comment.php +++ b/modules/comment/services/Comment.php @@ -282,7 +282,7 @@ public function datatable(array $params): void if (!$this->_isAllowed('moderate')) { $this->_error('core.api.error.not_allowed'); return; } if (!$this->_enabled()) { return; } - $dt = $this->_dtParams(); + $dt = $this->_dtParams($params); $status = (string) ($params['status'] ?? Tiger_Model_Comment::STATUS_PENDING); $rows = (new Tiger_Model_Comment())->byStatus($status, 500); diff --git a/modules/newsletter/assets/js/newsletter.admin.js b/modules/newsletter/assets/js/newsletter.admin.js index 7c4f2ad8..21eabd2e 100644 --- a/modules/newsletter/assets/js/newsletter.admin.js +++ b/modules/newsletter/assets/js/newsletter.admin.js @@ -31,7 +31,7 @@ var status = document.getElementById('newsletter-status'); grid = tigerDataTable('#newsletter-grid', { - service: { module: 'newsletter', service: 'subscribe', method: 'datatable' }, + service: { module: 'newsletter', service: 'subscribe', action: 'datatable' }, extraData: function () { return { status: status ? status.value : '' }; }, order: [[4, 'desc']], columns: [ diff --git a/modules/newsletter/controllers/AdminController.php b/modules/newsletter/controllers/AdminController.php index f75a5913..0562333d 100644 --- a/modules/newsletter/controllers/AdminController.php +++ b/modules/newsletter/controllers/AdminController.php @@ -18,7 +18,8 @@ public function init() /** The subscriber list. */ public function indexAction() { - $this->view->title = 'Newsletter — Tiger Admin'; - $this->view->statuses = Newsletter_Model_Subscriber::STATUSES; + $this->view->title = 'Newsletter — Tiger Admin'; + $this->view->statuses = Newsletter_Model_Subscriber::STATUSES; + $this->view->useDataTables = true; // the layout loads jQuery + DataTables when set } } diff --git a/modules/system/controllers/SitesController.php b/modules/system/controllers/SitesController.php index 4459eea6..d0e61d55 100644 --- a/modules/system/controllers/SitesController.php +++ b/modules/system/controllers/SitesController.php @@ -19,9 +19,10 @@ public function init() /** `/system/sites` — the host → org mappings grid + an add-domain form. */ public function indexAction() { - $this->view->title = 'Sites — Tiger Admin'; - $this->view->form = new System_Form_Site(); - $this->view->orgCount = (int) (new Tiger_Model_Org())->fetchAll( + $this->view->title = 'Sites — Tiger Admin'; + $this->view->useDataTables = true; // the layout loads jQuery + DataTables when set + $this->view->form = new System_Form_Site(); + $this->view->orgCount = (int) (new Tiger_Model_Org())->fetchAll( (new Tiger_Model_Org())->activeSelect() )->count(); } diff --git a/tests/ui/README.md b/tests/ui/README.md index 1f29f313..67c1d408 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -25,10 +25,18 @@ test account. Without it the authenticated scenarios are skipped; everything ano ## Exit codes -Both probes exit non-zero on a real failure, so they drop straight into CI or a cron. -A check that could not run (e.g. reCAPTCHA refused a tokenless client) reports **GATED**, not -PASS — a vacuous green is worse than no check. +Both probes exit non-zero on a real failure, so either drops into a script when you want that. +A check that could not run reports **GATED**, not PASS — a vacuous green is worse than no check. +With reCAPTCHA switched off on the test install (`tiger.recaptcha.enabled = 0`) nothing is gated; +see the probe's own footer. -## Promotion +## Scope — scenarios, by decision -A confirmed finding becomes a spec in `regression/` so it cannot come back silently. +These are **scenarios, run on demand.** There is deliberately no CI job, no promoted-spec +directory and no Playwright suite to maintain: the cost of a regression harness is ongoing and +the value here is finding real defects on real screens, which is what an on-demand pass does. + +So a confirmed finding becomes a **Jira ticket** (labelled `functional-user-test` + `ui-probe`), +not a test file. Where a whole CLASS of defect turns out to be statically detectable, the cheap +exhaustive home for it is a check in `paradigm-scan.php` — one scan, every view, milliseconds — +rather than a scenario per screen. diff --git a/themes/puma/assets/js/tiger.datatable.js b/themes/puma/assets/js/tiger.datatable.js index 473d692c..9baed0a0 100644 --- a/themes/puma/assets/js/tiger.datatable.js +++ b/themes/puma/assets/js/tiger.datatable.js @@ -29,10 +29,28 @@ var $ = window.jQuery; if (!$ || !$.fn || !$.fn.DataTable) { return null; } + // DataTables reports its own errors with a browser alert() by default, which breaks the house + // rule (no alert/confirm/prompt — AGENTS.md) and, worse, hides the failure from the console, + // where our cheapest detector looks. 'throw' routes it to the console instead: visible to a + // developer, visible to the browser probe, and it can never block the page behind a modal. + if ($.fn.dataTable && $.fn.dataTable.ext) { $.fn.dataTable.ext.errMode = 'throw'; } + opts = opts || {}; var svc = opts.service || {}; var extra = (typeof opts.extraData === 'function') ? opts.extraData : function () { return {}; }; + // The /api message names module + service + ACTION. `action` is the key this helper reads; a + // grid that says `method` instead sends no action at all, and the gateway can only refuse it. + // Refusing here names the real cause at the call site rather than letting DataTables report a + // generic warning about a request that was never going to work. + if (!svc.module || !svc.service || !svc.action) { + window.console && console.error( + 'tigerDataTable(' + selector + '): service needs { module, service, action } — got ' + + JSON.stringify(svc) + (svc.method ? ' (use `action`, not `method`)' : '') + ); + return null; + } + // Sensible defaults; caller's opts win. `service`/`extraData` are ours, not DT's. // stateSave persists page / length / search / order per grid (localStorage, keyed by table id + // path) so a grid remembers where you were across reloads + navigation. A caller can opt out with