diff --git a/frontend/index.html b/frontend/index.html
index 57288d2..48fd941 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -6744,6 +6744,11 @@ function profilerContentKindForFileType(ft) {
return (ft === 'linear_fixed' || ft === 'cyclic') ? 'record' : 'transparent';
}
+// A fresh record content object seeded with one empty record row.
+function profilerEmptyRecordContent(mode) {
+ return { mode: mode, kind: 'record', records: [{ num: 1, data: '' }] };
+}
+
function profilerValidateProfile(obj) {
if (!obj || typeof obj !== 'object') return 'Not an object';
if (!obj.name) return 'Missing name';
@@ -7065,9 +7070,14 @@ function profilerRenderContentEditor(i, r) {
for (let ri = 0; ri < c.records.length; ri++) {
const rec = c.records[ri];
html += '
' + rec.num + '' +
- '
';
+ '';
+ if (c.records.length > 1) {
+ html += '';
+ }
+ html += '';
}
html += '';
+ html += '';
} else {
html += '';
}
@@ -7091,7 +7101,7 @@ function profilerUpdateRuleFileType(i, value) {
const kind = profilerContentKindForFileType(value);
if (r.content.kind !== kind) {
r.content = kind === 'record'
- ? { mode: r.content.mode, kind: 'record', records: [] }
+ ? profilerEmptyRecordContent(r.content.mode)
: { mode: r.content.mode, kind: 'transparent', expected: '' };
}
}
@@ -7108,7 +7118,7 @@ function profilerSetContent(i, mode) {
} else {
const kind = profilerContentKindForFileType(r.fileType);
r.content = kind === 'record'
- ? { mode: mode, kind: 'record', records: [] }
+ ? profilerEmptyRecordContent(mode)
: { mode: mode, kind: 'transparent', expected: '' };
}
profilerRenderEditor();
@@ -7126,6 +7136,24 @@ function profilerUpdateRecord(i, ri, value) {
if (c && c.records) c.records[ri].data = value;
}
+function profilerAddRecord(i) {
+ if (!profilerDraft) return;
+ const c = profilerDraft.rules[i].content;
+ if (!c || c.kind !== 'record') return;
+ const lastNum = c.records.length ? c.records[c.records.length - 1].num : 0;
+ c.records.push({ num: lastNum + 1, data: '' });
+ profilerRenderEditor();
+}
+
+function profilerDeleteRecord(i, ri) {
+ if (!profilerDraft) return;
+ const c = profilerDraft.rules[i].content;
+ if (!c || c.kind !== 'record') return;
+ if (c.records.length <= 1) return;
+ c.records.splice(ri, 1);
+ profilerRenderEditor();
+}
+
function profilerSaveProfile() {
if (!profilerDraft) return;
profilerDraft.name = document.getElementById('profiler-name').value.trim();
diff --git a/frontend/sw.js b/frontend/sw.js
index 1cbafc7..668c43b 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'otaman-v46';
+const CACHE = 'otaman-v47';
const URLS = [
'index.html',
'help.html',
diff --git a/frontend/tests/profiler.test.js b/frontend/tests/profiler.test.js
index 1a33af4..5a633b8 100644
--- a/frontend/tests/profiler.test.js
+++ b/frontend/tests/profiler.test.js
@@ -21,7 +21,7 @@ function extractFunc(src, name) {
return src.slice(m.index, i + 1);
}
-const FNS = ['profilerNormHex', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerValidateProfile'];
+const FNS = ['profilerNormHex', 'profilerMatch', 'profilerMatchMin', 'profilerMaskPrefix4', 'profilerFileFields', 'profilerContentKindForFileType', 'profilerEmptyRecordContent', 'profilerValidateProfile'];
let code = '';
for (const f of FNS) code += extractFunc(html, f) + '\n';
eval(code);
@@ -92,6 +92,14 @@ test('profilerContentKindForFileType', () => {
assert.strictEqual(profilerContentKindForFileType('df'), 'transparent');
});
+test('profilerEmptyRecordContent seeds one empty record row', () => {
+ const c = profilerEmptyRecordContent('exact');
+ assert.strictEqual(c.kind, 'record');
+ assert.strictEqual(c.mode, 'exact');
+ assert.strictEqual(c.records.length, 1);
+ assert.deepStrictEqual(c.records[0], { num: 1, data: '' });
+});
+
test('profile validation', () => {
assert.strictEqual(profilerValidateProfile(null), 'Not an object');
assert.strictEqual(profilerValidateProfile({ name: 'x' }), 'Missing rules array');