Fix first-run onboarding UX: naming, locale, voices, Heute route, mobile drawer

Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-10-06 10:38:19 +00:00
parent ba89574c83
commit 225dc84b20
No known key found for this signature in database
26 changed files with 641 additions and 50 deletions

View File

@ -0,0 +1,76 @@
import { expect, test } from "@playwright/test";
import { completeOnboarding, rpc, signup } from "./helpers";
test("onboarding names the first Oo and shows it in chat", async ({ page }) => {
const stamp = Date.now();
await signup(page, `name-oo-${stamp}@rakazo.test`, "password12", "Namer");
await page.waitForURL(/\/onboarding/, { timeout: 20_000 });
await page.getByRole("button", { name: "Continue", exact: true }).click();
await expect(page.getByTestId("onboarding-name-step")).toBeVisible();
await page.getByTestId("onboarding-oo-name-input").fill("Luna");
await page.getByTestId("onboarding-name-step").getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: /Skip/i }).click();
await page.getByRole("button", { name: "Skip", exact: true }).click();
await expect(page).toHaveURL(/\/app\/g\/[^/]+/, { timeout: 30_000 });
await expect(page.getByTestId("bot-settings-trigger").getByText("Luna", { exact: true })).toBeVisible();
expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Luna");
});
test("German UI keeps Deutsch on language skip label", async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(navigator, "language", { value: "de-DE", configurable: true });
});
const stamp = Date.now();
await signup(page, `de-skip-${stamp}@rakazo.test`, "password12", "Deutsch");
await page.getByRole("button", { name: "Continue", exact: true }).click();
await page.getByRole("button", { name: "Skip", exact: true }).click();
await expect(page.getByRole("heading", { name: /Sprache wählen|Choose your language/i })).toBeVisible();
await expect(page.getByRole("button", { name: /Überspringen · Deutsch/i })).toBeVisible();
});
test("German voice step lists German voices after choosing Deutsch", async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(navigator, "language", { value: "de-DE", configurable: true });
});
const stamp = Date.now();
await signup(page, `de-voice-${stamp}@rakazo.test`, "password12", "Stimme");
await page.getByRole("button", { name: "Continue", exact: true }).click();
await page.getByRole("button", { name: "Skip", exact: true }).click();
await page.getByRole("button", { name: "Deutsch", exact: true }).click();
await expect(page.getByRole("button", { name: "Eva", exact: true })).toBeVisible();
await expect(page.getByRole("button", { name: "Alloy", exact: true })).toHaveCount(0);
await expect(page.getByRole("button", { name: "Eva", exact: true })).toHaveAttribute(
"aria-pressed",
"true",
);
});
test("main conversation does not show a group subtitle for one Oo", async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(navigator, "language", { value: "de-DE", configurable: true });
});
const stamp = Date.now();
await signup(page, `main-1-1-${stamp}@rakazo.test`, "password12", "Solo");
await completeOnboarding(page, undefined, { mainConversation: true });
await expect(page.getByTestId("bot-settings-trigger")).not.toContainText("Gruppenunterhaltung");
await expect(page.getByTestId("oo-group-stage")).toContainText(/1:1/i);
});
test.describe("mobile shell", () => {
test.use({
hasTouch: true,
isMobile: true,
viewport: { width: 390, height: 844 },
});
test("Escape closes the mobile navigation drawer", async ({ page }) => {
const stamp = Date.now();
await signup(page, `mobile-esc-${stamp}@rakazo.test`, "password12", "Escape");
await completeOnboarding(page);
await page.getByRole("button", { name: "Open navigation" }).click();
await expect(page.getByRole("button", { name: "Close navigation" })).toBeVisible();
await page.keyboard.press("Escape");
await expect(page.getByRole("button", { name: "Close navigation" })).toHaveCount(0);
await expect(page.getByTestId("open-navigation")).toBeFocused();
});
});

View File

@ -95,23 +95,25 @@ async function visible(locator: Locator, timeout: number) {
export async function completeOnboarding( export async function completeOnboarding(
page: Page, page: Page,
testInfo?: TestInfo, testInfo?: TestInfo,
options: { mainConversation?: boolean } = {}, options: { mainConversation?: boolean; ooName?: string } = {},
) { ) {
await page.waitForURL(/\/(onboarding|app)/, { timeout: 20_000 }); await page.waitForURL(/\/(onboarding|app)/, { timeout: 20_000 });
if (page.url().includes("/onboarding")) { if (page.url().includes("/onboarding")) {
await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute( await expect(page.locator('[data-rakazo-surface="onboarding"]')).toHaveAttribute(
"data-onboarding-step", "data-onboarding-step",
/^(emoji|language|voice|integrations|scan|bot)$/, /^(emoji|name|language|voice|integrations|scan|bot)$/,
{ timeout: 30_000 }, { timeout: 30_000 },
); );
} }
const design = page.getByRole("heading", { name: /Design your Oo/i }); const design = page.getByRole("heading", { name: /Design your Oo/i });
const naming = page.getByTestId("onboarding-name-step");
const language = page.getByRole("heading", { name: /Choose your language/i }); const language = page.getByRole("heading", { name: /Choose your language/i });
const voice = page.getByRole("heading", { name: /Pick a voice/i }); const voice = page.getByRole("heading", { name: /Pick a voice/i });
const integrations = page.getByRole("heading", { name: "Server integrations", exact: true }); const integrations = page.getByRole("heading", { name: "Server integrations", exact: true });
const chief = page.getByText("Chief").first(); const expectedOoName = options.ooName ?? "Chief";
const chief = page.getByText(expectedOoName).first();
// Scripted CI has needsModel false, so a fresh owner starts on integrations. // Scripted CI has needsModel false, so a fresh owner starts on integrations.
// A model-gated run still opens the look picker first. // A model-gated run still opens the look picker first.
@ -120,6 +122,13 @@ export async function completeOnboarding(
if (await design.isVisible()) { if (await design.isVisible()) {
await page.getByRole("button", { name: "Continue", exact: true }).click(); await page.getByRole("button", { name: "Continue", exact: true }).click();
await expect(naming).toBeVisible();
if (options.ooName) {
await page.getByTestId("onboarding-oo-name-input").fill(options.ooName);
await naming.getByRole("button", { name: "Continue", exact: true }).click();
} else {
await page.getByRole("button", { name: "Skip", exact: true }).click();
}
await expect(language).toBeVisible(); await expect(language).toBeVisible();
await page.getByRole("button", { name: /Skip/i }).click(); await page.getByRole("button", { name: /Skip/i }).click();
await expect(voice).toBeVisible(); await expect(voice).toBeVisible();

View File

@ -18,7 +18,10 @@ test("Heute shows native brief instead of Mission Control iframe", async ({ page
await completeOnboarding(page); await completeOnboarding(page);
await openHeute(page); await openHeute(page);
await expect(page).toHaveURL(/\/app\/[^/]+$/); await expect(page).toHaveURL(/\/app\/heute$/);
await page.reload();
await expect(page.getByTestId("heute-brief")).toBeVisible();
await expect(page).toHaveURL(/\/app\/heute$/);
const brief = page.getByTestId("heute-brief"); const brief = page.getByTestId("heute-brief");
await expect(brief).toBeVisible(); await expect(brief).toBeVisible();

View File

@ -11,7 +11,7 @@ test("focus choice suggests apps and preserves a completed connection", async ({
const stamp = Date.now(); const stamp = Date.now();
await signup(page, `onboarding-${stamp}@rakazo.test`, "password12", "Robin"); await signup(page, `onboarding-${stamp}@rakazo.test`, "password12", "Robin");
// The main bot greets the user in the main conversation, where onboarding lands. // The main bot greets the user in the main conversation, where onboarding lands.
await completeOnboarding(page, undefined, { mainConversation: true }); await completeOnboarding(page, undefined, { mainConversation: true, ooName: "Robin" });
await expect( await expect(
page.getByText("Hey Robin. Fresh start on my side, so I’ll keep this short."), page.getByText("Hey Robin. Fresh start on my side, so I’ll keep this short."),
@ -31,12 +31,11 @@ test("focus choice suggests apps and preserves a completed connection", async ({
await captureScreenshot(page, testInfo, "choice-card-onboarding"); await captureScreenshot(page, testInfo, "choice-card-onboarding");
await page.getByTestId("focus-choice-day").click(); await page.getByTestId("focus-choice-day").click();
// The focus step suggests apps but must not rename the bot: the name the // The focus step must not rename the bot: onboarding keeps the chosen name.
// user chose during creation ("Chief") is preserved.
await expect( await expect(
page.getByTestId("bot-settings-trigger").getByText("Chief", { exact: true }), page.getByTestId("bot-settings-trigger").getByText("Robin", { exact: true }),
).toBeVisible(); ).toBeVisible();
expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Chief"); expect((await rpc<{ name: string }>(page, "bots/ensureMain", {})).name).toBe("Robin");
await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible(); await expect(page.getByRole("combobox", { name: "Message" })).toBeVisible();
// The app cards speak for themselves; onboarding adds no text around them. // The app cards speak for themselves; onboarding adds no text around them.
await expect(page.getByText(/^Got it/)).toHaveCount(0); await expect(page.getByText(/^Got it/)).toHaveCount(0);
@ -103,7 +102,7 @@ test("focus choice suggests apps and preserves a completed connection", async ({
test("choice refresh failures leave options available for retry", async ({ page }) => { test("choice refresh failures leave options available for retry", async ({ page }) => {
await signup(page, `choice-refresh-${Date.now()}@rakazo.test`, "password12", "Choice Retry"); await signup(page, `choice-refresh-${Date.now()}@rakazo.test`, "password12", "Choice Retry");
await completeOnboarding(page, undefined, { mainConversation: true }); await completeOnboarding(page, undefined, { mainConversation: true, ooName: "Robin" });
const choice = page.getByTestId("focus-choice-day"); const choice = page.getByTestId("focus-choice-day");
await expect(choice).toBeEnabled(); await expect(choice).toBeEnabled();
// Keep the existing choice rendered while its save succeeds and navigation refresh fails. // Keep the existing choice rendered while its save succeeds and navigation refresh fails.

View File

@ -120,6 +120,10 @@ function SessionApp() {
} }
/> />
<Route path="/app" element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />} /> <Route path="/app" element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />} />
<Route
path="/app/heute"
element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />}
/>
<Route <Route
path="/app/g/:groupId" path="/app/g/:groupId"
element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />} element={user ? <ShellPage /> : <Navigate to="/sign-in" replace />}

View File

@ -0,0 +1,98 @@
import { Trans, useLingui } from "@lingui/react/macro";
import type { EvooPose } from "@rakazo/ui-web";
import { BotAvatar, Button } from "@rakazo/ui-web";
import { useId, useState } from "react";
import type { OnboardingEmojiConfig } from "./OnboardingOoDesigner";
export const DEFAULT_FIRST_OO_NAME = "Chief";
type OnboardingNameStepProps = {
config: OnboardingEmojiConfig;
name: string;
onNameChange: (name: string) => void;
onBack: () => void;
onContinue: () => void;
};
export function OnboardingNameStep({
config,
name,
onNameChange,
onBack,
onContinue,
}: OnboardingNameStepProps) {
const { t } = useLingui();
const inputId = useId();
const [draft, setDraft] = useState(name);
function commitAndContinue() {
const trimmed = draft.trim() || DEFAULT_FIRST_OO_NAME;
onNameChange(trimmed);
onContinue();
}
return (
<div className="mx-auto max-w-xl" data-testid="onboarding-name-step">
<h2 className="text-xl font-semibold tracking-tight text-foreground">
<Trans>Name your Oo</Trans>
</h2>
<p className="mt-1 text-sm text-muted-foreground">
<Trans>Something short and yours — or keep the default and rename later.</Trans>
</p>
<div className="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:items-start">
<BotAvatar
color={config.avatarValue}
size={120}
identity="onboarding-name-preview"
pose={config.pose as EvooPose}
interactive
className="shrink-0"
/>
<div className="w-full min-w-0 flex-1">
<label htmlFor={inputId} className="sr-only">
<Trans>Oo name</Trans>
</label>
<input
id={inputId}
data-testid="onboarding-oo-name-input"
type="text"
maxLength={48}
autoComplete="off"
autoCapitalize="words"
placeholder={DEFAULT_FIRST_OO_NAME}
value={draft}
onChange={(event) => setDraft(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") commitAndContinue();
}}
className="w-full rounded-2xl border-2 border-border bg-card px-4 py-3 text-lg font-semibold tracking-tight text-foreground shadow-sm outline-none transition-colors placeholder:text-muted-foreground/60 focus:border-primary"
/>
<p className="mt-2 text-xs text-muted-foreground">
{t`Press Enter or Continue — skip keeps ${DEFAULT_FIRST_OO_NAME}.`}
</p>
</div>
</div>
<div className="mt-8 flex items-center justify-between">
<Button variant="ghost" size="sm" onClick={onBack}>
<Trans>Back</Trans>
</Button>
<div className="flex gap-2">
<Button
variant="ghost"
onClick={() => {
onNameChange(DEFAULT_FIRST_OO_NAME);
onContinue();
}}
>
<Trans>Skip</Trans>
</Button>
<Button onClick={commitAndContinue}>
<Trans>Continue</Trans>
</Button>
</div>
</div>
</div>
);
}

View File

@ -1,15 +1,20 @@
import { Trans } from "@lingui/react/macro"; import { Trans } from "@lingui/react/macro";
import type { VoiceInfo } from "@rakazo/contracts"; import type { VoiceInfo } from "@rakazo/contracts";
import { defaultVoiceForLocale, voicesForLocale } from "@rakazo/core"; import { voicesForLocale } from "@rakazo/core";
import { Button } from "@rakazo/ui-web"; import { Button } from "@rakazo/ui-web";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import {
defaultOnboardingVoiceId,
offlineOnboardingVoices,
} from "../../lib/onboarding-voices";
import { rpc } from "../../lib/rpc"; import { rpc } from "../../lib/rpc";
import { readUiLocale } from "../../lib/ui-locale"; import type { UiLocale } from "../../lib/ui-locale";
/** Must match packages/adapters KokoroVoiceProvider placeholder. */ /** Must match packages/adapters KokoroVoiceProvider placeholder. */
export const KOKORO_LOCAL_TOKEN = "local-kokoro"; export const KOKORO_LOCAL_TOKEN = "local-kokoro";
type OnboardingVoiceStepProps = { type OnboardingVoiceStepProps = {
uiLocale: UiLocale;
selectedVoiceId: string; selectedVoiceId: string;
onSelectedVoiceIdChange: (voiceId: string) => void; onSelectedVoiceIdChange: (voiceId: string) => void;
onBack: () => void; onBack: () => void;
@ -18,6 +23,7 @@ type OnboardingVoiceStepProps = {
}; };
export function OnboardingVoiceStep({ export function OnboardingVoiceStep({
uiLocale,
selectedVoiceId, selectedVoiceId,
onSelectedVoiceIdChange, onSelectedVoiceIdChange,
onBack, onBack,
@ -36,13 +42,17 @@ export function OnboardingVoiceStep({
await rpc.voice.connect({ provider: "kokoro", apiKey: KOKORO_LOCAL_TOKEN }); await rpc.voice.connect({ provider: "kokoro", apiKey: KOKORO_LOCAL_TOKEN });
const listed = await rpc.voice.voices({ provider: "kokoro" }); const listed = await rpc.voice.voices({ provider: "kokoro" });
if (cancelled) return; if (cancelled) return;
const locale = readUiLocale(); const ordered = voicesForLocale(listed, uiLocale).slice(0, 6);
const ordered = voicesForLocale(listed, locale).slice(0, 6);
setVoiceOptions(ordered); setVoiceOptions(ordered);
const defaultVoice = defaultVoiceForLocale(listed, locale); const nextId = defaultOnboardingVoiceId(uiLocale, ordered);
if (defaultVoice) onSelectedVoiceIdChange(defaultVoice.id); if (nextId) onSelectedVoiceIdChange(nextId);
} catch { } catch {
if (!cancelled) setVoiceOptions([]); if (!cancelled) {
const fallback = offlineOnboardingVoices(uiLocale);
setVoiceOptions(fallback);
const nextId = defaultOnboardingVoiceId(uiLocale, fallback);
if (nextId) onSelectedVoiceIdChange(nextId);
}
} finally { } finally {
if (!cancelled) setVoiceLoading(false); if (!cancelled) setVoiceLoading(false);
} }
@ -54,7 +64,7 @@ export function OnboardingVoiceStep({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [onSelectedVoiceIdChange]); }, [onSelectedVoiceIdChange, uiLocale]);
async function skipVoice() { async function skipVoice() {
await previewTask.current; await previewTask.current;

View File

@ -1,4 +1,5 @@
import { useLingui } from "@lingui/react/macro"; import { useLingui } from "@lingui/react/macro";
import { isMultiOoGroupChat } from "../../lib/group-chat";
import type { ThreadSnapshot } from "@rakazo/contracts"; import type { ThreadSnapshot } from "@rakazo/contracts";
import { BotAvatar } from "@rakazo/ui-web"; import { BotAvatar } from "@rakazo/ui-web";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
@ -317,6 +318,7 @@ export function OoGroupStage({
[snapshot, speaker, enrichMember], [snapshot, speaker, enrichMember],
); );
const roster = snapshot?.members ?? []; const roster = snapshot?.members ?? [];
const multiOo = isMultiOoGroupChat(roster.length);
const groupSpark = useMemo( const groupSpark = useMemo(
() => extractGroupSpark(snapshot, workingBots.length), () => extractGroupSpark(snapshot, workingBots.length),
[snapshot, workingBots.length], [snapshot, workingBots.length],
@ -405,7 +407,7 @@ export function OoGroupStage({
if (sparkLabel && groupSpark?.kind === "peer-reply") return sparkLabel; if (sparkLabel && groupSpark?.kind === "peer-reply") return sparkLabel;
if (awarenessLabel) return awarenessLabel; if (awarenessLabel) return awarenessLabel;
if (sparkLabel) return sparkLabel; if (sparkLabel) return sparkLabel;
if (!speaker) return t`Group conversation`; if (!speaker) return multiOo ? t`Group conversation` : t`1:1 conversation`;
if (running && workingBots.length > 1) { if (running && workingBots.length > 1) {
return t`${workingBots.length} Oos are working — ${speaker.name} has the floor`; return t`${workingBots.length} Oos are working — ${speaker.name} has the floor`;
} }
@ -429,8 +431,15 @@ export function OoGroupStage({
thinkingIndex, thinkingIndex,
thinkingLines, thinkingLines,
workingBots.length, workingBots.length,
multiOo,
]); ]);
const headerSubtitle = multiOo
? t`Group conversation`
: speaker
? t`1:1 with ${speaker.name}`
: t`1:1 conversation`;
const energy = groupStageEnergy(phase, groupSpark, workingBots.length); const energy = groupStageEnergy(phase, groupSpark, workingBots.length);
const phaseLabel = showEnsembleOnly const phaseLabel = showEnsembleOnly
@ -454,7 +463,7 @@ export function OoGroupStage({
return ( return (
<section <section
data-testid="oo-group-stage" data-testid="oo-group-stage"
aria-label={t`Group conversation`} aria-label={multiOo ? t`Group conversation` : t`1:1 conversation`}
className={`relative shrink-0 overflow-hidden border-b border-border/60 ${hasMessages ? "max-h-[28vh]" : ""}`} className={`relative shrink-0 overflow-hidden border-b border-border/60 ${hasMessages ? "max-h-[28vh]" : ""}`}
> >
<TextStageCanvas <TextStageCanvas
@ -479,7 +488,7 @@ export function OoGroupStage({
<h2 className="mt-1.5 truncate text-ui-title font-semibold tracking-tight text-foreground"> <h2 className="mt-1.5 truncate text-ui-title font-semibold tracking-tight text-foreground">
{groupName ?? t`Group`} {groupName ?? t`Group`}
</h2> </h2>
<p className="truncate text-ui-body-sm text-muted-foreground">{t`Group conversation`}</p> <p className="truncate text-ui-body-sm text-muted-foreground">{headerSubtitle}</p>
</div> </div>
{awarenessLabel && awarenessEvent && speaker ? ( {awarenessLabel && awarenessEvent && speaker ? (

View File

@ -0,0 +1,5 @@
export const HEUTE_APP_PATH = "/app/heute";
export function isHeuteAppPath(pathname: string): boolean {
return pathname === HEUTE_APP_PATH || pathname.endsWith(`${HEUTE_APP_PATH}`);
}

View File

@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import { isMultiOoGroupChat } from "./group-chat.js";
describe("isMultiOoGroupChat", () => {
it("treats a single Oo as 1:1", () => {
expect(isMultiOoGroupChat(0)).toBe(false);
expect(isMultiOoGroupChat(1)).toBe(false);
});
it("treats two or more Oos as a group", () => {
expect(isMultiOoGroupChat(2)).toBe(true);
expect(isMultiOoGroupChat(5)).toBe(true);
});
});

View File

@ -0,0 +1,4 @@
/** True when the encounter has more than one Oo (group chat), not a 1:1 thread. */
export function isMultiOoGroupChat(memberCount: number): boolean {
return memberCount > 1;
}

View File

@ -0,0 +1,25 @@
import { describe, expect, it } from "vitest";
import { defaultOnboardingVoiceId, offlineOnboardingVoices } from "./onboarding-voices.js";
describe("offlineOnboardingVoices", () => {
it("offers German voices for de", () => {
expect(offlineOnboardingVoices("de").map((voice) => voice.id)).toEqual([
"df_eva",
"dm_bernd",
"df_victoria",
"dm_martin",
]);
});
it("offers English voices for en", () => {
const ids = offlineOnboardingVoices("en").map((voice) => voice.id);
expect(ids).toContain("af_heart");
expect(ids).not.toContain("df_eva");
});
});
describe("defaultOnboardingVoiceId", () => {
it("prefers df_eva for German", () => {
expect(defaultOnboardingVoiceId("de", offlineOnboardingVoices("de"))).toBe("df_eva");
});
});

View File

@ -0,0 +1,23 @@
import type { VoiceInfo } from "@rakazo/contracts";
import { defaultVoiceForLocale, GERMAN_VOICE_PRESETS, voicesForLocale } from "@rakazo/core";
/** English presets when the local voice server is unreachable during onboarding. */
const ENGLISH_ONBOARDING_VOICES: VoiceInfo[] = [
{ id: "af_heart", label: "Heart", description: "warm" },
{ id: "af_bella", label: "Bella", description: "expressive" },
{ id: "af_sky", label: "Sky", description: "bright" },
{ id: "af_nova", label: "Nova", description: "energetic" },
{ id: "am_adam", label: "Adam", description: "clear" },
{ id: "am_michael", label: "Michael", description: "steady" },
];
const OFFLINE_CATALOG = [...GERMAN_VOICE_PRESETS, ...ENGLISH_ONBOARDING_VOICES];
/** Locale-aware voice choices for onboarding when listing from the server fails. */
export function offlineOnboardingVoices(locale: string): VoiceInfo[] {
return voicesForLocale(OFFLINE_CATALOG, locale).slice(0, 6);
}
export function defaultOnboardingVoiceId(locale: string, voices: VoiceInfo[]): string {
return defaultVoiceForLocale(voices, locale)?.id ?? "";
}

View File

@ -303,6 +303,13 @@ msgstr "1:1-Unterhaltung"
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "1:1-Unterhaltung mit {title}" msgstr "1:1-Unterhaltung mit {title}"
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr "1:1 mit {0}"
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "Über Gruppen" msgstr "Über Gruppen"
@ -783,6 +790,7 @@ msgstr "Verfügbar, sobald die Routine gespeichert ist"
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "Kontextlimit" msgstr "Kontextlimit"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "Gruppe benennen"
msgid "Name this routine" msgid "Name this routine"
msgstr "Routine benennen" msgstr "Routine benennen"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr "Gib deinem Oo einen Namen"
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "Hals" msgstr "Hals"
@ -3627,6 +3640,10 @@ msgstr "Nur leere Spaces können gelöscht werden. Lösche zuerst die Bots und G
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "Gelesen werden nur Datei- und Ordnernamen." msgstr "Gelesen werden nur Datei- und Ordnernamen."
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr "Name des Oo"
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr "Vorlieben"
msgid "Present" msgid "Present"
msgstr "Anwesend" msgstr "Anwesend"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr "Enter oder Weiter — Überspringen behält Chief."
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "Skills" msgstr "Skills"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,9 +4603,10 @@ msgstr "Skills"
msgid "Skip" msgid "Skip"
msgstr "Überspringen" msgstr "Überspringen"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "Überspringen · Englisch" msgstr "Überspringen · {0}"
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
msgid "Skip or deploy key" msgid "Skip or deploy key"
@ -4614,6 +4637,10 @@ msgstr "Software-Update"
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "Etwas braucht deine Aufmerksamkeit." msgstr "Etwas braucht deine Aufmerksamkeit."
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr "Kurz und persönlich — oder Standard behalten und später umbenennen."
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "Sortiere die Prioritäten…" msgstr "Sortiere die Prioritäten…"

View File

@ -303,6 +303,13 @@ msgstr "1:1 conversation"
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "1:1 conversation with {title}" msgstr "1:1 conversation with {title}"
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr "1:1 with {0}"
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "About groups" msgstr "About groups"
@ -783,6 +790,7 @@ msgstr "Available after the routine is saved"
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "Context limit" msgstr "Context limit"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "Name this group"
msgid "Name this routine" msgid "Name this routine"
msgstr "Name this routine" msgstr "Name this routine"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr "Name your Oo"
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "Neck" msgstr "Neck"
@ -3627,6 +3640,10 @@ msgstr "Only empty spaces can be deleted. Delete its bots and groups first."
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "Only file and folder names are read." msgstr "Only file and folder names are read."
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr "Oo name"
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr "Preferences"
msgid "Present" msgid "Present"
msgstr "Present" msgstr "Present"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "Skills" msgstr "Skills"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,9 +4603,10 @@ msgstr "Skills"
msgid "Skip" msgid "Skip"
msgstr "Skip" msgstr "Skip"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "Skip · English" msgstr "Skip · {0}"
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
msgid "Skip or deploy key" msgid "Skip or deploy key"
@ -4614,6 +4637,10 @@ msgstr "Software update"
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "Something needs your attention." msgstr "Something needs your attention."
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr "Something short and yours — or keep the default and rename later."
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "Sorting priorities…" msgstr "Sorting priorities…"

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "" msgstr ""
@ -783,6 +790,7 @@ msgstr "Disponible después de guardar la rutina"
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "Límite de contexto" msgstr "Límite de contexto"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "Nombra este grupo"
msgid "Name this routine" msgid "Name this routine"
msgstr "Nombra esta rutina" msgstr "Nombra esta rutina"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr ""
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "Habilidades" msgstr "Habilidades"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "Habilidades"
msgid "Skip" msgid "Skip"
msgstr "Omitir" msgstr "Omitir"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr "Actualización de software"
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "" msgstr ""
@ -783,6 +790,7 @@ msgstr ""
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "कॉन्टेक्स्ट सीमा" msgstr "कॉन्टेक्स्ट सीमा"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "इस समूह को नाम दें"
msgid "Name this routine" msgid "Name this routine"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr ""
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "स्किल्स" msgstr "स्किल्स"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "स्किल्स"
msgid "Skip" msgid "Skip"
msgstr "छोड़ें" msgstr "छोड़ें"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr ""
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "" msgstr ""
@ -783,6 +790,7 @@ msgstr "루틴을 저장한 후 사용할 수 있습니다"
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "컨텍스트 한도" msgstr "컨텍스트 한도"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "그룹 이름"
msgid "Name this routine" msgid "Name this routine"
msgstr "이 루틴의 이름을 정하세요" msgstr "이 루틴의 이름을 정하세요"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr ""
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "스킬" msgstr "스킬"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "스킬"
msgid "Skip" msgid "Skip"
msgstr "건너뛰기" msgstr "건너뛰기"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr "소프트웨어 업데이트"
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "" msgstr ""
@ -783,6 +790,7 @@ msgstr ""
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "Limite de contexto" msgstr "Limite de contexto"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "Dar nome ao grupo"
msgid "Name this routine" msgid "Name this routine"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr ""
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "Habilidades" msgstr "Habilidades"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "Habilidades"
msgid "Skip" msgid "Skip"
msgstr "Pular" msgstr "Pular"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr ""
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "О группах" msgstr "О группах"
@ -783,6 +790,7 @@ msgstr "Доступно после сохранения задачи"
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "" msgstr ""
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "Назовите эту группу"
msgid "Name this routine" msgid "Name this routine"
msgstr "Назовите эту задачу" msgstr "Назовите эту задачу"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr "Удалить можно только пустые пространст
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "Навыки" msgstr "Навыки"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "Навыки"
msgid "Skip" msgid "Skip"
msgstr "Пропустить" msgstr "Пропустить"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr "Обновление программного обеспечения"
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "" msgstr ""
@ -783,6 +790,7 @@ msgstr ""
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "Bağlam sınırı" msgstr "Bağlam sınırı"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "Gruba bir ad ver"
msgid "Name this routine" msgid "Name this routine"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr ""
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "Beceriler" msgstr "Beceriler"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "Beceriler"
msgid "Skip" msgid "Skip"
msgstr "Atla" msgstr "Atla"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr ""
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -303,6 +303,13 @@ msgstr ""
msgid "1:1 conversation with {title}" msgid "1:1 conversation with {title}"
msgstr "" msgstr ""
#. placeholder {0}: mainConversationEntry.bot.name
#. placeholder {0}: speaker.name
#: src/components/stage/OoStage.tsx
#: src/pages/Shell.tsx
msgid "1:1 with {0}"
msgstr ""
#: src/pages/shell/bot-picker.tsx #: src/pages/shell/bot-picker.tsx
msgid "About groups" msgid "About groups"
msgstr "" msgstr ""
@ -783,6 +790,7 @@ msgstr "保存例行任务后可用"
#: src/components/browser/InAppBrowser.tsx #: src/components/browser/InAppBrowser.tsx
#: src/components/CookingMode.tsx #: src/components/CookingMode.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
#: src/pages/PluginsOverlay.tsx #: src/pages/PluginsOverlay.tsx
@ -1423,6 +1431,7 @@ msgid "Context limit"
msgstr "上下文限制" msgstr "上下文限制"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingOoDesigner.tsx #: src/components/onboarding/OnboardingOoDesigner.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
@ -3317,6 +3326,10 @@ msgstr "给这个群组命名"
msgid "Name this routine" msgid "Name this routine"
msgstr "给这个例行任务命名" msgstr "给这个例行任务命名"
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Name your Oo"
msgstr ""
#: src/components/OoAtelier.tsx #: src/components/OoAtelier.tsx
msgid "Neck" msgid "Neck"
msgstr "" msgstr ""
@ -3627,6 +3640,10 @@ msgstr ""
msgid "Only file and folder names are read." msgid "Only file and folder names are read."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Oo name"
msgstr ""
#: src/pages/ScratchpadSection.tsx #: src/pages/ScratchpadSection.tsx
#: src/pages/shell/computer-panel-preview.tsx #: src/pages/shell/computer-panel-preview.tsx
msgid "Open" msgid "Open"
@ -3833,6 +3850,10 @@ msgstr ""
msgid "Present" msgid "Present"
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Press Enter or Continue — skip keeps {DEFAULT_FIRST_OO_NAME}."
msgstr ""
#. placeholder {0}: props.name #. placeholder {0}: props.name
#: src/components/ArtifactFileCard.tsx #: src/components/ArtifactFileCard.tsx
msgid "Preview {0}" msgid "Preview {0}"
@ -4574,6 +4595,7 @@ msgid "Skills"
msgstr "技能" msgstr "技能"
#: src/components/integrations/IntegrationSetup.tsx #: src/components/integrations/IntegrationSetup.tsx
#: src/components/onboarding/OnboardingNameStep.tsx
#: src/components/onboarding/OnboardingScanStep.tsx #: src/components/onboarding/OnboardingScanStep.tsx
#: src/components/onboarding/OnboardingVoiceStep.tsx #: src/components/onboarding/OnboardingVoiceStep.tsx
#: src/lib/onboarding-copy.ts #: src/lib/onboarding-copy.ts
@ -4581,8 +4603,9 @@ msgstr "技能"
msgid "Skip" msgid "Skip"
msgstr "跳过" msgstr "跳过"
#. placeholder {0}: UI_LOCALE_LABELS[activeUiLocale]
#: src/pages/Onboarding.tsx #: src/pages/Onboarding.tsx
msgid "Skip · English" msgid "Skip · {0}"
msgstr "" msgstr ""
#: src/lib/localized-provider-hint.ts #: src/lib/localized-provider-hint.ts
@ -4614,6 +4637,10 @@ msgstr "软件更新"
msgid "Something needs your attention." msgid "Something needs your attention."
msgstr "" msgstr ""
#: src/components/onboarding/OnboardingNameStep.tsx
msgid "Something short and yours — or keep the default and rename later."
msgstr ""
#: src/lib/voice-persona-copy.ts #: src/lib/voice-persona-copy.ts
msgid "Sorting priorities…" msgid "Sorting priorities…"
msgstr "" msgstr ""

View File

@ -5,6 +5,10 @@ import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { IntegrationSetup } from "../components/integrations/IntegrationSetup"; import { IntegrationSetup } from "../components/integrations/IntegrationSetup";
import { OnboardingDiscoveryShell } from "../components/onboarding/OnboardingDiscoveryShell"; import { OnboardingDiscoveryShell } from "../components/onboarding/OnboardingDiscoveryShell";
import {
DEFAULT_FIRST_OO_NAME,
OnboardingNameStep,
} from "../components/onboarding/OnboardingNameStep";
import { import {
type OnboardingEmojiConfig, type OnboardingEmojiConfig,
OnboardingOoDesigner, OnboardingOoDesigner,
@ -23,7 +27,12 @@ import {
import { findLookCategory, lookPresetToAvatarValue } from "../lib/onboarding-look-categories"; import { findLookCategory, lookPresetToAvatarValue } from "../lib/onboarding-look-categories";
import { onboardingProfileUpdate } from "../lib/onboarding-profile"; import { onboardingProfileUpdate } from "../lib/onboarding-profile";
import { rpc, selectSpace } from "../lib/rpc"; import { rpc, selectSpace } from "../lib/rpc";
import { persistUiLocale, UI_LOCALE_LABELS, type UiLocale } from "../lib/ui-locale"; import {
persistUiLocale,
resolveUiLocale,
UI_LOCALE_LABELS,
type UiLocale,
} from "../lib/ui-locale";
import { collectLocalProfileSignals, persistUserProfileHarvest } from "../lib/user-profile-harvest"; import { collectLocalProfileSignals, persistUserProfileHarvest } from "../lib/user-profile-harvest";
/** Selects the main conversation's space and returns its route. */ /** Selects the main conversation's space and returns its route. */
@ -44,8 +53,9 @@ export function OnboardingPage() {
const { t } = useLingui(); const { t } = useLingui();
const navigate = useNavigate(); const navigate = useNavigate();
const [step, setStep] = useState< const [step, setStep] = useState<
"loading" | "emoji" | "language" | "voice" | "integrations" | "scan" | "bot" "loading" | "emoji" | "name" | "language" | "voice" | "integrations" | "scan" | "bot"
>("emoji"); >("emoji");
const [ooName, setOoName] = useState(DEFAULT_FIRST_OO_NAME);
const [selectedVoiceId, setSelectedVoiceId] = useState(""); const [selectedVoiceId, setSelectedVoiceId] = useState("");
const [voiceSkipped, setVoiceSkipped] = useState(false); const [voiceSkipped, setVoiceSkipped] = useState(false);
const [integrationSetup, setIntegrationSetup] = useState<IntegrationSetupState | null>(null); const [integrationSetup, setIntegrationSetup] = useState<IntegrationSetupState | null>(null);
@ -57,14 +67,9 @@ export function OnboardingPage() {
const [crewReady, setCrewReady] = useState(false); const [crewReady, setCrewReady] = useState(false);
const [emojiConfig, setEmojiConfig] = useState<OnboardingEmojiConfig>(defaultEmojiConfig); const [emojiConfig, setEmojiConfig] = useState<OnboardingEmojiConfig>(defaultEmojiConfig);
const [currentLocale, setCurrentLocale] = useState<string>(() => { const [currentLocale, setCurrentLocale] = useState<UiLocale>(() => resolveUiLocale());
try {
return localStorage.getItem("rakazo.uiLocale") ?? "en";
} catch {
return "en";
}
});
const [languageChoice, setLanguageChoice] = useState<UiLocale | null>(null); const [languageChoice, setLanguageChoice] = useState<UiLocale | null>(null);
const activeUiLocale = languageChoice ?? currentLocale;
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
@ -110,7 +115,7 @@ export function OnboardingPage() {
const discoveryPhase: OnboardingDiscoveryPhase = const discoveryPhase: OnboardingDiscoveryPhase =
step === "loading" step === "loading"
? "loading" ? "loading"
: step === "emoji" || step === "language" || step === "voice" : step === "emoji" || step === "name" || step === "language" || step === "voice"
? "emoji" ? "emoji"
: step === "integrations" : step === "integrations"
? "integrations" ? "integrations"
@ -145,7 +150,10 @@ export function OnboardingPage() {
setError(null); setError(null);
try { try {
const bot = await rpc.bots.ensureMain(); const bot = await rpc.bots.ensureMain();
await rpc.bots.update({ botId: bot.id, color: emojiConfig.avatarValue }).catch(() => null); const trimmedName = ooName.trim() || DEFAULT_FIRST_OO_NAME;
await rpc.bots
.update({ botId: bot.id, color: emojiConfig.avatarValue, name: trimmedName })
.catch(() => null);
setCrewReady(true); setCrewReady(true);
await rpc.profile await rpc.profile
@ -213,7 +221,11 @@ export function OnboardingPage() {
<OnboardingDiscoveryShell <OnboardingDiscoveryShell
items={discoveryItems} items={discoveryItems}
compact={ compact={
step === "emoji" || step === "language" || step === "voice" || step === "integrations" step === "emoji" ||
step === "name" ||
step === "language" ||
step === "voice" ||
step === "integrations"
} }
> >
{error && step !== "bot" ? <p className="mb-4 text-sm text-destructive">{error}</p> : null} {error && step !== "bot" ? <p className="mb-4 text-sm text-destructive">{error}</p> : null}
@ -222,6 +234,16 @@ export function OnboardingPage() {
<OnboardingOoDesigner <OnboardingOoDesigner
config={emojiConfig} config={emojiConfig}
onChange={setEmojiConfig} onChange={setEmojiConfig}
onContinue={() => setStep("name")}
/>
) : null}
{step === "name" ? (
<OnboardingNameStep
config={emojiConfig}
name={ooName}
onNameChange={setOoName}
onBack={() => setStep("emoji")}
onContinue={() => setStep("language")} onContinue={() => setStep("language")}
/> />
) : null} ) : null}
@ -241,7 +263,7 @@ export function OnboardingPage() {
type="button" type="button"
onClick={() => { onClick={() => {
persistUiLocale(code as UiLocale); persistUiLocale(code as UiLocale);
setCurrentLocale(code); setCurrentLocale(code as UiLocale);
setLanguageChoice(code as UiLocale); setLanguageChoice(code as UiLocale);
setStep("voice"); setStep("voice");
}} }}
@ -256,11 +278,18 @@ export function OnboardingPage() {
))} ))}
</div> </div>
<div className="mt-8 flex items-center justify-between"> <div className="mt-8 flex items-center justify-between">
<Button variant="ghost" size="sm" onClick={() => setStep("emoji")}> <Button variant="ghost" size="sm" onClick={() => setStep("name")}>
<Trans>Back</Trans> <Trans>Back</Trans>
</Button> </Button>
<Button onClick={() => setStep("voice")}> <Button
<Trans>Skip · English</Trans> onClick={() => {
const locale = resolveUiLocale();
setLanguageChoice(locale);
setCurrentLocale(locale);
setStep("voice");
}}
>
{t`Skip · ${UI_LOCALE_LABELS[activeUiLocale]}`}
</Button> </Button>
</div> </div>
</div> </div>
@ -268,6 +297,7 @@ export function OnboardingPage() {
{step === "voice" ? ( {step === "voice" ? (
<OnboardingVoiceStep <OnboardingVoiceStep
uiLocale={activeUiLocale}
selectedVoiceId={selectedVoiceId} selectedVoiceId={selectedVoiceId}
onSelectedVoiceIdChange={setSelectedVoiceId} onSelectedVoiceIdChange={setSelectedVoiceId}
onBack={() => setStep("language")} onBack={() => setStep("language")}

View File

@ -84,7 +84,9 @@ import {
useState, useState,
} from "react"; } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
import { HEUTE_APP_PATH, isHeuteAppPath } from "../lib/app-routes";
import { isMultiOoGroupChat } from "../lib/group-chat";
import { ArtifactPane } from "../components/ArtifactPane"; import { ArtifactPane } from "../components/ArtifactPane";
import { InAppBrowser } from "../components/browser/InAppBrowser"; import { InAppBrowser } from "../components/browser/InAppBrowser";
import { ComputerUpdateProgress } from "../components/ComputerUpdateProgress"; import { ComputerUpdateProgress } from "../components/ComputerUpdateProgress";
@ -267,14 +269,21 @@ import { SortableGroup } from "../components/SortableGroup";
export function ShellPage() { export function ShellPage() {
const { t } = useLingui(); const { t } = useLingui();
const { botId, groupId } = useParams(); const { botId, groupId } = useParams();
const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const onHeuteRoute = isHeuteAppPath(location.pathname);
const [activeNavPillar, setActiveNavPillar] = useState<NavPillar>(() => { const [activeNavPillar, setActiveNavPillar] = useState<NavPillar>(() => {
if (isHeuteAppPath(location.pathname)) return "heute";
if (groupId) return "projekte"; if (groupId) return "projekte";
if (botId) return "team"; if (botId) return "team";
return "heute"; return "heute";
}); });
useEffect(() => {
if (isHeuteAppPath(location.pathname)) setActiveNavPillar("heute");
}, [location.pathname]);
useEffect(() => { useEffect(() => {
if (botId) setActiveNavPillar("team"); if (botId) setActiveNavPillar("team");
}, [botId]); }, [botId]);
@ -611,6 +620,8 @@ export function ShellPage() {
const recordingSkill = activeTaughtSkills.find((skill) => skill.status === "recording") ?? null; const recordingSkill = activeTaughtSkills.find((skill) => skill.status === "recording") ?? null;
const routeBotId = useRef<string | undefined>(botId); const routeBotId = useRef<string | undefined>(botId);
routeBotId.current = botId; routeBotId.current = botId;
const routeOnHeute = useRef(onHeuteRoute);
routeOnHeute.current = onHeuteRoute;
const routeGroupId = useRef<string | undefined>(groupId); const routeGroupId = useRef<string | undefined>(groupId);
routeGroupId.current = groupId; routeGroupId.current = groupId;
const activeBotId = useRef<string | undefined>(inGroup ? undefined : active?.id); const activeBotId = useRef<string | undefined>(inGroup ? undefined : active?.id);
@ -745,6 +756,7 @@ export function ShellPage() {
return; return;
} }
if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) { if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) {
if (routeOnHeute.current) return;
navigate(firstThreadRoute(list, groupList, inProject), { replace: true }); navigate(firstThreadRoute(list, groupList, inProject), { replace: true });
} }
} finally { } finally {
@ -1580,6 +1592,9 @@ export function ShellPage() {
: snapshot?.botId === active?.id : snapshot?.botId === active?.id
? snapshot ? snapshot
: null; : null;
const activeGroupMemberCount =
activeGroup?.members?.length ?? activeSnapshot?.members?.length ?? 0;
const multiOoInGroup = inGroup && isMultiOoGroupChat(activeGroupMemberCount);
const activeReplyTarget = const activeReplyTarget =
replyTarget && activeSnapshot?.messages.some((message) => message.id === replyTarget.id) replyTarget && activeSnapshot?.messages.some((message) => message.id === replyTarget.id)
? replyTarget ? replyTarget
@ -2796,6 +2811,7 @@ export function ShellPage() {
ooSound.play("interact"); ooSound.play("interact");
setActiveNavPillar("heute"); setActiveNavPillar("heute");
setMobileSidebarOpen(false); setMobileSidebarOpen(false);
navigate(HEUTE_APP_PATH);
}} }}
className={rosterRowClass(activeNavPillar === "heute")} className={rosterRowClass(activeNavPillar === "heute")}
> >
@ -3213,10 +3229,17 @@ export function ShellPage() {
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug"> <span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
{composerRunning || active.status === "running" ? t`Busy` : active.status} {composerRunning || active.status === "running" ? t`Busy` : active.status}
</span> </span>
) : inGroup && projectSessionKey ? ( ) : multiOoInGroup && projectSessionKey ? (
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug"> <span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
{t`Group conversation`} {t`Group conversation`}
</span> </span>
) : inGroup &&
activeGroup?.kind === "main" &&
activeGroupMemberCount <= 1 &&
mainConversationEntry ? (
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
{t`1:1 with ${mainConversationEntry.bot.name}`}
</span>
) : inGroup && activeGroup?.kind !== "main" ? ( ) : inGroup && activeGroup?.kind !== "main" ? (
<span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug"> <span className="block truncate text-ui-body-sm font-medium text-muted-foreground/80 leading-snug">
{t`Project · sessions & folders`} {t`Project · sessions & folders`}

View File

@ -20,6 +20,7 @@ export function SidebarToggleButtons({
{collapsed && desktopBridge() ? <WindowChrome /> : null} {collapsed && desktopBridge() ? <WindowChrome /> : null}
<button <button
type="button" type="button"
data-testid="open-navigation"
aria-label={t`Open navigation`} aria-label={t`Open navigation`}
onClick={onOpenNavigation} onClick={onOpenNavigation}
className="app-no-drag grid size-control-lg shrink-0 place-items-center rounded-lg text-foreground/75 hover:bg-accent md:hidden" className="app-no-drag grid size-control-lg shrink-0 place-items-center rounded-lg text-foreground/75 hover:bg-accent md:hidden"

View File

@ -17,6 +17,21 @@ export function useMobileSidebar() {
return () => desktop.removeEventListener("change", closeMobileSidebar); return () => desktop.removeEventListener("change", closeMobileSidebar);
}, []); }, []);
useEffect(() => {
if (!mobileSidebarOpen) return;
function onKeyDown(event: KeyboardEvent) {
if (event.key !== "Escape" || window.matchMedia("(min-width: 768px)").matches) return;
event.preventDefault();
event.stopPropagation();
setMobileSidebarOpen(false);
queueMicrotask(() => {
document.querySelector<HTMLElement>('[data-testid="open-navigation"]')?.focus();
});
}
window.addEventListener("keydown", onKeyDown, true);
return () => window.removeEventListener("keydown", onKeyDown, true);
}, [mobileSidebarOpen]);
function onMobileSidebarTouchStart(event: React.TouchEvent<HTMLDivElement>) { function onMobileSidebarTouchStart(event: React.TouchEvent<HTMLDivElement>) {
if ( if (
mobileSidebarOpen || mobileSidebarOpen ||