From d313cfa3b3fa14f02890dc1a8ede9c805599a1af Mon Sep 17 00:00:00 2001 From: "Beau Beauchamp, WebTigers" Date: Mon, 17 Aug 2026 09:54:27 -0400 Subject: [PATCH] Skills grid: fix Install/Source click (JSON-in-attribute broke on esc) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking Install or Source threw "Uncaught SyntaxError ... JSON at position 1": the skill entry was stashed as JSON.stringify() in a data-entry attribute via esc(), but the shared esc() (text→innerHTML) does NOT escape double quotes, so the JSON's quotes closed the attribute early and getAttribute returned "{". Carry the entry as individual data-* attrs (source/sourceLabel/name/repo/ref/ path/url) read back into an object — no JSON in markup. Added a local attr() that adds the missing quote-escape, so any field is attribute-safe. Co-Authored-By: Claude Opus 4.8 --- .../agent/views/scripts/skills/index.phtml | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/modules/agent/views/scripts/skills/index.phtml b/modules/agent/views/scripts/skills/index.phtml index 60ce311..887fe95 100644 --- a/modules/agent/views/scripts/skills/index.phtml +++ b/modules/agent/views/scripts/skills/index.phtml @@ -79,6 +79,26 @@ document.addEventListener('DOMContentLoaded', function () { } function toast(res) { ((res && res.messages) || []).forEach(function (m) { TigerDOM.toast(m.message, { type: m.class }); }); } + // A skill entry rides on the button as individual data-* attrs (NOT a JSON blob: esc() doesn't + // quote-escape, so JSON in a double-quoted attribute breaks). attr() adds the missing "-escape. + function attr(s) { return esc(s).replace(/"/g, '"'); } + function entryAttrs(row) { + return ' data-source="' + attr(row.source) + '" data-sourcelabel="' + attr(row.sourceLabel) + '"' + + ' data-name="' + attr(row.name) + '" data-repo="' + attr(row.repo) + '"' + + ' data-ref="' + attr(row.ref) + '" data-path="' + attr(row.path) + '" data-url="' + attr(row.url) + '"'; + } + function readEntry(el) { + return { + source: el.getAttribute('data-source') || '', + sourceLabel: el.getAttribute('data-sourcelabel') || '', + name: el.getAttribute('data-name') || '', + repo: el.getAttribute('data-repo') || '', + ref: el.getAttribute('data-ref') || '', + path: el.getAttribute('data-path') || '', + url: el.getAttribute('data-url') || '' + }; + } + var table = tigerDataTable('#sk-table', { service: { module: 'agent', service: 'skills', action: 'datatable' }, ordering: false, // installed-first pinning is authoritative (server-side) @@ -109,13 +129,10 @@ document.addEventListener('DOMContentLoaded', function () { var h = ''; // View source (always) — key if installed, else the repo/ref/path for a live fetch. h += ' '; if (!row.installed) { - h += ''; } else { h += row.active @@ -130,7 +147,7 @@ document.addEventListener('DOMContentLoaded', function () { // ---- actions (delegated) ---- $('#sk-table tbody').on('click', '.sk-install', function () { - var entry = JSON.parse(this.getAttribute('data-entry') || '{}'); + var entry = readEntry(this); TigerButton.run(this, function () { return api('install', entry); }).then(function (res) { toast(res); table.ajax.reload(null, false); }); @@ -155,7 +172,7 @@ document.addEventListener('DOMContentLoaded', function () { $('#sk-table tbody').on('click', '.sk-src', function () { var b = this; var key = b.getAttribute('data-key'); - srcEntry = key ? null : JSON.parse(b.getAttribute('data-entry') || '{}'); + srcEntry = key ? null : readEntry(b); document.getElementById('sk-src-title').textContent = (b.getAttribute('data-name') || 'SKILL') + '.md'; document.getElementById('sk-src-sub').textContent = b.getAttribute('data-repo') || ''; document.getElementById('sk-src-body').textContent = 'Loading…';