Skip to content

Commit b00192b

Browse files
jayzalowitzclaude
andcommitted
fix(#187 follow-up): address Copilot review on dashboard brain prompt
Three findings from Copilot's review of PR #248, all addressed: - **Privacy copy was misleading**: "your data never leaves the device" was true only for the local brain path; the BYO API key path sends data to the third-party. Restructured the card so the privacy claim is scoped per-option: "Local brain — runs on your machine, no API keys, no per-message cost, your data never leaves the device" / "API key — uses Anthropic / OpenAI / Google. Faster on a small laptop, but each message goes to that provider." Headline now neutral: "Pick how your twin thinks." - **Stale provider state**: settingsData was wrapped in slowFetch with a 30s TTL, so a user who enabled their first AI provider in Settings and bounced back to the dashboard could keep seeing the prompt for half a minute. Switched to a direct fetchSettings() call — the endpoint is small and only relevant for first-run renders, so the cache savings weren't earned. - **False positive on transient API failure**: Both fetchSettings and fetchDecisions falling back to empty arrays meant a single blip would surface the onboarding prompt to users who actually have providers and decisions. Now requires positive evidence before showing: settingsFulfilled && decisionsFulfilled. Web build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2db0ef8 commit b00192b

1 file changed

Lines changed: 24 additions & 5 deletions

File tree

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

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -250,12 +250,16 @@ function renderBrainPrompt() {
250250
<span class="card-title">Your twin needs a brain to start</span>
251251
</div>
252252
<div class="card-subtitle" style="margin-bottom: 0.75rem; line-height: 1.5;">
253-
SkyTwin runs <strong>locally on your machine</strong> — no API keys, no per-message cost, your data never leaves the device. Pick up a free model in 5 minutes, or bring your own paid provider if you'd rather.
253+
Pick how your twin thinks. Either path takes about 5 minutes from Settings.
254254
</div>
255-
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
255+
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.75rem;">
256256
<a href="#/settings" class="btn btn-primary btn-sm">Set up the local brain</a>
257257
<a href="#/settings" class="btn btn-outline btn-sm">Or bring your own API key</a>
258258
</div>
259+
<div style="font-size: 0.8rem; color: var(--text-muted); line-height: 1.5;">
260+
<strong>Local brain</strong> — runs on your machine, no API keys, no per-message cost, your data never leaves the device.<br>
261+
<strong>API key</strong> — uses Anthropic / OpenAI / Google. Faster on a small laptop, but each message goes to that provider.
262+
</div>
259263
</div>
260264
`;
261265
}
@@ -295,7 +299,11 @@ export async function renderDashboard(container, userId) {
295299
fetchBriefing(userId),
296300
fetchLatestTwinBriefing(userId, 'daily').catch(() => null),
297301
slowFetch(`lifebooks-${userId}`, fetchLifebooks, [userId]),
298-
slowFetch(`settings-${userId}`, fetchSettings, [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),
299307
]);
300308

301309
const healthOk = health.status === 'fulfilled';
@@ -359,14 +367,25 @@ export async function renderDashboard(container, userId) {
359367

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

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.
362375
// Skip in tour mode — seeded demo user has providers pre-configured.
363-
const aiProviders = settingsData?.status === 'fulfilled'
376+
const settingsFulfilled = settingsData?.status === 'fulfilled';
377+
const decisionsFulfilled = decisions?.status === 'fulfilled';
378+
const aiProviders = settingsFulfilled
364379
? (settingsData.value?.aiProviders ?? [])
365380
: [];
366381
const enabledProviderCount = Array.isArray(aiProviders)
367382
? aiProviders.filter((p) => p?.enabled).length
368383
: 0;
369-
const showBrainPrompt = !tourMode && enabledProviderCount === 0 && recentDecisions.length === 0;
384+
const showBrainPrompt = !tourMode
385+
&& settingsFulfilled
386+
&& decisionsFulfilled
387+
&& enabledProviderCount === 0
388+
&& recentDecisions.length === 0;
370389

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

0 commit comments

Comments
 (0)