FN-013: establish i18n lint baseline and localize dashboard copy
Add a production i18n lint baseline while replacing dashboard copy with translation keys. - Localize task, board, mailbox, report, graph, settings, and shared dashboard surfaces. - Regenerate locale catalogs and resource types for the migration keys. - Add the i18n lint-baseline regression test and contributor guidance. Files changed: .changeset/fn-013-i18n-lint-baseline.md | 7 + docs/i18n-contributing.md | 11 + .../dashboard/app/components/ActiveAgentsPanel.tsx | 2 +- .../dashboard/app/components/AgentDetailView.tsx | 2 +- packages/dashboard/app/components/Board.tsx | 19 +- packages/dashboard/app/components/ChatView.tsx | 2 +- .../dashboard/app/components/ComposeChatPanel.tsx | 14 +- packages/dashboard/app/components/DockTaskList.tsx | 16 +- .../dashboard/app/components/FloatingWindow.tsx | 6 +- .../dashboard/app/components/GitHubImportModal.tsx | 4 +- .../dashboard/app/components/KnowledgeGraphPanel.tsx | 21 +- packages/dashboard/app/components/ListView.tsx | 2 +- .../app/components/MailboxArtifactAttachment.tsx | 16 +- packages/dashboard/app/components/MailboxModal.tsx | 12 +- .../app/components/MailboxStructuralItem.tsx | 13 +- .../app/components/MailboxTaskProposal.tsx | 10 +- .../app/components/MailboxTaskRecommendations.tsx | 12 +- packages/dashboard/app/components/MailboxView.tsx | 14 +- packages/dashboard/app/components/MermaidDiagram.tsx | 4 +- packages/dashboard/app/components/MeshTopology.tsx | 6 +- .../dashboard/app/components/MessageComposer.tsx | 24 +- .../app/components/NativeStructurePreview.tsx | 14 +- packages/dashboard/app/components/ProviderLoginDialog.tsx | 24 +- packages/dashboard/app/components/ReportActionMenu.tsx | 6 +- packages/dashboard/app/components/ReportModal.tsx | 26 +- packages/dashboard/app/components/TaskChatTab.tsx | 10 +- packages/dashboard/app/components/TaskDetailModal.tsx | 28 +- .../app/components/TaskVerificationStatus.tsx | 10 +- .../dashboard/app/components/WhatsAppChatPairingPanel.tsx | 38 +- .../components/command-center/CommandCenter.tsx | 4 +- .../components/command-center/IdeationPanel.tsx | 12 +- .../components/settings/sections/McpServersCard.tsx | 2 +- .../components/settings/sections/MemorySection.tsx | 4 +- .../app/task-modal-touch-resize-e2e-fixture.tsx | 28 +- packages/i18n/locales/en/app.json | 247 +++++++- packages/i18n/locales/es/app.json | 256 +++++++- packages/i18n/locales/fr/app.json | 256 +++++++- packages/i18n/locales/ko/app.json | 256 +++++++- packages/i18n/locales/pt-BR/app.json | 705 ++++++++++++++++++++ packages/i18n/locales/zh-CN/app.json | 256 +++++++- packages/i18n/locales/zh-TW/app.json | 256 +++++++- .../i18n/src/__tests__/i18n-lint-baseline.test.ts | 31 + packages/i18n/src/resources.d.ts | 229 ++++++- 43 files changed, 2627 insertions(+), 288 deletions(-) Fusion-Task-Id: FN-013 Fusion-Task-Lineage: 846ac221-c7c3-4ff0-a30d-69479b614765 Co-authored-by: Fusion <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-013-i18n-lint-baseline.md
Normal file
7
.changeset/fn-013-i18n-lint-baseline.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Restore the production i18n catalog lint guardrail.
|
||||
category: fix
|
||||
dev: Adds a runLinter regression test and keeps all supported app catalogs structurally synchronized.
|
||||
@@ -54,6 +54,17 @@ Any remaining user-facing copy must be localized with `t()` / `<Trans>` and an
|
||||
specific files or a small cluster in `lint.ignore`, includes an `FNXC` rationale,
|
||||
and has a filed follow-up task that removes the ignore. The settings sections
|
||||
cluster is no longer deferred as of FN-6771; keep those files covered by lint.
|
||||
The production catalog currently has a clean lint baseline: `pnpm i18n:lint`
|
||||
must finish with `No issues found.`. Keep the executable regression alongside the
|
||||
catalog tests so future shipping copy cannot reintroduce debt:
|
||||
|
||||
```bash
|
||||
pnpm --filter @fusion/i18n exec vitest run src/__tests__/i18n-lint-baseline.test.ts src/__tests__/i18n-gate-coverage.test.ts --silent=passed-only --reporter=dot
|
||||
```
|
||||
|
||||
`i18n-lint-baseline.test.ts` imports the root `i18next.config.ts` and calls the
|
||||
installed `runLinter` API directly. It therefore checks the same production
|
||||
inputs as the CLI rather than relying on a mocked catalog or a source-text count.
|
||||
The `@fusion/i18n` regression tests also assert the lint-ignore scope and live
|
||||
catalog key parity so those guardrails cannot silently drift.
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ function LiveAgentCard({ agent, projectId, onSelect, onOpenTaskLogs, activity }:
|
||||
{(activity?.summary || lastCompletedStep) && (
|
||||
<div className="live-agent-card-activity">
|
||||
{activity?.summary && <div className="live-agent-card-now-doing">{activity.summary}</div>}
|
||||
{lastCompletedStep && <div className="live-agent-card-last-step">Last completed Step {lastCompletedStep.index}: {lastCompletedStep.step.name}</div>}
|
||||
{lastCompletedStep && <div className="live-agent-card-last-step">{t("agents.lastCompletedStep", "Last completed Step {{index}}: {{name}}", { index: lastCompletedStep.index, name: lastCompletedStep.step.name })}</div>}
|
||||
</div>
|
||||
)}
|
||||
<div className="live-agent-card-transcript">
|
||||
|
||||
@@ -1337,7 +1337,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
||||
);
|
||||
|
||||
if (inline) {
|
||||
return <div className="agent-detail-inline-shell" role="region" aria-label="Agent detail">{detailContent}</div>;
|
||||
return <div className="agent-detail-inline-shell" role="region" aria-label={t("agents.detailLabel", "Agent detail")}>{detailContent}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,8 @@ import "./Lane.css";
|
||||
import "./Board.css";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { createPortal } from "react-dom";
|
||||
import { promoteTask, type ModelInfo, type BoardWorkflowsPayload, type BoardWorkflowColumn, type RevertTaskOptions, type RevertTaskResult } from "../api";
|
||||
import { useBlockerFanout, type BlockerFanoutColumnFlags } from "../hooks/useBlockerFanout";
|
||||
@@ -146,9 +148,9 @@ export { ALL_WORKFLOWS_BOARD_VIEW_ID } from "../utils/boardWorkflowSelection";
|
||||
type AggregateBoardColumn = BoardWorkflowColumn & { sourceWorkflowIds: string[] };
|
||||
type AggregateQuickCreateTarget = { columnId: string; workflowId: string };
|
||||
|
||||
function BoardWorkflowSkeleton({ empty = false }: { empty?: boolean }) {
|
||||
function BoardWorkflowSkeleton({ empty = false, t }: { empty?: boolean; t: TFunction<"app"> }) {
|
||||
return (
|
||||
<main className="board board-workflows-skeleton" id="board" aria-busy={!empty} aria-label={empty ? "No workflow lanes available" : "Loading workflow lanes"} data-testid={empty ? "board-workflows-empty" : "board-workflows-skeleton"}>
|
||||
<main className="board board-workflows-skeleton" id="board" aria-busy={!empty} aria-label={empty ? t("board.noWorkflowLanes", "No workflow lanes available") : t("board.loadingWorkflowLanes", "Loading workflow lanes")} data-testid={empty ? "board-workflows-empty" : "board-workflows-skeleton"}>
|
||||
{[0, 1, 2].map((index) => (
|
||||
<section className="board-workflows-skeleton__column card" key={index} aria-hidden="true">
|
||||
<div className="board-workflows-skeleton__header" />
|
||||
@@ -177,6 +179,7 @@ function columnDefOffersArchiveAllDone(columnDef: { flags: { complete?: boolean;
|
||||
}
|
||||
|
||||
export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, onMoveTask, onPauseTask, onUnpauseTask, onResetTask, onDuplicateTask, onMergeTask, onOpenDetail, onOpenRefine, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, mergeStrategy = "direct", onToggleAutoMerge, planAutoApproveEnabled, onTogglePlanAutoApprove, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onRevertTask, onReviseTask, onDeleteTask, onArchiveAllDone, onLoadArchivedTasks, onLoadMoreArchivedTasks, archivedHasMore, archivedLoadingMore, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, onOpenMission, staleHighFanoutBlockerAgeThresholdMs, lastFetchTimeMs, prAuthAvailable, onOpenWorkflowEditor, onCreateWorkflow, workflowControlsInHeader = false }: BoardProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const [archivedCollapsed, setArchivedCollapsed] = useState(true);
|
||||
/*
|
||||
FNXC:DoneColumnSorting 2026-06-29-16:57:
|
||||
@@ -926,7 +929,7 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
|
||||
before this deletion — the legacy board below was NOT the fetch-failure fallback.
|
||||
*/
|
||||
if (boardWorkflows === null || boardWorkflows.workflows.length === 0) {
|
||||
return <BoardWorkflowSkeleton empty={boardWorkflows !== null} />;
|
||||
return <BoardWorkflowSkeleton empty={boardWorkflows !== null} t={t} />;
|
||||
}
|
||||
|
||||
if (workflowMode && selectedWorkflow) {
|
||||
@@ -1032,8 +1035,8 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
|
||||
);
|
||||
})}
|
||||
{aggregateRevertedTasks.length > 0 && (
|
||||
<section className="reverted-tasks-section" aria-label="Reverted Tasks" data-testid="board-reverted-tasks">
|
||||
<h2>Reverted Tasks</h2>
|
||||
<section className="reverted-tasks-section" aria-label={t("tasks.revertedTasks", "Reverted Tasks")} data-testid="board-reverted-tasks">
|
||||
<h2>{t("tasks.revertedTasks", "Reverted Tasks")}</h2>
|
||||
{aggregateRevertedTasks.map((task) => (
|
||||
<TaskCard
|
||||
key={`reverted-${task.id}`}
|
||||
@@ -1133,8 +1136,8 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
|
||||
);
|
||||
})}
|
||||
{partitionRevertedTasks(selectedWorkflowTasks).reverted.length > 0 && (
|
||||
<section className="reverted-tasks-section" aria-label="Reverted Tasks" data-testid="board-reverted-tasks">
|
||||
<h2>Reverted Tasks</h2>
|
||||
<section className="reverted-tasks-section" aria-label={t("tasks.revertedTasks", "Reverted Tasks")} data-testid="board-reverted-tasks">
|
||||
<h2>{t("tasks.revertedTasks", "Reverted Tasks")}</h2>
|
||||
{partitionRevertedTasks(selectedWorkflowTasks).reverted.map((task) => <TaskCard key={`reverted-${task.id}`} task={task} taskColumnFlags={blockerFanoutColumnFlagsByTaskId.get(task.id)} onOpenDetail={onOpenDetail} onDeleteTask={onDeleteTask} onReviseTask={onReviseTask} addToast={addToast} disableDrag />)}
|
||||
</section>
|
||||
)}
|
||||
@@ -1217,5 +1220,5 @@ export function Board({ tasks, projectId, maxConcurrent, showWorktreeGrouping, o
|
||||
Rendering the skeleton rather than throwing keeps a hypothetical unreachable
|
||||
state a blank frame instead of a crashed board.
|
||||
*/
|
||||
return <BoardWorkflowSkeleton empty={false} />;
|
||||
return <BoardWorkflowSkeleton empty={false} t={t} />;
|
||||
}
|
||||
|
||||
@@ -3607,7 +3607,7 @@ export function ChatView({ projectId, addToast, floating = false, compactLayout
|
||||
{t("chat.matchedInMessage", "Matched: \"{{preview}}\"", { preview: session.matchedMessagePreview })}
|
||||
</div>
|
||||
) : null}
|
||||
{showArchivedSessions ? <button type="button" className="btn btn-sm btn-secondary" data-testid={`chat-archived-restore-${session.id}`} onClick={(event) => { event.stopPropagation(); void handleRestoreArchived(session.id); }}>Restore</button> : null}
|
||||
{showArchivedSessions ? <button type="button" className="btn btn-sm btn-secondary" data-testid={`chat-archived-restore-${session.id}`} onClick={(event) => { event.stopPropagation(); void handleRestoreArchived(session.id); }}>{t("chat.restore", "Restore")}</button> : null}
|
||||
<div className="chat-session-meta">
|
||||
<span className="chat-session-meta-model">
|
||||
{sessionResolvedModel?.provider ? <ProviderIcon provider={sessionResolvedModel.provider} size="sm" /> : null}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useComposerDictation } from "../hooks/useComposerDictation";
|
||||
import { MicButton } from "./MicButton";
|
||||
import type { NativeStructureEmbed } from "@fusion/core";
|
||||
@@ -21,6 +22,7 @@ interface ComposeChatPanelProps {
|
||||
* typed text is replaced only after the composer confirms, while embeds remain untouched.
|
||||
*/
|
||||
export function ComposeChatPanel({ projectId, embeds, draftBody, onUseDraft, onClose }: ComposeChatPanelProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const chat = useChat(projectId);
|
||||
const [request, setRequest] = useState("Draft a clear report or approval message around the attached structure.");
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
@@ -107,14 +109,14 @@ export function ComposeChatPanel({ projectId, embeds, draftBody, onUseDraft, onC
|
||||
const latestDraft = chat.streamingText || [...chat.messages].reverse().find((message) => message.role === "assistant")?.content || "";
|
||||
|
||||
return (
|
||||
<section id="compose-chat-panel" className="compose-chat-panel" aria-label="Compose chat narrative helper" data-testid="compose-chat-panel">
|
||||
<label className="message-composer-label" htmlFor="compose-chat-request">Draft narrative</label>
|
||||
<section id="compose-chat-panel" className="compose-chat-panel" aria-label={t("composeChat.ariaLabel", "Compose chat narrative helper")} data-testid="compose-chat-panel">
|
||||
<label className="message-composer-label" htmlFor="compose-chat-request">{t("composeChat.draftNarrative", "Draft narrative")}</label>
|
||||
<textarea ref={requestRef} id="compose-chat-request" className="input compose-chat-panel__input" value={request} onChange={(event) => setRequest(event.target.value)} />
|
||||
<div className="compose-chat-panel__output" aria-live="polite">{latestDraft || "Ask the assistant to draft the narrative around your attached structures."}</div>
|
||||
<div className="compose-chat-panel__output" aria-live="polite">{latestDraft || t("composeChat.emptyDraft", "Ask the assistant to draft the narrative around your attached structures.")}</div>
|
||||
<div className="compose-chat-panel__actions"><MicButton {...dictation.micProps} />
|
||||
<button className="btn btn-sm btn-primary" type="button" onClick={() => void send()} disabled={chat.isStreaming || isCreating || hasPendingPrompt || !request.trim()}>Draft</button>
|
||||
<button className="btn btn-sm btn-secondary" type="button" onClick={() => latestDraft && onUseDraft(latestDraft)} disabled={!latestDraft}>Use draft</button>
|
||||
<button className="btn btn-sm btn-secondary" type="button" onClick={close}>Close</button>
|
||||
<button className="btn btn-sm btn-primary" type="button" onClick={() => void send()} disabled={chat.isStreaming || isCreating || hasPendingPrompt || !request.trim()}>{t("composeChat.draft", "Draft")}</button>
|
||||
<button className="btn btn-sm btn-secondary" type="button" onClick={() => latestDraft && onUseDraft(latestDraft)} disabled={!latestDraft}>{t("composeChat.useDraft", "Use draft")}</button>
|
||||
<button className="btn btn-sm btn-secondary" type="button" onClick={close}>{t("actions.close", "Close")}</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isArchivedColumnRole, isCompleteColumnRole } from "../utils/columnRoles";
|
||||
import { partitionRevertedTasks } from "../utils/taskRevert";
|
||||
import type { GithubIssueAction, Task, TaskDetail } from "@fusion/core";
|
||||
@@ -48,6 +49,7 @@ export function DockTaskList({ columnFlagsByTaskId,
|
||||
prAuthAvailable = false,
|
||||
autoMergeEnabled = false,
|
||||
}: DockTaskListProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const [showDone, setShowDone] = useState(false);
|
||||
|
||||
const handleOpenTask = useCallback((task: Task | TaskDetail) => {
|
||||
@@ -86,13 +88,13 @@ export function DockTaskList({ columnFlagsByTaskId,
|
||||
}), [showDone, tasks, isTerminal, revertedTasks]);
|
||||
const hasDoneTasks = doneTasks.length > 0;
|
||||
const isEmpty = visibleTasks.length === 0;
|
||||
const emptyTitle = tasks.length === 0 ? "No tasks yet" : "No active tasks";
|
||||
const emptyTitle = tasks.length === 0 ? t("rightDock.noTasksYet", "No tasks yet") : t("rightDock.noActiveTasks", "No active tasks");
|
||||
const emptyCopy = tasks.length === 0
|
||||
? "Tasks you create or import will appear here for quick right-sidebar review."
|
||||
? t("rightDock.emptyCopy", "Tasks you create or import will appear here for quick right-sidebar review.")
|
||||
: hasDoneTasks
|
||||
? "Completed tasks are hidden until you choose Show Done. Archived tasks stay out of this compact sidebar."
|
||||
: "Archived tasks stay out of this compact sidebar. Active tasks will appear here when work is available.";
|
||||
const toggleLabel = showDone ? "Hide Done" : "Show Done";
|
||||
? t("rightDock.doneHiddenCopy", "Completed tasks are hidden until you choose Show Done. Archived tasks stay out of this compact sidebar.")
|
||||
: t("rightDock.archivedCopy", "Archived tasks stay out of this compact sidebar. Active tasks will appear here when work is available.");
|
||||
const toggleLabel = showDone ? t("rightDock.hideDone", "Hide Done") : t("rightDock.showDone", "Show Done");
|
||||
|
||||
return (
|
||||
<div className={`dock-task-list${isEmpty ? " dock-task-list--empty" : ""}`} data-testid="dock-task-list">
|
||||
@@ -109,8 +111,8 @@ export function DockTaskList({ columnFlagsByTaskId,
|
||||
</div>
|
||||
) : null}
|
||||
{revertedTasks.length > 0 && (
|
||||
<section className="dock-task-list__reverted" aria-label="Reverted Tasks" data-testid="dock-reverted-tasks">
|
||||
<h3>Reverted Tasks</h3>
|
||||
<section className="dock-task-list__reverted" aria-label={t("tasks.revertedTasks", "Reverted Tasks")} data-testid="dock-reverted-tasks">
|
||||
<h3>{t("tasks.revertedTasks", "Reverted Tasks")}</h3>
|
||||
{revertedTasks.map((task) => <TaskCard key={`reverted-${task.id}`} task={task} taskColumnFlags={columnFlagsByTaskId?.get(task.id)} projectId={projectId} onOpenDetail={handleOpenTask} onDeleteTask={onDeleteTask} onReviseTask={onReviseTask} addToast={addToast} disableDrag />)}
|
||||
</section>
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { X } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { isFullScreenSheetViewport, isShortViewport, isTabletTouchViewport, useViewportMode } from "../hooks/useViewportMode";
|
||||
import { currentFloatingZ, currentTaskDetailFloatingZ, nextFloatingZ, nextTaskDetailFloatingZ } from "./floatingWindowStack";
|
||||
import { isInsidePortalSafeSurface } from "../utils/portalSurfaces";
|
||||
@@ -211,6 +212,7 @@ export function FloatingWindow({
|
||||
ariaLabel,
|
||||
ariaLabelledBy,
|
||||
}: FloatingWindowProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const resolvedMinSize: FloatingWindowSize = minSize ?? { width: DEFAULT_MIN_WIDTH, height: DEFAULT_MIN_HEIGHT };
|
||||
const viewportMode = useViewportMode();
|
||||
/*
|
||||
@@ -661,7 +663,7 @@ export function FloatingWindow({
|
||||
data-testid={`floating-window-resize-${direction}`}
|
||||
{...(hasTabletTouchGeometry ? { "data-resize-hit-target": "true" } : {})}
|
||||
role="separator"
|
||||
aria-label="Resize floating window"
|
||||
aria-label={t("floatingWindow.resize", "Resize floating window")}
|
||||
onPointerDown={(event) => handleResizePointerDown(event, direction)}
|
||||
/>
|
||||
))}
|
||||
@@ -677,7 +679,7 @@ export function FloatingWindow({
|
||||
type="button"
|
||||
className="floating-window__close"
|
||||
onClick={onClose}
|
||||
aria-label="Close floating window"
|
||||
aria-label={t("floatingWindow.close", "Close floating window")}
|
||||
data-testid={`floating-window-close-${windowKey}`}
|
||||
>
|
||||
<X size={18} />
|
||||
|
||||
@@ -1639,8 +1639,8 @@ export function GitHubImportModal({ isOpen, onClose, onImport, onPlanningMode, o
|
||||
*/}
|
||||
<div className="github-import-controls" data-testid="github-import-controls">
|
||||
<div className="github-import-provider" role="group" aria-label={t("git.providerAriaLabel", "Import provider")}>
|
||||
<button type="button" className={`github-import-tab ${provider === "github" ? "active" : ""}`} aria-pressed={provider === "github"} onClick={() => setProvider("github")} disabled={loading || importing}>GitHub</button>
|
||||
{gitlabEnabled ? <button type="button" className={`github-import-tab ${provider === "gitlab" ? "active" : ""}`} aria-pressed={provider === "gitlab"} onClick={() => setProvider("gitlab")} disabled={loading || importing}>GitLab</button> : null}
|
||||
<button type="button" className={`github-import-tab ${provider === "github" ? "active" : ""}`} aria-pressed={provider === "github"} onClick={() => setProvider("github")} disabled={loading || importing}>{t("githubImport.github", "GitHub")}</button>
|
||||
{gitlabEnabled ? <button type="button" className={`github-import-tab ${provider === "gitlab" ? "active" : ""}`} aria-pressed={provider === "gitlab"} onClick={() => setProvider("gitlab")} disabled={loading || importing}>{t("githubImport.gitlab", "GitLab")}</button> : null}
|
||||
</div>
|
||||
{provider === "github" && (<>
|
||||
{/* Tab Navigation */}
|
||||
|
||||
@@ -15,7 +15,8 @@ function EndpointButton({ id, onSelect }: { id: string; onSelect: (id: string) =
|
||||
}
|
||||
|
||||
function EdgeList({ title, edges, total, endpoint, onSelect }: { title: string; edges: KnowledgeGraphEdge[]; total: number; endpoint: "from" | "to"; onSelect: (id: string) => void }) {
|
||||
return <section className="knowledge-graph-edge-list"><h4>{title} <span>showing {edges.length} of {total}</span></h4>{edges.length === 0 ? <p className="knowledge-graph-muted">None</p> : <ul>{edges.map((edge) => <li key={edge.id}><span>{edge.kind}</span><span className={`knowledge-graph-provenance knowledge-graph-provenance--${edge.provenance}`}>{edge.provenance}</span><EndpointButton id={edge[endpoint]} onSelect={onSelect} /></li>)}</ul>}</section>;
|
||||
const { t } = useTranslation("app");
|
||||
return <section className="knowledge-graph-edge-list"><h4>{title} <span>{t("knowledgeGraph.showingOf", "showing {{shown}} of {{total}}", { shown: edges.length, total })}</span></h4>{edges.length === 0 ? <p className="knowledge-graph-muted">{t("knowledgeGraph.none", "None")}</p> : <ul>{edges.map((edge) => <li key={edge.id}><span>{edge.kind}</span><span className={`knowledge-graph-provenance knowledge-graph-provenance--${edge.provenance}`}>{edge.provenance}</span><EndpointButton id={edge[endpoint]} onSelect={onSelect} /></li>)}</ul>}</section>;
|
||||
}
|
||||
|
||||
export function KnowledgeGraphPanel({ projectId, addToast }: { projectId?: string; addToast: (message: string, type: "success" | "error" | "info") => void }) {
|
||||
@@ -44,25 +45,25 @@ export function KnowledgeGraphPanel({ projectId, addToast }: { projectId?: strin
|
||||
const foundHops = graph.pathResult?.outcome === "found" ? graph.pathResult.hops : null;
|
||||
|
||||
return <section className="knowledge-graph-panel" data-testid="knowledge-graph-panel">
|
||||
<header className="knowledge-graph-status card"><div><strong>{graph.status.nodeCount} {t("memory.graphNodes", "nodes")} · {graph.status.edgeCount} {t("memory.graphEdges", "edges")}</strong><span>{graph.status.graphDir}</span></div><dl><div><dt>Node kinds</dt><dd>{Object.entries(graph.status.nodeKindCounts ?? {}).map(([kind, count]) => `${kind}: ${count}`).join(" · ") || "—"}</dd></div><div><dt>Edge kinds</dt><dd>{Object.entries(graph.status.edgeKindCounts ?? {}).map(([kind, count]) => `${kind}: ${count}`).join(" · ") || "—"}</dd></div><div><dt>Provenance</dt><dd>{Object.entries(graph.status.provenanceCounts ?? {}).map(([kind, count]) => `${kind}: ${count}`).join(" · ") || "—"}</dd></div></dl><label className="knowledge-graph-toggle"><input type="checkbox" checked={force} onChange={(event) => setForce(event.target.checked)} /> {t("memory.graphForce", "Force rebuild")}</label><button type="button" className="btn" disabled={graph.rebuilding} onClick={() => void graph.rebuild(force)}>{graph.rebuilding ? t("memory.graphRebuilding", "Rebuilding…") : t("memory.graphRebuild", "Rebuild")}</button></header>
|
||||
<header className="knowledge-graph-status card"><div><strong>{graph.status.nodeCount} {t("memory.graphNodes", "nodes")} · {graph.status.edgeCount} {t("memory.graphEdges", "edges")}</strong><span>{graph.status.graphDir}</span></div><dl><div><dt>{t("knowledgeGraph.nodeKinds", "Node kinds")}</dt><dd>{Object.entries(graph.status.nodeKindCounts ?? {}).map(([kind, count]) => `${kind}: ${count}`).join(" · ") || "—"}</dd></div><div><dt>{t("knowledgeGraph.edgeKinds", "Edge kinds")}</dt><dd>{Object.entries(graph.status.edgeKindCounts ?? {}).map(([kind, count]) => `${kind}: ${count}`).join(" · ") || "—"}</dd></div><div><dt>{t("knowledgeGraph.provenance", "Provenance")}</dt><dd>{Object.entries(graph.status.provenanceCounts ?? {}).map(([kind, count]) => `${kind}: ${count}`).join(" · ") || "—"}</dd></div></dl><label className="knowledge-graph-toggle"><input type="checkbox" checked={force} onChange={(event) => setForce(event.target.checked)} /> {t("memory.graphForce", "Force rebuild")}</label><button type="button" className="btn" disabled={graph.rebuilding} onClick={() => void graph.rebuild(force)}>{graph.rebuilding ? t("memory.graphRebuilding", "Rebuilding…") : t("memory.graphRebuild", "Rebuild")}</button></header>
|
||||
|
||||
<section className="knowledge-graph-filters card" aria-label={t("memory.graphFilters", "Graph filters")}>
|
||||
<div className="knowledge-graph-kind-options">{NODE_KINDS.map((kind) => <label key={kind}><input type="checkbox" checked={(graph.filters.kinds ?? []).includes(kind)} onChange={() => updateKinds(kind)} /> {kind}</label>)}</div>
|
||||
<input className="input" aria-label="Path prefix" placeholder="Path prefix" value={graph.filters.pathPrefix ?? ""} onChange={(event) => setFilter({ pathPrefix: event.target.value })} />
|
||||
<input className="input" aria-label="Node id prefix" placeholder="Node id prefix" value={graph.filters.idPrefix ?? ""} onChange={(event) => setFilter({ idPrefix: event.target.value })} />
|
||||
<input className="input" aria-label="Name regex" placeholder="Name regex" value={graph.filters.namePattern ?? ""} onChange={(event) => setFilter({ namePattern: event.target.value })} />
|
||||
<select className="select" aria-label="FNXC area" value={graph.filters.fnxcArea ?? ""} onChange={(event) => setFilter({ fnxcArea: event.target.value || undefined })}><option value="">All FNXC areas</option>{graph.status.fnxcAreas?.map((area) => <option key={area} value={area}>{area}</option>)}</select>
|
||||
<select className="select" aria-label="Symbol kind" value={graph.filters.symbolKind ?? ""} onChange={(event) => setFilter({ symbolKind: event.target.value || undefined })}><option value="">All symbol kinds</option>{SYMBOL_KINDS.map((kind) => <option key={kind}>{kind}</option>)}</select>
|
||||
<select className="select" aria-label="Owner" value={graph.filters.owner ?? ""} onChange={(event) => setFilter({ owner: event.target.value || undefined })}><option value="">All owners</option><option value="file">file</option><option value="derived">derived</option></select>
|
||||
<select className="select" aria-label="Result limit" value={graph.filters.limit ?? 50} onChange={(event) => setFilter({ limit: Number(event.target.value) })}>{[25, 50, 100, 200].map((limit) => <option key={limit} value={limit}>{limit} results</option>)}</select>
|
||||
<select className="select" aria-label="FNXC area" value={graph.filters.fnxcArea ?? ""} onChange={(event) => setFilter({ fnxcArea: event.target.value || undefined })}><option value="">{t("knowledgeGraph.allFnxcAreas", "All FNXC areas")}</option>{graph.status.fnxcAreas?.map((area) => <option key={area} value={area}>{area}</option>)}</select>
|
||||
<select className="select" aria-label="Symbol kind" value={graph.filters.symbolKind ?? ""} onChange={(event) => setFilter({ symbolKind: event.target.value || undefined })}><option value="">{t("knowledgeGraph.allSymbolKinds", "All symbol kinds")}</option>{SYMBOL_KINDS.map((kind) => <option key={kind}>{kind}</option>)}</select>
|
||||
<select className="select" aria-label="Owner" value={graph.filters.owner ?? ""} onChange={(event) => setFilter({ owner: event.target.value || undefined })}><option value="">{t("knowledgeGraph.allOwners", "All owners")}</option><option value="file">{t("knowledgeGraph.ownerFile", "file")}</option><option value="derived">{t("knowledgeGraph.ownerDerived", "derived")}</option></select>
|
||||
<select className="select" aria-label="Result limit" value={graph.filters.limit ?? 50} onChange={(event) => setFilter({ limit: Number(event.target.value) })}>{[25, 50, 100, 200].map((limit) => <option key={limit} value={limit}>{t("knowledgeGraph.limitResults", "{{limit}} results", { limit })}</option>)}</select>
|
||||
<button type="button" className="btn btn-ghost" onClick={graph.clearFilters}>{t("memory.graphClearFilters", "Clear filters")}</button>
|
||||
</section>
|
||||
|
||||
<div className="knowledge-graph-layout"><section className="card knowledge-graph-results"><h3>{graph.nodes?.total ?? 0} {t("memory.graphResults", "results")}</h3>{graph.loading ? <p data-testid="knowledge-graph-searching">Searching…</p> : graph.nodes?.nodes.length ? <><ul>{graph.nodes.nodes.map((node) => <li key={node.id}><button type="button" className="btn btn-ghost" onClick={() => void graph.selectNode(node.id)}><strong>{node.kind}: {node.name}</strong><span>{node.id} · {node.ownerPath}:{node.source.line}</span></button></li>)}</ul><div className="knowledge-graph-pagination"><button type="button" className="btn btn-ghost" disabled={!graph.filters.offset} onClick={() => setFilter({ offset: Math.max(0, (graph.filters.offset ?? 0) - (graph.filters.limit ?? 50)) })}>Previous</button><button type="button" className="btn btn-ghost" disabled={(graph.filters.offset ?? 0) + (graph.filters.limit ?? 50) >= (graph.nodes?.total ?? 0)} onClick={() => setFilter({ offset: (graph.filters.offset ?? 0) + (graph.filters.limit ?? 50) })}>Next</button></div></> : <p data-testid="knowledge-graph-no-results">{t("memory.graphNoResults", "No matching nodes.")}</p>}</section>
|
||||
<section className="card knowledge-graph-detail">{graph.detail ? <><h3>{graph.detail.node.name}</h3><p>{graph.detail.node.id}</p><p>{graph.detail.node.ownerPath}:{graph.detail.node.source.line}</p><EdgeList title="Outgoing" edges={graph.detail.outgoing} total={graph.detail.outgoingTotal} endpoint="to" onSelect={(id) => void graph.selectNode(id)} /><EdgeList title="Incoming" edges={graph.detail.incoming} total={graph.detail.incomingTotal} endpoint="from" onSelect={(id) => void graph.selectNode(id)} /></> : <p className="knowledge-graph-muted">Select a node to inspect its edges.</p>}</section></div>
|
||||
<div className="knowledge-graph-layout"><section className="card knowledge-graph-results"><h3>{graph.nodes?.total ?? 0} {t("memory.graphResults", "results")}</h3>{graph.loading ? <p data-testid="knowledge-graph-searching">{t("knowledgeGraph.searching", "Searching…")}</p> : graph.nodes?.nodes.length ? <><ul>{graph.nodes.nodes.map((node) => <li key={node.id}><button type="button" className="btn btn-ghost" onClick={() => void graph.selectNode(node.id)}><strong>{node.kind}: {node.name}</strong><span>{node.id} · {node.ownerPath}:{node.source.line}</span></button></li>)}</ul><div className="knowledge-graph-pagination"><button type="button" className="btn btn-ghost" disabled={!graph.filters.offset} onClick={() => setFilter({ offset: Math.max(0, (graph.filters.offset ?? 0) - (graph.filters.limit ?? 50)) })}>{t("knowledgeGraph.previous", "Previous")}</button><button type="button" className="btn btn-ghost" disabled={(graph.filters.offset ?? 0) + (graph.filters.limit ?? 50) >= (graph.nodes?.total ?? 0)} onClick={() => setFilter({ offset: (graph.filters.offset ?? 0) + (graph.filters.limit ?? 50) })}>{t("knowledgeGraph.next", "Next")}</button></div></> : <p data-testid="knowledge-graph-no-results">{t("memory.graphNoResults", "No matching nodes.")}</p>}</section>
|
||||
<section className="card knowledge-graph-detail">{graph.detail ? <><h3>{graph.detail.node.name}</h3><p>{graph.detail.node.id}</p><p>{graph.detail.node.ownerPath}:{graph.detail.node.source.line}</p><EdgeList title={t("knowledgeGraph.outgoing", "Outgoing")} edges={graph.detail.outgoing} total={graph.detail.outgoingTotal} endpoint="to" onSelect={(id) => void graph.selectNode(id)} /><EdgeList title={t("knowledgeGraph.incoming", "Incoming")} edges={graph.detail.incoming} total={graph.detail.incomingTotal} endpoint="from" onSelect={(id) => void graph.selectNode(id)} /></> : <p className="knowledge-graph-muted">{t("knowledgeGraph.selectNodeHint", "Select a node to inspect its edges.")}</p>}</section></div>
|
||||
|
||||
{graph.detail && <section className="knowledge-graph-neighbors card"><h3>Neighbors</h3><select className="select" aria-label="Neighbor direction" value={graph.neighborOptions.direction} onChange={(event) => graph.setNeighborOptions({ ...graph.neighborOptions, direction: event.target.value as "out" | "in" | "both" })}><option value="out">Outgoing</option><option value="in">Incoming</option><option value="both">Both directions</option></select><select className="select" aria-label="Neighbor depth" value={graph.neighborOptions.depth} onChange={(event) => graph.setNeighborOptions({ ...graph.neighborOptions, depth: Number(event.target.value) })}>{[1, 2, 3].map((depth) => <option key={depth} value={depth}>Depth {depth}</option>)}</select><div className="knowledge-graph-kind-options">{EDGE_KINDS.map((kind) => <label key={kind}><input type="checkbox" checked={(graph.neighborOptions.edgeKinds ?? []).includes(kind)} onChange={() => updateEdgeKinds(kind)} /> {kind}</label>)}</div><button type="button" className="btn btn-ghost" onClick={() => void graph.refreshNeighbors()}>Refresh neighbors</button>{graph.neighborResults?.neighbors.length ? <ul>{graph.neighborResults.neighbors.map((neighbor) => <li key={neighbor.node.id}><span>distance {neighbor.distance}</span><EndpointButton id={neighbor.node.id} onSelect={(id) => void graph.selectNode(id)} /></li>)}</ul> : <p className="knowledge-graph-muted">No neighbors found.</p>}</section>}
|
||||
{graph.detail && <section className="knowledge-graph-neighbors card"><h3>{t("knowledgeGraph.neighbors", "Neighbors")}</h3><select className="select" aria-label="Neighbor direction" value={graph.neighborOptions.direction} onChange={(event) => graph.setNeighborOptions({ ...graph.neighborOptions, direction: event.target.value as "out" | "in" | "both" })}><option value="out">{t("knowledgeGraph.outgoing", "Outgoing")}</option><option value="in">{t("knowledgeGraph.incoming", "Incoming")}</option><option value="both">{t("knowledgeGraph.bothDirections", "Both directions")}</option></select><select className="select" aria-label="Neighbor depth" value={graph.neighborOptions.depth} onChange={(event) => graph.setNeighborOptions({ ...graph.neighborOptions, depth: Number(event.target.value) })}>{[1, 2, 3].map((depth) => <option key={depth} value={depth}>{t("knowledgeGraph.depth", "Depth {{depth}}", { depth })}</option>)}</select><div className="knowledge-graph-kind-options">{EDGE_KINDS.map((kind) => <label key={kind}><input type="checkbox" checked={(graph.neighborOptions.edgeKinds ?? []).includes(kind)} onChange={() => updateEdgeKinds(kind)} /> {kind}</label>)}</div><button type="button" className="btn btn-ghost" onClick={() => void graph.refreshNeighbors()}>{t("knowledgeGraph.refreshNeighbors", "Refresh neighbors")}</button>{graph.neighborResults?.neighbors.length ? <ul>{graph.neighborResults.neighbors.map((neighbor) => <li key={neighbor.node.id}><span>{t("knowledgeGraph.distance", "distance {{distance}}", { distance: neighbor.distance })}</span><EndpointButton id={neighbor.node.id} onSelect={(id) => void graph.selectNode(id)} /></li>)}</ul> : <p className="knowledge-graph-muted">{t("knowledgeGraph.noNeighbors", "No neighbors found.")}</p>}</section>}
|
||||
|
||||
<section className="knowledge-graph-path card"><h3>Shortest path</h3><input className="input" aria-label="From node id" placeholder="From node id" value={graph.pathInput.fromId} onChange={(event) => graph.setPathInput({ ...graph.pathInput, fromId: event.target.value })} /><input className="input" aria-label="To node id" placeholder="To node id" value={graph.pathInput.toId} onChange={(event) => graph.setPathInput({ ...graph.pathInput, toId: event.target.value })} /><input className="input" aria-label="Maximum hops" type="number" min="1" max={limits.maxHops} value={graph.pathInput.maxHops} onChange={(event) => graph.setPathInput({ ...graph.pathInput, maxHops: Number(event.target.value) })} />{selected && <><button type="button" className="btn btn-ghost" onClick={() => graph.setPathInput({ ...graph.pathInput, fromId: selected })}>Use selected as from</button><button type="button" className="btn btn-ghost" onClick={() => graph.setPathInput({ ...graph.pathInput, toId: selected })}>Use selected as to</button></>}<button type="button" className="btn" onClick={() => void graph.findPath()}>Find path</button>{foundPath && <div data-testid="knowledge-graph-path-found"><strong>{foundHops} hops</strong><div className="knowledge-graph-path-chain">{foundPath.nodes.map((node, index) => <span key={node.id}><EndpointButton id={node.id} onSelect={(id) => void graph.selectNode(id)} />{index < foundPath.edges.length && <em>{foundPath.edges[index].kind}</em>}</span>)}</div></div>}{graph.pathResult?.outcome === "not-found" && <p data-testid="knowledge-graph-path-not-found">No path exists between these nodes.</p>}{graph.pathResult?.outcome === "limit-reached" && <div data-testid="knowledge-graph-path-limit-reached"><p>No path found within {graph.pathResult.maxHops} hops; {graph.pathResult.limit} was reached.</p>{graph.pathInput.maxHops < limits.maxHops && <button type="button" className="btn btn-ghost" onClick={() => graph.setPathInput({ ...graph.pathInput, maxHops: Math.min(limits.maxHops, graph.pathInput.maxHops + 1) })}>Raise hop limit</button>}</div>}{graph.error && <p data-testid="knowledge-graph-path-error" className="knowledge-graph-error">{graph.error}</p>}</section>
|
||||
<section className="knowledge-graph-path card"><h3>{t("knowledgeGraph.shortestPath", "Shortest path")}</h3><input className="input" aria-label="From node id" placeholder="From node id" value={graph.pathInput.fromId} onChange={(event) => graph.setPathInput({ ...graph.pathInput, fromId: event.target.value })} /><input className="input" aria-label="To node id" placeholder="To node id" value={graph.pathInput.toId} onChange={(event) => graph.setPathInput({ ...graph.pathInput, toId: event.target.value })} /><input className="input" aria-label="Maximum hops" type="number" min="1" max={limits.maxHops} value={graph.pathInput.maxHops} onChange={(event) => graph.setPathInput({ ...graph.pathInput, maxHops: Number(event.target.value) })} />{selected && <><button type="button" className="btn btn-ghost" onClick={() => graph.setPathInput({ ...graph.pathInput, fromId: selected })}>{t("knowledgeGraph.useSelectedFrom", "Use selected as from")}</button><button type="button" className="btn btn-ghost" onClick={() => graph.setPathInput({ ...graph.pathInput, toId: selected })}>{t("knowledgeGraph.useSelectedTo", "Use selected as to")}</button></>}<button type="button" className="btn" onClick={() => void graph.findPath()}>{t("knowledgeGraph.findPath", "Find path")}</button>{foundPath && <div data-testid="knowledge-graph-path-found"><strong>{t("knowledgeGraph.hops", "{{count}} hops", { count: foundHops })}</strong><div className="knowledge-graph-path-chain">{foundPath.nodes.map((node, index) => <span key={node.id}><EndpointButton id={node.id} onSelect={(id) => void graph.selectNode(id)} />{index < foundPath.edges.length && <em>{foundPath.edges[index].kind}</em>}</span>)}</div></div>}{graph.pathResult?.outcome === "not-found" && <p data-testid="knowledge-graph-path-not-found">{t("knowledgeGraph.noPath", "No path exists between these nodes.")}</p>}{graph.pathResult?.outcome === "limit-reached" && <div data-testid="knowledge-graph-path-limit-reached"><p>{t("knowledgeGraph.pathLimitReached", "No path found within {{maxHops}} hops; {{limit}} was reached.", { maxHops: graph.pathResult.maxHops, limit: graph.pathResult.limit })}</p>{graph.pathInput.maxHops < limits.maxHops && <button type="button" className="btn btn-ghost" onClick={() => graph.setPathInput({ ...graph.pathInput, maxHops: Math.min(limits.maxHops, graph.pathInput.maxHops + 1) })}>{t("knowledgeGraph.raiseHopLimit", "Raise hop limit")}</button>}</div>}{graph.error && <p data-testid="knowledge-graph-path-error" className="knowledge-graph-error">{graph.error}</p>}</section>
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -3075,7 +3075,7 @@ export function ListView({
|
||||
/>
|
||||
</div>
|
||||
{partitionRevertedTasks(tasks).reverted.length > 0 && (
|
||||
<section className="list-reverted-tasks" aria-label="Reverted Tasks" data-testid="list-reverted-tasks">
|
||||
<section className="list-reverted-tasks" aria-label={t("tasks.revertedTasks", "Reverted Tasks")} data-testid="list-reverted-tasks">
|
||||
<h2>{t("tasks.revertedTasks", "Reverted Tasks")}</h2>
|
||||
{partitionRevertedTasks(tasks).reverted.map((task) => (
|
||||
<div key={`reverted-${task.id}`} className="list-card">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { memo, useMemo, useState, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { ArtifactType } from "@fusion/core";
|
||||
import { artifactMediaUrlWithToken } from "../api";
|
||||
|
||||
@@ -41,9 +42,10 @@ export const MailboxArtifactAttachment = memo(function MailboxArtifactAttachment
|
||||
onOpenTask,
|
||||
hideTaskLink = false,
|
||||
}: MailboxArtifactAttachmentProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const id = readString(artifactId);
|
||||
const type = readArtifactType(artifactType);
|
||||
const label = readString(title) ?? "artifact";
|
||||
const label = readString(title) ?? t("mailbox.artifact", "artifact");
|
||||
const mediaMimeType = readString(mimeType);
|
||||
const task = readString(taskId);
|
||||
const [imageFailed, setImageFailed] = useState(false);
|
||||
@@ -57,20 +59,20 @@ export const MailboxArtifactAttachment = memo(function MailboxArtifactAttachment
|
||||
href={mediaUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={`Open artifact: ${label}`}
|
||||
aria-label={t("mailbox.openArtifactAria", "Open artifact: {{label}}", { label })}
|
||||
>
|
||||
Open artifact
|
||||
{t("mailbox.openArtifact", "Open artifact")}
|
||||
</a>
|
||||
);
|
||||
const taskLink = task && onOpenTask && !hideTaskLink ? (
|
||||
<button
|
||||
type="button"
|
||||
className="mailbox-artifact-attachment__link btn"
|
||||
aria-label={`View task: ${task}`}
|
||||
aria-label={t("mailbox.viewTaskAria", "View task: {{id}}", { id: task })}
|
||||
data-testid="mailbox-artifact-view-task"
|
||||
onClick={() => onOpenTask(task)}
|
||||
>
|
||||
View task
|
||||
{t("mailbox.viewTaskLabel", "View task")}
|
||||
</button>
|
||||
) : null;
|
||||
|
||||
@@ -91,7 +93,7 @@ export const MailboxArtifactAttachment = memo(function MailboxArtifactAttachment
|
||||
className="mailbox-artifact-attachment__media"
|
||||
src={mediaUrl}
|
||||
controls
|
||||
aria-label={`Video artifact: ${label}`}
|
||||
aria-label={t("mailbox.videoArtifactAria", "Video artifact: {{label}}", { label })}
|
||||
/>
|
||||
);
|
||||
} else if (type === "audio") {
|
||||
@@ -100,7 +102,7 @@ export const MailboxArtifactAttachment = memo(function MailboxArtifactAttachment
|
||||
className="mailbox-artifact-attachment__audio"
|
||||
src={mediaUrl}
|
||||
controls
|
||||
aria-label={`Audio artifact: ${label}`}
|
||||
aria-label={t("mailbox.audioArtifactAria", "Audio artifact: {{label}}", { label })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -919,7 +919,7 @@ export function MailboxModal({
|
||||
<Send size={14} />
|
||||
<span>{t("mailbox.outboxTab", "Outbox")}</span>
|
||||
</button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => { consumeCurrentDeepLink(); setActiveTab("archived"); setSelectedMessage(null); }} data-testid="mailbox-tab-archived"><Archive size={14} /><span>Archived</span></button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => { consumeCurrentDeepLink(); setActiveTab("archived"); setSelectedMessage(null); }} data-testid="mailbox-tab-archived"><Archive size={14} /><span>{t("mailbox.archived", "Archived")}</span></button>
|
||||
<button
|
||||
className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "agents" ? "active" : ""}`}
|
||||
onClick={() => { consumeCurrentDeepLink(); setActiveTab("agents"); setSelectedMessage(null); }}
|
||||
@@ -959,7 +959,7 @@ export function MailboxModal({
|
||||
<span>{t("mailbox.replyButton", "Reply")}</span>
|
||||
</button>
|
||||
)}
|
||||
{selectedMessage.archived ? <button className="btn btn-sm btn-secondary" onClick={() => handleUnarchiveMessage(selectedMessage.id)} data-testid="mailbox-unarchive"><Archive size={14} /><span>Restore</span></button> : <button className="btn btn-sm btn-secondary" onClick={() => handleArchiveMessage(selectedMessage.id)} data-testid="mailbox-archive"><Archive size={14} /><span>Archive</span></button>}
|
||||
{selectedMessage.archived ? <button className="btn btn-sm btn-secondary" onClick={() => handleUnarchiveMessage(selectedMessage.id)} data-testid="mailbox-unarchive"><Archive size={14} /><span>{t("mailbox.restore", "Restore")}</span></button> : <button className="btn btn-sm btn-secondary" onClick={() => handleArchiveMessage(selectedMessage.id)} data-testid="mailbox-archive"><Archive size={14} /><span>{t("mailbox.archive", "Archive")}</span></button>}
|
||||
{pendingDeleteMessageId === selectedMessage.id ? (
|
||||
<>
|
||||
{/* FNXC:MessageArchive 2026-08-12-22:51: Deletion requires a second intentional click so archive remains the safe default removal action. */}
|
||||
@@ -1110,15 +1110,15 @@ export function MailboxModal({
|
||||
{/* Inbox Tab */}
|
||||
{isMailboxArchivedTab(activeTab) && (
|
||||
<div className="mailbox-list" data-testid="mailbox-archived-list">
|
||||
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">No archived messages</div>}
|
||||
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">{t("mailbox.noArchivedMessages", "No archived messages")}</div>}
|
||||
{archivedInbox?.messages.map((message) => <button type="button" className="mailbox-item" key={message.id} onClick={() => void handleOpenMessage(message)} data-testid={`mailbox-item-${message.id}`}>{message.content}</button>)}
|
||||
</div>
|
||||
)}
|
||||
{activeTab === "inbox" && (
|
||||
<div className="mailbox-list" data-testid="mailbox-inbox-list">
|
||||
<div className="mailbox-structural-filter" role="group" aria-label="Inbox filter">
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "all"} data-testid="mailbox-structural-filter-all" onClick={() => setStructuralFilter("all")}>All</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "structural"} data-testid="mailbox-structural-filter-structural" onClick={() => setStructuralFilter("structural")}>Reports & approvals</button>
|
||||
<div className="mailbox-structural-filter" role="group" aria-label={t("mailbox.inboxFilter", "Inbox filter")}>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "all"} data-testid="mailbox-structural-filter-all" onClick={() => setStructuralFilter("all")}>{t("mailbox.all", "All")}</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "structural"} data-testid="mailbox-structural-filter-structural" onClick={() => setStructuralFilter("structural")}>{t("mailbox.reportsApprovals", "Reports & approvals")}</button>
|
||||
</div>
|
||||
{isLoading && !inbox && <MailboxSkeleton />}
|
||||
{inbox && inbox.messages.length === 0 && (
|
||||
|
||||
@@ -18,6 +18,7 @@ export function MailboxKindBadge({ metadata }: { metadata?: MessageMetadata }) {
|
||||
}
|
||||
|
||||
function MailboxApprovalDecision({ approvalRequestId, projectId, addToast, onDecided }: { approvalRequestId?: string; projectId?: string; addToast?: (message: string, type?: "success" | "error") => void; onDecided?: () => void }) {
|
||||
const { t } = useTranslation("app");
|
||||
const [detail, setDetail] = useState<ApprovalRequestDetail | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [unresolvable, setUnresolvable] = useState(false);
|
||||
@@ -67,15 +68,15 @@ function MailboxApprovalDecision({ approvalRequestId, projectId, addToast, onDec
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <span className="mailbox-approval-loading">Loading approval…</span>;
|
||||
if (unresolvable || !detail) return <span className="mailbox-approval-unresolvable" data-testid="mailbox-approval-unresolvable">This approval request is no longer available.</span>;
|
||||
if (loading) return <span className="mailbox-approval-loading">{t("mailbox.loadingApproval", "Loading approval…")}</span>;
|
||||
if (unresolvable || !detail) return <span className="mailbox-approval-unresolvable" data-testid="mailbox-approval-unresolvable">{t("mailbox.approvalUnavailable", "This approval request is no longer available.")}</span>;
|
||||
const status: ApprovalRequestStatus = detail.status;
|
||||
if (status !== "pending") return <span className="mailbox-inline-approval-status" data-testid="mailbox-inline-approval-status">Approval {status}</span>;
|
||||
if (status !== "pending") return <span className="mailbox-inline-approval-status" data-testid="mailbox-inline-approval-status">{t("mailbox.approvalStatus", "Approval {{status}}", { status })}</span>;
|
||||
return <div className="mailbox-inline-approval">
|
||||
<textarea className="input" value={comment} onChange={(event) => setComment(event.target.value)} placeholder="Optional comment" data-testid="mailbox-inline-approval-comment" />
|
||||
<textarea className="input" value={comment} onChange={(event) => setComment(event.target.value)} placeholder={t("mailbox.optionalComment", "Optional comment")} data-testid="mailbox-inline-approval-comment" />
|
||||
<div className="mailbox-inline-approval-actions">
|
||||
<button type="button" className="btn btn-sm btn-primary" disabled={deciding} onClick={() => void decide("approve")} data-testid="mailbox-inline-approval-approve">Approve</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" disabled={deciding} onClick={() => void decide("deny")} data-testid="mailbox-inline-approval-deny">Deny</button>
|
||||
<button type="button" className="btn btn-sm btn-primary" disabled={deciding} onClick={() => void decide("approve")} data-testid="mailbox-inline-approval-approve">{t("mailbox.approve", "Approve")}</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" disabled={deciding} onClick={() => void decide("deny")} data-testid="mailbox-inline-approval-deny">{t("mailbox.deny", "Deny")}</button>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { MessageMetadata } from "@fusion/core";
|
||||
import { createProposedTask } from "../api";
|
||||
import "./MailboxTaskProposal.css";
|
||||
|
||||
export function MailboxTaskProposal({ messageId, metadata, projectId, onOpenTask, onCreated }: { messageId: string; metadata?: MessageMetadata; projectId?: string; onOpenTask?: (id: string) => void; onCreated?: () => void }) {
|
||||
const { t } = useTranslation("app");
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [currentMetadata, setCurrentMetadata] = useState(metadata);
|
||||
useEffect(() => setCurrentMetadata(metadata), [metadata]);
|
||||
@@ -25,9 +27,9 @@ export function MailboxTaskProposal({ messageId, metadata, projectId, onOpenTask
|
||||
|
||||
return <section className="mailbox-task-proposal" data-testid="mailbox-task-proposal">
|
||||
<strong>{proposal.title}</strong><p>{proposal.description}</p>
|
||||
{status === "pending" && <button type="button" className="btn" disabled={creating} onClick={() => void create()}>{creating ? "Creating task…" : "Create task"}</button>}
|
||||
{status === "creating" && <button type="button" className="btn" disabled>Creating task…</button>}
|
||||
{status === "created" && currentMetadata.createdTaskId && <button type="button" className="btn" onClick={() => onOpenTask?.(currentMetadata.createdTaskId!)}>Task {currentMetadata.createdTaskId} created — View task</button>}
|
||||
{status === "dismissed" && <span>Task proposal dismissed</span>}
|
||||
{status === "pending" && <button type="button" className="btn" disabled={creating} onClick={() => void create()}>{creating ? t("mailbox.creatingTask", "Creating task…") : t("mailbox.createTask", "Create task")}</button>}
|
||||
{status === "creating" && <button type="button" className="btn" disabled>{t("mailbox.creatingTask", "Creating task…")}</button>}
|
||||
{status === "created" && currentMetadata.createdTaskId && <button type="button" className="btn" onClick={() => onOpenTask?.(currentMetadata.createdTaskId!)}>{t("mailbox.taskCreatedView", "Task {{id}} created — View task", { id: currentMetadata.createdTaskId })}</button>}
|
||||
{status === "dismissed" && <span>{t("mailbox.taskProposalDismissed", "Task proposal dismissed")}</span>}
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { MessageMetadata, TaskRecommendation } from "@fusion/core";
|
||||
import { createTaskFromRecommendation, fetchTaskDetail } from "../api";
|
||||
import "./MailboxTaskRecommendations.css";
|
||||
@@ -26,6 +27,7 @@ export function MailboxTaskRecommendations({
|
||||
projectId?: string;
|
||||
onOpenTask?: (taskId: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation("app");
|
||||
const target = getNoticeTarget(metadata);
|
||||
const [recommendations, setRecommendations] = useState<TaskRecommendation[] | null>(null);
|
||||
const [unavailable, setUnavailable] = useState(false);
|
||||
@@ -97,10 +99,10 @@ export function MailboxTaskRecommendations({
|
||||
}
|
||||
};
|
||||
|
||||
if (unavailable) return <p className="mailbox-task-recommendations__unavailable" data-testid="mailbox-task-recommendations-unavailable">Recommendations are no longer available.</p>;
|
||||
if (unavailable) return <p className="mailbox-task-recommendations__unavailable" data-testid="mailbox-task-recommendations-unavailable">{t("mailbox.recommendationsUnavailable", "Recommendations are no longer available.")}</p>;
|
||||
if (!recommendations) return null;
|
||||
|
||||
return <section className="mailbox-task-recommendations" data-testid="mailbox-task-recommendations" aria-label="Task recommendations">
|
||||
return <section className="mailbox-task-recommendations" data-testid="mailbox-task-recommendations" aria-label={t("mailbox.taskRecommendations", "Task recommendations")}>
|
||||
{recommendations.map((recommendation) => {
|
||||
const actionKey = `${target.taskId}:${recommendation.id}`;
|
||||
const createdTaskId = recommendation.createdTaskId ?? createdIds[actionKey];
|
||||
@@ -112,13 +114,13 @@ export function MailboxTaskRecommendations({
|
||||
<p>{recommendation.description}</p>
|
||||
</div>
|
||||
{createdTaskId ? (
|
||||
<button type="button" className="btn btn-primary" onClick={() => onOpenTask?.(createdTaskId)}>View task {createdTaskId}</button>
|
||||
<button type="button" className="btn btn-primary" onClick={() => onOpenTask?.(createdTaskId)}>{t("mailbox.viewTask", "View task {{id}}", { id: createdTaskId })}</button>
|
||||
) : (
|
||||
<div className="mailbox-task-recommendations__action">
|
||||
<button type="button" className="btn btn-primary" disabled={creating} onClick={() => void createRecommendation(recommendation)}>
|
||||
{creating ? "Creating…" : failed ? "Retry creating task" : "Create task"}
|
||||
{creating ? t("mailbox.creatingTask", "Creating…") : failed ? t("mailbox.retryCreatingTask", "Retry creating task") : t("mailbox.createTask", "Create task")}
|
||||
</button>
|
||||
{failed && <span className="mailbox-task-recommendations__error" role="status">Could not create task. Try again.</span>}
|
||||
{failed && <span className="mailbox-task-recommendations__error" role="status">{t("mailbox.createTaskError", "Could not create task. Try again.")}</span>}
|
||||
</div>
|
||||
)}
|
||||
</article>;
|
||||
|
||||
@@ -1017,11 +1017,11 @@ export function MailboxView({
|
||||
)}
|
||||
{selectedMessage.archived ? (
|
||||
<button className="btn btn-sm btn-secondary" onClick={() => handleUnarchiveMessage(selectedMessage.id)} data-testid="mailbox-unarchive">
|
||||
<Archive size={14} /><span>Restore</span>
|
||||
<Archive size={14} /><span>{t("mailbox.restore", "Restore")}</span>
|
||||
</button>
|
||||
) : (
|
||||
<button className="btn btn-sm btn-secondary" onClick={() => handleArchiveMessage(selectedMessage.id)} data-testid="mailbox-archive">
|
||||
<Archive size={14} /><span>Archive</span>
|
||||
<Archive size={14} /><span>{t("mailbox.archive", "Archive")}</span>
|
||||
</button>
|
||||
)}
|
||||
{pendingDeleteMessageId === selectedMessage.id ? (
|
||||
@@ -1153,7 +1153,7 @@ export function MailboxView({
|
||||
{isMailboxArchivedTab(activeTab) && (
|
||||
<div className="mailbox-list" data-testid="mailbox-archived-list">
|
||||
{isLoading && !archivedInbox && <MailboxSkeleton />}
|
||||
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">No archived messages</div>}
|
||||
{archivedInbox?.messages.length === 0 && <div className="mailbox-empty" data-testid="mailbox-archived-empty">{t("mailbox.noArchivedMessages", "No archived messages")}</div>}
|
||||
{archivedInbox?.messages.map((message) => (
|
||||
<button type="button" className="mailbox-item" key={message.id} onClick={() => void handleOpenMessage(message)} data-testid={`mailbox-item-${message.id}`}>
|
||||
<span className="mailbox-item-preview">{message.content}</span>
|
||||
@@ -1163,9 +1163,9 @@ export function MailboxView({
|
||||
)}
|
||||
{activeTab === "inbox" && (
|
||||
<div className="mailbox-list" data-testid="mailbox-inbox-list">
|
||||
<div className="mailbox-structural-filter" role="group" aria-label="Inbox filter">
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "all"} data-testid="mailbox-structural-filter-all" onClick={() => setStructuralFilter("all")}>All</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "structural"} data-testid="mailbox-structural-filter-structural" onClick={() => setStructuralFilter("structural")}>Reports & approvals</button>
|
||||
<div className="mailbox-structural-filter" role="group" aria-label={t("mailbox.inboxFilter", "Inbox filter")}>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "all"} data-testid="mailbox-structural-filter-all" onClick={() => setStructuralFilter("all")}>{t("mailbox.all", "All")}</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={structuralFilter === "structural"} data-testid="mailbox-structural-filter-structural" onClick={() => setStructuralFilter("structural")}>{t("mailbox.reportsApprovals", "Reports & approvals")}</button>
|
||||
</div>
|
||||
{isLoading && !inbox && <MailboxSkeleton />}
|
||||
{inbox && inbox.messages.length === 0 && (
|
||||
@@ -1626,7 +1626,7 @@ export function MailboxView({
|
||||
<Send size={14} />
|
||||
<span>{t("mailbox.outbox", "Outbox")}</span>
|
||||
</button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => handleSelectTab("archived")} data-testid="mailbox-tab-archived">Archived</button>
|
||||
<button className={`btn btn-sm btn-secondary mailbox-tab ${isMailboxArchivedTab(activeTab) ? "active" : ""}`} onClick={() => handleSelectTab("archived")} data-testid="mailbox-tab-archived">{t("mailbox.archived", "Archived")}</button>
|
||||
<button
|
||||
className={`btn btn-sm btn-secondary mailbox-tab ${activeTab === "agents" ? "active" : ""}`}
|
||||
onClick={() => handleSelectTab("agents")}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { memo, useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/*
|
||||
FNXC:Markdown 2026-06-23-03:15:
|
||||
@@ -39,6 +40,7 @@ export const MermaidDiagram = memo(function MermaidDiagram({
|
||||
chart,
|
||||
testId,
|
||||
}: MermaidDiagramProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const [errored, setErrored] = useState(false);
|
||||
|
||||
@@ -89,7 +91,7 @@ export const MermaidDiagram = memo(function MermaidDiagram({
|
||||
ref={containerRef}
|
||||
className="mailbox-mermaid"
|
||||
data-testid={testId}
|
||||
aria-label="Mermaid diagram"
|
||||
aria-label={t("mermaid.diagram", "Mermaid diagram")}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -7,10 +7,10 @@ import type { NodeMeshState } from "@fusion/core";
|
||||
* MeshTopology was repurposed for the shared-PostgreSQL mesh. Previously it
|
||||
* visualized peer SYNC state (which snapshots were exchanged between nodes);
|
||||
* now it visualizes active engine CONNECTIONS — which engines are connected to
|
||||
* the shared PG database, what tasks they're executing, and their heartbeat
|
||||
* the shared PG database, what work items they're executing, and their heartbeat
|
||||
* status. The node/peer topology data still comes from NodeMeshState (mDNS
|
||||
* discovery + central registry, both PG-backed). The optional `engines` prop
|
||||
* surfaces per-engine runtime status (in-flight tasks, active agents, last
|
||||
* surfaces per-engine runtime status (in-flight work items, active agents, last
|
||||
* activity) read directly from shared PG via GET /api/mesh/engines.
|
||||
*/
|
||||
|
||||
@@ -143,7 +143,7 @@ function MeshTopologyInner({ nodes, engines, className }: MeshTopologyProps): Re
|
||||
<li key={engine.projectId} className="mesh-topology__engine-item">
|
||||
<span className="mesh-topology__engine-name">{engine.projectName ?? engine.projectId}</span>
|
||||
<span className="mesh-topology__engine-status" data-runtime-status={engine.runtimeStatus}>{engine.runtimeStatus}</span>
|
||||
<span className="mesh-topology__engine-tasks">{engine.inFlightTasks} tasks</span>
|
||||
<span className="mesh-topology__engine-tasks">{t("mesh.taskCount", "{{count}} tasks", { count: engine.inFlightTasks })}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -242,7 +242,7 @@ export function MessageComposer({
|
||||
<div
|
||||
className={`message-composer${isNativeStructureDragOver ? " message-composer--native-structure-drag-over" : ""}`}
|
||||
data-testid="message-composer"
|
||||
aria-label="Message composer; drop a structure to attach it"
|
||||
aria-label={t("composer.ariaLabel", "Message composer; drop a structure to attach it")}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleDrop}
|
||||
@@ -261,9 +261,9 @@ export function MessageComposer({
|
||||
|
||||
<div className="message-composer-body">
|
||||
{/* FNXC:StructuralMail 2026-08-09-12:41: Quick mail remains the default and never adds metadata; report validation stays at send time so recipient gating remains independent. FN-8870 requires at least one complete section for structural reports. */}
|
||||
<div className="message-composer-mode" role="group" aria-label="Message mode">
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={mode === "quick"} data-testid="message-composer-mode-quick" onClick={() => { setMode("quick"); setIsComposeChatOpen(false); }}>Quick message</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={mode === "report"} data-testid="message-composer-mode-report" onClick={() => { setMode("report"); setIsComposeChatOpen(true); }}>Report</button>
|
||||
<div className="message-composer-mode" role="group" aria-label={t("composer.mode", "Message mode")}>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={mode === "quick"} data-testid="message-composer-mode-quick" onClick={() => { setMode("quick"); setIsComposeChatOpen(false); }}>{t("composer.quickMessage", "Quick message")}</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" aria-pressed={mode === "report"} data-testid="message-composer-mode-report" onClick={() => { setMode("report"); setIsComposeChatOpen(true); }}>{t("composer.report", "Report")}</button>
|
||||
</div>
|
||||
{/* Recipient selection */}
|
||||
{!recipient && (
|
||||
@@ -312,13 +312,13 @@ export function MessageComposer({
|
||||
)}
|
||||
|
||||
{mode === "report" && <div className="message-composer-report" data-testid="message-composer-report">
|
||||
<div className="message-composer-field"><label className="message-composer-label" htmlFor="report-title">Report title</label><input id="report-title" className="input" value={reportTitle} onChange={(event) => setReportTitle(event.target.value)} data-testid="report-title" /></div>
|
||||
<div className="message-composer-field"><label className="message-composer-label" htmlFor="report-title">{t("composer.reportTitle", "Report title")}</label><input id="report-title" className="input" value={reportTitle} onChange={(event) => setReportTitle(event.target.value)} data-testid="report-title" /></div>
|
||||
{sections.map((section) => <div className="message-composer-report-section" key={section.id}>
|
||||
<input className="input" value={section.heading} placeholder="Section heading" onChange={(event) => setSections((current) => current.map((item) => item.id === section.id ? { ...item, heading: event.target.value } : item))} data-testid={`report-section-heading-${section.id}`} />
|
||||
<textarea className="message-composer-textarea" value={section.body} placeholder="Section body" onChange={(event) => setSections((current) => current.map((item) => item.id === section.id ? { ...item, body: event.target.value } : item))} data-testid={`report-section-body-${section.id}`} />
|
||||
<button type="button" className="btn btn-sm btn-secondary" onClick={() => setSections((current) => current.filter((item) => item.id !== section.id))} data-testid={`report-section-remove-${section.id}`}>Remove section</button>
|
||||
<input className="input" value={section.heading} placeholder={t("composer.sectionHeading", "Section heading")} onChange={(event) => setSections((current) => current.map((item) => item.id === section.id ? { ...item, heading: event.target.value } : item))} data-testid={`report-section-heading-${section.id}`} />
|
||||
<textarea className="message-composer-textarea" value={section.body} placeholder={t("composer.sectionBody", "Section body")} onChange={(event) => setSections((current) => current.map((item) => item.id === section.id ? { ...item, body: event.target.value } : item))} data-testid={`report-section-body-${section.id}`} />
|
||||
<button type="button" className="btn btn-sm btn-secondary" onClick={() => setSections((current) => current.filter((item) => item.id !== section.id))} data-testid={`report-section-remove-${section.id}`}>{t("composer.removeSection", "Remove section")}</button>
|
||||
</div>)}
|
||||
<button type="button" className="btn btn-sm btn-secondary" onClick={() => setSections((current) => [...current, { id: sectionIdRef.current++, heading: "", body: "" }])} data-testid="report-section-add">Add section</button>
|
||||
<button type="button" className="btn btn-sm btn-secondary" onClick={() => setSections((current) => [...current, { id: sectionIdRef.current++, heading: "", body: "" }])} data-testid="report-section-add">{t("composer.addSection", "Add section")}</button>
|
||||
</div>}
|
||||
|
||||
{/* Content */}
|
||||
@@ -351,7 +351,7 @@ export function MessageComposer({
|
||||
persisted message metadata so reports carry first-class, independently reviewable embeds.
|
||||
*/}
|
||||
<div className="message-composer-field message-composer-field--structures">
|
||||
<label className="message-composer-label" htmlFor="message-native-structure">Attach structure</label>
|
||||
<label className="message-composer-label" htmlFor="message-native-structure">{t("composer.attachStructure", "Attach structure")}</label>
|
||||
<div className="message-composer-structure-controls">
|
||||
<select
|
||||
id="message-native-structure"
|
||||
@@ -361,7 +361,7 @@ export function MessageComposer({
|
||||
onChange={(event) => attachNativeStructure(event.target.value)}
|
||||
data-testid="message-composer-attach-structure"
|
||||
>
|
||||
<option value="">{nativeStructureCandidates.length === 0 ? "No structures available" : "Select structure…"}</option>
|
||||
<option value="">{nativeStructureCandidates.length === 0 ? t("composer.noStructures", "No structures available") : t("composer.selectStructure", "Select structure…")}</option>
|
||||
{nativeStructureCandidates.map((candidate, index) => (
|
||||
<option key={`${candidate.ref.kind}:${candidate.ref.id}`} value={index}>{candidate.ref.kind}: {candidate.label}</option>
|
||||
))}
|
||||
@@ -375,7 +375,7 @@ export function MessageComposer({
|
||||
capturedLabel={embed.label}
|
||||
onOpen={openNativeStructure}
|
||||
/>
|
||||
<button className="btn btn-sm btn-secondary" type="button" onClick={() => setNativeStructures((current) => current.filter((currentEmbed) => currentEmbed.kind !== embed.kind || currentEmbed.id !== embed.id))} aria-label={`Remove ${embed.label ?? embed.id}`}>Remove</button>
|
||||
<button className="btn btn-sm btn-secondary" type="button" onClick={() => setNativeStructures((current) => current.filter((currentEmbed) => currentEmbed.kind !== embed.kind || currentEmbed.id !== embed.id))} aria-label={t("composer.removeStructure", "Remove {{label}}", { label: embed.label ?? embed.id })}>{t("actions.remove", "Remove")}</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { memo, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BarChart3, CircleAlert, Flag, Lightbulb, Map, Target } from "lucide-react";
|
||||
import type { NativeStructurePreviewResult, NativeStructureRef } from "@fusion/core";
|
||||
import { fetchNativeStructurePreview } from "../api";
|
||||
@@ -39,6 +40,7 @@ function unavailableLabel(kind: string): string {
|
||||
* Roadmap items join this shared renderer with the roadmap icon and callback-only open action.
|
||||
*/
|
||||
export const NativeStructurePreview = memo(function NativeStructurePreview({ ref, payload, capturedLabel, onOpen }: NativeStructurePreviewProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const supportedKind = isSupportedKind(ref.kind);
|
||||
const refKey = `${ref.kind}\u0000${ref.id}\u0000${ref.projectId ?? ""}`;
|
||||
const [fetchedPayload, setFetchedPayload] = useState<{ refKey: string; result: NativeStructurePreviewResult } | undefined>();
|
||||
@@ -72,7 +74,7 @@ export const NativeStructurePreview = memo(function NativeStructurePreview({ ref
|
||||
return (
|
||||
<section className="native-structure-preview native-structure-preview--unavailable" data-testid="native-structure-preview-unavailable" data-reason="missing">
|
||||
<Icon aria-hidden="true" />
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">Preview unavailable</span><p>This structure is unavailable.</p></div>
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">{t("nativeStructure.previewUnavailable", "Preview unavailable")}</span><p>{t("nativeStructure.unavailable", "This structure is unavailable.")}</p></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -82,7 +84,7 @@ export const NativeStructurePreview = memo(function NativeStructurePreview({ ref
|
||||
return (
|
||||
<section className="native-structure-preview native-structure-preview--unavailable" data-testid="native-structure-preview-error">
|
||||
<Icon aria-hidden="true" />
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">Preview unavailable</span><p>Could not load this {unavailableLabel(ref.kind)}.</p></div>
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">{t("nativeStructure.previewUnavailable", "Preview unavailable")}</span><p>{t("nativeStructure.loadFailed", "Could not load this {{kind}}.", { kind: unavailableLabel(ref.kind) })}</p></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -91,7 +93,7 @@ export const NativeStructurePreview = memo(function NativeStructurePreview({ ref
|
||||
return (
|
||||
<section className="native-structure-preview" data-testid="native-structure-preview-loading" aria-busy="true">
|
||||
<Icon aria-hidden="true" />
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">Loading {unavailableLabel(ref.kind)}</span></div>
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">{t("nativeStructure.loading", "Loading {{kind}}", { kind: unavailableLabel(ref.kind) })}</span></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -100,7 +102,7 @@ export const NativeStructurePreview = memo(function NativeStructurePreview({ ref
|
||||
return (
|
||||
<section className="native-structure-preview native-structure-preview--unavailable" data-testid="native-structure-preview-unavailable" data-reason={result.reason}>
|
||||
<Icon aria-hidden="true" />
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">{unavailableLabel(result.kind)}</span><strong className="native-structure-preview__title">{capturedLabel?.trim() || "Preview unavailable"}</strong><p>This structure is unavailable.</p></div>
|
||||
<div className="native-structure-preview__content"><span className="native-structure-preview__label">{unavailableLabel(result.kind)}</span><strong className="native-structure-preview__title">{capturedLabel?.trim() || t("nativeStructure.previewUnavailable", "Preview unavailable")}</strong><p>{t("nativeStructure.unavailable", "This structure is unavailable.")}</p></div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -113,8 +115,8 @@ export const NativeStructurePreview = memo(function NativeStructurePreview({ ref
|
||||
<strong className="native-structure-preview__title">{result.title}</strong>
|
||||
<p className="native-structure-preview__excerpt">{result.excerpt}</p>
|
||||
</div>
|
||||
<button className="btn native-structure-preview__open" type="button" onClick={() => onOpen(ref, result)} aria-label={`Open ${result.kindLabel}: ${result.title}`}>
|
||||
Open
|
||||
<button className="btn native-structure-preview__open" type="button" onClick={() => onOpen(ref, result)} aria-label={t("nativeStructure.openAria", "Open {{kind}}: {{title}}", { kind: result.kindLabel, title: result.title })}>
|
||||
{t("actions.open", "Open")}
|
||||
</button>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLayoutEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createPortal } from "react-dom";
|
||||
import { CheckCircle2, ExternalLink, Loader2, X } from "lucide-react";
|
||||
import { OAuthManualCodeForm } from "./OAuthManualCodeForm";
|
||||
@@ -67,6 +68,7 @@ export function ProviderLoginDialog({
|
||||
onCancel,
|
||||
"data-testid": testId,
|
||||
}: ProviderLoginDialogProps) {
|
||||
const { t } = useTranslation("app");
|
||||
/*
|
||||
FNXC:ProviderAuth 2026-08-18-04:20:
|
||||
Claim the top of the shared floating stack ONCE on open, the same way ConfirmDialog does. The first
|
||||
@@ -106,10 +108,10 @@ export function ProviderLoginDialog({
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
onFocus={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="modal provider-login-dialog" role="dialog" aria-modal="true" aria-label={`Signing in to ${providerName}`}>
|
||||
<div className="modal provider-login-dialog" role="dialog" aria-modal="true" aria-label={t("providerLogin.signingInTo", "Signing in to {{provider}}", { provider: providerName })}>
|
||||
<div className="modal-header">
|
||||
<h3>Signing in to {providerName}</h3>
|
||||
<button className="modal-close" onClick={onCancel} aria-label="Cancel login" title="Cancel login">
|
||||
<h3>{t("providerLogin.signingInTo", "Signing in to {{provider}}", { provider: providerName })}</h3>
|
||||
<button className="modal-close" onClick={onCancel} aria-label={t("providerLogin.cancel", "Cancel login")} title={t("providerLogin.cancel", "Cancel login")}>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -121,15 +123,15 @@ export function ProviderLoginDialog({
|
||||
{phase === "waiting" ? <Loader2 size={16} className="provider-login-dialog__spinner" /> : <CheckCircle2 size={16} />}
|
||||
</span>
|
||||
<span className="provider-login-dialog__step-body">
|
||||
<strong>Approve the sign-in in your browser</strong>
|
||||
<strong>{t("providerLogin.approveInBrowser", "Approve the sign-in in your browser")}</strong>
|
||||
<small>
|
||||
{phase === "waiting"
|
||||
? "A tab should have opened. Finish signing in there — this dialog stays put."
|
||||
: "Authorization received."}
|
||||
? t("providerLogin.finishInBrowser", "A tab should have opened. Finish signing in there — this dialog stays put.")
|
||||
: t("providerLogin.authorizationReceived", "Authorization received.")}
|
||||
</small>
|
||||
{authUrl && phase === "waiting" && (
|
||||
<button className="btn btn-sm provider-login-dialog__reopen" onClick={onOpenAuthUrl}>
|
||||
<ExternalLink size={14} /> Open the sign-in page again
|
||||
<ExternalLink size={14} /> {t("providerLogin.openSignInAgain", "Open the sign-in page again")}
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
@@ -140,13 +142,13 @@ export function ProviderLoginDialog({
|
||||
{phase === "submitting" ? <Loader2 size={16} className="provider-login-dialog__spinner" /> : <CheckCircle2 size={16} />}
|
||||
</span>
|
||||
<span className="provider-login-dialog__step-body">
|
||||
<strong>Hand the authorization back to Fusion</strong>
|
||||
<strong>{t("providerLogin.handAuthorizationBack", "Hand the authorization back to Fusion")}</strong>
|
||||
<small>
|
||||
{phase === "submitting"
|
||||
? "Exchanging the authorization code…"
|
||||
? t("providerLogin.exchangingCode", "Exchanging the authorization code…")
|
||||
: phase === "succeeded"
|
||||
? "Connected."
|
||||
: "Usually automatic. If your browser lands on an error page, paste that page's full URL below."}
|
||||
? t("providerLogin.connected", "Connected.")
|
||||
: t("providerLogin.pasteRedirectUrl", "Usually automatic. If your browser lands on an error page, paste that page's full URL below.")}
|
||||
</small>
|
||||
</span>
|
||||
</li>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Bug, Lightbulb, LifeBuoy, MessageSquare } from "lucide-react";
|
||||
import type { ReportActionType } from "@fusion/core";
|
||||
import "./ReportActionMenu.css";
|
||||
@@ -13,6 +14,7 @@ const actions: Array<{ type: ReportActionType; label: string; Icon: typeof Bug }
|
||||
|
||||
/** Four guided entry points share the same report pipeline rather than issue textboxes. */
|
||||
export function ReportActionMenu({ onSelect }: { onSelect: (action: ReportActionType) => void }) {
|
||||
const { t } = useTranslation("app");
|
||||
const [open, setOpen] = useState(false);
|
||||
const [position, setPosition] = useState<{ top: number; left: number; right: number }>();
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
@@ -71,14 +73,14 @@ export function ReportActionMenu({ onSelect }: { onSelect: (action: ReportAction
|
||||
role="menu"
|
||||
style={{ top: position.top, left: position.left, right: position.right }}
|
||||
>
|
||||
{actions.map(({ type, label, Icon }) => <button className="report-action-menu__item" type="button" role="menuitem" key={type} onClick={() => { setOpen(false); onSelect(type); }}><Icon aria-hidden="true" />{label}</button>)}
|
||||
{actions.map(({ type, label, Icon }) => <button className="report-action-menu__item" type="button" role="menuitem" key={type} onClick={() => { setOpen(false); onSelect(type); }}><Icon aria-hidden="true" />{t(`report.actions.${type}`, label)}</button>)}
|
||||
</div>,
|
||||
document.body,
|
||||
)
|
||||
: null;
|
||||
|
||||
return <div className="report-action-menu">
|
||||
<button ref={triggerRef} className="btn btn-secondary" type="button" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen((value) => !value)}>Report</button>
|
||||
<button ref={triggerRef} className="btn btn-secondary" type="button" aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen((value) => !value)}>{t("report.menu", "Report")}</button>
|
||||
{menu}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -77,20 +77,20 @@ setResult(await reportFile({ actionType, targetType, report: result.report, endo
|
||||
} finally { setBusy(false); }
|
||||
};
|
||||
return <div className="report-modal-backdrop" role="presentation"><section className="card report-modal" role="dialog" aria-modal="true" aria-label={`${actionType} report`}>
|
||||
<button className="btn-icon report-modal__close" type="button" aria-label="Close report" onClick={onClose}>×</button>
|
||||
<button className="btn-icon report-modal__close" type="button" aria-label={t("report.close", "Close report")} onClick={onClose}>×</button>
|
||||
{error && <p className="report-modal__error" role="alert">{error}</p>}
|
||||
{!result && <><h2>{actionType[0].toUpperCase() + actionType.slice(1)}</h2><label htmlFor="report-prompt">{prompts[actionType]}</label><textarea id="report-prompt" className="input" value={prompt} onChange={(event) => setPrompt(event.target.value)} maxLength={4000} />
|
||||
{/* FNXC:ReportPipeline 2026-07-19-10:00: Screenshot storage is opt-in and
|
||||
requires retention confirmation before its artifact reference is sent. A
|
||||
capture that finishes after opt-out is discarded rather than restoring it. */}
|
||||
<label className="report-modal__screenshot-option"><input type="checkbox" checked={screenshotEnabled} onChange={(event) => { const enabled = event.target.checked; const generation = ++captureGeneration.current; setScreenshotEnabled(enabled); setScreenshotArtifactId(undefined); setRetentionConfirmed(false); if (enabled) void captureScreenshot(generation); else setBusy(false); }} /> Store a screenshot locally</label>
|
||||
<label className="report-modal__screenshot-option"><input type="checkbox" checked={screenshotEnabled} onChange={(event) => { const enabled = event.target.checked; const generation = ++captureGeneration.current; setScreenshotEnabled(enabled); setScreenshotArtifactId(undefined); setRetentionConfirmed(false); if (enabled) void captureScreenshot(generation); else setBusy(false); }} /> {t("report.storeScreenshot", "Store a screenshot locally")}</label>
|
||||
{screenshotEnabled && <div className="report-modal__screenshot-preview">
|
||||
{screenshotArtifactId ? <label className="report-modal__screenshot-option"><input type="checkbox" checked={retentionConfirmed} onChange={(event) => setRetentionConfirmed(event.target.checked)} /> I confirm Fusion may retain this screenshot locally for this report.</label> : <p>Capturing and storing locally…</p>}
|
||||
{screenshotArtifactId ? <label className="report-modal__screenshot-option"><input type="checkbox" checked={retentionConfirmed} onChange={(event) => setRetentionConfirmed(event.target.checked)} /> {t("report.confirmScreenshotRetention", "I confirm Fusion may retain this screenshot locally for this report.")}</label> : <p>{t("report.capturingScreenshot", "Capturing and storing locally…")}</p>}
|
||||
</div>}
|
||||
<label htmlFor="report-target">{t("report.targetLabel", "Filing target")}</label><select id="report-target" className="input" value={targetType ?? ""} onChange={(event) => setTargetType((event.target.value || undefined) as ReportTarget | undefined)}><option value="">{t("report.targetInherit", "Use configured action target")}</option><option value="issue">{t("report.targetIssue", "GitHub Issue")}</option><option value="discussion">{t("report.targetDiscussion", "GitHub Discussion")}</option></select>
|
||||
<details className="report-modal__activity-trace"><summary>Activity trace to send</summary><ul>{getRecentActivity().map((entry, index) => <li key={`${entry}-${index}`}>{entry}</li>)}</ul></details>
|
||||
<details className="report-modal__activity-trace"><summary>{t("report.activityTrace", "Activity trace to send")}</summary><ul>{getRecentActivity().map((entry, index) => <li key={`${entry}-${index}`}>{entry}</li>)}</ul></details>
|
||||
<button className="btn btn-primary" type="button" disabled={!prompt.trim() || busy} onClick={() => void submit()}>{error ? "Retry" : "Continue"}</button></>}
|
||||
{result?.kind === "draft-ready" && result.report && <><h2>Review your report</h2><label htmlFor="report-review-prompt">Report summary</label><textarea id="report-review-prompt" className="input" value={result.report.userPrompt} onChange={(event) => {
|
||||
{result?.kind === "draft-ready" && result.report && <><h2>{t("report.review", "Review your report")}</h2><label htmlFor="report-review-prompt">{t("report.summary", "Report summary")}</label><textarea id="report-review-prompt" className="input" value={result.report.userPrompt} onChange={(event) => {
|
||||
const userPrompt = event.target.value;
|
||||
// FNXC:ReportPipeline 2026-07-16-18:45:
|
||||
// Keep the original derivation marker when the guided prompt changes.
|
||||
@@ -98,7 +98,7 @@ setResult(await reportFile({ actionType, targetType, report: result.report, endo
|
||||
// context, rather than discarding the report's reproduction/environment
|
||||
// sections while the user is editing a draft.
|
||||
setResult({ ...result, report: { ...result.report!, userPrompt } });
|
||||
}} /><label htmlFor="report-review-body">Structured report</label><textarea id="report-review-body" className="input" value={result.report.body ?? ""} onChange={(event) => setResult({ ...result, report: { ...result.report!, body: event.target.value } })} /><button className="btn btn-primary" type="button" disabled={busy} onClick={() => void file()}>File report</button></>}
|
||||
}} /><label htmlFor="report-review-body">{t("report.structured", "Structured report")}</label><textarea id="report-review-body" className="input" value={result.report.body ?? ""} onChange={(event) => setResult({ ...result, report: { ...result.report!, body: event.target.value } })} /><button className="btn btn-primary" type="button" disabled={busy} onClick={() => void file()}>{t("report.file", "File report")}</button></>}
|
||||
{result?.kind === "duplicate-found" && result.issue && result.report && <>
|
||||
{/*
|
||||
FNXC:ReportPipeline 2026-07-16-21:30:
|
||||
@@ -107,27 +107,27 @@ setResult(await reportFile({ actionType, targetType, report: result.report, endo
|
||||
instead of posting a dedupe match immediately.
|
||||
*/}
|
||||
{/* FNXC:ReportPipeline 2026-07-18-20:45: A public-roadmap duplicate is endorsed through the same reviewed data-point UI as an issue, so reporters strengthen the tracked item rather than opening a parallel thread. */}
|
||||
<h2>{result.issue.roadmap ? t("report.roadmapDuplicate.title", "Already on the roadmap — add your data point?") : "Review data point for a similar open issue"}</h2>
|
||||
<h2>{result.issue.roadmap ? t("report.roadmapDuplicate.title", "Already on the roadmap — add your data point?") : t("report.duplicateReview", "Review data point for a similar open issue")}</h2>
|
||||
<a href={result.issue.url} target="_blank" rel="noreferrer">{result.issue.title}</a>
|
||||
<label htmlFor="report-duplicate-prompt">Report summary</label>
|
||||
<label htmlFor="report-duplicate-prompt">{t("report.summary", "Report summary")}</label>
|
||||
<textarea id="report-duplicate-prompt" className="input" value={result.report.userPrompt} onChange={(event) => {
|
||||
const userPrompt = event.target.value;
|
||||
setResult({ ...result, report: { ...result.report!, userPrompt } });
|
||||
}} />
|
||||
<label htmlFor="report-duplicate-body">Structured data point</label>
|
||||
<label htmlFor="report-duplicate-body">{t("report.structuredDataPoint", "Structured data point")}</label>
|
||||
<textarea id="report-duplicate-body" className="input" value={result.report.body ?? ""} onChange={(event) => setResult({ ...result, report: { ...result.report!, body: event.target.value } })} />
|
||||
<button className="btn btn-primary" type="button" disabled={busy} onClick={() => void file(result.issue!.discussionId ? undefined : result.issue!.roadmap ? undefined : result.issue!.number, result.issue!.discussionId, result.issue!.roadmap ? result.issue!.number : undefined)}>Confirm and add data point</button>
|
||||
<button className="btn btn-primary" type="button" disabled={busy} onClick={() => void file(result.issue!.discussionId ? undefined : result.issue!.roadmap ? undefined : result.issue!.number, result.issue!.discussionId, result.issue!.roadmap ? result.issue!.number : undefined)}>{t("report.confirmDataPoint", "Confirm and add data point")}</button>
|
||||
</>}
|
||||
|
||||
{(result?.kind === "filed" || result?.kind === "endorsed") && <>
|
||||
{/* FNXC:ReportPipeline 2026-07-18-12:30: When disabled Discussions fall back to
|
||||
Issues, state the actual filed destination rather than implying the report became a Discussion. */}
|
||||
<h2>{result.kind === "filed" && result.destination === "issue" ? "Report filed as an Issue" : "Report sent"}</h2><a href={result.url} target="_blank" rel="noreferrer">View on GitHub</a>{result.report?.body && <><label htmlFor="filed-report">Final report</label><textarea id="filed-report" className="input" value={result.report.body} readOnly /></>}</>}
|
||||
<h2>{result.kind === "filed" && result.destination === "issue" ? "Report filed as an Issue" : "Report sent"}</h2><a href={result.url} target="_blank" rel="noreferrer">{t("report.viewGitHub", "View on GitHub")}</a>{result.report?.body && <><label htmlFor="filed-report">{t("report.final", "Final report")}</label><textarea id="filed-report" className="input" value={result.report.body} readOnly /></>}</>}
|
||||
|
||||
{result?.kind === "help" && <><h2>Suggested help</h2><p>{result.answer?.summary ?? result.answer?.content}</p></>}
|
||||
{result?.kind === "help" && <><h2>{t("report.suggestedHelp", "Suggested help")}</h2><p>{result.answer?.summary ?? result.answer?.content}</p></>}
|
||||
{result?.kind === "unavailable" && <>
|
||||
<p role="alert">{result.message}</p>
|
||||
<button className="btn btn-secondary" type="button" onClick={() => { setResult(undefined); setError(undefined); }}>Return to prompt</button>
|
||||
<button className="btn btn-secondary" type="button" onClick={() => { setResult(undefined); setError(undefined); }}>{t("report.returnToPrompt", "Return to prompt")}</button>
|
||||
</>}
|
||||
</section></div>;
|
||||
}
|
||||
|
||||
@@ -419,6 +419,7 @@ and a dashed-edge variant of the existing `.task-chat-entry` block (tokens only,
|
||||
component), and `role="status"` so assistive tech announces it as a state message, not prose.
|
||||
*/
|
||||
function TaskChatLogGapNotice({ entry }: { entry: AgentLogEntry }) {
|
||||
const { t } = useTranslation("app");
|
||||
return (
|
||||
<article
|
||||
className="task-chat-entry task-chat-entry--gap"
|
||||
@@ -428,8 +429,8 @@ function TaskChatLogGapNotice({ entry }: { entry: AgentLogEntry }) {
|
||||
<div className="task-chat-entry-label-row">
|
||||
<span className="status-dot status-dot--error" aria-hidden="true" />
|
||||
<AlertTriangle size={14} aria-hidden="true" />
|
||||
<span className="task-chat-entry-kicker">Missing output</span>
|
||||
<TaskChatTimestamp timestamp={entry.timestamp} label="Missing output timestamp" />
|
||||
<span className="task-chat-entry-kicker">{t("taskChat.missingOutput", "Missing output")}</span>
|
||||
<TaskChatTimestamp timestamp={entry.timestamp} label={t("taskChat.missingOutputTimestamp", "Missing output timestamp")} />
|
||||
</div>
|
||||
<div className="task-chat-entry-text">{entry.text}</div>
|
||||
</article>
|
||||
@@ -437,6 +438,7 @@ function TaskChatLogGapNotice({ entry }: { entry: AgentLogEntry }) {
|
||||
}
|
||||
|
||||
function TaskChatText({ entries }: { entries: AgentLogEntry[] }) {
|
||||
const { t } = useTranslation("app");
|
||||
const firstEntry = entries[0];
|
||||
if (!firstEntry) return null;
|
||||
if (isLogGapMarker(firstEntry)) return <TaskChatLogGapNotice entry={firstEntry} />;
|
||||
@@ -449,8 +451,8 @@ function TaskChatText({ entries }: { entries: AgentLogEntry[] }) {
|
||||
{firstEntry.type === "status" && (
|
||||
<div className="task-chat-entry-label-row">
|
||||
<span className="status-dot status-dot--pending" aria-hidden="true" />
|
||||
<span className="task-chat-entry-kicker">Status update</span>
|
||||
<TaskChatTimestamp timestamp={getLatestEntryTimestamp(entries)} label="Status update timestamp" />
|
||||
<span className="task-chat-entry-kicker">{t("taskChat.statusUpdate", "Status update")}</span>
|
||||
<TaskChatTimestamp timestamp={getLatestEntryTimestamp(entries)} label={t("taskChat.statusUpdateTimestamp", "Status update timestamp")} />
|
||||
</div>
|
||||
)}
|
||||
{firstEntry.type !== "status" && <TaskChatTimestampMeta timestamp={getLatestEntryTimestamp(entries)} label="Text block timestamp" />}
|
||||
|
||||
@@ -461,7 +461,7 @@ export type TaskDetailContentProps = Omit<TaskDetailModalProps, "onClose"> & {
|
||||
onBackToBoard?: () => void;
|
||||
/*
|
||||
FNXC:FloatingWindow 2026-06-22-20:45:
|
||||
onPopOut, when supplied, renders a Maximize2 "Pop out" button in the gray header. List/Board wire it to push this task into App's floating task-detail window array, opening the same embedded TaskDetailContent inside a movable, resizable, non-blocking FloatingWindow. It is independent of embedded/onBackToBoard so List split-pane and the board full-panel can both expose it.
|
||||
onPopOut, when supplied, renders the header's Maximize2 pop-out button. List/Board wire it to push this task into App's floating task-detail window array, opening the same embedded TaskDetailContent inside a movable, resizable, non-blocking FloatingWindow. It is independent of embedded/onBackToBoard so List split-pane and the board full-panel can both expose it.
|
||||
*/
|
||||
onPopOut?: (task: Task) => void;
|
||||
/*
|
||||
@@ -4767,7 +4767,7 @@ export function TaskDetailContent({
|
||||
className="modal-edit-btn"
|
||||
onClick={() => onPopOut(task)}
|
||||
title={t("taskDetail.header.popOut", "Pop out")}
|
||||
aria-label="Pop out"
|
||||
aria-label={t("taskDetail.header.popOut", "Pop out")}
|
||||
data-testid="task-detail-pop-out"
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
@@ -7002,35 +7002,35 @@ export function TaskDetailContent({
|
||||
})()}
|
||||
{/* FNXC:SpecLockTaskDetail 2026-08-15-12:54: Spec alignment is low-frequency lock/hash provenance, so it renders LAST in the Definition (Plan) tab — operators opening Plan must see plan content first, not the alignment report. Keep this block at the tail of the Definition fragment. */}
|
||||
{specLock && (
|
||||
<section className="detail-section spec-lock-report" data-testid="spec-lock-report" aria-label="Spec lock alignment">
|
||||
<section className="detail-section spec-lock-report" data-testid="spec-lock-report" aria-label={t("taskDetail.specLock.alignmentLabel", "Spec lock alignment")}>
|
||||
<div className="detail-source-header">
|
||||
<div className="detail-source-summary">
|
||||
<span className="detail-source-label">Spec alignment</span>
|
||||
<span className="detail-source-label">{t("taskDetail.specLock.alignment", "Spec alignment")}</span>
|
||||
<span className="badge">{specLock.report?.alignment ?? "unavailable"}</span>
|
||||
</div>
|
||||
</div>
|
||||
<dl className="detail-source-grid">
|
||||
<div><dt>Latest lock</dt><dd>v{specLock.latestLock?.version ?? "—"}</dd></div>
|
||||
<div><dt>Current plan</dt><dd>v{specLock.currentPlan?.version ?? "—"}</dd></div>
|
||||
<div><dt>Lock state</dt><dd>{specLock.activeLock ? "active" : "inactive"}</dd></div>
|
||||
<div><dt>Findings</dt><dd>{specLock.report?.findings.length ?? 0}</dd></div>
|
||||
<div><dt>{t("taskDetail.specLock.latestLock", "Latest lock")}</dt><dd>v{specLock.latestLock?.version ?? "—"}</dd></div>
|
||||
<div><dt>{t("taskDetail.specLock.currentPlan", "Current plan")}</dt><dd>v{specLock.currentPlan?.version ?? "—"}</dd></div>
|
||||
<div><dt>{t("taskDetail.specLock.lockState", "Lock state")}</dt><dd>{specLock.activeLock ? "active" : "inactive"}</dd></div>
|
||||
<div><dt>{t("taskDetail.specLock.findings", "Findings")}</dt><dd>{specLock.report?.findings.length ?? 0}</dd></div>
|
||||
</dl>
|
||||
{specLock.latestLock && (
|
||||
<p className="spec-lock-provenance">
|
||||
Accepted {specLock.latestLock.acceptedAt} · plan hash {specLock.latestLock.currentPlanHash} · approval {specLock.latestLock.approvalFingerprint}
|
||||
{t("taskDetail.specLock.accepted", "Accepted {{acceptedAt}} · plan hash {{planHash}} · approval {{approval}}", { acceptedAt: specLock.latestLock.acceptedAt, planHash: specLock.latestLock.currentPlanHash, approval: specLock.latestLock.approvalFingerprint })}
|
||||
</p>
|
||||
)}
|
||||
{specLock.currentPlan && (
|
||||
<p className="spec-lock-provenance">
|
||||
Captured {specLock.currentPlan.capturedAt} · source revision {specLock.currentPlan.sourceRevision} · source hash {specLock.currentPlan.sourceHash}
|
||||
{t("taskDetail.specLock.captured", "Captured {{capturedAt}} · source revision {{sourceRevision}} · source hash {{sourceHash}}", { capturedAt: specLock.currentPlan.capturedAt, sourceRevision: specLock.currentPlan.sourceRevision, sourceHash: specLock.currentPlan.sourceHash })}
|
||||
</p>
|
||||
)}
|
||||
{specLock.latestLock?.diff?.changedSections.length ? (
|
||||
<p className="spec-lock-provenance">Re-lock changed: {specLock.latestLock.diff.changedSections.join(", ")}</p>
|
||||
<p className="spec-lock-provenance">{t("taskDetail.specLock.relockChanged", "Re-lock changed: {{sections}}", { sections: specLock.latestLock.diff.changedSections.join(", ") })}</p>
|
||||
) : null}
|
||||
{(specLock.history?.locks.length ?? 0) > 1 || (specLock.history?.currentPlans.length ?? 0) > 1 || (specLock.history?.reports.length ?? 0) > 1 ? (
|
||||
<p className="spec-lock-provenance">
|
||||
Retained history: {specLock.history.locks.map((lock) => `lock v${lock.version}`).join(", ") || "no locks"}; {specLock.history.currentPlans.map((plan) => `plan v${plan.version}`).join(", ") || "no plan evidence"}; {specLock.history.reports.length} reports
|
||||
{t("taskDetail.specLock.retainedHistory", "Retained history: {{locks}}; {{plans}}; {{reports}} reports", { locks: specLock.history.locks.map((lock) => `lock v${lock.version}`).join(", ") || "no locks", plans: specLock.history.currentPlans.map((plan) => `plan v${plan.version}`).join(", ") || "no plan evidence", reports: specLock.history.reports.length })}
|
||||
</p>
|
||||
) : null}
|
||||
{specLock.report?.findings.length ? (
|
||||
@@ -7200,8 +7200,8 @@ export function TaskDetailContent({
|
||||
*/}
|
||||
{isTaskReverted(task.sourceMetadata) && (
|
||||
<>
|
||||
<button className="btn btn-sm btn-danger" onClick={handleDelete} aria-label="Delete reverted task">Delete</button>
|
||||
{onReviseTask && <button className="btn btn-sm" onClick={() => { onReviseTask(task); requestClose?.(); }}>Revise</button>}
|
||||
<button className="btn btn-sm btn-danger" onClick={handleDelete} aria-label={t("taskDetail.reverted.deleteAria", "Delete reverted task")}>{t("taskDetail.delete.btn", "Delete")}</button>
|
||||
{onReviseTask && <button className="btn btn-sm" onClick={() => { onReviseTask(task); requestClose?.(); }}>{t("taskDetail.revise", "Revise")}</button>}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { TaskVerificationRequest } from "@fusion/core";
|
||||
import { AlertCircle, CheckCircle2, Loader2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./TaskVerificationStatus.css";
|
||||
|
||||
function formatDuration(durationMs: number | undefined): string | null {
|
||||
@@ -14,22 +15,23 @@ function formatDuration(durationMs: number | undefined): string | null {
|
||||
* command control or reimplements the chat/executor permission boundary.
|
||||
*/
|
||||
export function TaskVerificationStatus({ request, compact = false }: { request: TaskVerificationRequest | null; compact?: boolean }) {
|
||||
const { t } = useTranslation("app");
|
||||
if (!request) return null;
|
||||
|
||||
const running = request.status === "requested" || request.status === "running";
|
||||
const failed = request.status === "failed" || request.status === "rejected";
|
||||
const Icon = running ? Loader2 : failed ? AlertCircle : CheckCircle2;
|
||||
const summary = request.status === "rejected"
|
||||
? request.rejectionReason ?? "Request rejected"
|
||||
? request.rejectionReason ?? t("taskVerification.requestRejected", "Request rejected")
|
||||
: request.result
|
||||
? `${request.result.success ? "Passed" : "Failed"}${formatDuration(request.result.durationMs) ? ` · ${formatDuration(request.result.durationMs)}` : ""}`
|
||||
: request.status === "requested" ? "Queued for the task executor" : "Running in the task worktree";
|
||||
? `${request.result.success ? t("taskVerification.passed", "Passed") : t("taskVerification.failed", "Failed")}${formatDuration(request.result.durationMs) ? ` · ${formatDuration(request.result.durationMs)}` : ""}`
|
||||
: request.status === "requested" ? t("taskVerification.queued", "Queued for the task executor") : t("taskVerification.running", "Running in the task worktree");
|
||||
|
||||
return (
|
||||
<section className={`task-verification-status task-verification-status--${request.status}${compact ? " task-verification-status--compact" : ""}`} aria-live="polite" data-testid="task-verification-status">
|
||||
<div className="task-verification-status__heading">
|
||||
<Icon aria-hidden="true" className={running ? "task-verification-status__spinner" : undefined} />
|
||||
<strong>Verification · {request.profile}</strong>
|
||||
<strong>{t("taskVerification.heading", "Verification · {{profile}}", { profile: request.profile })}</strong>
|
||||
<span className="task-verification-status__state">{request.status}</span>
|
||||
</div>
|
||||
<p>{summary}</p>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import "./WhatsAppChatPairingPanel.css";
|
||||
|
||||
type WhatsAppStatus = {
|
||||
@@ -27,6 +28,7 @@ function pluginUrl(path: string, projectId?: string): string {
|
||||
* WhatsApp pairing belongs in Plugin Manager settings: operators need a scannable QR, connection feedback, and configuration guidance without discovering raw plugin API routes.
|
||||
*/
|
||||
export function WhatsAppChatPairingPanel({ projectId, settings }: WhatsAppChatPairingPanelProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const [connection, setConnection] = useState<WhatsAppStatus | null>(null);
|
||||
const [loadError, setLoadError] = useState<string | null>(null);
|
||||
const [phoneNumber, setPhoneNumber] = useState(() => String(settings?.pairingPhoneNumber ?? ""));
|
||||
@@ -108,56 +110,56 @@ export function WhatsAppChatPairingPanel({ projectId, settings }: WhatsAppChatPa
|
||||
<section className="whatsapp-pairing-panel" aria-labelledby="whatsapp-pairing-heading">
|
||||
<div className="whatsapp-pairing-heading-row">
|
||||
<div>
|
||||
<h5 id="whatsapp-pairing-heading" className="plugin-detail-section-heading">WhatsApp pairing</h5>
|
||||
<p className="whatsapp-pairing-description">Pair and monitor this project's WhatsApp connection here.</p>
|
||||
<h5 id="whatsapp-pairing-heading" className="plugin-detail-section-heading">{t("whatsapp.pairing", "WhatsApp pairing")}</h5>
|
||||
<p className="whatsapp-pairing-description">{t("whatsapp.description", "Pair and monitor this project's WhatsApp connection here.")}</p>
|
||||
</div>
|
||||
<button className="btn btn-secondary btn-sm" type="button" onClick={() => void refreshStatus()}>Refresh status</button>
|
||||
<button className="btn btn-secondary btn-sm" type="button" onClick={() => void refreshStatus()}>{t("whatsapp.refreshStatus", "Refresh status")}</button>
|
||||
</div>
|
||||
|
||||
<div className={`whatsapp-pairing-status whatsapp-pairing-status--${state}`} role="status" data-testid="whatsapp-pairing-status">
|
||||
<strong>Status: {state}</strong>
|
||||
{connection?.jid && <span>Connected as {connection.jid}</span>}
|
||||
<strong>{t("whatsapp.status", "Status:")} {state}</strong>
|
||||
{connection?.jid && <span>{t("whatsapp.connectedAs", "Connected as {{jid}}", { jid: connection.jid })}</span>}
|
||||
{(connection?.lastError || loadError) && <span className="field-error">{connection?.lastError ?? loadError}</span>}
|
||||
</div>
|
||||
|
||||
{state === "awaiting-qr" && (
|
||||
<div className="whatsapp-pairing-qr" data-testid="whatsapp-pairing-qr">
|
||||
{connection?.qrDataUrl ? (
|
||||
<img src={connection.qrDataUrl} alt="WhatsApp pairing QR code" className="whatsapp-pairing-qr-image" />
|
||||
<img src={connection.qrDataUrl} alt={t("whatsapp.qrCode", "WhatsApp pairing QR code")} className="whatsapp-pairing-qr-image" />
|
||||
) : (
|
||||
<p className="text-muted">Waiting for a fresh QR code. Keep this panel open and refresh if needed.</p>
|
||||
<p className="text-muted">{t("whatsapp.waitingQr", "Waiting for a fresh QR code. Keep this panel open and refresh if needed.")}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(pairingMode === "code" || state === "awaiting-code") && (
|
||||
<div className="whatsapp-pairing-code">
|
||||
<label htmlFor="whatsapp-pairing-phone">Phone number (E.164 digits without +)</label>
|
||||
<label htmlFor="whatsapp-pairing-phone">{t("whatsapp.phoneNumber", "Phone number (E.164 digits without +)")}</label>
|
||||
<div className="whatsapp-pairing-code-controls">
|
||||
<input id="whatsapp-pairing-phone" className="input" value={phoneNumber} onChange={(event) => setPhoneNumber(event.target.value)} inputMode="numeric" />
|
||||
<button className="btn btn-secondary" type="button" onClick={() => void requestPairingCode()} disabled={busy !== null}>
|
||||
{busy === "code" ? "Requesting code..." : "Request pairing code"}
|
||||
{busy === "code" ? t("whatsapp.requestingCode", "Requesting code...") : t("whatsapp.requestPairingCode", "Request pairing code")}
|
||||
</button>
|
||||
</div>
|
||||
{connection?.pairingCode && <output className="whatsapp-pairing-code-output">{connection.pairingCode}</output>}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state === "connected" && <p className="whatsapp-pairing-success">WhatsApp is paired and ready to receive messages from allowed senders.</p>}
|
||||
{state === "connected" && <p className="whatsapp-pairing-success">{t("whatsapp.connectedSuccess", "WhatsApp is paired and ready to receive messages from allowed senders.")}</p>}
|
||||
{actionError && <p className="field-error">{actionError}</p>}
|
||||
|
||||
<button className="btn btn-danger" type="button" onClick={() => void logoutForRepair()} disabled={busy !== null} data-testid="whatsapp-pairing-logout">
|
||||
{busy === "logout" ? "Logging out..." : "Logout and re-pair"}
|
||||
{busy === "logout" ? t("whatsapp.loggingOut", "Logging out...") : t("whatsapp.logoutRepair", "Logout and re-pair")}
|
||||
</button>
|
||||
|
||||
<aside className="whatsapp-pairing-instructions" aria-label="WhatsApp pairing instructions" data-testid="whatsapp-pairing-instructions">
|
||||
<h6>Pairing and configuration</h6>
|
||||
<aside className="whatsapp-pairing-instructions" aria-label={t("whatsapp.instructionsLabel", "WhatsApp pairing instructions")} data-testid="whatsapp-pairing-instructions">
|
||||
<h6>{t("whatsapp.pairingConfiguration", "Pairing and configuration")}</h6>
|
||||
<ol>
|
||||
<li>Install and enable this plugin, then keep this settings panel open.</li>
|
||||
<li>Set <strong>Allowed WhatsApp Senders</strong>; an empty list blocks all inbound messages.</li>
|
||||
<li>Choose <strong>QR</strong> to scan in WhatsApp Linked Devices, or <strong>code</strong> to enter a phone number and request a pairing code.</li>
|
||||
<li>Wait for the status above to become <strong>connected</strong>.</li>
|
||||
<li>Use Logout and re-pair to start over. If QR is still pending, wait briefly or refresh status for a new code.</li>
|
||||
<li>{t("whatsapp.installPlugin", "Install and enable this plugin, then keep this settings panel open.")}</li>
|
||||
<li>{t("whatsapp.allowedSenders", "Set {{label}}; an empty list blocks all inbound messages.", { label: "Allowed WhatsApp Senders" })}</li>
|
||||
<li>{t("whatsapp.choosePairingMethod", "Choose {{qr}} to scan in WhatsApp Linked Devices, or {{code}} to enter a phone number and request a pairing code.", { qr: "QR", code: "code" })}</li>
|
||||
<li>{t("whatsapp.waitConnected", "Wait for the status above to become {{status}}.", { status: "connected" })}</li>
|
||||
<li>{t("whatsapp.repairInstructions", "Use Logout and re-pair to start over. If QR is still pending, wait briefly or refresh status for a new code.")}</li>
|
||||
</ol>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
@@ -398,8 +398,8 @@ function OverviewTab({
|
||||
const verificationSection = verificationRequests.length > 0 ? (
|
||||
<section className="cc-verification-requests card" data-testid="command-center-verification-requests">
|
||||
<div className="cc-overview-chart-header">
|
||||
<h3 className="cc-area-section-title">Task verification</h3>
|
||||
<p>Latest executor-owned verification requests</p>
|
||||
<h3 className="cc-area-section-title">{t("commandCenter.taskVerification", "Task verification")}</h3>
|
||||
<p>{t("commandCenter.verificationRequests", "Latest executor-owned verification requests")}</p>
|
||||
</div>
|
||||
{verificationRequests.map((request) => (
|
||||
<div key={request.requestId} className="cc-verification-requests__item">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Lightbulb } from "lucide-react";
|
||||
import type { IdeationCandidate, IdeationSessionWithCandidates } from "@fusion/core";
|
||||
import { withProjectId } from "../../api/legacy";
|
||||
@@ -20,6 +21,7 @@ Mission convergence operation agents use. The visible Mission ID is persisted
|
||||
handoff evidence, not a copied document or a separate dashboard-only roadmap.
|
||||
*/
|
||||
export function IdeationPanel({ projectId }: { projectId?: string }) {
|
||||
const { t } = useTranslation("app");
|
||||
const [sessions, setSessions] = useState<IdeationSessionWithCandidates[]>([]);
|
||||
const [selectedId, setSelectedId] = useState<string>();
|
||||
const [title, setTitle] = useState("");
|
||||
@@ -48,11 +50,11 @@ export function IdeationPanel({ projectId }: { projectId?: string }) {
|
||||
try { await ideationRequest(`/${encodeURIComponent(selected.id)}/converge`, projectId, { method: "POST", body: JSON.stringify({ candidateId: item.id }) }); await refresh(); }
|
||||
catch (reason) { setError(reason instanceof Error ? reason.message : String(reason)); }
|
||||
};
|
||||
return <section className="ideation-panel" aria-label="Persisted ideation">
|
||||
<header className="ideation-panel__header"><Lightbulb /><div><h2>Ideation</h2><p>Capture alternatives, then converge one into the Mission hierarchy.</p></div></header>
|
||||
<form className="ideation-panel__form" onSubmit={submit}><input className="input" value={title} onChange={(event) => setTitle(event.target.value)} placeholder="Session title" aria-label="Session title" required /><button className="btn" type="submit">Start session</button></form>
|
||||
return <section className="ideation-panel" aria-label={t("ideation.persisted", "Persisted ideation")}>
|
||||
<header className="ideation-panel__header"><Lightbulb /><div><h2>{t("ideation.title", "Ideation")}</h2><p>{t("ideation.description", "Capture alternatives, then converge one into the Mission hierarchy.")}</p></div></header>
|
||||
<form className="ideation-panel__form" onSubmit={submit}><input className="input" value={title} onChange={(event) => setTitle(event.target.value)} placeholder={t("ideation.sessionTitle", "Session title")} aria-label={t("ideation.sessionTitle", "Session title")} required /><button className="btn" type="submit">{t("ideation.start", "Start session")}</button></form>
|
||||
{error && <p className="ideation-panel__error" role="alert">{error}</p>}
|
||||
<div className="ideation-panel__body"><aside className="ideation-panel__sessions">{sessions.length ? sessions.map((session) => <button className={`card ideation-panel__session ${session.id === selected?.id ? "is-selected" : ""}`} type="button" onClick={() => setSelectedId(session.id)} key={session.id}>{session.title}<span>{session.status}</span></button>) : <p>No sessions yet.</p>}</aside>
|
||||
<div className="ideation-panel__detail">{selected ? <><h3>{selected.title}</h3>{selected.targetMissionId && <p className="ideation-panel__handoff">Converged to Mission <strong>{selected.targetMissionId}</strong></p>}{selected.status === "open" && <form className="ideation-panel__form" onSubmit={addCandidate}><input className="input" value={candidate} onChange={(event) => setCandidate(event.target.value)} placeholder="Divergent candidate" aria-label="Divergent candidate" required /><button className="btn" type="submit">Add candidate</button></form>}<ul className="ideation-panel__candidates">{selected.candidates.map((item) => <li className="card" key={item.id}><p>{item.content}</p><small>{item.origin}{item.sourceRef ? ` · ${item.sourceRef}` : ""}</small>{selected.status === "open" && <button className="btn" type="button" onClick={() => void converge(item)}>Converge</button>}</li>)}</ul></> : <p>Select or start a session.</p>}</div></div>
|
||||
<div className="ideation-panel__body"><aside className="ideation-panel__sessions">{sessions.length ? sessions.map((session) => <button className={`card ideation-panel__session ${session.id === selected?.id ? "is-selected" : ""}`} type="button" onClick={() => setSelectedId(session.id)} key={session.id}>{session.title}<span>{session.status}</span></button>) : <p>{t("ideation.noSessions", "No sessions yet.")}</p>}</aside>
|
||||
<div className="ideation-panel__detail">{selected ? <><h3>{selected.title}</h3>{selected.targetMissionId && <p className="ideation-panel__handoff">{t("ideation.convergedToMission", "Converged to Mission")} <strong>{selected.targetMissionId}</strong></p>}{selected.status === "open" && <form className="ideation-panel__form" onSubmit={addCandidate}><input className="input" value={candidate} onChange={(event) => setCandidate(event.target.value)} placeholder={t("ideation.divergentCandidate", "Divergent candidate")} aria-label={t("ideation.divergentCandidate", "Divergent candidate")} required /><button className="btn" type="submit">{t("ideation.addCandidate", "Add candidate")}</button></form>}<ul className="ideation-panel__candidates">{selected.candidates.map((item) => <li className="card" key={item.id}><p>{item.content}</p><small>{item.origin}{item.sourceRef ? ` · ${item.sourceRef}` : ""}</small>{selected.status === "open" && <button className="btn" type="button" onClick={() => void converge(item)}>{t("ideation.converge", "Converge")}</button>}</li>)}</ul></> : <p>{t("ideation.selectOrStart", "Select or start a session.")}</p>}</div></div>
|
||||
</section>;
|
||||
}
|
||||
|
||||
@@ -721,7 +721,7 @@ export function McpServersCard({ scope, form, setForm, globalSettings, projectId
|
||||
<div className="mcp-editor card" data-testid="mcp-server-editor">
|
||||
<div className="mcp-editor-grid">
|
||||
<label className="form-group"><span>{t("settings.mcp.name", "Name")}</span><input className="input" value={editor.name} onChange={(event) => setEditor({ ...editor, name: event.target.value })} /></label>
|
||||
<label className="form-group"><span>{t("settings.mcp.transport", "Transport")}</span><select className="select" value={editor.transport} onChange={(event) => setEditor({ ...editor, transport: event.target.value as Transport })}><option value="stdio">stdio</option><option value="sse">SSE</option><option value="streamable-http">HTTP</option></select></label>
|
||||
<label className="form-group"><span>{t("settings.mcp.transport", "Transport")}</span><select className="select" value={editor.transport} onChange={(event) => setEditor({ ...editor, transport: event.target.value as Transport })}><option value="stdio">{t("settings.mcp.transportStdio", "stdio")}</option><option value="sse">{t("settings.mcp.transportSse", "SSE")}</option><option value="streamable-http">{t("settings.mcp.transportHttp", "HTTP")}</option></select></label>
|
||||
<label className="checkbox-label"><input type="checkbox" checked={editor.enabled} onChange={(event) => setEditor({ ...editor, enabled: event.target.checked })} /> {t("settings.mcp.serverEnabled", "Server enabled")}</label>
|
||||
{editor.transport === "stdio" ? <><label className="form-group"><span>{t("settings.mcp.command", "Command")}</span><input className="input" value={editor.command} onChange={(event) => setEditor({ ...editor, command: event.target.value })} /></label><label className="form-group"><span>{t("settings.mcp.args", "Arguments")}</span><input className="input" value={editor.argsText} onChange={(event) => setEditor({ ...editor, argsText: event.target.value })} /></label></> : <label className="form-group mcp-editor-grid__wide"><span>{t("settings.mcp.url", "URL")}</span><input className="input" value={editor.url} onChange={(event) => setEditor({ ...editor, url: event.target.value })} /></label>}
|
||||
</div>
|
||||
|
||||
@@ -264,7 +264,7 @@ export function MemorySection({ form, setForm, memory }: MemorySectionProps) {
|
||||
{formatMemoryFileOptionLabel(file)}
|
||||
</option>))}
|
||||
</select>
|
||||
{memoryDirty && (<small>Save or discard the current edits before switching files.</small>)}
|
||||
{memoryDirty && (<small>{t("settings.memory.saveBeforeSwitching", "Save or discard the current edits before switching files.")}</small>)}
|
||||
</div>
|
||||
{selectedMemoryFile && (<div className="memory-file-summary">
|
||||
<span>{memoryLayerNames[selectedMemoryFile.layer]}</span>
|
||||
@@ -305,7 +305,7 @@ export function MemorySection({ form, setForm, memory }: MemorySectionProps) {
|
||||
The descriptive branch moved behind the shared "?" beside the action button — operator requirement: no inline description paragraphs in Settings. The dirty-state line stays inline: it is the live reason the button is DISABLED, not help.
|
||||
*/}
|
||||
<SettingsHelpTip settingKey="memory-compact-file">{`Compacts ${selectedMemoryPath} and writes the result back to the same file.`}</SettingsHelpTip>
|
||||
{memoryDirty && (<small>Save or discard edits before compacting this file.</small>)}
|
||||
{memoryDirty && (<small>{t("settings.memory.saveBeforeCompacting", "Save or discard edits before compacting this file.")}</small>)}
|
||||
</div>)}
|
||||
|
||||
{memoryDirty && isEditingAllowed && (<div className="form-group">
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
import type { Task } from "@fusion/core";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import i18n from "i18next";
|
||||
import { I18nextProvider, initReactI18next } from "react-i18next";
|
||||
import { I18nextProvider, initReactI18next, useTranslation } from "react-i18next";
|
||||
import "./styles.css";
|
||||
import "./components/TaskDetailModal.css";
|
||||
import "./components/FloatingWindow.css";
|
||||
@@ -190,9 +190,10 @@ function TaskDetailTitleEmbeddedHarness() {
|
||||
}
|
||||
|
||||
function TaskDetailResizeHarness() {
|
||||
const { t } = useTranslation("app");
|
||||
return <FloatingWindow
|
||||
windowKey="task-detail-fixture"
|
||||
title="Task detail"
|
||||
title={t("fixture.taskDetail", "Task detail")}
|
||||
onClose={() => undefined}
|
||||
hideHeader
|
||||
dragHandleSelector=".task-detail-content--embedded > .modal-header"
|
||||
@@ -205,16 +206,17 @@ function TaskDetailResizeHarness() {
|
||||
testId="task-detail-modal-overlay"
|
||||
>
|
||||
<div className="task-detail-content task-detail-content--embedded">
|
||||
<div className="modal-header">Task detail</div>
|
||||
<div className="modal-body">Task detail body</div>
|
||||
<div className="modal-header">{t("fixture.taskDetail", "Task detail")}</div>
|
||||
<div className="modal-body">{t("fixture.taskDetailBody", "Task detail body")}</div>
|
||||
</div>
|
||||
</FloatingWindow>;
|
||||
}
|
||||
|
||||
function FloatingWindowHarness() {
|
||||
const { t } = useTranslation("app");
|
||||
return <FloatingWindow
|
||||
windowKey="fn-8605-floating"
|
||||
title="Floating task detail"
|
||||
title={t("fixture.floatingTaskDetail", "Floating task detail")}
|
||||
onClose={() => undefined}
|
||||
className="floating-window--task-detail"
|
||||
defaultSize={{ width: 560, height: 480 }}
|
||||
@@ -223,11 +225,12 @@ function FloatingWindowHarness() {
|
||||
persistGeometryKey="fusion:fn-8605-floating"
|
||||
suspendGeometryPersistenceOnMobile
|
||||
>
|
||||
<div>Floating task detail body</div>
|
||||
<div>{t("fixture.floatingTaskDetailBody", "Floating task detail body")}</div>
|
||||
</FloatingWindow>;
|
||||
}
|
||||
|
||||
function HeaderlessFloatingWindowHarness() {
|
||||
const { t } = useTranslation("app");
|
||||
const [actionCount, setActionCount] = useState(0);
|
||||
return <FloatingWindow
|
||||
windowKey="fn-8605-headerless-floating"
|
||||
@@ -242,11 +245,11 @@ function HeaderlessFloatingWindowHarness() {
|
||||
persistGeometryKey="fusion:fn-8605-headerless-floating"
|
||||
suspendGeometryPersistenceOnMobile
|
||||
>
|
||||
<div className="fn-8605-delegated-drag-handle">Headerless task detail
|
||||
<button type="button" data-testid="fn-8605-header-action" onClick={() => setActionCount((count) => count + 1)}>Header action</button>
|
||||
<div className="fn-8605-delegated-drag-handle">{t("fixture.headerlessTaskDetail", "Headerless task detail")}
|
||||
<button type="button" data-testid="fn-8605-header-action" onClick={() => setActionCount((count) => count + 1)}>{t("fixture.headerAction", "Header action")}</button>
|
||||
<output data-testid="fn-8605-header-action-count">{actionCount}</output>
|
||||
</div>
|
||||
<div>Floating task detail body</div>
|
||||
<div>{t("fixture.floatingTaskDetailBody", "Floating task detail body")}</div>
|
||||
</FloatingWindow>;
|
||||
}
|
||||
|
||||
@@ -257,9 +260,10 @@ FloatingWindow consumer. It must retain the shared 44px layout target while task
|
||||
its target out of flow.
|
||||
*/
|
||||
function GenericFloatingWindowHarness() {
|
||||
const { t } = useTranslation("app");
|
||||
return <FloatingWindow
|
||||
windowKey="fn-8612-generic-floating"
|
||||
title="Generic floating window"
|
||||
title={t("fixture.genericFloatingWindow", "Generic floating window")}
|
||||
onClose={() => undefined}
|
||||
hideHeader
|
||||
dragHandleSelector=".fn-8612-generic-drag-handle"
|
||||
@@ -269,8 +273,8 @@ function GenericFloatingWindowHarness() {
|
||||
persistGeometryKey="fusion:fn-8612-generic-floating"
|
||||
suspendGeometryPersistenceOnMobile
|
||||
>
|
||||
<div className="fn-8612-generic-drag-handle">Generic window header</div>
|
||||
<div>Generic floating window body</div>
|
||||
<div className="fn-8612-generic-drag-handle">{t("fixture.genericWindowHeader", "Generic window header")}</div>
|
||||
<div>{t("fixture.genericFloatingWindowBody", "Generic floating window body")}</div>
|
||||
</FloatingWindow>;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showLess": "Show less",
|
||||
"showMore": "Show more",
|
||||
"update": "Update",
|
||||
"yes": "Yes"
|
||||
"yes": "Yes",
|
||||
"open": "Open",
|
||||
"remove": "Remove"
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.",
|
||||
@@ -1155,7 +1157,9 @@
|
||||
"disableHeartbeatsSummary": "Disabled {{count}} heartbeats; skipped {{skipped}}",
|
||||
"enableHeartbeatsCountHint": "Enable {{count}} disabled project heartbeats",
|
||||
"disableHeartbeatsCountHint": "Disable {{count}} enabled project heartbeats",
|
||||
"bulkFailures": "failed {{count}}"
|
||||
"bulkFailures": "failed {{count}}",
|
||||
"detailLabel": "Agent detail",
|
||||
"lastCompletedStep": "Last completed Step {{index}}: {{name}}"
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1399,7 +1403,8 @@
|
||||
"noModelsAvailable": "No models available",
|
||||
"currentAgentTarget": "Current agent: {{name}}",
|
||||
"currentModelTarget": "Current model: {{model}}",
|
||||
"currentDefaultTarget": "Using the default chat target"
|
||||
"currentDefaultTarget": "Using the default chat target",
|
||||
"restore": "Restore"
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1938,7 +1943,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "Task verification",
|
||||
"verificationRequests": "Latest executor-owned verification requests"
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "Add Comment",
|
||||
@@ -2016,7 +2023,20 @@
|
||||
"toLabel": "To:",
|
||||
"wakeAgentCheckbox": "Wake agent immediately",
|
||||
"wakeAlwaysImmediate": "(agent is already set to immediate response mode)",
|
||||
"wakeOneOff": "(one-off override for this message only)"
|
||||
"wakeOneOff": "(one-off override for this message only)",
|
||||
"addSection": "Add section",
|
||||
"ariaLabel": "Message composer; drop a structure to attach it",
|
||||
"attachStructure": "Attach structure",
|
||||
"mode": "Message mode",
|
||||
"noStructures": "No structures available",
|
||||
"quickMessage": "Quick message",
|
||||
"removeSection": "Remove section",
|
||||
"removeStructure": "Remove {{label}}",
|
||||
"report": "Report",
|
||||
"reportTitle": "Report title",
|
||||
"sectionBody": "Section body",
|
||||
"sectionHeading": "Section heading",
|
||||
"selectStructure": "Select structure…"
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Cancel",
|
||||
@@ -3492,7 +3512,36 @@
|
||||
"typeSystem": "System",
|
||||
"typeUserToAgent": "You → Agent",
|
||||
"user": "User",
|
||||
"you": "You"
|
||||
"you": "You",
|
||||
"all": "All",
|
||||
"approvalStatus": "Approval {{status}}",
|
||||
"approvalUnavailable": "This approval request is no longer available.",
|
||||
"approve": "Approve",
|
||||
"archive": "Archive",
|
||||
"archived": "Archived",
|
||||
"artifact": "artifact",
|
||||
"audioArtifactAria": "Audio artifact: {{label}}",
|
||||
"createTask": "Create task",
|
||||
"createTaskError": "Could not create task. Try again.",
|
||||
"creatingTask": "Creating task…",
|
||||
"deny": "Deny",
|
||||
"inboxFilter": "Inbox filter",
|
||||
"loadingApproval": "Loading approval…",
|
||||
"noArchivedMessages": "No archived messages",
|
||||
"openArtifact": "Open artifact",
|
||||
"openArtifactAria": "Open artifact: {{label}}",
|
||||
"optionalComment": "Optional comment",
|
||||
"recommendationsUnavailable": "Recommendations are no longer available.",
|
||||
"reportsApprovals": "Reports & approvals",
|
||||
"restore": "Restore",
|
||||
"retryCreatingTask": "Retry creating task",
|
||||
"taskCreatedView": "Task {{id}} created — View task",
|
||||
"taskProposalDismissed": "Task proposal dismissed",
|
||||
"taskRecommendations": "Task recommendations",
|
||||
"videoArtifactAria": "Video artifact: {{label}}",
|
||||
"viewTask": "View task {{id}}",
|
||||
"viewTaskAria": "View task: {{id}}",
|
||||
"viewTaskLabel": "View task"
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "Audit Checks",
|
||||
@@ -5313,7 +5362,14 @@
|
||||
"resize": "Resize right dock",
|
||||
"viewExpanded": "{{label}} expanded",
|
||||
"views": "Right dock views",
|
||||
"resizeExpandedView": "Resize expanded right dock window"
|
||||
"resizeExpandedView": "Resize expanded right dock window",
|
||||
"archivedCopy": "Archived tasks stay out of this compact sidebar. Active tasks will appear here when work is available.",
|
||||
"doneHiddenCopy": "Completed tasks are hidden until you choose Show Done. Archived tasks stay out of this compact sidebar.",
|
||||
"emptyCopy": "Tasks you create or import will appear here for quick right-sidebar review.",
|
||||
"hideDone": "Hide Done",
|
||||
"noActiveTasks": "No active tasks",
|
||||
"noTasksYet": "No tasks yet",
|
||||
"showDone": "Show Done"
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "…and {{count}} more",
|
||||
@@ -6297,7 +6353,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md. Default: disabled.",
|
||||
"qmdInstalled": "qmd installed successfully",
|
||||
"qmdInstallFailed": "Failed to install qmd",
|
||||
"qmdInstallUnavailable": "qmd install finished, but qmd is still unavailable"
|
||||
"qmdInstallUnavailable": "qmd install finished, but qmd is still unavailable",
|
||||
"saveBeforeCompacting": "Save or discard edits before compacting this file.",
|
||||
"saveBeforeSwitching": "Save or discard the current edits before switching files."
|
||||
},
|
||||
"merge": {
|
||||
"abort": "Abort",
|
||||
@@ -6960,7 +7018,11 @@
|
||||
"projectTitle": "Project MCP servers",
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"enabledHint": "Default: disabled, with no servers configured."
|
||||
"enabledHint": "Default: disabled, with no servers configured.",
|
||||
"transport": "Transport",
|
||||
"transportHttp": "HTTP",
|
||||
"transportSse": "SSE",
|
||||
"transportStdio": "stdio"
|
||||
},
|
||||
"prompts": {
|
||||
"surfaceExplanation": "Use this section for agent role system prompt templates, role assignments, and global PromptKey segment overrides. Per-workflow step prompts for prompt and gate nodes are edited in the Workflow Editor. No default — unset (built-in role prompts apply until overridden)."
|
||||
@@ -7909,7 +7971,8 @@
|
||||
"header": {
|
||||
"back": "Back",
|
||||
"backToList": "Back to task list",
|
||||
"editTask": "Edit task"
|
||||
"editTask": "Edit task",
|
||||
"popOut": "Pop out"
|
||||
},
|
||||
"inputTokens": "Input",
|
||||
"lastUsed": "Last used",
|
||||
@@ -8241,6 +8304,22 @@
|
||||
"retry": "Retry creating task",
|
||||
"create": "Create task",
|
||||
"error": "Could not create task. Try again."
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": "Delete reverted task"
|
||||
},
|
||||
"revise": "Revise",
|
||||
"specLock": {
|
||||
"accepted": "Accepted {{acceptedAt}} · plan hash {{planHash}} · approval {{approval}}",
|
||||
"alignment": "Spec alignment",
|
||||
"alignmentLabel": "Spec lock alignment",
|
||||
"captured": "Captured {{capturedAt}} · source revision {{sourceRevision}} · source hash {{sourceHash}}",
|
||||
"currentPlan": "Current plan",
|
||||
"findings": "Findings",
|
||||
"latestLock": "Latest lock",
|
||||
"lockState": "Lock state",
|
||||
"relockChanged": "Re-lock changed: {{sections}}",
|
||||
"retainedHistory": "Retained history: {{locks}}; {{plans}}; {{reports}} reports"
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9379,7 +9458,11 @@
|
||||
"toolNames": "Tool names",
|
||||
"toolResult": "Tool result",
|
||||
"you": "You",
|
||||
"youMessage": "You message"
|
||||
"youMessage": "You message",
|
||||
"missingOutput": "Missing output",
|
||||
"missingOutputTimestamp": "Missing output timestamp",
|
||||
"statusUpdate": "Status update",
|
||||
"statusUpdateTimestamp": "Status update timestamp"
|
||||
},
|
||||
"report": {
|
||||
"roadmapMatch": {
|
||||
@@ -9392,6 +9475,146 @@
|
||||
"targetLabel": "Filing target",
|
||||
"targetInherit": "Use configured action target",
|
||||
"targetIssue": "GitHub Issue",
|
||||
"targetDiscussion": "GitHub Discussion"
|
||||
"targetDiscussion": "GitHub Discussion",
|
||||
"activityTrace": "Activity trace to send",
|
||||
"capturingScreenshot": "Capturing and storing locally…",
|
||||
"close": "Close report",
|
||||
"confirmDataPoint": "Confirm and add data point",
|
||||
"confirmScreenshotRetention": "I confirm Fusion may retain this screenshot locally for this report.",
|
||||
"duplicateReview": "Review data point for a similar open issue",
|
||||
"file": "File report",
|
||||
"final": "Final report",
|
||||
"menu": "Report",
|
||||
"returnToPrompt": "Return to prompt",
|
||||
"review": "Review your report",
|
||||
"storeScreenshot": "Store a screenshot locally",
|
||||
"structured": "Structured report",
|
||||
"structuredDataPoint": "Structured data point",
|
||||
"suggestedHelp": "Suggested help",
|
||||
"summary": "Report summary",
|
||||
"viewGitHub": "View on GitHub"
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "Compose chat narrative helper",
|
||||
"draft": "Draft",
|
||||
"draftNarrative": "Draft narrative",
|
||||
"emptyDraft": "Ask the assistant to draft the narrative around your attached structures.",
|
||||
"useDraft": "Use draft"
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "Floating task detail",
|
||||
"floatingTaskDetailBody": "Floating task detail body",
|
||||
"genericFloatingWindow": "Generic floating window",
|
||||
"genericFloatingWindowBody": "Generic floating window body",
|
||||
"genericWindowHeader": "Generic window header",
|
||||
"headerAction": "Header action",
|
||||
"headerlessTaskDetail": "Headerless task detail",
|
||||
"taskDetail": "Task detail",
|
||||
"taskDetailBody": "Task detail body"
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "Close floating window",
|
||||
"resize": "Resize floating window"
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab"
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "Add candidate",
|
||||
"converge": "Converge",
|
||||
"convergedToMission": "Converged to Mission",
|
||||
"description": "Capture alternatives, then converge one into the Mission hierarchy.",
|
||||
"divergentCandidate": "Divergent candidate",
|
||||
"noSessions": "No sessions yet.",
|
||||
"persisted": "Persisted ideation",
|
||||
"selectOrStart": "Select or start a session.",
|
||||
"sessionTitle": "Session title",
|
||||
"start": "Start session",
|
||||
"title": "Ideation"
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "All FNXC areas",
|
||||
"allOwners": "All owners",
|
||||
"allSymbolKinds": "All symbol kinds",
|
||||
"bothDirections": "Both directions",
|
||||
"depth": "Depth {{depth}}",
|
||||
"distance": "distance {{distance}}",
|
||||
"edgeKinds": "Edge kinds",
|
||||
"findPath": "Find path",
|
||||
"incoming": "Incoming",
|
||||
"limitResults": "{{limit}} results",
|
||||
"neighbors": "Neighbors",
|
||||
"next": "Next",
|
||||
"noNeighbors": "No neighbors found.",
|
||||
"noPath": "No path exists between these nodes.",
|
||||
"nodeKinds": "Node kinds",
|
||||
"none": "None",
|
||||
"outgoing": "Outgoing",
|
||||
"ownerDerived": "derived",
|
||||
"ownerFile": "file",
|
||||
"pathLimitReached": "No path found within {{maxHops}} hops; {{limit}} was reached.",
|
||||
"previous": "Previous",
|
||||
"provenance": "Provenance",
|
||||
"raiseHopLimit": "Raise hop limit",
|
||||
"refreshNeighbors": "Refresh neighbors",
|
||||
"searching": "Searching…",
|
||||
"selectNodeHint": "Select a node to inspect its edges.",
|
||||
"shortestPath": "Shortest path",
|
||||
"showingOf": "showing {{shown}} of {{total}}",
|
||||
"useSelectedFrom": "Use selected as from",
|
||||
"useSelectedTo": "Use selected as to"
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": "Mermaid diagram"
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "Could not load this {{kind}}.",
|
||||
"loading": "Loading {{kind}}",
|
||||
"openAria": "Open {{kind}}: {{title}}",
|
||||
"previewUnavailable": "Preview unavailable",
|
||||
"unavailable": "This structure is unavailable."
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "Approve the sign-in in your browser",
|
||||
"authorizationReceived": "Authorization received.",
|
||||
"cancel": "Cancel login",
|
||||
"connected": "Connected.",
|
||||
"exchangingCode": "Exchanging the authorization code…",
|
||||
"finishInBrowser": "A tab should have opened. Finish signing in there — this dialog stays put.",
|
||||
"handAuthorizationBack": "Hand the authorization back to Fusion",
|
||||
"openSignInAgain": "Open the sign-in page again",
|
||||
"pasteRedirectUrl": "Usually automatic. If your browser lands on an error page, paste that page's full URL below.",
|
||||
"signingInTo": "Signing in to {{provider}}"
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "Failed",
|
||||
"heading": "Verification · {{profile}}",
|
||||
"passed": "Passed",
|
||||
"queued": "Queued for the task executor",
|
||||
"requestRejected": "Request rejected",
|
||||
"running": "Running in the task worktree"
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "Set {{label}}; an empty list blocks all inbound messages.",
|
||||
"choosePairingMethod": "Choose {{qr}} to scan in WhatsApp Linked Devices, or {{code}} to enter a phone number and request a pairing code.",
|
||||
"connectedAs": "Connected as {{jid}}",
|
||||
"connectedSuccess": "WhatsApp is paired and ready to receive messages from allowed senders.",
|
||||
"description": "Pair and monitor this project's WhatsApp connection here.",
|
||||
"installPlugin": "Install and enable this plugin, then keep this settings panel open.",
|
||||
"instructionsLabel": "WhatsApp pairing instructions",
|
||||
"loggingOut": "Logging out...",
|
||||
"logoutRepair": "Logout and re-pair",
|
||||
"pairing": "WhatsApp pairing",
|
||||
"pairingConfiguration": "Pairing and configuration",
|
||||
"phoneNumber": "Phone number (E.164 digits without +)",
|
||||
"qrCode": "WhatsApp pairing QR code",
|
||||
"refreshStatus": "Refresh status",
|
||||
"repairInstructions": "Use Logout and re-pair to start over. If QR is still pending, wait briefly or refresh status for a new code.",
|
||||
"requestPairingCode": "Request pairing code",
|
||||
"requestingCode": "Requesting code...",
|
||||
"status": "Status:",
|
||||
"waitConnected": "Wait for the status above to become {{status}}.",
|
||||
"waitingQr": "Waiting for a fresh QR code. Keep this panel open and refresh if needed."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showMore": "Mostrar más",
|
||||
"update": "Actualizar",
|
||||
"yes": "Sí",
|
||||
"clear": ""
|
||||
"clear": "",
|
||||
"open": "",
|
||||
"remove": ""
|
||||
},
|
||||
"activityFeed": {
|
||||
"emptyHint": "",
|
||||
@@ -1145,7 +1147,9 @@
|
||||
"disableHeartbeatsSummary": "Se desactivaron {{count}} latidos; se omitieron {{skipped}}",
|
||||
"enableHeartbeatsCountHint": "Activar {{count}} latidos desactivados",
|
||||
"disableHeartbeatsCountHint": "Desactivar {{count}} latidos activados",
|
||||
"bulkFailures": "fallaron {{count}}"
|
||||
"bulkFailures": "fallaron {{count}}",
|
||||
"detailLabel": "",
|
||||
"lastCompletedStep": ""
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1389,7 +1393,8 @@
|
||||
"currentAgentTarget": "Current agent: {{name}}",
|
||||
"currentModelTarget": "Current model: {{model}}",
|
||||
"currentDefaultTarget": "Using the default chat target",
|
||||
"newNotAllowedForTaskChat": ""
|
||||
"newNotAllowedForTaskChat": "",
|
||||
"restore": ""
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1928,7 +1933,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "",
|
||||
"verificationRequests": ""
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "Añadir comentario",
|
||||
@@ -2006,7 +2013,20 @@
|
||||
"toLabel": "Para:",
|
||||
"wakeAgentCheckbox": "Despertar agente inmediatamente",
|
||||
"wakeAlwaysImmediate": "(el agente ya está configurado en modo de respuesta inmediata)",
|
||||
"wakeOneOff": "(anulación única para este mensaje solamente)"
|
||||
"wakeOneOff": "(anulación única para este mensaje solamente)",
|
||||
"addSection": "",
|
||||
"ariaLabel": "",
|
||||
"attachStructure": "",
|
||||
"mode": "",
|
||||
"noStructures": "",
|
||||
"quickMessage": "",
|
||||
"removeSection": "",
|
||||
"removeStructure": "",
|
||||
"report": "",
|
||||
"reportTitle": "",
|
||||
"sectionBody": "",
|
||||
"sectionHeading": "",
|
||||
"selectStructure": ""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Cancelar",
|
||||
@@ -3482,7 +3502,36 @@
|
||||
"typeSystem": "Sistema",
|
||||
"typeUserToAgent": "Usted → Agente",
|
||||
"user": "Usuario",
|
||||
"you": "Tú"
|
||||
"you": "Tú",
|
||||
"all": "",
|
||||
"approvalStatus": "",
|
||||
"approvalUnavailable": "",
|
||||
"approve": "",
|
||||
"archive": "",
|
||||
"archived": "",
|
||||
"artifact": "",
|
||||
"audioArtifactAria": "",
|
||||
"createTask": "",
|
||||
"createTaskError": "",
|
||||
"creatingTask": "",
|
||||
"deny": "",
|
||||
"inboxFilter": "",
|
||||
"loadingApproval": "",
|
||||
"noArchivedMessages": "",
|
||||
"openArtifact": "",
|
||||
"openArtifactAria": "",
|
||||
"optionalComment": "",
|
||||
"recommendationsUnavailable": "",
|
||||
"reportsApprovals": "",
|
||||
"restore": "",
|
||||
"retryCreatingTask": "",
|
||||
"taskCreatedView": "",
|
||||
"taskProposalDismissed": "",
|
||||
"taskRecommendations": "",
|
||||
"videoArtifactAria": "",
|
||||
"viewTask": "",
|
||||
"viewTaskAria": "",
|
||||
"viewTaskLabel": ""
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "Comprobaciones de auditoría",
|
||||
@@ -5303,7 +5352,14 @@
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
"resizeExpandedView": "",
|
||||
"archivedCopy": "",
|
||||
"doneHiddenCopy": "",
|
||||
"emptyCopy": "",
|
||||
"hideDone": "",
|
||||
"noActiveTasks": "",
|
||||
"noTasksYet": "",
|
||||
"showDone": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
@@ -6176,7 +6232,8 @@
|
||||
"openRouterRoutingOnlyHint": "",
|
||||
"openRouterAllowFallbacksHint": "",
|
||||
"openRouterRoutingSortHint": "",
|
||||
"requireParametersHint": ""
|
||||
"requireParametersHint": "",
|
||||
"modelOverrides": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord",
|
||||
@@ -6273,7 +6330,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
"qmdInstallUnavailable": "",
|
||||
"saveBeforeCompacting": "",
|
||||
"saveBeforeSwitching": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6621,7 +6680,11 @@
|
||||
"executorEscalationModel": "",
|
||||
"executorEscalationModelHelp": "",
|
||||
"selectExecutorEscalationModel": "",
|
||||
"noExecutorEscalationModel": ""
|
||||
"noExecutorEscalationModel": "",
|
||||
"modelOverrides": "",
|
||||
"projectLanesSubheading": "",
|
||||
"workflowLanesSubheading": "",
|
||||
"summarizationPointer": ""
|
||||
},
|
||||
"remote": {
|
||||
"acceptRoutes": "",
|
||||
@@ -6936,7 +6999,11 @@
|
||||
"projectTitle": "Project MCP servers",
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"enabledHint": ""
|
||||
"enabledHint": "",
|
||||
"transport": "",
|
||||
"transportHttp": "",
|
||||
"transportSse": "",
|
||||
"transportStdio": ""
|
||||
},
|
||||
"search": {
|
||||
"allSections": "",
|
||||
@@ -7870,7 +7937,8 @@
|
||||
"header": {
|
||||
"back": "Volver",
|
||||
"backToList": "Volver a la lista de tareas",
|
||||
"editTask": "Editar tarea"
|
||||
"editTask": "Editar tarea",
|
||||
"popOut": ""
|
||||
},
|
||||
"inputTokens": "Entrada",
|
||||
"lastUsed": "Último uso",
|
||||
@@ -8231,6 +8299,22 @@
|
||||
"retry": "",
|
||||
"create": "",
|
||||
"error": ""
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": ""
|
||||
},
|
||||
"revise": "",
|
||||
"specLock": {
|
||||
"accepted": "",
|
||||
"alignment": "",
|
||||
"alignmentLabel": "",
|
||||
"captured": "",
|
||||
"currentPlan": "",
|
||||
"findings": "",
|
||||
"latestLock": "",
|
||||
"lockState": "",
|
||||
"relockChanged": "",
|
||||
"retainedHistory": ""
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9369,7 +9453,11 @@
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
"youMessage": "",
|
||||
"missingOutput": "",
|
||||
"missingOutputTimestamp": "",
|
||||
"statusUpdate": "",
|
||||
"statusUpdateTimestamp": ""
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.",
|
||||
@@ -9392,6 +9480,146 @@
|
||||
"targetLabel": "Destino del envío",
|
||||
"targetInherit": "Usar el destino configurado para la acción",
|
||||
"targetIssue": "Incidencia de GitHub",
|
||||
"targetDiscussion": "Discusión de GitHub"
|
||||
"targetDiscussion": "Discusión de GitHub",
|
||||
"activityTrace": "",
|
||||
"capturingScreenshot": "",
|
||||
"close": "",
|
||||
"confirmDataPoint": "",
|
||||
"confirmScreenshotRetention": "",
|
||||
"duplicateReview": "",
|
||||
"file": "",
|
||||
"final": "",
|
||||
"menu": "",
|
||||
"returnToPrompt": "",
|
||||
"review": "",
|
||||
"storeScreenshot": "",
|
||||
"structured": "",
|
||||
"structuredDataPoint": "",
|
||||
"suggestedHelp": "",
|
||||
"summary": "",
|
||||
"viewGitHub": ""
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "",
|
||||
"draft": "",
|
||||
"draftNarrative": "",
|
||||
"emptyDraft": "",
|
||||
"useDraft": ""
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "",
|
||||
"floatingTaskDetailBody": "",
|
||||
"genericFloatingWindow": "",
|
||||
"genericFloatingWindowBody": "",
|
||||
"genericWindowHeader": "",
|
||||
"headerAction": "",
|
||||
"headerlessTaskDetail": "",
|
||||
"taskDetail": "",
|
||||
"taskDetailBody": ""
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "",
|
||||
"resize": ""
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "",
|
||||
"gitlab": ""
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "",
|
||||
"converge": "",
|
||||
"convergedToMission": "",
|
||||
"description": "",
|
||||
"divergentCandidate": "",
|
||||
"noSessions": "",
|
||||
"persisted": "",
|
||||
"selectOrStart": "",
|
||||
"sessionTitle": "",
|
||||
"start": "",
|
||||
"title": ""
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "",
|
||||
"allOwners": "",
|
||||
"allSymbolKinds": "",
|
||||
"bothDirections": "",
|
||||
"depth": "",
|
||||
"distance": "",
|
||||
"edgeKinds": "",
|
||||
"findPath": "",
|
||||
"incoming": "",
|
||||
"limitResults": "",
|
||||
"neighbors": "",
|
||||
"next": "",
|
||||
"noNeighbors": "",
|
||||
"noPath": "",
|
||||
"nodeKinds": "",
|
||||
"none": "",
|
||||
"outgoing": "",
|
||||
"ownerDerived": "",
|
||||
"ownerFile": "",
|
||||
"pathLimitReached": "",
|
||||
"previous": "",
|
||||
"provenance": "",
|
||||
"raiseHopLimit": "",
|
||||
"refreshNeighbors": "",
|
||||
"searching": "",
|
||||
"selectNodeHint": "",
|
||||
"shortestPath": "",
|
||||
"showingOf": "",
|
||||
"useSelectedFrom": "",
|
||||
"useSelectedTo": ""
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": ""
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"openAria": "",
|
||||
"previewUnavailable": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "",
|
||||
"authorizationReceived": "",
|
||||
"cancel": "",
|
||||
"connected": "",
|
||||
"exchangingCode": "",
|
||||
"finishInBrowser": "",
|
||||
"handAuthorizationBack": "",
|
||||
"openSignInAgain": "",
|
||||
"pasteRedirectUrl": "",
|
||||
"signingInTo": ""
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "",
|
||||
"heading": "",
|
||||
"passed": "",
|
||||
"queued": "",
|
||||
"requestRejected": "",
|
||||
"running": ""
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "",
|
||||
"choosePairingMethod": "",
|
||||
"connectedAs": "",
|
||||
"connectedSuccess": "",
|
||||
"description": "",
|
||||
"installPlugin": "",
|
||||
"instructionsLabel": "",
|
||||
"loggingOut": "",
|
||||
"logoutRepair": "",
|
||||
"pairing": "",
|
||||
"pairingConfiguration": "",
|
||||
"phoneNumber": "",
|
||||
"qrCode": "",
|
||||
"refreshStatus": "",
|
||||
"repairInstructions": "",
|
||||
"requestPairingCode": "",
|
||||
"requestingCode": "",
|
||||
"status": "",
|
||||
"waitConnected": "",
|
||||
"waitingQr": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showMore": "Afficher plus",
|
||||
"update": "Mettre à jour",
|
||||
"yes": "Oui",
|
||||
"clear": ""
|
||||
"clear": "",
|
||||
"open": "",
|
||||
"remove": ""
|
||||
},
|
||||
"activityFeed": {
|
||||
"emptyHint": "",
|
||||
@@ -1145,7 +1147,9 @@
|
||||
"disableHeartbeatsSummary": "{{count}} battements désactivés ; {{skipped}} ignorés",
|
||||
"enableHeartbeatsCountHint": "Activer {{count}} battements désactivés",
|
||||
"disableHeartbeatsCountHint": "Désactiver {{count}} battements activés",
|
||||
"bulkFailures": "{{count}} échecs"
|
||||
"bulkFailures": "{{count}} échecs",
|
||||
"detailLabel": "",
|
||||
"lastCompletedStep": ""
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1389,7 +1393,8 @@
|
||||
"currentAgentTarget": "Current agent: {{name}}",
|
||||
"currentModelTarget": "Current model: {{model}}",
|
||||
"currentDefaultTarget": "Using the default chat target",
|
||||
"newNotAllowedForTaskChat": ""
|
||||
"newNotAllowedForTaskChat": "",
|
||||
"restore": ""
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1928,7 +1933,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "",
|
||||
"verificationRequests": ""
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "Ajouter un commentaire",
|
||||
@@ -2006,7 +2013,20 @@
|
||||
"toLabel": "À :",
|
||||
"wakeAgentCheckbox": "Réveiller l'agent immédiatement",
|
||||
"wakeAlwaysImmediate": "(l'agent est déjà en mode de réponse immédiate)",
|
||||
"wakeOneOff": "(remplacement ponctuel pour ce message uniquement)"
|
||||
"wakeOneOff": "(remplacement ponctuel pour ce message uniquement)",
|
||||
"addSection": "",
|
||||
"ariaLabel": "",
|
||||
"attachStructure": "",
|
||||
"mode": "",
|
||||
"noStructures": "",
|
||||
"quickMessage": "",
|
||||
"removeSection": "",
|
||||
"removeStructure": "",
|
||||
"report": "",
|
||||
"reportTitle": "",
|
||||
"sectionBody": "",
|
||||
"sectionHeading": "",
|
||||
"selectStructure": ""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Annuler",
|
||||
@@ -3482,7 +3502,36 @@
|
||||
"typeSystem": "Système",
|
||||
"typeUserToAgent": "Vous → Agent",
|
||||
"user": "Utilisateur",
|
||||
"you": "Vous"
|
||||
"you": "Vous",
|
||||
"all": "",
|
||||
"approvalStatus": "",
|
||||
"approvalUnavailable": "",
|
||||
"approve": "",
|
||||
"archive": "",
|
||||
"archived": "",
|
||||
"artifact": "",
|
||||
"audioArtifactAria": "",
|
||||
"createTask": "",
|
||||
"createTaskError": "",
|
||||
"creatingTask": "",
|
||||
"deny": "",
|
||||
"inboxFilter": "",
|
||||
"loadingApproval": "",
|
||||
"noArchivedMessages": "",
|
||||
"openArtifact": "",
|
||||
"openArtifactAria": "",
|
||||
"optionalComment": "",
|
||||
"recommendationsUnavailable": "",
|
||||
"reportsApprovals": "",
|
||||
"restore": "",
|
||||
"retryCreatingTask": "",
|
||||
"taskCreatedView": "",
|
||||
"taskProposalDismissed": "",
|
||||
"taskRecommendations": "",
|
||||
"videoArtifactAria": "",
|
||||
"viewTask": "",
|
||||
"viewTaskAria": "",
|
||||
"viewTaskLabel": ""
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "Vérifications d'audit",
|
||||
@@ -5303,7 +5352,14 @@
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
"resizeExpandedView": "",
|
||||
"archivedCopy": "",
|
||||
"doneHiddenCopy": "",
|
||||
"emptyCopy": "",
|
||||
"hideDone": "",
|
||||
"noActiveTasks": "",
|
||||
"noTasksYet": "",
|
||||
"showDone": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
@@ -6176,7 +6232,8 @@
|
||||
"openRouterRoutingOnlyHint": "",
|
||||
"openRouterAllowFallbacksHint": "",
|
||||
"openRouterRoutingSortHint": "",
|
||||
"requireParametersHint": ""
|
||||
"requireParametersHint": "",
|
||||
"modelOverrides": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord",
|
||||
@@ -6273,7 +6330,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
"qmdInstallUnavailable": "",
|
||||
"saveBeforeCompacting": "",
|
||||
"saveBeforeSwitching": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6621,7 +6680,11 @@
|
||||
"executorEscalationModel": "",
|
||||
"executorEscalationModelHelp": "",
|
||||
"selectExecutorEscalationModel": "",
|
||||
"noExecutorEscalationModel": ""
|
||||
"noExecutorEscalationModel": "",
|
||||
"modelOverrides": "",
|
||||
"projectLanesSubheading": "",
|
||||
"workflowLanesSubheading": "",
|
||||
"summarizationPointer": ""
|
||||
},
|
||||
"remote": {
|
||||
"acceptRoutes": "",
|
||||
@@ -6936,7 +6999,11 @@
|
||||
"projectTitle": "Project MCP servers",
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"enabledHint": ""
|
||||
"enabledHint": "",
|
||||
"transport": "",
|
||||
"transportHttp": "",
|
||||
"transportSse": "",
|
||||
"transportStdio": ""
|
||||
},
|
||||
"search": {
|
||||
"allSections": "",
|
||||
@@ -7870,7 +7937,8 @@
|
||||
"header": {
|
||||
"back": "Retour",
|
||||
"backToList": "Retour à la liste des tâches",
|
||||
"editTask": "Modifier la tâche"
|
||||
"editTask": "Modifier la tâche",
|
||||
"popOut": ""
|
||||
},
|
||||
"inputTokens": "Entrée",
|
||||
"lastUsed": "Dernière utilisation",
|
||||
@@ -8231,6 +8299,22 @@
|
||||
"retry": "",
|
||||
"create": "",
|
||||
"error": ""
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": ""
|
||||
},
|
||||
"revise": "",
|
||||
"specLock": {
|
||||
"accepted": "",
|
||||
"alignment": "",
|
||||
"alignmentLabel": "",
|
||||
"captured": "",
|
||||
"currentPlan": "",
|
||||
"findings": "",
|
||||
"latestLock": "",
|
||||
"lockState": "",
|
||||
"relockChanged": "",
|
||||
"retainedHistory": ""
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9369,7 +9453,11 @@
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
"youMessage": "",
|
||||
"missingOutput": "",
|
||||
"missingOutputTimestamp": "",
|
||||
"statusUpdate": "",
|
||||
"statusUpdateTimestamp": ""
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.",
|
||||
@@ -9392,6 +9480,146 @@
|
||||
"targetLabel": "Cible de dépôt",
|
||||
"targetInherit": "Utiliser la cible configurée pour l’action",
|
||||
"targetIssue": "Issue GitHub",
|
||||
"targetDiscussion": "Discussion GitHub"
|
||||
"targetDiscussion": "Discussion GitHub",
|
||||
"activityTrace": "",
|
||||
"capturingScreenshot": "",
|
||||
"close": "",
|
||||
"confirmDataPoint": "",
|
||||
"confirmScreenshotRetention": "",
|
||||
"duplicateReview": "",
|
||||
"file": "",
|
||||
"final": "",
|
||||
"menu": "",
|
||||
"returnToPrompt": "",
|
||||
"review": "",
|
||||
"storeScreenshot": "",
|
||||
"structured": "",
|
||||
"structuredDataPoint": "",
|
||||
"suggestedHelp": "",
|
||||
"summary": "",
|
||||
"viewGitHub": ""
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "",
|
||||
"draft": "",
|
||||
"draftNarrative": "",
|
||||
"emptyDraft": "",
|
||||
"useDraft": ""
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "",
|
||||
"floatingTaskDetailBody": "",
|
||||
"genericFloatingWindow": "",
|
||||
"genericFloatingWindowBody": "",
|
||||
"genericWindowHeader": "",
|
||||
"headerAction": "",
|
||||
"headerlessTaskDetail": "",
|
||||
"taskDetail": "",
|
||||
"taskDetailBody": ""
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "",
|
||||
"resize": ""
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "",
|
||||
"gitlab": ""
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "",
|
||||
"converge": "",
|
||||
"convergedToMission": "",
|
||||
"description": "",
|
||||
"divergentCandidate": "",
|
||||
"noSessions": "",
|
||||
"persisted": "",
|
||||
"selectOrStart": "",
|
||||
"sessionTitle": "",
|
||||
"start": "",
|
||||
"title": ""
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "",
|
||||
"allOwners": "",
|
||||
"allSymbolKinds": "",
|
||||
"bothDirections": "",
|
||||
"depth": "",
|
||||
"distance": "",
|
||||
"edgeKinds": "",
|
||||
"findPath": "",
|
||||
"incoming": "",
|
||||
"limitResults": "",
|
||||
"neighbors": "",
|
||||
"next": "",
|
||||
"noNeighbors": "",
|
||||
"noPath": "",
|
||||
"nodeKinds": "",
|
||||
"none": "",
|
||||
"outgoing": "",
|
||||
"ownerDerived": "",
|
||||
"ownerFile": "",
|
||||
"pathLimitReached": "",
|
||||
"previous": "",
|
||||
"provenance": "",
|
||||
"raiseHopLimit": "",
|
||||
"refreshNeighbors": "",
|
||||
"searching": "",
|
||||
"selectNodeHint": "",
|
||||
"shortestPath": "",
|
||||
"showingOf": "",
|
||||
"useSelectedFrom": "",
|
||||
"useSelectedTo": ""
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": ""
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"openAria": "",
|
||||
"previewUnavailable": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "",
|
||||
"authorizationReceived": "",
|
||||
"cancel": "",
|
||||
"connected": "",
|
||||
"exchangingCode": "",
|
||||
"finishInBrowser": "",
|
||||
"handAuthorizationBack": "",
|
||||
"openSignInAgain": "",
|
||||
"pasteRedirectUrl": "",
|
||||
"signingInTo": ""
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "",
|
||||
"heading": "",
|
||||
"passed": "",
|
||||
"queued": "",
|
||||
"requestRejected": "",
|
||||
"running": ""
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "",
|
||||
"choosePairingMethod": "",
|
||||
"connectedAs": "",
|
||||
"connectedSuccess": "",
|
||||
"description": "",
|
||||
"installPlugin": "",
|
||||
"instructionsLabel": "",
|
||||
"loggingOut": "",
|
||||
"logoutRepair": "",
|
||||
"pairing": "",
|
||||
"pairingConfiguration": "",
|
||||
"phoneNumber": "",
|
||||
"qrCode": "",
|
||||
"refreshStatus": "",
|
||||
"repairInstructions": "",
|
||||
"requestPairingCode": "",
|
||||
"requestingCode": "",
|
||||
"status": "",
|
||||
"waitConnected": "",
|
||||
"waitingQr": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showMore": "더 보기",
|
||||
"update": "업데이트",
|
||||
"yes": "예",
|
||||
"clear": ""
|
||||
"clear": "",
|
||||
"open": "",
|
||||
"remove": ""
|
||||
},
|
||||
"activityFeed": {
|
||||
"emptyHint": "",
|
||||
@@ -1145,7 +1147,9 @@
|
||||
"disableHeartbeatsSummary": "",
|
||||
"enableHeartbeatsCountHint": "",
|
||||
"disableHeartbeatsCountHint": "",
|
||||
"bulkFailures": ""
|
||||
"bulkFailures": "",
|
||||
"detailLabel": "",
|
||||
"lastCompletedStep": ""
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1389,7 +1393,8 @@
|
||||
"currentAgentTarget": "Current agent: {{name}}",
|
||||
"currentModelTarget": "Current model: {{model}}",
|
||||
"currentDefaultTarget": "Using the default chat target",
|
||||
"newNotAllowedForTaskChat": ""
|
||||
"newNotAllowedForTaskChat": "",
|
||||
"restore": ""
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1928,7 +1933,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "",
|
||||
"verificationRequests": ""
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "댓글 추가",
|
||||
@@ -2006,7 +2013,20 @@
|
||||
"toLabel": "받는 사람:",
|
||||
"wakeAgentCheckbox": "에이전트 즉시 깨우기",
|
||||
"wakeAlwaysImmediate": "(에이전트가 이미 즉시 응답 모드로 설정되어 있습니다)",
|
||||
"wakeOneOff": "(이 메시지에만 적용되는 일회성 재정의)"
|
||||
"wakeOneOff": "(이 메시지에만 적용되는 일회성 재정의)",
|
||||
"addSection": "",
|
||||
"ariaLabel": "",
|
||||
"attachStructure": "",
|
||||
"mode": "",
|
||||
"noStructures": "",
|
||||
"quickMessage": "",
|
||||
"removeSection": "",
|
||||
"removeStructure": "",
|
||||
"report": "",
|
||||
"reportTitle": "",
|
||||
"sectionBody": "",
|
||||
"sectionHeading": "",
|
||||
"selectStructure": ""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "취소",
|
||||
@@ -3482,7 +3502,36 @@
|
||||
"typeSystem": "시스템",
|
||||
"typeUserToAgent": "나 → 에이전트",
|
||||
"user": "사용자",
|
||||
"you": "나"
|
||||
"you": "나",
|
||||
"all": "",
|
||||
"approvalStatus": "",
|
||||
"approvalUnavailable": "",
|
||||
"approve": "",
|
||||
"archive": "",
|
||||
"archived": "",
|
||||
"artifact": "",
|
||||
"audioArtifactAria": "",
|
||||
"createTask": "",
|
||||
"createTaskError": "",
|
||||
"creatingTask": "",
|
||||
"deny": "",
|
||||
"inboxFilter": "",
|
||||
"loadingApproval": "",
|
||||
"noArchivedMessages": "",
|
||||
"openArtifact": "",
|
||||
"openArtifactAria": "",
|
||||
"optionalComment": "",
|
||||
"recommendationsUnavailable": "",
|
||||
"reportsApprovals": "",
|
||||
"restore": "",
|
||||
"retryCreatingTask": "",
|
||||
"taskCreatedView": "",
|
||||
"taskProposalDismissed": "",
|
||||
"taskRecommendations": "",
|
||||
"videoArtifactAria": "",
|
||||
"viewTask": "",
|
||||
"viewTaskAria": "",
|
||||
"viewTaskLabel": ""
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "감사 검사",
|
||||
@@ -5303,7 +5352,14 @@
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
"resizeExpandedView": "",
|
||||
"archivedCopy": "",
|
||||
"doneHiddenCopy": "",
|
||||
"emptyCopy": "",
|
||||
"hideDone": "",
|
||||
"noActiveTasks": "",
|
||||
"noTasksYet": "",
|
||||
"showDone": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
@@ -6176,7 +6232,8 @@
|
||||
"openRouterRoutingOnlyHint": "",
|
||||
"openRouterAllowFallbacksHint": "",
|
||||
"openRouterRoutingSortHint": "",
|
||||
"requireParametersHint": ""
|
||||
"requireParametersHint": "",
|
||||
"modelOverrides": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord",
|
||||
@@ -6273,7 +6330,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
"qmdInstallUnavailable": "",
|
||||
"saveBeforeCompacting": "",
|
||||
"saveBeforeSwitching": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6621,7 +6680,11 @@
|
||||
"executorEscalationModel": "",
|
||||
"executorEscalationModelHelp": "",
|
||||
"selectExecutorEscalationModel": "",
|
||||
"noExecutorEscalationModel": ""
|
||||
"noExecutorEscalationModel": "",
|
||||
"modelOverrides": "",
|
||||
"projectLanesSubheading": "",
|
||||
"workflowLanesSubheading": "",
|
||||
"summarizationPointer": ""
|
||||
},
|
||||
"remote": {
|
||||
"acceptRoutes": "",
|
||||
@@ -6936,7 +6999,11 @@
|
||||
"projectTitle": "Project MCP servers",
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"enabledHint": ""
|
||||
"enabledHint": "",
|
||||
"transport": "",
|
||||
"transportHttp": "",
|
||||
"transportSse": "",
|
||||
"transportStdio": ""
|
||||
},
|
||||
"search": {
|
||||
"allSections": "",
|
||||
@@ -7870,7 +7937,8 @@
|
||||
"header": {
|
||||
"back": "뒤로",
|
||||
"backToList": "작업 목록으로 돌아가기",
|
||||
"editTask": "작업 편집"
|
||||
"editTask": "작업 편집",
|
||||
"popOut": ""
|
||||
},
|
||||
"inputTokens": "입력",
|
||||
"lastUsed": "마지막 사용",
|
||||
@@ -8231,6 +8299,22 @@
|
||||
"retry": "",
|
||||
"create": "",
|
||||
"error": ""
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": ""
|
||||
},
|
||||
"revise": "",
|
||||
"specLock": {
|
||||
"accepted": "",
|
||||
"alignment": "",
|
||||
"alignmentLabel": "",
|
||||
"captured": "",
|
||||
"currentPlan": "",
|
||||
"findings": "",
|
||||
"latestLock": "",
|
||||
"lockState": "",
|
||||
"relockChanged": "",
|
||||
"retainedHistory": ""
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9369,7 +9453,11 @@
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
"youMessage": "",
|
||||
"missingOutput": "",
|
||||
"missingOutputTimestamp": "",
|
||||
"statusUpdate": "",
|
||||
"statusUpdateTimestamp": ""
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.",
|
||||
@@ -9392,6 +9480,146 @@
|
||||
"targetLabel": "등록 대상",
|
||||
"targetInherit": "구성된 작업 대상 사용",
|
||||
"targetIssue": "GitHub 이슈",
|
||||
"targetDiscussion": "GitHub 토론"
|
||||
"targetDiscussion": "GitHub 토론",
|
||||
"activityTrace": "",
|
||||
"capturingScreenshot": "",
|
||||
"close": "",
|
||||
"confirmDataPoint": "",
|
||||
"confirmScreenshotRetention": "",
|
||||
"duplicateReview": "",
|
||||
"file": "",
|
||||
"final": "",
|
||||
"menu": "",
|
||||
"returnToPrompt": "",
|
||||
"review": "",
|
||||
"storeScreenshot": "",
|
||||
"structured": "",
|
||||
"structuredDataPoint": "",
|
||||
"suggestedHelp": "",
|
||||
"summary": "",
|
||||
"viewGitHub": ""
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "",
|
||||
"draft": "",
|
||||
"draftNarrative": "",
|
||||
"emptyDraft": "",
|
||||
"useDraft": ""
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "",
|
||||
"floatingTaskDetailBody": "",
|
||||
"genericFloatingWindow": "",
|
||||
"genericFloatingWindowBody": "",
|
||||
"genericWindowHeader": "",
|
||||
"headerAction": "",
|
||||
"headerlessTaskDetail": "",
|
||||
"taskDetail": "",
|
||||
"taskDetailBody": ""
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "",
|
||||
"resize": ""
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "",
|
||||
"gitlab": ""
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "",
|
||||
"converge": "",
|
||||
"convergedToMission": "",
|
||||
"description": "",
|
||||
"divergentCandidate": "",
|
||||
"noSessions": "",
|
||||
"persisted": "",
|
||||
"selectOrStart": "",
|
||||
"sessionTitle": "",
|
||||
"start": "",
|
||||
"title": ""
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "",
|
||||
"allOwners": "",
|
||||
"allSymbolKinds": "",
|
||||
"bothDirections": "",
|
||||
"depth": "",
|
||||
"distance": "",
|
||||
"edgeKinds": "",
|
||||
"findPath": "",
|
||||
"incoming": "",
|
||||
"limitResults": "",
|
||||
"neighbors": "",
|
||||
"next": "",
|
||||
"noNeighbors": "",
|
||||
"noPath": "",
|
||||
"nodeKinds": "",
|
||||
"none": "",
|
||||
"outgoing": "",
|
||||
"ownerDerived": "",
|
||||
"ownerFile": "",
|
||||
"pathLimitReached": "",
|
||||
"previous": "",
|
||||
"provenance": "",
|
||||
"raiseHopLimit": "",
|
||||
"refreshNeighbors": "",
|
||||
"searching": "",
|
||||
"selectNodeHint": "",
|
||||
"shortestPath": "",
|
||||
"showingOf": "",
|
||||
"useSelectedFrom": "",
|
||||
"useSelectedTo": ""
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": ""
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"openAria": "",
|
||||
"previewUnavailable": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "",
|
||||
"authorizationReceived": "",
|
||||
"cancel": "",
|
||||
"connected": "",
|
||||
"exchangingCode": "",
|
||||
"finishInBrowser": "",
|
||||
"handAuthorizationBack": "",
|
||||
"openSignInAgain": "",
|
||||
"pasteRedirectUrl": "",
|
||||
"signingInTo": ""
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "",
|
||||
"heading": "",
|
||||
"passed": "",
|
||||
"queued": "",
|
||||
"requestRejected": "",
|
||||
"running": ""
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "",
|
||||
"choosePairingMethod": "",
|
||||
"connectedAs": "",
|
||||
"connectedSuccess": "",
|
||||
"description": "",
|
||||
"installPlugin": "",
|
||||
"instructionsLabel": "",
|
||||
"loggingOut": "",
|
||||
"logoutRepair": "",
|
||||
"pairing": "",
|
||||
"pairingConfiguration": "",
|
||||
"phoneNumber": "",
|
||||
"qrCode": "",
|
||||
"refreshStatus": "",
|
||||
"repairInstructions": "",
|
||||
"requestPairingCode": "",
|
||||
"requestingCode": "",
|
||||
"status": "",
|
||||
"waitConnected": "",
|
||||
"waitingQr": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showLess": "Mostrar menos",
|
||||
"showMore": "Mostrar mais",
|
||||
"update": "Atualizar",
|
||||
"yes": "Sim"
|
||||
"yes": "Sim",
|
||||
"open": "",
|
||||
"remove": ""
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "O motor deste projeto não está em execução, então a automação de tarefas e as atualizações em tempo real podem estar pausadas. Inicie-o agora para reconectar o painel.",
|
||||
@@ -1155,7 +1157,9 @@
|
||||
"disableHeartbeatsSummary": "{{count}} heartbeats desativados; ignorados: {{skipped}}",
|
||||
"enableHeartbeatsCountHint": "Ativar {{count}} heartbeats desativados do projeto",
|
||||
"disableHeartbeatsCountHint": "Desativar {{count}} heartbeats ativados do projeto",
|
||||
"bulkFailures": "falharam {{count}}"
|
||||
"bulkFailures": "falharam {{count}}",
|
||||
"detailLabel": "",
|
||||
"lastCompletedStep": ""
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1399,7 +1403,8 @@
|
||||
"noModelsAvailable": "Nenhum modelo disponível",
|
||||
"currentAgentTarget": "Agente atual: {{name}}",
|
||||
"currentModelTarget": "Modelo atual: {{model}}",
|
||||
"currentDefaultTarget": "Usando o destino de chat padrão"
|
||||
"currentDefaultTarget": "Usando o destino de chat padrão",
|
||||
"restore": ""
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1938,7 +1943,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "",
|
||||
"verificationRequests": ""
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "Adicionar comentário",
|
||||
@@ -2016,7 +2023,20 @@
|
||||
"toLabel": "Para:",
|
||||
"wakeAgentCheckbox": "Acordar agente imediatamente",
|
||||
"wakeAlwaysImmediate": "(o agente já está configurado para o modo de resposta imediata)",
|
||||
"wakeOneOff": "(substituição única apenas para esta mensagem)"
|
||||
"wakeOneOff": "(substituição única apenas para esta mensagem)",
|
||||
"addSection": "",
|
||||
"ariaLabel": "",
|
||||
"attachStructure": "",
|
||||
"mode": "",
|
||||
"noStructures": "",
|
||||
"quickMessage": "",
|
||||
"removeSection": "",
|
||||
"removeStructure": "",
|
||||
"report": "",
|
||||
"reportTitle": "",
|
||||
"sectionBody": "",
|
||||
"sectionHeading": "",
|
||||
"selectStructure": ""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "Cancelar",
|
||||
@@ -3492,7 +3512,36 @@
|
||||
"typeSystem": "Sistema",
|
||||
"typeUserToAgent": "Você → Agente",
|
||||
"user": "Usuário",
|
||||
"you": "Você"
|
||||
"you": "Você",
|
||||
"all": "",
|
||||
"approvalStatus": "",
|
||||
"approvalUnavailable": "",
|
||||
"approve": "",
|
||||
"archive": "",
|
||||
"archived": "",
|
||||
"artifact": "",
|
||||
"audioArtifactAria": "",
|
||||
"createTask": "",
|
||||
"createTaskError": "",
|
||||
"creatingTask": "",
|
||||
"deny": "",
|
||||
"inboxFilter": "",
|
||||
"loadingApproval": "",
|
||||
"noArchivedMessages": "",
|
||||
"openArtifact": "",
|
||||
"openArtifactAria": "",
|
||||
"optionalComment": "",
|
||||
"recommendationsUnavailable": "",
|
||||
"reportsApprovals": "",
|
||||
"restore": "",
|
||||
"retryCreatingTask": "",
|
||||
"taskCreatedView": "",
|
||||
"taskProposalDismissed": "",
|
||||
"taskRecommendations": "",
|
||||
"videoArtifactAria": "",
|
||||
"viewTask": "",
|
||||
"viewTaskAria": "",
|
||||
"viewTaskLabel": ""
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "Verificações de auditoria",
|
||||
@@ -5313,7 +5362,14 @@
|
||||
"resize": "Redimensionar dock direito",
|
||||
"viewExpanded": "{{label}} expandido",
|
||||
"views": "Visualizações do dock direito",
|
||||
"resizeExpandedView": "Redimensionar janela expandida do dock direito"
|
||||
"resizeExpandedView": "Redimensionar janela expandida do dock direito",
|
||||
"archivedCopy": "",
|
||||
"doneHiddenCopy": "",
|
||||
"emptyCopy": "",
|
||||
"hideDone": "",
|
||||
"noActiveTasks": "",
|
||||
"noTasksYet": "",
|
||||
"showDone": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "…e mais {{count}}",
|
||||
@@ -6203,39 +6259,6 @@
|
||||
"followUpPolicyHint": "Padrão: somente sugerir.",
|
||||
"retentionDaysHint": "Padrão: 30."
|
||||
},
|
||||
"nodeSync": {
|
||||
"alwaysAsk": "Sempre perguntar",
|
||||
"automaticallySynchronizeSettingsBetweenThisNodeAndConnected": "Sincroniza automaticamente as configurações entre este nó e os nós remotos conectados. Padrão: desativado.",
|
||||
"conflictResolution": "Resolução de conflitos",
|
||||
"enableAutomaticSettingsSync": " Ativar sincronização automática de configurações ",
|
||||
"every15Minutes": "A cada 15 minutos",
|
||||
"every1Hour": "A cada 1 hora",
|
||||
"every30Minutes": "A cada 30 minutos",
|
||||
"every5Minutes": "A cada 5 minutos",
|
||||
"includeAPIKeysAndOAuthTokensInSync": "Inclui chaves de API e tokens OAuth nas operações de sincronização. Padrão: desativado.",
|
||||
"keepLocal": "Manter local",
|
||||
"keepRemote": "Manter remoto",
|
||||
"lastWriteWins": "A última gravação prevalece",
|
||||
"nodeSync": "Sincronização de nós",
|
||||
"syncInterval": "Intervalo de sincronização",
|
||||
"syncModelAuthCredentials": " Sincronizar credenciais de autenticação de modelo ",
|
||||
"workflowSettingsNotSynced": "As configurações de fluxo de trabalho ainda não são sincronizadas entre nós.",
|
||||
"syncIntervalHint": "Padrão: a cada 15 minutos.",
|
||||
"conflictResolutionHint": "Padrão: a última gravação prevalece."
|
||||
},
|
||||
"nodeRouting": {
|
||||
"blockExecution": "Bloquear execução",
|
||||
"configureHowTasksAreRoutedToExecutionNodes": "Configure como as tarefas são roteadas para os nós de execução.",
|
||||
"defaultExecutionNode": "Nó de execução padrão",
|
||||
"fallBackToLocal": "Retornar ao local",
|
||||
"localExecutionNoDefaultNode": "Execução local (sem nó padrão)",
|
||||
"nodeRouting": "Roteamento de nós",
|
||||
"selectedNode": "Nó selecionado:",
|
||||
"theseSettingsApplyAtTheProjectLevel": "Essas configurações se aplicam no nível do projeto.",
|
||||
"unavailableNodePolicy": "Política de nó indisponível",
|
||||
"usedWhenATaskHasNoNodeOverride": "Usado quando uma tarefa não tem substituição de nó. O status do nó é exibido para uma seleção de roteamento mais segura. Sem padrão — não definido (execução local).",
|
||||
"unavailableNodePolicyHint": "Padrão: bloquear execução."
|
||||
},
|
||||
"reset": {
|
||||
"button": "Redefinir configurações",
|
||||
"buttonShort": "Redefinir",
|
||||
@@ -6680,6 +6703,487 @@
|
||||
"quickAddSubmitOnEnter": "",
|
||||
"quickAddSubmitOnEnterHint": ""
|
||||
},
|
||||
"nodeSync": {
|
||||
"alwaysAsk": "Sempre perguntar",
|
||||
"automaticallySynchronizeSettingsBetweenThisNodeAndConnected": "Sincroniza automaticamente as configurações entre este nó e os nós remotos conectados. Padrão: desativado.",
|
||||
"conflictResolution": "Resolução de conflitos",
|
||||
"enableAutomaticSettingsSync": " Ativar sincronização automática de configurações ",
|
||||
"every15Minutes": "A cada 15 minutos",
|
||||
"every1Hour": "A cada 1 hora",
|
||||
"every30Minutes": "A cada 30 minutos",
|
||||
"every5Minutes": "A cada 5 minutos",
|
||||
"includeAPIKeysAndOAuthTokensInSync": "Inclui chaves de API e tokens OAuth nas operações de sincronização. Padrão: desativado.",
|
||||
"keepLocal": "Manter local",
|
||||
"keepRemote": "Manter remoto",
|
||||
"lastWriteWins": "A última gravação prevalece",
|
||||
"nodeSync": "Sincronização de nós",
|
||||
"syncInterval": "Intervalo de sincronização",
|
||||
"syncModelAuthCredentials": " Sincronizar credenciais de autenticação de modelo ",
|
||||
"workflowSettingsNotSynced": "As configurações de fluxo de trabalho ainda não são sincronizadas entre nós.",
|
||||
"syncIntervalHint": "Padrão: a cada 15 minutos.",
|
||||
"conflictResolutionHint": "Padrão: a última gravação prevalece."
|
||||
},
|
||||
"nodeRouting": {
|
||||
"blockExecution": "Bloquear execução",
|
||||
"configureHowTasksAreRoutedToExecutionNodes": "Configure como as tarefas são roteadas para os nós de execução.",
|
||||
"defaultExecutionNode": "Nó de execução padrão",
|
||||
"fallBackToLocal": "Retornar ao local",
|
||||
"localExecutionNoDefaultNode": "Execução local (sem nó padrão)",
|
||||
"nodeRouting": "Roteamento de nós",
|
||||
"selectedNode": "Nó selecionado:",
|
||||
"theseSettingsApplyAtTheProjectLevel": "Essas configurações se aplicam no nível do projeto.",
|
||||
"unavailableNodePolicy": "Política de nó indisponível",
|
||||
"usedWhenATaskHasNoNodeOverride": "Usado quando uma tarefa não tem substituição de nó. O status do nó é exibido para uma seleção de roteamento mais segura. Sem padrão — não definido (execução local).",
|
||||
"unavailableNodePolicyHint": "Padrão: bloquear execução."
|
||||
},
|
||||
"reset": {
|
||||
"button": "Redefinir configurações",
|
||||
"buttonShort": "Redefinir",
|
||||
"buttonTitle": "Redefinir configurações para os padrões",
|
||||
"dialogAriaLabel": "Redefinir configurações",
|
||||
"dialogTitle": "Redefinir configurações",
|
||||
"dialogBody": "Escolha o que redefinir para os padrões. Isso não pode ser desfeito.",
|
||||
"resetMenuAction": "Redefinir este menu ({{section}})",
|
||||
"resetAllProjectAction": "Redefinir todas as configurações do projeto",
|
||||
"menuResetSuccess": "As configurações de {{section}} foram redefinidas para os padrões",
|
||||
"allProjectResetSuccess": "Todas as configurações do projeto foram redefinidas para os padrões"
|
||||
},
|
||||
"commands": {
|
||||
"buildCommand": "Comando de build",
|
||||
"commands": "Comandos",
|
||||
"commandUsedToBuildTheProjectInjectedInto": "Comando usado para fazer o build do projeto — injetado nas especificações de tarefas geradas. Sem padrão — não definido.",
|
||||
"commandUsedToRunTestsInjectedIntoGenerated": "Comando usado para executar os testes — injetado nas especificações de tarefas geradas. Sem padrão — não definido.",
|
||||
"eGPnpmBuild": "ex.: pnpm build",
|
||||
"eGPnpmTest": "ex.: pnpm test",
|
||||
"testCommand": "Comando de teste"
|
||||
},
|
||||
"runtimesRuntimes": {
|
||||
"hermesRuntime": "Hermes Runtime",
|
||||
"openClawRuntime": "OpenClaw Runtime",
|
||||
"paperclipRuntime": "Paperclip Runtime"
|
||||
},
|
||||
"experimental": {
|
||||
"experimentalFeatures": "Recursos experimentais",
|
||||
"experimentalFeaturesAreEarlyCapabilitiesThatAreNot": " Recursos experimentais são funcionalidades iniciais que ainda não estão totalmente estáveis. Ative-os para testar novas funcionalidades, mas esteja ciente de que podem mudar ou ser removidos. Padrão: desativado para cada feature flag abaixo. ",
|
||||
"featureFlags": "Feature Flags"
|
||||
},
|
||||
"plugins": {
|
||||
"fusionPlugins": " Plugins do Fusion ",
|
||||
"piExtensions": " Extensões Pi ",
|
||||
"pluginManagerType": "Tipo de gerenciador de plugins",
|
||||
"plugins": "Plugins"
|
||||
},
|
||||
"agentPermissions": {
|
||||
"agentPermissions": "Permissões do agente",
|
||||
"agentProvisioningApprovals": "Aprovações de provisionamento de agente",
|
||||
"configureProjectLevelApprovalBehaviorForDurableProvisioning": " Configure o comportamento de aprovação em nível de projeto para ferramentas de provisionamento durável (fn_agent_create/fn_agent_delete). Padrão: nenhuma política de aprovação configurada (vazio). ",
|
||||
"perAgentSettingsOverrideProjectDefaultsEachCategory": "Os padrões do projeto se aplicam a agentes permanentes, workers de tarefas efêmeros e workers executores de fallback, a menos que uma substituição por agente seja definida. Regras exatas de ferramentas se combinam com a opção legada de criação de tarefa efêmera. Padrão: não definido — toda categoria de ação permite por padrão até que uma categoria seja explicitamente restringida."
|
||||
},
|
||||
"authentication": {
|
||||
"enterAPIKey": "Digite a chave de API",
|
||||
"key": "Chave: "
|
||||
},
|
||||
"actions": {
|
||||
"cancel": "Cancelar",
|
||||
"save": "Salvar"
|
||||
},
|
||||
"database": {
|
||||
"embeddedConnectionCapHelp": "Número máximo de conexões do servidor para o PostgreSQL integrado do Fusion. Aplicado após reiniciar o Fusion. Intervalo: 32–2.000. Não definido por padrão — o Fusion escolhe 500, ou 150 no Windows, onde cada conexão é um processo separado e limites mais altos podem travar os backends. O PostgreSQL externo usa o limite de conexões do seu provedor.",
|
||||
"embeddedConnectionCap": "Limite de conexões do PostgreSQL integrado",
|
||||
"embeddedConnectionCapError": "Informe um valor entre 32 e 2.000.",
|
||||
"advanced": "Configurações avançadas de banco de dados",
|
||||
"embeddedConnectionCapPlaceholder": "auto"
|
||||
},
|
||||
"remote": {
|
||||
"acceptRoutes": " Aceitar rotas ",
|
||||
"advancedNamedTunnel": "Avançado (Named Tunnel)",
|
||||
"advancedSettings": "Configurações avançadas",
|
||||
"authenticatedURL": "URL autenticada:",
|
||||
"authLinks": "Links de autenticação",
|
||||
"authLinkTokenType": "Tipo de token do link de autenticação",
|
||||
"automaticallyRestoreTunnelOnStartupIfItWas": "Restaura automaticamente o túnel na inicialização se ele estava em execução quando foi parado pela última vez. Padrão: desativado.",
|
||||
"cloudflare": "Cloudflare",
|
||||
"cloudflaredInstalled": "cloudflared instalado com sucesso",
|
||||
"cloudflaredIsInstalled": "cloudflared está instalado",
|
||||
"cloudflaredIsNotInstalled": "cloudflared não está instalado",
|
||||
"cloudflaredMustBeInstalledToStartTheTunnel": "É necessário instalar o cloudflared para iniciar o túnel",
|
||||
"enableShortLivedTokens": " Ativar tokens de curta duração ",
|
||||
"expiresAt": " · Expira em {{expiresAt}}",
|
||||
"external": "Externo ",
|
||||
"externalTunnelQRCode": "QR code do túnel externo",
|
||||
"generateQR": "Gerar QR",
|
||||
"generateShortLivedToken": "Gerar token de curta duração",
|
||||
"httpsYourDomainExample": "https://your-domain.example",
|
||||
"ifHomebrewIsUnavailable": "Se o Homebrew não estiver disponível: ",
|
||||
"ingressURL": "URL do ingress",
|
||||
"installCloudflared": "Instalar cloudflared",
|
||||
"installing": "Instalando…",
|
||||
"lastShortLivedTokenExpiresAt": "O último token de curta duração expira em ",
|
||||
"manualInstall": "Instalação manual: ",
|
||||
"ms": "ms)",
|
||||
"namedTunnelModeEnabled": "Modo Named Tunnel ativado — configure o nome do túnel, o token e a URL do ingress abaixo.",
|
||||
"noExpiry": " · Sem expiração",
|
||||
"persistentToken": "Token persistente",
|
||||
"persistentTokenRegenerated": "Token persistente regenerado",
|
||||
"qRSVGMarkup": "Marcação SVG do QR",
|
||||
"regeneratePersistentToken": "Regenerar token persistente",
|
||||
"rememberLastRunningState": " Lembrar último estado de execução ",
|
||||
"remoteAccess": "Acesso remoto",
|
||||
"remoteAccessCode": "Código de acesso remoto:",
|
||||
"remoteAccessQRCode": "QR code de acesso remoto",
|
||||
"remoteProvider": "Provedor remoto",
|
||||
"restarting": "Reiniciando…",
|
||||
"scanThisQRCodeOnYourPhone": "Escaneie este QR code no seu celular",
|
||||
"scanToConnect": "Escaneie para conectar:",
|
||||
"scanToOpen": "Escaneie para abrir:",
|
||||
"selectAProviderAboveToConfigureRemoteAccess": "Selecione um provedor acima para configurar o acesso remoto.",
|
||||
"shortLivedToken": "Token de curta duração",
|
||||
"shortLivedTokenGenerated": "Token de curta duração gerado",
|
||||
"shortLivedTTLMs": "TTL de curta duração (ms)",
|
||||
"showURL": "Mostrar URL",
|
||||
"startFresh": "Começar do zero",
|
||||
"starting": "Iniciando…",
|
||||
"startTunnel": "Iniciar túnel",
|
||||
"stopping": "Parando…",
|
||||
"stopTunnel": "Parar túnel",
|
||||
"tailnetURL": "URL da tailnet:",
|
||||
"tailscale": "Tailscale",
|
||||
"tailscaleFunnelWillExposeThisDashboardOnYour": "O Tailscale Funnel vai expor este painel na URL pública ",
|
||||
"tokenType": " Tipo de token: ",
|
||||
"tunnelDetected": " túnel detectado",
|
||||
"tunnelName": "Nome do túnel",
|
||||
"tunnelRestarted": "Túnel remoto reiniciado",
|
||||
"tunnelStarted": "Túnel remoto iniciado",
|
||||
"tunnelStopped": "Túnel remoto parado",
|
||||
"tunnelToken": "Token do túnel",
|
||||
"uRLAndQRGenerationUseTheSelectedToken": " A geração de URL e QR usa o tipo de token selecionado. ",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": " da sua tailnet — não é necessário configurar hostname ou porta.",
|
||||
"useExisting": "Usar existente",
|
||||
"usingQuickTunnel": "Usando o Quick Tunnel — cria automaticamente uma URL aleatória em trycloudflare.com, sem necessidade de conta. Padrão: ativado.",
|
||||
"installationFailed": "Falha na instalação",
|
||||
"acceptRoutesHint": "Padrão: desativado.",
|
||||
"shortLivedEnabledHint": "Padrão: desativado.",
|
||||
"shortLivedTtlMsHint": "Padrão: 900000 (15 minutos).",
|
||||
"cloudflareTunnelURL": "",
|
||||
"tunnelURL": ""
|
||||
},
|
||||
"worktrees": {
|
||||
"allowSilentSiblingBranchRenameDuringExecutorConflicts": " Permitir renomeação silenciosa de branch irmã durante conflitos do executor ",
|
||||
"alsoRebaseOntoLocalDefaultBranchHEAD": " Também fazer rebase no HEAD da branch padrão local ",
|
||||
"and": " e ",
|
||||
"andCanHidePriorCommitsFromTheDefault": " e pode ocultar commits anteriores do fluxo de recuperação padrão. Padrão: desativado. ",
|
||||
"autoDetectFusionBinWorktrunkOrPATH": "detecção automática (~/.fusion/bin/worktrunk ou $PATH)",
|
||||
"awaitingApproval": "Aguardando aprovação — abra Aprovações para continuar.",
|
||||
"branchCollisionSilentlyForksWorkOntoSiblingBranches": " bifurca silenciosamente o trabalho para branches irmãs como ",
|
||||
"browse": " Procurar ",
|
||||
"browseWorktreesDirectory": "Procurar diretório de worktrees",
|
||||
"closeParenPeriod": ").",
|
||||
"defaultsTo": ". O padrão é ",
|
||||
"defaultsToWorktreesLeaveEmptyUnlessOverriding": "O padrão é .worktrees — deixe em branco a menos que queira sobrescrever",
|
||||
"disabledByDefaultOptInWhenEnabledFusion": " Desativado por padrão (opcional). Quando ativado, o Fusion delega ao ",
|
||||
"discouragedThisRestoresTheLegacyBehaviorWhereA": " Não recomendado. Isso restaura o comportamento legado em que uma colisão de branch ativa do ",
|
||||
"enableWorktrunkAndRequestApprovalToInstallThe": "Ative o worktrunk e solicite aprovação para instalar o release fixado.",
|
||||
"enableWorktrunkIntegration": " Ativar integração com o worktrunk ",
|
||||
"failAndPauseTheTaskDefault": "Falhar e pausar a tarefa (padrão)",
|
||||
"fallBackToFusionsNativeWorktreeBackend": "Recorrer ao backend nativo de worktree do Fusion",
|
||||
"forWorktreeCreateSyncPruneAndRemoveOperations": " as operações de criação, sincronização, limpeza e remoção de worktree, seguindo a estrutura de diretórios do worktrunk. ",
|
||||
"inAdditionToTheRemoteRebaseAboveAlso": " Além do rebase remoto acima, também é feito o rebase da branch da tarefa sobre o HEAD da branch padrão local (rootDir). Isso identifica tarefas irmãs que sofreram merge localmente mas ainda não passaram por push — sem isso, duas tarefas simultâneas em que uma exclui código podem fazer com que a outra reintroduza esse código silenciosamente pela estratégia de fallback. Ativado por padrão; desative apenas se causar problemas no seu fluxo de trabalho. ",
|
||||
"installedAt": " instalado em ",
|
||||
"installTheWorktrunkBinaryBelowToEnableThis": "Instale o binário do worktrunk abaixo para ativar esta integração.",
|
||||
"installWorktrunk": "Instalar binário do worktrunk",
|
||||
"keepsProgressMovingBySwitchingToFusionApos": " mantém o progresso em andamento ao alternar para o backend de worktree integrado do Fusion. ",
|
||||
"limitsTotalGitWorktreesIncludingInReviewTasks": "Limita o total de worktrees do git, incluindo tarefas em revisão. Padrão: 4.",
|
||||
"maxWorktrees": "Máximo de worktrees",
|
||||
"worktreeLimitEnabled": "Limitar worktrees simultâneas",
|
||||
"worktreeLimitEnabledHelp": "Quando ativado, o Máximo de worktrees limita quantas tarefas podem ocupar uma worktree ao mesmo tempo. Quando desativado, o Máximo de tarefas simultâneas passa a ser o único limite. De qualquer forma, as tarefas sempre são executadas em sua própria worktree do git — isso não muda onde o trabalho é executado. Padrão: ativado.",
|
||||
"offByDefaultOptInWhenEnabledCompleted": "Desativado por padrão (opcional). Quando ativado, as worktrees de tarefas concluídas voltam para um pool ocioso em vez de serem excluídas, preservando caches de build para uma inicialização mais rápida. Mutuamente exclusivo com a nomeação de worktree por ID da tarefa.",
|
||||
"recycleNotApplicableWithTaskIdNaming": "Não disponível com a nomeação de worktree por ID da tarefa — esse modo fixa cada tarefa ao seu próprio diretório de worktree, o que é mutuamente exclusivo com o pool de reciclagem. Altere a nomeação para Aleatório ou Título da tarefa para ativar a reciclagem.",
|
||||
"openApprovals": "Abrir aprovações",
|
||||
"optionalLeaveBlankToAutoResolveFusionWill": "Opcional. Deixe em branco para resolução automática; o Fusion vai oferecer para instalar no primeiro uso.",
|
||||
"optionalSupports": " Opcional. Compatível com ",
|
||||
"pnpmInstallFrozenLockfile": "pnpm install --frozen-lockfile",
|
||||
"randomNamesEGSwiftFalcon": "Nomes aleatórios (ex.: swift-falcon)",
|
||||
"rebaseFromRemoteBeforeMerge": " Rebase do remoto antes do merge ",
|
||||
"rebaseRemote": "Remoto para rebase",
|
||||
"recycleWorktrees": " Reciclar worktrees ",
|
||||
"selectWorktreesDir": "Selecionar diretório de worktrees",
|
||||
"shellCommandToRunInEachNewWorktree": "Comando de shell a ser executado em cada nova worktree após a criação. Sem padrão — não definido.",
|
||||
"stopsOnWorktrunkErrorsForExplicitOperatorRecovery": " interrompe diante de erros do worktrunk para recuperação explícita pelo operador; ",
|
||||
"taskIDEGFN042": "ID da tarefa (ex.: FN-042)",
|
||||
"taskTitleEGFixLoginBug": "Título da tarefa (ex.: fix-login-bug)",
|
||||
"tryAgain": "Tentar novamente",
|
||||
"useGitDefault": "Usar padrão do git",
|
||||
"whenEnabledTheMergerFetchesFromTheConfigured": "Quando ativado, o merger busca (fetch) no remoto configurado e faz o rebase da branch da tarefa sobre a ponta mais recente da branch padrão antes do merge — capturando pushes simultâneos de outros colaboradores ou workers do Fusion. Quaisquer conflitos revelados pelo rebase seguem para o pipeline existente de resolução inteligente/IA. Padrão: ativado.",
|
||||
"whenUnsetOnlyAffectsNewlyCreatedWorktrees": " quando não definido. Afeta apenas worktrees recém-criadas. ",
|
||||
"whichRemoteToFetchForThePreMerge": " De qual remoto buscar (fetch) para o rebase pré-merge. \"Usar padrão do git\" recorre ao remoto configurado para a branch padrão (normalmente ",
|
||||
"worktreeInitCommand": "Comando de inicialização de worktree",
|
||||
"worktreeNamingStyle": "Estilo de nomeação de worktree",
|
||||
"worktrees": "Worktrees",
|
||||
"worktreesDirectory": "Diretório de worktrees",
|
||||
"worktrunk": " worktrunk ",
|
||||
"worktrunkBinaryPath": "Caminho do binário do worktrunk",
|
||||
"worktrunkFailureBehavior": "Comportamento em caso de falha do worktrunk",
|
||||
"worktrunkIntegration": "Integração com o worktrunk",
|
||||
"worktreesPickerNote": "Navegue até a pasta onde o Fusion deve criar as worktrees das tarefas e selecione o diretório atual.",
|
||||
"showWorktreeGroupingHelp": "Desativado por padrão. Quando ativado, as colunas de WIP e de processamento sempre agrupam as tarefas por worktree e exibem os nomes das worktrees, incluindo colunas de processamento no modo fluxo de trabalho.",
|
||||
"copyFilesHelp": "Opcional. Arquivos comuns relativos à raiz do repositório são copiados para worktrees novas ou reaproveitadas do pool antes da execução dos comandos de inicialização. Arquivos ou diretórios ausentes são ignorados sem expor o conteúdo. Padrão: vazio (nenhum arquivo copiado).",
|
||||
"namingStyleNotApplicableWhenRecycling": "O estilo de nomeação não se aplica ao reciclar worktrees — as worktrees do pool mantêm os nomes existentes. \"ID da tarefa\" não está disponível aqui porque worktrees fixadas a uma tarefa são mutuamente exclusivas com a reciclagem; desative Reciclar worktrees para usá-lo.",
|
||||
"howToNameFreshWorktreeDirectories": "Como nomear novos diretórios de worktree. Aplica-se apenas quando a reciclagem está desativada. \"ID da tarefa\" também fixa cada tarefa ao seu próprio diretório de worktree durante todo o seu ciclo de vida (mutuamente exclusivo com a reciclagem). Padrão: aleatório."
|
||||
},
|
||||
"configVersions": {
|
||||
"title": "Versões de configuração",
|
||||
"description": "Restaura qualquer versão de configuração do projeto registrada.",
|
||||
"loading": "Carregando versões…",
|
||||
"loadError": "Não foi possível carregar as versões de configuração",
|
||||
"rollbackError": "Não foi possível reverter a configuração",
|
||||
"empty": "Ainda não há versões de configuração.",
|
||||
"rollback": "Reverter",
|
||||
"rollingBack": "Revertendo…",
|
||||
"confirmTitle": "Reverter a configuração?",
|
||||
"confirmMessage": "Restaurar esta versão? A reversão é registrada como uma nova versão.",
|
||||
"confirmRollback": "Reverter"
|
||||
},
|
||||
"mcp": {
|
||||
"globalTitle": "Servidores MCP globais",
|
||||
"projectTitle": "Servidores MCP do projeto",
|
||||
"globalDescription": "Configure servidores MCP compartilhados por todos os projetos. As configurações do projeto podem substituir ou desativar esses servidores pelo nome.",
|
||||
"projectDescription": "Configure servidores MCP específicos do projeto, substituições e servidores herdados desativados.",
|
||||
"enabledHint": "Padrão: desativado, sem servidores configurados.",
|
||||
"transport": "",
|
||||
"transportHttp": "",
|
||||
"transportSse": "",
|
||||
"transportStdio": ""
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "Diretório atual:",
|
||||
"projectRoot": "(raiz do projeto)"
|
||||
},
|
||||
"movedStub": {
|
||||
"modelLanes": "As faixas de modelo por fase (execução, planejamento, revisor, seus fallbacks e o resumidor de título) agora ficam no fluxo de trabalho.",
|
||||
"openWorkflowSettings": "Abrir configurações do fluxo de trabalho",
|
||||
"reviewVerification": "As configurações de revisão, correção automática de verificação e aplicação de escopo agora ficam no fluxo de trabalho.",
|
||||
"stepExecution": "As configurações de execução de etapas (executar etapas em novas sessões, máximo de etapas paralelas) agora ficam no fluxo de trabalho.",
|
||||
"summarizerModelInline": "O modelo usado para resumir agora fica no fluxo de trabalho (faixa do resumidor de título). Abra as configurações do fluxo de trabalho para escolhê-lo."
|
||||
},
|
||||
"prompts": {
|
||||
"surfaceExplanation": "Use esta seção para modelos de prompt de sistema de papéis de agente, atribuições de papéis e substituições globais de segmento PromptKey. Os prompts de etapa por fluxo de trabalho para nós de prompt e gate são editados no Editor de Fluxo de Trabalho. Sem padrão — não definido (os prompts de papel integrados se aplicam até serem substituídos)."
|
||||
},
|
||||
"modelPricing": {
|
||||
"description": "Substitui as taxas por 1 milhão de tokens usadas nas estimativas de custo do Command Center. As substituições têm prioridade sobre a linha de base integrada; modelos não listados continuam usando a linha de base. Sem padrão — não definido (sem substituições)."
|
||||
},
|
||||
"projectModels": {
|
||||
"addPreset": " Adicionar predefinição ",
|
||||
"advancedWorkflowPolicy": " Política avançada de fluxo de trabalho ",
|
||||
"aIMergeCommitSummaries": " Resumos de commit de merge por IA ",
|
||||
"aITitleAndGitCommitMessageSummarization": " Resumo de título e mensagem de commit do Git por IA ",
|
||||
"automaticallyCompactContextWhenApproachingThisTokenCount": "Compacta o contexto automaticamente ao se aproximar desta contagem de tokens. Deixe em branco para não haver limite (compactação apenas em erros de overflow). Defina um número para compactar proativamente ao atingir essa contagem de tokens. Sem padrão — não definido (sem limite).",
|
||||
"autoSelectPresetBasedOnTaskSize": " Selecionar predefinição automaticamente com base no tamanho da tarefa ",
|
||||
"autoSummarizeLongDescriptionsAsTitles": " Resumir automaticamente descrições longas como títulos ",
|
||||
"taskDefinitionInInputLanguage": "Escrever definições de tarefa no idioma de entrada do operador",
|
||||
"taskDefinitionInInputLanguageHelp": "Quando ativado, o texto gerado da definição de tarefa usa os idiomas de entrada detectáveis suportados (espanhol, francês, coreano ou chinês como zh-CN). Títulos, marcadores e código permanecem em inglês. Entradas não suportadas ou não detectáveis permanecem em inglês. Padrão: desativado.",
|
||||
"configuredPresets": "Predefinições configuradas",
|
||||
"configuresTheModelUsedForTwoShortSummary": " Configura o modelo usado em duas tarefas de resumo curto: gerar automaticamente títulos de tarefa a partir de descrições longas e gerar resumos de commit de merge a partir dos commits das etapas e das estatísticas de diff. ",
|
||||
"defaultWorkflowModelLaneActions": "Ações padrão das faixas de modelo do fluxo de trabalho",
|
||||
"defaultWorkflowModelLanes": "Faixas de modelo padrão do fluxo de trabalho",
|
||||
"delete": " Excluir ",
|
||||
"edit": " Editar ",
|
||||
"executorModel": "Modelo do executor",
|
||||
"executorEscalationModel": "Modelo de escalonamento do executor",
|
||||
"executorEscalationModelHelp": "Modelo alternativo usado quando as novas tentativas de falha de ferramenta se esgotam. Sem padrão — não definido significa nenhum modelo alternativo; configure a política de escalonamento e um destino de nó opcional em Agendamento.",
|
||||
"selectExecutorEscalationModel": "Selecione um modelo de escalonamento",
|
||||
"noExecutorEscalationModel": "Nenhum modelo de escalonamento",
|
||||
"fallsBackTo": " Recorre a: ",
|
||||
"loadingAvailableModels": "Carregando modelos disponíveis…",
|
||||
"loadingWorkflowModelLanes": "Carregando faixas de modelo do fluxo de trabalho…",
|
||||
"modelLanes": "Faixas de modelo",
|
||||
"modelPresets": "Predefinições de modelo",
|
||||
"name": "Nome",
|
||||
"noCap": "Sem limite",
|
||||
"noModelsAvailableConfigureAuthenticationBeforeSelectingWorkflow": " Nenhum modelo disponível. Configure a autenticação antes de selecionar as faixas de modelo do fluxo de trabalho. ",
|
||||
"noModelsAvailableConfigureAuthenticationFirst": " Nenhum modelo disponível. Configure a autenticação primeiro. ",
|
||||
"noModelsAvailableConfigureAuthenticationFirst2": "Nenhum modelo disponível. Configure a autenticação primeiro.",
|
||||
"noPreset": "Nenhuma predefinição",
|
||||
"noPresetsConfiguredYet": "Nenhuma predefinição configurada ainda.",
|
||||
"openAProjectToEditWorkflowModelLanes": "Abra um projeto para editar as faixas de modelo do fluxo de trabalho.",
|
||||
"overrideGlobalModelSettingsAtTheProjectLevel": " Substitui as configurações globais de modelo no nível do projeto. Cada faixa controla um contexto específico de uso de IA. Faixas não definidas herdam da faixa global correspondente. O modelo padrão do projeto é o fallback para este projeto quando uma faixa mais específica não está definida. ",
|
||||
"presetEditor": "Editor de predefinições",
|
||||
"reset": " Redefinir ",
|
||||
"resetToDefaultNoCap": "Redefinir para o padrão (sem limite)",
|
||||
"resetToInheritFromGlobal": "Redefinir para herdar do global",
|
||||
"resetToInheritFromWorkflow": "Redefinir para herdar do fluxo de trabalho",
|
||||
"reviewerModel": "Modelo do revisor",
|
||||
"theseProjectOverridesApplyToTheActiveDefault": " Essas substituições do projeto se aplicam ao fluxo de trabalho padrão ativo. ",
|
||||
"tokenCap": "Limite de tokens",
|
||||
"useDefault": "Usar padrão",
|
||||
"useWorkflowDefault": "Usar padrão do fluxo de trabalho",
|
||||
"whenEnabledMergeCommitMessagesIncludeAnAI": " Quando ativado, as mensagens de commit de merge incluem um assunto gerado por IA além de um resumo no corpo (narrativa + tópicos + estatísticas de diff), em vez de apenas listar os assuntos dos commits das etapas. Usa o modelo de resumo de título. Padrão: ativado. ",
|
||||
"whenEnabledTasksCreatedWithoutATitleBut": " Quando ativado, tarefas criadas sem título, mas com descrições com mais de 200 caracteres, recebem automaticamente um título gerado por IA (máximo de 60 caracteres). O mesmo modelo também é usado para gerar corpos de mensagem de commit de merge como fallback quando o log de commits da branch está vazio (por exemplo, merges squash sem commits exclusivos), e títulos de issues de rastreamento do GitHub quando uma tarefa rastreada ainda não tem título. Padrão: desativado. ",
|
||||
"autoSelectModelPresetHint": "Padrão: desativado.",
|
||||
"prTitlePromptInstructionsHelp": "Orienta o título gerado por IA ao criar um PR. Deixe em branco para usar o prompt padrão de metadados do PR. Sem padrão — não definido.",
|
||||
"prDescriptionPromptInstructionsHelp": "Orienta o resumo, as alterações e as seções de teste gerados por IA ao criar um PR. Deixe em branco para usar o prompt padrão de metadados do PR. Sem padrão — não definido.",
|
||||
"chatHeading": "Chat",
|
||||
"chatDescription": "Escolha o destino padrão para novos chats diretos e se o Novo Chat deve perguntar ou usar esse padrão imediatamente.",
|
||||
"chatNewSessionMode": "Comportamento do Novo Chat",
|
||||
"chatNewSessionModePrompt": "Perguntar o modelo a cada vez",
|
||||
"chatNewSessionModeAlwaysDefault": "Sempre usar o padrão configurado",
|
||||
"chatNewSessionModeHelp": "O modo de pergunta abre o Novo Chat com este padrão pré-selecionado. O modo sempre-padrão pula a caixa de diálogo quando o padrão configurado está completo.",
|
||||
"chatDefaultKind": "Destino padrão do chat",
|
||||
"chatDefaultKindModel": "Modelo",
|
||||
"chatDefaultKindAgent": "Agente",
|
||||
"chatDefaultModel": "Modelo padrão do chat",
|
||||
"selectChatDefaultModel": "Selecione um modelo padrão do chat",
|
||||
"chatDefaultModelHelp": "No modo Modelo, o Novo Chat usa o agente de chat integrado do Fusion com este par provedor/modelo. Deixe em branco para recorrer à pergunta.",
|
||||
"chatDefaultAgent": "Agente padrão do chat",
|
||||
"loadingAgents": "Carregando agentes…",
|
||||
"selectChatDefaultAgent": "Selecione um agente padrão do chat",
|
||||
"chatDefaultAgentEmpty": "Ainda não há agentes disponíveis para este projeto.",
|
||||
"chatDefaultAgentHelp": "No modo Agente, o Novo Chat inicia um chat direto com o agente durável selecionado.",
|
||||
"chatDefaultReset": "Redefinir padrão do Chat",
|
||||
"modelOverrides": "",
|
||||
"projectLanesSubheading": "",
|
||||
"workflowLanesSubheading": "",
|
||||
"summarizationPointer": ""
|
||||
},
|
||||
"globalModels": {
|
||||
"allow": "allow",
|
||||
"commaSeparatedValuesSentToOpenRouterModelSync": "Valores separados por vírgula enviados à sincronização de modelos do OpenRouter. Sem padrão — não definido (sem filtro).",
|
||||
"controlsHowMuchReasoningEffortTheAIModel": "Controla o quanto de esforço de raciocínio o modelo de IA usa. Níveis mais altos produzem melhores resultados, mas custam mais. Sem padrão — não definido (aplica-se o esforço padrão do próprio modelo).",
|
||||
"default": "Padrão",
|
||||
"default2": "default",
|
||||
"defaultAIModelUsedForTaskExecutionWhen": "Modelo de IA padrão usado na execução de tarefas quando nenhuma substituição por tarefa está definida. "Usar padrão" permite que o mecanismo escolha automaticamente. Sem padrão — não definido.",
|
||||
"defaultModel": "Modelo padrão",
|
||||
"deny": "deny",
|
||||
"fallbackModel": "Modelo de fallback",
|
||||
"flowAndPublishesThemUnderTheOpencodeGo": " e os publica sob o provedor opencode-go nos seletores de modelo. Padrão: ativado. ",
|
||||
"fusion": "Fusion",
|
||||
"globalBaselineModelsForEachAIRoleProject": " Modelos de referência globais para cada função de IA. As configurações do projeto podem substituí-los por projeto. ",
|
||||
"httpsRunfusionAi": "https://runfusion.ai",
|
||||
"latency": "latency",
|
||||
"leaveEmptyToOmitThisHeaderDefaultFusion": "Deixe em branco para omitir este cabeçalho. Sem padrão — não definido (o Fusion usa o título \"Fusion\" como fallback quando não definido).",
|
||||
"leaveEmptyToOmitThisHeaderDefaultHttps": "Deixe em branco para omitir este cabeçalho. Sem padrão — não definido (o Fusion usa https://runfusion.ai como fallback quando não definido).",
|
||||
"modelLanes": "Faixas de modelo",
|
||||
"noFallback": "Sem fallback",
|
||||
"openaiAnthropic": "openai, anthropic",
|
||||
"openRouterAdvanced": "OpenRouter avançado",
|
||||
"openRouterAllowFallbacks": "OpenRouter: permitir fallbacks",
|
||||
"openRouterHTTPReferer": "OpenRouter HTTP-Referer",
|
||||
"openRouterOutputModalitiesFilter": "Filtro output_modalities do OpenRouter",
|
||||
"openRouterRoutingIgnore": "OpenRouter: ignorar roteamento",
|
||||
"openRouterRoutingOnly": "OpenRouter: somente roteamento",
|
||||
"openRouterRoutingOrder": "OpenRouter: ordem de roteamento",
|
||||
"openRouterRoutingSort": "OpenRouter: classificação de roteamento",
|
||||
"openRouterSupportedParametersFilter": "Filtro supported_parameters do OpenRouter",
|
||||
"openRouterXTitle": "OpenRouter X-Title",
|
||||
"price": "price",
|
||||
"providerName": "provider-name",
|
||||
"requireParameters": " Exigir parâmetros ",
|
||||
"startupModelSync": "Sincronização de modelos na inicialização",
|
||||
"syncOpencodeGoModelListAtStartup": " Sincronizar lista de modelos do opencode-go na inicialização ",
|
||||
"syncOpenRouterModelListAtStartup": " Sincronizar lista de modelos do OpenRouter na inicialização ",
|
||||
"text": "text",
|
||||
"thinkingEffort": "Esforço de raciocínio",
|
||||
"throughput": "throughput",
|
||||
"toolsStructuredOutputs": "tools, structured_outputs",
|
||||
"usedAutomaticallyIfThePrimaryDefaultModelHits": "Usado automaticamente se o modelo padrão principal encontrar um erro do provedor passível de nova tentativa, como limitação de taxa ou sobrecarga. Sem padrão — não definido.",
|
||||
"useDefault": "Usar padrão",
|
||||
"whenEnabledStartupFetchesTheLatestAvailableModels": " Quando ativado, a inicialização busca os modelos mais recentes disponíveis na API do OpenRouter para que os seletores de modelo sempre incluam o catálogo mais atual. Padrão: ativado. ",
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": " Quando ativado, a inicialização atualiza os modelos por meio do fluxo local do ",
|
||||
"commaSeparatedValuesSentToOpenRouterModelSyncOutputModalities": "Valores separados por vírgula enviados à sincronização de modelos do OpenRouter. Sem padrão — não definido (sem filtro).",
|
||||
"openRouterRoutingOrderHint": "Sem padrão — não definido (aplica-se a ordem de roteamento padrão do próprio OpenRouter).",
|
||||
"openRouterRoutingIgnoreHint": "Sem padrão — não definido (nenhum provedor ignorado).",
|
||||
"openRouterRoutingOnlyHint": "Sem padrão — não definido (sem restrição de provedor).",
|
||||
"openRouterAllowFallbacksHint": "Sem padrão — não definido (aplica-se o comportamento padrão de fallback do próprio OpenRouter).",
|
||||
"openRouterRoutingSortHint": "Sem padrão — não definido (aplica-se a classificação padrão do próprio OpenRouter).",
|
||||
"requireParametersHint": "Padrão: desativado.",
|
||||
"modelOverrides": ""
|
||||
},
|
||||
"appearance": {
|
||||
"hideAISessionNotificationBanners": "Ocultar banners de notificação de sessão de IA",
|
||||
"language": "Idioma",
|
||||
"languageAuto": "Automático",
|
||||
"languageAutoHint": "Seguir o idioma do navegador",
|
||||
"suppressTheLdquoNeedsYourInputRdquoBanner": "Ocultar o banner “precisa da sua entrada” que aparece quando sessões de IA estão aguardando entrada ou falharam.",
|
||||
"title": "Aparência",
|
||||
"openTasksInRightSidebarHelp": "Quando ativado, os cartões de tarefa do quadro abrem os detalhes na barra lateral direita quando ela estiver disponível; os estados mobile e de barra lateral oculta mantêm o painel de tarefa completo. Padrão: desativado.",
|
||||
"openMobileTasksInPopupHelp": "Quando ativado, cliques comuns em cartões de tarefa do quadro e na lista de Tarefas do dock direito abrem o popup de tarefa existente, mantendo o quadro ou a lista visível. Aberturas via deep-tab e outras formas de abertura de tarefa mantêm o comportamento atual. Padrão: desativado.",
|
||||
"taskPopupsBoardListOnly": "Manter os popups de tarefa na visualização em que foram abertos",
|
||||
"taskPopupsBoardListOnlyHelp": "Quando ativado, cada popup de detalhes de tarefa aberto aparece somente na visualização em que foi aberto. Trocar de visualização o oculta sem fechá-lo; voltar o restaura na mesma posição. Padrão: ativado.",
|
||||
"taskDetailChatFirstHelp": "Desativado por padrão: os detalhes da tarefa listam Atividade primeiro, e aberturas de tarefas não concluídas sem aba especificada caem em Atividade. Ative para restaurar a ordem/padrão com Chat primeiro; links diretos para o Chat continuam funcionando de qualquer forma.",
|
||||
"showCostBadgeOnCards": "Mostrar selos de custo nos cartões de tarefa",
|
||||
"showCostBadgeOnCardsHelp": "Padrão: desativado. Quando ativado, os cartões do quadro mostram o custo do modelo derivado ao lado do tempo de execução; quando o preço não está disponível, isso é indicado — e tarefas sem uso de tokens não mostram selo."
|
||||
},
|
||||
"search": {
|
||||
"allSections": "Mostrando todas as seções de configurações",
|
||||
"clear": "Limpar busca de configurações",
|
||||
"label": "Buscar configurações",
|
||||
"moreResults": "{{count}} a mais — continue digitando para refinar",
|
||||
"navigationLabel": "Navegação de configurações",
|
||||
"noMobileOptions": "Nenhuma seção corresponde a esta busca.",
|
||||
"noResults": "Nenhuma seção de configurações corresponde a \"{{query}}\".",
|
||||
"placeholder": "Buscar por configuração ou seção",
|
||||
"resultCount_one": "{{count}} seção correspondente",
|
||||
"resultCount_other": "{{count}} seções correspondentes",
|
||||
"settingResultCount_one": "{{count}} configuração correspondente",
|
||||
"settingResultCount_other": "{{count}} configurações correspondentes"
|
||||
},
|
||||
"globalGeneral": {
|
||||
"andShowsUpdateNoticesInTheCLIAnd": " e mostra avisos de atualização na CLI e no painel. A frequência é definida pela opção abaixo. Padrão: ativado. ",
|
||||
"autoReloadDashboardOnVersionChange": " Recarregar o painel automaticamente quando a versão mudar ",
|
||||
"checkForThe": " Verificar o ",
|
||||
"checkForUpdatesAutomatically": " Verificar atualizações automaticamente ",
|
||||
"cLIBinaryOnPATH": " binário da CLI no PATH ",
|
||||
"cLIBySpawning": " CLI executando",
|
||||
"controlsHowOftenTheDashboardReFetchesThe": " Controla a frequência com que o painel busca novamente o registro do npm. Use o controle de versão + atualização no cabeçalho para acionar uma verificação imediata a qualquer momento. Padrão: diário. ",
|
||||
"dailyRecommended": "Diário (recomendado)",
|
||||
"disableThisIfYourLocalDevProcessIs": ". Desative esta opção se o seu processo de desenvolvimento local for a fonte da verdade e você não quiser que um binário desatualizado instalado globalmente seja executado durante a verificação. Padrão: ativado. ",
|
||||
"frequency": "Frequência",
|
||||
"general": "Geral",
|
||||
"globalDefaultTrackingRepo": "Repositório de rastreamento padrão global",
|
||||
"reportRoadmapDedupeEnabled": "Deduplicação global do roadmap público",
|
||||
"reportRoadmapDedupeEnabledHelp": "Serve de reserva para projetos que não definem uma preferência de deduplicação do roadmap público. Sem padrão — não definido (os projetos usam ativado por padrão).",
|
||||
"reportRoadmapLabel": "Rótulo global do roadmap público",
|
||||
"reportRoadmapLabelHelp": "Rótulo de reserva para issues abertas do roadmap público quando um projeto não define o seu. Sem padrão — não definido (os projetos usam roadmap por padrão).",
|
||||
"reportRoadmapRepo": "Repositório global do roadmap público (opcional)",
|
||||
"reportRoadmapRepoHelp": "owner/repositório do GitHub de reserva para issues do roadmap público. Se não definido, usa o repositório de rastreamento. Sem padrão — não definido.",
|
||||
"leaveBothThinkingTogglesOffToKeepThe": " Deixe as duas opções de pensamento desativadas para manter o comportamento padrão original. Isso controla apenas as ",
|
||||
"manualOnlyNeverAutoCheck": "Somente manual — nunca verificar automaticamente",
|
||||
"onStartupOncePerServerLaunch": "Na inicialização — uma vez por execução do servidor",
|
||||
"ownerRepo": "owner/repo",
|
||||
"projectsInheritThisValueWhenTheyDoNot": "Os projetos herdam este valor quando não definem um repositório de rastreamento padrão próprio. Sem padrão — não definido.",
|
||||
"rowsAndDoesNotAffectAssistantTextOr": " linhas persistidas e não afeta o texto do assistente nem as linhas de ferramentas. Padrão: desativado tanto para agentes permanentes quanto efêmeros. ",
|
||||
"saveAIThinkingForEphemeralTaskWorkerAgents": " Salvar o pensamento da IA para agentes efêmeros / de execução de tarefas ",
|
||||
"saveAIThinkingForPermanentAgents": " Salvar o pensamento da IA para agentes permanentes ",
|
||||
"saveAIThinkingLogs": "Salvar logs de pensamento da IA",
|
||||
"saveToolOutputInAgentLogs": " Salvar a saída das ferramentas nos logs do agente ",
|
||||
"agentToolOutputLimit": " Limite de saída das ferramentas do agente ",
|
||||
"agentToolOutputLimitHint": " Número máximo de caracteres retornados de cada resultado de ferramenta injetado pelo motor. Quando não definido, herda o padrão do motor de 16.000 caracteres. Deixe em branco para usar o padrão. ",
|
||||
"noLimitOnAgentToolOutput": " Sem limite para a saída das ferramentas do agente ",
|
||||
"noLimitOnAgentToolOutputHint": " Desativa o limitador compartilhado de saída das ferramentas. Um único resultado de ferramenta pode consumir toda a janela de contexto do agente. Padrão: desativado; quando não definido, o orçamento herda o padrão do motor de 16.000 caracteres. ",
|
||||
"enableProactiveTaskChat": " Ativar atualizações proativas no chat da tarefa ",
|
||||
"enableProactiveTaskChatHint": " Quando ativado, o chat da tarefa relata o progresso das etapas, falhas, revisões e reversões em tempo real. Padrão: desativado. ",
|
||||
"updates": "Atualizações",
|
||||
"weekly": "Semanal",
|
||||
"whenDisabledToolRowsAreStillLoggedBut": " Quando desativado, as linhas de ferramentas continuam sendo registradas, mas os payloads detalhados das ferramentas são omitidos. Payloads de ferramentas muito grandes ainda podem ser cortados mesmo quando isso permanece ativado. Padrão: desativado. ",
|
||||
"whenEnabledDefaultTheDashboardAutomaticallyReloadsWhen": " Quando ativado (padrão), o painel recarrega automaticamente ao detectar uma nova versão de build — seja por rebuilds do servidor ou atualizações do service worker. Desative isso para permanecer na versão atual até atualizar manualmente. Padrão: ativado. ",
|
||||
"whenEnabledFusionChecksNpmForNewVersions": " Quando ativado, o Fusion verifica o npm em busca de novas versões de",
|
||||
"whenEnabledTheDashboardProbesForAGlobally": " Quando ativado, o painel verifica se existe, instalado globalmente, um",
|
||||
"gitLabEnabledHint": "Os valores globais de URL e token do GitLab servem de reserva para projetos que não definem os próprios valores. Sem padrão — não definido (o estado não definido se comporta como ativado até ser explicitamente desativado).",
|
||||
"gitLabInstanceUrlHint": "Em branco, usa o padrão GitLab.com. Os projetos herdam esta URL de GitLab autogerenciado, a menos que definam seu próprio valor no projeto. Sem padrão — não definido.",
|
||||
"gitLabApiBaseUrlHint": "Em branco, deriva de <instance>/api/v4. Substitua apenas para gateways de API do GitLab autogerenciado que usem uma URL absoluta http:// ou https:// diferente. Sem padrão — não definido.",
|
||||
"gitLabTokenTypeHint": "Sem padrão — não definido (o seletor usa token de acesso pessoal como reserva até que você escolha outra opção).",
|
||||
"gitLabAuthTokenHint": "Os projetos herdam este valor de reserva apenas quando não definem um token do GitLab próprio. Operações somente leitura exigem read_api ou api; ações de escrita exigem api; tokens de projeto/grupo continuam limitados pela associação ao recurso. Sem padrão — não definido.",
|
||||
"dismissModalsByClickingOutsideHint": " Quando ativado, clicar ou tocar no fundo de um modal fecha o modal. Padrão: desativado, para evitar fechamentos acidentais. ",
|
||||
"skipConfirmationDialogs": " Pular caixas de confirmação para ações críticas ",
|
||||
"skipConfirmationDialogsHint": " Quando ativado, ações destrutivas como excluir uma tarefa ou redefinir o progresso são executadas imediatamente, sem confirmação. Padrão: desativado",
|
||||
"releaseChannel": "Canal de lançamento",
|
||||
"releaseChannelHelp": "Stable segue os lançamentos oficiais. Beta segue pré-lançamentos gerados a partir da main e também recebe cada lançamento stable assim que ele ultrapassa o beta. Voltar para Stable nunca faz downgrade. Padrão: stable.",
|
||||
"autoUpdateAndRestart": " Atualizar e reiniciar automaticamente ",
|
||||
"autoUpdateAndRestartHelp": "Quando ativado, o Fusion instala automaticamente as atualizações disponíveis no canal de lançamento selecionado e reinicia para aplicá-las. Requer um processo supervisor pai (o padrão para `fn dashboard`); hosts iniciados com --no-supervise pulam a instalação. Padrão: desativado.",
|
||||
"channelStable": "Stable (recomendado)",
|
||||
"channelBeta": "Beta — builds iniciais da main",
|
||||
"quickAddSubmitOnEnter": "",
|
||||
"quickAddSubmitOnEnterHint": ""
|
||||
},
|
||||
"notifications": {
|
||||
"accessTokenOptional": "Token de acesso (opcional)",
|
||||
"agentClarification": "Esclarecimento do agente",
|
||||
@@ -6780,7 +7284,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "Transforma notas diárias em DREAMS.md e promove lições reutilizáveis para o MEMORY.md. Padrão: desativado.",
|
||||
"qmdInstalled": "qmd instalado com sucesso",
|
||||
"qmdInstallFailed": "Falha ao instalar o qmd",
|
||||
"qmdInstallUnavailable": "A instalação do qmd foi concluída, mas o qmd ainda está indisponível"
|
||||
"qmdInstallUnavailable": "A instalação do qmd foi concluída, mas o qmd ainda está indisponível",
|
||||
"saveBeforeCompacting": "",
|
||||
"saveBeforeSwitching": ""
|
||||
},
|
||||
"backups": {
|
||||
"02": "0 2 * * *",
|
||||
@@ -7909,7 +8415,8 @@
|
||||
"header": {
|
||||
"back": "Voltar",
|
||||
"backToList": "Voltar para a lista de tarefas",
|
||||
"editTask": "Editar tarefa"
|
||||
"editTask": "Editar tarefa",
|
||||
"popOut": ""
|
||||
},
|
||||
"inputTokens": "Entrada",
|
||||
"lastUsed": "Último uso",
|
||||
@@ -8241,6 +8748,22 @@
|
||||
"retry": "Tentar criar tarefa novamente",
|
||||
"create": "Criar tarefa",
|
||||
"error": "Não foi possível criar a tarefa. Tente novamente."
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": ""
|
||||
},
|
||||
"revise": "",
|
||||
"specLock": {
|
||||
"accepted": "",
|
||||
"alignment": "",
|
||||
"alignmentLabel": "",
|
||||
"captured": "",
|
||||
"currentPlan": "",
|
||||
"findings": "",
|
||||
"latestLock": "",
|
||||
"lockState": "",
|
||||
"relockChanged": "",
|
||||
"retainedHistory": ""
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9379,7 +9902,11 @@
|
||||
"toolNames": "Nomes de ferramentas",
|
||||
"toolResult": "Resultado de ferramenta",
|
||||
"you": "Você",
|
||||
"youMessage": "Sua mensagem"
|
||||
"youMessage": "Sua mensagem",
|
||||
"missingOutput": "",
|
||||
"missingOutputTimestamp": "",
|
||||
"statusUpdate": "",
|
||||
"statusUpdateTimestamp": ""
|
||||
},
|
||||
"report": {
|
||||
"roadmapMatch": {
|
||||
@@ -9392,6 +9919,146 @@
|
||||
"targetLabel": "Destino de envio",
|
||||
"targetInherit": "Usar destino de ação configurado",
|
||||
"targetIssue": "Issue do GitHub",
|
||||
"targetDiscussion": "Discussão do GitHub"
|
||||
"targetDiscussion": "Discussão do GitHub",
|
||||
"activityTrace": "",
|
||||
"capturingScreenshot": "",
|
||||
"close": "",
|
||||
"confirmDataPoint": "",
|
||||
"confirmScreenshotRetention": "",
|
||||
"duplicateReview": "",
|
||||
"file": "",
|
||||
"final": "",
|
||||
"menu": "",
|
||||
"returnToPrompt": "",
|
||||
"review": "",
|
||||
"storeScreenshot": "",
|
||||
"structured": "",
|
||||
"structuredDataPoint": "",
|
||||
"suggestedHelp": "",
|
||||
"summary": "",
|
||||
"viewGitHub": ""
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "",
|
||||
"draft": "",
|
||||
"draftNarrative": "",
|
||||
"emptyDraft": "",
|
||||
"useDraft": ""
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "",
|
||||
"floatingTaskDetailBody": "",
|
||||
"genericFloatingWindow": "",
|
||||
"genericFloatingWindowBody": "",
|
||||
"genericWindowHeader": "",
|
||||
"headerAction": "",
|
||||
"headerlessTaskDetail": "",
|
||||
"taskDetail": "",
|
||||
"taskDetailBody": ""
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "",
|
||||
"resize": ""
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "",
|
||||
"gitlab": ""
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "",
|
||||
"converge": "",
|
||||
"convergedToMission": "",
|
||||
"description": "",
|
||||
"divergentCandidate": "",
|
||||
"noSessions": "",
|
||||
"persisted": "",
|
||||
"selectOrStart": "",
|
||||
"sessionTitle": "",
|
||||
"start": "",
|
||||
"title": ""
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "",
|
||||
"allOwners": "",
|
||||
"allSymbolKinds": "",
|
||||
"bothDirections": "",
|
||||
"depth": "",
|
||||
"distance": "",
|
||||
"edgeKinds": "",
|
||||
"findPath": "",
|
||||
"incoming": "",
|
||||
"limitResults": "",
|
||||
"neighbors": "",
|
||||
"next": "",
|
||||
"noNeighbors": "",
|
||||
"noPath": "",
|
||||
"nodeKinds": "",
|
||||
"none": "",
|
||||
"outgoing": "",
|
||||
"ownerDerived": "",
|
||||
"ownerFile": "",
|
||||
"pathLimitReached": "",
|
||||
"previous": "",
|
||||
"provenance": "",
|
||||
"raiseHopLimit": "",
|
||||
"refreshNeighbors": "",
|
||||
"searching": "",
|
||||
"selectNodeHint": "",
|
||||
"shortestPath": "",
|
||||
"showingOf": "",
|
||||
"useSelectedFrom": "",
|
||||
"useSelectedTo": ""
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": ""
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"openAria": "",
|
||||
"previewUnavailable": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "",
|
||||
"authorizationReceived": "",
|
||||
"cancel": "",
|
||||
"connected": "",
|
||||
"exchangingCode": "",
|
||||
"finishInBrowser": "",
|
||||
"handAuthorizationBack": "",
|
||||
"openSignInAgain": "",
|
||||
"pasteRedirectUrl": "",
|
||||
"signingInTo": ""
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "",
|
||||
"heading": "",
|
||||
"passed": "",
|
||||
"queued": "",
|
||||
"requestRejected": "",
|
||||
"running": ""
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "",
|
||||
"choosePairingMethod": "",
|
||||
"connectedAs": "",
|
||||
"connectedSuccess": "",
|
||||
"description": "",
|
||||
"installPlugin": "",
|
||||
"instructionsLabel": "",
|
||||
"loggingOut": "",
|
||||
"logoutRepair": "",
|
||||
"pairing": "",
|
||||
"pairingConfiguration": "",
|
||||
"phoneNumber": "",
|
||||
"qrCode": "",
|
||||
"refreshStatus": "",
|
||||
"repairInstructions": "",
|
||||
"requestPairingCode": "",
|
||||
"requestingCode": "",
|
||||
"status": "",
|
||||
"waitConnected": "",
|
||||
"waitingQr": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showMore": "显示更多",
|
||||
"update": "更新",
|
||||
"yes": "是",
|
||||
"clear": ""
|
||||
"clear": "",
|
||||
"open": "",
|
||||
"remove": ""
|
||||
},
|
||||
"activityFeed": {
|
||||
"emptyHint": "",
|
||||
@@ -1145,7 +1147,9 @@
|
||||
"disableHeartbeatsSummary": "",
|
||||
"enableHeartbeatsCountHint": "",
|
||||
"disableHeartbeatsCountHint": "",
|
||||
"bulkFailures": ""
|
||||
"bulkFailures": "",
|
||||
"detailLabel": "",
|
||||
"lastCompletedStep": ""
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1389,7 +1393,8 @@
|
||||
"currentAgentTarget": "Current agent: {{name}}",
|
||||
"currentModelTarget": "Current model: {{model}}",
|
||||
"currentDefaultTarget": "Using the default chat target",
|
||||
"newNotAllowedForTaskChat": ""
|
||||
"newNotAllowedForTaskChat": "",
|
||||
"restore": ""
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1928,7 +1933,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "",
|
||||
"verificationRequests": ""
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "添加评论",
|
||||
@@ -2006,7 +2013,20 @@
|
||||
"toLabel": "收件人:",
|
||||
"wakeAgentCheckbox": "立即唤醒代理",
|
||||
"wakeAlwaysImmediate": "(代理已设置为立即响应模式)",
|
||||
"wakeOneOff": "(仅对此消息的一次性覆盖)"
|
||||
"wakeOneOff": "(仅对此消息的一次性覆盖)",
|
||||
"addSection": "",
|
||||
"ariaLabel": "",
|
||||
"attachStructure": "",
|
||||
"mode": "",
|
||||
"noStructures": "",
|
||||
"quickMessage": "",
|
||||
"removeSection": "",
|
||||
"removeStructure": "",
|
||||
"report": "",
|
||||
"reportTitle": "",
|
||||
"sectionBody": "",
|
||||
"sectionHeading": "",
|
||||
"selectStructure": ""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "取消",
|
||||
@@ -3482,7 +3502,36 @@
|
||||
"typeSystem": "系统",
|
||||
"typeUserToAgent": "你 → 代理",
|
||||
"user": "用户",
|
||||
"you": "你"
|
||||
"you": "你",
|
||||
"all": "",
|
||||
"approvalStatus": "",
|
||||
"approvalUnavailable": "",
|
||||
"approve": "",
|
||||
"archive": "",
|
||||
"archived": "",
|
||||
"artifact": "",
|
||||
"audioArtifactAria": "",
|
||||
"createTask": "",
|
||||
"createTaskError": "",
|
||||
"creatingTask": "",
|
||||
"deny": "",
|
||||
"inboxFilter": "",
|
||||
"loadingApproval": "",
|
||||
"noArchivedMessages": "",
|
||||
"openArtifact": "",
|
||||
"openArtifactAria": "",
|
||||
"optionalComment": "",
|
||||
"recommendationsUnavailable": "",
|
||||
"reportsApprovals": "",
|
||||
"restore": "",
|
||||
"retryCreatingTask": "",
|
||||
"taskCreatedView": "",
|
||||
"taskProposalDismissed": "",
|
||||
"taskRecommendations": "",
|
||||
"videoArtifactAria": "",
|
||||
"viewTask": "",
|
||||
"viewTaskAria": "",
|
||||
"viewTaskLabel": ""
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "审计检查",
|
||||
@@ -5303,7 +5352,14 @@
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
"resizeExpandedView": "",
|
||||
"archivedCopy": "",
|
||||
"doneHiddenCopy": "",
|
||||
"emptyCopy": "",
|
||||
"hideDone": "",
|
||||
"noActiveTasks": "",
|
||||
"noTasksYet": "",
|
||||
"showDone": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
@@ -6176,7 +6232,8 @@
|
||||
"openRouterRoutingOnlyHint": "",
|
||||
"openRouterAllowFallbacksHint": "",
|
||||
"openRouterRoutingSortHint": "",
|
||||
"requireParametersHint": ""
|
||||
"requireParametersHint": "",
|
||||
"modelOverrides": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord",
|
||||
@@ -6273,7 +6330,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
"qmdInstallUnavailable": "",
|
||||
"saveBeforeCompacting": "",
|
||||
"saveBeforeSwitching": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6621,7 +6680,11 @@
|
||||
"executorEscalationModel": "",
|
||||
"executorEscalationModelHelp": "",
|
||||
"selectExecutorEscalationModel": "",
|
||||
"noExecutorEscalationModel": ""
|
||||
"noExecutorEscalationModel": "",
|
||||
"modelOverrides": "",
|
||||
"projectLanesSubheading": "",
|
||||
"workflowLanesSubheading": "",
|
||||
"summarizationPointer": ""
|
||||
},
|
||||
"remote": {
|
||||
"acceptRoutes": "",
|
||||
@@ -6936,7 +6999,11 @@
|
||||
"projectTitle": "Project MCP servers",
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"enabledHint": ""
|
||||
"enabledHint": "",
|
||||
"transport": "",
|
||||
"transportHttp": "",
|
||||
"transportSse": "",
|
||||
"transportStdio": ""
|
||||
},
|
||||
"reset": {
|
||||
"button": "Reset Settings",
|
||||
@@ -7870,7 +7937,8 @@
|
||||
"header": {
|
||||
"back": "返回",
|
||||
"backToList": "返回任务列表",
|
||||
"editTask": "编辑任务"
|
||||
"editTask": "编辑任务",
|
||||
"popOut": ""
|
||||
},
|
||||
"inputTokens": "输入",
|
||||
"lastUsed": "最后使用",
|
||||
@@ -8231,6 +8299,22 @@
|
||||
"retry": "",
|
||||
"create": "",
|
||||
"error": ""
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": ""
|
||||
},
|
||||
"revise": "",
|
||||
"specLock": {
|
||||
"accepted": "",
|
||||
"alignment": "",
|
||||
"alignmentLabel": "",
|
||||
"captured": "",
|
||||
"currentPlan": "",
|
||||
"findings": "",
|
||||
"latestLock": "",
|
||||
"lockState": "",
|
||||
"relockChanged": "",
|
||||
"retainedHistory": ""
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9369,7 +9453,11 @@
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
"youMessage": "",
|
||||
"missingOutput": "",
|
||||
"missingOutputTimestamp": "",
|
||||
"statusUpdate": "",
|
||||
"statusUpdateTimestamp": ""
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.",
|
||||
@@ -9392,6 +9480,146 @@
|
||||
"targetLabel": "提交目标",
|
||||
"targetInherit": "使用已配置的操作目标",
|
||||
"targetIssue": "GitHub 议题",
|
||||
"targetDiscussion": "GitHub 讨论"
|
||||
"targetDiscussion": "GitHub 讨论",
|
||||
"activityTrace": "",
|
||||
"capturingScreenshot": "",
|
||||
"close": "",
|
||||
"confirmDataPoint": "",
|
||||
"confirmScreenshotRetention": "",
|
||||
"duplicateReview": "",
|
||||
"file": "",
|
||||
"final": "",
|
||||
"menu": "",
|
||||
"returnToPrompt": "",
|
||||
"review": "",
|
||||
"storeScreenshot": "",
|
||||
"structured": "",
|
||||
"structuredDataPoint": "",
|
||||
"suggestedHelp": "",
|
||||
"summary": "",
|
||||
"viewGitHub": ""
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "",
|
||||
"draft": "",
|
||||
"draftNarrative": "",
|
||||
"emptyDraft": "",
|
||||
"useDraft": ""
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "",
|
||||
"floatingTaskDetailBody": "",
|
||||
"genericFloatingWindow": "",
|
||||
"genericFloatingWindowBody": "",
|
||||
"genericWindowHeader": "",
|
||||
"headerAction": "",
|
||||
"headerlessTaskDetail": "",
|
||||
"taskDetail": "",
|
||||
"taskDetailBody": ""
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "",
|
||||
"resize": ""
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "",
|
||||
"gitlab": ""
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "",
|
||||
"converge": "",
|
||||
"convergedToMission": "",
|
||||
"description": "",
|
||||
"divergentCandidate": "",
|
||||
"noSessions": "",
|
||||
"persisted": "",
|
||||
"selectOrStart": "",
|
||||
"sessionTitle": "",
|
||||
"start": "",
|
||||
"title": ""
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "",
|
||||
"allOwners": "",
|
||||
"allSymbolKinds": "",
|
||||
"bothDirections": "",
|
||||
"depth": "",
|
||||
"distance": "",
|
||||
"edgeKinds": "",
|
||||
"findPath": "",
|
||||
"incoming": "",
|
||||
"limitResults": "",
|
||||
"neighbors": "",
|
||||
"next": "",
|
||||
"noNeighbors": "",
|
||||
"noPath": "",
|
||||
"nodeKinds": "",
|
||||
"none": "",
|
||||
"outgoing": "",
|
||||
"ownerDerived": "",
|
||||
"ownerFile": "",
|
||||
"pathLimitReached": "",
|
||||
"previous": "",
|
||||
"provenance": "",
|
||||
"raiseHopLimit": "",
|
||||
"refreshNeighbors": "",
|
||||
"searching": "",
|
||||
"selectNodeHint": "",
|
||||
"shortestPath": "",
|
||||
"showingOf": "",
|
||||
"useSelectedFrom": "",
|
||||
"useSelectedTo": ""
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": ""
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"openAria": "",
|
||||
"previewUnavailable": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "",
|
||||
"authorizationReceived": "",
|
||||
"cancel": "",
|
||||
"connected": "",
|
||||
"exchangingCode": "",
|
||||
"finishInBrowser": "",
|
||||
"handAuthorizationBack": "",
|
||||
"openSignInAgain": "",
|
||||
"pasteRedirectUrl": "",
|
||||
"signingInTo": ""
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "",
|
||||
"heading": "",
|
||||
"passed": "",
|
||||
"queued": "",
|
||||
"requestRejected": "",
|
||||
"running": ""
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "",
|
||||
"choosePairingMethod": "",
|
||||
"connectedAs": "",
|
||||
"connectedSuccess": "",
|
||||
"description": "",
|
||||
"installPlugin": "",
|
||||
"instructionsLabel": "",
|
||||
"loggingOut": "",
|
||||
"logoutRepair": "",
|
||||
"pairing": "",
|
||||
"pairingConfiguration": "",
|
||||
"phoneNumber": "",
|
||||
"qrCode": "",
|
||||
"refreshStatus": "",
|
||||
"repairInstructions": "",
|
||||
"requestPairingCode": "",
|
||||
"requestingCode": "",
|
||||
"status": "",
|
||||
"waitConnected": "",
|
||||
"waitingQr": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"showMore": "顯示更多",
|
||||
"update": "更新",
|
||||
"yes": "是",
|
||||
"clear": ""
|
||||
"clear": "",
|
||||
"open": "",
|
||||
"remove": ""
|
||||
},
|
||||
"activityFeed": {
|
||||
"emptyHint": "",
|
||||
@@ -1145,7 +1147,9 @@
|
||||
"disableHeartbeatsSummary": "",
|
||||
"enableHeartbeatsCountHint": "",
|
||||
"disableHeartbeatsCountHint": "",
|
||||
"bulkFailures": ""
|
||||
"bulkFailures": "",
|
||||
"detailLabel": "",
|
||||
"lastCompletedStep": ""
|
||||
},
|
||||
"app": {
|
||||
"backendError": {
|
||||
@@ -1389,7 +1393,8 @@
|
||||
"currentAgentTarget": "Current agent: {{name}}",
|
||||
"currentModelTarget": "Current model: {{model}}",
|
||||
"currentDefaultTarget": "Using the default chat target",
|
||||
"newNotAllowedForTaskChat": ""
|
||||
"newNotAllowedForTaskChat": "",
|
||||
"restore": ""
|
||||
},
|
||||
"chatRooms": {
|
||||
"error": {
|
||||
@@ -1928,7 +1933,9 @@
|
||||
"workflow:gate-failed": "Workflow gate failed",
|
||||
"approval:requested": "Approval requested"
|
||||
}
|
||||
}
|
||||
},
|
||||
"taskVerification": "",
|
||||
"verificationRequests": ""
|
||||
},
|
||||
"comments": {
|
||||
"addButton": "新增評論",
|
||||
@@ -2006,7 +2013,20 @@
|
||||
"toLabel": "收件人:",
|
||||
"wakeAgentCheckbox": "立即喚醒代理",
|
||||
"wakeAlwaysImmediate": "(代理已設定為立即回應模式)",
|
||||
"wakeOneOff": "(僅此訊息的一次性覆蓋)"
|
||||
"wakeOneOff": "(僅此訊息的一次性覆蓋)",
|
||||
"addSection": "",
|
||||
"ariaLabel": "",
|
||||
"attachStructure": "",
|
||||
"mode": "",
|
||||
"noStructures": "",
|
||||
"quickMessage": "",
|
||||
"removeSection": "",
|
||||
"removeStructure": "",
|
||||
"report": "",
|
||||
"reportTitle": "",
|
||||
"sectionBody": "",
|
||||
"sectionHeading": "",
|
||||
"selectStructure": ""
|
||||
},
|
||||
"confirm": {
|
||||
"cancel": "取消",
|
||||
@@ -3482,7 +3502,36 @@
|
||||
"typeSystem": "系統",
|
||||
"typeUserToAgent": "你 → 代理",
|
||||
"user": "使用者",
|
||||
"you": "你"
|
||||
"you": "你",
|
||||
"all": "",
|
||||
"approvalStatus": "",
|
||||
"approvalUnavailable": "",
|
||||
"approve": "",
|
||||
"archive": "",
|
||||
"archived": "",
|
||||
"artifact": "",
|
||||
"audioArtifactAria": "",
|
||||
"createTask": "",
|
||||
"createTaskError": "",
|
||||
"creatingTask": "",
|
||||
"deny": "",
|
||||
"inboxFilter": "",
|
||||
"loadingApproval": "",
|
||||
"noArchivedMessages": "",
|
||||
"openArtifact": "",
|
||||
"openArtifactAria": "",
|
||||
"optionalComment": "",
|
||||
"recommendationsUnavailable": "",
|
||||
"reportsApprovals": "",
|
||||
"restore": "",
|
||||
"retryCreatingTask": "",
|
||||
"taskCreatedView": "",
|
||||
"taskProposalDismissed": "",
|
||||
"taskRecommendations": "",
|
||||
"videoArtifactAria": "",
|
||||
"viewTask": "",
|
||||
"viewTaskAria": "",
|
||||
"viewTaskLabel": ""
|
||||
},
|
||||
"memory": {
|
||||
"auditChecksTitle": "稽核檢查",
|
||||
@@ -5303,7 +5352,14 @@
|
||||
"resize": "",
|
||||
"viewExpanded": "",
|
||||
"views": "",
|
||||
"resizeExpandedView": ""
|
||||
"resizeExpandedView": "",
|
||||
"archivedCopy": "",
|
||||
"doneHiddenCopy": "",
|
||||
"emptyCopy": "",
|
||||
"hideDone": "",
|
||||
"noActiveTasks": "",
|
||||
"noTasksYet": "",
|
||||
"showDone": ""
|
||||
},
|
||||
"routine": {
|
||||
"andMore_one": "",
|
||||
@@ -6176,7 +6232,8 @@
|
||||
"openRouterRoutingOnlyHint": "",
|
||||
"openRouterAllowFallbacksHint": "",
|
||||
"openRouterRoutingSortHint": "",
|
||||
"requireParametersHint": ""
|
||||
"requireParametersHint": "",
|
||||
"modelOverrides": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord",
|
||||
@@ -6273,7 +6330,9 @@
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
"qmdInstallUnavailable": "",
|
||||
"saveBeforeCompacting": "",
|
||||
"saveBeforeSwitching": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6621,7 +6680,11 @@
|
||||
"executorEscalationModel": "",
|
||||
"executorEscalationModelHelp": "",
|
||||
"selectExecutorEscalationModel": "",
|
||||
"noExecutorEscalationModel": ""
|
||||
"noExecutorEscalationModel": "",
|
||||
"modelOverrides": "",
|
||||
"projectLanesSubheading": "",
|
||||
"workflowLanesSubheading": "",
|
||||
"summarizationPointer": ""
|
||||
},
|
||||
"remote": {
|
||||
"acceptRoutes": "",
|
||||
@@ -6936,7 +6999,11 @@
|
||||
"projectTitle": "Project MCP servers",
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"enabledHint": ""
|
||||
"enabledHint": "",
|
||||
"transport": "",
|
||||
"transportHttp": "",
|
||||
"transportSse": "",
|
||||
"transportStdio": ""
|
||||
},
|
||||
"search": {
|
||||
"allSections": "",
|
||||
@@ -7870,7 +7937,8 @@
|
||||
"header": {
|
||||
"back": "返回",
|
||||
"backToList": "返回任務清單",
|
||||
"editTask": "編輯任務"
|
||||
"editTask": "編輯任務",
|
||||
"popOut": ""
|
||||
},
|
||||
"inputTokens": "輸入",
|
||||
"lastUsed": "最後使用",
|
||||
@@ -8231,6 +8299,22 @@
|
||||
"retry": "",
|
||||
"create": "",
|
||||
"error": ""
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": ""
|
||||
},
|
||||
"revise": "",
|
||||
"specLock": {
|
||||
"accepted": "",
|
||||
"alignment": "",
|
||||
"alignmentLabel": "",
|
||||
"captured": "",
|
||||
"currentPlan": "",
|
||||
"findings": "",
|
||||
"latestLock": "",
|
||||
"lockState": "",
|
||||
"relockChanged": "",
|
||||
"retainedHistory": ""
|
||||
}
|
||||
},
|
||||
"taskDocuments": {
|
||||
@@ -9369,7 +9453,11 @@
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
"youMessage": "",
|
||||
"missingOutput": "",
|
||||
"missingOutputTimestamp": "",
|
||||
"statusUpdate": "",
|
||||
"statusUpdateTimestamp": ""
|
||||
},
|
||||
"engineBanner": {
|
||||
"body": "The engine for this project is not running, so task automation and live updates may be paused. Start it now to reconnect the dashboard.",
|
||||
@@ -9392,6 +9480,146 @@
|
||||
"targetLabel": "提交目標",
|
||||
"targetInherit": "使用設定的操作目標",
|
||||
"targetIssue": "GitHub 議題",
|
||||
"targetDiscussion": "GitHub 討論"
|
||||
"targetDiscussion": "GitHub 討論",
|
||||
"activityTrace": "",
|
||||
"capturingScreenshot": "",
|
||||
"close": "",
|
||||
"confirmDataPoint": "",
|
||||
"confirmScreenshotRetention": "",
|
||||
"duplicateReview": "",
|
||||
"file": "",
|
||||
"final": "",
|
||||
"menu": "",
|
||||
"returnToPrompt": "",
|
||||
"review": "",
|
||||
"storeScreenshot": "",
|
||||
"structured": "",
|
||||
"structuredDataPoint": "",
|
||||
"suggestedHelp": "",
|
||||
"summary": "",
|
||||
"viewGitHub": ""
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "",
|
||||
"draft": "",
|
||||
"draftNarrative": "",
|
||||
"emptyDraft": "",
|
||||
"useDraft": ""
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "",
|
||||
"floatingTaskDetailBody": "",
|
||||
"genericFloatingWindow": "",
|
||||
"genericFloatingWindowBody": "",
|
||||
"genericWindowHeader": "",
|
||||
"headerAction": "",
|
||||
"headerlessTaskDetail": "",
|
||||
"taskDetail": "",
|
||||
"taskDetailBody": ""
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "",
|
||||
"resize": ""
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "",
|
||||
"gitlab": ""
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "",
|
||||
"converge": "",
|
||||
"convergedToMission": "",
|
||||
"description": "",
|
||||
"divergentCandidate": "",
|
||||
"noSessions": "",
|
||||
"persisted": "",
|
||||
"selectOrStart": "",
|
||||
"sessionTitle": "",
|
||||
"start": "",
|
||||
"title": ""
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "",
|
||||
"allOwners": "",
|
||||
"allSymbolKinds": "",
|
||||
"bothDirections": "",
|
||||
"depth": "",
|
||||
"distance": "",
|
||||
"edgeKinds": "",
|
||||
"findPath": "",
|
||||
"incoming": "",
|
||||
"limitResults": "",
|
||||
"neighbors": "",
|
||||
"next": "",
|
||||
"noNeighbors": "",
|
||||
"noPath": "",
|
||||
"nodeKinds": "",
|
||||
"none": "",
|
||||
"outgoing": "",
|
||||
"ownerDerived": "",
|
||||
"ownerFile": "",
|
||||
"pathLimitReached": "",
|
||||
"previous": "",
|
||||
"provenance": "",
|
||||
"raiseHopLimit": "",
|
||||
"refreshNeighbors": "",
|
||||
"searching": "",
|
||||
"selectNodeHint": "",
|
||||
"shortestPath": "",
|
||||
"showingOf": "",
|
||||
"useSelectedFrom": "",
|
||||
"useSelectedTo": ""
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": ""
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"openAria": "",
|
||||
"previewUnavailable": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "",
|
||||
"authorizationReceived": "",
|
||||
"cancel": "",
|
||||
"connected": "",
|
||||
"exchangingCode": "",
|
||||
"finishInBrowser": "",
|
||||
"handAuthorizationBack": "",
|
||||
"openSignInAgain": "",
|
||||
"pasteRedirectUrl": "",
|
||||
"signingInTo": ""
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "",
|
||||
"heading": "",
|
||||
"passed": "",
|
||||
"queued": "",
|
||||
"requestRejected": "",
|
||||
"running": ""
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "",
|
||||
"choosePairingMethod": "",
|
||||
"connectedAs": "",
|
||||
"connectedSuccess": "",
|
||||
"description": "",
|
||||
"installPlugin": "",
|
||||
"instructionsLabel": "",
|
||||
"loggingOut": "",
|
||||
"logoutRepair": "",
|
||||
"pairing": "",
|
||||
"pairingConfiguration": "",
|
||||
"phoneNumber": "",
|
||||
"qrCode": "",
|
||||
"refreshStatus": "",
|
||||
"repairInstructions": "",
|
||||
"requestPairingCode": "",
|
||||
"requestingCode": "",
|
||||
"status": "",
|
||||
"waitConnected": "",
|
||||
"waitingQr": ""
|
||||
}
|
||||
}
|
||||
|
||||
31
packages/i18n/src/__tests__/i18n-lint-baseline.test.ts
Normal file
31
packages/i18n/src/__tests__/i18n-lint-baseline.test.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { runLinter } from "i18next-cli";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import config from "../../../../i18next.config.ts";
|
||||
|
||||
const repoRoot = fileURLToPath(new URL("../../../..", import.meta.url));
|
||||
|
||||
/*
|
||||
FNXC:i18n-LintBaseline 2026-08-18-19:26:
|
||||
Run the installed linter API against the production root configuration so this regression covers the same shipping inputs as pnpm i18n:lint without replacing static analysis with a source-text count or a shell command.
|
||||
Protocol identifiers remain data in the production components; only rendered labels belong in the app catalog.
|
||||
*/
|
||||
describe("production i18n lint baseline", () => {
|
||||
it("keeps the configured shipping inputs free of hardcoded copy", async () => {
|
||||
// Vitest package commands run from packages/i18n; the root config's relative
|
||||
// globs must be evaluated from the repository root just like pnpm i18n:lint.
|
||||
const previousCwd = process.cwd();
|
||||
process.chdir(repoRoot);
|
||||
let result: Awaited<ReturnType<typeof runLinter>>;
|
||||
try {
|
||||
result = await runLinter(config);
|
||||
} finally {
|
||||
process.chdir(previousCwd);
|
||||
}
|
||||
const issueReport = Object.entries(result.files)
|
||||
.map(([file, issues]) => `${file}\n${issues.map((issue) => ` ${issue.line}: ${issue.text}`).join("\n")}`)
|
||||
.join("\n");
|
||||
|
||||
expect(result.success, `${result.message}\n${issueReport}`).toBe(true);
|
||||
});
|
||||
});
|
||||
229
packages/i18n/src/resources.d.ts
vendored
229
packages/i18n/src/resources.d.ts
vendored
@@ -20,10 +20,12 @@ export default interface Resources {
|
||||
"done": "Done",
|
||||
"edit": "Edit",
|
||||
"no": "No",
|
||||
"open": "Open",
|
||||
"openSettings": "Open Settings",
|
||||
"pull": "Pull",
|
||||
"refresh": "Refresh",
|
||||
"refreshInsights": "Refresh insights",
|
||||
"remove": "Remove",
|
||||
"retry": "Retry",
|
||||
"run": "Run",
|
||||
"save": "Save",
|
||||
@@ -443,6 +445,7 @@ export default interface Resources {
|
||||
"deleted": "Agent deleted",
|
||||
"deletionNotAvailable": "Deletion is not available while the agent is running.",
|
||||
"deletionPermanent": "This will permanently delete the agent and all associated data.",
|
||||
"detailLabel": "Agent detail",
|
||||
"detailLoadingLabel": "Agent detail loading",
|
||||
"details": "Details",
|
||||
"dialogAriaLabel": "Create new agent",
|
||||
@@ -623,6 +626,7 @@ export default interface Resources {
|
||||
"kTokens_other": "{{count}}k tokens",
|
||||
"last24h": "Last 24h",
|
||||
"last7d": "Last 7 days",
|
||||
"lastCompletedStep": "Last completed Step {{index}}: {{name}}",
|
||||
"lastHeartbeat": "Last heartbeat",
|
||||
"lastHeartbeatAt": "Last: {{time}}",
|
||||
"latestRunLabel": "Latest run",
|
||||
@@ -1351,6 +1355,7 @@ export default interface Resources {
|
||||
"resizeSidebar": "Resize chat sidebar",
|
||||
"responseCopied": "Response copied",
|
||||
"responseFailed": "Response failed",
|
||||
"restore": "Restore",
|
||||
"roomMemberCount_one": "{{count}} member",
|
||||
"roomMemberCount_other": "{{count}} members",
|
||||
"roomsGroupLabel": "Rooms",
|
||||
@@ -1854,6 +1859,7 @@ export default interface Resources {
|
||||
"tools": "Tools",
|
||||
"workflows": "Workflows"
|
||||
},
|
||||
"taskVerification": "Task verification",
|
||||
"team": {
|
||||
"agent": "Agent",
|
||||
"completedByAgent": "Tasks done by agent",
|
||||
@@ -1911,6 +1917,7 @@ export default interface Resources {
|
||||
"summaryTitle": "Summary",
|
||||
"toolCalls": "Tool calls"
|
||||
},
|
||||
"verificationRequests": "Latest executor-owned verification requests",
|
||||
"workflows": {
|
||||
"completedByWorkflow": "Tasks done by workflow",
|
||||
"cost": "Cost",
|
||||
@@ -1994,16 +2001,36 @@ export default interface Resources {
|
||||
"unsavedChanges": "Unsaved changes",
|
||||
"yes": "Yes"
|
||||
},
|
||||
"composeChat": {
|
||||
"ariaLabel": "Compose chat narrative helper",
|
||||
"draft": "Draft",
|
||||
"draftNarrative": "Draft narrative",
|
||||
"emptyDraft": "Ask the assistant to draft the narrative around your attached structures.",
|
||||
"useDraft": "Use draft"
|
||||
},
|
||||
"composer": {
|
||||
"addSection": "Add section",
|
||||
"ariaLabel": "Message composer; drop a structure to attach it",
|
||||
"attachStructure": "Attach structure",
|
||||
"loadingAgents": "Loading agents…",
|
||||
"messageId": "Message {{id}}",
|
||||
"messageLabel": "Message:",
|
||||
"messagePlaceholder": "Type your message…",
|
||||
"mode": "Message mode",
|
||||
"newMessageTitle": "New Message",
|
||||
"noAgentsAvailable": "No agents available",
|
||||
"noStructures": "No structures available",
|
||||
"quickMessage": "Quick message",
|
||||
"removeSection": "Remove section",
|
||||
"removeStructure": "Remove {{label}}",
|
||||
"replyTitle": "Reply",
|
||||
"replyingToLabel": "Replying to:",
|
||||
"report": "Report",
|
||||
"reportTitle": "Report title",
|
||||
"sectionBody": "Section body",
|
||||
"sectionHeading": "Section heading",
|
||||
"selectAgent": "Select agent…",
|
||||
"selectStructure": "Select structure…",
|
||||
"sendingButton": "Sending…",
|
||||
"toLabel": "To:",
|
||||
"wakeAgentCheckbox": "Wake agent immediately",
|
||||
@@ -2539,6 +2566,21 @@ export default interface Resources {
|
||||
"taskHeader": "Tasks",
|
||||
"taskMatches": "Task matches"
|
||||
},
|
||||
"fixture": {
|
||||
"floatingTaskDetail": "Floating task detail",
|
||||
"floatingTaskDetailBody": "Floating task detail body",
|
||||
"genericFloatingWindow": "Generic floating window",
|
||||
"genericFloatingWindowBody": "Generic floating window body",
|
||||
"genericWindowHeader": "Generic window header",
|
||||
"headerAction": "Header action",
|
||||
"headerlessTaskDetail": "Headerless task detail",
|
||||
"taskDetail": "Task detail",
|
||||
"taskDetailBody": "Task detail body"
|
||||
},
|
||||
"floatingWindow": {
|
||||
"close": "Close floating window",
|
||||
"resize": "Resize floating window"
|
||||
},
|
||||
"git": {
|
||||
"add": "Add",
|
||||
"addComment": "Add comment",
|
||||
@@ -2945,6 +2987,10 @@ export default interface Resources {
|
||||
"worktreesTotal_one": "{{count}} total",
|
||||
"worktreesTotal_other": "{{count}} total"
|
||||
},
|
||||
"githubImport": {
|
||||
"github": "GitHub",
|
||||
"gitlab": "GitLab"
|
||||
},
|
||||
"githubStarPrompt": {
|
||||
"body": "If Fusion has saved you time, a GitHub star goes a long way. It helps other developers discover the project and keeps the team motivated to ship improvements.",
|
||||
"dismissLabel": "Dismiss GitHub star prompt",
|
||||
@@ -3139,6 +3185,19 @@ export default interface Resources {
|
||||
"yoloHelp": "Required for non-interactive sessions that trigger shell-style tools.",
|
||||
"yoloLabel": "Auto-approve dangerous tool calls ({{flag}})"
|
||||
},
|
||||
"ideation": {
|
||||
"addCandidate": "Add candidate",
|
||||
"converge": "Converge",
|
||||
"convergedToMission": "Converged to Mission",
|
||||
"description": "Capture alternatives, then converge one into the Mission hierarchy.",
|
||||
"divergentCandidate": "Divergent candidate",
|
||||
"noSessions": "No sessions yet.",
|
||||
"persisted": "Persisted ideation",
|
||||
"selectOrStart": "Select or start a session.",
|
||||
"sessionTitle": "Session title",
|
||||
"start": "Start session",
|
||||
"title": "Ideation"
|
||||
},
|
||||
"inline": {
|
||||
"agent": "Agent",
|
||||
"breakDownSubtasks": "Break down into AI-generated subtasks",
|
||||
@@ -3292,6 +3351,38 @@ export default interface Resources {
|
||||
"issues": {
|
||||
"noIssuesFound": "No issues found"
|
||||
},
|
||||
"knowledgeGraph": {
|
||||
"allFnxcAreas": "All FNXC areas",
|
||||
"allOwners": "All owners",
|
||||
"allSymbolKinds": "All symbol kinds",
|
||||
"bothDirections": "Both directions",
|
||||
"depth": "Depth {{depth}}",
|
||||
"distance": "distance {{distance}}",
|
||||
"edgeKinds": "Edge kinds",
|
||||
"findPath": "Find path",
|
||||
"incoming": "Incoming",
|
||||
"limitResults": "{{limit}} results",
|
||||
"neighbors": "Neighbors",
|
||||
"next": "Next",
|
||||
"noNeighbors": "No neighbors found.",
|
||||
"noPath": "No path exists between these nodes.",
|
||||
"nodeKinds": "Node kinds",
|
||||
"none": "None",
|
||||
"outgoing": "Outgoing",
|
||||
"ownerDerived": "derived",
|
||||
"ownerFile": "file",
|
||||
"pathLimitReached": "No path found within {{maxHops}} hops; {{limit}} was reached.",
|
||||
"previous": "Previous",
|
||||
"provenance": "Provenance",
|
||||
"raiseHopLimit": "Raise hop limit",
|
||||
"refreshNeighbors": "Refresh neighbors",
|
||||
"searching": "Searching…",
|
||||
"selectNodeHint": "Select a node to inspect its edges.",
|
||||
"shortestPath": "Shortest path",
|
||||
"showingOf": "showing {{shown}} of {{total}}",
|
||||
"useSelectedFrom": "Use selected as from",
|
||||
"useSelectedTo": "Use selected as to"
|
||||
},
|
||||
"lane": {
|
||||
"collapse": "Collapse {{name}} lane",
|
||||
"expand": "Expand {{name}} lane"
|
||||
@@ -3412,6 +3503,7 @@ export default interface Resources {
|
||||
"agents": "Agents",
|
||||
"agentsTab": "Agents",
|
||||
"ago": "ago",
|
||||
"all": "All",
|
||||
"allAgents": "All agents",
|
||||
"allAgentsOption": "All agents",
|
||||
"approvalApprove": "Approve",
|
||||
@@ -3419,8 +3511,15 @@ export default interface Resources {
|
||||
"approvalDeny": "Deny",
|
||||
"approvalRequested": "Requested",
|
||||
"approvalRequester": "Requester",
|
||||
"approvalStatus": "Approval {{status}}",
|
||||
"approvalTask": "Task",
|
||||
"approvalUnavailable": "This approval request is no longer available.",
|
||||
"approvals": "Approvals",
|
||||
"approve": "Approve",
|
||||
"archive": "Archive",
|
||||
"archived": "Archived",
|
||||
"artifact": "artifact",
|
||||
"audioArtifactAria": "Audio artifact: {{label}}",
|
||||
"back": "Back",
|
||||
"backButton": "← Back",
|
||||
"closeAriaLabel": "Close",
|
||||
@@ -3431,14 +3530,19 @@ export default interface Resources {
|
||||
"composeTitle": "Compose message",
|
||||
"conversation": "Conversation",
|
||||
"conversationLabel": "Conversation",
|
||||
"createTask": "Create task",
|
||||
"createTaskError": "Could not create task. Try again.",
|
||||
"creatingTask": "Creating task…",
|
||||
"delete": "Delete",
|
||||
"deleteButton": "Delete",
|
||||
"deleteFailed": "Failed to delete message",
|
||||
"deny": "Deny",
|
||||
"from": "From",
|
||||
"fromLabel": "From:",
|
||||
"fromPrefix": "From: {{participant}}",
|
||||
"history": "History",
|
||||
"inbox": "Inbox",
|
||||
"inboxFilter": "Inbox filter",
|
||||
"inboxTab": "Inbox",
|
||||
"justNow": "Just now",
|
||||
"labelAgent": "Agent: {{id}}",
|
||||
@@ -3446,6 +3550,7 @@ export default interface Resources {
|
||||
"labelSystem": "System",
|
||||
"labelUser": "User: {{id}}",
|
||||
"labelYou": "You",
|
||||
"loadingApproval": "Loading approval…",
|
||||
"markAllRead": "Mark all read",
|
||||
"markAllReadButton": "Mark all read",
|
||||
"markAllReadTitle": "Mark all as read",
|
||||
@@ -3457,6 +3562,7 @@ export default interface Resources {
|
||||
"noAgentMessages": "No agent-to-agent messages",
|
||||
"noAgents": "No agents found",
|
||||
"noAgentsFound": "No agents found",
|
||||
"noArchivedMessages": "No archived messages",
|
||||
"noHistoricalApprovals": "No historical approvals",
|
||||
"noInbox": "No messages in your inbox",
|
||||
"noMessagesInbox": "No messages in your inbox",
|
||||
@@ -3465,18 +3571,28 @@ export default interface Resources {
|
||||
"noReceivedMessages": "No received messages for this agent",
|
||||
"noSentMessages": "No sent messages",
|
||||
"noSentMessagesAgent": "No sent messages for this agent",
|
||||
"openArtifact": "Open artifact",
|
||||
"openArtifactAria": "Open artifact: {{label}}",
|
||||
"optionalComment": "Optional comment",
|
||||
"outbox": "Outbox",
|
||||
"outboxTab": "Outbox",
|
||||
"pending": "Pending",
|
||||
"recommendationsUnavailable": "Recommendations are no longer available.",
|
||||
"refreshTitle": "Refresh",
|
||||
"reply": "Reply",
|
||||
"replyButton": "Reply",
|
||||
"replyLoadFailed": "Failed to load replied message. Click to retry.",
|
||||
"replyingTo": "Replying to",
|
||||
"replyingToMessage": "Replying to message",
|
||||
"reportsApprovals": "Reports & approvals",
|
||||
"resizeMessageListPane": "Resize message list pane",
|
||||
"restore": "Restore",
|
||||
"retryCreatingTask": "Retry creating task",
|
||||
"selectMessageToRead": "Select a message to read",
|
||||
"system": "System",
|
||||
"taskCreatedView": "Task {{id}} created — View task",
|
||||
"taskProposalDismissed": "Task proposal dismissed",
|
||||
"taskRecommendations": "Task recommendations",
|
||||
"timeDaysAgo_one": "{{count}}d ago",
|
||||
"timeDaysAgo_other": "{{count}}d ago",
|
||||
"timeHoursAgo_one": "{{count}}h ago",
|
||||
@@ -3494,6 +3610,10 @@ export default interface Resources {
|
||||
"typeSystem": "System",
|
||||
"typeUserToAgent": "You → Agent",
|
||||
"user": "User",
|
||||
"videoArtifactAria": "Video artifact: {{label}}",
|
||||
"viewTask": "View task {{id}}",
|
||||
"viewTaskAria": "View task: {{id}}",
|
||||
"viewTaskLabel": "View task",
|
||||
"you": "You"
|
||||
},
|
||||
"memory": {
|
||||
@@ -3665,6 +3785,9 @@ export default interface Resources {
|
||||
"status": "Status",
|
||||
"title": "Merge Details"
|
||||
},
|
||||
"mermaid": {
|
||||
"diagram": "Mermaid diagram"
|
||||
},
|
||||
"mesh": {
|
||||
"ariaLabel": "Node mesh topology visualization",
|
||||
"connecting": "Connecting",
|
||||
@@ -4116,6 +4239,13 @@ export default interface Resources {
|
||||
},
|
||||
"useDefault": "Use default"
|
||||
},
|
||||
"nativeStructure": {
|
||||
"loadFailed": "Could not load this {{kind}}.",
|
||||
"loading": "Loading {{kind}}",
|
||||
"openAria": "Open {{kind}}: {{title}}",
|
||||
"previewUnavailable": "Preview unavailable",
|
||||
"unavailable": "This structure is unavailable."
|
||||
},
|
||||
"nav": {
|
||||
"activityLog": "Activity Log",
|
||||
"agents": "Agents",
|
||||
@@ -5081,6 +5211,18 @@ export default interface Resources {
|
||||
"title": "Projects",
|
||||
"totalLabel": "Total"
|
||||
},
|
||||
"providerLogin": {
|
||||
"approveInBrowser": "Approve the sign-in in your browser",
|
||||
"authorizationReceived": "Authorization received.",
|
||||
"cancel": "Cancel login",
|
||||
"connected": "Connected.",
|
||||
"exchangingCode": "Exchanging the authorization code…",
|
||||
"finishInBrowser": "A tab should have opened. Finish signing in there — this dialog stays put.",
|
||||
"handAuthorizationBack": "Hand the authorization back to Fusion",
|
||||
"openSignInAgain": "Open the sign-in page again",
|
||||
"pasteRedirectUrl": "Usually automatic. If your browser lands on an error page, paste that page's full URL below.",
|
||||
"signingInTo": "Signing in to {{provider}}"
|
||||
},
|
||||
"providers": {
|
||||
"actions": {
|
||||
"addModel": "+ Add model",
|
||||
@@ -5245,6 +5387,17 @@ export default interface Resources {
|
||||
"window": "Window: {{start}} → {{end}}"
|
||||
},
|
||||
"report": {
|
||||
"activityTrace": "Activity trace to send",
|
||||
"capturingScreenshot": "Capturing and storing locally…",
|
||||
"close": "Close report",
|
||||
"confirmDataPoint": "Confirm and add data point",
|
||||
"confirmScreenshotRetention": "I confirm Fusion may retain this screenshot locally for this report.",
|
||||
"duplicateReview": "Review data point for a similar open issue",
|
||||
"file": "File report",
|
||||
"final": "Final report",
|
||||
"menu": "Report",
|
||||
"returnToPrompt": "Return to prompt",
|
||||
"review": "Review your report",
|
||||
"roadmapDuplicate": {
|
||||
"title": "Already on the roadmap — add your data point?"
|
||||
},
|
||||
@@ -5252,10 +5405,16 @@ export default interface Resources {
|
||||
"message": "This report matches a feature that is already planned.",
|
||||
"title": "Already on the roadmap"
|
||||
},
|
||||
"storeScreenshot": "Store a screenshot locally",
|
||||
"structured": "Structured report",
|
||||
"structuredDataPoint": "Structured data point",
|
||||
"suggestedHelp": "Suggested help",
|
||||
"summary": "Report summary",
|
||||
"targetDiscussion": "GitHub Discussion",
|
||||
"targetInherit": "Use configured action target",
|
||||
"targetIssue": "GitHub Issue",
|
||||
"targetLabel": "Filing target"
|
||||
"targetLabel": "Filing target",
|
||||
"viewGitHub": "View on GitHub"
|
||||
},
|
||||
"research": {
|
||||
"actionFailed": "Action failed",
|
||||
@@ -5320,13 +5479,20 @@ export default interface Resources {
|
||||
"viewLabel": "Research view"
|
||||
},
|
||||
"rightDock": {
|
||||
"archivedCopy": "Archived tasks stay out of this compact sidebar. Active tasks will appear here when work is available.",
|
||||
"closeExpandedView": "Close expanded right dock view",
|
||||
"collapse": "Collapse right dock",
|
||||
"doneHiddenCopy": "Completed tasks are hidden until you choose Show Done. Archived tasks stay out of this compact sidebar.",
|
||||
"emptyCopy": "Tasks you create or import will appear here for quick right-sidebar review.",
|
||||
"expand": "Expand right dock",
|
||||
"expandView": "Expand {{label}}",
|
||||
"hideDone": "Hide Done",
|
||||
"label": "Right dock",
|
||||
"noActiveTasks": "No active tasks",
|
||||
"noTasksYet": "No tasks yet",
|
||||
"resize": "Resize right dock",
|
||||
"resizeExpandedView": "Resize expanded right dock window",
|
||||
"showDone": "Show Done",
|
||||
"viewExpanded": "{{label}} expanded",
|
||||
"views": "Right dock views"
|
||||
},
|
||||
@@ -6280,7 +6446,11 @@ export default interface Resources {
|
||||
"globalDescription": "Configure MCP servers shared by all projects. Project settings may override or disable these servers by name.",
|
||||
"globalTitle": "Global MCP servers",
|
||||
"projectDescription": "Configure project-specific MCP servers, overrides, and disabled inherited servers.",
|
||||
"projectTitle": "Project MCP servers"
|
||||
"projectTitle": "Project MCP servers",
|
||||
"transport": "Transport",
|
||||
"transportHttp": "HTTP",
|
||||
"transportSse": "SSE",
|
||||
"transportStdio": "stdio"
|
||||
},
|
||||
"memory": {
|
||||
"03": "0 3 * * *",
|
||||
@@ -6324,6 +6494,8 @@ export default interface Resources {
|
||||
"qmdIsNotInstalledSearchWillUseLocal": " qmd is not installed. Search will use local files. Install indexed retrieval: ",
|
||||
"result": " result",
|
||||
"runsTheSameQmdBackedMemorySearchPath": "Runs the same qmd-backed memory_search path agents use.",
|
||||
"saveBeforeCompacting": "Save or discard edits before compacting this file.",
|
||||
"saveBeforeSwitching": "Save or discard the current edits before switching files.",
|
||||
"saveMemory": "Save Memory",
|
||||
"scheduleCron": "Schedule (cron)",
|
||||
"searchMemoryWithQmd": "Search memory with qmd",
|
||||
@@ -7722,6 +7894,8 @@ export default interface Resources {
|
||||
"loadingEarlierMessages": "Loading earlier messages…",
|
||||
"message": "Message",
|
||||
"messageActiveAgentSession": "Message active agent session",
|
||||
"missingOutput": "Missing output",
|
||||
"missingOutputTimestamp": "Missing output timestamp",
|
||||
"moreTools_one": ", +{{count}} more",
|
||||
"moreTools_other": ", +{{count}} more",
|
||||
"result": "Result",
|
||||
@@ -7733,6 +7907,8 @@ export default interface Resources {
|
||||
"reviewer": "Reviewer"
|
||||
},
|
||||
"sending": "Sending",
|
||||
"statusUpdate": "Status update",
|
||||
"statusUpdateTimestamp": "Status update timestamp",
|
||||
"thinking": "Thinking",
|
||||
"toolCall": "Tool call",
|
||||
"toolCallCount_one": "{{count}} tool call",
|
||||
@@ -7983,7 +8159,8 @@ export default interface Resources {
|
||||
"header": {
|
||||
"back": "Back",
|
||||
"backToList": "Back to task list",
|
||||
"editTask": "Edit task"
|
||||
"editTask": "Edit task",
|
||||
"popOut": "Pop out"
|
||||
},
|
||||
"inputTokens": "Input",
|
||||
"lastUsed": "Last used",
|
||||
@@ -8179,6 +8356,10 @@ export default interface Resources {
|
||||
"btn": "Retry",
|
||||
"retried": "Retried {{id}}"
|
||||
},
|
||||
"reverted": {
|
||||
"deleteAria": "Delete reverted task"
|
||||
},
|
||||
"revise": "Revise",
|
||||
"runtimeLinks": "Runtime links",
|
||||
"runtimeStatus": "Runtime status",
|
||||
"selfHealCounters": "Self-heal counters",
|
||||
@@ -8214,6 +8395,18 @@ export default interface Resources {
|
||||
"saving": "Saving…",
|
||||
"updated": "Spec updated"
|
||||
},
|
||||
"specLock": {
|
||||
"accepted": "Accepted {{acceptedAt}} · plan hash {{planHash}} · approval {{approval}}",
|
||||
"alignment": "Spec alignment",
|
||||
"alignmentLabel": "Spec lock alignment",
|
||||
"captured": "Captured {{capturedAt}} · source revision {{sourceRevision}} · source hash {{sourceHash}}",
|
||||
"currentPlan": "Current plan",
|
||||
"findings": "Findings",
|
||||
"latestLock": "Latest lock",
|
||||
"lockState": "Lock state",
|
||||
"relockChanged": "Re-lock changed: {{sections}}",
|
||||
"retainedHistory": "Retained history: {{locks}}; {{plans}}; {{reports}} reports"
|
||||
},
|
||||
"stall": {
|
||||
"noLogEntry": "No log entry yet",
|
||||
"noLogEntryTitle": "No 'In-review stall surfaced' entry on this task yet — self-healing may not have logged one within its rate-limit window.",
|
||||
@@ -8507,6 +8700,14 @@ export default interface Resources {
|
||||
"upToDate": "Up to date",
|
||||
"updateFailed": "Failed to update {{taskId}}: {{error}}"
|
||||
},
|
||||
"taskVerification": {
|
||||
"failed": "Failed",
|
||||
"heading": "Verification · {{profile}}",
|
||||
"passed": "Passed",
|
||||
"queued": "Queued for the task executor",
|
||||
"requestRejected": "Request rejected",
|
||||
"running": "Running in the task worktree"
|
||||
},
|
||||
"tasks": {
|
||||
"addTaskPlaceholder": "Add a task...",
|
||||
"addressPrFeedback": "Address PR feedback",
|
||||
@@ -8896,6 +9097,28 @@ export default interface Resources {
|
||||
"viewModeRemaining": "Remaining",
|
||||
"viewModeUsed": "Used"
|
||||
},
|
||||
"whatsapp": {
|
||||
"allowedSenders": "Set {{label}}; an empty list blocks all inbound messages.",
|
||||
"choosePairingMethod": "Choose {{qr}} to scan in WhatsApp Linked Devices, or {{code}} to enter a phone number and request a pairing code.",
|
||||
"connectedAs": "Connected as {{jid}}",
|
||||
"connectedSuccess": "WhatsApp is paired and ready to receive messages from allowed senders.",
|
||||
"description": "Pair and monitor this project's WhatsApp connection here.",
|
||||
"installPlugin": "Install and enable this plugin, then keep this settings panel open.",
|
||||
"instructionsLabel": "WhatsApp pairing instructions",
|
||||
"loggingOut": "Logging out...",
|
||||
"logoutRepair": "Logout and re-pair",
|
||||
"pairing": "WhatsApp pairing",
|
||||
"pairingConfiguration": "Pairing and configuration",
|
||||
"phoneNumber": "Phone number (E.164 digits without +)",
|
||||
"qrCode": "WhatsApp pairing QR code",
|
||||
"refreshStatus": "Refresh status",
|
||||
"repairInstructions": "Use Logout and re-pair to start over. If QR is still pending, wait briefly or refresh status for a new code.",
|
||||
"requestPairingCode": "Request pairing code",
|
||||
"requestingCode": "Requesting code...",
|
||||
"status": "Status:",
|
||||
"waitConnected": "Wait for the status above to become {{status}}.",
|
||||
"waitingQr": "Waiting for a fresh QR code. Keep this panel open and refresh if needed."
|
||||
},
|
||||
"workflow": {
|
||||
"advisoryExplanation": "Advisory workflow steps flagged non-blocking improvements:",
|
||||
"aggregateAdvisory": "Advisory",
|
||||
|
||||
Reference in New Issue
Block a user