Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Scope onboarding hints to the guide and share the browser store
  • Loading branch information
bcotrim committed Jul 24, 2026
commit eb0e68088ebdb1e1b4876e7989ddfcafae4d9607
12 changes: 1 addition & 11 deletions apps/studio/src/modules/user-settings/lib/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,7 @@ export async function saveOnboardingHints(
await lockAppdata();
try {
const userData = await loadUserData();
const current = userData.onboardingHints ?? {};
// Shallow-merge, but merge completedItems by key so concurrent item
// completions never clobber one another.
const merged: OnboardingHintsState = {
...current,
...partial,
completedItems: {
...( current.completedItems ?? {} ),
...( partial.completedItems ?? {} ),
},
};
const merged: OnboardingHintsState = { ...( userData.onboardingHints ?? {} ), ...partial };
await saveUserData( { ...userData, onboardingHints: merged } );
} finally {
await unlockAppdata();
Expand Down
4 changes: 0 additions & 4 deletions apps/studio/src/storage/storage-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,6 @@ export interface PromptWindowsSpeedUpResult {
export interface OnboardingHintsState {
tourCompletedVersion?: number;
tourDismissedVersion?: number;
checklistDismissed?: boolean;
checklistMinimized?: boolean;
completedItems?: Record< string, string >;
publishCoachmarkShown?: boolean;
}

export const EMPTY_USER_DATA: UserData = {
Expand Down
21 changes: 21 additions & 0 deletions apps/ui/src/data/core/connectors/browser-onboarding-hints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { OnboardingHintsState } from '../types';

// Workbench onboarding state. The desktop persists this in appdata via IPC; the
// browser connectors (local + hosted) have no such store, so it lives in
// localStorage, per origin.
const ONBOARDING_HINTS_STORAGE_KEY = 'studio-onboarding-hints';

export function readOnboardingHints(): OnboardingHintsState {
try {
const raw = window.localStorage.getItem( ONBOARDING_HINTS_STORAGE_KEY );
const parsed: unknown = raw ? JSON.parse( raw ) : {};
return parsed && typeof parsed === 'object' ? ( parsed as OnboardingHintsState ) : {};
} catch {
return {};
}
}

export function writeOnboardingHints( partial: Partial< OnboardingHintsState > ): void {
const merged: OnboardingHintsState = { ...readOnboardingHints(), ...partial };
window.localStorage.setItem( ONBOARDING_HINTS_STORAGE_KEY, JSON.stringify( merged ) );
}
25 changes: 1 addition & 24 deletions apps/ui/src/data/core/connectors/hosted/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { fetchWordPressVersions } from '@studio/common/lib/wordpress-versions';
import { __ } from '@wordpress/i18n';
import { readOnboardingHints, writeOnboardingHints } from '../browser-onboarding-hints';
import { applyStoredSiteOrder, storeSiteOrder } from '../browser-site-order';
import { UnsupportedError } from '../unsupported-error';
import type {
Expand All @@ -11,7 +12,6 @@ import type {
Connector,
InstalledApps,
LoadedAiSession,
OnboardingHintsState,
SiteDetails,
Snapshot,
SnapshotUsage,
Expand All @@ -25,29 +25,6 @@ export interface HostedConnectorOptions {
apiBaseUrl: string;
}

// Workbench onboarding state persists per origin in the browser surface.
const ONBOARDING_HINTS_STORAGE_KEY = 'studio-onboarding-hints';

function readOnboardingHints(): OnboardingHintsState {
try {
const raw = window.localStorage.getItem( ONBOARDING_HINTS_STORAGE_KEY );
const parsed: unknown = raw ? JSON.parse( raw ) : {};
return parsed && typeof parsed === 'object' ? ( parsed as OnboardingHintsState ) : {};
} catch {
return {};
}
}

function writeOnboardingHints( partial: Partial< OnboardingHintsState > ): void {
const current = readOnboardingHints();
const merged: OnboardingHintsState = {
...current,
...partial,
completedItems: { ...( current.completedItems ?? {} ), ...( partial.completedItems ?? {} ) },
};
window.localStorage.setItem( ONBOARDING_HINTS_STORAGE_KEY, JSON.stringify( merged ) );
}

// Envelope used by the backend's `/events` SSE stream so a single connection
// can carry both agent-run events and session-placement updates.
type ServerEvent =
Expand Down
24 changes: 1 addition & 23 deletions apps/ui/src/data/core/connectors/local/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getAuthenticationUrl } from '@studio/common/lib/oauth';
import { fetchWordPressVersions } from '@studio/common/lib/wordpress-versions';
import { __ } from '@wordpress/i18n';
import { readOnboardingHints, writeOnboardingHints } from '../browser-onboarding-hints';
import { applyStoredSiteOrder, storeSiteOrder } from '../browser-site-order';
import { buildPublishCheckoutUrl } from '../publish-checkout-url';
import { UnsupportedError } from '../unsupported-error';
Expand All @@ -16,7 +17,6 @@ import type {
InstalledApps,
LoadedAiSession,
LocalMediaFile,
OnboardingHintsState,
ProposedSitePath,
QuitSitesBehavior,
SelectedSiteFolder,
Expand All @@ -40,28 +40,6 @@ const COLOR_SCHEME_STORAGE_KEY = 'studio-local-color-scheme';
const EDITOR_STORAGE_KEY = 'studio-local-editor';
const TERMINAL_STORAGE_KEY = 'studio-local-terminal';
const QUIT_SITES_BEHAVIOR_STORAGE_KEY = 'studio-local-quit-sites-behavior';
// Workbench onboarding state persists per origin in the browser surface.
const ONBOARDING_HINTS_STORAGE_KEY = 'studio-onboarding-hints';

function readOnboardingHints(): OnboardingHintsState {
try {
const raw = window.localStorage.getItem( ONBOARDING_HINTS_STORAGE_KEY );
const parsed: unknown = raw ? JSON.parse( raw ) : {};
return parsed && typeof parsed === 'object' ? ( parsed as OnboardingHintsState ) : {};
} catch {
return {};
}
}

function writeOnboardingHints( partial: Partial< OnboardingHintsState > ): void {
const current = readOnboardingHints();
const merged: OnboardingHintsState = {
...current,
...partial,
completedItems: { ...( current.completedItems ?? {} ), ...( partial.completedItems ?? {} ) },
};
window.localStorage.setItem( ONBOARDING_HINTS_STORAGE_KEY, JSON.stringify( merged ) );
}

function parseQuitSitesBehavior( value: string | null ): QuitSitesBehavior | undefined {
return value === 'leave-running' || value === 'stop-and-auto-start' || value === 'stop'
Expand Down
1 change: 0 additions & 1 deletion apps/ui/src/data/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export type {
AppGlobals,
AppUpdateStatus,
AuthUser,
ChecklistItemId,
ColorScheme,
Connector,
CreateSiteParams,
Expand Down
33 changes: 6 additions & 27 deletions apps/ui/src/data/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,10 +424,9 @@ export interface Connector {
// Switches back to the legacy (classic) Studio UI.
disableAgenticUi(): Promise< void >;

// Agentic UI onboarding state (orientation tour + getting-started
// checklist). Distinct from getOnboardingCompleted (the pre-workbench
// first-run welcome flag). setOnboardingHints shallow-merges its partial;
// completedItems is merged by key. Hosted/web persist to localStorage.
// Agentic UI onboarding state. Distinct from getOnboardingCompleted (the
// pre-workbench first-run welcome flag). setOnboardingHints shallow-merges
// its partial. Desktop persists to app.json; hosted/web to localStorage.
getOnboardingHints(): Promise< OnboardingHintsState >;
setOnboardingHints( partial: Partial< OnboardingHintsState > ): Promise< void >;

Expand All @@ -446,33 +445,13 @@ export interface AppUpdateStatus {
version: string | null;
}

// Getting-started checklist item ids. Kept as a closed union so the checklist
// definitions, completion watchers, and persistence all agree on the set.
export type ChecklistItemId =
| 'create-site'
| 'first-agent-edit'
| 'visit-overview'
| 'publish-site'
| 'visit-app-settings'
| 'visit-site-settings';

// Persisted first-run onboarding state for the workbench. Separate from the
// pre-workbench welcome flag (getOnboardingCompleted) and from dismissed
// messages (which are append-only and so can't model replay/un-dismiss).
// pre-workbench welcome flag (getOnboardingCompleted).
export interface OnboardingHintsState {
// Version of the orientation tour the user finished or explicitly skipped.
// Version of the orientation guide the user finished or explicitly skipped.
tourCompletedVersion?: number;
// Version of the orientation tour the user closed early (Esc / X).
// Version of the orientation guide the user closed early (Esc / Skip).
tourDismissedVersion?: number;
// True once the getting-started checklist has been dismissed. Replay clears
// this — hence it can't ride the append-only dismissedMessages store.
checklistDismissed?: boolean;
// True while the checklist is collapsed to its compact (toast-like) bar.
checklistMinimized?: boolean;
// Completed checklist items → ISO timestamp of completion.
completedItems?: Partial< Record< ChecklistItemId, string > >;
// True once the one-shot publish coachmark has been shown (never re-fires).
publishCoachmarkShown?: boolean;
}

export interface SnapshotUsage {
Expand Down
18 changes: 1 addition & 17 deletions apps/ui/src/data/queries/use-onboarding-hints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,6 @@ export function useOnboardingCompleted() {
} );
}

// Shallow-merge a partial into the cached hints, merging completedItems by key
// so a checklist completion never clobbers a concurrent one.
function mergeHints(
current: OnboardingHintsState | undefined,
partial: Partial< OnboardingHintsState >
): OnboardingHintsState {
return {
...( current ?? {} ),
...partial,
completedItems: {
...( current?.completedItems ?? {} ),
...( partial.completedItems ?? {} ),
},
};
}

export function useSetOnboardingHints() {
const connector = useConnector();
const queryClient = useQueryClient();
Expand All @@ -58,7 +42,7 @@ export function useSetOnboardingHints() {
onMutate: ( partial ) => {
queryClient.setQueryData(
ONBOARDING_HINTS_QUERY_KEY,
( current: OnboardingHintsState | undefined ) => mergeHints( current, partial )
( current: OnboardingHintsState | undefined ) => ( { ...( current ?? {} ), ...partial } )
);
},
} );
Expand Down
Loading