evOo-local/apps/web/src/lib/onboarding-draft.ts
cynarAI 97787a0272
Production readiness phase 1: journey audit, fixes 1–4, E2E (#175)
* Production readiness phase 1: journeys doc, onboarding persistence, teardown, chat UX

- Add docs/PRODUCTION-READINESS.md with journey matrix and defect backlog
- Persist onboarding draft in session storage across reload
- Localize onboarding discovery main-bot role; German Chief of Staff string
- Destroy team sandboxes and home folders when deleting an account
- Keep composer/transcript visible during queued runs; extend send watchdog
- Add Playwright specs: reload persistence, DE intake, queued progress, console guard

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* Fix CI: sync Lingui catalogs, onboarding name draft, E2E stability

- Localize Chief of Staff in discovery shell; sync all locale PO files
- Propagate Oo name to onboarding draft while typing
- Adjust DE intake, queued-run, and console-error E2E specs

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* Format chat-queued-progress E2E spec

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* fix(api): scope account-delete sandbox teardown to owned spaces

Only destroy team sandboxes and homes for spaces the deleting user
owns with no remaining members or bots. Guard home removal under
dataDir/homes. Add unit and postgres journey coverage.

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

* chore: apply biome formatting

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
2026-10-07 11:25:39 +02:00

94 lines
2.7 KiB
TypeScript

import type { OnboardingEmojiConfig } from "../components/onboarding/OnboardingOoDesigner";
import type { UiLocale } from "./ui-locale";
export const ONBOARDING_DRAFT_STORAGE_KEY = "rakazo.onboardingDraft";
export type OnboardingStepDraft =
| "emoji"
| "name"
| "language"
| "voice"
| "integrations"
| "scan"
| "bot";
export type OnboardingDraft = {
step: OnboardingStepDraft;
emojiConfig: OnboardingEmojiConfig;
ooName: string;
languageChoice: UiLocale | null;
currentLocale: UiLocale;
selectedVoiceId: string;
voiceSkipped: boolean;
};
function readStorage(
storage: Pick<Storage, "getItem"> | null | undefined,
): Pick<Storage, "getItem"> | null {
if (storage !== undefined) return storage;
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
}
function writeStorage(
storage: Pick<Storage, "setItem" | "removeItem"> | null | undefined,
): Pick<Storage, "setItem" | "removeItem"> | null {
if (storage !== undefined) return storage;
return typeof sessionStorage !== "undefined" ? sessionStorage : null;
}
/** Restore in-progress onboarding after reload or deep link within the same tab. */
export function readOnboardingDraft(
storage: Pick<Storage, "getItem"> | null = readStorage(undefined),
): OnboardingDraft | null {
if (!storage) return null;
try {
const raw = storage.getItem(ONBOARDING_DRAFT_STORAGE_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw) as Partial<OnboardingDraft>;
if (
!parsed ||
typeof parsed.step !== "string" ||
!parsed.emojiConfig ||
typeof parsed.emojiConfig.avatarValue !== "string" ||
typeof parsed.emojiConfig.pose !== "string" ||
typeof parsed.ooName !== "string"
) {
return null;
}
return {
step: parsed.step as OnboardingStepDraft,
emojiConfig: parsed.emojiConfig,
ooName: parsed.ooName,
languageChoice: parsed.languageChoice ?? null,
currentLocale: parsed.currentLocale ?? "en",
selectedVoiceId: parsed.selectedVoiceId ?? "",
voiceSkipped: parsed.voiceSkipped ?? false,
};
} catch {
return null;
}
}
export function persistOnboardingDraft(
draft: OnboardingDraft,
storage: Pick<Storage, "setItem"> | null = writeStorage(undefined),
): void {
if (!storage) return;
try {
storage.setItem(ONBOARDING_DRAFT_STORAGE_KEY, JSON.stringify(draft));
} catch {
// Quota / private mode — onboarding still works without persistence.
}
}
export function clearOnboardingDraft(
storage: Pick<Storage, "removeItem"> | null = writeStorage(undefined),
): void {
if (!storage) return;
try {
storage.removeItem(ONBOARDING_DRAFT_STORAGE_KEY);
} catch {
// Ignore storage failures on completion.
}
}