继续收口共享组件并迁移业务页面

补充 PlatformToggleRow 共享整行开关组件与测试

将多个结果页、工作台和账号页面改用共享 Platform chrome

更新展示页、组件库文档与共享决策记录
This commit is contained in:
2026-08-31 11:13:32 +08:00
parent 5d14af1324
commit aadb09a9ea
14 changed files with 131 additions and 95 deletions
@@ -7766,3 +7766,9 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在
- 基础件:`components/ui` 新增语义 `Label`、原生 `Checkbox``Skeleton` 和语义 `Table` 组合件;继续沿用项目自有 shadcn open-code 源码,不引入额外运行时依赖。
- 样式:筛选工具栏与可导航列表行的 hover/active/focus/disabled 反馈、移动端断点和独立宿主所需 chrome 放入共享样式;共享主题仍只消费 `--platform-*` token,不依赖网站总 CSS。
- 验证:相关共享/兼容组件 10 个测试文件共 36 个断言、类型检查、编码检查、定向 ESLint、`git diff --check` 和生产构建均通过。
## 2026-08-31 共享开关组件与业务页面直引迁移
- 决策:新增 `packages/shared/src/components/PlatformToggleRow.tsx`,统一承接白底整行 checkbox / status 开关的语义、状态胶囊和禁用态;`src/components/common/PlatformToggleRow.tsx` 仅保留兼容导出,展示页改为直接从共享 barrel 引入。
- 迁移:`Match3DResultView``PuzzleResultView``VisualNovelResultView``SquareHoleResultView``RpgCreationResultViewImpl``RpgCreationResultActionBar``RpgCreationAssetDebugPanel``CustomWorldCreationHub``BabyObjectMatchWorkspace``AccountModal``CreationAgentWorkspace` 直接消费共享 `Platform*` chrome,玩法专属资源 / 媒体 / 上传 / 弹窗组件继续留在业务层。
- 验证:共享 PlatformToggleRow 定向测试、相关前端类型检查、编码检查和 `git diff --check` 通过;未改变业务行为或后端契约。
@@ -29,11 +29,14 @@
- `PlatformProgressBar``PlatformSegmentedTabs``PlatformFieldLabel``PlatformSubpanel`
- `PlatformAsyncStatePanel``PlatformFilterToolbar``PlatformIconBadge``PlatformRuntimeStatusToast`
- `PlatformInfoBlock``PlatformNavigableListItem``PlatformStatGrid`
- `PlatformToggleRow`(整行 checkbox / 状态开关)
迁移约定:`Button``Modal``SegmentedTabs``Switch``Input``Textarea``Badge``Card` 的 canonical source 位于 `packages/shared/src/components/ui`,旧的 `@genarrative/shared/components` API 作为兼容适配层继续保留。`Button``Input``Badge` 使用 CVADialog/Tabs/Switch 使用按需 Radix primitive;原生 `SelectField` 暂不迁移,避免破坏现有 `<option>` 与表单事件合同。后续组件按使用面逐个迁移。
2026-08-28 平台 chrome 的无业务依赖组件继续完成实现收口:新增 `PlatformAsyncStatePanel``PlatformFilterToolbar``PlatformIconBadge``PlatformInfoBlock``PlatformNavigableListItem``PlatformRuntimeStatusToast``PlatformStatGrid`。这些组件的实现统一位于 `packages/shared/src/components``src/components/common` 中同名文件仅保留兼容导出,因此现有业务页面无需一次性改写导入路径,实际渲染已复用共享包实现。带 API、store、编辑器或素材解析副作用的组件仍留在业务层,后续按同一边界逐项评估。
2026-08-31 继续迁移:`PlatformToggleRow` 已收口到共享包,保留 `src/components/common/PlatformToggleRow.tsx` 兼容出口;`Match3DResultView``PuzzleResultView``VisualNovelResultView``SquareHoleResultView``RpgCreationResultViewImpl``RpgCreationResultActionBar``RpgCreationAssetDebugPanel``CustomWorldCreationHub``BabyObjectMatchWorkspace``AccountModal``CreationAgentWorkspace` 已将共享 chrome 组件改为直接从 `@genarrative/shared/components` 引入。玩法专属的媒体、上传、编辑器、弹窗和资源状态组件仍保留在业务 common 层。
同时提供 `Platform*` 别名,便于从现有平台组件命名迁移;别名不携带平台业务语义。
Web 宿主需要显式启用 Tailwind 4,并引入 `@genarrative/shared/styles.css``@genarrative/shared/theme.css`Tauri WebView 与网站共用这套源码,移动端 React Native 不消费 DOM/CSS 组件。筛选工具栏所需的 `platform-category-*` chrome 样式也已放入共享样式文件,独立宿主无需依赖网站 `src/index.css`。组件库不提供页面壳或业务流程。`components.json` 只用于 shadcn CLI 定位源码,不允许覆盖现有业务组件。
+10 -8
View File
@@ -1,3 +1,13 @@
import {
PlatformActionButton,
PlatformAsyncStatePanel,
PlatformEmptyState,
PlatformFieldLabel,
PlatformPillBadge,
PlatformStatusMessage,
PlatformSubpanel,
PlatformTextField,
} from '@genarrative/shared/components';
import { ArrowLeft } from 'lucide-react';
import {
type ReactNode,
@@ -15,14 +25,6 @@ import type {
AuthSessionSummary,
AuthUser,
} from '../../services/authService';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformTextField } from '../common/PlatformTextField';
import type { PlatformSettingsSection } from './AuthUiContext';
import { CaptchaChallengeField } from './CaptchaChallengeField';
import { PlatformAuthModalShell } from './PlatformAuthModalShell';
@@ -8,6 +8,15 @@ import {
} from 'lucide-react';
import type { ChangeEvent } from 'react';
import { useEffect, useRef, useState } from 'react';
import {
PlatformActionButton,
PlatformEmptyState,
PlatformIconButton,
PlatformProgressBar,
PlatformStatusMessage,
PlatformSubpanel,
PlatformTextField,
} from '@genarrative/shared/components';
import { HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES } from '../../../packages/shared/src/contracts/hostBridge';
import {
@@ -23,13 +32,6 @@ import {
importHostImageFile,
importHostTextFile,
} from '../../services/host-bridge/hostBridge';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformProgressBar } from '../common/PlatformProgressBar';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformTextField } from '../common/PlatformTextField';
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
export type CreationAgentAnchorView = {
@@ -351,7 +353,9 @@ function buildCreationAgentSessionMarkdown({
streamingReplyText: string;
draftText: string;
}) {
if (!hasCreationAgentExportableContent(session, streamingReplyText, draftText)) {
if (
!hasCreationAgentExportableContent(session, streamingReplyText, draftText)
) {
return null;
}
@@ -1042,9 +1046,7 @@ export function CreationAgentWorkspace({
type="button"
aria-label="发送"
disabled={
isBusy ||
isComposerFileTaskRunning ||
!draftText.trim()
isBusy || isComposerFileTaskRunning || !draftText.trim()
}
onClick={submit}
className={`inline-flex h-11 w-11 items-center justify-center rounded-full text-white disabled:cursor-not-allowed disabled:opacity-40 ${theme.userBubbleClass}`}
@@ -1,11 +1,13 @@
import { useEffect, useMemo, useState } from 'react';
import {
PlatformActionButton,
PlatformAsyncStatePanel,
PlatformEmptyState,
PlatformSubpanel,
} from '@genarrative/shared/components';
import { resolveSelectionStageFromPath } from '../../routing/appPageRoutes';
import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import type { PublishShareModalPayload } from '../common/publishShareModalModel';
import type {
PlatformCreationTypeCard,
@@ -273,7 +275,9 @@ export function CustomWorldCreationHub({
<CustomWorldWorkCard
key={`${item.kind}-${item.id}`}
item={item}
previousMetricValues={metricSnapshot[buildWorkMetricCacheItemKey(item)]}
previousMetricValues={
metricSnapshot[buildWorkMetricCacheItemKey(item)]
}
onOpen={() => {
handleOpenShelfItem(item);
}}
@@ -1,16 +1,18 @@
import { Gift, Loader2, WandSparkles } from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import {
PlatformActionButton,
PlatformFieldLabel,
PlatformIconBadge,
PlatformPillBadge,
PlatformStatusMessage,
PlatformSubpanel,
PlatformTextField,
} from '@genarrative/shared/components';
import type { CreateBabyObjectMatchDraftRequest } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import { validateBabyObjectMatchItemNames } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformIconBadge } from '../common/PlatformIconBadge';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformTextField } from '../common/PlatformTextField';
type BabyObjectMatchWorkspaceProps = {
isBusy?: boolean;
@@ -21,6 +21,19 @@ import {
useState,
} from 'react';
import {
PlatformActionButton,
PlatformFieldLabel,
PlatformIconButton,
PlatformPillBadge,
PlatformProgressBar,
PlatformSegmentedTabs,
PlatformStatGrid,
PlatformStatusMessage,
PlatformSubpanel,
PlatformTextField,
} from '@genarrative/shared/components';
import type { CreationAudioAsset } from '../../../packages/shared/src/contracts/creationAudio';
import type { Match3DResultDraft } from '../../../packages/shared/src/contracts/match3dAgent';
import type {
@@ -59,23 +72,13 @@ import {
puzzleReferenceImageDataUrlToFile,
readPuzzleReferenceImageAsDataUrl,
} from '../../services/puzzleReferenceImage';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformMediaTileGrid } from '../common/PlatformMediaTileGrid';
import { PlatformMudPointConfirmDialog } from '../common/PlatformMudPointConfirmDialog';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformPillSwitch } from '../common/PlatformPillSwitch';
import { PlatformProgressBar } from '../common/PlatformProgressBar';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import { PlatformStatGrid } from '../common/PlatformStatGrid';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformTagEditor } from '../common/PlatformTagEditor';
import { PlatformTextField } from '../common/PlatformTextField';
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
import { useMudPointConfirmController } from '../common/useMudPointConfirmController';
@@ -1559,7 +1562,10 @@ function Match3DCoverImageEditor({
}
}}
onKeyDown={(event) => {
if (isGenerating || (event.key !== 'Enter' && event.key !== ' ')) {
if (
isGenerating ||
(event.key !== 'Enter' && event.key !== ' ')
) {
return;
}
event.preventDefault();
@@ -3121,7 +3127,9 @@ export function Match3DResultView({
if (!importedImageFile) {
return;
}
await setCoverImageFromFile(hostMatch3DImageResultToFile(importedImageFile));
await setCoverImageFromFile(
hostMatch3DImageResultToFile(importedImageFile),
);
})();
};
@@ -8,6 +8,20 @@ import {
} from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import {
PlatformActionButton,
PlatformEmptyState,
PlatformFieldLabel,
PlatformIconBadge,
PlatformIconButton,
PlatformPillBadge,
PlatformProgressBar,
PlatformSegmentedTabs,
PlatformStatusMessage,
PlatformSubpanel,
PlatformTextField,
} from '@genarrative/shared/components';
import type { CreativeDraftEditResult } from '../../../packages/shared/src/contracts/creativeAgent';
import type { PuzzleAgentActionRequest } from '../../../packages/shared/src/contracts/puzzleAgentActions';
import type {
@@ -19,21 +33,10 @@ import { updatePuzzleWork } from '../../services/puzzle-works';
import { getPuzzleHistoryAssetReferenceLabel } from '../../services/puzzle-works/puzzleHistoryAsset';
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
import { CreativeImageInputPanel } from '../common/CreativeImageInputPanel';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformIconBadge } from '../common/PlatformIconBadge';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformMudPointConfirmDialog } from '../common/PlatformMudPointConfirmDialog';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformProgressBar } from '../common/PlatformProgressBar';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformTagEditor } from '../common/PlatformTagEditor';
import { PlatformTextField } from '../common/PlatformTextField';
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
import PuzzleHistoryAssetPickerDialog from '../unified-creation/shared/PuzzleHistoryAssetPickerDialog';
@@ -766,9 +769,7 @@ function PuzzleLevelDetailDialog({
htmlFor={`puzzle-level-name-${level.levelId}`}
className="contents"
>
<PlatformFieldLabel variant="section">
</PlatformFieldLabel>
<PlatformFieldLabel variant="section"></PlatformFieldLabel>
</label>
<PlatformTextField
id={`puzzle-level-name-${level.levelId}`}
@@ -835,8 +836,7 @@ function PuzzleLevelDetailDialog({
submitDisabled={
isBusy ||
generationProgress.isGenerating ||
(!level.pictureDescription.trim() &&
!effectiveReferenceImageSrc)
(!level.pictureDescription.trim() && !effectiveReferenceImageSrc)
}
labels={{
imageField: '画面图',
@@ -1,5 +1,10 @@
import type { MouseEvent } from 'react';
import { useEffect, useMemo, useState } from 'react';
import {
PlatformEmptyState,
PlatformPillBadge,
PlatformSubpanel,
} from '@genarrative/shared/components';
import { resolveAssetReadUrl } from '../../services/assetReadUrlService';
import {
@@ -7,9 +12,6 @@ import {
openHostExternalUrl,
} from '../../services/host-bridge/hostBridge';
import type { CustomWorldProfile } from '../../types';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
type RpgCreationAssetDebugEntry = {
id: string;
@@ -1,12 +1,14 @@
import { type ReactNode, useState } from 'react';
import {
PlatformActionButton,
PlatformFieldLabel,
PlatformPillBadge,
PlatformStatusMessage,
PlatformSubpanel,
} from '@genarrative/shared/components';
import { resolveCustomWorldCoverPresentation } from '../../services/customWorldCover';
import type { CustomWorldProfile } from '../../types';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
import { CustomWorldCoverArtwork } from '../CustomWorldCoverArtwork';
@@ -1,10 +1,12 @@
import { useEffect, useMemo, useRef, useState } from 'react';
import {
PlatformProgressBar,
PlatformStatusMessage,
} from '@genarrative/shared/components';
import { rpgCreationAssetClient } from '../../services/rpg-creation/rpgCreationAssetClient';
import type { Character, CustomWorldProfile } from '../../types';
import { PlatformDangerConfirmDialog } from '../common/PlatformDangerConfirmDialog';
import { PlatformProgressBar } from '../common/PlatformProgressBar';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import {
CustomWorldEntityCatalog,
type ResultTab,
@@ -17,6 +17,7 @@ import {
PlatformNavigableListItem,
PlatformRuntimeStatusToast,
PlatformStatGrid,
PlatformToggleRow,
ProgressBar,
SegmentedTabs,
SelectField,
@@ -41,7 +42,6 @@ import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformMediaTileGrid } from '../common/PlatformMediaTileGrid';
import { PlatformTagEditor } from '../common/PlatformTagEditor';
import { PlatformToggleRow } from '../common/PlatformToggleRow';
import { PlatformUploadPreviewCard } from '../common/PlatformUploadPreviewCard';
import { PlatformUploadTile } from '../common/PlatformUploadTile';
@@ -1,4 +1,14 @@
import { PlatformStatGrid } from '@genarrative/shared/components';
import {
PlatformActionButton,
PlatformFieldLabel,
PlatformIconButton,
PlatformPillBadge,
PlatformStatGrid,
PlatformStatusMessage,
PlatformSubpanel,
PlatformSelectField,
PlatformTextField,
} from '@genarrative/shared/components';
import {
CheckCircle2,
ImagePlus,
@@ -32,19 +42,9 @@ import {
type SquareHoleImageAssetKind,
updateSquareHoleWork,
} from '../../services/square-hole-works';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformPillBadge } from '../common/PlatformPillBadge';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import {
PlatformSelectField,
PlatformTextField,
} from '../common/PlatformTextField';
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
type SquareHoleResultViewProps = {
@@ -16,6 +16,19 @@ import {
import { type ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import {
PlatformActionButton,
PlatformEmptyState,
PlatformFieldLabel,
PlatformIconButton,
PlatformSegmentedTabs,
PlatformStatusMessage,
PlatformSubpanel,
PlatformToggleRow,
PlatformSelectField,
PlatformTextField,
} from '@genarrative/shared/components';
import type {
VisualNovelCharacterDraft,
VisualNovelCharacterRole,
@@ -45,22 +58,10 @@ import {
type VisualNovelUploadAssetKind,
} from '../../services/visual-novel-creation';
import { useAuthUi } from '../auth/AuthUiContext';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard';
import { PlatformBackActionButton } from '../common/PlatformBackActionButton';
import { PlatformEmptyState } from '../common/PlatformEmptyState';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import { PlatformIconButton } from '../common/PlatformIconButton';
import { PlatformMediaFrame } from '../common/PlatformMediaFrame';
import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton';
import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import { PlatformSubpanel } from '../common/PlatformSubpanel';
import {
PlatformSelectField,
PlatformTextField,
} from '../common/PlatformTextField';
import { PlatformToggleRow } from '../common/PlatformToggleRow';
import { PlatformToolModalShell } from '../common/PlatformToolModalShell';
import { visualNovelLocalPreviewDraft } from '../visual-novel-runtime/visualNovelLocalPreviewData';
@@ -439,9 +440,11 @@ function VisualNovelAssetPickerDialog({
const [isGeneratingImage, setIsGeneratingImage] = useState(false);
const [error, setError] = useState<string | null>(null);
const canImportHostImage =
config.previewTone === 'image' && canUseNativeHostCapability('file.importImage');
config.previewTone === 'image' &&
canUseNativeHostCapability('file.importImage');
const canImportHostAudio =
config.previewTone === 'audio' && canUseNativeHostCapability('file.importAudio');
config.previewTone === 'audio' &&
canUseNativeHostCapability('file.importAudio');
const canImportHostAsset = canImportHostImage || canImportHostAudio;
useEffect(() => {