Prune stale docs and update .hermes content

Delete a large set of outdated documentation (many files under docs/ and .hermes/plans/, including audits, design, prd, technical, planning, assets, and todos). Update and consolidate .hermes content: refresh shared-memory pages (decision-log, development-workflow, document-map, pitfalls, project-overview, team-conventions) and several skills/references under .hermes/skills. Also modify AGENTS.md, README.md, UI_CODING_STANDARD.md, docs/README.md and .encoding-check-ignore. Purpose: clean up stale planning/audit material and keep current hermes documentation and related top-level docs in sync.
This commit is contained in:
2026-05-15 06:24:07 +08:00
parent 2eded08bc7
commit 3cb3efb4d0
708 changed files with 4033 additions and 142328 deletions

View File

@@ -1,6 +1,6 @@
import {
ArrowRight,
AlertCircle,
ArrowRight,
BookOpen,
Camera,
CheckCircle2,
@@ -16,8 +16,8 @@ import {
Heart,
LogIn,
MessageCircle,
Pencil,
Palette,
Pencil,
Plus,
Search,
Settings,
@@ -47,22 +47,22 @@ import communityQqQrImage from '../../../media/social-media-group/qq.png';
import communityWechatQrImage from '../../../media/social-media-group/wechat.png';
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
import type {
ConfirmWechatProfileRechargeOrderResponse,
CustomWorldLibraryEntry,
PlatformBrowseHistoryEntry,
ConfirmWechatProfileRechargeOrderResponse,
ProfileDashboardCardKey,
ProfileDashboardSummary,
ProfilePlayedWorkSummary,
ProfilePlayStatsResponse,
ProfileReferralInviteCenterResponse,
ProfileRechargeCenterResponse,
ProfileRechargeProduct,
WechatMiniProgramPayParams,
ProfileReferralInviteCenterResponse,
ProfileSaveArchiveSummary,
ProfileTaskCenterResponse,
ProfileTaskItem,
ProfileWalletLedgerResponse,
RedeemProfileRewardCodeResponse,
WechatMiniProgramPayParams,
} from '../../../packages/shared/src/contracts/runtime';
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
@@ -77,8 +77,8 @@ import {
claimRpgProfileTaskReward,
confirmWechatRpgProfileRechargeOrder,
createRpgProfileRechargeOrder,
getRpgProfileReferralInviteCenter,
getRpgProfileRechargeCenter,
getRpgProfileReferralInviteCenter,
getRpgProfileTasks,
getRpgProfileWalletLedger,
redeemRpgProfileReferralInviteCode,
@@ -242,6 +242,15 @@ type DiscoverChannel =
| 'ranking'
| 'edutainment';
type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like';
type PlatformCategoryKindFilter =
| 'all'
| 'puzzle'
| 'match3d'
| 'square-hole'
| 'visual-novel'
| 'big-fish'
| 'custom-world';
type PlatformCategorySortMode = 'composite' | 'latest' | 'play' | 'like';
const COMMUNITY_QR_CODES = [
{
@@ -269,6 +278,27 @@ const EDUTAINMENT_DISCOVER_CHANNEL = {
id: 'edutainment',
label: EDUTAINMENT_WORK_TAG,
} as const;
const PLATFORM_CATEGORY_KIND_FILTERS: Array<{
id: PlatformCategoryKindFilter;
label: string;
}> = [
{ id: 'all', label: '全部' },
{ id: 'puzzle', label: '拼图' },
{ id: 'match3d', label: '抓鹅' },
{ id: 'square-hole', label: '方洞' },
{ id: 'visual-novel', label: '视觉' },
{ id: 'big-fish', label: '大鱼' },
{ id: 'custom-world', label: 'RPG' },
];
const PLATFORM_CATEGORY_SORT_OPTIONS: Array<{
id: PlatformCategorySortMode;
label: string;
}> = [
{ id: 'composite', label: '综合' },
{ id: 'latest', label: '最新' },
{ id: 'play', label: '游玩' },
{ id: 'like', label: '点赞' },
];
const BABY_LOVE_DRAWING_DEFAULT_CARD = {
title: '宝贝爱画',
subtitle: '空白画板',
@@ -1391,6 +1421,123 @@ function PlatformCategoryGameItem({
);
}
function PlatformCategoryFilterDialog({
kindFilter,
sortMode,
resultCount,
onKindFilterChange,
onSortModeChange,
onReset,
onClose,
}: {
kindFilter: PlatformCategoryKindFilter;
sortMode: PlatformCategorySortMode;
resultCount: number;
onKindFilterChange: (filter: PlatformCategoryKindFilter) => void;
onSortModeChange: (mode: PlatformCategorySortMode) => void;
onReset: () => void;
onClose: () => void;
}) {
return (
<div className="platform-modal-backdrop fixed inset-0 z-[90] flex items-end justify-center px-3 py-4 sm:items-center">
<button
type="button"
aria-label="关闭分类筛选"
className="absolute inset-0"
onClick={onClose}
/>
<div
role="dialog"
aria-modal="true"
aria-label="分类筛选"
className="platform-modal-shell platform-category-filter-dialog relative w-full max-w-md overflow-hidden rounded-[1.35rem]"
>
<div className="flex min-w-0 items-center justify-between gap-3 px-4 py-4">
<div className="min-w-0">
<div className="text-base font-black text-[var(--platform-text-strong)]">
</div>
<div className="mt-0.5 text-xs font-semibold text-[var(--platform-text-soft)]">
{resultCount}
</div>
</div>
<button
type="button"
onClick={onClose}
className="platform-modal-close flex h-9 w-9 items-center justify-center rounded-full"
aria-label="关闭"
>
<XCircle className="h-5 w-5" />
</button>
</div>
<div className="grid gap-4 px-4 pb-4">
<div className="grid gap-2">
<div className="text-xs font-black text-[var(--platform-text-soft)]">
</div>
<div className="platform-category-filter-dialog__options">
{PLATFORM_CATEGORY_KIND_FILTERS.map((option) => {
const active = option.id === kindFilter;
return (
<button
key={option.id}
type="button"
onClick={() => onKindFilterChange(option.id)}
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
>
{option.label}
</button>
);
})}
</div>
</div>
<div className="grid gap-2">
<div className="text-xs font-black text-[var(--platform-text-soft)]">
</div>
<div className="platform-category-filter-dialog__options">
{PLATFORM_CATEGORY_SORT_OPTIONS.map((option) => {
const active = option.id === sortMode;
return (
<button
key={option.id}
type="button"
onClick={() => onSortModeChange(option.id)}
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
>
{option.label}
</button>
);
})}
</div>
</div>
</div>
<div className="platform-category-filter-dialog__actions">
<button
type="button"
onClick={onReset}
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--secondary"
>
</button>
<button
type="button"
onClick={onClose}
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--primary"
>
</button>
</div>
</div>
</div>
);
}
function buildPublicCategoryGroups(
featuredEntries: PlatformPublicGalleryCard[],
latestEntries: PlatformPublicGalleryCard[],
@@ -1830,6 +1977,64 @@ function getPlatformCategoryCompositeScore(entry: PlatformPublicGalleryCard) {
);
}
function getPlatformCategoryKindFilter(entry: PlatformPublicGalleryCard) {
if (isPuzzleGalleryEntry(entry)) {
return 'puzzle';
}
if (isMatch3DGalleryEntry(entry)) {
return 'match3d';
}
if (isSquareHoleGalleryEntry(entry)) {
return 'square-hole';
}
if (isVisualNovelGalleryEntry(entry)) {
return 'visual-novel';
}
if (isBigFishGalleryEntry(entry)) {
return 'big-fish';
}
return 'custom-world';
}
function matchesPlatformCategoryKindFilter(
entry: PlatformPublicGalleryCard,
kindFilter: PlatformCategoryKindFilter,
) {
return (
kindFilter === 'all' || getPlatformCategoryKindFilter(entry) === kindFilter
);
}
function sortPlatformCategoryEntries(
entries: PlatformPublicGalleryCard[],
sortMode: PlatformCategorySortMode,
) {
return [...entries].sort((left, right) => {
if (sortMode === 'latest') {
return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
}
const metricDiff =
sortMode === 'play'
? getPlatformWorldPlayCount(right) - getPlatformWorldPlayCount(left)
: sortMode === 'like'
? getPlatformWorldLikeCount(right) - getPlatformWorldLikeCount(left)
: getPlatformCategoryCompositeScore(right) -
getPlatformCategoryCompositeScore(left);
if (metricDiff !== 0) {
return metricDiff;
}
return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
});
}
function getPlatformCategoryPrimaryMetric(entry: PlatformPublicGalleryCard) {
const likeCount = getPlatformWorldLikeCount(entry);
if (likeCount > 0) {
@@ -3463,6 +3668,12 @@ export function RpgEntryHomeView({
const [selectedCategoryTag, setSelectedCategoryTag] = useState<string | null>(
null,
);
const [categoryKindFilter, setCategoryKindFilter] =
useState<PlatformCategoryKindFilter>('all');
const [categorySortMode, setCategorySortMode] =
useState<PlatformCategorySortMode>('composite');
const [isCategoryFilterPanelOpen, setIsCategoryFilterPanelOpen] =
useState(false);
const [discoverChannel, setDiscoverChannel] =
useState<DiscoverChannel>('recommend');
const mobileDiscoverFeedRef = useRef<HTMLElement | null>(null);
@@ -3586,17 +3797,24 @@ export function RpgEntryHomeView({
return [];
}
return [...activeCategoryGroup.entries].sort((left, right) => {
const scoreDiff =
getPlatformCategoryCompositeScore(right) -
getPlatformCategoryCompositeScore(left);
if (scoreDiff !== 0) {
return scoreDiff;
}
return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
});
}, [activeCategoryGroup]);
return sortPlatformCategoryEntries(
activeCategoryGroup.entries.filter((entry) =>
matchesPlatformCategoryKindFilter(entry, categoryKindFilter),
),
categorySortMode,
);
}, [activeCategoryGroup, categoryKindFilter, categorySortMode]);
const activeCategoryRawCount = activeCategoryGroup?.entries.length ?? 0;
const activeCategoryFilterLabel =
PLATFORM_CATEGORY_KIND_FILTERS.find(
(option) => option.id === categoryKindFilter,
)?.label ?? '全部';
const activeCategorySortLabel =
PLATFORM_CATEGORY_SORT_OPTIONS.find(
(option) => option.id === categorySortMode,
)?.label ?? '综合';
const activeCategoryFilterCount = activeCategoryEntries.length;
const categoryFilterApplied = categoryKindFilter !== 'all';
const visibleTabs = useMemo<PlatformHomeTab[]>(
() =>
isAuthenticated
@@ -3928,7 +4146,7 @@ export function RpgEntryHomeView({
setIsWalletLedgerOpen(true);
loadWalletLedger();
};
const loadRechargeCenter = () => {
const loadRechargeCenter = useCallback(() => {
setRechargeError(null);
setIsLoadingRechargeCenter(true);
void getRpgProfileRechargeCenter()
@@ -3940,7 +4158,7 @@ export function RpgEntryHomeView({
);
})
.finally(() => setIsLoadingRechargeCenter(false));
};
}, []);
const refreshRechargeState = useCallback(
() => {
loadRechargeCenter();
@@ -4301,6 +4519,18 @@ export function RpgEntryHomeView({
const submitMobileSearch = () => {
submitWorkSearch(mobileSearchKeyword);
};
const cycleCategorySortMode = () => {
const currentIndex = PLATFORM_CATEGORY_SORT_OPTIONS.findIndex(
(option) => option.id === categorySortMode,
);
const nextIndex =
currentIndex >= 0
? (currentIndex + 1) % PLATFORM_CATEGORY_SORT_OPTIONS.length
: 0;
setCategorySortMode(
PLATFORM_CATEGORY_SORT_OPTIONS[nextIndex]?.id ?? 'composite',
);
};
const desktopHeroEntry =
featuredShelf[0] ?? generalLatestEntries[0] ?? myEntries[0] ?? null;
const desktopHeroCover = desktopHeroEntry
@@ -4904,12 +5134,18 @@ export function RpgEntryHomeView({
<div className="platform-category-filter-row">
<button
type="button"
onClick={() => setIsCategoryFilterPanelOpen(true)}
aria-haspopup="dialog"
className="platform-category-filter-button"
>
<SlidersHorizontal className="h-4 w-4" />
<span></span>
<span>
{categoryFilterApplied
? activeCategoryFilterLabel
: '筛选'}
</span>
<span className="platform-category-filter-button__count">
{activeCategoryGroup.entries.length}
{activeCategoryFilterCount}
</span>
</button>
<span className="platform-category-filter-divider" />
@@ -4933,22 +5169,27 @@ export function RpgEntryHomeView({
<button
type="button"
onClick={cycleCategorySortMode}
className="platform-category-sort-button"
>
<span></span>
<span>{activeCategorySortLabel}</span>
<ChevronDown className="h-3.5 w-3.5" />
</button>
<div className="platform-category-game-list">
{activeCategoryEntries.map((entry) => (
<PlatformCategoryGameItem
key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}`}
entry={entry}
categoryTag={activeCategoryGroup.tag}
onClick={() => onOpenGalleryDetail(entry)}
/>
))}
</div>
{activeCategoryEntries.length > 0 ? (
<div className="platform-category-game-list">
{activeCategoryEntries.map((entry) => (
<PlatformCategoryGameItem
key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
entry={entry}
categoryTag={activeCategoryGroup.tag}
onClick={() => onOpenGalleryDetail(entry)}
/>
))}
</div>
) : (
<EmptyShelf text="当前筛选下没有作品。" />
)}
</>
) : (
<EmptyShelf text="公开广场暂时还没有可分类的作品。" />
@@ -5069,35 +5310,63 @@ export function RpgEntryHomeView({
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
{isLoadingPlatform ? (
<EmptyShelf text="正在读取作品分类..." />
) : activeCategoryGroup && desktopCategoryGrid.length > 0 ? (
) : activeCategoryGroup && activeCategoryRawCount > 0 ? (
<>
<div className="mb-4 flex min-w-0 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
<div className="mb-4 flex min-w-0 items-center gap-2">
<button
type="button"
onClick={() => setIsCategoryFilterPanelOpen(true)}
aria-haspopup="dialog"
className="platform-category-filter-button"
>
<SlidersHorizontal className="h-4 w-4" />
<span>
{categoryFilterApplied ? activeCategoryFilterLabel : '筛选'}
</span>
<span className="platform-category-filter-button__count">
{activeCategoryFilterCount}
</span>
</button>
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
return (
<button
key={`${group.tag}:desktop-discover-category`}
type="button"
onClick={() => setSelectedCategoryTag(group.tag)}
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<div className="grid gap-4 xl:grid-cols-3">
{desktopCategoryGrid.map((entry) => (
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:desktop-discover-category:${activeCategoryGroup.tag}`}
entry={entry}
onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
/>
))}
return (
<button
key={`${group.tag}:desktop-discover-category`}
type="button"
onClick={() => setSelectedCategoryTag(group.tag)}
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<button
type="button"
onClick={cycleCategorySortMode}
className="platform-category-sort-button shrink-0"
>
<span>{activeCategorySortLabel}</span>
<ChevronDown className="h-3.5 w-3.5" />
</button>
</div>
{desktopCategoryGrid.length > 0 ? (
<div className="grid gap-4 xl:grid-cols-3">
{desktopCategoryGrid.map((entry) => (
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:desktop-discover-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
entry={entry}
onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
/>
))}
</div>
) : (
<EmptyShelf text="当前筛选下没有作品。" />
)}
</>
) : (
<EmptyShelf text="暂时还没有可分类的作品。" />
@@ -5747,35 +6016,65 @@ export function RpgEntryHomeView({
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
{isLoadingPlatform ? (
<EmptyShelf text="正在读取作品分类..." />
) : activeCategoryGroup && desktopCategoryGrid.length > 0 ? (
) : activeCategoryGroup && activeCategoryRawCount > 0 ? (
<>
<div className="mb-4 flex min-w-0 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
<div className="mb-4 flex min-w-0 items-center gap-2">
<button
type="button"
onClick={() => setIsCategoryFilterPanelOpen(true)}
aria-haspopup="dialog"
className="platform-category-filter-button"
>
<SlidersHorizontal className="h-4 w-4" />
<span>
{categoryFilterApplied
? activeCategoryFilterLabel
: '筛选'}
</span>
<span className="platform-category-filter-button__count">
{activeCategoryFilterCount}
</span>
</button>
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
{categoryGroups.map((group) => {
const active = group.tag === activeCategoryGroup.tag;
return (
<button
key={`${group.tag}:desktop-category`}
type="button"
onClick={() => setSelectedCategoryTag(group.tag)}
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<div className="grid gap-4 xl:grid-cols-3">
{desktopCategoryGrid.map((entry) => (
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:desktop-category:${activeCategoryGroup.tag}`}
entry={entry}
onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
/>
))}
return (
<button
key={`${group.tag}:desktop-category`}
type="button"
onClick={() => setSelectedCategoryTag(group.tag)}
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
>
{group.tag}
</button>
);
})}
</div>
<button
type="button"
onClick={cycleCategorySortMode}
className="platform-category-sort-button shrink-0"
>
<span>{activeCategorySortLabel}</span>
<ChevronDown className="h-3.5 w-3.5" />
</button>
</div>
{desktopCategoryGrid.length > 0 ? (
<div className="grid gap-4 xl:grid-cols-3">
{desktopCategoryGrid.map((entry) => (
<WorldCard
key={`${buildPublicGalleryCardKey(entry)}:desktop-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
entry={entry}
onClick={() => openRecommendGalleryDetail(entry)}
className="w-full min-w-0"
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
/>
))}
</div>
) : (
<EmptyShelf text="当前筛选下没有作品。" />
)}
</>
) : (
<EmptyShelf text="暂时还没有可分类的作品。" />
@@ -5885,6 +6184,20 @@ export function RpgEntryHomeView({
onClose={() => setRechargePaymentResult(null)}
/>
) : null;
const categoryFilterDialog: ReactNode = isCategoryFilterPanelOpen ? (
<PlatformCategoryFilterDialog
kindFilter={categoryKindFilter}
sortMode={categorySortMode}
resultCount={activeCategoryFilterCount}
onKindFilterChange={setCategoryKindFilter}
onSortModeChange={setCategorySortMode}
onReset={() => {
setCategoryKindFilter('all');
setCategorySortMode('composite');
}}
onClose={() => setIsCategoryFilterPanelOpen(false)}
/>
) : null;
if (!isDesktopLayout) {
const isMobileRecommendTab = activeTab === 'home';
@@ -5969,6 +6282,7 @@ export function RpgEntryHomeView({
{rewardCodeModal}
{rechargeModal}
{rechargePaymentResultModal}
{categoryFilterDialog}
{isTaskCenterOpen ? (
<ProfileTaskCenterModal
center={taskCenter}
@@ -6101,6 +6415,7 @@ export function RpgEntryHomeView({
{rewardCodeModal}
{rechargeModal}
{rechargePaymentResultModal}
{categoryFilterDialog}
{isTaskCenterOpen ? (
<ProfileTaskCenterModal
center={taskCenter}