diff --git a/api-report/apps/api/src/modules/process/contract.d.ts b/api-report/apps/api/src/modules/process/contract.d.ts index 9bdbe72..0241bd6 100644 --- a/api-report/apps/api/src/modules/process/contract.d.ts +++ b/api-report/apps/api/src/modules/process/contract.d.ts @@ -56,6 +56,11 @@ export type ProcessService = { instancesOf(workflowId: string): Promise; /** 删一版:生效版与在途钉版都不许删(见 `ActiveVersionError` / `PinnedVersionError`) */ deleteVersion(id: string, version: number): Promise; + /** + * 删整条流(全部版本)—— 生效版不拦:整条流的删除是明示动作,不是手滑; + * 但**任一版有在途单就拒**(`PinnedVersionError`)。返回剩下的全部包。 + */ + deleteFlow(id: string): Promise; /** * 改版本记录:**号与名**,只动这一版 —— 号自己定。 * 名不设闸(标签不参与流转);号设两道:不许撞同流已有号(`VersionTakenError`)、 diff --git a/api-report/packages/contracts/src/api/governance.d.ts b/api-report/packages/contracts/src/api/governance.d.ts index d7f4cd7..063e22b 100644 --- a/api-report/packages/contracts/src/api/governance.d.ts +++ b/api-report/packages/contracts/src/api/governance.d.ts @@ -1052,6 +1052,124 @@ export declare const deleteProcessVersionRoute: { }, z.core.$strip>>; }, z.core.$strip>; }; +/** + * 删整条流 —— 连同它的**所有版本**。货架上一条流一张卡,删的就是那张卡。 + * 两道闸:任一版有在途单即拒(那些单要按它跑完);还有调起规则指着它也拒 —— + * 删了包留着规则,那规则再命中就调不动任何东西,是个哑火的配置。 + */ +export declare const deleteProcessFlowRoute: { + readonly method: "DELETE"; + readonly path: "/api/gov/process/:id"; + readonly params: z.ZodObject<{ + id: z.ZodString; + }, z.core.$strip>; + readonly output: z.ZodObject<{ + packages: z.ZodArray; + name: z.ZodString; + version: z.ZodNumber; + state: z.ZodEnum<{ + active: "active"; + retired: "retired"; + }>; + callInterface: z.ZodObject<{ + input: z.ZodRecord; + output: z.ZodRecord; + }, z.core.$strip>; + nodes: z.ZodArray; + id: z.core.$ZodBranded; + label: z.ZodString; + layout: z.ZodOptional; + y: z.ZodOptional; + }, z.core.$strip>>; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"end">; + output: z.ZodOptional>; + id: z.core.$ZodBranded; + label: z.ZodString; + layout: z.ZodOptional; + y: z.ZodOptional; + }, z.core.$strip>>; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"activity">; + activityId: z.core.$ZodBranded; + activityVersion: z.ZodOptional; + failure: z.ZodDefault; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"reroute">; + edgeId: z.ZodString; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"escalate">; + approvalNodeId: z.core.$ZodBranded; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"tolerate">; + }, z.core.$strip>], "kind">>; + message: z.ZodOptional; + id: z.core.$ZodBranded; + label: z.ZodString; + layout: z.ZodOptional; + y: z.ZodOptional; + }, z.core.$strip>>; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"approval">; + reject: z.ZodDiscriminatedUnion<[z.ZodObject<{ + kind: z.ZodLiteral<"reopen">; + nodeId: z.core.$ZodBranded; + }, z.core.$strip>, z.ZodObject<{ + kind: z.ZodLiteral<"terminate">; + }, z.core.$strip>], "kind">; + nudge: z.ZodOptional; + }, z.core.$strip>>>; + }, z.core.$strip>>; + message: z.ZodOptional; + id: z.core.$ZodBranded; + label: z.ZodString; + layout: z.ZodOptional; + y: z.ZodOptional; + }, z.core.$strip>>; + }, z.core.$strip>], "kind">>; + edges: z.ZodArray; + to: z.core.$ZodBranded; + condition: z.ZodOptional; + label: z.ZodOptional; + tone: z.ZodOptional>; + reopen: z.ZodDefault; + }, z.core.$strip>>; + variables: z.ZodDefault; + }, z.core.$strip>>>; + description: z.ZodOptional; + diagram: z.ZodOptional>; + }, z.core.$strip>>; + }, z.core.$strip>; +}; /** * 改版本记录 —— **版本号自己定**(2026-07-29 用户定)、名字随之:改哪版只动哪版。 * diff --git a/apps/api/src/app/routes/governance-ops.ts b/apps/api/src/app/routes/governance-ops.ts index 161957c..128eac8 100644 --- a/apps/api/src/app/routes/governance-ops.ts +++ b/apps/api/src/app/routes/governance-ops.ts @@ -35,6 +35,7 @@ import { resumeTaskRoute, staffIdSchema, taskIdSchema, + deleteProcessFlowRoute, deleteProcessVersionRoute, editProcessVersionRoute, setNodeOwnerRoute, @@ -511,6 +512,35 @@ export function registerGovernanceOpsRoutes(app: Hono, container: Contai } }); + /** + * 删整条流:连同全部版本。两道闸 —— 在途单钉着(409,用例层); + * **还有调起规则指着它也拒**(409,这一层查):删了包留着规则,那规则就是哑火的配置。 + * 反查放在路由层而不是用例层:process 不该知道 trigger 的存在(L7 不依赖它)。 + */ + app.delete(deleteProcessFlowRoute.path, async (c) => { + const denied = await governProcess(c.get('account')); + if (denied) return c.json(denied, denyStatus(denied)); + const id = c.req.param('id'); + const rules = (await container.trigger.list()).filter((t) => t.processPackageId === id); + if (rules.length > 0) { + const body: ApiError = { + code: 'conflict', + message: `还有 ${rules.length} 条调起规则指着这条流 —— 先去触发器区改掉或删掉它们`, + }; + return c.json(body, 409); + } + try { + return c.json({ packages: await container.process.deleteFlow(id) }); + } catch (error) { + if (error instanceof PinnedVersionError) { + const body: ApiError = { code: 'conflict', message: error.message }; + return c.json(body, 409); + } + const body: ApiError = { code: 'not-found', message: error instanceof Error ? error.message : String(error) }; + return c.json(body, 404); + } + }); + /** 改版本记录:号自己定、名随之 —— 撞号与「跑过单改号」409,别的版一字不动 */ app.post(editProcessVersionRoute.path, async (c) => { const denied = await governProcess(c.get('account')); diff --git a/apps/api/src/modules/process/CONTRACT.md b/apps/api/src/modules/process/CONTRACT.md index c2e3412..dd328a5 100644 --- a/apps/api/src/modules/process/CONTRACT.md +++ b/apps/api/src/modules/process/CONTRACT.md @@ -30,6 +30,9 @@ 与**活动装载**(unknown-activity);字段类的三条检查(边未映射 / 必填未映射 / 类型相抵)全退役。 - **版本可删,两道闸**:生效版不可删(先停用,`ActiveVersionError`)、在途钉版不可删 (那些单要按它跑完,`PinnedVersionError`)。 + - **整条流可删**(`deleteFlow`,2026-07-29):连同全部版本。生效版这道闸**不设** —— + 删整条流是明示动作,先停用再删只是多一步仪式;在途单那道闸照设。 + 「还有调起规则指着它」由**路由层**反查(process 不依赖 trigger,L7 不知道它存在)。 - **版本号与名归设计人员自己定**(2026-07-29 用户定): · **号自己编**,不再只能系统递增 —— 两道闸:不许撞同流已有号(`VersionTakenError`)、 在途钉版不许改号(`PinnedVersionError`,那些单正按这个号找定义)。 diff --git a/apps/api/src/modules/process/__tests__/version-lifecycle.test.ts b/apps/api/src/modules/process/__tests__/version-lifecycle.test.ts index 841de91..291500d 100644 --- a/apps/api/src/modules/process/__tests__/version-lifecycle.test.ts +++ b/apps/api/src/modules/process/__tests__/version-lifecycle.test.ts @@ -110,6 +110,17 @@ describe('版本的删与改名', () => { expect(h.rows).toHaveLength(2); }); + it('删整条流:所有版本一起没 —— 含生效版,这是明示动作不是手滑', async () => { + const h = harness([pkg(2, 'active'), pkg(1, 'retired')]); + expect(await h.service.deleteFlow('quote')).toEqual([]); + }); + + it('删整条流:任一版有在途单就拒 —— 那些单要按它跑完', async () => { + const h = harness([pkg(2, 'active'), pkg(1, 'retired')], 2); + await expect(h.service.deleteFlow('quote')).rejects.toBeInstanceOf(PinnedVersionError); + expect(h.rows, '一版都没删').toHaveLength(2); + }); + it('改名只动这一版 —— 各版本正是靠名字互相区分', async () => { const h = harness([pkg(2, 'active'), pkg(1, 'retired')]); const after = await h.service.editVersion('quote', 1, { name: '统一报价 · 手工议价' }); diff --git a/apps/api/src/modules/process/contract.ts b/apps/api/src/modules/process/contract.ts index 784f49f..05b25fd 100644 --- a/apps/api/src/modules/process/contract.ts +++ b/apps/api/src/modules/process/contract.ts @@ -81,6 +81,11 @@ export type ProcessService = { /** 删一版:生效版与在途钉版都不许删(见 `ActiveVersionError` / `PinnedVersionError`) */ deleteVersion(id: string, version: number): Promise; + /** + * 删整条流(全部版本)—— 生效版不拦:整条流的删除是明示动作,不是手滑; + * 但**任一版有在途单就拒**(`PinnedVersionError`)。返回剩下的全部包。 + */ + deleteFlow(id: string): Promise; /** * 改版本记录:**号与名**,只动这一版 —— 号自己定。 * 名不设闸(标签不参与流转);号设两道:不许撞同流已有号(`VersionTakenError`)、 diff --git a/apps/api/src/modules/process/internal/usecase/process-service.ts b/apps/api/src/modules/process/internal/usecase/process-service.ts index 072693c..c935609 100644 --- a/apps/api/src/modules/process/internal/usecase/process-service.ts +++ b/apps/api/src/modules/process/internal/usecase/process-service.ts @@ -177,6 +177,22 @@ export function createProcessService(deps: ProcessServiceDeps): ProcessService { return after.filter((p) => p.id === id).sort((a, b) => b.version - a.version); }, + async deleteFlow(id) { + const all = await deps.store.listPackages(); + const mine = all.filter((p) => p.id === id); + if (mine.length === 0) throw new Error(`流程不存在:${id}`); + /* + * 生效版这道闸**不设**:删整条流是明示动作,先停用再删只是多一步仪式。 + * 在途单那道闸必须设 —— 定义没了那些单就再也推不动,与删单版同一条理由。 + */ + for (const v of mine) { + const pinned = await deps.store.countPinnedInFlight(id, v.version); + if (pinned > 0) throw new PinnedVersionError(id, v.version, pinned); + } + for (const v of mine) await deps.store.deletePackage(id, v.version); + return deps.store.listPackages(); + }, + async editVersion(id, version, patch) { const one = await deps.store.getPackage(id, version); if (one === undefined) throw new StaleVersionError(id, version); diff --git a/apps/web/src/app/gov-shell.tsx b/apps/web/src/app/gov-shell.tsx index cafb0d3..160c557 100644 --- a/apps/web/src/app/gov-shell.tsx +++ b/apps/web/src/app/gov-shell.tsx @@ -34,7 +34,7 @@ import { ToolPage, ToolsHome } from '@features/gov-tool'; import { DataHome, LedgersHome, ObjPage } from '@features/gov-data'; import { ArchiveHome } from '@features/gov-archive'; import { TestbedHome } from '@features/gov-testbed'; -import { CockpitHome, KnowledgeHome, MemPage } from '@features/gov-memory'; +import { CockpitHome } from '@features/gov-memory'; import { ChanPage, LlmPage, ModelsHome, SystemHome } from '@features/gov-system'; /* ── URL 编解码:`#///[/:…]` ────────────────── */ @@ -298,7 +298,6 @@ function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) tasks: TasksHome, people: PeopleHome, ledgers: LedgersHome, - knowledge: KnowledgeHome, system: SystemHome, }[zone]; return

; @@ -322,13 +321,11 @@ function Detail({ route, nav }: { route: Route; nav: Nav }) { case 'trigger': return ; case 'act': - return ; + return ; case 'tool': return ; case 'obj': return ; - case 'mem': - return ; case 'llm': return ; case 'chan': diff --git a/apps/web/src/app/styles/console.css b/apps/web/src/app/styles/console.css index 324301d..86bc693 100644 --- a/apps/web/src/app/styles/console.css +++ b/apps/web/src/app/styles/console.css @@ -260,6 +260,45 @@ .g-tile-f > .g-btn { margin-left: auto; height: 24px; padding: 0 9px; font-size: 11px; } .g-tile-f .r { margin-left: auto; font-family: var(--mono); color: var(--ink-4); } +/* ── 流程卡(货架)—— 两段:头(身份 · 状态 · ⋯)+ 数据板(四格计量) ────── + 从前是「头 + 芯片行 + 一大片空 + 脚注」,卡撑到 158 高全靠留白; + 现在段段有内容、段间有线,高矮由内容说了算。 */ +.g-fcard { position: relative; display: flex; flex-direction: column; background: var(--card); border: 1px solid var(--cline); border-radius: 12px; box-shadow: var(--cshadow); overflow: hidden; transition: border-color var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast); } +.g-fcard:hover { border-color: #c9d6e6; box-shadow: 0 5px 14px -5px rgba(19, 28, 38, .22); transform: translateY(-1px); } +/* 整面一枚透明按钮 = 进这条流;字面不吃点击,直接穿到它身上(卡里还住着 ⋯ 与输入框) */ +.g-fcard > .hit { position: absolute; inset: 0; z-index: 0; border: 0; background: transparent; cursor: pointer; } +.g-fcard > .hit:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: 12px; } +.g-fcard > .hd, .g-fcard > .plate { position: relative; z-index: 1; pointer-events: none; } +.g-fcard button, .g-fcard input { pointer-events: auto; } +.g-fcard > .hd { display: flex; align-items: center; gap: 9px; padding: 13px 12px 12px 13px; } +.g-fcard .ico { flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center; color: var(--brand-deep); background: var(--brand-wash); } +.g-fcard.off .ico { color: var(--ink-4); background: #eef1f5; } +.g-fcard .ttl { flex: 1; min-width: 0; line-height: 1.3; } +.g-fcard .ttl b { display: block; font-size: 14px; font-weight: 700; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.g-fcard .ttl em { display: block; font-style: normal; font-size: 10.5px; font-family: var(--mono); color: var(--ink-4); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.g-fcard .ttl .g-input { width: 100%; height: 25px; padding: 0 7px; font-size: 13px; } +.g-fcard .stx { flex: none; font: 600 10.5px/1 var(--mono); color: var(--ink-4); } +.g-fcard .stx.on { color: var(--ok); } +.g-fcard .mbtn { flex: none; width: 24px; height: 24px; display: grid; place-items: center; border: 1px solid transparent; border-radius: 7px; background: transparent; color: var(--ink-4); cursor: pointer; } +.g-fcard .mbtn:hover, .g-fcard .mbtn.on { border-color: var(--cline); background: #f3f6fa; color: var(--ink); } +/* 数据板:四格等宽、竖线分隔 —— 数字在上、名目在下,一行说完这条流有多大 */ +.g-fcard .plate { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line-soft); background: #fafbfd; } +.g-fcard .plate > span { display: flex; flex-direction: column; align-items: center; gap: 3px; padding: 9px 4px 10px; border-left: 1px solid var(--line-soft); } +.g-fcard .plate > span:first-child { border-left: 0; } +.g-fcard .plate b { font: 700 15px/1 var(--mono); letter-spacing: -.02em; color: var(--ink); } +.g-fcard .plate i { font-style: normal; font-size: 10px; color: var(--ink-4); } +.g-fcard .plate > span.bad b { color: var(--danger); } +/* ⋯ 菜单:压在卡内右上角(卡是 overflow:hidden,菜单只在卡里活动) */ +.g-fcard .menu { position: absolute; right: 10px; top: 41px; z-index: 3; width: 138px; display: flex; flex-direction: column; gap: 2px; padding: 5px; border: 1px solid var(--cline); border-radius: 9px; background: #fff; box-shadow: 0 10px 22px -8px rgba(19, 28, 38, .3); } +.g-fcard .menu button { display: flex; align-items: center; gap: 6px; width: 100%; height: 27px; padding: 0 8px; border: 0; border-radius: 6px; background: transparent; font: 500 12px/1 var(--sans); color: var(--ink); text-align: left; cursor: pointer; } +.g-fcard .menu button:hover { background: #f2f5f9; } +.g-fcard .menu button.bad { color: var(--danger); } +.g-fcard .menu button.bad:hover { background: #fdf2f2; } +.g-fcard .menu .q { padding: 3px 8px 5px; font-size: 11px; line-height: 1.5; color: var(--ink-3); } +/* 幽灵卡:新建住在货架里 —— 与真卡同宽同高,一眼看出「这儿还能再添一条」 */ +.g-fcard.ghost { flex-direction: row; align-items: center; justify-content: center; gap: 7px; min-height: 102px; border-style: dashed; border-color: #c6d1de; background: transparent; box-shadow: none; color: var(--ink-4); font: 600 12.5px/1 var(--sans); cursor: pointer; } +.g-fcard.ghost:hover { border-color: var(--brand); background: var(--brand-wash); color: var(--brand-deep); box-shadow: none; transform: none; } + /* ============ 规则清单(触发器):整行可下钻 ============ */ .g-rules { display: flex; flex-direction: column; } .g-rule { display: grid; grid-template-columns: 30px minmax(0, 1fr) 84px 108px 78px 18px; align-items: center; gap: 12px; width: 100%; padding: 13px 16px; border: 0; border-bottom: 1px solid var(--line-soft); background: transparent; font: inherit; text-align: left; cursor: pointer; transition: background var(--t-fast); } diff --git a/apps/web/src/features/gov-activity/internal/panel.tsx b/apps/web/src/features/gov-activity/internal/panel.tsx index e366f7f..f3c6a33 100644 --- a/apps/web/src/features/gov-activity/internal/panel.tsx +++ b/apps/web/src/features/gov-activity/internal/panel.tsx @@ -15,7 +15,6 @@ import { Card, Chip, Clear, - Entry, Field, Note, PageHead, @@ -39,10 +38,7 @@ type InstallReply = { }; import { PARA } from '@web-shared/gov/tone.ts'; import type { Nav } from '@web-shared/gov/gov-route.ts'; -import { - useGovActivities, - useGovMemory, -} from '@web-shared/gov/gov-api.ts'; +import { useGovActivities } from '@web-shared/gov/gov-api.ts'; export type HomeProps = { tab: string; nav: Nav }; @@ -108,9 +104,8 @@ export function LibraryHome({ nav }: HomeProps) { /* ---------------- ⑥ 工具管理 ---------------- */ -export function ActPage({ id, nav }: { id: string; nav: Nav }) { +export function ActPage({ id }: { id: string }) { const q = useGovActivities(); - const memory = useGovMemory(id); const a = (q.data?.activities ?? []).find((x) => x.activityId === id); if (a === undefined) return 活动不存在。; return ( @@ -208,12 +203,6 @@ export function ActPage({ id, nav }: { id: string; nav: Nav }) { {a.assetVersions.length === 0 && 尚无资产版本。} - nav.jump('knowledge', 'layers')} - /> ); } diff --git a/apps/web/src/features/gov-memory/index.ts b/apps/web/src/features/gov-memory/index.ts index e02e0c9..66edeb7 100644 --- a/apps/web/src/features/gov-memory/index.ts +++ b/apps/web/src/features/gov-memory/index.ts @@ -2,4 +2,4 @@ * feature `gov-memory` 唯一出口(编码规范 §9.1)。 * 记忆管理:度量仪表、活动记忆四层 */ -export { CockpitHome, KnowledgeHome, MemPage } from './internal/panel.tsx'; +export { CockpitHome } from './internal/panel.tsx'; diff --git a/apps/web/src/features/gov-memory/internal/panel.tsx b/apps/web/src/features/gov-memory/internal/panel.tsx index dcdcb88..618f3a8 100644 --- a/apps/web/src/features/gov-memory/internal/panel.tsx +++ b/apps/web/src/features/gov-memory/internal/panel.tsx @@ -1,24 +1,19 @@ /** - * feature `gov-memory` —— 运营中心的两处:**经营驾驶舱**(度量八指标)与**知识库**(活动记忆四层)。 + * feature `gov-memory` —— **经营驾驶舱**(运营首页:度量八指标 + 异常面)。 * * 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移: * DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。 * - * 原本这两件同挂在「记忆管理」一区,是两种心智硬挤在一起: - * 看指标问的是「今天跑得怎样」,管沉淀问的是「学到了什么、放不放行」。三面拆分后各自成页。 + * 「活动记忆」那一页 2026-07-29 撤销(用户定:运营中心删掉数据模块)—— + * 记忆的放行口还没成形,摆一个空区不如不摆;真要做时随「数据资产」一并重开。 + * 文件名仍叫 gov-memory:度量本就是留痕的投影,改名要动一串 import,等有第二处再说。 */ -import { useState } from 'react'; import { TERMINAL_STATE_LABEL } from '@contracts'; import type { TerminalState } from '@contracts'; import { Ico } from '@web-shared/Ico.tsx'; -import { Chip, Clear, Kpi, NList, NRow, Note, PageHead, QueryGate, Todo } from '@web-shared/gov/primitives.tsx'; +import { Clear, Kpi, Note, QueryGate, Todo } from '@web-shared/gov/primitives.tsx'; import type { Nav } from '@web-shared/gov/gov-route.ts'; -import { - useGovActivities, - useGovMemory, - useGovMetrics, - useGovTasks, -} from '@web-shared/gov/gov-api.ts'; +import { useGovMetrics, useGovTasks } from '@web-shared/gov/gov-api.ts'; export type HomeProps = { tab: string; nav: Nav }; @@ -165,76 +160,3 @@ export function CockpitHome({ nav }: HomeProps) { ); } - -/* ---------------- 知识库 ---------------- */ - -/** 活动记忆四层 + 候选增量的放行口 —— 记忆的终点是成为模型,那道门在这里 */ -export function KnowledgeHome({ nav }: HomeProps) { - const activities = useGovActivities(); - const [picked, setPicked] = useState(undefined); - const memory = useGovMemory(picked); - - return ( - <> -

- {(activities.data?.activities ?? []).map((a) => ( - - ))} -
- - {(memory.data?.entries ?? []).map((e) => ( - 候选 · {e.incrementState} - ) : ( - 已成条目 - ) - } - onClick={() => nav.push({ k: 'mem', id: e.id })} - /> - ))} - {picked === undefined && 选一个活动,看它这些年学到了什么。} - {picked !== undefined && (memory.data?.entries ?? []).length === 0 && ( - 这个活动还没有记忆条目 —— 任务落终态封卷、蒸馏钩子跑过之后,沉淀会出现在这里。 - )} - - - ); -} - -/* ---------------- 记忆条目详情 ---------------- */ - -export function MemPage({ id }: { id: string }) { - const activities = useGovActivities(); - const first = (activities.data?.activities ?? [])[0]?.activityId; - const memory = useGovMemory(first); - const e = (memory.data?.entries ?? []).find((x) => x.id === id); - if (e === undefined) return 记忆条目需从对应活动进入。; - return ( - - {e.evidenceCount} 条证据 - {e.incrementState !== undefined && 候选增量 · {e.incrementState}} - - } - desc="写入唯蒸馏,条目必挂证据链;记忆不载权力——强制力只经候选增量 + 回放 + 人放行进入业务模型。" - /> - ); -} diff --git a/apps/web/src/features/gov-process/internal/panel.tsx b/apps/web/src/features/gov-process/internal/panel.tsx index f3aeefc..6e6a7d8 100644 --- a/apps/web/src/features/gov-process/internal/panel.tsx +++ b/apps/web/src/features/gov-process/internal/panel.tsx @@ -19,6 +19,7 @@ import type { KeyboardEvent } from 'react'; import { useMutation, useQueryClient, useQuery } from '@tanstack/react-query'; import type { ClosureReport, ProcessPackage, ProcessNode } from '@contracts'; import { + deleteProcessFlowRoute, deleteProcessVersionRoute, processPackageSchema, editProcessVersionRoute, @@ -110,8 +111,6 @@ function OverviewHome() { unit="%" sub="不闭合不得激活" /> - {/* 网关已废除:分支 = 条件出边,不再是一种要素 */} - ); @@ -151,6 +150,11 @@ function FlowShelf({ nav }: { nav: Nav }) { const [nfName, setNfName] = useState(''); const [nfId, setNfId] = useState(''); const [nfError, setNfError] = useState(undefined); + /** 卡上的 ⋯ 菜单:开在哪张卡上;`drop` = 那张卡的菜单已翻到删除确认 */ + const [menu, setMenu] = useState<{ id: string; drop: boolean } | null>(null); + /** 就地改名:改的是卡面显示的**那一版**(名字是版本的属性) */ + const [renaming, setRenaming] = useState(null); + const [cardErr, setCardErr] = useState(undefined); // 标识进 URL 和触发器规则里引用,所以收紧:小写字母开头,只许小写/数字/连字符 const idOk = /^[a-z][a-z0-9-]{1,40}$/.test(nfId); const idTaken = flows.some((f) => f.id === nfId); @@ -180,13 +184,41 @@ function FlowShelf({ nav }: { nav: Nav }) { onError: (e: unknown) => setNfError((e as { message?: string }).message ?? '建流程失败'), }); + /** 卡上改名 —— 落在卡面那一版上(整条流的名字就是它生效版的名字) */ + const rename = useMutation({ + mutationFn: (input: { id: string; version: number; name: string }) => + call<{ packages: ProcessPackage[] }>(editProcessVersionRoute, { + params: { id: input.id, version: String(input.version) }, + body: { name: input.name }, + }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['gov', 'process'] }); + setRenaming(null); + }, + onError: (e: unknown) => setCardErr((e as { message?: string }).message ?? '改名失败'), + }); + + /** 删整条流 —— 在途单钉着、还有调起规则指着,服务端都会挡回来(409),判词照抄 */ + const dropFlow = useMutation({ + mutationFn: (id: string) => + call<{ packages: ProcessPackage[] }>(deleteProcessFlowRoute, { params: { id } }), + onSuccess: () => { + void queryClient.invalidateQueries({ queryKey: ['gov', 'process'] }); + setMenu(null); + }, + onError: (e: unknown) => { + setCardErr((e as { message?: string }).message ?? '删不掉'); + setMenu(null); + }, + }); + return ( - {creating ? ( + {creating && (
- setNfName(e.target.value)} /> + setNfName(e.target.value)} />
- ) : ( - setCreating(true) }]} /> )} + {cardErr !== undefined && {cardErr}}
{flows.map((f) => { @@ -227,50 +258,110 @@ function FlowShelf({ nav }: { nav: Nav }) { activity: shown.nodes.filter((n) => n.kind === 'activity').length, approval: shown.nodes.filter((n) => n.kind === 'approval').length, }; + const open = menu?.id === f.id; return ( - + +
+ {/* 数据板:四格等宽、竖线分隔 —— 一行说完这条流有多大、闭合没有 */} +
+ + {c.activity} + 活动 + + + {c.approval} + 审批 + + + + {ec.closed}/{ec.total} + + 边闭合 + + + {f.versions.length} + 版本 + +
+ {open && ( +
+ {menu.drop ? ( + <> + 删掉整条流?{f.versions.length} 个版本一起没 + + + + ) : ( + <> + + + + )} +
+ )} + ); })} + {/* 幽灵卡:新建就住货架里 —— 与真卡同一尺寸,一眼看出「这儿还能再添一条」 */} +
); diff --git a/apps/web/src/shared/gov/__tests__/surfaces.test.ts b/apps/web/src/shared/gov/__tests__/surfaces.test.ts index 33ddd81..2b82118 100644 --- a/apps/web/src/shared/gov/__tests__/surfaces.test.ts +++ b/apps/web/src/shared/gov/__tests__/surfaces.test.ts @@ -79,7 +79,8 @@ describe('legacyGovTarget —— 旧 #/gov 深链一条不死', () => { it('已删的区落到对应新家', () => { expect(legacyGovTarget('#/gov/memory/metrics')).toBe('/ops#/ops/cockpit/dash'); - expect(legacyGovTarget('#/gov/memory/layers')).toBe('/ops#/ops/knowledge/layers'); + // 活动记忆那一区 2026-07-29 整个撤了(运营 · 数据)—— 旧链接落运营首页,不给白屏 + expect(legacyGovTarget('#/gov/memory/layers')).toBe('/ops#/ops/cockpit/dash'); }); /* diff --git a/apps/web/src/shared/gov/gov-route.ts b/apps/web/src/shared/gov/gov-route.ts index bad977d..d42c319 100644 --- a/apps/web/src/shared/gov/gov-route.ts +++ b/apps/web/src/shared/gov/gov-route.ts @@ -17,7 +17,6 @@ export type Route = | { k: 'act'; id: string } | { k: 'tool'; id: string } | { k: 'obj'; id: string } - | { k: 'mem'; id: string } | { k: 'llm'; id: string } | { k: 'chan'; id: string }; @@ -25,7 +24,8 @@ export type Route = * 十一区 —— 分属两个台面(见 `surfaces.ts`): * 设计中心定义企业(战略 → 方法 → 能力 → 对象),运营中心看企业跑。 * 触发器不再独立成区:定义并入 workflows,启停并入 tasks; - * 记忆一分为二:度量升为 cockpit 首页,活动记忆归 knowledge。 + * 记忆只剩度量:升为 cockpit 首页。活动记忆那一区(运营 · 数据)2026-07-29 撤销 —— + * 记忆的放行口未建成形,摆一个空区不如不摆;真要做时随「数据资产」一并重开。 */ export const GOV_ZONES = [ /* @@ -49,7 +49,6 @@ export const GOV_ZONES = [ 'tasks', 'people', 'ledgers', - 'knowledge', 'system', ] as const; export type GovZone = (typeof GOV_ZONES)[number]; @@ -69,7 +68,6 @@ export const ZONES: ReadonlyArray<{ key: GovZone; label: string; icon: string }> { key: 'tasks', label: '任务', icon: 'list-checks' }, { key: 'people', label: '员工', icon: 'users' }, { key: 'ledgers', label: '台账', icon: 'table' }, - { key: 'knowledge', label: '数据', icon: 'layers' }, { key: 'system', label: '系统', icon: 'settings' }, ]; @@ -93,12 +91,6 @@ export const ZM: Readonly> = { design: ['blueprint', 'triggers', 'workflows', 'library', 'tools', 'data', 'archive', 'models', 'testbed'], - ops: ['cockpit', 'runs', 'tasks', 'people', 'ledgers', 'knowledge', 'system'], + ops: ['cockpit', 'runs', 'tasks', 'people', 'ledgers', 'system'], }; /** @@ -72,7 +72,6 @@ const LEGACY_MOVES: Readonly> = { 'testbed/inbound': 'design/testbed/run', 'system/llm': 'design/models/llm', memory: 'ops/cockpit/dash', - 'memory/layers': 'ops/knowledge/layers', }; /** 运营首页 —— 认不出的旧链接一律落这儿,不给白屏 */ diff --git a/packages/contracts/src/api/governance.ts b/packages/contracts/src/api/governance.ts index 0645a0a..ae084ba 100644 --- a/packages/contracts/src/api/governance.ts +++ b/packages/contracts/src/api/governance.ts @@ -238,6 +238,18 @@ export const deleteProcessVersionRoute = { output: z.object({ packages: z.array(processPackageSchema) }), } as const satisfies RouteContract; +/** + * 删整条流 —— 连同它的**所有版本**。货架上一条流一张卡,删的就是那张卡。 + * 两道闸:任一版有在途单即拒(那些单要按它跑完);还有调起规则指着它也拒 —— + * 删了包留着规则,那规则再命中就调不动任何东西,是个哑火的配置。 + */ +export const deleteProcessFlowRoute = { + method: 'DELETE', + path: '/api/gov/process/:id', + params: z.object({ id: z.string() }), + output: z.object({ packages: z.array(processPackageSchema) }), +} as const satisfies RouteContract; + /** * 改版本记录 —— **版本号自己定**(2026-07-29 用户定)、名字随之:改哪版只动哪版。 * diff --git a/packages/ui-kit/src/Ico.tsx b/packages/ui-kit/src/Ico.tsx index e31a99e..6ad8e93 100644 --- a/packages/ui-kit/src/Ico.tsx +++ b/packages/ui-kit/src/Ico.tsx @@ -57,6 +57,7 @@ const GLYPHS: Record = { paperclip: (), "building-2": (<>), "panel-left-open": (<>), + "more-horizontal": (<>), "trash-2": (<>), calculator: (<>), /* —— 治理中心扩充图标 —— */