milestone 32: task-shaped configuration, file mode as a rendering, config status api

This commit is contained in:
2026-08-22 22:42:50 +02:00
parent c99a37d170
commit 24521ab9a9
89 changed files with 6101 additions and 3750 deletions
@@ -0,0 +1,138 @@
import { useState, type FormEvent } from "react";
import * as stylex from "@stylexjs/stylex";
import { ApiError } from "@/lib/api";
import InlineError from "@/lib/InlineError";
import type { Blocklist, BlocklistInput } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
const styles = stylex.create({
form: {
display: "flex",
flexDirection: "column",
gap: "0.75rem",
marginTop: "1rem",
maxWidth: "36rem",
},
heading: {
fontSize: "1.125rem",
lineHeight: "1.75rem",
fontWeight: 500,
},
fieldLabel: {
display: "block",
fontSize: "0.875rem",
lineHeight: "1.25rem",
fontWeight: 500,
},
checkboxLabel: {
display: "flex",
alignItems: "center",
gap: "0.5rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
fontWeight: 500,
},
buttonRow: {
display: "flex",
alignItems: "center",
gap: "0.5rem",
},
cancel: {
fontWeight: 500,
},
});
/**
* Drops the rejection the page already renders inline below the form. Anything
* else is a bug in this component and must reach the console instead of dying
* silently in the submit handler.
*/
export function swallowMutationError(error: unknown): void {
if (error instanceof ApiError) return;
throw error;
}
interface BlocklistFormProps {
initial?: Blocklist;
busy: boolean;
error: Error | null;
onSubmit: (input: BlocklistInput) => Promise<void>;
onCancel?: () => void;
}
export default function BlocklistForm({ initial, busy, error, onSubmit, onCancel }: BlocklistFormProps) {
const [url, setUrl] = useState(initial?.url ?? "");
const [name, setName] = useState(initial?.name ?? "");
const [enabled, setEnabled] = useState(initial?.enabled ?? true);
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
try {
await onSubmit({ url: url.trim(), name: name.trim(), enabled });
} catch (error) {
swallowMutationError(error);
return;
}
if (initial === undefined) {
setUrl("");
setName("");
setEnabled(true);
}
}
return (
<form onSubmit={handleSubmit} {...stylex.props(styles.form)}>
<h2 {...stylex.props(styles.heading)}>{initial === undefined ? "Add source" : `Edit ${initial.name}`}</h2>
<div>
<label htmlFor="blocklist-url" {...stylex.props(styles.fieldLabel)}>
URL
</label>
<input
id="blocklist-url"
type="url"
required
value={url}
onChange={(event) => setUrl(event.target.value)}
{...stylex.props(shared.input, shared.focusRing)}
/>
</div>
<div>
<label htmlFor="blocklist-name" {...stylex.props(styles.fieldLabel)}>
Name
</label>
<input
id="blocklist-name"
type="text"
required
value={name}
onChange={(event) => setName(event.target.value)}
{...stylex.props(shared.input, shared.focusRing)}
/>
</div>
<label {...stylex.props(styles.checkboxLabel)}>
<input
type="checkbox"
checked={enabled}
onChange={(event) => setEnabled(event.target.checked)}
{...stylex.props(shared.focusRing)}
/>
Enabled
</label>
<div {...stylex.props(styles.buttonRow)}>
<button type="submit" disabled={busy} {...stylex.props(shared.primaryButton, shared.focusRing)}>
{initial === undefined ? "Add source" : "Save changes"}
</button>
{onCancel !== undefined && (
<button
type="button"
onClick={onCancel}
{...stylex.props(shared.button, styles.cancel, shared.focusRing)}
>
Cancel
</button>
)}
</div>
<InlineError error={error} />
</form>
);
}