fix(web): four "fixes" from this branch that did not actually fix anything
A verification pass re-read every finding from the original sweep against the code on this branch rather than against the commit messages. It found that four of them were still broken, two because the edit I made was inert. Commit messages claim; code decides. - **The Storybook typecheck was never on.** `tsconfig.json` listed `.storybook` as a bare directory name, and tsc silently skips dot-prefixed directories in that form — so the entry typechecked nothing at all. Proved it by planting `export const __probe: string = 1` in `.storybook/preview.tsx` and watching `bun run lint` pass. `.storybook/**/*` is what actually pulls it in; the same probe now fails as it should. - **The Moonlight stale-PIN reset was a no-op.** `submit.reset()` sat at the top of `onSubmit`, immediately before `submit.mutate(...)` — which moves the status to pending in the same update, so it cleared a flag that was already changing. The green "PIN sent" note therefore still greeted the next pairing attempt over an empty PIN box. It now resets on the transition that actually matters: `pin_pending` going false → true. - **The session⇄game controls had the enforcement flag inverted**, and I never touched it. `enforced.length === 0 || …` reads an EMPTY list as "this build enforces everything", when the contract says the opposite in as many words: "Empty on a platform with no launch path (macOS), so the console can say so instead of offering a switch that does nothing". On exactly the platform the flag exists for, every control stayed live and reported success for an axis the host would never act on. Absent still means "assume it acts" — that is the compatible reading for an older host, and a different case from present-empty. - **Logout stopped revoking after a restart.** The epoch was a module-level counter starting at 1, so it revoked within one process run and then reset — and since the seal key derives from the stable mgmt token, a cookie captured before a restart unsealed fine and was accepted again for the rest of its 7-day TTL. One service restart undid the whole fix. It persists next to the host's config now. Verified: log out, restart the console, the captured cookie still 401s, a fresh login still works. Two more the pass rated as partial, both worth closing: - The plugin-UI response filter was a denylist of four header names, so `Clear-Site-Data` sailed through — a plugin error page could wipe `pf_session` and sign the operator out of the console, on our own origin, because the iframe is same-origin by design. It is an allowlist now; a plugin-supplied CSP, `X-Frame-Options` or CORS header no longer speaks for us either. - A half-configured TLS setup now refuses to start instead of logging a warning and serving anyway. Neither shape can work — one path missing puts the login password on the LAN in the clear, and PUNKTFUNK_UI_SECURE without TLS marks the cookie Secure so the browser drops it and login can never stick. Exiting with a reason beats a console that looks fine and is not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,11 +32,18 @@ export const SessionGameCard: FC = () => {
|
||||
const q = useGetSessionSettings();
|
||||
const save = useSetSessionSettings();
|
||||
const server = q.data?.settings;
|
||||
// Which axes this build acts on. Empty on a platform with no launch path (macOS), where the
|
||||
// controls are shown disabled rather than hidden — "does nothing here" is information.
|
||||
const enforced = q.data?.enforced ?? [];
|
||||
const acts = (field: string) =>
|
||||
enforced.length === 0 || enforced.includes(field);
|
||||
// Which axes this build acts on. An EMPTY list means the build enforces nothing — the contract
|
||||
// says so outright ("Empty on a platform with no launch path (macOS), so the console can say so
|
||||
// instead of offering a switch that does nothing"), and this card's own comment promises the
|
||||
// controls are "shown disabled rather than hidden".
|
||||
//
|
||||
// The old `enforced.length === 0 || …` read empty as "enforces EVERYTHING", so on exactly the
|
||||
// platform the flag exists for, every control stayed live: clicking one PUT the setting and
|
||||
// toasted success for an axis the host would never act on. Absent (an older host that never
|
||||
// sent the field) still means "assume it acts" — that is the compatible reading, and it is a
|
||||
// different case from present-and-empty.
|
||||
const enforced = q.data?.enforced;
|
||||
const acts = (field: string) => !enforced || enforced.includes(field);
|
||||
|
||||
// The grace field is free text while being typed, so it gets a local buffer; the other two axes
|
||||
// are discrete and go straight to the host.
|
||||
@@ -162,7 +169,10 @@ export const SessionGameCard: FC = () => {
|
||||
</Field>
|
||||
)}
|
||||
|
||||
{enforced.length === 0 && (
|
||||
{/* Present-and-empty is the "this build acts on none of it" signal; ABSENT
|
||||
is an older host that never sent the field, where claiming inertness
|
||||
would be a guess. Same distinction `acts()` makes above. */}
|
||||
{enforced?.length === 0 && (
|
||||
<Badge variant="outline">{m.session_game_inert()}</Badge>
|
||||
)}
|
||||
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Info, KeyRound } from "lucide-react";
|
||||
import { type FC, useState } from "react";
|
||||
import { type FC, useEffect, useRef, useState } from "react";
|
||||
import { getListPairedClientsQueryKey } from "@/api/gen/clients/clients";
|
||||
import type { PairingStatus } from "@/api/gen/model/pairingStatus";
|
||||
import {
|
||||
@@ -23,10 +23,24 @@ export const MoonlightPairingSection: FC = () => {
|
||||
const pairing = useGetPairingStatus({ query: { refetchInterval: 2_000 } });
|
||||
const submit = useSubmitPairingPin();
|
||||
|
||||
// Clear the previous attempt's outcome when a NEW pairing knock arrives.
|
||||
//
|
||||
// The mutation's success flag outlives the form — the section never unmounts, only the inner
|
||||
// <form> is conditional — so the green "PIN sent" note was still on screen above an empty PIN
|
||||
// box the next time Moonlight asked. Resetting inside `onSubmit` (the first attempt at this)
|
||||
// does nothing: `mutate` moves the status to pending in the same update, so `isSuccess` was
|
||||
// already about to go false. The transition that matters is `pin_pending` going false → true.
|
||||
const pending = pairing.data?.pin_pending ?? false;
|
||||
const wasPending = useRef(pending);
|
||||
useEffect(() => {
|
||||
if (pending && !wasPending.current) {
|
||||
submit.reset();
|
||||
setPin("");
|
||||
}
|
||||
wasPending.current = pending;
|
||||
}, [pending, submit.reset]);
|
||||
|
||||
const onSubmit = () => {
|
||||
// The mutation's success/error flags outlive the form: without this, starting a SECOND
|
||||
// pairing attempt showed the previous one's "PIN sent" confirmation before a digit was typed.
|
||||
submit.reset();
|
||||
submit.mutate(
|
||||
{ data: { pin } },
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user