diff --git a/page-data-html-detect.mjs b/page-data-html-detect.mjs
index 623feab..3a6dfbf 100644
--- a/page-data-html-detect.mjs
+++ b/page-data-html-detect.mjs
@@ -47,13 +47,17 @@ export function detectPageDataDatasetUsageFromHtml(html) {
// this API must therefore only require the safe, default capability.
remember(match[1], { softDelete: true });
}
- for (const match of text.matchAll(/\.insertRow\(\s*([A-Za-z_$][\w$]*)/g)) {
+ // `${...}` inside an HTML template literal is a row-id interpolation, not a
+ // JavaScript identifier passed as the dataset argument. Without this guard,
+ // snippets such as `onclick="deleteRow(${row.id})"` are detected as a `$`
+ // dataset and can make the whole workspace binding pass fail.
+ for (const match of text.matchAll(/\.insertRow\(\s*(?!\$\{)([A-Za-z_$][\w$]*)/g)) {
remember(constants.get(match[1]) ?? match[1], { insert: true });
}
- for (const match of text.matchAll(/\.listRows\(\s*([A-Za-z_$][\w$]*)/g)) {
+ for (const match of text.matchAll(/\.listRows\(\s*(?!\$\{)([A-Za-z_$][\w$]*)/g)) {
remember(constants.get(match[1]) ?? match[1], { read: true });
}
- for (const match of text.matchAll(/\.deleteRow\(\s*([A-Za-z_$][\w$]*)/g)) {
+ for (const match of text.matchAll(/\.deleteRow\(\s*(?!\$\{)([A-Za-z_$][\w$]*)/g)) {
remember(constants.get(match[1]) ?? match[1], { softDelete: true });
}
diff --git a/page-data-html-detect.test.mjs b/page-data-html-detect.test.mjs
index 1ddcb1a..15a86b6 100644
--- a/page-data-html-detect.test.mjs
+++ b/page-data-html-detect.test.mjs
@@ -44,6 +44,17 @@ test('detectPageDataDatasetUsageFromHtml resolves dataset constants', () => {
assert.deepEqual(usage.get('reading_survey'), { read: true });
});
+test('detectPageDataDatasetUsageFromHtml ignores template-literal row id interpolation', () => {
+ const html = `
+ const DATASET = 'daily_log';
+ await client.listRows(DATASET, { limit: 200 });
+ rows.map((row) => \`\`);
+ `;
+ const usage = detectPageDataDatasetUsageFromHtml(html);
+ assert.deepEqual([...usage.entries()], [['daily_log', { read: true }]]);
+ assert.equal(usage.has('$'), false);
+});
+
test('assertPolicyMatchesHtmlDatasets rejects mismatched dataset names', () => {
const html = `await c.insertRow('tkmind_exp_survey', {});`;
assert.throws(
diff --git a/page-data-workspace-ensure.mjs b/page-data-workspace-ensure.mjs
index 25643d6..9ac54b3 100644
--- a/page-data-workspace-ensure.mjs
+++ b/page-data-workspace-ensure.mjs
@@ -137,20 +137,23 @@ export async function ensurePageDataHtmlPagesBound({
continue;
}
- const assessment = await assessPageDataHtmlBinding({
- pool,
- userId,
- publishDir: workspaceRoot,
- relativePath: file.relativePath,
- html: file.content,
- findPageByRelativePath,
- });
- if (assessment.bound) {
- skipped.push({ relativePath: file.relativePath, reason: 'already_bound' });
- continue;
- }
-
try {
+ // Keep every page's assessment and repair isolated. A malformed legacy
+ // page must be reported in `errors`, not reject the entire MindSpace page
+ // listing that invokes this best-effort maintenance pass.
+ const assessment = await assessPageDataHtmlBinding({
+ pool,
+ userId,
+ publishDir: workspaceRoot,
+ relativePath: file.relativePath,
+ html: file.content,
+ findPageByRelativePath,
+ });
+ if (assessment.bound) {
+ skipped.push({ relativePath: file.relativePath, reason: 'already_bound' });
+ continue;
+ }
+
for (const datasetName of usage.keys()) {
await ensureRegisteredDatasetFromHtml({
workspaceRoot,