diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 820716cd..3a43bc6e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -189,9 +189,19 @@ jobs: bun-version: 1.3.14 - name: Install dependencies run: bun install --frozen-lockfile - - name: Install Chromium - run: bunx playwright install --with-deps chromium - - run: bun run test:web:e2e + - name: Install Chromium and D-Bus + run: | + bunx playwright install --with-deps chromium + sudo apt-get install --no-install-recommends -y dbus-daemon + - name: Verify isolated Chromium cold starts + timeout-minutes: 2 + run: dbus-run-session -- node --experimental-strip-types tests/web/browser-cold-start-smoke.mjs + env: + OPENPI_CHROME_PATH: /opt/google/chrome/chrome + - run: dbus-run-session -- bun run test:web:e2e + env: + # The runner's installed Chrome has Ubuntu's native sandbox profile. + OPENPI_CHROME_PATH: /opt/google/chrome/chrome - name: Upload Playwright failure artifacts if: failure() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 diff --git a/README.md b/README.md index d12f6a75..0e4eadc3 100644 --- a/README.md +++ b/README.md @@ -569,7 +569,7 @@ pi install npm:pi-intercom ### 独立 Web 工作台 -Web runtime 不嵌入交互式终端 Session。它由独立进程创建自己的 Pi `AgentSessionRuntime`、独立 `~/.pi/agent/web-sessions` 持久化目录和生命周期;浏览器发送消息、新建 Session 或切换工作区,不会写入或切换任何已经运行的终端 Pi Session,Web Session 也不会出现在终端的默认 Session 列表中。在侧栏选择 Session 会把它激活为 Web 进程的当前 Pi Session;Prompt 只会投递到请求时仍匹配的活动 Web Session。独立的只读历史浏览不属于首版范围。 +Web runtime 不嵌入交互式终端 Session。它由独立进程创建自己的 Pi `AgentSessionRuntime`、独立 `~/.pi/agent/web-sessions` 持久化目录和生命周期;浏览器发送消息、新建 Session 或切换工作区,不会写入或切换任何已经运行的终端 Pi Session,Web Session 也不会出现在终端的默认 Session 列表中。在侧栏选择 Session 会把它激活为 Web 进程的当前 Pi Session;Prompt 只会投递到请求时仍匹配的活动 Web Session。其他页面切换活动会话时,本页保留正在看的会话及其后台进度;需要继续对话或使用交互工具时,点击“切换到此会话”。读取与刷新不会自动抢占活动会话。 同一 Pi agent 目录一次只允许一个 Web Host 持有该 Session/元数据目录。第二个 `openpi web` 会明确拒绝启动;正常关停会先排空共享目录变更再释放租约,进程崩溃后仅在确认原 owner 的 PID 与进程启动身份不再匹配时恢复。一个 Host 可在侧栏管理多个工作区,因此不需要为每个仓库启动一个进程。 @@ -590,6 +590,19 @@ openpi web /path/to/repo # 指定初始工作区 Web 可以在选择工作区之前预选可用模型。选择仅保留在当前页面,创建会话后确认模型生效再发送第一条消息;模型不可用时会提示并阻止发送,不会自动换成默认模型。打开已有会话时使用该会话的模型。 +Web 设置页可以添加或编辑自定义模型、保存服务商 API Key。模型写入 Pi 原生 `models.json`,密钥通过 Pi 登录接口写入原生凭据存储,已有密钥不会回显或进入模型对话。写入要求活动会话空闲;外部配置发生变化时需刷新后重试。OAuth 和需要额外认证字段的服务商仍使用 Pi 原生登录。技能、插件、子代理及 OpenPI 自有偏好的 Web 表单统一提交到 `/openpi-setup`,进度与确认留在会话中;提交回执不代表配置已保存。 + +设置中的模型树直接选择编辑对象,同一会话的未保存模型草稿会保留。窄屏设置使用带文字的分类选择器;聊天宽度和字号支持恢复默认值。配置请求在对话中显示原始操作及后续结果,运行期间暂停再次应用,成功与否以原生配置结果为准。 + +输入 `/` 可查看原生发现的命令及不可用原因。`/openpi-setup` 使用原生配置回合;无参数的 `/ps`、`/lg`、`/subagents`、`/btw` 打开对应 Web 面板,不发送模型请求。这些面板快捷命令不接收参数或图片,带参时保留输入并提示。`/usage` 使用原生命令,在聊天中显示查询结果。运行中的回合显示“已处理”时间,结束后显示“用时”;时间从 Pi 实际开始执行计算,并随原生 Session 保存。没有可靠耗时记录的旧回合不推算补值。 + +输入框支持直接粘贴受支持的图片和混合文字,读取期间保留发送等待状态。生成文件中的 PNG、JPEG、GIF、WebP 可在已有文件授权范围内预览(上限 20 MiB);SVG 与 HTML 不作为图片执行。Git、文件和子代理面板隐藏时会减少自动读取,重新打开时恢复更新。 + +长会话可向上加载更早的消息,通常按最多 20 个用户回合取页,同时保留 250 条/2 MiB 上限;单个过大的回合仍可分段找回。阅读窗口上限为 1000 条/8 MiB。加载和流式追加保持阅读位置;发送新消息或点击“跳至最新”返回当前尾部。单条超长用户或助手正文可按原生条目 ID 显式加载完整内容,每次最多读取 32,000 个字符;工具输出仍保持有界预览。运行中有草稿时同时提供发送和停止按钮,排队数来自 Pi;已接收不表示已经执行完成。 + +变更面板默认展示 Git 未暂存变更,也可切换到暂存、分支或会话基线;输入框不再显示整个工作区的累计变更文件。新 Web 回合在对话中展示该轮期间的文件变化,审阅读取随 Pi Session 保存的有界 diff,不会用当前工作树冒充历史结果。此比较不能区分同一时间其他进程的改动;非 Git 工作区、过大的初始变更或超出记录上限时会标明无法验证或部分结果,旧回合不回填。手动变更面板的列表与逐文件 diff 分开加载,大型未跟踪文件不会阻止读取整个工作区;达到显示限额会标注截断。会话基线仍是有容量限制的附加视图,不能替代 Git。跨 worktree 的原生编辑记录可以在“生成文件”中查看;读取工作区外的文件需点击“只读打开此文件”,仅授权该文件,不放宽整个目录。 + +内嵌浏览器使用本机 Chromium CDP 的 PNG screencast 推送,根据面板大小和屏幕像素密度调整,最多保留最新待显示帧。它不是目标页面 iframe,也不提供桌面浏览器全部功能;复杂原生弹窗、下载或系统集成仍可通过“在浏览器中打开”使用外部浏览器。 在 Plan/Setup 原有工具范围内,模型调用 `ask_user` 时,发起任务的 Web 标签页会显示结构化问题卡片:选择选项、添加补充说明或填写自己的答案,复核后才提交给正在等待的工具调用。关闭卡片不会提交草稿;留空的自定义答案表示要求澄清问题。刷新同一标签可恢复尚未过期的提问(未提交草稿不持久化),其他标签不能代答。提问最多等待 15 分钟,停止运行、切换 Session 或关闭 Host 会取消等待;它不替代原生权限审批,也不意味着任意终端自定义界面已支持 Web。 OpenPI 的 `/plan` 调研通过 `plan_ready` 成功提交计划后,Web 会在聊天区展示可收起、可复制的 Markdown 计划卡片,刷新后仍可查看。卡片展示的是工具返回的计划,不代表批准或开始实施;超出 Web 传输上限的结果会标明为预览。输入框上方的开关由现有 Plan 扩展切换当前 Session 的规划状态,不调用模型、不发送命令气泡,也不清空草稿;发送任务后才开始规划。首次发送后,输入框占位提示会说明“本次对话使用 Plan 模式”,刷新后保留,退出后恢复。任务运行期间不能切换模式,退出不代表批准或开始实施。手动 `/plan <目标>` 仍会直接开始规划,`/plan off` 仍可退出;Plan Ready 的实施确认仍需使用 TUI。 diff --git a/SETUP.md b/SETUP.md index 360568d6..0a3e6a7b 100644 --- a/SETUP.md +++ b/SETUP.md @@ -36,6 +36,11 @@ The terminal theme above remains Pi-owned. OpenPI Web has separate package-owned ## Configure this package +The Web settings forms submit package-owned changes through `/openpi-setup`; they do not introduce another settings writer. Role/model choices, concurrency, skill and plugin configuration requests remain visible in that episode, including required confirmation and its final apply/closure receipt. The Web submission message is not proof of persistence. Pi-owned model definitions and credentials have separate direct forms backed by Pi's `models.json` and native login API; secrets never enter the setup prompt. + +Web shows the original setup command and its results, including failed or unapplied requests, rather than hiding the configuration episode. Appearance controls wait for the current run to settle before accepting another change. Chat-width and font-size reset buttons request the existing defaults through the same setup entry point. The model tree selects the editable Pi definition and retains unsaved drafts within the same Session; narrow screens use a labeled settings-section selector. + +Each delivered setup request explicitly announces its new episode. Success and closure messages apply only to their completed episode; a later `/openpi-setup` request can activate the writer again without reusing the prior episode's authority. The `compact` footer preset resolves to `plain` style with the default single-row layout; it is not a separate stored style. Applying it to the default footer does not change its appearance. Successful setup receipts distinguish saving the document from changing effective settings and list only fields that actually changed. Keeping defaults without invoking the writer does not create a file. Explicit field assignments retain their meaning: `footerStyle="compact"` is invalid; the allowed styles are `plain`, `powerline`, and `powerline-mono`. Setup should explain the legal choices and wait for a valid choice instead of silently applying a preset or default. Requesting the `compact` preset is valid and resets both style and layout. Tool writes reject invalid footer styles and presets; stored invalid styles block writes rather than being repaired automatically. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index e660f802..2aeefabd 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -20,6 +20,15 @@ revision `ebeb961` (Apache License 2.0). OpenPI uses its own Web host protocol and headless Chromium/CDP implementation; it does not embed either project or copy Maka's Electron-only `WebContentsView`. +The follow-up Git, file-preview and settings investigation also consulted +`agegr/pi-web` revision `1eb5e66a37c468aca7f0d338edb23de4fd84433e` (MIT), +`jmfederico/pi-web` revision `60a29acbfc710908e73b0df9857cb63b53b672d3` (MIT), +and `apache/maka` revision `0dc1142aa90000e45627017e846b6d57d1e165e0` +(Apache License 2.0). These are architectural references, not embedded packages. +The screencast sizing approach was informed by Chromium DevTools' +[`ScreencastView.ts`](https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/panels/screencast/ScreencastView.ts) +(BSD-3-Clause). OpenPI retains its own bounded transport and Session authority. + ## OAuth model providers `extensions/ai-providers/` adapts protocol and OAuth details from diff --git a/docs/research/README.md b/docs/research/README.md index 540177bc..546fb550 100644 --- a/docs/research/README.md +++ b/docs/research/README.md @@ -8,6 +8,24 @@ Research records preserve sourced investigation and distinguish observations, in ## Validated investigations +- [`WEB_TURN_EVIDENCE_AND_ITEM_RECOVERY_2026-09-23.md`](WEB_TURN_EVIDENCE_AND_ITEM_RECOVERY_2026-09-23.md) — Codex-style paged Pi history/item recovery and per-user-turn Git evidence; local gates passed, CI pending ([#597](https://github.com/openpi-dev/openpi/issues/597), [PR #598](https://github.com/openpi-dev/openpi/pull/598)). + +- [`WEB_GIT_REVIEW_IDENTITY_2026-09-23.md`](WEB_GIT_REVIEW_IDENTITY_2026-09-23.md) — 逐文件重命名两端、暂存/工作树比较对象身份及真实 Chromium 刷新验证([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #598](https://github.com/openpi-dev/openpi/pull/598))。 + +- [`WEB_READING_AND_LIFECYCLE_2026-09-22.md`](WEB_READING_AND_LIFECYCLE_2026-09-22.md) — 长会话历史、正文阅读、后台执行投影、待处理消息及输入控制文件身份;含真实 SDK 与 Chromium 验证([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #598](https://github.com/openpi-dev/openpi/pull/598))。 + +- [`WEB_INTERACTION_ROUND5_2026-09-22.md`](WEB_INTERACTION_ROUND5_2026-09-22.md) — 侧栏后台工作、图片粘贴与预览、设置布局、原生命令和完整回合耗时;含真实连续 setup 的正反证据与边界([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #598](https://github.com/openpi-dev/openpi/pull/598))。 + +- [`WEB_INTERACTION_ROUND4_2026-09-21.md`](WEB_INTERACTION_ROUND4_2026-09-21.md) — 表单回执、冲突重载、迟到凭据反馈、设置键盘导航与浏览器拖拽的第四轮验证([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #598](https://github.com/openpi-dev/openpi/pull/598))。 + +- [`WEB_INTERACTION_ROUND3_2026-09-21.md`](WEB_INTERACTION_ROUND3_2026-09-21.md) — 模型表单草稿、重复 diff 读取、本地浏览器地址、编辑组合键与指针请求积压的第三轮复现及修复([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #598](https://github.com/openpi-dev/openpi/pull/598))。 + +- [`WEB_NATIVE_INTERACTIONS_2026-09-20.md`](WEB_NATIVE_INTERACTIONS_2026-09-20.md) — Git 原生比较范围、CDP 浏览器、外部文件预览和 Pi 原生可编辑设置的参考调查与修复验收([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #598](https://github.com/openpi-dev/openpi/pull/598))。 + +- [`WEB_PR561_ASYNC_ITERATION_2026-09-20.md`](WEB_PR561_ASYNC_ITERATION_2026-09-20.md) — 第二轮 PR #561 测试:会话级变更投影、diff 焦点、终端队列及创建取消、文件引用清理;含合并后整合范围([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #561](https://github.com/openpi-dev/openpi/pull/561))。 + +- [`WEB_PR561_USAGE_ITERATION_2026-09-20.md`](WEB_PR561_USAGE_ITERATION_2026-09-20.md) — PR #561 使用测试:图片导入的发送与会话边界、浏览器粘贴与 Shift、键盘分栏([#597](https://github.com/openpi-dev/openpi/issues/597)、[PR #561](https://github.com/openpi-dev/openpi/pull/561))。 + - [`WEB_MODULE_IMPLEMENTATION_2026-09-19.md`](WEB_MODULE_IMPLEMENTATION_2026-09-19.md) — Web 工作区十二模块的实现边界、现有/第三方组件复用判断、测试与非 Benchmark 的性能限制([#559](https://github.com/openpi-dev/openpi/issues/559)、[#560](https://github.com/openpi-dev/openpi/issues/560)、[PR #561](https://github.com/openpi-dev/openpi/pull/561))。 - [`WEB_SUBAGENT_INSPECTION_2026-09-18.md`](WEB_SUBAGENT_INSPECTION_2026-09-18.md) — 子代理任务总览、真实状态和只读子对话;含 Pi Web / Maka 组件参考及验证边界([#560](https://github.com/openpi-dev/openpi/issues/560))。 diff --git a/docs/research/WEB_GIT_REVIEW_IDENTITY_2026-09-23.md b/docs/research/WEB_GIT_REVIEW_IDENTITY_2026-09-23.md new file mode 100644 index 00000000..115b9fd2 --- /dev/null +++ b/docs/research/WEB_GIT_REVIEW_IDENTITY_2026-09-23.md @@ -0,0 +1,33 @@ +# Git review:重命名详情与比较身份 + +- Status: validated +- Created: 2026-09-23 +- Verified: 2026-09-23 +- Source boundary: OpenPI `0bc3470` + 本记录所在 PR #598 的 Git review 修复 +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充[原生交互调查](WEB_NATIVE_INTERACTIONS_2026-09-20.md) + +## 复现与根因 + +评审指出的两项问题在 `0bc3470` 仍可复现。真实临时仓库执行 `git mv` 后,暂存摘要正确显示 renamed,旧详情查询却在识别重命名前把 pathspec 限定为新路径,导致旧路径被排除,退化成新增文件。新回归包括纯重命名、重命名后编辑、分支比较,以及含方括号的字面文件名。 + +另用 `git hash-object` 与 `git update-index --cacheinfo` 只替换 index blob,保留工作文件的内容、size、mtime、ctime 不变;新旧暂存文本均为 +1/-1。旧摘要 revision 相同,但详情已经改变。未暂存视图也存在此问题,因为 index 是它的左端。只移动 HEAD、保持 index/工作文件不变,同样可能出现统计相同而比较内容不同。 + +## 修复 + +使用 [Git 官方 raw diff 格式](https://git-scm.com/docs/git-diff#_raw_output_format)中的完整前后对象 ID、文件模式、状态与路径生成比较身份,不读取 index 的 mtime,也不写入临时 tree 到用户仓库。工作树参与比较时继续保留有界文件元数据;纯暂存视图由 Git 比较身份决定,不受无关工作文件时间戳影响。 + +逐文件详情先在完整 raw 元数据中找出重命名两端,再用字面 pathspec 查询。详情的 raw 元数据和 patch 来自同一次 Git 调用,随后只返回请求的文件。摘要继续只读取元数据和 numstat,不加载整个工作区的 patch。保留已有超时、字节/文件数限额,以及禁用 external diff/textconv 的边界。 + +## 验证与限制 + +六项真实 Git 回归在旧实现上失败,修复后 Git review 的 15 项测试通过。分别移除旧路径和对象身份保护,两组回归重新失败;恢复后通过。没有引入第二份 Git 状态存储或新的配置选项。 + +真实 Chromium + WebHost + Git 用例确认:面板显示 `rename from` / `rename to`;外部只改变暂存 blob 后,经现有窗口 focus 刷新路径,仍打开的 diff 从 staged-a 更新为 staged-b,草稿及输入焦点保留。该用例不调用模型。 + +评审提到的滚轮失败属于较早提交 `4480b8d`。后续 `13465ad` 让测试夹具等待 React 被动效果,保留滚动合并与调用次数断言;`0bc3470` 的 Node 24 与全部 CI 已通过。本轮仍重跑完整门禁,以新提交的检查为准,不用历史绿色代替当前验收。 + +Git 命令之间不是仓库事务快照;并发编辑时仍依赖下一次刷新收敛。本轮不声称对 PR 全部文件完成手工浏览器验收,也不把局部响应时间当作性能 Benchmark。原始红绿、消融、浏览器截图和最终验证日志保存在私有 `openpi-pr561-usage-20260920/git-review-20260923`,不包含用户会话或凭据。 + +`81d4c67` 的 Node 22/24/26 与 Windows CI 通过;Web 任务在运行用例前的冷启动门禁失败。诊断记录首次给出无效 D-Bus 地址错误,端口文件仍未出现,后两次启动成功。CI 因而使用 [`dbus-run-session`](https://dbus.freedesktop.org/doc/dbus-run-session.1.html) 为冷启动和浏览器测试提供独立会话总线,随测试进程结束清理。此调整消除已知环境错误,不增加生产启动预算、不禁用 sandbox,也不证明此前所有启动超时都来自同一个原因;最终结果以新提交的 CI 为准。 diff --git a/docs/research/WEB_INTERACTION_ROUND3_2026-09-21.md b/docs/research/WEB_INTERACTION_ROUND3_2026-09-21.md new file mode 100644 index 00000000..c092cf3b --- /dev/null +++ b/docs/research/WEB_INTERACTION_ROUND3_2026-09-21.md @@ -0,0 +1,39 @@ +# Web 第三轮交互回归 + +- Status: validated +- Created: 2026-09-21 +- Verified: 2026-09-21(完整检查、回归与真实 Chromium 验收;不代表部署) +- Source boundary: OpenPI `4d229e5` + PR #598 本轮变更;Pi SDK 0.85.1;本机 Chromium +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充 [原生 Web 交互调查](WEB_NATIVE_INTERACTIONS_2026-09-20.md),不覆盖前两轮观察 + +## 已复现的五个问题 + +| 场景 | 修复前的可观测结果 | 修复 | +| --- | --- | --- | +| 填写自定义模型地址,切换到技能后返回 | 未提交的地址变为空字符串 | 模型页首次访问时加载,随后切换标签保留表单;Session 变化仍隔离表单 | +| 选中文件 diff 后刷新相同版本的工作区摘要 | 内容版本未变仍产生第二次逐文件读取 | 依赖 Git revision 与文件身份,不依赖摘要对象是否重新分配;真实版本变化仍重新读 | +| 浏览器地址只输入 `127.0.0.1:端口` | 自动补 HTTPS,无法连接普通本地 HTTP 开发服务器 | 裸写 loopback 地址使用 HTTP,保留指定端口;公网和显式 HTTPS 不降级 | +| 内嵌页面输入框使用 Control/Command+A、Z | 前端完全不发送组合键,无法全选/撤销 | 传递编辑键的修饰位,使用 CDP 原生编辑命令处理全选/撤销/重做;不插入快捷键字母,保留宿主粘贴路径 | +| 鼠标移动时服务器请求未返回 | 20 次移动继续派发 20 个请求,旧位置积压 | 只保留一个在途移动和最新位置;关闭/隐藏后丢弃未发送位置 | + +修复前新增测试有 6 条失败,对应五个问题(Control 与 Meta 分别覆盖);原有 18 条通过。修复后这组 24 条全部通过。后续补充 loopback/公网/显式协议/端口和工具关闭的边界回归。没有为了修复这些问题增加模型工具、配置入口或独立运行时队列。 + +## 真实浏览器验证 + +新增端到端用例在临时端口提供真实 HTTP 页面,OpenPI Host 启动独立 Chromium;外层 UI 通过真实鼠标/键盘事件向 CDP 转发。验证裸地址能加载,Control 与 Meta 全选后输入 `X` 都替换整段文本,撤销恢复原文。 + +页面随输入把角落标记从红色改为绿色。测试读取外层 UI 已显示 PNG 的像素确认颜色变化,避免用 HTTP 成功回执或后端事件代替画面已更新。时间样本从外层按键开始到观察到更新的 PNG,含自动化及轮询开销,是本机 smoke 上界,不能当作净链路延迟、统计性能保证或跨机器 Benchmark。 + +模型表单另有生产构建端到端用例,填写地址和名称,连续切换技能/插件/模型,确认草稿保持。所有用例在测试实例上执行,不请求真实模型,不修改用户常驻 57161 服务。 + +## 消融与验收 + +在单个在途移动和最新位置机制生效后,移除原有 60ms 定时节流及对应 timer 状态。专项测试仍通过,因此保留简化,首个输入不再人为等待 60ms。diff 修复只更改现有 effect 的版本依赖,没有添加缓存框架。 + +- 开发 `pi list` 唯一 OpenPI 来源仍为当前隔离 worktree;源码基线 `4d229e5`。主工作区未提交工作和稳定运行副本均未改动。 +- `bun run check` 通过;`bun run test` 为 Node 1711 通过、1 项 Windows 专属跳过,Vitest 329 通过;完整生产构建 Playwright 56/56 通过。 +- 真实页面的 Control+A → 输入与 Meta+A → 输入到已显示 PNG 变色,两次本机上界样本分别为 151ms、123ms;撤销均恢复原文和红色标记。样本含自动化及轮询开销,没有与上轮的 50 次指针派发耗时直接比较。 +- 原始日志、画面样本与时间样本保留在私有证据目录 `openpi-pr561-usage-20260920/round3`;不提交用户凭据或 Session。 +- 限制:未声称完整支持系统剪贴板复制、IME、所有浏览器快捷键或公网远程桌面;延迟样本仅来自本机固定输入页面。 diff --git a/docs/research/WEB_INTERACTION_ROUND4_2026-09-21.md b/docs/research/WEB_INTERACTION_ROUND4_2026-09-21.md new file mode 100644 index 00000000..573dc0b2 --- /dev/null +++ b/docs/research/WEB_INTERACTION_ROUND4_2026-09-21.md @@ -0,0 +1,35 @@ +# Web 第四轮:表单回执、键盘和拖拽 + +- Status: validated +- Created: 2026-09-21 +- Verified: 2026-09-21(完整检查、独立实例与真实 Chromium;未部署) +- Source boundary: OpenPI `c781376` + PR #598 本轮变更;Pi SDK 0.85.1 +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充 [第三轮交互回归](WEB_INTERACTION_ROUND3_2026-09-21.md) + +## 五类可复现问题 + +| 问题 | 修复前证据 | 修复后行为 | +| --- | --- | --- | +| 修改模型后仍显示已保存 | 保存后修改输出上限、上下文长度、API 或推理开关,旧回执仍显示;四条组件用例失败,真实页面复现数值场景 | 所有字段共用编辑状态更新,修改即清除旧回执 | +| 冲突后刷新只更新 revision | 临时 models.json 外部改为 3072,页面仍编辑 2048;首次保存被拒绝,点刷新后表单仍是 2048,重试可能覆盖外部修改 | “载入最新配置”重新填充选中模型;读取中禁止编辑,未创建的新模型草稿不因无关刷新丢失 | +| 另一服务商下显示迟到保存提示 | A 保存未结束时切到 B,A 的成功提示出现在 B 下;可控 Promise 回归失败 | 按服务商作用域中止旧 UI 请求、忽略迟到回执并清空旧反馈;不声称撤回可能完成的原生写入 | +| 设置标签缺少键盘导航 | 实际页面在“常规”按右键无变化;Home/End 无效,缺少 roving tabindex | 左右循环切换、Home/End 到首尾,同步焦点、选中态与 tabpanel 标签关系 | +| 浏览器鼠标状态丢失 | 真实 Chromium 拖出面板后只有 mousedown,没有 mouseup;组件还把右键转成左键 | 捕获指针并传递有界按钮位;松开、取消、失去捕获、隐藏/关闭均释放,丢弃未发送拖拽位置 | + +修复前设置相关新增用例有 7 条失败(不同字段分别覆盖),鼠标相关 2 条组件用例失败;真实 Chromium 越界拖拽用例也失败。失败日志与浏览器 trace 已单独保留。 + +## 验证 + +- `bun run check` 通过;Node 1711 通过、1 项 Windows 专属跳过;Vitest 338 通过;生产构建 Chromium E2E 56/56 通过。 +- 真实浏览器用例验证越界拖拽仍收到 mouseup,右键收到 button=2、buttons=0。HTTP 层覆盖按钮位透传以及负数、非整数、超限值拒绝。 +- 独立 57461 实例手工复测:方向键切到模型页;保存后将 3072 改为 1024,旧成功提示消失;外部配置改为 6144 后触发冲突,再载入最新配置,表单显示 6144。没有调用真实模型。 +- `pi list` 唯一 OpenPI 来源是开发 worktree,检查时 HEAD 为 `c781376`;测试实例加载该源码及本轮构建。用户 57161 常驻副本和主工作区未提交内容未改动。 +- 原始证据保留在私有目录 `openpi-pr561-usage-20260920/round4`,包括失败/通过日志、失败浏览器 trace 和修复后画面;不提交凭据或私有 Session。这是工程回归,不是正式 Benchmark。 + +## 简化与边界 + +消融移除了模型表单独立的刷新 revision 计数器,重载请求直接携带目标身份;首次加载、标签切换保留草稿、冲突重试和回执测试仍通过,因此保留删除。字段修改共用小的状态更新函数,避免不同控件漏清回执,没有新增配置存储、模型工具或任务状态机。 + +设置仍遵循 Pi 原生配置与 `/openpi-setup` 的既有归属。鼠标协议只新增可选标准按钮位,兼容旧客户端;复制、IME、完整系统菜单和任意触控手势不在这轮验证范围内。本记录证明上述修复,不代表所有 Web UI 问题均已消除,后续迭代仍由 #597 跟踪。 diff --git a/docs/research/WEB_INTERACTION_ROUND5_2026-09-22.md b/docs/research/WEB_INTERACTION_ROUND5_2026-09-22.md new file mode 100644 index 00000000..ad5f0fd4 --- /dev/null +++ b/docs/research/WEB_INTERACTION_ROUND5_2026-09-22.md @@ -0,0 +1,43 @@ +# Web 第五轮:侧栏、设置、图片与回合反馈 + +- Status: validated +- Created: 2026-09-22 +- Verified: 2026-09-22(整合检查、完整测试、真实 Chromium 与独立真实模型;未部署) +- Source boundary: OpenPI `ece3e42`,合入 main `6132d99` 的 `d9568c4` 加 PR #598 本轮变更;Pi SDK 0.85.1 +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充 [第四轮](WEB_INTERACTION_ROUND4_2026-09-21.md) + +## 调查范围与参考 + +用户报告侧边工具卡顿、长标题挤占会话列表、复制图片后输入异常、设置与 Pi Web 的细节差异,以及缺少处理时间和可用的 `/` 菜单。参考仍使用已固定的 `agegr/pi-web` `1eb5e66a37c468aca7f0d338edb23de4fd84433e` 设置布局与模型树;Maka 的工作栏参考沿用[先前记录](WEB_NATIVE_INTERACTIONS_2026-09-20.md)。处理时间的视觉需求来自用户提供的 Codex 截图,不声称复用其内部实现或实现完整产品对等。 + +## 已复现与修复 + +| 场景 | 问题 | 修复边界 | +| --- | --- | --- | +| 隐藏工具时主会话持续更新 | Git 重复扫描、隐藏 diff 读取、子会话轮询和文件记录展开继续消耗资源 | 隐藏时减少自动工作;可见时恢复;Git 只留一个在途请求和一次后续刷新,打开面板提升后台刷新优先级 | +| 长会话标题 | 多行完整首条消息撑高侧栏 | 固定紧凑单行、省略显示,完整标题仍可查看 | +| 混合文字与图片粘贴 | 文件列表兼容、原生文字粘贴与导入队列边界不完整 | 保留原生文字光标/撤销;有界顺序导入;单个坏文件不丢掉其他有效图片 | +| 图片读取时首次创建会话 | 设置或思考控件创建 Session,待导入图片被当作跨会话数据丢弃 | 只有匹配创建回执的 Session ID、路径和工作区才迁移;真实跨会话切换仍取消 | +| 已授权图片文件 | 只有元数据,缺少可读预览;相同内容的元数据刷新又会重复下载 | 使用现有 Session/handle/revision 授权下载;字节检查 PNG/JPEG/GIF/WebP,20 MiB 上限;身份撤销、切换与关闭中止读取并回收 URL | +| 设置编辑与窄屏使用 | 模型树与编辑对象分离、草稿切换丢失、窄屏图标难识别 | 模型树直接选编辑对象,保留同 Session 草稿;文字分类选择器、恢复默认尺寸、明确忙碌与结果反馈 | +| `/` 命令菜单 | 已发现命令全部不可用且缺少原因 | 来源核对后的原生 setup 可执行;无参数面板命令打开现有工具;其他命令展示原因,带参面板命令保留草稿并解释 | +| 执行中与结束后的耗时 | 无完整回合耗时;异步原生命令返回后可能过早丢掉执行身份 | 从 Pi agent_start 计算单调耗时,以 agent_settled 结束;原生 Session custom entry 保存显示证据,刷新恢复;取消请求仍只发送执行身份 | +| setup 请求结果 | 展示逻辑把 setup 后直到下一条普通用户消息的内容全部隐藏,留下等待回复 | 原生 custom message 的 details 保留原始命令;展示操作、结果、错误和耗时,去重 optimistic 请求;旧记录保留结果且不重试无关上一条任务 | + +## 验证与证据边界 + +专项测试分别覆盖输入/取消、文件授权与释放、设置表单、原生执行事实和 UI 投影。真实 Chromium 覆盖系统剪贴板 PNG 与混合文字、图片预览、390px 设置导航、模型草稿、长标题、运行/结束时间,以及当前 package 的原生命令发现。命令 E2E 的服务用隔离 agent 目录加载当前 checkout,先运行 provenance 检查,避免空扩展配置造成假测试。 + +独立 57561 测试服务的 `pi list` 唯一 OpenPI 来源为开发 checkout。真实模型第一次外观修改已确认落盘,界面显示原始请求、最终答复和用时 25s;同一 Session 第二次请求没有应用,模型声称工具已经隐藏。独立的真实 SDK/假 provider 与两次真实模型配置均确认后续请求会重新带入 writer,真实采样中的工具状态依次为 present/absent/present/absent,最终深色配置已落盘。这些反例排除了“每次第二轮工具必然缺失”的结论;历史成功提示的绝对措辞可能误导模型,是推断而非已证明的唯一原因。新请求明确声明新 episode,成功/关闭提示限定在已结束 episode,运行时单次 writer 约束不变。 + +当前整合验证:`bun run check` 通过;Node 1856 passed / 1 Windows-only skip;Vitest 417 passed;生产构建 Chromium E2E 61/61 passed。设置弹窗的可访问性检查等待实际入场动画结束;冷启动浏览器先确认 open 请求回执,再验证图片显示,没有关闭 Chromium sandbox 或放宽功能断言。Windows 的 POSIX 文件权限断言仅在适用平台执行,配置内容与安全边界测试保留。 + +新 episode 提示的专项 25/25 通过,真实 SDK 集成覆盖关闭、普通回合、成功应用、再次成功应用及重复调用阻止。澄清后另两次真实配置各成功应用一次,耗时约 9.96s 和 7.98s;工具再次出现、完成后再次隐藏的 payload 结构已单独采样。最终完整检查与测试重新通过;远端新 CI 尚未执行。日志和截图以私有目录 `openpi-pr561-usage-20260920/round5-20260922-1714` 保存,包含路径、大小和 SHA-256 manifest;私有模型 Session 留在独立测试目录,不发布凭据或 provider payload。这是工程使用回归,不是通用响应时间 Benchmark。 + +## 简化与限制 + +消融删除了多余剪贴板文件包装函数;图片预览依赖实际下载身份,复用已有授权协议而不增加图片代理;运行时间只有当前回合一个 ticker。移除创建回执的路径校验会导致错误路径携带图片,已由失败回归证明不能省略。移除实际 agent_start 边界会把准入等待算入执行时间,因此保留。 + +没有新增设置存储、模型工具、自然语言解析器或第二套会话状态机。OpenPI 自有设置继续使用 `/openpi-setup`。旧 Session 缺少可靠完整回合时间时不补造历史耗时。PNG 仍有带宽成本,SVG/HTML 不作为可执行图片预览;TIFF、HEIC、完整 IME、Safari、系统弹窗及任意浏览器手势未在本轮宣称覆盖。用户 57161 常驻服务未部署或重启。 diff --git a/docs/research/WEB_NATIVE_INTERACTIONS_2026-09-20.md b/docs/research/WEB_NATIVE_INTERACTIONS_2026-09-20.md new file mode 100644 index 00000000..47b2f779 --- /dev/null +++ b/docs/research/WEB_NATIVE_INTERACTIONS_2026-09-20.md @@ -0,0 +1,60 @@ +# Web 原生能力与交互修复 + +- Status: validated +- Created: 2026-09-20 +- Verified: 2026-09-21(来源核对、完整检查、回归与独立实例 smoke;不代表生产部署) +- Source boundary: OpenPI `1d9607b` 后的 PR #598;Pi SDK 0.85.1;下列冻结的开源参考与当日官方文档 +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充前两轮 PR #561 使用测试记录,不改写其结论 + +## 已核对的参考 + +| 项目 | 来源与观察 | 本次采用范围 | +| --- | --- | --- | +| Codex | [官方 Review 文档](https://learn.chatgpt.com/docs/code-review?surface=app)区分未暂存、暂存、提交、分支、Last turn,并支持多仓库选择 | 区分 Git 状态和会话编辑证据;不推断闭源应用的基线实现 | +| Maka | [`git-review.ts`](https://github.com/apache/maka/blob/0dc1142aa90000e45627017e846b6d57d1e165e0/packages/core/src/git-review.ts) 提供 branch/unstaged/staged;[browser-host.ts](https://github.com/apache/maka/blob/0dc1142aa90000e45627017e846b6d57d1e165e0/apps/desktop/src/main/browser/browser-host.ts) 使用 Electron WebContentsView | Git 原生比较范围;桌面浏览器组件不能直接搬到纯 Web | +| Claude Code | [Checkpointing](https://code.claude.com/docs/en/checkpointing) 记录原生文件编辑;Bash 编辑和多数子代理编辑不在回退保证内,也不替代 Git | 不把会话记录冒充完整 Git 差异或所有文件的回退能力 | +| agegr/pi-web | [`git-status.ts`](https://github.com/agegr/pi-web/blob/1eb5e66a37c468aca7f0d338edb23de4fd84433e/lib/git-status.ts)、[`file-access.ts`](https://github.com/agegr/pi-web/blob/1eb5e66a37c468aca7f0d338edb23de4fd84433e/lib/file-access.ts);原生凭据、models.json、技能 API | 列表与逐文件 diff 分离;文件读取权限单独处理;Web 表单连接 Pi 原生配置 | +| jmfederico/pi-web | [`git-backend.ts`](https://github.com/jmfederico/pi-web/blob/60a29acbfc710908e73b0df9857cb63b53b672d3/pi-web-plugins/git/git-backend.ts) 使用 Git porcelain 状态与按路径 diff | 复用 Git 机制,不要求先快照所有未跟踪文件 | +| Chromium DevTools | [ScreencastView.ts](https://github.com/ChromeDevTools/devtools-frontend/blob/main/front_end/panels/screencast/ScreencastView.ts) 考虑像素密度与显示尺寸 | DPR 感知的 CDP screencast;OpenPI 自己维护认证、背压与会话生命周期 | + +## 浏览器方案比较与选择 + +当前基础是本机独立 Chromium + CDP + Web 图像视口,不是嵌入目标 URL 的 iframe。旧实现轮询 JPEG,每次输入还重新读取页面状态;固定 1x 绘制又在高 DPI 面板中放大。用户提供的模糊、卡顿和裁切截图与这些实现问题相符,但截图不能单独证明所有卡顿都来自这一层。 + +现成系统需要连同宿主考虑:[Browserless LiveURL](https://docs.browserless.io/baas/monitor-sessions/embedding-live-url) 是浏览器后端及交互视图;[noVNC](https://github.com/novnc/noVNC) 需要 VNC/WebSocket 服务;[KasmVNC](https://docs.kasmvnc.com/docs/install/) 面向桌面服务器环境;[Electron WebContentsView](https://www.electronjs.org/docs/latest/api/web-contents-view) 需要桌面壳。它们可作为独立产品方案,但不是无成本嵌入一段前端即可替换。 + +本次保留现有 Chromium 生命周期,改用 PNG screencast 推送、最多 4MP 的 DPR 视口、单个解码任务与最新待显示帧、滚轮合并及取消清理。页面状态低频刷新;指针输入不重复读取整页状态。没有新增 VNC 服务、云浏览器账户或桌面壳。PNG 仍有编码/带宽成本,这不是通用视频远程桌面性能保证。 + +## Git、文件证据与读取 + +只读现场调查发现:用户选中的目录含约 1.25 万个未跟踪文件,首个检查文件约 8 MiB,超过会话基线 4 MiB 配额。旧实现未保存基线,却只显示通用 Git 读取错误。编辑证据还包含其他 worktree 的绝对路径,旧文件预览只允许当前工作区。 + +修复默认读取 Git 未暂存状态,提供暂存与分支比较,先返回有界列表,再加载选中文件 diff。会话基线是独立选项;缺失时明确说明不能恢复,并指向可用的 Git 范围或会话记录,不提高配额掩盖问题。会话文件列表显示原生工具返回的编辑 diff;Bash 等未提供对应证据的操作不被伪造为完整编辑记录。 + +工作区外的文件必须由用户显式点击只读打开。授权绑定当前 Session、单个文件及文件身份,不扩大到父目录或相邻链接;刷新保留该显式选择,Session 变化仍会撤销。百分号路径只解码一次。所有这些是浏览器文件访问边界,不限制模型原本被授予的原生工具权限。 + +## 设置与 setup 的关系 + +此前的 `1d9607b` 已修复设置/思考菜单在尚无 Session 时被禁用的问题:按需准备真实 Pi Session,不发送虚构 Prompt;草稿通过精确创建回执交接。设置与思考两种先后顺序分别有浏览器回归。 + +会话初始化还发现 headless `session_start` 为未使用的 fd 搜索工具提前查找/下载二进制。它可能等待约 30 秒,而 Web 请求窗口为 15 秒。改为工具被调用时加载,TUI 保留原行为。独立 agent 的冷启动样本从约 4.2 秒降到约 0.39 秒,旁路启动回执约 0.03 秒;这些不是模型回答延迟,也不能证明用户截图中每次超时只有这一种原因。消融移除 headless guard 后,阻塞查找的专项回归失败,恢复后通过。 + +设置页不应只有“由 Pi 管理”的说明。模型表单写入同一份原生 models.json,保留未编辑的字段与服务商,使用修订校验避免覆盖已观察到的外部修改。API Key 通过 ModelRuntime.login 保存,GET 只返回无密钥字段,失败消息也不透传服务商异常中的凭据。写入要求当前 Session 空闲。支持 Responses、Chat Completions、Anthropic Messages 的自定义模型表单;OAuth 和多字段认证仍保留 Pi 原生登录,未伪装成已经支持 Web OAuth。 + +OpenPI 自有设置仍通过 `/openpi-setup` 的原有 episode 与 typed writer;技能/插件需求及角色模型/并发表单提交到该入口,不增设 package 配置写入接口。表单区分请求被接受、执行中和实际保存的会话回执;需要原生确认的安装仍沿用确认流程。密钥不进入这条模型对话路径。更改运行中扩展的加载仍由原生生命周期负责,不自动热加载任意插件。 + +## 验证与限制 + +- 独立 agent 目录的 `pi list` 唯一 OpenPI 来源为开发 worktree,HEAD `1d9607b` + 本 PR 变更;用户常驻 57161 实例未更新、未重启。 +- 浏览器专项使用本机 Chrome 与临时 HTTP 页面:720×900 CSS 视口获得 1440×1800 PNG;800×1100 获得 1600×2200 PNG;高 DPI 输入坐标成功写入字段。没有用静态 mock 声称端到端性能已达标。 +- 原生 Pi 集成测试验证模型保存后重新加载、凭据持久化、读接口脱敏和忙碌/过期 Session 拒绝;HTTP 测试验证未认证写入拒绝与异常脱敏;组件测试分开验证表单和 setup 提交语义。 +- Git 测试覆盖大未跟踪文件、摘要/详情分离和比较范围;文件测试覆盖单文件授权、Session/路径边界、百分号路径与刷新。 +- 完整 `bun run check` 通过;`bun run test`:Node 1711 通过、1 项 Windows 专属测试跳过,Vitest 317 通过;Playwright 54/54 通过,包括跨工作区文件显式授权后预览/刷新、桌面与移动端、无障碍和旧服务商状态兼容性。 +- 独立 57361 实例手工验证模型表单保存并更新列表、测试密钥保存后清空输入、子代理表单仍走 setup。只使用临时配置和不可用的测试服务商端点,没有向模型发起请求;测试页与服务已关闭。 +- 私有原始证据归档:本机 `openpi-pr561-usage-20260920`,包含浏览器 smoke、临时 agent 配置与测试日志,不提交凭据或用户 Session。这些是工程 smoke/回归证据,不是具有冻结模型任务与用量统计的正式 Benchmark。 + +消融目标:保留原生 Git、Pi 配置与 CDP seam,避免另外增加快照框架、配置控制平面或屏幕共享服务。移除等待显示帧队列,仅保留最新帧;移除全仓文件内容预读,仅在选中后读取。相关回归验证仍覆盖输入、显示和文件访问行为。 + +2026-09-21 的消融移除了配置写入中重复的 runtime 通知,只保留 Host 保存回执后的通知,相关回归仍通过,因此保留简化。另一次临时移除当前 Session 的模型重新绑定后,原生模型测试明确失败:会话仍使用旧端点;恢复这一步后通过,因此保留。写入期间复用现有 Prompt admission 队列,避免新增一套锁或配置状态机。 diff --git a/docs/research/WEB_PR561_ASYNC_ITERATION_2026-09-20.md b/docs/research/WEB_PR561_ASYNC_ITERATION_2026-09-20.md new file mode 100644 index 00000000..78ee4b8a --- /dev/null +++ b/docs/research/WEB_PR561_ASYNC_ITERATION_2026-09-20.md @@ -0,0 +1,63 @@ +# PR #561 第二轮:异步取消、会话边界与编辑焦点 + +- 状态:validated(限下述源码、组件、runtime、Chromium 与真实模型验证;不是 Benchmark 或新的设计 Decision)。 +- 创建 / 验证日期:2026-09-20。 +- 源码边界:第一轮修复 `2ea045f290b0df4217a730a61948e8160714620b`,分支 `codex/pr-561-usage-fixes`,在同一独立 worktree 上继续。 +- 关联:[Issue #559](https://github.com/openpi-dev/openpi/issues/559)、[Issue #560](https://github.com/openpi-dev/openpi/issues/560)、原始 [PR #561](https://github.com/openpi-dev/openpi/pull/561)。合并后的持续优化由 [Issue #597](https://github.com/openpi-dev/openpi/issues/597) 跟踪。 +- Supersedes:none;补充[第一轮记录](WEB_PR561_USAGE_ITERATION_2026-09-20.md),保留其历史验证范围。 +- 后续提交:[PR #598](https://github.com/openpi-dev/openpi/pull/598) 将前两轮修复合并提交。 + +## 运行与证据边界 + +先核对 README 运行时来源约定、干净的 `2ea045f` checkout 及隔离 `pi list`。唯一 OpenPI 来源仍为此次修复的独立 checkout,完整本地路径保存在私有 provenance receipt。真实服务和正式 `web/dist/` 均来自该 worktree;修改后重建并重启后端。沿用 Node 24.20.0、Bun 1.4.2,以及仓库外的本地 Responses provider、`gpt-6-astra` / `high` 配置。 + +测试工作区为仓库外的人工夹具。将该目录初始化为独立 Git 仓库,然后在新 Web Session 中让真实模型通过普通 `write` 工具创建 `round2-note.txt`。没有修改用户原工作区、全局安装、已有密钥或模型配置。 + +## 五类已复现问题 + +| 问题 | 证据 | 修复边界 | +| --- | --- | --- | +| Git 变更串到新会话 | 组件复现 A 已有结果后切到 B,B 请求失败仍保留 A 的文件;A 的迟到返回还能在 B 的 200ms debounce 窗口内更新结果。 | 用稳定 Session id/path 作为目标;切换前清空旧投影,清理时立即中止旧请求,完成、错误与 finally 都检查取消状态。 | +| Diff 刷新抢走输入焦点 | 真实页面打开 `round2-note.txt` 的差异,在主输入框输入未发送草稿;模型轮次引发刷新后,焦点从输入框跳到差异区域。组件测试同样复现。 | 焦点跟随选中文件路径的改变,而不跟随每次网络响应中新建的文件对象。浏览器回归确认刷新后还能继续输入,草稿保留。 | +| 终端停止后继续发送排队输入 | 用可控 Promise 阻塞首个输入,排队第二条命令,再分别触发首条失败、组件卸载或输出流断开。旧实现仍发送第二条命令。 | 队列在每次实际派发前检查连接、终止和卸载状态。已发出的请求不冒充可撤销,未发出的命令不自动重放。 | +| 已取消的终端创建仍会启动 PTY | Runtime 测试分别在排队和加载阶段调用 dispose、切换 Session、切换工作目录;旧实现仍调用 spawn。 | pending create 归属精确创建请求,清理撤销其身份;开始创建及异步加载后、spawn 前再次检查。容量预留按独立请求标识释放,取消请求不能清掉较新请求的预留。dispose 后仍可为后续活动 Session 正常创建终端。 | +| 文件引用校验不能完整取消 | 校验时“取消”按钮禁用;Session 改变或组件卸载不会中止请求,迟到的成功返回仍可调用旧的插入回调。 | 校验中保留取消按钮;关闭、卸载与 Session 改变均取消请求。迟到返回不插入引用,并释放已取得的 artifact handle;旧 finally 不清除新请求身份。 | + +后三类时间窗口以确定性组件 / runtime 测试为主要证据,不宣称人工点击可每次稳定触发。终端测试不使用真实危险命令,也不把已派发命令的网络失败解释为“远端未执行”。 + +## 验证与消融 + +- 修改前新增 9 条组件断言、6 条 runtime 断言失败;另一个原有快速交互用例暴露菜单 next-frame 焦点尚未就绪的测试同步问题。该用例改为等待 Astryx 菜单焦点就绪后选择,不改变产品焦点代码来迎合测试。 +- 专项:4 个组件文件 25 / 25 通过,PTY runtime 8 / 8 通过。 +- 新增正式浏览器专项:diff 刷新保持草稿焦点、文件校验期间取消,2 / 2 通过。 +- `bun run check`:通过。 +- `bun run test`:Node 1702 passed / 1 platform skip / 0 failed;Vitest 309 / 309 passed。 +- 完整 `bun run test:web:e2e`:52 / 52 通过,包括新增两项回归和既有桌面 / 移动端 / accessibility 检查。 +- 消融:删除 Git review 中与请求 AbortSignal 重复的 generation 计数器,保留已有请求取消机制;25 条专项仍通过,因此保留简化实现。没有新增通用异步任务管理器、重试框架、用户配置项或模型工具。 + +真实模型在修复前成功创建文件并回复 `ROUND2_FOCUS_TEST`。修复后首次请求遇到本地网关 `gateway_concurrency_limit`;页面显示失败,草稿及输入焦点保留。重试成功返回 `ROUND2_FIXED_FOCUS`,刷新后焦点仍在原输入框。真实 PTY 返回 `ROUND2_TERMINAL_OK`,退出显示状态码 0;点击重新启动后返回 `ROUND2_RESTART_OK`,再次正常退出。网关限流属于本地服务状态,未计入五类产品问题。 + +本地证据身份为 `openpi-pr561-usage-20260920/round2`,在 `~/.local/state/openpi-pr561-usage-20260920/round2/` 保留 `before-ui.log`、`before-runtime.log`、`after-runtime.log`、`ablation-final.log`、`check-final.log`、`test.log`、`targeted-e2e.log`、`e2e.log`。`provenance.json` 记录最终提交、加载来源与日志摘要哈希。这些不是公开 Benchmark 原始数据;配置、认证与私有 Session 不入库。 + +未验证 Safari、真实屏幕阅读器、手机输入法或长期断线。编译仍有既有的 >500 kB bundle 提示。本轮保证限定在上述生命周期和交互边界。 + +## 合并后整合范围 + +2026-09-20,PR #561 的最终 head `c2f5b10` 已补入 PTY 创建取消修复,并通过 `45f12a4` 合并到 main。为 Issue #597 准备后续 PR 时,从该 main 建立 `codex/web-ux-597`,保留其 `InteractiveTerminalManager` 实现,只增加本轮的六条取消边界回归。本记录表格保留原始发现与当时验证的历史事实;后续 PR 的新增修复范围因此为其余九类问题,不能将上游已合并的实现再次算作新增贡献。 + +整合后在 `45f12a4` main 加本轮修复上重新验证:`bun run check` 通过;`bun run test` 为 Node 1703 passed / 1 platform skip / 0 failed,Vitest 309 / 309;Chromium 端到端 52 / 52。日志另存于原本地证据目录下的 `publish-597/`。此前真实模型、真实 PTY 和界面观察仍只证明上文明确标出的原验证版本,不能替代新基线的人工验证。 + +## 后续截图反馈:首次入口、冷启动和侧边会话 + +2026-09-20,用户在 `6c58e98` 常驻实例反馈:选了目录但尚未发送消息时,设置和思考等级均置灰;创建 Session 等待较长;主任务运行时侧边会话出现超时。用户明确要求暂不更新正在使用的实例,因此本次仅在开发 checkout 和独立端口验证,未修改或重启常驻服务。 + +已确认的源码事实与改动: + +- 设置与思考菜单此前显式拒绝 `workspaceDraft`。现在由用户点击入口时复用或创建真实 Pi Session,确认其身份后再打开;未选目录则先打开工作区选择。无需发送占位消息,也不因打开入口调用模型。选择目录取消后不创建 Session。 +- 首次草稿转移使用创建回执中的精确 Session/工作区身份,避免依赖“创建中”中间帧一定被 React 绘制;设置与思考两种打开顺序均验证不丢草稿、不重复创建、不发送消息。 +- `file-search` 的 `session_start` 原本等待 fd/rg 探测和缺失二进制下载。下载上限 30 秒,Web 请求默认 15 秒;这条初始化链也会出现在 headless 子 Session。改为 headless/Web 在真正调用搜索工具时再初始化,工具执行仍等待可用二进制并保留错误、超时和完整性检查。TUI 的原有初始化行为不变。 +- 侧边会话的开始/发送显示明确的准备状态;已取消的返回不再改变面板。浏览器回归在主 Session 显示 running 时完成侧边开始、追问与取消;该测试是 UI/协议夹具,不冒充真实模型并发压测。 + +独立本机观察:全新 agent 目录第一次创建 Session 约 4.232 秒,缓存已就绪时约 0.239 秒;修改后另一个无搜索二进制缓存的 agent 目录首次创建约 0.387 秒,侧边启动回执约 0.026 秒,启动过程没有安装 fd。这里测量创建/启动回执,不是模型完成时间;网络、进程缓存和机器负载不同,不构成普遍性能保证。此前那一次用户超时没有保留完整请求时序,不能仅凭截图断言唯一原因;本轮消除了可直接造成该类超时的启动等待,并验证侧边入口不被主任务运行状态禁用。 + +消融时移除 headless 早返回,受控的不可完成二进制初始化使启动回归重新失败;恢复后通过。首次草稿逻辑也移除了对 `sessionSwitching` 中间渲染的推断,改用已有创建操作产生的回执。真实独立 Web 页面验证了:尚未发消息时打开设置、保留草稿、将思考等级从 high 改为 low。验证日志保存在原证据目录的 `first-use/` 与 `latency/`;最终完整回归结果由 PR #598 的 Validation 更新记录。 diff --git a/docs/research/WEB_PR561_USAGE_ITERATION_2026-09-20.md b/docs/research/WEB_PR561_USAGE_ITERATION_2026-09-20.md new file mode 100644 index 00000000..2d83e536 --- /dev/null +++ b/docs/research/WEB_PR561_USAGE_ITERATION_2026-09-20.md @@ -0,0 +1,45 @@ +# PR #561 使用测试与五项交互修复 + +- 状态:validated(限下述源码、组件、Host、Chromium 与真实模型验证;不是 Benchmark 或设计 Decision)。 +- 创建 / 验证日期:2026-09-20。 +- 源码边界:PR #561 head `089d3d48d5df80adcaf3a7a9d8b2bbd6a2d5de18`,修复分支 `codex/pr-561-usage-fixes`。 +- 关联讨论:[Issue #559](https://github.com/openpi-dev/openpi/issues/559)、[Issue #560](https://github.com/openpi-dev/openpi/issues/560);原始 [PR #561](https://github.com/openpi-dev/openpi/pull/561)。合并后的持续优化由 [Issue #597](https://github.com/openpi-dev/openpi/issues/597) 跟踪。 +- Supersedes:none;补充此前体验记录,不改写历史结论。 +- 后续提交:[PR #598](https://github.com/openpi-dev/openpi/pull/598) 将本轮与第二轮修复合并到一个 PR。 + +## 运行来源 + +原工作区存在大量用户修改,因此从 PR head 创建独立 worktree。使用 Node 24.20.0、Bun 1.4.2 和 frozen lockfile 安装依赖。隔离 `PI_CODING_AGENT_DIR` 中 `pi list` 的唯一 OpenPI 来源是此次修复的独立 checkout,完整本地路径保存在私有 provenance receipt。没有改写原工作区、全局 Pi 安装或既有 Codex 配置。 + +测试实例从该 worktree 的 `bin/openpi.js` 启动,提供真实生产静态产物。按本地 Codex 配置接入 loopback Responses 服务,使用 `gpt-6-astra` / `high`,保留普通模型工具。凭据仅存于仓库外隔离配置,文件权限 0600,不进入本文或测试夹具。 + +## 已验证的问题与修复 + +| 问题 | 修复前证据 | 修复与验收 | +| --- | --- | --- | +| 图片未读完就允许发送 | 延迟 `File.arrayBuffer()`,输入文字后发送按钮仍可用。发送代码没有导入锁,会提交不含待导入图片的草稿。 | 所有提交路径共用导入锁,按钮同步禁用;Enter 不提交,图片就绪后原文字和图片一起发送。 | +| 图片越过会话边界 | 会话 A 选图,读取完成前切到 B;旧读取完成后 B 的空草稿获得图片并启用发送。可控 Promise 组件测试复现,不声称普通磁盘读速下每次可手工触发。 | 读取以独立身份归属于当前草稿,切换或卸载后旧结果、错误和 finally 均不能影响新草稿。另验证 A → B → A 和新旧读取重叠。 | +| 内嵌浏览器无法粘贴 | 实际点击本地网页输入框,粘贴 `PASTE_中文_561` 后内容仍是原来的 `replace me`。 | 明确的文本动作经现有认证、活动 Session 检查和串行 CDP 生命周期送入 `Input.insertText`;实机输入框显示完整中文文本。每次限制 16,384 个 UTF-16 code units,超限显式提示且不截断发送。 | +| Shift 修饰键丢失 | 实际 Tab 后再 Shift+Tab,焦点继续前移到链接;页面键盘日志显示 `shift=false`。 | key down/up 传递 Shift 位,Host 验证 modifiers 范围;实机 Shift+Tab 回到第一输入框,页面日志显示 `shift=true`。 | +| 分栏只能用指针调整 | 实际页面两个手柄均为 `aria-hidden=true`、`tabIndex=-1`,没有可访问角色。 | 使用带名称、范围和当前值的可聚焦 separator。方向键按屏幕方向移动,Home/End 到边界,Enter 复位;实机左侧 280→296、右侧 520→536。保留拖动、阈值收起和恢复行为。 | + +浏览器普通字母与 Shift 选择继续走已有键盘动作;本轮未宣称完整支持 IME、跨应用剪切/复制或所有系统快捷键。图片异步竞态的确定性证据来自组件测试;真实模型测试只证明普通文字与工具链路,不冒充图像理解验收。 + +## 验证与消融 + +- 新增回归在 PR 原始实现上:6 条失败,8 条既有用例通过(左右手柄各一条断言)。 +- 修复后的专项组件测试:通过;新增 A → B → A 与交错导入覆盖。 +- Host 专项:41 / 41 通过,覆盖文本与 Shift 动作、空值/超限/错误类型、非法修饰键、过期 Session 拒绝。 +- `bun run check`:通过。 +- `bun run test`:Node 1696 passed / 1 platform skip / 0 failed;Vitest 300 / 300 passed。 +- 最终 `bun run test:web:e2e`:50 / 50 通过,含图片发送、真实 PTY、桌面/移动布局、键盘分栏、指针收起/恢复和 accessibility。首轮 4 条 accessibility 检查曾发现新增可聚焦手柄位于 landmark 外;最终将手柄归入命名面板调整区域,保留顶层 main,没有关闭或削弱 axe 规则。 +- 真实模型:修复前 `bash` 返回 `OPENPI_561_LIVE_OK`;重建、重启后返回 `OPENPI_561_FIXED_OK`,页面均显示完成。 +- 消融:移除仅用于本地取消标记的 `AbortController`,改用单一导入身份;原 14 条专项测试仍通过,因此保留更小实现。没有引入通用上传管理器、额外状态机、配置项或常驻模型工具。 + +新增逻辑归属现有 React 草稿生命周期、WebHost 输入验证和 Session-bound CDP 服务。Pi Session、权限、模型选择与工具执行事实仍由原有层拥有。 + +本地证据身份:`openpi-pr561-usage-20260920`,日志位于 `~/.local/state/openpi-pr561-usage-20260920/`,最终凭据为 `check-final.log`、`test-verified.log`、`e2e-verified.log`。`before-tests.log`、`ablation.log`、`host-tests.log` 和早期 `e2e*.log` 保留失败与消融过程。`provenance.json` 记录最终源码提交、加载来源和最终日志摘要哈希。其中隔离模型配置、凭据与 Session 为私有原始证据,不发布;公共可复现证据是仓库测试及本次代码差异。 + +## 局限 + +真实桌面测试使用本机 Chromium,自动化还覆盖已有移动布局与 accessibility 用例;未验证 Safari、手机软键盘、IME 组合输入、真实屏幕阅读器或长期断线。构建仍有原有的大于 500 kB chunk 提示。本轮只处理上述五项,不代表整个 PR 已无其他体验问题。 diff --git a/docs/research/WEB_READING_AND_LIFECYCLE_2026-09-22.md b/docs/research/WEB_READING_AND_LIFECYCLE_2026-09-22.md new file mode 100644 index 00000000..b409a1a4 --- /dev/null +++ b/docs/research/WEB_READING_AND_LIFECYCLE_2026-09-22.md @@ -0,0 +1,66 @@ +# Web 历史阅读、正文排版与异步面板 + +- Status: validated +- Created: 2026-09-22 +- Verified: 2026-09-23(完整本地门禁、真实 SDK 与 Chromium;未部署) +- Source boundary: OpenPI `fbf15d2` → `4480b8d`,合并 main `35ebf01`、`43cd2e9` 与 PR #598 后续变更;Pi SDK 0.85.1 +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充[第五轮](WEB_INTERACTION_ROUND5_2026-09-22.md) + +## 已复现的问题 + +- 自建原生 Session 从 63 条增长到 264 条后,接口只返回尾部 250 条,最初用户消息缺失;124 条大输出也触发 2 MiB 截断。页面没有历史加载入口。 +- 正文列表编号被基础样式清除,长文件路径占据正文并居中折行。新正文样式保持字号配置和执行过程设计,桌面与窄屏复测通过。 +- 侧边对话 A 发送回执迟到时,覆盖用户已经切到 B 的导航和草稿。 +- 用户已回主输入框,终端连接或重启结束后仍抢走焦点。 +- 文件预览因设置或 Session 变化自动关闭,但外层面板状态没有清除。 +- 浏览器被信号终止时只检查 exitCode,等待满 8 秒后误报超时;spawn 权限错误也没有正确接收。SIGTERM/EACCES 两项真实子进程修复验证通过。 +- 背景任务切走后只在最终结束时发送进度,页面又混用了另一当前会话的 idle;返回原会话还会丢失计时/取消身份。现在按原生 Session owner 保存现有 trace,后台边界事件限频更新,输入控制与所看会话的状态分开。 +- 同文 native 消息会一次删除多个待处理输入;历史已有同文内容或记录滑出窗口时,输入也可能消失或重新出现在末尾。现在区分准入与展示关联、保存作用域和发送时间,并保守保留 Pi 仍在排队的消息。 +- 复制 JSONL 保留同一个 ID 时,界面可能显示另一文件的停止/设置控件。控制校验现在同时使用 Pi 当前 ID 和文件路径;旁观视图有显式激活入口。 +- 运行中只显示 Stop,无法通过鼠标发送追加消息;旁观时还显示了另一会话的模型及旧排队回执。现在有草稿时可分别选择发送或停止,旁观隐藏这些控制者信息。 +- 另一标签页切换输入控制会话时,当前历史阅读视图也被卸载,已加载旧页和滚动位置丢失。现在保留所看 Session 的视图,并在原生控制身份刷新前禁用输入;App、Store 和历史 hook 的集成回归及撤除权限保护的消融已验证。 +- 嵌入浏览器初始状态迟到会覆盖用户正在输入或已经打开的新网址;打开、刷新请求结束也会覆盖期间新写的地址。状态恢复现在尊重草稿,导航回执仅在地址没有再编辑时同步输入框。 + +## 实现与验证边界 + +历史分页复用 Pi 原生 Session 分支 ancestry,不新增持久会话存储或服务端分支缓存。单页及客户端阅读窗口有界,默认跟随最新时不积累整个历史。prepend 后保持阅读锚点,Session 或分支改变时不混入旧页。 + +侧边三类问题的五项基线回归失败,修复后专项通过;移除导航代际检查再次复现错误回跳。导航不会取消已提交后台任务,上下文关闭不会恢复旧焦点或返回旧工具。 + +`2f78cf3` 的本地门禁:`bun run check` 通过;Node 1869 passed / 1 Windows-only skip;Vitest 459 passed;生产构建 Chromium 65/65 passed。真实历史用例包括找回最初问题、加载前后锚点偏移不超过 2px、流式追加、发送后回最新和迟到旧分支页拒绝。旁观用例通过真实 SDK/HTTP 验证发送按钮与 Enter、A/B 隔离、回到 A 后停止仅影响 A,以及界面最终退出运行状态。faux provider 不发送真实模型请求,这些结果不是模型能力或通用性能 Benchmark。 + +收尾的阅读窗口及地址草稿修复后,完整检查再次通过,Node 1869 passed / 1 skip、Vitest 465 passed、全量 Chromium 65/65 passed。消融移除地址保护后四条用例失败;移除控制身份清空后,历史仍保留但未确认权限时输入错误地可用,回归准确失败。恢复保护后全部通过,没有增加新的输入队列或视图控制状态机。 + +PR `fbf15d2` 的 Node 22/24/26 与 Windows CI 通过,Web E2E 60/61,失败为嵌入 Chromium 启动超时。原日志没有退出信号或 stderr,因此不能认定 AppArmor 是唯一原因。Linux CI 改用 runner 已安装 Chrome,保留原生 sandbox;新 CI 尚待验证。依据见 [Chromium 官方说明](https://chromium.googlesource.com/chromium/src/+/main/docs/security/apparmor-userns-restrictions.md)与 [runner 软件清单](https://github.com/actions/runner-images/blob/main/images/ubuntu/Ubuntu2404-Readme.md)。 + +后续 `2f78cf3` 的 [CI 35727178052](https://github.com/openpi-dev/openpi/actions/runs/35727178052) 中,Node 22/24/26 通过,Linux Chromium 成功启动,Web E2E 64/65。唯一失败的 trace 表明越界松开请求按序送达并返回 200,截图显示测试在选中的文字内开始拖动。本地独立 Chromium 诊断证明原生拖放可产生 `dragstart → pointercancel → dragend` 而没有 `mouseup`,与 [HTML 拖放模型](https://html.spec.whatwg.org/multipage/dnd.html#drag-and-drop-processing-model)一致;旧 CI fixture 未记录 drag 事件,故该次准确事件序列仍是推断。修订测试分别要求普通拖动的 `mouseup`,以及文字拖放的松开终态与对应 `dragstart`,没有扩大输入协议。Windows 原生并行与串行组均完成且零失败,UI 组仍在逐项通过时触发整步五分钟上限;保留原生进程测试的隔离,并将完整套件上限改为十分钟,job 仍有十五分钟上限。修订结果另以新提交的 CI 为准。 + +`4e9ba87` 的 [CI 35730543135](https://github.com/openpi-dev/openpi/actions/runs/35730543135) 已全部通过:Node 22.19.0 / 24 / 26、Linux Web E2E 65/65、Windows 进程生命周期及完整套件。没有跳过失败测试或关闭浏览器 sandbox。 + +纯文档提交 `6126e38` 的[后续 CI](https://github.com/openpi-dev/openpi/actions/runs/35731747730) 再次出现约 8 秒的 Chromium 调试端口等待超时,其他 64 项 Web 测试通过。这份反例说明一次绿色结果不能证明冷启动稳定,也不支持将先前启动超时直接归因为 AppArmor。旧实现丢弃了 Chromium stderr,因此该次真实原因仍未知。收尾补充启动阶段、耗时、端口文件读取错误及最多 4 KiB stderr 尾部,失败证据只写入宿主日志和 Error cause,HTTP 仍返回原有文案;成功后释放尾部并继续排空管道。Linux CI 增加三次独立新进程、新配置目录的冷启动验证,任一失败都保持失败,不以重试掩盖,也不放宽原八秒启动预算。 + +`0b5a0f2` 的[冷启动门禁](https://github.com/openpi-dev/openpi/actions/runs/35734591338) 进一步抓到清理竞态:第一次启动后的 dispose 删除 `Default` 目录时发生 `ENOTEMPTY`。旧实现发送 SIGKILL 后立即删除目录,没有等待进程及继承 stderr 的后代关闭。修订复用原生 close 事实、保留两秒关闭上限,关闭成功后才删除配置目录,并为文件系统清理保留有限重试;未能关闭时不删除仍可能写入的目录,已有 Session 仍可重试清理。启动失败后的清理错误放入 cause,不覆盖最初的启动错误。真实子进程及后代最终写入的回归在旧实现上两项失败,在修订实现上通过;这验证清理顺序,不声称已经证明它是此前端口等待超时的唯一原因。 + +该关闭事实的测试区分平台:后代继承 stderr 并延后 close 的场景限定 POSIX,Windows 不假设相同管道语义;所有平台另有真实单进程回归,确认 close 到达后才开始删除目录,旧实现仍会准确失败。浏览器输入夹具先等待 React 完成被动效果再发送滚轮事件,保留在途请求数量和累计滚动距离的原断言。 + +用户“卡住”反馈的只读证据有两层:Git 拉取先在 240 秒后超时,后续两次约 75 秒后报告连接 GitHub 失败;后台仍消费后续消息并写入新工具结果。事件环则在两次会话切换后停止更新该后台任务。常驻 LaunchAgent 未显式配置工具 shell 所使用的代理,这与连接失败相关,但仅凭 plist 不能证明完整进程环境。没有修改该实例的代理、Git 配置或运行进程。 + +待应用的 LaunchAgent 副本已在私有证据目录准备,复用现有工具 shell 的代理并保留 loopback 绕过。使用该候选环境单独执行只读 `git ls-remote`,约 1.98 秒成功;这是单次连通性验证,不保证后续网络请求时延。原 plist 和常驻进程均未改动。 + +后续对照使用相同的只读 Git 命令及最小基础环境,仅分别合入原 plist 与候选 plist 的环境变量:原环境约 2.97 秒成功,候选约 1.70 秒成功。这份反证意味着不能认定代理缺失是此前超时的唯一原因,也不能把候选配置称作已验证的永久网络修复。后台进度盲点的确定性复现与修复不依赖该网络推断。 + +真实 SDK + WebHost + Chromium 的旁观用例已验证:A 工具挂起期间激活 B,A 页面仍保留自身消息、排队数及耗时;B 的同文输入不会混入 A;重新控制 A 后停止仅影响 A,B 继续运行。原始日志、截图及 SDK 调查保存在私有 `openpi-pr561-usage-20260920/round6-20260922`,manifest 记录文件名、大小及 SHA-256;没有发布凭据或用户 Session。用户 57161 常驻版本保持不变。 + +## 展示关联与原生事实 + +本轮同步 main `35ebf01`(#595)的结构化问答、计划卡片和原生命令反馈。原 setup 请求继续可见,只有带明确原生父节点关系的内部展开记录被折叠;`/usage` 改用主线原生命令反馈。计划修改和问答归属沿用 Session ID 加文件路径,旧计划回执不能清除重新取得控制后的新请求。浏览器帧流等待主线新增的异步认证头,低高度输入区给计划栏预留空间,命令菜单按实际可用空间选择上下位置。 + +后续 main `43cd2e9` 的配置诊断/回滚、等待中的配置锁恢复、工作区准入后 Session 身份复查、Windows PTY 关闭、工作流产物节点预算及会话日期也已保留。配置回执仍区分“保存文档”和“实际变更”,Web 表单继续走同一个原生 setup 入口。 + +合并时将 React 渲染身份与原生 entryId 明确分开,后者继续负责历史锚点和准入展示关联。没有消息身份的旧事件只做一次性的完整内容匹配;已经提供原生身份的消息不按正文去重,避免吞掉独立的同文输入。新增的两个重复气泡回归保留原失败证据,执行详情滚动区也补齐键盘可达性。 + +Pi SDK 0.85.1 的 UserMessage 没有请求 ID,message_end 先通知监听器,再追加到 Session。真实 SDK 反例中,外部原生同文 follow-up 先于 Web B/C 入队,按 Web trace FIFO 推断送达会把外部记录错误关联给 B。因此本轮没有发布伪 `prompt_delivered`,也没有包装 SDK 公共方法或加入新的调用上下文框架。 + +UI 只有在明确准入后才尝试合并重复展示,原生排队数量作为保留消息的下限;原生文本块与普通文本统一比较,图片身份不弱化成仅 MIME。展示关联只用于防止重复/回漂和释放本地临时数据,不作为模型执行、配置保存或磁盘落盘的证明。额外原生输入造成关联不明确时保守保留展示;更强的逐请求消息关联需要 SDK 提供创建消息时的正式扩展点,这只是调查建议,不是已采纳约束。 diff --git a/docs/research/WEB_TURN_EVIDENCE_AND_ITEM_RECOVERY_2026-09-23.md b/docs/research/WEB_TURN_EVIDENCE_AND_ITEM_RECOVERY_2026-09-23.md new file mode 100644 index 00000000..e6beb0ff --- /dev/null +++ b/docs/research/WEB_TURN_EVIDENCE_AND_ITEM_RECOVERY_2026-09-23.md @@ -0,0 +1,34 @@ +# Web 逐轮变更与历史条目恢复 + +- Status: validated locally; PR CI pending +- Created: 2026-09-23 +- Verified: 2026-09-23(完整本地检查、单测与 Chromium 67/67;提交后 CI 待确认) +- Source boundary: PR #598 的 `codex/web-ux-597` 工作树,基线 `250731f`;Pi SDK 0.85.1 +- Issue: [#597](https://github.com/openpi-dev/openpi/issues/597) +- PR: [#598](https://github.com/openpi-dev/openpi/pull/598) +- Supersedes: none;补充[历史阅读与生命周期](WEB_READING_AND_LIFECYCLE_2026-09-22.md)和[Git 比较身份](WEB_GIT_REVIEW_IDENTITY_2026-09-23.md) + +## 参考与问题边界 + +Codex 的公开 App Server 协议把按游标读取轮次与按需读取条目分开:[`thread/turns/list`](https://github.com/openai/codex/blob/main/codex-rs/app-server-protocol/src/protocol/v2/thread.rs) 的 `nextCursor` 继续向前读取,`itemsView` 可选择摘要或完整条目;[`thread/items/list`](https://github.com/openai/codex/blob/main/codex-rs/app-server-protocol/src/protocol/v2/thread.rs) 按条目读取。其 [`turn/diff/updated`](https://github.com/openai/codex/blob/main/codex-rs/app-server-protocol/src/protocol/v2/turn.rs) 带轮次 ID 和聚合 diff。公开代码不包含 Codex 桌面端的 React 展示实现;本次卡片位置、三行预览及显式加载入口以用户截图为视觉参考,不声称复用了桌面组件。 + +OpenPI 原有 Web 快照每次最多投影 250 个 Pi Session 条目及 2 MiB;单条正文最多 12,000 字符和 64 段。旧的“加载更早的消息”只能取得更早的**条目**,不能恢复已经截断的**同一条消息正文**。因此“已到会话开头”和全局截断警告可以同时出现,却没有正文恢复操作。输入框的累计 Git 文件气泡也把发送前已经存在的脏文件算在当前对话旁边,不能表达某一轮的修改。 + +## 实现判断 + +- 历史页继续基于 Pi `SessionManager.getBranch()` 和原生条目 ID;超过 20 个用户回合的普通页对齐用户消息,同时保留 250 条/2 MiB 上限。未超过轮数上限的现有前序条目不额外裁掉,供新消息与本地发送锚点对齐。单轮超过预算时仍分段,不能跳过原生条目。客户端按 ID 去重、保留滚动锚点并在分支变化时拒绝迟到页。 +- 可见用户/助手正文被截断时,用户显式触发 `/api/session/item`,以精确 Session ID、文件路径、原生条目 ID 及字符游标每次读取最多 32,000 字符。服务端只读当前 Pi 分支上的用户/助手正文或原生命令输入;工具结果不由此接口批量展开。跨 Session、跨文件或旧分支条目失败关闭。完整内容不进入常驻快照;隐藏工具输出的截断也不再生成全局会话警告。 +- Web 专用 Pi extension factory 在原生用户 `message_start` 前等待隔离 Git 基线,遇到下一条用户消息或 `agent_settled` 时比较并保存有界 `openpi-web-turn-changes` 自定义条目。身份是持久化用户消息的原生 entry ID,不是渲染轮次序号;记录只投影路径和计数摘要,点击审阅再按同一分支读取持久化的 diff。既有右侧 Git 审阅工具仍用于当前工作树的未暂存、暂存、分支及会话基线范围。 +- 逐轮比较是**该轮期间的工作区时间差**,不证明具体由 agent 写入;并发的外部文件修改可能包含在内。基线最多 64 MiB,记录最多 200 个文件/256 KiB。基线不可用与确认零修改分别持久化为 `unavailable` 和 `complete` + 0;超限使用 `partial`、未知总数和截断说明。旧 Session 没有逐轮记录,不按当前 Git 状态回填;不提供无可靠逆补丁依据的 Undo 操作。 + +## 验证与剩余风险 + +专项测试覆盖 205 个预存脏文件不归入后续回合、连续用户消息、重载后的原生 ID、零修改、非 Git/仓库切换、超限记录、超长正文与超过 64 段的恢复、复制 JSONL 同 ID 的文件身份、分页无漏条及 HTTP 认证。真实 Chromium 的桌面/窄屏夹具验证逐轮卡片和按需恢复;旧历史窗口发送新消息与后台观察者中的原生/本地消息去重也通过 3/3 专项浏览器回归。它使用原生 SessionManager + WebHost 测试宿主,不等于真实模型的文件修改可靠性测量。 + +本地最终版本 `bun run check` 通过;`bun run test` 为 Node 1948 通过、5 跳过、Vitest 498 通过;生产构建的完整 Chromium 回归 67/67 通过。此前完整浏览器运行先后出现右键 `mouseup` 超时,以及文件引用弹窗在 390px 宽度入场动画中途被 axe 取样而颜色对比度不足;两条各自单独复跑曾通过。文件引用说明文字现在使用更高对比度,弹窗可访问性检查等待 opacity 到 1,与同套件其他弹窗检查一致。最终整套 67/67 在该修复后实际通过;提交后 CI 与常驻 57161 进程仍未由此验证,57161 未重启。 + +## 消融 + +逐文件差异原本还要发起第二次详情读取;去掉该请求后,单轮卡片、选中文件、错误状态与桌面/窄屏浏览器验证仍通过,因此保留更小的单次详情读取。最初按“窗口中第一个用户消息”无条件裁去所有前序条目,导致本地已接收消息的原生锚点消失,完整浏览器回归出现重复气泡;保留未超过 20 轮上限的前序条目后恢复。对于确实被字节限制裁掉的锚点,只有原生 `parentId` 精确匹配才确认本地发送;反例测试仍保留无法证明归属的同文消息。旧阅读窗口中本地发送与实时用户事件重叠时,每条未确认本地输入最多压住一条实时回显;额外同文事件保留,且不会将回显当作提交回执。 + +当前仍不能从 Git 时间差区分同一工作树中的其他写入者;非 Git 或超过基线额度时无法给出准确逐轮差异。全文恢复限定用户/助手可见正文,工具原始输出仍使用既有有界证据与恢复引用。不可把专项验证或本地构建当作常驻 57161 的部署证据。 diff --git a/extensions/file-search/index.ts b/extensions/file-search/index.ts index c4758635..9e89d01f 100644 --- a/extensions/file-search/index.ts +++ b/extensions/file-search/index.ts @@ -1,7 +1,10 @@ /** * file-search — first-class `fd` and `rg` tools for pi. * - * On session start the extension resolves a usable binary for each tool: + * In the TUI the extension resolves a usable binary on session start. + * Headless/Web sessions resolve on the first search tool call so optional + * downloads never block opening a Session or starting an independent child. + * Resolution order: * a normally installed system binary is preferred (silently), then an * existing binary in the agent's managed bin directory (`~/.pi/agent/bin`, * silently — e.g. cached by an earlier download), and only when neither @@ -145,6 +148,7 @@ export default function fileSearchTools(pi: ExtensionAPI) { patchOwnedTools(pi, "fileSearch", { enable: OPENPI_TOOL_SURFACE.fileSearch.entry, }); + if (!ctx.hasUI) return; const exit = await Effect.runPromiseExit( Effect.gen(function* () { const initialized = yield* Effect.all( diff --git a/extensions/setup/index.ts b/extensions/setup/index.ts index c09d769e..3915164c 100644 --- a/extensions/setup/index.ts +++ b/extensions/setup/index.ts @@ -186,12 +186,12 @@ export function buildSetupSuccessText( `Saved OpenPI setup. ${changed.length ? `Changed effective fields: ${changed.join(", ")}.` : "Effective configuration unchanged; saving may create or migrate the document without changing behavior."}`, `${currentConfiguration}${normalizationNote}`, "Describe changes only from this receipt's changed effective fields and final configuration. A successful save does not imply a visual or behavioral change. Do not infer a before/after transition from the requested preset name.", - "This setup episode is complete; configure_my_pi_setup is now hidden. Do not call it again. Do not edit configuration files directly. If the user requests another configuration change, tell them to run /openpi-setup to start a new setup episode.", + "This setup episode is complete; configure_my_pi_setup is now hidden for this completed episode. Do not call it again within this completed episode. Do not edit configuration files directly. A later /openpi-setup starts a new episode and makes the writer available again.", ].join(" "); } export function buildSetupNoopClosureText() { - return "No configuration update was confirmed in this setup run. The configuration writer is now hidden. To make a configuration change, run /openpi-setup ; do not edit configuration files directly."; + return "No configuration update was confirmed in this setup episode, which is now closed. The configuration writer is now hidden for this closed episode. A later /openpi-setup starts a new episode and makes the writer available again. Do not edit configuration files directly."; } export const CONFIGURE_MY_PI_SETUP_TOOL_NAME = "configure_my_pi_setup"; @@ -217,7 +217,12 @@ export default function openPiSetup(pi: ExtensionAPI) { let claimedToolCallId: string | undefined; let blockedMatchingClaimCount = 0; let requestSequence = 0; - const pendingRequests: Array<{ requestId: string; prompt: string }> = []; + const pendingRequests: Array<{ + requestId: string; + prompt: string; + request: string; + command: "openpi-setup" | "my-pi-setup"; + }> = []; const publishEpisode = () => pi.events.emit(OPENPI_SETUP_EPISODE_CHANNEL, { active: episode === "active", @@ -280,9 +285,17 @@ export default function openPiSetup(pi: ExtensionAPI) { pi.sendMessage( { customType: SETUP_REQUEST_CUSTOM_TYPE, - content: request.prompt, + content: [ + "A new OpenPI setup episode has started for this /openpi-setup request. configure_my_pi_setup is active for this episode. Earlier success or closure messages apply only to their earlier episodes.", + "", + request.prompt, + ].join("\n"), display: true, - details: { requestId: request.requestId }, + details: { + requestId: request.requestId, + request: request.request, + command: request.command, + }, }, { triggerTurn: true }, ); @@ -632,7 +645,11 @@ export default function openPiSetup(pi: ExtensionAPI) { }, }); - const setupHandler = async (args: string, ctx: ExtensionCommandContext) => { + const setupHandler = async ( + args: string, + ctx: ExtensionCommandContext, + command: "openpi-setup" | "my-pi-setup", + ) => { const request = args.trim(); const inspected = inspectSetupConfig(); const diagnostics = formatSetupDiagnostics(inspected); @@ -676,17 +693,22 @@ export default function openPiSetup(pi: ExtensionAPI) { throw new Error(message); } const requestId = `setup-${++requestSequence}`; - pendingRequests.push({ requestId, prompt: prompt.join("\n") }); + pendingRequests.push({ + requestId, + prompt: prompt.join("\n"), + request: args, + command, + }); if (episode === "idle") episode = "armed"; if (ctx.isIdle() && episode === "armed") dispatchNextRequest(ctx); }; pi.registerCommand("openpi-setup", { description: "View or change OpenPI configuration in natural language", - handler: setupHandler, + handler: (args, ctx) => setupHandler(args, ctx, "openpi-setup"), }); pi.registerCommand("my-pi-setup", { description: "Legacy alias — use /openpi-setup", - handler: setupHandler, + handler: (args, ctx) => setupHandler(args, ctx, "my-pi-setup"), }); } diff --git a/tests/extensions/file-search/startup.spec.ts b/tests/extensions/file-search/startup.spec.ts new file mode 100644 index 00000000..c072ce29 --- /dev/null +++ b/tests/extensions/file-search/startup.spec.ts @@ -0,0 +1,36 @@ +import type { + ExtensionAPI, + ExtensionContext, +} from "@earendil-works/pi-coding-agent"; +import { Effect } from "effect"; +import { afterEach, expect, it, vi } from "vitest"; +import fileSearchTools from "../../../extensions/file-search/index.ts"; +import { liveBinaryEnv } from "../../../extensions/file-search/src/binaries.ts"; + +afterEach(() => vi.restoreAllMocks()); + +it("does not block headless parent or child startup on optional search binary initialization", async () => { + const probe = vi.spyOn(liveBinaryEnv, "probe").mockReturnValue(Effect.never); + let sessionStart: + | ((event: unknown, context: ExtensionContext) => unknown) + | undefined; + const pi = { + on(event: string, handler: unknown) { + if (event === "session_start") + sessionStart = handler as typeof sessionStart; + }, + registerTool() {}, + getActiveTools: () => ["read", "bash"], + setActiveTools() {}, + } as unknown as ExtensionAPI; + fileSearchTools(pi); + expect(sessionStart).toBeDefined(); + const result = await Promise.race([ + Promise.resolve( + sessionStart!({}, { hasUI: false, mode: "print" } as ExtensionContext), + ).then(() => "ready"), + new Promise((resolve) => setTimeout(() => resolve("blocked"), 100)), + ]); + expect(result).toBe("ready"); + expect(probe).not.toHaveBeenCalled(); +}); diff --git a/tests/extensions/setup/index.test.ts b/tests/extensions/setup/index.test.ts index a6fe15c4..ce351e43 100644 --- a/tests/extensions/setup/index.test.ts +++ b/tests/extensions/setup/index.test.ts @@ -338,6 +338,7 @@ test("session_start hides configure_my_pi_setup after registration refresh", asy }); test("openpi-setup and my-pi-setup expose the tool then inject the setup message", async () => { + const prompts: string[] = []; for (const name of ["openpi-setup", "my-pi-setup"] as const) { const h = visibilityHarness(); await h.emit("session_start"); @@ -351,8 +352,35 @@ test("openpi-setup and my-pi-setup expose the tool then inject the setup message assert.match(String(content), /available only for this setup run/i); assert.match(String(content), /\/openpi-setup /); assert.doesNotMatch(String(content), /intercom/i); + prompts.push(String(content)); + assert.deepEqual( + (h.setupRequests()[0]?.message as { details?: unknown }).details, + { + requestId: "setup-1", + command: name, + request: "关闭下一步预测", + }, + ); assert.deepEqual(h.userMessages, []); } + assert.equal( + prompts[0], + prompts[1], + "presentation metadata must not alter the model-facing setup prompt", + ); +}); + +test("setup metadata retains the original argument text independently of the expanded prompt", async () => { + const h = visibilityHarness(); + await h.emit("session_start"); + await h.runCommand("my-pi-setup", " set theme to dark\n"); + const message = h.setupRequests()[0]?.message as { + details: { request: string; command: string }; + content: string; + }; + assert.equal(message.details.request, " set theme to dark\n"); + assert.equal(message.details.command, "my-pi-setup"); + assert.match(message.content, /this request:\nset theme to dark\n/u); }); test("setup activation fails closed for a foreign same-name writer", async () => { @@ -534,7 +562,8 @@ test("successful setup result closes the episode and names the only re-entry", ( assert.match(result, /Normalized or migrated.*ui\.footerStyle/); assert.match(result, /setup episode is complete/i); assert.match(result, /configure_my_pi_setup.*hidden/i); - assert.match(result, /do not call it again/i); + assert.match(result, /do not call it again within this completed episode/i); + assert.match(result, /new episode and makes the writer available again/i); assert.match(result, /do not edit.*configuration files/i); assert.match(result, /\/openpi-setup /); }); @@ -544,6 +573,8 @@ test("no-op setup result records the bounded re-entry contract", () => { assert.match(result, /no configuration update was confirmed/i); assert.match(result, /now hidden/i); + assert.match(result, /for this closed episode/i); + assert.match(result, /new episode and makes the writer available again/i); assert.match(result, /\/openpi-setup /); assert.match(result, /do not edit configuration files directly/i); }); diff --git a/tests/extensions/setup/integration.test.ts b/tests/extensions/setup/integration.test.ts index 4ffe0d5f..211cb0ef 100644 --- a/tests/extensions/setup/integration.test.ts +++ b/tests/extensions/setup/integration.test.ts @@ -9,7 +9,7 @@ import { promisify } from "node:util"; const execFileAsync = promisify(execFile); -test("real Pi provider snapshots setup writer before the first call and records no-op closure", { +test("real Pi provider snapshots setup writer across closed and successfully applied episodes", { timeout: 20_000, }, async () => { const cwd = await mkdtemp(path.join(tmpdir(), "openpi-setup-integration-")); @@ -75,6 +75,17 @@ faux.setResponses([ snapshots.push(capture(context)); return fauxAssistantMessage("Setup finished."); }, + (context) => { + snapshots.push(capture(context)); + return fauxAssistantMessage( + [fauxToolCall("configure_my_pi_setup", { ui_web_theme: "dark" }, { id: "apply-next" })], + { stopReason: "toolUse" }, + ); + }, + (context) => { + snapshots.push(capture(context)); + return fauxAssistantMessage("Next setup finished."); + }, ]); const settingsManager = SettingsManager.inMemory(undefined, { projectTrusted: false }); const modelRuntime = await ModelRuntime.create({ @@ -102,6 +113,8 @@ try { await session.waitForIdle(); await session.prompt("/openpi-setup disable suggestions"); await session.waitForIdle(); + await session.prompt("/openpi-setup use dark theme"); + await session.waitForIdle(); const config = JSON.parse(await readFile(agentDir + "/my-pi-setup.json", "utf8")); process.stdout.write(JSON.stringify({ snapshots, active: session.getActiveToolNames(), config })); } finally { @@ -135,9 +148,9 @@ try { tools?: Array<{ name: string }>; }>; active: string[]; - config: { suggestions: { enabled: boolean } }; + config: { suggestions: { enabled: boolean }; ui: { webTheme: string } }; }; - assert.equal(result.snapshots.length, 4); + assert.equal(result.snapshots.length, 6); assert.ok( result.snapshots[0]?.tools?.some( ({ name }) => name === "configure_my_pi_setup", @@ -178,6 +191,38 @@ try { "one parallel call must execute and the other must be blocked", ); assert.equal(result.config.suggestions.enabled, false); + assert.equal(result.config.ui.webTheme, "dark"); + assert.ok( + result.snapshots[4]?.tools?.some( + ({ name }) => name === "configure_my_pi_setup", + ), + "the next setup after a successful apply must expose its own writer", + ); + const nextRequest = result.snapshots[4]?.messages + .filter(({ role }) => role === "user") + .at(-1); + assert.match( + JSON.stringify(nextRequest?.content), + /A new OpenPI setup episode has started/, + ); + assert.match( + JSON.stringify(nextRequest?.content), + /Earlier success or closure messages apply only to their earlier episodes/, + ); + assert.match( + JSON.stringify(result.snapshots[4]?.messages), + /Do not call it again within this completed episode/, + ); + assert.equal( + result.snapshots[5]?.messages.filter( + ({ role, toolName, isError }) => + role === "toolResult" && + toolName === "configure_my_pi_setup" && + !isError, + ).length, + 2, + "each setup episode must apply once even with a prior success in context", + ); assert.equal(result.active.includes("configure_my_pi_setup"), false); } finally { await rm(cwd, { recursive: true, force: true }); diff --git a/tests/web/app-render.spec.ts b/tests/web/app-render.spec.ts index cd0d3991..ff543662 100644 --- a/tests/web/app-render.spec.ts +++ b/tests/web/app-render.spec.ts @@ -22,6 +22,7 @@ import { SubagentDetailView } from "../../web/ui/src/features/subagents/Subagent import { Transcript } from "../../web/ui/src/features/transcript/Transcript.tsx"; import { i18n } from "../../web/ui/src/i18n.ts"; import { WebClient } from "../../web/ui/src/protocol/client.ts"; +import type { EventStreamOptions } from "../../web/ui/src/protocol/event-stream.ts"; import { createWebStore, webStore } from "../../web/ui/src/store/web-store.ts"; afterEach(() => { @@ -498,7 +499,7 @@ it("marks only live execution evidence for shimmer styling", () => { expect(process?.querySelectorAll(".process-step.running")).toHaveLength(2); }); -it("keeps OpenPI setup episodes out of the main conversation", () => { +it("folds legacy setup instructions while keeping results and subsequent task messages visible", () => { const snapshot = activeSnapshot(); snapshot.selectedSession!.entries = [ { @@ -590,9 +591,360 @@ it("keeps OpenPI setup episodes out of the main conversation", () => { screen.getAllByText("Continue the actual task").length, ).toBeGreaterThan(0); expect(screen.getByText("Visible continuation")).toBeTruthy(); - expect(screen.queryByText("Hidden configuration response")).toBeNull(); - expect(screen.queryByText("Hidden configuration evidence")).toBeNull(); - expect(screen.queryByText("/openpi-setup Apply a dark theme")).toBeNull(); + expect(screen.getByText("Hidden configuration response")).toBeTruthy(); + expect( + screen.getAllByText("Hidden configuration evidence").length, + ).toBeGreaterThan(0); + expect( + screen.getByText("/openpi-setup Apply a dark theme", { + selector: ".message-body", + }), + ).toBeTruthy(); + expect(screen.queryByText("Apply a dark theme")).toBeNull(); +}); + +it.each([true, false])( + "only folds a setup echo linked to its exact native command parent (%s)", + (linked) => { + const snapshot = activeSnapshot(); + snapshot.runtime.status = "idle"; + const content = "/openpi-setup set theme to dark"; + snapshot.selectedSession!.entries = [ + projectEntry({ + id: "command-entry", + parentId: null, + timestamp: "2026-09-22T00:00:00Z", + type: "custom", + customType: "openpi-web-command-input", + data: { text: content, commandId: "command-one" }, + }), + projectEntry({ + id: "setup-entry", + parentId: linked ? "command-entry" : "other-entry", + timestamp: "2026-09-22T00:00:01Z", + type: "custom_message", + customType: "openpi-setup-request", + display: true, + content: "Expanded internal configuration instructions", + details: { + requestId: "setup-one", + command: "openpi-setup", + request: "set theme to dark", + }, + }), + { + id: "result-entry", + timestamp: "2026-09-22T00:00:02Z", + type: "message", + message: { role: "assistant", content: "The theme is now dark." }, + }, + ]; + const projected = vi.fn(); + const view = renderWithI18n( + createElement(Transcript, { + snapshot, + liveMessages: [ + { + key: "optimistic-command-one", + message: { role: "user", content }, + optimistic: { + sessionId: "session", + sessionPath: "/tmp/session", + commandId: "command-one", + afterEntryId: null, + admitted: true, + }, + }, + ], + liveRunning: false, + livePhase: "idle", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + onPromptProjection: projected, + }), + ); + expect( + view.container.querySelectorAll(".message-row.user .message-body"), + ).toHaveLength(linked ? 1 : 2); + expect( + screen.queryByText("Expanded internal configuration instructions"), + ).toBeNull(); + expect(screen.getByText("The theme is now dark.")).toBeTruthy(); + expect(projected).toHaveBeenCalledWith("session", "/tmp/session", [ + { key: "optimistic-command-one", entryId: "command-entry" }, + ]); + }, +); + +it("does not consume a later identical command draft using an older native command id", () => { + const snapshot = activeSnapshot(); + const content = "/usage"; + snapshot.selectedSession!.entries = [ + projectEntry({ + id: "older-command", + parentId: null, + timestamp: "2026-09-22T00:00:00Z", + type: "custom", + customType: "openpi-web-command-input", + data: { text: content, commandId: "old-command-id" }, + }), + ]; + const projected = vi.fn(); + const view = renderWithI18n( + createElement(Transcript, { + snapshot, + liveMessages: [ + { + key: "optimistic-new-command-id", + message: { role: "user", content }, + optimistic: { + sessionId: "session", + sessionPath: "/tmp/session", + commandId: "new-command-id", + afterEntryId: "older-command", + admitted: true, + }, + }, + ], + liveRunning: true, + livePhase: "running", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + onPromptProjection: projected, + }), + ); + expect( + view.container.querySelectorAll(".message-row.user .message-body"), + ).toHaveLength(2); + expect(projected).not.toHaveBeenCalled(); +}); + +it.each([1, 2])( + "reconciles timestamp-free live users once while keeping native history ids (%s copies)", + (copies) => { + const snapshot = activeSnapshot(); + snapshot.runtime.status = "idle"; + const content = "New user request while reading old history"; + snapshot.selectedSession!.entries = [ + { + id: "before-request", + type: "message", + timestamp: "2026-09-22T00:00:00Z", + message: { + role: "assistant", + content: "Previous answer", + timestamp: 100, + }, + }, + { + id: "native-request", + parentId: "before-request", + type: "message", + timestamp: "2026-09-22T00:00:01Z", + message: { + role: "user", + content, + timestamp: 200, + parts: [{ type: "text", text: content }], + }, + }, + ]; + const projected = vi.fn(); + const view = renderWithI18n( + createElement(Transcript, { + snapshot, + liveMessages: [ + { + key: "optimistic-new-command", + message: { role: "user", content }, + optimistic: { + sessionId: "session", + sessionPath: "/tmp/session", + commandId: "new-command", + afterEntryId: "before-request", + admitted: true, + }, + }, + ...Array.from({ length: copies }, (_, index) => ({ + key: `legacy-user-${index}`, + message: { role: "user", content }, + })), + ], + liveRunning: false, + livePhase: "idle", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + onPromptProjection: projected, + }), + ); + expect( + view.container.querySelectorAll(".message-row.user .message-body"), + ).toHaveLength(copies); + expect( + view.container + .querySelector(".message-row.user") + ?.getAttribute("data-history-entry"), + ).toBe("native-request"); + expect(projected).toHaveBeenCalledWith("session", "/tmp/session", [ + { key: "optimistic-new-command", entryId: "native-request" }, + ]); + }, +); + +it.each(["openpi-setup", "my-pi-setup"])( + "shows the original /%s request once and retries the command instead of the internal prompt", + async (command) => { + const snapshot = activeSnapshot(); + snapshot.runtime = { status: "idle", capabilities: {} }; + const request = "set theme to dark"; + const content = `/${command} ${request}`; + snapshot.selectedSession!.entries = [ + projectEntry({ + id: "setup", + parentId: null, + type: "custom_message", + customType: "openpi-setup-request", + timestamp: "2026-09-22T00:00:00Z", + content: "INTERNAL EXPANDED CONFIGURATION PROMPT", + display: true, + details: { requestId: "setup-1", command, request }, + }), + { + id: "result", + type: "message", + timestamp: "2026-09-22T00:00:01Z", + message: { + role: "toolResult", + toolName: "configure_my_pi_setup", + toolCallId: "setup-call", + content: "Configuration write failed", + isError: true, + }, + }, + { + id: "answer", + type: "message", + timestamp: "2026-09-22T00:00:02Z", + message: { + role: "assistant", + content: "Configuration could not be completed", + stopReason: "error", + errorMessage: "Fixture provider error", + }, + }, + { + id: "timing", + type: "custom", + timestamp: "2026-09-22T00:00:03Z", + turnTiming: { + version: 1, + sessionId: "session", + commandId: "web-command", + epoch: 1, + startedAt: 1000, + finishedAt: 4000, + elapsedMs: 3000, + outcome: "failed", + }, + }, + ]; + const resend = vi.fn(async () => true); + const view = renderWithI18n( + createElement(Transcript, { + snapshot, + liveMessages: [ + { key: "optimistic-web-command", message: { role: "user", content } }, + ], + liveRunning: false, + livePhase: "idle", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: resend, + }), + ); + const users = view.container.querySelectorAll( + ".message-row.user .message-body", + ); + expect(users).toHaveLength(1); + expect(users[0]?.textContent).toBe(content); + expect( + screen.queryByText("INTERNAL EXPANDED CONFIGURATION PROMPT"), + ).toBeNull(); + expect( + screen.getAllByText("Configuration write failed").length, + ).toBeGreaterThan(0); + expect(screen.getByText("Fixture provider error")).toBeTruthy(); + expect( + view.container.querySelector(".turn-duration")?.textContent, + ).toContain("3s"); + await act(async () => { + fireEvent.click( + screen.getByRole("button", { name: i18n.t("retryPrompt") }), + ); + }); + expect(resend).toHaveBeenCalledWith(content); + }, +); + +it("does not retry an unrelated user task when a legacy setup request has no original-command metadata", () => { + const snapshot = activeSnapshot(); + snapshot.runtime = { status: "idle", capabilities: {} }; + snapshot.selectedSession!.entries = [ + { + id: "user", + type: "message", + timestamp: "2026-09-22T00:00:00Z", + message: { role: "user", content: "Unrelated earlier task" }, + }, + projectEntry({ + id: "setup", + parentId: "user", + type: "custom_message", + customType: "openpi-setup-request", + timestamp: "2026-09-22T00:00:01Z", + content: "Legacy expanded setup prompt", + display: true, + }), + { + id: "error", + type: "message", + timestamp: "2026-09-22T00:00:02Z", + message: { + role: "assistant", + content: "", + stopReason: "error", + errorMessage: "Visible setup failure", + }, + }, + ]; + renderWithI18n( + createElement(Transcript, { + snapshot, + liveMessages: [], + liveRunning: false, + livePhase: "idle", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + }), + ); + expect(screen.getByText("Visible setup failure")).toBeTruthy(); + expect( + screen.queryByRole("button", { name: i18n.t("retryPrompt") }), + ).toBeNull(); }); it("renders side conversation messages with transcript role styling", async () => { @@ -676,6 +1028,7 @@ function activeSnapshot(): WebSnapshot { generatedAt: "2026-09-01T10:00:00Z", cursor: 1, currentSessionId: "session", + currentSessionPath: "/tmp/session", workspaces: [], sessions: [], models: [], @@ -692,6 +1045,179 @@ function activeSnapshot(): WebSnapshot { }; } +it("keeps the reader mounted across an external controller change and revokes input until refreshed", async () => { + const original = webStore.getState(); + const snapshot = activeSnapshot(); + snapshot.runtime.status = "idle"; + snapshot.workspaces = [{ path: "/tmp", name: "Workspace", current: true }]; + snapshot.sessions = [ + { + id: "session", + path: "/tmp/session", + cwd: "/tmp", + name: "Reading A", + created: snapshot.generatedAt, + modified: snapshot.generatedAt, + source: "web-session", + origin: "web", + controller: "web", + readOnly: false, + messageCount: 4, + firstMessage: "Earlier question", + }, + ]; + const entry = ( + id: string, + role: string, + content: string, + parentId: string | null, + ) => ({ + id, + parentId, + type: "message" as const, + timestamp: snapshot.generatedAt, + message: { role, content }, + }); + snapshot.selectedSession!.entries = [ + entry("latest", "assistant", "Recent answer", "older-answer"), + ]; + snapshot.selectedSession!.history = { + leafEntryId: "latest", + beforeEntryId: "latest", + }; + snapshot.selectedSession!.truncation = { + ...truncation, + truncated: true, + entriesOmitted: 2, + }; + let eventStream!: EventStreamOptions; + const client = new WebClient(); + let completeRefresh!: (next: WebSnapshot) => void; + const refresh = vi.spyOn(client, "snapshot").mockReturnValue( + new Promise((resolve) => { + completeRefresh = resolve; + }), + ); + const prompt = vi.spyOn(client, "prompt"); + const history = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockResolvedValue({ + ...snapshot.selectedSession!, + entries: [ + entry("older-user", "user", "Earlier question", null), + entry("older-answer", "assistant", "Earlier answer", "older-user"), + ], + anchorEntryId: "latest", + requestedBeforeEntryId: "latest", + truncation: { ...truncation, entriesOmitted: 0 }, + history: { + leafEntryId: "latest", + beforeEntryId: null, + anchorEntryId: "latest", + anchorOnBranch: true, + }, + }); + const store = createWebStore(client, { + consumeEvents: (options) => { + eventStream = options; + options.onConnected(); + return new Promise((resolve) => + options.signal.addEventListener("abort", () => resolve(), { + once: true, + }), + ); + }, + }); + store.setState({ + snapshot, + cursor: 1, + selectedPath: "/tmp/session", + selectedWorkspace: "/tmp", + workspaceDraft: false, + }); + webStore.setState(store.getState(), true); + const unsubscribe = store.subscribe((next) => webStore.setState(next, true)); + const view = renderWithI18n(createElement(App)); + try { + await act(async () => + fireEvent.click( + screen.getByRole("button", { name: i18n.t("historyLoadOlder") }), + ), + ); + expect(screen.getByText("Earlier answer")).toBeTruthy(); + const viewport = view.container.querySelector( + '.conversation[role="log"]', + )!; + Object.defineProperty(viewport, "scrollHeight", { + configurable: true, + value: 1000, + }); + Object.defineProperty(viewport, "clientHeight", { + configurable: true, + value: 200, + }); + viewport.scrollTop = 100; + fireEvent.scroll(viewport); + const anchor = store.getState().historyAnchor; + expect(anchor?.entryId).toBe("latest"); + act(() => + eventStream.onEvent({ + protocolVersion: 1, + sequence: 2, + timestamp: snapshot.generatedAt, + type: "session_switched", + detail: { sessionId: "controller-b", sessionPath: "/tmp/b" }, + }), + ); + expect(view.container.querySelector('.conversation[role="log"]')).toBe( + viewport, + ); + expect(screen.getByText("Earlier answer")).toBeTruthy(); + expect(store.getState().historyAnchor).toEqual(anchor); + expect( + view.container.querySelector(".composer textarea") + ?.disabled, + ).toBe(true); + expect( + await store.getState().actions.sendPrompt("Must not reach old A"), + ).toBe(false); + expect(prompt).not.toHaveBeenCalled(); + expect(refresh).toHaveBeenCalledWith("/tmp/session", anchor); + const next = structuredClone(snapshot); + next.cursor = 2; + next.currentSessionId = "controller-b"; + next.currentSessionPath = "/tmp/b"; + next.sessions[0]!.controller = "none"; + next.sessions.push({ + ...next.sessions[0]!, + id: "controller-b", + path: "/tmp/b", + controller: "web", + }); + next.selectedSession!.history = { + ...next.selectedSession!.history!, + anchorEntryId: "latest", + anchorOnBranch: true, + }; + await act(async () => completeRefresh(next)); + expect(view.container.querySelector('.conversation[role="log"]')).toBe( + viewport, + ); + expect(screen.getByText("Earlier answer")).toBeTruthy(); + expect(viewport.scrollTop).toBe(100); + expect(store.getState().historyAnchor).toEqual(anchor); + expect(history).toHaveBeenCalledOnce(); + } finally { + view.unmount(); + store.getState().actions.stop(); + unsubscribe(); + refresh.mockRestore(); + prompt.mockRestore(); + history.mockRestore(); + webStore.setState(original, true); + } +}); + it("groups transcript turns with state and confirmed file change receipts", () => { const snapshot = activeSnapshot(); snapshot.runtime.status = "idle"; @@ -1015,6 +1541,68 @@ it("shows cancellation and queued follow-up receipts on the active session", () ).toBe(true); }); +it("shows Pi's queued messages beside the composer after reload and in a background session", () => { + const snapshot = activeSnapshot(); + const selected = snapshot.selectedSession!; + snapshot.currentSessionId = "another-session"; + snapshot.selectedExecution = { + sessionId: selected.id, + sessionPath: selected.path, + status: "running", + pendingFollowUps: 2, + queuedMessages: ["First follow-up", "Second follow-up"], + liveTools: [], + liveToolsOmitted: 0, + }; + const store = createWebStore(); + const props = { + snapshot, + selectedWorkspace: "/tmp", + sessionSwitching: false, + promptAdmissionPending: false, + liveRunning: false, + landing: false, + activeTurn: null, + turnCancellationPending: false, + turnTerminalStatus: null, + pendingFollowUpsReceipt: null, + thinkingPendingLevel: null, + actions: store.getState().actions, + }; + const view = renderWithI18n(createElement(Composer, props)); + const queued = screen.getByRole("region", { + name: i18n.t("pendingFollowUpsHint", { count: 2 }), + }); + expect(queued.textContent).toContain("First follow-up"); + expect(queued.textContent).toContain("Second follow-up"); + expect( + screen.queryByText(i18n.t("pendingFollowUpsHint", { count: 2 }), { + selector: ".composer-hint", + }), + ).toBeNull(); + + snapshot.selectedExecution = { + ...snapshot.selectedExecution!, + pendingFollowUps: 0, + queuedMessages: [], + }; + view.rerender( + createElement( + I18nextProvider, + { i18n }, + createElement(Composer, { + ...props, + snapshot: structuredClone(snapshot), + }), + ), + ); + expect( + screen.queryByRole("region", { + name: i18n.t("pendingFollowUpsHint", { count: 2 }), + }), + ).toBeNull(); +}); + it("keeps background terminal activity and omission receipts visible", () => { const snapshot = activeSnapshot(); snapshot.runtime.capabilities = { @@ -1624,31 +2212,39 @@ describe("thinking level picker", () => { ).toBe(i18n.t("thinkingUnsupportedHint")); }); - it("disables the picker for a workspace draft", () => { + it("prepares a native session when opening thinking from a workspace draft", async () => { const props = thinkingProps(idleThinkingSnapshot()); - const { container } = renderWithI18n( - createElement(Composer, { ...props, workspaceDraft: true }), + const prepareSession = vi.fn(async () => null); + renderWithI18n( + createElement(Composer, { + ...props, + workspaceDraft: true, + actions: { ...props.actions, prepareSession }, + }), ); expect( screen.getByRole("button", { name: thinkingPickerName("medium"), }).disabled, - ).toBe(true); - expect( - container.querySelector(".thinking-picker-wrap")?.getAttribute("title"), - ).toBe(i18n.t("thinkingDraftHint")); + ).toBe(false); + await act(async () => + fireEvent.click( + screen.getByRole("button", { name: thinkingPickerName("medium") }), + ), + ); + expect(prepareSession).toHaveBeenCalledOnce(); }); - it("disables the picker for a non-current session", () => { + it("hides the controller's picker while viewing a non-current session", () => { const props = thinkingProps( idleThinkingSnapshot({ currentSessionId: "another-session" }), ); const { container } = renderWithI18n(createElement(Composer, props)); expect( - screen.getByRole("button", { + screen.queryByRole("button", { name: thinkingPickerName("medium"), - }).disabled, - ).toBe(true); + }), + ).toBeNull(); expect( container.querySelector(".thinking-picker-wrap")?.getAttribute("title"), ).toBe(i18n.t("thinkingInactiveHint")); @@ -2459,6 +3055,7 @@ it("clears an old session draft without letting its late send clear the new one" const nextSnapshot = { ...snapshot, currentSessionId: "next-session", + currentSessionPath: "/tmp/next-session", selectedSession: { ...snapshot.selectedSession!, id: "next-session", diff --git a/tests/web/artifact-authorization.spec.ts b/tests/web/artifact-authorization.spec.ts new file mode 100644 index 00000000..39494638 --- /dev/null +++ b/tests/web/artifact-authorization.spec.ts @@ -0,0 +1,82 @@ +// @vitest-environment jsdom + +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { createElement, useContext } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import { ArtifactProvider } from "../../web/ui/src/features/artifacts/Artifacts.tsx"; +import { ArtifactContext } from "../../web/ui/src/features/artifacts/context.ts"; +import { WebApiError, WebClient } from "../../web/ui/src/protocol/client.ts"; +import { i18n } from "../../web/ui/src/i18n.ts"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +function Opener() { + const artifacts = useContext(ArtifactContext); + return createElement( + "button", + { + type: "button", + onClick: () => artifacts?.open("/other/worktree/index.ts"), + }, + "Open file", + ); +} + +it("waits for an explicit file-only approval before reading another worktree", async () => { + vi.spyOn(WebClient.prototype, "resolveArtifact").mockRejectedValue( + new WebApiError("Outside workspace", 403, "ARTIFACT_DENIED"), + ); + const authorize = vi + .spyOn(WebClient.prototype, "authorizeArtifact") + .mockResolvedValue({ handle: "one-file" }); + vi.spyOn(WebClient.prototype, "artifactPreview").mockResolvedValue({ + artifact: { + handle: "one-file", + sessionId: "s", + path: "/other/worktree/index.ts", + name: "index.ts", + bytes: 24, + revision: "a".repeat(64), + preview: "text", + }, + identity: "same-file", + text: "export const answer = 42;", + truncated: false, + }); + vi.spyOn(WebClient.prototype, "releaseArtifact").mockResolvedValue({}); + render( + createElement( + I18nextProvider, + { i18n }, + createElement( + ArtifactProvider, + { sessionId: "s" }, + createElement(Opener), + ), + ), + ); + fireEvent.click(screen.getByRole("button", { name: "Open file" })); + const consent = await screen.findByRole("button", { + name: i18n.t("artifactAuthorizeFile"), + }); + expect(authorize).not.toHaveBeenCalled(); + fireEvent.click(consent); + await waitFor(() => + expect(screen.getByText("export const answer = 42;")).toBeTruthy(), + ); + expect(authorize).toHaveBeenCalledWith( + "s", + "/other/worktree/index.ts", + expect.any(AbortSignal), + ); +}); diff --git a/tests/web/artifact-evidence.e2e.ts b/tests/web/artifact-evidence.e2e.ts index 8c1f1a96..68e5d9db 100644 --- a/tests/web/artifact-evidence.e2e.ts +++ b/tests/web/artifact-evidence.e2e.ts @@ -1,5 +1,5 @@ import { execFile } from "node:child_process"; -import { mkdtemp, readFile, rm } from "node:fs/promises"; +import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; @@ -25,6 +25,9 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st browser, }) => { const cwd = await mkdtemp(join(tmpdir(), "openpi-evidence-browser-")); + const outside = await mkdtemp(join(tmpdir(), "openpi-external-file-")); + const externalPath = join(outside, "external%25.ts"); + await writeFile(externalPath, "export const otherWorktree = 42;"); const manager = SessionManager.inMemory(cwd); const path = join(cwd, "report space.md"); const writeTool = createEvidenceWriteTool(cwd); @@ -180,7 +183,9 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st isError: row.isError, timestamp: Date.now(), }); - assistant("[Report](./report%20space.md)\n\n[Missing](./missing.md)"); + assistant( + `[Report](./report%20space.md)\n\n[Missing](./missing.md)\n\n[Other worktree](${encodeURI(externalPath)})`, + ); const listeners = new Set<(event: WebRuntimeEvent) => void>(); const runtime: WebRuntimeController = { searchModels: (query, limit) => @@ -259,16 +264,7 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st await generatedPreview .getByRole("button", { name: /关闭预览|Close preview/u }) .click(); - const changesTrigger = page.locator(".session-changes-trigger"); - await expect(changesTrigger).toContainText( - /1 (?:个文件已更改|file changed)/u, - ); - await expect( - changesTrigger.locator(".session-changes-counts"), - ).toContainText("+1-1"); - await changesTrigger.click(); - const changesPopover = page.locator(".session-changes-popover"); - await expect(changesPopover).toContainText("report space.md"); + await expect(page.locator(".session-changes-trigger")).toHaveCount(0); for (const group of await page.locator(".process-sequence > summary").all()) await group.click(); for (const summary of await page @@ -332,17 +328,22 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st await expect( page.getByText("unknown tool evidence", { exact: true }).first(), ).toBeAttached(); - await changesTrigger.click(); - const reviewTrigger = changesPopover.getByRole("button", { - name: /report space\.md/u, - }); - await reviewTrigger.click(); + await page + .getByRole("button", { name: /打开工具|Open tools/u, exact: true }) + .click(); + await workbar.locator(".workbar-add-tab").click(); + await page.getByRole("menuitem", { name: /^(?:变更|Changes)/u }).click(); const review = page.getByRole("complementary", { name: /变更|Changes/u, }); + await review + .getByRole("combobox", { name: /变更范围|Change scope/u }) + .selectOption("session"); await expect(review).toContainText( /自本会话开始后的变更|Changes since this session started/u, ); + await expect(review.locator(".session-review-file")).toHaveCount(1); + await review.locator(".session-review-file").click(); await expect( review.getByRole("figure", { name: /变更差异|Change diff/u }), ).toContainText("Reviewed content"); @@ -355,16 +356,12 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st review.getByRole("figure", { name: /变更差异|Change diff/u }), ).toContainText("Reviewed content"); await review.getByRole("button", { name: /^(?:关闭|Close)$/u }).click(); - await expect(changesTrigger).toBeFocused(); await page.setViewportSize({ width: 390, height: 844 }); - await expect(changesTrigger).toBeVisible(); - expect( - await changesTrigger.evaluate( - (element) => element.scrollWidth <= element.clientWidth, - ), - ).toBe(true); - await changesTrigger.click(); - await reviewTrigger.click(); + await page + .getByRole("button", { name: /打开工具|Open tools/u, exact: true }) + .click(); + await workbar.locator(".workbar-add-tab").click(); + await page.getByRole("menuitem", { name: /^(?:变更|Changes)/u }).click(); const mobileReview = page.getByRole("complementary", { name: /变更|Changes/u, }); @@ -378,7 +375,6 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st await mobileReview .getByRole("button", { name: /^(?:关闭|Close)$/u }) .click(); - await expect(changesTrigger).toBeFocused(); await page.setViewportSize({ width: 1280, height: 844 }); await page.getByRole("button", { name: "Report", exact: true }).click(); const artifactPanel = page.getByRole("complementary", { @@ -440,10 +436,26 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st await page.getByRole("button", { name: /关闭预览|Close preview/u }).click(); await page.getByRole("button", { name: "Missing", exact: true }).click(); await expect(artifactPanel).toContainText("File no longer exists"); + await page.getByRole("button", { name: /关闭预览|Close preview/u }).click(); + await page + .getByRole("button", { name: "Other worktree", exact: true }) + .click(); + await expect(artifactPanel).not.toContainText("export const otherWorktree"); + await page + .getByRole("button", { name: /只读打开此文件|Open this file read-only/u }) + .click(); + await expect(artifactPanel).toContainText( + "export const otherWorktree = 42;", + ); + await page.getByRole("button", { name: /刷新文件|Refresh file/u }).click(); + await expect(artifactPanel).toContainText( + "export const otherWorktree = 42;", + ); } finally { await context.close(); await host.stop(); await rm(cwd, { recursive: true, force: true }); + await rm(outside, { recursive: true, force: true }); await rm(baselineDirectory, { recursive: true, force: true }); } }); diff --git a/tests/web/artifact-images.e2e.ts b/tests/web/artifact-images.e2e.ts new file mode 100644 index 00000000..d3760bcb --- /dev/null +++ b/tests/web/artifact-images.e2e.ts @@ -0,0 +1,161 @@ +import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { SessionManager } from "@earendil-works/pi-coding-agent"; +import { expect, test } from "@playwright/test"; +import { WebHost } from "../../web/host/web-host.ts"; +import { projectWebModelSearch } from "../../web/runtime/model-discovery.ts"; +import type { WebRuntimeController } from "../../web/runtime/types.ts"; + +test("previews a real screenshot through its authorized handle, fits it, and reports corrupt images", async ({ + browser, +}, testInfo) => { + const cwd = await mkdtemp(join(tmpdir(), "openpi-image-preview-")); + const context = await browser.newContext(); + const page = await context.newPage(); + await page.setViewportSize({ width: 1400, height: 900 }); + const imageData = await page.evaluate(() => { + const canvas = document.createElement("canvas"); + canvas.width = 1200; + canvas.height = 600; + const drawing = canvas.getContext("2d")!; + drawing.fillStyle = "#234678"; + drawing.fillRect(0, 0, 1200, 600); + drawing.fillStyle = "#fff"; + drawing.font = "48px sans-serif"; + drawing.fillText("Authorized screenshot preview", 40, 90); + drawing.fillStyle = "#4ada78"; + drawing.fillRect(0, 560, 1200, 40); + return canvas.toDataURL("image/png").split(",")[1]!; + }); + await writeFile( + join(cwd, "screenshot.png"), + Buffer.from(imageData, "base64"), + ); + await writeFile( + join(cwd, "broken.png"), + Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 0]), + ); + await writeFile( + join(cwd, "vector.svg"), + '', + ); + const manager = SessionManager.inMemory(cwd); + manager.appendMessage({ + role: "assistant", + content: [ + { + type: "text", + text: "[Screenshot](./screenshot.png) [Broken](./broken.png) [Vector](./vector.svg)", + }, + ], + api: "openai-responses", + provider: "fixture", + model: "fixture", + stopReason: "stop", + timestamp: Date.now(), + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + }); + const runtime: WebRuntimeController = { + cwd, + workspaceSelected: true, + sessionDirectory: cwd, + sessionManager: manager, + searchModels: (query, limit) => + projectWebModelSearch(runtime.listModels(), query, limit), + isIdle: () => true, + getActiveTurn: () => undefined, + listModels: () => [], + setModel: async () => { + throw new Error("unused"); + }, + newSession: async () => ({ + cancelled: true, + sessionId: manager.getSessionId(), + }), + switchSession: async () => ({ cancelled: true }), + cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), + subscribe: () => () => {}, + dispose: async () => {}, + sendPrompt: async () => { + throw new Error("unused"); + }, + }; + const host = new WebHost({ runtime }); + const downloads: string[] = []; + page.on("request", (request) => { + const url = new URL(request.url()); + if ( + url.pathname === "/api/artifacts/content" && + url.searchParams.get("download") === "1" + ) + downloads.push(url.searchParams.get("handle")!); + }); + try { + await host.start(); + await page.goto(host.origin); + await page.getByRole("button", { name: "Screenshot", exact: true }).click(); + const image = page.getByRole("img", { + name: "screenshot.png", + exact: true, + }); + await expect(image).toBeVisible(); + const sizing = await image.evaluate((element) => { + const img = element as HTMLImageElement; + return { + width: img.width, + naturalWidth: img.naturalWidth, + naturalHeight: img.naturalHeight, + panel: img.closest(".artifact-panel-body")!.clientWidth, + }; + }); + expect(sizing.naturalWidth).toBe(1200); + expect(sizing.naturalHeight).toBe(600); + expect(sizing.width).toBeLessThan(sizing.panel); + const details = page.locator(".artifact-file-details"); + await expect(details).not.toHaveAttribute("open"); + await details.locator("summary").click(); + await expect(details).toContainText(manager.getSessionId()); + expect(downloads).toHaveLength(1); + const blobUrl = await image.getAttribute("src"); + await page.screenshot({ + path: testInfo.outputPath("artifact-image-preview.png"), + }); + await page.getByRole("button", { name: /Close preview|关闭预览/u }).click(); + expect( + await page.evaluate(async (url) => { + try { + await fetch(url!); + return true; + } catch { + return false; + } + }, blobUrl), + ).toBe(false); + await page.getByRole("button", { name: "Vector", exact: true }).click(); + await expect(page.locator(".artifact-revision")).toHaveCount(1); + await expect(page.locator(".artifact-image-preview")).toHaveCount(0); + expect(downloads).toHaveLength(1); + expect( + await page.evaluate(() => Reflect.get(window, "previewScriptExecuted")), + ).toBeUndefined(); + await page.getByRole("button", { name: /Close preview|关闭预览/u }).click(); + await page.getByRole("button", { name: "Broken", exact: true }).click(); + await expect( + page.locator(".artifact-panel").getByRole("alert"), + ).toContainText(/preview this image|图片预览失败/u); + await expect(page.locator(".artifact-image-preview img")).toHaveCount(0); + expect(downloads).toHaveLength(2); + } finally { + await context.close(); + await host.stop(); + await rm(cwd, { recursive: true, force: true }); + } +}); diff --git a/tests/web/artifact-images.spec.ts b/tests/web/artifact-images.spec.ts new file mode 100644 index 00000000..973fac6b --- /dev/null +++ b/tests/web/artifact-images.spec.ts @@ -0,0 +1,340 @@ +// @vitest-environment jsdom + +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { createElement } from "react"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; +import type { ArtifactPreview } from "../../web/protocol/artifacts.ts"; +import { Markdown } from "../../web/ui/src/components/Markdown.tsx"; +import { ArtifactProvider } from "../../web/ui/src/features/artifacts/Artifacts.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; +import { WebApiError, WebClient } from "../../web/ui/src/protocol/client.ts"; + +const createUrl = vi.fn((_blob: Blob) => "blob:artifact-image"); +const revokeUrl = vi.fn((_url: string) => {}); +const png = () => new Blob([new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])]); + +beforeEach(() => { + createUrl.mockClear(); + revokeUrl.mockClear(); + const OriginalURL = URL; + vi.stubGlobal( + "URL", + class extends OriginalURL { + static createObjectURL = createUrl; + static revokeObjectURL = revokeUrl; + }, + ); + vi.spyOn(WebClient.prototype, "resolveArtifact").mockImplementation( + async (_session, reference) => ({ handle: reference }), + ); + vi.spyOn(WebClient.prototype, "releaseArtifact").mockResolvedValue({}); + vi.spyOn(WebClient.prototype, "artifactPreview").mockImplementation( + async (sessionId, handle): Promise => ({ + artifact: { + handle, + sessionId, + name: handle.slice(2), + path: `/workspace/${handle.slice(2)}`, + revision: "a".repeat(64), + bytes: 8, + preview: "unsupported", + }, + identity: handle, + truncated: false, + }), + ); + vi.spyOn(WebClient.prototype, "artifactMetadata").mockImplementation( + async (_session, handle) => ({ identity: handle }), + ); +}); +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + vi.unstubAllGlobals(); +}); + +const node = (sessionId = "s", first = "first.png") => + createElement( + ArtifactProvider, + { sessionId }, + createElement(Markdown, null, `[First](./${first}) [Second](./second.png)`), + ); + +it.each([ + ["png", [137, 80, 78, 71, 13, 10, 26, 10], "image/png"], + ["jpg", [255, 216, 255], "image/jpeg"], + ["gif", Array.from("GIF89a", (char) => char.charCodeAt(0)), "image/gif"], + [ + "webp", + Array.from("RIFF0000WEBP", (char) => char.charCodeAt(0)), + "image/webp", + ], +] as const)( + "previews a %s through its existing authorized download and releases its blob on close", + async (extension, bytes, mime) => { + const download = vi + .spyOn(WebClient.prototype, "downloadArtifact") + .mockResolvedValue(new Blob([new Uint8Array(bytes)])); + render(node("s", `first.${extension}`)); + fireEvent.click(screen.getByRole("button", { name: "First" })); + const image = await screen.findByAltText( + `first.${extension}`, + ); + expect(download).toHaveBeenCalledWith( + expect.objectContaining({ + sessionId: "s", + handle: `./first.${extension}`, + revision: "a".repeat(64), + }), + expect.any(AbortSignal), + ); + expect(createUrl.mock.calls[0]![0].type).toBe(mime); + expect(image.hidden).toBe(true); + fireEvent.load(image); + expect(image.hidden).toBe(false); + expect(screen.queryByText(i18n.t("artifactUnsupported"))).toBeNull(); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("closePreview") }), + ); + expect(download.mock.calls[0]![1].aborted).toBe(true); + expect(revokeUrl).toHaveBeenCalledWith("blob:artifact-image"); + }, +); + +it.each(["close", "session"])( + "ignores an image download that finishes after %s", + async (action) => { + let finish!: (blob: Blob) => void; + const download = vi + .spyOn(WebClient.prototype, "downloadArtifact") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const view = render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + await waitFor(() => expect(download).toHaveBeenCalledOnce()); + if (action === "close") + fireEvent.click( + screen.getByRole("button", { name: i18n.t("closePreview") }), + ); + else view.rerender(node("different-session")); + expect(download.mock.calls[0]![1].aborted).toBe(true); + await act(async () => finish(png())); + expect(createUrl).not.toHaveBeenCalled(); + expect(screen.queryByRole("complementary")).toBeNull(); + }, +); + +it("releases the previous image and cancels the new read when its panel unmounts", async () => { + let finish!: (blob: Blob) => void; + const download = vi + .spyOn(WebClient.prototype, "downloadArtifact") + .mockResolvedValueOnce(png()) + .mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const view = render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + await screen.findByAltText("first.png"); + fireEvent.click(screen.getByRole("button", { name: "Second" })); + await waitFor(() => expect(download).toHaveBeenCalledTimes(2)); + expect(revokeUrl).toHaveBeenCalledWith("blob:artifact-image"); + expect(screen.queryByAltText("first.png")).toBeNull(); + view.unmount(); + expect(download.mock.calls[1]![1].aborted).toBe(true); + await act(async () => finish(png())); + expect(createUrl).toHaveBeenCalledOnce(); +}); + +it("rejects disguised SVG bytes and keeps the explicit download available", async () => { + vi.spyOn(WebClient.prototype, "downloadArtifact").mockResolvedValue( + new Blob([""]), + ); + render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + expect((await screen.findByRole("alert")).textContent).toContain( + i18n.t("artifactImagePreviewFailed"), + ); + expect(createUrl).not.toHaveBeenCalled(); + expect( + screen.getByRole("button", { + name: i18n.t("downloadFile"), + }).disabled, + ).toBe(false); +}); + +it("reports raster decode errors without leaving a broken image", async () => { + vi.spyOn(WebClient.prototype, "downloadArtifact").mockResolvedValue(png()); + render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + const image = await screen.findByAltText("first.png"); + fireEvent.error(image); + expect(screen.getByRole("alert").textContent).toContain( + i18n.t("artifactImagePreviewFailed"), + ); + expect(screen.queryByAltText("first.png")).toBeNull(); +}); + +it("keeps a decoded image when a metadata refresh returns the same authorized content", async () => { + const first: ArtifactPreview = { + artifact: { + sessionId: "s", + handle: "./first.png", + path: "/workspace/first.png", + name: "first.png", + revision: "a".repeat(64), + bytes: 8, + preview: "unsupported", + }, + identity: "before-touch", + truncated: false, + }; + const previews = vi + .mocked(WebClient.prototype.artifactPreview) + .mockResolvedValueOnce(first) + .mockResolvedValue({ + ...first, + artifact: { ...first.artifact }, + identity: "after-touch", + }); + vi.mocked(WebClient.prototype.artifactMetadata).mockResolvedValue({ + identity: "after-touch", + }); + const download = vi + .spyOn(WebClient.prototype, "downloadArtifact") + .mockResolvedValue(png()); + render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + const image = await screen.findByAltText("first.png"); + fireEvent.load(image); + expect(image.hidden).toBe(false); + await waitFor(() => expect(previews).toHaveBeenCalledTimes(2), { + timeout: 3500, + }); + expect(download).toHaveBeenCalledOnce(); + expect(download.mock.calls[0]![1].aborted).toBe(false); + expect(revokeUrl).not.toHaveBeenCalled(); + expect(screen.getByAltText("first.png")).toBe(image); + expect(image.hidden).toBe(false); +}); + +it.each(["revision", "handle", "path"] as const)( + "replaces the image when its %s changes and cancels a late replacement", + async (field) => { + const first: ArtifactPreview = { + artifact: { + sessionId: "s", + handle: "./first.png", + path: "/workspace/first.png", + name: "first.png", + revision: "a".repeat(64), + bytes: 8, + preview: "unsupported", + }, + identity: "before", + truncated: false, + }; + const updated = { + ...first.artifact, + [field]: + field === "revision" + ? "b".repeat(64) + : field === "path" + ? "/another/first.png" + : "new-grant", + }; + vi.mocked(WebClient.prototype.artifactPreview) + .mockResolvedValueOnce(first) + .mockResolvedValue({ ...first, artifact: updated, identity: "after" }); + vi.mocked(WebClient.prototype.artifactMetadata).mockResolvedValue({ + identity: "after", + }); + let finish!: (blob: Blob) => void; + const download = vi + .spyOn(WebClient.prototype, "downloadArtifact") + .mockResolvedValueOnce(png()) + .mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const view = render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + const image = await screen.findByAltText("first.png"); + fireEvent.load(image); + await waitFor(() => expect(download).toHaveBeenCalledTimes(2), { + timeout: 3500, + }); + expect(download.mock.calls[0]![1].aborted).toBe(true); + expect(image.isConnected).toBe(false); + expect(revokeUrl).toHaveBeenCalledWith("blob:artifact-image"); + view.unmount(); + expect(download.mock.calls[1]![1].aborted).toBe(true); + await act(async () => finish(png())); + expect(createUrl).toHaveBeenCalledOnce(); + }, +); + +it.each([401, 403, 410])( + "cancels an in-flight image when metadata returns revoked access (%s)", + async (status) => { + vi.mocked(WebClient.prototype.artifactMetadata).mockRejectedValue( + new WebApiError( + "File access revoked", + status, + status === 410 ? "ARTIFACT_EXPIRED" : "ARTIFACT_DENIED", + ), + ); + let finish!: (blob: Blob) => void; + const download = vi + .spyOn(WebClient.prototype, "downloadArtifact") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const view = render(node()); + fireEvent.click(screen.getByRole("button", { name: "First" })); + await waitFor(() => expect(download).toHaveBeenCalledOnce()); + await screen.findByText("File access revoked", {}, { timeout: 3500 }); + expect(view.container.querySelector(".artifact-image-preview")).toBeNull(); + expect( + screen.getByRole("button", { + name: i18n.t("downloadFile"), + }).disabled, + ).toBe(true); + // Revoked content is removed in the commit; React runs the child's + // passive-effect cleanup (which aborts the fetch) after that commit. + await waitFor(() => expect(download.mock.calls[0]![1].aborted).toBe(true)); + await act(async () => finish(png())); + expect(createUrl).not.toHaveBeenCalled(); + expect(screen.queryByAltText("first.png")).toBeNull(); + }, +); + +it.each(["svg", "html"])( + "does not automatically download a %s as an image", + async (extension) => { + const download = vi.spyOn(WebClient.prototype, "downloadArtifact"); + render(node("s", `first.${extension}`)); + fireEvent.click(screen.getByRole("button", { name: "First" })); + await screen.findByText(i18n.t("artifactUnsupported")); + expect(download).not.toHaveBeenCalled(); + expect(createUrl).not.toHaveBeenCalled(); + }, +); diff --git a/tests/web/artifact-lifecycle.spec.ts b/tests/web/artifact-lifecycle.spec.ts new file mode 100644 index 00000000..077913d8 --- /dev/null +++ b/tests/web/artifact-lifecycle.spec.ts @@ -0,0 +1,128 @@ +// @vitest-environment jsdom +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { createElement, useContext } from "react"; +import { afterEach, expect, it, vi } from "vitest"; +import type { ArtifactPreview } from "../../web/protocol/artifacts.ts"; +import { ArtifactProvider } from "../../web/ui/src/features/artifacts/Artifacts.tsx"; +import { ArtifactContext } from "../../web/ui/src/features/artifacts/context.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; +import { i18n } from "../../web/ui/src/i18n.ts"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); +function Content() { + const artifacts = useContext(ArtifactContext); + return createElement( + "div", + null, + createElement( + "button", + { type: "button", onClick: () => artifacts?.open("report.txt") }, + "Open file", + ), + createElement("input", { "aria-label": "Settings input" }), + ); +} +it.each(["disabled", "session"])( + "notifies the outer panel when %s closes a pending preview without stealing focus", + async (boundary) => { + vi.spyOn(WebClient.prototype, "resolveArtifact").mockResolvedValue({ + handle: "grant", + }); + vi.spyOn(WebClient.prototype, "releaseArtifact").mockResolvedValue({}); + let finish!: (value: ArtifactPreview) => void; + const read = vi + .spyOn(WebClient.prototype, "artifactPreview") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const close = vi.fn(); + const node = (changed = false) => + createElement( + ArtifactProvider, + { + sessionId: + changed && boundary === "session" ? "other-session" : "session", + disabled: changed && boundary === "disabled", + onClose: close, + }, + createElement(Content), + ); + const view = render(node()); + const opener = screen.getByRole("button", { name: "Open file" }); + opener.focus(); + fireEvent.click(opener); + await waitFor(() => expect(read).toHaveBeenCalledOnce()); + const settings = screen.getByRole("textbox", { name: "Settings input" }); + settings.focus(); + view.rerender(node(true)); + expect(screen.queryByRole("complementary")).toBeNull(); + expect(document.activeElement).toBe(settings); + expect(close).toHaveBeenCalledExactlyOnceWith("context"); + expect(read.mock.calls[0]![2]?.aborted).toBe(true); + await act(async () => + finish({ + artifact: { + sessionId: "session", + handle: "grant", + path: "/report.txt", + name: "report.txt", + revision: "a", + bytes: 4, + preview: "text", + }, + text: "Late file", + truncated: false, + }), + ); + expect(screen.queryByText("Late file")).toBeNull(); + expect(close).toHaveBeenCalledOnce(); + }, +); + +it("keeps an explicit close distinct and returns focus to its opener", async () => { + vi.spyOn(WebClient.prototype, "resolveArtifact").mockResolvedValue({ + handle: "grant", + }); + vi.spyOn(WebClient.prototype, "releaseArtifact").mockResolvedValue({}); + vi.spyOn(WebClient.prototype, "artifactPreview").mockResolvedValue({ + artifact: { + sessionId: "s", + handle: "grant", + path: "/report.txt", + name: "report.txt", + revision: "a", + bytes: 4, + preview: "text", + }, + text: "File text", + truncated: false, + }); + const close = vi.fn(); + render( + createElement( + ArtifactProvider, + { sessionId: "s", onClose: close }, + createElement(Content), + ), + ); + const opener = screen.getByRole("button", { name: "Open file" }); + opener.focus(); + fireEvent.click(opener); + await screen.findByText("File text"); + fireEvent.click(screen.getByRole("button", { name: i18n.t("closePreview") })); + expect(close).toHaveBeenCalledExactlyOnceWith("user"); + expect(document.activeElement).toBe(opener); +}); diff --git a/tests/web/artifacts.test.ts b/tests/web/artifacts.test.ts index 5bbfddf2..00d11cde 100644 --- a/tests/web/artifacts.test.ts +++ b/tests/web/artifacts.test.ts @@ -23,6 +23,51 @@ function code(value: string) { error instanceof ArtifactError && error.code === value; } +test("explicit external-file authorization is read-only, exact-file and session-bound", async () => { + const root = await mkdtemp(join(tmpdir(), "openpi-external-file-")); + const workspace = join(root, "workspace"); + const other = join(root, "worktree"); + await mkdir(workspace); + await mkdir(other); + const file = join(other, "index%20.ts"); + await writeFile(file, "export const value = 1;\n"); + await writeFile(join(other, "neighbor.ts"), "not granted"); + let sessionId = "s"; + const reader = new ArtifactReader(() => ({ sessionId, cwd: workspace })); + try { + await assert.rejects( + reader.resolveFile("s", file), + code("ARTIFACT_DENIED"), + ); + await assert.rejects( + reader.authorizeFile("other", file), + code("ARTIFACT_DENIED"), + ); + const handle = await reader.authorizeFile("s", file.replaceAll("%", "%25")); + assert.equal( + (await reader.read(handle, "s")).preview.text, + "export const value = 1;\n", + ); + await assert.rejects( + reader.resolveFile("s", "neighbor.ts", handle), + code("ARTIFACT_DENIED"), + ); + await assert.rejects( + reader.authorizeFile("s", "../worktree/neighbor.ts"), + code("ARTIFACT_DENIED"), + ); + await assert.rejects( + reader.authorizeFile("s", other), + code("ARTIFACT_UNSUPPORTED"), + ); + sessionId = "next"; + await assert.rejects(reader.read(handle, "s"), code("ARTIFACT_EXPIRED")); + } finally { + reader.dispose(); + await rm(root, { recursive: true, force: true }); + } +}); + for (const [name, source, expected, truncated] of [ ["literal replacement character", "report: \ufffd", "report: \ufffd", false], ["UTF-8 BOM", "\ufeffreport", "\ufeffreport", false], diff --git a/tests/web/browser-cold-start-smoke.mjs b/tests/web/browser-cold-start-smoke.mjs new file mode 100644 index 00000000..ef0522b7 --- /dev/null +++ b/tests/web/browser-cold-start-smoke.mjs @@ -0,0 +1,50 @@ +import assert from "node:assert/strict"; +import { createServer } from "node:http"; +import { EmbeddedBrowserManager } from "../../web/host/embedded-browser.ts"; + +// A fresh profile/process every time. A failed sample remains a failure; +// subsequent samples collect evidence rather than retrying away the outcome. +const fixture = createServer((_request, response) => { + response.setHeader("Content-Type", "text/html; charset=utf-8"); + response.end("Cold startupReady"); +}); +await new Promise((resolve) => fixture.listen(0, "127.0.0.1", resolve)); +const address = fixture.address(); +assert.ok(address && typeof address !== "string"); +const results = []; +try { + for (let sample = 1; sample <= 3; sample++) { + const manager = new EmbeddedBrowserManager(); + const started = performance.now(); + try { + await manager.open( + `cold-start-${sample}`, + `http://127.0.0.1:${address.port}/`, + ); + results.push({ + sample, + success: true, + elapsedMs: Math.round(performance.now() - started), + }); + } catch (error) { + results.push({ + sample, + success: false, + elapsedMs: Math.round(performance.now() - started), + error: error instanceof Error ? error.message : String(error), + }); + } finally { + await manager.dispose(); + } + console.log(JSON.stringify(results.at(-1))); + } +} finally { + fixture.closeAllConnections(); + await new Promise((resolve, reject) => + fixture.close((error) => (error ? reject(error) : resolve())), + ); +} +assert.ok( + results.every((result) => result.success), + "Every cold startup must succeed", +); diff --git a/tests/web/command-discovery.test.ts b/tests/web/command-discovery.test.ts index 5c072d01..f6da93c7 100644 --- a/tests/web/command-discovery.test.ts +++ b/tests/web/command-discovery.test.ts @@ -11,6 +11,7 @@ import { WEB_MAX_COMMANDS, } from "../../web/protocol/types.ts"; import { + assertWebCommandSupported, commandsForServices, createCommandDiscoveryBridge, projectWebCommands, @@ -59,6 +60,7 @@ test("projects Pi command identities without exposing source metadata", () => { name: "extension:run", source: "extension", availability: "unsupported", + unavailableReason: "not_integrated", description: "Run an extension", }, { @@ -110,6 +112,111 @@ test("bounds command projections by count, bytes, and scan work", () => { assert.equal(scanBounded.truncation.commandsOmitted, 1_025); }); +function ownedCommand(name: string, extension: string) { + const item = command(name, "extension"); + item.sourceInfo.path = fileURLToPath( + new URL(`../../extensions/${extension}/index.ts`, import.meta.url), + ); + return item; +} + +test("offers native setup and existing Web panels only for the actual package command sources", () => { + const result = projectWebCommands([ + ownedCommand("openpi-setup", "setup"), + ownedCommand("my-pi-setup", "setup"), + ownedCommand("ps", "background-terminals"), + ownedCommand("lg", "git-info"), + ownedCommand("subagents", "subagents"), + ownedCommand("usage", "usage"), + ownedCommand("btw", "subagents"), + ownedCommand("sessions", "sessions"), + command("openpi-setup", "extension"), + command("ps", "extension"), + ]); + assert.deepEqual( + result.commands.slice(0, 2).map(({ name, availability, action }) => ({ + name, + availability, + action, + })), + [ + { name: "openpi-setup", availability: "available", action: undefined }, + { name: "my-pi-setup", availability: "available", action: undefined }, + ], + ); + assert.deepEqual( + result.commands.slice(2, 6).map(({ action }) => action), + ["terminal", "review", "subagents", undefined], + ); + assert.equal(result.commands[6]?.action, "side-conversation"); + assert.equal(result.commands[7]?.unavailableReason, "terminal_only"); + assert.ok( + result.commands + .slice(8) + .every( + (item) => + item.availability === "unsupported" && item.action === undefined, + ), + ); + assert.equal(JSON.stringify(result).includes("extensions/"), false); +}); + +test("guards the native untruncated registry while leaving ordinary prompts and unknown slash text to Pi", async () => { + const services = Object.create(null) as AgentSessionServices; + const available = [ + ...Array.from({ length: WEB_MAX_COMMANDS + 1 }, (_, index) => + command(`template-${index}`, "prompt"), + ), + ownedCommand("openpi-setup", "setup"), + ownedCommand("ps", "background-terminals"), + ownedCommand("btw", "subagents"), + command("unreviewed", "extension"), + ]; + const bridge = createCommandDiscoveryBridge(); + const api = { getCommands: () => available } as ExtensionAPI; + await (typeof bridge.extension === "function" + ? bridge.extension(api) + : bridge.extension.factory(api)); + registerCommandDiscoveryBridge(services, bridge); + assert.equal( + commandsForServices(services).commands.some((item) => item.name === "btw"), + false, + ); + assert.doesNotThrow(() => + assertWebCommandSupported(services, "ordinary text"), + ); + assert.doesNotThrow(() => + assertWebCommandSupported(services, "/unknown/path"), + ); + assert.doesNotThrow(() => + assertWebCommandSupported(services, "/template-0 request"), + ); + assert.doesNotThrow(() => + assertWebCommandSupported(services, "/openpi-setup inspect settings"), + ); + assert.throws( + () => assertWebCommandSupported(services, "/ps"), + /opens a Web panel/u, + ); + assert.throws( + () => assertWebCommandSupported(services, "/ps ignored arguments"), + /without arguments/u, + ); + assert.throws( + () => assertWebCommandSupported(services, "/btw question"), + /opens a Web panel/u, + ); + assert.throws( + () => assertWebCommandSupported(services, "/unreviewed"), + /not available in the Web runtime/u, + ); + available.push(command("unsafe", "extension")); + assert.throws( + () => assertWebCommandSupported(services, "/unsafe"), + /not available/u, + ); +}); + test("fails closed before binding and follows the bridge registered for services", async () => { const services = Object.create(null) as AgentSessionServices; assert.throws( diff --git a/tests/web/composer-clipboard.e2e.ts b/tests/web/composer-clipboard.e2e.ts new file mode 100644 index 00000000..b028300f --- /dev/null +++ b/tests/web/composer-clipboard.e2e.ts @@ -0,0 +1,85 @@ +import { expect, test } from "@playwright/test"; +import { installThinkingFixture } from "./thinking-e2e-support.ts"; + +test("native Chromium image paste preserves accompanying text, caret, undo and image payloads", async ({ + page, + context, +}) => { + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + await installThinkingFixture(page); + let admission: + | { content: string; images?: { mimeType: string; data: string }[] } + | undefined; + await page.route("**/api/prompt", async (route) => { + admission = route.request().postDataJSON(); + await route.fulfill({ + status: 202, + json: { id: "clipboard-prompt", accepted: true }, + }); + }); + await page.goto("/"); + const input = page.getByRole("textbox", { name: "描述任务" }); + await expect(input).toBeEnabled(); + await input.fill("before after"); + await input.evaluate((element) => { + if (element instanceof HTMLTextAreaElement) element.setSelectionRange(7, 7); + }); + await page.evaluate(async () => { + const canvas = document.createElement("canvas"); + canvas.width = 64; + canvas.height = 32; + const drawing = canvas.getContext("2d")!; + drawing.fillStyle = "#123abc"; + drawing.fillRect(0, 0, 64, 32); + const png = await new Promise((resolve, reject) => + canvas.toBlob( + (blob) => (blob ? resolve(blob) : reject(new Error("No PNG"))), + "image/png", + ), + ); + await navigator.clipboard.write([ + new ClipboardItem({ + "image/png": png, + "text/plain": new Blob(["caption"], { type: "text/plain" }), + }), + ]); + }); + const modifier = process.platform === "darwin" ? "Meta" : "Control"; + await input.press(`${modifier}+V`); + await expect(input).toHaveValue("before caption after"); + await expect(page.locator(".composer-attachment")).toHaveCount(1); + await input.press(`${modifier}+Z`); + await expect(input).toHaveValue("before after"); + await input.press(`${modifier}+Shift+Z`); + await expect(input).toHaveValue("before caption after"); + await page.evaluate(async () => { + const items = await navigator.clipboard.read(); + const png = await items[0]!.getType("image/png"); + await navigator.clipboard.write([new ClipboardItem({ "image/png": png })]); + }); + await input.press(`${modifier}+V`); + await expect(page.locator(".composer-attachment")).toHaveCount(2); + await expect(input).toHaveValue("before caption after"); + await expect + .poll(() => + page + .locator(".composer-attachment img") + .evaluateAll((images) => + images.every( + (image) => + image instanceof HTMLImageElement && image.naturalWidth === 64, + ), + ), + ) + .toBe(true); + await page.getByRole("button", { name: "发送", exact: true }).click(); + await expect.poll(() => admission).toBeTruthy(); + expect(admission?.content).toBe("before caption after"); + expect(admission?.images).toHaveLength(2); + expect( + admission?.images?.every( + (image) => + image.mimeType === "image/png" && image.data.startsWith("iVBOR"), + ), + ).toBe(true); +}); diff --git a/tests/web/composer-context.spec.ts b/tests/web/composer-context.spec.ts index 24ab6f4e..36a32327 100644 --- a/tests/web/composer-context.spec.ts +++ b/tests/web/composer-context.spec.ts @@ -3,6 +3,7 @@ import { act, cleanup, + createEvent, fireEvent, render, screen, @@ -13,10 +14,14 @@ import { I18nextProvider } from "react-i18next"; import { afterAll, afterEach, beforeAll, expect, it, vi } from "vitest"; import type { WebSnapshot } from "../../web/protocol/types.ts"; import { Composer } from "../../web/ui/src/features/composer/Composer.tsx"; +import { FileReferenceDialog } from "../../web/ui/src/features/composer/FileReferenceDialog.tsx"; import { i18n } from "../../web/ui/src/i18n.ts"; import { compactSummary } from "../../web/ui/src/lib/format.ts"; import { WebApiError, WebClient } from "../../web/ui/src/protocol/client.ts"; -import { createWebStore } from "../../web/ui/src/store/web-store.ts"; +import { + createWebStore, + type WebStoreState, +} from "../../web/ui/src/store/web-store.ts"; beforeAll(() => { Object.defineProperty(HTMLDialogElement.prototype, "showModal", { @@ -102,8 +107,10 @@ function setup(overrides: Record = {}) { const sendPrompt = vi.fn(async (_content: string) => true); const discoverCommands = vi.fn(async () => {}); const props = { + workspaceDraft: false, + createdSession: null as WebStoreState["createdSession"], snapshot: snapshot(), - selectedPath: "/tmp/workspace/one.jsonl", + selectedPath: "/tmp/workspace/one.jsonl" as string | null, selectedWorkspace: "/tmp/workspace", sessionSwitching: false, promptAdmissionPending: false, @@ -123,6 +130,72 @@ function setup(overrides: Record = {}) { return { ...view, node, props, sendPrompt, discoverCommands }; } +it("lets a user click Send for a follow-up while Stop remains available", async () => { + const value = snapshot(); + value.runtime.status = "running"; + value.runtime.activeTurn = { + sessionId: "session-1", + sessionPath: "/tmp/workspace/one.jsonl", + commandId: "running", + epoch: 1, + }; + value.selectedExecution = { + sessionId: "session-1", + sessionPath: "/tmp/workspace/one.jsonl", + status: "running", + pendingFollowUps: 2, + liveTools: [], + liveToolsOmitted: 0, + }; + const { props, sendPrompt } = setup({ + snapshot: value, + liveRunning: true, + pendingFollowUpsReceipt: 1, + }); + const cancel = vi.spyOn(props.actions, "cancelActiveTurn"); + fireEvent.change(screen.getByRole("textbox"), { + target: { value: "Follow-up by button" }, + }); + expect( + screen.getByRole("button", { name: i18n.t("stopTurn") }) + .disabled, + ).toBe(false); + expect( + screen.getByText(i18n.t("pendingFollowUpsHint", { count: 2 })), + ).toBeTruthy(); + expect( + screen.queryByText(i18n.t("pendingFollowUpsHint", { count: 1 })), + ).toBeNull(); + await act(async () => + fireEvent.click(screen.getByRole("button", { name: i18n.t("send") })), + ); + expect(sendPrompt).toHaveBeenCalledWith("Follow-up by button"); + expect(cancel).not.toHaveBeenCalled(); +}); + +it("hides another controller's model and stale queue receipt while offering activation", () => { + const value = snapshot(); + value.currentSessionId = "other"; + value.currentSessionPath = "/tmp/other.jsonl"; + value.models = [ + { + provider: "p", + id: "b", + name: "Other model", + label: "Other model", + current: true, + }, + ]; + setup({ snapshot: value, pendingFollowUpsReceipt: 1 }); + expect(screen.queryByRole("button", { name: /Other model/u })).toBeNull(); + expect( + screen.queryByText(i18n.t("pendingFollowUpsHint", { count: 1 })), + ).toBeNull(); + expect( + screen.getByRole("button", { name: i18n.t("activateViewedSession") }), + ).toBeTruthy(); +}); + it("shows the snapshot target and unified context actions", async () => { const { discoverCommands, sendPrompt } = setup(); expect(screen.getByText("First task")).toBeTruthy(); @@ -270,6 +343,79 @@ it("keeps a failed active-session reference editable", async () => { ).toBeTruthy(); }); +it("allows cancelling a file reference while validation is pending", async () => { + const resolve = vi + .spyOn(WebClient.prototype, "resolveArtifact") + .mockImplementation(() => new Promise(() => {})); + setup(); + fireEvent.click(screen.getByRole("button", { name: i18n.t("addContext") })); + fireEvent.click( + screen.getByRole("menuitem", { name: /Reference workspace file/u }), + ); + fireEvent.change( + screen.getByRole("textbox", { name: i18n.t("fileReferenceLabel") }), + { target: { value: "README.md" } }, + ); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("insertReference") }), + ); + const cancel = screen.getByRole("button", { + name: i18n.t("cancel"), + }); + expect(cancel.disabled).toBe(false); + fireEvent.click(cancel); + expect(resolve.mock.calls[0]?.[3]?.aborted).toBe(true); + expect(screen.queryByRole("dialog")).toBeNull(); +}); + +it.each(["session-change", "unmount"])( + "never inserts a pending file reference after %s", + async (boundary) => { + let finish!: (value: { handle: string }) => void; + const resolve = vi + .spyOn(WebClient.prototype, "resolveArtifact") + .mockImplementation( + () => + new Promise((done) => { + finish = done; + }), + ); + vi.spyOn(WebClient.prototype, "artifactMetadata").mockResolvedValue({ + identity: "stable", + }); + const release = vi + .spyOn(WebClient.prototype, "releaseArtifact") + .mockResolvedValue({}); + const onInsert = vi.fn(); + const node = (sessionId: string) => + createElement( + I18nextProvider, + { i18n }, + createElement(FileReferenceDialog, { + open: true, + sessionId, + onClose: vi.fn(), + onInsert, + }), + ); + const view = render(node("a")); + fireEvent.change( + screen.getByRole("textbox", { name: i18n.t("fileReferenceLabel") }), + { target: { value: "README.md" } }, + ); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("insertReference") }), + ); + if (boundary === "unmount") view.unmount(); + else view.rerender(node("b")); + const cancelled = resolve.mock.calls[0]?.[3]?.aborted; + await act(async () => finish({ handle: "old-handle" })); + expect(cancelled).toBe(true); + expect(onInsert).not.toHaveBeenCalled(); + expect(release).toHaveBeenCalledWith("a", "old-handle"); + }, +); + it("can add a visible file path to the first message of a new session", async () => { const resolve = vi.spyOn(WebClient.prototype, "resolveArtifact"); setup({ workspaceDraft: true }); @@ -277,6 +423,12 @@ it("can add a visible file path to the first message of a new session", async () name: i18n.t("describeTask"), }); fireEvent.click(screen.getByRole("button", { name: i18n.t("addContext") })); + // Astryx commits menu focus on the next animation frame. + await waitFor(() => + expect(document.activeElement).toBe( + screen.getByRole("menuitem", { name: /^Add images/u }), + ), + ); fireEvent.click( screen.getByRole("menuitem", { name: /Reference workspace file/u }), ); @@ -290,7 +442,7 @@ it("can add a visible file path to the first message of a new session", async () await waitFor(() => expect(input.value).toBe("`README.md`")); expect(resolve).not.toHaveBeenCalled(); - expect(document.activeElement).toBe(input); + await waitFor(() => expect(document.activeElement).toBe(input)); }); it("does not submit with Enter while a model choice is unconfirmed", () => { @@ -303,6 +455,317 @@ it("does not submit with Enter while a model choice is unconfirmed", () => { expect(input.value).toBe("keep me"); }); +it("retains the first draft when opening a control prepares an empty Session", () => { + const empty = snapshot(); + delete empty.selectedSession; + delete empty.currentSessionId; + const { props, node, rerender, sendPrompt } = setup({ + workspaceDraft: true, + snapshot: empty, + }); + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "do not lose my first draft" } }); + rerender(node({ ...props, sessionSwitching: true })); + rerender( + node({ + ...props, + workspaceDraft: false, + snapshot: snapshot(), + createdSession: { + epoch: 1, + sessionId: "session-1", + sessionPath: "/tmp/workspace/one.jsonl", + workspacePath: "/tmp/workspace", + }, + }), + ); + expect(input.value).toBe("do not lose my first draft"); + expect(sendPrompt).not.toHaveBeenCalled(); +}); + +function pendingImage(name = "delayed.png") { + const file = new File(["pending"], name, { type: "image/png" }); + const bytes = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]).buffer; + let finish!: (bytes: ArrayBuffer) => void; + const reading = new Promise((resolve) => { + finish = resolve; + }); + const read = vi.fn(() => reading); + Object.defineProperty(file, "arrayBuffer", { value: read }); + return { file, read, finish: () => finish(bytes) }; +} + +function pasteImages(input: HTMLElement, files: File[], text = "") { + const event = createEvent.paste(input, { + clipboardData: { files, getData: () => text }, + }); + fireEvent(input, event); + return event; +} + +it("accepts a clipboard image exposed through items without swallowing accompanying text", async () => { + setup(); + const image = pendingImage("clipboard.png"); + const input = screen.getByRole("textbox"); + const event = createEvent.paste(input, { + clipboardData: { + files: [], + items: [{ kind: "file", getAsFile: () => image.file }], + getData: () => "copied caption", + }, + }); + fireEvent(input, event); + expect(event.defaultPrevented).toBe(false); + expect( + screen.getByText(i18n.t("imageAttachmentsLoading")).getAttribute("role"), + ).toBe("status"); + await act(async () => image.finish()); + expect(screen.getByText("clipboard.png")).toBeTruthy(); + expect(screen.queryByText(i18n.t("imageAttachmentsLoading"))).toBeNull(); +}); + +it("retains rapid image pastes in order while an earlier image is still reading", async () => { + const { sendPrompt } = setup(); + const input = screen.getByRole("textbox"); + const first = pendingImage("first.png"); + const second = pendingImage("second.png"); + expect(pasteImages(input, [first.file]).defaultPrevented).toBe(true); + pasteImages(input, [second.file]); + expect(second.read).not.toHaveBeenCalled(); + await act(async () => second.finish()); + expect(screen.queryByText("second.png")).toBeNull(); + await act(async () => first.finish()); + fireEvent.keyDown(input, { key: "Enter" }); + expect(sendPrompt).toHaveBeenCalledWith("", [ + expect.objectContaining({ name: "first.png" }), + expect.objectContaining({ name: "second.png" }), + ]); +}); + +it("keeps valid images from a paste even when another file is unsupported", async () => { + const { sendPrompt } = setup(); + const first = pendingImage("good.png"); + const last = pendingImage("last.png"); + const invalid = new File(["not an image"], "notes.txt", { + type: "text/plain", + }); + const input = screen.getByRole("textbox"); + pasteImages(input, [first.file, invalid, last.file]); + await act(async () => { + first.finish(); + last.finish(); + }); + expect(screen.getByRole("alert").textContent).toContain("notes.txt"); + expect(screen.getByText("good.png")).toBeTruthy(); + expect(screen.getByText("last.png")).toBeTruthy(); + fireEvent.keyDown(input, { key: "Enter" }); + expect(sendPrompt).toHaveBeenCalledWith("", [ + expect.objectContaining({ name: "good.png" }), + expect.objectContaining({ name: "last.png" }), + ]); +}); + +it("counts pending pastes toward the image limit and reports a rejected paste", async () => { + setup(); + const input = screen.getByRole("textbox"); + const accepted = Array.from({ length: 4 }, (_, index) => + pendingImage(`${index}.png`), + ); + const rejected = pendingImage("fifth.png"); + for (const image of accepted) pasteImages(input, [image.file]); + pasteImages(input, [rejected.file]); + expect(screen.getByRole("alert").textContent).toBe( + i18n.t("imageAttachmentCount", { count: 4 }), + ); + await act(async () => { + for (const image of accepted) image.finish(); + }); + expect( + screen.getAllByRole("button", { name: /^Remove attachment/u }), + ).toHaveLength(4); + expect(rejected.read).not.toHaveBeenCalled(); +}); + +it("discards queued clipboard images when their session is changed", async () => { + const { node, props, rerender } = setup(); + const input = screen.getByRole("textbox"); + const first = pendingImage("first.png"); + const queued = pendingImage("queued.png"); + pasteImages(input, [first.file]); + pasteImages(input, [queued.file]); + rerender( + node({ + ...props, + snapshot: snapshot("session-2", "/tmp/workspace/two.jsonl"), + selectedPath: "/tmp/workspace/two.jsonl", + }), + ); + await act(async () => first.finish()); + expect(queued.read).not.toHaveBeenCalled(); + expect(screen.queryByText("first.png")).toBeNull(); + expect(screen.queryByText("queued.png")).toBeNull(); + expect(screen.queryByText(i18n.t("imageAttachmentsLoading"))).toBeNull(); +}); + +it("keeps pending images when the exact creation receipt transfers the draft to its first Session", async () => { + const { node, props, rerender, sendPrompt } = setup({ workspaceDraft: true }); + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "Inspect the pasted image" } }); + const image = pendingImage("created.png"); + pasteImages(input, [image.file]); + const path = "/tmp/workspace/created.jsonl"; + rerender(node({ ...props, sessionSwitching: true, selectedPath: null })); + rerender( + node({ + ...props, + sessionSwitching: true, + selectedPath: path, + snapshot: snapshot("created-session", path), + }), + ); + rerender( + node({ + ...props, + workspaceDraft: false, + snapshot: snapshot("created-session", path), + selectedPath: path, + createdSession: { + epoch: 1, + sessionId: "created-session", + sessionPath: path, + workspacePath: "/tmp/workspace", + }, + }), + ); + const importStillPending = screen.getByRole("button", { + name: i18n.t("send"), + }).disabled; + await act(async () => image.finish()); + expect(screen.queryByText("created.png")).not.toBeNull(); + expect(importStillPending).toBe(true); + fireEvent.click(screen.getByRole("button", { name: i18n.t("send") })); + expect(sendPrompt).toHaveBeenCalledWith("Inspect the pasted image", [ + expect.objectContaining({ name: "created.png" }), + ]); +}); + +it.each(["missing", "different-id", "different-path", "different-workspace"])( + "does not transfer pending images to a same-workspace Session with a %s creation receipt", + async (mismatch) => { + const { node, props, rerender } = setup({ workspaceDraft: true }); + const image = pendingImage("unowned.png"); + pasteImages(screen.getByRole("textbox"), [image.file]); + const path = "/tmp/workspace/other.jsonl"; + rerender( + node({ + ...props, + workspaceDraft: false, + snapshot: snapshot("other-session", path), + selectedPath: path, + createdSession: + mismatch === "missing" + ? null + : { + epoch: 1, + sessionId: + mismatch === "different-id" + ? "unrelated-session" + : "other-session", + sessionPath: + mismatch === "different-path" + ? "/tmp/workspace/copied.jsonl" + : path, + workspacePath: + mismatch === "different-workspace" + ? "/another/workspace" + : "/tmp/workspace", + }, + }), + ); + await act(async () => image.finish()); + expect(screen.queryByText("unowned.png")).toBeNull(); + expect(screen.queryByText(i18n.t("imageAttachmentsLoading"))).toBeNull(); + }, +); + +it("waits for an in-progress attachment before either send gesture", async () => { + const { container, sendPrompt } = setup(); + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "Inspect the attached image" } }); + const image = pendingImage(); + fireEvent.change(container.querySelector('input[type="file"]')!, { + target: { files: [image.file] }, + }); + const send = screen.getByRole("button", { + name: i18n.t("send"), + }); + expect(send.disabled).toBe(true); + fireEvent.keyDown(input, { key: "Enter" }); + expect(sendPrompt).not.toHaveBeenCalled(); + await act(async () => image.finish()); + expect(send.disabled).toBe(false); + fireEvent.click(send); + expect(sendPrompt).toHaveBeenCalledWith("Inspect the attached image", [ + expect.objectContaining({ mimeType: "image/png", name: "delayed.png" }), + ]); +}); + +it("discards an attachment that finishes reading after switching sessions", async () => { + const { container, node, props, rerender } = setup(); + const image = pendingImage(); + fireEvent.change(container.querySelector('input[type="file"]')!, { + target: { files: [image.file] }, + }); + rerender( + node({ + ...props, + snapshot: snapshot("session-2", "/tmp/workspace/two.jsonl"), + selectedPath: "/tmp/workspace/two.jsonl", + }), + ); + await act(async () => image.finish()); + expect(screen.queryByText("delayed.png")).toBeNull(); + expect( + screen.getByRole("button", { name: i18n.t("send") }) + .disabled, + ).toBe(true); +}); + +it("does not let an old import settle a newer import after returning to the same session", async () => { + const { container, node, props, rerender } = setup(); + const old = pendingImage("old.png"); + fireEvent.change(container.querySelector('input[type="file"]')!, { + target: { files: [old.file] }, + }); + rerender( + node({ + ...props, + snapshot: snapshot("session-2", "/tmp/workspace/two.jsonl"), + selectedPath: "/tmp/workspace/two.jsonl", + }), + ); + rerender(node(props)); + const current = pendingImage("current.png"); + fireEvent.change(container.querySelector('input[type="file"]')!, { + target: { files: [current.file] }, + }); + fireEvent.change(screen.getByRole("textbox"), { + target: { value: "new draft" }, + }); + await act(async () => old.finish()); + expect(screen.queryByText("old.png")).toBeNull(); + expect( + screen.getByRole("button", { name: i18n.t("send") }) + .disabled, + ).toBe(true); + await act(async () => current.finish()); + expect(screen.getByText("current.png")).toBeTruthy(); + expect( + screen.getByRole("button", { name: i18n.t("send") }) + .disabled, + ).toBe(false); +}); + it("does not transfer a settled submission into a different session", async () => { let resolve!: (accepted: boolean) => void; const receipt = new Promise((done) => { @@ -332,3 +795,22 @@ it("does not transfer a settled submission into a different session", async () = }); expect(input.value).toBe("new draft"); }); + +it("does not expose send or stop for a copied Session with the controller's embedded id", () => { + const value = snapshot(); + value.selectedSession!.path = "/tmp/workspace/copied.jsonl"; + value.runtime.status = "running"; + value.runtime.activeTurn = { + sessionId: "session-1", + sessionPath: "/tmp/workspace/one.jsonl", + commandId: "owner-turn", + epoch: 1, + }; + setup({ + snapshot: value, + selectedPath: value.selectedSession!.path, + liveRunning: true, + }); + expect(screen.queryByRole("button", { name: i18n.t("stopTurn") })).toBeNull(); + expect(screen.getByRole("textbox").disabled).toBe(true); +}); diff --git a/tests/web/conversation-reading.e2e.ts b/tests/web/conversation-reading.e2e.ts new file mode 100644 index 00000000..699e1e6e --- /dev/null +++ b/tests/web/conversation-reading.e2e.ts @@ -0,0 +1,118 @@ +import { expect, test } from "@playwright/test"; +import type { WebSnapshot } from "../../web/protocol/types.ts"; + +test("conversation text stays readable and file links retain their exact target", async ({ + page, +}, testInfo) => { + const path = + "/workspace/packages/runtime-host/src/a-long-directory/session-history.ts"; + await page.route("**/events?**", (route) => + route.fulfill({ + contentType: "text/event-stream", + body: ": heartbeat\n\n", + }), + ); + await page.route("**/api/snapshot**", async (route) => { + const response = await route.fetch(); + const snapshot = (await response.json()) as WebSnapshot; + if (!snapshot.selectedSession) + throw new Error("Missing real Session fixture"); + snapshot.preferences = { ...snapshot.preferences, chatFontSize: 14 }; + snapshot.runtime = { + ...snapshot.runtime, + status: "idle", + activeTurn: undefined, + }; + snapshot.selectedSession.entries = [ + { + id: "reading-question", + timestamp: "2026-09-22T08:00:00Z", + type: "message", + message: { + role: "user", + content: + "帮我检查这次修改,列出结论和仍需验证的地方。保留执行过程,我还想向上查看之前的问题。", + }, + }, + { + id: "reading-tool", + timestamp: "2026-09-22T08:00:01Z", + type: "message", + message: { + role: "assistant", + content: "", + parts: [ + { + type: "toolCall", + id: "reading-call", + name: "read", + arguments: JSON.stringify({ path }), + }, + ], + }, + }, + { + id: "reading-result", + timestamp: "2026-09-22T08:00:02Z", + type: "message", + message: { + role: "toolResult", + toolCallId: "reading-call", + toolName: "read", + content: "Verified the current session path.", + isError: false, + }, + }, + { + id: "reading-answer", + timestamp: "2026-09-22T08:00:03Z", + type: "message", + message: { + role: "assistant", + stopReason: "stop", + content: [ + "已经完成检查,**历史消息按会话保存**,界面应保留阅读位置。下方是这次修改的具体结果。", + "## 检查结果", + "1. 向上加载更早的消息时,当前内容仍留在原来的位置。\n2. 切换会话后,迟到的结果不会进入另一段对话。\n3. `sessionId`、`sessionPath` 与原生记录共同确定读取目标。", + `相关实现:[session-history.ts](${path})。完整路径可以在链接提示中查看。`, + "这里是一段中英文混合内容:OpenPI keeps the current reading position. 段落、列表和行内代码应自然换行,避免长路径挤占正文。", + "```ts\nconst result = await loadEarlierMessages(sessionId);\nreturn result.entries;\n```", + ].join("\n\n"), + }, + }, + ]; + await route.fulfill({ response, json: snapshot }); + }); + for (const width of [1440, 390]) { + await page.setViewportSize({ width, height: 1000 }); + await page.goto("/"); + const conversation = page.locator(".conversation"); + await expect(page.getByText("检查结果", { exact: true })).toBeVisible(); + await conversation.evaluate((element) => { + element.scrollTo({ top: 0, behavior: "instant" }); + }); + await expect( + page.getByText( + "帮我检查这次修改,列出结论和仍需验证的地方。保留执行过程,我还想向上查看之前的问题。", + { exact: true }, + ), + ).toBeInViewport({ ratio: 1 }); + const link = page.getByRole("button", { + name: "session-history.ts", + exact: true, + }); + await expect(link).toHaveAttribute("title", `Open read-only file: ${path}`); + await expect(link.locator(".artifact-link-path")).toBeHidden(); + await expect(page.locator(".process-sequence")).toBeVisible(); + expect( + await conversation.evaluate( + (element) => element.scrollWidth <= element.clientWidth, + ), + ).toBe(true); + const response = page.locator(".message-row.response .markdown"); + await expect(response).toHaveCSS("font-size", "14px"); + await page.screenshot({ + path: testInfo.outputPath(`conversation-reading-${width}.png`), + }); + } +}); diff --git a/tests/web/embedded-browser-cleanup.test.ts b/tests/web/embedded-browser-cleanup.test.ts new file mode 100644 index 00000000..b05fd780 --- /dev/null +++ b/tests/web/embedded-browser-cleanup.test.ts @@ -0,0 +1,281 @@ +import assert from "node:assert/strict"; +import { spawn } from "node:child_process"; +import { once } from "node:events"; +import fs from "node:fs"; +import { + access, + chmod, + mkdir, + mkdtemp, + readFile, + writeFile, +} from "node:fs/promises"; +import { syncBuiltinESMExports } from "node:module"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { EmbeddedBrowserManager } from "../../web/host/embedded-browser.ts"; + +test("waits for a native browser process to close before removing its profile on every platform", { + timeout: 10_000, +}, async (context) => { + const directory = await mkdtemp(join(tmpdir(), "openpi-browser-close-")); + const profile = join(directory, "profile"); + await mkdir(profile); + const browser = spawn( + process.execPath, + [ + "-e", + "process.stderr.write('browser-ready'); setInterval(() => {}, 1_000);", + ], + { + stdio: ["ignore", "ignore", "pipe"], + }, + ); + let hasClosed = false; + const closed = new Promise((resolve) => + browser.once("close", () => { + hasClosed = true; + resolve(); + }), + ); + assert.ok(browser.stderr); + const ready = once(browser.stderr, "data"); + browser.stderr.resume(); + const manager = new EmbeddedBrowserManager(); + Object.assign(manager, { + session: { + sessionId: "fixture", + process: browser, + closed, + profile, + frameListeners: new Set(), + stopListening() {}, + cdp: { close() {} }, + }, + }); + const remove = fs.promises.rm; + const removal = context.mock.method( + fs.promises, + "rm", + async ( + path: Parameters[0], + options: Parameters[1], + ) => { + if (path === profile) + assert.equal( + hasClosed, + true, + "native close must precede profile removal", + ); + return remove(path, options); + }, + ); + syncBuiltinESMExports(); + try { + await ready; + await manager.dispose(); + assert.equal(hasClosed, true); + await assert.rejects(access(profile), { code: "ENOENT" }); + } finally { + removal.mock.restore(); + syncBuiltinESMExports(); + if (browser.exitCode === null && browser.signalCode === null) + browser.kill("SIGKILL"); + await closed; + await manager.dispose(); + await remove(directory, { recursive: true, force: true }); + } +}); + +test("waits for browser close and its last profile write before removing the profile", { + skip: + process.platform === "win32" + ? "This descendant fixture relies on POSIX inherited-pipe lifetime after parent exit; Windows stdio closes differently." + : false, + timeout: 10_000, +}, async (context) => { + const directory = await mkdtemp(join(tmpdir(), "openpi-browser-cleanup-")); + const profile = join(directory, "profile"); + const release = join(directory, "release"); + const marker = join(directory, "writer-result"); + await mkdir(profile); + const renderer = ` + const fs = require('node:fs'); + const path = require('node:path'); + const [profile, release, marker] = process.argv.slice(1); + process.stderr.write('renderer-ready'); + const timer = setInterval(() => { + if (!fs.existsSync(release)) return; + clearInterval(timer); + try { + fs.writeFileSync(path.join(profile, 'late-write'), 'renderer final write'); + fs.writeFileSync(marker, 'profile retained until renderer closed'); + } catch { + fs.writeFileSync(marker, 'profile removed too early'); + } + process.exit(0); + }, 5); + setTimeout(() => process.exit(2), 5_000).unref(); + `; + const browser = spawn( + process.execPath, + [ + "-e", + ` + require('node:child_process').spawn(process.execPath, ['-e', ${JSON.stringify(renderer)}, ...process.argv.slice(1)], { + stdio: ['ignore', 'ignore', 'inherit'], + }); + `, + profile, + release, + marker, + ], + { stdio: ["ignore", "ignore", "pipe"] }, + ); + let hasClosed = false; + const closed = new Promise((resolve) => + browser.once("close", () => { + hasClosed = true; + resolve(); + }), + ); + const exited = once(browser, "exit"); + assert.ok(browser.stderr); + const ready = once(browser.stderr, "data"); + browser.stderr.resume(); + const manager = new EmbeddedBrowserManager(); + // The public disposal path needs a live native process, not a running CDP + // server. Its renderer keeps stderr open after the browser process exits. + Object.assign(manager, { + session: { + sessionId: "fixture", + process: browser, + closed, + profile, + frameListeners: new Set(), + stopListening() {}, + cdp: { close() {} }, + }, + }); + const remove = fs.promises.rm; + const removal = context.mock.method( + fs.promises, + "rm", + async ( + path: Parameters[0], + options: Parameters[1], + ) => { + if (path === profile) + assert.equal( + hasClosed, + true, + "profile removal must wait for the browser close event", + ); + return remove(path, options); + }, + ); + syncBuiltinESMExports(); + try { + await ready; + const disposed = manager.dispose().then( + () => undefined, + (error: unknown) => error, + ); + await exited; + assert.equal( + hasClosed, + false, + "exit alone does not confirm inherited stderr has closed", + ); + await writeFile(release, "release renderer"); + await closed; + assert.equal(await disposed, undefined); + assert.equal( + await readFile(marker, "utf8"), + "profile retained until renderer closed", + ); + await assert.rejects(access(profile), { code: "ENOENT" }); + } finally { + removal.mock.restore(); + syncBuiltinESMExports(); + await writeFile(release, "release renderer"); + if (browser.exitCode === null && browser.signalCode === null) + browser.kill("SIGKILL"); + await closed; + await manager.dispose(); + await remove(directory, { recursive: true, force: true }); + } +}); + +test("preserves the original startup failure when profile cleanup also fails", { + skip: process.platform === "win32", + timeout: 10_000, +}, async (context) => { + const directory = await mkdtemp( + join(tmpdir(), "openpi-browser-cleanup-error-"), + ); + const executable = join(directory, "browser"); + const profileRecord = join(directory, "profile"); + const configured = process.env.OPENPI_CHROME_PATH; + const manager = new EmbeddedBrowserManager(); + const remove = fs.promises.rm; + let profile: string | undefined; + context.mock.method(console, "error", () => {}); + const removal = context.mock.method( + fs.promises, + "rm", + async ( + path: Parameters[0], + options: Parameters[1], + ) => { + profile ??= await readFile(profileRecord, "utf8").catch(() => undefined); + if (path === profile) + throw Object.assign(new Error("fixture cleanup denied"), { + code: "EACCES", + }); + return remove(path, options); + }, + ); + syncBuiltinESMExports(); + try { + await writeFile( + executable, + `#!/bin/sh +for argument do + case "$argument" in --user-data-dir=*) printf '%s' "${"${argument#*=}"}" > '${profileRecord}' ;; esac +done +exit 7 +`, + ); + await chmod(executable, 0o700); + process.env.OPENPI_CHROME_PATH = executable; + await assert.rejects( + manager.open("fixture", "http://127.0.0.1/"), + (error: unknown) => { + assert.ok(error instanceof Error); + assert.equal( + error.message, + "Embedded browser exited during startup (exit 7)", + ); + assert.ok(error.cause && typeof error.cause === "object"); + const cause = error.cause as Record; + assert.equal(cause.failure, "exit"); + assert.equal(cause.exitCode, 7); + assert.ok( + cause.cleanupError, + "the secondary cleanup failure must remain available to operators", + ); + return true; + }, + ); + } finally { + removal.mock.restore(); + syncBuiltinESMExports(); + if (configured === undefined) delete process.env.OPENPI_CHROME_PATH; + else process.env.OPENPI_CHROME_PATH = configured; + await manager.dispose(); + if (profile) await remove(profile, { recursive: true, force: true }); + await remove(directory, { recursive: true, force: true }); + } +}); diff --git a/tests/web/embedded-browser-startup.test.ts b/tests/web/embedded-browser-startup.test.ts new file mode 100644 index 00000000..60b55c4d --- /dev/null +++ b/tests/web/embedded-browser-startup.test.ts @@ -0,0 +1,220 @@ +import assert from "node:assert/strict"; +import { + access, + chmod, + mkdtemp, + readFile, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { EmbeddedBrowserManager } from "../../web/host/embedded-browser.ts"; + +function startupDiagnostics(error: unknown) { + assert.ok(error instanceof Error); + assert.ok(error.cause && typeof error.cause === "object"); + return error.cause as Record; +} + +test("preserves the startup failure and profile cleanup even when host logging throws", { + skip: process.platform === "win32", + timeout: 12_000, +}, async (context) => { + const directory = await mkdtemp( + join(tmpdir(), "openpi-browser-log-failure-"), + ); + const executable = join(directory, "browser"); + const profileRecord = join(directory, "profile"); + const configured = process.env.OPENPI_CHROME_PATH; + const manager = new EmbeddedBrowserManager(); + let attempts = 0; + context.mock.method(console, "error", () => { + attempts++; + throw new Error("fixture logging failed"); + }); + try { + await writeFile( + executable, + `#!/bin/sh +for argument do + case "$argument" in --user-data-dir=*) printf '%s' "${"${argument#*=}"}" > '${profileRecord}' ;; esac +done +printf 'fixture browser exit reason\\n' >&2 +exit 7 +`, + ); + await chmod(executable, 0o700); + process.env.OPENPI_CHROME_PATH = executable; + await assert.rejects( + manager.open("fixture", "http://127.0.0.1/"), + (error: unknown) => { + assert.ok(error instanceof Error); + assert.equal( + error.message, + "Embedded browser exited during startup (exit 7)", + ); + assert.equal(startupDiagnostics(error).failure, "exit"); + return true; + }, + ); + assert.equal(attempts, 1); + await assert.rejects(access(await readFile(profileRecord, "utf8")), { + code: "ENOENT", + }); + assert.equal(await manager.state("fixture"), undefined); + } finally { + if (configured === undefined) delete process.env.OPENPI_CHROME_PATH; + else process.env.OPENPI_CHROME_PATH = configured; + await manager.dispose(); + await rm(directory, { recursive: true, force: true }); + } +}); + +test("reports a browser killed during startup instead of waiting for the port timeout", { + skip: process.platform === "win32", + timeout: 12_000, +}, async (context) => { + const logged: unknown[] = []; + context.mock.method(console, "error", (_message: unknown, cause: unknown) => { + logged.push(cause); + }); + const directory = await mkdtemp(join(tmpdir(), "openpi-browser-startup-")); + const executable = join(directory, "browser"); + const configured = process.env.OPENPI_CHROME_PATH; + const manager = new EmbeddedBrowserManager(); + try { + await writeFile( + executable, + "#!/bin/sh\nprintf 'fixture browser terminated during initialization\\n' >&2\nkill -TERM $$\n", + ); + await chmod(executable, 0o700); + process.env.OPENPI_CHROME_PATH = executable; + const started = performance.now(); + await assert.rejects( + manager.open("fixture", "http://127.0.0.1/"), + (error: unknown) => { + const cause = startupDiagnostics(error); + assert.equal(cause.stage, "debug-port"); + assert.equal(cause.failure, "exit"); + assert.equal(cause.signal, "SIGTERM"); + assert.ok(typeof cause.stderr === "string"); + assert.match( + cause.stderr, + /fixture browser terminated during initialization/, + ); + assert.ok(error instanceof Error); + assert.match(error.message, /SIGTERM/); + assert.doesNotMatch(error.message, /fixture browser terminated/); + assert.deepEqual(logged, [cause]); + return true; + }, + ); + assert.ok( + performance.now() - started < 2_000, + "process death must not wait for the eight-second startup deadline", + ); + } finally { + if (configured === undefined) delete process.env.OPENPI_CHROME_PATH; + else process.env.OPENPI_CHROME_PATH = configured; + await manager.dispose(); + await rm(directory, { recursive: true, force: true }); + } +}); + +test("rejects a non-executable browser without an unhandled child-process error", { + skip: process.platform === "win32", + timeout: 12_000, +}, async (context) => { + const logged: unknown[] = []; + context.mock.method(console, "error", (_message: unknown, cause: unknown) => { + logged.push(cause); + }); + const directory = await mkdtemp(join(tmpdir(), "openpi-browser-permission-")); + const executable = join(directory, "browser"); + const configured = process.env.OPENPI_CHROME_PATH; + const manager = new EmbeddedBrowserManager(); + try { + await writeFile(executable, "#!/bin/sh\nexit 0\n", { mode: 0o600 }); + process.env.OPENPI_CHROME_PATH = executable; + await assert.rejects( + manager.open("fixture", "http://127.0.0.1/"), + (error: unknown) => { + const cause = startupDiagnostics(error); + assert.equal(cause.failure, "spawn"); + assert.equal(cause.spawnErrorCode, "EACCES"); + assert.ok(error instanceof Error); + assert.match(error.message, /could not start.*EACCES/); + assert.deepEqual(logged, [cause]); + return true; + }, + ); + assert.equal(await manager.state("fixture"), undefined); + } finally { + if (configured === undefined) delete process.env.OPENPI_CHROME_PATH; + else process.env.OPENPI_CHROME_PATH = configured; + await manager.dispose(); + await rm(directory, { recursive: true, force: true }); + } +}); + +test("retains a bounded startup stderr tail and port errno without exposing them in the timeout message", { + skip: process.platform === "win32", + timeout: 12_000, +}, async (context) => { + const directory = await mkdtemp(join(tmpdir(), "openpi-browser-timeout-")); + const executable = join(directory, "browser"); + const configured = process.env.OPENPI_CHROME_PATH; + const manager = new EmbeddedBrowserManager(); + const logged: unknown[] = []; + context.mock.method(console, "error", (_message: unknown, cause: unknown) => { + logged.push(cause); + }); + try { + await writeFile( + executable, + `#!/bin/sh +for argument do + case "$argument" in --user-data-dir=*) fixture_profile="${"${argument#*=}"}" ;; esac +done +mkdir "$fixture_profile/DevToolsActivePort" +printf '%s' 'discarded-prefix:${"界".repeat(2_000)}:retained-tail' >&2 +printf ' fixture_profile=%s\\n' "$fixture_profile" >&2 +exec /bin/sleep 30 +`, + ); + await chmod(executable, 0o700); + process.env.OPENPI_CHROME_PATH = executable; + await assert.rejects( + manager.open("fixture", "http://127.0.0.1/"), + (error: unknown) => { + const cause = startupDiagnostics(error); + assert.equal(cause.stage, "debug-port"); + assert.equal(cause.failure, "timeout"); + assert.equal(cause.portReadErrorCode, "EISDIR"); + assert.equal(cause.exitCode, null); + assert.equal(cause.signal, null); + assert.ok( + typeof cause.elapsedMs === "number" && + cause.elapsedMs >= 8_000 && + cause.elapsedMs < 10_000, + ); + assert.ok(typeof cause.stderr === "string"); + assert.ok(Buffer.byteLength(cause.stderr, "utf8") <= 4 * 1024); + assert.match(cause.stderr, /:retained-tail/); + assert.doesNotMatch(cause.stderr, /discarded-prefix/); + assert.match(cause.stderr, /fixture_profile=/); + assert.ok(error instanceof Error); + assert.equal(error.message, "Embedded browser startup timed out"); + assert.deepEqual(logged, [cause]); + return true; + }, + ); + } finally { + if (configured === undefined) delete process.env.OPENPI_CHROME_PATH; + else process.env.OPENPI_CHROME_PATH = configured; + await manager.dispose(); + await rm(directory, { recursive: true, force: true }); + } +}); diff --git a/tests/web/full-message-ui.spec.tsx b/tests/web/full-message-ui.spec.tsx new file mode 100644 index 00000000..0e48b332 --- /dev/null +++ b/tests/web/full-message-ui.spec.tsx @@ -0,0 +1,111 @@ +// @vitest-environment jsdom +import { act, cleanup, fireEvent, render, screen, within } from "@testing-library/react"; +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import type { WebSnapshot } from "../../web/protocol/types.ts"; +import { FullMessageText } from "../../web/ui/src/features/transcript/FullMessageText.tsx"; +import { Transcript } from "../../web/ui/src/features/transcript/Transcript.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; +import { copyText } from "../../web/ui/src/lib/clipboard.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; + +vi.mock("../../web/ui/src/lib/clipboard.ts", () => ({ copyText: vi.fn(async () => true) })); + +afterEach(() => { cleanup(); vi.restoreAllMocks(); vi.clearAllMocks(); }); + +function withI18n(element: ReturnType) { + return createElement(I18nextProvider, { i18n }, element); +} + +it("loads exact native message text in explicit pages and replaces the preview", async () => { + const read = vi.spyOn(WebClient.prototype, "sessionItem") + .mockResolvedValueOnce({ entryId: "answer", text: "Full first part ", nextCursor: 16, totalChars: 27 }) + .mockResolvedValueOnce({ entryId: "answer", text: "and second", nextCursor: null, totalChars: 27 }); + const { container } = render(withI18n(createElement(FullMessageText, { + preview: "Truncated preview", sessionId: "session", sessionPath: "/tmp/session", entryId: "answer", markdown: true, + }))); + expect(container.textContent).toContain("Truncated preview"); + await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Load full message" })); }); + expect(container.textContent).toContain("Full first part"); + expect(container.textContent).not.toContain("Truncated preview"); + await act(async () => { fireEvent.click(screen.getByRole("button", { name: "Load more" })); }); + expect(container.textContent).toContain("Full first part and second"); + expect(read).toHaveBeenNthCalledWith(1, "session", "/tmp/session", "answer", 0, expect.any(AbortSignal)); + expect(read).toHaveBeenNthCalledWith(2, "session", "/tmp/session", "answer", 16, expect.any(AbortSignal)); + expect(screen.queryByRole("button", { name: "Load more" })).toBeNull(); +}); + +it("aborts a late page when the native message identity changes", async () => { + let finish!: (page: { entryId: string; text: string; nextCursor: null; totalChars: number }) => void; + const read = vi.spyOn(WebClient.prototype, "sessionItem").mockImplementation(() => new Promise((resolve) => { finish = resolve; })); + const component = (entryId: string) => withI18n(createElement(FullMessageText, { + key: entryId, preview: `Preview ${entryId}`, sessionId: "session", sessionPath: "/tmp/session", entryId, markdown: false, + })); + const { rerender, container } = render(component("first")); + fireEvent.click(screen.getByRole("button", { name: "Load full message" })); + const signal = read.mock.calls[0]![4]; + rerender(component("second")); + expect(signal.aborted).toBe(true); + await act(async () => finish({ entryId: "first", text: "Stale", nextCursor: null, totalChars: 5 })); + expect(container.textContent).toContain("Preview second"); + expect(container.textContent).not.toContain("Stale"); +}); + +it("offers recovery only for clipped visible text, not bounded tool evidence", () => { + const truncation = { truncated: true, entriesOmitted: 0, messagesTruncated: 2, messagePartsOmitted: 0, maxBytes: 2 * 1024 * 1024 }; + const snapshot: WebSnapshot = { + protocolVersion: 1, preferences: { theme: "system" }, generatedAt: "2026-09-23T00:00:00Z", cursor: 1, + currentSessionId: "session", currentSessionPath: "/tmp/session", workspaces: [], sessions: [], models: [], + runtime: { status: "idle", capabilities: {} }, + truncation: { truncated: false, sessionsOmitted: 0, workspacesOmitted: 0, modelsOmitted: 0, maxBytes: 4 * 1024 * 1024, bytes: 0 }, + selectedSession: { id: "session", path: "/tmp/session", cwd: "/tmp", bytes: 1, truncation, entries: [ + { id: "prompt", type: "message", timestamp: "2026-09-23T00:00:00Z", message: { role: "user", content: "Question", truncation: { truncated: true, text: true } } }, + { id: "result", type: "message", timestamp: "2026-09-23T00:00:01Z", message: { role: "toolResult", content: "Bounded tool output", toolName: "bash", truncation: { truncated: true, text: true } } }, + { id: "answer", type: "message", timestamp: "2026-09-23T00:00:02Z", message: { role: "assistant", content: "Answer preview", truncation: { truncated: true, text: true, visibleText: true } } }, + ] }, + }; + render(withI18n(createElement(Transcript, { + snapshot, liveMessages: [], liveRunning: false, livePhase: "idle", liveRetry: null, + thinkingStarts: {}, thinkingDurations: {}, scrollToBottom: 0, onResend: async () => true, + }))); + expect(screen.getAllByRole("button", { name: "Load full message" })).toHaveLength(1); + expect(screen.queryByText("Some message contents exceed the preview limit and are truncated.")).toBeNull(); + expect(screen.queryByText("Beginning of conversation")).toBeNull(); +}); + +it("uses the complete native text for copy and edit only after recovery finishes", async () => { + const read = vi.spyOn(WebClient.prototype, "sessionItem").mockResolvedValue({ + entryId: "prompt", text: "The complete question after recovery", nextCursor: null, totalChars: 36, + }); + const snapshot: WebSnapshot = { + protocolVersion: 1, preferences: { theme: "system" }, generatedAt: "2026-09-23T00:00:00Z", cursor: 1, + currentSessionId: "session", currentSessionPath: "/tmp/session", workspaces: [], sessions: [], models: [], + runtime: { status: "idle", capabilities: {} }, + truncation: { truncated: false, sessionsOmitted: 0, workspacesOmitted: 0, modelsOmitted: 0, maxBytes: 4 * 1024 * 1024, bytes: 0 }, + selectedSession: { id: "session", path: "/tmp/session", cwd: "/tmp", bytes: 1, + truncation: { truncated: true, entriesOmitted: 0, messagesTruncated: 1, messagePartsOmitted: 0, maxBytes: 2 * 1024 * 1024 }, + entries: [{ id: "prompt", type: "message", timestamp: "2026-09-23T00:00:00Z", message: { role: "user", content: "Question preview", truncation: { truncated: true, visibleText: true } } }], + }, + }; + const { container, rerender } = render(withI18n(createElement(Transcript, { + snapshot, liveMessages: [], liveRunning: false, livePhase: "idle", liveRetry: null, + thinkingStarts: {}, thinkingDurations: {}, scrollToBottom: 0, onResend: async () => true, + }))); + const question = container.querySelector(".message-row.user")!; + expect(within(question).getByRole("button", { name: "Copy message" }).disabled).toBe(true); + expect(within(question).queryByRole("button", { name: "Edit message" })).toBeNull(); + await act(async () => fireEvent.click(within(question).getByRole("button", { name: "Load full message" }))); + expect(read).toHaveBeenCalledOnce(); + expect(within(question).getByRole("button", { name: "Copy message" }).disabled).toBe(false); + await act(async () => fireEvent.click(within(question).getByRole("button", { name: "Copy message" }))); + expect(copyText).toHaveBeenCalledWith("The complete question after recovery"); + fireEvent.click(within(question).getByRole("button", { name: "Edit message" })); + expect(within(question).getByRole("textbox", { name: "Edit message" }).value).toBe("The complete question after recovery"); + + rerender(withI18n(createElement(Transcript, { + snapshot: { ...snapshot, cursor: 2 }, liveMessages: [], liveRunning: false, livePhase: "idle", liveRetry: null, + thinkingStarts: {}, thinkingDurations: {}, scrollToBottom: 0, onResend: async () => true, + }))); + expect(within(question).getByRole("textbox", { name: "Edit message" }).value).toBe("The complete question after recovery"); +}); diff --git a/tests/web/generated-files.test.ts b/tests/web/generated-files.test.ts index 1a416038..0fbd75fb 100644 --- a/tests/web/generated-files.test.ts +++ b/tests/web/generated-files.test.ts @@ -78,3 +78,23 @@ test("generated files deduplicate by resolved path and keep the latest result", }, ]); }); + +test("generated files retain only the successful tool's recorded edit", () => { + const edit = { + ...result("edit", false), + details: { diff: "-before\n+after" }, + }; + const [file] = generatedFiles([ + call("edit", "edit", "/other/repo/file.ts"), + edit, + ]); + assert.equal(file?.diff, "-before\n+after"); + assert.equal(file?.diffTruncated, false); + assert.deepEqual( + generatedFiles([ + call("edit", "edit", "/other/repo/file.ts"), + { ...edit, isError: true }, + ]), + [], + ); +}); diff --git a/tests/web/git-review-host.test.ts b/tests/web/git-review-host.test.ts index b87b3233..e73fc0ab 100644 --- a/tests/web/git-review-host.test.ts +++ b/tests/web/git-review-host.test.ts @@ -62,6 +62,7 @@ test("Git review HTTP access authenticates and binds the exact Session", async ( const query = new URLSearchParams({ sessionId: session.id, path: session.path, + source: "session", }); assert.equal( diff --git a/tests/web/git-review-lifecycle.spec.ts b/tests/web/git-review-lifecycle.spec.ts new file mode 100644 index 00000000..291b21c9 --- /dev/null +++ b/tests/web/git-review-lifecycle.spec.ts @@ -0,0 +1,186 @@ +// @vitest-environment jsdom + +import { act, cleanup, fireEvent, renderHook } from "@testing-library/react"; +import { afterEach, expect, it, vi } from "vitest"; +import type { + WebGitReviewResult, + WebSessionProjection, +} from "../../web/protocol/types.ts"; +import { useGitReview } from "../../web/ui/src/features/review/use-git-review.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; + +afterEach(() => { + cleanup(); + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + +function session(id: string): WebSessionProjection { + return { + id, + path: `/sessions/${id}.jsonl`, + cwd: "/workspace", + entries: [], + bytes: 0, + truncation: { + truncated: false, + maxBytes: 1024, + entriesOmitted: 0, + messagesTruncated: 0, + messagePartsOmitted: 0, + }, + }; +} +const oldResult: WebGitReviewResult = { + ok: true, + snapshot: { + repositoryRoot: "/workspace", + revision: "old", + comparison: "session", + currentBranch: "main", + baseBranch: null, + additions: 1, + deletions: 0, + truncated: false, + files: [ + { + path: "old-session.txt", + status: "untracked", + additions: 1, + deletions: 0, + diff: "+old", + diffTruncated: false, + }, + ], + }, +}; + +it("hides the previous session's files immediately and on a new-session error", async () => { + vi.useFakeTimers(); + vi.spyOn(WebClient.prototype, "gitReview") + .mockResolvedValueOnce(oldResult) + .mockRejectedValueOnce(new Error("new session unavailable")); + const { result, rerender } = renderHook( + ({ selected }) => useGitReview(selected, 1), + { initialProps: { selected: session("a") } }, + ); + await act(() => vi.advanceTimersByTimeAsync(200)); + expect(result.current.result).toEqual(oldResult); + rerender({ selected: session("b") }); + expect(result.current.result).toBeNull(); + await act(() => vi.advanceTimersByTimeAsync(200)); + expect(result.current.error).toBe("new session unavailable"); + expect(result.current.result).toBeNull(); +}); + +it("ignores a previous-session response during the next session's debounce window", async () => { + vi.useFakeTimers(); + let finish!: (value: WebGitReviewResult) => void; + vi.spyOn(WebClient.prototype, "gitReview").mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const { result, rerender } = renderHook( + ({ selected }) => useGitReview(selected, 1), + { initialProps: { selected: session("a") } }, + ); + await act(() => vi.advanceTimersByTimeAsync(200)); + rerender({ selected: session("b") }); + await act(async () => finish(oldResult)); + expect(result.current.result).toBeNull(); + expect(result.current.error).toBeNull(); +}); + +it("does not scan hidden Git changes for every running-turn update and refreshes when shown", async () => { + vi.useFakeTimers(); + const read = vi + .spyOn(WebClient.prototype, "gitReview") + .mockResolvedValue(oldResult); + const { rerender } = renderHook( + ({ key, active }) => + useGitReview(session("a"), key, { active, running: true }), + { initialProps: { key: 1, active: false } }, + ); + for (let key = 2; key < 20; key++) { + rerender({ key, active: false }); + await act(() => vi.advanceTimersByTimeAsync(300)); + } + expect(read).not.toHaveBeenCalled(); + rerender({ key: 20, active: true }); + await act(() => vi.advanceTimersByTimeAsync(200)); + expect(read).toHaveBeenCalledTimes(1); +}); + +it("does not scan hidden Git changes when a running turn regains window focus", async () => { + vi.useFakeTimers(); + const read = vi + .spyOn(WebClient.prototype, "gitReview") + .mockResolvedValue(oldResult); + const { result } = renderHook(() => + useGitReview(session("a"), 1, { active: false, running: true }), + ); + await act(() => vi.advanceTimersByTimeAsync(300)); + await act(async () => { + fireEvent(window, new Event("focus")); + fireEvent(document, new Event("visibilitychange")); + }); + await act(() => vi.advanceTimersByTimeAsync(5_000)); + expect(read).not.toHaveBeenCalled(); + await act(() => result.current.refresh()); + expect(read).toHaveBeenCalledTimes(1); +}); + +it("promotes a deferred background refresh when the Git panel becomes visible", async () => { + vi.useFakeTimers(); + const read = vi + .spyOn(WebClient.prototype, "gitReview") + .mockResolvedValue(oldResult); + const { rerender } = renderHook( + ({ key, active }) => + useGitReview(session("a"), key, { active, running: false }), + { initialProps: { key: 1, active: false } }, + ); + await act(() => vi.advanceTimersByTimeAsync(200)); + expect(read).toHaveBeenCalledTimes(1); + rerender({ key: 2, active: false }); + await act(() => vi.advanceTimersByTimeAsync(100)); + rerender({ key: 2, active: true }); + await act(() => vi.advanceTimersByTimeAsync(200)); + expect(read).toHaveBeenCalledTimes(2); + await act(() => vi.advanceTimersByTimeAsync(2_000)); + expect(read).toHaveBeenCalledTimes(2); +}); + +it("lets a slow Git result settle and coalesces repeated refreshes into one follow-up", async () => { + vi.useFakeTimers(); + let finish!: (value: WebGitReviewResult) => void; + const read = vi + .spyOn(WebClient.prototype, "gitReview") + .mockImplementationOnce( + () => + new Promise((resolve) => { + finish = resolve; + }), + ) + .mockResolvedValue(oldResult); + const { result, rerender } = renderHook( + ({ key }) => + useGitReview(session("a"), key, { active: true, running: true }), + { initialProps: { key: 1 } }, + ); + await act(() => vi.advanceTimersByTimeAsync(200)); + const signal = read.mock.calls[0]![2]; + for (let key = 2; key < 20; key++) { + rerender({ key }); + await act(() => vi.advanceTimersByTimeAsync(100)); + } + expect(read).toHaveBeenCalledTimes(1); + expect(signal?.aborted).toBe(false); + await act(async () => finish(oldResult)); + expect(result.current.result).toEqual(oldResult); + await act(() => vi.advanceTimersByTimeAsync(5_000)); + expect(read).toHaveBeenCalledTimes(2); + expect(signal?.aborted).toBe(false); +}); diff --git a/tests/web/git-review.e2e.ts b/tests/web/git-review.e2e.ts new file mode 100644 index 00000000..c3a894ff --- /dev/null +++ b/tests/web/git-review.e2e.ts @@ -0,0 +1,118 @@ +import { execFile, execFileSync } from "node:child_process"; +import { lstat, mkdtemp, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { promisify } from "node:util"; +import { SessionManager } from "@earendil-works/pi-coding-agent"; +import { expect, test } from "@playwright/test"; +import { GitReviewBaselineStore } from "../../web/host/git-review.ts"; +import { WebHost } from "../../web/host/web-host.ts"; +import { projectWebModelSearch } from "../../web/runtime/model-discovery.ts"; +import type { WebRuntimeController } from "../../web/runtime/types.ts"; + +const exec = promisify(execFile); + +test("real Git rename details and index-only edits refresh the open browser diff", async ({ + browser, +}, testInfo) => { + const root = await mkdtemp(join(tmpdir(), "openpi-git-browser-")); + const git = (...args: string[]) => exec("git", ["-C", root, ...args]); + await git("init", "-b", "main"); + await git("config", "user.name", "OpenPI Fixture"); + await git("config", "user.email", "fixture@example.invalid"); + await writeFile(join(root, "old.txt"), "rename source\n"); + await writeFile(join(root, "index.txt"), "base\n"); + await git("add", "."); + await git("commit", "-m", "fixture"); + await git("mv", "old.txt", "new.txt"); + const updateIndex = async (content: string) => { + const oid = execFileSync( + "git", + ["-C", root, "hash-object", "-w", "--stdin"], + { input: content, encoding: "utf8" }, + ).trim(); + await git("update-index", "--cacheinfo", "100644", oid, "index.txt"); + }; + await updateIndex("staged-a\n"); + const originalStat = await lstat(join(root, "index.txt"), { bigint: true }); + const manager = SessionManager.inMemory(root); + const runtime: WebRuntimeController = { + cwd: root, + workspaceSelected: true, + sessionDirectory: join(root, ".git", "sessions"), + sessionManager: manager, + isIdle: () => true, + getActiveTurn: () => undefined, + listModels: () => [], + searchModels: (query, limit) => projectWebModelSearch([], query, limit), + setModel: async () => { + throw new Error("unused"); + }, + newSession: async () => ({ + cancelled: true, + sessionId: manager.getSessionId(), + }), + switchSession: async () => ({ cancelled: true }), + cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), + subscribe: () => () => {}, + dispose: async () => {}, + sendPrompt: async () => { + throw new Error("Review must not call a model"); + }, + }; + const host = new WebHost({ + runtime, + gitReviews: new GitReviewBaselineStore( + runtime.sessionDirectory, + join(root, ".git", "baselines"), + ), + }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + locale: "zh-CN", + }); + const page = await context.newPage(); + try { + await host.start(); + await page.goto(host.origin); + await page.locator(".task-tools-trigger").click(); + const workbar = page.locator(".workbar-panel"); + await workbar.getByRole("button", { name: /^变更/u }).click(); + await workbar + .getByRole("combobox", { name: "变更范围" }) + .selectOption("staged"); + await workbar.getByRole("button", { name: "new.txt", exact: true }).click(); + const diff = workbar.getByRole("figure"); + await expect(diff).toContainText("rename from old.txt"); + await expect(diff).toContainText("rename to new.txt"); + await page.screenshot({ path: testInfo.outputPath("native-rename.png") }); + await workbar.getByRole("button", { name: "返回变更文件" }).click(); + await workbar + .getByRole("button", { name: "index.txt", exact: true }) + .click(); + await expect(diff).toContainText("staged-a"); + const draft = page.getByRole("textbox", { name: "描述任务" }); + await draft.fill("保留正在编辑的草稿"); + await updateIndex("staged-b\n"); + const nextStat = await lstat(join(root, "index.txt"), { bigint: true }); + expect([nextStat.size, nextStat.mtimeNs, nextStat.ctimeNs]).toEqual([ + originalStat.size, + originalStat.mtimeNs, + originalStat.ctimeNs, + ]); + // Returning from an external Git operation triggers the existing refresh + // path; keep the file open and the input focused during reconciliation. + await page.evaluate(() => window.dispatchEvent(new Event("focus"))); + await expect(diff).toContainText("staged-b", { timeout: 10_000 }); + await expect(diff).not.toContainText("staged-a"); + await expect(draft).toBeFocused(); + await expect(draft).toHaveValue("保留正在编辑的草稿"); + await page.screenshot({ + path: testInfo.outputPath("native-index-refresh.png"), + }); + } finally { + await context.close(); + await host.stop(); + await rm(root, { recursive: true, force: true }); + } +}); diff --git a/tests/web/git-review.test.ts b/tests/web/git-review.test.ts index 16abe949..9678b101 100644 --- a/tests/web/git-review.test.ts +++ b/tests/web/git-review.test.ts @@ -1,13 +1,23 @@ import assert from "node:assert/strict"; -import { execFile } from "node:child_process"; +import { execFile, execFileSync } from "node:child_process"; import { randomBytes } from "node:crypto"; -import { mkdtemp, readdir, rm, unlink, writeFile } from "node:fs/promises"; +import { + lstat, + mkdir, + mkdtemp, + readFile, + readdir, + rm, + unlink, + writeFile, +} from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { after, test } from "node:test"; import { promisify } from "node:util"; import { countGitDiffLines, + captureGitTurnChanges, GitReviewBaselineStore, readGitReview, } from "../../web/host/git-review.ts"; @@ -114,6 +124,73 @@ test("Git review baseline reports only changes made after a Session starts", asy ); }); +test("turn capture excludes preexisting dirty work, records edits, and distinguishes no changes", async () => { + const root = await repository(); + await writeFile(join(root, "base.txt"), "base\npreexisting\n", "utf8"); + await writeFile(join(root, "already-untracked.txt"), "before\n", "utf8"); + await Promise.all( + Array.from({ length: 205 }, (_, index) => + writeFile(join(root, `preexisting-${index}.txt`), "before\n", "utf8"), + ), + ); + const first = await captureGitTurnChanges("current:test-turn-1", root); + try { + const initial = await first.read(); + assert.equal(initial.ok, true); + if (initial.ok) assert.deepEqual(initial.snapshot.files, []); + await writeFile(join(root, "base.txt"), "base\nchanged\n", "utf8"); + await writeFile(join(root, "already-untracked.txt"), "after\n", "utf8"); + await writeFile(join(root, "new.txt"), "new\n", "utf8"); + const changed = await first.read(); + assert.equal(changed.ok, true); + if (changed.ok) { + assert.deepEqual(changed.snapshot.files.map((file) => file.path).sort(), [ + "already-untracked.txt", + "base.txt", + "new.txt", + ]); + assert.equal(changed.snapshot.additions, 3); + assert.equal(changed.snapshot.deletions, 2); + } + } finally { + await first.dispose(); + } + const second = await captureGitTurnChanges("current:test-turn-2", root); + try { + const unchanged = await second.read(); + assert.equal(unchanged.ok, true); + if (unchanged.ok) assert.deepEqual(unchanged.snapshot.files, []); + } finally { + await second.dispose(); + } +}); + +test("turn capture fails closed if the workspace switches Git repositories", async () => { + const root = await repository(); + const nested = join(root, "nested"); + await mkdir(nested); + const capture = await captureGitTurnChanges("current:nested-turn", nested); + try { + await git(nested, "init", "-b", "main"); + const result = await capture.read(); + assert.deepEqual(result, { ok: false, reason: "git_failed" }); + } finally { + await capture.dispose(); + } +}); + +test("turn capture does not recapture after its dirty-worktree budget is exceeded", async () => { + const root = await repository(); + await writeFile( + join(root, "oversized.bin"), + Buffer.alloc(5 * 1024 * 1024, 7), + ); + await assert.rejects( + captureGitTurnChanges("current:oversized-turn", root, 4 * 1024 * 1024), + /baseline unavailable/iu, + ); +}); + test("Git review bounds repeated Session baselines with large dirty files", async () => { const root = await repository(); const baselineDirectory = await mkdtemp( @@ -156,7 +233,7 @@ test("Git review bounds repeated Session baselines with large dirty files", asyn assert.equal((await store.read(firstSession, root)).ok, true); assert.deepEqual(await store.read(secondSession, root), { ok: false, - reason: "git_failed", + reason: "baseline_unavailable", }); }); @@ -192,6 +269,195 @@ test("Git review reports a non-repository instead of an empty snapshot", async ( }); }); +test("native Git views remain usable with an oversized untracked file and load diffs on demand", async () => { + const root = await repository(); + await writeFile(join(root, "large.bin"), Buffer.alloc(8 * 1024 * 1024)); + await writeFile(join(root, "small.txt"), "small change\n"); + const overview = await readGitReview(root, { + source: "unstaged", + summary: true, + }); + assert.equal(overview.ok, true); + if (!overview.ok) return; + assert.equal(overview.snapshot.comparison, "unstaged"); + assert.equal( + overview.snapshot.files.find((file) => file.path === "large.bin") + ?.diffLoaded, + false, + ); + assert.ok(overview.snapshot.files.every((file) => file.diff === "")); + const detail = await readGitReview(root, { + source: "unstaged", + filePath: "small.txt", + }); + assert.equal(detail.ok, true); + if (!detail.ok) return; + assert.deepEqual( + detail.snapshot.files.map((file) => file.path), + ["small.txt"], + ); + assert.match(detail.snapshot.files[0]!.diff, /small change/u); + const large = await readGitReview(root, { + source: "unstaged", + filePath: "large.bin", + }); + assert.equal(large.ok, true); + if (large.ok) assert.equal(large.snapshot.files[0]?.diffTruncated, true); + const staged = await readGitReview(root, { source: "staged", summary: true }); + assert.equal(staged.ok, true); + if (staged.ok) assert.equal(staged.snapshot.files.length, 0); + await git(root, "add", "small.txt"); + const stagedFile = await readGitReview(root, { + source: "staged", + filePath: "small.txt", + }); + assert.equal(stagedFile.ok, true); + if (stagedFile.ok) + assert.match(stagedFile.snapshot.files[0]!.diff, /small change/u); +}); + +for (const changed of [false, true]) { + test(`staged rename detail retains both literal paths (edited: ${changed})`, async () => { + const root = await repository(); + const oldPath = "old [1].txt"; + const newPath = "new [1].txt"; + const original = + Array.from({ length: 20 }, (_, index) => `line ${index}`).join("\n") + + "\n"; + await writeFile(join(root, oldPath), original); + await git(root, "add", "--", oldPath); + await git(root, "commit", "-m", "rename source"); + await git(root, "mv", "--", oldPath, newPath); + if (changed) + await writeFile( + join(root, newPath), + original.replace("line 10\n", "edited line\n"), + ); + await writeFile( + join(root, "new 1.txt"), + "must not enter selected detail\n", + ); + await git(root, "add", "-A"); + const summary = await readGitReview(root, { + source: "staged", + summary: true, + }); + assert.ok(summary.ok); + const metadata = summary.snapshot.files.find( + (file) => file.path === newPath, + )!; + assert.equal(metadata.status, "renamed"); + const detail = await readGitReview(root, { + source: "staged", + filePath: newPath, + }); + assert.ok(detail.ok); + assert.equal(detail.snapshot.files.length, 1); + const file = detail.snapshot.files[0]!; + assert.equal(file.status, "renamed"); + assert.equal(file.previousPath, oldPath); + assert.equal(file.path, newPath); + assert.equal(file.additions, changed ? 1 : 0); + assert.equal(file.deletions, changed ? 1 : 0); + assert.match(file.diff, /rename from/u); + assert.match(file.diff, /rename to/u); + assert.doesNotMatch( + file.diff, + /\/dev\/null|must not enter selected detail/u, + ); + }); +} + +test("branch rename detail retains the source from the merge base", async () => { + const root = await repository(); + await git(root, "checkout", "-b", "feature/rename"); + await git(root, "mv", "base.txt", "renamed.txt"); + await git(root, "commit", "-m", "rename"); + const detail = await readGitReview(root, { + source: "branch", + filePath: "renamed.txt", + }); + assert.ok(detail.ok); + assert.equal(detail.snapshot.files[0]?.status, "renamed"); + assert.equal(detail.snapshot.files[0]?.previousPath, "base.txt"); + assert.match(detail.snapshot.files[0]!.diff, /rename from base.txt/u); +}); + +function indexBlob(root: string, content: string) { + return execFileSync("git", ["-C", root, "hash-object", "-w", "--stdin"], { + input: content, + encoding: "utf8", + }).trim(); +} + +for (const source of ["staged", "unstaged"] as const) { + test(`${source} summary revision tracks index-only blob changes with identical statistics`, async () => { + const root = await repository(); + const path = join(root, "base.txt"); + await writeFile(path, "worktree\n"); + await git( + root, + "update-index", + "--cacheinfo", + "100644", + indexBlob(root, "staged-a\n"), + "base.txt", + ); + const beforeFile = await lstat(path, { bigint: true }); + const before = await readGitReview(root, { source, summary: true }); + const beforeDetail = await readGitReview(root, { + source, + filePath: "base.txt", + }); + assert.ok(before.ok && beforeDetail.ok); + await git( + root, + "update-index", + "--cacheinfo", + "100644", + indexBlob(root, "staged-b\n"), + "base.txt", + ); + const after = await readGitReview(root, { source, summary: true }); + const afterDetail = await readGitReview(root, { + source, + filePath: "base.txt", + }); + assert.ok(after.ok && afterDetail.ok); + const afterFile = await lstat(path, { bigint: true }); + assert.deepEqual( + [afterFile.size, afterFile.mtimeNs, afterFile.ctimeNs], + [beforeFile.size, beforeFile.mtimeNs, beforeFile.ctimeNs], + ); + assert.equal(await readFile(path, "utf8"), "worktree\n"); + assert.deepEqual(after.snapshot.files, before.snapshot.files); + assert.match(beforeDetail.snapshot.files[0]!.diff, /staged-a/u); + assert.match(afterDetail.snapshot.files[0]!.diff, /staged-b/u); + assert.notEqual(after.snapshot.revision, before.snapshot.revision); + const unchanged = await readGitReview(root, { source, summary: true }); + assert.ok(unchanged.ok); + assert.equal(unchanged.snapshot.revision, after.snapshot.revision); + }); +} + +test("staged summary revision also tracks a changed HEAD blob without touching the index or worktree", async () => { + const root = await repository(); + const firstHead = (await git(root, "rev-parse", "HEAD")).trim(); + await writeFile(join(root, "base.txt"), "next-base\n"); + await git(root, "add", "base.txt"); + await git(root, "commit", "-m", "next base"); + const secondHead = (await git(root, "rev-parse", "HEAD")).trim(); + await writeFile(join(root, "base.txt"), "staged\n"); + await git(root, "add", "base.txt"); + await git(root, "update-ref", "HEAD", firstHead); + const before = await readGitReview(root, { source: "staged", summary: true }); + await git(root, "update-ref", "HEAD", secondHead); + const after = await readGitReview(root, { source: "staged", summary: true }); + assert.ok(before.ok && after.ok); + assert.deepEqual(after.snapshot.files, before.snapshot.files); + assert.notEqual(after.snapshot.revision, before.snapshot.revision); +}); + test("Git diff counts ignore metadata outside hunks", () => { assert.deepEqual( countGitDiffLines( diff --git a/tests/web/interactive-terminal-ui.spec.ts b/tests/web/interactive-terminal-ui.spec.ts new file mode 100644 index 00000000..a7439aa0 --- /dev/null +++ b/tests/web/interactive-terminal-ui.spec.ts @@ -0,0 +1,245 @@ +// @vitest-environment jsdom + +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import { InteractiveTerminal } from "../../web/ui/src/features/workbar/InteractiveTerminal.tsx"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; +import { i18n } from "../../web/ui/src/i18n.ts"; + +const input = vi.hoisted(() => ({ send: (_data: string) => {} })); +vi.mock("@xterm/xterm", () => ({ + Terminal: class { + cols = 80; + rows = 24; + options = { disableStdin: true }; + textarea?: HTMLTextAreaElement; + loadAddon() {} + open(host: HTMLElement) { + this.textarea = document.createElement("textarea"); + this.textarea.setAttribute("aria-label", "Terminal input"); + host.append(this.textarea); + } + attachCustomKeyEventHandler() {} + focus() { + this.textarea?.focus(); + } + dispose() { + this.textarea?.remove(); + } + reset() {} + write() {} + onData(callback: (data: string) => void) { + input.send = callback; + return { dispose() {} }; + } + onResize() { + return { dispose() {} }; + } + }, +})); +vi.mock("@xterm/addon-fit", () => ({ + FitAddon: class { + fit() {} + }, +})); +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +it.each([false, true])( + "a delayed restart respects the current focus owner (moved: %s)", + async (moved) => { + const create = vi + .spyOn(WebClient.prototype, "createInteractiveTerminal") + .mockResolvedValue({ + id: "terminal-a", + sessionId: "a", + cwd: "/workspace", + exited: false, + exitCode: null, + }); + vi.spyOn( + WebClient.prototype, + "resizeInteractiveTerminal", + ).mockResolvedValue({ resized: true }); + vi.spyOn( + WebClient.prototype, + "streamInteractiveTerminal", + ).mockImplementation( + (_session, _id, _offset, signal) => + new Promise((resolve) => { + signal.addEventListener("abort", () => resolve(), { once: true }); + }), + ); + let closed!: (value: { closed: true }) => void; + vi.spyOn( + WebClient.prototype, + "closeInteractiveTerminal", + ).mockImplementation( + () => + new Promise((resolve) => { + closed = resolve; + }), + ); + render( + createElement( + "div", + null, + createElement("input", { "aria-label": "Main prompt" }), + createElement(InteractiveTerminal, { + sessionId: "a", + cwd: "/workspace", + }), + ), + ); + await waitFor(() => expect(create).toHaveBeenCalledOnce()); + const restart = screen.getByRole("button", { + name: i18n.t("restartTerminal"), + }); + restart.focus(); + fireEvent.click(restart); + const main = screen.getByRole("textbox", { name: "Main prompt" }); + if (moved) main.focus(); + await act(async () => closed({ closed: true })); + await waitFor(() => expect(create).toHaveBeenCalledTimes(2)); + expect(document.activeElement).toBe( + moved ? main : screen.getByRole("textbox", { name: "Terminal input" }), + ); + }, +); + +it.each([false, true])( + "delayed terminal readiness respects a later focus choice (moved: %s)", + async (moved) => { + let finish!: (value: { + id: string; + sessionId: string; + cwd: string; + exited: boolean; + exitCode: number | null; + }) => void; + const create = vi + .spyOn(WebClient.prototype, "createInteractiveTerminal") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + vi.spyOn( + WebClient.prototype, + "resizeInteractiveTerminal", + ).mockResolvedValue({ resized: true }); + const stream = vi + .spyOn(WebClient.prototype, "streamInteractiveTerminal") + .mockImplementation( + (_session, _id, _offset, signal) => + new Promise((resolve) => { + signal.addEventListener("abort", () => resolve(), { once: true }); + }), + ); + render( + createElement( + "div", + null, + createElement("input", { "aria-label": "Main prompt" }), + createElement(InteractiveTerminal, { + sessionId: "a", + cwd: "/workspace", + }), + ), + ); + await waitFor(() => expect(create).toHaveBeenCalledOnce()); + const expected = screen.getByRole("textbox", { + name: moved ? "Main prompt" : "Terminal input", + }); + if (moved) expected.focus(); + await act(async () => + finish({ + id: "terminal-a", + sessionId: "a", + cwd: "/workspace", + exited: false, + exitCode: null, + }), + ); + await waitFor(() => expect(stream).toHaveBeenCalledOnce()); + expect(document.activeElement).toBe(expected); + }, +); + +it.each(["failure", "unmount", "disconnect"])( + "drops unsent terminal input after %s", + async (boundary) => { + vi.spyOn( + WebClient.prototype, + "createInteractiveTerminal", + ).mockResolvedValue({ + id: "terminal-a", + sessionId: "a", + cwd: "/workspace", + exited: false, + exitCode: null, + }); + vi.spyOn( + WebClient.prototype, + "resizeInteractiveTerminal", + ).mockResolvedValue({ resized: true }); + let disconnect!: () => void; + vi.spyOn( + WebClient.prototype, + "streamInteractiveTerminal", + ).mockImplementation( + (_session, _id, _offset, signal) => + new Promise((resolve) => { + disconnect = resolve; + signal.addEventListener("abort", () => resolve(), { once: true }); + }), + ); + let finish!: (value: { written: true }) => void; + let fail!: (reason: Error) => void; + const first = new Promise<{ written: true }>((resolve, reject) => { + finish = resolve; + fail = reject; + }); + const write = vi + .spyOn(WebClient.prototype, "writeInteractiveTerminal") + .mockReturnValueOnce(first) + .mockResolvedValue({ written: true }); + const view = render( + createElement( + I18nextProvider, + { i18n }, + createElement(InteractiveTerminal, { + sessionId: "a", + cwd: "/workspace", + }), + ), + ); + await waitFor(() => + expect( + WebClient.prototype.streamInteractiveTerminal, + ).toHaveBeenCalledOnce(), + ); + await act(async () => input.send("first\r")); + expect(write).toHaveBeenCalledOnce(); + act(() => input.send("must-not-execute\r")); + if (boundary === "unmount") view.unmount(); + if (boundary === "disconnect") await act(async () => disconnect()); + await act(async () => { + if (boundary === "failure") fail(new Error("write outcome unknown")); + else finish({ written: true }); + }); + expect(write).toHaveBeenCalledTimes(1); + }, +); diff --git a/tests/web/interactive-terminal.test.ts b/tests/web/interactive-terminal.test.ts index 13ab088f..54a79499 100644 --- a/tests/web/interactive-terminal.test.ts +++ b/tests/web/interactive-terminal.test.ts @@ -8,6 +8,52 @@ import { } from "../../web/host/interactive-terminal.ts"; import type { WebInteractiveTerminalEvent } from "../../web/protocol/types.ts"; +for (const boundary of [ + "dispose", + "switch-session", + "switch-workspace", +] as const) { + for (const started of [false, true]) { + test(`cancels ${started ? "loading" : "queued"} terminal creation on ${boundary}`, async () => { + let spawned = 0; + const manager = new InteractiveTerminalManager({ + spawn: () => { + spawned++; + return new FakePty(); + }, + }); + try { + const pending = manager.create({ + sessionId: "a", + cwd: ".", + cols: 80, + rows: 24, + }); + const rejected = assert.rejects(pending, /cancelled/u); + if (started) await Promise.resolve(); + if (boundary === "dispose") manager.dispose(); + else + manager.retain( + boundary === "switch-session" ? "b" : "a", + boundary === "switch-workspace" ? ".." : ".", + ); + await rejected; + assert.equal(spawned, 0); + const next = await manager.create({ + sessionId: "b", + cwd: ".", + cols: 80, + rows: 24, + }); + assert.equal(next.sessionId, "b"); + assert.equal(spawned, 1); + } finally { + manager.dispose(); + } + }); + } +} + class FakePty implements IPty { readonly pid = 42; cols = 80; diff --git a/tests/web/markdown-streaming.spec.ts b/tests/web/markdown-streaming.spec.ts index 6181ab9c..350c631a 100644 --- a/tests/web/markdown-streaming.spec.ts +++ b/tests/web/markdown-streaming.spec.ts @@ -101,6 +101,7 @@ it("parses changed text without reparsing mounted history during streaming or sn cursor: 1, preferences: { theme: "system" }, currentSessionId: "current", + currentSessionPath: "/tmp/current.jsonl", workspaces: [], sessions: [], models: [], @@ -183,6 +184,7 @@ it("parses changed text without reparsing mounted history during streaming or sn const other = structuredClone(snapshot); other.currentSessionId = "other"; + other.currentSessionPath = "/tmp/other.jsonl"; other.selectedSession!.id = "other"; other.selectedSession!.path = "/tmp/other.jsonl"; other.selectedSession!.entries[0].message!.content = diff --git a/tests/web/model-configuration.test.ts b/tests/web/model-configuration.test.ts new file mode 100644 index 00000000..436e18ac --- /dev/null +++ b/tests/web/model-configuration.test.ts @@ -0,0 +1,180 @@ +import assert from "node:assert/strict"; +import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; +import { ModelRuntime, SessionManager } from "@earendil-works/pi-coding-agent"; +import { + readModelConfigurations, + saveModelConfiguration, + validModelConfiguration, +} from "../../web/runtime/model-configuration.ts"; +import { PiWebRuntime } from "../../web/runtime/pi-runtime.ts"; +import type { WebModelConfiguration } from "../../web/runtime/types.ts"; + +const model: WebModelConfiguration = { + provider: "local-fixture", + id: "fixture-model", + name: "Fixture", + baseUrl: "http://127.0.0.1:9/v1", + api: "openai-responses", + reasoning: true, + contextWindow: 128000, + maxTokens: 4096, +}; + +test("native model configuration preserves other fields, rejects stale writes and never returns credentials", async () => { + const directory = await mkdtemp(join(tmpdir(), "openpi-model-config-")); + try { + const path = join(directory, "models.json"); + await writeFile( + path, + JSON.stringify({ + providers: { + [model.provider]: { + apiKey: "secret-fixture", + headers: { "X-Credential": "header-fixture" }, + baseUrl: model.baseUrl, + api: model.api, + models: [ + { + ...model, + input: ["text", "image"], + cost: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 }, + }, + ], + }, + }, + }), + ); + const before = await readModelConfigurations(directory); + assert.equal(before.models.length, 1); + assert.doesNotMatch( + JSON.stringify(before), + /secret-fixture|header-fixture|apiKey|headers/u, + ); + await saveModelConfiguration(directory, before.revision, { + ...model, + name: "Updated", + }); + const persisted = JSON.parse(await readFile(path, "utf8")); + assert.equal(persisted.providers[model.provider].apiKey, "secret-fixture"); + assert.deepEqual(persisted.providers[model.provider].models[0].input, [ + "text", + "image", + ]); + assert.equal(persisted.providers[model.provider].models[0].name, "Updated"); + // Windows reports a synthetic mode; ACLs are not represented by POSIX bits. + if (process.platform !== "win32") + assert.equal((await stat(path)).mode & 0o777, 0o600); + await assert.rejects( + saveModelConfiguration(directory, before.revision, model), + /changed/u, + ); + const runtime = await ModelRuntime.create({ + modelsPath: path, + authPath: join(directory, "auth.json"), + modelsStorePath: join(directory, "catalog.json"), + allowModelNetwork: false, + }); + assert.equal(runtime.getError(), undefined); + assert.equal(runtime.getModel(model.provider, model.id)?.name, "Updated"); + assert.equal( + validModelConfiguration({ + ...model, + baseUrl: "https://user:secret@example.com", + }), + false, + ); + assert.equal( + validModelConfiguration({ + ...model, + baseUrl: "https://example.com?key=secret", + }), + false, + ); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); + +test("Web saves a custom provider key through Pi login, reloads it, and rejects busy or stale sessions", async () => { + const directory = await mkdtemp(join(tmpdir(), "openpi-credentials-")); + try { + await saveModelConfiguration( + directory, + (await readModelConfigurations(directory)).revision, + model, + ); + const options = { + modelsPath: join(directory, "models.json"), + authPath: join(directory, "auth.json"), + modelsStorePath: join(directory, "catalog.json"), + allowModelNetwork: false, + }; + const modelRuntime = await ModelRuntime.create(options); + const session = { + sessionManager: SessionManager.inMemory(directory), + isStreaming: false, + model: modelRuntime.getModel(model.provider, model.id)!, + async setModel(next: NonNullable>) { + this.model = next; + }, + }; + const runtime = Object.assign( + Object.create(PiWebRuntime.prototype) as object, + { + runtime: { services: { agentDir: directory, modelRuntime }, session }, + hasSelectedWorkspace: true, + runtimeOperations: new Set(), + listeners: new Set(), + }, + ) as unknown as PiWebRuntime; + const sessionId = session.sessionManager.getSessionId(); + await runtime.saveProviderKey( + sessionId, + model.provider, + "fixture-write-only-key", + ); + assert.equal( + JSON.parse(await readFile(options.authPath, "utf8"))[model.provider].key, + "fixture-write-only-key", + ); + assert.doesNotMatch( + JSON.stringify(runtime.listProviderAuth()), + /fixture-write-only-key/u, + ); + const reloaded = await ModelRuntime.create(options); + assert.equal( + (await reloaded.getAuth(model.provider))?.auth.apiKey, + "fixture-write-only-key", + ); + await runtime.saveModelConfiguration( + sessionId, + (await readModelConfigurations(directory)).revision, + { + ...model, + baseUrl: "http://127.0.0.1:10/v1", + name: "Updated current model", + }, + ); + assert.equal(session.model.baseUrl, "http://127.0.0.1:10/v1"); + assert.equal(session.model.name, "Updated current model"); + session.isStreaming = true; + await assert.rejects( + runtime.saveProviderKey(sessionId, model.provider, "busy"), + /idle/u, + ); + session.isStreaming = false; + await assert.rejects( + runtime.saveProviderKey("stale", model.provider, "stale"), + /idle/u, + ); + assert.equal( + JSON.parse(await readFile(options.authPath, "utf8"))[model.provider].key, + "fixture-write-only-key", + ); + } finally { + await rm(directory, { recursive: true, force: true }); + } +}); diff --git a/tests/web/openpi-web.e2e.ts b/tests/web/openpi-web.e2e.ts index d0f3c8b4..1a1e8ae0 100644 --- a/tests/web/openpi-web.e2e.ts +++ b/tests/web/openpi-web.e2e.ts @@ -1,10 +1,13 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises"; +import { createServer } from "node:http"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { AxeBuilder } from "@axe-core/playwright"; import { SessionManager } from "@earendil-works/pi-coding-agent"; import { expect, type Page, test } from "@playwright/test"; +import { DEFAULT_SETUP_CONFIG } from "../../extensions/shared/setup-config.ts"; import type { WebSnapshot } from "../../web/protocol/types.ts"; +import { projectWebSetupConfig } from "../../web/runtime/settings-catalog.ts"; import { installThinkingFixture, MOCK_SESSION_ID, @@ -16,11 +19,186 @@ if (!token) throw new Error("OPENPI_WEB_E2E_TOKEN is required"); const authenticatedPath = "/"; +for (const firstControl of ["settings", "thinking"] as const) { + test(`first-use ${firstControl} works before sending a message and preserves the draft`, async ({ + page, + }) => { + const sessionId = "first-use-session"; + const path = "/first-use/session.jsonl"; + let created = false; + let level = "high"; + let revision = 10_000; + let creations = 0; + const prompts: unknown[] = []; + await page.route("**/events?**", (route) => + route.fulfill({ + contentType: "text/event-stream", + body: ": heartbeat\n\n", + }), + ); + await page.route("**/api/snapshot**", async (route) => { + const response = await route.fetch(); + const snapshot = await response.json(); + snapshot.cursor = revision; + snapshot.workspaces = [ + { path: "/first-use", name: "First use", current: created }, + ]; + snapshot.sessions = []; + snapshot.models = [ + { + provider: "test", + id: "reasoner", + name: "Reasoner", + label: "Reasoner", + current: true, + }, + ]; + snapshot.thinking = { + supported: true, + available: ["low", "high"], + level, + revision, + }; + snapshot.runtime = { status: "idle", capabilities: {} }; + if (created) { + snapshot.currentSessionId = sessionId; + snapshot.currentSessionPath = path; + snapshot.sessions = [ + { + id: sessionId, + path, + cwd: "/first-use", + source: "web-session", + origin: "web", + controller: "web", + readOnly: false, + name: "New session", + created: "2026-09-20T00:00:00Z", + modified: "2026-09-20T00:00:00Z", + messageCount: 0, + firstMessage: "", + }, + ]; + snapshot.selectedSession = { + id: sessionId, + path, + cwd: "/first-use", + entries: [], + bytes: 0, + truncation: { + truncated: false, + maxBytes: 1024, + entriesOmitted: 0, + messagesTruncated: 0, + messagePartsOmitted: 0, + }, + }; + } else { + delete snapshot.currentSessionId; + delete snapshot.currentSessionPath; + delete snapshot.selectedSession; + } + await route.fulfill({ response, json: snapshot }); + }); + await page.route("**/api/sessions", async (route) => { + const body = route.request().postDataJSON(); + expect(body.workspacePath).toBe("/first-use"); + created = true; + creations++; + revision++; + await route.fulfill({ + status: 201, + json: { commandId: body.commandId, sessionId, sessionPath: path }, + }); + }); + await page.route("**/api/thinking", async (route) => { + const body = route.request().postDataJSON(); + expect(body.sessionId).toBe(sessionId); + level = body.level; + revision++; + await route.fulfill({ + json: { + sessionId, + supported: true, + available: ["low", "high"], + level, + revision, + }, + }); + }); + await page.route("**/api/settings/catalog?**", (route) => + route.fulfill({ + json: { + sessionId, + setup: projectWebSetupConfig(DEFAULT_SETUP_CONFIG), + resources: { + skills: [], + plugins: [], + totals: { extensions: 0, skills: 0, prompts: 0, themes: 0 }, + diagnostics: { extensionErrors: 0, skillErrors: 0 }, + truncation: { + truncated: false, + skillsOmitted: 0, + pluginsOmitted: 0, + resourcesOmitted: 0, + }, + }, + }, + }), + ); + await page.route("**/api/prompts", (route) => { + prompts.push(route.request().postDataJSON()); + return route.fulfill({ + status: 500, + json: { error: "No prompt expected" }, + }); + }); + await page.setViewportSize({ width: 1440, height: 900 }); + await openWorkbench(page); + const settings = page.getByRole("button", { name: "设置", exact: true }); + const thinking = page.locator(".thinking-picker"); + await expect(settings).toBeEnabled(); + await expect(thinking).toBeEnabled(); + await page.locator(".workspace-picker").click(); + await page + .getByRole("menuitem", { name: "First use", exact: true }) + .click(); + const input = page.getByRole("textbox", { name: "描述任务" }); + await input.fill("Keep my first message"); + const openSettings = async () => { + await settings.click(); + const dialog = page.getByRole("dialog", { name: "设置", exact: true }); + await expect(dialog).toBeVisible(); + await expect( + dialog.getByRole("heading", { name: "常规", exact: true }), + ).toBeVisible(); + await dialog.getByRole("button", { name: "关闭", exact: true }).click(); + }; + const chooseThinking = async () => { + await thinking.click(); + await page.getByRole("menuitem", { name: "low", exact: true }).click(); + await expect(thinking).toHaveAttribute("aria-label", "思考等级: low"); + }; + if (firstControl === "settings") { + await openSettings(); + await chooseThinking(); + } else { + await chooseThinking(); + await openSettings(); + } + expect(creations).toBe(1); + expect(prompts).toEqual([]); + await expect(input).toHaveValue("Keep my first message"); + await page.unrouteAll({ behavior: "wait" }); + }); +} + function alignControlledSessionFixture(snapshot: WebSnapshot) { const session = snapshot.selectedSession; if (!session || session.id !== snapshot.currentSessionId) { throw new Error("A controlled fixture must select its current Session"); } + snapshot.currentSessionPath = session.path; snapshot.sessions = [ { id: session.id, @@ -125,6 +303,8 @@ for (const width of [1280, 390]) { await page.getByRole("button", { name: "取消", exact: true }).click(); await page.getByRole("button", { name: "编辑消息", exact: true }).click(); await expect(editor).toHaveValue("Original message ".repeat(30)); + // Finish active snapshot handlers before Playwright disposes their context. + await page.unrouteAll({ behavior: "wait" }); }); } @@ -439,6 +619,14 @@ test("desktop panes resize by pointer and collapse beyond their thresholds", asy expect(initialSidebar).not.toBeNull(); expect(initialConversation).not.toBeNull(); + const sidebarHandle = page.getByRole("separator", { name: "调整侧边栏宽度" }); + await sidebarHandle.focus(); + await page.keyboard.press("ArrowRight"); + await expect(sidebarHandle).toHaveAttribute("aria-valuenow", "296"); + await expect(sidebar).toHaveCSS("width", "296px"); + await page.keyboard.press("Enter"); + await expect(sidebar).toHaveCSS("width", "280px"); + await dragPane(page, "left", 80); await expect .poll(async () => (await sidebar.boundingBox())?.width ?? 0) @@ -453,6 +641,12 @@ test("desktop panes resize by pointer and collapse beyond their thresholds", asy await page.getByRole("button", { name: "展开侧边栏", exact: true }).click(); const workbar = await openWorkbarTool(page, "生成文件"); + const workbarHandle = page.getByRole("separator", { name: "调整工具栏宽度" }); + await workbarHandle.focus(); + await page.keyboard.press("ArrowLeft"); + await expect(workbarHandle).toHaveAttribute("aria-valuenow", "536"); + await page.keyboard.press("Enter"); + await expect(workbarHandle).toHaveAttribute("aria-valuenow", "520"); const centerBeforeRightDrag = await conversation.boundingBox(); await dragPane(page, "right", -70); await expect @@ -472,9 +666,107 @@ test("desktop panes resize by pointer and collapse beyond their thresholds", asy await expect(page.locator('[data-pane-resizer="right"]')).toHaveCount(0); }); +test("refreshing an open diff preserves the draft and composer focus", async ({ + page, +}) => { + let refreshed = false; + await page.route("**/api/git-review?**", (route) => + route.fulfill({ + json: { + ok: true, + snapshot: { + repositoryRoot: "/workspace", + currentBranch: "main", + baseBranch: null, + comparison: "session", + revision: refreshed ? "new" : "old", + additions: 1, + deletions: 0, + truncated: false, + files: [ + { + path: "focus-note.txt", + status: "untracked", + additions: 1, + deletions: 0, + diff: `@@ -0,0 +1 @@\n+${refreshed ? "refreshed" : "original"}`, + diffTruncated: false, + }, + ], + }, + }, + }), + ); + await page.setViewportSize({ width: 1440, height: 900 }); + await openWorkbench(page); + const workbar = await openWorkbarTool(page, "变更"); + await workbar + .getByRole("button", { name: "focus-note.txt", exact: true }) + .click(); + const input = page.getByRole("textbox", { name: "描述任务" }); + await input.fill("Keep editing this draft"); + refreshed = true; + await page.evaluate(() => window.dispatchEvent(new Event("focus"))); + await expect(workbar.getByRole("figure")).toContainText("refreshed"); + await expect(input).toBeFocused(); + await input.press("End"); + await input.pressSequentially(" safely"); + await expect(input).toHaveValue("Keep editing this draft safely"); +}); + +test("file reference validation can be cancelled without changing the draft", async ({ + page, +}) => { + let release!: () => void; + let started!: () => void; + const pending = new Promise((resolve) => { + release = resolve; + }); + const requested = new Promise((resolve) => { + started = resolve; + }); + await page.route("**/api/artifacts/resolve", async (route) => { + started(); + await pending; + await route + .fulfill({ status: 503, json: { error: "fixture unavailable" } }) + .catch(() => {}); + }); + try { + await openWorkbench(page); + const input = page.getByRole("textbox", { name: "描述任务" }); + await input.fill("Keep this draft"); + await page.getByRole("button", { name: "添加上下文", exact: true }).click(); + await page.getByRole("menuitem", { name: /引用工作区文件/u }).click(); + const dialog = page.getByRole("dialog"); + await dialog.getByRole("textbox").fill("README.md"); + await dialog.getByRole("button", { name: "插入引用" }).click(); + await requested; + const cancel = dialog.getByRole("button", { name: "取消", exact: true }); + await expect(cancel).toBeEnabled(); + await cancel.click(); + await expect(dialog).toBeHidden(); + await expect(input).toHaveValue("Keep this draft"); + } finally { + release(); + await page.unrouteAll({ behavior: "wait" }); + } +}); + test("workbar exposes five tools and completes a side conversation lifecycle", async ({ page, }) => { + await page.route("**/api/snapshot**", async (route) => { + const response = await route.fetch(); + const snapshot = await response.json(); + snapshot.runtime.status = "running"; + snapshot.runtime.activeTurn = { + sessionId: snapshot.currentSessionId, + commandId: "busy-parent", + epoch: 1, + }; + await route.fulfill({ response, json: snapshot }); + }); type SideAction = { sessionId: string; kind: "subagents"; @@ -564,6 +856,9 @@ test("workbar exposes five tools and completes a side conversation lifecycle", a await question.fill("Inspect this in isolation"); await question.press("Enter"); await expect(workbar.getByText("Side response")).toBeVisible(); + await expect( + page.getByRole("button", { name: "停止当前轮次", exact: true }), + ).toBeVisible(); await expect .poll(() => actions.map((action) => action.action)) .toEqual(["spawn-btw"]); @@ -601,9 +896,238 @@ test("terminal tool runs a real workspace shell", async ({ page }) => { .toContain("OPENPI_WEB_TERMINAL_OK"); }); +test("model configuration drafts survive switching settings tabs", async ({ + page, +}) => { + await page.route("**/api/models/configuration?**", (route) => + route.fulfill({ json: { revision: "draft-test", models: [] } }), + ); + await openWorkbench(page); + await page.getByRole("button", { name: "设置", exact: true }).click(); + const dialog = page.getByRole("dialog", { name: "设置" }); + const generalTab = dialog.getByRole("tab", { name: "常规", exact: true }); + await generalTab.focus(); + await page.keyboard.press("ArrowRight"); + await expect( + dialog.getByRole("tab", { name: "模型", exact: true }), + ).toBeFocused(); + await dialog + .getByRole("textbox", { name: "API 地址", exact: true }) + .fill("http://localhost:12345/v1"); + await dialog + .getByRole("textbox", { name: "显示名称", exact: true }) + .fill("Unfinished model"); + await dialog.getByRole("tab", { name: "技能", exact: true }).click(); + await page.keyboard.press("End"); + await expect( + dialog.getByRole("tab", { name: "插件", exact: true }), + ).toBeFocused(); + await page.keyboard.press("Home"); + await expect(generalTab).toBeFocused(); + await dialog.getByRole("tab", { name: "模型", exact: true }).click(); + await expect( + dialog.getByRole("textbox", { name: "API 地址", exact: true }), + ).toHaveValue("http://localhost:12345/v1"); + await expect( + dialog.getByRole("textbox", { name: "显示名称", exact: true }), + ).toHaveValue("Unfinished model"); +}); + +test("real embedded Chromium opens bare local addresses and paints shortcut edits", async ({ + page, +}, testInfo) => { + const inputs: string[] = []; + const mouseEvents: Array<{ + phase: string | null; + button: string | null; + buttons: string | null; + }> = []; + const fixture = createServer((request, response) => { + const url = new URL(request.url ?? "/", "http://localhost"); + if (url.pathname === "/mouse") { + mouseEvents.push({ + phase: url.searchParams.get("phase"), + button: url.searchParams.get("button"), + buttons: url.searchParams.get("buttons"), + }); + response.end("ok"); + return; + } + if (url.pathname === "/event") { + inputs.push(url.searchParams.get("text") ?? ""); + response.end("ok"); + return; + } + response.setHeader("Content-Type", "text/html; charset=utf-8"); + response.end( + `Real input fixture
`, + ); + }); + await new Promise((resolve) => fixture.listen(0, "127.0.0.1", resolve)); + const address = fixture.address(); + if (!address || typeof address === "string") + throw new Error("Missing fixture port"); + try { + await page.setViewportSize({ width: 1440, height: 900 }); + await openWorkbench(page); + const workbar = await openWorkbarTool(page, "浏览器"); + await workbar + .getByRole("textbox", { name: "浏览器地址" }) + .fill(`127.0.0.1:${address.port}`); + // The host allows 8s for cold Chromium startup; await its receipt before + // beginning the separate painted-frame assertion. + const startup = page.waitForResponse( + (response) => + response.url().endsWith("/api/browser/open") && + response.request().method() === "POST", + { timeout: 20000 }, + ); + await workbar + .getByRole("button", { name: "打开地址", exact: true }) + .click(); + const started = await startup; + expect(started.ok(), await started.text()).toBe(true); + expect(started.request().postDataJSON()).toMatchObject({ + url: `http://127.0.0.1:${address.port}/`, + }); + const viewport = workbar.locator(".browser-viewport"); + const frame = viewport.locator("img"); + await expect(frame).toBeVisible(); + const painted = () => + frame.evaluate((image) => { + if ( + !(image instanceof HTMLImageElement) || + !image.complete || + !image.naturalWidth + ) + return false; + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = 1; + const context = canvas.getContext("2d"); + if (!context) return false; + context.drawImage(image, image.naturalWidth - 20, 20, 1, 1, 0, 0, 1, 1); + const [red, green] = context.getImageData(0, 0, 1, 1).data; + return green! > 150 && red! < 50; + }); + const samples: Array<{ + shortcut: string; + inputToPaintUpperBoundMs: number; + }> = []; + for (const modifier of ["Control", "Meta"]) { + await viewport.click({ position: { x: 30, y: 30 } }); + const started = performance.now(); + await viewport.press(`${modifier}+a`); + await viewport.press("X"); + await expect.poll(() => inputs.at(-1)).toBe("X"); + await expect.poll(painted).toBe(true); + samples.push({ + shortcut: `${modifier}+a`, + inputToPaintUpperBoundMs: Math.round(performance.now() - started), + }); + if (modifier === "Control") + await viewport.screenshot({ + path: testInfo.outputPath("browser-input.png"), + }); + await viewport.press(`${modifier}+z`); + await expect.poll(() => inputs.at(-1)).toBe("original text"); + await expect.poll(painted).toBe(false); + } + const bounds = await viewport.boundingBox(); + if (!bounds) throw new Error("Browser viewport missing"); + const textDragStart = mouseEvents.length; + await page.mouse.move(bounds.x + 40, bounds.y + 30); + await page.mouse.down(); + await expect + .poll(() => + mouseEvents + .slice(textDragStart) + .some((event) => event.phase === "mousedown"), + ) + .toBe(true); + await page.mouse.move(bounds.x - 40, bounds.y + 30, { steps: 3 }); + await page.mouse.up(); + // Undo can leave selected text. Native HTML dragging suppresses mouseup + // and ends with dragend instead; both must report the released button. + const textRelease = () => + mouseEvents + .slice(textDragStart) + .find( + (event) => event.phase === "mouseup" || event.phase === "dragend", + ); + await expect.poll(textRelease).toMatchObject({ + phase: expect.stringMatching(/^(mouseup|dragend)$/), + button: "0", + buttons: "0", + }); + if (textRelease()?.phase === "dragend") { + await expect + .poll(() => + mouseEvents + .slice(textDragStart) + .some((event) => event.phase === "dragstart"), + ) + .toBe(true); + } + // A non-selectable area exercises plain pointer capture independently of + // the browser's native drag-and-drop lifecycle. + const pointerDragStart = mouseEvents.length; + await page.mouse.move(bounds.x + 40, bounds.y + 100); + await page.mouse.down(); + await expect + .poll(() => + mouseEvents + .slice(pointerDragStart) + .some((event) => event.phase === "mousedown"), + ) + .toBe(true); + await page.mouse.move(bounds.x - 40, bounds.y + 100, { steps: 3 }); + await page.mouse.up(); + await expect + .poll(() => + mouseEvents + .slice(pointerDragStart) + .find((event) => event.phase === "mouseup"), + ) + .toEqual({ phase: "mouseup", button: "0", buttons: "0" }); + await viewport.click({ button: "right", position: { x: 40, y: 100 } }); + await expect + .poll(() => mouseEvents.at(-1)) + .toEqual({ phase: "mouseup", button: "2", buttons: "0" }); + const evidencePath = testInfo.outputPath("input-to-painted-frame.json"); + await writeFile( + evidencePath, + JSON.stringify( + { + samples, + mouseEvents, + limitation: + "Local smoke including automation and polling overhead; not a benchmark.", + }, + null, + 2, + ), + ); + await testInfo.attach("input-to-painted-frame", { + path: evidencePath, + contentType: "application/json", + }); + } finally { + fixture.closeAllConnections(); + await new Promise((resolve, reject) => + fixture.close((error) => (error ? reject(error) : resolve())), + ); + } +}); + test("browser tool keeps an interactive page inside the workbar", async ({ page, }) => { + await page.route("**/api/browser/frames?**", (route) => + route.fulfill({ + contentType: "text/event-stream", + body: `data: ${JSON.stringify({ mimeType: "image/png", data: "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4nGP4DwQACfsD/fteaysAAAAASUVORK5CYII=", width: 400, height: 600 })}\n\n`, + }), + ); type BrowserState = { sessionId: string; url: string; @@ -1030,15 +1554,31 @@ test("discovers and completes Pi commands without submitting unsupported command /\/deploy/u, /\/inspect/u, /\/optimize/u, + /\/extension:setup/u, ]); + const unavailableOption = listbox.getByRole("option", { + name: /\/extension:setup/u, + }); + await expect(unavailableOption).toBeDisabled(); + await expect(unavailableOption).toContainText("此扩展命令尚未接入 Web"); const menu = page.locator(".slash-command-menu"); const menuBox = await menu.boundingBox(); const composerBox = await page.locator(".composer").boundingBox(); expect(menuBox).not.toBeNull(); expect(composerBox).not.toBeNull(); expect(menuBox!.height).toBeLessThanOrEqual(225); - expect(menuBox!.y).toBeGreaterThanOrEqual( - composerBox!.y + composerBox!.height + 7, + if ((await menu.getAttribute("data-placement")) === "above") { + expect(menuBox!.y + menuBox!.height).toBeLessThanOrEqual( + composerBox!.y - 7, + ); + } else { + expect(menuBox!.y).toBeGreaterThanOrEqual( + composerBox!.y + composerBox!.height + 7, + ); + } + expect(menuBox!.y).toBeGreaterThanOrEqual(0); + expect(menuBox!.y + menuBox!.height).toBeLessThanOrEqual( + page.viewportSize()!.height, ); for (let index = 0; index < 5; index++) await input.press("ArrowDown"); @@ -1470,6 +2010,7 @@ test("recovers an unknown prompt admission only after an explicit user decision" const response = await route.fetch(); const snapshot = await response.json(); snapshot.currentSessionId = sessionId; + snapshot.currentSessionPath = "/unknown-admission/session.jsonl"; snapshot.workspaces = [ { path: "/unknown-admission", name: "Recovery", current: true }, ]; @@ -1587,6 +2128,24 @@ test("inspects session-scoped runtime and terminal details on desktop and mobile const response = await route.fetch(); const snapshot = await response.json(); snapshot.currentSessionId = sessionId; + snapshot.workspaces = [ + { path: "/inspection", name: "Inspection", current: true }, + ]; + snapshot.sessions = [ + { + id: sessionId, + path: "/inspection/session.jsonl", + cwd: "/inspection", + source: "web-session", + origin: "web", + controller: "web", + readOnly: false, + created: "2026-09-20T00:00:00Z", + modified: "2026-09-20T00:00:00Z", + messageCount: 0, + firstMessage: "", + }, + ]; snapshot.selectedSession = { id: sessionId, path: "/inspection/session.jsonl", @@ -1706,18 +2265,27 @@ test("inspects session-scoped runtime and terminal details on desktop and mobile await expect(dialog).not.toBeVisible(); await page.getByRole("button", { name: "服务商凭据", exact: true }).click(); const providerSettings = page.getByRole("dialog", { name: "设置" }); + await expect(providerSettings).toHaveCSS("opacity", "1"); const settingsNavigation = providerSettings.getByRole("tablist", { name: "设置导航", }); + const selectSettings = async (id: "models" | "general", label: string) => { + if (width <= 640) + await providerSettings + .getByRole("combobox", { name: "设置导航" }) + .selectOption(id); + else + await settingsNavigation + .getByRole("tab", { name: label, exact: true }) + .click(); + }; await expect( providerSettings.getByRole("heading", { name: "常规" }), ).toBeVisible(); await expect( providerSettings.getByRole("button", { name: "打开运行详情" }), ).toBeVisible(); - await settingsNavigation - .getByRole("tab", { name: "模型", exact: true }) - .click(); + await selectSettings("models", "模型"); await expect( providerSettings.getByRole("heading", { name: "Inspection Model" }), ).toBeVisible(); @@ -1725,18 +2293,14 @@ test("inspects session-scoped runtime and terminal details on desktop and mobile await expect .poll(() => reads.filter((x) => x === "providers/auth-status").length) .toBe(width === 1280 ? 1 : 2); - await settingsNavigation - .getByRole("tab", { name: "常规", exact: true }) - .click(); + await selectSettings("general", "常规"); await expect( providerSettings.getByRole("heading", { name: "常规" }), ).toBeVisible(); await expect( providerSettings.getByRole("button", { name: "打开运行详情" }), ).toBeVisible(); - await settingsNavigation - .getByRole("tab", { name: "模型", exact: true }) - .click(); + await selectSettings("models", "模型"); await expect( providerSettings.getByRole("heading", { name: "Inspection Model" }), ).toBeVisible(); @@ -1858,6 +2422,7 @@ test("adds validated file references without hiding the prompt contract", async await dialog.getByRole("button", { name: "插入引用" }).click(); await expect(dialog).toContainText("请选择当前会话工作区内的文件。"); await expect(draft).toHaveValue("`README.md`"); + await expect(dialog).toHaveCSS("opacity", "1"); expect((await new AxeBuilder({ page }).analyze()).violations).toEqual([]); await page.screenshot({ path: testInfo.outputPath(`file-reference-${width}.png`), diff --git a/tests/web/pi-runtime.test.ts b/tests/web/pi-runtime.test.ts index 7c644e57..ee517ba5 100644 --- a/tests/web/pi-runtime.test.ts +++ b/tests/web/pi-runtime.test.ts @@ -4,6 +4,15 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import test, { type TestContext } from "node:test"; import { SessionManager } from "@earendil-works/pi-coding-agent"; +import type { + AgentSessionServices, + ExtensionAPI, +} from "@earendil-works/pi-coding-agent"; +import { fileURLToPath } from "node:url"; +import { + createCommandDiscoveryBridge, + registerCommandDiscoveryBridge, +} from "../../web/runtime/command-discovery.ts"; import { jsonByteLength } from "../../web/protocol/types.ts"; import { PiWebRuntime } from "../../web/runtime/pi-runtime.ts"; import { matchesSessionIdentity } from "../../web/runtime/session-identity.ts"; @@ -173,8 +182,16 @@ function promptSession(sessionId: string) { let followUpMessages: string[] = []; return { isStreaming: false, + get isIdle() { + return !this.isStreaming; + }, pendingMessageCount: 0, - sessionManager: { getSessionId: () => sessionId }, + sessionManager: { + getSessionId: () => sessionId, + getSessionFile: (): string | undefined => undefined, + appendCustomEntry: (_customType: string, _data?: unknown) => + "fixture-entry", + }, abort: async (): Promise => undefined, subscribe( listener: (event: { @@ -205,6 +222,7 @@ function promptSession(sessionId: string) { type PromptSession = ReturnType; type FakeAgentRuntime = { session: PromptSession; + services?: AgentSessionServices; dispose: () => Promise; }; type PromptRuntimeHarness = { @@ -412,6 +430,7 @@ test("Plan control targets the active idle owned Session without admitting a pro try { const request = { sessionId: "session-a", + sessionPath: "current:session-a", enabled: true, expectedRevision: null, }; @@ -465,6 +484,50 @@ test("prompt admission waits for Pi preflight acceptance", async () => { await Promise.resolve(); }); +test("a terminal-only extension command is rejected before Pi dispatch and trace admission", async () => { + const session = promptSession("session-a"); + const runtime = promptHarness(session); + const services = Object.create(null) as AgentSessionServices; + runtime.runtime.services = services; + const bridge = createCommandDiscoveryBridge(); + const api = { + getCommands: () => [ + { + name: "sessions", + source: "extension", + sourceInfo: { + path: fileURLToPath( + new URL("../../extensions/sessions/index.ts", import.meta.url), + ), + source: "fixture", + scope: "user", + origin: "package", + }, + }, + ], + } as ExtensionAPI; + await (typeof bridge.extension === "function" + ? bridge.extension(api) + : bridge.extension.factory(api)); + registerCommandDiscoveryBridge(services, bridge); + await assert.rejects( + runtime.sendPrompt("/sessions hello", { + commandId: "blocked-command", + expectedSessionId: "session-a", + }), + (error: unknown) => { + assert.ok(error instanceof WebRuntimeRequestError); + assert.equal(error.code, "PROMPT_REJECTED"); + assert.equal(error.statusCode, 422); + assert.match(error.message, /not available in the Web runtime/u); + return true; + }, + ); + assert.equal(session.calls.length, 0); + assert.equal(runtime.activePromptTrace, undefined); + assert.deepEqual(runtime.pendingPromptTraces, []); +}); + test("prompt admission snapshots Pi follow-up messages", async () => { const session = promptSession("session-a"); session.isStreaming = true; @@ -531,7 +594,26 @@ test("handled input snapshots an externally pending follow-up without claiming o const session = promptSession("session-a"); session.isStreaming = true; const runtime = promptHarness(session); - const admission = runtime.sendPrompt("/handled", { + const services = Object.create(null) as AgentSessionServices; + runtime.runtime.services = services; + registerCommandDiscoveryBridge(services, { + extension: () => undefined, + read: () => [ + { + name: "openpi-setup", + source: "extension", + sourceInfo: { + path: fileURLToPath( + new URL("../../extensions/setup/index.ts", import.meta.url), + ), + source: "fixture", + scope: "user", + origin: "package", + }, + }, + ], + }); + const admission = runtime.sendPrompt("/openpi-setup", { commandId: "command-handled", expectedSessionId: "session-a", }); @@ -544,6 +626,84 @@ test("handled input snapshots an externally pending follow-up without claiming o await Promise.resolve(); }); +test("an extension triggerTurn retains its admitted identity until delayed native agent_start", async (t) => { + let clock = 100; + t.mock.method(performance, "now", () => clock); + const session = promptSession("session-a"); + const runtime = promptHarness(session); + const services = Object.create(null) as AgentSessionServices; + runtime.runtime.services = services; + registerCommandDiscoveryBridge(services, { + extension: () => undefined, + read: () => [ + { + name: "openpi-setup", + source: "extension", + sourceInfo: { + path: fileURLToPath( + new URL("../../extensions/setup/index.ts", import.meta.url), + ), + source: "fixture", + scope: "user", + origin: "package", + }, + }, + ], + }); + const events: WebRuntimeEvent[] = []; + runtime.subscribe((event) => events.push(event)); + const admission = runtime.sendPrompt("/openpi-setup change theme", { + commandId: "setup-command", + expectedSessionId: "session-a", + }); + await Promise.resolve(); + // sendCustomMessage has entered _runAgentPrompt, but extension listeners + // have not yet finished delivering the agent_start event to Web. + session.isStreaming = true; + session.calls[0].options.preflightResult?.(true); + session.calls[0].run.resolve(); + await admission; + await new Promise((resolve) => setImmediate(resolve)); + assert.equal( + events.some((event) => event.type === "prompt_settled"), + false, + ); + assert.equal(runtime.activePromptTrace?.commandId, "setup-command"); + assert.equal(runtime.activePromptTrace?.started, false); + const projectEvent = ( + PiWebRuntime.prototype as unknown as { + projectEvent( + this: PromptRuntimeHarness, + session: object, + event: object, + ): void; + } + ).projectEvent; + clock = 1100; + projectEvent.call(runtime, session, { type: "agent_start" }); + assert.equal( + events.find((event) => event.type === "turn_started")?.detail?.commandId, + "setup-command", + ); + assert.equal( + events.find((event) => event.type === "turn_started")?.detail?.elapsedMs, + 0, + ); + runtime.activePromptTrace!.outcome = "completed"; + clock = 3600; + session.isStreaming = false; + projectEvent.call(runtime, session, { type: "agent_settled" }); + assert.equal( + events.find((event) => event.type === "turn_settled")?.detail?.elapsedMs, + 2500, + ); + assert.equal( + events.find((event) => event.type === "turn_settled")?.detail?.outcome, + "completed", + ); + assert.equal(runtime.activePromptTrace, undefined); +}); + test("prompt preflight rejection is a typed non-admission", async () => { const session = promptSession("session-a"); const runtime = promptHarness(session); @@ -1007,6 +1167,12 @@ test("handled prompt emits a correlated settlement without agent events", async test("a command's delayed native turn retains its origin without lending it to another run", async () => { const session = promptSession("session-a"); const runtime = promptHarness(session); + const services = Object.create(null) as AgentSessionServices; + runtime.runtime.services = services; + registerCommandDiscoveryBridge(services, { + extension: () => undefined, + read: () => [], + }); const events: WebRuntimeEvent[] = []; runtime.subscribe((event) => events.push(event)); const project = ( @@ -1627,7 +1793,9 @@ test("runtime creation failure releases the Web Host lease", async () => { } }); -test("message_end and queued prompts do not settle a running turn", () => { +test("message_end and queued prompts do not settle a running turn", (t) => { + t.mock.method(Date, "now", () => 10000); + t.mock.method(performance, "now", () => 100); const session = { sessionManager: { getSessionId: () => "session" } }; const harness = Object.create(PiWebRuntime.prototype) as RuntimeHarness; harness.runtime = { session }; @@ -1749,6 +1917,9 @@ test("message_end and queued prompts do not settle a running turn", () => { queued: false, userMessageObserved: true, epoch: 2, + executionStartedAt: 10000, + executionClock: 100, + sessionPath: "current:session", }); assert.deepEqual( events @@ -1757,7 +1928,14 @@ test("message_end and queued prompts do not settle a running turn", () => { [ { type: "turn_started", - detail: { sessionId: "session", commandId: "first", epoch: 1 }, + detail: { + sessionId: "session", + commandId: "first", + epoch: 1, + startedAt: 10000, + elapsedMs: 0, + sessionPath: "current:session", + }, }, { type: "turn_settled", @@ -1766,17 +1944,29 @@ test("message_end and queued prompts do not settle a running turn", () => { commandId: "first", epoch: 1, outcome: "cancelled", + startedAt: 10000, + elapsedMs: 0, + sessionPath: "current:session", }, }, { type: "turn_started", - detail: { sessionId: "session", commandId: "third", epoch: 2 }, + detail: { + sessionId: "session", + commandId: "third", + epoch: 2, + startedAt: 10000, + elapsedMs: 0, + sessionPath: "current:session", + }, }, ], ); }); -test("toolUse message_end without a terminal result settles as uncertain", () => { +test("toolUse message_end without a terminal result settles as uncertain", (t) => { + t.mock.method(Date, "now", () => 10000); + t.mock.method(performance, "now", () => 100); const session = { sessionManager: { getSessionId: () => "session" } }; const harness = Object.create(PiWebRuntime.prototype) as RuntimeHarness; harness.runtime = { session }; @@ -1839,6 +2029,9 @@ test("toolUse message_end without a terminal result settles as uncertain", () => commandId: "tool-use", epoch: 1, outcome: "uncertain", + startedAt: 10000, + elapsedMs: 0, + sessionPath: "current:session", }, ], ); diff --git a/tests/web/plan-card.spec.ts b/tests/web/plan-card.spec.ts index fe072f20..782fa100 100644 --- a/tests/web/plan-card.spec.ts +++ b/tests/web/plan-card.spec.ts @@ -72,6 +72,7 @@ function transcript( generatedAt: "2026-09-19T00:00:00Z", cursor: 1, currentSessionId: active ? "session" : "other", + currentSessionPath: active ? "/tmp/session" : "/tmp/other", workspaces: [], sessions: [], models: [], diff --git a/tests/web/plan-provider.e2e.ts b/tests/web/plan-provider.e2e.ts index e8606c68..b2d52037 100644 --- a/tests/web/plan-provider.e2e.ts +++ b/tests/web/plan-provider.e2e.ts @@ -97,6 +97,7 @@ test("Plan switch changes only owner state; first message gets planning context headers, data: { sessionId, + sessionPath, enabled: false, expectedRevision: before.runtime.planRevision, }, diff --git a/tests/web/playwright.config.ts b/tests/web/playwright.config.ts index 011d2569..7a7d782a 100644 --- a/tests/web/playwright.config.ts +++ b/tests/web/playwright.config.ts @@ -1,7 +1,7 @@ import { tmpdir } from "node:os"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { defineConfig } from "@playwright/test"; +import { chromium, defineConfig } from "@playwright/test"; const repositoryRoot = resolve( dirname(fileURLToPath(import.meta.url)), @@ -13,7 +13,6 @@ const token = const origin = `http://127.0.0.1:${port}`; const browserExecutable = process.env.OPENPI_WEB_BROWSER_EXECUTABLE; const outputDirectory = resolve(tmpdir(), "openpi-web-playwright-results"); -const agentDirectory = resolve(outputDirectory, "agent"); process.env.OPENPI_WEB_E2E_TOKEN = token; @@ -21,6 +20,15 @@ export default defineConfig({ testDir: repositoryRoot, testMatch: [ "tests/web/openpi-web.e2e.ts", + "tests/web/composer-clipboard.e2e.ts", + "tests/web/settings-parity.e2e.ts", + "tests/web/conversation-reading.e2e.ts", + "tests/web/workbench-polish.e2e.ts", + "tests/web/session-history.e2e.ts", + "tests/web/turn-changes-history.e2e.ts", + "tests/web/session-observer.e2e.ts", + "tests/web/git-review.e2e.ts", + "tests/web/artifact-images.e2e.ts", "tests/web/artifact-evidence.e2e.ts", "tests/web/subagent-inspection.e2e.ts", ], @@ -40,12 +48,15 @@ export default defineConfig({ }, webServer: { command: - "node --experimental-strip-types ./bin/openpi.js web . --port 57109 --no-open", + "node --experimental-strip-types ./tests/web/start-test-server.mjs web . --port 57109 --no-open", cwd: repositoryRoot, env: { ...process.env, OPENPI_WEB_TOKEN: token, - PI_CODING_AGENT_DIR: agentDirectory, + OPENPI_CHROME_PATH: + process.env.OPENPI_CHROME_PATH ?? + browserExecutable ?? + chromium.executablePath(), }, reuseExistingServer: false, timeout: 30_000, diff --git a/tests/web/prompt-visibility.spec.ts b/tests/web/prompt-visibility.spec.ts new file mode 100644 index 00000000..1441ed77 --- /dev/null +++ b/tests/web/prompt-visibility.spec.ts @@ -0,0 +1,478 @@ +// @vitest-environment jsdom +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { cleanup, render } from "@testing-library/react"; +import { afterEach, expect, it, vi } from "vitest"; +import { projectMessage, type WebSnapshot } from "../../web/protocol/types.ts"; +import { Transcript } from "../../web/ui/src/features/transcript/Transcript.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; +import type { LiveEntry } from "../../web/ui/src/store/web-store.ts"; + +afterEach(cleanup); + +it("keeps an unaccepted prompt even when another native input matches its text", () => { + const value = snapshot(); + value.selectedSession!.entries.push({ + id: "other-input", + timestamp: "2026-09-22T09:01:00Z", + type: "message", + message: { role: "user", content: "继续" }, + }); + const pending: LiveEntry = { + key: "sending", + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "sending", + afterEntryId: "old-answer", + admitted: false, + }, + }; + const remember = vi.fn(); + const view = render(node(value, [pending], remember)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(3); + expect(remember).not.toHaveBeenCalled(); +}); + +it("does not resurrect an already projected prompt after its native entry leaves the latest window", () => { + const value = snapshot(); + value.selectedSession!.entries.push({ + id: "accepted-input", + timestamp: "2026-09-22T09:01:00Z", + type: "message", + message: projectMessage({ + role: "user", + content: [{ type: "text", text: "继续" }], + }), + }); + const pending: LiveEntry = { + key: "accepted", + timestamp: "2026-09-22T09:01:00Z", + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "accepted", + afterEntryId: "old-answer", + admitted: true, + }, + }; + const remember = vi.fn(); + const view = render(node(value, [pending], remember)); + expect(remember).toHaveBeenCalledWith("s", "/session.jsonl", [ + { key: "accepted", entryId: "accepted-input" }, + ]); + const projected: LiveEntry = { + ...pending, + message: { role: "user", content: "" }, + optimistic: { ...pending.optimistic!, projectedEntryId: "accepted-input" }, + }; + value.selectedSession!.entries = [ + { + id: "later", + timestamp: "2026-09-22T09:02:00Z", + type: "message", + message: { role: "assistant", content: "More recent progress" }, + }, + ]; + view.rerender(node(structuredClone(value), [projected], remember)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(0); + expect(view.container.textContent).not.toContain("继续"); +}); + +it("shows the native queue separately from a persisted identical prompt", () => { + const value = snapshot(); + value.selectedSession!.entries.push({ + id: "external-followup", + timestamp: "2026-09-22T09:01:00Z", + type: "message", + message: { role: "user", content: "继续" }, + }); + value.selectedExecution = { + sessionId: "s", + sessionPath: "/session.jsonl", + status: "running", + pendingFollowUps: 2, + queuedMessages: ["继续", "继续"], + liveTools: [], + liveToolsOmitted: 0, + }; + const pending: LiveEntry[] = ["b", "c"].map((commandId) => ({ + key: `optimistic-${commandId}`, + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId, + admitted: true, + afterEntryId: "old-answer", + }, + })); + const view = render(node(value, pending)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); + expect(view.container.textContent).toContain( + i18n.t("pendingFollowUpsHint", { count: 2 }), + ); +}); + +it("consumes only one local preview per native queued message", () => { + const value = snapshot(); + value.selectedExecution = { + sessionId: "s", + sessionPath: "/session.jsonl", + status: "running", + pendingFollowUps: 1, + queuedMessages: ["继续"], + liveTools: [], + liveToolsOmitted: 0, + }; + const pending: LiveEntry[] = ["first", "second"].map((commandId) => ({ + key: `optimistic-${commandId}`, + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId, + admitted: true, + afterEntryId: "old-answer", + }, + })); + const view = render(node(value, pending)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); + value.selectedExecution.queuedMessages = []; + value.selectedExecution.pendingFollowUps = 0; + view.rerender(node(structuredClone(value), pending)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(3); +}); + +function snapshot(): WebSnapshot { + return { + protocolVersion: 1, + generatedAt: "2026-09-22T09:00:00Z", + cursor: 1, + preferences: { theme: "light" }, + currentSessionId: "s", + currentSessionPath: "/session.jsonl", + workspaces: [], + sessions: [], + models: [], + selectedSession: { + id: "s", + path: "/session.jsonl", + cwd: "/workspace", + bytes: 0, + entries: [ + { + id: "old-question", + timestamp: "2026-09-22T09:00:00Z", + type: "message", + message: { role: "user", content: "继续" }, + }, + { + id: "old-answer", + timestamp: "2026-09-22T09:00:01Z", + type: "message", + message: { + role: "assistant", + content: "已完成上一轮。", + stopReason: "stop", + }, + }, + ], + truncation: { + truncated: false, + maxBytes: 2097152, + entriesOmitted: 0, + messagesTruncated: 0, + messagePartsOmitted: 0, + }, + }, + runtime: { status: "running", capabilities: {} }, + truncation: { + truncated: false, + maxBytes: 4194304, + bytes: 0, + sessionsOmitted: 0, + workspacesOmitted: 0, + modelsOmitted: 0, + }, + }; +} + +function node( + value: WebSnapshot, + live: LiveEntry[], + onPromptProjection?: ( + sessionId: string, + sessionPath: string, + pairs: { key: string; entryId: string }[], + ) => void, +) { + return createElement( + I18nextProvider, + { i18n }, + createElement(Transcript, { + snapshot: value, + liveMessages: live, + liveRunning: value.runtime.status === "running", + livePhase: value.runtime.status === "running" ? "running" : "idle", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + onPromptProjection, + }), + ); +} + +it("does not use an old identical prompt as acknowledgement of a newly submitted message", () => { + const value = snapshot(); + const live: LiveEntry & { + optimistic: { commandId: string; afterEntryId: string | null }; + } = { + key: "optimistic-new", + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "new", + admitted: true, + afterEntryId: "old-answer", + }, + }; + const view = render(node(value, [live])); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); + value.selectedSession!.entries.push({ + id: "new-question", + timestamp: "2026-09-22T08:59:00Z", + type: "message", + message: projectMessage({ + role: "user", + content: [{ type: "text", text: "继续" }], + }), + }); + view.rerender(node(structuredClone(value), [live])); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); +}); + +it("consumes each new persisted acknowledgement once when identical follow-ups share an anchor", () => { + const value = snapshot(); + const live: Array< + LiveEntry & { + optimistic: { commandId: string; afterEntryId: string | null }; + } + > = ["a", "b"].map((commandId) => ({ + key: `optimistic-${commandId}`, + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId, + admitted: true, + afterEntryId: "old-answer", + }, + })); + value.selectedSession!.entries.push({ + id: "new-a", + timestamp: "2026-09-22T09:00:02Z", + type: "message", + message: { role: "user", content: "继续" }, + }); + const view = render(node(value, live)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(3); +}); + +it("keeps a pending message and its sent time when its original anchor is outside the visible window", () => { + const value = snapshot(); + const sent = "2026-09-22T09:02:00Z"; + const live: LiveEntry = { + key: "optimistic-pending", + timestamp: sent, + message: { role: "user", content: "继续" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "pending", + admitted: true, + afterEntryId: "outside-window", + }, + }; + const view = render(node(value, [live])); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); + expect( + view.container + .querySelector(".message-row.user:last-of-type time") + ?.getAttribute("datetime"), + ).toBe(sent); + value.cursor++; + view.rerender(node(structuredClone(value), [live])); + const messages = view.container.querySelectorAll(".message-row.user"); + expect(messages).toHaveLength(2); + expect(messages[1]?.querySelector("time")?.getAttribute("datetime")).toBe( + sent, + ); +}); + +it("acknowledges an admitted prompt whose clipped anchor is its native parent", () => { + const value = snapshot(); + value.selectedSession!.entries = [ + { + id: "new-question", + parentId: "outside-window", + timestamp: "2026-09-22T09:02:00Z", + type: "message", + message: { role: "user", content: "New request" }, + }, + ]; + const pending: LiveEntry = { + key: "optimistic-new", + message: { role: "user", content: "New request" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "new", + afterEntryId: "outside-window", + admitted: true, + }, + }; + const remember = vi.fn(); + const view = render(node(value, [pending], remember)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(1); + expect(remember).toHaveBeenCalledWith("s", "/session.jsonl", [ + { key: "optimistic-new", entryId: "new-question" }, + ]); + + value.selectedSession!.entries[0]!.parentId = "different-branch"; + view.rerender(node(structuredClone(value), [pending], remember)); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); +}); + +it("keeps the local admission visible while a live user event awaits native projection", () => { + const value = snapshot(); + const pending: LiveEntry = { + key: "optimistic-new", + message: { role: "user", content: "New request" }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "new", + afterEntryId: "old-answer", + admitted: true, + }, + }; + const event: LiveEntry = { + key: "native-event", + message: { role: "user", content: "New request" }, + }; + const view = render(node(value, [pending, event])); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(2); + expect(view.container.textContent).toContain("New request"); + + view.rerender( + node(structuredClone(value), [ + pending, + event, + { ...event, key: "another-user-event" }, + ]), + ); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(3); + + const unadmitted: LiveEntry = { + ...pending, + optimistic: { ...pending.optimistic!, admitted: false }, + }; + view.rerender(node(structuredClone(value), [unadmitted, event])); + expect(view.container.querySelectorAll(".message-row.user")).toHaveLength(3); +}); + +it("keeps the running marker on the native turn while follow-up messages are still pending", () => { + const value = snapshot(); + value.selectedSession!.entries[1]!.message = { + role: "assistant", + content: "", + stopReason: "toolUse", + parts: [ + { type: "toolCall", id: "active-git", name: "bash", arguments: "{}" }, + ], + }; + value.runtime.activeTurn = { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "original", + epoch: 1, + }; + const live: LiveEntry[] = ["后续 A", "后续 B"].map((content, index) => ({ + key: `optimistic-${index}`, + message: { role: "user", content }, + optimistic: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: `queued-${index}`, + admitted: true, + afterEntryId: "old-answer", + }, + })); + const view = render(node(value, live)); + expect( + view.container.querySelector('[data-turn="1"] .turn-state.running'), + ).not.toBeNull(); + expect( + view.container.querySelector('[data-turn="2"] .turn-state.running'), + ).toBeNull(); + expect( + view.container.querySelector('[data-turn="3"] .turn-state.running'), + ).toBeNull(); +}); + +it("shows the selected background session's execution without mixing in the controller's messages", () => { + const value = snapshot(); + value.currentSessionId = "controller"; + value.runtime = { status: "idle", capabilities: {} }; + value.selectedExecution = { + sessionId: "s", + sessionPath: "/session.jsonl", + status: "running", + pendingFollowUps: 2, + liveTools: [], + liveToolsOmitted: 0, + activeTurn: { + sessionId: "s", + sessionPath: "/session.jsonl", + commandId: "background", + epoch: 1, + startedAt: Date.now(), + elapsedMs: 5000, + }, + }; + const view = render( + node(value, [ + { + key: "controller-live", + message: { + role: "assistant", + content: "Only the other Session should see this", + }, + }, + ]), + ); + expect(view.container.textContent).not.toContain( + "Only the other Session should see this", + ); + expect( + view.container.querySelector(".conversation-running")?.textContent, + ).toContain(i18n.t("backgroundSessionRunning")); + expect(view.container.textContent).toContain( + i18n.t("pendingFollowUpsHint", { count: 2 }), + ); + expect(view.getByRole("timer")).toBeTruthy(); + expect( + view.queryByRole("button", { name: i18n.t("editMessage") }), + ).toBeNull(); + + value.selectedExecution.sessionPath = "/different-copy.jsonl"; + view.rerender(node(structuredClone(value), [])); + expect(view.container.querySelector(".conversation-running")).toBeNull(); + expect(view.queryByRole("timer")).toBeNull(); +}); diff --git a/tests/web/protocol.test.ts b/tests/web/protocol.test.ts index 8723a6e6..8d923c05 100644 --- a/tests/web/protocol.test.ts +++ b/tests/web/protocol.test.ts @@ -7,6 +7,7 @@ import { WEB_MAX_MESSAGE_PARTS, WEB_MAX_SELECTED_TRANSCRIPT_BYTES, } from "../../web/protocol/types.ts"; +import { WEB_TURN_CHANGES_ENTRY } from "../../web/protocol/turn-changes.ts"; test("message projection does not create phantom text for detail-only messages", () => { const projected = projectMessage({ @@ -62,6 +63,7 @@ test("assistant errors are bounded and redact credentials before they reach the assert.ok(!projected.errorMessage?.includes("\u0000")); assert.ok((projected.errorMessage?.length ?? 0) < 600); assert.equal(projected.truncation?.text, true); + assert.equal(projected.truncation?.visibleText, undefined); }); test("message projection keeps text parts separated without phantom blank lines", () => { @@ -170,6 +172,96 @@ test("message projection bounds parts and reports exact omissions", () => { assert.equal(projected.parts?.length, WEB_MAX_MESSAGE_PARTS); assert.equal(projected.truncation?.partsOmitted, 7); assert.equal(projected.truncation?.truncated, true); + assert.equal(projected.truncation?.visibleText, true); +}); + +test("visible text recovery is limited to clipped user and assistant bodies", () => { + const longText = "x".repeat(13_000); + assert.equal( + projectMessage({ role: "user", content: longText }).truncation?.visibleText, + true, + ); + assert.equal( + projectMessage({ + role: "assistant", + content: [{ type: "text", text: longText }], + }).truncation?.visibleText, + true, + ); + assert.equal( + projectMessage({ role: "toolResult", content: longText }).truncation + ?.visibleText, + undefined, + ); + const thinking = projectMessage({ + role: "assistant", + content: [{ type: "thinking", thinking: longText }], + }); + assert.equal(thinking.truncation?.text, true); + assert.equal(thinking.truncation?.visibleText, undefined); + const argumentsOnly = projectMessage({ + role: "assistant", + content: [ + { type: "toolCall", name: "bash", arguments: { command: longText } }, + ], + }); + assert.equal(argumentsOnly.truncation?.visibleText, undefined); + const omittedText = projectMessage({ + role: "assistant", + content: [ + ...Array.from({ length: WEB_MAX_MESSAGE_PARTS }, () => ({ + type: "thinking", + thinking: "short", + })), + { type: "text", text: "later body" }, + ], + }); + assert.equal(omittedText.truncation?.visibleText, true); + const farOmittedText = projectMessage({ + role: "assistant", + content: [ + ...Array.from({ length: WEB_MAX_MESSAGE_PARTS * 2 }, () => ({ + type: "thinking", + thinking: "short", + })), + { type: "text", text: "later body" }, + ], + }); + assert.equal(farOmittedText.truncation?.visibleText, true); +}); + +test("turn changes project bounded summary without persisted diff contents", () => { + const projected = projectEntry({ + type: "custom", + id: "changes", + parentId: "answer", + timestamp: "2026-09-23T01:00:00.000Z", + customType: WEB_TURN_CHANGES_ENTRY, + data: { + version: 1, + sessionId: "session", + promptEntryId: "prompt", + state: "complete", + fileCount: 1, + additions: 1, + deletions: 1, + files: [ + { + path: "src/a.ts", + status: "modified", + diff: "sensitive patch", + diffTruncated: false, + additions: 1, + deletions: 1, + }, + ], + }, + }); + assert.equal(projected.turnChanges?.promptEntryId, "prompt"); + assert.deepEqual(projected.turnChanges?.files, [ + { path: "src/a.ts", status: "modified", additions: 1, deletions: 1 }, + ]); + assert.ok(!JSON.stringify(projected).includes("sensitive patch")); }); test("projection does not inspect parts or getters beyond its work budget", () => { diff --git a/tests/web/provider-error.spec.ts b/tests/web/provider-error.spec.ts index 6c4f61b0..321d2d98 100644 --- a/tests/web/provider-error.spec.ts +++ b/tests/web/provider-error.spec.ts @@ -23,6 +23,7 @@ function snapshot(message: ReturnType): WebSnapshot { cursor: 1, preferences: { theme: "system" }, currentSessionId: "session", + currentSessionPath: "/tmp/session.jsonl", sessions: [], workspaces: [], models: [], diff --git a/tests/web/provider-settings-page.spec.ts b/tests/web/provider-settings-page.spec.ts index 1b53e929..0825af49 100644 --- a/tests/web/provider-settings-page.spec.ts +++ b/tests/web/provider-settings-page.spec.ts @@ -194,10 +194,137 @@ function settingsFetcher() { if (path.includes("/api/settings/catalog")) { return reply(settingsPayload()); } + if (path.includes("/api/models/configuration")) + return reply({ revision: "revision", models: [] }); return providerReply(); }); } +it("supports arrow, Home and End navigation with one tabbable settings tab", async () => { + vi.stubGlobal("fetch", settingsFetcher()); + renderSettings(); + const general = screen.getByRole("tab", { name: i18n.t("generalSettings") }); + general.focus(); + fireEvent.keyDown(general, { key: "ArrowRight" }); + const modelsTab = screen.getByRole("tab", { name: i18n.t("modelSettings") }); + expect(modelsTab.getAttribute("aria-selected")).toBe("true"); + expect(document.activeElement).toBe(modelsTab); + fireEvent.keyDown(modelsTab, { key: "End" }); + const plugins = screen.getByRole("tab", { name: i18n.t("pluginsSettings") }); + expect(document.activeElement).toBe(plugins); + fireEvent.keyDown(plugins, { key: "Home" }); + expect(document.activeElement).toBe(general); + expect( + screen.getAllByRole("tab").filter((tab) => tab.tabIndex === 0), + ).toHaveLength(1); +}); + +it("preserves an unfinished model configuration when changing settings tabs", async () => { + vi.stubGlobal("fetch", settingsFetcher()); + renderSettings(); + fireEvent.click(screen.getByRole("tab", { name: i18n.t("modelSettings") })); + const address = await screen.findByRole("textbox", { + name: i18n.t("modelConfig_baseUrl"), + }); + await waitFor(() => + expect((address as HTMLInputElement).disabled).toBe(false), + ); + fireEvent.change(address, { target: { value: "http://localhost:12345/v1" } }); + fireEvent.click(screen.getByRole("tab", { name: i18n.t("skillsSettings") })); + fireEvent.click(screen.getByRole("tab", { name: i18n.t("modelSettings") })); + expect( + ( + screen.getByRole("textbox", { + name: i18n.t("modelConfig_baseUrl"), + }) as HTMLInputElement + ).value, + ).toBe("http://localhost:12345/v1"); +}); + +it("saves a write-only key directly to Pi without submitting a setup prompt", async () => { + const requests: Array<{ path: string; body: Record }> = []; + const fetcher = settingsFetcher(); + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + if (init?.method === "POST") { + requests.push({ + path: String(input), + body: JSON.parse(String(init.body)), + }); + return reply({ saved: true }); + } + return fetcher(input); + }), + ); + const configure = vi.fn(async () => true); + const refreshed = vi.fn(async () => true); + renderSettings({ + onConfigureOpenPi: configure, + onPreferencesChanged: refreshed, + }); + fireEvent.click(screen.getByRole("tab", { name: i18n.t("modelSettings") })); + await screen.findByText(i18n.t("credentialConfigured")); + fireEvent.change(screen.getByLabelText(i18n.t("provider")), { + target: { value: "deepseek" }, + }); + const input = screen.getByLabelText( + i18n.t("providerApiKey"), + ) as HTMLInputElement; + expect(input.type).toBe("password"); + expect(input.value).toBe(""); + fireEvent.change(input, { target: { value: "fixture-private-key" } }); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("saveProviderKey") }), + ); + await screen.findByText(i18n.t("providerKeySaved")); + expect(input.value).toBe(""); + expect(requests).toEqual([ + { + path: "/api/providers/api-key", + body: { + sessionId: "session-a", + provider: "deepseek", + apiKey: "fixture-private-key", + }, + }, + ]); + expect(configure).not.toHaveBeenCalled(); + expect(refreshed).toHaveBeenCalled(); +}); + +it("submits role and skill changes through the canonical setup entry", async () => { + vi.stubGlobal("fetch", settingsFetcher()); + const configure = vi.fn(async () => true); + renderSettings({ onConfigureOpenPi: configure }); + await screen.findByText(i18n.t("agentBehavior")); + fireEvent.click( + screen.getByRole("tab", { name: i18n.t("subagentsSettings") }), + ); + fireEvent.change(screen.getByLabelText(i18n.t("workflowConcurrencyLabel")), { + target: { value: "3" }, + }); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("configureViaSetup") }), + ); + await waitFor(() => + expect(configure).toHaveBeenCalledWith(expect.stringContaining("3")), + ); + fireEvent.click(screen.getByRole("tab", { name: i18n.t("skillsSettings") })); + fireEvent.change( + within(screen.getByRole("tabpanel")).getByLabelText( + i18n.t("setupConfigurationRequest"), + ), + { target: { value: "Configure the local skill" } }, + ); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("configureViaSetup") }), + ); + await waitFor(() => + expect(configure).toHaveBeenCalledWith("Configure the local skill"), + ); +}); + it("matches the pi-web settings shell and selects models through Pi", async () => { const fetcher = settingsFetcher(); const onSelectModel = vi.fn(); @@ -227,14 +354,62 @@ it("matches the pi-web settings shell and selects models through Pi", async () = ).toBeGreaterThan(0); fireEvent.click(screen.getByRole("tab", { name: i18n.t("generalSettings") })); fireEvent.click(screen.getByRole("tab", { name: i18n.t("modelSettings") })); - await waitFor(() => expect(fetcher).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(fetcher).toHaveBeenCalledTimes(3)); fireEvent.click(screen.getByRole("button", { name: /DeepSeek V4/u })); expect((await screen.findAllByText("DeepSeek")).length).toBeGreaterThan(0); expect(screen.getByText(i18n.t("credentialMissing"))).toBeTruthy(); fireEvent.click(screen.getByRole("button", { name: i18n.t("useThisModel") })); expect(onSelectModel).toHaveBeenCalledWith("deepseek/deepseek-v4"); expect(view.container.querySelector(".settings-model-sidebar")).toBeTruthy(); - expect(fetcher).toHaveBeenCalledTimes(2); + expect(fetcher).toHaveBeenCalledTimes(3); +}); + +it("keeps configured models editable before their credentials make them available", async () => { + const fallback = settingsFetcher(); + vi.stubGlobal( + "fetch", + vi.fn(async (input: RequestInfo | URL) => { + if (String(input).includes("/api/models/configuration")) + return reply({ + revision: "configuration-only", + models: [ + { + provider: "unconnected", + id: "model", + name: "Configured before login", + baseUrl: "http://localhost:12345/v1", + api: "openai-responses", + reasoning: true, + contextWindow: 128000, + maxTokens: 4096, + }, + ], + }); + return fallback(input); + }), + ); + renderSettings(); + fireEvent.click(screen.getByRole("tab", { name: i18n.t("modelSettings") })); + fireEvent.click( + await screen.findByRole("button", { name: /Configured before login/ }), + ); + await waitFor(() => + expect( + screen.getByRole("textbox", { + name: i18n.t("modelConfig_name"), + }).value, + ).toBe("Configured before login"), + ); + expect( + screen.getByRole("button", { + name: i18n.t("unavailable"), + }).disabled, + ).toBe(true); + expect( + screen.getByRole("button", { + name: i18n.t("saveModelConfiguration"), + }).disabled, + ).toBe(false); }); it("shows canonical General state and routes real setup/runtime actions", async () => { @@ -347,6 +522,105 @@ it("keeps an accepted OpenPI setup request inside the settings dialog", async () expect(screen.getByText(i18n.t("setupRequestAccepted"))).toBeTruthy(); }); +it("explains why appearance changes are unavailable during an active turn", async () => { + vi.stubGlobal("fetch", settingsFetcher()); + const configure = vi.fn(async () => true); + const view = renderSettings({ + setupBusy: true, + onConfigureOpenPi: configure, + }); + await screen.findByText(i18n.t("agentBehavior")); + expect(screen.getByText(i18n.t("settingsSetupBusyHint"))).toBeTruthy(); + const theme = screen.getByRole("radio", { + name: i18n.t("themeDark"), + }); + expect(theme.disabled).toBe(true); + expect( + screen.getByRole("switch", { + name: i18n.t("expandThinkingByDefault"), + }).disabled, + ).toBe(true); + fireEvent.click(theme); + expect(configure).not.toHaveBeenCalled(); + view.rerender( + settingsElement({ setupBusy: false, onConfigureOpenPi: configure }), + ); + expect(theme.disabled).toBe(false); + expect(screen.queryByText(i18n.t("settingsSetupBusyHint"))).toBeNull(); +}); + +it("restores chat appearance through setup and rolls back a rejected request", async () => { + const payload = settingsPayload(); + payload.setup.ui.webChatWidth = 1200; + payload.setup.ui.webChatFontSize = 18; + vi.stubGlobal( + "fetch", + vi.fn(async () => reply(payload)), + ); + const configure = vi.fn(async () => false); + renderSettings({ onConfigureOpenPi: configure }); + await screen.findByText(i18n.t("agentBehavior")); + const resetWidth = screen.getByRole("button", { + name: i18n.t("resetChatContentWidth"), + }); + await waitFor(() => expect(resetWidth.disabled).toBe(false)); + fireEvent.click(resetWidth); + await waitFor(() => + expect(configure).toHaveBeenCalledWith( + i18n.t("setupRequestSetChatWidth", { value: 820 }), + ), + ); + await waitFor(() => + expect( + screen + .getByRole("slider", { name: i18n.t("chatContentWidth") }) + .getAttribute("aria-valuenow"), + ).toBe("1200"), + ); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("resetChatFontSize") }), + ); + await waitFor(() => + expect(configure).toHaveBeenCalledWith( + i18n.t("setupRequestSetChatFontSize", { value: 14 }), + ), + ); + await waitFor(() => + expect( + screen + .getByRole("slider", { name: i18n.t("chatFontSize") }) + .getAttribute("aria-valuenow"), + ).toBe("18"), + ); +}); + +it("explains unresolved message admission instead of asking the user to keep waiting", async () => { + vi.stubGlobal("fetch", settingsFetcher()); + renderSettings({ setupBlockedReason: i18n.t("setupResolveAdmission") }); + await screen.findByText(i18n.t("agentBehavior")); + expect(screen.getByText(i18n.t("setupResolveAdmission"))).toBeTruthy(); + expect(screen.queryByText(i18n.t("settingsSetupBusyHint"))).toBeNull(); + expect( + screen.getByRole("radio", { name: i18n.t("themeDark") }) + .disabled, + ).toBe(true); +}); + +it("switches sections through the compact settings picker", async () => { + vi.stubGlobal("fetch", settingsFetcher()); + renderSettings(); + fireEvent.change( + screen.getByRole("combobox", { name: i18n.t("settingsNavigation") }), + { target: { value: "plugins" } }, + ); + expect(screen.getByRole("tabpanel").id).toBe("settings-panel-plugins"); + expect( + screen + .getByRole("tab", { name: i18n.t("pluginsSettings") }) + .getAttribute("aria-selected"), + ).toBe("true"); +}); + it("waits for a running setup request to settle before refreshing", async () => { const fetcher = settingsFetcher(); const onConfigureOpenPi = vi.fn(async () => true); diff --git a/tests/web/questions-provider.e2e.ts b/tests/web/questions-provider.e2e.ts index 2b5f2977..94ffaa34 100644 --- a/tests/web/questions-provider.e2e.ts +++ b/tests/web/questions-provider.e2e.ts @@ -213,8 +213,8 @@ for (const theme of ["light", "dark"] as const) { const next = JSON.stringify(provider.requests[1]!.body); expect(next).toContain("沿用当前主题"); expect(next).toContain("桌面和手机都要验证"); - // #561 hides Setup episodes from the main transcript. Completion must - // still be recorded by Pi, not inferred from the card disappearing. + // Completion is recorded by Pi, not inferred from the card disappearing. + // The user's original setup request remains visible exactly once. await expect .poll(async () => { const snapshot = await ( @@ -236,7 +236,7 @@ for (const theme of ["light", "dark"] as const) { page.getByText("/openpi-setup 请先询问我的偏好,不要修改配置。", { exact: true, }), - ).toHaveCount(0); + ).toHaveCount(1); } finally { await provider.close(); await rm(workspace, { recursive: true, force: true }); diff --git a/tests/web/review-panel.spec.ts b/tests/web/review-panel.spec.ts index 039a1f67..2d28d321 100644 --- a/tests/web/review-panel.spec.ts +++ b/tests/web/review-panel.spec.ts @@ -1,17 +1,60 @@ // @vitest-environment jsdom -import { cleanup, fireEvent, render, screen } from "@testing-library/react"; -import { createElement, type ReactElement } from "react"; +import { + act, + cleanup, + fireEvent, + render, + screen, +} from "@testing-library/react"; +import { createElement, Fragment, type ReactElement } from "react"; import { I18nextProvider } from "react-i18next"; import { afterEach, expect, it, vi } from "vitest"; import type { WebGitReviewSnapshot } from "../../web/protocol/types.ts"; import { parseDiffRows } from "../../web/ui/src/features/review/DiffCodePreview.tsx"; import { ReviewPanel } from "../../web/ui/src/features/review/ReviewPanel.tsx"; -import { SessionChangesPopover } from "../../web/ui/src/features/review/SessionChangesPopover.tsx"; import { i18n } from "../../web/ui/src/i18n.ts"; afterEach(cleanup); +it("does not reload the selected diff for an unchanged snapshot revision", async () => { + const readFile = vi.fn(async () => snapshot.files[0]); + const makePanel = (revision: string) => + withI18n( + createElement(ReviewPanel, { + review: { + result: { + ok: true, + snapshot: { + ...snapshot, + revision, + files: snapshot.files.map((file) => ({ + ...file, + diff: "", + diffLoaded: false, + })), + }, + }, + loading: false, + error: null, + refresh: async () => {}, + readFile, + }, + initialFilePath: snapshot.files[0]!.path, + onClose: () => {}, + }), + ); + const { rerender } = render(makePanel("unchanged")); + await act(async () => {}); + expect(readFile).toHaveBeenCalledTimes(1); + rerender(makePanel("unchanged")); + await act(async () => {}); + expect(readFile).toHaveBeenCalledTimes(1); + rerender(makePanel("changed")); + await act(async () => {}); + expect(readFile).toHaveBeenCalledTimes(2); +}); + const snapshot: WebGitReviewSnapshot = { repositoryRoot: "/workspace", currentBranch: "feature/review", @@ -60,38 +103,6 @@ it("does not invent a numbered context row from a trailing diff newline", () => ]); }); -it("shows a Codex-style session change trigger backed by Git files", () => { - const onOpenReview = vi.fn(); - const { container } = render( - withI18n(createElement(SessionChangesPopover, { snapshot, onOpenReview })), - ); - const trigger = screen.getByRole("button", { - name: /2 files changed/u, - }); - expect(trigger.textContent).toContain("+8"); - expect(trigger.textContent).toContain("-3"); - vi.spyOn(trigger, "getBoundingClientRect").mockReturnValue({ - top: 280, - } as DOMRect); - fireEvent.click(trigger); - const popover = screen.getByRole("dialog", { name: "Changes" }); - expect(popover.style.maxHeight).toBe("260px"); - const file = screen.getByRole("button", { - name: /very-long-file-name\.tsx/u, - }); - fireEvent.click(file); - expect(onOpenReview).toHaveBeenCalledTimes(1); - expect(onOpenReview).toHaveBeenCalledWith( - "src/features/review/very-long-file-name.tsx", - trigger, - ); - expect(container.querySelector(".session-changes-popover")).toBeNull(); - fireEvent.click(trigger); - fireEvent.keyDown(document, { key: "Escape" }); - expect(container.querySelector(".session-changes-popover")).toBeNull(); - expect(document.activeElement).toBe(trigger); -}); - it("switches from the changed-file list to a separate full-height diff view", () => { const onClose = vi.fn(); const refresh = vi.fn(async () => {}); @@ -157,6 +168,35 @@ it("opens a selected popover file directly in the dedicated preview", () => { ).toContain("export const value"); }); +it("does not steal composer focus when the selected diff refreshes", () => { + const node = (data: WebGitReviewSnapshot) => + withI18n( + createElement( + Fragment, + null, + createElement("textarea", { "aria-label": "draft" }), + createElement(ReviewPanel, { + review: { + result: { ok: true, snapshot: data }, + loading: false, + error: null, + refresh: vi.fn(async () => {}), + }, + initialFilePath: "new-file.ts", + onClose: vi.fn(), + embedded: true, + }), + ), + ); + const { rerender } = render(node(snapshot)); + const input = screen.getByRole("textbox", { name: "draft" }); + input.focus(); + rerender( + node({ ...snapshot, files: snapshot.files.map((file) => ({ ...file })) }), + ); + expect(document.activeElement).toBe(input); +}); + it("distinguishes Git read failures from an empty repository state", () => { const refresh = vi.fn(async () => {}); render( @@ -178,3 +218,64 @@ it("distinguishes Git read failures from an empty repository state", () => { fireEvent.click(screen.getByRole("button", { name: "Retry" })); expect(refresh).toHaveBeenCalledTimes(1); }); + +it("keeps hidden diff reads and focus dormant, then cancels a read when hidden again", async () => { + let finish!: (file: (typeof snapshot.files)[number]) => void; + const readFile = vi.fn( + (_path: string, _signal: AbortSignal) => + new Promise<(typeof snapshot.files)[number]>((resolve) => { + finish = resolve; + }), + ); + const node = (active: boolean, selectedPath: string) => + withI18n( + createElement( + Fragment, + null, + createElement("textarea", { "aria-label": "draft" }), + createElement(ReviewPanel, { + active, + embedded: true, + initialFilePath: selectedPath, + onClose: () => {}, + review: { + result: { + ok: true, + snapshot: { + ...snapshot, + files: snapshot.files.map((file) => ({ + ...file, + diff: "", + diffLoaded: false, + })), + }, + }, + loading: false, + error: null, + refresh: async () => {}, + readFile, + }, + }), + ), + ); + const { rerender } = render(node(false, snapshot.files[0]!.path)); + const input = screen.getByRole("textbox", { name: "draft" }); + input.focus(); + rerender(node(false, "new-file.ts")); + expect(readFile).not.toHaveBeenCalled(); + expect(document.activeElement).toBe(input); + rerender(node(true, "new-file.ts")); + expect(readFile).toHaveBeenCalledTimes(1); + expect(document.activeElement).toBe( + screen.getByRole("region", { name: /new-file\.ts/u }), + ); + const signal = readFile.mock.calls[0]![1]; + rerender(node(false, "new-file.ts")); + input.focus(); + expect(signal.aborted).toBe(true); + await act(async () => finish(snapshot.files[1]!)); + expect(document.activeElement).toBe(input); + expect(screen.queryByRole("figure", { name: "Change diff" })).toBeNull(); + rerender(node(true, "new-file.ts")); + expect(readFile).toHaveBeenCalledTimes(2); +}); diff --git a/tests/web/session-execution.test.ts b/tests/web/session-execution.test.ts new file mode 100644 index 00000000..27caf2a2 --- /dev/null +++ b/tests/web/session-execution.test.ts @@ -0,0 +1,416 @@ +import assert from "node:assert/strict"; +import { mkdtemp, mkdir, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test, { type TestContext } from "node:test"; +import { Type } from "typebox"; +import { + fauxAssistantMessage, + fauxProvider, + fauxToolCall, +} from "@earendil-works/pi-ai"; +import { + createAgentSessionFromServices, + createAgentSessionRuntime, + DefaultResourceLoader, + ModelRuntime, + SessionManager, + SettingsManager, + type AgentSessionRuntime, + type CreateAgentSessionRuntimeFactory, +} from "@earendil-works/pi-coding-agent"; +import { PiWebRuntime } from "../../web/runtime/pi-runtime.ts"; +import { PiWebAdapter } from "../../web/adapter/pi-adapter.ts"; +import type { WebRuntimeEvent } from "../../web/runtime/types.ts"; +import { WEB_TURN_TIMING_ENTRY } from "../../web/protocol/turn-timing.ts"; + +function gate() { + let enter!: () => void; + let release!: () => void; + return { + entered: new Promise((resolve) => { + enter = resolve; + }), + released: new Promise((resolve) => { + release = resolve; + }), + enter: () => enter(), + release: () => release(), + }; +} + +async function fixture(t: TestContext) { + const directory = await mkdtemp(join(tmpdir(), "openpi-session-execution-")); + const gates = [gate(), gate()]; + const makeRuntime = async (name: string) => { + const cwd = join(directory, name); + const agentDir = join(cwd, "agent"); + await mkdir(agentDir, { recursive: true }); + const provider = `execution-fixture-${name}`; + const faux = fauxProvider({ + api: provider, + provider, + models: [{ id: "fixture", name: "Fixture", reasoning: false }], + }); + faux.setResponses( + name === "A" + ? [ + fauxAssistantMessage( + fauxToolCall("hold_fixture", { phase: 0 }, { id: "hold-a-0" }), + { stopReason: "toolUse" }, + ), + fauxAssistantMessage( + fauxToolCall("hold_fixture", { phase: 1 }, { id: "hold-a-1" }), + { stopReason: "toolUse" }, + ), + fauxAssistantMessage("A finished"), + fauxAssistantMessage("A follow-up finished"), + fauxAssistantMessage("A final follow-up finished"), + ] + : [fauxAssistantMessage("B finished")], + ); + const modelRuntime = await ModelRuntime.create({ + authPath: join(agentDir, "auth.json"), + modelsPath: join(agentDir, "models.json"), + }); + modelRuntime.registerNativeProvider(faux.provider); + await modelRuntime.setRuntimeApiKey(provider, "fixture-key"); + const settingsManager = SettingsManager.inMemory(undefined, { + projectTrusted: false, + }); + const loader = new DefaultResourceLoader({ + cwd, + agentDir, + settingsManager, + noExtensions: true, + noSkills: true, + noThemes: true, + noPromptTemplates: true, + noContextFiles: true, + }); + await loader.reload(); + const services = { + cwd, + agentDir, + settingsManager, + modelRuntime, + resourceLoader: loader, + diagnostics: [], + }; + const create: CreateAgentSessionRuntimeFactory = async (options) => { + const result = await createAgentSessionFromServices({ + services, + sessionManager: options.sessionManager, + model: faux.getModel(), + tools: ["read", "hold_fixture"], + customTools: [ + { + name: "hold_fixture", + label: "Hold fixture", + description: "Wait for an explicit test gate", + parameters: Type.Object({ phase: Type.Number() }), + async execute(_id, args, signal, onUpdate) { + if ( + !args || + typeof args !== "object" || + !("phase" in args) || + typeof args.phase !== "number" + ) + throw new Error("Invalid fixture phase"); + const current = gates[args.phase]; + if (!current) throw new Error("Unknown fixture phase"); + current.enter(); + if (args.phase === 1) + for (let index = 0; index < 100; index++) + onUpdate?.({ + content: [{ type: "text", text: `Partial ${index}` }], + details: {}, + }); + await Promise.race([ + current.released, + new Promise((resolve) => { + if (signal?.aborted) resolve(); + else + signal?.addEventListener("abort", () => resolve(), { + once: true, + }); + }), + ]); + return { + content: [{ type: "text", text: "Gate released" }], + details: {}, + }; + }, + }, + ], + }); + return { ...result, services, diagnostics: [] }; + }; + return createAgentSessionRuntime(create, { + cwd, + agentDir, + sessionManager: SessionManager.create(cwd, join(directory, "sessions")), + }); + }; + const a = await makeRuntime("A"); + const b = await makeRuntime("B"); + const web = Reflect.construct(PiWebRuntime, [ + a, + join(directory, "sessions"), + { timeoutMs: 10000, release: async () => {} }, + { release: async () => {} }, + true, + ]) as PiWebRuntime; + const internal = web as unknown as { + startRuntimeSession(): Promise; + replaceRuntime(runtime: AgentSessionRuntime): Promise; + }; + await internal.startRuntimeSession(); + let bOwned = false; + t.after(async () => { + for (const item of gates) item.release(); + await web.dispose(); + if (!bOwned) await b.dispose(); + await rm(directory, { recursive: true, force: true }); + }); + const events: WebRuntimeEvent[] = []; + web.subscribe((event) => events.push(event)); + return { + web, + a, + b, + gates, + events, + async switchToB() { + await internal.replaceRuntime(b); + bOwned = true; + }, + }; +} + +test("a real Pi Session continues tool progress and queued follow-ups after another Session becomes current", { + timeout: 10000, +}, async (t) => { + const { web, a, b, gates, events, switchToB } = await fixture(t); + const sid = a.session.sessionManager.getSessionId(); + const path = a.session.sessionManager.getSessionFile()!; + await web.sendPrompt("Run the fixture", { + commandId: "run-a", + expectedSessionId: sid, + }); + await gates[0]!.entered; + await web.sendPrompt("Follow up one", { + commandId: "follow-up-1", + expectedSessionId: sid, + }); + await web.sendPrompt("Follow up two", { + commandId: "follow-up-2", + expectedSessionId: sid, + }); + assert.equal(web.getSessionExecution(sid, path).pendingFollowUps, 2); + assert.deepEqual(web.getSessionExecution(sid, path).queuedMessages, [ + "Follow up one", + "Follow up two", + ]); + await switchToB(); + const since = events.length; + gates[0]!.release(); + await gates[1]!.entered; + await new Promise((resolve) => setTimeout(resolve, 150)); + assert.equal(a.session.isStreaming, true); + assert.equal( + web.sessionManager.getSessionId(), + b.session.sessionManager.getSessionId(), + ); + assert.ok( + events + .slice(since) + .some( + (event) => + event.type === "session_progress" && event.detail?.sessionId === sid, + ), + "background native tool boundaries must report progress before the run settles", + ); + assert.equal( + events.slice(since).filter((event) => event.type === "session_progress") + .length, + 1, + "partial tool output and model tokens must not trigger snapshot storms", + ); + assert.equal( + events.slice(since).some((event) => event.type === "message_update"), + false, + ); + const execution = web.getSessionExecution(sid, path); + assert.equal( + web.getSessionManagerForRead(sid, path), + a.session.sessionManager, + ); + assert.equal(execution.status, "running"); + assert.equal( + execution.pendingFollowUps, + a.session.getFollowUpMessages().length, + ); + assert.deepEqual(execution.queuedMessages, [ + "Follow up one", + "Follow up two", + ]); + assert.deepEqual( + execution.liveTools.map((item) => item.call.id), + ["hold-a-1"], + ); + assert.equal(execution.activeTurn?.commandId, "run-a"); + const snapshot = await new PiWebAdapter(web).getSnapshot(path); + assert.equal( + snapshot.currentSessionId, + b.session.sessionManager.getSessionId(), + ); + assert.equal(snapshot.runtime.status, "idle"); + assert.equal(snapshot.selectedExecution?.sessionId, sid); + assert.equal(snapshot.selectedExecution?.sessionPath, path); + assert.equal(snapshot.selectedExecution?.status, "running"); + assert.equal( + snapshot.selectedExecution?.pendingFollowUps, + a.session.getFollowUpMessages().length, + ); + assert.deepEqual(snapshot.selectedExecution?.queuedMessages, [ + "Follow up one", + "Follow up two", + ]); + assert.equal( + web.getSessionExecution(sid, b.session.sessionManager.getSessionFile()!) + .status, + "unknown", + ); + assert.equal( + web.getSessionManagerForRead( + sid, + b.session.sessionManager.getSessionFile()!, + ), + undefined, + ); + assert.equal( + web.getSessionExecution(sid, b.session.sessionManager.getSessionFile()!) + .pendingFollowUps, + undefined, + ); + const current = web.getSessionExecution( + b.session.sessionManager.getSessionId(), + b.session.sessionManager.getSessionFile()!, + ); + assert.equal(current.status, "idle"); + assert.equal(current.pendingFollowUps, 0); + assert.deepEqual(current.queuedMessages, []); + assert.deepEqual(current.liveTools, []); + assert.equal( + events.slice(since).some((event) => event.type === "tool_execution_start"), + false, + "background events must not replace the current Session's live tool channel", + ); +}); + +test("returning to a real running Pi Session preserves the original cancel identity", { + timeout: 10000, +}, async (t) => { + const { web, a, gates, switchToB } = await fixture(t); + const sid = a.session.sessionManager.getSessionId(); + await web.sendPrompt("Run fixture", { + commandId: "run-a", + expectedSessionId: sid, + }); + await gates[0]!.entered; + const original = web.getActiveTurn()!; + await switchToB(); + await web.switchSession(a.session.sessionManager.getSessionFile()!); + assert.equal(web.getActiveTurn()?.commandId, original.commandId); + assert.equal(web.getActiveTurn()?.epoch, original.epoch); + assert.equal(web.getActiveTurn()?.startedAt, original.startedAt); + const nativeAbort = a.session.abort.bind(a.session); + let aborts = 0; + t.mock.method(a.session, "abort", async () => { + aborts++; + return nativeAbort(); + }); + const cancelled = await web.cancelTurn(original); + assert.equal( + aborts, + 1, + "the restored identity must reach the owning native abort", + ); + assert.equal(a.session.isIdle, true); + // The faux provider reports the aborted stream as an error. Preserve the + // actual terminal classification instead of treating abort() as evidence. + assert.equal(cancelled.state, "failed"); + assert.match(cancelled.error ?? "", /active turn failed/); +}); + +test("background settlement records timing in the owning Pi Session, never the currently selected one", { + timeout: 10000, +}, async (t) => { + const { web, a, b, gates, events, switchToB } = await fixture(t); + await web.sendPrompt("Run fixture", { + commandId: "run-a", + expectedSessionId: a.session.sessionManager.getSessionId(), + }); + await gates[0]!.entered; + await switchToB(); + const since = events.length; + for (const item of gates) item.release(); + await a.session.waitForIdle(); + const timing = a.session.sessionManager + .getEntries() + .filter( + (entry) => + entry.type === "custom" && entry.customType === WEB_TURN_TIMING_ENTRY, + ); + assert.equal(timing.length, 1); + assert.equal( + b.session.sessionManager + .getEntries() + .some( + (entry) => + entry.type === "custom" && entry.customType === WEB_TURN_TIMING_ENTRY, + ), + false, + ); + assert.equal( + events.slice(since).some((event) => event.type === "turn_settled"), + false, + ); + await new Promise((resolve) => setImmediate(resolve)); + assert.equal( + web.getSessionManagerForRead( + a.session.sessionManager.getSessionId(), + a.session.sessionManager.getSessionFile()!, + ), + undefined, + ); +}); + +test("promoting a background Session cancels its scheduled progress notification", { + timeout: 10000, +}, async (t) => { + const { web, a, gates, events, switchToB } = await fixture(t); + const sid = a.session.sessionManager.getSessionId(); + await web.sendPrompt("Run fixture", { + commandId: "run-a", + expectedSessionId: sid, + }); + await gates[0]!.entered; + await switchToB(); + gates[0]!.release(); + await gates[1]!.entered; + await web.switchSession(a.session.sessionManager.getSessionFile()!); + const since = events.length; + await new Promise((resolve) => setTimeout(resolve, 150)); + assert.equal( + events + .slice(since) + .some( + (event) => + event.type === "session_progress" && event.detail?.sessionId === sid, + ), + false, + ); + assert.equal(web.getActiveTurn()?.commandId, "run-a"); +}); diff --git a/tests/web/session-history.e2e.ts b/tests/web/session-history.e2e.ts new file mode 100644 index 00000000..985f032b --- /dev/null +++ b/tests/web/session-history.e2e.ts @@ -0,0 +1,302 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { SessionManager } from "@earendil-works/pi-coding-agent"; +import { expect, test } from "@playwright/test"; +import { WebHost } from "../../web/host/web-host.ts"; +import type { WebRuntimeController } from "../../web/runtime/types.ts"; +import { projectWebModelSearch } from "../../web/runtime/model-discovery.ts"; + +const usage = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, +}; +async function historyFixture() { + const cwd = await mkdtemp(join(tmpdir(), "openpi-history-browser-")); + const manager = SessionManager.inMemory(cwd); + const root = manager.appendMessage({ + role: "user", + content: "Shared fixture root", + timestamp: 1, + }); + manager.appendMessage({ + role: "user", + content: "Original question unique to the old branch", + timestamp: 2, + }); + const assistant = (text: string) => + manager.appendMessage({ + role: "assistant", + content: [{ type: "text", text }], + api: "openai-responses", + provider: "fixture", + model: "fixture", + usage, + stopReason: "stop", + timestamp: Date.now(), + }); + for (let index = 0; index < 130; index++) { + manager.appendMessage({ + role: "assistant", + content: [ + { type: "text", text: `History progress ${index}.` }, + { + type: "toolCall", + id: `call-${index}`, + name: "bash", + arguments: { command: `printf fixture-${index}` }, + }, + ], + api: "openai-responses", + provider: "fixture", + model: "fixture", + usage, + stopReason: "toolUse", + timestamp: Date.now(), + }); + manager.appendMessage({ + role: "toolResult", + toolName: "bash", + toolCallId: `call-${index}`, + content: [{ type: "text", text: `Fixture result ${index}` }], + isError: false, + timestamp: Date.now(), + }); + } + assistant("Latest fixture answer."); + const runtime: WebRuntimeController = { + cwd, + workspaceSelected: true, + sessionDirectory: cwd, + sessionManager: manager, + isIdle: () => true, + getActiveTurn: () => undefined, + listModels: () => [], + searchModels: (query, limit) => projectWebModelSearch([], query, limit), + setModel: async () => { + throw new Error("unused"); + }, + newSession: async () => ({ + cancelled: true, + sessionId: manager.getSessionId(), + }), + switchSession: async () => ({ cancelled: true }), + cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), + subscribe: () => () => {}, + dispose: async () => {}, + sendPrompt: async (content) => { + manager.appendMessage({ role: "user", content, timestamp: Date.now() }); + host.publish("message_end", { + sessionId: manager.getSessionId(), + messageKey: "new-user", + message: { role: "user", content }, + }); + return { pendingFollowUps: 0 }; + }, + }; + const host = new WebHost({ runtime }); + await host.start(); + return { + host, + manager, + root, + assistant, + async close() { + await host.stop(); + await rm(cwd, { recursive: true, force: true }); + }, + }; +} + +test("older native history restores the user's question without moving the visible reading anchor", async ({ + browser, +}, testInfo) => { + const fixture = await historyFixture(); + const context = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + locale: "zh-CN", + }); + const page = await context.newPage(); + try { + await page.goto(fixture.host.origin); + const conversation = page.locator(".conversation"); + await expect(conversation).toBeVisible(); + await expect(conversation.locator(".message-row.user")).toHaveCount(0); + await conversation.evaluate((element) => + element.scrollTo({ top: 0, behavior: "instant" }), + ); + const load = page.getByRole("button", { + name: "加载更早的消息", + exact: true, + }); + await expect(load).toBeVisible(); + const anchor = conversation + .locator(".message-row.assistant.response") + .first(); + const key = await anchor.getAttribute("data-history-entry"); + const before = await anchor.boundingBox(); + await load.click(); + await expect( + conversation.getByText("Original question unique to the old branch", { + exact: true, + }), + ).toHaveCount(1); + const anchored = conversation.locator(`[data-history-entry="${key}"]`); + await expect + .poll(async () => Math.abs((await anchored.boundingBox())!.y - before!.y)) + .toBeLessThanOrEqual(2); + const stableScroll = await conversation.evaluate( + (element) => element.scrollTop, + ); + expect(stableScroll).toBeGreaterThan(0); + fixture.host.publish("message_update", { + sessionId: fixture.manager.getSessionId(), + messageKey: "live-answer", + message: { + role: "assistant", + content: "A new streamed answer is growing.", + }, + }); + await expect( + conversation.getByText("A new streamed answer is growing.", { + exact: true, + }), + ).toHaveCount(1); + expect( + Math.abs( + (await conversation.evaluate((element) => element.scrollTop)) - + stableScroll, + ), + ).toBeLessThanOrEqual(2); + const newLeaf = fixture.assistant("A new streamed answer is growing."); + const refresh = page.waitForResponse(async (response) => { + const url = new URL(response.url()); + return ( + url.pathname === "/api/snapshot" && + url.searchParams.has("historyAnchor") && + response.ok() && + (await response.json()).selectedSession?.history?.leafEntryId === + newLeaf + ); + }); + fixture.host.publish("message_end", { + sessionId: fixture.manager.getSessionId(), + messageKey: "live-answer", + message: { + role: "assistant", + content: "A new streamed answer is growing.", + }, + }); + const refreshed = await (await refresh).json(); + expect(refreshed.selectedSession.history.anchorOnBranch).toBe(true); + await expect + .poll(() => conversation.locator(".message-row.user").count()) + .toBe(2); + await conversation.evaluate((element) => + element.scrollTo({ top: 0, behavior: "instant" }), + ); + await expect( + page.getByRole("button", { name: "加载更早的消息" }), + ).toHaveCount(0); + await expect( + conversation.getByText("Original question unique to the old branch", { + exact: true, + }), + ).toBeInViewport(); + await page.screenshot({ + path: testInfo.outputPath("history-original-question-restored.png"), + }); + const input = page.getByRole("textbox", { name: "描述任务" }); + await input.fill("New user request while reading old history"); + await page.getByRole("button", { name: "发送", exact: true }).click(); + await expect(input).toHaveValue(""); + await expect( + conversation.getByText("New user request while reading old history", { + exact: true, + }), + ).toBeInViewport(); + } finally { + await context.close(); + await fixture.close(); + } +}); + +test("a late real history page cannot enter a different native branch", async ({ + browser, +}) => { + const fixture = await historyFixture(); + const context = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + locale: "zh-CN", + }); + const page = await context.newPage(); + let release!: () => void; + const gate = new Promise((resolve) => { + release = resolve; + }); + let ready!: () => void; + const read = new Promise((resolve) => { + ready = resolve; + }); + try { + await page.route("**/api/session/history?**", async (route) => { + const response = await route.fetch(); + const payload = await response.json(); + expect( + payload.session.entries.some( + (entry: { message?: { content?: string } }) => + entry.message?.content === + "Original question unique to the old branch", + ), + ).toBe(true); + ready(); + await gate; + await route.fulfill({ response }).catch(() => {}); + }); + await page.goto(fixture.host.origin); + const conversation = page.locator(".conversation"); + await expect(conversation).toBeVisible(); + await conversation.evaluate((element) => + element.scrollTo({ top: 0, behavior: "instant" }), + ); + await page + .getByRole("button", { name: "加载更早的消息", exact: true }) + .click(); + await read; + fixture.manager.branch(fixture.root); + fixture.manager.appendMessage({ + role: "user", + content: "New branch question", + timestamp: Date.now(), + }); + fixture.assistant("New branch answer"); + fixture.host.publish("session_progress", { + sessionId: fixture.manager.getSessionId(), + }); + await expect( + conversation.getByText("New branch question", { exact: true }), + ).toBeVisible(); + release(); + await expect( + conversation.getByText("Original question unique to the old branch", { + exact: true, + }), + ).toHaveCount(0); + await expect( + conversation.getByText("New branch answer", { exact: true }), + ).toBeVisible(); + await expect( + page.getByText("会话分支已更改,已切换到当前分支的历史。", { + exact: true, + }), + ).toBeVisible(); + } finally { + release(); + await context.close(); + await fixture.close(); + } +}); diff --git a/tests/web/session-history.spec.ts b/tests/web/session-history.spec.ts new file mode 100644 index 00000000..41c73593 --- /dev/null +++ b/tests/web/session-history.spec.ts @@ -0,0 +1,332 @@ +// @vitest-environment jsdom +import { act, cleanup, renderHook } from "@testing-library/react"; +import { afterEach, expect, it, vi } from "vitest"; +import { + jsonByteLength, + type WebSessionHistoryPage, + type WebSessionProjection, +} from "../../web/protocol/types.ts"; +import { useSessionHistory } from "../../web/ui/src/features/transcript/use-session-history.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); +const entry = (index: number) => ({ + type: "message" as const, + id: `e${index}`, + parentId: index ? `e${index - 1}` : null, + timestamp: new Date(index).toISOString(), + message: { + role: index % 10 === 0 ? "user" : "assistant", + content: `Entry ${index}`, + }, +}); +function session( + from = 2, + through = 3, + overrides: Partial = {}, +): WebSessionProjection { + const entries = Array.from({ length: through - from + 1 }, (_, index) => + entry(from + index), + ); + return { + id: "s", + path: "/sessions/s.jsonl", + cwd: "/workspace", + entries, + bytes: jsonByteLength(entries), + truncation: { + truncated: from > 0, + entriesOmitted: from, + messagesTruncated: 0, + messagePartsOmitted: 0, + maxBytes: 2 * 1024 * 1024, + }, + history: { + leafEntryId: `e${through}`, + beforeEntryId: from > 0 ? `e${from}` : null, + }, + ...overrides, + }; +} +function page( + from = 0, + through = 1, + anchor = "e3", + before = "e2", +): WebSessionHistoryPage { + return { + ...session(from, through), + anchorEntryId: anchor, + requestedBeforeEntryId: before, + history: { + leafEntryId: anchor, + beforeEntryId: from > 0 ? `e${from}` : null, + anchorEntryId: anchor, + anchorOnBranch: true, + }, + }; +} +function setup(selected = session(), callbacks = {}) { + const onAnchorChange = vi.fn(); + const beforePrepend = vi.fn(); + const onRefresh = vi.fn(async () => true); + return { + ...renderHook( + ({ selected }) => + useSessionHistory(selected, { + onAnchorChange, + beforePrepend, + onRefresh, + ...callbacks, + }), + { initialProps: { selected } }, + ), + onAnchorChange, + beforePrepend, + onRefresh, + }; +} + +it("does not fetch or retain sliding snapshots until the reader asks for history", () => { + const read = vi.spyOn(WebClient.prototype, "sessionHistory"); + const { result, rerender, onAnchorChange } = setup(); + for (let index = 10; index < 100; index++) { + const selected = session(index, index + 1); + rerender({ selected }); + expect(result.current.session).toBe(selected); + } + expect(result.current.engaged).toBe(false); + expect(read).not.toHaveBeenCalled(); + expect(onAnchorChange).not.toHaveBeenCalled(); +}); + +it("loads native older entries before the current page and reaches the start", async () => { + const read = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockResolvedValue(page()); + const { result, beforePrepend, onAnchorChange } = setup(); + await act(() => result.current.loadOlder()); + expect(read).toHaveBeenCalledWith( + { sessionId: "s", sessionPath: "/sessions/s.jsonl", entryId: "e3" }, + "e2", + expect.any(AbortSignal), + ); + expect(result.current.session?.entries.map(({ id }) => id)).toEqual([ + "e0", + "e1", + "e2", + "e3", + ]); + expect(result.current.session?.entries[0]?.message?.role).toBe("user"); + expect(result.current.hasMore).toBe(false); + expect(beforePrepend).toHaveBeenCalledOnce(); + expect(onAnchorChange).toHaveBeenLastCalledWith({ + sessionId: "s", + sessionPath: "/sessions/s.jsonl", + entryId: "e3", + }); +}); + +it("keeps an in-flight older page when a verified append arrives", async () => { + let finish!: (page: WebSessionHistoryPage) => void; + const read = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const { result, rerender } = setup(); + act(() => { + void result.current.loadOlder(); + }); + const appended = session(3, 4, { + history: { + leafEntryId: "e4", + beforeEntryId: "e3", + anchorEntryId: "e3", + anchorOnBranch: true, + }, + }); + rerender({ selected: appended }); + expect(read.mock.calls[0]![2].aborted).toBe(false); + await act(async () => finish(page())); + expect(result.current.session?.entries.map(({ id }) => id)).toEqual([ + "e0", + "e1", + "e2", + "e3", + "e4", + ]); + expect(read).toHaveBeenCalledOnce(); +}); + +it.each(["session-id", "copied-path"])( + "discards a delayed older page after changing %s", + async (change) => { + let finish!: (page: WebSessionHistoryPage) => void; + const read = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const { result, rerender, beforePrepend } = setup(); + act(() => { + void result.current.loadOlder(); + }); + const replacement = session( + 20, + 21, + change === "session-id" + ? { id: "other" } + : { path: "/sessions/copy.jsonl" }, + ); + rerender({ selected: replacement }); + expect(read.mock.calls[0]![2].aborted).toBe(true); + await act(async () => finish(page())); + expect(result.current.session).toBe(replacement); + expect(beforePrepend).not.toHaveBeenCalled(); + }, +); + +it("does not splice a pending page into an unverified or forked snapshot", async () => { + let finish!: (page: WebSessionHistoryPage) => void; + const read = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + const { result, rerender, beforePrepend } = setup(session(), { + onRefresh: () => new Promise(() => {}), + }); + act(() => { + void result.current.loadOlder(); + }); + rerender({ selected: session(20, 21) }); + await act(async () => finish(page())); + expect(result.current.verifying).toBe(true); + expect(result.current.session?.entries.map(({ id }) => id)).toEqual([ + "e2", + "e3", + ]); + expect(beforePrepend).not.toHaveBeenCalled(); + const forked = session(20, 21, { + history: { + leafEntryId: "e21", + beforeEntryId: "e20", + anchorEntryId: "e3", + anchorOnBranch: false, + }, + }); + rerender({ selected: forked }); + expect(result.current.session).toBe(forked); + expect(result.current.error).toBe("historyChanged"); + expect(read.mock.calls[0]![2].aborted).toBe(true); +}); + +it("keeps a load error visible and permits retry without discarding the current page", async () => { + const read = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockRejectedValueOnce(new Error("offline")) + .mockResolvedValueOnce(page()); + const { result } = setup(); + await act(() => result.current.loadOlder()); + expect(result.current.error).toBe("historyUnavailable"); + expect(result.current.session?.entries.map(({ id }) => id)).toEqual([ + "e2", + "e3", + ]); + await act(() => result.current.loadOlder()); + expect(read).toHaveBeenCalledTimes(2); + expect(result.current.error).toBeNull(); + expect(result.current.session?.entries[0]?.id).toBe("e0"); +}); + +it("keeps only a bounded reading window and releases it on Jump to latest", async () => { + const read = vi + .spyOn(WebClient.prototype, "sessionHistory") + .mockResolvedValueOnce(page(750, 999, "e1249", "e1000")) + .mockResolvedValueOnce(page(500, 749, "e1249", "e750")) + .mockResolvedValueOnce(page(250, 499, "e1249", "e500")) + .mockResolvedValueOnce(page(0, 249, "e1249", "e250")); + const latest = session(1000, 1249); + const { result, onAnchorChange } = setup(latest); + for (let index = 0; index < 4; index++) + await act(() => result.current.loadOlder()); + expect(read).toHaveBeenCalledTimes(4); + expect(result.current.session?.entries).toHaveLength(1000); + expect(result.current.session?.entries[0]?.id).toBe("e0"); + expect(result.current.hasNewer).toBe(true); + act(() => result.current.resetToLatest()); + expect(result.current.session).toBe(latest); + expect(result.current.engaged).toBe(false); + expect(onAnchorChange).toHaveBeenLastCalledWith(null); +}); + +it("keeps a verified older reading position when many new entries create a gap", async () => { + vi.spyOn(WebClient.prototype, "sessionHistory").mockResolvedValue(page()); + const { result, rerender } = setup(); + await act(() => result.current.loadOlder()); + rerender({ + selected: session(100, 101, { + history: { + leafEntryId: "e101", + beforeEntryId: "e100", + anchorEntryId: "e3", + anchorOnBranch: true, + }, + }), + }); + expect(result.current.session?.entries.map(({ id }) => id)).toEqual([ + "e0", + "e1", + "e2", + "e3", + ]); + expect(result.current.hasNewer).toBe(true); + expect(result.current.verifying).toBe(false); +}); + +it("bounds retained bytes even when each page has far fewer than 250 entries", async () => { + const text = "文".repeat(12000); + const large = (from: number, through: number) => { + const value = session(from, through); + value.entries = value.entries.map((entry) => ({ + ...entry, + message: { + role: "assistant", + content: text, + parts: [{ type: "text", text }], + }, + })); + value.bytes = jsonByteLength(value.entries); + return value; + }; + vi.spyOn(WebClient.prototype, "sessionHistory").mockImplementation( + async (anchor, before) => { + const end = Number(before.slice(1)); + const result = page(end - 25, end - 1, anchor.entryId, before); + const content = large(end - 25, end - 1); + return { ...result, entries: content.entries, bytes: content.bytes }; + }, + ); + const { result } = setup(large(125, 149)); + for (let index = 0; index < 5; index++) + await act(() => result.current.loadOlder()); + expect(result.current.session?.entries[0]?.id).toBe("e0"); + expect(result.current.session!.entries.length).toBeLessThan(150); + expect(jsonByteLength(result.current.session?.entries)).toBeLessThanOrEqual( + 8 * 1024 * 1024, + ); + expect(result.current.hasNewer).toBe(true); +}); diff --git a/tests/web/session-history.test.ts b/tests/web/session-history.test.ts new file mode 100644 index 00000000..fb595196 --- /dev/null +++ b/tests/web/session-history.test.ts @@ -0,0 +1,764 @@ +import assert from "node:assert/strict"; +import { mkdir, mkdtemp, rm, readFile, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test, { after, type TestContext } from "node:test"; +import { SessionManager } from "@earendil-works/pi-coding-agent"; +import { WEB_COMMAND_INPUT } from "../../extensions/shared/web-command-feedback.ts"; +import { WEB_TURN_CHANGES_ENTRY } from "../../web/protocol/turn-changes.ts"; +import { + jsonByteLength, + WEB_MAX_ENTRIES, + WEB_MAX_SELECTED_TRANSCRIPT_BYTES, +} from "../../web/protocol/types.ts"; +import type { WebRuntimeController } from "../../web/runtime/types.ts"; +import { projectWebModelSearch } from "../../web/runtime/model-discovery.ts"; + +const agentDir = await mkdtemp(join(tmpdir(), "openpi-history-test-agent-")); +process.env.PI_CODING_AGENT_DIR = agentDir; +const { PiWebAdapter } = await import("../../web/adapter/pi-adapter.ts"); +const { WebHost } = await import("../../web/host/web-host.ts"); +after(() => rm(agentDir, { recursive: true, force: true })); +const usage = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, +}; + +async function fixture(t: TestContext, persisted = false) { + const cwd = await mkdtemp(join(tmpdir(), "openpi-history-native-")); + const directory = join(cwd, "sessions"); + await mkdir(directory); + t.after(() => rm(cwd, { recursive: true, force: true })); + const manager = persisted + ? SessionManager.create(cwd, directory) + : SessionManager.inMemory(cwd); + const runtime: WebRuntimeController = { + cwd, + workspaceSelected: true, + sessionDirectory: directory, + sessionManager: manager, + isIdle: () => true, + getActiveTurn: () => undefined, + cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), + sendPrompt: async () => ({ pendingFollowUps: 0 }), + newSession: async () => ({ + cancelled: true, + sessionId: manager.getSessionId(), + }), + switchSession: async () => ({ cancelled: true }), + listModels: () => [], + searchModels: (query, limit) => projectWebModelSearch([], query, limit), + setModel: async () => { + throw new Error("unused"); + }, + subscribe: () => () => {}, + dispose: async () => {}, + }; + return { manager, runtime, adapter: new PiWebAdapter(runtime), directory }; +} +function assistant(manager: SessionManager, text: string) { + return manager.appendMessage({ + role: "assistant", + content: [{ type: "text", text }], + api: "openai-responses", + provider: "fixture", + model: "fixture", + stopReason: "stop", + usage, + timestamp: Date.now(), + }); +} + +test("bounded native pages recover the user request omitted by a long single turn", async (t) => { + const { manager, adapter } = await fixture(t); + manager.appendMessage({ + role: "user", + content: "Original user request", + timestamp: 1, + }); + for (let index = 0; index < 540; index++) + assistant(manager, `Progress ${index}`); + const latest = (await adapter.getSnapshot()).selectedSession!; + assert.equal(latest.entries.length, WEB_MAX_ENTRIES); + assert.equal( + latest.entries.some((entry) => entry.message?.role === "user"), + false, + ); + let before = latest.history!.beforeEntryId; + const all = [...latest.entries]; + while (before) { + const result = await adapter.getSessionHistory( + latest.id, + latest.path, + latest.history!.leafEntryId!, + before, + ); + assert.equal(result.status, "ok"); + if (result.status !== "ok") return; + assert.ok(result.session.entries.length <= WEB_MAX_ENTRIES); + assert.ok( + jsonByteLength({ session: result.session }) <= + WEB_MAX_SELECTED_TRANSCRIPT_BYTES, + ); + assert.equal(result.session.requestedBeforeEntryId, before); + all.unshift(...result.session.entries); + before = result.session.history!.beforeEntryId; + } + assert.deepEqual( + all.map((entry) => entry.id), + manager.getBranch().map((entry) => entry.id), + ); + assert.equal(all[0]?.message?.content, "Original user request"); +}); + +test("history pages follow user turns and retain every native entry", async (t) => { + const { manager, adapter } = await fixture(t); + for (let turn = 0; turn < 65; turn++) { + manager.appendMessage({ + role: "user", + content: `Request ${turn}`, + timestamp: turn, + }); + for (let step = 0; step < 4; step++) + assistant(manager, `Response ${turn}.${step}`); + } + const latest = (await adapter.getSnapshot()).selectedSession!; + const pages = [latest.entries]; + assert.equal(latest.entries[0]?.message?.role, "user"); + assert.equal( + latest.entries.filter((entry) => entry.message?.role === "user").length, + 20, + ); + let before = latest.history!.beforeEntryId; + while (before) { + const result = await adapter.getSessionHistory( + latest.id, + latest.path, + latest.history!.leafEntryId!, + before, + ); + assert.equal(result.status, "ok"); + if (result.status !== "ok") return; + assert.equal(result.session.entries[0]?.message?.role, "user"); + assert.ok( + result.session.entries.filter((entry) => entry.message?.role === "user") + .length <= 20, + ); + pages.unshift(result.session.entries); + assert.notEqual(result.session.history?.beforeEntryId, before); + before = result.session.history!.beforeEntryId; + } + assert.deepEqual( + pages.flat().map((entry) => entry.id), + manager.getBranch().map((entry) => entry.id), + ); +}); + +test("a short page retains the native predecessor needed to reconcile a new prompt", async (t) => { + const { manager, adapter } = await fixture(t); + const predecessor = assistant(manager, "Earlier context"); + manager.appendMessage({ role: "user", content: "New prompt", timestamp: 1 }); + const latest = (await adapter.getSnapshot()).selectedSession!; + assert.equal(latest.entries[0]?.id, predecessor); + assert.equal(latest.truncation.entriesOmitted, 0); +}); + +test("a byte-bounded partial turn remains reachable behind a complete latest turn", async (t) => { + const { manager, adapter } = await fixture(t); + manager.appendMessage({ + role: "user", + content: "Large earlier turn", + timestamp: 1, + }); + for (let step = 0; step < 240; step++) + assistant(manager, `Evidence ${step}: ` + "content ".repeat(1200)); + manager.appendMessage({ role: "user", content: "Latest turn", timestamp: 2 }); + assistant(manager, "Latest answer"); + const latest = (await adapter.getSnapshot()).selectedSession!; + assert.equal(latest.entries.at(-2)?.message?.content, "Latest turn"); + let before = latest.history!.beforeEntryId; + const all = [...latest.entries]; + while (before) { + const result = await adapter.getSessionHistory( + latest.id, + latest.path, + latest.history!.leafEntryId!, + before, + ); + assert.equal(result.status, "ok"); + if (result.status !== "ok") return; + assert.ok(result.session.entries.length > 0); + all.unshift(...result.session.entries); + assert.notEqual(result.session.history?.beforeEntryId, before); + before = result.session.history!.beforeEntryId; + } + assert.deepEqual( + all.map((entry) => entry.id), + manager.getBranch().map((entry) => entry.id), + ); +}); + +test("byte-bounded history remains pageable when fewer than 250 entries fill the snapshot", async (t) => { + const { manager, adapter } = await fixture(t); + manager.appendMessage({ + role: "user", + content: "Before large results", + timestamp: 1, + }); + for (let index = 0; index < 140; index++) + assistant(manager, `${index}:` + "evidence ".repeat(1200)); + const latest = (await adapter.getSnapshot()).selectedSession!; + assert.ok(manager.getBranch().length < WEB_MAX_ENTRIES); + assert.ok(latest.truncation.entriesOmitted > 0); + const result = await adapter.getSessionHistory( + latest.id, + latest.path, + latest.history!.leafEntryId!, + latest.history!.beforeEntryId!, + ); + assert.equal(result.status, "ok"); + if (result.status !== "ok") return; + assert.ok( + jsonByteLength({ session: result.session }) <= + WEB_MAX_SELECTED_TRANSCRIPT_BYTES, + ); + assert.equal( + result.session.entries[0]?.message?.content, + "Before large results", + ); + assert.equal(result.session.history?.beforeEntryId, null); +}); + +test("native ancestry accepts appended entries and rejects an existing entry on a different branch", async (t) => { + const { manager, adapter } = await fixture(t); + const root = manager.appendMessage({ + role: "user", + content: "Shared root", + timestamp: 1, + }); + assistant(manager, "Old branch"); + const anchor = manager.getLeafId()!; + const latest = (await adapter.getSnapshot()).selectedSession!; + assistant(manager, "Appended entry"); + const appended = ( + await adapter.getSnapshot(latest.path, { + sessionId: latest.id, + entryId: anchor, + }) + ).selectedSession!; + assert.equal(appended.history?.anchorOnBranch, true); + assert.equal( + (await adapter.getSessionHistory(latest.id, latest.path, anchor, anchor)) + .status, + "ok", + ); + assert.equal( + ( + await adapter.getSessionHistory( + latest.id, + latest.path, + root, + manager.getLeafId()!, + ) + ).status, + "changed", + ); + manager.branch(root); + assistant(manager, "New branch"); + assert.ok( + manager.getEntry(anchor), + "the old entry remains in the native tree", + ); + const changed = ( + await adapter.getSnapshot(latest.path, { + sessionId: latest.id, + entryId: anchor, + }) + ).selectedSession!; + assert.equal(changed.history?.anchorOnBranch, false); + assert.equal( + (await adapter.getSessionHistory(latest.id, latest.path, anchor, anchor)) + .status, + "changed", + ); +}); + +test("an oversized multi-part message keeps its native entry boundary instead of becoming an empty skipped page", async (t) => { + const { manager, adapter } = await fixture(t); + const user = manager.appendMessage({ + role: "user", + content: "Original question", + timestamp: 1, + }); + const huge = manager.appendMessage({ + role: "assistant", + content: Array.from({ length: 64 }, () => ({ + type: "text", + text: "测试".repeat(6000), + })), + api: "openai-responses", + provider: "fixture", + model: "fixture", + stopReason: "stop", + usage, + timestamp: 2, + }); + const current = (await adapter.getSnapshot()).selectedSession!; + assert.ok(current.entries.some((entry) => entry.id === huge)); + assert.ok( + current.entries.find((entry) => entry.id === huge)?.message?.truncation + ?.partsOmitted, + ); + const tail = assistant(manager, "Later response"); + const result = await adapter.getSessionHistory( + current.id, + current.path, + tail, + tail, + ); + assert.equal(result.status, "ok"); + if (result.status !== "ok") return; + assert.ok( + result.session.entries.some((entry) => entry.id === huge), + "the oversized entry itself remains reachable", + ); + assert.ok( + jsonByteLength({ session: result.session }) <= + WEB_MAX_SELECTED_TRANSCRIPT_BYTES, + ); + const ids = new Set(result.session.entries.map((entry) => entry.id)); + if (result.session.history?.beforeEntryId) { + const previous = await adapter.getSessionHistory( + current.id, + current.path, + tail, + result.session.history.beforeEntryId, + ); + assert.equal(previous.status, "ok"); + if (previous.status === "ok") + for (const entry of previous.session.entries) ids.add(entry.id); + } + assert.ok( + ids.has(user), + "the question before the oversized response is also reachable", + ); +}); + +test("history reads keep copied Session files distinct even when embedded IDs match", async (t) => { + const { manager, adapter, directory } = await fixture(t, true); + const root = manager.appendMessage({ + role: "user", + content: "Original", + timestamp: 1, + }); + const originalAnchor = assistant(manager, "Original branch response"); + const original = (await adapter.getSnapshot()).selectedSession!; + const copyPath = join(directory, "copied.jsonl"); + await writeFile(copyPath, await readFile(manager.getSessionFile()!)); + const copy = SessionManager.open(copyPath); + copy.branch(root); + const copyAnchor = assistant(copy, "Copy branch response"); + assert.equal(copy.getSessionId(), manager.getSessionId()); + assert.equal( + ( + await adapter.getSessionHistory( + original.id, + copyPath, + originalAnchor, + originalAnchor, + ) + ).status, + "changed", + ); + const result = await adapter.getSessionHistory( + original.id, + copyPath, + copyAnchor, + copyAnchor, + ); + assert.equal(result.status, "ok"); + if (result.status === "ok") assert.equal(result.session.path, copyPath); + const copiedItem = await adapter.getSessionItem( + original.id, + copyPath, + copyAnchor, + 0, + ); + assert.equal(copiedItem.status, "ok"); + if (copiedItem.status === "ok") + assert.equal(copiedItem.page.text, "Copy branch response"); + assert.equal( + (await adapter.getSessionItem(original.id, original.path, copyAnchor, 0)) + .status, + "changed", + ); + const wrongIdentity = ( + await adapter.getSnapshot(original.path, { + sessionId: "different", + entryId: root, + }) + ).selectedSession!; + assert.equal(wrongIdentity.history?.anchorOnBranch, false); +}); + +test("native item pages recover truncated text and text beyond the preview part limit", async (t) => { + const { manager, adapter } = await fixture(t); + const body = `${"a".repeat(31_999)}\ud83d\ude00${"b".repeat(40_000)}`; + const user = manager.appendMessage({ + role: "user", + content: body, + timestamp: 1, + }); + const parts = Array.from({ length: 70 }, (_, index) => ({ + type: "text" as const, + text: `Segment ${index}: ${"content".repeat(250)}`, + })); + const answer = manager.appendMessage({ + role: "assistant", + content: parts, + api: "openai-responses", + provider: "fixture", + model: "fixture", + stopReason: "stop", + usage, + timestamp: 2, + }); + const snapshot = (await adapter.getSnapshot()).selectedSession!; + assert.equal( + snapshot.entries.find((entry) => entry.id === user)?.message?.truncation + ?.visibleText, + true, + ); + assert.equal( + snapshot.entries.find((entry) => entry.id === answer)?.message?.truncation + ?.visibleText, + true, + ); + + for (const [entryId, expected] of [ + [user, body], + [answer, parts.map((part) => part.text).join("\n")], + ] as const) { + let cursor = 0; + let restored = ""; + while (true) { + const result = await adapter.getSessionItem( + snapshot.id, + snapshot.path, + entryId, + cursor, + ); + assert.equal(result.status, "ok"); + if (result.status !== "ok") break; + assert.ok(result.page.text.length <= 32_000); + restored += result.page.text; + if (result.page.nextCursor === null) { + assert.equal(result.page.totalChars, expected.length); + break; + } + assert.ok(result.page.nextCursor > cursor); + cursor = result.page.nextCursor; + } + assert.equal(restored, expected); + } + assert.equal( + ( + await adapter.getSessionItem( + snapshot.id, + snapshot.path, + user, + body.length + 1, + ) + ).status, + "invalid_cursor", + ); +}); + +test("item hydration rejects entries outside the selected native branch", async (t) => { + const { manager, adapter } = await fixture(t); + const root = manager.appendMessage({ + role: "user", + content: "Shared", + timestamp: 1, + }); + const discarded = assistant(manager, "Old branch"); + const session = (await adapter.getSnapshot()).selectedSession!; + manager.branch(root); + assistant(manager, "New branch"); + assert.equal( + (await adapter.getSessionItem(session.id, session.path, discarded, 0)) + .status, + "changed", + ); + assert.equal( + (await adapter.getSessionItem("different", session.path, root, 0)).status, + "changed", + ); + assert.equal( + (await adapter.getSessionItem(session.id, session.path, root, 0)).status, + "ok", + ); +}); + +test("item hydration includes visible native command input without exposing tool results", async (t) => { + const { manager, adapter } = await fixture(t); + const content = "/openpi-setup " + "model ".repeat(3_000); + const command = manager.appendCustomEntry(WEB_COMMAND_INPUT, { + text: content, + }); + const tool = manager.appendMessage({ + role: "toolResult", + toolCallId: "tool-1", + toolName: "bash", + content: [{ type: "text", text: "private tool output" }], + isError: false, + timestamp: 2, + }); + const session = (await adapter.getSnapshot()).selectedSession!; + const result = await adapter.getSessionItem( + session.id, + session.path, + command, + 0, + ); + assert.equal(result.status, "ok"); + if (result.status === "ok") assert.equal(result.page.text, content); + assert.equal( + (await adapter.getSessionItem(session.id, session.path, tool, 0)).status, + "changed", + ); +}); + +test("snapshot fitting retains a real latest-entry cursor before dropping bounded catalog rows", async (t) => { + const { manager, runtime, adapter, directory } = await fixture(t, true); + manager.appendMessage({ + role: "user", + content: "Question before the large response", + timestamp: 1, + }); + const huge = manager.appendMessage({ + role: "assistant", + content: Array.from({ length: 64 }, () => ({ + type: "text", + text: "测试".repeat(6000), + })), + api: "openai-responses", + provider: "fixture", + model: "fixture", + stopReason: "stop", + usage, + timestamp: 2, + }); + for (let index = 0; index < 500; index++) { + const other = SessionManager.create(runtime.cwd, directory); + other.appendMessage({ + role: "user", + content: "问".repeat(500), + timestamp: index, + }); + assistant(other, ""); + } + runtime.listModels = () => + Array.from({ length: 250 }, (_, index) => ({ + provider: "模".repeat(500), + id: `${index}${"模".repeat(500)}`, + name: "模".repeat(500), + label: "模".repeat(500), + current: index === 0, + })); + const snapshot = await adapter.getSnapshot(); + assert.ok(jsonByteLength(snapshot) <= 4 * 1024 * 1024); + assert.ok( + snapshot.selectedSession?.entries.some((entry) => entry.id === huge), + ); + assert.ok(snapshot.selectedSession?.history?.beforeEntryId); + assert.ok( + snapshot.truncation.sessionsOmitted > 0 || + snapshot.truncation.modelsOmitted > 0, + ); +}); + +test("history HTTP reads require authentication and exact bounded native cursors", async (t) => { + const { manager, runtime } = await fixture(t); + manager.appendMessage({ role: "user", content: "Question", timestamp: 1 }); + const leaf = assistant(manager, "Answer"); + const host = new WebHost({ runtime, token: "ab".repeat(32) }); + await host.start(); + t.after(() => host.stop()); + const headers = { Authorization: `Bearer ${"ab".repeat(32)}` }; + const path = `current:${manager.getSessionId()}`; + const query = new URLSearchParams({ + sessionId: manager.getSessionId(), + path, + anchorEntryId: leaf, + beforeEntryId: leaf, + }); + const address = `${host.origin}/api/session/history?${query}`; + assert.equal((await fetch(address)).status, 401); + assert.equal( + (await fetch(`${address}&beforeEntryId=${leaf}`, { headers })).status, + 400, + ); + const response = await fetch(address, { headers }); + assert.equal(response.status, 200); + assert.equal( + (await response.json()).session.entries[0].message.content, + "Question", + ); + query.set("path", join(tmpdir(), "not-a-known-session.jsonl")); + assert.equal( + (await fetch(`${host.origin}/api/session/history?${query}`, { headers })) + .status, + 404, + ); + query.set("path", path); + query.set("sessionId", "different-session"); + assert.equal( + (await fetch(`${host.origin}/api/session/history?${query}`, { headers })) + .status, + 409, + ); +}); + +test("item HTTP reads are explicit, authenticated, and bounded", async (t) => { + const { manager, runtime } = await fixture(t); + const text = "A".repeat(90_000); + const entryId = manager.appendMessage({ + role: "user", + content: text, + timestamp: 1, + }); + const host = new WebHost({ runtime, token: "cd".repeat(32) }); + await host.start(); + t.after(() => host.stop()); + const headers = { Authorization: `Bearer ${"cd".repeat(32)}` }; + const query = new URLSearchParams({ + sessionId: manager.getSessionId(), + sessionPath: `current:${manager.getSessionId()}`, + entryId, + cursor: "0", + }); + const address = `${host.origin}/api/session/item?${query}`; + assert.equal((await fetch(address)).status, 401); + assert.equal( + (await fetch(`${address}&entryId=${entryId}`, { headers })).status, + 400, + ); + const response = await fetch(address, { headers }); + assert.equal(response.status, 200); + const page = await response.json(); + assert.equal(page.text, text.slice(0, 32_000)); + assert.equal(page.nextCursor, 32_000); + query.set("cursor", "9999999999"); + assert.equal( + (await fetch(`${host.origin}/api/session/item?${query}`, { headers })) + .status, + 400, + ); + query.set("cursor", "0"); + query.set("sessionId", "different"); + assert.equal( + (await fetch(`${host.origin}/api/session/item?${query}`, { headers })) + .status, + 409, + ); +}); + +test("turn review reads the persisted prompt-bound diff, not the current workspace", async (t) => { + const { manager, runtime, adapter } = await fixture(t); + const promptEntryId = manager.appendMessage({ + role: "user", + content: "Edit the file", + timestamp: 1, + }); + assistant(manager, "Done"); + const sessionId = manager.getSessionId(); + const path = `current:${sessionId}`; + const diff = + "diff --git a/example.txt b/example.txt\n@@ -1 +1 @@\n-old\n+new\n"; + manager.appendCustomEntry(WEB_TURN_CHANGES_ENTRY, { + version: 1, + sessionId, + promptEntryId, + state: "complete", + fileCount: 1, + files: [ + { + path: "example.txt", + status: "modified", + additions: 1, + deletions: 1, + diff, + diffTruncated: false, + }, + ], + additions: 1, + deletions: 1, + }); + const result = await adapter.getTurnChanges(sessionId, path, promptEntryId); + assert.equal(result.ok, true); + if (result.ok) assert.equal(result.changes.files[0]?.diff, diff); + assert.deepEqual( + await adapter.getTurnChanges(sessionId, path, promptEntryId, "other.txt"), + { ok: false, reason: "not_found" }, + ); + + const host = new WebHost({ runtime, token: "ef".repeat(32) }); + await host.start(); + t.after(() => host.stop()); + const headers = { Authorization: `Bearer ${"ef".repeat(32)}` }; + const query = new URLSearchParams({ + sessionId, + sessionPath: path, + promptEntryId, + }); + const address = `${host.origin}/api/turn-changes?${query}`; + assert.equal((await fetch(address)).status, 401); + assert.equal( + (await fetch(`${address}&promptEntryId=${promptEntryId}`, { headers })) + .status, + 400, + ); + assert.equal( + (await (await fetch(address, { headers })).json()).changes.files[0].diff, + diff, + ); + + manager.appendMessage({ + role: "user", + content: "A later turn", + timestamp: 2, + }); + manager.appendCustomEntry(WEB_TURN_CHANGES_ENTRY, { + version: 1, + sessionId, + promptEntryId, + state: "complete", + fileCount: 1, + files: [ + { + path: "wrong.txt", + status: "added", + additions: 1, + deletions: 0, + diff: "wrong", + diffTruncated: false, + }, + ], + additions: 1, + deletions: 0, + }); + assert.equal( + (await adapter.getTurnChanges(sessionId, path, promptEntryId)).ok, + true, + ); + manager.branch(promptEntryId); + assert.deepEqual( + await adapter.getTurnChanges(sessionId, path, promptEntryId), + { ok: false, reason: "not_found" }, + ); +}); diff --git a/tests/web/session-observer.e2e.ts b/tests/web/session-observer.e2e.ts new file mode 100644 index 00000000..6d642044 --- /dev/null +++ b/tests/web/session-observer.e2e.ts @@ -0,0 +1,394 @@ +import { mkdir, mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { + fauxAssistantMessage, + fauxProvider, + fauxToolCall, +} from "@earendil-works/pi-ai"; +import { + type AgentSessionRuntime, + type CreateAgentSessionRuntimeFactory, + createAgentSessionFromServices, + createAgentSessionRuntime, + DefaultResourceLoader, + ModelRuntime, + SessionManager, + SettingsManager, +} from "@earendil-works/pi-coding-agent"; +import { expect, test } from "@playwright/test"; +import { Type } from "typebox"; +import { WebHost } from "../../web/host/web-host.ts"; +import { PiWebRuntime } from "../../web/runtime/pi-runtime.ts"; + +function gate() { + let entered!: () => void; + let release!: () => void; + return { + entered: new Promise((resolve) => { + entered = resolve; + }), + released: new Promise((resolve) => { + release = resolve; + }), + enter: () => entered(), + release: () => release(), + }; +} + +async function observerFixture() { + const directory = await mkdtemp(join(tmpdir(), "openpi-observer-browser-")); + const workspace = join(directory, "workspace"); + await mkdir(workspace); + const gates = [gate(), gate(), gate(), gate()]; + const aborts = { A: 0, B: 0 }; + const makeRuntime = async (name: "A" | "B") => { + const agentDir = join(directory, `agent-${name}`); + await mkdir(agentDir); + const provider = `observer-fixture-${name}`; + const faux = fauxProvider({ + api: provider, + provider, + models: [{ id: "fixture", name: `Fixture ${name}`, reasoning: false }], + }); + const holding = (phase: number, label: string) => + fauxAssistantMessage( + [ + { type: "text", text: label }, + fauxToolCall( + "hold_fixture", + { phase }, + { id: `hold-${name}-${phase}` }, + ), + ], + { stopReason: "toolUse" }, + ); + faux.setResponses( + name === "A" + ? [ + holding(0, "A entered the first tool"), + holding(1, "A entered the second tool"), + fauxAssistantMessage("A finished"), + fauxAssistantMessage("A first follow-up finished"), + fauxAssistantMessage("A second follow-up finished"), + ] + : [ + holding(2, "B bootstrap tool is running"), + fauxAssistantMessage("B bootstrap finished"), + holding(3, "B consumed its own follow-up"), + fauxAssistantMessage("B follow-up finished"), + ], + ); + const modelRuntime = await ModelRuntime.create({ + authPath: join(agentDir, "auth.json"), + modelsPath: join(agentDir, "models.json"), + }); + modelRuntime.registerNativeProvider(faux.provider); + await modelRuntime.setRuntimeApiKey(provider, "fixture-key"); + const settingsManager = SettingsManager.inMemory(undefined, { + projectTrusted: false, + }); + const loader = new DefaultResourceLoader({ + cwd: workspace, + agentDir, + settingsManager, + noExtensions: true, + noSkills: true, + noThemes: true, + noPromptTemplates: true, + noContextFiles: true, + }); + await loader.reload(); + const services = { + cwd: workspace, + agentDir, + settingsManager, + modelRuntime, + resourceLoader: loader, + diagnostics: [], + }; + const create: CreateAgentSessionRuntimeFactory = async (options) => { + const created = await createAgentSessionFromServices({ + services, + sessionManager: options.sessionManager, + model: faux.getModel(), + tools: ["read", "hold_fixture"], + customTools: [ + { + name: "hold_fixture", + label: "Hold fixture", + description: "Wait for the explicit browser test gate", + parameters: Type.Object({ phase: Type.Number() }), + async execute(_id, args, signal, onUpdate) { + if ( + !args || + typeof args !== "object" || + !("phase" in args) || + typeof args.phase !== "number" + ) + throw new Error("Invalid fixture phase"); + const current = gates[args.phase]; + if (!current) throw new Error("Unknown fixture phase"); + current.enter(); + onUpdate?.({ + content: [ + { type: "text", text: `Phase ${args.phase} entered` }, + ], + details: {}, + }); + await Promise.race([ + current.released, + new Promise((resolve) => { + if (signal?.aborted) resolve(); + else + signal?.addEventListener("abort", () => resolve(), { + once: true, + }); + }), + ]); + return { + content: [{ type: "text", text: "Gate released" }], + details: {}, + }; + }, + }, + ], + }); + created.session.setSessionName(`Observer ${name}`); + const abort = created.session.abort.bind(created.session); + created.session.abort = async () => { + aborts[name]++; + await abort(); + }; + return { ...created, services, diagnostics: [] }; + }; + return createAgentSessionRuntime(create, { + cwd: workspace, + agentDir, + sessionManager: SessionManager.create( + workspace, + join(directory, "sessions"), + ), + }); + }; + const a = await makeRuntime("A"); + const b = await makeRuntime("B"); + const web = Reflect.construct(PiWebRuntime, [ + b, + join(directory, "sessions"), + { timeoutMs: 10000, release: async () => {} }, + { release: async () => {} }, + true, + ]) as PiWebRuntime; + const internal = web as unknown as { + startRuntimeSession(): Promise; + replaceRuntime(runtime: AgentSessionRuntime): Promise; + }; + // Reach a real retained-B/current-A state before the browser connects. All + // later switches and cancellation go through the public authenticated API. + await internal.startRuntimeSession(); + await web.sendPrompt("Start the B fixture", { commandId: "bootstrap-b" }); + await gates[2]!.entered; + await internal.replaceRuntime(a); + const host = new WebHost({ runtime: web }); + await host.start(); + return { + host, + web, + a, + b, + gates, + aborts, + async close() { + for (const item of gates) item.release(); + await host.stop(); + await rm(directory, { recursive: true, force: true }); + }, + }; +} + +test("a readonly observer keeps its pending messages and native progress, then retakes control and stops only its Session", async ({ + browser, +}, testInfo) => { + const fixture = await observerFixture(); + const context = await browser.newContext({ + viewport: { width: 1440, height: 1000 }, + locale: "zh-CN", + }); + const page = await context.newPage(); + const aId = fixture.a.session.sessionManager.getSessionId(); + const aPath = fixture.a.session.sessionManager.getSessionFile()!; + const bId = fixture.b.session.sessionManager.getSessionId(); + const bPath = fixture.b.session.sessionManager.getSessionFile()!; + const token = new URLSearchParams( + new URL(fixture.host.url).hash.slice(1), + ).get("token")!; + const headers = { Authorization: `Bearer ${token}` }; + try { + await page.goto(fixture.host.origin); + const input = page.getByRole("textbox", { name: "描述任务" }); + await input.fill("Run observer A"); + await page.getByRole("button", { name: "发送", exact: true }).click(); + await fixture.gates[0]!.entered; + await expect( + page + .locator(".conversation") + .getByText("Run observer A", { exact: true }), + ).toHaveCount(1); + const original = fixture.web.getActiveTurn()!; + const pendingText = "shared pending message"; + for (let index = 0; index < 2; index++) { + await expect(input).toHaveValue(""); + await input.fill(pendingText); + if (index === 0) { + await expect( + page.getByRole("button", { name: "停止当前轮次", exact: true }), + ).toBeVisible(); + await page.getByRole("button", { name: "发送", exact: true }).click(); + } else { + await input.press("Enter"); + } + await expect(input).toHaveValue(""); + } + const conversation = page.locator(".conversation"); + const queue = page.getByRole("region", { name: "2 条消息正在排队" }); + const ownPending = queue + .getByRole("listitem") + .filter({ hasText: pendingText }); + await expect(ownPending).toHaveCount(2); + await expect( + conversation.getByText(pendingText, { exact: true }), + ).toHaveCount(0); + await expect( + conversation.getByText("2 条消息正在排队", { exact: true }), + ).toBeVisible(); + const toolbar = page.locator(".composer-toolbar"); + const hint = page.locator(".composer-hint"); + await expect(hint).toHaveAttribute("data-visible", "false"); + await page.reload(); + await expect(ownPending).toHaveCount(2); + await page.setViewportSize({ width: 390, height: 740 }); + await expect(queue).toBeInViewport(); + const queueBounds = await queue.boundingBox(); + const composerBounds = await page.locator(".composer").boundingBox(); + expect( + queueBounds && + composerBounds && + queueBounds.y + queueBounds.height <= composerBounds.y, + ).toBe(true); + expect( + await page.evaluate( + () => document.documentElement.scrollWidth <= window.innerWidth, + ), + ).toBe(true); + await page.screenshot({ + path: testInfo.outputPath("queued-messages-mobile.png"), + }); + await page.setViewportSize({ width: 1440, height: 1000 }); + const timer = conversation.getByRole("timer"); + await expect(timer).toBeVisible(); + const beforeTimer = await timer.innerText(); + const switched = await context.request.post( + `${fixture.host.origin}/api/sessions/select`, + { headers, data: { path: bPath } }, + ); + expect(switched.ok()).toBe(true); + expect(fixture.web.sessionManager.getSessionId()).toBe(bId); + const activate = page.getByRole("button", { + name: "切换到此会话", + exact: true, + }); + await expect(activate).toBeVisible(); + await expect(input).toBeDisabled(); + await expect(toolbar).toBeHidden(); + await expect(hint).toHaveAttribute("data-visible", "false"); + await expect(page.getByRole("heading", { level: 1 })).toHaveText( + "Observer A", + ); + const bPrompt = await context.request.post( + `${fixture.host.origin}/api/prompt`, + { + headers, + data: { + sessionId: bId, + sessionPath: bPath, + commandId: "b-other-client-follow-up", + content: pendingText, + }, + }, + ); + expect(bPrompt.status()).toBe(202); + fixture.gates[2]!.release(); + await fixture.gates[3]!.entered; + fixture.gates[0]!.release(); + await fixture.gates[1]!.entered; + await expect( + conversation.getByText("A entered the second tool", { exact: true }), + ).toBeVisible(); + await expect( + conversation.getByText("B consumed its own follow-up", { exact: true }), + ).toHaveCount(0); + await expect(ownPending).toHaveCount(2); + await expect( + conversation.getByText("2 条消息正在排队", { exact: true }), + ).toBeVisible(); + await expect(timer).not.toHaveText(beforeTimer); + await expect(toolbar).toBeHidden(); + await expect(hint).toHaveAttribute("data-visible", "false"); + expect( + fixture.web + .getSessionExecution(aId, aPath) + .liveTools.map((tool) => tool.call.id), + ).toContain("hold-A-1"); + expect(fixture.web.getSessionExecution(aId, aPath).pendingFollowUps).toBe( + 2, + ); + await page.screenshot({ + path: testInfo.outputPath("readonly-session-A-progress.png"), + }); + await activate.click(); + await expect(activate).toHaveCount(0); + await expect(input).toBeEnabled(); + await expect(toolbar).toBeVisible(); + await expect(hint).toHaveAttribute("data-visible", "false"); + await expect(queue).toBeVisible(); + expect(fixture.web.getActiveTurn()).toMatchObject({ + sessionId: aId, + commandId: original.commandId, + epoch: original.epoch, + }); + const stopRequest = page.waitForRequest( + (request) => + new URL(request.url()).pathname === "/api/turns/cancel" && + request.method() === "POST", + ); + await page + .getByRole("button", { name: "停止当前轮次", exact: true }) + .click(); + expect((await stopRequest).postDataJSON()).toMatchObject({ + sessionId: aId, + commandId: original.commandId, + epoch: original.epoch, + }); + await expect.poll(() => fixture.a.session.isIdle).toBe(true); + expect(fixture.aborts.A).toBe(1); + expect(fixture.aborts.B).toBe(0); + expect(fixture.b.session.isStreaming).toBe(true); + await expect( + page.getByRole("button", { name: "停止当前轮次", exact: true }), + ).toHaveCount(0); + await expect(timer).toHaveCount(0); + await expect(queue).toHaveCount(0); + await expect( + conversation + .locator(".message-row.user .message-body") + .filter({ hasText: pendingText }), + ).toHaveCount(2); + await page.screenshot({ + path: testInfo.outputPath("observer-A-stopped-B-still-running.png"), + }); + } finally { + await context.close(); + await fixture.close(); + } +}); diff --git a/tests/web/settings-feedback.spec.ts b/tests/web/settings-feedback.spec.ts new file mode 100644 index 00000000..0b5eb6b9 --- /dev/null +++ b/tests/web/settings-feedback.spec.ts @@ -0,0 +1,268 @@ +// @vitest-environment jsdom +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, + within, +} from "@testing-library/react"; +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import type { WebModelConfiguration } from "../../web/runtime/types.ts"; +import { ModelConfigurationEditor } from "../../web/ui/src/features/settings/ModelConfigurationEditor.tsx"; +import { ProviderStatusSection } from "../../web/ui/src/features/settings/ProviderStatusSection.tsx"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; +import { i18n } from "../../web/ui/src/i18n.ts"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); +const model: WebModelConfiguration = { + provider: "alpha", + id: "a", + name: "Alpha", + baseUrl: "http://localhost:12345/v1", + api: "openai-responses", + reasoning: true, + contextWindow: 128000, + maxTokens: 4096, +}; +function editor() { + return createElement( + I18nextProvider, + { i18n }, + createElement(ModelConfigurationEditor, { + sessionId: "s", + busy: false, + onSaved: async () => true, + }), + ); +} + +it("edits the sidebar selection and keeps separate drafts when changing models", async () => { + const second = { ...model, id: "b", name: "Beta" }; + const configurations = vi + .spyOn(WebClient.prototype, "modelConfigurations") + .mockResolvedValue({ + revision: "r1", + models: [model, second], + }); + const node = (selectedKey: string) => + createElement( + I18nextProvider, + { i18n }, + createElement(ModelConfigurationEditor, { + sessionId: "s", + selectedKey, + busy: false, + onSaved: async () => true, + }), + ); + const view = render(node("alpha/a")); + const name = await screen.findByRole("textbox", { + name: i18n.t("modelConfig_name"), + }); + await waitFor(() => expect(name.value).toBe("Alpha")); + fireEvent.change(name, { target: { value: "Unfinished Alpha" } }); + view.rerender(node("alpha/b")); + await waitFor(() => expect(name.value).toBe("Beta")); + fireEvent.change(name, { target: { value: "Unfinished Beta" } }); + view.rerender(node("alpha/a")); + await waitFor(() => expect(name.value).toBe("Unfinished Alpha")); + view.rerender(node("alpha/b")); + await waitFor(() => expect(name.value).toBe("Unfinished Beta")); + expect(configurations).toHaveBeenCalledOnce(); +}); + +it("keeps the current model selected when an earlier model save settles", async () => { + vi.spyOn(WebClient.prototype, "modelConfigurations").mockResolvedValue({ + revision: "r1", + models: [model, { ...model, id: "b", name: "Beta" }], + }); + let finish!: (value: { saved: true }) => void; + vi.spyOn(WebClient.prototype, "saveModelConfiguration").mockReturnValue( + new Promise((resolve) => { + finish = resolve; + }), + ); + const onSelect = vi.fn(); + const node = (selectedKey: string) => + createElement( + I18nextProvider, + { i18n }, + createElement(ModelConfigurationEditor, { + sessionId: "s", + selectedKey, + onSelect, + busy: false, + onSaved: async () => true, + }), + ); + const view = render(node("alpha/a")); + const name = await screen.findByRole("textbox", { + name: i18n.t("modelConfig_name"), + }); + await waitFor(() => expect(name.value).toBe("Alpha")); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("saveModelConfiguration") }), + ); + view.rerender(node("alpha/b")); + await act(async () => finish({ saved: true })); + await waitFor(() => expect(name.value).toBe("Beta")); + expect(onSelect).not.toHaveBeenCalled(); + expect(screen.queryByText(i18n.t("modelConfigurationSaved"))).toBeNull(); +}); +async function selectModel() { + const selection = await screen.findByRole("combobox", { + name: i18n.t("configuredModels"), + }); + await waitFor(() => + expect((selection as HTMLSelectElement).disabled).toBe(false), + ); + fireEvent.change(selection, { target: { value: "alpha/a" } }); +} + +it.each(["maxTokens", "contextWindow", "api", "reasoning"] as const)( + "clears the saved receipt when %s is edited", + async (field) => { + vi.spyOn(WebClient.prototype, "modelConfigurations").mockResolvedValue({ + revision: "r1", + models: [model], + }); + vi.spyOn(WebClient.prototype, "saveModelConfiguration").mockResolvedValue({ + saved: true, + }); + render(editor()); + await selectModel(); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("saveModelConfiguration") }), + ); + await screen.findByText(i18n.t("modelConfigurationSaved")); + if (field === "reasoning") + fireEvent.click( + screen.getByRole("checkbox", { name: i18n.t("modelConfig_reasoning") }), + ); + else if (field === "api") + fireEvent.change( + screen.getByRole("combobox", { name: i18n.t("modelConfig_api") }), + { target: { value: "openai-completions" } }, + ); + else + fireEvent.change( + screen.getByRole("spinbutton", { + name: i18n.t(`modelConfig_${field}`), + }), + { target: { value: "2048" } }, + ); + expect(screen.queryByText(i18n.t("modelConfigurationSaved"))).toBeNull(); + }, +); + +it("reloads the displayed values after a revision conflict instead of resubmitting stale fields", async () => { + const latest = { + ...model, + name: "Changed elsewhere", + baseUrl: "http://localhost:54321/v1", + maxTokens: 8192, + }; + vi.spyOn(WebClient.prototype, "modelConfigurations") + .mockResolvedValueOnce({ revision: "r1", models: [model] }) + .mockResolvedValue({ revision: "r2", models: [latest] }); + const save = vi + .spyOn(WebClient.prototype, "saveModelConfiguration") + .mockRejectedValueOnce(new Error("Revision changed")) + .mockResolvedValue({ saved: true }); + render(editor()); + await selectModel(); + fireEvent.change( + screen.getByRole("textbox", { name: i18n.t("modelConfig_name") }), + { target: { value: "Local draft" } }, + ); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("saveModelConfiguration") }), + ); + fireEvent.click(within(await screen.findByRole("alert")).getByRole("button")); + await waitFor(() => + expect( + ( + screen.getByRole("textbox", { + name: i18n.t("modelConfig_baseUrl"), + }) as HTMLInputElement + ).value, + ).toBe(latest.baseUrl), + ); + expect( + ( + screen.getByRole("spinbutton", { + name: i18n.t("modelConfig_maxTokens"), + }) as HTMLInputElement + ).value, + ).toBe("8192"); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("saveModelConfiguration") }), + ); + await waitFor(() => + expect(save).toHaveBeenLastCalledWith( + "s", + "r2", + latest, + expect.any(AbortSignal), + ), + ); +}); + +it("does not show a late credential save receipt under a different provider", async () => { + vi.spyOn(WebClient.prototype, "providerAuth").mockResolvedValue({ + providers: ["alpha", "beta"].map((id) => ({ + id, + name: id, + configured: false, + authMethods: ["api_key"], + subscription: false, + nameTruncated: false, + })), + truncation: { + truncated: false, + providersOmitted: 0, + namesTruncated: 0, + maxProviders: 250, + }, + }); + let finish!: (value: { saved: true }) => void; + vi.spyOn(WebClient.prototype, "saveProviderKey").mockReturnValue( + new Promise((resolve) => { + finish = resolve; + }), + ); + const node = (providerId: string) => + createElement( + I18nextProvider, + { i18n }, + createElement(ProviderStatusSection, { + sessionId: "s", + providerId, + active: true, + }), + ); + const { rerender } = render(node("alpha")); + fireEvent.change(await screen.findByLabelText(i18n.t("providerApiKey")), { + target: { value: "fixture-key" }, + }); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("saveProviderKey") }), + ); + rerender(node("beta")); + await act(async () => finish({ saved: true })); + expect(screen.queryByText(i18n.t("providerKeySaved"))).toBeNull(); + expect( + ( + screen.getByRole("combobox", { + name: i18n.t("provider"), + }) as HTMLSelectElement + ).value, + ).toBe("beta"); +}); diff --git a/tests/web/settings-parity.e2e.ts b/tests/web/settings-parity.e2e.ts new file mode 100644 index 00000000..8615abd5 --- /dev/null +++ b/tests/web/settings-parity.e2e.ts @@ -0,0 +1,127 @@ +import { expect, test } from "@playwright/test"; +import { DEFAULT_SETUP_CONFIG } from "../../extensions/shared/setup-config.ts"; +import { projectWebSetupConfig } from "../../web/runtime/settings-catalog.ts"; +import { + installThinkingFixture, + MOCK_SESSION_ID, +} from "./thinking-e2e-support.ts"; + +test("settings keep labeled mobile navigation and edit the selected model without losing drafts", async ({ + page, +}, testInfo) => { + await installThinkingFixture(page); + await page.route("**/api/settings/catalog?**", (route) => + route.fulfill({ + json: { + sessionId: MOCK_SESSION_ID, + setup: projectWebSetupConfig(DEFAULT_SETUP_CONFIG), + resources: { + skills: [], + plugins: [], + totals: { extensions: 0, skills: 0, prompts: 0, themes: 0 }, + diagnostics: { extensionErrors: 0, skillErrors: 0 }, + truncation: { + truncated: false, + skillsOmitted: 0, + pluginsOmitted: 0, + resourcesOmitted: 0, + }, + }, + }, + }), + ); + let configurationReads = 0; + await page.route("**/api/models/configuration?**", (route) => { + configurationReads++; + return route.fulfill({ + json: { + revision: "settings-parity", + models: [ + { + provider: "mock", + id: "reasoner", + name: "Mock Reasoner", + baseUrl: "http://localhost:12345/v1", + api: "openai-responses", + reasoning: true, + contextWindow: 128000, + maxTokens: 4096, + }, + ], + }, + }); + }); + await page.route("**/api/providers/auth-status?**", (route) => + route.fulfill({ + json: { + providers: [ + { + id: "mock", + name: "Mock", + configured: true, + authMethods: ["api_key"], + subscription: false, + nameTruncated: false, + }, + ], + truncation: { + truncated: false, + providersOmitted: 0, + namesTruncated: 0, + maxProviders: 250, + }, + }, + }), + ); + await page.setViewportSize({ width: 1440, height: 900 }); + await page.goto("/"); + await page.getByRole("button", { name: "设置", exact: true }).click(); + const dialog = page.getByRole("dialog", { name: "设置", exact: true }); + await expect( + dialog.getByRole("heading", { name: "常规", exact: true }), + ).toBeVisible(); + await expect( + dialog.getByRole("button", { name: "恢复默认聊天宽度" }), + ).toBeDisabled(); + await expect( + dialog.getByRole("button", { name: "恢复默认字体大小" }), + ).toBeDisabled(); + expect( + await dialog + .locator(".settings-chat-controls") + .evaluate((element) => element.clientWidth), + ).toBeLessThanOrEqual(420); + const dark = dialog.getByRole("radio", { name: "深色", exact: true }); + await page.keyboard.press("Shift"); + await dark.focus(); + await expect(dark.locator("..")).toHaveCSS("outline-style", "solid"); + await page.screenshot({ + path: testInfo.outputPath("settings-general-desktop.png"), + }); + + await page.setViewportSize({ width: 390, height: 844 }); + const picker = dialog.getByRole("combobox", { name: "设置导航" }); + await expect(picker).toBeVisible(); + await expect(dialog.getByRole("tablist")).toBeHidden(); + await picker.selectOption("models"); + const name = dialog.getByRole("textbox", { name: "显示名称", exact: true }); + await expect(name).toHaveValue("Mock Reasoner"); + await name.fill("Unfinished reasoner"); + const add = dialog.locator(".settings-model-provider-link"); + await add.click(); + await expect(name).toHaveValue(""); + await name.fill("Unfinished new model"); + await dialog.getByRole("button", { name: /Mock Reasoner/ }).click(); + await expect(name).toHaveValue("Unfinished reasoner"); + await add.click(); + await expect(name).toHaveValue("Unfinished new model"); + expect(configurationReads).toBe(1); + expect( + await dialog.evaluate( + (element) => element.scrollWidth <= element.clientWidth, + ), + ).toBe(true); + await page.screenshot({ + path: testInfo.outputPath("settings-model-mobile.png"), + }); +}); diff --git a/tests/web/side-conversation-navigation.spec.ts b/tests/web/side-conversation-navigation.spec.ts new file mode 100644 index 00000000..b942ceca --- /dev/null +++ b/tests/web/side-conversation-navigation.spec.ts @@ -0,0 +1,138 @@ +// @vitest-environment jsdom +import { + act, + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import { createElement } from "react"; +import { afterEach, expect, it, vi } from "vitest"; +import type { WebSubagentDetail } from "../../extensions/shared/web-observer-registry.ts"; +import { Providers } from "../../web/ui/src/app/providers.tsx"; +import { WorkbarPanel } from "../../web/ui/src/features/workbar/WorkbarPanel.tsx"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; +import { i18n } from "../../web/ui/src/i18n.ts"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); +const detail = (id: string): WebSubagentDetail => ({ + kind: "subagents", + id, + title: `Conversation ${id}`, + status: "done", + origin: "btw", + createdAt: 1, + cwd: "/workspace", + prompt: `Question ${id}`, + transcript: [], + liveTools: [], + finalText: `Response ${id}`, + truncated: false, + omittedEntries: 0, +}); +function panel() { + vi.spyOn(WebClient.prototype, "subagentDetail").mockImplementation( + async (_session, id) => ({ sessionId: "session-a", detail: detail(id) }), + ); + return render( + createElement( + Providers, + null, + createElement(WorkbarPanel, { + visible: true, + requestedTool: "side-conversation", + requestRevision: 0, + sessionId: "session-a", + cwd: "/workspace", + capabilities: { + subagents: { + items: [detail("A"), detail("B")], + truncated: false, + omitted: 0, + }, + }, + messages: [], + review: { + result: null, + loading: false, + error: null, + refresh: async () => {}, + }, + conversationCollapsed: false, + onRestoreConversation: () => {}, + onBeforeArtifactOpen: () => {}, + onClose: () => {}, + }), + ), + ); +} +function choose(id: string) { + fireEvent.click( + screen.getByRole("button", { name: new RegExp(`Conversation ${id}`) }), + ); +} +function back() { + fireEvent.click( + screen.getByRole("button", { name: i18n.t("backToSideConversations") }), + ); +} +const input = () => + screen.getByPlaceholderText( + i18n.t("continueSideConversation"), + ); + +it("keeps the user's navigation when an earlier send returns without cancelling the background task", async () => { + let finish!: (value: { + sessionId: string; + detail: WebSubagentDetail; + }) => void; + const send = vi + .spyOn(WebClient.prototype, "subagentAction") + .mockImplementation( + () => + new Promise((resolve) => { + finish = resolve; + }), + ); + panel(); + choose("B"); + fireEvent.change(input(), { target: { value: "Retained draft B" } }); + back(); + choose("A"); + fireEvent.change(input(), { target: { value: "Follow up A" } }); + fireEvent.click(screen.getByRole("button", { name: i18n.t("send") })); + await waitFor(() => expect(send).toHaveBeenCalledOnce()); + back(); + choose("B"); + expect(send.mock.calls[0]![2]?.aborted).toBe(false); + await act(async () => + finish({ sessionId: "session-a", detail: detail("A") }), + ); + expect(screen.getByText("Question B")).toBeTruthy(); + expect(screen.queryByText("Question A")).toBeNull(); + expect(input().value).toBe("Retained draft B"); + expect(send).toHaveBeenCalledOnce(); + back(); + choose("A"); + expect(input().value).toBe(""); +}); + +it("keeps unsent drafts attached to their side conversation", async () => { + panel(); + choose("A"); + fireEvent.change(input(), { target: { value: "Draft A" } }); + back(); + choose("B"); + expect(input().value).toBe(""); + fireEvent.change(input(), { target: { value: "Draft B" } }); + back(); + choose("A"); + expect(input().value).toBe("Draft A"); + back(); + choose("B"); + expect(input().value).toBe("Draft B"); +}); diff --git a/tests/web/slash-command-menu.spec.ts b/tests/web/slash-command-menu.spec.ts index 4478cfff..2a18afd5 100644 --- a/tests/web/slash-command-menu.spec.ts +++ b/tests/web/slash-command-menu.spec.ts @@ -1,6 +1,12 @@ // @vitest-environment jsdom -import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; import { createElement } from "react"; import { I18nextProvider } from "react-i18next"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -43,13 +49,22 @@ const commands: WebCommandSummary[] = [ name: "ps", description: "List background terminals", source: "extension", - availability: "unsupported", + availability: "available", + action: "terminal", }, { name: "btw", description: "Keeps working while asking a side question", source: "extension", - availability: "unsupported", + availability: "available", + action: "side-conversation", + }, + { + name: "openpi-setup", + description: "Configure OpenPI", + source: "extension", + availability: "available", + argumentHint: "[request]", }, ]; @@ -60,6 +75,7 @@ function snapshot(): WebSnapshot { generatedAt: "2026-09-09T00:00:00Z", cursor: 1, currentSessionId: "session-1", + currentSessionPath: "/tmp/workspace/session.jsonl", workspaces: [{ path: "/tmp/workspace", name: "Workspace", current: true }], sessions: [], selectedSession: { @@ -94,11 +110,15 @@ function renderComposer( draft?: boolean; landing?: boolean; commandDiscovery?: Partial; + onCommandAction?: ( + action: NonNullable, + ) => boolean; } = {}, ) { const store = createWebStore(); const discoverCommands = vi.fn(async () => {}); const sendPrompt = vi.fn(async () => true); + const onCommandAction = options.onCommandAction ?? vi.fn(() => true); const props = { snapshot: snapshot(), selectedWorkspace: "/tmp/workspace", @@ -112,6 +132,7 @@ function renderComposer( turnTerminalStatus: null, pendingFollowUpsReceipt: null, thinkingPendingLevel: null, + onCommandAction, commandDiscovery: { sessionId: "session-1", status: "ready" as const, @@ -130,7 +151,7 @@ function renderComposer( render( createElement(I18nextProvider, { i18n }, createElement(Composer, props)), ); - return { discoverCommands, sendPrompt }; + return { discoverCommands, sendPrompt, onCommandAction }; } function enterCommand(value: string) { @@ -145,17 +166,21 @@ function enterCommand(value: string) { } describe("Web slash command discovery", () => { - it("shows only runnable commands until a query asks for an unsupported one", () => { + it("lists runnable commands first and keeps unsupported commands discoverable", () => { expect(filterWebCommands(commands, "").map((item) => item.name)).toEqual([ "review", "release", + "ps", + "btw", + "openpi-setup", + "extension:setup", ]); expect( filterWebCommands(commands, "review").map((item) => item.name), ).toEqual(["review"]); expect( filterWebCommands(commands, "configure").map((item) => item.name), - ).toEqual(["extension:setup"]); + ).toEqual(["openpi-setup", "extension:setup"]); expect( filterWebCommands(commands, "skill").map((item) => item.name), ).toEqual(["release"]); @@ -180,14 +205,21 @@ describe("Web slash command discovery", () => { expect(screen.queryByRole("listbox")).toBeNull(); }); - it("summarizes a discovery result with no runnable Web commands", () => { + it("shows the command and its reason instead of an empty dead end when all commands are unsupported", () => { renderComposer({ commandDiscovery: { commands: [commands[0]!], totalAvailable: 1 }, }); enterCommand("/"); - expect(screen.getByText(i18n.t("commandsNoAvailable"))).toBeTruthy(); - expect(screen.queryByRole("listbox")).toBeNull(); + expect( + screen.getByRole("option", { + name: /extension:setup/u, + }).disabled, + ).toBe(true); + expect( + screen.getByText(i18n.t("commandUnavailable_not_integrated")), + ).toBeTruthy(); + expect(screen.queryByText(i18n.t("commandsNoAvailable"))).toBeNull(); }); it("keeps no-match distinct when all discovered commands are unsupported", () => { @@ -307,4 +339,88 @@ describe("Web slash command discovery", () => { expect(screen.getByText(i18n.t("commandSessionRequired"))).toBeTruthy(); expect(discoverCommands).not.toHaveBeenCalled(); }); + + it.each([ + ["ps", "terminal"], + ["lg", "review"], + ["subagents", "subagents"], + ["usage", "runtime"], + ["btw", "side-conversation"], + ] as const)( + "completes then opens /%s without sending a model prompt", + (name, action) => { + const { sendPrompt, onCommandAction } = renderComposer({ + commandDiscovery: { + commands: [ + { name, source: "extension", availability: "available", action }, + ], + }, + }); + const input = enterCommand(`/${name}`); + fireEvent.keyDown(input, { key: "Enter" }); + expect(input.value).toBe(`/${name} `); + expect(onCommandAction).not.toHaveBeenCalled(); + fireEvent.keyDown(input, { key: "Enter" }); + expect(onCommandAction).toHaveBeenCalledWith(action); + expect(sendPrompt).not.toHaveBeenCalled(); + expect(input.value).toBe(""); + }, + ); + + it("keeps panel command arguments and attachments instead of dropping them", async () => { + const { sendPrompt, onCommandAction } = renderComposer(); + const input = enterCommand("/ps retain these arguments"); + fireEvent.keyDown(input, { key: "Enter" }); + expect(screen.getByRole("alert").textContent).toBe( + i18n.t("commandPanelArguments"), + ); + expect(input.value).toBe("/ps retain these arguments"); + fireEvent.change(input, { target: { value: "/ps " } }); + const file = new File( + [new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])], + "image.png", + { type: "image/png" }, + ); + fireEvent.change( + input.closest("form")!.querySelector('input[type="file"]')!, + { target: { files: [file] } }, + ); + await waitFor(() => expect(screen.getByText("image.png")).toBeTruthy()); + fireEvent.keyDown(input, { key: "Enter" }); + expect(screen.getByRole("alert").textContent).toBe( + i18n.t("commandPanelArguments"), + ); + expect(screen.getByText("image.png")).toBeTruthy(); + expect(input.value).toBe("/ps "); + expect(onCommandAction).not.toHaveBeenCalled(); + expect(sendPrompt).not.toHaveBeenCalled(); + }); + + it("retains the command if its panel cannot open", () => { + const action = vi.fn(() => false); + const { sendPrompt } = renderComposer({ onCommandAction: action }); + const input = enterCommand("/ps "); + fireEvent.keyDown(input, { key: "Enter" }); + expect(action).toHaveBeenCalledWith("terminal"); + expect(input.value).toBe("/ps "); + expect(screen.getByRole("alert").textContent).toBe( + i18n.t("commandPanelUnavailable"), + ); + expect(sendPrompt).not.toHaveBeenCalled(); + }); + + it("executes canonical setup through Pi and rejects unsupported commands with arguments", () => { + const { sendPrompt } = renderComposer(); + const input = enterCommand("/extension:setup inspect this"); + fireEvent.keyDown(input, { key: "Enter" }); + expect(screen.getByRole("alert").textContent).toContain( + i18n.t("commandUnavailable_not_integrated"), + ); + expect(sendPrompt).not.toHaveBeenCalled(); + fireEvent.change(input, { + target: { value: "/openpi-setup inspect settings" }, + }); + fireEvent.keyDown(input, { key: "Enter" }); + expect(sendPrompt).toHaveBeenCalledWith("/openpi-setup inspect settings"); + }); }); diff --git a/tests/web/start-test-server.mjs b/tests/web/start-test-server.mjs new file mode 100644 index 00000000..4625d656 --- /dev/null +++ b/tests/web/start-test-server.mjs @@ -0,0 +1,20 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); +// Seed only this test process. Output cleanup, earlier runs and user settings +// must never determine which OpenPI commands this server loads. +const agentDirectory = mkdtempSync(join(tmpdir(), "openpi-web-e2e-agent-")); +process.once("exit", () => rmSync(agentDirectory, { recursive: true, force: true })); +process.env.PI_CODING_AGENT_DIR = agentDirectory; +writeFileSync(join(agentDirectory, "settings.json"), `${JSON.stringify({ packages: [repositoryRoot] })}\n`, { mode: 0o600 }); +execFileSync(process.execPath, [join(repositoryRoot, "scripts/provenance.mjs")], { + cwd: repositoryRoot, + env: process.env, + stdio: "inherit", +}); +process.argv[1] = join(repositoryRoot, "bin/openpi.js"); +await import("../../bin/openpi.js"); diff --git a/tests/web/subagent-panel.spec.ts b/tests/web/subagent-panel.spec.ts index 2649e354..646ed1dc 100644 --- a/tests/web/subagent-panel.spec.ts +++ b/tests/web/subagent-panel.spec.ts @@ -375,6 +375,7 @@ function snapshot(): WebSnapshot { generatedAt: "2026-09-18T00:00:00Z", cursor: 1, currentSessionId: "session-a", + currentSessionPath: "/workspace/session.jsonl", workspaces: [], sessions: [], models: [], @@ -608,6 +609,7 @@ it.each([true, false])( it("does not borrow a current Session child's status for the same ID in historical conversation", () => { const value = snapshot(); value.currentSessionId = "different-active-parent"; + value.currentSessionPath = "/workspace/different-parent.jsonl"; const inspect = vi.fn(); const view = render( createElement( diff --git a/tests/web/thinking-e2e-support.ts b/tests/web/thinking-e2e-support.ts index 7b367d05..bd6989e4 100644 --- a/tests/web/thinking-e2e-support.ts +++ b/tests/web/thinking-e2e-support.ts @@ -118,6 +118,7 @@ export async function installThinkingFixture( } const snapshot = structuredClone(baseSnapshot); snapshot.currentSessionId = MOCK_SESSION_ID; + snapshot.currentSessionPath = MOCK_SESSION_PATH; snapshot.sessions = [ { id: MOCK_SESSION_ID, diff --git a/tests/web/turn-changes-history.e2e.ts b/tests/web/turn-changes-history.e2e.ts new file mode 100644 index 00000000..14ac6c8d --- /dev/null +++ b/tests/web/turn-changes-history.e2e.ts @@ -0,0 +1,157 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { SessionManager } from "@earendil-works/pi-coding-agent"; +import { expect, test } from "@playwright/test"; +import { WebHost } from "../../web/host/web-host.ts"; +import { + WEB_TURN_CHANGES_ENTRY, + type WebTurnChangesDetail, +} from "../../web/protocol/turn-changes.ts"; +import { projectWebModelSearch } from "../../web/runtime/model-discovery.ts"; +import type { WebRuntimeController } from "../../web/runtime/types.ts"; + +const usage = { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, +}; + +test("saved per-turn review and full native message load at desktop and mobile widths", async ({ + browser, +}, testInfo) => { + const cwd = await mkdtemp(join(tmpdir(), "openpi-turn-ui-")); + const manager = SessionManager.inMemory(cwd); + const first = manager.appendMessage({ + role: "user", + content: "Update the report", + timestamp: 1, + }); + manager.appendMessage({ + role: "assistant", + content: [{ type: "text", text: "The report is updated." }], + api: "openai-responses", + provider: "fixture", + model: "fixture", + usage, + stopReason: "stop", + timestamp: 2, + }); + const changes: WebTurnChangesDetail = { + version: 1, + sessionId: manager.getSessionId(), + promptEntryId: first, + state: "complete", + fileCount: 5, + additions: 15, + deletions: 5, + files: Array.from({ length: 5 }, (_, index) => ({ + path: `src/report-${index + 1}.ts`, + status: "modified" as const, + additions: index + 1, + deletions: 1, + diff: `@@ -1 +1 @@\n-old-${index}\n+saved-turn-${index}`, + diffLoaded: true, + diffTruncated: false, + })), + }; + manager.appendCustomEntry(WEB_TURN_CHANGES_ENTRY, changes); + const longMessage = `Initial question: ${"long message content ".repeat(800)}END_OF_NATIVE_MESSAGE`; + manager.appendMessage({ role: "user", content: longMessage, timestamp: 3 }); + manager.appendMessage({ + role: "assistant", + content: [{ type: "text", text: "A separate, later answer." }], + api: "openai-responses", + provider: "fixture", + model: "fixture", + usage, + stopReason: "stop", + timestamp: 4, + }); + const runtime: WebRuntimeController = { + cwd, + workspaceSelected: true, + sessionDirectory: cwd, + sessionManager: manager, + isIdle: () => true, + getActiveTurn: () => undefined, + listModels: () => [], + searchModels: (query, limit) => projectWebModelSearch([], query, limit), + setModel: async () => { + throw new Error("unused"); + }, + newSession: async () => ({ + cancelled: true, + sessionId: manager.getSessionId(), + }), + switchSession: async () => ({ cancelled: true }), + cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), + subscribe: () => () => {}, + dispose: async () => {}, + sendPrompt: async () => { + throw new Error("Fixture is read only"); + }, + }; + const host = new WebHost({ runtime }); + const context = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + locale: "zh-CN", + }); + const page = await context.newPage(); + try { + await host.start(); + await page.goto(host.origin); + const card = page + .locator(".conversation-turn") + .first() + .locator(".turn-changes"); + await expect(card).toContainText("此轮期间 5 个文件发生变化"); + await expect( + card.locator(".turn-changes-list .turn-changes-file"), + ).toHaveCount(3); + await card.getByRole("button", { name: "再显示 2 个文件" }).click(); + await expect( + card.locator(".turn-changes-list .turn-changes-file"), + ).toHaveCount(5); + await card.getByRole("button", { name: "审阅本轮变更" }).click(); + await expect(card.getByRole("figure", { name: "变更差异" })).toContainText( + "saved-turn-0", + ); + await expect(page.locator(".session-changes-trigger")).toHaveCount(0); + await page.screenshot({ + path: testInfo.outputPath("turn-review-desktop.png"), + }); + await card.getByRole("button", { name: "返回本轮变更" }).click(); + + const full = page.getByRole("button", { name: "加载完整消息" }); + await expect(full).toHaveCount(1); + await expect(page.getByText("END_OF_NATIVE_MESSAGE")).toHaveCount(0); + await full.click(); + await expect(page.getByText("END_OF_NATIVE_MESSAGE")).toBeVisible(); + await page.setViewportSize({ width: 390, height: 844 }); + await card.scrollIntoViewIfNeeded(); + await expect(card).toBeVisible(); + expect( + await card.evaluate( + (element) => element.scrollWidth <= element.clientWidth + 1, + ), + ).toBe(true); + const jump = page.getByRole("button", { name: "跳至最新" }); + await expect(jump).toBeVisible(); + const jumpBox = await jump.boundingBox(); + const composerBox = await page.locator(".composer").boundingBox(); + expect( + jumpBox && composerBox && jumpBox.y + jumpBox.height < composerBox.y, + ).toBe(true); + await page.screenshot({ + path: testInfo.outputPath("turn-review-mobile.png"), + }); + } finally { + await context.close(); + await host.stop(); + await rm(cwd, { recursive: true, force: true }); + } +}); diff --git a/tests/web/turn-changes-ui.spec.tsx b/tests/web/turn-changes-ui.spec.tsx new file mode 100644 index 00000000..ea2bc245 --- /dev/null +++ b/tests/web/turn-changes-ui.spec.tsx @@ -0,0 +1,139 @@ +// @vitest-environment jsdom +import { act, cleanup, fireEvent, render, screen, within } from "@testing-library/react"; +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import type { WebTurnChanges, WebTurnChangesDetail } from "../../web/protocol/turn-changes.ts"; +import type { WebSnapshot } from "../../web/protocol/types.ts"; +import { TurnChangesCard } from "../../web/ui/src/features/transcript/TurnChangesCard.tsx"; +import { Transcript } from "../../web/ui/src/features/transcript/Transcript.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; + +afterEach(() => { cleanup(); vi.restoreAllMocks(); }); + +const files = Array.from({ length: 5 }, (_, index) => ({ + path: `src/feature/file-${index + 1}.ts`, + status: "modified" as const, + additions: index + 1, + deletions: 1, +})); + +const changes: WebTurnChanges = { + version: 1, + sessionId: "session", + promptEntryId: "prompt-1", + state: "complete", + fileCount: 5, + files, + additions: 15, + deletions: 5, +}; + +function withI18n(element: ReturnType) { + return createElement(I18nextProvider, { i18n }, element); +} + +function snapshot(entries: NonNullable["entries"]): WebSnapshot { + const truncation = { + truncated: false, + entriesOmitted: 0, + messagesTruncated: 0, + messagePartsOmitted: 0, + maxBytes: 2 * 1024 * 1024, + }; + return { + protocolVersion: 1, + preferences: { theme: "system" }, + generatedAt: "2026-09-23T00:00:00Z", + cursor: 1, + currentSessionId: "session", + currentSessionPath: "/tmp/session", + workspaces: [], + sessions: [], + models: [], + runtime: { status: "idle", capabilities: {} }, + truncation: { truncated: false, sessionsOmitted: 0, workspacesOmitted: 0, modelsOmitted: 0, maxBytes: 4 * 1024 * 1024, bytes: 0 }, + selectedSession: { id: "session", path: "/tmp/session", cwd: "/tmp", entries, bytes: 1, truncation }, + }; +} + +function transcript(entries: NonNullable["entries"]) { + return withI18n(createElement(Transcript, { + snapshot: snapshot(entries), + liveMessages: [], + liveRunning: false, + livePhase: "idle" as const, + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + })); +} + +it("attaches a saved change receipt to its native prompt, not an adjacent turn", () => { + const entries = [ + { id: "prompt-1", type: "message" as const, timestamp: "2026-09-23T00:00:00Z", message: { role: "user", content: "Edit files" } }, + { id: "answer-1", type: "message" as const, timestamp: "2026-09-23T00:00:01Z", message: { role: "assistant", content: "Done." } }, + { id: "changes-1", type: "custom" as const, timestamp: "2026-09-23T00:00:02Z", turnChanges: changes }, + { id: "prompt-2", type: "message" as const, timestamp: "2026-09-23T00:00:03Z", message: { role: "user", content: "Explain" } }, + { id: "answer-2", type: "message" as const, timestamp: "2026-09-23T00:00:04Z", message: { role: "assistant", content: "Explanation." } }, + ]; + const { container, rerender } = render(transcript(entries)); + const turns = container.querySelectorAll(".conversation-turn"); + expect(turns).toHaveLength(2); + expect(turns[0]?.querySelector(".final-response")?.nextElementSibling?.classList.contains("turn-changes")).toBe(true); + expect(turns[0]?.querySelector(".turn-changes")?.textContent).toContain("5 files changed during this turn"); + expect(turns[1]?.querySelector(".turn-changes")).toBeNull(); + + rerender(transcript(entries.slice(1))); + expect(container.querySelector(".turn-changes")).toBeNull(); + rerender(transcript(entries)); + expect(container.querySelectorAll(".turn-changes")).toHaveLength(1); +}); + +it("shows a bounded file summary and reviews the saved turn diff on demand", async () => { + const detail: WebTurnChangesDetail = { + ...changes, + files: files.map((file, index) => ({ ...file, diff: `@@ -1 +1 @@\n-old\n+saved-turn-${index}`, diffTruncated: false, diffLoaded: true })), + }; + const read = vi.spyOn(WebClient.prototype, "turnChanges") + .mockResolvedValueOnce({ ok: true, changes: detail }); + const { container } = render(withI18n(createElement(TurnChangesCard, { + changes, sessionId: "session", sessionPath: "/tmp/session", + }))); + expect(container.querySelectorAll(".turn-changes-list .turn-changes-file")).toHaveLength(3); + expect(screen.getByText("+15")).toBeTruthy(); + fireEvent.click(screen.getByRole("button", { name: /Show 2 more files/u })); + expect(container.querySelectorAll(".turn-changes-list .turn-changes-file")).toHaveLength(5); + fireEvent.click(screen.getByRole("button", { name: "Review turn changes" })); + await act(async () => { await Promise.resolve(); await Promise.resolve(); }); + expect(read).toHaveBeenNthCalledWith(1, "session", "/tmp/session", "prompt-1", expect.any(AbortSignal)); + expect(read).toHaveBeenCalledTimes(1); + expect(within(screen.getByRole("figure", { name: "Change diff" })).getByText("saved-turn-0")).toBeTruthy(); + fireEvent.click(screen.getByRole("button", { name: "Back to turn changes" })); + expect(container.querySelectorAll(".turn-changes-list .turn-changes-file")).toHaveLength(5); +}); + +it("does not claim exact totals for partial evidence or render unknown evidence", () => { + const { container, rerender } = render(withI18n(createElement(TurnChangesCard, { + changes: { ...changes, state: "partial", fileCount: null }, sessionId: "session", sessionPath: "/tmp/session", + }))); + expect(screen.getByText("At least 5 files changed during this turn")).toBeTruthy(); + expect(screen.getByText("This turn's change record is incomplete.")).toBeTruthy(); + expect(container.querySelector(".turn-changes-totals")).toBeNull(); + rerender(withI18n(createElement(TurnChangesCard, { + changes: { ...changes, state: "unavailable", files: [], fileCount: null }, sessionId: "session", sessionPath: "/tmp/session", + }))); + expect(container.querySelector(".turn-changes")).toBeNull(); + expect(screen.getByText("Unable to verify file changes for this turn.")).toBeTruthy(); + rerender(withI18n(createElement(TurnChangesCard, { + changes: { ...changes, state: "partial", files: [], fileCount: null }, sessionId: "session", sessionPath: "/tmp/session", + }))); + expect(screen.getByText("This turn's change record is incomplete.")).toBeTruthy(); + rerender(withI18n(createElement(TurnChangesCard, { + changes: { ...changes, files: [], fileCount: 0, additions: 0, deletions: 0 }, sessionId: "session", sessionPath: "/tmp/session", + }))); + expect(container.querySelector(".turn-changes-unavailable")).toBeNull(); +}); diff --git a/tests/web/turn-changes.test.ts b/tests/web/turn-changes.test.ts new file mode 100644 index 00000000..c37ed53f --- /dev/null +++ b/tests/web/turn-changes.test.ts @@ -0,0 +1,217 @@ +import assert from "node:assert/strict"; +import { execFile } from "node:child_process"; +import { + mkdir, + mkdtemp, + readFile, + rename, + rm, + writeFile, +} from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { after, test } from "node:test"; +import { promisify } from "node:util"; +import { + SessionManager, + type ExtensionAPI, +} from "@earendil-works/pi-coding-agent"; +import { createTurnChangeRecorder } from "../../web/runtime/turn-changes.ts"; +import { + WEB_TURN_CHANGES_ENTRY, + readTurnChangesDetail, +} from "../../web/protocol/turn-changes.ts"; + +const execFileAsync = promisify(execFile); +const roots = new Set(); +after(async () => { + await Promise.all( + [...roots].map((root) => rm(root, { recursive: true, force: true })), + ); +}); + +async function repository() { + const root = await mkdtemp(join(tmpdir(), "openpi-turn-record-")); + roots.add(root); + await execFileAsync("git", ["-C", root, "init", "-b", "main"]); + await execFileAsync("git", [ + "-C", + root, + "config", + "user.name", + "OpenPI Test", + ]); + await execFileAsync("git", [ + "-C", + root, + "config", + "user.email", + "openpi@example.invalid", + ]); + await writeFile(join(root, "file.txt"), "initial\n"); + await execFileAsync("git", ["-C", root, "add", "file.txt"]); + await execFileAsync("git", ["-C", root, "commit", "-m", "initial"]); + return root; +} + +async function sessionDirectory() { + const directory = await mkdtemp(join(tmpdir(), "openpi-turn-sessions-")); + roots.add(directory); + return directory; +} + +function recorderEvents(manager: SessionManager, root: string) { + const events = new Map unknown>(); + const recorder = createTurnChangeRecorder(manager, root); + if (typeof recorder === "function") + throw new Error("Expected a named extension"); + recorder.factory({ + on(name: string, callback: (event: unknown) => unknown) { + events.set(name, callback); + }, + } as ExtensionAPI); + const emit = async (name: string, event: unknown = { type: name }) => { + const callback = events.get(name); + if (!callback) throw new Error(`No ${name} handler`); + await callback(event); + }; + const prompt = async (text: string) => { + const message = { + role: "user" as const, + content: text, + timestamp: Date.now(), + }; + await emit("message_start", { type: "message_start", message }); + return manager.appendMessage(message); + }; + const answer = (text: string) => + manager.appendMessage({ + role: "assistant", + content: [{ type: "text", text }], + api: "openai-responses", + provider: "openai", + model: "test", + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp: Date.now(), + }); + return { emit, prompt, answer }; +} + +function changes(manager: SessionManager) { + return manager + .getBranch() + .filter( + (entry) => + entry.type === "custom" && entry.customType === WEB_TURN_CHANGES_ENTRY, + ) + .map((entry) => + entry.type === "custom" ? readTurnChangesDetail(entry.data) : undefined, + ); +} + +test("recorder finalizes each user prompt before a queued follow-up and survives reload", async () => { + const root = await repository(); + await writeFile(join(root, "file.txt"), "preexisting\n"); + await writeFile(join(root, "untouched.txt"), "preexisting untracked\n"); + const sessions = await sessionDirectory(); + const manager = SessionManager.create(root, sessions); + const events = recorderEvents(manager, root); + const firstId = await events.prompt("Edit the file"); + await writeFile(join(root, "file.txt"), "first\n"); + events.answer("First edit done"); + const secondId = await events.prompt("Edit it again"); + await writeFile(join(root, "file.txt"), "second\n"); + events.answer("Second edit done"); + await events.emit("agent_settled"); + + const results = changes(manager); + assert.equal(results.length, 2); + assert.equal(results[0]?.promptEntryId, firstId); + assert.equal(results[0]?.state, "complete"); + assert.equal(results[0]?.files[0]?.path, "file.txt"); + assert.match(results[0]?.files[0]?.diff ?? "", /\+first/u); + assert.match(results[0]?.files[0]?.diff ?? "", /-preexisting/u); + assert.ok( + results.every((result) => + result?.files.every((file) => file.path !== "untouched.txt"), + ), + ); + assert.equal(results[1]?.promptEntryId, secondId); + assert.equal(results[1]?.state, "complete"); + assert.match(results[1]?.files[0]?.diff ?? "", /\+second/u); + assert.match(results[1]?.files[0]?.diff ?? "", /-first/u); + + const file = manager.getSessionFile(); + assert.ok(file); + assert.ok((await readFile(file, "utf8")).includes(WEB_TURN_CHANGES_ENTRY)); + const reopened = SessionManager.open(file, sessions); + assert.deepEqual(changes(reopened), results); +}); + +test("recorder persists zero changes separately from an unavailable baseline", async () => { + const root = await repository(); + const manager = SessionManager.create(root, await sessionDirectory()); + const events = recorderEvents(manager, root); + const noChangeId = await events.prompt("Inspect only"); + events.answer("Nothing changed"); + await events.emit("agent_settled"); + assert.deepEqual(changes(manager)[0], { + version: 1, + sessionId: manager.getSessionId(), + promptEntryId: noChangeId, + state: "complete", + fileCount: 0, + files: [], + additions: 0, + deletions: 0, + }); + + // A missing Git repository cannot be mistaken for a clean turn. + await rename(join(root, ".git"), join(root, ".git-disabled")); + const unavailableId = await events.prompt("This cannot be observed"); + events.answer("Cannot verify a change"); + await events.emit("agent_settled"); + const latest = changes(manager)[1]; + assert.equal(latest?.promptEntryId, unavailableId); + assert.equal(latest?.state, "unavailable"); + assert.equal(latest?.fileCount, null); +}); + +test("recorder does not report a changed repository as a verified turn", async () => { + const root = await repository(); + const nested = join(root, "nested"); + await mkdir(nested); + const manager = SessionManager.create(nested, await sessionDirectory()); + const events = recorderEvents(manager, nested); + const id = await events.prompt("Change the nested project"); + await execFileAsync("git", ["-C", nested, "init", "-b", "main"]); + events.answer("Repository changed"); + await events.emit("agent_settled"); + assert.equal(changes(manager)[0]?.promptEntryId, id); + assert.equal(changes(manager)[0]?.state, "unavailable"); +}); + +test("large turn diffs persist as partial evidence instead of an exact file count", async () => { + const root = await repository(); + const manager = SessionManager.create(root, await sessionDirectory()); + const events = recorderEvents(manager, root); + const id = await events.prompt("Write a large change"); + await writeFile(join(root, "file.txt"), `${"x".repeat(300_000)}\n`); + events.answer("Large change done"); + await events.emit("agent_settled"); + const result = changes(manager)[0]; + assert.equal(result?.promptEntryId, id); + assert.equal(result?.state, "partial"); + assert.equal(result?.fileCount, null); + assert.equal(result?.files[0]?.path, "file.txt"); + assert.equal(result?.files[0]?.diffTruncated, true); + assert.equal(result?.additions, 1); +}); diff --git a/tests/web/turn-elapsed.spec.ts b/tests/web/turn-elapsed.spec.ts new file mode 100644 index 00000000..33b924bd --- /dev/null +++ b/tests/web/turn-elapsed.spec.ts @@ -0,0 +1,342 @@ +// @vitest-environment jsdom +import { act, cleanup, render, screen } from "@testing-library/react"; +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import type { WebSnapshot } from "../../web/protocol/types.ts"; +import { Transcript } from "../../web/ui/src/features/transcript/Transcript.tsx"; +import { formatTurnDuration } from "../../web/ui/src/features/transcript/TurnElapsed.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; + +afterEach(() => { + cleanup(); + vi.useRealTimers(); + vi.restoreAllMocks(); +}); +const truncation = { + bytes: 0, + maxBytes: 1024, + messagesTruncated: 0, + messagePartsOmitted: 0, + entriesOmitted: 0, + modelsOmitted: 0, + sessionsOmitted: 0, + workspacesOmitted: 0, + truncated: false, +}; +function snapshot(): WebSnapshot { + return { + protocolVersion: 1, + preferences: { theme: "system" }, + generatedAt: new Date().toISOString(), + cursor: 1, + currentSessionId: "session", + currentSessionPath: "/session.jsonl", + workspaces: [], + sessions: [], + models: [], + truncation, + runtime: { + status: "running", + capabilities: {}, + activeTurn: { + sessionId: "session", + sessionPath: "/session.jsonl", + commandId: "a", + epoch: 1, + startedAt: 10000, + elapsedMs: 100000, + }, + }, + selectedSession: { + id: "session", + path: "/session.jsonl", + cwd: "/", + entries: [], + bytes: 0, + truncation, + }, + }; +} +function node(value: WebSnapshot) { + return createElement( + I18nextProvider, + { i18n }, + createElement(Transcript, { + snapshot: value, + liveMessages: [], + liveRunning: false, + livePhase: "idle", + liveRetry: null, + thinkingStarts: {}, + thinkingDurations: {}, + scrollToBottom: 0, + onResend: async () => true, + }), + ); +} + +it("uses one monotonic ticker, reconciles without going backward, and cleans up for another Session", () => { + vi.useFakeTimers(); + const intervals = vi.spyOn(window, "setInterval"); + const clearedIntervals = vi.spyOn(window, "clearInterval"); + let clock = 1000; + vi.spyOn(performance, "now").mockImplementation(() => clock); + const value = snapshot(); + value.selectedSession!.entries = [ + { + id: "thinking", + type: "message", + timestamp: new Date().toISOString(), + message: { + role: "assistant", + content: "", + parts: [ + { type: "thinking", text: "First step" }, + { type: "thinking", text: "Second step" }, + ], + }, + }, + ]; + const view = render(node(value)); + expect(screen.getByRole("timer").textContent).toContain("1m40s"); + expect(intervals).toHaveBeenCalledOnce(); + act(() => { + clock += 2000; + vi.advanceTimersByTime(2000); + }); + expect(screen.getByRole("timer").textContent).toContain("1m42s"); + vi.setSystemTime(new Date(1)); + view.rerender( + node({ + ...value, + runtime: { + ...value.runtime, + activeTurn: { ...value.runtime.activeTurn!, elapsedMs: 90000 }, + }, + }), + ); + expect(screen.getByRole("timer").textContent).toContain("1m42s"); + expect(vi.getTimerCount()).toBe(1); + view.rerender( + node({ + ...value, + selectedSession: { + ...value.selectedSession!, + path: "/copied-session.jsonl", + }, + }), + ); + expect(screen.queryByRole("timer")).toBeNull(); + expect(clearedIntervals).toHaveBeenCalledWith( + intervals.mock.results[0]!.value, + ); + // jsdom queues a one-shot toggle event when the old process details close. + // It is not the elapsed interval; flush only that immediate DOM task. + act(() => vi.advanceTimersByTime(0)); + expect(intervals).toHaveBeenCalledOnce(); + expect(vi.getTimerCount()).toBe(0); +}); + +it("recovers the elapsed value on refresh and resets only for a different turn identity", () => { + vi.useFakeTimers(); + const value = snapshot(); + const view = render(node(value)); + expect(screen.getByRole("timer").textContent).toContain("1m40s"); + view.rerender( + node({ + ...value, + runtime: { + ...value.runtime, + activeTurn: { + ...value.runtime.activeTurn!, + commandId: "b", + epoch: 2, + elapsedMs: 3000, + }, + }, + }), + ); + expect(screen.getByRole("timer").textContent).toContain("3s"); + expect(vi.getTimerCount()).toBe(1); +}); + +it.each(["completed", "failed", "cancelled", "uncertain"] as const)( + "shows fixed persisted elapsed time for a %s run", + (outcome) => { + vi.useFakeTimers(); + const value = snapshot(); + value.runtime = { status: "idle", capabilities: {} }; + value.selectedSession!.entries = [ + { + id: "timing", + type: "custom", + timestamp: "2026-09-22T00:00:00Z", + turnTiming: { + version: 1, + sessionId: "session", + commandId: "a", + epoch: 1, + startedAt: 10000, + finishedAt: 167000, + elapsedMs: 157000, + outcome, + }, + }, + ]; + const view = render(node(value)); + expect( + screen.getByText(i18n.t("turnElapsedFinished", { duration: "2m37s" })), + ).toBeTruthy(); + expect( + view.container + .querySelector(".turn-duration") + ?.getAttribute("data-outcome"), + ).toBe(outcome); + act(() => vi.advanceTimersByTime(60000)); + expect( + screen.getByText(i18n.t("turnElapsedFinished", { duration: "2m37s" })), + ).toBeTruthy(); + expect(vi.getTimerCount()).toBe(0); + }, +); + +it("does not invent a duration from old timestamps or incomplete runtime evidence", () => { + const value = snapshot(); + value.runtime.activeTurn = { + sessionId: "session", + commandId: "legacy", + epoch: 1, + }; + value.selectedSession!.entries = [ + { + id: "old-user", + type: "message", + timestamp: "2020-01-01T00:00:00Z", + message: { role: "user", content: "Old question" }, + }, + ]; + const view = render(node(value)); + expect(screen.queryByRole("timer")).toBeNull(); + expect(view.container.querySelector(".turn-duration")).toBeNull(); +}); + +it("places settled duration after execution details and before the final text response", () => { + const value = snapshot(); + value.runtime = { status: "idle", capabilities: {} }; + value.selectedSession!.entries = [ + { + id: "user", + type: "message", + timestamp: "2026-09-22T00:00:00Z", + message: { role: "user", content: "Question" }, + }, + { + id: "answer", + type: "message", + timestamp: "2026-09-22T00:02:36Z", + message: { + role: "assistant", + content: "Final answer", + stopReason: "stop", + parts: [{ type: "thinking", text: "Verifying the result" }], + }, + }, + { + id: "timing", + type: "custom", + timestamp: "2026-09-22T00:02:37Z", + turnTiming: { + version: 1, + sessionId: "session", + commandId: "a", + epoch: 1, + startedAt: 10000, + finishedAt: 167000, + elapsedMs: 157000, + outcome: "completed", + }, + }, + ]; + const view = render(node(value)); + const process = view.container.querySelector(".process-sequence")!; + const duration = view.container.querySelector(".turn-duration")!; + const answer = view.container.querySelector(".final-response")!; + expect( + process.compareDocumentPosition(duration) & + Node.DOCUMENT_POSITION_FOLLOWING, + ).toBeTruthy(); + expect( + duration.compareDocumentPosition(answer) & Node.DOCUMENT_POSITION_FOLLOWING, + ).toBeTruthy(); +}); + +it("does not move a duration backward across a user or another timing record", () => { + const value = snapshot(); + value.runtime = { status: "idle", capabilities: {} }; + const timing = { + version: 1 as const, + sessionId: "session", + commandId: "a", + epoch: 1, + startedAt: 1000, + finishedAt: 2000, + elapsedMs: 1000, + outcome: "completed" as const, + }; + value.selectedSession!.entries = [ + { + id: "answer", + type: "message", + timestamp: "2026-09-22T00:00:01Z", + message: { + role: "assistant", + content: "Previous answer", + stopReason: "stop", + }, + }, + { + id: "timing-a", + type: "custom", + timestamp: "2026-09-22T00:00:02Z", + turnTiming: timing, + }, + { + id: "timing-b", + type: "custom", + timestamp: "2026-09-22T00:00:03Z", + turnTiming: { ...timing, commandId: "b", epoch: 2, elapsedMs: 2000 }, + }, + { + id: "user", + type: "message", + timestamp: "2026-09-22T00:00:04Z", + message: { role: "user", content: "Next question" }, + }, + { + id: "timing-c", + type: "custom", + timestamp: "2026-09-22T00:00:05Z", + turnTiming: { ...timing, commandId: "c", epoch: 3, elapsedMs: 3000 }, + }, + ]; + const view = render(node(value)); + const ordered = [ + ...view.container.querySelectorAll( + ".turn-duration, .message-row.user .message-body, .message-row.response .message-content", + ), + ].map((element) => element.textContent?.trim()); + expect(ordered).toEqual([ + i18n.t("turnElapsedFinished", { duration: "1s" }), + "Previous answer", + i18n.t("turnElapsedFinished", { duration: "2s" }), + "Next question", + i18n.t("turnElapsedFinished", { duration: "3s" }), + ]); +}); + +it("formats the requested running and settled duration styles", () => { + expect(formatTurnDuration(2142000, "zh-CN", true)).toBe("35分钟42秒"); + expect(formatTurnDuration(157000, "zh-CN", false)).toBe("2m37s"); + expect(formatTurnDuration(3601000, "en", true)).toBe("1h0m1s"); +}); diff --git a/tests/web/turn-timing.test.ts b/tests/web/turn-timing.test.ts new file mode 100644 index 00000000..aed7a2f0 --- /dev/null +++ b/tests/web/turn-timing.test.ts @@ -0,0 +1,207 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { SessionManager } from "@earendil-works/pi-coding-agent"; +import { projectEntry } from "../../web/protocol/types.ts"; +import { + readTurnTiming, + WEB_TURN_TIMING_ENTRY, +} from "../../web/protocol/turn-timing.ts"; +import { PiWebRuntime } from "../../web/runtime/pi-runtime.ts"; +import type { WebRuntimeEvent } from "../../web/runtime/types.ts"; + +function harness(sessionManager = SessionManager.inMemory(process.cwd())) { + const session = { sessionManager }; + const events: WebRuntimeEvent[] = []; + const runtime = Object.create(PiWebRuntime.prototype) as { + runtime: { session: typeof session }; + pendingPromptTraces: unknown[]; + activePromptTrace?: { + commandId: string; + sessionId: string; + startedAt: number; + started: boolean; + queued: boolean; + outcome?: "completed" | "failed" | "cancelled" | "uncertain"; + }; + listeners: Set<(event: WebRuntimeEvent) => void>; + nextTurnEpoch: number; + terminalTurnKeys: Set; + turnSettlementWaiters: Map>; + turnAbortOperations: Map>; + getActiveTurn: PiWebRuntime["getActiveTurn"]; + projectEvent: (session: object, event: object) => void; + }; + Object.assign(runtime, { + runtime: { session }, + pendingPromptTraces: [], + listeners: new Set([(event: WebRuntimeEvent) => events.push(event)]), + nextTurnEpoch: 0, + terminalTurnKeys: new Set(), + turnSettlementWaiters: new Map(), + turnAbortOperations: new Map(), + activePromptTrace: { + commandId: "prompt-a", + sessionId: sessionManager.getSessionId(), + startedAt: -10000, + started: false, + queued: false, + }, + }); + return { + runtime, + sessionManager, + events, + emit: (event: object) => runtime.projectEvent(session, event), + }; +} + +test("Pi execution timing excludes queue time, survives clock rollback and persists once at agent_settled", (t) => { + let monotonic = 1000; + let wall = 200000; + t.mock.method(performance, "now", () => monotonic); + t.mock.method(Date, "now", () => wall); + const { runtime, sessionManager, emit, events } = harness(); + sessionManager.appendMessage({ + role: "user", + content: "hello", + timestamp: wall, + }); + emit({ type: "agent_start" }); + assert.equal(runtime.getActiveTurn()?.elapsedMs, 0); + assert.equal(runtime.getActiveTurn()?.startedAt, 200000); + assert.equal( + runtime.getActiveTurn()?.sessionPath, + `current:${sessionManager.getSessionId()}`, + ); + monotonic = 158000; + wall = 100000; + assert.equal(runtime.getActiveTurn()?.elapsedMs, 157000); + emit({ + type: "message_end", + message: { + role: "assistant", + content: [{ type: "text", text: "done" }], + stopReason: "stop", + }, + }); + assert.equal( + sessionManager.getEntries().filter((entry) => entry.type === "custom") + .length, + 0, + ); + emit({ type: "agent_settled" }); + emit({ type: "agent_settled" }); + const records = sessionManager + .getEntries() + .filter((entry) => entry.type === "custom"); + assert.equal(records.length, 1); + const record = records[0]!; + assert.equal(record.customType, WEB_TURN_TIMING_ENTRY); + const timing = readTurnTiming(record.data); + assert.equal(timing?.elapsedMs, 157000); + assert.equal(timing?.outcome, "completed"); + assert.equal(timing?.finishedAt, 100000); + const projection = projectEntry(record); + assert.deepEqual( + "turnTiming" in projection ? projection.turnTiming : undefined, + timing, + ); + assert.equal(sessionManager.buildSessionContext().messages.length, 1); + assert.equal( + events.filter((event) => event.type === "turn_settled").length, + 1, + ); +}); + +for (const outcome of ["failed", "cancelled", "uncertain"] as const) { + test(`timing preserves ${outcome} without changing the terminal outcome`, () => { + const { runtime, sessionManager, emit, events } = harness(); + emit({ type: "agent_start" }); + runtime.activePromptTrace!.outcome = outcome; + emit({ type: "agent_settled" }); + const record = sessionManager + .getEntries() + .find((entry) => entry.type === "custom"); + assert.equal(record && readTurnTiming(record.data)?.outcome, outcome); + assert.equal( + events.find((event) => event.type === "turn_settled")?.detail?.outcome, + outcome, + ); + }); +} + +test("an optional timing write failure cannot suppress Pi's terminal event", (t) => { + const { runtime, sessionManager, emit, events } = harness(); + t.mock.method(sessionManager, "appendCustomEntry", () => { + throw new Error("fixture disk failure"); + }); + emit({ type: "agent_start" }); + runtime.activePromptTrace!.outcome = "cancelled"; + assert.doesNotThrow(() => emit({ type: "agent_settled" })); + assert.equal( + events.find((event) => event.type === "turn_settled")?.detail?.outcome, + "cancelled", + ); + assert.equal(runtime.getActiveTurn(), undefined); +}); + +test("settled timing survives reopening the native Session file without entering model context", async (t) => { + const directory = await mkdtemp(join(tmpdir(), "openpi-turn-timing-")); + t.after(() => rm(directory, { recursive: true, force: true })); + const manager = SessionManager.create(directory, directory); + const { runtime, emit } = harness(manager); + manager.appendMessage({ + role: "user", + content: "hello", + timestamp: Date.now(), + }); + emit({ type: "agent_start" }); + manager.appendMessage({ + role: "assistant", + content: [{ type: "text", text: "done" }], + api: "openai-responses", + provider: "fixture", + model: "fixture", + stopReason: "stop", + timestamp: Date.now(), + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + }); + runtime.activePromptTrace!.outcome = "completed"; + emit({ type: "agent_settled" }); + const reopened = SessionManager.open(manager.getSessionFile()!); + const timings = reopened + .getBranch() + .map((entry) => projectEntry(entry)) + .filter((entry) => "turnTiming" in entry); + assert.equal(timings.length, 1); + assert.equal(timings[0]?.turnTiming?.commandId, "prompt-a"); + assert.equal(reopened.buildSessionContext().messages.length, 2); +}); + +test("unrecognized custom entries and malformed timing do not become duration evidence", () => { + const timing = { + version: 1, + sessionId: "s", + commandId: "c", + epoch: 1, + startedAt: 1000, + finishedAt: 2000, + elapsedMs: 1000, + outcome: "completed", + }; + assert.equal(readTurnTiming({ ...timing, elapsedMs: -1 }), undefined); + assert.equal(readTurnTiming({ ...timing, outcome: "looks-done" }), undefined); + const session = SessionManager.inMemory(process.cwd()); + session.appendCustomEntry("unrelated", timing); + assert.equal("turnTiming" in projectEntry(session.getEntries()[0]!), false); +}); diff --git a/tests/web/web-host.test.ts b/tests/web/web-host.test.ts index a824fd5c..972df44a 100644 --- a/tests/web/web-host.test.ts +++ b/tests/web/web-host.test.ts @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { once } from "node:events"; -import { mkdtemp, rm } from "node:fs/promises"; +import { mkdtemp, rm, writeFile } from "node:fs/promises"; import { request as httpRequest } from "node:http"; import { createConnection } from "node:net"; import { tmpdir } from "node:os"; @@ -16,7 +16,10 @@ import type { GitReviewService } from "../../web/host/git-review.ts"; import type { InteractiveTerminalService } from "../../web/host/interactive-terminal.ts"; import { PiWebAdapter } from "../../web/adapter/pi-adapter.ts"; import type { WebHostOptions } from "../../web/host/web-host.ts"; -import type { WebInteractiveTerminalEvent } from "../../web/protocol/types.ts"; +import { + WEB_BROWSER_TEXT_MAX_LENGTH, + type WebInteractiveTerminalEvent, +} from "../../web/protocol/types.ts"; import { projectWebModelSearch } from "../../web/runtime/model-discovery.ts"; import { type WebRuntimeController, @@ -265,6 +268,7 @@ test("serves workspaces through a runtime isolated from terminal sessions", asyn const planRequest = { sessionId: sessionManager.getSessionId(), + sessionPath: mutationSessionPath(sessionManager), enabled: true, expectedRevision: null, }; @@ -295,6 +299,20 @@ test("serves workspaces through a runtime isolated from terminal sessions", asyn planCalls++; return { status: "planning", revision: "plan-1", hasPrompt: false }; }; + assert.equal( + ( + await postPlan({ + ...planRequest, + sessionPath: "/tmp/copied-session.jsonl", + }) + ).status, + 409, + ); + assert.equal( + (await postPlan({ ...planRequest, sessionPath: undefined })).status, + 400, + ); + assert.equal(planCalls, 0); const switchedPlan = await postPlan(planRequest); assert.equal(switchedPlan.status, 200); assert.deepEqual(await switchedPlan.json(), { @@ -1998,6 +2016,134 @@ async function startTestHost( return { host, launched, headers }; } +test("external file preview requires an explicit authenticated single-file grant", async () => { + const cwd = await mkdtemp(join(tmpdir(), "openpi-external-http-")); + const outside = await mkdtemp(join(tmpdir(), "openpi-external-source-")); + const file = join(outside, "index.ts"); + await writeFile(file, "export const verified = true;"); + const runtime = testRuntime(cwd); + const sessionId = runtime.sessionManager.getSessionId(); + const { host, launched, headers } = await startTestHost(runtime); + const body = JSON.stringify({ + sessionId, + reference: file, + access: "read-external-file", + }); + try { + assert.equal( + ( + await fetch(`${launched.origin}/api/artifacts/authorize-file`, { + method: "POST", + body, + }) + ).status, + 401, + ); + const ordinary = await fetch(`${launched.origin}/api/artifacts/resolve`, { + method: "POST", + headers, + body: JSON.stringify({ sessionId, reference: file, access: "read-file" }), + }); + assert.equal(ordinary.status, 403); + const allowed = await fetch( + `${launched.origin}/api/artifacts/authorize-file`, + { method: "POST", headers, body }, + ); + assert.equal(allowed.status, 200); + const { handle } = await allowed.json(); + const preview = await fetch( + `${launched.origin}/api/artifacts/content?${new URLSearchParams({ sessionId, handle })}`, + { headers }, + ); + assert.equal(preview.status, 200); + assert.equal((await preview.json()).text, "export const verified = true;"); + } finally { + await host.stop(); + await rm(cwd, { recursive: true, force: true }); + await rm(outside, { recursive: true, force: true }); + } +}); + +test("browser frame streaming authenticates, keeps the latest frame and releases subscribers", async () => { + const cwd = await mkdtemp(join(tmpdir(), "openpi-browser-stream-")); + const runtime = testRuntime(cwd); + const sessionId = runtime.sessionManager.getSessionId(); + const state = { + sessionId, + url: "https://example.com/", + title: "Example", + width: 800, + height: 600, + loading: false, + canGoBack: false, + canGoForward: false, + }; + let subscriptions = 0; + const embeddedBrowser: EmbeddedBrowserService = { + open: async () => state, + state: async () => state, + frame: async () => undefined, + action: async () => state, + retain() {}, + async dispose() {}, + async subscribeFrames(_id, listener) { + subscriptions++; + listener({ + data: "old-frame", + mimeType: "image/png", + width: 800, + height: 600, + }); + listener({ + data: "new-frame", + mimeType: "image/png", + width: 800, + height: 600, + }); + return () => { + subscriptions--; + }; + }, + }; + const { host, launched, headers } = await startTestHost(runtime, { + embeddedBrowser, + }); + const controller = new AbortController(); + try { + const url = `${launched.origin}/api/browser/frames?${new URLSearchParams({ sessionId })}`; + assert.equal((await fetch(url)).status, 401); + const response = await fetch(url, { + headers, + signal: AbortSignal.any([controller.signal, AbortSignal.timeout(3_000)]), + }); + assert.equal(response.status, 200); + assert.equal(response.headers.get("content-type"), "text/event-stream"); + const reader = response.body!.getReader(); + let body = ""; + while (!body.includes("new-frame")) { + const { value, done } = await reader.read(); + assert.equal(done, false); + body += new TextDecoder().decode(value); + } + assert.equal(body.includes("old-frame"), false); + assert.equal(subscriptions, 1); + for (let index = 0; index < 3; index++) { + const viewer = await fetch(url, { headers, signal: controller.signal }); + assert.equal(viewer.status, 200); + } + assert.equal((await fetch(url, { headers })).status, 429); + assert.equal(subscriptions, 4); + await reader.cancel(); + controller.abort(); + await host.stop(); + assert.equal(subscriptions, 0); + } finally { + controller.abort(); + await host.stop(); + await rm(cwd, { recursive: true, force: true }); + } +}); + test("exposes an embedded browser and an active-Session interactive terminal", async () => { const cwd = await mkdtemp(join(tmpdir(), "openpi-web-tools-")); const runtime = testRuntime(cwd); @@ -2175,6 +2321,59 @@ test("exposes an embedded browser and an active-Session interactive terminal", a assert.equal(browserAction.status, 200); assert.deepEqual(browserActions, [{ type: "reload" }]); + for (const action of [ + { action: "text", text: "paste 中文\nnext line" }, + { action: "key", event: "down", key: "Tab", modifiers: 8 }, + { + action: "mouse", + event: "move", + x: 20, + y: 20, + button: "left", + buttons: 1, + }, + ]) { + const response = await fetch(`${launched.origin}/api/browser/action`, { + method: "POST", + headers: jsonHeaders, + body: JSON.stringify({ sessionId, ...action }), + }); + assert.equal(response.status, 200); + const { action: type, ...detail } = action; + assert.deepEqual(browserActions.at(-1), { type, ...detail }); + } + const acceptedCount = browserActions.length; + for (const action of [ + { action: "text", text: "" }, + { action: "text", text: "x".repeat(WEB_BROWSER_TEXT_MAX_LENGTH + 1) }, + { action: "text", text: 42 }, + { action: "key", event: "down", key: "Tab", modifiers: -1 }, + { action: "key", event: "down", key: "Tab", modifiers: 16 }, + { action: "key", event: "down", key: "Tab", modifiers: 1.5 }, + { action: "mouse", event: "move", x: 20, y: 20, buttons: 8 }, + { action: "mouse", event: "move", x: 20, y: 20, buttons: -1 }, + { action: "mouse", event: "move", x: 20, y: 20, buttons: 1.5 }, + ]) { + const response = await fetch(`${launched.origin}/api/browser/action`, { + method: "POST", + headers: jsonHeaders, + body: JSON.stringify({ sessionId, ...action }), + }); + assert.equal(response.status, 400); + } + assert.equal(browserActions.length, acceptedCount); + const stalePaste = await fetch(`${launched.origin}/api/browser/action`, { + method: "POST", + headers: jsonHeaders, + body: JSON.stringify({ + sessionId: "another-session", + action: "text", + text: "private draft", + }), + }); + assert.equal(stalePaste.status, 409); + assert.equal(browserActions.length, acceptedCount); + const created = await fetch(`${launched.origin}/api/terminal`, { method: "POST", headers: jsonHeaders, @@ -3604,6 +3803,53 @@ test("stop aborts an open workspace picker", async () => { } }); +test("provider key writes require authentication and sanitize provider failures", async () => { + const cwd = await mkdtemp(join(tmpdir(), "openpi-web-provider-write-")); + const runtime = testRuntime(cwd); + const calls: string[] = []; + runtime.saveProviderKey = async (_session, _provider, key) => { + calls.push(key); + if (key === "fixture-secret-failure") + throw new Error(`Provider echoed ${key}`); + }; + const { host, launched, headers } = await startTestHost(runtime); + try { + const body = JSON.stringify({ + sessionId: runtime.sessionManager.getSessionId(), + provider: "fixture", + apiKey: "fixture-secret-success", + }); + assert.equal( + ( + await fetch(`${launched.origin}/api/providers/api-key`, { + method: "POST", + body, + }) + ).status, + 401, + ); + assert.deepEqual(calls, []); + const saved = await fetch(`${launched.origin}/api/providers/api-key`, { + method: "POST", + headers: { ...headers, "Content-Type": "application/json" }, + body, + }); + assert.equal(saved.status, 200); + assert.doesNotMatch(await saved.text(), /fixture-secret/u); + const failure = await fetch(`${launched.origin}/api/providers/api-key`, { + method: "POST", + headers: { ...headers, "Content-Type": "application/json" }, + body: body.replace("fixture-secret-success", "fixture-secret-failure"), + }); + assert.equal(failure.status, 422); + assert.doesNotMatch(await failure.text(), /fixture-secret-failure/u); + assert.equal(calls.length, 2); + } finally { + await host.stop(); + await rm(cwd, { recursive: true, force: true }); + } +}); + test("stop bounds an authenticated incomplete HTTP request", async () => { const cwd = await mkdtemp(join(tmpdir(), "openpi-web-stop-http-")); const { host, launched, headers } = await startTestHost(testRuntime(cwd)); diff --git a/tests/web/web-protocol.spec.ts b/tests/web/web-protocol.spec.ts index 7551b6db..4935f712 100644 --- a/tests/web/web-protocol.spec.ts +++ b/tests/web/web-protocol.spec.ts @@ -9,6 +9,26 @@ afterEach(() => { vi.unstubAllGlobals(); }); +it("sends only cancellation identity when an active turn includes timing projections", async () => { + const fetcher = vi + .fn() + .mockResolvedValue(new Response(JSON.stringify({ state: "accepted" }))); + vi.stubGlobal("fetch", fetcher); + await new WebClient().cancelActiveTurn({ + sessionId: "s", + commandId: "c", + epoch: 1, + sessionPath: "/session.jsonl", + startedAt: 1000, + elapsedMs: 2000, + }); + expect(JSON.parse(fetcher.mock.calls[0]![1].body)).toEqual({ + sessionId: "s", + commandId: "c", + epoch: 1, + }); +}); + it("settles an evicted question receipt as unknown while retaining controller rejections", async () => { const fetcher = vi .fn() diff --git a/tests/web/web-store.spec.ts b/tests/web/web-store.spec.ts index f633f121..32a68d46 100644 --- a/tests/web/web-store.spec.ts +++ b/tests/web/web-store.spec.ts @@ -161,7 +161,12 @@ it("Plan changes wait for canonical confirmation, prevent duplicate clicks and d expect(store.getState().planSelectionPending).toBe(true); await store.getState().actions.selectPlanMode(true); expect(await store.getState().actions.sendPrompt("wait")).toBe(false); - expect(change).toHaveBeenCalledExactlyOnceWith("session-1", true, null); + expect(change).toHaveBeenCalledExactlyOnceWith( + "session-1", + "/tmp/ws/session.jsonl", + true, + null, + ); const after = snapshot(); after.cursor++; after.runtime.plan = "planning"; @@ -175,6 +180,77 @@ it("Plan changes wait for canonical confirmation, prevent duplicate clicks and d expect(prompt).not.toHaveBeenCalled(); }); +it("does not change Plan mode from a copied Session observer", async () => { + const client = new FakeClient(); + const change = vi.spyOn(client, "setPlanMode"); + const current = snapshot(); + current.currentSessionPath = current.selectedSession!.path; + current.runtime.planRevision = null; + current.selectedSession!.path = "/tmp/ws/copied-session.jsonl"; + current.sessions.push({ + ...current.sessions[0]!, + path: current.selectedSession!.path, + controller: "none", + }); + const store = createWebStore(client); + store.setState({ + snapshot: current, + selectedPath: current.selectedSession!.path, + workspaceDraft: false, + }); + await store.getState().actions.selectPlanMode(true); + expect(change).not.toHaveBeenCalled(); + expect(store.getState().planSelectionPending).toBe(false); +}); + +it("keeps a newer Plan write pending when an older receipt arrives after retaking the same view", async () => { + const client = new FakeClient(); + const initial = snapshot(); + initial.currentSessionPath = initial.selectedSession!.path; + initial.runtime.plan = "inactive"; + initial.runtime.planRevision = null; + client.snapshots.push(Promise.resolve(initial)); + const first = deferred<{ sessionId: string }>(); + const second = deferred<{ sessionId: string }>(); + const change = vi + .spyOn(client, "setPlanMode") + .mockReturnValueOnce(first.promise) + .mockReturnValueOnce(second.promise); + const store = createWebStore(client); + await store.getState().actions.refreshSnapshot(); + const oldWrite = store.getState().actions.selectPlanMode(true); + const observer = structuredClone(initial); + observer.currentSessionId = "other"; + observer.currentSessionPath = "/tmp/ws/other.jsonl"; + observer.sessions[0]!.controller = "none"; + observer.sessions.push({ + ...initial.sessions[0]!, + id: "other", + path: observer.currentSessionPath, + }); + client.snapshots.push(Promise.resolve(observer)); + await store.getState().actions.refreshSnapshot(); + expect(store.getState().planSelectionPending).toBe(false); + const activated = structuredClone(initial); + activated.runtime.plan = "planning"; + activated.runtime.planRevision = "first-plan"; + client.snapshots.push(Promise.resolve(activated)); + await store.getState().actions.selectSession(initial.selectedSession!.path); + const newWrite = store.getState().actions.selectPlanMode(false); + expect(change).toHaveBeenCalledTimes(2); + expect(store.getState().planSelectionPending).toBe(true); + first.resolve({ sessionId: "session-1" }); + await oldWrite; + expect(store.getState().planSelectionPending).toBe(true); + const confirmed = structuredClone(initial); + confirmed.runtime.planRevision = "second-plan"; + client.snapshots.push(Promise.resolve(confirmed)); + second.resolve({ sessionId: "session-1" }); + await newWrite; + expect(store.getState().planSelectionPending).toBe(false); + expect(store.getState().snapshot?.runtime.plan).toBe("inactive"); +}); + class FakeClient extends WebClient { snapshots: Array> = []; snapshotPaths: Array = []; @@ -391,6 +467,260 @@ afterEach(() => { }); describe("OpenPI Web store", () => { + it("retains a valid requested background view without moving the input controller", async () => { + const client = new FakeClient(); + const current = activeSnapshot("controller", "/tmp/ws/controller.jsonl"); + const viewed = activeSnapshot("viewed", "/tmp/ws/viewed.jsonl"); + const background = { + ...current, + sessions: [ + ...current.sessions, + { ...viewed.sessions[0]!, controller: "none" as const }, + ], + selectedSession: viewed.selectedSession, + selectedExecution: { + sessionId: "viewed", + sessionPath: "/tmp/ws/viewed.jsonl", + status: "running" as const, + pendingFollowUps: 2, + liveTools: [], + liveToolsOmitted: 0, + }, + }; + client.snapshots.push( + Promise.resolve(background), + Promise.resolve(current), + ); + const store = createWebStore(client); + store.setState({ + snapshot: viewed, + selectedPath: "/tmp/ws/viewed.jsonl", + liveMessages: [ + { + key: "optimistic-read-view", + message: { role: "user", content: "Queued in viewed Session" }, + optimistic: { + sessionId: "viewed", + sessionPath: "/tmp/ws/viewed.jsonl", + commandId: "read-view", + afterEntryId: null, + admitted: true, + }, + }, + { + key: "old-controller-live", + message: { role: "assistant", content: "Old live fragment" }, + }, + ], + }); + expect(await store.getState().actions.refreshSnapshot()).toBe(true); + expect(client.snapshotPaths).toEqual(["/tmp/ws/viewed.jsonl"]); + expect(store.getState().selectedPath).toBe("/tmp/ws/viewed.jsonl"); + expect(store.getState().snapshot?.currentSessionId).toBe("controller"); + expect(store.getState().snapshot?.selectedExecution?.pendingFollowUps).toBe( + 2, + ); + expect(store.getState().liveMessages.map((entry) => entry.key)).toEqual([ + "optimistic-read-view", + ]); + expect( + await store.getState().actions.sendPrompt("must stay read only"), + ).toBe(false); + expect(client.prompts).toEqual([]); + store.getState().actions.stop(); + }); + + it("keeps both scoped pending submissions when one same-text native message arrives", async () => { + const client = new FakeClient(); + client.snapshots.push(Promise.resolve(snapshot())); + const stream = eventStreamHarness(); + const store = createWebStore(client, { + consumeEvents: stream.consumeEvents, + }); + store.getState().actions.start(); + try { + await vi.waitFor(() => + expect(stream.consumeEvents).toHaveBeenCalledOnce(), + ); + await store.getState().actions.sendPrompt("继续"); + await store.getState().actions.sendPrompt("继续"); + stream.emit( + runtimeEvent(10, "message_start", { + sessionId: "session-1", + messageKey: "native-user", + message: { role: "user", content: "继续" }, + }), + ); + const pending = store + .getState() + .liveMessages.filter((entry) => entry.optimistic); + expect(pending).toHaveLength(2); + expect(pending[0]?.optimistic).toMatchObject({ + sessionId: "session-1", + sessionPath: "/tmp/ws/session.jsonl", + }); + for (let index = 0; index < 12; index++) + stream.emit( + runtimeEvent(11 + index, "message_start", { + sessionId: "session-1", + messageKey: `live-${index}`, + message: { role: "assistant", content: `Step ${index}` }, + }), + ); + expect( + store.getState().liveMessages.filter((entry) => entry.optimistic), + ).toHaveLength(2); + expect( + store.getState().liveMessages.filter((entry) => !entry.optimistic) + .length, + ).toBeLessThanOrEqual(8); + } finally { + store.getState().actions.stop(); + } + }); + + it("keeps its requested view and scoped pending message when another client changes the controller", async () => { + const client = new FakeClient(); + const viewed = snapshot(); + const controller = activeSnapshot( + "controller", + "/tmp/ws/controller.jsonl", + { cursor: 20 }, + ); + const background = { + ...controller, + sessions: [ + ...controller.sessions, + { ...viewed.sessions[0]!, controller: "none" as const }, + ], + selectedSession: viewed.selectedSession, + }; + client.snapshots.push( + Promise.resolve(viewed), + Promise.resolve(background), + Promise.resolve(controller), + ); + const stream = eventStreamHarness(); + const store = createWebStore(client, { + consumeEvents: stream.consumeEvents, + }); + store.getState().actions.start(); + try { + await vi.waitFor(() => + expect(stream.consumeEvents).toHaveBeenCalledOnce(), + ); + await store.getState().actions.sendPrompt("My pending message"); + stream.emit( + runtimeEvent(10, "session_switched", { + sessionId: "controller", + sessionPath: "/tmp/ws/controller.jsonl", + }), + ); + await vi.waitFor(() => + expect(store.getState().sessionSwitching).toBe(false), + ); + expect(store.getState().selectedPath).toBe("/tmp/ws/session.jsonl"); + expect(store.getState().snapshot?.currentSessionId).toBe("controller"); + expect( + store.getState().liveMessages.filter((entry) => entry.optimistic), + ).toHaveLength(1); + stream.emit( + runtimeEvent(21, "message_update", { + sessionId: "controller", + messageKey: "foreign", + message: { role: "assistant", content: "Foreign controller content" }, + }), + ); + expect( + store + .getState() + .liveMessages.some( + (entry) => entry.message.content === "Foreign controller content", + ), + ).toBe(false); + expect( + await store + .getState() + .actions.sendPrompt("Not authorized in this view"), + ).toBe(false); + expect(client.prompts).toHaveLength(1); + } finally { + store.getState().actions.stop(); + } + }); + + it.each([true, false])( + "only clears own pending when selection changes the viewed file (same view: %s)", + async (sameView) => { + const client = new FakeClient(); + const viewed = activeSnapshot("A", "/tmp/ws/a.jsonl"); + const controller = activeSnapshot("B", "/tmp/ws/b.jsonl"); + const background = { + ...controller, + sessions: [ + ...controller.sessions, + { ...viewed.sessions[0]!, controller: "none" as const }, + ], + selectedSession: viewed.selectedSession, + }; + const target = sameView ? viewed : controller; + client.snapshots.push(Promise.resolve(target)); + const stream = eventStreamHarness(); + const store = createWebStore(client, { + consumeEvents: stream.consumeEvents, + }); + store.setState({ + snapshot: background, + cursor: 4, + selectedPath: "/tmp/ws/a.jsonl", + selectedWorkspace: "/tmp/ws", + liveMessages: [ + { + key: "optimistic-a", + message: { role: "user", content: "Queued A" }, + optimistic: { + sessionId: "A", + sessionPath: "/tmp/ws/a.jsonl", + commandId: "a", + afterEntryId: null, + admitted: true, + }, + }, + ], + }); + vi.spyOn(client, "selectSession").mockImplementation(async (path) => { + if (sameView) + stream.emit( + runtimeEvent(5, "session_switched", { + sessionId: "A", + sessionPath: path, + }), + ); + return {}; + }); + store.getState().actions.start(); + try { + await vi.waitFor(() => + expect(stream.consumeEvents).toHaveBeenCalledOnce(), + ); + await store + .getState() + .actions.selectSession(target.selectedSession!.path); + expect(store.getState().selectedPath).toBe( + target.selectedSession!.path, + ); + expect(store.getState().snapshot?.currentSessionId).toBe( + target.currentSessionId, + ); + expect( + store.getState().liveMessages.filter((entry) => entry.optimistic), + ).toHaveLength(sameView ? 1 : 0); + } finally { + store.getState().actions.stop(); + } + }, + ); + it("discovers commands once for the active Session and caches the result", async () => { const client = new FakeClient(); client.snapshots.push(Promise.resolve(snapshot())); @@ -706,13 +1036,14 @@ describe("OpenPI Web store", () => { expect(store.getState().modelSearch.models).toEqual([]); }); - it("falls back to the canonical active Session when a selected transcript is stale", async () => { + it("keeps an available transcript as a read-only view when a different Session controls input", async () => { const client = new FakeClient(); const stale = activeSnapshot("session-2", "/tmp/ws/current.jsonl"); stale.sessions.unshift({ ...stale.sessions[0]!, id: "session-1", path: "/tmp/ws/browsed.jsonl", + controller: "none", }); stale.selectedSession = { ...stale.selectedSession!, @@ -730,10 +1061,14 @@ describe("OpenPI Web store", () => { await store.getState().actions.refreshSnapshot({ resetCursor: true }), ).toBe(true); - expect(client.snapshotPaths).toEqual(["/tmp/ws/browsed.jsonl", null]); - expect(store.getState().selectedPath).toBe("/tmp/ws/current.jsonl"); - expect(store.getState().snapshot?.selectedSession?.id).toBe("session-2"); - expect(store.getState().cursor).toBe(6); + expect(client.snapshotPaths).toEqual(["/tmp/ws/browsed.jsonl"]); + expect(store.getState().selectedPath).toBe("/tmp/ws/browsed.jsonl"); + expect(store.getState().snapshot?.selectedSession?.id).toBe("session-1"); + expect(store.getState().snapshot?.currentSessionId).toBe("session-2"); + expect( + await store.getState().actions.sendPrompt("do not switch control"), + ).toBe(false); + expect(client.prompts).toEqual([]); }); it("clears a vanished selected path and retries the canonical snapshot once", async () => { @@ -918,7 +1253,7 @@ describe("OpenPI Web store", () => { store.getState().actions.stop(); }); - it("rejects a same-ID copied transcript after the canonical retry too", async () => { + it("allows a same-ID copied transcript to be read without granting the original's input control", async () => { const client = new FakeClient(); const copied = snapshot(); const copyPath = "/tmp/ws/copy.jsonl"; @@ -932,15 +1267,28 @@ describe("OpenPI Web store", () => { const store = createWebStore(client); store.setState({ selectedPath: copyPath }); - expect(await store.getState().actions.refreshSnapshot()).toBe(false); + expect(await store.getState().actions.refreshSnapshot()).toBe(true); - expect(client.snapshotPaths).toEqual([copyPath, null]); - expect(store.getState().selectedPath).toBeNull(); - expect(store.getState().snapshot).toBeNull(); + expect(client.snapshotPaths).toEqual([copyPath]); + expect(store.getState().selectedPath).toBe(copyPath); + expect(store.getState().snapshot?.selectedSession?.path).toBe(copyPath); expect( await store.getState().actions.sendPrompt("do not send to the original"), ).toBe(false); expect(client.prompts).toEqual([]); + const cancel = vi + .spyOn(client, "cancelActiveTurn") + .mockResolvedValue({ state: "accepted" }); + store.setState({ + activeTurn: { + sessionId: "session-1", + sessionPath: "/tmp/ws/session.jsonl", + commandId: "original-turn", + epoch: 1, + }, + }); + await store.getState().actions.cancelActiveTurn(); + expect(cancel).not.toHaveBeenCalled(); }); it.each(["network failure", "invalid canonical projection"])( @@ -1128,6 +1476,180 @@ describe("OpenPI Web store", () => { store.getState().actions.stop(); }); + it("marks only the exact HTTP-accepted prompt as admitted for display", async () => { + const client = new FakeClient(); + client.snapshots.push(Promise.resolve(snapshot())); + const receipt = deferred(); + const prompt = vi.spyOn(client, "prompt").mockReturnValue(receipt.promise); + const store = createWebStore(client); + await store.getState().actions.refreshSnapshot(); + + const sending = store.getState().actions.sendPrompt("pending"); + expect(store.getState().liveMessages[0]?.optimistic?.admitted).toBe(false); + receipt.resolve({ id: prompt.mock.calls[0]![2], accepted: true }); + expect(await sending).toBe(true); + expect(store.getState().liveMessages[0]?.optimistic?.admitted).toBe(true); + store.getState().actions.stop(); + }); + + it.each(["unknown", "lost", "wrong-command", "not-accepted"])( + "does not mark %s prompt admission as accepted for display", + async (result) => { + const client = new FakeClient(); + vi.spyOn(client, "snapshot").mockResolvedValue(snapshot()); + vi.spyOn(client, "prompt").mockImplementation(async (_id, _text, id) => { + if (result === "unknown") + throw new WebApiError("unknown", 409, "COMMAND_ADMISSION_UNKNOWN"); + if (result === "lost") throw new TypeError("lost receipt"); + return { + id: result === "wrong-command" ? "other" : id, + accepted: result !== "not-accepted", + }; + }); + const store = createWebStore(client); + await store.getState().actions.refreshSnapshot(); + await store.getState().actions.sendPrompt("pending"); + expect(store.getState().liveMessages[0]?.optimistic?.admitted).toBe( + false, + ); + store.getState().actions.stop(); + }, + ); + + it("marks only the matching scoped prompt accepted by an event", async () => { + const client = new FakeClient(); + client.snapshots.push(Promise.resolve(snapshot())); + const receipt = deferred(); + const prompt = vi.spyOn(client, "prompt").mockReturnValue(receipt.promise); + const stream = eventStreamHarness(); + const store = createWebStore(client, { + consumeEvents: stream.consumeEvents, + }); + await store.getState().actions.refreshSnapshot(); + store.getState().actions.start(); + const sending = store.getState().actions.sendPrompt("pending"); + const commandId = prompt.mock.calls[0]![2]; + const selected = store.getState().snapshot!; + store.setState({ + snapshot: { + ...selected, + currentSessionId: "controller-other", + currentSessionPath: "/tmp/ws/other.jsonl", + }, + }); + stream.emit( + runtimeEvent(5, "prompt_accepted", { + commandId, + sessionId: "session-1", + sessionPath: "/tmp/ws/copy.jsonl", + }), + ); + stream.emit( + runtimeEvent(6, "prompt_accepted", { + commandId: "another-command", + sessionId: "session-1", + sessionPath: "/tmp/ws/session.jsonl", + }), + ); + expect(store.getState().liveMessages[0]?.optimistic?.admitted).toBe(false); + stream.emit( + runtimeEvent(7, "prompt_accepted", { + commandId, + sessionId: "session-1", + sessionPath: "/tmp/ws/session.jsonl", + }), + ); + expect(store.getState().liveMessages[0]?.optimistic?.admitted).toBe(true); + expect(store.getState().snapshot?.currentSessionId).toBe( + "controller-other", + ); + expect(store.getState().liveRunning).toBe(false); + receipt.resolve({ id: commandId, accepted: true }); + await sending; + store.getState().actions.stop(); + }); + + it("remembers only valid admitted prompt projections and releases their preview data", async () => { + const client = new FakeClient(); + client.snapshots.push(Promise.resolve(snapshot())); + const receipt = deferred(); + const prompt = vi.spyOn(client, "prompt").mockReturnValue(receipt.promise); + const store = createWebStore(client); + await store.getState().actions.refreshSnapshot(); + const sending = store + .getState() + .actions.sendPrompt("image", [ + { mimeType: "image/png", data: "cHJldmlldw==" }, + ]); + const original = store.getState().liveMessages[0]!; + const native = { + id: "native-user", + parentId: null, + type: "message" as const, + timestamp: original.timestamp!, + message: original.message, + }; + const current = store.getState().snapshot!; + store.setState({ + snapshot: { + ...current, + selectedSession: { ...current.selectedSession!, entries: [native] }, + }, + }); + const remember = store.getState().actions.rememberPromptProjection; + const pair = [{ key: original.key, entryId: native.id }]; + remember("session-1", "/tmp/ws/session.jsonl", pair); + expect(store.getState().liveMessages[0]).toBe(original); + receipt.resolve({ id: prompt.mock.calls[0]![2], accepted: true }); + await sending; + const accepted = store.getState().liveMessages[0]!; + expect(accepted.message.parts?.[0]).toHaveProperty("previewUrl"); + remember("wrong-id", "/tmp/ws/session.jsonl", pair); + remember("session-1", "/tmp/ws/copy.jsonl", pair); + remember("session-1", "/tmp/ws/session.jsonl", [ + { key: original.key, entryId: "missing" }, + ]); + expect(store.getState().liveMessages[0]).toBe(accepted); + remember("session-1", "/tmp/ws/session.jsonl", pair); + expect(store.getState().liveMessages[0]).toEqual({ + ...accepted, + optimistic: { ...accepted.optimistic, projectedEntryId: native.id }, + message: { role: "user", content: "" }, + }); + const projected = store.getState().liveMessages[0]!; + remember("session-1", "/tmp/ws/session.jsonl", pair); + expect(store.getState().liveMessages[0]).toBe(projected); + const nextPending = { + ...accepted, + key: "next-pending", + optimistic: { ...accepted.optimistic!, commandId: "next-command" }, + }; + store.setState({ liveMessages: [projected, nextPending] }); + remember("session-1", "/tmp/ws/session.jsonl", [ + { key: nextPending.key, entryId: native.id }, + ]); + expect(store.getState().liveMessages[1]).toBe(nextPending); + const newerSnapshot = store.getState().snapshot!; + store.setState({ + snapshot: { + ...newerSnapshot, + selectedSession: { + ...newerSnapshot.selectedSession!, + entries: [{ ...native, id: "newer-native-user" }], + }, + }, + }); + remember("session-1", "/tmp/ws/session.jsonl", [ + { key: projected.key, entryId: "newer-native-user" }, + ]); + expect(store.getState().liveMessages[0]).toBe(projected); + remember("session-1", "/tmp/ws/session.jsonl", [ + { key: nextPending.key, entryId: native.id }, + ]); + expect(store.getState().liveMessages[1]).toBe(nextPending); + store.getState().actions.stop(); + }); + it("ignores prompt state events owned by another active Session", async () => { const client = new FakeClient(); client.snapshots.push(Promise.resolve(snapshot())); @@ -1329,13 +1851,16 @@ describe("OpenPI Web store", () => { store.getState().actions.stop(); }); - it("recovers the canonical Session after an external activation", async () => { + it("recovers the canonical Session when an external activation no longer lists the previously viewed file", async () => { const client = new FakeClient(); client.snapshots.push( Promise.resolve(snapshot()), Promise.resolve( activeSnapshot("session-2", "/tmp/ws/external.jsonl", { cursor: 5 }), ), + Promise.resolve( + activeSnapshot("session-2", "/tmp/ws/external.jsonl", { cursor: 5 }), + ), ); const stream = eventStreamHarness(); const store = createWebStore(client, { @@ -1351,7 +1876,7 @@ describe("OpenPI Web store", () => { }), ); await vi.waitFor(() => - expect(store.getState().sessionSwitching).toBe(false), + expect(store.getState().snapshot?.currentSessionId).toBe("session-2"), ); expect(store.getState().selectedPath).toBe("/tmp/ws/external.jsonl"); @@ -2040,6 +2565,14 @@ describe("OpenPI Web store", () => { expect(store.getState().liveMessages).toEqual([ { key: `optimistic-${commandId}`, + timestamp: expect.any(String), + optimistic: { + sessionId: "session-1", + sessionPath: "/tmp/ws/session.jsonl", + commandId, + afterEntryId: null, + admitted: true, + }, message: { role: "user", content: "once" }, }, ]); @@ -2326,6 +2859,28 @@ describe("draft model selection", () => { return { client, store }; } + it("prepares settings before the first prompt and reuses the confirmed Session", async () => { + const { client, store } = draftHarness(); + store.getState().actions.setWorkspace("/tmp/ws"); + client.snapshots.push(Promise.resolve(snapshot())); + const target = await store.getState().actions.prepareSession(); + expect(target?.sessionId).toBe("session-1"); + expect(client.creations).toHaveLength(1); + expect(client.prompts).toHaveLength(0); + expect(await store.getState().actions.prepareSession()).toMatchObject({ + sessionId: target?.sessionId, + workspacePath: "/tmp/ws", + }); + expect(client.creations).toHaveLength(1); + }); + + it("does not prepare or prompt when initial workspace selection is cancelled", async () => { + const { client, store } = draftHarness(); + expect(await store.getState().actions.prepareSession()).toBeNull(); + expect(client.creations).toHaveLength(0); + expect(client.prompts).toHaveLength(0); + }); + it("selects before a workspace without creating or mutating a Session and retains it after chooser cancellation", async () => { const { client, store } = draftHarness(); await store.getState().actions.selectModel("test/model"); @@ -2844,7 +3399,7 @@ describe("thinking level selection", () => { for (const resetCursor of [false, true]) { for (const outcome of ["success", "rejection"] as const) { - it(`canonical same-ID copy recovery clears old thinking and protects a newer POST from late ${outcome} (resetCursor=${resetCursor})`, async () => { + it(`same-ID controller changes require explicit selection and protect a newer thinking POST from late ${outcome} (resetCursor=${resetCursor})`, async () => { const { client, store } = await harness({ revision: 40 }); const superseded = deferred(); client.setThinkingResults.push(superseded.promise); @@ -2862,8 +3417,8 @@ describe("thinking level selection", () => { ...snapshot().sessions[0], controller: "none", }); - // The requested old file still exists, but the host now controls its - // same-ID copy. Recover through the store's real canonical retry. + // Keep the old file readable while the controller moves to its + // same-ID copy. Only explicit selection can enable new mutations. client.snapshots.push( Promise.resolve({ ...copied, @@ -2874,16 +3429,17 @@ describe("thinking level selection", () => { expect( await store.getState().actions.refreshSnapshot({ resetCursor }), ).toBe(true); - expect(client.snapshotPaths.slice(-2)).toEqual([ - "/tmp/ws/session.jsonl", - null, - ]); - expect(store.getState().selectedPath).toBe(copiedPath); + expect(client.snapshotPaths.at(-1)).toBe("/tmp/ws/session.jsonl"); + expect(store.getState().selectedPath).toBe("/tmp/ws/session.jsonl"); expect(store.getState().thinkingPendingLevel).toBeNull(); expect(store.getState().snapshot?.thinking).toMatchObject({ level: "medium", revision: 2, }); + store.getState().actions.selectThinking("low"); + expect(mutations).toHaveBeenCalledOnce(); + await store.getState().actions.selectSession(copiedPath); + expect(store.getState().selectedPath).toBe(copiedPath); const newer = deferred(); client.setThinkingResults.push(newer.promise); diff --git a/tests/web/workbar-input.spec.ts b/tests/web/workbar-input.spec.ts new file mode 100644 index 00000000..da6f23e4 --- /dev/null +++ b/tests/web/workbar-input.spec.ts @@ -0,0 +1,427 @@ +// @vitest-environment jsdom + +import { + act, + cleanup, + fireEvent, + render, + screen, +} from "@testing-library/react"; +import { createElement } from "react"; +import { I18nextProvider } from "react-i18next"; +import { afterEach, expect, it, vi } from "vitest"; +import { PaneResizeHandle } from "../../web/ui/src/components/PaneResizeHandle.tsx"; +import { EmbeddedBrowserPanel } from "../../web/ui/src/features/workbar/EmbeddedBrowserPanel.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; + +afterEach(() => { + cleanup(); + vi.useRealTimers(); + vi.unstubAllGlobals(); + vi.restoreAllMocks(); +}); + +async function browserPanel(restore?: Promise) { + const state = { + sessionId: "session-1", + url: "https://example.com/", + title: "Input fixture", + width: 800, + height: 600, + loading: false, + canGoBack: true, + canGoForward: false, + }; + vi.spyOn(WebClient.prototype, "browserState").mockImplementation(async () => { + await restore; + return state; + }); + vi.spyOn(WebClient.prototype, "browserFrame").mockResolvedValue(null); + vi.spyOn(WebClient.prototype, "streamBrowserFrames").mockImplementation( + (_id, signal) => + new Promise((resolve) => + signal.addEventListener("abort", () => resolve(), { once: true }), + ), + ); + const action = vi + .spyOn(WebClient.prototype, "browserAction") + .mockResolvedValue(state); + const view = render( + createElement( + I18nextProvider, + { i18n }, + createElement(EmbeddedBrowserPanel, { + sessionId: "session-1", + active: true, + }), + ), + ); + const viewport = await screen.findByRole("application", { + name: "Input fixture", + }); + // A visible DOM commit can precede passive effects such as the native wheel + // listener. Settle React before the fixture starts dispatching input. + await act(async () => {}); + return { + state, + viewport, + action, + unmount: view.unmount, + }; +} + +it("forwards pasted Unicode text into the embedded page", async () => { + const { viewport, action } = await browserPanel(); + fireEvent.paste(viewport, { + clipboardData: { getData: () => "hello 中文\nworld" }, + }); + expect(action).toHaveBeenCalledWith("session-1", { + type: "text", + text: "hello 中文\nworld", + }); +}); + +it("keeps a newly entered address when the previous browser state arrives late", async () => { + let restore!: () => void; + const loading = browserPanel( + new Promise((resolve) => { + restore = resolve; + }), + ); + const address = screen.getByRole("textbox", { + name: i18n.t("browserAddress"), + }); + fireEvent.focus(address); + fireEvent.change(address, { target: { value: "127.0.0.1:43210/new" } }); + fireEvent.blur(address); + await act(async () => restore()); + const { state } = await loading; + expect(address.value).toBe("127.0.0.1:43210/new"); + const open = vi + .spyOn(WebClient.prototype, "openBrowser") + .mockResolvedValue(state); + fireEvent.click(screen.getByRole("button", { name: i18n.t("browserGo") })); + expect(open).toHaveBeenCalledWith( + "session-1", + "http://127.0.0.1:43210/new", + expect.any(Object), + expect.any(AbortSignal), + ); + await act(async () => {}); +}); + +it("does not restore an older address after the user already opened a new page", async () => { + let restore!: () => void; + const loading = browserPanel( + new Promise((resolve) => { + restore = resolve; + }), + ); + vi.spyOn(WebClient.prototype, "openBrowser").mockImplementation( + async (sessionId, url, viewport) => { + const opened = { + sessionId, + url, + ...viewport, + title: "Input fixture", + loading: false, + canGoBack: false, + canGoForward: false, + }; + vi.mocked(WebClient.prototype.browserState).mockResolvedValue(opened); + return opened; + }, + ); + const address = screen.getByRole("textbox", { + name: i18n.t("browserAddress"), + }); + fireEvent.change(address, { target: { value: "localhost:43210/new" } }); + fireEvent.click(screen.getByRole("button", { name: i18n.t("browserGo") })); + await loading; + expect(address.value).toBe("http://localhost:43210/new"); + await act(async () => restore()); + expect(address.value).toBe("http://localhost:43210/new"); +}); + +it.each(["open", "back", "reload"] as const)( + "preserves a newer address draft while %s navigation finishes", + async (navigation) => { + const { state, action } = await browserPanel(); + let finish!: (value: typeof state) => void; + const receipt = new Promise((resolve) => { + finish = resolve; + }); + const open = vi.spyOn(WebClient.prototype, "openBrowser"); + const address = screen.getByRole("textbox", { + name: i18n.t("browserAddress"), + }); + if (navigation === "open") { + open.mockReturnValueOnce(receipt); + fireEvent.change(address, { target: { value: "localhost:43210/first" } }); + fireEvent.click( + screen.getByRole("button", { name: i18n.t("browserGo") }), + ); + } else { + action.mockReturnValueOnce(receipt); + fireEvent.click( + screen.getByRole("button", { + name: i18n.t(navigation === "back" ? "browserBack" : "browserReload"), + }), + ); + } + fireEvent.change(address, { target: { value: "localhost:43210/second" } }); + fireEvent.blur(address); + await act(async () => + finish({ ...state, url: "http://localhost:43210/first" }), + ); + expect(address.value).toBe("localhost:43210/second"); + open.mockResolvedValue(state); + fireEvent.click(screen.getByRole("button", { name: i18n.t("browserGo") })); + expect(open).toHaveBeenLastCalledWith( + "session-1", + "http://localhost:43210/second", + expect.any(Object), + expect.any(AbortSignal), + ); + await act(async () => {}); + }, +); + +it("coalesces wheel deltas while an input request is in flight", async () => { + const { viewport, action, state } = await browserPanel(); + vi.spyOn(viewport, "getBoundingClientRect").mockReturnValue({ + left: 0, + top: 0, + width: 800, + height: 600, + } as DOMRect); + let complete!: (value: typeof state) => void; + action.mockReturnValueOnce( + new Promise((resolve) => { + complete = resolve; + }), + ); + fireEvent.wheel(viewport, { clientX: 10, clientY: 10, deltaY: 10 }); + fireEvent.wheel(viewport, { clientX: 20, clientY: 20, deltaY: 20 }); + fireEvent.wheel(viewport, { clientX: 30, clientY: 30, deltaY: 30 }); + expect(action).toHaveBeenCalledTimes(1); + await act(async () => complete(state)); + expect(action).toHaveBeenCalledTimes(2); + expect(action).toHaveBeenLastCalledWith( + "session-1", + expect.objectContaining({ + type: "mouse", + event: "wheel", + deltaY: 50, + x: 30, + y: 30, + }), + ); +}); + +it("preserves Shift when navigating the embedded page backwards", async () => { + const { viewport, action } = await browserPanel(); + fireEvent.keyDown(viewport, { key: "Tab", code: "Tab", shiftKey: true }); + expect(action).toHaveBeenCalledWith( + "session-1", + expect.objectContaining({ type: "key", key: "Tab", modifiers: 8 }), + ); + fireEvent.keyUp(viewport, { key: "Tab", code: "Tab", shiftKey: true }); + expect(action).toHaveBeenLastCalledWith( + "session-1", + expect.objectContaining({ event: "up", modifiers: 8 }), + ); +}); + +it.each([ + ["127.0.0.1:12345/test", "http://127.0.0.1:12345/test"], + ["localhost:12345/test", "http://localhost:12345/test"], + ["[::1]:12345/test", "http://[::1]:12345/test"], + ["localhost:443", "http://localhost:443/"], + ["127.example.com/test", "https://127.example.com/test"], + ["https://localhost:12345/test", "https://localhost:12345/test"], +])("chooses the correct protocol and port for %s", async (input, expected) => { + const { state } = await browserPanel(); + const open = vi + .spyOn(WebClient.prototype, "openBrowser") + .mockResolvedValue(state); + fireEvent.change( + screen.getByRole("textbox", { name: i18n.t("browserAddress") }), + { target: { value: input } }, + ); + fireEvent.click(screen.getByRole("button", { name: i18n.t("browserGo") })); + expect(open).toHaveBeenCalledWith( + "session-1", + expected, + expect.any(Object), + expect.any(AbortSignal), + ); + await act(async () => {}); +}); + +it.each([ + { metaKey: true, modifiers: 4 }, + { ctrlKey: true, modifiers: 2 }, +])( + "forwards editing shortcuts without inserting a literal letter (%j)", + async ({ modifiers, ...keys }) => { + const { viewport, action } = await browserPanel(); + fireEvent.keyDown(viewport, { key: "a", code: "KeyA", ...keys }); + expect(action).toHaveBeenCalledWith("session-1", { + type: "key", + event: "down", + key: "a", + code: "KeyA", + modifiers, + }); + fireEvent.keyUp(viewport, { key: "a", code: "KeyA", ...keys }); + expect(action).toHaveBeenLastCalledWith( + "session-1", + expect.objectContaining({ event: "up", modifiers }), + ); + const count = action.mock.calls.length; + fireEvent.keyDown(viewport, { key: "v", code: "KeyV", ...keys }); + expect(action).toHaveBeenCalledTimes(count); + }, +); + +it("keeps only the newest pointer position while the host is slow", async () => { + const { viewport, action, state } = await browserPanel(); + vi.useFakeTimers(); + vi.stubGlobal("PointerEvent", MouseEvent); + vi.spyOn(viewport, "getBoundingClientRect").mockReturnValue({ + left: 0, + top: 0, + width: 800, + height: 600, + } as DOMRect); + let finish!: (value: typeof state) => void; + action.mockReturnValueOnce( + new Promise((resolve) => { + finish = resolve; + }), + ); + for (let x = 1; x <= 20; x++) { + fireEvent.pointerMove(viewport, { clientX: x, clientY: 10 }); + await act(() => vi.advanceTimersByTimeAsync(60)); + } + expect(action).toHaveBeenCalledTimes(1); + await act(async () => finish(state)); + await act(() => vi.advanceTimersByTimeAsync(60)); + expect(action).toHaveBeenCalledTimes(2); + expect(action).toHaveBeenLastCalledWith( + "session-1", + expect.objectContaining({ type: "mouse", event: "move", x: 20 }), + ); +}); + +it("drops an unsent pointer update when the browser tool closes", async () => { + const { viewport, action, state, unmount } = await browserPanel(); + vi.stubGlobal("PointerEvent", MouseEvent); + vi.spyOn(viewport, "getBoundingClientRect").mockReturnValue({ + left: 0, + top: 0, + width: 800, + height: 600, + } as DOMRect); + let finish!: (value: typeof state) => void; + action.mockReturnValueOnce( + new Promise((resolve) => { + finish = resolve; + }), + ); + fireEvent.pointerMove(viewport, { clientX: 10, clientY: 10 }); + fireEvent.pointerMove(viewport, { clientX: 20, clientY: 10 }); + expect(action).toHaveBeenCalledTimes(1); + unmount(); + await act(async () => finish(state)); + expect(action).toHaveBeenCalledTimes(1); +}); + +it.each(["left", "right"] as const)( + "captures %s-button drags and releases them when capture is lost", + async (buttonName) => { + const { viewport, action } = await browserPanel(); + class TestPointerEvent extends MouseEvent { + pointerId: number; + constructor(type: string, init: PointerEventInit = {}) { + super(type, init); + this.pointerId = init.pointerId ?? 7; + } + } + vi.stubGlobal("PointerEvent", TestPointerEvent); + const capture = vi.fn(); + Object.defineProperty(viewport, "setPointerCapture", { value: capture }); + vi.spyOn(viewport, "getBoundingClientRect").mockReturnValue({ + left: 0, + top: 0, + width: 800, + height: 600, + } as DOMRect); + const button = buttonName === "left" ? 0 : 2; + const buttons = buttonName === "left" ? 1 : 2; + fireEvent.pointerDown(viewport, { + pointerId: 7, + button, + buttons, + clientX: 20, + clientY: 20, + }); + expect(action).toHaveBeenCalledWith( + "session-1", + expect.objectContaining({ + type: "mouse", + event: "down", + button: buttonName, + buttons, + }), + ); + expect(capture).toHaveBeenCalledWith(7); + fireEvent.pointerMove(viewport, { + pointerId: 7, + buttons, + clientX: 30, + clientY: 20, + }); + expect(action).toHaveBeenLastCalledWith( + "session-1", + expect.objectContaining({ event: "move", button: buttonName, buttons }), + ); + fireEvent.lostPointerCapture(viewport, { pointerId: 7 }); + await act(async () => {}); + expect(action).toHaveBeenLastCalledWith( + "session-1", + expect.objectContaining({ event: "up", button: buttonName, buttons: 0 }), + ); + }, +); + +it.each(["left", "right"] as const)( + "exposes bounded keyboard resizing for the %s pane", + (side) => { + const change = vi.fn(); + render( + createElement(PaneResizeHandle, { + side, + value: 400, + min: 200, + max: 600, + defaultValue: 300, + onChange: change, + onDraggingChange: vi.fn(), + }), + ); + const handle = screen.getByRole("separator"); + expect(handle.tabIndex).toBe(0); + expect(handle.getAttribute("aria-valuenow")).toBe("400"); + fireEvent.keyDown(handle, { key: "ArrowRight" }); + expect(change).toHaveBeenLastCalledWith(side === "left" ? 416 : 384); + fireEvent.keyDown(handle, { key: "Home" }); + expect(change).toHaveBeenLastCalledWith(200); + fireEvent.keyDown(handle, { key: "End" }); + expect(change).toHaveBeenLastCalledWith(600); + fireEvent.keyDown(handle, { key: "Enter" }); + expect(change).toHaveBeenLastCalledWith(300); + }, +); diff --git a/tests/web/workbar-performance.spec.ts b/tests/web/workbar-performance.spec.ts new file mode 100644 index 00000000..db8b7b54 --- /dev/null +++ b/tests/web/workbar-performance.spec.ts @@ -0,0 +1,113 @@ +// @vitest-environment jsdom + +import { + act, + cleanup, + fireEvent, + render, + screen, +} from "@testing-library/react"; +import { createElement } from "react"; +import { afterEach, expect, it, vi } from "vitest"; +import type { WebSubagentDetail } from "../../extensions/shared/web-observer-registry.ts"; +import { Providers } from "../../web/ui/src/app/providers.tsx"; +import type { WorkbarTool } from "../../web/ui/src/features/workbar/types.ts"; +import { WorkbarPanel } from "../../web/ui/src/features/workbar/WorkbarPanel.tsx"; +import { i18n } from "../../web/ui/src/i18n.ts"; +import { WebClient } from "../../web/ui/src/protocol/client.ts"; + +afterEach(() => { + cleanup(); + vi.useRealTimers(); + vi.restoreAllMocks(); +}); + +it("pauses side-conversation polling across hidden panels and inactive tabs, then resumes on return", async () => { + vi.useFakeTimers(); + const detail: WebSubagentDetail = { + kind: "subagents", + id: "side-1", + title: "Side question", + status: "running", + origin: "btw", + createdAt: 1, + cwd: "/workspace", + prompt: "Inspect this", + transcript: [], + liveTools: [], + finalText: "", + truncated: false, + omittedEntries: 0, + }; + const read = vi + .spyOn(WebClient.prototype, "subagentDetail") + .mockResolvedValue({ sessionId: "session-a", detail }); + const changes = vi.fn(); + const node = ( + visible: boolean, + requestedTool: WorkbarTool, + requestRevision: number, + ) => + createElement( + Providers, + null, + createElement(WorkbarPanel, { + visible, + requestedTool, + requestRevision, + sessionId: "session-a", + cwd: "/workspace", + capabilities: { + subagents: { items: [detail], truncated: false, omitted: 0 }, + }, + messages: [], + review: { + result: null, + loading: false, + error: null, + refresh: async () => {}, + }, + conversationCollapsed: false, + onRestoreConversation: () => {}, + onBeforeArtifactOpen: () => {}, + onClose: () => {}, + onActiveToolChange: changes, + }), + ); + const view = render(node(true, "side-conversation", 0)); + fireEvent.click(screen.getByRole("button", { name: /Side question/u })); + await act(async () => {}); + expect(read).toHaveBeenCalledTimes(1); + await act(() => vi.advanceTimersByTimeAsync(1_000)); + expect(read).toHaveBeenCalledTimes(2); + view.rerender(node(false, "side-conversation", 0)); + expect(read.mock.calls[1]![2].aborted).toBe(true); + await act(() => vi.advanceTimersByTimeAsync(5_000)); + expect(read).toHaveBeenCalledTimes(2); + expect(changes).toHaveBeenLastCalledWith(null); + view.rerender(node(true, "side-conversation", 0)); + await act(async () => {}); + expect(read).toHaveBeenCalledTimes(3); + expect(changes).toHaveBeenLastCalledWith("side-conversation"); + view.rerender(node(true, "files", 1)); + expect(read.mock.calls[2]![2].aborted).toBe(true); + await act(() => vi.advanceTimersByTimeAsync(5_000)); + expect(read).toHaveBeenCalledTimes(3); + fireEvent.click( + screen.getByRole("button", { + name: i18n.t("sideConversation"), + }), + ); + await act(async () => {}); + expect(read).toHaveBeenCalledTimes(4); + fireEvent.click( + screen.getByRole("button", { + name: `${i18n.t("close")} ${i18n.t("sideConversation")}`, + }), + ); + expect(read.mock.calls[3]![2].aborted).toBe(true); + await act(() => vi.advanceTimersByTimeAsync(5_000)); + expect(read).toHaveBeenCalledTimes(4); + view.unmount(); + expect(changes).toHaveBeenLastCalledWith(null); +}); diff --git a/tests/web/workbench-polish.e2e.ts b/tests/web/workbench-polish.e2e.ts new file mode 100644 index 00000000..35d0f34a --- /dev/null +++ b/tests/web/workbench-polish.e2e.ts @@ -0,0 +1,194 @@ +import { expect, test } from "@playwright/test"; +import type { + WebCommandDiscoveryResult, + WebSnapshot, +} from "../../web/protocol/types.ts"; + +test("long session titles stay compact and elapsed time survives refresh with confirmed settlement", async ({ + page, +}, testInfo) => { + const title = + "持续优化侧边工具与配置体验,保留原生执行事实并验证真实浏览器。".repeat(12); + let settled = false; + const start = Date.now() - 2142000; + await page.route("**/events?**", (route) => + route.fulfill({ + contentType: "text/event-stream", + body: ": heartbeat\n\n", + }), + ); + await page.route("**/api/snapshot**", async (route) => { + const response = await route.fetch(); + const value = (await response.json()) as WebSnapshot; + const session = value.selectedSession; + if (!session) throw new Error("Missing real Session fixture"); + value.sessions = value.sessions.map((item) => + item.path === session.path ? { ...item, name: title } : item, + ); + value.cursor = settled ? 92002 : 92001; + value.runtime = { + ...value.runtime, + status: settled ? "idle" : "running", + activeTurn: settled + ? undefined + : { + sessionId: session.id, + sessionPath: session.path, + commandId: "current-fixture", + epoch: 2, + startedAt: start, + elapsedMs: 2142000, + }, + }; + value.selectedExecution = { + sessionId: session.id, + sessionPath: session.path, + status: value.runtime.status, + activeTurn: value.runtime.activeTurn, + pendingFollowUps: 0, + liveTools: [], + liveToolsOmitted: 0, + }; + session.entries = [ + { + id: "question-previous", + type: "message", + timestamp: new Date(start - 200000).toISOString(), + message: { role: "user", content: "Previous request" }, + }, + { + id: "answer-previous", + type: "message", + timestamp: new Date(start - 43000).toISOString(), + message: { + role: "assistant", + content: "Previous completed answer", + stopReason: "stop", + }, + }, + { + id: "timing-previous", + type: "custom", + timestamp: new Date(start - 43000).toISOString(), + turnTiming: { + version: 1, + sessionId: session.id, + commandId: "previous-fixture", + epoch: 1, + startedAt: start - 200000, + finishedAt: start - 43000, + elapsedMs: 157000, + outcome: "completed", + }, + }, + { + id: "question-current", + type: "message", + timestamp: new Date(start).toISOString(), + message: { role: "user", content: "Current request" }, + }, + ]; + if (settled) + session.entries.push( + { + id: "answer-current", + type: "message", + timestamp: new Date(start + 2145000).toISOString(), + message: { + role: "assistant", + content: "Current completed answer", + stopReason: "stop", + }, + }, + { + id: "timing-current", + type: "custom", + timestamp: new Date(start + 2145000).toISOString(), + turnTiming: { + version: 1, + sessionId: session.id, + commandId: "current-fixture", + epoch: 2, + startedAt: start, + finishedAt: start + 2145000, + elapsedMs: 2145000, + outcome: "completed", + }, + }, + ); + await route.fulfill({ response, json: value }); + }); + await page.setViewportSize({ width: 1440, height: 1000 }); + await page.goto("/"); + const row = page.locator(".session.active"); + await expect(row).toBeVisible(); + expect((await row.boundingBox())!.height).toBeLessThanOrEqual(40); + await expect(row.locator(".session-title")).toHaveAttribute("title", title); + await expect(page.getByRole("timer")).toContainText("已处理 35分钟"); + await expect(page.getByText("用时 2m37s", { exact: true })).toBeVisible(); + await page.screenshot({ + path: testInfo.outputPath("compact-sidebar-running-time.png"), + }); + await page.reload(); + await expect(page.getByRole("timer")).toContainText("已处理 35分钟"); + settled = true; + await page.reload(); + await expect(page.getByRole("timer")).toHaveCount(0); + const elapsed = page.getByText("用时 35m45s", { exact: true }); + await expect(elapsed).toBeVisible(); + const answer = page.getByText("Current completed answer", { exact: true }); + expect((await elapsed.boundingBox())!.y).toBeLessThan( + (await answer.boundingBox())!.y, + ); + await page.screenshot({ + path: testInfo.outputPath("settled-time-before-answer.png"), + }); +}); + +test("native command discovery offers real Web panels without sending a model prompt", async ({ + page, +}) => { + const prompts: string[] = []; + page.on("request", (request) => { + if (request.url().endsWith("/api/prompt") && request.method() === "POST") + prompts.push(request.postData() ?? ""); + }); + await page.goto("/"); + const input = page.getByRole("textbox", { name: "描述任务" }); + const discovered = page.waitForResponse( + (response) => + new URL(response.url()).pathname === "/api/commands" && response.ok(), + ); + await input.fill("/"); + const commands = (await ( + await discovered + ).json()) as WebCommandDiscoveryResult; + expect( + commands.commands.find((command) => command.name === "openpi-setup"), + ).toMatchObject({ availability: "available", source: "extension" }); + expect( + commands.commands.find((command) => command.name === "usage"), + ).toMatchObject({ availability: "available", source: "extension" }); + expect( + commands.commands.find((command) => command.name === "usage")?.action, + ).toBeUndefined(); + await expect( + page.getByRole("option", { name: /openpi-setup/ }).first(), + ).toBeVisible(); + await expect( + page.getByRole("option", { name: /usage/ }).first(), + ).toBeVisible(); + await input.fill("/btw keep this question"); + await page.getByRole("button", { name: "发送", exact: true }).click(); + await expect(input).toHaveValue("/btw keep this question"); + await expect(page.getByRole("alert")).toContainText("不能携带参数或图片"); + expect(prompts).toEqual([]); + await input.fill("/subagents"); + await input.press("Escape"); + await page.getByRole("button", { name: "发送", exact: true }).click(); + await expect( + page.getByRole("complementary", { name: "子代理", exact: true }), + ).toBeVisible(); + await expect(input).toHaveValue(""); + expect(prompts).toEqual([]); +}); diff --git a/web/adapter/pi-adapter.ts b/web/adapter/pi-adapter.ts index 5afc3ac2..83c226d3 100644 --- a/web/adapter/pi-adapter.ts +++ b/web/adapter/pi-adapter.ts @@ -16,6 +16,7 @@ import { SessionManager, } from "@earendil-works/pi-coding-agent"; import { loadSessionPreviewData } from "../../extensions/sessions/preview-loader.ts"; +import { WEB_COMMAND_INPUT } from "../../extensions/shared/web-command-feedback.ts"; import { webCapabilitySnapshot } from "../../extensions/shared/web-observer-registry.ts"; import { boundedText, @@ -31,8 +32,10 @@ import { WEB_MAX_SESSIONS, WEB_MAX_SESSION_PREVIEW, WEB_MAX_SNAPSHOT_BYTES, + WEB_MAX_SELECTED_TRANSCRIPT_BYTES, WEB_MAX_WORKSPACES, type WebSessionProjection, + type WebSessionHistoryPage, type WebSessionSummary, type WebSnapshotTruncation, type WebWorkspaceSummary, @@ -42,6 +45,11 @@ import type { WebThinkingProjection, } from "../runtime/types.ts"; import { matchesSessionIdentity } from "../runtime/session-identity.ts"; +import { + WEB_TURN_CHANGES_ENTRY, + readTurnChangesDetail, + type WebTurnChangesResult, +} from "../protocol/turn-changes.ts"; export class WebReadOnlySessionError extends Error { readonly code = "SESSION_NOT_FOUND" as const; @@ -63,6 +71,69 @@ type WorkspaceStateSnapshot = { const TERMINAL_DISCOVERY_MAX_BYTES = 256 * 1024; const TERMINAL_DISCOVERY_MAX_FILES = WEB_MAX_SESSIONS; +const HISTORY_PAGE_TURNS = 20; +const ITEM_PAGE_TEXT_CHARS = 32_000; + +function visibleTextSegments(content: unknown) { + if (typeof content === "string") return [content]; + if (!Array.isArray(content)) return []; + const texts = content.flatMap((part): string[] => + part && typeof part === "object" && part.type === "text" && typeof part.text === "string" + ? [part.text] : [], + ); + return texts.flatMap((value, index) => index ? ["\n", value] : [value]); +} + +function visibleTextPage(content: unknown, cursor: number) { + const segments = visibleTextSegments(content); + const totalChars = segments.reduce((total, part) => total + part.length, 0); + if (cursor > totalChars) return null; + let position = 0; + let text = ""; + for (const part of segments) { + const endOfPart = position + part.length; + if (cursor < endOfPart && text.length < ITEM_PAGE_TEXT_CHARS) { + const start = Math.max(0, cursor - position); + let end = Math.min(part.length, start + ITEM_PAGE_TEXT_CHARS - text.length); + if (end < part.length && end > start && /[\uD800-\uDBFF]/u.test(part[end - 1]!)) end--; + text += part.slice(start, end); + if (end < part.length) break; + } + position = endOfPart; + if (text.length >= ITEM_PAGE_TEXT_CHARS) break; + } + const next = cursor + text.length; + return { text, nextCursor: next < totalChars ? next : null, totalChars }; +} + +function alignHistoryPage( + projected: ReturnType, + totalEntries: number, +) { + const prompts = projected.entries.flatMap((entry, index) => + entry.message?.role === "user" ? [index] : [], + ); + const start = prompts.length > HISTORY_PAGE_TURNS + ? prompts[prompts.length - HISTORY_PAGE_TURNS]! + : 0; + if (start === 0) return projected; + projected.entries.splice(0, start); + projected.bytes = jsonByteLength(projected.entries); + const messagesTruncated = projected.entries.filter((entry) => entry.message?.truncation).length; + const messagePartsOmitted = projected.entries.reduce( + (total, entry) => total + (entry.message?.truncation?.partsOmitted ?? 0), + 0, + ); + const entriesOmitted = totalEntries - projected.entries.length; + projected.truncation = { + ...projected.truncation, + entriesOmitted, + messagesTruncated, + messagePartsOmitted, + truncated: entriesOmitted > 0 || messagesTruncated > 0 || messagePartsOmitted > 0, + }; + return projected; +} type ReadOnlyTerminalSessionInfo = { id: string; @@ -910,7 +981,7 @@ export class PiWebAdapter { }; } - async getSnapshot(selectedPath?: string) { + async getSnapshot(selectedPath?: string, historyAnchor?: { sessionId: string; entryId: string }) { await this.ensureWorkspaceStateLoaded(); const sessionProjection = await this.listSessionProjection(selectedPath); const sessions = sessionProjection.sessions; @@ -945,10 +1016,10 @@ export class PiWebAdapter { (session) => this.isCurrentSession(session), )?.path; const selectedSession = path - ? await this.getSession(path, sessions) + ? await this.getSession(path, sessions, historyAnchor) : undefined; const usage = - selectedSession?.id === this.runtime.sessionManager.getSessionId() + selectedSession && this.isCurrentSession(selectedSession) ? this.runtime.getSessionUsage?.() : undefined; const allModels = this.runtime.listModels(); @@ -966,15 +1037,20 @@ export class PiWebAdapter { label: boundedText(model.label, WEB_MAX_SESSION_PREVIEW), })); const thinking = this.safeThinking(); + const selectedExecution = selectedSession + ? this.runtime.getSessionExecution?.(selectedSession.id, selectedSession.path) + : undefined; const snapshot = { ...(this.runtime.workspaceSelected === true - ? { currentSessionId: this.runtime.sessionManager.getSessionId() } + ? { currentSessionId: this.runtime.sessionManager.getSessionId(), + currentSessionPath: this.runtime.sessionManager.getSessionFile() ?? `current:${this.runtime.sessionManager.getSessionId()}` } : {}), workspaces, sessions, models, ...(thinking ? { thinking } : {}), ...(selectedSession ? { selectedSession } : {}), + ...(selectedExecution ? { selectedExecution } : {}), ...(usage ? { usage } : {}), runtime: { status: this.runtime.isIdle() @@ -1018,12 +1094,14 @@ export class PiWebAdapter { const selected = snapshot.selectedSession; if (selected && selected.entries.length > 0) { let removed = 0; - while (selected.entries.length > 0 && bytes > targetBytes) { + while (selected.entries.length > 1 && bytes > targetBytes) { const entry = selected.entries.shift(); if (!entry) break; removed++; bytes -= jsonByteLength(entry) + 1; } + const totalEntries = selected.truncation.entriesOmitted + removed + selected.entries.length; + if (removed > 0) alignHistoryPage(selected, totalEntries); let messagePartsOmitted = 0; let messagesTruncated = 0; for (const entry of selected.entries) { @@ -1035,10 +1113,11 @@ export class PiWebAdapter { selected.truncation = { ...selected.truncation, truncated: true, - entriesOmitted: selected.truncation.entriesOmitted + removed, + entriesOmitted: totalEntries - selected.entries.length, messagePartsOmitted, messagesTruncated, }; + if (selected.history) selected.history.beforeEntryId = selected.entries[0]?.id ?? null; } const selectedPath = selected?.path; @@ -1152,21 +1231,135 @@ export class PiWebAdapter { async getSession( path: string, knownSessions?: WebSessionSummary[], + historyAnchor?: { sessionId: string; entryId: string }, ): Promise { const sessions = knownSessions ?? (await this.listSessions(path)); const summary = sessions.find((session) => session.path === path); if (!summary) return undefined; const manager = - this.isCurrentSession(summary) + this.runtime.getSessionManagerForRead?.(summary.id, summary.path) ?? + (this.isCurrentSession(summary) ? this.runtime.sessionManager - : SessionManager.open(path); - const projected = projectEntries(manager.getBranch(), (path) => resolve(summary.cwd, path)); + : SessionManager.open(path)); + const branch = manager.getBranch(); + const projected = alignHistoryPage( + projectEntries(branch, (path) => resolve(summary.cwd, path)), + branch.length, + ); return { id: summary.id, path: summary.path, cwd: summary.cwd, ...projected, + history: { + leafEntryId: manager.getLeafId(), + beforeEntryId: projected.truncation.entriesOmitted > 0 ? projected.entries[0]?.id ?? null : null, + ...(historyAnchor ? { + anchorEntryId: historyAnchor.entryId, + anchorOnBranch: historyAnchor.sessionId === summary.id && manager.getSessionId() === historyAnchor.sessionId && branch.some((entry) => entry.id === historyAnchor.entryId), + } : {}), + }, }; } + + async getSessionHistory(sessionId: string, path: string, anchorEntryId: string, beforeEntryId: string) { + const summary = (await this.listSessions(path)).find((session) => session.path === path); + if (!summary) return { status: "not_found" as const }; + const manager = this.runtime.getSessionManagerForRead?.(summary.id, summary.path) ?? + (this.isCurrentSession(summary) ? this.runtime.sessionManager : SessionManager.open(path)); + if (manager.getSessionId() !== sessionId || summary.id !== sessionId) + return { status: "changed" as const }; + const branch = manager.getBranch(); + const anchor = branch.findIndex((entry) => entry.id === anchorEntryId); + const before = branch.findIndex((entry) => entry.id === beforeEntryId); + if (anchor < 0 || before < 0 || before > anchor) return { status: "changed" as const }; + const prefix = branch.slice(0, before); + const projectionBudget = WEB_MAX_SELECTED_TRANSCRIPT_BYTES - + jsonByteLength({ id: summary.id, path: summary.path, cwd: summary.cwd, anchorEntryId, requestedBeforeEntryId: beforeEntryId }) - 2048; + const projected = alignHistoryPage( + projectEntries(prefix, (file) => resolve(summary.cwd, file), projectionBudget), + prefix.length, + ); + const session: WebSessionHistoryPage = { + id: summary.id, path: summary.path, cwd: summary.cwd, + anchorEntryId, requestedBeforeEntryId: beforeEntryId, + ...projected, + history: { leafEntryId: manager.getLeafId(), beforeEntryId: projected.truncation.entriesOmitted > 0 ? projected.entries[0]?.id ?? null : null, anchorEntryId, anchorOnBranch: true }, + }; + // The complete response, not only the entry array, shares the transcript budget. + while (session.entries.length > 0 && jsonByteLength({ session }) > WEB_MAX_SELECTED_TRANSCRIPT_BYTES) { + const removed = session.entries.shift(); + session.truncation = { + ...session.truncation, + truncated: true, + entriesOmitted: session.truncation.entriesOmitted + 1, + messagesTruncated: session.truncation.messagesTruncated - (removed?.message?.truncation ? 1 : 0), + messagePartsOmitted: session.truncation.messagePartsOmitted - (removed?.message?.truncation?.partsOmitted ?? 0), + }; + session.history!.beforeEntryId = session.entries[0]?.id ?? null; + } + alignHistoryPage(session, prefix.length); + session.bytes = jsonByteLength(session.entries); + session.history!.beforeEntryId = session.truncation.entriesOmitted > 0 + ? session.entries[0]?.id ?? null : null; + return { status: "ok" as const, session }; + } + + async getTurnChanges( + sessionId: string, + path: string, + promptEntryId: string, + filePath?: string, + ): Promise { + const summary = (await this.listSessions(path)).find((session) => session.path === path); + if (!summary) return { ok: false, reason: "not_found" }; + const manager = this.runtime.getSessionManagerForRead?.(summary.id, summary.path) ?? + (this.isCurrentSession(summary) ? this.runtime.sessionManager : SessionManager.open(path)); + if (summary.id !== sessionId || manager.getSessionId() !== sessionId) + return { ok: false, reason: "identity_changed" }; + const branch = manager.getBranch(); + const prompt = branch.findIndex((entry) => + entry.id === promptEntryId && entry.type === "message" && entry.message.role === "user" + ); + if (prompt < 0) return { ok: false, reason: "identity_changed" }; + const following = branch.slice(prompt + 1); + const nextPrompt = following.findIndex((entry) => entry.type === "message" && entry.message.role === "user"); + const turn = nextPrompt < 0 ? following : following.slice(0, nextPrompt); + const changes = turn.find((entry) => { + if (entry.type !== "custom" || entry.customType !== WEB_TURN_CHANGES_ENTRY) return false; + const detail = readTurnChangesDetail(entry.data); + return detail?.promptEntryId === promptEntryId && detail.sessionId === sessionId; + }); + const detail = changes?.type === "custom" ? readTurnChangesDetail(changes.data) : undefined; + if (!detail) return { ok: false, reason: "not_found" }; + if (detail.state === "unavailable") return { ok: false, reason: "unavailable" }; + if (filePath) { + const file = detail.files.find((item) => item.path === filePath); + if (!file) return { ok: false, reason: "not_found" }; + return { ok: true, changes: { ...detail, files: [file] } }; + } + return { ok: true, changes: detail }; + } + + async getSessionItem(sessionId: string, path: string, entryId: string, cursor: number) { + const summary = (await this.listSessions(path)).find((session) => session.path === path); + if (!summary) return { status: "not_found" as const }; + const manager = this.runtime.getSessionManagerForRead?.(summary.id, summary.path) ?? + (this.isCurrentSession(summary) ? this.runtime.sessionManager : SessionManager.open(path)); + if (summary.id !== sessionId || manager.getSessionId() !== sessionId) + return { status: "changed" as const }; + const entry = manager.getBranch().find((item) => item.id === entryId); + if (!entry) return { status: "changed" as const }; + let content: unknown; + if (entry.type === "message" && (entry.message.role === "user" || entry.message.role === "assistant")) + content = entry.message.content; + else if (entry.type === "custom" && entry.customType === WEB_COMMAND_INPUT && + entry.data && typeof entry.data === "object" && "text" in entry.data) + content = entry.data.text; + else return { status: "changed" as const }; + const page = visibleTextPage(content, cursor); + return page ? { status: "ok" as const, page: { entryId, ...page } } + : { status: "invalid_cursor" as const }; + } } diff --git a/web/dist/app.js b/web/dist/app.js index 817221c4..e9152a4c 100644 --- a/web/dist/app.js +++ b/web/dist/app.js @@ -1,52 +1,56 @@ -var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,o)=>(o=n==null?{}:e(i(n)),c(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function k(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var A=/\/+/g;function j(e,t){return typeof e==`object`&&e&&e.key!=null?k(``+e.key):t.toString(36)}function M(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function N(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,N(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+j(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(A,`$&/`)+`/`),N(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(A,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=u()})),f=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&j(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&j(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var k=new MessageChannel,A=k.port2;k.port1.onmessage=D,O=function(){A.postMessage(null)}}else O=function(){_(D,0)};function j(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,j(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),p=o(((e,t)=>{t.exports=f()})),m=o((e=>{var t=d();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=m()})),g=o((e=>{var t=p(),n=d(),r=h();function i(e){var t=`https://react.dev/errors/`+e;if(1B||(e.current=z[B],z[B]=null,B--)}function U(e,t){B++,z[B]=e.current,e.current=t}var ee=V(null),te=V(null),ne=V(null),W=V(null);function re(e,t){switch(U(ne,t),U(te,e),U(ee,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Kd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Kd(t),e=qd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}H(ee),U(ee,e)}function ie(){H(ee),H(te),H(ne)}function ae(e){e.memoizedState!==null&&U(W,e);var t=ee.current,n=qd(t,e.type);t!==n&&(U(te,e),U(ee,n))}function oe(e){te.current===e&&(H(ee),H(te)),W.current===e&&(H(W),rp._currentValue=R)}var se,ce;function le(e){if(se===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);se=t&&t[1]||``,ce=-1()=>(t||(e((t={exports:{}}).exports,t),e=null),t.exports),s=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},c=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;li[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},l=(n,r,o)=>(o=n==null?{}:e(i(n)),c(r||!n||!n.__esModule||!a.call(n,`default`)?t(o,`default`,{value:n,enumerable:!0}):o,n));(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function D(e,t){return E(e.type,t,e.props)}function O(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function k(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var A=/\/+/g;function j(e,t){return typeof e==`object`&&e&&e.key!=null?k(``+e.key):t.toString(36)}function M(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function N(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,N(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+j(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(A,`$&/`)+`/`),N(o,r,i,``,function(e){return e})):o!=null&&(O(o)&&(o=D(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(A,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u{t.exports=u()})),f=o((e=>{function t(e,t){var n=e.length;e.push(t);a:for(;0>>1,a=e[r];if(0>>1;ri(c,n))li(u,c)?(e[r]=u,e[l]=n,r=l):(e[r]=c,e[s]=n,r=s);else if(li(u,n))e[r]=u,e[l]=n,r=l;else break a}}return t}function i(e,t){var n=e.sortIndex-t.sortIndex;return n===0?e.id-t.id:n}if(e.unstable_now=void 0,typeof performance==`object`&&typeof performance.now==`function`){var a=performance;e.unstable_now=function(){return a.now()}}else{var o=Date,s=o.now();e.unstable_now=function(){return o.now()-s}}var c=[],l=[],u=1,d=null,f=3,p=!1,m=!1,h=!1,g=!1,_=typeof setTimeout==`function`?setTimeout:null,v=typeof clearTimeout==`function`?clearTimeout:null,y=typeof setImmediate<`u`?setImmediate:null;function b(e){for(var i=n(l);i!==null;){if(i.callback===null)r(l);else if(i.startTime<=e)r(l),i.sortIndex=i.expirationTime,t(c,i);else break;i=n(l)}}function x(e){if(h=!1,b(e),!m)if(n(c)!==null)m=!0,S||(S=!0,O());else{var t=n(l);t!==null&&j(x,t.startTime-e)}}var S=!1,C=-1,w=5,T=-1;function E(){return g?!0:!(e.unstable_now()-Tt&&E());){var o=d.callback;if(typeof o==`function`){d.callback=null,f=d.priorityLevel;var s=o(d.expirationTime<=t);if(t=e.unstable_now(),typeof s==`function`){d.callback=s,b(t),i=!0;break b}d===n(c)&&r(c),b(t)}else r(c);d=n(c)}if(d!==null)i=!0;else{var u=n(l);u!==null&&j(x,u.startTime-t),i=!1}}break a}finally{d=null,f=a,p=!1}}}finally{i?O():S=!1}}}var O;if(typeof y==`function`)O=function(){y(D)};else if(typeof MessageChannel<`u`){var k=new MessageChannel,A=k.port2;k.port1.onmessage=D,O=function(){A.postMessage(null)}}else O=function(){_(D,0)};function j(t,n){C=_(function(){t(e.unstable_now())},n)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(e){e.callback=null},e.unstable_forceFrameRate=function(e){0>e||125o?(r.sortIndex=a,t(l,r),n(c)===null&&r===n(l)&&(h?(v(C),C=-1):h=!0,j(x,a-o))):(r.sortIndex=s,t(c,r),m||p||(m=!0,S||(S=!0,O()))),r},e.unstable_shouldYield=E,e.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}})),p=o(((e,t)=>{t.exports=f()})),m=o((e=>{var t=d();function n(e){var t=`https://react.dev/errors/`+e;if(1{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=m()})),g=o((e=>{var t=p(),n=d(),r=h();function i(e){var t=`https://react.dev/errors/`+e;if(1B||(e.current=z[B],z[B]=null,B--)}function U(e,t){B++,z[B]=e.current,e.current=t}var ee=V(null),te=V(null),W=V(null),ne=V(null);function G(e,t){switch(U(W,t),U(te,e),U(ee,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?Wd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=Wd(t),e=Gd(t,e);else switch(e){case`svg`:e=1;break;case`math`:e=2;break;default:e=0}}H(ee),U(ee,e)}function re(){H(ee),H(te),H(W)}function ie(e){e.memoizedState!==null&&U(ne,e);var t=ee.current,n=Gd(t,e.type);t!==n&&(U(te,e),U(ee,n))}function ae(e){te.current===e&&(H(ee),H(te)),ne.current===e&&(H(ne),tp._currentValue=R)}var K,oe;function se(e){if(K===void 0)try{throw Error()}catch(e){var t=e.stack.trim().match(/\n( *(at )?)/);K=t&&t[1]||``,oe=-1)`:-1i||c[r]!==l[i]){var u=` -`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ue=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?le(n):``}function fe(e,t){switch(e.tag){case 26:case 27:case 5:return le(e.type);case 16:return le(`Lazy`);case 13:return e.child!==t&&t!==null?le(`Suspense Fallback`):le(`Suspense`);case 19:return le(`SuspenseList`);case 0:case 15:return de(e.type,!1);case 11:return de(e.type.render,!1);case 1:return de(e.type,!0);case 31:return le(`Activity`);default:return``}}function pe(e){try{var t=``,n=null;do t+=fe(e,n),n=e,e=e.return;while(e);return t}catch(e){return` +`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(``)&&(u=u.replace(``,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ce=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?se(n):``}function ue(e,t){switch(e.tag){case 26:case 27:case 5:return se(e.type);case 16:return se(`Lazy`);case 13:return e.child!==t&&t!==null?se(`Suspense Fallback`):se(`Suspense`);case 19:return se(`SuspenseList`);case 0:case 15:return le(e.type,!1);case 11:return le(e.type.render,!1);case 1:return le(e.type,!0);case 31:return se(`Activity`);default:return``}}function de(e){try{var t=``,n=null;do t+=ue(e,n),n=e,e=e.return;while(e);return t}catch(e){return` Error generating stack: `+e.message+` -`+e.stack}}var me=Object.prototype.hasOwnProperty,he=t.unstable_scheduleCallback,ge=t.unstable_cancelCallback,_e=t.unstable_shouldYield,ve=t.unstable_requestPaint,ye=t.unstable_now,be=t.unstable_getCurrentPriorityLevel,xe=t.unstable_ImmediatePriority,Se=t.unstable_UserBlockingPriority,Ce=t.unstable_NormalPriority,we=t.unstable_LowPriority,Te=t.unstable_IdlePriority,Ee=t.log,De=t.unstable_setDisableYieldValue,Oe=null,ke=null;function Ae(e){if(typeof Ee==`function`&&De(e),ke&&typeof ke.setStrictMode==`function`)try{ke.setStrictMode(Oe,e)}catch{}}var je=Math.clz32?Math.clz32:Pe,Me=Math.log,Ne=Math.LN2;function Pe(e){return e>>>=0,e===0?32:31-(Me(e)/Ne|0)|0}var Fe=256,Ie=262144,Le=4194304;function Re(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function ze(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Re(n))):i=Re(o):i=Re(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Re(n))):i=Re(o)):i=Re(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Be(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Ve(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function He(){var e=Le;return Le<<=1,!(Le&62914560)&&(Le=4194304),e}function Ue(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function We(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ge(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),nn=!1;if(tn)try{var rn={};Object.defineProperty(rn,"passive",{get:function(){nn=!0}}),window.addEventListener(`test`,rn,rn),window.removeEventListener(`test`,rn,rn)}catch{nn=!1}var an=null,on=null,sn=null;function cn(){if(sn)return sn;var e,t=on,n=t.length,r,i=`value`in an?an.value:an.textContent,a=i.length;for(e=0;e=Bn),Un=` `,K=!1;function Wn(e,t){switch(e){case`keyup`:return Rn.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Gn(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Kn=!1;function qn(e,t){switch(e){case`compositionend`:return Gn(t);case`keypress`:return t.which===32?(K=!0,Un):null;case`textInput`:return e=t.data,e===Un&&K?null:e;default:return null}}function Jn(e,t){if(Kn)return e===`compositionend`||!zn&&Wn(e,t)?(e=cn(),sn=on=an=null,Kn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=hr(n)}}function _r(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?_r(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function vr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=kt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=kt(e.document)}return t}function yr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var br=tn&&`documentMode`in document&&11>=document.documentMode,xr=null,Sr=null,Cr=null,wr=!1;function Tr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;wr||xr==null||xr!==kt(r)||(r=xr,`selectionStart`in r&&yr(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Cr&&mr(Cr,r)||(Cr=r,r=Ad(Sr,`onSelect`),0>=o,i-=o,_i=1<<32-je(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),Ei&&yi(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),Ei&&yi(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return Ei&&yi(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),Ei&&yi(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&ya(l)===r.type){n(e,r.sibling),c=a(r,o.props),Ea(c,o),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}o.type===y?(c=ii(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ri(o.type,o.key,o.props,null,e.mode,c),Ea(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}t(e,r),r=r.sibling}c=si(o,e.mode,c),c.return=e,e=c}return s(e);case O:return o=ya(o),b(e,r,o,c)}if(F(o))return h(e,r,o,c);if(M(o)){if(l=M(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ta(o),c);if(o.$$typeof===C)return b(e,r,qi(e,o),c);Da(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=ai(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{wa=0;var i=b(e,t,n,r);return Ca=null,i}catch(t){if(t===pa||t===ha)throw t;var a=$r(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var ka=Oa(!0),Aa=Oa(!1),ja=!1;function Ma(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Na(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Pa(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Fa(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Il&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=Xr(e),Yr(e,null,n),t}return Kr(e,r,t,n),Xr(e)}function Ia(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,qe(e,n)}}function La(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ra=!1;function za(){if(Ra){var e=ia;if(e!==null)throw e}}function Ba(e,t,n,r){Ra=!1;var i=e.updateQueue;ja=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(zl&f)===f:(r&f)===f){f!==0&&f===ra&&(Ra=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:ja=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),ql|=o,e.lanes=o,e.memoizedState=d}}function Va(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Ha(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=I.T,s={};I.T=s,ks(e,!1,t,n);try{var c=i(),l=I.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Os(e,t,sa(c,r),hu(e)):Os(e,t,r,hu(e))}catch(n){Os(e,t,{then:function(){},status:`rejected`,reason:n},hu())}finally{L.p=a,o!==null&&s.types!==null&&(o.types=s.types),I.T=o}}function vs(){}function ys(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=bs(e).queue;_s(e,a,t,R,n===null?vs:function(){return xs(e),n(r)})}function bs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:R,baseState:R,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ao,lastRenderedState:R},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Ao,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function xs(e){var t=bs(e);t.next===null&&(t=e.alternate.memoizedState),Os(e,t.next.queue,{},hu())}function Ss(){return J(rp)}function Cs(){return To().memoizedState}function ws(){return To().memoizedState}function Ts(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=hu();e=Pa(n);var r=Fa(t,e,n);r!==null&&(_u(r,t,n),Ia(r,t,n)),t={cache:$i()},e.payload=t;return}t=t.return}}function Es(e,t,n){var r=hu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},As(e)?js(t,n):(n=qr(e,t,n,r),n!==null&&(_u(n,e,r),Ms(n,t,r)))}function Ds(e,t,n){Os(e,t,n,hu())}function Os(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(As(e))js(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,pr(s,o))return Kr(e,t,i,0),Ll===null&&Gr(),!1}catch{}if(n=qr(e,t,i,r),n!==null)return _u(n,e,r),Ms(n,t,r),!0}return!1}function ks(e,t,n,r){if(r={lane:2,revertLane:md(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},As(e)){if(t)throw Error(i(479))}else t=qr(e,n,r,2),t!==null&&_u(t,e,2)}function As(e){var t=e.alternate;return e===io||t!==null&&t===io}function js(e,t){co=so=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function Ms(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,qe(e,n)}}var Ns={readContext:J,use:Oo,useCallback:ho,useContext:ho,useEffect:ho,useImperativeHandle:ho,useLayoutEffect:ho,useInsertionEffect:ho,useMemo:ho,useReducer:ho,useRef:ho,useState:ho,useDebugValue:ho,useDeferredValue:ho,useTransition:ho,useSyncExternalStore:ho,useId:ho,useHostTransitionStatus:ho,useFormState:ho,useActionState:ho,useOptimistic:ho,useMemoCache:ho,useCacheRefresh:ho};Ns.useEffectEvent=ho;var Ps={readContext:J,use:Oo,useCallback:function(e,t){return wo().memoizedState=[e,t===void 0?null:t],e},useContext:J,useEffect:is,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),ns(4194308,4,us.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ns(4194308,4,e,t)},useInsertionEffect:function(e,t){ns(4,2,e,t)},useMemo:function(e,t){var n=wo();t=t===void 0?null:t;var r=e();if(lo){Ae(!0);try{e()}finally{Ae(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=wo();if(n!==void 0){var i=n(t);if(lo){Ae(!0);try{n(t)}finally{Ae(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=Es.bind(null,io,e),[r.memoizedState,e]},useRef:function(e){var t=wo();return e={current:e},t.memoizedState=e},useState:function(e){e=Bo(e);var t=e.queue,n=Ds.bind(null,io,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:fs,useDeferredValue:function(e,t){return hs(wo(),e,t)},useTransition:function(){var e=Bo(!1);return e=_s.bind(null,io,e.queue,!0,!1),wo().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=io,a=wo();if(Ei){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),Ll===null)throw Error(i(349));zl&127||Fo(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,is(Lo.bind(null,r,o,e),[e]),r.flags|=2048,es(9,{destroy:void 0},Io.bind(null,r,o,n,t),null),n},useId:function(){var e=wo(),t=Ll.identifierPrefix;if(Ei){var n=vi,r=_i;n=(r&~(1<<32-je(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=uo++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[et]=t,o[tt]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(zd(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Oc(t)}}return Nc(t),kc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Oc(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=ne.current,Ni(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=wi,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[et]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Id(e.nodeValue,n)),e||Ai(t,!0)}else e=Gd(e).createTextNode(r),e[et]=t,t.stateNode=e}return Nc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=Ni(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[et]=t}else Pi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Nc(t),e=!1}else n=Fi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(eo(t),t):(eo(t),null);if(t.flags&128)throw Error(i(558))}return Nc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=Ni(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[et]=t}else Pi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;Nc(t),a=!1}else a=Fi(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(eo(t),t):(eo(t),null)}return eo(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),jc(t,t.updateQueue),Nc(t),null);case 4:return ie(),e===null&&Ed(t.stateNode.containerInfo),Nc(t),null;case 10:return Vi(t.type),Nc(t),null;case 19:if(H(to),r=t.memoizedState,r===null)return Nc(t),null;if(a=!!(t.flags&128),o=r.rendering,o===null)if(a)Mc(r,!1);else{if(Kl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=no(e),o!==null){for(t.flags|=128,Mc(r,!1),e=o.updateQueue,t.updateQueue=e,jc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)ni(n,e),n=n.sibling;return U(to,to.current&1|2),Ei&&yi(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&ye()>ru&&(t.flags|=128,a=!0,Mc(r,!1),t.lanes=4194304)}else{if(!a)if(e=no(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,jc(t,e),Mc(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!Ei)return Nc(t),null}else 2*ye()-r.renderingStartTime>ru&&n!==536870912&&(t.flags|=128,a=!0,Mc(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(Nc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=ye(),e.sibling=null,n=to.current,U(to,a?n&1|2:n&1),Ei&&yi(t,r.treeForkCount),e);case 22:case 23:return eo(t),qa(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(Nc(t),t.subtreeFlags&6&&(t.flags|=8192)):Nc(t),n=t.updateQueue,n!==null&&jc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&H(la),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),Vi(Qi),Nc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Fc(e,t){switch(Si(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Vi(Qi),ie(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return oe(t),null;case 31:if(t.memoizedState!==null){if(eo(t),t.alternate===null)throw Error(i(340));Pi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(eo(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Pi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return H(to),null;case 4:return ie(),null;case 10:return Vi(t.type),null;case 22:case 23:return eo(t),qa(),e!==null&&H(la),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return Vi(Qi),null;case 25:return null;default:return null}}function Ic(e,t){switch(Si(t),t.tag){case 3:Vi(Qi),ie();break;case 26:case 27:case 5:oe(t);break;case 4:ie();break;case 31:t.memoizedState!==null&&eo(t);break;case 13:eo(t);break;case 19:H(to);break;case 10:Vi(t.type);break;case 22:case 23:eo(t),qa(),e!==null&&H(la);break;case 24:Vi(Qi)}}function Lc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){qu(t,t.return,e)}}function Rc(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){qu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){qu(t,t.return,e)}}function zc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Ha(t,n)}catch(t){qu(e,e.return,t)}}}function Bc(e,t,n){n.props=Vs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){qu(e,t,n)}}function Vc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){qu(e,t,n)}}function Hc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){qu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){qu(e,t,n)}else n.current=null}function Uc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){qu(e,e.return,t)}}function Wc(e,t,n){try{var r=e.stateNode;Bd(r,e.type,n,t),r[tt]=t}catch(t){qu(e,e.return,t)}}function Gc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&nf(e.type)||e.tag===4}function Kc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Gc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&nf(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function qc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Kt));else if(r!==4&&(r===27&&nf(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(qc(e,t,n),e=e.sibling;e!==null;)qc(e,t,n),e=e.sibling}function Jc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&nf(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Jc(e,t,n),e=e.sibling;e!==null;)Jc(e,t,n),e=e.sibling}function Yc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);zd(t,r,n),t[et]=e,t[tt]=n}catch(t){qu(e,e.return,t)}}var Xc=!1,Zc=!1,Qc=!1,$c=typeof WeakSet==`function`?WeakSet:Set,el=null;function tl(e,t){if(e=e.containerInfo,Ud=fp,e=vr(e),yr(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(Wd={focusedElem:e,selectionRange:n},fp=!1,el=t;el!==null;)if(t=el,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,el=e;else for(;el!==null;){switch(t=el,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),zd(o,r,n),o[et]=e,pt(o),r=o;break a;case`link`:var s=Kf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=gr(s,h),v=gr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,I.T=null,n=du,du=null;var o=su,s=lu;if(ou=0,cu=su=null,lu=0,Il&6)throw Error(i(331));var c=Il;if(Il|=4,jl(o.current),Cl(o,o.current,s,n),Il=c,sd(0,!1),ke&&typeof ke.onPostCommitFiberRoot==`function`)try{ke.onPostCommitFiberRoot(Oe,o)}catch{}return!0}finally{L.p=a,I.T=r,Uu(e,t)}}function Ku(e,t,n){t=li(n,t),t=qs(e.stateNode,t,2),e=Fa(e,t,2),e!==null&&(We(e,2),od(e))}function qu(e,t,n){if(e.tag===3)Ku(e,e,n);else for(;t!==null;){if(t.tag===3){Ku(t,e,n);break}if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(au===null||!au.has(r))){e=li(n,e),n=Js(2),r=Fa(t,n,2),r!==null&&(Ys(n,r,t,e),We(r,2),od(r));break}}t=t.return}}function Ju(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Fl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Wl=!0,i.add(n),e=Yu.bind(null,e,t,n),t.then(e,e))}function Yu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Ll===e&&(zl&n)===n&&(Kl===4||Kl===3&&(zl&62914560)===zl&&300>ye()-tu?!(Il&2)&&wu(e,0):Yl|=n,Zl===zl&&(Zl=0)),od(e)}function Xu(e,t){t===0&&(t=He()),e=Jr(e,t),e!==null&&(We(e,t),od(e))}function Zu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Xu(e,n)}function Qu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Xu(e,n)}function $u(e,t){return he(e,t)}var ed=null,td=null,nd=!1,rd=!1,id=!1,ad=0;function od(e){e!==td&&e.next===null&&(td===null?ed=td=e:td=td.next=e),rd=!0,nd||(nd=!0,pd())}function sd(e,t){if(!id&&rd){id=!0;do for(var n=!1,r=ed;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-je(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,fd(r,a))}else a=zl,a=ze(r,r===Ll?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Be(r,a)||(n=!0,fd(r,a));r=r.next}while(n);id=!1}}function cd(){ld()}function ld(){rd=nd=!1;var e=0;ad!==0&&Xd()&&(e=ad);for(var t=ye(),n=null,r=ed;r!==null;){var i=r.next,a=ud(r,t);a===0?(r.next=null,n===null?ed=i:n.next=i,i===null&&(td=n)):(n=r,(e!==0||a&3)&&(rd=!0)),r=i}ou!==0&&ou!==5||sd(e,!1),ad!==0&&(ad=0)}function ud(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&Vd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function Ef(e,t,n){var r=Tf;if(r&&typeof t==`string`&&t){var i=jt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),bf.has(i)||(bf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),zd(t,`link`,e),pt(t),r.head.appendChild(t)))}}function Df(e){Sf.D(e),Ef(`dns-prefetch`,e,null)}function Of(e,t){Sf.C(e,t),Ef(`preconnect`,e,t)}function kf(e,t,n){Sf.L(e,t,n);var r=Tf;if(r&&e&&t){var i=`link[rel="preload"][as="`+jt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+jt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+jt(n.imageSizes)+`"]`)):i+=`[href="`+jt(e)+`"]`;var a=i;switch(t){case`style`:a=Ff(e);break;case`script`:a=zf(e)}yf.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),yf.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(If(a))||t===`script`&&r.querySelector(Bf(a))||(t=r.createElement(`link`),zd(t,`link`,e),pt(t),r.head.appendChild(t)))}}function Af(e,t){Sf.m(e,t);var n=Tf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+jt(r)+`"][href="`+jt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=zf(e)}if(!yf.has(a)&&(e=m({rel:`modulepreload`,href:e},t),yf.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Bf(a)))return}r=n.createElement(`link`),zd(r,`link`,e),pt(r),n.head.appendChild(r)}}}function jf(e,t,n){Sf.S(e,t,n);var r=Tf;if(r&&e){var i=ft(r).hoistableStyles,a=Ff(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(If(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=yf.get(a))&&Uf(e,n);var c=o=r.createElement(`link`);pt(c),zd(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Hf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Mf(e,t){Sf.X(e,t);var n=Tf;if(n&&e){var r=ft(n).hoistableScripts,i=zf(e),a=r.get(i);a||(a=n.querySelector(Bf(i)),a||(e=m({src:e,async:!0},t),(t=yf.get(i))&&Wf(e,t),a=n.createElement(`script`),pt(a),zd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Nf(e,t){Sf.M(e,t);var n=Tf;if(n&&e){var r=ft(n).hoistableScripts,i=zf(e),a=r.get(i);a||(a=n.querySelector(Bf(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=yf.get(i))&&Wf(e,t),a=n.createElement(`script`),pt(a),zd(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Pf(e,t,n,r){var a=(a=ne.current)?xf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Ff(n.href),n=ft(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Ff(n.href);var o=ft(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(If(e)))&&!o._p&&(s.instance=o,s.state.loading=5),yf.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},yf.set(e,n),o||Rf(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=zf(n),n=ft(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Ff(e){return`href="`+jt(e)+`"`}function If(e){return`link[rel="stylesheet"][`+e+`]`}function Lf(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function Rf(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),zd(t,`link`,n),pt(t),e.head.appendChild(t))}function zf(e){return`[src="`+jt(e)+`"]`}function Bf(e){return`script[async]`+e}function Vf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+jt(n.href)+`"]`);if(r)return t.instance=r,pt(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),pt(r),zd(r,`style`,a),Hf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Ff(n.href);var o=e.querySelector(If(a));if(o)return t.state.loading|=4,t.instance=o,pt(o),o;r=Lf(n),(a=yf.get(a))&&Uf(r,a),o=(e.ownerDocument||e).createElement(`link`),pt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),zd(o,`link`,r),t.state.loading|=4,Hf(o,n.precedence,e),t.instance=o;case`script`:return o=zf(n.src),(a=e.querySelector(Bf(o)))?(t.instance=a,pt(a),a):(r=n,(a=yf.get(o))&&(r=m({},n),Wf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),pt(a),zd(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Hf(r,n.precedence,e));return t.instance}function Hf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Jf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function Yf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Xf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Ff(r.href),a=t.querySelector(If(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=$f.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,pt(a);return}a=t.ownerDocument||t,r=Lf(r),(i=yf.get(i))&&Uf(r,i),a=a.createElement(`link`),pt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),zd(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=$f.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Zf=0;function Qf(e,t){return e.stylesheets&&e.count===0&&tp(e,e.stylesheets),0Zf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function $f(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)tp(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var ep=null;function tp(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,ep=new Map,t.forEach(np,e),ep=null,$f.call(e))}function np(e,t){if(!(t.state.loading&4)){var n=ep.get(e);if(n)var r=n.get(null);else{n=new Map,ep.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),y=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),b=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),x=e=>{let t=b(e);return t.charAt(0).toUpperCase()+t.slice(1)},S={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},C=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},w=l(d(),1),T=(0,w.createContext)({}),E=()=>(0,w.useContext)(T),D=(0,w.forwardRef)(({color:e,size:t,strokeWidth:n,absoluteStrokeWidth:r,className:i=``,children:a,iconNode:o,...s},c)=>{let{size:l=24,strokeWidth:u=2,absoluteStrokeWidth:d=!1,color:f=`currentColor`,className:p=``}=E()??{},m=r??d?Number(n??u)*24/Number(t??l):n??u;return(0,w.createElement)(`svg`,{ref:c,...S,width:t??l??S.width,height:t??l??S.height,stroke:e??f,strokeWidth:m,className:v(`lucide`,p,i),...!a&&!C(s)&&{"aria-hidden":`true`},...s},[...o.map(([e,t])=>(0,w.createElement)(e,t)),...Array.isArray(a)?a:[a]])}),O=(e,t)=>{let n=(0,w.forwardRef)(({className:n,...r},i)=>(0,w.createElement)(D,{ref:i,iconNode:t,className:v(`lucide-${y(x(e))}`,`lucide-${e}`,n),...r}));return n.displayName=x(e),n},k=O(`archive-restore`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h2`,key:`tvwodi`}],[`path`,{d:`M20 8v11a2 2 0 0 1-2 2h-2`,key:`1gkqxj`}],[`path`,{d:`m9 15 3-3 3 3`,key:`1pd0qc`}],[`path`,{d:`M12 12v9`,key:`192myk`}]]),A=O(`archive`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8`,key:`1s80jp`}],[`path`,{d:`M10 12h4`,key:`a56b0p`}]]),j=O(`arrow-down`,[[`path`,{d:`M12 5v14`,key:`s699le`}],[`path`,{d:`m19 12-7 7-7-7`,key:`1idqje`}]]),M=O(`arrow-left`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]),N=O(`arrow-right`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`m12 5 7 7-7 7`,key:`xquz4c`}]]),P=O(`arrow-up-down`,[[`path`,{d:`m21 16-4 4-4-4`,key:`f6ql7i`}],[`path`,{d:`M17 20V4`,key:`1ejh1v`}],[`path`,{d:`m3 8 4-4 4 4`,key:`11wl7u`}],[`path`,{d:`M7 4v16`,key:`1glfcx`}]]),F=O(`arrow-up`,[[`path`,{d:`m5 12 7-7 7 7`,key:`hav0vg`}],[`path`,{d:`M12 19V5`,key:`x0mq9r`}]]),I=O(`bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),L=O(`brain`,[[`path`,{d:`M12 18V5`,key:`adv99a`}],[`path`,{d:`M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4`,key:`1e3is1`}],[`path`,{d:`M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5`,key:`1gqd8o`}],[`path`,{d:`M17.997 5.125a4 4 0 0 1 2.526 5.77`,key:`iwvgf7`}],[`path`,{d:`M18 18a4 4 0 0 0 2-7.464`,key:`efp6ie`}],[`path`,{d:`M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517`,key:`1gq6am`}],[`path`,{d:`M6 18a4 4 0 0 1-2-7.464`,key:`k1g0md`}],[`path`,{d:`M6.003 5.125a4 4 0 0 0-2.526 5.77`,key:`q97ue3`}]]),R=O(`calendar`,[[`path`,{d:`M8 2v3`,key:`1ioesn`}],[`path`,{d:`M16 2v3`,key:`otl347`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}],[`path`,{d:`M3 9h18`,key:`1pudct`}]]),z=O(`check-check`,[[`path`,{d:`M18 6 7 17l-5-5`,key:`116fxf`}],[`path`,{d:`m22 10-7.5 7.5L13 16`,key:`ke71qq`}]]),B=O(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),V=O(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),H=O(`chevron-left`,[[`path`,{d:`m15 18-6-6 6-6`,key:`1wnfg3`}]]),U=O(`chevron-right`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),ee=O(`chevrons-left`,[[`path`,{d:`m11 17-5-5 5-5`,key:`13zhaf`}],[`path`,{d:`m18 17-5-5 5-5`,key:`h8a8et`}]]),te=O(`chevrons-right`,[[`path`,{d:`m6 17 5-5-5-5`,key:`xnjwq`}],[`path`,{d:`m13 17 5-5-5-5`,key:`17xmmf`}]]),ne=O(`circle-check-big`,[[`path`,{d:`M21.801 10A10 10 0 1 1 17 3.335`,key:`yps3ct`}],[`path`,{d:`m9 11 3 3L22 4`,key:`1pflzl`}]]),W=O(`circle-check`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m16 9-5.5 5.5L8 12`,key:`xofnsj`}]]),re=O(`circle-dashed`,[[`path`,{d:`M10.1 2.182a10 10 0 0 1 3.8 0`,key:`5ilxe3`}],[`path`,{d:`M13.9 21.818a10 10 0 0 1-3.8 0`,key:`11zvb9`}],[`path`,{d:`M17.609 3.721a10 10 0 0 1 2.69 2.7`,key:`1iw5b2`}],[`path`,{d:`M2.182 13.9a10 10 0 0 1 0-3.8`,key:`c0bmvh`}],[`path`,{d:`M20.279 17.609a10 10 0 0 1-2.7 2.69`,key:`1ruxm7`}],[`path`,{d:`M21.818 10.1a10 10 0 0 1 0 3.8`,key:`qkgqxc`}],[`path`,{d:`M3.721 6.391a10 10 0 0 1 2.7-2.69`,key:`1mcia2`}],[`path`,{d:`M6.391 20.279a10 10 0 0 1-2.69-2.7`,key:`1fvljs`}]]),ie=O(`circle-stop`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`rect`,{x:`9`,y:`9`,width:`6`,height:`6`,rx:`1`,key:`1ssd4o`}]]),ae=O(`circle-x`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),oe=O(`clipboard`,[[`rect`,{width:`8`,height:`4`,x:`8`,y:`2`,rx:`1`,ry:`1`,key:`tgr4d6`}],[`path`,{d:`M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2`,key:`116196`}]]),se=O(`clock`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 6v6l4 2`,key:`mmk7yg`}]]),ce=O(`cloud-fog`,[[`path`,{d:`M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242`,key:`1pljnt`}],[`path`,{d:`M16 17H7`,key:`pygtm1`}],[`path`,{d:`M17 21H9`,key:`1u2q02`}]]),le=O(`code-xml`,[[`path`,{d:`m18 16 4-4-4-4`,key:`1inbqp`}],[`path`,{d:`m6 8-4 4 4 4`,key:`15zrgr`}],[`path`,{d:`m14.5 4-5 16`,key:`e7oirm`}]]),ue=O(`columns-2`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M12 3v18`,key:`108xh3`}]]),de=O(`command`,[[`path`,{d:`M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3`,key:`11bfej`}]]),fe=O(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),pe=O(`cpu`,[[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M17 20v2`,key:`1rnc9c`}],[`path`,{d:`M17 2v2`,key:`11trls`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M2 17h2`,key:`7oei6x`}],[`path`,{d:`M2 7h2`,key:`asdhe0`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`M20 17h2`,key:`1fpfkl`}],[`path`,{d:`M20 7h2`,key:`1o8tra`}],[`path`,{d:`M7 20v2`,key:`4gnj0m`}],[`path`,{d:`M7 2v2`,key:`1i4yhu`}],[`rect`,{x:`4`,y:`4`,width:`16`,height:`16`,rx:`2`,key:`1vbyd7`}],[`rect`,{x:`8`,y:`8`,width:`8`,height:`8`,rx:`1`,key:`z9xiuo`}]]),me=O(`database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),he=O(`download`,[[`path`,{d:`M12 15V3`,key:`m9g1x1`}],[`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`,key:`ih7n3h`}],[`path`,{d:`m7 10 5 5 5-5`,key:`brsn70`}]]),ge=O(`earth`,[[`path`,{d:`M21.54 15H17a2 2 0 0 0-2 2v4.54`,key:`1djwo0`}],[`path`,{d:`M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17`,key:`1tzkfa`}],[`path`,{d:`M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05`,key:`14pb5j`}],[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),_e=O(`ellipsis`,[[`circle`,{cx:`12`,cy:`12`,r:`1`,key:`41hilf`}],[`circle`,{cx:`19`,cy:`12`,r:`1`,key:`1wjl8i`}],[`circle`,{cx:`5`,cy:`12`,r:`1`,key:`1pcz8c`}]]),ve=O(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),ye=O(`eye-off`,[[`path`,{d:`M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49`,key:`ct8e1f`}],[`path`,{d:`M14.084 14.158a3 3 0 0 1-4.242-4.242`,key:`151rxh`}],[`path`,{d:`M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143`,key:`13bj9a`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}]]),be=O(`file-code-corner`,[[`path`,{d:`M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35`,key:`1wthlu`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`m5 16-3 3 3 3`,key:`331omg`}],[`path`,{d:`m9 22 3-3-3-3`,key:`lsp7cz`}]]),xe=O(`file-diff`,[[`path`,{d:`M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z`,key:`1oefj6`}],[`path`,{d:`M9 10h6`,key:`9gxzsh`}],[`path`,{d:`M12 13V7`,key:`h0r20n`}],[`path`,{d:`M9 17h6`,key:`r8uit2`}]]),Se=O(`file-pen-line`,[[`path`,{d:`M14.364 13.634a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506l4.013-4.009a1 1 0 0 0-3.004-3.004z`,key:`ukzhwg`}],[`path`,{d:`M14.487 7.858A1 1 0 0 1 14 7V2`,key:`1klhew`}],[`path`,{d:`M20 19.645V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l2.516 2.516`,key:`rxaxab`}],[`path`,{d:`M8 18h1`,key:`13wk12`}]]),Ce=O(`file-text`,[[`path`,{d:`M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z`,key:`1oefj6`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M10 9H8`,key:`b1mrlr`}],[`path`,{d:`M16 13H8`,key:`t4e002`}],[`path`,{d:`M16 17H8`,key:`z1uh3a`}]]),we=O(`flower-2`,[[`path`,{d:`M12 5a3 3 0 1 1 3 3m-3-3a3 3 0 1 0-3 3m3-3v1M9 8a3 3 0 1 0 3 3M9 8h1m5 0a3 3 0 1 1-3 3m3-3h-1m-2 3v-1`,key:`3pnvol`}],[`circle`,{cx:`12`,cy:`8`,r:`2`,key:`1822b1`}],[`path`,{d:`M12 10v12`,key:`6ubwww`}],[`path`,{d:`M12 22c4.2 0 7-1.667 7-5-4.2 0-7 1.667-7 5Z`,key:`9hd38g`}],[`path`,{d:`M12 22c-4.2 0-7-1.667-7-5 4.2 0 7 1.667 7 5Z`,key:`ufn41s`}]]),Te=O(`folder-cog`,[[`path`,{d:`M10.3 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.98a2 2 0 0 1 1.69.9l.66 1.2A2 2 0 0 0 12 6h8a2 2 0 0 1 2 2v3.3`,key:`128dxu`}],[`path`,{d:`m14.305 19.53.923-.382`,key:`3m78fa`}],[`path`,{d:`m15.228 16.852-.923-.383`,key:`npixar`}],[`path`,{d:`m16.852 15.228-.383-.923`,key:`5xggr7`}],[`path`,{d:`m16.852 20.772-.383.924`,key:`dpfhf9`}],[`path`,{d:`m19.148 15.228.383-.923`,key:`1reyyz`}],[`path`,{d:`m19.53 21.696-.382-.924`,key:`1goivc`}],[`path`,{d:`m20.772 16.852.924-.383`,key:`htqkph`}],[`path`,{d:`m20.772 19.148.924.383`,key:`9w9pjp`}],[`circle`,{cx:`18`,cy:`18`,r:`3`,key:`1xkwt0`}]]),Ee=O(`folder-output`,[[`path`,{d:`M2 7.5V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-1.5`,key:`1yk7aj`}],[`path`,{d:`M2 13h10`,key:`pgb2dq`}],[`path`,{d:`m5 10-3 3 3 3`,key:`1r8ie0`}]]),De=O(`folder`,[[`path`,{d:`M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z`,key:`1kt360`}]]),Oe=O(`funnel`,[[`path`,{d:`M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z`,key:`sc7q7i`}]]),ke=O(`gauge`,[[`path`,{d:`m12 14 4-4`,key:`9kzdfg`}],[`path`,{d:`M3.34 19a10 10 0 1 1 17.32 0`,key:`19p75a`}]]),Ae=O(`git-branch`,[[`path`,{d:`M15 6a9 9 0 0 0-9 9V3`,key:`1cii5b`}],[`circle`,{cx:`18`,cy:`6`,r:`3`,key:`1h7g24`}],[`circle`,{cx:`6`,cy:`18`,r:`3`,key:`fqmcym`}]]),je=O(`globe`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20`,key:`13o1zl`}],[`path`,{d:`M2 12h20`,key:`9i4pu4`}]]),Me=O(`image-plus`,[[`path`,{d:`M16 5h6`,key:`1vod17`}],[`path`,{d:`M19 2v6`,key:`4bpg5p`}],[`path`,{d:`M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5`,key:`1ue2ih`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}]]),Ne=O(`image`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,ry:`2`,key:`1m3agn`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}]]),Pe=O(`info`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 16v-4`,key:`1dtifu`}],[`path`,{d:`M12 8h.01`,key:`e9boi3`}]]),Fe=O(`key-round`,[[`path`,{d:`M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z`,key:`1s6t7t`}],[`circle`,{cx:`16.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`w0ekpg`}]]),Ie=O(`layers`,[[`path`,{d:`M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z`,key:`zw3jo`}],[`path`,{d:`M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12`,key:`1wduqc`}],[`path`,{d:`M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17`,key:`kqbvx6`}]]),Le=O(`lightbulb`,[[`path`,{d:`M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5`,key:`1gvzjb`}],[`path`,{d:`M9 18h6`,key:`x1upvd`}],[`path`,{d:`M10 22h4`,key:`ceow96`}]]),Re=O(`menu`,[[`path`,{d:`M4 5h16`,key:`1tepv9`}],[`path`,{d:`M4 12h16`,key:`1lakjw`}],[`path`,{d:`M4 19h16`,key:`1djgab`}]]),ze=O(`message-circle-question-mark`,[[`path`,{d:`M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719`,key:`1sd12s`}],[`path`,{d:`M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3`,key:`1u773s`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),Be=O(`messages-square`,[[`path`,{d:`M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z`,key:`1n2ejm`}],[`path`,{d:`M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1`,key:`1qfcsi`}]]),Ve=O(`mic`,[[`path`,{d:`M12 19v3`,key:`npa21l`}],[`path`,{d:`M19 10v2a7 7 0 0 1-14 0v-2`,key:`1vc78b`}],[`rect`,{x:`9`,y:`2`,width:`6`,height:`13`,rx:`3`,key:`s6n7sd`}]]),He=O(`monitor`,[[`rect`,{width:`20`,height:`14`,x:`2`,y:`3`,rx:`2`,key:`48i651`}],[`line`,{x1:`8`,x2:`16`,y1:`21`,y2:`21`,key:`1svkeh`}],[`line`,{x1:`12`,x2:`12`,y1:`17`,y2:`21`,key:`vw1qmm`}]]),Ue=O(`moon`,[[`path`,{d:`M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401`,key:`kfwtm`}]]),We=O(`panel-left-close`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M9 3v18`,key:`fh3hqa`}],[`path`,{d:`m16 15-3-3 3-3`,key:`14y99z`}]]),Ge=O(`panel-left-open`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M9 3v18`,key:`fh3hqa`}],[`path`,{d:`m14 9 3 3-3 3`,key:`8010ee`}]]),Ke=O(`panel-right`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M15 3v18`,key:`14nvp0`}]]),qe=O(`pencil`,[[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}],[`path`,{d:`m15 5 4 4`,key:`1mk7zo`}]]),Je=O(`plug`,[[`path`,{d:`M12 22v-5`,key:`1ega77`}],[`path`,{d:`M15 8V2`,key:`18g5xt`}],[`path`,{d:`M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z`,key:`1xoxul`}],[`path`,{d:`M9 8V2`,key:`14iosj`}]]),Ye=O(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),Xe=O(`puzzle`,[[`path`,{d:`M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z`,key:`w46dr5`}]]),Ze=O(`refresh-cw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]),Qe=O(`rotate-ccw`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}]]),$e=O(`search`,[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]),et=O(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),tt=O(`settings`,[[`path`,{d:`M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915`,key:`1i5ecw`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),nt=O(`sliders-horizontal`,[[`path`,{d:`M10 5H3`,key:`1qgfaw`}],[`path`,{d:`M12 19H3`,key:`yhmn1j`}],[`path`,{d:`M14 3v4`,key:`1sua03`}],[`path`,{d:`M16 17v4`,key:`1q0r14`}],[`path`,{d:`M21 12h-9`,key:`1o4lsq`}],[`path`,{d:`M21 19h-5`,key:`1rlt1p`}],[`path`,{d:`M21 5h-7`,key:`1oszz2`}],[`path`,{d:`M8 10v4`,key:`tgpxqk`}],[`path`,{d:`M8 12H3`,key:`a7s4jb`}]]),rt=O(`sparkles`,[[`path`,{d:`M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z`,key:`1s2grr`}],[`path`,{d:`M20 2v4`,key:`1rf3ol`}],[`path`,{d:`M22 4h-4`,key:`gwowj6`}],[`circle`,{cx:`4`,cy:`20`,r:`2`,key:`6kqj1y`}]]),it=O(`square-pen`,[[`path`,{d:`M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7`,key:`1m0v6g`}],[`path`,{d:`M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z`,key:`ohrbg2`}]]),at=O(`square-terminal`,[[`path`,{d:`m7 11 2-2-2-2`,key:`1lz0vl`}],[`path`,{d:`M11 13h4`,key:`1p7l4v`}],[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,ry:`2`,key:`1m3agn`}]]),ot=O(`square`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}]]),st=O(`sun`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`m4.93 4.93 1.41 1.41`,key:`149t6j`}],[`path`,{d:`m17.66 17.66 1.41 1.41`,key:`ptbguv`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`m6.34 17.66-1.41 1.41`,key:`1m8zz5`}],[`path`,{d:`m19.07 4.93-1.41 1.41`,key:`1shlcs`}]]),ct=O(`terminal`,[[`path`,{d:`M12 19h8`,key:`baeox8`}],[`path`,{d:`m4 17 6-6-6-6`,key:`1yngyt`}]]),lt=O(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),ut=O(`tree-pine`,[[`path`,{d:`m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z`,key:`cpyugq`}],[`path`,{d:`M12 22v-3`,key:`kmzjlo`}]]),dt=O(`triangle-alert`,[[`path`,{d:`m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3`,key:`wmoenq`}],[`path`,{d:`M12 9v4`,key:`juzpu7`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),ft=O(`workflow`,[[`rect`,{width:`8`,height:`8`,x:`3`,y:`3`,rx:`2`,key:`by2w9f`}],[`path`,{d:`M7 11v4a2 2 0 0 0 2 2h4`,key:`xkn7yn`}],[`rect`,{width:`8`,height:`8`,x:`13`,y:`13`,rx:`2`,key:`1cgmvn`}]]),pt=O(`wrench`,[[`path`,{d:`M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z`,key:`1ngwbx`}]]),mt=O(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),ht=_(),G=e=>typeof e==`string`,gt=()=>{let e,t,n=new Promise((n,r)=>{e=n,t=r});return n.resolve=e,n.reject=t,n},_t=e=>e==null?``:String(e),vt=(e,t,n)=>{e.forEach(e=>{t[e]&&(n[e]=t[e])})},yt=/###/g,bt=e=>e&&e.includes(`###`)?e.replace(yt,`.`):e,xt=e=>!e||G(e),St=(e,t,n)=>{let r=G(t)?t.split(`.`):t,i=0;for(;i{let{obj:r,k:i}=St(e,t,Object);if(r!==void 0||t.length===1){r[i]=n;return}let a=t[t.length-1],o=t.slice(0,t.length-1),s=St(e,o,Object);for(;s.obj===void 0&&o.length;)a=`${o[o.length-1]}.${a}`,o=o.slice(0,o.length-1),s=St(e,o,Object),s?.obj&&s.obj[`${s.k}.${a}`]!==void 0&&(s.obj=void 0);s.obj[`${s.k}.${a}`]=n},wt=(e,t,n,r)=>{let{obj:i,k:a}=St(e,t,Object);i[a]=i[a]||[],i[a].push(n)},Tt=(e,t)=>{let{obj:n,k:r}=St(e,t);if(n&&Object.prototype.hasOwnProperty.call(n,r))return n[r]},Et=(e,t,n)=>{let r=Tt(e,n);return r===void 0?Tt(t,n):r},Dt=(e,t,n)=>{for(let r in t)r!==`__proto__`&&r!==`constructor`&&(Object.prototype.hasOwnProperty.call(e,r)?G(e[r])||e[r]instanceof String||G(t[r])||t[r]instanceof String?n&&(e[r]=t[r]):Dt(e[r],t[r],n):e[r]=t[r]);return e},Ot=e=>e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,`\\$&`),kt={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`,"/":`/`},At=e=>G(e)?e.replace(/[&<>"'\/]/g,e=>kt[e]):e,jt=class{constructor(e){this.capacity=e,this.regExpMap=new Map,this.regExpQueue=[]}getRegExp(e){let t=this.regExpMap.get(e);if(t!==void 0)return t;let n=new RegExp(e);return this.regExpQueue.length===this.capacity&&this.regExpMap.delete(this.regExpQueue.shift()),this.regExpMap.set(e,n),this.regExpQueue.push(e),n}},Mt=[` `,`,`,`?`,`!`,`;`],Nt=new jt(20),Pt=(e,t,n)=>{t||=``,n||=``;let r=Mt.filter(e=>!t.includes(e)&&!n.includes(e));if(r.length===0)return!0;let i=Nt.getRegExp(`(${r.map(e=>e===`?`?`\\?`:e).join(`|`)})`),a=!i.test(e);if(!a){let t=e.indexOf(n);t>0&&!i.test(e.substring(0,t))&&(a=!0)}return a},Ft=(e,t,n=`.`)=>{if(!e)return;if(e[t])return Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;let r=t.split(n),i=e;for(let e=0;ee?.replace(/_/g,`-`),Lt={type:`logger`,log(e){this.output(`log`,e)},warn(e){this.output(`warn`,e)},error(e){this.output(`error`,e)},output(e,t){console?.[e]?.apply?.(console,t)}},Rt=new class e{constructor(e,t={}){this.init(e,t)}init(e,t={}){this.prefix=t.prefix||`i18next:`,this.logger=e||Lt,this.options=t,this.debug=t.debug}log(...e){return this.forward(e,`log`,``,!0)}warn(...e){return this.forward(e,`warn`,``,!0)}error(...e){return this.forward(e,`error`,``)}deprecate(...e){return this.forward(e,`warn`,`WARNING DEPRECATED: `,!0)}forward(e,t,n,r){return r&&!this.debug?null:(e=e.map(e=>G(e)?e.replace(/[\r\n\x00-\x1F\x7F]/g,` `):e),G(e[0])&&(e[0]=`${n}${this.prefix} ${e[0]}`),this.logger[t](e))}create(t){return new e(this.logger,{prefix:`${this.prefix}:${t}:`,...this.options})}clone(t){return t||=this.options,t.prefix=t.prefix||this.prefix,new e(this.logger,t)}},zt=class{constructor(){this.observers={}}on(e,t){return e.split(` `).forEach(e=>{this.observers[e]||(this.observers[e]=new Map);let n=this.observers[e].get(t)||0;this.observers[e].set(t,n+1)}),this}off(e,t){if(this.observers[e]){if(!t){delete this.observers[e];return}this.observers[e].delete(t)}}once(e,t){let n=(...r)=>{t(...r),this.off(e,n)};return this.on(e,n),this}emit(e,...t){this.observers[e]&&Array.from(this.observers[e].entries()).forEach(([e,n])=>{for(let r=0;r{for(let i=0;i-1&&this.options.ns.splice(t,1)}getResource(e,t,n,r={}){let i=r.keySeparator===void 0?this.options.keySeparator:r.keySeparator,a=r.ignoreJSONStructure===void 0?this.options.ignoreJSONStructure:r.ignoreJSONStructure,o;e.includes(`.`)?o=e.split(`.`):(o=[e,t],n&&(Array.isArray(n)?o.push(...n):G(n)&&i?o.push(...n.split(i)):o.push(n)));let s=Tt(this.data,o);return!s&&!t&&!n&&e.includes(`.`)&&(e=o[0],t=o[1],n=o.slice(2).join(`.`)),s||!a||!G(n)?s:Ft(this.data?.[e]?.[t],n,i)}addResource(e,t,n,r,i={silent:!1}){let a=i.keySeparator===void 0?this.options.keySeparator:i.keySeparator,o=[e,t];n&&(o=o.concat(a?n.split(a):n)),e.includes(`.`)&&(o=e.split(`.`),r=t,t=o[1]),this.addNamespaces(t),Ct(this.data,o,r),i.silent||this.emit(`added`,e,t,n,r)}addResources(e,t,n,r={silent:!1}){for(let r in n)(G(n[r])||Array.isArray(n[r]))&&this.addResource(e,t,r,n[r],{silent:!0});r.silent||this.emit(`added`,e,t,n)}addResourceBundle(e,t,n,r,i,a={silent:!1,skipCopy:!1}){let o=[e,t];e.includes(`.`)&&(o=e.split(`.`),r=n,n=t,t=o[1]),this.addNamespaces(t);let s=Tt(this.data,o)||{};a.skipCopy||(n=JSON.parse(JSON.stringify(n))),r?Dt(s,n,i):s={...s,...n},Ct(this.data,o,s),a.silent||this.emit(`added`,e,t,n)}removeResourceBundle(e,t){this.hasResourceBundle(e,t)&&delete this.data[e][t],this.removeNamespaces(t),this.emit(`removed`,e,t)}hasResourceBundle(e,t){return this.getResource(e,t)!==void 0}getResourceBundle(e,t){return t||=this.options.defaultNS,this.getResource(e,t)}getDataByLanguage(e){return this.data[e]}hasLanguageSomeTranslations(e){let t=this.getDataByLanguage(e);return!!(t&&Object.keys(t)||[]).find(e=>t[e]&&Object.keys(t[e]).length>0)}toJSON(){return this.data}},Vt={processors:{},addPostProcessor(e){this.processors[e.name]=e},handle(e,t,n,r,i){return e.forEach(e=>{t=this.processors[e]?.process(t,n,r,i)??t}),t}},Ht=Symbol(`i18next/PATH_KEY`);function Ut(){let e=[],t=Object.create(null),n;return t.get=(r,i)=>(n?.revoke?.(),i===Ht?e:(e.push(i),n=Proxy.revocable(r,t),n.proxy)),Proxy.revocable(Object.create(null),t).proxy}function Wt(e,t){let{[Ht]:n}=e(Ut()),r=t?.keySeparator??`.`,i=t?.nsSeparator??`:`,a=t?.enableSelector===`strict`;if(n.length>1&&i){let e=t?.ns,o=a?Array.isArray(e)?e:e?[e]:null:Array.isArray(e)?e:null;if(o&&(a?o:o.length>1?o.slice(1):[]).includes(n[0]))return`${n[0]}${i}${n.slice(1).join(r)}`}return n.join(r)}var Gt=e=>!G(e)&&typeof e!=`boolean`&&typeof e!=`number`,Kt=class e extends zt{constructor(e,t={}){super(),vt([`resourceStore`,`languageUtils`,`pluralResolver`,`interpolator`,`backendConnector`,`i18nFormat`,`utils`],e,this),this.options=t,this.options.keySeparator===void 0&&(this.options.keySeparator=`.`),this.logger=Rt.create(`translator`),this.checkedLoadedFor={}}changeLanguage(e){e&&(this.language=e)}exists(e,t={interpolation:{}}){let n={...t};if(e==null)return!1;let r=this.resolve(e,n);if(r?.res===void 0)return!1;let i=Gt(r.res);return!(n.returnObjects===!1&&i)}extractFromKey(e,t){let n=t.nsSeparator===void 0?this.options.nsSeparator:t.nsSeparator;n===void 0&&(n=`:`);let r=t.keySeparator===void 0?this.options.keySeparator:t.keySeparator,i=t.ns||this.options.defaultNS||[],a=n&&e.includes(n),o=!this.options.userDefinedKeySeparator&&!t.keySeparator&&!this.options.userDefinedNsSeparator&&!t.nsSeparator&&!Pt(e,n,r);if(a&&!o){let t=e.match(this.interpolator.nestingRegexp);if(t&&t.length>0)return{key:e,namespaces:G(i)?[i]:i};let a=e.split(n);(n!==r||n===r&&this.options.ns.includes(a[0]))&&(i=a.shift()),e=a.join(r)}return{key:e,namespaces:G(i)?[i]:i}}translate(t,n,r){let i=typeof n==`object`?{...n}:n;if(typeof i!=`object`&&this.options.overloadTranslationOptionHandler&&(i=this.options.overloadTranslationOptionHandler(arguments)),typeof i==`object`&&(i={...i}),i||={},t==null)return``;typeof t==`function`&&(t=Wt(t,{...this.options,...i})),Array.isArray(t)||(t=[String(t)]),t=t.map(e=>typeof e==`function`?Wt(e,{...this.options,...i}):String(e));let a=i.returnDetails===void 0?this.options.returnDetails:i.returnDetails,o=i.keySeparator===void 0?this.options.keySeparator:i.keySeparator,{key:s,namespaces:c}=this.extractFromKey(t[t.length-1],i),l=c[c.length-1],u=i.nsSeparator===void 0?this.options.nsSeparator:i.nsSeparator;u===void 0&&(u=`:`);let d=i.lng||this.language,f=i.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(d?.toLowerCase()===`cimode`)return f?a?{res:`${l}${u}${s}`,usedKey:s,exactUsedKey:s,usedLng:d,usedNS:l,usedParams:this.getUsedParamsDetails(i)}:`${l}${u}${s}`:a?{res:s,usedKey:s,exactUsedKey:s,usedLng:d,usedNS:l,usedParams:this.getUsedParamsDetails(i)}:s;let p=this.resolve(t,i),m=p?.res,h=p?.usedKey||s,g=p?.exactUsedKey||s,_=[`[object Number]`,`[object Function]`,`[object RegExp]`],v=i.joinArrays===void 0?this.options.joinArrays:i.joinArrays,y=!this.i18nFormat||this.i18nFormat.handleAsObject,b=i.count!==void 0&&!G(i.count),x=e.hasDefaultValue(i),S=b?this.pluralResolver.getSuffix(d,i.count,i):``,C=i.ordinal&&b?this.pluralResolver.getSuffix(d,i.count,{ordinal:!1}):``,w=b&&!i.ordinal&&i.count===0,T=w&&i[`defaultValue${this.options.pluralSeparator}zero`]||i[`defaultValue${S}`]||i[`defaultValue${C}`]||i.defaultValue,E=m;y&&!m&&x&&(E=T);let D=Gt(E),O=Object.prototype.toString.apply(E);if(y&&E&&D&&!_.includes(O)&&!(G(v)&&Array.isArray(E))){if(!i.returnObjects&&!this.options.returnObjects){this.options.returnedObjectHandler||this.logger.warn(`accessing an object - but returnObjects options is not enabled!`);let e=this.options.returnedObjectHandler?this.options.returnedObjectHandler(h,E,{...i,ns:c}):`key '${s} (${this.language})' returned an object instead of string.`;return a?(p.res=e,p.usedParams=this.getUsedParamsDetails(i),p):e}if(o){let e=Array.isArray(E),t=e?[]:{},n=e?g:h;for(let e in E)if(Object.prototype.hasOwnProperty.call(E,e)){let r=`${n}${o}${e}`;t[e]=x&&!m?this.translate(r,{...i,defaultValue:Gt(T)?T[e]:void 0,joinArrays:!1,ns:c}):this.translate(r,{...i,joinArrays:!1,ns:c}),t[e]===r&&(t[e]=E[e])}m=t}}else if(y&&G(v)&&Array.isArray(m))m=m.join(v),m&&=this.extendTranslation(m,t,i,r);else{let e=!1,n=!1;!this.isValidLookup(m)&&x&&(e=!0,m=T),this.isValidLookup(m)||(n=!0,m=s);let a=(i.missingKeyNoValueFallbackToKey||this.options.missingKeyNoValueFallbackToKey)&&n?void 0:m,c=x&&T!==m&&this.options.updateMissing;if(n||e||c){if(this.logger.log(c?`updateKey`:`missingKey`,d,l,b&&!c?`${s}${this.pluralResolver.getSuffix(d,i.count,i)}`:s,c?T:m),o){let e=this.resolve(s,{...i,keySeparator:!1});e&&e.res&&this.logger.warn(`Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.`)}let e=[],t=this.languageUtils.getFallbackCodes(this.options.fallbackLng,i.lng||this.language);if(this.options.saveMissingTo===`fallback`&&t&&t[0])for(let n=0;n{let r=x&&n!==m?n:a;this.options.missingKeyHandler?this.options.missingKeyHandler(e,l,t,r,c,i):this.backendConnector?.saveMissing&&this.backendConnector.saveMissing(e,l,t,r,c,i),this.emit(`missingKey`,e,l,t,m)};this.options.saveMissing&&(this.options.saveMissingPlurals&&b?e.forEach(e=>{let t=this.pluralResolver.getSuffixes(e,i);w&&i[`defaultValue${this.options.pluralSeparator}zero`]&&!t.includes(`${this.options.pluralSeparator}zero`)&&t.push(`${this.options.pluralSeparator}zero`),t.forEach(t=>{n([e],s+t,i[`defaultValue${t}`]||T)})}):n(e,s,T))}m=this.extendTranslation(m,t,i,p,r),n&&m===s&&this.options.appendNamespaceToMissingKey&&(m=`${l}${u}${s}`),(n||e)&&this.options.parseMissingKeyHandler&&(m=this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey?`${l}${u}${s}`:s,e?m:void 0,i))}return a?(p.res=m,p.usedParams=this.getUsedParamsDetails(i),p):m}extendTranslation(e,t,n,r,i){if(this.i18nFormat?.parse)e=this.i18nFormat.parse(e,{...this.options.interpolation.defaultVariables,...n},n.lng||this.language||r.usedLng,r.usedNS,r.usedKey,{resolved:r});else if(!n.skipInterpolation){n.interpolation&&this.interpolator.init({...n,interpolation:{...this.options.interpolation,...n.interpolation}});let a=G(e)&&(n?.interpolation?.skipOnVariables===void 0?this.options.interpolation.skipOnVariables:n.interpolation.skipOnVariables),o;if(a){let t=e.match(this.interpolator.nestingRegexp);o=t&&t.length}let s=n.replace&&!G(n.replace)?n.replace:n;if(this.options.interpolation.defaultVariables&&(s={...this.options.interpolation.defaultVariables,...s}),e=this.interpolator.interpolate(e,s,n.lng||this.language||r.usedLng,n),a){let t=e.match(this.interpolator.nestingRegexp),r=t&&t.length;oi?.[0]===e[0]&&!n.context?(this.logger.warn(`It seems you are nesting recursively key: ${e[0]} in key: ${t[0]}`),null):this.translate(...e,t),n)),n.interpolation&&this.interpolator.reset()}let a=n.postProcess||this.options.postProcess,o=G(a)?[a]:a;return e!=null&&o?.length&&n.applyPostProcessor!==!1&&(e=Vt.handle(o,e,t,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...r,usedParams:this.getUsedParamsDetails(n)},...n}:n,this)),e}resolve(e,t={}){let n,r,i,a,o;return G(e)&&(e=[e]),Array.isArray(e)&&(e=e.map(e=>typeof e==`function`?Wt(e,{...this.options,...t}):e)),e.forEach(e=>{if(this.isValidLookup(n))return;let s=this.extractFromKey(e,t),c=s.key;r=c;let l=s.namespaces;this.options.fallbackNS&&(l=l.concat(this.options.fallbackNS));let u=t.count!==void 0&&!G(t.count),d=u&&!t.ordinal&&t.count===0,f=t.context!==void 0&&(G(t.context)||typeof t.context==`number`)&&t.context!==``,p=t.lngs?t.lngs:this.languageUtils.toResolveHierarchy(t.lng||this.language,t.fallbackLng);l.forEach(e=>{this.isValidLookup(n)||(o=e,!this.checkedLoadedFor[`${p[0]}-${e}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(o)&&(this.checkedLoadedFor[`${p[0]}-${e}`]=!0,this.logger.warn(`key "${r}" for languages "${p.join(`, `)}" won't get resolved as namespace "${o}" was not yet loaded`,`This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!`)),p.forEach(r=>{if(this.isValidLookup(n))return;a=r;let o=[c];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(o,c,r,e,t);else{let e;u&&(e=this.pluralResolver.getSuffix(r,t.count,t));let n=`${this.options.pluralSeparator}zero`,i=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(u&&(t.ordinal&&e.startsWith(i)&&o.push(c+e.replace(i,this.options.pluralSeparator)),o.push(c+e),d&&o.push(c+n)),f){let r=`${c}${this.options.contextSeparator||`_`}${t.context}`;o.push(r),u&&(t.ordinal&&e.startsWith(i)&&o.push(r+e.replace(i,this.options.pluralSeparator)),o.push(r+e),d&&o.push(r+n))}}let s;for(;s=o.pop();)this.isValidLookup(n)||(i=s,n=this.getResource(r,e,s,t))}))})}),{res:n,usedKey:r,exactUsedKey:i,usedLng:a,usedNS:o}}isValidLookup(e){return e!==void 0&&!(!this.options.returnNull&&e===null)&&!(!this.options.returnEmptyString&&e===``)}getResource(e,t,n,r={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(e,t,n,r):this.resourceStore.getResource(e,t,n,r)}getUsedParamsDetails(e={}){let t=[`defaultValue`,`ordinal`,`context`,`replace`,`lng`,`lngs`,`fallbackLng`,`ns`,`keySeparator`,`nsSeparator`,`returnObjects`,`returnDetails`,`joinArrays`,`postProcess`,`interpolation`],n=e.replace&&!G(e.replace),r=n?e.replace:e;if(n&&e.count!==void 0&&(r={...r,count:e.count}),this.options.interpolation.defaultVariables&&(r={...this.options.interpolation.defaultVariables,...r}),!n){r={...r};for(let e of t)delete r[e]}return r}static hasDefaultValue(e){for(let t in e)if(Object.prototype.hasOwnProperty.call(e,t)&&t.startsWith(`defaultValue`)&&e[t]!==void 0)return!0;return!1}},qt=class{constructor(e){this.options=e,this.supportedLngs=this.options.supportedLngs||!1,this.logger=Rt.create(`languageUtils`),this.resolveHierarchyCache={}}clearCache(){this.resolveHierarchyCache={}}getScriptPartFromCode(e){if(e=It(e),!e||!e.includes(`-`))return null;let t=e.split(`-`);return t.length===2||(t.pop(),t[t.length-1].toLowerCase()===`x`)?null:this.formatLanguageCode(t.join(`-`))}getLanguagePartFromCode(e){if(e=It(e),!e||!e.includes(`-`))return e;let t=e.split(`-`);return this.formatLanguageCode(t[0])}formatLanguageCode(e){if(G(e)&&e.includes(`-`)){let t;try{t=Intl.getCanonicalLocales(e)[0]}catch{}return t&&this.options.lowerCaseLng&&(t=t.toLowerCase()),t||(this.options.lowerCaseLng?e.toLowerCase():e)}return this.options.cleanCode||this.options.lowerCaseLng?e.toLowerCase():e}isSupportedCode(e){return(this.options.load===`languageOnly`||this.options.nonExplicitSupportedLngs)&&(e=this.getLanguagePartFromCode(e)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.includes(e)}getBestMatchFromCodes(e){if(!e)return null;let t;return e.forEach(e=>{if(t)return;let n=this.formatLanguageCode(e);(!this.options.supportedLngs||this.isSupportedCode(n))&&(t=n)}),!t&&this.options.supportedLngs&&e.forEach(e=>{if(t)return;let n=this.getScriptPartFromCode(e);if(this.isSupportedCode(n))return t=n;let r=this.getLanguagePartFromCode(e);if(this.isSupportedCode(r))return t=r;t=this.options.supportedLngs.find(e=>e===r?!0:!e.includes(`-`)&&!r.includes(`-`)?!1:!!(e.includes(`-`)&&!r.includes(`-`)&&e.slice(0,e.indexOf(`-`))===r||e.startsWith(r)&&r.length>1))}),t||=this.getFallbackCodes(this.options.fallbackLng)[0],t}getFallbackCodes(e,t){if(!e)return[];if(typeof e==`function`&&(e=e(t)),G(e)&&(e=[e]),Array.isArray(e))return e;if(!t)return e.default||[];let n=e[t];return n||=e[this.getScriptPartFromCode(t)],n||=e[this.formatLanguageCode(t)],n||=e[this.getLanguagePartFromCode(t)],n||=e.default,n||[]}toResolveHierarchy(e,t){let n=this.options.fallbackLng,r=Array.isArray(n)?n.join(`|`):n;r!==this._cachedFallbackLng&&(this.resolveHierarchyCache={},this._cachedFallbackLng=r);let i=t===void 0||t===!1||G(t),a=t===void 0&&typeof this.options.fallbackLng==`function`,o=G(e)&&i&&!a,s=null;if(o){let n;n=t===void 0?`undefined`:t===!1?`boolean:false`:`string:${t}`,s=`${e.length}:${e}|${n}`}if(s!==null){let e=this.resolveHierarchyCache[s];if(e!==void 0)return e.slice()}let c=this.getFallbackCodes((t===!1?[]:t)||this.options.fallbackLng||[],e),l=[],u=e=>{e&&(this.isSupportedCode(e)?l.push(e):this.logger.warn(`rejecting language code not found in supportedLngs: ${e}`))};return G(e)&&(e.includes(`-`)||e.includes(`_`))?(this.options.load!==`languageOnly`&&u(this.formatLanguageCode(e)),this.options.load!==`languageOnly`&&this.options.load!==`currentOnly`&&u(this.getScriptPartFromCode(e)),this.options.load!==`currentOnly`&&u(this.getLanguagePartFromCode(e))):G(e)&&u(this.formatLanguageCode(e)),c.forEach(e=>{l.includes(e)||u(this.formatLanguageCode(e))}),s===null?l:(this.resolveHierarchyCache[s]=l,l.slice())}},Jt={zero:0,one:1,two:2,few:3,many:4,other:5},Yt={select:e=>e===1?`one`:`other`,resolvedOptions:()=>({pluralCategories:[`one`,`other`]})},Xt=class{constructor(e,t={}){this.languageUtils=e,this.options=t,this.logger=Rt.create(`pluralResolver`),this.pluralRulesCache={}}clearCache(){this.pluralRulesCache={}}getRule(e,t={}){let n=It(e===`dev`?`en`:e),r=t.ordinal?`ordinal`:`cardinal`,i=JSON.stringify({cleanedCode:n,type:r});if(i in this.pluralRulesCache)return this.pluralRulesCache[i];let a;try{a=new Intl.PluralRules(n,{type:r})}catch{if(typeof Intl>`u`)return this.logger.error(`No Intl support, please use an Intl polyfill!`),Yt;if(!e.match(/-|_/))return Yt;let n=this.languageUtils.getLanguagePartFromCode(e);a=this.getRule(n,t)}return this.pluralRulesCache[i]=a,a}needsPlural(e,t={}){let n=this.getRule(e,t);return n||=this.getRule(`dev`,t),n?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(e,t,n={}){return this.getSuffixes(e,n).map(e=>`${t}${e}`)}getSuffixes(e,t={}){let n=this.getRule(e,t);return n||=this.getRule(`dev`,t),n?n.resolvedOptions().pluralCategories.sort((e,t)=>Jt[e]-Jt[t]).map(e=>`${this.options.prepend}${t.ordinal?`ordinal${this.options.prepend}`:``}${e}`):[]}getSuffix(e,t,n={}){let r=this.getRule(e,n);return r?`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:``}${r.select(t)}`:(this.logger.warn(`no plural rule found for: ${e}`),this.getSuffix(`dev`,t,n))}},Zt=(e,t,n,r=`.`,i=!0)=>{let a=Et(e,t,n);return!a&&i&&G(n)&&(a=Ft(e,n,r),a===void 0&&(a=Ft(t,n,r))),a},Qt=e=>e.replace(/\$/g,`$$$$`),$t=class{constructor(e={}){this.logger=Rt.create(`interpolator`),this.options=e,this.format=e?.interpolation?.format||(e=>e),this.init(e)}init(e={}){e.interpolation||={escapeValue:!0};let{escape:t,escapeValue:n,useRawValueToEscape:r,prefix:i,prefixEscaped:a,suffix:o,suffixEscaped:s,formatSeparator:c,unescapeSuffix:l,unescapePrefix:u,nestingPrefix:d,nestingPrefixEscaped:f,nestingSuffix:p,nestingSuffixEscaped:m,nestingOptionsSeparator:h,maxReplaces:g,alwaysFormat:_}=e.interpolation;this.escape=t===void 0?At:t,this.escapeValue=n===void 0||n,this.useRawValueToEscape=r!==void 0&&r,this.prefix=i?Ot(i):a||`{{`,this.suffix=o?Ot(o):s||`}}`,this.formatSeparator=c||`,`,this.unescapePrefix=l?``:u?Ot(u):`-`,this.unescapeSuffix=this.unescapePrefix?``:l?Ot(l):``,this.nestingPrefix=d?Ot(d):f||Ot(`$t(`),this.nestingSuffix=p?Ot(p):m||Ot(`)`),this.nestingOptionsSeparator=h||`,`,this.maxReplaces=g||1e3,this.alwaysFormat=_!==void 0&&_,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){let e=(e,t)=>e?.source===t?(e.lastIndex=0,e):new RegExp(t,`g`);this.regexp=e(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=e(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=e(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(e,t,n,r){let i,a,o,s=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},c=e=>{if(!e.includes(this.formatSeparator)){let i=Zt(t,s,e,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(i,void 0,n,{...r,...t,interpolationkey:e}):i}let i=e.split(this.formatSeparator),a=i.shift().trim(),o=i.join(this.formatSeparator).trim();return this.format(Zt(t,s,a,this.options.keySeparator,this.options.ignoreJSONStructure),o,n,{...r,...t,interpolationkey:a})};this.resetRegExp(),!this.escapeValue&&typeof e==`string`&&/\$t\([^)]*\{[^}]*\{\{/.test(e)&&this.logger.warn(`nesting options string contains interpolated variables with escapeValue: false — if any of those values are attacker-controlled they can inject additional nesting options (e.g. redirect lng/ns). Sanitise untrusted input before passing it to t(), or keep escapeValue: true.`);let l=r?.missingInterpolationHandler||this.options.missingInterpolationHandler,u=r?.interpolation?.skipOnVariables===void 0?this.options.interpolation.skipOnVariables:r.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:e=>e},{regex:this.regexp,safeValue:e=>this.escapeValue?this.escape(e):e}].forEach(t=>{for(o=0;i=t.regex.exec(e);){let n=i[1].trim();if(a=c(n),a===void 0)if(typeof l==`function`){let t=l(e,i,r);a=G(t)?t:``}else if(r&&Object.prototype.hasOwnProperty.call(r,n))a=``;else if(u){a=i[0];continue}else this.logger.warn(`missed to pass in variable ${n} for interpolating ${e}`),a=``;else!G(a)&&!this.useRawValueToEscape&&(a=_t(a));let s=t.safeValue(a);if(e=e.replace(i[0],Qt(s)),u?(t.regex.lastIndex+=s.length,t.regex.lastIndex-=i[0].length):t.regex.lastIndex=0,o++,o>=this.maxReplaces)break}}),e}nest(e,t,n={}){let r,i,a,o=(e,t)=>{let n=this.nestingOptionsSeparator;if(!e.includes(n))return e;let r=e.split(RegExp(`${Ot(n)}[ ]*{`)),i=`{${r[1]}`;e=r[0],i=this.interpolate(i,a);let o=i.match(/'/g),s=i.match(/"/g);((o?.length??0)%2==0&&!s||(s?.length??0)%2!=0)&&(i=i.replace(/'/g,`"`));try{a=JSON.parse(i),t&&(a={...t,...a})}catch(t){return this.logger.warn(`failed parsing options string in nesting for key ${e}`,t),`${e}${n}${i}`}return a.defaultValue&&a.defaultValue.includes(this.prefix)&&delete a.defaultValue,e};for(;r=this.nestingRegexp.exec(e);){let s=[];a={...n},a=a.replace&&!G(a.replace)?a.replace:a,a.applyPostProcessor=!1,delete a.defaultValue;let c=/{.*}/s.test(r[1])?r[1].lastIndexOf(`}`)+1:r[1].indexOf(this.formatSeparator);if(c!==-1&&(s=r[1].slice(c).split(this.formatSeparator).map(e=>e.trim()).filter(Boolean),r[1]=r[1].slice(0,c)),i=t(o.call(this,r[1].trim(),a),a),i&&r[0]===e&&!G(i))return i;G(i)||(i=_t(i)),i||=(this.logger.warn(`missed to resolve ${r[1]} for nesting ${e}`),``),s.length&&(i=s.reduce((e,t)=>this.format(e,t,n.lng,{...n,interpolationkey:r[1].trim()}),i.trim())),e=e.replace(r[0],i),this.regexp.lastIndex=0}return e}},en=e=>{let t=e.toLowerCase().trim(),n={};if(e.includes(`(`)){let r=e.split(`(`);t=r[0].toLowerCase().trim();let i=r[1].slice(0,-1);t===`currency`&&!i.includes(`:`)?n.currency||=i.trim():t===`relativetime`&&!i.includes(`:`)?n.range||=i.trim():i.split(`;`).forEach(e=>{if(e){let[t,...r]=e.split(`:`),i=r.join(`:`).trim().replace(/^'+|'+$/g,``),a=t.trim();n[a]||(n[a]=i),i===`false`&&(n[a]=!1),i===`true`&&(n[a]=!0),isNaN(i)||(n[a]=parseInt(i,10))}})}return{formatName:t,formatOptions:n}},tn=e=>{let t={};return(n,r,i)=>{let a=i;i&&i.interpolationkey&&i.formatParams&&i.formatParams[i.interpolationkey]&&i[i.interpolationkey]&&(a={...a,[i.interpolationkey]:void 0});let o=r+JSON.stringify(a),s=t[o];return s||(s=e(It(r),i),t[o]=s),s(n)}},nn=e=>(t,n,r)=>e(It(n),r)(t),rn=class{constructor(e={}){this.logger=Rt.create(`formatter`),this.options=e,this.init(e)}init(e,t={interpolation:{}}){this.formatSeparator=t.interpolation.formatSeparator||`,`;let n=t.cacheInBuiltFormats?tn:nn;this.formats={number:n((e,t)=>{let n=new Intl.NumberFormat(e,{...t});return e=>n.format(e)}),currency:n((e,t)=>{let n=new Intl.NumberFormat(e,{...t,style:`currency`});return e=>n.format(e)}),datetime:n((e,t)=>{let n=new Intl.DateTimeFormat(e,{...t});return e=>n.format(e)}),relativetime:n((e,t)=>{let n=new Intl.RelativeTimeFormat(e,{...t});return e=>n.format(e,t.range||`day`)}),list:n((e,t)=>{let n=new Intl.ListFormat(e,{...t});return e=>n.format(e)})}}add(e,t){this.formats[e.toLowerCase().trim()]=t}addCached(e,t){this.formats[e.toLowerCase().trim()]=tn(t)}format(e,t,n,r={}){if(!t||e==null)return e;let i=t.split(this.formatSeparator),a=[];for(let e=0;e-1&&!t.includes(`)`)&&e+1{let{formatName:i,formatOptions:a}=en(t);if(this.formats[i]){let t=e;try{let o=r?.formatParams?.[r.interpolationkey]||{},s=o.locale||o.lng||r.locale||r.lng||n;t=this.formats[i](e,s,{...a,...r,...o})}catch(e){this.logger.warn(e)}return t}return this.logger.warn(`there was no format function for ${i}`),e},e)}},an=(e,t)=>{e.pending[t]!==void 0&&(delete e.pending[t],e.pendingCount--)},on=class extends zt{constructor(e,t,n,r={}){super(),this.backend=e,this.store=t,this.services=n,this.languageUtils=n.languageUtils,this.options=r,this.logger=Rt.create(`backendConnector`),this.waitingReads=[],this.maxParallelReads=r.maxParallelReads||10,this.readingCalls=0,this.maxRetries=r.maxRetries>=0?r.maxRetries:5,this.retryTimeout=r.retryTimeout>=1?r.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(n,r.backend,r)}queueLoad(e,t,n,r){let i={},a={},o={},s={};return e.forEach(e=>{let r=!0;t.forEach(t=>{let o=`${e}|${t}`;!n.reload&&this.store.hasResourceBundle(e,t)?this.state[o]=2:this.state[o]<0||(this.state[o]===1?a[o]===void 0&&(a[o]=!0):(this.state[o]=1,r=!1,a[o]===void 0&&(a[o]=!0),i[o]===void 0&&(i[o]=!0),s[t]===void 0&&(s[t]=!0)))}),r||(o[e]=!0)}),(Object.keys(i).length||Object.keys(a).length)&&this.queue.push({pending:a,pendingCount:Object.keys(a).length,loaded:{},errors:[],callback:r}),{toLoad:Object.keys(i),pending:Object.keys(a),toLoadLanguages:Object.keys(o),toLoadNamespaces:Object.keys(s)}}loaded(e,t,n){let r=e.split(`|`),i=r[0],a=r[1];t&&this.emit(`failedLoading`,i,a,t),!t&&n&&this.store.addResourceBundle(i,a,n,void 0,void 0,{skipCopy:!0}),this.state[e]=t?-1:2,t&&n&&(this.state[e]=0);let o={};this.queue.forEach(n=>{wt(n.loaded,[i],a),an(n,e),t&&n.errors.push(t),n.pendingCount===0&&!n.done&&(Object.keys(n.loaded).forEach(e=>{o[e]||(o[e]={});let t=n.loaded[e];t.length&&t.forEach(t=>{o[e][t]===void 0&&(o[e][t]=!0)})}),n.done=!0,n.errors.length?n.callback(n.errors):n.callback())}),this.emit(`loaded`,o),this.queue=this.queue.filter(e=>!e.done)}read(e,t,n,r=0,i=this.retryTimeout,a){if(!e.length)return a(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:e,ns:t,fcName:n,tried:r,wait:i,callback:a});return}this.readingCalls++;let o=(o,s)=>{if(this.readingCalls--,this.waitingReads.length>0){let e=this.waitingReads.shift();this.read(e.lng,e.ns,e.fcName,e.tried,e.wait,e.callback)}if(o&&s&&r{this.read(e,t,n,r+1,i*2,a)},i);return}a(o,s)},s=this.backend[n].bind(this.backend);if(s.length===2){try{let n=s(e,t);n&&typeof n.then==`function`?n.then(e=>o(null,e)).catch(o):o(null,n)}catch(e){o(e)}return}return s(e,t,o)}prepareLoading(e,t,n={},r){if(!this.backend)return this.logger.warn(`No backend was added via i18next.use. Will not load resources.`),r&&r();G(e)&&(e=this.languageUtils.toResolveHierarchy(e)),G(t)&&(t=[t]);let i=this.queueLoad(e,t,n,r);if(!i.toLoad.length)return i.pending.length||r(),null;i.toLoad.forEach(e=>{this.loadOne(e)})}load(e,t,n){this.prepareLoading(e,t,{},n)}reload(e,t,n){this.prepareLoading(e,t,{reload:!0},n)}loadOne(e,t=``){let n=e.split(`|`),r=n[0],i=n[1];this.read(r,i,`read`,void 0,void 0,(n,a)=>{n&&this.logger.warn(`${t}loading namespace ${i} for language ${r} failed`,n),!n&&a&&this.logger.log(`${t}loaded namespace ${i} for language ${r}`,a),this.loaded(e,n,a)})}saveMissing(e,t,n,r,i,a={},o=()=>{}){if(this.services?.utils?.hasLoadedNamespace&&!this.services?.utils?.hasLoadedNamespace(t)){this.logger.warn(`did not save key "${n}" as the namespace "${t}" was not yet loaded`,`This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!`);return}if(n!=null&&n!==``){if(this.backend?.create){let s={...a,isUpdate:i},c=this.backend.create.bind(this.backend);if(c.length<6)try{let i;i=c.length===5?c(e,t,n,r,s):c(e,t,n,r),i&&typeof i.then==`function`?i.then(e=>o(null,e)).catch(o):o(null,i)}catch(e){o(e)}else c(e,t,n,r,o,s)}!e||!e[0]||this.store.addResource(e[0],t,n,r)}}},sn=()=>({debug:!1,initAsync:!0,ns:[`translation`],defaultNS:[`translation`],fallbackLng:[`dev`],fallbackNS:!1,supportedLngs:!1,nonExplicitSupportedLngs:!1,load:`all`,preload:!1,keySeparator:`.`,nsSeparator:`:`,pluralSeparator:`_`,contextSeparator:`_`,enableSelector:!1,partialBundledLanguages:!1,saveMissing:!1,updateMissing:!1,saveMissingTo:`fallback`,saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,postProcessPassResolved:!1,returnNull:!1,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:!1,parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:e=>{let t={};if(typeof e[1]==`object`&&(t=e[1]),G(e[1])&&(t.defaultValue=e[1]),G(e[2])&&(t.tDescription=e[2]),typeof e[2]==`object`||typeof e[3]==`object`){let n=e[3]||e[2];Object.keys(n).forEach(e=>{t[e]=n[e]})}return t},interpolation:{escapeValue:!0,prefix:`{{`,suffix:`}}`,formatSeparator:`,`,unescapePrefix:`-`,nestingPrefix:`$t(`,nestingSuffix:`)`,nestingOptionsSeparator:`,`,maxReplaces:1e3,skipOnVariables:!0},cacheInBuiltFormats:!0}),cn=e=>(G(e.ns)&&(e.ns=[e.ns]),G(e.fallbackLng)&&(e.fallbackLng=[e.fallbackLng]),G(e.fallbackNS)&&(e.fallbackNS=[e.fallbackNS]),e.supportedLngs&&!e.supportedLngs.includes(`cimode`)&&(e.supportedLngs=e.supportedLngs.concat([`cimode`])),e),ln=()=>{},un=e=>{Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach(t=>{typeof e[t]==`function`&&(e[t]=e[t].bind(e))})},dn=class e extends zt{constructor(e={},t){if(super(),this.options=cn(e),this.services={},this.logger=Rt,this.modules={external:[]},un(this),t&&!this.isInitialized&&!e.isClone){if(!this.options.initAsync)return this.init(e,t),this;setTimeout(()=>{this.init(e,t)},0)}}init(e={},t){this.isInitializing=!0,typeof e==`function`&&(t=e,e={}),e.defaultNS==null&&e.ns&&(G(e.ns)?e.defaultNS=e.ns:e.ns.includes(`translation`)||(e.defaultNS=e.ns[0]));let n=sn();this.options={...n,...this.options,...cn(e)},this.options.interpolation={...n.interpolation,...this.options.interpolation},e.keySeparator!==void 0&&(this.options.userDefinedKeySeparator=e.keySeparator),e.nsSeparator!==void 0&&(this.options.userDefinedNsSeparator=e.nsSeparator),typeof this.options.overloadTranslationOptionHandler!=`function`&&(this.options.overloadTranslationOptionHandler=n.overloadTranslationOptionHandler);let r=e=>e?typeof e==`function`?new e:e:null;if(!this.options.isClone){this.modules.logger?Rt.init(r(this.modules.logger),this.options):Rt.init(null,this.options);let e;e=this.modules.formatter?this.modules.formatter:rn;let t=new qt(this.options);this.store=new Bt(this.options.resources,this.options);let n=this.services;n.logger=Rt,n.resourceStore=this.store,n.languageUtils=t,n.pluralResolver=new Xt(t,{prepend:this.options.pluralSeparator}),e&&(n.formatter=r(e),n.formatter.init&&n.formatter.init(n,this.options),this.options.interpolation.format=n.formatter.format.bind(n.formatter)),n.interpolator=new $t(this.options),n.utils={hasLoadedNamespace:this.hasLoadedNamespace.bind(this)},n.backendConnector=new on(r(this.modules.backend),n.resourceStore,n,this.options),n.backendConnector.on(`*`,(e,...t)=>{this.emit(e,...t)}),this.modules.languageDetector&&(n.languageDetector=r(this.modules.languageDetector),n.languageDetector.init&&n.languageDetector.init(n,this.options.detection,this.options)),this.modules.i18nFormat&&(n.i18nFormat=r(this.modules.i18nFormat),n.i18nFormat.init&&n.i18nFormat.init(this)),this.translator=new Kt(this.services,this.options),this.translator.on(`*`,(e,...t)=>{this.emit(e,...t)}),this.modules.external.forEach(e=>{e.init&&e.init(this)})}if(this.format=this.options.interpolation.format,t||=ln,this.options.fallbackLng&&!this.services.languageDetector&&!this.options.lng){let e=this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);e.length>0&&e[0]!==`dev`&&(this.options.lng=e[0])}!this.services.languageDetector&&!this.options.lng&&this.logger.warn(`init: no languageDetector is used and no lng is defined`),[`getResource`,`hasResourceBundle`,`getResourceBundle`,`getDataByLanguage`].forEach(e=>{this[e]=(...t)=>this.store[e](...t)}),[`addResource`,`addResources`,`addResourceBundle`,`removeResourceBundle`].forEach(e=>{this[e]=(...t)=>(this.store[e](...t),this)});let i=gt(),a=()=>{let e=(e,n)=>{this.isInitializing=!1,this.isInitialized&&!this.initializedStoreOnce&&this.logger.warn(`init: i18next is already initialized. You should call init just once!`),this.isInitialized=!0,this.options.isClone||this.logger.log(`initialized`,this.options),this.emit(`initialized`,this.options),i.resolve(n),t(e,n)};if((this.languages||this.isLanguageChangingTo)&&!this.isInitialized)return e(null,this.t.bind(this));this.changeLanguage(this.options.lng,e)};return this.options.resources||!this.options.initAsync?a():setTimeout(a,0),i}loadResources(e,t=ln){let n=t,r=G(e)?e:this.language;if(typeof e==`function`&&(n=e),!this.options.resources||this.options.partialBundledLanguages){if(r?.toLowerCase()===`cimode`&&(!this.options.preload||this.options.preload.length===0))return n();let e=[],t=t=>{t&&t!==`cimode`&&this.services.languageUtils.toResolveHierarchy(t).forEach(t=>{t!==`cimode`&&(e.includes(t)||e.push(t))})};r?t(r):this.services.languageUtils.getFallbackCodes(this.options.fallbackLng).forEach(e=>t(e)),this.options.preload?.forEach?.(e=>t(e)),this.services.backendConnector.load(e,this.options.ns,e=>{!e&&!this.resolvedLanguage&&this.language&&this.setResolvedLanguage(this.language),n(e)})}else n(null)}reloadResources(e,t,n){let r=gt();return typeof e==`function`&&(n=e,e=void 0),typeof t==`function`&&(n=t,t=void 0),e||=this.languages,t||=this.options.ns,n||=ln,this.services.backendConnector.reload(e,t,e=>{r.resolve(),n(e)}),r}use(e){if(!e)throw Error(`You are passing an undefined module! Please check the object you are passing to i18next.use()`);if(!e.type)throw Error(`You are passing a wrong module! Please check the object you are passing to i18next.use()`);return e.type===`backend`&&(this.modules.backend=e),(e.type===`logger`||e.log&&e.warn&&e.error)&&(this.modules.logger=e),e.type===`languageDetector`&&(this.modules.languageDetector=e),e.type===`i18nFormat`&&(this.modules.i18nFormat=e),e.type===`postProcessor`&&Vt.addPostProcessor(e),e.type===`formatter`&&(this.modules.formatter=e),e.type===`3rdParty`&&this.modules.external.push(e),this}setResolvedLanguage(e){if(!(!e||!this.languages)&&![`cimode`,`dev`].includes(e)){for(let e=0;e{this.language=e,this.languages=this.services.languageUtils.toResolveHierarchy(e),this.resolvedLanguage=void 0,this.setResolvedLanguage(e)},i=(i,a)=>{a?this.isLanguageChangingTo===e&&(r(a),this.translator.changeLanguage(a),this.isLanguageChangingTo=void 0,this.emit(`languageChanged`,a),this.logger.log(`languageChanged`,a)):this.isLanguageChangingTo=void 0,n.resolve((...e)=>this.t(...e)),t&&t(i,(...e)=>this.t(...e))},a=t=>{!e&&!t&&this.services.languageDetector&&(t=[]);let n=G(t)?t:t&&t[0],a=this.store.hasLanguageSomeTranslations(n)?n:this.services.languageUtils.getBestMatchFromCodes(G(t)?[t]:t);a&&(this.language||r(a),this.translator.language||this.translator.changeLanguage(a),this.services.languageDetector?.cacheUserLanguage?.(a)),this.loadResources(a,e=>{i(e,a)})};return!e&&this.services.languageDetector&&!this.services.languageDetector.async?a(this.services.languageDetector.detect()):!e&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect.length===0?this.services.languageDetector.detect().then(a):this.services.languageDetector.detect(a):a(e),n}getFixedT(e,t,n,r){let i=r?.scopeNs,a=(e,t,...r)=>{let o;o=typeof t==`object`?{...t}:this.options.overloadTranslationOptionHandler([e,t].concat(r)),o.lng=o.lng||a.lng,o.lngs=o.lngs||a.lngs;let s=o.ns!==void 0&&o.ns!==null;o.ns=o.ns||a.ns,o.keyPrefix!==``&&(o.keyPrefix=o.keyPrefix||n||a.keyPrefix);let c={...this.options,...o};Array.isArray(i)&&!s&&(c.ns=i),typeof o.keyPrefix==`function`&&(o.keyPrefix=Wt(o.keyPrefix,c));let l=this.options.keySeparator||`.`,u;return o.keyPrefix&&Array.isArray(e)?u=e.map(e=>(typeof e==`function`&&(e=Wt(e,c)),`${o.keyPrefix}${l}${e}`)):(typeof e==`function`&&(e=Wt(e,c)),u=o.keyPrefix?`${o.keyPrefix}${l}${e}`:e),this.t(u,o)};return G(e)?a.lng=e:a.lngs=e,a.ns=t,a.keyPrefix=n,a}t(...e){return this.translator?.translate(...e)}exists(...e){return this.translator?.exists(...e)}setDefaultNamespace(e){this.options.defaultNS=e}hasLoadedNamespace(e,t={}){if(!this.isInitialized)return this.logger.warn(`hasLoadedNamespace: i18next was not initialized`,this.languages),!1;if(!this.languages||!this.languages.length)return this.logger.warn(`hasLoadedNamespace: i18n.languages were undefined or empty`,this.languages),!1;let n=t.lng||this.resolvedLanguage||this.languages[0],r=this.options?this.options.fallbackLng:!1,i=this.languages[this.languages.length-1];if(n.toLowerCase()===`cimode`)return!0;let a=(e,t)=>{let n=this.services.backendConnector.state[`${e}|${t}`];return n===-1||n===0||n===2};if(t.precheck){let e=t.precheck(this,a);if(e!==void 0)return e}return!!(this.hasResourceBundle(n,e)||!this.services.backendConnector.backend||this.options.resources&&!this.options.partialBundledLanguages||a(n,e)&&(!r||a(i,e)))}loadNamespaces(e,t){let n=gt();return this.options.ns?(G(e)&&(e=[e]),e.forEach(e=>{this.options.ns.includes(e)||this.options.ns.push(e)}),this.loadResources(e=>{n.resolve(),t&&t(e)}),n):(t&&t(),Promise.resolve())}loadLanguages(e,t){let n=gt();G(e)&&(e=[e]);let r=this.options.preload||[],i=e.filter(e=>!r.includes(e)&&this.services.languageUtils.isSupportedCode(e));return i.length?(this.options.preload=r.concat(i),this.loadResources(e=>{n.resolve(),t&&t(e)}),n):(t&&t(),Promise.resolve())}dir(e){if(e||=this.resolvedLanguage||(this.languages?.length>0?this.languages[0]:this.language),!e)return`rtl`;try{let t=new Intl.Locale(e);if(t&&t.getTextInfo){let e=t.getTextInfo();if(e&&e.direction)return e.direction}}catch{}let t=`ar.shu.sqr.ssh.xaa.yhd.yud.aao.abh.abv.acm.acq.acw.acx.acy.adf.ads.aeb.aec.afb.ajp.apc.apd.arb.arq.ars.ary.arz.auz.avl.ayh.ayl.ayn.ayp.bbz.pga.he.iw.ps.pbt.pbu.pst.prp.prd.ug.ur.ydd.yds.yih.ji.yi.hbo.men.xmn.fa.jpr.peo.pes.prs.dv.sam.ckb`.split(`.`),n=this.services?.languageUtils||new qt(sn());return e.toLowerCase().indexOf(`-latn`)>1?`ltr`:t.includes(n.getLanguagePartFromCode(e))||e.toLowerCase().indexOf(`-arab`)>1?`rtl`:`ltr`}static createInstance(t={},n){let r=new e(t,n);return r.createInstance=e.createInstance,r}cloneInstance(t={},n=ln){let r=t.forkResourceStore;r&&delete t.forkResourceStore;let i={...this.options,...t,isClone:!0},a=new e(i);if((t.debug!==void 0||t.prefix!==void 0)&&(a.logger=a.logger.clone(t)),[`store`,`services`,`language`].forEach(e=>{a[e]=this[e]}),a.services={...this.services},a.services.utils={hasLoadedNamespace:a.hasLoadedNamespace.bind(a)},r&&(a.store=new Bt(Object.keys(this.store.data).reduce((e,t)=>(e[t]={...this.store.data[t]},e[t]=Object.keys(e[t]).reduce((n,r)=>(n[r]={...e[t][r]},n),e[t]),e),{}),i),a.services.resourceStore=a.store),t.interpolation){let e={...sn().interpolation,...this.options.interpolation,...t.interpolation},n={...i,interpolation:e};a.services.interpolator=new $t(n)}return a.translator=new Kt(a.services,i),a.translator.on(`*`,(e,...t)=>{a.emit(e,...t)}),a.init(i,n),a.translator.options=i,a.translator.backendConnector.services.utils={hasLoadedNamespace:a.hasLoadedNamespace.bind(a)},a}toJSON(){return{options:this.options,store:this.store,language:this.language,languages:this.languages,resolvedLanguage:this.resolvedLanguage}}}.createInstance();dn.createInstance,dn.dir,dn.init,dn.loadResources,dn.reloadResources,dn.use,dn.changeLanguage,dn.getFixedT,dn.t,dn.exists,dn.setDefaultNamespace,dn.hasLoadedNamespace,dn.loadNamespaces,dn.loadLanguages;var fn=(e,t,n,r)=>{let i=[n,{code:t,...r||{}}];if(e?.services?.logger?.forward)return e.services.logger.forward(i,`warn`,`react-i18next::`,!0);yn(i[0])&&(i[0]=`react-i18next:: ${i[0]}`),e?.services?.logger?.warn?e.services.logger.warn(...i):console?.warn&&console.warn(...i)},pn={},mn=(e,t,n,r)=>{yn(n)&&pn[n]||(yn(n)&&(pn[n]=new Date),fn(e,t,n,r))},hn=(e,t)=>()=>{if(e.isInitialized)t();else{let n=()=>{setTimeout(()=>{e.off(`initialized`,n)},0),t()};e.on(`initialized`,n)}},gn=(e,t,n)=>{e.loadNamespaces(t,hn(e,n))},_n=(e,t,n,r)=>{if(yn(n)&&(n=[n]),e.options.preload&&e.options.preload.indexOf(t)>-1)return gn(e,n,r);n.forEach(t=>{e.options.ns.indexOf(t)<0&&e.options.ns.push(t)}),e.loadLanguages(t,hn(e,r))},vn=(e,t,n={})=>!t.languages||!t.languages.length?(mn(t,`NO_LANGUAGES`,`i18n.languages were undefined or empty`,{languages:t.languages}),!0):t.hasLoadedNamespace(e,{lng:n.lng,precheck:(t,r)=>{if(n.bindI18n&&n.bindI18n.indexOf(`languageChanging`)>-1&&t.services.backendConnector.backend&&t.isLanguageChangingTo&&!r(t.isLanguageChangingTo,e))return!1}}),yn=e=>typeof e==`string`,bn=e=>typeof e==`object`&&!!e,xn=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,Sn={"&":`&`,"&":`&`,"<":`<`,"<":`<`,">":`>`,">":`>`,"'":`'`,"'":`'`,""":`"`,""":`"`," ":` `," ":` `,"©":`©`,"©":`©`,"®":`®`,"®":`®`,"…":`…`,"…":`…`,"/":`/`,"/":`/`},Cn=e=>Sn[e],wn={bindI18n:`languageChanged`,bindI18nStore:``,transEmptyNodeValue:``,transSupportBasicHtmlNodes:!0,transWrapTextNodes:``,transKeepBasicHtmlNodesFor:[`br`,`strong`,`i`,`p`],useSuspense:!0,unescape:e=>e.replace(xn,Cn),transDefaultProps:void 0},Tn=(e={})=>{wn={...wn,...e}},En=()=>wn,Dn,On=e=>{Dn=e},kn=()=>Dn,An={type:`3rdParty`,init(e){Tn(e.options.react),On(e)}},jn=(0,w.createContext)(),Mn=class{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach(e=>{this.usedNamespaces[e]||(this.usedNamespaces[e]=!0)})}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}},Nn=o((e=>{var t=d();function n(e,t){return e===t&&(e!==0||1/e==1/t)||e!==e&&t!==t}var r=typeof Object.is==`function`?Object.is:n,i=t.useState,a=t.useEffect,o=t.useLayoutEffect,s=t.useDebugValue;function c(e,t){var n=t(),r=i({inst:{value:n,getSnapshot:t}}),c=r[0].inst,u=r[1];return o(function(){c.value=n,c.getSnapshot=t,l(c)&&u({inst:c})},[e,n,t]),a(function(){return l(c)&&u({inst:c}),e(function(){l(c)&&u({inst:c})})},[e]),s(n),n}function l(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!r(e,n)}catch{return!0}}function u(e,t){return t()}var f=typeof window>`u`||window.document===void 0||window.document.createElement===void 0?u:c;e.useSyncExternalStore=t.useSyncExternalStore===void 0?f:t.useSyncExternalStore})),Pn=o(((e,t)=>{t.exports=Nn()}))(),Fn={t:(e,t)=>{if(yn(t))return t;if(bn(t)&&yn(t.defaultValue))return t.defaultValue;if(typeof e==`function`)return``;if(Array.isArray(e)){let t=e[e.length-1];return typeof t==`function`?``:t}return e},ready:!1},In=()=>()=>{},Ln=(e,t={})=>{let{i18n:n}=t,{i18n:r,defaultNS:i}=(0,w.useContext)(jn)||{},a=n||r||kn();a&&!a.reportNamespaces&&(a.reportNamespaces=new Mn),a||mn(a,`NO_I18NEXT_INSTANCE`,`useTranslation: You will need to pass in an i18next instance by using initReactI18next or by passing it via props or context. In monorepo setups, make sure there is only one instance of react-i18next.`);let o=(0,w.useMemo)(()=>({...En(),...a?.options?.react,...t}),[a,t]),{useSuspense:s,keyPrefix:c}=o,l=e||i||a?.options?.defaultNS,u=yn(l)?[l]:l||[`translation`],d=(0,w.useMemo)(()=>u,u);a?.reportNamespaces?.addUsedNamespaces?.(d);let f=(0,w.useRef)(0),p=(0,w.useCallback)(e=>{if(!a)return In;let{bindI18n:t,bindI18nStore:n}=o,r=()=>{f.current+=1,e()};return t&&a.on(t,r),n&&a.store.on(n,r),()=>{t&&t.split(` `).forEach(e=>a.off(e,r)),n&&n.split(` `).forEach(e=>a.store.off(e,r))}},[a,o]),m=(0,w.useRef)(),h=(0,w.useCallback)(()=>{if(!a)return Fn;let e=!!(a.isInitialized||a.initializedStoreOnce)&&d.every(e=>vn(e,a,o)),n=t.lng||a.language,r=f.current,i=m.current;if(i&&i.ready===e&&i.lng===n&&i.keyPrefix===c&&i.revision===r)return i;let s={t:a.getFixedT(n,o.nsMode===`fallback`?d:d[0],c,{scopeNs:d}),ready:e,lng:n,keyPrefix:c,revision:r};return m.current=s,s},[a,d,c,o,t.lng]),[g,_]=(0,w.useState)(0),{t:v,ready:y}=(0,Pn.useSyncExternalStore)(p,h,h);(0,w.useEffect)(()=>{if(a&&!y&&!s){let e=()=>_(e=>e+1);t.lng?_n(a,t.lng,d,e):gn(a,d,e)}},[a,t.lng,d,y,s,g]);let b=a||{},x=(0,w.useRef)(null),S=(0,w.useRef)(),C=e=>{let t=Object.getOwnPropertyDescriptors(e);t.__original&&delete t.__original;let n=Object.create(Object.getPrototypeOf(e),t);if(!Object.prototype.hasOwnProperty.call(n,`__original`))try{Object.defineProperty(n,"__original",{value:e,writable:!1,enumerable:!1,configurable:!1})}catch{}return n},T=(0,w.useMemo)(()=>{let e=b,t=e?.language,n=e;e&&(x.current&&x.current.__original===e&&S.current===t?n=x.current:(n=C(e),x.current=n,S.current=t));let r=!y&&!s?(...e)=>(mn(a,`USE_T_BEFORE_READY`,`useTranslation: t was called before ready. When using useSuspense: false, make sure to check the ready flag before using t.`),v(...e)):v,i=[r,n,y];return i.t=r,i.i18n=n,i.ready=y,i},[v,b,y,b.resolvedLanguage,b.language,b.languages]);if(a&&s&&!y){let e=!1;try{e=!1}catch{}throw e&&mn(a,`SUSPENDED_WHILE_LOADING`,`useTranslation: suspended while translations are loading (useSuspense is true by default). Add a boundary above this component, or set react.useSuspense: false in the i18next init options. https://react.i18next.com/latest/usetranslation-hook`),new Promise(e=>{let n=()=>e();t.lng?_n(a,t.lng,d,n):gn(a,d,n)})}return T};function Rn({i18n:e,defaultNS:t,children:n}){let r=(0,w.useMemo)(()=>({i18n:e,defaultNS:t}),[e,t]);return(0,w.createElement)(jn.Provider,{value:r},n)}var zn=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},Bn=(e=>e?zn(e):zn),Vn=e=>e;function Hn(e,t=Vn){let n=w.useSyncExternalStore(e.subscribe,w.useCallback(()=>t(e.getState()),[e,t]),w.useCallback(()=>t(e.getInitialState()),[e,t]));return w.useDebugValue(n),n}var Un=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),K=o(((e,t)=>{t.exports=Un()}))(),Wn=Array.from({length:16},(e,t)=>`cell-${t+1}`);function Gn({animated:e=!1,compact:t=!1}){let[n,r]=(0,w.useState)(0),i=t?10:16,a=(0,K.jsxs)(`strong`,{className:t?`brand-lockup compact`:`brand-lockup`,children:[(0,K.jsx)(`span`,{className:`brand-word`,children:`Open`}),(0,K.jsx)(`span`,{className:`pixel-mark`,role:`img`,"aria-label":`OpenPI`,children:Wn.slice(0,i).map(e=>(0,K.jsx)(`i`,{},e))})]},n);return e?(0,K.jsx)(`button`,{className:`landing-brand`,type:`button`,"aria-label":`Replay OpenPI logo animation`,onClick:()=>r(e=>e+1),children:a}):a}function Kn(e,t,n){return Math.min(n,Math.max(t,Math.round(e)))}function qn({side:e,value:t,min:n,max:r,defaultValue:i,collapseThreshold:a,onCollapse:o,onExpandPastMax:s,onChange:c,onDraggingChange:l}){let u=(0,w.useRef)(null),d=(e,t)=>{u.current?.pointerId===t&&(u.current=null,e.hasPointerCapture(t)&&e.releasePointerCapture(t),l(!1))},f=e=>c(Kn(e,n,r));return(0,K.jsx)(`div`,{className:`pane-resizer ${e}`,"aria-hidden":`true`,"data-pane-resizer":e,onDoubleClick:()=>f(i),onPointerDown:e=>{e.button===0&&(e.preventDefault(),u.current={pointerId:e.pointerId,startX:e.clientX,startValue:t},e.currentTarget.setPointerCapture(e.pointerId),l(!0))},onPointerMove:t=>{let n=u.current;if(!n||n.pointerId!==t.pointerId)return;let i=t.clientX-n.startX,c=n.startValue+(e===`left`?i:-i);if(s&&c>=r+48){d(t.currentTarget,t.pointerId),s();return}if(o&&a!==void 0&&c<=a){d(t.currentTarget,t.pointerId),o();return}f(c)},onPointerUp:e=>d(e.currentTarget,e.pointerId),onPointerCancel:e=>d(e.currentTarget,e.pointerId),onLostPointerCapture:()=>{u.current&&(u.current=null,l(!1))}})}function Jn(e,t){let n=t||{};return(e[e.length-1]===``?[...e,``]:e).join((n.padRight?` `:``)+`,`+(n.padLeft===!1?``:` `)).trim()}var Yn=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,Xn=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,Zn={};function Qn(e,t){return((t||Zn).jsx?Xn:Yn).test(e)}var $n=/[ \t\n\f\r]/g;function er(e){return typeof e==`object`?e.type===`text`&&tr(e.value):tr(e)}function tr(e){return e.replace($n,``)===``}var nr=class{constructor(e,t,n){this.normal=t,this.property=e,n&&(this.space=n)}};nr.prototype.normal={},nr.prototype.property={},nr.prototype.space=void 0;function rr(e,t){let n={},r={};for(let t of e)Object.assign(n,t.property),Object.assign(r,t.normal);return new nr(n,r,t)}function ir(e){return e.toLowerCase()}var ar=class{constructor(e,t){this.attribute=t,this.property=e}};ar.prototype.attribute=``,ar.prototype.booleanish=!1,ar.prototype.boolean=!1,ar.prototype.commaOrSpaceSeparated=!1,ar.prototype.commaSeparated=!1,ar.prototype.defined=!1,ar.prototype.mustUseProperty=!1,ar.prototype.number=!1,ar.prototype.overloadedBoolean=!1,ar.prototype.property=``,ar.prototype.spaceSeparated=!1,ar.prototype.space=void 0;var or=s({boolean:()=>cr,booleanish:()=>lr,commaOrSpaceSeparated:()=>pr,commaSeparated:()=>fr,number:()=>q,overloadedBoolean:()=>ur,spaceSeparated:()=>dr}),sr=0,cr=mr(),lr=mr(),ur=mr(),q=mr(),dr=mr(),fr=mr(),pr=mr();function mr(){return 2**++sr}var hr=Object.keys(or),gr=class extends ar{constructor(e,t,n,r){let i=-1;if(super(e,t),_r(this,`space`,r),typeof n==`number`)for(;++i4&&n.slice(0,4)===`data`&&Ar.test(t)){if(t.charAt(4)===`-`){let e=t.slice(5).replace(kr,Nr);r=`data`+e.charAt(0).toUpperCase()+e.slice(1)}else{let e=t.slice(4);if(!kr.test(e)){let n=e.replace(Or,Mr);n.charAt(0)!==`-`&&(n=`-`+n),t=`data`+n}}i=gr}return new i(r,t)}function Mr(e){return`-`+e.toLowerCase()}function Nr(e){return e.charAt(1).toUpperCase()}var Pr=rr([yr,Sr,wr,Tr,Er],`html`),Fr=rr([yr,Cr,wr,Tr,Er],`svg`);function Ir(e){return e.join(` `).trim()}var Lr=o(((e,t)=>{var n=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,r=/\n/g,i=/^\s*/,a=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,o=/^:\s*/,s=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,c=/^[;\s]*/,l=/^\s+|\s+$/g;function u(e,t){if(typeof e!=`string`)throw TypeError(`First argument must be a string`);if(!e)return[];t||={};var l=1,u=1;function f(e){var t=e.match(r);t&&(l+=t.length);var n=e.lastIndexOf(` -`);u=~n?e.length-n:u+e.length}function p(){var e={line:l,column:u};return function(t){return t.position=new m(e),_(),t}}function m(e){this.start=e,this.end={line:l,column:u},this.source=t.source}m.prototype.content=e;function h(n){var r=Error(t.source+`:`+l+`:`+u+`: `+n);if(r.reason=n,r.filename=t.source,r.line=l,r.column=u,r.source=e,!t.silent)throw r}function g(t){var n=t.exec(e);if(n){var r=n[0];return f(r),e=e.slice(r.length),n}}function _(){g(i)}function v(e){var t;for(e||=[];t=y();)t!==!1&&e.push(t);return e}function y(){var t=p();if(e.charAt(0)==`/`&&e.charAt(1)==`*`){for(var n=2;e.charAt(n)!=``&&(e.charAt(n)!=`*`||e.charAt(n+1)!=`/`);)++n;if(n+=2,e.charAt(n-1)===``)return h(`End of comment missing`);var r=e.slice(2,n-2);return u+=2,f(r),e=e.slice(n),u+=2,t({type:`comment`,comment:r})}}function b(){var e=p(),t=g(a);if(t){if(y(),!g(o))return h(`property missing ':'`);var r=g(s),i=e({type:`declaration`,property:d(t[0].replace(n,``)),value:r?d(r[0].replace(n,``)):``});return g(c),i}}function x(){var e=[];v(e);for(var t;t=b();)t!==!1&&(e.push(t),v(e));return e}return _(),x()}function d(e){return e?e.replace(l,``):``}t.exports=u})),Rr=o((e=>{var t=e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var n=t(Lr());function r(e,t){let r=null;if(!e||typeof e!=`string`)return r;let i=(0,n.default)(e),a=typeof t==`function`;return i.forEach(e=>{if(e.type!==`declaration`)return;let{property:n,value:i}=e;a?t(n,i,e):i&&(r||={},r[n]=i)}),r}})),zr=o((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.camelCase=void 0;var t=/^--[a-zA-Z0-9_-]+$/,n=/-([a-z])/g,r=/^[^-]+$/,i=/^-(webkit|moz|ms|o|khtml)-/,a=/^-(ms)-/,o=function(e){return!e||r.test(e)||t.test(e)},s=function(e,t){return t.toUpperCase()},c=function(e,t){return`${t}-`};e.camelCase=function(e,t){return t===void 0&&(t={}),o(e)?e:(e=e.toLowerCase(),e=t.reactCompat?e.replace(a,c):e.replace(i,c),e.replace(n,s))}})),Br=o(((e,t)=>{var n=(e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}})(Rr()),r=zr();function i(e,t){var i={};return!e||typeof e!=`string`||(0,n.default)(e,function(e,n){e&&n&&(i[(0,r.camelCase)(e,t)]=n)}),i}i.default=i,t.exports=i})),Vr=Ur(`end`),Hr=Ur(`start`);function Ur(e){return t;function t(t){let n=t&&t.position&&t.position[e]||{};if(typeof n.line==`number`&&n.line>0&&typeof n.column==`number`&&n.column>0)return{line:n.line,column:n.column,offset:typeof n.offset==`number`&&n.offset>-1?n.offset:void 0}}}function Wr(e){let t=Hr(e),n=Vr(e);if(t&&n)return{start:t,end:n}}function Gr(e){return!e||typeof e!=`object`?``:`position`in e||`type`in e?qr(e.position):`start`in e||`end`in e?qr(e):`line`in e||`column`in e?Kr(e):``}function Kr(e){return Jr(e&&e.line)+`:`+Jr(e&&e.column)}function qr(e){return Kr(e&&e.start)+`-`+Kr(e&&e.end)}function Jr(e){return e&&typeof e==`number`?e:1}var Yr=class extends Error{constructor(e,t,n){super(),typeof t==`string`&&(n=t,t=void 0);let r=``,i={},a=!1;if(t&&(i=`line`in t&&`column`in t||`start`in t&&`end`in t?{place:t}:`type`in t?{ancestors:[t],place:t.position}:{...t}),typeof e==`string`?r=e:!i.cause&&e&&(a=!0,r=e.message,i.cause=e),!i.ruleId&&!i.source&&typeof n==`string`){let e=n.indexOf(`:`);e===-1?i.ruleId=n:(i.source=n.slice(0,e),i.ruleId=n.slice(e+1))}if(!i.place&&i.ancestors&&i.ancestors){let e=i.ancestors[i.ancestors.length-1];e&&(i.place=e.position)}let o=i.place&&`start`in i.place?i.place.start:i.place;this.ancestors=i.ancestors||void 0,this.cause=i.cause||void 0,this.column=o?o.column:void 0,this.fatal=void 0,this.file=``,this.message=r,this.line=o?o.line:void 0,this.name=Gr(i.place)||`1:1`,this.place=i.place||void 0,this.reason=this.message,this.ruleId=i.ruleId||void 0,this.source=i.source||void 0,this.stack=a&&i.cause&&typeof i.cause.stack==`string`?i.cause.stack:``,this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}};Yr.prototype.file=``,Yr.prototype.name=``,Yr.prototype.reason=``,Yr.prototype.message=``,Yr.prototype.stack=``,Yr.prototype.column=void 0,Yr.prototype.line=void 0,Yr.prototype.ancestors=void 0,Yr.prototype.cause=void 0,Yr.prototype.fatal=void 0,Yr.prototype.place=void 0,Yr.prototype.ruleId=void 0,Yr.prototype.source=void 0;var Xr=l(Br(),1),Zr={}.hasOwnProperty,Qr=new Map,$r=/[A-Z]/g,ei=new Set([`table`,`tbody`,`thead`,`tfoot`,`tr`]),ti=new Set([`td`,`th`]);function ni(e,t){if(!t||t.Fragment===void 0)throw TypeError("Expected `Fragment` in options");let n=t.filePath||void 0,r;if(t.development){if(typeof t.jsxDEV!=`function`)throw TypeError("Expected `jsxDEV` in options when `development: true`");r=pi(n,t.jsxDEV)}else{if(typeof t.jsx!=`function`)throw TypeError("Expected `jsx` in production options");if(typeof t.jsxs!=`function`)throw TypeError("Expected `jsxs` in production options");r=fi(n,t.jsx,t.jsxs)}let i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||`react`,evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space===`svg`?Fr:Pr,stylePropertyNameCase:t.stylePropertyNameCase||`dom`,tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},a=ri(i,e,void 0);return a&&typeof a!=`string`?a:i.create(e,i.Fragment,{children:a||void 0},void 0)}function ri(e,t,n){if(t.type===`element`)return ii(e,t,n);if(t.type===`mdxFlowExpression`||t.type===`mdxTextExpression`)return ai(e,t);if(t.type===`mdxJsxFlowElement`||t.type===`mdxJsxTextElement`)return si(e,t,n);if(t.type===`mdxjsEsm`)return oi(e,t);if(t.type===`root`)return ci(e,t,n);if(t.type===`text`)return li(e,t)}function ii(e,t,n){let r=e.schema,i=r;t.tagName.toLowerCase()===`svg`&&r.space===`html`&&(i=Fr,e.schema=i),e.ancestors.push(t);let a=yi(e,t.tagName,!1),o=mi(e,t),s=gi(e,t);return ei.has(t.tagName)&&(s=s.filter(function(e){return typeof e!=`string`||!er(e)})),ui(e,o,a,t),di(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function ai(e,t){if(t.data&&t.data.estree&&e.evaluater){let n=t.data.estree.body[0];return n.type,e.evaluater.evaluateExpression(n.expression)}bi(e,t.position)}function oi(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);bi(e,t.position)}function si(e,t,n){let r=e.schema,i=r;t.name===`svg`&&r.space===`html`&&(i=Fr,e.schema=i),e.ancestors.push(t);let a=t.name===null?e.Fragment:yi(e,t.name,!0),o=hi(e,t),s=gi(e,t);return ui(e,o,a,t),di(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function ci(e,t,n){let r={};return di(r,gi(e,t)),e.create(t,e.Fragment,r,n)}function li(e,t){return t.value}function ui(e,t,n,r){typeof n!=`string`&&n!==e.Fragment&&e.passNode&&(t.node=r)}function di(e,t){if(t.length>0){let n=t.length>1?t:t[0];n&&(e.children=n)}}function fi(e,t,n){return r;function r(e,r,i,a){let o=Array.isArray(i.children)?n:t;return a?o(r,i,a):o(r,i)}}function pi(e,t){return n;function n(n,r,i,a){let o=Array.isArray(i.children),s=Hr(n);return t(r,i,a,o,{columnNumber:s?s.column-1:void 0,fileName:e,lineNumber:s?s.line:void 0},void 0)}}function mi(e,t){let n={},r,i;for(i in t.properties)if(i!==`children`&&Zr.call(t.properties,i)){let a=_i(e,i,t.properties[i]);if(a){let[i,o]=a;e.tableCellAlignToStyle&&i===`align`&&typeof o==`string`&&ti.has(t.tagName)?r=o:n[i]=o}}if(r){let t=n.style||={};t[e.stylePropertyNameCase===`css`?`text-align`:`textAlign`]=r}return n}function hi(e,t){let n={};for(let r of t.attributes)if(r.type===`mdxJsxExpressionAttribute`)if(r.data&&r.data.estree&&e.evaluater){let t=r.data.estree.body[0];t.type;let i=t.expression;i.type;let a=i.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else bi(e,t.position);else{let i=r.name,a;if(r.value&&typeof r.value==`object`)if(r.value.data&&r.value.data.estree&&e.evaluater){let t=r.value.data.estree.body[0];t.type,a=e.evaluater.evaluateExpression(t.expression)}else bi(e,t.position);else a=r.value===null||r.value;n[i]=a}return n}function gi(e,t){let n=[],r=-1,i=e.passKeys?new Map:Qr;for(;++ri?0:i+t:t>i?i:t,n=n>0?n:0,r.length<1e4)o=Array.from(r),o.unshift(t,n),e.splice(...o);else for(n&&e.splice(t,n);a0?(Mi(e,e.length,0,t),e):t}var Pi={}.hasOwnProperty;function Fi(e){let t={},n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)==65535||(n&65535)==65534||n>1114111?`�`:String.fromCodePoint(n)}function zi(e){return e.replace(/[\t\n\r ]+/g,` `).replace(/^ | $/g,``).toLowerCase().toUpperCase()}var Bi=Zi(/[A-Za-z]/),Vi=Zi(/[\dA-Za-z]/),Hi=Zi(/[#-'*+\--9=?A-Z^-~]/);function Ui(e){return e!==null&&(e<32||e===127)}var Wi=Zi(/\d/),Gi=Zi(/[\dA-Fa-f]/),Ki=Zi(/[!-/:-@[-`{-~]/);function J(e){return e!==null&&e<-2}function qi(e){return e!==null&&(e<0||e===32)}function Ji(e){return e===-2||e===-1||e===32}var Yi=Zi(/\p{P}|\p{S}/u),Xi=Zi(/\s/);function Zi(e){return t;function t(t){return t!==null&&t>-1&&e.test(String.fromCharCode(t))}}function Qi(e){let t=[],n=-1,r=0,i=0;for(;++n55295&&a<57344){let t=e.charCodeAt(n+1);a<56320&&t>56319&&t<57344?(o=String.fromCharCode(a,t),i=1):o=`�`}else o=String.fromCharCode(a);o&&=(t.push(e.slice(r,n),encodeURIComponent(o)),r=n+i+1,``),i&&=(n+=i,0)}return t.join(``)+e.slice(r)}function $i(e,t,n,r){let i=r?r-1:1/0,a=0;return o;function o(r){return Ji(r)?(e.enter(n),s(r)):t(r)}function s(r){return Ji(r)&&a++o))return;let n=t.events.length,a=n,s,c;for(;a--;)if(t.events[a][0]===`exit`&&t.events[a][1].type===`chunkFlow`){if(s){c=t.events[a][1].end;break}s=!0}for(_(r),e=n;er;){let r=n[i];t.containerState=r[1],r[0].exit.call(t,e)}n.length=r}function v(){i.write([null]),a=void 0,i=void 0,t.containerState._closeFlow=void 0}}function aa(e,t,n){return $i(e,e.attempt(this.parser.constructs.document,t,n),`linePrefix`,this.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)}function oa(e){if(e===null||qi(e)||Xi(e))return 1;if(Yi(e))return 2}function sa(e,t,n){let r=[],i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;let d={...e[r][1].end},f={...e[n][1].start};da(d,-c),da(f,c),o={type:c>1?`strongSequence`:`emphasisSequence`,start:d,end:{...e[r][1].end}},s={type:c>1?`strongSequence`:`emphasisSequence`,start:{...e[n][1].start},end:f},a={type:c>1?`strongText`:`emphasisText`,start:{...e[r][1].end},end:{...e[n][1].start}},i={type:c>1?`strong`:`emphasis`,start:{...o.start},end:{...s.end}},e[r][1].end={...o.start},e[n][1].start={...s.end},l=[],e[r][1].end.offset-e[r][1].start.offset&&(l=Ni(l,[[`enter`,e[r][1],t],[`exit`,e[r][1],t]])),l=Ni(l,[[`enter`,i,t],[`enter`,o,t],[`exit`,o,t],[`enter`,a,t]]),l=Ni(l,sa(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),l=Ni(l,[[`exit`,a,t],[`enter`,s,t],[`exit`,s,t],[`exit`,i,t]]),e[n][1].end.offset-e[n][1].start.offset?(u=2,l=Ni(l,[[`enter`,e[n][1],t],[`exit`,e[n][1],t]])):u=0,Mi(e,r-1,n-r+3,l),n=r+l.length-u-2;break}}for(n=-1;++n0&&Ji(t)?$i(e,v,`linePrefix`,a+1)(t):v(t)}function v(t){return t===null||J(t)?e.check(wa,h,b)(t):(e.enter(`codeFlowValue`),y(t))}function y(t){return t===null||J(t)?(e.exit(`codeFlowValue`),v(t)):(e.consume(t),y)}function b(n){return e.exit(`codeFenced`),t(n)}function x(e,t,n){let i=0;return a;function a(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),c}function c(t){return e.enter(`codeFencedFence`),Ji(t)?$i(e,l,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):l(t)}function l(t){return t===s?(e.enter(`codeFencedFenceSequence`),u(t)):n(t)}function u(t){return t===s?(i++,e.consume(t),u):i>=o?(e.exit(`codeFencedFenceSequence`),Ji(t)?$i(e,d,`whitespace`)(t):d(t)):n(t)}function d(r){return r===null||J(r)?(e.exit(`codeFencedFence`),t(r)):n(r)}}}function Da(e,t,n){let r=this;return i;function i(t){return t===null?n(t):(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}var Oa={name:`codeIndented`,tokenize:Aa},ka={partial:!0,tokenize:ja};function Aa(e,t,n){let r=this;return i;function i(t){return e.enter(`codeIndented`),$i(e,a,`linePrefix`,5)(t)}function a(e){let t=r.events[r.events.length-1];return t&&t[1].type===`linePrefix`&&t[2].sliceSerialize(t[1],!0).length>=4?o(e):n(e)}function o(t){return t===null?c(t):J(t)?e.attempt(ka,o,c)(t):(e.enter(`codeFlowValue`),s(t))}function s(t){return t===null||J(t)?(e.exit(`codeFlowValue`),o(t)):(e.consume(t),s)}function c(n){return e.exit(`codeIndented`),t(n)}}function ja(e,t,n){let r=this;return i;function i(t){return r.parser.lazy[r.now().line]?n(t):J(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),i):$i(e,a,`linePrefix`,5)(t)}function a(e){let a=r.events[r.events.length-1];return a&&a[1].type===`linePrefix`&&a[2].sliceSerialize(a[1],!0).length>=4?t(e):J(e)?i(e):n(e)}}var Ma={name:`codeText`,previous:Pa,resolve:Na,tokenize:Fa};function Na(e){let t=e.length-4,n=3,r,i;if((e[n][1].type===`lineEnding`||e[n][1].type===`space`)&&(e[t][1].type===`lineEnding`||e[t][1].type===`space`)){for(r=n;++r=this.left.length+this.right.length)throw RangeError("Cannot access index `"+e+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return ethis.left.length?this.right.slice(this.right.length-n+this.left.length,this.right.length-e+this.left.length).reverse():this.left.slice(e).concat(this.right.slice(this.right.length-n+this.left.length).reverse())}splice(e,t,n){let r=t||0;this.setCursor(Math.trunc(e));let i=this.right.splice(this.right.length-r,1/0);return n&&La(this.left,n),i.reverse()}pop(){return this.setCursor(1/0),this.left.pop()}push(e){this.setCursor(1/0),this.left.push(e)}pushMany(e){this.setCursor(1/0),La(this.left,e)}unshift(e){this.setCursor(0),this.right.push(e)}unshiftMany(e){this.setCursor(0),La(this.right,e.reverse())}setCursor(e){if(!(e===this.left.length||e>this.left.length&&this.right.length===0||e<0&&this.left.length===0))if(e=4?t(i):e.interrupt(r.parser.constructs.flow,n,t)(i)}}function Ga(e,t,n,r,i,a,o,s,c){let l=c||1/0,u=0;return d;function d(t){return t===60?(e.enter(r),e.enter(i),e.enter(a),e.consume(t),e.exit(a),f):t===null||t===32||t===41||Ui(t)?n(t):(e.enter(r),e.enter(o),e.enter(s),e.enter(`chunkString`,{contentType:`string`}),h(t))}function f(n){return n===62?(e.enter(a),e.consume(n),e.exit(a),e.exit(i),e.exit(r),t):(e.enter(s),e.enter(`chunkString`,{contentType:`string`}),p(n))}function p(t){return t===62?(e.exit(`chunkString`),e.exit(s),f(t)):t===null||t===60||J(t)?n(t):(e.consume(t),t===92?m:p)}function m(t){return t===60||t===62||t===92?(e.consume(t),p):p(t)}function h(i){return!u&&(i===null||i===41||qi(i))?(e.exit(`chunkString`),e.exit(s),e.exit(o),e.exit(r),t(i)):u999||l===null||l===91||l===93&&!c||l===94&&!s&&`_hiddenFootnoteSupport`in o.parser.constructs?n(l):l===93?(e.exit(a),e.enter(i),e.consume(l),e.exit(i),e.exit(r),t):J(l)?(e.enter(`lineEnding`),e.consume(l),e.exit(`lineEnding`),u):(e.enter(`chunkString`,{contentType:`string`}),d(l))}function d(t){return t===null||t===91||t===93||J(t)||s++>999?(e.exit(`chunkString`),u(t)):(e.consume(t),c||=!Ji(t),t===92?f:d)}function f(t){return t===91||t===92||t===93?(e.consume(t),s++,d):d(t)}}function qa(e,t,n,r,i,a){let o;return s;function s(t){return t===34||t===39||t===40?(e.enter(r),e.enter(i),e.consume(t),e.exit(i),o=t===40?41:t,c):n(t)}function c(n){return n===o?(e.enter(i),e.consume(n),e.exit(i),e.exit(r),t):(e.enter(a),l(n))}function l(t){return t===o?(e.exit(a),c(o)):t===null?n(t):J(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),$i(e,l,`linePrefix`)):(e.enter(`chunkString`,{contentType:`string`}),u(t))}function u(t){return t===o||t===null||J(t)?(e.exit(`chunkString`),l(t)):(e.consume(t),t===92?d:u)}function d(t){return t===o||t===92?(e.consume(t),u):u(t)}}function Ja(e,t){let n;return r;function r(i){return J(i)?(e.enter(`lineEnding`),e.consume(i),e.exit(`lineEnding`),n=!0,r):Ji(i)?$i(e,r,n?`linePrefix`:`lineSuffix`)(i):t(i)}}var Ya={name:`definition`,tokenize:Za},Xa={partial:!0,tokenize:Qa};function Za(e,t,n){let r=this,i;return a;function a(t){return e.enter(`definition`),o(t)}function o(t){return Ka.call(r,e,s,n,`definitionLabel`,`definitionLabelMarker`,`definitionLabelString`)(t)}function s(t){return i=zi(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),c):n(t)}function c(t){return qi(t)?Ja(e,l)(t):l(t)}function l(t){return Ga(e,u,n,`definitionDestination`,`definitionDestinationLiteral`,`definitionDestinationLiteralMarker`,`definitionDestinationRaw`,`definitionDestinationString`)(t)}function u(t){return e.attempt(Xa,d,d)(t)}function d(t){return Ji(t)?$i(e,f,`whitespace`)(t):f(t)}function f(a){return a===null||J(a)?(e.exit(`definition`),r.parser.defined.push(i),t(a)):n(a)}}function Qa(e,t,n){return r;function r(t){return qi(t)?Ja(e,i)(t):n(t)}function i(t){return qa(e,a,n,`definitionTitle`,`definitionTitleMarker`,`definitionTitleString`)(t)}function a(t){return Ji(t)?$i(e,o,`whitespace`)(t):o(t)}function o(e){return e===null||J(e)?t(e):n(e)}}var $a={name:`hardBreakEscape`,tokenize:eo};function eo(e,t,n){return r;function r(t){return e.enter(`hardBreakEscape`),e.consume(t),i}function i(r){return J(r)?(e.exit(`hardBreakEscape`),t(r)):n(r)}}var to={name:`headingAtx`,resolve:no,tokenize:ro};function no(e,t){let n=e.length-2,r=3,i,a;return e[r][1].type===`whitespace`&&(r+=2),n-2>r&&e[n][1].type===`whitespace`&&(n-=2),e[n][1].type===`atxHeadingSequence`&&(r===n-1||n-4>r&&e[n-2][1].type===`whitespace`)&&(n-=r+1===n?2:4),n>r&&(i={type:`atxHeadingText`,start:e[r][1].start,end:e[n][1].end},a={type:`chunkText`,start:e[r][1].start,end:e[n][1].end,contentType:`text`},Mi(e,r,n-r+1,[[`enter`,i,t],[`enter`,a,t],[`exit`,a,t],[`exit`,i,t]])),e}function ro(e,t,n){let r=0;return i;function i(t){return e.enter(`atxHeading`),a(t)}function a(t){return e.enter(`atxHeadingSequence`),o(t)}function o(t){return t===35&&r++<6?(e.consume(t),o):t===null||qi(t)?(e.exit(`atxHeadingSequence`),s(t)):n(t)}function s(n){return n===35?(e.enter(`atxHeadingSequence`),c(n)):n===null||J(n)?(e.exit(`atxHeading`),t(n)):Ji(n)?$i(e,s,`whitespace`)(n):(e.enter(`atxHeadingText`),l(n))}function c(t){return t===35?(e.consume(t),c):(e.exit(`atxHeadingSequence`),s(t))}function l(t){return t===null||t===35||qi(t)?(e.exit(`atxHeadingText`),s(t)):(e.consume(t),l)}}var io=`address.article.aside.base.basefont.blockquote.body.caption.center.col.colgroup.dd.details.dialog.dir.div.dl.dt.fieldset.figcaption.figure.footer.form.frame.frameset.h1.h2.h3.h4.h5.h6.head.header.hr.html.iframe.legend.li.link.main.menu.menuitem.nav.noframes.ol.optgroup.option.p.param.search.section.summary.table.tbody.td.tfoot.th.thead.title.tr.track.ul`.split(`.`),ao=[`pre`,`script`,`style`,`textarea`],oo={concrete:!0,name:`htmlFlow`,resolveTo:lo,tokenize:uo},so={partial:!0,tokenize:po},co={partial:!0,tokenize:fo};function lo(e){let t=e.length;for(;t--&&(e[t][0]!==`enter`||e[t][1].type!==`htmlFlow`););return t>1&&e[t-2][1].type===`linePrefix`&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function uo(e,t,n){let r=this,i,a,o,s,c;return l;function l(e){return u(e)}function u(t){return e.enter(`htmlFlow`),e.enter(`htmlFlowData`),e.consume(t),d}function d(s){return s===33?(e.consume(s),f):s===47?(e.consume(s),a=!0,h):s===63?(e.consume(s),i=3,r.interrupt?t:I):Bi(s)?(e.consume(s),o=String.fromCharCode(s),g):n(s)}function f(a){return a===45?(e.consume(a),i=2,p):a===91?(e.consume(a),i=5,s=0,m):Bi(a)?(e.consume(a),i=4,r.interrupt?t:I):n(a)}function p(i){return i===45?(e.consume(i),r.interrupt?t:I):n(i)}function m(i){return i===`CDATA[`.charCodeAt(s++)?(e.consume(i),s===6?r.interrupt?t:O:m):n(i)}function h(t){return Bi(t)?(e.consume(t),o=String.fromCharCode(t),g):n(t)}function g(s){if(s===null||s===47||s===62||qi(s)){let c=s===47,l=o.toLowerCase();return!c&&!a&&ao.includes(l)?(i=1,r.interrupt?t(s):O(s)):io.includes(o.toLowerCase())?(i=6,c?(e.consume(s),_):r.interrupt?t(s):O(s)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(s):a?v(s):y(s))}return s===45||Vi(s)?(e.consume(s),o+=String.fromCharCode(s),g):n(s)}function _(i){return i===62?(e.consume(i),r.interrupt?t:O):n(i)}function v(t){return Ji(t)?(e.consume(t),v):E(t)}function y(t){return t===47?(e.consume(t),E):t===58||t===95||Bi(t)?(e.consume(t),b):Ji(t)?(e.consume(t),y):E(t)}function b(t){return t===45||t===46||t===58||t===95||Vi(t)?(e.consume(t),b):x(t)}function x(t){return t===61?(e.consume(t),S):Ji(t)?(e.consume(t),x):y(t)}function S(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),c=t,C):Ji(t)?(e.consume(t),S):w(t)}function C(t){return t===c?(e.consume(t),c=null,T):t===null||J(t)?n(t):(e.consume(t),C)}function w(t){return t===null||t===34||t===39||t===47||t===60||t===61||t===62||t===96||qi(t)?x(t):(e.consume(t),w)}function T(e){return e===47||e===62||Ji(e)?y(e):n(e)}function E(t){return t===62?(e.consume(t),D):n(t)}function D(t){return t===null||J(t)?O(t):Ji(t)?(e.consume(t),D):n(t)}function O(t){return t===45&&i===2?(e.consume(t),M):t===60&&i===1?(e.consume(t),N):t===62&&i===4?(e.consume(t),L):t===63&&i===3?(e.consume(t),I):t===93&&i===5?(e.consume(t),F):J(t)&&(i===6||i===7)?(e.exit(`htmlFlowData`),e.check(so,R,k)(t)):t===null||J(t)?(e.exit(`htmlFlowData`),k(t)):(e.consume(t),O)}function k(t){return e.check(co,A,R)(t)}function A(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),j}function j(t){return t===null||J(t)?k(t):(e.enter(`htmlFlowData`),O(t))}function M(t){return t===45?(e.consume(t),I):O(t)}function N(t){return t===47?(e.consume(t),o=``,P):O(t)}function P(t){if(t===62){let n=o.toLowerCase();return ao.includes(n)?(e.consume(t),L):O(t)}return Bi(t)&&o.length<8?(e.consume(t),o+=String.fromCharCode(t),P):O(t)}function F(t){return t===93?(e.consume(t),I):O(t)}function I(t){return t===62?(e.consume(t),L):t===45&&i===2?(e.consume(t),I):O(t)}function L(t){return t===null||J(t)?(e.exit(`htmlFlowData`),R(t)):(e.consume(t),L)}function R(n){return e.exit(`htmlFlow`),t(n)}}function fo(e,t,n){let r=this;return i;function i(t){return J(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a):n(t)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}function po(e,t,n){return r;function r(r){return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),e.attempt(ma,t,n)}}var mo={name:`htmlText`,tokenize:ho};function ho(e,t,n){let r=this,i,a,o;return s;function s(t){return e.enter(`htmlText`),e.enter(`htmlTextData`),e.consume(t),c}function c(t){return t===33?(e.consume(t),l):t===47?(e.consume(t),x):t===63?(e.consume(t),y):Bi(t)?(e.consume(t),w):n(t)}function l(t){return t===45?(e.consume(t),u):t===91?(e.consume(t),a=0,m):Bi(t)?(e.consume(t),v):n(t)}function u(t){return t===45?(e.consume(t),p):n(t)}function d(t){return t===null?n(t):t===45?(e.consume(t),f):J(t)?(o=d,N(t)):(e.consume(t),d)}function f(t){return t===45?(e.consume(t),p):d(t)}function p(e){return e===62?M(e):e===45?f(e):d(e)}function m(t){return t===`CDATA[`.charCodeAt(a++)?(e.consume(t),a===6?h:m):n(t)}function h(t){return t===null?n(t):t===93?(e.consume(t),g):J(t)?(o=h,N(t)):(e.consume(t),h)}function g(t){return t===93?(e.consume(t),_):h(t)}function _(t){return t===62?M(t):t===93?(e.consume(t),_):h(t)}function v(t){return t===null||t===62?M(t):J(t)?(o=v,N(t)):(e.consume(t),v)}function y(t){return t===null?n(t):t===63?(e.consume(t),b):J(t)?(o=y,N(t)):(e.consume(t),y)}function b(e){return e===62?M(e):y(e)}function x(t){return Bi(t)?(e.consume(t),S):n(t)}function S(t){return t===45||Vi(t)?(e.consume(t),S):C(t)}function C(t){return J(t)?(o=C,N(t)):Ji(t)?(e.consume(t),C):M(t)}function w(t){return t===45||Vi(t)?(e.consume(t),w):t===47||t===62||qi(t)?T(t):n(t)}function T(t){return t===47?(e.consume(t),M):t===58||t===95||Bi(t)?(e.consume(t),E):J(t)?(o=T,N(t)):Ji(t)?(e.consume(t),T):M(t)}function E(t){return t===45||t===46||t===58||t===95||Vi(t)?(e.consume(t),E):D(t)}function D(t){return t===61?(e.consume(t),O):J(t)?(o=D,N(t)):Ji(t)?(e.consume(t),D):T(t)}function O(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),i=t,k):J(t)?(o=O,N(t)):Ji(t)?(e.consume(t),O):(e.consume(t),A)}function k(t){return t===i?(e.consume(t),i=void 0,j):t===null?n(t):J(t)?(o=k,N(t)):(e.consume(t),k)}function A(t){return t===null||t===34||t===39||t===60||t===61||t===96?n(t):t===47||t===62||qi(t)?T(t):(e.consume(t),A)}function j(e){return e===47||e===62||qi(e)?T(e):n(e)}function M(r){return r===62?(e.consume(r),e.exit(`htmlTextData`),e.exit(`htmlText`),t):n(r)}function N(t){return e.exit(`htmlTextData`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),P}function P(t){return Ji(t)?$i(e,F,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):F(t)}function F(t){return e.enter(`htmlTextData`),o(t)}}var go={name:`labelEnd`,resolveAll:bo,resolveTo:xo,tokenize:So},_o={tokenize:Co},vo={tokenize:wo},yo={tokenize:To};function bo(e){let t=-1,n=[];for(;++t=3&&(a===null||J(a))?(e.exit(`thematicBreak`),t(a)):n(a)}function c(t){return t===i?(e.consume(t),r++,c):(e.exit(`thematicBreakSequence`),Ji(t)?$i(e,s,`whitespace`)(t):s(t))}}var Po={continuation:{tokenize:Ro},exit:Bo,name:`list`,tokenize:Lo},Fo={partial:!0,tokenize:Vo},Io={partial:!0,tokenize:zo};function Lo(e,t,n){let r=this,i=r.events[r.events.length-1],a=i&&i[1].type===`linePrefix`?i[2].sliceSerialize(i[1],!0).length:0,o=0;return s;function s(t){let i=r.containerState.type||(t===42||t===43||t===45?`listUnordered`:`listOrdered`);if(i===`listUnordered`?!r.containerState.marker||t===r.containerState.marker:Wi(t)){if(r.containerState.type||(r.containerState.type=i,e.enter(i,{_container:!0})),i===`listUnordered`)return e.enter(`listItemPrefix`),t===42||t===45?e.check(Mo,n,l)(t):l(t);if(!r.interrupt||t===49)return e.enter(`listItemPrefix`),e.enter(`listItemValue`),c(t)}return n(t)}function c(t){return Wi(t)&&++o<10?(e.consume(t),c):(!r.interrupt||o<2)&&(r.containerState.marker?t===r.containerState.marker:t===41||t===46)?(e.exit(`listItemValue`),l(t)):n(t)}function l(t){return e.enter(`listItemMarker`),e.consume(t),e.exit(`listItemMarker`),r.containerState.marker=r.containerState.marker||t,e.check(ma,r.interrupt?n:u,e.attempt(Fo,f,d))}function u(e){return r.containerState.initialBlankLine=!0,a++,f(e)}function d(t){return Ji(t)?(e.enter(`listItemPrefixWhitespace`),e.consume(t),e.exit(`listItemPrefixWhitespace`),f):n(t)}function f(n){return r.containerState.size=a+r.sliceSerialize(e.exit(`listItemPrefix`),!0).length,t(n)}}function Ro(e,t,n){let r=this;return r.containerState._closeFlow=void 0,e.check(ma,i,a);function i(n){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,$i(e,t,`listItemIndent`,r.containerState.size+1)(n)}function a(n){return r.containerState.furtherBlankLines||!Ji(n)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,o(n)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(Io,t,o)(n))}function o(i){return r.containerState._closeFlow=!0,r.interrupt=void 0,$i(e,e.attempt(Po,t,n),`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(i)}}function zo(e,t,n){let r=this;return $i(e,i,`listItemIndent`,r.containerState.size+1);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`listItemIndent`&&i[2].sliceSerialize(i[1],!0).length===r.containerState.size?t(e):n(e)}}function Bo(e){e.exit(this.containerState.type)}function Vo(e,t,n){let r=this;return $i(e,i,`listItemPrefixWhitespace`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:5);function i(e){let i=r.events[r.events.length-1];return!Ji(e)&&i&&i[1].type===`listItemPrefixWhitespace`?t(e):n(e)}}var Ho={name:`setextUnderline`,resolveTo:Uo,tokenize:Wo};function Uo(e,t){let n=e.length,r,i,a;for(;n--;)if(e[n][0]===`enter`){if(e[n][1].type===`content`){r=n;break}e[n][1].type===`paragraph`&&(i=n)}else e[n][1].type===`content`&&e.splice(n,1),!a&&e[n][1].type===`definition`&&(a=n);let o={type:`setextHeading`,start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type=`setextHeadingText`,a?(e.splice(i,0,[`enter`,o,t]),e.splice(a+1,0,[`exit`,e[r][1],t]),e[r][1].end={...e[a][1].end}):e[r][1]=o,e.push([`exit`,o,t]),e}function Wo(e,t,n){let r=this,i;return a;function a(t){let a=r.events.length,s;for(;a--;)if(r.events[a][1].type!==`lineEnding`&&r.events[a][1].type!==`linePrefix`&&r.events[a][1].type!==`content`){s=r.events[a][1].type===`paragraph`;break}return!r.parser.lazy[r.now().line]&&(r.interrupt||s)?(e.enter(`setextHeadingLine`),i=t,o(t)):n(t)}function o(t){return e.enter(`setextHeadingLineSequence`),s(t)}function s(t){return t===i?(e.consume(t),s):(e.exit(`setextHeadingLineSequence`),Ji(t)?$i(e,c,`lineSuffix`)(t):c(t))}function c(r){return r===null||J(r)?(e.exit(`setextHeadingLine`),t(r)):n(r)}}var Go={tokenize:Ko};function Ko(e){let t=this,n=e.attempt(ma,r,e.attempt(this.parser.constructs.flowInitial,i,$i(e,e.attempt(this.parser.constructs.flow,i,e.attempt(Ba,i)),`linePrefix`)));return n;function r(r){if(r===null){e.consume(r);return}return e.enter(`lineEndingBlank`),e.consume(r),e.exit(`lineEndingBlank`),t.currentConstruct=void 0,n}function i(r){if(r===null){e.consume(r);return}return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),t.currentConstruct=void 0,n}}var qo={resolveAll:Zo()},Jo=Xo(`string`),Yo=Xo(`text`);function Xo(e){return{resolveAll:Zo(e===`text`?Qo:void 0),tokenize:t};function t(t){let n=this,r=this.parser.constructs[e],i=t.attempt(r,a,o);return a;function a(e){return c(e)?i(e):o(e)}function o(e){if(e===null){t.consume(e);return}return t.enter(`data`),t.consume(e),s}function s(e){return c(e)?(t.exit(`data`),i(e)):(t.consume(e),s)}function c(e){if(e===null)return!0;let t=r[e],i=-1;if(t)for(;++iss,contentInitial:()=>ts,disable:()=>cs,document:()=>es,flow:()=>rs,flowInitial:()=>ns,insideSpan:()=>os,string:()=>is,text:()=>as}),es={42:Po,43:Po,45:Po,48:Po,49:Po,50:Po,51:Po,52:Po,53:Po,54:Po,55:Po,56:Po,57:Po,62:ga},ts={91:Ya},ns={[-2]:Oa,[-1]:Oa,32:Oa},rs={35:to,42:Mo,45:[Ho,Mo],60:oo,61:Ho,95:Mo,96:Ta,126:Ta},is={38:Sa,92:ba},as={[-5]:Ao,[-4]:Ao,[-3]:Ao,33:Eo,38:Sa,42:ca,60:[fa,mo],91:Oo,92:[$a,ba],93:go,95:ca,96:Ma},os={null:[ca,qo]},ss={null:[42,95]},cs={null:[]};function ls(e,t,n){let r={_bufferIndex:-1,_index:0,line:n&&n.line||1,column:n&&n.column||1,offset:n&&n.offset||0},i={},a=[],o=[],s=[],c={attempt:C(x),check:C(S),consume:v,enter:y,exit:b,interrupt:C(S,{interrupt:!0})},l={code:null,containerState:{},defineSkip:h,events:[],now:m,parser:e,previous:null,sliceSerialize:f,sliceStream:p,write:d},u=t.tokenize.call(l,c);return t.resolveAll&&a.push(t),l;function d(e){return o=Ni(o,e),g(),o[o.length-1]===null?(w(t,0),l.events=sa(a,l.events,l),l.events):[]}function f(e,t){return ds(p(e),t)}function p(e){return us(o,e)}function m(){let{_bufferIndex:e,_index:t,line:n,column:i,offset:a}=r;return{_bufferIndex:e,_index:t,line:n,column:i,offset:a}}function h(e){i[e.line]=e.column,E()}function g(){let e;for(;r._index-1){let e=o[0];typeof e==`string`?o[0]=e.slice(r):o.shift()}a>0&&o.push(e[i].slice(0,a))}return o}function ds(e,t){let n=-1,r=[],i;for(;++n>>=0,e===0?32:31-(Ae(e)/je|0)|0}var Ne=256,Pe=262144,Fe=4194304;function Ie(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Le(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ie(n))):i=Ie(o):i=Ie(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ie(n))):i=Ie(o)):i=Ie(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Re(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function ze(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Be(){var e=Fe;return Fe<<=1,!(Fe&62914560)&&(Fe=4194304),e}function Ve(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function He(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Ue(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0`u`||window.document===void 0||window.document.createElement===void 0),en=!1;if($t)try{var tn={};Object.defineProperty(tn,"passive",{get:function(){en=!0}}),window.addEventListener(`test`,tn,tn),window.removeEventListener(`test`,tn,tn)}catch{en=!1}var nn=null,rn=null,an=null;function on(){if(an)return an;var e,t=rn,n=t.length,r,i=`value`in nn?nn.value:nn.textContent,a=i.length;for(e=0;e=Rn),Vn=` `,J=!1;function Hn(e,t){switch(e){case`keyup`:return In.indexOf(t.keyCode)!==-1;case`keydown`:return t.keyCode!==229;case`keypress`:case`mousedown`:case`focusout`:return!0;default:return!1}}function Un(e){return e=e.detail,typeof e==`object`&&`data`in e?e.data:null}var Wn=!1;function Gn(e,t){switch(e){case`compositionend`:return Un(t);case`keypress`:return t.which===32?(J=!0,Vn):null;case`textInput`:return e=t.data,e===Vn&&J?null:e;default:return null}}function Kn(e,t){if(Wn)return e===`compositionend`||!Ln&&Hn(e,t)?(e=on(),an=rn=nn=null,Wn=!1,e):null;switch(e){case`paste`:return null;case`keypress`:if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}a:{for(;n;){if(n.nextSibling){n=n.nextSibling;break a}n=n.parentNode}n=void 0}n=pr(n)}}function hr(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?hr(e,t.parentNode):`contains`in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function gr(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Dt(e.document);t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href==`string`}catch{n=!1}if(n)e=t.contentWindow;else break;t=Dt(e.document)}return t}function _r(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t===`input`&&(e.type===`text`||e.type===`search`||e.type===`tel`||e.type===`url`||e.type===`password`)||t===`textarea`||e.contentEditable===`true`)}var vr=$t&&`documentMode`in document&&11>=document.documentMode,yr=null,br=null,xr=null,Sr=!1;function Cr(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Sr||yr==null||yr!==Dt(r)||(r=yr,`selectionStart`in r&&_r(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),xr&&fr(xr,r)||(xr=r,r=Od(br,`onSelect`),0>=o,i-=o,hi=1<<32-ke(t)+i|n<h?(g=d,d=null):g=d.sibling;var _=p(i,d,s[h],c);if(_===null){d===null&&(d=g);break}e&&d&&_.alternate===null&&t(i,d),a=o(_,a,h),u===null?l=_:u.sibling=_,u=_,d=g}if(h===s.length)return n(i,d),wi&&_i(i,h),l;if(d===null){for(;hg?(_=h,h=null):_=h.sibling;var y=p(a,h,v.value,l);if(y===null){h===null&&(h=_);break}e&&h&&y.alternate===null&&t(a,h),s=o(y,s,g),d===null?u=y:d.sibling=y,d=y,h=_}if(v.done)return n(a,h),wi&&_i(a,g),u;if(h===null){for(;!v.done;g++,v=c.next())v=f(a,v.value,l),v!==null&&(s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return wi&&_i(a,g),u}for(h=r(h);!v.done;g++,v=c.next())v=m(h,a,g,v.value,l),v!==null&&(e&&v.alternate!==null&&h.delete(v.key===null?g:v.key),s=o(v,s,g),d===null?u=v:d.sibling=v,d=v);return e&&h.forEach(function(e){return t(a,e)}),wi&&_i(a,g),u}function b(e,r,o,c){if(typeof o==`object`&&o&&o.type===y&&o.key===null&&(o=o.props.children),typeof o==`object`&&o){switch(o.$$typeof){case _:a:{for(var l=o.key;r!==null;){if(r.key===l){if(l=o.type,l===y){if(r.tag===7){n(e,r.sibling),c=a(r,o.props.children),c.return=e,e=c;break a}}else if(r.elementType===l||typeof l==`object`&&l&&l.$$typeof===O&&_a(l)===r.type){n(e,r.sibling),c=a(r,o.props),wa(c,o),c.return=e,e=c;break a}n(e,r);break}t(e,r),r=r.sibling}o.type===y?(c=ni(o.props.children,e.mode,c,o.key),c.return=e,e=c):(c=ti(o.type,o.key,o.props,null,e.mode,c),wa(c,o),c.return=e,e=c)}return s(e);case v:a:{for(l=o.key;r!==null;){if(r.key===l)if(r.tag===4&&r.stateNode.containerInfo===o.containerInfo&&r.stateNode.implementation===o.implementation){n(e,r.sibling),c=a(r,o.children||[]),c.return=e,e=c;break a}else{n(e,r);break}t(e,r),r=r.sibling}c=ai(o,e.mode,c),c.return=e,e=c}return s(e);case O:return o=_a(o),b(e,r,o,c)}if(F(o))return h(e,r,o,c);if(M(o)){if(l=M(o),typeof l!=`function`)throw Error(i(150));return o=l.call(o),g(e,r,o,c)}if(typeof o.then==`function`)return b(e,r,Ca(o),c);if(o.$$typeof===C)return b(e,r,Gi(e,o),c);Ta(e,o)}return typeof o==`string`&&o!==``||typeof o==`number`||typeof o==`bigint`?(o=``+o,r!==null&&r.tag===6?(n(e,r.sibling),c=a(r,o),c.return=e,e=c):(n(e,r),c=ri(o,e.mode,c),c.return=e,e=c),s(e)):n(e,r)}return function(e,t,n,r){try{Sa=0;var i=b(e,t,n,r);return xa=null,i}catch(t){if(t===da||t===pa)throw t;var a=Zr(29,t,null,e.mode);return a.lanes=r,a.return=e,a}}}var Da=Ea(!0),Oa=Ea(!1),ka=!1;function Aa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function ja(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,callbacks:null})}function Ma(e){return{lane:e,tag:0,payload:null,callback:null,next:null}}function Na(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,Pl&2){var i=r.pending;return i===null?t.next=t:(t.next=i.next,i.next=t),r.pending=t,t=Jr(e),qr(e,null,n),t}return Wr(e,r,t,n),Jr(e)}function Pa(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,n&4194048)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ge(e,n)}}function Fa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var i=null,a=null;if(n=n.firstBaseUpdate,n!==null){do{var o={lane:n.lane,tag:n.tag,payload:n.payload,callback:null,next:null};a===null?i=a=o:a=a.next=o,n=n.next}while(n!==null);a===null?i=a=t:a=a.next=t}else i=a=t;n={baseState:r.baseState,firstBaseUpdate:i,lastBaseUpdate:a,shared:r.shared,callbacks:r.callbacks},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}var Ia=!1;function La(){if(Ia){var e=na;if(e!==null)throw e}}function Ra(e,t,n,r){Ia=!1;var i=e.updateQueue;ka=!1;var a=i.firstBaseUpdate,o=i.lastBaseUpdate,s=i.shared.pending;if(s!==null){i.shared.pending=null;var c=s,l=c.next;c.next=null,o===null?a=l:o.next=l,o=c;var u=e.alternate;u!==null&&(u=u.updateQueue,s=u.lastBaseUpdate,s!==o&&(s===null?u.firstBaseUpdate=l:s.next=l,u.lastBaseUpdate=c))}if(a!==null){var d=i.baseState;o=0,u=l=c=null,s=a;do{var f=s.lane&-536870913,p=f!==s.lane;if(p?(Ll&f)===f:(r&f)===f){f!==0&&f===ta&&(Ia=!0),u!==null&&(u=u.next={lane:0,tag:s.tag,payload:s.payload,callback:null,next:null});a:{var h=e,g=s;f=t;var _=n;switch(g.tag){case 1:if(h=g.payload,typeof h==`function`){d=h.call(_,d,f);break a}d=h;break a;case 3:h.flags=h.flags&-65537|128;case 0:if(h=g.payload,f=typeof h==`function`?h.call(_,d,f):h,f==null)break a;d=m({},d,f);break a;case 2:ka=!0}}f=s.callback,f!==null&&(e.flags|=64,p&&(e.flags|=8192),p=i.callbacks,p===null?i.callbacks=[f]:p.push(f))}else p={lane:f,tag:s.tag,payload:s.payload,callback:s.callback,next:null},u===null?(l=u=p,c=d):u=u.next=p,o|=f;if(s=s.next,s===null){if(s=i.shared.pending,s===null)break;p=s,s=p.next,p.next=null,i.lastBaseUpdate=p,i.shared.pending=null}}while(1);u===null&&(c=d),i.baseState=c,i.firstBaseUpdate=l,i.lastBaseUpdate=u,a===null&&(i.shared.lanes=0),Gl|=o,e.lanes=o,e.memoizedState=d}}function za(e,t){if(typeof e!=`function`)throw Error(i(191,e));e.call(t)}function Ba(e,t){var n=e.callbacks;if(n!==null)for(e.callbacks=null,e=0;ea?a:8;var o=I.T,s={};I.T=s,Ds(e,!1,t,n);try{var c=i(),l=I.S;l!==null&&l(s,c),typeof c==`object`&&c&&typeof c.then==`function`?Es(e,t,aa(c,r),pu(e)):Es(e,t,r,pu(e))}catch(n){Es(e,t,{then:function(){},status:`rejected`,reason:n},pu())}finally{L.p=a,o!==null&&s.types!==null&&(o.types=s.types),I.T=o}}function gs(){}function _s(e,t,n,r){if(e.tag!==5)throw Error(i(476));var a=vs(e).queue;hs(e,a,t,R,n===null?gs:function(){return ys(e),n(r)})}function vs(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:R,baseState:R,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Oo,lastRenderedState:R},next:null};var n={};return t.next={memoizedState:n,baseState:n,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Oo,lastRenderedState:n},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function ys(e){var t=vs(e);t.next===null&&(t=e.alternate.memoizedState),Es(e,t.next.queue,{},pu())}function bs(){return X(tp)}function xs(){return Co().memoizedState}function Ss(){return Co().memoizedState}function Cs(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var n=pu();e=Ma(n);var r=Na(t,e,n);r!==null&&(hu(r,t,n),Pa(r,t,n)),t={cache:Zi()},e.payload=t;return}t=t.return}}function ws(e,t,n){var r=pu();n={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null},Os(e)?ks(t,n):(n=Gr(e,t,n,r),n!==null&&(hu(n,e,r),As(n,t,r)))}function Ts(e,t,n){Es(e,t,n,pu())}function Es(e,t,n,r){var i={lane:r,revertLane:0,gesture:null,action:n,hasEagerState:!1,eagerState:null,next:null};if(Os(e))ks(t,i);else{var a=e.alternate;if(e.lanes===0&&(a===null||a.lanes===0)&&(a=t.lastRenderedReducer,a!==null))try{var o=t.lastRenderedState,s=a(o,n);if(i.hasEagerState=!0,i.eagerState=s,dr(s,o))return Wr(e,t,i,0),Fl===null&&Ur(),!1}catch{}if(n=Gr(e,t,i,r),n!==null)return hu(n,e,r),As(n,t,r),!0}return!1}function Ds(e,t,n,r){if(r={lane:2,revertLane:fd(),gesture:null,action:r,hasEagerState:!1,eagerState:null,next:null},Os(e)){if(t)throw Error(i(479))}else t=Gr(e,n,r,2),t!==null&&hu(t,e,2)}function Os(e){var t=e.alternate;return e===no||t!==null&&t===no}function ks(e,t){oo=ao=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function As(e,t,n){if(n&4194048){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Ge(e,n)}}var js={readContext:X,use:Eo,useCallback:po,useContext:po,useEffect:po,useImperativeHandle:po,useLayoutEffect:po,useInsertionEffect:po,useMemo:po,useReducer:po,useRef:po,useState:po,useDebugValue:po,useDeferredValue:po,useTransition:po,useSyncExternalStore:po,useId:po,useHostTransitionStatus:po,useFormState:po,useActionState:po,useOptimistic:po,useMemoCache:po,useCacheRefresh:po};js.useEffectEvent=po;var Ms={readContext:X,use:Eo,useCallback:function(e,t){return So().memoizedState=[e,t===void 0?null:t],e},useContext:X,useEffect:ns,useImperativeHandle:function(e,t,n){n=n==null?null:n.concat([e]),es(4194308,4,cs.bind(null,t,e),n)},useLayoutEffect:function(e,t){return es(4194308,4,e,t)},useInsertionEffect:function(e,t){es(4,2,e,t)},useMemo:function(e,t){var n=So();t=t===void 0?null:t;var r=e();if(so){Oe(!0);try{e()}finally{Oe(!1)}}return n.memoizedState=[r,t],r},useReducer:function(e,t,n){var r=So();if(n!==void 0){var i=n(t);if(so){Oe(!0);try{n(t)}finally{Oe(!1)}}}else i=t;return r.memoizedState=r.baseState=i,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:i},r.queue=e,e=e.dispatch=ws.bind(null,no,e),[r.memoizedState,e]},useRef:function(e){var t=So();return e={current:e},t.memoizedState=e},useState:function(e){e=Ro(e);var t=e.queue,n=Ts.bind(null,no,t);return t.dispatch=n,[e.memoizedState,n]},useDebugValue:us,useDeferredValue:function(e,t){return ps(So(),e,t)},useTransition:function(){var e=Ro(!1);return e=hs.bind(null,no,e.queue,!0,!1),So().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,n){var r=no,a=So();if(wi){if(n===void 0)throw Error(i(407));n=n()}else{if(n=t(),Fl===null)throw Error(i(349));Ll&127||No(r,t,n)}a.memoizedState=n;var o={value:n,getSnapshot:t};return a.queue=o,ns(Fo.bind(null,r,o,e),[e]),r.flags|=2048,Qo(9,{destroy:void 0},Po.bind(null,r,o,n,t),null),n},useId:function(){var e=So(),t=Fl.identifierPrefix;if(wi){var n=gi,r=hi;n=(r&~(1<<32-ke(r)-1)).toString(32)+n,t=`_`+t+`R_`+n,n=co++,0<\/script>`,o=o.removeChild(o.firstChild);break;case`select`:o=typeof r.is==`string`?s.createElement(`select`,{is:r.is}):s.createElement(`select`),r.multiple?o.multiple=!0:r.size&&(o.size=r.size);break;default:o=typeof r.is==`string`?s.createElement(a,{is:r.is}):s.createElement(a)}}o[Qe]=t,o[$e]=r;a:for(s=t.child;s!==null;){if(s.tag===5||s.tag===6)o.appendChild(s.stateNode);else if(s.tag!==4&&s.tag!==27&&s.child!==null){s.child.return=s,s=s.child;continue}if(s===t)break a;for(;s.sibling===null;){if(s.return===null||s.return===t)break a;s=s.return}s.sibling.return=s.return,s=s.sibling}t.stateNode=o;a:switch(Ld(o,a,r),a){case`button`:case`input`:case`select`:case`textarea`:r=!!r.autoFocus;break a;case`img`:r=!0;break a;default:r=!1}r&&Ec(t)}}return jc(t),Dc(t,t.type,e===null?null:e.memoizedProps,t.pendingProps,n),null;case 6:if(e&&t.stateNode!=null)e.memoizedProps!==r&&Ec(t);else{if(typeof r!=`string`&&t.stateNode===null)throw Error(i(166));if(e=W.current,ji(t)){if(e=t.stateNode,n=t.memoizedProps,r=null,a=Si,a!==null)switch(a.tag){case 27:case 5:r=a.memoizedProps}e[Qe]=t,e=!!(e.nodeValue===n||r!==null&&!0===r.suppressHydrationWarning||Pd(e.nodeValue,n)),e||Oi(t,!0)}else e=Ud(e).createTextNode(r),e[Qe]=t,t.stateNode=e}return jc(t),null;case 31:if(n=t.memoizedState,e===null||e.memoizedState!==null){if(r=ji(t),n!==null){if(e===null){if(!r)throw Error(i(318));if(e=t.memoizedState,e=e===null?null:e.dehydrated,!e)throw Error(i(557));e[Qe]=t}else Mi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;jc(t),e=!1}else n=Ni(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=n),e=!0;if(!e)return t.flags&256?(Qa(t),t):(Qa(t),null);if(t.flags&128)throw Error(i(558))}return jc(t),null;case 13:if(r=t.memoizedState,e===null||e.memoizedState!==null&&e.memoizedState.dehydrated!==null){if(a=ji(t),r!==null&&r.dehydrated!==null){if(e===null){if(!a)throw Error(i(318));if(a=t.memoizedState,a=a===null?null:a.dehydrated,!a)throw Error(i(317));a[Qe]=t}else Mi(),!(t.flags&128)&&(t.memoizedState=null),t.flags|=4;jc(t),a=!1}else a=Ni(),e!==null&&e.memoizedState!==null&&(e.memoizedState.hydrationErrors=a),a=!0;if(!a)return t.flags&256?(Qa(t),t):(Qa(t),null)}return Qa(t),t.flags&128?(t.lanes=n,t):(n=r!==null,e=e!==null&&e.memoizedState!==null,n&&(r=t.child,a=null,r.alternate!==null&&r.alternate.memoizedState!==null&&r.alternate.memoizedState.cachePool!==null&&(a=r.alternate.memoizedState.cachePool.pool),o=null,r.memoizedState!==null&&r.memoizedState.cachePool!==null&&(o=r.memoizedState.cachePool.pool),o!==a&&(r.flags|=2048)),n!==e&&n&&(t.child.flags|=8192),kc(t,t.updateQueue),jc(t),null);case 4:return re(),e===null&&wd(t.stateNode.containerInfo),jc(t),null;case 10:return zi(t.type),jc(t),null;case 19:if(H($a),r=t.memoizedState,r===null)return jc(t),null;if(a=!!(t.flags&128),o=r.rendering,o===null)if(a)Ac(r,!1);else{if(Wl!==0||e!==null&&e.flags&128)for(e=t.child;e!==null;){if(o=eo(e),o!==null){for(t.flags|=128,Ac(r,!1),e=o.updateQueue,t.updateQueue=e,kc(t,e),t.subtreeFlags=0,e=n,n=t.child;n!==null;)ei(n,e),n=n.sibling;return U($a,$a.current&1|2),wi&&_i(t,r.treeForkCount),t.child}e=e.sibling}r.tail!==null&&_e()>tu&&(t.flags|=128,a=!0,Ac(r,!1),t.lanes=4194304)}else{if(!a)if(e=eo(o),e!==null){if(t.flags|=128,a=!0,e=e.updateQueue,t.updateQueue=e,kc(t,e),Ac(r,!0),r.tail===null&&r.tailMode===`hidden`&&!o.alternate&&!wi)return jc(t),null}else 2*_e()-r.renderingStartTime>tu&&n!==536870912&&(t.flags|=128,a=!0,Ac(r,!1),t.lanes=4194304);r.isBackwards?(o.sibling=t.child,t.child=o):(e=r.last,e===null?t.child=o:e.sibling=o,r.last=o)}return r.tail===null?(jc(t),null):(e=r.tail,r.rendering=e,r.tail=e.sibling,r.renderingStartTime=_e(),e.sibling=null,n=$a.current,U($a,a?n&1|2:n&1),wi&&_i(t,r.treeForkCount),e);case 22:case 23:return Qa(t),Ga(),r=t.memoizedState!==null,e===null?r&&(t.flags|=8192):e.memoizedState!==null!==r&&(t.flags|=8192),r?n&536870912&&!(t.flags&128)&&(jc(t),t.subtreeFlags&6&&(t.flags|=8192)):jc(t),n=t.updateQueue,n!==null&&kc(t,n.retryQueue),n=null,e!==null&&e.memoizedState!==null&&e.memoizedState.cachePool!==null&&(n=e.memoizedState.cachePool.pool),r=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(r=t.memoizedState.cachePool.pool),r!==n&&(t.flags|=2048),e!==null&&H(sa),null;case 24:return n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),zi(Xi),jc(t),null;case 25:return null;case 30:return null}throw Error(i(156,t.tag))}function Nc(e,t){switch(bi(t),t.tag){case 1:return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return zi(Xi),re(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 26:case 27:case 5:return ae(t),null;case 31:if(t.memoizedState!==null){if(Qa(t),t.alternate===null)throw Error(i(340));Mi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 13:if(Qa(t),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(i(340));Mi()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return H($a),null;case 4:return re(),null;case 10:return zi(t.type),null;case 22:case 23:return Qa(t),Ga(),e!==null&&H(sa),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 24:return zi(Xi),null;case 25:return null;default:return null}}function Pc(e,t){switch(bi(t),t.tag){case 3:zi(Xi),re();break;case 26:case 27:case 5:ae(t);break;case 4:re();break;case 31:t.memoizedState!==null&&Qa(t);break;case 13:Qa(t);break;case 19:H($a);break;case 10:zi(t.type);break;case 22:case 23:Qa(t),Ga(),e!==null&&H(sa);break;case 24:zi(Xi)}}function Fc(e,t){try{var n=t.updateQueue,r=n===null?null:n.lastEffect;if(r!==null){var i=r.next;n=i;do{if((n.tag&e)===e){r=void 0;var a=n.create,o=n.inst;r=a(),o.destroy=r}n=n.next}while(n!==i)}}catch(e){Gu(t,t.return,e)}}function Ic(e,t,n){try{var r=t.updateQueue,i=r===null?null:r.lastEffect;if(i!==null){var a=i.next;r=a;do{if((r.tag&e)===e){var o=r.inst,s=o.destroy;if(s!==void 0){o.destroy=void 0,i=t;var c=n,l=s;try{l()}catch(e){Gu(i,c,e)}}}r=r.next}while(r!==a)}}catch(e){Gu(t,t.return,e)}}function Lc(e){var t=e.updateQueue;if(t!==null){var n=e.stateNode;try{Ba(t,n)}catch(t){Gu(e,e.return,t)}}}function Rc(e,t,n){n.props=zs(e.type,e.memoizedProps),n.state=e.memoizedState;try{n.componentWillUnmount()}catch(n){Gu(e,t,n)}}function zc(e,t){try{var n=e.ref;if(n!==null){switch(e.tag){case 26:case 27:case 5:var r=e.stateNode;break;case 30:r=e.stateNode;break;default:r=e.stateNode}typeof n==`function`?e.refCleanup=n(r):n.current=r}}catch(n){Gu(e,t,n)}}function Bc(e,t){var n=e.ref,r=e.refCleanup;if(n!==null)if(typeof r==`function`)try{r()}catch(n){Gu(e,t,n)}finally{e.refCleanup=null,e=e.alternate,e!=null&&(e.refCleanup=null)}else if(typeof n==`function`)try{n(null)}catch(n){Gu(e,t,n)}else n.current=null}function Vc(e){var t=e.type,n=e.memoizedProps,r=e.stateNode;try{a:switch(t){case`button`:case`input`:case`select`:case`textarea`:n.autoFocus&&r.focus();break a;case`img`:n.src?r.src=n.src:n.srcSet&&(r.srcset=n.srcSet)}}catch(t){Gu(e,e.return,t)}}function Hc(e,t,n){try{var r=e.stateNode;Rd(r,e.type,n,t),r[$e]=t}catch(t){Gu(e,e.return,t)}}function Uc(e){return e.tag===5||e.tag===3||e.tag===26||e.tag===27&&ef(e.type)||e.tag===4}function Wc(e){a:for(;;){for(;e.sibling===null;){if(e.return===null||Uc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.tag===27&&ef(e.type)||e.flags&2||e.child===null||e.tag===4)continue a;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Gc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?(n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n).insertBefore(e,t):(t=n.nodeType===9?n.body:n.nodeName===`HTML`?n.ownerDocument.body:n,t.appendChild(e),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Wt));else if(r!==4&&(r===27&&ef(e.type)&&(n=e.stateNode,t=null),e=e.child,e!==null))for(Gc(e,t,n),e=e.sibling;e!==null;)Gc(e,t,n),e=e.sibling}function Kc(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(r===27&&ef(e.type)&&(n=e.stateNode),e=e.child,e!==null))for(Kc(e,t,n),e=e.sibling;e!==null;)Kc(e,t,n),e=e.sibling}function qc(e){var t=e.stateNode,n=e.memoizedProps;try{for(var r=e.type,i=t.attributes;i.length;)t.removeAttributeNode(i[0]);Ld(t,r,n),t[Qe]=e,t[$e]=n}catch(t){Gu(e,e.return,t)}}var Jc=!1,Yc=!1,Xc=!1,Zc=typeof WeakSet==`function`?WeakSet:Set,Qc=null;function $c(e,t){if(e=e.containerInfo,Vd=up,e=gr(e),_r(e)){if(`selectionStart`in e)var n={start:e.selectionStart,end:e.selectionEnd};else a:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var a=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break a}var s=0,c=-1,l=-1,u=0,d=0,f=e,p=null;b:for(;;){for(var m;f!==n||a!==0&&f.nodeType!==3||(c=s+a),f!==o||r!==0&&f.nodeType!==3||(l=s+r),f.nodeType===3&&(s+=f.nodeValue.length),(m=f.firstChild)!==null;)p=f,f=m;for(;;){if(f===e)break b;if(p===n&&++u===a&&(c=s),p===o&&++d===r&&(l=s),(m=f.nextSibling)!==null)break;f=p,p=f.parentNode}f=m}n=c===-1||l===-1?null:{start:c,end:l}}else n=null}n||={start:0,end:0}}else n=null;for(Hd={focusedElem:e,selectionRange:n},up=!1,Qc=t;Qc!==null;)if(t=Qc,e=t.child,t.subtreeFlags&1028&&e!==null)e.return=t,Qc=e;else for(;Qc!==null;){switch(t=Qc,o=t.alternate,e=t.flags,t.tag){case 0:if(e&4&&(e=t.updateQueue,e=e===null?null:e.events,e!==null))for(n=0;n title`))),Ld(o,r,n),o[Qe]=e,dt(o),r=o;break a;case`link`:var s=Wf(`link`,`href`,a).get(r+(n.href||``));if(s){for(var c=0;cg&&(o=g,g=h,h=o);var _=mr(s,h),v=mr(s,g);if(_&&v&&(p.rangeCount!==1||p.anchorNode!==_.node||p.anchorOffset!==_.offset||p.focusNode!==v.node||p.focusOffset!==v.offset)){var y=d.createRange();y.setStart(_.node,_.offset),p.removeAllRanges(),h>g?(p.addRange(y),p.extend(v.node,v.offset)):(y.setEnd(v.node,v.offset),p.addRange(y))}}}}for(d=[],p=s;p=p.parentNode;)p.nodeType===1&&d.push({element:p,left:p.scrollLeft,top:p.scrollTop});for(typeof s.focus==`function`&&s.focus(),s=0;sn?32:n,I.T=null,n=lu,lu=null;var o=au,s=su;if(iu=0,ou=au=null,su=0,Pl&6)throw Error(i(331));var c=Pl;if(Pl|=4,kl(o.current),xl(o,o.current,s,n),Pl=c,ad(0,!1),De&&typeof De.onPostCommitFiberRoot==`function`)try{De.onPostCommitFiberRoot(Ee,o)}catch{}return!0}finally{L.p=a,I.T=r,Vu(e,t)}}function Wu(e,t,n){t=si(n,t),t=Gs(e.stateNode,t,2),e=Na(e,t,2),e!==null&&(He(e,2),id(e))}function Gu(e,t,n){if(e.tag===3)Wu(e,e,n);else for(;t!==null;){if(t.tag===3){Wu(t,e,n);break}if(t.tag===1){var r=t.stateNode;if(typeof t.type.getDerivedStateFromError==`function`||typeof r.componentDidCatch==`function`&&(ru===null||!ru.has(r))){e=si(n,e),n=Ks(2),r=Na(t,n,2),r!==null&&(qs(n,r,t,e),He(r,2),id(r));break}}t=t.return}}function Ku(e,t,n){var r=e.pingCache;if(r===null){r=e.pingCache=new Nl;var i=new Set;r.set(t,i)}else i=r.get(t),i===void 0&&(i=new Set,r.set(t,i));i.has(n)||(Hl=!0,i.add(n),e=qu.bind(null,e,t,n),t.then(e,e))}function qu(e,t,n){var r=e.pingCache;r!==null&&r.delete(t),e.pingedLanes|=e.suspendedLanes&n,e.warmLanes&=~n,Fl===e&&(Ll&n)===n&&(Wl===4||Wl===3&&(Ll&62914560)===Ll&&300>_e()-$l?!(Pl&2)&&Su(e,0):ql|=n,Yl===Ll&&(Yl=0)),id(e)}function Ju(e,t){t===0&&(t=Be()),e=Kr(e,t),e!==null&&(He(e,t),id(e))}function Yu(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),Ju(e,n)}function Xu(e,t){var n=0;switch(e.tag){case 31:case 13:var r=e.stateNode,a=e.memoizedState;a!==null&&(n=a.retryLane);break;case 19:r=e.stateNode;break;case 22:r=e.stateNode._retryCache;break;default:throw Error(i(314))}r!==null&&r.delete(t),Ju(e,n)}function Zu(e,t){return pe(e,t)}var Qu=null,$u=null,ed=!1,td=!1,nd=!1,rd=0;function id(e){e!==$u&&e.next===null&&($u===null?Qu=$u=e:$u=$u.next=e),td=!0,ed||(ed=!0,dd())}function ad(e,t){if(!nd&&td){nd=!0;do for(var n=!1,r=Qu;r!==null;){if(!t)if(e!==0){var i=r.pendingLanes;if(i===0)var a=0;else{var o=r.suspendedLanes,s=r.pingedLanes;a=(1<<31-ke(42|e)+1)-1,a&=i&~(o&~s),a=a&201326741?a&201326741|1:a?a|2:0}a!==0&&(n=!0,ud(r,a))}else a=Ll,a=Le(r,r===Fl?a:0,r.cancelPendingCommit!==null||r.timeoutHandle!==-1),!(a&3)||Re(r,a)||(n=!0,ud(r,a));r=r.next}while(n);nd=!1}}function od(){sd()}function sd(){td=ed=!1;var e=0;rd!==0&&Jd()&&(e=rd);for(var t=_e(),n=null,r=Qu;r!==null;){var i=r.next,a=cd(r,t);a===0?(r.next=null,n===null?Qu=i:n.next=i,i===null&&($u=n)):(n=r,(e!==0||a&3)&&(td=!0)),r=i}iu!==0&&iu!==5||ad(e,!1),rd!==0&&(rd=0)}function cd(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,i=e.expirationTimes,a=e.pendingLanes&-62914561;0s)break;var u=c.transferSize,d=c.initiatorType;u&&zd(d)&&(c=c.responseEnd,o+=u*(c`u`?null:document;function wf(e,t,n){var r=Cf;if(r&&typeof t==`string`&&t){var i=kt(t);i=`link[rel="`+e+`"][href="`+i+`"]`,typeof n==`string`&&(i+=`[crossorigin="`+n+`"]`),vf.has(i)||(vf.add(i),e={rel:e,crossOrigin:n,href:t},r.querySelector(i)===null&&(t=r.createElement(`link`),Ld(t,`link`,e),dt(t),r.head.appendChild(t)))}}function Tf(e){bf.D(e),wf(`dns-prefetch`,e,null)}function Ef(e,t){bf.C(e,t),wf(`preconnect`,e,t)}function Df(e,t,n){bf.L(e,t,n);var r=Cf;if(r&&e&&t){var i=`link[rel="preload"][as="`+kt(t)+`"]`;t===`image`&&n&&n.imageSrcSet?(i+=`[imagesrcset="`+kt(n.imageSrcSet)+`"]`,typeof n.imageSizes==`string`&&(i+=`[imagesizes="`+kt(n.imageSizes)+`"]`)):i+=`[href="`+kt(e)+`"]`;var a=i;switch(t){case`style`:a=Nf(e);break;case`script`:a=Lf(e)}_f.has(a)||(e=m({rel:`preload`,href:t===`image`&&n&&n.imageSrcSet?void 0:e,as:t},n),_f.set(a,e),r.querySelector(i)!==null||t===`style`&&r.querySelector(Pf(a))||t===`script`&&r.querySelector(Rf(a))||(t=r.createElement(`link`),Ld(t,`link`,e),dt(t),r.head.appendChild(t)))}}function Of(e,t){bf.m(e,t);var n=Cf;if(n&&e){var r=t&&typeof t.as==`string`?t.as:`script`,i=`link[rel="modulepreload"][as="`+kt(r)+`"][href="`+kt(e)+`"]`,a=i;switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:a=Lf(e)}if(!_f.has(a)&&(e=m({rel:`modulepreload`,href:e},t),_f.set(a,e),n.querySelector(i)===null)){switch(r){case`audioworklet`:case`paintworklet`:case`serviceworker`:case`sharedworker`:case`worker`:case`script`:if(n.querySelector(Rf(a)))return}r=n.createElement(`link`),Ld(r,`link`,e),dt(r),n.head.appendChild(r)}}}function kf(e,t,n){bf.S(e,t,n);var r=Cf;if(r&&e){var i=ut(r).hoistableStyles,a=Nf(e);t||=`default`;var o=i.get(a);if(!o){var s={loading:0,preload:null};if(o=r.querySelector(Pf(a)))s.loading=5;else{e=m({rel:`stylesheet`,href:e,"data-precedence":t},n),(n=_f.get(a))&&Vf(e,n);var c=o=r.createElement(`link`);dt(c),Ld(c,`link`,e),c._p=new Promise(function(e,t){c.onload=e,c.onerror=t}),c.addEventListener(`load`,function(){s.loading|=1}),c.addEventListener(`error`,function(){s.loading|=2}),s.loading|=4,Bf(o,t,r)}o={type:`stylesheet`,instance:o,count:1,state:s},i.set(a,o)}}}function Af(e,t){bf.X(e,t);var n=Cf;if(n&&e){var r=ut(n).hoistableScripts,i=Lf(e),a=r.get(i);a||(a=n.querySelector(Rf(i)),a||(e=m({src:e,async:!0},t),(t=_f.get(i))&&Hf(e,t),a=n.createElement(`script`),dt(a),Ld(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function jf(e,t){bf.M(e,t);var n=Cf;if(n&&e){var r=ut(n).hoistableScripts,i=Lf(e),a=r.get(i);a||(a=n.querySelector(Rf(i)),a||(e=m({src:e,async:!0,type:`module`},t),(t=_f.get(i))&&Hf(e,t),a=n.createElement(`script`),dt(a),Ld(a,`link`,e),n.head.appendChild(a)),a={type:`script`,instance:a,count:1,state:null},r.set(i,a))}}function Mf(e,t,n,r){var a=(a=W.current)?yf(a):null;if(!a)throw Error(i(446));switch(e){case`meta`:case`title`:return null;case`style`:return typeof n.precedence==`string`&&typeof n.href==`string`?(t=Nf(n.href),n=ut(a).hoistableStyles,r=n.get(t),r||(r={type:`style`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};case`link`:if(n.rel===`stylesheet`&&typeof n.href==`string`&&typeof n.precedence==`string`){e=Nf(n.href);var o=ut(a).hoistableStyles,s=o.get(e);if(s||(a=a.ownerDocument||a,s={type:`stylesheet`,instance:null,count:0,state:{loading:0,preload:null}},o.set(e,s),(o=a.querySelector(Pf(e)))&&!o._p&&(s.instance=o,s.state.loading=5),_f.has(e)||(n={rel:`preload`,as:`style`,href:n.href,crossOrigin:n.crossOrigin,integrity:n.integrity,media:n.media,hrefLang:n.hrefLang,referrerPolicy:n.referrerPolicy},_f.set(e,n),o||If(a,e,n,s.state))),t&&r===null)throw Error(i(528,``));return s}if(t&&r!==null)throw Error(i(529,``));return null;case`script`:return t=n.async,n=n.src,typeof n==`string`&&t&&typeof t!=`function`&&typeof t!=`symbol`?(t=Lf(n),n=ut(a).hoistableScripts,r=n.get(t),r||(r={type:`script`,instance:null,count:0,state:null},n.set(t,r)),r):{type:`void`,instance:null,count:0,state:null};default:throw Error(i(444,e))}}function Nf(e){return`href="`+kt(e)+`"`}function Pf(e){return`link[rel="stylesheet"][`+e+`]`}function Ff(e){return m({},e,{"data-precedence":e.precedence,precedence:null})}function If(e,t,n,r){e.querySelector(`link[rel="preload"][as="style"][`+t+`]`)?r.loading=1:(t=e.createElement(`link`),r.preload=t,t.addEventListener(`load`,function(){return r.loading|=1}),t.addEventListener(`error`,function(){return r.loading|=2}),Ld(t,`link`,n),dt(t),e.head.appendChild(t))}function Lf(e){return`[src="`+kt(e)+`"]`}function Rf(e){return`script[async]`+e}function zf(e,t,n){if(t.count++,t.instance===null)switch(t.type){case`style`:var r=e.querySelector(`style[data-href~="`+kt(n.href)+`"]`);if(r)return t.instance=r,dt(r),r;var a=m({},n,{"data-href":n.href,"data-precedence":n.precedence,href:null,precedence:null});return r=(e.ownerDocument||e).createElement(`style`),dt(r),Ld(r,`style`,a),Bf(r,n.precedence,e),t.instance=r;case`stylesheet`:a=Nf(n.href);var o=e.querySelector(Pf(a));if(o)return t.state.loading|=4,t.instance=o,dt(o),o;r=Ff(n),(a=_f.get(a))&&Vf(r,a),o=(e.ownerDocument||e).createElement(`link`),dt(o);var s=o;return s._p=new Promise(function(e,t){s.onload=e,s.onerror=t}),Ld(o,`link`,r),t.state.loading|=4,Bf(o,n.precedence,e),t.instance=o;case`script`:return o=Lf(n.src),(a=e.querySelector(Rf(o)))?(t.instance=a,dt(a),a):(r=n,(a=_f.get(o))&&(r=m({},n),Hf(r,a)),e=e.ownerDocument||e,a=e.createElement(`script`),dt(a),Ld(a,`link`,r),e.head.appendChild(a),t.instance=a);case`void`:return null;default:throw Error(i(443,t.type))}else t.type===`stylesheet`&&!(t.state.loading&4)&&(r=t.instance,t.state.loading|=4,Bf(r,n.precedence,e));return t.instance}function Bf(e,t,n){for(var r=n.querySelectorAll(`link[rel="stylesheet"][data-precedence],style[data-precedence]`),i=r.length?r[r.length-1]:null,a=i,o=0;o title`):null)}function Kf(e,t,n){if(n===1||t.itemProp!=null)return!1;switch(e){case`meta`:case`title`:return!0;case`style`:if(typeof t.precedence!=`string`||typeof t.href!=`string`||t.href===``)break;return!0;case`link`:if(typeof t.rel!=`string`||typeof t.href!=`string`||t.href===``||t.onLoad||t.onError)break;switch(t.rel){case`stylesheet`:return e=t.disabled,typeof t.precedence==`string`&&e==null;default:return!0}case`script`:if(t.async&&typeof t.async!=`function`&&typeof t.async!=`symbol`&&!t.onLoad&&!t.onError&&t.src&&typeof t.src==`string`)return!0}return!1}function qf(e){return!(e.type===`stylesheet`&&!(e.state.loading&3))}function Jf(e,t,n,r){if(n.type===`stylesheet`&&(typeof r.media!=`string`||!1!==matchMedia(r.media).matches)&&!(n.state.loading&4)){if(n.instance===null){var i=Nf(r.href),a=t.querySelector(Pf(i));if(a){t=a._p,typeof t==`object`&&t&&typeof t.then==`function`&&(e.count++,e=Zf.bind(e),t.then(e,e)),n.state.loading|=4,n.instance=a,dt(a);return}a=t.ownerDocument||t,r=Ff(r),(i=_f.get(i))&&Vf(r,i),a=a.createElement(`link`),dt(a);var o=a;o._p=new Promise(function(e,t){o.onload=e,o.onerror=t}),Ld(a,`link`,r),n.instance=a}e.stylesheets===null&&(e.stylesheets=new Map),e.stylesheets.set(n,t),(t=n.state.preload)&&!(n.state.loading&3)&&(e.count++,n=Zf.bind(e),t.addEventListener(`load`,n),t.addEventListener(`error`,n))}}var Yf=0;function Xf(e,t){return e.stylesheets&&e.count===0&&$f(e,e.stylesheets),0Yf?50:800)+t);return e.unsuspend=n,function(){e.unsuspend=null,clearTimeout(r),clearTimeout(i)}}:null}function Zf(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)$f(this,this.stylesheets);else if(this.unsuspend){var e=this.unsuspend;this.unsuspend=null,e()}}}var Qf=null;function $f(e,t){e.stylesheets=null,e.unsuspend!==null&&(e.count++,Qf=new Map,t.forEach(ep,e),Qf=null,Zf.call(e))}function ep(e,t){if(!(t.state.loading&4)){var n=Qf.get(e);if(n)var r=n.get(null);else{n=new Map,Qf.set(e,n);for(var i=e.querySelectorAll(`link[data-precedence],style[data-precedence]`),a=0;a{function n(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>`u`||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!=`function`))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(n)}catch(e){console.error(e)}}n(),t.exports=g()})),v=(...e)=>e.filter((e,t,n)=>!!e&&e.trim()!==``&&n.indexOf(e)===t).join(` `).trim(),y=e=>e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase(),b=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(e,t,n)=>n?n.toUpperCase():t.toLowerCase()),x=e=>{let t=b(e);return t.charAt(0).toUpperCase()+t.slice(1)},S={xmlns:`http://www.w3.org/2000/svg`,width:24,height:24,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:2,strokeLinecap:`round`,strokeLinejoin:`round`},C=e=>{for(let t in e)if(t.startsWith(`aria-`)||t===`role`||t===`title`)return!0;return!1},w=l(d(),1),T=(0,w.createContext)({}),E=()=>(0,w.useContext)(T),D=(0,w.forwardRef)(({color:e,size:t,strokeWidth:n,absoluteStrokeWidth:r,className:i=``,children:a,iconNode:o,...s},c)=>{let{size:l=24,strokeWidth:u=2,absoluteStrokeWidth:d=!1,color:f=`currentColor`,className:p=``}=E()??{},m=r??d?Number(n??u)*24/Number(t??l):n??u;return(0,w.createElement)(`svg`,{ref:c,...S,width:t??l??S.width,height:t??l??S.height,stroke:e??f,strokeWidth:m,className:v(`lucide`,p,i),...!a&&!C(s)&&{"aria-hidden":`true`},...s},[...o.map(([e,t])=>(0,w.createElement)(e,t)),...Array.isArray(a)?a:[a]])}),O=(e,t)=>{let n=(0,w.forwardRef)(({className:n,...r},i)=>(0,w.createElement)(D,{ref:i,iconNode:t,className:v(`lucide-${y(x(e))}`,`lucide-${e}`,n),...r}));return n.displayName=x(e),n},k=O(`archive-restore`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h2`,key:`tvwodi`}],[`path`,{d:`M20 8v11a2 2 0 0 1-2 2h-2`,key:`1gkqxj`}],[`path`,{d:`m9 15 3-3 3 3`,key:`1pd0qc`}],[`path`,{d:`M12 12v9`,key:`192myk`}]]),A=O(`archive`,[[`rect`,{width:`20`,height:`5`,x:`2`,y:`3`,rx:`1`,key:`1wp1u1`}],[`path`,{d:`M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8`,key:`1s80jp`}],[`path`,{d:`M10 12h4`,key:`a56b0p`}]]),j=O(`arrow-down`,[[`path`,{d:`M12 5v14`,key:`s699le`}],[`path`,{d:`m19 12-7 7-7-7`,key:`1idqje`}]]),M=O(`arrow-left`,[[`path`,{d:`m12 19-7-7 7-7`,key:`1l729n`}],[`path`,{d:`M19 12H5`,key:`x3x0zl`}]]),N=O(`arrow-right`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`m12 5 7 7-7 7`,key:`xquz4c`}]]),P=O(`arrow-up-down`,[[`path`,{d:`m21 16-4 4-4-4`,key:`f6ql7i`}],[`path`,{d:`M17 20V4`,key:`1ejh1v`}],[`path`,{d:`m3 8 4-4 4 4`,key:`11wl7u`}],[`path`,{d:`M7 4v16`,key:`1glfcx`}]]),F=O(`arrow-up`,[[`path`,{d:`m5 12 7-7 7 7`,key:`hav0vg`}],[`path`,{d:`M12 19V5`,key:`x0mq9r`}]]),I=O(`bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),L=O(`brain`,[[`path`,{d:`M12 18V5`,key:`adv99a`}],[`path`,{d:`M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4`,key:`1e3is1`}],[`path`,{d:`M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5`,key:`1gqd8o`}],[`path`,{d:`M17.997 5.125a4 4 0 0 1 2.526 5.77`,key:`iwvgf7`}],[`path`,{d:`M18 18a4 4 0 0 0 2-7.464`,key:`efp6ie`}],[`path`,{d:`M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517`,key:`1gq6am`}],[`path`,{d:`M6 18a4 4 0 0 1-2-7.464`,key:`k1g0md`}],[`path`,{d:`M6.003 5.125a4 4 0 0 0-2.526 5.77`,key:`q97ue3`}]]),R=O(`calendar`,[[`path`,{d:`M8 2v3`,key:`1ioesn`}],[`path`,{d:`M16 2v3`,key:`otl347`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}],[`path`,{d:`M3 9h18`,key:`1pudct`}]]),z=O(`check-check`,[[`path`,{d:`M18 6 7 17l-5-5`,key:`116fxf`}],[`path`,{d:`m22 10-7.5 7.5L13 16`,key:`ke71qq`}]]),B=O(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),V=O(`chevron-down`,[[`path`,{d:`m6 9 6 6 6-6`,key:`qrunsl`}]]),H=O(`chevron-left`,[[`path`,{d:`m15 18-6-6 6-6`,key:`1wnfg3`}]]),U=O(`chevron-right`,[[`path`,{d:`m9 18 6-6-6-6`,key:`mthhwq`}]]),ee=O(`chevrons-left`,[[`path`,{d:`m11 17-5-5 5-5`,key:`13zhaf`}],[`path`,{d:`m18 17-5-5 5-5`,key:`h8a8et`}]]),te=O(`chevrons-right`,[[`path`,{d:`m6 17 5-5-5-5`,key:`xnjwq`}],[`path`,{d:`m13 17 5-5-5-5`,key:`17xmmf`}]]),W=O(`circle-check-big`,[[`path`,{d:`M21.801 10A10 10 0 1 1 17 3.335`,key:`yps3ct`}],[`path`,{d:`m9 11 3 3L22 4`,key:`1pflzl`}]]),ne=O(`circle-check`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m16 9-5.5 5.5L8 12`,key:`xofnsj`}]]),G=O(`circle-dashed`,[[`path`,{d:`M10.1 2.182a10 10 0 0 1 3.8 0`,key:`5ilxe3`}],[`path`,{d:`M13.9 21.818a10 10 0 0 1-3.8 0`,key:`11zvb9`}],[`path`,{d:`M17.609 3.721a10 10 0 0 1 2.69 2.7`,key:`1iw5b2`}],[`path`,{d:`M2.182 13.9a10 10 0 0 1 0-3.8`,key:`c0bmvh`}],[`path`,{d:`M20.279 17.609a10 10 0 0 1-2.7 2.69`,key:`1ruxm7`}],[`path`,{d:`M21.818 10.1a10 10 0 0 1 0 3.8`,key:`qkgqxc`}],[`path`,{d:`M3.721 6.391a10 10 0 0 1 2.7-2.69`,key:`1mcia2`}],[`path`,{d:`M6.391 20.279a10 10 0 0 1-2.69-2.7`,key:`1fvljs`}]]),re=O(`circle-stop`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`rect`,{x:`9`,y:`9`,width:`6`,height:`6`,rx:`1`,key:`1ssd4o`}]]),ie=O(`circle-x`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`m15 9-6 6`,key:`1uzhvr`}],[`path`,{d:`m9 9 6 6`,key:`z0biqf`}]]),ae=O(`clipboard`,[[`rect`,{width:`8`,height:`4`,x:`8`,y:`2`,rx:`1`,ry:`1`,key:`tgr4d6`}],[`path`,{d:`M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2`,key:`116196`}]]),K=O(`clock`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 6v6l4 2`,key:`mmk7yg`}]]),oe=O(`cloud-fog`,[[`path`,{d:`M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242`,key:`1pljnt`}],[`path`,{d:`M16 17H7`,key:`pygtm1`}],[`path`,{d:`M17 21H9`,key:`1u2q02`}]]),se=O(`code-xml`,[[`path`,{d:`m18 16 4-4-4-4`,key:`1inbqp`}],[`path`,{d:`m6 8-4 4 4 4`,key:`15zrgr`}],[`path`,{d:`m14.5 4-5 16`,key:`e7oirm`}]]),ce=O(`columns-2`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M12 3v18`,key:`108xh3`}]]),le=O(`command`,[[`path`,{d:`M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3`,key:`11bfej`}]]),ue=O(`copy`,[[`rect`,{width:`14`,height:`14`,x:`8`,y:`8`,rx:`2`,ry:`2`,key:`17jyea`}],[`path`,{d:`M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2`,key:`zix9uf`}]]),de=O(`cpu`,[[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M17 20v2`,key:`1rnc9c`}],[`path`,{d:`M17 2v2`,key:`11trls`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M2 17h2`,key:`7oei6x`}],[`path`,{d:`M2 7h2`,key:`asdhe0`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`M20 17h2`,key:`1fpfkl`}],[`path`,{d:`M20 7h2`,key:`1o8tra`}],[`path`,{d:`M7 20v2`,key:`4gnj0m`}],[`path`,{d:`M7 2v2`,key:`1i4yhu`}],[`rect`,{x:`4`,y:`4`,width:`16`,height:`16`,rx:`2`,key:`1vbyd7`}],[`rect`,{x:`8`,y:`8`,width:`8`,height:`8`,rx:`1`,key:`z9xiuo`}]]),fe=O(`database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),pe=O(`download`,[[`path`,{d:`M12 15V3`,key:`m9g1x1`}],[`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`,key:`ih7n3h`}],[`path`,{d:`m7 10 5 5 5-5`,key:`brsn70`}]]),me=O(`earth`,[[`path`,{d:`M21.54 15H17a2 2 0 0 0-2 2v4.54`,key:`1djwo0`}],[`path`,{d:`M7 3.34V5a3 3 0 0 0 3 3a2 2 0 0 1 2 2c0 1.1.9 2 2 2a2 2 0 0 0 2-2c0-1.1.9-2 2-2h3.17`,key:`1tzkfa`}],[`path`,{d:`M11 21.95V18a2 2 0 0 0-2-2a2 2 0 0 1-2-2v-1a2 2 0 0 0-2-2H2.05`,key:`14pb5j`}],[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}]]),he=O(`ellipsis`,[[`circle`,{cx:`12`,cy:`12`,r:`1`,key:`41hilf`}],[`circle`,{cx:`19`,cy:`12`,r:`1`,key:`1wjl8i`}],[`circle`,{cx:`5`,cy:`12`,r:`1`,key:`1pcz8c`}]]),ge=O(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),_e=O(`eye-off`,[[`path`,{d:`M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49`,key:`ct8e1f`}],[`path`,{d:`M14.084 14.158a3 3 0 0 1-4.242-4.242`,key:`151rxh`}],[`path`,{d:`M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143`,key:`13bj9a`}],[`path`,{d:`m2 2 20 20`,key:`1ooewy`}]]),ve=O(`file-code-corner`,[[`path`,{d:`M4 12.15V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.706.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2h-3.35`,key:`1wthlu`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`m5 16-3 3 3 3`,key:`331omg`}],[`path`,{d:`m9 22 3-3-3-3`,key:`lsp7cz`}]]),ye=O(`file-diff`,[[`path`,{d:`M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z`,key:`1oefj6`}],[`path`,{d:`M9 10h6`,key:`9gxzsh`}],[`path`,{d:`M12 13V7`,key:`h0r20n`}],[`path`,{d:`M9 17h6`,key:`r8uit2`}]]),be=O(`file-pen-line`,[[`path`,{d:`M14.364 13.634a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506l4.013-4.009a1 1 0 0 0-3.004-3.004z`,key:`ukzhwg`}],[`path`,{d:`M14.487 7.858A1 1 0 0 1 14 7V2`,key:`1klhew`}],[`path`,{d:`M20 19.645V20a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l2.516 2.516`,key:`rxaxab`}],[`path`,{d:`M8 18h1`,key:`13wk12`}]]),xe=O(`file-text`,[[`path`,{d:`M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z`,key:`1oefj6`}],[`path`,{d:`M14 2v5a1 1 0 0 0 1 1h5`,key:`wfsgrz`}],[`path`,{d:`M10 9H8`,key:`b1mrlr`}],[`path`,{d:`M16 13H8`,key:`t4e002`}],[`path`,{d:`M16 17H8`,key:`z1uh3a`}]]),Se=O(`flower-2`,[[`path`,{d:`M12 5a3 3 0 1 1 3 3m-3-3a3 3 0 1 0-3 3m3-3v1M9 8a3 3 0 1 0 3 3M9 8h1m5 0a3 3 0 1 1-3 3m3-3h-1m-2 3v-1`,key:`3pnvol`}],[`circle`,{cx:`12`,cy:`8`,r:`2`,key:`1822b1`}],[`path`,{d:`M12 10v12`,key:`6ubwww`}],[`path`,{d:`M12 22c4.2 0 7-1.667 7-5-4.2 0-7 1.667-7 5Z`,key:`9hd38g`}],[`path`,{d:`M12 22c-4.2 0-7-1.667-7-5 4.2 0 7 1.667 7 5Z`,key:`ufn41s`}]]),Ce=O(`folder-cog`,[[`path`,{d:`M10.3 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.98a2 2 0 0 1 1.69.9l.66 1.2A2 2 0 0 0 12 6h8a2 2 0 0 1 2 2v3.3`,key:`128dxu`}],[`path`,{d:`m14.305 19.53.923-.382`,key:`3m78fa`}],[`path`,{d:`m15.228 16.852-.923-.383`,key:`npixar`}],[`path`,{d:`m16.852 15.228-.383-.923`,key:`5xggr7`}],[`path`,{d:`m16.852 20.772-.383.924`,key:`dpfhf9`}],[`path`,{d:`m19.148 15.228.383-.923`,key:`1reyyz`}],[`path`,{d:`m19.53 21.696-.382-.924`,key:`1goivc`}],[`path`,{d:`m20.772 16.852.924-.383`,key:`htqkph`}],[`path`,{d:`m20.772 19.148.924.383`,key:`9w9pjp`}],[`circle`,{cx:`18`,cy:`18`,r:`3`,key:`1xkwt0`}]]),we=O(`folder-output`,[[`path`,{d:`M2 7.5V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H4a2 2 0 0 1-2-1.5`,key:`1yk7aj`}],[`path`,{d:`M2 13h10`,key:`pgb2dq`}],[`path`,{d:`m5 10-3 3 3 3`,key:`1r8ie0`}]]),Te=O(`folder`,[[`path`,{d:`M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z`,key:`1kt360`}]]),Ee=O(`funnel`,[[`path`,{d:`M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z`,key:`sc7q7i`}]]),De=O(`gauge`,[[`path`,{d:`m12 14 4-4`,key:`9kzdfg`}],[`path`,{d:`M3.34 19a10 10 0 1 1 17.32 0`,key:`19p75a`}]]),Oe=O(`git-branch`,[[`path`,{d:`M15 6a9 9 0 0 0-9 9V3`,key:`1cii5b`}],[`circle`,{cx:`18`,cy:`6`,r:`3`,key:`1h7g24`}],[`circle`,{cx:`6`,cy:`18`,r:`3`,key:`fqmcym`}]]),ke=O(`globe`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20`,key:`13o1zl`}],[`path`,{d:`M2 12h20`,key:`9i4pu4`}]]),Ae=O(`image-plus`,[[`path`,{d:`M16 5h6`,key:`1vod17`}],[`path`,{d:`M19 2v6`,key:`4bpg5p`}],[`path`,{d:`M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5`,key:`1ue2ih`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}]]),je=O(`image`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,ry:`2`,key:`1m3agn`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}]]),Me=O(`info`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 16v-4`,key:`1dtifu`}],[`path`,{d:`M12 8h.01`,key:`e9boi3`}]]),Ne=O(`key-round`,[[`path`,{d:`M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z`,key:`1s6t7t`}],[`circle`,{cx:`16.5`,cy:`7.5`,r:`.5`,fill:`currentColor`,key:`w0ekpg`}]]),Pe=O(`layers`,[[`path`,{d:`M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z`,key:`zw3jo`}],[`path`,{d:`M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12`,key:`1wduqc`}],[`path`,{d:`M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17`,key:`kqbvx6`}]]),Fe=O(`lightbulb`,[[`path`,{d:`M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5`,key:`1gvzjb`}],[`path`,{d:`M9 18h6`,key:`x1upvd`}],[`path`,{d:`M10 22h4`,key:`ceow96`}]]),Ie=O(`menu`,[[`path`,{d:`M4 5h16`,key:`1tepv9`}],[`path`,{d:`M4 12h16`,key:`1lakjw`}],[`path`,{d:`M4 19h16`,key:`1djgab`}]]),Le=O(`message-circle-question-mark`,[[`path`,{d:`M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719`,key:`1sd12s`}],[`path`,{d:`M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3`,key:`1u773s`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),Re=O(`messages-square`,[[`path`,{d:`M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z`,key:`1n2ejm`}],[`path`,{d:`M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1`,key:`1qfcsi`}]]),ze=O(`mic`,[[`path`,{d:`M12 19v3`,key:`npa21l`}],[`path`,{d:`M19 10v2a7 7 0 0 1-14 0v-2`,key:`1vc78b`}],[`rect`,{x:`9`,y:`2`,width:`6`,height:`13`,rx:`3`,key:`s6n7sd`}]]),Be=O(`monitor`,[[`rect`,{width:`20`,height:`14`,x:`2`,y:`3`,rx:`2`,key:`48i651`}],[`line`,{x1:`8`,x2:`16`,y1:`21`,y2:`21`,key:`1svkeh`}],[`line`,{x1:`12`,x2:`12`,y1:`17`,y2:`21`,key:`vw1qmm`}]]),Ve=O(`moon`,[[`path`,{d:`M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401`,key:`kfwtm`}]]),He=O(`panel-left-close`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M9 3v18`,key:`fh3hqa`}],[`path`,{d:`m16 15-3-3 3-3`,key:`14y99z`}]]),Ue=O(`panel-left-open`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M9 3v18`,key:`fh3hqa`}],[`path`,{d:`m14 9 3 3-3 3`,key:`8010ee`}]]),We=O(`panel-right`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M15 3v18`,key:`14nvp0`}]]),Ge=O(`pencil`,[[`path`,{d:`M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z`,key:`1a8usu`}],[`path`,{d:`m15 5 4 4`,key:`1mk7zo`}]]),Ke=O(`plug`,[[`path`,{d:`M12 22v-5`,key:`1ega77`}],[`path`,{d:`M15 8V2`,key:`18g5xt`}],[`path`,{d:`M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z`,key:`1xoxul`}],[`path`,{d:`M9 8V2`,key:`14iosj`}]]),qe=O(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),Je=O(`puzzle`,[[`path`,{d:`M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z`,key:`w46dr5`}]]),Ye=O(`refresh-cw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]),Xe=O(`rotate-ccw`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}]]),Ze=O(`search`,[[`path`,{d:`m21 21-4.34-4.34`,key:`14j7rj`}],[`circle`,{cx:`11`,cy:`11`,r:`8`,key:`4ej97u`}]]),Qe=O(`send`,[[`path`,{d:`M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z`,key:`1ffxy3`}],[`path`,{d:`m21.854 2.147-10.94 10.939`,key:`12cjpa`}]]),$e=O(`settings`,[[`path`,{d:`M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915`,key:`1i5ecw`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]),et=O(`sliders-horizontal`,[[`path`,{d:`M10 5H3`,key:`1qgfaw`}],[`path`,{d:`M12 19H3`,key:`yhmn1j`}],[`path`,{d:`M14 3v4`,key:`1sua03`}],[`path`,{d:`M16 17v4`,key:`1q0r14`}],[`path`,{d:`M21 12h-9`,key:`1o4lsq`}],[`path`,{d:`M21 19h-5`,key:`1rlt1p`}],[`path`,{d:`M21 5h-7`,key:`1oszz2`}],[`path`,{d:`M8 10v4`,key:`tgpxqk`}],[`path`,{d:`M8 12H3`,key:`a7s4jb`}]]),tt=O(`sparkles`,[[`path`,{d:`M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z`,key:`1s2grr`}],[`path`,{d:`M20 2v4`,key:`1rf3ol`}],[`path`,{d:`M22 4h-4`,key:`gwowj6`}],[`circle`,{cx:`4`,cy:`20`,r:`2`,key:`6kqj1y`}]]),nt=O(`square-pen`,[[`path`,{d:`M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7`,key:`1m0v6g`}],[`path`,{d:`M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z`,key:`ohrbg2`}]]),rt=O(`square-terminal`,[[`path`,{d:`m7 11 2-2-2-2`,key:`1lz0vl`}],[`path`,{d:`M11 13h4`,key:`1p7l4v`}],[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,ry:`2`,key:`1m3agn`}]]),it=O(`square`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}]]),at=O(`sun`,[[`circle`,{cx:`12`,cy:`12`,r:`4`,key:`4exip2`}],[`path`,{d:`M12 2v2`,key:`tus03m`}],[`path`,{d:`M12 20v2`,key:`1lh1kg`}],[`path`,{d:`m4.93 4.93 1.41 1.41`,key:`149t6j`}],[`path`,{d:`m17.66 17.66 1.41 1.41`,key:`ptbguv`}],[`path`,{d:`M2 12h2`,key:`1t8f8n`}],[`path`,{d:`M20 12h2`,key:`1q8mjw`}],[`path`,{d:`m6.34 17.66-1.41 1.41`,key:`1m8zz5`}],[`path`,{d:`m19.07 4.93-1.41 1.41`,key:`1shlcs`}]]),ot=O(`terminal`,[[`path`,{d:`M12 19h8`,key:`baeox8`}],[`path`,{d:`m4 17 6-6-6-6`,key:`1yngyt`}]]),st=O(`trash-2`,[[`path`,{d:`M10 11v6`,key:`nco0om`}],[`path`,{d:`M14 11v6`,key:`outv1u`}],[`path`,{d:`M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6`,key:`miytrc`}],[`path`,{d:`M3 6h18`,key:`d0wm0j`}],[`path`,{d:`M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2`,key:`e791ji`}]]),ct=O(`tree-pine`,[[`path`,{d:`m17 14 3 3.3a1 1 0 0 1-.7 1.7H4.7a1 1 0 0 1-.7-1.7L7 14h-.3a1 1 0 0 1-.7-1.7L9 9h-.2A1 1 0 0 1 8 7.3L12 3l4 4.3a1 1 0 0 1-.8 1.7H15l3 3.3a1 1 0 0 1-.7 1.7H17Z`,key:`cpyugq`}],[`path`,{d:`M12 22v-3`,key:`kmzjlo`}]]),lt=O(`triangle-alert`,[[`path`,{d:`m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3`,key:`wmoenq`}],[`path`,{d:`M12 9v4`,key:`juzpu7`}],[`path`,{d:`M12 17h.01`,key:`p32p05`}]]),ut=O(`workflow`,[[`rect`,{width:`8`,height:`8`,x:`3`,y:`3`,rx:`2`,key:`by2w9f`}],[`path`,{d:`M7 11v4a2 2 0 0 0 2 2h4`,key:`xkn7yn`}],[`rect`,{width:`8`,height:`8`,x:`13`,y:`13`,rx:`2`,key:`1cgmvn`}]]),dt=O(`wrench`,[[`path`,{d:`M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z`,key:`1ngwbx`}]]),ft=O(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),pt=_(),q=e=>typeof e==`string`,mt=()=>{let e,t,n=new Promise((n,r)=>{e=n,t=r});return n.resolve=e,n.reject=t,n},ht=e=>e==null?``:String(e),gt=(e,t,n)=>{e.forEach(e=>{t[e]&&(n[e]=t[e])})},_t=/###/g,vt=e=>e&&e.includes(`###`)?e.replace(_t,`.`):e,yt=e=>!e||q(e),bt=(e,t,n)=>{let r=q(t)?t.split(`.`):t,i=0;for(;i{let{obj:r,k:i}=bt(e,t,Object);if(r!==void 0||t.length===1){r[i]=n;return}let a=t[t.length-1],o=t.slice(0,t.length-1),s=bt(e,o,Object);for(;s.obj===void 0&&o.length;)a=`${o[o.length-1]}.${a}`,o=o.slice(0,o.length-1),s=bt(e,o,Object),s?.obj&&s.obj[`${s.k}.${a}`]!==void 0&&(s.obj=void 0);s.obj[`${s.k}.${a}`]=n},St=(e,t,n,r)=>{let{obj:i,k:a}=bt(e,t,Object);i[a]=i[a]||[],i[a].push(n)},Ct=(e,t)=>{let{obj:n,k:r}=bt(e,t);if(n&&Object.prototype.hasOwnProperty.call(n,r))return n[r]},wt=(e,t,n)=>{let r=Ct(e,n);return r===void 0?Ct(t,n):r},Tt=(e,t,n)=>{for(let r in t)r!==`__proto__`&&r!==`constructor`&&(Object.prototype.hasOwnProperty.call(e,r)?q(e[r])||e[r]instanceof String||q(t[r])||t[r]instanceof String?n&&(e[r]=t[r]):Tt(e[r],t[r],n):e[r]=t[r]);return e},Et=e=>e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,`\\$&`),Dt={"&":`&`,"<":`<`,">":`>`,'"':`"`,"'":`'`,"/":`/`},Ot=e=>q(e)?e.replace(/[&<>"'\/]/g,e=>Dt[e]):e,kt=class{constructor(e){this.capacity=e,this.regExpMap=new Map,this.regExpQueue=[]}getRegExp(e){let t=this.regExpMap.get(e);if(t!==void 0)return t;let n=new RegExp(e);return this.regExpQueue.length===this.capacity&&this.regExpMap.delete(this.regExpQueue.shift()),this.regExpMap.set(e,n),this.regExpQueue.push(e),n}},At=[` `,`,`,`?`,`!`,`;`],jt=new kt(20),Mt=(e,t,n)=>{t||=``,n||=``;let r=At.filter(e=>!t.includes(e)&&!n.includes(e));if(r.length===0)return!0;let i=jt.getRegExp(`(${r.map(e=>e===`?`?`\\?`:e).join(`|`)})`),a=!i.test(e);if(!a){let t=e.indexOf(n);t>0&&!i.test(e.substring(0,t))&&(a=!0)}return a},Nt=(e,t,n=`.`)=>{if(!e)return;if(e[t])return Object.prototype.hasOwnProperty.call(e,t)?e[t]:void 0;let r=t.split(n),i=e;for(let e=0;ee?.replace(/_/g,`-`),Ft={type:`logger`,log(e){this.output(`log`,e)},warn(e){this.output(`warn`,e)},error(e){this.output(`error`,e)},output(e,t){console?.[e]?.apply?.(console,t)}},It=new class e{constructor(e,t={}){this.init(e,t)}init(e,t={}){this.prefix=t.prefix||`i18next:`,this.logger=e||Ft,this.options=t,this.debug=t.debug}log(...e){return this.forward(e,`log`,``,!0)}warn(...e){return this.forward(e,`warn`,``,!0)}error(...e){return this.forward(e,`error`,``)}deprecate(...e){return this.forward(e,`warn`,`WARNING DEPRECATED: `,!0)}forward(e,t,n,r){return r&&!this.debug?null:(e=e.map(e=>q(e)?e.replace(/[\r\n\x00-\x1F\x7F]/g,` `):e),q(e[0])&&(e[0]=`${n}${this.prefix} ${e[0]}`),this.logger[t](e))}create(t){return new e(this.logger,{prefix:`${this.prefix}:${t}:`,...this.options})}clone(t){return t||=this.options,t.prefix=t.prefix||this.prefix,new e(this.logger,t)}},Lt=class{constructor(){this.observers={}}on(e,t){return e.split(` `).forEach(e=>{this.observers[e]||(this.observers[e]=new Map);let n=this.observers[e].get(t)||0;this.observers[e].set(t,n+1)}),this}off(e,t){if(this.observers[e]){if(!t){delete this.observers[e];return}this.observers[e].delete(t)}}once(e,t){let n=(...r)=>{t(...r),this.off(e,n)};return this.on(e,n),this}emit(e,...t){this.observers[e]&&Array.from(this.observers[e].entries()).forEach(([e,n])=>{for(let r=0;r{for(let i=0;i-1&&this.options.ns.splice(t,1)}getResource(e,t,n,r={}){let i=r.keySeparator===void 0?this.options.keySeparator:r.keySeparator,a=r.ignoreJSONStructure===void 0?this.options.ignoreJSONStructure:r.ignoreJSONStructure,o;e.includes(`.`)?o=e.split(`.`):(o=[e,t],n&&(Array.isArray(n)?o.push(...n):q(n)&&i?o.push(...n.split(i)):o.push(n)));let s=Ct(this.data,o);return!s&&!t&&!n&&e.includes(`.`)&&(e=o[0],t=o[1],n=o.slice(2).join(`.`)),s||!a||!q(n)?s:Nt(this.data?.[e]?.[t],n,i)}addResource(e,t,n,r,i={silent:!1}){let a=i.keySeparator===void 0?this.options.keySeparator:i.keySeparator,o=[e,t];n&&(o=o.concat(a?n.split(a):n)),e.includes(`.`)&&(o=e.split(`.`),r=t,t=o[1]),this.addNamespaces(t),xt(this.data,o,r),i.silent||this.emit(`added`,e,t,n,r)}addResources(e,t,n,r={silent:!1}){for(let r in n)(q(n[r])||Array.isArray(n[r]))&&this.addResource(e,t,r,n[r],{silent:!0});r.silent||this.emit(`added`,e,t,n)}addResourceBundle(e,t,n,r,i,a={silent:!1,skipCopy:!1}){let o=[e,t];e.includes(`.`)&&(o=e.split(`.`),r=n,n=t,t=o[1]),this.addNamespaces(t);let s=Ct(this.data,o)||{};a.skipCopy||(n=JSON.parse(JSON.stringify(n))),r?Tt(s,n,i):s={...s,...n},xt(this.data,o,s),a.silent||this.emit(`added`,e,t,n)}removeResourceBundle(e,t){this.hasResourceBundle(e,t)&&delete this.data[e][t],this.removeNamespaces(t),this.emit(`removed`,e,t)}hasResourceBundle(e,t){return this.getResource(e,t)!==void 0}getResourceBundle(e,t){return t||=this.options.defaultNS,this.getResource(e,t)}getDataByLanguage(e){return this.data[e]}hasLanguageSomeTranslations(e){let t=this.getDataByLanguage(e);return!!(t&&Object.keys(t)||[]).find(e=>t[e]&&Object.keys(t[e]).length>0)}toJSON(){return this.data}},zt={processors:{},addPostProcessor(e){this.processors[e.name]=e},handle(e,t,n,r,i){return e.forEach(e=>{t=this.processors[e]?.process(t,n,r,i)??t}),t}},Bt=Symbol(`i18next/PATH_KEY`);function Vt(){let e=[],t=Object.create(null),n;return t.get=(r,i)=>(n?.revoke?.(),i===Bt?e:(e.push(i),n=Proxy.revocable(r,t),n.proxy)),Proxy.revocable(Object.create(null),t).proxy}function Ht(e,t){let{[Bt]:n}=e(Vt()),r=t?.keySeparator??`.`,i=t?.nsSeparator??`:`,a=t?.enableSelector===`strict`;if(n.length>1&&i){let e=t?.ns,o=a?Array.isArray(e)?e:e?[e]:null:Array.isArray(e)?e:null;if(o&&(a?o:o.length>1?o.slice(1):[]).includes(n[0]))return`${n[0]}${i}${n.slice(1).join(r)}`}return n.join(r)}var Ut=e=>!q(e)&&typeof e!=`boolean`&&typeof e!=`number`,Wt=class e extends Lt{constructor(e,t={}){super(),gt([`resourceStore`,`languageUtils`,`pluralResolver`,`interpolator`,`backendConnector`,`i18nFormat`,`utils`],e,this),this.options=t,this.options.keySeparator===void 0&&(this.options.keySeparator=`.`),this.logger=It.create(`translator`),this.checkedLoadedFor={}}changeLanguage(e){e&&(this.language=e)}exists(e,t={interpolation:{}}){let n={...t};if(e==null)return!1;let r=this.resolve(e,n);if(r?.res===void 0)return!1;let i=Ut(r.res);return!(n.returnObjects===!1&&i)}extractFromKey(e,t){let n=t.nsSeparator===void 0?this.options.nsSeparator:t.nsSeparator;n===void 0&&(n=`:`);let r=t.keySeparator===void 0?this.options.keySeparator:t.keySeparator,i=t.ns||this.options.defaultNS||[],a=n&&e.includes(n),o=!this.options.userDefinedKeySeparator&&!t.keySeparator&&!this.options.userDefinedNsSeparator&&!t.nsSeparator&&!Mt(e,n,r);if(a&&!o){let t=e.match(this.interpolator.nestingRegexp);if(t&&t.length>0)return{key:e,namespaces:q(i)?[i]:i};let a=e.split(n);(n!==r||n===r&&this.options.ns.includes(a[0]))&&(i=a.shift()),e=a.join(r)}return{key:e,namespaces:q(i)?[i]:i}}translate(t,n,r){let i=typeof n==`object`?{...n}:n;if(typeof i!=`object`&&this.options.overloadTranslationOptionHandler&&(i=this.options.overloadTranslationOptionHandler(arguments)),typeof i==`object`&&(i={...i}),i||={},t==null)return``;typeof t==`function`&&(t=Ht(t,{...this.options,...i})),Array.isArray(t)||(t=[String(t)]),t=t.map(e=>typeof e==`function`?Ht(e,{...this.options,...i}):String(e));let a=i.returnDetails===void 0?this.options.returnDetails:i.returnDetails,o=i.keySeparator===void 0?this.options.keySeparator:i.keySeparator,{key:s,namespaces:c}=this.extractFromKey(t[t.length-1],i),l=c[c.length-1],u=i.nsSeparator===void 0?this.options.nsSeparator:i.nsSeparator;u===void 0&&(u=`:`);let d=i.lng||this.language,f=i.appendNamespaceToCIMode||this.options.appendNamespaceToCIMode;if(d?.toLowerCase()===`cimode`)return f?a?{res:`${l}${u}${s}`,usedKey:s,exactUsedKey:s,usedLng:d,usedNS:l,usedParams:this.getUsedParamsDetails(i)}:`${l}${u}${s}`:a?{res:s,usedKey:s,exactUsedKey:s,usedLng:d,usedNS:l,usedParams:this.getUsedParamsDetails(i)}:s;let p=this.resolve(t,i),m=p?.res,h=p?.usedKey||s,g=p?.exactUsedKey||s,_=[`[object Number]`,`[object Function]`,`[object RegExp]`],v=i.joinArrays===void 0?this.options.joinArrays:i.joinArrays,y=!this.i18nFormat||this.i18nFormat.handleAsObject,b=i.count!==void 0&&!q(i.count),x=e.hasDefaultValue(i),S=b?this.pluralResolver.getSuffix(d,i.count,i):``,C=i.ordinal&&b?this.pluralResolver.getSuffix(d,i.count,{ordinal:!1}):``,w=b&&!i.ordinal&&i.count===0,T=w&&i[`defaultValue${this.options.pluralSeparator}zero`]||i[`defaultValue${S}`]||i[`defaultValue${C}`]||i.defaultValue,E=m;y&&!m&&x&&(E=T);let D=Ut(E),O=Object.prototype.toString.apply(E);if(y&&E&&D&&!_.includes(O)&&!(q(v)&&Array.isArray(E))){if(!i.returnObjects&&!this.options.returnObjects){this.options.returnedObjectHandler||this.logger.warn(`accessing an object - but returnObjects options is not enabled!`);let e=this.options.returnedObjectHandler?this.options.returnedObjectHandler(h,E,{...i,ns:c}):`key '${s} (${this.language})' returned an object instead of string.`;return a?(p.res=e,p.usedParams=this.getUsedParamsDetails(i),p):e}if(o){let e=Array.isArray(E),t=e?[]:{},n=e?g:h;for(let e in E)if(Object.prototype.hasOwnProperty.call(E,e)){let r=`${n}${o}${e}`;t[e]=x&&!m?this.translate(r,{...i,defaultValue:Ut(T)?T[e]:void 0,joinArrays:!1,ns:c}):this.translate(r,{...i,joinArrays:!1,ns:c}),t[e]===r&&(t[e]=E[e])}m=t}}else if(y&&q(v)&&Array.isArray(m))m=m.join(v),m&&=this.extendTranslation(m,t,i,r);else{let e=!1,n=!1;!this.isValidLookup(m)&&x&&(e=!0,m=T),this.isValidLookup(m)||(n=!0,m=s);let a=(i.missingKeyNoValueFallbackToKey||this.options.missingKeyNoValueFallbackToKey)&&n?void 0:m,c=x&&T!==m&&this.options.updateMissing;if(n||e||c){if(this.logger.log(c?`updateKey`:`missingKey`,d,l,b&&!c?`${s}${this.pluralResolver.getSuffix(d,i.count,i)}`:s,c?T:m),o){let e=this.resolve(s,{...i,keySeparator:!1});e&&e.res&&this.logger.warn(`Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.`)}let e=[],t=this.languageUtils.getFallbackCodes(this.options.fallbackLng,i.lng||this.language);if(this.options.saveMissingTo===`fallback`&&t&&t[0])for(let n=0;n{let r=x&&n!==m?n:a;this.options.missingKeyHandler?this.options.missingKeyHandler(e,l,t,r,c,i):this.backendConnector?.saveMissing&&this.backendConnector.saveMissing(e,l,t,r,c,i),this.emit(`missingKey`,e,l,t,m)};this.options.saveMissing&&(this.options.saveMissingPlurals&&b?e.forEach(e=>{let t=this.pluralResolver.getSuffixes(e,i);w&&i[`defaultValue${this.options.pluralSeparator}zero`]&&!t.includes(`${this.options.pluralSeparator}zero`)&&t.push(`${this.options.pluralSeparator}zero`),t.forEach(t=>{n([e],s+t,i[`defaultValue${t}`]||T)})}):n(e,s,T))}m=this.extendTranslation(m,t,i,p,r),n&&m===s&&this.options.appendNamespaceToMissingKey&&(m=`${l}${u}${s}`),(n||e)&&this.options.parseMissingKeyHandler&&(m=this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey?`${l}${u}${s}`:s,e?m:void 0,i))}return a?(p.res=m,p.usedParams=this.getUsedParamsDetails(i),p):m}extendTranslation(e,t,n,r,i){if(this.i18nFormat?.parse)e=this.i18nFormat.parse(e,{...this.options.interpolation.defaultVariables,...n},n.lng||this.language||r.usedLng,r.usedNS,r.usedKey,{resolved:r});else if(!n.skipInterpolation){n.interpolation&&this.interpolator.init({...n,interpolation:{...this.options.interpolation,...n.interpolation}});let a=q(e)&&(n?.interpolation?.skipOnVariables===void 0?this.options.interpolation.skipOnVariables:n.interpolation.skipOnVariables),o;if(a){let t=e.match(this.interpolator.nestingRegexp);o=t&&t.length}let s=n.replace&&!q(n.replace)?n.replace:n;if(this.options.interpolation.defaultVariables&&(s={...this.options.interpolation.defaultVariables,...s}),e=this.interpolator.interpolate(e,s,n.lng||this.language||r.usedLng,n),a){let t=e.match(this.interpolator.nestingRegexp),r=t&&t.length;oi?.[0]===e[0]&&!n.context?(this.logger.warn(`It seems you are nesting recursively key: ${e[0]} in key: ${t[0]}`),null):this.translate(...e,t),n)),n.interpolation&&this.interpolator.reset()}let a=n.postProcess||this.options.postProcess,o=q(a)?[a]:a;return e!=null&&o?.length&&n.applyPostProcessor!==!1&&(e=zt.handle(o,e,t,this.options&&this.options.postProcessPassResolved?{i18nResolved:{...r,usedParams:this.getUsedParamsDetails(n)},...n}:n,this)),e}resolve(e,t={}){let n,r,i,a,o;return q(e)&&(e=[e]),Array.isArray(e)&&(e=e.map(e=>typeof e==`function`?Ht(e,{...this.options,...t}):e)),e.forEach(e=>{if(this.isValidLookup(n))return;let s=this.extractFromKey(e,t),c=s.key;r=c;let l=s.namespaces;this.options.fallbackNS&&(l=l.concat(this.options.fallbackNS));let u=t.count!==void 0&&!q(t.count),d=u&&!t.ordinal&&t.count===0,f=t.context!==void 0&&(q(t.context)||typeof t.context==`number`)&&t.context!==``,p=t.lngs?t.lngs:this.languageUtils.toResolveHierarchy(t.lng||this.language,t.fallbackLng);l.forEach(e=>{this.isValidLookup(n)||(o=e,!this.checkedLoadedFor[`${p[0]}-${e}`]&&this.utils?.hasLoadedNamespace&&!this.utils?.hasLoadedNamespace(o)&&(this.checkedLoadedFor[`${p[0]}-${e}`]=!0,this.logger.warn(`key "${r}" for languages "${p.join(`, `)}" won't get resolved as namespace "${o}" was not yet loaded`,`This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!`)),p.forEach(r=>{if(this.isValidLookup(n))return;a=r;let o=[c];if(this.i18nFormat?.addLookupKeys)this.i18nFormat.addLookupKeys(o,c,r,e,t);else{let e;u&&(e=this.pluralResolver.getSuffix(r,t.count,t));let n=`${this.options.pluralSeparator}zero`,i=`${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;if(u&&(t.ordinal&&e.startsWith(i)&&o.push(c+e.replace(i,this.options.pluralSeparator)),o.push(c+e),d&&o.push(c+n)),f){let r=`${c}${this.options.contextSeparator||`_`}${t.context}`;o.push(r),u&&(t.ordinal&&e.startsWith(i)&&o.push(r+e.replace(i,this.options.pluralSeparator)),o.push(r+e),d&&o.push(r+n))}}let s;for(;s=o.pop();)this.isValidLookup(n)||(i=s,n=this.getResource(r,e,s,t))}))})}),{res:n,usedKey:r,exactUsedKey:i,usedLng:a,usedNS:o}}isValidLookup(e){return e!==void 0&&!(!this.options.returnNull&&e===null)&&!(!this.options.returnEmptyString&&e===``)}getResource(e,t,n,r={}){return this.i18nFormat?.getResource?this.i18nFormat.getResource(e,t,n,r):this.resourceStore.getResource(e,t,n,r)}getUsedParamsDetails(e={}){let t=[`defaultValue`,`ordinal`,`context`,`replace`,`lng`,`lngs`,`fallbackLng`,`ns`,`keySeparator`,`nsSeparator`,`returnObjects`,`returnDetails`,`joinArrays`,`postProcess`,`interpolation`],n=e.replace&&!q(e.replace),r=n?e.replace:e;if(n&&e.count!==void 0&&(r={...r,count:e.count}),this.options.interpolation.defaultVariables&&(r={...this.options.interpolation.defaultVariables,...r}),!n){r={...r};for(let e of t)delete r[e]}return r}static hasDefaultValue(e){for(let t in e)if(Object.prototype.hasOwnProperty.call(e,t)&&t.startsWith(`defaultValue`)&&e[t]!==void 0)return!0;return!1}},Gt=class{constructor(e){this.options=e,this.supportedLngs=this.options.supportedLngs||!1,this.logger=It.create(`languageUtils`),this.resolveHierarchyCache={}}clearCache(){this.resolveHierarchyCache={}}getScriptPartFromCode(e){if(e=Pt(e),!e||!e.includes(`-`))return null;let t=e.split(`-`);return t.length===2||(t.pop(),t[t.length-1].toLowerCase()===`x`)?null:this.formatLanguageCode(t.join(`-`))}getLanguagePartFromCode(e){if(e=Pt(e),!e||!e.includes(`-`))return e;let t=e.split(`-`);return this.formatLanguageCode(t[0])}formatLanguageCode(e){if(q(e)&&e.includes(`-`)){let t;try{t=Intl.getCanonicalLocales(e)[0]}catch{}return t&&this.options.lowerCaseLng&&(t=t.toLowerCase()),t||(this.options.lowerCaseLng?e.toLowerCase():e)}return this.options.cleanCode||this.options.lowerCaseLng?e.toLowerCase():e}isSupportedCode(e){return(this.options.load===`languageOnly`||this.options.nonExplicitSupportedLngs)&&(e=this.getLanguagePartFromCode(e)),!this.supportedLngs||!this.supportedLngs.length||this.supportedLngs.includes(e)}getBestMatchFromCodes(e){if(!e)return null;let t;return e.forEach(e=>{if(t)return;let n=this.formatLanguageCode(e);(!this.options.supportedLngs||this.isSupportedCode(n))&&(t=n)}),!t&&this.options.supportedLngs&&e.forEach(e=>{if(t)return;let n=this.getScriptPartFromCode(e);if(this.isSupportedCode(n))return t=n;let r=this.getLanguagePartFromCode(e);if(this.isSupportedCode(r))return t=r;t=this.options.supportedLngs.find(e=>e===r?!0:!e.includes(`-`)&&!r.includes(`-`)?!1:!!(e.includes(`-`)&&!r.includes(`-`)&&e.slice(0,e.indexOf(`-`))===r||e.startsWith(r)&&r.length>1))}),t||=this.getFallbackCodes(this.options.fallbackLng)[0],t}getFallbackCodes(e,t){if(!e)return[];if(typeof e==`function`&&(e=e(t)),q(e)&&(e=[e]),Array.isArray(e))return e;if(!t)return e.default||[];let n=e[t];return n||=e[this.getScriptPartFromCode(t)],n||=e[this.formatLanguageCode(t)],n||=e[this.getLanguagePartFromCode(t)],n||=e.default,n||[]}toResolveHierarchy(e,t){let n=this.options.fallbackLng,r=Array.isArray(n)?n.join(`|`):n;r!==this._cachedFallbackLng&&(this.resolveHierarchyCache={},this._cachedFallbackLng=r);let i=t===void 0||t===!1||q(t),a=t===void 0&&typeof this.options.fallbackLng==`function`,o=q(e)&&i&&!a,s=null;if(o){let n;n=t===void 0?`undefined`:t===!1?`boolean:false`:`string:${t}`,s=`${e.length}:${e}|${n}`}if(s!==null){let e=this.resolveHierarchyCache[s];if(e!==void 0)return e.slice()}let c=this.getFallbackCodes((t===!1?[]:t)||this.options.fallbackLng||[],e),l=[],u=e=>{e&&(this.isSupportedCode(e)?l.push(e):this.logger.warn(`rejecting language code not found in supportedLngs: ${e}`))};return q(e)&&(e.includes(`-`)||e.includes(`_`))?(this.options.load!==`languageOnly`&&u(this.formatLanguageCode(e)),this.options.load!==`languageOnly`&&this.options.load!==`currentOnly`&&u(this.getScriptPartFromCode(e)),this.options.load!==`currentOnly`&&u(this.getLanguagePartFromCode(e))):q(e)&&u(this.formatLanguageCode(e)),c.forEach(e=>{l.includes(e)||u(this.formatLanguageCode(e))}),s===null?l:(this.resolveHierarchyCache[s]=l,l.slice())}},Kt={zero:0,one:1,two:2,few:3,many:4,other:5},qt={select:e=>e===1?`one`:`other`,resolvedOptions:()=>({pluralCategories:[`one`,`other`]})},Jt=class{constructor(e,t={}){this.languageUtils=e,this.options=t,this.logger=It.create(`pluralResolver`),this.pluralRulesCache={}}clearCache(){this.pluralRulesCache={}}getRule(e,t={}){let n=Pt(e===`dev`?`en`:e),r=t.ordinal?`ordinal`:`cardinal`,i=JSON.stringify({cleanedCode:n,type:r});if(i in this.pluralRulesCache)return this.pluralRulesCache[i];let a;try{a=new Intl.PluralRules(n,{type:r})}catch{if(typeof Intl>`u`)return this.logger.error(`No Intl support, please use an Intl polyfill!`),qt;if(!e.match(/-|_/))return qt;let n=this.languageUtils.getLanguagePartFromCode(e);a=this.getRule(n,t)}return this.pluralRulesCache[i]=a,a}needsPlural(e,t={}){let n=this.getRule(e,t);return n||=this.getRule(`dev`,t),n?.resolvedOptions().pluralCategories.length>1}getPluralFormsOfKey(e,t,n={}){return this.getSuffixes(e,n).map(e=>`${t}${e}`)}getSuffixes(e,t={}){let n=this.getRule(e,t);return n||=this.getRule(`dev`,t),n?n.resolvedOptions().pluralCategories.sort((e,t)=>Kt[e]-Kt[t]).map(e=>`${this.options.prepend}${t.ordinal?`ordinal${this.options.prepend}`:``}${e}`):[]}getSuffix(e,t,n={}){let r=this.getRule(e,n);return r?`${this.options.prepend}${n.ordinal?`ordinal${this.options.prepend}`:``}${r.select(t)}`:(this.logger.warn(`no plural rule found for: ${e}`),this.getSuffix(`dev`,t,n))}},Yt=(e,t,n,r=`.`,i=!0)=>{let a=wt(e,t,n);return!a&&i&&q(n)&&(a=Nt(e,n,r),a===void 0&&(a=Nt(t,n,r))),a},Xt=e=>e.replace(/\$/g,`$$$$`),Zt=class{constructor(e={}){this.logger=It.create(`interpolator`),this.options=e,this.format=e?.interpolation?.format||(e=>e),this.init(e)}init(e={}){e.interpolation||={escapeValue:!0};let{escape:t,escapeValue:n,useRawValueToEscape:r,prefix:i,prefixEscaped:a,suffix:o,suffixEscaped:s,formatSeparator:c,unescapeSuffix:l,unescapePrefix:u,nestingPrefix:d,nestingPrefixEscaped:f,nestingSuffix:p,nestingSuffixEscaped:m,nestingOptionsSeparator:h,maxReplaces:g,alwaysFormat:_}=e.interpolation;this.escape=t===void 0?Ot:t,this.escapeValue=n===void 0||n,this.useRawValueToEscape=r!==void 0&&r,this.prefix=i?Et(i):a||`{{`,this.suffix=o?Et(o):s||`}}`,this.formatSeparator=c||`,`,this.unescapePrefix=l?``:u?Et(u):`-`,this.unescapeSuffix=this.unescapePrefix?``:l?Et(l):``,this.nestingPrefix=d?Et(d):f||Et(`$t(`),this.nestingSuffix=p?Et(p):m||Et(`)`),this.nestingOptionsSeparator=h||`,`,this.maxReplaces=g||1e3,this.alwaysFormat=_!==void 0&&_,this.resetRegExp()}reset(){this.options&&this.init(this.options)}resetRegExp(){let e=(e,t)=>e?.source===t?(e.lastIndex=0,e):new RegExp(t,`g`);this.regexp=e(this.regexp,`${this.prefix}(.+?)${this.suffix}`),this.regexpUnescape=e(this.regexpUnescape,`${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`),this.nestingRegexp=e(this.nestingRegexp,`${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`)}interpolate(e,t,n,r){let i,a,o,s=this.options&&this.options.interpolation&&this.options.interpolation.defaultVariables||{},c=e=>{if(!e.includes(this.formatSeparator)){let i=Yt(t,s,e,this.options.keySeparator,this.options.ignoreJSONStructure);return this.alwaysFormat?this.format(i,void 0,n,{...r,...t,interpolationkey:e}):i}let i=e.split(this.formatSeparator),a=i.shift().trim(),o=i.join(this.formatSeparator).trim();return this.format(Yt(t,s,a,this.options.keySeparator,this.options.ignoreJSONStructure),o,n,{...r,...t,interpolationkey:a})};this.resetRegExp(),!this.escapeValue&&typeof e==`string`&&/\$t\([^)]*\{[^}]*\{\{/.test(e)&&this.logger.warn(`nesting options string contains interpolated variables with escapeValue: false — if any of those values are attacker-controlled they can inject additional nesting options (e.g. redirect lng/ns). Sanitise untrusted input before passing it to t(), or keep escapeValue: true.`);let l=r?.missingInterpolationHandler||this.options.missingInterpolationHandler,u=r?.interpolation?.skipOnVariables===void 0?this.options.interpolation.skipOnVariables:r.interpolation.skipOnVariables;return[{regex:this.regexpUnescape,safeValue:e=>e},{regex:this.regexp,safeValue:e=>this.escapeValue?this.escape(e):e}].forEach(t=>{for(o=0;i=t.regex.exec(e);){let n=i[1].trim();if(a=c(n),a===void 0)if(typeof l==`function`){let t=l(e,i,r);a=q(t)?t:``}else if(r&&Object.prototype.hasOwnProperty.call(r,n))a=``;else if(u){a=i[0];continue}else this.logger.warn(`missed to pass in variable ${n} for interpolating ${e}`),a=``;else!q(a)&&!this.useRawValueToEscape&&(a=ht(a));let s=t.safeValue(a);if(e=e.replace(i[0],Xt(s)),u?(t.regex.lastIndex+=s.length,t.regex.lastIndex-=i[0].length):t.regex.lastIndex=0,o++,o>=this.maxReplaces)break}}),e}nest(e,t,n={}){let r,i,a,o=(e,t)=>{let n=this.nestingOptionsSeparator;if(!e.includes(n))return e;let r=e.split(RegExp(`${Et(n)}[ ]*{`)),i=`{${r[1]}`;e=r[0],i=this.interpolate(i,a);let o=i.match(/'/g),s=i.match(/"/g);((o?.length??0)%2==0&&!s||(s?.length??0)%2!=0)&&(i=i.replace(/'/g,`"`));try{a=JSON.parse(i),t&&(a={...t,...a})}catch(t){return this.logger.warn(`failed parsing options string in nesting for key ${e}`,t),`${e}${n}${i}`}return a.defaultValue&&a.defaultValue.includes(this.prefix)&&delete a.defaultValue,e};for(;r=this.nestingRegexp.exec(e);){let s=[];a={...n},a=a.replace&&!q(a.replace)?a.replace:a,a.applyPostProcessor=!1,delete a.defaultValue;let c=/{.*}/s.test(r[1])?r[1].lastIndexOf(`}`)+1:r[1].indexOf(this.formatSeparator);if(c!==-1&&(s=r[1].slice(c).split(this.formatSeparator).map(e=>e.trim()).filter(Boolean),r[1]=r[1].slice(0,c)),i=t(o.call(this,r[1].trim(),a),a),i&&r[0]===e&&!q(i))return i;q(i)||(i=ht(i)),i||=(this.logger.warn(`missed to resolve ${r[1]} for nesting ${e}`),``),s.length&&(i=s.reduce((e,t)=>this.format(e,t,n.lng,{...n,interpolationkey:r[1].trim()}),i.trim())),e=e.replace(r[0],i),this.regexp.lastIndex=0}return e}},Qt=e=>{let t=e.toLowerCase().trim(),n={};if(e.includes(`(`)){let r=e.split(`(`);t=r[0].toLowerCase().trim();let i=r[1].slice(0,-1);t===`currency`&&!i.includes(`:`)?n.currency||=i.trim():t===`relativetime`&&!i.includes(`:`)?n.range||=i.trim():i.split(`;`).forEach(e=>{if(e){let[t,...r]=e.split(`:`),i=r.join(`:`).trim().replace(/^'+|'+$/g,``),a=t.trim();n[a]||(n[a]=i),i===`false`&&(n[a]=!1),i===`true`&&(n[a]=!0),isNaN(i)||(n[a]=parseInt(i,10))}})}return{formatName:t,formatOptions:n}},$t=e=>{let t={};return(n,r,i)=>{let a=i;i&&i.interpolationkey&&i.formatParams&&i.formatParams[i.interpolationkey]&&i[i.interpolationkey]&&(a={...a,[i.interpolationkey]:void 0});let o=r+JSON.stringify(a),s=t[o];return s||(s=e(Pt(r),i),t[o]=s),s(n)}},en=e=>(t,n,r)=>e(Pt(n),r)(t),tn=class{constructor(e={}){this.logger=It.create(`formatter`),this.options=e,this.init(e)}init(e,t={interpolation:{}}){this.formatSeparator=t.interpolation.formatSeparator||`,`;let n=t.cacheInBuiltFormats?$t:en;this.formats={number:n((e,t)=>{let n=new Intl.NumberFormat(e,{...t});return e=>n.format(e)}),currency:n((e,t)=>{let n=new Intl.NumberFormat(e,{...t,style:`currency`});return e=>n.format(e)}),datetime:n((e,t)=>{let n=new Intl.DateTimeFormat(e,{...t});return e=>n.format(e)}),relativetime:n((e,t)=>{let n=new Intl.RelativeTimeFormat(e,{...t});return e=>n.format(e,t.range||`day`)}),list:n((e,t)=>{let n=new Intl.ListFormat(e,{...t});return e=>n.format(e)})}}add(e,t){this.formats[e.toLowerCase().trim()]=t}addCached(e,t){this.formats[e.toLowerCase().trim()]=$t(t)}format(e,t,n,r={}){if(!t||e==null)return e;let i=t.split(this.formatSeparator),a=[];for(let e=0;e-1&&!t.includes(`)`)&&e+1{let{formatName:i,formatOptions:a}=Qt(t);if(this.formats[i]){let t=e;try{let o=r?.formatParams?.[r.interpolationkey]||{},s=o.locale||o.lng||r.locale||r.lng||n;t=this.formats[i](e,s,{...a,...r,...o})}catch(e){this.logger.warn(e)}return t}return this.logger.warn(`there was no format function for ${i}`),e},e)}},nn=(e,t)=>{e.pending[t]!==void 0&&(delete e.pending[t],e.pendingCount--)},rn=class extends Lt{constructor(e,t,n,r={}){super(),this.backend=e,this.store=t,this.services=n,this.languageUtils=n.languageUtils,this.options=r,this.logger=It.create(`backendConnector`),this.waitingReads=[],this.maxParallelReads=r.maxParallelReads||10,this.readingCalls=0,this.maxRetries=r.maxRetries>=0?r.maxRetries:5,this.retryTimeout=r.retryTimeout>=1?r.retryTimeout:350,this.state={},this.queue=[],this.backend?.init?.(n,r.backend,r)}queueLoad(e,t,n,r){let i={},a={},o={},s={};return e.forEach(e=>{let r=!0;t.forEach(t=>{let o=`${e}|${t}`;!n.reload&&this.store.hasResourceBundle(e,t)?this.state[o]=2:this.state[o]<0||(this.state[o]===1?a[o]===void 0&&(a[o]=!0):(this.state[o]=1,r=!1,a[o]===void 0&&(a[o]=!0),i[o]===void 0&&(i[o]=!0),s[t]===void 0&&(s[t]=!0)))}),r||(o[e]=!0)}),(Object.keys(i).length||Object.keys(a).length)&&this.queue.push({pending:a,pendingCount:Object.keys(a).length,loaded:{},errors:[],callback:r}),{toLoad:Object.keys(i),pending:Object.keys(a),toLoadLanguages:Object.keys(o),toLoadNamespaces:Object.keys(s)}}loaded(e,t,n){let r=e.split(`|`),i=r[0],a=r[1];t&&this.emit(`failedLoading`,i,a,t),!t&&n&&this.store.addResourceBundle(i,a,n,void 0,void 0,{skipCopy:!0}),this.state[e]=t?-1:2,t&&n&&(this.state[e]=0);let o={};this.queue.forEach(n=>{St(n.loaded,[i],a),nn(n,e),t&&n.errors.push(t),n.pendingCount===0&&!n.done&&(Object.keys(n.loaded).forEach(e=>{o[e]||(o[e]={});let t=n.loaded[e];t.length&&t.forEach(t=>{o[e][t]===void 0&&(o[e][t]=!0)})}),n.done=!0,n.errors.length?n.callback(n.errors):n.callback())}),this.emit(`loaded`,o),this.queue=this.queue.filter(e=>!e.done)}read(e,t,n,r=0,i=this.retryTimeout,a){if(!e.length)return a(null,{});if(this.readingCalls>=this.maxParallelReads){this.waitingReads.push({lng:e,ns:t,fcName:n,tried:r,wait:i,callback:a});return}this.readingCalls++;let o=(o,s)=>{if(this.readingCalls--,this.waitingReads.length>0){let e=this.waitingReads.shift();this.read(e.lng,e.ns,e.fcName,e.tried,e.wait,e.callback)}if(o&&s&&r{this.read(e,t,n,r+1,i*2,a)},i);return}a(o,s)},s=this.backend[n].bind(this.backend);if(s.length===2){try{let n=s(e,t);n&&typeof n.then==`function`?n.then(e=>o(null,e)).catch(o):o(null,n)}catch(e){o(e)}return}return s(e,t,o)}prepareLoading(e,t,n={},r){if(!this.backend)return this.logger.warn(`No backend was added via i18next.use. Will not load resources.`),r&&r();q(e)&&(e=this.languageUtils.toResolveHierarchy(e)),q(t)&&(t=[t]);let i=this.queueLoad(e,t,n,r);if(!i.toLoad.length)return i.pending.length||r(),null;i.toLoad.forEach(e=>{this.loadOne(e)})}load(e,t,n){this.prepareLoading(e,t,{},n)}reload(e,t,n){this.prepareLoading(e,t,{reload:!0},n)}loadOne(e,t=``){let n=e.split(`|`),r=n[0],i=n[1];this.read(r,i,`read`,void 0,void 0,(n,a)=>{n&&this.logger.warn(`${t}loading namespace ${i} for language ${r} failed`,n),!n&&a&&this.logger.log(`${t}loaded namespace ${i} for language ${r}`,a),this.loaded(e,n,a)})}saveMissing(e,t,n,r,i,a={},o=()=>{}){if(this.services?.utils?.hasLoadedNamespace&&!this.services?.utils?.hasLoadedNamespace(t)){this.logger.warn(`did not save key "${n}" as the namespace "${t}" was not yet loaded`,`This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!`);return}if(n!=null&&n!==``){if(this.backend?.create){let s={...a,isUpdate:i},c=this.backend.create.bind(this.backend);if(c.length<6)try{let i;i=c.length===5?c(e,t,n,r,s):c(e,t,n,r),i&&typeof i.then==`function`?i.then(e=>o(null,e)).catch(o):o(null,i)}catch(e){o(e)}else c(e,t,n,r,o,s)}!e||!e[0]||this.store.addResource(e[0],t,n,r)}}},an=()=>({debug:!1,initAsync:!0,ns:[`translation`],defaultNS:[`translation`],fallbackLng:[`dev`],fallbackNS:!1,supportedLngs:!1,nonExplicitSupportedLngs:!1,load:`all`,preload:!1,keySeparator:`.`,nsSeparator:`:`,pluralSeparator:`_`,contextSeparator:`_`,enableSelector:!1,partialBundledLanguages:!1,saveMissing:!1,updateMissing:!1,saveMissingTo:`fallback`,saveMissingPlurals:!0,missingKeyHandler:!1,missingInterpolationHandler:!1,postProcess:!1,postProcessPassResolved:!1,returnNull:!1,returnEmptyString:!0,returnObjects:!1,joinArrays:!1,returnedObjectHandler:!1,parseMissingKeyHandler:!1,appendNamespaceToMissingKey:!1,appendNamespaceToCIMode:!1,overloadTranslationOptionHandler:e=>{let t={};if(typeof e[1]==`object`&&(t=e[1]),q(e[1])&&(t.defaultValue=e[1]),q(e[2])&&(t.tDescription=e[2]),typeof e[2]==`object`||typeof e[3]==`object`){let n=e[3]||e[2];Object.keys(n).forEach(e=>{t[e]=n[e]})}return t},interpolation:{escapeValue:!0,prefix:`{{`,suffix:`}}`,formatSeparator:`,`,unescapePrefix:`-`,nestingPrefix:`$t(`,nestingSuffix:`)`,nestingOptionsSeparator:`,`,maxReplaces:1e3,skipOnVariables:!0},cacheInBuiltFormats:!0}),on=e=>(q(e.ns)&&(e.ns=[e.ns]),q(e.fallbackLng)&&(e.fallbackLng=[e.fallbackLng]),q(e.fallbackNS)&&(e.fallbackNS=[e.fallbackNS]),e.supportedLngs&&!e.supportedLngs.includes(`cimode`)&&(e.supportedLngs=e.supportedLngs.concat([`cimode`])),e),sn=()=>{},cn=e=>{Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach(t=>{typeof e[t]==`function`&&(e[t]=e[t].bind(e))})},ln=class e extends Lt{constructor(e={},t){if(super(),this.options=on(e),this.services={},this.logger=It,this.modules={external:[]},cn(this),t&&!this.isInitialized&&!e.isClone){if(!this.options.initAsync)return this.init(e,t),this;setTimeout(()=>{this.init(e,t)},0)}}init(e={},t){this.isInitializing=!0,typeof e==`function`&&(t=e,e={}),e.defaultNS==null&&e.ns&&(q(e.ns)?e.defaultNS=e.ns:e.ns.includes(`translation`)||(e.defaultNS=e.ns[0]));let n=an();this.options={...n,...this.options,...on(e)},this.options.interpolation={...n.interpolation,...this.options.interpolation},e.keySeparator!==void 0&&(this.options.userDefinedKeySeparator=e.keySeparator),e.nsSeparator!==void 0&&(this.options.userDefinedNsSeparator=e.nsSeparator),typeof this.options.overloadTranslationOptionHandler!=`function`&&(this.options.overloadTranslationOptionHandler=n.overloadTranslationOptionHandler);let r=e=>e?typeof e==`function`?new e:e:null;if(!this.options.isClone){this.modules.logger?It.init(r(this.modules.logger),this.options):It.init(null,this.options);let e;e=this.modules.formatter?this.modules.formatter:tn;let t=new Gt(this.options);this.store=new Rt(this.options.resources,this.options);let n=this.services;n.logger=It,n.resourceStore=this.store,n.languageUtils=t,n.pluralResolver=new Jt(t,{prepend:this.options.pluralSeparator}),e&&(n.formatter=r(e),n.formatter.init&&n.formatter.init(n,this.options),this.options.interpolation.format=n.formatter.format.bind(n.formatter)),n.interpolator=new Zt(this.options),n.utils={hasLoadedNamespace:this.hasLoadedNamespace.bind(this)},n.backendConnector=new rn(r(this.modules.backend),n.resourceStore,n,this.options),n.backendConnector.on(`*`,(e,...t)=>{this.emit(e,...t)}),this.modules.languageDetector&&(n.languageDetector=r(this.modules.languageDetector),n.languageDetector.init&&n.languageDetector.init(n,this.options.detection,this.options)),this.modules.i18nFormat&&(n.i18nFormat=r(this.modules.i18nFormat),n.i18nFormat.init&&n.i18nFormat.init(this)),this.translator=new Wt(this.services,this.options),this.translator.on(`*`,(e,...t)=>{this.emit(e,...t)}),this.modules.external.forEach(e=>{e.init&&e.init(this)})}if(this.format=this.options.interpolation.format,t||=sn,this.options.fallbackLng&&!this.services.languageDetector&&!this.options.lng){let e=this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);e.length>0&&e[0]!==`dev`&&(this.options.lng=e[0])}!this.services.languageDetector&&!this.options.lng&&this.logger.warn(`init: no languageDetector is used and no lng is defined`),[`getResource`,`hasResourceBundle`,`getResourceBundle`,`getDataByLanguage`].forEach(e=>{this[e]=(...t)=>this.store[e](...t)}),[`addResource`,`addResources`,`addResourceBundle`,`removeResourceBundle`].forEach(e=>{this[e]=(...t)=>(this.store[e](...t),this)});let i=mt(),a=()=>{let e=(e,n)=>{this.isInitializing=!1,this.isInitialized&&!this.initializedStoreOnce&&this.logger.warn(`init: i18next is already initialized. You should call init just once!`),this.isInitialized=!0,this.options.isClone||this.logger.log(`initialized`,this.options),this.emit(`initialized`,this.options),i.resolve(n),t(e,n)};if((this.languages||this.isLanguageChangingTo)&&!this.isInitialized)return e(null,this.t.bind(this));this.changeLanguage(this.options.lng,e)};return this.options.resources||!this.options.initAsync?a():setTimeout(a,0),i}loadResources(e,t=sn){let n=t,r=q(e)?e:this.language;if(typeof e==`function`&&(n=e),!this.options.resources||this.options.partialBundledLanguages){if(r?.toLowerCase()===`cimode`&&(!this.options.preload||this.options.preload.length===0))return n();let e=[],t=t=>{t&&t!==`cimode`&&this.services.languageUtils.toResolveHierarchy(t).forEach(t=>{t!==`cimode`&&(e.includes(t)||e.push(t))})};r?t(r):this.services.languageUtils.getFallbackCodes(this.options.fallbackLng).forEach(e=>t(e)),this.options.preload?.forEach?.(e=>t(e)),this.services.backendConnector.load(e,this.options.ns,e=>{!e&&!this.resolvedLanguage&&this.language&&this.setResolvedLanguage(this.language),n(e)})}else n(null)}reloadResources(e,t,n){let r=mt();return typeof e==`function`&&(n=e,e=void 0),typeof t==`function`&&(n=t,t=void 0),e||=this.languages,t||=this.options.ns,n||=sn,this.services.backendConnector.reload(e,t,e=>{r.resolve(),n(e)}),r}use(e){if(!e)throw Error(`You are passing an undefined module! Please check the object you are passing to i18next.use()`);if(!e.type)throw Error(`You are passing a wrong module! Please check the object you are passing to i18next.use()`);return e.type===`backend`&&(this.modules.backend=e),(e.type===`logger`||e.log&&e.warn&&e.error)&&(this.modules.logger=e),e.type===`languageDetector`&&(this.modules.languageDetector=e),e.type===`i18nFormat`&&(this.modules.i18nFormat=e),e.type===`postProcessor`&&zt.addPostProcessor(e),e.type===`formatter`&&(this.modules.formatter=e),e.type===`3rdParty`&&this.modules.external.push(e),this}setResolvedLanguage(e){if(!(!e||!this.languages)&&![`cimode`,`dev`].includes(e)){for(let e=0;e{this.language=e,this.languages=this.services.languageUtils.toResolveHierarchy(e),this.resolvedLanguage=void 0,this.setResolvedLanguage(e)},i=(i,a)=>{a?this.isLanguageChangingTo===e&&(r(a),this.translator.changeLanguage(a),this.isLanguageChangingTo=void 0,this.emit(`languageChanged`,a),this.logger.log(`languageChanged`,a)):this.isLanguageChangingTo=void 0,n.resolve((...e)=>this.t(...e)),t&&t(i,(...e)=>this.t(...e))},a=t=>{!e&&!t&&this.services.languageDetector&&(t=[]);let n=q(t)?t:t&&t[0],a=this.store.hasLanguageSomeTranslations(n)?n:this.services.languageUtils.getBestMatchFromCodes(q(t)?[t]:t);a&&(this.language||r(a),this.translator.language||this.translator.changeLanguage(a),this.services.languageDetector?.cacheUserLanguage?.(a)),this.loadResources(a,e=>{i(e,a)})};return!e&&this.services.languageDetector&&!this.services.languageDetector.async?a(this.services.languageDetector.detect()):!e&&this.services.languageDetector&&this.services.languageDetector.async?this.services.languageDetector.detect.length===0?this.services.languageDetector.detect().then(a):this.services.languageDetector.detect(a):a(e),n}getFixedT(e,t,n,r){let i=r?.scopeNs,a=(e,t,...r)=>{let o;o=typeof t==`object`?{...t}:this.options.overloadTranslationOptionHandler([e,t].concat(r)),o.lng=o.lng||a.lng,o.lngs=o.lngs||a.lngs;let s=o.ns!==void 0&&o.ns!==null;o.ns=o.ns||a.ns,o.keyPrefix!==``&&(o.keyPrefix=o.keyPrefix||n||a.keyPrefix);let c={...this.options,...o};Array.isArray(i)&&!s&&(c.ns=i),typeof o.keyPrefix==`function`&&(o.keyPrefix=Ht(o.keyPrefix,c));let l=this.options.keySeparator||`.`,u;return o.keyPrefix&&Array.isArray(e)?u=e.map(e=>(typeof e==`function`&&(e=Ht(e,c)),`${o.keyPrefix}${l}${e}`)):(typeof e==`function`&&(e=Ht(e,c)),u=o.keyPrefix?`${o.keyPrefix}${l}${e}`:e),this.t(u,o)};return q(e)?a.lng=e:a.lngs=e,a.ns=t,a.keyPrefix=n,a}t(...e){return this.translator?.translate(...e)}exists(...e){return this.translator?.exists(...e)}setDefaultNamespace(e){this.options.defaultNS=e}hasLoadedNamespace(e,t={}){if(!this.isInitialized)return this.logger.warn(`hasLoadedNamespace: i18next was not initialized`,this.languages),!1;if(!this.languages||!this.languages.length)return this.logger.warn(`hasLoadedNamespace: i18n.languages were undefined or empty`,this.languages),!1;let n=t.lng||this.resolvedLanguage||this.languages[0],r=this.options?this.options.fallbackLng:!1,i=this.languages[this.languages.length-1];if(n.toLowerCase()===`cimode`)return!0;let a=(e,t)=>{let n=this.services.backendConnector.state[`${e}|${t}`];return n===-1||n===0||n===2};if(t.precheck){let e=t.precheck(this,a);if(e!==void 0)return e}return!!(this.hasResourceBundle(n,e)||!this.services.backendConnector.backend||this.options.resources&&!this.options.partialBundledLanguages||a(n,e)&&(!r||a(i,e)))}loadNamespaces(e,t){let n=mt();return this.options.ns?(q(e)&&(e=[e]),e.forEach(e=>{this.options.ns.includes(e)||this.options.ns.push(e)}),this.loadResources(e=>{n.resolve(),t&&t(e)}),n):(t&&t(),Promise.resolve())}loadLanguages(e,t){let n=mt();q(e)&&(e=[e]);let r=this.options.preload||[],i=e.filter(e=>!r.includes(e)&&this.services.languageUtils.isSupportedCode(e));return i.length?(this.options.preload=r.concat(i),this.loadResources(e=>{n.resolve(),t&&t(e)}),n):(t&&t(),Promise.resolve())}dir(e){if(e||=this.resolvedLanguage||(this.languages?.length>0?this.languages[0]:this.language),!e)return`rtl`;try{let t=new Intl.Locale(e);if(t&&t.getTextInfo){let e=t.getTextInfo();if(e&&e.direction)return e.direction}}catch{}let t=`ar.shu.sqr.ssh.xaa.yhd.yud.aao.abh.abv.acm.acq.acw.acx.acy.adf.ads.aeb.aec.afb.ajp.apc.apd.arb.arq.ars.ary.arz.auz.avl.ayh.ayl.ayn.ayp.bbz.pga.he.iw.ps.pbt.pbu.pst.prp.prd.ug.ur.ydd.yds.yih.ji.yi.hbo.men.xmn.fa.jpr.peo.pes.prs.dv.sam.ckb`.split(`.`),n=this.services?.languageUtils||new Gt(an());return e.toLowerCase().indexOf(`-latn`)>1?`ltr`:t.includes(n.getLanguagePartFromCode(e))||e.toLowerCase().indexOf(`-arab`)>1?`rtl`:`ltr`}static createInstance(t={},n){let r=new e(t,n);return r.createInstance=e.createInstance,r}cloneInstance(t={},n=sn){let r=t.forkResourceStore;r&&delete t.forkResourceStore;let i={...this.options,...t,isClone:!0},a=new e(i);if((t.debug!==void 0||t.prefix!==void 0)&&(a.logger=a.logger.clone(t)),[`store`,`services`,`language`].forEach(e=>{a[e]=this[e]}),a.services={...this.services},a.services.utils={hasLoadedNamespace:a.hasLoadedNamespace.bind(a)},r&&(a.store=new Rt(Object.keys(this.store.data).reduce((e,t)=>(e[t]={...this.store.data[t]},e[t]=Object.keys(e[t]).reduce((n,r)=>(n[r]={...e[t][r]},n),e[t]),e),{}),i),a.services.resourceStore=a.store),t.interpolation){let e={...an().interpolation,...this.options.interpolation,...t.interpolation},n={...i,interpolation:e};a.services.interpolator=new Zt(n)}return a.translator=new Wt(a.services,i),a.translator.on(`*`,(e,...t)=>{a.emit(e,...t)}),a.init(i,n),a.translator.options=i,a.translator.backendConnector.services.utils={hasLoadedNamespace:a.hasLoadedNamespace.bind(a)},a}toJSON(){return{options:this.options,store:this.store,language:this.language,languages:this.languages,resolvedLanguage:this.resolvedLanguage}}}.createInstance();ln.createInstance,ln.dir,ln.init,ln.loadResources,ln.reloadResources,ln.use,ln.changeLanguage,ln.getFixedT,ln.t,ln.exists,ln.setDefaultNamespace,ln.hasLoadedNamespace,ln.loadNamespaces,ln.loadLanguages;var un=(e,t,n,r)=>{let i=[n,{code:t,...r||{}}];if(e?.services?.logger?.forward)return e.services.logger.forward(i,`warn`,`react-i18next::`,!0);_n(i[0])&&(i[0]=`react-i18next:: ${i[0]}`),e?.services?.logger?.warn?e.services.logger.warn(...i):console?.warn&&console.warn(...i)},dn={},fn=(e,t,n,r)=>{_n(n)&&dn[n]||(_n(n)&&(dn[n]=new Date),un(e,t,n,r))},pn=(e,t)=>()=>{if(e.isInitialized)t();else{let n=()=>{setTimeout(()=>{e.off(`initialized`,n)},0),t()};e.on(`initialized`,n)}},mn=(e,t,n)=>{e.loadNamespaces(t,pn(e,n))},hn=(e,t,n,r)=>{if(_n(n)&&(n=[n]),e.options.preload&&e.options.preload.indexOf(t)>-1)return mn(e,n,r);n.forEach(t=>{e.options.ns.indexOf(t)<0&&e.options.ns.push(t)}),e.loadLanguages(t,pn(e,r))},gn=(e,t,n={})=>!t.languages||!t.languages.length?(fn(t,`NO_LANGUAGES`,`i18n.languages were undefined or empty`,{languages:t.languages}),!0):t.hasLoadedNamespace(e,{lng:n.lng,precheck:(t,r)=>{if(n.bindI18n&&n.bindI18n.indexOf(`languageChanging`)>-1&&t.services.backendConnector.backend&&t.isLanguageChangingTo&&!r(t.isLanguageChangingTo,e))return!1}}),_n=e=>typeof e==`string`,vn=e=>typeof e==`object`&&!!e,yn=/&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34|nbsp|#160|copy|#169|reg|#174|hellip|#8230|#x2F|#47);/g,bn={"&":`&`,"&":`&`,"<":`<`,"<":`<`,">":`>`,">":`>`,"'":`'`,"'":`'`,""":`"`,""":`"`," ":` `," ":` `,"©":`©`,"©":`©`,"®":`®`,"®":`®`,"…":`…`,"…":`…`,"/":`/`,"/":`/`},xn=e=>bn[e],Sn={bindI18n:`languageChanged`,bindI18nStore:``,transEmptyNodeValue:``,transSupportBasicHtmlNodes:!0,transWrapTextNodes:``,transKeepBasicHtmlNodesFor:[`br`,`strong`,`i`,`p`],useSuspense:!0,unescape:e=>e.replace(yn,xn),transDefaultProps:void 0},Cn=(e={})=>{Sn={...Sn,...e}},wn=()=>Sn,Tn,En=e=>{Tn=e},Dn=()=>Tn,On={type:`3rdParty`,init(e){Cn(e.options.react),En(e)}},kn=(0,w.createContext)(),An=class{constructor(){this.usedNamespaces={}}addUsedNamespaces(e){e.forEach(e=>{this.usedNamespaces[e]||(this.usedNamespaces[e]=!0)})}getUsedNamespaces(){return Object.keys(this.usedNamespaces)}},jn=o((e=>{var t=d();function n(e,t){return e===t&&(e!==0||1/e==1/t)||e!==e&&t!==t}var r=typeof Object.is==`function`?Object.is:n,i=t.useState,a=t.useEffect,o=t.useLayoutEffect,s=t.useDebugValue;function c(e,t){var n=t(),r=i({inst:{value:n,getSnapshot:t}}),c=r[0].inst,u=r[1];return o(function(){c.value=n,c.getSnapshot=t,l(c)&&u({inst:c})},[e,n,t]),a(function(){return l(c)&&u({inst:c}),e(function(){l(c)&&u({inst:c})})},[e]),s(n),n}function l(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!r(e,n)}catch{return!0}}function u(e,t){return t()}var f=typeof window>`u`||window.document===void 0||window.document.createElement===void 0?u:c;e.useSyncExternalStore=t.useSyncExternalStore===void 0?f:t.useSyncExternalStore})),Mn=o(((e,t)=>{t.exports=jn()}))(),Nn={t:(e,t)=>{if(_n(t))return t;if(vn(t)&&_n(t.defaultValue))return t.defaultValue;if(typeof e==`function`)return``;if(Array.isArray(e)){let t=e[e.length-1];return typeof t==`function`?``:t}return e},ready:!1},Pn=()=>()=>{},Fn=(e,t={})=>{let{i18n:n}=t,{i18n:r,defaultNS:i}=(0,w.useContext)(kn)||{},a=n||r||Dn();a&&!a.reportNamespaces&&(a.reportNamespaces=new An),a||fn(a,`NO_I18NEXT_INSTANCE`,`useTranslation: You will need to pass in an i18next instance by using initReactI18next or by passing it via props or context. In monorepo setups, make sure there is only one instance of react-i18next.`);let o=(0,w.useMemo)(()=>({...wn(),...a?.options?.react,...t}),[a,t]),{useSuspense:s,keyPrefix:c}=o,l=e||i||a?.options?.defaultNS,u=_n(l)?[l]:l||[`translation`],d=(0,w.useMemo)(()=>u,u);a?.reportNamespaces?.addUsedNamespaces?.(d);let f=(0,w.useRef)(0),p=(0,w.useCallback)(e=>{if(!a)return Pn;let{bindI18n:t,bindI18nStore:n}=o,r=()=>{f.current+=1,e()};return t&&a.on(t,r),n&&a.store.on(n,r),()=>{t&&t.split(` `).forEach(e=>a.off(e,r)),n&&n.split(` `).forEach(e=>a.store.off(e,r))}},[a,o]),m=(0,w.useRef)(),h=(0,w.useCallback)(()=>{if(!a)return Nn;let e=!!(a.isInitialized||a.initializedStoreOnce)&&d.every(e=>gn(e,a,o)),n=t.lng||a.language,r=f.current,i=m.current;if(i&&i.ready===e&&i.lng===n&&i.keyPrefix===c&&i.revision===r)return i;let s={t:a.getFixedT(n,o.nsMode===`fallback`?d:d[0],c,{scopeNs:d}),ready:e,lng:n,keyPrefix:c,revision:r};return m.current=s,s},[a,d,c,o,t.lng]),[g,_]=(0,w.useState)(0),{t:v,ready:y}=(0,Mn.useSyncExternalStore)(p,h,h);(0,w.useEffect)(()=>{if(a&&!y&&!s){let e=()=>_(e=>e+1);t.lng?hn(a,t.lng,d,e):mn(a,d,e)}},[a,t.lng,d,y,s,g]);let b=a||{},x=(0,w.useRef)(null),S=(0,w.useRef)(),C=e=>{let t=Object.getOwnPropertyDescriptors(e);t.__original&&delete t.__original;let n=Object.create(Object.getPrototypeOf(e),t);if(!Object.prototype.hasOwnProperty.call(n,`__original`))try{Object.defineProperty(n,"__original",{value:e,writable:!1,enumerable:!1,configurable:!1})}catch{}return n},T=(0,w.useMemo)(()=>{let e=b,t=e?.language,n=e;e&&(x.current&&x.current.__original===e&&S.current===t?n=x.current:(n=C(e),x.current=n,S.current=t));let r=!y&&!s?(...e)=>(fn(a,`USE_T_BEFORE_READY`,`useTranslation: t was called before ready. When using useSuspense: false, make sure to check the ready flag before using t.`),v(...e)):v,i=[r,n,y];return i.t=r,i.i18n=n,i.ready=y,i},[v,b,y,b.resolvedLanguage,b.language,b.languages]);if(a&&s&&!y){let e=!1;try{e=!1}catch{}throw e&&fn(a,`SUSPENDED_WHILE_LOADING`,`useTranslation: suspended while translations are loading (useSuspense is true by default). Add a boundary above this component, or set react.useSuspense: false in the i18next init options. https://react.i18next.com/latest/usetranslation-hook`),new Promise(e=>{let n=()=>e();t.lng?hn(a,t.lng,d,n):mn(a,d,n)})}return T};function In({i18n:e,defaultNS:t,children:n}){let r=(0,w.useMemo)(()=>({i18n:e,defaultNS:t}),[e,t]);return(0,w.createElement)(kn.Provider,{value:r},n)}var Ln=e=>{let t,n=new Set,r=(e,r)=>{let i=typeof e==`function`?e(t):e;if(!Object.is(i,t)){let e=t;t=r??(typeof i!=`object`||!i)?i:Object.assign({},t,i),n.forEach(n=>n(t,e))}},i=()=>t,a={setState:r,getState:i,getInitialState:()=>o,subscribe:e=>(n.add(e),()=>n.delete(e))},o=t=e(r,i,a);return a},Rn=(e=>e?Ln(e):Ln),zn=e=>e;function Bn(e,t=zn){let n=w.useSyncExternalStore(e.subscribe,w.useCallback(()=>t(e.getState()),[e,t]),w.useCallback(()=>t(e.getInitialState()),[e,t]));return w.useDebugValue(n),n}var Vn=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.fragment`);function r(e,n,r){var i=null;if(r!==void 0&&(i=``+r),n.key!==void 0&&(i=``+n.key),`key`in n)for(var a in r={},n)a!==`key`&&(r[a]=n[a]);else r=n;return n=r.ref,{$$typeof:t,type:e,key:i,ref:n===void 0?null:n,props:r}}e.Fragment=n,e.jsx=r,e.jsxs=r})),J=o(((e,t)=>{t.exports=Vn()}))(),Hn=Array.from({length:16},(e,t)=>`cell-${t+1}`);function Un({animated:e=!1,compact:t=!1}){let[n,r]=(0,w.useState)(0),i=t?10:16,a=(0,J.jsxs)(`strong`,{className:t?`brand-lockup compact`:`brand-lockup`,children:[(0,J.jsx)(`span`,{className:`brand-word`,children:`Open`}),(0,J.jsx)(`span`,{className:`pixel-mark`,role:`img`,"aria-label":`OpenPI`,children:Hn.slice(0,i).map(e=>(0,J.jsx)(`i`,{},e))})]},n);return e?(0,J.jsx)(`button`,{className:`landing-brand`,type:`button`,"aria-label":`Replay OpenPI logo animation`,onClick:()=>r(e=>e+1),children:a}):a}function Wn(e,t,n){return Math.min(n,Math.max(t,Math.round(e)))}function Gn({side:e,value:t,min:n,max:r,defaultValue:i,collapseThreshold:a,onCollapse:o,onExpandPastMax:s,onChange:c,onDraggingChange:l}){let{t:u}=Fn(),d=(0,w.useRef)(null),f=(e,t)=>{d.current?.pointerId===t&&(d.current=null,e.hasPointerCapture(t)&&e.releasePointerCapture(t),l(!1))},p=e=>c(Wn(e,n,r));return(0,J.jsx)(`hr`,{className:`pane-resizer ${e}`,tabIndex:0,"aria-label":u(e===`left`?`resizeSidebar`:`resizeWorkbar`),"aria-orientation":`vertical`,"aria-valuenow":t,"aria-valuemin":n,"aria-valuemax":r,"data-pane-resizer":e,onKeyDown:a=>{let o;switch(a.key){case`ArrowLeft`:case`ArrowRight`:o=t+(a.key===`ArrowRight`?1:-1)*(e===`left`?16:-16);break;case`Home`:o=n;break;case`End`:o=r;break;case`Enter`:o=i;break;default:return}a.preventDefault(),p(o)},onDoubleClick:()=>p(i),onPointerDown:e=>{e.button===0&&(e.preventDefault(),d.current={pointerId:e.pointerId,startX:e.clientX,startValue:t},e.currentTarget.setPointerCapture(e.pointerId),l(!0))},onPointerMove:t=>{let n=d.current;if(!n||n.pointerId!==t.pointerId)return;let i=t.clientX-n.startX,c=n.startValue+(e===`left`?i:-i);if(s&&c>=r+48){f(t.currentTarget,t.pointerId),s();return}if(o&&a!==void 0&&c<=a){f(t.currentTarget,t.pointerId),o();return}p(c)},onPointerUp:e=>f(e.currentTarget,e.pointerId),onPointerCancel:e=>f(e.currentTarget,e.pointerId),onLostPointerCapture:()=>{d.current&&(d.current=null,l(!1))}})}function Kn(e,t){let n=t||{};return(e[e.length-1]===``?[...e,``]:e).join((n.padRight?` `:``)+`,`+(n.padLeft===!1?``:` `)).trim()}var qn=/^[$_\p{ID_Start}][$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,Jn=/^[$_\p{ID_Start}][-$_\u{200C}\u{200D}\p{ID_Continue}]*$/u,Yn={};function Xn(e,t){return((t||Yn).jsx?Jn:qn).test(e)}var Zn=/[ \t\n\f\r]/g;function Qn(e){return typeof e==`object`?e.type===`text`&&$n(e.value):$n(e)}function $n(e){return e.replace(Zn,``)===``}var er=class{constructor(e,t,n){this.normal=t,this.property=e,n&&(this.space=n)}};er.prototype.normal={},er.prototype.property={},er.prototype.space=void 0;function tr(e,t){let n={},r={};for(let t of e)Object.assign(n,t.property),Object.assign(r,t.normal);return new er(n,r,t)}function nr(e){return e.toLowerCase()}var rr=class{constructor(e,t){this.attribute=t,this.property=e}};rr.prototype.attribute=``,rr.prototype.booleanish=!1,rr.prototype.boolean=!1,rr.prototype.commaOrSpaceSeparated=!1,rr.prototype.commaSeparated=!1,rr.prototype.defined=!1,rr.prototype.mustUseProperty=!1,rr.prototype.number=!1,rr.prototype.overloadedBoolean=!1,rr.prototype.property=``,rr.prototype.spaceSeparated=!1,rr.prototype.space=void 0;var ir=s({boolean:()=>or,booleanish:()=>sr,commaOrSpaceSeparated:()=>dr,commaSeparated:()=>ur,number:()=>Y,overloadedBoolean:()=>cr,spaceSeparated:()=>lr}),ar=0,or=fr(),sr=fr(),cr=fr(),Y=fr(),lr=fr(),ur=fr(),dr=fr();function fr(){return 2**++ar}var pr=Object.keys(ir),mr=class extends rr{constructor(e,t,n,r){let i=-1;if(super(e,t),hr(this,`space`,r),typeof n==`number`)for(;++i4&&n.slice(0,4)===`data`&&Or.test(t)){if(t.charAt(4)===`-`){let e=t.slice(5).replace(Dr,jr);r=`data`+e.charAt(0).toUpperCase()+e.slice(1)}else{let e=t.slice(4);if(!Dr.test(e)){let n=e.replace(Er,Ar);n.charAt(0)!==`-`&&(n=`-`+n),t=`data`+n}}i=mr}return new i(r,t)}function Ar(e){return`-`+e.toLowerCase()}function jr(e){return e.charAt(1).toUpperCase()}var Mr=tr([_r,br,Sr,Cr,wr],`html`),Nr=tr([_r,xr,Sr,Cr,wr],`svg`);function Pr(e){return e.join(` `).trim()}var Fr=o(((e,t)=>{var n=/\/\*[^*]*\*+([^/*][^*]*\*+)*\//g,r=/\n/g,i=/^\s*/,a=/^(\*?[-#/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/,o=/^:\s*/,s=/^((?:'(?:\\'|.)*?'|"(?:\\"|.)*?"|\([^)]*?\)|[^};])+)/,c=/^[;\s]*/,l=/^\s+|\s+$/g;function u(e,t){if(typeof e!=`string`)throw TypeError(`First argument must be a string`);if(!e)return[];t||={};var l=1,u=1;function f(e){var t=e.match(r);t&&(l+=t.length);var n=e.lastIndexOf(` +`);u=~n?e.length-n:u+e.length}function p(){var e={line:l,column:u};return function(t){return t.position=new m(e),_(),t}}function m(e){this.start=e,this.end={line:l,column:u},this.source=t.source}m.prototype.content=e;function h(n){var r=Error(t.source+`:`+l+`:`+u+`: `+n);if(r.reason=n,r.filename=t.source,r.line=l,r.column=u,r.source=e,!t.silent)throw r}function g(t){var n=t.exec(e);if(n){var r=n[0];return f(r),e=e.slice(r.length),n}}function _(){g(i)}function v(e){var t;for(e||=[];t=y();)t!==!1&&e.push(t);return e}function y(){var t=p();if(e.charAt(0)==`/`&&e.charAt(1)==`*`){for(var n=2;e.charAt(n)!=``&&(e.charAt(n)!=`*`||e.charAt(n+1)!=`/`);)++n;if(n+=2,e.charAt(n-1)===``)return h(`End of comment missing`);var r=e.slice(2,n-2);return u+=2,f(r),e=e.slice(n),u+=2,t({type:`comment`,comment:r})}}function b(){var e=p(),t=g(a);if(t){if(y(),!g(o))return h(`property missing ':'`);var r=g(s),i=e({type:`declaration`,property:d(t[0].replace(n,``)),value:r?d(r[0].replace(n,``)):``});return g(c),i}}function x(){var e=[];v(e);for(var t;t=b();)t!==!1&&(e.push(t),v(e));return e}return _(),x()}function d(e){return e?e.replace(l,``):``}t.exports=u})),Ir=o((e=>{var t=e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(e,"__esModule",{value:!0}),e.default=r;var n=t(Fr());function r(e,t){let r=null;if(!e||typeof e!=`string`)return r;let i=(0,n.default)(e),a=typeof t==`function`;return i.forEach(e=>{if(e.type!==`declaration`)return;let{property:n,value:i}=e;a?t(n,i,e):i&&(r||={},r[n]=i)}),r}})),Lr=o((e=>{Object.defineProperty(e,"__esModule",{value:!0}),e.camelCase=void 0;var t=/^--[a-zA-Z0-9_-]+$/,n=/-([a-z])/g,r=/^[^-]+$/,i=/^-(webkit|moz|ms|o|khtml)-/,a=/^-(ms)-/,o=function(e){return!e||r.test(e)||t.test(e)},s=function(e,t){return t.toUpperCase()},c=function(e,t){return`${t}-`};e.camelCase=function(e,t){return t===void 0&&(t={}),o(e)?e:(e=e.toLowerCase(),e=t.reactCompat?e.replace(a,c):e.replace(i,c),e.replace(n,s))}})),Rr=o(((e,t)=>{var n=(e&&e.__importDefault||function(e){return e&&e.__esModule?e:{default:e}})(Ir()),r=Lr();function i(e,t){var i={};return!e||typeof e!=`string`||(0,n.default)(e,function(e,n){e&&n&&(i[(0,r.camelCase)(e,t)]=n)}),i}i.default=i,t.exports=i})),zr=Vr(`end`),Br=Vr(`start`);function Vr(e){return t;function t(t){let n=t&&t.position&&t.position[e]||{};if(typeof n.line==`number`&&n.line>0&&typeof n.column==`number`&&n.column>0)return{line:n.line,column:n.column,offset:typeof n.offset==`number`&&n.offset>-1?n.offset:void 0}}}function Hr(e){let t=Br(e),n=zr(e);if(t&&n)return{start:t,end:n}}function Ur(e){return!e||typeof e!=`object`?``:`position`in e||`type`in e?Gr(e.position):`start`in e||`end`in e?Gr(e):`line`in e||`column`in e?Wr(e):``}function Wr(e){return Kr(e&&e.line)+`:`+Kr(e&&e.column)}function Gr(e){return Wr(e&&e.start)+`-`+Wr(e&&e.end)}function Kr(e){return e&&typeof e==`number`?e:1}var qr=class extends Error{constructor(e,t,n){super(),typeof t==`string`&&(n=t,t=void 0);let r=``,i={},a=!1;if(t&&(i=`line`in t&&`column`in t||`start`in t&&`end`in t?{place:t}:`type`in t?{ancestors:[t],place:t.position}:{...t}),typeof e==`string`?r=e:!i.cause&&e&&(a=!0,r=e.message,i.cause=e),!i.ruleId&&!i.source&&typeof n==`string`){let e=n.indexOf(`:`);e===-1?i.ruleId=n:(i.source=n.slice(0,e),i.ruleId=n.slice(e+1))}if(!i.place&&i.ancestors&&i.ancestors){let e=i.ancestors[i.ancestors.length-1];e&&(i.place=e.position)}let o=i.place&&`start`in i.place?i.place.start:i.place;this.ancestors=i.ancestors||void 0,this.cause=i.cause||void 0,this.column=o?o.column:void 0,this.fatal=void 0,this.file=``,this.message=r,this.line=o?o.line:void 0,this.name=Ur(i.place)||`1:1`,this.place=i.place||void 0,this.reason=this.message,this.ruleId=i.ruleId||void 0,this.source=i.source||void 0,this.stack=a&&i.cause&&typeof i.cause.stack==`string`?i.cause.stack:``,this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}};qr.prototype.file=``,qr.prototype.name=``,qr.prototype.reason=``,qr.prototype.message=``,qr.prototype.stack=``,qr.prototype.column=void 0,qr.prototype.line=void 0,qr.prototype.ancestors=void 0,qr.prototype.cause=void 0,qr.prototype.fatal=void 0,qr.prototype.place=void 0,qr.prototype.ruleId=void 0,qr.prototype.source=void 0;var Jr=l(Rr(),1),Yr={}.hasOwnProperty,Xr=new Map,Zr=/[A-Z]/g,Qr=new Set([`table`,`tbody`,`thead`,`tfoot`,`tr`]),$r=new Set([`td`,`th`]);function ei(e,t){if(!t||t.Fragment===void 0)throw TypeError("Expected `Fragment` in options");let n=t.filePath||void 0,r;if(t.development){if(typeof t.jsxDEV!=`function`)throw TypeError("Expected `jsxDEV` in options when `development: true`");r=di(n,t.jsxDEV)}else{if(typeof t.jsx!=`function`)throw TypeError("Expected `jsx` in production options");if(typeof t.jsxs!=`function`)throw TypeError("Expected `jsxs` in production options");r=ui(n,t.jsx,t.jsxs)}let i={Fragment:t.Fragment,ancestors:[],components:t.components||{},create:r,elementAttributeNameCase:t.elementAttributeNameCase||`react`,evaluater:t.createEvaluater?t.createEvaluater():void 0,filePath:n,ignoreInvalidStyle:t.ignoreInvalidStyle||!1,passKeys:t.passKeys!==!1,passNode:t.passNode||!1,schema:t.space===`svg`?Nr:Mr,stylePropertyNameCase:t.stylePropertyNameCase||`dom`,tableCellAlignToStyle:t.tableCellAlignToStyle!==!1},a=ti(i,e,void 0);return a&&typeof a!=`string`?a:i.create(e,i.Fragment,{children:a||void 0},void 0)}function ti(e,t,n){if(t.type===`element`)return ni(e,t,n);if(t.type===`mdxFlowExpression`||t.type===`mdxTextExpression`)return ri(e,t);if(t.type===`mdxJsxFlowElement`||t.type===`mdxJsxTextElement`)return ai(e,t,n);if(t.type===`mdxjsEsm`)return ii(e,t);if(t.type===`root`)return oi(e,t,n);if(t.type===`text`)return si(e,t)}function ni(e,t,n){let r=e.schema,i=r;t.tagName.toLowerCase()===`svg`&&r.space===`html`&&(i=Nr,e.schema=i),e.ancestors.push(t);let a=_i(e,t.tagName,!1),o=fi(e,t),s=mi(e,t);return Qr.has(t.tagName)&&(s=s.filter(function(e){return typeof e!=`string`||!Qn(e)})),ci(e,o,a,t),li(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function ri(e,t){if(t.data&&t.data.estree&&e.evaluater){let n=t.data.estree.body[0];return n.type,e.evaluater.evaluateExpression(n.expression)}vi(e,t.position)}function ii(e,t){if(t.data&&t.data.estree&&e.evaluater)return e.evaluater.evaluateProgram(t.data.estree);vi(e,t.position)}function ai(e,t,n){let r=e.schema,i=r;t.name===`svg`&&r.space===`html`&&(i=Nr,e.schema=i),e.ancestors.push(t);let a=t.name===null?e.Fragment:_i(e,t.name,!0),o=pi(e,t),s=mi(e,t);return ci(e,o,a,t),li(o,s),e.ancestors.pop(),e.schema=r,e.create(t,a,o,n)}function oi(e,t,n){let r={};return li(r,mi(e,t)),e.create(t,e.Fragment,r,n)}function si(e,t){return t.value}function ci(e,t,n,r){typeof n!=`string`&&n!==e.Fragment&&e.passNode&&(t.node=r)}function li(e,t){if(t.length>0){let n=t.length>1?t:t[0];n&&(e.children=n)}}function ui(e,t,n){return r;function r(e,r,i,a){let o=Array.isArray(i.children)?n:t;return a?o(r,i,a):o(r,i)}}function di(e,t){return n;function n(n,r,i,a){let o=Array.isArray(i.children),s=Br(n);return t(r,i,a,o,{columnNumber:s?s.column-1:void 0,fileName:e,lineNumber:s?s.line:void 0},void 0)}}function fi(e,t){let n={},r,i;for(i in t.properties)if(i!==`children`&&Yr.call(t.properties,i)){let a=hi(e,i,t.properties[i]);if(a){let[i,o]=a;e.tableCellAlignToStyle&&i===`align`&&typeof o==`string`&&$r.has(t.tagName)?r=o:n[i]=o}}if(r){let t=n.style||={};t[e.stylePropertyNameCase===`css`?`text-align`:`textAlign`]=r}return n}function pi(e,t){let n={};for(let r of t.attributes)if(r.type===`mdxJsxExpressionAttribute`)if(r.data&&r.data.estree&&e.evaluater){let t=r.data.estree.body[0];t.type;let i=t.expression;i.type;let a=i.properties[0];a.type,Object.assign(n,e.evaluater.evaluateExpression(a.argument))}else vi(e,t.position);else{let i=r.name,a;if(r.value&&typeof r.value==`object`)if(r.value.data&&r.value.data.estree&&e.evaluater){let t=r.value.data.estree.body[0];t.type,a=e.evaluater.evaluateExpression(t.expression)}else vi(e,t.position);else a=r.value===null||r.value;n[i]=a}return n}function mi(e,t){let n=[],r=-1,i=e.passKeys?new Map:Xr;for(;++ri?0:i+t:t>i?i:t,n=n>0?n:0,r.length<1e4)o=Array.from(r),o.unshift(t,n),e.splice(...o);else for(n&&e.splice(t,n);a0?(Ai(e,e.length,0,t),e):t}var Mi={}.hasOwnProperty;function Ni(e){let t={},n=-1;for(;++n13&&n<32||n>126&&n<160||n>55295&&n<57344||n>64975&&n<65008||(n&65535)==65535||(n&65535)==65534||n>1114111?`�`:String.fromCodePoint(n)}function Li(e){return e.replace(/[\t\n\r ]+/g,` `).replace(/^ | $/g,``).toLowerCase().toUpperCase()}var Ri=Yi(/[A-Za-z]/),zi=Yi(/[\dA-Za-z]/),Bi=Yi(/[#-'*+\--9=?A-Z^-~]/);function Vi(e){return e!==null&&(e<32||e===127)}var Hi=Yi(/\d/),Ui=Yi(/[\dA-Fa-f]/),Wi=Yi(/[!-/:-@[-`{-~]/);function X(e){return e!==null&&e<-2}function Gi(e){return e!==null&&(e<0||e===32)}function Ki(e){return e===-2||e===-1||e===32}var qi=Yi(/\p{P}|\p{S}/u),Ji=Yi(/\s/);function Yi(e){return t;function t(t){return t!==null&&t>-1&&e.test(String.fromCharCode(t))}}function Xi(e){let t=[],n=-1,r=0,i=0;for(;++n55295&&a<57344){let t=e.charCodeAt(n+1);a<56320&&t>56319&&t<57344?(o=String.fromCharCode(a,t),i=1):o=`�`}else o=String.fromCharCode(a);o&&=(t.push(e.slice(r,n),encodeURIComponent(o)),r=n+i+1,``),i&&=(n+=i,0)}return t.join(``)+e.slice(r)}function Zi(e,t,n,r){let i=r?r-1:1/0,a=0;return o;function o(r){return Ki(r)?(e.enter(n),s(r)):t(r)}function s(r){return Ki(r)&&a++o))return;let n=t.events.length,a=n,s,c;for(;a--;)if(t.events[a][0]===`exit`&&t.events[a][1].type===`chunkFlow`){if(s){c=t.events[a][1].end;break}s=!0}for(_(r),e=n;er;){let r=n[i];t.containerState=r[1],r[0].exit.call(t,e)}n.length=r}function v(){i.write([null]),a=void 0,i=void 0,t.containerState._closeFlow=void 0}}function ra(e,t,n){return Zi(e,e.attempt(this.parser.constructs.document,t,n),`linePrefix`,this.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)}function ia(e){if(e===null||Gi(e)||Ji(e))return 1;if(qi(e))return 2}function aa(e,t,n){let r=[],i=-1;for(;++i1&&e[n][1].end.offset-e[n][1].start.offset>1?2:1;let d={...e[r][1].end},f={...e[n][1].start};la(d,-c),la(f,c),o={type:c>1?`strongSequence`:`emphasisSequence`,start:d,end:{...e[r][1].end}},s={type:c>1?`strongSequence`:`emphasisSequence`,start:{...e[n][1].start},end:f},a={type:c>1?`strongText`:`emphasisText`,start:{...e[r][1].end},end:{...e[n][1].start}},i={type:c>1?`strong`:`emphasis`,start:{...o.start},end:{...s.end}},e[r][1].end={...o.start},e[n][1].start={...s.end},l=[],e[r][1].end.offset-e[r][1].start.offset&&(l=ji(l,[[`enter`,e[r][1],t],[`exit`,e[r][1],t]])),l=ji(l,[[`enter`,i,t],[`enter`,o,t],[`exit`,o,t],[`enter`,a,t]]),l=ji(l,aa(t.parser.constructs.insideSpan.null,e.slice(r+1,n),t)),l=ji(l,[[`exit`,a,t],[`enter`,s,t],[`exit`,s,t],[`exit`,i,t]]),e[n][1].end.offset-e[n][1].start.offset?(u=2,l=ji(l,[[`enter`,e[n][1],t],[`exit`,e[n][1],t]])):u=0,Ai(e,r-1,n-r+3,l),n=r+l.length-u-2;break}}for(n=-1;++n0&&Ki(t)?Zi(e,v,`linePrefix`,a+1)(t):v(t)}function v(t){return t===null||X(t)?e.check(Sa,h,b)(t):(e.enter(`codeFlowValue`),y(t))}function y(t){return t===null||X(t)?(e.exit(`codeFlowValue`),v(t)):(e.consume(t),y)}function b(n){return e.exit(`codeFenced`),t(n)}function x(e,t,n){let i=0;return a;function a(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),c}function c(t){return e.enter(`codeFencedFence`),Ki(t)?Zi(e,l,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):l(t)}function l(t){return t===s?(e.enter(`codeFencedFenceSequence`),u(t)):n(t)}function u(t){return t===s?(i++,e.consume(t),u):i>=o?(e.exit(`codeFencedFenceSequence`),Ki(t)?Zi(e,d,`whitespace`)(t):d(t)):n(t)}function d(r){return r===null||X(r)?(e.exit(`codeFencedFence`),t(r)):n(r)}}}function Ta(e,t,n){let r=this;return i;function i(t){return t===null?n(t):(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}var Ea={name:`codeIndented`,tokenize:Oa},Da={partial:!0,tokenize:ka};function Oa(e,t,n){let r=this;return i;function i(t){return e.enter(`codeIndented`),Zi(e,a,`linePrefix`,5)(t)}function a(e){let t=r.events[r.events.length-1];return t&&t[1].type===`linePrefix`&&t[2].sliceSerialize(t[1],!0).length>=4?o(e):n(e)}function o(t){return t===null?c(t):X(t)?e.attempt(Da,o,c)(t):(e.enter(`codeFlowValue`),s(t))}function s(t){return t===null||X(t)?(e.exit(`codeFlowValue`),o(t)):(e.consume(t),s)}function c(n){return e.exit(`codeIndented`),t(n)}}function ka(e,t,n){let r=this;return i;function i(t){return r.parser.lazy[r.now().line]?n(t):X(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),i):Zi(e,a,`linePrefix`,5)(t)}function a(e){let a=r.events[r.events.length-1];return a&&a[1].type===`linePrefix`&&a[2].sliceSerialize(a[1],!0).length>=4?t(e):X(e)?i(e):n(e)}}var Aa={name:`codeText`,previous:Ma,resolve:ja,tokenize:Na};function ja(e){let t=e.length-4,n=3,r,i;if((e[n][1].type===`lineEnding`||e[n][1].type===`space`)&&(e[t][1].type===`lineEnding`||e[t][1].type===`space`)){for(r=n;++r=this.left.length+this.right.length)throw RangeError("Cannot access index `"+e+"` in a splice buffer of size `"+(this.left.length+this.right.length)+"`");return ethis.left.length?this.right.slice(this.right.length-n+this.left.length,this.right.length-e+this.left.length).reverse():this.left.slice(e).concat(this.right.slice(this.right.length-n+this.left.length).reverse())}splice(e,t,n){let r=t||0;this.setCursor(Math.trunc(e));let i=this.right.splice(this.right.length-r,1/0);return n&&Fa(this.left,n),i.reverse()}pop(){return this.setCursor(1/0),this.left.pop()}push(e){this.setCursor(1/0),this.left.push(e)}pushMany(e){this.setCursor(1/0),Fa(this.left,e)}unshift(e){this.setCursor(0),this.right.push(e)}unshiftMany(e){this.setCursor(0),Fa(this.right,e.reverse())}setCursor(e){if(!(e===this.left.length||e>this.left.length&&this.right.length===0||e<0&&this.left.length===0))if(e=4?t(i):e.interrupt(r.parser.constructs.flow,n,t)(i)}}function Ua(e,t,n,r,i,a,o,s,c){let l=c||1/0,u=0;return d;function d(t){return t===60?(e.enter(r),e.enter(i),e.enter(a),e.consume(t),e.exit(a),f):t===null||t===32||t===41||Vi(t)?n(t):(e.enter(r),e.enter(o),e.enter(s),e.enter(`chunkString`,{contentType:`string`}),h(t))}function f(n){return n===62?(e.enter(a),e.consume(n),e.exit(a),e.exit(i),e.exit(r),t):(e.enter(s),e.enter(`chunkString`,{contentType:`string`}),p(n))}function p(t){return t===62?(e.exit(`chunkString`),e.exit(s),f(t)):t===null||t===60||X(t)?n(t):(e.consume(t),t===92?m:p)}function m(t){return t===60||t===62||t===92?(e.consume(t),p):p(t)}function h(i){return!u&&(i===null||i===41||Gi(i))?(e.exit(`chunkString`),e.exit(s),e.exit(o),e.exit(r),t(i)):u999||l===null||l===91||l===93&&!c||l===94&&!s&&`_hiddenFootnoteSupport`in o.parser.constructs?n(l):l===93?(e.exit(a),e.enter(i),e.consume(l),e.exit(i),e.exit(r),t):X(l)?(e.enter(`lineEnding`),e.consume(l),e.exit(`lineEnding`),u):(e.enter(`chunkString`,{contentType:`string`}),d(l))}function d(t){return t===null||t===91||t===93||X(t)||s++>999?(e.exit(`chunkString`),u(t)):(e.consume(t),c||=!Ki(t),t===92?f:d)}function f(t){return t===91||t===92||t===93?(e.consume(t),s++,d):d(t)}}function Ga(e,t,n,r,i,a){let o;return s;function s(t){return t===34||t===39||t===40?(e.enter(r),e.enter(i),e.consume(t),e.exit(i),o=t===40?41:t,c):n(t)}function c(n){return n===o?(e.enter(i),e.consume(n),e.exit(i),e.exit(r),t):(e.enter(a),l(n))}function l(t){return t===o?(e.exit(a),c(o)):t===null?n(t):X(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),Zi(e,l,`linePrefix`)):(e.enter(`chunkString`,{contentType:`string`}),u(t))}function u(t){return t===o||t===null||X(t)?(e.exit(`chunkString`),l(t)):(e.consume(t),t===92?d:u)}function d(t){return t===o||t===92?(e.consume(t),u):u(t)}}function Ka(e,t){let n;return r;function r(i){return X(i)?(e.enter(`lineEnding`),e.consume(i),e.exit(`lineEnding`),n=!0,r):Ki(i)?Zi(e,r,n?`linePrefix`:`lineSuffix`)(i):t(i)}}var qa={name:`definition`,tokenize:Ya},Ja={partial:!0,tokenize:Xa};function Ya(e,t,n){let r=this,i;return a;function a(t){return e.enter(`definition`),o(t)}function o(t){return Wa.call(r,e,s,n,`definitionLabel`,`definitionLabelMarker`,`definitionLabelString`)(t)}function s(t){return i=Li(r.sliceSerialize(r.events[r.events.length-1][1]).slice(1,-1)),t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),c):n(t)}function c(t){return Gi(t)?Ka(e,l)(t):l(t)}function l(t){return Ua(e,u,n,`definitionDestination`,`definitionDestinationLiteral`,`definitionDestinationLiteralMarker`,`definitionDestinationRaw`,`definitionDestinationString`)(t)}function u(t){return e.attempt(Ja,d,d)(t)}function d(t){return Ki(t)?Zi(e,f,`whitespace`)(t):f(t)}function f(a){return a===null||X(a)?(e.exit(`definition`),r.parser.defined.push(i),t(a)):n(a)}}function Xa(e,t,n){return r;function r(t){return Gi(t)?Ka(e,i)(t):n(t)}function i(t){return Ga(e,a,n,`definitionTitle`,`definitionTitleMarker`,`definitionTitleString`)(t)}function a(t){return Ki(t)?Zi(e,o,`whitespace`)(t):o(t)}function o(e){return e===null||X(e)?t(e):n(e)}}var Za={name:`hardBreakEscape`,tokenize:Qa};function Qa(e,t,n){return r;function r(t){return e.enter(`hardBreakEscape`),e.consume(t),i}function i(r){return X(r)?(e.exit(`hardBreakEscape`),t(r)):n(r)}}var $a={name:`headingAtx`,resolve:eo,tokenize:to};function eo(e,t){let n=e.length-2,r=3,i,a;return e[r][1].type===`whitespace`&&(r+=2),n-2>r&&e[n][1].type===`whitespace`&&(n-=2),e[n][1].type===`atxHeadingSequence`&&(r===n-1||n-4>r&&e[n-2][1].type===`whitespace`)&&(n-=r+1===n?2:4),n>r&&(i={type:`atxHeadingText`,start:e[r][1].start,end:e[n][1].end},a={type:`chunkText`,start:e[r][1].start,end:e[n][1].end,contentType:`text`},Ai(e,r,n-r+1,[[`enter`,i,t],[`enter`,a,t],[`exit`,a,t],[`exit`,i,t]])),e}function to(e,t,n){let r=0;return i;function i(t){return e.enter(`atxHeading`),a(t)}function a(t){return e.enter(`atxHeadingSequence`),o(t)}function o(t){return t===35&&r++<6?(e.consume(t),o):t===null||Gi(t)?(e.exit(`atxHeadingSequence`),s(t)):n(t)}function s(n){return n===35?(e.enter(`atxHeadingSequence`),c(n)):n===null||X(n)?(e.exit(`atxHeading`),t(n)):Ki(n)?Zi(e,s,`whitespace`)(n):(e.enter(`atxHeadingText`),l(n))}function c(t){return t===35?(e.consume(t),c):(e.exit(`atxHeadingSequence`),s(t))}function l(t){return t===null||t===35||Gi(t)?(e.exit(`atxHeadingText`),s(t)):(e.consume(t),l)}}var no=`address.article.aside.base.basefont.blockquote.body.caption.center.col.colgroup.dd.details.dialog.dir.div.dl.dt.fieldset.figcaption.figure.footer.form.frame.frameset.h1.h2.h3.h4.h5.h6.head.header.hr.html.iframe.legend.li.link.main.menu.menuitem.nav.noframes.ol.optgroup.option.p.param.search.section.summary.table.tbody.td.tfoot.th.thead.title.tr.track.ul`.split(`.`),ro=[`pre`,`script`,`style`,`textarea`],io={concrete:!0,name:`htmlFlow`,resolveTo:so,tokenize:co},ao={partial:!0,tokenize:uo},oo={partial:!0,tokenize:lo};function so(e){let t=e.length;for(;t--&&(e[t][0]!==`enter`||e[t][1].type!==`htmlFlow`););return t>1&&e[t-2][1].type===`linePrefix`&&(e[t][1].start=e[t-2][1].start,e[t+1][1].start=e[t-2][1].start,e.splice(t-2,2)),e}function co(e,t,n){let r=this,i,a,o,s,c;return l;function l(e){return u(e)}function u(t){return e.enter(`htmlFlow`),e.enter(`htmlFlowData`),e.consume(t),d}function d(s){return s===33?(e.consume(s),f):s===47?(e.consume(s),a=!0,h):s===63?(e.consume(s),i=3,r.interrupt?t:I):Ri(s)?(e.consume(s),o=String.fromCharCode(s),g):n(s)}function f(a){return a===45?(e.consume(a),i=2,p):a===91?(e.consume(a),i=5,s=0,m):Ri(a)?(e.consume(a),i=4,r.interrupt?t:I):n(a)}function p(i){return i===45?(e.consume(i),r.interrupt?t:I):n(i)}function m(i){return i===`CDATA[`.charCodeAt(s++)?(e.consume(i),s===6?r.interrupt?t:O:m):n(i)}function h(t){return Ri(t)?(e.consume(t),o=String.fromCharCode(t),g):n(t)}function g(s){if(s===null||s===47||s===62||Gi(s)){let c=s===47,l=o.toLowerCase();return!c&&!a&&ro.includes(l)?(i=1,r.interrupt?t(s):O(s)):no.includes(o.toLowerCase())?(i=6,c?(e.consume(s),_):r.interrupt?t(s):O(s)):(i=7,r.interrupt&&!r.parser.lazy[r.now().line]?n(s):a?v(s):y(s))}return s===45||zi(s)?(e.consume(s),o+=String.fromCharCode(s),g):n(s)}function _(i){return i===62?(e.consume(i),r.interrupt?t:O):n(i)}function v(t){return Ki(t)?(e.consume(t),v):E(t)}function y(t){return t===47?(e.consume(t),E):t===58||t===95||Ri(t)?(e.consume(t),b):Ki(t)?(e.consume(t),y):E(t)}function b(t){return t===45||t===46||t===58||t===95||zi(t)?(e.consume(t),b):x(t)}function x(t){return t===61?(e.consume(t),S):Ki(t)?(e.consume(t),x):y(t)}function S(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),c=t,C):Ki(t)?(e.consume(t),S):w(t)}function C(t){return t===c?(e.consume(t),c=null,T):t===null||X(t)?n(t):(e.consume(t),C)}function w(t){return t===null||t===34||t===39||t===47||t===60||t===61||t===62||t===96||Gi(t)?x(t):(e.consume(t),w)}function T(e){return e===47||e===62||Ki(e)?y(e):n(e)}function E(t){return t===62?(e.consume(t),D):n(t)}function D(t){return t===null||X(t)?O(t):Ki(t)?(e.consume(t),D):n(t)}function O(t){return t===45&&i===2?(e.consume(t),M):t===60&&i===1?(e.consume(t),N):t===62&&i===4?(e.consume(t),L):t===63&&i===3?(e.consume(t),I):t===93&&i===5?(e.consume(t),F):X(t)&&(i===6||i===7)?(e.exit(`htmlFlowData`),e.check(ao,R,k)(t)):t===null||X(t)?(e.exit(`htmlFlowData`),k(t)):(e.consume(t),O)}function k(t){return e.check(oo,A,R)(t)}function A(t){return e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),j}function j(t){return t===null||X(t)?k(t):(e.enter(`htmlFlowData`),O(t))}function M(t){return t===45?(e.consume(t),I):O(t)}function N(t){return t===47?(e.consume(t),o=``,P):O(t)}function P(t){if(t===62){let n=o.toLowerCase();return ro.includes(n)?(e.consume(t),L):O(t)}return Ri(t)&&o.length<8?(e.consume(t),o+=String.fromCharCode(t),P):O(t)}function F(t){return t===93?(e.consume(t),I):O(t)}function I(t){return t===62?(e.consume(t),L):t===45&&i===2?(e.consume(t),I):O(t)}function L(t){return t===null||X(t)?(e.exit(`htmlFlowData`),R(t)):(e.consume(t),L)}function R(n){return e.exit(`htmlFlow`),t(n)}}function lo(e,t,n){let r=this;return i;function i(t){return X(t)?(e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),a):n(t)}function a(e){return r.parser.lazy[r.now().line]?n(e):t(e)}}function uo(e,t,n){return r;function r(r){return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),e.attempt(fa,t,n)}}var fo={name:`htmlText`,tokenize:po};function po(e,t,n){let r=this,i,a,o;return s;function s(t){return e.enter(`htmlText`),e.enter(`htmlTextData`),e.consume(t),c}function c(t){return t===33?(e.consume(t),l):t===47?(e.consume(t),x):t===63?(e.consume(t),y):Ri(t)?(e.consume(t),w):n(t)}function l(t){return t===45?(e.consume(t),u):t===91?(e.consume(t),a=0,m):Ri(t)?(e.consume(t),v):n(t)}function u(t){return t===45?(e.consume(t),p):n(t)}function d(t){return t===null?n(t):t===45?(e.consume(t),f):X(t)?(o=d,N(t)):(e.consume(t),d)}function f(t){return t===45?(e.consume(t),p):d(t)}function p(e){return e===62?M(e):e===45?f(e):d(e)}function m(t){return t===`CDATA[`.charCodeAt(a++)?(e.consume(t),a===6?h:m):n(t)}function h(t){return t===null?n(t):t===93?(e.consume(t),g):X(t)?(o=h,N(t)):(e.consume(t),h)}function g(t){return t===93?(e.consume(t),_):h(t)}function _(t){return t===62?M(t):t===93?(e.consume(t),_):h(t)}function v(t){return t===null||t===62?M(t):X(t)?(o=v,N(t)):(e.consume(t),v)}function y(t){return t===null?n(t):t===63?(e.consume(t),b):X(t)?(o=y,N(t)):(e.consume(t),y)}function b(e){return e===62?M(e):y(e)}function x(t){return Ri(t)?(e.consume(t),S):n(t)}function S(t){return t===45||zi(t)?(e.consume(t),S):C(t)}function C(t){return X(t)?(o=C,N(t)):Ki(t)?(e.consume(t),C):M(t)}function w(t){return t===45||zi(t)?(e.consume(t),w):t===47||t===62||Gi(t)?T(t):n(t)}function T(t){return t===47?(e.consume(t),M):t===58||t===95||Ri(t)?(e.consume(t),E):X(t)?(o=T,N(t)):Ki(t)?(e.consume(t),T):M(t)}function E(t){return t===45||t===46||t===58||t===95||zi(t)?(e.consume(t),E):D(t)}function D(t){return t===61?(e.consume(t),O):X(t)?(o=D,N(t)):Ki(t)?(e.consume(t),D):T(t)}function O(t){return t===null||t===60||t===61||t===62||t===96?n(t):t===34||t===39?(e.consume(t),i=t,k):X(t)?(o=O,N(t)):Ki(t)?(e.consume(t),O):(e.consume(t),A)}function k(t){return t===i?(e.consume(t),i=void 0,j):t===null?n(t):X(t)?(o=k,N(t)):(e.consume(t),k)}function A(t){return t===null||t===34||t===39||t===60||t===61||t===96?n(t):t===47||t===62||Gi(t)?T(t):(e.consume(t),A)}function j(e){return e===47||e===62||Gi(e)?T(e):n(e)}function M(r){return r===62?(e.consume(r),e.exit(`htmlTextData`),e.exit(`htmlText`),t):n(r)}function N(t){return e.exit(`htmlTextData`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),P}function P(t){return Ki(t)?Zi(e,F,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):F(t)}function F(t){return e.enter(`htmlTextData`),o(t)}}var mo={name:`labelEnd`,resolveAll:vo,resolveTo:yo,tokenize:bo},ho={tokenize:xo},go={tokenize:So},_o={tokenize:Co};function vo(e){let t=-1,n=[];for(;++t=3&&(a===null||X(a))?(e.exit(`thematicBreak`),t(a)):n(a)}function c(t){return t===i?(e.consume(t),r++,c):(e.exit(`thematicBreakSequence`),Ki(t)?Zi(e,s,`whitespace`)(t):s(t))}}var Mo={continuation:{tokenize:Io},exit:Ro,name:`list`,tokenize:Fo},No={partial:!0,tokenize:zo},Po={partial:!0,tokenize:Lo};function Fo(e,t,n){let r=this,i=r.events[r.events.length-1],a=i&&i[1].type===`linePrefix`?i[2].sliceSerialize(i[1],!0).length:0,o=0;return s;function s(t){let i=r.containerState.type||(t===42||t===43||t===45?`listUnordered`:`listOrdered`);if(i===`listUnordered`?!r.containerState.marker||t===r.containerState.marker:Hi(t)){if(r.containerState.type||(r.containerState.type=i,e.enter(i,{_container:!0})),i===`listUnordered`)return e.enter(`listItemPrefix`),t===42||t===45?e.check(Ao,n,l)(t):l(t);if(!r.interrupt||t===49)return e.enter(`listItemPrefix`),e.enter(`listItemValue`),c(t)}return n(t)}function c(t){return Hi(t)&&++o<10?(e.consume(t),c):(!r.interrupt||o<2)&&(r.containerState.marker?t===r.containerState.marker:t===41||t===46)?(e.exit(`listItemValue`),l(t)):n(t)}function l(t){return e.enter(`listItemMarker`),e.consume(t),e.exit(`listItemMarker`),r.containerState.marker=r.containerState.marker||t,e.check(fa,r.interrupt?n:u,e.attempt(No,f,d))}function u(e){return r.containerState.initialBlankLine=!0,a++,f(e)}function d(t){return Ki(t)?(e.enter(`listItemPrefixWhitespace`),e.consume(t),e.exit(`listItemPrefixWhitespace`),f):n(t)}function f(n){return r.containerState.size=a+r.sliceSerialize(e.exit(`listItemPrefix`),!0).length,t(n)}}function Io(e,t,n){let r=this;return r.containerState._closeFlow=void 0,e.check(fa,i,a);function i(n){return r.containerState.furtherBlankLines=r.containerState.furtherBlankLines||r.containerState.initialBlankLine,Zi(e,t,`listItemIndent`,r.containerState.size+1)(n)}function a(n){return r.containerState.furtherBlankLines||!Ki(n)?(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,o(n)):(r.containerState.furtherBlankLines=void 0,r.containerState.initialBlankLine=void 0,e.attempt(Po,t,o)(n))}function o(i){return r.containerState._closeFlow=!0,r.interrupt=void 0,Zi(e,e.attempt(Mo,t,n),`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(i)}}function Lo(e,t,n){let r=this;return Zi(e,i,`listItemIndent`,r.containerState.size+1);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`listItemIndent`&&i[2].sliceSerialize(i[1],!0).length===r.containerState.size?t(e):n(e)}}function Ro(e){e.exit(this.containerState.type)}function zo(e,t,n){let r=this;return Zi(e,i,`listItemPrefixWhitespace`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:5);function i(e){let i=r.events[r.events.length-1];return!Ki(e)&&i&&i[1].type===`listItemPrefixWhitespace`?t(e):n(e)}}var Bo={name:`setextUnderline`,resolveTo:Vo,tokenize:Ho};function Vo(e,t){let n=e.length,r,i,a;for(;n--;)if(e[n][0]===`enter`){if(e[n][1].type===`content`){r=n;break}e[n][1].type===`paragraph`&&(i=n)}else e[n][1].type===`content`&&e.splice(n,1),!a&&e[n][1].type===`definition`&&(a=n);let o={type:`setextHeading`,start:{...e[r][1].start},end:{...e[e.length-1][1].end}};return e[i][1].type=`setextHeadingText`,a?(e.splice(i,0,[`enter`,o,t]),e.splice(a+1,0,[`exit`,e[r][1],t]),e[r][1].end={...e[a][1].end}):e[r][1]=o,e.push([`exit`,o,t]),e}function Ho(e,t,n){let r=this,i;return a;function a(t){let a=r.events.length,s;for(;a--;)if(r.events[a][1].type!==`lineEnding`&&r.events[a][1].type!==`linePrefix`&&r.events[a][1].type!==`content`){s=r.events[a][1].type===`paragraph`;break}return!r.parser.lazy[r.now().line]&&(r.interrupt||s)?(e.enter(`setextHeadingLine`),i=t,o(t)):n(t)}function o(t){return e.enter(`setextHeadingLineSequence`),s(t)}function s(t){return t===i?(e.consume(t),s):(e.exit(`setextHeadingLineSequence`),Ki(t)?Zi(e,c,`lineSuffix`)(t):c(t))}function c(r){return r===null||X(r)?(e.exit(`setextHeadingLine`),t(r)):n(r)}}var Uo={tokenize:Wo};function Wo(e){let t=this,n=e.attempt(fa,r,e.attempt(this.parser.constructs.flowInitial,i,Zi(e,e.attempt(this.parser.constructs.flow,i,e.attempt(Ra,i)),`linePrefix`)));return n;function r(r){if(r===null){e.consume(r);return}return e.enter(`lineEndingBlank`),e.consume(r),e.exit(`lineEndingBlank`),t.currentConstruct=void 0,n}function i(r){if(r===null){e.consume(r);return}return e.enter(`lineEnding`),e.consume(r),e.exit(`lineEnding`),t.currentConstruct=void 0,n}}var Go={resolveAll:Yo()},Ko=Jo(`string`),qo=Jo(`text`);function Jo(e){return{resolveAll:Yo(e===`text`?Xo:void 0),tokenize:t};function t(t){let n=this,r=this.parser.constructs[e],i=t.attempt(r,a,o);return a;function a(e){return c(e)?i(e):o(e)}function o(e){if(e===null){t.consume(e);return}return t.enter(`data`),t.consume(e),s}function s(e){return c(e)?(t.exit(`data`),i(e)):(t.consume(e),s)}function c(e){if(e===null)return!0;let t=r[e],i=-1;if(t)for(;++ias,contentInitial:()=>$o,disable:()=>os,document:()=>Qo,flow:()=>ts,flowInitial:()=>es,insideSpan:()=>is,string:()=>ns,text:()=>rs}),Qo={42:Mo,43:Mo,45:Mo,48:Mo,49:Mo,50:Mo,51:Mo,52:Mo,53:Mo,54:Mo,55:Mo,56:Mo,57:Mo,62:ma},$o={91:qa},es={[-2]:Ea,[-1]:Ea,32:Ea},ts={35:$a,42:Ao,45:[Bo,Ao],60:io,61:Bo,95:Ao,96:Ca,126:Ca},ns={38:ba,92:va},rs={[-5]:Oo,[-4]:Oo,[-3]:Oo,33:wo,38:ba,42:oa,60:[ua,fo],91:Eo,92:[Za,va],93:mo,95:oa,96:Aa},is={null:[oa,Go]},as={null:[42,95]},os={null:[]};function ss(e,t,n){let r={_bufferIndex:-1,_index:0,line:n&&n.line||1,column:n&&n.column||1,offset:n&&n.offset||0},i={},a=[],o=[],s=[],c={attempt:C(x),check:C(S),consume:v,enter:y,exit:b,interrupt:C(S,{interrupt:!0})},l={code:null,containerState:{},defineSkip:h,events:[],now:m,parser:e,previous:null,sliceSerialize:f,sliceStream:p,write:d},u=t.tokenize.call(l,c);return t.resolveAll&&a.push(t),l;function d(e){return o=ji(o,e),g(),o[o.length-1]===null?(w(t,0),l.events=aa(a,l.events,l),l.events):[]}function f(e,t){return ls(p(e),t)}function p(e){return cs(o,e)}function m(){let{_bufferIndex:e,_index:t,line:n,column:i,offset:a}=r;return{_bufferIndex:e,_index:t,line:n,column:i,offset:a}}function h(e){i[e.line]=e.column,E()}function g(){let e;for(;r._index-1){let e=o[0];typeof e==`string`?o[0]=e.slice(r):o.shift()}a>0&&o.push(e[i].slice(0,a))}return o}function ls(e,t){let n=-1,r=[],i;for(;++n0){let e=a.tokenStack[a.tokenStack.length-1];(e[1]||Ts).call(a,void 0,e[0])}for(r.position={start:Ss(e.length>0?e[0][1].start:{line:1,column:1,offset:0}),end:Ss(e.length>0?e[e.length-2][1].end:{line:1,column:1,offset:0})},d=-1;++d0&&(r.className=[`language-`+i[0]]);let a={type:`element`,tagName:`code`,properties:r,children:[{type:`text`,value:n}]};return t.meta&&(a.data={meta:t.meta}),e.patch(t,a),a=e.applyData(t,a),a={type:`element`,tagName:`pre`,properties:{},children:[a]},e.patch(t,a),a}function As(e,t){let n={type:`element`,tagName:`del`,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function js(e,t){let n={type:`element`,tagName:`em`,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function Ms(e,t){let n=typeof e.options.clobberPrefix==`string`?e.options.clobberPrefix:`user-content-`,r=String(t.identifier).toUpperCase(),i=Qi(r.toLowerCase()),a=e.footnoteOrder.indexOf(r),o,s=e.footnoteCounts.get(r);s===void 0?(s=0,e.footnoteOrder.push(r),o=e.footnoteOrder.length):o=a+1,s+=1,e.footnoteCounts.set(r,s);let c={type:`element`,tagName:`a`,properties:{href:`#`+n+`fn-`+i,id:n+`fnref-`+i+(s>1?`-`+s:``),dataFootnoteRef:!0,ariaDescribedBy:[`footnote-label`]},children:[{type:`text`,value:String(o)}]};e.patch(t,c);let l={type:`element`,tagName:`sup`,properties:{},children:[c]};return e.patch(t,l),e.applyData(t,l)}function Ns(e,t){let n={type:`element`,tagName:`h`+t.depth,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function Ps(e,t){if(e.options.allowDangerousHtml){let n={type:`raw`,value:t.value};return e.patch(t,n),e.applyData(t,n)}}function Fs(e,t){let n=t.referenceType,r=`]`;if(n===`collapsed`?r+=`[]`:n===`full`&&(r+=`[`+(t.label||t.identifier)+`]`),t.type===`imageReference`)return[{type:`text`,value:`![`+t.alt+r}];let i=e.all(t),a=i[0];a&&a.type===`text`?a.value=`[`+a.value:i.unshift({type:`text`,value:`[`});let o=i[i.length-1];return o&&o.type===`text`?o.value+=r:i.push({type:`text`,value:r}),i}function Is(e,t){let n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return Fs(e,t);let i={src:Qi(r.url||``),alt:t.alt};r.title!==null&&r.title!==void 0&&(i.title=r.title);let a={type:`element`,tagName:`img`,properties:i,children:[]};return e.patch(t,a),e.applyData(t,a)}function Ls(e,t){let n={src:Qi(t.url)};t.alt!==null&&t.alt!==void 0&&(n.alt=t.alt),t.title!==null&&t.title!==void 0&&(n.title=t.title);let r={type:`element`,tagName:`img`,properties:n,children:[]};return e.patch(t,r),e.applyData(t,r)}function Rs(e,t){let n={type:`text`,value:t.value.replace(/\r?\n|\r/g,` `)};e.patch(t,n);let r={type:`element`,tagName:`code`,properties:{},children:[n]};return e.patch(t,r),e.applyData(t,r)}function zs(e,t){let n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return Fs(e,t);let i={href:Qi(r.url||``)};r.title!==null&&r.title!==void 0&&(i.title=r.title);let a={type:`element`,tagName:`a`,properties:i,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function Bs(e,t){let n={href:Qi(t.url)};t.title!==null&&t.title!==void 0&&(n.title=t.title);let r={type:`element`,tagName:`a`,properties:n,children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Vs(e,t,n){let r=e.all(t),i=n?Hs(n):Us(t),a={},o=[];if(typeof t.checked==`boolean`){let e=r[0],n;e&&e.type===`element`&&e.tagName===`p`?n=e:(n={type:`element`,tagName:`p`,properties:{},children:[]},r.unshift(n)),n.children.length>0&&n.children.unshift({type:`text`,value:` `}),n.children.unshift({type:`element`,tagName:`input`,properties:{type:`checkbox`,checked:t.checked,disabled:!0},children:[]}),a.className=[`task-list-item`]}let s=-1;for(;++s0){let e=a.tokenStack[a.tokenStack.length-1];(e[1]||Cs).call(a,void 0,e[0])}for(r.position={start:bs(e.length>0?e[0][1].start:{line:1,column:1,offset:0}),end:bs(e.length>0?e[e.length-2][1].end:{line:1,column:1,offset:0})},d=-1;++d0&&(r.className=[`language-`+i[0]]);let a={type:`element`,tagName:`code`,properties:r,children:[{type:`text`,value:n}]};return t.meta&&(a.data={meta:t.meta}),e.patch(t,a),a=e.applyData(t,a),a={type:`element`,tagName:`pre`,properties:{},children:[a]},e.patch(t,a),a}function Os(e,t){let n={type:`element`,tagName:`del`,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function ks(e,t){let n={type:`element`,tagName:`em`,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function As(e,t){let n=typeof e.options.clobberPrefix==`string`?e.options.clobberPrefix:`user-content-`,r=String(t.identifier).toUpperCase(),i=Xi(r.toLowerCase()),a=e.footnoteOrder.indexOf(r),o,s=e.footnoteCounts.get(r);s===void 0?(s=0,e.footnoteOrder.push(r),o=e.footnoteOrder.length):o=a+1,s+=1,e.footnoteCounts.set(r,s);let c={type:`element`,tagName:`a`,properties:{href:`#`+n+`fn-`+i,id:n+`fnref-`+i+(s>1?`-`+s:``),dataFootnoteRef:!0,ariaDescribedBy:[`footnote-label`]},children:[{type:`text`,value:String(o)}]};e.patch(t,c);let l={type:`element`,tagName:`sup`,properties:{},children:[c]};return e.patch(t,l),e.applyData(t,l)}function js(e,t){let n={type:`element`,tagName:`h`+t.depth,properties:{},children:e.all(t)};return e.patch(t,n),e.applyData(t,n)}function Ms(e,t){if(e.options.allowDangerousHtml){let n={type:`raw`,value:t.value};return e.patch(t,n),e.applyData(t,n)}}function Ns(e,t){let n=t.referenceType,r=`]`;if(n===`collapsed`?r+=`[]`:n===`full`&&(r+=`[`+(t.label||t.identifier)+`]`),t.type===`imageReference`)return[{type:`text`,value:`![`+t.alt+r}];let i=e.all(t),a=i[0];a&&a.type===`text`?a.value=`[`+a.value:i.unshift({type:`text`,value:`[`});let o=i[i.length-1];return o&&o.type===`text`?o.value+=r:i.push({type:`text`,value:r}),i}function Ps(e,t){let n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return Ns(e,t);let i={src:Xi(r.url||``),alt:t.alt};r.title!==null&&r.title!==void 0&&(i.title=r.title);let a={type:`element`,tagName:`img`,properties:i,children:[]};return e.patch(t,a),e.applyData(t,a)}function Fs(e,t){let n={src:Xi(t.url)};t.alt!==null&&t.alt!==void 0&&(n.alt=t.alt),t.title!==null&&t.title!==void 0&&(n.title=t.title);let r={type:`element`,tagName:`img`,properties:n,children:[]};return e.patch(t,r),e.applyData(t,r)}function Is(e,t){let n={type:`text`,value:t.value.replace(/\r?\n|\r/g,` `)};e.patch(t,n);let r={type:`element`,tagName:`code`,properties:{},children:[n]};return e.patch(t,r),e.applyData(t,r)}function Ls(e,t){let n=String(t.identifier).toUpperCase(),r=e.definitionById.get(n);if(!r)return Ns(e,t);let i={href:Xi(r.url||``)};r.title!==null&&r.title!==void 0&&(i.title=r.title);let a={type:`element`,tagName:`a`,properties:i,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function Rs(e,t){let n={href:Xi(t.url)};t.title!==null&&t.title!==void 0&&(n.title=t.title);let r={type:`element`,tagName:`a`,properties:n,children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function zs(e,t,n){let r=e.all(t),i=n?Bs(n):Vs(t),a={},o=[];if(typeof t.checked==`boolean`){let e=r[0],n;e&&e.type===`element`&&e.tagName===`p`?n=e:(n={type:`element`,tagName:`p`,properties:{},children:[]},r.unshift(n)),n.children.length>0&&n.children.unshift({type:`text`,value:` `}),n.children.unshift({type:`element`,tagName:`input`,properties:{type:`checkbox`,checked:t.checked,disabled:!0},children:[]}),a.className=[`task-list-item`]}let s=-1;for(;++s1}function Ws(e,t){let n={},r=e.all(t),i=-1;for(typeof t.start==`number`&&t.start!==1&&(n.start=t.start);++i0){let r={type:`element`,tagName:`tbody`,properties:{},children:e.wrap(n,!0)},a=Hr(t.children[1]),o=Vr(t.children[t.children.length-1]);a&&o&&(r.position={start:a,end:o}),i.push(r)}let a={type:`element`,tagName:`table`,properties:{},children:e.wrap(i,!0)};return e.patch(t,a),e.applyData(t,a)}function Ys(e,t,n){let r=n?n.children:void 0,i=(r?r.indexOf(t):1)===0?`th`:`td`,a=n&&n.type===`table`?n.align:void 0,o=a?a.length:t.children.length,s=-1,c=[];for(;++s0,!0),r[0]),i=r.index+r[0].length,r=n.exec(t);return a.push(ec(t.slice(i),i>0,!1)),a.join(``)}function ec(e,t,n){let r=0,i=e.length;if(t){let t=e.codePointAt(r);for(;t===Zs||t===Qs;)r++,t=e.codePointAt(r)}if(n){let t=e.codePointAt(i-1);for(;t===Zs||t===Qs;)i--,t=e.codePointAt(i-1)}return i>r?e.slice(r,i):``}function tc(e,t){let n={type:`text`,value:$s(String(t.value))};return e.patch(t,n),e.applyData(t,n)}function nc(e,t){let n={type:`element`,tagName:`hr`,properties:{},children:[]};return e.patch(t,n),e.applyData(t,n)}var rc={blockquote:Ds,break:Os,code:ks,delete:As,emphasis:js,footnoteReference:Ms,heading:Ns,html:Ps,imageReference:Is,image:Ls,inlineCode:Rs,linkReference:zs,link:Bs,listItem:Vs,list:Ws,paragraph:Gs,root:Ks,strong:qs,table:Js,tableCell:Xs,tableRow:Ys,text:tc,thematicBreak:nc,toml:ic,yaml:ic,definition:ic,footnoteDefinition:ic};function ic(){}var{defineProperty:ac}=Object,oc=typeof self==`object`?self:globalThis,sc=(e,t)=>{switch(e){case`Function`:case`SharedWorker`:case`Worker`:case`eval`:case`setInterval`:case`setTimeout`:throw TypeError(`unable to deserialize `+e)}return new oc[e](t)},cc=(e,t)=>{let n=(t,n)=>(e.set(n,t),t),r=i=>{if(e.has(i))return e.get(i);let[a,o]=t[i];switch(a){case 0:case-1:return n(o,i);case 1:{let e=n([],i);for(let t of o)e.push(r(t));return e}case 2:{let e=n({},i);for(let[t,n]of o){let i=r(t),a=r(n);i===`__proto__`?ac(e,i,{value:a,configurable:!0,enumerable:!0,writable:!0}):e[i]=a}return e}case 3:return n(new Date(o),i);case 4:{let{source:e,flags:t}=o;return n(new RegExp(e,t),i)}case 5:{let e=n(new Map,i);for(let[t,n]of o)e.set(r(t),r(n));return e}case 6:{let e=n(new Set,i);for(let t of o)e.add(r(t));return e}case 7:{let{name:e,message:t}=o;return n(typeof oc[e]==`function`?sc(e,t):Error(t),i)}case 8:return n(BigInt(o),i);case`BigInt`:return n(Object(BigInt(o)),i);case`ArrayBuffer`:return n(new Uint8Array(o).buffer,o);case`DataView`:{let{buffer:e}=new Uint8Array(o);return n(new DataView(e),o)}case`-0`:return-0}return n(sc(a,o),i)};return r},lc=e=>cc(new Map,e)(0),uc=``,{toString:dc}={},{keys:fc,is:pc}=Object,mc=e=>{let t=typeof e;if(t!==`object`||!e)return[0,t];let n=dc.call(e).slice(8,-1);switch(n){case`Array`:return[1,uc];case`Object`:return[2,uc];case`Date`:return[3,uc];case`RegExp`:return[4,uc];case`Map`:return[5,uc];case`Set`:return[6,uc];case`DataView`:return[1,n]}return n.includes(`Array`)?[1,n]:e instanceof Error?[7,e.name||`Error`]:[2,n]},hc=([e,t])=>e===0&&(t===`function`||t===`symbol`),gc=(e,t,n,r)=>{let i=(e,t)=>{let i=r.push(e)-1;return n.set(t,i),i},a=o=>{if(n.has(o))return n.get(o);let[s,c]=mc(o);switch(s){case 0:{let t=o;switch(c){case`bigint`:s=8,t=o.toString();break;case`number`:if(!o&&pc(o,-0))return r.push([`-0`])-1;break;case`function`:case`symbol`:if(e)throw TypeError(`unable to serialize `+c);t=null;break;case`undefined`:return i([-1],o)}return i([s,t],o)}case 1:{if(c){let e=o;return c===`DataView`?e=new Uint8Array(o.buffer):c===`ArrayBuffer`&&(e=new Uint8Array(o)),i([c,[...e]],o)}let e=[],t=i([s,e],o);for(let t of o)e.push(a(t));return t}case 2:{if(c)switch(c){case`BigInt`:return i([c,o.toString()],o);case`Boolean`:case`Number`:case`String`:return i([c,o.valueOf()],o)}if(t&&`toJSON`in o)return a(o.toJSON());let n=[],r=i([s,n],o);for(let t of fc(o))(e||!hc(mc(o[t])))&&n.push([a(t),a(o[t])]);return r}case 3:return i([s,isNaN(o.getTime())?uc:o.toISOString()],o);case 4:{let{source:e,flags:t}=o;return i([s,{source:e,flags:t}],o)}case 5:{let t=[],n=i([s,t],o);for(let[n,r]of o)(e||!(hc(mc(n))||hc(mc(r))))&&t.push([a(n),a(r)]);return n}case 6:{let t=[],n=i([s,t],o);for(let n of o)(e||!hc(mc(n)))&&t.push(a(n));return n}}let{message:l}=o;return i([s,{name:c,message:l}],o)};return a},_c=(e,{json:t,lossy:n}={})=>{let r=[];return gc(!(t||n),!!t,new Map,r)(e),r},vc=typeof structuredClone==`function`?(e,t)=>t&&(`json`in t||`lossy`in t)?lc(_c(e,t)):structuredClone(e):(e,t)=>lc(_c(e,t));function yc(e,t){let n=[{type:`text`,value:`↩`}];return t>1&&n.push({type:`element`,tagName:`sup`,properties:{},children:[{type:`text`,value:String(t)}]}),n}function bc(e,t){return`Back to reference `+(e+1)+(t>1?`-`+t:``)}function xc(e){let t=typeof e.options.clobberPrefix==`string`?e.options.clobberPrefix:`user-content-`,n=e.options.footnoteBackContent||yc,r=e.options.footnoteBackLabel||bc,i=e.options.footnoteLabel||`Footnotes`,a=e.options.footnoteLabelTagName||`h2`,o=e.options.footnoteLabelProperties||{className:[`sr-only`]},s=[],c=-1;for(;++c0&&d.push({type:`text`,value:` `});let e=typeof n==`string`?n:n(c,u);typeof e==`string`&&(e={type:`text`,value:e}),d.push({type:`element`,tagName:`a`,properties:{href:`#`+t+`fnref-`+l+(u>1?`-`+u:``),dataFootnoteBackref:``,ariaLabel:typeof r==`string`?r:r(c,u),className:[`data-footnote-backref`]},children:Array.isArray(e)?e:[e]})}let p=a[a.length-1];if(p&&p.type===`element`&&p.tagName===`p`){let e=p.children[p.children.length-1];e&&e.type===`text`?e.value+=` `:p.children.push({type:`text`,value:` `}),p.children.push(...d)}else a.push(...d);let m={type:`element`,tagName:`li`,properties:{id:t+`fn-`+l},children:e.wrap(a,!0)};e.patch(i,m),s.push(m)}if(s.length!==0)return{type:`element`,tagName:`section`,properties:{dataFootnotes:!0,className:[`footnotes`]},children:[{type:`element`,tagName:a,properties:{...vc(o),id:`footnote-label`},children:[{type:`text`,value:i}]},{type:`text`,value:` +`});let l={type:`element`,tagName:`li`,properties:a,children:o};return e.patch(t,l),e.applyData(t,l)}function Bs(e){let t=!1;if(e.type===`list`){t=e.spread||!1;let n=e.children,r=-1;for(;!t&&++r1}function Hs(e,t){let n={},r=e.all(t),i=-1;for(typeof t.start==`number`&&t.start!==1&&(n.start=t.start);++i0){let r={type:`element`,tagName:`tbody`,properties:{},children:e.wrap(n,!0)},a=Br(t.children[1]),o=zr(t.children[t.children.length-1]);a&&o&&(r.position={start:a,end:o}),i.push(r)}let a={type:`element`,tagName:`table`,properties:{},children:e.wrap(i,!0)};return e.patch(t,a),e.applyData(t,a)}function qs(e,t,n){let r=n?n.children:void 0,i=(r?r.indexOf(t):1)===0?`th`:`td`,a=n&&n.type===`table`?n.align:void 0,o=a?a.length:t.children.length,s=-1,c=[];for(;++s0,!0),r[0]),i=r.index+r[0].length,r=n.exec(t);return a.push(Qs(t.slice(i),i>0,!1)),a.join(``)}function Qs(e,t,n){let r=0,i=e.length;if(t){let t=e.codePointAt(r);for(;t===Ys||t===Xs;)r++,t=e.codePointAt(r)}if(n){let t=e.codePointAt(i-1);for(;t===Ys||t===Xs;)i--,t=e.codePointAt(i-1)}return i>r?e.slice(r,i):``}function $s(e,t){let n={type:`text`,value:Zs(String(t.value))};return e.patch(t,n),e.applyData(t,n)}function ec(e,t){let n={type:`element`,tagName:`hr`,properties:{},children:[]};return e.patch(t,n),e.applyData(t,n)}var tc={blockquote:Ts,break:Es,code:Ds,delete:Os,emphasis:ks,footnoteReference:As,heading:js,html:Ms,imageReference:Ps,image:Fs,inlineCode:Is,linkReference:Ls,link:Rs,listItem:zs,list:Hs,paragraph:Us,root:Ws,strong:Gs,table:Ks,tableCell:Js,tableRow:qs,text:$s,thematicBreak:ec,toml:nc,yaml:nc,definition:nc,footnoteDefinition:nc};function nc(){}var{defineProperty:rc}=Object,ic=typeof self==`object`?self:globalThis,ac=(e,t)=>{switch(e){case`Function`:case`SharedWorker`:case`Worker`:case`eval`:case`setInterval`:case`setTimeout`:throw TypeError(`unable to deserialize `+e)}return new ic[e](t)},oc=(e,t)=>{let n=(t,n)=>(e.set(n,t),t),r=i=>{if(e.has(i))return e.get(i);let[a,o]=t[i];switch(a){case 0:case-1:return n(o,i);case 1:{let e=n([],i);for(let t of o)e.push(r(t));return e}case 2:{let e=n({},i);for(let[t,n]of o){let i=r(t),a=r(n);i===`__proto__`?rc(e,i,{value:a,configurable:!0,enumerable:!0,writable:!0}):e[i]=a}return e}case 3:return n(new Date(o),i);case 4:{let{source:e,flags:t}=o;return n(new RegExp(e,t),i)}case 5:{let e=n(new Map,i);for(let[t,n]of o)e.set(r(t),r(n));return e}case 6:{let e=n(new Set,i);for(let t of o)e.add(r(t));return e}case 7:{let{name:e,message:t}=o;return n(typeof ic[e]==`function`?ac(e,t):Error(t),i)}case 8:return n(BigInt(o),i);case`BigInt`:return n(Object(BigInt(o)),i);case`ArrayBuffer`:return n(new Uint8Array(o).buffer,o);case`DataView`:{let{buffer:e}=new Uint8Array(o);return n(new DataView(e),o)}case`-0`:return-0}return n(ac(a,o),i)};return r},sc=e=>oc(new Map,e)(0),cc=``,{toString:lc}={},{keys:uc,is:dc}=Object,fc=e=>{let t=typeof e;if(t!==`object`||!e)return[0,t];let n=lc.call(e).slice(8,-1);switch(n){case`Array`:return[1,cc];case`Object`:return[2,cc];case`Date`:return[3,cc];case`RegExp`:return[4,cc];case`Map`:return[5,cc];case`Set`:return[6,cc];case`DataView`:return[1,n]}return n.includes(`Array`)?[1,n]:e instanceof Error?[7,e.name||`Error`]:[2,n]},pc=([e,t])=>e===0&&(t===`function`||t===`symbol`),mc=(e,t,n,r)=>{let i=(e,t)=>{let i=r.push(e)-1;return n.set(t,i),i},a=o=>{if(n.has(o))return n.get(o);let[s,c]=fc(o);switch(s){case 0:{let t=o;switch(c){case`bigint`:s=8,t=o.toString();break;case`number`:if(!o&&dc(o,-0))return r.push([`-0`])-1;break;case`function`:case`symbol`:if(e)throw TypeError(`unable to serialize `+c);t=null;break;case`undefined`:return i([-1],o)}return i([s,t],o)}case 1:{if(c){let e=o;return c===`DataView`?e=new Uint8Array(o.buffer):c===`ArrayBuffer`&&(e=new Uint8Array(o)),i([c,[...e]],o)}let e=[],t=i([s,e],o);for(let t of o)e.push(a(t));return t}case 2:{if(c)switch(c){case`BigInt`:return i([c,o.toString()],o);case`Boolean`:case`Number`:case`String`:return i([c,o.valueOf()],o)}if(t&&`toJSON`in o)return a(o.toJSON());let n=[],r=i([s,n],o);for(let t of uc(o))(e||!pc(fc(o[t])))&&n.push([a(t),a(o[t])]);return r}case 3:return i([s,isNaN(o.getTime())?cc:o.toISOString()],o);case 4:{let{source:e,flags:t}=o;return i([s,{source:e,flags:t}],o)}case 5:{let t=[],n=i([s,t],o);for(let[n,r]of o)(e||!(pc(fc(n))||pc(fc(r))))&&t.push([a(n),a(r)]);return n}case 6:{let t=[],n=i([s,t],o);for(let n of o)(e||!pc(fc(n)))&&t.push(a(n));return n}}let{message:l}=o;return i([s,{name:c,message:l}],o)};return a},hc=(e,{json:t,lossy:n}={})=>{let r=[];return mc(!(t||n),!!t,new Map,r)(e),r},gc=typeof structuredClone==`function`?(e,t)=>t&&(`json`in t||`lossy`in t)?sc(hc(e,t)):structuredClone(e):(e,t)=>sc(hc(e,t));function _c(e,t){let n=[{type:`text`,value:`↩`}];return t>1&&n.push({type:`element`,tagName:`sup`,properties:{},children:[{type:`text`,value:String(t)}]}),n}function vc(e,t){return`Back to reference `+(e+1)+(t>1?`-`+t:``)}function yc(e){let t=typeof e.options.clobberPrefix==`string`?e.options.clobberPrefix:`user-content-`,n=e.options.footnoteBackContent||_c,r=e.options.footnoteBackLabel||vc,i=e.options.footnoteLabel||`Footnotes`,a=e.options.footnoteLabelTagName||`h2`,o=e.options.footnoteLabelProperties||{className:[`sr-only`]},s=[],c=-1;for(;++c0&&d.push({type:`text`,value:` `});let e=typeof n==`string`?n:n(c,u);typeof e==`string`&&(e={type:`text`,value:e}),d.push({type:`element`,tagName:`a`,properties:{href:`#`+t+`fnref-`+l+(u>1?`-`+u:``),dataFootnoteBackref:``,ariaLabel:typeof r==`string`?r:r(c,u),className:[`data-footnote-backref`]},children:Array.isArray(e)?e:[e]})}let p=a[a.length-1];if(p&&p.type===`element`&&p.tagName===`p`){let e=p.children[p.children.length-1];e&&e.type===`text`?e.value+=` `:p.children.push({type:`text`,value:` `}),p.children.push(...d)}else a.push(...d);let m={type:`element`,tagName:`li`,properties:{id:t+`fn-`+l},children:e.wrap(a,!0)};e.patch(i,m),s.push(m)}if(s.length!==0)return{type:`element`,tagName:`section`,properties:{dataFootnotes:!0,className:[`footnotes`]},children:[{type:`element`,tagName:a,properties:{...gc(o),id:`footnote-label`},children:[{type:`text`,value:i}]},{type:`text`,value:` `},{type:`element`,tagName:`ol`,properties:{},children:e.wrap(s,!0)},{type:`text`,value:` -`}]}}var Sc=(function(e){if(e==null)return Dc;if(typeof e==`function`)return Ec(e);if(typeof e==`object`)return Array.isArray(e)?Cc(e):wc(e);if(typeof e==`string`)return Tc(e);throw Error(`Expected function, string, or object as test`)});function Cc(e){let t=[],n=-1;for(;++n`:``))+`)`})}return u;function u(){let l=Ac,u,d,f;if((!t||a(e,i,c[c.length-1]||void 0))&&(l=Mc(n(e,c)),l[0]===!1))return l;if(`children`in e&&e.children){let t=e;if(t.children&&l[0]!==`skip`)for(d=(r?t.children.length:-1)+o,f=c.concat(t);d>-1&&d`:``))+`)`})}return u;function u(){let l=Oc,u,d,f;if((!t||a(e,i,c[c.length-1]||void 0))&&(l=Ac(n(e,c)),l[0]===!1))return l;if(`children`in e&&e.children){let t=e;if(t.children&&l[0]!==`skip`)for(d=(r?t.children.length:-1)+o,f=c.concat(t);d>-1&&d0&&n.push({type:`text`,value:` -`}),n}function Vc(e){let t=0,n=e.charCodeAt(t);for(;n===9||n===32;)t++,n=e.charCodeAt(t);return e.slice(t)}function Hc(e,t){let n=Ic(e,t),r=n.one(e,void 0),i=xc(n),a=Array.isArray(r)?{type:`root`,children:r}:r||{type:`root`,children:[]};return i&&(`children`in a,a.children.push({type:`text`,value:` -`},i)),a}function Uc(e,t){return e&&`run`in e?async function(n,r){let i=Hc(n,{file:r,...t});await e.run(i,r)}:function(n,r){return Hc(n,{file:r,...e||t})}}function Wc(e){if(e)throw e}var Gc=o(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=Object.prototype.toString,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(e){return typeof Array.isArray==`function`?Array.isArray(e):r.call(e)===`[object Array]`},s=function(e){if(!e||r.call(e)!==`[object Object]`)return!1;var t=n.call(e,`constructor`),i=e.constructor&&e.constructor.prototype&&n.call(e.constructor.prototype,`isPrototypeOf`);if(e.constructor&&!t&&!i)return!1;for(var a in e);return a===void 0||n.call(e,a)},c=function(e,t){i&&t.name===`__proto__`?i(e,t.name,{enumerable:!0,configurable:!0,value:t.newValue,writable:!0}):e[t.name]=t.newValue},l=function(e,t){if(t===`__proto__`){if(!n.call(e,t))return;if(a)return a(e,t).value}return e[t]};t.exports=function e(){var t,n,r,i,a,u,d=arguments[0],f=1,p=arguments.length,m=!1;for(typeof d==`boolean`&&(m=d,d=arguments[1]||{},f=2),(d==null||typeof d!=`object`&&typeof d!=`function`)&&(d={});ft.length,o;r&&t.push(i);try{o=e.apply(this,t)}catch(e){let t=e;if(r&&n)throw t;return i(t)}r||(o&&o.then&&typeof o.then==`function`?o.then(a,i):o instanceof Error?i(o):a(o))}function i(e,...r){n||(n=!0,t(e,...r))}function a(e){i(null,e)}}var Yc={basename:Xc,dirname:Zc,extname:Qc,join:$c,sep:`/`};function Xc(e,t){if(t!==void 0&&typeof t!=`string`)throw TypeError(`"ext" argument must be a string`);nl(e);let n=0,r=-1,i=e.length,a;if(t===void 0||t.length===0||t.length>e.length){for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else r<0&&(a=!0,r=i+1);return r<0?``:e.slice(n,r)}if(t===e)return``;let o=-1,s=t.length-1;for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else o<0&&(a=!0,o=i+1),s>-1&&(e.codePointAt(i)===t.codePointAt(s--)?s<0&&(r=i):(s=-1,r=o));return n===r?r=o:r<0&&(r=e.length),e.slice(n,r)}function Zc(e){if(nl(e),e.length===0)return`.`;let t=-1,n=e.length,r;for(;--n;)if(e.codePointAt(n)===47){if(r){t=n;break}}else r||=!0;return t<0?e.codePointAt(0)===47?`/`:`.`:t===1&&e.codePointAt(0)===47?`//`:e.slice(0,t)}function Qc(e){nl(e);let t=e.length,n=-1,r=0,i=-1,a=0,o;for(;t--;){let s=e.codePointAt(t);if(s===47){if(o){r=t+1;break}continue}n<0&&(o=!0,n=t+1),s===46?i<0?i=t:a!==1&&(a=1):i>-1&&(a=-1)}return i<0||n<0||a===0||a===1&&i===n-1&&i===r+1?``:e.slice(i,n)}function $c(...e){let t=-1,n;for(;++t0&&e.codePointAt(e.length-1)===47&&(n+=`/`),t?`/`+n:n}function tl(e,t){let n=``,r=0,i=-1,a=0,o=-1,s,c;for(;++o<=e.length;){if(o2){if(c=n.lastIndexOf(`/`),c!==n.length-1){c<0?(n=``,r=0):(n=n.slice(0,c),r=n.length-1-n.lastIndexOf(`/`)),i=o,a=0;continue}}else if(n.length>0){n=``,r=0,i=o,a=0;continue}}t&&(n=n.length>0?n+`/..`:`..`,r=2)}else n.length>0?n+=`/`+e.slice(i+1,o):n=e.slice(i+1,o),r=o-i-1;i=o,a=0}else s===46&&a>-1?a++:a=-1}return n}function nl(e){if(typeof e!=`string`)throw TypeError(`Path must be a string. Received `+JSON.stringify(e))}var rl={cwd:il};function il(){return`/`}function al(e){return!!(typeof e==`object`&&e&&`href`in e&&e.href&&`protocol`in e&&e.protocol&&e.auth===void 0)}function ol(e){if(typeof e==`string`)e=new URL(e);else if(!al(e)){let t=TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code=`ERR_INVALID_ARG_TYPE`,t}if(e.protocol!==`file:`){let e=TypeError(`The URL must be of scheme file`);throw e.code=`ERR_INVALID_URL_SCHEME`,e}return sl(e)}function sl(e){if(e.hostname!==``){let e=TypeError(`File URL host must be "localhost" or empty on darwin`);throw e.code=`ERR_INVALID_FILE_URL_HOST`,e}let t=e.pathname,n=-1;for(;++n0){let[r,...a]=t,o=n[i][1];Kc(o)&&Kc(r)&&(r=(0,hl.default)(!0,o,r)),n[i]=[e,r,...a]}}}}().freeze();function vl(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `parser`")}function yl(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `compiler`")}function bl(e,t){if(t)throw Error("Cannot call `"+e+"` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.")}function xl(e){if(!Kc(e)||typeof e.type!=`string`)throw TypeError("Expected node, got `"+e+"`")}function Sl(e,t,n){if(!n)throw Error("`"+e+"` finished async. Use `"+t+"` instead")}function Cl(e){return wl(e)?e:new ll(e)}function wl(e){return!!(e&&typeof e==`object`&&`message`in e&&`messages`in e)}function Tl(e){return typeof e==`string`||El(e)}function El(e){return!!(e&&typeof e==`object`&&`byteLength`in e&&`byteOffset`in e)}var Dl=[],Ol={allowDangerousHtml:!0},kl=/^(https?|ircs?|mailto|xmpp)$/i,Al=[{from:`astPlugins`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowDangerousHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowNode`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowElement`},{from:`allowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowedElements`},{from:`className`,id:`remove-classname`},{from:`disallowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`disallowedElements`},{from:`escapeHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`includeElementIndex`,id:`#remove-includeelementindex`},{from:`includeNodeIndex`,id:`change-includenodeindex-to-includeelementindex`},{from:`linkTarget`,id:`remove-linktarget`},{from:`plugins`,id:`change-plugins-to-remarkplugins`,to:`remarkPlugins`},{from:`rawSourcePos`,id:`#remove-rawsourcepos`},{from:`renderers`,id:`change-renderers-to-components`,to:`components`},{from:`source`,id:`change-source-to-children`,to:`children`},{from:`sourcePos`,id:`#remove-sourcepos`},{from:`transformImageUri`,id:`#add-urltransform`,to:`urlTransform`},{from:`transformLinkUri`,id:`#add-urltransform`,to:`urlTransform`}];function jl(e){let t=Ml(e),n=Nl(e);return Pl(t.runSync(t.parse(n),n),e)}function Ml(e){let t=e.rehypePlugins||Dl,n=e.remarkPlugins||Dl,r=e.remarkRehypeOptions?{...e.remarkRehypeOptions,...Ol}:Ol;return _l().use(Es).use(n).use(Uc,r).use(t)}function Nl(e){let t=e.children||``,n=new ll;return typeof t==`string`?n.value=t:``+t,n}function Pl(e,t){let n=t.allowedElements,r=t.allowElement,i=t.components,a=t.disallowedElements,o=t.skipHtml,s=t.unwrapDisallowed,c=t.urlTransform||Fl;for(let e of Al)Object.hasOwn(t,e.from)&&``+e.from+(e.to?"use `"+e.to+"` instead":`remove it`)+e.id;return Nc(e,l),ni(e,{Fragment:K.Fragment,components:i,ignoreInvalidStyle:!0,jsx:K.jsx,jsxs:K.jsxs,passKeys:!0,passNode:!0});function l(e,t,i){if(e.type===`raw`&&i&&typeof t==`number`)return o?i.children.splice(t,1):i.children[t]={type:`text`,value:e.value},t;if(e.type===`element`){let t;for(t in wi)if(Object.hasOwn(wi,t)&&Object.hasOwn(e.properties,t)){let n=e.properties[t],r=wi[t];(r===null||r.includes(e.tagName))&&(e.properties[t]=c(String(n||``),t,e))}}if(e.type===`element`){let o=n?!n.includes(e.tagName):a?a.includes(e.tagName):!1;if(!o&&r&&typeof t==`number`&&(o=!r(e,t,i)),o&&i&&typeof t==`number`)return s&&e.children?i.children.splice(t,1,...e.children):i.children.splice(t,1),t}}}function Fl(e){let t=e.indexOf(`:`),n=e.indexOf(`?`),r=e.indexOf(`#`),i=e.indexOf(`/`);return t===-1||i!==-1&&t>i||n!==-1&&t>n||r!==-1&&t>r||kl.test(e.slice(0,t))?e:``}var Il=[`ariaDescribedBy`,`ariaLabel`,`ariaLabelledBy`],Ll={ancestors:{tbody:[`table`],td:[`table`],th:[`table`],thead:[`table`],tfoot:[`table`],tr:[`table`]},attributes:{a:[...Il,`dataFootnoteBackref`,`dataFootnoteRef`,[`className`,`data-footnote-backref`],`href`],blockquote:[`cite`],code:[[`className`,/^language-./]],del:[`cite`],div:[`itemScope`,`itemType`],dl:[...Il],h2:[[`className`,`sr-only`]],img:[...Il,`longDesc`,`src`],input:[[`disabled`,!0],[`type`,`checkbox`]],ins:[`cite`],li:[[`className`,`task-list-item`]],ol:[...Il,[`className`,`contains-task-list`]],q:[`cite`],section:[`dataFootnotes`,[`className`,`footnotes`]],source:[`srcSet`],summary:[...Il],table:[...Il],ul:[...Il,[`className`,`contains-task-list`]],"*":`abbr.accept.acceptCharset.accessKey.action.align.alt.axis.border.cellPadding.cellSpacing.char.charOff.charSet.checked.clear.colSpan.color.cols.compact.coords.dateTime.dir.encType.frame.hSpace.headers.height.hrefLang.htmlFor.id.isMap.itemProp.label.lang.maxLength.media.method.multiple.name.noHref.noShade.noWrap.open.prompt.readOnly.rev.rowSpan.rows.rules.scope.selected.shape.size.span.start.summary.tabIndex.title.useMap.vAlign.value.width`.split(`.`)},clobber:[`ariaDescribedBy`,`ariaLabelledBy`,`id`,`name`],clobberPrefix:`user-content-`,protocols:{cite:[`http`,`https`],href:[`http`,`https`,`irc`,`ircs`,`mailto`,`xmpp`],longDesc:[`http`,`https`],src:[`http`,`https`]},required:{input:{disabled:!0,type:`checkbox`}},strip:[`script`],tagNames:`a.b.blockquote.br.code.dd.del.details.div.dl.dt.em.h1.h2.h3.h4.h5.h6.hr.i.img.input.ins.kbd.li.ol.p.picture.pre.q.rp.rt.ruby.s.samp.section.source.span.strike.strong.sub.summary.sup.table.tbody.td.tfoot.th.thead.tr.tt.ul.var`.split(`.`)},Rl={}.hasOwnProperty;function zl(e,t){let n={type:`root`,children:[]},r=Bl({schema:t?{...Ll,...t}:Ll,stack:[]},e);return r&&(Array.isArray(r)?r.length===1?n=r[0]:n.children=r:n=r),n}function Bl(e,t){if(t&&typeof t==`object`){let n=t;switch(typeof n.type==`string`?n.type:``){case`comment`:return Vl(e,n);case`doctype`:return Hl(e,n);case`element`:return Ul(e,n);case`root`:return Wl(e,n);case`text`:return Gl(e,n)}}}function Vl(e,t){if(e.schema.allowComments){let e=typeof t.value==`string`?t.value:``,n=e.indexOf(`-->`),r={type:`comment`,value:n<0?e:e.slice(0,n)};return Ql(r,t),r}}function Hl(e,t){if(e.schema.allowDoctypes){let e={type:`doctype`};return Ql(e,t),e}}function Ul(e,t){let n=typeof t.tagName==`string`?t.tagName:``;e.stack.push(n);let r=Kl(e,t.children),i=ql(e,t.properties);e.stack.pop();let a=!1;if(n&&n!==`*`&&(!e.schema.tagNames||e.schema.tagNames.includes(n))&&(a=!0,e.schema.ancestors&&Rl.call(e.schema.ancestors,n))){let t=e.schema.ancestors[n],r=-1;for(a=!1;++r1){let e=!1,n=0;for(;++n-1&&a>c||o>-1&&a>o||s>-1&&a>s)return!0;let l=-1;for(;++l4&&t.slice(0,4).toLowerCase()===`data`)return n}function eu(e){return function(t){return zl(t,e)}}function tu(e){if(typeof e!=`string`)throw TypeError(`Expected a string`);return e.replace(/[|\\{}()[\]^$+*?.]/g,`\\$&`).replace(/-/g,`\\x2d`)}function nu(e,t,n){let r=Sc((n||{}).ignore||[]),i=ru(t),a=-1;for(;++a0?{type:`text`,value:a}:void 0),a===!1?r.lastIndex=n+1:(s!==n&&u.push({type:`text`,value:e.value.slice(s,n)}),Array.isArray(a)?u.push(...a):a&&u.push(a),s=n+d[0].length,l=!0),!r.global)break;d=r.exec(e.value)}return l?(s?\]}]+$/.exec(e);if(!t)return[e,void 0];e=e.slice(0,t.index);let n=t[0],r=n.indexOf(`)`),i=lu(e,`(`),a=lu(e,`)`);for(;r!==-1&&i>a;)e+=n.slice(0,r+1),n=n.slice(r+1),r=n.indexOf(`)`),a++;return[e,n]}function Tu(e,t){let n=e.input.charCodeAt(e.index-1);return(e.index===0||Xi(n)||Yi(n))&&(!t||n!==47)}Fu.peek=Pu;function Eu(){this.buffer()}function Du(e){this.enter({type:`footnoteReference`,identifier:``,label:``},e)}function Ou(){this.buffer()}function ku(e){this.enter({type:`footnoteDefinition`,identifier:``,label:``,children:[]},e)}function Au(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=zi(this.sliceSerialize(e)).toLowerCase(),n.label=t}function ju(e){this.exit(e)}function Mu(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=zi(this.sliceSerialize(e)).toLowerCase(),n.label=t}function Nu(e){this.exit(e)}function Pu(){return`[`}function Fu(e,t,n,r){let i=n.createTracker(r),a=i.move(`[^`),o=n.enter(`footnoteReference`),s=n.enter(`reference`);return a+=i.move(n.safe(n.associationId(e),{after:`]`,before:a})),s(),o(),a+=i.move(`]`),a}function Iu(){return{enter:{gfmFootnoteCallString:Eu,gfmFootnoteCall:Du,gfmFootnoteDefinitionLabelString:Ou,gfmFootnoteDefinition:ku},exit:{gfmFootnoteCallString:Au,gfmFootnoteCall:ju,gfmFootnoteDefinitionLabelString:Mu,gfmFootnoteDefinition:Nu}}}function Lu(e){let t=!1;return e&&e.firstLineBlank&&(t=!0),{handlers:{footnoteDefinition:n,footnoteReference:Fu},unsafe:[{character:`[`,inConstruct:[`label`,`phrasing`,`reference`]}]};function n(e,n,r,i){let a=r.createTracker(i),o=a.move(`[^`),s=r.enter(`footnoteDefinition`),c=r.enter(`label`);return o+=a.move(r.safe(r.associationId(e),{before:o,after:`]`})),c(),o+=a.move(`]:`),e.children&&e.children.length>0&&(a.shift(4),o+=a.move((t?` -`:` `)+r.indentLines(r.containerFlow(e,a.current()),t?zu:Ru))),s(),o}}function Ru(e,t,n){return t===0?e:zu(e,t,n)}function zu(e,t,n){return(n?``:` `)+e}var Bu=[`autolink`,`destinationLiteral`,`destinationRaw`,`reference`,`titleQuote`,`titleApostrophe`];Gu.peek=Ku;function Vu(){return{canContainEols:[`delete`],enter:{strikethrough:Uu},exit:{strikethrough:Wu}}}function Hu(){return{unsafe:[{character:`~`,inConstruct:`phrasing`,notInConstruct:Bu}],handlers:{delete:Gu}}}function Uu(e){this.enter({type:`delete`,children:[]},e)}function Wu(e){this.exit(e)}function Gu(e,t,n,r){let i=n.createTracker(r),a=n.enter(`strikethrough`),o=i.move(`~~`);return o+=n.containerPhrasing(e,{...i.current(),before:o,after:`~`}),o+=i.move(`~~`),a(),o}function Ku(){return`~`}function qu(e){return e.length}function Ju(e,t){let n=t||{},r=(n.align||[]).concat(),i=n.stringLength||qu,a=[],o=[],s=[],c=[],l=0,u=-1;for(;++ul&&(l=e[u].length);++ac[a])&&(c[a]=e)}t.push(o)}o[u]=t,s[u]=r}let d=-1;if(typeof r==`object`&&`length`in r)for(;++dc[d]&&(c[d]=i),p[d]=i),f[d]=o}o.splice(1,0,f),s.splice(1,0,p),u=-1;let m=[];for(;++u `),a.shift(2);let o=n.indentLines(n.containerFlow(e,a.current()),Qu);return i(),o}function Qu(e,t,n){return`>`+(n?``:` `)+e}function $u(e,t){return ed(e,t.inConstruct,!0)&&!ed(e,t.notInConstruct,!1)}function ed(e,t,n){if(typeof t==`string`&&(t=[t]),!t||t.length===0)return n;let r=-1;for(;++ro&&(o=a):a=1,i=r+t.length,r=n.indexOf(t,i);return o}function rd(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function id(e){let t=e.options.fence||"`";if(t!=="`"&&t!==`~`)throw Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function ad(e,t,n,r){let i=id(n),a=e.value||``,o=i==="`"?`GraveAccent`:`Tilde`;if(rd(e,n)){let e=n.enter(`codeIndented`),t=n.indentLines(a,od);return e(),t}let s=n.createTracker(r),c=i.repeat(Math.max(nd(a,i)+1,3)),l=n.enter(`codeFenced`),u=s.move(c);if(e.lang){let t=n.enter(`codeFencedLang${o}`);u+=s.move(n.safe(e.lang,{before:u,after:` `,encode:["`"],...s.current()})),t()}if(e.lang&&e.meta){let t=n.enter(`codeFencedMeta${o}`);u+=s.move(` `),u+=s.move(n.safe(e.meta,{before:u,after:` +`}),n}function zc(e){let t=0,n=e.charCodeAt(t);for(;n===9||n===32;)t++,n=e.charCodeAt(t);return e.slice(t)}function Bc(e,t){let n=Pc(e,t),r=n.one(e,void 0),i=yc(n),a=Array.isArray(r)?{type:`root`,children:r}:r||{type:`root`,children:[]};return i&&(`children`in a,a.children.push({type:`text`,value:` +`},i)),a}function Vc(e,t){return e&&`run`in e?async function(n,r){let i=Bc(n,{file:r,...t});await e.run(i,r)}:function(n,r){return Bc(n,{file:r,...e||t})}}function Hc(e){if(e)throw e}var Uc=o(((e,t)=>{var n=Object.prototype.hasOwnProperty,r=Object.prototype.toString,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(e){return typeof Array.isArray==`function`?Array.isArray(e):r.call(e)===`[object Array]`},s=function(e){if(!e||r.call(e)!==`[object Object]`)return!1;var t=n.call(e,`constructor`),i=e.constructor&&e.constructor.prototype&&n.call(e.constructor.prototype,`isPrototypeOf`);if(e.constructor&&!t&&!i)return!1;for(var a in e);return a===void 0||n.call(e,a)},c=function(e,t){i&&t.name===`__proto__`?i(e,t.name,{enumerable:!0,configurable:!0,value:t.newValue,writable:!0}):e[t.name]=t.newValue},l=function(e,t){if(t===`__proto__`){if(!n.call(e,t))return;if(a)return a(e,t).value}return e[t]};t.exports=function e(){var t,n,r,i,a,u,d=arguments[0],f=1,p=arguments.length,m=!1;for(typeof d==`boolean`&&(m=d,d=arguments[1]||{},f=2),(d==null||typeof d!=`object`&&typeof d!=`function`)&&(d={});ft.length,o;r&&t.push(i);try{o=e.apply(this,t)}catch(e){let t=e;if(r&&n)throw t;return i(t)}r||(o&&o.then&&typeof o.then==`function`?o.then(a,i):o instanceof Error?i(o):a(o))}function i(e,...r){n||(n=!0,t(e,...r))}function a(e){i(null,e)}}var qc={basename:Jc,dirname:Yc,extname:Xc,join:Zc,sep:`/`};function Jc(e,t){if(t!==void 0&&typeof t!=`string`)throw TypeError(`"ext" argument must be a string`);el(e);let n=0,r=-1,i=e.length,a;if(t===void 0||t.length===0||t.length>e.length){for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else r<0&&(a=!0,r=i+1);return r<0?``:e.slice(n,r)}if(t===e)return``;let o=-1,s=t.length-1;for(;i--;)if(e.codePointAt(i)===47){if(a){n=i+1;break}}else o<0&&(a=!0,o=i+1),s>-1&&(e.codePointAt(i)===t.codePointAt(s--)?s<0&&(r=i):(s=-1,r=o));return n===r?r=o:r<0&&(r=e.length),e.slice(n,r)}function Yc(e){if(el(e),e.length===0)return`.`;let t=-1,n=e.length,r;for(;--n;)if(e.codePointAt(n)===47){if(r){t=n;break}}else r||=!0;return t<0?e.codePointAt(0)===47?`/`:`.`:t===1&&e.codePointAt(0)===47?`//`:e.slice(0,t)}function Xc(e){el(e);let t=e.length,n=-1,r=0,i=-1,a=0,o;for(;t--;){let s=e.codePointAt(t);if(s===47){if(o){r=t+1;break}continue}n<0&&(o=!0,n=t+1),s===46?i<0?i=t:a!==1&&(a=1):i>-1&&(a=-1)}return i<0||n<0||a===0||a===1&&i===n-1&&i===r+1?``:e.slice(i,n)}function Zc(...e){let t=-1,n;for(;++t0&&e.codePointAt(e.length-1)===47&&(n+=`/`),t?`/`+n:n}function $c(e,t){let n=``,r=0,i=-1,a=0,o=-1,s,c;for(;++o<=e.length;){if(o2){if(c=n.lastIndexOf(`/`),c!==n.length-1){c<0?(n=``,r=0):(n=n.slice(0,c),r=n.length-1-n.lastIndexOf(`/`)),i=o,a=0;continue}}else if(n.length>0){n=``,r=0,i=o,a=0;continue}}t&&(n=n.length>0?n+`/..`:`..`,r=2)}else n.length>0?n+=`/`+e.slice(i+1,o):n=e.slice(i+1,o),r=o-i-1;i=o,a=0}else s===46&&a>-1?a++:a=-1}return n}function el(e){if(typeof e!=`string`)throw TypeError(`Path must be a string. Received `+JSON.stringify(e))}var tl={cwd:nl};function nl(){return`/`}function rl(e){return!!(typeof e==`object`&&e&&`href`in e&&e.href&&`protocol`in e&&e.protocol&&e.auth===void 0)}function il(e){if(typeof e==`string`)e=new URL(e);else if(!rl(e)){let t=TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code=`ERR_INVALID_ARG_TYPE`,t}if(e.protocol!==`file:`){let e=TypeError(`The URL must be of scheme file`);throw e.code=`ERR_INVALID_URL_SCHEME`,e}return al(e)}function al(e){if(e.hostname!==``){let e=TypeError(`File URL host must be "localhost" or empty on darwin`);throw e.code=`ERR_INVALID_FILE_URL_HOST`,e}let t=e.pathname,n=-1;for(;++n0){let[r,...a]=t,o=n[i][1];Wc(o)&&Wc(r)&&(r=(0,pl.default)(!0,o,r)),n[i]=[e,r,...a]}}}}().freeze();function gl(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `parser`")}function _l(e,t){if(typeof t!=`function`)throw TypeError("Cannot `"+e+"` without `compiler`")}function vl(e,t){if(t)throw Error("Cannot call `"+e+"` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`.")}function yl(e){if(!Wc(e)||typeof e.type!=`string`)throw TypeError("Expected node, got `"+e+"`")}function bl(e,t,n){if(!n)throw Error("`"+e+"` finished async. Use `"+t+"` instead")}function xl(e){return Sl(e)?e:new sl(e)}function Sl(e){return!!(e&&typeof e==`object`&&`message`in e&&`messages`in e)}function Cl(e){return typeof e==`string`||wl(e)}function wl(e){return!!(e&&typeof e==`object`&&`byteLength`in e&&`byteOffset`in e)}var Tl=[],El={allowDangerousHtml:!0},Dl=/^(https?|ircs?|mailto|xmpp)$/i,Ol=[{from:`astPlugins`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowDangerousHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`allowNode`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowElement`},{from:`allowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`allowedElements`},{from:`className`,id:`remove-classname`},{from:`disallowedTypes`,id:`replace-allownode-allowedtypes-and-disallowedtypes`,to:`disallowedElements`},{from:`escapeHtml`,id:`remove-buggy-html-in-markdown-parser`},{from:`includeElementIndex`,id:`#remove-includeelementindex`},{from:`includeNodeIndex`,id:`change-includenodeindex-to-includeelementindex`},{from:`linkTarget`,id:`remove-linktarget`},{from:`plugins`,id:`change-plugins-to-remarkplugins`,to:`remarkPlugins`},{from:`rawSourcePos`,id:`#remove-rawsourcepos`},{from:`renderers`,id:`change-renderers-to-components`,to:`components`},{from:`source`,id:`change-source-to-children`,to:`children`},{from:`sourcePos`,id:`#remove-sourcepos`},{from:`transformImageUri`,id:`#add-urltransform`,to:`urlTransform`},{from:`transformLinkUri`,id:`#add-urltransform`,to:`urlTransform`}];function kl(e){let t=Al(e),n=jl(e);return Ml(t.runSync(t.parse(n),n),e)}function Al(e){let t=e.rehypePlugins||Tl,n=e.remarkPlugins||Tl,r=e.remarkRehypeOptions?{...e.remarkRehypeOptions,...El}:El;return hl().use(ws).use(n).use(Vc,r).use(t)}function jl(e){let t=e.children||``,n=new sl;return typeof t==`string`?n.value=t:``+t,n}function Ml(e,t){let n=t.allowedElements,r=t.allowElement,i=t.components,a=t.disallowedElements,o=t.skipHtml,s=t.unwrapDisallowed,c=t.urlTransform||Nl;for(let e of Ol)Object.hasOwn(t,e.from)&&``+e.from+(e.to?"use `"+e.to+"` instead":`remove it`)+e.id;return jc(e,l),ei(e,{Fragment:J.Fragment,components:i,ignoreInvalidStyle:!0,jsx:J.jsx,jsxs:J.jsxs,passKeys:!0,passNode:!0});function l(e,t,i){if(e.type===`raw`&&i&&typeof t==`number`)return o?i.children.splice(t,1):i.children[t]={type:`text`,value:e.value},t;if(e.type===`element`){let t;for(t in Si)if(Object.hasOwn(Si,t)&&Object.hasOwn(e.properties,t)){let n=e.properties[t],r=Si[t];(r===null||r.includes(e.tagName))&&(e.properties[t]=c(String(n||``),t,e))}}if(e.type===`element`){let o=n?!n.includes(e.tagName):a?a.includes(e.tagName):!1;if(!o&&r&&typeof t==`number`&&(o=!r(e,t,i)),o&&i&&typeof t==`number`)return s&&e.children?i.children.splice(t,1,...e.children):i.children.splice(t,1),t}}}function Nl(e){let t=e.indexOf(`:`),n=e.indexOf(`?`),r=e.indexOf(`#`),i=e.indexOf(`/`);return t===-1||i!==-1&&t>i||n!==-1&&t>n||r!==-1&&t>r||Dl.test(e.slice(0,t))?e:``}var Pl=[`ariaDescribedBy`,`ariaLabel`,`ariaLabelledBy`],Fl={ancestors:{tbody:[`table`],td:[`table`],th:[`table`],thead:[`table`],tfoot:[`table`],tr:[`table`]},attributes:{a:[...Pl,`dataFootnoteBackref`,`dataFootnoteRef`,[`className`,`data-footnote-backref`],`href`],blockquote:[`cite`],code:[[`className`,/^language-./]],del:[`cite`],div:[`itemScope`,`itemType`],dl:[...Pl],h2:[[`className`,`sr-only`]],img:[...Pl,`longDesc`,`src`],input:[[`disabled`,!0],[`type`,`checkbox`]],ins:[`cite`],li:[[`className`,`task-list-item`]],ol:[...Pl,[`className`,`contains-task-list`]],q:[`cite`],section:[`dataFootnotes`,[`className`,`footnotes`]],source:[`srcSet`],summary:[...Pl],table:[...Pl],ul:[...Pl,[`className`,`contains-task-list`]],"*":`abbr.accept.acceptCharset.accessKey.action.align.alt.axis.border.cellPadding.cellSpacing.char.charOff.charSet.checked.clear.colSpan.color.cols.compact.coords.dateTime.dir.encType.frame.hSpace.headers.height.hrefLang.htmlFor.id.isMap.itemProp.label.lang.maxLength.media.method.multiple.name.noHref.noShade.noWrap.open.prompt.readOnly.rev.rowSpan.rows.rules.scope.selected.shape.size.span.start.summary.tabIndex.title.useMap.vAlign.value.width`.split(`.`)},clobber:[`ariaDescribedBy`,`ariaLabelledBy`,`id`,`name`],clobberPrefix:`user-content-`,protocols:{cite:[`http`,`https`],href:[`http`,`https`,`irc`,`ircs`,`mailto`,`xmpp`],longDesc:[`http`,`https`],src:[`http`,`https`]},required:{input:{disabled:!0,type:`checkbox`}},strip:[`script`],tagNames:`a.b.blockquote.br.code.dd.del.details.div.dl.dt.em.h1.h2.h3.h4.h5.h6.hr.i.img.input.ins.kbd.li.ol.p.picture.pre.q.rp.rt.ruby.s.samp.section.source.span.strike.strong.sub.summary.sup.table.tbody.td.tfoot.th.thead.tr.tt.ul.var`.split(`.`)},Il={}.hasOwnProperty;function Ll(e,t){let n={type:`root`,children:[]},r=Rl({schema:t?{...Fl,...t}:Fl,stack:[]},e);return r&&(Array.isArray(r)?r.length===1?n=r[0]:n.children=r:n=r),n}function Rl(e,t){if(t&&typeof t==`object`){let n=t;switch(typeof n.type==`string`?n.type:``){case`comment`:return zl(e,n);case`doctype`:return Bl(e,n);case`element`:return Vl(e,n);case`root`:return Hl(e,n);case`text`:return Ul(e,n)}}}function zl(e,t){if(e.schema.allowComments){let e=typeof t.value==`string`?t.value:``,n=e.indexOf(`-->`),r={type:`comment`,value:n<0?e:e.slice(0,n)};return Xl(r,t),r}}function Bl(e,t){if(e.schema.allowDoctypes){let e={type:`doctype`};return Xl(e,t),e}}function Vl(e,t){let n=typeof t.tagName==`string`?t.tagName:``;e.stack.push(n);let r=Wl(e,t.children),i=Gl(e,t.properties);e.stack.pop();let a=!1;if(n&&n!==`*`&&(!e.schema.tagNames||e.schema.tagNames.includes(n))&&(a=!0,e.schema.ancestors&&Il.call(e.schema.ancestors,n))){let t=e.schema.ancestors[n],r=-1;for(a=!1;++r1){let e=!1,n=0;for(;++n-1&&a>c||o>-1&&a>o||s>-1&&a>s)return!0;let l=-1;for(;++l4&&t.slice(0,4).toLowerCase()===`data`)return n}function Ql(e){return function(t){return Ll(t,e)}}function $l(e){if(typeof e!=`string`)throw TypeError(`Expected a string`);return e.replace(/[|\\{}()[\]^$+*?.]/g,`\\$&`).replace(/-/g,`\\x2d`)}function eu(e,t,n){let r=bc((n||{}).ignore||[]),i=tu(t),a=-1;for(;++a0?{type:`text`,value:a}:void 0),a===!1?r.lastIndex=n+1:(s!==n&&u.push({type:`text`,value:e.value.slice(s,n)}),Array.isArray(a)?u.push(...a):a&&u.push(a),s=n+d[0].length,l=!0),!r.global)break;d=r.exec(e.value)}return l?(s?\]}]+$/.exec(e);if(!t)return[e,void 0];e=e.slice(0,t.index);let n=t[0],r=n.indexOf(`)`),i=su(e,`(`),a=su(e,`)`);for(;r!==-1&&i>a;)e+=n.slice(0,r+1),n=n.slice(r+1),r=n.indexOf(`)`),a++;return[e,n]}function Cu(e,t){let n=e.input.charCodeAt(e.index-1);return(e.index===0||Ji(n)||qi(n))&&(!t||n!==47)}Nu.peek=Mu;function wu(){this.buffer()}function Tu(e){this.enter({type:`footnoteReference`,identifier:``,label:``},e)}function Eu(){this.buffer()}function Du(e){this.enter({type:`footnoteDefinition`,identifier:``,label:``,children:[]},e)}function Ou(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=Li(this.sliceSerialize(e)).toLowerCase(),n.label=t}function ku(e){this.exit(e)}function Au(e){let t=this.resume(),n=this.stack[this.stack.length-1];n.type,n.identifier=Li(this.sliceSerialize(e)).toLowerCase(),n.label=t}function ju(e){this.exit(e)}function Mu(){return`[`}function Nu(e,t,n,r){let i=n.createTracker(r),a=i.move(`[^`),o=n.enter(`footnoteReference`),s=n.enter(`reference`);return a+=i.move(n.safe(n.associationId(e),{after:`]`,before:a})),s(),o(),a+=i.move(`]`),a}function Pu(){return{enter:{gfmFootnoteCallString:wu,gfmFootnoteCall:Tu,gfmFootnoteDefinitionLabelString:Eu,gfmFootnoteDefinition:Du},exit:{gfmFootnoteCallString:Ou,gfmFootnoteCall:ku,gfmFootnoteDefinitionLabelString:Au,gfmFootnoteDefinition:ju}}}function Fu(e){let t=!1;return e&&e.firstLineBlank&&(t=!0),{handlers:{footnoteDefinition:n,footnoteReference:Nu},unsafe:[{character:`[`,inConstruct:[`label`,`phrasing`,`reference`]}]};function n(e,n,r,i){let a=r.createTracker(i),o=a.move(`[^`),s=r.enter(`footnoteDefinition`),c=r.enter(`label`);return o+=a.move(r.safe(r.associationId(e),{before:o,after:`]`})),c(),o+=a.move(`]:`),e.children&&e.children.length>0&&(a.shift(4),o+=a.move((t?` +`:` `)+r.indentLines(r.containerFlow(e,a.current()),t?Lu:Iu))),s(),o}}function Iu(e,t,n){return t===0?e:Lu(e,t,n)}function Lu(e,t,n){return(n?``:` `)+e}var Ru=[`autolink`,`destinationLiteral`,`destinationRaw`,`reference`,`titleQuote`,`titleApostrophe`];Uu.peek=Wu;function zu(){return{canContainEols:[`delete`],enter:{strikethrough:Vu},exit:{strikethrough:Hu}}}function Bu(){return{unsafe:[{character:`~`,inConstruct:`phrasing`,notInConstruct:Ru}],handlers:{delete:Uu}}}function Vu(e){this.enter({type:`delete`,children:[]},e)}function Hu(e){this.exit(e)}function Uu(e,t,n,r){let i=n.createTracker(r),a=n.enter(`strikethrough`),o=i.move(`~~`);return o+=n.containerPhrasing(e,{...i.current(),before:o,after:`~`}),o+=i.move(`~~`),a(),o}function Wu(){return`~`}function Gu(e){return e.length}function Ku(e,t){let n=t||{},r=(n.align||[]).concat(),i=n.stringLength||Gu,a=[],o=[],s=[],c=[],l=0,u=-1;for(;++ul&&(l=e[u].length);++ac[a])&&(c[a]=e)}t.push(o)}o[u]=t,s[u]=r}let d=-1;if(typeof r==`object`&&`length`in r)for(;++dc[d]&&(c[d]=i),p[d]=i),f[d]=o}o.splice(1,0,f),s.splice(1,0,p),u=-1;let m=[];for(;++u `),a.shift(2);let o=n.indentLines(n.containerFlow(e,a.current()),Xu);return i(),o}function Xu(e,t,n){return`>`+(n?``:` `)+e}function Zu(e,t){return Qu(e,t.inConstruct,!0)&&!Qu(e,t.notInConstruct,!1)}function Qu(e,t,n){if(typeof t==`string`&&(t=[t]),!t||t.length===0)return n;let r=-1;for(;++ro&&(o=a):a=1,i=r+t.length,r=n.indexOf(t,i);return o}function td(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function nd(e){let t=e.options.fence||"`";if(t!=="`"&&t!==`~`)throw Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function rd(e,t,n,r){let i=nd(n),a=e.value||``,o=i==="`"?`GraveAccent`:`Tilde`;if(td(e,n)){let e=n.enter(`codeIndented`),t=n.indentLines(a,id);return e(),t}let s=n.createTracker(r),c=i.repeat(Math.max(ed(a,i)+1,3)),l=n.enter(`codeFenced`),u=s.move(c);if(e.lang){let t=n.enter(`codeFencedLang${o}`);u+=s.move(n.safe(e.lang,{before:u,after:` `,encode:["`"],...s.current()})),t()}if(e.lang&&e.meta){let t=n.enter(`codeFencedMeta${o}`);u+=s.move(` `),u+=s.move(n.safe(e.meta,{before:u,after:` `,encode:["`"],...s.current()})),t()}return u+=s.move(` `),a&&(u+=s.move(a+` -`)),u+=s.move(c),l(),u}function od(e,t,n){return(n?``:` `)+e}function sd(e){let t=e.options.quote||`"`;if(t!==`"`&&t!==`'`)throw Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function cd(e,t,n,r){let i=sd(n),a=i===`"`?`Quote`:`Apostrophe`,o=n.enter(`definition`),s=n.enter(`label`),c=n.createTracker(r),l=c.move(`[`);return l+=c.move(n.safe(n.associationId(e),{before:l,after:`]`,...c.current()})),l+=c.move(`]: `),s(),!e.url||/[\0- \u007F]/.test(e.url)?(s=n.enter(`destinationLiteral`),l+=c.move(`<`),l+=c.move(n.safe(e.url,{before:l,after:`>`,...c.current()})),l+=c.move(`>`)):(s=n.enter(`destinationRaw`),l+=c.move(n.safe(e.url,{before:l,after:e.title?` `:` -`,...c.current()}))),s(),e.title&&(s=n.enter(`title${a}`),l+=c.move(` `+i),l+=c.move(n.safe(e.title,{before:l,after:i,...c.current()})),l+=c.move(i),s()),o(),l}function ld(e){let t=e.options.emphasis||`*`;if(t!==`*`&&t!==`_`)throw Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function ud(e){return`&#x`+e.toString(16).toUpperCase()+`;`}function dd(e,t,n){let r=oa(e),i=oa(t);return r===void 0?i===void 0?n===`_`?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:r===1?i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}fd.peek=pd;function fd(e,t,n,r){let i=ld(n),a=n.enter(`emphasis`),o=n.createTracker(r),s=o.move(i),c=o.move(n.containerPhrasing(e,{after:i,before:s,...o.current()})),l=c.charCodeAt(0),u=dd(r.before.charCodeAt(r.before.length-1),l,i);u.inside&&(c=ud(l)+c.slice(1));let d=c.charCodeAt(c.length-1),f=dd(r.after.charCodeAt(0),d,i);f.inside&&(c=c.slice(0,-1)+ud(d));let p=o.move(i);return a(),n.attentionEncodeSurroundingInfo={after:f.outside,before:u.outside},s+c+p}function pd(e,t,n){return n.options.emphasis||`*`}function md(e,t){let n=!1;return Nc(e,function(e){if(`value`in e&&/\r?\n|\r/.test(e.value)||e.type===`break`)return n=!0,!1}),!!((!e.depth||e.depth<3)&&Ei(e)&&(t.options.setext||n))}function hd(e,t,n,r){let i=Math.max(Math.min(6,e.depth||1),1),a=n.createTracker(r);if(md(e,n)){let t=n.enter(`headingSetext`),r=n.enter(`phrasing`),o=n.containerPhrasing(e,{...a.current(),before:` +`)),u+=s.move(c),l(),u}function id(e,t,n){return(n?``:` `)+e}function ad(e){let t=e.options.quote||`"`;if(t!==`"`&&t!==`'`)throw Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function od(e,t,n,r){let i=ad(n),a=i===`"`?`Quote`:`Apostrophe`,o=n.enter(`definition`),s=n.enter(`label`),c=n.createTracker(r),l=c.move(`[`);return l+=c.move(n.safe(n.associationId(e),{before:l,after:`]`,...c.current()})),l+=c.move(`]: `),s(),!e.url||/[\0- \u007F]/.test(e.url)?(s=n.enter(`destinationLiteral`),l+=c.move(`<`),l+=c.move(n.safe(e.url,{before:l,after:`>`,...c.current()})),l+=c.move(`>`)):(s=n.enter(`destinationRaw`),l+=c.move(n.safe(e.url,{before:l,after:e.title?` `:` +`,...c.current()}))),s(),e.title&&(s=n.enter(`title${a}`),l+=c.move(` `+i),l+=c.move(n.safe(e.title,{before:l,after:i,...c.current()})),l+=c.move(i),s()),o(),l}function sd(e){let t=e.options.emphasis||`*`;if(t!==`*`&&t!==`_`)throw Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function cd(e){return`&#x`+e.toString(16).toUpperCase()+`;`}function ld(e,t,n){let r=ia(e),i=ia(t);return r===void 0?i===void 0?n===`_`?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:r===1?i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:i===void 0?{inside:!1,outside:!1}:i===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}ud.peek=dd;function ud(e,t,n,r){let i=sd(n),a=n.enter(`emphasis`),o=n.createTracker(r),s=o.move(i),c=o.move(n.containerPhrasing(e,{after:i,before:s,...o.current()})),l=c.charCodeAt(0),u=ld(r.before.charCodeAt(r.before.length-1),l,i);u.inside&&(c=cd(l)+c.slice(1));let d=c.charCodeAt(c.length-1),f=ld(r.after.charCodeAt(0),d,i);f.inside&&(c=c.slice(0,-1)+cd(d));let p=o.move(i);return a(),n.attentionEncodeSurroundingInfo={after:f.outside,before:u.outside},s+c+p}function dd(e,t,n){return n.options.emphasis||`*`}function fd(e,t){let n=!1;return jc(e,function(e){if(`value`in e&&/\r?\n|\r/.test(e.value)||e.type===`break`)return n=!0,!1}),!!((!e.depth||e.depth<3)&&wi(e)&&(t.options.setext||n))}function pd(e,t,n,r){let i=Math.max(Math.min(6,e.depth||1),1),a=n.createTracker(r);if(fd(e,n)){let t=n.enter(`headingSetext`),r=n.enter(`phrasing`),o=n.containerPhrasing(e,{...a.current(),before:` `,after:` `});return r(),t(),o+` `+(i===1?`=`:`-`).repeat(o.length-(Math.max(o.lastIndexOf(`\r`),o.lastIndexOf(` `))+1))}let o=`#`.repeat(i),s=n.enter(`headingAtx`),c=n.enter(`phrasing`);a.move(o+` `);let l=n.containerPhrasing(e,{before:`# `,after:` -`,...a.current()});return/^[\t ]/.test(l)&&(l=ud(l.charCodeAt(0))+l.slice(1)),l=l?o+` `+l:o,n.options.closeAtx&&(l+=` `+o),c(),s(),l}gd.peek=_d;function gd(e){return e.value||``}function _d(){return`<`}vd.peek=yd;function vd(e,t,n,r){let i=sd(n),a=i===`"`?`Quote`:`Apostrophe`,o=n.enter(`image`),s=n.enter(`label`),c=n.createTracker(r),l=c.move(`![`);return l+=c.move(n.safe(e.alt,{before:l,after:`]`,...c.current()})),l+=c.move(`](`),s(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(s=n.enter(`destinationLiteral`),l+=c.move(`<`),l+=c.move(n.safe(e.url,{before:l,after:`>`,...c.current()})),l+=c.move(`>`)):(s=n.enter(`destinationRaw`),l+=c.move(n.safe(e.url,{before:l,after:e.title?` `:`)`,...c.current()}))),s(),e.title&&(s=n.enter(`title${a}`),l+=c.move(` `+i),l+=c.move(n.safe(e.title,{before:l,after:i,...c.current()})),l+=c.move(i),s()),l+=c.move(`)`),o(),l}function yd(){return`!`}bd.peek=xd;function bd(e,t,n,r){let i=e.referenceType,a=n.enter(`imageReference`),o=n.enter(`label`),s=n.createTracker(r),c=s.move(`![`),l=n.safe(e.alt,{before:c,after:`]`,...s.current()});c+=s.move(l+`][`),o();let u=n.stack;n.stack=[],o=n.enter(`reference`);let d=n.safe(n.associationId(e),{before:c,after:`]`,...s.current()});return o(),n.stack=u,a(),i===`full`||!l||l!==d?c+=s.move(d+`]`):i===`shortcut`?c=c.slice(0,-1):c+=s.move(`]`),c}function xd(){return`!`}Sd.peek=Cd;function Sd(e,t,n){let r=e.value||``,i="`",a=-1;for(;RegExp("(^|[^`])"+i+"([^`]|$)").test(r);)i+="`";for(/[^ \r\n]/.test(r)&&(/^[ \r\n]/.test(r)&&/[ \r\n]$/.test(r)||/^`|`$/.test(r))&&(r=` `+r+` `);++a\u007F]/.test(e.url))}Td.peek=Ed;function Td(e,t,n,r){let i=sd(n),a=i===`"`?`Quote`:`Apostrophe`,o=n.createTracker(r),s,c;if(wd(e,n)){let t=n.stack;n.stack=[],s=n.enter(`autolink`);let r=o.move(`<`);return r+=o.move(n.containerPhrasing(e,{before:r,after:`>`,...o.current()})),r+=o.move(`>`),s(),n.stack=t,r}s=n.enter(`link`),c=n.enter(`label`);let l=o.move(`[`);return l+=o.move(n.containerPhrasing(e,{before:l,after:`](`,...o.current()})),l+=o.move(`](`),c(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(c=n.enter(`destinationLiteral`),l+=o.move(`<`),l+=o.move(n.safe(e.url,{before:l,after:`>`,...o.current()})),l+=o.move(`>`)):(c=n.enter(`destinationRaw`),l+=o.move(n.safe(e.url,{before:l,after:e.title?` `:`)`,...o.current()}))),c(),e.title&&(c=n.enter(`title${a}`),l+=o.move(` `+i),l+=o.move(n.safe(e.title,{before:l,after:i,...o.current()})),l+=o.move(i),c()),l+=o.move(`)`),s(),l}function Ed(e,t,n){return wd(e,n)?`<`:`[`}Dd.peek=Od;function Dd(e,t,n,r){let i=e.referenceType,a=n.enter(`linkReference`),o=n.enter(`label`),s=n.createTracker(r),c=s.move(`[`),l=n.containerPhrasing(e,{before:c,after:`]`,...s.current()});c+=s.move(l+`][`),o();let u=n.stack;n.stack=[],o=n.enter(`reference`);let d=n.safe(n.associationId(e),{before:c,after:`]`,...s.current()});return o(),n.stack=u,a(),i===`full`||!l||l!==d?c+=s.move(d+`]`):i===`shortcut`?c=c.slice(0,-1):c+=s.move(`]`),c}function Od(){return`[`}function kd(e){let t=e.options.bullet||`*`;if(t!==`*`&&t!==`+`&&t!==`-`)throw Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function Ad(e){let t=kd(e),n=e.options.bulletOther;if(!n)return t===`*`?`-`:`*`;if(n!==`*`&&n!==`+`&&n!==`-`)throw Error("Cannot serialize items with `"+n+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(n===t)throw Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+n+"`) to be different");return n}function jd(e){let t=e.options.bulletOrdered||`.`;if(t!==`.`&&t!==`)`)throw Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function Md(e){let t=e.options.rule||`*`;if(t!==`*`&&t!==`-`&&t!==`_`)throw Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function Nd(e,t,n,r){let i=n.enter(`list`),a=n.bulletCurrent,o=e.ordered?jd(n):kd(n),s=e.ordered?o===`.`?`)`:`.`:Ad(n),c=t&&n.bulletLastUsed?o===n.bulletLastUsed:!1;if(!e.ordered){let t=e.children?e.children[0]:void 0;if((o===`*`||o===`-`)&&t&&(!t.children||!t.children[0])&&n.stack[n.stack.length-1]===`list`&&n.stack[n.stack.length-2]===`listItem`&&n.stack[n.stack.length-3]===`list`&&n.stack[n.stack.length-4]===`listItem`&&n.indexStack[n.indexStack.length-1]===0&&n.indexStack[n.indexStack.length-2]===0&&n.indexStack[n.indexStack.length-3]===0&&(c=!0),Md(n)===o&&t){let t=-1;for(;++t-1?t.start:1)+(n.options.incrementListMarker===!1?0:t.children.indexOf(e))+a);let o=a.length+1;(i===`tab`||i===`mixed`&&(t&&t.type===`list`&&t.spread||e.spread))&&(o=Math.ceil(o/4)*4);let s=n.createTracker(r);s.move(a+` `.repeat(o-a.length)),s.shift(o);let c=n.enter(`listItem`),l=n.indentLines(n.containerFlow(e,s.current()),u);return c(),l;function u(e,t,n){return t?(n?``:` `.repeat(o))+e:(n?a:a+` `.repeat(o-a.length))+e}}function Id(e,t,n,r){let i=n.enter(`paragraph`),a=n.enter(`phrasing`),o=n.containerPhrasing(e,r);return a(),i(),o}var Ld=Sc([`break`,`delete`,`emphasis`,`footnote`,`footnoteReference`,`image`,`imageReference`,`inlineCode`,`inlineMath`,`link`,`linkReference`,`mdxJsxTextElement`,`mdxTextExpression`,`strong`,`text`,`textDirective`]);function Rd(e,t,n,r){return(e.children.some(function(e){return Ld(e)})?n.containerPhrasing:n.containerFlow).call(n,e,r)}function zd(e){let t=e.options.strong||`*`;if(t!==`*`&&t!==`_`)throw Error("Cannot serialize strong with `"+t+"` for `options.strong`, expected `*`, or `_`");return t}Bd.peek=Vd;function Bd(e,t,n,r){let i=zd(n),a=n.enter(`strong`),o=n.createTracker(r),s=o.move(i+i),c=o.move(n.containerPhrasing(e,{after:i,before:s,...o.current()})),l=c.charCodeAt(0),u=dd(r.before.charCodeAt(r.before.length-1),l,i);u.inside&&(c=ud(l)+c.slice(1));let d=c.charCodeAt(c.length-1),f=dd(r.after.charCodeAt(0),d,i);f.inside&&(c=c.slice(0,-1)+ud(d));let p=o.move(i+i);return a(),n.attentionEncodeSurroundingInfo={after:f.outside,before:u.outside},s+c+p}function Vd(e,t,n){return n.options.strong||`*`}function Hd(e,t,n,r){return n.safe(e.value,r)}function Ud(e){let t=e.options.ruleRepetition||3;if(t<3)throw Error("Cannot serialize rules with repetition `"+t+"` for `options.ruleRepetition`, expected `3` or more");return t}function Wd(e,t,n){let r=(Md(n)+(n.options.ruleSpaces?` `:``)).repeat(Ud(n));return n.options.ruleSpaces?r.slice(0,-1):r}var Gd={blockquote:Zu,break:td,code:ad,definition:cd,emphasis:fd,hardBreak:td,heading:hd,html:gd,image:vd,imageReference:bd,inlineCode:Sd,link:Td,linkReference:Dd,list:Nd,listItem:Fd,paragraph:Id,root:Rd,strong:Bd,text:Hd,thematicBreak:Wd};function Kd(){return{enter:{table:qd,tableData:Zd,tableHeader:Zd,tableRow:Yd},exit:{codeText:Qd,table:Jd,tableData:Xd,tableHeader:Xd,tableRow:Xd}}}function qd(e){let t=e._align;this.enter({type:`table`,align:t.map(function(e){return e===`none`?null:e}),children:[]},e),this.data.inTable=!0}function Jd(e){this.exit(e),this.data.inTable=void 0}function Yd(e){this.enter({type:`tableRow`,children:[]},e)}function Xd(e){this.exit(e)}function Zd(e){this.enter({type:`tableCell`,children:[]},e)}function Qd(e){let t=this.resume();this.data.inTable&&(t=t.replace(/\\([\\|])/g,$d));let n=this.stack[this.stack.length-1];n.type,n.value=t,this.exit(e)}function $d(e,t){return t===`|`?t:e}function ef(e){let t=e||{},n=t.tableCellPadding,r=t.tablePipeAlign,i=t.stringLength,a=n?` `:`|`;return{unsafe:[{character:`\r`,inConstruct:`tableCell`},{character:` +`,...a.current()});return/^[\t ]/.test(l)&&(l=cd(l.charCodeAt(0))+l.slice(1)),l=l?o+` `+l:o,n.options.closeAtx&&(l+=` `+o),c(),s(),l}md.peek=hd;function md(e){return e.value||``}function hd(){return`<`}gd.peek=_d;function gd(e,t,n,r){let i=ad(n),a=i===`"`?`Quote`:`Apostrophe`,o=n.enter(`image`),s=n.enter(`label`),c=n.createTracker(r),l=c.move(`![`);return l+=c.move(n.safe(e.alt,{before:l,after:`]`,...c.current()})),l+=c.move(`](`),s(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(s=n.enter(`destinationLiteral`),l+=c.move(`<`),l+=c.move(n.safe(e.url,{before:l,after:`>`,...c.current()})),l+=c.move(`>`)):(s=n.enter(`destinationRaw`),l+=c.move(n.safe(e.url,{before:l,after:e.title?` `:`)`,...c.current()}))),s(),e.title&&(s=n.enter(`title${a}`),l+=c.move(` `+i),l+=c.move(n.safe(e.title,{before:l,after:i,...c.current()})),l+=c.move(i),s()),l+=c.move(`)`),o(),l}function _d(){return`!`}vd.peek=yd;function vd(e,t,n,r){let i=e.referenceType,a=n.enter(`imageReference`),o=n.enter(`label`),s=n.createTracker(r),c=s.move(`![`),l=n.safe(e.alt,{before:c,after:`]`,...s.current()});c+=s.move(l+`][`),o();let u=n.stack;n.stack=[],o=n.enter(`reference`);let d=n.safe(n.associationId(e),{before:c,after:`]`,...s.current()});return o(),n.stack=u,a(),i===`full`||!l||l!==d?c+=s.move(d+`]`):i===`shortcut`?c=c.slice(0,-1):c+=s.move(`]`),c}function yd(){return`!`}bd.peek=xd;function bd(e,t,n){let r=e.value||``,i="`",a=-1;for(;RegExp("(^|[^`])"+i+"([^`]|$)").test(r);)i+="`";for(/[^ \r\n]/.test(r)&&(/^[ \r\n]/.test(r)&&/[ \r\n]$/.test(r)||/^`|`$/.test(r))&&(r=` `+r+` `);++a\u007F]/.test(e.url))}Cd.peek=wd;function Cd(e,t,n,r){let i=ad(n),a=i===`"`?`Quote`:`Apostrophe`,o=n.createTracker(r),s,c;if(Sd(e,n)){let t=n.stack;n.stack=[],s=n.enter(`autolink`);let r=o.move(`<`);return r+=o.move(n.containerPhrasing(e,{before:r,after:`>`,...o.current()})),r+=o.move(`>`),s(),n.stack=t,r}s=n.enter(`link`),c=n.enter(`label`);let l=o.move(`[`);return l+=o.move(n.containerPhrasing(e,{before:l,after:`](`,...o.current()})),l+=o.move(`](`),c(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(c=n.enter(`destinationLiteral`),l+=o.move(`<`),l+=o.move(n.safe(e.url,{before:l,after:`>`,...o.current()})),l+=o.move(`>`)):(c=n.enter(`destinationRaw`),l+=o.move(n.safe(e.url,{before:l,after:e.title?` `:`)`,...o.current()}))),c(),e.title&&(c=n.enter(`title${a}`),l+=o.move(` `+i),l+=o.move(n.safe(e.title,{before:l,after:i,...o.current()})),l+=o.move(i),c()),l+=o.move(`)`),s(),l}function wd(e,t,n){return Sd(e,n)?`<`:`[`}Td.peek=Ed;function Td(e,t,n,r){let i=e.referenceType,a=n.enter(`linkReference`),o=n.enter(`label`),s=n.createTracker(r),c=s.move(`[`),l=n.containerPhrasing(e,{before:c,after:`]`,...s.current()});c+=s.move(l+`][`),o();let u=n.stack;n.stack=[],o=n.enter(`reference`);let d=n.safe(n.associationId(e),{before:c,after:`]`,...s.current()});return o(),n.stack=u,a(),i===`full`||!l||l!==d?c+=s.move(d+`]`):i===`shortcut`?c=c.slice(0,-1):c+=s.move(`]`),c}function Ed(){return`[`}function Dd(e){let t=e.options.bullet||`*`;if(t!==`*`&&t!==`+`&&t!==`-`)throw Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function Od(e){let t=Dd(e),n=e.options.bulletOther;if(!n)return t===`*`?`-`:`*`;if(n!==`*`&&n!==`+`&&n!==`-`)throw Error("Cannot serialize items with `"+n+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(n===t)throw Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+n+"`) to be different");return n}function kd(e){let t=e.options.bulletOrdered||`.`;if(t!==`.`&&t!==`)`)throw Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function Ad(e){let t=e.options.rule||`*`;if(t!==`*`&&t!==`-`&&t!==`_`)throw Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function jd(e,t,n,r){let i=n.enter(`list`),a=n.bulletCurrent,o=e.ordered?kd(n):Dd(n),s=e.ordered?o===`.`?`)`:`.`:Od(n),c=t&&n.bulletLastUsed?o===n.bulletLastUsed:!1;if(!e.ordered){let t=e.children?e.children[0]:void 0;if((o===`*`||o===`-`)&&t&&(!t.children||!t.children[0])&&n.stack[n.stack.length-1]===`list`&&n.stack[n.stack.length-2]===`listItem`&&n.stack[n.stack.length-3]===`list`&&n.stack[n.stack.length-4]===`listItem`&&n.indexStack[n.indexStack.length-1]===0&&n.indexStack[n.indexStack.length-2]===0&&n.indexStack[n.indexStack.length-3]===0&&(c=!0),Ad(n)===o&&t){let t=-1;for(;++t-1?t.start:1)+(n.options.incrementListMarker===!1?0:t.children.indexOf(e))+a);let o=a.length+1;(i===`tab`||i===`mixed`&&(t&&t.type===`list`&&t.spread||e.spread))&&(o=Math.ceil(o/4)*4);let s=n.createTracker(r);s.move(a+` `.repeat(o-a.length)),s.shift(o);let c=n.enter(`listItem`),l=n.indentLines(n.containerFlow(e,s.current()),u);return c(),l;function u(e,t,n){return t?(n?``:` `.repeat(o))+e:(n?a:a+` `.repeat(o-a.length))+e}}function Pd(e,t,n,r){let i=n.enter(`paragraph`),a=n.enter(`phrasing`),o=n.containerPhrasing(e,r);return a(),i(),o}var Fd=bc([`break`,`delete`,`emphasis`,`footnote`,`footnoteReference`,`image`,`imageReference`,`inlineCode`,`inlineMath`,`link`,`linkReference`,`mdxJsxTextElement`,`mdxTextExpression`,`strong`,`text`,`textDirective`]);function Id(e,t,n,r){return(e.children.some(function(e){return Fd(e)})?n.containerPhrasing:n.containerFlow).call(n,e,r)}function Ld(e){let t=e.options.strong||`*`;if(t!==`*`&&t!==`_`)throw Error("Cannot serialize strong with `"+t+"` for `options.strong`, expected `*`, or `_`");return t}Rd.peek=zd;function Rd(e,t,n,r){let i=Ld(n),a=n.enter(`strong`),o=n.createTracker(r),s=o.move(i+i),c=o.move(n.containerPhrasing(e,{after:i,before:s,...o.current()})),l=c.charCodeAt(0),u=ld(r.before.charCodeAt(r.before.length-1),l,i);u.inside&&(c=cd(l)+c.slice(1));let d=c.charCodeAt(c.length-1),f=ld(r.after.charCodeAt(0),d,i);f.inside&&(c=c.slice(0,-1)+cd(d));let p=o.move(i+i);return a(),n.attentionEncodeSurroundingInfo={after:f.outside,before:u.outside},s+c+p}function zd(e,t,n){return n.options.strong||`*`}function Bd(e,t,n,r){return n.safe(e.value,r)}function Vd(e){let t=e.options.ruleRepetition||3;if(t<3)throw Error("Cannot serialize rules with repetition `"+t+"` for `options.ruleRepetition`, expected `3` or more");return t}function Hd(e,t,n){let r=(Ad(n)+(n.options.ruleSpaces?` `:``)).repeat(Vd(n));return n.options.ruleSpaces?r.slice(0,-1):r}var Ud={blockquote:Yu,break:$u,code:rd,definition:od,emphasis:ud,hardBreak:$u,heading:pd,html:md,image:gd,imageReference:vd,inlineCode:bd,link:Cd,linkReference:Td,list:jd,listItem:Nd,paragraph:Pd,root:Id,strong:Rd,text:Bd,thematicBreak:Hd};function Wd(){return{enter:{table:Gd,tableData:Yd,tableHeader:Yd,tableRow:qd},exit:{codeText:Xd,table:Kd,tableData:Jd,tableHeader:Jd,tableRow:Jd}}}function Gd(e){let t=e._align;this.enter({type:`table`,align:t.map(function(e){return e===`none`?null:e}),children:[]},e),this.data.inTable=!0}function Kd(e){this.exit(e),this.data.inTable=void 0}function qd(e){this.enter({type:`tableRow`,children:[]},e)}function Jd(e){this.exit(e)}function Yd(e){this.enter({type:`tableCell`,children:[]},e)}function Xd(e){let t=this.resume();this.data.inTable&&(t=t.replace(/\\([\\|])/g,Zd));let n=this.stack[this.stack.length-1];n.type,n.value=t,this.exit(e)}function Zd(e,t){return t===`|`?t:e}function Qd(e){let t=e||{},n=t.tableCellPadding,r=t.tablePipeAlign,i=t.stringLength,a=n?` `:`|`;return{unsafe:[{character:`\r`,inConstruct:`tableCell`},{character:` `,inConstruct:`tableCell`},{atBreak:!0,character:`|`,after:`[ :-]`},{character:`|`,inConstruct:`tableCell`},{atBreak:!0,character:`:`,after:`-`},{atBreak:!0,character:`-`,after:`[:|-]`}],handlers:{inlineCode:f,table:o,tableCell:c,tableRow:s}};function o(e,t,n,r){return l(u(e,n,r),e.align)}function s(e,t,n,r){let i=l([d(e,n,r)]);return i.slice(0,i.indexOf(` -`))}function c(e,t,n,r){let i=n.enter(`tableCell`),o=n.enter(`phrasing`),s=n.containerPhrasing(e,{...r,before:a,after:a});return o(),i(),s}function l(e,t){return Ju(e,{align:t,alignDelimiters:r,padding:n,stringLength:i})}function u(e,t,n){let r=e.children,i=-1,a=[],o=t.enter(`table`);for(;++i0&&!n&&(e[e.length-1][1]._gfmAutolinkLiteralWalkedInto=!0),n}var Nf={tokenize:Vf,partial:!0};function Pf(){return{document:{91:{name:`gfmFootnoteDefinition`,tokenize:Rf,continuation:{tokenize:zf},exit:Bf}},text:{91:{name:`gfmFootnoteCall`,tokenize:Lf},93:{name:`gfmPotentialFootnoteCall`,add:`after`,tokenize:Ff,resolveTo:If}}}}function Ff(e,t,n){let r=this,i=r.events.length,a=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),o;for(;i--;){let e=r.events[i][1];if(e.type===`labelImage`){o=e;break}if(e.type===`gfmFootnoteCall`||e.type===`labelLink`||e.type===`label`||e.type===`image`||e.type===`link`)break}return s;function s(i){if(!o||!o._balanced)return n(i);let s=zi(r.sliceSerialize({start:o.end,end:r.now()}));return s.codePointAt(0)!==94||!a.includes(s.slice(1))?n(i):(e.enter(`gfmFootnoteCallLabelMarker`),e.consume(i),e.exit(`gfmFootnoteCallLabelMarker`),t(i))}}function If(e,t){let n=e.length;for(;n--;)if(e[n][1].type===`labelImage`&&e[n][0]===`enter`){e[n][1];break}e[n+1][1].type=`data`,e[n+3][1].type=`gfmFootnoteCallLabelMarker`;let r={type:`gfmFootnoteCall`,start:Object.assign({},e[n+3][1].start),end:Object.assign({},e[e.length-1][1].end)},i={type:`gfmFootnoteCallMarker`,start:Object.assign({},e[n+3][1].end),end:Object.assign({},e[n+3][1].end)};i.end.column++,i.end.offset++,i.end._bufferIndex++;let a={type:`gfmFootnoteCallString`,start:Object.assign({},i.end),end:Object.assign({},e[e.length-1][1].start)},o={type:`chunkString`,contentType:`string`,start:Object.assign({},a.start),end:Object.assign({},a.end)},s=[e[n+1],e[n+2],[`enter`,r,t],e[n+3],e[n+4],[`enter`,i,t],[`exit`,i,t],[`enter`,a,t],[`enter`,o,t],[`exit`,o,t],[`exit`,a,t],e[e.length-2],e[e.length-1],[`exit`,r,t]];return e.splice(n,e.length-n+1,...s),e}function Lf(e,t,n){let r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),a=0,o;return s;function s(t){return e.enter(`gfmFootnoteCall`),e.enter(`gfmFootnoteCallLabelMarker`),e.consume(t),e.exit(`gfmFootnoteCallLabelMarker`),c}function c(t){return t===94?(e.enter(`gfmFootnoteCallMarker`),e.consume(t),e.exit(`gfmFootnoteCallMarker`),e.enter(`gfmFootnoteCallString`),e.enter(`chunkString`).contentType=`string`,l):n(t)}function l(s){if(a>999||s===93&&!o||s===null||s===91||qi(s))return n(s);if(s===93){e.exit(`chunkString`);let a=e.exit(`gfmFootnoteCallString`);return i.includes(zi(r.sliceSerialize(a)))?(e.enter(`gfmFootnoteCallLabelMarker`),e.consume(s),e.exit(`gfmFootnoteCallLabelMarker`),e.exit(`gfmFootnoteCall`),t):n(s)}return qi(s)||(o=!0),a++,e.consume(s),s===92?u:l}function u(t){return t===91||t===92||t===93?(e.consume(t),a++,l):l(t)}}function Rf(e,t,n){let r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),a,o=0,s;return c;function c(t){return e.enter(`gfmFootnoteDefinition`)._container=!0,e.enter(`gfmFootnoteDefinitionLabel`),e.enter(`gfmFootnoteDefinitionLabelMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionLabelMarker`),l}function l(t){return t===94?(e.enter(`gfmFootnoteDefinitionMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionMarker`),e.enter(`gfmFootnoteDefinitionLabelString`),e.enter(`chunkString`).contentType=`string`,u):n(t)}function u(t){if(o>999||t===93&&!s||t===null||t===91||qi(t))return n(t);if(t===93){e.exit(`chunkString`);let n=e.exit(`gfmFootnoteDefinitionLabelString`);return a=zi(r.sliceSerialize(n)),e.enter(`gfmFootnoteDefinitionLabelMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionLabelMarker`),e.exit(`gfmFootnoteDefinitionLabel`),f}return qi(t)||(s=!0),o++,e.consume(t),t===92?d:u}function d(t){return t===91||t===92||t===93?(e.consume(t),o++,u):u(t)}function f(t){return t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),i.includes(a)||i.push(a),$i(e,p,`gfmFootnoteDefinitionWhitespace`)):n(t)}function p(e){return t(e)}}function zf(e,t,n){return e.check(ma,t,e.attempt(Nf,t,n))}function Bf(e){e.exit(`gfmFootnoteDefinition`)}function Vf(e,t,n){let r=this;return $i(e,i,`gfmFootnoteDefinitionIndent`,5);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`gfmFootnoteDefinitionIndent`&&i[2].sliceSerialize(i[1],!0).length===4?t(e):n(e)}}function Hf(e){let t=(e||{}).singleTilde,n={name:`strikethrough`,tokenize:i,resolveAll:r};return t??=!0,{text:{126:n},insideSpan:{null:[n]},attentionMarkers:{null:[126]}};function r(e,t){let n=-1;for(;++n1?r(a):(e.consume(a),o++,c);if(o<2&&!t)return r(a);let l=e.exit(`strikethroughSequenceTemporary`),u=oa(a);return l._open=!u||u===2&&!!s,l._close=!s||s===2&&!!u,n(a)}}}var Uf=class{constructor(){this.map=[]}add(e,t,n){Wf(this,e,t,n)}consume(e){if(this.map.sort(function(e,t){return e[0]-t[0]}),this.map.length===0)return;let t=this.map.length,n=[];for(;t>0;)--t,n.push(e.slice(this.map[t][0]+this.map[t][1]),this.map[t][2]),e.length=this.map[t][0];n.push(e.slice()),e.length=0;let r=n.pop();for(;r;){for(let t of r)e.push(t);r=n.pop()}this.map.length=0}};function Wf(e,t,n,r){let i=0;if(n!==0||r.length!==0){for(;i-1;){let e=r.events[t][1].type;if(e===`lineEnding`||e===`linePrefix`)t--;else break}let i=t>-1?r.events[t][1].type:null,a=i===`tableHead`||i===`tableRow`?S:c;return a===S&&r.parser.lazy[r.now().line]?n(e):a(e)}function c(t){return e.enter(`tableHead`),e.enter(`tableRow`),l(t)}function l(e){return e===124?u(e):(o=!0,a+=1,u(e))}function u(t){return t===null?n(t):J(t)?a>1?(a=0,r.interrupt=!0,e.exit(`tableRow`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),p):n(t):Ji(t)?$i(e,u,`whitespace`)(t):(a+=1,o&&(o=!1,i+=1),t===124?(e.enter(`tableCellDivider`),e.consume(t),e.exit(`tableCellDivider`),o=!0,u):(e.enter(`data`),d(t)))}function d(t){return t===null||t===124||qi(t)?(e.exit(`data`),u(t)):(e.consume(t),t===92?f:d)}function f(t){return t===92||t===124?(e.consume(t),d):d(t)}function p(t){return r.interrupt=!1,r.parser.lazy[r.now().line]?n(t):(e.enter(`tableDelimiterRow`),o=!1,Ji(t)?$i(e,m,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):m(t))}function m(t){return t===45||t===58?g(t):t===124?(o=!0,e.enter(`tableCellDivider`),e.consume(t),e.exit(`tableCellDivider`),h):x(t)}function h(t){return Ji(t)?$i(e,g,`whitespace`)(t):g(t)}function g(t){return t===58?(a+=1,o=!0,e.enter(`tableDelimiterMarker`),e.consume(t),e.exit(`tableDelimiterMarker`),_):t===45?(a+=1,_(t)):t===null||J(t)?b(t):x(t)}function _(t){return t===45?(e.enter(`tableDelimiterFiller`),v(t)):x(t)}function v(t){return t===45?(e.consume(t),v):t===58?(o=!0,e.exit(`tableDelimiterFiller`),e.enter(`tableDelimiterMarker`),e.consume(t),e.exit(`tableDelimiterMarker`),y):(e.exit(`tableDelimiterFiller`),y(t))}function y(t){return Ji(t)?$i(e,b,`whitespace`)(t):b(t)}function b(n){return n===124?m(n):n===null||J(n)?!o||i!==a?x(n):(e.exit(`tableDelimiterRow`),e.exit(`tableHead`),t(n)):x(n)}function x(e){return n(e)}function S(t){return e.enter(`tableRow`),C(t)}function C(n){return n===124?(e.enter(`tableCellDivider`),e.consume(n),e.exit(`tableCellDivider`),C):n===null||J(n)?(e.exit(`tableRow`),t(n)):Ji(n)?$i(e,C,`whitespace`)(n):(e.enter(`data`),w(n))}function w(t){return t===null||t===124||qi(t)?(e.exit(`data`),C(t)):(e.consume(t),t===92?T:w)}function T(t){return t===92||t===124?(e.consume(t),w):w(t)}}function Jf(e,t){let n=-1,r=!0,i=0,a=[0,0,0,0],o=[0,0,0,0],s=!1,c=0,l,u,d,f=new Uf;for(;++nn[2]+1){let t=n[2]+1,r=n[3]-n[2]-1;e.add(t,r,[])}}e.add(n[3]+1,0,[[`exit`,o,t]])}return i!==void 0&&(a.end=Object.assign({},Zf(t.events,i)),e.add(i,0,[[`exit`,a,t]]),a=void 0),a}function Xf(e,t,n,r,i){let a=[],o=Zf(t.events,n);i&&(i.end=Object.assign({},o),a.push([`exit`,i,t])),r.end=Object.assign({},o),a.push([`exit`,r,t]),e.add(n+1,0,a)}function Zf(e,t){let n=e[t],r=n[0]===`enter`?`start`:`end`;return n[1][r]}var Qf={name:`tasklistCheck`,tokenize:ep};function $f(){return{text:{91:Qf}}}function ep(e,t,n){let r=this;return i;function i(t){return r.previous!==null||!r._gfmTasklistFirstContentOfListItem?n(t):(e.enter(`taskListCheck`),e.enter(`taskListCheckMarker`),e.consume(t),e.exit(`taskListCheckMarker`),a)}function a(t){return qi(t)?(e.enter(`taskListCheckValueUnchecked`),e.consume(t),e.exit(`taskListCheckValueUnchecked`),o):t===88||t===120?(e.enter(`taskListCheckValueChecked`),e.consume(t),e.exit(`taskListCheckValueChecked`),o):n(t)}function o(t){return t===93?(e.enter(`taskListCheckMarker`),e.consume(t),e.exit(`taskListCheckMarker`),e.exit(`taskListCheck`),s):n(t)}function s(r){return J(r)?t(r):Ji(r)?e.check({tokenize:tp},t,n)(r):n(r)}}function tp(e,t,n){return $i(e,r,`whitespace`);function r(e){return e===null?n(e):t(e)}}function np(e){return Fi([vf(),Pf(),Hf(e),Kf(),$f()])}var rp={};function ip(e){let t=this,n=e||rp,r=t.data(),i=r.micromarkExtensions||=[],a=r.fromMarkdownExtensions||=[],o=r.toMarkdownExtensions||=[];i.push(np(n)),a.push(sf()),o.push(cf(n))}function ap(e){return!!e.trim()&&!e.startsWith(`#`)&&!e.startsWith(`//`)&&(!/^[a-z][a-z\d+.-]*:/iu.test(e)||/^[a-z]:[\\/]/iu.test(e))}var op=(0,w.createContext)(null);async function sp(e){try{return await navigator.clipboard.writeText(e),!0}catch{}let t=document.activeElement,n=window.getSelection(),r=n?Array.from({length:n.rangeCount},(e,t)=>n.getRangeAt(t).cloneRange()):[],i=t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement?{start:t.selectionStart,end:t.selectionEnd,direction:t.selectionDirection}:null,a=document.createElement(`textarea`);a.value=e,a.readOnly=!0,a.style.cssText=`position:fixed;opacity:0;pointer-events:none`;try{return document.body.append(a),a.focus({preventScroll:!0}),a.select(),document.execCommand(`copy`)}catch{return!1}finally{if(a.remove(),t instanceof HTMLElement&&t.isConnected&&(t.focus({preventScroll:!0}),i&&i.start!==null&&i.end!==null&&(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&t.setSelectionRange(i.start,i.end,i.direction??void 0)),n){n.removeAllRanges();for(let e of r)e.startContainer.isConnected&&e.endContainer.isConnected&&n.addRange(e)}}}function cp(){return e=>{let t=e=>e.type===`text`?e.value:e.type===`element`&&![`ul`,`ol`].includes(e.tagName)?e.children.map(t).join(``):``,n=e=>{if(e.type===`element`&&(e.tagName===`li`||e.tagName===`p`)){let n=t(e).trim();for(let t of e.children)t.type===`element`&&t.tagName===`input`&&t.properties.type===`checkbox`&&n&&(t.properties.ariaLabel=n)}for(let t of e.children)t.type===`element`&&n(t)};n(e)}}function lp(){return e=>{let t=e=>{if(e.type===`element`){let t=e.tagName===`a`?`href`:e.tagName===`img`?`src`:void 0,n=t?e.properties[t]:void 0,r=typeof n==`string`?n.replace(/%5c/giu,`\\`):n;t&&typeof r==`string`&&/^[a-z]:[\\/]/iu.test(r)&&!/[\x00-\x1f\x7f]/u.test(r)&&(e.data={...e.data,openpiWindowsPath:r},e.properties[t]=`./`)}for(let n of e.children)n.type===`element`&&t(n)};t(e)}}function up(e){let t=e?.data?.openpiWindowsPath;return typeof t==`string`?t:void 0}function dp(e){if(!e.trim())return``;try{let t=new URL(e,document.baseURI);return[`http:`,`https:`,`mailto:`,`tel:`].includes(t.protocol)?t.href:``}catch{return``}}function fp({children:e,node:t,...n}){let{t:r}=Ln(),[i,a]=(0,w.useState)(`idle`),o=(0,w.useRef)(0),s=(0,w.useRef)(void 0),c=(0,w.useRef)(null);(0,w.useEffect)(()=>()=>{o.current+=1,window.clearTimeout(s.current)},[]);let l=i===`copied`;return(0,K.jsxs)(`div`,{className:`markdown-code-block`,children:[(0,K.jsxs)(`div`,{className:`markdown-code-toolbar`,children:[i===`failed`&&(0,K.jsx)(`span`,{role:`status`,children:r(`copyCodeFailed`)}),(0,K.jsx)(`button`,{type:`button`,"aria-label":r(l?`copiedCode`:`copyCode`),title:r(l?`copiedCode`:`copyCode`),disabled:i===`copying`,onClick:()=>{let e=++o.current;window.clearTimeout(s.current),a(`copying`),sp(c.current?.textContent??``).then(t=>{e===o.current&&(a(t?`copied`:`failed`),t&&(s.current=window.setTimeout(()=>a(`idle`),1200)))})},children:l?(0,K.jsx)(B,{"aria-hidden":`true`}):(0,K.jsx)(oe,{"aria-hidden":`true`})})]}),(0,K.jsx)(`section`,{className:`markdown-code-scroll`,"aria-label":r(`codeBlock`),tabIndex:0,onKeyDown:e=>{if(e.key!==`ArrowLeft`&&e.key!==`ArrowRight`)return;let t=e.currentTarget;t.scrollWidth<=t.clientWidth||(e.preventDefault(),t.scrollLeft+=e.key===`ArrowRight`?40:-40)},children:(0,K.jsx)(`pre`,{ref:c,...n,children:e})})]})}var pp=(0,w.memo)(function({children:e}){let t=(0,w.useContext)(op);return(0,K.jsx)(`div`,{className:`markdown`,children:(0,K.jsx)(jl,{remarkPlugins:[ip,cu],rehypePlugins:[lp,eu,cp],urlTransform:e=>ap(e)?e:dp(e),components:{pre:fp,table({children:e,...t}){return(0,K.jsx)(`section`,{className:`markdown-table-scroll`,"aria-label":`Table`,tabIndex:0,children:(0,K.jsx)(`table`,{...t,children:e})})},a({href:e,node:n,children:r,...i}){if(e=up(n)??e,ap(e??``))return t?(0,K.jsxs)(`button`,{type:`button`,className:`artifact-link`,title:`Open read-only file: ${e}`,onClick:()=>t.open(e??``,t.parent),children:[r,(0,K.jsxs)(`small`,{className:`artifact-link-path`,"aria-hidden":`true`,children:[` `,`(`,e,`)`]})]}):(0,K.jsx)(`span`,{title:`Open this file from its Session`,children:r});let a=dp(e??``);return a?(0,K.jsx)(`a`,{...i,href:a,target:`_blank`,rel:`noreferrer`,children:r}):r},img({src:e,node:n,alt:r,title:i}){if(e=up(n)??e,ap(e??``))return t?(0,K.jsxs)(`button`,{type:`button`,className:`artifact-link`,title:`Open read-only file: ${e}`,onClick:()=>t.open(e??``,t.parent),children:[`[image: `,r||`image`,`]`,(0,K.jsxs)(`small`,{className:`artifact-link-path`,"aria-hidden":`true`,children:[` `,`(`,e,`)`]})]}):(0,K.jsxs)(`span`,{children:[`[image: `,r||`image`,`]`]});let a=dp(e??``),o=r||`image`;return a?(0,K.jsxs)(`a`,{href:a,title:i,target:`_blank`,rel:`noreferrer`,children:[`[image: `,o,`]`]}):(0,K.jsx)(`span`,{children:o})}},children:e})})}),mp=8e3;function hp(e,t){let n=0;for(let r of e){let e=r.codePointAt(0);if(n+=e<=127?1:e<=2047?2:e<=65535?3:4,n>t)return}return n}function gp(e){return hp(e,mp)!==void 0}function _p(e){return typeof e==`string`&&/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(e)}var vp=Symbol.for(`@tt-a1i/openpi/web-command-feedback/v1`),yp=Reflect.get(globalThis,vp);if(yp!==void 0&&!(yp instanceof WeakMap))throw Error(`Incompatible Web feedback bridge`);yp===void 0&&Object.defineProperty(globalThis,vp,{value:yp??new WeakMap});var bp=12288;function xp(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function Sp(e,t=!1){let n=t?e.slice(-24576):e.slice(0,bp*2),r=n.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\|$)/gu,``).replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/gu,``).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/gu,``).split(` +`))}function c(e,t,n,r){let i=n.enter(`tableCell`),o=n.enter(`phrasing`),s=n.containerPhrasing(e,{...r,before:a,after:a});return o(),i(),s}function l(e,t){return Ku(e,{align:t,alignDelimiters:r,padding:n,stringLength:i})}function u(e,t,n){let r=e.children,i=-1,a=[],o=t.enter(`table`);for(;++i0&&!n&&(e[e.length-1][1]._gfmAutolinkLiteralWalkedInto=!0),n}var jf={tokenize:zf,partial:!0};function Mf(){return{document:{91:{name:`gfmFootnoteDefinition`,tokenize:If,continuation:{tokenize:Lf},exit:Rf}},text:{91:{name:`gfmFootnoteCall`,tokenize:Ff},93:{name:`gfmPotentialFootnoteCall`,add:`after`,tokenize:Nf,resolveTo:Pf}}}}function Nf(e,t,n){let r=this,i=r.events.length,a=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),o;for(;i--;){let e=r.events[i][1];if(e.type===`labelImage`){o=e;break}if(e.type===`gfmFootnoteCall`||e.type===`labelLink`||e.type===`label`||e.type===`image`||e.type===`link`)break}return s;function s(i){if(!o||!o._balanced)return n(i);let s=Li(r.sliceSerialize({start:o.end,end:r.now()}));return s.codePointAt(0)!==94||!a.includes(s.slice(1))?n(i):(e.enter(`gfmFootnoteCallLabelMarker`),e.consume(i),e.exit(`gfmFootnoteCallLabelMarker`),t(i))}}function Pf(e,t){let n=e.length;for(;n--;)if(e[n][1].type===`labelImage`&&e[n][0]===`enter`){e[n][1];break}e[n+1][1].type=`data`,e[n+3][1].type=`gfmFootnoteCallLabelMarker`;let r={type:`gfmFootnoteCall`,start:Object.assign({},e[n+3][1].start),end:Object.assign({},e[e.length-1][1].end)},i={type:`gfmFootnoteCallMarker`,start:Object.assign({},e[n+3][1].end),end:Object.assign({},e[n+3][1].end)};i.end.column++,i.end.offset++,i.end._bufferIndex++;let a={type:`gfmFootnoteCallString`,start:Object.assign({},i.end),end:Object.assign({},e[e.length-1][1].start)},o={type:`chunkString`,contentType:`string`,start:Object.assign({},a.start),end:Object.assign({},a.end)},s=[e[n+1],e[n+2],[`enter`,r,t],e[n+3],e[n+4],[`enter`,i,t],[`exit`,i,t],[`enter`,a,t],[`enter`,o,t],[`exit`,o,t],[`exit`,a,t],e[e.length-2],e[e.length-1],[`exit`,r,t]];return e.splice(n,e.length-n+1,...s),e}function Ff(e,t,n){let r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),a=0,o;return s;function s(t){return e.enter(`gfmFootnoteCall`),e.enter(`gfmFootnoteCallLabelMarker`),e.consume(t),e.exit(`gfmFootnoteCallLabelMarker`),c}function c(t){return t===94?(e.enter(`gfmFootnoteCallMarker`),e.consume(t),e.exit(`gfmFootnoteCallMarker`),e.enter(`gfmFootnoteCallString`),e.enter(`chunkString`).contentType=`string`,l):n(t)}function l(s){if(a>999||s===93&&!o||s===null||s===91||Gi(s))return n(s);if(s===93){e.exit(`chunkString`);let a=e.exit(`gfmFootnoteCallString`);return i.includes(Li(r.sliceSerialize(a)))?(e.enter(`gfmFootnoteCallLabelMarker`),e.consume(s),e.exit(`gfmFootnoteCallLabelMarker`),e.exit(`gfmFootnoteCall`),t):n(s)}return Gi(s)||(o=!0),a++,e.consume(s),s===92?u:l}function u(t){return t===91||t===92||t===93?(e.consume(t),a++,l):l(t)}}function If(e,t,n){let r=this,i=r.parser.gfmFootnotes||(r.parser.gfmFootnotes=[]),a,o=0,s;return c;function c(t){return e.enter(`gfmFootnoteDefinition`)._container=!0,e.enter(`gfmFootnoteDefinitionLabel`),e.enter(`gfmFootnoteDefinitionLabelMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionLabelMarker`),l}function l(t){return t===94?(e.enter(`gfmFootnoteDefinitionMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionMarker`),e.enter(`gfmFootnoteDefinitionLabelString`),e.enter(`chunkString`).contentType=`string`,u):n(t)}function u(t){if(o>999||t===93&&!s||t===null||t===91||Gi(t))return n(t);if(t===93){e.exit(`chunkString`);let n=e.exit(`gfmFootnoteDefinitionLabelString`);return a=Li(r.sliceSerialize(n)),e.enter(`gfmFootnoteDefinitionLabelMarker`),e.consume(t),e.exit(`gfmFootnoteDefinitionLabelMarker`),e.exit(`gfmFootnoteDefinitionLabel`),f}return Gi(t)||(s=!0),o++,e.consume(t),t===92?d:u}function d(t){return t===91||t===92||t===93?(e.consume(t),o++,u):u(t)}function f(t){return t===58?(e.enter(`definitionMarker`),e.consume(t),e.exit(`definitionMarker`),i.includes(a)||i.push(a),Zi(e,p,`gfmFootnoteDefinitionWhitespace`)):n(t)}function p(e){return t(e)}}function Lf(e,t,n){return e.check(fa,t,e.attempt(jf,t,n))}function Rf(e){e.exit(`gfmFootnoteDefinition`)}function zf(e,t,n){let r=this;return Zi(e,i,`gfmFootnoteDefinitionIndent`,5);function i(e){let i=r.events[r.events.length-1];return i&&i[1].type===`gfmFootnoteDefinitionIndent`&&i[2].sliceSerialize(i[1],!0).length===4?t(e):n(e)}}function Bf(e){let t=(e||{}).singleTilde,n={name:`strikethrough`,tokenize:i,resolveAll:r};return t??=!0,{text:{126:n},insideSpan:{null:[n]},attentionMarkers:{null:[126]}};function r(e,t){let n=-1;for(;++n1?r(a):(e.consume(a),o++,c);if(o<2&&!t)return r(a);let l=e.exit(`strikethroughSequenceTemporary`),u=ia(a);return l._open=!u||u===2&&!!s,l._close=!s||s===2&&!!u,n(a)}}}var Vf=class{constructor(){this.map=[]}add(e,t,n){Hf(this,e,t,n)}consume(e){if(this.map.sort(function(e,t){return e[0]-t[0]}),this.map.length===0)return;let t=this.map.length,n=[];for(;t>0;)--t,n.push(e.slice(this.map[t][0]+this.map[t][1]),this.map[t][2]),e.length=this.map[t][0];n.push(e.slice()),e.length=0;let r=n.pop();for(;r;){for(let t of r)e.push(t);r=n.pop()}this.map.length=0}};function Hf(e,t,n,r){let i=0;if(n!==0||r.length!==0){for(;i-1;){let e=r.events[t][1].type;if(e===`lineEnding`||e===`linePrefix`)t--;else break}let i=t>-1?r.events[t][1].type:null,a=i===`tableHead`||i===`tableRow`?S:c;return a===S&&r.parser.lazy[r.now().line]?n(e):a(e)}function c(t){return e.enter(`tableHead`),e.enter(`tableRow`),l(t)}function l(e){return e===124?u(e):(o=!0,a+=1,u(e))}function u(t){return t===null?n(t):X(t)?a>1?(a=0,r.interrupt=!0,e.exit(`tableRow`),e.enter(`lineEnding`),e.consume(t),e.exit(`lineEnding`),p):n(t):Ki(t)?Zi(e,u,`whitespace`)(t):(a+=1,o&&(o=!1,i+=1),t===124?(e.enter(`tableCellDivider`),e.consume(t),e.exit(`tableCellDivider`),o=!0,u):(e.enter(`data`),d(t)))}function d(t){return t===null||t===124||Gi(t)?(e.exit(`data`),u(t)):(e.consume(t),t===92?f:d)}function f(t){return t===92||t===124?(e.consume(t),d):d(t)}function p(t){return r.interrupt=!1,r.parser.lazy[r.now().line]?n(t):(e.enter(`tableDelimiterRow`),o=!1,Ki(t)?Zi(e,m,`linePrefix`,r.parser.constructs.disable.null.includes(`codeIndented`)?void 0:4)(t):m(t))}function m(t){return t===45||t===58?g(t):t===124?(o=!0,e.enter(`tableCellDivider`),e.consume(t),e.exit(`tableCellDivider`),h):x(t)}function h(t){return Ki(t)?Zi(e,g,`whitespace`)(t):g(t)}function g(t){return t===58?(a+=1,o=!0,e.enter(`tableDelimiterMarker`),e.consume(t),e.exit(`tableDelimiterMarker`),_):t===45?(a+=1,_(t)):t===null||X(t)?b(t):x(t)}function _(t){return t===45?(e.enter(`tableDelimiterFiller`),v(t)):x(t)}function v(t){return t===45?(e.consume(t),v):t===58?(o=!0,e.exit(`tableDelimiterFiller`),e.enter(`tableDelimiterMarker`),e.consume(t),e.exit(`tableDelimiterMarker`),y):(e.exit(`tableDelimiterFiller`),y(t))}function y(t){return Ki(t)?Zi(e,b,`whitespace`)(t):b(t)}function b(n){return n===124?m(n):n===null||X(n)?!o||i!==a?x(n):(e.exit(`tableDelimiterRow`),e.exit(`tableHead`),t(n)):x(n)}function x(e){return n(e)}function S(t){return e.enter(`tableRow`),C(t)}function C(n){return n===124?(e.enter(`tableCellDivider`),e.consume(n),e.exit(`tableCellDivider`),C):n===null||X(n)?(e.exit(`tableRow`),t(n)):Ki(n)?Zi(e,C,`whitespace`)(n):(e.enter(`data`),w(n))}function w(t){return t===null||t===124||Gi(t)?(e.exit(`data`),C(t)):(e.consume(t),t===92?T:w)}function T(t){return t===92||t===124?(e.consume(t),w):w(t)}}function Kf(e,t){let n=-1,r=!0,i=0,a=[0,0,0,0],o=[0,0,0,0],s=!1,c=0,l,u,d,f=new Vf;for(;++nn[2]+1){let t=n[2]+1,r=n[3]-n[2]-1;e.add(t,r,[])}}e.add(n[3]+1,0,[[`exit`,o,t]])}return i!==void 0&&(a.end=Object.assign({},Yf(t.events,i)),e.add(i,0,[[`exit`,a,t]]),a=void 0),a}function Jf(e,t,n,r,i){let a=[],o=Yf(t.events,n);i&&(i.end=Object.assign({},o),a.push([`exit`,i,t])),r.end=Object.assign({},o),a.push([`exit`,r,t]),e.add(n+1,0,a)}function Yf(e,t){let n=e[t],r=n[0]===`enter`?`start`:`end`;return n[1][r]}var Xf={name:`tasklistCheck`,tokenize:Qf};function Zf(){return{text:{91:Xf}}}function Qf(e,t,n){let r=this;return i;function i(t){return r.previous!==null||!r._gfmTasklistFirstContentOfListItem?n(t):(e.enter(`taskListCheck`),e.enter(`taskListCheckMarker`),e.consume(t),e.exit(`taskListCheckMarker`),a)}function a(t){return Gi(t)?(e.enter(`taskListCheckValueUnchecked`),e.consume(t),e.exit(`taskListCheckValueUnchecked`),o):t===88||t===120?(e.enter(`taskListCheckValueChecked`),e.consume(t),e.exit(`taskListCheckValueChecked`),o):n(t)}function o(t){return t===93?(e.enter(`taskListCheckMarker`),e.consume(t),e.exit(`taskListCheckMarker`),e.exit(`taskListCheck`),s):n(t)}function s(r){return X(r)?t(r):Ki(r)?e.check({tokenize:$f},t,n)(r):n(r)}}function $f(e,t,n){return Zi(e,r,`whitespace`);function r(e){return e===null?n(e):t(e)}}function ep(e){return Ni([gf(),Mf(),Bf(e),Wf(),Zf()])}var tp={};function np(e){let t=this,n=e||tp,r=t.data(),i=r.micromarkExtensions||=[],a=r.fromMarkdownExtensions||=[],o=r.toMarkdownExtensions||=[];i.push(ep(n)),a.push(af()),o.push(of(n))}function rp(e){return!!e.trim()&&!e.startsWith(`#`)&&!e.startsWith(`//`)&&(!/^[a-z][a-z\d+.-]*:/iu.test(e)||/^[a-z]:[\\/]/iu.test(e))}var ip=(0,w.createContext)(null);async function ap(e){try{return await navigator.clipboard.writeText(e),!0}catch{}let t=document.activeElement,n=window.getSelection(),r=n?Array.from({length:n.rangeCount},(e,t)=>n.getRangeAt(t).cloneRange()):[],i=t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement?{start:t.selectionStart,end:t.selectionEnd,direction:t.selectionDirection}:null,a=document.createElement(`textarea`);a.value=e,a.readOnly=!0,a.style.cssText=`position:fixed;opacity:0;pointer-events:none`;try{return document.body.append(a),a.focus({preventScroll:!0}),a.select(),document.execCommand(`copy`)}catch{return!1}finally{if(a.remove(),t instanceof HTMLElement&&t.isConnected&&(t.focus({preventScroll:!0}),i&&i.start!==null&&i.end!==null&&(t instanceof HTMLInputElement||t instanceof HTMLTextAreaElement)&&t.setSelectionRange(i.start,i.end,i.direction??void 0)),n){n.removeAllRanges();for(let e of r)e.startContainer.isConnected&&e.endContainer.isConnected&&n.addRange(e)}}}function op(){return e=>{let t=e=>e.type===`text`?e.value:e.type===`element`&&![`ul`,`ol`].includes(e.tagName)?e.children.map(t).join(``):``,n=e=>{if(e.type===`element`&&(e.tagName===`li`||e.tagName===`p`)){let n=t(e).trim();for(let t of e.children)t.type===`element`&&t.tagName===`input`&&t.properties.type===`checkbox`&&n&&(t.properties.ariaLabel=n)}for(let t of e.children)t.type===`element`&&n(t)};n(e)}}function sp(){return e=>{let t=e=>{if(e.type===`element`){let t=e.tagName===`a`?`href`:e.tagName===`img`?`src`:void 0,n=t?e.properties[t]:void 0,r=typeof n==`string`?n.replace(/%5c/giu,`\\`):n;t&&typeof r==`string`&&/^[a-z]:[\\/]/iu.test(r)&&!/[\x00-\x1f\x7f]/u.test(r)&&(e.data={...e.data,openpiWindowsPath:r},e.properties[t]=`./`)}for(let n of e.children)n.type===`element`&&t(n)};t(e)}}function cp(e){let t=e?.data?.openpiWindowsPath;return typeof t==`string`?t:void 0}function lp(e){if(!e.trim())return``;try{let t=new URL(e,document.baseURI);return[`http:`,`https:`,`mailto:`,`tel:`].includes(t.protocol)?t.href:``}catch{return``}}function up({children:e,node:t,...n}){let{t:r}=Fn(),[i,a]=(0,w.useState)(`idle`),o=(0,w.useRef)(0),s=(0,w.useRef)(void 0),c=(0,w.useRef)(null);(0,w.useEffect)(()=>()=>{o.current+=1,window.clearTimeout(s.current)},[]);let l=i===`copied`;return(0,J.jsxs)(`div`,{className:`markdown-code-block`,children:[(0,J.jsxs)(`div`,{className:`markdown-code-toolbar`,children:[i===`failed`&&(0,J.jsx)(`span`,{role:`status`,children:r(`copyCodeFailed`)}),(0,J.jsx)(`button`,{type:`button`,"aria-label":r(l?`copiedCode`:`copyCode`),title:r(l?`copiedCode`:`copyCode`),disabled:i===`copying`,onClick:()=>{let e=++o.current;window.clearTimeout(s.current),a(`copying`),ap(c.current?.textContent??``).then(t=>{e===o.current&&(a(t?`copied`:`failed`),t&&(s.current=window.setTimeout(()=>a(`idle`),1200)))})},children:l?(0,J.jsx)(B,{"aria-hidden":`true`}):(0,J.jsx)(ae,{"aria-hidden":`true`})})]}),(0,J.jsx)(`section`,{className:`markdown-code-scroll`,"aria-label":r(`codeBlock`),tabIndex:0,onKeyDown:e=>{if(e.key!==`ArrowLeft`&&e.key!==`ArrowRight`)return;let t=e.currentTarget;t.scrollWidth<=t.clientWidth||(e.preventDefault(),t.scrollLeft+=e.key===`ArrowRight`?40:-40)},children:(0,J.jsx)(`pre`,{ref:c,...n,children:e})})]})}var dp=(0,w.memo)(function({children:e}){let t=(0,w.useContext)(ip);return(0,J.jsx)(`div`,{className:`markdown`,children:(0,J.jsx)(kl,{remarkPlugins:[np,ou],rehypePlugins:[sp,Ql,op],urlTransform:e=>rp(e)?e:lp(e),components:{pre:up,table({children:e,...t}){return(0,J.jsx)(`section`,{className:`markdown-table-scroll`,"aria-label":`Table`,tabIndex:0,children:(0,J.jsx)(`table`,{...t,children:e})})},a({href:e,node:n,children:r,...i}){if(e=cp(n)??e,rp(e??``))return t?(0,J.jsxs)(`button`,{type:`button`,className:`artifact-link`,title:`Open read-only file: ${e}`,onClick:()=>t.open(e??``,t.parent),children:[r,(0,J.jsxs)(`small`,{className:`artifact-link-path`,"aria-hidden":`true`,children:[` `,`(`,e,`)`]})]}):(0,J.jsx)(`span`,{title:`Open this file from its Session`,children:r});let a=lp(e??``);return a?(0,J.jsx)(`a`,{...i,href:a,target:`_blank`,rel:`noreferrer`,children:r}):r},img({src:e,node:n,alt:r,title:i}){if(e=cp(n)??e,rp(e??``))return t?(0,J.jsxs)(`button`,{type:`button`,className:`artifact-link`,title:`Open read-only file: ${e}`,onClick:()=>t.open(e??``,t.parent),children:[`[image: `,r||`image`,`]`,(0,J.jsxs)(`small`,{className:`artifact-link-path`,"aria-hidden":`true`,children:[` `,`(`,e,`)`]})]}):(0,J.jsxs)(`span`,{children:[`[image: `,r||`image`,`]`]});let a=lp(e??``),o=r||`image`;return a?(0,J.jsxs)(`a`,{href:a,title:i,target:`_blank`,rel:`noreferrer`,children:[`[image: `,o,`]`]}):(0,J.jsx)(`span`,{children:o})}},children:e})})}),fp=8e3;function pp(e,t){let n=0;for(let r of e){let e=r.codePointAt(0);if(n+=e<=127?1:e<=2047?2:e<=65535?3:4,n>t)return}return n}function mp(e){return pp(e,fp)!==void 0}function hp(e){return typeof e==`string`&&/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(e)}var gp=Symbol.for(`@tt-a1i/openpi/web-command-feedback/v1`),_p=Reflect.get(globalThis,gp);if(_p!==void 0&&!(_p instanceof WeakMap))throw Error(`Incompatible Web feedback bridge`);_p===void 0&&Object.defineProperty(globalThis,gp,{value:_p??new WeakMap});var vp=12288;function yp(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function bp(e,t=!1){let n=t?e.slice(-24576):e.slice(0,vp*2),r=n.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\|$)/gu,``).replace(/(?:\x1b\[|\x9b)[0-?]*[ -/]*[@-~]/gu,``).replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f\u202a-\u202e\u2066-\u2069]/gu,``).split(` `),i=(t?r.slice(-300):r.slice(0,300)).join(` -`),a=new TextEncoder().encode(i),o=t?Math.max(0,a.length-bp):0,s=t?a.length:Math.min(a.length,bp);for(;oo&&(a[s]&192)==128;)s--;return{text:new TextDecoder().decode(a.subarray(o,s)),truncated:e.length>n.length||r.length>300||a.length>12288}}function Cp(e){if(e.evidenceArguments)return e.evidenceArguments;try{return xp(JSON.parse(e.arguments))}catch{return{}}}function wp(e){return[`read`,`write`,`edit`,`bash`,`bg_start`,`bg_status`].includes(e)}function Tp(e,t){if(e===`bash`&&t?.terminalReceipt)return t.terminalReceipt;let n=xp(t?.details);if(e.startsWith(`bg_`)){let e=n.status;if(e===`running`)return{state:`running`};if([`done`,`failed`,`killed`,`timed_out`].includes(String(e)))return{state:e===`killed`?`cancelled`:e===`timed_out`?`timed_out`:e===`failed`?`failed`:`returned`,exitCode:typeof n.exitCode==`number`?n.exitCode:void 0,signal:typeof n.signal==`string`?Sp(n.signal).text:void 0}}if(e===`bash`&&t?.isError===!0&&!t.truncation){let e=t.content.trimEnd().split(` -`).at(-1);if(e===`Command aborted`)return{state:`cancelled`};if(/^Command timed out after \d+(?:\.\d+)? seconds$/u.test(e??``))return{state:`timed_out`};let n=/^Command exited with code (-?\d+)$/u.exec(e??``);if(n)return{state:`failed`,exitCode:Number(n[1])}}}function Ep(e){let t=/^\s*Tests\s+((?:\d+ (?:passed|failed|skipped|todo)(?:\s*\|\s*)?)+)\s*\((\d+)\)\s*$/mu.exec(e);if(t){let n=Number(/(\d+) passed/u.exec(t[1])?.[1]??0),r=Number(/(\d+) failed/u.exec(t[1])?.[1]??0),i=Number(t[2]);if(Number.isSafeInteger(i)&&n+r<=i)return{format:`Vitest`,tests:i,passed:n,failed:r,cancelled:0,failures:Dp(e,/^\s*(?:FAIL\s|×\s)/u)}}let n=/^Ran (\d+) tests? across \d+ files?\./mu.exec(e),r=/^\s*(\d+) pass\s*$/mu.exec(e),i=/^\s*(\d+) fail\s*$/mu.exec(e);if(n&&r&&i){let t=Number(n[1]),a=Number(r[1]),o=Number(i[1]);if(Number.isSafeInteger(t)&&a+o<=t)return{format:`Bun`,tests:t,passed:a,failed:o,cancelled:0,failures:Dp(e,/^\s*\(fail\) /u)}}let a=t=>{let n=RegExp(`^[#ℹ] ${t} (\\d+)$`,`mu`).exec(e),r=n?Number(n[1]):void 0;return r!==void 0&&Number.isSafeInteger(r)?r:void 0},o=a(`tests`),s=a(`pass`),c=a(`fail`),l=a(`cancelled`)??0;if(o===void 0||s===void 0||c===void 0||s+c+l>o)return;let u=Dp(e,/^(?:\s*not ok \d+\b|✖ )/u);return{format:/^ℹ tests /mu.test(e)?`Node`:`TAP`,tests:o,passed:s,failed:c,cancelled:l,failures:u}}function Dp(e,t){return e.split(` +`),a=new TextEncoder().encode(i),o=t?Math.max(0,a.length-vp):0,s=t?a.length:Math.min(a.length,vp);for(;oo&&(a[s]&192)==128;)s--;return{text:new TextDecoder().decode(a.subarray(o,s)),truncated:e.length>n.length||r.length>300||a.length>12288}}function xp(e){if(e.evidenceArguments)return e.evidenceArguments;try{return yp(JSON.parse(e.arguments))}catch{return{}}}function Sp(e){return[`read`,`write`,`edit`,`bash`,`bg_start`,`bg_status`].includes(e)}function Cp(e,t){if(e===`bash`&&t?.terminalReceipt)return t.terminalReceipt;let n=yp(t?.details);if(e.startsWith(`bg_`)){let e=n.status;if(e===`running`)return{state:`running`};if([`done`,`failed`,`killed`,`timed_out`].includes(String(e)))return{state:e===`killed`?`cancelled`:e===`timed_out`?`timed_out`:e===`failed`?`failed`:`returned`,exitCode:typeof n.exitCode==`number`?n.exitCode:void 0,signal:typeof n.signal==`string`?bp(n.signal).text:void 0}}if(e===`bash`&&t?.isError===!0&&!t.truncation){let e=t.content.trimEnd().split(` +`).at(-1);if(e===`Command aborted`)return{state:`cancelled`};if(/^Command timed out after \d+(?:\.\d+)? seconds$/u.test(e??``))return{state:`timed_out`};let n=/^Command exited with code (-?\d+)$/u.exec(e??``);if(n)return{state:`failed`,exitCode:Number(n[1])}}}function wp(e){let t=/^\s*Tests\s+((?:\d+ (?:passed|failed|skipped|todo)(?:\s*\|\s*)?)+)\s*\((\d+)\)\s*$/mu.exec(e);if(t){let n=Number(/(\d+) passed/u.exec(t[1])?.[1]??0),r=Number(/(\d+) failed/u.exec(t[1])?.[1]??0),i=Number(t[2]);if(Number.isSafeInteger(i)&&n+r<=i)return{format:`Vitest`,tests:i,passed:n,failed:r,cancelled:0,failures:Tp(e,/^\s*(?:FAIL\s|×\s)/u)}}let n=/^Ran (\d+) tests? across \d+ files?\./mu.exec(e),r=/^\s*(\d+) pass\s*$/mu.exec(e),i=/^\s*(\d+) fail\s*$/mu.exec(e);if(n&&r&&i){let t=Number(n[1]),a=Number(r[1]),o=Number(i[1]);if(Number.isSafeInteger(t)&&a+o<=t)return{format:`Bun`,tests:t,passed:a,failed:o,cancelled:0,failures:Tp(e,/^\s*\(fail\) /u)}}let a=t=>{let n=RegExp(`^[#ℹ] ${t} (\\d+)$`,`mu`).exec(e),r=n?Number(n[1]):void 0;return r!==void 0&&Number.isSafeInteger(r)?r:void 0},o=a(`tests`),s=a(`pass`),c=a(`fail`),l=a(`cancelled`)??0;if(o===void 0||s===void 0||c===void 0||s+c+l>o)return;let u=Tp(e,/^(?:\s*not ok \d+\b|✖ )/u);return{format:/^ℹ tests /mu.test(e)?`Node`:`TAP`,tests:o,passed:s,failed:c,cancelled:l,failures:u}}function Tp(e,t){return e.split(` `).flatMap((e,n,r)=>t.test(e)?[r.slice(n,n+8).join(` -`)]:[]).slice(0,20)}function Op(e,t,n){let r=Cp(e),i=xp(t?.details),a=Tp(e.name,t),o=a?.state??(n===`running`?`running`:t?.isError===!0?`failed`:t?.isError===!1?`returned`:`unknown`),s=[`bash`,`bg_start`,`bg_status`].includes(e.name),c=Sp(t?.content??``,s),l=e.name===`read`?/\n\n(\[(?:\d+ more lines in file\. Use offset=\d+ to continue\.|Showing lines [^\n]+)\])$/u.exec(c.text):null,u=c.truncated||!!t?.truncation||xp(i.truncation).truncated===!0||e.evidenceTruncated===!0,d=s?Ep(c.text):void 0,f=e.name===`read`?`file`:[`write`,`edit`].includes(e.name)?`change`:d?`test`:`terminal`,p=[`write`,`edit`].includes(e.name)&&t?.isError===!1&&typeof i.diff==`string`?Sp(i.diff):void 0,m=typeof r.offset==`number`&&Number.isSafeInteger(r.offset)&&r.offset>0?r.offset:1;return{kind:f,state:o,processState:a?.state??(e.name===`bash`&&t?.isError===!1?`returned`:e.name===`bash`&&n===`running`?`running`:`unknown`),output:l?c.text.slice(0,l.index):c.text,truncated:u||p?.truncated===!0,readRecovery:l?.[1],numberLines:e.name===`read`&&t?.isError===!1&&xp(i.truncation).firstLineExceedsLimit!==!0&&!!c.text,path:typeof r.path==`string`?Sp(r.path).text:void 0,resolvedPath:typeof r.resolvedPath==`string`?Sp(r.resolvedPath).text:void 0,command:typeof r.command==`string`?Sp(r.command).text:void 0,offset:m,diff:p?.text,tests:d,change:t?.isError===!1&&[`created`,`overwritten`,`unchanged`].includes(String(i.change))?String(i.change):void 0,evidenceUnavailable:typeof i.evidenceUnavailable==`string`?Sp(i.evidenceUnavailable).text:void 0,exitCode:a&&`exitCode`in a?a.exitCode:void 0,signal:a&&`signal`in a?a.signal:void 0,recovery:typeof i.fullOutputPath==`string`?Sp(i.fullOutputPath).text:void 0,rawArguments:Sp(e.arguments).text,rawResult:Sp(JSON.stringify({content:t?.content,details:t?.details,isError:t?.isError})).text}}new TextEncoder;var kp=`openpi.web.controller`,Ap,jp,Mp=0;function Np(e){try{window.sessionStorage.setItem(kp,e)}catch{}return e}async function Pp(){let e=Mp;if(!window.navigator.locks)return Np(window.crypto.randomUUID());let t=null;try{t=window.sessionStorage.getItem(kp)}catch{}let n=_p(t)?t:window.crypto.randomUUID();for(;;){let t=n,r=await new Promise(n=>{window.navigator.locks.request(`openpi.web.controller:${t}`,{ifAvailable:!0},async t=>{if(e!==Mp){n(null);return}if(!t){n(!1);return}await new Promise(e=>{jp=e,n(!0)})}).catch(()=>n(null))});if(e!==Mp)throw new DOMException(`Page was suspended`,`AbortError`);if(r===null)return Np(window.crypto.randomUUID());if(r)return Np(t);n=window.crypto.randomUUID()}}function Fp(){return Ap??=Pp()}window.addEventListener(`pagehide`,()=>{Mp++,jp?.(),jp=void 0,Ap=void 0}),window.addEventListener(`pageshow`,e=>{e.persisted&&Fp().catch(()=>{})});var Ip=`openpi.web.token`,Lp=class extends Error{status;code;constructor(e,t,n){super(e),this.status=t,this.code=n,this.name=`WebApiError`}};function Rp(){let e=document.querySelector(`meta[name="openpi-web-token"]`)?.content,t=new URLSearchParams(location.hash.slice(1)).get(`token`),n=e&&/^[a-f0-9]{64}$/i.test(e)?e:t;if(t&&history.replaceState(null,``,`${location.pathname}${location.search}`),n){try{window.sessionStorage.setItem(Ip,n)}catch{}return n}try{return window.sessionStorage.getItem(Ip)}catch{return null}}var zp=class{token=Rp();async headers(e=!1){return{Authorization:`Bearer ${this.token??``}`,"X-OpenPI-Web-Controller":await Fp(),...e?{"Content-Type":`application/json`}:{}}}async request(e,t={}){let{timeoutMs:n=15e3,timeoutMessage:r=`Request timed out. Please try again.`,...i}=t,a=new AbortController,o=!1,s=()=>a.abort();t.signal?.aborted&&s(),t.signal?.addEventListener(`abort`,s,{once:!0});let c=window.setTimeout(()=>{o=!0,a.abort()},n);try{let n=await fetch(e,{...i,signal:a.signal,headers:{...await this.headers(!!t.body),...t.headers}}),r=await n.json();if(a.signal.aborted)throw Error(`Request aborted`);if(!n.ok)throw new Lp(r.error||`Request failed (${n.status})`,n.status,r.code);return r}catch(e){throw o?Error(r):e}finally{window.clearTimeout(c),t.signal?.removeEventListener(`abort`,s)}}snapshot(e){let t=e?`?path=${encodeURIComponent(e)}`:``;return this.request(`/api/snapshot${t}`)}gitReview(e,t,n){return this.request(`/api/git-review?${new URLSearchParams({sessionId:e,path:t})}`,{signal:n,timeoutMessage:`Git review timed out. Please retry.`})}resolveArtifact(e,t,n,r){return this.request(`/api/artifacts/resolve`,{method:`POST`,body:JSON.stringify({sessionId:e,reference:t,parent:n,access:`read-file`}),signal:r})}artifactMetadata(e,t,n){return this.request(`/api/artifacts/content?${new URLSearchParams({sessionId:e,handle:t,metadata:`1`})}`,{signal:n})}artifactPreview(e,t,n){return this.request(`/api/artifacts/content?${new URLSearchParams({sessionId:e,handle:t})}`,{signal:n})}releaseArtifact(e,t){return this.request(`/api/artifacts/content?`+new URLSearchParams({sessionId:e,handle:t}),{method:`DELETE`})}async downloadArtifact(e,t){let n=new AbortController,r=()=>n.abort();t.addEventListener(`abort`,r,{once:!0}),t.aborted&&r();let i=window.setTimeout(r,15e3);try{let t=await fetch(`/api/artifacts/content?${new URLSearchParams({sessionId:e.sessionId,handle:e.handle,revision:e.revision,download:`1`})}`,{headers:await this.headers(),signal:n.signal});if(!t.ok){let e=await t.json();throw new Lp(e.error||`Download failed`,t.status,e.code)}let r=t.body?.getReader();if(!r)throw Error(`Download body unavailable`);let i=[],a=0;try{for(;;){let{value:e,done:t}=await r.read();if(t)break;if(a+=e.byteLength,a>20971520)throw Error(`Download exceeds the 20 MiB limit`);i.push(new Uint8Array(e))}}finally{await r.cancel()}return new Blob(i,{type:`application/octet-stream`})}finally{window.clearTimeout(i),t.removeEventListener(`abort`,r)}}chooseWorkspace(){return this.request(`/api/workspaces/select`,{method:`POST`})}openBrowser(e,t,n,r){return this.request(`/api/browser/open`,{method:`POST`,body:JSON.stringify({sessionId:e,url:t,...n}),signal:r})}browserState(e,t){return this.request(`/api/browser/state?${new URLSearchParams({sessionId:e})}`,{signal:t})}browserAction(e,t,n){let{type:r,...i}=t;return this.request(`/api/browser/action`,{method:`POST`,body:JSON.stringify({sessionId:e,action:r,...i}),signal:n})}async browserFrame(e,t){let n=await fetch(`/api/browser/frame?${new URLSearchParams({sessionId:e})}`,{headers:await this.headers(),signal:t});if(n.status===404)return null;if(!n.ok){let e=await n.json();throw new Lp(e.error||`Request failed (${n.status})`,n.status,e.code)}return n.blob()}createInteractiveTerminal(e,t,n,r){return this.request(`/api/terminal`,{method:`POST`,body:JSON.stringify({sessionId:e,cols:t,rows:n}),signal:r})}interactiveTerminal(e,t,n){return this.request(`/api/terminal?${new URLSearchParams({sessionId:e,id:t})}`,{signal:n})}closeInteractiveTerminal(e,t){return this.request(`/api/terminal?${new URLSearchParams({sessionId:e,id:t})}`,{method:`DELETE`})}writeInteractiveTerminal(e,t,n){return this.request(`/api/terminal/input`,{method:`POST`,body:JSON.stringify({sessionId:e,id:t,data:n})})}resizeInteractiveTerminal(e,t,n,r){return this.request(`/api/terminal/resize`,{method:`POST`,body:JSON.stringify({sessionId:e,id:t,cols:n,rows:r})})}async streamInteractiveTerminal(e,t,n,r,i){let a=new URLSearchParams({sessionId:e,id:t});n!==void 0&&a.set(`after`,String(n));let o=await fetch(`/api/terminal/events?${a}`,{headers:await this.headers(),signal:r});if(!o.ok){let e=await o.json().catch(()=>({}));throw new Lp(e.error||`Terminal stream failed (${o.status})`,o.status,e.code)}let s=o.body?.getReader();if(!s)throw Error(`Terminal stream is unavailable`);let c=new TextDecoder,l=``;try{for(;!r.aborted;){let{value:e,done:t}=await s.read();if(t)break;for(l+=c.decode(e,{stream:!0});;){let e=l.search(/\r?\n\r?\n/u);if(e<0)break;let t=l.slice(0,e),n=l.startsWith(`\r +`)]:[]).slice(0,20)}function Ep(e,t,n){let r=xp(e),i=yp(t?.details),a=Cp(e.name,t),o=a?.state??(n===`running`?`running`:t?.isError===!0?`failed`:t?.isError===!1?`returned`:`unknown`),s=[`bash`,`bg_start`,`bg_status`].includes(e.name),c=bp(t?.content??``,s),l=e.name===`read`?/\n\n(\[(?:\d+ more lines in file\. Use offset=\d+ to continue\.|Showing lines [^\n]+)\])$/u.exec(c.text):null,u=c.truncated||!!t?.truncation||yp(i.truncation).truncated===!0||e.evidenceTruncated===!0,d=s?wp(c.text):void 0,f=e.name===`read`?`file`:[`write`,`edit`].includes(e.name)?`change`:d?`test`:`terminal`,p=[`write`,`edit`].includes(e.name)&&t?.isError===!1&&typeof i.diff==`string`?bp(i.diff):void 0,m=typeof r.offset==`number`&&Number.isSafeInteger(r.offset)&&r.offset>0?r.offset:1;return{kind:f,state:o,processState:a?.state??(e.name===`bash`&&t?.isError===!1?`returned`:e.name===`bash`&&n===`running`?`running`:`unknown`),output:l?c.text.slice(0,l.index):c.text,truncated:u||p?.truncated===!0,readRecovery:l?.[1],numberLines:e.name===`read`&&t?.isError===!1&&yp(i.truncation).firstLineExceedsLimit!==!0&&!!c.text,path:typeof r.path==`string`?bp(r.path).text:void 0,resolvedPath:typeof r.resolvedPath==`string`?bp(r.resolvedPath).text:void 0,command:typeof r.command==`string`?bp(r.command).text:void 0,offset:m,diff:p?.text,tests:d,change:t?.isError===!1&&[`created`,`overwritten`,`unchanged`].includes(String(i.change))?String(i.change):void 0,evidenceUnavailable:typeof i.evidenceUnavailable==`string`?bp(i.evidenceUnavailable).text:void 0,exitCode:a&&`exitCode`in a?a.exitCode:void 0,signal:a&&`signal`in a?a.signal:void 0,recovery:typeof i.fullOutputPath==`string`?bp(i.fullOutputPath).text:void 0,rawArguments:bp(e.arguments).text,rawResult:bp(JSON.stringify({content:t?.content,details:t?.details,isError:t?.isError})).text}}var Dp=2097152,Op=16384,kp=new TextEncoder;function Ap(e){return kp.encode(JSON.stringify(e)).byteLength}var jp=`openpi.web.controller`,Mp,Np,Pp=0;function Fp(e){try{window.sessionStorage.setItem(jp,e)}catch{}return e}async function Ip(){let e=Pp;if(!window.navigator.locks)return Fp(window.crypto.randomUUID());let t=null;try{t=window.sessionStorage.getItem(jp)}catch{}let n=hp(t)?t:window.crypto.randomUUID();for(;;){let t=n,r=await new Promise(n=>{window.navigator.locks.request(`openpi.web.controller:${t}`,{ifAvailable:!0},async t=>{if(e!==Pp){n(null);return}if(!t){n(!1);return}await new Promise(e=>{Np=e,n(!0)})}).catch(()=>n(null))});if(e!==Pp)throw new DOMException(`Page was suspended`,`AbortError`);if(r===null)return Fp(window.crypto.randomUUID());if(r)return Fp(t);n=window.crypto.randomUUID()}}function Lp(){return Mp??=Ip()}window.addEventListener(`pagehide`,()=>{Pp++,Np?.(),Np=void 0,Mp=void 0}),window.addEventListener(`pageshow`,e=>{e.persisted&&Lp().catch(()=>{})});var Rp=`openpi.web.token`,zp=class extends Error{status;code;constructor(e,t,n){super(e),this.status=t,this.code=n,this.name=`WebApiError`}};function Bp(){let e=document.querySelector(`meta[name="openpi-web-token"]`)?.content,t=new URLSearchParams(location.hash.slice(1)).get(`token`),n=e&&/^[a-f0-9]{64}$/i.test(e)?e:t;if(t&&history.replaceState(null,``,`${location.pathname}${location.search}`),n){try{window.sessionStorage.setItem(Rp,n)}catch{}return n}try{return window.sessionStorage.getItem(Rp)}catch{return null}}var Vp=class{token=Bp();async headers(e=!1){return{Authorization:`Bearer ${this.token??``}`,"X-OpenPI-Web-Controller":await Lp(),...e?{"Content-Type":`application/json`}:{}}}async request(e,t={}){let{timeoutMs:n=15e3,timeoutMessage:r=`Request timed out. Please try again.`,...i}=t,a=new AbortController,o=!1,s=()=>a.abort();t.signal?.aborted&&s(),t.signal?.addEventListener(`abort`,s,{once:!0});let c=window.setTimeout(()=>{o=!0,a.abort()},n);try{let n=await fetch(e,{...i,signal:a.signal,headers:{...await this.headers(!!t.body),...t.headers}}),r=await n.json();if(a.signal.aborted)throw Error(`Request aborted`);if(!n.ok)throw new zp(r.error||`Request failed (${n.status})`,n.status,r.code);return r}catch(e){throw o?Error(r):e}finally{window.clearTimeout(c),t.signal?.removeEventListener(`abort`,s)}}snapshot(e,t){let n=new URLSearchParams;e&&n.set(`path`,e),t&&t.sessionPath===e&&(n.set(`historyAnchor`,t.entryId),n.set(`historySessionId`,t.sessionId));let r=n.size?`?${n}`:``;return this.request(`/api/snapshot${r}`)}async sessionHistory(e,t,n){return(await this.request(`/api/session/history?${new URLSearchParams({sessionId:e.sessionId,path:e.sessionPath,anchorEntryId:e.entryId,beforeEntryId:t})}`,{signal:n,timeoutMessage:`History request timed out. Please retry.`})).session}sessionItem(e,t,n,r,i){return this.request(`/api/session/item?${new URLSearchParams({sessionId:e,sessionPath:t,entryId:n,cursor:String(r)})}`,{signal:i,timeoutMessage:`Message request timed out. Please retry.`})}turnChanges(e,t,n,r,i){return this.request(`/api/turn-changes?${new URLSearchParams({sessionId:e,sessionPath:t,promptEntryId:n,...i?{filePath:i}:{}})}`,{signal:r,timeoutMessage:`Turn changes request timed out. Please retry.`})}gitReview(e,t,n,r){return this.request(`/api/git-review?${new URLSearchParams({sessionId:e,path:t,...r})}`,{signal:n,timeoutMessage:`Git review timed out. Please retry.`})}resolveArtifact(e,t,n,r){return this.request(`/api/artifacts/resolve`,{method:`POST`,body:JSON.stringify({sessionId:e,reference:t,parent:n,access:`read-file`}),signal:r})}artifactMetadata(e,t,n){return this.request(`/api/artifacts/content?${new URLSearchParams({sessionId:e,handle:t,metadata:`1`})}`,{signal:n})}artifactPreview(e,t,n){return this.request(`/api/artifacts/content?${new URLSearchParams({sessionId:e,handle:t})}`,{signal:n})}authorizeArtifact(e,t,n){return this.request(`/api/artifacts/authorize-file`,{method:`POST`,body:JSON.stringify({sessionId:e,reference:t,access:`read-external-file`}),signal:n})}releaseArtifact(e,t){return this.request(`/api/artifacts/content?`+new URLSearchParams({sessionId:e,handle:t}),{method:`DELETE`})}async downloadArtifact(e,t){let n=new AbortController,r=()=>n.abort();t.addEventListener(`abort`,r,{once:!0}),t.aborted&&r();let i=window.setTimeout(r,15e3);try{let t=await fetch(`/api/artifacts/content?${new URLSearchParams({sessionId:e.sessionId,handle:e.handle,revision:e.revision,download:`1`})}`,{headers:await this.headers(),signal:n.signal});if(!t.ok){let e=await t.json();throw new zp(e.error||`Download failed`,t.status,e.code)}let r=t.body?.getReader();if(!r)throw Error(`Download body unavailable`);let i=[],a=0;try{for(;;){let{value:e,done:t}=await r.read();if(t)break;if(a+=e.byteLength,a>20971520)throw Error(`Download exceeds the 20 MiB limit`);i.push(new Uint8Array(e))}}finally{await r.cancel()}return new Blob(i,{type:`application/octet-stream`})}finally{window.clearTimeout(i),t.removeEventListener(`abort`,r)}}chooseWorkspace(){return this.request(`/api/workspaces/select`,{method:`POST`})}openBrowser(e,t,n,r){return this.request(`/api/browser/open`,{method:`POST`,body:JSON.stringify({sessionId:e,url:t,...n}),signal:r})}browserState(e,t){return this.request(`/api/browser/state?${new URLSearchParams({sessionId:e})}`,{signal:t})}browserAction(e,t,n){let{type:r,...i}=t;return this.request(`/api/browser/action`,{method:`POST`,body:JSON.stringify({sessionId:e,action:r,...i}),signal:n})}async browserFrame(e,t){let n=await fetch(`/api/browser/frame?${new URLSearchParams({sessionId:e})}`,{headers:await this.headers(),signal:t});if(n.status===404)return null;if(!n.ok){let e=await n.json();throw new zp(e.error||`Request failed (${n.status})`,n.status,e.code)}return n.blob()}async streamBrowserFrames(e,t,n){let r=await fetch(`/api/browser/frames?${new URLSearchParams({sessionId:e})}`,{headers:await this.headers(),signal:t});if(!r.ok)throw new zp(`Browser stream unavailable`,r.status);let i=r.body?.getReader();if(!i)throw Error(`Browser stream unavailable`);let a=new TextDecoder,o=``;try{for(;!t.aborted;){let{value:e,done:t}=await i.read();if(t)break;if(o+=a.decode(e,{stream:!0}),o.length>26214400)throw Error(`Browser frame exceeds its limit`);let r=o.indexOf(` + +`);for(;r>=0;){let e=o.slice(0,r);if(o=o.slice(r+2),e.startsWith(`data: `)){let t=JSON.parse(e.slice(6));t.mimeType===`image/png`&&typeof t.data==`string`&&n(t)}r=o.indexOf(` + +`)}}}finally{await i.cancel().catch(()=>void 0)}}createInteractiveTerminal(e,t,n,r){return this.request(`/api/terminal`,{method:`POST`,body:JSON.stringify({sessionId:e,cols:t,rows:n}),signal:r})}interactiveTerminal(e,t,n){return this.request(`/api/terminal?${new URLSearchParams({sessionId:e,id:t})}`,{signal:n})}closeInteractiveTerminal(e,t){return this.request(`/api/terminal?${new URLSearchParams({sessionId:e,id:t})}`,{method:`DELETE`})}writeInteractiveTerminal(e,t,n){return this.request(`/api/terminal/input`,{method:`POST`,body:JSON.stringify({sessionId:e,id:t,data:n})})}resizeInteractiveTerminal(e,t,n,r){return this.request(`/api/terminal/resize`,{method:`POST`,body:JSON.stringify({sessionId:e,id:t,cols:n,rows:r})})}async streamInteractiveTerminal(e,t,n,r,i){let a=new URLSearchParams({sessionId:e,id:t});n!==void 0&&a.set(`after`,String(n));let o=await fetch(`/api/terminal/events?${a}`,{headers:await this.headers(),signal:r});if(!o.ok){let e=await o.json().catch(()=>({}));throw new zp(e.error||`Terminal stream failed (${o.status})`,o.status,e.code)}let s=o.body?.getReader();if(!s)throw Error(`Terminal stream is unavailable`);let c=new TextDecoder,l=``;try{for(;!r.aborted;){let{value:e,done:t}=await s.read();if(t)break;for(l+=c.decode(e,{stream:!0});;){let e=l.search(/\r?\n\r?\n/u);if(e<0)break;let t=l.slice(0,e),n=l.startsWith(`\r `,e)?4:2;l=l.slice(e+n);let r=t.split(/\r?\n/u).filter(e=>e.startsWith(`data:`)).map(e=>e.slice(5).trimStart()).join(` -`);r&&i(JSON.parse(r))}}}finally{await s.cancel().catch(()=>void 0)}}renameWorkspace(e,t){return this.request(`/api/workspaces`,{method:`PATCH`,body:JSON.stringify({path:e,name:t})})}removeWorkspace(e){return this.request(`/api/workspaces?path=${encodeURIComponent(e)}`,{method:`DELETE`})}createSession(e,t){return this.request(`/api/sessions`,{method:`POST`,body:JSON.stringify({workspacePath:e,commandId:t})})}selectSession(e){return this.request(`/api/sessions/select`,{method:`POST`,body:JSON.stringify({path:e})})}renameSession(e,t){return this.request(`/api/sessions`,{method:`PATCH`,body:JSON.stringify({path:e,name:t})})}archiveSession(e){return this.request(`/api/sessions/archive?path=${encodeURIComponent(e)}`,{method:`POST`})}unarchiveSession(e){return this.request(`/api/sessions/unarchive?path=${encodeURIComponent(e)}`,{method:`POST`})}thinking(e,t){return this.request(`/api/thinking?sessionId=${encodeURIComponent(e)}`,{signal:t})}setThinkingLevel(e,t,n){return this.request(`/api/thinking`,{method:`POST`,body:JSON.stringify({sessionId:e,sessionPath:n,level:t})})}setPlanMode(e,t,n){return this.request(`/api/plan`,{method:`POST`,body:JSON.stringify({sessionId:e,enabled:t,expectedRevision:n})})}trust(e,t){return this.request(`/api/trust?sessionId=${encodeURIComponent(e)}`,{signal:t})}providerAuth(e,t){return this.request(`/api/providers/auth-status?sessionId=${encodeURIComponent(e)}`,{signal:t})}commands(e,t){return this.request(`/api/commands?sessionId=${encodeURIComponent(e)}`,{signal:t})}settingsCatalog(e,t){return this.request(`/api/settings/catalog?sessionId=${encodeURIComponent(e)}`,{signal:t})}terminalDetail(e,t,n){return this.request(`/api/capabilities/detail?kind=background-terminals&id=${encodeURIComponent(t)}&sessionId=${encodeURIComponent(e)}`,{signal:n})}subagentDetail(e,t,n){return this.request(`/api/capabilities/detail?kind=subagents&id=${encodeURIComponent(t)}&sessionId=${encodeURIComponent(e)}`,{signal:n})}subagentAction(e,t,n){return this.request(`/api/capabilities/action`,{method:`POST`,body:JSON.stringify({sessionId:e,...t}),signal:n})}selectModel(e,t,n,r){return this.request(`/api/model`,{method:`POST`,body:JSON.stringify({provider:e,modelId:t,sessionId:n,sessionPath:r})})}searchModels(e,t,n){let r=new URLSearchParams({query:e,limit:`50`});return t&&r.set(`sessionId`,t),this.request(`/api/models?${r}`,{signal:n})}cancelActiveTurn(e){return this.request(`/api/turns/cancel`,{method:`POST`,body:JSON.stringify(e)})}pendingQuestions(e,t){return this.request(`/api/questions/pending?sessionId=${encodeURIComponent(e)}`,{signal:t})}async answerQuestions(e,t,n){try{return await this.request(`/api/questions/answer`,{method:`POST`,body:JSON.stringify({sessionId:e.sessionId,requestId:e.requestId,action:t===null?`dismiss`:`answer`,...t===null?{}:{answers:t}}),signal:n})}catch(e){if(e instanceof Lp&&e.code===`STALE_QUESTION`)return{state:`stale`};throw e}}async prompt(e,t,n,r,i=!1,a=[]){let o=await this.request(`/api/prompt`,{method:`POST`,body:JSON.stringify({sessionId:e,sessionPath:r,content:t,commandId:n,retry:i,images:a,controllerId:await Fp()}),timeoutMs:3e4,timeoutMessage:`Request timed out; admission may still be pending. Retry the same message to recover its receipt.`});if(typeof o.id!=`string`||!o.id||o.accepted!==!0)throw Error(`Invalid prompt receipt; retry the same message to recover its admission.`);return o}},Bp=(0,w.forwardRef)(function({sessionId:e,children:t,disabled:n=!1,onOpen:r,onClose:i},a){let{t:o}=Ln(),s=(0,w.useMemo)(()=>new zp,[]),[c,l]=(0,w.useState)(null),[u,d]=(0,w.useState)(null),[f,p]=(0,w.useState)(null),[m,h]=(0,w.useState)(!1),[g,_]=(0,w.useState)(null),v=(0,w.useRef)(null),y=(0,w.useRef)(0),b=(0,w.useRef)(null),x=(0,w.useRef)(null),S=(0,w.useRef)(new Set),C=(0,w.useRef)(void 0),T=(0,w.useCallback)((t,i)=>{if(n)return;r?.();let a=document.activeElement;a instanceof HTMLElement&&a.closest(`.artifact-panel`)||(v.current=a instanceof HTMLElement?a:null),y.current++,d(null),p(null),_(null),C.current=i,l({reference:t,parent:i,sessionId:e})},[n,r,e]),E=(0,w.useCallback)(()=>{y.current++,l(null),d(null),_(null),i?.(),v.current?.focus()},[i]);(0,w.useImperativeHandle)(a,()=>({close:E}),[E]),(0,w.useEffect)(()=>{c&&c.sessionId!==e&&(y.current++,l(null),d(null),_(null))},[e,c]),(0,w.useEffect)(()=>{!n||!c||(y.current++,l(null),d(null),_(null))},[n,c]),(0,w.useEffect)(()=>{if(!c||!e||c.sessionId!==e)return;let t=new AbortController,n,r=c.parent,i,a=!1,o,l=2e3;b.current?.focus();let u=async()=>{if(document.visibilityState===`hidden`){i=setTimeout(u,2e3);return}try{if(n||=(await s.resolveArtifact(e,c.reference,r,t.signal)).handle,r&&=(s.releaseArtifact(e,r).catch(()=>void 0),void 0),a){s.releaseArtifact(e,n).catch(()=>void 0);return}if(o&&(await s.artifactMetadata(e,n,t.signal)).identity===o){a||p(null),l=Math.min(l*2,3e4);return}let i=await s.artifactPreview(e,n,t.signal);a||(o=i.identity,l=2e3,d(i),p(null))}catch(e){l=Math.min(l*2,3e4),a||p(e instanceof Error?e.message:`Unable to read file`)}finally{a||(i=setTimeout(u,l))}};return u(),()=>{a=!0,t.abort(),clearTimeout(i),x.current?.abort(),h(!1),n&&n!==C.current&&s.releaseArtifact(e,n).catch(()=>void 0),r&&s.releaseArtifact(e,r).catch(()=>void 0);for(let e of S.current)URL.revokeObjectURL(e);S.current.clear()}},[s,c,e]);let D=(0,w.useMemo)(()=>({open:T}),[T]),O=u?.artifact.path??c?.reference??``,k=u?.artifact.name??O.split(/[\\/]/u).at(-1)??O,A=async()=>{if(!u||m)return;let e=new AbortController;x.current=e,h(!0);try{let t=await s.downloadArtifact(u.artifact,e.signal);if(e.signal.aborted)return;let n=URL.createObjectURL(t);S.current.add(n);let r=document.createElement(`a`);r.href=n,r.download=u.artifact.name,r.click(),setTimeout(()=>{URL.revokeObjectURL(n),S.current.delete(n)},1e3)}catch(t){e.signal.aborted||p(t instanceof Error?t.message:`Download failed`)}finally{e.signal.aborted||h(!1)}};return(0,K.jsxs)(op.Provider,{value:D,children:[t,c&&(0,K.jsxs)(`aside`,{className:`artifact-panel`,"aria-label":o(`filePreview`),onKeyDown:e=>{e.key===`Escape`&&(e.stopPropagation(),E())},children:[(0,K.jsxs)(`header`,{children:[(0,K.jsxs)(`div`,{children:[(0,K.jsxs)(`small`,{children:[o(`filePreview`),` · `,o(`artifactReadOnly`)]}),(0,K.jsx)(`h2`,{title:k,children:k})]}),(0,K.jsx)(`button`,{ref:b,type:`button`,"aria-label":o(`closePreview`),title:o(`closePreview`),onClick:E,children:(0,K.jsx)(mt,{"aria-hidden":`true`})})]}),(0,K.jsxs)(`div`,{className:`artifact-panel-body`,children:[(0,K.jsxs)(`div`,{className:`artifact-source`,children:[(0,K.jsx)(`code`,{title:O,children:O}),(0,K.jsx)(`button`,{type:`button`,"aria-label":o(`copyFilePath`),title:o(`copyFilePath`),onClick:()=>{let e=++y.current;sp(O).then(t=>{e===y.current&&_(t?`copied`:`failed`)})},children:g===`copied`?(0,K.jsx)(B,{"aria-hidden":`true`}):(0,K.jsx)(oe,{"aria-hidden":`true`})})]}),g&&(0,K.jsx)(`p`,{role:`status`,children:o(g===`copied`?`filePathCopied`:`copyFailed`)}),(0,K.jsx)(`p`,{className:`artifact-session`,children:o(`artifactSession`,{sessionId:e})}),(0,K.jsxs)(`div`,{className:`artifact-actions`,children:[(0,K.jsxs)(`button`,{type:`button`,onClick:()=>{y.current++,_(null),C.current=void 0,l(t=>t?{sessionId:e,reference:u?.artifact.path??t.reference,...u?{}:{parent:t.parent}}:null)},children:[(0,K.jsx)(Ze,{"aria-hidden":`true`}),` `,o(`refreshFile`)]}),(0,K.jsxs)(`button`,{type:`button`,disabled:!u||m,onClick:()=>void A(),children:[(0,K.jsx)(he,{"aria-hidden":`true`}),` `,o(m?`downloadingFile`:`downloadFile`)]})]}),f&&(0,K.jsxs)(`p`,{role:`status`,className:`evidence-warning`,children:[u?o(`artifactOlderPreview`):``,f]}),!u&&!f&&(0,K.jsx)(`p`,{role:`status`,children:o(`readingFile`)}),u&&(0,K.jsxs)(K.Fragment,{children:[(0,K.jsxs)(`p`,{className:`artifact-revision`,children:[o(`artifactVersion`),`:`,` `,(0,K.jsx)(`code`,{children:u.artifact.revision}),` ·`,` `,o(`artifactBytes`,{count:u.artifact.bytes})]}),u.truncated&&(0,K.jsx)(`p`,{className:`evidence-warning`,children:o(`artifactPreviewTruncated`)}),u.text===void 0?(0,K.jsx)(`p`,{children:o(`artifactUnsupported`)}):/\.(?:md|markdown)$/iu.test(u.artifact.name)?(0,K.jsx)(op.Provider,{value:{open:T,parent:u.artifact.handle},children:(0,K.jsx)(pp,{children:u.text})}):(0,K.jsx)(`section`,{"aria-label":`File preview content`,children:(0,K.jsx)(`pre`,{children:u.text})})]})]})]})]})}),Vp={},Hp;function Up(){if(Hp)return Vp;Hp=1,Object.defineProperty(Vp,"__esModule",{value:!0}),Vp.styleq=void 0;var e=new WeakMap,t=`$$css`;function n(n){var r,i,a;return n!=null&&(r=n.disableCache===!0,i=n.disableMix===!0,a=n.transform),function(){for(var n=[],o=``,s=null,c=``,l=r?null:e,u=Array(arguments.length),d=0;d0;){var f=u.pop();if(f!=null&&f!==!1){if(Array.isArray(f)){for(var p=0;p0&&(i.style=n),r!=null&&r!==``&&(i[`data-style-src`]=r),i}Object.freeze({});function Gp(e){return(e.style.anchorName??``).split(`,`).map(e=>e.trim()).filter(Boolean)}function Kp(e,t){e.style.anchorName=t.join(`, `)}function qp(e,t){let n=Gp(e);n.includes(t)||(n.push(t),Kp(e,n))}function Jp(e,t){Kp(e,Gp(e).filter(e=>e!==t))}var Yp=0,Xp=null,Zp=!1;function Qp(){Yp+=1}function $p(){Xp=Yp}function em(){Zp||typeof document>`u`||(Zp=!0,document.addEventListener(`pointerdown`,Qp,!0),document.addEventListener(`keydown`,Qp,!0),document.addEventListener(`click`,$p,!0))}function tm(){return em(),Yp}function nm(){return em(),Xp===Yp}var rm=new Set(`p.h1.h2.h3.h4.h5.h6.dt.pre.legend.data.dfn.meter.output.progress.option.optgroup.table.thead.tbody.tfoot.tr.colgroup.ul.ol.menu.dl.select.datalist.picture.hgroup.ruby.rt.rp.a.button.label.summary.span.em.strong.b.i.u.s.small.mark.code.kbd.samp.var.sub.sup.abbr.cite.q.time.bdi.bdo.ins.del`.split(`.`));function im(e){if(!e)return null;let t=null,n=e;for(;n;)rm.has(n.tagName.toLowerCase())&&(t=n),n=n.parentElement;return t?.parentElement??null}var am={"--color-accent":`var(--color-accent)`,"--color-accent-muted":`var(--color-accent-muted)`,"--color-on-accent":`var(--color-on-accent)`,"--color-neutral":`var(--color-neutral)`,"--color-background-surface":`var(--color-background-surface)`,"--color-background-body":`var(--color-background-body)`,"--color-overlay":`var(--color-overlay)`,"--color-overlay-hover":`var(--color-overlay-hover)`,"--color-overlay-pressed":`var(--color-overlay-pressed)`,"--color-background-muted":`var(--color-background-muted)`,"--color-text-primary":`var(--color-text-primary)`,"--color-text-secondary":`var(--color-text-secondary)`,"--color-text-disabled":`var(--color-text-disabled)`,"--color-text-accent":`var(--color-text-accent)`,"--color-on-dark":`var(--color-on-dark)`,"--color-on-light":`var(--color-on-light)`,"--color-icon-accent":`var(--color-icon-accent)`,"--color-icon-primary":`var(--color-icon-primary)`,"--color-icon-secondary":`var(--color-icon-secondary)`,"--color-icon-disabled":`var(--color-icon-disabled)`,"--color-background-card":`var(--color-background-card)`,"--color-background-popover":`var(--color-background-popover)`,"--color-background-inverted":`var(--color-background-inverted)`,"--color-background-error-inverted":`var(--color-background-error-inverted)`,"--color-success":`var(--color-success)`,"--color-success-muted":`var(--color-success-muted)`,"--color-on-success":`var(--color-on-success)`,"--color-error":`var(--color-error)`,"--color-error-muted":`var(--color-error-muted)`,"--color-on-error":`var(--color-on-error)`,"--color-warning":`var(--color-warning)`,"--color-warning-muted":`var(--color-warning-muted)`,"--color-on-warning":`var(--color-on-warning)`,"--color-border":`var(--color-border)`,"--color-border-emphasized":`var(--color-border-emphasized)`,"--color-skeleton":`var(--color-skeleton)`,"--color-track":`var(--color-track)`,"--color-shadow":`var(--color-shadow)`,"--color-tint-hover":`var(--color-tint-hover)`,"--color-background-blue":`var(--color-background-blue)`,"--color-border-blue":`var(--color-border-blue)`,"--color-icon-blue":`var(--color-icon-blue)`,"--color-text-blue":`var(--color-text-blue)`,"--color-background-cyan":`var(--color-background-cyan)`,"--color-border-cyan":`var(--color-border-cyan)`,"--color-icon-cyan":`var(--color-icon-cyan)`,"--color-text-cyan":`var(--color-text-cyan)`,"--color-background-gray":`var(--color-background-gray)`,"--color-border-gray":`var(--color-border-gray)`,"--color-icon-gray":`var(--color-icon-gray)`,"--color-text-gray":`var(--color-text-gray)`,"--color-background-green":`var(--color-background-green)`,"--color-border-green":`var(--color-border-green)`,"--color-icon-green":`var(--color-icon-green)`,"--color-text-green":`var(--color-text-green)`,"--color-background-orange":`var(--color-background-orange)`,"--color-border-orange":`var(--color-border-orange)`,"--color-icon-orange":`var(--color-icon-orange)`,"--color-text-orange":`var(--color-text-orange)`,"--color-background-pink":`var(--color-background-pink)`,"--color-border-pink":`var(--color-border-pink)`,"--color-icon-pink":`var(--color-icon-pink)`,"--color-text-pink":`var(--color-text-pink)`,"--color-background-purple":`var(--color-background-purple)`,"--color-border-purple":`var(--color-border-purple)`,"--color-icon-purple":`var(--color-icon-purple)`,"--color-text-purple":`var(--color-text-purple)`,"--color-background-red":`var(--color-background-red)`,"--color-border-red":`var(--color-border-red)`,"--color-icon-red":`var(--color-icon-red)`,"--color-text-red":`var(--color-text-red)`,"--color-background-teal":`var(--color-background-teal)`,"--color-border-teal":`var(--color-border-teal)`,"--color-icon-teal":`var(--color-icon-teal)`,"--color-text-teal":`var(--color-text-teal)`,"--color-background-yellow":`var(--color-background-yellow)`,"--color-border-yellow":`var(--color-border-yellow)`,"--color-icon-yellow":`var(--color-icon-yellow)`,"--color-text-yellow":`var(--color-text-yellow)`,__varGroupHash__:`xj0fimd`},om={"--spacing-0":`var(--spacing-0)`,"--spacing-0-5":`var(--spacing-0-5)`,"--spacing-1":`var(--spacing-1)`,"--spacing-1-5":`var(--spacing-1-5)`,"--spacing-2":`var(--spacing-2)`,"--spacing-3":`var(--spacing-3)`,"--spacing-4":`var(--spacing-4)`,"--spacing-5":`var(--spacing-5)`,"--spacing-6":`var(--spacing-6)`,"--spacing-7":`var(--spacing-7)`,"--spacing-8":`var(--spacing-8)`,"--spacing-9":`var(--spacing-9)`,"--spacing-10":`var(--spacing-10)`,"--spacing-11":`var(--spacing-11)`,"--spacing-12":`var(--spacing-12)`,__varGroupHash__:`x1kvdh9l`},sm={"--focus-outline-width":`var(--focus-outline-width)`,"--focus-outline-style":`var(--focus-outline-style)`,"--focus-outline-color":`var(--focus-outline-color)`,"--focus-outline-offset":`var(--focus-outline-offset)`,__varGroupHash__:`xzxs3qz`},cm={"--duration-fast-min":`var(--duration-fast-min)`,"--duration-fast":`var(--duration-fast)`,"--duration-fast-max":`var(--duration-fast-max)`,"--duration-medium-min":`var(--duration-medium-min)`,"--duration-medium":`var(--duration-medium)`,"--duration-medium-max":`var(--duration-medium-max)`,"--duration-slow-min":`var(--duration-slow-min)`,"--duration-slow":`var(--duration-slow)`,"--duration-slow-max":`var(--duration-slow-max)`,__varGroupHash__:`x14lkjui`},lm={"--ease-standard":`var(--ease-standard)`,__varGroupHash__:`xf09i69`},um={0:`spacing0`,.5:`spacing0_5`,1:`spacing1`,1.5:`spacing1_5`,2:`spacing2`,3:`spacing3`,4:`spacing4`,5:`spacing5`,6:`spacing6`,8:`spacing8`,10:`spacing10`},dm={0:{kZCmMZ:`x18gyask`,kwRFfy:`x1s0aq8i`,kLKAdn:`x1ydh6w3`,kGO01o:`x1l20ajd`,$$css:!0},1:{kZCmMZ:`x1vsv5vr`,kwRFfy:`x1nryj5t`,kLKAdn:`xfsso4q`,kGO01o:`xy143xn`,$$css:!0},2:{kZCmMZ:`x12gdq22`,kwRFfy:`x1djylfy`,kLKAdn:`x1xye8es`,kGO01o:`x1wesfrj`,$$css:!0},3:{kZCmMZ:`x126nfab`,kwRFfy:`x1t818jl`,kLKAdn:`x1vlblms`,kGO01o:`xvmdzux`,$$css:!0},4:{kZCmMZ:`x1rey3nv`,kwRFfy:`xnjyzlh`,kLKAdn:`x1oa1p4a`,kGO01o:`x1awphl8`,$$css:!0},5:{kZCmMZ:`x1blguxw`,kwRFfy:`xdbrk9v`,kLKAdn:`xx7rijo`,kGO01o:`x1hk98q`,$$css:!0},6:{kZCmMZ:`x31w388`,kwRFfy:`x1we12cn`,kLKAdn:`x1adxfkp`,kGO01o:`xjpqqx5`,$$css:!0},8:{kZCmMZ:`x1j3hnjz`,kwRFfy:`x1q91b2g`,kLKAdn:`xoxd1wu`,kGO01o:`x2oz4g1`,$$css:!0},10:{kZCmMZ:`xqp078j`,kwRFfy:`x160ivqr`,kLKAdn:`xk6660b`,kGO01o:`x2izi54`,$$css:!0},"0.5":{kZCmMZ:`x138rykx`,kwRFfy:`x1le3yxw`,kLKAdn:`xbx876j`,kGO01o:`xij103a`,$$css:!0},"1.5":{kZCmMZ:`xfti1ec`,kwRFfy:`x17hk9do`,kLKAdn:`x1kwdpsa`,kGO01o:`x1opdxmq`,$$css:!0}},fm={0:{"--container-padding-inline-start":`x1gu2k80`,"--container-padding-inline-end":`x91ghl5`,$$css:!0},1:{"--container-padding-inline-start":`x1cvlban`,"--container-padding-inline-end":`x2oyxnl`,$$css:!0},2:{"--container-padding-inline-start":`x1xlrr2o`,"--container-padding-inline-end":`xcas3b9`,$$css:!0},3:{"--container-padding-inline-start":`xfdwxua`,"--container-padding-inline-end":`xu0ipoa`,$$css:!0},4:{"--container-padding-inline-start":`x1dlhslv`,"--container-padding-inline-end":`xs0pscg`,$$css:!0},5:{"--container-padding-inline-start":`x1s81nki`,"--container-padding-inline-end":`xgkj7vj`,$$css:!0},6:{"--container-padding-inline-start":`x1ep0dkj`,"--container-padding-inline-end":`x94cj42`,$$css:!0},8:{"--container-padding-inline-start":`xw1diwv`,"--container-padding-inline-end":`x1b9k1pi`,$$css:!0},10:{"--container-padding-inline-start":`xserb3f`,"--container-padding-inline-end":`xx5lg5w`,$$css:!0},"0.5":{"--container-padding-inline-start":`x14ws0sr`,"--container-padding-inline-end":`x1wz3t3y`,$$css:!0},"1.5":{"--container-padding-inline-start":`x176g23i`,"--container-padding-inline-end":`xntetml`,$$css:!0}},pm={0:{"--container-padding-block-start":`x1i3qcxz`,$$css:!0},1:{"--container-padding-block-start":`xnsckjb`,$$css:!0},2:{"--container-padding-block-start":`xa8b4fq`,$$css:!0},3:{"--container-padding-block-start":`x11k4f5r`,$$css:!0},4:{"--container-padding-block-start":`xm01sq8`,$$css:!0},5:{"--container-padding-block-start":`xp8wdkl`,$$css:!0},6:{"--container-padding-block-start":`x1hmud4d`,$$css:!0},8:{"--container-padding-block-start":`xfv60at`,$$css:!0},10:{"--container-padding-block-start":`x17h9kl7`,$$css:!0},"0.5":{"--container-padding-block-start":`xvdf9ev`,$$css:!0},"1.5":{"--container-padding-block-start":`x1kbx601`,$$css:!0}},mm={0:{"--container-padding-block-end":`xkunwnr`,$$css:!0},1:{"--container-padding-block-end":`x57a7ii`,$$css:!0},2:{"--container-padding-block-end":`x1lsgcmx`,$$css:!0},3:{"--container-padding-block-end":`x1q3ppug`,$$css:!0},4:{"--container-padding-block-end":`x4hfsld`,$$css:!0},5:{"--container-padding-block-end":`xbib2ws`,$$css:!0},6:{"--container-padding-block-end":`x1q8d17g`,$$css:!0},8:{"--container-padding-block-end":`x8lgq76`,$$css:!0},10:{"--container-padding-block-end":`x15vxphk`,$$css:!0},"0.5":{"--container-padding-block-end":`x1cao3zv`,$$css:!0},"1.5":{"--container-padding-block-end":`xv53x8y`,$$css:!0}},hm={0:{kZCmMZ:`x18gyask`,$$css:!0},1:{kZCmMZ:`x1vsv5vr`,$$css:!0},2:{kZCmMZ:`x12gdq22`,$$css:!0},3:{kZCmMZ:`x126nfab`,$$css:!0},4:{kZCmMZ:`x1rey3nv`,$$css:!0},5:{kZCmMZ:`x1blguxw`,$$css:!0},6:{kZCmMZ:`x31w388`,$$css:!0},8:{kZCmMZ:`x1j3hnjz`,$$css:!0},10:{kZCmMZ:`xqp078j`,$$css:!0},"0.5":{kZCmMZ:`x138rykx`,$$css:!0},"1.5":{kZCmMZ:`xfti1ec`,$$css:!0}},gm={0:{kwRFfy:`x1s0aq8i`,$$css:!0},1:{kwRFfy:`x1nryj5t`,$$css:!0},2:{kwRFfy:`x1djylfy`,$$css:!0},3:{kwRFfy:`x1t818jl`,$$css:!0},4:{kwRFfy:`xnjyzlh`,$$css:!0},5:{kwRFfy:`xdbrk9v`,$$css:!0},6:{kwRFfy:`x1we12cn`,$$css:!0},8:{kwRFfy:`x1q91b2g`,$$css:!0},10:{kwRFfy:`x160ivqr`,$$css:!0},"0.5":{kwRFfy:`x1le3yxw`,$$css:!0},"1.5":{kwRFfy:`x17hk9do`,$$css:!0}},_m={0:{kLKAdn:`x1ydh6w3`,$$css:!0},1:{kLKAdn:`xfsso4q`,$$css:!0},2:{kLKAdn:`x1xye8es`,$$css:!0},3:{kLKAdn:`x1vlblms`,$$css:!0},4:{kLKAdn:`x1oa1p4a`,$$css:!0},5:{kLKAdn:`xx7rijo`,$$css:!0},6:{kLKAdn:`x1adxfkp`,$$css:!0},8:{kLKAdn:`xoxd1wu`,$$css:!0},10:{kLKAdn:`xk6660b`,$$css:!0},"0.5":{kLKAdn:`xbx876j`,$$css:!0},"1.5":{kLKAdn:`x1kwdpsa`,$$css:!0}},vm={0:{kGO01o:`x1l20ajd`,$$css:!0},1:{kGO01o:`xy143xn`,$$css:!0},2:{kGO01o:`x1wesfrj`,$$css:!0},3:{kGO01o:`xvmdzux`,$$css:!0},4:{kGO01o:`x1awphl8`,$$css:!0},5:{kGO01o:`x1hk98q`,$$css:!0},6:{kGO01o:`xjpqqx5`,$$css:!0},8:{kGO01o:`x2oz4g1`,$$css:!0},10:{kGO01o:`x2izi54`,$$css:!0},"0.5":{kGO01o:`xij103a`,$$css:!0},"1.5":{kGO01o:`x1opdxmq`,$$css:!0}},ym={reset:{"--container-padding-inline-start":`xrhngw9`,"--container-padding-inline-end":`xjsfl84`,"--container-padding-block-start":`x1047aw6`,"--container-padding-block-end":`xax9j7h`,"--layout-padding-outer-x":`xdt8ak2`,"--layout-padding-outer-y":`x1rs4lu4`,"--layout-padding-inner-x":`x1qfll2g`,"--layout-padding-inner-y":`xyvxpqs`,"--_section-padding-propagated":`x1f17rg1`,$$css:!0}},bm=h(),xm={keoZOQ:`x1vhfslr`,k1K539:`xlm3tn6`,$$css:!0},Sm={base:{keoZOQ:`xdj266r`,k1K539:`xat24cr`,keTefX:`x1lziwak`,k71WvV:`x14z9mp`,kLKAdn:`xexx8yu`,kGO01o:`x18d9i69`,kZCmMZ:`x1c1uobl`,kwRFfy:`xyri2b`,kMzoRj:`xc342km`,ksu8eU:`xng3xce`,kVQacm:`x1rea2x4`,kMv6JI:`x9ynric`,kGuDYH:`xjm74w1`,kLWn49:`xw6l6zx`,kWkggS:`xjbqb8w`,$$css:!0},fixed:{kVAEAm:`xixxii4`,$$css:!0},offsetBlock:e=>[xm,{"--x-marginBlockStart":(e=>typeof e==`number`?e+`px`:e??void 0)(e),"--x-marginBlockEnd":(e=>typeof e==`number`?e+`px`:e??void 0)(e)}],offsetInline:e=>[{keTefX:e==null?e:`x4lel18`,k71WvV:e==null?e:`x1c9tiao`,$$css:!0},{"--x-marginInlineStart":(e=>typeof e==`number`?e+`px`:e??void 0)(e),"--x-marginInlineEnd":(e=>typeof e==`number`?e+`px`:e??void 0)(e)}]};function Cm(e){return typeof e==`number`?`${e}px`:e}function wm(e,t){let n=e.ownerDocument.defaultView;if(!n)return{};let r=n.getComputedStyle(e),i=n.getComputedStyle(t);return{...r.direction!==i.direction&&{direction:r.direction},...r.writingMode!==i.writingMode&&{writingMode:r.writingMode}}}function Tm(e=`above`,t=`center`){if(e===`above`||e===`below`){let n=e===`above`?`self-block-start`:`self-block-end`;return t===`start`?`${n} span-self-inline-end`:t===`end`?`${n} span-self-inline-start`:n}let n=e===`start`?`self-inline-start`:`self-inline-end`;return t===`start`?`${n} span-self-block-end`:t===`end`?`${n} span-self-block-start`:n}function Em(e=`above`,t=`center`){let n=`flip-block, flip-inline, flip-block flip-inline`;if(t!==`center`)return n;if(e===`above`||e===`below`){let[t,r]=e===`above`?[`top`,`bottom`]:[`bottom`,`top`];return`${n}, ${t} span-left, ${t} span-right, ${r} span-left, ${r} span-right`}let[r,i]=e===`start`?[`left`,`right`]:[`right`,`left`];return`${n}, ${r} span-top, ${r} span-bottom, ${i} span-top, ${i} span-bottom`}function Dm(e){let{mode:t,onShow:n,onHide:r,lightDismiss:i=!1}=e,a=t===`context`?e.lazyMount??!1:!1,o=(0,w.useId)(),s=`--astryx-layer-${o.replace(/:/g,``)}`,[c,l]=(0,w.useState)(!1),u=(0,w.useRef)(null),d=(0,w.useRef)(null),f=(0,w.useRef)(null),p=(0,w.useRef)(null),m=(0,w.useRef)(null),[h,g]=(0,w.useState)(null),_=(0,w.useRef)(!1),v=(0,w.useRef)(!1),y=(0,w.useRef)(null),b=(0,w.useRef)(null),x=(0,w.useCallback)(()=>{let e=tm();return y.current===e},[]),S=(0,w.useCallback)(e=>{typeof e.showPopover==`function`?e.showPopover({source:f.current??void 0}):e.style.display=`block`,d.current=e},[]),C=(0,w.useCallback)(e=>{if(t!==`context`)return!0;let n=m.current;if(n===null)return!1;let r=n.portalTarget??p.current?.parentElement??null;return e.parentElement===r},[t]),T=(0,w.useCallback)(()=>{if(t!==`context`)return;let e=p.current,n=e?.parentElement??null;if(!e||!n)return;let r=im(n),i={portalTarget:r,portalStyle:r?wm(e,r):{}};m.current=i,g(i)},[t]),E=(0,w.useCallback)(()=>{t!==`context`||!a||(m.current=null,g(null))},[t,a]),D=(0,w.useCallback)(()=>{if(x())return;let e=u.current,t=e&&C(e)?e:null;if(!t){_.current=!0,T();return}v.current||(S(t),v.current=!0,l(!0),n?.())},[n,T,S,C,x]),O=(0,w.useCallback)(()=>{if(_.current=!1,v.current){let e=u.current;d.current=null,v.current=!1,e&&(typeof e.hidePopover==`function`?e.hidePopover():e.style.display=`none`),l(!1),r?.()}E()},[r,E]),k=(0,w.useCallback)(e=>{f.current&&f.current!==e&&Jp(f.current,s),e&&qp(e,s),f.current=e},[s]),A=(0,w.useCallback)(e=>{if(b.current?.(),nm())return;y.current=tm();let t=e.defaultView,n=null,r=()=>{y.current=null,e.removeEventListener(`click`,i,!0),n!==null&&(t?.clearTimeout(n),n=null),b.current===r&&(b.current=null)},i=()=>{e.removeEventListener(`click`,i,!0),t?n=t.setTimeout(()=>{n=null,b.current===r&&r()},0):r()};e.addEventListener(`click`,i,!0),b.current=r},[]);(0,w.useEffect)(()=>(tm(),()=>b.current?.()),[]);let j=(0,w.useCallback)(e=>{e.newState===`closed`&&v.current&&(d.current=null,v.current=!1,A(e.currentTarget?.ownerDocument??document),l(!1),r?.(),E())},[r,E,A]),M=(0,w.useRef)(null),N=(0,w.useRef)(null),P=(0,w.useCallback)((e,t)=>{M.current&&N.current&&(M.current!==e||N.current!==t)&&(M.current.removeEventListener(`toggle`,N.current),M.current=null,N.current=null),e&&M.current!==e&&(e.addEventListener(`toggle`,t),M.current=e,N.current=t)},[]),F=(0,w.useCallback)(e=>{u.current=e,P(e,j),e&&_.current?(_.current=!1,D()):e&&v.current&&d.current!==e&&C(e)&&S(e)},[j,P,D,S,C]),I=(0,w.useCallback)(e=>{p.current=e,e&&(!a||_.current||v.current)&&T()},[a,T]);(0,w.useEffect)(()=>(u.current&&P(u.current,j),()=>{M.current&&N.current&&(M.current.removeEventListener(`toggle`,N.current),M.current=null,N.current=null)}),[j,P]);let L=(0,w.useCallback)((e,t)=>{let n=(0,K.jsx)(`template`,{ref:I});if(h===null)return(0,K.jsx)(K.Fragment,{children:n});let{placement:r=`above`,alignment:a=`center`,positioning:c=`anchor`,offset:l,role:u,"aria-label":d,xstyle:f,className:p,style:m,as:g=`div`,onMouseEnter:_,onMouseLeave:v}=t||{},y=c===`custom`?{positionAnchor:s}:{positionAnchor:s,positionArea:Tm(r,a),positionTryFallbacks:Em(r,a)},b=c===`anchor`&&l?r===`above`||r===`below`?Sm.offsetBlock(Cm(l)):Sm.offsetInline(Cm(l)):null,x=Y(Sm.base,ym.reset,b,f),S=p?`${p} ${x.className??``}`:x.className,C=(0,K.jsx)(g,{ref:F,id:o,role:u,"aria-label":d,popover:i?`auto`:`manual`,className:S,style:{...x.style,...y,...h.portalStyle,...m},onMouseEnter:_,onMouseLeave:v,children:e});return(0,K.jsxs)(K.Fragment,{children:[n,h.portalTarget?(0,bm.createPortal)(C,h.portalTarget):C]})},[s,h,o,i,F,I]),R=(0,w.useCallback)((e,t)=>{let{x:n,y:r,xstyle:a,className:s,style:c}=t,l={top:r,left:n},u=Y(Sm.base,ym.reset,Sm.fixed,a),d=s?`${s} ${u.className??``}`:u.className;return(0,K.jsx)(`div`,{ref:F,id:o,popover:i?`auto`:`manual`,className:d,style:{...u.style,...l,...c},children:e})},[F,o,i]),z=(0,w.useMemo)(()=>({ref:k,anchorId:s,show:D,hide:O,isOpen:c,wasJustDismissed:x,id:o,render:L}),[k,s,D,O,c,x,o,L]),B=(0,w.useMemo)(()=>({ref:void 0,show:D,hide:O,isOpen:c,wasJustDismissed:x,id:o,render:R}),[D,O,c,x,o,R]);return t===`context`?z:B}function Om(e){let t=Dm(e);return(0,w.useMemo)(()=>{let{wasJustDismissed:e,...n}=t;return n},[t])}function km(e){return Dm(e)}var Am=`button:not([disabled]), a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable]:not([contenteditable="false"]), audio[controls], video[controls], iframe, details > summary:first-child`,jm=(0,w.createContext)(0);jm.displayName=`LayerDepthContext`;function Mm(){return(0,w.use)(jm)}function Nm({children:e}){let t=(0,w.use)(jm);return(0,K.jsx)(jm,{value:t+1,children:e})}Nm.displayName=`LayerDepthProvider`;var Pm=229;function Fm(e){return e.isComposing===!0||e.keyCode===Pm}var Im=[],Lm=new WeakMap,Rm=0,zm=!1;function Bm(e){let t=Lm.get(e);if(t!==void 0)return t;let n=Rm++;return Lm.set(e,n),n}function Vm(e,t){if(e.depth!==t.depth)return e.depth-t.depth;let n=e.getContainer?.()??null,r=t.getContainer?.()??null;if(n!=null&&r!=null&&n!==r){if(r.contains(n))return 1;if(n.contains(r))return-1}return e.seq-t.seq}function Hm(e){return e.isPresent?.()??!0}var Um=!1;function Wm(){return Um}function Gm(){Um=!0}function Km(){Um=!1}function qm(){let e=null;for(let t of Im)Hm(t)&&(e==null||Vm(t,e)>0)&&(e=t);return e}function Jm(e){return qm()?.token===e}function Ym(){let e=qm();return e!=null&&(e.behavior===`block`||e.dismiss(),!0)}function Xm(e){if(e.key===`Escape`){if(Fm(e)){qm()!=null&&e.preventDefault();return}e.defaultPrevented||Ym()&&e.preventDefault()}}function Zm(){zm||typeof document>`u`||(document.addEventListener(`keydown`,Xm),document.addEventListener(`compositionstart`,Gm,!0),document.addEventListener(`compositionend`,Km,!0),document.addEventListener(`blur`,Km,!0),zm=!0)}function Qm(){!zm||typeof document>`u`||(document.removeEventListener(`keydown`,Xm),document.removeEventListener(`compositionstart`,Gm,!0),document.removeEventListener(`compositionend`,Km,!0),document.removeEventListener(`blur`,Km,!0),Um=!1,zm=!1)}function $m(e){let t={...e,seq:Bm(e.token)};return Im.push(t),Zm(),()=>{let e=Im.indexOf(t);e!==-1&&Im.splice(e,1),Im.length===0&&Qm()}}function eh(e){let{isActive:t,onDismiss:n,escapeBehavior:r=`close`,getContainer:i,isPresent:a,isEnabled:o=!0}=e,s=Mm(),c=(0,w.useRef)({}),l=(0,w.useRef)(n),u=(0,w.useRef)(i),d=(0,w.useRef)(a);(0,w.useEffect)(()=>{l.current=n,u.current=i,d.current=a});let f=t&&o;return(0,w.useEffect)(()=>{if(f)return $m({token:c.current,depth:s,behavior:r,getContainer:()=>u.current?.()??null,isPresent:()=>d.current?.()??!0,dismiss:()=>l.current()})},[f,s,r]),{shouldDismissOnCloseRequest:(0,w.useCallback)(()=>f&&!Wm()&&Jm(c.current),[f])}}var th=0;function nh(e){if(e.hasAttribute(`inert`)||e.closest(`[inert]`)||e.hidden||e.closest(`[hidden]`)||e.closest(`[aria-hidden="true"]`))return!1;if(typeof window<`u`&&window.getComputedStyle){let t=window.getComputedStyle(e);if(t.visibility===`hidden`||t.display===`none`)return!1}return!0}function rh(e){return Array.from(e.querySelectorAll(Am)).filter(nh)}function ih(e){try{e.focus()}catch{}return document.activeElement===e}function ah(e){let t=rh(e);for(let e of t)if(ih(e))return!0;return!1}function oh(e){let t=rh(e);for(let e=t.length-1;e>=0;e--)if(ih(t[e]))return!0;return!1}function sh(e){let{isActive:t,onEscape:n}=e,r=(0,w.useRef)(null),i=(0,w.useRef)(null),a=(0,w.useRef)(!1),o=t&&n!=null;eh({isActive:o,onDismiss:()=>{n?.()},getContainer:()=>r.current}),(0,w.useEffect)(()=>{if(o)return th+=1,()=>{--th}},[o]);let s=(0,w.useCallback)(()=>{r.current&&ah(r.current)},[]);return(0,w.useEffect)(()=>{if(!t)return;let e=document.activeElement,n=r.current;return()=>{let t=document.activeElement;(t==null||t===document.body||t===document.documentElement||n!=null&&n.contains(t))&&e!=null&&e.isConnected&&typeof e.focus==`function`&&e.focus()}},[t]),(0,w.useEffect)(()=>{if(!t)return;let e=e=>{let t=r.current;if(!t)return;let n=e.target;if(t.contains(n))i.current=n;else if(a.current){let e=ah(t);e&&i.current===document.activeElement?oh(t):!e&&i.current instanceof HTMLElement&&t.contains(i.current)&&ih(i.current),i.current=document.activeElement}a.current=!1};return document.addEventListener(`focus`,e,!0),()=>{document.removeEventListener(`focus`,e,!0)}},[t]),(0,w.useEffect)(()=>{if(!t)return;let e=e=>{let t=r.current;if(t&&e.key===`Tab`){a.current=!0;let n=rh(t);if(n.length===0){let n=document.activeElement;if(!(n instanceof HTMLElement)||!t.contains(n))return;e.preventDefault(),i.current=n,a.current=!1;return}let r=n[0],o=n[n.length-1];e.shiftKey?document.activeElement===r&&(e.preventDefault(),o.focus()):document.activeElement===o&&(e.preventDefault(),r.focus())}};return document.addEventListener(`keydown`,e),()=>{document.removeEventListener(`keydown`,e)}},[t,n]),{containerRef:r,focusFirst:s}}var ch=`keyboard`,lh=!1;function uh(){ch=`pointer`}function dh(e){e.metaKey||e.altKey||e.ctrlKey||(ch=`keyboard`)}function fh(){lh||typeof document>`u`||(lh=!0,document.addEventListener(`pointerdown`,uh,{capture:!0,passive:!0}),document.addEventListener(`keydown`,dh,{capture:!0,passive:!0}))}function ph(){return ch}var mh=new Set([`touch`,`pen`]),hh=new Set([`button`,`checkbox`,`combobox`,`link`,`menuitem`,`menuitemcheckbox`,`menuitemradio`,`option`,`radio`,`searchbox`,`slider`,`spinbutton`,`switch`,`tab`,`textbox`]);function gh(e){let t=e.getAttribute(`role`);if(t!=null&&t!==``)return hh.has(t);switch(e.tagName){case`BUTTON`:case`INPUT`:case`LABEL`:case`SELECT`:case`SUMMARY`:case`TEXTAREA`:return!0;case`A`:case`AREA`:return e.hasAttribute(`href`);default:return _h(e)}}function _h(e){if(e.isContentEditable===!0)return!0;let t=e.getAttribute(`contenteditable`);return t!=null&&t!==`false`}function vh(e){let{touchTrigger:t,isEnabled:n,isControlled:r,isOpen:i,layerId:a,triggerRef:o,show:s,hide:c}=e,l=(0,w.useRef)(!1),u=(0,w.useRef)(i);u.current=i;let d=(0,w.useRef)(c);d.current=c;let f=(0,w.useRef)(a);f.current=a;let p=(0,w.useRef)(!1),m=(0,w.useRef)(null);(0,w.useEffect)(()=>{fh()},[]);let h=(0,w.useCallback)(()=>{p.current=!1;let e=m.current;e!=null&&(m.current=null,document.removeEventListener(`pointerdown`,e,!0))},[]),g=(0,w.useCallback)(()=>{if(p.current=!0,m.current!=null)return;let e=e=>{let t=e.target;(t==null||o.current?.contains(t)!==!0&&document.getElementById(f.current)?.contains(t)!==!0)&&(h(),d.current())};m.current=e,document.addEventListener(`pointerdown`,e,!0)},[o,h]);(0,w.useEffect)(()=>h,[h]);let _=(0,w.useCallback)(()=>l.current&&ph()===`pointer`,[]),v=(0,w.useCallback)(e=>{l.current=e.pointerType===`touch`},[]),y=(0,w.useCallback)(e=>{let i=mh.has(e.pointerType);if(l.current=i,!i||r)return!1;let a=o.current;return(t===`auto`?a!=null&&gh(a)?`none`:`tap`:t)===`none`||!n||u.current||p.current?(h(),c(),!0):(g(),s(),!0)},[t,n,r,o,s,c,g,h]),b=(0,w.useRef)(i);return(0,w.useEffect)(()=>{b.current&&!i&&h(),b.current=i},[i,h]),{isTouchPointerRef:l,isTouchInteraction:_,handlePointerEnter:v,handlePointerDown:y,clearTapOpen:h}}cm[`--duration-fast-max`],lm[`--ease-standard`];var yh={below:{kKVMdj:`xl1vlw0 x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0},above:{kKVMdj:`x3psbcj x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0},end:{kKVMdj:`x1i331go x1vxsm5i x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0},start:{kKVMdj:`xck01x9 x18lne9g x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0}},bh=`astryx`,xh=bh,Sh=bh,Ch=bh;function wh(e){return`${xh}-${e}`}function Th(e){return`data-${Sh}-${e}`}function Eh(e){return`--${Ch}-${e}`}function Dh(e){return`data-${e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase()}`}function Oh(e,t){return/^\d/.test(t)?`${e}-${t}`:t}function kh(e,t){let n=[wh(e)];if(t)for(let[e,r]of Object.entries(t))r!=null&&n.push(Oh(e,String(r)));return n.join(` `)}function Ah(e){let t={};if(e)for(let[n,r]of Object.entries(e))r!=null&&(t[Dh(n)]=String(r));return t}function X(e,t,n){let r=kh(e,t),i=n?.legacyNames?.map(e=>wh(e))??[];return{className:i.length>0?[r,...i].join(` `):r,...Ah(t)}}var jh=100,Mh={container:{kWkggS:`x19aspcf`,kMwMTN:`xrkvqaz`,kaIpWk:`x1hviunn`,kMv6JI:`x9ynric`,kGuDYH:`xjm74w1`,kLWn49:`xw6l6zx`,$$css:!0}};function Nh(e){return e.hasAttribute(`tabindex`)?e.tabIndex>=0:[`A`,`BUTTON`,`INPUT`,`SELECT`,`TEXTAREA`].includes(e.tagName)?!e.disabled:!!e.isContentEditable}function Ph(e={}){let{placement:t=`above`,alignment:n=`center`,delay:r=200,hideDelay:i=0,focusTrigger:a=`auto`,touchTrigger:o=`auto`,isEnabled:s=!0,isOpen:c,isDefaultOpen:l=!1,onShow:u,onHide:d}=e,f=Om({mode:`context`,onShow:u,onHide:d}),p=Mh.container,m=(0,w.useRef)(null),h=(0,w.useRef)(null),g=(0,w.useRef)(null),_=(0,w.useCallback)(()=>{m.current&&=(clearTimeout(m.current),null),h.current&&=(clearTimeout(h.current),null)},[]),v=(0,w.useCallback)(()=>{_(),f.show()},[_,f]),y=(0,w.useCallback)(()=>{_(),f.hide()},[_,f]),b=vh({touchTrigger:o,isEnabled:s,isControlled:c!==void 0,isOpen:f.isOpen,layerId:f.id,triggerRef:g,show:v,hide:y}),x=(0,w.useCallback)(()=>{!s||c===!1||(_(),m.current=setTimeout(()=>{f.show()},r))},[s,c,_,f,r]),S=(0,w.useCallback)(()=>{c!==!0&&(_(),h.current=setTimeout(()=>{f.hide()},i>0?i:jh))},[c,_,f,i]),C=(0,w.useCallback)(()=>{h.current&&=(clearTimeout(h.current),null)},[]),T=(0,w.useCallback)(()=>{b.isTouchPointerRef.current||x()},[b,x]),E=(0,w.useCallback)(()=>{b.isTouchPointerRef.current||S()},[b,S]),D=(0,w.useCallback)(e=>{s&&(b.isTouchInteraction()||e.target.matches(`:focus-visible`)&&(_(),f.show()))},[s,b,_,f]),O=(0,w.useCallback)(()=>{S()},[S]),k=(0,w.useCallback)(e=>{b.handlePointerDown(e)||c===void 0&&(_(),f.hide())},[b,c,_,f]),{handlePointerEnter:A,clearTapOpen:j}=b,M=(0,w.useCallback)(e=>{g.current&&(g.current.removeEventListener(`mouseenter`,T),g.current.removeEventListener(`mouseleave`,E),g.current.removeEventListener(`focusin`,D),g.current.removeEventListener(`focusout`,O),g.current.removeEventListener(`pointerenter`,A),g.current.removeEventListener(`pointerdown`,k)),e&&(e.addEventListener(`pointerenter`,A),e.addEventListener(`mouseenter`,T),e.addEventListener(`mouseleave`,E),e.addEventListener(`pointerdown`,k),(a===`always`||a===`auto`&&Nh(e))&&(e.addEventListener(`focusin`,D),e.addEventListener(`focusout`,O))),g.current=e},[a,T,E,D,O,A,k]),N=(0,w.useCallback)(e=>{f.ref(e),M(e)},[f,M]);(0,w.useEffect)(()=>()=>{_()},[_]),(0,w.useEffect)(()=>{l&&f.show()},[]),(0,w.useEffect)(()=>{c!==void 0&&(c?(_(),f.show()):(_(),f.hide()))},[c,_,f]),eh({isActive:!0,isPresent:()=>{let e=typeof document>`u`?null:document.getElementById(f.id);if(e==null)return!1;try{return e.matches(`:popover-open`)}catch{return f.isOpen}},onDismiss:()=>{if(_(),j(),c!==void 0){d?.();return}f.hide()}});let P=(0,w.useCallback)((e,r)=>{let i=r?.placement??t,a={placement:i,alignment:r?.alignment??n,offset:om[`--spacing-1`],role:`tooltip`,xstyle:[p,yh[i]],className:X(`tooltip`).className,onMouseEnter:C,onMouseLeave:S};return f.render((0,K.jsx)(`div`,{className:`xfsso4q xy143xn x12gdq22 x1djylfy xw5ewwj x13faqbe`,children:e}),a)},[f,t,n,p,C,S]);return{ref:N,positionRef:f.ref,interactionRef:M,anchorId:f.anchorId,describedBy:f.id,renderTooltip:P}}var Fh={primary:{kMwMTN:`x1tgivj0`,$$css:!0},secondary:{kMwMTN:`xv1l7n4`,$$css:!0},disabled:{kMwMTN:`xnbbluu`,$$css:!0},placeholder:{kMwMTN:`xv1l7n4`,$$css:!0},accent:{kMwMTN:`xjse4m1`,$$css:!0},inherit:{kMwMTN:`x1heor9g`,$$css:!0}},Ih={normal:{k63SB2:`x1sodnla`,$$css:!0},medium:{k63SB2:`x1e4wzip`,$$css:!0},semibold:{k63SB2:`x2mo6ok`,$$css:!0},bold:{k63SB2:`x1lvx875`,$$css:!0}},Lh={body:{k63SB2:`xxovm9e`,$$css:!0},large:{k63SB2:`x149oux8`,$$css:!0},label:{k63SB2:`xmhvcl5`,$$css:!0},code:{k63SB2:`xx3eeay`,$$css:!0},supporting:{k63SB2:`xv8on6e`,$$css:!0},"display-1":{k63SB2:`x1txul5o`,$$css:!0},"display-2":{k63SB2:`x1y36c3f`,$$css:!0},"display-3":{k63SB2:`x1on40hk`,$$css:!0},inherit:{k63SB2:`x1pd3egz`,$$css:!0}},Rh={body:{kGuDYH:`xjm74w1`,kLWn49:`xw6l6zx`,$$css:!0},large:{kGuDYH:`x18juvz8`,kLWn49:`xf74fhv`,$$css:!0},label:{kGuDYH:`xcr08ib`,kLWn49:`x1kq96og`,$$css:!0},code:{kGuDYH:`xp03k98`,kLWn49:`x17iicif`,kMv6JI:`x9m5x89`,$$css:!0},supporting:{kGuDYH:`x141an7d`,kLWn49:`x1ltkj2j`,$$css:!0},"display-1":{kGuDYH:`xsub3ws`,kLWn49:`x112ttwr`,$$css:!0},"display-2":{kGuDYH:`x1yego12`,kLWn49:`xh0iwvy`,$$css:!0},"display-3":{kGuDYH:`xlgnzhf`,kLWn49:`x1ujwuaq`,$$css:!0},inherit:{kGuDYH:`x1qlqyl8`,kLWn49:`x15bjb6t`,$$css:!0}},zh={"4xs":{kGuDYH:`xxc45ev`,$$css:!0},"3xs":{kGuDYH:`x10p7juq`,$$css:!0},"2xs":{kGuDYH:`x16a80zy`,$$css:!0},xsm:{kGuDYH:`x51wmvv`,$$css:!0},sm:{kGuDYH:`x1eqnyfr`,$$css:!0},base:{kGuDYH:`x1j29vfg`,$$css:!0},lg:{kGuDYH:`xc7cgfe`,$$css:!0},xl:{kGuDYH:`x1wqms48`,$$css:!0},"2xl":{kGuDYH:`xhs0kqb`,$$css:!0},"3xl":{kGuDYH:`x10srzze`,$$css:!0},"4xl":{kGuDYH:`xqcvi3d`,$$css:!0}},Bh={inline:{k1xSpc:`xt0psk2`,$$css:!0},block:{k1xSpc:`x1lliihq`,$$css:!0}},Vh={singleLine:{kVQacm:`xb3r6kr`,kg5iWk:`xlyipyv`,khDVqt:`xuxw1ft`,k1xSpc:`x1lliihq`,$$css:!0},multiLine:{kVQacm:`xb3r6kr`,k1xSpc:`x104kibb`,kgKLqz:`x1ua5tub`,$$css:!0}},Hh={"break-word":{kTgw9:`x1lldw8n`,kHjlTd:`x1mzt3pk`,$$css:!0},"break-all":{kTgw9:`x1yn0g08`,$$css:!0}},Uh={wrap:{kN2L0X:`xk4td0m`,$$css:!0},nowrap:{kN2L0X:`xebhuq6`,$$css:!0},balance:{kN2L0X:`x1w2vvpw`,$$css:!0},pretty:{kN2L0X:`x1fzhlzt`,$$css:!0}},Wh={enabled:{kxwWH2:`x1b2iylo`,kzeHkT:`xwgcxoh`,k1xSpc:`x1lliihq`,$$css:!0}},Gh={strikethrough:{kybGjl:`xmqliwb`,$$css:!0}},Kh={enabled:{kcqcaj:`xss6m8b`,$$css:!0}},qh={start:{k9WMMc:`x1yc453h`,$$css:!0},center:{k9WMMc:`x2b8uid`,$$css:!0},end:{k9WMMc:`xp4054r`,$$css:!0}},Jh={content:{ks0D6T:`xw5ewwj`,kTgw9:`x13faqbe`,$$css:!0}},Yh=null,Xh=new Map;function Zh(){return typeof ResizeObserver>`u`?null:(Yh||=new ResizeObserver(e=>{for(let t of e){let e=Xh.get(t.target);e&&e(t)}}),Yh)}function Qh(e,t){Xh.set(e,t),Zh()?.observe(e),t({target:e})}function $h(e){Xh.delete(e),Yh&&(Yh.unobserve(e),Xh.size===0&&(Yh.disconnect(),Yh=null))}function eg(e){let{maxLines:t}=e,[n,r]=(0,w.useState)(!1),[i,a]=(0,w.useState)(``),o=(0,w.useRef)(null),s=(0,w.useCallback)(e=>{if(t===0){r(!1);return}if(a(e.textContent??``),t===1)r(e.scrollWidth>e.offsetWidth);else{let t=e.scrollHeight;try{let n=document.createRange();n.selectNodeContents(e),t=n.getBoundingClientRect().height,n.detach()}catch{}r(t>e.offsetHeight)}},[t]);return{ref:(0,w.useCallback)(e=>{o.current&&$h(o.current),o.current=e,e&&t>0?typeof ResizeObserver<`u`?Qh(e,()=>{s(e)}):s(e):(r(!1),a(``))},[t,s]),isTruncated:n,fullText:i}}function tg(e){return e===`base`?``:e.split(`+`).map(e=>{let[t,n]=e.split(`:`);return n===void 0?`.${t}`:/^\d/.test(n)?`.${t}-${n}`:`.${n}`}).join(``)}function ng(e,t){let n={...e,...t},r=[e.className,t.className].filter(Boolean).join(` `);r?n.className=r:delete n.className;let i=t.style&&e.style?{...e.style,...t.style}:t.style||e.style;return i?n.style=i:delete n.style,n}function rg(e,t,n,r){if(typeof e==`string`){let i=e,a=t??{className:``},o=n,s=a.className?`${i} ${a.className}`:i;o&&(s=`${s} ${o}`);let c=r&&a.style?{...a.style,...r}:r||a.style;return{...a,className:s,style:c}}let i=ng(e,typeof t==`string`?{className:t}:t??{});return typeof n==`string`?i=ng(i,{className:n}):n!=null&&(i=ng(i,{style:n})),r!=null&&(i=ng(i,{style:r})),i}function ig(...e){return t=>{let n=[];for(let r of e)if(typeof r==`function`){let e=r(t);n.push(typeof e==`function`?e:()=>r(null))}else if(r!=null){let e=r;e.current=t,n.push(()=>{e.current=null})}if(t!=null&&n.length>0)return()=>{for(let e of n)e()}}}function ag(...e){return t=>{for(let n of e)if(n?.(t),t.defaultPrevented)return}}function og(e){return e!=null&&typeof e!=`boolean`&&e!==``}function sg(...e){let t=e.flatMap(e=>typeof e==`string`?e.trim().split(/\s+/):[]).filter(Boolean);if(t.length!==0)return Array.from(new Set(t)).join(` `)}function cg(e,t=[],n){return{ariaLabelledBy:n?sg(n.labelID,e):void 0,ariaDescribedBy:sg(n?.describedByIDs,...t)}}var lg={kbCHJM:`x1nrll8i`,k3aq6I:`xsqj5wx`,$$css:!0},ug={mirror:{k3aq6I:`xgtlewx`,$$css:!0},centerInline:e=>[lg,{"--x-transform":`translate(-50%, ${e})`==null?void 0:`translate(-50%, ${e})`}]},dg=sm[`--focus-outline-width`],fg=sm[`--focus-outline-style`],pg=sm[`--focus-outline-color`];sm[`--focus-outline-offset`],`${dg}${fg}${pg}`;var mg={focusVisible:{kMeerF:`x1k57tk5 x1vidyx5`,k3XXqK:`x1t137rt x1jhp3zv`,kjBf7l:`xx47ajj`,kInvED:`x1wfwxd8 x1vwwbsn`,$$css:!0},focusWithin:{kMeerF:`x1k57tk5 x11j6mr8`,k3XXqK:`x1t137rt xciu248`,kjBf7l:`x1uy843r`,kInvED:`x1wfwxd8 x1jumodi`,$$css:!0},focusWithinFirstChild:{kMeerF:`x1k57tk5 xmmisi4`,k3XXqK:`x1t137rt xfd04fr`,kjBf7l:`xobxmqy`,kInvED:`x1wfwxd8 x2vr5qc`,$$css:!0},suppressed:{kMeerF:`x1k57tk5`,k3XXqK:`x1t137rt`,kInvED:`x1wfwxd8`,$$css:!0},publishFocusVisibleVars:{"--_focus-outline":`x17wzz1v xqih627`,"--_focus-outline-offset":`xgzxwq1 xqchwus`,$$css:!0},focusWithinOrPublished:{kI3sdo:`xaw4jrz x16s19ga`,kInvED:`x1kvmbwa x1jumodi`,$$css:!0}};function hg(e){return(...t)=>Y(e,...t)}var gg={focusVisible:hg(mg.focusVisible),focusWithin:hg(mg.focusWithin),focusWithinFirstChild:hg(mg.focusWithinFirstChild),suppressed:hg(mg.suppressed),publishFocusVisibleVars:hg(mg.publishFocusVisibleVars),focusWithinOrPublished:hg(mg.focusWithinOrPublished)};function _g(e,t,n,r,i,a){return(0,w.useMemo)(()=>ig(e,t,n,r,i,a),[e,t,n,r,i,a])}var vg=`modulepreload`,yg=function(e){return`/`+e},bg={},xg=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=yg(t,n),t=s(t),t in bg)return;bg[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:vg,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},Sg=(0,w.lazy)(async()=>xg(()=>Promise.resolve().then(()=>zb).then(e=>({default:e.Tooltip})),void 0)),Cg={body:`primary`,large:`primary`,label:`primary`,supporting:`secondary`,code:`primary`,"display-1":`primary`,"display-2":`primary`,"display-3":`primary`,inherit:`inherit`};function wg(e){return e in Rh?e:`body`}function Tg(e){return e in Fh?e:`primary`}function Eg({type:e=`body`,size:t,color:n,weight:r,display:i=`inline`,maxLines:a=0,hasTruncateTooltip:o=!0,wordBreak:s,textWrap:c,justify:l=`start`,hasCapsize:u=!1,hasStrikethrough:d=!1,hasTabularNumbers:f=!1,xstyle:p,className:m,style:h,as:g=`span`,children:_,ref:v,...y}){let b=n??Cg[e]??`primary`,x=wg(e),S=Tg(b),C=s??(a===1?`break-all`:`break-word`),T=a>0||u?`block`:i,E=eg({maxLines:a}),D=typeof o==`string`?o:`above`,O=a>0&&o!==!1&&E.isTruncated,k=(0,w.useRef)(null),A=_g(v,E.ref,k),j=a>1?{WebkitLineClamp:a}:void 0;return(0,K.jsxs)(K.Fragment,{children:[(0,K.jsx)(g,{ref:A,...rg(X(`text`,{type:e,size:t,color:b}),Y(Fh[S],Rh[x],t&&zh[t],Lh[x],r&&Ih[r],a===1?Vh.singleLine:a>1?Vh.multiLine:Bh[T],a>0&&Hh[C],c&&Uh[c],l!==`start`&&qh[l],u&&Wh.enabled,d&&Gh.strikethrough,f&&Kh.enabled,p),m,{...h,...j}),...y,children:_}),O&&(0,K.jsx)(w.Suspense,{fallback:null,children:(0,K.jsx)(Sg,{anchorRef:k,content:(0,K.jsx)(`span`,{...Y(Jh.content),children:E.fullText}),placement:D})})]})}Eg.displayName=`Text`;var Dg=.375,Og={sm:{diameter:10,border:2},md:{diameter:14,border:3},lg:{diameter:18,border:3},xl:{diameter:28,border:4}},kg=[`--_spinner-ring-diameter`,`--_spinner-ring-stroke`],Ag=`--_spinner-box-size`;function jg(){if(!(typeof CSS>`u`||typeof CSS.registerProperty!=`function`))for(let e of kg)try{CSS.registerProperty({name:e,syntax:``,inherits:!0,initialValue:`0px`})}catch{}}jg();var Mg=new Set,Ng=!1;function Pg(){Ng=!1;let e=[];for(let t of Mg)e.push(...t.getAnimations());Mg.clear();for(let t of e)t.startTime=0}function Fg(e){if(e!=null&&typeof e.getAnimations==`function`)return Mg.add(e),Ng||(Ng=!0,requestAnimationFrame(Pg)),()=>{Mg.delete(e)}}var Ig={wrapper:{k1xSpc:`x3nfvp2`,kXwgrk:`xdt5ytf`,kGNEyG:`x6s0dn4`,kOIVth:`x1txdalj`,$$css:!0},spinner:{k1xSpc:`xwz0xwf`,kgQiWS:`x1ku5rj1`,kVQacm:`xb3r6kr`,kXLuUW:`xxymvpz`,"--_spinner-ring-diameter":`x2lq4xu`,"--_spinner-ring-stroke":`x10qssua`,"--_spinner-box-size":`x69vvuq`,$$css:!0},circle:{kDwRjp:`xbh8q5q`,kU5bRw:`x1owpc8m`,kPFa82:`xio8zfp`,kfJifR:`xgw3ha0`,$$css:!0},track:{kjVXCG:`xalkhop`,$$css:!0}},Lg={sm:{"--spinner-diameter":`x11wm0hx`,"--spinner-stroke-width":`xls98ul`,$$css:!0},md:{"--spinner-diameter":`x15pu9g6`,"--spinner-stroke-width":`xr0wkrm`,$$css:!0},lg:{"--spinner-diameter":`x1w424tr`,"--spinner-stroke-width":`xr0wkrm`,$$css:!0},xl:{"--spinner-diameter":`x1orj1z9`,"--spinner-stroke-width":`x7y2bof`,$$css:!0}},Rg={default:{"--spinner-color":`xt1b8mc`,"--spinner-track-color":`xspt9s2`,$$css:!0},subtle:{"--spinner-color":`x1jevo6s`,"--spinner-track-color":`xspt9s2`,$$css:!0},onMedia:{"--spinner-color":`x13u6jys`,"--spinner-track-color":`x1ufpcf6`,$$css:!0},inherit:{"--spinner-color":`x1uzk0gl`,"--spinner-track-color":`xbfzqbu`,$$css:!0}},zg={default:{kDd8S0:`x1g350g8`,$$css:!0},subtle:{kDd8S0:`x1g350g8`,$$css:!0},onMedia:{kDd8S0:`x1smxkh6`,$$css:!0},inherit:{kDd8S0:`x7bo2k`,$$css:!0}};function Bg({size:e=`md`,shade:t=`default`,label:n,xstyle:r,className:i,style:a,"aria-label":o,"data-testid":s,ref:c,...l}){let{border:u,diameter:d}=Og[e],f=d+u*2,p=f/2,m=Math.PI*d,h=m*Dg,g=n!=null,_=(0,w.useId)(),v=g&&typeof n==`string`&&o==null,y=(0,K.jsx)(`span`,{ref:g?void 0:c,role:`status`,"aria-label":v?void 0:o??(typeof n==`string`?n:void 0)??`Loading`,"aria-labelledby":v?_:void 0,"data-testid":g?void 0:s,...g?{}:l,...rg(g?``:X(`spinner`,{size:e,shade:t}),Y(Ig.spinner,!g&&Lg[e],!g&&Rg[t],!g&&r),g?void 0:i,{...g?{}:a,width:`var(${Ag}, ${f}px)`,height:`var(${Ag}, ${f}px)`}),children:(0,K.jsxs)(`svg`,{ref:Fg,width:f,height:f,viewBox:`0 0 ${f} ${f}`,"aria-hidden":`true`,className:`xlp1x4z x1lliihq x1so62im x1rea2x4 x14qxm4i xnh0sag xa4qsjk x1ka1v4i x1esw782`,children:[(0,K.jsx)(`circle`,{cx:p,cy:p,r:d/2,strokeWidth:u,...Y(Ig.circle,Ig.track,zg[t])}),(0,K.jsx)(`circle`,{cx:p,cy:p,r:d/2,strokeWidth:u,strokeDasharray:`${h} ${m-h}`,transform:`rotate(-90 ${p} ${p})`,className:`xbh8q5q x1owpc8m xio8zfp xgw3ha0 xtve3lm x1vy8frr`})]})});return g?(0,K.jsxs)(`div`,{ref:c,"data-testid":s,...l,...rg(X(`spinner`,{size:e,shade:t}),Y(Ig.wrapper,Lg[e],Rg[t],r),i,a),children:[y,typeof n==`string`?(0,K.jsx)(Eg,{id:_,type:`body`,weight:`bold`,children:n}):n]}):y}Bg.displayName=`Spinner`;function Vg({children:e,as:t=`span`,ref:n,...r}){return(0,w.createElement)(t,{ref:n,...r,className:`x10l6tqk x1i1rx1s xjm9jq1 xkdpibf x1717udv xb3r6kr xzpqnlu xuxw1ft xng3xce x13vifvy x1o0tod x47corl x87ps6o`},e)}Vg.displayName=`VisuallyHidden`;var Hg=`data-astryx-edge-comp`,Ug={keTefX:`x1kftj4p`,k71WvV:`x92e2el`,$$css:!0},Wg={inset:e=>[Ug,{"--x-126w13d":(e=>typeof e==`number`?e+`px`:e??void 0)(`calc(-1 * ${e})`),"--x-4drmnv":(e=>typeof e==`number`?e+`px`:e??void 0)(`calc(-1 * ${e})`)}]},Gg=(0,w.createContext)(null);Gg.displayName=`SizeContext`;function Kg(e,t=`md`){let n=(0,w.use)(Gg);return e??n??t}Gg.Provider;var qg=(0,w.createContext)(null);qg.displayName=`ButtonGroupContext`;function Jg(){return(0,w.use)(qg)}var Yg=(0,w.createContext)(null);Yg.displayName=`LinkContext`;function Xg(e){function t({href:t,ref:n,...r}){return(0,w.createElement)(e,{ref:n,href:t,to:t,...r})}return t.displayName=`LinkWithTo(${typeof e==`string`?e:e.displayName||e.name||`Component`})`,t}function Zg(e){let t=(0,w.use)(Yg),n=e??t?.component??`a`;return(0,w.useMemo)(()=>n===`a`?`a`:Xg(n),[n])}`${am[`--color-overlay-hover`]}${am[`--color-overlay-hover`]}`,`${am[`--color-overlay-pressed`]}${am[`--color-overlay-pressed`]}`,`${am[`--color-neutral`]}${am[`--color-neutral`]}`;var Qg={backgroundColor:{kWkggS:`xjbqb8w x1anq1lc xoevpu5 xprvw0a`,$$css:!0},backgroundImage:{kKwaWg:`x7uyq82 xmvprkv xetgvay`,$$css:!0},backgroundImageOnNeutral:{kKwaWg:`x14bno8m xzmimnh x1otsd3y xo3fi6e`,$$css:!0}};function $g(e,t){let n=t&&t.cache?t.cache:l_,r=t&&t.serializer?t.serializer:s_;return(t&&t.strategy?t.strategy:i_)(e,{cache:n,serializer:r})}function e_(e){return e==null||typeof e==`number`||typeof e==`boolean`}function t_(e,t,n,r){let i=e_(r)?r:n(r),a=t.get(i);return a===void 0&&(a=e.call(this,r),t.set(i,a)),a}function n_(e,t,n){let r=Array.prototype.slice.call(arguments,3),i=n(r),a=t.get(i);return a===void 0&&(a=e.apply(this,r),t.set(i,a)),a}function r_(e,t,n,r,i){return n.bind(t,e,r,i)}function i_(e,t){let n=e.length===1?t_:n_;return r_(e,this,n,t.cache.create(),t.serializer)}function a_(e,t){return r_(e,this,n_,t.cache.create(),t.serializer)}function o_(e,t){return r_(e,this,t_,t.cache.create(),t.serializer)}var s_=function(){return JSON.stringify(arguments)},c_=class{constructor(){this.cache=Object.create(null)}get(e){return this.cache[e]}set(e,t){this.cache[e]=t}},l_={create:function(){return new c_}},u_={variadic:a_,monadic:o_},d_=/(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;function f_(e){let t={};return e.replace(d_,e=>{let n=e.length;switch(e[0]){case`G`:t.era=n===4?`long`:n===5?`narrow`:`short`;break;case`y`:t.year=n===2?`2-digit`:`numeric`;break;case`Y`:case`u`:case`U`:case`r`:throw RangeError("`Y/u/U/r` (year) patterns are not supported, use `y` instead");case`q`:case`Q`:throw RangeError("`q/Q` (quarter) patterns are not supported");case`M`:case`L`:t.month=[`numeric`,`2-digit`,`short`,`long`,`narrow`][n-1];break;case`w`:case`W`:throw RangeError("`w/W` (week) patterns are not supported");case`d`:t.day=[`numeric`,`2-digit`][n-1];break;case`D`:case`F`:case`g`:throw RangeError("`D/F/g` (day) patterns are not supported, use `d` instead");case`E`:t.weekday=n===4?`long`:n===5?`narrow`:`short`;break;case`e`:if(n<4)throw RangeError("`e..eee` (weekday) patterns are not supported");t.weekday=[`short`,`long`,`narrow`,`short`][n-3];break;case`c`:if(n<4)throw RangeError("`c..ccc` (weekday) patterns are not supported");t.weekday=[`short`,`long`,`narrow`,`short`][n-3];break;case`a`:t.hour12=!0;break;case`b`:case`B`:throw RangeError("`b/B` (period) patterns are not supported, use `a` instead");case`h`:t.hourCycle=`h12`,t.hour=[`numeric`,`2-digit`][n-1];break;case`H`:t.hourCycle=`h23`,t.hour=[`numeric`,`2-digit`][n-1];break;case`K`:t.hourCycle=`h11`,t.hour=[`numeric`,`2-digit`][n-1];break;case`k`:t.hourCycle=`h24`,t.hour=[`numeric`,`2-digit`][n-1];break;case`j`:case`J`:case`C`:throw RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");case`m`:t.minute=[`numeric`,`2-digit`][n-1];break;case`s`:t.second=[`numeric`,`2-digit`][n-1];break;case`S`:case`A`:throw RangeError("`S/A` (second) patterns are not supported, use `s` instead");case`z`:t.timeZoneName=n<4?`short`:`long`;break;case`Z`:case`O`:case`v`:case`V`:case`X`:case`x`:throw RangeError("`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead")}return``}),t}var p_=/[\t-\r \x85\u200E\u200F\u2028\u2029]/i;function m_(e){if(e.length===0)throw Error(`Number skeleton cannot be empty`);let t=e.split(p_).filter(e=>e.length>0),n=[];for(let e of t){let t=e.split(`/`);if(t.length===0)throw Error(`Invalid number skeleton`);let[r,...i]=t;for(let e of i)if(e.length===0)throw Error(`Invalid number skeleton`);n.push({stem:r,options:i})}return n}function h_(e){return e.replace(/^(.*?)-/,``)}var g_=/^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g,__=/^(@+)?(\+|#+)?[rs]?$/g,v_=/(\*)(0+)|(#+)(0+)|(0+)/g,y_=/^(0+)$/;function b_(e){let t={};return e[e.length-1]===`r`?t.roundingPriority=`morePrecision`:e[e.length-1]===`s`&&(t.roundingPriority=`lessPrecision`),e.replace(__,function(e,n,r){return typeof r==`string`?r===`+`?t.minimumSignificantDigits=n.length:n[0]===`#`?t.maximumSignificantDigits=n.length:(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length+(typeof r==`string`?r.length:0)):(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length),``}),t}function x_(e){switch(e){case`sign-auto`:return{signDisplay:`auto`};case`sign-accounting`:case`()`:return{currencySign:`accounting`};case`sign-always`:case`+!`:return{signDisplay:`always`};case`sign-accounting-always`:case`()!`:return{signDisplay:`always`,currencySign:`accounting`};case`sign-except-zero`:case`+?`:return{signDisplay:`exceptZero`};case`sign-accounting-except-zero`:case`()?`:return{signDisplay:`exceptZero`,currencySign:`accounting`};case`sign-never`:case`+_`:return{signDisplay:`never`}}}function S_(e){let t;if(e[0]===`E`&&e[1]===`E`?(t={notation:`engineering`},e=e.slice(2)):e[0]===`E`&&(t={notation:`scientific`},e=e.slice(1)),t){let n=e.slice(0,2);if(n===`+!`?(t.signDisplay=`always`,e=e.slice(2)):n===`+?`&&(t.signDisplay=`exceptZero`,e=e.slice(2)),!y_.test(e))throw Error(`Malformed concise eng/scientific notation`);t.minimumIntegerDigits=e.length}return t}function C_(e){return x_(e)||{}}function w_(e){let t={};for(let n of e){switch(n.stem){case`percent`:case`%`:t.style=`percent`;continue;case`%x100`:t.style=`percent`,t.scale=100;continue;case`currency`:t.style=`currency`,t.currency=n.options[0];continue;case`group-off`:case`,_`:t.useGrouping=!1;continue;case`precision-integer`:case`.`:t.maximumFractionDigits=0;continue;case`measure-unit`:case`unit`:t.style=`unit`,t.unit=h_(n.options[0]);continue;case`compact-short`:case`K`:t.notation=`compact`,t.compactDisplay=`short`;continue;case`compact-long`:case`KK`:t.notation=`compact`,t.compactDisplay=`long`;continue;case`scientific`:t={...t,notation:`scientific`,...n.options.reduce((e,t)=>({...e,...C_(t)}),{})};continue;case`engineering`:t={...t,notation:`engineering`,...n.options.reduce((e,t)=>({...e,...C_(t)}),{})};continue;case`notation-simple`:t.notation=`standard`;continue;case`unit-width-narrow`:t.currencyDisplay=`narrowSymbol`,t.unitDisplay=`narrow`;continue;case`unit-width-short`:t.currencyDisplay=`code`,t.unitDisplay=`short`;continue;case`unit-width-full-name`:t.currencyDisplay=`name`,t.unitDisplay=`long`;continue;case`unit-width-iso-code`:t.currencyDisplay=`symbol`;continue;case`scale`:t.scale=parseFloat(n.options[0]);continue;case`rounding-mode-floor`:t.roundingMode=`floor`;continue;case`rounding-mode-ceiling`:t.roundingMode=`ceil`;continue;case`rounding-mode-down`:t.roundingMode=`trunc`;continue;case`rounding-mode-up`:t.roundingMode=`expand`;continue;case`rounding-mode-half-even`:t.roundingMode=`halfEven`;continue;case`rounding-mode-half-down`:t.roundingMode=`halfTrunc`;continue;case`rounding-mode-half-up`:t.roundingMode=`halfExpand`;continue;case`integer-width`:if(n.options.length>1)throw RangeError(`integer-width stems only accept a single optional option`);n.options[0].replace(v_,function(e,n,r,i,a,o){if(n)t.minimumIntegerDigits=r.length;else if(i&&a)throw Error(`We currently do not support maximum integer digits`);else if(o)throw Error(`We currently do not support exact integer digits`);return``});continue}if(y_.test(n.stem)){t.minimumIntegerDigits=n.stem.length;continue}if(g_.test(n.stem)){if(n.options.length>1)throw RangeError(`Fraction-precision stems only accept a single optional option`);n.stem.replace(g_,function(e,n,r,i,a,o){return r===`*`?t.minimumFractionDigits=n.length:i&&i[0]===`#`?t.maximumFractionDigits=i.length:a&&o?(t.minimumFractionDigits=a.length,t.maximumFractionDigits=a.length+o.length):(t.minimumFractionDigits=n.length,t.maximumFractionDigits=n.length),``});let e=n.options[0];e===`w`?t={...t,trailingZeroDisplay:`stripIfInteger`}:e&&(t={...t,...b_(e)});continue}if(__.test(n.stem)){t={...t,...b_(n.stem)};continue}let e=x_(n.stem);e&&(t={...t,...e});let r=S_(n.stem);r&&(t={...t,...r})}return t}var T_=function(e){return e[e.EXPECT_ARGUMENT_CLOSING_BRACE=1]=`EXPECT_ARGUMENT_CLOSING_BRACE`,e[e.EMPTY_ARGUMENT=2]=`EMPTY_ARGUMENT`,e[e.MALFORMED_ARGUMENT=3]=`MALFORMED_ARGUMENT`,e[e.EXPECT_ARGUMENT_TYPE=4]=`EXPECT_ARGUMENT_TYPE`,e[e.INVALID_ARGUMENT_TYPE=5]=`INVALID_ARGUMENT_TYPE`,e[e.EXPECT_ARGUMENT_STYLE=6]=`EXPECT_ARGUMENT_STYLE`,e[e.INVALID_NUMBER_SKELETON=7]=`INVALID_NUMBER_SKELETON`,e[e.INVALID_DATE_TIME_SKELETON=8]=`INVALID_DATE_TIME_SKELETON`,e[e.EXPECT_NUMBER_SKELETON=9]=`EXPECT_NUMBER_SKELETON`,e[e.EXPECT_DATE_TIME_SKELETON=10]=`EXPECT_DATE_TIME_SKELETON`,e[e.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE=11]=`UNCLOSED_QUOTE_IN_ARGUMENT_STYLE`,e[e.EXPECT_SELECT_ARGUMENT_OPTIONS=12]=`EXPECT_SELECT_ARGUMENT_OPTIONS`,e[e.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE=13]=`EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE`,e[e.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE=14]=`INVALID_PLURAL_ARGUMENT_OFFSET_VALUE`,e[e.EXPECT_SELECT_ARGUMENT_SELECTOR=15]=`EXPECT_SELECT_ARGUMENT_SELECTOR`,e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR=16]=`EXPECT_PLURAL_ARGUMENT_SELECTOR`,e[e.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT=17]=`EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT`,e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT=18]=`EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT`,e[e.INVALID_PLURAL_ARGUMENT_SELECTOR=19]=`INVALID_PLURAL_ARGUMENT_SELECTOR`,e[e.DUPLICATE_PLURAL_ARGUMENT_SELECTOR=20]=`DUPLICATE_PLURAL_ARGUMENT_SELECTOR`,e[e.DUPLICATE_SELECT_ARGUMENT_SELECTOR=21]=`DUPLICATE_SELECT_ARGUMENT_SELECTOR`,e[e.MISSING_OTHER_CLAUSE=22]=`MISSING_OTHER_CLAUSE`,e[e.INVALID_TAG=23]=`INVALID_TAG`,e[e.INVALID_TAG_NAME=25]=`INVALID_TAG_NAME`,e[e.UNMATCHED_CLOSING_TAG=26]=`UNMATCHED_CLOSING_TAG`,e[e.UNCLOSED_TAG=27]=`UNCLOSED_TAG`,e}({});function E_(e){return e.type===0}function D_(e){return e.type===1}function O_(e){return e.type===2}function k_(e){return e.type===3}function A_(e){return e.type===4}function j_(e){return e.type===5}function M_(e){return e.type===6}function N_(e){return e.type===7}function P_(e){return e.type===8}function F_(e){return!!(e&&typeof e==`object`&&e.type===0)}function I_(e){return!!(e&&typeof e==`object`&&e.type===1)}var L_=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/,R_={"001":[`H`,`h`],419:[`h`,`H`,`hB`,`hb`],AC:[`H`,`h`,`hb`,`hB`],AD:[`H`,`hB`],AE:[`h`,`hB`,`hb`,`H`],AF:[`H`,`hb`,`hB`,`h`],AG:[`h`,`hb`,`H`,`hB`],AI:[`H`,`h`,`hb`,`hB`],AL:[`h`,`H`,`hB`],AM:[`H`,`hB`],AO:[`H`,`hB`],AR:[`h`,`H`,`hB`,`hb`],AS:[`h`,`H`],AT:[`H`,`hB`],AU:[`h`,`hb`,`H`,`hB`],AW:[`H`,`hB`],AX:[`H`],AZ:[`H`,`hB`,`h`],BA:[`H`,`hB`,`h`],BB:[`h`,`hb`,`H`,`hB`],BD:[`h`,`hB`,`H`],BE:[`H`,`hB`],BF:[`H`,`hB`],BG:[`H`,`hB`,`h`],BH:[`h`,`hB`,`hb`,`H`],BI:[`H`,`h`],BJ:[`H`,`hB`],BL:[`H`,`hB`],BM:[`h`,`hb`,`H`,`hB`],BN:[`hb`,`hB`,`h`,`H`],BO:[`h`,`H`,`hB`,`hb`],BQ:[`H`],BR:[`H`,`hB`],BS:[`h`,`hb`,`H`,`hB`],BT:[`h`,`H`],BW:[`H`,`h`,`hb`,`hB`],BY:[`H`,`h`],BZ:[`H`,`h`,`hb`,`hB`],CA:[`h`,`hb`,`H`,`hB`],CC:[`H`,`h`,`hb`,`hB`],CD:[`hB`,`H`],CF:[`H`,`h`,`hB`],CG:[`H`,`hB`],CH:[`H`,`hB`,`h`],CI:[`H`,`hB`],CK:[`H`,`h`,`hb`,`hB`],CL:[`h`,`H`,`hB`,`hb`],CM:[`H`,`h`,`hB`],CN:[`H`,`hB`,`hb`,`h`],CO:[`h`,`H`,`hB`,`hb`],CP:[`H`],CR:[`h`,`H`,`hB`,`hb`],CU:[`h`,`H`,`hB`,`hb`],CV:[`H`,`hB`],CW:[`H`,`hB`],CX:[`H`,`h`,`hb`,`hB`],CY:[`h`,`H`,`hb`,`hB`],CZ:[`H`],DE:[`H`,`hB`],DG:[`H`,`h`,`hb`,`hB`],DJ:[`h`,`H`],DK:[`H`],DM:[`h`,`hb`,`H`,`hB`],DO:[`h`,`H`,`hB`,`hb`],DZ:[`h`,`hB`,`hb`,`H`],EA:[`H`,`h`,`hB`,`hb`],EC:[`h`,`H`,`hB`,`hb`],EE:[`H`,`hB`],EG:[`h`,`hB`,`hb`,`H`],EH:[`h`,`hB`,`hb`,`H`],ER:[`h`,`H`],ES:[`H`,`hB`,`h`,`hb`],ET:[`hB`,`hb`,`h`,`H`],FI:[`H`],FJ:[`h`,`hb`,`H`,`hB`],FK:[`H`,`h`,`hb`,`hB`],FM:[`h`,`hb`,`H`,`hB`],FO:[`H`,`h`],FR:[`H`,`hB`],GA:[`H`,`hB`],GB:[`H`,`h`,`hb`,`hB`],GD:[`h`,`hb`,`H`,`hB`],GE:[`H`,`hB`,`h`],GF:[`H`,`hB`],GG:[`H`,`h`,`hb`,`hB`],GH:[`h`,`H`],GI:[`H`,`h`,`hb`,`hB`],GL:[`H`,`h`],GM:[`h`,`hb`,`H`,`hB`],GN:[`H`,`hB`],GP:[`H`,`hB`],GQ:[`H`,`hB`,`h`,`hb`],GR:[`h`,`H`,`hb`,`hB`],GS:[`H`,`h`,`hb`,`hB`],GT:[`h`,`H`,`hB`,`hb`],GU:[`h`,`hb`,`H`,`hB`],GW:[`H`,`hB`],GY:[`h`,`hb`,`H`,`hB`],HK:[`h`,`hB`,`hb`,`H`],HN:[`h`,`H`,`hB`,`hb`],HR:[`H`,`hB`],HU:[`H`,`h`],IC:[`H`,`h`,`hB`,`hb`],ID:[`H`],IE:[`H`,`h`,`hb`,`hB`],IL:[`H`,`hB`],IM:[`H`,`h`,`hb`,`hB`],IN:[`h`,`H`],IO:[`H`,`h`,`hb`,`hB`],IQ:[`h`,`hB`,`hb`,`H`],IR:[`hB`,`H`],IS:[`H`],IT:[`H`,`hB`],JE:[`H`,`h`,`hb`,`hB`],JM:[`h`,`hb`,`H`,`hB`],JO:[`h`,`hB`,`hb`,`H`],JP:[`H`,`K`,`h`],KE:[`hB`,`hb`,`H`,`h`],KG:[`H`,`h`,`hB`,`hb`],KH:[`hB`,`h`,`H`,`hb`],KI:[`h`,`hb`,`H`,`hB`],KM:[`H`,`h`,`hB`,`hb`],KN:[`h`,`hb`,`H`,`hB`],KP:[`h`,`H`,`hB`,`hb`],KR:[`h`,`H`,`hB`,`hb`],KW:[`h`,`hB`,`hb`,`H`],KY:[`h`,`hb`,`H`,`hB`],KZ:[`H`,`hB`],LA:[`H`,`hb`,`hB`,`h`],LB:[`h`,`hB`,`hb`,`H`],LC:[`h`,`hb`,`H`,`hB`],LI:[`H`,`hB`,`h`],LK:[`H`,`h`,`hB`,`hb`],LR:[`h`,`hb`,`H`,`hB`],LS:[`h`,`H`],LT:[`H`,`h`,`hb`,`hB`],LU:[`H`,`h`,`hB`],LV:[`H`,`hB`,`hb`,`h`],LY:[`h`,`hB`,`hb`,`H`],MA:[`H`,`h`,`hB`,`hb`],MC:[`H`,`hB`],MD:[`H`,`hB`],ME:[`H`,`hB`,`h`],MF:[`H`,`hB`],MG:[`H`,`h`],MH:[`h`,`hb`,`H`,`hB`],MK:[`H`,`h`,`hb`,`hB`],ML:[`H`],MM:[`hB`,`hb`,`H`,`h`],MN:[`H`,`h`,`hb`,`hB`],MO:[`h`,`hB`,`hb`,`H`],MP:[`h`,`hb`,`H`,`hB`],MQ:[`H`,`hB`],MR:[`h`,`hB`,`hb`,`H`],MS:[`H`,`h`,`hb`,`hB`],MT:[`H`,`h`],MU:[`H`,`h`],MV:[`H`,`h`],MW:[`h`,`hb`,`H`,`hB`],MX:[`h`,`H`,`hB`,`hb`],MY:[`hb`,`hB`,`h`,`H`],MZ:[`H`,`hB`],NA:[`h`,`H`,`hB`,`hb`],NC:[`H`,`hB`],NE:[`H`],NF:[`H`,`h`,`hb`,`hB`],NG:[`H`,`h`,`hb`,`hB`],NI:[`h`,`H`,`hB`,`hb`],NL:[`H`,`hB`],NO:[`H`,`h`],NP:[`H`,`h`,`hB`],NR:[`H`,`h`,`hb`,`hB`],NU:[`H`,`h`,`hb`,`hB`],NZ:[`h`,`hb`,`H`,`hB`],OM:[`h`,`hB`,`hb`,`H`],PA:[`h`,`H`,`hB`,`hb`],PE:[`h`,`H`,`hB`,`hb`],PF:[`H`,`h`,`hB`],PG:[`h`,`H`],PH:[`h`,`hB`,`hb`,`H`],PK:[`h`,`hB`,`H`],PL:[`H`,`h`],PM:[`H`,`hB`],PN:[`H`,`h`,`hb`,`hB`],PR:[`h`,`H`,`hB`,`hb`],PS:[`h`,`hB`,`hb`,`H`],PT:[`H`,`hB`],PW:[`h`,`H`],PY:[`h`,`H`,`hB`,`hb`],QA:[`h`,`hB`,`hb`,`H`],RE:[`H`,`hB`],RO:[`H`,`hB`],RS:[`H`,`hB`,`h`],RU:[`H`],RW:[`H`,`h`],SA:[`h`,`hB`,`hb`,`H`],SB:[`h`,`hb`,`H`,`hB`],SC:[`H`,`h`,`hB`],SD:[`h`,`hB`,`hb`,`H`],SE:[`H`],SG:[`h`,`hb`,`H`,`hB`],SH:[`H`,`h`,`hb`,`hB`],SI:[`H`,`hB`],SJ:[`H`],SK:[`H`],SL:[`h`,`hb`,`H`,`hB`],SM:[`H`,`h`,`hB`],SN:[`H`,`h`,`hB`],SO:[`h`,`H`],SR:[`H`,`hB`],SS:[`h`,`hb`,`H`,`hB`],ST:[`H`,`hB`],SV:[`h`,`H`,`hB`,`hb`],SX:[`H`,`h`,`hb`,`hB`],SY:[`h`,`hB`,`hb`,`H`],SZ:[`h`,`hb`,`H`,`hB`],TA:[`H`,`h`,`hb`,`hB`],TC:[`h`,`hb`,`H`,`hB`],TD:[`h`,`H`,`hB`],TF:[`H`,`h`,`hB`],TG:[`H`,`hB`],TH:[`H`,`h`],TJ:[`H`,`h`],TL:[`H`,`hB`,`hb`,`h`],TM:[`H`,`h`],TN:[`h`,`hB`,`hb`,`H`],TO:[`h`,`H`],TR:[`H`,`hB`],TT:[`h`,`hb`,`H`,`hB`],TW:[`hB`,`hb`,`h`,`H`],TZ:[`hB`,`hb`,`H`,`h`],UA:[`H`,`hB`,`h`],UG:[`hB`,`hb`,`H`,`h`],UM:[`h`,`hb`,`H`,`hB`],US:[`h`,`hb`,`H`,`hB`],UY:[`h`,`H`,`hB`,`hb`],UZ:[`H`,`hB`,`h`],VA:[`H`,`h`,`hB`],VC:[`h`,`hb`,`H`,`hB`],VE:[`h`,`H`,`hB`,`hb`],VG:[`h`,`hb`,`H`,`hB`],VI:[`h`,`hb`,`H`,`hB`],VN:[`H`,`h`],VU:[`h`,`H`],WF:[`H`,`hB`],WS:[`h`,`H`],XK:[`H`,`hB`,`h`],YE:[`h`,`hB`,`hb`,`H`],YT:[`H`,`hB`],ZA:[`H`,`h`,`hb`,`hB`],ZM:[`h`,`hb`,`H`,`hB`],ZW:[`H`,`h`],"af-ZA":[`H`,`h`,`hB`,`hb`],"ar-001":[`h`,`hB`,`hb`,`H`],"ca-ES":[`H`,`h`,`hB`],"en-001":[`h`,`hb`,`H`,`hB`],"en-HK":[`h`,`hb`,`H`,`hB`],"en-IL":[`H`,`h`,`hb`,`hB`],"en-MY":[`h`,`hb`,`H`,`hB`],"es-BR":[`H`,`h`,`hB`,`hb`],"es-ES":[`H`,`h`,`hB`,`hb`],"es-GQ":[`H`,`h`,`hB`,`hb`],"fr-CA":[`H`,`h`,`hB`],"gl-ES":[`H`,`h`,`hB`],"gu-IN":[`hB`,`hb`,`h`,`H`],"hi-IN":[`hB`,`h`,`H`],"it-CH":[`H`,`h`,`hB`],"it-IT":[`H`,`h`,`hB`],"kn-IN":[`hB`,`h`,`H`],"ku-SY":[`H`,`hB`],"ml-IN":[`hB`,`h`,`H`],"mr-IN":[`hB`,`hb`,`h`,`H`],"pa-IN":[`hB`,`hb`,`h`,`H`],"ta-IN":[`hB`,`h`,`hb`,`H`],"te-IN":[`hB`,`h`,`H`],"zu-ZA":[`H`,`hB`,`hb`,`h`]};function z_(e,t){let n=``;for(let r=0;r>1),c=B_(t);for((c==`H`||c==`k`)&&(s=0);s-->0;)n+=`a`;for(;o-->0;)n=c+n}else n+=i===`J`?`H`:i}return n}function B_(e){let t=e.hourCycle;if(t===void 0&&e.hourCycles&&e.hourCycles.length&&(t=e.hourCycles[0]),t)switch(t){case`h24`:return`k`;case`h23`:return`H`;case`h12`:return`h`;case`h11`:return`K`;default:throw Error(`Invalid hourCycle`)}let n=e.language,r;return n!==`root`&&(r=e.maximize().region),(R_[r||``]||R_[n||``]||R_[`${n}-001`]||R_[`001`])[0]}var V_=RegExp(`^${L_.source}*`),H_=RegExp(`${L_.source}*$`);function U_(e,t){return{start:e,end:t}}var W_=!!Object.fromEntries,G_=!!String.prototype.trimStart,K_=!!String.prototype.trimEnd,q_=W_?Object.fromEntries:function(e){let t={};for(let[n,r]of e)t[n]=r;return t},J_=G_?function(e){return e.trimStart()}:function(e){return e.replace(V_,``)},Y_=K_?function(e){return e.trimEnd()}:function(e){return e.replace(H_,``)},X_=RegExp(`([^\\p{White_Space}\\p{Pattern_Syntax}]*)`,`yu`);function Z_(e,t){return X_.lastIndex=t,X_.exec(e)[1]??``}function Q_(e){if(e.length===0)return null;let t=1,n=1;for(let r=0;r=55296&&i<=56319&&r+1=56320&&t<=57343?2:1}else r++}return{offset:e.length,line:t,column:n}}var $_=class{constructor(e,t={}){this.message=e,this.position={offset:0,line:1,column:1},this.ignoreTag=!!t.ignoreTag,this.locale=t.locale,this.requiresOtherClause=!!t.requiresOtherClause,this.shouldParseSkeletons=!!t.shouldParseSkeletons}parse(){if(this.offset()!==0)throw Error(`parser can only be used once`);if(this.message.length>0){let e=this.message.charCodeAt(0);if(e!==35&&e!==39&&e!==60&&e!==123&&e!==125){let e=Q_(this.message);if(e){let t=this.clonePosition();return this.position=e,{val:[{type:0,value:this.message,location:U_(t,this.clonePosition())}],err:null}}}}return this.parseMessage(0,``,!1)}parseMessage(e,t,n){let r=[];for(;!this.isEOF();){let i=this.char();if(i===123){let t=this.parseArgument(e,n);if(t.err)return t;r.push(t.val)}else if(i===125&&e>0)break;else if(i===35&&(t===`plural`||t===`selectordinal`)){let e=this.clonePosition();this.bump(),r.push({type:7,location:U_(e,this.clonePosition())})}else if(i===60&&!this.ignoreTag&&this.peek()===47){if(n)break;return this.error(26,U_(this.clonePosition(),this.clonePosition()))}else if(i===60&&!this.ignoreTag&&ev(this.peek()||0)){let n=this.parseTag(e,t);if(n.err)return n;r.push(n.val)}else{let n=this.parseLiteral(e,t);if(n.err)return n;r.push(n.val)}}return{val:r,err:null}}parseTag(e,t){let n=this.clonePosition();this.bump();let r=this.parseTagName();if(this.bumpSpace(),this.bumpIf(`/>`))return{val:{type:0,value:`<${r}/>`,location:U_(n,this.clonePosition())},err:null};if(this.bumpIf(`>`)){let i=this.parseMessage(e+1,t,!0);if(i.err)return i;let a=i.val,o=this.clonePosition();if(this.bumpIf(``)?{val:{type:8,value:r,children:a,location:U_(n,this.clonePosition())},err:null}:this.error(23,U_(o,this.clonePosition()))):this.error(26,U_(e,this.clonePosition()))}return this.error(27,U_(n,this.clonePosition()))}return this.error(23,U_(n,this.clonePosition()))}parseTagName(){let e=this.offset();for(this.bump();!this.isEOF()&&nv(this.char());)this.bump();return this.message.slice(e,this.offset())}parseLiteral(e,t){let n=this.clonePosition(),r=``;for(;;){let n=this.tryParseQuote(t);if(n){r+=n;continue}let i=this.tryParseUnquoted(e,t);if(i){r+=i;continue}let a=this.tryParseLeftAngleBracket();if(a){r+=a;continue}break}let i=U_(n,this.clonePosition());return{val:{type:0,value:r,location:i},err:null}}tryParseLeftAngleBracket(){return!this.isEOF()&&this.char()===60&&(this.ignoreTag||!tv(this.peek()||0))?(this.bump(),`<`):null}tryParseQuote(e){if(this.isEOF()||this.char()!==39)return null;switch(this.peek()){case 39:return this.bump(),this.bump(),`'`;case 123:case 60:case 62:case 125:break;case 35:if(e===`plural`||e===`selectordinal`)break;return null;default:return null}this.bump();let t=[this.char()];for(this.bump();!this.isEOF();){let e=this.char();if(e===39)if(this.peek()===39)t.push(39),this.bump();else{this.bump();break}else t.push(e);this.bump()}return String.fromCodePoint(...t)}tryParseUnquoted(e,t){if(this.isEOF())return null;let n=this.char();return n===60||n===123||n===35&&(t===`plural`||t===`selectordinal`)||n===125&&e>0?null:(this.bump(),String.fromCodePoint(n))}parseArgument(e,t){let n=this.clonePosition();if(this.bump(),this.bumpSpace(),this.isEOF())return this.error(1,U_(n,this.clonePosition()));if(this.char()===125)return this.bump(),this.error(2,U_(n,this.clonePosition()));let r=this.parseIdentifierIfPossible().value;if(!r)return this.error(3,U_(n,this.clonePosition()));if(this.bumpSpace(),this.isEOF())return this.error(1,U_(n,this.clonePosition()));switch(this.char()){case 125:return this.bump(),{val:{type:1,value:r,location:U_(n,this.clonePosition())},err:null};case 44:return this.bump(),this.bumpSpace(),this.isEOF()?this.error(1,U_(n,this.clonePosition())):this.parseArgumentOptions(e,t,r,n);default:return this.error(3,U_(n,this.clonePosition()))}}parseIdentifierIfPossible(){let e=this.clonePosition(),t=this.offset(),n=Z_(this.message,t),r=t+n.length;return this.bumpTo(r),{value:n,location:U_(e,this.clonePosition())}}parseArgumentOptions(e,t,n,r){let i=this.clonePosition(),a=this.parseIdentifierIfPossible().value,o=this.clonePosition();switch(a){case``:return this.error(4,U_(i,o));case`number`:case`date`:case`time`:{this.bumpSpace();let e=null;if(this.bumpIf(`,`)){this.bumpSpace();let t=this.clonePosition(),n=this.parseSimpleArgStyleIfPossible();if(n.err)return n;let r=Y_(n.val);if(r.length===0)return this.error(6,U_(this.clonePosition(),this.clonePosition()));e={style:r,styleLocation:U_(t,this.clonePosition())}}let t=this.tryParseArgumentClose(r);if(t.err)return t;let i=U_(r,this.clonePosition());if(e&&e.style.startsWith(`::`)){let t=J_(e.style.slice(2));if(a===`number`){let r=this.parseNumberSkeletonFromString(t,e.styleLocation);return r.err?r:{val:{type:2,value:n,location:i,style:r.val},err:null}}{if(t.length===0)return this.error(10,i);let r=t;this.locale&&(r=z_(t,this.locale));let o={type:1,pattern:r,location:e.styleLocation,parsedOptions:this.shouldParseSkeletons?f_(r):{}};return{val:{type:a===`date`?3:4,value:n,location:i,style:o},err:null}}}return{val:{type:a===`number`?2:a===`date`?3:4,value:n,location:i,style:e?.style??null},err:null}}case`plural`:case`selectordinal`:case`select`:{let i=this.clonePosition();if(this.bumpSpace(),!this.bumpIf(`,`))return this.error(12,U_(i,{...i}));this.bumpSpace();let o=this.parseIdentifierIfPossible(),s=0;if(a!==`select`&&o.value===`offset`){if(!this.bumpIf(`:`))return this.error(13,U_(this.clonePosition(),this.clonePosition()));this.bumpSpace();let e=this.tryParseDecimalInteger(13,14);if(e.err)return e;this.bumpSpace(),o=this.parseIdentifierIfPossible(),s=e.val}let c=this.tryParsePluralOrSelectOptions(e,a,t,o);if(c.err)return c;let l=this.tryParseArgumentClose(r);if(l.err)return l;let u=U_(r,this.clonePosition());return a===`select`?{val:{type:5,value:n,options:q_(c.val),location:u},err:null}:{val:{type:6,value:n,options:q_(c.val),offset:s,pluralType:a===`plural`?`cardinal`:`ordinal`,location:u},err:null}}default:return this.error(5,U_(i,o))}}tryParseArgumentClose(e){return this.isEOF()||this.char()!==125?this.error(1,U_(e,this.clonePosition())):(this.bump(),{val:!0,err:null})}parseSimpleArgStyleIfPossible(){let e=0,t=this.clonePosition();for(;!this.isEOF();)switch(this.char()){case 39:{this.bump();let e=this.clonePosition();if(!this.bumpUntil(`'`))return this.error(11,U_(e,this.clonePosition()));this.bump();break}case 123:e+=1,this.bump();break;case 125:if(e>0)--e;else return{val:this.message.slice(t.offset,this.offset()),err:null};break;default:this.bump()}return{val:this.message.slice(t.offset,this.offset()),err:null}}parseNumberSkeletonFromString(e,t){let n=[];try{n=m_(e)}catch{return this.error(7,t)}return{val:{type:0,tokens:n,location:t,parsedOptions:this.shouldParseSkeletons?w_(n):{}},err:null}}tryParsePluralOrSelectOptions(e,t,n,r){let i=!1,a=[],o=new Set,{value:s,location:c}=r;for(;;){if(s.length===0){let e=this.clonePosition();if(t!==`select`&&this.bumpIf(`=`)){let t=this.tryParseDecimalInteger(16,19);if(t.err)return t;c=U_(e,this.clonePosition()),s=this.message.slice(e.offset,this.offset())}else break}if(o.has(s))return this.error(t===`select`?21:20,c);s===`other`&&(i=!0),this.bumpSpace();let r=this.clonePosition();if(!this.bumpIf(`{`))return this.error(t===`select`?17:18,U_(this.clonePosition(),this.clonePosition()));let l=this.parseMessage(e+1,t,n);if(l.err)return l;let u=this.tryParseArgumentClose(r);if(u.err)return u;a.push([s,{value:l.val,location:U_(r,this.clonePosition())}]),o.add(s),this.bumpSpace(),{value:s,location:c}=this.parseIdentifierIfPossible()}return a.length===0?this.error(t===`select`?15:16,U_(this.clonePosition(),this.clonePosition())):this.requiresOtherClause&&!i?this.error(22,U_(this.clonePosition(),this.clonePosition())):{val:a,err:null}}tryParseDecimalInteger(e,t){let n=1,r=this.clonePosition();this.bumpIf(`+`)||this.bumpIf(`-`)&&(n=-1);let i=!1,a=0;for(;!this.isEOF();){let e=this.char();if(e>=48&&e<=57)i=!0,a=a*10+(e-48),this.bump();else break}let o=U_(r,this.clonePosition());return i?(a*=n,Number.isSafeInteger(a)?{val:a,err:null}:this.error(t,o)):this.error(e,o)}offset(){return this.position.offset}isEOF(){return this.offset()===this.message.length}clonePosition(){return{offset:this.position.offset,line:this.position.line,column:this.position.column}}char(){let e=this.position.offset;if(e>=this.message.length)throw Error(`out of bound`);let t=this.message.codePointAt(e);if(t===void 0)throw Error(`Offset ${e} is at invalid UTF-16 code unit boundary`);return t}error(e,t){return{val:null,err:{kind:e,message:this.message,location:t}}}bump(){if(this.isEOF())return;let e=this.char();e===10?(this.position.line+=1,this.position.column=1,this.position.offset+=1):(this.position.column+=1,this.position.offset+=e<65536?1:2)}bumpIf(e){if(this.message.startsWith(e,this.offset())){for(let t=0;t=0?(this.bumpTo(n),!0):(this.bumpTo(this.message.length),!1)}bumpTo(e){if(this.offset()>e)throw Error(`targetOffset ${e} must be greater than or equal to the current offset ${this.offset()}`);for(e=Math.min(e,this.message.length);;){let t=this.offset();if(t===e)break;if(t>e)throw Error(`targetOffset ${e} is at invalid UTF-16 code unit boundary`);if(this.bump(),this.isEOF())break}}bumpSpace(){for(;!this.isEOF()&&rv(this.char());)this.bump()}peek(){if(this.isEOF())return null;let e=this.char(),t=this.offset();return this.message.charCodeAt(t+(e>=65536?2:1))??null}};function ev(e){return e>=97&&e<=122||e>=65&&e<=90}function tv(e){return ev(e)||e===47}function nv(e){return e===45||e===46||e>=48&&e<=57||e===95||e>=97&&e<=122||e>=65&&e<=90||e==183||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8255&&e<=8256||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}function rv(e){return e>=9&&e<=13||e===32||e===133||e>=8206&&e<=8207||e===8232||e===8233}function iv(e){e.forEach(e=>{if(delete e.location,j_(e)||M_(e))for(let t in e.options)delete e.options[t].location,iv(e.options[t].value);else O_(e)&&F_(e.style)||(k_(e)||A_(e))&&I_(e.style)?delete e.style.location:P_(e)&&iv(e.children)})}function av(e,t={}){t={shouldParseSkeletons:!0,requiresOtherClause:!0,...t};let n=new $_(e,t).parse();if(n.err){let e=SyntaxError(T_[n.err.kind]);throw e.location=n.err.location,e.originalMessage=n.err.message,e}return t?.captureLocation||iv(n.val),n.val}var ov=class extends Error{constructor(e,t,n){super(e),this.code=t,this.originalMessage=n}toString(){return`[formatjs Error: ${this.code}] ${this.message}`}},sv=class extends ov{constructor(e,t,n,r){super(`Invalid values for "${e}": "${t}". Options are "${Object.keys(n).join(`", "`)}"`,`INVALID_VALUE`,r)}},cv=class extends ov{constructor(e,t,n){super(`Value for "${e}" must be of type ${t}`,`INVALID_VALUE`,n)}},lv=class extends ov{constructor(e,t){super(`The intl string context variable "${e}" was not provided to the string "${t}"`,`MISSING_VALUE`,t)}};function uv(e){return e.length<2?e:e.reduce((e,t)=>{let n=e[e.length-1];return!n||n.type!==0||t.type!==0?e.push(t):n.value+=t.value,e},[])}function dv(e){return typeof e==`function`}function fv(e,t,n,r,i,a,o){if(e.length===1&&E_(e[0]))return[{type:0,value:e[0].value}];let s=[];for(let c of e){if(E_(c)){s.push({type:0,value:c.value});continue}if(N_(c)){typeof a==`number`&&s.push({type:0,value:n.getNumberFormat(t).format(a)});continue}let{value:e}=c;if(!(i&&e in i))throw new lv(e,o);let l=i[e];if(D_(c)){(!l||typeof l==`string`||typeof l==`number`||typeof l==`bigint`)&&(l=typeof l==`string`||typeof l==`number`||typeof l==`bigint`?String(l):``),s.push({type:typeof l==`string`?0:1,value:l});continue}if(k_(c)){let e=typeof c.style==`string`?r.date[c.style]:I_(c.style)?c.style.parsedOptions:void 0;s.push({type:0,value:n.getDateTimeFormat(t,e).format(l)});continue}if(A_(c)){let e=typeof c.style==`string`?r.time[c.style]:I_(c.style)?c.style.parsedOptions:r.time.medium;s.push({type:0,value:n.getDateTimeFormat(t,e).format(l)});continue}if(O_(c)){let e=typeof c.style==`string`?r.number[c.style]:F_(c.style)?c.style.parsedOptions:void 0;if(e&&e.scale){let t=e.scale||1;if(typeof l==`bigint`){if(!Number.isInteger(t))throw TypeError(`Cannot apply fractional scale ${t} to bigint value. Scale must be an integer when formatting bigint.`);l*=BigInt(t)}else l*=t}s.push({type:0,value:n.getNumberFormat(t,e).format(l)});continue}if(P_(c)){let{children:e,value:l}=c,u=i[l];if(!dv(u))throw new cv(l,`function`,o);let d=u(fv(e,t,n,r,i,a).map(e=>e.value));Array.isArray(d)||(d=[d]),s.push(...d.map(e=>({type:typeof e==`string`?0:1,value:e})))}if(j_(c)){let e=l,a=(Object.prototype.hasOwnProperty.call(c.options,e)?c.options[e]:void 0)||c.options.other;if(!a)throw new sv(c.value,l,Object.keys(c.options),o);s.push(...fv(a.value,t,n,r,i));continue}if(M_(c)){let e=`=${l}`,a=Object.prototype.hasOwnProperty.call(c.options,e)?c.options[e]:void 0;if(!a){if(!Intl.PluralRules)throw new ov(`Intl.PluralRules is not available in this environment. +`);r&&i(JSON.parse(r))}}}finally{await s.cancel().catch(()=>void 0)}}renameWorkspace(e,t){return this.request(`/api/workspaces`,{method:`PATCH`,body:JSON.stringify({path:e,name:t})})}removeWorkspace(e){return this.request(`/api/workspaces?path=${encodeURIComponent(e)}`,{method:`DELETE`})}createSession(e,t){return this.request(`/api/sessions`,{method:`POST`,body:JSON.stringify({workspacePath:e,commandId:t})})}selectSession(e){return this.request(`/api/sessions/select`,{method:`POST`,body:JSON.stringify({path:e})})}renameSession(e,t){return this.request(`/api/sessions`,{method:`PATCH`,body:JSON.stringify({path:e,name:t})})}archiveSession(e){return this.request(`/api/sessions/archive?path=${encodeURIComponent(e)}`,{method:`POST`})}unarchiveSession(e){return this.request(`/api/sessions/unarchive?path=${encodeURIComponent(e)}`,{method:`POST`})}thinking(e,t){return this.request(`/api/thinking?sessionId=${encodeURIComponent(e)}`,{signal:t})}setThinkingLevel(e,t,n){return this.request(`/api/thinking`,{method:`POST`,body:JSON.stringify({sessionId:e,sessionPath:n,level:t})})}setPlanMode(e,t,n,r){return this.request(`/api/plan`,{method:`POST`,body:JSON.stringify({sessionId:e,sessionPath:t,enabled:n,expectedRevision:r})})}trust(e,t){return this.request(`/api/trust?sessionId=${encodeURIComponent(e)}`,{signal:t})}providerAuth(e,t){return this.request(`/api/providers/auth-status?sessionId=${encodeURIComponent(e)}`,{signal:t})}saveProviderKey(e,t,n,r){return this.request(`/api/providers/api-key`,{method:`POST`,body:JSON.stringify({sessionId:e,provider:t,apiKey:n}),signal:r})}modelConfigurations(e,t){return this.request(`/api/models/configuration?sessionId=${encodeURIComponent(e)}`,{signal:t})}saveModelConfiguration(e,t,n,r){return this.request(`/api/models/configuration`,{method:`POST`,body:JSON.stringify({sessionId:e,revision:t,model:n}),signal:r})}commands(e,t){return this.request(`/api/commands?sessionId=${encodeURIComponent(e)}`,{signal:t})}settingsCatalog(e,t){return this.request(`/api/settings/catalog?sessionId=${encodeURIComponent(e)}`,{signal:t})}terminalDetail(e,t,n){return this.request(`/api/capabilities/detail?kind=background-terminals&id=${encodeURIComponent(t)}&sessionId=${encodeURIComponent(e)}`,{signal:n})}subagentDetail(e,t,n){return this.request(`/api/capabilities/detail?kind=subagents&id=${encodeURIComponent(t)}&sessionId=${encodeURIComponent(e)}`,{signal:n})}subagentAction(e,t,n){return this.request(`/api/capabilities/action`,{method:`POST`,body:JSON.stringify({sessionId:e,...t}),signal:n})}selectModel(e,t,n,r){return this.request(`/api/model`,{method:`POST`,body:JSON.stringify({provider:e,modelId:t,sessionId:n,sessionPath:r})})}searchModels(e,t,n){let r=new URLSearchParams({query:e,limit:`50`});return t&&r.set(`sessionId`,t),this.request(`/api/models?${r}`,{signal:n})}cancelActiveTurn(e){return this.request(`/api/turns/cancel`,{method:`POST`,body:JSON.stringify({sessionId:e.sessionId,commandId:e.commandId,epoch:e.epoch})})}pendingQuestions(e,t){return this.request(`/api/questions/pending?sessionId=${encodeURIComponent(e)}`,{signal:t})}async answerQuestions(e,t,n){try{return await this.request(`/api/questions/answer`,{method:`POST`,body:JSON.stringify({sessionId:e.sessionId,requestId:e.requestId,action:t===null?`dismiss`:`answer`,...t===null?{}:{answers:t}}),signal:n})}catch(e){if(e instanceof zp&&e.code===`STALE_QUESTION`)return{state:`stale`};throw e}}async prompt(e,t,n,r,i=!1,a=[]){let o=await this.request(`/api/prompt`,{method:`POST`,body:JSON.stringify({sessionId:e,sessionPath:r,content:t,commandId:n,retry:i,images:a,controllerId:await Lp()}),timeoutMs:3e4,timeoutMessage:`Request timed out; admission may still be pending. Retry the same message to recover its receipt.`});if(typeof o.id!=`string`||!o.id||o.accepted!==!0)throw Error(`Invalid prompt receipt; retry the same message to recover its admission.`);return o}};function Hp(e,t){return t.every((t,n)=>e[n]===t)}function Up(e){if(Hp(e,[137,80,78,71,13,10,26,10]))return`image/png`;if(Hp(e,[255,216,255]))return`image/jpeg`;let t=new TextDecoder(`ascii`).decode(e.subarray(0,12));return t.startsWith(`GIF87a`)||t.startsWith(`GIF89a`)?`image/gif`:t.startsWith(`RIFF`)&&t.slice(8,12)===`WEBP`?`image/webp`:null}function Wp(e){let t=``,n=32768;for(let r=0;r3145728)throw Error(`image-size`);let t=new Uint8Array(await e.arrayBuffer()),n=Up(t);if(!n)throw Error(`image-type`);let r=Wp(t);return{id:globalThis.crypto?.randomUUID?.()??`${Date.now()}-${e.name}`,name:e.name||`image`,size:t.length,mimeType:n,data:r,previewUrl:`data:${n};base64,${r}`}}function Kp({artifact:e,client:t}){let{t:n}=Fn(),[r,i]=(0,w.useState)(null),[a,o]=(0,w.useState)(null),{sessionId:s,handle:c,revision:l}=e;return(0,w.useEffect)(()=>{let e=new AbortController,r;return i(null),o(null),t.downloadArtifact({sessionId:s,handle:c,revision:l},e.signal).then(async t=>{let a=Up(new Uint8Array(await t.slice(0,12).arrayBuffer()));if(!e.signal.aborted){if(!a)throw Error(n(`artifactImagePreviewFailed`));r=URL.createObjectURL(new Blob([t],{type:a})),i({url:r,loaded:!1})}}).catch(t=>{e.signal.aborted||o(t instanceof Error?t.message:n(`artifactImagePreviewFailed`))}),()=>{e.abort(),r&&URL.revokeObjectURL(r)}},[s,c,l,t,n]),a?(0,J.jsxs)(`div`,{role:`alert`,className:`evidence-warning`,children:[(0,J.jsx)(`p`,{children:n(`artifactImagePreviewFailed`)}),a!==n(`artifactImagePreviewFailed`)&&(0,J.jsx)(`small`,{children:a})]}):(0,J.jsxs)(`div`,{className:`artifact-image-preview`,children:[!r?.loaded&&(0,J.jsx)(`p`,{role:`status`,children:n(`readingFile`)}),r&&(0,J.jsx)(`img`,{src:r.url,alt:e.name,hidden:!r.loaded,onLoad:()=>i(e=>e?{...e,loaded:!0}:null),onError:()=>o(n(`artifactImagePreviewFailed`))})]})}var qp=(0,w.forwardRef)(function({sessionId:e,children:t,disabled:n=!1,onOpen:r,onClose:i},a){let{t:o}=Fn(),s=(0,w.useMemo)(()=>new Vp,[]),[c,l]=(0,w.useState)(null),[u,d]=(0,w.useState)(null),[f,p]=(0,w.useState)(null),[m,h]=(0,w.useState)(!1),[g,_]=(0,w.useState)(!1),[v,y]=(0,w.useState)(null),b=(0,w.useRef)(null),x=(0,w.useRef)(0),S=(0,w.useRef)(null),C=(0,w.useRef)(null),T=(0,w.useRef)(new Set),E=(0,w.useRef)(void 0),D=(0,w.useCallback)((t,i)=>{if(n)return;r?.();let a=document.activeElement;a instanceof HTMLElement&&a.closest(`.artifact-panel`)||(b.current=a instanceof HTMLElement?a:null),x.current++,d(null),p(null),h(!1),y(null),E.current=i,l({reference:t,parent:i,sessionId:e})},[n,r,e]),O=(0,w.useCallback)(()=>{x.current++,l(null),d(null),y(null),i?.(`user`),b.current?.focus()},[i]);(0,w.useImperativeHandle)(a,()=>({close:O}),[O]),(0,w.useEffect)(()=>{!c||!n&&c.sessionId===e||(x.current++,l(null),d(null),y(null),b.current=null,E.current=void 0,i?.(`context`))},[n,e,c,i]),(0,w.useEffect)(()=>{if(!c||!e||c.sessionId!==e)return;let t=new AbortController,n,r=c.parent,i,a=!1,o,l=2e3;S.current?.focus();let u=async()=>{if(document.visibilityState===`hidden`){i=setTimeout(u,2e3);return}try{if(n||=(await(c.external?s.authorizeArtifact(e,c.reference,t.signal):s.resolveArtifact(e,c.reference,r,t.signal))).handle,r&&=(s.releaseArtifact(e,r).catch(()=>void 0),void 0),a){s.releaseArtifact(e,n).catch(()=>void 0);return}if(o&&(await s.artifactMetadata(e,n,t.signal)).identity===o){a||p(null),l=Math.min(l*2,3e4);return}let i=await s.artifactPreview(e,n,t.signal);a||(o=i.identity,l=2e3,d(i),p(null))}catch(e){let t=e instanceof zp&&[401,403,410].includes(e.status);!a&&t&&(d(null),C.current?.abort()),a||h(e instanceof zp&&e.code===`ARTIFACT_DENIED`),l=Math.min(l*2,3e4),a||p(e instanceof Error?e.message:`Unable to read file`),t&&(a=!0)}finally{a||(i=setTimeout(u,l))}};return u(),()=>{a=!0,t.abort(),clearTimeout(i),C.current?.abort(),_(!1),n&&n!==E.current&&s.releaseArtifact(e,n).catch(()=>void 0),r&&s.releaseArtifact(e,r).catch(()=>void 0);for(let e of T.current)URL.revokeObjectURL(e);T.current.clear()}},[s,c,e]);let k=(0,w.useMemo)(()=>({open:D}),[D]),A=u?.artifact.path??c?.reference??``,j=u?.artifact.name??A.split(/[\\/]/u).at(-1)??A,M=async()=>{if(!u||g)return;let e=new AbortController;C.current=e,_(!0);try{let t=await s.downloadArtifact(u.artifact,e.signal);if(e.signal.aborted)return;let n=URL.createObjectURL(t);T.current.add(n);let r=document.createElement(`a`);r.href=n,r.download=u.artifact.name,r.click(),setTimeout(()=>{URL.revokeObjectURL(n),T.current.delete(n)},1e3)}catch(t){e.signal.aborted||p(t instanceof Error?t.message:`Download failed`)}finally{e.signal.aborted||_(!1)}};return(0,J.jsxs)(ip.Provider,{value:k,children:[t,c&&(0,J.jsxs)(`aside`,{className:`artifact-panel`,"aria-label":o(`filePreview`),onKeyDown:e=>{e.key===`Escape`&&(e.stopPropagation(),O())},children:[(0,J.jsxs)(`header`,{children:[(0,J.jsxs)(`div`,{children:[(0,J.jsxs)(`small`,{children:[o(`filePreview`),` · `,o(`artifactReadOnly`)]}),(0,J.jsx)(`h2`,{title:j,children:j})]}),(0,J.jsx)(`button`,{ref:S,type:`button`,"aria-label":o(`closePreview`),title:o(`closePreview`),onClick:O,children:(0,J.jsx)(ft,{"aria-hidden":`true`})})]}),(0,J.jsxs)(`div`,{className:`artifact-panel-body`,children:[(0,J.jsxs)(`div`,{className:`artifact-source`,children:[(0,J.jsx)(`code`,{title:A,children:A}),(0,J.jsx)(`button`,{type:`button`,"aria-label":o(`copyFilePath`),title:o(`copyFilePath`),onClick:()=>{let e=++x.current;ap(A).then(t=>{e===x.current&&y(t?`copied`:`failed`)})},children:v===`copied`?(0,J.jsx)(B,{"aria-hidden":`true`}):(0,J.jsx)(ae,{"aria-hidden":`true`})})]}),v&&(0,J.jsx)(`p`,{role:`status`,children:o(v===`copied`?`filePathCopied`:`copyFailed`)}),(0,J.jsxs)(`div`,{className:`artifact-actions`,children:[(0,J.jsxs)(`button`,{type:`button`,onClick:()=>{x.current++,y(null),E.current=void 0,l(t=>t?{sessionId:e,reference:u?encodeURI(u.artifact.path):t.reference,...t.external?{external:!0}:{},...u?{}:{parent:t.parent}}:null)},children:[(0,J.jsx)(Ye,{"aria-hidden":`true`}),` `,o(`refreshFile`)]}),(0,J.jsxs)(`button`,{type:`button`,disabled:!u||g,onClick:()=>void M(),children:[(0,J.jsx)(pe,{"aria-hidden":`true`}),` `,o(g?`downloadingFile`:`downloadFile`)]})]}),f&&(0,J.jsxs)(`p`,{role:`status`,className:`evidence-warning`,children:[u?o(`artifactOlderPreview`):``,f]}),m&&!c.external&&/^(?:\/(?!\/)|[a-z]:[\\/])/iu.test(c.reference)&&(0,J.jsxs)(`div`,{className:`artifact-external-access`,children:[(0,J.jsx)(`p`,{children:o(`artifactExternalAccess`)}),(0,J.jsx)(`button`,{type:`button`,onClick:()=>{p(null),h(!1),l({...c,parent:void 0,external:!0})},children:o(`artifactAuthorizeFile`)})]}),!u&&!f&&(0,J.jsx)(`p`,{role:`status`,children:o(`readingFile`)}),u&&(0,J.jsxs)(J.Fragment,{children:[u.truncated&&(0,J.jsx)(`p`,{className:`evidence-warning`,children:o(`artifactPreviewTruncated`)}),/\.(?:png|jpe?g|gif|webp)$/iu.test(u.artifact.name)?(0,J.jsx)(Kp,{artifact:u.artifact,client:s},JSON.stringify([u.artifact.sessionId,u.artifact.path,u.artifact.handle,u.artifact.revision])):u.text===void 0?(0,J.jsx)(`p`,{children:o(`artifactUnsupported`)}):/\.(?:md|markdown)$/iu.test(u.artifact.name)?(0,J.jsx)(ip.Provider,{value:{open:D,parent:u.artifact.handle},children:(0,J.jsx)(dp,{children:u.text})}):(0,J.jsx)(`section`,{"aria-label":`File preview content`,tabIndex:0,children:(0,J.jsx)(`pre`,{children:u.text})}),(0,J.jsxs)(`details`,{className:`artifact-file-details`,children:[(0,J.jsx)(`summary`,{children:o(`artifactFileDetails`)}),(0,J.jsx)(`p`,{className:`artifact-session`,children:o(`artifactSession`,{sessionId:e})}),(0,J.jsxs)(`p`,{className:`artifact-revision`,children:[o(`artifactVersion`),`:`,` `,(0,J.jsx)(`code`,{children:u.artifact.revision}),` ·`,` `,o(`artifactBytes`,{count:u.artifact.bytes})]})]})]})]})]})]})}),Jp={},Yp;function Xp(){if(Yp)return Jp;Yp=1,Object.defineProperty(Jp,"__esModule",{value:!0}),Jp.styleq=void 0;var e=new WeakMap,t=`$$css`;function n(n){var r,i,a;return n!=null&&(r=n.disableCache===!0,i=n.disableMix===!0,a=n.transform),function(){for(var n=[],o=``,s=null,c=``,l=r?null:e,u=Array(arguments.length),d=0;d0;){var f=u.pop();if(f!=null&&f!==!1){if(Array.isArray(f)){for(var p=0;p0&&(i.style=n),r!=null&&r!==``&&(i[`data-style-src`]=r),i}Object.freeze({});function $p(e){return(e.style.anchorName??``).split(`,`).map(e=>e.trim()).filter(Boolean)}function em(e,t){e.style.anchorName=t.join(`, `)}function tm(e,t){let n=$p(e);n.includes(t)||(n.push(t),em(e,n))}function nm(e,t){em(e,$p(e).filter(e=>e!==t))}var rm=0,im=null,am=!1;function om(){rm+=1}function sm(){im=rm}function cm(){am||typeof document>`u`||(am=!0,document.addEventListener(`pointerdown`,om,!0),document.addEventListener(`keydown`,om,!0),document.addEventListener(`click`,sm,!0))}function lm(){return cm(),rm}function um(){return cm(),im===rm}var dm=new Set(`p.h1.h2.h3.h4.h5.h6.dt.pre.legend.data.dfn.meter.output.progress.option.optgroup.table.thead.tbody.tfoot.tr.colgroup.ul.ol.menu.dl.select.datalist.picture.hgroup.ruby.rt.rp.a.button.label.summary.span.em.strong.b.i.u.s.small.mark.code.kbd.samp.var.sub.sup.abbr.cite.q.time.bdi.bdo.ins.del`.split(`.`));function fm(e){if(!e)return null;let t=null,n=e;for(;n;)dm.has(n.tagName.toLowerCase())&&(t=n),n=n.parentElement;return t?.parentElement??null}var pm={"--color-accent":`var(--color-accent)`,"--color-accent-muted":`var(--color-accent-muted)`,"--color-on-accent":`var(--color-on-accent)`,"--color-neutral":`var(--color-neutral)`,"--color-background-surface":`var(--color-background-surface)`,"--color-background-body":`var(--color-background-body)`,"--color-overlay":`var(--color-overlay)`,"--color-overlay-hover":`var(--color-overlay-hover)`,"--color-overlay-pressed":`var(--color-overlay-pressed)`,"--color-background-muted":`var(--color-background-muted)`,"--color-text-primary":`var(--color-text-primary)`,"--color-text-secondary":`var(--color-text-secondary)`,"--color-text-disabled":`var(--color-text-disabled)`,"--color-text-accent":`var(--color-text-accent)`,"--color-on-dark":`var(--color-on-dark)`,"--color-on-light":`var(--color-on-light)`,"--color-icon-accent":`var(--color-icon-accent)`,"--color-icon-primary":`var(--color-icon-primary)`,"--color-icon-secondary":`var(--color-icon-secondary)`,"--color-icon-disabled":`var(--color-icon-disabled)`,"--color-background-card":`var(--color-background-card)`,"--color-background-popover":`var(--color-background-popover)`,"--color-background-inverted":`var(--color-background-inverted)`,"--color-background-error-inverted":`var(--color-background-error-inverted)`,"--color-success":`var(--color-success)`,"--color-success-muted":`var(--color-success-muted)`,"--color-on-success":`var(--color-on-success)`,"--color-error":`var(--color-error)`,"--color-error-muted":`var(--color-error-muted)`,"--color-on-error":`var(--color-on-error)`,"--color-warning":`var(--color-warning)`,"--color-warning-muted":`var(--color-warning-muted)`,"--color-on-warning":`var(--color-on-warning)`,"--color-border":`var(--color-border)`,"--color-border-emphasized":`var(--color-border-emphasized)`,"--color-skeleton":`var(--color-skeleton)`,"--color-track":`var(--color-track)`,"--color-shadow":`var(--color-shadow)`,"--color-tint-hover":`var(--color-tint-hover)`,"--color-background-blue":`var(--color-background-blue)`,"--color-border-blue":`var(--color-border-blue)`,"--color-icon-blue":`var(--color-icon-blue)`,"--color-text-blue":`var(--color-text-blue)`,"--color-background-cyan":`var(--color-background-cyan)`,"--color-border-cyan":`var(--color-border-cyan)`,"--color-icon-cyan":`var(--color-icon-cyan)`,"--color-text-cyan":`var(--color-text-cyan)`,"--color-background-gray":`var(--color-background-gray)`,"--color-border-gray":`var(--color-border-gray)`,"--color-icon-gray":`var(--color-icon-gray)`,"--color-text-gray":`var(--color-text-gray)`,"--color-background-green":`var(--color-background-green)`,"--color-border-green":`var(--color-border-green)`,"--color-icon-green":`var(--color-icon-green)`,"--color-text-green":`var(--color-text-green)`,"--color-background-orange":`var(--color-background-orange)`,"--color-border-orange":`var(--color-border-orange)`,"--color-icon-orange":`var(--color-icon-orange)`,"--color-text-orange":`var(--color-text-orange)`,"--color-background-pink":`var(--color-background-pink)`,"--color-border-pink":`var(--color-border-pink)`,"--color-icon-pink":`var(--color-icon-pink)`,"--color-text-pink":`var(--color-text-pink)`,"--color-background-purple":`var(--color-background-purple)`,"--color-border-purple":`var(--color-border-purple)`,"--color-icon-purple":`var(--color-icon-purple)`,"--color-text-purple":`var(--color-text-purple)`,"--color-background-red":`var(--color-background-red)`,"--color-border-red":`var(--color-border-red)`,"--color-icon-red":`var(--color-icon-red)`,"--color-text-red":`var(--color-text-red)`,"--color-background-teal":`var(--color-background-teal)`,"--color-border-teal":`var(--color-border-teal)`,"--color-icon-teal":`var(--color-icon-teal)`,"--color-text-teal":`var(--color-text-teal)`,"--color-background-yellow":`var(--color-background-yellow)`,"--color-border-yellow":`var(--color-border-yellow)`,"--color-icon-yellow":`var(--color-icon-yellow)`,"--color-text-yellow":`var(--color-text-yellow)`,__varGroupHash__:`xj0fimd`},mm={"--spacing-0":`var(--spacing-0)`,"--spacing-0-5":`var(--spacing-0-5)`,"--spacing-1":`var(--spacing-1)`,"--spacing-1-5":`var(--spacing-1-5)`,"--spacing-2":`var(--spacing-2)`,"--spacing-3":`var(--spacing-3)`,"--spacing-4":`var(--spacing-4)`,"--spacing-5":`var(--spacing-5)`,"--spacing-6":`var(--spacing-6)`,"--spacing-7":`var(--spacing-7)`,"--spacing-8":`var(--spacing-8)`,"--spacing-9":`var(--spacing-9)`,"--spacing-10":`var(--spacing-10)`,"--spacing-11":`var(--spacing-11)`,"--spacing-12":`var(--spacing-12)`,__varGroupHash__:`x1kvdh9l`},hm={"--focus-outline-width":`var(--focus-outline-width)`,"--focus-outline-style":`var(--focus-outline-style)`,"--focus-outline-color":`var(--focus-outline-color)`,"--focus-outline-offset":`var(--focus-outline-offset)`,__varGroupHash__:`xzxs3qz`},gm={"--duration-fast-min":`var(--duration-fast-min)`,"--duration-fast":`var(--duration-fast)`,"--duration-fast-max":`var(--duration-fast-max)`,"--duration-medium-min":`var(--duration-medium-min)`,"--duration-medium":`var(--duration-medium)`,"--duration-medium-max":`var(--duration-medium-max)`,"--duration-slow-min":`var(--duration-slow-min)`,"--duration-slow":`var(--duration-slow)`,"--duration-slow-max":`var(--duration-slow-max)`,__varGroupHash__:`x14lkjui`},_m={"--ease-standard":`var(--ease-standard)`,__varGroupHash__:`xf09i69`},vm={0:`spacing0`,.5:`spacing0_5`,1:`spacing1`,1.5:`spacing1_5`,2:`spacing2`,3:`spacing3`,4:`spacing4`,5:`spacing5`,6:`spacing6`,8:`spacing8`,10:`spacing10`},ym={0:{kZCmMZ:`x18gyask`,kwRFfy:`x1s0aq8i`,kLKAdn:`x1ydh6w3`,kGO01o:`x1l20ajd`,$$css:!0},1:{kZCmMZ:`x1vsv5vr`,kwRFfy:`x1nryj5t`,kLKAdn:`xfsso4q`,kGO01o:`xy143xn`,$$css:!0},2:{kZCmMZ:`x12gdq22`,kwRFfy:`x1djylfy`,kLKAdn:`x1xye8es`,kGO01o:`x1wesfrj`,$$css:!0},3:{kZCmMZ:`x126nfab`,kwRFfy:`x1t818jl`,kLKAdn:`x1vlblms`,kGO01o:`xvmdzux`,$$css:!0},4:{kZCmMZ:`x1rey3nv`,kwRFfy:`xnjyzlh`,kLKAdn:`x1oa1p4a`,kGO01o:`x1awphl8`,$$css:!0},5:{kZCmMZ:`x1blguxw`,kwRFfy:`xdbrk9v`,kLKAdn:`xx7rijo`,kGO01o:`x1hk98q`,$$css:!0},6:{kZCmMZ:`x31w388`,kwRFfy:`x1we12cn`,kLKAdn:`x1adxfkp`,kGO01o:`xjpqqx5`,$$css:!0},8:{kZCmMZ:`x1j3hnjz`,kwRFfy:`x1q91b2g`,kLKAdn:`xoxd1wu`,kGO01o:`x2oz4g1`,$$css:!0},10:{kZCmMZ:`xqp078j`,kwRFfy:`x160ivqr`,kLKAdn:`xk6660b`,kGO01o:`x2izi54`,$$css:!0},"0.5":{kZCmMZ:`x138rykx`,kwRFfy:`x1le3yxw`,kLKAdn:`xbx876j`,kGO01o:`xij103a`,$$css:!0},"1.5":{kZCmMZ:`xfti1ec`,kwRFfy:`x17hk9do`,kLKAdn:`x1kwdpsa`,kGO01o:`x1opdxmq`,$$css:!0}},bm={0:{"--container-padding-inline-start":`x1gu2k80`,"--container-padding-inline-end":`x91ghl5`,$$css:!0},1:{"--container-padding-inline-start":`x1cvlban`,"--container-padding-inline-end":`x2oyxnl`,$$css:!0},2:{"--container-padding-inline-start":`x1xlrr2o`,"--container-padding-inline-end":`xcas3b9`,$$css:!0},3:{"--container-padding-inline-start":`xfdwxua`,"--container-padding-inline-end":`xu0ipoa`,$$css:!0},4:{"--container-padding-inline-start":`x1dlhslv`,"--container-padding-inline-end":`xs0pscg`,$$css:!0},5:{"--container-padding-inline-start":`x1s81nki`,"--container-padding-inline-end":`xgkj7vj`,$$css:!0},6:{"--container-padding-inline-start":`x1ep0dkj`,"--container-padding-inline-end":`x94cj42`,$$css:!0},8:{"--container-padding-inline-start":`xw1diwv`,"--container-padding-inline-end":`x1b9k1pi`,$$css:!0},10:{"--container-padding-inline-start":`xserb3f`,"--container-padding-inline-end":`xx5lg5w`,$$css:!0},"0.5":{"--container-padding-inline-start":`x14ws0sr`,"--container-padding-inline-end":`x1wz3t3y`,$$css:!0},"1.5":{"--container-padding-inline-start":`x176g23i`,"--container-padding-inline-end":`xntetml`,$$css:!0}},xm={0:{"--container-padding-block-start":`x1i3qcxz`,$$css:!0},1:{"--container-padding-block-start":`xnsckjb`,$$css:!0},2:{"--container-padding-block-start":`xa8b4fq`,$$css:!0},3:{"--container-padding-block-start":`x11k4f5r`,$$css:!0},4:{"--container-padding-block-start":`xm01sq8`,$$css:!0},5:{"--container-padding-block-start":`xp8wdkl`,$$css:!0},6:{"--container-padding-block-start":`x1hmud4d`,$$css:!0},8:{"--container-padding-block-start":`xfv60at`,$$css:!0},10:{"--container-padding-block-start":`x17h9kl7`,$$css:!0},"0.5":{"--container-padding-block-start":`xvdf9ev`,$$css:!0},"1.5":{"--container-padding-block-start":`x1kbx601`,$$css:!0}},Sm={0:{"--container-padding-block-end":`xkunwnr`,$$css:!0},1:{"--container-padding-block-end":`x57a7ii`,$$css:!0},2:{"--container-padding-block-end":`x1lsgcmx`,$$css:!0},3:{"--container-padding-block-end":`x1q3ppug`,$$css:!0},4:{"--container-padding-block-end":`x4hfsld`,$$css:!0},5:{"--container-padding-block-end":`xbib2ws`,$$css:!0},6:{"--container-padding-block-end":`x1q8d17g`,$$css:!0},8:{"--container-padding-block-end":`x8lgq76`,$$css:!0},10:{"--container-padding-block-end":`x15vxphk`,$$css:!0},"0.5":{"--container-padding-block-end":`x1cao3zv`,$$css:!0},"1.5":{"--container-padding-block-end":`xv53x8y`,$$css:!0}},Cm={0:{kZCmMZ:`x18gyask`,$$css:!0},1:{kZCmMZ:`x1vsv5vr`,$$css:!0},2:{kZCmMZ:`x12gdq22`,$$css:!0},3:{kZCmMZ:`x126nfab`,$$css:!0},4:{kZCmMZ:`x1rey3nv`,$$css:!0},5:{kZCmMZ:`x1blguxw`,$$css:!0},6:{kZCmMZ:`x31w388`,$$css:!0},8:{kZCmMZ:`x1j3hnjz`,$$css:!0},10:{kZCmMZ:`xqp078j`,$$css:!0},"0.5":{kZCmMZ:`x138rykx`,$$css:!0},"1.5":{kZCmMZ:`xfti1ec`,$$css:!0}},wm={0:{kwRFfy:`x1s0aq8i`,$$css:!0},1:{kwRFfy:`x1nryj5t`,$$css:!0},2:{kwRFfy:`x1djylfy`,$$css:!0},3:{kwRFfy:`x1t818jl`,$$css:!0},4:{kwRFfy:`xnjyzlh`,$$css:!0},5:{kwRFfy:`xdbrk9v`,$$css:!0},6:{kwRFfy:`x1we12cn`,$$css:!0},8:{kwRFfy:`x1q91b2g`,$$css:!0},10:{kwRFfy:`x160ivqr`,$$css:!0},"0.5":{kwRFfy:`x1le3yxw`,$$css:!0},"1.5":{kwRFfy:`x17hk9do`,$$css:!0}},Tm={0:{kLKAdn:`x1ydh6w3`,$$css:!0},1:{kLKAdn:`xfsso4q`,$$css:!0},2:{kLKAdn:`x1xye8es`,$$css:!0},3:{kLKAdn:`x1vlblms`,$$css:!0},4:{kLKAdn:`x1oa1p4a`,$$css:!0},5:{kLKAdn:`xx7rijo`,$$css:!0},6:{kLKAdn:`x1adxfkp`,$$css:!0},8:{kLKAdn:`xoxd1wu`,$$css:!0},10:{kLKAdn:`xk6660b`,$$css:!0},"0.5":{kLKAdn:`xbx876j`,$$css:!0},"1.5":{kLKAdn:`x1kwdpsa`,$$css:!0}},Em={0:{kGO01o:`x1l20ajd`,$$css:!0},1:{kGO01o:`xy143xn`,$$css:!0},2:{kGO01o:`x1wesfrj`,$$css:!0},3:{kGO01o:`xvmdzux`,$$css:!0},4:{kGO01o:`x1awphl8`,$$css:!0},5:{kGO01o:`x1hk98q`,$$css:!0},6:{kGO01o:`xjpqqx5`,$$css:!0},8:{kGO01o:`x2oz4g1`,$$css:!0},10:{kGO01o:`x2izi54`,$$css:!0},"0.5":{kGO01o:`xij103a`,$$css:!0},"1.5":{kGO01o:`x1opdxmq`,$$css:!0}},Dm={reset:{"--container-padding-inline-start":`xrhngw9`,"--container-padding-inline-end":`xjsfl84`,"--container-padding-block-start":`x1047aw6`,"--container-padding-block-end":`xax9j7h`,"--layout-padding-outer-x":`xdt8ak2`,"--layout-padding-outer-y":`x1rs4lu4`,"--layout-padding-inner-x":`x1qfll2g`,"--layout-padding-inner-y":`xyvxpqs`,"--_section-padding-propagated":`x1f17rg1`,$$css:!0}},Om=h(),km={keoZOQ:`x1vhfslr`,k1K539:`xlm3tn6`,$$css:!0},Am={base:{keoZOQ:`xdj266r`,k1K539:`xat24cr`,keTefX:`x1lziwak`,k71WvV:`x14z9mp`,kLKAdn:`xexx8yu`,kGO01o:`x18d9i69`,kZCmMZ:`x1c1uobl`,kwRFfy:`xyri2b`,kMzoRj:`xc342km`,ksu8eU:`xng3xce`,kVQacm:`x1rea2x4`,kMv6JI:`x9ynric`,kGuDYH:`xjm74w1`,kLWn49:`xw6l6zx`,kWkggS:`xjbqb8w`,$$css:!0},fixed:{kVAEAm:`xixxii4`,$$css:!0},offsetBlock:e=>[km,{"--x-marginBlockStart":(e=>typeof e==`number`?e+`px`:e??void 0)(e),"--x-marginBlockEnd":(e=>typeof e==`number`?e+`px`:e??void 0)(e)}],offsetInline:e=>[{keTefX:e==null?e:`x4lel18`,k71WvV:e==null?e:`x1c9tiao`,$$css:!0},{"--x-marginInlineStart":(e=>typeof e==`number`?e+`px`:e??void 0)(e),"--x-marginInlineEnd":(e=>typeof e==`number`?e+`px`:e??void 0)(e)}]};function jm(e){return typeof e==`number`?`${e}px`:e}function Mm(e,t){let n=e.ownerDocument.defaultView;if(!n)return{};let r=n.getComputedStyle(e),i=n.getComputedStyle(t);return{...r.direction!==i.direction&&{direction:r.direction},...r.writingMode!==i.writingMode&&{writingMode:r.writingMode}}}function Nm(e=`above`,t=`center`){if(e===`above`||e===`below`){let n=e===`above`?`self-block-start`:`self-block-end`;return t===`start`?`${n} span-self-inline-end`:t===`end`?`${n} span-self-inline-start`:n}let n=e===`start`?`self-inline-start`:`self-inline-end`;return t===`start`?`${n} span-self-block-end`:t===`end`?`${n} span-self-block-start`:n}function Pm(e=`above`,t=`center`){let n=`flip-block, flip-inline, flip-block flip-inline`;if(t!==`center`)return n;if(e===`above`||e===`below`){let[t,r]=e===`above`?[`top`,`bottom`]:[`bottom`,`top`];return`${n}, ${t} span-left, ${t} span-right, ${r} span-left, ${r} span-right`}let[r,i]=e===`start`?[`left`,`right`]:[`right`,`left`];return`${n}, ${r} span-top, ${r} span-bottom, ${i} span-top, ${i} span-bottom`}function Fm(e){let{mode:t,onShow:n,onHide:r,lightDismiss:i=!1}=e,a=t===`context`?e.lazyMount??!1:!1,o=(0,w.useId)(),s=`--astryx-layer-${o.replace(/:/g,``)}`,[c,l]=(0,w.useState)(!1),u=(0,w.useRef)(null),d=(0,w.useRef)(null),f=(0,w.useRef)(null),p=(0,w.useRef)(null),m=(0,w.useRef)(null),[h,g]=(0,w.useState)(null),_=(0,w.useRef)(!1),v=(0,w.useRef)(!1),y=(0,w.useRef)(null),b=(0,w.useRef)(null),x=(0,w.useCallback)(()=>{let e=lm();return y.current===e},[]),S=(0,w.useCallback)(e=>{typeof e.showPopover==`function`?e.showPopover({source:f.current??void 0}):e.style.display=`block`,d.current=e},[]),C=(0,w.useCallback)(e=>{if(t!==`context`)return!0;let n=m.current;if(n===null)return!1;let r=n.portalTarget??p.current?.parentElement??null;return e.parentElement===r},[t]),T=(0,w.useCallback)(()=>{if(t!==`context`)return;let e=p.current,n=e?.parentElement??null;if(!e||!n)return;let r=fm(n),i={portalTarget:r,portalStyle:r?Mm(e,r):{}};m.current=i,g(i)},[t]),E=(0,w.useCallback)(()=>{t!==`context`||!a||(m.current=null,g(null))},[t,a]),D=(0,w.useCallback)(()=>{if(x())return;let e=u.current,t=e&&C(e)?e:null;if(!t){_.current=!0,T();return}v.current||(S(t),v.current=!0,l(!0),n?.())},[n,T,S,C,x]),O=(0,w.useCallback)(()=>{if(_.current=!1,v.current){let e=u.current;d.current=null,v.current=!1,e&&(typeof e.hidePopover==`function`?e.hidePopover():e.style.display=`none`),l(!1),r?.()}E()},[r,E]),k=(0,w.useCallback)(e=>{f.current&&f.current!==e&&nm(f.current,s),e&&tm(e,s),f.current=e},[s]),A=(0,w.useCallback)(e=>{if(b.current?.(),um())return;y.current=lm();let t=e.defaultView,n=null,r=()=>{y.current=null,e.removeEventListener(`click`,i,!0),n!==null&&(t?.clearTimeout(n),n=null),b.current===r&&(b.current=null)},i=()=>{e.removeEventListener(`click`,i,!0),t?n=t.setTimeout(()=>{n=null,b.current===r&&r()},0):r()};e.addEventListener(`click`,i,!0),b.current=r},[]);(0,w.useEffect)(()=>(lm(),()=>b.current?.()),[]);let j=(0,w.useCallback)(e=>{e.newState===`closed`&&v.current&&(d.current=null,v.current=!1,A(e.currentTarget?.ownerDocument??document),l(!1),r?.(),E())},[r,E,A]),M=(0,w.useRef)(null),N=(0,w.useRef)(null),P=(0,w.useCallback)((e,t)=>{M.current&&N.current&&(M.current!==e||N.current!==t)&&(M.current.removeEventListener(`toggle`,N.current),M.current=null,N.current=null),e&&M.current!==e&&(e.addEventListener(`toggle`,t),M.current=e,N.current=t)},[]),F=(0,w.useCallback)(e=>{u.current=e,P(e,j),e&&_.current?(_.current=!1,D()):e&&v.current&&d.current!==e&&C(e)&&S(e)},[j,P,D,S,C]),I=(0,w.useCallback)(e=>{p.current=e,e&&(!a||_.current||v.current)&&T()},[a,T]);(0,w.useEffect)(()=>(u.current&&P(u.current,j),()=>{M.current&&N.current&&(M.current.removeEventListener(`toggle`,N.current),M.current=null,N.current=null)}),[j,P]);let L=(0,w.useCallback)((e,t)=>{let n=(0,J.jsx)(`template`,{ref:I});if(h===null)return(0,J.jsx)(J.Fragment,{children:n});let{placement:r=`above`,alignment:a=`center`,positioning:c=`anchor`,offset:l,role:u,"aria-label":d,xstyle:f,className:p,style:m,as:g=`div`,onMouseEnter:_,onMouseLeave:v}=t||{},y=c===`custom`?{positionAnchor:s}:{positionAnchor:s,positionArea:Nm(r,a),positionTryFallbacks:Pm(r,a)},b=c===`anchor`&&l?r===`above`||r===`below`?Am.offsetBlock(jm(l)):Am.offsetInline(jm(l)):null,x=Qp(Am.base,Dm.reset,b,f),S=p?`${p} ${x.className??``}`:x.className,C=(0,J.jsx)(g,{ref:F,id:o,role:u,"aria-label":d,popover:i?`auto`:`manual`,className:S,style:{...x.style,...y,...h.portalStyle,...m},onMouseEnter:_,onMouseLeave:v,children:e});return(0,J.jsxs)(J.Fragment,{children:[n,h.portalTarget?(0,Om.createPortal)(C,h.portalTarget):C]})},[s,h,o,i,F,I]),R=(0,w.useCallback)((e,t)=>{let{x:n,y:r,xstyle:a,className:s,style:c}=t,l={top:r,left:n},u=Qp(Am.base,Dm.reset,Am.fixed,a),d=s?`${s} ${u.className??``}`:u.className;return(0,J.jsx)(`div`,{ref:F,id:o,popover:i?`auto`:`manual`,className:d,style:{...u.style,...l,...c},children:e})},[F,o,i]),z=(0,w.useMemo)(()=>({ref:k,anchorId:s,show:D,hide:O,isOpen:c,wasJustDismissed:x,id:o,render:L}),[k,s,D,O,c,x,o,L]),B=(0,w.useMemo)(()=>({ref:void 0,show:D,hide:O,isOpen:c,wasJustDismissed:x,id:o,render:R}),[D,O,c,x,o,R]);return t===`context`?z:B}function Im(e){let t=Fm(e);return(0,w.useMemo)(()=>{let{wasJustDismissed:e,...n}=t;return n},[t])}function Lm(e){return Fm(e)}var Rm=`button:not([disabled]), a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled]), [contenteditable]:not([contenteditable="false"]), audio[controls], video[controls], iframe, details > summary:first-child`,zm=(0,w.createContext)(0);zm.displayName=`LayerDepthContext`;function Bm(){return(0,w.use)(zm)}function Vm({children:e}){let t=(0,w.use)(zm);return(0,J.jsx)(zm,{value:t+1,children:e})}Vm.displayName=`LayerDepthProvider`;var Hm=229;function Um(e){return e.isComposing===!0||e.keyCode===Hm}var Wm=[],Gm=new WeakMap,Km=0,qm=!1;function Jm(e){let t=Gm.get(e);if(t!==void 0)return t;let n=Km++;return Gm.set(e,n),n}function Ym(e,t){if(e.depth!==t.depth)return e.depth-t.depth;let n=e.getContainer?.()??null,r=t.getContainer?.()??null;if(n!=null&&r!=null&&n!==r){if(r.contains(n))return 1;if(n.contains(r))return-1}return e.seq-t.seq}function Xm(e){return e.isPresent?.()??!0}var Zm=!1;function Qm(){return Zm}function $m(){Zm=!0}function eh(){Zm=!1}function th(){let e=null;for(let t of Wm)Xm(t)&&(e==null||Ym(t,e)>0)&&(e=t);return e}function nh(e){return th()?.token===e}function rh(){let e=th();return e!=null&&(e.behavior===`block`||e.dismiss(),!0)}function ih(e){if(e.key===`Escape`){if(Um(e)){th()!=null&&e.preventDefault();return}e.defaultPrevented||rh()&&e.preventDefault()}}function ah(){qm||typeof document>`u`||(document.addEventListener(`keydown`,ih),document.addEventListener(`compositionstart`,$m,!0),document.addEventListener(`compositionend`,eh,!0),document.addEventListener(`blur`,eh,!0),qm=!0)}function oh(){!qm||typeof document>`u`||(document.removeEventListener(`keydown`,ih),document.removeEventListener(`compositionstart`,$m,!0),document.removeEventListener(`compositionend`,eh,!0),document.removeEventListener(`blur`,eh,!0),Zm=!1,qm=!1)}function sh(e){let t={...e,seq:Jm(e.token)};return Wm.push(t),ah(),()=>{let e=Wm.indexOf(t);e!==-1&&Wm.splice(e,1),Wm.length===0&&oh()}}function ch(e){let{isActive:t,onDismiss:n,escapeBehavior:r=`close`,getContainer:i,isPresent:a,isEnabled:o=!0}=e,s=Bm(),c=(0,w.useRef)({}),l=(0,w.useRef)(n),u=(0,w.useRef)(i),d=(0,w.useRef)(a);(0,w.useEffect)(()=>{l.current=n,u.current=i,d.current=a});let f=t&&o;return(0,w.useEffect)(()=>{if(f)return sh({token:c.current,depth:s,behavior:r,getContainer:()=>u.current?.()??null,isPresent:()=>d.current?.()??!0,dismiss:()=>l.current()})},[f,s,r]),{shouldDismissOnCloseRequest:(0,w.useCallback)(()=>f&&!Qm()&&nh(c.current),[f])}}var lh=0;function uh(e){if(e.hasAttribute(`inert`)||e.closest(`[inert]`)||e.hidden||e.closest(`[hidden]`)||e.closest(`[aria-hidden="true"]`))return!1;if(typeof window<`u`&&window.getComputedStyle){let t=window.getComputedStyle(e);if(t.visibility===`hidden`||t.display===`none`)return!1}return!0}function dh(e){return Array.from(e.querySelectorAll(Rm)).filter(uh)}function fh(e){try{e.focus()}catch{}return document.activeElement===e}function ph(e){let t=dh(e);for(let e of t)if(fh(e))return!0;return!1}function mh(e){let t=dh(e);for(let e=t.length-1;e>=0;e--)if(fh(t[e]))return!0;return!1}function hh(e){let{isActive:t,onEscape:n}=e,r=(0,w.useRef)(null),i=(0,w.useRef)(null),a=(0,w.useRef)(!1),o=t&&n!=null;ch({isActive:o,onDismiss:()=>{n?.()},getContainer:()=>r.current}),(0,w.useEffect)(()=>{if(o)return lh+=1,()=>{--lh}},[o]);let s=(0,w.useCallback)(()=>{r.current&&ph(r.current)},[]);return(0,w.useEffect)(()=>{if(!t)return;let e=document.activeElement,n=r.current;return()=>{let t=document.activeElement;(t==null||t===document.body||t===document.documentElement||n!=null&&n.contains(t))&&e!=null&&e.isConnected&&typeof e.focus==`function`&&e.focus()}},[t]),(0,w.useEffect)(()=>{if(!t)return;let e=e=>{let t=r.current;if(!t)return;let n=e.target;if(t.contains(n))i.current=n;else if(a.current){let e=ph(t);e&&i.current===document.activeElement?mh(t):!e&&i.current instanceof HTMLElement&&t.contains(i.current)&&fh(i.current),i.current=document.activeElement}a.current=!1};return document.addEventListener(`focus`,e,!0),()=>{document.removeEventListener(`focus`,e,!0)}},[t]),(0,w.useEffect)(()=>{if(!t)return;let e=e=>{let t=r.current;if(t&&e.key===`Tab`){a.current=!0;let n=dh(t);if(n.length===0){let n=document.activeElement;if(!(n instanceof HTMLElement)||!t.contains(n))return;e.preventDefault(),i.current=n,a.current=!1;return}let r=n[0],o=n[n.length-1];e.shiftKey?document.activeElement===r&&(e.preventDefault(),o.focus()):document.activeElement===o&&(e.preventDefault(),r.focus())}};return document.addEventListener(`keydown`,e),()=>{document.removeEventListener(`keydown`,e)}},[t,n]),{containerRef:r,focusFirst:s}}var gh=`keyboard`,_h=!1;function vh(){gh=`pointer`}function yh(e){e.metaKey||e.altKey||e.ctrlKey||(gh=`keyboard`)}function bh(){_h||typeof document>`u`||(_h=!0,document.addEventListener(`pointerdown`,vh,{capture:!0,passive:!0}),document.addEventListener(`keydown`,yh,{capture:!0,passive:!0}))}function xh(){return gh}var Sh=new Set([`touch`,`pen`]),Ch=new Set([`button`,`checkbox`,`combobox`,`link`,`menuitem`,`menuitemcheckbox`,`menuitemradio`,`option`,`radio`,`searchbox`,`slider`,`spinbutton`,`switch`,`tab`,`textbox`]);function wh(e){let t=e.getAttribute(`role`);if(t!=null&&t!==``)return Ch.has(t);switch(e.tagName){case`BUTTON`:case`INPUT`:case`LABEL`:case`SELECT`:case`SUMMARY`:case`TEXTAREA`:return!0;case`A`:case`AREA`:return e.hasAttribute(`href`);default:return Th(e)}}function Th(e){if(e.isContentEditable===!0)return!0;let t=e.getAttribute(`contenteditable`);return t!=null&&t!==`false`}function Eh(e){let{touchTrigger:t,isEnabled:n,isControlled:r,isOpen:i,layerId:a,triggerRef:o,show:s,hide:c}=e,l=(0,w.useRef)(!1),u=(0,w.useRef)(i);u.current=i;let d=(0,w.useRef)(c);d.current=c;let f=(0,w.useRef)(a);f.current=a;let p=(0,w.useRef)(!1),m=(0,w.useRef)(null);(0,w.useEffect)(()=>{bh()},[]);let h=(0,w.useCallback)(()=>{p.current=!1;let e=m.current;e!=null&&(m.current=null,document.removeEventListener(`pointerdown`,e,!0))},[]),g=(0,w.useCallback)(()=>{if(p.current=!0,m.current!=null)return;let e=e=>{let t=e.target;(t==null||o.current?.contains(t)!==!0&&document.getElementById(f.current)?.contains(t)!==!0)&&(h(),d.current())};m.current=e,document.addEventListener(`pointerdown`,e,!0)},[o,h]);(0,w.useEffect)(()=>h,[h]);let _=(0,w.useCallback)(()=>l.current&&xh()===`pointer`,[]),v=(0,w.useCallback)(e=>{l.current=e.pointerType===`touch`},[]),y=(0,w.useCallback)(e=>{let i=Sh.has(e.pointerType);if(l.current=i,!i||r)return!1;let a=o.current;return(t===`auto`?a!=null&&wh(a)?`none`:`tap`:t)===`none`||!n||u.current||p.current?(h(),c(),!0):(g(),s(),!0)},[t,n,r,o,s,c,g,h]),b=(0,w.useRef)(i);return(0,w.useEffect)(()=>{b.current&&!i&&h(),b.current=i},[i,h]),{isTouchPointerRef:l,isTouchInteraction:_,handlePointerEnter:v,handlePointerDown:y,clearTapOpen:h}}gm[`--duration-fast-max`],_m[`--ease-standard`];var Dh={below:{kKVMdj:`xl1vlw0 x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0},above:{kKVMdj:`x3psbcj x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0},end:{kKVMdj:`x1i331go x1vxsm5i x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0},start:{kKVMdj:`xck01x9 x18lne9g x1aquc0h`,k44tkh:`x9uej1z`,kyAemX:`x128ha8g`,kWV6AL:`xskzprw`,$$css:!0}},Oh=`astryx`,kh=Oh,Ah=Oh,jh=Oh;function Mh(e){return`${kh}-${e}`}function Nh(e){return`data-${Ah}-${e}`}function Ph(e){return`--${jh}-${e}`}function Fh(e){return`data-${e.replace(/([a-z0-9])([A-Z])/g,`$1-$2`).toLowerCase()}`}function Ih(e,t){return/^\d/.test(t)?`${e}-${t}`:t}function Lh(e,t){let n=[Mh(e)];if(t)for(let[e,r]of Object.entries(t))r!=null&&n.push(Ih(e,String(r)));return n.join(` `)}function Rh(e){let t={};if(e)for(let[n,r]of Object.entries(e))r!=null&&(t[Fh(n)]=String(r));return t}function zh(e,t,n){let r=Lh(e,t),i=n?.legacyNames?.map(e=>Mh(e))??[];return{className:i.length>0?[r,...i].join(` `):r,...Rh(t)}}var Bh=100,Vh={container:{kWkggS:`x19aspcf`,kMwMTN:`xrkvqaz`,kaIpWk:`x1hviunn`,kMv6JI:`x9ynric`,kGuDYH:`xjm74w1`,kLWn49:`xw6l6zx`,$$css:!0}};function Hh(e){return e.hasAttribute(`tabindex`)?e.tabIndex>=0:[`A`,`BUTTON`,`INPUT`,`SELECT`,`TEXTAREA`].includes(e.tagName)?!e.disabled:!!e.isContentEditable}function Uh(e={}){let{placement:t=`above`,alignment:n=`center`,delay:r=200,hideDelay:i=0,focusTrigger:a=`auto`,touchTrigger:o=`auto`,isEnabled:s=!0,isOpen:c,isDefaultOpen:l=!1,onShow:u,onHide:d}=e,f=Im({mode:`context`,onShow:u,onHide:d}),p=Vh.container,m=(0,w.useRef)(null),h=(0,w.useRef)(null),g=(0,w.useRef)(null),_=(0,w.useCallback)(()=>{m.current&&=(clearTimeout(m.current),null),h.current&&=(clearTimeout(h.current),null)},[]),v=(0,w.useCallback)(()=>{_(),f.show()},[_,f]),y=(0,w.useCallback)(()=>{_(),f.hide()},[_,f]),b=Eh({touchTrigger:o,isEnabled:s,isControlled:c!==void 0,isOpen:f.isOpen,layerId:f.id,triggerRef:g,show:v,hide:y}),x=(0,w.useCallback)(()=>{!s||c===!1||(_(),m.current=setTimeout(()=>{f.show()},r))},[s,c,_,f,r]),S=(0,w.useCallback)(()=>{c!==!0&&(_(),h.current=setTimeout(()=>{f.hide()},i>0?i:Bh))},[c,_,f,i]),C=(0,w.useCallback)(()=>{h.current&&=(clearTimeout(h.current),null)},[]),T=(0,w.useCallback)(()=>{b.isTouchPointerRef.current||x()},[b,x]),E=(0,w.useCallback)(()=>{b.isTouchPointerRef.current||S()},[b,S]),D=(0,w.useCallback)(e=>{s&&(b.isTouchInteraction()||e.target.matches(`:focus-visible`)&&(_(),f.show()))},[s,b,_,f]),O=(0,w.useCallback)(()=>{S()},[S]),k=(0,w.useCallback)(e=>{b.handlePointerDown(e)||c===void 0&&(_(),f.hide())},[b,c,_,f]),{handlePointerEnter:A,clearTapOpen:j}=b,M=(0,w.useCallback)(e=>{g.current&&(g.current.removeEventListener(`mouseenter`,T),g.current.removeEventListener(`mouseleave`,E),g.current.removeEventListener(`focusin`,D),g.current.removeEventListener(`focusout`,O),g.current.removeEventListener(`pointerenter`,A),g.current.removeEventListener(`pointerdown`,k)),e&&(e.addEventListener(`pointerenter`,A),e.addEventListener(`mouseenter`,T),e.addEventListener(`mouseleave`,E),e.addEventListener(`pointerdown`,k),(a===`always`||a===`auto`&&Hh(e))&&(e.addEventListener(`focusin`,D),e.addEventListener(`focusout`,O))),g.current=e},[a,T,E,D,O,A,k]),N=(0,w.useCallback)(e=>{f.ref(e),M(e)},[f,M]);(0,w.useEffect)(()=>()=>{_()},[_]),(0,w.useEffect)(()=>{l&&f.show()},[]),(0,w.useEffect)(()=>{c!==void 0&&(c?(_(),f.show()):(_(),f.hide()))},[c,_,f]),ch({isActive:!0,isPresent:()=>{let e=typeof document>`u`?null:document.getElementById(f.id);if(e==null)return!1;try{return e.matches(`:popover-open`)}catch{return f.isOpen}},onDismiss:()=>{if(_(),j(),c!==void 0){d?.();return}f.hide()}});let P=(0,w.useCallback)((e,r)=>{let i=r?.placement??t,a={placement:i,alignment:r?.alignment??n,offset:mm[`--spacing-1`],role:`tooltip`,xstyle:[p,Dh[i]],className:zh(`tooltip`).className,onMouseEnter:C,onMouseLeave:S};return f.render((0,J.jsx)(`div`,{className:`xfsso4q xy143xn x12gdq22 x1djylfy xw5ewwj x13faqbe`,children:e}),a)},[f,t,n,p,C,S]);return{ref:N,positionRef:f.ref,interactionRef:M,anchorId:f.anchorId,describedBy:f.id,renderTooltip:P}}var Wh={primary:{kMwMTN:`x1tgivj0`,$$css:!0},secondary:{kMwMTN:`xv1l7n4`,$$css:!0},disabled:{kMwMTN:`xnbbluu`,$$css:!0},placeholder:{kMwMTN:`xv1l7n4`,$$css:!0},accent:{kMwMTN:`xjse4m1`,$$css:!0},inherit:{kMwMTN:`x1heor9g`,$$css:!0}},Gh={normal:{k63SB2:`x1sodnla`,$$css:!0},medium:{k63SB2:`x1e4wzip`,$$css:!0},semibold:{k63SB2:`x2mo6ok`,$$css:!0},bold:{k63SB2:`x1lvx875`,$$css:!0}},Kh={body:{k63SB2:`xxovm9e`,$$css:!0},large:{k63SB2:`x149oux8`,$$css:!0},label:{k63SB2:`xmhvcl5`,$$css:!0},code:{k63SB2:`xx3eeay`,$$css:!0},supporting:{k63SB2:`xv8on6e`,$$css:!0},"display-1":{k63SB2:`x1txul5o`,$$css:!0},"display-2":{k63SB2:`x1y36c3f`,$$css:!0},"display-3":{k63SB2:`x1on40hk`,$$css:!0},inherit:{k63SB2:`x1pd3egz`,$$css:!0}},qh={body:{kGuDYH:`xjm74w1`,kLWn49:`xw6l6zx`,$$css:!0},large:{kGuDYH:`x18juvz8`,kLWn49:`xf74fhv`,$$css:!0},label:{kGuDYH:`xcr08ib`,kLWn49:`x1kq96og`,$$css:!0},code:{kGuDYH:`xp03k98`,kLWn49:`x17iicif`,kMv6JI:`x9m5x89`,$$css:!0},supporting:{kGuDYH:`x141an7d`,kLWn49:`x1ltkj2j`,$$css:!0},"display-1":{kGuDYH:`xsub3ws`,kLWn49:`x112ttwr`,$$css:!0},"display-2":{kGuDYH:`x1yego12`,kLWn49:`xh0iwvy`,$$css:!0},"display-3":{kGuDYH:`xlgnzhf`,kLWn49:`x1ujwuaq`,$$css:!0},inherit:{kGuDYH:`x1qlqyl8`,kLWn49:`x15bjb6t`,$$css:!0}},Jh={"4xs":{kGuDYH:`xxc45ev`,$$css:!0},"3xs":{kGuDYH:`x10p7juq`,$$css:!0},"2xs":{kGuDYH:`x16a80zy`,$$css:!0},xsm:{kGuDYH:`x51wmvv`,$$css:!0},sm:{kGuDYH:`x1eqnyfr`,$$css:!0},base:{kGuDYH:`x1j29vfg`,$$css:!0},lg:{kGuDYH:`xc7cgfe`,$$css:!0},xl:{kGuDYH:`x1wqms48`,$$css:!0},"2xl":{kGuDYH:`xhs0kqb`,$$css:!0},"3xl":{kGuDYH:`x10srzze`,$$css:!0},"4xl":{kGuDYH:`xqcvi3d`,$$css:!0}},Yh={inline:{k1xSpc:`xt0psk2`,$$css:!0},block:{k1xSpc:`x1lliihq`,$$css:!0}},Xh={singleLine:{kVQacm:`xb3r6kr`,kg5iWk:`xlyipyv`,khDVqt:`xuxw1ft`,k1xSpc:`x1lliihq`,$$css:!0},multiLine:{kVQacm:`xb3r6kr`,k1xSpc:`x104kibb`,kgKLqz:`x1ua5tub`,$$css:!0}},Zh={"break-word":{kTgw9:`x1lldw8n`,kHjlTd:`x1mzt3pk`,$$css:!0},"break-all":{kTgw9:`x1yn0g08`,$$css:!0}},Qh={wrap:{kN2L0X:`xk4td0m`,$$css:!0},nowrap:{kN2L0X:`xebhuq6`,$$css:!0},balance:{kN2L0X:`x1w2vvpw`,$$css:!0},pretty:{kN2L0X:`x1fzhlzt`,$$css:!0}},$h={enabled:{kxwWH2:`x1b2iylo`,kzeHkT:`xwgcxoh`,k1xSpc:`x1lliihq`,$$css:!0}},eg={strikethrough:{kybGjl:`xmqliwb`,$$css:!0}},tg={enabled:{kcqcaj:`xss6m8b`,$$css:!0}},ng={start:{k9WMMc:`x1yc453h`,$$css:!0},center:{k9WMMc:`x2b8uid`,$$css:!0},end:{k9WMMc:`xp4054r`,$$css:!0}},rg={content:{ks0D6T:`xw5ewwj`,kTgw9:`x13faqbe`,$$css:!0}},ig=null,ag=new Map;function og(){return typeof ResizeObserver>`u`?null:(ig||=new ResizeObserver(e=>{for(let t of e){let e=ag.get(t.target);e&&e(t)}}),ig)}function sg(e,t){ag.set(e,t),og()?.observe(e),t({target:e})}function cg(e){ag.delete(e),ig&&(ig.unobserve(e),ag.size===0&&(ig.disconnect(),ig=null))}function lg(e){let{maxLines:t}=e,[n,r]=(0,w.useState)(!1),[i,a]=(0,w.useState)(``),o=(0,w.useRef)(null),s=(0,w.useCallback)(e=>{if(t===0){r(!1);return}if(a(e.textContent??``),t===1)r(e.scrollWidth>e.offsetWidth);else{let t=e.scrollHeight;try{let n=document.createRange();n.selectNodeContents(e),t=n.getBoundingClientRect().height,n.detach()}catch{}r(t>e.offsetHeight)}},[t]);return{ref:(0,w.useCallback)(e=>{o.current&&cg(o.current),o.current=e,e&&t>0?typeof ResizeObserver<`u`?sg(e,()=>{s(e)}):s(e):(r(!1),a(``))},[t,s]),isTruncated:n,fullText:i}}function ug(e){return e===`base`?``:e.split(`+`).map(e=>{let[t,n]=e.split(`:`);return n===void 0?`.${t}`:/^\d/.test(n)?`.${t}-${n}`:`.${n}`}).join(``)}function dg(e,t){let n={...e,...t},r=[e.className,t.className].filter(Boolean).join(` `);r?n.className=r:delete n.className;let i=t.style&&e.style?{...e.style,...t.style}:t.style||e.style;return i?n.style=i:delete n.style,n}function fg(e,t,n,r){if(typeof e==`string`){let i=e,a=t??{className:``},o=n,s=a.className?`${i} ${a.className}`:i;o&&(s=`${s} ${o}`);let c=r&&a.style?{...a.style,...r}:r||a.style;return{...a,className:s,style:c}}let i=dg(e,typeof t==`string`?{className:t}:t??{});return typeof n==`string`?i=dg(i,{className:n}):n!=null&&(i=dg(i,{style:n})),r!=null&&(i=dg(i,{style:r})),i}function pg(...e){return t=>{let n=[];for(let r of e)if(typeof r==`function`){let e=r(t);n.push(typeof e==`function`?e:()=>r(null))}else if(r!=null){let e=r;e.current=t,n.push(()=>{e.current=null})}if(t!=null&&n.length>0)return()=>{for(let e of n)e()}}}function mg(...e){return t=>{for(let n of e)if(n?.(t),t.defaultPrevented)return}}function hg(e){return e!=null&&typeof e!=`boolean`&&e!==``}function gg(...e){let t=e.flatMap(e=>typeof e==`string`?e.trim().split(/\s+/):[]).filter(Boolean);if(t.length!==0)return Array.from(new Set(t)).join(` `)}function _g(e,t=[],n){return{ariaLabelledBy:n?gg(n.labelID,e):void 0,ariaDescribedBy:gg(n?.describedByIDs,...t)}}var vg={kbCHJM:`x1nrll8i`,k3aq6I:`xsqj5wx`,$$css:!0},yg={mirror:{k3aq6I:`xgtlewx`,$$css:!0},centerInline:e=>[vg,{"--x-transform":`translate(-50%, ${e})`==null?void 0:`translate(-50%, ${e})`}]},bg=hm[`--focus-outline-width`],xg=hm[`--focus-outline-style`],Sg=hm[`--focus-outline-color`];hm[`--focus-outline-offset`],`${bg}${xg}${Sg}`;var Cg={focusVisible:{kMeerF:`x1k57tk5 x1vidyx5`,k3XXqK:`x1t137rt x1jhp3zv`,kjBf7l:`xx47ajj`,kInvED:`x1wfwxd8 x1vwwbsn`,$$css:!0},focusWithin:{kMeerF:`x1k57tk5 x11j6mr8`,k3XXqK:`x1t137rt xciu248`,kjBf7l:`x1uy843r`,kInvED:`x1wfwxd8 x1jumodi`,$$css:!0},focusWithinFirstChild:{kMeerF:`x1k57tk5 xmmisi4`,k3XXqK:`x1t137rt xfd04fr`,kjBf7l:`xobxmqy`,kInvED:`x1wfwxd8 x2vr5qc`,$$css:!0},suppressed:{kMeerF:`x1k57tk5`,k3XXqK:`x1t137rt`,kInvED:`x1wfwxd8`,$$css:!0},publishFocusVisibleVars:{"--_focus-outline":`x17wzz1v xqih627`,"--_focus-outline-offset":`xgzxwq1 xqchwus`,$$css:!0},focusWithinOrPublished:{kI3sdo:`xaw4jrz x16s19ga`,kInvED:`x1kvmbwa x1jumodi`,$$css:!0}};function wg(e){return(...t)=>Qp(e,...t)}var Tg={focusVisible:wg(Cg.focusVisible),focusWithin:wg(Cg.focusWithin),focusWithinFirstChild:wg(Cg.focusWithinFirstChild),suppressed:wg(Cg.suppressed),publishFocusVisibleVars:wg(Cg.publishFocusVisibleVars),focusWithinOrPublished:wg(Cg.focusWithinOrPublished)};function Eg(e,t,n,r,i,a){return(0,w.useMemo)(()=>pg(e,t,n,r,i,a),[e,t,n,r,i,a])}var Dg=`modulepreload`,Og=function(e){return`/`+e},kg={},Ag=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=Og(t,n),t=s(t),t in kg)return;kg[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:Dg,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},jg=(0,w.lazy)(async()=>Ag(()=>Promise.resolve().then(()=>Jb).then(e=>({default:e.Tooltip})),void 0)),Mg={body:`primary`,large:`primary`,label:`primary`,supporting:`secondary`,code:`primary`,"display-1":`primary`,"display-2":`primary`,"display-3":`primary`,inherit:`inherit`};function Ng(e){return e in qh?e:`body`}function Pg(e){return e in Wh?e:`primary`}function Fg({type:e=`body`,size:t,color:n,weight:r,display:i=`inline`,maxLines:a=0,hasTruncateTooltip:o=!0,wordBreak:s,textWrap:c,justify:l=`start`,hasCapsize:u=!1,hasStrikethrough:d=!1,hasTabularNumbers:f=!1,xstyle:p,className:m,style:h,as:g=`span`,children:_,ref:v,...y}){let b=n??Mg[e]??`primary`,x=Ng(e),S=Pg(b),C=s??(a===1?`break-all`:`break-word`),T=a>0||u?`block`:i,E=lg({maxLines:a}),D=typeof o==`string`?o:`above`,O=a>0&&o!==!1&&E.isTruncated,k=(0,w.useRef)(null),A=Eg(v,E.ref,k),j=a>1?{WebkitLineClamp:a}:void 0;return(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(g,{ref:A,...fg(zh(`text`,{type:e,size:t,color:b}),Qp(Wh[S],qh[x],t&&Jh[t],Kh[x],r&&Gh[r],a===1?Xh.singleLine:a>1?Xh.multiLine:Yh[T],a>0&&Zh[C],c&&Qh[c],l!==`start`&&ng[l],u&&$h.enabled,d&&eg.strikethrough,f&&tg.enabled,p),m,{...h,...j}),...y,children:_}),O&&(0,J.jsx)(w.Suspense,{fallback:null,children:(0,J.jsx)(jg,{anchorRef:k,content:(0,J.jsx)(`span`,{...Qp(rg.content),children:E.fullText}),placement:D})})]})}Fg.displayName=`Text`;var Ig=.375,Lg={sm:{diameter:10,border:2},md:{diameter:14,border:3},lg:{diameter:18,border:3},xl:{diameter:28,border:4}},Rg=[`--_spinner-ring-diameter`,`--_spinner-ring-stroke`],zg=`--_spinner-box-size`;function Bg(){if(!(typeof CSS>`u`||typeof CSS.registerProperty!=`function`))for(let e of Rg)try{CSS.registerProperty({name:e,syntax:``,inherits:!0,initialValue:`0px`})}catch{}}Bg();var Vg=new Set,Hg=!1;function Ug(){Hg=!1;let e=[];for(let t of Vg)e.push(...t.getAnimations());Vg.clear();for(let t of e)t.startTime=0}function Wg(e){if(e!=null&&typeof e.getAnimations==`function`)return Vg.add(e),Hg||(Hg=!0,requestAnimationFrame(Ug)),()=>{Vg.delete(e)}}var Gg={wrapper:{k1xSpc:`x3nfvp2`,kXwgrk:`xdt5ytf`,kGNEyG:`x6s0dn4`,kOIVth:`x1txdalj`,$$css:!0},spinner:{k1xSpc:`xwz0xwf`,kgQiWS:`x1ku5rj1`,kVQacm:`xb3r6kr`,kXLuUW:`xxymvpz`,"--_spinner-ring-diameter":`x2lq4xu`,"--_spinner-ring-stroke":`x10qssua`,"--_spinner-box-size":`x69vvuq`,$$css:!0},circle:{kDwRjp:`xbh8q5q`,kU5bRw:`x1owpc8m`,kPFa82:`xio8zfp`,kfJifR:`xgw3ha0`,$$css:!0},track:{kjVXCG:`xalkhop`,$$css:!0}},Kg={sm:{"--spinner-diameter":`x11wm0hx`,"--spinner-stroke-width":`xls98ul`,$$css:!0},md:{"--spinner-diameter":`x15pu9g6`,"--spinner-stroke-width":`xr0wkrm`,$$css:!0},lg:{"--spinner-diameter":`x1w424tr`,"--spinner-stroke-width":`xr0wkrm`,$$css:!0},xl:{"--spinner-diameter":`x1orj1z9`,"--spinner-stroke-width":`x7y2bof`,$$css:!0}},qg={default:{"--spinner-color":`xt1b8mc`,"--spinner-track-color":`xspt9s2`,$$css:!0},subtle:{"--spinner-color":`x1jevo6s`,"--spinner-track-color":`xspt9s2`,$$css:!0},onMedia:{"--spinner-color":`x13u6jys`,"--spinner-track-color":`x1ufpcf6`,$$css:!0},inherit:{"--spinner-color":`x1uzk0gl`,"--spinner-track-color":`xbfzqbu`,$$css:!0}},Jg={default:{kDd8S0:`x1g350g8`,$$css:!0},subtle:{kDd8S0:`x1g350g8`,$$css:!0},onMedia:{kDd8S0:`x1smxkh6`,$$css:!0},inherit:{kDd8S0:`x7bo2k`,$$css:!0}};function Yg({size:e=`md`,shade:t=`default`,label:n,xstyle:r,className:i,style:a,"aria-label":o,"data-testid":s,ref:c,...l}){let{border:u,diameter:d}=Lg[e],f=d+u*2,p=f/2,m=Math.PI*d,h=m*Ig,g=n!=null,_=(0,w.useId)(),v=g&&typeof n==`string`&&o==null,y=(0,J.jsx)(`span`,{ref:g?void 0:c,role:`status`,"aria-label":v?void 0:o??(typeof n==`string`?n:void 0)??`Loading`,"aria-labelledby":v?_:void 0,"data-testid":g?void 0:s,...g?{}:l,...fg(g?``:zh(`spinner`,{size:e,shade:t}),Qp(Gg.spinner,!g&&Kg[e],!g&&qg[t],!g&&r),g?void 0:i,{...g?{}:a,width:`var(${zg}, ${f}px)`,height:`var(${zg}, ${f}px)`}),children:(0,J.jsxs)(`svg`,{ref:Wg,width:f,height:f,viewBox:`0 0 ${f} ${f}`,"aria-hidden":`true`,className:`xlp1x4z x1lliihq x1so62im x1rea2x4 x14qxm4i xnh0sag xa4qsjk x1ka1v4i x1esw782`,children:[(0,J.jsx)(`circle`,{cx:p,cy:p,r:d/2,strokeWidth:u,...Qp(Gg.circle,Gg.track,Jg[t])}),(0,J.jsx)(`circle`,{cx:p,cy:p,r:d/2,strokeWidth:u,strokeDasharray:`${h} ${m-h}`,transform:`rotate(-90 ${p} ${p})`,className:`xbh8q5q x1owpc8m xio8zfp xgw3ha0 xtve3lm x1vy8frr`})]})});return g?(0,J.jsxs)(`div`,{ref:c,"data-testid":s,...l,...fg(zh(`spinner`,{size:e,shade:t}),Qp(Gg.wrapper,Kg[e],qg[t],r),i,a),children:[y,typeof n==`string`?(0,J.jsx)(Fg,{id:_,type:`body`,weight:`bold`,children:n}):n]}):y}Yg.displayName=`Spinner`;function Xg({children:e,as:t=`span`,ref:n,...r}){return(0,w.createElement)(t,{ref:n,...r,className:`x10l6tqk x1i1rx1s xjm9jq1 xkdpibf x1717udv xb3r6kr xzpqnlu xuxw1ft xng3xce x13vifvy x1o0tod x47corl x87ps6o`},e)}Xg.displayName=`VisuallyHidden`;var Zg=`data-astryx-edge-comp`,Qg={keTefX:`x1kftj4p`,k71WvV:`x92e2el`,$$css:!0},$g={inset:e=>[Qg,{"--x-126w13d":(e=>typeof e==`number`?e+`px`:e??void 0)(`calc(-1 * ${e})`),"--x-4drmnv":(e=>typeof e==`number`?e+`px`:e??void 0)(`calc(-1 * ${e})`)}]},e_=(0,w.createContext)(null);e_.displayName=`SizeContext`;function t_(e,t=`md`){let n=(0,w.use)(e_);return e??n??t}e_.Provider;var n_=(0,w.createContext)(null);n_.displayName=`ButtonGroupContext`;function r_(){return(0,w.use)(n_)}var i_=(0,w.createContext)(null);i_.displayName=`LinkContext`;function a_(e){function t({href:t,ref:n,...r}){return(0,w.createElement)(e,{ref:n,href:t,to:t,...r})}return t.displayName=`LinkWithTo(${typeof e==`string`?e:e.displayName||e.name||`Component`})`,t}function o_(e){let t=(0,w.use)(i_),n=e??t?.component??`a`;return(0,w.useMemo)(()=>n===`a`?`a`:a_(n),[n])}`${pm[`--color-overlay-hover`]}${pm[`--color-overlay-hover`]}`,`${pm[`--color-overlay-pressed`]}${pm[`--color-overlay-pressed`]}`,`${pm[`--color-neutral`]}${pm[`--color-neutral`]}`;var s_={backgroundColor:{kWkggS:`xjbqb8w x1anq1lc xoevpu5 xprvw0a`,$$css:!0},backgroundImage:{kKwaWg:`x7uyq82 xmvprkv xetgvay`,$$css:!0},backgroundImageOnNeutral:{kKwaWg:`x14bno8m xzmimnh x1otsd3y xo3fi6e`,$$css:!0}};function c_(e,t){let n=t&&t.cache?t.cache:v_,r=t&&t.serializer?t.serializer:g_;return(t&&t.strategy?t.strategy:p_)(e,{cache:n,serializer:r})}function l_(e){return e==null||typeof e==`number`||typeof e==`boolean`}function u_(e,t,n,r){let i=l_(r)?r:n(r),a=t.get(i);return a===void 0&&(a=e.call(this,r),t.set(i,a)),a}function d_(e,t,n){let r=Array.prototype.slice.call(arguments,3),i=n(r),a=t.get(i);return a===void 0&&(a=e.apply(this,r),t.set(i,a)),a}function f_(e,t,n,r,i){return n.bind(t,e,r,i)}function p_(e,t){let n=e.length===1?u_:d_;return f_(e,this,n,t.cache.create(),t.serializer)}function m_(e,t){return f_(e,this,d_,t.cache.create(),t.serializer)}function h_(e,t){return f_(e,this,u_,t.cache.create(),t.serializer)}var g_=function(){return JSON.stringify(arguments)},__=class{constructor(){this.cache=Object.create(null)}get(e){return this.cache[e]}set(e,t){this.cache[e]=t}},v_={create:function(){return new __}},y_={variadic:m_,monadic:h_},b_=/(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;function x_(e){let t={};return e.replace(b_,e=>{let n=e.length;switch(e[0]){case`G`:t.era=n===4?`long`:n===5?`narrow`:`short`;break;case`y`:t.year=n===2?`2-digit`:`numeric`;break;case`Y`:case`u`:case`U`:case`r`:throw RangeError("`Y/u/U/r` (year) patterns are not supported, use `y` instead");case`q`:case`Q`:throw RangeError("`q/Q` (quarter) patterns are not supported");case`M`:case`L`:t.month=[`numeric`,`2-digit`,`short`,`long`,`narrow`][n-1];break;case`w`:case`W`:throw RangeError("`w/W` (week) patterns are not supported");case`d`:t.day=[`numeric`,`2-digit`][n-1];break;case`D`:case`F`:case`g`:throw RangeError("`D/F/g` (day) patterns are not supported, use `d` instead");case`E`:t.weekday=n===4?`long`:n===5?`narrow`:`short`;break;case`e`:if(n<4)throw RangeError("`e..eee` (weekday) patterns are not supported");t.weekday=[`short`,`long`,`narrow`,`short`][n-3];break;case`c`:if(n<4)throw RangeError("`c..ccc` (weekday) patterns are not supported");t.weekday=[`short`,`long`,`narrow`,`short`][n-3];break;case`a`:t.hour12=!0;break;case`b`:case`B`:throw RangeError("`b/B` (period) patterns are not supported, use `a` instead");case`h`:t.hourCycle=`h12`,t.hour=[`numeric`,`2-digit`][n-1];break;case`H`:t.hourCycle=`h23`,t.hour=[`numeric`,`2-digit`][n-1];break;case`K`:t.hourCycle=`h11`,t.hour=[`numeric`,`2-digit`][n-1];break;case`k`:t.hourCycle=`h24`,t.hour=[`numeric`,`2-digit`][n-1];break;case`j`:case`J`:case`C`:throw RangeError("`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead");case`m`:t.minute=[`numeric`,`2-digit`][n-1];break;case`s`:t.second=[`numeric`,`2-digit`][n-1];break;case`S`:case`A`:throw RangeError("`S/A` (second) patterns are not supported, use `s` instead");case`z`:t.timeZoneName=n<4?`short`:`long`;break;case`Z`:case`O`:case`v`:case`V`:case`X`:case`x`:throw RangeError("`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead")}return``}),t}var S_=/[\t-\r \x85\u200E\u200F\u2028\u2029]/i;function C_(e){if(e.length===0)throw Error(`Number skeleton cannot be empty`);let t=e.split(S_).filter(e=>e.length>0),n=[];for(let e of t){let t=e.split(`/`);if(t.length===0)throw Error(`Invalid number skeleton`);let[r,...i]=t;for(let e of i)if(e.length===0)throw Error(`Invalid number skeleton`);n.push({stem:r,options:i})}return n}function w_(e){return e.replace(/^(.*?)-/,``)}var T_=/^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g,E_=/^(@+)?(\+|#+)?[rs]?$/g,D_=/(\*)(0+)|(#+)(0+)|(0+)/g,O_=/^(0+)$/;function k_(e){let t={};return e[e.length-1]===`r`?t.roundingPriority=`morePrecision`:e[e.length-1]===`s`&&(t.roundingPriority=`lessPrecision`),e.replace(E_,function(e,n,r){return typeof r==`string`?r===`+`?t.minimumSignificantDigits=n.length:n[0]===`#`?t.maximumSignificantDigits=n.length:(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length+(typeof r==`string`?r.length:0)):(t.minimumSignificantDigits=n.length,t.maximumSignificantDigits=n.length),``}),t}function A_(e){switch(e){case`sign-auto`:return{signDisplay:`auto`};case`sign-accounting`:case`()`:return{currencySign:`accounting`};case`sign-always`:case`+!`:return{signDisplay:`always`};case`sign-accounting-always`:case`()!`:return{signDisplay:`always`,currencySign:`accounting`};case`sign-except-zero`:case`+?`:return{signDisplay:`exceptZero`};case`sign-accounting-except-zero`:case`()?`:return{signDisplay:`exceptZero`,currencySign:`accounting`};case`sign-never`:case`+_`:return{signDisplay:`never`}}}function j_(e){let t;if(e[0]===`E`&&e[1]===`E`?(t={notation:`engineering`},e=e.slice(2)):e[0]===`E`&&(t={notation:`scientific`},e=e.slice(1)),t){let n=e.slice(0,2);if(n===`+!`?(t.signDisplay=`always`,e=e.slice(2)):n===`+?`&&(t.signDisplay=`exceptZero`,e=e.slice(2)),!O_.test(e))throw Error(`Malformed concise eng/scientific notation`);t.minimumIntegerDigits=e.length}return t}function M_(e){return A_(e)||{}}function N_(e){let t={};for(let n of e){switch(n.stem){case`percent`:case`%`:t.style=`percent`;continue;case`%x100`:t.style=`percent`,t.scale=100;continue;case`currency`:t.style=`currency`,t.currency=n.options[0];continue;case`group-off`:case`,_`:t.useGrouping=!1;continue;case`precision-integer`:case`.`:t.maximumFractionDigits=0;continue;case`measure-unit`:case`unit`:t.style=`unit`,t.unit=w_(n.options[0]);continue;case`compact-short`:case`K`:t.notation=`compact`,t.compactDisplay=`short`;continue;case`compact-long`:case`KK`:t.notation=`compact`,t.compactDisplay=`long`;continue;case`scientific`:t={...t,notation:`scientific`,...n.options.reduce((e,t)=>({...e,...M_(t)}),{})};continue;case`engineering`:t={...t,notation:`engineering`,...n.options.reduce((e,t)=>({...e,...M_(t)}),{})};continue;case`notation-simple`:t.notation=`standard`;continue;case`unit-width-narrow`:t.currencyDisplay=`narrowSymbol`,t.unitDisplay=`narrow`;continue;case`unit-width-short`:t.currencyDisplay=`code`,t.unitDisplay=`short`;continue;case`unit-width-full-name`:t.currencyDisplay=`name`,t.unitDisplay=`long`;continue;case`unit-width-iso-code`:t.currencyDisplay=`symbol`;continue;case`scale`:t.scale=parseFloat(n.options[0]);continue;case`rounding-mode-floor`:t.roundingMode=`floor`;continue;case`rounding-mode-ceiling`:t.roundingMode=`ceil`;continue;case`rounding-mode-down`:t.roundingMode=`trunc`;continue;case`rounding-mode-up`:t.roundingMode=`expand`;continue;case`rounding-mode-half-even`:t.roundingMode=`halfEven`;continue;case`rounding-mode-half-down`:t.roundingMode=`halfTrunc`;continue;case`rounding-mode-half-up`:t.roundingMode=`halfExpand`;continue;case`integer-width`:if(n.options.length>1)throw RangeError(`integer-width stems only accept a single optional option`);n.options[0].replace(D_,function(e,n,r,i,a,o){if(n)t.minimumIntegerDigits=r.length;else if(i&&a)throw Error(`We currently do not support maximum integer digits`);else if(o)throw Error(`We currently do not support exact integer digits`);return``});continue}if(O_.test(n.stem)){t.minimumIntegerDigits=n.stem.length;continue}if(T_.test(n.stem)){if(n.options.length>1)throw RangeError(`Fraction-precision stems only accept a single optional option`);n.stem.replace(T_,function(e,n,r,i,a,o){return r===`*`?t.minimumFractionDigits=n.length:i&&i[0]===`#`?t.maximumFractionDigits=i.length:a&&o?(t.minimumFractionDigits=a.length,t.maximumFractionDigits=a.length+o.length):(t.minimumFractionDigits=n.length,t.maximumFractionDigits=n.length),``});let e=n.options[0];e===`w`?t={...t,trailingZeroDisplay:`stripIfInteger`}:e&&(t={...t,...k_(e)});continue}if(E_.test(n.stem)){t={...t,...k_(n.stem)};continue}let e=A_(n.stem);e&&(t={...t,...e});let r=j_(n.stem);r&&(t={...t,...r})}return t}var P_=function(e){return e[e.EXPECT_ARGUMENT_CLOSING_BRACE=1]=`EXPECT_ARGUMENT_CLOSING_BRACE`,e[e.EMPTY_ARGUMENT=2]=`EMPTY_ARGUMENT`,e[e.MALFORMED_ARGUMENT=3]=`MALFORMED_ARGUMENT`,e[e.EXPECT_ARGUMENT_TYPE=4]=`EXPECT_ARGUMENT_TYPE`,e[e.INVALID_ARGUMENT_TYPE=5]=`INVALID_ARGUMENT_TYPE`,e[e.EXPECT_ARGUMENT_STYLE=6]=`EXPECT_ARGUMENT_STYLE`,e[e.INVALID_NUMBER_SKELETON=7]=`INVALID_NUMBER_SKELETON`,e[e.INVALID_DATE_TIME_SKELETON=8]=`INVALID_DATE_TIME_SKELETON`,e[e.EXPECT_NUMBER_SKELETON=9]=`EXPECT_NUMBER_SKELETON`,e[e.EXPECT_DATE_TIME_SKELETON=10]=`EXPECT_DATE_TIME_SKELETON`,e[e.UNCLOSED_QUOTE_IN_ARGUMENT_STYLE=11]=`UNCLOSED_QUOTE_IN_ARGUMENT_STYLE`,e[e.EXPECT_SELECT_ARGUMENT_OPTIONS=12]=`EXPECT_SELECT_ARGUMENT_OPTIONS`,e[e.EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE=13]=`EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE`,e[e.INVALID_PLURAL_ARGUMENT_OFFSET_VALUE=14]=`INVALID_PLURAL_ARGUMENT_OFFSET_VALUE`,e[e.EXPECT_SELECT_ARGUMENT_SELECTOR=15]=`EXPECT_SELECT_ARGUMENT_SELECTOR`,e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR=16]=`EXPECT_PLURAL_ARGUMENT_SELECTOR`,e[e.EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT=17]=`EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT`,e[e.EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT=18]=`EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT`,e[e.INVALID_PLURAL_ARGUMENT_SELECTOR=19]=`INVALID_PLURAL_ARGUMENT_SELECTOR`,e[e.DUPLICATE_PLURAL_ARGUMENT_SELECTOR=20]=`DUPLICATE_PLURAL_ARGUMENT_SELECTOR`,e[e.DUPLICATE_SELECT_ARGUMENT_SELECTOR=21]=`DUPLICATE_SELECT_ARGUMENT_SELECTOR`,e[e.MISSING_OTHER_CLAUSE=22]=`MISSING_OTHER_CLAUSE`,e[e.INVALID_TAG=23]=`INVALID_TAG`,e[e.INVALID_TAG_NAME=25]=`INVALID_TAG_NAME`,e[e.UNMATCHED_CLOSING_TAG=26]=`UNMATCHED_CLOSING_TAG`,e[e.UNCLOSED_TAG=27]=`UNCLOSED_TAG`,e}({});function F_(e){return e.type===0}function I_(e){return e.type===1}function L_(e){return e.type===2}function R_(e){return e.type===3}function z_(e){return e.type===4}function B_(e){return e.type===5}function V_(e){return e.type===6}function H_(e){return e.type===7}function U_(e){return e.type===8}function W_(e){return!!(e&&typeof e==`object`&&e.type===0)}function G_(e){return!!(e&&typeof e==`object`&&e.type===1)}var K_=/[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/,q_={"001":[`H`,`h`],419:[`h`,`H`,`hB`,`hb`],AC:[`H`,`h`,`hb`,`hB`],AD:[`H`,`hB`],AE:[`h`,`hB`,`hb`,`H`],AF:[`H`,`hb`,`hB`,`h`],AG:[`h`,`hb`,`H`,`hB`],AI:[`H`,`h`,`hb`,`hB`],AL:[`h`,`H`,`hB`],AM:[`H`,`hB`],AO:[`H`,`hB`],AR:[`h`,`H`,`hB`,`hb`],AS:[`h`,`H`],AT:[`H`,`hB`],AU:[`h`,`hb`,`H`,`hB`],AW:[`H`,`hB`],AX:[`H`],AZ:[`H`,`hB`,`h`],BA:[`H`,`hB`,`h`],BB:[`h`,`hb`,`H`,`hB`],BD:[`h`,`hB`,`H`],BE:[`H`,`hB`],BF:[`H`,`hB`],BG:[`H`,`hB`,`h`],BH:[`h`,`hB`,`hb`,`H`],BI:[`H`,`h`],BJ:[`H`,`hB`],BL:[`H`,`hB`],BM:[`h`,`hb`,`H`,`hB`],BN:[`hb`,`hB`,`h`,`H`],BO:[`h`,`H`,`hB`,`hb`],BQ:[`H`],BR:[`H`,`hB`],BS:[`h`,`hb`,`H`,`hB`],BT:[`h`,`H`],BW:[`H`,`h`,`hb`,`hB`],BY:[`H`,`h`],BZ:[`H`,`h`,`hb`,`hB`],CA:[`h`,`hb`,`H`,`hB`],CC:[`H`,`h`,`hb`,`hB`],CD:[`hB`,`H`],CF:[`H`,`h`,`hB`],CG:[`H`,`hB`],CH:[`H`,`hB`,`h`],CI:[`H`,`hB`],CK:[`H`,`h`,`hb`,`hB`],CL:[`h`,`H`,`hB`,`hb`],CM:[`H`,`h`,`hB`],CN:[`H`,`hB`,`hb`,`h`],CO:[`h`,`H`,`hB`,`hb`],CP:[`H`],CR:[`h`,`H`,`hB`,`hb`],CU:[`h`,`H`,`hB`,`hb`],CV:[`H`,`hB`],CW:[`H`,`hB`],CX:[`H`,`h`,`hb`,`hB`],CY:[`h`,`H`,`hb`,`hB`],CZ:[`H`],DE:[`H`,`hB`],DG:[`H`,`h`,`hb`,`hB`],DJ:[`h`,`H`],DK:[`H`],DM:[`h`,`hb`,`H`,`hB`],DO:[`h`,`H`,`hB`,`hb`],DZ:[`h`,`hB`,`hb`,`H`],EA:[`H`,`h`,`hB`,`hb`],EC:[`h`,`H`,`hB`,`hb`],EE:[`H`,`hB`],EG:[`h`,`hB`,`hb`,`H`],EH:[`h`,`hB`,`hb`,`H`],ER:[`h`,`H`],ES:[`H`,`hB`,`h`,`hb`],ET:[`hB`,`hb`,`h`,`H`],FI:[`H`],FJ:[`h`,`hb`,`H`,`hB`],FK:[`H`,`h`,`hb`,`hB`],FM:[`h`,`hb`,`H`,`hB`],FO:[`H`,`h`],FR:[`H`,`hB`],GA:[`H`,`hB`],GB:[`H`,`h`,`hb`,`hB`],GD:[`h`,`hb`,`H`,`hB`],GE:[`H`,`hB`,`h`],GF:[`H`,`hB`],GG:[`H`,`h`,`hb`,`hB`],GH:[`h`,`H`],GI:[`H`,`h`,`hb`,`hB`],GL:[`H`,`h`],GM:[`h`,`hb`,`H`,`hB`],GN:[`H`,`hB`],GP:[`H`,`hB`],GQ:[`H`,`hB`,`h`,`hb`],GR:[`h`,`H`,`hb`,`hB`],GS:[`H`,`h`,`hb`,`hB`],GT:[`h`,`H`,`hB`,`hb`],GU:[`h`,`hb`,`H`,`hB`],GW:[`H`,`hB`],GY:[`h`,`hb`,`H`,`hB`],HK:[`h`,`hB`,`hb`,`H`],HN:[`h`,`H`,`hB`,`hb`],HR:[`H`,`hB`],HU:[`H`,`h`],IC:[`H`,`h`,`hB`,`hb`],ID:[`H`],IE:[`H`,`h`,`hb`,`hB`],IL:[`H`,`hB`],IM:[`H`,`h`,`hb`,`hB`],IN:[`h`,`H`],IO:[`H`,`h`,`hb`,`hB`],IQ:[`h`,`hB`,`hb`,`H`],IR:[`hB`,`H`],IS:[`H`],IT:[`H`,`hB`],JE:[`H`,`h`,`hb`,`hB`],JM:[`h`,`hb`,`H`,`hB`],JO:[`h`,`hB`,`hb`,`H`],JP:[`H`,`K`,`h`],KE:[`hB`,`hb`,`H`,`h`],KG:[`H`,`h`,`hB`,`hb`],KH:[`hB`,`h`,`H`,`hb`],KI:[`h`,`hb`,`H`,`hB`],KM:[`H`,`h`,`hB`,`hb`],KN:[`h`,`hb`,`H`,`hB`],KP:[`h`,`H`,`hB`,`hb`],KR:[`h`,`H`,`hB`,`hb`],KW:[`h`,`hB`,`hb`,`H`],KY:[`h`,`hb`,`H`,`hB`],KZ:[`H`,`hB`],LA:[`H`,`hb`,`hB`,`h`],LB:[`h`,`hB`,`hb`,`H`],LC:[`h`,`hb`,`H`,`hB`],LI:[`H`,`hB`,`h`],LK:[`H`,`h`,`hB`,`hb`],LR:[`h`,`hb`,`H`,`hB`],LS:[`h`,`H`],LT:[`H`,`h`,`hb`,`hB`],LU:[`H`,`h`,`hB`],LV:[`H`,`hB`,`hb`,`h`],LY:[`h`,`hB`,`hb`,`H`],MA:[`H`,`h`,`hB`,`hb`],MC:[`H`,`hB`],MD:[`H`,`hB`],ME:[`H`,`hB`,`h`],MF:[`H`,`hB`],MG:[`H`,`h`],MH:[`h`,`hb`,`H`,`hB`],MK:[`H`,`h`,`hb`,`hB`],ML:[`H`],MM:[`hB`,`hb`,`H`,`h`],MN:[`H`,`h`,`hb`,`hB`],MO:[`h`,`hB`,`hb`,`H`],MP:[`h`,`hb`,`H`,`hB`],MQ:[`H`,`hB`],MR:[`h`,`hB`,`hb`,`H`],MS:[`H`,`h`,`hb`,`hB`],MT:[`H`,`h`],MU:[`H`,`h`],MV:[`H`,`h`],MW:[`h`,`hb`,`H`,`hB`],MX:[`h`,`H`,`hB`,`hb`],MY:[`hb`,`hB`,`h`,`H`],MZ:[`H`,`hB`],NA:[`h`,`H`,`hB`,`hb`],NC:[`H`,`hB`],NE:[`H`],NF:[`H`,`h`,`hb`,`hB`],NG:[`H`,`h`,`hb`,`hB`],NI:[`h`,`H`,`hB`,`hb`],NL:[`H`,`hB`],NO:[`H`,`h`],NP:[`H`,`h`,`hB`],NR:[`H`,`h`,`hb`,`hB`],NU:[`H`,`h`,`hb`,`hB`],NZ:[`h`,`hb`,`H`,`hB`],OM:[`h`,`hB`,`hb`,`H`],PA:[`h`,`H`,`hB`,`hb`],PE:[`h`,`H`,`hB`,`hb`],PF:[`H`,`h`,`hB`],PG:[`h`,`H`],PH:[`h`,`hB`,`hb`,`H`],PK:[`h`,`hB`,`H`],PL:[`H`,`h`],PM:[`H`,`hB`],PN:[`H`,`h`,`hb`,`hB`],PR:[`h`,`H`,`hB`,`hb`],PS:[`h`,`hB`,`hb`,`H`],PT:[`H`,`hB`],PW:[`h`,`H`],PY:[`h`,`H`,`hB`,`hb`],QA:[`h`,`hB`,`hb`,`H`],RE:[`H`,`hB`],RO:[`H`,`hB`],RS:[`H`,`hB`,`h`],RU:[`H`],RW:[`H`,`h`],SA:[`h`,`hB`,`hb`,`H`],SB:[`h`,`hb`,`H`,`hB`],SC:[`H`,`h`,`hB`],SD:[`h`,`hB`,`hb`,`H`],SE:[`H`],SG:[`h`,`hb`,`H`,`hB`],SH:[`H`,`h`,`hb`,`hB`],SI:[`H`,`hB`],SJ:[`H`],SK:[`H`],SL:[`h`,`hb`,`H`,`hB`],SM:[`H`,`h`,`hB`],SN:[`H`,`h`,`hB`],SO:[`h`,`H`],SR:[`H`,`hB`],SS:[`h`,`hb`,`H`,`hB`],ST:[`H`,`hB`],SV:[`h`,`H`,`hB`,`hb`],SX:[`H`,`h`,`hb`,`hB`],SY:[`h`,`hB`,`hb`,`H`],SZ:[`h`,`hb`,`H`,`hB`],TA:[`H`,`h`,`hb`,`hB`],TC:[`h`,`hb`,`H`,`hB`],TD:[`h`,`H`,`hB`],TF:[`H`,`h`,`hB`],TG:[`H`,`hB`],TH:[`H`,`h`],TJ:[`H`,`h`],TL:[`H`,`hB`,`hb`,`h`],TM:[`H`,`h`],TN:[`h`,`hB`,`hb`,`H`],TO:[`h`,`H`],TR:[`H`,`hB`],TT:[`h`,`hb`,`H`,`hB`],TW:[`hB`,`hb`,`h`,`H`],TZ:[`hB`,`hb`,`H`,`h`],UA:[`H`,`hB`,`h`],UG:[`hB`,`hb`,`H`,`h`],UM:[`h`,`hb`,`H`,`hB`],US:[`h`,`hb`,`H`,`hB`],UY:[`h`,`H`,`hB`,`hb`],UZ:[`H`,`hB`,`h`],VA:[`H`,`h`,`hB`],VC:[`h`,`hb`,`H`,`hB`],VE:[`h`,`H`,`hB`,`hb`],VG:[`h`,`hb`,`H`,`hB`],VI:[`h`,`hb`,`H`,`hB`],VN:[`H`,`h`],VU:[`h`,`H`],WF:[`H`,`hB`],WS:[`h`,`H`],XK:[`H`,`hB`,`h`],YE:[`h`,`hB`,`hb`,`H`],YT:[`H`,`hB`],ZA:[`H`,`h`,`hb`,`hB`],ZM:[`h`,`hb`,`H`,`hB`],ZW:[`H`,`h`],"af-ZA":[`H`,`h`,`hB`,`hb`],"ar-001":[`h`,`hB`,`hb`,`H`],"ca-ES":[`H`,`h`,`hB`],"en-001":[`h`,`hb`,`H`,`hB`],"en-HK":[`h`,`hb`,`H`,`hB`],"en-IL":[`H`,`h`,`hb`,`hB`],"en-MY":[`h`,`hb`,`H`,`hB`],"es-BR":[`H`,`h`,`hB`,`hb`],"es-ES":[`H`,`h`,`hB`,`hb`],"es-GQ":[`H`,`h`,`hB`,`hb`],"fr-CA":[`H`,`h`,`hB`],"gl-ES":[`H`,`h`,`hB`],"gu-IN":[`hB`,`hb`,`h`,`H`],"hi-IN":[`hB`,`h`,`H`],"it-CH":[`H`,`h`,`hB`],"it-IT":[`H`,`h`,`hB`],"kn-IN":[`hB`,`h`,`H`],"ku-SY":[`H`,`hB`],"ml-IN":[`hB`,`h`,`H`],"mr-IN":[`hB`,`hb`,`h`,`H`],"pa-IN":[`hB`,`hb`,`h`,`H`],"ta-IN":[`hB`,`h`,`hb`,`H`],"te-IN":[`hB`,`h`,`H`],"zu-ZA":[`H`,`hB`,`hb`,`h`]};function J_(e,t){let n=``;for(let r=0;r>1),c=Y_(t);for((c==`H`||c==`k`)&&(s=0);s-->0;)n+=`a`;for(;o-->0;)n=c+n}else n+=i===`J`?`H`:i}return n}function Y_(e){let t=e.hourCycle;if(t===void 0&&e.hourCycles&&e.hourCycles.length&&(t=e.hourCycles[0]),t)switch(t){case`h24`:return`k`;case`h23`:return`H`;case`h12`:return`h`;case`h11`:return`K`;default:throw Error(`Invalid hourCycle`)}let n=e.language,r;return n!==`root`&&(r=e.maximize().region),(q_[r||``]||q_[n||``]||q_[`${n}-001`]||q_[`001`])[0]}var X_=RegExp(`^${K_.source}*`),Z_=RegExp(`${K_.source}*$`);function Q_(e,t){return{start:e,end:t}}var $_=!!Object.fromEntries,ev=!!String.prototype.trimStart,tv=!!String.prototype.trimEnd,nv=$_?Object.fromEntries:function(e){let t={};for(let[n,r]of e)t[n]=r;return t},rv=ev?function(e){return e.trimStart()}:function(e){return e.replace(X_,``)},iv=tv?function(e){return e.trimEnd()}:function(e){return e.replace(Z_,``)},av=RegExp(`([^\\p{White_Space}\\p{Pattern_Syntax}]*)`,`yu`);function ov(e,t){return av.lastIndex=t,av.exec(e)[1]??``}function sv(e){if(e.length===0)return null;let t=1,n=1;for(let r=0;r=55296&&i<=56319&&r+1=56320&&t<=57343?2:1}else r++}return{offset:e.length,line:t,column:n}}var cv=class{constructor(e,t={}){this.message=e,this.position={offset:0,line:1,column:1},this.ignoreTag=!!t.ignoreTag,this.locale=t.locale,this.requiresOtherClause=!!t.requiresOtherClause,this.shouldParseSkeletons=!!t.shouldParseSkeletons}parse(){if(this.offset()!==0)throw Error(`parser can only be used once`);if(this.message.length>0){let e=this.message.charCodeAt(0);if(e!==35&&e!==39&&e!==60&&e!==123&&e!==125){let e=sv(this.message);if(e){let t=this.clonePosition();return this.position=e,{val:[{type:0,value:this.message,location:Q_(t,this.clonePosition())}],err:null}}}}return this.parseMessage(0,``,!1)}parseMessage(e,t,n){let r=[];for(;!this.isEOF();){let i=this.char();if(i===123){let t=this.parseArgument(e,n);if(t.err)return t;r.push(t.val)}else if(i===125&&e>0)break;else if(i===35&&(t===`plural`||t===`selectordinal`)){let e=this.clonePosition();this.bump(),r.push({type:7,location:Q_(e,this.clonePosition())})}else if(i===60&&!this.ignoreTag&&this.peek()===47){if(n)break;return this.error(26,Q_(this.clonePosition(),this.clonePosition()))}else if(i===60&&!this.ignoreTag&&lv(this.peek()||0)){let n=this.parseTag(e,t);if(n.err)return n;r.push(n.val)}else{let n=this.parseLiteral(e,t);if(n.err)return n;r.push(n.val)}}return{val:r,err:null}}parseTag(e,t){let n=this.clonePosition();this.bump();let r=this.parseTagName();if(this.bumpSpace(),this.bumpIf(`/>`))return{val:{type:0,value:`<${r}/>`,location:Q_(n,this.clonePosition())},err:null};if(this.bumpIf(`>`)){let i=this.parseMessage(e+1,t,!0);if(i.err)return i;let a=i.val,o=this.clonePosition();if(this.bumpIf(``)?{val:{type:8,value:r,children:a,location:Q_(n,this.clonePosition())},err:null}:this.error(23,Q_(o,this.clonePosition()))):this.error(26,Q_(e,this.clonePosition()))}return this.error(27,Q_(n,this.clonePosition()))}return this.error(23,Q_(n,this.clonePosition()))}parseTagName(){let e=this.offset();for(this.bump();!this.isEOF()&&dv(this.char());)this.bump();return this.message.slice(e,this.offset())}parseLiteral(e,t){let n=this.clonePosition(),r=``;for(;;){let n=this.tryParseQuote(t);if(n){r+=n;continue}let i=this.tryParseUnquoted(e,t);if(i){r+=i;continue}let a=this.tryParseLeftAngleBracket();if(a){r+=a;continue}break}let i=Q_(n,this.clonePosition());return{val:{type:0,value:r,location:i},err:null}}tryParseLeftAngleBracket(){return!this.isEOF()&&this.char()===60&&(this.ignoreTag||!uv(this.peek()||0))?(this.bump(),`<`):null}tryParseQuote(e){if(this.isEOF()||this.char()!==39)return null;switch(this.peek()){case 39:return this.bump(),this.bump(),`'`;case 123:case 60:case 62:case 125:break;case 35:if(e===`plural`||e===`selectordinal`)break;return null;default:return null}this.bump();let t=[this.char()];for(this.bump();!this.isEOF();){let e=this.char();if(e===39)if(this.peek()===39)t.push(39),this.bump();else{this.bump();break}else t.push(e);this.bump()}return String.fromCodePoint(...t)}tryParseUnquoted(e,t){if(this.isEOF())return null;let n=this.char();return n===60||n===123||n===35&&(t===`plural`||t===`selectordinal`)||n===125&&e>0?null:(this.bump(),String.fromCodePoint(n))}parseArgument(e,t){let n=this.clonePosition();if(this.bump(),this.bumpSpace(),this.isEOF())return this.error(1,Q_(n,this.clonePosition()));if(this.char()===125)return this.bump(),this.error(2,Q_(n,this.clonePosition()));let r=this.parseIdentifierIfPossible().value;if(!r)return this.error(3,Q_(n,this.clonePosition()));if(this.bumpSpace(),this.isEOF())return this.error(1,Q_(n,this.clonePosition()));switch(this.char()){case 125:return this.bump(),{val:{type:1,value:r,location:Q_(n,this.clonePosition())},err:null};case 44:return this.bump(),this.bumpSpace(),this.isEOF()?this.error(1,Q_(n,this.clonePosition())):this.parseArgumentOptions(e,t,r,n);default:return this.error(3,Q_(n,this.clonePosition()))}}parseIdentifierIfPossible(){let e=this.clonePosition(),t=this.offset(),n=ov(this.message,t),r=t+n.length;return this.bumpTo(r),{value:n,location:Q_(e,this.clonePosition())}}parseArgumentOptions(e,t,n,r){let i=this.clonePosition(),a=this.parseIdentifierIfPossible().value,o=this.clonePosition();switch(a){case``:return this.error(4,Q_(i,o));case`number`:case`date`:case`time`:{this.bumpSpace();let e=null;if(this.bumpIf(`,`)){this.bumpSpace();let t=this.clonePosition(),n=this.parseSimpleArgStyleIfPossible();if(n.err)return n;let r=iv(n.val);if(r.length===0)return this.error(6,Q_(this.clonePosition(),this.clonePosition()));e={style:r,styleLocation:Q_(t,this.clonePosition())}}let t=this.tryParseArgumentClose(r);if(t.err)return t;let i=Q_(r,this.clonePosition());if(e&&e.style.startsWith(`::`)){let t=rv(e.style.slice(2));if(a===`number`){let r=this.parseNumberSkeletonFromString(t,e.styleLocation);return r.err?r:{val:{type:2,value:n,location:i,style:r.val},err:null}}{if(t.length===0)return this.error(10,i);let r=t;this.locale&&(r=J_(t,this.locale));let o={type:1,pattern:r,location:e.styleLocation,parsedOptions:this.shouldParseSkeletons?x_(r):{}};return{val:{type:a===`date`?3:4,value:n,location:i,style:o},err:null}}}return{val:{type:a===`number`?2:a===`date`?3:4,value:n,location:i,style:e?.style??null},err:null}}case`plural`:case`selectordinal`:case`select`:{let i=this.clonePosition();if(this.bumpSpace(),!this.bumpIf(`,`))return this.error(12,Q_(i,{...i}));this.bumpSpace();let o=this.parseIdentifierIfPossible(),s=0;if(a!==`select`&&o.value===`offset`){if(!this.bumpIf(`:`))return this.error(13,Q_(this.clonePosition(),this.clonePosition()));this.bumpSpace();let e=this.tryParseDecimalInteger(13,14);if(e.err)return e;this.bumpSpace(),o=this.parseIdentifierIfPossible(),s=e.val}let c=this.tryParsePluralOrSelectOptions(e,a,t,o);if(c.err)return c;let l=this.tryParseArgumentClose(r);if(l.err)return l;let u=Q_(r,this.clonePosition());return a===`select`?{val:{type:5,value:n,options:nv(c.val),location:u},err:null}:{val:{type:6,value:n,options:nv(c.val),offset:s,pluralType:a===`plural`?`cardinal`:`ordinal`,location:u},err:null}}default:return this.error(5,Q_(i,o))}}tryParseArgumentClose(e){return this.isEOF()||this.char()!==125?this.error(1,Q_(e,this.clonePosition())):(this.bump(),{val:!0,err:null})}parseSimpleArgStyleIfPossible(){let e=0,t=this.clonePosition();for(;!this.isEOF();)switch(this.char()){case 39:{this.bump();let e=this.clonePosition();if(!this.bumpUntil(`'`))return this.error(11,Q_(e,this.clonePosition()));this.bump();break}case 123:e+=1,this.bump();break;case 125:if(e>0)--e;else return{val:this.message.slice(t.offset,this.offset()),err:null};break;default:this.bump()}return{val:this.message.slice(t.offset,this.offset()),err:null}}parseNumberSkeletonFromString(e,t){let n=[];try{n=C_(e)}catch{return this.error(7,t)}return{val:{type:0,tokens:n,location:t,parsedOptions:this.shouldParseSkeletons?N_(n):{}},err:null}}tryParsePluralOrSelectOptions(e,t,n,r){let i=!1,a=[],o=new Set,{value:s,location:c}=r;for(;;){if(s.length===0){let e=this.clonePosition();if(t!==`select`&&this.bumpIf(`=`)){let t=this.tryParseDecimalInteger(16,19);if(t.err)return t;c=Q_(e,this.clonePosition()),s=this.message.slice(e.offset,this.offset())}else break}if(o.has(s))return this.error(t===`select`?21:20,c);s===`other`&&(i=!0),this.bumpSpace();let r=this.clonePosition();if(!this.bumpIf(`{`))return this.error(t===`select`?17:18,Q_(this.clonePosition(),this.clonePosition()));let l=this.parseMessage(e+1,t,n);if(l.err)return l;let u=this.tryParseArgumentClose(r);if(u.err)return u;a.push([s,{value:l.val,location:Q_(r,this.clonePosition())}]),o.add(s),this.bumpSpace(),{value:s,location:c}=this.parseIdentifierIfPossible()}return a.length===0?this.error(t===`select`?15:16,Q_(this.clonePosition(),this.clonePosition())):this.requiresOtherClause&&!i?this.error(22,Q_(this.clonePosition(),this.clonePosition())):{val:a,err:null}}tryParseDecimalInteger(e,t){let n=1,r=this.clonePosition();this.bumpIf(`+`)||this.bumpIf(`-`)&&(n=-1);let i=!1,a=0;for(;!this.isEOF();){let e=this.char();if(e>=48&&e<=57)i=!0,a=a*10+(e-48),this.bump();else break}let o=Q_(r,this.clonePosition());return i?(a*=n,Number.isSafeInteger(a)?{val:a,err:null}:this.error(t,o)):this.error(e,o)}offset(){return this.position.offset}isEOF(){return this.offset()===this.message.length}clonePosition(){return{offset:this.position.offset,line:this.position.line,column:this.position.column}}char(){let e=this.position.offset;if(e>=this.message.length)throw Error(`out of bound`);let t=this.message.codePointAt(e);if(t===void 0)throw Error(`Offset ${e} is at invalid UTF-16 code unit boundary`);return t}error(e,t){return{val:null,err:{kind:e,message:this.message,location:t}}}bump(){if(this.isEOF())return;let e=this.char();e===10?(this.position.line+=1,this.position.column=1,this.position.offset+=1):(this.position.column+=1,this.position.offset+=e<65536?1:2)}bumpIf(e){if(this.message.startsWith(e,this.offset())){for(let t=0;t=0?(this.bumpTo(n),!0):(this.bumpTo(this.message.length),!1)}bumpTo(e){if(this.offset()>e)throw Error(`targetOffset ${e} must be greater than or equal to the current offset ${this.offset()}`);for(e=Math.min(e,this.message.length);;){let t=this.offset();if(t===e)break;if(t>e)throw Error(`targetOffset ${e} is at invalid UTF-16 code unit boundary`);if(this.bump(),this.isEOF())break}}bumpSpace(){for(;!this.isEOF()&&fv(this.char());)this.bump()}peek(){if(this.isEOF())return null;let e=this.char(),t=this.offset();return this.message.charCodeAt(t+(e>=65536?2:1))??null}};function lv(e){return e>=97&&e<=122||e>=65&&e<=90}function uv(e){return lv(e)||e===47}function dv(e){return e===45||e===46||e>=48&&e<=57||e===95||e>=97&&e<=122||e>=65&&e<=90||e==183||e>=192&&e<=214||e>=216&&e<=246||e>=248&&e<=893||e>=895&&e<=8191||e>=8204&&e<=8205||e>=8255&&e<=8256||e>=8304&&e<=8591||e>=11264&&e<=12271||e>=12289&&e<=55295||e>=63744&&e<=64975||e>=65008&&e<=65533||e>=65536&&e<=983039}function fv(e){return e>=9&&e<=13||e===32||e===133||e>=8206&&e<=8207||e===8232||e===8233}function pv(e){e.forEach(e=>{if(delete e.location,B_(e)||V_(e))for(let t in e.options)delete e.options[t].location,pv(e.options[t].value);else L_(e)&&W_(e.style)||(R_(e)||z_(e))&&G_(e.style)?delete e.style.location:U_(e)&&pv(e.children)})}function mv(e,t={}){t={shouldParseSkeletons:!0,requiresOtherClause:!0,...t};let n=new cv(e,t).parse();if(n.err){let e=SyntaxError(P_[n.err.kind]);throw e.location=n.err.location,e.originalMessage=n.err.message,e}return t?.captureLocation||pv(n.val),n.val}var hv=class extends Error{constructor(e,t,n){super(e),this.code=t,this.originalMessage=n}toString(){return`[formatjs Error: ${this.code}] ${this.message}`}},gv=class extends hv{constructor(e,t,n,r){super(`Invalid values for "${e}": "${t}". Options are "${Object.keys(n).join(`", "`)}"`,`INVALID_VALUE`,r)}},_v=class extends hv{constructor(e,t,n){super(`Value for "${e}" must be of type ${t}`,`INVALID_VALUE`,n)}},vv=class extends hv{constructor(e,t){super(`The intl string context variable "${e}" was not provided to the string "${t}"`,`MISSING_VALUE`,t)}};function yv(e){return e.length<2?e:e.reduce((e,t)=>{let n=e[e.length-1];return!n||n.type!==0||t.type!==0?e.push(t):n.value+=t.value,e},[])}function bv(e){return typeof e==`function`}function xv(e,t,n,r,i,a,o){if(e.length===1&&F_(e[0]))return[{type:0,value:e[0].value}];let s=[];for(let c of e){if(F_(c)){s.push({type:0,value:c.value});continue}if(H_(c)){typeof a==`number`&&s.push({type:0,value:n.getNumberFormat(t).format(a)});continue}let{value:e}=c;if(!(i&&e in i))throw new vv(e,o);let l=i[e];if(I_(c)){(!l||typeof l==`string`||typeof l==`number`||typeof l==`bigint`)&&(l=typeof l==`string`||typeof l==`number`||typeof l==`bigint`?String(l):``),s.push({type:typeof l==`string`?0:1,value:l});continue}if(R_(c)){let e=typeof c.style==`string`?r.date[c.style]:G_(c.style)?c.style.parsedOptions:void 0;s.push({type:0,value:n.getDateTimeFormat(t,e).format(l)});continue}if(z_(c)){let e=typeof c.style==`string`?r.time[c.style]:G_(c.style)?c.style.parsedOptions:r.time.medium;s.push({type:0,value:n.getDateTimeFormat(t,e).format(l)});continue}if(L_(c)){let e=typeof c.style==`string`?r.number[c.style]:W_(c.style)?c.style.parsedOptions:void 0;if(e&&e.scale){let t=e.scale||1;if(typeof l==`bigint`){if(!Number.isInteger(t))throw TypeError(`Cannot apply fractional scale ${t} to bigint value. Scale must be an integer when formatting bigint.`);l*=BigInt(t)}else l*=t}s.push({type:0,value:n.getNumberFormat(t,e).format(l)});continue}if(U_(c)){let{children:e,value:l}=c,u=i[l];if(!bv(u))throw new _v(l,`function`,o);let d=u(xv(e,t,n,r,i,a).map(e=>e.value));Array.isArray(d)||(d=[d]),s.push(...d.map(e=>({type:typeof e==`string`?0:1,value:e})))}if(B_(c)){let e=l,a=(Object.prototype.hasOwnProperty.call(c.options,e)?c.options[e]:void 0)||c.options.other;if(!a)throw new gv(c.value,l,Object.keys(c.options),o);s.push(...xv(a.value,t,n,r,i));continue}if(V_(c)){let e=`=${l}`,a=Object.prototype.hasOwnProperty.call(c.options,e)?c.options[e]:void 0;if(!a){if(!Intl.PluralRules)throw new hv(`Intl.PluralRules is not available in this environment. Try polyfilling it using "@formatjs/intl-pluralrules" -`,`MISSING_INTL_API`,o);let e=typeof l==`bigint`?Number(l):l,r=n.getPluralRules(t,{type:c.pluralType}).select(e-(c.offset||0));a=(Object.prototype.hasOwnProperty.call(c.options,r)?c.options[r]:void 0)||c.options.other}if(!a)throw new sv(c.value,l,Object.keys(c.options),o);let u=typeof l==`bigint`?Number(l):l;s.push(...fv(a.value,t,n,r,i,u-(c.offset||0)));continue}}return uv(s)}function pv(e,t){return t?{...e,...t,...Object.keys(e).reduce((n,r)=>(n[r]={...e[r],...t[r]},n),{})}:e}function mv(e,t){return t?Object.keys(e).reduce((n,r)=>(n[r]=pv(e[r],t[r]),n),{...e}):e}function hv(e){return{create(){return{get(t){return e[t]},set(t,n){e[t]=n}}}}}function gv(e={number:{},dateTime:{},pluralRules:{}}){return{getNumberFormat:$g((...e)=>new Intl.NumberFormat(...e),{cache:hv(e.number),strategy:u_.variadic}),getDateTimeFormat:$g((...e)=>new Intl.DateTimeFormat(...e),{cache:hv(e.dateTime),strategy:u_.variadic}),getPluralRules:$g((...e)=>new Intl.PluralRules(...e),{cache:hv(e.pluralRules),strategy:u_.variadic})}}var _v=class e{constructor(t,n=e.defaultLocale,r,i){if(this.formatterCache={number:{},dateTime:{},pluralRules:{}},this.format=e=>{let t=this.formatToParts(e);if(t.length===1)return t[0].value;let n=t.reduce((e,t)=>(!e.length||t.type!==0||typeof e[e.length-1]!=`string`?e.push(t.value):e[e.length-1]+=t.value,e),[]);return n.length<=1?n[0]||``:n},this.formatToParts=e=>fv(this.ast,this.locales,this.formatters,this.formats,e,void 0,this.message),this.resolvedOptions=()=>({locale:this.resolvedLocale?.toString()||Intl.NumberFormat.supportedLocalesOf(this.locales)[0]}),this.getAst=()=>this.ast,this.locales=n,this.resolvedLocale=e.resolveLocale(n),typeof t==`string`){if(this.message=t,!e.__parse)throw TypeError("IntlMessageFormat.__parse must be set to process `message` of type `string`");let{...n}=i||{};this.ast=e.__parse(t,{...n,locale:this.resolvedLocale})}else this.ast=t;if(!Array.isArray(this.ast))throw TypeError(`A message must be provided as a String or AST.`);this.formats=mv(e.formats,r),this.formatters=i&&i.formatters||gv(this.formatterCache)}static{this.memoizedDefaultLocale=null}static get defaultLocale(){return e.memoizedDefaultLocale||=new Intl.NumberFormat().resolvedOptions().locale,e.memoizedDefaultLocale}static{this.resolveLocale=e=>{if(Intl.Locale===void 0)return;let t=Intl.NumberFormat.supportedLocalesOf(e);return t.length>0?new Intl.Locale(t[0]):new Intl.Locale(typeof e==`string`?e:e[0])}}static{this.__parse=av}static{this.formats={number:{integer:{maximumFractionDigits:0},currency:{style:`currency`},percent:{style:`percent`}},date:{short:{month:`numeric`,day:`numeric`,year:`2-digit`},medium:{month:`short`,day:`numeric`,year:`numeric`},long:{month:`long`,day:`numeric`,year:`numeric`},full:{weekday:`long`,month:`long`,day:`numeric`,year:`numeric`}},time:{short:{hour:`numeric`,minute:`numeric`},medium:{hour:`numeric`,minute:`numeric`,second:`numeric`},long:{hour:`numeric`,minute:`numeric`,second:`numeric`,timeZoneName:`short`},full:{hour:`numeric`,minute:`numeric`,second:`numeric`,timeZoneName:`short`}}}}},vv={"@astryx.pagination.label":{defaultMessage:`Pagination`,description:`Aria label for the pagination navigation region.`},"@astryx.pagination.previous":{defaultMessage:`Go to previous page`,description:`Aria label for the previous-page button.`},"@astryx.pagination.next":{defaultMessage:`Go to next page`,description:`Aria label for the next-page button.`},"@astryx.pagination.previousBy":{defaultMessage:`Go back {step, number} {step, plural, one {page} other {pages}}`,description:"Aria label for the previous button when it advances more than one page per click (the `step` prop > 1). `step` is the number of pages skipped."},"@astryx.pagination.nextBy":{defaultMessage:`Go forward {step, number} {step, plural, one {page} other {pages}}`,description:"Aria label for the next button when it advances more than one page per click (the `step` prop > 1). `step` is the number of pages skipped."},"@astryx.pagination.first":{defaultMessage:`Go to first page`,description:`Aria label for the first-page button (« double chevron) in the input pagination variant.`},"@astryx.pagination.last":{defaultMessage:`Go to last page`,description:`Aria label for the last-page button (» double chevron) in the input pagination variant.`},"@astryx.pagination.goToPage":{defaultMessage:`Go to page {page, number}`,description:"Aria label for an individual page-number button. `page` is 1-based."},"@astryx.pagination.goToPageInput":{defaultMessage:`Go to page`,description:`Aria label for the editable page/row number box in the input pagination variant. No number — the box holds the value itself.`},"@astryx.pagination.pageLabel":{defaultMessage:`Page`,description:`Visible label before the editable box in the input pagination variant. Example: "Page [ 1 ] / 10".`},"@astryx.pagination.ofTotalPages":{defaultMessage:`/ {total, number}`,description:`Visible total shown after the editable box in the input pagination variant. Example: the "/ 10" in "Page [ 1 ] / 10".`},"@astryx.pagination.pageIndicators":{defaultMessage:`Page indicators`,description:`Aria label for the dots-variant page-indicator group.`},"@astryx.pagination.itemsPerPage":{defaultMessage:`Items per page`,description:`Label for the page-size selector.`},"@astryx.pagination.count":{defaultMessage:`{from, number}–{to, number} of {total, number}`,description:`Visible range-of-total text on a pagination bar. Example: "1–20 of 347"; the en-dash is translator's choice.`},"@astryx.pagination.pageOfTotal":{defaultMessage:`Page {current, number} of {total, number}`,description:`Visible "Page X of Y" text on the compact pagination variant; also announced by screen readers. Keep short — sits in a compact toolbar.`},"@astryx.pagination.pageAnnounce":{defaultMessage:`Page {current, number}`,description:`Screen-reader announcement when a page changes and total is unknown.`},"@astryx.powersearch.editor.field":{defaultMessage:`Field`,description:`Noun form-label above the field-picker dropdown in the PowerSearch filter-builder popover (which data column to filter on). Not an action.`},"@astryx.powersearch.editor.operator":{defaultMessage:`Operator`,description:`Noun form-label above the operator dropdown in the PowerSearch filter-builder popover. Refers to a comparison verb ("is", "contains"), not a math or phone operator.`},"@astryx.powersearch.editor.addFilter":{defaultMessage:`+ Add filter`,description:`Button label inside a group in the PowerSearch filter-builder; adds another filter row (e.g. "Status = Active"). The leading "+ " is a plus-sign character.`},"@astryx.powersearch.editor.removeFilter":{defaultMessage:`Remove filter`,description:`Screen-reader-only label on the "×" icon button next to a filter row in the PowerSearch editor; removes that row. Imperative verb.`},"@astryx.powersearch.editor.groupOperator":{defaultMessage:`Group operator`,description:`Screen-reader-only label for the AND/OR toggle that combines sibling filters inside a filter group. Sighted users see just "AND" or "OR".`},"@astryx.powersearch.editor.group":{defaultMessage:`Group`,description:`Fallback noun label shown on a nested filter-group chip when no AND/OR combining operator has been chosen. Use the noun ("a cluster"), not the verb "to group".`},"@astryx.powersearch.editor.delete":{defaultMessage:`Delete`,description:`Button label inside the PowerSearch filter-editor popover; deletes the currently-edited filter row. Imperative verb form.`},"@astryx.powersearch.editor.cancel":{defaultMessage:`Cancel`,description:`Button label inside the PowerSearch filter-editor popover; closes the popover and discards pending edits. Imperative verb form.`},"@astryx.powersearch.editor.apply":{defaultMessage:`Apply`,description:`Primary button label inside the PowerSearch filter-editor popover; confirms the edited filter. Imperative verb; consumers may override to "Save".`},"@astryx.powersearch.valueEditor.value":{defaultMessage:`Value`,description:'Noun form-label above a single free-text/number input in the PowerSearch value editor (e.g. the "acme" in `Name contains acme`). Not a verb or "worth".'},"@astryx.powersearch.valueEditor.values":{defaultMessage:`Values`,description:"Plural noun form-label above a multi-value chip input in the PowerSearch value editor. Should match its singular counterpart `Value` in your language."},"@astryx.powersearch.valueEditor.time":{defaultMessage:`Time`,description:`Noun form-label above a time-of-day (HH:MM) picker in the PowerSearch value editor. Clock time, not duration or era.`},"@astryx.powersearch.valueEditor.date":{defaultMessage:`Date`,description:`Noun form-label above a calendar-date picker in the PowerSearch value editor. Calendar date, not romantic date or fruit.`},"@astryx.powersearch.valueEditor.relativeDate":{defaultMessage:`Relative date`,description:`Label for the relative-date selector (e.g. "Last 7 days") in the PowerSearch value editor.`},"@astryx.powersearch.valueEditor.startDate":{defaultMessage:`Start date`,description:"Noun form-label above the start-of-range date picker in the PowerSearch value editor. Pairs with `End date` — keep the two parallel in your language."},"@astryx.powersearch.valueEditor.endDate":{defaultMessage:`End date`,description:"Noun form-label above the end-of-range date picker in the PowerSearch value editor. Pairs with `Start date` — keep the two parallel."},"@astryx.powersearch.valueEditor.entities":{defaultMessage:`Entities`,description:`"Entities" is jargon — plural noun form-label above an entity picker (people, teams, projects). Prefer a natural collective like "items" if your language has no equivalent.`},"@astryx.powersearch.valueEditor.searchPlaceholder":{defaultMessage:`Search…`,description:"Placeholder inside the search input in the PowerSearch entity/typeahead picker. Imperative verb; trailing `…` is one character."},"@astryx.powersearch.valueEditor.enterValuePlaceholder":{defaultMessage:`Enter value…`,description:"Placeholder inside a free-text single-value input in the PowerSearch value editor. Imperative verb; trailing `…` is one character."},"@astryx.powersearch.valueEditor.addValuesPlaceholder":{defaultMessage:`Add values…`,description:"Placeholder inside a multi-value chip input where the user types items and presses Enter to add each as a chip. Imperative verb; trailing `…` is one character."},"@astryx.powersearch.valueEditor.enterNumberPlaceholder":{defaultMessage:`Enter number…`,description:"Placeholder inside a numeric input in the PowerSearch value editor. Imperative verb; trailing `…` is one character."},"@astryx.powersearch.valueEditor.selectValuesPlaceholder":{defaultMessage:`Select values…`,description:`Placeholder on a dropdown for choosing values from a fixed enum list in the PowerSearch value editor. Imperative verb (user selects, not types).`},"@astryx.powersearch.operator.contains":{defaultMessage:`contains`,description:"PowerSearch string operator, rendered inline as ` contains ` (e.g. `Name contains acme`). Lowercase verb form."},"@astryx.powersearch.operator.notContains":{defaultMessage:`does not contain`,description:"PowerSearch negated string operator. Example: `Name does not contain test`. Lowercase; pairs with `contains`."},"@astryx.powersearch.operator.startsWith":{defaultMessage:`starts with`,description:"PowerSearch string prefix operator. Example: `Email starts with admin@`. Lowercase."},"@astryx.powersearch.operator.notStartsWith":{defaultMessage:`does not start with`,description:"PowerSearch negated prefix operator. Example: `Email does not start with test`. Lowercase; pairs with `starts with`."},"@astryx.powersearch.operator.endsWith":{defaultMessage:`ends with`,description:"PowerSearch string suffix operator. Example: `Email ends with @meta.com`. Lowercase."},"@astryx.powersearch.operator.notEndsWith":{defaultMessage:`does not end with`,description:"PowerSearch negated suffix operator. Example: `Email does not end with @gmail.com`. Lowercase; pairs with `ends with`."},"@astryx.powersearch.operator.is":{defaultMessage:`is`,description:"PowerSearch equality operator for strings/enums. Example: `Status is Active`. Separate from `operator.equals` (numbers) — translations may diverge."},"@astryx.powersearch.operator.isNot":{defaultMessage:`is not`,description:"PowerSearch inequality operator for strings/enums. Example: `Status is not Draft`. Pairs with `is`; separate from `operator.notEquals`."},"@astryx.powersearch.operator.equals":{defaultMessage:`is`,description:'PowerSearch numeric equality operator. Example: `Age is 30`. Ships same English "is" as `operator.is` but is separate so numbers may diverge (e.g. "equals").'},"@astryx.powersearch.operator.notEquals":{defaultMessage:`is not`,description:"PowerSearch numeric inequality operator. Example: `Count is not 0`. Same divergence option as `operator.equals`."},"@astryx.powersearch.operator.greaterThan":{defaultMessage:`is greater than`,description:"PowerSearch numeric operator, strictly greater than. Example: `Age is greater than 18`. Lowercase."},"@astryx.powersearch.operator.lessThan":{defaultMessage:`is less than`,description:"PowerSearch numeric operator, strictly less than. Example: `Priority is less than 5`. Lowercase."},"@astryx.powersearch.operator.greaterThanOrEqual":{defaultMessage:`is greater than or equal to`,description:'PowerSearch numeric operator, ≥. Example: `Age is greater than or equal to 21`. A shorter form (e.g. "≥") is fine if idiomatic.'},"@astryx.powersearch.operator.lessThanOrEqual":{defaultMessage:`is less than or equal to`,description:"PowerSearch numeric operator, ≤. Example: `Priority is less than or equal to 3`. A shorter form is fine if idiomatic."},"@astryx.powersearch.operator.before":{defaultMessage:`is before`,description:"PowerSearch date operator, strictly earlier. Example: `Created is before 2024-01-01`. Temporal, not spatial."},"@astryx.powersearch.operator.after":{defaultMessage:`is after`,description:"PowerSearch date operator, strictly later. Example: `Updated is after 2024-06-01`. Temporal."},"@astryx.powersearch.operator.between":{defaultMessage:`is between`,description:"PowerSearch date operator, inclusive range. Example: `Created is between 2024-01-01 and 2024-06-30`. The `and ` portion is composed separately."},"@astryx.powersearch.operator.isTrue":{defaultMessage:`is true`,description:"PowerSearch boolean operator: matches truthy. Example: `Is admin is true`. Pairs with `is false`; field may be affirmative or a yes/no question."},"@astryx.powersearch.operator.isFalse":{defaultMessage:`is false`,description:"PowerSearch boolean operator: matches falsy. Example: `Is admin is false`. Pairs with `is true`."},"@astryx.powersearch.operator.isAnyOf":{defaultMessage:`is any of`,description:"PowerSearch list operator: value is in the set. Example: `Status is any of [Active, Paused, Draft]`. The value list is composed separately; pairs with `is none of`."},"@astryx.powersearch.operator.isNoneOf":{defaultMessage:`is none of`,description:"PowerSearch negated list operator: value not in the set. Example: `Status is none of [Archived, Deleted]`. Pairs with `is any of`."},"@astryx.powersearch.valueEditor.itemsCount":{defaultMessage:`{count, number} {count, plural, one {item} other {items}}`,description:"Overflow summary on a compact filter chip when the list of selected items is too long. Example: `3 items` or `1 item`."},"@astryx.powersearch.valueEditor.entitiesCount":{defaultMessage:`{count, number} {count, plural, one {entity} other {entities}}`,description:"Overflow summary on a compact filter chip when the list of selected entities is too long. Example: `5 entities` or `1 entity`. Pair with `itemsCount` translation."},"@astryx.powersearch.valueEditor.dateRange":{defaultMessage:`date range`,description:"Fallback lowercase noun rendered inline in a filter chip when a date-range value can't be formatted (e.g. `Created is between date range`). Keep lowercase."},"@astryx.powersearch.valueEditor.filtersCount":{defaultMessage:`{count, number} {count, plural, one {filter} other {filters}}`,description:"Summary inside a filter chip when the value is a nested set of filters. Example: `3 filters` or `1 filter`."},"@astryx.powersearch.resultCount":{defaultMessage:`{count, number} {count, plural, one {result} other {results}}`,description:"Live result-count text next to the PowerSearch input, announced to screen readers on change. Example: `12 results`, `1 result`; keep compact."},"@astryx.alertDialog.cancel":{defaultMessage:`Cancel`,description:`Button label on the secondary/dismiss button of an AlertDialog (modal confirmation). Imperative verb; consumers usually override with task-specific text.`},"@astryx.appShell.mobileNavigation":{defaultMessage:`Mobile navigation`,description:`Screen-reader-only accessible name for the mobile-only navigation region on small viewports. "Mobile" = phone/tablet (small screen), not "movable".`},"@astryx.appShell.skipToContent":{defaultMessage:`Skip to content`,description:`Text of the skip link — the first focusable element on the page, visible only while keyboard-focused. Activating it jumps focus past the navigation to the main content area. Imperative verb; keep short.`},"@astryx.avatar.nameWithStatus":{defaultMessage:`{name}, {status}`,description:`Screen-reader accessible name for an Avatar showing a status indicator; composes the person's name with the status label, e.g. "Jane Doe, Online". {name} = the avatar's name/alt text, {status} = the status dot's label. Adjust separator and order per locale.`},"@astryx.avatarGroup.label":{defaultMessage:`Avatars`,description:`Screen-reader-only fallback name for a horizontal cluster of user avatar images. Plural noun; consumers usually override with "Team members", "Attendees", etc.`},"@astryx.avatarGroup.keyboardHint":{defaultMessage:`Use arrow keys to move between avatars`,description:`Screen-reader-only instruction attached (via aria-describedby) to a group of interactive avatars that share a single Tab stop. Tells keyboard users the Left/Right arrow keys move focus between the avatars. Only announced when the group has interactive (link/button) avatars.`},"@astryx.avatarGroup.overflow":{defaultMessage:`{count, number} more`,description:'Accessible name for the "+N" overflow indicator at the end of an AvatarGroup — announces how many additional avatars are not shown. Example: `5 more`. The visible "+N" text is unaffected; this is the aria-label only.'},"@astryx.banner.dismiss":{defaultMessage:`Dismiss`,description:`"Dismiss" = close/hide this notification (not "reject a person"). Tooltip on the small X button on a Banner, and its aria label when the banner's title is not plain text.`},"@astryx.banner.dismissTitled":{defaultMessage:`{dismiss} {title}`,description:"Aria label on the small X button on a Banner, naming which banner it closes so stacked banners are distinguishable. `{dismiss}` is the already-translated tooltip text from `banner.dismiss`; keep it verbatim in the message so visible and accessible labels match. `{title}` is the banner's own title text — example: `Dismiss Upload failed`. Reorder the placeholders freely."},"@astryx.calendar.previousMonth":{defaultMessage:`Previous month`,description:"Screen-reader-only label on the left-arrow button in a Calendar's month header (navigates one month back). Pairs with `calendar.nextMonth`."},"@astryx.calendar.nextMonth":{defaultMessage:`Next month`,description:"Screen-reader-only label on the right-arrow button in a Calendar's month header (navigates one month forward). Pairs with `calendar.previousMonth`."},"@astryx.calendar.daySelected":{defaultMessage:`{date}, selected`,description:'Accessible name for the Calendar day button that is the current single-mode selection. `{date}` is the localized full date, e.g. "Thursday, January 15, 2026". The trailing state word tells screen-reader users the focused day is selected.'},"@astryx.calendar.dayRangeStart":{defaultMessage:`{date}, range start`,description:"Accessible name for the Calendar day button that begins the selected date range (or the first pick of an in-progress range). `{date}` is the localized full date."},"@astryx.calendar.dayRangeEnd":{defaultMessage:`{date}, range end`,description:"Accessible name for the Calendar day button that ends the selected date range. `{date}` is the localized full date. Pairs with `calendar.dayRangeStart`."},"@astryx.calendar.dayRangeStartAndEnd":{defaultMessage:`{date}, range start and range end`,description:"Accessible name for a Calendar day button that both begins and ends a completed one-day range. `{date}` is the localized full date."},"@astryx.calendar.dayInRange":{defaultMessage:`{date}, in range`,description:"Accessible name for a Calendar day button strictly inside the selected date range (not an endpoint). `{date}` is the localized full date."},"@astryx.calendar.rangeStartAnnounce":{defaultMessage:`Start date {date}. Select an end date.`,description:"Screen-reader announcement after the first pick of a Calendar range selection. `{date}` is the localized full date. Prompts the user that a second pick completes the range."},"@astryx.calendar.rangeCompleteAnnounce":{defaultMessage:`Selected range: {start} to {end}.`,description:"Screen-reader announcement after the second pick completes a Calendar range selection. `{start}` and `{end}` are localized full dates in chronological order."},"@astryx.calendar.rangeClearedAnnounce":{defaultMessage:`Cleared start date {date}. Select a start date.`,description:"Screen-reader announcement when the user clicks the in-progress range start again, which clears it instead of completing a zero-length range. `{date}` is the localized full date."},"@astryx.carousel.label":{defaultMessage:`Carousel`,description:`Screen-reader-only fallback name for a horizontally-scrolling row of items. If "carousel" is unfamiliar in your locale, prefer the standard term (e.g. "slider").`},"@astryx.carousel.scrollLeft":{defaultMessage:`Scroll left`,description:"Screen-reader-only label on the left arrow button in a Carousel. Pairs with `carousel.scrollRight`; in RTL locales, coordinate the two so left/right match layout."},"@astryx.carousel.scrollRight":{defaultMessage:`Scroll right`,description:"Screen-reader-only label on the right arrow button in a Carousel. Pairs with `carousel.scrollLeft`; same RTL note."},"@astryx.carousel.slideLabel":{defaultMessage:`Slide {current, number} of {total, number}`,description:"Screen-reader accessible name for one slide in a Carousel, giving its position. `current` is the 1-based slide number; `total` is the slide count."},"@astryx.chat.status.sending":{defaultMessage:`Sending`,description:`Chat send-status caption under an outgoing message while it is being transmitted. Part of the set sending → sent → delivered → read (or failed) — keep tense/aspect consistent.`},"@astryx.chat.status.sent":{defaultMessage:`Sent`,description:`Chat send-status caption shown once the message reaches the server. Part of the set sending → **sent** → delivered → read (or failed) — keep tense consistent.`},"@astryx.chat.status.delivered":{defaultMessage:`Delivered`,description:`Chat send-status caption shown once the recipient's device received the message. Part of the set sending → sent → **delivered** → read (or failed).`},"@astryx.chat.status.read":{defaultMessage:`Read`,description:`Chat send-status caption shown once the recipient opened the message. English past-participle ("has been read", /rɛd/), not the present verb — part of the set sending → sent → delivered → **read**.`},"@astryx.chat.status.failed":{defaultMessage:`Failed`,description:`Chat send-status caption shown when the send attempt errored. Part of the set — the terminal failure branch, orthogonal to the sent → delivered → read success track.`},"@astryx.chat.messageAriaLabel":{defaultMessage:`Message {status}`,description:"Screen-reader-only accessible name for a chat message row. `{status}` interpolates the localized status word (e.g. `Message sent`, `Message delivered`) — reorder if needed."},"@astryx.chat.pastedText.expand":{defaultMessage:`Expand`,description:`Button label on a chip in the chat composer representing a long pasted text block; clicking reveals full content. "Expand" here means reveal more, not grow physically.`},"@astryx.checkboxList.item.checkbox":{defaultMessage:`Checkbox`,description:`Screen-reader-only last-ditch fallback name for a checkbox inside a list item when no label is provided. Should almost never render — consumers should supply a real label.`},"@astryx.commandPalette.emptySearch":{defaultMessage:`No results`,description:`Fallback empty-state text inside a CommandPalette when the user's query has no matches. Very short (2 words); neutral tone.`},"@astryx.commandPalette.emptyBootstrap":{defaultMessage:`Type to search`,description:`Onboarding empty-state text shown inside a CommandPalette on first open, before the user has typed anything. Imperative sentence fragment.`},"@astryx.commandPalette.resultCount":{defaultMessage:`{count, number} {count, plural, one {result} other {results}}`,description:"Screen-reader-only announcement of how many commands match the CommandPalette query as the user types. Example: `12 results`, `1 result`; keep compact."},"@astryx.commandPalette.noResultsFor":{defaultMessage:`No results for {query}`,description:"Screen-reader-only announcement when a CommandPalette query matches nothing. `{query}` is the user's verbatim search text; keep it last if your language allows so truncation-by-AT still conveys the outcome."},"@astryx.commandPalette.loading":{defaultMessage:`Loading`,description:`Screen-reader-only announcement that a CommandPalette search has started and results are being fetched. Present-progressive form; matches the visible spinner.`},"@astryx.dateRangeInput.presetDateRanges":{defaultMessage:`Preset date ranges`,description:`Screen-reader-only accessible name for the sidebar of quick-pick preset ranges inside a DateRangeInput popover (e.g. "Last 7 days", "This month").`},"@astryx.dateTimeInput.timePlaceholder":{defaultMessage:`Select a time`,description:`Grey placeholder inside the empty time-of-day slot in a DateTimeInput. "Time" = clock time (HH:MM), not duration.`},"@astryx.dialog.close":{defaultMessage:`Close`,description:`"Close" = shut/dismiss the dialog, not "nearby" (English homograph). Aria label AND tooltip on the X at the top-right of a Dialog.`},"@astryx.dropdownMenu.label":{defaultMessage:`Menu`,description:`Screen-reader-only fallback name for a dropdown menu popover. Very generic; consumers usually override. Noun ("a menu"), not the imperative.`},"@astryx.lightbox.close":{defaultMessage:`Close`,description:`"Close" = shut/dismiss (not "nearby"). Screen-reader-only label on the X button that dismisses a Lightbox.`},"@astryx.lightbox.previous":{defaultMessage:`Previous`,description:"Screen-reader-only label on the left-arrow button in a Lightbox (navigates to previous media item). Pairs with `lightbox.next`."},"@astryx.lightbox.next":{defaultMessage:`Next`,description:"Screen-reader-only label on the right-arrow button in a Lightbox (navigates to next media item). Pairs with `lightbox.previous`."},"@astryx.listInput.emptyTitle":{defaultMessage:`No {itemName}s yet`,description:'EmptyState title shown inside a lab ListInput when its collection has no records. `{itemName}` is the consumer\'s singular noun for one record (e.g. "guest"); the source appends a literal "s" to pluralize it, which only works for regular English plurals. If your language cannot pluralize an interpolated noun this way, rephrase around `{itemName}` instead (e.g. "No {itemName} added yet").'},"@astryx.listInput.emptyDescription":{defaultMessage:`Add a {itemName} to get started.`,description:'EmptyState supporting text shown inside a lab ListInput when its collection has no records. `{itemName}` is the consumer\'s singular noun for one record (e.g. "guest").'},"@astryx.listInput.addItem":{defaultMessage:`Add {itemName}`,description:'Accessible label on the button that appends a new record to a lab ListInput. `{itemName}` is the consumer\'s singular noun for one record (e.g. "guest").'},"@astryx.listInput.removeItem":{defaultMessage:`Remove {itemName} {position, number}`,description:'Accessible label and tooltip on the button that deletes one record from a lab ListInput. `{itemName}` is the consumer\'s singular noun for one record; `{position}` is its 1-based row number (e.g. "Remove guest 2").'},"@astryx.listInput.removeUnavailable":{defaultMessage:`Remove is unavailable while the list is disabled`,description:`Tooltip shown on the Remove button when the ListInput is disabled or loading, explaining why the action cannot be performed.`},"@astryx.listInput.reorderItem":{defaultMessage:`Reorder {itemName} {position, number}`,description:'Accessible label on the drag-handle button that reorders one record in a lab ListInput. `{itemName}` is the consumer\'s singular noun for one record; `{position}` is its 1-based row number (e.g. "Reorder guest 2").'},"@astryx.listInput.fieldLabelWithPosition":{defaultMessage:`{header}, {itemName} {position, number} of {total, number}`,description:'Accessible name for a field inside a lab ListInput row after the first row, disambiguating repeated column labels. `{header}` is the column\'s own label (e.g. "Name"); `{itemName}` is the consumer\'s singular noun for one record; `{position}`/`{total}` are the row\'s 1-based index and the total row count (e.g. "Name, guest 2 of 3").'},"@astryx.listInput.reorderInstructions":{defaultMessage:`Use Arrow Up or Arrow Down to move this item one position. Press Space or Enter to pick it up for extended keyboard reordering.`,description:`Visually-hidden instructions describing how to use a lab ListInput row's keyboard reorder handle, referenced via aria-describedby from every reorder button.`},"@astryx.listInput.announceAdded":{defaultMessage:`Added {itemName} {position, number}.`,description:"Screen-reader-only live announcement after a new record is appended to a lab ListInput. `{itemName}` is the consumer's singular noun for one record; `{position}` is the new record's 1-based row number."},"@astryx.listInput.announceRemoved":{defaultMessage:`Removed {itemName} {position, number}.`,description:"Screen-reader-only live announcement after a record is deleted from a lab ListInput. `{itemName}` is the consumer's singular noun for one record; `{position}` is the removed record's former 1-based row number."},"@astryx.listInput.announceGrabbed":{defaultMessage:`{itemName} {position, number} grabbed. Use arrow keys to move, Space or Enter to drop, and Escape to cancel.`,description:"Screen-reader-only live announcement when a lab ListInput record's keyboard reorder handle enters extended \"lift\" mode. `{itemName}` is the consumer's singular noun for one record; `{position}` is its 1-based row number."},"@astryx.listInput.announceMovedToPosition":{defaultMessage:`{itemName} moved to position {position, number} of {total, number}.`,description:"Screen-reader-only live announcement each time a lab ListInput record's reorder position changes (arrow-key step, keyboard lift-mode preview, or pointer drag). `{itemName}` is the consumer's singular noun for one record; `{position}`/`{total}` are the record's new 1-based position and the total row count."},"@astryx.listInput.announceReorderCancelled":{defaultMessage:`Reordering cancelled.`,description:`Screen-reader-only live announcement when a lab ListInput reorder in progress is cancelled (Escape key, blur, or the collection becoming disabled/loading mid-drag).`},"@astryx.listInput.announceReturnedToPosition":{defaultMessage:`{itemName} returned to position {position, number}.`,description:"Screen-reader-only live announcement when a lab ListInput reorder is committed without the record's position actually changing. `{itemName}` is the consumer's singular noun for one record; `{position}` is its unchanged 1-based row number."},"@astryx.listInput.announceDropped":{defaultMessage:`{itemName} dropped at position {position, number} of {total, number}.`,description:"Screen-reader-only live announcement when a lab ListInput reorder is committed with the record's position actually changing. `{itemName}` is the consumer's singular noun for one record; `{position}`/`{total}` are its new 1-based position and the total row count."},"@astryx.listInput.announceAlreadyAtBoundary":{defaultMessage:`This {itemName} is already {boundary, select, first {first} last {last} other {}}.`,description:'Screen-reader-only live announcement when an arrow-key reorder attempt has no effect because the record is already at that end of the list. `{itemName}` is the consumer\'s singular noun for one record; `{boundary}` is always exactly "first" or "last".'},"@astryx.markdown.taskList":{defaultMessage:`Task list`,description:"Screen-reader-only accessible name for a GitHub-flavored Markdown task list (rendered from `- [ ] item` / `- [x] done` syntax)."},"@astryx.markdown.table":{defaultMessage:`Table`,description:'Screen-reader-only accessible name for a table rendered inside a Markdown block. Noun ("a table"), not the verb. Separate from `@astryx.table.label` — translations may diverge.'},"@astryx.mobileNav.closeNavigation":{defaultMessage:`Close navigation`,description:"Screen-reader-only label on the X/close button that dismisses the MobileNav overlay. Pairs with `mobileNav.toggle.open`."},"@astryx.multiSelector.selectAll":{defaultMessage:`Select all`,description:`Label on the checkbox/toggle at the top of a MultiSelector dropdown that selects every option. "All" is a determiner here (as in "all the options"), not the pronoun.`},"@astryx.multiSelector.searchPlaceholder":{defaultMessage:`Search…`,description:"Placeholder inside the search input at the top of a MultiSelector's dropdown panel. Imperative verb; trailing `…` is one character."},"@astryx.multiSelector.searchOptions":{defaultMessage:`Search options`,description:`Screen-reader-only accessible name for that same search input inside a MultiSelector.`},"@astryx.multiSelector.empty":{defaultMessage:`No options`,description:`Shown in a MultiSelector's dropdown panel when it was given no options at all, and announced in a polite live region on open. Very short (2 words); neutral tone, not error-y.`},"@astryx.multiSelector.selectAllPartiallySelected":{defaultMessage:`{label}, partially selected`,description:'Accessible name for the MultiSelector select-all option while only some options are selected. `{label}` is the visible select-all label (e.g. "Select all"). ARIA forbids aria-selected="mixed" on options, so the indeterminate state is conveyed through the name instead. "Partially" = some but not all.'},"@astryx.popover.close":{defaultMessage:`Close popover`,description:`"Close" = shut/dismiss, not "nearby". Screen-reader-only label on the close button inside a Popover.`},"@astryx.selector.searchPlaceholder":{defaultMessage:`Search…`,description:"Placeholder inside the search input at the top of a Selector's dropdown panel (for filtering options). Imperative verb; trailing `…` is one character."},"@astryx.selector.searchOptions":{defaultMessage:`Search options`,description:`"Options" = the list of choices in the dropdown. Screen-reader-only accessible name for the search input inside a Selector.`},"@astryx.selector.empty":{defaultMessage:`No options`,description:`Shown in a Selector's dropdown panel when it was given no options at all, and announced in a polite live region on open. Very short (2 words); neutral tone, not error-y.`},"@astryx.sideNav.label":{defaultMessage:`Side navigation`,description:`Screen-reader-only accessible name for the primary vertical sidebar nav (usually on the left).`},"@astryx.sideNav.resizeSidebar":{defaultMessage:`Resize sidebar`,description:`Screen-reader-only label on the vertical drag handle at the right edge of the SideNav that lets the user resize the sidebar's width.`},"@astryx.sideNav.heading.openMenu":{defaultMessage:`Open menu`,description:"Screen-reader-only label on the `⋯` overflow-menu button embedded in a SideNav section heading. Same string as `topNav.heading.openMenu` — translations may share."},"@astryx.tabList.label":{defaultMessage:`Tabs`,description:`Screen-reader-only fallback name for a horizontal tab bar. Plural noun; "Tabs" here = UI tab panels, not browser tabs or the Tab key.`},"@astryx.table.label":{defaultMessage:`Table`,description:`Fallback screen-reader-only accessible name for a data-table region when the consumer provides none. Noun ("a table"), not the verb "to table".`},"@astryx.table.noData":{defaultMessage:`No data`,description:`Fallback empty-state text in the table body when there are zero rows. Neutral tone (not error-y); consumers commonly override with something specific like "No results".`},"@astryx.table.filter.allPlaceholder":{defaultMessage:`All`,description:`Placeholder on a per-column filter dropdown when nothing is selected, meaning "no filter — all rows match". Determiner form (as in "all values"), not the pronoun.`},"@astryx.table.filter.reset":{defaultMessage:`Reset`,description:`Button label inside a table's filter panel/popover; clears pending filter values back to defaults. Imperative verb.`},"@astryx.table.filter.apply":{defaultMessage:`Apply`,description:"Primary button label inside a table's filter panel/popover; commits pending filter values. Imperative verb; pairs with `Reset`."},"@astryx.table.rowStatus.columnHeader":{defaultMessage:`Row status`,description:`Screen-reader-only column header for the narrow status-indicator gutter a Table gains from useTableRowStatus. Sighted users see a blank gutter; assistive tech announces this as the column name.`},"@astryx.table.selection.selectAllRows":{defaultMessage:`Select all rows`,description:`Aria label for the "select all rows" checkbox in a Table header.`},"@astryx.table.selection.selectRow":{defaultMessage:`Select row`,description:`Aria label for the "select row" checkbox on a Table row.`},"@astryx.table.selection.selectRowNamed":{defaultMessage:`Select {label}`,description:`Aria label for a Table row's selection checkbox when a per-row label is available (via getRowLabel). \`label\` is the row's human-readable identity, e.g. "Alice".`},"@astryx.table.sort.ascending":{defaultMessage:`Sort ascending`,description:"Screen-reader-only label on a column header button that will sort the column ascending. Part of a set with `sort.descending` and `sort.clear` — keep parallel."},"@astryx.table.sort.descending":{defaultMessage:`Sort descending`,description:"Screen-reader-only label on a column header button that will sort the column descending. Part of a set with `sort.ascending` and `sort.clear`."},"@astryx.table.sort.clear":{defaultMessage:`Clear sort`,description:`Screen-reader-only label on a column header button that will remove the current sort. "Clear" here means remove, not transparent. Part of the sort set.`},"@astryx.table.sort.direction.ascending":{defaultMessage:`ascending`,description:"Localized direction word interpolated as `direction` into @astryx.table.sort.sortedBy and @astryx.table.sort.sortedByWithPriority."},"@astryx.table.sort.direction.descending":{defaultMessage:`descending`,description:"Localized direction word interpolated as `direction` into @astryx.table.sort.sortedBy and @astryx.table.sort.sortedByWithPriority."},"@astryx.table.sort.sortBy":{defaultMessage:`Sort by {label}`,description:"Aria label for a sortable Table header button when the column is unsorted. `label` is the column header text."},"@astryx.table.sort.sortedBy":{defaultMessage:`Sort by {label}, sorted {direction}`,description:"Aria label for a sorted Table header button. `direction` is the localized direction word from @astryx.table.sort.direction.*."},"@astryx.table.sort.sortedByWithPriority":{defaultMessage:`Sort by {label}, sorted {direction}, priority {rank, number} of {total, number}`,description:"Aria label for a sorted Table header button in multi-sort. `rank` is the 1-based position of this column in the sort order; `total` is the number of sorted columns."},"@astryx.toast.dismiss":{defaultMessage:`Dismiss notification`,description:"Screen-reader-only label on the X button of a Toast (transient notification popup). Distinct from `banner.dismiss` (persistent banner)."},"@astryx.toast.viewport":{defaultMessage:`Notifications`,description:`Screen-reader-only accessible name for the invisible landmark region hosting the stack of Toast popups (usually pinned to a screen corner).`},"@astryx.tokenizer.clearAll":{defaultMessage:`Clear all`,description:`Label on the "×" button that removes every token/chip from a Tokenizer input. Imperative verb + determiner "all"; short.`},"@astryx.topNav.heading.openMenu":{defaultMessage:`Open menu`,description:"Screen-reader-only label on the `⋯` overflow button in a TopNav section heading. Kept separate from `sideNav.heading.openMenu` so translations may diverge."},"@astryx.topNav.landmarkLabel":{defaultMessage:`Top navigation`,description:`Default accessible name (aria-label) for the