fix(dashboard): mobile chat New Chat button placement + merged fn-2469

- ChatView mobile: hide the top sidebar header's "New Chat" button on
  <=768px and show the existing footer button as a pinned full-width
  action with safe-area padding. Both buttons existed in JSX; only
  CSS was needed.
- styles.css: comments compose/edit textareas use var(--surface) so
  they remain visible against the task detail modal's --card panel
  (in light theme --bg and --card both resolve to #ffffff).
- Includes resolved-conflict changes from fusion/fn-2469 in
  ModelOnboardingModal and SettingsModal (and the related test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-24 20:43:17 -07:00
parent 820582b722
commit 5ba4395d4e
5 changed files with 208 additions and 2 deletions

View File

@@ -176,6 +176,7 @@ export function SettingsModal({
const [authProviders, setAuthProviders] = useState<AuthProvider[]>([]);
const [authLoading, setAuthLoading] = useState(false);
const [authActionInProgress, setAuthActionInProgress] = useState<string | null>(null);
const [loginInstructions, setLoginInstructions] = useState<Record<string, string>>({});
const [apiKeyInputs, setApiKeyInputs] = useState<Record<string, string>>({});
const [apiKeyErrors, setApiKeyErrors] = useState<Record<string, string>>({});
const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
@@ -288,6 +289,16 @@ export function SettingsModal({
try {
const { providers } = await fetchAuthStatus();
setAuthProviders(providers);
setLoginInstructions((prev) => {
const next: Record<string, string> = {};
for (const [providerId, instructions] of Object.entries(prev)) {
const provider = providers.find((candidate) => candidate.id === providerId);
if (provider && !provider.authenticated) {
next[providerId] = instructions;
}
}
return Object.keys(next).length === Object.keys(prev).length ? prev : next;
});
} catch {
// Silently fail — auth may not be configured
}
@@ -386,8 +397,20 @@ export function SettingsModal({
const handleLogin = useCallback(async (providerId: string) => {
setAuthActionInProgress(providerId);
setLoginInstructions((prev) => {
if (!(providerId in prev)) {
return prev;
}
const next = { ...prev };
delete next[providerId];
return next;
});
try {
const { url } = await loginProvider(providerId);
const { url, instructions } = await loginProvider(providerId);
if (instructions?.trim()) {
setLoginInstructions((prev) => ({ ...prev, [providerId]: instructions }));
}
window.open(appendTokenQuery(url), "_blank");
// Poll for auth completion every 2 seconds
@@ -402,6 +425,14 @@ export function SettingsModal({
pollIntervalRef.current = null;
}
setAuthActionInProgress(null);
setLoginInstructions((prev) => {
if (!(providerId in prev)) {
return prev;
}
const next = { ...prev };
delete next[providerId];
return next;
});
addToast("Login successful", "success");
}
} catch {
@@ -411,6 +442,14 @@ export function SettingsModal({
} catch (err) {
addToast(getErrorMessage(err) || "Login failed", "error");
setAuthActionInProgress(null);
setLoginInstructions((prev) => {
if (!(providerId in prev)) {
return prev;
}
const next = { ...prev };
delete next[providerId];
return next;
});
}
}, [addToast]);
@@ -3257,6 +3296,14 @@ export function SettingsModal({
Login
</button>
)}
{loginInstructions[provider.id] && (
<p
className="auth-login-instructions"
data-testid={`auth-login-instructions-${provider.id}`}
>
{loginInstructions[provider.id]}
</p>
)}
</div>
)}
</div>