From 29d0cd9744371f38f42faccbc07425f0c8e8e3ef Mon Sep 17 00:00:00 2001 From: "nekomimi.pet" Date: Sun, 4 Oct 2026 10:22:13 -0400 Subject: [PATCH] fix(dashboard): one-line tabs and footer on phones, keep checking marque dns - Tabs stay on one line on a phone (no shortcut numbers, smaller type, and a sideways scroll if it still does not fit); the footer does the same. - Inline add/claim fields wrap their button below instead of overlapping it. - After writing a marque zone, check dns at 2, 6 and 15 seconds rather than once, since a nameserver can lag the write. --- apps/main-app/public/editor/app.tsx | 27 +++++++++++-------- apps/main-app/public/editor/views/domains.tsx | 26 ++++++++++++++---- apps/main-app/public/editor/views/secrets.tsx | 2 +- apps/main-app/public/styles/global.css | 10 +++++++ 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/apps/main-app/public/editor/app.tsx b/apps/main-app/public/editor/app.tsx index 2d346e4..83e7478 100644 --- a/apps/main-app/public/editor/app.tsx +++ b/apps/main-app/public/editor/app.tsx @@ -139,14 +139,14 @@ export function App() { // The page itself never scrolls: header, key hints, tabs and footer stay put and only the pane's body scrolls.
-
+
{/* One terminal pane: the tabs are its title strip, the rows live inside. The fairy holds its left edge. */}
{TABS.map((entry, index) => ( ))} @@ -265,14 +265,19 @@ const Mascot = () => ( /** The bottom bar: its top edge is the rail the fairy sits on. */ const Footer = () => (
-
+ {/* The fairy sits outside the scrolling strip, which would clip her. */} +
- built with ♡ by @nekomimi.pet - {FOOTER_LINKS.map((link) => ( - - {link.label} - - ))} +
+ + built with ♡ by @nekomimi.pet + + {FOOTER_LINKS.map((link) => ( + + {link.label} + + ))} +
) diff --git a/apps/main-app/public/editor/views/domains.tsx b/apps/main-app/public/editor/views/domains.tsx index 9627f75..4523cad 100644 --- a/apps/main-app/public/editor/views/domains.tsx +++ b/apps/main-app/public/editor/views/domains.tsx @@ -41,8 +41,11 @@ const HOSTING_NODES = [ const openDomain = (domain: string) => openInNewTab(`https://${domain}`) -/** Marque's nameservers pick up a zone write within a second or so; give them a little more before checking. */ -const MARQUE_SETTLE_MS = 2000 +/** + * When to check dns after writing a marque zone. Its nameservers usually answer + * within a second or two, but not always, so a slow one gets a few more tries. + */ +const MARQUE_CHECKS_MS = [2000, 6000, 15_000] as const /** `?dns=`, left by the sign-in that let wisp edit marque dns, reopens that domain's dialog. */ const dnsParam = (): string | null => new URLSearchParams(window.location.search).get('dns') @@ -299,7 +302,7 @@ function ClaimForm() { claim a subdomain} inline - className="min-w-0 flex-1" + className="flex-1" suffix=".wisp.place" value={handle} onChange={(event) => setHandle(event.target.value)} @@ -347,7 +350,7 @@ function AddDomainForm({ onAdded }: { onAdded: (id: string) => void }) { add a domain you own} inline - className="min-w-0 flex-1" + className="flex-1" name="domain" placeholder="example.com" autoCapitalize="none" @@ -484,9 +487,22 @@ function MarqueSetup({ domain, status, onVerify }: MarqueSetupProps) { success: `added ${domain.domain} to your marque dns ✦`, failure: 'could not set up marque dns', }) + // Checks stop once the domain verifies, since the dialog then drops this panel. + // The latest domain and callback are read through a ref: every check refetches + // the domain list, and restarting the timers on that would check forever. + const latest = useRef({ domain, onVerify }) + latest.current = { domain, onVerify } + const [written, setWritten] = useState(false) + useEffect(() => { + if (!written) return + const check = () => latest.current.onVerify(latest.current.domain) + const timers = MARQUE_CHECKS_MS.map((delay) => setTimeout(check, delay)) + return () => timers.forEach(clearTimeout) + }, [written]) + const apply = (replace: boolean) => setUp.mutate(replace, { - onSuccess: () => setTimeout(() => onVerify(domain), MARQUE_SETTLE_MS), + onSuccess: () => setWritten(true), // A conflict that appeared since the dialog opened is shown from a fresh status. onError: () => client.invalidateQueries({ queryKey: keys.marque(domain.id) }), }) diff --git a/apps/main-app/public/editor/views/secrets.tsx b/apps/main-app/public/editor/views/secrets.tsx index ad85ea2..05e5976 100644 --- a/apps/main-app/public/editor/views/secrets.tsx +++ b/apps/main-app/public/editor/views/secrets.tsx @@ -98,7 +98,7 @@ export function Secrets() { new secret} inline - className="min-w-0 flex-1" + className="flex-1" name="name" required maxLength={64} diff --git a/apps/main-app/public/styles/global.css b/apps/main-app/public/styles/global.css index 8c53571..f643b37 100644 --- a/apps/main-app/public/styles/global.css +++ b/apps/main-app/public/styles/global.css @@ -418,6 +418,16 @@ border-top: 2px dashed var(--rule); } + /* A strip that stays on one line on a phone and scrolls sideways instead of wrapping. */ + .scroll-x { + overflow-x: auto; + scrollbar-width: none; + } + + .scroll-x::-webkit-scrollbar { + display: none; + } + /* The only scrolling region: the page stays put around it. */ .pane-body { scrollbar-gutter: stable; -- 2.54.0