Commit 2ef1a8af by luoqi

feat(助手): P4 草稿状态机 —— 出新版即作废旧版,作废态显式标注且不可确认

产品定(2026-08-12):作废发生在**新草稿生成时**,不是在确认时。
否则主管面前会同时摆着两张都能点确认的卡,点错就是分错一批人。

- state: 'pending' → 'active' | 'superseded' | 'confirmed' | 'cancelled'
  ️ 顺带解掉一个命名撞车:原 state:'pending' 与「待分配」的 group:'pending'
  同名不同义,读代码时极易混。
- 新草稿到达时**跨消息**扫描,把此前所有 active 作废;confirmed/cancelled
  是终态, 不许被回退。
- edit 指令改为落到**唯一那张 active**, 不再是"最后一张" —— 重出一版后
  最后一张可能正是刚作废的,改上去主管一点反应都看不到。
- 作废态:顶部横幅 + 底部说明 + 确认按钮禁用 + 降到 60% 不透明度。
   不隐藏、 不折叠 —— 他要能看见自己出过几版、哪一版才是当前的。
  ️ 顶部横幅不能省:确认单可以很长,视线停在上半部分时看不到底部那句。

代码组织(按 dev-plan §三):
- 数据模型 + 纯函数操作抽到 chat-blocks.ts(use-assistant-chat 534 → 479 行)。
  草稿状态机的失败形态全是静默的,端到端点一遍代价高且不稳 —— 抽成纯函数锁住。
- pac-web 补 vitest(此前无任何测试基建),8 条回归覆盖跨消息作废、终态不回退、
  同 id 重推清空 edits、findActiveSheet 不退化成"最后一张"。

tsc(service/web) 干净;pac-web build 通过;vitest 8 passed;jest 1212 passed。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
parent 6658baca
...@@ -8,7 +8,8 @@ ...@@ -8,7 +8,8 @@
"start": "next start -p 3100", "start": "next start -p 3100",
"lint": "eslint .", "lint": "eslint .",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"clean": "rm -rf .next .turbo" "clean": "rm -rf .next .turbo",
"test": "vitest run"
}, },
"dependencies": { "dependencies": {
"@pac/types": "workspace:*", "@pac/types": "workspace:*",
...@@ -46,6 +47,7 @@ ...@@ -46,6 +47,7 @@
"eslint": "^9.17.0", "eslint": "^9.17.0",
"eslint-config-next": "^16.2.4", "eslint-config-next": "^16.2.4",
"tailwindcss": "^4.2.4", "tailwindcss": "^4.2.4",
"typescript": "^5.9.3" "typescript": "^5.9.3",
"vitest": "^3.2.7"
} }
} }
...@@ -242,7 +242,8 @@ export function AssignmentConfirmSheet({ ...@@ -242,7 +242,8 @@ export function AssignmentConfirmSheet({
}: { }: {
requestId: string; requestId: string;
sheet: AssignmentProposal; sheet: AssignmentProposal;
state: 'pending' | 'confirmed' | 'cancelled'; /** 草稿状态机,见 use-assistant-chat 的 Block 注释。`superseded` = 已被新版顶掉 */
state: 'active' | 'superseded' | 'confirmed' | 'cancelled';
assignmentId?: string; assignmentId?: string;
/** 落库成功 → 交给外层把卡片切终态 + 往消息流注入一条文本(补模型记忆) */ /** 落库成功 → 交给外层把卡片切终态 + 往消息流注入一条文本(补模型记忆) */
/** `modelSummary` = 同一句话的**喂模型版**(带完整批次 id,界面不显示) */ /** `modelSummary` = 同一句话的**喂模型版**(带完整批次 id,界面不显示) */
...@@ -370,7 +371,14 @@ export function AssignmentConfirmSheet({ ...@@ -370,7 +371,14 @@ export function AssignmentConfirmSheet({
const done = state === 'confirmed'; const done = state === 'confirmed';
/// 助手撤的整批(state 由 tool_result 切过来);本地撤的走 revoked /// 助手撤的整批(state 由 tool_result 切过来);本地撤的走 revoked
const cancelled = state === 'cancelled'; const cancelled = state === 'cancelled';
const readOnly = done || cancelled || submitting; /**
* ⭐ 已被新版顶掉 —— 主管让助手重排了一版,这张当场作废。
* 🔴 **必须显式标注 + 禁用确认**,⛔ 不许只是变灰、更不许消失:
* 两张都能点确认的卡摆在一起,点错就是分错一批人;
* 而直接消失又会让他以为自己看错了、或者系统吞了东西。
*/
const superseded = state === 'superseded';
const readOnly = done || cancelled || superseded || submitting;
/** /**
* 生效条目 = 提案条目 − 删掉的 + 拖拽改派 + 逐条时效。 * 生效条目 = 提案条目 − 删掉的 + 拖拽改派 + 逐条时效。
...@@ -957,9 +965,25 @@ export function AssignmentConfirmSheet({ ...@@ -957,9 +965,25 @@ export function AssignmentConfirmSheet({
onDragOver={onDragOverAnywhere} onDragOver={onDragOverAnywhere}
onDragEnd={stopAutoScroll} onDragEnd={stopAutoScroll}
onDrop={stopAutoScroll} onDrop={stopAutoScroll}
className="overflow-hidden border-brand-100 text-[12px] shadow-sm" className={
superseded
? // ⚠️ 作废态**只压低存在感,不隐藏**:主色边框换成中性、内容压到 60% 不透明度。
// ⛔ 不用 `hidden`、⛔ 不折叠 —— 他要能看见自己出过几版。
'overflow-hidden border-slate-200 text-[12px] opacity-60 shadow-none'
: 'overflow-hidden border-brand-100 text-[12px] shadow-sm'
}
> >
{/* {/*
⭐ 作废横幅 —— 钉在**卡片顶部**。
🔴 光靠底部那句不够:确认单可以很长,主管的视线停在卡片上半部分时
根本看不到底下写着"已作废",而顶部这条第一眼就在。
*/}
{superseded && (
<div className="border-b border-amber-200 bg-amber-50 px-3 py-1.5 text-[11px] font-medium text-amber-800">
这一版已作废,不能确认 —— 当前有效的是下面最新的那一张
</div>
)}
{/*
① 汇总 ① 汇总
🔴 **必须 `flex-wrap`**(2026-08-11 产品指出):窗口窄的时候这一行装不下 🔴 **必须 `flex-wrap`**(2026-08-11 产品指出):窗口窄的时候这一行装不下
「共 N 人 · 拟分 X · 待分配 Y」+ 角标 + 时效下拉,不换行就只能挤 —— 「共 N 人 · 拟分 X · 待分配 Y」+ 角标 + 时效下拉,不换行就只能挤 ——
...@@ -1431,7 +1455,15 @@ export function AssignmentConfirmSheet({ ...@@ -1431,7 +1455,15 @@ export function AssignmentConfirmSheet({
{/* ④ 确认 */} {/* ④ 确认 */}
<div className="border-t px-3 py-2"> <div className="border-t px-3 py-2">
{error && <p className="mb-1.5 text-[11px] text-rose-600">{error}</p>} {error && <p className="mb-1.5 text-[11px] text-rose-600">{error}</p>}
{cancelled && !revoked ? ( {superseded ? (
/**
* ⭐ 已被新版顶掉。**说清三件事**:作废了、为什么、去哪找当前那张。
* ⛔ 不要只写"已失效" —— 主管会以为是系统出错,而不是他自己刚才要求重排的结果。
*/
<span className="text-[11.5px] font-medium text-amber-700">
这一版已作废 —— 你让我重排了一版,请看下面最新的那张
</span>
) : cancelled && !revoked ? (
// 助手撤的:本地没有三个数,只说状态 —— 具体数字助手在消息里已经原话转述过 // 助手撤的:本地没有三个数,只说状态 —— 具体数字助手在消息里已经原话转述过
<span className="text-[11.5px] font-medium text-slate-500">这批已撤销</span> <span className="text-[11.5px] font-medium text-slate-500">这批已撤销</span>
) : done ? ( ) : done ? (
......
import { describe, expect, it } from 'vitest';
import type { AssignmentProposal } from '@pac/types';
import {
applyIncomingSheet,
findActiveSheet,
type Block,
type ChatMessage,
type DraftState,
} from './chat-blocks';
/**
* 草稿状态机的回归 —— 这段逻辑的失败形态**全都是静默的**:
* 改错了卡片主管看不到反应、两张 active 同时能点确认就是分错一批人。
* 端到端点一遍代价高且不稳定,所以抽成纯函数在这里锁住。
*/
const sheet = (n: number) => ({ placed: n } as unknown as AssignmentProposal);
function msg(id: string, ...sheets: Array<[string, DraftState]>): ChatMessage {
return {
id,
role: 'assistant',
blocks: sheets.map(([requestId, state]): Block => ({
kind: 'assignment_sheet',
requestId,
sheet: sheet(1),
state,
})),
};
}
/** 摊平成 [requestId, state] 便于断言 */
function states(ms: ChatMessage[]): Array<[string, DraftState]> {
return ms.flatMap((m) =>
m.blocks
.filter((b): b is Extract<Block, { kind: 'assignment_sheet' }> => b.kind === 'assignment_sheet')
.map((b) => [b.requestId, b.state] as [string, DraftState]),
);
}
describe('applyIncomingSheet', () => {
it('第一张草稿挂到宿主消息下,状态是 active', () => {
const out = applyIncomingSheet([msg('m1')], {
requestId: 'r1',
sheet: sheet(10),
hostMessageId: 'm1',
});
expect(states(out)).toEqual([['r1', 'active']]);
});
it('🔴 新草稿到达时,把**上一轮消息里**的 active 作废(跨消息扫描)', () => {
const prev = [msg('m1', ['r1', 'active']), msg('m2')];
const out = applyIncomingSheet(prev, {
requestId: 'r2',
sheet: sheet(20),
hostMessageId: 'm2',
});
expect(states(out)).toEqual([
['r1', 'superseded'],
['r2', 'active'],
]);
});
it('🔴 终态不许被回退:confirmed / cancelled 不受作废扫描影响', () => {
const prev = [msg('m1', ['r1', 'confirmed'], ['r2', 'cancelled']), msg('m2')];
const out = applyIncomingSheet(prev, {
requestId: 'r3',
sheet: sheet(30),
hostMessageId: 'm2',
});
expect(states(out)).toEqual([
['r1', 'confirmed'],
['r2', 'cancelled'],
['r3', 'active'],
]);
});
it('同一 requestId 重推:原地换内容、回到 active、⛔ 不堆第二张卡', () => {
const withEdits: ChatMessage = {
id: 'm1',
role: 'assistant',
blocks: [
{
kind: 'assignment_sheet',
requestId: 'r1',
sheet: sheet(1),
state: 'active',
edits: [{ seq: 1, ops: [] }],
},
],
};
const out = applyIncomingSheet([withEdits], {
requestId: 'r1',
sheet: sheet(99),
hostMessageId: 'm1',
});
expect(states(out)).toEqual([['r1', 'active']]);
const b = out[0]!.blocks[0] as Extract<Block, { kind: 'assignment_sheet' }>;
expect(b.sheet).toEqual(sheet(99));
// ⚠️ 旧编辑指令必须清空 —— 对新名单没有意义
expect(b.edits).toEqual([]);
});
it('任一时刻最多一张 active(连出三版)', () => {
let ms: ChatMessage[] = [msg('m1')];
for (const r of ['r1', 'r2', 'r3']) {
ms = applyIncomingSheet(ms, { requestId: r, sheet: sheet(1), hostMessageId: 'm1' });
}
expect(states(ms).filter(([, s]) => s === 'active')).toHaveLength(1);
expect(states(ms)).toEqual([
['r1', 'superseded'],
['r2', 'superseded'],
['r3', 'active'],
]);
});
});
describe('findActiveSheet', () => {
it('🔴 找的是 active,不是「最后一张」—— 这正是 2026-08-12 改掉的坑', () => {
// 顺序上 r2 在后,但它已作废;助手的修改必须落到 r1 上
const ms = [msg('m1', ['r1', 'active']), msg('m2', ['r2', 'superseded'])];
const at = findActiveSheet(ms);
expect(at).toEqual({ messageIndex: 0, blockIndex: 0 });
});
it('一张 active 都没有时返回 null(⛔ 不许退化成改最后一张)', () => {
const ms = [msg('m1', ['r1', 'confirmed']), msg('m2', ['r2', 'superseded'])];
expect(findActiveSheet(ms)).toBeNull();
});
it('没有确认单时返回 null', () => {
expect(findActiveSheet([msg('m1')])).toBeNull();
});
});
import type { AssignmentProposal, SheetEditOp } from '@pac/types';
/**
* chat-blocks —— 助手消息流的**数据模型 + 纯函数操作**。
*
* 从 `use-assistant-chat` 抽出来的原因:草稿状态机(active / superseded / …)
* 是这条链路上最容易出错、也最值得回归的一段逻辑,而它原来内联在 React hook 里,
* 只能靠端到端点一遍来验。抽成纯函数之后可以直接测。
*
* ⛔ 这里只放**不碰 React、不碰网络**的东西。带副作用的仍留在 hook 里。
*/
/** 一步工具调用(Claude 式透明步骤:看到调了哪个工具、入参、返回数据)。 */
export interface ToolStep {
id: string;
/** 后端 toolCallId,用于把 tool_result/tool_error 精确匹配回对应步骤(支持并行/多调用)。 */
callId?: string;
tool: string;
args: unknown;
result?: unknown;
status: 'running' | 'done' | 'error';
error?: string;
}
/** 模型产出的 HTML 卡片(在输出区沙箱 iframe 渲染)。 */
export interface Artifact {
id: string;
title?: string;
html: string;
/** true = 还在流式生成中(只注入内容,图表脚本不执行);false/缺省 = 最终(执行图表脚本)。 */
streaming?: boolean;
}
/**
* 草稿状态机 —— **任一时刻最多只有一张 `active`**。
*
* ⭐ `superseded` = 主管让助手重出了一版,这张当场作废。
* 🔴 作废发生在**新版生成时**,⛔ 不是在确认时(产品定,2026-08-12):
* 否则主管面前会同时摆着两张都能点确认的卡,点错就是分错一批人。
* ⚠️ 作废的卡**继续留在对话里并显式标注**,⛔ 不许静默变灰、⛔ 不许消失 ——
* 他要能看见自己出过几版、以及哪一版才是当前的。
* ⚠️ `confirmed` / `cancelled` 是**终态**,⛔ 不许被后续的作废扫描回退。
*/
export type DraftState = 'active' | 'superseded' | 'confirmed' | 'cancelled';
export type Block =
/**
* 文本块。`modelText` = **只给模型看**的版本(缺省时模型看 `text`)。
*
* 🔴 为什么需要两份:确认后注入的那句话里要带**完整批次 uuid**,否则模型撤销时
* 只能拿界面上的 8 位短号去调 `revoke_assignment` —— 实测必然报「批次不存在」。
* 但 uuid 对主管毫无意义,摆在对话里纯属噪音。⇒ 显示一份、喂模型一份。
* ⚠️ 用它承载的必须是**同一件事的两种措辞**,⛔ 不许塞模型专属的隐藏指令:
* 主管看不见的话就无法纠正,那是"界面替主管说话"。
*/
| { kind: 'text'; text: string; modelText?: string }
| { kind: 'tool'; step: ToolStep }
| { kind: 'artifact'; artifact: Artifact }
/**
* 全景确认单 —— **原生 React 组件**,不是 artifact。
*
* ⚠️ 为什么不能用 artifact:artifact 跑在 `sandbox="allow-scripts"` 的 iframe 里,
* CSP 是 `connect-src 'none'` —— **卡片内不可能发出写请求**。确认单要能点确认落库,
* 只能是原生组件。
* 分岔记牢:**只读展示用 artifact,可交互用原生组件。**
*
* ⚠️ sheet 由**服务端经侧信道推来**,不是模型生成的 —— 它含几百个 planId,
* 让模型吐等于让它编。
*/
| {
kind: 'assignment_sheet';
requestId: string;
sheet: AssignmentProposal;
state: DraftState;
/** 确认后的批次 id(终态展示用) */
assignmentId?: string;
/**
* ⭐ 助手下发的**语义编辑指令队列**(edit_assignment_sheet 走侧信道推来)。
* 卡片组件按 `seq` 只执行新增的那一批,执行完把结果回一句进对话。
* ⚠️ 用队列而不是"最新一条":主管连着说两句时,两批指令都要生效。
*/
edits?: { seq: number; ops: SheetEditOp[] }[];
};
export interface ChatMessage {
id: string;
role: 'user' | 'assistant';
blocks: Block[];
}
export type ChatStatus = 'idle' | 'streaming' | 'error';
// ─────────────────────────────────────────────────────────
// 纯函数操作
// ─────────────────────────────────────────────────────────
/** 把 tool_result/tool_error 匹配回对应工具步骤:优先按 callId,退化到"最后一个 running"。 */
export function findToolIdx(blocks: Block[], callId: string | undefined): number {
if (callId) {
const i = blocks.findIndex((b) => b.kind === 'tool' && b.step.callId === callId);
if (i !== -1) return i;
}
for (let i = blocks.length - 1; i >= 0; i--) {
const b = blocks[i];
if (b && b.kind === 'tool' && b.step.status === 'running') return i;
}
return -1;
}
/** 按 callId upsert artifact 块:流式 html 增量更新,最终 tool_call 覆盖(title+完整 html)。 */
export function upsertArtifact(
blocks: Block[],
id: string,
patch: { title?: string; html?: string; streaming?: boolean },
): Block[] {
const i = blocks.findIndex((b) => b.kind === 'artifact' && b.artifact.id === id);
if (i === -1) {
return [
...blocks,
{
kind: 'artifact',
artifact: { id, title: patch.title, html: patch.html ?? '', streaming: patch.streaming },
},
];
}
return blocks.map((b, j) => {
if (j !== i || b.kind !== 'artifact') return b;
return {
kind: 'artifact',
artifact: {
id,
title: patch.title ?? b.artifact.title,
html: patch.html ?? b.artifact.html,
streaming: patch.streaming ?? b.artifact.streaming,
},
};
});
}
/**
* 新草稿到达 —— upsert 它,并把**此前所有 `active` 的当场作废**。
*
* 🔴 必须**跨消息**扫:上一版几乎总在**上一轮**的消息里
* (主管看着它才说「重排一版」的)。只扫当前消息永远扫不到,而且不会报错。
* ⚠️ 只作废 `active`:`confirmed` / `cancelled` 是终态,⛔ 不许回退。
* ⚠️ 同一 `requestId` 重推时清空 `edits` —— 旧的编辑指令对新名单没有意义。
*
* @param hostMessageId 新卡挂到哪条消息下(通常是当前这条助手消息)
*/
export function applyIncomingSheet(
messages: ChatMessage[],
input: { requestId: string; sheet: AssignmentProposal; hostMessageId: string },
): ChatMessage[] {
const { requestId, sheet, hostMessageId } = input;
let placed = false;
const swept = messages.map((m) => ({
...m,
blocks: m.blocks.map((b): Block => {
if (b.kind !== 'assignment_sheet') return b;
if (b.requestId === requestId) {
placed = true;
return { ...b, sheet, state: 'active', edits: [] };
}
return b.state === 'active' ? { ...b, state: 'superseded' } : b;
}),
}));
if (placed) return swept;
return swept.map((m) =>
m.id === hostMessageId
? { ...m, blocks: [...m.blocks, { kind: 'assignment_sheet', requestId, sheet, state: 'active' }] }
: m,
);
}
/**
* 找**当前有效**的那张确认单(唯一的 `active`)。
*
* ⭐ 2026-08-12 由「最后一张」改成「唯一 active」:重出一版之后,最后一张可能
* 正是刚作废的那张 —— 助手的修改指令打上去,主管一点反应都看不到。
* ⚠️ 模型手里没有 requestId(服务端铸的,没进它上下文),它说「这批」指的就是当前有效那张。
*/
export function findActiveSheet(
messages: ChatMessage[],
): { messageIndex: number; blockIndex: number } | null {
for (let mi = messages.length - 1; mi >= 0; mi--) {
const blocks = messages[mi]?.blocks ?? [];
const bi = blocks.findIndex((b) => b.kind === 'assignment_sheet' && b.state === 'active');
if (bi >= 0) return { messageIndex: mi, blockIndex: bi };
}
return null;
}
...@@ -6,65 +6,25 @@ import { env } from '@/lib/env'; ...@@ -6,65 +6,25 @@ import { env } from '@/lib/env';
import { emitPetEvent } from '@/lib/pet-events'; import { emitPetEvent } from '@/lib/pet-events';
import { useAuthStore } from '@/stores/auth-store'; import { useAuthStore } from '@/stores/auth-store';
/** 一步工具调用(Claude 式透明步骤:看到调了哪个工具、入参、返回数据)。 */ // ⭐ 数据模型 + 纯函数操作已抽到 chat-blocks(草稿状态机是最该有回归的一段逻辑,
export interface ToolStep { // 内联在 hook 里只能端到端点一遍才能验)。这里 re-export 保持下游 import 不变。
id: string; import {
/** 后端 toolCallId,用于把 tool_result/tool_error 精确匹配回对应步骤(支持并行/多调用)。 */ applyIncomingSheet,
callId?: string; findActiveSheet,
tool: string; findToolIdx,
args: unknown; upsertArtifact,
result?: unknown; type Block,
status: 'running' | 'done' | 'error'; type ChatMessage,
error?: string; type ChatStatus,
} } from './chat-blocks';
/** 模型产出的 HTML 卡片(在输出区沙箱 iframe 渲染)。 */ export type { Artifact, Block, ChatMessage, ChatStatus, DraftState, ToolStep } from './chat-blocks';
export interface Artifact {
id: string;
title?: string;
html: string;
/** true = 还在流式生成中(只注入内容,图表脚本不执行);false/缺省 = 最终(执行图表脚本)。 */
streaming?: boolean;
}
export type Block = let _id = 0;
/** const nextId = () => `m${Date.now()}_${_id++}`;
* 文本块。`modelText` = **只给模型看**的版本(缺省时模型看 `text`)。
* /** render_artifact 是本地"渲染"工具,不当普通工具步骤展示,而是渲成卡片。 */
* 🔴 为什么需要两份:确认后注入的那句话里要带**完整批次 uuid**,否则模型撤销时 const ARTIFACT_TOOL = 'render_artifact';
* 只能拿界面上的 8 位短号去调 `revoke_assignment` —— 实测必然报「批次不存在」。
* 但 uuid 对主管毫无意义,摆在对话里纯属噪音。⇒ 显示一份、喂模型一份。
* ⚠️ 用它承载的必须是**同一件事的两种措辞**,⛔ 不许塞模型专属的隐藏指令:
* 主管看不见的话就无法纠正,那是"界面替主管说话"。
*/
| { kind: 'text'; text: string; modelText?: string }
| { kind: 'tool'; step: ToolStep }
| { kind: 'artifact'; artifact: Artifact }
/**
* 全景确认单 —— **原生 React 组件**,不是 artifact。
*
* ⚠️ 为什么不能用 artifact:artifact 跑在 `sandbox="allow-scripts"` 的 iframe 里,
* CSP 是 `connect-src 'none'` —— **卡片内不可能发出写请求**。确认单要能点确认落库,
* 只能是原生组件。
* 分岔记牢:**只读展示用 artifact,可交互用原生组件。**
*
* ⚠️ sheet 由**服务端经侧信道推来**,不是模型生成的 —— 它含几百个 planId,
* 让模型吐等于让它编。
*/
| {
kind: 'assignment_sheet';
requestId: string;
sheet: AssignmentProposal;
state: 'pending' | 'confirmed' | 'cancelled';
/** 确认后的批次 id(终态展示用) */
assignmentId?: string;
/**
* ⭐ 助手下发的**语义编辑指令队列**(edit_assignment_sheet 走侧信道推来)。
* 卡片组件按 `seq` 只执行新增的那一批,执行完把结果回一句进对话。
* ⚠️ 用队列而不是"最新一条":主管连着说两句时,两批指令都要生效。
*/
edits?: { seq: number; ops: SheetEditOp[] }[];
};
/** /**
* 从 revoke_assignment 的返回里抠出 assignmentId。 * 从 revoke_assignment 的返回里抠出 assignmentId。
...@@ -74,10 +34,7 @@ export type Block = ...@@ -74,10 +34,7 @@ export type Block =
*/ */
function extractRevokedId(result: unknown): string | null { function extractRevokedId(result: unknown): string | null {
try { try {
const raw = const raw = typeof result === 'string' ? result : JSON.stringify(result ?? '');
typeof result === 'string'
? result
: JSON.stringify(result ?? '');
const m = raw.match(/"assignmentId"\s*:\s*"([0-9a-fA-F-]{36})"/); const m = raw.match(/"assignmentId"\s*:\s*"([0-9a-fA-F-]{36})"/);
return m?.[1] ?? null; return m?.[1] ?? null;
} catch { } catch {
...@@ -85,59 +42,6 @@ function extractRevokedId(result: unknown): string | null { ...@@ -85,59 +42,6 @@ function extractRevokedId(result: unknown): string | null {
} }
} }
/** render_artifact 是本地"渲染"工具,不当普通工具步骤展示,而是渲成卡片。 */
const ARTIFACT_TOOL = 'render_artifact';
export interface ChatMessage {
id: string;
role: 'user' | 'assistant';
blocks: Block[];
}
export type ChatStatus = 'idle' | 'streaming' | 'error';
let _id = 0;
const nextId = () => `m${Date.now()}_${_id++}`;
/** 把 tool_result/tool_error 匹配回对应工具步骤:优先按 callId,退化到"最后一个 running"。 */
function findToolIdx(blocks: Block[], callId: string | undefined): number {
if (callId) {
const i = blocks.findIndex((b) => b.kind === 'tool' && b.step.callId === callId);
if (i !== -1) return i;
}
for (let i = blocks.length - 1; i >= 0; i--) {
const b = blocks[i];
if (b && b.kind === 'tool' && b.step.status === 'running') return i;
}
return -1;
}
/** 按 callId upsert artifact 块:流式 html 增量更新,最终 tool_call 覆盖(title+完整 html)。 */
function upsertArtifact(
blocks: Block[],
id: string,
patch: { title?: string; html?: string; streaming?: boolean },
): Block[] {
const i = blocks.findIndex((b) => b.kind === 'artifact' && b.artifact.id === id);
if (i === -1) {
return [
...blocks,
{ kind: 'artifact', artifact: { id, title: patch.title, html: patch.html ?? '', streaming: patch.streaming } },
];
}
return blocks.map((b, j) => {
if (j !== i || b.kind !== 'artifact') return b;
return {
kind: 'artifact',
artifact: {
id,
title: patch.title ?? b.artifact.title,
html: patch.html ?? b.artifact.html,
streaming: patch.streaming ?? b.artifact.streaming,
},
};
});
}
/** 把一条消息压平成后端要的 {role, content} 文本(工具块不回传,模型自行重新决策)。 */ /** 把一条消息压平成后端要的 {role, content} 文本(工具块不回传,模型自行重新决策)。 */
function toApiMessage(m: ChatMessage): { role: 'user' | 'assistant'; content: string } | null { function toApiMessage(m: ChatMessage): { role: 'user' | 'assistant'; content: string } | null {
...@@ -223,20 +127,15 @@ export function useAssistantChat() { ...@@ -223,20 +127,15 @@ export function useAssistantChat() {
appendText((evt.text as string) ?? ''); appendText((evt.text as string) ?? '');
break; break;
case 'assignment_sheet': case 'assignment_sheet':
// ⭐ 侧信道推来的确认单。upsert 按 requestId —— 模型若因某种原因重复调用, // ⭐ 侧信道推来的确认单:upsert 它 + 把此前所有 active 的作废。
// 不该在对话里堆出两张卡片。 // 逻辑在 chat-blocks.applyIncomingSheet(纯函数,有回归测试)。
patch((blocks) => { setMessages((prev) =>
const rid = String(evt.requestId ?? ''); applyIncomingSheet(prev, {
const i = blocks.findIndex((b) => b.kind === 'assignment_sheet' && b.requestId === rid); requestId: String(evt.requestId ?? ''),
const next: Block = {
kind: 'assignment_sheet',
requestId: rid,
sheet: evt.sheet as AssignmentProposal, sheet: evt.sheet as AssignmentProposal,
state: 'pending', hostMessageId: assistantId,
}; }),
if (i >= 0) return [...blocks.slice(0, i), next, ...blocks.slice(i + 1)]; );
return [...blocks, next];
});
break; break;
case 'assignment_sheet_edit': case 'assignment_sheet_edit':
// ⭐ 助手要改**已经呈现**的那张卡:指令挂到最后一张确认单上,由它自己去匹配执行。 // ⭐ 助手要改**已经呈现**的那张卡:指令挂到最后一张确认单上,由它自己去匹配执行。
...@@ -244,12 +143,13 @@ export function useAssistantChat() { ...@@ -244,12 +143,13 @@ export function useAssistantChat() {
// 🔴 必须**跨消息**找,⛔ 不能用 patch(那只改当前这条助手消息)—— // 🔴 必须**跨消息**找,⛔ 不能用 patch(那只改当前这条助手消息)——
// 确认单几乎总是在**上一轮**的消息里(主管看着它才提的要求), // 确认单几乎总是在**上一轮**的消息里(主管看着它才提的要求),
// 只在当前消息里找永远找不到,而且不会报错、界面毫无反应。 // 只在当前消息里找永远找不到,而且不会报错、界面毫无反应。
// ⚠️ 挂"最后一张"而不是按 requestId 找:主管说"把某某移出这批"指的就是眼前这张, // ⭐ 找的是**唯一那张 `active`**,⛔ 不再是"最后一张"(2026-08-12)——
// 而模型手里根本没有 requestId(服务端铸的,没进它上下文)。 // 重出一版之后,最后一张可能正是刚作废的那张,改上去主管一点反应都看不到。
// 模型手里没有 requestId(服务端铸的,没进它上下文),它说"这批"指的就是当前有效那张。
setMessages((prev) => { setMessages((prev) => {
for (let mi = prev.length - 1; mi >= 0; mi--) { const at = findActiveSheet(prev);
const bi = prev[mi]!.blocks.map((b) => b.kind).lastIndexOf('assignment_sheet'); if (!at) return prev;
if (bi < 0) continue; const { messageIndex: mi, blockIndex: bi } = at;
const b = prev[mi]!.blocks[bi] as Extract<Block, { kind: 'assignment_sheet' }>; const b = prev[mi]!.blocks[bi] as Extract<Block, { kind: 'assignment_sheet' }>;
const next: Block = { const next: Block = {
...b, ...b,
...@@ -261,8 +161,6 @@ export function useAssistantChat() { ...@@ -261,8 +161,6 @@ export function useAssistantChat() {
return prev.map((m, k) => return prev.map((m, k) =>
k === mi ? { ...m, blocks: m.blocks.map((x, j) => (j === bi ? next : x)) } : m, k === mi ? { ...m, blocks: m.blocks.map((x, j) => (j === bi ? next : x)) } : m,
); );
}
return prev;
}); });
break; break;
case 'artifact_html': case 'artifact_html':
......
...@@ -44,7 +44,7 @@ importers: ...@@ -44,7 +44,7 @@ importers:
version: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.3) version: 16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.3)
fumadocs-mdx: fumadocs-mdx:
specifier: 15.0.13 specifier: 15.0.13
version: 15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.3))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5) version: 15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.3))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2))
fumadocs-openapi: fumadocs-openapi:
specifier: 11.0.6 specifier: 11.0.6
version: 11.0.6(7a340a1a90ed181d65cb8a5509544648) version: 11.0.6(7a340a1a90ed181d65cb8a5509544648)
...@@ -405,6 +405,9 @@ importers: ...@@ -405,6 +405,9 @@ importers:
typescript: typescript:
specifier: ^5.9.3 specifier: ^5.9.3
version: 5.9.3 version: 5.9.3
vitest:
specifier: ^3.2.7
version: 3.2.7(@types/debug@4.1.13)(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)
packages/types: packages/types:
dependencies: dependencies:
...@@ -3344,6 +3347,9 @@ packages: ...@@ -3344,6 +3347,9 @@ packages:
'@types/body-parser@1.19.6': '@types/body-parser@1.19.6':
resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/connect@3.4.38': '@types/connect@3.4.38':
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
...@@ -3446,6 +3452,9 @@ packages: ...@@ -3446,6 +3452,9 @@ packages:
'@types/debug@4.1.13': '@types/debug@4.1.13':
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/eslint-scope@3.7.7': '@types/eslint-scope@3.7.7':
resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==}
...@@ -3746,6 +3755,35 @@ packages: ...@@ -3746,6 +3755,35 @@ packages:
resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==}
engines: {node: '>= 20'} engines: {node: '>= 20'}
'@vitest/expect@3.2.7':
resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==}
'@vitest/mocker@3.2.7':
resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==}
peerDependencies:
msw: ^2.4.9
vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@3.2.7':
resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==}
'@vitest/runner@3.2.7':
resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==}
'@vitest/snapshot@3.2.7':
resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==}
'@vitest/spy@3.2.7':
resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==}
'@vitest/utils@3.2.7':
resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==}
'@webassemblyjs/ast@1.14.1': '@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
...@@ -4007,6 +4045,10 @@ packages: ...@@ -4007,6 +4045,10 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
ast-types-flow@0.0.8: ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
...@@ -4208,6 +4250,10 @@ packages: ...@@ -4208,6 +4250,10 @@ packages:
magicast: magicast:
optional: true optional: true
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
cacheable-lookup@7.0.0: cacheable-lookup@7.0.0:
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
...@@ -4246,6 +4292,10 @@ packages: ...@@ -4246,6 +4292,10 @@ packages:
ccount@2.0.1: ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
chai@5.3.3:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
chalk@4.1.2: chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'} engines: {node: '>=10'}
...@@ -4269,6 +4319,10 @@ packages: ...@@ -4269,6 +4319,10 @@ packages:
chardet@2.1.1: chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
check-error@2.1.3:
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
engines: {node: '>= 16'}
chokidar@4.0.3: chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'} engines: {node: '>= 14.16.0'}
...@@ -4697,6 +4751,10 @@ packages: ...@@ -4697,6 +4751,10 @@ packages:
babel-plugin-macros: babel-plugin-macros:
optional: true optional: true
deep-eql@5.0.2:
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
engines: {node: '>=6'}
deep-is@0.1.4: deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
...@@ -4876,6 +4934,9 @@ packages: ...@@ -4876,6 +4934,9 @@ packages:
resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
es-module-lexer@2.1.0: es-module-lexer@2.1.0:
resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==}
...@@ -5129,6 +5190,10 @@ packages: ...@@ -5129,6 +5190,10 @@ packages:
resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
expect-type@1.4.0:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
expect@30.3.0: expect@30.3.0:
resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
...@@ -6064,6 +6129,9 @@ packages: ...@@ -6064,6 +6129,9 @@ packages:
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
js-yaml@3.14.2: js-yaml@3.14.2:
resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
hasBin: true hasBin: true
...@@ -6319,6 +6387,9 @@ packages: ...@@ -6319,6 +6387,9 @@ packages:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true hasBin: true
loupe@3.2.1:
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
lowercase-keys@3.0.0: lowercase-keys@3.0.0:
resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
...@@ -6941,6 +7012,10 @@ packages: ...@@ -6941,6 +7012,10 @@ packages:
pathe@2.0.3: pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pathval@2.0.1:
resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
engines: {node: '>= 14.16'}
pause@0.0.1: pause@0.0.1:
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
...@@ -7413,6 +7488,9 @@ packages: ...@@ -7413,6 +7488,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@3.0.7: signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
...@@ -7491,6 +7569,9 @@ packages: ...@@ -7491,6 +7569,9 @@ packages:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
stacktrace-parser@0.1.11: stacktrace-parser@0.1.11:
resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==}
engines: {node: '>=6'} engines: {node: '>=6'}
...@@ -7502,6 +7583,9 @@ packages: ...@@ -7502,6 +7583,9 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
stop-iteration-iterator@1.1.0: stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
...@@ -7581,6 +7665,9 @@ packages: ...@@ -7581,6 +7665,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
strip-literal@3.1.0:
resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
strtok3@10.3.5: strtok3@10.3.5:
resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==} resolution: {integrity: sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==}
engines: {node: '>=18'} engines: {node: '>=18'}
...@@ -7684,6 +7771,12 @@ packages: ...@@ -7684,6 +7771,12 @@ packages:
through@2.3.8: through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
tinyexec@1.2.4: tinyexec@1.2.4:
resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==}
engines: {node: '>=18'} engines: {node: '>=18'}
...@@ -7696,6 +7789,18 @@ packages: ...@@ -7696,6 +7789,18 @@ packages:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
tinypool@1.1.1:
resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
engines: {node: ^18.0.0 || >=20.0.0}
tinyrainbow@2.0.0:
resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
engines: {node: '>=14.0.0'}
tinyspy@4.0.4:
resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
engines: {node: '>=14.0.0'}
tmpl@1.0.5: tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
...@@ -7985,6 +8090,79 @@ packages: ...@@ -7985,6 +8090,79 @@ packages:
vfile@6.0.3: vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
vite-node@3.2.4:
resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite@7.3.6:
resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
peerDependencies:
'@types/node': ^20.19.0 || >=22.12.0
jiti: '>=1.21.0'
less: ^4.0.0
lightningcss: ^1.21.0
sass: ^1.70.0
sass-embedded: ^1.70.0
stylus: '>=0.54.8'
sugarss: ^5.0.0
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vitest@3.2.7:
resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@types/debug': ^4.1.12
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
'@vitest/browser': 3.2.7
'@vitest/ui': 3.2.7
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@types/debug':
optional: true
'@types/node':
optional: true
'@vitest/browser':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
walker@1.0.8: walker@1.0.8:
resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
...@@ -8043,6 +8221,11 @@ packages: ...@@ -8043,6 +8221,11 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
winston-transport@4.9.0: winston-transport@4.9.0:
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
...@@ -11073,6 +11256,11 @@ snapshots: ...@@ -11073,6 +11256,11 @@ snapshots:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
'@types/node': 22.19.17 '@types/node': 22.19.17
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/connect@3.4.38': '@types/connect@3.4.38':
dependencies: dependencies:
'@types/node': 22.19.17 '@types/node': 22.19.17
...@@ -11202,6 +11390,8 @@ snapshots: ...@@ -11202,6 +11390,8 @@ snapshots:
dependencies: dependencies:
'@types/ms': 2.1.0 '@types/ms': 2.1.0
'@types/deep-eql@4.0.2': {}
'@types/eslint-scope@3.7.7': '@types/eslint-scope@3.7.7':
dependencies: dependencies:
'@types/eslint': 9.6.1 '@types/eslint': 9.6.1
...@@ -11500,6 +11690,48 @@ snapshots: ...@@ -11500,6 +11690,48 @@ snapshots:
'@vercel/oidc@3.2.0': {} '@vercel/oidc@3.2.0': {}
'@vitest/expect@3.2.7':
dependencies:
'@types/chai': 5.2.3
'@vitest/spy': 3.2.7
'@vitest/utils': 3.2.7
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.7(vite@7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2))':
dependencies:
'@vitest/spy': 3.2.7
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)
'@vitest/pretty-format@3.2.7':
dependencies:
tinyrainbow: 2.0.0
'@vitest/runner@3.2.7':
dependencies:
'@vitest/utils': 3.2.7
pathe: 2.0.3
strip-literal: 3.1.0
'@vitest/snapshot@3.2.7':
dependencies:
'@vitest/pretty-format': 3.2.7
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@3.2.7':
dependencies:
tinyspy: 4.0.4
'@vitest/utils@3.2.7':
dependencies:
'@vitest/pretty-format': 3.2.7
loupe: 3.2.1
tinyrainbow: 2.0.0
'@webassemblyjs/ast@1.14.1': '@webassemblyjs/ast@1.14.1':
dependencies: dependencies:
'@webassemblyjs/helper-numbers': 1.13.2 '@webassemblyjs/helper-numbers': 1.13.2
...@@ -11884,6 +12116,8 @@ snapshots: ...@@ -11884,6 +12116,8 @@ snapshots:
get-intrinsic: 1.3.0 get-intrinsic: 1.3.0
is-array-buffer: 3.0.5 is-array-buffer: 3.0.5
assertion-error@2.0.1: {}
ast-types-flow@0.0.8: {} ast-types-flow@0.0.8: {}
astring@1.9.0: {} astring@1.9.0: {}
...@@ -12117,6 +12351,8 @@ snapshots: ...@@ -12117,6 +12351,8 @@ snapshots:
pkg-types: 2.3.1 pkg-types: 2.3.1
rc9: 2.1.2 rc9: 2.1.2
cac@6.7.14: {}
cacheable-lookup@7.0.0: {} cacheable-lookup@7.0.0: {}
cacheable-request@10.2.14: cacheable-request@10.2.14:
...@@ -12156,6 +12392,14 @@ snapshots: ...@@ -12156,6 +12392,14 @@ snapshots:
ccount@2.0.1: {} ccount@2.0.1: {}
chai@5.3.3:
dependencies:
assertion-error: 2.0.1
check-error: 2.1.3
deep-eql: 5.0.2
loupe: 3.2.1
pathval: 2.0.1
chalk@4.1.2: chalk@4.1.2:
dependencies: dependencies:
ansi-styles: 4.3.0 ansi-styles: 4.3.0
...@@ -12173,6 +12417,8 @@ snapshots: ...@@ -12173,6 +12417,8 @@ snapshots:
chardet@2.1.1: {} chardet@2.1.1: {}
check-error@2.1.3: {}
chokidar@4.0.3: chokidar@4.0.3:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
...@@ -12583,6 +12829,8 @@ snapshots: ...@@ -12583,6 +12829,8 @@ snapshots:
dedent@1.7.2: {} dedent@1.7.2: {}
deep-eql@5.0.2: {}
deep-is@0.1.4: {} deep-is@0.1.4: {}
deepmerge-ts@7.1.5: {} deepmerge-ts@7.1.5: {}
...@@ -12812,6 +13060,8 @@ snapshots: ...@@ -12812,6 +13060,8 @@ snapshots:
iterator.prototype: 1.1.5 iterator.prototype: 1.1.5
math-intrinsics: 1.1.0 math-intrinsics: 1.1.0
es-module-lexer@1.7.0: {}
es-module-lexer@2.1.0: {} es-module-lexer@2.1.0: {}
es-module-lexer@2.3.1: {} es-module-lexer@2.3.1: {}
...@@ -12897,8 +13147,8 @@ snapshots: ...@@ -12897,8 +13147,8 @@ snapshots:
'@next/eslint-plugin-next': 16.2.4 '@next/eslint-plugin-next': 16.2.4
eslint: 9.39.4(jiti@2.7.0) eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10 eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react: 7.37.5(eslint@9.39.4(jiti@2.7.0))
eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4(jiti@2.7.0))
...@@ -12920,7 +13170,7 @@ snapshots: ...@@ -12920,7 +13170,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0)):
dependencies: dependencies:
'@nolyfill/is-core-module': 1.0.39 '@nolyfill/is-core-module': 1.0.39
debug: 4.4.3 debug: 4.4.3
...@@ -12931,22 +13181,22 @@ snapshots: ...@@ -12931,22 +13181,22 @@ snapshots:
tinyglobby: 0.2.16 tinyglobby: 0.2.16
unrs-resolver: 1.11.1 unrs-resolver: 1.11.1
optionalDependencies: optionalDependencies:
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
dependencies: dependencies:
debug: 3.2.7 debug: 3.2.7
optionalDependencies: optionalDependencies:
'@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/parser': 8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)
eslint: 9.39.4(jiti@2.7.0) eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10 eslint-import-resolver-node: 0.3.10
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.7.0))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)): eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0)):
dependencies: dependencies:
'@rtsao/scc': 1.1.0 '@rtsao/scc': 1.1.0
array-includes: 3.1.9 array-includes: 3.1.9
...@@ -12957,7 +13207,7 @@ snapshots: ...@@ -12957,7 +13207,7 @@ snapshots:
doctrine: 2.1.0 doctrine: 2.1.0
eslint: 9.39.4(jiti@2.7.0) eslint: 9.39.4(jiti@2.7.0)
eslint-import-resolver-node: 0.3.10 eslint-import-resolver-node: 0.3.10
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)))(eslint@9.39.4(jiti@2.7.0)) eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.4(jiti@2.7.0))
hasown: 2.0.4 hasown: 2.0.4
is-core-module: 2.16.2 is-core-module: 2.16.2
is-glob: 4.0.3 is-glob: 4.0.3
...@@ -13183,6 +13433,8 @@ snapshots: ...@@ -13183,6 +13433,8 @@ snapshots:
exit-x@0.2.2: {} exit-x@0.2.2: {}
expect-type@1.4.0: {}
expect@30.3.0: expect@30.3.0:
dependencies: dependencies:
'@jest/expect-utils': 30.3.0 '@jest/expect-utils': 30.3.0
...@@ -13446,7 +13698,7 @@ snapshots: ...@@ -13446,7 +13698,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
fumadocs-mdx@15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.3))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5): fumadocs-mdx@15.0.13(@types/mdast@4.0.4)(@types/mdx@2.0.14)(@types/react@19.2.14)(fumadocs-core@16.10.6(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.14.0(react@19.2.5))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.4.3))(next@16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)):
dependencies: dependencies:
'@mdx-js/mdx': 3.1.1 '@mdx-js/mdx': 3.1.1
'@standard-schema/spec': 1.1.0 '@standard-schema/spec': 1.1.0
...@@ -13471,6 +13723,7 @@ snapshots: ...@@ -13471,6 +13723,7 @@ snapshots:
'@types/react': 19.2.14 '@types/react': 19.2.14
next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next: 16.2.4(@babel/core@7.29.0)(@opentelemetry/api@1.9.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
react: 19.2.5 react: 19.2.5
vite: 7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
...@@ -14440,6 +14693,8 @@ snapshots: ...@@ -14440,6 +14693,8 @@ snapshots:
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-tokens@9.0.1: {}
js-yaml@3.14.2: js-yaml@3.14.2:
dependencies: dependencies:
argparse: 1.0.10 argparse: 1.0.10
...@@ -14656,6 +14911,8 @@ snapshots: ...@@ -14656,6 +14911,8 @@ snapshots:
dependencies: dependencies:
js-tokens: 4.0.0 js-tokens: 4.0.0
loupe@3.2.1: {}
lowercase-keys@3.0.0: {} lowercase-keys@3.0.0: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
...@@ -15539,6 +15796,8 @@ snapshots: ...@@ -15539,6 +15796,8 @@ snapshots:
pathe@2.0.3: {} pathe@2.0.3: {}
pathval@2.0.1: {}
pause@0.0.1: {} pause@0.0.1: {}
pend@1.2.0: {} pend@1.2.0: {}
...@@ -16162,6 +16421,8 @@ snapshots: ...@@ -16162,6 +16421,8 @@ snapshots:
side-channel-map: 1.0.1 side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2 side-channel-weakmap: 1.0.2
siginfo@2.0.0: {}
signal-exit@3.0.7: {} signal-exit@3.0.7: {}
signal-exit@4.1.0: {} signal-exit@4.1.0: {}
...@@ -16252,6 +16513,8 @@ snapshots: ...@@ -16252,6 +16513,8 @@ snapshots:
dependencies: dependencies:
escape-string-regexp: 2.0.0 escape-string-regexp: 2.0.0
stackback@0.0.2: {}
stacktrace-parser@0.1.11: stacktrace-parser@0.1.11:
dependencies: dependencies:
type-fest: 0.7.1 type-fest: 0.7.1
...@@ -16260,6 +16523,8 @@ snapshots: ...@@ -16260,6 +16523,8 @@ snapshots:
statuses@2.0.2: {} statuses@2.0.2: {}
std-env@3.10.0: {}
stop-iteration-iterator@1.1.0: stop-iteration-iterator@1.1.0:
dependencies: dependencies:
es-errors: 1.3.0 es-errors: 1.3.0
...@@ -16373,6 +16638,10 @@ snapshots: ...@@ -16373,6 +16638,10 @@ snapshots:
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
strip-literal@3.1.0:
dependencies:
js-tokens: 9.0.1
strtok3@10.3.5: strtok3@10.3.5:
dependencies: dependencies:
'@tokenizer/token': 0.3.0 '@tokenizer/token': 0.3.0
...@@ -16481,6 +16750,10 @@ snapshots: ...@@ -16481,6 +16750,10 @@ snapshots:
through@2.3.8: {} through@2.3.8: {}
tinybench@2.9.0: {}
tinyexec@0.3.2: {}
tinyexec@1.2.4: {} tinyexec@1.2.4: {}
tinyglobby@0.2.16: tinyglobby@0.2.16:
...@@ -16493,6 +16766,12 @@ snapshots: ...@@ -16493,6 +16766,12 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.4) fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4 picomatch: 4.0.4
tinypool@1.1.1: {}
tinyrainbow@2.0.0: {}
tinyspy@4.0.4: {}
tmpl@1.0.5: {} tmpl@1.0.5: {}
to-regex-range@5.0.1: to-regex-range@5.0.1:
...@@ -16828,6 +17107,84 @@ snapshots: ...@@ -16828,6 +17107,84 @@ snapshots:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
vfile-message: 4.0.3 vfile-message: 4.0.3
vite-node@3.2.4(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
vite@7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2):
dependencies:
esbuild: 0.28.1
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
postcss: 8.5.15
rollup: 4.62.2
tinyglobby: 0.2.17
optionalDependencies:
'@types/node': 22.19.17
fsevents: 2.3.3
jiti: 2.7.0
lightningcss: 1.32.0
terser: 5.46.2
vitest@3.2.7(@types/debug@4.1.13)(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.7
'@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2))
'@vitest/pretty-format': 3.2.7
'@vitest/runner': 3.2.7
'@vitest/snapshot': 3.2.7
'@vitest/spy': 3.2.7
'@vitest/utils': 3.2.7
chai: 5.3.3
debug: 4.4.3
expect-type: 1.4.0
magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.4
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinyglobby: 0.2.17
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 7.3.6(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)
vite-node: 3.2.4(@types/node@22.19.17)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.13
'@types/node': 22.19.17
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
- tsx
- yaml
walker@1.0.8: walker@1.0.8:
dependencies: dependencies:
makeerror: 1.0.12 makeerror: 1.0.12
...@@ -16963,6 +17320,11 @@ snapshots: ...@@ -16963,6 +17320,11 @@ snapshots:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
winston-transport@4.9.0: winston-transport@4.9.0:
dependencies: dependencies:
logform: 2.7.0 logform: 2.7.0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment