Skip to content

Commit c9fa927

Browse files
jayzalowitzclaude
andcommitted
fix(#187 follow-up): conditional settings fetch + match enabled convention
Two findings from Copilot's re-review of PR #248: - **fetchSettings on every render is noisy**: dropping it into the Promise.allSettled batch meant SSE-driven re-renders hit /api/settings every tick once the user had any activity. Restructured: only fetch when the cheap prerequisites (`!tourMode && recentDecisions.length === 0`) already point at first-run. After the user gets any decision the fetch doesn't happen at all. - **Provider-enabled check disagreed with Settings UI**: settings.js treats providers as enabled unless `enabled === false` (`p.enabled !== false`); my dashboard code only counted truthy values. An existing user with providers stored without an explicit `enabled` field would have been incorrectly shown the first-run banner. Aligned to `p?.enabled !== false`. Web build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b00192b commit c9fa927

1 file changed

Lines changed: 22 additions & 24 deletions

File tree

apps/web/public/js/pages/dashboard.js

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ export async function renderDashboard(container, userId) {
283283
// and user action move them around constantly.
284284
// Slow-changing data — wrapped in slowFetch so a 4s first-scan tick or
285285
// a debounced SSE re-render doesn't burn 13 round-trips per cycle.
286-
const [health, accuracy, confidence, learning, approvals, decisions, skillGaps, progress, learned, unmetCreds, googleOAuth, credsStatus, briefingData, twinBriefingData, lifebooksData, settingsData] = await Promise.allSettled([
286+
const [health, accuracy, confidence, learning, approvals, decisions, skillGaps, progress, learned, unmetCreds, googleOAuth, credsStatus, briefingData, twinBriefingData, lifebooksData] = await Promise.allSettled([
287287
fetchHealth(),
288288
fetchAccuracy(userId),
289289
fetchConfidence(userId),
@@ -299,11 +299,6 @@ export async function renderDashboard(container, userId) {
299299
fetchBriefing(userId),
300300
fetchLatestTwinBriefing(userId, 'daily').catch(() => null),
301301
slowFetch(`lifebooks-${userId}`, fetchLifebooks, [userId]),
302-
// Not slow-cached: a user enabling their first AI provider in
303-
// Settings should make the first-run prompt go away on the next
304-
// dashboard render, not 30s later. The endpoint is small and
305-
// only relevant on first-run renders anyway.
306-
fetchSettings(userId),
307302
]);
308303

309304
const healthOk = health.status === 'fulfilled';
@@ -367,25 +362,28 @@ export async function renderDashboard(container, userId) {
367362

368363
const tourMode = (() => { try { return localStorage.getItem(KEY_TOUR_MODE) === '1'; } catch { return false; } })();
369364

370-
// Only show the prompt when we have positive evidence the user is
371-
// in the first-run state. If either fetch failed, we don't know
372-
// their provider count or decision count — falling back to "show
373-
// the prompt" would surface it during transient API errors and
374-
// for users who actually have providers but hit a blip.
375-
// Skip in tour mode — seeded demo user has providers pre-configured.
376-
const settingsFulfilled = settingsData?.status === 'fulfilled';
365+
// First-run "needs a brain" prompt. Two prerequisites are cheap and
366+
// already known here: tour mode (always-off) and recentDecisions
367+
// (zero only on first-run-ish accounts). Only when both clear do we
368+
// pay for a settings fetch — keeps SSE-driven re-renders from hitting
369+
// /api/settings on every tick once the user has any history.
370+
// Provider enablement matches settings.js: a provider is "enabled"
371+
// unless `enabled === false`, so existing rows without an explicit
372+
// field are treated as on (same convention the Settings UI uses).
377373
const decisionsFulfilled = decisions?.status === 'fulfilled';
378-
const aiProviders = settingsFulfilled
379-
? (settingsData.value?.aiProviders ?? [])
380-
: [];
381-
const enabledProviderCount = Array.isArray(aiProviders)
382-
? aiProviders.filter((p) => p?.enabled).length
383-
: 0;
384-
const showBrainPrompt = !tourMode
385-
&& settingsFulfilled
386-
&& decisionsFulfilled
387-
&& enabledProviderCount === 0
388-
&& recentDecisions.length === 0;
374+
let showBrainPrompt = false;
375+
if (!tourMode && decisionsFulfilled && recentDecisions.length === 0) {
376+
try {
377+
const settings = await fetchSettings(userId);
378+
const aiProviders = Array.isArray(settings?.aiProviders) ? settings.aiProviders : [];
379+
const enabledProviderCount = aiProviders.filter((p) => p?.enabled !== false).length;
380+
showBrainPrompt = enabledProviderCount === 0;
381+
} catch {
382+
// Settings fetch failed — don't surface the banner on a transient
383+
// error. The next render will retry.
384+
showBrainPrompt = false;
385+
}
386+
}
389387

390388
// "While you were away" — count anything new since the last visit so the
391389
// user feels like the twin has been working for them, not just sitting

0 commit comments

Comments
 (0)