the picker's register link opens the form

reported as "I click register a server and nothing happens", and nothing was broken. three controls on
that screen carry the label. two are buttons that open the form; the one in the server picker — top left,
where you look first — was a plain <Link> to the servers section.

/offscale and /offscale/servers render the same view (useOffscaleSection defaults to servers when there is
no param), so from either one the click navigated to something identical. the URL moved and nothing else
did, and the only visible result was a second button with the same label further down the page.

so the link now carries ?register=1 and ServersView consumes it. the flag is stripped with replace on the
way in, which is not tidiness: left in the URL, closing the form and clicking the picker again would be a
navigation to the URL already showing, and nothing would happen — the same bug, restored.

ServersView.test.tsx pins all three: the form opens on arrival, the flag is consumed, and no flag means no
form. removing the effect fails the first two.
This commit is contained in:
2026-08-17 12:58:47 +00:00
parent 373d67e0a0
commit 58fbf12e4b
4 changed files with 124 additions and 3 deletions
+20 -1
View File
@@ -1,7 +1,8 @@
import { useEffect, useRef, useState } from 'react';
import { useSearchParams } from 'react-router';
import { Plus, Loader2, Server, Check, Activity, Pencil, Trash2 } from 'lucide-react';
import type { OffscaleServer, OffscaleHealth } from './shared';
import { MIN_OFFSCALE_VERSION } from './shared';
import { MIN_OFFSCALE_VERSION, OFFSCALE_REGISTER_PARAM } from './shared';
import { useOffscaleServers, useOffscaleHealth, offscaleErrorMessage } from './useOffscaleServers';
import { Card, SectionHeader, Button, Dot, Badge, ErrorNote } from './Cards';
import { ServerForm } from './ServerForm';
@@ -136,11 +137,29 @@ export const ServersView = () => {
const healthProbe = useOffscaleHealth();
const [formFor, setFormFor] = useState<'new' | OffscaleServer | null>(null);
const [searchParams, setSearchParams] = useSearchParams();
const [health, setHealth] = useState<Record<number, OffscaleHealth>>({});
// Several probes are in flight at once now, so this is a set of ids rather than the one id it used to be.
const [testingIds, setTestingIds] = useState<readonly number[]>([]);
const [actionError, setActionError] = useState<string | null>(null);
/*
The URL may ask for the form: the server picker links here with `?register=1` so its "Register a server"
opens the thing rather than landing you on a page that offers the same thing again.
The flag is CONSUMED — read once, then stripped with `replace`. Leaving it in place would break the
control a second time: close the form, click the picker again, and the URL would already say
`?register=1`, so there would be no navigation and nothing would happen. Stripping it also means a
reload does not reopen a form you dismissed.
*/
useEffect(() => {
if (searchParams.get(OFFSCALE_REGISTER_PARAM) !== '1') return;
setFormFor('new');
const next = new URLSearchParams(searchParams);
next.delete(OFFSCALE_REGISTER_PARAM);
setSearchParams(next, { replace: true });
}, [searchParams, setSearchParams]);
const test = async (id: number) => {
setTestingIds((prev) => (prev.includes(id) ? prev : [...prev, id]));
setActionError(null);