diff --git a/docs/architecture-audit-2026-07-30/SetupReadinessImplementation.md b/docs/architecture-audit-2026-07-30/SetupReadinessImplementation.md new file mode 100644 index 0000000000..0d0def0b8e --- /dev/null +++ b/docs/architecture-audit-2026-07-30/SetupReadinessImplementation.md @@ -0,0 +1,165 @@ +# Architecture Audit — Setup Readiness Implementation + +**Date:** 2026-07-30 +**Scope:** goal-driven setup FSM, secret-free tool detection, Cloud membership, +repo policy, history import, sync verification, destination routing, native +setup reentry accelerator +**Mode:** implementation audit + +## Acceptance criteria + +- [x] Goal-specific visible steps and guards replace slide-index completion. +- [x] Progress is resumable and normalized through one versioned schema. +- [x] Completion persists progress and outcome in one settings write. +- [x] Key detection cannot retain secret-bearing RPC fields. +- [x] Cloud create/join has one shared membership command boundary. +- [x] Organization success requires authoritative roster convergence. +- [x] Admin and member team paths have distinct mutation permissions. +- [x] Workspace, Project, Work Item, Session, and Org remain separate concepts. +- [x] Dismissed/completed users can reopen the same checklist. +- [x] Personal, work-management, and team goals land on existing product + surfaces. + +## Layer 1 — Compilation correctness + +- `npm run typecheck`: passed. +- ESLint over every changed TypeScript/TSX file: passed. +- Focused Vitest suite: 26 tests passed across flow guards, secret-safe + detection, hidden test reentry, route reentry, Settings reentry, outcome + migration, and the atomic settings commit boundary. +- Native menu changes are checked through the `system_services` crate; no + network or persistence wire schema changed. + +## Layer 2 — Dead code and structural deduplication + +- `useCloudOrgMembershipActions` is called by both onboarding and + `CreateCollabOrgView`; create/join auth refresh, alias creation, and roster + convergence no longer have parallel implementations. +- Existing Key validation, external-history rescan, workspace remote resolver, + appearance settings, Cloud policy clients, sync engine, tutorial registry, + Chat Panel tab factories, and sidebar scope atoms remain authoritative. +- Legacy presentation-only onboarding steps remain exported for now; removing + them is a separate cleanup because other imports/tests may still reference + them. They are no longer in the production `STEP_CONFIGS` execution path. + +## Layer 3 — Naming consistency + +| Name | Meaning | +| -------------------------- | -------------------------------------------------------- | +| `SetupWalkthroughProgress` | Durable, secret-free decisions and confirmed results | +| `SetupOperation` | One foreground mutation/detection command | +| `selectedOrgId` | Managed Cloud organization membership selected for setup | +| `repoScopes` | Normalized Git remote identities, never local paths | +| `verifiedAt` | The current team-path postcondition completed | + +The app retains the existing `SetupWalkthrough` route/component name for +compatibility; user-facing copy calls it a setup checklist/readiness flow. + +## Layer 4 — Semantic overloading + +The implementation does not merge domain objects to simplify the wizard: + +| Term | Product meaning in the flow | +| ------------ | ---------------------------------------------- | +| Workspace | Local folders/files used by an agent | +| Session | Agent conversation and execution context | +| Work Item | Trackable task with status/assignee/discussion | +| Project | Planning container for Work Items | +| Organization | Cloud membership and team policy boundary | + +The dedicated work-model step exposes these distinctions before the user lands +in Work Management. + +## Layer 5 — Default branch analysis + +- No goal is selected by default; the first transition is guarded. +- Personal and work-management goals omit Cloud steps rather than silently + pretending team readiness. +- Sharing defaults to `metadata_only`, retaining an explicit privacy boundary. +- A local folder with no Git remote is rejected as a team scope. +- Tool detection failures resolve per provider and do not fail the complete + scan; the UI still reports “not found” rather than claiming success. +- Future/unknown persisted progress fails schema parsing and resets to the + version-1 initial state. + +## Layer 6 — Cross-domain concept leakage + +`useSetupWalkthroughController` is an application-layer orchestrator. It reads +domain state and invokes domain-owned commands; it does not implement +credential parsing, Git remote normalization, Cloud membership, policy RPCs, +sync traversal, appearance persistence, or Chat Panel tab construction. + +The only setup-owned persisted data is readiness metadata. No domain credential +or remote session payload is copied into setup state. + +## Layer 7 — New developer confusion test + +- Step visibility is pure (`getVisibleSetupStepIds`). +- Transition guards are pure (`canCompleteSetupStep`). +- Navigation reachability is pure (`canNavigateToSetupStep`). +- Secret stripping is named and independently tested + (`sanitizeDetectedTool`). +- Foreground operations have explicit names and one active-operation gate. +- The acceptance matrix lives next to the feature in `TEST_CASES.md`. + +## Layer 8 — Wire protocol and serialization + +- The detection RPC remains secret-bearing, but `sanitizeDetectedTool` + constructs a new allow-listed summary synchronously; tests prove API key, + session token, and environment values do not survive. +- `createCloudInvite` retains the existing plaintext-on-device/hash-on-wire + contract. +- Repo scopes sent to `cloud_set_org_repo_scopes` come only from + `resolveShareableScopeKeys`. +- Sharing floor uses the existing typed `CollabSessionAccessMode`. +- Repo scopes and sharing floor are two existing server RPCs, not one + transaction. The UI reports success only after both finish; if the second + fails, the step stays incomplete and is safely retryable. No false + cross-RPC atomicity is claimed. +- No live payload capture was added; existing client schema tests remain the + wire-format gate. + +## Layer 9 — Init parity across entry points + +| Entry point | Shared path | +| ----------------------------------- | ----------------------------------------------- | +| First Release run | Setup route → readiness FSM | +| Settings “Setup checklist” | Same route and persisted FSM | +| DOM shortcut fallback | Atomic setup-only reset → same route and FSM | +| macOS/Linux native app menu | `menu-reopen-setup` → same reset and route | +| Windows custom/native menu | `menu-reopen-setup` → same reset and route | +| Existing organization selection | Same `Org2CloudOrg` roster and namespaced scope | +| Create organization from setup | Shared membership hook | +| Create organization from regular UI | Shared membership hook | +| Join via pasted link/code | Shared membership hook and parser | + +OS deep-link acceptance remains owned by the existing Cloud deep-link handler; +it converges on the same roster atom, which setup reads when reopened. + +## Layer 10 — Resolver symmetry + +All step decisions resolve in the same order: + +1. normalize persisted progress; +2. derive visible steps from goal; +3. normalize current step against the visible set; +4. enforce the current step’s postcondition; +5. persist the next progress snapshot; +6. on completion, atomically persist terminal outcome plus final progress. + +Cloud membership similarly resolves auth refresh → mutation → roster +postcondition → local selection. A superseded/expired session cannot commit a +false organization result. + +## Verification + +- `rustfmt --edition 2021 --check src-tauri/crates/system-services/src/app_menu.rs`: passed. +- `cargo check -p system_services`: passed. + +## Architecture verdict + +All 10 layers were covered. The implementation removes the disconnected +slide-deck architecture and introduces one small orchestration layer over +existing domain owners. Remaining risks are explicit: Cloud admin policy spans +two server RPCs, sync-engine drain does not provide a server receipt count, and +the live team path depends on the existing cloud/dual-instance E2E suites. diff --git a/docs/architecture-audit-2026-07-31/SetupWalkthroughCleanup.md b/docs/architecture-audit-2026-07-31/SetupWalkthroughCleanup.md new file mode 100644 index 0000000000..e2beaf3a40 --- /dev/null +++ b/docs/architecture-audit-2026-07-31/SetupWalkthroughCleanup.md @@ -0,0 +1,79 @@ +# Architecture Audit — Setup Walkthrough Cleanup + +**Scope:** readiness onboarding entry points, step registry, WizardSystem +navigation boundary, and legacy setup export chain + +**Date:** 2026-07-31 + +**Auditor:** Codex + +## Acceptance criteria + +- Production setup imports only the active readiness-flow step implementation. +- Current onboarding behavior and persisted flow state remain unchanged. +- Active/completed/locked navigation state has one shared presentation owner. +- No removed legacy symbol remains reachable through a direct or barrel import. +- TypeScript, focused behavior tests, ESLint, and systematic reference sweeps + pass. + +## Entry point and ownership trace + +`SetupWalkthrough/index.tsx` is the production setup surface. Its visible steps +come from `STEP_CONFIGS` in `flow.ts`; every configured renderer is implemented +by `steps/ReadinessSteps.tsx`. The controller remains the authoritative owner of +current step, completed step ids, navigation guards, persistence, and terminal +outcome. `WizardStepNavigation` receives a projection of that state and owns +presentation only. + +The deleted files were reachable only from the obsolete `steps/index.ts` and +`components/index.ts` re-export chains. Neither barrel was imported by the +production flow after the active `SetupOperationError` import was changed to +its concrete readiness module. + +## Deleted legacy chain + +| Symbol/file | Producing path | Verdict | +| ------------------------------------- | ------------------------------------------------------ | ---------------------------------- | +| `AnimatedTitle`, `AnimatedTitleProps` | `components/AnimatedTitle.tsx` → `components/index.ts` | Delete; no active caller | +| `CompleteStep` | `steps/CompleteStep.tsx` → `steps/index.ts` | Delete; absent from `STEP_CONFIGS` | +| `DevPassportStep` | `steps/DevPassportStep.tsx` → `steps/index.ts` | Delete; absent from `STEP_CONFIGS` | +| `GitHubStep` | `steps/GitHubStep.tsx` → `steps/index.ts` | Delete; absent from `STEP_CONFIGS` | +| `RepoStep` | `steps/RepoStep.tsx` → `steps/index.ts` | Delete; absent from `STEP_CONFIGS` | +| `ThemeSelectionStep` | `steps/ThemeSelectionStep.tsx` → `steps/index.ts` | Delete; absent from `STEP_CONFIGS` | +| `WelcomeStep` | `steps/WelcomeStep.tsx` → `steps/index.ts` | Delete; absent from `STEP_CONFIGS` | +| `AGENT_CODE_NAMES` | `constants.ts` | Delete; no active caller | + +## Layer review + +| Layer | Coverage | Result | +| -------------------------------- | -------- | ------------------------------------------------------------------------------------------- | +| 1. Compilation and imports | Covered | Direct readiness import resolves; removed barrels have no consumers. | +| 2. Structure and dead code | Covered | Obsolete step/component chains deleted; shared navigation removes duplicated structure. | +| 3. Types and naming | Covered | Removed orphan `AnimatedTitleProps`; generic navigation item/props preserve step-id typing. | +| 4. Domain ownership | Covered | Controller/flow retain setup state and guards; the primitive owns no domain state. | +| 5. State transitions | Covered | Existing `goToStep` and `canNavigateToSetupStep` paths are reused without new transitions. | +| 6. Persistence | Covered | No schema or writer changed; setup progress writes remain in the controller/settings path. | +| 7. Error/async boundaries | Covered | Busy state disables navigation; async selection remains delegated to the owner. | +| 8. Wire protocol | Skipped | No RPC, serialization, or protocol change. | +| 9. Initialization parity | Skipped | No new initialization path or default. | +| 10. Resolver/runtime integration | Skipped | No resolver, worker, or backend integration change. | + +## Systematic sweeps + +- Removed-symbol search covers all of `src` and returns no references. +- Production imports of `SetupWalkthrough/steps` and + `SetupWalkthrough/components` barrels return no references. +- Raw interactive elements under `src/modules/SetupWalkthrough` return no + matches; native interaction now lives at the shared component boundary. +- Arbitrary `text-[Npx]` classes in SetupWalkthrough and edited WizardSystem + primitives return no matches. + +## Verification and remaining risk + +Focused static-render tests cover navigation current/completed/locked/busy +states and the shared semantic description primitive. Existing flow and i18n +tests cover the unchanged state machine and localized content. TypeScript and +ESLint cover import/export integrity. + +Remaining risk is limited to visual density at uncommon viewport/font-scale +combinations; no data, persistence, protocol, or backend risk was introduced. diff --git a/docs/frontend-ui-audit-2026-07-30/SetupReadinessFlow.md b/docs/frontend-ui-audit-2026-07-30/SetupReadinessFlow.md new file mode 100644 index 0000000000..2813532522 --- /dev/null +++ b/docs/frontend-ui-audit-2026-07-30/SetupReadinessFlow.md @@ -0,0 +1,80 @@ +# Frontend UI Audit — Setup Readiness Flow + +**Files:** `src/modules/SetupWalkthrough/index.tsx`, +`src/modules/SetupWalkthrough/steps/ReadinessSteps.tsx`, +`src/modules/shared/layouts/OnboardingLayout/index.tsx`, +`src/components/ActionCard/index.tsx`, +`src/components/ActionCard/types.ts`, +`src/scaffold/WizardSystem/primitives/SelectionGrid.tsx`, +`src/scaffold/Tutorials/TutorialsModal.tsx`, +`src/scaffold/Tutorials/GeneralLayoutTour.tsx`, +`src/scaffold/Tutorials/CodeEditorTour.tsx` +**Date:** 2026-07-30 +**Auditor:** Codex + +## D1 — Raw HTML vs Design System + +| Line / element | Element | Verdict | Reason | Suggested change | +| -------------------------------------------- | ------------------------------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| `SetupWalkthrough/index.tsx` step navigation | Native ` + ) : null; + + const inlineContent = ( <>
{showCheckbox && !showRadio && ( @@ -147,22 +186,12 @@ const ActionCard: React.FC = ({ )} {showRadio && } - {iconElement ? ( -
- {iconElement} -
- ) : Icon ? ( - - ) : null} + {leadingIcon}

{title}

- {badge && ( - - {badge} - - )} + {badgeElement}
{description && (

{description}

@@ -171,28 +200,8 @@ const ActionCard: React.FC = ({ {tooltip && !isSelected && } - {hasButton && ( - - )} - - {showTrailingCheck && - (tooltip ? ( - - - - - - ) : ( - - ))} + {actionButton} + {trailingCheck} {showArrow && ( = ({ ); + const stackedContent = ( +
+
+
+ {(showCheckbox || showRadio) && ( + <> + {showCheckbox && !showRadio && ( + + )} + {showRadio && } + + )} + {leadingIcon && ( + + {leadingIcon} + + )} +
+ +
+ {badgeElement} + {tooltip && !isSelected && } + {trailingCheck} + {showArrow && ( + + )} +
+
+ +
+

{title}

+ {description && ( +

+ {description} +

+ )} +
+ + {actionButton &&
{actionButton}
} +
+ ); + + const content = layout === "stacked" ? stackedContent : inlineContent; + // A clickable card is an interactive control, not a generic div. Native // button semantics make every wizard/selection surface keyboard reachable // (Tab + Enter/Space) and expose the control to assistive technology. Cards @@ -211,7 +274,11 @@ const ActionCard: React.FC = ({ // invalid nested buttons; only that explicit action performs the callback. if (hasButton) { return ( -
+
{content}
); @@ -227,6 +294,7 @@ const ActionCard: React.FC = ({ onClick={handleCardClick} disabled={disabled} aria-pressed={hasSelector ? selected : undefined} + data-action-card-layout={layout} data-testid={dataTestId} > {content} @@ -236,4 +304,8 @@ const ActionCard: React.FC = ({ export default ActionCard; export { SELECTION_CARD_CLASSES, getSelectionCardClass } from "./config"; -export type { ActionCardProps, ActionCardVariant } from "./types"; +export type { + ActionCardLayout, + ActionCardProps, + ActionCardVariant, +} from "./types"; diff --git a/src/components/ActionCard/types.ts b/src/components/ActionCard/types.ts index 4771a2363a..01da6126f0 100644 --- a/src/components/ActionCard/types.ts +++ b/src/components/ActionCard/types.ts @@ -5,6 +5,7 @@ import type { LucideIcon } from "lucide-react"; import type { ReactNode } from "react"; export type ActionCardVariant = "default" | "primary" | "secondary" | "subtle"; +export type ActionCardLayout = "inline" | "stacked"; export interface ActionCardProps { /** @@ -28,6 +29,13 @@ export interface ActionCardProps { */ variant?: ActionCardVariant; + /** + * Content arrangement. Stacked keeps badges and selection affordances out of + * the title row for wider choice cards. + * @default 'inline' + */ + layout?: ActionCardLayout; + /** * Icon component (Lucide icon). * For custom icons (e.g. ModelIcon), use iconElement instead. diff --git a/src/components/AppLogo/index.tsx b/src/components/AppLogo/index.tsx new file mode 100644 index 0000000000..b5dd9d304a --- /dev/null +++ b/src/components/AppLogo/index.tsx @@ -0,0 +1,35 @@ +import { memo } from "react"; + +import { classNames } from "@src/util/ui/classNames"; + +import appLogoUrl from "../../../public/logo.png"; + +export interface AppLogoProps { + size?: number; + className?: string; + alt?: string; +} + +/** + * Canonical ORGII application logo. + * + * Importing the existing application asset through webpack keeps onboarding, + * packaged builds, and the desktop icon on the same visual identity. + */ +const AppLogo = memo( + ({ size = 32, className, alt = "ORGII" }) => ( + {alt} + ) +); + +AppLogo.displayName = "AppLogo"; + +export default AppLogo; diff --git a/src/components/GlobalShortcuts/index.tsx b/src/components/GlobalShortcuts/index.tsx index f1fc63ae40..727ceba878 100644 --- a/src/components/GlobalShortcuts/index.tsx +++ b/src/components/GlobalShortcuts/index.tsx @@ -1,8 +1,12 @@ import type { FC } from "react"; import { useGlobalShortcuts } from "@src/hooks/navigation/useGlobalShortcuts"; +import { useSetupWalkthroughTestShortcut } from "@src/modules/SetupWalkthrough/useSetupWalkthroughTestShortcut"; export const GlobalShortcuts: FC = () => { + // Register first so the exact hidden test chord is consumed before regular + // route-level shortcuts observe the event. + useSetupWalkthroughTestShortcut(); useGlobalShortcuts(); return null; diff --git a/src/components/InlineAlert/InlineAlert.test.ts b/src/components/InlineAlert/InlineAlert.test.ts new file mode 100644 index 0000000000..2f8931cfcf --- /dev/null +++ b/src/components/InlineAlert/InlineAlert.test.ts @@ -0,0 +1,16 @@ +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; + +import InlineAlert from "."; + +describe("InlineAlert", () => { + it("forwards an explicit live-region role", () => { + const markup = renderToStaticMarkup( + createElement(InlineAlert, { type: "danger", role: "alert" }, "Failed") + ); + + expect(markup).toContain('role="alert"'); + expect(markup).toContain("Failed"); + }); +}); diff --git a/src/components/InlineAlert/index.tsx b/src/components/InlineAlert/index.tsx index 9362e0898b..0932b6fdc3 100644 --- a/src/components/InlineAlert/index.tsx +++ b/src/components/InlineAlert/index.tsx @@ -98,6 +98,8 @@ export interface InlineAlertProps { closeIcon?: React.ReactNode; /** Accessible label for close button */ closeAriaLabel?: string; + /** Optional live-region semantic for dynamic status or error feedback. */ + role?: React.AriaRole; } const InlineAlert: React.FC = ({ @@ -113,6 +115,7 @@ const InlineAlert: React.FC = ({ onClose, closeIcon, closeAriaLabel = "Close", + role, }) => { const styles = STYLE_MAP[type]; const [expanded, setExpanded] = React.useState(presentation !== "pill"); @@ -179,6 +182,7 @@ const InlineAlert: React.FC = ({ return (
{isPill ? ( diff --git a/src/components/LanguageSelector/index.tsx b/src/components/LanguageSelector/index.tsx index 076f766266..a07ad1b578 100644 --- a/src/components/LanguageSelector/index.tsx +++ b/src/components/LanguageSelector/index.tsx @@ -60,6 +60,9 @@ export interface LanguageSelectorProps { */ className?: string; + /** Accessible name forwarded to the shared Select trigger. */ + ariaLabel?: string; + /** * Callback when language changes */ @@ -75,6 +78,7 @@ export function LanguageSelector({ variant = "default", showIcon = true, className, + ariaLabel, onLanguageChange, }: LanguageSelectorProps) { const { i18n, t } = useTranslation("settings"); @@ -116,6 +120,7 @@ export function LanguageSelector({ variant={variant} prefix={showIcon ? : undefined} className={className} + ariaLabel={ariaLabel} dropdownWidthMode="auto" /> ); diff --git a/src/components/ProgressBar/ProgressBar.test.ts b/src/components/ProgressBar/ProgressBar.test.ts new file mode 100644 index 0000000000..f3487a2ac7 --- /dev/null +++ b/src/components/ProgressBar/ProgressBar.test.ts @@ -0,0 +1,31 @@ +import React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; + +import ProgressBar from "."; + +describe("ProgressBar accessibility contract", () => { + it("exposes bounded progress semantics when it has an accessible label", () => { + const html = renderToStaticMarkup( + React.createElement(ProgressBar, { + percent: 125, + ariaLabel: "Step 4 of 8", + }) + ); + + expect(html).toContain('role="progressbar"'); + expect(html).toContain('aria-label="Step 4 of 8"'); + expect(html).toContain('aria-valuemin="0"'); + expect(html).toContain('aria-valuemax="100"'); + expect(html).toContain('aria-valuenow="100"'); + }); + + it("does not create an unnamed progressbar when no label is provided", () => { + const html = renderToStaticMarkup( + React.createElement(ProgressBar, { percent: 40 }) + ); + + expect(html).not.toContain('role="progressbar"'); + expect(html).not.toContain("aria-valuenow"); + }); +}); diff --git a/src/components/ProgressBar/index.tsx b/src/components/ProgressBar/index.tsx index cf53b2cd47..a2fcdf0121 100644 --- a/src/components/ProgressBar/index.tsx +++ b/src/components/ProgressBar/index.tsx @@ -21,6 +21,8 @@ export interface ProgressBarProps { className?: string; /** Background color class for the track (default: "bg-fill-3") */ trackColor?: string; + /** Accessible label; when provided, exposes progressbar semantics. */ + ariaLabel?: string; } export const ProgressBar: React.FC = memo( @@ -32,6 +34,7 @@ export const ProgressBar: React.FC = memo( animated = false, className = "", trackColor = "bg-fill-3", + ariaLabel, }) => { const widthClass = width === "flex" ? "flex-1" : width; const heightStyle = @@ -45,6 +48,11 @@ export const ProgressBar: React.FC = memo(
:first-child:not(:only-child) { + opacity: 0.42; + } +} diff --git a/src/components/RepositoryAssetIcon/index.tsx b/src/components/RepositoryAssetIcon/index.tsx new file mode 100644 index 0000000000..4635a7b2d3 --- /dev/null +++ b/src/components/RepositoryAssetIcon/index.tsx @@ -0,0 +1,74 @@ +import React from "react"; + +import "./index.scss"; + +type RepositorySvgAsset = React.FC> | string; + +export interface RepositoryAssetIconProps extends Omit< + React.SVGAttributes, + "children" +> { + source: RepositorySvgAsset; + size?: number | string; +} + +/** + * Adapts an existing repository SVG asset to the product's monochrome icon + * treatment while preserving multi-layer artwork through tonal opacity. + */ +const RepositoryAssetIcon: React.FC = ({ + source, + size = 16, + className = "", + ...rest +}) => { + const resolvedClassName = `repository-asset-icon ${className}`.trim(); + + // Vitest resolves static SVG imports to URLs; production webpack resolves + // the same assets to SVGR components. Keep both environments deterministic. + if (typeof source === "string") { + return ( + + ); + } + + const Source = source; + return ( + + ); +}; + +RepositoryAssetIcon.displayName = "RepositoryAssetIcon"; + +export interface BoundRepositoryAssetIconProps extends Omit< + RepositoryAssetIconProps, + "source" +> {} + +export function createRepositoryAssetIcon( + source: RepositorySvgAsset, + displayName: string +): React.ComponentType { + const BoundRepositoryAssetIcon: React.FC = ( + props + ) => ; + BoundRepositoryAssetIcon.displayName = displayName; + return BoundRepositoryAssetIcon; +} + +export default RepositoryAssetIcon; diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index 7cfefa6902..094353b5ae 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -92,6 +92,7 @@ const Select = forwardRef( radius = SELECT_DEFAULTS.radius, variant = "default", dataTestId, + ariaLabel, }, ref ) => { @@ -341,6 +342,10 @@ const Select = forwardRef( ref={triggerRef} className={wrapperClasses} data-testid={dataTestId} + role="combobox" + aria-label={ariaLabel} + aria-haspopup="listbox" + aria-expanded={currentPopupVisible} onClick={toggle} onKeyDown={handleKeyDown} onFocus={onFocus} diff --git a/src/components/Select/types.ts b/src/components/Select/types.ts index fe6b6cef32..6b435b7b99 100644 --- a/src/components/Select/types.ts +++ b/src/components/Select/types.ts @@ -75,4 +75,6 @@ export interface SelectProps { variant?: "default" | "ghost"; /** Stable selector for rendered UI tests. */ dataTestId?: string; + /** Accessible name for the keyboard-focusable select trigger. */ + ariaLabel?: string; } diff --git a/src/components/WindowChrome/WindowsTopBar.tsx b/src/components/WindowChrome/WindowsTopBar.tsx index fb9df19e28..abad30b6f2 100644 --- a/src/components/WindowChrome/WindowsTopBar.tsx +++ b/src/components/WindowChrome/WindowsTopBar.tsx @@ -8,6 +8,7 @@ import { open } from "@tauri-apps/plugin-shell"; import { Minus, Square, X } from "lucide-react"; import React, { memo, useCallback } from "react"; +import { SETUP_WALKTHROUGH_TEST_MENU_EVENT } from "@src/config/keyboard/setupWalkthroughShortcut"; import { closeWindow, maxWindow, @@ -213,6 +214,13 @@ function getMenuItems(menu: NativeMenuKey): NativeMenuItem[] { ]; case "help": return [ + { + type: "item", + text: "Restart Setup Guide", + accelerator: "Ctrl+Alt+O", + action: () => emitMenuEvent(SETUP_WALKTHROUGH_TEST_MENU_EVENT), + }, + { type: "separator" }, { type: "item", text: "Documentation", diff --git a/src/config/keyboard/setupWalkthroughShortcut.ts b/src/config/keyboard/setupWalkthroughShortcut.ts new file mode 100644 index 0000000000..82a2fc718a --- /dev/null +++ b/src/config/keyboard/setupWalkthroughShortcut.ts @@ -0,0 +1,6 @@ +export const SETUP_WALKTHROUGH_TEST_SHORTCUT = { + mac: "⌘⌥O", + other: "Ctrl+Alt+O", +} as const; + +export const SETUP_WALKTHROUGH_TEST_MENU_EVENT = "menu-reopen-setup"; diff --git a/src/config/settingsSchema/registry/general.ts b/src/config/settingsSchema/registry/general.ts index 8ef9d32086..b7c3975e51 100644 --- a/src/config/settingsSchema/registry/general.ts +++ b/src/config/settingsSchema/registry/general.ts @@ -10,6 +10,10 @@ import { FAMILIAR_LANGUAGE_TECH_STACKS, TECH_SAVVY_LEVELS, } from "@src/config/profile/userProfile"; +import { + DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + SetupWalkthroughProgressSchema, +} from "@src/config/settingsSchema/setupWalkthroughProgress"; import type { SettingDefinition } from "@src/config/settingsSchema/types"; const USER_PROFILE_PRESET_SCHEMA = z.object({ @@ -271,6 +275,13 @@ export const GENERAL_SETTINGS_REGISTRY = { "First-use setup walkthrough outcome. Open shows the walkthrough automatically; completed and dismissed keep it closed", category: "general", }, + "general.setupWalkthroughProgress": { + schema: SetupWalkthroughProgressSchema, + default: DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + description: + "Resumable, secret-free readiness state for the first-use setup walkthrough", + category: "general", + }, "general.githubStarPromptCompleted": { schema: z.boolean(), default: false, diff --git a/src/config/settingsSchema/setupWalkthroughProgress.ts b/src/config/settingsSchema/setupWalkthroughProgress.ts new file mode 100644 index 0000000000..9339b1429b --- /dev/null +++ b/src/config/settingsSchema/setupWalkthroughProgress.ts @@ -0,0 +1,70 @@ +import { z } from "zod"; + +export const SETUP_GOALS = [ + "personal", + "team_activity", + "work_management", +] as const; + +export const SETUP_SHARING_LEVELS = [ + "off", + "metadata_only", + "full_replay", +] as const; + +export const SetupToolSummarySchema = z.object({ + agentType: z.enum(["codex", "claude_code", "cursor_cli"]), + found: z.boolean(), + keyCount: z.number().int().nonnegative(), + validatedCount: z.number().int().nonnegative(), +}); + +export const SetupWalkthroughProgressSchema = z.object({ + version: z.literal(1), + goal: z.enum(SETUP_GOALS).nullable(), + currentStepId: z.string(), + completedStepIds: z.array(z.string()), + tools: z.array(SetupToolSummarySchema), + historySessionCount: z.number().int().nonnegative().nullable(), + selectedOrgId: z.string().nullable(), + selectedOrgName: z.string().nullable(), + selectedOrgRole: z.string().nullable(), + repoScopes: z.array(z.string()), + sharingFloor: z.enum(SETUP_SHARING_LEVELS), + inviteLink: z.string().nullable(), + tutorialId: z.enum(["general-layout", "code-editor"]).nullable(), + verifiedAt: z.number().nonnegative().nullable(), +}); + +export type SetupWalkthroughProgress = z.infer< + typeof SetupWalkthroughProgressSchema +>; + +export function createDefaultSetupWalkthroughProgress(): SetupWalkthroughProgress { + return { + version: 1, + goal: null, + currentStepId: "goal", + completedStepIds: [], + tools: [], + historySessionCount: null, + selectedOrgId: null, + selectedOrgName: null, + selectedOrgRole: null, + repoScopes: [], + sharingFloor: "metadata_only", + inviteLink: null, + tutorialId: null, + verifiedAt: null, + }; +} + +export const DEFAULT_SETUP_WALKTHROUGH_PROGRESS = + createDefaultSetupWalkthroughProgress(); + +export function normalizeSetupWalkthroughProgress( + value: unknown +): SetupWalkthroughProgress { + const parsed = SetupWalkthroughProgressSchema.safeParse(value); + return parsed.success ? parsed.data : createDefaultSetupWalkthroughProgress(); +} diff --git a/src/config/windowChromeTokens.ts b/src/config/windowChromeTokens.ts new file mode 100644 index 0000000000..c3bda91614 --- /dev/null +++ b/src/config/windowChromeTokens.ts @@ -0,0 +1,9 @@ +/** + * Shared desktop window-chrome dimensions. + * + * Keep native-titlebar spacing centralized so full-window surfaces do not + * independently guess where macOS traffic lights end. + */ +export const WINDOW_CHROME_TOKENS = { + titleBarHeight: 36, +} as const; diff --git a/src/features/Org2Cloud/useCloudOrgMembershipActions.ts b/src/features/Org2Cloud/useCloudOrgMembershipActions.ts new file mode 100644 index 0000000000..d6e22deb80 --- /dev/null +++ b/src/features/Org2Cloud/useCloudOrgMembershipActions.ts @@ -0,0 +1,105 @@ +import { useAtom } from "jotai"; +import { useCallback } from "react"; + +import { refreshOrg2CloudAuthForAction } from "./org2CloudAuthAction"; +import { org2CloudAuthAtom } from "./org2CloudAuthAtom"; +import { acceptCloudInvite, createCloudOrg } from "./org2CloudManagementClient"; +import { parseCloudInviteInput } from "./org2CloudOrgManagement"; +import { + type Org2CloudOrg, + useRefetchOrg2CloudOrgs, +} from "./org2CloudOrgsAtom"; +import { ensureProjectOrgForCloudOrg } from "./org2CloudProjectOrgAlias"; + +export type CloudOrgMembershipActionError = + | "signed_out" + | "session_expired" + | "session_superseded" + | "session_unavailable" + | "invalid_invite" + | "roster_not_converged"; + +export class CloudOrgMembershipActionFailure extends Error { + constructor(readonly code: CloudOrgMembershipActionError) { + super(code); + this.name = "CloudOrgMembershipActionFailure"; + } +} + +/** + * One membership command boundary shared by onboarding and the regular + * organization creator. Each command refreshes auth, commits exactly one + * server mutation, then waits until the authoritative roster proves the + * postcondition before returning success. + */ +export function useCloudOrgMembershipActions(): { + createOrganization: (name: string) => Promise; + joinOrganization: (inviteInput: string) => Promise; +} { + const [auth, setAuth] = useAtom(org2CloudAuthAtom); + const refetchOrgs = useRefetchOrg2CloudOrgs(); + + const withFreshAuth = useCallback(async () => { + if (!auth) throw new CloudOrgMembershipActionFailure("signed_out"); + const refreshed = await refreshOrg2CloudAuthForAction(auth, setAuth); + if (refreshed.status === "expired") { + throw new CloudOrgMembershipActionFailure("session_expired"); + } + if (refreshed.status === "superseded") { + throw new CloudOrgMembershipActionFailure("session_superseded"); + } + if (refreshed.status === "unavailable") { + throw new CloudOrgMembershipActionFailure("session_unavailable"); + } + return refreshed.auth; + }, [auth, setAuth]); + + const createOrganization = useCallback( + async (name: string): Promise => { + const trimmedName = name.trim(); + const fresh = await withFreshAuth(); + const { orgId } = await createCloudOrg(fresh.accessToken, trimmedName); + try { + await ensureProjectOrgForCloudOrg({ orgId, name: trimmedName }); + } catch { + // The sync engine re-ensures this best-effort local alias per start. + } + const orgs = await refetchOrgs({ + until: (items) => items.some((item) => item.orgId === orgId), + }); + const created = orgs.find((item) => item.orgId === orgId); + if (!created) { + throw new CloudOrgMembershipActionFailure("roster_not_converged"); + } + return created; + }, + [refetchOrgs, withFreshAuth] + ); + + const joinOrganization = useCallback( + async (rawInvite: string): Promise => { + const inviteCode = parseCloudInviteInput(rawInvite); + if (!inviteCode) { + throw new CloudOrgMembershipActionFailure("invalid_invite"); + } + const fresh = await withFreshAuth(); + const result = await acceptCloudInvite(fresh.accessToken, inviteCode); + const orgs = await refetchOrgs({ + until: (items) => items.some((item) => item.orgId === result.orgId), + }); + const joined = orgs.find((item) => item.orgId === result.orgId); + if (!joined) { + throw new CloudOrgMembershipActionFailure("roster_not_converged"); + } + try { + await ensureProjectOrgForCloudOrg(joined); + } catch { + // The sync engine re-ensures this best-effort local alias per start. + } + return joined; + }, + [refetchOrgs, withFreshAuth] + ); + + return { createOrganization, joinOrganization }; +} diff --git a/src/features/TeamCollaboration/components/CreateCollabOrgView/index.tsx b/src/features/TeamCollaboration/components/CreateCollabOrgView/index.tsx index ec1d22229b..e6cf62dcb0 100644 --- a/src/features/TeamCollaboration/components/CreateCollabOrgView/index.tsx +++ b/src/features/TeamCollaboration/components/CreateCollabOrgView/index.tsx @@ -1,4 +1,4 @@ -import { useAtom, useSetAtom } from "jotai"; +import { useAtomValue, useSetAtom } from "jotai"; import { Cloud, Laptop, LogIn, Plus } from "lucide-react"; import React, { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -9,18 +9,12 @@ import Button from "@src/components/Button"; import Input from "@src/components/Input"; import Message from "@src/components/Message"; import { DETAIL_PANEL_TOKENS } from "@src/config/detailPanelTokens"; -import { refreshOrg2CloudAuthForAction } from "@src/features/Org2Cloud/org2CloudAuthAction"; import { org2CloudAuthAtom } from "@src/features/Org2Cloud/org2CloudAuthAtom"; +import { cloudManagementErrorMessage } from "@src/features/Org2Cloud/org2CloudOrgManagement"; import { - acceptCloudInvite, - createCloudOrg, -} from "@src/features/Org2Cloud/org2CloudManagementClient"; -import { - cloudManagementErrorMessage, - parseCloudInviteInput, -} from "@src/features/Org2Cloud/org2CloudOrgManagement"; -import { useRefetchOrg2CloudOrgs } from "@src/features/Org2Cloud/org2CloudOrgsAtom"; -import { ensureProjectOrgForCloudOrg } from "@src/features/Org2Cloud/org2CloudProjectOrgAlias"; + CloudOrgMembershipActionFailure, + useCloudOrgMembershipActions, +} from "@src/features/Org2Cloud/useCloudOrgMembershipActions"; import { useOrg2CloudSignIn } from "@src/features/Org2Cloud/useOrg2CloudSignIn"; import { SECTION_ACTION_GAP_CLASSES, @@ -61,8 +55,9 @@ const CreateCollabOrgView: React.FC = ({ onCreated, }) => { const { t } = useTranslation(["navigation", "common"]); - const [cloudAuth, setCloudAuth] = useAtom(org2CloudAuthAtom); - const refetchCloudOrgs = useRefetchOrg2CloudOrgs(); + const cloudAuth = useAtomValue(org2CloudAuthAtom); + const { createOrganization, joinOrganization } = + useCloudOrgMembershipActions(); const [source, setSource] = useState(null); const [mode, setMode] = useState(CREATE_MODE); @@ -140,65 +135,22 @@ const CreateCollabOrgView: React.FC = ({ // management client (JWT from the cloud account), then refresh // org2CloudOrgsAtom so the sidebar selector picks the org up immediately. const handleCloudSubmit = useCallback(async () => { - const current = cloudAuth; - if (!current) return; - const refreshed = await refreshOrg2CloudAuthForAction( - current, - setCloudAuth - ); - if (refreshed.status === "expired") { - throw new Error(t("navigation:cloud.sessionExpired")); - } - if (refreshed.status === "superseded") return; - if (refreshed.status === "unavailable") { - throw new Error(t("navigation:cloud.orgPanel.loadError")); - } - const fresh = refreshed.auth; - if (mode === CREATE_MODE) { - const { orgId } = await createCloudOrg(fresh.accessToken, orgName.trim()); - // Project-org alias (cloud-parity Phase B): local project/work-item - // mutations under this org route into the collab outbox from the very - // first edit. Best-effort — the sync engine re-ensures it per start. - try { - await ensureProjectOrgForCloudOrg({ orgId, name: orgName.trim() }); - } catch { - // Non-fatal: the engine's per-org pass self-heals the alias. - } - await refetchCloudOrgs({ - until: (orgs) => orgs.some((org) => org.orgId === orgId), - }); + const created = await createOrganization(orgName); Message.success(t("navigation:cloud.orgManagement.create.createdToast")); // Land straight in the org management panel (invites, members, repo // scopes) instead of a dead-end success screen. openOrganizationTab({ - organization: { kind: "cloud", cloudOrg: { orgId } }, + organization: { + kind: "cloud", + cloudOrg: { orgId: created.orgId }, + }, title: t("navigation:collaboration.manageOrg"), }); return; } - const inviteCode = parseCloudInviteInput(inviteInput); - if (!inviteCode) { - throw new Error(t("navigation:cloud.orgManagement.errors.inviteInvalid")); - } - const result = await acceptCloudInvite(fresh.accessToken, inviteCode); - const orgs = await refetchCloudOrgs({ - until: (items) => items.some((org) => org.orgId === result.orgId), - }); - const joined = orgs.find((org) => org.orgId === result.orgId); - if (!joined) { - // Do not close the form or show a success toast unless the refreshed - // roster confirms that the invite produced an active membership. - throw new Error(t("navigation:cloud.orgPanel.loadError")); - } - // Project-org alias on join (cloud-parity Phase B); best-effort, the - // engine re-ensures it per start. - try { - await ensureProjectOrgForCloudOrg(joined); - } catch { - // Non-fatal: the engine's per-org pass self-heals the alias. - } + const joined = await joinOrganization(inviteInput); Message.success( t("navigation:cloud.orgManagement.join.joinedToast", { org: joined.name, @@ -206,14 +158,13 @@ const CreateCollabOrgView: React.FC = ({ ); onCancel(); }, [ - cloudAuth, + createOrganization, inviteInput, + joinOrganization, mode, onCancel, openOrganizationTab, orgName, - refetchCloudOrgs, - setCloudAuth, t, ]); @@ -232,13 +183,26 @@ const CreateCollabOrgView: React.FC = ({ } catch (err) { // Cloud failures carry §22 ORG2_* codes (ORG2_INVITE_EXPIRED, // ORG2_QUOTA_EXCEEDED, …) — surface the specific translated message. - setError( - source === CLOUD_SOURCE - ? cloudManagementErrorMessage(err, t) - : err instanceof Error - ? err.message - : String(err) - ); + if ( + source === CLOUD_SOURCE && + err instanceof CloudOrgMembershipActionFailure + ) { + setError( + err.code === "invalid_invite" + ? t("navigation:cloud.orgManagement.errors.inviteInvalid") + : err.code === "session_expired" + ? t("navigation:cloud.sessionExpired") + : t("navigation:cloud.orgPanel.loadError") + ); + } else { + setError( + source === CLOUD_SOURCE + ? cloudManagementErrorMessage(err, t) + : err instanceof Error + ? err.message + : String(err) + ); + } } finally { setLoading(false); } diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 26c734024a..f782949f79 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -167,6 +167,7 @@ "goToSettings": "Zu Einstellungen", "restart": "Neu starten", "switchToStation": "Zu {{station}} wechseln", + "switchMethod": "Methode wechseln", "rate": "Bewerten", "openInTab": "In Tab öffnen", "insert": "Einfügen", diff --git a/src/i18n/locales/de/navigation.json b/src/i18n/locales/de/navigation.json index 359c0eb790..6283356548 100644 --- a/src/i18n/locales/de/navigation.json +++ b/src/i18n/locales/de/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Alle einklappen", "markAllRead": "Alle als gelesen markieren" }, + "guide": { + "trigger": "Anleitung öffnen", + "title": "Einrichtung fortsetzen", + "startSession": "Sitzung starten", + "setUpTeam": "Team einrichten", + "manageWork": "Arbeit verwalten", + "openTutorials": "Tutorials öffnen", + "quickSetup": "Sprache und Darstellung", + "close": "Anleitung schließen", + "progressLabel": "{{completed}} von {{total}} Einrichtungsschritten abgeschlossen", + "localWorkspace": "Lokaler Arbeitsbereich" + }, "search": { "folders": "Workspace suchen...", "sessions": "Sitzungen suchen...", @@ -141,7 +153,8 @@ "workstation": "Arbeitsstation", "viewRam": "RAM anzeigen", "tutorials": "Tutorials", - "openSettings": "Einstellungen öffnen" + "openSettings": "Einstellungen öffnen", + "setupChecklist": "Schnelleinrichtung" }, "empty": { "noItems": "Keine Elemente", diff --git a/src/i18n/locales/de/onboarding.json b/src/i18n/locales/de/onboarding.json index 2c2574e99c..4c836f8f4b 100644 --- a/src/i18n/locales/de/onboarding.json +++ b/src/i18n/locales/de/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Einrichtung überspringen", "getStarted": "Loslegen", "goToSettings": "Zu den Einstellungen" + }, + "readiness": { + "hero": { + "title": "Richten wir Ihr ORGII ein", + "description": "Ein paar kurze Einstellungen, um Ihren Arbeitsbereich zu personalisieren." + }, + "presentation": { + "label": "Layout-Vorschau", + "native": "App-nativ", + "cinematic": "Filmische Karte", + "classic": "Klassisches Panel" + }, + "classicPanel": { + "title": "Passe ORGII an dich an", + "description": "Wähle Sprache, Darstellung, Design und Akzentfarbe. Du kannst alles jederzeit ändern." + }, + "sidebar": { + "brandTag": "Einrichtung", + "subtitle": "Wählen Sie Sprache und Erscheinungsbild. Alles Weitere bleibt in der App verfügbar.", + "ariaLabel": "Setup-Fortschritt", + "stepProgress": "Schritt {{current}} von {{total}}", + "saved": "Fortschritt automatisch gespeichert", + "help": "Abgeschlossene Schritte bleiben bearbeitbar. Zukünftige Schritte werden freigeschaltet, wenn Sie fertig sind." + }, + "goal": { + "title": "Was möchten Sie zuerst erreichen?", + "description": "ORGII passt die Einrichtung an und führt Sie zur richtigen Produktoberfläche, wenn Sie fertig sind.", + "recommended": "Empfohlen", + "hint": "Dadurch wird der Einrichtungspfad geändert; es schränkt nicht dauerhaft ein, was Sie verwenden können.", + "personal": { + "title": "Starten Sie eine Agentensitzung", + "description": "Richten Sie einen persönlichen Codierungsworkflow ein und öffnen Sie das Launchpad." + }, + "team": { + "title": "Sehen Sie sich die Team-Codex-Aktivität an", + "description": "Verbinden Sie eine Organisation, wählen Sie Repo-Sichtbarkeit und öffnen Sie den Team-Posteingang." + }, + "work": { + "title": "Arbeit verwalten", + "description": "Projekte und Arbeitselemente verstehen und dann das erste erstellen item." + } + }, + "tools": { + "title": "Erkennen Sie Ihre Codierungstools", + "description": "Überprüfen Sie Codex, Claude Code und Cursor mithilfe des vorhandenen lokalen Validierungsdienstes.", + "detectedDescription": "Lokaler Anmeldedaten- oder Abonnementstatus", + "found": "{{count}} Konto(s) gefunden · {{validated}} validiert", + "notFound": "Kein verwendbares Konto erkannt", + "notScanned": "Noch nicht gescannt", + "detect": "Erkennungstools", + "importHistory": "Codex-Verlauf importieren", + "historyImported": "{{count}} Codex-Sitzung(en) sind lokal verfügbar", + "privacy": "Geheimnisse kommen nie ins Setup Staat. Nur Anbieter, Anzahl und Validierungsstatus bleiben erhalten." + }, + "organization": { + "title": "Verbinden Sie die Teamorganisation", + "description": "Melden Sie sich einmal an, wählen Sie dann eine vorhandene Organisation aus, erstellen Sie eine oder treten Sie mit einer Einladung bei.", + "signInHint": "Für die Teamaktivität wird ein ORG2 Cloud-Konto verwendet, sodass Mitglieder dieselbe Organisationsgrenze teilen.", + "signIn": "Anmelden/Registrieren", + "existing": "Ihre Organisationen", + "role": "Rolle: {{role}}", + "mode": "Organisationsaktion", + "create": "Organisation erstellen", + "join": "Organisation beitreten", + "orgName": "Organisationsname", + "orgNamePlaceholder": "z.B. Acme Engineering", + "invite": "Link oder Code einladen", + "invitePlaceholder": "Fügen Sie den von Ihrem Administrator freigegebenen Link ein", + "selected": "{{org}} ist für die Teameinrichtung ausgewählt", + "roles": { + "owner": "Eigentümer", + "admin": "Administrator", + "member": "Mitglied", + "guest": "Gast" + } + }, + "sharing": { + "title": "Wählen Sie aus, was das Team sehen kann", + "description": "Die Repository-Remote ist die Sichtbarkeitsgrenze. Die Freigabeebene steuert, wie viele Sitzungsdetails veröffentlicht werden.", + "workspace": "Arbeitsbereich", + "workspaceDescription": "Der lokale Ordner, in dem Ihr Agent ausgeführt wird", + "noWorkspace": "Kein Arbeitsbereich geöffnet", + "openWorkspace": "Arbeitsbereich öffnen", + "repoScope": "Repository-Bereich", + "repoScopeDescription": "Normalisierte Git-Remotes vom aktuellen Arbeitsbereich auflösen", + "detectScope": "Repo erkennen Bereich", + "remote": "Gemeinsam nutzbare Fernbedienung", + "level": "Mindestfreigabestufe", + "levelDescription": "Von der Organisation auf übereinstimmende Sitzungen angewendet", + "off": "Aus", + "metadata": "Nur Metadaten", + "replay": "Vollständige Wiedergabe", + "save": "Richtlinie speichern und überprüfen", + "createInvite": "Mitgliedseinladung erstellen", + "copy": "Kopieren", + "verified": "Der Server hat diese Repository-Richtlinie und die Synchronisierungswarteschlange akzeptiert leer.", + "memberHint": "{{org}} wird von einem Administrator verwaltet. Ihr lokaler Codex-Verlauf kann gemäß der Repository-Richtlinie synchronisiert werden.", + "verifyMember": "Überprüfen Sie meinen Synchronisierungspfad", + "memberVerified": "Ihre Organisationsauswahl ist aktiv und die aktuelle Synchronisierungsanforderung wurde gelöscht." + }, + "basics": { + "title": "Grundeinstellungen", + "description": "Sie können diese später jederzeit ändern.", + "workspace": "Standard Arbeitsbereich", + "workspaceDescription": "Sitzungen werden für Dateien in einem Arbeitsbereich ausgeführt; Projekte und Arbeitselemente organisieren die Arbeit, anstatt das Dateisystem zu ersetzen.", + "openWorkspace": "Arbeitsbereich öffnen", + "changeWorkspace": "Arbeitsbereich ändern", + "settingsHint": "Tools, Arbeitsbereiche, Organisationen, Team-Sichtbarkeit und Tutorials können später in der App eingerichtet werden." + }, + "tutorial": { + "title": "Wählen Sie ein kontextbezogenes Tutorial aus", + "description": "Es beginnt nach der Einrichtung auf der realen Produktoberfläche, auf der sich die Steuerelemente befinden.", + "hint": "Sie können jedes Tutorial später über das Menü „Einstellungen“ wiedergeben." + }, + "model": { + "title": "Wie die ORGII-Funktion zusammenpasst", + "description": "Diese Objekte lösen verschiedene Probleme; Keiner von ihnen ist ein anderer Name für einen Ordner.", + "session": { + "title": "Sitzung", + "description": "Ein Konversations- und Ausführungskontext für einen Agenten. Es kann auf einen Arbeitsbereich verweisen." + }, + "workItem": { + "title": "Arbeitselement", + "description": "Eine nachverfolgbare Aufgabe mit Status, Beauftragtem, Diskussion und verknüpften Sitzungen." + }, + "project": { + "title": "Projekt", + "description": "Ein Planungscontainer für Arbeitselemente. Es kann die Arbeit zwischen Repositorys koordinieren." + }, + "workspace": { + "title": "Arbeitsbereich/Dateisystem", + "description": "Die echten lokalen Ordner und Dateien werden von den Agenten gelesen und geändert." + }, + "relationship": "Projekt → Arbeitselement → Sitzung → Arbeitsbereichsdateien" + }, + "ready": { + "title": "Das Setup ist bereit", + "description": "Überprüfen Sie die dauerhaften Prüfungen unten und öffnen Sie dann die Oberfläche, die Ihrem ursprünglichen Ziel entspricht.", + "toolsReady": "Agentenzugriff erkannt", + "toolsLater": "Agentenzugriff kann später hinzugefügt werden", + "workspaceReady": "Arbeitsbereich ausgewählt", + "workspaceLater": "Arbeitsbereich kann als nächstes geöffnet werden", + "teamPolicyReady": "Team-Sichtbarkeitsrichtlinie bestätigt", + "memberSyncReady": "Mitglieder-Synchronisierungspfad bestätigt", + "teamDestination": "Team-Posteingang öffnen", + "workDestination": "Erstes Arbeitselement erstellen", + "personalDestination": "Agentensitzung starten", + "destinationHint": "Ihr Einrichtungsfortschritt bleibt danach erhalten Neustart.", + "teamDestinationHint": "Ihre ausgewählte Organisation und der Setup-Fortschritt bleiben nach dem Neustart erhalten.", + "openDestination": "Fertig stellen und öffnen" + }, + "destinations": { + "teamInbox": "Team-Posteingang", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Melden Sie sich zuerst bei ORG2 Cloud an.", + "sessionExpired": "Ihre ORG2 Cloud-Sitzung ist abgelaufen. Melden Sie sich erneut an.", + "invalidInvite": "Dieser Einladungslink oder Code ist ungültig.", + "rosterNotConverged": "Die Organisation ist in Ihrer aktualisierten Mitgliederliste noch nicht sichtbar.", + "cloudUnavailable": "ORG2 Cloud ist nicht verfügbar. Versuchen Sie es erneut.", + "unknown": "Der Einrichtungsvorgang ist fehlgeschlagen. Versuchen Sie es erneut.", + "workspaceRequired": "Öffnen Sie einen Arbeitsbereich, bevor Sie den Teambereich konfigurieren.", + "gitRemoteRequired": "Dieser Arbeitsbereich verfügt über kein Git-Remote. Fügen Sie eine Fernbedienung hinzu und versuchen Sie es dann erneut.", + "orgAndScopeRequired": "Wählen Sie zuerst eine Organisation und einen Repository-Bereich aus.", + "orgRequired": "Wählen Sie zuerst eine Organisation aus.", + "policyChanged": "Die Teameinstellungen wurden beim Speichern geändert. Überprüfen und bestätigen Sie sie erneut.", + "inviteOrgChanged": "Die ausgewählte Organisation hat sich beim Erstellen der Einladung geändert. Versuchen Sie es erneut.", + "syncOrgChanged": "Die ausgewählte Organisation hat sich während der Überprüfung der Synchronisierung geändert. Versuchen Sie es noch einmal." + } + }, + "tutorials": { + "modalTitle": "Tutorials", + "start": "Start", + "chrome": { + "stepProgress": "Schritt {{current}} von {{total}}", + "close": "Schließen", + "previous": "Vorheriger Schritt", + "next": "Nächster Schritt", + "finish": "Tour beenden", + "keyboardHint": "Nutze ← / → oder < / >", + "desktop": "Desktop", + "myStation": "Meine Station", + "infinity": "Infinity", + "agentStation": "Agent Station" + }, + "generalLayout": { + "title": "Allgemeine Layout-Tour", + "description": "Lernen Sie die Sitzungsseitenleiste, das Chat-Panel, den Stationsmodus-Umschalter, die Workstation, das Dock und die App-Bereiche kennen.", + "duration": "1 Minute", + "steps": { + "chat-panel": { + "title": "Chat-Panel", + "body": "Hier führen Benutzer Gespräche mit Agenten, überprüfen Aktivitäten und senden Folgeanweisungen." + }, + "station-mode-pill": { + "title": "Wechseln Sie die Station Modi", + "body": "Verwenden Sie dieses Tablet, um den Sendermodus zu wechseln. Desktop bedeutet „Meine Station“, Ihr Arbeitsplatz. Infinity bedeutet Agent Station, die Agentenaktivitätsansicht." + }, + "dock": { + "title": "Agent Station-Dock", + "body": "Das Dock wechselt zwischen Apps innerhalb der Station. Die Tour deaktiviert vorübergehend das automatische Ausblenden, sodass diese Steuerelemente sichtbar bleiben." + }, + "all-tabs": { + "title": "Alle Registerkarten", + "body": "Das erste Docksymbol zeigt alle geöffneten Registerkarten zusammen, unabhängig davon, welche Workstation-App sie besitzt." + }, + "code-editor": { + "title": "Code-Editor", + "body": "Verwenden Sie den Code-Editor für Dateien, Diffs, Terminals, Quellcodeverwaltung und Codierungsänderungen, die während eines vorgenommen wurden Sitzung." + }, + "browser": { + "title": "Browser", + "body": "Verwenden Sie den Browser für Webseiten, Vorschauen, App-Tests und browserbasierte Untersuchungen neben dem Chat." + }, + "projects": { + "title": "Projekte", + "body": "Verwenden Sie Projekte, um Arbeitselemente, Pläne und Projektstatus zu verfolgen, die mit dem aktuellen Arbeitsbereich verbunden sind." + } + } + }, + "codeEditor": { + "title": "Code-Editor-Tour", + "description": "Lernen Sie Registerkarten, Repo- und Branch-Wechsel, Quellcodeverwaltung, Git-Verlauf und das Projekt-Dashboard kennen.", + "duration": "2 Minuten", + "steps": { + "tabs": { + "title": "Code-Editor-Registerkarten", + "body": "Tabs sammeln die Dateien, Unterschiede, Terminals, Quellcodeverwaltungsansichten und Dashboards, die Sie während der Arbeit öffnen im Repo." + }, + "repo-selector": { + "title": "Repos erstellen oder wechseln", + "body": "Verwenden Sie den Repo-Selektor, um das aktive Repo zu wechseln oder ein anderes Repository oder einen anderen Arbeitsbereich hinzuzufügen." + }, + "branch-selector": { + "title": "Zweige ändern", + "body": "Der Zweigselektor zeigt den aktuellen Git-Zweig an und öffnet den Zweigwechsel oder den Erstellungsfluss für dieses Repo." + }, + "editor-surface": { + "title": "Editor-Arbeitsbereich", + "body": "Dies ist die Hauptoberfläche des Code-Editors. Öffnen Sie hier Dateien, überprüfen Sie Unterschiede, führen Sie Terminals aus und überprüfen Sie generierte Änderungen." + }, + "create-tabs": { + "title": "Neue Registerkarten erstellen", + "body": "Verwenden Sie das Plus-Menü in „Alle Registerkarten“, um Terminals, Browser-Registerkarten, Dateien, Dashboards und Repo-Dienstprogramme zu erstellen." + }, + "source-control": { + "title": "Git-Änderungen", + "body": "Open Source Control, um geänderte Dateien zu überprüfen, Arbeit bereitzustellen oder aufzuheben, festzuschreiben, zu ziehen, zu pushen, abzurufen und Mit der Fernbedienung synchronisieren." + }, + "git-history": { + "title": "Git-Verlauf", + "body": "Git-Verlauf schaltet die Quellcodeverwaltung in den Commit-Verlaufsmodus, sodass Sie frühere Commits und damit verbundene Änderungen überprüfen können." + }, + "dashboard": { + "title": "Code-Editor-Dashboard", + "body": "Das Dashboard ist die Startseite des Code-Editors für Arbeitsbereiche. Verwenden Sie es, um Repos hinzuzufügen, Repo-Details zu öffnen und in die Projektarbeit einzusteigen." + } + } + } } } diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 4b8f01ec80..e639ff33b7 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -169,6 +169,7 @@ "goToSettings": "Go to Settings", "restart": "Restart", "switchToStation": "Switch to {{station}}", + "switchMethod": "Switch method", "rate": "Rate", "openInTab": "Open in Tab", "insert": "Insert", diff --git a/src/i18n/locales/en/navigation.json b/src/i18n/locales/en/navigation.json index 5acae43251..0ecda5f4ea 100644 --- a/src/i18n/locales/en/navigation.json +++ b/src/i18n/locales/en/navigation.json @@ -147,6 +147,18 @@ "collapseAll": "Collapse All", "markAllRead": "Mark All Read" }, + "guide": { + "trigger": "Open guide", + "title": "Continue setup", + "startSession": "Start a session", + "setUpTeam": "Set up a team", + "manageWork": "Manage work", + "openTutorials": "Open tutorials", + "quickSetup": "Language and appearance", + "close": "Close guide", + "progressLabel": "{{completed}} of {{total}} setup milestones complete", + "localWorkspace": "Local workspace" + }, "search": { "folders": "Search Workspace...", "sessions": "Search Sessions...", @@ -169,6 +181,7 @@ "language": "Language", "workstation": "Workstation", "viewRam": "View RAM", + "setupChecklist": "Quick setup", "tutorials": "Tutorials", "openSettings": "Open Settings" }, diff --git a/src/i18n/locales/en/onboarding.json b/src/i18n/locales/en/onboarding.json index 561b0c0554..27199586ae 100644 --- a/src/i18n/locales/en/onboarding.json +++ b/src/i18n/locales/en/onboarding.json @@ -22,6 +22,38 @@ "complete": { "title": "Complete", "description": "Ready to go" + }, + "goal": { + "title": "Goal", + "description": "Choose your first outcome" + }, + "tools": { + "title": "Tools & keys", + "description": "Detect local agent access" + }, + "organization": { + "title": "Organization", + "description": "Create, join, or select" + }, + "sharing": { + "title": "Team visibility", + "description": "Choose scope and detail" + }, + "basics": { + "title": "Basics", + "description": "Workspace and appearance" + }, + "tutorial": { + "title": "Tutorial", + "description": "Learn in context" + }, + "workModel": { + "title": "How work fits", + "description": "Sessions, work items, projects" + }, + "ready": { + "title": "Ready", + "description": "Verify and open the right view" } }, "welcome": { @@ -70,5 +102,265 @@ "skipSetup": "Skip Setup", "getStarted": "Get Started", "goToSettings": "Go to Settings" + }, + "readiness": { + "hero": { + "title": "Let's set up your ORGII", + "description": "A few quick choices to personalize your workspace." + }, + "presentation": { + "label": "Preview layout", + "native": "App native", + "cinematic": "Cinematic card", + "classic": "Classic panel" + }, + "classicPanel": { + "title": "Make ORGII feel like yours", + "description": "Choose a comfortable language, appearance, theme, and accent color. You can change them anytime." + }, + "sidebar": { + "brandTag": "Setup", + "subtitle": "Choose your language and appearance. Everything else stays available in the app.", + "ariaLabel": "Setup progress", + "stepProgress": "Step {{current}} of {{total}}", + "saved": "Progress saved automatically", + "help": "Completed steps stay editable. Future steps unlock as you finish." + }, + "goal": { + "title": "What do you want to accomplish first?", + "description": "ORGII will adapt setup and take you to the right product surface when you finish.", + "recommended": "Recommended", + "hint": "This changes the setup path; it does not permanently limit what you can use.", + "personal": { + "title": "Start an agent session", + "description": "Set up a personal coding workflow and open the Launchpad." + }, + "team": { + "title": "See team Codex activity", + "description": "Connect an organization, choose repo visibility, and open Team Inbox." + }, + "work": { + "title": "Manage work", + "description": "Understand Projects and Work Items, then create the first item." + } + }, + "tools": { + "title": "Detect your coding tools", + "description": "Check Codex, Claude Code, and Cursor using the existing local validation service.", + "detectedDescription": "Local credential or subscription status", + "found": "{{count}} account(s) found · {{validated}} validated", + "notFound": "No usable account detected", + "notScanned": "Not scanned yet", + "detect": "Detect tools", + "importHistory": "Import Codex history", + "historyImported": "{{count}} Codex session(s) are available locally", + "privacy": "Secrets never enter setup state. Only provider, count, and validation status are retained." + }, + "organization": { + "title": "Connect the team organization", + "description": "Sign in once, then select an existing organization, create one, or join with an invite.", + "signInHint": "Team activity uses an ORG2 Cloud account so members share the same organization boundary.", + "signIn": "Sign in / Register", + "existing": "Your organizations", + "role": "Role: {{role}}", + "mode": "Organization action", + "create": "Create organization", + "join": "Join organization", + "orgName": "Organization name", + "orgNamePlaceholder": "e.g. Acme Engineering", + "invite": "Invite link or code", + "invitePlaceholder": "Paste the link your admin shared", + "selected": "{{org}} is selected for team setup", + "roles": { + "owner": "Owner", + "admin": "Admin", + "member": "Member", + "guest": "Guest" + } + }, + "sharing": { + "title": "Choose what the team can see", + "description": "The repository remote is the visibility boundary. Sharing level controls how much session detail is published.", + "workspace": "Workspace", + "workspaceDescription": "The local folder where your agent runs", + "noWorkspace": "No workspace open", + "openWorkspace": "Open workspace", + "repoScope": "Repository scope", + "repoScopeDescription": "Resolve normalized Git remotes from the current workspace", + "detectScope": "Detect repo scope", + "remote": "Shareable remote", + "level": "Minimum sharing level", + "levelDescription": "Applied by the organization to matching sessions", + "off": "Off", + "metadata": "Metadata only", + "replay": "Full replay", + "save": "Save and verify policy", + "createInvite": "Create member invite", + "copy": "Copy", + "verified": "The server accepted this repository policy and the sync queue drained.", + "memberHint": "{{org}} is managed by an admin. Your local Codex history is ready to sync under their repository policy.", + "verifyMember": "Verify my sync path", + "memberVerified": "Your organization selection is active and the current sync request drained." + }, + "basics": { + "title": "Basic preferences", + "description": "You can always change these later.", + "workspace": "Default workspace", + "workspaceDescription": "Sessions run against files in a workspace; projects and work items organize work rather than replace the filesystem.", + "openWorkspace": "Open workspace", + "changeWorkspace": "Change workspace", + "settingsHint": "Tools, workspaces, organizations, team visibility, and tutorials can be configured later from the app." + }, + "tutorial": { + "title": "Pick one contextual tutorial", + "description": "It starts after setup, on the real product surface where the controls exist.", + "hint": "You can replay either tutorial later from the Settings menu." + }, + "model": { + "title": "How ORGII work fits together", + "description": "These objects solve different problems; none of them is another name for a folder.", + "session": { + "title": "Session", + "description": "A conversation and execution context for an agent. It may reference one workspace." + }, + "workItem": { + "title": "Work Item", + "description": "A trackable task with status, assignee, discussion, and linked sessions." + }, + "project": { + "title": "Project", + "description": "A planning container for Work Items. It may coordinate work across repositories." + }, + "workspace": { + "title": "Workspace / filesystem", + "description": "The real local folders and files agents read and modify." + }, + "relationship": "Project → Work Item → Session → Workspace files" + }, + "ready": { + "title": "Setup is ready", + "description": "Review the durable checks below, then open the surface that matches your original goal.", + "toolsReady": "Agent access detected", + "toolsLater": "Agent access can be added later", + "workspaceReady": "Workspace selected", + "workspaceLater": "Workspace can be opened next", + "teamPolicyReady": "Team visibility policy confirmed", + "memberSyncReady": "Member sync path confirmed", + "teamDestination": "Open Team Inbox", + "workDestination": "Create the first Work Item", + "personalDestination": "Start an agent session", + "destinationHint": "Your setup progress remains persisted after restart.", + "teamDestinationHint": "Your selected organization and setup progress remain persisted after restart.", + "openDestination": "Finish and open" + }, + "destinations": { + "teamInbox": "Team Inbox", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Sign in to ORG2 Cloud first.", + "sessionExpired": "Your ORG2 Cloud session expired. Sign in again.", + "invalidInvite": "This invite link or code is invalid.", + "rosterNotConverged": "The organization is not visible in your refreshed membership list yet.", + "cloudUnavailable": "ORG2 Cloud is unavailable. Try again.", + "unknown": "The setup operation failed. Try again.", + "workspaceRequired": "Open a workspace before configuring team scope.", + "gitRemoteRequired": "This workspace has no Git remote. Add a remote, then try again.", + "orgAndScopeRequired": "Select an organization and repository scope first.", + "orgRequired": "Select an organization first.", + "policyChanged": "Team settings changed while saving. Review and verify them again.", + "inviteOrgChanged": "The selected organization changed while creating the invite. Try again.", + "syncOrgChanged": "The selected organization changed while verifying sync. Try again." + } + }, + "tutorials": { + "modalTitle": "Tutorials", + "start": "Start", + "chrome": { + "stepProgress": "Step {{current}} of {{total}}", + "close": "Close", + "previous": "Previous step", + "next": "Next step", + "finish": "Finish tour", + "keyboardHint": "Use ← / → or < / >", + "desktop": "Desktop", + "myStation": "My Station", + "infinity": "Infinity", + "agentStation": "Agent Station" + }, + "generalLayout": { + "title": "General layout tour", + "description": "Learn the Session sidebar, Chat Panel, station mode switcher, Workstation, dock, and app areas.", + "duration": "1 min", + "steps": { + "chat-panel": { + "title": "Chat Panel", + "body": "This is where users have conversations with agents, review activity, and send follow-up instructions." + }, + "station-mode-pill": { + "title": "Switch station modes", + "body": "Use this pill to switch station modes. Desktop means My Station, your workspace. Infinity means Agent Station, the agent activity view." + }, + "dock": { + "title": "Agent Station dock", + "body": "The dock switches apps inside the station. The tour temporarily disables auto-hide so these controls stay visible." + }, + "all-tabs": { + "title": "All Tabs", + "body": "The first dock icon shows all open tabs together, regardless of which workstation app owns them." + }, + "code-editor": { + "title": "Code Editor", + "body": "Use Code Editor for files, diffs, terminals, source control, and coding changes made during a session." + }, + "browser": { + "title": "Browser", + "body": "Use Browser for web pages, previews, app testing, and browser-based investigation alongside the chat." + }, + "projects": { + "title": "Projects", + "body": "Use Projects to track work items, plans, and project state connected to the current workspace." + } + } + }, + "codeEditor": { + "title": "Code Editor tour", + "description": "Learn tabs, repo and branch switching, Source Control, Git History, and the project dashboard.", + "duration": "2 min", + "steps": { + "tabs": { + "title": "Code Editor tabs", + "body": "Tabs collect the files, diffs, terminals, source control views, and dashboards you open while working in the repo." + }, + "repo-selector": { + "title": "Create or switch repos", + "body": "Use the repo selector to switch the active repo or add another repository or workspace." + }, + "branch-selector": { + "title": "Change branches", + "body": "The branch selector shows the current Git branch and opens the branch switch or create flow for this repo." + }, + "editor-surface": { + "title": "Editor workspace", + "body": "This is the main Code Editor surface. Open files, inspect diffs, run terminals, and review generated changes here." + }, + "create-tabs": { + "title": "Create new tabs", + "body": "Use the plus menu in All Tabs to create terminals, browser tabs, files, dashboards, and repo utilities." + }, + "source-control": { + "title": "Git changes", + "body": "Open Source Control to review changed files, stage or unstage work, commit, pull, push, fetch, and sync with the remote." + }, + "git-history": { + "title": "Git history", + "body": "Git History switches Source Control into commit history mode so you can inspect previous commits and related changes." + }, + "dashboard": { + "title": "Code Editor dashboard", + "body": "The dashboard is the Code Editor home tab for workspaces. Use it to add repos, open repo details, and jump into project work." + } + } + } } } diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index d71dbd2703..11f74de259 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -167,6 +167,7 @@ "goToSettings": "Ir a Configuración", "restart": "Reiniciar", "switchToStation": "Cambiar a {{station}}", + "switchMethod": "Cambiar método", "rate": "Calificar", "openInTab": "Abrir en pestaña", "insert": "Insertar", diff --git a/src/i18n/locales/es/navigation.json b/src/i18n/locales/es/navigation.json index 75b6ce3e2c..853cd592aa 100644 --- a/src/i18n/locales/es/navigation.json +++ b/src/i18n/locales/es/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Contraer todo", "markAllRead": "Marcar todo como leído" }, + "guide": { + "trigger": "Abrir guía", + "title": "Continuar configuración", + "startSession": "Iniciar una sesión", + "setUpTeam": "Configurar un equipo", + "manageWork": "Gestionar trabajo", + "openTutorials": "Abrir tutoriales", + "quickSetup": "Idioma y apariencia", + "close": "Cerrar guía", + "progressLabel": "{{completed}} de {{total}} pasos completados", + "localWorkspace": "Espacio de trabajo local" + }, "search": { "folders": "Buscar en el espacio de trabajo...", "sessions": "Buscar sesiones...", @@ -141,7 +153,8 @@ "workstation": "Estación de trabajo", "viewRam": "Ver RAM", "tutorials": "Tutoriales", - "openSettings": "Abrir configuración" + "openSettings": "Abrir configuración", + "setupChecklist": "Configuración rápida" }, "empty": { "noItems": "Sin elementos", diff --git a/src/i18n/locales/es/onboarding.json b/src/i18n/locales/es/onboarding.json index e151ae9c88..8d93d1a57c 100644 --- a/src/i18n/locales/es/onboarding.json +++ b/src/i18n/locales/es/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Omitir configuraci?n", "getStarted": "Comenzar", "goToSettings": "Ir a Ajustes" + }, + "readiness": { + "hero": { + "title": "Configuremos tu ORGII", + "description": "Unas pocas opciones rápidas para personalizar tu espacio de trabajo." + }, + "presentation": { + "label": "Vista previa del diseño", + "native": "Nativo de la app", + "cinematic": "Tarjeta cinematográfica", + "classic": "Panel clásico" + }, + "classicPanel": { + "title": "Adapta ORGII a tu estilo", + "description": "Elige el idioma, la apariencia, el tema y el color de énfasis. Puedes cambiarlos en cualquier momento." + }, + "sidebar": { + "brandTag": "Configuración", + "subtitle": "Elige el idioma y la apariencia. Todo lo demás seguirá disponible en la aplicación.", + "ariaLabel": "Progreso de la configuración", + "stepProgress": "Paso {{current}} de {{total}}", + "saved": "Progreso guardado automáticamente", + "help": "Los pasos completados permanecen editables. Los pasos futuros se desbloquearán a medida que termines." + }, + "goal": { + "title": "¿Qué deseas lograr primero?", + "description": "ORGII adaptará la configuración y te llevará a la superficie correcta del producto cuando termines.", + "recommended": "Recomendado", + "hint": "Esto cambia la ruta de configuración; no limita permanentemente lo que puede usar.", + "personal": { + "title": "Iniciar una sesión de agente", + "description": "Configurar un flujo de trabajo de codificación personal y abrir el Launchpad." + }, + "team": { + "title": "Ver la actividad del Codex del equipo", + "description": "Conectar una organización, elegir la visibilidad del repositorio y abrir la Bandeja de entrada del equipo." + }, + "work": { + "title": "Administrar el trabajo", + "description": "Comprenda proyectos y elementos de trabajo, luego cree el primer elemento." + } + }, + "tools": { + "title": "Detecta tus herramientas de codificación", + "description": "Comprueba Codex, Claude Code y Cursor utilizando el servicio de validación local existente.", + "detectedDescription": "Estado de suscripción o credencial local", + "found": "{{count}} cuenta(s) encontrada(s) · {{validated}} validada", + "notFound": "No se detectó ninguna cuenta utilizable", + "notScanned": "Aún no escaneada", + "detect": "Herramientas de detección", + "importHistory": "Importar historial de Codex", + "historyImported": "{{count}} Las sesiones de Codex están disponibles localmente", + "privacy": "Los secretos nunca ingresan a la configuración estado. Solo se conservan el estado del proveedor, el recuento y la validación." + }, + "organization": { + "title": "Conecta la organización del equipo", + "description": "Inicia sesión una vez, luego selecciona una organización existente, crea una o únete con una invitación.", + "signInHint": "La actividad del equipo utiliza una cuenta de ORG2 Cloud para que los miembros compartan los mismos límites de la organización.", + "signIn": "Iniciar sesión/Registrarse", + "existing": "Tus organizaciones", + "role": "Rol: {{role}}", + "mode": "Acción de la organización", + "create": "Crear organización", + "join": "Unirse a la organización", + "orgName": "Nombre de la organización", + "orgNamePlaceholder": "p.ej. Acme Engineering", + "invite": "Enlace o código de invitación", + "invitePlaceholder": "Pegue el enlace que compartió su administrador", + "selected": "{{org}} está seleccionado para la configuración del equipo", + "roles": { + "owner": "Propietario", + "admin": "Administrador", + "member": "Miembro", + "guest": "Invitado" + } + }, + "sharing": { + "title": "Elija lo que el equipo puede ver", + "description": "El repositorio remoto es el límite de visibilidad. El nivel de uso compartido controla la cantidad de detalles de la sesión que se publican.", + "workspace": "Espacio de trabajo", + "workspaceDescription": "La carpeta local donde se ejecuta su agente", + "noWorkspace": "No hay espacio de trabajo abierto", + "openWorkspace": "Abrir espacio de trabajo", + "repoScope": "Alcance del repositorio", + "repoScopeDescription": "Resolver controles remotos de Git normalizados desde el espacio de trabajo actual", + "detectScope": "Detectar repositorio alcance", + "remote": "Remoto compartible", + "level": "Nivel mínimo de uso compartido", + "levelDescription": "Aplicado por la organización a sesiones coincidentes", + "off": "Desactivado", + "metadata": "Solo metadatos", + "replay": "Reproducción completa", + "save": "Guardar y verificar política", + "createInvite": "Crear invitación de miembro", + "copy": "Copiar", + "verified": "El servidor aceptó esta política de repositorio y la cola de sincronización drenado.", + "memberHint": "{{org}} es administrado por un administrador. Su historial local de Codex está listo para sincronizarse según su política de repositorio.", + "verifyMember": "Verifique mi ruta de sincronización", + "memberVerified": "La selección de su organización está activa y la solicitud de sincronización actual está agotada." + }, + "basics": { + "title": "Preferencias básicas", + "description": "Siempre puedes cambiarlas más tarde.", + "workspace": "Predeterminado espacio de trabajo", + "workspaceDescription": "Las sesiones se ejecutan en archivos en un espacio de trabajo; los proyectos y elementos de trabajo organizan el trabajo en lugar de reemplazar el sistema de archivos.", + "openWorkspace": "Abrir espacio de trabajo", + "changeWorkspace": "Cambiar espacio de trabajo", + "settingsHint": "Las herramientas, los espacios de trabajo, las organizaciones, la visibilidad del equipo y los tutoriales se pueden configurar más adelante en la aplicación." + }, + "tutorial": { + "title": "Elija un tutorial contextual", + "description": "Comienza después de la configuración, en la superficie real del producto donde existen los controles.", + "hint": "Puede reproducir cualquiera de los tutoriales más adelante desde el menú Configuración." + }, + "model": { + "title": "Cómo funciona ORGII encaja", + "description": "Estos objetos resuelven diferentes problemas; ninguno de ellos es otro nombre para una carpeta.", + "session": { + "title": "Sesión", + "description": "Un contexto de conversación y ejecución para un agente. Puede hacer referencia a un espacio de trabajo." + }, + "workItem": { + "title": "Elemento de trabajo", + "description": "Una tarea rastreable con estado, responsable, discusión y sesiones vinculadas." + }, + "project": { + "title": "Proyecto", + "description": "Un contenedor de planificación para elementos de trabajo. Puede coordinar el trabajo entre repositorios." + }, + "workspace": { + "title": "Espacio de trabajo/sistema de archivos", + "description": "Las carpetas y archivos locales reales que los agentes leen y modifican." + }, + "relationship": "Proyecto → Elemento de trabajo → Sesión → Archivos del espacio de trabajo" + }, + "ready": { + "title": "La configuración está lista", + "description": "Revise las comprobaciones duraderas a continuación y luego abra la superficie que coincida con su objetivo original.", + "toolsReady": "Acceso del agente detectado", + "toolsLater": "El acceso del agente se puede agregar más tarde", + "workspaceReady": "Espacio de trabajo seleccionado", + "workspaceLater": "El espacio de trabajo se puede abrir a continuación", + "teamPolicyReady": "Política de visibilidad del equipo confirmada", + "memberSyncReady": "Ruta de sincronización de miembros confirmada", + "teamDestination": "Abrir bandeja de entrada del equipo", + "workDestination": "Crear el primer elemento de trabajo", + "personalDestination": "Iniciar una sesión de agente", + "destinationHint": "El progreso de su configuración persiste después reiniciar.", + "teamDestinationHint": "La organización seleccionada y el progreso de configuración persisten después del reinicio.", + "openDestination": "Finalice y abra" + }, + "destinations": { + "teamInbox": "Bandeja de entrada del equipo", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Inicie sesión en ORG2 Cloud primero.", + "sessionExpired": "Su sesión de ORG2 Cloud expiró. Inicie sesión nuevamente.", + "invalidInvite": "Este enlace o código de invitación no es válido.", + "rosterNotConverged": "La organización aún no está visible en su lista de miembros actualizada.", + "cloudUnavailable": "ORG2 Cloud no está disponible. Inténtalo de nuevo.", + "unknown": "La operación de configuración falló. Inténtalo de nuevo.", + "workspaceRequired": "Abre un espacio de trabajo antes de configurar el alcance del equipo.", + "gitRemoteRequired": "Este espacio de trabajo no tiene control remoto Git. Agregue un control remoto y vuelva a intentarlo.", + "orgAndScopeRequired": "Primero seleccione una organización y el alcance del repositorio.", + "orgRequired": "Primero seleccione una organización.", + "policyChanged": "La configuración del equipo cambió al guardar. Revíselos y verifíquelos nuevamente.", + "inviteOrgChanged": "La organización seleccionada cambió al crear la invitación. Inténtalo de nuevo.", + "syncOrgChanged": "La organización seleccionada cambió mientras se verificaba la sincronización. Inténtalo de nuevo." + } + }, + "tutorials": { + "modalTitle": "Tutoriales", + "start": "Iniciar", + "chrome": { + "stepProgress": "Paso {{current}} de {{total}}", + "close": "Cerrar", + "previous": "Paso anterior", + "next": "Siguiente paso", + "finish": "Finalizar recorrido", + "keyboardHint": "Usa ← / → o < / >", + "desktop": "Escritorio", + "myStation": "Mi estación", + "infinity": "Infinity", + "agentStation": "Estación de agente" + }, + "generalLayout": { + "title": "Recorrido general por el diseño", + "description": "Conozca la barra lateral de sesión, el panel de chat, el conmutador de modo de estación, la estación de trabajo, la base y las áreas de aplicaciones.", + "duration": "1 min", + "steps": { + "chat-panel": { + "title": "Panel de chat", + "body": "Aquí es donde los usuarios conversan con los agentes, revisan la actividad y envían instrucciones de seguimiento." + }, + "station-mode-pill": { + "title": "Cambiar de estación modos", + "body": "Utilice esta pastilla para cambiar los modos de estación. Escritorio significa My Station, tu espacio de trabajo. Infinito significa Agent Station, la vista de actividad del agente." + }, + "dock": { + "title": "Base de Agent Station", + "body": "La base cambia de aplicaciones dentro de la estación. El recorrido desactiva temporalmente la ocultación automática para que estos controles permanezcan visibles." + }, + "all-tabs": { + "title": "Todas las pestañas", + "body": "El primer ícono del Dock muestra todas las pestañas abiertas juntas, independientemente de qué aplicación de estación de trabajo las posee." + }, + "code-editor": { + "title": "Editor de código", + "body": "Utilice el Editor de código para archivos, diferencias, terminales, control de fuente y cambios de codificación realizados durante un sesión." + }, + "browser": { + "title": "Navegador", + "body": "Utilice el navegador para páginas web, vistas previas, pruebas de aplicaciones e investigación basada en navegador junto con el chat." + }, + "projects": { + "title": "Proyectos", + "body": "Utilice Proyectos para realizar un seguimiento de los elementos de trabajo, los planes y el estado del proyecto conectados al espacio de trabajo actual." + } + } + }, + "codeEditor": { + "title": "Recorrido por el editor de código", + "description": "Aprenda sobre pestañas, cambio de repositorio y rama, control de código fuente, historial de Git y el panel del proyecto.", + "duration": "2 minutos", + "steps": { + "tabs": { + "title": "Pestañas del editor de código", + "body": "Las pestañas recopilan los archivos, diferencias, terminales, vistas de control de código fuente y paneles que abre mientras trabaja en el repositorio." + }, + "repo-selector": { + "title": "Crear o cambiar repositorios", + "body": "Utilice el selector de repositorio para cambiar el repositorio activo o agregar otro repositorio o espacio de trabajo." + }, + "branch-selector": { + "title": "Cambiar ramas", + "body": "El selector de ramas muestra la rama Git actual y abre el cambio de rama o crea flujo para este repositorio." + }, + "editor-surface": { + "title": "Espacio de trabajo del editor", + "body": "Esta es la superficie principal del Editor de código. Abra archivos, inspeccione diferencias, ejecute terminales y revise los cambios generados aquí." + }, + "create-tabs": { + "title": "Crear nuevas pestañas", + "body": "Utilice el menú más en Todas las pestañas para crear terminales, pestañas del navegador, archivos, paneles y utilidades de repositorio." + }, + "source-control": { + "title": "Cambios de Git", + "body": "Control de código abierto para revisar archivos modificados, preparar o cancelar el trabajo, confirmar, extraer, enviar, recuperar y sincronizar con el control remoto." + }, + "git-history": { + "title": "Historial de Git", + "body": "Historial de Git cambia el Control de fuente al modo de historial de confirmaciones para que puedas inspeccionar confirmaciones anteriores y cambios relacionados." + }, + "dashboard": { + "title": "Panel del Editor de código", + "body": "El panel es la pestaña de inicio del Editor de código para los espacios de trabajo. Úselo para agregar repositorios, abrir detalles del repositorio y comenzar a trabajar en el proyecto." + } + } + } } } diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index daee023edf..a40629fdf8 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -167,6 +167,7 @@ "goToSettings": "Aller aux Paramètres", "restart": "Redémarrer", "switchToStation": "Basculer vers {{station}}", + "switchMethod": "Changer de méthode", "rate": "Évaluer", "openInTab": "Ouvrir dans un onglet", "insert": "Insérer", diff --git a/src/i18n/locales/fr/navigation.json b/src/i18n/locales/fr/navigation.json index c220f551b0..90ea7e49b2 100644 --- a/src/i18n/locales/fr/navigation.json +++ b/src/i18n/locales/fr/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Tout réduire", "markAllRead": "Tout marquer comme lu" }, + "guide": { + "trigger": "Ouvrir le guide", + "title": "Continuer la configuration", + "startSession": "Démarrer une session", + "setUpTeam": "Configurer une équipe", + "manageWork": "Gérer le travail", + "openTutorials": "Ouvrir les tutoriels", + "quickSetup": "Langue et apparence", + "close": "Fermer le guide", + "progressLabel": "{{completed}} étapes sur {{total}} terminées", + "localWorkspace": "Espace de travail local" + }, "search": { "folders": "Rechercher dans l’espace de travail...", "sessions": "Rechercher des sessions...", @@ -141,7 +153,8 @@ "workstation": "Poste de travail", "viewRam": "Voir la RAM", "tutorials": "Tutoriels", - "openSettings": "Ouvrir les paramètres" + "openSettings": "Ouvrir les paramètres", + "setupChecklist": "Configuration rapide" }, "empty": { "noItems": "Aucun élément", diff --git a/src/i18n/locales/fr/onboarding.json b/src/i18n/locales/fr/onboarding.json index c730085a8b..26a3a1bf3f 100644 --- a/src/i18n/locales/fr/onboarding.json +++ b/src/i18n/locales/fr/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Ignorer la configuration", "getStarted": "Commencer", "goToSettings": "Aller aux Paramètres" + }, + "readiness": { + "hero": { + "title": "Configurons votre ORGII", + "description": "Quelques choix rapides pour personnaliser votre espace de travail." + }, + "presentation": { + "label": "Aperçu de la mise en page", + "native": "Natif de l’app", + "cinematic": "Carte immersive", + "classic": "Panneau classique" + }, + "classicPanel": { + "title": "Adaptez ORGII à vos préférences", + "description": "Choisissez la langue, l’apparence, le thème et la couleur d’accentuation. Vous pourrez les modifier à tout moment." + }, + "sidebar": { + "brandTag": "Configuration", + "subtitle": "Choisissez votre langue et votre apparence. Le reste demeure disponible dans l’application.", + "ariaLabel": "Progression de la configuration", + "stepProgress": "Étape {{current}} sur {{total}}", + "saved": "Progression enregistrée automatiquement", + "help": "Les étapes terminées restent modifiables. Les étapes futures se débloquent à mesure que vous terminez." + }, + "goal": { + "title": "Que souhaitez-vous accomplir en premier ?", + "description": "ORGII adaptera la configuration et vous amènera à la bonne surface du produit lorsque vous aurez terminé.", + "recommended": "Recommandé", + "hint": "Cela modifie le chemin de configuration ; cela ne limite pas de manière permanente ce que vous pouvez utiliser.", + "personal": { + "title": "Démarrez une session d'agent", + "description": "Configurez un flux de travail de codage personnel et ouvrez le Launchpad." + }, + "team": { + "title": "Voir l'activité du Codex de l'équipe", + "description": "Connectez une organisation, choisissez la visibilité du référentiel et ouvrez la boîte de réception de l'équipe." + }, + "work": { + "title": "Gérez le travail", + "description": "Comprenez les projets et les éléments de travail, puis créez le premier élément." + } + }, + "tools": { + "title": "Détectez vos outils de codage", + "description": "Vérifiez le Codex, Claude Code et Cursor à l'aide du service de validation local existant.", + "detectedDescription": "Identifiant local ou statut d'abonnement", + "found": "{{count}} compte(s) trouvé(s) · {{validated}} validé", + "notFound": "Aucun compte utilisable détecté", + "notScanned": "Pas encore analysé", + "detect": "Outils de détection", + "importHistory": "Importer l'historique du Codex", + "historyImported": "{{count}} Les sessions Codex sont disponibles localement", + "privacy": "Les secrets n'entrent jamais état de configuration. Seuls le fournisseur, le nombre et le statut de validation sont conservés." + }, + "organization": { + "title": "Connectez l'organisation de l'équipe", + "description": "Connectez-vous une fois, puis sélectionnez une organisation existante, créez-en une ou rejoignez-la avec une invitation.", + "signInHint": "L'activité de l'équipe utilise un compte ORG2 Cloud afin que les membres partagent la même limite d'organisation.", + "signIn": "Connexion/Inscription", + "existing": "Vos organisations", + "role": "Rôle : {{role}}", + "mode": "Action de l'organisation", + "create": "Créer une organisation", + "join": "Rejoindre l'organisation", + "orgName": "Nom de l'organisation", + "orgNamePlaceholder": "par exemple. Acme Engineering", + "invite": "Lien ou code d'invitation", + "invitePlaceholder": "Collez le lien partagé par votre administrateur", + "selected": "{{org}} est sélectionné pour la configuration de l'équipe", + "roles": { + "owner": "Propriétaire", + "admin": "Administrateur", + "member": "Membre", + "guest": "Invité" + } + }, + "sharing": { + "title": "Choisissez ce que l'équipe peut voir", + "description": "La télécommande du référentiel est la limite de visibilité. Le niveau de partage contrôle la quantité de détails de session publiés.", + "workspace": "Espace de travail", + "workspaceDescription": "Le dossier local dans lequel votre agent s'exécute", + "noWorkspace": "Aucun espace de travail ouvert", + "openWorkspace": "Espace de travail ouvert", + "repoScope": "Étendue du référentiel", + "repoScopeDescription": "Résoudre les télécommandes Git normalisées à partir de l'espace de travail actuel", + "detectScope": "Détecter le dépôt portée", + "remote": "Télécommande partageable", + "level": "Niveau de partage minimum", + "levelDescription": "Appliqué par l'organisation aux sessions correspondantes", + "off": "Désactivé", + "metadata": "Métadonnées uniquement", + "replay": "Relecture complète", + "save": "Enregistrer et vérifier la politique", + "createInvite": "Créer une invitation de membre", + "copy": "Copier", + "verified": "Le serveur a accepté cette politique de référentiel et la file d'attente de synchronisation drainé.", + "memberHint": "{{org}} est géré par un administrateur. Votre historique Codex local est prêt à être synchronisé conformément à leur politique de référentiel.", + "verifyMember": "Vérifiez mon chemin de synchronisation", + "memberVerified": "La sélection de votre organisation est active et la demande de synchronisation actuelle est épuisée." + }, + "basics": { + "title": "Préférences de base", + "description": "Vous pourrez toujours les modifier plus tard.", + "workspace": "Par défaut. espace de travail", + "workspaceDescription": "Les sessions s'exécutent sur des fichiers dans un espace de travail ; les projets et les éléments de travail organisent le travail plutôt que de remplacer le système de fichiers.", + "openWorkspace": "Ouvrir l'espace de travail", + "changeWorkspace": "Changer d'espace de travail", + "settingsHint": "Les outils, espaces de travail, organisations, options de visibilité d’équipe et tutoriels pourront être configurés plus tard dans l’application." + }, + "tutorial": { + "title": "Choisissez un didacticiel contextuel", + "description": "Il démarre après la configuration, sur la surface réelle du produit où les commandes existent.", + "hint": "Vous pouvez rejouer l'un ou l'autre didacticiel plus tard à partir du menu Paramètres." + }, + "model": { + "title": "Comment le fonctionnement d'ORGII s'articule", + "description": "Ces objets résolvent différents problèmes ; aucun d'entre eux n'est un autre nom pour un dossier.", + "session": { + "title": "Session", + "description": "Un contexte de conversation et d'exécution pour un agent. Il peut faire référence à un espace de travail." + }, + "workItem": { + "title": "Élément de travail", + "description": "Une tâche traçable avec statut, responsable, discussion et sessions liées." + }, + "project": { + "title": "Projet", + "description": "Un conteneur de planification pour les éléments de travail. Il peut coordonner le travail entre les référentiels." + }, + "workspace": { + "title": "Espace de travail/système de fichiers", + "description": "Les agents de dossiers et de fichiers locaux réels lisent et modifient." + }, + "relationship": "Projet → Élément de travail → Session → Fichiers de l'espace de travail" + }, + "ready": { + "title": "La configuration est prête", + "description": "Examinez les vérifications durables ci-dessous, puis ouvrez la surface qui correspond à votre objectif initial.", + "toolsReady": "Accès de l'agent. détecté", + "toolsLater": "L'accès à l'agent peut être ajouté ultérieurement", + "workspaceReady": "L'espace de travail sélectionné", + "workspaceLater": "L'espace de travail peut être ouvert ensuite", + "teamPolicyReady": "Politique de visibilité de l'équipe confirmée", + "memberSyncReady": "Chemin de synchronisation des membres confirmé", + "teamDestination": "Ouvrir la boîte de réception de l'équipe", + "workDestination": "Créer le premier élément de travail", + "personalDestination": "Démarrer une session d'agent", + "destinationHint": "La progression de votre configuration reste conservée après redémarrer.", + "teamDestinationHint": "Votre organisation sélectionnée et la progression de votre configuration restent conservées après le redémarrage.", + "openDestination": "Terminez et ouvrez" + }, + "destinations": { + "teamInbox": "Boîte de réception de l'équipe", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Connectez-vous d'abord à ORG2 Cloud.", + "sessionExpired": "Votre session ORG2 Cloud a expiré. Connectez-vous à nouveau.", + "invalidInvite": "Ce lien ou code d'invitation n'est pas valide.", + "rosterNotConverged": "L'organisation n'est pas encore visible dans votre liste de membres actualisée.", + "cloudUnavailable": "ORG2 Cloud n'est pas disponible. Réessayez.", + "unknown": "L'opération de configuration a échoué. Réessayez.", + "workspaceRequired": "Ouvrez un espace de travail avant de configurer la portée de l'équipe.", + "gitRemoteRequired": "Cet espace de travail n'a pas de télécommande Git. Ajoutez une personne distante, puis réessayez.", + "orgAndScopeRequired": "Sélectionnez d'abord une organisation et une étendue de référentiel.", + "orgRequired": "Sélectionnez d'abord une organisation.", + "policyChanged": "Les paramètres de l'équipe ont été modifiés lors de l'enregistrement. Examinez-les et vérifiez-les à nouveau.", + "inviteOrgChanged": "L'organisation sélectionnée a changé lors de la création de l'invitation. Réessayez.", + "syncOrgChanged": "L'organisation sélectionnée a changé lors de la vérification de la synchronisation. Réessayez." + } + }, + "tutorials": { + "modalTitle": "Tutoriels", + "start": "Démarrer", + "chrome": { + "stepProgress": "Étape {{current}} sur {{total}}", + "close": "Fermer", + "previous": "Étape précédente", + "next": "Étape suivante", + "finish": "Terminer la visite", + "keyboardHint": "Utilisez ← / → ou < / >", + "desktop": "Bureau", + "myStation": "Ma Station", + "infinity": "Infinity", + "agentStation": "Agent Station" + }, + "generalLayout": { + "title": "Visite générale de la présentation", + "description": "Découvrez la barre latérale de session, le panneau de discussion, le sélecteur de mode de station, le poste de travail, le dock et les zones d'application.", + "duration": "1 minute", + "steps": { + "chat-panel": { + "title": "Panneau de discussion", + "body": "C'est ici que les utilisateurs discutent avec les agents, examinent leurs activités et envoient des instructions de suivi." + }, + "station-mode-pill": { + "title": "Changer de station modes", + "body": "Utilisez cette pilule pour changer de mode de station. Bureau signifie Ma Station, votre espace de travail. Infinity signifie Agent Station, la vue de l'activité de l'agent." + }, + "dock": { + "title": "Dock Agent Station", + "body": "Le dock change d'application à l'intérieur de la station. La visite guidée désactive temporairement le masquage automatique afin que ces contrôles restent visibles." + }, + "all-tabs": { + "title": "Tous les onglets", + "body": "La première icône du Dock affiche tous les onglets ouverts ensemble, quelle que soit l'application du poste de travail qui les possède." + }, + "code-editor": { + "title": "Éditeur de code", + "body": "Utilisez l'éditeur de code pour les fichiers, les différences, les terminaux, le contrôle de source et les modifications de codage apportées au cours d'une session." + }, + "browser": { + "title": "Navigateur", + "body": "Utilisez le navigateur pour les pages Web, les aperçus, les tests d'applications et les enquêtes basées sur le navigateur parallèlement au chat." + }, + "projects": { + "title": "Projets", + "body": "Utilisez Projets pour suivre les éléments de travail, les plans et l'état du projet connectés à l'espace de travail actuel." + } + } + }, + "codeEditor": { + "title": "Visite de l'éditeur de code", + "description": "Découvrez les onglets, le changement de dépôt et de branche, le contrôle de source, l'historique Git et le tableau de bord du projet.", + "duration": "2 minutes", + "steps": { + "tabs": { + "title": "Onglets de l'éditeur de code", + "body": "Les onglets collectent les fichiers, les différences, les terminaux, les vues de contrôle de source et les tableaux de bord que vous ouvrez lorsque vous travaillez. le dépôt." + }, + "repo-selector": { + "title": "Créer ou changer de dépôt", + "body": "Utilisez le sélecteur de dépôt pour changer de dépôt actif ou ajouter un autre dépôt ou espace de travail." + }, + "branch-selector": { + "title": "Changer de branche", + "body": "Le sélecteur de branche affiche la branche Git actuelle et ouvre le commutateur de branche ou crée un flux pour ce dépôt." + }, + "editor-surface": { + "title": "Espace de travail de l'éditeur", + "body": "Il s'agit de la surface principale de l'éditeur de code. Ouvrez les fichiers, inspectez les différences, exécutez les terminaux et examinez les modifications générées ici." + }, + "create-tabs": { + "title": "Créez de nouveaux onglets", + "body": "Utilisez le menu plus dans Tous les onglets pour créer des terminaux, des onglets de navigateur, des fichiers, des tableaux de bord et des utilitaires de dépôt." + }, + "source-control": { + "title": "Modifications Git", + "body": "Contrôle Open Source pour examiner les fichiers modifiés, organiser ou annuler le travail, valider, extraire, pousser, récupérer et synchroniser. avec la télécommande." + }, + "git-history": { + "title": "Historique Git", + "body": "L'historique Git fait passer le contrôle de source en mode historique des validations afin que vous puissiez inspecter les validations précédentes et les modifications associées." + }, + "dashboard": { + "title": "Tableau de bord de l'éditeur de code", + "body": "Le tableau de bord est l'onglet d'accueil de l'éditeur de code pour les espaces de travail. Utilisez-le pour ajouter des dépôts, ouvrir les détails du dépôt et vous lancer dans le travail du projet." + } + } + } } } diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index d5598daa55..e990c54c79 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -167,6 +167,7 @@ "goToSettings": "設定へ移動", "restart": "再起動", "switchToStation": "{{station}}に切り替え", + "switchMethod": "方法を切り替える", "rate": "評価", "openInTab": "タブで開く", "insert": "挿入", diff --git a/src/i18n/locales/ja/navigation.json b/src/i18n/locales/ja/navigation.json index d5c80fd9e7..dc8565aefb 100644 --- a/src/i18n/locales/ja/navigation.json +++ b/src/i18n/locales/ja/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "すべて折りたたむ", "markAllRead": "すべて既読にする" }, + "guide": { + "trigger": "ガイドを開く", + "title": "設定を続ける", + "startSession": "セッションを開始", + "setUpTeam": "チームを設定", + "manageWork": "作業を管理", + "openTutorials": "チュートリアルを開く", + "quickSetup": "言語と外観", + "close": "ガイドを閉じる", + "progressLabel": "{{total}} 件中 {{completed}} 件の設定が完了", + "localWorkspace": "ローカルワークスペース" + }, "search": { "folders": "ワークスペースを検索...", "sessions": "セッションを検索...", @@ -139,7 +151,8 @@ "workstation": "ワークステーション", "viewRam": "RAM を表示", "tutorials": "チュートリアル", - "openSettings": "設定を開く" + "openSettings": "設定を開く", + "setupChecklist": "クイックセットアップ" }, "empty": { "noItems": "アイテムがありません", diff --git a/src/i18n/locales/ja/onboarding.json b/src/i18n/locales/ja/onboarding.json index 7f458448a7..bc6fa16259 100644 --- a/src/i18n/locales/ja/onboarding.json +++ b/src/i18n/locales/ja/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "セットアップをスキップ", "getStarted": "始める", "goToSettings": "設定へ移動" + }, + "readiness": { + "hero": { + "title": "ORGII をセットアップしましょう", + "description": "いくつかの簡単な選択でワークスペースを自分好みにできます。" + }, + "presentation": { + "label": "レイアウトプレビュー", + "native": "アプリ標準", + "cinematic": "シネマティックカード", + "classic": "クラシックパネル" + }, + "classicPanel": { + "title": "ORGII を自分好みに", + "description": "使いやすい言語、外観、テーマ、アクセントカラーを選択してください。設定はいつでも変更できます。" + }, + "sidebar": { + "brandTag": "セットアップ", + "subtitle": "言語と外観を選ぶだけで始められます。その他の設定は後からアプリ内で行えます。", + "ariaLabel": "進行状況の設定", + "stepProgress": "ステップ {{current}} / {{total}}", + "saved": "進行状況は自動的に保存されます", + "help": "完了したステップは編集可能なままです。終了すると、今後のステップのロックが解除されます。" + }, + "goal": { + "title": "最初に何を達成したいですか?", + "description": "ORGII がセットアップを調整し、完了すると適切な製品画面に移動します。", + "recommended": "推奨", + "hint": "これにより、セットアップ パスが変更されます。使用できる内容を永続的に制限するものではありません。", + "personal": { + "title": "エージェント セッションを開始します", + "description": "個人のコーディング ワークフローを設定し、Launchpad を開きます。" + }, + "team": { + "title": "チーム Codex アクティビティを表示します。", + "description": "組織を接続し、リポジトリの公開設定を選択して、チーム インボックスを開きます。" + }, + "work": { + "title": "作業を管理する", + "description": "プロジェクトと作業を理解するアイテムを選択し、最初のアイテムを作成します。" + } + }, + "tools": { + "title": "コーディング ツールを検出します。", + "description": "既存のローカル検証サービスを使用して、Codex、Claude Code、および Cursor を確認します。", + "detectedDescription": "ローカル認証情報またはサブスクリプション ステータス", + "found": "{{count}} 個のアカウントが見つかりました · {{validated}} 個が検証されました", + "notFound": "使用可能なアカウントが検出されません", + "notScanned": "まだスキャンされていません", + "detect": "検出ツール", + "importHistory": "Codex 履歴のインポート", + "historyImported": "{{count}} 個の Codex セッションが利用可能ですローカルでは", + "privacy": "シークレットがセットアップ状態になることはありません。プロバイダー、カウント、および検証ステータスのみが保持されます。" + }, + "organization": { + "title": "チーム組織に接続する", + "description": "一度サインインし、既存の組織を選択するか、組織を作成するか、招待で参加します。", + "signInHint": "チーム アクティビティは ORG2 Cloud アカウントを使用するため、メンバーは同じ組織境界を共有します。", + "signIn": "サインイン / 登録", + "existing": "組織", + "role": "役割: {{role}}", + "mode": "組織アクション", + "create": "組織の作成", + "join": "組織に参加", + "orgName": "組織名", + "orgNamePlaceholder": "例: Acme Engineering", + "invite": "招待リンクまたはコード", + "invitePlaceholder": "管理者が共有したリンクを貼り付けます", + "selected": "{{org}} がチーム設定に選択されています", + "roles": { + "owner": "オーナー", + "admin": "管理者", + "member": "メンバー", + "guest": "ゲスト" + } + }, + "sharing": { + "title": "チームが表示できるものを選択してください", + "description": "リポジトリ リモートは可視性の境界です。共有レベルは、公開されるセッションの詳細の量を制御します。", + "workspace": "ワークスペース", + "workspaceDescription": "エージェントが実行されるローカル フォルダー", + "noWorkspace": "開いているワークスペースがありません", + "openWorkspace": "開いているワークスペース", + "repoScope": "リポジトリ スコープ", + "repoScopeDescription": "現在のワークスペースから正規化された Git リモートを解決します", + "detectScope": "リポジトリを検出しますスコープ", + "remote": "共有可能なリモート", + "level": "最小共有レベル", + "levelDescription": "組織によって一致するセッションに適用されます", + "off": "オフ", + "metadata": "メタデータのみ", + "replay": "完全な再生", + "save": "ポリシーを保存して確認します", + "createInvite": "メンバーの招待を作成します", + "copy": "コピー", + "verified": "サーバーはこのリポジトリ ポリシーと同期キューを受け入れましたドレインされました。", + "memberHint": "{{org}} は管理者によって管理されています。ローカルの Codex 履歴は、リポジトリ ポリシーに基づいて同期する準備ができています。", + "verifyMember": "同期パスを確認してください。", + "memberVerified": "組織の選択がアクティブで、現在の同期リクエストが排出されています。" + }, + "basics": { + "title": "基本設定", + "description": "これらは後からいつでも変更できます。", + "workspace": "デフォルトworkspace", + "workspaceDescription": "セッションはワークスペース内のファイルに対して実行されます。プロジェクトと作業項目は、ファイル システムを置き換えるのではなく、作業を整理します。", + "openWorkspace": "ワークスペースを開く", + "changeWorkspace": "ワークスペースを変更", + "settingsHint": "ツール、ワークスペース、組織、チームの公開範囲、チュートリアルは後からアプリ内で設定できます。" + }, + "tutorial": { + "title": "状況に応じたチュートリアルを 1 つ選択してください。", + "description": "セットアップ後に、コントロールが存在する実際の製品表面で開始されます。", + "hint": "どちらのチュートリアルも後で [設定] メニューから再生できます。" + }, + "model": { + "title": "ORGII の動作の連携方法", + "description": "これらのオブジェクトは、さまざまな問題を解決します。どれもフォルダの別名ではありません。", + "session": { + "title": "セッション", + "description": "エージェントの会話および実行コンテキスト。 1 つのワークスペースを参照する場合があります。" + }, + "workItem": { + "title": "作業項目", + "description": "ステータス、担当者、ディスカッション、およびリンクされたセッションを含む追跡可能なタスク。" + }, + "project": { + "title": "プロジェクト", + "description": "作業項目の計画コンテナ。リポジトリ全体で作業を調整する場合があります。" + }, + "workspace": { + "title": "ワークスペース / ファイルシステム", + "description": "エージェントが読み取って変更する実際のローカル フォルダーとファイル。" + }, + "relationship": "プロジェクト → 作業項目 → セッション → ワークスペース ファイル" + }, + "ready": { + "title": "セットアップの準備が完了しました。", + "description": "以下の耐久性チェックを確認し、元のファイルと一致するサーフェスを開きます。目標。", + "toolsReady": "エージェントのアクセスが検出されました", + "toolsLater": "エージェント アクセスは後で追加できます", + "workspaceReady": "選択されたワークスペース", + "workspaceLater": "次にワークスペースを開くことができます", + "teamPolicyReady": "チーム可視性ポリシーを確認しました", + "memberSyncReady": "メンバーの同期パスを確認しました", + "teamDestination": "チーム インボックスを開きます", + "workDestination": "最初の作業項目を作成します", + "personalDestination": "エージェント セッションを開始します", + "destinationHint": "セットアップの進行状況は残ります", + "teamDestinationHint": "選択した組織とセットアップの進行状況は再起動後も保持されます。", + "openDestination": "終了して開きます" + }, + "destinations": { + "teamInbox": "チーム受信箱", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "まず ORG2 Cloud にサインインします。", + "sessionExpired": "ORG2 Cloud セッションの有効期限が切れました。もう一度サインインしてください。", + "invalidInvite": "この招待リンクまたはコードは無効です。", + "rosterNotConverged": "組織は更新されたメンバーシップ リストにまだ表示されていません。", + "cloudUnavailable": "ORG2 Cloud は使用できません。もう一度お試しください。", + "unknown": "セットアップ操作が失敗しました。もう一度お試しください。", + "workspaceRequired": "チーム スコープを設定する前にワークスペースを開いてください。", + "gitRemoteRequired": "このワークスペースには Git リモートがありません。リモートを追加して、再試行してください。", + "orgAndScopeRequired": "最初に組織とリポジトリ スコープを選択してください。", + "orgRequired": "最初に組織を選択してください。", + "policyChanged": "保存中にチーム設定が変更されました。もう一度見直して確認してください。", + "inviteOrgChanged": "選択した組織は、招待の作成中に変更されました。もう一度お試しください。", + "syncOrgChanged": "同期の確認中に、選択した組織が変更されました。もう一度お試しください。" + } + }, + "tutorials": { + "modalTitle": "チュートリアル", + "start": "開始", + "chrome": { + "stepProgress": "ステップ {{current}} / {{total}}", + "close": "閉じる", + "previous": "前のステップ", + "next": "次のステップ", + "finish": "ツアーを終了", + "keyboardHint": "← / → または < / > を使用", + "desktop": "デスクトップ", + "myStation": "マイステーション", + "infinity": "Infinity", + "agentStation": "エージェント ステーション" + }, + "generalLayout": { + "title": "一般的なレイアウト ツアー", + "description": "セッション サイドバー、チャット パネル、ステーション モード スイッチャー、ワークステーション、ドック、アプリ領域について学びます。", + "duration": "1 分", + "steps": { + "chat-panel": { + "title": "チャット パネル", + "body": "ここで、ユーザーはエージェントと会話し、アクティビティを確認し、フォローアップを送信します。" + }, + "station-mode-pill": { + "title": "ステーション モードを切り替える", + "body": "この錠剤を使用してステーション モードを切り替えます。デスクトップとは、My Station、つまりワークスペースを意味します。 Infinity は、エージェント ステーション、エージェント アクティビティ ビューを意味します。" + }, + "dock": { + "title": "エージェント ステーション ドック", + "body": "ドックは、ステーション内のアプリを切り替えます。このツアーでは、自動非表示を一時的に無効にして、これらのコントロールが表示されたままにします。" + }, + "all-tabs": { + "title": "すべてのタブ", + "body": "最初のドック アイコンには、どのワークステーション アプリがタブを所有しているかに関係なく、開いているすべてのタブがまとめて表示されます。" + }, + "code-editor": { + "title": "コード エディタ", + "body": "ファイル、差分、ターミナル、ソース管理、および実行中に行われたコーディングの変更には、コード エディタを使用します。セッション。" + }, + "browser": { + "title": "ブラウザ", + "body": "ブラウザは、チャットと並行して Web ページ、プレビュー、アプリのテスト、ブラウザベースの調査に使用します。" + }, + "projects": { + "title": "プロジェクト", + "body": "プロジェクトを使用して、現在のワークスペースに接続されている作業項目、計画、およびプロジェクトの状態を追跡します。" + } + } + }, + "codeEditor": { + "title": "コード エディターのツアー", + "description": "タブ、リポジトリとブランチの切り替え、ソース管理、Git 履歴、プロジェクト ダッシュボードについて学びます。", + "duration": "2 分", + "steps": { + "tabs": { + "title": "コード エディターのタブ", + "body": "タブには、ファイル、差分、ターミナル、ソース管理ビュー、およびダッシュボードが収集されます。リポジトリ内での作業中に開くことができます。" + }, + "repo-selector": { + "title": "リポジトリの作成または切り替え", + "body": "リポジトリ セレクタを使用して、アクティブなリポジトリを切り替えるか、別のリポジトリまたはワークスペースを追加します。" + }, + "branch-selector": { + "title": "ブランチの変更", + "body": "ブランチ セレクタは、現在の Git ブランチを表示し、ブランチ スイッチを開くか、このリポジトリのフローを作成します。" + }, + "editor-surface": { + "title": "エディタ ワークスペース", + "body": "これメインのコード エディター サーフェイスです。ファイルを開いたり、差分を検査したり、ターミナルを実行したり、生成された変更をここで確認したりできます。" + }, + "create-tabs": { + "title": "新しいタブを作成する", + "body": "すべてのタブのプラス メニューを使用して、ターミナル、ブラウザ タブ、ファイル、ダッシュボード、リポジトリ ユーティリティを作成します。" + }, + "source-control": { + "title": "Git の変更", + "body": "オープン ソース コントロールを使用して、変更されたファイルを確認したり、作業をステージングまたはステージング解除したり、コミット、プル、プッシュ、" + }, + "git-history": { + "title": "Git 履歴", + "body": "Git 履歴はソース管理をコミット履歴モードに切り替えて、以前のコミットや関連する変更を検査できるようにします。" + }, + "dashboard": { + "title": "コード エディター ダッシュボード", + "body": "ダッシュボードは、ワークスペースのコード エディターのホーム タブです。これを使用して、リポジトリを追加し、リポジトリの詳細を開いて、プロジェクトの作業に参加します。" + } + } + } } } diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index a4698e2a65..5cbea86bac 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -167,6 +167,7 @@ "goToSettings": "설정으로 이동", "restart": "재시작", "switchToStation": "{{station}}로 전환", + "switchMethod": "방법 전환", "rate": "평가", "openInTab": "탭에서 열기", "insert": "삽입", diff --git a/src/i18n/locales/ko/navigation.json b/src/i18n/locales/ko/navigation.json index 6f681446ad..4c8b99bb52 100644 --- a/src/i18n/locales/ko/navigation.json +++ b/src/i18n/locales/ko/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "모두 접기", "markAllRead": "모두 읽음으로 표시" }, + "guide": { + "trigger": "가이드 열기", + "title": "설정 계속하기", + "startSession": "세션 시작", + "setUpTeam": "팀 설정", + "manageWork": "작업 관리", + "openTutorials": "튜토리얼 열기", + "quickSetup": "언어 및 모양", + "close": "가이드 닫기", + "progressLabel": "설정 {{total}}개 중 {{completed}}개 완료", + "localWorkspace": "로컬 작업 공간" + }, "search": { "folders": "워크스페이스 검색...", "sessions": "세션 검색...", @@ -139,7 +151,8 @@ "workstation": "Workstation", "viewRam": "RAM 보기", "tutorials": "튜토리얼", - "openSettings": "설정 열기" + "openSettings": "설정 열기", + "setupChecklist": "빠른 설정" }, "empty": { "noItems": "항목 없음", diff --git a/src/i18n/locales/ko/onboarding.json b/src/i18n/locales/ko/onboarding.json index f6170f0e68..06d71be9b0 100644 --- a/src/i18n/locales/ko/onboarding.json +++ b/src/i18n/locales/ko/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "설정 건너뛰기", "getStarted": "시작하기", "goToSettings": "설정으로 이동" + }, + "readiness": { + "hero": { + "title": "ORGII 설정을 시작해 볼까요", + "description": "몇 가지 빠른 선택으로 작업 공간을 나에게 맞게 설정하세요." + }, + "presentation": { + "label": "레이아웃 미리보기", + "native": "앱 기본", + "cinematic": "시네마틱 카드", + "classic": "클래식 패널" + }, + "classicPanel": { + "title": "ORGII를 나에게 맞게 설정", + "description": "편한 언어, 외관, 테마와 강조 색상을 선택하세요. 언제든지 변경할 수 있습니다." + }, + "sidebar": { + "brandTag": "설정", + "subtitle": "언어와 화면 모양만 선택하면 시작할 수 있습니다. 나머지는 앱에서 나중에 설정할 수 있습니다.", + "ariaLabel": "설정 진행 상황", + "stepProgress": "{{current}}/{{total}} 단계", + "saved": "진행 상황은 자동으로 저장됩니다.", + "help": "완료된 단계는 편집 가능한 상태로 유지됩니다. 완료하면 향후 단계가 잠금 해제됩니다." + }, + "goal": { + "title": "먼저 무엇을 달성하고 싶습니까?", + "description": "완료되면 ORGII가 설정을 조정하고 올바른 제품 화면으로 안내합니다.", + "recommended": "권장", + "hint": "이렇게 하면 설정 경로가 변경됩니다. 사용할 수 있는 항목을 영구적으로 제한하지 않습니다.", + "personal": { + "title": "에이전트 세션 시작", + "description": "개인 코딩 워크플로를 설정하고 Launchpad를 엽니다." + }, + "team": { + "title": "팀 Codex 활동 보기", + "description": "조직을 연결하고 저장소 가시성을 선택하고 팀 받은 편지함을 엽니다." + }, + "work": { + "title": "작업 관리", + "description": "프로젝트 및 작업 이해 항목을 선택한 다음 첫 번째 항목을 만듭니다." + } + }, + "tools": { + "title": "코딩 도구를 감지합니다.", + "description": "기존 로컬 유효성 검사 서비스를 사용하여 Codex, Claude Code 및 Cursor를 확인합니다.", + "detectedDescription": "로컬 자격 증명 또는 구독 상태", + "found": "{{count}}개 계정 발견 · {{validated}}개 검증됨", + "notFound": "사용 가능한 계정 감지 없음", + "notScanned": "아직 스캔되지 않음", + "detect": "도구 감지", + "importHistory": "Codex 기록 가져오기", + "historyImported": "{{count}} Codex 세션 사용 가능 로컬", + "privacy": "비밀번호는 절대 설정 상태로 들어가지 않습니다. 공급자, 개수 및 유효성 검사 상태만 유지됩니다." + }, + "organization": { + "title": "팀 조직 연결", + "description": "한 번 로그인한 후 기존 조직을 선택하거나 조직을 생성하거나 초대로 참여하세요.", + "signInHint": "팀 활동은 ORG2 Cloud 계정을 사용하므로 구성원은 동일한 조직 경계를 공유합니다.", + "signIn": "로그인/등록", + "existing": "귀하의 조직", + "role": "역할: {{role}}", + "mode": "조직 작업", + "create": "조직 생성", + "join": "조직 가입", + "orgName": "조직 이름", + "orgNamePlaceholder": "예: Acme Engineering", + "invite": "링크 또는 코드 초대", + "invitePlaceholder": "관리자가 공유한 링크 붙여넣기", + "selected": "{{org}}가 팀 설정을 위해 선택되었습니다.", + "roles": { + "owner": "소유자", + "admin": "관리자", + "member": "구성원", + "guest": "게스트" + } + }, + "sharing": { + "title": "팀이 볼 수 있는 항목 선택", + "description": "원격 저장소 가시성 경계입니다. 공유 수준은 게시되는 세션 세부 정보의 양을 제어합니다.", + "workspace": "작업 공간", + "workspaceDescription": "에이전트가 실행되는 로컬 폴더", + "noWorkspace": "열린 작업 공간 없음", + "openWorkspace": "작업 공간 열기", + "repoScope": "저장소 범위", + "repoScopeDescription": "현재 작업 공간에서 정규화된 Git 원격 해결", + "detectScope": "저장소 감지 범위", + "remote": "공유 가능한 원격", + "level": "최소 공유 수준", + "levelDescription": "일치하는 세션에 조직에서 적용", + "off": "끄기", + "metadata": "메타데이터만", + "replay": "전체 재생", + "save": "정책 저장 및 확인", + "createInvite": "구성원 초대 만들기", + "copy": "복사", + "verified": "서버가 이 저장소 정책과 동기화 대기열을 수락했습니다. 배수되었습니다.", + "memberHint": "{{org}}는 관리자가 관리합니다. 로컬 Codex 기록은 저장소 정책에 따라 동기화할 준비가 되었습니다.", + "verifyMember": "동기화 경로를 확인하세요", + "memberVerified": "조직 선택이 활성화되어 있고 현재 동기화 요청이 드레이닝되었습니다." + }, + "basics": { + "title": "기본 설정", + "description": "나중에 언제든 변경할 수 있습니다.", + "workspace": "기본값 작업 공간", + "workspaceDescription": "세션은 작업 공간의 파일에 대해 실행됩니다. 프로젝트 및 작업 항목은 파일 시스템을 교체하는 대신 작업을 구성합니다.", + "openWorkspace": "작업 공간 열기", + "changeWorkspace": "작업 공간 변경", + "settingsHint": "도구, 작업 공간, 조직, 팀 공개 범위, 튜토리얼은 나중에 앱에서 필요할 때 설정할 수 있습니다." + }, + "tutorial": { + "title": "상황에 맞는 튜토리얼 하나 선택", + "description": "설정 후 컨트롤이 있는 실제 제품 표면에서 시작됩니다.", + "hint": "나중에 설정 메뉴에서 두 튜토리얼 중 하나를 다시 재생할 수 있습니다." + }, + "model": { + "title": "ORGII 작동 방식", + "description": "이러한 개체는 다양한 문제를 해결합니다. 그 중 어느 것도 폴더의 다른 이름이 아닙니다.", + "session": { + "title": "세션", + "description": "에이전트의 대화 및 실행 컨텍스트입니다. 하나의 작업공간을 참조할 수 있습니다." + }, + "workItem": { + "title": "작업 항목", + "description": "상태, 담당자, 토론 및 연결된 세션이 포함된 추적 가능한 작업." + }, + "project": { + "title": "프로젝트", + "description": "작업 항목에 대한 계획 컨테이너입니다. 저장소 전반에 걸쳐 작업을 조정할 수 있습니다." + }, + "workspace": { + "title": "작업 공간/파일 시스템", + "description": "실제 로컬 폴더 및 파일 에이전트가 읽고 수정합니다." + }, + "relationship": "프로젝트 → 작업 항목 → 세션 → 작업 공간 파일" + }, + "ready": { + "title": "설정이 준비되었습니다.", + "description": "아래 내구성 검사를 검토한 다음 원본과 일치하는 표면을 엽니다. 목표입니다.", + "toolsReady": "에이전트 액세스가 감지되었습니다", + "toolsLater": "에이전트 액세스는 나중에 추가할 수 있습니다.", + "workspaceReady": "작업 영역이 선택됨", + "workspaceLater": "작업 영역은 다음에 열 수 있습니다.", + "teamPolicyReady": "팀 가시성 정책 확인됨", + "memberSyncReady": "멤버 동기화 경로 확인됨", + "teamDestination": "팀 받은 편지함 열기", + "workDestination": "첫 번째 작업 항목 만들기", + "personalDestination": "에이전트 세션 시작", + "destinationHint": "설정 진행 상황은 다음 이후에도 유지됩니다. 다시 시작하세요.", + "teamDestinationHint": "다시 시작한 후에도 선택한 조직 및 설정 진행 상황이 유지됩니다.", + "openDestination": "완료하고 열기" + }, + "destinations": { + "teamInbox": "팀 받은 편지함", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "먼저 ORG2 Cloud에 로그인하세요.", + "sessionExpired": "ORG2 Cloud 세션이 만료되었습니다. 다시 로그인하세요.", + "invalidInvite": "이 초대 링크 또는 코드가 유효하지 않습니다.", + "rosterNotConverged": "새로 고친 멤버십 목록에 해당 조직이 아직 표시되지 않습니다.", + "cloudUnavailable": "ORG2 Cloud를 사용할 수 없습니다. 다시 시도해 보세요.", + "unknown": "설정 작업이 실패했습니다. 다시 시도하세요.", + "workspaceRequired": "팀 범위를 구성하기 전에 작업공간을 여세요.", + "gitRemoteRequired": "이 작업공간에는 Git 원격이 없습니다. 리모컨을 추가한 후 다시 시도하세요.", + "orgAndScopeRequired": "먼저 조직과 저장소 범위를 선택하세요.", + "orgRequired": "먼저 조직을 선택하세요.", + "policyChanged": "저장하는 동안 팀 설정이 변경되었습니다. 다시 검토하고 확인하세요.", + "inviteOrgChanged": "초대를 만드는 동안 선택한 조직이 변경되었습니다. 다시 시도하세요.", + "syncOrgChanged": "동기화를 확인하는 동안 선택한 조직이 변경되었습니다. 다시 시도하세요." + } + }, + "tutorials": { + "modalTitle": "튜토리얼", + "start": "시작", + "chrome": { + "stepProgress": "단계 {{current}}/{{total}}", + "close": "닫기", + "previous": "이전 단계", + "next": "다음 단계", + "finish": "둘러보기 완료", + "keyboardHint": "← / → 또는 < / > 사용", + "desktop": "데스크톱", + "myStation": "내 스테이션", + "infinity": "Infinity", + "agentStation": "에이전트 스테이션" + }, + "generalLayout": { + "title": "일반 레이아웃 둘러보기", + "description": "세션 사이드바, 채팅 패널, 스테이션 모드 전환기, 워크스테이션, 도크 및 앱 영역에 대해 알아보세요.", + "duration": "1분", + "steps": { + "chat-panel": { + "title": "채팅 패널", + "body": "여기서 사용자는 에이전트와 대화하고, 활동을 검토하고, 후속 조치를 보냅니다. 지침." + }, + "station-mode-pill": { + "title": "스테이션 모드 전환", + "body": "스테이션 모드를 전환하려면 이 알약을 사용하세요. 데스크탑은 작업 공간인 My Station을 의미합니다. Infinity는 에이전트 활동 보기인 Agent Station을 의미합니다." + }, + "dock": { + "title": "Agent Station 도킹", + "body": "도크는 스테이션 내부의 앱을 전환합니다. 둘러보기는 자동 숨기기를 일시적으로 비활성화하여 이러한 컨트롤이 계속 표시되도록 합니다." + }, + "all-tabs": { + "title": "모든 탭", + "body": "첫 번째 도킹 아이콘은 어떤 워크스테이션 앱이 소유하는지에 관계없이 열려 있는 모든 탭을 함께 표시합니다." + }, + "code-editor": { + "title": "코드 편집기", + "body": "파일, diff, 터미널, 소스 제어 및 코딩 변경 사항에 대해 코드 편집기를 사용합니다. 세션." + }, + "browser": { + "title": "브라우저", + "body": "채팅과 함께 웹페이지, 미리보기, 앱 테스트 및 브라우저 기반 조사를 위해 브라우저를 사용합니다." + }, + "projects": { + "title": "프로젝트", + "body": "프로젝트를 사용하여 현재 작업공간에 연결된 작업 항목, 계획 및 프로젝트 상태를 추적합니다." + } + } + }, + "codeEditor": { + "title": "코드 편집기 둘러보기", + "description": "탭, 저장소 및 분기 전환, 소스 제어, Git 기록 및 프로젝트 대시보드에 대해 알아보세요.", + "duration": "2분", + "steps": { + "tabs": { + "title": "코드 편집기 탭", + "body": "탭은 작업하는 동안 연 파일, 차이점, 터미널, 소스 제어 보기 및 대시보드를 수집합니다." + }, + "repo-selector": { + "title": "저장소 생성 또는 전환", + "body": "활성 저장소를 전환하거나 다른 저장소나 작업공간을 추가하려면 저장소 선택기를 사용하세요." + }, + "branch-selector": { + "title": "분기 변경", + "body": "분기 선택기는 현재 Git 분기를 표시하고 분기 전환을 열거나 이 저장소에 대한 흐름을 생성합니다." + }, + "editor-surface": { + "title": "편집기 작업공간", + "body": "이것은 기본 코드 편집기 화면입니다. 여기에서 파일을 열고, diff를 검사하고, 터미널을 실행하고, 생성된 변경 사항을 검토하세요." + }, + "create-tabs": { + "title": "새 탭 만들기", + "body": "모든 탭의 더하기 메뉴를 사용하여 터미널, 브라우저 탭, 파일, 대시보드 및 저장소 유틸리티를 만드세요." + }, + "source-control": { + "title": "Git 변경 사항", + "body": "소스 제어를 열어 변경된 파일을 검토하고, 작업 준비 또는 취소, 커밋, 풀, 푸시, 가져오기, 그리고 리모컨과 동기화하세요." + }, + "git-history": { + "title": "Git 기록", + "body": "Git 기록은 소스 제어를 커밋 기록 모드로 전환하므로 이전 커밋 및 관련 변경 사항을 검사할 수 있습니다." + }, + "dashboard": { + "title": "코드 편집기 대시보드", + "body": "대시보드는 작업 영역에 대한 코드 편집기 홈 탭입니다. 이를 사용하여 저장소를 추가하고, 저장소 세부정보를 열고, 프로젝트 작업에 뛰어들 수 있습니다." + } + } + } } } diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index cb589d4dd4..f006278da6 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -168,6 +168,7 @@ "goToSettings": "Przejdź do ustawień", "restart": "Uruchom ponownie", "switchToStation": "Przełącz na {{station}}", + "switchMethod": "Zmień metodę", "rate": "Oceń", "openInTab": "Otwórz w karcie", "insert": "Wstaw", diff --git a/src/i18n/locales/pl/navigation.json b/src/i18n/locales/pl/navigation.json index 949661053b..ca616fc6f1 100644 --- a/src/i18n/locales/pl/navigation.json +++ b/src/i18n/locales/pl/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Zwiń wszystko", "markAllRead": "Oznacz wszystkie jako przeczytane" }, + "guide": { + "trigger": "Otwórz przewodnik", + "title": "Kontynuuj konfigurację", + "startSession": "Rozpocznij sesję", + "setUpTeam": "Skonfiguruj zespół", + "manageWork": "Zarządzaj pracą", + "openTutorials": "Otwórz samouczki", + "quickSetup": "Język i wygląd", + "close": "Zamknij przewodnik", + "progressLabel": "Ukończono {{completed}} z {{total}} kroków", + "localWorkspace": "Lokalny obszar roboczy" + }, "search": { "folders": "Szukaj w Workspace...", "sessions": "Szukaj sesji...", @@ -139,7 +151,8 @@ "workstation": "Stacja robocza", "viewRam": "Pokaż RAM", "tutorials": "Samouczki", - "openSettings": "Otwórz ustawienia" + "openSettings": "Otwórz ustawienia", + "setupChecklist": "Szybka konfiguracja" }, "empty": { "noItems": "Brak elementów", diff --git a/src/i18n/locales/pl/onboarding.json b/src/i18n/locales/pl/onboarding.json index 1b06268c32..795412bdc7 100644 --- a/src/i18n/locales/pl/onboarding.json +++ b/src/i18n/locales/pl/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Pomiń konfigurację", "getStarted": "Rozpocznij", "goToSettings": "Przejdź do Ustawień" + }, + "readiness": { + "hero": { + "title": "Skonfigurujmy Twoje ORGII", + "description": "Kilka szybkich wyborów, aby spersonalizować środowisko pracy." + }, + "presentation": { + "label": "Podgląd układu", + "native": "Natywny aplikacji", + "cinematic": "Karta filmowa", + "classic": "Panel klasyczny" + }, + "classicPanel": { + "title": "Dostosuj ORGII do siebie", + "description": "Wybierz język, wygląd, motyw i kolor akcentu. Możesz je zmienić w dowolnym momencie." + }, + "sidebar": { + "brandTag": "Konfiguracja", + "subtitle": "Wybierz język i wygląd. Wszystkie pozostałe ustawienia pozostaną dostępne w aplikacji.", + "ariaLabel": "Postęp konfiguracji", + "stepProgress": "Krok {{current}} z {{total}}", + "saved": "Postęp zapisywany jest automatycznie", + "help": "Ukończone kroki można edytować. Dalsze kroki zostaną odblokowane po zakończeniu." + }, + "goal": { + "title": "Co chcesz osiągnąć w pierwszej kolejności?", + "description": "ORGII dostosuje konfigurację i po zakończeniu przeniesie Cię do odpowiedniego produktu.", + "recommended": "Zalecane", + "hint": "To zmienia ścieżkę konfiguracji; nie ogranicza to trwale tego, czego możesz użyć.", + "personal": { + "title": "Rozpocznij sesję agenta", + "description": "Skonfiguruj osobisty przepływ pracy z kodowaniem i otwórz Launchpad." + }, + "team": { + "title": "Zobacz aktywność zespołu w Kodeksie", + "description": "Połącz organizację, wybierz widoczność repozytorium i otwórz skrzynkę odbiorczą zespołu." + }, + "work": { + "title": "Zarządzaj pracą", + "description": "Poznaj projekty i elementy pracy, następnie utwórz pierwszy element." + } + }, + "tools": { + "title": "Wykryj narzędzia do kodowania", + "description": "Sprawdź Kodeks, Kod Claude'a i Kursor, korzystając z istniejącej lokalnej usługi sprawdzania poprawności.", + "detectedDescription": "Lokalne dane uwierzytelniające lub stan subskrypcji", + "found": "Znaleziono {{count}} konta(a) · {{validated}} zweryfikowane", + "notFound": "Nie wykryto nadającego się do użytku konta", + "notScanned": "Jeszcze nie przeskanowane", + "detect": "Wykryj narzędzia", + "importHistory": "Importuj historię kodeksu", + "historyImported": "{{count}} Sesje kodeksu są dostępne lokalnie", + "privacy": "Sekrety nigdy nie wchodzą w stan konfiguracji. Zachowywane są tylko dostawca, liczba i stan weryfikacji." + }, + "organization": { + "title": "Połącz organizację zespołu", + "description": "Zaloguj się raz, a następnie wybierz istniejącą organizację, utwórz ją lub dołącz za zaproszeniem.", + "signInHint": "Aktywność zespołu korzysta z konta ORG2 Cloud, więc członkowie dzielą te same granice organizacji.", + "signIn": "Zaloguj się / zarejestruj", + "existing": "Twoje organizacje", + "role": "Rola: {{role}}", + "mode": "Działanie organizacji", + "create": "Utwórz organizację", + "join": "Dołącz do organizacji", + "orgName": "Nazwa organizacji", + "orgNamePlaceholder": "np. Acme Engineering", + "invite": "Zaproś link lub kod", + "invitePlaceholder": "Wklej link udostępniony przez administratora", + "selected": "{{org}} jest wybrany do konfiguracji zespołu", + "roles": { + "owner": "Właściciel", + "admin": "Administrator", + "member": "Członek", + "guest": "Gość" + } + }, + "sharing": { + "title": "Wybierz, co zespół może widzieć", + "description": "Pilot repozytorium zapewnia widoczność granica. Poziom udostępniania kontroluje ilość publikowanych szczegółów sesji.", + "workspace": "Przestrzeń robocza", + "workspaceDescription": "Folder lokalny, w którym działa Twój agent", + "noWorkspace": "Brak otwartego obszaru roboczego", + "openWorkspace": "Otwórz obszar roboczy", + "repoScope": "Zakres repozytorium", + "repoScopeDescription": "Rozwiąż znormalizowane zdalne zdalne Git z bieżącego obszaru roboczego", + "detectScope": "Wykryj repozytorium zakres", + "remote": "Udostępniany pilot", + "level": "Minimalny poziom udostępniania", + "levelDescription": "Stosowany przez organizację do pasujących sesji", + "off": "Wyłączony", + "metadata": "Tylko metadane", + "replay": "Pełne odtwarzanie", + "save": "Zapisz i zweryfikuj zasady", + "createInvite": "Utwórz zaproszenie dla członków", + "copy": "Kopiuj", + "verified": "Serwer zaakceptował tę politykę repozytorium i kolejkę synchronizacji wyczerpany.", + "memberHint": "{{org}} jest zarządzany przez administratora. Twoja lokalna historia Kodeksu jest gotowa do synchronizacji zgodnie z zasadami repozytorium.", + "verifyMember": "Sprawdź moją ścieżkę synchronizacji", + "memberVerified": "Wybór organizacji jest aktywny, a bieżące żądanie synchronizacji zostało wyczerpane." + }, + "basics": { + "title": "Podstawowe ustawienia", + "description": "Zawsze możesz je później zmienić.", + "workspace": "Domyślne obszar roboczy", + "workspaceDescription": "Sesje uruchamiane z plikami w obszarze roboczym; projekty i elementy pracy organizują pracę zamiast zastępować system plików.", + "openWorkspace": "Otwórz obszar roboczy", + "changeWorkspace": "Zmień obszar roboczy", + "settingsHint": "Narzędzia, obszary robocze, organizacje, widoczność zespołu i samouczki można skonfigurować później w aplikacji." + }, + "tutorial": { + "title": "Wybierz jeden samouczek kontekstowy", + "description": "Rozpoczyna się po konfiguracji, na powierzchni prawdziwego produktu, gdzie znajdują się elementy sterujące.", + "hint": "Każdy samouczek możesz odtworzyć później w menu Ustawienia." + }, + "model": { + "title": "Jak działa ORGII", + "description": "Te obiekty rozwiązują różne problemy; żadna z nich nie jest inną nazwą folderu.", + "session": { + "title": "Sesja", + "description": "Kontekst konwersacji i wykonywania dla agenta. Może odnosić się do jednego obszaru roboczego." + }, + "workItem": { + "title": "Element pracy", + "description": "Zadanie z możliwością śledzenia ze statusem, osobą przypisaną, dyskusją i połączonymi sesjami." + }, + "project": { + "title": "Projekt", + "description": "Kontener do planowania elementów pracy. Może koordynować pracę między repozytoriami." + }, + "workspace": { + "title": "Przestrzeń robocza/system plików", + "description": "Prawdziwe lokalne foldery i pliki są odczytywane i modyfikowane przez agentów." + }, + "relationship": "Projekt → Element roboczy → Sesja → Pliki obszaru roboczego" + }, + "ready": { + "title": "Konfiguracja jest gotowa", + "description": "Przejrzyj poniższe trwałe kontrole, a następnie otwórz powierzchnię odpowiadającą pierwotnemu celowi.", + "toolsReady": "Agent wykryto dostęp", + "toolsLater": "Dostęp agenta można dodać później", + "workspaceReady": "Wybrany obszar roboczy", + "workspaceLater": "Przestrzeń roboczą można otworzyć następnie", + "teamPolicyReady": "Zasady widoczności zespołu potwierdzone", + "memberSyncReady": "Ścieżka synchronizacji członków potwierdzona", + "teamDestination": "Otwórz skrzynkę odbiorczą zespołu", + "workDestination": "Utwórz pierwszy element roboczy", + "personalDestination": "Rozpocznij sesję agenta", + "destinationHint": "Twój postęp konfiguracji pozostaje niezmieniony po uruchom ponownie.", + "teamDestinationHint": "Wybrana organizacja i postęp konfiguracji pozostają niezmienione po ponownym uruchomieniu.", + "openDestination": "Zakończ i otwórz" + }, + "destinations": { + "teamInbox": "Skrzynka odbiorcza zespołu", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Najpierw zaloguj się do ORG2 Cloud.", + "sessionExpired": "Twoja sesja ORG2 Cloud wygasła. Zaloguj się ponownie.", + "invalidInvite": "Ten link lub kod zaproszenia jest nieprawidłowy.", + "rosterNotConverged": "Organizacja nie jest jeszcze widoczna na Twojej odświeżonej liście członków.", + "cloudUnavailable": "Chmura ORG2 jest niedostępna. Spróbuj ponownie.", + "unknown": "Operacja konfiguracji nie powiodła się. Spróbuj ponownie.", + "workspaceRequired": "Otwórz obszar roboczy przed skonfigurowaniem zakresu zespołu.", + "gitRemoteRequired": "W tym obszarze roboczym nie ma pilota Git. Dodaj pilota i spróbuj ponownie.", + "orgAndScopeRequired": "Najpierw wybierz organizację i zakres repozytorium.", + "orgRequired": "Najpierw wybierz organizację.", + "policyChanged": "Ustawienia zespołu zostały zmienione podczas zapisywania. Przejrzyj je i zweryfikuj ponownie.", + "inviteOrgChanged": "Wybrana organizacja została zmieniona podczas tworzenia zaproszenia. Spróbuj ponownie.", + "syncOrgChanged": "Wybrana organizacja została zmieniona podczas sprawdzania synchronizacji. Spróbuj ponownie." + } + }, + "tutorials": { + "modalTitle": "Poradniki", + "start": "Rozpocznij", + "chrome": { + "stepProgress": "Krok {{current}} z {{total}}", + "close": "Zamknij", + "previous": "Poprzedni krok", + "next": "Następny krok", + "finish": "Zakończ prezentację", + "keyboardHint": "Użyj ← / → lub < / >", + "desktop": "Pulpit", + "myStation": "Moja stacja", + "infinity": "Infinity", + "agentStation": "Stacja agentów" + }, + "generalLayout": { + "title": "Ogólny przewodnik po układzie", + "description": "Poznaj pasek boczny sesji, panel czatu, przełącznik trybu stacji, stację roboczą, stację dokującą i obszary aplikacji.", + "duration": "1 min", + "steps": { + "chat-panel": { + "title": "Panel czatu", + "body": "W tym miejscu użytkownicy prowadzą rozmowy z agentami, przeglądają aktywność i wysyłają dalsze instrukcje." + }, + "station-mode-pill": { + "title": "Stacja przełączania tryby", + "body": "Użyj tej pigułki, aby przełączać tryby stacji. Pulpit oznacza My Station, Twoje miejsce pracy. Infinity oznacza Agent Station, widok aktywności agenta." + }, + "dock": { + "title": "Dok Agent Station", + "body": "Dok przełącza aplikacje wewnątrz stacji. Wycieczka tymczasowo wyłącza automatyczne ukrywanie, więc te elementy sterujące pozostają widoczne." + }, + "all-tabs": { + "title": "Wszystkie karty", + "body": "Pierwsza ikona w doku pokazuje wszystkie otwarte karty razem, niezależnie od tego, która aplikacja na stacji roboczej jest ich właścicielem." + }, + "code-editor": { + "title": "Edytor kodu", + "body": "Używaj edytora kodu do plików, różnic, terminali, kontroli źródła i zmian w kodowaniu wprowadzonych podczas sesji." + }, + "browser": { + "title": "Przeglądarka", + "body": "Używaj przeglądarki do tworzenia stron internetowych, podglądów, testowania aplikacji i badań w przeglądarce podczas czatu." + }, + "projects": { + "title": "Projekty", + "body": "Używaj Projektów do śledzenia elementów pracy, planów i stanu projektu połączonych z bieżącym obszarem roboczym." + } + } + }, + "codeEditor": { + "title": "Przewodnik po Edytorze kodu", + "description": "Dowiedz się o kartach, przełączaniu repozytoriów i gałęzi, kontroli źródła, historii Git i panelu projektu.", + "duration": "2 min", + "steps": { + "tabs": { + "title": "Karty Edytora kodu", + "body": "Na kartach gromadzone są pliki, różnice, terminale, widoki kontroli źródła i pulpity nawigacyjne, których używasz otwórz podczas pracy w repozytorium." + }, + "repo-selector": { + "title": "Utwórz lub przełącz repo", + "body": "Użyj selektora repozytorium, aby przełączyć aktywne repozytorium lub dodać kolejne repozytorium lub obszar roboczy." + }, + "branch-selector": { + "title": "Zmień gałęzie", + "body": "Selektor gałęzi pokazuje bieżącą gałąź Git i otwiera przełącznik gałęzi lub utwórz przepływ dla tego repozytorium." + }, + "editor-surface": { + "title": "Obszar roboczy edytora", + "body": "To jest główna powierzchnia Edytora kodu. Tutaj otwieraj pliki, sprawdzaj różnice, uruchamiaj terminale i przeglądaj wygenerowane zmiany." + }, + "create-tabs": { + "title": "Utwórz nowe karty", + "body": "Użyj menu plus na wszystkich kartach, aby tworzyć terminale, karty przeglądarki, pliki, pulpity nawigacyjne i narzędzia repo." + }, + "source-control": { + "title": "Zmiany w Gicie", + "body": "Open Source Control w celu przeglądania zmienionych plików, tworzenia lub cofania pracy, zatwierdzania, ściągania, wypychania, pobieraj i synchronizuj ze zdalnym." + }, + "git-history": { + "title": "Historia Git", + "body": "Historia Git przełącza Kontrolę źródła w tryb historii zatwierdzeń, dzięki czemu możesz sprawdzać poprzednie zatwierdzenia i powiązane zmiany." + }, + "dashboard": { + "title": "Panel kontrolny Edytora kodu", + "body": "Panel kontrolny to główna karta Edytora kodu dla obszarów roboczych. Użyj go, aby dodać repo, otworzyć szczegóły repozytorium i przejść do pracy nad projektem." + } + } + } } } diff --git a/src/i18n/locales/pt/common.json b/src/i18n/locales/pt/common.json index 2c26fb1bce..59be1e55aa 100644 --- a/src/i18n/locales/pt/common.json +++ b/src/i18n/locales/pt/common.json @@ -168,6 +168,7 @@ "goToSettings": "Ir para configurações", "restart": "Reiniciar", "switchToStation": "Alternar para {{station}}", + "switchMethod": "Alterar método", "rate": "Avaliar", "openInTab": "Abrir em aba", "insert": "Inserir", diff --git a/src/i18n/locales/pt/navigation.json b/src/i18n/locales/pt/navigation.json index 21488dd22b..7220f1dc4f 100644 --- a/src/i18n/locales/pt/navigation.json +++ b/src/i18n/locales/pt/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Recolher tudo", "markAllRead": "Marcar tudo como lido" }, + "guide": { + "trigger": "Abrir guia", + "title": "Continuar configuração", + "startSession": "Iniciar uma sessão", + "setUpTeam": "Configurar uma equipe", + "manageWork": "Gerenciar trabalho", + "openTutorials": "Abrir tutoriais", + "quickSetup": "Idioma e aparência", + "close": "Fechar guia", + "progressLabel": "{{completed}} de {{total}} etapas concluídas", + "localWorkspace": "Espaço de trabalho local" + }, "search": { "folders": "Pesquisar no Workspace...", "sessions": "Pesquisar sessões...", @@ -141,7 +153,8 @@ "workstation": "Estação de trabalho", "viewRam": "Ver RAM", "tutorials": "Tutoriais", - "openSettings": "Abrir configurações" + "openSettings": "Abrir configurações", + "setupChecklist": "Configuração rápida" }, "empty": { "noItems": "Nenhum item", diff --git a/src/i18n/locales/pt/onboarding.json b/src/i18n/locales/pt/onboarding.json index e00e92db28..4031c825dc 100644 --- a/src/i18n/locales/pt/onboarding.json +++ b/src/i18n/locales/pt/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Pular configuração", "getStarted": "Começar", "goToSettings": "Ir para Configurações" + }, + "readiness": { + "hero": { + "title": "Vamos configurar seu ORGII", + "description": "Algumas escolhas rápidas para personalizar seu workspace." + }, + "presentation": { + "label": "Prévia do layout", + "native": "Nativo do app", + "cinematic": "Cartão cinematográfico", + "classic": "Painel clássico" + }, + "classicPanel": { + "title": "Deixe o ORGII do seu jeito", + "description": "Escolha idioma, aparência, tema e cor de destaque. Você pode alterá-los a qualquer momento." + }, + "sidebar": { + "brandTag": "Configuração", + "subtitle": "Escolha o idioma e a aparência. Todo o resto continuará disponível no aplicativo.", + "ariaLabel": "Progresso da configuração", + "stepProgress": "Etapa {{current}} de {{total}}", + "saved": "Progresso salvo automaticamente", + "help": "As etapas concluídas permanecem editáveis. As etapas futuras serão desbloqueadas quando você terminar." + }, + "goal": { + "title": "O que você deseja realizar primeiro?", + "description": "ORGII adaptará a configuração e levará você à superfície correta do produto quando terminar.", + "recommended": "Recomendado", + "hint": "Isso altera o caminho de configuração; isso não limita permanentemente o que você pode usar.", + "personal": { + "title": "Iniciar uma sessão de agente", + "description": "Configure um fluxo de trabalho de codificação pessoal e abra o Launchpad." + }, + "team": { + "title": "Veja a atividade do Codex da equipe", + "description": "Conecte uma organização, escolha a visibilidade do repositório e abra a Caixa de entrada da equipe." + }, + "work": { + "title": "Gerencie o trabalho", + "description": "Entenda os projetos e itens de trabalho e crie o primeiro item." + } + }, + "tools": { + "title": "Detecte suas ferramentas de codificação", + "description": "Verifique Codex, Claude Code e Cursor usando o serviço de validação local existente.", + "detectedDescription": "Credencial local ou status de assinatura", + "found": "{{count}} contas encontradas · {{validated}} validadas", + "notFound": "Nenhuma conta utilizável detectada", + "notScanned": "Ainda não verificada", + "detect": "Ferramentas de detecção", + "importHistory": "Importar histórico do Codex", + "historyImported": "{{count}} Sessões do Codex estão disponíveis localmente", + "privacy": "Segredos nunca entre no estado de configuração. Apenas o provedor, a contagem e o status de validação são mantidos." + }, + "organization": { + "title": "Conecte a organização da equipe", + "description": "Faça login uma vez e selecione uma organização existente, crie uma ou participe com um convite.", + "signInHint": "A atividade da equipe usa uma conta ORG2 Cloud para que os membros compartilhem os mesmos limites da organização.", + "signIn": "Entrar/Registrar", + "existing": "Suas organizações", + "role": "Função: {{role}}", + "mode": "Ação da organização", + "create": "Criar organização", + "join": "Ingressar na organização", + "orgName": "Nome da organização", + "orgNamePlaceholder": "por exemplo Acme Engineering", + "invite": "Link ou código de convite", + "invitePlaceholder": "Cole o link que seu administrador compartilhou", + "selected": "{{org}} está selecionado para configuração da equipe", + "roles": { + "owner": "Proprietário", + "admin": "Administrador", + "member": "Membro", + "guest": "Convidado" + } + }, + "sharing": { + "title": "Escolha o que a equipe pode ver", + "description": "O repositório remoto é o limite de visibilidade. O nível de compartilhamento controla quantos detalhes da sessão são publicados.", + "workspace": "Espaço de trabalho", + "workspaceDescription": "A pasta local onde seu agente é executado", + "noWorkspace": "Nenhum espaço de trabalho aberto", + "openWorkspace": "Espaço de trabalho aberto", + "repoScope": "Escopo do repositório", + "repoScopeDescription": "Resolver controles remotos normalizados do Git do espaço de trabalho atual", + "detectScope": "Detectar repositório escopo", + "remote": "Remoto compartilhável", + "level": "Nível mínimo de compartilhamento", + "levelDescription": "Aplicado pela organização às sessões correspondentes", + "off": "Desativado", + "metadata": "Somente metadados", + "replay": "Reprodução completa", + "save": "Política de salvar e verificar", + "createInvite": "Criar convite de membro", + "copy": "Copiar", + "verified": "O servidor aceitou esta política de repositório e a fila de sincronização drenado.", + "memberHint": "{{org}} é gerenciado por um administrador. Seu histórico local do Codex está pronto para ser sincronizado de acordo com a política do repositório.", + "verifyMember": "Verifique meu caminho de sincronização", + "memberVerified": "A seleção da sua organização está ativa e a solicitação de sincronização atual foi esgotada." + }, + "basics": { + "title": "Preferências básicas", + "description": "Você poderá alterá-las a qualquer momento.", + "workspace": "Padrão espaço de trabalho", + "workspaceDescription": "As sessões são executadas em arquivos em um espaço de trabalho; projetos e itens de trabalho organizam o trabalho em vez de substituir o sistema de arquivos.", + "openWorkspace": "Abrir espaço de trabalho", + "changeWorkspace": "Alterar espaço de trabalho", + "settingsHint": "Ferramentas, espaços de trabalho, organizações, visibilidade da equipe e tutoriais podem ser configurados mais tarde no aplicativo." + }, + "tutorial": { + "title": "Escolha um tutorial contextual", + "description": "Ele começa após a configuração, na superfície real do produto onde existem os controles.", + "hint": "Você pode reproduzir qualquer tutorial posteriormente no menu Configurações." + }, + "model": { + "title": "Como o trabalho do ORGII se encaixa", + "description": "Esses objetos resolvem problemas diferentes; nenhum deles é outro nome para uma pasta.", + "session": { + "title": "Sessão", + "description": "Um contexto de conversação e execução para um agente. Pode fazer referência a um espaço de trabalho." + }, + "workItem": { + "title": "Item de trabalho", + "description": "Uma tarefa rastreável com status, responsável, discussão e sessões vinculadas." + }, + "project": { + "title": "Projeto", + "description": "Um contêiner de planejamento para itens de trabalho. Ele pode coordenar o trabalho entre repositórios." + }, + "workspace": { + "title": "Espaço de trabalho / sistema de arquivos", + "description": "As pastas e arquivos locais reais que os agentes leem e modificam." + }, + "relationship": "Projeto → Item de trabalho → Sessão → Arquivos de espaço de trabalho" + }, + "ready": { + "title": "A configuração está pronta", + "description": "Revise as verificações duráveis abaixo e abra a superfície que corresponde ao seu objetivo original.", + "toolsReady": "Acesso do agente detectado", + "toolsLater": "O acesso do agente pode ser adicionado posteriormente", + "workspaceReady": "Espaço de trabalho selecionado", + "workspaceLater": "O espaço de trabalho pode ser aberto em seguida", + "teamPolicyReady": "Política de visibilidade da equipe confirmada", + "memberSyncReady": "Caminho de sincronização do membro confirmado", + "teamDestination": "Abrir caixa de entrada da equipe", + "workDestination": "Criar o primeiro item de trabalho", + "personalDestination": "Iniciar uma sessão do agente", + "destinationHint": "Seu progresso de configuração permanece persistido após reinicie.", + "teamDestinationHint": "A organização selecionada e o progresso da configuração permanecem após a reinicialização.", + "openDestination": "Conclua e abra" + }, + "destinations": { + "teamInbox": "Caixa de entrada da equipe", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Faça login no ORG2 Cloud primeiro.", + "sessionExpired": "Sua sessão do ORG2 Cloud expirou. Faça login novamente.", + "invalidInvite": "Este link ou código de convite é inválido.", + "rosterNotConverged": "A organização ainda não está visível em sua lista de membros atualizada.", + "cloudUnavailable": "ORG2 Cloud não está disponível. Tente novamente.", + "unknown": "A operação de configuração falhou. Tente novamente.", + "workspaceRequired": "Abra um espaço de trabalho antes de configurar o escopo da equipe.", + "gitRemoteRequired": "Este espaço de trabalho não tem Git remoto. Adicione um controle remoto e tente novamente.", + "orgAndScopeRequired": "Selecione primeiro uma organização e o escopo do repositório.", + "orgRequired": "Selecione uma organização primeiro.", + "policyChanged": "As configurações da equipe foram alteradas ao salvar. Revise-os e verifique-os novamente.", + "inviteOrgChanged": "A organização selecionada foi alterada durante a criação do convite. Tente novamente.", + "syncOrgChanged": "A organização selecionada foi alterada durante a verificação da sincronização. Tente novamente." + } + }, + "tutorials": { + "modalTitle": "Tutoriais", + "start": "Iniciar", + "chrome": { + "stepProgress": "Etapa {{current}} de {{total}}", + "close": "Fechar", + "previous": "Etapa anterior", + "next": "Próxima etapa", + "finish": "Terminar o tour", + "keyboardHint": "Use ← / → ou < / >", + "desktop": "Desktop", + "myStation": "Minha estação", + "infinity": "Infinity", + "agentStation": "Agent Station" + }, + "generalLayout": { + "title": "Tour de layout geral", + "description": "Conheça a barra lateral da sessão, painel de bate-papo, alternador de modo de estação, estação de trabalho, dock e áreas de aplicativos.", + "duration": "1 min", + "steps": { + "chat-panel": { + "title": "Painel de bate-papo", + "body": "É onde os usuários conversam com agentes, analisam atividades e enviam instruções de acompanhamento." + }, + "station-mode-pill": { + "title": "Alternar estação modos", + "body": "Use esta pílula para alternar os modos de estação. Desktop significa My Station, seu espaço de trabalho. Infinito significa Agent Station, a visualização da atividade do agente." + }, + "dock": { + "title": "Agent Station dock", + "body": "O dock alterna aplicativos dentro da estação. O tour desativa temporariamente a ocultação automática para que esses controles permaneçam visíveis." + }, + "all-tabs": { + "title": "Todas as guias", + "body": "O primeiro ícone de encaixe mostra todas as guias abertas juntas, independentemente do aplicativo da estação de trabalho que as possui." + }, + "code-editor": { + "title": "Editor de código", + "body": "Use o Editor de código para arquivos, diferenças, terminais, controle de origem e alterações de codificação feitas durante um sessão." + }, + "browser": { + "title": "Navegador", + "body": "Use o navegador para páginas da web, visualizações, testes de aplicativos e investigação baseada em navegador junto com o bate-papo." + }, + "projects": { + "title": "Projetos", + "body": "Use projetos para rastrear itens de trabalho, planos e estado do projeto conectado ao espaço de trabalho atual." + } + } + }, + "codeEditor": { + "title": "Tour do editor de código", + "description": "Aprenda guias, alternância de repositórios e ramificações, controle de origem, histórico do Git e o painel do projeto.", + "duration": "2 min", + "steps": { + "tabs": { + "title": "Guias do editor de código", + "body": "As guias coletam os arquivos, diferenças, terminais, visualizações de controle de origem e painéis que você abre enquanto trabalhando no repositório." + }, + "repo-selector": { + "title": "Criar ou alternar repositórios", + "body": "Use o seletor de repositório para alternar o repositório ativo ou adicionar outro repositório ou espaço de trabalho." + }, + "branch-selector": { + "title": "Alterar ramificações", + "body": "O seletor de ramificação mostra a ramificação Git atual e abre a opção de ramificação ou cria fluxo para este repositório." + }, + "editor-surface": { + "title": "Espaço de trabalho do editor", + "body": "Esta é a superfície principal do editor de código. Abra arquivos, inspecione diferenças, execute terminais e revise as alterações geradas aqui." + }, + "create-tabs": { + "title": "Criar novas guias", + "body": "Use o menu de adição em Todas as guias para criar terminais, guias do navegador, arquivos, painéis e utilitários de repositório." + }, + "source-control": { + "title": "Alterações do Git", + "body": "Open Source Control para revisar arquivos alterados, preparar ou desestabilizar trabalho, confirmar, puxar, enviar, buscar e sincronizar com o controle remoto." + }, + "git-history": { + "title": "Histórico do Git", + "body": "O histórico do Git alterna o controle de origem para o modo de histórico de commits para que você possa inspecionar commits anteriores e alterações relacionadas." + }, + "dashboard": { + "title": "Painel do Editor de Código", + "body": "O painel é a guia inicial do Editor de Código para espaços de trabalho. Use-o para adicionar repositórios, abrir detalhes do repositório e iniciar o trabalho do projeto." + } + } + } } } diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index 4e16ad160a..fb4f548932 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -167,6 +167,7 @@ "goToSettings": "Перейти к настройкам", "restart": "Перезапустить", "switchToStation": "Переключиться на {{station}}", + "switchMethod": "Сменить способ", "rate": "Оценить", "openInTab": "Открыть во вкладке", "insert": "Вставить", diff --git a/src/i18n/locales/ru/navigation.json b/src/i18n/locales/ru/navigation.json index 93cd47c543..d1b8b9ac27 100644 --- a/src/i18n/locales/ru/navigation.json +++ b/src/i18n/locales/ru/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Свернуть все", "markAllRead": "Отметить все как прочитанные" }, + "guide": { + "trigger": "Открыть руководство", + "title": "Продолжить настройку", + "startSession": "Начать сессию", + "setUpTeam": "Настроить команду", + "manageWork": "Управлять работой", + "openTutorials": "Открыть обучение", + "quickSetup": "Язык и внешний вид", + "close": "Закрыть руководство", + "progressLabel": "Выполнено {{completed}} из {{total}} шагов", + "localWorkspace": "Локальное рабочее пространство" + }, "search": { "folders": "Искать в рабочей области...", "sessions": "Искать сессии...", @@ -139,7 +151,8 @@ "workstation": "Рабочая станция", "viewRam": "Показать RAM", "tutorials": "Обучение", - "openSettings": "Открыть настройки" + "openSettings": "Открыть настройки", + "setupChecklist": "Быстрая настройка" }, "empty": { "noItems": "Нет элементов", diff --git a/src/i18n/locales/ru/onboarding.json b/src/i18n/locales/ru/onboarding.json index 028e2e4d82..b3ba6287e6 100644 --- a/src/i18n/locales/ru/onboarding.json +++ b/src/i18n/locales/ru/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Пропустить настройку", "getStarted": "Начать", "goToSettings": "Перейти к настройкам" + }, + "readiness": { + "hero": { + "title": "Настроим ваш ORGII", + "description": "Несколько быстрых настроек для персонализации рабочего пространства." + }, + "presentation": { + "label": "Предпросмотр макета", + "native": "Стиль приложения", + "cinematic": "Кинематографичная карточка", + "classic": "Классическая панель" + }, + "classicPanel": { + "title": "Настройте ORGII под себя", + "description": "Выберите язык, оформление, тему и акцентный цвет. Их можно изменить в любое время." + }, + "sidebar": { + "brandTag": "Настройка", + "subtitle": "Выберите язык и оформление. Остальные настройки останутся доступны в приложении.", + "ariaLabel": "Прогресс настройки", + "stepProgress": "Шаг {{current}} из {{total}}", + "saved": "Прогресс сохраняется автоматически", + "help": "Завершенные шаги можно редактировать. Последующие шаги открываются по мере завершения." + }, + "goal": { + "title": "Что вы хотите выполнить в первую очередь?", + "description": "ORGII адаптирует настройку и перенаправит вас на нужную поверхность продукта, когда вы закончите.", + "recommended": "Рекомендуется", + "hint": "Это изменит путь настройки; это не ограничивает навсегда то, что вы можете использовать.", + "personal": { + "title": "Начать сеанс агента", + "description": "Настроить личный рабочий процесс кодирования и открыть панель запуска." + }, + "team": { + "title": "Просмотреть действия команды по Кодексу", + "description": "Подключить организацию, выбрать видимость репозитория и открыть папку «Входящие группы»." + }, + "work": { + "title": "Управлять работой", + "description": "Понимать проекты и работу Элементы, затем создайте первый элемент." + } + }, + "tools": { + "title": "Определите свои инструменты кодирования", + "description": "Проверьте Codex, Claude Code и Cursor, используя существующую локальную службу проверки.", + "detectedDescription": "Локальные учетные данные или статус подписки", + "found": "Обнаружено {{count}} аккаунтов · {{validated}} подтверждено", + "notFound": "Пригодная к использованию учетная запись не обнаружена", + "notScanned": "Пока не проверено", + "detect": "Инструменты обнаружения", + "importHistory": "Импортировать историю Кодекса", + "historyImported": "{{count}} Сеансы Кодекса доступны локально", + "privacy": "Секреты никогда не входите в состояние настройки. Сохраняются только поставщик, количество и статус проверки." + }, + "organization": { + "title": "Подключите организацию группы", + "description": "Войдите в систему один раз, затем выберите существующую организацию, создайте ее или присоединитесь по приглашению.", + "signInHint": "Действия команды используют учетную запись ORG2 Cloud, поэтому участники имеют одну и ту же границу организации.", + "signIn": "Войдите/зарегистрируйтесь", + "existing": "Ваши организации", + "role": "Роль: {{role}}", + "mode": "Действие организации", + "create": "Создать организацию", + "join": "Присоединиться к организации", + "orgName": "Название организации", + "orgNamePlaceholder": "например. Acme Engineering", + "invite": "Ссылка или код для приглашения", + "invitePlaceholder": "Вставьте ссылку, которой поделился ваш администратор", + "selected": "{{org}} выбран для настройки группы", + "roles": { + "owner": "Владелец", + "admin": "Администратор", + "member": "Член", + "guest": "Гость" + } + }, + "sharing": { + "title": "Выберите то, что может видеть команда", + "description": "Удаленный репозиторий — это граница видимости. Уровень общего доступа определяет, сколько подробностей сеанса публикуется.", + "workspace": "Рабочая область", + "workspaceDescription": "Локальная папка, в которой работает ваш агент", + "noWorkspace": "Рабочая область не открыта", + "openWorkspace": "Открыть рабочую область", + "repoScope": "Область репозитория", + "repoScopeDescription": "Разрешить нормализованные удаленные Git из текущей рабочей области", + "detectScope": "Обнаружить репозиторий область действия", + "remote": "Пульт дистанционного управления с общим доступом", + "level": "Минимальный уровень общего доступа", + "levelDescription": "Применяется организацией к соответствующим сеансам", + "off": "Выкл.", + "metadata": "Только метаданные", + "replay": "Полное воспроизведение", + "save": "Политика сохранения и проверки", + "createInvite": "Создать приглашение участника", + "copy": "Копировать", + "verified": "Сервер принял эту политику репозитория и очередь синхронизации опустошен.", + "memberHint": "{{org}} управляется администратором. Ваша локальная история Кодекса готова к синхронизации в соответствии с политикой хранилища.", + "verifyMember": "Проверьте мой путь синхронизации", + "memberVerified": "Выбранная вами организация активна, а текущий запрос на синхронизацию удален." + }, + "basics": { + "title": "Основные настройки", + "description": "Их всегда можно изменить позже.", + "workspace": "По умолчанию рабочая область", + "workspaceDescription": "Сеансы выполняются с файлами в рабочей области; проекты и рабочие элементы организуют работу, а не заменяют файловую систему.", + "openWorkspace": "Открыть рабочую область", + "changeWorkspace": "Изменить рабочую область", + "settingsHint": "Инструменты, рабочие области, организации, видимость команды и обучение можно настроить позже в приложении." + }, + "tutorial": { + "title": "Выберите одно контекстное руководство.", + "description": "Оно запускается после установки, на реальной поверхности продукта, где существуют элементы управления.", + "hint": "Вы можете воспроизвести любое руководство позже из меню «Настройки»." + }, + "model": { + "title": "Как работает ORGII", + "description": "Эти объекты решают разные проблемы; ни одно из них не является другим именем папки.", + "session": { + "title": "Сеанс", + "description": "Контекст диалога и выполнения для агента. Он может ссылаться на одно рабочее пространство." + }, + "workItem": { + "title": "Рабочий элемент", + "description": "Отслеживаемая задача со статусом, исполнителем, обсуждением и связанными сеансами." + }, + "project": { + "title": "Проект", + "description": "Контейнер планирования для рабочих элементов. Он может координировать работу между репозиториями." + }, + "workspace": { + "title": "Рабочая область/файловая система", + "description": "Реальные локальные папки и файлы агенты считывают и изменяют." + }, + "relationship": "Проект → Рабочий элемент → Сеанс → Файлы рабочей области" + }, + "ready": { + "title": "Настройка готова", + "description": "Проверьте постоянные проверки ниже, затем откройте поверхность, соответствующую вашей первоначальной цели.", + "toolsReady": "Доступ агента обнаружен", + "toolsLater": "Доступ агента можно добавить позже", + "workspaceReady": "Рабочая область выбрана", + "workspaceLater": "Рабочая область может быть открыта следующей", + "teamPolicyReady": "Политика видимости команды подтверждена", + "memberSyncReady": "Путь синхронизации участников подтвержден", + "teamDestination": "Открыть папку «Входящие команды»", + "workDestination": "Создать первый рабочий элемент", + "personalDestination": "Начать сеанс агента", + "destinationHint": "Ваш ход настройки сохраняется после перезапустите.", + "teamDestinationHint": "Выбранная вами организация и ход настройки сохранятся после перезапуска.", + "openDestination": "Завершите и откройте" + }, + "destinations": { + "teamInbox": "Входящие группы", + "launchpad": "Панель запуска" + }, + "errors": { + "signInRequired": "Сначала войдите в облако ORG2.", + "sessionExpired": "Срок вашего сеанса облака ORG2 истек. Войдите еще раз.", + "invalidInvite": "Эта ссылка или код приглашения недействительны.", + "rosterNotConverged": "Организация пока не отображается в вашем обновленном списке участников.", + "cloudUnavailable": "Облако ORG2 недоступно. Попробуйте еще раз.", + "unknown": "Операция установки не удалась. Попробуйте еще раз.", + "workspaceRequired": "Откройте рабочую область перед настройкой области действия группы.", + "gitRemoteRequired": "В этой рабочей области нет удаленного Git. Добавьте удаленное устройство и повторите попытку.", + "orgAndScopeRequired": "Сначала выберите организацию и область хранилища.", + "orgRequired": "Сначала выберите организацию.", + "policyChanged": "Настройки группы изменились при сохранении. Просмотрите и подтвердите их еще раз.", + "inviteOrgChanged": "Выбранная организация была изменена при создании приглашения. Повторите попытку.", + "syncOrgChanged": "Выбранная организация изменилась во время проверки синхронизации. Попробуйте еще раз." + } + }, + "tutorials": { + "modalTitle": "Руководства", + "start": "Начать", + "chrome": { + "stepProgress": "Шаг {{current}} из {{total}}", + "close": "Закрыть", + "previous": "Предыдущий шаг", + "next": "Следующий шаг", + "finish": "Завершить тур", + "keyboardHint": "Используйте ← / → или < / >", + "desktop": "Рабочий стол", + "myStation": "Моя станция", + "infinity": "Infinity", + "agentStation": "Agent Station" + }, + "generalLayout": { + "title": "Обзор общего макета", + "description": "Изучите боковую панель сеанса, панель чата, переключатель режима станции, рабочую станцию, док-станцию и области приложений.", + "duration": "1 минута", + "steps": { + "chat-panel": { + "title": "Панель чата", + "body": "Здесь пользователи общаются с агентами, просматривают действия и отправляют последующие инструкции." + }, + "station-mode-pill": { + "title": "Переключение станции режимы", + "body": "Используйте эту таблетку для переключения режимов станции. Рабочий стол означает «Моя станция», ваше рабочее пространство. Infinity означает Agent Station, представление активности агента." + }, + "dock": { + "title": "Док-станция Agent Station", + "body": "Док-станция переключает приложения внутри станции. В обзоре временно отключается автоматическое скрытие, поэтому эти элементы управления остаются видимыми." + }, + "all-tabs": { + "title": "Все вкладки", + "body": "Первый значок в панели отображает все открытые вкладки вместе, независимо от того, какому приложению рабочей станции они принадлежат." + }, + "code-editor": { + "title": "Редактор кода", + "body": "Используйте редактор кода для файлов, различий, терминалов, системы управления версиями и изменений кода, внесенных во время просмотра. сеанс." + }, + "browser": { + "title": "Браузер", + "body": "Используйте браузер для веб-страниц, предварительного просмотра, тестирования приложений и исследований на основе браузера вместе с чатом." + }, + "projects": { + "title": "Проекты", + "body": "Используйте проекты для отслеживания рабочих элементов, планов и состояния проекта, подключенного к текущей рабочей области." + } + } + }, + "codeEditor": { + "title": "Обзор редактора кода", + "description": "Изучите вкладки, репозиторий и переключение ветвей, систему управления версиями, историю Git и панель управления проектом.", + "duration": "2 минуты", + "steps": { + "tabs": { + "title": "Вкладки редактора кода", + "body": "Вкладки собирают файлы, различия, терминалы, представления системы управления версиями и панели мониторинга, которые вы открываете. во время работы в репозитории." + }, + "repo-selector": { + "title": "Создание или переключение репозитория", + "body": "Используйте селектор репозитория, чтобы переключить активный репозиторий или добавить другой репозиторий или рабочую область." + }, + "branch-selector": { + "title": "Изменить ветки", + "body": "Селектор ветвей показывает текущую ветку Git и открывает переключатель ветки или создание потока для этого репозитория." + }, + "editor-surface": { + "title": "Рабочая область редактора", + "body": "Это основной редактор кода поверхность. Открывайте файлы, проверяйте различия, запускайте терминалы и просматривайте внесенные изменения здесь." + }, + "create-tabs": { + "title": "Создавайте новые вкладки", + "body": "Используйте меню «плюс» на вкладках «Все вкладки», чтобы создавать терминалы, вкладки браузера, файлы, информационные панели и утилиты репозитория." + }, + "source-control": { + "title": "Изменения Git", + "body": "Откройте систему управления исходным кодом для просмотра измененных файлов, подготовки или отмены работы, фиксации, извлечения, отправки, извлечения и т. д. синхронизироваться с пультом." + }, + "git-history": { + "title": "История Git", + "body": "Git History переключает систему управления исходным кодом в режим истории коммитов, чтобы вы могли проверять предыдущие коммиты и связанные с ними изменения." + }, + "dashboard": { + "title": "Панель управления редактора кода", + "body": "Панель мониторинга — это главная вкладка редактора кода для рабочих областей. Используйте его, чтобы добавлять репозитории, открывать сведения о репозиториях и приступать к работе над проектом." + } + } + } } } diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 9e3af373af..40a903830c 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -167,6 +167,7 @@ "goToSettings": "Ayarlara Git", "restart": "Yeniden Başlat", "switchToStation": "{{station}} moduna geç", + "switchMethod": "Yöntemi değiştir", "rate": "Değerlendir", "openInTab": "Sekmede Aç", "insert": "Ekle", diff --git a/src/i18n/locales/tr/navigation.json b/src/i18n/locales/tr/navigation.json index 3b8b7df2bf..9e63707de3 100644 --- a/src/i18n/locales/tr/navigation.json +++ b/src/i18n/locales/tr/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Tümünü daralt", "markAllRead": "Tümünü okundu olarak işaretle" }, + "guide": { + "trigger": "Kılavuzu aç", + "title": "Kuruluma devam et", + "startSession": "Oturum başlat", + "setUpTeam": "Ekip kur", + "manageWork": "İşi yönet", + "openTutorials": "Eğitimleri aç", + "quickSetup": "Dil ve görünüm", + "close": "Kılavuzu kapat", + "progressLabel": "{{total}} kurulum adımından {{completed}} tanesi tamamlandı", + "localWorkspace": "Yerel çalışma alanı" + }, "search": { "folders": "Çalışma alanında ara...", "sessions": "Oturum ara...", @@ -139,7 +151,8 @@ "workstation": "İş istasyonu", "viewRam": "RAM’i görüntüle", "tutorials": "Eğitimler", - "openSettings": "Ayarları aç" + "openSettings": "Ayarları aç", + "setupChecklist": "Hızlı kurulum" }, "empty": { "noItems": "Öğe yok", diff --git a/src/i18n/locales/tr/onboarding.json b/src/i18n/locales/tr/onboarding.json index c14bdd5151..94e9ae8d91 100644 --- a/src/i18n/locales/tr/onboarding.json +++ b/src/i18n/locales/tr/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Kurulumu Atla", "getStarted": "Başla", "goToSettings": "Ayarlara Git" + }, + "readiness": { + "hero": { + "title": "ORGII'nizi birlikte ayarlayalım", + "description": "Çalışma alanınızı kişiselleştirmek için birkaç hızlı seçim." + }, + "presentation": { + "label": "Düzen önizleme", + "native": "Uygulama yerel", + "cinematic": "Sinematik kart", + "classic": "Klasik panel" + }, + "classicPanel": { + "title": "ORGII'yi kendinize göre ayarlayın", + "description": "Rahat ettiğiniz dili, görünümü, temayı ve vurgu rengini seçin. Bunları istediğiniz zaman değiştirebilirsiniz." + }, + "sidebar": { + "brandTag": "Kurulum", + "subtitle": "Dilinizi ve görünümü seçin. Diğer tüm ayarlar uygulamada kullanılabilir kalır.", + "ariaLabel": "Kurulum ilerleme durumu", + "stepProgress": "Adım {{current}} / {{total}}", + "saved": "İlerleme otomatik olarak kaydedilir", + "help": "Tamamlanan adımlar düzenlenebilir durumda kalır. Bitirdikçe sonraki adımların kilidi açılır." + }, + "goal": { + "title": "İlk olarak neyi başarmak istiyorsunuz?", + "description": "ORGII, kurulumu uyarlayacak ve işiniz bittiğinde sizi doğru ürün yüzeyine götürecektir.", + "recommended": "Önerilir", + "hint": "Bu, kurulum yolunu değiştirir; kullanabileceklerinizi kalıcı olarak sınırlamaz.", + "personal": { + "title": "Bir temsilci oturumu başlatın", + "description": "Kişisel bir kodlama iş akışı ayarlayın ve Launchpad'i açın." + }, + "team": { + "title": "Ekip Codex etkinliğini görün", + "description": "Bir kuruluşa bağlanın, depo görünürlüğünü seçin ve Ekip Gelen Kutusu'nu açın." + }, + "work": { + "title": "Çalışmayı yönetin", + "description": "Projeleri ve Çalışmaları Anlayın Öğeler'e gidin ve ardından ilk öğeyi oluşturun." + } + }, + "tools": { + "title": "Kodlama araçlarınızı algılayın", + "description": "Mevcut yerel doğrulama hizmetini kullanarak Codex, Claude Code ve Cursor'u kontrol edin.", + "detectedDescription": "Yerel kimlik bilgisi veya abonelik durumu", + "found": "{{count}} hesap bulundu · {{validated}} doğrulandı", + "notFound": "Kullanılabilir hesap algılanmadı", + "notScanned": "Henüz taranmadı", + "detect": "Algılama araçları", + "importHistory": "Codex geçmişini içe aktar", + "historyImported": "{{count}} Codex oturumu yerel olarak kullanılabilir", + "privacy": "Gizliler asla kurulum durumuna girmeyin. Yalnızca sağlayıcı, sayım ve doğrulama durumu korunur." + }, + "organization": { + "title": "Ekip kuruluşunu bağlayın", + "description": "Bir kez oturum açın, ardından mevcut bir kuruluş seçin, bir kuruluş oluşturun veya bir davetle katılın.", + "signInHint": "Ekip etkinliği bir ORG2 Bulut hesabı kullandığından üyeler aynı kuruluş sınırını paylaşır.", + "signIn": "Oturum açın / Kaydolun", + "existing": "Kuruluşlarınız", + "role": "Rol: {{role}}", + "mode": "Kuruluş işlemi", + "create": "Kuruluş oluştur", + "join": "Kuruluşa katıl", + "orgName": "Kuruluş adı", + "orgNamePlaceholder": "ör. Acme Engineering", + "invite": "Bağlantıyı veya kodu davet edin", + "invitePlaceholder": "Yöneticinizin paylaştığı bağlantıyı yapıştırın", + "selected": "{{org}} ekip kurulumu için seçildi", + "roles": { + "owner": "Sahip", + "admin": "Yönetici", + "member": "Üye", + "guest": "Misafir" + } + }, + "sharing": { + "title": "Ekibin neleri görebileceğini seçin", + "description": "Depo uzaktan görünürlük sınırıdır. Paylaşım düzeyi, ne kadar oturum ayrıntısının yayınlanacağını kontrol eder.", + "workspace": "Çalışma alanı", + "workspaceDescription": "Aracınızın çalıştığı yerel klasör", + "noWorkspace": "Açık çalışma alanı yok", + "openWorkspace": "Açık çalışma alanı", + "repoScope": "Depo kapsamı", + "repoScopeDescription": "Geçerli çalışma alanından normalleştirilmiş Git uzaktan kumandalarını çözümleyin", + "detectScope": "Repo'yu algıla kapsam", + "remote": "Paylaşılabilir uzaktan kumanda", + "level": "Minimum paylaşım düzeyi", + "levelDescription": "Kuruluş tarafından eşleşen oturumlara uygulanır", + "off": "Kapalı", + "metadata": "Yalnızca meta veriler", + "replay": "Tam tekrar oynatma", + "save": "Politikayı kaydet ve doğrula", + "createInvite": "Üye daveti oluştur", + "copy": "Kopyala", + "verified": "Sunucu bu depo politikasını ve senkronizasyon kuyruğunu kabul etti boşaltıldı.", + "memberHint": "{{org}} bir yönetici tarafından yönetiliyor. Yerel Codex geçmişiniz, depo politikaları kapsamında senkronize edilmeye hazır.", + "verifyMember": "Senkronizasyon yolumu doğrula", + "memberVerified": "Kuruluş seçiminiz etkin ve mevcut senkronizasyon isteği boşaltıldı." + }, + "basics": { + "title": "Temel tercihler", + "description": "Bunları daha sonra istediğiniz zaman değiştirebilirsiniz.", + "workspace": "Varsayılan çalışma alanı", + "workspaceDescription": "Oturumlar bir çalışma alanındaki dosyalarda çalıştırılır; projeler ve iş öğeleri, dosya sistemini değiştirmek yerine işi düzenler.", + "openWorkspace": "Çalışma alanını açın", + "changeWorkspace": "Çalışma alanını değiştirin", + "settingsHint": "Araçlar, çalışma alanları, kuruluşlar, ekip görünürlüğü ve eğitimler daha sonra uygulamada yapılandırılabilir." + }, + "tutorial": { + "title": "Bağlamsal bir eğitim seçin", + "description": "Kurulumdan sonra, kontrollerin bulunduğu gerçek ürün yüzeyinde başlar.", + "hint": "Her iki eğitimi de daha sonra Ayarlar menüsünden yeniden oynatabilirsiniz." + }, + "model": { + "title": "ORGII'nin çalışması nasıl birbirine uyar?", + "description": "Bu nesneler farklı sorunları çözer; hiçbiri bir klasörün başka bir adı değildir.", + "session": { + "title": "Oturum", + "description": "Bir aracı için konuşma ve yürütme bağlamı. Bir çalışma alanına referans verebilir." + }, + "workItem": { + "title": "Çalışma Öğesi", + "description": "Durum, atanan kişi, tartışma ve bağlantılı oturumlar içeren izlenebilir bir görev." + }, + "project": { + "title": "Proje", + "description": "Çalışma Öğeleri için bir planlama kapsayıcısı. Depolardaki çalışmayı koordine edebilir." + }, + "workspace": { + "title": "Çalışma alanı / dosya sistemi", + "description": "Gerçek yerel klasörler ve dosyalar aracıları okur ve değiştirir." + }, + "relationship": "Proje → Çalışma Öğesi → Oturum → Çalışma alanı dosyaları" + }, + "ready": { + "title": "Kurulum hazır", + "description": "Aşağıdaki dayanıklı kontrolleri inceleyin, ardından orijinal hedefinize uygun yüzeyi açın.", + "toolsReady": "Aracı erişimi algılandı", + "toolsLater": "Temsilci erişimi daha sonra eklenebilir", + "workspaceReady": "Çalışma alanı seçildi", + "workspaceLater": "Çalışma alanı bundan sonra açılabilir", + "teamPolicyReady": "Ekip görünürlük politikası onaylandı", + "memberSyncReady": "Üye senkronizasyon yolu onaylandı", + "teamDestination": "Ekip Gelen Kutusunu Aç", + "workDestination": "İlk Çalışma Öğesini oluşturun", + "personalDestination": "Bir temsilci oturumu başlatın", + "destinationHint": "Kurulum ilerlemeniz şu işlemlerden sonra devam eder: yeniden başlatın.", + "teamDestinationHint": "Seçtiğiniz kuruluş ve kurulum ilerleme durumu, yeniden başlatmanın ardından devam eder.", + "openDestination": "Sonlandırın ve" + }, + "destinations": { + "teamInbox": "Ekip Gelen Kutusu", + "launchpad": "Launchpad'i" + }, + "errors": { + "signInRequired": "Önce ORG2 Cloud'da oturum açın.", + "sessionExpired": "ORG2 Cloud oturumunuzun süresi doldu. Tekrar oturum açın.", + "invalidInvite": "Bu davet bağlantısı veya kodu geçersiz.", + "rosterNotConverged": "Kuruluş henüz yenilenen üyelik listenizde görünmüyor.", + "cloudUnavailable": "ORG2 Cloud kullanılamıyor. Tekrar deneyin.", + "unknown": "Kurulum işlemi başarısız oldu. Tekrar deneyin.", + "workspaceRequired": "Ekip kapsamını yapılandırmadan önce bir çalışma alanı açın.", + "gitRemoteRequired": "Bu çalışma alanında Git uzaktan kumandası yok. Bir uzaktan kumanda ekleyin ve tekrar deneyin.", + "orgAndScopeRequired": "Önce bir kuruluş ve veri havuzu kapsamı seçin.", + "orgRequired": "Önce bir kuruluş seçin.", + "policyChanged": "Kaydetme sırasında ekip ayarları değişti. Bunları tekrar inceleyip doğrulayın.", + "inviteOrgChanged": "Seçilen kuruluş, davet oluşturulurken değiştirildi. Tekrar deneyin.", + "syncOrgChanged": "Senkronizasyon doğrulanırken seçilen kuruluş değişti. Tekrar deneyin." + } + }, + "tutorials": { + "modalTitle": "Eğitimler", + "start": "Başlat", + "chrome": { + "stepProgress": "Adım {{current}} / {{total}}", + "close": "Kapat", + "previous": "Önceki adım", + "next": "Sonraki adım", + "finish": "Turu bitir", + "keyboardHint": "← / → veya < / > kullanın", + "desktop": "Masaüstü", + "myStation": "İstasyonum", + "infinity": "Infinity", + "agentStation": "Agent Station" + }, + "generalLayout": { + "title": "Genel düzen turu", + "description": "Oturum kenar çubuğu, Sohbet Paneli, istasyon modu değiştirici, İş istasyonu, bağlantı noktası ve uygulama alanlarını öğrenin.", + "duration": "1 dk.", + "steps": { + "chat-panel": { + "title": "Sohbet Paneli", + "body": "Bu, kullanıcıların temsilcilerle sohbet ettiği, etkinliği incelediği ve takip talimatlarını gönderdiği yerdir." + }, + "station-mode-pill": { + "title": "İstasyon değiştir modlar", + "body": "İstasyon modlarını değiştirmek için bu hapı kullanın. Masaüstü, My Station, yani çalışma alanınız anlamına gelir. Sonsuzluk, ajan etkinlik görünümü olan Agent Station anlamına gelir." + }, + "dock": { + "title": "Agent Station bağlantı istasyonu", + "body": "Dok, istasyonun içindeki uygulamaları değiştirir. Tur, otomatik gizlemeyi geçici olarak devre dışı bırakır, böylece bu kontroller görünür kalır." + }, + "all-tabs": { + "title": "Tüm Sekmeler", + "body": "İlk dock simgesi, hangi iş istasyonu uygulamasının bunlara sahip olduğuna bakılmaksızın tüm açık sekmeleri bir arada gösterir." + }, + "code-editor": { + "title": "Kod Düzenleyici", + "body": "Dosyalar, farklar, terminaller, kaynak kontrolü ve bir işlem sırasında yapılan kodlama değişiklikleri için Kod Düzenleyici'yi kullanın. oturum." + }, + "browser": { + "title": "Tarayıcı", + "body": "Sohbetin yanı sıra web sayfaları, önizlemeler, uygulama testleri ve tarayıcı tabanlı araştırmalar için Tarayıcıyı kullanın." + }, + "projects": { + "title": "Projeler", + "body": "Geçerli çalışma alanına bağlı iş öğelerini, planları ve proje durumunu izlemek için Projeler'i kullanın." + } + } + }, + "codeEditor": { + "title": "Kod Düzenleyici turu", + "description": "Sekmeleri, depo ve dal değiştirmeyi, Kaynak Denetimini, Git Geçmişini ve proje kontrol panelini öğrenin.", + "duration": "2 dk.", + "steps": { + "tabs": { + "title": "Kod Düzenleyici sekmeleri", + "body": "Sekmeler, açıkken açtığınız dosyaları, farkları, terminalleri, kaynak kontrol görünümlerini ve kontrol panellerini toplar. depoda çalışmak." + }, + "repo-selector": { + "title": "Depolar oluşturun veya değiştirin", + "body": "Etkin depoyu değiştirmek veya başka bir depo veya çalışma alanı eklemek için depo seçiciyi kullanın." + }, + "branch-selector": { + "title": "Dalları değiştirin", + "body": "Dal seçici mevcut Git dalını gösterir ve şube anahtarını açar veya bu depo için akış oluşturur." + }, + "editor-surface": { + "title": "Düzenleyici çalışma alanı", + "body": "Bu, ana Kod Düzenleyici yüzeyidir. Dosyaları açın, farkları inceleyin, terminalleri çalıştırın ve oluşturulan değişiklikleri burada inceleyin." + }, + "create-tabs": { + "title": "Yeni sekmeler oluşturun", + "body": "Terminaller, tarayıcı sekmeleri, dosyalar, kontrol panelleri ve depo yardımcı programları oluşturmak için Tüm Sekmeler'deki artı menüsünü kullanın." + }, + "source-control": { + "title": "Git değişiklikleri", + "body": "Değiştirilen dosyaları incelemek, çalışmayı hazırlamak veya geri almak, onaylamak, çekmek, göndermek, göndermek için Kaynak Denetimini açın. getir ve uzaktan kumandayla senkronize et." + }, + "git-history": { + "title": "Git geçmişi", + "body": "Git Geçmişi, Kaynak Kontrolünü taahhüt geçmişi moduna geçirir, böylece önceki taahhütleri ve ilgili değişiklikleri inceleyebilirsiniz." + }, + "dashboard": { + "title": "Kod Düzenleyici kontrol paneli", + "body": "Kod Düzenleyici kontrol paneli, çalışma alanlarına yönelik Kod Düzenleyici ana sekmesidir. Repo eklemek, repo ayrıntılarını açmak ve proje çalışmasına geçmek için bunu kullanın." + } + } + } } } diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index 4e6fa79113..40fb04f829 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -167,6 +167,7 @@ "goToSettings": "Đi đến Cài đặt", "restart": "Khởi động lại", "switchToStation": "Chuyển sang {{station}}", + "switchMethod": "Đổi phương thức", "rate": "Đánh giá", "openInTab": "Mở trong tab", "insert": "Chèn", diff --git a/src/i18n/locales/vi/navigation.json b/src/i18n/locales/vi/navigation.json index 4e55d3dc89..79ec43e0d3 100644 --- a/src/i18n/locales/vi/navigation.json +++ b/src/i18n/locales/vi/navigation.json @@ -118,6 +118,18 @@ "collapseAll": "Thu gọn tất cả", "markAllRead": "Đánh dấu tất cả đã đọc" }, + "guide": { + "trigger": "Mở hướng dẫn", + "title": "Tiếp tục thiết lập", + "startSession": "Bắt đầu phiên", + "setUpTeam": "Thiết lập nhóm", + "manageWork": "Quản lý công việc", + "openTutorials": "Mở hướng dẫn sử dụng", + "quickSetup": "Ngôn ngữ và giao diện", + "close": "Đóng hướng dẫn", + "progressLabel": "Đã hoàn thành {{completed}}/{{total}} bước thiết lập", + "localWorkspace": "Không gian làm việc cục bộ" + }, "search": { "folders": "Tìm kiếm Workspace...", "sessions": "Tìm kiếm phiên...", @@ -139,7 +151,8 @@ "workstation": "Workstation", "viewRam": "Xem RAM", "tutorials": "Hướng dẫn", - "openSettings": "Mở cài đặt" + "openSettings": "Mở cài đặt", + "setupChecklist": "Thiết lập nhanh" }, "empty": { "noItems": "Không có mục nào", diff --git a/src/i18n/locales/vi/onboarding.json b/src/i18n/locales/vi/onboarding.json index 5df458e707..19d1352cd1 100644 --- a/src/i18n/locales/vi/onboarding.json +++ b/src/i18n/locales/vi/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "Bỏ qua thiết lập", "getStarted": "Bắt đầu", "goToSettings": "Đi đến Cài đặt" + }, + "readiness": { + "hero": { + "title": "Hãy thiết lập ORGII của bạn", + "description": "Một vài lựa chọn nhanh để cá nhân hóa không gian làm việc." + }, + "presentation": { + "label": "Xem trước bố cục", + "native": "Giao diện gốc", + "cinematic": "Thẻ điện ảnh", + "classic": "Bảng cổ điển" + }, + "classicPanel": { + "title": "Điều chỉnh ORGII theo bạn", + "description": "Chọn ngôn ngữ, giao diện, chủ đề và màu nhấn phù hợp. Bạn có thể thay đổi bất cứ lúc nào." + }, + "sidebar": { + "brandTag": "Thiết lập", + "subtitle": "Chọn ngôn ngữ và giao diện. Mọi thiết lập khác vẫn có sẵn trong ứng dụng.", + "ariaLabel": "Tiến trình thiết lập", + "stepProgress": "Bước {{current}} / {{total}}", + "saved": "Tiến trình được lưu tự động", + "help": "Các bước đã hoàn thành vẫn có thể chỉnh sửa được. Các bước trong tương lai sẽ mở khóa khi bạn hoàn thành." + }, + "goal": { + "title": "Bạn muốn hoàn thành điều gì trước tiên?", + "description": "ORGII sẽ điều chỉnh quá trình thiết lập và đưa bạn đến bề mặt sản phẩm phù hợp khi bạn hoàn thành.", + "recommended": "Được đề xuất", + "hint": "Điều này thay đổi đường dẫn thiết lập; nó không giới hạn vĩnh viễn những gì bạn có thể sử dụng.", + "personal": { + "title": "Bắt đầu phiên đại lý", + "description": "Thiết lập quy trình mã hóa cá nhân và mở Launchpad." + }, + "team": { + "title": "Xem hoạt động Codex của nhóm", + "description": "Kết nối một tổ chức, chọn khả năng hiển thị kho lưu trữ và mở Hộp thư đến của nhóm." + }, + "work": { + "title": "Quản lý công việc", + "description": "Hiểu rõ về Dự án và Mục công việc, sau đó tạo dự án đầu tiên item." + } + }, + "tools": { + "title": "Phát hiện các công cụ mã hóa của bạn", + "description": "Kiểm tra Codex, Mã Claude và Con trỏ bằng cách sử dụng dịch vụ xác thực cục bộ hiện có.", + "detectedDescription": "Trạng thái đăng ký hoặc chứng chỉ cục bộ", + "found": "Đã tìm thấy {{count}} tài khoản · {{validated}} đã xác thực", + "notFound": "Không phát hiện thấy tài khoản nào có thể sử dụng được", + "notScanned": "Chưa được quét", + "detect": "Công cụ phát hiện", + "importHistory": "Nhập lịch sử Codex", + "historyImported": "{{count}} Phiên Codex có sẵn cục bộ", + "privacy": "Bí mật không bao giờ được truy cập trạng thái thiết lập. Chỉ nhà cung cấp, số lượng và trạng thái xác thực được giữ lại." + }, + "organization": { + "title": "Kết nối tổ chức nhóm", + "description": "Đăng nhập một lần, sau đó chọn một tổ chức hiện có, tạo một tổ chức hoặc tham gia bằng lời mời.", + "signInHint": "Hoạt động nhóm sử dụng tài khoản ORG2 Cloud để các thành viên có chung ranh giới tổ chức.", + "signIn": "Đăng nhập / Đăng ký", + "existing": "Tổ chức của bạn", + "role": "Vai trò: {{role}}", + "mode": "Hành động của tổ chức", + "create": "Tạo tổ chức", + "join": "Tham gia tổ chức", + "orgName": "Tên tổ chức", + "orgNamePlaceholder": "ví dụ: Acme Engineering", + "invite": "Liên kết hoặc mã mời", + "invitePlaceholder": "Dán liên kết mà quản trị viên của bạn đã chia sẻ", + "selected": "{{org}} được chọn để thiết lập nhóm", + "roles": { + "owner": "Chủ sở hữu", + "admin": "Quản trị viên", + "member": "Thành viên", + "guest": "Khách" + } + }, + "sharing": { + "title": "Chọn những gì nhóm có thể thấy", + "description": "Điều khiển từ xa của kho lưu trữ là ranh giới hiển thị. Mức chia sẻ kiểm soát lượng chi tiết phiên được xuất bản.", + "workspace": "Không gian làm việc", + "workspaceDescription": "Thư mục cục bộ nơi tác nhân của bạn chạy", + "noWorkspace": "Không có không gian làm việc nào mở", + "openWorkspace": "Mở không gian làm việc", + "repoScope": "Phạm vi kho lưu trữ", + "repoScopeDescription": "Giải quyết các điều khiển từ xa Git đã chuẩn hóa từ không gian làm việc hiện tại", + "detectScope": "Phát hiện phạm vi kho lưu trữ", + "remote": "Có thể chia sẻ từ xa", + "level": "Mức chia sẻ tối thiểu", + "levelDescription": "Được tổ chức áp dụng cho các phiên khớp", + "off": "Tắt", + "metadata": "Chỉ siêu dữ liệu", + "replay": "Phát lại đầy đủ", + "save": "Lưu và xác minh chính sách", + "createInvite": "Tạo lời mời thành viên", + "copy": "Sao chép", + "verified": "Máy chủ đã chấp nhận chính sách kho lưu trữ này và hàng đợi đồng bộ hóa đã hết.", + "memberHint": "{{org}} là được quản lý bởi một quản trị viên. Lịch sử Codex cục bộ của bạn đã sẵn sàng đồng bộ hóa theo chính sách kho lưu trữ của họ.", + "verifyMember": "Xác minh đường dẫn đồng bộ hóa của tôi", + "memberVerified": "Lựa chọn tổ chức của bạn đang hoạt động và yêu cầu đồng bộ hóa hiện tại đã được loại bỏ." + }, + "basics": { + "title": "Tùy chọn cơ bản", + "description": "Bạn luôn có thể thay đổi sau.", + "workspace": "Không gian làm việc mặc định", + "workspaceDescription": "Các phiên chạy trên các tệp trong một không gian làm việc; các dự án và mục công việc tổ chức công việc thay vì thay thế hệ thống tệp.", + "openWorkspace": "Mở không gian làm việc", + "changeWorkspace": "Thay đổi không gian làm việc", + "settingsHint": "Công cụ, không gian làm việc, tổ chức, khả năng hiển thị nhóm và hướng dẫn có thể được thiết lập sau trong ứng dụng." + }, + "tutorial": { + "title": "Chọn một hướng dẫn theo ngữ cảnh", + "description": "Hướng dẫn này sẽ bắt đầu sau khi thiết lập, trên bề mặt sản phẩm thực nơi có các điều khiển.", + "hint": "Bạn có thể phát lại hướng dẫn sau từ menu Cài đặt." + }, + "model": { + "title": "Cách ORGII hoạt động khớp với nhau", + "description": "Các đối tượng này giải quyết các vấn đề khác nhau; không có cái nào trong số đó là tên khác cho một thư mục.", + "session": { + "title": "Phiên", + "description": "Bối cảnh cuộc trò chuyện và thực thi của một tổng đài viên. Nó có thể tham chiếu một không gian làm việc." + }, + "workItem": { + "title": "Mục công việc", + "description": "Một nhiệm vụ có thể theo dõi với trạng thái, người được giao, cuộc thảo luận và các phiên được liên kết." + }, + "project": { + "title": "Dự án", + "description": "Một vùng chứa lập kế hoạch cho các Mục công việc. Nó có thể điều phối công việc trên các kho lưu trữ." + }, + "workspace": { + "title": "Không gian làm việc / hệ thống tệp", + "description": "Các tác nhân tệp và thư mục cục bộ thực sự đọc và sửa đổi." + }, + "relationship": "Dự án → Mục công việc → Phiên → Tệp không gian làm việc" + }, + "ready": { + "title": "Thiết lập đã sẵn sàng", + "description": "Xem lại các bước kiểm tra lâu dài bên dưới, sau đó mở giao diện phù hợp với mục tiêu ban đầu của bạn.", + "toolsReady": "Đã phát hiện quyền truy cập Agent", + "toolsLater": "Có thể thêm quyền truy cập của nhân viên hỗ trợ sau", + "workspaceReady": "Không gian làm việc đã chọn", + "workspaceLater": "Không gian làm việc có thể được mở tiếp theo", + "teamPolicyReady": "Chính sách hiển thị của nhóm đã được xác nhận", + "memberSyncReady": "Đã xác nhận đường dẫn đồng bộ hóa thành viên", + "teamDestination": "Mở Hộp thư đến của nhóm", + "workDestination": "Tạo Mục công việc đầu tiên", + "personalDestination": "Bắt đầu phiên nhân viên hỗ trợ", + "destinationHint": "Tiến trình thiết lập của bạn vẫn được duy trì sau khi khởi động lại.", + "teamDestinationHint": "Bạn đã chọn Tiến trình tổ chức và thiết lập vẫn được duy trì sau khi khởi động lại.", + "openDestination": "Hoàn tất và mở" + }, + "destinations": { + "teamInbox": "Hộp thư đến của nhóm", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "Trước tiên hãy đăng nhập vào ORG2 Cloud.", + "sessionExpired": "Phiên ORG2 Cloud của bạn đã hết hạn. Đăng nhập lại.", + "invalidInvite": "Liên kết hoặc mã mời này không hợp lệ.", + "rosterNotConverged": "Tổ chức này chưa hiển thị trong danh sách thành viên được làm mới của bạn.", + "cloudUnavailable": "ORG2 Cloud không khả dụng. Hãy thử lại.", + "unknown": "Thao tác thiết lập không thành công. Hãy thử lại.", + "workspaceRequired": "Mở không gian làm việc trước khi định cấu hình phạm vi nhóm.", + "gitRemoteRequired": "Không gian làm việc này không có điều khiển từ xa Git. Thêm điều khiển từ xa rồi thử lại.", + "orgAndScopeRequired": "Trước tiên, hãy chọn tổ chức và phạm vi kho lưu trữ.", + "orgRequired": "Chọn tổ chức trước.", + "policyChanged": "Cài đặt nhóm đã thay đổi trong khi lưu. Hãy xem lại và xác minh lại.", + "inviteOrgChanged": "Tổ chức được chọn đã thay đổi khi tạo lời mời. Hãy thử lại.", + "syncOrgChanged": "Tổ chức được chọn đã thay đổi trong khi xác minh đồng bộ hóa. Hãy thử lại." + } + }, + "tutorials": { + "modalTitle": "Hướng dẫn", + "start": "Bắt đầu", + "chrome": { + "stepProgress": "Bước {{current}} / {{total}}", + "close": "Đóng", + "previous": "Bước trước", + "next": "Bước tiếp theo", + "finish": "Kết thúc chuyến tham quan", + "keyboardHint": "Dùng ← / → hoặc < / >", + "desktop": "Máy tính để bàn", + "myStation": "Trạm của tôi", + "infinity": "Infinity", + "agentStation": "Trạm đại lý" + }, + "generalLayout": { + "title": "Tham quan bố cục chung", + "description": "Tìm hiểu thanh bên Phiên, Bảng trò chuyện, trình chuyển đổi chế độ trạm, Máy trạm, đế và các khu vực ứng dụng.", + "duration": "1 phút", + "steps": { + "chat-panel": { + "title": "Bảng trò chuyện", + "body": "Đây là nơi người dùng trò chuyện với tổng đài viên, xem xét hoạt động và gửi hướng dẫn tiếp theo." + }, + "station-mode-pill": { + "title": "Chuyển trạm chế độ", + "body": "Sử dụng viên thuốc này để chuyển đổi chế độ trạm. Máy tính để bàn có nghĩa là Trạm của tôi, không gian làm việc của bạn. Infinity có nghĩa là Trạm đại lý, chế độ xem hoạt động của đại lý." + }, + "dock": { + "title": "Đế trạm đại lý", + "body": "Đế cắm chuyển đổi các ứng dụng bên trong trạm. Chuyến tham quan tạm thời vô hiệu hóa tính năng tự động ẩn để các điều khiển này vẫn hiển thị." + }, + "all-tabs": { + "title": "Tất cả các tab", + "body": "Biểu tượng dock đầu tiên hiển thị tất cả các tab đang mở cùng nhau, bất kể ứng dụng máy trạm nào sở hữu chúng." + }, + "code-editor": { + "title": "Trình chỉnh sửa mã", + "body": "Sử dụng Trình chỉnh sửa mã cho các tệp, điểm khác biệt, thiết bị đầu cuối, kiểm soát nguồn và các thay đổi mã hóa được thực hiện trong quá trình tham quan phiên." + }, + "browser": { + "title": "Trình duyệt", + "body": "Sử dụng Trình duyệt cho các trang web, bản xem trước, thử nghiệm ứng dụng và điều tra dựa trên trình duyệt cùng với cuộc trò chuyện." + }, + "projects": { + "title": "Dự án", + "body": "Sử dụng Dự án để theo dõi các mục công việc, kế hoạch và trạng thái dự án được kết nối với không gian làm việc hiện tại." + } + } + }, + "codeEditor": { + "title": "Chuyến tham quan Trình chỉnh sửa mã", + "description": "Tìm hiểu các tab, kho lưu trữ và chuyển nhánh, Kiểm soát nguồn, Lịch sử Git và trang tổng quan dự án.", + "duration": "2 phút", + "steps": { + "tabs": { + "title": "tab Trình chỉnh sửa mã", + "body": "Các tab thu thập các tệp, khác biệt, thiết bị đầu cuối, chế độ xem kiểm soát nguồn và trang tổng quan bạn mở khi làm việc trong repo." + }, + "repo-selector": { + "title": "Tạo hoặc chuyển đổi kho lưu trữ", + "body": "Sử dụng bộ chọn kho lưu trữ để chuyển đổi kho lưu trữ đang hoạt động hoặc thêm một kho lưu trữ hoặc không gian làm việc khác." + }, + "branch-selector": { + "title": "Thay đổi nhánh", + "body": "Bộ chọn nhánh hiển thị nhánh Git hiện tại và mở nút chuyển nhánh hoặc tạo luồng cho kho lưu trữ này." + }, + "editor-surface": { + "title": "Không gian làm việc của Trình chỉnh sửa", + "body": "Đây là bề mặt chính của Trình soạn thảo Mã. Mở tệp, kiểm tra các khác biệt, chạy thiết bị đầu cuối và xem xét các thay đổi đã tạo tại đây." + }, + "create-tabs": { + "title": "Tạo tab mới", + "body": "Sử dụng menu dấu cộng trong Tất cả các tab để tạo thiết bị đầu cuối, tab trình duyệt, tệp, trang tổng quan và tiện ích kho lưu trữ." + }, + "source-control": { + "title": "Thay đổi Git", + "body": "Kiểm soát nguồn mở để xem xét các tệp đã thay đổi, giai đoạn hoặc giai đoạn công việc, cam kết, kéo, đẩy, tìm nạp và đồng bộ hóa với từ xa." + }, + "git-history": { + "title": "Lịch sử Git", + "body": "Lịch sử Git chuyển Kiểm soát nguồn sang chế độ lịch sử cam kết để bạn có thể kiểm tra các cam kết trước đó và các thay đổi liên quan." + }, + "dashboard": { + "title": "Trang tổng quan của Code Editor", + "body": "Trang tổng quan là tab trang chủ của Code Editor dành cho không gian làm việc. Sử dụng nó để thêm kho lưu trữ, mở chi tiết kho lưu trữ và bắt tay vào công việc dự án." + } + } + } } } diff --git a/src/i18n/locales/zh-Hant/common.json b/src/i18n/locales/zh-Hant/common.json index d64e7bf0c6..2418a8ab2b 100644 --- a/src/i18n/locales/zh-Hant/common.json +++ b/src/i18n/locales/zh-Hant/common.json @@ -169,6 +169,7 @@ "goToSettings": "前往設置", "restart": "重新啓動", "switchToStation": "切換到{{station}}", + "switchMethod": "切換方式", "rate": "評分", "openInTab": "在標籤頁中打開", "insert": "插入", diff --git a/src/i18n/locales/zh-Hant/navigation.json b/src/i18n/locales/zh-Hant/navigation.json index f3867a6e01..c72d1f784c 100644 --- a/src/i18n/locales/zh-Hant/navigation.json +++ b/src/i18n/locales/zh-Hant/navigation.json @@ -119,6 +119,18 @@ "collapseAll": "全部摺疊", "markAllRead": "全部標為已讀" }, + "guide": { + "trigger": "開啟使用引導", + "title": "繼續設定", + "startSession": "開始會話", + "setUpTeam": "設定團隊", + "manageWork": "管理工作", + "openTutorials": "開啟頁面教學", + "quickSetup": "語言與外觀", + "close": "關閉引導", + "progressLabel": "已完成 {{completed}}/{{total}} 項設定", + "localWorkspace": "本機工作區" + }, "search": { "folders": "搜尋工作區...", "sessions": "搜尋會話...", @@ -140,7 +152,8 @@ "workstation": "工作站", "viewRam": "查看 RAM", "tutorials": "教學", - "openSettings": "開啟設定" + "openSettings": "開啟設定", + "setupChecklist": "快速設定" }, "empty": { "noItems": "暫無內容", diff --git a/src/i18n/locales/zh-Hant/onboarding.json b/src/i18n/locales/zh-Hant/onboarding.json index 707b9c08ec..8d6de5c7b8 100644 --- a/src/i18n/locales/zh-Hant/onboarding.json +++ b/src/i18n/locales/zh-Hant/onboarding.json @@ -70,5 +70,265 @@ "skipSetup": "跳過設置", "getStarted": "開始使用", "goToSettings": "前往設置" + }, + "readiness": { + "hero": { + "title": "一起設定你的 ORGII", + "description": "透過幾個快速選項,打造更適合你的工作空間。" + }, + "presentation": { + "label": "預覽樣式", + "native": "App 原生", + "cinematic": "沉浸卡片", + "classic": "經典面板" + }, + "classicPanel": { + "title": "讓 ORGII 更適合你", + "description": "選擇順手的語言、外觀、主題和強調色;這些設定之後都可以隨時修改。" + }, + "sidebar": { + "brandTag": "設定", + "subtitle": "選擇語言與外觀即可開始使用,其他設定之後仍可在應用程式內完成。", + "ariaLabel": "設定進度", + "stepProgress": "第 {{current}} / {{total}} 步", + "saved": "進度已自動儲存", + "help": "已完成步驟可隨時返回;後續步驟會按完成進度解鎖。" + }, + "goal": { + "title": "你首先想完成什麼?", + "description": "ORGII 會據此調整設定流程,並在完成後帶你到正確的產品頁面。", + "recommended": "推薦", + "hint": "這個選擇只調整首次設定路徑,不會永久限制你能使用的功能。", + "personal": { + "title": "啟動 Agent 會話", + "description": "完成個人編碼工作流程設定並開啟 Launchpad。" + }, + "team": { + "title": "查看團隊 Codex 活動", + "description": "連結組織、設定倉庫可見性,然後開啟 Team Inbox。" + }, + "work": { + "title": "管理工作", + "description": "瞭解專案和工作項,然後建立第一個工作項。" + } + }, + "tools": { + "title": "偵測你的程式設計工具", + "description": "使用現有本機驗證服務檢查 Codex、Claude Code 和 Cursor。", + "detectedDescription": "本機憑證或訂閱狀態", + "found": "發現 {{count}} 個帳戶 · {{validated}} 個已驗證", + "notFound": "沒有偵測到可用帳戶", + "notScanned": "尚未偵測", + "detect": "偵測工具", + "importHistory": "匯入 Codex 歷史", + "historyImported": "本機已有 {{count}} 條 Codex 會話可用", + "privacy": "金鑰不會進入設定狀態;只保留工具、數量和驗證結果。" + }, + "organization": { + "title": "連結團隊組織", + "description": "登入一次,然後選擇現有組織、建立組織,或透過邀請加入。", + "signInHint": "團隊活動使用 ORG2 Cloud 帳戶,讓成員處於同一個組織邊界。", + "signIn": "登入 / 註冊", + "existing": "你的組織", + "role": "角色:{{role}}", + "mode": "組織作業", + "create": "建立組織", + "join": "加入組織", + "orgName": "組織名稱", + "orgNamePlaceholder": "例如:Acme Engineering", + "invite": "邀請連結或邀請碼", + "invitePlaceholder": "貼上管理員分享的連結", + "selected": "已選擇 {{org}} 用於團隊設定", + "roles": { + "owner": "所有者", + "admin": "管理員", + "member": "成員", + "guest": "訪客" + } + }, + "sharing": { + "title": "選擇團隊能看到什麼", + "description": "Git 遠端倉庫是可見性邊界;分享等級決定發布多少會話細節。", + "workspace": "Workspace", + "workspaceDescription": "Agent 實際運作的本機目錄", + "noWorkspace": "尚未開啟 Workspace", + "openWorkspace": "開啟 Workspace", + "repoScope": "倉庫範圍", + "repoScopeDescription": "從目前 Workspace 解析規範化的 Git remote", + "detectScope": "偵測倉庫範圍", + "remote": "可分享 remote", + "level": "最低分享等級", + "levelDescription": "套用於該組織中符合倉庫的會話", + "off": "關閉", + "metadata": "僅元資料", + "replay": "完整回放", + "save": "儲存並驗證策略", + "createInvite": "建立成員邀請", + "copy": "複製", + "verified": "服務端已接受倉庫策略,同步佇列也已完成本輪處理。", + "memberHint": "{{org}} 的策略由管理者管理;你的本機 Codex 歷史會依照管理員設定的倉庫策略同步。", + "verifyMember": "驗證我的同步連結", + "memberVerified": "組織選擇已生效,本輪同步請求已處理完畢。" + }, + "basics": { + "title": "基礎偏好", + "description": "之後隨時都可以變更。", + "workspace": "預設 Workspace", + "workspaceDescription": "會話在 Workspace 檔案中運作;專案和工作項目用於組織工作,不會取代檔案系統。", + "openWorkspace": "開啟 Workspace", + "changeWorkspace": "切換 Workspace", + "settingsHint": "工具、Workspace、組織、團隊可見性和教學都可以之後在應用程式內按需設定。" + }, + "tutorial": { + "title": "選擇一個頁面教學", + "description": "設定完成後,教學會在控制項真實存在的產品頁面中啟動。", + "hint": "之後也可以從設定選單重複播放任意教學。" + }, + "model": { + "title": "ORGII 的工作物件如何配合", + "description": "這些物件解決不同問題;它們都不是資料夾的另一種叫法。", + "session": { + "title": "會話 Session", + "description": "Agent 的對話與執行上下文,可以關聯一個 Workspace。" + }, + "workItem": { + "title": "工作項 Work Item", + "description": "可追蹤的任務,包含狀態、負責人、討論和關聯會話。" + }, + "project": { + "title": "專案 Project", + "description": "工作項目的規劃容器,也可以協調跨倉庫工作。" + }, + "workspace": { + "title": "Workspace / 檔案系統", + "description": "Agent 實際讀取和修改的本機目錄與檔案。" + }, + "relationship": "專案 → 工作項 → 會話 → Workspace 檔案" + }, + "ready": { + "title": "設定已準備好", + "description": "檢查下面已落盤的結果,然後開啟與你最初目標一致的頁面。", + "toolsReady": "已偵測 Agent 權限", + "toolsLater": "可以稍後新增 Agent 權限", + "workspaceReady": "已選擇 Workspace", + "workspaceLater": "下一步可開啟 Workspace", + "teamPolicyReady": "團隊可見性策略已確認", + "memberSyncReady": "成員同步連結已確認", + "teamDestination": "開啟 Team Inbox", + "workDestination": "建立第一個工作項目", + "personalDestination": "啟動 Agent 會話", + "destinationHint": "設定進度在重新啟動後仍會保留。", + "teamDestinationHint": "所選的組織和設定進度在重新啟動後仍會保留。", + "openDestination": "完成並開啟" + }, + "destinations": { + "teamInbox": "Team Inbox", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "請先登入 ORG2 Cloud。", + "sessionExpired": "ORG2 Cloud 登入已過期,請重新登入。", + "invalidInvite": "邀請連結或邀請碼無效。", + "rosterNotConverged": "刷新後的組織清單中暫時還沒有該組織。", + "cloudUnavailable": "ORG2 Cloud 暫時無法使用,請重試。", + "unknown": "設定操作失敗,請重試。", + "workspaceRequired": "設定團隊範圍前,請先開啟一個 Workspace。", + "gitRemoteRequired": "目前 Workspace 沒有 Git remote,請新增後重試。", + "orgAndScopeRequired": "請先選擇組織和倉庫範圍。", + "orgRequired": "請先選擇組織。", + "policyChanged": "儲存期間團隊設定發生變化,請檢查後重新驗證。", + "inviteOrgChanged": "建立邀請期間所選組織發生變化,請重試。", + "syncOrgChanged": "驗證同步期間所選組織發生變化,請重試。" + } + }, + "tutorials": { + "modalTitle": "頁面教學", + "start": "開始", + "chrome": { + "stepProgress": "第 {{current}} / {{total}} 步", + "close": "關閉", + "previous": "上一步", + "next": "下一步", + "finish": "完成教學", + "keyboardHint": "使用 ← / → 或 < / >", + "desktop": "桌面", + "myStation": "我的工作站", + "infinity": "無限", + "agentStation": "Agent 工作站" + }, + "generalLayout": { + "title": "整體佈局教學", + "description": "了解會話側欄、聊天面板、工作站模式切換、Workstation、Dock 與應用程式區域。", + "duration": "1 分鐘", + "steps": { + "chat-panel": { + "title": "聊天面板", + "body": "在這裡與 Agent 對話、查看活動,並發送後續指示。" + }, + "station-mode-pill": { + "title": "切換工作站模式", + "body": "使用此按鈕切換工作站模式。桌面代表“我的工作站”,也就是你的 Workspace;無限代表“Agent 工作站”,用於查看 Agent 活動。" + }, + "dock": { + "title": "Agent 工作站 Dock", + "body": "Dock 用於切換工作站內的應用。教學期間會暫時關閉自動隱藏,讓這些控制項保持可見。" + }, + "all-tabs": { + "title": "全部標籤頁", + "body": "第一個 Dock 圖示會集中顯示所有已開啟的標籤頁,不受其所屬工作站應用程式限制。" + }, + "code-editor": { + "title": "程式碼編輯器", + "body": "在程式碼編輯器中處理檔案、Diff、終端機、原始碼管理,以及在會話中產生的程式碼變更。" + }, + "browser": { + "title": "瀏覽器", + "body": "在聊天的同時使用瀏覽器查看網頁與預覽、測試應用,並進行基於瀏覽器的調查。" + }, + "projects": { + "title": "專案", + "body": "使用專案追蹤與目前 Workspace 相關的工作項、計畫和專案狀態。" + } + } + }, + "codeEditor": { + "title": "程式碼編輯器教學", + "description": "了解標籤頁、倉庫與分支切換、原始碼管理、Git 歷史和專案看板。", + "duration": "2 分鐘", + "steps": { + "tabs": { + "title": "程式碼編輯器標籤頁", + "body": "標籤頁集中承載你在倉庫工作時開啟的檔案、Diff、終端機、原始碼管理檢視和看板。" + }, + "repo-selector": { + "title": "建立或切換倉庫", + "body": "使用倉庫選擇器切換目前倉庫,或新增另一個倉庫或 Workspace。" + }, + "branch-selector": { + "title": "切換分支", + "body": "分支選擇器會顯示目前 Git 分支,並開啟該倉庫的分支切換或建立流程。" + }, + "editor-surface": { + "title": "編輯器 Workspace", + "body": "這是程式碼編輯器的主要工作區。你可以在這裡開啟檔案、檢查 Diff、執行終端並審核產生的變更。" + }, + "create-tabs": { + "title": "建立新標籤頁", + "body": "使用「全部標籤頁」中的加號選單建立終端機、瀏覽器標籤頁、檔案、看板和倉庫工具。" + }, + "source-control": { + "title": "Git 變更", + "body": "開啟原始碼管理以查看變更檔案、暫存或取消暫存、提交,以及執行拉取、推送、取得和遠端同步。" + }, + "git-history": { + "title": "Git 歷史", + "body": "Git 歷史會把原始碼管理切換到提交歷史模式,供你檢查過去的提交及相關變更。" + }, + "dashboard": { + "title": "程式碼編輯器看板", + "body": "看板是程式碼編輯器面向 Workspace 的首頁標籤頁,可用於新增倉庫、查看倉庫詳情並進入專案工作。" + } + } + } } } diff --git a/src/i18n/locales/zh/common.json b/src/i18n/locales/zh/common.json index 16ca9ac761..d21ce538bd 100644 --- a/src/i18n/locales/zh/common.json +++ b/src/i18n/locales/zh/common.json @@ -169,6 +169,7 @@ "goToSettings": "前往设置", "restart": "重新启动", "switchToStation": "切换到{{station}}", + "switchMethod": "切换方式", "rate": "评分", "openInTab": "在标签页中打开", "insert": "插入", diff --git a/src/i18n/locales/zh/navigation.json b/src/i18n/locales/zh/navigation.json index 243fb3f7ea..c6c9cb0b80 100644 --- a/src/i18n/locales/zh/navigation.json +++ b/src/i18n/locales/zh/navigation.json @@ -119,6 +119,18 @@ "collapseAll": "全部折叠", "markAllRead": "全部标为已读" }, + "guide": { + "trigger": "打开使用引导", + "title": "继续设置", + "startSession": "开始会话", + "setUpTeam": "设置团队", + "manageWork": "管理工作", + "openTutorials": "打开页面教程", + "quickSetup": "语言与外观", + "close": "关闭引导", + "progressLabel": "已完成 {{completed}}/{{total}} 项设置", + "localWorkspace": "本地工作区" + }, "search": { "folders": "搜索工作区...", "sessions": "搜索会话...", @@ -139,6 +151,7 @@ "language": "语言", "workstation": "工作站", "viewRam": "查看 RAM", + "setupChecklist": "快速设置", "tutorials": "教程", "openSettings": "打开设置" }, diff --git a/src/i18n/locales/zh/onboarding.json b/src/i18n/locales/zh/onboarding.json index ab3b475b43..8abf63028a 100644 --- a/src/i18n/locales/zh/onboarding.json +++ b/src/i18n/locales/zh/onboarding.json @@ -22,6 +22,38 @@ "complete": { "title": "完成", "description": "准备就绪" + }, + "goal": { + "title": "目标", + "description": "选择第一次要完成的事" + }, + "tools": { + "title": "工具与密钥", + "description": "检测本机 Agent 权限" + }, + "organization": { + "title": "组织", + "description": "创建、加入或选择" + }, + "sharing": { + "title": "团队可见性", + "description": "选择范围和详细程度" + }, + "basics": { + "title": "基础设置", + "description": "Workspace 与外观" + }, + "tutorial": { + "title": "页面教程", + "description": "在真实页面中学习" + }, + "workModel": { + "title": "工作关系", + "description": "会话、工作项与项目" + }, + "ready": { + "title": "准备完成", + "description": "验证并打开正确页面" } }, "welcome": { @@ -70,5 +102,265 @@ "skipSetup": "跳过设置", "getStarted": "开始使用", "goToSettings": "前往设置" + }, + "readiness": { + "hero": { + "title": "一起设置你的 ORGII", + "description": "通过几个快速选项,打造更适合你的工作空间。" + }, + "presentation": { + "label": "预览样式", + "native": "App 原生", + "cinematic": "沉浸卡片", + "classic": "经典面板" + }, + "classicPanel": { + "title": "让 ORGII 更适合你", + "description": "选择顺手的语言、外观、主题和强调色;这些设置之后都可以随时修改。" + }, + "sidebar": { + "brandTag": "设置", + "subtitle": "选择语言与外观即可开始使用,其他设置之后仍可在应用内完成。", + "ariaLabel": "设置进度", + "stepProgress": "第 {{current}} / {{total}} 步", + "saved": "进度已自动保存", + "help": "已完成步骤可随时返回;后续步骤会按完成进度解锁。" + }, + "goal": { + "title": "你首先想完成什么?", + "description": "ORGII 会据此调整设置流程,并在完成后把你带到正确的产品页面。", + "recommended": "推荐", + "hint": "这个选择只调整首次设置路径,不会永久限制你能使用的功能。", + "personal": { + "title": "启动 Agent 会话", + "description": "完成个人编码工作流设置并打开 Launchpad。" + }, + "team": { + "title": "查看团队 Codex 活动", + "description": "连接组织、设置仓库可见性,然后打开 Team Inbox。" + }, + "work": { + "title": "管理工作", + "description": "理解项目和工作项,然后创建第一个工作项。" + } + }, + "tools": { + "title": "检测你的编码工具", + "description": "使用现有本机验证服务检查 Codex、Claude Code 和 Cursor。", + "detectedDescription": "本机凭证或订阅状态", + "found": "发现 {{count}} 个账户 · {{validated}} 个已验证", + "notFound": "没有检测到可用账户", + "notScanned": "尚未检测", + "detect": "检测工具", + "importHistory": "导入 Codex 历史", + "historyImported": "本机已有 {{count}} 条 Codex 会话可用", + "privacy": "密钥不会进入设置状态;只保留工具、数量和验证结果。" + }, + "organization": { + "title": "连接团队组织", + "description": "登录一次,然后选择现有组织、创建组织,或通过邀请加入。", + "signInHint": "团队活动使用 ORG2 Cloud 账户,让成员处于同一个组织边界中。", + "signIn": "登录 / 注册", + "existing": "你的组织", + "role": "角色:{{role}}", + "mode": "组织操作", + "create": "创建组织", + "join": "加入组织", + "orgName": "组织名称", + "orgNamePlaceholder": "例如:Acme Engineering", + "invite": "邀请链接或邀请码", + "invitePlaceholder": "粘贴管理员分享的链接", + "selected": "已选择 {{org}} 用于团队设置", + "roles": { + "owner": "所有者", + "admin": "管理员", + "member": "成员", + "guest": "访客" + } + }, + "sharing": { + "title": "选择团队能看到什么", + "description": "Git 远程仓库是可见性边界;分享等级决定发布多少会话细节。", + "workspace": "Workspace", + "workspaceDescription": "Agent 实际运行的本地目录", + "noWorkspace": "尚未打开 Workspace", + "openWorkspace": "打开 Workspace", + "repoScope": "仓库范围", + "repoScopeDescription": "从当前 Workspace 解析规范化的 Git remote", + "detectScope": "检测仓库范围", + "remote": "可分享 remote", + "level": "最低分享等级", + "levelDescription": "应用于该组织中匹配仓库的会话", + "off": "关闭", + "metadata": "仅元数据", + "replay": "完整回放", + "save": "保存并验证策略", + "createInvite": "创建成员邀请", + "copy": "复制", + "verified": "服务端已接受仓库策略,同步队列也已完成本轮处理。", + "memberHint": "{{org}} 的策略由管理员管理;你的本机 Codex 历史会按管理员配置的仓库策略同步。", + "verifyMember": "验证我的同步链路", + "memberVerified": "组织选择已生效,本轮同步请求已处理完毕。" + }, + "basics": { + "title": "基础偏好", + "description": "之后随时可以更改。", + "workspace": "默认 Workspace", + "workspaceDescription": "会话在 Workspace 文件中运行;项目和工作项用于组织工作,并不会替代文件系统。", + "openWorkspace": "打开 Workspace", + "changeWorkspace": "切换 Workspace", + "settingsHint": "工具、Workspace、组织、团队可见性和教程都可以之后在应用内按需设置。" + }, + "tutorial": { + "title": "选择一个页面教程", + "description": "设置完成后,教程会在控件真实存在的产品页面中启动。", + "hint": "之后也可以从设置菜单重复播放任意教程。" + }, + "model": { + "title": "ORGII 的工作对象如何配合", + "description": "这些对象解决不同问题;它们都不是文件夹的另一种叫法。", + "session": { + "title": "会话 Session", + "description": "Agent 的对话与执行上下文,可以关联一个 Workspace。" + }, + "workItem": { + "title": "工作项 Work Item", + "description": "可跟踪的任务,包含状态、负责人、讨论和关联会话。" + }, + "project": { + "title": "项目 Project", + "description": "工作项的规划容器,也可以协调跨仓库工作。" + }, + "workspace": { + "title": "Workspace / 文件系统", + "description": "Agent 实际读取和修改的本地目录与文件。" + }, + "relationship": "项目 → 工作项 → 会话 → Workspace 文件" + }, + "ready": { + "title": "设置已准备好", + "description": "检查下面已落盘的结果,然后打开与你最初目标一致的页面。", + "toolsReady": "已检测 Agent 权限", + "toolsLater": "可以稍后添加 Agent 权限", + "workspaceReady": "已选择 Workspace", + "workspaceLater": "下一步可打开 Workspace", + "teamPolicyReady": "团队可见性策略已确认", + "memberSyncReady": "成员同步链路已确认", + "teamDestination": "打开 Team Inbox", + "workDestination": "创建第一个工作项", + "personalDestination": "启动 Agent 会话", + "destinationHint": "设置进度在重启后仍会保留。", + "teamDestinationHint": "选择的组织和设置进度在重启后仍会保留。", + "openDestination": "完成并打开" + }, + "destinations": { + "teamInbox": "Team Inbox", + "launchpad": "Launchpad" + }, + "errors": { + "signInRequired": "请先登录 ORG2 Cloud。", + "sessionExpired": "ORG2 Cloud 登录已过期,请重新登录。", + "invalidInvite": "邀请链接或邀请码无效。", + "rosterNotConverged": "刷新后的组织列表中暂时还没有该组织。", + "cloudUnavailable": "ORG2 Cloud 暂时不可用,请重试。", + "unknown": "设置操作失败,请重试。", + "workspaceRequired": "配置团队范围前,请先打开一个 Workspace。", + "gitRemoteRequired": "当前 Workspace 没有 Git remote,请添加后重试。", + "orgAndScopeRequired": "请先选择组织和仓库范围。", + "orgRequired": "请先选择组织。", + "policyChanged": "保存期间团队设置发生变化,请检查后重新验证。", + "inviteOrgChanged": "创建邀请期间所选组织发生变化,请重试。", + "syncOrgChanged": "验证同步期间所选组织发生变化,请重试。" + } + }, + "tutorials": { + "modalTitle": "页面教程", + "start": "开始", + "chrome": { + "stepProgress": "第 {{current}} / {{total}} 步", + "close": "关闭", + "previous": "上一步", + "next": "下一步", + "finish": "完成教程", + "keyboardHint": "使用 ← / → 或 < / >", + "desktop": "桌面", + "myStation": "我的工作站", + "infinity": "无限", + "agentStation": "Agent 工作站" + }, + "generalLayout": { + "title": "整体布局教程", + "description": "了解会话侧栏、聊天面板、工作站模式切换、Workstation、Dock 与应用区域。", + "duration": "1 分钟", + "steps": { + "chat-panel": { + "title": "聊天面板", + "body": "在这里与 Agent 对话、查看活动,并发送后续指令。" + }, + "station-mode-pill": { + "title": "切换工作站模式", + "body": "使用此按钮切换工作站模式。桌面代表“我的工作站”,也就是你的 Workspace;无限代表“Agent 工作站”,用于查看 Agent 活动。" + }, + "dock": { + "title": "Agent 工作站 Dock", + "body": "Dock 用于切换工作站内的应用。教程期间会暂时关闭自动隐藏,让这些控件保持可见。" + }, + "all-tabs": { + "title": "全部标签页", + "body": "第一个 Dock 图标会集中显示所有已打开的标签页,不受其所属工作站应用限制。" + }, + "code-editor": { + "title": "代码编辑器", + "body": "在代码编辑器中处理文件、Diff、终端、源代码管理,以及会话中产生的代码变更。" + }, + "browser": { + "title": "浏览器", + "body": "在聊天的同时使用浏览器查看网页与预览、测试应用,并开展基于浏览器的调查。" + }, + "projects": { + "title": "项目", + "body": "使用项目跟踪与当前 Workspace 相关的工作项、计划和项目状态。" + } + } + }, + "codeEditor": { + "title": "代码编辑器教程", + "description": "了解标签页、仓库与分支切换、源代码管理、Git 历史和项目看板。", + "duration": "2 分钟", + "steps": { + "tabs": { + "title": "代码编辑器标签页", + "body": "标签页集中承载你在仓库工作时打开的文件、Diff、终端、源代码管理视图和看板。" + }, + "repo-selector": { + "title": "创建或切换仓库", + "body": "使用仓库选择器切换当前仓库,或添加另一个仓库或 Workspace。" + }, + "branch-selector": { + "title": "切换分支", + "body": "分支选择器会显示当前 Git 分支,并打开该仓库的分支切换或创建流程。" + }, + "editor-surface": { + "title": "编辑器 Workspace", + "body": "这是代码编辑器的主要工作区。你可以在这里打开文件、检查 Diff、运行终端并审核生成的变更。" + }, + "create-tabs": { + "title": "创建新标签页", + "body": "使用“全部标签页”中的加号菜单创建终端、浏览器标签页、文件、看板和仓库工具。" + }, + "source-control": { + "title": "Git 变更", + "body": "打开源代码管理以查看变更文件、暂存或取消暂存、提交,以及执行拉取、推送、获取和远程同步。" + }, + "git-history": { + "title": "Git 历史", + "body": "Git 历史会把源代码管理切换到提交历史模式,供你检查过去的提交及相关变更。" + }, + "dashboard": { + "title": "代码编辑器看板", + "body": "看板是代码编辑器面向 Workspace 的主页标签页,可用于添加仓库、查看仓库详情并进入项目工作。" + } + } + } } } diff --git a/src/modules/SetupWalkthrough/TEST_CASES.md b/src/modules/SetupWalkthrough/TEST_CASES.md new file mode 100644 index 0000000000..6e096bbe06 --- /dev/null +++ b/src/modules/SetupWalkthrough/TEST_CASES.md @@ -0,0 +1,65 @@ +# Quick setup acceptance cases + +## Outcome and ownership + +- First-run outcome: choose language and appearance, then enter the product + without completing an account, organization, workspace, or tutorial wizard. +- The canonical language and appearance settings atoms own each preference; + quick setup does not create a draft or duplicate source of truth. +- The presentation selector is component-local preview state. It defaults to + the App-native version on every mount and never writes a product setting. +- Settings owns the terminal `open | completed | dismissed` outcome and the + secret-free setup progress compatibility object. +- Tools, credentials, workspaces, organizations, team visibility, work items, + and tutorials remain owned by their existing product surfaces. + +## State machine + +| State | User event | Visible result | Persisted effect | Next state | +| ------- | ------------------------------ | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------- | +| Editing | Change language | The hero, settings section, labels, and actions update locale immediately | Canonical language setting is queued for disk | Editing | +| Editing | Change appearance/theme/accent | The ambient scene and canonical settings controls preview the selection | Canonical appearance setting is queued for disk | Editing | +| Editing | Switch presentation | App-native, cinematic, and classic forms swap in place with the same values | None; this is local preview state | Editing | +| Editing | Get Started | Both actions remain visible; the primary action enters loading | Outcome and compatibility progress persist in one batch after earlier preference writes | Workstation | +| Editing | Skip Setup | Both terminal actions remain visible and disabled while the save completes | Dismissed outcome and unchanged compatibility progress persist in one batch | Workstation | +| Closing | Repeated finish/skip | No second operation starts | No duplicate completion write | Closing | +| Closing | Save fails | Inline app message reports failure; chosen preferences remain visible | Outcome is not published as completed/dismissed | Editing | + +## Behavioral matrix + +| Case | Expected result | +| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| New install | Quick setup opens automatically with valid defaults; there is no step count, progress bar, or staged navigation. | +| Existing install opens Quick setup | Current language, theme, and primary color hydrate from canonical Settings values. | +| Change language | The whole surface updates locale immediately and the preference survives restart. | +| Change appearance mode | The canonical theme transition runs; the matching theme preset and default accent update consistently. | +| Change theme preset | Theme CSS loads before the canonical theme/accent batch is published. | +| Change primary color | The existing primary-color atom applies and persists the selection immediately. | +| Switch to cinematic | The historical immersive card renders with the same language, appearance, theme, and color bindings. | +| Switch to classic | The branded vertical panel renders full-width controls with the same language, appearance, theme, color, and terminal actions. | +| Switch back to App native | The canonical Settings form returns without resetting a preference or terminal state. | +| Reopen after selecting cinematic | The selector returns to App native; only actual preference values hydrate from Settings. | +| Finish immediately after a preference change | The settings write queue orders preference writes before the atomic completion batch. | +| Finish twice | The closing ref prevents duplicate completion writes and navigation. | +| Completion write fails | The user stays on quick setup and can retry; the terminal outcome does not diverge from progress. | +| Skip | No preference is reset; the dismissed outcome closes automatic first-run setup. | +| Reopen from Settings | The localized “Quick setup” menu item opens the same surface with current preferences. | +| Hidden test shortcut | `⌘⌥O` / `Ctrl+Alt+O` atomically resets setup-owned outcome/progress and opens quick setup without changing product data. | +| Optional setup | Tools, workspaces, organizations, team visibility, and tutorials remain available later from their existing app surfaces. | +| Responsive layout | Wide desktop uses the cinematic hero + canonical settings section; constrained widths hide the decorative hero and preserve a compact ORGII brand header above the form. | +| UI consistency | App native uses unmodified Settings/Wizard components and shared tokens. Cinematic is isolated historical styling; classic composes vertical `SectionRow`s. All share controls, state, and completion. | +| Reduced motion | The mascot float animation is disabled when the OS requests reduced motion. | +| Accessibility | Every select trigger has a localized accessible name; the decorative mascot/planet are hidden from assistive technology and terminal actions retain native buttons. | +| Supported locale | Quick-setup copy and the Settings navigation entry exist in all 13 supported locales with matching key/interpolation shape. | +| Legacy progress | Completing quick setup retains existing optional setup fields and adds the idempotent `preferences` completion marker. | + +## Verification + +- Unit: `__tests__/preferenceSetup.test.ts`, `__tests__/testShortcut.test.ts`, + `__tests__/i18n.test.ts`, `SetupWalkthroughSidebar.test.ts`, setup + navigation/settings tests, and `settingsAtom.atomic.test.ts`. +- Visual: desktop app opened through the hidden shortcut in dark and light + themes; verify all three dropdown-selectable presentations, shared values, four + preference rows, constrained-width fallback, no step UI, and stable loading state. +- Static gates: TypeScript typecheck, formatting, focused Vitest coverage, and + `git diff --check`. diff --git a/src/modules/SetupWalkthrough/__tests__/flow.test.ts b/src/modules/SetupWalkthrough/__tests__/flow.test.ts new file mode 100644 index 0000000000..fbbaf6039e --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/flow.test.ts @@ -0,0 +1,162 @@ +import { describe, expect, it } from "vitest"; + +import { DEFAULT_SETUP_WALKTHROUGH_PROGRESS } from "@src/config/settingsSchema/setupWalkthroughProgress"; + +import { + advanceSetupProgress, + applySetupOrganizationSelection, + canCompleteSetupStep, + canNavigateToSetupStep, + captureSetupTeamPolicy, + getVisibleSetupStepIds, + setupTeamPolicyMatches, +} from "../flow"; + +describe("setup walkthrough flow", () => { + it("keeps personal setup focused while team setup includes governance", () => { + const focusedPath = [ + "goal", + "tools", + "basics", + "tutorial", + "work-model", + "ready", + ]; + + expect( + getVisibleSetupStepIds({ + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + goal: "personal", + }) + ).toEqual(focusedPath); + expect( + getVisibleSetupStepIds({ + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + goal: "work_management", + }) + ).toEqual(focusedPath); + expect( + getVisibleSetupStepIds({ + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + goal: "team_activity", + }) + ).toEqual([ + "goal", + "tools", + "organization", + "sharing", + "basics", + "tutorial", + "work-model", + "ready", + ]); + }); + + it("does not advance until the current postcondition is true", () => { + const blocked = { + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + goal: "team_activity" as const, + currentStepId: "organization", + }; + expect(advanceSetupProgress(blocked)).toBe(blocked); + expect( + advanceSetupProgress({ ...blocked, selectedOrgId: "org-1" }).currentStepId + ).toBe("sharing"); + }); + + it("requires an admin policy commit and a member sync check", () => { + const base = { + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + goal: "team_activity" as const, + selectedOrgId: "org-1", + }; + expect( + canCompleteSetupStep({ ...base, selectedOrgRole: "owner" }, "sharing") + ).toBe(false); + expect( + canCompleteSetupStep({ ...base, selectedOrgRole: "member" }, "sharing") + ).toBe(false); + expect( + canCompleteSetupStep( + { + ...base, + selectedOrgRole: "member", + verifiedAt: Date.now(), + }, + "sharing" + ) + ).toBe(true); + }); + + it("blocks jumping over unfinished steps", () => { + const progress = { + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + goal: "personal" as const, + }; + expect(canNavigateToSetupStep(progress, "ready")).toBe(false); + expect(canNavigateToSetupStep(progress, "goal")).toBe(true); + }); + + it("preserves a same-org draft and hydrates a newly selected org", () => { + const progress = { + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + selectedOrgId: "org-1", + selectedOrgName: "Old name", + repoScopes: ["github.com/acme/app"], + sharingFloor: "full_replay" as const, + inviteLink: "invite", + verifiedAt: 123, + }; + + expect( + applySetupOrganizationSelection(progress, { + orgId: "org-1", + name: "Renamed", + role: "owner", + repoScopes: [], + sharingFloor: "off", + }) + ).toMatchObject({ + selectedOrgName: "Renamed", + repoScopes: ["github.com/acme/app"], + sharingFloor: "full_replay", + inviteLink: "invite", + verifiedAt: 123, + }); + + expect( + applySetupOrganizationSelection(progress, { + orgId: "org-2", + name: "Second org", + role: "member", + repoScopes: ["github.com/acme/other"], + sharingFloor: "metadata_only", + }) + ).toMatchObject({ + selectedOrgId: "org-2", + repoScopes: ["github.com/acme/other"], + sharingFloor: "metadata_only", + inviteLink: null, + verifiedAt: null, + }); + }); + + it("rejects a stale team-policy completion", () => { + const progress = { + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + selectedOrgId: "org-1", + repoScopes: ["github.com/acme/app"], + sharingFloor: "metadata_only" as const, + }; + const snapshot = captureSetupTeamPolicy(progress); + + expect(snapshot).not.toBeNull(); + expect(setupTeamPolicyMatches(progress, snapshot!)).toBe(true); + expect( + setupTeamPolicyMatches( + { ...progress, sharingFloor: "full_replay" }, + snapshot! + ) + ).toBe(false); + }); +}); diff --git a/src/modules/SetupWalkthrough/__tests__/i18n.test.ts b/src/modules/SetupWalkthrough/__tests__/i18n.test.ts new file mode 100644 index 0000000000..7b8ff9f3f8 --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/i18n.test.ts @@ -0,0 +1,169 @@ +import { describe, expect, it } from "vitest"; + +import deNavigation from "@src/i18n/locales/de/navigation.json"; +import deOnboarding from "@src/i18n/locales/de/onboarding.json"; +import enNavigation from "@src/i18n/locales/en/navigation.json"; +import enOnboarding from "@src/i18n/locales/en/onboarding.json"; +import esNavigation from "@src/i18n/locales/es/navigation.json"; +import esOnboarding from "@src/i18n/locales/es/onboarding.json"; +import frNavigation from "@src/i18n/locales/fr/navigation.json"; +import frOnboarding from "@src/i18n/locales/fr/onboarding.json"; +import jaNavigation from "@src/i18n/locales/ja/navigation.json"; +import jaOnboarding from "@src/i18n/locales/ja/onboarding.json"; +import koNavigation from "@src/i18n/locales/ko/navigation.json"; +import koOnboarding from "@src/i18n/locales/ko/onboarding.json"; +import plNavigation from "@src/i18n/locales/pl/navigation.json"; +import plOnboarding from "@src/i18n/locales/pl/onboarding.json"; +import ptNavigation from "@src/i18n/locales/pt/navigation.json"; +import ptOnboarding from "@src/i18n/locales/pt/onboarding.json"; +import ruNavigation from "@src/i18n/locales/ru/navigation.json"; +import ruOnboarding from "@src/i18n/locales/ru/onboarding.json"; +import trNavigation from "@src/i18n/locales/tr/navigation.json"; +import trOnboarding from "@src/i18n/locales/tr/onboarding.json"; +import viNavigation from "@src/i18n/locales/vi/navigation.json"; +import viOnboarding from "@src/i18n/locales/vi/onboarding.json"; +import zhHantNavigation from "@src/i18n/locales/zh-Hant/navigation.json"; +import zhHantOnboarding from "@src/i18n/locales/zh-Hant/onboarding.json"; +import zhNavigation from "@src/i18n/locales/zh/navigation.json"; +import zhOnboarding from "@src/i18n/locales/zh/onboarding.json"; +import { TUTORIALS } from "@src/scaffold/Tutorials/tutorialRegistry"; + +type TranslationTree = Record; + +const LOCALES = { + de: { onboarding: deOnboarding, navigation: deNavigation }, + en: { onboarding: enOnboarding, navigation: enNavigation }, + es: { onboarding: esOnboarding, navigation: esNavigation }, + fr: { onboarding: frOnboarding, navigation: frNavigation }, + ja: { onboarding: jaOnboarding, navigation: jaNavigation }, + ko: { onboarding: koOnboarding, navigation: koNavigation }, + pl: { onboarding: plOnboarding, navigation: plNavigation }, + pt: { onboarding: ptOnboarding, navigation: ptNavigation }, + ru: { onboarding: ruOnboarding, navigation: ruNavigation }, + tr: { onboarding: trOnboarding, navigation: trNavigation }, + vi: { onboarding: viOnboarding, navigation: viNavigation }, + zh: { onboarding: zhOnboarding, navigation: zhNavigation }, + "zh-Hant": { + onboarding: zhHantOnboarding, + navigation: zhHantNavigation, + }, +} as const; + +function flatten( + tree: TranslationTree, + prefix = "", + result: Record = {} +): Record { + for (const [key, value] of Object.entries(tree)) { + const path = prefix ? `${prefix}.${key}` : key; + if (value && typeof value === "object") { + flatten(value as TranslationTree, path, result); + } else { + result[path] = String(value); + } + } + return result; +} + +function interpolationVariables(value: string): string[] { + return [...value.matchAll(/\{\{(.*?)\}\}/g)].map((match) => match[1]).sort(); +} + +function readPath(tree: TranslationTree, path: string): unknown { + return path.split(".").reduce((value, segment) => { + if (!value || typeof value !== "object") return undefined; + return (value as TranslationTree)[segment]; + }, tree); +} + +describe("setup walkthrough i18n contract", () => { + const english = flatten({ + readiness: enOnboarding.readiness, + tutorials: enOnboarding.tutorials, + }); + + it("keeps the setup and tutorial key shape complete in every locale", () => { + for (const [locale, resources] of Object.entries(LOCALES)) { + const translated = flatten({ + readiness: resources.onboarding.readiness, + tutorials: resources.onboarding.tutorials, + }); + + expect(Object.keys(translated).sort(), locale).toEqual( + Object.keys(english).sort() + ); + expect( + Object.values(translated).every((value) => value.trim().length > 0), + locale + ).toBe(true); + } + }); + + it("preserves interpolation variables and rejects migration artifacts", () => { + for (const [locale, resources] of Object.entries(LOCALES)) { + const translated = flatten({ + readiness: resources.onboarding.readiness, + tutorials: resources.onboarding.tutorials, + }); + + for (const [key, englishValue] of Object.entries(english)) { + expect( + interpolationVariables(translated[key]), + `${locale}:${key}` + ).toEqual(interpolationVariables(englishValue)); + expect(translated[key], `${locale}:${key}`).not.toMatch( + /__KEEP_|__ITEM_|__ELEMENT_||\uE000|\uE001/ + ); + } + } + }); + + it("does not silently fall back to the English onboarding for other locales", () => { + for (const [locale, resources] of Object.entries(LOCALES)) { + if (locale === "en") continue; + const translated = flatten({ + readiness: resources.onboarding.readiness, + tutorials: resources.onboarding.tutorials, + }); + const unchangedCount = Object.keys(english).filter( + (key) => translated[key] === english[key] + ).length; + + // Product names and keyboard glyphs may intentionally stay unchanged. + expect(unchangedCount, locale).toBeLessThan(30); + } + }); + + it("covers the tutorial registry and setup-checklist navigation entry", () => { + for (const [locale, resources] of Object.entries(LOCALES)) { + expect( + resources.navigation.sidebar.settingsMenu.setupChecklist, + locale + ).toBeTruthy(); + + for (const tutorial of TUTORIALS) { + expect( + readPath(resources.onboarding, tutorial.titleKey), + `${locale}:${tutorial.titleKey}` + ).toBeTruthy(); + expect( + readPath(resources.onboarding, tutorial.descriptionKey), + `${locale}:${tutorial.descriptionKey}` + ).toBeTruthy(); + expect( + readPath(resources.onboarding, tutorial.durationKey), + `${locale}:${tutorial.durationKey}` + ).toBeTruthy(); + } + } + }); + + it("keeps the localized tutorial keyboard hint intact", () => { + for (const [locale, resources] of Object.entries(LOCALES)) { + const hint = resources.onboarding.tutorials.chrome.keyboardHint; + expect(hint, locale).toContain("←"); + expect(hint, locale).toContain("→"); + expect(hint, locale).toContain("< / >"); + } + }); +}); diff --git a/src/modules/SetupWalkthrough/__tests__/layoutTokens.test.ts b/src/modules/SetupWalkthrough/__tests__/layoutTokens.test.ts new file mode 100644 index 0000000000..2989d785f3 --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/layoutTokens.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it } from "vitest"; + +import { HOST_DESKTOP } from "@src/config/windowChromeRadius"; +import { WINDOW_CHROME_TOKENS } from "@src/config/windowChromeTokens"; +import { DEFAULT_SIDEBAR_WIDTH } from "@src/store/ui/sidebarAtom"; + +import { + SETUP_WALKTHROUGH_LAYOUT_TOKENS, + resolveSetupSidebarLayout, +} from "../layoutTokens"; + +describe("setup walkthrough layout tokens", () => { + it("reserves the shared titlebar height below macOS traffic lights", () => { + expect(resolveSetupSidebarLayout(HOST_DESKTOP.MACOS)).toEqual({ + panelWidth: DEFAULT_SIDEBAR_WIDTH, + contentTopInset: WINDOW_CHROME_TOKENS.titleBarHeight, + }); + }); + + it.each([HOST_DESKTOP.WINDOWS, HOST_DESKTOP.LINUX])( + "does not add a native traffic-light inset on %s", + (host) => { + expect(resolveSetupSidebarLayout(host)).toEqual({ + panelWidth: DEFAULT_SIDEBAR_WIDTH, + contentTopInset: 0, + }); + } + ); + + it("uses shared responsive, typography, and reduced-motion utilities", () => { + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.sidebar).toContain("lg:!flex"); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.card).toContain("!max-w-screen-2xl"); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.hero).toContain("pb-0"); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.hero).not.toContain("pb-10"); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.mobileProgress).toContain( + "lg:hidden" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.mainContent).toContain( + "overflow-y-auto" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.mainContent).toContain("sm:px-8"); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.stepFrame).toContain( + "animate-fade-in" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.stepFrame).toContain( + "motion-reduce:animate-none" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.presentationToolbar).toContain( + "max-w-[900px]" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.nativePreferenceList).toContain( + "!bg-transparent" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.cinematicPreferenceCard).toContain( + "setup-preferences-card" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.classicPreferenceCard).toContain( + "bg-bg-1" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.classicPreferenceControl).toBe( + "w-full" + ); + expect(SETUP_WALKTHROUGH_LAYOUT_TOKENS.choiceGrid).toBe( + "max-sm:!grid-cols-1" + ); + }); +}); diff --git a/src/modules/SetupWalkthrough/__tests__/preferenceSetup.test.ts b/src/modules/SetupWalkthrough/__tests__/preferenceSetup.test.ts new file mode 100644 index 0000000000..ddac8aeaf0 --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/preferenceSetup.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; + +import { DEFAULT_SETUP_WALKTHROUGH_PROGRESS } from "@src/config/settingsSchema/setupWalkthroughProgress"; + +import { + PREFERENCE_SETUP_COMPLETION_ID, + completePreferenceSetup, +} from "../preferenceSetup"; + +describe("preference setup completion", () => { + it("marks the compact setup complete while preserving optional setup data", () => { + const previous = { + ...DEFAULT_SETUP_WALKTHROUGH_PROGRESS, + completedStepIds: ["tools"], + selectedOrgId: "org-1", + repoScopes: ["github.com/acme/app"], + tutorialId: "code-editor" as const, + }; + + expect(completePreferenceSetup(previous)).toEqual({ + ...previous, + currentStepId: PREFERENCE_SETUP_COMPLETION_ID, + completedStepIds: ["tools", PREFERENCE_SETUP_COMPLETION_ID], + }); + }); + + it("is idempotent when finish is replayed", () => { + const completed = completePreferenceSetup( + DEFAULT_SETUP_WALKTHROUGH_PROGRESS + ); + + expect(completePreferenceSetup(completed)).toEqual(completed); + }); +}); diff --git a/src/modules/SetupWalkthrough/__tests__/setupCommands.test.ts b/src/modules/SetupWalkthrough/__tests__/setupCommands.test.ts new file mode 100644 index 0000000000..3fb4c89bec --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/setupCommands.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it, vi } from "vitest"; + +import { detectSetupTools, sanitizeDetectedTool } from "../setupCommands"; + +describe("setup commands", () => { + it("drops every secret-bearing field from detected credentials", () => { + const summary = sanitizeDetectedTool("codex", { + success: true, + agent_type: "codex", + message: "found", + keys: [ + { + id: "key-1", + name: "Codex", + auth_method: "oauth", + api_key: "must-not-survive", + session_token: "must-not-survive", + env_vars: { OPENAI_API_KEY: "must-not-survive" }, + validated: true, + }, + ], + }); + expect(summary).toEqual({ + agentType: "codex", + found: true, + keyCount: 1, + validatedCount: 1, + }); + expect(JSON.stringify(summary)).not.toContain("must-not-survive"); + }); + + it("isolates a provider detection failure without failing the scan", async () => { + const detect = vi.fn(async (agentType: string) => { + if (agentType === "claude_code") throw new Error("unavailable"); + return { + success: true, + agent_type: agentType, + message: "ok", + keys: [], + }; + }); + const results = await detectSetupTools(detect); + expect(results).toHaveLength(3); + expect( + results.find((result) => result.agentType === "claude_code") + ).toEqual({ + agentType: "claude_code", + found: false, + keyCount: 0, + validatedCount: 0, + }); + }); +}); diff --git a/src/modules/SetupWalkthrough/__tests__/testShortcut.test.ts b/src/modules/SetupWalkthrough/__tests__/testShortcut.test.ts new file mode 100644 index 0000000000..b781385adc --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/testShortcut.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, it, vi } from "vitest"; + +import { + createSetupWalkthroughTestUpdates, + isSetupWalkthroughTestShortcut, + runSetupWalkthroughTestEntry, +} from "../useSetupWalkthroughTestShortcut"; + +function shortcutEvent( + overrides: Partial[0]> = {} +) { + return { + altKey: true, + code: "KeyO", + ctrlKey: false, + isComposing: false, + metaKey: true, + repeat: false, + shiftKey: false, + ...overrides, + }; +} + +describe("setup walkthrough hidden test shortcut", () => { + it("matches the platform primary modifier and rejects near misses", () => { + expect(isSetupWalkthroughTestShortcut(shortcutEvent(), "MacIntel")).toBe( + true + ); + expect( + isSetupWalkthroughTestShortcut( + shortcutEvent({ ctrlKey: true, metaKey: false }), + "Win32" + ) + ).toBe(true); + expect( + isSetupWalkthroughTestShortcut( + shortcutEvent({ altKey: false }), + "MacIntel" + ) + ).toBe(false); + expect( + isSetupWalkthroughTestShortcut( + shortcutEvent({ shiftKey: true }), + "MacIntel" + ) + ).toBe(false); + expect( + isSetupWalkthroughTestShortcut( + shortcutEvent({ repeat: true }), + "MacIntel" + ) + ).toBe(false); + }); + + it("creates a fresh reset without unrelated settings", () => { + const first = createSetupWalkthroughTestUpdates(); + const second = createSetupWalkthroughTestUpdates(); + + expect(first).toEqual({ + "general.setupWalkthroughOutcome": "open", + "general.setupWalkthroughProgress": expect.objectContaining({ + goal: null, + currentStepId: "goal", + completedStepIds: [], + }), + }); + expect(Object.keys(first)).toEqual([ + "general.setupWalkthroughOutcome", + "general.setupWalkthroughProgress", + ]); + expect(first["general.setupWalkthroughProgress"]).not.toBe( + second["general.setupWalkthroughProgress"] + ); + }); + + it("navigates only after the atomic settings write succeeds", async () => { + const events: string[] = []; + const persist = vi.fn(async () => { + events.push("persist"); + }); + const navigate = vi.fn(() => { + events.push("navigate"); + }); + + await runSetupWalkthroughTestEntry({ persist, navigate }); + + expect(persist).toHaveBeenCalledWith(createSetupWalkthroughTestUpdates()); + expect(events).toEqual(["persist", "navigate"]); + }); + + it("does not navigate when resetting setup state fails", async () => { + const navigate = vi.fn(); + + await expect( + runSetupWalkthroughTestEntry({ + persist: vi.fn().mockRejectedValue(new Error("disk unavailable")), + navigate, + }) + ).rejects.toThrow("disk unavailable"); + + expect(navigate).not.toHaveBeenCalled(); + }); +}); diff --git a/src/modules/SetupWalkthrough/__tests__/useSyncedSetupWalkthroughProgress.test.ts b/src/modules/SetupWalkthrough/__tests__/useSyncedSetupWalkthroughProgress.test.ts new file mode 100644 index 0000000000..ce22f5f406 --- /dev/null +++ b/src/modules/SetupWalkthrough/__tests__/useSyncedSetupWalkthroughProgress.test.ts @@ -0,0 +1,119 @@ +// @vitest-environment jsdom +import { act, createElement } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, +} from "vitest"; + +import { + type SetupWalkthroughProgress, + createDefaultSetupWalkthroughProgress, +} from "@src/config/settingsSchema/setupWalkthroughProgress"; + +import { useSyncedSetupWalkthroughProgress } from "../useSyncedSetupWalkthroughProgress"; + +function completedPersonalProgress(): SetupWalkthroughProgress { + return { + ...createDefaultSetupWalkthroughProgress(), + goal: "personal", + currentStepId: "work-model", + completedStepIds: ["goal", "tools", "basics", "tutorial"], + }; +} + +function Harness({ stored }: { stored: SetupWalkthroughProgress }) { + const { progress, replaceProgress } = + useSyncedSetupWalkthroughProgress(stored); + + return createElement( + "button", + { + "data-goal": progress.goal ?? "unselected", + "data-step": progress.currentStepId, + onClick: () => + replaceProgress({ + ...progress, + goal: "team_activity", + }), + type: "button", + }, + "Select team" + ); +} + +describe("useSyncedSetupWalkthroughProgress", () => { + let container: HTMLDivElement; + let root: Root; + const actEnvironment = globalThis as typeof globalThis & { + IS_REACT_ACT_ENVIRONMENT?: boolean; + }; + + beforeAll(() => { + actEnvironment.IS_REACT_ACT_ENVIRONMENT = true; + }); + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + }); + + afterAll(() => { + Reflect.deleteProperty(actEnvironment, "IS_REACT_ACT_ENVIRONMENT"); + }); + + it("returns a mounted walkthrough to Goal when the persisted owner resets", async () => { + const completed = completedPersonalProgress(); + + await act(async () => { + root.render(createElement(Harness, { stored: completed })); + }); + + expect(container.querySelector("button")?.dataset).toMatchObject({ + goal: "personal", + step: "work-model", + }); + + await act(async () => { + root.render( + createElement(Harness, { + stored: createDefaultSetupWalkthroughProgress(), + }) + ); + }); + + expect(container.querySelector("button")?.dataset).toMatchObject({ + goal: "unselected", + step: "goal", + }); + }); + + it("keeps a local draft when an unrelated parent render retains the same persisted value", async () => { + const stored = createDefaultSetupWalkthroughProgress(); + + await act(async () => { + root.render(createElement(Harness, { stored })); + }); + + act(() => container.querySelector("button")?.click()); + + await act(async () => { + root.render(createElement(Harness, { stored })); + }); + + expect(container.querySelector("button")?.dataset.goal).toBe( + "team_activity" + ); + }); +}); diff --git a/src/modules/SetupWalkthrough/components/AnimatedTitle.tsx b/src/modules/SetupWalkthrough/components/AnimatedTitle.tsx deleted file mode 100644 index 1442cbf568..0000000000 --- a/src/modules/SetupWalkthrough/components/AnimatedTitle.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/** - * AnimatedTitle Component - * - * Displays a large centered title that optionally fades out - * and reveals a smaller header at top. - */ -import React from "react"; - -import type { AnimatedTitleProps } from "../types"; - -export const AnimatedTitle: React.FC = ({ - title, - subtitle, - persistent = false, - hideSmallTitle = false, -}) => { - const [showBigTitle, setShowBigTitle] = React.useState(true); - - React.useEffect(() => { - if (!persistent) { - const timer = setTimeout(() => { - setShowBigTitle(false); - }, 2000); - - return () => clearTimeout(timer); - } - }, [persistent]); - - return ( - <> - {/* Small title at top - shows after big title fades (unless hideSmallTitle is true) */} - {!hideSmallTitle && ( -
- {title} -
- )} - - {/* Big animated title - fades out after 2s (unless persistent) */} -
-

- {title} -

- {subtitle && ( -

- {subtitle} -

- )} -
- - ); -}; diff --git a/src/modules/SetupWalkthrough/components/SetupPreferencesPanel.tsx b/src/modules/SetupWalkthrough/components/SetupPreferencesPanel.tsx new file mode 100644 index 0000000000..76ef996eae --- /dev/null +++ b/src/modules/SetupWalkthrough/components/SetupPreferencesPanel.tsx @@ -0,0 +1,376 @@ +import React, { type ComponentType, type FC, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "@src/components/Button"; +import LanguageSelector from "@src/components/LanguageSelector"; +import Select, { type SelectOption } from "@src/components/Select"; +import type { PrimaryColorPreset } from "@src/config/appearance/primaryColors"; +import { HEADER_ICON_SIZE } from "@src/config/workstation/tokens"; +import { useAppearanceState } from "@src/modules/MainApp/Settings/sections/useAppearanceState"; +import { + SECTION_CONTROL_STYLE, + SectionContainer, + SectionRow, +} from "@src/modules/shared/layouts/SectionLayout"; +import { DETAIL_PANEL_TOKENS } from "@src/modules/shared/layouts/blocks"; +import { + FormField, + WizardStepContent, +} from "@src/scaffold/WizardSystem/primitives"; + +import { SETUP_WALKTHROUGH_LAYOUT_TOKENS } from "../layoutTokens"; +import { + AppearancePreferenceIcon, + BasicsStepIcon, + LanguagePreferenceIcon, + ThemePreferenceIcon, +} from "./SetupStepIcons"; + +export type SetupPreferencesPresentation = "native" | "cinematic" | "classic"; + +interface SetupPreferencesPanelProps { + isClosing: boolean; + onComplete: () => void; + onSkip: () => void; + initialPresentation?: SetupPreferencesPresentation; +} + +interface PreferenceLabelProps { + icon?: ComponentType<{ size?: number | string; className?: string }>; + label: string; + showAccent?: boolean; +} + +const CinematicPreferenceLabel: FC = ({ + icon: Icon, + label, + showAccent = false, +}) => ( + + + {showAccent ? ( + + ) : ( + Icon && + {label} + +); + +/** + * Both presentations bind to the same canonical settings state. The selector + * is intentionally component-local preview state: changing the visual version + * never writes an application preference or changes onboarding completion. + */ +const SetupPreferencesPanel: React.FC = ({ + isClosing, + onComplete, + onSkip, + initialPresentation = "native", +}) => { + const { t } = useTranslation(["onboarding", "settings"]); + const [presentation, setPresentation] = + useState(initialPresentation); + const { + appearanceMode, + appearanceModeOptions, + globalThemeId, + handleAppearanceModeChange, + handleThemeChange, + primaryColorOptions, + primaryColorPreset, + setPrimaryColorPreset, + themeOptions, + } = useAppearanceState(); + + const languageLabel = t("settings:general.language"); + const appearanceLabel = t("settings:general.appearanceMode"); + const themeLabel = t("settings:general.themePreset"); + const colorLabel = t("settings:general.primaryColor"); + const isCinematic = presentation === "cinematic"; + const isClassic = presentation === "classic"; + + const presentationOptions: SelectOption[] = [ + { + value: "native", + label: t("onboarding:readiness.presentation.native"), + }, + { + value: "cinematic", + label: t("onboarding:readiness.presentation.cinematic"), + }, + { + value: "classic", + label: t("onboarding:readiness.presentation.classic"), + }, + ]; + + const preferenceRowClass = isCinematic + ? SETUP_WALKTHROUGH_LAYOUT_TOKENS.cinematicPreferenceRow + : isClassic + ? SETUP_WALKTHROUGH_LAYOUT_TOKENS.classicPreferenceRow + : undefined; + const preferenceControlClass = isCinematic + ? SETUP_WALKTHROUGH_LAYOUT_TOKENS.cinematicPreferenceControl + : isClassic + ? SETUP_WALKTHROUGH_LAYOUT_TOKENS.classicPreferenceControl + : undefined; + const preferenceControlStyle = isClassic ? undefined : SECTION_CONTROL_STYLE; + const preferenceRowLayout = isClassic ? "vertical" : "horizontal"; + + const preferenceFields = ( + + + ) : ( + languageLabel + ) + } + className={preferenceRowClass} + layout={preferenceRowLayout} + > + {isCinematic ? ( +
+ +
+ ) : ( + + )} +
+ + ) : ( + appearanceLabel + ) + } + className={preferenceRowClass} + layout={preferenceRowLayout} + > + void handleThemeChange(String(value))} + options={themeOptions} + style={preferenceControlStyle} + className={preferenceControlClass} + size="large" + variant={isCinematic ? "ghost" : "default"} + ariaLabel={themeLabel} + dataTestId="setup-theme" + /> + + + ) : ( + colorLabel + ) + } + className={preferenceRowClass} + layout={preferenceRowLayout} + > + + setPresentation( + value === "cinematic" || value === "classic" ? value : "native" + ) + } + style={SECTION_CONTROL_STYLE} + disabled={isClosing} + ariaLabel={t("onboarding:readiness.presentation.label")} + dataTestId="setup-presentation" + /> + +
+ + {isCinematic ? ( +
+ {form} +
+ ) : isClassic ? ( +
+ {form} +
+ ) : ( +
+ {form} +
+ )} +
+ ); +}; + +export default SetupPreferencesPanel; diff --git a/src/modules/SetupWalkthrough/components/SetupStepIcons.tsx b/src/modules/SetupWalkthrough/components/SetupStepIcons.tsx new file mode 100644 index 0000000000..74c29978e9 --- /dev/null +++ b/src/modules/SetupWalkthrough/components/SetupStepIcons.tsx @@ -0,0 +1,54 @@ +import workModelIcon from "@src/assets/fileTypeIcons/flow.svg"; +import organizationIcon from "@src/assets/fileTypeIcons/folder-cluster.svg"; +import tutorialIcon from "@src/assets/fileTypeIcons/folder-docs.svg"; +import sharingIcon from "@src/assets/fileTypeIcons/folder-review.svg"; +import goalIcon from "@src/assets/fileTypeIcons/folder-target.svg"; +import themeIcon from "@src/assets/fileTypeIcons/folder-theme.svg"; +import languageIcon from "@src/assets/fileTypeIcons/i18n.svg"; +import toolsIcon from "@src/assets/fileTypeIcons/key.svg"; +import appearanceIcon from "@src/assets/fileTypeIcons/moon.svg"; +import readyIcon from "@src/assets/fileTypeIcons/rocket.svg"; +import basicsIcon from "@src/assets/fileTypeIcons/settings.svg"; +import { createRepositoryAssetIcon } from "@src/components/RepositoryAssetIcon"; + +export const GoalStepIcon = createRepositoryAssetIcon(goalIcon, "GoalStepIcon"); +export const ToolsStepIcon = createRepositoryAssetIcon( + toolsIcon, + "ToolsStepIcon" +); +export const OrganizationStepIcon = createRepositoryAssetIcon( + organizationIcon, + "OrganizationStepIcon" +); +export const SharingStepIcon = createRepositoryAssetIcon( + sharingIcon, + "SharingStepIcon" +); +export const BasicsStepIcon = createRepositoryAssetIcon( + basicsIcon, + "BasicsStepIcon" +); +export const TutorialStepIcon = createRepositoryAssetIcon( + tutorialIcon, + "TutorialStepIcon" +); +export const WorkModelStepIcon = createRepositoryAssetIcon( + workModelIcon, + "WorkModelStepIcon" +); +export const ReadyStepIcon = createRepositoryAssetIcon( + readyIcon, + "ReadyStepIcon" +); +export const LanguagePreferenceIcon = createRepositoryAssetIcon( + languageIcon, + "LanguagePreferenceIcon" +); +export const AppearancePreferenceIcon = createRepositoryAssetIcon( + appearanceIcon, + "AppearancePreferenceIcon" +); +export const ThemePreferenceIcon = createRepositoryAssetIcon( + themeIcon, + "ThemePreferenceIcon" +); diff --git a/src/modules/SetupWalkthrough/components/SetupWalkthroughSidebar.tsx b/src/modules/SetupWalkthrough/components/SetupWalkthroughSidebar.tsx new file mode 100644 index 0000000000..7eee9cb5cf --- /dev/null +++ b/src/modules/SetupWalkthrough/components/SetupWalkthroughSidebar.tsx @@ -0,0 +1,62 @@ +import React, { memo } from "react"; + +import setupMascot from "@src/assets/onboarding/org2-pearl-relay-mascot.png"; +import AppLogo from "@src/components/AppLogo"; + +import { SETUP_WALKTHROUGH_LAYOUT_TOKENS } from "../layoutTokens"; + +export interface SetupWalkthroughSidebarProps { + title: React.ReactNode; + description: string; +} + +/** + * Cinematic first-run hero. It owns presentation only; preference values, + * completion, and navigation remain with the setup controller surface. + */ +const SetupWalkthroughSidebar: React.FC = memo( + ({ title, description }) => ( +
+
+
+ + + ORGII + +
+ +
+

+ {title} +

+

+ {description} +

+
+ +
+
+ +
+
+
+ ) +); + +SetupWalkthroughSidebar.displayName = "SetupWalkthroughSidebar"; + +export default SetupWalkthroughSidebar; diff --git a/src/modules/SetupWalkthrough/components/__tests__/SetupPreferencesPanel.interaction.test.ts b/src/modules/SetupWalkthrough/components/__tests__/SetupPreferencesPanel.interaction.test.ts new file mode 100644 index 0000000000..09a9bfb92d --- /dev/null +++ b/src/modules/SetupWalkthrough/components/__tests__/SetupPreferencesPanel.interaction.test.ts @@ -0,0 +1,177 @@ +// @vitest-environment jsdom +import React, { act } from "react"; +import { type Root, createRoot } from "react-dom/client"; +import { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; + +import SetupPreferencesPanel from "../SetupPreferencesPanel"; + +interface MockSelectOption { + label: string; + value: string; +} + +interface MockSelectProps { + value: string; + options: MockSelectOption[]; + onChange: (value: string) => void; + dataTestId?: string; + disabled?: boolean; +} + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +vi.mock("@src/modules/MainApp/Settings/sections/useAppearanceState", () => ({ + useAppearanceState: () => ({ + appearanceMode: "dark", + appearanceModeOptions: [{ label: "Dark", value: "dark" }], + globalThemeId: "orgii-dark", + handleAppearanceModeChange: vi.fn(), + handleThemeChange: vi.fn(), + primaryColorOptions: [{ label: "Blue", value: "blue" }], + primaryColorPreset: "blue", + setPrimaryColorPreset: vi.fn(), + themeOptions: [{ label: "ORGII Dark", value: "orgii-dark" }], + }), +})); + +vi.mock("@src/components/LanguageSelector", () => ({ + default: ({ ariaLabel }: { ariaLabel?: string }) => + React.createElement("div", { + "aria-label": ariaLabel, + "data-testid": "setup-language", + }), +})); + +vi.mock("@src/components/Select", () => ({ + default: ({ + value, + options, + onChange, + dataTestId, + disabled, + }: MockSelectProps) => + React.createElement( + "select", + { + value, + disabled, + "data-testid": dataTestId, + onChange: (event: React.ChangeEvent) => + onChange(event.currentTarget.value), + }, + options.map((option) => + React.createElement( + "option", + { key: option.value, value: option.value }, + option.label + ) + ) + ), +})); + +describe("SetupPreferencesPanel presentation switching", () => { + let container: HTMLDivElement; + let root: Root; + const actEnvironment = globalThis as typeof globalThis & { + IS_REACT_ACT_ENVIRONMENT?: boolean; + }; + + beforeAll(() => { + actEnvironment.IS_REACT_ACT_ENVIRONMENT = true; + }); + + beforeEach(() => { + container = document.createElement("div"); + document.body.appendChild(container); + root = createRoot(container); + }); + + afterEach(() => { + act(() => root.unmount()); + container.remove(); + }); + + afterAll(() => { + Reflect.deleteProperty(actEnvironment, "IS_REACT_ACT_ENVIRONMENT"); + }); + + it("switches presentations without resetting preference values or callbacks", async () => { + const onComplete = vi.fn(); + + await act(async () => { + root.render( + React.createElement(SetupPreferencesPanel, { + isClosing: false, + onComplete, + onSkip: vi.fn(), + }) + ); + }); + + const presentation = container.querySelector( + '[data-testid="setup-presentation"]' + ); + expect(presentation?.value).toBe("native"); + expect( + container.querySelector('[data-testid="setup-presentation-native"]') + ).not.toBeNull(); + + act(() => { + if (!presentation) return; + presentation.value = "classic"; + presentation.dispatchEvent(new Event("change", { bubbles: true })); + }); + + expect( + container.querySelector('[data-testid="setup-presentation-classic"]') + ).not.toBeNull(); + expect( + container.querySelector( + '[data-testid="setup-appearance-mode"]' + )?.value + ).toBe("dark"); + + act(() => { + if (!presentation) return; + presentation.value = "cinematic"; + presentation.dispatchEvent(new Event("change", { bubbles: true })); + }); + + expect( + container.querySelector('[data-testid="setup-presentation-cinematic"]') + ).not.toBeNull(); + expect( + container.querySelector( + '[data-testid="setup-appearance-mode"]' + )?.value + ).toBe("dark"); + + act(() => { + container + .querySelector('[data-testid="setup-finish"]') + ?.click(); + }); + expect(onComplete).toHaveBeenCalledOnce(); + + act(() => { + if (!presentation) return; + presentation.value = "native"; + presentation.dispatchEvent(new Event("change", { bubbles: true })); + }); + + expect( + container.querySelector('[data-testid="setup-presentation-native"]') + ).not.toBeNull(); + }); +}); diff --git a/src/modules/SetupWalkthrough/components/__tests__/SetupPreferencesPanel.test.ts b/src/modules/SetupWalkthrough/components/__tests__/SetupPreferencesPanel.test.ts new file mode 100644 index 0000000000..04e1dfbf84 --- /dev/null +++ b/src/modules/SetupWalkthrough/components/__tests__/SetupPreferencesPanel.test.ts @@ -0,0 +1,114 @@ +import React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it, vi } from "vitest"; + +import SetupPreferencesPanel from "../SetupPreferencesPanel"; + +vi.mock("react-i18next", () => ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +vi.mock("@src/modules/MainApp/Settings/sections/useAppearanceState", () => ({ + useAppearanceState: () => ({ + appearanceMode: "dark", + appearanceModeOptions: [{ label: "Dark", value: "dark" }], + globalThemeId: "orgii-dark", + handleAppearanceModeChange: vi.fn(), + handleThemeChange: vi.fn(), + primaryColorOptions: [{ label: "Blue", value: "blue" }], + primaryColorPreset: "blue", + setPrimaryColorPreset: vi.fn(), + themeOptions: [{ label: "ORGII Dark", value: "orgii-dark" }], + }), +})); + +vi.mock("@src/components/LanguageSelector", () => ({ + default: ({ ariaLabel }: { ariaLabel?: string }) => + React.createElement("div", { + "aria-label": ariaLabel, + "data-testid": "setup-language", + }), +})); + +describe("SetupPreferencesPanel", () => { + it("renders four canonical preference controls and terminal actions", () => { + const html = renderToStaticMarkup( + React.createElement(SetupPreferencesPanel, { + isClosing: false, + onComplete: vi.fn(), + onSkip: vi.fn(), + }) + ); + + expect(html).toContain('data-testid="setup-language"'); + expect(html).toContain('data-testid="setup-presentation"'); + expect(html).toContain('data-testid="setup-presentation-native"'); + expect(html).toContain('data-testid="setup-appearance-mode"'); + expect(html).toContain('data-testid="setup-theme"'); + expect(html).toContain('data-testid="setup-primary-color"'); + expect(html.match(/role="combobox"/g)).toHaveLength(4); + expect(html.match(/aria-haspopup="listbox"/g)).toHaveLength(4); + expect(html).toContain("bg-primary-container"); + expect(html).toContain("!bg-transparent"); + expect(html.match(/class="section-layout-row/g)).toHaveLength(4); + expect(html).not.toContain("setup-preference-row"); + expect(html).not.toContain("setup-preference-cta"); + expect(html).toContain('data-testid="setup-finish"'); + expect(html).toContain('data-testid="setup-skip"'); + }); + + it("keeps terminal actions visible and disables them while closing", () => { + const html = renderToStaticMarkup( + React.createElement(SetupPreferencesPanel, { + isClosing: true, + onComplete: vi.fn(), + onSkip: vi.fn(), + }) + ); + + expect(html).toContain('data-testid="setup-finish"'); + expect(html).toContain('data-testid="setup-skip"'); + expect(html.match(/disabled=""/g)).toHaveLength(2); + }); + + it("can start in the cinematic presentation without changing bindings", () => { + const html = renderToStaticMarkup( + React.createElement(SetupPreferencesPanel, { + isClosing: false, + onComplete: vi.fn(), + onSkip: vi.fn(), + initialPresentation: "cinematic", + }) + ); + + expect(html).toContain('data-testid="setup-presentation-cinematic"'); + expect(html).toContain("setup-preferences-card"); + expect(html).toContain("setup-preference-row"); + expect(html).toContain("setup-preference-cta"); + expect(html).toContain('data-testid="setup-appearance-mode"'); + expect(html).toContain('data-testid="setup-theme"'); + expect(html).toContain('data-testid="setup-primary-color"'); + }); + + it("can start in the classic vertical presentation with canonical controls", () => { + const html = renderToStaticMarkup( + React.createElement(SetupPreferencesPanel, { + isClosing: false, + onComplete: vi.fn(), + onSkip: vi.fn(), + initialPresentation: "classic", + }) + ); + + expect(html).toContain('data-testid="setup-presentation-classic"'); + expect(html).toContain("onboarding:readiness.classicPanel.title"); + expect(html).toContain("onboarding:readiness.classicPanel.description"); + expect(html).not.toContain("logo.png"); + expect(html).toContain("!bg-transparent"); + expect(html.match(/flex-col/g)?.length).toBeGreaterThanOrEqual(4); + expect(html).toContain('data-testid="setup-appearance-mode"'); + expect(html).toContain('data-testid="setup-theme"'); + expect(html).toContain('data-testid="setup-primary-color"'); + expect(html).not.toContain("setup-preference-row"); + }); +}); diff --git a/src/modules/SetupWalkthrough/components/__tests__/SetupWalkthroughSidebar.test.ts b/src/modules/SetupWalkthrough/components/__tests__/SetupWalkthroughSidebar.test.ts new file mode 100644 index 0000000000..d2b23ed2a0 --- /dev/null +++ b/src/modules/SetupWalkthrough/components/__tests__/SetupWalkthroughSidebar.test.ts @@ -0,0 +1,29 @@ +import React from "react"; +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, it } from "vitest"; + +import SetupWalkthroughSidebar from "../SetupWalkthroughSidebar"; + +describe("SetupWalkthroughSidebar", () => { + it("renders the cinematic brand hero without wizard progress", () => { + const html = renderToStaticMarkup( + React.createElement(SetupWalkthroughSidebar, { + title: React.createElement( + React.Fragment, + null, + "Let's set up your ", + React.createElement("span", null, "ORGII") + ), + description: "A few quick choices to personalize your workspace.", + }) + ); + + expect(html).toContain("Let's set up your"); + expect(html).toContain("personalize your workspace"); + expect(html).toContain("logo.png"); + expect(html).toContain("org2-pearl-relay-mascot.png"); + expect(html).toContain('aria-labelledby="setup-hero-title"'); + expect(html).not.toContain('role="progressbar"'); + expect(html).not.toContain('aria-label="Setup steps"'); + }); +}); diff --git a/src/modules/SetupWalkthrough/components/index.ts b/src/modules/SetupWalkthrough/components/index.ts deleted file mode 100644 index e52cdb8e51..0000000000 --- a/src/modules/SetupWalkthrough/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * SetupWalkthrough shared components - */ -export { AnimatedTitle } from "./AnimatedTitle"; diff --git a/src/modules/SetupWalkthrough/config.tsx b/src/modules/SetupWalkthrough/config.tsx index 1a64905186..8efe2fb82e 100644 --- a/src/modules/SetupWalkthrough/config.tsx +++ b/src/modules/SetupWalkthrough/config.tsx @@ -2,22 +2,25 @@ * SetupWalkthrough step configuration */ import { - FolderGit2, - Github, - IdCard, - Palette, - Rocket, - Sparkles, -} from "lucide-react"; - + BasicsStepIcon, + GoalStepIcon, + OrganizationStepIcon, + ReadyStepIcon, + SharingStepIcon, + ToolsStepIcon, + TutorialStepIcon, + WorkModelStepIcon, +} from "./components/SetupStepIcons"; import { - CompleteStep, - DevPassportStep, - GitHubStep, - RepoStep, - ThemeSelectionStep, - WelcomeStep, -} from "./steps"; + BasicsStep, + GoalStep, + OrganizationStep, + ReadyStep, + SharingStep, + ToolsStep, + TutorialStep, + WorkModelStep, +} from "./steps/ReadinessSteps"; import type { StepConfig } from "./types"; // ============================================ @@ -26,39 +29,51 @@ import type { StepConfig } from "./types"; export const STEP_CONFIGS: StepConfig[] = [ { - id: "welcome", - i18nKey: "welcome", - icon: Sparkles, - content: , + id: "goal", + i18nKey: "goal", + icon: GoalStepIcon, + component: GoalStep, + }, + { + id: "tools", + i18nKey: "tools", + icon: ToolsStepIcon, + component: ToolsStep, + }, + { + id: "organization", + i18nKey: "organization", + icon: OrganizationStepIcon, + component: OrganizationStep, }, { - id: "theme", - i18nKey: "theme", - icon: Palette, - content: , + id: "sharing", + i18nKey: "sharing", + icon: SharingStepIcon, + component: SharingStep, }, { - id: "dev-passport", - i18nKey: "devPassport", - icon: IdCard, - content: , + id: "basics", + i18nKey: "basics", + icon: BasicsStepIcon, + component: BasicsStep, }, { - id: "github", - i18nKey: "github", - icon: Github, - content: , + id: "tutorial", + i18nKey: "tutorial", + icon: TutorialStepIcon, + component: TutorialStep, }, { - id: "workspace", - i18nKey: "workspace", - icon: FolderGit2, - content: , + id: "work-model", + i18nKey: "workModel", + icon: WorkModelStepIcon, + component: WorkModelStep, }, { - id: "complete", - i18nKey: "complete", - icon: Rocket, - content: , + id: "ready", + i18nKey: "ready", + icon: ReadyStepIcon, + component: ReadyStep, }, ]; diff --git a/src/modules/SetupWalkthrough/constants.ts b/src/modules/SetupWalkthrough/constants.ts deleted file mode 100644 index 2b6684576d..0000000000 --- a/src/modules/SetupWalkthrough/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * SetupWalkthrough constants - */ - -// ============================================ -// Agent Code Names -// ============================================ - -/** Code names for random generation in DevPassport step */ -export const AGENT_CODE_NAMES = [ - "Shadow Fox", - "Night Owl", - "Storm Rider", - "Cyber Wolf", - "Ghost Protocol", - "Iron Phoenix", - "Silent Viper", - "Dark Matter", - "Neon Spectre", - "Zero Cool", - "Binary Star", - "Quantum Leap", - "Code Breaker", - "Pixel Phantom", - "Stack Overflow", - "Null Pointer", - "Root Access", - "Kernel Panic", - "Syntax Error", - "Cache Money", -] as const; diff --git a/src/modules/SetupWalkthrough/flow.ts b/src/modules/SetupWalkthrough/flow.ts new file mode 100644 index 0000000000..f837836a60 --- /dev/null +++ b/src/modules/SetupWalkthrough/flow.ts @@ -0,0 +1,171 @@ +import type { SetupWalkthroughProgress } from "@src/config/settingsSchema/setupWalkthroughProgress"; + +export const SETUP_STEP_IDS = [ + "goal", + "tools", + "organization", + "sharing", + "basics", + "tutorial", + "work-model", + "ready", +] as const; + +export type SetupStepId = (typeof SETUP_STEP_IDS)[number]; + +export interface SetupOrganizationSelection { + orgId: string; + name: string; + role: string; + repoScopes: string[]; + sharingFloor: SetupWalkthroughProgress["sharingFloor"]; +} + +export interface SetupTeamPolicySnapshot { + selectedOrgId: string; + repoScopes: string[]; + sharingFloor: SetupWalkthroughProgress["sharingFloor"]; +} + +/** + * Selecting the same org is a harmless roster refresh and must not erase a + * user's in-progress policy draft. A different org is a privacy boundary: + * hydrate its known server mirrors and clear verification/invite state. + */ +export function applySetupOrganizationSelection( + progress: SetupWalkthroughProgress, + selection: SetupOrganizationSelection +): SetupWalkthroughProgress { + if (progress.selectedOrgId === selection.orgId) { + return { + ...progress, + selectedOrgName: selection.name, + selectedOrgRole: selection.role, + }; + } + return { + ...progress, + selectedOrgId: selection.orgId, + selectedOrgName: selection.name, + selectedOrgRole: selection.role, + repoScopes: selection.repoScopes, + sharingFloor: selection.sharingFloor, + inviteLink: null, + verifiedAt: null, + }; +} + +export function captureSetupTeamPolicy( + progress: SetupWalkthroughProgress +): SetupTeamPolicySnapshot | null { + return progress.selectedOrgId + ? { + selectedOrgId: progress.selectedOrgId, + repoScopes: [...progress.repoScopes], + sharingFloor: progress.sharingFloor, + } + : null; +} + +export function setupTeamPolicyMatches( + progress: SetupWalkthroughProgress, + snapshot: SetupTeamPolicySnapshot +): boolean { + return ( + progress.selectedOrgId === snapshot.selectedOrgId && + progress.sharingFloor === snapshot.sharingFloor && + progress.repoScopes.length === snapshot.repoScopes.length && + progress.repoScopes.every( + (scope, index) => scope === snapshot.repoScopes[index] + ) + ); +} + +export function isTeamSetup(progress: SetupWalkthroughProgress): boolean { + return progress.goal === "team_activity"; +} + +export function getVisibleSetupStepIds( + progress: SetupWalkthroughProgress +): SetupStepId[] { + return isTeamSetup(progress) + ? [...SETUP_STEP_IDS] + : SETUP_STEP_IDS.filter( + (step) => step !== "organization" && step !== "sharing" + ); +} + +export function getNormalizedCurrentStep( + progress: SetupWalkthroughProgress +): SetupStepId { + const visible = getVisibleSetupStepIds(progress); + return visible.includes(progress.currentStepId as SetupStepId) + ? (progress.currentStepId as SetupStepId) + : visible[0]; +} + +export function canCompleteSetupStep( + progress: SetupWalkthroughProgress, + stepId: SetupStepId +): boolean { + switch (stepId) { + case "goal": + return progress.goal !== null; + case "organization": + return progress.selectedOrgId !== null; + case "sharing": + // Admin/owner onboarding proves a committed repo policy. Members cannot + // mutate governance, but still explicitly request and drain one sync + // pass before the team path is considered complete. + return progress.selectedOrgRole === "member" + ? progress.selectedOrgId !== null && progress.verifiedAt !== null + : progress.repoScopes.length > 0 && progress.verifiedAt !== null; + default: + return true; + } +} + +export function advanceSetupProgress( + progress: SetupWalkthroughProgress +): SetupWalkthroughProgress { + const current = getNormalizedCurrentStep(progress); + if (!canCompleteSetupStep(progress, current)) return progress; + const visible = getVisibleSetupStepIds(progress); + const index = visible.indexOf(current); + const next = visible[Math.min(index + 1, visible.length - 1)]; + return { + ...progress, + currentStepId: next, + completedStepIds: Array.from( + new Set([...progress.completedStepIds, current]) + ), + }; +} + +export function retreatSetupProgress( + progress: SetupWalkthroughProgress +): SetupWalkthroughProgress { + const visible = getVisibleSetupStepIds(progress); + const current = getNormalizedCurrentStep(progress); + const index = visible.indexOf(current); + return { + ...progress, + currentStepId: visible[Math.max(0, index - 1)], + }; +} + +export function canNavigateToSetupStep( + progress: SetupWalkthroughProgress, + target: SetupStepId +): boolean { + const visible = getVisibleSetupStepIds(progress); + const currentIndex = visible.indexOf(getNormalizedCurrentStep(progress)); + const targetIndex = visible.indexOf(target); + return ( + targetIndex >= 0 && + (targetIndex <= currentIndex || + visible + .slice(0, targetIndex) + .every((step) => progress.completedStepIds.includes(step))) + ); +} diff --git a/src/modules/SetupWalkthrough/index.scss b/src/modules/SetupWalkthrough/index.scss deleted file mode 100644 index b54da05f70..0000000000 --- a/src/modules/SetupWalkthrough/index.scss +++ /dev/null @@ -1,28 +0,0 @@ -// ============================================ -// Toolbar hiding when in walkthrough-mode -// ============================================ -body.walkthrough-mode { - // Hide the tab bar entirely - .tab-bar { - display: none !important; - } - - // Hide ALL toolbar sections (covers all toolbar elements) - [data-toolbar-section] { - display: none !important; - } -} - -// ============================================ -// Animations -// ============================================ -@keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} diff --git a/src/modules/SetupWalkthrough/index.tsx b/src/modules/SetupWalkthrough/index.tsx index 29d15107c0..484695175d 100644 --- a/src/modules/SetupWalkthrough/index.tsx +++ b/src/modules/SetupWalkthrough/index.tsx @@ -1,91 +1,64 @@ -/** - * Setup Walkthrough Page - * - * A wizard-style onboarding flow entered automatically for first-time users. - * Completion persists a completed outcome and emits the GitHub Star value - * moment. Skipping persists a dismissed outcome without prompting for a Star. - * - * Renders outside AppShell (no sidebar) for a focused experience. - */ -import { useSetAtom } from "jotai"; -import { ArrowLeft, ArrowRight, Check } from "lucide-react"; -import React, { useCallback, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; +import { useAtomValue, useSetAtom } from "jotai"; +import React, { useCallback, useMemo, useRef, useState } from "react"; +import { Trans, useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import Button from "@src/components/Button"; -import "@src/components/DevPassport/devpassport.css"; +import AppLogo from "@src/components/AppLogo"; import Message from "@src/components/Message"; import { ROUTES } from "@src/config/routes"; +import { normalizeSetupWalkthroughProgress } from "@src/config/settingsSchema/setupWalkthroughProgress"; import { CODEMIRROR_STYLE_NONCE } from "@src/features/CodeMirror/config/nonce"; import { signalGitHubStarValueMoment } from "@src/features/GitHubStar"; import { OnboardingLayout } from "@src/modules/shared/layouts"; -import { PanelFooter } from "@src/modules/shared/layouts/blocks"; -import { saveSettingAtom } from "@src/store/settings/settingsAtom"; +import { + saveSettingsBatchAtom, + settingsAtom, +} from "@src/store/settings/settingsAtom"; import { type SetupWalkthroughOutcome, shouldSignalGitHubStarAfterSetup, } from "@src/store/settings/setupWalkthrough"; -import { STEP_CONFIGS } from "./config"; -import "./index.scss"; - -// ============================================ -// Global Styles (injected) -// ============================================ +import SetupPreferencesPanel from "./components/SetupPreferencesPanel"; +import SetupWalkthroughSidebar from "./components/SetupWalkthroughSidebar"; +import { + SETUP_WALKTHROUGH_HERO_PANEL_STYLE, + SETUP_WALKTHROUGH_LAYOUT_TOKENS, +} from "./layoutTokens"; +import { completePreferenceSetup } from "./preferenceSetup"; +import "./setupWalkthrough.scss"; const WALKTHROUGH_STYLES = ` - body.walkthrough-mode .tab-bar { - display: none !important; - } - body.walkthrough-mode [data-toolbar-section] { - display: none !important; - } - @keyframes fadeInUp { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } - } + body.walkthrough-mode .tab-bar { display: none !important; } + body.walkthrough-mode [data-toolbar-section] { display: none !important; } `; -// ============================================ -// Main Component -// ============================================ - const SetupWalkthrough: React.FC = () => { const navigate = useNavigate(); const { t } = useTranslation("onboarding"); - const saveSetting = useSetAtom(saveSettingAtom); - const [currentStepIndex, setCurrentStepIndex] = useState(0); + const saveSettings = useSetAtom(saveSettingsBatchAtom); + const storedProgress = + useAtomValue(settingsAtom)["general.setupWalkthroughProgress"]; + const progress = useMemo( + () => normalizeSetupWalkthroughProgress(storedProgress), + [storedProgress] + ); const [isClosing, setIsClosing] = useState(false); const closingRef = useRef(false); - // Add/remove body class for hiding tabbar - React.useLayoutEffect(() => { - document.body.classList.add("walkthrough-mode"); - return () => { - document.body.classList.remove("walkthrough-mode"); - }; - }, []); - - const currentStep = STEP_CONFIGS[currentStepIndex]; - const isFirstStep = currentStepIndex === 0; - const isLastStep = currentStepIndex === STEP_CONFIGS.length - 1; - const closeWalkthrough = useCallback( async (outcome: Exclude) => { if (closingRef.current) return; closingRef.current = true; setIsClosing(true); try { - await saveSetting({ - key: "general.setupWalkthroughOutcome", - value: outcome, + const finalProgress = + outcome === "completed" + ? completePreferenceSetup(progress) + : progress; + await saveSettings({ + "general.setupWalkthroughOutcome": outcome, + "general.setupWalkthroughProgress": finalProgress, }); if (shouldSignalGitHubStarAfterSetup(outcome)) { signalGitHubStarValueMoment(); @@ -98,127 +71,60 @@ const SetupWalkthrough: React.FC = () => { setIsClosing(false); } }, - [navigate, saveSetting, t] + [navigate, progress, saveSettings, t] ); - const handleNext = useCallback(() => { - if (isLastStep) { - void closeWalkthrough("completed"); - } else { - setCurrentStepIndex((prev) => prev + 1); - } - }, [closeWalkthrough, isLastStep]); - - const handleBack = useCallback(() => { - if (!isFirstStep) { - setCurrentStepIndex((prev) => prev - 1); - } - }, [isFirstStep]); - - const handleSkip = useCallback(() => { - void closeWalkthrough("dismissed"); - }, [closeWalkthrough]); - - // Left content: Step navigation const leftContent = ( -
-
- {STEP_CONFIGS.map((step, index) => { - const StepIcon = step.icon; - const isActive = index === currentStepIndex; - const isCompleted = index < currentStepIndex; - - return ( - - ); - })} -
-
+ + ), + }} + /> + } + description={t("readiness.hero.description")} + /> ); - // Right content: Step content + footer const rightContent = ( -
-
- {currentStep.content} +
+
+ + + ORGII + +
+
+
+ void closeWalkthrough("completed")} + onSkip={() => void closeWalkthrough("dismissed")} + /> +
- - } - onClick={handleBack} - > - {t("common:actions.back")} - - ) : undefined - } - secondaryActions={ - !isLastStep - ? [ - { - label: t("navigation.skipSetup"), - onClick: handleSkip, - disabled: isClosing, - }, - ] - : undefined - } - primaryAction={{ - label: isLastStep - ? t("navigation.getStarted") - : t("common:actions.continue"), - onClick: handleNext, - loading: isClosing, - disabled: isClosing, - icon: isLastStep ? : , - }} - />
); return ( <> - {/* Global styles for walkthrough mode */} - diff --git a/src/modules/SetupWalkthrough/layoutTokens.ts b/src/modules/SetupWalkthrough/layoutTokens.ts new file mode 100644 index 0000000000..a9bd20aee9 --- /dev/null +++ b/src/modules/SetupWalkthrough/layoutTokens.ts @@ -0,0 +1,106 @@ +import { + HOST_DESKTOP, + type HostDesktop, + resolveHostDesktop, +} from "@src/config/windowChromeRadius"; +import { WINDOW_CHROME_TOKENS } from "@src/config/windowChromeTokens"; +import { TYPOGRAPHY } from "@src/config/workstation/tokens"; +import { DETAIL_PANEL_TOKENS } from "@src/modules/shared/layouts/blocks"; +import { DEFAULT_SIDEBAR_WIDTH } from "@src/store/ui/sidebarAtom"; + +/** + * Feature composition tokens for the full-screen setup surface. + * + * Reusable controls keep their own visual contracts; this object owns only + * the setup shell's responsive composition so spacing, motion, and overrides + * are not rebuilt across JSX and SCSS. + */ +export const SETUP_WALKTHROUGH_LAYOUT_TOKENS = { + shell: "setup-walkthrough-ambient !overflow-hidden !bg-bg-2 !p-0", + card: "setup-walkthrough-card !mx-auto !max-h-none !w-full !max-w-screen-2xl !rounded-none !border-0 !bg-transparent !shadow-none", + sidebar: + "!hidden !max-w-none !basis-5/12 !shrink-0 !items-stretch !justify-stretch !bg-transparent !p-0 lg:!flex", + sidebarContent: "relative flex h-full w-full flex-col overflow-hidden", + brandRow: "flex items-center gap-3", + brandLogo: "rounded-xl", + brandCopy: "min-w-0", + brandTitleRow: "flex items-center gap-2", + brandTitle: `${TYPOGRAPHY.statistic} tracking-tight text-text-1`, + brandTag: `${TYPOGRAPHY.badge} uppercase tracking-wide text-text-3`, + brandDescription: "max-w-52", + progress: "mt-6", + progressLabel: `mb-2 flex items-center justify-between gap-3 ${TYPOGRAPHY.contentSubtitle}`, + progressLabelText: "font-medium text-text-2", + navigation: "mt-5", + hero: "relative flex h-full w-full flex-col px-10 pb-0 pt-20 xl:px-20 xl:pt-24", + heroCopy: "relative z-10 mt-20 max-w-lg xl:mt-24", + heroTitle: + "m-0 text-4xl font-semibold leading-tight tracking-tight text-text-1 xl:text-6xl", + heroBrandAccent: "setup-walkthrough-brand-accent", + heroDescription: + "mt-5 max-w-md text-base leading-7 text-text-2 xl:text-lg xl:leading-8", + heroVisual: "relative mt-auto min-h-72 flex-1", + heroPlanet: "setup-walkthrough-planet absolute inset-x-0 bottom-0 h-40", + heroMascot: + "setup-walkthrough-mascot absolute bottom-10 left-1/2 h-64 w-auto -translate-x-1/2 object-contain xl:h-80", + main: "!min-w-0 !bg-transparent !p-0", + mainContent: + "relative flex h-full w-full flex-col items-center justify-center overflow-y-auto px-5 py-16 sm:px-8 lg:px-10 xl:px-12", + mobileProgress: `absolute left-5 top-16 flex items-center gap-3 text-text-1 sm:left-10 lg:hidden ${TYPOGRAPHY.secondary}`, + mobileProgressTitle: "font-semibold tracking-tight", + contentScroll: "contents", + stepFrame: + "animate-fade-in flex w-full justify-center motion-reduce:animate-none", + presentationStack: "flex w-full flex-col items-center gap-3", + presentationToolbar: `${DETAIL_PANEL_TOKENS.contentWidth} flex justify-end`, + presentationField: "w-full max-w-xs", + nativePreferenceList: "!bg-transparent", + classicPreferenceCard: + "w-full max-w-2xl rounded-2xl border border-border-1 bg-bg-1 shadow-sm", + classicPreferenceContent: "!max-w-none gap-5 px-6 py-6 sm:px-8 [&>div]:gap-4", + classicPreferenceList: + "!rounded-none !border-0 !bg-transparent !px-0 [&>.section-layout-row]:after:!inset-x-0", + classicPreferenceRow: "!min-h-0 !py-3", + classicPreferenceControl: "w-full", + cinematicPreferenceCard: + "setup-preferences-card w-full max-w-2xl rounded-3xl p-6 sm:p-8", + cinematicPreferenceContent: + "!max-w-none gap-7 [&_h1]:!text-xl [&_h1]:!leading-7 [&>header]:items-center [&>header]:gap-4 [&>div]:gap-5", + cinematicPreferenceList: + "!flex !flex-col !gap-3 !border-0 !bg-transparent !p-0 [&>.section-layout-row]:after:!hidden", + cinematicPreferenceRow: + "setup-preference-row !min-h-16 rounded-xl !px-4 !py-3 sm:!min-h-20 sm:!px-5", + cinematicPreferenceLabel: "flex items-center gap-3 font-medium text-text-1", + cinematicPreferenceIcon: + "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-text-2", + cinematicPreferenceControl: "w-full sm:w-56", + cinematicPreferenceCta: "setup-preference-cta !h-12 !rounded-xl", + cinematicPreferenceSecondary: "self-center", + footer: "", + choiceGrid: "max-sm:!grid-cols-1", +} as const; + +export const SETUP_WALKTHROUGH_HERO_PANEL_STYLE: React.CSSProperties = { + flex: "0 0 46%", + width: "46%", + maxWidth: "none", +}; + +export interface SetupSidebarLayout { + panelWidth: number; + contentTopInset: number; +} + +/** + * Resolves setup-shell dimensions from the same sidebar and window-chrome + * tokens used by the main application shell. + */ +export function resolveSetupSidebarLayout( + host: HostDesktop = resolveHostDesktop() +): SetupSidebarLayout { + return { + panelWidth: DEFAULT_SIDEBAR_WIDTH, + contentTopInset: + host === HOST_DESKTOP.MACOS ? WINDOW_CHROME_TOKENS.titleBarHeight : 0, + }; +} diff --git a/src/modules/SetupWalkthrough/preferenceSetup.ts b/src/modules/SetupWalkthrough/preferenceSetup.ts new file mode 100644 index 0000000000..53769f8f43 --- /dev/null +++ b/src/modules/SetupWalkthrough/preferenceSetup.ts @@ -0,0 +1,19 @@ +import type { SetupWalkthroughProgress } from "@src/config/settingsSchema/setupWalkthroughProgress"; + +export const PREFERENCE_SETUP_COMPLETION_ID = "preferences"; + +/** + * Completion is a single persisted transition. Legacy setup data is retained + * so opening the optional setup surfaces later never loses previous work. + */ +export function completePreferenceSetup( + progress: SetupWalkthroughProgress +): SetupWalkthroughProgress { + return { + ...progress, + currentStepId: PREFERENCE_SETUP_COMPLETION_ID, + completedStepIds: Array.from( + new Set([...progress.completedStepIds, PREFERENCE_SETUP_COMPLETION_ID]) + ), + }; +} diff --git a/src/modules/SetupWalkthrough/setupCommands.ts b/src/modules/SetupWalkthrough/setupCommands.ts new file mode 100644 index 0000000000..5ae145973f --- /dev/null +++ b/src/modules/SetupWalkthrough/setupCommands.ts @@ -0,0 +1,65 @@ +import { + type AutoDetectResult, + type ModelType, + autoDetectKey, +} from "@src/api/services/keyValidation"; +import { + externalHistoryRescanSource, + fetchExternalSourceStats, +} from "@src/api/tauri/externalHistory"; +import type { SetupWalkthroughProgress } from "@src/config/settingsSchema/setupWalkthroughProgress"; +import { loadSessionRoster } from "@src/store/session"; + +export type SetupToolSummary = SetupWalkthroughProgress["tools"][number]; + +const TOOL_TYPES = ["codex", "claude_code", "cursor_cli"] as const; + +/** + * Convert the secret-bearing detection RPC into the only shape onboarding is + * allowed to retain. API keys, tokens, environment values and account + * metadata are discarded in the same synchronous turn. + */ +export function sanitizeDetectedTool( + agentType: SetupToolSummary["agentType"], + result: AutoDetectResult +): SetupToolSummary { + return { + agentType, + found: result.success && result.keys.length > 0, + keyCount: result.keys.length, + validatedCount: result.keys.filter((key) => key.validated === true).length, + }; +} + +export async function detectSetupTools( + detect: (agentType: ModelType) => Promise = autoDetectKey +): Promise { + const settled = await Promise.allSettled( + TOOL_TYPES.map(async (agentType) => + sanitizeDetectedTool(agentType, await detect(agentType)) + ) + ); + return settled.map((result, index) => + result.status === "fulfilled" + ? result.value + : { + agentType: TOOL_TYPES[index], + found: false, + keyCount: 0, + validatedCount: 0, + } + ); +} + +/** + * Explicit, source-scoped Codex import. The shared rescan service coalesces + * concurrent callers; a roster reload happens only when the cache changed. + */ +export async function importCodexHistory(): Promise { + const result = await externalHistoryRescanSource("codex_app"); + if (result.changedSources.length > 0) { + await loadSessionRoster({ forceRefresh: true }); + } + const stats = await fetchExternalSourceStats("codex_app"); + return stats.sessionCount; +} diff --git a/src/modules/SetupWalkthrough/setupWalkthrough.scss b/src/modules/SetupWalkthrough/setupWalkthrough.scss new file mode 100644 index 0000000000..f972b01fea --- /dev/null +++ b/src/modules/SetupWalkthrough/setupWalkthrough.scss @@ -0,0 +1,146 @@ +.setup-walkthrough-ambient { + --setup-glow-primary-strong: color-mix( + in srgb, + var(--color-primary-6) 34%, + transparent + ); + --setup-glow-primary-soft: color-mix( + in srgb, + var(--color-primary-5) 22%, + transparent + ); + --setup-card-surface: color-mix(in srgb, var(--color-bg-1) 86%, transparent); + --setup-card-border: color-mix( + in srgb, + var(--color-primary-6) 24%, + var(--color-border-1) + ); + --setup-row-surface: color-mix(in srgb, var(--color-fill-2) 76%, transparent); + --setup-card-blur: 24px; + --setup-mascot-motion: 5s; + + background: + radial-gradient( + ellipse 46% 58% at 18% 88%, + var(--setup-glow-primary-strong), + transparent 68% + ), + radial-gradient( + ellipse 42% 54% at 88% 26%, + var(--setup-glow-primary-soft), + transparent 72% + ), + linear-gradient( + 135deg, + color-mix(in srgb, var(--color-bg-2) 94%, var(--color-text-1)), + var(--color-bg-2) + ); +} + +.setup-walkthrough-card { + isolation: isolate; +} + +.setup-walkthrough-brand-accent { + background: linear-gradient( + 100deg, + var(--color-primary-5), + var(--color-primary-7) + ); + background-clip: text; + color: transparent; + -webkit-background-clip: text; +} + +.setup-walkthrough-planet { + border-radius: 50% 50% 0 0; + background: + radial-gradient( + ellipse at 58% 0%, + color-mix(in srgb, var(--color-primary-6) 76%, var(--color-bg-1)), + transparent 20% + ), + linear-gradient( + 100deg, + color-mix(in srgb, var(--color-primary-7) 74%, var(--color-text-1)), + color-mix(in srgb, var(--color-primary-5) 82%, var(--color-bg-1)) + ); + filter: blur(0.5px); + box-shadow: + 0 -18px 60px var(--setup-glow-primary-strong), + 0 -2px 18px color-mix(in srgb, var(--color-primary-5) 62%, transparent); + opacity: 0.78; + transform: scaleX(1.35); +} + +.setup-walkthrough-mascot { + animation: setup-mascot-float var(--setup-mascot-motion) ease-in-out infinite; + filter: drop-shadow( + 0 18px 24px color-mix(in srgb, var(--color-primary-7) 48%, transparent) + ); + transform-origin: center bottom; +} + +.setup-preferences-card { + border: 1px solid var(--setup-card-border); + background: var(--setup-card-surface); + box-shadow: + 0 28px 80px color-mix(in srgb, var(--color-bg-1) 72%, transparent), + inset 0 1px 0 color-mix(in srgb, var(--color-text-1) 8%, transparent); + backdrop-filter: blur(var(--setup-card-blur)); + -webkit-backdrop-filter: blur(var(--setup-card-blur)); +} + +.setup-preference-row { + border: 1px solid color-mix(in srgb, var(--color-border-1) 72%, transparent); + background: var(--setup-row-surface); + transition: + border-color 160ms ease, + background-color 160ms ease, + transform 160ms ease; +} + +.setup-preference-row:hover, +.setup-preference-row:focus-within { + border-color: color-mix( + in srgb, + var(--color-primary-6) 46%, + var(--color-border-1) + ); + background: color-mix(in srgb, var(--color-fill-3) 82%, transparent); + transform: translateY(-1px); +} + +.setup-preference-row .select-selector { + min-height: 2.5rem; +} + +.setup-preference-cta { + background: linear-gradient( + 100deg, + color-mix(in srgb, var(--color-primary-6) 72%, var(--color-bg-1)), + var(--color-primary-6) + ) !important; + box-shadow: 0 12px 28px + color-mix(in srgb, var(--color-primary-6) 26%, transparent); +} + +@keyframes setup-mascot-float { + 0%, + 100% { + transform: translateX(-50%) translateY(0); + } + 50% { + transform: translateX(-50%) translateY(-8px); + } +} + +@media (prefers-reduced-motion: reduce) { + .setup-walkthrough-mascot { + animation: none; + } + + .setup-preference-row { + transition: none; + } +} diff --git a/src/modules/SetupWalkthrough/steps/CompleteStep.tsx b/src/modules/SetupWalkthrough/steps/CompleteStep.tsx deleted file mode 100644 index 63ec01be0f..0000000000 --- a/src/modules/SetupWalkthrough/steps/CompleteStep.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Complete Step - * - * Setup complete, ready to start using the app. - */ -import { Sparkles } from "lucide-react"; -import React from "react"; -import { useTranslation } from "react-i18next"; - -import Button from "@src/components/Button"; - -import { AnimatedTitle } from "../components"; - -export const CompleteStep: React.FC = () => { - const { t } = useTranslation("onboarding"); - - return ( - <> - -
- -
- - ); -}; diff --git a/src/modules/SetupWalkthrough/steps/DevPassportStep.tsx b/src/modules/SetupWalkthrough/steps/DevPassportStep.tsx deleted file mode 100644 index c8e11a77d8..0000000000 --- a/src/modules/SetupWalkthrough/steps/DevPassportStep.tsx +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Dev Passport Step - * - * Allows user to create their developer passport with code name and avatar. - */ -import { Dices, Upload } from "lucide-react"; -import React, { useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import Button from "@src/components/Button"; -import { - type PageContent, - PassportDossier, - type UserProfile, -} from "@src/components/DevPassport"; - -import { AnimatedTitle } from "../components"; -import { AGENT_CODE_NAMES } from "../constants"; - -export const DevPassportStep: React.FC = () => { - const { t } = useTranslation("onboarding"); - const [codeName, setCodeName] = useState(""); - const [profileImage, setProfileImage] = useState(null); - const [isGenerated, setIsGenerated] = useState(false); - const [currentSheetIndex, setCurrentSheetIndex] = useState(-1); - const fileInputRef = useRef(null); - - const handleRandomName = () => { - const randomIndex = Math.floor(Math.random() * AGENT_CODE_NAMES.length); - setCodeName(AGENT_CODE_NAMES[randomIndex]); - }; - - const handleImageUpload = (event: React.ChangeEvent) => { - const file = event.target.files?.[0]; - if (file) { - const reader = new FileReader(); - reader.onloadend = () => { - setProfileImage(reader.result as string); - }; - reader.readAsDataURL(file); - } - }; - - const handleUploadClick = () => { - fileInputRef.current?.click(); - }; - - const handleGenerate = () => { - setIsGenerated(true); - }; - - const handleFlip = (index: number) => { - setCurrentSheetIndex(index); - }; - - // Generate stable ID number once on mount (lazy initializer runs outside render) - const [idNumber] = useState( - () => `VE-${Math.random().toString(36).substring(2, 8).toUpperCase()}` - ); - - // Generate user profile from form data - const userProfile: UserProfile = { - name: codeName.toUpperCase() || "AGENT", - role: t("devPassport.role"), - memberSince: new Date() - .toLocaleDateString("en-US", { - day: "2-digit", - month: "short", - year: "numeric", - }) - .toUpperCase(), - idNumber, - avatarUrl: profileImage || "", - }; - - const passportPages: PageContent[] = [ - { id: "p0", type: "profile" }, - { id: "p1", type: "stamps", stamps: [] }, - { id: "p2", type: "stamps", stamps: [] }, - { id: "p3", type: "stamps", stamps: [] }, - { id: "p4", type: "stamps", stamps: [] }, - { id: "p5", type: "stamps", stamps: [] }, - ]; - - // Show passport when generated - if (isGenerated) { - return ( - <> - -
- {/* Passport Display Section - uses Dossier animation */} -
-
- -
-
-
- - ); - } - - return ( - <> - - -
- {/* Code Name Section */} -
- -
- setCodeName(event.target.value)} - /> - -
-
- - {/* Profile Image Section */} -
- -
-
- {profileImage ? ( - Profile - ) : ( - - )} -
-
- - {t("devPassport.selectImage")} - - - {t("devPassport.avatarHint")} - -
-
- -
- - {/* Generate Button */} - -
- - ); -}; diff --git a/src/modules/SetupWalkthrough/steps/GitHubStep.tsx b/src/modules/SetupWalkthrough/steps/GitHubStep.tsx deleted file mode 100644 index ca970a5e05..0000000000 --- a/src/modules/SetupWalkthrough/steps/GitHubStep.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * GitHub Step - * - * Connect GitHub account for repository access. - */ -import { Github } from "lucide-react"; -import React from "react"; -import { useTranslation } from "react-i18next"; - -import Button from "@src/components/Button"; - -import { AnimatedTitle } from "../components"; - -export const GitHubStep: React.FC = () => { - const { t } = useTranslation("onboarding"); - - return ( - <> - -
- -

{t("github.hint")}

-
- - ); -}; diff --git a/src/modules/SetupWalkthrough/steps/ReadinessSteps.tsx b/src/modules/SetupWalkthrough/steps/ReadinessSteps.tsx new file mode 100644 index 0000000000..a24ec9299f --- /dev/null +++ b/src/modules/SetupWalkthrough/steps/ReadinessSteps.tsx @@ -0,0 +1,760 @@ +import { + Boxes, + BriefcaseBusiness, + Building2, + Check, + Clipboard, + Cloud, + Eye, + FolderGit2, + Inbox, + KeyRound, + LayoutDashboard, + Link2, + ListChecks, + MessageSquare, + MonitorCog, + Play, + Plus, + RefreshCw, + ShieldCheck, + User, + Users, +} from "lucide-react"; +import React, { useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Button from "@src/components/Button"; +import InlineAlert from "@src/components/InlineAlert"; +import Input from "@src/components/Input"; +import Select from "@src/components/Select"; +import { TYPOGRAPHY } from "@src/config/workstation/tokens"; +import { openOrg2CloudSignIn } from "@src/features/Org2Cloud/useOrg2CloudSignIn"; +import { useAppearanceState } from "@src/modules/MainApp/Settings/sections/useAppearanceState"; +import { + SECTION_ACTION_GAP_CLASSES, + SECTION_CONTROL_STYLE, + SECTION_PATH_TEXT_CLASSES, + SECTION_VALUE_SMALL_SECONDARY_CLASSES, + SECTION_VALUE_TEXT_CLASSES, + SECTION_VALUE_TEXT_SUCCESS_CLASSES, + SectionContainer, + SectionDescription, + SectionRow, +} from "@src/modules/shared/layouts/SectionLayout"; +import { DETAIL_PANEL_TOKENS } from "@src/modules/shared/layouts/blocks"; +import { openWorkspaceSpotlight } from "@src/scaffold/GlobalSpotlight/openSpotlight"; +import { TUTORIALS } from "@src/scaffold/Tutorials/tutorialRegistry"; +import { + SelectionGrid, + type SelectionGridOption, + WizardStepContent, +} from "@src/scaffold/WizardSystem/primitives"; + +import { + BasicsStepIcon, + GoalStepIcon, + OrganizationStepIcon, + ReadyStepIcon, + SharingStepIcon, + ToolsStepIcon, + TutorialStepIcon, + WorkModelStepIcon, +} from "../components/SetupStepIcons"; +import { SETUP_WALKTHROUGH_LAYOUT_TOKENS } from "../layoutTokens"; +import type { SetupWalkthroughController } from "../useSetupWalkthroughController"; + +type StepProps = { controller: SetupWalkthroughController }; + +const CONTROL_STYLE = { width: "100%", maxWidth: "100%" } as const; + +export const GoalStep: React.FC = ({ controller }) => { + const { t } = useTranslation("onboarding"); + const options = useMemo< + SelectionGridOption>[] + >( + () => [ + { + key: "personal", + label: t("readiness.goal.personal.title"), + description: t("readiness.goal.personal.description"), + icon: User, + dataTestId: "setup-goal-personal", + }, + { + key: "team_activity", + label: t("readiness.goal.team.title"), + description: t("readiness.goal.team.description"), + icon: Users, + dataTestId: "setup-goal-team", + }, + { + key: "work_management", + label: t("readiness.goal.work.title"), + description: t("readiness.goal.work.description"), + icon: BriefcaseBusiness, + dataTestId: "setup-goal-work", + }, + ], + [t] + ); + return ( + + + {t("readiness.goal.hint")} + + ); +}; + +const TOOL_LABELS: Record = { + codex: "Codex", + claude_code: "Claude Code", + cursor_cli: "Cursor", +}; + +export const ToolsStep: React.FC = ({ controller }) => { + const { t } = useTranslation("onboarding"); + const byType = new Map( + controller.progress.tools.map((tool) => [tool.agentType, tool]) + ); + const isDetecting = controller.activeOperation === "detect-tools"; + const isImporting = controller.activeOperation === "import-history"; + return ( + + + {["codex", "claude_code", "cursor_cli"].map((agentType) => { + const tool = byType.get( + agentType as "codex" | "claude_code" | "cursor_cli" + ); + return ( + + + {tool?.found && } + {tool + ? tool.found + ? t("readiness.tools.found", { + count: tool.keyCount, + validated: tool.validatedCount, + }) + : t("readiness.tools.notFound") + : t("readiness.tools.notScanned")} + + + ); + })} + +
+ + +
+ {controller.progress.historySessionCount !== null && ( + + {t("readiness.tools.historyImported", { + count: controller.progress.historySessionCount, + })} + + )} + {t("readiness.tools.privacy")} +
+ ); +}; + +export const OrganizationStep: React.FC = ({ controller }) => { + const { t } = useTranslation("onboarding"); + const [mode, setMode] = useState<"create" | "join">("create"); + const [orgName, setOrgName] = useState(""); + const [invite, setInvite] = useState(""); + const openSignIn = React.useCallback(() => { + controller.setOperationError(null); + void openOrg2CloudSignIn().catch((error: unknown) => { + controller.setOperationError( + error instanceof Error ? error.message : String(error) + ); + }); + }, [controller]); + const orgOptions = controller.cloudOrgs.map((org) => ({ + key: org.orgId, + label: org.name, + description: t("readiness.organization.role", { + role: t(`readiness.organization.roles.${org.role.toLowerCase()}`, { + defaultValue: org.role, + }), + }), + icon: Building2, + })); + const selected = controller.progress.selectedOrgId; + return ( + + {!controller.cloudAuth ? ( + } + onClick={openSignIn} + data-testid="setup-cloud-sign-in" + > + {t("readiness.organization.signIn")} + + } + > + {t("readiness.organization.signInHint")} + + ) : ( + <> + {orgOptions.length > 0 && ( + + + { + const org = controller.cloudOrgs.find( + (item) => item.orgId === orgId + ); + if (org) controller.selectOrganization(org); + }} + columns={2} + cardVariant="subtle" + compactCards + className={SETUP_WALKTHROUGH_LAYOUT_TOKENS.choiceGrid} + /> + + + )} + + + + + +
+ + +
+
+
+ + )} + {selected && ( + + {t("readiness.organization.selected", { + org: controller.progress.selectedOrgName, + })} + + )} +
+ ); +}; + +export const SharingStep: React.FC = ({ controller }) => { + const { t } = useTranslation("onboarding"); + const isMember = controller.progress.selectedOrgRole === "member"; + const isSaved = controller.progress.verifiedAt !== null; + return ( + + + +
+ + {controller.workspaceFolders.length + ? controller.workspaceFolders + .map((folder) => folder.name) + .join(", ") + : t("readiness.sharing.noWorkspace")} + + +
+
+ {!isMember && ( + <> + + + + {controller.progress.repoScopes.map((scope) => ( + + {scope} + + ))} + + + + + - - -