From 4827682de53ba0ee80dfb37aa115e8e82347f9d4 Mon Sep 17 00:00:00 2001 From: chenchen Date: Fri, 8 May 2026 11:19:21 +0800 Subject: [PATCH] refactor(client): retire client-side resources UI per new product spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 2026-05-08 product package (docs/product-package/) redraws the client / Manager boundary. Per 08-client-guide.md §1-7, the client explicitly does NOT carry resource binding, permission grants, or any account / security surface — those move entirely to Manager. This commit removes the client-side resources surface that landed in slices 2-4 (commits d1db2c1, c0363be, a28c900): Deleted: - cc-haha/desktop/src/api/heicodeResources.ts (API client) - cc-haha/desktop/src/stores/resourceStore.ts (zustand) - cc-haha/desktop/src/pages/ResourceBindings.tsx (page) - cc-haha/desktop/src/components/resources/Modals.tsx (3 modals) - cc-haha/src/server/api/heicode-resources.ts (proxy) Reverted: - Sidebar.tsx: drop the Resources nav item + RESOURCES_TAB_ID import - ContentRouter.tsx: drop the 'resources' branch + import - tabStore.ts: drop RESOURCES_TAB_ID + 'resources' from TabType - router.ts: drop 'heicode-resources' case + handler import - i18n zh.ts + en.ts: strip ~63 keys (sidebar.resources + resources.* + grants.*) Kept (still useful for the new spec's Manager-side data needs): - mcpAuth schema in types/provider.ts - mcpAuth wired through CreateProviderInput / UpdateProviderInput - providerService persistence of mcpAuth on add/update - Path A login flow that decodes JWT exp claims and stores the pair on the saved provider Why keep token persistence even though the client doesn't expose binding/grant UI any more? Per product spec the Manager will surface 余额 / 模型 / 用量 / 调用日志 (§2.3.1 in mcp-server's 待办 doc), and the client will surface high-risk approvals (08-client-guide.md §5). Both flows need a JWT pair we can refresh without re-prompting for password — that machinery is already in place. Next slice candidates per product spec (08 + 10 + 11): - High-risk approval dialog (新增 Tier 1, mock-wired UI first) - Task card + intent input as main client surface - Execution feedback panel (Agnet sub-stage status) - Delivery result panel None of those are in this commit; this commit is purely cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) --- cc-haha/desktop/src/api/heicodeResources.ts | 180 ------ .../src/components/layout/ContentRouter.tsx | 3 - .../desktop/src/components/layout/Sidebar.tsx | 11 +- .../src/components/resources/Modals.tsx | 538 ------------------ cc-haha/desktop/src/i18n/locales/en.ts | 70 --- cc-haha/desktop/src/i18n/locales/zh.ts | 70 --- .../desktop/src/pages/ResourceBindings.tsx | 528 ----------------- cc-haha/desktop/src/stores/resourceStore.ts | 178 ------ cc-haha/desktop/src/stores/tabStore.ts | 7 +- cc-haha/src/server/api/heicode-resources.ts | 193 ------- cc-haha/src/server/router.ts | 4 - docs/product-package/01-product-overview.md | 82 +++ .../02-homepage-design-brief.md | 173 ++++++ .../03-user-journey-and-core-flow.md | 184 ++++++ .../04-platform-usage-guide.md | 185 ++++++ docs/product-package/05-ppt-copy.md | 191 +++++++ .../06-security-and-credential-management.md | 108 ++++ .../07-integration-boundaries.md | 148 +++++ docs/product-package/08-client-guide.md | 118 ++++ docs/product-package/09-demo-script.md | 177 ++++++ .../10-frontend-detail-spec.md | 328 +++++++++++ .../11-product-prototype-wireframes.md | 454 +++++++++++++++ .../12-narrated-user-operation-flow.md | 341 +++++++++++ .../13-platform-description.md | 392 +++++++++++++ docs/product-package/README.md | 48 ++ 25 files changed, 2933 insertions(+), 1778 deletions(-) delete mode 100644 cc-haha/desktop/src/api/heicodeResources.ts delete mode 100644 cc-haha/desktop/src/components/resources/Modals.tsx delete mode 100644 cc-haha/desktop/src/pages/ResourceBindings.tsx delete mode 100644 cc-haha/desktop/src/stores/resourceStore.ts delete mode 100644 cc-haha/src/server/api/heicode-resources.ts create mode 100644 docs/product-package/01-product-overview.md create mode 100644 docs/product-package/02-homepage-design-brief.md create mode 100644 docs/product-package/03-user-journey-and-core-flow.md create mode 100644 docs/product-package/04-platform-usage-guide.md create mode 100644 docs/product-package/05-ppt-copy.md create mode 100644 docs/product-package/06-security-and-credential-management.md create mode 100644 docs/product-package/07-integration-boundaries.md create mode 100644 docs/product-package/08-client-guide.md create mode 100644 docs/product-package/09-demo-script.md create mode 100644 docs/product-package/10-frontend-detail-spec.md create mode 100644 docs/product-package/11-product-prototype-wireframes.md create mode 100644 docs/product-package/12-narrated-user-operation-flow.md create mode 100644 docs/product-package/13-platform-description.md create mode 100644 docs/product-package/README.md diff --git a/cc-haha/desktop/src/api/heicodeResources.ts b/cc-haha/desktop/src/api/heicodeResources.ts deleted file mode 100644 index d0f2840..0000000 --- a/cc-haha/desktop/src/api/heicodeResources.ts +++ /dev/null @@ -1,180 +0,0 @@ -// desktop/src/api/heicodeResources.ts -// -// API client for the local server's /api/heicode-resources/* proxy. -// The local server forwards each call to mcp-server (apimtaiji.azure-api.net) -// using the JWT stored in the active provider's mcpAuth, with transparent -// refresh on the 24h boundary. -// -// Server contract: cc-haha/src/server/api/heicode-resources.ts -// mcp-server contract: Heicode-接口契约文档.md §2 + §3 - -import { api } from './client' - -export type ResourceType = - | 'git' - | 'sk' - | 'project_doc' - | 'cloud_account' - | 'cloud_resource' - -export type ResourceStatus = 'pending' | 'active' | 'disabled' | 'revoked' - -export type ResourceBinding = { - id: string - user_id: string - type: ResourceType - name: string - external_ref?: string - metadata: Record - permission_scope: string[] - constraints: Record - secret_ref?: string - status: ResourceStatus - created_by: string - updated_by: string - created_at: string - updated_at: string -} - -export type ResourceBindingsList = { - items: ResourceBinding[] - total: number - offset: number - limit: number -} - -export type GrantStatus = 'active' | 'suspended' | 'revoked' | 'expired' - -export type ResourceGrant = { - id: string - user_id: string - resource_id: string - binding_scope: string - role?: string - agent_id?: string | null - allowed_actions: string[] - constraints: Record - status: GrantStatus - expires_at?: string | null - created_by: string - revoked_by?: string | null - created_at: string - revoked_at?: string | null -} - -export type ResourceGrantsList = { - items: ResourceGrant[] - total: number - offset: number - limit: number -} - -export type ApiEnvelope = { - success: boolean - data?: T - message?: string | null -} - -// ─── Create / Update payloads (mcp-server contract §2.1 / §2.4 / §3.1) ─── - -export type CreateBindingInput = { - type: ResourceType - name: string - external_ref?: string - metadata?: Record - permission_scope?: string[] - constraints?: Record - secret_ref?: string - status?: ResourceStatus -} - -export type UpdateBindingInput = Partial<{ - name: string - external_ref: string - metadata: Record - permission_scope: string[] - constraints: Record - secret_ref: string - status: ResourceStatus -}> - -export type CreateGrantInput = { - resource_id: string - binding_scope: string - role?: string - agent_id?: string | null - allowed_actions?: string[] - constraints?: Record - expires_at?: string | null - status?: GrantStatus -} - -const BASE = '/api/heicode-resources' - -export const heicodeResourcesApi = { - // ── ResourceBindings ────────────────────────────────────────── - listBindings(opts?: { type?: ResourceType; status?: ResourceStatus }) { - const params = new URLSearchParams() - if (opts?.type) params.set('type', opts.type) - if (opts?.status) params.set('status', opts.status) - const qs = params.toString() - return api.get>( - `${BASE}${qs ? `?${qs}` : ''}`, - ) - }, - - getBinding(id: string) { - return api.get>(`${BASE}/${encodeURIComponent(id)}`) - }, - - createBinding(input: CreateBindingInput) { - return api.post>(BASE, input) - }, - - updateBinding(id: string, input: UpdateBindingInput) { - return api.put>( - `${BASE}/${encodeURIComponent(id)}`, - input, - ) - }, - - deleteBinding(id: string) { - return api.delete>( - `${BASE}/${encodeURIComponent(id)}`, - ) - }, - - // ── ResourceGrants ──────────────────────────────────────────── - listGrants(opts?: { - resource_id?: string - role?: string - binding_scope?: string - status?: GrantStatus - }) { - const params = new URLSearchParams() - if (opts?.resource_id) params.set('resource_id', opts.resource_id) - if (opts?.role) params.set('role', opts.role) - if (opts?.binding_scope) params.set('binding_scope', opts.binding_scope) - if (opts?.status) params.set('status', opts.status) - const qs = params.toString() - return api.get>( - `${BASE}/grants${qs ? `?${qs}` : ''}`, - ) - }, - - getGrant(id: string) { - return api.get>( - `${BASE}/grants/${encodeURIComponent(id)}`, - ) - }, - - createGrant(input: CreateGrantInput) { - return api.post>(`${BASE}/grants`, input) - }, - - revokeGrant(id: string) { - return api.delete>( - `${BASE}/grants/${encodeURIComponent(id)}`, - ) - }, -} diff --git a/cc-haha/desktop/src/components/layout/ContentRouter.tsx b/cc-haha/desktop/src/components/layout/ContentRouter.tsx index 6b534c0..c79176c 100644 --- a/cc-haha/desktop/src/components/layout/ContentRouter.tsx +++ b/cc-haha/desktop/src/components/layout/ContentRouter.tsx @@ -4,7 +4,6 @@ import { EmptySession } from '../../pages/EmptySession' import { ActiveSession } from '../../pages/ActiveSession' import { ScheduledTasks } from '../../pages/ScheduledTasks' import { Settings } from '../../pages/Settings' -import { ResourceBindings } from '../../pages/ResourceBindings' import { TerminalSettings } from '../../pages/TerminalSettings' export function ContentRouter() { @@ -20,8 +19,6 @@ export function ContentRouter() { page = } else if (activeTabType === 'scheduled') { page = - } else if (activeTabType === 'resources') { - page = } else if (activeTabType !== 'terminal') { page = } diff --git a/cc-haha/desktop/src/components/layout/Sidebar.tsx b/cc-haha/desktop/src/components/layout/Sidebar.tsx index c61a16f..83ee65f 100644 --- a/cc-haha/desktop/src/components/layout/Sidebar.tsx +++ b/cc-haha/desktop/src/components/layout/Sidebar.tsx @@ -5,7 +5,7 @@ import { useTranslation } from '../../i18n' import { ProjectFilter } from './ProjectFilter' import { ConfirmDialog } from '../shared/ConfirmDialog' import type { SessionListItem } from '../../types/session' -import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID, RESOURCES_TAB_ID } from '../../stores/tabStore' +import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID } from '../../stores/tabStore' import { useChatStore } from '../../stores/chatStore' import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore' @@ -195,15 +195,6 @@ export function Sidebar() { > {t('sidebar.scheduled')} - useTabStore.getState().openTab(RESOURCES_TAB_ID, t('sidebar.resources'), 'resources')} - icon={link} - > - {t('sidebar.resources')} - void - onCancel: () => void -} - -export function ConfirmDialog({ - open, - title, - body, - confirmLabel, - danger, - busy, - errorMessage, - onConfirm, - onCancel, -}: ConfirmDialogProps) { - const t = useTranslation() - if (!open) return null - return ( - -
-
-

- {title} -

-
-
- {body} -
- {errorMessage ? ( -
- {errorMessage} -
- ) : null} -
- - -
-
-
- ) -} - -// ─── BindingFormModal ────────────────────────────────────────── - -type BindingFormProps = { - open: boolean - busy?: boolean - errorMessage?: string | null - onSubmit: (input: CreateBindingInput) => void - onCancel: () => void -} - -export function BindingFormModal({ - open, - busy, - errorMessage, - onSubmit, - onCancel, -}: BindingFormProps) { - const t = useTranslation() - const [type, setType] = useState('git') - const [name, setName] = useState('') - const [externalRef, setExternalRef] = useState('') - const [permissionScope, setPermissionScope] = useState('') - const [secretRef, setSecretRef] = useState('') - const [status, setStatus] = useState('pending') - - // reset when reopened - useEffect(() => { - if (open) { - setType('git') - setName('') - setExternalRef('') - setPermissionScope('') - setSecretRef('') - setStatus('pending') - } - }, [open]) - - if (!open) return null - - const canSubmit = name.trim().length > 0 && !busy - - const handleSubmit = () => { - const scope = permissionScope - .split(/[\n,]/) - .map((s) => s.trim()) - .filter(Boolean) - const input: CreateBindingInput = { - type, - name: name.trim(), - ...(externalRef.trim() && { external_ref: externalRef.trim() }), - ...(scope.length > 0 && { permission_scope: scope }), - ...(secretRef.trim() && { secret_ref: secretRef.trim() }), - status, - } - onSubmit(input) - } - - return ( - -
-
-

- {t('resources.modal.create.title')} -

-

- {t('resources.modal.create.subtitle')} -

-
- -
- - - - - - setName(e.target.value)} - disabled={busy} - placeholder="my-repo" - maxLength={255} - className={inputClass} - /> - - - - setExternalRef(e.target.value)} - disabled={busy} - placeholder="https://example.com/org/repo.git" - className={inputClass} - /> - - - -