diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index cf9d63d4..872d7ab0 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -79,5 +79,5 @@ Plans:
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
---
-*Milestone status:* `in_progress`
-*Next command:* `/gsd-execute-phase 6`
+*Milestone status:* `complete`
+*Next command:* `/gsd-new-milestone`
diff --git a/.planning/STATE.md b/.planning/STATE.md
index 746f139d..51750b06 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,7 +2,7 @@
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
-status: v1.0 milestone in progress
+status: v1.0 milestone complete
last_updated: "2026-04-17T06:09:01.300Z"
last_activity: 2026-04-17
progress:
@@ -20,13 +20,13 @@ progress:
See: .planning/PROJECT.md (updated 2026-04-07)
**Core value:** Keep the frontend visually familiar while preserving and hardening new-system behavior end to end.
-**Current focus:** Milestone v1.0 in progress
+**Current focus:** Milestone v1.0 completed
## Workflow State
-- Current workflow: milestone execution (v1.0)
-- Next workflow: execute-phase
-- Next command: /gsd-execute-phase 6
+- Current workflow: milestone complete (v1.0)
+- Next workflow: new-milestone
+- Next command: /gsd-new-milestone
## Artifacts
@@ -53,6 +53,5 @@ See: .planning/PROJECT.md (updated 2026-04-07)
|---|-------------|------|--------|-----------|
| 260415-owq | 归档当前git diff为Phase 06验收后补丁:检查改动、更新06-UAT/06-VERIFICATION/06-SUMMARY(必要时)与STATE,再做原子提交 | 2026-04-15 | atomic | [260415-owq-git-diff-phase-06-06-uat-06-verification](./quick/260415-owq-git-diff-phase-06-06-uat-06-verification/) |
| 260416-koe | 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证) | 2026-04-16 | pending | [260416-koe-phase-06](./quick/260416-koe-phase-06/) |
-| 260417-kcb | suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage | 2026-04-17 | pending | [260417-kcb-suggestion-hover-dropdown-child-tooltip-](./quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/) |
-Last activity: 2026-04-17 - Completed quick task 260417-kcb: suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage
+Last activity: 2026-04-17
diff --git a/.planning/milestones/v1.0-ROADMAP.md b/.planning/milestones/v1.0-ROADMAP.md
index cf9d63d4..872d7ab0 100644
--- a/.planning/milestones/v1.0-ROADMAP.md
+++ b/.planning/milestones/v1.0-ROADMAP.md
@@ -79,5 +79,5 @@ Plans:
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
---
-*Milestone status:* `in_progress`
-*Next command:* `/gsd-execute-phase 6`
+*Milestone status:* `complete`
+*Next command:* `/gsd-new-milestone`
diff --git a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-CONTEXT.md b/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-CONTEXT.md
deleted file mode 100644
index dc107bca..00000000
--- a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-CONTEXT.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Quick Task 260417-kcb: suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage - Context
-
-**Gathered:** 2026-04-17
-**Status:** Ready for planning
-
-
-## Task Boundary
-
-实现 suggestion 多字内容展示:
-- 悬浮 suggestion 显示 dropdown(内容来自 children)
-- 悬浮 dropdown 菜单项显示 tooltip(内容来自 detail)
-- 点击 suggestion:将 children 的 skill id 组合为数组并发送 bootstrap
-- 点击 dropdown 菜单项:仅发送当前 id 进行 bootstrap
-- 发起 bootstrap 时同步更新 sessionStorage
-
-
-
-
-## Implementation Decisions
-
-### Dropdown Trigger
-- 使用 suggestion hover 打开 dropdown,同时保留 suggestion click 行为。
-
-### Tooltip Source
-- tooltip 文案优先使用 children.detail,无 detail 时回退到 skill name。
-
-### Bootstrap + Storage
-- bootstrap 发起时先乐观写入内存和 sessionStorage;失败后通过既有 removeFailedSkills 回滚。
-
-### the agent's Discretion
-- 不变更现有 API 入参与消息结构语义,仅扩展可选字段与前端交互。
-
-
-
-
-## Specific Ideas
-
-新增 children.detail 可选属性以支撑菜单项 tooltip。
-
-
diff --git a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-PLAN.md b/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-PLAN.md
deleted file mode 100644
index 6f1bb2d5..00000000
--- a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-PLAN.md
+++ /dev/null
@@ -1,37 +0,0 @@
----
-mode: quick-full
-must_haves:
- truths:
- - suggestion hover 能看到 children dropdown
- - dropdown 菜单项 hover 能看到 detail tooltip
- - suggestion click 发送 children 全量 id bootstrap
- - dropdown item click 仅发送当前 id bootstrap
- - bootstrap 发起时 sessionStorage 立即同步,失败可回滚
- artifacts:
- - frontend/src/components/workspace/input-box.tsx
- - frontend/src/hooks/use-iframe-skill.ts
- - frontend/src/core/i18n/locales/types.ts
- - frontend/src/core/iframe-messages.ts
- - frontend/src/core/i18n/locales/zh-CN.ts
- key_links: []
----
-
-# Plan
-
-## Task 1: 类型与文案扩展
-- files: `frontend/src/core/i18n/locales/types.ts`, `frontend/src/core/iframe-messages.ts`, `frontend/src/core/i18n/locales/zh-CN.ts`
-- action: 为 children 增加 `detail` 可选字段并补充中文建议数据。
-- verify: TypeScript 通过,i18n suggestions 可读取 detail。
-- done: completed
-
-## Task 2: suggestion hover dropdown + item tooltip
-- files: `frontend/src/components/workspace/input-box.tsx`
-- action: 引入 hover 打开 dropdown,菜单项展示 children,tooltip 展示 detail。
-- verify: 交互符合需求,主 suggestion 与子项点击行为分离。
-- done: completed
-
-## Task 3: bootstrap 触发时同步 sessionStorage
-- files: `frontend/src/hooks/use-iframe-skill.ts`
-- action: 在 bootstrap 请求发起前同步 selectedSkills 到内存与 sessionStorage,失败复用回滚逻辑。
-- verify: `pnpm -s typecheck` 通过。
-- done: completed
diff --git a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-RESEARCH.md b/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-RESEARCH.md
deleted file mode 100644
index e906f015..00000000
--- a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-RESEARCH.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Quick Task 260417-kcb - Research
-
-## Findings
-
-1. 当前 `SuggestionList` 已具备 children 优先 bootstrap 机制,改造点集中在 UI 展示和事件分流。
-2. 代码库已有 Radix `DropdownMenu` 与 `Tooltip` 封装,直接复用可最小化风险。
-3. `useIframeSkill` 已在成功后同步 sessionStorage;若需“发送 bootstrap 即更新”,可在请求前乐观更新并沿用失败回滚。
-
-## Integration Notes
-
-- 受影响文件:`input-box.tsx`、`use-iframe-skill.ts`、i18n 类型与 zh-CN 文案。
-- 不涉及后端接口变更。
diff --git a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-SUMMARY.md b/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-SUMMARY.md
deleted file mode 100644
index 6aba2b82..00000000
--- a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-SUMMARY.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Quick Task 260417-kcb - Summary
-
-## Delivered
-
-- 新增 `detail` 可选属性并在中文 suggestions 中补充 detail 文案。
-- suggestion 悬浮时可见 dropdown,内容来自 children。
-- dropdown 菜单项悬浮时显示 tooltip,内容来自 detail(无 detail 回退 name)。
-- 点击 suggestion:bootstrap 使用 children 的全部 id。
-- 点击 dropdown 菜单项:bootstrap 仅携带该菜单项 id。
-- bootstrap 发起时即更新 sessionStorage 与本地 selectedSkills,失败走回滚。
-
-## Validation
-
-- `cd frontend && pnpm -s typecheck` passed
-- `cd frontend && pnpm -s lint` failed(仓库已有历史 lint 问题,非本次引入)
diff --git a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-VERIFICATION.md b/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-VERIFICATION.md
deleted file mode 100644
index f4276d37..00000000
--- a/.planning/quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/260417-kcb-VERIFICATION.md
+++ /dev/null
@@ -1,14 +0,0 @@
-status: passed
-
-# Verification
-
-- [x] suggestion hover shows dropdown children
-- [x] dropdown item hover shows tooltip(detail)
-- [x] suggestion click bootstraps all child ids
-- [x] dropdown item click bootstraps only selected id
-- [x] bootstrap start updates sessionStorage
-- [x] TypeScript check passed
-
-## Residual Risk
-
-- 未在本地启动浏览器进行可视化手动回归;建议在真实页面快速 smoke test 一次。
diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx
index 1639b188..142a80ee 100644
--- a/frontend/src/components/workspace/input-box.tsx
+++ b/frontend/src/components/workspace/input-box.tsx
@@ -937,7 +937,6 @@ function SuggestionList({
}) {
const { t } = useI18n();
const { textInput } = usePromptInputController();
- const [openDropdownKey, setOpenDropdownKey] = useState(null);
const suggestions = t.inputBox.suggestions;
const promptSuggestions = suggestions.filter(
(
@@ -1003,114 +1002,18 @@ function SuggestionList({
},
[bootstrapAndLockSkills, isBootstrapping, textInput],
);
-
- const handleSuggestionChildClick = useCallback(
- ({
- childSkill,
- suggestionTitle,
- }: {
- childSkill: SelectedSkillPayloadItem;
- suggestionTitle: string;
- }) => {
- if (isBootstrapping) return;
- setOpenDropdownKey(null);
- const id = String(childSkill.id).trim();
- const name = childSkill.name?.trim() ?? "";
- if (!id || !name) return;
- void bootstrapAndLockSkills({
- selectedSkills: [{ id, name, detail: childSkill.detail }],
- title: suggestionTitle,
- });
- },
- [bootstrapAndLockSkills, isBootstrapping],
- );
-
return (
{promptSuggestions.map((suggestion) => (
- (() => {
- const childSkills = (suggestion.children ?? [])
- .map((item) => ({
- id: String(item.id).trim(),
- name: item.name?.trim() ?? "",
- detail: item.detail?.trim() ?? "",
- }))
- .filter(
- (
- item,
- ): item is {
- id: string;
- name: string;
- detail: string;
- } => Boolean(item.id) && Boolean(item.name),
- );
-
- if (childSkills.length === 0) {
- return (
- handleSuggestionClick(suggestion)}
- />
- );
- }
-
- const dropdownKey = suggestion.suggestion;
- return (
- setOpenDropdownKey(open ? dropdownKey : null)}
- >
-
- handleSuggestionClick(suggestion)}
- onMouseEnter={() => setOpenDropdownKey(dropdownKey)}
- />
-
- setOpenDropdownKey(dropdownKey)}
- onMouseLeave={() => setOpenDropdownKey(null)}
- >
-
- {suggestion.suggestion}
-
-
-
- {childSkills.map((item) => (
-
- {
- event.preventDefault();
- handleSuggestionChildClick({
- childSkill: item,
- suggestionTitle: suggestion.suggestion,
- });
- }}
- >
- {item.name}
-
-
- ))}
-
-
-
- );
- })()
+ handleSuggestionClick(suggestion)}
+ />
))}
);
diff --git a/frontend/src/components/workspace/tooltip.tsx b/frontend/src/components/workspace/tooltip.tsx
index 0bbb2e5b..ece59ba4 100644
--- a/frontend/src/components/workspace/tooltip.tsx
+++ b/frontend/src/components/workspace/tooltip.tsx
@@ -1,7 +1,5 @@
"use client";
-import { Children, type ComponentProps, isValidElement } from "react";
-
import {
Tooltip as TooltipPrimitive,
TooltipContent,
@@ -11,23 +9,15 @@ import {
export function Tooltip({
children,
content,
- side,
...props
}: {
children: React.ReactNode;
- side?: ComponentProps["side"];
content?: React.ReactNode;
}) {
- const hasSingleElementChild =
- Children.count(children) === 1 && isValidElement(children);
- const triggerChild = hasSingleElementChild ? children : {children};
-
return (
- {triggerChild}
-
- {content}
-
+ {children}
+ {content}
);
}
diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts
index 3a85dd44..b9e79878 100644
--- a/frontend/src/core/i18n/locales/types.ts
+++ b/frontend/src/core/i18n/locales/types.ts
@@ -3,7 +3,6 @@ import type { LucideIcon } from "lucide-react";
export interface SelectedSkillPayloadItem {
id: string | number;
name: string;
- detail?: string;
}
export interface Translations {
diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts
index 2d3a10e1..f215c790 100644
--- a/frontend/src/core/i18n/locales/zh-CN.ts
+++ b/frontend/src/core/i18n/locales/zh-CN.ts
@@ -126,66 +126,31 @@ export const zhCN: Translations = {
prompt:
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
icon: PenLineIcon,
- children: [
- {
- id: "6057",
- name: "生辰解语",
- detail:
- "四柱八字命理分析。\n当用户询问八字、四柱、命理、算命、Bazi、运势预测、命盘分析,\n或想了解其八字命盘、运势、大运、流年时,请使用此功能。",
- },
- ],
+ children: [{ id: "6057", name: "生辰解语" }],
},
{
suggestion: "小红书种草",
prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。",
icon: CompassIcon,
- children: [
- {
- id: "6099",
- name: "小红书笔记智造官",
- detail:
- "根据用户需求及提供资料,撰写小红书笔记内容(标题与正文)。\n生成图片卡片(封面及正文卡片),并支持发布小红书笔记。",
- },
- ],
+ children: [{ id: "6099", name: "小红书笔记智造官" }],
},
{
suggestion: "精美报告",
prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。",
icon: GraduationCapIcon,
- children: [
- {
- id: "6100",
- name: "MD 转 PDF 助手",
- detail:
- "将 Markdown(.md)文件转换为专业排版的 PDF 文档。\n支持将 markdown 转换为 PDF。\n支持将 .md 文件转为 .pdf。\n支持从 markdown 生成 PDF 报告。\n适用于中文技术报告、学术论文、文档编写及各类专业排版需求。",
- },
- ],
+ children: [{ id: "6100", name: "MD 转 PDF 助手" }],
},
{
suggestion: "excel数据处理",
prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。",
icon: MicroscopeIcon,
- children: [
- {
- id: "17",
- name: "Excel处理",
- detail:
- "全面的电子表格创建、编辑与分析功能。\n支持公式运算、格式设置、数据分析和可视化呈现。\n当 Claude 需要处理各类电子表格文件(如 .xlsx、.xlsm、.csv、.tsv 等格式)时,可执行以下操作:\n(1) 创建包含公式与格式的新表格;\n(2) 读取或分析表格数据;\n(3) 在保留公式的前提下修改现有表格;\n(4) 在表格内进行数据分析与可视化处理;\n(5) 重新计算公式。",
- },
- ],
+ children: [{ id: "17", name: "Excel处理" }],
},
{
suggestion: "营销策划",
prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。",
icon: ShapesIcon,
- children: [
- {
- id: "217",
- name: "产品营销背景",
- detail:
- "当用户需要创建或更新产品营销背景文档时使用。\n可用于沉淀目标用户、市场环境与竞争格局等关键信息。",
- },
- ],
+ children: [{ id: "217", name: "产品营销背景" }],
},
],
suggestionsCreate: [
diff --git a/frontend/src/core/iframe-messages.ts b/frontend/src/core/iframe-messages.ts
index a9d6866f..9008f22d 100644
--- a/frontend/src/core/iframe-messages.ts
+++ b/frontend/src/core/iframe-messages.ts
@@ -68,7 +68,6 @@ export interface SelectedSkillMessage {
export interface SelectedSkillPayloadItem {
id: string | number;
name: string;
- detail?: string;
}
type UnknownRecord = Record;
@@ -108,16 +107,8 @@ export function isSelectedSkillsMessage(
if (!skill) return false;
const id = skill.id;
const name = skill.name;
- const detail = skill.detail;
const isValidId = typeof id === "string" || typeof id === "number";
- const isValidDetail =
- detail === undefined || typeof detail === "string";
- return (
- isValidId &&
- typeof name === "string" &&
- name.trim().length > 0 &&
- isValidDetail
- );
+ return isValidId && typeof name === "string" && name.trim().length > 0;
});
}
diff --git a/frontend/src/hooks/use-iframe-skill.ts b/frontend/src/hooks/use-iframe-skill.ts
index 90743a4e..bf4fcf82 100644
--- a/frontend/src/hooks/use-iframe-skill.ts
+++ b/frontend/src/hooks/use-iframe-skill.ts
@@ -30,26 +30,6 @@ function getThreadStorageKey(threadId?: string | null): string | null {
return `${STORAGE_KEYS.byThreadPrefix}${normalized}`;
}
-function persistSkillsToSessionStorage(
- skills: SkillData[],
- threadId?: string | null,
-) {
- const threadKey = getThreadStorageKey(threadId);
- if (skills.length === 0) {
- window.sessionStorage.removeItem(STORAGE_KEYS.latest);
- if (threadKey) {
- window.sessionStorage.removeItem(threadKey);
- }
- return;
- }
-
- const payload = JSON.stringify(skills);
- window.sessionStorage.setItem(STORAGE_KEYS.latest, payload);
- if (threadKey) {
- window.sessionStorage.setItem(threadKey, payload);
- }
-}
-
function parseStoredSkills(raw: string | null): SkillData[] {
if (!raw) return [];
try {
@@ -235,8 +215,21 @@ export function useIframeSkill(
// 4. 选择变化时同步到 sessionStorage
useEffect(() => {
- // 空数组也要同步到存储,避免 UI 状态与缓存不一致
- persistSkillsToSessionStorage(selectedSkills, threadId);
+ const threadKey = getThreadStorageKey(threadId);
+ if (selectedSkills.length === 0) {
+ // 空数组也要同步到存储,避免 UI 状态与缓存不一致
+ window.sessionStorage.removeItem(STORAGE_KEYS.latest);
+ if (threadKey) {
+ window.sessionStorage.removeItem(threadKey);
+ }
+ return;
+ }
+
+ const payload = JSON.stringify(selectedSkills);
+ window.sessionStorage.setItem(STORAGE_KEYS.latest, payload);
+ if (threadKey) {
+ window.sessionStorage.setItem(threadKey, payload);
+ }
}, [selectedSkills, threadId]);
// 发送选择预定义 skill
@@ -293,15 +286,6 @@ export function useIframeSkill(
});
try {
- // 发起 bootstrap 时立即同步缓存;失败会在 removeFailedSkills 中回滚。
- const normalizedSkills = selectedSkills.map((item) => ({
- skill_id: String(item.id),
- title: item.name,
- }));
- setSelectedSkill(normalizedSkills[0] ?? null);
- setSelectedSkills(normalizedSkills);
- persistSkillsToSessionStorage(normalizedSkills, threadId);
-
const result = await bootstrapRemoteSkill({
thread_id: threadId,
content_ids,
@@ -324,12 +308,12 @@ export function useIframeSkill(
}
sendSelectSkill(selectedSkills);
- const latestSkills = selectedSkills.map((item) => ({
+ const normalizedSkills = selectedSkills.map((item) => ({
skill_id: String(item.id),
title: item.name,
}));
- setSelectedSkill(latestSkills[0] ?? null);
- setSelectedSkills(latestSkills);
+ setSelectedSkill(normalizedSkills[0] ?? null);
+ setSelectedSkills(normalizedSkills);
toast.success(t.skills.loadSuccessWithTitle(title), {
description: result.message || t.skills.createdFiles(result.created_files),