milestone 24: persist and surface the unsupported-line count

This commit is contained in:
2026-08-13 20:44:27 +02:00
parent 21571e448e
commit 1bce81eea0
18 changed files with 799 additions and 20 deletions
@@ -19,6 +19,7 @@ const BLOCKLISTS = {
wildcard_count: 10,
exception_count: 7,
skipped_regex_count: 3,
skipped_unsupported_count: 21,
checksum: "abc",
},
{
@@ -32,6 +33,7 @@ const BLOCKLISTS = {
wildcard_count: 0,
exception_count: 0,
skipped_regex_count: 0,
skipped_unsupported_count: 0,
checksum: null,
},
],
@@ -102,6 +104,7 @@ const SNAPSHOT = {
wildcards: 12,
exceptions: 9,
skipped_regex: 4,
skipped_unsupported: 17,
},
],
};
@@ -117,7 +120,13 @@ test("renders the source table and the status empty state", async () => {
expect(screen.getByText("10")).toBeTruthy();
expect(screen.getByText("7")).toBeTruthy();
expect(screen.getByText("3")).toBeTruthy();
expect(screen.getByText("21")).toBeTruthy();
expect(screen.getByText("never")).toBeTruthy();
expect(screen.getByRole("columnheader", { name: "Skipped regex" })).toBeTruthy();
expect(screen.getByRole("columnheader", { name: "Skipped unsupported" })).toBeTruthy();
expect(
screen.getByText(/Skipped unsupported lines are syntax nxdns cannot translate into a DNS decision/),
).toBeTruthy();
const enabledToggle = screen.getByLabelText("StevenBlack enabled") as HTMLInputElement;
expect(enabledToggle.checked).toBe(true);
@@ -153,6 +162,7 @@ test("update now disables the button, then replaces the status section from the
wildcards: 12,
exceptions: 9,
skipped_regex: 4,
skipped_unsupported: 17,
},
{
id: 2,
@@ -166,6 +176,7 @@ test("update now disables the button, then replaces the status section from the
wildcards: 0,
exceptions: 0,
skipped_regex: 0,
skipped_unsupported: 0,
},
],
};
@@ -180,6 +191,8 @@ test("update now disables the button, then replaces the status section from the
expect(screen.getByText("12")).toBeTruthy();
expect(screen.getByText("9")).toBeTruthy();
expect(screen.getByText("4")).toBeTruthy();
expect(screen.getByText("17")).toBeTruthy();
expect(screen.getAllByRole("columnheader", { name: "Skipped unsupported" })).toHaveLength(2);
expect(screen.queryByText(/run .Update now. to fetch status/)).toBeNull();
// The store notifies one flush before the mutation's success state lands.
await screen.findByText(/Update completed/);
@@ -42,6 +42,10 @@ const styles = stylex.create({
marginTop: "1rem",
color: colors.textMuted,
},
note: {
marginTop: "0.5rem",
color: colors.textMuted,
},
table: {
width: "100%",
minWidth: "max-content",
@@ -155,6 +159,7 @@ export default function BlocklistsPage() {
<th {...stylex.props(shared.th)}>Wildcards</th>
<th {...stylex.props(shared.th)}>Exceptions</th>
<th {...stylex.props(shared.th)}>Skipped regex</th>
<th {...stylex.props(shared.th)}>Skipped unsupported</th>
<th {...stylex.props(shared.th)}>Last updated</th>
<th {...stylex.props(shared.th)}>
<span {...stylex.props(shared.srOnly)}>Actions</span>
@@ -188,6 +193,9 @@ export default function BlocklistsPage() {
<td {...stylex.props(shared.td, shared.tabularNums)}>{b.wildcard_count}</td>
<td {...stylex.props(shared.td, shared.tabularNums)}>{b.exception_count}</td>
<td {...stylex.props(shared.td, shared.tabularNums)}>{b.skipped_regex_count}</td>
<td {...stylex.props(shared.td, shared.tabularNums)}>
{b.skipped_unsupported_count}
</td>
<td {...stylex.props(shared.td)}>
{b.last_updated === null ? "never" : formatTime(b.last_updated)}
</td>
@@ -221,6 +229,13 @@ export default function BlocklistsPage() {
))}
</tbody>
</table>
<p {...stylex.props(styles.note)}>
Both Skipped columns count lines nxdns read and did not take. Skipped regex lines are patterns
nxdns accepts only from you adopt one you trust as a regex rule. Skipped unsupported lines are
syntax nxdns cannot translate into a DNS decision: cosmetic element hiding, browser-only
modifiers. A skipped unsupported count that dwarfs the domain count usually means the list is
written for a browser extension, and its DNS or hosts variant will block more here.
</p>
</div>
)}
<InlineError error={tableError} />
@@ -89,6 +89,7 @@ export default function SourceStatusSection({ sources, namesById }: SourceStatus
<th {...stylex.props(shared.th)}>Wildcards</th>
<th {...stylex.props(shared.th)}>Exceptions</th>
<th {...stylex.props(shared.th)}>Skipped regex</th>
<th {...stylex.props(shared.th)}>Skipped unsupported</th>
<th {...stylex.props(shared.th)}>Last error</th>
</tr>
</thead>
@@ -112,6 +113,9 @@ export default function SourceStatusSection({ sources, namesById }: SourceStatus
<td {...stylex.props(shared.td, shared.tabularNums)}>{source.wildcards}</td>
<td {...stylex.props(shared.td, shared.tabularNums)}>{source.exceptions}</td>
<td {...stylex.props(shared.td, shared.tabularNums)}>{source.skipped_regex}</td>
<td {...stylex.props(shared.td, shared.tabularNums)}>
{source.skipped_unsupported}
</td>
<td {...stylex.props(shared.td)}>
{source.last_error === "" ? (
<span {...stylex.props(styles.absent)}></span>