milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
@@ -4,6 +4,7 @@ import { groupSourcesPutMutation, groupSourcesQuery } from "@/lib/queries";
import type { Blocklist } from "@/lib/types";
import { sameSet, toggleSource } from "./sourceSet";
import InlineError from "@/lib/InlineError";
import { buttonClass, focusRing, primaryButtonClass } from "@/ui/classes";
interface Props {
groupId: number;
@@ -46,6 +47,7 @@ export default function GroupSourcesEditor({ groupId, blocklists }: Props) {
type="checkbox"
checked={current.includes(blocklist.id)}
onChange={() => setSelected(toggleSource(current, blocklist.id))}
className={focusRing}
/>
{blocklist.name}
</label>
@@ -60,16 +62,12 @@ export default function GroupSourcesEditor({ groupId, blocklists }: Props) {
onClick={() =>
mutation.mutate({ id: groupId, sourceIds: current }, { onSuccess: () => setSelected(null) })
}
className="rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
className={primaryButtonClass}
>
Save sources
</button>
{dirty && (
<button
type="button"
onClick={() => setSelected(null)}
className="rounded border border-zinc-300 px-3 py-1.5 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-zinc-700"
>
<button type="button" onClick={() => setSelected(null)} className={buttonClass}>
Discard
</button>
)}
+12 -14
View File
@@ -10,15 +10,12 @@ import {
import type { Blocklist, Group } from "@/lib/types";
import GroupSourcesEditor from "./GroupSourcesEditor";
import InlineError from "@/lib/InlineError";
import { focusRing, primaryButtonClass, smallButtonClass, smallInputClass } from "@/ui/classes";
const DEFAULT_GROUP_ID = 1;
const DEFAULT_GROUP_NOTE = "The default group cannot be renamed or deleted.";
const inputClass = "rounded border border-zinc-300 bg-white px-2 py-1.5 text-sm dark:border-zinc-700 dark:bg-zinc-900";
const buttonClass =
"rounded border border-zinc-300 px-2 py-1 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 disabled:opacity-50 dark:border-zinc-700";
const primaryButtonClass =
"rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600";
const groupButtonClass = `${smallButtonClass} disabled:opacity-50`;
export default function GroupsPage() {
const { data: groups } = useSuspenseQuery(groupsQuery());
@@ -47,7 +44,7 @@ export default function GroupsPage() {
type="text"
value={newName}
onChange={(event) => setNewName(event.target.value)}
className={inputClass}
className={smallInputClass}
/>
<button type="submit" disabled={createMutation.isPending} className={primaryButtonClass}>
Create
@@ -94,10 +91,10 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
aria-label={`New name for ${group.name}`}
value={name}
onChange={(event) => setName(event.target.value)}
className={inputClass}
className={smallInputClass}
autoFocus
/>
<button type="submit" disabled={updateMutation.isPending} className={buttonClass}>
<button type="submit" disabled={updateMutation.isPending} className={groupButtonClass}>
Save
</button>
<button
@@ -106,7 +103,7 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
setName(group.name);
setRenaming(false);
}}
className={buttonClass}
className={groupButtonClass}
>
Cancel
</button>
@@ -119,6 +116,7 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
type="checkbox"
checked={group.safe_search}
disabled={updateMutation.isPending}
className={focusRing}
onChange={(event) =>
updateMutation.mutate({
id: group.id,
@@ -133,7 +131,7 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
type="button"
aria-expanded={expanded}
onClick={() => setExpanded((open) => !open)}
className={buttonClass}
className={groupButtonClass}
>
Sources
</button>
@@ -146,7 +144,7 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
setName(group.name);
setRenaming(true);
}}
className={buttonClass}
className={groupButtonClass}
>
Rename
</button>
@@ -159,11 +157,11 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
setConfirming(false);
deleteMutation.mutate(group.id);
}}
className={`${buttonClass} text-red-700 dark:text-red-400`}
className={`${groupButtonClass} text-red-700 dark:text-red-400`}
>
Confirm delete
</button>
<button type="button" onClick={() => setConfirming(false)} className={buttonClass}>
<button type="button" onClick={() => setConfirming(false)} className={groupButtonClass}>
Cancel
</button>
</>
@@ -173,7 +171,7 @@ function GroupRow({ group, blocklists }: { group: Group; blocklists: Blocklist[]
disabled={isDefault}
title={isDefault ? DEFAULT_GROUP_NOTE : undefined}
onClick={() => setConfirming(true)}
className={`${buttonClass} text-red-700 dark:text-red-400`}
className={`${groupButtonClass} text-red-700 dark:text-red-400`}
>
Delete
</button>