初始化项目版本

This commit is contained in:
Axhub Make
2026-07-29 16:04:39 +08:00
commit 4305a1082b
2629 changed files with 760590 additions and 0 deletions

View File

@@ -0,0 +1,499 @@
# OneOS V2 Global Operation Actions Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Make OneOS V2 list action columns expose “查看/详情” first and one workflow action second, while moving management, history, and dangerous actions into the shared “更多” menu; migrate the vehicle asset list to that shared behavior.
**Architecture:** Keep `OperationActions` as the only renderer and menu owner. Split detail-entry recognition from history recognition in the flat-item adapter, then consume the public `view`, `edit`, and `more` inputs from the vehicle list. Update every normative document that currently describes the old “details in more” behavior.
**Tech Stack:** React 18, TypeScript 5.9, Vitest 4, Lucide React, Vite, CSS.
---
## File map
- Create `src/common/OperationActions.test.tsx`: focused regression tests for action ordering, detail/history classification, hidden actions, and empty menus.
- Modify `src/common/OperationActions.tsx`: render detail first, allow one workflow primary second, and keep history items in `more`.
- Modify `src/common/operation-actions-spec.md`: public component contract and migration guidance.
- Modify `src/resources/design-system/DESIGN.md`: global normative summary.
- Modify `src/resources/design-system/chapters/03-components.md`: component chapter summary and widths.
- Modify `src/prototypes/vm-shared/DESIGN.md`: full shared pattern, examples, and forbidden patterns.
- Modify `src/prototypes/vehicle-management/components/ListView.tsx`: remove the private menu and use `OperationActions`.
- Modify `src/prototypes/vehicle-management/style.css`: remove vehicle-only action-menu skin and size the sticky operation column for the shared component.
### Task 1: Lock the shared action contract with tests
**Files:**
- Create: `src/common/OperationActions.test.tsx`
- [ ] **Step 1: Write regression tests for classification and markup order**
Create the following test file:
```tsx
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { describe, expect, it, vi } from 'vitest';
import {
OperationActions,
splitOperationActions,
type OperationActionItem,
} from './OperationActions';
const action = (key: string, label: string): OperationActionItem => ({
key,
label,
onClick: vi.fn(),
});
describe('splitOperationActions', () => {
it('separates the detail entry from history actions', () => {
const items = [
action('history', '操作记录'),
action('edit', '编辑'),
action('view', '查看详情'),
action('owner', '设置运维负责人'),
];
const result = splitOperationActions(items);
expect(result.view?.label).toBe('查看详情');
expect(result.edit?.label).toBe('编辑');
expect(result.more.map((item) => item.label)).toEqual([
'操作记录',
'设置运维负责人',
]);
});
it('does not promote 查看记录 as a detail entry', () => {
const result = splitOperationActions([
action('viewRecords', '查看记录'),
action('process', '处置'),
]);
expect(result.view).toBeUndefined();
expect(result.process?.label).toBe('处置');
expect(result.more.map((item) => item.label)).toEqual(['查看记录']);
});
});
describe('OperationActions', () => {
it('renders detail before the workflow action and more', () => {
const html = renderToStaticMarkup(
<OperationActions
view={{ label: '查看', onClick: vi.fn() }}
edit={{ label: '编辑', onClick: vi.fn() }}
more={[action('owner', '设置运维负责人')]}
/>,
);
expect(html.indexOf('>查看<')).toBeLessThan(html.indexOf('>编辑<'));
expect(html.indexOf('>编辑<')).toBeLessThan(html.indexOf('aria-label="更多操作"'));
});
it('does not render more when all menu actions are hidden', () => {
const html = renderToStaticMarkup(
<OperationActions
view={{ label: '详情', onClick: vi.fn() }}
more={[{ ...action('owner', '设置运维负责人'), hidden: true }]}
/>,
);
expect(html).toContain('>详情<');
expect(html).not.toContain('aria-label="更多操作"');
});
});
```
- [ ] **Step 2: Run the focused tests and verify the old behavior fails**
Run:
```bash
npm run test:run -- src/common/OperationActions.test.tsx
```
Expected: at least the ordering and classification assertions fail because the current implementation puts `view` into “更多” when `edit` or `process` exists and treats history labels as `view`.
### Task 2: Implement the global shared-component behavior
**Files:**
- Modify: `src/common/OperationActions.tsx`
- Test: `src/common/OperationActions.test.tsx`
- [ ] **Step 1: Update the public contract and primary ordering**
Replace the `view` prop comment, primary-selection block, and `outsidePrimaries` construction with:
```tsx
/**
* 查看 / 详情:对象详情入口,固定在外侧首位。
* 历史型“查看记录 / 操作记录”应通过 more 传入。
*/
view?: OperationPrimaryAction;
```
```tsx
const visibleEdit = edit && !edit.hidden ? edit : undefined;
const visibleProcess = process && !process.hidden ? process : undefined;
const visibleView = view && !view.hidden ? view : undefined;
const outsidePrimaries = useMemo(() => {
const list: Array<{ kind: PrimaryKind; action: OperationPrimaryAction; defaultLabel: string }> =
[];
if (visibleView) {
list.push({
kind: 'view',
action: visibleView,
defaultLabel: visibleView.label || '详情',
});
}
if (visibleEdit) {
list.push({ kind: 'edit', action: visibleEdit, defaultLabel: '编辑' });
} else if (visibleProcess) {
list.push({
kind: 'process',
action: visibleProcess,
defaultLabel: visibleProcess.label || '处理',
});
}
return list.slice(0, 2);
}, [visibleEdit, visibleProcess, visibleView]);
```
Delete `hasWorkPrimary`, `viewInMore`, `viewOutside`, and the block that injects `visibleView` into `visibleMore`.
- [ ] **Step 2: Keep deduplication without hiding history actions**
Replace `visibleMore` with:
```tsx
const visibleMore = useMemo(() => {
const items: OperationActionItem[] = [];
more.forEach((item) => {
if (item.hidden) return;
if (visibleEdit && (item.key === 'edit' || item.label === '编辑' || item.label === '编辑合同')) {
return;
}
if (
visibleProcess &&
(item.key === 'process' ||
item.label === '处理' ||
item.label === '处置' ||
item.label === (visibleProcess.label || ''))
) {
return;
}
if (
visibleView &&
(item.key === 'view' ||
item.label === '查看' ||
item.label === '详情' ||
item.label === '查看详情')
) {
return;
}
items.push(item);
});
return items;
}, [more, visibleView, visibleEdit, visibleProcess]);
```
- [ ] **Step 3: Separate detail labels from history labels in the adapter**
Replace the view label set and view classification with:
```tsx
const DETAIL_LABELS = new Set(['详情', '查看', '查看详情']);
const HISTORY_LABELS = new Set(['查看记录', '操作记录']);
```
```tsx
const viewItem = visible.find(
(item) => item.key === 'view' || DETAIL_LABELS.has(item.label),
);
const more = visible.filter(
(item) =>
item !== editItem &&
item !== processItem &&
item !== viewItem,
);
```
Return `viewItem.label` unchanged. Preserve history items—including `key === 'viewRecords'`, `key === 'history'`, and `HISTORY_LABELS` matches—in `more`.
- [ ] **Step 4: Update the component-level summary comment**
Use:
```tsx
/**
* 列表操作列(强制):详情入口固定首位,编辑/处理外显一个,
* 历史、低频管理和危险操作进入更多。
*/
```
- [ ] **Step 5: Run the focused tests**
Run:
```bash
npm run test:run -- src/common/OperationActions.test.tsx
```
Expected: 4 tests pass.
- [ ] **Step 6: Run TypeScript checking**
Run:
```bash
npm run typecheck
```
Expected: exit code 0 with no TypeScript diagnostics.
### Task 3: Update every normative OneOS V2 document
**Files:**
- Modify: `src/resources/design-system/DESIGN.md`
- Modify: `src/resources/design-system/chapters/03-components.md`
- Modify: `src/prototypes/vm-shared/DESIGN.md`
- Modify: `src/common/operation-actions-spec.md`
- [ ] **Step 1: Replace the global §3.16 layout summary**
In `src/resources/design-system/DESIGN.md`, define:
```text
[查看/详情] [编辑/处理/处置] [⋮ 更多]
↑ 固定首位 ↑ 最多一个 ↑ 历史 / 管理 / 危险操作
```
State that detail entry is external, “查看记录 / 操作记录” remains in more, only one workflow action is external when detail exists, and two workflow actions are allowed only when there is no detail entry.
- [ ] **Step 2: Update the component chapter summary**
In `src/resources/design-system/chapters/03-components.md`, replace the old sentence with:
```markdown
- 操作列(强制)`OperationActions`:对象入口 `[查看/详情]` 固定首位,常用 `[编辑/处理]` 外显一个;历史、低频管理与危险操作进入 `[⋮ 更多]`;列宽 148184推荐 `fixed: 'right'`。详见 `vm-shared/DESIGN.md`
```
- [ ] **Step 3: Rewrite the shared full pattern**
In `src/prototypes/vm-shared/DESIGN.md`:
- change the diagram to the approved three-part order;
- define `查看/详情` as the object entry;
- define `查看记录/操作记录` as history and keep them in more;
- change the suggested width to `148184px`;
- update the example so `view={{ label: '查看详情', ... }}` opens the object and `more` contains a history action;
- replace the old forbidden-pattern text with prohibitions against hiding the detail entry, exposing history as the detail entry, or showing more than two primary actions.
- [ ] **Step 4: Rewrite the public component spec**
In `src/common/operation-actions-spec.md`, document the same ordering and replace the example with:
```tsx
<OperationActions
view={{ label: '查看', onClick: () => openDetail(record) }}
edit={canEdit ? { onClick: () => openEdit(record) } : undefined}
more={[
{ key: 'history', label: '操作记录', onClick: () => openHistory(record) },
]}
/>
```
- [ ] **Step 5: Scan for stale normative wording**
Run:
```bash
rg -n "查看详情.*更多|查看记录.*外显|低频.*查看详情|\\[编辑\\].*\\[处理\\]" \
src/resources/design-system \
src/prototypes/vm-shared/DESIGN.md \
src/common/operation-actions-spec.md
```
Expected: no statement contradicts the new rules. Historical background text is acceptable only when explicitly marked as the old behavior.
### Task 4: Migrate the vehicle asset operation column
**Files:**
- Modify: `src/prototypes/vehicle-management/components/ListView.tsx`
- Modify: `src/prototypes/vehicle-management/style.css`
- [ ] **Step 1: Import the shared component and remove private-menu imports**
Add the correct relative import:
```tsx
import { OperationActions } from '../../../common/OperationActions';
```
Remove `Eye`, `MoreHorizontal`, `ShieldAlert`, and `UserCog` from Lucide imports only if no remaining usage exists after migration. Keep imports still used elsewhere in `ListView.tsx`.
- [ ] **Step 2: Delete the private `ActionsMoreMenu` component**
Remove the complete `ActionsMoreMenu` function, including its local `open` state, click-outside listener, escape listener, and private menu markup.
- [ ] **Step 3: Replace both vehicle action-cell call sites**
At each current `<ActionsMoreMenu ... />` site, render:
```tsx
<OperationActions
view={{ label: '查看', onClick: () => onOpenDetail(row) }}
edit={{ label: '编辑', onClick: () => onEdit(row) }}
more={[
{
key: 'owner',
label: '设置运维负责人',
onClick: () => onOps(row),
},
]}
/>
```
If an existing local permission flag controls edit or operations management, pass `undefined` or `hidden: true` before rendering rather than creating an empty menu. Do not introduce new permission semantics.
- [ ] **Step 4: Remove the private menu skin**
Delete vehicle-only selectors whose sole owner was `ActionsMoreMenu`, including:
```css
.va-actions-more-wrap
.va-btn-more
.va-actions-menu
.va-actions-menu__item
.va-actions-menu__icon
.va-actions-menu__text
.va-actions-menu__title
.va-actions-menu__sub
```
Also remove `:has(.va-actions-menu)` stacking workarounds made obsolete by the portal-based shared menu. Keep unrelated table and sticky-column rules.
- [ ] **Step 5: Size and align the shared operation column**
Set the vehicle column styles to:
```css
.va-th-actions,
.va-td-actions {
min-width: 184px !important;
width: 184px !important;
max-width: 184px !important;
box-sizing: border-box;
}
.va-td-actions {
overflow: visible !important;
vertical-align: middle !important;
}
.va-td-actions .vm-operation-actions {
width: 100%;
justify-content: flex-start;
}
```
Use the same width in the `ListView.tsx` column definition or width map if it is also hard-coded there.
- [ ] **Step 6: Run static validation**
Run:
```bash
npm run typecheck
npm run test:run -- src/common/OperationActions.test.tsx
npm run build
```
Expected: all commands exit 0.
### Task 5: Browser verification
**Files:**
- Verify: `src/prototypes/vehicle-management/components/ListView.tsx`
- Verify: `src/common/OperationActions.tsx`
- Verify: `src/prototypes/vehicle-management/style.css`
- [ ] **Step 1: Start the local prototype**
Run:
```bash
npm run dev -- --host 127.0.0.1
```
Expected: Vite prints a local URL and remains running.
- [ ] **Step 2: Verify desktop behavior**
Open the vehicle asset list and confirm:
- every row shows `查看`, then `编辑`, then the ellipsis;
- `查看` enters the same detail flow as before;
- `编辑` opens the same edit flow as before;
- the ellipsis menu contains only `设置运维负责人`;
- the menu is not clipped by the sticky operation column or scrolling container;
- buttons remain on one line and the operation column is visually stable while horizontally scrolling.
- [ ] **Step 3: Verify keyboard and constrained width**
At desktop and a narrow viewport:
- use Tab to focus `查看`, `编辑`, and the ellipsis in order;
- press Enter or Space to open the menu;
- press Escape and confirm it closes;
- confirm no empty ellipsis appears when a permission-controlled menu item is absent;
- confirm the shared 44px hit areas remain usable and do not overlap.
- [ ] **Step 4: Record verification evidence**
Capture at least one desktop screenshot with the menu open and one narrow-viewport screenshot. Note the tested page path and any permission state used in the final handoff.
### Task 6: Final consistency and completion checks
**Files:**
- Verify: `docs/superpowers/specs/2026-07-26-global-operation-actions-design.md`
- Verify: all files modified in Tasks 14
- [ ] **Step 1: Run the full verification suite**
Run:
```bash
npm run test:run
npm run typecheck
npm run build
```
Expected: all commands exit 0.
- [ ] **Step 2: Re-run the normative consistency scan**
Run:
```bash
rg -n "查看详情.*更多|低频.*查看详情|查看记录.*外显" \
src/resources/design-system \
src/prototypes/vm-shared/DESIGN.md \
src/common/operation-actions-spec.md \
src/common/OperationActions.tsx
```
Expected: no contradictory active rule remains.
- [ ] **Step 3: Review the final change scope**
Because `/Users/sylvawong/oneos-v2` is not currently a Git repository, list changed files by explicit path and verify that `.superpowers/brainstorm/` artifacts are not included in the deliverable. Do not claim commits were created.