fix(merge): repair post-merge regressions after origin/main integration

Follow-up fixes on top of the origin/main merge (886f925cd) addressing
regressions the conflict resolution introduced. main's web suite is the
oracle (100% green); resolution principle was main's engine/backend +
HEAD's UI, unioned.

daemon:
- library-sync.ts: correct design-systems import to ./design-systems/index.js
  (design-systems became a directory module on main).
- tests/server-bootstrap-regression: add LIBRARY_DIR to the PathDeps fixture
  (main-added test x HEAD-added LIBRARY_DIR field).

web:
- WorkspaceTabsBar: union — restore main's onboarding Search-popover close
  behaviour + guards, keep HEAD's library/brands nav entries.
- HomeView: restore main's composer sending-state (await onSubmit, widen its
  return type to Promise<boolean>|boolean|void, pass submitting to HomeHero).
- MemorySection.test: take main's version to match main's two-loop memory
  component.
- i18n: restore dropped settings.onboardingRoleMarketing key across types.ts
  and all locales.
- App/BrandsTab/EntryNavRail/router/home-intent: union fixes restoring main
  features dropped during conflict resolution (needs_input handling, etc.).

Validation: pnpm guard + full pnpm typecheck (all 23 packages) green.
Known-red: EntryShell onboarding step 3 intentionally retains HEAD's "build"
step rather than main's brand-extract step; 8 EntryShell.onboarding /
App.onboarding-amr-e2e tests stay red pending that onboarding decision.
This commit is contained in:
pftom
2026-06-23 09:03:27 +08:00
parent 886f925cdf
commit 688544ff7d
30 changed files with 635 additions and 207 deletions

View File

@@ -23,7 +23,7 @@ import { readFile, stat } from 'node:fs/promises';
import type Database from 'better-sqlite3';
import type { LibraryAssetKind, LibrarySourceKind } from '@open-design/contracts';
import { listConversations, listMessages, listProjects } from './db.js';
import { listDesignSystems } from './design-systems.js';
import { listDesignSystems } from './design-systems/index.js';
import { listFiles, resolveProjectDir } from './projects.js';
import { registerLibraryAsset } from './library.js';
import { findReferencedAssetByOrigin, hasDesignSystemSource } from './library-store.js';

View File

@@ -385,6 +385,7 @@ describe('bootstrap route regressions', () => {
CRAFT_DIR: path.join(tempRoot, 'craft'),
DESIGN_SYSTEMS_DIR: path.join(tempRoot, 'design-systems'),
DESIGN_TEMPLATES_DIR: path.join(tempRoot, 'design-templates'),
LIBRARY_DIR: path.join(tempRoot, 'library'),
OD_BIN: path.join(tempRoot, 'od'),
PROJECT_ROOT: tempRoot,
PROJECTS_DIR: path.join(tempRoot, 'projects'),

View File

@@ -21,6 +21,7 @@ import { PluginDetailView } from './components/PluginDetailView';
import type { CreateInput, ImportClaudeDesignOutcome } from './components/NewProjectPanel';
import { MemoryToast } from './components/MemoryToast';
import { Toast } from './components/Toast';
import { CenteredLoader } from './components/Loading';
import { PetOverlay, type PetTaskCenter } from './components/pet/PetOverlay';
import { buildPetTaskCenter } from './components/pet/taskCenter';
import { migrateCustomPetAtlas } from './components/pet/pets';
@@ -119,6 +120,7 @@ import type {
const APP_CONFIG_CHANGED_EVENT = 'open-design:app-config-changed';
const AMR_AGENT_ID = 'amr';
const AMR_PROFILE_ENV_KEY = 'OPEN_DESIGN_AMR_PROFILE';
const AGENT_FOCUS_REFRESH_THROTTLE_MS = 10_000;
export function shouldSyncMediaProvidersOnSave(
mediaProviders: AppConfig['mediaProviders'],
@@ -369,6 +371,7 @@ function AppInner() {
const amrModelsRef = useRef<AmrModelsResponse | null>(null);
const amrPollGenerationRef = useRef(0);
const agentStreamRequestSeqRef = useRef(0);
const agentFocusRefreshLastRunRef = useRef(Date.now());
const [amrPollRestartToken, setAmrPollRestartToken] = useState(0);
const [providerModelsCache, setProviderModelsCache] = useState<
Record<string, ProviderModelOption[]>
@@ -1046,6 +1049,12 @@ function AppInner() {
reconcileFetchedProjects(list, request);
}, [beginProjectListRequest, reconcileFetchedProjects]);
const refreshProjectsStrict = useCallback(async () => {
const request = beginProjectListRequest();
const list = await listProjects({ throwOnError: true });
reconcileFetchedProjects(list, request);
}, [beginProjectListRequest, reconcileFetchedProjects]);
const refreshDesignSystems = useCallback(async () => {
const list = await fetchDesignSystems();
setDesignSystems(list);
@@ -1162,6 +1171,14 @@ function AppInner() {
const handleThemeChange = useCallback(
(theme: AppConfig['theme']) => {
const next = { ...config, theme };
// Apply to the DOM synchronously inside the click handler so the theme
// flips instantly. Otherwise the visible switch waits on the (heavier)
// React re-render of the whole tree before the layout effect re-applies
// it — which reads as a perceptible lag after the click.
applyAppearanceToDocument({
theme: theme ?? 'system',
accentColor: config.accentColor,
});
saveConfig(next);
void syncConfigToDaemon(next);
setConfig(next);
@@ -1275,6 +1292,29 @@ function AppInner() {
[beginAgentStreamRequest, config, isCurrentAgentStreamRequest],
);
useEffect(() => {
if (!daemonLive || agentsLoading) return;
const refreshIfDue = () => {
if (document.visibilityState === 'hidden') return;
const now = Date.now();
if (now - agentFocusRefreshLastRunRef.current < AGENT_FOCUS_REFRESH_THROTTLE_MS) return;
agentFocusRefreshLastRunRef.current = now;
void refreshAgents();
};
const handleVisibilityChange = () => {
if (document.visibilityState === 'visible') refreshIfDue();
};
window.addEventListener('focus', refreshIfDue);
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
window.removeEventListener('focus', refreshIfDue);
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [agentsLoading, daemonLive, refreshAgents]);
useEffect(() => {
const handleAppConfigChanged = () => {
void fetchDaemonConfig().then((daemonConfig) => {
@@ -1804,17 +1844,30 @@ function AppInner() {
});
}, []);
const activeProject =
const loadedActiveProject =
route.kind === 'project'
? (projects.find((p) => p.id === route.projectId) ?? null)
: null;
const routeProjectPlaceholder = useMemo<Project | null>(() => {
if (route.kind !== 'project') return null;
const now = Date.now();
return {
id: route.projectId,
name: 'Untitled',
skillId: null,
designSystemId: null,
createdAt: now,
updatedAt: now,
};
}, [route]);
const activeProject = loadedActiveProject ?? routeProjectPlaceholder;
// Deep-linked route to a project we don't have yet (e.g. after a refresh
// that finishes after the project list comes back). Fetch it in the
// background so the view can render rather than bouncing to home.
useEffect(() => {
if (route.kind !== 'project') return;
if (activeProject) return;
if (loadedActiveProject) return;
if (!projects.length && !daemonLive) return;
if (projects.some((p) => p.id === route.projectId)) return;
let cancelled = false;
@@ -1850,7 +1903,7 @@ function AppInner() {
return () => {
cancelled = true;
};
}, [route, activeProject, projects, daemonLive, beginProjectListRequest, reconcileFetchedProjects, t]);
}, [route, loadedActiveProject, projects, daemonLive, beginProjectListRequest, reconcileFetchedProjects, t]);
const openSettings = useCallback((
section: SettingsSection = 'execution',
@@ -2033,7 +2086,18 @@ function AppInner() {
// EntryView / ProjectView split so the discovery surface stays
// independent of any active project.
let appMain: ReactNode;
if (route.kind === 'marketplace') {
const pendingFirstRunOnboardingRoute =
route.kind === 'home' &&
route.view === 'home' &&
config.onboardingCompleted !== true &&
!daemonConfigLoaded;
if (pendingFirstRunOnboardingRoute) {
appMain = (
<div className="entry-shell entry-shell--no-header">
<CenteredLoader label={t('entry.loadingWorkspace')} />
</div>
);
} else if (route.kind === 'marketplace') {
appMain = <MarketplaceView />;
} else if (route.kind === 'marketplace-detail') {
appMain = <PluginDetailView pluginId={route.pluginId} />;
@@ -2155,6 +2219,7 @@ function AppInner() {
onOpenLiveArtifact={handleOpenLiveArtifact}
onDeleteProject={handleDeleteProject}
onRenameProject={handleRenameProject}
onProjectsRefresh={refreshProjectsStrict}
onChangeDefaultDesignSystem={handleChangeDefaultDesignSystem}
onCreateDesignSystem={() => navigate({ kind: 'design-system-create' })}
onOpenDesignSystem={(id: string) => navigate({ kind: 'design-system-detail', designSystemId: id })}

View File

@@ -62,12 +62,16 @@ export function BrandsTab({ onApplyDesignSystem, onOpenProject }: BrandsTabProps
if (isBrandsView) void refresh();
}, [isBrandsView, refresh]);
// While a brand is mid-extraction, poll so its card flips from `extracting`
// to the finalized preview without the user leaving and returning. Scoped to
// the active view and torn down once nothing is extracting, so a hidden tab
// While a brand is mid-extraction (or paused awaiting user input), poll so its
// card flips from `extracting` to the finalized preview — or back from
// `needs_input` once the user answers — without leaving and returning. Scoped
// to the active view and torn down once nothing is in-flight, so a hidden tab
// never polls.
const hasExtracting = useMemo(
() => (brands ?? []).some((b) => b.meta.status === 'extracting'),
() =>
(brands ?? []).some(
(b) => b.meta.status === 'extracting' || b.meta.status === 'needs_input',
),
[brands],
);
useEffect(() => {

View File

@@ -167,6 +167,15 @@ export function EntryNavRail({ view, onViewChange, onNewProject, open, onClose }
>
<Icon name="layers-filled" size={18} />
</NavButton>
<NavButton
active={view === 'brands'}
ariaLabel={t('entry.navBrands')}
tooltip={t('entry.navBrands')}
onClick={() => selectView('brands')}
testId="entry-nav-brands"
>
<Icon name="blocks" size={18} />
</NavButton>
<NavButton
active={view === 'tasks'}
ariaLabel={t('entry.navTasks')}

View File

@@ -202,7 +202,7 @@ interface Props {
projectsLoading?: boolean;
designSystems?: DesignSystemSummary[];
defaultDesignSystemId?: string | null;
onSubmit: (payload: PluginLoopSubmit) => void;
onSubmit: (payload: PluginLoopSubmit) => Promise<boolean> | boolean | void;
onOpenProject: (id: string) => void;
onViewAllProjects: () => void;
onBrowseRegistry?: () => void;
@@ -322,6 +322,9 @@ export function HomeView({
examplePromptInfoRef.current = info;
}, []);
const [error, setError] = useState<string | null>(null);
// Composer in-flight guard: disables the send button, shows Sending…, and
// swallows repeat clicks across the whole async create tail.
const [sending, setSending] = useState(false);
const [elevenLabsVoices, setElevenLabsVoices] = useState<AudioVoiceOption[]>([]);
const [elevenLabsVoicesLoading, setElevenLabsVoicesLoading] = useState(false);
// Live AIHubMix image catalogue merged into the home media composer's model
@@ -1610,6 +1613,9 @@ export function HomeView({
}, [plugins, chipIntentTick]);
async function submit() {
// The send button disables itself while sending, but the Enter-to-send
// path lands here directly — swallow re-entry during the in-flight window.
if (sending) return;
const trimmed = prompt.trim();
if (!trimmed && stagedFiles.length === 0) return;
// P0 ui_click area=chat_composer element=send_button. Fires before the
@@ -1634,122 +1640,146 @@ export function HomeView({
);
return;
}
const defaultInputs = { prompt: trimmed };
// The persistent picker is the single source of truth for the new project's
// design system, so every product kind (not just prototype/deck) carries
// the user's selection — the plugin's `designSystem` input is only the
// apply-template hint and is kept in sync via handleDesignSystemChange.
const submittedDesignSystemId = designSystemId;
// Composer inputs are forwarded as-is; the deferred footer/media fields are
// stripped from this set just below to form the run-facing inputs.
const submittedApplyInputs = submittedActive ? submittedActive.inputs : defaultInputs;
// Inputs forwarded to the run AND used to build the run-facing snapshot:
// drop every now-hidden footer/media setting so the first-turn
// question-form flow collects them instead of inheriting a baked-in
// default (`ratio: 16:9`, `duration: 5`, `audioType: speech`, …). The
// snapshot is resolved from these stripped inputs too — the daemon renders
// `## Plugin inputs` from `snapshot.inputs` and tells the agent not to
// re-ask about anything listed there, so leaving the deferred defaults in
// the snapshot would suppress the discovery flow even though
// `onSubmit.pluginInputs` was stripped. Stripping only removes non-required
// fields (`subject`/`style`/`aspect`/`mediaKind` stay), so the
// od-media-generation apply still validates.
const submittedPluginInputs = submittedActive
? stripArtifactFooterInputs(submittedApplyInputs)
: defaultInputs;
const activeInputsChangedForSubmit = submittedActive
? !inputsEqual(submittedActive.result?.appliedPlugin?.inputs ?? submittedActive.inputs, submittedPluginInputs)
: false;
if (submittedActive && (!submittedActive.result || activeInputsChangedForSubmit)) {
const result = await resolveActivePlugin(submittedActive.record, submittedPluginInputs);
if (!result) {
setError(`Failed to apply ${submittedActive.record.title}. Check the plugin parameters and try again.`);
setError(null);
// Sending covers the whole async tail — a pending plugin apply (when one
// must resolve first) and the project-creation roundtrip are both windows
// a second click could otherwise re-enter.
setSending(true);
try {
const defaultInputs = { prompt: trimmed };
// The persistent picker is the single source of truth for the new project's
// design system, so every product kind (not just prototype/deck) carries
// the user's selection — the plugin's `designSystem` input is only the
// apply-template hint and is kept in sync via handleDesignSystemChange.
const submittedDesignSystemId = designSystemId;
// Composer inputs are forwarded as-is; the deferred footer/media fields are
// stripped from this set just below to form the run-facing inputs.
const submittedApplyInputs = submittedActive ? submittedActive.inputs : defaultInputs;
// Inputs forwarded to the run AND used to build the run-facing snapshot:
// drop every now-hidden footer/media setting so the first-turn
// question-form flow collects them instead of inheriting a baked-in
// default (`ratio: 16:9`, `duration: 5`, `audioType: speech`, …). The
// snapshot is resolved from these stripped inputs too — the daemon renders
// `## Plugin inputs` from `snapshot.inputs` and tells the agent not to
// re-ask about anything listed there, so leaving the deferred defaults in
// the snapshot would suppress the discovery flow even though
// `onSubmit.pluginInputs` was stripped. Stripping only removes non-required
// fields (`subject`/`style`/`aspect`/`mediaKind` stay), so the
// od-media-generation apply still validates.
const submittedPluginInputs = submittedActive
? stripArtifactFooterInputs(submittedApplyInputs)
: defaultInputs;
const activeInputsChangedForSubmit = submittedActive
? !inputsEqual(submittedActive.result?.appliedPlugin?.inputs ?? submittedActive.inputs, submittedPluginInputs)
: false;
if (submittedActive && (!submittedActive.result || activeInputsChangedForSubmit)) {
const result = await resolveActivePlugin(submittedActive.record, submittedPluginInputs);
if (!result) {
setError(`Failed to apply ${submittedActive.record.title}. Check the plugin parameters and try again.`);
return;
}
submittedActive = { ...submittedActive, result, inputs: submittedPluginInputs };
setActive(submittedActive);
}
// Reconcile each selected context against the serialized prompt text before
// forwarding it. Inline-backed contexts (inserted as `@mention` pills) are
// only sent while their token survives in the prompt — the Lexical composer
// lets users delete a mention pill (backspace, edit), and when they do that
// plugin/MCP/connector should stop being sent. Context-only `Use`
// selections never carry a token, so they stay in the payload until the
// user explicitly clears them.
const contextPlugins = selectedPluginContexts
.filter((item) => !item.inlineBacked || mentionTokenPresent(trimmed, item.record.title))
.map((item) => ({
id: item.record.id,
title: item.record.title,
...(item.record.manifest?.description
? { description: item.record.manifest.description }
: {}),
}));
const contextMcpServers = selectedMcpContexts
.filter((item) => !item.inlineBacked || mentionTokenPresent(trimmed, item.server.label || item.server.id))
.map((item) => ({
id: item.server.id,
...(item.server.label ? { label: item.server.label } : {}),
...(item.server.transport ? { transport: item.server.transport } : {}),
...(item.server.url ? { url: item.server.url } : {}),
...(item.server.command ? { command: item.server.command } : {}),
}));
const contextConnectors = selectedConnectorContexts
.filter((item) => !item.inlineBacked || mentionTokenPresent(trimmed, item.connector.name))
.map((item) => ({
id: item.connector.id,
name: item.connector.name,
provider: item.connector.provider,
category: item.connector.category,
status: item.connector.status,
...(item.connector.accountLabel ? { accountLabel: item.connector.accountLabel } : {}),
}));
const submittedProjectKind =
submittedActive?.projectKind ?? fallbackProjectKind ?? projectKindForSkill(activeSkill) ?? 'other';
const submittedProjectMetadata = submittedActive?.mediaSurface
? metadataForHomeMediaComposer(submittedActive.mediaSurface, submittedActive.inputs, promptTemplates)
: homeCreateProjectMetadata(
submittedProjectKind,
submittedActive?.inputs ?? null,
submittedActive?.projectMetadata ?? fallbackProjectMetadata ?? null,
);
// Scenario plugins (chips / preset cards) and explicit skill picks are
// mutually exclusive routing sources — never send both (#2972).
const resolvedSkillId = submittedActive ? null : activeSkill?.id ?? null;
const routedPluginId =
sessionMode === 'design'
? submittedActive?.record.id ?? DEFAULT_UNSELECTED_SCENARIO_PLUGIN_ID
: submittedActive?.record.id ?? null;
// The example-prompt override is a one-shot marker. Decide whether to
// send it now, but defer spending the marker until the create is
// accepted — a rejected attempt stays retryable and must resend it.
const examplePromptKey = 'od:example-prompt-used';
const examplePromptToSend =
examplePromptInfoRef.current != null && localStorage.getItem(examplePromptKey) == null
? examplePromptInfoRef.current
: null;
const accepted = await onSubmit({
prompt: trimmed,
pluginId: routedPluginId,
pluginType: submittedActive?.record.marketplaceTrust ?? (routedPluginId ? 'official' : null),
skillId: resolvedSkillId,
appliedPluginSnapshotId: submittedActive?.result?.appliedPlugin?.snapshotId ?? null,
pluginTitle: submittedActive?.record.title ?? null,
taskKind: submittedActive?.result?.appliedPlugin?.taskKind ?? null,
pluginInputs: submittedPluginInputs,
projectKind: submittedProjectKind,
projectMetadata: submittedProjectMetadata,
designSystemId: submittedDesignSystemId,
contextPlugins,
contextMcpServers,
contextConnectors,
attachments: stagedFiles,
...(workingDir ? { workingDir } : {}),
...(workingDirToken ? { workingDirToken } : {}),
conversationMode: sessionMode,
...(examplePromptToSend ? { examplePromptContext: examplePromptToSend } : {}),
});
if (accepted === false) {
setError('Failed to start the run. Make sure the daemon is reachable, then try again.');
return;
}
submittedActive = { ...submittedActive, result, inputs: submittedPluginInputs };
setActive(submittedActive);
// Create accepted — now it is safe to spend the one-shot marker.
if (examplePromptToSend) localStorage.setItem(examplePromptKey, '1');
// Only drop the staged contexts once the run actually started — a
// rejected creation keeps them so the retry sends the same payload.
setSelectedPluginContexts([]);
setSelectedMcpContexts([]);
setSelectedConnectorContexts([]);
} catch (err) {
// A submit handler that throws (instead of resolving false) lands on
// the same recovery path as a rejected creation.
console.warn('Home composer submit failed', err);
setError('Failed to start the run. Make sure the daemon is reachable, then try again.');
} finally {
setSending(false);
}
// Reconcile each selected context against the serialized prompt text before
// forwarding it. Inline-backed contexts (inserted as `@mention` pills) are
// only sent while their token survives in the prompt — the Lexical composer
// lets users delete a mention pill (backspace, edit), and when they do that
// plugin/MCP/connector should stop being sent. Context-only `Use`
// selections never carry a token, so they stay in the payload until the
// user explicitly clears them.
const contextPlugins = selectedPluginContexts
.filter((item) => !item.inlineBacked || mentionTokenPresent(trimmed, item.record.title))
.map((item) => ({
id: item.record.id,
title: item.record.title,
...(item.record.manifest?.description
? { description: item.record.manifest.description }
: {}),
}));
const contextMcpServers = selectedMcpContexts
.filter((item) => !item.inlineBacked || mentionTokenPresent(trimmed, item.server.label || item.server.id))
.map((item) => ({
id: item.server.id,
...(item.server.label ? { label: item.server.label } : {}),
...(item.server.transport ? { transport: item.server.transport } : {}),
...(item.server.url ? { url: item.server.url } : {}),
...(item.server.command ? { command: item.server.command } : {}),
}));
const contextConnectors = selectedConnectorContexts
.filter((item) => !item.inlineBacked || mentionTokenPresent(trimmed, item.connector.name))
.map((item) => ({
id: item.connector.id,
name: item.connector.name,
provider: item.connector.provider,
category: item.connector.category,
status: item.connector.status,
...(item.connector.accountLabel ? { accountLabel: item.connector.accountLabel } : {}),
}));
const submittedProjectKind =
submittedActive?.projectKind ?? fallbackProjectKind ?? projectKindForSkill(activeSkill) ?? 'other';
const submittedProjectMetadata = submittedActive?.mediaSurface
? metadataForHomeMediaComposer(submittedActive.mediaSurface, submittedActive.inputs, promptTemplates)
: homeCreateProjectMetadata(
submittedProjectKind,
submittedActive?.inputs ?? null,
submittedActive?.projectMetadata ?? fallbackProjectMetadata ?? null,
);
// Scenario plugins (chips / preset cards) and explicit skill picks are
// mutually exclusive routing sources — never send both (#2972).
const resolvedSkillId = submittedActive ? null : activeSkill?.id ?? null;
const routedPluginId =
sessionMode === 'design'
? submittedActive?.record.id ?? DEFAULT_UNSELECTED_SCENARIO_PLUGIN_ID
: submittedActive?.record.id ?? null;
onSubmit({
prompt: trimmed,
pluginId: routedPluginId,
pluginType: submittedActive?.record.marketplaceTrust ?? (routedPluginId ? 'official' : null),
skillId: resolvedSkillId,
appliedPluginSnapshotId: submittedActive?.result?.appliedPlugin?.snapshotId ?? null,
pluginTitle: submittedActive?.record.title ?? null,
taskKind: submittedActive?.result?.appliedPlugin?.taskKind ?? null,
pluginInputs: submittedPluginInputs,
projectKind: submittedProjectKind,
projectMetadata: submittedProjectMetadata,
designSystemId: submittedDesignSystemId,
contextPlugins,
contextMcpServers,
contextConnectors,
attachments: stagedFiles,
...(workingDir ? { workingDir } : {}),
...(workingDirToken ? { workingDirToken } : {}),
conversationMode: sessionMode,
...(() => {
if (!examplePromptInfoRef.current) return {};
const key = 'od:example-prompt-used';
if (localStorage.getItem(key)) return {};
localStorage.setItem(key, '1');
return { examplePromptContext: examplePromptInfoRef.current };
})(),
});
setSelectedPluginContexts([]);
setSelectedMcpContexts([]);
setSelectedConnectorContexts([]);
}
return (
@@ -1761,6 +1791,7 @@ export function HomeView({
prompt={prompt}
onPromptChange={handlePromptChange}
onSubmit={submit}
submitting={sending}
sessionMode={sessionMode}
onSessionModeChange={setSessionMode}
activePluginTitle={activeBadgeTitle}

View File

@@ -53,7 +53,7 @@ interface TabDragTarget {
interface Props {
route: Route;
projects: Project[];
// Once onboarding is finished (completed or skipped), the permanent entry
// Once onboarding is finished, the permanent entry
// tab must never linger on the 'onboarding' (Welcome) view — some completion
// paths navigate straight to a new project/design-system and leave the entry
// tab showing Welcome in the background. This flips it back to Home.
@@ -494,11 +494,15 @@ export function WorkspaceTabsBar({ route, projects, onboardingCompleted = false
// Auto-close the Welcome tab once onboarding ends: rewrite any entry tab
// still parked on the 'onboarding' view back to 'home'. This catches every
// finish path uniformly — Skip, last-step Continue, and the design-system
// Generate route that navigates to a fresh project while leaving the entry
// tab on Welcome in the background.
// finish path uniformly — last-step Continue and any future route that
// navigates away while leaving the entry tab on Welcome in the background.
useEffect(() => {
if (!onboardingCompleted) return;
// Don't rewrite the tab back to 'home' while the user is *still* on the
// onboarding route — a previously-completed user who re-opens /onboarding
// should keep the "Onboarding" tab label, not flip to "Home". The rewrite
// still fires the moment they navigate away (onboardingActive turns false).
if (onboardingActive) return;
setState((current) => {
if (!current.tabs.some((tab) => tab.kind === 'entry' && tab.view === 'onboarding')) {
return current;
@@ -512,7 +516,17 @@ export function WorkspaceTabsBar({ route, projects, onboardingCompleted = false
),
});
});
}, [onboardingCompleted]);
}, [onboardingCompleted, onboardingActive]);
// Close the Search-tabs popover whenever onboarding becomes active. The
// trigger button is hidden during onboarding, so a popover left open across
// a route flip to /onboarding (e.g. browser back/forward, which bypasses
// activateTab/createNewTab) would otherwise float over the first-run flow
// with no visible control to dismiss it. The portal is also gated on
// !onboardingActive below so it never renders for the frame before this runs.
useEffect(() => {
if (onboardingActive) setTabsMenuOpen(false);
}, [onboardingActive]);
// Scroll the active tab into view when it changes. The strip itself
// is native-scrollable horizontally (see CSS), so we just nudge the
@@ -976,12 +990,14 @@ export function WorkspaceTabsBar({ route, projects, onboardingCompleted = false
data-tooltip="New tab"
data-tooltip-placement="bottom"
aria-label="New tab"
data-testid="workspace-tabs-new-tab"
disabled={onboardingActive}
>
<Icon name="plus" size={14} />
</button>
</div>
<div className="workspace-tabs-actions" ref={menuRef}>
{onboardingActive ? null : (
<button
type="button"
className={`workspace-tabs-icon-btn od-tooltip${tabsMenuOpen ? ' is-active' : ''}`}
@@ -995,7 +1011,8 @@ export function WorkspaceTabsBar({ route, projects, onboardingCompleted = false
>
<Icon name="search" size={15} />
</button>
{tabsMenuOpen && typeof document !== 'undefined'
)}
{tabsMenuOpen && !onboardingActive && typeof document !== 'undefined'
? createPortal(
<div
className="workspace-tabs-popover"
@@ -1077,7 +1094,7 @@ export function WorkspaceTabsBar({ route, projects, onboardingCompleted = false
const previewDisplay = displayTabById.get(previewTab.id)
?? displayTabFor(previewTab, projectById, t);
const previewDetail = describePreviewDetail(previewTab, projectById);
const previewWidth = Math.max(1, Math.round(hoverPreview.anchorWidth));
const previewWidth = Math.max(220, Math.round(hoverPreview.anchorWidth));
const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : 1024;
const left = Math.max(
0,

View File

@@ -128,6 +128,17 @@ export const ar: Dict = {
'settings.onboardingNewsletterBody': 'احصل على تحديثات المنتج والقوالب الجديدة وأنظمة التصميم وأنشطة السفراء في بريدك. اختياري — يمكنك التخطي.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'تسجيل الدخول إلى Open Design',
'settings.onboardingCloudBody': 'سجّل الدخول وابدأ التصميم فورًا باستخدام الذكاء الاصطناعي السحابي، دون أي إعداد معقّد.',
'settings.onboardingCloudSignIn': 'تسجيل الدخول إلى Open Design Cloud',
'settings.onboardingCloudContinue': 'متابعة (تم تسجيل الدخول)',
'settings.onboardingCloudAlternative': 'استخدم واجهة سطر أوامر محلية أو مفتاح API الخاص بك',
'settings.onboardingCloudRights': 'جميع الحقوق محفوظة.',
'settings.onboardingCloudOr': 'أو',
'settings.onboardingGateTooltipNoRuntime': 'الخطوات التالية تعمل بالذكاء الاصطناعي — اختر طريقة تشغيل للمتابعة.',
'settings.onboardingGateTooltipAmr': 'الخطوات التالية تعمل بالذكاء الاصطناعي — سجّل الدخول إلى Open Design AMR للمتابعة.',
'settings.onboardingGateTooltipLocal': 'الخطوات التالية تعمل بالذكاء الاصطناعي — اختر CLI محليًا متاحًا للمتابعة.',
'settings.onboardingGateTooltipByok': 'الخطوات التالية تعمل بالذكاء الاصطناعي — أضف مفتاح النموذج واختبره للمتابعة.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'رسمي',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const ar: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 وكالة تسويق',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const ar: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'الإعدادات',
'settings.title': 'وضع التنفيذ',
'settings.subtitle': 'اختر بين CLI المحلي و BYOK.',
@@ -464,7 +475,7 @@ export const ar: Dict = {
'entry.discordAria': 'انضم إلى Discord الخاص بـ Open Design',
'entry.discordAriaWithOnline': 'انضم إلى Discord الخاص بـ Open Design - {online}',
'entry.discordOnlineLabel': '{count} متصل',
'entry.followXLabel': 'تابع @nexudotio على X',
'entry.followXLabel': 'تابع @OpenDesignHQ على X',
'entry.resizeAria': 'تغيير حجم الشريط الجانبي',
'entry.loadingWorkspace': 'جاري تحميل مساحة العمل...',
'entry.useEverywhereTitle': 'استخدمه في كل مكان',
@@ -2004,7 +2015,7 @@ export const ar: Dict = {
'designFiles.usefulInfoTip5': 'لا توجد أفكار؟ جرّب قالب إضافة من قسم المجتمع في الصفحة الرئيسية.',
'designFiles.usefulInfoTip6': 'لديك سؤال أو اقتراح؟ انضم إلينا على Discord.',
'designFiles.usefulInfoTip7': 'أعجبك Open Design؟ امنحنا نجمة على GitHub.',
'designFiles.usefulInfoTip8': 'تابع @nexudotio على X لمعرفة آخر المستجدات.',
'designFiles.usefulInfoTip8': 'تابع @OpenDesignHQ على X لمعرفة آخر المستجدات.',
'designFiles.usefulInfoTip9': 'اسحب الصور أو المستندات أو مجلدات كاملة إلى هذه اللوحة — سيستخدمها الوكيل كسياق.',
'designFiles.usefulInfoTip10': 'انقر على «رسم جديد» وارسم تخطيطًا تقريبيًا ليولّد الوكيل التصميم منه.',
'designFiles.usefulInfoTip11': 'حدد أي عنصر في المعاينة للتعليق عليه — سيجري الوكيل تعديلات موجهة.',
@@ -3189,6 +3200,9 @@ export const ar: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const de: Dict = {
'settings.onboardingNewsletterBody': 'Erhalte Produkt-Updates, neue Vorlagen, Design-System-Drops und Botschafter-Arbeit per E-Mail. Optional — kann übersprungen werden.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Bei Open Design anmelden',
'settings.onboardingCloudBody': 'Melde dich an und gestalte sofort mit Cloud-KI ganz ohne komplizierte Einrichtung.',
'settings.onboardingCloudSignIn': 'Bei Open Design Cloud anmelden',
'settings.onboardingCloudContinue': 'Weiter (angemeldet)',
'settings.onboardingCloudAlternative': 'Lokale CLI oder eigenen API-Schlüssel verwenden',
'settings.onboardingCloudRights': 'Alle Rechte vorbehalten.',
'settings.onboardingCloudOr': 'oder',
'settings.onboardingGateTooltipNoRuntime': 'Die nächsten Schritte laufen über KI — wähle eine Laufzeit, um fortzufahren.',
'settings.onboardingGateTooltipAmr': 'Die nächsten Schritte laufen über KI — melde dich bei Open Design AMR an, um fortzufahren.',
'settings.onboardingGateTooltipLocal': 'Die nächsten Schritte laufen über KI — wähle eine verfügbare lokale CLI, um fortzufahren.',
'settings.onboardingGateTooltipByok': 'Die nächsten Schritte laufen über KI — füge deinen Modell-Key hinzu und teste ihn, um fortzufahren.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Offiziell',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const de: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Marketingagentur',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const de: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Einstellungen',
'settings.title': 'Ausführungsmodus',
'settings.subtitle': 'Wählen Sie zwischen lokaler CLI und BYOK.',
@@ -464,7 +475,7 @@ export const de: Dict = {
'entry.discordAria': 'Tritt dem Open Design Discord bei',
'entry.discordAriaWithOnline': 'Tritt dem Open Design Discord bei - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Folge @nexudotio auf X',
'entry.followXLabel': 'Folge @OpenDesignHQ auf X',
'entry.resizeAria': 'Seitenleiste skalieren',
'entry.loadingWorkspace': 'Workspace wird geladen…',
'entry.useEverywhereTitle': 'Überall verwenden',
@@ -2004,7 +2015,7 @@ export const de: Dict = {
'designFiles.usefulInfoTip5': 'Keine Idee? Probiere eine Plugin-Vorlage aus der Community auf der Startseite.',
'designFiles.usefulInfoTip6': 'Fragen oder Feedback? Komm zu uns auf Discord.',
'designFiles.usefulInfoTip7': 'Gefällt dir Open Design? Gib uns einen Stern auf GitHub.',
'designFiles.usefulInfoTip8': 'Folge @nexudotio auf X für Neuigkeiten.',
'designFiles.usefulInfoTip8': 'Folge @OpenDesignHQ auf X für Neuigkeiten.',
'designFiles.usefulInfoTip9': 'Ziehe Bilder, Dokumente oder ganze Ordner in dieses Panel — der Agent nutzt sie als Kontext.',
'designFiles.usefulInfoTip10': 'Klicke auf „Neue Skizze“ und zeichne ein Layout — der Agent setzt es um.',
'designFiles.usefulInfoTip11': 'Wähle ein Element in der Vorschau aus und kommentiere es — der Agent ändert gezielt.',
@@ -3189,6 +3200,9 @@ export const de: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const en: Dict = {
'settings.onboardingNewsletterBody': 'Get product updates, new templates, design-system drops, and ambassador work in your inbox. Optional — you can skip this.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Sign in to Open Design',
'settings.onboardingCloudBody': 'Sign in to start designing with cloud AI right away — no complex setup required.',
'settings.onboardingCloudSignIn': 'Sign in to Open Design Cloud',
'settings.onboardingCloudContinue': 'Continue (signed in)',
'settings.onboardingCloudAlternative': 'Use a local CLI or your own API key',
'settings.onboardingCloudRights': 'All rights reserved.',
'settings.onboardingCloudOr': 'or',
'settings.onboardingGateTooltipNoRuntime': 'The next steps run on AI — pick a runtime to continue.',
'settings.onboardingGateTooltipAmr': 'The next steps run on AI — sign in to Open Design AMR to continue.',
'settings.onboardingGateTooltipLocal': 'The next steps run on AI — select an available local CLI to continue.',
'settings.onboardingGateTooltipByok': 'The next steps run on AI — add and test your model key to continue.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Official',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const en: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Marketing agency',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const en: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Settings',
'settings.title': 'Execution mode',
'settings.subtitle': 'Choose Local CLI or BYOK.',
@@ -464,7 +475,7 @@ export const en: Dict = {
'entry.discordAria': 'Join the Open Design Discord',
'entry.discordAriaWithOnline': 'Join the Open Design Discord - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Follow @nexudotio on X',
'entry.followXLabel': 'Follow @OpenDesignHQ on X',
'entry.resizeAria': 'Resize sidebar',
'entry.loadingWorkspace': 'Loading workspace…',
'entry.useEverywhereTitle': 'Use everywhere',
@@ -1840,7 +1851,7 @@ export const en: Dict = {
'preview.usePlugin': 'Use plugin',
'preview.usePluginOnly': 'Use plugin only',
'preview.usePluginOnlyDesc': 'Use the plugin structure and write the content yourself',
'preview.replicateContent': 'Replicate this content',
'preview.replicateContent': 'Make same',
'preview.replicateContentDesc': 'Use the example prompt to recreate the preview',
'preview.shareMenu': 'Share',
'preview.exportMenu': 'Export',
@@ -2004,7 +2015,7 @@ export const en: Dict = {
'designFiles.usefulInfoTip5': 'Out of ideas? Try a plugin template from Community on the Home page.',
'designFiles.usefulInfoTip6': 'Questions or feedback? Join us on Discord.',
'designFiles.usefulInfoTip7': 'Enjoying Open Design? Star us on GitHub.',
'designFiles.usefulInfoTip8': 'Follow @nexudotio on X for the latest updates.',
'designFiles.usefulInfoTip8': 'Follow @OpenDesignHQ on X for the latest updates.',
'designFiles.usefulInfoTip9': 'Drag images, docs, or whole folders into this panel — the agent uses them as context.',
'designFiles.usefulInfoTip10': 'Click New sketch to draw a rough layout and let the agent build from it.',
'designFiles.usefulInfoTip11': 'Select any element in the preview to comment — the agent makes targeted edits.',
@@ -3189,6 +3200,9 @@ export const en: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a design system to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Design System',

View File

@@ -128,6 +128,17 @@ export const esES: Dict = {
'settings.onboardingNewsletterBody': 'Recibe novedades de producto, nuevas plantillas, design systems y trabajo de embajadores en tu bandeja. Opcional: puedes omitirlo.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Inicia sesión en Open Design',
'settings.onboardingCloudBody': 'Inicia sesión y empieza a diseñar al instante con IA en la nube, sin configuraciones complejas.',
'settings.onboardingCloudSignIn': 'Inicia sesión en Open Design Cloud',
'settings.onboardingCloudContinue': 'Continuar (sesión iniciada)',
'settings.onboardingCloudAlternative': 'Usa una CLI local o tu propia clave de API',
'settings.onboardingCloudRights': 'Todos los derechos reservados.',
'settings.onboardingCloudOr': 'o',
'settings.onboardingGateTooltipNoRuntime': 'Los siguientes pasos se ejecutan con IA: elige un runtime para continuar.',
'settings.onboardingGateTooltipAmr': 'Los siguientes pasos se ejecutan con IA: inicia sesión en Open Design AMR para continuar.',
'settings.onboardingGateTooltipLocal': 'Los siguientes pasos se ejecutan con IA: selecciona una CLI local disponible para continuar.',
'settings.onboardingGateTooltipByok': 'Los siguientes pasos se ejecutan con IA: añade y prueba tu clave de modelo para continuar.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Oficial',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const esES: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Agencia de marketing',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const esES: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Ajustes',
'settings.title': 'Modo de ejecución',
'settings.subtitle': 'Elige entre CLI local y BYOK.',
@@ -464,7 +475,7 @@ export const esES: Dict = {
'entry.discordAria': 'Únete al Discord de Open Design',
'entry.discordAriaWithOnline': 'Únete al Discord de Open Design - {online}',
'entry.discordOnlineLabel': '{count} en línea',
'entry.followXLabel': 'Sigue a @nexudotio en X',
'entry.followXLabel': 'Sigue a @OpenDesignHQ en X',
'entry.resizeAria': 'Redimensionar barra lateral',
'entry.loadingWorkspace': 'Cargando espacio de trabajo…',
'entry.useEverywhereTitle': 'Usar en todas partes',
@@ -2004,7 +2015,7 @@ export const esES: Dict = {
'designFiles.usefulInfoTip5': '¿Sin ideas? Prueba una plantilla de plugin en Comunidad, en la página de inicio.',
'designFiles.usefulInfoTip6': '¿Dudas o sugerencias? Únete a nuestro Discord.',
'designFiles.usefulInfoTip7': '¿Te gusta Open Design? Danos una estrella en GitHub.',
'designFiles.usefulInfoTip8': 'Sigue a @nexudotio en X para conocer las novedades.',
'designFiles.usefulInfoTip8': 'Sigue a @OpenDesignHQ en X para conocer las novedades.',
'designFiles.usefulInfoTip9': 'Arrastra imágenes, documentos o carpetas enteras a este panel: el agente los usará como contexto.',
'designFiles.usefulInfoTip10': 'Haz clic en «Nuevo boceto», dibuja un layout y deja que el agente lo genere.',
'designFiles.usefulInfoTip11': 'Selecciona cualquier elemento en la vista previa para comentarlo: el agente hará cambios concretos.',
@@ -3189,6 +3200,9 @@ export const esES: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const fa: Dict = {
'settings.onboardingNewsletterBody': 'Get product updates, new templates, design-system drops, and ambassador work in your inbox. Optional — you can skip this.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'ورود به Open Design',
'settings.onboardingCloudBody': 'وارد شوید و بی‌درنگ با هوش مصنوعی ابری طراحی را آغاز کنید؛ بدون هیچ پیکربندی پیچیده‌ای.',
'settings.onboardingCloudSignIn': 'ورود به Open Design Cloud',
'settings.onboardingCloudContinue': 'ادامه (وارد شده‌اید)',
'settings.onboardingCloudAlternative': 'از یک CLI محلی یا کلید API خودتان استفاده کنید',
'settings.onboardingCloudRights': 'همهٔ حقوق محفوظ است.',
'settings.onboardingCloudOr': 'یا',
'settings.onboardingGateTooltipNoRuntime': 'مراحل بعدی با هوش مصنوعی اجرا می‌شوند — برای ادامه یک روش اجرا انتخاب کنید.',
'settings.onboardingGateTooltipAmr': 'مراحل بعدی با هوش مصنوعی اجرا می‌شوند — برای ادامه وارد Open Design AMR شوید.',
'settings.onboardingGateTooltipLocal': 'مراحل بعدی با هوش مصنوعی اجرا می‌شوند — برای ادامه یک CLI محلی در دسترس انتخاب کنید.',
'settings.onboardingGateTooltipByok': 'مراحل بعدی با هوش مصنوعی اجرا می‌شوند — برای ادامه کلید مدل خود را افزوده و تست کنید.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'رسمی',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const fa: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 آژانس بازاریابی',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const fa: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'تنظیمات',
'settings.title': 'حالت اجرا',
'settings.subtitle': 'بین CLI محلی و BYOK انتخاب کنید.',
@@ -464,7 +475,7 @@ export const fa: Dict = {
'entry.discordAria': 'به Discord اوپن دیزاین بپیوندید',
'entry.discordAriaWithOnline': 'به Discord اوپن دیزاین بپیوندید - {online}',
'entry.discordOnlineLabel': '{count} آنلاین',
'entry.followXLabel': 'دنبال کردن @nexudotio در X',
'entry.followXLabel': 'دنبال کردن @OpenDesignHQ در X',
'entry.resizeAria': 'تغییر اندازه نوار کناری',
'entry.loadingWorkspace': 'در حال بارگذاری فضای کاری…',
'entry.useEverywhereTitle': 'استفاده در همه‌جا',
@@ -2004,7 +2015,7 @@ export const fa: Dict = {
'designFiles.usefulInfoTip5': 'ایده‌ای ندارید؟ قالب‌های افزونه در بخش انجمن صفحهٔ اصلی را امتحان کنید.',
'designFiles.usefulInfoTip6': 'سؤال یا پیشنهادی دارید؟ به Discord ما بپیوندید.',
'designFiles.usefulInfoTip7': 'از Open Design راضی هستید؟ در GitHub به ما ستاره بدهید.',
'designFiles.usefulInfoTip8': 'برای آخرین خبرها @nexudotio را در X دنبال کنید.',
'designFiles.usefulInfoTip8': 'برای آخرین خبرها @OpenDesignHQ را در X دنبال کنید.',
'designFiles.usefulInfoTip9': 'تصاویر، اسناد یا حتی کل پوشه‌ها را به این پنل بکشید — عامل از آن‌ها به عنوان زمینه استفاده می‌کند.',
'designFiles.usefulInfoTip10': 'روی «طرح جدید» کلیک کنید و یک چیدمان بکشید تا عامل بر اساس آن بسازد.',
'designFiles.usefulInfoTip11': 'هر عنصری را در پیش‌نمایش انتخاب و نظر بدهید — عامل همان بخش را اصلاح می‌کند.',
@@ -3184,6 +3195,9 @@ export const fa: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const fr: Dict = {
'settings.onboardingNewsletterBody': 'Recevez les nouveautés produit, les nouveaux modèles, les nouveaux systèmes de design et les réalisations des ambassadeurs par e-mail. Facultatif — vous pouvez ignorer cette étape.',
'settings.onboardingConnectTitle': 'Choisir un runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Se connecter à Open Design',
'settings.onboardingCloudBody': 'Connectez-vous et créez immédiatement avec lIA cloud, sans configuration complexe.',
'settings.onboardingCloudSignIn': 'Se connecter à Open Design Cloud',
'settings.onboardingCloudContinue': 'Continuer (connecté)',
'settings.onboardingCloudAlternative': 'Utiliser une CLI locale ou votre propre clé API',
'settings.onboardingCloudRights': 'Tous droits réservés.',
'settings.onboardingCloudOr': 'ou',
'settings.onboardingGateTooltipNoRuntime': 'Les étapes suivantes tournent sur IA — choisissez un runtime pour continuer.',
'settings.onboardingGateTooltipAmr': 'Les étapes suivantes tournent sur IA — connectez-vous à Open Design AMR pour continuer.',
'settings.onboardingGateTooltipLocal': 'Les étapes suivantes tournent sur IA — sélectionnez une CLI locale disponible pour continuer.',
'settings.onboardingGateTooltipByok': 'Les étapes suivantes tournent sur IA — ajoutez et testez votre clé de modèle pour continuer.',
'settings.onboardingRecommended': 'Recommandé',
'settings.onboardingAmrCloudOfficialBadge': 'Officiel',
'settings.onboardingLocalTitle': 'Agent de code local',
@@ -169,6 +180,7 @@ export const fr: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Ingénieur',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Agence marketing',
'settings.onboardingRoleGrowth': '📈 Croissance',
'settings.onboardingRoleOps': '⚙️ Opérations',
'settings.onboardingRoleFounder': '🚀 Fondateur / dirigeant',
@@ -197,7 +209,6 @@ export const fr: Dict = {
'settings.onboardingBack': 'Retour',
'settings.onboardingContinue': 'Continuer',
'settings.onboardingFinish': 'Terminer la configuration',
'settings.onboardingSkip': 'Ignorer pour linstant',
'settings.kicker': 'Paramètres',
'settings.title': 'Mode d\'exécution',
'settings.subtitle': 'Choisissez entre CLI local et BYOK.',
@@ -464,7 +475,7 @@ export const fr: Dict = {
'entry.discordAria': 'Rejoindre le Discord dOpen Design',
'entry.discordAriaWithOnline': 'Rejoindre le Discord dOpen Design - {online}',
'entry.discordOnlineLabel': '{count} en ligne',
'entry.followXLabel': 'Suivre @nexudotio sur X',
'entry.followXLabel': 'Suivre @OpenDesignHQ sur X',
'entry.resizeAria': 'Redimensionner la barre latérale',
'entry.loadingWorkspace': 'Chargement de l\'espace de travail…',
'entry.useEverywhereTitle': 'Utiliser partout',
@@ -2004,7 +2015,7 @@ export const fr: Dict = {
'designFiles.usefulInfoTip5': 'En panne didées ? Essayez un modèle de plugin dans Communauté, sur laccueil.',
'designFiles.usefulInfoTip6': 'Questions ou suggestions ? Rejoignez-nous sur Discord.',
'designFiles.usefulInfoTip7': 'Open Design vous plaît ? Donnez-nous une étoile sur GitHub.',
'designFiles.usefulInfoTip8': 'Suivez @nexudotio sur X pour suivre les nouveautés.',
'designFiles.usefulInfoTip8': 'Suivez @OpenDesignHQ sur X pour suivre les nouveautés.',
'designFiles.usefulInfoTip9': 'Glissez images, documents ou dossiers entiers dans ce panneau — lagent les utilisera comme contexte.',
'designFiles.usefulInfoTip10': 'Cliquez sur « Nouveau croquis », dessinez une mise en page et laissez lagent la générer.',
'designFiles.usefulInfoTip11': 'Sélectionnez un élément dans laperçu pour le commenter — lagent fera des retouches ciblées.',
@@ -3189,6 +3200,9 @@ export const fr: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const hu: Dict = {
'settings.onboardingNewsletterBody': 'Get product updates, new templates, design-system drops, and ambassador work in your inbox. Optional — you can skip this.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Bejelentkezés az Open Designba',
'settings.onboardingCloudBody': 'Jelentkezz be, és azonnal tervezz felhőalapú MI-vel bonyolult beállítás nélkül.',
'settings.onboardingCloudSignIn': 'Bejelentkezés az Open Design Cloudba',
'settings.onboardingCloudContinue': 'Folytatás (bejelentkezve)',
'settings.onboardingCloudAlternative': 'Helyi CLI vagy saját API-kulcs használata',
'settings.onboardingCloudRights': 'Minden jog fenntartva.',
'settings.onboardingCloudOr': 'vagy',
'settings.onboardingGateTooltipNoRuntime': 'A következő lépések MI-n futnak — válassz futtatókörnyezetet a folytatáshoz.',
'settings.onboardingGateTooltipAmr': 'A következő lépések MI-n futnak — jelentkezz be az Open Design AMR-be a folytatáshoz.',
'settings.onboardingGateTooltipLocal': 'A következő lépések MI-n futnak — válassz egy elérhető helyi CLI-t a folytatáshoz.',
'settings.onboardingGateTooltipByok': 'A következő lépések MI-n futnak — add meg és teszteld a modellkulcsodat a folytatáshoz.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Hivatalos',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const hu: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Marketingügynökség',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const hu: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Beállítások',
'settings.title': 'Végrehajtási mód',
'settings.subtitle': 'Válassz helyi CLI és BYOK között.',
@@ -464,7 +475,7 @@ export const hu: Dict = {
'entry.discordAria': 'Csatlakozás az Open Design Discordhoz',
'entry.discordAriaWithOnline': 'Csatlakozás az Open Design Discordhoz - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Kövesd a @nexudotio fiókot az X-en',
'entry.followXLabel': 'Kövesd a @OpenDesignHQ fiókot az X-en',
'entry.resizeAria': 'Oldalsáv átméretezése',
'entry.loadingWorkspace': 'Munkaterület betöltése…',
'entry.useEverywhereTitle': 'Használat bárhol',
@@ -2004,7 +2015,7 @@ export const hu: Dict = {
'designFiles.usefulInfoTip5': 'Nincs ötleted? Próbálj ki egy plugin sablont a kezdőlap Közösség részében.',
'designFiles.usefulInfoTip6': 'Kérdésed vagy javaslatod van? Csatlakozz hozzánk Discordon.',
'designFiles.usefulInfoTip7': 'Tetszik az Open Design? Adj egy csillagot GitHubon.',
'designFiles.usefulInfoTip8': 'Kövesd a @nexudotio fiókot X-en a friss hírekért.',
'designFiles.usefulInfoTip8': 'Kövesd a @OpenDesignHQ fiókot X-en a friss hírekért.',
'designFiles.usefulInfoTip9': 'Húzz képeket, dokumentumokat vagy egész mappákat erre a panelre — az ügynök kontextusként használja őket.',
'designFiles.usefulInfoTip10': 'Kattints az „Új vázlat” gombra, rajzolj egy elrendezést, és az ügynök elkészíti.',
'designFiles.usefulInfoTip11': 'Jelölj ki bármilyen elemet az előnézetben és írj megjegyzést — az ügynök célzottan módosít.',
@@ -3189,6 +3200,9 @@ export const hu: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const id: Dict = {
'settings.onboardingNewsletterBody': 'Get product updates, new templates, design-system drops, and ambassador work in your inbox. Optional — you can skip this.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Masuk ke Open Design',
'settings.onboardingCloudBody': 'Masuk dan langsung mulai mendesain dengan AI cloud, tanpa pengaturan rumit.',
'settings.onboardingCloudSignIn': 'Masuk ke Open Design Cloud',
'settings.onboardingCloudContinue': 'Lanjutkan (sudah masuk)',
'settings.onboardingCloudAlternative': 'Gunakan CLI lokal atau kunci API Anda sendiri',
'settings.onboardingCloudRights': 'Semua hak dilindungi.',
'settings.onboardingCloudOr': 'atau',
'settings.onboardingGateTooltipNoRuntime': 'Langkah berikutnya berjalan dengan AI — pilih runtime untuk melanjutkan.',
'settings.onboardingGateTooltipAmr': 'Langkah berikutnya berjalan dengan AI — masuk ke Open Design AMR untuk melanjutkan.',
'settings.onboardingGateTooltipLocal': 'Langkah berikutnya berjalan dengan AI — pilih CLI lokal yang tersedia untuk melanjutkan.',
'settings.onboardingGateTooltipByok': 'Langkah berikutnya berjalan dengan AI — tambahkan dan uji kunci model Anda untuk melanjutkan.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Resmi',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const id: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Agensi pemasaran',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const id: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Pengaturan',
'settings.title': 'Mode eksekusi',
'settings.subtitle': 'Pilih antara CLI lokal dan BYOK.',
@@ -464,7 +475,7 @@ export const id: Dict = {
'entry.discordAria': 'Gabung Discord Open Design',
'entry.discordAriaWithOnline': 'Gabung Discord Open Design - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Ikuti @nexudotio di X',
'entry.followXLabel': 'Ikuti @OpenDesignHQ di X',
'entry.resizeAria': 'Ubah ukuran sidebar',
'entry.loadingWorkspace': 'Memuat workspace...',
'entry.useEverywhereTitle': 'Gunakan di mana saja',
@@ -2004,7 +2015,7 @@ export const id: Dict = {
'designFiles.usefulInfoTip5': 'Belum ada ide? Coba template plugin di Komunitas pada halaman Beranda.',
'designFiles.usefulInfoTip6': 'Ada pertanyaan atau saran? Gabung Discord kami.',
'designFiles.usefulInfoTip7': 'Suka Open Design? Beri kami bintang di GitHub.',
'designFiles.usefulInfoTip8': 'Ikuti @nexudotio di X untuk kabar terbaru.',
'designFiles.usefulInfoTip8': 'Ikuti @OpenDesignHQ di X untuk kabar terbaru.',
'designFiles.usefulInfoTip9': 'Seret gambar, dokumen, atau seluruh folder ke panel ini — agen akan memakainya sebagai konteks.',
'designFiles.usefulInfoTip10': 'Klik Sketsa baru, gambar tata letak kasar, dan biarkan agen membuatnya.',
'designFiles.usefulInfoTip11': 'Pilih elemen apa pun di pratinjau untuk berkomentar — agen akan mengubahnya secara terarah.',
@@ -3189,6 +3200,9 @@ export const id: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const it: Dict = {
'settings.onboardingNewsletterBody': 'Ricevi novità di prodotto, nuovi template, design system e attività degli ambassador nella tua casella. Opzionale — puoi saltare.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Accedi a Open Design',
'settings.onboardingCloudBody': 'Accedi e inizia subito a progettare con lIA nel cloud, senza configurazioni complesse.',
'settings.onboardingCloudSignIn': 'Accedi a Open Design Cloud',
'settings.onboardingCloudContinue': 'Continua (accesso effettuato)',
'settings.onboardingCloudAlternative': 'Usa una CLI locale o la tua chiave API',
'settings.onboardingCloudRights': 'Tutti i diritti riservati.',
'settings.onboardingCloudOr': 'o',
'settings.onboardingGateTooltipNoRuntime': 'I passaggi successivi vengono eseguiti tramite IA — scegli un runtime per continuare.',
'settings.onboardingGateTooltipAmr': 'I passaggi successivi vengono eseguiti tramite IA — accedi a Open Design AMR per continuare.',
'settings.onboardingGateTooltipLocal': 'I passaggi successivi vengono eseguiti tramite IA — seleziona una CLI locale disponibile per continuare.',
'settings.onboardingGateTooltipByok': 'I passaggi successivi vengono eseguiti tramite IA — aggiungi e testa la tua chiave del modello per continuare.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Ufficiale',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const it: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Agenzia di marketing',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const it: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Impostazioni',
'settings.title': 'Esecuzione e modello',
'settings.subtitle': 'Scegli tra CLI locale e BYOK.',
@@ -464,7 +475,7 @@ export const it: Dict = {
'entry.discordAria': 'Unisciti al Discord di Open Design',
'entry.discordAriaWithOnline': 'Unisciti al Discord di Open Design - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Segui @nexudotio su X',
'entry.followXLabel': 'Segui @OpenDesignHQ su X',
'entry.resizeAria': 'Ridimensiona la barra laterale',
'entry.loadingWorkspace': 'Caricamento dello spazio di lavoro…',
'entry.useEverywhereTitle': 'Usa ovunque',
@@ -2004,7 +2015,7 @@ export const it: Dict = {
'designFiles.usefulInfoTip5': 'Senza idee? Prova un template plugin nella Community in Home.',
'designFiles.usefulInfoTip6': 'Domande o suggerimenti? Unisciti al nostro Discord.',
'designFiles.usefulInfoTip7': 'Ti piace Open Design? Lascia una stella su GitHub.',
'designFiles.usefulInfoTip8': 'Segui @nexudotio su X per le ultime novità.',
'designFiles.usefulInfoTip8': 'Segui @OpenDesignHQ su X per le ultime novità.',
'designFiles.usefulInfoTip9': 'Trascina immagini, documenti o intere cartelle in questo pannello — lagente li userà come contesto.',
'designFiles.usefulInfoTip10': 'Clicca su «Nuovo schizzo», disegna un layout e lascia che lagente lo generi.',
'designFiles.usefulInfoTip11': 'Seleziona un elemento nellanteprima per commentarlo — lagente farà modifiche mirate.',
@@ -3189,6 +3200,9 @@ export const it: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const ja: Dict = {
'settings.onboardingNewsletterBody': '製品アップデート、新しいテンプレート、デザインシステム、アンバサダーの活動をメールでお届けします。任意 — スキップできます。',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Open Design にサインイン',
'settings.onboardingCloudBody': 'サインインすれば、クラウド AI ですぐにデザインを始められます。複雑な設定は一切不要です。',
'settings.onboardingCloudSignIn': 'Open Design Cloud にサインイン',
'settings.onboardingCloudContinue': '続行(ログイン済み)',
'settings.onboardingCloudAlternative': 'ローカル CLI または自分の API キーを使う',
'settings.onboardingCloudRights': 'All rights reserved.',
'settings.onboardingCloudOr': 'または',
'settings.onboardingGateTooltipNoRuntime': 'この先のステップは AI で実行されます。続けるには実行方法を選んでください。',
'settings.onboardingGateTooltipAmr': 'この先のステップは AI で実行されます。続けるには Open Design AMR にサインインしてください。',
'settings.onboardingGateTooltipLocal': 'この先のステップは AI で実行されます。続けるには利用可能なローカル CLI を選んでください。',
'settings.onboardingGateTooltipByok': 'この先のステップは AI で実行されます。続けるにはモデルキーを追加してテストしてください。',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': '公式',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const ja: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 マーケティング代理店',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const ja: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': '設定',
'settings.title': '実行モード',
'settings.subtitle': 'ローカル CLI と BYOK のどちらを使うか選択します。',
@@ -464,7 +475,7 @@ export const ja: Dict = {
'entry.discordAria': 'Open Design の Discord に参加',
'entry.discordAriaWithOnline': 'Open Design の Discord に参加 - {online}',
'entry.discordOnlineLabel': '{count} 人がオンライン',
'entry.followXLabel': 'X で @nexudotio をフォロー',
'entry.followXLabel': 'X で @OpenDesignHQ をフォロー',
'entry.resizeAria': 'サイドバーをリサイズ',
'entry.loadingWorkspace': 'ワークスペースを読み込み中…',
'entry.useEverywhereTitle': 'どこでも使う',
@@ -2004,7 +2015,7 @@ export const ja: Dict = {
'designFiles.usefulInfoTip5': 'アイデアに迷ったら、ホームのコミュニティにあるプラグインテンプレートをお試しください。',
'designFiles.usefulInfoTip6': '質問やご意見は、Discord でお気軽にどうぞ。',
'designFiles.usefulInfoTip7': '気に入ったら、GitHub でスターをお願いします。',
'designFiles.usefulInfoTip8': 'X で @nexudotio をフォローして最新情報をチェック。',
'designFiles.usefulInfoTip8': 'X で @OpenDesignHQ をフォローして最新情報をチェック。',
'designFiles.usefulInfoTip9': '画像やドキュメント、フォルダーごとこのパネルにドラッグすると、エージェントがコンテキストとして使用します。',
'designFiles.usefulInfoTip10': 'スケッチ機能でラフなレイアウトを描くと、エージェントがそれを元に生成します。',
'designFiles.usefulInfoTip11': 'プレビューで要素を選択してコメントすると、エージェントがその箇所を修正します。',
@@ -3189,6 +3200,9 @@ export const ja: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const ko: Dict = {
'settings.onboardingNewsletterBody': '제품 업데이트, 새 템플릿, 디자인 시스템, 앰배서더 활동을 메일로 받아보세요. 선택 사항이며 건너뛸 수 있습니다.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Open Design에 로그인',
'settings.onboardingCloudBody': '로그인하면 클라우드 AI로 바로 디자인을 시작할 수 있습니다. 복잡한 설정이 전혀 필요 없습니다.',
'settings.onboardingCloudSignIn': 'Open Design Cloud에 로그인',
'settings.onboardingCloudContinue': '계속 (로그인됨)',
'settings.onboardingCloudAlternative': '로컬 CLI 또는 자체 API 키 사용',
'settings.onboardingCloudRights': 'All rights reserved.',
'settings.onboardingCloudOr': '또는',
'settings.onboardingGateTooltipNoRuntime': '다음 단계는 AI로 실행됩니다 — 계속하려면 실행 방식을 선택하세요.',
'settings.onboardingGateTooltipAmr': '다음 단계는 AI로 실행됩니다 — 계속하려면 Open Design AMR에 로그인하세요.',
'settings.onboardingGateTooltipLocal': '다음 단계는 AI로 실행됩니다 — 계속하려면 사용 가능한 로컬 CLI를 선택하세요.',
'settings.onboardingGateTooltipByok': '다음 단계는 AI로 실행됩니다 — 계속하려면 모델 키를 추가하고 테스트하세요.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': '공식',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const ko: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 마케팅 에이전시',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const ko: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': '설정',
'settings.title': '실행 모드',
'settings.subtitle': '로컬 CLI와 BYOK 중에서 선택하세요.',
@@ -464,7 +475,7 @@ export const ko: Dict = {
'entry.discordAria': 'Open Design Discord 참여하기',
'entry.discordAriaWithOnline': 'Open Design Discord 참여하기 - {online}',
'entry.discordOnlineLabel': '{count}명 온라인',
'entry.followXLabel': 'X에서 @nexudotio 팔로우하기',
'entry.followXLabel': 'X에서 @OpenDesignHQ 팔로우하기',
'entry.resizeAria': '사이드바 크기 조절',
'entry.loadingWorkspace': '워크스페이스를 불러오는 중…',
'entry.useEverywhereTitle': '어디서나 사용',
@@ -2004,7 +2015,7 @@ export const ko: Dict = {
'designFiles.usefulInfoTip5': '아이디어가 없다면 홈의 커뮤니티에서 플러그인 템플릿을 사용해 보세요.',
'designFiles.usefulInfoTip6': '질문이나 제안이 있다면 Discord에서 함께 이야기해요.',
'designFiles.usefulInfoTip7': '마음에 드셨다면 GitHub에서 스타를 눌러 주세요.',
'designFiles.usefulInfoTip8': 'X에서 @nexudotio를 팔로우하고 최신 소식을 받아 보세요.',
'designFiles.usefulInfoTip8': 'X에서 @OpenDesignHQ를 팔로우하고 최신 소식을 받아 보세요.',
'designFiles.usefulInfoTip9': '이미지, 문서, 폴더째로 이 패널에 끌어다 놓으면 에이전트가 문맥으로 활용합니다.',
'designFiles.usefulInfoTip10': '새 스케치로 레이아웃을 그리면 에이전트가 그대로 만들어 줍니다.',
'designFiles.usefulInfoTip11': '미리보기에서 요소를 선택해 댓글을 남기면 에이전트가 해당 부분만 수정합니다.',
@@ -3189,6 +3200,9 @@ export const ko: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const pl: Dict = {
'settings.onboardingNewsletterBody': 'Otrzymuj nowości produktowe, nowe szablony, design systemy i działania ambasadorów na skrzynkę. Opcjonalne — możesz pominąć.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Zaloguj się do Open Design',
'settings.onboardingCloudBody': 'Zaloguj się i od razu zacznij projektować z chmurową SI — bez skomplikowanej konfiguracji.',
'settings.onboardingCloudSignIn': 'Zaloguj się do Open Design Cloud',
'settings.onboardingCloudContinue': 'Kontynuuj (zalogowano)',
'settings.onboardingCloudAlternative': 'Użyj lokalnego CLI lub własnego klucza API',
'settings.onboardingCloudRights': 'Wszelkie prawa zastrzeżone.',
'settings.onboardingCloudOr': 'lub',
'settings.onboardingGateTooltipNoRuntime': 'Kolejne kroki działają na AI — wybierz środowisko uruchomieniowe, aby kontynuować.',
'settings.onboardingGateTooltipAmr': 'Kolejne kroki działają na AI — zaloguj się do Open Design AMR, aby kontynuować.',
'settings.onboardingGateTooltipLocal': 'Kolejne kroki działają na AI — wybierz dostępne lokalne CLI, aby kontynuować.',
'settings.onboardingGateTooltipByok': 'Kolejne kroki działają na AI — dodaj i przetestuj swój klucz modelu, aby kontynuować.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Oficjalne',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const pl: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Agencja marketingowa',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const pl: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Ustawienia',
'settings.title': 'Tryb wykonywania',
'settings.subtitle': 'Wybierz lokalne CLI albo BYOK.',
@@ -464,7 +475,7 @@ export const pl: Dict = {
'entry.discordAria': 'Dołącz do Discorda Open Design',
'entry.discordAriaWithOnline': 'Dołącz do Discorda Open Design - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Obserwuj @nexudotio na X',
'entry.followXLabel': 'Obserwuj @OpenDesignHQ na X',
'entry.resizeAria': 'Zmień rozmiar paska bocznego',
'entry.loadingWorkspace': 'Ładowanie obszaru roboczego…',
'entry.useEverywhereTitle': 'Używaj wszędzie',
@@ -2004,7 +2015,7 @@ export const pl: Dict = {
'designFiles.usefulInfoTip5': 'Brak pomysłów? Wypróbuj szablon wtyczki w sekcji Społeczność na stronie głównej.',
'designFiles.usefulInfoTip6': 'Pytania lub sugestie? Dołącz do nas na Discordzie.',
'designFiles.usefulInfoTip7': 'Podoba Ci się Open Design? Zostaw gwiazdkę na GitHubie.',
'designFiles.usefulInfoTip8': 'Obserwuj @nexudotio na X, by śledzić nowości.',
'designFiles.usefulInfoTip8': 'Obserwuj @OpenDesignHQ na X, by śledzić nowości.',
'designFiles.usefulInfoTip9': 'Przeciągnij obrazy, dokumenty lub całe foldery do tego panelu — agent użyje ich jako kontekstu.',
'designFiles.usefulInfoTip10': 'Kliknij „Nowy szkic”, narysuj układ, a agent wygeneruje go za Ciebie.',
'designFiles.usefulInfoTip11': 'Zaznacz dowolny element w podglądzie i dodaj komentarz — agent wprowadzi punktowe zmiany.',
@@ -3189,6 +3200,9 @@ export const pl: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const ptBR: Dict = {
'settings.onboardingNewsletterBody': 'Receba novidades do produto, novos templates, design systems e trabalho de embaixadores no seu e-mail. Opcional — você pode pular.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Entrar no Open Design',
'settings.onboardingCloudBody': 'Entre e comece a criar na hora com IA na nuvem, sem configurações complicadas.',
'settings.onboardingCloudSignIn': 'Entrar no Open Design Cloud',
'settings.onboardingCloudContinue': 'Continuar (conectado)',
'settings.onboardingCloudAlternative': 'Usar uma CLI local ou sua própria chave de API',
'settings.onboardingCloudRights': 'Todos os direitos reservados.',
'settings.onboardingCloudOr': 'ou',
'settings.onboardingGateTooltipNoRuntime': 'As próximas etapas rodam em IA — escolha um runtime para continuar.',
'settings.onboardingGateTooltipAmr': 'As próximas etapas rodam em IA — faça login no Open Design AMR para continuar.',
'settings.onboardingGateTooltipLocal': 'As próximas etapas rodam em IA — selecione uma CLI local disponível para continuar.',
'settings.onboardingGateTooltipByok': 'As próximas etapas rodam em IA — adicione e teste sua chave de modelo para continuar.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Oficial',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const ptBR: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Agência de marketing',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const ptBR: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Configurações',
'settings.title': 'Modo de execução',
'settings.subtitle': 'Escolha entre CLI local e BYOK.',
@@ -464,7 +475,7 @@ export const ptBR: Dict = {
'entry.discordAria': 'Entre no Discord do Open Design',
'entry.discordAriaWithOnline': 'Entre no Discord do Open Design - {online}',
'entry.discordOnlineLabel': '{count} online',
'entry.followXLabel': 'Siga @nexudotio no X',
'entry.followXLabel': 'Siga @OpenDesignHQ no X',
'entry.resizeAria': 'Redimensionar barra lateral',
'entry.loadingWorkspace': 'Carregando área de trabalho…',
'entry.useEverywhereTitle': 'Usar em qualquer lugar',
@@ -2004,7 +2015,7 @@ export const ptBR: Dict = {
'designFiles.usefulInfoTip5': 'Sem ideias? Experimente um template de plugin na Comunidade, na página inicial.',
'designFiles.usefulInfoTip6': 'Dúvidas ou sugestões? Entre no nosso Discord.',
'designFiles.usefulInfoTip7': 'Gostou do Open Design? Deixe uma estrela no GitHub.',
'designFiles.usefulInfoTip8': 'Siga @nexudotio no X para acompanhar as novidades.',
'designFiles.usefulInfoTip8': 'Siga @OpenDesignHQ no X para acompanhar as novidades.',
'designFiles.usefulInfoTip9': 'Arraste imagens, documentos ou pastas inteiras para este painel — o agente usará tudo como contexto.',
'designFiles.usefulInfoTip10': 'Clique em «Novo rascunho», desenhe um layout e deixe o agente gerá-lo.',
'designFiles.usefulInfoTip11': 'Selecione qualquer elemento na prévia para comentar — o agente fará ajustes pontuais.',
@@ -3189,6 +3200,9 @@ export const ptBR: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const ru: Dict = {
'settings.onboardingNewsletterBody': 'Получайте новости продукта, новые шаблоны, дизайн-системы и работу амбассадоров на почту. Необязательно — можно пропустить.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Войти в Open Design',
'settings.onboardingCloudBody': 'Войдите и сразу начните создавать с облачным ИИ — без сложных настроек.',
'settings.onboardingCloudSignIn': 'Войти в Open Design Cloud',
'settings.onboardingCloudContinue': 'Продолжить (вы вошли)',
'settings.onboardingCloudAlternative': 'Использовать локальный CLI или свой ключ API',
'settings.onboardingCloudRights': 'Все права защищены.',
'settings.onboardingCloudOr': 'или',
'settings.onboardingGateTooltipNoRuntime': 'Следующие шаги выполняются на ИИ — выберите способ запуска, чтобы продолжить.',
'settings.onboardingGateTooltipAmr': 'Следующие шаги выполняются на ИИ — войдите в Open Design AMR, чтобы продолжить.',
'settings.onboardingGateTooltipLocal': 'Следующие шаги выполняются на ИИ — выберите доступный локальный CLI, чтобы продолжить.',
'settings.onboardingGateTooltipByok': 'Следующие шаги выполняются на ИИ — добавьте и протестируйте ключ модели, чтобы продолжить.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Официальный',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const ru: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Маркетинговое агентство',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const ru: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Настройки',
'settings.title': 'Режим выполнения',
'settings.subtitle': 'Выберите локальный CLI или BYOK.',
@@ -464,7 +475,7 @@ export const ru: Dict = {
'entry.discordAria': 'Присоединиться к Discord-сообществу Open Design',
'entry.discordAriaWithOnline': 'Присоединиться к Discord-сообществу Open Design — {online}',
'entry.discordOnlineLabel': '{count} онлайн',
'entry.followXLabel': 'Подписаться на @nexudotio в X',
'entry.followXLabel': 'Подписаться на @OpenDesignHQ в X',
'entry.resizeAria': 'Изменить размер боковой панели',
'entry.loadingWorkspace': 'Загрузка рабочего пространства…',
'entry.useEverywhereTitle': 'Использовать везде',
@@ -2004,7 +2015,7 @@ export const ru: Dict = {
'designFiles.usefulInfoTip5': 'Нет идей? Попробуйте шаблон плагина в разделе «Сообщество» на главной.',
'designFiles.usefulInfoTip6': 'Вопросы или идеи? Присоединяйтесь к нам в Discord.',
'designFiles.usefulInfoTip7': 'Нравится Open Design? Поставьте звезду на GitHub.',
'designFiles.usefulInfoTip8': 'Подписывайтесь на @nexudotio в X, чтобы не пропустить новинки.',
'designFiles.usefulInfoTip8': 'Подписывайтесь на @OpenDesignHQ в X, чтобы не пропустить новинки.',
'designFiles.usefulInfoTip9': 'Перетащите изображения, документы или целые папки в эту панель — агент использует их как контекст.',
'designFiles.usefulInfoTip10': 'Нажмите «Новый эскиз», нарисуйте макет — агент сгенерирует дизайн по нему.',
'designFiles.usefulInfoTip11': 'Выделите любой элемент в предпросмотре и оставьте комментарий — агент внесёт точечные правки.',
@@ -3189,6 +3200,9 @@ export const ru: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const th: Dict = {
'settings.onboardingNewsletterBody': 'Get product updates, new templates, design-system drops, and ambassador work in your inbox. Optional — you can skip this.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'ลงชื่อเข้าใช้ Open Design',
'settings.onboardingCloudBody': 'ลงชื่อเข้าใช้แล้วเริ่มออกแบบด้วย AI บนคลาวด์ได้ทันที โดยไม่ต้องตั้งค่าที่ซับซ้อน',
'settings.onboardingCloudSignIn': 'ลงชื่อเข้าใช้ Open Design Cloud',
'settings.onboardingCloudContinue': 'ดำเนินการต่อ (เข้าสู่ระบบแล้ว)',
'settings.onboardingCloudAlternative': 'ใช้ CLI ในเครื่องหรือคีย์ API ของคุณเอง',
'settings.onboardingCloudRights': 'สงวนลิขสิทธิ์ทั้งหมด',
'settings.onboardingCloudOr': 'หรือ',
'settings.onboardingGateTooltipNoRuntime': 'ขั้นตอนต่อไปทำงานด้วย AI — เลือกวิธีรันเพื่อดำเนินการต่อ',
'settings.onboardingGateTooltipAmr': 'ขั้นตอนต่อไปทำงานด้วย AI — ลงชื่อเข้าใช้ Open Design AMR เพื่อดำเนินการต่อ',
'settings.onboardingGateTooltipLocal': 'ขั้นตอนต่อไปทำงานด้วย AI — เลือก CLI ในเครื่องที่พร้อมใช้งานเพื่อดำเนินการต่อ',
'settings.onboardingGateTooltipByok': 'ขั้นตอนต่อไปทำงานด้วย AI — เพิ่มและทดสอบคีย์โมเดลของคุณเพื่อดำเนินการต่อ',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'อย่างเป็นทางการ',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const th: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 เอเจนซีการตลาด',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const th: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'การตั้งค่า',
'settings.title': 'โหมดการรัน',
'settings.subtitle': 'เลือกระหว่าง Local CLI และ BYOK',
@@ -464,7 +475,7 @@ export const th: Dict = {
'entry.discordAria': 'เข้าร่วม Discord ของ Open Design',
'entry.discordAriaWithOnline': 'เข้าร่วม Discord ของ Open Design - {online}',
'entry.discordOnlineLabel': 'ออนไลน์ {count} คน',
'entry.followXLabel': 'ติดตาม @nexudotio บน X',
'entry.followXLabel': 'ติดตาม @OpenDesignHQ บน X',
'entry.resizeAria': 'ปรับขนาดแถบด้านข้าง',
'entry.loadingWorkspace': 'กำลังโหลดพื้นที่ทำงาน…',
'entry.useEverywhereTitle': 'ใช้ได้ทุกที่',
@@ -2004,7 +2015,7 @@ export const th: Dict = {
'designFiles.usefulInfoTip5': 'ยังไม่มีไอเดีย? ลองเทมเพลตปลั๊กอินในคอมมูนิตี้บนหน้าแรก',
'designFiles.usefulInfoTip6': 'มีคำถามหรือข้อเสนอแนะ? เข้าร่วม Discord ของเรา',
'designFiles.usefulInfoTip7': 'ถ้าชอบ Open Design ฝากกดดาวให้เราบน GitHub',
'designFiles.usefulInfoTip8': 'ติดตาม @nexudotio บน X เพื่อรับข่าวสารล่าสุด',
'designFiles.usefulInfoTip8': 'ติดตาม @OpenDesignHQ บน X เพื่อรับข่าวสารล่าสุด',
'designFiles.usefulInfoTip9': 'ลากรูป เอกสาร หรือทั้งโฟลเดอร์มาที่แผงนี้ เอเจนต์จะใช้เป็นบริบท',
'designFiles.usefulInfoTip10': 'กด สเก็ตช์ใหม่ วาดเลย์เอาต์คร่าว ๆ แล้วให้เอเจนต์สร้างตาม',
'designFiles.usefulInfoTip11': 'เลือกองค์ประกอบใดก็ได้ในพรีวิวเพื่อคอมเมนต์ เอเจนต์จะแก้ไขเฉพาะจุด',
@@ -3189,6 +3200,9 @@ export const th: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const tr: Dict = {
'settings.onboardingNewsletterBody': 'Ürün güncellemelerini, yeni şablonları, tasarım sistemlerini ve elçi çalışmalarını gelen kutunuzda alın. İsteğe bağlı — atlayabilirsiniz.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Open Designa giriş yap',
'settings.onboardingCloudBody': 'Giriş yap ve bulut yapay zekâsıyla hemen tasarlamaya başla — karmaşık kurulum gerekmez.',
'settings.onboardingCloudSignIn': 'Open Design Clouda giriş yap',
'settings.onboardingCloudContinue': 'Devam et (giriş yapıldı)',
'settings.onboardingCloudAlternative': 'Yerel bir CLI ya da kendi API anahtarını kullan',
'settings.onboardingCloudRights': 'Tüm hakları saklıdır.',
'settings.onboardingCloudOr': 'veya',
'settings.onboardingGateTooltipNoRuntime': 'Sonraki adımlar yapay zekâ ile çalışır — devam etmek için bir çalışma yöntemi seçin.',
'settings.onboardingGateTooltipAmr': 'Sonraki adımlar yapay zekâ ile çalışır — devam etmek için Open Design AMRye giriş yapın.',
'settings.onboardingGateTooltipLocal': 'Sonraki adımlar yapay zekâ ile çalışır — devam etmek için kullanılabilir bir yerel CLI seçin.',
'settings.onboardingGateTooltipByok': 'Sonraki adımlar yapay zekâ ile çalışır — devam etmek için model anahtarınızı ekleyip test edin.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Resmi',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const tr: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Pazarlama ajansı',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const tr: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Ayarlar',
'settings.title': 'Yürütme modu',
'settings.subtitle': 'Yerel CLI ile BYOK arasında seçim yapın.',
@@ -464,7 +475,7 @@ export const tr: Dict = {
'entry.discordAria': 'Open Design Discord\'una katılın',
'entry.discordAriaWithOnline': 'Open Design Discord\'una katılın - {online}',
'entry.discordOnlineLabel': '{count} çevrimiçi',
'entry.followXLabel': 'X\'te @nexudotio hesabını takip et',
'entry.followXLabel': 'X\'te @OpenDesignHQ hesabını takip et',
'entry.resizeAria': 'Yan çubuğu yeniden boyutlandır',
'entry.loadingWorkspace': 'Çalışma alanı yükleniyor…',
'entry.useEverywhereTitle': 'Her yerde kullan',
@@ -2004,7 +2015,7 @@ export const tr: Dict = {
'designFiles.usefulInfoTip5': 'Fikriniz mi yok? Ana sayfadaki Topluluk bölümünden bir eklenti şablonu deneyin.',
'designFiles.usefulInfoTip6': 'Soru veya öneriniz mi var? Discord sunucumuza katılın.',
'designFiles.usefulInfoTip7': 'Beğendiyseniz GitHub üzerinde bize yıldız verin.',
'designFiles.usefulInfoTip8': 'Yenilikler için X üzerinde @nexudotio hesabını takip edin.',
'designFiles.usefulInfoTip8': 'Yenilikler için X üzerinde @OpenDesignHQ hesabını takip edin.',
'designFiles.usefulInfoTip9': 'Görselleri, dokümanları veya klasörleri bu panele sürükleyin; ajan onları bağlam olarak kullanır.',
'designFiles.usefulInfoTip10': 'Yeni eskiz ile kaba bir düzen çizin, ajan onu tasarıma dönüştürsün.',
'designFiles.usefulInfoTip11': 'Önizlemede herhangi bir ögeyi seçip yorum bırakın; ajan hedefli düzenleme yapar.',
@@ -3189,6 +3200,9 @@ export const tr: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const uk: Dict = {
'settings.onboardingNewsletterBody': 'Отримуйте новини продукту, нові шаблони, дизайн-системи та роботу амбасадорів на пошту. Необов’язково — можна пропустити.',
'settings.onboardingConnectTitle': 'Choose a runtime',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': 'Увійти в Open Design',
'settings.onboardingCloudBody': 'Увійдіть і одразу почніть створювати з хмарним ШІ — без складних налаштувань.',
'settings.onboardingCloudSignIn': 'Увійти в Open Design Cloud',
'settings.onboardingCloudContinue': 'Продовжити (ви увійшли)',
'settings.onboardingCloudAlternative': 'Використати локальний CLI або власний ключ API',
'settings.onboardingCloudRights': 'Усі права захищено.',
'settings.onboardingCloudOr': 'або',
'settings.onboardingGateTooltipNoRuntime': 'Наступні кроки виконуються на ШІ — виберіть спосіб запуску, щоб продовжити.',
'settings.onboardingGateTooltipAmr': 'Наступні кроки виконуються на ШІ — увійдіть в Open Design AMR, щоб продовжити.',
'settings.onboardingGateTooltipLocal': 'Наступні кроки виконуються на ШІ — виберіть доступний локальний CLI, щоб продовжити.',
'settings.onboardingGateTooltipByok': 'Наступні кроки виконуються на ШІ — додайте та протестуйте ключ моделі, щоб продовжити.',
'settings.onboardingRecommended': 'Recommended',
'settings.onboardingAmrCloudOfficialBadge': 'Офіційний',
'settings.onboardingLocalTitle': 'Local coding agent',
@@ -169,6 +180,7 @@ export const uk: Dict = {
'settings.onboardingRoleDesigner': '🎨 Designer',
'settings.onboardingRoleEngineer': '💻 Engineer',
'settings.onboardingRoleMarketing': '📣 Marketing',
'settings.onboardingRoleAgency': '📣 Маркетингова агенція',
'settings.onboardingRoleGrowth': '📈 Growth',
'settings.onboardingRoleOps': '⚙️ Operations',
'settings.onboardingRoleFounder': '🚀 Founder / executive',
@@ -197,7 +209,6 @@ export const uk: Dict = {
'settings.onboardingBack': 'Back',
'settings.onboardingContinue': 'Continue',
'settings.onboardingFinish': 'Finish setup',
'settings.onboardingSkip': 'Skip for now',
'settings.kicker': 'Налаштування',
'settings.title': 'Режим виконання',
'settings.subtitle': 'Виберіть локальний CLI або BYOK.',
@@ -464,7 +475,7 @@ export const uk: Dict = {
'entry.discordAria': 'Приєднатися до Discord Open Design',
'entry.discordAriaWithOnline': 'Приєднатися до Discord Open Design - {online}',
'entry.discordOnlineLabel': '{count} онлайн',
'entry.followXLabel': 'Стежити за @nexudotio в X',
'entry.followXLabel': 'Стежити за @OpenDesignHQ в X',
'entry.resizeAria': 'Змінити розмір бічної панелі',
'entry.loadingWorkspace': 'Завантаження робочого простору…',
'entry.useEverywhereTitle': 'Використовувати скрізь',
@@ -2004,7 +2015,7 @@ export const uk: Dict = {
'designFiles.usefulInfoTip5': 'Немає ідей? Спробуйте шаблон плагіна в розділі «Спільнота» на головній.',
'designFiles.usefulInfoTip6': 'Питання чи пропозиції? Приєднуйтесь до нас у Discord.',
'designFiles.usefulInfoTip7': 'Подобається Open Design? Поставте зірку на GitHub.',
'designFiles.usefulInfoTip8': 'Стежте за @nexudotio в X, щоб дізнаватися про новинки.',
'designFiles.usefulInfoTip8': 'Стежте за @OpenDesignHQ в X, щоб дізнаватися про новинки.',
'designFiles.usefulInfoTip9': 'Перетягніть зображення, документи чи цілі папки в цю панель — агент використає їх як контекст.',
'designFiles.usefulInfoTip10': 'Натисніть «Новий ескіз», намалюйте макет — агент згенерує дизайн за ним.',
'designFiles.usefulInfoTip11': 'Виділіть будь-який елемент у попередньому перегляді й залиште коментар — агент внесе точкові правки.',
@@ -3189,6 +3200,9 @@ export const uk: Dict = {
'brand.colorsCount': '{count} colors',
'brand.extracting': 'Extracting…',
'brand.failed': 'Extraction failed',
'brand.needsInput': 'Needs input',
'brand.needsInputHint': 'Extraction paused — open the project to finish verification or answer the question.',
'brand.appliedToChat': 'Using {name}',
'brand.previewEmpty': 'Select a brand to preview',
'brand.viewDetails': 'View details',
'newBrand.title': 'New Brand',

View File

@@ -128,6 +128,17 @@ export const zhCN: Dict = {
'settings.onboardingNewsletterBody': '留下邮箱,接收产品更新、新模板、设计系统资源和社区活动。',
'settings.onboardingConnectTitle': '选择运行方式',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': '登录 Open Design',
'settings.onboardingCloudBody': '登录后可直接用云端 AI 开始设计,无需任何复杂配置。',
'settings.onboardingCloudSignIn': '登录 Open Design 云端',
'settings.onboardingCloudContinue': '继续(已登录)',
'settings.onboardingCloudAlternative': '使用本地 CLI 或自己的 API Key',
'settings.onboardingCloudRights': '保留所有权利。',
'settings.onboardingCloudOr': '或',
'settings.onboardingGateTooltipNoRuntime': '后续流程由 AI 运行,请先选择一种运行方式。',
'settings.onboardingGateTooltipAmr': '后续流程由 AI 运行,请先登录 Open Design AMR。',
'settings.onboardingGateTooltipLocal': '后续流程由 AI 运行,请先选择一个可用的本地 CLI。',
'settings.onboardingGateTooltipByok': '后续流程由 AI 运行,请先填写并测试通过你的模型 Key。',
'settings.onboardingRecommended': '推荐',
'settings.onboardingAmrCloudOfficialBadge': '官方',
'settings.onboardingLocalTitle': '本地 Coding Agent',
@@ -169,6 +180,7 @@ export const zhCN: Dict = {
'settings.onboardingRoleDesigner': '🎨 设计师',
'settings.onboardingRoleEngineer': '💻 研发',
'settings.onboardingRoleMarketing': '📣 营销',
'settings.onboardingRoleAgency': '📣 营销代理商',
'settings.onboardingRoleGrowth': '📈 增长',
'settings.onboardingRoleOps': '⚙️ 运营',
'settings.onboardingRoleFounder': '🚀 创始人 / 管理者',
@@ -197,7 +209,6 @@ export const zhCN: Dict = {
'settings.onboardingBack': '上一步',
'settings.onboardingContinue': '继续',
'settings.onboardingFinish': '完成',
'settings.onboardingSkip': '先跳过',
'settings.kicker': '设置',
'settings.title': '执行模式',
'settings.subtitle': '在本机 CLI 与 BYOK 之间选择。',
@@ -464,7 +475,7 @@ export const zhCN: Dict = {
'entry.discordAria': '加入 Open Design Discord',
'entry.discordAriaWithOnline': '加入 Open Design Discord - {online}',
'entry.discordOnlineLabel': '{count} 在线',
'entry.followXLabel': '在 X 上关注 @nexudotio',
'entry.followXLabel': '在 X 上关注 @OpenDesignHQ',
'entry.resizeAria': '调整侧边栏宽度',
'entry.loadingWorkspace': '正在加载工作区…',
'entry.useEverywhereTitle': '随处使用',
@@ -2004,7 +2015,7 @@ export const zhCN: Dict = {
'designFiles.usefulInfoTip5': '没有想法?试试首页 Community 的插件模板,一键生成同款。',
'designFiles.usefulInfoTip6': '遇到问题或有建议?加入 Discord 和我们聊聊。',
'designFiles.usefulInfoTip7': '觉得好用?在 GitHub 给我们点个 Star。',
'designFiles.usefulInfoTip8': '在 X 上关注 @nexudotio,第一时间了解新功能。',
'designFiles.usefulInfoTip8': '在 X 上关注 @OpenDesignHQ,第一时间了解新功能。',
'designFiles.usefulInfoTip9': '把图片、文档甚至整个文件夹拖到当前面板,智能体都会用作上下文。',
'designFiles.usefulInfoTip10': '点「新建草图」手绘一个布局,让智能体照着生成。',
'designFiles.usefulInfoTip11': '在预览中圈选任意元素发表评论,智能体会针对性修改。',
@@ -3190,6 +3201,9 @@ export const zhCN: Dict = {
'brand.colorsCount': '{count} 种颜色',
'brand.extracting': '提取中…',
'brand.failed': '提取失败',
'brand.needsInput': '需要操作',
'brand.needsInputHint': '提取已暂停 —— 打开项目完成验证或回答问题。',
'brand.appliedToChat': '正在使用 {name}',
'brand.previewEmpty': '选择一个设计系统进行预览',
'brand.viewDetails': '查看详情',
'newBrand.title': '新建设计系统',

View File

@@ -128,6 +128,17 @@ export const zhTW: Dict = {
'settings.onboardingNewsletterBody': '在信箱裡收到產品更新、新模版、設計系統與大使活動。選填——可以跳過。',
'settings.onboardingConnectTitle': '選擇執行方式',
'settings.onboardingConnectBody': '',
'settings.onboardingCloudTitle': '登入 Open Design',
'settings.onboardingCloudBody': '登入後可直接用雲端 AI 開始設計,無需任何複雜設定。',
'settings.onboardingCloudSignIn': '登入 Open Design 雲端',
'settings.onboardingCloudContinue': '繼續(已登入)',
'settings.onboardingCloudAlternative': '使用本機 CLI 或自己的 API Key',
'settings.onboardingCloudRights': '保留所有權利。',
'settings.onboardingCloudOr': '或',
'settings.onboardingGateTooltipNoRuntime': '後續流程由 AI 執行,請先選擇一種執行方式。',
'settings.onboardingGateTooltipAmr': '後續流程由 AI 執行,請先登入 Open Design AMR。',
'settings.onboardingGateTooltipLocal': '後續流程由 AI 執行,請先選擇一個可用的本機 CLI。',
'settings.onboardingGateTooltipByok': '後續流程由 AI 執行,請先填寫並測試通過你的模型 Key。',
'settings.onboardingRecommended': '推荐',
'settings.onboardingAmrCloudOfficialBadge': '官方',
'settings.onboardingLocalTitle': '本地 Coding Agent',
@@ -169,6 +180,7 @@ export const zhTW: Dict = {
'settings.onboardingRoleDesigner': '🎨 設計師',
'settings.onboardingRoleEngineer': '💻 研發',
'settings.onboardingRoleMarketing': '📣 行銷',
'settings.onboardingRoleAgency': '📣 行銷代理商',
'settings.onboardingRoleGrowth': '📈 增長',
'settings.onboardingRoleOps': '⚙️ 營運',
'settings.onboardingRoleFounder': '🚀 創辦人 / 管理者',
@@ -197,7 +209,6 @@ export const zhTW: Dict = {
'settings.onboardingBack': '上一步',
'settings.onboardingContinue': '继续',
'settings.onboardingFinish': '完成設定',
'settings.onboardingSkip': '先跳過',
'settings.kicker': '設定',
'settings.title': '執行模式',
'settings.subtitle': '在本機 CLI 與 BYOK 之間選擇。',
@@ -464,7 +475,7 @@ export const zhTW: Dict = {
'entry.discordAria': '加入 Open Design Discord',
'entry.discordAriaWithOnline': '加入 Open Design Discord - {online}',
'entry.discordOnlineLabel': '{count} 在線',
'entry.followXLabel': '在 X 上追蹤 @nexudotio',
'entry.followXLabel': '在 X 上追蹤 @OpenDesignHQ',
'entry.resizeAria': '調整側邊欄寬度',
'entry.loadingWorkspace': '正在載入工作區…',
'entry.useEverywhereTitle': '隨處使用',
@@ -2004,7 +2015,7 @@ export const zhTW: Dict = {
'designFiles.usefulInfoTip5': '沒有想法?試試首頁「社群」的外掛模板,一鍵生成同款。',
'designFiles.usefulInfoTip6': '遇到問題或有建議?加入 Discord 和我們聊聊。',
'designFiles.usefulInfoTip7': '覺得好用?在 GitHub 給我們點個 Star。',
'designFiles.usefulInfoTip8': '在 X 上關注 @nexudotio,第一時間了解新功能。',
'designFiles.usefulInfoTip8': '在 X 上關注 @OpenDesignHQ,第一時間了解新功能。',
'designFiles.usefulInfoTip9': '把圖片、文件甚至整個資料夾拖到目前面板,智慧體都會用作上下文。',
'designFiles.usefulInfoTip10': '點「新建草圖」手繪一個版面,讓智慧體照著生成。',
'designFiles.usefulInfoTip11': '在預覽中圈選任意元素發表評論,智慧體會針對性修改。',
@@ -3190,6 +3201,9 @@ export const zhTW: Dict = {
'brand.colorsCount': '{count} 種顏色',
'brand.extracting': '擷取中…',
'brand.failed': '擷取失敗',
'brand.needsInput': '需要操作',
'brand.needsInputHint': '提取已暫停 —— 開啟專案完成驗證或回答問題。',
'brand.appliedToChat': '正在使用 {name}',
'brand.previewEmpty': '選擇一個品牌進行預覽',
'brand.viewDetails': '查看詳情',
'newBrand.title': '新增品牌',

View File

@@ -152,6 +152,17 @@ export interface Dict {
'settings.onboardingNewsletterBody': string;
'settings.onboardingConnectTitle': string;
'settings.onboardingConnectBody': string;
'settings.onboardingCloudTitle': string;
'settings.onboardingCloudBody': string;
'settings.onboardingCloudSignIn': string;
'settings.onboardingCloudContinue': string;
'settings.onboardingCloudAlternative': string;
'settings.onboardingCloudOr': string;
'settings.onboardingCloudRights': string;
'settings.onboardingGateTooltipNoRuntime': string;
'settings.onboardingGateTooltipAmr': string;
'settings.onboardingGateTooltipLocal': string;
'settings.onboardingGateTooltipByok': string;
'settings.onboardingRecommended': string;
'settings.onboardingAmrCloudOfficialBadge': string;
'settings.onboardingLocalTitle': string;
@@ -193,6 +204,7 @@ export interface Dict {
'settings.onboardingRoleDesigner': string;
'settings.onboardingRoleEngineer': string;
'settings.onboardingRoleMarketing': string;
'settings.onboardingRoleAgency': string;
'settings.onboardingRoleGrowth': string;
'settings.onboardingRoleOps': string;
'settings.onboardingRoleFounder': string;
@@ -221,7 +233,6 @@ export interface Dict {
'settings.onboardingBack': string;
'settings.onboardingContinue': string;
'settings.onboardingFinish': string;
'settings.onboardingSkip': string;
'settings.kicker': string;
'settings.title': string;
'settings.subtitle': string;
@@ -3332,6 +3343,9 @@ export interface Dict {
'brand.colorsCount': string;
'brand.extracting': string;
'brand.failed': string;
'brand.needsInput': string;
'brand.needsInputHint': string;
'brand.appliedToChat': string;
'brand.previewEmpty': string;
'brand.viewDetails': string;
'newBrand.title': string;

View File

@@ -96,10 +96,13 @@ export function parseRoute(pathname: string): Route {
return { kind: 'home', view: 'design-systems' };
}
if (parts[0] === 'brands') {
// Brands merged into Design systems: a brand is a `user:<id>` design system
// and extraction now starts from the design-system create wizard. Legacy
// `/brands` and `/brands/:id` deep-links redirect onto the unified tab.
return { kind: 'home', view: 'design-systems' };
// The Brands tab shows everything inline in its preview panel; there is no
// separate detail view. A `/brands/:id` deep-link just preselects which
// brand the inline preview renders.
if (parts[1]) {
return { kind: 'home', view: 'brands', brandId: decodeURIComponent(parts[1]) };
}
return { kind: 'home', view: 'brands' };
}
if (parts[0] === 'automations' || parts[0] === 'tasks') {
return { kind: 'home', view: 'tasks' };

View File

@@ -12,12 +12,21 @@
export const HOME_CHIP_INTENT_EVENT = 'od:home-chip-intent';
let pendingChipId: string | null = null;
let pendingNotice: string | null = null;
export interface HomeChipOptions {
/** A one-shot confirmation banner HomeView shows once it consumes the chip —
* e.g. "Using Ramp Brand Kit" after "Use in new chat". Makes an otherwise
* invisible navigate+apply visibly verifiable to the user. */
notice?: string;
}
// Queue a Home composer chip to auto-select on the next Home render, then
// notify any mounted HomeView. Safe to call before HomeView exists — the
// pending id survives until consumed.
export function requestHomeChip(chipId: string): void {
// pending id (and optional confirmation notice) survives until consumed.
export function requestHomeChip(chipId: string, options?: HomeChipOptions): void {
pendingChipId = chipId;
pendingNotice = options?.notice ?? null;
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent(HOME_CHIP_INTENT_EVENT, { detail: { chipId } }));
}
@@ -30,6 +39,14 @@ export function consumePendingHomeChip(): string | null {
return chipId;
}
// Read and clear the pending confirmation notice queued alongside the chip.
// Returns null when none is queued. Consume AFTER consumePendingHomeChip.
export function consumePendingHomeNotice(): string | null {
const notice = pendingNotice;
pendingNotice = null;
return notice;
}
// Peek without consuming — lets a consumer bail early (e.g. plugins not yet
// loaded) without dropping the pending intent.
export function hasPendingHomeChip(): boolean {

View File

@@ -66,6 +66,10 @@ async function openManualMemoryTab() {
fireEvent.click(await screen.findByRole('tab', { name: 'Add manually' }));
}
async function openAdvancedMemoryModal() {
fireEvent.click(await screen.findByRole('button', { name: 'Advanced' }));
}
// The top-level "Memories" / "How it works" tabs render their label plus a
// caption, so their accessible name is composite. Match by the leading label.
const howItWorksTopTab = { name: /^How it works/ } as const;
@@ -203,7 +207,6 @@ describe('MemorySection', () => {
await waitFor(() => {
expect(screen.getByText('UI preferences')).toBeTruthy();
});
expect(screen.getByText('✓ Memory created')).toBeTruthy();
expect(createBodies).toEqual([
{
name: 'UI preferences',
@@ -290,9 +293,7 @@ describe('MemorySection', () => {
renderMemorySection();
// The entry editor opened from a tree node renders inside the Add-manually
// sub-tab.
await openManualMemoryTab();
await openAdvancedMemoryModal();
const treeSummary = await screen.findByText('Memory tree');
const treeDetails = treeSummary.closest('details')!;
expect(within(treeDetails).getByText('/project')).toBeTruthy();
@@ -378,6 +379,7 @@ describe('MemorySection', () => {
renderMemorySection();
await openAdvancedMemoryModal();
const treeDetails = (await screen.findByText('Memory tree')).closest('details')!;
const childRow = within(treeDetails)
.getByText('Design agent goal')
@@ -427,9 +429,7 @@ describe('MemorySection', () => {
renderMemorySection();
// The "✓ Index saved" flash toast renders inside the Add-manually sub-tab,
// so open it before saving the index.
await openManualMemoryTab();
await openAdvancedMemoryModal();
fireEvent.click(await screen.findByText('MEMORY.md (index)'));
const indexArea = await findMemoryIndexTextarea();
fireEvent.change(indexArea, {
@@ -440,9 +440,8 @@ describe('MemorySection', () => {
fireEvent.click(screen.getByRole('button', { name: 'Save index' }));
await waitFor(() => {
expect(screen.getByText('✓ Index saved')).toBeTruthy();
expect(putBodies).toEqual(['# Memory\n\n- Existing bullet\n- New bullet\n']);
});
expect(putBodies).toEqual(['# Memory\n\n- Existing bullet\n- New bullet\n']);
});
it('reveals the editor after editing an existing memory entry', async () => {
@@ -563,6 +562,7 @@ describe('MemorySection', () => {
const savedRow = await screen.findByText('UI preferences');
const extractionRow = await screen.findByText('Remember I prefer dark mode');
await openAddMemories();
await openAdvancedMemoryModal();
const indexSummary = screen.getByText('MEMORY.md (index)')
.closest('summary') as HTMLElement;
@@ -572,7 +572,8 @@ describe('MemorySection', () => {
expect(extractionRow.closest('.library-card')?.className).toContain(
'memory-extraction-card',
);
expect(indexSummary.closest('.memory-advanced-section')).toBeTruthy();
expect(indexSummary.closest('.memory-action-modal--advanced')).toBeTruthy();
expect(indexSummary.closest('.memory-advanced-card')).toBeTruthy();
expect(indexSummary.closest('.memory-records-section')).toBeNull();
expect(screen.queryByText('Extraction history')).toBeNull();
expect(indexSummary.className).toContain('memory-details-summary');
@@ -1490,7 +1491,7 @@ describe('MemorySection', () => {
fireEvent.click(screen.getByRole('button', { name: 'Save' }));
await waitFor(() => {
expect(screen.getByText('✓ Memory saved')).toBeTruthy();
expect(screen.getByText('Updated preference')).toBeTruthy();
});
expect(putBodies).toEqual([
{
@@ -1501,7 +1502,6 @@ describe('MemorySection', () => {
body: '- Prefer spacious layouts',
},
]);
expect(screen.getByText('Updated preference')).toBeTruthy();
});
it('keeps the expanded preview control visually distinct from delete', async () => {
@@ -1706,6 +1706,7 @@ describe('MemorySection', () => {
renderMemorySection();
await openAdvancedMemoryModal();
fireEvent.click(await screen.findByText('MEMORY.md (index)'));
const indexArea = await findMemoryIndexTextarea();
fireEvent.change(indexArea, {