feat!: remove the shared demo account

The demo account — one shared user that whitelisted emails entered from
the login page, reset on a schedule — goes end to end (owner decision,
E34): demo.py and the reset job, users.is_demo and demo_whitelist
(migration 0098 deletes the demo user, its audit rows, the reset job's
override and notices, then drops the column and table; the downgrade
restores the empty schema), the /auth/demo entry, the admin whitelist and
reset, the Demo tab, the banner, the login page's "Try the demo", and
every is_demo branch in routes, userscope, scheduler and the UI.

require_human only ever refused the demo account, so its 87 uses become
current_user (owner-approved); require_write_scope keeps its own check.
The login page's "or" divider now shows only beside the Google button:
without the demo entry under it, an instance without Google would have
said "or" to nothing.

A contract test (removedFeatures) fails on the word anywhere in the
backend, frontend, e2e or scripts — seen red on a planted hit. Size
ceilings tightened for the modules that shrank; admin.py is back under
the limit.
This commit is contained in:
2026-10-05 00:19:38 +02:00
parent cca3315836
commit 6d8b7be6d8
109 changed files with 487 additions and 1400 deletions
+28 -30
View File
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { qk } from "../lib/queryKeys";
import { History, Pause, Play, RefreshCw } from "lucide-react";
import { api, type AdminQuotaRow, type Me } from "../lib/api";
import { api, type AdminQuotaRow } from "../lib/api";
import { formatEta, formatNumber, formatUnit, quotaActionLabel } from "../lib/format";
import { notify } from "../lib/notifications";
import { notifyYouTubeActionError } from "../lib/youtubeErrors";
@@ -61,13 +61,13 @@ export default function Stats() {
</PageToolbar>
)}
{tab === "system" && isAdmin ? <SystemStats /> : <Overview me={me} />}
{tab === "system" && isAdmin ? <SystemStats /> : <Overview />}
</div>
);
}
// Per-user view: sync status + your own API usage + (for real accounts) the manual sync actions.
function Overview({ me }: { me: Me }) {
// Per-user view: sync status + your own API usage + the manual sync actions.
function Overview() {
const { t, i18n } = useTranslation();
const lang = i18n.language;
const qc = useQueryClient();
@@ -203,32 +203,30 @@ function Overview({ me }: { me: Me }) {
)}
</Section>
{!me.is_demo && (
<Section title={t("stats.sync.actions")}>
<div className="flex flex-wrap gap-2">
<Tooltip hint={t("stats.sync.syncSubscriptionsHint")}>
<Button
variant="glass"
icon={<RefreshCw />}
loading={syncSubs.isPending}
onClick={() => syncSubs.mutate()}
>
{t("stats.sync.syncSubscriptions")}
</Button>
</Tooltip>
<Tooltip hint={t("stats.sync.backfillEverythingHint")}>
<Button
variant="glass"
icon={<History />}
loading={deepAll.isPending}
onClick={() => deepAll.mutate()}
>
{t("stats.sync.backfillEverything")}
</Button>
</Tooltip>
</div>
</Section>
)}
<Section title={t("stats.sync.actions")}>
<div className="flex flex-wrap gap-2">
<Tooltip hint={t("stats.sync.syncSubscriptionsHint")}>
<Button
variant="glass"
icon={<RefreshCw />}
loading={syncSubs.isPending}
onClick={() => syncSubs.mutate()}
>
{t("stats.sync.syncSubscriptions")}
</Button>
</Tooltip>
<Tooltip hint={t("stats.sync.backfillEverythingHint")}>
<Button
variant="glass"
icon={<History />}
loading={deepAll.isPending}
onClick={() => deepAll.mutate()}
>
{t("stats.sync.backfillEverything")}
</Button>
</Tooltip>
</div>
</Section>
</>
);
}