@duckduckgo/autoconsent 15.0.0 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.github/dependabot.yml +0 -3
  2. package/.github/workflows/release.yml +0 -4
  3. package/AGENTS.md +3 -3
  4. package/CHANGELOG.md +32 -0
  5. package/build.sh +3 -2
  6. package/dist/addon-firefox/background.bundle.js +8 -24
  7. package/dist/addon-firefox/compact-rules.json +1 -1
  8. package/dist/addon-firefox/content.bundle.js +184 -686
  9. package/dist/addon-firefox/manifest.json +1 -1
  10. package/dist/addon-firefox/popup.bundle.js +196 -25
  11. package/dist/addon-firefox/popup.html +98 -5
  12. package/dist/addon-firefox/rule-index.json +1 -0
  13. package/dist/addon-firefox/rules.json +1 -1
  14. package/dist/addon-mv3/background.bundle.js +8 -24
  15. package/dist/addon-mv3/compact-rules.json +1 -1
  16. package/dist/addon-mv3/content.bundle.js +184 -686
  17. package/dist/addon-mv3/manifest.json +1 -1
  18. package/dist/addon-mv3/popup.bundle.js +196 -25
  19. package/dist/addon-mv3/popup.html +98 -5
  20. package/dist/addon-mv3/rule-index.json +1 -0
  21. package/dist/addon-mv3/rules.json +1 -1
  22. package/dist/autoconsent.cjs.js +184 -685
  23. package/dist/autoconsent.esm.js +184 -685
  24. package/dist/autoconsent.playwright.js +185 -689
  25. package/dist/autoconsent.standalone.js +3625 -0
  26. package/dist/types/cmps/base.d.ts +3 -3
  27. package/dist/types/cmps/trustarc-top.d.ts +1 -1
  28. package/dist/types/eval-handler.d.ts +3 -3
  29. package/dist/types/heuristics.d.ts +2 -2
  30. package/dist/types/types.d.ts +10 -23
  31. package/dist/types/web.d.ts +0 -9
  32. package/docs/api.md +1 -8
  33. package/docs/puppeteer.md +1 -3
  34. package/docs/rule-syntax.md +0 -17
  35. package/eslint.config.mjs +1 -1
  36. package/lib/cmps/base.ts +5 -6
  37. package/lib/cmps/conversant.ts +7 -4
  38. package/lib/cmps/trustarc-top.ts +1 -1
  39. package/lib/cmps/uniconsent.ts +4 -4
  40. package/lib/eval-handler.ts +6 -3
  41. package/lib/heuristics.ts +15 -15
  42. package/lib/types.ts +9 -21
  43. package/lib/utils.ts +13 -9
  44. package/lib/web.ts +3 -53
  45. package/package.json +4 -17
  46. package/playwright/content.ts +1 -2
  47. package/readme.md +3 -6
  48. package/rules/autoconsent/dreamlab-cmp.json +13 -0
  49. package/rules/build.ts +4 -23
  50. package/rules/compact-rules.json +1 -1
  51. package/rules/rule-index-builder.ts +58 -0
  52. package/rules/rule-index.json +1 -0
  53. package/rules/rules.json +1 -1
  54. package/standalone/content.ts +101 -0
  55. package/tests/dreamlab-cmp.spec.ts +3 -0
  56. package/tests/quantcast.spec.ts +0 -6
  57. package/tests-wtr/heuristics/get-actionable-popups.ts +13 -13
  58. package/tests-wtr/heuristics/heuristic-cmp.ts +32 -33
  59. package/tests-wtr/lifecycle/find-cmp.ts +5 -8
  60. package/tests-wtr/web/do-opt-in.test.ts +0 -1
  61. package/tests-wtr/web/do-opt-out.test.ts +0 -1
  62. package/tests-wtr/web/do-self-test.test.ts +0 -1
  63. package/tests-wtr/web/filter-cmps.test.ts +0 -1
  64. package/tests-wtr/web/receive-message-callback.test.ts +1 -2
  65. package/tests-wtr/web/update-state.test.ts +1 -3
  66. package/tsconfig.build.json +1 -1
  67. package/.github/workflows/update-filterlist.yml +0 -42
  68. package/dist/autoconsent.extra.cjs.js +0 -15709
  69. package/dist/autoconsent.extra.esm.js +0 -15641
  70. package/dist/types/cmps/consentomatic.d.ts +0 -34
  71. package/dist/types/consentomatic/index.d.ts +0 -2
  72. package/dist/types/consentomatic/tools.d.ts +0 -10
  73. package/dist/types/filterlist-engine.d.ts +0 -2
  74. package/dist/types/filterlist-utils.d.ts +0 -4
  75. package/dist/types/web-extra.d.ts +0 -11
  76. package/lib/cmps/consentomatic.ts +0 -97
  77. package/lib/consentomatic/index.ts +0 -252
  78. package/lib/consentomatic/tools.ts +0 -199
  79. package/lib/filterlist-engine.ts +0 -5
  80. package/lib/filterlist-utils.ts +0 -45
  81. package/lib/web-extra.ts +0 -116
  82. package/rules/consentomatic.json +0 -1
  83. package/rules/filterlist.txt +0 -18440
  84. package/rules/filterlists/overrides.txt +0 -4
  85. package/scripts/compile-filterlist.mjs +0 -63
  86. package/scripts/fetch-easylist.sh +0 -25
  87. package/scripts/rebuild-filterlist.mjs +0 -216
  88. package/tests/oil.spec.ts +0 -7
  89. package/tests/springer.spec.ts +0 -7
  90. package/tests/wordpressgdpr.spec.ts +0 -6
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Autoconsent",
4
- "version": "2026.6.19",
4
+ "version": "2026.6.23",
5
5
  "background": {
6
6
  "scripts": [
7
7
  "background.bundle.js"
@@ -761,14 +761,6 @@
761
761
  return parseImpl(url, 5, suffixLookup, options, getEmptyResult());
762
762
  }
763
763
 
764
- // lib/types.ts
765
- var PopupHandlingModes = {
766
- None: -1,
767
- Reject: 0,
768
- Tier1: 1,
769
- Tier2: 2
770
- };
771
-
772
764
  // lib/utils.ts
773
765
  function copyObject(data) {
774
766
  if (globalThis.structuredClone) {
@@ -786,12 +778,10 @@
786
778
  enableCosmeticRules: true,
787
779
  enableGeneratedRules: true,
788
780
  enableHeuristicDetection: false,
789
- enableHeuristicAction: false,
790
781
  enablePopupMutationObserver: false,
791
782
  detectRetries: 20,
792
783
  isMainWorld: false,
793
784
  prehideTimeout: 2e3,
794
- enableFilterList: false,
795
785
  visualTest: false,
796
786
  logs: {
797
787
  lifecycle: false,
@@ -804,7 +794,8 @@
804
794
  },
805
795
  performanceLoggingEnabled: false,
806
796
  heuristicPopupSearchTimeout: 100,
807
- heuristicMode: PopupHandlingModes.Reject
797
+ heuristicMode: "off"
798
+ // heuristic disabled by default
808
799
  };
809
800
  const updatedConfig = copyObject(defaultConfig);
810
801
  for (const key of Object.keys(defaultConfig)) {
@@ -823,8 +814,8 @@
823
814
  if (storedConfig.enablePopupMutationObserver === void 0) {
824
815
  storedConfig.enablePopupMutationObserver = true;
825
816
  }
826
- if (storedConfig.enableHeuristicAction === void 0) {
827
- storedConfig.enableHeuristicAction = true;
817
+ if (storedConfig.heuristicMode === void 0) {
818
+ storedConfig.heuristicMode = "tier1";
828
819
  }
829
820
  if (!storedConfig.logs) {
830
821
  storedConfig.logs = {
@@ -913,6 +904,7 @@
913
904
  }
914
905
 
915
906
  // addon/popup.ts
907
+ var maxRenderedRules = 100;
916
908
  async function init() {
917
909
  const autoconsentConfig = await storageGet("config");
918
910
  const enabledCheckbox = document.querySelector("input#enabled");
@@ -926,8 +918,10 @@
926
918
  const cosmeticOffRadio = document.querySelector("input#cosmetic-off");
927
919
  const generatedOnRadio = document.querySelector("input#generated-on");
928
920
  const generatedOffRadio = document.querySelector("input#generated-off");
929
- const heuristicActionOnRadio = document.querySelector("input#heuristic-action-on");
930
- const heuristicActionOffRadio = document.querySelector("input#heuristic-action-off");
921
+ const heuristicOffRadio = document.querySelector("input#heuristic-off");
922
+ const heuristicRejectRadio = document.querySelector("input#heuristic-reject");
923
+ const heuristicTier1Radio = document.querySelector("input#heuristic-tier1");
924
+ const heuristicTier2Radio = document.querySelector("input#heuristic-tier2");
931
925
  const visualTestOnRadio = document.querySelector("input#visual-test-on");
932
926
  const visualTestOffRadio = document.querySelector("input#visual-test-off");
933
927
  const popupMutationOnRadio = document.querySelector("input#popup-mutation-on");
@@ -942,6 +936,38 @@
942
936
  const logsMessagesCheckbox = document.querySelector("input#logs-messages");
943
937
  const ruleReloadButton = document.querySelector("#reload");
944
938
  const resetButton = document.querySelector("#reset");
939
+ const ruleSectionControls = {
940
+ generated: {
941
+ details: document.querySelector("#generated-rules-section"),
942
+ count: document.querySelector("#generated-rules-count"),
943
+ search: document.querySelector("#generated-rules-search"),
944
+ enableAll: document.querySelector("#generated-rules-enable-all"),
945
+ disableAll: document.querySelector("#generated-rules-disable-all"),
946
+ status: document.querySelector("#generated-rules-status"),
947
+ list: document.querySelector("#generated-rules-list")
948
+ },
949
+ generic: {
950
+ details: document.querySelector("#generic-rules-section"),
951
+ count: document.querySelector("#generic-rules-count"),
952
+ search: document.querySelector("#generic-rules-search"),
953
+ enableAll: document.querySelector("#generic-rules-enable-all"),
954
+ disableAll: document.querySelector("#generic-rules-disable-all"),
955
+ status: document.querySelector("#generic-rules-status"),
956
+ list: document.querySelector("#generic-rules-list")
957
+ },
958
+ code: {
959
+ details: document.querySelector("#code-rules-section"),
960
+ count: document.querySelector("#code-rules-count"),
961
+ search: document.querySelector("#code-rules-search"),
962
+ enableAll: document.querySelector("#code-rules-enable-all"),
963
+ disableAll: document.querySelector("#code-rules-disable-all"),
964
+ status: document.querySelector("#code-rules-status"),
965
+ list: document.querySelector("#code-rules-list")
966
+ }
967
+ };
968
+ let ruleIndexPromise = null;
969
+ let ruleIndex = null;
970
+ autoconsentConfig.disabledCmps = autoconsentConfig.disabledCmps || [];
945
971
  const [currentTab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
946
972
  if (!currentTab.url || !currentTab.id) {
947
973
  console.error("Current tab is not valid");
@@ -1007,10 +1033,14 @@
1007
1033
  } else {
1008
1034
  generatedOffRadio.checked = true;
1009
1035
  }
1010
- if (autoconsentConfig.enableHeuristicAction) {
1011
- heuristicActionOnRadio.checked = true;
1036
+ if (autoconsentConfig.heuristicMode === "reject") {
1037
+ heuristicRejectRadio.checked = true;
1038
+ } else if (autoconsentConfig.heuristicMode === "tier1") {
1039
+ heuristicTier1Radio.checked = true;
1040
+ } else if (autoconsentConfig.heuristicMode === "tier2") {
1041
+ heuristicTier2Radio.checked = true;
1012
1042
  } else {
1013
- heuristicActionOffRadio.checked = true;
1043
+ heuristicOffRadio.checked = true;
1014
1044
  }
1015
1045
  if (autoconsentConfig.visualTest) {
1016
1046
  visualTestOnRadio.checked = true;
@@ -1060,12 +1090,22 @@
1060
1090
  }
1061
1091
  generatedOnRadio.addEventListener("change", generatedChange);
1062
1092
  generatedOffRadio.addEventListener("change", generatedChange);
1063
- function heuristicActionChange() {
1064
- autoconsentConfig.enableHeuristicAction = heuristicActionOnRadio.checked;
1093
+ function heuristicModeChange() {
1094
+ if (heuristicRejectRadio.checked) {
1095
+ autoconsentConfig.heuristicMode = "reject";
1096
+ } else if (heuristicTier1Radio.checked) {
1097
+ autoconsentConfig.heuristicMode = "tier1";
1098
+ } else if (heuristicTier2Radio.checked) {
1099
+ autoconsentConfig.heuristicMode = "tier2";
1100
+ } else {
1101
+ autoconsentConfig.heuristicMode = "off";
1102
+ }
1065
1103
  storageSet({ config: autoconsentConfig });
1066
1104
  }
1067
- heuristicActionOnRadio.addEventListener("change", heuristicActionChange);
1068
- heuristicActionOffRadio.addEventListener("change", heuristicActionChange);
1105
+ heuristicOffRadio.addEventListener("change", heuristicModeChange);
1106
+ heuristicRejectRadio.addEventListener("change", heuristicModeChange);
1107
+ heuristicTier1Radio.addEventListener("change", heuristicModeChange);
1108
+ heuristicTier2Radio.addEventListener("change", heuristicModeChange);
1069
1109
  function visualTestChange() {
1070
1110
  autoconsentConfig.visualTest = visualTestOnRadio.checked;
1071
1111
  storageSet({ config: autoconsentConfig });
@@ -1111,10 +1151,141 @@
1111
1151
  logsMessagesCheckbox.addEventListener("change", () => {
1112
1152
  updateLogsConfig();
1113
1153
  });
1154
+ async function loadRuleIndex() {
1155
+ if (!ruleIndexPromise) {
1156
+ ruleIndexPromise = fetch("./rule-index.json").then(async (res) => await res.json());
1157
+ }
1158
+ ruleIndex = await ruleIndexPromise;
1159
+ updateRuleCounts();
1160
+ return ruleIndex;
1161
+ }
1162
+ function updateRuleCounts() {
1163
+ if (!ruleIndex) {
1164
+ return;
1165
+ }
1166
+ const disabledCmps = autoconsentConfig.disabledCmps || [];
1167
+ Object.keys(ruleSectionControls).forEach((section) => {
1168
+ const sectionRules = ruleIndex?.filter((rule) => rule.section === section) || [];
1169
+ const disabledInSection = sectionRules.filter((rule) => disabledCmps.includes(rule.name)).length;
1170
+ const count = ruleSectionControls[section].count;
1171
+ count.textContent = `(${disabledInSection}/${sectionRules.length} disabled)`;
1172
+ count.classList.toggle("rule-count-disabled", disabledInSection > 0);
1173
+ });
1174
+ }
1175
+ function setRuleEnabled(ruleName, enabled) {
1176
+ const disabledCmps = new Set(autoconsentConfig.disabledCmps || []);
1177
+ if (enabled) {
1178
+ disabledCmps.delete(ruleName);
1179
+ } else {
1180
+ disabledCmps.add(ruleName);
1181
+ }
1182
+ autoconsentConfig.disabledCmps = Array.from(disabledCmps).sort();
1183
+ storageSet({ config: autoconsentConfig });
1184
+ updateRuleCounts();
1185
+ }
1186
+ function renderOpenRuleSections() {
1187
+ Object.keys(ruleSectionControls).forEach((section) => {
1188
+ if (ruleSectionControls[section].details.open) {
1189
+ renderRuleSection(section);
1190
+ }
1191
+ });
1192
+ }
1193
+ function setRuleSectionEnabled(section, enabled) {
1194
+ if (!ruleIndex) {
1195
+ return;
1196
+ }
1197
+ const disabledCmps = new Set(autoconsentConfig.disabledCmps || []);
1198
+ ruleIndex.filter((rule) => rule.section === section).forEach((rule) => {
1199
+ if (enabled) {
1200
+ disabledCmps.delete(rule.name);
1201
+ } else {
1202
+ disabledCmps.add(rule.name);
1203
+ }
1204
+ });
1205
+ autoconsentConfig.disabledCmps = Array.from(disabledCmps).sort();
1206
+ storageSet({ config: autoconsentConfig });
1207
+ updateRuleCounts();
1208
+ renderOpenRuleSections();
1209
+ }
1210
+ function renderRuleSection(section) {
1211
+ const controls = ruleSectionControls[section];
1212
+ if (!ruleIndex) {
1213
+ controls.status.textContent = "Rules are not loaded yet.";
1214
+ return;
1215
+ }
1216
+ const disabledCmps = autoconsentConfig.disabledCmps || [];
1217
+ const searchQuery = controls.search.value.trim().toLowerCase();
1218
+ const sectionRules = ruleIndex.filter((rule) => rule.section === section);
1219
+ const matchingRules = sectionRules.filter((rule) => {
1220
+ if (!searchQuery) {
1221
+ return disabledCmps.includes(rule.name);
1222
+ }
1223
+ return rule.name.toLowerCase().includes(searchQuery) || !!rule.urlPattern?.toLowerCase().includes(searchQuery);
1224
+ });
1225
+ const renderedRules = matchingRules.slice(0, maxRenderedRules);
1226
+ const disabledInSection = sectionRules.filter((rule) => disabledCmps.includes(rule.name)).length;
1227
+ controls.status.textContent = searchQuery ? `Showing ${renderedRules.length} of ${matchingRules.length} matches. ${disabledInSection} disabled.` : `Showing ${renderedRules.length} of ${disabledInSection} disabled rules. Search to find enabled rules.`;
1228
+ controls.list.replaceChildren(
1229
+ ...renderedRules.map((rule) => {
1230
+ const checkbox = document.createElement("input");
1231
+ checkbox.type = "checkbox";
1232
+ checkbox.checked = !disabledCmps.includes(rule.name);
1233
+ checkbox.addEventListener("change", () => {
1234
+ setRuleEnabled(rule.name, checkbox.checked);
1235
+ renderRuleSection(section);
1236
+ });
1237
+ const label = document.createElement("label");
1238
+ label.className = "rule-row";
1239
+ label.title = rule.urlPattern ? `${rule.name}
1240
+ ${rule.urlPattern}` : rule.name;
1241
+ label.append(checkbox, document.createTextNode(` ${rule.name}${rule.cosmetic ? " (cosmetic)" : ""}`));
1242
+ return label;
1243
+ })
1244
+ );
1245
+ }
1246
+ async function loadAndRenderRuleSection(section) {
1247
+ ruleSectionControls[section].status.textContent = "Loading rules...";
1248
+ try {
1249
+ await loadRuleIndex();
1250
+ renderRuleSection(section);
1251
+ } catch (e) {
1252
+ console.error("Failed to load rule index", e);
1253
+ ruleSectionControls[section].status.textContent = "Failed to load rules.";
1254
+ }
1255
+ }
1256
+ Object.keys(ruleSectionControls).forEach((section) => {
1257
+ const controls = ruleSectionControls[section];
1258
+ controls.details.addEventListener("toggle", () => {
1259
+ if (controls.details.open) {
1260
+ loadAndRenderRuleSection(section);
1261
+ }
1262
+ });
1263
+ controls.search.addEventListener("input", () => renderRuleSection(section));
1264
+ controls.enableAll.addEventListener("click", async () => {
1265
+ await loadRuleIndex();
1266
+ setRuleSectionEnabled(section, true);
1267
+ });
1268
+ controls.disableAll.addEventListener("click", async () => {
1269
+ await loadRuleIndex();
1270
+ setRuleSectionEnabled(section, false);
1271
+ });
1272
+ });
1273
+ loadRuleIndex().catch((e) => {
1274
+ console.error("Failed to load rule index", e);
1275
+ });
1114
1276
  ruleReloadButton.addEventListener("click", async () => {
1115
- const res = await fetch("./rules.json");
1116
- storageSet({
1117
- rules: await res.json()
1277
+ const [compactRulesRes, fullRulesRes] = await Promise.all([fetch("./compact-rules.json"), fetch("./rules.json")]);
1278
+ const fullRules = await fullRulesRes.json();
1279
+ await storageSet({
1280
+ rules: await compactRulesRes.json(),
1281
+ fullRules: fullRules.autoconsent
1282
+ });
1283
+ ruleIndexPromise = null;
1284
+ ruleIndex = null;
1285
+ Object.keys(ruleSectionControls).forEach((section) => {
1286
+ if (ruleSectionControls[section].details.open) {
1287
+ loadAndRenderRuleSection(section);
1288
+ }
1118
1289
  });
1119
1290
  });
1120
1291
  resetButton.addEventListener("click", async () => {
@@ -23,6 +23,52 @@
23
23
  cursor: pointer;
24
24
  border-radius: 2px;
25
25
  }
26
+
27
+ .rule-section {
28
+ margin: 0.5em 0;
29
+ }
30
+
31
+ .rule-search {
32
+ box-sizing: border-box;
33
+ margin: 0.5em 0;
34
+ width: 100%;
35
+ }
36
+
37
+ .rule-actions {
38
+ display: flex;
39
+ gap: 0.5em;
40
+ margin: 0.5em 0;
41
+ }
42
+
43
+ .rule-actions button {
44
+ flex: 1;
45
+ }
46
+
47
+ .rule-list {
48
+ border: 1px solid #ddd;
49
+ max-height: 12em;
50
+ overflow-y: auto;
51
+ padding: 0.25em;
52
+ }
53
+
54
+ .rule-row {
55
+ display: block;
56
+ overflow: hidden;
57
+ padding: 0.15em 0;
58
+ text-overflow: ellipsis;
59
+ white-space: nowrap;
60
+ }
61
+
62
+ .rule-list-status {
63
+ color: #666;
64
+ font-size: 0.9em;
65
+ margin: 0.25em 0;
66
+ }
67
+
68
+ .rule-count-disabled {
69
+ color: #b00020;
70
+ font-weight: bold;
71
+ }
26
72
  </style>
27
73
  </head>
28
74
 
@@ -96,16 +142,63 @@
96
142
  </fieldset>
97
143
 
98
144
  <fieldset>
99
- <legend>Handle unknown popups (beta)</legend>
145
+ <legend>Rules</legend>
146
+
147
+ <details class="rule-section" id="generated-rules-section">
148
+ <summary>Generated rules <span id="generated-rules-count"></span></summary>
149
+ <input class="rule-search" type="search" id="generated-rules-search" placeholder="Search generated rules" />
150
+ <div class="rule-actions">
151
+ <button id="generated-rules-enable-all">Enable all</button>
152
+ <button id="generated-rules-disable-all">Disable all</button>
153
+ </div>
154
+ <div class="rule-list-status" id="generated-rules-status"></div>
155
+ <div class="rule-list" id="generated-rules-list"></div>
156
+ </details>
157
+
158
+ <details class="rule-section" id="generic-rules-section">
159
+ <summary>Generic rules <span id="generic-rules-count"></span></summary>
160
+ <input class="rule-search" type="search" id="generic-rules-search" placeholder="Search generic rules" />
161
+ <div class="rule-actions">
162
+ <button id="generic-rules-enable-all">Enable all</button>
163
+ <button id="generic-rules-disable-all">Disable all</button>
164
+ </div>
165
+ <div class="rule-list-status" id="generic-rules-status"></div>
166
+ <div class="rule-list" id="generic-rules-list"></div>
167
+ </details>
168
+
169
+ <details class="rule-section" id="code-rules-section">
170
+ <summary>Code-based rules <span id="code-rules-count"></span></summary>
171
+ <input class="rule-search" type="search" id="code-rules-search" placeholder="Search code-based rules" />
172
+ <div class="rule-actions">
173
+ <button id="code-rules-enable-all">Enable all</button>
174
+ <button id="code-rules-disable-all">Disable all</button>
175
+ </div>
176
+ <div class="rule-list-status" id="code-rules-status"></div>
177
+ <div class="rule-list" id="code-rules-list"></div>
178
+ </details>
179
+ </fieldset>
180
+
181
+ <fieldset>
182
+ <legend>Handle unknown popups (heuristic rule)</legend>
183
+
184
+ <div>
185
+ <input type="radio" id="heuristic-off" name="heuristic-mode" value="off" checked />
186
+ <label for="heuristic-off">Off</label>
187
+ </div>
188
+
189
+ <div>
190
+ <input type="radio" id="heuristic-reject" name="heuristic-mode" value="reject" />
191
+ <label for="heuristic-reject">Reject when possible</label>
192
+ </div>
100
193
 
101
194
  <div>
102
- <input type="radio" id="heuristic-action-on" name="heuristic-action" value="true" />
103
- <label for="heuristic-action-on">On</label>
195
+ <input type="radio" id="heuristic-tier1" name="heuristic-mode" value="tier1" />
196
+ <label for="heuristic-tier1">Acknowledge if no reject (tier 1)</label>
104
197
  </div>
105
198
 
106
199
  <div>
107
- <input type="radio" id="heuristic-action-off" name="heuristic-action" value="false" checked />
108
- <label for="heuristic-action-off">Off</label>
200
+ <input type="radio" id="heuristic-tier2" name="heuristic-mode" value="tier2" />
201
+ <label for="heuristic-tier2">Accept if no reject or acknowledge (tier 2)</label>
109
202
  </div>
110
203
  </fieldset>
111
204