FN-7614: replace planning-mode banner with yellow nav badge for needs-input state

Planning Mode's "waiting for input" indicator moves from a top banner (whose button did not redirect correctly) to a yellow status-dot badge on the Planning nav destination, matching the existing chat unread-badge pattern.

- Add a `planningNeedsInput` flag in app lifecycle utils to detect awaiting_input planning sessions
- Exclude planning awaiting_input sessions from SessionNotificationBanner so the banner no longer shows for this case
- Add a status-dot--pending badge to the Planning entry in LeftSidebarNav and to the Planning item/tab in MobileNavBar
- Add/extend tests covering appLifecycle, LeftSidebarNav, MobileNavBar, and SessionNotificationBanner behavior
- Add changeset (patch) documenting the fix
- Update dashboard-guide.md docs

Files changed:
 .changeset/fn-7614-planning-badge.md               |  7 +++
 docs/dashboard-guide.md                            |  4 ++
 packages/dashboard/app/App.tsx                     | 13 +++-
 .../dashboard/app/components/LeftSidebarNav.tsx    | 10 +++
 packages/dashboard/app/components/MobileNavBar.css | 20 ++++++
 packages/dashboard/app/components/MobileNavBar.tsx | 19 +++++-
 .../components/__tests__/LeftSidebarNav.test.tsx   | 28 +++++++++
 .../app/components/__tests__/MobileNavBar.test.tsx | 41 ++++++++++++
 .../__tests__/SessionNotificationBanner.test.tsx   | 44 +++++++++++++
 .../app/utils/__tests__/appLifecycle.test.ts       | 73 +++++++++++++++++++++-
 packages/dashboard/app/utils/appLifecycle.ts       | 12 ++++
 packages/i18n/locales/en/app.json                  |  1 +
 packages/i18n/locales/es/app.json                  |  1 +
 packages/i18n/locales/fr/app.json                  |  1 +
 packages/i18n/locales/ko/app.json                  |  1 +
 packages/i18n/locales/zh-CN/app.json               |  1 +
 packages/i18n/locales/zh-TW/app.json               |  1 +
 packages/i18n/src/resources.d.ts                   |  1 +
 18 files changed, 275 insertions(+), 3 deletions(-)

Fusion-Task-Id: FN-7614

Fusion-Task-Lineage: 249e6ee8-149c-4d51-85b6-561dfc30c769

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-05 21:14:22 -07:00
parent 32e8bbe459
commit 5631c88d54
18 changed files with 275 additions and 3 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Planning Mode "needs input" now shows a yellow nav badge instead of a top banner.
category: fix
dev: Excludes planning `awaiting_input` sessions from SessionNotificationBanner and adds a `status-dot--pending` dot to the Planning nav destination (LeftSidebarNav + MobileNavBar More item/tab), driven by a new `planningNeedsInput` flag.

View File

@@ -415,6 +415,10 @@ Use **Show worktree grouping on the board** in **Settings → Worktrees** when y
Planning is a desktop/tablet left-sidebar main-content destination after **Command Center**. It opens the planning-session list and composer in the main content region; mobile continues to use the compact planning entry points. Planning Mode now includes branch controls on the summary screen before you create a task.
<!-- FNXC:SessionBanner 2026-07-05-00:00: A Planning session waiting for input no longer shows a top-of-board SessionNotificationBanner (its Resume button did not reliably redirect into Planning Mode). Instead the Planning nav destination shows a yellow `status-dot--pending` dot (desktop left sidebar, mobile More item, and mobile More tab icon) until you open Planning or the session resolves. Other session types (subtask breakdown, mission/milestone/slice interviews, CLI-agent needs-attention, and errored sessions of any type, including Planning) continue to surface via the top-of-board banner. -->
When a Planning session is awaiting your input, look for the yellow needs-input dot on the Planning nav destination (desktop left sidebar; mobile More sheet item and More tab icon) rather than a banner — clicking Planning always opens the correct docked Planning view.
<!-- FNXC:PlanningModeDeepeningCheckpoint 2026-07-02-12:18: Planning Mode must pause before every final summary at a mandatory "Would you like to go deeper?" checkpoint so users can request inferred follow-up themes, enter a custom topic, or proceed without deepening. -->
Before Planning Mode shows **Planning Complete!** or the final plan summary, it first asks **Would you like to go deeper?**. Select one or more suggested themes to continue the interview, use **Other** to add a custom topic, or choose **No, continue to final summary** to reveal the pending summary and task-creation actions.

View File

@@ -92,6 +92,7 @@ import {
requiresNativeShellOnboarding,
shouldShowFirstEverBootLoader,
isSessionNeedingInputForBanner,
isPlanningAwaitingInput,
getCliActionDisabledReasonForBanner,
executeCliSessionBannerAction,
} from "./utils/appLifecycle";
@@ -103,6 +104,7 @@ export {
requiresNativeShellOnboarding,
shouldShowFirstEverBootLoader,
isSessionNeedingInputForBanner,
isPlanningAwaitingInput,
getCliActionDisabledReasonForBanner,
executeCliSessionBannerAction,
} from "./utils/appLifecycle";
@@ -365,9 +367,16 @@ function AppInner() {
/*
* FNXC:SessionBanner 2026-06-14-19:32:
* CLI agent sessions use `waiting_on_input` and `needs_attention` to represent user-actionable states. The banner feed must include those statuses in addition to the legacy planning-session statuses so visible CLI actions cannot be silently hidden from users.
*
* FNXC:SessionBanner 2026-07-05-00:00:
* Planning `awaiting_input` sessions are excluded from the banner feed: the banner's Resume button did not
* reliably redirect into Planning Mode. That signal now surfaces as a yellow `status-dot--pending` nav badge
* (see `planningNeedsInput` below) whose click target is the already-correct `planning` view navigation.
* Planning sessions in `error` status are unaffected and still render in the banner.
*/
const sessionsNeedingInput = bgSessions.filter(isSessionNeedingInputForBanner);
const sessionsNeedingInput = bgSessions.filter((s) => isSessionNeedingInputForBanner(s) && !isPlanningAwaitingInput(s));
const sessionBannersHidden = useSessionBannersHidden();
const planningNeedsInput = bgPlanningSessions.some((s) => s.status === "awaiting_input");
// Modal state/handlers - required before useViewState
const modalManager = useModalManager({
@@ -1586,6 +1595,7 @@ function AppInner() {
mailboxUnreadCount={mailboxUnreadCount}
mailboxPendingApprovalCount={mailboxPendingApprovalCount}
chatHasUnreadResponse={chatHasUnreadResponse}
planningNeedsInput={planningNeedsInput}
experimentalFeatures={{
insights: insightsEnabled,
memoryView: memoryEnabled,
@@ -1668,6 +1678,7 @@ function AppInner() {
onOpenPlanning={openPlanningWithNav}
onResumePlanning={resumePlanningWithNav}
activePlanningSessionCount={bgPlanningSessions.length}
planningNeedsInput={planningNeedsInput}
onOpenUsage={() => openUsageWithNav(null)}
onViewAllProjects={handleViewAllProjects}
onRunScript={runScriptWithNav}

View File

@@ -109,6 +109,13 @@ export interface LeftSidebarNavProps {
mailboxUnreadCount?: number;
mailboxPendingApprovalCount?: number;
chatHasUnreadResponse?: boolean;
/*
FNXC:Navigation 2026-07-05-00:00:
Planning Mode "awaiting input" no longer shows a top-of-board banner (its Resume button did not reliably
redirect). Instead this flag drives a yellow `status-dot--pending` dot on the Planning nav destination,
mirroring `chatHasUnreadResponse` exactly, so the click target is always the working `planning` nav item.
*/
planningNeedsInput?: boolean;
experimentalFeatures?: LeftSidebarExperimentalFeatures;
pluginDashboardViews?: PluginDashboardViewEntry[];
showAgentsTab?: boolean;
@@ -156,6 +163,7 @@ export function LeftSidebarNav({
mailboxUnreadCount = 0,
mailboxPendingApprovalCount = 0,
chatHasUnreadResponse = false,
planningNeedsInput = false,
experimentalFeatures,
pluginDashboardViews = [],
showAgentsTab = false,
@@ -324,6 +332,8 @@ export function LeftSidebarNav({
isActive: view === "planning",
icon: Lightbulb,
testId: "sidebar-nav-planning",
// FNXC:Navigation 2026-07-05-00:00: mirrors the chat item's `dot` below — replaces the broken-Resume banner.
dot: planningNeedsInput && view !== "planning" ? "pending" : undefined,
onSelect: () => onChangeView("planning"),
},
{

View File

@@ -263,6 +263,26 @@ Wrap every tab icon in the same token-sized icon slot and keep unread/pending do
color: var(--text-muted);
}
/*
FNXC:Navigation 2026-07-05-00:00:
Planning "awaiting input" moved from a top-of-board banner (broken Resume redirect) to a yellow needs-input dot on
this More-sheet item, mirroring the mobile-nav-tab icon-wrapper pattern so the dot positions relative to the icon
rather than the full-width row.
*/
.mobile-more-item-icon-wrapper {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
}
.mobile-more-item-icon-dot {
position: absolute;
top: -2px;
right: -2px;
}
.mobile-more-item-badge {
margin-left: auto;
min-width: 20px;

View File

@@ -88,6 +88,14 @@ export interface MobileNavBarProps {
onOpenPlanning?: () => void;
onResumePlanning?: () => void;
activePlanningSessionCount?: number;
/*
FNXC:Navigation 2026-07-05-00:00:
Planning Mode "awaiting input" no longer shows a top-of-board banner (its Resume button did not reliably
redirect). Instead this flag drives a yellow `status-dot--pending` dot on the Planning More-sheet item and the
More tab icon, mirroring `chatHasUnreadResponse`'s `mobile-nav-chat-unread-dot`, so the click target is always
the working Planning navigation.
*/
planningNeedsInput?: boolean;
onOpenUsage?: () => void;
onRunScript?: (name: string, command: string) => void;
projectId?: string;
@@ -149,6 +157,7 @@ export function MobileNavBar({
onOpenPlanning,
onResumePlanning,
activePlanningSessionCount = 0,
planningNeedsInput = false,
onOpenUsage,
onRunScript,
projectId,
@@ -469,6 +478,9 @@ export function MobileNavBar({
>
<span className="mobile-nav-tab-icon-wrapper">
<MoreHorizontal />
{planningNeedsInput && view !== "planning" && !isMoreOpen && (
<span className="status-dot status-dot--pending mobile-nav-chat-unread-dot" aria-label={t("nav.planningNeedsInputAriaLabel", "Planning needs your input")} />
)}
</span>
<span className="mobile-nav-tab-label">{t("nav.more", "More")}</span>
</button>
@@ -613,7 +625,12 @@ export function MobileNavBar({
data-testid="mobile-more-item-planning"
onClick={() => handleMoreAction(planningHandler)}
>
<Lightbulb />
<span className="mobile-more-item-icon-wrapper">
<Lightbulb />
{planningNeedsInput && (
<span className="status-dot status-dot--pending mobile-more-item-icon-dot" aria-label={t("nav.planningNeedsInputAriaLabel", "Planning needs your input")} />
)}
</span>
<span>{t("nav.planning", "Planning")}</span>
{activePlanningSessionCount > 0 && (
<span className="mobile-more-item-badge">{formatCount(activePlanningSessionCount)}</span>

View File

@@ -647,4 +647,32 @@ describe("LeftSidebarNav", () => {
expect(() => fireEvent.click(screen.getByTestId("sidebar-nav-settings"))).not.toThrow();
});
/*
FNXC:Navigation 2026-07-05-00:00:
FN-7614: planning-awaiting-input moved from a top-of-board banner (broken Resume redirect) to a yellow
`status-dot--pending` dot on this Planning nav destination, mirroring `chatHasUnreadResponse` exactly.
*/
describe("planningNeedsInput dot (FN-7614)", () => {
it("shows the pending status dot on the Planning item when planningNeedsInput is true and not on the planning view", () => {
renderSidebar({ planningNeedsInput: true, view: "board" });
const planningButton = screen.getByTestId("sidebar-nav-planning");
expect(planningButton.querySelector(".status-dot.status-dot--pending")).toBeTruthy();
});
it("hides the dot when the user is already on the planning view", () => {
renderSidebar({ planningNeedsInput: true, view: "planning" });
const planningButton = screen.getByTestId("sidebar-nav-planning");
expect(planningButton.querySelector(".status-dot.status-dot--pending")).toBeNull();
});
it("hides the dot when planningNeedsInput is false", () => {
renderSidebar({ planningNeedsInput: false, view: "board" });
const planningButton = screen.getByTestId("sidebar-nav-planning");
expect(planningButton.querySelector(".status-dot.status-dot--pending")).toBeNull();
});
});
});

View File

@@ -557,6 +557,47 @@ describe("MobileNavBar", () => {
expect(screen.queryByLabelText("Unread chat response")).toBeNull();
});
/*
FNXC:Navigation 2026-07-05-00:00:
FN-7614: planning-awaiting-input moved from a top-of-board banner (broken Resume redirect) to a yellow
needs-input dot on the Planning More-sheet item and the More tab icon, mirroring the chat unread dot pattern.
The existing activePlanningSessionCount count badge must remain unaffected.
*/
describe("planningNeedsInput dot (FN-7614)", () => {
it("shows a needs-input dot on the More tab icon when planningNeedsInput is true and the sheet is closed", () => {
render(<MobileNavBar {...createDefaultProps()} view="board" planningNeedsInput={true} />);
expect(screen.getByLabelText("Planning needs your input")).toBeInTheDocument();
});
it("hides the More tab dot when planningNeedsInput is false", () => {
render(<MobileNavBar {...createDefaultProps()} view="board" planningNeedsInput={false} />);
expect(screen.queryByLabelText("Planning needs your input")).toBeNull();
});
it("hides the More tab dot while already on the planning view", () => {
render(<MobileNavBar {...createDefaultProps()} view="planning" planningNeedsInput={true} />);
expect(screen.queryByLabelText("Planning needs your input")).toBeNull();
});
it("shows a needs-input dot on the Planning More-sheet item and keeps the count badge unaffected", () => {
render(<MobileNavBar {...createDefaultProps()} view="board" planningNeedsInput={true} activePlanningSessionCount={2} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
const planningItem = screen.getByTestId("mobile-more-item-planning");
expect(planningItem.querySelector(".status-dot.status-dot--pending")).toBeTruthy();
expect(planningItem).toHaveTextContent("2");
});
it("hides the Planning More-sheet dot when planningNeedsInput is false while the count badge still renders", () => {
render(<MobileNavBar {...createDefaultProps()} view="board" planningNeedsInput={false} activePlanningSessionCount={3} />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-more"));
const planningItem = screen.getByTestId("mobile-more-item-planning");
expect(planningItem.querySelector(".status-dot.status-dot--pending")).toBeNull();
expect(planningItem).toHaveTextContent("3");
});
});
it("skills More-sheet item calls onChangeView with 'skills'", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="board" showSkillsTab={true} />);

View File

@@ -2,6 +2,7 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
import { fireEvent, render, screen } from "@testing-library/react";
import type { AiSessionSummary } from "../../api";
import { SessionNotificationBanner, dismissedIds } from "../SessionNotificationBanner";
import { isPlanningAwaitingInput, isSessionNeedingInputForBanner } from "../../utils/appLifecycle";
function buildSession(overrides: Partial<AiSessionSummary>): AiSessionSummary {
return {
@@ -283,6 +284,49 @@ describe("SessionNotificationBanner", () => {
expect(onDismissSession).toHaveBeenCalledWith("error-dismiss");
});
/*
FNXC:SessionBanner 2026-07-05-00:00:
Symptom Verification (FN-7614): the production banner feed (App.tsx `sessionsNeedingInput`) filters via
`isSessionNeedingInputForBanner(s) && !isPlanningAwaitingInput(s)` before it ever reaches this component. Given a
lone planning awaiting_input session, that filter yields an empty array, so the banner must render NO entry
(and no banner region at all) — reproducing the original broken-Resume-button symptom being fixed by the nav badge.
*/
it("renders no banner entry (and no banner region) for a lone planning awaiting_input session, using the production banner filter", () => {
const planningAwaitingInput = buildSession({ id: "planning-solo", type: "planning", status: "awaiting_input", title: "Solo planning session" });
const filtered = [planningAwaitingInput].filter((s) => isSessionNeedingInputForBanner(s) && !isPlanningAwaitingInput(s));
const { container } = render(
<SessionNotificationBanner
sessions={filtered}
onResumeSession={vi.fn()}
onDismissSession={vi.fn()}
onDismissAll={vi.fn()}
/>,
);
expect(filtered).toEqual([]);
expect(screen.queryByText("Solo planning session")).not.toBeInTheDocument();
expect(container.firstChild).toBeNull();
});
it("keeps a mixed non-planning awaiting-input session visible while excluding planning-awaiting-input via the production filter", () => {
const planningAwaitingInput = buildSession({ id: "planning-solo2", type: "planning", status: "awaiting_input", title: "Excluded planning session" });
const cliAwaitingInput = buildSession({ id: "cli-mixed", type: "cli-agent", status: "waiting_on_input", title: "Visible CLI session" });
const filtered = [planningAwaitingInput, cliAwaitingInput].filter((s) => isSessionNeedingInputForBanner(s) && !isPlanningAwaitingInput(s));
render(
<SessionNotificationBanner
sessions={filtered}
onResumeSession={vi.fn()}
onDismissSession={vi.fn()}
onDismissAll={vi.fn()}
/>,
);
expect(screen.queryByText("Excluded planning session")).not.toBeInTheDocument();
expect(screen.getByText("Visible CLI session")).toBeInTheDocument();
});
it("preserves dismissed error sessions when session status changes", () => {
const { rerender } = render(
<SessionNotificationBanner

View File

@@ -1,6 +1,77 @@
import { describe, expect, it } from "vitest";
import { buildRemoteDashboardUrl, resolveDesktopShellRedirectTarget } from "../appLifecycle";
import type { AiSessionSummary } from "../../api";
import {
buildRemoteDashboardUrl,
isPlanningAwaitingInput,
isSessionNeedingInputForBanner,
resolveDesktopShellRedirectTarget,
} from "../appLifecycle";
function makeSession(overrides: Partial<AiSessionSummary> & Pick<AiSessionSummary, "id">): AiSessionSummary {
return {
id: overrides.id,
type: overrides.type ?? "planning",
status: overrides.status ?? "generating",
title: overrides.title ?? overrides.id,
projectId: overrides.projectId ?? null,
lockedByTab: overrides.lockedByTab ?? null,
updatedAt: overrides.updatedAt ?? "2026-04-08T00:00:00.000Z",
};
}
/*
FNXC:SessionBanner 2026-07-05-00:00:
Symptom Verification (FN-7614): planning `awaiting_input` sessions must be excluded from the banner feed
(`isSessionNeedingInputForBanner(s) && !isPlanningAwaitingInput(s)`), while planning `error` and non-planning
awaiting-input sessions must remain — this is the invariant the App.tsx `sessionsNeedingInput` filter relies on.
*/
describe("isPlanningAwaitingInput", () => {
it("is true only for planning sessions awaiting input", () => {
expect(isPlanningAwaitingInput(makeSession({ id: "p1", type: "planning", status: "awaiting_input" }))).toBe(true);
});
it("is false for planning sessions in other statuses", () => {
expect(isPlanningAwaitingInput(makeSession({ id: "p2", type: "planning", status: "generating" }))).toBe(false);
expect(isPlanningAwaitingInput(makeSession({ id: "p3", type: "planning", status: "error" }))).toBe(false);
});
it("is false for non-planning sessions even when awaiting input", () => {
expect(isPlanningAwaitingInput(makeSession({ id: "c1", type: "cli-agent", status: "awaiting_input" }))).toBe(false);
});
});
describe("sessionsNeedingInput banner filter (isSessionNeedingInputForBanner + !isPlanningAwaitingInput)", () => {
function bannerFilter(sessions: AiSessionSummary[]): AiSessionSummary[] {
return sessions.filter((s) => isSessionNeedingInputForBanner(s) && !isPlanningAwaitingInput(s));
}
it("excludes a lone planning awaiting_input session from the banner feed", () => {
const sessions = [makeSession({ id: "p1", type: "planning", status: "awaiting_input" })];
expect(bannerFilter(sessions)).toEqual([]);
});
it("keeps planning error sessions in the banner feed", () => {
const errorSession = makeSession({ id: "p2", type: "planning", status: "error" });
expect(bannerFilter([errorSession])).toEqual([errorSession]);
});
it("keeps non-planning awaiting-input sessions in the banner feed", () => {
const cliSession = makeSession({ id: "c1", type: "cli-agent", status: "awaiting_input" });
expect(bannerFilter([cliSession])).toEqual([cliSession]);
});
it("excludes planning-awaiting-input while keeping a mixed non-planning awaiting-input session", () => {
const planningAwaiting = makeSession({ id: "p3", type: "planning", status: "awaiting_input" });
const cliAwaiting = makeSession({ id: "c2", type: "cli-agent", status: "awaiting_input" });
expect(bannerFilter([planningAwaiting, cliAwaiting])).toEqual([cliAwaiting]);
});
it("excludes generating planning sessions (never in the banner or badge input)", () => {
const generating = makeSession({ id: "p4", type: "planning", status: "generating" });
expect(bannerFilter([generating])).toEqual([]);
});
});
describe("resolveDesktopShellRedirectTarget", () => {
const remoteProfile = {

View File

@@ -176,6 +176,18 @@ export function isSessionNeedingInputForBanner(session: AiSessionSummary): boole
);
}
/*
FNXC:SessionBanner 2026-07-05-00:00:
Planning-Mode "awaiting input" is no longer a SessionNotificationBanner entry: the banner's Resume button did not
reliably redirect into the Planning Mode interface, so awaiting-input planning sessions are now surfaced as a yellow
`status-dot--pending` badge on the Planning nav destination instead (LeftSidebarNav + MobileNavBar), whose click
target is the already-correct `planning` view navigation. Planning sessions in `error` status are unaffected and
still render in the banner via `isSessionNeedingInputForBanner` above.
*/
export function isPlanningAwaitingInput(session: AiSessionSummary): boolean {
return session.type === "planning" && session.status === "awaiting_input";
}
export function getCliActionDisabledReasonForBanner(session: AiSessionSummary, action: CliActionId): string | null {
if ((action === "advance" || action === "relaunch") && !session.cliSessionId) {
return "CLI session id is missing.";

View File

@@ -4031,6 +4031,7 @@
"moreSheetTitle": "Navigate",
"noScriptsAddOne": "No scripts — add one…",
"planning": "Planning",
"planningNeedsInputAriaLabel": "Planning needs your input",
"primaryNavAriaLabel": "Primary navigation",
"projects": "Projects",
"research": "Research",

View File

@@ -4021,6 +4021,7 @@
"moreSheetTitle": "Navegar",
"noScriptsAddOne": "Sin scripts — agregar uno…",
"planning": "Planificación",
"planningNeedsInputAriaLabel": "Planificación necesita tu entrada",
"primaryNavAriaLabel": "Navegación principal",
"projects": "Proyectos",
"research": "Investigación",

View File

@@ -4021,6 +4021,7 @@
"moreSheetTitle": "Naviguer",
"noScriptsAddOne": "Aucun script — en ajouter un…",
"planning": "Planification",
"planningNeedsInputAriaLabel": "La planification nécessite votre saisie",
"primaryNavAriaLabel": "Navigation principale",
"projects": "Projets",
"research": "Recherche",

View File

@@ -4021,6 +4021,7 @@
"moreSheetTitle": "탐색",
"noScriptsAddOne": "스크립트 없음 — 추가하세요…",
"planning": "계획",
"planningNeedsInputAriaLabel": "계획에 입력이 필요합니다",
"primaryNavAriaLabel": "기본 탐색",
"projects": "프로젝트",
"research": "연구",

View File

@@ -4021,6 +4021,7 @@
"moreSheetTitle": "导航",
"noScriptsAddOne": "无脚本 — 添加一个…",
"planning": "规划",
"planningNeedsInputAriaLabel": "规划需要您的输入",
"primaryNavAriaLabel": "主导航",
"projects": "项目",
"research": "研究",

View File

@@ -4021,6 +4021,7 @@
"moreSheetTitle": "導覽",
"noScriptsAddOne": "無腳本 — 新增一個…",
"planning": "規劃",
"planningNeedsInputAriaLabel": "規劃需要您的輸入",
"primaryNavAriaLabel": "主導覽",
"projects": "專案",
"research": "研究",

View File

@@ -4032,6 +4032,7 @@ export default interface Resources {
"moreSheetTitle": "Navigate",
"noScriptsAddOne": "No scripts — add one…",
"planning": "Planning",
"planningNeedsInputAriaLabel": "Planning needs your input",
"primaryNavAriaLabel": "Primary navigation",
"projects": "Projects",
"research": "Research",