用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/mazrean/wsh2026 --skill wsh-dm-optimization命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Crok AI chat rendering optimization — SSE debouncing, ChatMessage memoization, Markdown re-render prevention
Home page performance optimization — Post defaultScope, SSR data reduction, lazy media hydration for WSH 2026 CaX app
Lazy loading modal containers and route components to reduce initial bundle size — CrokContainer, NewPostModalContainer
基于 SOC 职业分类
正在显示 SKILL.md
| name | wsh-dm-optimization |
| description | DM page and flow optimization — afterSave hook, message rendering, unread count queries for WSH 2026 |
DM一覧 scores TBT=13.80/30, DM詳細 scores TBT=10.80/30, and DM送信 flow TBT=0.00/25. Main bottleneck is the DirectMessage afterSave hook running heavy count queries synchronously.
Use this skill when optimizing DM-related pages, the DM send flow, or DirectMessage model hooks.
The current afterSave hook at server/src/models/DirectMessage.ts:75-107 performs three sequential operations:
// Current (heavy):
DirectMessage.addHook("afterSave", async (message) => {
// 1. Re-fetch the just-saved message (UNNECESSARY)
const directMessage = await DirectMessage.findByPk(message.get().id);
// 2. Fetch conversation to find receiver
const conversation = await DirectMessageConversation.findByPk(directMessage?.conversationId);
// 3. COUNT with JOIN across all conversations (EXPENSIVE)
const unreadCount = await DirectMessage.count({
distinct: true,
where: { senderId: { [Op.ne]: receiverId }, isRead: false },
include: [{ association: "conversation", where: {...}, required: true }],
});
});
Fix:
message.get() directly (step 1 eliminated)message.get().conversationId with DirectMessageConversation.unscoped().findByPk() (step 2 simplified)// Optimized count - avoid JOIN:
const conversationIds = await DirectMessageConversation.unscoped().findAll({
attributes: ['id'],
where: { [Op.or]: [{ initiatorId: receiverId }, { memberId: receiverId }] },
raw: true,
}).then(cs => cs.map(c => c.id));
const unreadCount = await DirectMessage.unscoped().count({
where: {
conversationId: { [Op.in]: conversationIds },
senderId: { [Op.ne]: receiverId },
isRead: false,
},
});
Or even simpler — defer the count computation entirely and emit the event without waiting:
// Fire-and-forget pattern:
DirectMessage.addHook("afterSave", async (message) => {
const data = message.get();
const conversation = await DirectMessageConversation.unscoped().findByPk(data.conversationId);
if (!conversation) return;
const receiverId = conversation.initiatorId === data.senderId
? conversation.memberId : conversation.initiatorId;
// Emit message event immediately (don't wait for count)
eventhub.emit(`dm:conversation/${conversation.id}:message`, message);
// Count asynchronously (non-blocking for the save operation)
setImmediate(async () => {
const unreadCount = await DirectMessage.unscoped().count({...});
eventhub.emit(`dm:unread/${receiverId}`, { unreadCount });
});
});
Impact: DM send TBT, DM detail TBT, DM list TBT all improve.
DirectMessagePage renders all messages in a .map() without individual memoization.
Impact: Minor TBT improvement for DM detail page.
| Pitfall | Symptom | Fix |
|---|---|---|
| Removing findByPk breaks sender include | Message sent without sender data | Use message.reload() only if sender data is needed in the event |
| Deferred count causes stale unread badge | Badge shows wrong number briefly | Acceptable trade-off for performance |
| unscoped() loses default ordering | Messages in wrong order | Add explicit order clause |
| Change | Visual Impact | Mitigation |
|---|---|---|
| Optimizing afterSave | None — server-side only | N/A |
| Memoizing messages | None — same output | Test DM detail VRT |
server/src/models/DirectMessage.tsserver/src/routes/api/direct_message.tsclient/src/containers/DirectMessageListContainer.tsxclient/src/containers/DirectMessageContainer.tsxDirectMessage.update() in the read endpoint